diff --git a/.gitignore b/.gitignore index 79c70512..563e7a4e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ **/.lake # Rust -/target +**/target # Nix result* diff --git a/Benchmarks/CompileInit.lean b/Benchmarks/CompileInit.lean new file mode 100644 index 00000000..2a073666 --- /dev/null +++ b/Benchmarks/CompileInit.lean @@ -0,0 +1,3 @@ +import Init + +def main : IO Unit := pure () diff --git a/Cargo.lock b/Cargo.lock index 7de24578..e6e1f132 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "aiur" +version = "0.1.0" +dependencies = [ + "indexmap", + "multi-stark", + "rayon", + "rustc-hash", + "tracing", + "tracing-texray", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -61,6 +73,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + [[package]] name = "anyhow" version = "1.0.102" @@ -163,6 +225,14 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bignat" +version = "0.1.0" +source = "git+https://github.com/argumentcomputer/lean-ffi.git?rev=839b405de708b80504fda39abe1402114b4135a5#839b405de708b80504fda39abe1402114b4135a5" +dependencies = [ + "num-bigint", +] + [[package]] name = "bincode" version = "2.0.1" @@ -299,17 +369,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chacha20" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "rand_core 0.10.1", -] - [[package]] name = "chrono" version = "0.4.44" @@ -352,6 +411,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "const-oid" version = "0.10.2" @@ -794,7 +859,10 @@ version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ + "anstream", + "anstyle", "env_filter", + "jiff", "log", ] @@ -1694,6 +1762,12 @@ dependencies = [ "z32", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.13.0" @@ -1719,10 +1793,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] -name = "ix_rs" +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", + "rayon", + "rustc-hash", + "sha2 0.10.9", +] + +[[package]] +name = "ix-ffi" version = "0.1.0" dependencies = [ + "aiur", "anyhow", + "bignat", "bincode", "blake3", "bytes", @@ -1732,15 +1839,15 @@ dependencies = [ "iroh", "iroh-base", "itertools 0.14.0", + "ix-common", + "ix-compile", + "ix-kernel", + "ixon", "lean-ffi", - "memmap2", "mimalloc", "multi-stark", "n0-error", "num-bigint", - "quickcheck", - "quickcheck_macros", - "rand 0.10.1", "rayon", "rustc-hash", "serde", @@ -1752,6 +1859,69 @@ dependencies = [ "tracing-texray", ] +[[package]] +name = "ix-kernel" +version = "0.1.0" +dependencies = [ + "bignat", + "blake3", + "dashmap", + "env_logger", + "indexmap", + "itertools 0.14.0", + "ix-common", + "ixon", + "log", + "num-bigint", + "quickcheck", + "quickcheck_macros", + "rayon", + "rustc-hash", +] + +[[package]] +name = "ixon" +version = "0.1.0" +dependencies = [ + "bignat", + "blake3", + "dashmap", + "indexmap", + "ix-common", + "memmap2", + "num-bigint", + "quickcheck", + "quickcheck_macros", + "rayon", + "rustc-hash", + "sha2 0.10.9", + "tiny-keccak", +] + +[[package]] +name = "jiff" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "js-sys" version = "0.3.97" @@ -1773,8 +1943,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lean-ffi" version = "0.1.0" -source = "git+https://github.com/argumentcomputer/lean-ffi?rev=cc98ebf67bf453ac3827cb767f78b13ea674dd6a#cc98ebf67bf453ac3827cb767f78b13ea674dd6a" +source = "git+https://github.com/argumentcomputer/lean-ffi.git?rev=839b405de708b80504fda39abe1402114b4135a5#839b405de708b80504fda39abe1402114b4135a5" dependencies = [ + "bignat", "bindgen", "cc", "num-bigint", @@ -2356,6 +2527,12 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -2768,6 +2945,15 @@ dependencies = [ "serde", ] +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + [[package]] name = "portmapper" version = "0.15.0" @@ -2998,7 +3184,6 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "chacha20", "getrandom 0.4.2", "rand_core 0.10.1", ] @@ -3973,6 +4158,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.23.1" diff --git a/Cargo.toml b/Cargo.toml index 58b90be3..c0b0b64f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,57 +1,63 @@ -[package] -name = "ix_rs" +[workspace] +members = [ + "crates/aiur", + "crates/common", + "crates/compile", + "crates/ffi", + "crates/ixon", + "crates/kernel", +] +# `zisk/` and `sp1/` are their own Cargo workspaces (guest + host) built via +# the respective zkVM toolchains; excluded so host workspace ops don't pick +# them up. +exclude = ["zisk", "sp1"] +resolver = "2" + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" + +[workspace.package] version = "0.1.0" edition = "2024" +license = "MIT OR Apache-2.0" + +[workspace.dependencies] +# Internal crates +aiur = { path = "crates/aiur" } +ix-common = { path = "crates/common" } +ix-compile = { path = "crates/compile" } +ixon = { path = "crates/ixon" } +ix-kernel = { path = "crates/kernel" } -[lib] -crate-type = ["staticlib"] +# lean-ffi tree (lean-ffi crate + factored-out bignat sub-crate) +bignat = { git = "https://github.com/argumentcomputer/lean-ffi.git", rev = "839b405de708b80504fda39abe1402114b4135a5" } +lean-ffi = { git = "https://github.com/argumentcomputer/lean-ffi.git", rev = "839b405de708b80504fda39abe1402114b4135a5" } -[dependencies] +# External shared deps anyhow = "1" blake3 = "1.8.4" +dashmap = "6.1.0" +indexmap = "2" itertools = "0.14.0" -indexmap = { version = "2", features = ["rayon"] } -lean-ffi = { git = "https://github.com/argumentcomputer/lean-ffi", rev = "cc98ebf67bf453ac3827cb767f78b13ea674dd6a" } +log = "0.4" +memmap2 = "0.9" mimalloc = { version = "0.1", default-features = false } multi-stark = { git = "https://github.com/argumentcomputer/multi-stark.git", rev = "9ecab51d553445c0cc7b571af00a76b8a83a6f8c" } num-bigint = "0.4.6" +quickcheck = "1.0.3" +quickcheck_macros = "1.0.0" rayon = "1" rustc-hash = "2" -tiny-keccak = { version = "2", features = ["keccak"] } -dashmap = { version = "6.1.0", features = ["rayon"] } -memmap2 = "0.9" sha2 = "0.10" -# 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 } +tiny-keccak = { version = "2", features = ["keccak"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-texray = { git = "https://github.com/argumentcomputer/tracing-texray", rev = "8ce04e3422cd48e68ef47fab95dba7d06b8c368c" } -bincode = { version = "2.0.1", optional = true } -serde = { version = "1.0.219", features = ["derive"], optional = true } - -[dev-dependencies] -quickcheck = "1.0.3" -rand = "0.10.1" -quickcheck_macros = "1.0.0" - -[features] -default = [] -parallel = ["multi-stark/parallel"] -test-ffi = [] -net = ["bytes", "tokio", "iroh", "iroh-base", "n0-error", "getrandom", "bincode", "serde" ] - -[profile.dev] -panic = "abort" - -[profile.release] -panic = "abort" -[lints.rust] +[workspace.lints.rust] invalid_reference_casting = "warn" nonstandard_style = "warn" rust_2018_idioms = { level = "warn", priority = -1 } @@ -60,7 +66,7 @@ unreachable_pub = "warn" unused_lifetimes = "warn" unused_qualifications = "warn" -[lints.clippy] +[workspace.lints.clippy] all = { level = "warn", priority = -1 } cast_lossless = "warn" cast_possible_truncation = "warn" diff --git a/Ix/Cli/CompileCmd.lean b/Ix/Cli/CompileCmd.lean index a00f95e5..0fcb5713 100644 --- a/Ix/Cli/CompileCmd.lean +++ b/Ix/Cli/CompileCmd.lean @@ -34,6 +34,8 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do let pathStr := path.as! String let outPath : String := (p.flag? "out").map (·.as! String) |>.getD (defaultOutPathFor pathStr) + let rootName? : Option Lean.Name := + (p.flag? "root").map (·.as! String |>.toName) buildFile pathStr let leanEnv ← getFileEnv pathStr diff --git a/Ix/Meta.lean b/Ix/Meta.lean index 41cfa980..4430c52e 100644 --- a/Ix/Meta.lean +++ b/Ix/Meta.lean @@ -88,17 +88,41 @@ def fetchMathlibCache (cwd : Option FilePath) : IO Unit := do if exitCode != 0 then throw $ IO.userError "lake exe cache get failed" +/-- Walk up from `start` looking for `lake-manifest.json`. -/ +partial def findLakeRoot (start : FilePath) : IO (Option FilePath) := do + if ← (start / "lake-manifest.json").pathExists then + return some start + match start.parent with + | none => return none + | some p => if p == start then return none else findLakeRoot p + +/-- Walk up from `cur` collecting directory names until reaching `root`, +yielding the path components between them (in top-down order). -/ +partial def collectRelParts (root cur : FilePath) (acc : List String) : Option (List String) := + if cur == root then some acc + else match cur.fileName, cur.parent with + | some name, some par => + if par == cur then none else collectRelParts root par (name :: acc) + | _, _ => none + /-- Build the Lean module at the given file path using Lake. Also fetches Mathlib cache if the project depends on it. -/ def buildFile (path : FilePath) : IO Unit := do let path ← IO.FS.realPath path - let some moduleName := path.fileStem + let some stem := path.fileStem | throw $ IO.userError s!"cannot determine module name from {path}" - fetchMathlibCache path.parent + let some parent := path.parent + | throw $ IO.userError s!"cannot determine parent of {path}" + let some root ← findLakeRoot parent + | throw $ IO.userError s!"no lake-manifest.json found at or above {parent}" + let some relParts := collectRelParts root parent [] + | throw $ IO.userError s!"{path} is not under {root}" + let moduleName := ".".intercalate (relParts ++ [stem]) + fetchMathlibCache root let child ← IO.Process.spawn { cmd := "lake" args := #["build", moduleName] - cwd := path.parent + cwd := root stdout := .inherit stderr := .inherit } diff --git a/README.md b/README.md index 0f7af7e6..23224f1c 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,204 @@ Compiler performance benchmarks are tracked at https://bencher.dev/console/proje **Rust tests:** `cargo test` or `cargo nextest run` +### Proving under SP1 + +The Ix kernel typechecker has an SP1 guest at `sp1/guest/` driven by a host at +`sp1/host/`. The workflow is two steps: first compile a Lean program to a `.ixe` +serialized `Ixon.Env`, then feed that file to the SP1 host to either execute or +prove the typecheck. + +**Nix users only:** enter the SP1 dev shell first to pick up `cargo-prove` and +the succinct Rust toolchain: + +``` +nix develop .#sp1 +``` + +Non-Nix users: install the SP1 toolchain manually per the +[SP1 docs](https://docs.succinct.xyz/docs/sp1/getting-started/install). + +1. **Compile a `.ixe` from a Lean file.** Pass `--path` to the Lean source, + `--root` to slice the environment to the transitive closure of one constant, + and `--out` for the output path. For example, to compile + `Nat.add_comm` from the benchmark file: + + ``` + lake exe ix compile --path Benchmarks/CheckNatAddComm.lean \ + --root Nat.add_comm \ + --out nataddcomm.ixe + ``` + + Omitting `--root` emits the entire environment; omitting `--out` defaults to + the lowercased input stem plus `.ixe` (e.g. `checknataddcomm.ixe`). + +2. **Execute or prove under SP1.** From `sp1/host/`, run the host with `--ixe` + pointing at the file produced above: + + ``` + cd sp1/host + # Execute the kernel typecheck in the SP1 VM (no proof), prints failures + cycles + RUST_LOG=info cargo run --release -- --execute --ixe ../../nataddcomm.ixe + # Generate and verify a compressed SP1 proof of the same typecheck (CPU) + RUST_LOG=info cargo run --release -- --ixe ../../nataddcomm.ixe + ``` + + With no `--ixe`, the host runs against an empty `Ixon.Env`. + + **Kernel modes.** The default is Anon mode (metadata-erased kernel with + lazy on-demand ingress — matches Aiur's `kernel_check_test` semantics). + Pass `--meta` to run Meta mode (preserves names + dup-level-param check; + eager full-env ingress). Both prove the same structural typecheck; Meta + is strictly more constrained but slightly more expensive in cycles. + + **GPU proving.** Build with the `cuda` feature and set `SP1_PROVER=cuda` + at runtime: + + ``` + SP1_PROVER=cuda RUST_LOG=info cargo run --release --features cuda -- \ + --ixe ../../nataddcomm.ixe + ``` + + See the [SP1 CUDA docs](https://docs.succinct.xyz/docs/sp1/generating-proofs/hardware-acceleration/cuda) + for prerequisites (Docker + an NVIDIA GPU with CUDA 12+). + +### Proving under Zisk + +The Ix kernel typechecker also has a Zisk guest at `zisk/guest/` driven by a +host at `zisk/host/`. The workflow mirrors the SP1 one — first compile a Lean +program to a `.ixe`, then feed it to the Zisk host to either execute or prove +the typecheck. + +**Nix users only:** enter the Zisk dev shell first to pick up `cargo-zisk`, +`ziskemu`, and the RISC-V toolchain needed to build the guest: + +``` +nix develop .#zisk +``` + +Non-Nix users: install Zisk manually per the +[Zisk install docs](https://0xpolygonhermez.github.io/zisk/getting_started/installation.html). + +1. **Compile a `.ixe` from a Lean file.** Same as for SP1: + + ``` + lake exe ix compile --path Benchmarks/CheckNatAddComm.lean \ + --root Nat.add_comm \ + --out nataddcomm.ixe + ``` + +2. **Execute or prove under Zisk.** From `zisk/`, run the host with `--ixe`: + + ``` + cd zisk + # Execute the kernel typecheck in the Zisk VM (no proof), prints cycles + RUST_LOG=info cargo run --release -- --execute --ixe ../nataddcomm.ixe + # Run the constraint checker without generating a proof + RUST_LOG=info cargo run --release -- --verify-constraints --ixe ../nataddcomm.ixe + # Generate and verify a VadcopFinal proof of the same typecheck (CPU) + RUST_LOG=info cargo run --release -- --ixe ../nataddcomm.ixe + ``` + + With no `--ixe`, the host runs against an empty `Ixon.Env`. The host's + `build.rs` invokes `zisk_sdk::build_program("../guest")`, so `cargo run` + transparently rebuilds the guest ELF whenever its sources change. + + **Kernel modes.** Same `--meta` flag as the SP1 host (default is Anon + with lazy on-demand ingress; `--meta` switches to Meta with eager + full-env ingress). + + **GPU proving.** Pass `--gpu` at runtime — Zisk's prover backend ships + with CUDA support by default (the `cpu-only` Cargo feature is opt-out + and is not set here): + + ``` + RUST_LOG=info cargo run --release -- --gpu --ixe ../nataddcomm.ixe + ``` + + Requires a CUDA-capable GPU and the matching CUDA runtime libraries + visible to the linker (`LD_LIBRARY_PATH`). The Nix `.#zisk` shell wires + these up automatically; for non-Nix setups follow the + [Zisk install docs](https://0xpolygonhermez.github.io/zisk/getting_started/installation.html). + + **Memlock limit (Linux).** Zisk's assembly emulator `mmap`s ROM and + trace buffers with `MAP_LOCKED`, so the per-process locked-memory + rlimit (`ulimit -l`) must be high enough to back the trace. Many + Linux distros default to a low memlock limit; the Linux kernel + itself sets non-privileged processes to `RAM / 8`, which is still + too small for larger envs. Symptom: `mmap(rom) errno=11=Resource + temporarily unavailable` followed by `Shmem creation … failed with + exit status: 255` during `STARTING_ASM_MICROSERVICES`. + + *Per-shell (does not survive new logins or reboot):* + + ``` + sudo prlimit --memlock=unlimited:unlimited --pid $$ + ulimit -l # confirm: prints 'unlimited' + ``` + + *Persistent (recommended; both edits needed because `systemd` and + PAM apply rlimits on different paths — `DefaultLimitMEMLOCK` only + reaches services systemd spawns directly, interactive shells go + through PAM):* + + ``` + # 1. systemd-spawned services + sudo sed -i 's|^#DefaultLimitMEMLOCK=.*|DefaultLimitMEMLOCK=infinity|' \ + /etc/systemd/system.conf + + # 2. PAM session limits (applied to login shells, sudo, etc.) + sudo tee /etc/security/limits.d/99-memlock.conf >/dev/null <<'EOF' + * soft memlock unlimited + * hard memlock unlimited + EOF + + # Apply: log out of every login shell and back in (no reboot needed). + # Verify in a new shell: + ulimit -l # → unlimited + cat /proc/$$/limits | grep "locked" # → unlimited / unlimited + ``` + + Matches the Zisk + [installation docs](https://0xpolygonhermez.github.io/zisk/getting_started/installation.html). + + **Heap cap.** The Zisk zkVM has a hard 512 MB RAM cap + ([`RAM_SIZE`](https://github.com/0xPolygonHermez/zisk/blob/v0.17.0/core/src/mem.rs#L111)), + of which ~510 MB is usable heap, and isn't configurable without + rebuilding the proving setup. Envs whose deserialized in-memory + representation exceeds that won't fit (full `TutorialDefs.lean` pulls in + Lean stdlib + Batteries + LSpec, around 1 GB resident). For bigger envs, + prefer the SP1 backend (default 24 GB runtime cap + [`DEFAULT_MEMORY_LIMIT`](https://github.com/succinctlabs/sp1/blob/v6.2.0/crates/core/executor/src/opts.rs#L25), + configurable via `MEMORY_LIMIT` env var up to a ~1 TB JIT ceiling + [`MAX_JIT_LOG_ADDR`](https://github.com/succinctlabs/sp1/blob/v6.2.0/crates/primitives/src/consts.rs#L11)), + or shrink the env via `--root ` to take the transitive closure of + a single constant. + + **Host RAM cap (`--max-witness-stored`).** Distinct from the in-guest + heap cap above, the prover side (Zisk's `proofman`) holds in-flight + witness traces in host RAM during `CALCULATING_CONTRIBUTIONS`. Peak + host RAM per shard ≈ `N × avg-witness-size + fixed overhead`, where + `N` is the `max_witness_stored` setting. The Ix kernel typecheck + workload averages ~25 GB per witness on typical 200–300 kB anon-byte + shards. + + The `zisk-host` CLI defaults to `--max-witness-stored 5` (Zisk's + built-in default is 10, tuned for larger-memory boxes). Override per + machine: + + | Host RAM | `--max-witness-stored` | Notes | + | -------- | ---------------------- | ------------------------------------------------------ | + | ≤ 128 GB | `3` | Override down; consider smaller shards too | + | 256 GB | `5` (project default) | Comfortable margin on the typical setup | + | 512 GB | `10` (Zisk default) | Override up for maximum prover parallelism | + | ≥ 1 TB | `10` (Zisk default) | Override up; default is conservative for this workload | + + Lowering the cap roughly linearly bounds peak RAM but throttles + prover parallelism (~10–30 % slower in practice). Raise it if your + machine has more RAM headroom; lower it if you OOM during + `CALCULATING_CONTRIBUTIONS`. Not relevant for `--execute` or + `--verify-constraints` modes. + ### Nix #### Prerequisites diff --git a/Tests/MinimalDefs.lean b/Tests/MinimalDefs.lean new file mode 100644 index 00000000..a83067cc --- /dev/null +++ b/Tests/MinimalDefs.lean @@ -0,0 +1,5 @@ +def myConst : Nat := 42 + +def myId (x : Nat) : Nat := x + +theorem myReflEq (n : Nat) : n = n := rfl diff --git a/crates/aiur/Cargo.toml b/crates/aiur/Cargo.toml new file mode 100644 index 00000000..c8d59ee0 --- /dev/null +++ b/crates/aiur/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "aiur" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +indexmap = { workspace = true } +multi-stark = { workspace = true } +rayon = { workspace = true } +rustc-hash = { workspace = true } +tracing = { workspace = true } +tracing-texray = { workspace = true } + +[features] +default = [] +parallel = ["multi-stark/parallel"] + +[lints] +workspace = true diff --git a/src/aiur/bytecode.rs b/crates/aiur/src/bytecode.rs similarity index 81% rename from src/aiur/bytecode.rs rename to crates/aiur/src/bytecode.rs index cb2769d1..927a3b40 100644 --- a/src/aiur/bytecode.rs +++ b/crates/aiur/src/bytecode.rs @@ -3,23 +3,23 @@ use crate::FxIndexMap; use super::G; pub struct Toplevel { - pub(crate) functions: Vec, - pub(crate) memory_sizes: Vec, + pub functions: Vec, + pub memory_sizes: Vec, } pub struct Function { - pub(crate) body: Block, - pub(crate) layout: FunctionLayout, - pub(crate) entry: bool, - pub(crate) constrained: bool, + pub body: Block, + pub layout: FunctionLayout, + pub entry: bool, + pub constrained: bool, } #[derive(Clone, Copy)] pub struct FunctionLayout { - pub(crate) input_size: usize, - pub(crate) selectors: usize, - pub(crate) auxiliaries: usize, - pub(crate) lookups: usize, + pub input_size: usize, + pub selectors: usize, + pub auxiliaries: usize, + pub lookups: usize, } impl FunctionLayout { @@ -29,8 +29,8 @@ impl FunctionLayout { } pub struct Block { - pub(crate) ops: Vec, - pub(crate) ctrl: Ctrl, + pub ops: Vec, + pub ctrl: Ctrl, } pub enum Op { diff --git a/src/aiur/constraints.rs b/crates/aiur/src/constraints.rs similarity index 97% rename from src/aiur/constraints.rs rename to crates/aiur/src/constraints.rs index abdbef2b..67827e1d 100644 --- a/src/aiur/constraints.rs +++ b/crates/aiur/src/constraints.rs @@ -7,22 +7,18 @@ use multi_stark::{ use std::{array, ops::Range, sync::LazyLock}; use crate::{ - FxIndexMap, - aiur::{ - G, - bytecode::{Block, Ctrl, Function, FunctionLayout, Op, Toplevel, ValIdx}, - function_channel, - gadgets::{ - AiurGadget, - bytes1::{Bytes1, Bytes1Op}, - bytes2::{Bytes2, Bytes2Op}, - }, - memory_channel, u8_add_channel, u8_and_channel, - u8_bit_decomposition_channel, u8_chain_rotr4_channel, - u8_chain_rotr7_channel, u8_less_than_channel, u8_mul_channel, - u8_or_channel, u8_range_check_channel, u8_shift_left_channel, - u8_shift_right_channel, u8_sub_channel, u8_xor_channel, + FxIndexMap, G, + bytecode::{Block, Ctrl, Function, FunctionLayout, Op, Toplevel, ValIdx}, + function_channel, + gadgets::{ + AiurGadget, + bytes1::{Bytes1, Bytes1Op}, + bytes2::{Bytes2, Bytes2Op}, }, + memory_channel, u8_add_channel, u8_and_channel, u8_bit_decomposition_channel, + u8_chain_rotr4_channel, u8_chain_rotr7_channel, u8_less_than_channel, + u8_mul_channel, u8_or_channel, u8_range_check_channel, u8_shift_left_channel, + u8_shift_right_channel, u8_sub_channel, u8_xor_channel, }; type Expr = SymbolicExpression; diff --git a/src/aiur/execute.rs b/crates/aiur/src/execute.rs similarity index 97% rename from src/aiur/execute.rs rename to crates/aiur/src/execute.rs index e28dae6e..cafd61bc 100644 --- a/src/aiur/execute.rs +++ b/crates/aiur/src/execute.rs @@ -3,28 +3,25 @@ use rustc_hash::FxHashMap; use std::collections::hash_map::Entry; use crate::{ - FxIndexMap, - aiur::{ - G, - bytecode::{Block, Ctrl, FunIdx, Function, Op, Toplevel}, - gadgets::{ - AiurGadget, - bytes1::{Bytes1, Bytes1Op, Bytes1Queries}, - bytes2::{Bytes2, Bytes2Op, Bytes2Queries}, - }, + FxIndexMap, G, + bytecode::{Block, Ctrl, FunIdx, Function, Op, Toplevel}, + gadgets::{ + AiurGadget, + bytes1::{Bytes1, Bytes1Op, Bytes1Queries}, + bytes2::{Bytes2, Bytes2Op, Bytes2Queries}, }, }; pub struct QueryResult { pub(crate) output: Vec, - pub(crate) multiplicity: G, + pub multiplicity: G, } pub type QueryMap = FxIndexMap, QueryResult>; pub struct QueryRecord { - pub(crate) function_queries: Vec, - pub(crate) memory_queries: FxIndexMap, + pub function_queries: Vec, + pub memory_queries: FxIndexMap, pub(crate) bytes1_queries: Bytes1Queries, pub(crate) bytes2_queries: Bytes2Queries, } @@ -44,17 +41,17 @@ impl QueryRecord { } } -pub(crate) struct IOKeyInfo { - pub(crate) idx: usize, - pub(crate) len: usize, +pub struct IOKeyInfo { + pub idx: usize, + pub len: usize, } pub struct IOBuffer { /// Per-channel data arenas. `idx` slots into `data[&channel]`. - pub(crate) data: FxHashMap>, + pub data: FxHashMap>, /// Channel-keyed info map; same `key` on different channels resolves /// to distinct `IOKeyInfo`. - pub(crate) map: FxHashMap<(G, Vec), IOKeyInfo>, + pub map: FxHashMap<(G, Vec), IOKeyInfo>, } impl IOBuffer { diff --git a/src/aiur/gadgets.rs b/crates/aiur/src/gadgets.rs similarity index 97% rename from src/aiur/gadgets.rs rename to crates/aiur/src/gadgets.rs index 2a3ca95f..a71c2b9d 100644 --- a/src/aiur/gadgets.rs +++ b/crates/aiur/src/gadgets.rs @@ -6,7 +6,7 @@ use multi_stark::{ p3_matrix::dense::RowMajorMatrix, }; -use crate::aiur::{G, execute::QueryRecord}; +use crate::{G, execute::QueryRecord}; /// A trait representing a generic Aiur gadget. /// diff --git a/src/aiur/gadgets/blake3.rs b/crates/aiur/src/gadgets/blake3.rs similarity index 99% rename from src/aiur/gadgets/blake3.rs rename to crates/aiur/src/gadgets/blake3.rs index 434f43b5..9fd508f9 100644 --- a/src/aiur/gadgets/blake3.rs +++ b/crates/aiur/src/gadgets/blake3.rs @@ -14,7 +14,7 @@ use multi_stark::{ p3_matrix::dense::RowMajorMatrix, }; -use crate::aiur::{ +use crate::{ G, execute::QueryRecord, gadgets::{ diff --git a/src/aiur/gadgets/bytes1.rs b/crates/aiur/src/gadgets/bytes1.rs similarity index 99% rename from src/aiur/gadgets/bytes1.rs rename to crates/aiur/src/gadgets/bytes1.rs index 1cc6525b..cc83c5c9 100644 --- a/src/aiur/gadgets/bytes1.rs +++ b/crates/aiur/src/gadgets/bytes1.rs @@ -6,7 +6,7 @@ use multi_stark::{ p3_matrix::dense::RowMajorMatrix, }; -use crate::aiur::{ +use crate::{ G, execute::QueryRecord, gadgets::AiurGadget, u8_bit_decomposition_channel, u8_shift_left_channel, u8_shift_right_channel, }; diff --git a/src/aiur/gadgets/bytes2.rs b/crates/aiur/src/gadgets/bytes2.rs similarity index 99% rename from src/aiur/gadgets/bytes2.rs rename to crates/aiur/src/gadgets/bytes2.rs index d8478aad..949dcff8 100644 --- a/src/aiur/gadgets/bytes2.rs +++ b/crates/aiur/src/gadgets/bytes2.rs @@ -6,7 +6,7 @@ use multi_stark::{ p3_matrix::dense::RowMajorMatrix, }; -use crate::aiur::{ +use crate::{ G, execute::QueryRecord, gadgets::AiurGadget, u8_add_channel, u8_and_channel, u8_chain_rotr4_channel, u8_chain_rotr7_channel, u8_less_than_channel, u8_mul_channel, u8_or_channel, u8_range_check_channel, u8_sub_channel, diff --git a/src/aiur.rs b/crates/aiur/src/lib.rs similarity index 91% rename from src/aiur.rs rename to crates/aiur/src/lib.rs index d54325cc..c0494edf 100644 --- a/src/aiur.rs +++ b/crates/aiur/src/lib.rs @@ -6,9 +6,12 @@ pub mod memory; pub mod synthesis; pub mod trace; +use indexmap::IndexMap; use multi_stark::p3_field::PrimeCharacteristicRing; +use rustc_hash::FxBuildHasher; pub type G = multi_stark::p3_goldilocks::Goldilocks; +pub type FxIndexMap = IndexMap; #[inline] pub const fn function_channel() -> G { diff --git a/src/aiur/memory.rs b/crates/aiur/src/memory.rs similarity index 98% rename from src/aiur/memory.rs rename to crates/aiur/src/memory.rs index fa676394..d6e74ff3 100644 --- a/src/aiur/memory.rs +++ b/crates/aiur/src/memory.rs @@ -13,7 +13,7 @@ use rayon::{ slice::ParallelSliceMut, }; -use crate::aiur::{G, execute::QueryRecord, memory_channel}; +use crate::{G, execute::QueryRecord, memory_channel}; pub struct Memory { width: usize, diff --git a/src/aiur/synthesis.rs b/crates/aiur/src/synthesis.rs similarity index 99% rename from src/aiur/synthesis.rs rename to crates/aiur/src/synthesis.rs index 1dd7f971..7d063593 100644 --- a/src/aiur/synthesis.rs +++ b/crates/aiur/src/synthesis.rs @@ -12,7 +12,7 @@ use rayon::iter::{ IntoParallelIterator, IntoParallelRefIterator, ParallelIterator, }; -use crate::aiur::{ +use crate::{ G, bytecode::{FunIdx, Toplevel}, constraints::Constraints, diff --git a/src/aiur/trace.rs b/crates/aiur/src/trace.rs similarity index 97% rename from src/aiur/trace.rs rename to crates/aiur/src/trace.rs index df72f54d..067792fb 100644 --- a/src/aiur/trace.rs +++ b/crates/aiur/src/trace.rs @@ -11,20 +11,16 @@ use rayon::{ }; use crate::{ - FxIndexMap, - aiur::{ - G, - bytecode::{Block, Ctrl, Function, Op, Toplevel}, - execute::{IOBuffer, IOKeyInfo, QueryRecord}, - function_channel, - gadgets::{bytes1::Bytes1, bytes2::Bytes2}, - memory::Memory, - u8_add_channel, u8_and_channel, u8_bit_decomposition_channel, - u8_chain_rotr4_channel, u8_chain_rotr7_channel, u8_less_than_channel, - u8_mul_channel, u8_or_channel, u8_range_check_channel, - u8_shift_left_channel, u8_shift_right_channel, u8_sub_channel, - u8_xor_channel, - }, + FxIndexMap, G, + bytecode::{Block, Ctrl, Function, Op, Toplevel}, + execute::{IOBuffer, IOKeyInfo, QueryRecord}, + function_channel, + gadgets::{bytes1::Bytes1, bytes2::Bytes2}, + memory::Memory, + u8_add_channel, u8_and_channel, u8_bit_decomposition_channel, + u8_chain_rotr4_channel, u8_chain_rotr7_channel, u8_less_than_channel, + u8_mul_channel, u8_or_channel, u8_range_check_channel, u8_shift_left_channel, + u8_shift_right_channel, u8_sub_channel, u8_xor_channel, }; struct ColumnIndex { diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml new file mode 100644 index 00000000..6ff7109c --- /dev/null +++ b/crates/common/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "ix-common" +version.workspace = true +edition.workspace = true +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"] + +[lints] +workspace = true diff --git a/src/ix/address.rs b/crates/common/src/address.rs similarity index 81% rename from src/ix/address.rs rename to crates/common/src/address.rs index df90d061..334aafd1 100644 --- a/src/ix/address.rs +++ b/crates/common/src/address.rs @@ -36,6 +36,23 @@ impl Address { self.hash.as_bytes() } + /// Flatten a slice of addresses into a `[u8; 32 * N]` byte blob. + pub fn pack(addrs: &[Address]) -> Vec { + let mut out = Vec::with_capacity(addrs.len() * 32); + for a in addrs { + out.extend_from_slice(a.as_bytes()); + } + out + } + + /// Iterate addresses out of a flat `[u8; 32 * N]` byte blob. Trailing + /// bytes shorter than 32 are silently dropped (via `chunks_exact`). + pub fn unpack(bytes: &[u8]) -> impl Iterator + '_ { + bytes + .chunks_exact(32) + .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`. /// @@ -43,8 +60,8 @@ impl Address { /// 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` / `aux_name_to_addr` namespace). - pub fn to_unique_name(&self) -> crate::ix::env::Name { - use crate::ix::env::Name; + 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(), @@ -53,8 +70,8 @@ impl Address { /// 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::ix::env::Name) -> Option { - use crate::ix::env::NameData; + 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, @@ -79,11 +96,8 @@ impl Address { /// Used by `compile/mutual.rs` 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::ix::env::Name, - ) -> crate::ix::env::Name { - use crate::ix::env::{Name, NameData}; + 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. @@ -127,8 +141,8 @@ impl StdHash for Address { } } -#[cfg(test)] -pub mod tests { +#[cfg(any(test, feature = "quickcheck"))] +pub mod arbitrary { use super::*; use quickcheck::{Arbitrary, Gen}; diff --git a/src/ix/env.rs b/crates/common/src/env.rs similarity index 97% rename from src/ix/env.rs rename to crates/common/src/env.rs index ca700056..b2d6675a 100644 --- a/src/ix/env.rs +++ b/crates/common/src/env.rs @@ -14,7 +14,7 @@ use std::{ sync::Arc, }; -use lean_ffi::nat::Nat; +use bignat::Nat; use rustc_hash::FxHashMap; // -- Name tags ---------------------------------------------------------------- @@ -1464,3 +1464,51 @@ impl ConstantInfo { /// The Lean kernel environment: a map from names to their constant declarations. pub type Env = FxHashMap; + +#[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)), + } + } + } +} diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs new file mode 100644 index 00000000..e51d043c --- /dev/null +++ b/crates/common/src/lib.rs @@ -0,0 +1,21 @@ +//! Foundational types shared across the Ix project. +//! +//! 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. +//! - `mutual`: mutual-block helpers used by both compile and kernel. +//! - `strong_ordering`: small comparison helper. +//! +//! Plus the `FxIndexMap` / `FxIndexSet` aliases that the rest of the project +//! uses. + +use indexmap::{IndexMap, IndexSet}; +use rustc_hash::FxBuildHasher; + +pub mod address; +pub mod env; +pub mod strong_ordering; + +pub type FxIndexMap = IndexMap; +pub type FxIndexSet = IndexSet; diff --git a/src/ix/strong_ordering.rs b/crates/common/src/strong_ordering.rs similarity index 100% rename from src/ix/strong_ordering.rs rename to crates/common/src/strong_ordering.rs diff --git a/crates/compile/Cargo.toml b/crates/compile/Cargo.toml new file mode 100644 index 00000000..3fff593a --- /dev/null +++ b/crates/compile/Cargo.toml @@ -0,0 +1,23 @@ +[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/src/ix/compile.rs b/crates/compile/src/compile.rs similarity index 97% rename from src/ix/compile.rs rename to crates/compile/src/compile.rs index a16b2bc1..07949d77 100644 --- a/src/ix/compile.rs +++ b/crates/compile/src/compile.rs @@ -13,36 +13,38 @@ use std::{ sync::{Arc, atomic::Ordering as AtomicOrdering}, }; -use lean_ffi::nat::Nat; - -use crate::{ - ix::address::Address, - ix::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, SourceInfo as LeanSourceInfo, - Substring as LeanSubstring, Syntax as LeanSyntax, SyntaxPreresolved, +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, SourceInfo as LeanSourceInfo, + Substring as LeanSubstring, Syntax as LeanSyntax, SyntaxPreresolved, +}; +use ix_common::strong_ordering::SOrd; + +use ixon::{ + CompileError, Tag0, + 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, }, - ix::graph::NameSet, - ix::ixon::{ - CompileError, Tag0, - 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, - }, - sharing::{self, analyze_block, build_sharing_vec, decide_sharing}, - univ::Univ, + env::{Env as IxonEnv, Named}, + expr::Expr, + metadata::{ + ConstantMeta, ConstantMetaInfo, DataValue, ExprMeta, ExprMetaData, KVMap, }, - ix::mutual::{Def, Ind, MutConst, MutCtx, Rec, ctx_to_all}, - ix::strong_ordering::SOrd, + sharing::{self, analyze_block, build_sharing_vec, decide_sharing}, + univ::Univ, +}; + +use crate::{ + graph::NameSet, + mutual::{Def, Ind, MutConst, MutCtx, Rec, ctx_to_all}, }; /// Whether to track hash-consed sizes during compilation. @@ -87,7 +89,7 @@ pub struct KernelCtx { /// etc.) with aux-substituted types at `resolve_lean_name_addr`-derived /// addresses that may shift as alpha-collapse reassigns addresses over /// the course of compilation. - pub kenv: crate::ix::kernel::env::KEnv, + pub kenv: ix_kernel::env::KEnv, } impl Default for KernelCtx { @@ -98,7 +100,7 @@ impl Default for KernelCtx { impl KernelCtx { pub fn new() -> Self { - KernelCtx { kenv: crate::ix::kernel::env::KEnv::new() } + KernelCtx { kenv: ix_kernel::env::KEnv::new() } } } @@ -442,7 +444,7 @@ fn compile_univ_indices( fn univ_sort_key(univ: &Arc) -> Vec { let mut buf = Vec::new(); - crate::ix::ixon::univ::put_univ(univ, &mut buf); + ixon::univ::put_univ(univ, &mut buf); buf } @@ -540,7 +542,7 @@ fn collect_expr_tables( Ok(()) } -pub(crate) fn preseed_expr_tables( +pub fn preseed_expr_tables( exprs: &[(&LeanExpr, &[Name])], mut_ctx: &MutCtx, cache: &mut BlockCache, @@ -582,7 +584,7 @@ pub(crate) fn preseed_expr_tables( Ok(()) } -pub(crate) fn collect_mut_const_exprs<'a>( +pub fn collect_mut_const_exprs<'a>( cnst: &'a MutConst, exprs: &mut Vec<(&'a LeanExpr, &'a [Name])>, ) { @@ -621,7 +623,7 @@ pub fn compile_expr( cache: &mut BlockCache, stt: &CompileState, ) -> Result, CompileError> { - use crate::ix::ixon::metadata::CallSiteEntry; + use ixon::metadata::CallSiteEntry; // Stack-based iterative compilation to avoid stack overflow enum Frame { @@ -774,7 +776,7 @@ pub fn compile_expr( let compiling_is_aux_regen = cache .compiling .as_ref() - .is_some_and(crate::ix::decompile::is_aux_gen_suffix); + .is_some_and(crate::decompile::is_aux_gen_suffix); if !compiling_is_aux_regen { if let Some(plan) = stt.call_site_plans.get(name) && !plan.is_identity() @@ -1461,11 +1463,11 @@ fn compile_data_value(dv: &LeanDataValue, stt: &CompileState) -> DataValue { // Serialize Int and store as blob let mut bytes = Vec::new(); match i { - crate::ix::env::Int::OfNat(n) => { + ix_common::env::Int::OfNat(n) => { bytes.push(0); bytes.extend_from_slice(&n.to_le_bytes()); }, - crate::ix::env::Int::NegSucc(n) => { + ix_common::env::Int::NegSucc(n) => { bytes.push(1); bytes.extend_from_slice(&n.to_le_bytes()); }, @@ -1671,16 +1673,16 @@ fn apply_sharing(exprs: Vec>) -> (Vec>, Vec>) { } /// Result of applying sharing to a singleton constant. -pub(crate) struct SingletonSharingResult { +pub struct SingletonSharingResult { /// The compiled Constant - pub(crate) constant: Constant, + pub constant: Constant, /// Hash-consed size of expressions - pub(crate) hash_consed_size: usize, + pub hash_consed_size: usize, } /// Apply sharing to a Definition and return a Constant with stats. #[allow(clippy::needless_pass_by_value)] -pub(crate) fn apply_sharing_to_definition_with_stats( +pub fn apply_sharing_to_definition_with_stats( def: Definition, refs: Vec
, univs: Vec>, @@ -1704,7 +1706,7 @@ pub(crate) fn apply_sharing_to_definition_with_stats( /// Apply sharing to an Axiom and return a Constant with stats. #[allow(clippy::needless_pass_by_value)] -pub(crate) fn apply_sharing_to_axiom_with_stats( +pub fn apply_sharing_to_axiom_with_stats( ax: Axiom, refs: Vec
, univs: Vec>, @@ -1722,7 +1724,7 @@ pub(crate) fn apply_sharing_to_axiom_with_stats( /// Apply sharing to a Quotient and return a Constant with stats. #[allow(clippy::needless_pass_by_value)] -pub(crate) fn apply_sharing_to_quotient_with_stats( +pub fn apply_sharing_to_quotient_with_stats( quot: Quotient, refs: Vec
, univs: Vec>, @@ -1743,7 +1745,7 @@ pub(crate) fn apply_sharing_to_quotient_with_stats( } /// Apply sharing to a Recursor and return a Constant with stats. -pub(crate) fn apply_sharing_to_recursor_with_stats( +pub fn apply_sharing_to_recursor_with_stats( rec: Recursor, refs: Vec
, univs: Vec>, @@ -1780,15 +1782,15 @@ pub(crate) fn apply_sharing_to_recursor_with_stats( } /// Result of applying sharing to a mutual block. -pub(crate) struct MutualBlockSharingResult { +pub struct MutualBlockSharingResult { /// The compiled Constant - pub(crate) constant: Constant, + pub constant: Constant, /// Hash-consed size of all expressions in the block - pub(crate) hash_consed_size: usize, + pub hash_consed_size: usize, } /// Apply sharing to a mutual block and return a Constant with stats. -pub(crate) fn apply_sharing_to_mutual_block( +pub fn apply_sharing_to_mutual_block( mut_consts: Vec, refs: Vec
, univs: Vec>, @@ -1960,7 +1962,7 @@ enum MutConstKind { /// Compile a Definition. /// Arena persists across type + value within a constant. -pub(crate) fn compile_definition( +pub fn compile_definition( def: &Def, mut_ctx: &MutCtx, cache: &mut BlockCache, @@ -2031,7 +2033,7 @@ fn compile_recursor_rule( /// Compile a Recursor. /// Arena grows across type and all rule RHS expressions. -pub(crate) fn compile_recursor( +pub fn compile_recursor( rec: &Rec, mut_ctx: &MutCtx, cache: &mut BlockCache, @@ -2158,7 +2160,7 @@ fn compile_constructor( /// 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(crate) fn compile_inductive( +pub fn compile_inductive( ind: &Ind, mut_ctx: &MutCtx, cache: &mut BlockCache, @@ -2315,20 +2317,20 @@ fn compile_quotient( // =========================================================================== /// Result of compiling a mutual block. -pub(crate) struct CompiledMutualBlock { +pub struct CompiledMutualBlock { /// The compiled Constant - pub(crate) constant: Constant, + pub constant: Constant, /// Content-addressed hash - pub(crate) addr: Address, + pub addr: Address, /// Hash-consed size (theoretical minimum with perfect DAG sharing) - pub(crate) hash_consed_size: usize, + pub hash_consed_size: usize, /// Serialized size (actual bytes) - pub(crate) serialized_size: usize, + pub serialized_size: usize, } /// Compile a mutual block with block-level sharing. /// Returns the Constant, its content-addressed hash, and size statistics. -pub(crate) fn compile_mutual_block( +pub fn compile_mutual_block( mut_consts: Vec, refs: Vec
, univs: Vec>, @@ -3890,18 +3892,18 @@ fn compile_mutual( .map(|r| r.clone()) } -pub(crate) mod aux_gen; +pub mod aux_gen; mod env; -pub(crate) mod mutual; -pub(crate) mod nat_conv; -pub(crate) mod surgery; +pub mod mutual; +pub mod nat_conv; +pub mod surgery; pub use env::{compile_env, compile_env_with_options}; #[cfg(test)] mod tests { use super::*; - use crate::ix::env::{BinderInfo, Expr as LeanExpr, Level}; - use crate::ix::ixon::metadata::CallSiteEntry; + use ix_common::env::{BinderInfo, Expr as LeanExpr, Level}; + use ixon::metadata::CallSiteEntry; #[test] fn test_compile_univ_zero() { @@ -4314,7 +4316,7 @@ mod tests { #[test] fn test_compile_axiom() { - use crate::ix::env::{AxiomVal, ConstantVal}; + use ix_common::env::{AxiomVal, ConstantVal}; // Create a simple axiom: axiom myAxiom : Type let name = Name::str(Name::anon(), "myAxiom".to_string()); @@ -4348,7 +4350,7 @@ mod tests { #[test] fn test_compile_simple_def() { - use crate::ix::env::{ + use ix_common::env::{ ConstantVal, DefinitionSafety, DefinitionVal, ReducibilityHints, }; @@ -4402,11 +4404,11 @@ mod tests { #[test] fn test_compile_self_referential_def() { - use crate::ix::env::{ + use ix_common::env::{ ConstantInfo as LeanConstantInfo, ConstantVal, DefinitionSafety, DefinitionVal, Env as LeanEnv, ReducibilityHints, }; - use crate::ix::ixon::constant::ConstantInfo; + 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) @@ -4462,7 +4464,7 @@ mod tests { #[test] fn test_compile_env_single_axiom() { - use crate::ix::env::{AxiomVal, ConstantVal}; + use ix_common::env::{AxiomVal, ConstantVal}; // Create a minimal environment with just one axiom let name = Name::str(Name::anon(), "myAxiom".to_string()); @@ -4484,7 +4486,7 @@ mod tests { #[test] fn test_compile_env_two_independent_axioms() { - use crate::ix::env::{AxiomVal, ConstantVal}; + use ix_common::env::{AxiomVal, ConstantVal}; let name1 = Name::str(Name::anon(), "axiom1".to_string()); let name2 = Name::str(Name::anon(), "axiom2".to_string()); @@ -4528,7 +4530,7 @@ mod tests { #[test] fn test_compile_env_def_referencing_axiom() { - use crate::ix::env::{ + use ix_common::env::{ AxiomVal, ConstantVal, DefinitionSafety, DefinitionVal, ReducibilityHints, }; @@ -4578,7 +4580,7 @@ mod tests { /// to the single representative in the Muts array. #[test] fn test_compile_mutual_alpha_equivalent_defs() { - use crate::ix::env::{ + use ix_common::env::{ ConstantVal, DefinitionSafety, DefinitionVal, ReducibilityHints, }; @@ -4656,7 +4658,7 @@ mod tests { /// with projections indexing correctly into the array. #[test] fn test_compile_mutual_alpha_equiv_with_different_third() { - use crate::ix::env::{ + use ix_common::env::{ ConstantVal, DefinitionSafety, DefinitionVal, ReducibilityHints, }; @@ -4765,8 +4767,8 @@ mod tests { #[test] fn test_mutual_block_roundtrip() { - use crate::ix::env::DefinitionSafety; - use crate::ix::ixon::constant::{DefKind, Definition}; + 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); @@ -4836,7 +4838,7 @@ mod tests { #[test] fn test_definition_with_sharing() { - use crate::ix::ixon::constant::{DefKind, Definition}; + use ixon::constant::{DefKind, Definition}; // Create a definition where typ and value share structure let sort0 = Expr::sort(0); @@ -4857,7 +4859,7 @@ mod tests { // Create constant with sharing at Constant level let def = Definition { kind: DefKind::Definition, - safety: crate::ix::env::DefinitionSafety::Safe, + safety: ix_common::env::DefinitionSafety::Safe, lvls: 0, typ: rewritten[0].clone(), value: rewritten[1].clone(), @@ -4880,7 +4882,7 @@ mod tests { #[test] fn test_axiom_with_sharing() { - use crate::ix::ixon::constant::Axiom; + use ixon::constant::Axiom; // Axiom with repeated subterms in its type let sort0 = Expr::sort(0); @@ -4915,7 +4917,7 @@ mod tests { #[test] fn test_recursor_with_sharing() { - use crate::ix::ixon::constant::{Recursor, RecursorRule}; + use ixon::constant::{Recursor, RecursorRule}; // Recursor with shared subterms across typ and rules let sort0 = Expr::sort(0); @@ -4981,7 +4983,7 @@ mod tests { #[test] fn test_inductive_with_sharing() { - use crate::ix::ixon::constant::{Constructor, Inductive}; + use ixon::constant::{Constructor, Inductive}; // Inductive with shared subterms across type and constructors let sort0 = Expr::sort(0); @@ -5091,8 +5093,8 @@ mod tests { #[test] fn test_roundtrip_axiom() { - use crate::ix::decompile::decompile_env; - use crate::ix::env::{AxiomVal, ConstantVal}; + 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()); @@ -5125,8 +5127,8 @@ mod tests { #[test] fn test_roundtrip_axiom_with_level_params() { - use crate::ix::decompile::decompile_env; - use crate::ix::env::{AxiomVal, ConstantVal, Env as LeanEnv}; + 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()); @@ -5168,8 +5170,8 @@ mod tests { #[test] fn test_roundtrip_definition() { - use crate::ix::decompile::decompile_env; - use crate::ix::env::{ + use crate::decompile::decompile_env; + use ix_common::env::{ ConstantVal, DefinitionSafety, DefinitionVal, ReducibilityHints, }; @@ -5180,13 +5182,13 @@ mod tests { Name::str(Name::anon(), "x".to_string()), type1.clone(), type1.clone(), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ); let value = LeanExpr::lam( Name::str(Name::anon(), "x".to_string()), type1, LeanExpr::bvar(Nat::from(0u64)), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ); let def = DefinitionVal { cnst: ConstantVal { name: name.clone(), level_params: vec![], typ }, @@ -5221,8 +5223,8 @@ mod tests { #[test] fn test_roundtrip_def_referencing_axiom() { - use crate::ix::decompile::decompile_env; - use crate::ix::env::{ + use crate::decompile::decompile_env; + use ix_common::env::{ AxiomVal, ConstantVal, DefinitionSafety, DefinitionVal, Env as LeanEnv, ReducibilityHints, }; @@ -5278,8 +5280,8 @@ mod tests { #[test] fn test_roundtrip_quotient() { - use crate::ix::decompile::decompile_env; - use crate::ix::env::{ConstantVal, Env as LeanEnv, QuotKind, QuotVal}; + 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()); @@ -5298,9 +5300,9 @@ mod tests { Name::anon(), LeanExpr::bvar(Nat::from(1u64)), prop.clone(), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ); let typ = LeanExpr::all( alpha, @@ -5309,9 +5311,9 @@ mod tests { Name::anon(), rel_type, sort_u.clone(), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ); let quot = QuotVal { @@ -5348,8 +5350,8 @@ mod tests { #[test] fn test_roundtrip_theorem() { - use crate::ix::decompile::decompile_env; - use crate::ix::env::{ConstantVal, Env as LeanEnv, TheoremVal}; + 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()); @@ -5389,8 +5391,8 @@ mod tests { #[test] fn test_roundtrip_opaque() { - use crate::ix::decompile::decompile_env; - use crate::ix::env::{ConstantVal, Env as LeanEnv, OpaqueVal}; + 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()); @@ -5431,8 +5433,8 @@ mod tests { #[test] fn test_roundtrip_multiple_constants() { - use crate::ix::decompile::decompile_env; - use crate::ix::env::{ + use crate::decompile::decompile_env; + use ix_common::env::{ AxiomVal, ConstantVal, DefinitionSafety, DefinitionVal, Env as LeanEnv, ReducibilityHints, TheoremVal, }; @@ -5506,8 +5508,8 @@ mod tests { #[test] fn test_roundtrip_inductive_simple() { - use crate::ix::decompile::decompile_env; - use crate::ix::env::{ + use crate::decompile::decompile_env; + use ix_common::env::{ ConstantVal, ConstructorVal, Env as LeanEnv, InductiveVal, }; @@ -5589,8 +5591,8 @@ mod tests { #[test] fn test_roundtrip_inductive_with_multiple_ctors() { - use crate::ix::decompile::decompile_env; - use crate::ix::env::{ + use crate::decompile::decompile_env; + use ix_common::env::{ ConstantVal, ConstructorVal, Env as LeanEnv, InductiveVal, }; @@ -5673,8 +5675,8 @@ mod tests { #[test] fn test_roundtrip_mutual_definitions() { - use crate::ix::decompile::decompile_env; - use crate::ix::env::{ + use crate::decompile::decompile_env; + use ix_common::env::{ ConstantVal, DefinitionSafety, DefinitionVal, Env as LeanEnv, ReducibilityHints, }; @@ -5690,7 +5692,7 @@ mod tests { Name::anon(), type0.clone(), type0.clone(), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ); // f := fun x => g x @@ -5701,7 +5703,7 @@ mod tests { LeanExpr::cnst(g_name.clone(), vec![]), LeanExpr::bvar(Nat::from(0u64)), ), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ); // g := fun x => f x @@ -5712,7 +5714,7 @@ mod tests { LeanExpr::cnst(f_name.clone(), vec![]), LeanExpr::bvar(Nat::from(0u64)), ), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ); // Mutual block: both reference each other @@ -5784,8 +5786,8 @@ mod tests { #[test] fn test_roundtrip_mutual_inductives() { - use crate::ix::decompile::decompile_env; - use crate::ix::env::{ + use crate::decompile::decompile_env; + use ix_common::env::{ ConstantVal, ConstructorVal, Env as LeanEnv, InductiveVal, }; @@ -5855,7 +5857,7 @@ mod tests { Name::anon(), odd_type.clone(), even_type.clone(), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ); let even_succ_ctor = ConstructorVal { @@ -5876,7 +5878,7 @@ mod tests { Name::anon(), even_type.clone(), odd_type.clone(), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ); let odd_succ_ctor = ConstructorVal { @@ -5942,8 +5944,8 @@ mod tests { #[test] fn test_roundtrip_inductive_with_recursor() { - use crate::ix::decompile::decompile_env; - use crate::ix::env::{ConstantVal, InductiveVal, RecursorVal}; + use crate::decompile::decompile_env; + use ix_common::env::{ConstantVal, InductiveVal, RecursorVal}; // Create Empty type with recursor (no constructors) // inductive Empty : Type @@ -5976,7 +5978,7 @@ mod tests { Name::anon(), empty_type.clone(), LeanExpr::sort(Level::param(u.clone())), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ); let rec_type = LeanExpr::all( Name::str(Name::anon(), "motive".to_string()), @@ -5988,9 +5990,9 @@ mod tests { LeanExpr::bvar(Nat::from(1u64)), LeanExpr::bvar(Nat::from(0u64)), ), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ), - crate::ix::env::BinderInfo::Implicit, + ix_common::env::BinderInfo::Implicit, ); let recursor = RecursorVal { diff --git a/src/ix/compile/aux_gen.rs b/crates/compile/src/compile/aux_gen.rs similarity index 96% rename from src/ix/compile/aux_gen.rs rename to crates/compile/src/compile/aux_gen.rs index 080a57e8..f9e06e5a 100644 --- a/src/ix/compile/aux_gen.rs +++ b/crates/compile/src/compile/aux_gen.rs @@ -74,28 +74,28 @@ //! - `.sparseCasesOn`, `.sparseCasesOnEq` //! - `.casesOnSameCtor`, `.casesOnSameCtorHet` -pub(crate) mod below; -pub(crate) mod brecon; -pub(crate) mod cases_on; -pub(crate) mod expr_utils; -pub(crate) mod nested; -pub(crate) mod rec_on; -pub(crate) mod recursor; +pub mod below; +pub mod brecon; +pub mod cases_on; +pub mod expr_utils; +pub mod nested; +pub mod rec_on; +pub mod recursor; use std::sync::Arc; use rustc_hash::FxHashMap; -use crate::ix::compile::CompileState; -use crate::ix::env::{ +use crate::compile::CompileState; +use ix_common::env::{ ConstantVal, Env as LeanEnv, Expr as LeanExpr, Name, RecursorRule, RecursorVal, }; -use crate::ix::ixon::CompileError; +use ixon::CompileError; /// A regenerated constant ready for compilation. #[derive(Clone)] -pub(crate) enum PatchedConstant { +pub enum PatchedConstant { /// A regenerated `.rec` recursor. Rec(RecursorVal), /// A regenerated `.recOn` definition (arg-reordered `.rec` wrapper). @@ -118,7 +118,7 @@ pub(crate) enum PatchedConstant { /// flips to `Unsafe` whenever the type or value mentions any unsafe constant — /// and every auxiliary references its parent inductive. #[derive(Clone)] -pub(crate) struct AuxDef { +pub struct AuxDef { pub name: Name, pub level_params: Vec, pub typ: LeanExpr, @@ -134,7 +134,7 @@ pub(crate) struct AuxDef { /// (to canonicalize Lean-source-order originals before structural /// comparison). #[derive(Clone, Default)] -pub(crate) struct AuxPatchesOutput { +pub struct AuxPatchesOutput { /// The regenerated canonical-layout constants, keyed by their /// Lean-visible source-indexed name (e.g. `A.rec`, `A.below_2`). pub patches: FxHashMap, @@ -185,12 +185,12 @@ pub(crate) struct AuxPatchesOutput { /// `InductiveVal.all` of any block member). It determines the canonical /// `.rec_N` naming and the source-aux walk used to compute the /// hash-sort permutation. -pub(crate) fn generate_aux_patches( +pub fn generate_aux_patches( sorted_classes: &[Vec], original_all: &[Name], lean_env: &Arc, stt: &CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + kctx: &mut crate::compile::KernelCtx, ) -> Result { let mut patches: FxHashMap = FxHashMap::default(); let mut aliases: FxHashMap = FxHashMap::default(); @@ -257,8 +257,8 @@ pub(crate) fn generate_aux_patches( let metadata_has_nested = original_all.iter().any(|name| { matches!( lean_env.get(name), - Some(crate::ix::env::ConstantInfo::InductInfo(v)) - if crate::ix::compile::nat_conv::nat_to_usize(&v.num_nested) > 0 + Some(ix_common::env::ConstantInfo::InductInfo(v)) + if crate::compile::nat_conv::nat_to_usize(&v.num_nested) > 0 ) }); let (canonical_recs, is_prop) = if metadata_has_nested @@ -529,7 +529,7 @@ pub(crate) fn generate_aux_patches( for (rec_name, rec_val) in canonical_recs.iter().take(n_classes) { // Build casesOn name: rec_name is "I.rec", casesOn name is "I.casesOn" let ind_name = match rec_name.as_data() { - crate::ix::env::NameData::Str(parent, _, _) => parent.clone(), + ix_common::env::NameData::Str(parent, _, _) => parent.clone(), _ => continue, }; let cases_on_name = Name::str(ind_name, "casesOn".to_string()); @@ -549,7 +549,7 @@ pub(crate) fn generate_aux_patches( // types (unlike below_N/brecOn_N which ARE generated via BRecOn.lean). for (rec_name, rec_val) in canonical_recs.iter().take(n_classes) { let ind_name = match rec_name.as_data() { - crate::ix::env::NameData::Str(parent, _, _) => parent.clone(), + ix_common::env::NameData::Str(parent, _, _) => parent.clone(), _ => continue, }; let rec_on_name = Name::str(ind_name, "recOn".to_string()); @@ -650,7 +650,7 @@ pub(crate) fn generate_aux_patches( .and_then(|c| c.first()) .map(|n| n.pretty()) .unwrap_or_default(); - if *crate::ix::compile::IX_TIMING + if *crate::compile::IX_TIMING && _below_elapsed.as_secs_f32() + _brecon_elapsed.as_secs_f32() > 0.3 { eprintln!( @@ -669,7 +669,7 @@ pub(crate) fn generate_aux_patches( .and_then(|c| c.first()) .map(|n| n.pretty()) .unwrap_or_default(); - if *crate::ix::compile::IX_TIMING && _p1_elapsed.as_secs_f32() > 0.5 { + if *crate::compile::IX_TIMING && _p1_elapsed.as_secs_f32() > 0.5 { eprintln!( "[gen_patches] {:?} recGen={:.2}s patches={}", _gen_label, @@ -721,13 +721,13 @@ pub(crate) fn generate_aux_patches( ) { let rep_ctors = match lean_env.get(rep) { - Some(crate::ix::env::ConstantInfo::InductInfo(v)) => { + Some(ix_common::env::ConstantInfo::InductInfo(v)) => { v.ctors.clone() }, _ => vec![], }; let alias_ctors = match lean_env.get(alias) { - Some(crate::ix::env::ConstantInfo::InductInfo(v)) => { + Some(ix_common::env::ConstantInfo::InductInfo(v)) => { v.ctors.clone() }, _ => vec![], @@ -896,7 +896,7 @@ pub(crate) fn generate_aux_patches( /// This distinguishes `.below` auxiliaries from coincidental name collisions /// like structure field accessors (e.g., `NewDecl.below : NewDecl → LocalDecl`). fn is_below_shaped(typ: &LeanExpr) -> bool { - use crate::ix::env::ExprData; + use ix_common::env::ExprData; let mut cur = typ; loop { match cur.as_data() { @@ -914,16 +914,16 @@ fn is_below_shaped(typ: &LeanExpr) -> bool { /// and may differ from the originals in `lean_env`. The canonical TC /// (`stt.canon_tc`) uses `canon_kenv` exclusively, so it sees the /// correct types for PProd(motive, I.below ...) inference. -pub(crate) fn populate_canon_kenv_with_below( +pub fn populate_canon_kenv_with_below( below_consts: &[below::BelowConstant], sorted_classes: &[Vec], - lean_env: &crate::ix::env::Env, + lean_env: &ix_common::env::Env, stt: &CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + kctx: &mut crate::compile::KernelCtx, ) { - use crate::ix::kernel::constant::KConst; - use crate::ix::kernel::id::KId; - use crate::ix::kernel::ingress::{ + use ix_kernel::constant::KConst; + use ix_kernel::id::KId; + use ix_kernel::ingress::{ lean_expr_to_zexpr_with_kenv, resolve_lean_name_addr, }; @@ -965,9 +965,9 @@ pub(crate) fn populate_canon_kenv_with_below( KConst::Defn { name: d.name.clone(), level_params: d.level_params.clone(), - kind: crate::ix::ixon::constant::DefKind::Definition, - safety: crate::ix::env::DefinitionSafety::Safe, - hints: crate::ix::env::ReducibilityHints::Abbrev, + kind: ixon::constant::DefKind::Definition, + safety: ix_common::env::DefinitionSafety::Safe, + hints: ix_common::env::ReducibilityHints::Abbrev, lvls: d.level_params.len() as u64, ty: ty_z, val: val_z, diff --git a/src/ix/compile/aux_gen/below.rs b/crates/compile/src/compile/aux_gen/below.rs similarity index 98% rename from src/ix/compile/aux_gen/below.rs rename to crates/compile/src/compile/aux_gen/below.rs index a483e83f..407dc5e7 100644 --- a/src/ix/compile/aux_gen/below.rs +++ b/crates/compile/src/compile/aux_gen/below.rs @@ -8,12 +8,12 @@ //! //! Follows `refs/lean4/src/Lean/Meta/Constructions/BRecOn.lean:59-108`. -use crate::ix::compile::nat_conv::{nat_to_usize, try_nat_to_usize}; -use crate::ix::env::{ +use crate::compile::nat_conv::{nat_to_usize, try_nat_to_usize}; +use ix_common::env::{ BinderInfo, ConstantInfo, ConstructorVal, Env as LeanEnv, Expr as LeanExpr, ExprData, InductiveVal, Level, LevelData, Name, RecursorVal, }; -use crate::ix::ixon::CompileError; +use ixon::CompileError; use super::expr_utils::{ LocalDecl, decompose_apps, find_motive_fvar, forall_telescope, fresh_fvar, @@ -38,7 +38,7 @@ pub(super) fn aux_rec_suffix_idx(aux_rec_name: &Name) -> Option { /// A generated `.below` constant — either a definition (Type-level) /// or an inductive (Prop-level). #[derive(Clone)] -pub(crate) enum BelowConstant { +pub enum BelowConstant { /// Type-level `.below`: a reducible definition using `.rec` + PProd. Def(BelowDef), /// Prop-level `.below`: an inductive type with constructors. @@ -53,7 +53,7 @@ pub(crate) enum BelowConstant { /// type or value references an unsafe constant — for unsafe inductives this /// always triggers because `.below` mentions the parent inductive's `.rec`. #[derive(Clone)] -pub(crate) struct BelowDef { +pub struct BelowDef { pub name: Name, pub level_params: Vec, pub typ: LeanExpr, @@ -63,7 +63,7 @@ pub(crate) struct BelowDef { /// A generated `.below` inductive (Prop-level case). #[derive(Clone)] -pub(crate) struct BelowIndc { +pub struct BelowIndc { pub name: Name, pub level_params: Vec, pub n_params: usize, @@ -87,7 +87,7 @@ pub(crate) struct BelowIndc { /// A constructor for a Prop-level `.below` inductive. #[derive(Clone)] -pub(crate) struct BelowCtor { +pub struct BelowCtor { pub name: Name, pub typ: LeanExpr, pub n_params: usize, @@ -107,13 +107,13 @@ pub(crate) struct BelowCtor { /// Note: `is_prop` is distinct from `is_large`. A Prop inductive with single /// constructors and all-Prop fields gets large elimination (`drec`), but Lean /// still generates `.below` as an inductive via `IndPredBelow`. -pub(crate) fn generate_below_constants( +pub fn generate_below_constants( sorted_classes: &[Vec], canonical_recs: &[(Name, RecursorVal)], lean_env: &LeanEnv, is_prop: bool, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) -> Result, CompileError> { let n_classes = sorted_classes.len(); if n_classes == 0 || canonical_recs.is_empty() { @@ -262,7 +262,7 @@ pub(crate) fn generate_below_constants( /// Build a single `.below` definition for a Type-level inductive. /// /// The `.below` definition's value is: -/// ``` +/// ```text /// λ {params} {motives} (indices) (major), /// I.rec.{succ(rlvl), lvls...} params /// (λ (indices) (major), Sort rlvl) -- for each motive @@ -276,8 +276,8 @@ fn build_below_def( lean_env: &LeanEnv, n_classes: usize, canonical_recs: &[(Name, RecursorVal)], - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) -> Result { let n_params = try_nat_to_usize(&rec_val.num_params)?; let n_motives = try_nat_to_usize(&rec_val.num_motives)?; @@ -475,8 +475,8 @@ fn build_below_value( rlvl: &Level, _n_classes: usize, _canonical_recs: &[(Name, RecursorVal)], - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) -> Result { let n_params = try_nat_to_usize(&rec_val.num_params)?; let n_motives = try_nat_to_usize(&rec_val.num_motives)?; @@ -1687,10 +1687,10 @@ fn mk_imax_aux(l1: &Level, l2: &Level) -> Level { /// Uses raw `Level::succ` / `Level::max` to faithfully preserve the kernel's /// level structure — no distribution of Succ over Max, no subsumption. pub(super) fn kuniv_to_level( - u: &crate::ix::kernel::level::KUniv, + u: &ix_kernel::level::KUniv, param_names: &[Name], ) -> Level { - use crate::ix::kernel::level::UnivData; + use ix_kernel::level::UnivData; match u.data() { UnivData::Zero(_) => Level::zero(), UnivData::Succ(inner, _) => Level::succ(kuniv_to_level(inner, param_names)), @@ -1774,7 +1774,7 @@ pub(super) fn mk_punit_unit(lvl: &Level) -> LeanExpr { /// Used when extracting motive domains from the recursor type for Prop-level /// `.below` inductives. The recursor may have large elimination (extra `u` /// param), but `.below` motives always target Prop. -pub(crate) fn replace_result_sort_with_prop(expr: &LeanExpr) -> LeanExpr { +pub fn replace_result_sort_with_prop(expr: &LeanExpr) -> LeanExpr { match expr.as_data() { ExprData::ForallE(name, dom, body, bi, _) => LeanExpr::all( name.clone(), diff --git a/src/ix/compile/aux_gen/brecon.rs b/crates/compile/src/compile/aux_gen/brecon.rs similarity index 98% rename from src/ix/compile/aux_gen/brecon.rs rename to crates/compile/src/compile/aux_gen/brecon.rs index dff154d8..fdcd0582 100644 --- a/src/ix/compile/aux_gen/brecon.rs +++ b/crates/compile/src/compile/aux_gen/brecon.rs @@ -8,13 +8,13 @@ //! `.brecOn.go` uses PProd-wrapped motives; `.brecOn` projects first component. //! Reference: `refs/lean4/src/Lean/Meta/Constructions/BRecOn.lean:191-308` -use crate::ix::compile::nat_conv::try_nat_to_usize; -use crate::ix::env::{ +use crate::compile::nat_conv::try_nat_to_usize; +use bignat::Nat; +use ix_common::env::{ BinderInfo, ConstantInfo, Env as LeanEnv, Expr as LeanExpr, ExprData, InductiveVal, Level, LevelData, Name, RecursorVal, }; -use crate::ix::ixon::CompileError; -use lean_ffi::nat::Nat; +use ixon::CompileError; use super::below::{ BelowConstant, mk_level_succ, mk_pprod, mk_pprod_mk, mk_punit_unit, @@ -44,7 +44,7 @@ use rustc_hash::FxHashMap; /// `.brecOn.eq`. `.go` and `.brecOn` are always `Defn`; `.eq` is `Thm` /// (safe) or unsafe `Defn` with `hints := .opaque`. #[derive(Clone)] -pub(crate) struct BRecOnDef { +pub struct BRecOnDef { pub name: Name, pub level_params: Vec, pub typ: LeanExpr, @@ -59,14 +59,14 @@ pub(crate) struct BRecOnDef { /// from Phase 1 and the `.below` constants from Phase 2. /// `is_prop` determines whether to generate Prop-level (single theorem) or /// Type-level (`.brecOn.go` + `.brecOn`) forms. -pub(crate) fn generate_brecon_constants( +pub fn generate_brecon_constants( sorted_classes: &[Vec], canonical_recs: &[(Name, RecursorVal)], below_consts: &[BelowConstant], lean_env: &LeanEnv, is_prop: bool, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) -> Result, CompileError> { let n_classes = sorted_classes.len(); if n_classes == 0 || canonical_recs.is_empty() || below_consts.is_empty() { @@ -623,8 +623,8 @@ fn build_type_brecon_fvar( below_names: &[Name], lean_env: &LeanEnv, n_classes: usize, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) -> Result, CompileError> { // canon_kenv is populated by `populate_canon_kenv_with_below` in // aux_gen.rs between Phase 2 and Phase 3. It contains PUnit, PProd, @@ -1076,16 +1076,15 @@ fn build_type_brecon_fvar( }, ]; - if let Some((eq_typ, eq_val)) = eq_result { - results.push(BRecOnDef { - name: eq_name, - level_params: rec_level_params.clone(), - typ: eq_typ, - value: eq_val, - is_unsafe, - is_prop: false, - }); - } + let (eq_typ, eq_val) = eq_result; + results.push(BRecOnDef { + name: eq_name, + level_params: rec_level_params.clone(), + typ: eq_typ, + value: eq_val, + is_unsafe, + is_prop: false, + }); Ok(results) } @@ -1441,18 +1440,14 @@ fn build_type_brecon_eq_fvar( // `Eq` and `HEq` binders in `motive_wrapped` and // `build_minor_via_cases_sim`'s remaining list. rec_level_params: &[Name], - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, -) -> Option<(LeanExpr, LeanExpr)> { - // .brecOn.eq requires Eq and Eq.refl as constants. In the full pipeline, - // aux_gen is only called when the original Lean environment has these - // constants, so this always succeeds. But in minimal test environments - // (e.g., unit tests with synthetic inductives), Eq may not exist. - // Return None in that case — matching the old BVar code's behavior. - // - // TODO: Accept a lean_env parameter and check lean_env.get("Eq").is_some() - // for a more principled guard. For now, we always generate .eq since the - // real pipeline guarantees Eq exists. + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, +) -> (LeanExpr, LeanExpr) { + // .brecOn.eq requires Eq and Eq.refl as constants. The real pipeline only + // calls aux_gen when the original Lean environment has these, so this + // always succeeds. If a future minimal-test caller needs to opt out (e.g. + // a synthetic inductive without Eq), reintroduce an `Option` return or + // gate via a `lean_env.get("Eq").is_some()` check. let _ = n_minors; let _n_motives = motive_fvars.len(); @@ -1577,7 +1572,7 @@ fn build_type_brecon_eq_fvar( kctx, ); if let Some(eq_value) = eq_value_opt { - return Some((eq_type, eq_value)); + return (eq_type, eq_value); } // Fall through to the simple path if the indexed construction // couldn't be completed (e.g., missing ctor info). @@ -1717,7 +1712,7 @@ fn build_type_brecon_eq_fvar( let eq_value = mk_lambda(eq_val, all_decls); - Some((eq_type, eq_value)) + (eq_type, eq_value) } // ========================================================================= @@ -1785,8 +1780,8 @@ fn build_indexed_eq_value( // `Eq` and `HEq` binders (matching Lean's `mkEqAndProof` in // `refs/lean4/src/Lean/Meta/Tactic/Cases.lean:30-37`). rec_level_params: &[Name], - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) -> Option { let n_indices = index_decls.len(); let outer_major = &major_fvars[0]; diff --git a/src/ix/compile/aux_gen/cases_on.rs b/crates/compile/src/compile/aux_gen/cases_on.rs similarity index 98% rename from src/ix/compile/aux_gen/cases_on.rs rename to crates/compile/src/compile/aux_gen/cases_on.rs index 4d2ec2e7..b14a4163 100644 --- a/src/ix/compile/aux_gen/cases_on.rs +++ b/crates/compile/src/compile/aux_gen/cases_on.rs @@ -10,8 +10,8 @@ //! //! Follows `refs/lean4/src/library/constructions/cases_on.cpp`. -use crate::ix::compile::aux_gen::AuxDef; -use crate::ix::env::{ +use crate::compile::aux_gen::AuxDef; +use ix_common::env::{ BinderInfo, ConstantInfo, Env as LeanEnv, Expr as LeanExpr, ExprData, Level, Name, RecursorVal, }; @@ -53,7 +53,7 @@ fn mk_pi_unit(e: &LeanExpr, unit: &LeanExpr) -> LeanExpr { /// Uses FVar-based construction: opens the rec type into FVars, builds /// casesOn type and value using FVar references, then abstracts with /// mk_forall/mk_lambda. -pub(crate) fn generate_cases_on( +pub fn generate_cases_on( name: &Name, rec_val: &RecursorVal, lean_env: &LeanEnv, @@ -65,7 +65,7 @@ pub(crate) fn generate_cases_on( // Extract target inductive name from "A.casesOn" → "A" let target_ind = match name.as_data() { - crate::ix::env::NameData::Str(parent, s, _) if s == "casesOn" => { + ix_common::env::NameData::Str(parent, s, _) if s == "casesOn" => { parent.clone() }, _ => return None, @@ -370,8 +370,8 @@ fn get_minor_name( #[cfg(test)] mod tests { use super::*; - use crate::ix::env::{BinderInfo, ConstantVal, InductiveVal, Literal}; - use lean_ffi::nat::Nat; + use bignat::Nat; + use ix_common::env::{BinderInfo, ConstantVal, InductiveVal, Literal}; fn mk_name_for(s: &str) -> Name { let mut n = Name::anon(); diff --git a/src/ix/compile/aux_gen/expr_utils.rs b/crates/compile/src/compile/aux_gen/expr_utils.rs similarity index 96% rename from src/ix/compile/aux_gen/expr_utils.rs rename to crates/compile/src/compile/aux_gen/expr_utils.rs index 986dc6f0..92105974 100644 --- a/src/ix/compile/aux_gen/expr_utils.rs +++ b/crates/compile/src/compile/aux_gen/expr_utils.rs @@ -9,14 +9,14 @@ use rustc_hash::{FxHashMap, FxHashSet}; -use crate::ix::address::Address; -use crate::ix::compile::nat_conv::{nat_to_u64, nat_to_usize}; -use crate::ix::env::{ +use crate::compile::nat_conv::{nat_to_u64, nat_to_usize}; +use bignat::Nat; +use ix_common::address::Address; +use ix_common::env::{ BinderInfo, Expr as LeanExpr, ExprData, Level, LevelData, Name, }; -use crate::ix::kernel::ingress::{lean_level_to_kuniv, resolve_lean_name_addr}; -use crate::ix::kernel::mode::Meta; -use lean_ffi::nat::Nat; +use ix_kernel::ingress::{lean_level_to_kuniv, resolve_lean_name_addr}; +use ix_kernel::mode::Meta; // ========================================================================= // FVar infrastructure @@ -28,7 +28,7 @@ use lean_ffi::nat::Nat; /// FVar space. The `fvar_name` is a unique identifier; `binder_name` is /// the cosmetic name that appears in the final forall/lambda chain. #[derive(Clone)] -pub(crate) struct LocalDecl { +pub struct LocalDecl { pub fvar_name: Name, pub binder_name: Name, pub domain: LeanExpr, @@ -36,7 +36,7 @@ pub(crate) struct LocalDecl { } /// Create a fresh FVar with a unique name derived from `prefix` and `idx`. -pub(crate) fn fresh_fvar(prefix: &str, idx: usize) -> (Name, LeanExpr) { +pub 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) @@ -119,13 +119,13 @@ pub(super) struct IndRecInfo { /// loop body and terminates peeling, potentially yielding a shorter /// `indices` vec than Lean's stored `num_indices`. pub(super) fn decompose_inductive_type( - ind: &crate::ix::env::InductiveVal, + ind: &ix_common::env::InductiveVal, ind_univs: &[Level], param_fvars: &[LocalDecl], - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, -) -> Result { - use crate::ix::ixon::CompileError; + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, +) -> Result { + use ixon::CompileError; let n_params = param_fvars.len(); let ty = subst_levels(&ind.cnst.typ, &ind.cnst.level_params, ind_univs); @@ -274,7 +274,7 @@ pub(super) fn decompose_inductive_type( /// position MUST verify `decls.len() == n` before indexing — otherwise /// a surprising input shape becomes a panic. Prefer /// [`forall_telescope_exact`] when a precise arity is required. -pub(crate) fn forall_telescope( +pub fn forall_telescope( expr: &LeanExpr, n: usize, prefix: &str, @@ -322,10 +322,7 @@ pub(super) fn forall_telescope_exact( start_idx: usize, context: &str, what: &str, -) -> Result< - (Vec, Vec, LeanExpr), - crate::ix::ixon::CompileError, -> { +) -> Result<(Vec, Vec, LeanExpr), ixon::CompileError> { let (fvars, decls, body) = forall_telescope(expr, n, prefix, start_idx); if decls.len() != n { // Include enough context to pinpoint the shape problem: every peeled @@ -337,7 +334,7 @@ pub(super) fn forall_telescope_exact( format!("{}:{}", d.binder_name.pretty(), describe_expr_head(&d.domain)) }) .collect(); - return Err(crate::ix::ixon::CompileError::UnsupportedExpr { + return Err(ixon::CompileError::UnsupportedExpr { desc: format!( "{context}: expected {n} leading foralls ({what}), got {actual}. \ Peeled binders (name:domain_kind): [{binders}]. \ @@ -447,7 +444,7 @@ pub(super) fn mk_forall(body: LeanExpr, binders: &[LocalDecl]) -> LeanExpr { /// Build a lambda chain by batch-abstracting all FVars in a single pass. /// /// Same semantics as `mk_forall` but produces `λ (x : T), body`. -pub(crate) fn mk_lambda(body: LeanExpr, binders: &[LocalDecl]) -> LeanExpr { +pub fn mk_lambda(body: LeanExpr, binders: &[LocalDecl]) -> LeanExpr { mk_binder_chain(body, binders, BinderKind::Lambda) } @@ -601,10 +598,7 @@ pub(super) fn batch_abstract( /// /// `instantiate1` is used when peeling forall binders during recursor /// construction (matching Lean C++ and lean4lean). -pub(crate) fn instantiate1( - body: &LeanExpr, - replacement: &LeanExpr, -) -> LeanExpr { +pub fn instantiate1(body: &LeanExpr, replacement: &LeanExpr) -> LeanExpr { instantiate1_at(body, replacement, 0) } @@ -862,7 +856,7 @@ pub(super) fn shift_vars( // ========================================================================= /// Substitute universe parameters in expressions. -pub(crate) fn subst_levels( +pub fn subst_levels( expr: &LeanExpr, params: &[Name], univs: &[Level], @@ -1259,7 +1253,7 @@ impl<'a> RestoreState<'a> { } /// Open lambda binders into FVars (matching forall_telescope but for lambdas). -pub(crate) fn lambda_telescope( +pub fn lambda_telescope( expr: &LeanExpr, n: usize, prefix: &str, @@ -1490,7 +1484,7 @@ pub(super) fn mk_const(name: &Name, univs: &[Level]) -> LeanExpr { /// /// Called by the kernel's `mk_local_decl` during inductive processing /// to ensure parameter/field types are clean before entering the local context. -pub(crate) fn consume_type_annotations(e: &LeanExpr) -> LeanExpr { +pub fn consume_type_annotations(e: &LeanExpr) -> LeanExpr { let (head, args) = decompose_apps(e); if let ExprData::Const(name, _, _) = head.as_data() { let n = name.pretty(); @@ -1507,7 +1501,7 @@ pub(crate) fn consume_type_annotations(e: &LeanExpr) -> LeanExpr { } /// Decompose an application spine: `f a1 a2 ... an` -> `(f, [a1, ..., an])`. -pub(crate) fn decompose_apps(expr: &LeanExpr) -> (LeanExpr, Vec) { +pub 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() { @@ -1724,14 +1718,14 @@ pub(super) fn find_motive_fvar( /// ``` /// Ensure PUnit and PProd are in the given kenv for kernel type inference. /// Accepts `kctx` so callers can choose which KernelCtx to populate. -pub(crate) fn ensure_prelude_in_kenv_of( - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, +pub fn ensure_prelude_in_kenv_of( + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) { - use crate::ix::kernel::constant::KConst; - use crate::ix::kernel::expr::KExpr; - use crate::ix::kernel::id::KId; - use crate::ix::kernel::level::KUniv; + use ix_kernel::constant::KConst; + use ix_kernel::expr::KExpr; + use ix_kernel::id::KId; + use ix_kernel::level::KUniv; let n2a = Some(&stt.name_to_addr); let aux_n2a = Some(&stt.aux_name_to_addr); @@ -1950,17 +1944,15 @@ pub(crate) fn ensure_prelude_in_kenv_of( /// requires the parent). fn ensure_in_kenv_of_inner_env( name: &Name, - lean_env: &crate::ix::env::Env, - stt: &crate::ix::compile::CompileState, - kenv: &mut crate::ix::kernel::env::KEnv, + lean_env: &ix_common::env::Env, + stt: &crate::compile::CompileState, + kenv: &mut ix_kernel::env::KEnv, replace_axio_stub: bool, ) { - use crate::ix::env::{ConstantInfo as LCI, DefinitionSafety}; - use crate::ix::kernel::constant::KConst; - use crate::ix::kernel::id::KId; - use crate::ix::kernel::ingress::{ - lean_expr_to_zexpr_cached, param_names_hash, - }; + use ix_common::env::{ConstantInfo as LCI, DefinitionSafety}; + use ix_kernel::constant::KConst; + use ix_kernel::id::KId; + use ix_kernel::ingress::{lean_expr_to_zexpr_cached, param_names_hash}; let n2a = Some(&stt.name_to_addr); let aux_n2a = Some(&stt.aux_name_to_addr); @@ -1982,10 +1974,10 @@ fn ensure_in_kenv_of_inner_env( // Helper: convert a LeanExpr to KExpr with the given level param names, // using the KEnv's persistent ingress cache. Callers are top-level, so // we start with an empty binder-name stack. - let to_z = |expr: &crate::ix::env::Expr, + let to_z = |expr: &ix_common::env::Expr, lp: &[Name], - kenv: &mut crate::ix::kernel::env::KEnv| - -> crate::ix::kernel::expr::KExpr { + kenv: &mut ix_kernel::env::KEnv| + -> ix_kernel::expr::KExpr { let pn_h = param_names_hash(lp); let mut binder_names: Vec = Vec::new(); lean_expr_to_zexpr_cached( @@ -2059,7 +2051,7 @@ fn ensure_in_kenv_of_inner_env( KConst::Defn { name: name.clone(), level_params: lp.clone(), - kind: crate::ix::ixon::constant::DefKind::Definition, + kind: ixon::constant::DefKind::Definition, safety: d.safety, hints: d.hints, lvls: lp.len() as u64, @@ -2079,9 +2071,9 @@ fn ensure_in_kenv_of_inner_env( KConst::Defn { name: name.clone(), level_params: lp.clone(), - kind: crate::ix::ixon::constant::DefKind::Theorem, + kind: ixon::constant::DefKind::Theorem, safety: DefinitionSafety::Safe, - hints: crate::ix::env::ReducibilityHints::Opaque, + hints: ix_common::env::ReducibilityHints::Opaque, lvls: lp.len() as u64, ty, val, @@ -2099,9 +2091,9 @@ fn ensure_in_kenv_of_inner_env( KConst::Defn { name: name.clone(), level_params: lp.clone(), - kind: crate::ix::ixon::constant::DefKind::Opaque, + kind: ixon::constant::DefKind::Opaque, safety: DefinitionSafety::Safe, - hints: crate::ix::env::ReducibilityHints::Opaque, + hints: ix_common::env::ReducibilityHints::Opaque, lvls: lp.len() as u64, ty, val, @@ -2157,9 +2149,9 @@ fn ensure_in_kenv_of_inner_env( fn ensure_in_kenv_of_inner( name: &Name, - lean_env: &crate::ix::env::Env, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + lean_env: &ix_common::env::Env, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, replace_axio_stub: bool, ) { ensure_in_kenv_of_inner_env( @@ -2171,11 +2163,11 @@ fn ensure_in_kenv_of_inner( ); } -pub(crate) fn ensure_in_kenv_of( +pub fn ensure_in_kenv_of( name: &Name, - lean_env: &crate::ix::env::Env, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + lean_env: &ix_common::env::Env, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) { ensure_in_kenv_of_inner(name, lean_env, stt, kctx, false); } @@ -2183,30 +2175,30 @@ pub(crate) fn ensure_in_kenv_of( /// Like [`ensure_in_kenv_of`], but upgrades an existing type-only `Axio` /// stub into the real constant. This is required before WHNF paths that must /// unfold reducible definitions or inspect inductive/ctor metadata. -pub(crate) fn ensure_full_in_kenv_of( +pub fn ensure_full_in_kenv_of( name: &Name, - lean_env: &crate::ix::env::Env, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + lean_env: &ix_common::env::Env, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) { ensure_in_kenv_of_inner(name, lean_env, stt, kctx, true); } fn ensure_full_in_tc_env( name: &Name, - lean_env: &crate::ix::env::Env, - stt: &crate::ix::compile::CompileState, - kenv: &mut crate::ix::kernel::env::KEnv, + lean_env: &ix_common::env::Env, + stt: &crate::compile::CompileState, + kenv: &mut ix_kernel::env::KEnv, ) { ensure_in_kenv_of_inner_env(name, lean_env, stt, kenv, true); } /// Convenience wrapper: ingress into the **original** kenv (`stt.kctx`). -pub(crate) fn ensure_in_kenv( +pub fn ensure_in_kenv( name: &Name, - lean_env: &crate::ix::env::Env, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + lean_env: &ix_common::env::Env, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) { ensure_in_kenv_of(name, lean_env, stt, kctx); } @@ -2224,8 +2216,8 @@ pub(super) struct TcScope<'a> { fvar_levels: FxHashMap, base_depth: usize, param_names: &'a [Name], - stt: &'a crate::ix::compile::CompileState, - tc: crate::ix::kernel::tc::TypeChecker<'a, Meta>, + stt: &'a crate::compile::CompileState, + tc: ix_kernel::tc::TypeChecker<'a, Meta>, /// How many extra locals are currently pushed above base_depth. extra_locals: usize, } @@ -2235,8 +2227,8 @@ impl<'a> TcScope<'a> { pub(super) fn new( outer_fvar_ctx: &[LocalDecl], param_names: &'a [Name], - stt: &'a crate::ix::compile::CompileState, - kctx: &'a mut crate::ix::compile::KernelCtx, + stt: &'a crate::compile::CompileState, + kctx: &'a mut crate::compile::KernelCtx, ) -> Self { let fvar_levels: FxHashMap = outer_fvar_ctx .iter() @@ -2244,7 +2236,7 @@ impl<'a> TcScope<'a> { .map(|(i, decl)| (decl.fvar_name.clone(), i)) .collect(); - let mut tc = crate::ix::kernel::tc::TypeChecker::new(&mut kctx.kenv); + let mut tc = ix_kernel::tc::TypeChecker::new(&mut kctx.kenv); tc.infer_only = true; // Push outer FVar types once. @@ -2347,9 +2339,9 @@ impl<'a> TcScope<'a> { fn get_level_error( &self, ty: &LeanExpr, - kexpr: &crate::ix::kernel::expr::KExpr, - e: &crate::ix::kernel::error::TcError, - ) -> crate::ix::ixon::CompileError { + kexpr: &ix_kernel::expr::KExpr, + e: &ix_kernel::error::TcError, + ) -> ixon::CompileError { eprintln!("[TcScope::get_level] FAILED"); eprintln!(" lean_expr: {}", ty.pretty()); eprintln!(" kexpr: {kexpr}"); @@ -2361,10 +2353,10 @@ impl<'a> TcScope<'a> { self.extra_locals ); // Dump kenv entries for constants referenced in the expression. - let mut stack: Vec<&crate::ix::kernel::expr::KExpr> = vec![kexpr]; + let mut stack: Vec<&ix_kernel::expr::KExpr> = vec![kexpr]; let mut seen_ids = std::collections::HashSet::new(); while let Some(expr) = stack.pop() { - use crate::ix::kernel::expr::ExprData as ZED; + use ix_kernel::expr::ExprData as ZED; match expr.data() { ZED::Const(id, us, _) => { if seen_ids.insert(id.clone()) { @@ -2391,7 +2383,7 @@ impl<'a> TcScope<'a> { _ => {}, } } - crate::ix::ixon::CompileError::UnsupportedExpr { + ixon::CompileError::UnsupportedExpr { desc: format!( "TcScope::get_level({}): tc.infer failed: {e}", ty.pretty() @@ -2412,7 +2404,7 @@ impl<'a> TcScope<'a> { pub(super) fn get_level( &mut self, ty: &LeanExpr, - ) -> Result { + ) -> Result { // Fast path: read Sort level from stored type (matching Lean's // inferAppType which peels foralls without substituting term args). // Sort levels use level params, not BVars, so the level is correct @@ -2432,14 +2424,14 @@ impl<'a> TcScope<'a> { let inferred = loop { match self.tc.infer(&kexpr) { Ok(inferred) => break inferred, - Err(crate::ix::kernel::error::TcError::UnknownConst(addr)) + Err(ix_kernel::error::TcError::UnknownConst(addr)) if faulted_addrs.insert(addr.clone()) && self.fault_in_addr(&addr) => {}, Err(e) => return Err(self.get_level_error(ty, &kexpr, &e)), } }; let ku = self.tc.ensure_sort(&inferred).map_err(|e| { - crate::ix::ixon::CompileError::UnsupportedExpr { + ixon::CompileError::UnsupportedExpr { desc: format!("TcScope::get_level: ensure_sort failed: {e}"), } })?; @@ -2463,7 +2455,7 @@ impl<'a> TcScope<'a> { /// Check if a Level is guaranteed non-zero. Matches Lean's `is_not_zero`: /// true for Succ(_), Param, Max(a,b) where either is not-zero. fn is_not_zero_level(l: &Level) -> bool { - use crate::ix::env::LevelData; + use ix_common::env::LevelData; match l.as_data() { LevelData::Succ(_, _) => true, LevelData::Max(a, b, _) => { @@ -2484,8 +2476,8 @@ impl<'a> TcScope<'a> { /// application, not enough foralls, result isn't Sort, or the constant /// isn't found in the kernel env). fn try_infer_app_sort_level(&self, ty: &LeanExpr) -> Option { - use crate::ix::env::ExprData; - use crate::ix::kernel::expr::ExprData as ZED; + use ix_common::env::ExprData; + use ix_kernel::expr::ExprData as ZED; // Decompose into head constant + args. let (head, args) = decompose_apps(ty); @@ -2498,7 +2490,7 @@ impl<'a> TcScope<'a> { let n2a = Some(&self.stt.name_to_addr); let aux_n2a = Some(&self.stt.aux_name_to_addr); let addr = resolve_lean_name_addr(name, n2a, aux_n2a); - let kid = crate::ix::kernel::id::KId::new(addr, name.clone()); + let kid = ix_kernel::id::KId::new(addr, name.clone()); let kconst = self.tc.env.get(&kid)?; let kty = kconst.ty(); @@ -2535,10 +2527,10 @@ impl<'a> TcScope<'a> { /// the concrete levels from a Const's level args. fn kuniv_to_level_with_const_levels( &self, - u: &crate::ix::kernel::level::KUniv, + u: &ix_kernel::level::KUniv, const_levels: &[Level], ) -> Level { - use crate::ix::kernel::level::UnivData; + use ix_kernel::level::UnivData; match u.data() { UnivData::Zero(_) => Level::zero(), UnivData::Succ(inner, _) => { @@ -2681,13 +2673,13 @@ impl<'a> TcScope<'a> { /// `Mdata` layers carried by the kernel expression are re-wrapped around /// the result in original order — matching `egress_expr`. pub(super) fn kexpr_to_lean( - expr: &crate::ix::kernel::expr::KExpr, + expr: &ix_kernel::expr::KExpr, outer_depth: usize, fvar_levels: &FxHashMap, local_depth: usize, param_names: &[Name], ) -> LeanExpr { - use crate::ix::kernel::expr::ExprData as KED; + use ix_kernel::expr::ExprData as KED; // Reverse `fvar_levels` lazily via linear search — the FVar context is // small in practice (a handful of param/motive/minor/index binders), @@ -2772,11 +2764,11 @@ pub(super) fn kexpr_to_lean( kexpr_to_lean(val, outer_depth, fvar_levels, local_depth, param_names), ), KED::Nat(n, _, _) => { - use crate::ix::env::Literal; + use ix_common::env::Literal; LeanExpr::lit(Literal::NatVal(n.clone())) }, KED::Str(s, _, _) => { - use crate::ix::env::Literal; + use ix_common::env::Literal; LeanExpr::lit(Literal::StrVal(s.clone())) }, }; @@ -2805,8 +2797,8 @@ fn collect_lean_source_name_hints( fvar_levels: &FxHashMap, depth: usize, param_names: &[Name], - stt: &crate::ix::compile::CompileState, - out: &mut FxHashMap, + stt: &crate::compile::CompileState, + out: &mut FxHashMap, ) { if source_name_hint_candidate(source) && !expr_has_bvar(source) { let key = @@ -2909,8 +2901,8 @@ fn restore_lean_source_name_hints( fvar_levels: &FxHashMap, depth: usize, param_names: &[Name], - stt: &crate::ix::compile::CompileState, - hints: &FxHashMap, + stt: &crate::compile::CompileState, + hints: &FxHashMap, ) -> LeanExpr { if source_name_hint_candidate(generated) && !expr_has_bvar(generated) { let key = to_kexpr_static(generated, fvar_levels, depth, param_names, stt) @@ -3061,7 +3053,7 @@ fn expr_has_bvar(expr: &LeanExpr) -> bool { fn restore_source_names_same_content( generated: &LeanExpr, source: &LeanExpr, - stt: &crate::ix::compile::CompileState, + stt: &crate::compile::CompileState, ) -> LeanExpr { let source = strip_mdata_ref(source); @@ -3077,7 +3069,7 @@ fn restore_source_names_same_content( fn restore_source_names_same_content_inner( generated: &LeanExpr, source: &LeanExpr, - stt: &crate::ix::compile::CompileState, + stt: &crate::compile::CompileState, ) -> LeanExpr { match (generated.as_data(), source.as_data()) { ( @@ -3146,7 +3138,7 @@ fn strip_mdata_ref(mut expr: &LeanExpr) -> &LeanExpr { fn same_resolved_name_addr( a: &Name, b: &Name, - stt: &crate::ix::compile::CompileState, + stt: &crate::compile::CompileState, ) -> bool { if a == b { return true; @@ -3167,13 +3159,13 @@ fn to_kexpr_static( fvar_levels: &FxHashMap, ctx_depth: usize, param_names: &[Name], - stt: &crate::ix::compile::CompileState, -) -> crate::ix::kernel::expr::KExpr { + stt: &crate::compile::CompileState, +) -> ix_kernel::expr::KExpr { let n2a = Some(&stt.name_to_addr); let aux_n2a = Some(&stt.aux_name_to_addr); - use crate::ix::kernel::expr::KExpr; - use crate::ix::kernel::id::KId; - use crate::ix::kernel::level::KUniv; + use ix_kernel::expr::KExpr; + use ix_kernel::id::KId; + use ix_kernel::level::KUniv; match expr.as_data() { ExprData::Fvar(fname, _) => { @@ -3225,7 +3217,7 @@ fn to_kexpr_static( KExpr::prj(zid, nat_to_u64(idx), ke) }, ExprData::Lit(lit, _) => { - use crate::ix::env::Literal; + use ix_common::env::Literal; match lit { Literal::NatVal(n) => { let addr = Address::hash(&nat_to_u64(n).to_le_bytes()); @@ -3277,7 +3269,7 @@ fn collect_lean_const_refs(expr: &LeanExpr, out: &mut FxHashSet) { #[cfg(test)] mod tests { use super::*; - use crate::ix::env::BinderInfo; + use ix_common::env::BinderInfo; fn mk_name_for(s: &str) -> Name { let mut n = Name::anon(); @@ -3654,7 +3646,7 @@ mod tests { fn consume_type_annotations_strips_known_wrappers() { // `outParam α` reduces to `α`. We use a stub inductive name that the // function recognizes. - use crate::ix::env::BinderInfo; + use ix_common::env::BinderInfo; let inner = sort0(); let wrapped = LeanExpr::app( LeanExpr::cnst(mk_name_for("outParam"), vec![]), diff --git a/src/ix/compile/aux_gen/nested.rs b/crates/compile/src/compile/aux_gen/nested.rs similarity index 98% rename from src/ix/compile/aux_gen/nested.rs rename to crates/compile/src/compile/aux_gen/nested.rs index 726f70a1..5a57d2c2 100644 --- a/src/ix/compile/aux_gen/nested.rs +++ b/crates/compile/src/compile/aux_gen/nested.rs @@ -14,26 +14,26 @@ //! - Spec_params are built in FVar space during detection, then abstracted back //! to BVars for the returned `CompileFlatMember`. +use bignat::Nat; use blake3::Hash; -use lean_ffi::nat::Nat; use rustc_hash::{FxHashMap, FxHashSet}; use super::expr_utils::{ LocalDecl, batch_abstract, decompose_apps, forall_telescope, instantiate_pi_params, instantiate1, mk_forall, subst_levels, }; -use crate::ix::compile::nat_conv::{nat_to_u64, nat_to_usize}; -use crate::ix::env::{ +use crate::compile::nat_conv::{nat_to_u64, nat_to_usize}; +use ix_common::env::{ ConstantInfo, Env as LeanEnv, Expr as LeanExpr, ExprData, Level, Name, }; -use crate::ix::ixon::CompileError; +use ixon::CompileError; /// A member of the flat block (original inductive or nested auxiliary). /// /// Spec_params use BVars relative to the block's parameter context: /// `BVar(0)` = innermost (last) param, `BVar(n_params-1)` = outermost (first). #[derive(Clone)] -pub(crate) struct CompileFlatMember { +pub struct CompileFlatMember { pub name: Name, pub spec_params: Vec, pub occurrence_level_args: Vec, @@ -51,7 +51,7 @@ pub(crate) struct CompileFlatMember { /// Matches the C++ kernel's `elim_nested_inductive_result`: auxiliary types /// like `_nested.Array_1` replace `Array (Part α β)` so that the recursor /// generator can treat all members uniformly. -pub(crate) struct ExpandedBlock { +pub struct ExpandedBlock { /// All types in the expanded block: originals first, then auxiliaries. pub types: Vec, /// `aux_name → nested_expr`: the original nested application with block @@ -82,7 +82,7 @@ pub(crate) struct ExpandedBlock { /// All members share the same `level_params` and `n_params` — auxiliaries /// have the block's parameters, not the external inductive's own parameters. #[derive(Clone)] -pub(crate) struct ExpandedMember { +pub struct ExpandedMember { /// Inductive name: original name for originals, `_nested.ExtInd_N` for /// auxiliaries (scoped under `all[0]`). pub name: Name, @@ -101,7 +101,7 @@ pub(crate) struct ExpandedMember { /// A constructor in the expanded block. #[derive(Clone)] -pub(crate) struct ExpandedCtor { +pub struct ExpandedCtor { /// Constructor name: for auxiliaries, prefixed with aux name. pub name: Name, /// Constructor type with nested refs replaced by aux const applications. @@ -424,7 +424,7 @@ impl<'a> ExpandCtx<'a> { /// /// Matches the C++ kernel's `elim_nested_inductive_fn::operator()()` at /// `refs/lean4/src/kernel/inductive.cpp:1045-1077`. -pub(crate) fn expand_nested_block( +pub fn expand_nested_block( ordered_originals: &[Name], lean_env: &LeanEnv, alias_to_rep: &FxHashMap, @@ -612,9 +612,9 @@ pub(crate) fn expand_nested_block( /// appear in user-visible env: `RestoreCtx` converts them back to /// `ExtInd spec_params` expressions during recursor emission. So renaming /// them by canonical index is purely an internal-labeling change. -pub(crate) fn sort_aux_by_partition_refinement( +pub fn sort_aux_by_partition_refinement( expanded: &mut ExpandedBlock, - stt: &crate::ix::compile::CompileState, + stt: &crate::compile::CompileState, ) -> Result, CompileError> { let n_originals = expanded.n_originals; let n_total = expanded.types.len(); @@ -629,9 +629,9 @@ pub(crate) fn sort_aux_by_partition_refinement( // fixed positional MutRef, so alpha-equivalent originals collapse to the same // aux signature. If any referenced original is unresolved, compare_expr now // errors instead of falling back to namespace-sensitive name hashes. - use crate::ix::compile::{BlockCache, sort_consts}; - use crate::ix::env::{ConstantVal, ConstructorVal, InductiveVal}; - use crate::ix::mutual::{Ind, MutConst}; + use crate::compile::{BlockCache, sort_consts}; + use crate::mutual::{Ind, MutConst}; + use ix_common::env::{ConstantVal, ConstructorVal, InductiveVal}; let level_params = expanded.level_params.clone(); @@ -780,7 +780,7 @@ pub(crate) fn sort_aux_by_partition_refinement( let nested_prefix = { let first_aux_name = &expanded.types[n_originals].name; match first_aux_name.as_data() { - crate::ix::env::NameData::Str(prefix, _, _) => prefix.clone(), + ix_common::env::NameData::Str(prefix, _, _) => prefix.clone(), _ => { return Err(CompileError::InvalidMutualBlock { reason: format!( @@ -806,7 +806,7 @@ pub(crate) fn sort_aux_by_partition_refinement( // Extract the "" identifier from old suffix. let ext_name = match old_name.as_data() { - crate::ix::env::NameData::Str(_, suffix, _) => { + ix_common::env::NameData::Str(_, suffix, _) => { // Old suffix is "_" — strip the trailing "_". let s: &str = suffix.as_ref(); // Find the last underscore — everything before is "". @@ -940,7 +940,7 @@ pub(crate) fn sort_aux_by_partition_refinement( /// /// `original_all` is the source-order Lean `InductiveVal.all` list — /// not alpha-collapsed representatives, and not canonical-aux-sorted. -pub(crate) fn source_aux_order( +pub fn source_aux_order( original_all: &[Name], lean_env: &LeanEnv, ) -> Result)>, CompileError> { @@ -954,7 +954,7 @@ pub(crate) fn source_aux_order( /// Like [`source_aux_order`], but also reports the source mutual-block member /// whose constructor walk first discovered each auxiliary. -pub(crate) fn source_aux_order_with_owner( +pub fn source_aux_order_with_owner( original_all: &[Name], lean_env: &LeanEnv, ) -> Result)>, CompileError> { @@ -990,7 +990,7 @@ fn source_aux_order_from_expanded( /// source auxes whose spec_params reference inductives that belong to /// a different SCC block — those auxes are handled by that block's /// compilation, not ours. -pub(crate) const PERM_OUT_OF_SCC: usize = usize::MAX; +pub const PERM_OUT_OF_SCC: usize = usize::MAX; /// Compute the permutation mapping Lean-source aux-walk positions to /// canonical aux positions. Returns `perm: Vec` @@ -1024,11 +1024,11 @@ pub(crate) const PERM_OUT_OF_SCC: usize = usize::MAX; /// Returns an error if some canonical aux has no matching source. This /// shouldn't happen because canonical members are always a subset (via /// dedup) of what a full source walk would find. -pub(crate) fn compute_aux_perm( +pub fn compute_aux_perm( expanded: &ExpandedBlock, original_all: &[Name], lean_env: &LeanEnv, - stt: &crate::ix::compile::CompileState, + stt: &crate::compile::CompileState, orig_to_canon_names: &std::collections::HashMap, ) -> Result, CompileError> { let n_originals = expanded.n_originals; @@ -1208,12 +1208,12 @@ pub(crate) fn compute_aux_perm( fn aux_spec_eq( canon: &LeanExpr, src: &LeanExpr, - stt: &crate::ix::compile::CompileState, + stt: &crate::compile::CompileState, source_to_canon_fvar: &FxHashMap, cache: &mut FxHashMap<(Hash, Hash), bool>, ) -> bool { - let canon = crate::ix::congruence::strip_mdata(canon); - let src = crate::ix::congruence::strip_mdata(src); + let canon = crate::congruence::strip_mdata(canon); + let src = crate::congruence::strip_mdata(src); let key = (*canon.get_hash(), *src.get_hash()); if let Some(cached) = cache.get(&key) { @@ -1226,7 +1226,7 @@ fn aux_spec_eq( source_to_canon_fvar.get(b).map_or(a == b, |expected| a == expected) }, (ExprData::Sort(a, _), ExprData::Sort(b, _)) => { - crate::ix::congruence::level_alpha_eq(a, b).is_ok() + crate::congruence::level_alpha_eq(a, b).is_ok() }, ( ExprData::Const(a_name, a_lvls, _), @@ -1236,7 +1236,7 @@ fn aux_spec_eq( || a_lvls .iter() .zip(b_lvls.iter()) - .any(|(a, b)| crate::ix::congruence::level_alpha_eq(a, b).is_err()) + .any(|(a, b)| crate::congruence::level_alpha_eq(a, b).is_err()) { return false; } @@ -1669,7 +1669,7 @@ struct FvarFlatMember { /// caught by checking for non-param FVars in the detected spec_params. /// /// Ported from the kernel's `build_flat_block` (`src/ix/kernel/inductive.rs:364-475`). -pub(crate) fn build_compile_flat_block( +pub fn build_compile_flat_block( ordered_originals: &[Name], lean_env: &LeanEnv, ) -> Result, CompileError> { @@ -1680,7 +1680,7 @@ pub(crate) fn build_compile_flat_block( /// environment first for all lookups. Used by the expand/restore path /// to scan expanded constructor types (where nested refs are already /// replaced with auxiliary const applications). -pub(crate) fn build_compile_flat_block_with_overlay( +pub fn build_compile_flat_block_with_overlay( ordered_originals: &[Name], lean_env: &LeanEnv, overlay: Option<&LeanEnv>, @@ -1905,7 +1905,7 @@ fn abstract_spec_params_to_bvars( /// This function computes the pointwise max of `occurrence_level_args` across /// all auxiliaries with the same `name`, then updates all of them. fn maximize_occurrence_levels(flat: &mut [FvarFlatMember], n_originals: usize) { - use crate::ix::env::LevelData; + use ix_common::env::LevelData; use rustc_hash::FxHashMap; // Group auxiliary members by external inductive name. @@ -2080,10 +2080,10 @@ fn try_detect_nested_fvar( #[cfg(test)] mod tests { use super::*; - use crate::ix::env::{ + use bignat::Nat; + use ix_common::env::{ AxiomVal, ConstantVal, InductiveVal, Level as LL, Name, }; - use lean_ffi::nat::Nat; fn mk_name_for(s: &str) -> Name { let mut n = Name::anon(); @@ -2134,7 +2134,7 @@ mod tests { mk_name_for("x"), sort0(), LeanExpr::cnst(mk_name_for("Target"), vec![]), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ); assert!(expr_mentions_any_name(&e, &names_of([mk_name_for("Target")]))); } @@ -2219,7 +2219,7 @@ mod tests { mk_name_for("x"), sort0(), LeanExpr::bvar(Nat::from(0u64)), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ); assert!(!has_invalid_spec_ref(&e, &[])); } @@ -2231,7 +2231,7 @@ mod tests { mk_name_for("x"), sort0(), LeanExpr::fvar(unknown), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ); assert!(has_invalid_spec_ref(&e, &[])); } @@ -2285,7 +2285,7 @@ mod tests { mk_name_for("_"), LeanExpr::cnst(nat_name.clone(), vec![]), LeanExpr::cnst(nat_name.clone(), vec![]), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, ), }, is_unsafe: false, diff --git a/src/ix/compile/aux_gen/rec_on.rs b/crates/compile/src/compile/aux_gen/rec_on.rs similarity index 97% rename from src/ix/compile/aux_gen/rec_on.rs rename to crates/compile/src/compile/aux_gen/rec_on.rs index eec09eaa..2bc934a9 100644 --- a/src/ix/compile/aux_gen/rec_on.rs +++ b/crates/compile/src/compile/aux_gen/rec_on.rs @@ -7,8 +7,8 @@ //! the FVar/declaration arrays, then close back with mk_forall/mk_lambda. //! Follows `refs/lean4/src/Lean/Meta/Constructions/RecOn.lean`. -use crate::ix::compile::aux_gen::AuxDef; -use crate::ix::env::{Level, Name, RecursorVal}; +use crate::compile::aux_gen::AuxDef; +use ix_common::env::{Level, Name, RecursorVal}; use super::expr_utils::{ forall_telescope, mk_app_n, mk_const, mk_forall, mk_lambda, @@ -17,10 +17,7 @@ use super::expr_utils::{ /// Generate a `.recOn` definition from a canonical `.rec`. /// /// Returns `None` if the recursor type cannot be decomposed. -pub(crate) fn generate_rec_on( - name: &Name, - rec_val: &RecursorVal, -) -> Option { +pub fn generate_rec_on(name: &Name, rec_val: &RecursorVal) -> Option { let n_params = rec_val.num_params.to_u64()? as usize; let n_motives = rec_val.num_motives.to_u64()? as usize; let n_minors = rec_val.num_minors.to_u64()? as usize; @@ -79,8 +76,8 @@ pub(crate) fn generate_rec_on( #[cfg(test)] mod tests { use super::*; - use crate::ix::env::{BinderInfo, ConstantVal, Expr as LeanExpr, ExprData}; - use lean_ffi::nat::Nat; + use bignat::Nat; + use ix_common::env::{BinderInfo, ConstantVal, Expr as LeanExpr, ExprData}; fn mk_name(s: &str) -> Name { Name::str(Name::anon(), s.to_string()) diff --git a/src/ix/compile/aux_gen/recursor.rs b/crates/compile/src/compile/aux_gen/recursor.rs similarity index 96% rename from src/ix/compile/aux_gen/recursor.rs rename to crates/compile/src/compile/aux_gen/recursor.rs index c5c51539..6c8924b6 100644 --- a/src/ix/compile/aux_gen/recursor.rs +++ b/crates/compile/src/compile/aux_gen/recursor.rs @@ -11,16 +11,14 @@ //! Key difference from C++: we use FVar-based intermediate computation //! (see `expr_utils.rs`) then abstract back into de Bruijn binder chains. -use crate::ix::compile::nat_conv::{ - nat_to_u64, nat_to_usize, try_nat_to_usize, -}; -use crate::ix::env::{ +use crate::compile::nat_conv::{nat_to_u64, nat_to_usize, try_nat_to_usize}; +use bignat::Nat; +use ix_common::env::{ BinderInfo, ConstantInfo, ConstantVal, ConstructorVal, Env as LeanEnv, Expr as LeanExpr, ExprData, InductiveVal, Level, Name, NameData, RecursorRule, RecursorVal, }; -use crate::ix::ixon::CompileError; -use lean_ffi::nat::Nat; +use ixon::CompileError; use super::expr_utils::{ LocalDecl, decompose_apps, fresh_fvar, instantiate_spec_with_fvars, @@ -45,7 +43,7 @@ use super::expr_utils::{ /// /// The caller is responsible for applying `RestoreCtx::restore` to the /// output to replace auxiliary const references with original nested apps. -pub(crate) fn generate_recursors_from_expanded( +pub fn generate_recursors_from_expanded( sorted_classes: &[Vec], expanded: &super::nested::ExpandedBlock, // `source_of_canonical[canonical_i]` = Lean source-walk index `source_j` @@ -57,8 +55,8 @@ pub(crate) fn generate_recursors_from_expanded( // no nested-aux hash-sort permutation. source_of_canonical: Option<&[usize]>, lean_env: &LeanEnv, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) -> Result<(Vec<(Name, RecursorVal)>, bool), CompileError> { if expanded.types.is_empty() { return Ok((vec![], false)); @@ -292,11 +290,11 @@ struct FlatInfo { /// directly and pass the appropriate `KernelCtx` — this wrapper is kept /// only so unit tests don't have to plumb one through. #[cfg(test)] -pub(crate) fn generate_canonical_recursors( +pub fn generate_canonical_recursors( sorted_classes: &[Vec], lean_env: &LeanEnv, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) -> Result<(Vec<(Name, RecursorVal)>, bool), CompileError> { generate_canonical_recursors_with_overlay( sorted_classes, @@ -342,7 +340,7 @@ pub(crate) fn generate_canonical_recursors( fn reorder_flat_by_layout( flat: Vec, n_classes: usize, - layout: &crate::ix::ixon::env::AuxLayout, + layout: &ixon::env::AuxLayout, ) -> Result< Vec, (Vec, String), @@ -434,13 +432,13 @@ fn reorder_flat_by_layout( Ok(primary) } -pub(crate) fn generate_canonical_recursors_with_overlay( +pub fn generate_canonical_recursors_with_overlay( sorted_classes: &[Vec], lean_env: &LeanEnv, overlay: Option<&LeanEnv>, pre_flat: Option>, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) -> Result<(Vec<(Name, RecursorVal)>, bool), CompileError> { generate_canonical_recursors_with_layout( sorted_classes, @@ -455,20 +453,20 @@ pub(crate) fn generate_canonical_recursors_with_overlay( } /// Like [`generate_canonical_recursors_with_overlay`] but accepts an -/// optional [`crate::ix::ixon::env::AuxLayout`] that reorders the aux +/// optional [`ixon::env::AuxLayout`] that reorders the aux /// section of the flat block per its `perm` before recursor generation. /// /// This is the hook decompile uses to pin its canonical layout to /// compile's first-run result. With `aux_layout = None`, falls back to /// the discovery order produced by `build_compile_flat_block_with_overlay`. -pub(crate) fn generate_canonical_recursors_with_layout( +pub fn generate_canonical_recursors_with_layout( sorted_classes: &[Vec], lean_env: &LeanEnv, overlay: Option<&LeanEnv>, pre_flat: Option>, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, - aux_layout: Option<&crate::ix::ixon::env::AuxLayout>, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, + aux_layout: Option<&ixon::env::AuxLayout>, // Optional Lean-source index per canonical aux position, used for // emitting `all0.rec_{source_j + 1}` names directly. If provided // alongside `aux_layout`, both must agree (this parameter takes @@ -950,8 +948,8 @@ fn build_rec_type( rec_level_params: &[Name], lean_env: &LeanEnv, overlay: Option<&LeanEnv>, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, nested_rewrite: Option<&mut NestedRewriteCtx>, ) -> LeanExpr { let env_get = |name: &Name| -> Option { @@ -1350,8 +1348,8 @@ fn build_minor_type( motive_fvars: &[LeanExpr], ind_univs: &[Level], rec_level_params: &[Name], - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, // Shared scratch for nested-aux level rewrites across every ctor in // the block. `None` when the block doesn't need any rewriting. nested_rewrite: Option<&mut NestedRewriteCtx>, @@ -1631,8 +1629,8 @@ fn build_rec_rules( // Lean-source-indexed aux naming (see caller doc). `None` falls back // to `canonical_i + 1`. source_of_canonical: Option<&[usize]>, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, nested_rewrite: Option<&mut NestedRewriteCtx>, ) -> Result, CompileError> { let _ = n_classes; // Kept for signature parity with `build_rec_type`. @@ -2002,7 +2000,7 @@ fn build_rule_ih_fvar( /// Lean's `appendAfter`: append a suffix string to a Name. /// /// Matches `Init/Meta/Defs.lean:317-320`: -/// ``` +/// ```text /// def appendAfter (n : Name) (suffix : String) : Name := /// n.modifyBase fun /// | str p s => Name.mkStr p (s ++ suffix) @@ -2096,7 +2094,7 @@ fn find_rec_target( param_fvars: &[LeanExpr], n_params: usize, scope: &mut super::expr_utils::TcScope<'_>, - _stt: &crate::ix::compile::CompileState, + _stt: &crate::compile::CompileState, ) -> Option { // Phase 1: syntactic peel + match. let mut ty = dom.clone(); @@ -2330,15 +2328,15 @@ fn compute_is_large_and_k( n_classes: usize, n_params: usize, lean_env: &LeanEnv, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) -> Result<(bool, bool, bool), CompileError> { - use crate::ix::kernel::constant::KConst; - use crate::ix::kernel::id::KId; - use crate::ix::kernel::ingress::{ + use ix_kernel::constant::KConst; + use ix_kernel::id::KId; + use ix_kernel::ingress::{ lean_expr_to_zexpr_with_kenv, resolve_lean_name_addr, }; - use crate::ix::kernel::mode::Meta; + use ix_kernel::mode::Meta; let n2a = Some(&stt.name_to_addr); let aux_n2a = Some(&stt.aux_name_to_addr); @@ -2351,7 +2349,7 @@ fn compute_is_large_and_k( u64, u64, Vec>, - crate::ix::kernel::expr::KExpr, + ix_kernel::expr::KExpr, bool, )> = Vec::new(); @@ -2459,7 +2457,7 @@ fn compute_is_large_and_k( let first_n_indices = ind_infos[0].2; // Use the TC for the appropriate context. - let mut tc = crate::ix::kernel::tc::TypeChecker::new(&mut kctx.kenv); + let mut tc = ix_kernel::tc::TypeChecker::new(&mut kctx.kenv); // Compute the WHNF-reduced result sort level via the kernel. This peels // params+indices with whnf at each step — crucial for inductives whose @@ -2533,7 +2531,7 @@ fn compute_is_large_and_k( && is_prop; let _cilk_elapsed = _cilk_start.elapsed(); - if *crate::ix::compile::IX_TIMING && _cilk_elapsed.as_secs_f32() > 0.1 { + if *crate::compile::IX_TIMING && _cilk_elapsed.as_secs_f32() > 0.1 { eprintln!( "[compute_is_large_and_k] {:?} total={:.3}s ingress={:.3}s n_classes={} kenv_size={}", classes[0].ind.cnst.name.pretty(), @@ -2553,8 +2551,8 @@ fn compute_is_large_and_k( fn ingress_target_type_deps( target_ty: &LeanExpr, lean_env: &LeanEnv, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) { let mut seen = rustc_hash::FxHashSet::default(); let mut queue = Vec::new(); @@ -2578,8 +2576,8 @@ fn ingress_field_deps( class: &FlatInfo, _lvl_params: &[Name], lean_env: &LeanEnv, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) { let mut seen = rustc_hash::FxHashSet::default(); let mut queue: Vec = Vec::new(); @@ -2603,8 +2601,8 @@ fn ingress_aux_gen_dep( name: &Name, ci: &ConstantInfo, lean_env: &LeanEnv, - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, queue: &mut Vec, ) { match ci { @@ -2653,15 +2651,15 @@ fn ingress_type_stub( name: &Name, typ: &LeanExpr, level_params: &[Name], - stt: &crate::ix::compile::CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + stt: &crate::compile::CompileState, + kctx: &mut crate::compile::KernelCtx, ) { - use crate::ix::kernel::constant::KConst; - use crate::ix::kernel::id::KId; - use crate::ix::kernel::ingress::{ + use ix_kernel::constant::KConst; + use ix_kernel::id::KId; + use ix_kernel::ingress::{ lean_expr_to_zexpr_with_kenv, resolve_lean_name_addr, }; - use crate::ix::kernel::mode::Meta; + use ix_kernel::mode::Meta; let n2a = Some(&stt.name_to_addr); let aux_n2a = Some(&stt.aux_name_to_addr); @@ -2733,9 +2731,9 @@ fn collect_const_refs(expr: &LeanExpr, out: &mut Vec) { /// referenced by that same comment for the historical record. #[allow(dead_code)] fn peek_result_sort( - ty: &crate::ix::kernel::expr::KExpr, -) -> Option> { - use crate::ix::kernel::expr::ExprData as ZED; + ty: &ix_kernel::expr::KExpr, +) -> Option> { + use ix_kernel::expr::ExprData as ZED; let mut cur = ty.clone(); loop { match cur.data() { @@ -2749,7 +2747,7 @@ fn peek_result_sort( #[cfg(test)] mod tests { use super::*; - use crate::ix::compile::aux_gen::below::{ + use crate::compile::aux_gen::below::{ BelowConstant, generate_below_constants, }; @@ -2877,7 +2875,7 @@ mod tests { } fn insert_aux_stub_def(env: &mut LeanEnv, ind: &Name, suffix: &str) -> Name { - use crate::ix::env::{DefinitionSafety, DefinitionVal, ReducibilityHints}; + use ix_common::env::{DefinitionSafety, DefinitionVal, ReducibilityHints}; let def_name = Name::str(ind.clone(), suffix.into()); env.insert( @@ -3477,8 +3475,8 @@ mod tests { env.insert(ctor_name, ConstantInfo::CtorInfo(ctor)); let classes = vec![vec![ind_name]]; - let tmp_stt = crate::ix::compile::CompileState::default(); - let mut kctx = crate::ix::compile::KernelCtx::new(); + let tmp_stt = crate::compile::CompileState::default(); + let mut kctx = crate::compile::KernelCtx::new(); let (result, _is_prop) = generate_canonical_recursors(&classes, &env, &tmp_stt, &mut kctx) .unwrap(); @@ -3503,8 +3501,8 @@ mod tests { #[test] fn test_aux_gen_alpha_collapse() { let (env, a, b) = build_alpha_collapse_env(); - let stt = crate::ix::compile::CompileState::default(); - let mut kctx = crate::ix::compile::KernelCtx::new(); + let stt = crate::compile::CompileState::default(); + let mut kctx = crate::compile::KernelCtx::new(); // After sort_consts collapse, A≅B → 1 class. let classes = vec![vec![a.clone(), b.clone()]]; @@ -3563,12 +3561,12 @@ mod tests { #[test] fn test_alpha_collapse_sort_consts_groups_inductives() { - use crate::ix::compile::{BlockCache, mk_indc, sort_consts}; - use crate::ix::env::ConstantInfo as LeanCI; - use crate::ix::mutual::MutConst; + use crate::compile::{BlockCache, mk_indc, sort_consts}; + use crate::mutual::MutConst; + use ix_common::env::ConstantInfo as LeanCI; let (env, a, b) = build_alpha_collapse_env(); - let stt = crate::ix::compile::CompileState::default(); + let stt = crate::compile::CompileState::default(); let mut cache = BlockCache::default(); let mut cs = Vec::new(); @@ -3594,7 +3592,7 @@ mod tests { #[test] fn test_alpha_collapse_compile_env_addresses_inductives_and_ctors() { - use crate::ix::compile::env::compile_env; + use crate::compile::env::compile_env; let (env, a, b) = build_alpha_collapse_env(); let lean_env = std::sync::Arc::new(env); @@ -3617,7 +3615,7 @@ mod tests { #[test] fn test_alpha_collapse_aux_gen_aliases_primary_aux_to_rep() { - use crate::ix::compile::aux_gen::{self, PatchedConstant}; + use crate::compile::aux_gen::{self, PatchedConstant}; let (mut env, a, b) = build_alpha_collapse_env(); let all = vec![a.clone(), b.clone()]; @@ -3633,8 +3631,8 @@ mod tests { let a_brecon = insert_aux_stub_def(&mut env, &a, "brecOn"); let b_brecon = insert_aux_stub_def(&mut env, &b, "brecOn"); - let stt = crate::ix::compile::CompileState::default(); - let mut kctx = crate::ix::compile::KernelCtx::new(); + let stt = crate::compile::CompileState::default(); + let mut kctx = crate::compile::KernelCtx::new(); let out = aux_gen::generate_aux_patches( &[vec![a.clone(), b.clone()]], &all, @@ -3675,8 +3673,8 @@ mod tests { #[test] fn test_aux_gen_alpha_collapse_3() { let (env, a, b, c) = build_alpha_collapse_3_env(); - let stt = crate::ix::compile::CompileState::default(); - let mut kctx = crate::ix::compile::KernelCtx::new(); + let stt = crate::compile::CompileState::default(); + let mut kctx = crate::compile::KernelCtx::new(); // All 3 collapse into 1 class. let classes = vec![vec![a.clone(), b.clone(), c.clone()]]; @@ -3710,8 +3708,8 @@ mod tests { #[test] fn test_aux_gen_over_merge_alpha_collapse() { let (env, a, b, c) = build_over_merge_alpha_collapse_env(); - let stt = crate::ix::compile::CompileState::default(); - let mut kctx = crate::ix::compile::KernelCtx::new(); + let stt = crate::compile::CompileState::default(); + let mut kctx = crate::compile::KernelCtx::new(); // A≅B collapse into 1 class, C is a separate class → 2 classes. let classes = vec![vec![a.clone(), b.clone()], vec![c.clone()]]; @@ -3767,8 +3765,8 @@ mod tests { #[test] fn test_aux_gen_over_merge() { let (env, a, b, c) = build_over_merge_env(); - let stt = crate::ix::compile::CompileState::default(); - let mut kctx = crate::ix::compile::KernelCtx::new(); + let stt = crate::compile::CompileState::default(); + let mut kctx = crate::compile::KernelCtx::new(); // No alpha-collapse: A≠B (B has 2 fields), A≠C, B≠C → 3 classes. let classes = vec![vec![a.clone()], vec![b.clone()], vec![c.clone()]]; @@ -3810,8 +3808,8 @@ mod tests { #[test] fn test_aux_gen_below_indc_prop() { let (env, a, b) = build_alpha_collapse_env(); - let stt = crate::ix::compile::CompileState::default(); - let mut kctx = crate::ix::compile::KernelCtx::new(); + let stt = crate::compile::CompileState::default(); + let mut kctx = crate::compile::KernelCtx::new(); let classes = vec![vec![a.clone(), b.clone()]]; let (recs, is_prop) = @@ -3846,8 +3844,8 @@ mod tests { #[test] fn test_aux_gen_below_def_type() { let (env, t) = build_type_nat_env(); - let stt = crate::ix::compile::CompileState::default(); - let mut kctx = crate::ix::compile::KernelCtx::new(); + let stt = crate::compile::CompileState::default(); + let mut kctx = crate::compile::KernelCtx::new(); let classes = vec![vec![t.clone()]]; let (recs, is_prop) = @@ -3888,8 +3886,8 @@ mod tests { #[test] fn test_aux_gen_is_prop_vs_is_large() { let (env, p) = build_prop_drec_env(); - let stt = crate::ix::compile::CompileState::default(); - let mut kctx = crate::ix::compile::KernelCtx::new(); + let stt = crate::compile::CompileState::default(); + let mut kctx = crate::compile::KernelCtx::new(); let classes = vec![vec![p.clone()]]; let (recs, is_prop) = @@ -3933,7 +3931,7 @@ mod tests { #[ignore] #[test] fn test_aux_gen_compile_roundtrip() { - use crate::ix::compile::env::compile_env; + use crate::compile::env::compile_env; use std::sync::Arc; let (mut env, a, b) = build_alpha_collapse_env(); @@ -3986,18 +3984,18 @@ mod tests { /// Type-level brecOn: Nat-like T generates .brecOn.go + .brecOn (no .eq without Eq in env). #[test] fn test_brecon_type_level() { - use crate::ix::compile::aux_gen::below::generate_below_constants; - use crate::ix::compile::aux_gen::brecon::generate_brecon_constants; + use crate::compile::aux_gen::below::generate_below_constants; + use crate::compile::aux_gen::brecon::generate_brecon_constants; let (env, t) = build_type_nat_env(); - let stt = crate::ix::compile::CompileState::default(); - let mut kctx = crate::ix::compile::KernelCtx::new(); + let stt = crate::compile::CompileState::default(); + let mut kctx = crate::compile::KernelCtx::new(); // Ingress prelude (PUnit, PProd) and the inductive into the kenv // so TcScope can resolve them during brecOn sort-level inference. - crate::ix::compile::aux_gen::expr_utils::ensure_prelude_in_kenv_of( + crate::compile::aux_gen::expr_utils::ensure_prelude_in_kenv_of( &stt, &mut kctx, ); - crate::ix::compile::aux_gen::expr_utils::ensure_in_kenv_of( + crate::compile::aux_gen::expr_utils::ensure_in_kenv_of( &t, &env, &stt, &mut kctx, ); @@ -4012,7 +4010,7 @@ mod tests { assert_eq!(below.len(), 1); // Populate kenv with .below types for brecOn generation. - crate::ix::compile::aux_gen::populate_canon_kenv_with_below( + crate::compile::aux_gen::populate_canon_kenv_with_below( &below, &classes, &std::sync::Arc::new(env.clone()), @@ -4046,12 +4044,12 @@ mod tests { /// Prop-level brecOn: alpha-collapse A/B generates single .brecOn per class. #[test] fn test_brecon_prop_alpha_collapse() { - use crate::ix::compile::aux_gen::below::generate_below_constants; - use crate::ix::compile::aux_gen::brecon::generate_brecon_constants; + use crate::compile::aux_gen::below::generate_below_constants; + use crate::compile::aux_gen::brecon::generate_brecon_constants; let (env, a, b) = build_alpha_collapse_env(); - let stt = crate::ix::compile::CompileState::default(); - let mut kctx = crate::ix::compile::KernelCtx::new(); + let stt = crate::compile::CompileState::default(); + let mut kctx = crate::compile::KernelCtx::new(); let classes = vec![vec![a.clone(), b.clone()]]; let (recs, is_prop) = @@ -4081,8 +4079,8 @@ mod tests { /// Non-recursive inductives should NOT generate brecOn. #[test] fn test_brecon_skipped_for_non_recursive() { - use crate::ix::compile::aux_gen::below::generate_below_constants; - use crate::ix::compile::aux_gen::brecon::generate_brecon_constants; + use crate::compile::aux_gen::below::generate_below_constants; + use crate::compile::aux_gen::brecon::generate_brecon_constants; // Build a simple non-recursive inductive: Unit | unit : Unit let unit = n("Unit"); @@ -4122,8 +4120,8 @@ mod tests { }), ); - let stt = crate::ix::compile::CompileState::default(); - let mut kctx = crate::ix::compile::KernelCtx::new(); + let stt = crate::compile::CompileState::default(); + let mut kctx = crate::compile::KernelCtx::new(); let classes = vec![vec![unit]]; let (recs, is_prop) = generate_canonical_recursors(&classes, &env, &stt, &mut kctx).unwrap(); @@ -4150,7 +4148,7 @@ mod tests { /// with real Lean environments that include .below and .brecOn constants. #[test] fn test_brecon_type_compile_roundtrip() { - use crate::ix::compile::env::compile_env; + use crate::compile::env::compile_env; use std::sync::Arc; let (mut env, t) = build_type_nat_env(); diff --git a/src/ix/compile/env.rs b/crates/compile/src/compile/env.rs similarity index 98% rename from src/ix/compile/env.rs rename to crates/compile/src/compile/env.rs index 7f0731b8..0b36bc23 100644 --- a/src/ix/compile/env.rs +++ b/crates/compile/src/compile/env.rs @@ -14,15 +14,15 @@ use dashmap::DashMap; use rayon::prelude::*; use rustc_hash::FxHashSet; -use crate::ix::address::Address; -use crate::ix::compile::{ +use crate::compile::{ BlockCache, CompileOptions, CompileState, compile_const, compile_const_no_aux, }; -use crate::ix::condense::compute_sccs; -use crate::ix::env::{Env as LeanEnv, Name}; -use crate::ix::graph::{NameSet, build_ref_graph}; -use crate::ix::ground::ground_consts; -use crate::ix::ixon::CompileError; +use crate::condense::compute_sccs; +use crate::graph::{NameSet, build_ref_graph}; +use crate::ground::ground_consts; +use ix_common::address::Address; +use ix_common::env::{Env as LeanEnv, Name}; +use ixon::CompileError; // =========================================================================== // Progress + diagnostic logging @@ -150,8 +150,7 @@ pub fn compile_env_with_options( // Filter ungrounded names from the ref graph before SCC computation so // condensed blocks only contain constants we can actually compile. - let grounded_out_refs: crate::ix::graph::RefMap = if ungrounded_map.is_empty() - { + let grounded_out_refs: crate::graph::RefMap = if ungrounded_map.is_empty() { graph.out_refs } else { graph @@ -455,7 +454,7 @@ pub fn compile_env_with_options( // Spawn worker threads for _ in 0..num_threads { s.spawn(move || { - let mut worker_kctx = crate::ix::compile::KernelCtx::new(); + let mut worker_kctx = crate::compile::KernelCtx::new(); loop { // Try to get work from the ready queue let work = { @@ -742,7 +741,7 @@ pub fn compile_env_with_options( // Check for slow blocks let elapsed = block_start.elapsed(); - if *crate::ix::compile::IX_TIMING && elapsed.as_secs_f32() > 1.0 { + 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 path={} cc={:.2}s", @@ -956,7 +955,7 @@ fn aux_gen_seed_names() -> Vec { /// references; we resolve each referenced name back to its own SCC-rep via /// `condensed.low_links`. fn precompile_aux_gen_prereqs( - condensed: &crate::ix::condense::CondensedBlocks, + condensed: &crate::condense::CondensedBlocks, lean_env: &Arc, stt: &CompileState, ) -> Result<(), CompileError> { @@ -1011,7 +1010,7 @@ fn precompile_aux_gen_prereqs( // Compile each SCC in dep-first order, moving compiled names to // `aux_name_to_addr` so later SCCs can resolve their Const refs. - let mut prereq_kctx = crate::ix::compile::KernelCtx::new(); + let mut prereq_kctx = crate::compile::KernelCtx::new(); for rep in order { if stt.aux_name_to_addr.contains_key(&rep) { continue; // Already compiled (e.g., via a prior prereq run). diff --git a/src/ix/compile/mutual.rs b/crates/compile/src/compile/mutual.rs similarity index 97% rename from src/ix/compile/mutual.rs rename to crates/compile/src/compile/mutual.rs index fa7ef7d9..68bbca19 100644 --- a/src/ix/compile/mutual.rs +++ b/crates/compile/src/compile/mutual.rs @@ -15,23 +15,24 @@ use std::sync::Arc; use rustc_hash::FxHashMap; -use lean_ffi::nat::Nat; - -use crate::ix::address::Address; -use crate::ix::compile::aux_gen::below::BelowIndc; -use crate::ix::compile::aux_gen::brecon::BRecOnDef; -use crate::ix::compile::aux_gen::recursor; -use crate::ix::compile::aux_gen::{self, PatchedConstant}; -use crate::ix::compile::{ +use bignat::Nat; + +use crate::compile::aux_gen::below::BelowIndc; +use crate::compile::aux_gen::brecon::BRecOnDef; +use crate::compile::aux_gen::recursor; +use crate::compile::aux_gen::{self, PatchedConstant}; +use crate::compile::{ BlockCache, CompileState, collect_mut_const_exprs, compile_definition, compile_inductive, compile_mutual_block, compile_name, compile_recursor, preseed_expr_tables, sort_consts, }; -use crate::ix::env::{ +use crate::mutual::{Def, Ind, MutConst}; +use ix_common::address::Address; +use ix_common::env::{ ConstantInfo as LeanConstantInfo, ConstantVal, ConstructorVal, DefinitionSafety, Env as LeanEnv, Name, ReducibilityHints, }; -use crate::ix::ixon::{ +use ixon::{ CompileError, constant::{ Constant, DefKind, MutConst as IxonMutConst, ctor_proj_constant, @@ -41,7 +42,6 @@ use crate::ix::ixon::{ metadata::{ConstantMeta, ConstantMetaInfo}, univ::Univ, }; -use crate::ix::mutual::{Def, Ind, MutConst}; // =========================================================================== // compile_aux_block @@ -57,11 +57,11 @@ use crate::ix::mutual::{Def, Ind, MutConst}; /// `stt.name_to_addr`) so they don't interfere with the scheduler's /// dependency tracking. The scheduler's promotion path in `env.rs` moves /// them to `name_to_addr` when the block is processed. -pub(crate) fn compile_aux_block( +pub fn compile_aux_block( aux_consts: &[MutConst], lean_env: &Arc, stt: &CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + kctx: &mut crate::compile::KernelCtx, ) -> Result<(), CompileError> { compile_aux_block_with_rename(aux_consts, lean_env, stt, kctx, None, None) } @@ -95,16 +95,16 @@ pub(crate) fn compile_aux_block( /// Without this reorder, `sort_consts` on recursors picks an independent /// canonical permutation that diverges from the inductive block's layout. /// See `docs/ix_canonicity.md` §6.2 and the rationale in -/// `kernel::inductive::populate_recursor_rules_from_block`. +/// `ix_kernel::inductive::populate_recursor_rules_from_block`. /// /// The class ordering produced by `sort_consts` is preserved as a /// stable tiebreak: classes that map to `u64::MAX` (no key entry) keep /// their `sort_consts` relative position at the tail. -pub(crate) fn compile_aux_block_with_rename( +pub fn compile_aux_block_with_rename( aux_consts: &[MutConst], lean_env: &Arc, stt: &CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + kctx: &mut crate::compile::KernelCtx, name_rename: Option<&FxHashMap>, class_order_key: Option<&dyn Fn(&MutConst) -> u64>, ) -> Result<(), CompileError> { @@ -199,7 +199,7 @@ pub(crate) fn compile_aux_block_with_rename( if ixon_mutuals.len() == 1 && !matches!(&ixon_mutuals[0], IxonMutConst::Indc(_)) { - use crate::ix::compile::{ + use crate::compile::{ apply_sharing_to_definition_with_stats, apply_sharing_to_recursor_with_stats, }; @@ -508,13 +508,13 @@ fn content_address(constant: &Constant) -> Address { /// /// Only runs for inductive blocks. Non-inductive mutual blocks return /// immediately. -pub(crate) fn generate_and_compile_aux_recursors( +pub fn generate_and_compile_aux_recursors( cs: &[MutConst], class_names: &[Vec], lean_env: &Arc, stt: &CompileState, - kctx: &mut crate::ix::compile::KernelCtx, -) -> Result, CompileError> { + kctx: &mut crate::compile::KernelCtx, +) -> Result, CompileError> { // Guard: aux_gen canonical generation only runs for blocks containing // inductives. Non-inductive blocks (plain defs, recursor-only SCCs, // etc.) have no canonical auxiliaries to generate. @@ -603,7 +603,7 @@ pub(crate) fn generate_and_compile_aux_recursors( // `original_all` (= `source_all` above) is hoisted to the enclosing // scope so the aux-name rename map construction below can reuse it. let original_all: Vec = source_all; - let mut aux_layout: Option = None; + let mut aux_layout: Option = None; if !original_all.is_empty() && let Some(perm) = aux_out.perm.clone() && !perm.is_empty() @@ -639,7 +639,7 @@ pub(crate) fn generate_and_compile_aux_recursors( }); } aux_layout = - Some(crate::ix::compile::surgery::AuxLayout { perm, source_ctor_counts }); + Some(crate::compile::surgery::AuxLayout { perm, source_ctor_counts }); } // NOTE: Historically, a canonical→source rename map was built here @@ -889,7 +889,7 @@ pub(crate) fn generate_and_compile_aux_recursors( // blocks). See the aux_gen.rs module docs for the full rationale. let total = aux_total_start.elapsed(); - if *crate::ix::compile::IX_TIMING && total.as_secs_f32() > 0.5 { + if *crate::compile::IX_TIMING && total.as_secs_f32() > 0.5 { eprintln!( "[aux_gen] {:?} total={:.2}s gen={:.2}s rec={:.2}s cases={:.2}s recOn={:.2}s below={:.2}s belowRec={:.2}s brecon={:.2}s patches={}", block_label, @@ -939,7 +939,7 @@ fn below_indc_to_mut_const( .collect(); MutConst::Indc(Ind { - ind: crate::ix::env::InductiveVal { + ind: ix_common::env::InductiveVal { cnst: ConstantVal { name: bi.name.clone(), level_params: bi.level_params.clone(), @@ -1054,7 +1054,7 @@ fn compile_below_recursors( below_indcs: &[MutConst], lean_env: &Arc, stt: &CompileState, - kctx: &mut crate::ix::compile::KernelCtx, + kctx: &mut crate::compile::KernelCtx, ) -> Result<(), CompileError> { // Build a small overlay with just the .below inductives + ctors. // These don't exist in the original lean_env, but generate_canonical_recursors diff --git a/src/ix/compile/nat_conv.rs b/crates/compile/src/compile/nat_conv.rs similarity index 82% rename from src/ix/compile/nat_conv.rs rename to crates/compile/src/compile/nat_conv.rs index b1a2be22..a96ec090 100644 --- a/src/ix/compile/nat_conv.rs +++ b/crates/compile/src/compile/nat_conv.rs @@ -5,14 +5,14 @@ //! `num_nested`) are always small values. These utilities make the conversion //! explicit rather than silently producing 0 on overflow. -use lean_ffi::nat::Nat; +use bignat::Nat; -use crate::ix::ixon::CompileError; +use ixon::CompileError; /// Convert a Lean `Nat` to `usize`, returning `CompileError` on overflow. /// /// Use in functions that return `Result<_, CompileError>`. -pub(crate) fn try_nat_to_usize(n: &Nat) -> Result { +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(), }) @@ -23,11 +23,11 @@ pub(crate) fn try_nat_to_usize(n: &Nat) -> Result { /// 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(crate) fn nat_to_usize(n: &Nat) -> usize { +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(crate) fn nat_to_u64(n: &Nat) -> u64 { +pub fn nat_to_u64(n: &Nat) -> u64 { n.to_u64().expect("Nat field exceeds u64") } diff --git a/src/ix/compile/surgery.rs b/crates/compile/src/compile/surgery.rs similarity index 98% rename from src/ix/compile/surgery.rs rename to crates/compile/src/compile/surgery.rs index fb204f14..94a5029d 100644 --- a/src/ix/compile/surgery.rs +++ b/crates/compile/src/compile/surgery.rs @@ -23,12 +23,12 @@ use std::sync::Arc; use rustc_hash::FxHashMap; -use crate::ix::env::{ +use ix_common::env::{ ConstantInfo as LeanConstantInfo, ConstructorVal, Env as LeanEnv, Expr as LeanExpr, ExprData, Level, Name, NameData, RecursorVal, }; -use crate::ix::ixon::error::CompileError; -use crate::ix::ixon::expr::Expr as IxonExpr; +use ixon::error::CompileError; +use ixon::expr::Expr as IxonExpr; use super::{ aux_gen::expr_utils::{ @@ -151,7 +151,7 @@ impl BRecOnCallSitePlan { } } -pub(crate) fn rec_name_to_brecon_name(name: &Name) -> Option { +pub fn rec_name_to_brecon_name(name: &Name) -> Option { match name.as_data() { NameData::Str(parent, s, _) if s == "rec" => { Some(Name::str(parent.clone(), "brecOn".to_string())) @@ -163,7 +163,7 @@ pub(crate) fn rec_name_to_brecon_name(name: &Name) -> Option { } } -pub(crate) fn rec_name_to_below_name(name: &Name) -> Option { +pub fn rec_name_to_below_name(name: &Name) -> Option { match name.as_data() { NameData::Str(parent, s, _) if s == "rec" => { Some(Name::str(parent.clone(), "below".to_string())) @@ -182,7 +182,7 @@ pub(crate) fn rec_name_to_below_name(name: &Name) -> Option { /// Collect a Lean App telescope: peel App nodes to get `(head, [a1, ..., aN])`. /// /// Arguments are returned in application order (leftmost first). -pub(crate) fn collect_lean_telescope<'a>( +pub fn collect_lean_telescope<'a>( e: &'a LeanExpr, ) -> (&'a LeanExpr, Vec<&'a LeanExpr>) { let mut args: Vec<&'a LeanExpr> = Vec::new(); @@ -199,7 +199,7 @@ pub(crate) fn collect_lean_telescope<'a>( /// /// Arguments are returned in application order (leftmost first). #[allow(dead_code)] -pub(crate) fn collect_ixon_telescope( +pub fn collect_ixon_telescope( e: &Arc, ) -> (Arc, Vec>) { let mut args: Vec> = Vec::new(); @@ -237,14 +237,14 @@ pub(crate) fn collect_ixon_telescope( /// its own plan). We skip generating a plan for a phantom `X.rec` /// itself, since that belongs to the block owning `X`. /// -/// The [`AuxLayout`] type is re-exported from `crate::ix::ixon::env` so it +/// The [`AuxLayout`] type is re-exported from `ixon::env` so it /// can live in the Ixon env side-table and survive serialization — see the -/// doc on [`crate::ix::ixon::env::AuxLayout`] for the canonical definition. -pub(crate) use crate::ix::ixon::env::AuxLayout; +/// doc on [`ixon::env::AuxLayout`] for the canonical definition. +pub use ixon::env::AuxLayout; const PERM_OUT_OF_SCC: usize = usize::MAX; -pub(crate) fn compute_call_site_plans( +pub fn compute_call_site_plans( sorted_classes: &[Vec], original_all: &[Name], lean_env: &LeanEnv, @@ -706,7 +706,7 @@ pub(crate) fn compute_call_site_plans( /// recursor call then goes through the normal call-site surgery for its own /// SCC. #[allow(clippy::too_many_arguments)] -pub(crate) fn adapt_split_minor( +pub fn adapt_split_minor( rec_name: &Name, rec_levels: &[Level], plan: &CallSitePlan, @@ -1115,8 +1115,8 @@ fn dump_plan_state( #[cfg(test)] mod tests { use super::*; - use crate::ix::env::{ConstantVal, ConstructorVal, InductiveVal}; - use lean_ffi::nat::Nat; + use bignat::Nat; + use ix_common::env::{ConstantVal, ConstructorVal, InductiveVal}; fn n(s: &str) -> Name { Name::str(Name::anon(), s.to_string()) @@ -1208,8 +1208,8 @@ mod tests { fn build_test_env( names: &[&str], ctor_counts: &[usize], - ) -> crate::ix::env::Env { - let mut env = crate::ix::env::Env::default(); + ) -> ix_common::env::Env { + let mut env = ix_common::env::Env::default(); let all: Vec = names.iter().map(|s| n(s)).collect(); for (i, &name_str) in names.iter().enumerate() { @@ -1398,7 +1398,7 @@ mod tests { ctor_counts: &[usize], aux_motives: usize, aux_minors: usize, - ) -> crate::ix::env::Env { + ) -> ix_common::env::Env { let mut env = build_test_env(names, ctor_counts); // Overwrite each inductive's recursor with inflated motive/minor counts. let total_motives = (names.len() + aux_motives) as u64; diff --git a/src/ix/condense.rs b/crates/compile/src/condense.rs similarity index 98% rename from src/ix/condense.rs rename to crates/compile/src/condense.rs index ab0a6b27..f293aaa0 100644 --- a/src/ix/condense.rs +++ b/crates/compile/src/condense.rs @@ -5,11 +5,10 @@ use rustc_hash::FxHashMap; -use crate::{ - FxIndexSet, - ix::env::Name, - ix::graph::{NameSet, RefMap}, -}; +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 { diff --git a/src/ix/congruence.rs b/crates/compile/src/congruence.rs similarity index 98% rename from src/ix/congruence.rs rename to crates/compile/src/congruence.rs index 4f075020..c74c2ee3 100644 --- a/src/ix/congruence.rs +++ b/crates/compile/src/congruence.rs @@ -14,8 +14,8 @@ pub mod perm; -use crate::ix::env::{ConstantInfo, Expr, ExprData, Level, LevelData, Literal}; -use lean_ffi::nat::Nat; +use bignat::Nat; +use ix_common::env::{ConstantInfo, Expr, ExprData, Level, LevelData, Literal}; /// Check that two Lean levels are equal modulo the same simplifications /// `Level::max_smart` / `Level::imax_smart` perform. @@ -334,7 +334,7 @@ pub fn const_alpha_eq( // ========================================================================= /// Strip Mdata wrappers from an expression. -pub(crate) fn strip_mdata(e: &Expr) -> &Expr { +pub fn strip_mdata(e: &Expr) -> &Expr { let mut cur = e; while let ExprData::Mdata(_, inner, _) = cur.as_data() { cur = inner; @@ -342,11 +342,7 @@ pub(crate) fn strip_mdata(e: &Expr) -> &Expr { cur } -pub(crate) fn check_nat_eq( - a: &Nat, - b: &Nat, - field: &str, -) -> Result<(), String> { +pub fn check_nat_eq(a: &Nat, b: &Nat, field: &str) -> Result<(), String> { let av = a.to_u64().unwrap_or(u64::MAX); let bv = b.to_u64().unwrap_or(u64::MAX); if av != bv { @@ -411,7 +407,7 @@ mod tests { //! `Level::imax_smart` (see `src/ix/env.rs:340-404`), so they double //! as a contract test for those constructors. use super::*; - use crate::ix::env::Name; + use ix_common::env::Name; fn p(s: &str) -> Level { Level::param(Name::str(Name::anon(), s.to_string())) } diff --git a/src/ix/congruence/perm.rs b/crates/compile/src/congruence/perm.rs similarity index 99% rename from src/ix/congruence/perm.rs rename to crates/compile/src/congruence/perm.rs index 3def435b..f179c39e 100644 --- a/src/ix/congruence/perm.rs +++ b/crates/compile/src/congruence/perm.rs @@ -65,19 +65,16 @@ //! - `InductInfo`, `CtorInfo`, `AxiomInfo`, `QuotInfo` — pass-through //! (no permutation needed). -use lean_ffi::nat::Nat; +use bignat::Nat; use rustc_hash::FxHashMap; -use crate::ix::compile::aux_gen::expr_utils::{ - forall_telescope, lambda_telescope, -}; -use crate::ix::{ - address::Address, - env::{ - ConstantInfo, ConstantVal, Expr, ExprData, Name, RecursorRule, RecursorVal, - }, +use ix_common::address::Address; +use ix_common::env::{ + ConstantInfo, ConstantVal, Expr, ExprData, Name, RecursorRule, RecursorVal, }; +use crate::compile::aux_gen::expr_utils::{forall_telescope, lambda_telescope}; + use super::{check_nat_eq, expr_alpha_eq, level_alpha_eq, strip_mdata}; /// Sentinel for `aux_perm` entries that don't correspond to any canonical @@ -352,7 +349,7 @@ impl PermCtx { /// counterparts. Built once per binder telescope, passed by shared /// reference into the alpha-eq walk. #[derive(Default, Clone)] -pub(crate) struct Corr { +pub struct Corr { fvar_map: FxHashMap, fvar_alts: FxHashMap>, punit_motive_gen: Vec, @@ -538,7 +535,7 @@ fn classify_defn_shape(name: &Name) -> DefnShape { /// Collect up to `n` trailing `Str` segments of `name`, from leaf /// outward. `Num` segments or `Anonymous` terminate collection early. fn collect_name_tail_strs(name: &Name, n: usize) -> Vec { - use crate::ix::env::NameData; + use ix_common::env::NameData; let mut out: Vec = Vec::with_capacity(n); let mut cur = name.clone(); for _ in 0..n { @@ -1273,8 +1270,8 @@ fn n_canonical_minors_of(ctx: &PermCtx) -> usize { fn add_motive_alts( corr: &mut Corr, ctx: &PermCtx, - orig_decls: &[crate::ix::compile::aux_gen::expr_utils::LocalDecl], - gen_decls: &[crate::ix::compile::aux_gen::expr_utils::LocalDecl], + orig_decls: &[crate::compile::aux_gen::expr_utils::LocalDecl], + gen_decls: &[crate::compile::aux_gen::expr_utils::LocalDecl], ) { let n_params = ctx.n_params; let n_source_motives = ctx.n_source_motives(); @@ -1353,7 +1350,7 @@ fn is_motive_app(e: &Expr, motives: &[Name]) -> bool { /// - `LetE` / `Proj` / `Mdata`: recurse; `Mdata` is stripped before /// matching so it's essentially a no-op. /// - `Sort`, `Lit`: compare literally. -pub(crate) fn expr_alpha_eq_ctx( +pub fn expr_alpha_eq_ctx( g: &Expr, orig: &Expr, ctx: &PermCtx, @@ -1800,9 +1797,8 @@ fn peel_all_lambdas( expr: &Expr, prefix: &str, min_count: usize, -) -> (Vec, Vec, Expr) -{ - use crate::ix::compile::aux_gen::expr_utils::LocalDecl; +) -> (Vec, Vec, Expr) { + use crate::compile::aux_gen::expr_utils::LocalDecl; let (mut fvars, mut decls, mut body): (Vec, Vec, Expr) = if min_count == 0 { diff --git a/src/ix/decompile.rs b/crates/compile/src/decompile.rs similarity index 98% rename from src/ix/decompile.rs rename to crates/compile/src/decompile.rs index 3a954a7b..6f5edcc3 100644 --- a/src/ix/decompile.rs +++ b/crates/compile/src/decompile.rs @@ -9,35 +9,37 @@ #![allow(clippy::map_err_ignore)] #![allow(clippy::match_same_arms)] -use lean_ffi::nat::Nat; +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 crate::{ - ix::address::Address, - ix::compile::CompileState, - ix::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, Tag0, + constant::{ + Axiom, Constant, ConstantInfo, Constructor, DefKind, Definition, + DefinitionProj, Inductive, InductiveProj, MutConst, Quotient, Recursor, + RecursorProj, }, - ix::ixon::{ - DecompileError, Tag0, - constant::{ - Axiom, Constant, ConstantInfo, Constructor, DefKind, Definition, - DefinitionProj, Inductive, InductiveProj, MutConst, Quotient, Recursor, - RecursorProj, - }, - env::Named, - expr::Expr, - metadata::{ - CallSiteEntry, ConstantMeta, ConstantMetaInfo, DataValue, ExprMeta, - ExprMetaData, KVMap, - }, - univ::Univ, + env::Named, + expr::Expr, + metadata::{ + CallSiteEntry, ConstantMeta, ConstantMetaInfo, DataValue, ExprMeta, + ExprMetaData, KVMap, }, - ix::mutual::{Def, Ind, MutConst as LeanMutConst, MutCtx, all_to_ctx}, + univ::Univ, +}; + +use crate::{ + compile::CompileState, + mutual::{Def, Ind, MutConst as LeanMutConst, MutCtx, all_to_ctx}, }; use dashmap::DashMap; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; @@ -1913,14 +1915,14 @@ enum AuxKind { /// we're going to regenerate anyway should never have its call-sites /// surgered, since the regenerated body is emitted in canonical order /// by construction. -pub(crate) fn is_aux_gen_suffix(name: &Name) -> bool { +pub fn is_aux_gen_suffix(name: &Name) -> bool { classify_aux_gen(name).is_some() } /// Classify an aux_gen constant by suffix, returning (kind, root_inductive). /// The root inductive is the base inductive the auxiliary is derived from. fn classify_aux_gen(name: &Name) -> Option<(AuxKind, Name)> { - use crate::ix::env::NameData; + use ix_common::env::NameData; let s1 = name.last_str()?; let p1 = match name.as_data() { NameData::Str(parent, _, _) => parent.clone(), @@ -2015,7 +2017,7 @@ fn def_safety(is_unsafe: bool) -> DefinitionSafety { /// `mkDefinitionValInferringUnsafe`, which always flips to `Unsafe` when the /// parent inductive is unsafe (the value references the parent's `.rec`). fn below_def_to_lean( - def: &crate::ix::compile::aux_gen::below::BelowDef, + def: &crate::compile::aux_gen::below::BelowDef, ) -> LeanConstantInfo { LeanConstantInfo::DefnInfo(DefinitionVal { cnst: ConstantVal { @@ -2036,7 +2038,7 @@ fn below_def_to_lean( /// branch of `IndPredBelow`). The constructor `is_unsafe` matches the /// enclosing inductive — the kernel rejects mixed safety within an inductive. fn below_indc_to_lean( - indc: &crate::ix::compile::aux_gen::below::BelowIndc, + indc: &crate::compile::aux_gen::below::BelowIndc, all_below_names: &[Name], ) -> (InductiveVal, Vec) { let ctor_names: Vec = @@ -2096,7 +2098,7 @@ fn below_indc_to_lean( /// (`Lean/Environment.lean:2797`), which replaces a theorem with an unsafe /// definition whenever `env.hasUnsafe` fires on the type or value. fn brecon_def_to_lean( - def: &crate::ix::compile::aux_gen::brecon::BRecOnDef, + def: &crate::compile::aux_gen::brecon::BRecOnDef, ) -> LeanConstantInfo { let cnst = ConstantVal { name: def.name.clone(), @@ -2408,11 +2410,11 @@ fn roundtrip_block( stt: &CompileState, dstt: &DecompileState, ) -> Result, DecompileError> { - use crate::ix::compile::{ + use crate::compile::{ BlockCache as CompileBlockCache, compile_definition, compile_inductive, compile_mutual_block, compile_recursor, sort_consts, }; - use crate::ix::mutual::ctx_to_all; + use crate::mutual::ctx_to_all; let mut results: FxHashMap = FxHashMap::default(); if consts.is_empty() { @@ -2507,7 +2509,7 @@ fn roundtrip_block( // Singleton: compile as bare constant (no Muts wrapper). let result = match &ixon_mutuals[0] { MutConst::Defn(def) => { - crate::ix::compile::apply_sharing_to_definition_with_stats( + crate::compile::apply_sharing_to_definition_with_stats( def.clone(), block_refs, block_univs, @@ -2515,7 +2517,7 @@ fn roundtrip_block( ) }, MutConst::Recr(rec) => { - crate::ix::compile::apply_sharing_to_recursor_with_stats( + crate::compile::apply_sharing_to_recursor_with_stats( rec.clone(), block_refs, block_univs, @@ -3182,7 +3184,7 @@ fn decompile_named_const( /// Idempotent: if `stt.aux_perms` already has an entry for the name, we /// leave it alone (compile-in-progress stt wins over rehydrated copy). fn rehydrate_aux_perms_from_env(stt: &CompileState) { - use crate::ix::ixon::metadata::ConstantMetaInfo; + use ixon::metadata::ConstantMetaInfo; let mut n_muts = 0usize; let mut n_muts_with_layout = 0usize; @@ -3294,7 +3296,7 @@ fn block_mut_consts_from_env( #[derive(Clone)] struct StoredPlanBlock { class_names: Vec>, - aux_layout: Option, + aux_layout: Option, flat_names: Vec, } @@ -3390,7 +3392,7 @@ fn fallback_plan_blocks_from_sort( env: &LeanEnv, stt: &CompileState, ) -> Result, DecompileError> { - use crate::ix::compile::{BlockCache as CompileBlockCache, sort_consts}; + use crate::compile::{BlockCache as CompileBlockCache, sort_consts}; let cs = block_mut_consts_from_env(all_names, env)?; if cs.is_empty() { @@ -3422,7 +3424,7 @@ fn install_decompile_call_site_plans( env: &LeanEnv, stt: &CompileState, ) -> Result<(), DecompileError> { - use crate::ix::compile::{aux_gen, surgery}; + use crate::compile::{aux_gen, surgery}; if all_names.is_empty() { return Ok(()); @@ -3502,11 +3504,11 @@ fn decompile_block_aux_gen( all_names: &[Name], aux_members: &[(AuxKind, Name)], env: &mut LeanEnv, - kctx: &mut crate::ix::compile::KernelCtx, + kctx: &mut crate::compile::KernelCtx, stt: &CompileState, dstt: &DecompileState, ) -> Vec<(Name, DecompileError)> { - use crate::ix::compile::aux_gen::{ + use crate::compile::aux_gen::{ below::{BelowConstant, generate_below_constants}, brecon::generate_brecon_constants, cases_on::generate_cases_on, @@ -3541,7 +3543,7 @@ fn decompile_block_aux_gen( // Ingress transitive dependencies from constructor field types. { - use crate::ix::graph::get_constant_info_references; + use crate::graph::get_constant_info_references; for ind_name in all_names { if let Some(ci) = env.get(ind_name) { for ref_name in get_constant_info_references(ci) { @@ -3587,7 +3589,7 @@ fn decompile_block_aux_gen( // // See `docs/ix_canonicity.md` §9.3 / §17.2 for the canonicity // commitment this upholds. - let aux_layout_for_block: Option = None; + let aux_layout_for_block: Option = None; let (canonical_recs, is_prop) = if needs_rec || needs_rec_on @@ -3596,7 +3598,7 @@ fn decompile_block_aux_gen( || needs_below_rec || needs_brecon { - match crate::ix::compile::aux_gen::recursor::generate_canonical_recursors_with_layout( + match crate::compile::aux_gen::recursor::generate_canonical_recursors_with_layout( &classes, env, None, None, stt, kctx, aux_layout_for_block.as_ref(), None, // source_of_canonical derived from aux_layout inside _with_layout @@ -3697,7 +3699,7 @@ fn decompile_block_aux_gen( for co_name in &cases_on_members { let ind_name = match co_name.as_data() { - crate::ix::env::NameData::Str(parent, _, _) => parent.clone(), + ix_common::env::NameData::Str(parent, _, _) => parent.clone(), _ => continue, }; let rec_name = Name::str(ind_name.clone(), "rec".to_string()); @@ -3768,7 +3770,7 @@ fn decompile_block_aux_gen( // Phase 1c: Generate .recOn definitions (arg-reordered .rec wrapper). if needs_rec_on { - use crate::ix::compile::aux_gen::rec_on::generate_rec_on; + use crate::compile::aux_gen::rec_on::generate_rec_on; let rec_on_members: Vec<&Name> = aux_members .iter() @@ -3778,7 +3780,7 @@ fn decompile_block_aux_gen( for ro_name in &rec_on_members { let ind_name = match ro_name.as_data() { - crate::ix::env::NameData::Str(parent, _, _) => parent.clone(), + ix_common::env::NameData::Str(parent, _, _) => parent.clone(), _ => continue, }; let rec_name = Name::str(ind_name, "rec".to_string()); @@ -4233,8 +4235,7 @@ fn decompile_block_aux_gen( if let Some(orig) = orig_env { for (name, generated_ci) in &generated_consts { if let Some(orig_ci) = orig.get(name) - && let Err(e) = - crate::ix::congruence::const_alpha_eq(generated_ci, orig_ci) + && let Err(e) = crate::congruence::const_alpha_eq(generated_ci, orig_ci) { aux_gen_errors.push(( name.clone(), @@ -4262,10 +4263,10 @@ fn decompile_block_aux_gen( pub fn decompile_env( stt: &CompileState, ) -> Result { - use crate::ix::compile::KernelCtx; - use crate::ix::compile::aux_gen::expr_utils; - use crate::ix::condense::compute_sccs; - use crate::ix::graph::{NameSet, RefMap, get_constant_info_references}; + use crate::compile::KernelCtx; + use crate::compile::aux_gen::expr_utils; + use crate::condense::compute_sccs; + use crate::graph::{NameSet, RefMap, get_constant_info_references}; let dstt = DecompileState::default(); @@ -4660,8 +4661,8 @@ pub fn check_decompile( #[cfg(test)] mod tests { use super::*; - use crate::ix::compile::compile_name; - use crate::ix::env::Level; + use crate::compile::compile_name; + use ix_common::env::Level; /// Register a Name in `stt.env.names` so `decompile_name` can resolve it. /// Mirrors `compile_name` (content-address the name, insert into names map). @@ -4674,7 +4675,7 @@ mod tests { fn lean_telescope(e: &LeanExpr) -> (LeanExpr, Vec) { let mut args = Vec::new(); let mut cur = e.clone(); - while let crate::ix::env::ExprData::App(f, a, _) = cur.as_data() { + while let ix_common::env::ExprData::App(f, a, _) = cur.as_data() { args.push(a.clone()); cur = f.clone(); } @@ -4685,7 +4686,7 @@ mod tests { /// Pull the bvar index out of a Lean expr, or None if it isn't a bvar. fn bvar_idx(e: &LeanExpr) -> Option { match e.as_data() { - crate::ix::env::ExprData::Bvar(n, _) => n.to_u64(), + ix_common::env::ExprData::Bvar(n, _) => n.to_u64(), _ => None, } } @@ -4773,7 +4774,7 @@ mod tests { // The reconstructed spine should be in *source* order: Var 10, 11, 12. let (head_lean, args) = lean_telescope(&decompiled); match head_lean.as_data() { - crate::ix::env::ExprData::Const(name, _, _) => { + ix_common::env::ExprData::Const(name, _, _) => { assert_eq!(*name, head_name, "head const name mismatch"); }, other => panic!("expected Const head, got {other:?}"), @@ -4872,7 +4873,7 @@ mod tests { // Expected source-order spine: App(App(head, motive_ref), major). let (head_lean, args) = lean_telescope(&decompiled); match head_lean.as_data() { - crate::ix::env::ExprData::Const(name, _, _) => { + ix_common::env::ExprData::Const(name, _, _) => { assert_eq!(*name, head_name); }, other => panic!("expected head Const, got {other:?}"), @@ -4885,7 +4886,7 @@ mod tests { // args[0] is the collapsed motive — must be Const(target), NOT the // decoy lambda from sharing[0]. match args[0].as_data() { - crate::ix::env::ExprData::Const(name, _, _) => { + ix_common::env::ExprData::Const(name, _, _) => { assert_eq!( *name, target_name, "args[0] is the Collapsed motive and must resolve via \ @@ -4921,9 +4922,9 @@ mod tests { // ------------------------------------------------------------------------- #[test] fn test_projection_decompile_loads_meta_extensions() { - use crate::ix::address::Address; - use crate::ix::env::DefinitionSafety; - use crate::ix::ixon::constant::{ + use ix_common::address::Address; + use ix_common::env::DefinitionSafety; + use ixon::constant::{ DefKind, Definition, DefinitionProj, MutConst as IxMutConst, }; @@ -5041,7 +5042,7 @@ mod tests { LeanConstantInfo::DefnInfo(dv) => { let (head_lean, args) = lean_telescope(&dv.value); match head_lean.as_data() { - crate::ix::env::ExprData::Const(name, _, _) => { + ix_common::env::ExprData::Const(name, _, _) => { assert_eq!( *name, head_name, "CallSite head should decode as `head`" @@ -5051,7 +5052,7 @@ mod tests { } assert_eq!(args.len(), 2, "CallSite had 2 entries -> 2 app args"); match args[0].as_data() { - crate::ix::env::ExprData::Const(name, _, _) => { + ix_common::env::ExprData::Const(name, _, _) => { assert_eq!( *name, target_name, "Collapsed arg must resolve via loaded meta_sharing[0]" diff --git a/src/ix/graph.rs b/crates/compile/src/graph.rs similarity index 98% rename from src/ix/graph.rs rename to crates/compile/src/graph.rs index f90c7a95..e10bd777 100644 --- a/src/ix/graph.rs +++ b/crates/compile/src/graph.rs @@ -9,7 +9,7 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rustc_hash::{FxHashMap, FxHashSet}; use std::collections::hash_map::Entry; -use crate::ix::env::{ConstantInfo, Env, Expr, ExprData, Name}; +use ix_common::env::{ConstantInfo, Env, Expr, ExprData, Name}; /// A set of [`Name`]s, used to represent the neighbors of a node in the reference graph. pub type NameSet = FxHashSet; @@ -97,9 +97,7 @@ pub fn build_ref_graph(env: &Env) -> RefGraph { RefGraph { out_refs, in_refs } } -pub(crate) fn get_constant_info_references( - constant_info: &ConstantInfo, -) -> NameSet { +pub fn get_constant_info_references(constant_info: &ConstantInfo) -> NameSet { let cache = &mut FxHashMap::default(); match constant_info { ConstantInfo::AxiomInfo(val) => get_expr_references(&val.cnst.typ, cache), @@ -179,8 +177,8 @@ fn get_expr_references<'a>( #[cfg(test)] mod tests { use super::*; - use crate::ix::env::*; - use lean_ffi::nat::Nat; + use bignat::Nat; + use ix_common::env::*; fn n(s: &str) -> Name { Name::str(Name::anon(), s.to_string()) diff --git a/src/ix/ground.rs b/crates/compile/src/ground.rs similarity index 98% rename from src/ix/ground.rs rename to crates/compile/src/ground.rs index ffe7325a..c854a311 100644 --- a/src/ix/ground.rs +++ b/crates/compile/src/ground.rs @@ -9,15 +9,14 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rustc_hash::{FxHashMap, FxHashSet}; use std::collections::hash_map::Entry; -use lean_ffi::nat::Nat; +use bignat::Nat; -use crate::{ - ix::env::{ - ConstantInfo, Env, Expr, ExprData, InductiveVal, Level, LevelData, Name, - }, - ix::graph::RefMap, +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 @@ -235,8 +234,8 @@ fn ground_level( #[cfg(test)] mod tests { use super::*; - use crate::ix::env::*; - use crate::ix::graph::build_ref_graph; + use crate::graph::build_ref_graph; + use ix_common::env::*; fn n(s: &str) -> Name { Name::str(Name::anon(), s.to_string()) diff --git a/src/ix/kernel/egress.rs b/crates/compile/src/kernel_egress.rs similarity index 98% rename from src/ix/kernel/egress.rs rename to crates/compile/src/kernel_egress.rs index 10435cfe..ea1299c1 100644 --- a/src/ix/kernel/egress.rs +++ b/crates/compile/src/kernel_egress.rs @@ -7,20 +7,20 @@ use rayon::iter::{ }; use rustc_hash::FxHashMap; -use crate::ix::env::{ +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 crate::ix::ixon::constant::DefKind; -use lean_ffi::nat::Nat; +use ixon::constant::DefKind; -use super::constant::KConst; -use super::env::KEnv; -use super::expr::{ExprData, KExpr, MData}; -use super::id::KId; -use super::level::{KUniv, UnivData}; -use super::mode::Meta; +use ix_kernel::constant::KConst; +use ix_kernel::env::KEnv; +use ix_kernel::expr::{ExprData, KExpr, MData}; +use ix_kernel::id::KId; +use ix_kernel::level::{KUniv, UnivData}; +use ix_kernel::mode::Meta; /// Convert a zero kernel universe to a Lean level. fn egress_level(u: &KUniv, level_params: &[Name]) -> env::Level { @@ -53,7 +53,7 @@ fn egress_levels( } /// Expression egress cache, keyed by content hash. -type Cache = FxHashMap; +type Cache = FxHashMap; /// Convert a zero kernel expression to a Lean expression. fn egress_expr( @@ -343,23 +343,23 @@ use std::sync::Arc; use indexmap::IndexSet; -use crate::ix::address::Address; -use crate::ix::compile::{ +use crate::compile::{ apply_sharing_to_axiom_with_stats, apply_sharing_to_definition_with_stats, apply_sharing_to_mutual_block, apply_sharing_to_quotient_with_stats, apply_sharing_to_recursor_with_stats, }; -use crate::ix::ixon::constant::{ +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 crate::ix::ixon::env::{Env as IxonEnv, Named}; -use crate::ix::ixon::expr::Expr as IxonExpr; -use crate::ix::ixon::metadata::ConstantMetaInfo; -use crate::ix::ixon::univ::Univ as IxonUniv; +use ixon::env::{Env as IxonEnv, Named}; +use ixon::expr::Expr as IxonExpr; +use ixon::metadata::ConstantMetaInfo; +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 @@ -384,9 +384,9 @@ struct EgressCtx { /// Memoized expression conversion. Keyed by `KExpr::addr()` (content /// hash); same hash → same Ixon expression (within a single block's /// tables). - expr_cache: FxHashMap>, + expr_cache: FxHashMap>, /// Memoized universe conversion. - univ_cache: FxHashMap>, + univ_cache: FxHashMap>, } impl EgressCtx { @@ -1256,14 +1256,17 @@ pub fn ixon_egress( #[cfg(test)] mod tests { use super::*; - use crate::ix::address::Address; - use crate::ix::env::{ + use ix_common::address::Address; + use ix_common::env::{ BinderInfo, DefinitionSafety, ExprData as LeanExprData, Literal, QuotKind, ReducibilityHints, }; - use crate::ix::kernel::constant::RecRule; - use crate::ix::kernel::expr::KExpr; - use crate::ix::kernel::id::KId; + 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(); diff --git a/crates/compile/src/lib.rs b/crates/compile/src/lib.rs new file mode 100644 index 00000000..da38ab96 --- /dev/null +++ b/crates/compile/src/lib.rs @@ -0,0 +1,11 @@ +//! Lean ↔ Ixon compilation pipeline plus kernel ↔ Lean egress. + +pub mod compile; +pub mod condense; +pub mod congruence; +pub mod decompile; +pub mod graph; +pub mod ground; +pub mod kernel_egress; +pub mod mutual; +pub mod store; diff --git a/src/ix/mutual.rs b/crates/compile/src/mutual.rs similarity index 97% rename from src/ix/mutual.rs rename to crates/compile/src/mutual.rs index b3bf8122..b7486a75 100644 --- a/src/ix/mutual.rs +++ b/crates/compile/src/mutual.rs @@ -5,16 +5,15 @@ //! [`ctx_to_all`] / [`all_to_ctx`] functions convert between ordered name //! vectors and index maps. -use lean_ffi::nat::Nat; - -use crate::{ - ix::env::{ - ConstructorVal, DefinitionSafety, DefinitionVal, Expr, InductiveVal, Name, - OpaqueVal, RecursorVal, ReducibilityHints, TheoremVal, - }, - ix::ixon::constant::DefKind, +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 @@ -195,7 +194,7 @@ impl MutConst { #[cfg(test)] mod tests { use super::*; - use crate::ix::env::{ConstantVal, Level}; + use ix_common::env::{ConstantVal, Level}; fn n(s: &str) -> Name { Name::str(Name::anon(), s.to_string()) diff --git a/src/ix/store.rs b/crates/compile/src/store.rs similarity index 98% rename from src/ix/store.rs rename to crates/compile/src/store.rs index fe45f508..33dcaf79 100644 --- a/src/ix/store.rs +++ b/crates/compile/src/store.rs @@ -4,7 +4,7 @@ //! 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 crate::ix::address::Address; +use ix_common::address::Address; use std::env; use std::fs; use std::io; diff --git a/crates/ffi/Cargo.toml b/crates/ffi/Cargo.toml new file mode 100644 index 00000000..aac4f212 --- /dev/null +++ b/crates/ffi/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "ix-ffi" +version.workspace = true +edition.workspace = true +license.workspace = true + +[lib] +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 } +ix-common = { workspace = true } +ix-compile = { workspace = true } +ixon = { workspace = true } +ix-kernel = { workspace = true } +lean-ffi = { workspace = true } +multi-stark = { workspace = true } +mimalloc = { workspace = true } +num-bigint = { workspace = true } +rayon = { workspace = true } +rustc-hash = { workspace = true } +sha2 = { workspace = true } +tiny-keccak = { workspace = true } +tracing = { workspace = true } +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 } +serde = { version = "1.0.219", features = ["derive"], optional = true } + +[features] +default = [] +parallel = ["aiur/parallel"] +test-ffi = [] +net = ["bytes", "tokio", "iroh", "iroh-base", "n0-error", "getrandom", "bincode", "serde"] + +[lints] +workspace = true diff --git a/src/ffi/_iroh.rs b/crates/ffi/src/_iroh.rs similarity index 100% rename from src/ffi/_iroh.rs rename to crates/ffi/src/_iroh.rs diff --git a/src/ffi/aiur.rs b/crates/ffi/src/aiur.rs similarity index 95% rename from src/ffi/aiur.rs rename to crates/ffi/src/aiur.rs index aa565fd2..01ad45ac 100644 --- a/src/ffi/aiur.rs +++ b/crates/ffi/src/aiur.rs @@ -3,7 +3,7 @@ use multi_stark::p3_field::integers::QuotientMap; pub mod protocol; pub mod toplevel; -use crate::aiur::G; +use aiur::G; use lean_ffi::object::LeanRef; #[inline] diff --git a/src/ffi/aiur/protocol.rs b/crates/ffi/src/aiur/protocol.rs similarity index 97% rename from src/ffi/aiur/protocol.rs rename to crates/ffi/src/aiur/protocol.rs index ed2ec7b4..6f323249 100644 --- a/src/ffi/aiur/protocol.rs +++ b/crates/ffi/src/aiur/protocol.rs @@ -12,18 +12,16 @@ use lean_ffi::object::{ }; use crate::{ - aiur::{ - G, - execute::{IOBuffer, IOKeyInfo}, - synthesis::AiurSystem, - }, - ffi::aiur::{ - lean_unbox_g, lean_unbox_nat_as_usize, toplevel::decode_toplevel, - }, + aiur::{lean_unbox_g, lean_unbox_nat_as_usize, toplevel::decode_toplevel}, lean::{ LeanAiurCommitmentParameters, LeanAiurFriParameters, LeanAiurToplevel, }, }; +use aiur::{ + G, + execute::{IOBuffer, IOKeyInfo}, + synthesis::AiurSystem, +}; // ============================================================================= // External class registration @@ -121,7 +119,7 @@ extern "C" fn rs_aiur_toplevel_execute( let mut query_counts: Vec<(usize, usize)> = Vec::with_capacity( query_record.function_queries.len() + toplevel.memory_sizes.len(), ); - let summarize = |q: &crate::aiur::execute::QueryMap| -> (usize, usize) { + let summarize = |q: &aiur::execute::QueryMap| -> (usize, usize) { let mut rows = 0usize; let mut hits = 0usize; for (_, res) in q.iter() { diff --git a/src/ffi/aiur/toplevel.rs b/crates/ffi/src/aiur/toplevel.rs similarity index 97% rename from src/ffi/aiur/toplevel.rs rename to crates/ffi/src/aiur/toplevel.rs index d2b75a22..072e2203 100644 --- a/src/ffi/aiur/toplevel.rs +++ b/crates/ffi/src/aiur/toplevel.rs @@ -4,16 +4,13 @@ use lean_ffi::object::{LeanBorrowed, LeanCtor, LeanRef}; use crate::lean::LeanAiurFunction; -use crate::{ - FxIndexMap, - aiur::{ - G, - bytecode::{Block, Ctrl, Function, FunctionLayout, Op, Toplevel, ValIdx}, - }, - lean::LeanAiurToplevel, +use crate::lean::LeanAiurToplevel; +use aiur::{ + FxIndexMap, G, + bytecode::{Block, Ctrl, Function, FunctionLayout, Op, Toplevel, ValIdx}, }; -use crate::ffi::aiur::{lean_unbox_g, lean_unbox_nat_as_usize}; +use crate::aiur::{lean_unbox_g, lean_unbox_nat_as_usize}; fn decode_vec_val_idx(obj: LeanBorrowed<'_>) -> Vec { obj.as_array().map(|x| lean_unbox_nat_as_usize(&x)) diff --git a/src/ffi/builder.rs b/crates/ffi/src/builder.rs similarity index 100% rename from src/ffi/builder.rs rename to crates/ffi/src/builder.rs diff --git a/src/ffi/byte_array.rs b/crates/ffi/src/byte_array.rs similarity index 100% rename from src/ffi/byte_array.rs rename to crates/ffi/src/byte_array.rs diff --git a/src/ffi/compile.rs b/crates/ffi/src/compile.rs similarity index 97% rename from src/ffi/compile.rs rename to crates/ffi/src/compile.rs index b91e19c3..cf928511 100644 --- a/src/ffi/compile.rs +++ b/crates/ffi/src/compile.rs @@ -9,44 +9,44 @@ use std::sync::Arc; -use crate::ix::address::Address; -use crate::ix::compile::{ - CompileOptions, CompileState, compile_env_with_options, -}; -use crate::ix::condense::compute_sccs; -use crate::ix::decompile::decompile_env; -use crate::ix::env::Name; -use crate::ix::graph::build_ref_graph; -use crate::ix::ixon::constant::Constant as IxonConstant; -#[cfg(feature = "test-ffi")] -use crate::ix::ixon::constant::ConstantInfo; -#[cfg(feature = "test-ffi")] -use crate::ix::ixon::expr::Expr as IxonExpr; -use crate::ix::ixon::{Comm, ConstantMeta}; use crate::lean::{ LeanIxBlock, LeanIxCompileError, LeanIxCompilePhases, LeanIxCondensedBlocks, LeanIxConstantInfo, LeanIxDecompileError, LeanIxName, LeanIxRawEnvironment, LeanIxSerializeError, LeanIxonRawBlob, LeanIxonRawComm, LeanIxonRawConst, LeanIxonRawEnv, LeanIxonRawNameEntry, LeanIxonRawNamed, }; -use lean_ffi::nat::Nat; +use ix_common::address::Address; +use ix_common::env::Name; +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, LeanExcept, LeanList, LeanOwned, LeanProd, LeanRef, LeanString, }; -use crate::ffi::builder::LeanBuildCache; -use crate::ffi::ixon::env::decoded_to_ixon_env; -use crate::ffi::lean_env::decode_env; +use crate::builder::LeanBuildCache; use crate::lean::LeanIxAddress; +use crate::lean_env::decode_env; +use crate::lean_ixon::env::decoded_to_ixon_env; #[cfg(feature = "test-ffi")] -use crate::ffi::lean_env::{GlobalCache, decode_name}; +use crate::lean::{LeanIxBlockCompareDetail, LeanIxBlockCompareResult}; #[cfg(feature = "test-ffi")] -use crate::ix::ixon::serialize::put_expr; +use crate::lean_env::{GlobalCache, decode_name}; #[cfg(feature = "test-ffi")] -use crate::lean::{LeanIxBlockCompareDetail, LeanIxBlockCompareResult}; +use ixon::serialize::put_expr; #[cfg(feature = "test-ffi")] use std::collections::HashMap; @@ -1002,17 +1002,17 @@ extern "C" fn rs_get_pre_sharing_exprs( let mut exprs = Vec::new(); for mc in muts { match mc { - crate::ix::ixon::constant::MutConst::Defn(def) => { + ixon::constant::MutConst::Defn(def) => { exprs.push(def.typ.clone()); exprs.push(def.value.clone()); }, - crate::ix::ixon::constant::MutConst::Indc(ind) => { + ixon::constant::MutConst::Indc(ind) => { exprs.push(ind.typ.clone()); for ctor in &ind.ctors { exprs.push(ctor.typ.clone()); } }, - crate::ix::ixon::constant::MutConst::Recr(rec) => { + ixon::constant::MutConst::Recr(rec) => { exprs.push(rec.typ.clone()); for rule in &rec.rules { exprs.push(rule.rhs.clone()); @@ -1091,13 +1091,9 @@ extern "C" fn rs_get_pre_sharing_exprs_len( let mut count = 0; for mc in muts { match mc { - crate::ix::ixon::constant::MutConst::Defn(_) => count += 2, - crate::ix::ixon::constant::MutConst::Indc(ind) => { - count += 1 + ind.ctors.len() - }, - crate::ix::ixon::constant::MutConst::Recr(rec) => { - count += 1 + rec.rules.len() - }, + 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 @@ -1155,7 +1151,7 @@ extern "C" fn rs_get_compiled_const_count( // Error type FFI builders // ============================================================================= -use crate::ix::ixon::error::{CompileError, DecompileError, SerializeError}; +use ixon::error::{CompileError, DecompileError, SerializeError}; impl LeanIxSerializeError { /// Build a Lean Ixon.SerializeError from a Rust SerializeError. @@ -1244,7 +1240,7 @@ impl LeanIxSerializeError { }, 5 => SerializeError::AddressError, 6 => { - let max = Nat::from_obj(&self.get_obj(0)) + let max = LeanNat::to_nat(&self.get_obj(0)) .to_u64() .and_then(|x| usize::try_from(x).ok()) .unwrap_or(0); @@ -1335,7 +1331,7 @@ impl LeanIxDecompileError { pub fn decode(&self) -> DecompileError { match self.as_ctor().tag() { 0 => { - let refs_len = Nat::from_obj(&self.get_obj(0)) + let refs_len = LeanNat::to_nat(&self.get_obj(0)) .to_u64() .and_then(|x| usize::try_from(x).ok()) .unwrap_or(0); @@ -1344,7 +1340,7 @@ impl LeanIxDecompileError { DecompileError::InvalidRefIndex { idx, refs_len, constant } }, 1 => { - let univs_len = Nat::from_obj(&self.get_obj(0)) + let univs_len = LeanNat::to_nat(&self.get_obj(0)) .to_u64() .and_then(|x| usize::try_from(x).ok()) .unwrap_or(0); @@ -1353,7 +1349,7 @@ impl LeanIxDecompileError { DecompileError::InvalidUnivIndex { idx, univs_len, constant } }, 2 => { - let max = Nat::from_obj(&self.get_obj(0)) + let max = LeanNat::to_nat(&self.get_obj(0)) .to_u64() .and_then(|x| usize::try_from(x).ok()) .unwrap_or(0); @@ -1362,7 +1358,7 @@ impl LeanIxDecompileError { DecompileError::InvalidShareIndex { idx, max, constant } }, 3 => { - let ctx_size = Nat::from_obj(&self.get_obj(0)) + let ctx_size = LeanNat::to_nat(&self.get_obj(0)) .to_u64() .and_then(|x| usize::try_from(x).ok()) .unwrap_or(0); @@ -1371,7 +1367,7 @@ impl LeanIxDecompileError { DecompileError::InvalidRecIndex { idx, ctx_size, constant } }, 4 => { - let max = Nat::from_obj(&self.get_obj(0)) + let max = LeanNat::to_nat(&self.get_obj(0)) .to_u64() .and_then(|x| usize::try_from(x).ok()) .unwrap_or(0); diff --git a/src/ffi/graph.rs b/crates/ffi/src/graph.rs similarity index 94% rename from src/ffi/graph.rs rename to crates/ffi/src/graph.rs index acfcacff..5dfdd9e8 100644 --- a/src/ffi/graph.rs +++ b/crates/ffi/src/graph.rs @@ -2,21 +2,21 @@ use std::sync::Arc; -use crate::ix::condense::compute_sccs; -use crate::ix::graph::build_ref_graph; 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::ffi::builder::LeanBuildCache; -use crate::ffi::lean_env::decode_env; +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: &crate::ix::graph::RefMap, + refs: &ix_compile::graph::RefMap, ) -> LeanArray { let arr = LeanArray::alloc(refs.len()); for (i, (name, ref_set)) in refs.iter().enumerate() { @@ -38,7 +38,7 @@ impl LeanIxCondensedBlocks { /// Build a RustCondensedBlocks structure. pub fn build( cache: &mut LeanBuildCache, - condensed: &crate::ix::condense::CondensedBlocks, + condensed: &ix_compile::condense::CondensedBlocks, ) -> Self { // Build lowLinks: Array (Ix.Name × Ix.Name) let low_links_arr = LeanArray::alloc(condensed.low_links.len()); diff --git a/src/iroh.rs b/crates/ffi/src/iroh.rs similarity index 100% rename from src/iroh.rs rename to crates/ffi/src/iroh.rs diff --git a/src/iroh/client.rs b/crates/ffi/src/iroh/client.rs similarity index 100% rename from src/iroh/client.rs rename to crates/ffi/src/iroh/client.rs diff --git a/src/iroh/server.rs b/crates/ffi/src/iroh/server.rs similarity index 100% rename from src/iroh/server.rs rename to crates/ffi/src/iroh/server.rs diff --git a/src/ffi/ix.rs b/crates/ffi/src/ix.rs similarity index 100% rename from src/ffi/ix.rs rename to crates/ffi/src/ix.rs diff --git a/src/ffi/ix/address.rs b/crates/ffi/src/ix/address.rs similarity index 98% rename from src/ffi/ix/address.rs rename to crates/ffi/src/ix/address.rs index f389f8d1..34512e45 100644 --- a/src/ffi/ix/address.rs +++ b/crates/ffi/src/ix/address.rs @@ -2,8 +2,8 @@ //! //! Address = { hash : ByteArray } - ByteArray wrapper for blake3 Hash -use crate::ix::address::Address; use crate::lean::LeanIxAddress; +use ix_common::address::Address; use lean_ffi::object::{LeanArray, LeanBorrowed, LeanByteArray, LeanOwned}; impl LeanIxAddress { diff --git a/src/ffi/ix/constant.rs b/crates/ffi/src/ix/constant.rs similarity index 91% rename from src/ffi/ix/constant.rs rename to crates/ffi/src/ix/constant.rs index ebc01d8c..5c3fb70a 100644 --- a/src/ffi/ix/constant.rs +++ b/crates/ffi/src/ix/constant.rs @@ -10,23 +10,23 @@ //! - Tag 6: ctorInfo (v : ConstructorVal) //! - Tag 7: recInfo (v : RecursorVal) -use crate::ix::env::{ - AxiomVal, ConstantInfo, ConstantVal, ConstructorVal, DefinitionSafety, - DefinitionVal, InductiveVal, Name, OpaqueVal, QuotKind, QuotVal, - RecursorRule, RecursorVal, ReducibilityHints, TheoremVal, -}; use crate::lean::{ LeanIxAxiomVal, LeanIxConstantInfo, LeanIxConstantVal, LeanIxConstructorVal, LeanIxDefinitionVal, LeanIxExpr, LeanIxInductiveVal, LeanIxName, LeanIxOpaqueVal, LeanIxQuotVal, LeanIxRecursorRule, LeanIxRecursorVal, LeanIxReducibilityHints, LeanIxTheoremVal, }; -use lean_ffi::nat::Nat; +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::ffi::builder::LeanBuildCache; +use crate::builder::LeanBuildCache; // ============================================================================= // ConstantVal @@ -121,7 +121,7 @@ impl LeanIxRecursorRule { let ctor = self.as_ctor(); RecursorRule { ctor: LeanIxName(ctor.get(0)).decode(), - n_fields: Nat::from_obj(&ctor.get(1)), + n_fields: LeanNat::to_nat(&ctor.get(1)), rhs: LeanIxExpr(ctor.get(2)).decode(), } } @@ -141,7 +141,7 @@ impl LeanIxRecursorRule { 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 = Nat::to_lean(&rule.n_fields); + let n_fields_obj = LeanNat::from_nat(&rule.n_fields); let rhs_obj = LeanIxExpr::build(cache, &rule.rhs); let rule_obj = LeanIxRecursorRule::alloc(0); @@ -253,11 +253,11 @@ impl LeanIxConstantInfo { 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 = Nat::to_lean(&v.num_params); - let num_indices_obj = Nat::to_lean(&v.num_indices); + 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 = Nat::to_lean(&v.num_nested); + let num_nested_obj = LeanNat::from_nat(&v.num_nested); // 6 object fields, 3 scalar bytes for bools let induct_val = LeanIxInductiveVal::alloc(0); @@ -280,9 +280,9 @@ impl LeanIxConstantInfo { // 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 = Nat::to_lean(&v.cidx); - let num_params_obj = Nat::to_lean(&v.num_params); - let num_fields_obj = Nat::to_lean(&v.num_fields); + 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); @@ -302,10 +302,10 @@ impl LeanIxConstantInfo { // 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 = Nat::to_lean(&v.num_params); - let num_indices_obj = Nat::to_lean(&v.num_indices); - let num_motives_obj = Nat::to_lean(&v.num_motives); - let num_minors_obj = Nat::to_lean(&v.num_minors); + 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 @@ -404,11 +404,11 @@ impl LeanIxConstantInfo { ConstantInfo::InductInfo(InductiveVal { cnst: LeanIxConstantVal(inner_val.get_obj(0)).decode(), - num_params: Nat::from_obj(&inner_val.get_obj(1)), - num_indices: Nat::from_obj(&inner_val.get_obj(2)), + 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: Nat::from_obj(&inner_val.get_obj(5)), + num_nested: LeanNat::to_nat(&inner_val.get_obj(5)), is_rec, is_unsafe, is_reflexive, @@ -421,9 +421,9 @@ impl LeanIxConstantInfo { ConstantInfo::CtorInfo(ConstructorVal { cnst: LeanIxConstantVal(inner_val.get_obj(0)).decode(), induct: LeanIxName(inner_val.get_obj(1)).decode(), - cidx: Nat::from_obj(&inner_val.get_obj(2)), - num_params: Nat::from_obj(&inner_val.get_obj(3)), - num_fields: Nat::from_obj(&inner_val.get_obj(4)), + 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, }) }, @@ -440,10 +440,10 @@ impl LeanIxConstantInfo { ConstantInfo::RecInfo(RecursorVal { cnst: LeanIxConstantVal(inner_val.get_obj(0)).decode(), all: LeanIxName::decode_array(inner_val.get_obj(1).as_array()), - num_params: Nat::from_obj(&inner_val.get_obj(2)), - num_indices: Nat::from_obj(&inner_val.get_obj(3)), - num_motives: Nat::from_obj(&inner_val.get_obj(4)), - num_minors: Nat::from_obj(&inner_val.get_obj(5)), + 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, diff --git a/src/ffi/ix/data.rs b/crates/ffi/src/ix/data.rs similarity index 92% rename from src/ffi/ix/data.rs rename to crates/ffi/src/ix/data.rs index 7fbb6228..355be8bb 100644 --- a/src/ffi/ix/data.rs +++ b/crates/ffi/src/ix/data.rs @@ -1,18 +1,18 @@ //! Ix.DataValue, Ix.Syntax, Ix.SourceInfo build/decode/roundtrip FFI. -use crate::ix::env::{ - DataValue, Int, Name, SourceInfo, Substring, Syntax, SyntaxPreresolved, -}; use crate::lean::{ LeanIxDataValue, LeanIxInt, LeanIxName, LeanIxSourceInfo, LeanIxSubstring, LeanIxSyntax, LeanIxSyntaxPreresolved, }; -use lean_ffi::nat::Nat; +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::ffi::builder::LeanBuildCache; +use crate::builder::LeanBuildCache; impl LeanIxInt { /// Build a Ix.Int (ofNat or negSucc). @@ -20,12 +20,12 @@ impl LeanIxInt { match int { Int::OfNat(n) => { let ctor = LeanIxInt::alloc(0); - ctor.set_obj(0, Nat::to_lean(n)); + ctor.set_obj(0, LeanNat::from_nat(n)); ctor }, Int::NegSucc(n) => { let ctor = LeanIxInt::alloc(1); - ctor.set_obj(0, Nat::to_lean(n)); + ctor.set_obj(0, LeanNat::from_nat(n)); ctor }, } @@ -37,7 +37,7 @@ impl LeanIxInt { /// Ix.Int: ofNat (tag 0, 1 field) | negSucc (tag 1, 1 field) pub fn decode(&self) -> Int { let ctor = self.as_ctor(); - let nat = Nat::from_obj(&ctor.get(0)); + let nat = LeanNat::to_nat(&ctor.get(0)); match ctor.tag() { 0 => Int::OfNat(nat), 1 => Int::NegSucc(nat), @@ -51,8 +51,8 @@ impl LeanIxSubstring { pub fn build(ss: &Substring) -> Self { let ctor = LeanIxSubstring::alloc(0); ctor.set_obj(0, LeanString::new(ss.str.as_str())); - ctor.set_obj(1, Nat::to_lean(&ss.start_pos)); - ctor.set_obj(2, Nat::to_lean(&ss.stop_pos)); + ctor.set_obj(1, LeanNat::from_nat(&ss.start_pos)); + ctor.set_obj(2, LeanNat::from_nat(&ss.stop_pos)); ctor } } @@ -63,8 +63,8 @@ impl LeanIxSubstring { let ctor = self.as_ctor(); Substring { str: ctor.get(0).as_string().to_string(), - start_pos: Nat::from_obj(&ctor.get(1)), - stop_pos: Nat::from_obj(&ctor.get(2)), + start_pos: LeanNat::to_nat(&ctor.get(1)), + stop_pos: LeanNat::to_nat(&ctor.get(2)), } } } @@ -77,16 +77,16 @@ impl LeanIxSourceInfo { SourceInfo::Original(leading, pos, trailing, end_pos) => { let ctor = LeanIxSourceInfo::alloc(0); ctor.set_obj(0, LeanIxSubstring::build(leading)); - ctor.set_obj(1, Nat::to_lean(pos)); + ctor.set_obj(1, LeanNat::from_nat(pos)); ctor.set_obj(2, LeanIxSubstring::build(trailing)); - ctor.set_obj(3, Nat::to_lean(end_pos)); + 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, Nat::to_lean(pos)); - ctor.set_obj(1, Nat::to_lean(end_pos)); + 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 }, @@ -108,9 +108,9 @@ impl LeanIxSourceInfo { // original SourceInfo::Original( LeanIxSubstring(ctor.get(0)).decode(), - Nat::from_obj(&ctor.get(1)), + LeanNat::to_nat(&ctor.get(1)), LeanIxSubstring(ctor.get(2)).decode(), - Nat::from_obj(&ctor.get(3)), + LeanNat::to_nat(&ctor.get(3)), ) }, 1 => { @@ -118,8 +118,8 @@ impl LeanIxSourceInfo { let canonical = self.get_num_8(0) != 0; SourceInfo::Synthetic( - Nat::from_obj(&self.get_obj(0)), - Nat::from_obj(&self.get_obj(1)), + LeanNat::to_nat(&self.get_obj(0)), + LeanNat::to_nat(&self.get_obj(1)), canonical, ) }, @@ -298,7 +298,7 @@ impl LeanIxDataValue { }, DataValue::OfNat(n) => { let ctor = LeanIxDataValue::alloc(3); - ctor.set_obj(0, Nat::to_lean(n)); + ctor.set_obj(0, LeanNat::from_nat(n)); ctor }, DataValue::OfInt(i) => { @@ -351,13 +351,13 @@ impl LeanIxDataValue { }, 3 => { // ofNat: 1 object field - DataValue::OfNat(Nat::from_obj(&ctor.get(0))) + 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 = Nat::from_obj(&inner_ctor.get(0)); + 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)), diff --git a/src/ffi/ix/env.rs b/crates/ffi/src/ix/env.rs similarity index 97% rename from src/ffi/ix/env.rs rename to crates/ffi/src/ix/env.rs index c4179fe2..3800bc61 100644 --- a/src/ffi/ix/env.rs +++ b/crates/ffi/src/ix/env.rs @@ -2,15 +2,15 @@ use rustc_hash::FxHashMap; -use crate::ix::env::{ConstantInfo, Name}; 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::ffi::builder::LeanBuildCache; +use crate::builder::LeanBuildCache; // ============================================================================= // HashMap Building @@ -140,7 +140,7 @@ impl LeanIxRawEnvironment { /// so we return just the array, not a structure containing it. pub fn build( cache: &mut LeanBuildCache, - consts: &crate::ix::env::Env, + consts: &ix_common::env::Env, ) -> Self { // Build consts array: Array (Name × ConstantInfo) let consts_arr = LeanArray::alloc(consts.len()); @@ -242,7 +242,7 @@ pub extern "C" fn rs_roundtrip_ix_environment( env_ptr: LeanIxEnvironment>, ) -> LeanIxRawEnvironment { let decoded = env_ptr.decode(); - let env: crate::ix::env::Env = decoded.into_iter().collect(); + let env: ix_common::env::Env = decoded.into_iter().collect(); let mut cache = LeanBuildCache::with_capacity(env.len()); LeanIxRawEnvironment::build(&mut cache, &env) } diff --git a/src/ffi/ix/expr.rs b/crates/ffi/src/ix/expr.rs similarity index 96% rename from src/ffi/ix/expr.rs rename to crates/ffi/src/ix/expr.rs index ced0bb75..4b5e4bfc 100644 --- a/src/ffi/ix/expr.rs +++ b/crates/ffi/src/ix/expr.rs @@ -14,18 +14,18 @@ //! - Tag 10: mdata (data : Array (Name × DataValue)) (expr : Expr) (hash : Address) //! - Tag 11: proj (typeName : Name) (idx : Nat) (struct : Expr) (hash : Address) -use crate::ffi::builder::LeanBuildCache; -use crate::ix::env::{ - BinderInfo, DataValue, Expr, ExprData, Level, Literal, Name, -}; +use crate::builder::LeanBuildCache; use crate::lean::LeanIxAddress; use crate::lean::{ LeanIxBinderInfo, LeanIxDataValue, LeanIxExpr, LeanIxLevel, LeanIxLiteral, LeanIxName, }; -use lean_ffi::nat::Nat; +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 { @@ -40,7 +40,7 @@ impl LeanIxExpr { let result = match expr.as_data() { ExprData::Bvar(idx, h) => { let ctor = LeanIxExpr::alloc(0); - ctor.set_obj(0, Nat::to_lean(idx)); + ctor.set_obj(0, LeanNat::from_nat(idx)); ctor.set_obj(1, LeanIxAddress::build_from_hash(h)); ctor }, @@ -139,7 +139,7 @@ impl LeanIxExpr { }, ExprData::Proj(type_name, idx, struct_expr, h) => { let name_obj = LeanIxName::build(cache, type_name); - let idx_obj = Nat::to_lean(idx); + 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); @@ -162,7 +162,7 @@ impl LeanIxExpr { match ctor.tag() { 0 => { // bvar - let idx = Nat::from_obj(&ctor.get(0)); + let idx = LeanNat::to_nat(&ctor.get(0)); Expr::bvar(idx) }, 1 => { @@ -247,7 +247,7 @@ impl LeanIxExpr { 11 => { // proj: typeName, idx, struct, hash let type_name = LeanIxName(ctor.get(0)).decode(); - let idx = Nat::from_obj(&ctor.get(1)); + let idx = LeanNat::to_nat(&ctor.get(1)); let struct_expr = LeanIxExpr(ctor.get(2)).decode(); Expr::proj(type_name, idx, struct_expr) @@ -263,7 +263,7 @@ impl LeanIxLiteral { match lit { Literal::NatVal(n) => { let ctor = LeanIxLiteral::alloc(0); - ctor.set_obj(0, Nat::to_lean(n)); + ctor.set_obj(0, LeanNat::from_nat(n)); ctor }, Literal::StrVal(s) => { @@ -282,7 +282,7 @@ impl LeanIxLiteral { match ctor.tag() { 0 => { // natVal - let nat = Nat::from_obj(&ctor.get(0)); + let nat = LeanNat::to_nat(&ctor.get(0)); Literal::NatVal(nat) }, 1 => { diff --git a/src/ffi/ix/level.rs b/crates/ffi/src/ix/level.rs similarity index 98% rename from src/ffi/ix/level.rs rename to crates/ffi/src/ix/level.rs index 5039c386..fead881f 100644 --- a/src/ffi/ix/level.rs +++ b/crates/ffi/src/ix/level.rs @@ -8,11 +8,11 @@ //! - Tag 4: param (n : Name) (hash : Address) //! - Tag 5: mvar (n : Name) (hash : Address) -use crate::ix::env::{Level, LevelData}; use crate::lean::{LeanIxLevel, LeanIxName}; +use ix_common::env::{Level, LevelData}; use lean_ffi::object::{LeanArray, LeanBorrowed, LeanOwned, LeanRef}; -use crate::ffi::builder::LeanBuildCache; +use crate::builder::LeanBuildCache; use crate::lean::LeanIxAddress; impl LeanIxLevel { diff --git a/src/ffi/ix/name.rs b/crates/ffi/src/ix/name.rs similarity index 94% rename from src/ffi/ix/name.rs rename to crates/ffi/src/ix/name.rs index 62e72ec0..be359723 100644 --- a/src/ffi/ix/name.rs +++ b/crates/ffi/src/ix/name.rs @@ -5,14 +5,14 @@ //! - Tag 1: str (parent : Name) (s : String) (hash : Address) //! - Tag 2: num (parent : Name) (i : Nat) (hash : Address) -use crate::ix::env::{Name, NameData}; use crate::lean::LeanIxName; -use lean_ffi::nat::Nat; +use ix_common::env::{Name, NameData}; +use lean_ffi::object::LeanNat; use lean_ffi::object::{ LeanArray, LeanBorrowed, LeanOwned, LeanRef, LeanString, }; -use crate::ffi::builder::LeanBuildCache; +use crate::builder::LeanBuildCache; use crate::lean::LeanIxAddress; impl LeanIxName { @@ -41,7 +41,7 @@ impl LeanIxName { }, NameData::Num(parent, n, h) => { let parent_obj = Self::build(cache, parent); - let n_obj = Nat::to_lean(n); + let n_obj = LeanNat::from_nat(n); let ctor = LeanIxName::alloc(2); ctor.set_obj(0, parent_obj); ctor.set_obj(1, n_obj); @@ -85,7 +85,7 @@ impl LeanIxName { 2 => { // num: parent, i, hash let parent = LeanIxName(ctor.get(0)).decode(); - let i = Nat::from_obj(&ctor.get(1)); + let i = LeanNat::to_nat(&ctor.get(1)); Name::num(parent, i) }, _ => panic!("Invalid Ix.Name tag: {}", ctor.tag()), diff --git a/src/ffi/keccak.rs b/crates/ffi/src/keccak.rs similarity index 100% rename from src/ffi/keccak.rs rename to crates/ffi/src/keccak.rs diff --git a/src/ffi/kernel.rs b/crates/ffi/src/kernel.rs similarity index 95% rename from src/ffi/kernel.rs rename to crates/ffi/src/kernel.rs index 84ba7d84..183e71dc 100644 --- a/src/ffi/kernel.rs +++ b/crates/ffi/src/kernel.rs @@ -37,7 +37,7 @@ use std::thread; use std::time::{Duration, Instant}; use lean_ffi::include::lean_object; -use lean_ffi::nat::Nat; +use lean_ffi::object::LeanNat; use rustc_hash::FxHashMap; use lean_ffi::object::{ @@ -48,40 +48,36 @@ use lean_ffi::object::{ use crate::lean::LeanIxCheckError; #[cfg(feature = "test-ffi")] -use crate::ffi::lean_env::{GlobalCache, decode_name}; -use crate::ffi::lean_env::{decode_env, decode_name_array}; -use crate::ix::address::Address; -use crate::ix::compile::{ +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}; +use ix_compile::compile::{ CompileOptions, CompileState, compile_env_with_options, }; #[cfg(feature = "test-ffi")] -use crate::ix::decompile::decompile_env; -use crate::ix::env::{Name, NameData}; -use crate::ix::ixon::constant::ConstantInfo as IxonCI; +use ix_compile::decompile::decompile_env; #[cfg(feature = "test-ffi")] -use crate::ix::ixon::constant::MutConst as IxonMutConst; -use crate::ix::ixon::env::Env as IxonEnv; -#[cfg(feature = "test-ffi")] -use crate::ix::ixon::expr::Expr as IxonExpr; -use crate::ix::ixon::metadata::ConstantMetaInfo; -#[cfg(feature = "test-ffi")] -use crate::ix::kernel::egress::{ixon_egress, lean_egress}; -use crate::ix::kernel::env::KEnv; -use crate::ix::kernel::error::TcError; -use crate::ix::kernel::id::KId; -use crate::ix::kernel::ingress::{ +use ix_compile::kernel_egress::{ixon_egress, lean_egress}; +use ix_kernel::env::KEnv; +use ix_kernel::error::TcError; +use ix_kernel::id::KId; +use ix_kernel::ingress::{ IxonIngressLookups, build_ixon_ingress_lookups, ingress_const_shallow_into_kenv_with_lookups, ixon_ingress_owned, }; -use crate::ix::kernel::ingress::{ - anon_ctor_proj_addr, anon_defn_proj_addr, anon_indc_proj_addr, - anon_recr_proj_addr, -}; #[cfg(feature = "test-ffi")] -use crate::ix::kernel::ingress::{ixon_ingress, lean_ingress}; -use crate::ix::kernel::mode::{Anon, CheckDupLevelParams, KernelMode, Meta}; -use crate::ix::kernel::tc::TypeChecker; -use crate::ix::profile::{BlockProfile, ProfileBuilder, ProfileSink}; +use ix_kernel::ingress::{ixon_ingress, lean_ingress}; +use ix_kernel::mode::{Anon, CheckDupLevelParams, KernelMode, Meta}; +use ix_kernel::profile::{BlockProfile, ProfileBuilder, ProfileSink}; +use ix_kernel::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( @@ -448,8 +444,7 @@ fn poison_second_rec_rule_returns_first_minor( 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: crate::ix::ixon::constant::Constant = - (*rec_arc).clone(); + let mut rec_constant: ixon::constant::Constant = (*rec_arc).clone(); drop(rec_arc); match &mut rec_constant.info { @@ -485,8 +480,7 @@ fn poison_second_rec_rule_returns_first_minor( block_addr.hex() ) })?; - let mut block_constant: crate::ix::ixon::constant::Constant = - (*block_arc).clone(); + let mut block_constant: ixon::constant::Constant = (*block_arc).clone(); drop(block_arc); match &mut block_constant.info { IxonCI::Muts(members) => { @@ -533,7 +527,7 @@ fn poison_second_rec_rule_returns_first_minor( #[cfg(feature = "test-ffi")] fn poison_recursor_rule_payload( - rec: &mut crate::ix::ixon::constant::Recursor, + rec: &mut ixon::constant::Recursor, ) -> Result<(), String> { if rec.rules.len() < 2 { return Err(format!( @@ -746,7 +740,7 @@ pub extern "C" fn rs_kernel_ixon_names( /// to downstream consumers (Aiur, kernel primitive resolution). #[unsafe(no_mangle)] pub extern "C" fn rs_prim_addrs_canonical() -> LeanIOResult { - let table = crate::ix::kernel::primitive::PrimAddrs::lean_parity_table(); + let table = ix_kernel::primitive::PrimAddrs::lean_parity_table(); let arr = LeanArray::alloc(table.len()); for (i, (name, hex)) in table.iter().enumerate() { let name_obj: LeanOwned = LeanString::new(name).into(); @@ -792,7 +786,7 @@ fn build_lean_name(name: &Name) -> LeanOwned { }, NameData::Num(parent, n, _) => { let parent = build_lean_name(parent); - let part = Nat::to_lean(n); + let part = LeanNat::from_nat(n); unsafe { LeanOwned::from_raw(lean_name_mk_numeral( parent.into_raw(), @@ -1298,6 +1292,11 @@ fn resolve_kernel_check_workers_from( // 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. @@ -1314,93 +1313,34 @@ enum AnonWorkItem { /// 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`] (shared with the +/// SP1/Zisk guests) and layers the FFI's per-target result-slot +/// bookkeeping on top. fn build_anon_work( env: &IxonEnv, ) -> Result<(Vec, Vec
), String> { - use crate::ix::ixon::constant::ConstantInfo as CI; - use crate::ix::ixon::constant::MutConst as MC; - use crate::ix::ixon::lazy::ConstVariantTag as Tag; + use ix_kernel::anon_work::AnonWorkItem as KItem; - let mut work: Vec = Vec::new(); + let kernel_work = ix_kernel::anon_work::build_anon_work(env)?; + let mut work: Vec = Vec::with_capacity(kernel_work.len()); let mut addrs: 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(); - - // Dispatch on the outer Tag4 byte via `peek_variant` — no body - // parse, no allocation. Only `Muts` blocks require a full - // materialization (to enumerate members for projection-address - // computation); the resulting `Arc` drops at the end of - // that match arm. Standalones (Defn/Recr/Axio/Quot, ~95% of the - // env) and projection skips don't even touch the body. - // - // Before this change, every constant was fully materialized here - // and (worse) pinned forever in `LazyConstant.cache`'s OnceLock. - // For mathlib that pinned ~30 GB of parsed `Arc` trees in - // the shared `Arc` before kernel checking even started. - // The cache-free `LazyConstant` policy + this peek path keep - // env-side memory bounded to "bytes (mmap'd) + per-const headers". - 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 => { + 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: addr.clone() }); + work.push(AnonWorkItem::Standalone { result_idx, addr }); }, - 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() - )); - }; - // Compute kernel-checkable targets deterministically. Each - // member contributes its projection address; inductive members - // contribute one CPrj per constructor. - let mut targets: Vec
= Vec::new(); - for (i, member) in members.iter().enumerate() { - let i = i as u64; - let member_addr = match member { - MC::Defn(_) => anon_defn_proj_addr(&addr, i), - MC::Indc(_) => anon_indc_proj_addr(&addr, i), - MC::Recr(_) => anon_recr_proj_addr(&addr, i), - }; - targets.push(member_addr); - if let MC::Indc(ind) = member { - for cidx in 0..ind.ctors.len() as u64 { - targets.push(anon_ctor_proj_addr(&addr, i, cidx)); - } - } - } - if targets.is_empty() { - continue; - } - let primary_addr = targets[0].clone(); - let result_idxs: Vec = - (addrs.len()..addrs.len() + targets.len()).collect(); + KItem::Block { primary, targets, .. } => { + let start = addrs.len(); + let result_idxs: Vec = (start..start + targets.len()).collect(); addrs.extend(targets); - work.push(AnonWorkItem::Block { primary_addr, result_idxs }); + work.push(AnonWorkItem::Block { primary_addr: primary, result_idxs }); }, } } - Ok((work, addrs)) } @@ -1940,7 +1880,7 @@ pub extern "C" fn rs_shard_esp( 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 crate::ix::shard::shard_esp( + match ix_kernel::shard::shard_esp( &esp_path.to_string(), num_shards, balance, @@ -3072,7 +3012,7 @@ pub extern "C" fn rs_kernel_roundtrip( // Build a plain Lean `Env` from decompile's DashMap for the standard // compare_envs / find_diff flow. let t5 = Instant::now(); - let mut decompiled_env = crate::ix::env::Env::default(); + let mut decompiled_env = ix_common::env::Env::default(); for entry in dstt.env.iter() { decompiled_env.insert(entry.key().clone(), entry.value().clone()); } @@ -3108,10 +3048,10 @@ pub extern "C" fn rs_kernel_roundtrip( /// manageable. #[cfg(feature = "test-ffi")] fn compare_envs( - original: &crate::ix::env::Env, - egressed: &crate::ix::env::Env, + original: &ix_common::env::Env, + egressed: &ix_common::env::Env, ) -> (Vec, usize, usize) { - use crate::ix::env::ConstantInfo as LCI; + use ix_common::env::ConstantInfo as LCI; let total = original.len(); let mut errors: Vec = Vec::new(); @@ -3181,11 +3121,11 @@ fn compare_envs( /// Returns a path-annotated description of where the mismatch is. #[cfg(feature = "test-ffi")] fn find_diff( - a: &crate::ix::env::Expr, - b: &crate::ix::env::Expr, + a: &ix_common::env::Expr, + b: &ix_common::env::Expr, path: &str, ) -> String { - use crate::ix::env::ExprData; + use ix_common::env::ExprData; if a.get_hash() == b.get_hash() { return format!("{path}: hashes match (ok)"); @@ -3281,7 +3221,7 @@ fn find_diff( let mut val_diffs = Vec::new(); for (i, ((n1, v1), (_, v2))) in kvs1.iter().zip(kvs2.iter()).enumerate() { - use crate::ix::env::hash_data_value; + 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); diff --git a/src/lean.rs b/crates/ffi/src/lean.rs similarity index 100% rename from src/lean.rs rename to crates/ffi/src/lean.rs diff --git a/src/ffi/lean_env.rs b/crates/ffi/src/lean_env.rs similarity index 94% rename from src/ffi/lean_env.rs rename to crates/ffi/src/lean_env.rs index 13296fa9..1bd907d3 100644 --- a/src/ffi/lean_env.rs +++ b/crates/ffi/src/lean_env.rs @@ -2,7 +2,7 @@ //! //! Provides functions to walk Lean object pointers and decode them into //! the Rust `Name`, `Level`, `Expr`, and `ConstantInfo` types defined in -//! `crate::ix::env`. Used by the compilation pipeline to read the Lean +//! `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 @@ -17,11 +17,12 @@ use rayon::prelude::*; use rustc_hash::FxHashMap; -use crate::ix::compile::{CompileOptions, compile_env_with_options}; -use crate::ix::decompile::{check_decompile, decompile_env}; +use ix_compile::compile::{CompileOptions, compile_env_with_options}; +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, LeanRef, LeanShared, }; @@ -35,7 +36,7 @@ use crate::lean::{ LeanIxSyntaxPreresolved, LeanIxTheoremVal, }; -use crate::ix::env::{ +use ix_common::env::{ AxiomVal, BinderInfo, ConstantInfo, ConstantVal, ConstructorVal, DataValue, DefinitionSafety, DefinitionVal, Env, Expr, InductiveVal, Int, Level, Literal, Name, OpaqueVal, QuotKind, QuotVal, RecursorRule, RecursorVal, @@ -55,7 +56,7 @@ const PARALLEL_THRESHOLD: usize = 100; /// generated recursor is intentionally canonical rather than source-identical. fn primary_addresses_collapse( all: &[Name], - stt: &crate::ix::compile::CompileState, + stt: &ix_compile::compile::CompileState, ) -> bool { let mut seen = rustc_hash::FxHashSet::default(); for name in all { @@ -72,12 +73,12 @@ fn primary_addresses_collapse( fn build_aux_perm_ctx( all: &[Name], env: &Env, - stt: &crate::ix::compile::CompileState, + stt: &ix_compile::compile::CompileState, perm: &[usize], -) -> Option { - use crate::ix::compile::aux_gen; - use crate::ix::congruence::perm::{PermCtx, RecHeadInfo, RecHeadKind}; - use crate::ix::env::{ConstantInfo as LeanCI, ExprData}; +) -> Option { + use ix_common::env::{ConstantInfo as LeanCI, ExprData}; + use ix_compile::compile::aux_gen; + use ix_compile::congruence::perm::{PermCtx, RecHeadInfo, RecHeadKind}; let first = all.first()?; let n_params = match env.get(first) { @@ -162,7 +163,7 @@ fn build_aux_perm_ctx( ); } - let mut const_addr: FxHashMap = + let mut const_addr: FxHashMap = FxHashMap::default(); let mut add_addr = |name: &Name| { if let Some(addr) = stt.resolve_addr(name) { @@ -194,8 +195,8 @@ fn build_aux_perm_ctx( fn collect_const_addrs( e: &Expr, - stt: &crate::ix::compile::CompileState, - out: &mut FxHashMap, + stt: &ix_compile::compile::CompileState, + out: &mut FxHashMap, ) { match e.as_data() { ExprData::Const(n, _, _) => { @@ -261,13 +262,13 @@ fn build_aux_perm_ctx( fn build_collapse_const_map( all: &[Name], env: &Env, - stt: &crate::ix::compile::CompileState, + stt: &ix_compile::compile::CompileState, ) -> FxHashMap { - use crate::ix::env::ConstantInfo as LeanCI; + use ix_common::env::ConstantInfo as LeanCI; let mut map: FxHashMap = FxHashMap::default(); // Group primary members by canonical address; the first member with a // given address is the representative. - let mut rep_by_addr: FxHashMap = + let mut rep_by_addr: FxHashMap = FxHashMap::default(); for member in all { let Some(addr) = stt.resolve_addr(member) else { @@ -323,21 +324,21 @@ fn build_collapse_const_map( #[derive(Clone)] struct AuxCompareEntry { generated: ConstantInfo, - ctx: Option, + ctx: Option, } fn aux_patch_to_lean_ci( - patch: &crate::ix::compile::aux_gen::PatchedConstant, -) -> Option { - use crate::ix::env::{ + patch: &ix_compile::compile::aux_gen::PatchedConstant, +) -> ConstantInfo { + use ix_common::env::{ ConstantInfo as LeanCI, ConstantVal as LeanCV, DefinitionVal, InductiveVal, }; - Some(match patch { - crate::ix::compile::aux_gen::PatchedConstant::Rec(r) => { + match patch { + ix_compile::compile::aux_gen::PatchedConstant::Rec(r) => { LeanCI::RecInfo(r.clone()) }, - crate::ix::compile::aux_gen::PatchedConstant::CasesOn(d) - | crate::ix::compile::aux_gen::PatchedConstant::RecOn(d) => { + ix_compile::compile::aux_gen::PatchedConstant::CasesOn(d) + | ix_compile::compile::aux_gen::PatchedConstant::RecOn(d) => { LeanCI::DefnInfo(DefinitionVal { cnst: LeanCV { name: d.name.clone(), @@ -350,7 +351,7 @@ fn aux_patch_to_lean_ci( all: vec![], }) }, - crate::ix::compile::aux_gen::PatchedConstant::BelowDef(d) => { + ix_compile::compile::aux_gen::PatchedConstant::BelowDef(d) => { LeanCI::DefnInfo(DefinitionVal { cnst: LeanCV { name: d.name.clone(), @@ -363,7 +364,7 @@ fn aux_patch_to_lean_ci( all: vec![], }) }, - crate::ix::compile::aux_gen::PatchedConstant::BRecOn(d) => { + ix_compile::compile::aux_gen::PatchedConstant::BRecOn(d) => { LeanCI::DefnInfo(DefinitionVal { cnst: LeanCV { name: d.name.clone(), @@ -376,7 +377,7 @@ fn aux_patch_to_lean_ci( all: vec![], }) }, - crate::ix::compile::aux_gen::PatchedConstant::BelowIndc(bi) => { + ix_compile::compile::aux_gen::PatchedConstant::BelowIndc(bi) => { LeanCI::InductInfo(InductiveVal { cnst: LeanCV { name: bi.name.clone(), @@ -393,7 +394,7 @@ fn aux_patch_to_lean_ci( is_reflexive: bi.is_reflexive, }) }, - }) + } } fn aux_congruence_result( @@ -402,8 +403,8 @@ fn aux_congruence_result( original: &ConstantInfo, entry: Option<&AuxCompareEntry>, ) -> Result<(), String> { - use crate::ix::congruence::const_alpha_eq; - use crate::ix::congruence::perm::const_alpha_eq_with_perm; + use ix_compile::congruence::const_alpha_eq; + use ix_compile::congruence::perm::const_alpha_eq_with_perm; if let Ok(()) = const_alpha_eq(decompiled, original) { return Ok(()); } @@ -478,11 +479,11 @@ fn aux_congruence_result( fn build_aux_compare_contexts( env: &Arc, - stt: &crate::ix::compile::CompileState, + stt: &ix_compile::compile::CompileState, ) -> FxHashMap { - use crate::ix::compile::KernelCtx; - use crate::ix::compile::aux_gen::{self, expr_utils}; - use crate::ix::env::ConstantInfo as LeanCI; + use ix_common::env::ConstantInfo as LeanCI; + use ix_compile::compile::KernelCtx; + use ix_compile::compile::aux_gen::{self, expr_utils}; use rustc_hash::FxHashSet; let mut by_name = FxHashMap::default(); @@ -523,12 +524,11 @@ fn build_aux_compare_contexts( None }; for (patch_name, patch) in aux_out.patches.iter() { - if let Some(generated) = aux_patch_to_lean_ci(patch) { - by_name.insert( - patch_name.clone(), - AuxCompareEntry { generated, ctx: ctx.clone() }, - ); - } + let generated = aux_patch_to_lean_ci(patch); + by_name.insert( + patch_name.clone(), + AuxCompareEntry { generated, ctx: ctx.clone() }, + ); } } by_name @@ -610,7 +610,7 @@ pub fn decode_name(obj: LeanBorrowed<'_>, global: &GlobalCache) -> Name { let pos = n.get_obj(1); match n.as_ctor().tag() { 1 => Name::str(pre, pos.as_string().to_string()), - 2 => Name::num(pre, Nat::from_obj(&pos)), + 2 => Name::num(pre, LeanNat::to_nat(&pos)), tag => unreachable!("Invalid Lean.Name tag: {tag}"), } }; @@ -668,8 +668,8 @@ fn decode_level(obj: LeanBorrowed<'_>, cache: &mut Cache<'_>) -> 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 = Nat::from_obj(&s.get_obj(1)); - let stop_pos = Nat::from_obj(&s.get_obj(2)); + 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 } } @@ -681,14 +681,14 @@ fn decode_source_info(obj: LeanBorrowed<'_>) -> SourceInfo { match si.as_ctor().tag() { 0 => { let leading = decode_substring(si.get_obj(0)); - let pos = Nat::from_obj(&si.get_obj(1)); + let pos = LeanNat::to_nat(&si.get_obj(1)); let trailing = decode_substring(si.get_obj(2)); - let end_pos = Nat::from_obj(&si.get_obj(3)); + let end_pos = LeanNat::to_nat(&si.get_obj(3)); SourceInfo::Original(leading, pos, trailing, end_pos) }, 1 => { - let pos = Nat::from_obj(&si.get_obj(0)); - let end_pos = Nat::from_obj(&si.get_obj(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) }, @@ -768,10 +768,10 @@ fn decode_name_data_value( 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(Nat::from_obj(&dv.get_obj(0))), + 3 => DataValue::OfNat(LeanNat::to_nat(&dv.get_obj(0))), 4 => { let i = LeanIxInt::from_ctor(dv.get_obj(0).as_ctor()); - let nat = Nat::from_obj(&i.get_obj(0)); + let nat = LeanNat::to_nat(&i.get_obj(0)); let int = match i.as_ctor().tag() { 0 => Int::OfNat(nat), 1 => Int::NegSucc(nat), @@ -799,7 +799,7 @@ pub fn decode_expr(obj: LeanBorrowed<'_>, cache: &mut Cache<'_>) -> Expr { _ => unreachable!("Invalid Lean.BinderInfo tag: {b}"), }; let expr = match e.as_ctor().tag() { - 0 => Expr::bvar(Nat::from_obj(&e.get_obj(0))), + 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)), @@ -842,7 +842,7 @@ pub fn decode_expr(obj: LeanBorrowed<'_>, cache: &mut Cache<'_>) -> Expr { 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(Nat::from_obj(&inner))), + 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}"), } @@ -857,7 +857,7 @@ pub fn decode_expr(obj: LeanBorrowed<'_>, cache: &mut Cache<'_>) -> Expr { }, 11 => { let typ_name = decode_name(e.get_obj(0), cache.global); - let idx = Nat::from_obj(&e.get_obj(1)); + 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) }, @@ -873,7 +873,7 @@ fn decode_recursor_rule( ) -> RecursorRule { let r = LeanIxRecursorRule::from_ctor(obj.as_ctor()); let ctor_name = decode_name(r.get_obj(0), cache.global); - let n_fields = Nat::from_obj(&r.get_obj(1)); + 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 } } @@ -980,8 +980,8 @@ pub fn decode_constant_info( 5 => { let inner = LeanIxInductiveVal::from_ctor(inner_obj.as_ctor()); let constant_val = decode_constant_val(inner.get_obj(0), cache); - let num_params = Nat::from_obj(&inner.get_obj(1)); - let num_indices = Nat::from_obj(&inner.get_obj(2)); + 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)) @@ -990,7 +990,7 @@ pub fn decode_constant_info( .into_iter() .map(|o| decode_name(o, cache.global)) .collect(); - let num_nested = Nat::from_obj(&inner.get_obj(5)); + 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; @@ -1010,9 +1010,9 @@ pub fn decode_constant_info( 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 = Nat::from_obj(&inner.get_obj(2)); - let num_params = Nat::from_obj(&inner.get_obj(3)); - let num_fields = Nat::from_obj(&inner.get_obj(4)); + 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, @@ -1030,10 +1030,10 @@ pub fn decode_constant_info( .into_iter() .map(|o| decode_name(o, cache.global)) .collect(); - let num_params = Nat::from_obj(&inner.get_obj(2)); - let num_indices = Nat::from_obj(&inner.get_obj(3)); - let num_motives = Nat::from_obj(&inner.get_obj(4)); - let num_minors = Nat::from_obj(&inner.get_obj(5)); + 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)) @@ -1117,11 +1117,11 @@ extern "C" fn rs_tmp_decode_const_map( obj: LeanList>, ) -> usize { // Enable hash-consed size tracking for debugging - crate::ix::compile::TRACK_HASH_CONSED_SIZE + ix_compile::compile::TRACK_HASH_CONSED_SIZE .store(true, std::sync::atomic::Ordering::Relaxed); // Enable verbose sharing analysis for debugging pathological blocks - crate::ix::compile::ANALYZE_SHARING + ix_compile::compile::ANALYZE_SHARING .store(false, std::sync::atomic::Ordering::Relaxed); let env = decode_env(obj); @@ -1150,12 +1150,12 @@ extern "C" fn rs_tmp_decode_const_map( // Phase 1b: Aux_gen congruence (full env) eprintln!("[rust-compile] Phase 1b: Checking aux_gen congruence..."); { - use crate::ix::compile::aux_gen::{self, PatchedConstant, expr_utils}; - use crate::ix::congruence::const_alpha_eq; - use crate::ix::env::{ + use ix_common::env::{ ConstantInfo as LeanCI, ConstantVal as LeanCV, DefinitionSafety, DefinitionVal, InductiveVal, ReducibilityHints, }; + use ix_compile::compile::aux_gen::{self, PatchedConstant, expr_utils}; + use ix_compile::congruence::const_alpha_eq; use rustc_hash::{FxHashMap, FxHashSet}; // Build per-block PermCtx for the permutation-aware comparator. @@ -1165,11 +1165,11 @@ extern "C" fn rs_tmp_decode_const_map( fn build_perm_ctx_1b( all: &[Name], env: &Env, - stt: &crate::ix::compile::CompileState, + stt: &ix_compile::compile::CompileState, perm: &[usize], - ) -> Option { - use crate::ix::congruence::perm::{PermCtx, RecHeadInfo, RecHeadKind}; - use crate::ix::env::{ConstantInfo as LeanCI, ExprData}; + ) -> Option { + use ix_common::env::{ConstantInfo as LeanCI, ExprData}; + use ix_compile::congruence::perm::{PermCtx, RecHeadInfo, RecHeadKind}; let first = all.first()?; let n_params = match env.get(first) { @@ -1257,7 +1257,7 @@ extern "C" fn rs_tmp_decode_const_map( ); } - let mut const_addr: FxHashMap = + let mut const_addr: FxHashMap = FxHashMap::default(); let mut add_addr = |name: &Name| { if let Some(addr) = stt.resolve_addr(name) { @@ -1290,8 +1290,8 @@ extern "C" fn rs_tmp_decode_const_map( } fn collect_const_addrs( e: &Expr, - stt: &crate::ix::compile::CompileState, - out: &mut FxHashMap, + stt: &ix_compile::compile::CompileState, + out: &mut FxHashMap, ) { match e.as_data() { ExprData::Const(n, _, _) => { @@ -1377,7 +1377,7 @@ extern "C" fn rs_tmp_decode_const_map( continue; } - let mut local_kctx = crate::ix::compile::KernelCtx::new(); + let mut local_kctx = ix_compile::compile::KernelCtx::new(); expr_utils::ensure_prelude_in_kenv_of(&stt, &mut local_kctx); let orig_aux_out = match aux_gen::generate_aux_patches( &original_classes, @@ -1405,7 +1405,7 @@ extern "C" fn rs_tmp_decode_const_map( // `rs_compile_validate_aux`) for the full builder; the // `#[cfg(feature = "test-ffi")]` Phase 1b path here uses a // local copy with the same logic. - let perm_ctx_1b: Option = + let perm_ctx_1b: Option = if let Some(perm) = &orig_aux_out.perm && !perm.is_empty() { @@ -1475,7 +1475,7 @@ extern "C" fn rs_tmp_decode_const_map( }; let orig_ci: &LeanCI = orig_ci_ref; let eq_result = match &perm_ctx_1b { - Some(ctx) => crate::ix::congruence::perm::const_alpha_eq_with_perm( + Some(ctx) => ix_compile::congruence::perm::const_alpha_eq_with_perm( &gen_ci, orig_ci, ctx, ), None => const_alpha_eq(&gen_ci, orig_ci), @@ -1598,9 +1598,9 @@ extern "C" fn rs_tmp_decode_const_map( eprintln!("[rust-compile] Phase 6: Deserializing and re-decompiling..."); let t4 = std::time::Instant::now(); let mut buf: &[u8] = &serialized; - match crate::ix::ixon::env::Env::get(&mut buf) { + match ixon::env::Env::get(&mut buf) { Ok(fresh_env) => { - let fresh_stt = crate::ix::compile::CompileState { + let fresh_stt = ix_compile::compile::CompileState { env: fresh_env, ..Default::default() }; @@ -1690,7 +1690,7 @@ impl PhaseResult { extern "C" fn rs_compile_validate_aux( obj: LeanList>, ) -> usize { - use crate::ix::congruence::const_alpha_eq; + use ix_compile::congruence::const_alpha_eq; use rustc_hash::FxHashSet; let t_total = std::time::Instant::now(); @@ -1796,10 +1796,10 @@ extern "C" fn rs_compile_validate_aux( let mut p2 = PhaseResult::new("2. Aux_gen congruence"); println!("{VALIDATE_PREFIX} phase 2: checking aux_gen congruence..."); { - use crate::ix::compile::aux_gen::{self, PatchedConstant, expr_utils}; - use crate::ix::compile::{KernelCtx, mk_indc}; - use crate::ix::env::ConstantInfo as LeanCI; - use crate::ix::mutual::MutConst; + use ix_common::env::ConstantInfo as LeanCI; + use ix_compile::compile::aux_gen::{self, PatchedConstant, expr_utils}; + use ix_compile::compile::{KernelCtx, mk_indc}; + use ix_compile::mutual::MutConst; // Ephemeral kernel context for original-structure congruence testing. // Shared across all blocks (accumulates inductives incrementally). @@ -1867,7 +1867,7 @@ extern "C" fn rs_compile_validate_aux( // introducing races (even though individual DashMap inserts are safe, // a reader may observe a partially-ingressed kctx and fail). { - use crate::ix::graph::get_constant_info_references; + use ix_compile::graph::get_constant_info_references; // Step A (serial): enumerate the transitive-closure of names to // ingress. BFS walking the env hashmap is cheap — the per-node cost // is a lookup and a ref-walk, dwarfed by Step B's actual ingress. @@ -1913,18 +1913,18 @@ extern "C" fn rs_compile_validate_aux( // Build a `PermCtx` for the block: the congruence comparator uses // it to walk gen vs orig in lockstep with permutation awareness. - // See `crate::ix::congruence::perm` for details. + // See `ix_compile::congruence::perm` for details. // // `n_primary = all.len()` because Phase 2 uses singleton classes // (one class per original, no alpha-collapse at the primary level). fn build_perm_ctx( all: &[Name], env: &Env, - stt: &crate::ix::compile::CompileState, + stt: &ix_compile::compile::CompileState, perm: &[usize], - ) -> Option { - use crate::ix::congruence::perm::{PermCtx, RecHeadInfo}; - use crate::ix::env::ConstantInfo as LeanCI; + ) -> Option { + use ix_common::env::ConstantInfo as LeanCI; + use ix_compile::congruence::perm::{PermCtx, RecHeadInfo}; use rustc_hash::FxHashMap; let first = all.first()?; @@ -1968,7 +1968,7 @@ extern "C" fn rs_compile_validate_aux( // - Aux `.below_N` (kind = Below) — `{first}.below_{N}` // - Primary `.brecOn`/.go/.eq (kind = BRecOn) // - Aux `.brecOn_N`/.go/.eq (kind = BRecOn) - use crate::ix::congruence::perm::RecHeadKind; + use ix_compile::congruence::perm::RecHeadKind; let n_motives = n_primary + source_aux_ctor_counts.len(); let n_minors: usize = primary_ctor_counts.iter().sum::() + source_aux_ctor_counts.iter().sum::(); @@ -2049,7 +2049,7 @@ extern "C" fn rs_compile_validate_aux( // operate on collapsed blocks pick up the rewrites automatically. // (Built below at the PermCtx construction site so `env`/`stt` // borrows don't conflict with the const_addr-collecting closure.) - let mut const_addr: FxHashMap = + let mut const_addr: FxHashMap = FxHashMap::default(); let mut add_addr = |name: &Name| { if let Some(addr) = stt.resolve_addr(name) { @@ -2084,10 +2084,10 @@ extern "C" fn rs_compile_validate_aux( } fn collect_const_addrs( e: &Expr, - stt: &crate::ix::compile::CompileState, - out: &mut FxHashMap, + stt: &ix_compile::compile::CompileState, + out: &mut FxHashMap, ) { - use crate::ix::env::ExprData; + use ix_common::env::ExprData; match e.as_data() { ExprData::Const(n, _, _) => { if let Some(addr) = stt.resolve_addr(n) { @@ -2145,12 +2145,12 @@ extern "C" fn rs_compile_validate_aux( } // Helper to wrap a patch as a Lean `ConstantInfo` for alpha-eq. - fn patch_to_lean_ci(patch: &PatchedConstant) -> Option { - use crate::ix::env::{ + fn patch_to_lean_ci(patch: &PatchedConstant) -> ConstantInfo { + use ix_common::env::{ ConstantInfo as LeanCI, ConstantVal as LeanCV, DefinitionSafety, DefinitionVal, InductiveVal, ReducibilityHints, }; - Some(match patch { + match patch { PatchedConstant::Rec(r) => LeanCI::RecInfo(r.clone()), PatchedConstant::CasesOn(d) | PatchedConstant::RecOn(d) => { LeanCI::DefnInfo(DefinitionVal { @@ -2202,7 +2202,7 @@ extern "C" fn rs_compile_validate_aux( is_unsafe: false, is_reflexive: bi.is_reflexive, }), - }) + } } // Diagnostic dump printed per-thread on alpha-eq failure. Writes go @@ -2215,7 +2215,7 @@ extern "C" fn rs_compile_validate_aux( orig_ci: &ConstantInfo, err: &str, ) { - use crate::ix::env::{Expr, ExprData as ED}; + use ix_common::env::{Expr, ExprData as ED}; fn extract_sort(e: &Expr, depth: usize) -> String { match e.as_data() { @@ -2268,7 +2268,7 @@ extern "C" fn rs_compile_validate_aux( // Build a PermCtx for this block once. When the block has no // nested auxes (`perm == None` or empty), we pass `None` and // fall through to plain `const_alpha_eq`. - let perm_ctx: Option = + let perm_ctx: Option = if let Some(p) = &orig_aux_out.perm && !p.is_empty() { @@ -2282,16 +2282,18 @@ extern "C" fn rs_compile_validate_aux( let mut result = BlockResult::default(); let mut dumped = 0usize; for (patch_name, patch) in orig_patches.iter() { - let Some(gen_ci) = patch_to_lean_ci(patch) else { continue }; + let gen_ci = patch_to_lean_ci(patch); let Some(orig_ci_ref) = env.get(patch_name) else { continue; // Synthetic name — no Lean original. }; let orig_ci: &LeanCI = orig_ci_ref; let eq_result = match &perm_ctx { - Some(ctx) => crate::ix::congruence::perm::const_alpha_eq_with_perm( - &gen_ci, orig_ci, ctx, - ), + Some(ctx) => { + ix_compile::congruence::perm::const_alpha_eq_with_perm( + &gen_ci, orig_ci, ctx, + ) + }, None => const_alpha_eq(&gen_ci, orig_ci), }; @@ -2333,7 +2335,7 @@ extern "C" fn rs_compile_validate_aux( // Precompute canonical addresses: any orig_addr that matches another Named // entry's canonical addr is in consts legitimately (not an ephemeral leak). // The gather itself parallelizes cleanly over the DashMap. - let canonical_addrs: FxHashSet = + let canonical_addrs: FxHashSet = stt.env.named.par_iter().map(|e| e.value().addr.clone()).collect(); // Parallel scan over named DashMap. Each check is read-only against @@ -2463,17 +2465,17 @@ extern "C" fn rs_compile_validate_aux( } fn describe_addr( - stt: &crate::ix::compile::CompileState, - addr: &crate::ix::address::Address, + stt: &ix_compile::compile::CompileState, + addr: &ix_common::address::Address, ) -> String { match stt.env.get_const(addr).map(|c| c.info.clone()) { - Some(crate::ix::ixon::constant::ConstantInfo::RPrj(p)) => { + Some(ixon::constant::ConstantInfo::RPrj(p)) => { format!("RPrj(idx={}, block={:.12})", p.idx, p.block.hex()) }, - Some(crate::ix::ixon::constant::ConstantInfo::IPrj(p)) => { + Some(ixon::constant::ConstantInfo::IPrj(p)) => { format!("IPrj(idx={}, block={:.12})", p.idx, p.block.hex()) }, - Some(crate::ix::ixon::constant::ConstantInfo::CPrj(p)) => { + Some(ixon::constant::ConstantInfo::CPrj(p)) => { format!( "CPrj(idx={}, cidx={}, block={:.12})", p.idx, @@ -2487,14 +2489,14 @@ extern "C" fn rs_compile_validate_aux( } fn describe_rprj_block( - stt: &crate::ix::compile::CompileState, - addr: &crate::ix::address::Address, + stt: &ix_compile::compile::CompileState, + addr: &ix_common::address::Address, ) -> Option { fn expand_shares_expr( - expr: &Arc, - sharing: &[Arc], - ) -> Arc { - use crate::ix::ixon::expr::Expr; + expr: &Arc, + sharing: &[Arc], + ) -> Arc { + use ixon::expr::Expr; match expr.as_ref() { Expr::Share(idx) => sharing.get(*idx as usize).map_or_else( || expr.clone(), @@ -2528,10 +2530,10 @@ extern "C" fn rs_compile_validate_aux( } fn expand_shares_member( - member: &crate::ix::ixon::constant::MutConst, - sharing: &[Arc], - ) -> crate::ix::ixon::constant::MutConst { - use crate::ix::ixon::constant::{MutConst, RecursorRule}; + member: &ixon::constant::MutConst, + sharing: &[Arc], + ) -> ixon::constant::MutConst { + use ixon::constant::{MutConst, RecursorRule}; match member { MutConst::Defn(def) => { let mut def = def.clone(); @@ -2563,18 +2565,18 @@ extern "C" fn rs_compile_validate_aux( } } - fn expr_hash_prefix(expr: &Arc) -> String { + fn expr_hash_prefix(expr: &Arc) -> String { let mut buf = Vec::new(); - crate::ix::ixon::serialize::put_expr(expr, &mut buf); - let h = crate::ix::address::Address::hash(&buf); + ixon::serialize::put_expr(expr, &mut buf); + let h = ix_common::address::Address::hash(&buf); format!("{}:{}", buf.len(), &h.hex()[..12]) } fn member_parts_summary( - member: &crate::ix::ixon::constant::MutConst, - sharing: &[Arc], + member: &ixon::constant::MutConst, + sharing: &[Arc], ) -> String { - use crate::ix::ixon::constant::MutConst; + use ixon::constant::MutConst; let expanded = expand_shares_member(member, sharing); match expanded { MutConst::Defn(def) => { @@ -2598,12 +2600,12 @@ extern "C" fn rs_compile_validate_aux( } let proj = match stt.env.get_const(addr).map(|c| c.info.clone()) { - Some(crate::ix::ixon::constant::ConstantInfo::RPrj(p)) => p, + Some(ixon::constant::ConstantInfo::RPrj(p)) => p, _ => return None, }; let block = stt.env.get_const(&proj.block)?; let member_count_for_names = match &block.info { - crate::ix::ixon::constant::ConstantInfo::Muts(ms) => ms.len(), + ixon::constant::ConstantInfo::Muts(ms) => ms.len(), _ => 0, }; let proj_names: Vec = (0..member_count_for_names) @@ -2615,17 +2617,17 @@ extern "C" fn rs_compile_validate_aux( .chain(stt.name_to_addr.iter()) .filter_map(|entry| { match stt.env.get_const(entry.value()).map(|c| c.info.clone()) { - Some(crate::ix::ixon::constant::ConstantInfo::RPrj(p)) + Some(ixon::constant::ConstantInfo::RPrj(p)) if p.block == proj.block && p.idx == idx => { Some(entry.key().pretty()) }, - Some(crate::ix::ixon::constant::ConstantInfo::IPrj(p)) + Some(ixon::constant::ConstantInfo::IPrj(p)) if p.block == proj.block && p.idx == idx => { Some(entry.key().pretty()) }, - Some(crate::ix::ixon::constant::ConstantInfo::DPrj(p)) + Some(ixon::constant::ConstantInfo::DPrj(p)) if p.block == proj.block && p.idx == idx => { Some(entry.key().pretty()) @@ -2659,22 +2661,22 @@ extern "C" fn rs_compile_validate_aux( }) .collect(); let (members, per_member_hashes) = match &block.info { - crate::ix::ixon::constant::ConstantInfo::Muts(ms) => { + ixon::constant::ConstantInfo::Muts(ms) => { let per: Vec = ms .iter() .map(|m| { // Compute a per-member byte hash for quick diffing. let mut buf = Vec::new(); m.put(&mut buf); - let h = crate::ix::address::Address::hash(&buf); + let h = ix_common::address::Address::hash(&buf); let expanded = expand_shares_member(m, &block.sharing); let mut expanded_buf = Vec::new(); expanded.put(&mut expanded_buf); - let expanded_h = crate::ix::address::Address::hash(&expanded_buf); + let expanded_h = ix_common::address::Address::hash(&expanded_buf); let tag = match m { - crate::ix::ixon::constant::MutConst::Defn(_) => "Defn", - crate::ix::ixon::constant::MutConst::Indc(_) => "Indc", - crate::ix::ixon::constant::MutConst::Recr(_) => "Recr", + ixon::constant::MutConst::Defn(_) => "Defn", + ixon::constant::MutConst::Indc(_) => "Indc", + ixon::constant::MutConst::Recr(_) => "Recr", }; let parts = member_parts_summary(m, &block.sharing); format!( @@ -3563,7 +3565,7 @@ extern "C" fn rs_compile_validate_aux( // ends before we drop it. let fresh_env = { let mut buf: &[u8] = &serialized; - match crate::ix::ixon::env::Env::get(&mut buf) { + match ixon::env::Env::get(&mut buf) { Ok(fe) => Some(fe), Err(e) => { p7.record_fail(format!("deserialize FAILED: {e}")); @@ -3576,7 +3578,7 @@ extern "C" fn rs_compile_validate_aux( match fresh_env { Some(fresh_env) => { - let fresh_stt = crate::ix::compile::CompileState { + let fresh_stt = ix_compile::compile::CompileState { env: fresh_env, ..Default::default() }; @@ -3672,7 +3674,7 @@ extern "C" fn rs_compile_validate_aux( (None, None) => true, _ => false, }; - let aux_eq_result = if crate::ix::decompile::is_aux_gen_suffix(name) + let aux_eq_result = if ix_compile::decompile::is_aux_gen_suffix(name) && !(type_ok && val_ok) { Some(aux_congruence_result( @@ -3738,8 +3740,8 @@ extern "C" fn rs_compile_validate_aux( // ══════════════════════════════════════════════════════════════════════ let mut p8 = PhaseResult::new("8. Nested detection"); { - use crate::ix::compile::aux_gen::nested::build_compile_flat_block; - use crate::ix::env::ConstantInfo; + use ix_common::env::ConstantInfo; + use ix_compile::compile::aux_gen::nested::build_compile_flat_block; /// Build a dotted Lean name from a dot-separated string. /// Numeric components (e.g. the `0` in `_private.Foo.0.Bar`) are @@ -3880,7 +3882,7 @@ impl ConstSizeBreakdown { #[cfg(feature = "test-ffi")] /// Analyze the serialized size of a constant and its transitive dependencies. -fn analyze_const_size(stt: &crate::ix::compile::CompileState, name_str: &str) { +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); @@ -4013,10 +4015,10 @@ fn analyze_const_size(stt: &crate::ix::compile::CompileState, name_str: &str) { /// Build a name index for metadata serialization. #[cfg(feature = "test-ffi")] fn build_name_index( - stt: &crate::ix::compile::CompileState, -) -> crate::ix::ixon::metadata::NameIndex { - use crate::ix::address::Address; - use crate::ix::ixon::metadata::NameIndex; + 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; @@ -4037,10 +4039,10 @@ fn build_name_index( /// Compute size breakdown for a constant (alpha-invariant vs metadata). #[cfg(feature = "test-ffi")] fn compute_const_size_breakdown( - constant: &crate::ix::ixon::constant::Constant, + constant: &ixon::constant::Constant, name: &Name, - stt: &crate::ix::compile::CompileState, - name_index: &crate::ix::ixon::metadata::NameIndex, + stt: &ix_compile::compile::CompileState, + name_index: &ixon::metadata::NameIndex, ) -> ConstSizeBreakdown { // Alpha-invariant size let alpha_size = serialized_const_size(constant); @@ -4058,8 +4060,8 @@ fn compute_const_size_breakdown( /// Compute the serialized size of constant metadata. #[cfg(feature = "test-ffi")] fn serialized_meta_size( - meta: &crate::ix::ixon::metadata::ConstantMeta, - name_index: &crate::ix::ixon::metadata::NameIndex, + meta: &ixon::metadata::ConstantMeta, + name_index: &ixon::metadata::NameIndex, ) -> usize { let mut buf = Vec::new(); meta @@ -4088,9 +4090,7 @@ pub fn parse_name(s: &str) -> Name { /// Compute the serialized size of a constant. #[cfg(feature = "test-ffi")] -fn serialized_const_size( - constant: &crate::ix::ixon::constant::Constant, -) -> usize { +fn serialized_const_size(constant: &ixon::constant::Constant) -> usize { let mut buf = Vec::new(); constant.put(&mut buf); buf.len() @@ -4098,11 +4098,11 @@ fn serialized_const_size( /// Analyze block size statistics: hash-consing vs serialization. #[cfg(feature = "test-ffi")] -fn analyze_block_size_stats(stt: &crate::ix::compile::CompileState) { - use crate::ix::compile::BlockSizeStats; +fn analyze_block_size_stats(stt: &ix_compile::compile::CompileState) { + use ix_compile::compile::BlockSizeStats; // Check if hash-consed size tracking was enabled - let tracking_enabled = crate::ix::compile::TRACK_HASH_CONSED_SIZE + let tracking_enabled = ix_compile::compile::TRACK_HASH_CONSED_SIZE .load(std::sync::atomic::Ordering::Relaxed); if !tracking_enabled { println!("\n=== Block Size Analysis ==="); diff --git a/src/ffi/iroh.rs b/crates/ffi/src/lean_iroh.rs similarity index 100% rename from src/ffi/iroh.rs rename to crates/ffi/src/lean_iroh.rs diff --git a/src/ffi/ixon.rs b/crates/ffi/src/lean_ixon.rs similarity index 100% rename from src/ffi/ixon.rs rename to crates/ffi/src/lean_ixon.rs diff --git a/src/ffi/ixon/compare.rs b/crates/ffi/src/lean_ixon/compare.rs similarity index 96% rename from src/ffi/ixon/compare.rs rename to crates/ffi/src/lean_ixon/compare.rs index aa82724b..6890d89d 100644 --- a/src/ffi/ixon/compare.rs +++ b/crates/ffi/src/lean_ixon/compare.rs @@ -2,14 +2,16 @@ use std::collections::HashMap; -use crate::ix::compile::{BlockCache, CompileState, compile_env, compile_expr}; -use crate::ix::env::Name; -use crate::ix::ixon::serialize::put_expr; -use crate::ix::mutual::MutCtx; 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::ffi::lean_env::{ +use crate::lean_env::{ Cache as LeanCache, GlobalCache, decode_expr, decode_name, }; @@ -195,7 +197,7 @@ pub unsafe extern "C" fn rs_free_compiled_env(ptr: *mut RustBlockEnv) { pub extern "C" fn rs_build_compiled_env( env_consts_ptr: LeanList>, ) -> *mut RustBlockEnv { - use crate::ffi::lean_env::decode_env; + use crate::lean_env::decode_env; // Decode Lean environment let rust_env = decode_env(env_consts_ptr); diff --git a/src/ffi/ixon/constant.rs b/crates/ffi/src/lean_ixon/constant.rs similarity index 97% rename from src/ffi/ixon/constant.rs rename to crates/ffi/src/lean_ixon/constant.rs index eabde98b..d5fe112f 100644 --- a/src/ffi/ixon/constant.rs +++ b/crates/ffi/src/lean_ixon/constant.rs @@ -6,14 +6,6 @@ use std::sync::Arc; -use crate::ix::ixon::constant::{ - Axiom as IxonAxiom, Constant as IxonConstant, - ConstantInfo as IxonConstantInfo, Constructor as IxonConstructor, - ConstructorProj, DefKind, Definition as IxonDefinition, DefinitionProj, - Inductive as IxonInductive, InductiveProj, MutConst, - Quotient as IxonQuotient, Recursor as IxonRecursor, RecursorProj, - RecursorRule as IxonRecursorRule, -}; use crate::lean::{ LeanIxAddress, LeanIxonAxiom, LeanIxonConstant, LeanIxonConstantInfo, LeanIxonConstructor, LeanIxonConstructorProj, LeanIxonDefinition, @@ -21,6 +13,14 @@ use crate::lean::{ LeanIxonInductiveProj, LeanIxonMutConst, LeanIxonQuotient, LeanIxonRecursor, LeanIxonRecursorProj, LeanIxonRecursorRule, LeanIxonUniv, }; +use ixon::constant::{ + Axiom as IxonAxiom, Constant as IxonConstant, + ConstantInfo as IxonConstantInfo, Constructor as IxonConstructor, + ConstructorProj, DefKind, Definition as IxonDefinition, DefinitionProj, + Inductive as IxonInductive, InductiveProj, MutConst, + Quotient as IxonQuotient, Recursor as IxonRecursor, RecursorProj, + RecursorRule as IxonRecursorRule, +}; #[cfg(feature = "test-ffi")] use lean_ffi::object::LeanBorrowed; use lean_ffi::object::{LeanArray, LeanOwned, LeanRef}; @@ -47,9 +47,9 @@ impl LeanIxonDefinition { }; ctor.set_num_8(0, kind_val); let safety_val: u8 = match def.safety { - crate::ix::env::DefinitionSafety::Unsafe => 0, - crate::ix::env::DefinitionSafety::Safe => 1, - crate::ix::env::DefinitionSafety::Partial => 2, + ix_common::env::DefinitionSafety::Unsafe => 0, + ix_common::env::DefinitionSafety::Safe => 1, + ix_common::env::DefinitionSafety::Partial => 2, }; ctor.set_num_8(1, safety_val); ctor @@ -75,9 +75,9 @@ impl LeanIxonDefinition { }; let safety_val = self.get_num_8(1); let safety = match safety_val { - 0 => crate::ix::env::DefinitionSafety::Unsafe, - 1 => crate::ix::env::DefinitionSafety::Safe, - 2 => crate::ix::env::DefinitionSafety::Partial, + 0 => ix_common::env::DefinitionSafety::Unsafe, + 1 => ix_common::env::DefinitionSafety::Safe, + 2 => ix_common::env::DefinitionSafety::Partial, _ => panic!("Invalid DefinitionSafety: {}", safety_val), }; IxonDefinition { kind, safety, lvls, typ, value } @@ -210,10 +210,10 @@ impl LeanIxonQuotient { ctor.set_obj(0, typ_obj); ctor.set_num_64(0, quot.lvls); let kind_val: u8 = match quot.kind { - crate::ix::env::QuotKind::Type => 0, - crate::ix::env::QuotKind::Ctor => 1, - crate::ix::env::QuotKind::Lift => 2, - crate::ix::env::QuotKind::Ind => 3, + ix_common::env::QuotKind::Type => 0, + ix_common::env::QuotKind::Ctor => 1, + ix_common::env::QuotKind::Lift => 2, + ix_common::env::QuotKind::Ind => 3, }; ctor.set_num_8(0, kind_val); ctor @@ -229,10 +229,10 @@ impl LeanIxonQuotient { let lvls = self.get_num_64(0); let kind_val = self.get_num_8(0); let kind = match kind_val { - 0 => crate::ix::env::QuotKind::Type, - 1 => crate::ix::env::QuotKind::Ctor, - 2 => crate::ix::env::QuotKind::Lift, - 3 => crate::ix::env::QuotKind::Ind, + 0 => ix_common::env::QuotKind::Type, + 1 => ix_common::env::QuotKind::Ctor, + 2 => ix_common::env::QuotKind::Lift, + 3 => ix_common::env::QuotKind::Ind, _ => panic!("Invalid QuotKind: {}", kind_val), }; IxonQuotient { kind, lvls, typ } diff --git a/src/ffi/ixon/enums.rs b/crates/ffi/src/lean_ixon/enums.rs similarity index 97% rename from src/ffi/ixon/enums.rs rename to crates/ffi/src/lean_ixon/enums.rs index 0afa0593..54fa7a4f 100644 --- a/src/ffi/ixon/enums.rs +++ b/crates/ffi/src/lean_ixon/enums.rs @@ -1,10 +1,10 @@ //! Ixon enum types: DefKind, DefinitionSafety, QuotKind build/decode/roundtrip FFI. -use crate::ix::env::{DefinitionSafety, QuotKind}; -use crate::ix::ixon::constant::DefKind; use crate::lean::{ LeanIxonDefKind, LeanIxonDefinitionSafety, LeanIxonQuotKind, }; +use ix_common::env::{DefinitionSafety, QuotKind}; +use ixon::constant::DefKind; #[cfg(feature = "test-ffi")] use lean_ffi::object::LeanBorrowed; use lean_ffi::object::{LeanOwned, LeanRef}; diff --git a/src/ffi/ixon/env.rs b/crates/ffi/src/lean_ixon/env.rs similarity index 97% rename from src/ffi/ixon/env.rs rename to crates/ffi/src/lean_ixon/env.rs index 02d3b27b..120dd26e 100644 --- a/src/ffi/ixon/env.rs +++ b/crates/ffi/src/lean_ixon/env.rs @@ -3,23 +3,23 @@ //! Provides full decode/build cycle for RawEnv and its component types: //! RawConst, RawNamed, RawBlob, RawComm. -use crate::ix::address::Address; -use crate::ix::env::Name; -use crate::ix::ixon::comm::Comm; -use crate::ix::ixon::constant::Constant as IxonConstant; -use crate::ix::ixon::env::{Env as IxonEnv, Named as IxonNamed}; -use crate::ix::ixon::merkle::merkle_root_canonical; -use crate::ix::ixon::metadata::ConstantMeta; use crate::lean::{ LeanIxName, LeanIxonComm, LeanIxonConstant, LeanIxonConstantMeta, LeanIxonRawBlob, LeanIxonRawComm, LeanIxonRawConst, LeanIxonRawEnv, LeanIxonRawNameEntry, LeanIxonRawNamed, }; +use ix_common::address::Address; +use ix_common::env::Name; +use ixon::comm::Comm; +use ixon::constant::Constant as IxonConstant; +use ixon::env::{Env as IxonEnv, Named as IxonNamed}; +use ixon::merkle::merkle_root_canonical; +use ixon::metadata::ConstantMeta; use lean_ffi::object::{ LeanArray, LeanBorrowed, LeanByteArray, LeanExcept, LeanOwned, LeanRef, }; -use crate::ffi::builder::LeanBuildCache; +use crate::builder::LeanBuildCache; use crate::lean::LeanIxAddress; // ============================================================================= diff --git a/src/ffi/ixon/expr.rs b/crates/ffi/src/lean_ixon/expr.rs similarity index 99% rename from src/ffi/ixon/expr.rs rename to crates/ffi/src/lean_ixon/expr.rs index 4fb71091..cea24163 100644 --- a/src/ffi/ixon/expr.rs +++ b/crates/ffi/src/lean_ixon/expr.rs @@ -2,8 +2,8 @@ use std::sync::Arc; -use crate::ix::ixon::expr::Expr as IxonExpr; use crate::lean::LeanIxonExpr; +use ixon::expr::Expr as IxonExpr; use lean_ffi::object::{LeanArray, LeanBorrowed, LeanOwned, LeanRef}; /// Decode Array UInt64 from Lean. diff --git a/src/ffi/ixon/meta.rs b/crates/ffi/src/lean_ixon/meta.rs similarity index 99% rename from src/ffi/ixon/meta.rs rename to crates/ffi/src/lean_ixon/meta.rs index 3962f65d..13db8ec0 100644 --- a/src/ffi/ixon/meta.rs +++ b/crates/ffi/src/lean_ixon/meta.rs @@ -2,19 +2,19 @@ //! //! Includes: DataValue, KVMap, ExprMetaData, ExprMetaArena, ConstantMeta, Named, Comm -use crate::ix::address::Address; -use crate::ix::env::BinderInfo; -use crate::ix::ixon::Comm; -use crate::ix::ixon::env::Named; -use crate::ix::ixon::metadata::{ - ConstantMeta, ConstantMetaInfo, DataValue as IxonDataValue, ExprMeta, - ExprMetaData, KVMap, -}; use crate::lean::{ LeanIxReducibilityHints, LeanIxonComm, LeanIxonConstantMeta, LeanIxonDataValue, LeanIxonExprMetaArena, LeanIxonExprMetaData, LeanIxonNamed, }; +use ix_common::address::Address; +use ix_common::env::BinderInfo; +use ixon::Comm; +use ixon::env::Named; +use ixon::metadata::{ + ConstantMeta, ConstantMetaInfo, DataValue as IxonDataValue, ExprMeta, + ExprMetaData, KVMap, +}; use lean_ffi::object::{ LeanArray, LeanBorrowed, LeanOption, LeanOwned, LeanProd, LeanRef, }; diff --git a/src/ffi/ixon/serialize.rs b/crates/ffi/src/lean_ixon/serialize.rs similarity index 97% rename from src/ffi/ixon/serialize.rs rename to crates/ffi/src/lean_ixon/serialize.rs index 5106a9b6..68061c03 100644 --- a/src/ffi/ixon/serialize.rs +++ b/crates/ffi/src/lean_ixon/serialize.rs @@ -5,13 +5,13 @@ use std::sync::Arc; -use crate::ix::address::Address; -use crate::ix::ixon::serialize::put_expr; -use crate::ix::ixon::sharing::hash_expr; -use crate::ix::ixon::univ::put_univ; use crate::lean::{ LeanIxAddress, LeanIxonConstant, LeanIxonExpr, LeanIxonRawEnv, LeanIxonUniv, }; +use ix_common::address::Address; +use ixon::serialize::put_expr; +use ixon::sharing::hash_expr; +use ixon::univ::put_univ; use lean_ffi::object::{LeanBorrowed, LeanByteArray, LeanOwned}; /// Check if Lean's computed hash matches Rust's computed hash. @@ -77,7 +77,7 @@ pub extern "C" fn rs_eq_env_serialization( raw_env_obj: LeanIxonRawEnv>, bytes_obj: LeanByteArray>, ) -> bool { - use crate::ix::ixon::env::Env; + use ixon::env::Env; let debug = std::env::var("IX_DEBUG_SERDE").is_ok(); let decoded = raw_env_obj.decode(); @@ -246,7 +246,7 @@ pub extern "C" fn rs_eq_env_serialization( extern "C" fn rs_env_serde_roundtrip( lean_bytes_obj: LeanByteArray, ) -> bool { - use crate::ix::ixon::env::Env; + use ixon::env::Env; // Get bytes from Lean ByteArray let lean_bytes = lean_bytes_obj.as_bytes().to_vec(); @@ -294,7 +294,7 @@ extern "C" fn rs_env_serde_roundtrip( extern "C" fn rs_env_serde_check( lean_bytes_obj: LeanByteArray, ) -> bool { - use crate::ix::ixon::env::Env; + use ixon::env::Env; // Get bytes from Lean ByteArray let lean_bytes = lean_bytes_obj.as_bytes().to_vec(); diff --git a/src/ffi/ixon/sharing.rs b/crates/ffi/src/lean_ixon/sharing.rs similarity index 95% rename from src/ffi/ixon/sharing.rs rename to crates/ffi/src/lean_ixon/sharing.rs index 85065081..9811fb63 100644 --- a/src/ffi/ixon/sharing.rs +++ b/crates/ffi/src/lean_ixon/sharing.rs @@ -2,12 +2,10 @@ use std::sync::Arc; -use crate::ix::ixon::expr::Expr as IxonExpr; -use crate::ix::ixon::serialize::put_expr; -use crate::ix::ixon::sharing::{ - analyze_block, build_sharing_vec, decide_sharing, -}; use crate::lean::LeanIxonExpr; +use ixon::expr::Expr as IxonExpr; +use ixon::serialize::put_expr; +use ixon::sharing::{analyze_block, build_sharing_vec, decide_sharing}; use lean_ffi::object::{LeanArray, LeanBorrowed, LeanByteArray, LeanOwned}; /// FFI: Debug sharing analysis - print usage counts for subterms with usage >= 2. @@ -23,7 +21,7 @@ pub extern "C" fn rs_debug_sharing_analysis( let (info_map, _ptr_to_hash, topo_order) = analyze_block(&exprs, false); let effective_sizes = - crate::ix::ixon::sharing::compute_effective_sizes(&info_map, &topo_order); + ixon::sharing::compute_effective_sizes(&info_map, &topo_order); println!("[Rust] Found {} unique subterms", info_map.len()); diff --git a/src/ffi/ixon/univ.rs b/crates/ffi/src/lean_ixon/univ.rs similarity index 98% rename from src/ffi/ixon/univ.rs rename to crates/ffi/src/lean_ixon/univ.rs index c85dde83..57b8eab1 100644 --- a/src/ffi/ixon/univ.rs +++ b/crates/ffi/src/lean_ixon/univ.rs @@ -2,8 +2,8 @@ use std::sync::Arc; -use crate::ix::ixon::univ::Univ; use crate::lean::LeanIxonUniv; +use ixon::univ::Univ; use lean_ffi::object::{LeanArray, LeanBorrowed, LeanOwned, LeanRef}; impl LeanIxonUniv { diff --git a/crates/ffi/src/lib.rs b/crates/ffi/src/lib.rs new file mode 100644 index 00000000..38df7e1d --- /dev/null +++ b/crates/ffi/src/lib.rs @@ -0,0 +1,60 @@ +// `_iroh` provides dummy fallbacks when the `net` feature is disabled or on +// `aarch64-darwin` (where iroh doesn't work). Lean and C don't support feature +// flags, so we always expose iroh symbols — they just panic with an error +// message when called without `net`. +#[cfg(any( + not(feature = "net"), + all(target_os = "macos", target_arch = "aarch64") +))] +pub mod _iroh; +pub mod aiur; +pub mod byte_array; +#[cfg(all( + feature = "net", + not(all(target_os = "macos", target_arch = "aarch64")) +))] +pub mod iroh; +pub mod keccak; +pub mod lean_env; +#[cfg(all( + feature = "net", + not(all(target_os = "macos", target_arch = "aarch64")) +))] +pub mod lean_iroh; +pub mod texray; +pub mod unsigned; + +pub mod builder; +pub mod compile; +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; +mod sha256; + +#[global_allocator] +static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; + +#[cfg(feature = "test-ffi")] +use lean_ffi::object::{ + LeanArray, LeanBorrowed, LeanByteArray, LeanOwned, LeanRef, +}; + +#[cfg(feature = "test-ffi")] +#[unsafe(no_mangle)] +extern "C" fn rs_boxed_u32s_are_equivalent_to_bytes( + u32s: LeanArray, + bytes: LeanByteArray>, +) -> bool { + let u32s_flat: Vec = u32s + .map(|elem| elem.unbox_u32()) + .into_iter() + .flat_map(u32::to_le_bytes) + .collect(); + u32s_flat == bytes.as_bytes() +} diff --git a/src/ffi/primitives.rs b/crates/ffi/src/primitives.rs similarity index 91% rename from src/ffi/primitives.rs rename to crates/ffi/src/primitives.rs index c73e1448..89408189 100644 --- a/src/ffi/primitives.rs +++ b/crates/ffi/src/primitives.rs @@ -4,6 +4,7 @@ //! (ix-specific types not covered by the lean-ffi test suite). 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}; @@ -34,8 +35,8 @@ fn build_assoc_list_nat_nat(pairs: &[(Nat, Nat)]) -> LeanOwned { let mut list = LeanOwned::box_usize(0); // nil for (k, v) in pairs.iter().rev() { let cons = LeanCtor::alloc(1, 3, 0); // AssocList.cons - cons.set(0, Nat::to_lean(k)); - cons.set(1, Nat::to_lean(v)); + cons.set(0, LeanNat::from_nat(k)); + cons.set(1, LeanNat::from_nat(v)); cons.set(2, list); list = cons.into(); } @@ -53,7 +54,7 @@ pub extern "C" fn rs_roundtrip_dhashmap_raw_nat_nat( } let raw_ctor = raw_ptr.as_ctor(); - let size = Nat::from_obj(&raw_ctor.get(0)); + let size = LeanNat::to_nat(&raw_ctor.get(0)); let buckets = raw_ctor.get(1).as_array(); // Decode and rebuild buckets @@ -85,15 +86,15 @@ pub extern "C" fn rs_roundtrip_dhashmap_raw_nat_nat( let old_bucket = new_buckets.get(bucket_idx).to_owned_ref(); let new_bucket = LeanCtor::alloc(1, 3, 0); - new_bucket.set(0, Nat::to_lean(k)); - new_bucket.set(1, Nat::to_lean(v)); + new_bucket.set(0, LeanNat::from_nat(k)); + new_bucket.set(1, LeanNat::from_nat(v)); new_bucket.set(2, old_bucket); new_buckets.set(bucket_idx, new_bucket); } // Build Raw let raw = LeanCtor::alloc(0, 2, 0); - raw.set(0, Nat::to_lean(&size)); + raw.set(0, LeanNat::from_nat(&size)); raw.set(1, new_buckets); raw.into() } @@ -119,7 +120,7 @@ pub extern "C" fn rs_roundtrip_hashmap_nat_nat( map_ptr: LeanCtor>, ) -> LeanOwned { // Due to unboxing, map_ptr points directly to Raw - let size = Nat::from_obj(&map_ptr.get(0)); + let size = LeanNat::to_nat(&map_ptr.get(0)); let buckets = map_ptr.get(1).as_array(); // Decode buckets (Array of AssocLists) @@ -160,8 +161,8 @@ pub extern "C" fn rs_roundtrip_hashmap_nat_nat( // Build AssocList.cons key value tail (tag 1, 3 fields) let new_bucket = LeanCtor::alloc(1, 3, 0); - new_bucket.set(0, Nat::to_lean(k)); - new_bucket.set(1, Nat::to_lean(v)); + new_bucket.set(0, LeanNat::from_nat(k)); + new_bucket.set(1, LeanNat::from_nat(v)); new_bucket.set(2, old_bucket); new_buckets.set(bucket_idx, new_bucket); } @@ -169,7 +170,7 @@ pub extern "C" fn rs_roundtrip_hashmap_nat_nat( // Build Raw (ctor 0, 2 fields: size, buckets) // Due to unboxing, this IS the HashMap let raw = LeanCtor::alloc(0, 2, 0); - raw.set(0, Nat::to_lean(&size)); + raw.set(0, LeanNat::from_nat(&size)); raw.set(1, new_buckets); raw.into() } @@ -188,7 +189,7 @@ pub fn decode_assoc_list_nat_nat(obj: LeanBorrowed<'_>) -> Vec<(Nat, Nat)> { let key = ctor.get(0); let val = ctor.get(1); let next = ctor.get(2).as_raw(); - result.push((Nat::from_obj(&key), Nat::from_obj(&val))); + result.push((LeanNat::to_nat(&key), LeanNat::to_nat(&val))); current = unsafe { LeanBorrowed::from_raw(next) }; } diff --git a/src/ffi/refcount.rs b/crates/ffi/src/refcount.rs similarity index 96% rename from src/ffi/refcount.rs rename to crates/ffi/src/refcount.rs index f5edd77c..a6834599 100644 --- a/src/ffi/refcount.rs +++ b/crates/ffi/src/refcount.rs @@ -17,11 +17,12 @@ use std::thread; use lean_ffi::LeanShared; use lean_ffi::nat::Nat; +use lean_ffi::object::LeanNat; use lean_ffi::object::{ LeanArray, LeanBorrowed, LeanList, LeanOwned, LeanRef, LeanString, }; -use crate::ffi::builder::LeanBuildCache; +use crate::builder::LeanBuildCache; use crate::lean::{LeanIxExpr, LeanIxLevel, LeanIxName}; // ============================================================================= @@ -92,7 +93,7 @@ pub extern "C" fn rs_refcount_deep_borrow_expr( _ => 1, } } - Nat::from(count_nodes(&expr)).to_lean().into() + LeanNat::from_nat(&Nat::from(count_nodes(&expr))).into() } // ============================================================================= @@ -109,7 +110,7 @@ pub extern "C" fn rs_refcount_owned_array_drop( for i in 0..count { let _ = LeanIxName(arr.get(i)).decode(); } - Nat::from(count as u64).to_lean().into() + LeanNat::from_nat(&Nat::from(count as u64)).into() } /// Take an owned List of Exprs, consume and count. @@ -122,7 +123,7 @@ pub extern "C" fn rs_refcount_owned_list_drop( let _ = LeanIxExpr(elem).decode(); count += 1; } - Nat::from(count).to_lean().into() + LeanNat::from_nat(&Nat::from(count)).into() } // ============================================================================= @@ -140,7 +141,8 @@ pub extern "C" fn rs_refcount_clone_and_compare( let decoded1 = owned1.decode(); let decoded2 = owned2.decode(); // owned1 and owned2 both dropped → two lean_dec calls - Nat::from(if decoded1 == decoded2 { 1u64 } else { 0u64 }).to_lean().into() + LeanNat::from_nat(&Nat::from(if decoded1 == decoded2 { 1u64 } else { 0u64 })) + .into() } // ============================================================================= @@ -180,7 +182,7 @@ pub extern "C" fn rs_refcount_nested_borrow( let _level = LeanIxLevel(pair.get(1)).decode(); count += 1; } - Nat::from(count).to_lean().into() + LeanNat::from_nat(&Nat::from(count)).into() } // ============================================================================= @@ -313,7 +315,7 @@ pub extern "C" fn rs_mt_parallel_decode_names( .collect(); let total: u64 = handles.into_iter().map(|h| h.join().unwrap()).sum(); - Nat::from(total).to_lean().into() + LeanNat::from_nat(&Nat::from(total)).into() } /// Mark an array of Ix.Exprs as MT, decode in parallel from N threads. @@ -352,7 +354,7 @@ pub extern "C" fn rs_mt_parallel_decode_exprs( .collect(); let total: u64 = handles.into_iter().map(|h| h.join().unwrap()).sum(); - Nat::from(total).to_lean().into() + LeanNat::from_nat(&Nat::from(total)).into() } /// Parallel roundtrip: mark array of Names as MT, each thread decodes @@ -364,7 +366,7 @@ pub extern "C" fn rs_mt_parallel_roundtrip_names( arr: LeanArray>, n_threads: usize, ) -> LeanArray { - use crate::ix::env::Name; + use ix_common::env::Name; let shared = LeanShared::new(arr.inner().to_owned_ref()); let len = arr.len(); @@ -428,5 +430,5 @@ pub extern "C" fn rs_mt_shared_expr_stress( .collect(); let total: u64 = handles.into_iter().map(|h| h.join().unwrap()).sum(); - Nat::from(total).to_lean().into() + LeanNat::from_nat(&Nat::from(total)).into() } diff --git a/src/sha256.rs b/crates/ffi/src/sha256.rs similarity index 100% rename from src/sha256.rs rename to crates/ffi/src/sha256.rs diff --git a/src/ffi/texray.rs b/crates/ffi/src/texray.rs similarity index 100% rename from src/ffi/texray.rs rename to crates/ffi/src/texray.rs diff --git a/src/ffi/unsigned.rs b/crates/ffi/src/unsigned.rs similarity index 100% rename from src/ffi/unsigned.rs rename to crates/ffi/src/unsigned.rs diff --git a/crates/ixon/Cargo.toml b/crates/ixon/Cargo.toml new file mode 100644 index 00000000..d2c0183c --- /dev/null +++ b/crates/ixon/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "ixon" +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 } +memmap2 = { 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"] } +rayon = { workspace = true } + +[dev-dependencies] +ix-common = { workspace = true, features = ["quickcheck"] } +quickcheck = { workspace = true } +quickcheck_macros = { workspace = true } + +[lints] +workspace = true diff --git a/src/ix/ixon/assumption_tree.rs b/crates/ixon/src/assumption_tree.rs similarity index 99% rename from src/ix/ixon/assumption_tree.rs rename to crates/ixon/src/assumption_tree.rs index 4cf61d2b..05500c2f 100644 --- a/src/ix/ixon/assumption_tree.rs +++ b/crates/ixon/src/assumption_tree.rs @@ -36,7 +36,7 @@ //! `leaves()` clean (it returns only real leaves, not the synthetic //! padding addresses). -use crate::ix::address::Address; +use ix_common::address::Address; use super::merkle::{MerklePath, leaf_hash, node_hash, zero_address}; use super::proof::{FLAG_CLAIM, VARIANT_ASSUMPTION_TREE}; diff --git a/src/ix/ixon/comm.rs b/crates/ixon/src/comm.rs similarity index 98% rename from src/ix/ixon/comm.rs rename to crates/ixon/src/comm.rs index a27afcb2..9de866ca 100644 --- a/src/ix/ixon/comm.rs +++ b/crates/ixon/src/comm.rs @@ -3,7 +3,7 @@ #![allow(clippy::map_err_ignore)] #![allow(clippy::needless_pass_by_value)] -use crate::ix::address::Address; +use ix_common::address::Address; use super::tag::Tag4; @@ -81,6 +81,7 @@ impl Comm { mod tests { use super::*; use quickcheck::Arbitrary; + use quickcheck_macros::quickcheck; impl Arbitrary for Comm { fn arbitrary(g: &mut quickcheck::Gen) -> Self { diff --git a/src/ix/ixon/constant.rs b/crates/ixon/src/constant.rs similarity index 94% rename from src/ix/ixon/constant.rs rename to crates/ixon/src/constant.rs index 0f311566..12cc881b 100644 --- a/src/ix/ixon/constant.rs +++ b/crates/ixon/src/constant.rs @@ -10,8 +10,8 @@ use std::sync::Arc; -use crate::ix::address::Address; -use crate::ix::env::{DefinitionSafety, QuotKind}; +use ix_common::address::Address; +use ix_common::env::{DefinitionSafety, QuotKind}; use super::expr::Expr; use super::univ::Univ; @@ -310,10 +310,11 @@ pub fn ctor_proj_constant(idx: u64, cidx: u64, block: Address) -> Constant { #[cfg(test)] pub mod tests { use super::*; - use crate::ix::env::{DefinitionSafety, QuotKind}; - use crate::ix::ixon::expr::tests::arbitrary_expr; - use crate::ix::ixon::tests::gen_range; + use crate::expr::tests::arbitrary_expr; + use crate::tests::gen_range; + use ix_common::env::{DefinitionSafety, QuotKind}; use quickcheck::{Arbitrary, Gen}; + use quickcheck_macros::quickcheck; impl Arbitrary for DefKind { fn arbitrary(g: &mut Gen) -> Self { @@ -325,27 +326,6 @@ pub mod tests { } } - 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, - } - } - } - pub fn gen_sharing(g: &mut Gen) -> Vec> { (0..gen_range(g, 0..4)).map(|_| arbitrary_expr(g)).collect() } @@ -355,7 +335,7 @@ pub mod tests { } pub fn gen_univs(g: &mut Gen) -> Vec> { - use crate::ix::ixon::univ::tests::arbitrary_univ; + use crate::univ::tests::arbitrary_univ; (0..gen_range(g, 0..4)).map(|_| arbitrary_univ(g)).collect() } diff --git a/src/ix/ixon/env.rs b/crates/ixon/src/env.rs similarity index 88% rename from src/ix/ixon/env.rs rename to crates/ixon/src/env.rs index e8363774..1e27774d 100644 --- a/src/ix/ixon/env.rs +++ b/crates/ixon/src/env.rs @@ -1,17 +1,17 @@ //! Environment for storing Ixon data. -use dashmap::DashMap; use rustc_hash::{FxHashMap, FxHashSet}; use std::collections::VecDeque; use std::sync::Arc; -use crate::ix::address::Address; -use crate::ix::env::{Name, ReducibilityHints}; +use ix_common::address::Address; +use ix_common::env::{Name, ReducibilityHints}; use super::comm::Comm; use super::constant::Constant; use super::lazy::LazyConstant; -use super::metadata::ConstantMeta; +use super::map::IxonMap; +use super::metadata::{ConstantMeta, ConstantMetaInfo}; /// A named constant with metadata. #[derive(Clone, Debug)] @@ -43,7 +43,7 @@ impl Named { /// correctly permute source-order aux motives/minors into canonical /// positions. Both arrays have one entry per source-walk-discovered aux. /// -/// This lives in `ixon::env` (not `compile::surgery`, where it originated) +/// This lives in `ixon::env` (not `ix_compile::surgery`, where it originated) /// so it can be persisted into the serialized Ixon environment as a /// side-table on [`Env::aux_layouts`]. The surgery layer re-exports it. /// @@ -75,15 +75,15 @@ pub struct AuxLayout { pub struct Env { /// Alpha-invariant constants: Address -> LazyConstant (raw bytes + /// optional materialized cache; see [`LazyConstant`]). - pub consts: DashMap, + pub consts: IxonMap, /// Named references: Name -> (constant address, metadata, ctx) - pub named: DashMap, + pub named: IxonMap, /// Raw data blobs: Address -> bytes - pub blobs: DashMap>, + pub blobs: IxonMap>, /// Hash-consed Lean.Name components: Address -> Name - pub names: DashMap, + pub names: IxonMap, /// Cryptographic commitments: commitment Address -> Comm - pub comms: DashMap, + pub comms: IxonMap, /// Reducibility hints sidecar harvested by [`Env::get_anon`] from the /// otherwise-discarded Named section. Keyed by the constant's /// projection/standalone address (i.e. `Named.addr` — the address the @@ -103,16 +103,22 @@ pub struct Env { impl Env { pub fn new() -> Self { Env { - consts: DashMap::new(), - named: DashMap::new(), - blobs: DashMap::new(), - names: DashMap::new(), - comms: DashMap::new(), + consts: IxonMap::new(), + named: IxonMap::new(), + blobs: IxonMap::new(), + names: IxonMap::new(), + comms: IxonMap::new(), anon_hints: FxHashMap::default(), } } /// Store a blob and return its content address. + /// + /// Host-only because `IxonMap` is `DashMap` here (interior-mutable `&self` + /// inserts). On `riscv64` `IxonMap` is `FxHashMap`, which requires `&mut`; + /// the guest builds `Env` via `Env::get` deserialization and doesn't need + /// the store helpers. + #[cfg(not(target_arch = "riscv64"))] pub fn store_blob(&self, bytes: Vec) -> Address { let addr = Address::hash(&bytes); self.blobs.insert(addr.clone(), bytes); @@ -129,12 +135,17 @@ impl Env { /// Serializes the constant once and pre-populates the /// [`LazyConstant`] cache so subsequent `Env::put` is a memcpy and /// the first `get_const` call is free. + /// + /// Host-only — see `store_blob`. + #[cfg(not(target_arch = "riscv64"))] pub fn store_const(&self, addr: Address, constant: Constant) { self.consts.insert(addr, LazyConstant::from_constant(constant)); } /// Store an already-serialized constant under `addr` (lazy load path). /// `bytes` must be exactly what `Constant::put` produced for `addr`. + /// Host-only — see `store_blob`. + #[cfg(not(target_arch = "riscv64"))] pub fn store_const_lazy(&self, addr: Address, bytes: Arc<[u8]>) { self.consts.insert(addr, LazyConstant::from_bytes(bytes)); } @@ -143,6 +154,8 @@ impl Env { /// `(mmap, offset, len)` must reference exactly what `Constant::put` /// produced for `addr`. Used by [`Env::get_anon_mmap`] to avoid /// heap-copying on-disk bytes — the OS page cache backs the slice. + /// Host-only — see `store_blob`. + #[cfg(not(target_arch = "riscv64"))] pub fn store_const_lazy_mmap( &self, addr: Address, @@ -175,7 +188,8 @@ impl Env { self.consts.get(addr).map(|r| Arc::from(r.value().raw_bytes())) } - /// Register a named constant. + /// Register a named constant. Host-only — see `store_blob`. + #[cfg(not(target_arch = "riscv64"))] pub fn register_name(&self, name: Name, named: Named) { self.named.insert(name, named); } @@ -185,7 +199,8 @@ impl Env { self.named.get(name).map(|r| r.clone()) } - /// Store a hash-consed name component. + /// Store a hash-consed name component. Host-only — see `store_blob`. + #[cfg(not(target_arch = "riscv64"))] pub fn store_name(&self, addr: Address, name: Name) { self.names.insert(addr, name); } @@ -195,7 +210,8 @@ impl Env { self.names.get(addr).map(|r| r.clone()) } - /// Store a commitment. + /// Store a commitment. Host-only — see `store_blob`. + #[cfg(not(target_arch = "riscv64"))] pub fn store_comm(&self, addr: Address, comm: Comm) { self.comms.insert(addr, comm); } @@ -215,6 +231,20 @@ impl Env { self.named.len() } + /// 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. + pub fn checkable_addrs(&self) -> Vec
{ + self + .named + .iter() + .filter(|e| !matches!(e.value().meta.info, ConstantMetaInfo::Muts { .. })) + .map(|e| e.value().addr.clone()) + .collect() + } + /// Number of hash-consed name components. pub fn name_count(&self) -> usize { self.names.len() @@ -276,28 +306,31 @@ impl Env { } impl Clone for Env { + // `mut` is only needed on `riscv64` where `IxonMap` wraps `FxHashMap` and + // `insert` takes `&mut self`; on host `DashMap::insert` takes `&self`. + #[cfg_attr(not(target_arch = "riscv64"), allow(unused_mut))] fn clone(&self) -> Self { - let consts = DashMap::new(); + let mut consts = IxonMap::new(); for entry in self.consts.iter() { consts.insert(entry.key().clone(), entry.value().clone()); } - let named = DashMap::new(); + let mut named = IxonMap::new(); for entry in self.named.iter() { named.insert(entry.key().clone(), entry.value().clone()); } - let blobs = DashMap::new(); + let mut blobs = IxonMap::new(); for entry in self.blobs.iter() { blobs.insert(entry.key().clone(), entry.value().clone()); } - let names = DashMap::new(); + let mut names = IxonMap::new(); for entry in self.names.iter() { names.insert(entry.key().clone(), entry.value().clone()); } - let comms = DashMap::new(); + let mut comms = IxonMap::new(); for entry in self.comms.iter() { comms.insert(entry.key().clone(), entry.value().clone()); } @@ -316,9 +349,9 @@ impl Clone for Env { #[cfg(test)] mod tests { use super::*; - use crate::ix::env::Name; - use crate::ix::ixon::constant::{Axiom, Constant, ConstantInfo}; - use crate::ix::ixon::expr::Expr; + use crate::constant::{Axiom, Constant, ConstantInfo}; + use crate::expr::Expr; + use ix_common::env::Name; use std::sync::Arc; fn n(s: &str) -> Name { diff --git a/src/ix/ixon/error.rs b/crates/ixon/src/error.rs similarity index 99% rename from src/ix/ixon/error.rs rename to crates/ixon/src/error.rs index 26f22334..0e2c8462 100644 --- a/src/ix/ixon/error.rs +++ b/crates/ixon/src/error.rs @@ -1,6 +1,6 @@ //! Custom error types for Ixon serialization and compilation. -use crate::ix::address::Address; +use ix_common::address::Address; /// Errors during serialization/deserialization. #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/src/ix/ixon/expr.rs b/crates/ixon/src/expr.rs similarity index 98% rename from src/ix/ixon/expr.rs rename to crates/ixon/src/expr.rs index 5a6f6267..815ed69c 100644 --- a/src/ix/ixon/expr.rs +++ b/crates/ixon/src/expr.rs @@ -145,10 +145,11 @@ impl Expr { #[cfg(test)] pub mod tests { use super::*; - use crate::ix::ixon::constant::Constant; - use crate::ix::ixon::serialize::{get_expr, put_expr}; - use crate::ix::ixon::tests::gen_range; + use crate::constant::Constant; + use crate::serialize::{get_expr, put_expr}; + use crate::tests::gen_range; use quickcheck::{Arbitrary, Gen}; + use quickcheck_macros::quickcheck; use std::ptr; #[derive(Clone, Copy)] @@ -169,7 +170,7 @@ pub mod tests { /// Generate an arbitrary Expr using pointer-tree technique (no stack overflow) pub fn arbitrary_expr(g: &mut Gen) -> Arc { - use crate::ix::ixon::tests::next_case; + use crate::tests::next_case; let mut root = Expr::Var(0); let mut stack = vec![&mut root as *mut Expr]; diff --git a/src/ix/ixon/lazy.rs b/crates/ixon/src/lazy.rs similarity index 88% rename from src/ix/ixon/lazy.rs rename to crates/ixon/src/lazy.rs index 269a82eb..612e67e9 100644 --- a/src/ix/ixon/lazy.rs +++ b/crates/ixon/src/lazy.rs @@ -45,7 +45,7 @@ use std::sync::Arc; use memmap2::Mmap; -use crate::ix::address::Address; +use ix_common::address::Address; use super::constant::{Constant, ConstantInfo}; @@ -104,6 +104,15 @@ pub struct LazyConstant { /// this `None`; their `get()` parses fresh and does not store — /// see the module-level "Cache policy" docs. cache: Option>, + /// Deferred address verification: when set (the `.ixe` load paths), + /// `get()` checks `Address::hash(bytes) == pending_addr` before + /// parsing. This moves the per-constant content-hash from env parse + /// time to first materialization, so constants that are shipped in a + /// closure but never forced by the typechecker are never hashed — + /// while everything the kernel CERTIFIES is still verified (checking + /// a constant forces its materialization). `None` for the + /// compile-side path, which owns the parsed value already. + pending_addr: Option
, } impl LazyConstant { @@ -114,7 +123,22 @@ impl LazyConstant { /// `Constant::put` produced for the address this entry is stored /// under. Use [`Self::verify_address`] for an explicit check. pub fn from_bytes(bytes: Arc<[u8]>) -> Self { - LazyConstant { bytes: BytesSource::Heap(bytes), cache: None } + LazyConstant { + bytes: BytesSource::Heap(bytes), + cache: None, + pending_addr: None, + } + } + + /// Like [`Self::from_bytes`], but with the address-binding check + /// deferred to first [`Self::get`]. Used by `Env::get_anon` so env + /// parse time does not pay one content hash per constant. + pub fn from_bytes_deferred(bytes: Arc<[u8]>, addr: Address) -> Self { + LazyConstant { + bytes: BytesSource::Heap(bytes), + cache: None, + pending_addr: Some(addr), + } } /// Construct from a memory-mapped window. The `Arc` keeps @@ -124,7 +148,11 @@ impl LazyConstant { /// Used by `Env::get_anon_mmap` to avoid heap-copying the on-disk /// byte stream — the OS page cache is the source of truth. pub fn from_mmap_slice(mmap: Arc, offset: usize, len: usize) -> Self { - LazyConstant { bytes: BytesSource::Mmap { mmap, offset, len }, cache: None } + LazyConstant { + bytes: BytesSource::Mmap { mmap, offset, len }, + cache: None, + pending_addr: None, + } } /// Construct from a structured `Constant` (the in-memory build path, @@ -137,6 +165,7 @@ impl LazyConstant { LazyConstant { bytes: BytesSource::Heap(buf.into()), cache: Some(Arc::new(c)), + pending_addr: None, } } @@ -151,6 +180,16 @@ impl LazyConstant { if let Some(c) = &self.cache { return Ok(c.clone()); } + if let Some(expected) = &self.pending_addr { + let computed = Address::hash(self.bytes.as_slice()); + if computed != *expected { + return Err(format!( + "LazyConstant::get: bytes hash to {} but stored under {}", + computed.hex(), + expected.hex() + )); + } + } let mut slice: &[u8] = self.bytes.as_slice(); let parsed = Constant::get(&mut slice) .map_err(|e| format!("LazyConstant::get: {e}"))?; @@ -246,9 +285,9 @@ impl Eq for LazyConstant {} #[cfg(test)] mod tests { use super::*; - use crate::ix::env::DefinitionSafety; - use crate::ix::ixon::constant::{Axiom, ConstantInfo, DefKind, Definition}; - use crate::ix::ixon::expr::Expr; + use crate::constant::{Axiom, ConstantInfo, DefKind, Definition}; + use crate::expr::Expr; + use ix_common::env::DefinitionSafety; fn axiom_constant() -> Constant { Constant::new(ConstantInfo::Axio(Axiom { @@ -391,7 +430,7 @@ mod tests { /// roundtrip it through `Constant::put`/`Constant::get` for the /// `peek_variant` Muts test. fn muts_constant() -> Constant { - use crate::ix::ixon::constant::{ConstantInfo, MutConst}; + use crate::constant::{ConstantInfo, MutConst}; let m1 = MutConst::Defn(Definition { kind: DefKind::Definition, safety: DefinitionSafety::Safe, @@ -431,8 +470,8 @@ mod tests { #[test] fn peek_variant_quot() { - use crate::ix::env::QuotKind; - use crate::ix::ixon::constant::{ConstantInfo, Quotient}; + use crate::constant::{ConstantInfo, Quotient}; + use ix_common::env::QuotKind; let q = Constant::new(ConstantInfo::Quot(Quotient { kind: QuotKind::Type, lvls: 1, @@ -443,7 +482,7 @@ mod tests { #[test] fn peek_variant_dprj() { - use crate::ix::ixon::constant::{ConstantInfo, DefinitionProj}; + use crate::constant::{ConstantInfo, DefinitionProj}; let p = Constant::new(ConstantInfo::DPrj(DefinitionProj { idx: 0, block: Address::hash(b"some-block"), @@ -453,7 +492,7 @@ mod tests { #[test] fn peek_variant_iprj() { - use crate::ix::ixon::constant::{ConstantInfo, InductiveProj}; + use crate::constant::{ConstantInfo, InductiveProj}; let p = Constant::new(ConstantInfo::IPrj(InductiveProj { idx: 0, block: Address::hash(b"some-block"), @@ -463,7 +502,7 @@ mod tests { #[test] fn peek_variant_rprj() { - use crate::ix::ixon::constant::{ConstantInfo, RecursorProj}; + use crate::constant::{ConstantInfo, RecursorProj}; let p = Constant::new(ConstantInfo::RPrj(RecursorProj { idx: 0, block: Address::hash(b"some-block"), @@ -473,7 +512,7 @@ mod tests { #[test] fn peek_variant_cprj() { - use crate::ix::ixon::constant::{ConstantInfo, ConstructorProj}; + use crate::constant::{ConstantInfo, ConstructorProj}; let p = Constant::new(ConstantInfo::CPrj(ConstructorProj { idx: 0, cidx: 0, diff --git a/src/ix/ixon.rs b/crates/ixon/src/lib.rs similarity index 98% rename from src/ix/ixon.rs rename to crates/ixon/src/lib.rs index 5c540514..84a13add 100644 --- a/src/ix/ixon.rs +++ b/crates/ixon/src/lib.rs @@ -13,6 +13,7 @@ pub mod env; pub mod error; pub mod expr; pub mod lazy; +pub mod map; pub mod merkle; pub mod metadata; pub mod proof; @@ -86,7 +87,7 @@ pub mod tests { #[cfg(test)] mod doc_examples { use super::*; - use crate::ix::address::Address; + use ix_common::address::Address; // ========================================================================= // Tag4 examples (docs section "Tag4 (4-bit flag)") @@ -373,8 +374,8 @@ mod doc_examples { fn definition_packed_kind_safety() { // DefKind::Definition = 0, DefinitionSafety::Safe = 1 // Packed: (0 << 2) | 1 = 0x01 - use crate::ix::env::DefinitionSafety; use constant::{DefKind, Definition}; + use ix_common::env::DefinitionSafety; let def = Definition { kind: DefKind::Definition, @@ -392,8 +393,8 @@ mod doc_examples { fn definition_opaque_unsafe() { // DefKind::Opaque = 1, DefinitionSafety::Unsafe = 0 // Packed: (1 << 2) | 0 = 0x04 - use crate::ix::env::DefinitionSafety; use constant::{DefKind, Definition}; + use ix_common::env::DefinitionSafety; let def = Definition { kind: DefKind::Opaque, @@ -411,8 +412,8 @@ mod doc_examples { fn definition_theorem_partial() { // DefKind::Theorem = 2, DefinitionSafety::Partial = 2 // Packed: (2 << 2) | 2 = 0x0A - use crate::ix::env::DefinitionSafety; use constant::{DefKind, Definition}; + use ix_common::env::DefinitionSafety; let def = Definition { kind: DefKind::Theorem, @@ -433,8 +434,8 @@ mod doc_examples { #[test] fn constant_defn_tag() { // Constant with Defn -> Tag4 { flag: 0xD, size: 0 } -> 0xD0 - use crate::ix::env::DefinitionSafety; use constant::{Constant, ConstantInfo, DefKind, Definition}; + use ix_common::env::DefinitionSafety; let constant = Constant::new(ConstantInfo::Defn(Definition { kind: DefKind::Definition, @@ -451,8 +452,8 @@ mod doc_examples { #[test] fn constant_muts_tag() { // Muts with 3 entries -> Tag4 { flag: 0xC, size: 3 } -> 0xC3 - use crate::ix::env::DefinitionSafety; use constant::{Constant, ConstantInfo, DefKind, Definition, MutConst}; + use ix_common::env::DefinitionSafety; let def = Definition { kind: DefKind::Definition, diff --git a/crates/ixon/src/map.rs b/crates/ixon/src/map.rs new file mode 100644 index 00000000..748ebafb --- /dev/null +++ b/crates/ixon/src/map.rs @@ -0,0 +1,101 @@ +//! Map type used by `Env`. +//! +//! On host, this is `dashmap::DashMap` — concurrent reads/writes for the +//! parallel ingress path. On `riscv64`, `DashMap` pulls in `parking_lot_core`, +//! whose `HashTable::new` calls `Instant::now()` (unsupported in the Zisk +//! guest's stdlib). Instead, alias to a thin single-threaded wrapper around +//! `rustc_hash::FxHashMap` that mirrors the subset of DashMap's API used here. + +#[cfg(not(target_arch = "riscv64"))] +pub use dashmap::DashMap as IxonMap; + +#[cfg(target_arch = "riscv64")] +pub use riscv_impl::IxonMap; + +#[cfg(target_arch = "riscv64")] +mod riscv_impl { + use std::collections::hash_map; + use std::hash::Hash; + use std::ops::Deref; + + use rustc_hash::FxHashMap; + + #[derive(Debug)] + pub struct IxonMap(FxHashMap); + + impl Default for IxonMap { + fn default() -> Self { + Self(FxHashMap::default()) + } + } + + impl IxonMap { + pub fn new() -> Self { + Self::default() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn contains_key(&self, k: &K) -> bool { + self.0.contains_key(k) + } + + pub fn insert(&mut self, k: K, v: V) -> Option { + self.0.insert(k, v) + } + + pub fn clear(&mut self) { + self.0.clear(); + } + + pub fn get(&self, k: &K) -> Option> { + self.0.get_key_value(k).map(|(k, v)| MapRef { k, v }) + } + + pub fn get_mut(&mut self, k: &K) -> Option<&mut V> { + self.0.get_mut(k) + } + + pub fn iter(&self) -> Iter<'_, K, V> { + Iter(self.0.iter()) + } + } + + /// Read guard exposing DashMap's `key()` / `value()` shape. + pub struct MapRef<'a, K, V> { + k: &'a K, + v: &'a V, + } + + impl<'a, K, V> MapRef<'a, K, V> { + pub fn key(&self) -> &K { + self.k + } + + pub fn value(&self) -> &V { + self.v + } + } + + impl<'a, K, V> Deref for MapRef<'a, K, V> { + type Target = V; + fn deref(&self) -> &V { + self.v + } + } + + pub struct Iter<'a, K, V>(hash_map::Iter<'a, K, V>); + + impl<'a, K, V> Iterator for Iter<'a, K, V> { + type Item = MapRef<'a, K, V>; + fn next(&mut self) -> Option { + self.0.next().map(|(k, v)| MapRef { k, v }) + } + } +} diff --git a/src/ix/ixon/merkle.rs b/crates/ixon/src/merkle.rs similarity index 99% rename from src/ix/ixon/merkle.rs rename to crates/ixon/src/merkle.rs index 081f89e9..78505f29 100644 --- a/src/ix/ixon/merkle.rs +++ b/crates/ixon/src/merkle.rs @@ -30,7 +30,7 @@ //! introduces CVE-2012-2459-style malleability where two distinct leaf //! lists can produce the same root. -use crate::ix::address::Address; +use ix_common::address::Address; /// Domain-separation prefix for leaf hashes. pub const LEAF_DOMAIN: u8 = 0x00; @@ -197,6 +197,7 @@ pub fn verify_merkle_proof( mod tests { use super::*; use quickcheck::{Arbitrary, Gen}; + use quickcheck_macros::quickcheck; fn addr(seed: &[u8]) -> Address { Address::hash(seed) diff --git a/src/ix/ixon/metadata.rs b/crates/ixon/src/metadata.rs similarity index 96% rename from src/ix/ixon/metadata.rs rename to crates/ixon/src/metadata.rs index e6f48db1..c8f16d2c 100644 --- a/src/ix/ixon/metadata.rs +++ b/crates/ixon/src/metadata.rs @@ -12,8 +12,8 @@ use std::collections::HashMap; use std::sync::Arc; -use crate::ix::address::Address; -use crate::ix::env::{self, BinderInfo, Name, ReducibilityHints}; +use ix_common::address::Address; +use ix_common::env::{self, BinderInfo, Name, ReducibilityHints}; use super::env::AuxLayout; use super::expr::Expr; @@ -330,7 +330,7 @@ pub fn resolve_kvmap( }, DataValue::OfNat(a) => { let bytes = ixon_env.get_blob(a)?; - env::DataValue::OfNat(lean_ffi::nat::Nat::from_le_bytes(&bytes)) + env::DataValue::OfNat(bignat::Nat::from_le_bytes(&bytes)) }, DataValue::OfInt(a) => { let bytes = ixon_env.get_blob(a)?; @@ -389,8 +389,8 @@ fn deser_addr(buf: &mut &[u8]) -> Option
{ fn deser_int(bytes: &[u8]) -> Option { let (&tag, rest) = bytes.split_first()?; match tag { - 0 => Some(env::Int::OfNat(lean_ffi::nat::Nat::from_le_bytes(rest))), - 1 => Some(env::Int::NegSucc(lean_ffi::nat::Nat::from_le_bytes(rest))), + 0 => Some(env::Int::OfNat(bignat::Nat::from_le_bytes(rest))), + 1 => Some(env::Int::NegSucc(bignat::Nat::from_le_bytes(rest))), _ => None, } } @@ -401,8 +401,8 @@ fn deser_substring( ) -> Option { let str_addr = deser_addr(buf)?; let s = String::from_utf8(ixon_env.get_blob(&str_addr)?).ok()?; - let start_pos = lean_ffi::nat::Nat::from(deser_tag0(buf)?); - let stop_pos = lean_ffi::nat::Nat::from(deser_tag0(buf)?); + let start_pos = bignat::Nat::from(deser_tag0(buf)?); + let stop_pos = bignat::Nat::from(deser_tag0(buf)?); Some(env::Substring { str: s, start_pos, stop_pos }) } @@ -413,9 +413,9 @@ fn deser_source_info( match deser_u8(buf)? { 0 => { let leading = deser_substring(buf, ixon_env)?; - let leading_pos = lean_ffi::nat::Nat::from(deser_tag0(buf)?); + let leading_pos = bignat::Nat::from(deser_tag0(buf)?); let trailing = deser_substring(buf, ixon_env)?; - let trailing_pos = lean_ffi::nat::Nat::from(deser_tag0(buf)?); + let trailing_pos = bignat::Nat::from(deser_tag0(buf)?); Some(env::SourceInfo::Original( leading, leading_pos, @@ -424,8 +424,8 @@ fn deser_source_info( )) }, 1 => { - let start = lean_ffi::nat::Nat::from(deser_tag0(buf)?); - let end = lean_ffi::nat::Nat::from(deser_tag0(buf)?); + let start = bignat::Nat::from(deser_tag0(buf)?); + let end = bignat::Nat::from(deser_tag0(buf)?); let canonical = deser_u8(buf)? != 0; Some(env::SourceInfo::Synthetic(start, end, canonical)) }, @@ -560,8 +560,15 @@ pub(super) fn get_vec_len(buf: &mut &[u8]) -> Result { // BinderInfo and ReducibilityHints serialization // =========================================================================== -impl BinderInfo { - pub fn put(&self, buf: &mut Vec) { +/// Extension trait for serializing/deserializing small env-side enums whose +/// types live in `ix-types` (so we can't define inherent impls here). +pub trait IxonByteSerde: Sized { + fn put_ser(&self, buf: &mut Vec); + fn get_ser(buf: &mut &[u8]) -> Result; +} + +impl IxonByteSerde for BinderInfo { + fn put_ser(&self, buf: &mut Vec) { match self { Self::Default => put_u8(0, buf), Self::Implicit => put_u8(1, buf), @@ -570,7 +577,7 @@ impl BinderInfo { } } - pub fn get_ser(buf: &mut &[u8]) -> Result { + fn get_ser(buf: &mut &[u8]) -> Result { match get_u8(buf)? { 0 => Ok(Self::Default), 1 => Ok(Self::Implicit), @@ -581,8 +588,8 @@ impl BinderInfo { } } -impl ReducibilityHints { - pub fn put(&self, buf: &mut Vec) { +impl IxonByteSerde for ReducibilityHints { + fn put_ser(&self, buf: &mut Vec) { match self { Self::Opaque => put_u8(0, buf), Self::Abbrev => put_u8(1, buf), @@ -593,7 +600,7 @@ impl ReducibilityHints { } } - pub fn get_ser(buf: &mut &[u8]) -> Result { + fn get_ser(buf: &mut &[u8]) -> Result { match get_u8(buf)? { 0 => Ok(Self::Opaque), 1 => Ok(Self::Abbrev), @@ -1004,7 +1011,7 @@ impl ConstantMetaInfo { put_u8(0, buf); put_idx(name, idx, buf)?; put_idx_vec(lvls, idx, buf)?; - hints.put(buf); + hints.put_ser(buf); put_idx_vec(all, idx, buf)?; put_idx_vec(ctx, idx, buf)?; arena.put_indexed(idx, buf)?; @@ -1182,28 +1189,6 @@ impl ConstantMetaInfo { #[cfg(test)] mod tests { use super::*; - use quickcheck::{Arbitrary, Gen}; - - 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)), - } - } - } #[test] fn test_binder_info_roundtrip() { @@ -1214,7 +1199,7 @@ mod tests { BinderInfo::InstImplicit, ] { let mut buf = Vec::new(); - bi.put(&mut buf); + bi.put_ser(&mut buf); assert_eq!(BinderInfo::get_ser(&mut buf.as_slice()).unwrap(), bi); } } @@ -1227,7 +1212,7 @@ mod tests { ReducibilityHints::Regular(42), ] { let mut buf = Vec::new(); - h.put(&mut buf); + h.put_ser(&mut buf); assert_eq!(ReducibilityHints::get_ser(&mut buf.as_slice()).unwrap(), h); } } diff --git a/src/ix/ixon/proof.rs b/crates/ixon/src/proof.rs similarity index 99% rename from src/ix/ixon/proof.rs rename to crates/ixon/src/proof.rs index 805f0017..94fc2b6e 100644 --- a/src/ix/ixon/proof.rs +++ b/crates/ixon/src/proof.rs @@ -8,8 +8,8 @@ //! order. This enables revealing specific fields of a committed constant without //! exposing the full structure. -use crate::ix::address::Address; -use crate::ix::env::{DefinitionSafety, QuotKind}; +use ix_common::address::Address; +use ix_common::env::{DefinitionSafety, QuotKind}; use super::constant::DefKind; use super::tag::{Tag0, Tag4}; @@ -144,7 +144,7 @@ pub enum RevealConstantInfo { /// Orthogonal to typechecking; carries no assumptions. /// - **Contains**: structural membership claim — `const_addr` is a leaf /// in the merkle tree rooted at `tree`. Used by the aggregation -/// circuit to discharge a leaf from a conditional claim's assumption +/// circuit to resolve a leaf from a conditional claim's assumption /// set. Carries no assumptions itself. /// /// The `assumptions` root may be any merkle tree (canonical sorted+ @@ -1176,6 +1176,7 @@ impl Proof { mod tests { use super::*; use quickcheck::{Arbitrary, Gen}; + use quickcheck_macros::quickcheck; // ========== Arbitrary impls ========== diff --git a/src/ix/ixon/serialize.rs b/crates/ixon/src/serialize.rs similarity index 91% rename from src/ix/ixon/serialize.rs rename to crates/ixon/src/serialize.rs index ecbed0bf..cdbee2a9 100644 --- a/src/ix/ixon/serialize.rs +++ b/crates/ixon/src/serialize.rs @@ -9,8 +9,8 @@ use std::sync::Arc; -use crate::ix::address::Address; -use crate::ix::env::{DefinitionSafety, QuotKind}; +use ix_common::address::Address; +use ix_common::env::{DefinitionSafety, QuotKind, ReducibilityHints}; use super::constant::{ Axiom, Constant, ConstantInfo, Constructor, ConstructorProj, DefKind, @@ -18,6 +18,7 @@ use super::constant::{ Recursor, RecursorProj, RecursorRule, }; use super::expr::Expr; +use super::metadata::IxonByteSerde; use super::tag::{Tag0, Tag4}; use super::univ::{Univ, get_univ, put_univ}; @@ -75,6 +76,27 @@ fn put_address(a: &Address, buf: &mut Vec) { put_bytes(a.as_bytes(), buf); } +/// Read the optional trailing `anon_hints` section written by `Env::put`. +/// No-op when no bytes remain after the comms section — backward-compatible +/// with `.ixe` that have no hints or predate the section. Inserts into +/// `env.anon_hints` (overwriting any value harvested from a Named section, +/// with the identical value, so the order of harvest vs. section is moot). +fn read_anon_hints_section( + buf: &mut &[u8], + env: &mut Env, +) -> Result<(), String> { + if buf.is_empty() { + return Ok(()); + } + let n = get_u64(buf)?; + for _ in 0..n { + let addr = get_address(buf)?; + let hints = ReducibilityHints::get_ser(buf)?; + env.anon_hints.insert(addr, hints); + } + Ok(()) +} + fn get_address(buf: &mut &[u8]) -> Result { if buf.len() < 32 { return Err(format!("get_address: need 32 bytes, have {}", buf.len())); @@ -425,28 +447,26 @@ impl DefKind { } } -impl DefinitionSafety { - fn to_u8(self) -> u8 { - match self { - Self::Unsafe => 0, - Self::Safe => 1, - Self::Partial => 2, - } +fn definition_safety_to_u8(s: DefinitionSafety) -> u8 { + match s { + DefinitionSafety::Unsafe => 0, + DefinitionSafety::Safe => 1, + DefinitionSafety::Partial => 2, } +} - fn from_u8(x: u8) -> Result { - match x { - 0 => Ok(Self::Unsafe), - 1 => Ok(Self::Safe), - 2 => Ok(Self::Partial), - x => Err(format!("DefinitionSafety::from_u8: invalid {x}")), - } +fn definition_safety_from_u8(x: u8) -> Result { + match x { + 0 => Ok(DefinitionSafety::Unsafe), + 1 => Ok(DefinitionSafety::Safe), + 2 => Ok(DefinitionSafety::Partial), + x => Err(format!("DefinitionSafety::from_u8: invalid {x}")), } } /// Pack DefKind (2 bits) and DefinitionSafety (2 bits) into a single byte. fn pack_def_kind_safety(kind: DefKind, safety: DefinitionSafety) -> u8 { - (kind.to_u8() << 2) | safety.to_u8() + (kind.to_u8() << 2) | definition_safety_to_u8(safety) } /// Unpack DefKind and DefinitionSafety from a single byte. @@ -454,12 +474,12 @@ fn unpack_def_kind_safety( b: u8, ) -> Result<(DefKind, DefinitionSafety), String> { let kind = DefKind::from_u8(b >> 2)?; - let safety = DefinitionSafety::from_u8(b & 0x3)?; + let safety = definition_safety_from_u8(b & 0x3)?; Ok((kind, safety)) } -impl QuotKind { - pub fn put_ser(&self, buf: &mut Vec) { +impl IxonByteSerde for QuotKind { + fn put_ser(&self, buf: &mut Vec) { match self { Self::Type => put_u8(0, buf), Self::Ctor => put_u8(1, buf), @@ -468,7 +488,7 @@ impl QuotKind { } } - pub fn get_ser(buf: &mut &[u8]) -> Result { + fn get_ser(buf: &mut &[u8]) -> Result { match get_u8(buf)? { 0 => Ok(Self::Type), 1 => Ok(Self::Ctor), @@ -870,8 +890,8 @@ impl Constant { // Name serialization // ============================================================================ -use crate::ix::env::{Name, NameData}; -use lean_ffi::nat::Nat; +use bignat::Nat; +use ix_common::env::{Name, NameData}; use rustc_hash::FxHashMap; /// Serialize a Name to bytes (full recursive serialization, for standalone use). @@ -1109,6 +1129,7 @@ impl Env { /// insertion orders), and DashMap iteration order is shard-dependent. /// Sorting the keys is the minimum work to guarantee that. pub fn put(&self, buf: &mut Vec) -> Result<(), String> { + #[cfg(not(target_arch = "riscv64"))] use rayon::slice::ParallelSliceMut; // Chatty per-section logging gated on IX_QUIET=1 (disables) so we can @@ -1131,7 +1152,10 @@ impl Env { // ───────────────────────────────────────────────────────────────────── let mut const_addrs: Vec
= self.consts.iter().map(|e| e.key().clone()).collect(); + #[cfg(not(target_arch = "riscv64"))] const_addrs.par_sort_unstable(); + #[cfg(target_arch = "riscv64")] + const_addrs.sort_unstable(); let root = merkle_root_canonical(&const_addrs).unwrap_or_else(zero_address); put_address(&root, buf); @@ -1144,7 +1168,10 @@ impl Env { } let mut blob_addrs: Vec
= self.blobs.iter().map(|e| e.key().clone()).collect(); + #[cfg(not(target_arch = "riscv64"))] blob_addrs.par_sort_unstable(); + #[cfg(target_arch = "riscv64")] + blob_addrs.sort_unstable(); put_u64(blob_addrs.len() as u64, buf); for addr in &blob_addrs { if let Some(entry) = self.blobs.get(addr) { @@ -1260,9 +1287,14 @@ impl Env { // Sort by the cached name hash bytes (same key used by every existing // Section 4 ordering guarantee). `par_sort_unstable_by` uses rayon to // parallelize the compare across all cores. + #[cfg(not(target_arch = "riscv64"))] named_keys.par_sort_unstable_by(|a, b| { a.get_hash().as_bytes().cmp(b.get_hash().as_bytes()) }); + #[cfg(target_arch = "riscv64")] + named_keys.sort_unstable_by(|a, b| { + a.get_hash().as_bytes().cmp(b.get_hash().as_bytes()) + }); if !quiet { eprintln!( "[Env::put] section 4/5 named: collected+sorted in {:.1}s, \ @@ -1297,7 +1329,10 @@ impl Env { } let mut comm_addrs: Vec
= self.comms.iter().map(|e| e.key().clone()).collect(); + #[cfg(not(target_arch = "riscv64"))] comm_addrs.par_sort_unstable(); + #[cfg(target_arch = "riscv64")] + comm_addrs.sort_unstable(); put_u64(comm_addrs.len() as u64, buf); for addr in &comm_addrs { if let Some(entry) = self.comms.get(addr) { @@ -1311,6 +1346,30 @@ impl Env { sec_start.elapsed().as_secs_f64(), buf.len(), ); + } + + // Optional trailing section: anon_hints (Address -> ReducibilityHints). + // `get_anon` normally HARVESTS hints from the Named section; a closure + // sub-env (built for shard injection) DROPS that section, which would + // force the kernel to `Regular(0)` and add a def-eq overhead vs whole-env + // proving. Carrying the hints here lets the guest reproduce vanilla kernel + // behavior exactly. Written only when populated, so compiler-produced + // `.ixe` (empty map) are byte-identical to before; loaders read it only if + // bytes remain after comms. Hints are performance-only (the `Regular(0)` + // fallback is always correct), so this section is intentionally NOT covered + // by the consts merkle root. + if !self.anon_hints.is_empty() { + let mut hint_addrs: Vec
= + self.anon_hints.keys().cloned().collect(); + hint_addrs.sort_unstable(); + put_u64(hint_addrs.len() as u64, buf); + for addr in &hint_addrs { + put_address(addr, buf); + self.anon_hints[addr].put_ser(buf); + } + } + + if !quiet { eprintln!( "[Env::put] ALL DONE: {} bytes in {:.1}s", buf.len(), @@ -1343,7 +1402,8 @@ impl Env { // recomputed value at the end of deserialization. let stored_root = get_address(buf)?; - let env = Env::new(); + #[cfg_attr(not(target_arch = "riscv64"), allow(unused_mut))] + let mut env = Env::new(); // Section 1: Blobs let num_blobs = get_u64(buf)?; @@ -1359,6 +1419,20 @@ impl Env { } let (bytes, rest) = buf.split_at(len); *buf = rest; + // Blob bytes are bound to their address HERE, eagerly: literal + // identity downstream (kernel `ExprKey::Nat`/`Str` interning, + // `Constant.refs` blob entries, assumption filtering) keys on the + // address, so an unverified blob would let an adversarial env bind + // wrong bytes to a content address. Blobs are a small fraction of + // env bytes; constants stay lazily verified (see LazyConstant). + let computed = Address::hash(bytes); + if computed != addr { + return Err(format!( + "Env::get: blob bytes hash to {} but stored under {}", + computed.hex(), + addr.hex() + )); + } env.blobs.insert(addr, bytes.to_vec()); } @@ -1390,7 +1464,9 @@ impl Env { addr.hex() )); } - env.store_const_lazy(addr, bytes.into()); + env + .consts + .insert(addr, crate::lazy::LazyConstant::from_bytes(bytes.into())); } // Section 3: Names (build lookup table and reverse index for metadata) @@ -1431,6 +1507,9 @@ impl Env { env.comms.insert(addr, comm); } + // Optional trailing anon_hints section (see `Env::put`). + read_anon_hints_section(buf, &mut env)?; + // Verify the stored merkle root matches what we'd compute from // env.consts. Empty const set → expected = zero_address(). // Rejects any tampering with the header. @@ -1503,6 +1582,20 @@ impl Env { } let (bytes, rest) = buf.split_at(len); *buf = rest; + // Blob bytes are bound to their address HERE, eagerly: literal + // identity downstream (kernel `ExprKey::Nat`/`Str` interning, + // `Constant.refs` blob entries, assumption filtering) keys on the + // address, so an unverified blob would let an adversarial env bind + // wrong bytes to a content address. Blobs are a small fraction of + // env bytes; constants stay lazily verified (see LazyConstant). + let computed = Address::hash(bytes); + if computed != addr { + return Err(format!( + "Env::get_anon: blob bytes hash to {} but stored under {}", + computed.hex(), + addr.hex() + )); + } env.blobs.insert(addr, bytes.to_vec()); } @@ -1520,15 +1613,16 @@ impl Env { } let (bytes, rest) = buf.split_at(len); *buf = rest; - let computed = Address::hash(bytes); - if computed != addr { - return Err(format!( - "Env::get_anon: const at idx {i} bytes hash to {} but stored under {}", - computed.hex(), - addr.hex() - )); - } - env.store_const_lazy(addr, bytes.into()); + let _ = i; + // Address binding is verified lazily at first materialization + // (`LazyConstant::get`) instead of one content hash per constant + // here — constants shipped in a closure but never forced by the + // typechecker are never hashed, while everything the kernel + // certifies still gets verified on the way in. + env.consts.insert( + addr.clone(), + crate::lazy::LazyConstant::from_bytes_deferred(bytes.into(), addr), + ); } // Section 3: Names — parse and DISCARD. We still need a populated @@ -1573,6 +1667,11 @@ impl Env { let _comm = Comm::get(buf)?; } + // Optional trailing anon_hints section (see `Env::put`). For a closure + // sub-env this carries the hints the dropped Named section would have, so + // the kernel reproduces vanilla behavior with no def-eq overhead. + read_anon_hints_section(buf, &mut env)?; + drop(names_lookup); drop(name_reverse_index); @@ -1613,6 +1712,7 @@ impl Env { /// On Linux, the kernel's adaptive readahead handles the linear /// scan during section parsing efficiently; subsequent random /// access from worker kernel-check threads pages in as needed. + #[cfg(not(target_arch = "riscv64"))] pub fn get_anon_mmap(path: &std::path::Path) -> Result { let file = std::fs::File::open(path).map_err(|e| { format!("Env::get_anon_mmap: open {}: {e}", path.display()) @@ -1696,6 +1796,20 @@ impl Env { } let (bytes, rest) = buf.split_at(len); buf = rest; + // Blob bytes are bound to their address HERE, eagerly: literal + // identity downstream (kernel `ExprKey::Nat`/`Str` interning, + // `Constant.refs` blob entries, assumption filtering) keys on the + // address, so an unverified blob would let an adversarial env bind + // wrong bytes to a content address. Blobs are a small fraction of + // env bytes; constants stay lazily verified (see LazyConstant). + let computed = Address::hash(bytes); + if computed != addr { + return Err(format!( + "Env::get_anon_mmap: blob bytes hash to {} but stored under {}", + computed.hex(), + addr.hex() + )); + } env.blobs.insert(addr, bytes.to_vec()); } @@ -1723,7 +1837,14 @@ impl Env { addr.hex() )); } - env.store_const_lazy_mmap(addr, Arc::clone(&mmap), offset, len); + env.consts.insert( + addr, + crate::lazy::LazyConstant::from_mmap_slice( + Arc::clone(&mmap), + offset, + len, + ), + ); buf = &buf[len..]; } @@ -1763,6 +1884,9 @@ impl Env { let _comm = Comm::get(&mut buf)?; } + // Optional trailing anon_hints section (see `Env::put`). + read_anon_hints_section(&mut buf, &mut env)?; + drop(names_lookup); drop(name_reverse_index); @@ -1882,8 +2006,9 @@ impl Env { /// Mathlib because 4.7M shard-lock acquisitions dominate vs the one-time /// ~150 MB tuple-clone allocation. fn topological_sort_names( - names: &dashmap::DashMap, + names: &crate::map::IxonMap, ) -> Vec<(Address, Name)> { + #[cfg(not(target_arch = "riscv64"))] use rayon::slice::ParallelSliceMut; use rustc_hash::FxHashSet; @@ -1922,7 +2047,10 @@ fn topological_sort_names( // during DFS). Parallel sort uses rayon over address bytes. let mut sorted_entries: Vec<(Address, Name)> = names.iter().map(|e| (e.key().clone(), e.value().clone())).collect(); + #[cfg(not(target_arch = "riscv64"))] sorted_entries.par_sort_unstable_by(|a, b| a.0.cmp(&b.0)); + #[cfg(target_arch = "riscv64")] + sorted_entries.sort_unstable_by(|a, b| a.0.cmp(&b.0)); for (_, name) in &sorted_entries { visit(name, &mut visited, &mut result); } @@ -1933,9 +2061,10 @@ fn topological_sort_names( #[cfg(test)] mod tests { use super::*; - use crate::ix::ixon::constant::tests::gen_constant; - use crate::ix::ixon::tests::gen_range; + use crate::constant::tests::gen_constant; + use crate::tests::gen_range; use quickcheck::{Arbitrary, Gen}; + use quickcheck_macros::quickcheck; #[quickcheck] fn prop_pack_bools_roundtrip(x: Vec) -> bool { @@ -2215,8 +2344,8 @@ mod tests { } fn defn_const_discriminator(refs: Vec
, lvls: u64) -> Constant { - use crate::ix::env::DefinitionSafety; - use crate::ix::ixon::constant::{DefKind, Definition}; + use crate::constant::{DefKind, Definition}; + use ix_common::env::DefinitionSafety; Constant::with_tables( ConstantInfo::Defn(Definition { kind: DefKind::Definition, @@ -2251,7 +2380,7 @@ mod tests { #[test] fn env_root_empty_env_is_zero_address() { - use crate::ix::ixon::merkle::zero_address; + use crate::merkle::zero_address; let env = Env::new(); let mut buf = Vec::new(); env.put(&mut buf).unwrap(); @@ -2265,7 +2394,7 @@ mod tests { #[test] fn env_root_present_when_consts_nonempty() { - use crate::ix::ixon::merkle::zero_address; + use crate::merkle::zero_address; let env = Env::new(); env.store_const(Address::hash(b"a"), defn_const(vec![])); let mut buf = Vec::new(); @@ -2364,8 +2493,16 @@ mod tests { let off = 68 + 3; assert!(off < buf.len()); buf[off] ^= 0xFF; - let res = Env::get_anon(&mut buf.as_slice()); - let err = res.expect_err("tampered const bytes should be rejected"); + // Address binding is verified lazily: parse succeeds, but the + // tampered constant is rejected at first materialization (which is + // what gates anything the kernel certifies). + let parsed = Env::get_anon(&mut buf.as_slice()) + .expect("get_anon defers per-const verification"); + let entry = parsed.consts.iter().next().expect("one const"); + let err = entry + .value() + .get() + .expect_err("tampered const bytes should be rejected at get()"); assert!( err.contains("bytes hash to") && err.contains("but stored under"), "expected per-entry verify error, got: {err}" @@ -2403,7 +2540,7 @@ mod tests { #[test] fn get_anon_keeps_consts_drops_metadata() { - use crate::ix::ixon::env::Named; + use crate::env::Named; let env = Env::new(); // Round-trip tests must use canonical addresses (see store_canonical // helper); `Env::get`/`get_anon` reject entries whose bytes don't diff --git a/src/ix/ixon/sharing.rs b/crates/ixon/src/sharing.rs similarity index 99% rename from src/ix/ixon/sharing.rs rename to crates/ixon/src/sharing.rs index 610d07c2..ccf87dd2 100644 --- a/src/ix/ixon/sharing.rs +++ b/crates/ixon/src/sharing.rs @@ -1060,7 +1060,7 @@ mod tests { #[test] fn test_roundtrip_with_sharing() { - use crate::ix::ixon::serialize::{get_expr, put_expr}; + use crate::serialize::{get_expr, put_expr}; // Create a simple expression with potential sharing let var0 = Expr::var(0); diff --git a/src/ix/ixon/tag.rs b/crates/ixon/src/tag.rs similarity index 99% rename from src/ix/ixon/tag.rs rename to crates/ixon/src/tag.rs index 240d9688..35465aaa 100644 --- a/src/ix/ixon/tag.rs +++ b/crates/ixon/src/tag.rs @@ -225,6 +225,7 @@ impl Tag0 { mod tests { use super::*; use quickcheck::{Arbitrary, Gen}; + use quickcheck_macros::quickcheck; // ============================================================================ // Arbitrary implementations diff --git a/src/ix/ixon/univ.rs b/crates/ixon/src/univ.rs similarity index 98% rename from src/ix/ixon/univ.rs rename to crates/ixon/src/univ.rs index ce3e9db8..41282332 100644 --- a/src/ix/ixon/univ.rs +++ b/crates/ixon/src/univ.rs @@ -162,8 +162,9 @@ pub fn get_univ(buf: &mut &[u8]) -> Result, String> { #[cfg(test)] pub mod tests { use super::*; - use crate::ix::ixon::tests::{gen_range, next_case}; + use crate::tests::{gen_range, next_case}; use quickcheck::{Arbitrary, Gen}; + use quickcheck_macros::quickcheck; use std::ptr; #[derive(Clone, Copy)] diff --git a/crates/kernel/Cargo.toml b/crates/kernel/Cargo.toml new file mode 100644 index 00000000..ebb91120 --- /dev/null +++ b/crates/kernel/Cargo.toml @@ -0,0 +1,47 @@ +[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 } + +[target.'cfg(not(target_arch = "riscv64"))'.dependencies] +dashmap = { workspace = true, features = ["rayon"] } +rayon = { workspace = true } + +[dev-dependencies] +# `examples/perf_check.rs` initializes a logger so the `KEnv::Drop` perf +# summary (emitted via `log::info!` when `IX_PERF_COUNTERS=1`) actually +# reaches stderr. Dev-only — production builds never pull this in. +env_logger = "0.11" +quickcheck = { workspace = true } +quickcheck_macros = { workspace = true } + +# Native rerun of the SP1/Zisk guest `check_const` loop. The zkVM build +# disables `IX_*` env vars (`src/lib.rs::env_var_os`), so the perf-counter +# / fuel summary in `src/perf.rs` is only observable through this binary. +# See the example's module doc for the intended workflow. +[[example]] +name = "perf_check" +path = "examples/perf_check.rs" + +# Native single-constant check (one work item via the lazy-anon path the +# Zisk guest uses), so the `IX_*` instrumentation can target one expensive +# constant without re-checking its whole env. +[[example]] +name = "check_one" +path = "examples/check_one.rs" + +[lints] +workspace = true diff --git a/crates/kernel/examples/block_reduce_histo.rs b/crates/kernel/examples/block_reduce_histo.rs new file mode 100644 index 00000000..4be6c47f --- /dev/null +++ b/crates/kernel/examples/block_reduce_histo.rs @@ -0,0 +1,114 @@ +//! Check a single named constant's block and dump per-constant reduction +//! histograms: which constants the kernel delta-unfolds, which recursors +//! fire iota, and how many `Nat.succ` peels happen. Reverse-maps addresses +//! to Lean names via the env's `named` metadata. +//! +//! IX_REDUCE_HISTO=1 cargo run -p ix-kernel --release \ +//! --example block_reduce_histo -- [top_k] + +use std::sync::atomic::Ordering; + +use ix_common::address::Address; +use ix_kernel::anon_work::build_anon_work; +use ix_kernel::env::KEnv; +use ix_kernel::id::KId; +use ix_kernel::mode::Anon; +use ix_kernel::tc::TypeChecker; +use ixon::constant::ConstantInfo as CI; +use ixon::env::Env as IxonEnv; + +/// A constant's home block: a projection's `block`, else the constant itself. +fn block_of(env: &IxonEnv, addr: &Address) -> Address { + 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(), + } +} + +fn main() { + let args: Vec = std::env::args().collect(); + let (Some(path), Some(target)) = (args.get(1).cloned(), args.get(2).cloned()) + else { + eprintln!("usage: block_reduce_histo [top_k]"); + std::process::exit(1); + }; + let top_k: usize = args.get(3).and_then(|s| s.parse().ok()).unwrap_or(40); + + if std::env::var_os("IX_REDUCE_HISTO").is_none() { + eprintln!("warning: IX_REDUCE_HISTO is not set; histograms will be empty"); + } + + let bytes = std::fs::read(&path).expect("read .ixe"); + let full = IxonEnv::get(&mut &bytes[..]).expect("get"); + + // Resolve the Lean name to its anon addr, then its home block. + let Some(target_addr) = full + .named + .iter() + .find(|e| e.key().to_string() == target) + .map(|e| e.value().addr.clone()) + else { + eprintln!("name not found in env: {target}"); + std::process::exit(1); + }; + let target_block = block_of(&full, &target_addr); + eprintln!( + "target {target} addr={}… block={}…", + &target_addr.hex()[..16], + &target_block.hex()[..16] + ); + + // Find the anon work item for that block and check just that item. + let env = IxonEnv::get_anon(&mut &bytes[..]).expect("get_anon"); + let work = build_anon_work(&env).expect("build_anon_work"); + let item = work + .iter() + .find(|item| block_of(&env, item.primary()) == target_block) + .expect("no work item for target block"); + + let mut kenv = KEnv::::new(); + let start = std::time::Instant::now(); + let kid = KId::::new(item.primary().clone(), ()); + let mut tc = TypeChecker::::new_with_lazy_anon(&mut kenv, &env); + let result = tc.check_const(&kid); + let fuel = tc.fuel_used(); + eprintln!( + "check_const: {:?} in {:.1}s, fuel(heartbeats)={fuel}", + result.map(|_| "ok"), + start.elapsed().as_secs_f64() + ); + + // Reverse-map addr → Lean name (named addrs are anon addrs here). + let name_of = |addr: &Address| -> String { + full.named.iter().find(|e| e.value().addr == *addr).map_or_else( + || format!("", &addr.hex()[..16]), + |e| e.key().to_string(), + ) + }; + + let dump = |label: &str, histo: &dashmap::DashMap| { + let mut rows: Vec<(Address, u64)> = + histo.iter().map(|e| (e.key().clone(), *e.value())).collect(); + rows.sort_by(|a, b| b.1.cmp(&a.1)); + let total: u64 = rows.iter().map(|(_, n)| n).sum(); + eprintln!("\n== {label}: {total} total, {} distinct ==", rows.len()); + for (addr, n) in rows.iter().take(top_k) { + #[allow(clippy::cast_precision_loss)] + let pct = *n as f64 / total.max(1) as f64 * 100.0; + eprintln!(" {n:>12} ({pct:>5.1}%) {}", name_of(addr)); + } + }; + + dump("delta unfolds", &ix_kernel::perf::DELTA_HISTO); + dump("iota reductions", &ix_kernel::perf::IOTA_HISTO); + eprintln!( + "\nNat.succ peels: {}", + ix_kernel::perf::NAT_SUCC_PEELS.load(Ordering::Relaxed) + ); +} diff --git a/crates/kernel/examples/check_one.rs b/crates/kernel/examples/check_one.rs new file mode 100644 index 00000000..4ae4d946 --- /dev/null +++ b/crates/kernel/examples/check_one.rs @@ -0,0 +1,72 @@ +//! Native single-constant check, mirroring the Zisk guest's reuse-mode path +//! (`zisk/guest/src/main.rs`): resolve a Lean NAME through the env's `named` +//! metadata to its ingress block, then `check_const` exactly that work item +//! with the lazy-anon TypeChecker. Exists so the kernel's `IX_*` +//! instrumentation (inert inside zkVM guests — see `src/lib.rs::env_var_os`) +//! can be pointed at one expensive constant without re-checking a whole env. +//! +//! IX_PERF_COUNTERS=1 cargo run --release --example check_one -- \ +//! ~/ix/init.ixe '_private.Init.Data.Range.Polymorphic.SInt.0.Int16.instRxcHasSize_eq' + +use std::env; +use std::fs; +use std::time::Instant; + +use ix_kernel::anon_work::build_anon_work; +use ix_kernel::env::KEnv; +use ix_kernel::id::KId; +use ix_kernel::mode::Anon; +use ix_kernel::tc::TypeChecker; +use ixon::constant::ConstantInfo as CI; +use ixon::env::Env as IxonEnv; + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .format_timestamp(None) + .init(); + let path = env::args().nth(1).expect("usage: check_one "); + let name = env::args().nth(2).expect("usage: check_one "); + let bytes = fs::read(&path).expect("read ixe"); + + let t_parse = Instant::now(); + let env = IxonEnv::get(&mut &bytes[..]).expect("decode env"); + eprintln!("[check_one] parse: {:>8.2?}", t_parse.elapsed()); + + // Name → constant address → ingress block (mirrors zisk-host + // `block_of_addr`): a projection's `block`, else the address itself. + let addr = env + .named + .iter() + .find(|e| e.key().pretty() == name || e.key().to_string() == name) + .map_or_else( + || panic!("no constant named {name:?} in {path}"), + |e| e.value().addr.clone(), + ); + let block = match env.get_const(&addr).map(|c| c.info.clone()) { + Some(CI::IPrj(p)) => p.block, + Some(CI::CPrj(p)) => p.block, + Some(CI::RPrj(p)) => p.block, + Some(CI::DPrj(p)) => p.block, + _ => addr.clone(), + }; + + let work = build_anon_work(&env).expect("build_anon_work"); + let item = + work.iter().find(|it| *it.primary() == block).expect("work item for block"); + + let mut kenv = KEnv::::new(); + let mut tc = TypeChecker::::new_with_lazy_anon(&mut kenv, &env); + let kid = KId::::new(item.primary().clone(), ()); + let t_check = Instant::now(); + let result = tc.check_const(&kid); + eprintln!( + "[check_one] check: {:>8.2?} result: {:?}", + t_check.elapsed(), + result.is_ok() + ); + if let Err(e) = result { + eprintln!("[check_one] error: {e:?}"); + std::process::exit(1); + } +} diff --git a/crates/kernel/examples/heaviest_block.rs b/crates/kernel/examples/heaviest_block.rs new file mode 100644 index 00000000..378c3748 --- /dev/null +++ b/crates/kernel/examples/heaviest_block.rs @@ -0,0 +1,117 @@ +//! Identify, by Lean name, the heaviest blocks in a `.ixe` — in particular the +//! atomic mutual block that floors sharding. Profiles the env (heartbeats per +//! block), ranks blocks, then reverse-maps each block's address to the Lean +//! names of its members via the env's `names` metadata. +//! +//! cargo run -p ix-kernel --release --example heaviest_block -- [top_k] + +use ix_common::address::Address; +use ix_kernel::anon_work::build_anon_work; +use ix_kernel::env::KEnv; +use ix_kernel::id::KId; +use ix_kernel::mode::Anon; +use ix_kernel::profile::{ProfileBuilder, ProfileSink}; +use ix_kernel::tc::TypeChecker; +use ixon::constant::ConstantInfo as CI; +use ixon::env::Env as IxonEnv; + +/// A constant's home block: a projection's `block`, else the constant itself. +fn block_of(env: &IxonEnv, addr: &Address) -> Address { + 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(), + } +} + +fn main() { + let args: Vec = std::env::args().collect(); + let Some(path) = args.get(1).cloned() else { + eprintln!("usage: heaviest_block [top_k]"); + std::process::exit(1); + }; + let top_k: usize = args.get(2).and_then(|s| s.parse().ok()).unwrap_or(3); + + let bytes = std::fs::read(&path).expect("read .ixe"); + + // ---- Profile (anon) for per-block heartbeats. ---- + let env = IxonEnv::get_anon(&mut &bytes[..]).expect("get_anon"); + let work = build_anon_work(&env).expect("build_anon_work"); + let mut kenv = KEnv::::new(); + kenv.profile_sink = Some(ProfileSink::new(true)); + for item in &work { + let kid = KId::::new(item.primary().clone(), ()); + let mut tc = TypeChecker::::new_with_lazy_anon(&mut kenv, &env); + let _ = tc.check_const(&kid); + tc.finish_constant_accounting(); + } + let sink = kenv.profile_sink.take().unwrap(); + let mut builder = ProfileBuilder::new(); + for (consumer, rec) in &sink.records { + builder.block(block_of(&env, consumer), rec.fuel, 0, 1); + } + let profile = builder.finish(); + let mut blocks: Vec<(Address, u64)> = + profile.blocks().iter().map(|b| (b.addr.clone(), b.heartbeats)).collect(); + blocks.sort_by(|a, b| b.1.cmp(&a.1)); + + // ---- Full env for names; reverse-map addresses → Lean names. ---- + let full = IxonEnv::get(&mut &bytes[..]).expect("get"); + + eprintln!( + "env metadata: {} consts, {} named, {} names entries", + full.consts.iter().count(), + full.named.iter().count(), + full.names.iter().count(), + ); + // Diagnostic: is the heaviest anon block addr present in consts/names/named? + if let Some((addr, _)) = blocks.first() { + eprintln!( + "diag: heaviest anon block {}… in consts={} names={} named_addrs={}", + &addr.hex()[..16], + full.get_const(addr).is_some(), + full.names.get(addr).is_some(), + full.named.iter().any(|e| e.value().addr == *addr), + ); + } + eprintln!("diag: sample names entries (addr → name):"); + for e in full.names.iter().take(4) { + eprintln!(" {}… → {}", &e.key().hex()[..16], e.value()); + } + eprintln!("diag: sample consts keys:"); + for e in full.consts.iter().take(4) { + eprintln!(" {}…", &e.key().hex()[..16]); + } + eprintln!("Top {top_k} blocks by heartbeats in {path}:"); + for (addr, hb) in blocks.iter().take(top_k) { + let direct = full + .named + .iter() + .find(|e| e.value().addr == *addr) + .map(|e| e.key().to_string()); + let mut members: Vec = full + .named + .iter() + .filter(|e| block_of(&full, &e.value().addr) == *addr) + .map(|e| e.key().to_string()) + .collect(); + members.sort(); + members.dedup(); + eprintln!("\n {hb} heartbeats — block {}…", &addr.hex()[..16]); + if let Some(n) = direct { + eprintln!(" name: {n}"); + } + eprintln!(" {} named member(s):", members.len()); + for m in members.iter().take(40) { + eprintln!(" {m}"); + } + if members.len() > 40 { + eprintln!(" … and {} more", members.len() - 40); + } + } +} diff --git a/crates/kernel/examples/manifest_info.rs b/crates/kernel/examples/manifest_info.rs new file mode 100644 index 00000000..57f02854 --- /dev/null +++ b/crates/kernel/examples/manifest_info.rs @@ -0,0 +1,26 @@ +//! Inspect a `.ixes` manifest: list the heaviest shards (index, heartbeats, +//! block count) so you can target one with the host's `--only-shard`. +//! +//! cargo run -p ix-kernel --release --example manifest_info -- + +use ix_kernel::shard::ShardManifest; + +fn main() { + let path = std::env::args().nth(1).expect("usage: manifest_info "); + let bytes = std::fs::read(&path).expect("read .ixes"); + let m = ShardManifest::from_bytes(&bytes).expect("parse .ixes"); + let mut idx: Vec<(usize, u64, usize)> = m + .shards + .iter() + .enumerate() + .map(|(i, s)| (i, s.heartbeats, s.blocks.len())) + .collect(); + idx.sort_by(|a, b| b.1.cmp(&a.1)); + println!("{} shards. Heaviest:", m.num_shards); + for (i, hb, nb) in idx.iter().take(6) { + println!( + " manifest index {i}: {hb} heartbeats, {nb} block(s) → host --only-shard {}", + i + 1 + ); + } +} diff --git a/crates/kernel/examples/perf_check.rs b/crates/kernel/examples/perf_check.rs new file mode 100644 index 00000000..a8c9869b --- /dev/null +++ b/crates/kernel/examples/perf_check.rs @@ -0,0 +1,73 @@ +//! Native re-run of the SP1/Zisk guest's `check_const` loop, used to gather +//! data the zkVM guests cannot surface from inside the proof environment. +//! +//! `crates/kernel/src/lib.rs` short-circuits every `IX_*` env-var read on +//! `target_os = "zkvm"`, so the kernel's perf-counter infrastructure +//! (`crates/kernel/src/perf.rs`) is inert inside SP1/Zisk guests. Run this +//! native binary against the same `.ixe` to read cache hit rates, peak/avg +//! fuel per constant, and a wall-clock baseline. Combine with the SP1 +//! cycle-tracker output (see `sp1/host/src/main.rs` and +//! `sp1/guest/src/main.rs`) to attribute zkVM cycles to specific phases. +//! +//! IX_PERF_COUNTERS=1 cargo run --release --example perf_check -- path.ixe +//! +//! Mirrors `sp1/guest/src/main.rs` (Anon mode; no Meta-mode variant since +//! the cache and fuel behaviour is materially the same and Meta only adds +//! a few percent of cycles in the zkVM). + +use std::env; +use std::fs; +use std::time::Instant; + +use ix_kernel::id::KId; +use ix_kernel::ingress::ixon_ingress_owned; +use ix_kernel::mode::Anon; +use ix_kernel::tc::TypeChecker; +use ixon::env::Env as IxonEnv; +use ixon::metadata::ConstantMetaInfo; + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .format_timestamp(None) + .init(); + let path = env::args().nth(1).expect("usage: perf_check "); + let bytes = fs::read(&path).expect("read ixe"); + + // Collect (KId, pretty-name) pairs so the per-kid log lines line up with + // the `kid_NNN` labels printed by the SP1 guest's cycle tracker. + let env = IxonEnv::get(&mut &bytes[..]).expect("decode env"); + let entries: Vec<(KId, String)> = env + .named + .iter() + .filter(|e| !matches!(e.value().meta.info, ConstantMetaInfo::Muts { .. })) + .map(|e| (KId::::new(e.value().addr.clone(), ()), e.key().pretty())) + .collect(); + let kids: Vec> = entries.iter().map(|(k, _)| k.clone()).collect(); + eprintln!("[perf_check] env loaded: {} consts", kids.len()); + for (i, (_, name)) in entries.iter().enumerate() { + eprintln!(" kid_{i:03} {name}"); + } + + let t_ingress = Instant::now(); + let (mut kenv, _intern) = ixon_ingress_owned::(env).expect("ingress"); + eprintln!("[perf_check] ingress: {:>8.2?}", t_ingress.elapsed()); + + let t_check = Instant::now(); + let mut failures = 0u32; + let mut tc = TypeChecker::new(&mut kenv); + for kid in &kids { + if tc.check_const(kid).is_err() { + failures = failures.saturating_add(1); + } + } + eprintln!( + "[perf_check] check_const: {:>8.2?} ({} failures)", + t_check.elapsed(), + failures + ); + // Drop the TC first so its borrow on KEnv ends; KEnv's `Drop` is what + // actually dumps the `PerfCounters` summary when IX_PERF_COUNTERS=1. + drop(tc); + drop(kenv); +} diff --git a/crates/kernel/examples/shard_names.rs b/crates/kernel/examples/shard_names.rs new file mode 100644 index 00000000..740955b7 --- /dev/null +++ b/crates/kernel/examples/shard_names.rs @@ -0,0 +1,65 @@ +//! Name the member defs of given shards of a `.ixes` manifest. +//! +//! cargo run -p ix-kernel --release --example shard_names -- ... + +use std::collections::HashMap; + +use ix_common::address::Address; +use ix_kernel::shard::ShardManifest; +use ixon::constant::ConstantInfo as CI; +use ixon::env::Env as IxonEnv; + +fn block_of(env: &IxonEnv, addr: &Address) -> Address { + 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(), + } +} + +fn main() { + let a: Vec = std::env::args().collect(); + let ixe = &a[1]; + let ixes = &a[2]; + let idxs: Vec = a[3..].iter().filter_map(|s| s.parse().ok()).collect(); + + let env = IxonEnv::get(&mut &std::fs::read(ixe).unwrap()[..]).expect("get"); + let m = + ShardManifest::from_bytes(&std::fs::read(ixes).unwrap()).expect("manifest"); + + // block address -> member def names (group named consts by their home block). + let mut block2names: HashMap> = HashMap::new(); + for e in env.named.iter() { + let blk = block_of(&env, &e.value().addr); + block2names.entry(blk).or_default().push(e.key().to_string()); + } + + for idx in idxs { + let s = &m.shards[idx]; + println!( + "\n=== shard {idx}: {} block(s), {} heartbeats ===", + s.blocks.len(), + s.heartbeats + ); + for (shown, b) in s.blocks.iter().enumerate() { + let mut names = block2names.get(b).cloned().unwrap_or_default(); + names.sort(); + names.dedup(); + if shown < 12 { + if names.is_empty() { + println!(" {}… (no named member)", &b.hex()[..16]); + } else { + println!(" {}… {}", &b.hex()[..16], names.join(", ")); + } + } + } + if s.blocks.len() > 12 { + println!(" … and {} more block(s)", s.blocks.len() - 12); + } + } +} diff --git a/crates/kernel/examples/shard_plan.rs b/crates/kernel/examples/shard_plan.rs new file mode 100644 index 00000000..bfb1e3f7 --- /dev/null +++ b/crates/kernel/examples/shard_plan.rs @@ -0,0 +1,347 @@ +//! Generate a `.ixes` sharding manifest from a `.ixe` env, out of circuit — a +//! standalone equivalent of `ix profile` + `ix shard` that does NOT go through +//! the Lean/FFI layer. Useful for producing a plan to feed the Zisk host's +//! `--shard-plan`, and for iterating the partitioner without a full Lean build. +//! +//! Usage: +//! cargo run -p ix-kernel --release --example shard_plan -- \ +//! [--shards N | --max-cycles C] [--ram-gb G] [--cycles-per-heartbeat K] [--out path] +//! +//! By DEFAULT (no flags) it sizes the shard count automatically from this +//! machine's total RAM (`/proc/meminfo`): RAM → per-shard cycle cap (prover +//! model `peak_RAM_GB ≈ 45 + 32 × cycles_billions`, ~78% of RAM) → cap ÷ +//! cycles-per-heartbeat → N from total heartbeats. So you just hand it the +//! `.ixe`; no budget to pick. +//! +//! --shards N force exactly N shards (skip the estimate). +//! --max-cycles C force a per-shard guest-cycle budget (skip RAM read). +//! --ram-gb G override detected RAM (for what-if sizing). +//! --cycles-per-heartbeat K heartbeat→cycle conversion (default 215000, +//! measured across envs; recalibrate per env with one +//! `--execute`: a shard's steps ÷ its heartbeats). +//! --out path output .ixes (default .ixes). +//! +//! Profiles the env with the real kernel (single worker, cache-isolated so +//! heartbeats reflect un-memoized in-circuit cost and every delta-unfold is +//! recorded), aggregates per-constant records into a block profile, partitions, +//! and writes the manifest. + +use ix_common::address::Address; +use ix_kernel::anon_work::build_anon_work; +use ix_kernel::env::KEnv; +use ix_kernel::id::KId; +use ix_kernel::mode::Anon; +use ix_kernel::profile::{BlockProfile, ProfileBuilder, ProfileSink}; +use ix_kernel::shard::{ + Hypergraph, ShardManifest, cycle_cap_for_ram, partition_for_cycle_cap, +}; +use ix_kernel::tc::TypeChecker; +use ixon::constant::ConstantInfo as CI; +use ixon::env::Env as IxonEnv; + +/// Map a constant to its ingress block: a projection's `block` address, +/// otherwise the constant itself (mirrors the FFI profiler's `profile_block_of`). +fn block_of(env: &IxonEnv, addr: &Address) -> Address { + 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(), + } +} + +fn block_size(env: &IxonEnv, block: &Address) -> u32 { + env + .get_const_bytes(block) + .map_or(0, |b| u32::try_from(b.len()).unwrap_or(u32::MAX)) +} + +/// Parse a count that may use float/scientific notation (e.g. `4.5e9`). +fn parse_count(s: &str) -> u64 { + let v: f64 = s.parse().unwrap_or_else(|_| { + eprintln!("error: not a number: {s}"); + std::process::exit(1); + }); + if v < 0.0 { + eprintln!("error: must be non-negative: {s}"); + std::process::exit(1); + } + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + // floor semantics intended; sign guarded by the non-negative exit above + { + v as u64 + } +} + +/// Total RAM in GiB from `/proc/meminfo` (`None` off-Linux / on parse failure). +fn detect_ram_gb() -> Option { + let s = std::fs::read_to_string("/proc/meminfo").ok()?; + let line = s.lines().find(|l| l.starts_with("MemTotal:"))?; + let kb: f64 = line.split_whitespace().nth(1)?.parse().ok()?; + Some(kb / 1024.0 / 1024.0) +} + +fn usage() -> ! { + eprintln!( + "usage: shard_plan [--shards N | --max-cycles C] \ + [--ram-gb G] [--cycles-per-heartbeat K] [--store-dir dir] [--out path]" + ); + eprintln!( + " default: size N automatically from this machine's RAM (/proc/meminfo).\n \ + --shards N forces a count; --max-cycles C forces a per-shard cycle budget;\n \ + --ram-gb G overrides detected RAM.\n \ + --store-dir dir: store-aware planning — work items whose targets are all\n \ + covered by the proof store are excluded (not profiled, not partitioned);\n \ + the manifest covers only NOVEL work. The prover resolves the covered\n \ + remainder by folding the store's proofs (zisk-host --store-dir)." + ); + std::process::exit(1); +} + +/// The proof store's covered-target set: the union of `proofs/*.cover` +/// (packed 32-byte addresses), exactly as `zisk-host --store-dir` writes them. +fn load_covered(dir: &str) -> std::collections::HashSet
{ + let pdir = std::path::Path::new(dir).join("proofs"); + let mut covered = std::collections::HashSet::new(); + for idx in 0.. { + let Ok(bytes) = std::fs::read(pdir.join(format!("{idx}.cover"))) else { + break; + }; + covered.extend(Address::unpack(&bytes)); + } + covered +} + +fn main() { + // ---- Parse args. ---- + let args: Vec = std::env::args().collect(); + let mut path: Option = None; + let mut shards: Option = None; + let mut naive = false; + let mut max_cycles: Option = None; + // Default heartbeat→cycle ratio. Measured across 12 envs: large, shardable + // envs (>20k hb) cluster at 194–208k whole-env; the per-shard ratio runs + // higher (less memoization) — mergesort's heaviest shard is ~216k. 215k is + // that per-shard value (the conservative end for the regime that needs + // sharding). Tiny envs run lower (~130–160k, cheap arithmetic); a couple of + // mid envs run ~258k (heavy def-eq) — recalibrate those via --execute. + let mut cyc_per_hb: u64 = 215_000; + let mut ram_gb: Option = None; + let mut store_dir: Option = None; + let mut out: Option = None; + let mut i = 1; + while i < args.len() { + match args[i].as_str() { + "--naive" => { + naive = true; + }, + "--shards" => { + i += 1; + shards = Some( + usize::try_from(parse_count(args.get(i).unwrap_or(&String::new()))) + .expect("shard count fits usize"), + ); + }, + "--max-cycles" => { + i += 1; + max_cycles = Some(parse_count(args.get(i).unwrap_or(&String::new()))); + }, + "--cycles-per-heartbeat" => { + i += 1; + cyc_per_hb = parse_count(args.get(i).unwrap_or(&String::new())).max(1); + }, + "--ram-gb" => { + i += 1; + ram_gb = args.get(i).and_then(|s| s.parse::().ok()); + }, + "--store-dir" => { + i += 1; + store_dir = args.get(i).cloned(); + }, + "--out" => { + i += 1; + out = args.get(i).cloned(); + }, + s if s.starts_with("--") => { + eprintln!("error: unknown flag {s}"); + usage(); + }, + s if path.is_none() => path = Some(s.to_string()), + s => { + eprintln!("error: unexpected argument {s}"); + usage(); + }, + } + i += 1; + } + let Some(path) = path else { usage() }; + if shards.is_some() && max_cycles.is_some() { + eprintln!("error: pass at most one of --shards / --max-cycles"); + usage(); + } + let out = out.unwrap_or_else(|| format!("{path}.ixes")); + + // ---- Store-aware planning: drop work the store already covers. ---- + // A work item is covered iff every target it would certify is in the store + // (the same rule the prover applies per shard). Covered items are not + // profiled (no point typechecking them again) and not partitioned — they're + // resolved at prove time by folding the store's proofs. Covered BLOCKS are + // also excluded from the hypergraph: a novel→covered delta edge is an + // assumption discharged at aggregation, not a cut to minimize. + let covered: std::collections::HashSet
= + store_dir.as_deref().map(load_covered).unwrap_or_default(); + let covered_item = |item: &ix_kernel::anon_work::AnonWorkItem| -> bool { + !covered.is_empty() + && item.proven_targets().iter().all(|t| covered.contains(t)) + }; + + // ---- Profile: run the kernel, recording heartbeats + delta-unfold edges. ---- + let bytes = std::fs::read(&path).expect("read .ixe"); + let env = + IxonEnv::get_anon(&mut &bytes[..]).expect("invalid Ixon environment"); + let work = build_anon_work(&env).expect("build_anon_work"); + let (novel_work, covered_work): (Vec<_>, Vec<_>) = + work.iter().partition(|item| !covered_item(item)); + // Blocks whose work item is covered — excluded from the hypergraph below. + let covered_blocks: std::collections::HashSet
= + covered_work.iter().map(|item| block_of(&env, item.primary())).collect(); + if novel_work.is_empty() { + eprintln!( + "nothing to plan: the store covers all {} work items", + work.len() + ); + std::process::exit(0); + } + + let mut kenv = KEnv::::new(); + kenv.profile_sink = Some(ProfileSink::new(true)); // isolate = sound recording + let mut failures = 0usize; + for item in &novel_work { + let kid = KId::::new(item.primary().clone(), ()); + let mut tc = TypeChecker::::new_with_lazy_anon(&mut kenv, &env); + if tc.check_const(&kid).is_err() { + failures += 1; + } + tc.finish_constant_accounting(); + } + let sink = kenv.profile_sink.take().expect("profile sink"); + + // ---- Aggregate per-constant records into a block-level BlockProfile. ---- + let mut builder = ProfileBuilder::new(); + for (consumer, rec) in &sink.records { + let cb = block_of(&env, consumer); + let cs = block_size(&env, &cb); + builder.block(cb.clone(), rec.fuel, cs, 1); + for prod in &rec.producers { + let pb = block_of(&env, prod); + if covered_blocks.contains(&pb) { + continue; // assumption against the store, not a partition edge + } + let ps = block_size(&env, &pb); + builder.block(pb.clone(), 0, ps, 0); + builder.delta_edge(cb.clone(), pb); + } + } + let profile: BlockProfile = builder.finish(); + + // ---- Choose the partition. Default: size N from the machine's RAM (no + // budget needed). --shards N forces a count; --max-cycles C forces a budget. + let (shard_of, n, tree) = if let (true, Some(n)) = (naive, shards) { + // Naive baseline: group blocks into N contiguous (address-sorted) chunks, + // ignoring the delta graph — to isolate the value of smart grouping. No + // bisection tree (the prover falls back to a flat fold). + let nb = profile.num_blocks(); + let chunk = nb.div_ceil(n.max(1)).max(1); + let shard_of: Vec = (0..nb) + .map(|b| { + u32::try_from((b / chunk).min(n - 1)).expect("shard index fits u32") + }) + .collect(); + eprintln!("naive grouping into {n} contiguous block chunks"); + (shard_of, n, None) + } else if let Some(n) = shards { + let h = Hypergraph::from_profile(&profile); + let (so, t) = h.partition_with_tree(n, 0.05); + (so, n, Some(t)) + } else { + // Per-shard cycle cap: explicit --max-cycles, else derived from RAM. + let cap = match max_cycles { + Some(c) => { + eprintln!("budget: --max-cycles {c}"); + c + }, + None => { + let ram = ram_gb.or_else(detect_ram_gb).unwrap_or_else(|| { + eprintln!("warning: could not read /proc/meminfo; assuming 256 GB (override with --ram-gb)"); + 256.0 + }); + let c = cycle_cap_for_ram(ram); + if c == 0 { + eprintln!( + "error: {ram:.0} GB RAM is below the ~45 GB prover base — nothing will \ + prove on this machine." + ); + std::process::exit(1); + } + eprintln!( + "auto: machine RAM {ram:.0} GB → per-shard cap ~{c} cycles \ + (~78% RAM via prover model peak_GB≈45+32·Bcyc)" + ); + c + }, + }; + let plan = partition_for_cycle_cap(&profile, cap, cyc_per_hb, 0.05); + let cyc = u128::from(plan.max_shard_heartbeats) * u128::from(cyc_per_hb); + let floor_cyc = + u128::from(plan.largest_block_heartbeats) * u128::from(cyc_per_hb); + eprintln!( + " total heartbeats {} → heartbeat_cap {} (@ {cyc_per_hb} cyc/hb) → N={}", + profile.total_heartbeats(), + plan.heartbeat_cap, + plan.num_shards, + ); + eprintln!( + " heaviest shard: {} hb (~{cyc} cycles); largest atomic block: {} hb (~{floor_cyc} cycles)", + plan.max_shard_heartbeats, plan.largest_block_heartbeats, + ); + if plan.infeasible_atomic_floor { + eprintln!( + " WARNING: the largest atomic (mutual) block alone exceeds the cap — NO \ + shard count can fit it. Split that block upstream (Lean side) or use a \ + bigger box." + ); + } else if plan.max_shard_heartbeats > plan.heartbeat_cap { + eprintln!( + " note: heaviest shard still over cap at N={} (pinned by the atomic-block \ + floor); more shards won't lower it.", + plan.num_shards, + ); + } + (plan.shard_of, plan.num_shards, Some(plan.tree)) + }; + + // ---- Manifest. ---- + let mut manifest = ShardManifest::build(&profile, &shard_of, n); + if let Some(t) = tree { + manifest = manifest.with_tree(t); + } + for s in &mut manifest.shards { + s.assumption_root = ixon::merkle::merkle_root_canonical(&s.foreign_blocks); + } + std::fs::write(&out, manifest.to_bytes()).expect("write .ixes manifest"); + + eprintln!( + "profiled {} novel work items ({} store-covered skipped, {failures} failure(s)), \ + {} blocks, {} delta edges → {} shards; wrote {out}", + novel_work.len(), + covered_work.len(), + profile.num_blocks(), + profile.num_edges(), + manifest.num_shards, + ); + eprintln!("{}", manifest.summary()); +} diff --git a/src/ix/kernel/anon_env.rs b/crates/kernel/src/anon_env.rs similarity index 95% rename from src/ix/kernel/anon_env.rs rename to crates/kernel/src/anon_env.rs index cfc18168..fe8375f7 100644 --- a/src/ix/kernel/anon_env.rs +++ b/crates/kernel/src/anon_env.rs @@ -24,9 +24,9 @@ use rustc_hash::FxHashSet; use std::collections::VecDeque; use std::sync::Arc; -use crate::ix::address::Address; -use crate::ix::ixon::constant::Constant; -use crate::ix::ixon::env::Env as IxonEnv; +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)] @@ -103,10 +103,10 @@ impl<'a> AnonEnv<'a> { #[cfg(test)] mod tests { use super::*; - use crate::ix::env::Name; - use crate::ix::ixon::constant::{Axiom, ConstantInfo}; - use crate::ix::ixon::env::Named; - use crate::ix::ixon::expr::Expr; + 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( diff --git a/crates/kernel/src/anon_work.rs b/crates/kernel/src/anon_work.rs new file mode 100644 index 00000000..dad205a5 --- /dev/null +++ b/crates/kernel/src/anon_work.rs @@ -0,0 +1,183 @@ +//! 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 `TypeChecker::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 `Tag4` 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 `tc.check_const(primary)` 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 `tc.check_const`. + 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 + /// union of `proven_targets()` over a full `build_anon_work` is exactly + /// `env.consts.keys()`. + 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 + }, + } + } +} + +/// 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()`. +/// +/// Errors only on a corrupted env (missing const at an enumerated +/// address, or a Tag4 head byte that doesn't correspond to a known +/// `ConstantInfo` variant). +pub fn build_anon_work(env: &IxonEnv) -> Result, String> { + use ConstVariantTag as Tag; + use ixon::constant::ConstantInfo as CI; + use ixon::constant::MutConst as MC; + + 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() + )); + }; + let mut targets: Vec
= Vec::new(); + for (i, member) in members.iter().enumerate() { + let i = i as u64; + let member_addr = match member { + MC::Defn(_) => anon_defn_proj_addr(&addr, i), + MC::Indc(_) => anon_indc_proj_addr(&addr, i), + MC::Recr(_) => anon_recr_proj_addr(&addr, i), + }; + targets.push(member_addr); + if let MC::Indc(ind) = member { + for cidx in 0..ind.ctors.len() as u64 { + targets.push(anon_ctor_proj_addr(&addr, i, cidx)); + } + } + } + if targets.is_empty() { + continue; + } + let primary = targets[0].clone(); + work.push(AnonWorkItem::Block { + block_addr: addr.clone(), + primary, + targets, + }); + }, + } + } + + Ok(work) +} diff --git a/src/ix/kernel/canonical_check.rs b/crates/kernel/src/canonical_check.rs similarity index 99% rename from src/ix/kernel/canonical_check.rs rename to crates/kernel/src/canonical_check.rs index 68429f99..9c028fec 100644 --- a/src/ix/kernel/canonical_check.rs +++ b/crates/kernel/src/canonical_check.rs @@ -38,7 +38,7 @@ use std::cmp::Ordering; use rustc_hash::FxHashMap; -use crate::ix::address::Address; +use ix_common::address::Address; use super::constant::{KConst, RecRule}; use super::error::TcError; @@ -47,7 +47,7 @@ use super::id::KId; use super::level::{KUniv, UnivData}; use super::mode::KernelMode; -pub use crate::ix::strong_ordering::SOrd; +pub use ix_common::strong_ordering::SOrd; // =========================================================================== // KMutCtx — block-local address → class-index map @@ -415,11 +415,11 @@ fn compare_krecr( /// alpha-collapse on the canonical IXON form, which doesn't include /// hints (and treats safety as a separate sidecar in practice). fn compare_kdefn( - x_kind: crate::ix::ixon::constant::DefKind, + x_kind: ixon::constant::DefKind, x_lvls: u64, x_ty: &KExpr, x_val: &KExpr, - y_kind: crate::ix::ixon::constant::DefKind, + y_kind: ixon::constant::DefKind, y_lvls: u64, y_ty: &KExpr, y_val: &KExpr, @@ -826,10 +826,10 @@ pub fn validate_canonical_block_single_pass( #[cfg(test)] mod tests { use super::*; - use crate::ix::address::Address; - use crate::ix::env::{BinderInfo, Name}; - use crate::ix::env::{DefinitionSafety, ReducibilityHints}; - use crate::ix::ixon::constant::DefKind; + use ix_common::address::Address; + use ix_common::env::{BinderInfo, Name}; + use ix_common::env::{DefinitionSafety, ReducibilityHints}; + use ixon::constant::DefKind; use super::super::expr::KExpr; use super::super::level::KUniv; diff --git a/src/ix/kernel/check.rs b/crates/kernel/src/check.rs similarity index 96% rename from src/ix/kernel/check.rs rename to crates/kernel/src/check.rs index 0b0f9764..094ecf5e 100644 --- a/src/ix/kernel/check.rs +++ b/crates/kernel/src/check.rs @@ -1,13 +1,12 @@ //! Constant checking dispatch. -use std::sync::LazyLock; use std::time::{Duration, Instant}; use rustc_hash::FxHashSet; -use crate::ix::address::Address; -use crate::ix::env::{DefinitionSafety, QuotKind}; -use crate::ix::ixon::constant::DefKind; +use ix_common::address::Address; +use ix_common::env::{DefinitionSafety, QuotKind}; +use ixon::constant::DefKind; use super::constant::KConst; use super::env::Addr; @@ -26,14 +25,20 @@ use super::tc::TypeChecker; /// `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`. -static IX_DECL_DIFF: LazyLock = - LazyLock::new(|| std::env::var("IX_DECL_DIFF").is_ok()); +#[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. -static IX_PHASE_TIMING: LazyLock = - LazyLock::new(|| std::env::var("IX_PHASE_TIMING").is_ok()); +#[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 { @@ -67,7 +72,7 @@ impl TypeChecker<'_, M> { /// Type-check a single constant. Clears per-constant caches first. pub fn check_const(&mut self, id: &KId) -> Result<(), TcError> where - M::MField>: CheckDupLevelParams, + M::MField>: CheckDupLevelParams, { let c = self.get_const(id)?; if let Some(block) = self.coordinated_block_for(&c)? { @@ -84,7 +89,7 @@ impl TypeChecker<'_, M> { fn check_const_member_fresh(&mut self, id: &KId) -> Result<(), TcError> where - M::MField>: CheckDupLevelParams, + M::MField>: CheckDupLevelParams, { self.reset(); self.begin_const(id); @@ -99,7 +104,7 @@ impl TypeChecker<'_, M> { c: &KConst, ) -> Result<(), TcError> where - M::MField>: CheckDupLevelParams, + M::MField>: CheckDupLevelParams, { let phase_timing = *IX_PHASE_TIMING; let overall = if phase_timing { Some(Instant::now()) } else { None }; @@ -154,16 +159,16 @@ impl TypeChecker<'_, M> { // (delta, iota, native, ...) is missing for convergence. let val_ty_whnf = self.whnf(&val_ty); let ty_whnf = self.whnf(ty); - eprintln!("[decl diff] DeclTypeMismatch"); - eprintln!(" val_ty: {val_ty}"); - eprintln!(" ty: {ty}"); + log::info!("[decl diff] DeclTypeMismatch"); + log::info!(" val_ty: {val_ty}"); + log::info!(" ty: {ty}"); match &val_ty_whnf { - Ok(w) => eprintln!(" val_ty whnf: {w}"), - Err(e) => eprintln!(" val_ty whnf: ERR {e}"), + Ok(w) => log::info!(" val_ty whnf: {w}"), + Err(e) => log::info!(" val_ty whnf: ERR {e}"), } match &ty_whnf { - Ok(w) => eprintln!(" ty whnf: {w}"), - Err(e) => eprintln!(" ty whnf: ERR {e}"), + Ok(w) => log::info!(" ty whnf: {w}"), + Err(e) => log::info!(" ty whnf: ERR {e}"), } } return Err(TcError::DeclTypeMismatch); @@ -187,7 +192,7 @@ impl TypeChecker<'_, M> { if let Some(t0) = overall && self.phase_timing_label_matches(id) { - eprintln!( + log::info!( "[phase] {} total={:>8.1?} dup_lvls={:>8.1?} validate={:>8.1?} validate_ty={:>8.1?} validate_val={:>8.1?} validate_rules={:>8.1?} validate_univ={:>8.1?} infer_ty={:>8.1?} infer_val={:>8.1?} def_eq={:>8.1?} safety={:>8.1?} safety_ty={:>8.1?} safety_val={:>8.1?}", id, t0.elapsed(), @@ -335,7 +340,7 @@ impl TypeChecker<'_, M> { requested: &KId, ) -> Result<(), TcError> where - M::MField>: CheckDupLevelParams, + M::MField>: CheckDupLevelParams, { let phase_timing = *IX_PHASE_TIMING; let overall = if phase_timing { Some(Instant::now()) } else { None }; @@ -390,7 +395,7 @@ impl TypeChecker<'_, M> { if let Some(t0) = overall && self.phase_timing_label_matches(block) { - eprintln!( + log::info!( "[phase-block] {} kind={:?} members={} total={:>8.1?} get_members={:>8.1?} prevalidate={:>8.1?} validate_ty={:>8.1?} validate_val={:>8.1?} validate_rules={:>8.1?} validate_univ={:>8.1?} classify={:>8.1?} body={:>8.1?}", block, kind, @@ -479,7 +484,7 @@ impl TypeChecker<'_, M> { } fn phase_timing_label_matches(&self, id: &KId) -> bool { - match std::env::var("IX_KERNEL_DEBUG_CONST") { + match crate::env_var("IX_KERNEL_DEBUG_CONST") { Ok(filter) if filter.is_empty() => true, Ok(filter) => { id.to_string().contains(&filter) @@ -867,15 +872,15 @@ mod tests { use super::super::level::KUniv; use super::super::mode::Anon; use super::super::tc::TypeChecker; - use crate::ix::address::Address; - use crate::ix::env::{DefinitionSafety, ReducibilityHints}; - use crate::ix::ixon::constant::DefKind; + use ix_common::address::Address; + use ix_common::env::{DefinitionSafety, ReducibilityHints}; + use ixon::constant::DefKind; #[test] fn profile_sink_records_delta_edge_and_fuel() { - use crate::ix::kernel::mode::Meta; - use crate::ix::kernel::testing as t; - use crate::ix::profile::ProfileSink; + 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( @@ -1139,13 +1144,13 @@ mod tests { #[test] fn check_duplicate_level_params_rejected() { - use crate::ix::kernel::mode::Meta; + use crate::mode::Meta; type ME = KExpr; type MU = KUniv; let mut env = KEnv::::new(); let dup_name = - crate::ix::env::Name::str(crate::ix::env::Name::anon(), "u".into()); + 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(), diff --git a/src/ix/kernel/claim.rs b/crates/kernel/src/claim.rs similarity index 95% rename from src/ix/kernel/claim.rs rename to crates/kernel/src/claim.rs index 301300c7..8d6dc65b 100644 --- a/src/ix/kernel/claim.rs +++ b/crates/kernel/src/claim.rs @@ -12,11 +12,11 @@ use rustc_hash::FxHashSet; -use crate::ix::address::Address; -use crate::ix::ixon::constant::ConstantInfo; -use crate::ix::ixon::env::Env; -use crate::ix::ixon::merkle::merkle_root_canonical; -use crate::ix::ixon::proof::Claim; +use ix_common::address::Address; +use ixon::constant::ConstantInfo; +use ixon::env::Env; +use ixon::merkle::merkle_root_canonical; +use ixon::proof::Claim; /// Canonical merkle root over the env's `consts.keys()`. Also called /// from the env serializer. Returns `None` for an empty const set. @@ -91,12 +91,10 @@ pub fn build_check_env_claim(env: &Env) -> Option { #[cfg(test)] mod tests { use super::*; - use crate::ix::env::DefinitionSafety; - use crate::ix::ixon::constant::{ - Axiom, Constant, ConstantInfo, DefKind, Definition, - }; - use crate::ix::ixon::expr::Expr; - use crate::ix::ixon::merkle::leaf_hash; + 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 axiom_const(refs: Vec
) -> Constant { diff --git a/src/ix/kernel/congruence.rs b/crates/kernel/src/congruence.rs similarity index 97% rename from src/ix/kernel/congruence.rs rename to crates/kernel/src/congruence.rs index 2ba3ff44..906ae1b2 100644 --- a/src/ix/kernel/congruence.rs +++ b/crates/kernel/src/congruence.rs @@ -3,8 +3,8 @@ //! Validates that Ixon ingress in Anon mode produces structurally correct //! constants by comparing the Lean `ConstantInfo` against the loaded `KConst`. -use crate::ix::address::Address; -use crate::ix::env::{self as lean, ConstantInfo as LeanCI, Literal, Name}; +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}; @@ -17,7 +17,7 @@ pub struct NameResolver { } impl NameResolver { - pub fn from_ixon_env(ixon_env: &crate::ix::ixon::env::Env) -> Self { + 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()); @@ -364,22 +364,22 @@ fn zero_const_tag(c: &KConst) -> &'static str { #[cfg(test)] mod tests { use super::*; - use crate::ix::address::Address; - use crate::ix::env::{ + 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 crate::ix::ixon::env::{Env as IxonEnv, Named}; - use crate::ix::kernel::constant::KConst; - use crate::ix::kernel::id::KId; - use crate::ix::kernel::mode::Anon; + 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) -> lean_ffi::nat::Nat { - lean_ffi::nat::Nat::from(x) + fn n(x: u64) -> bignat::Nat { + bignat::Nat::from(x) } // ---- test helpers ---- @@ -725,7 +725,7 @@ mod tests { let kc = KConst::::Defn { name: (), level_params: (), - kind: crate::ix::ixon::constant::DefKind::Definition, + kind: ixon::constant::DefKind::Definition, safety: DefinitionSafety::Safe, hints: ReducibilityHints::Opaque, lvls: 0, @@ -768,7 +768,7 @@ mod tests { let kc = KConst::::Defn { name: (), level_params: (), - kind: crate::ix::ixon::constant::DefKind::Definition, + kind: ixon::constant::DefKind::Definition, safety: DefinitionSafety::Safe, hints: ReducibilityHints::Opaque, lvls: 0, @@ -969,7 +969,7 @@ mod tests { let k = KConst::::Defn { name: (), level_params: (), - kind: crate::ix::ixon::constant::DefKind::Theorem, + kind: ixon::constant::DefKind::Theorem, safety: DefinitionSafety::Safe, hints: ReducibilityHints::Opaque, lvls: 0, diff --git a/src/ix/kernel/constant.rs b/crates/kernel/src/constant.rs similarity index 95% rename from src/ix/kernel/constant.rs rename to crates/kernel/src/constant.rs index 09bde5f0..16a3592b 100644 --- a/src/ix/kernel/constant.rs +++ b/crates/kernel/src/constant.rs @@ -3,8 +3,8 @@ //! Each variant carries structural fields plus metadata fields //! (`name`, `level_params`, `lean_all`) for roundtrip fidelity in Meta mode. -use crate::ix::env::{DefinitionSafety, Name, QuotKind, ReducibilityHints}; -use crate::ix::ixon::constant::DefKind; +use ix_common::env::{DefinitionSafety, Name, QuotKind, ReducibilityHints}; +use ixon::constant::DefKind; use super::expr::KExpr; use super::id::KId; @@ -153,9 +153,9 @@ mod tests { use super::super::level::KUniv; use super::super::mode::Anon; use super::*; - use crate::ix::address::Address; - use crate::ix::env::{DefinitionSafety, QuotKind, ReducibilityHints}; - use crate::ix::ixon::constant::DefKind; + use ix_common::address::Address; + use ix_common::env::{DefinitionSafety, QuotKind, ReducibilityHints}; + use ixon::constant::DefKind; fn sort0() -> KExpr { KExpr::sort(KUniv::zero()) diff --git a/src/ix/kernel/def_eq.rs b/crates/kernel/src/def_eq.rs similarity index 92% rename from src/ix/kernel/def_eq.rs rename to crates/kernel/src/def_eq.rs index a9323339..fb873cfc 100644 --- a/src/ix/kernel/def_eq.rs +++ b/crates/kernel/src/def_eq.rs @@ -7,9 +7,7 @@ //! 4. Iterative lazy delta with same-head-spine optimization //! 5. Full WHNF, structural comparison, eta, struct eta -use std::sync::LazyLock; - -use crate::ix::ixon::constant::DefKind; +use ixon::constant::DefKind; use super::constant::KConst; use super::env::Addr; @@ -23,34 +21,35 @@ use super::subst::{instantiate_rev, lift}; use super::tc::{ MAX_DEF_EQ_DEPTH, MAX_WHNF_FUEL, TypeChecker, collect_app_spine, }; +use super::whnf::PrimFamily; /// When set, trace every `is_def_eq` call where one side's head constant /// starts with the prefix in `IX_DEF_EQ_TRACE` (e.g. `IX_DEF_EQ_TRACE=bmod` /// 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: LazyLock> = - LazyLock::new(|| std::env::var("IX_DEF_EQ_TRACE").ok()); +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: LazyLock = - LazyLock::new(|| std::env::var("IX_DEF_EQ_COUNT_LOG").is_ok()); +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: LazyLock> = - LazyLock::new(|| std::env::var("IX_DEF_EQ_MAX_DUMP").ok()); +static IX_DEF_EQ_MAX_DUMP: crate::EnvString = + crate::EnvString::new(|| crate::env_var("IX_DEF_EQ_MAX_DUMP").ok()); -static IX_ETA_TRACE: LazyLock> = - LazyLock::new(|| std::env::var("IX_ETA_TRACE").ok()); +static IX_ETA_TRACE: crate::EnvString = + crate::EnvString::new(|| crate::env_var("IX_ETA_TRACE").ok()); -static IX_PROJ_DELTA_TRACE: LazyLock> = - LazyLock::new(|| std::env::var("IX_PROJ_DELTA_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); @@ -65,7 +64,7 @@ impl TypeChecker<'_, M> { if *IX_DEF_EQ_COUNT_LOG { let n = DEF_EQ_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); if n.is_multiple_of(100_000) && n > 0 { - eprintln!("[is_def_eq] count={n}"); + log::info!("[is_def_eq] count={n}"); } } if a.ptr_eq(b) { @@ -87,12 +86,12 @@ impl TypeChecker<'_, M> { let a_hit = head_const_name(a).is_some_and(|n| n.contains(prefix)); let b_hit = head_const_name(b).is_some_and(|n| n.contains(prefix)); if a_hit || b_hit { - eprintln!( + log::info!( "[deq] depth={} a={}", self.def_eq_depth, compact_def_eq_expr(a) ); - eprintln!( + log::info!( "[deq] depth={} b={}", self.def_eq_depth, compact_def_eq_expr(b) @@ -120,8 +119,8 @@ impl TypeChecker<'_, M> { // per method call. Any true result moves the originals into `add_equiv` // before returning. let eq_ctx = self.def_eq_ctx_key(a, b); - let a_key: crate::ix::kernel::equiv::EqKey = (a.hash_key(), eq_ctx); - let b_key: crate::ix::kernel::equiv::EqKey = (b.hash_key(), eq_ctx); + let a_key: crate::equiv::EqKey = (a.hash_key(), eq_ctx); + let b_key: crate::equiv::EqKey = (b.hash_key(), eq_ctx); if self.equiv_manager.is_equiv(&a_key, &b_key) { return Ok(true); @@ -211,7 +210,7 @@ impl TypeChecker<'_, M> { let ok = result?; if trace_active { - eprintln!( + log::info!( "[deq] depth={} -> {} ({})", self.def_eq_depth, ok, @@ -220,8 +219,8 @@ impl TypeChecker<'_, M> { // On FAIL, also dump the full a/b that failed (post-Tier-1 quick). // Lets us see what the def-eq engine actually compared. if !ok { - eprintln!("[deq fail] depth={} a-full: {a}", self.def_eq_depth); - eprintln!("[deq fail] depth={} b-full: {b}", self.def_eq_depth); + log::info!("[deq fail] depth={} a-full: {a}", self.def_eq_depth); + log::info!("[deq fail] depth={} b-full: {b}", self.def_eq_depth); } self.def_eq_trace_depth = self.def_eq_trace_depth.saturating_sub(1); } @@ -351,11 +350,17 @@ impl TypeChecker<'_, M> { // primitives entirely when either side has a free variable, unless // eagerReduce is active. let nat_ok = (!wa.has_fvars() && !wb.has_fvars()) || self.eager_reduce; + let fam_a = self.head_prim_family(&wa); + let fam_b = self.head_prim_family(&wb); if nat_ok { - if let Some(wa2) = self.try_reduce_nat(&wa)? { + if fam_a == PrimFamily::Nat + && let Some(wa2) = self.try_reduce_nat(&wa)? + { return self.is_def_eq(&wa2, &wb); } - if let Some(wb2) = self.try_reduce_nat(&wb)? { + if fam_b == PrimFamily::Nat + && let Some(wb2) = self.try_reduce_nat(&wb)? + { return self.is_def_eq(&wa, &wb2); } } @@ -365,17 +370,25 @@ impl TypeChecker<'_, M> { // (lean4 `type_checker.cpp:986-991`, lean4lean `TypeChecker.lean:625-628`). // Ix-specific `try_reduce_decidable` runs after native to keep the // reference-aligned segment tight. - if let Some(wa2) = self.try_reduce_native(&wa)? { + if fam_a == PrimFamily::Native + && let Some(wa2) = self.try_reduce_native(&wa)? + { return self.is_def_eq(&wa2, &wb); } - if let Some(wb2) = self.try_reduce_native(&wb)? { + if fam_b == PrimFamily::Native + && let Some(wb2) = self.try_reduce_native(&wb)? + { return self.is_def_eq(&wa, &wb2); } - if let Some(wa2) = self.try_reduce_decidable(&wa)? { + if fam_a == PrimFamily::Decidable + && let Some(wa2) = self.try_reduce_decidable(&wa)? + { return self.is_def_eq(&wa2, &wb); } - if let Some(wb2) = self.try_reduce_decidable(&wb)? { + if fam_b == PrimFamily::Decidable + && let Some(wb2) = self.try_reduce_decidable(&wb)? + { return self.is_def_eq(&wa, &wb2); } @@ -496,9 +509,9 @@ impl TypeChecker<'_, M> { } if self.def_eq_trace_depth > 0 { - eprintln!("[deq tier4 break] depth={}", self.def_eq_depth); - eprintln!(" wa: {wa}"); - eprintln!(" wb: {wb}"); + log::info!("[deq tier4 break] depth={}", self.def_eq_depth); + log::info!(" wa: {wa}"); + log::info!(" wb: {wb}"); } // Tier 4b: post-delta congruence checks (lean4lean isDefEqConst/Fvar/Proj) @@ -539,15 +552,15 @@ impl TypeChecker<'_, M> { // 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) = std::env::var("IX_DEF_EQ_TIER5_DUMP") + 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 { - eprintln!("[deq tier5 fail] depth={}", self.def_eq_depth); - eprintln!(" wa: {wa}"); - eprintln!(" wb: {wb}"); + log::info!("[deq tier5 fail] depth={}", self.def_eq_depth); + log::info!(" wa: {wa}"); + log::info!(" wb: {wb}"); } } @@ -807,7 +820,7 @@ impl TypeChecker<'_, M> { Ok(ty) => ty, Err(_) => return Ok(false), }; - if !self.is_prop_type(&a_ty)? { + if !self.is_prop_type(&a_ty) { return Ok(false); } let b_ty = match self.with_infer_only(|tc| tc.infer(b)) { @@ -826,14 +839,11 @@ impl TypeChecker<'_, M> { /// the inner chain are propagated as `Ok(false)` (treating ill-typed /// metadata as non-prop), matching the previous behaviour of /// `try_proof_irrel`. - pub(crate) fn is_prop_type( - &mut self, - ty: &KExpr, - ) -> Result> { + 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 Ok(cached); + return cached; } self.env.perf.record_is_prop_miss(); self.record_hot_miss("is-prop", ty); @@ -852,7 +862,7 @@ impl TypeChecker<'_, M> { Err(_) => false, }; self.env.is_prop_cache.insert(cache_key, result); - Ok(result) + result } /// Unit-like type: non-recursive, 0 indices, 1 ctor with 0 fields. @@ -925,6 +935,26 @@ impl TypeChecker<'_, M> { } } + /// Allocation-free check that `e` could decompose to `base + offset`: + /// a Nat literal, `Nat.zero`/`Nat.succ`, or an app whose head constant is + /// `Nat.succ`/`Nat.add`. Walks the app chain by reference — no spine Vec. + fn nat_offset_candidate(&self, e: &KExpr) -> bool { + let mut cur = e; + loop { + match cur.data() { + ExprData::Nat(..) => return true, + ExprData::Const(id, _, _) => { + let p = &self.prims; + return id.addr == p.nat_zero.addr + || id.addr == p.nat_succ.addr + || id.addr == p.nat_add.addr; + }, + ExprData::App(f, _, _) => cur = f, + _ => return false, + } + } + } + /// If expression is nat-succ, return the predecessor. /// Matches both `Nat(n+1)` → `Nat(n)` and `Nat.succ e` → `e`. fn nat_succ_of(&mut self, e: &KExpr) -> Option> { @@ -933,8 +963,8 @@ impl TypeChecker<'_, M> { if v.0 == num_bigint::BigUint::ZERO { return None; } - let pred = lean_ffi::nat::Nat(&v.0 - num_bigint::BigUint::from(1u64)); - let pred_addr = crate::ix::address::Address::hash(&pred.to_le_bytes()); + 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() { @@ -970,8 +1000,16 @@ impl TypeChecker<'_, M> { } } - /// M2: Nat offset reduction for lazy delta loop (lean4lean isDefEqOffset). - /// Returns Some(true/false) if both are nat-zero or nat-succ, None otherwise. + /// M2: Nat offset reduction for lazy delta loop (lean4lean isDefEqOffset), + /// generalized to offset form: each side decomposes to `base + offset` + /// (`Lit n`, `succ` layers, and `Nat.add base (Lit m)` — the compact stuck + /// form WHNF now leaves — all read in O(1) per layer), the shared offset + /// is stripped in ONE step, and the remainders compare through full + /// def-eq. This collapses `succ^k(x) ≟ succ^k(x)` from k `is_def_eq` + /// recursion levels (which blew `MAX_DEF_EQ_DEPTH` for large k) to one. + /// Stripping is verdict-preserving: `+k` is definitionally injective, the + /// same semantics the previous one-succ-peel already relied on. + /// Non-offset shapes fall back (`None`) to the generic path unchanged. fn try_def_eq_offset( &mut self, a: &KExpr, @@ -986,12 +1024,27 @@ impl TypeChecker<'_, M> { if self.is_nat_zero(a) && self.is_nat_zero(b) { return Ok(Some(true)); } - match (self.nat_succ_of(a), self.nat_succ_of(b)) { - (Some(a_pred), Some(b_pred)) => { - Ok(Some(self.is_def_eq(&a_pred, &b_pred)?)) - }, - _ => Ok(None), + // Allocation-free quick reject: decompose walks app spines, so only run + // it when both heads are plausibly offset-shaped (the old one-succ-peel + // rejected non-Nat shapes in O(1) off `e.data()` — keep that property). + if !self.nat_offset_candidate(a) || !self.nat_offset_candidate(b) { + return Ok(None); } + let Some((base_a, ka)) = self.nat_offset_decompose(a)? else { + return Ok(None); + }; + let Some((base_b, kb)) = self.nat_offset_decompose(b)? else { + return Ok(None); + }; + let k = ka.0.clone().min(kb.0.clone()); + if k == num_bigint::BigUint::ZERO { + // No shared offset to strip (e.g. literal 0 vs offset-shaped): defer + // to the generic path, matching the previous conservative behavior. + return Ok(None); + } + let ra = self.nat_offset_rebuild(base_a, bignat::Nat(ka.0 - &k)); + let rb = self.nat_offset_rebuild(base_b, bignat::Nat(kb.0 - &k)); + Ok(Some(self.is_def_eq(&ra, &rb)?)) } // ----------------------------------------------------------------------- @@ -1047,8 +1100,8 @@ impl TypeChecker<'_, M> { // Build list right-to-left: foldr let mut list = nil; for c in s.chars().rev() { - let nat_val = lean_ffi::nat::Nat::from(c as u64); - let nat_addr = crate::ix::address::Address::hash(&nat_val.to_le_bytes()); + 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)); @@ -1093,7 +1146,7 @@ impl TypeChecker<'_, M> { // 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(crate::ix::env::Name::anon()))); + 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(name, bi, ty, body)); self.is_def_eq(t, &s_lam) @@ -1307,7 +1360,7 @@ impl TypeChecker<'_, M> { /// Check if a constant has Regular reducibility hints (not Abbrev or Opaque). /// Used to guard the same-head-spine optimization (lean4lean: dt.hints.isRegular). fn is_regular(&mut self, id: &KId) -> Result> { - use crate::ix::env::ReducibilityHints; + use ix_common::env::ReducibilityHints; Ok(matches!( self.try_get_const(id)?, Some(KConst::Defn { hints: ReducibilityHints::Regular(_), .. }) @@ -1329,7 +1382,7 @@ impl TypeChecker<'_, M> { /// - `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 crate::ix::env::ReducibilityHints; + use ix_common::env::ReducibilityHints; Ok(match self.try_get_const(id)? { Some(KConst::Defn { kind, hints, .. }) => match kind { DefKind::Opaque | DefKind::Theorem => (0, 0), @@ -1522,7 +1575,7 @@ impl TypeChecker<'_, M> { if !filter.is_empty() && !id_s.contains(filter) { return; } - eprintln!( + log::info!( "[proj-delta] const={} depth={} phase={} proj={}.{} a={} b={}", self.debug_label.as_deref().unwrap_or(""), self.def_eq_depth, @@ -1566,7 +1619,7 @@ impl TypeChecker<'_, M> { if !filter.is_empty() && !id_s.contains(filter) { return; } - eprintln!( + log::info!( "[eta] const={} depth={} reason={} id={} idx={} a={} b={}", self.debug_label.as_deref().unwrap_or(""), self.def_eq_depth, @@ -1635,12 +1688,12 @@ fn compact_def_eq_head(e: &KExpr) -> String { } fn short_def_eq_addr(e: &KExpr) -> String { - e.addr().to_hex().chars().take(12).collect() + format!("uid{}", e.addr()) } -/// Canonical ordering for cache keys: (min, max) by hash bytes. +/// Canonical ordering for cache keys: (min, max) by uid. fn canonical_pair(a: Addr, b: Addr) -> (Addr, Addr) { - if a.as_bytes() <= b.as_bytes() { (a, b) } else { (b, a) } + if a <= b { (a, b) } else { (b, a) } } /// Extract head constant KId from expression or app spine. @@ -1696,17 +1749,21 @@ impl TypeChecker<'_, M> { { return; } - eprintln!( + log::info!( "[deq max] {kind} depth={} a_head={} b_head={} wa_head={} wb_head={}", - self.def_eq_depth, a_head, b_head, wa_head, wb_head + self.def_eq_depth, + a_head, + b_head, + wa_head, + wb_head ); - eprintln!(" a: {a}"); - eprintln!(" b: {b}"); + log::info!(" a: {a}"); + log::info!(" b: {b}"); if let Some(wa) = wa { - eprintln!(" wa: {wa}"); + log::info!(" wa: {wa}"); } if let Some(wb) = wb { - eprintln!(" wb: {wb}"); + log::info!(" wb: {wb}"); } } @@ -1725,7 +1782,7 @@ impl TypeChecker<'_, M> { { return; } - eprintln!( + log::info!( "[deq max] rec-fuel depth={} a={} b={}", self.def_eq_depth, compact_def_eq_expr(a), @@ -1744,9 +1801,9 @@ mod tests { use super::super::level::KUniv; use super::super::mode::{Anon, Meta}; use super::super::tc::TypeChecker; - use crate::ix::address::Address; - use crate::ix::env::{DataValue, DefinitionSafety, Name, ReducibilityHints}; - use crate::ix::ixon::constant::DefKind; + use ix_common::address::Address; + use ix_common::env::{DataValue, DefinitionSafety, Name, ReducibilityHints}; + use ixon::constant::DefKind; type AE = KExpr; type ME = KExpr; @@ -1901,6 +1958,10 @@ mod tests { ); let plain = ME::cnst(id, Box::new([])); + // Interning ignores mdata (the shallow Const key carries only the + // address + universe uids), so both collapse to one canonical node. + let tagged = tc.intern(tagged); + let plain = tc.intern(plain); assert_eq!(tagged.addr(), plain.addr()); assert!(tc.is_def_eq(&tagged, &plain).unwrap()); } diff --git a/src/ix/kernel/env.rs b/crates/kernel/src/env.rs similarity index 67% rename from src/ix/kernel/env.rs rename to crates/kernel/src/env.rs index 1fdda019..a1e3d5a8 100644 --- a/src/ix/kernel/env.rs +++ b/crates/kernel/src/env.rs @@ -11,7 +11,7 @@ use std::collections::BTreeSet; use rustc_hash::{FxHashMap, FxHashSet}; use std::cell::OnceCell; -use crate::ix::address::Address; +use ix_common::address::Address; use super::constant::{KConst, RecRule}; use super::error::TcError; @@ -22,27 +22,69 @@ use super::mode::KernelMode; use super::perf::PerfCounters; use super::primitive::Primitives; -/// Content-addressed Merkle hash. 32 bytes, `Copy`, no allocation. +/// Canonical identity of an expression or universe node: the +/// intern-assigned uid. Plain `u64`, allocated from a process-global +/// counter (`expr.rs::fresh_uid`) and NEVER reused, so uid equality +/// implies structural equality and cache keys built from uids cannot +/// alias across intern-table clears (a stale key can only miss). /// -/// Earlier revisions stored `Addr = Arc` and threaded all -/// constructions through a process-global `DashMap` intern table to dedup -/// the inner allocation. On full-mathlib kernel-check runs that table grew -/// to 100M+ entries (≈8+ GiB) and dominated RSS, even though the per-worker -/// `KEnv` caches were correctly cleared per scheduled block. Switching to a -/// `Copy` value drops the global intern, eliminates one allocation per -/// `KExpr`/`KUniv` construction, and reduces per-`ExprData` overhead -/// from `Arc` (8-byte pointer + 16-byte heap header + 32-byte -/// Hash) to a single in-place 32-byte field. Identity comparison falls -/// back from `Arc::ptr_eq` (single pointer compare) to a 32-byte memcmp, -/// which is a single AVX2 cycle on modern x86 and dominated by the -/// surrounding kernel work. -pub type Addr = blake3::Hash; +/// This is KERNEL-INTERNAL identity for ephemeral in-memory nodes — +/// distinct from the Ixon `Address` layer (blake3 over serialized +/// content) that constants/blobs carry into claims, Merkle roots, and +/// the proof store. Uids must never escape into a serialized artifact; +/// see `docs/kernel_identity.md` for the boundary rule and the +/// proof-carrying-code implications. +/// +/// Earlier revisions stored the blake3 content hash of every node here, +/// computed at construction: profiling on the Zisk guest put that hashing +/// (`app_hash` + the blake3 wrapper) at ~20% of guest cycles on +/// reduction-heavy constants. Identity is now assigned by the intern +/// table from shallow structural keys ([`ExprKey`]/[`UnivKey`]) instead +/// of computed from content. +pub type Addr = u64; + +/// Key type for local-context hashing (`tc.rs::ctx_addr_for_lbr`) and +/// other places that still need a collision-resistant digest over +/// variable-length content. Per-node expression identity uses [`Addr`]. +pub type CtxAddr = blake3::Hash; + +/// Shallow structural key of an expression node: the variant tag plus the +/// uids of its children and its semantic payload. Mirrors EXACTLY what the +/// historical per-node content hash covered — display names, binder info, +/// and mdata are excluded — so interning semantics are unchanged. Children +/// are identified by uid: within one table, equal keys ⇔ structurally +/// equal nodes (children canonical by induction). +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub enum ExprKey { + Var(u64), + FVar(u64), + Sort(Addr), + Const(Address, Box<[Addr]>), + App(Addr, Addr), + Lam(Addr, Addr), + All(Addr, Addr), + Let(Addr, Addr, Addr, bool), + Prj(Address, u64, Addr), + Nat(Address), + Str(Address), +} + +/// Shallow structural key of a universe node. `Param` display names are +/// excluded, mirroring the historical hash semantics. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum UnivKey { + Zero, + Succ(Addr), + Max(Addr, Addr), + IMax(Addr, Addr), + Param(u64), +} /// Hash-consing intern table for expressions and universes. /// -/// Single-threaded and owned by one `KEnv`. Guarantees pointer uniqueness -/// by blake3 hash within that environment: `ptr(a) == ptr(b)` iff -/// `hash(a) == hash(b)`. +/// Single-threaded and owned by one `KEnv`. Guarantees uid/pointer +/// uniqueness per shallow structural key within that environment: +/// `ptr(a) == ptr(b)` iff `key(a) == key(b)` for interned values. /// /// Also owns reusable scratch buffers used by `subst`, `simul_subst`, and /// `lift` to memoize content-addressed sub-traversals within a single @@ -52,8 +94,14 @@ pub type Addr = blake3::Hash; /// already passed for hash-consing eliminates the malloc/free churn while /// keeping the per-call invariant (caches are cleared on entry). pub struct InternTable { - pub(crate) univs: FxHashMap>, - pub(crate) exprs: FxHashMap>, + pub(crate) univs: FxHashMap>, + pub(crate) exprs: FxHashMap>, + /// Uids of canonical (interned) nodes. `intern_expr`/`intern_univ` + /// short-circuit on members, and use it to detect non-canonical + /// children that must be canonicalized before the shallow key is + /// meaningful. + pub(crate) canon_exprs: FxHashSet, + pub(crate) canon_univs: FxHashSet, /// Scratch buffer for `subst` / `simul_subst` per-call memoization, /// keyed by `(addr, depth)`. Cleared on entry. Owned here so the /// allocation persists across calls. @@ -63,6 +111,14 @@ pub struct InternTable { /// because `lift` is invoked from inside `subst_cached`, and the two /// caches have different semantics, so they must not share entries. pub(crate) lift_scratch: FxHashMap<(Addr, u64), KExpr>, + /// Pool of scratch maps for `clo_subst` per-call memoization, keyed by + /// `(addr, depth)`. A pool rather than a single buffer because + /// `clo_subst` re-enters itself through `clo_readback` of environment + /// entries (each readback substitutes under a *different* environment, + /// so the memo must not be shared between the nesting levels). Maps are + /// cleared and returned to the pool on exit, so allocations persist + /// across calls like the other scratches. + pub(crate) clo_scratch_pool: Vec>>, } impl Default for InternTable { @@ -71,50 +127,201 @@ impl Default for InternTable { } } +/// Shallow structural key of an expression whose children are canonical. +pub fn expr_key(e: &KExpr) -> ExprKey { + use super::expr::ExprData; + match e.data() { + ExprData::Var(i, _, _) => ExprKey::Var(*i), + ExprData::FVar(id, _, _) => ExprKey::FVar(id.0), + ExprData::Sort(u, _) => ExprKey::Sort(*u.addr()), + ExprData::Const(id, us, _) => { + ExprKey::Const(id.addr.clone(), us.iter().map(|u| *u.addr()).collect()) + }, + ExprData::App(f, a, _) => ExprKey::App(*f.addr(), *a.addr()), + ExprData::Lam(_, _, t, b, _) => ExprKey::Lam(*t.addr(), *b.addr()), + ExprData::All(_, _, t, b, _) => ExprKey::All(*t.addr(), *b.addr()), + ExprData::Let(_, t, v, b, nd, _) => { + ExprKey::Let(*t.addr(), *v.addr(), *b.addr(), *nd) + }, + ExprData::Prj(id, f, v, _) => ExprKey::Prj(id.addr.clone(), *f, *v.addr()), + ExprData::Nat(_, ba, _) => ExprKey::Nat(ba.clone()), + ExprData::Str(_, ba, _) => ExprKey::Str(ba.clone()), + } +} + +/// Shallow structural key of a universe whose children are canonical. +pub fn univ_key(u: &KUniv) -> UnivKey { + use super::level::UnivData; + match u.data() { + UnivData::Zero(_) => UnivKey::Zero, + UnivData::Succ(inner, _) => UnivKey::Succ(*inner.addr()), + UnivData::Max(a, b, _) => UnivKey::Max(*a.addr(), *b.addr()), + UnivData::IMax(a, b, _) => UnivKey::IMax(*a.addr(), *b.addr()), + UnivData::Param(idx, _, _) => UnivKey::Param(*idx), + } +} + impl InternTable { pub fn new() -> Self { InternTable { univs: FxHashMap::default(), exprs: FxHashMap::default(), + canon_exprs: FxHashSet::default(), + canon_univs: FxHashSet::default(), subst_scratch: FxHashMap::default(), lift_scratch: FxHashMap::default(), + clo_scratch_pool: Vec::new(), } } /// Read-only fast path: return the canonical interned universe for - /// `hash` if already present. Used by instrumented callers that want - /// to record hit/miss separately; plain callers should use - /// `intern_univ`. + /// `key` if already present. The key must be built from CANONICAL + /// children (their uids). Plain callers should use `intern_univ`. #[inline] - pub fn try_get_univ(&self, hash: &blake3::Hash) -> Option> { - self.univs.get(hash).cloned() + pub fn try_get_univ(&self, key: &UnivKey) -> Option> { + self.univs.get(key).cloned() } /// Read-only fast path counterpart of `try_get_univ` for expressions. #[inline] - pub fn try_get_expr(&self, hash: &blake3::Hash) -> Option> { - self.exprs.get(hash).cloned() + pub fn try_get_expr(&self, key: &ExprKey) -> Option> { + self.exprs.get(key).cloned() } - /// Intern a universe: if one with the same hash exists, return the - /// existing Arc (ensuring pointer uniqueness). Otherwise insert and - /// return. + /// Intern a universe: returns the canonical value for its structural + /// identity, recursively canonicalizing children as needed so the + /// shallow key is meaningful. pub fn intern_univ(&mut self, u: KUniv) -> KUniv { - let key = *u.addr(); + use super::level::UnivData; + if self.canon_univs.contains(u.addr()) { + return u; + } + // Canonicalize children first; rebuild only if any child changed. + let u = match u.data() { + UnivData::Succ(inner, _) => { + let ci = self.intern_univ(inner.clone()); + if ci.ptr_eq(inner) { + u + } else { + KUniv::new(UnivData::Succ(ci, super::expr::fresh_uid())) + } + }, + UnivData::Max(a, b, _) => { + let ca = self.intern_univ(a.clone()); + let cb = self.intern_univ(b.clone()); + if ca.ptr_eq(a) && cb.ptr_eq(b) { + u + } else { + KUniv::new(UnivData::Max(ca, cb, super::expr::fresh_uid())) + } + }, + UnivData::IMax(a, b, _) => { + let ca = self.intern_univ(a.clone()); + let cb = self.intern_univ(b.clone()); + if ca.ptr_eq(a) && cb.ptr_eq(b) { + u + } else { + KUniv::new(UnivData::IMax(ca, cb, super::expr::fresh_uid())) + } + }, + UnivData::Zero(_) | UnivData::Param(..) => u, + }; + let key = univ_key(&u); if let Some(existing) = self.univs.get(&key) { return existing.clone(); } - self.univs.entry(key).or_insert(u).clone() + self.canon_univs.insert(*u.addr()); + self.univs.insert(key, u.clone()); + u } - /// Intern an expression: same pointer-uniqueness guarantee as - /// `intern_univ`. + /// Intern an expression: returns the canonical value for its structural + /// identity. Children are canonicalized recursively when needed (a node + /// built outside the table has non-canonical children whose uids would + /// make the shallow key meaningless), preserving the historical + /// content-hash interning semantics. pub fn intern_expr(&mut self, e: KExpr) -> KExpr { - let key = *e.addr(); + use super::expr::ExprData; + if self.canon_exprs.contains(e.addr()) { + return e; + } + let e = match e.data() { + ExprData::Sort(un, _) => { + let cu = self.intern_univ(un.clone()); + if cu.ptr_eq(un) { e } else { KExpr::sort_mdata(cu, e.mdata().clone()) } + }, + ExprData::Const(id, us, _) => { + let cus: Box<[KUniv]> = + us.iter().map(|un| self.intern_univ(un.clone())).collect(); + if cus.iter().zip(us.iter()).all(|(a, b)| a.ptr_eq(b)) { + e + } else { + KExpr::cnst_mdata(id.clone(), cus, e.mdata().clone()) + } + }, + ExprData::App(f, a, _) => { + let cf = self.intern_expr(f.clone()); + let ca = self.intern_expr(a.clone()); + if cf.ptr_eq(f) && ca.ptr_eq(a) { + e + } else { + KExpr::app_mdata(cf, ca, e.mdata().clone()) + } + }, + ExprData::Lam(n, bi, t, b, _) => { + let ct = self.intern_expr(t.clone()); + let cb = self.intern_expr(b.clone()); + if ct.ptr_eq(t) && cb.ptr_eq(b) { + e + } else { + KExpr::lam_mdata(n.clone(), bi.clone(), ct, cb, e.mdata().clone()) + } + }, + ExprData::All(n, bi, t, b, _) => { + let ct = self.intern_expr(t.clone()); + let cb = self.intern_expr(b.clone()); + if ct.ptr_eq(t) && cb.ptr_eq(b) { + e + } else { + KExpr::all_mdata(n.clone(), bi.clone(), ct, cb, e.mdata().clone()) + } + }, + ExprData::Let(n, t, v, b, nd, _) => { + let ct = self.intern_expr(t.clone()); + let cv = self.intern_expr(v.clone()); + let cb = self.intern_expr(b.clone()); + if ct.ptr_eq(t) && cv.ptr_eq(v) && cb.ptr_eq(b) { + e + } else { + KExpr::let_mdata(n.clone(), ct, cv, cb, *nd, e.mdata().clone()) + } + }, + ExprData::Prj(id, f, v, _) => { + let cv = self.intern_expr(v.clone()); + if cv.ptr_eq(v) { + e + } else { + KExpr::prj_mdata(id.clone(), *f, cv, e.mdata().clone()) + } + }, + ExprData::Var(..) + | ExprData::FVar(..) + | ExprData::Nat(..) + | ExprData::Str(..) => e, + }; + let key = expr_key(&e); if let Some(existing) = self.exprs.get(&key) { + // The shallow key (exact structural Eq over variant tag + child + // uids + payload — never a truncated or content-hashed key) plus + // canonical children make this hit structurally exact. Checked in + // debug builds; a violation here would be an interning bug, not + // an input an adversary can craft (uids are assigned, not hashed). + debug_assert!(existing == &e, "intern hit is not structurally equal"); return existing.clone(); } - self.exprs.entry(key).or_insert(e).clone() + self.canon_exprs.insert(*e.addr()); + self.exprs.insert(key, e.clone()); + e } } @@ -254,9 +461,9 @@ pub struct KEnv { // than `Arc::as_ptr` pointers, avoiding the ABA problem where deallocated // pointers are reused by the allocator for semantically different expressions. /// WHNF cache (full, with delta): (expr_hash, ctx_hash)-keyed. - pub whnf_cache: FxHashMap<(Addr, Addr), KExpr>, + pub whnf_cache: FxHashMap<(Addr, CtxAddr), KExpr>, /// WHNF cache (no delta): (expr_hash, ctx_hash)-keyed. - pub whnf_no_delta_cache: FxHashMap<(Addr, Addr), KExpr>, + pub whnf_no_delta_cache: FxHashMap<(Addr, CtxAddr), KExpr>, /// Cheap-mode WHNF cache (no delta, DEF_EQ_CORE flags): same key shape as /// `whnf_no_delta_cache`, but populated by cheap-projection callers in the /// def-eq lazy-delta loop. Cheap output is NOT shared with full callers @@ -265,41 +472,41 @@ pub struct KEnv { /// gated to cheap-mode callers only — mirrors the `def_eq_cheap_cache` /// pattern. Without this, every iteration of the lazy-delta loop redoes /// `whnf_no_delta_for_def_eq` from scratch (mathlib hot path). - pub whnf_no_delta_cheap_cache: FxHashMap<(Addr, Addr), KExpr>, + pub whnf_no_delta_cheap_cache: FxHashMap<(Addr, CtxAddr), KExpr>, /// WHNF core cache: structural-only reduction (beta/iota/zeta/proj), /// no native primitives, no delta. Mirrors lean4lean's `whnfCoreCache` /// (refs/lean4lean/Lean4Lean/TypeChecker.lean:19) and lean4 C++'s /// `m_whnf_core`. Populated only when flags are FULL — cheap-projection /// results are not safe to share with full callers. - pub whnf_core_cache: FxHashMap<(Addr, Addr), KExpr>, + pub whnf_core_cache: FxHashMap<(Addr, CtxAddr), KExpr>, /// Cheap-mode WHNF core cache: same key shape as `whnf_core_cache`, but /// populated by cheap-projection callers (DEF_EQ_CORE flags) inside the /// def-eq lazy-delta loop. Same soundness reasoning as /// `whnf_no_delta_cheap_cache` — cheap output stays in its own pool so /// full callers always see a properly-reduced result. - pub whnf_core_cheap_cache: FxHashMap<(Addr, Addr), KExpr>, + pub whnf_core_cheap_cache: FxHashMap<(Addr, CtxAddr), KExpr>, /// Infer cache: keyed by (expr_hash, ctx_hash). Context-dependent. /// Populated only from full-mode `infer` (i.e. not from `with_infer_only`), /// so every cached result has passed the validation `infer_only` skips. /// Both modes read from this same cache — an `infer_only` lookup happily /// consumes a full-mode result since it's strictly stronger. - pub infer_cache: FxHashMap<(Addr, Addr), KExpr>, + pub infer_cache: FxHashMap<(Addr, CtxAddr), KExpr>, /// Infer-only cache: keyed like `infer_cache`, but populated only by /// `with_infer_only` synthesis and read only while infer-only is active. /// This keeps unchecked results out of the validated full-mode cache while /// still sharing repeated proof-irrelevance/projection probes. - pub infer_only_cache: FxHashMap<(Addr, Addr), KExpr>, + pub infer_only_cache: FxHashMap<(Addr, CtxAddr), KExpr>, /// Full def-eq cache: keyed by (expr_hash, expr_hash, ctx_hash). /// Context-dependent. Entries in this cache are valid for both full and /// cheap def-eq callers. - pub def_eq_cache: FxHashMap<(Addr, Addr, Addr), bool>, + pub def_eq_cache: FxHashMap<(Addr, Addr, CtxAddr), bool>, /// Cheap def-eq cache: same key as `def_eq_cache`, but only for comparisons /// performed inside cheap projection reductions. Cheap `false` can be a /// full-mode false negative, so those entries must not be visible to full /// callers. - pub def_eq_cheap_cache: FxHashMap<(Addr, Addr, Addr), bool>, + pub def_eq_cheap_cache: FxHashMap<(Addr, Addr, CtxAddr), bool>, /// Failed def-eq pairs in lazy delta: canonical ordering by hash. - pub def_eq_failure: FxHashSet<(Addr, Addr, Addr)>, + pub def_eq_failure: FxHashSet<(Addr, Addr, CtxAddr)>, /// Constant-instantiation cache: caches the result of /// `instantiate_univ_params(val, us)` for each `Const(id, us)` head encountered /// during delta unfolding. Keyed by the head expression's content hash, which @@ -307,10 +514,18 @@ pub struct KEnv { /// 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<(Addr, Addr), KExpr>, + pub ingress_cache: FxHashMap<(CtxAddr, CtxAddr), KExpr>, /// "Is this type Prop?" cache, keyed by (type_hash, ctx_hash). /// /// `try_proof_irrel` is called on essentially every `is_def_eq` @@ -322,7 +537,7 @@ pub struct KEnv { /// proof-irrelevance probe skip those three calls. Empirically this /// is the dominant cost on mathlib proof-heavy blocks, where the same /// propositions are tested for equality thousands of times. - pub is_prop_cache: FxHashMap<(Addr, Addr), bool>, + pub is_prop_cache: FxHashMap<(Addr, CtxAddr), bool>, /// Generated recursors, keyed by inductive Muts block id. pub recursor_cache: FxHashMap, Vec>>, /// Nested-auxiliary order expected by stored recursors in this environment. @@ -339,6 +554,12 @@ pub struct KEnv { /// so every member of a bad block reports the same structured failure. pub block_check_results: FxHashMap, Result<(), TcError>>, + /// Primitive-reducer family per head-constant address (see + /// `whnf.rs::PrimFamily`). Pure function of the address; memoized so + /// the WHNF loops classify each head with one map probe instead of a + /// ~30-address compare gauntlet across five recognizers. + pub prim_family_cache: FxHashMap, + /// Next free-variable id for checker-local binder openings. /// /// Type-checking caches live on `KEnv`, not on one `TypeChecker`, so FVar @@ -358,7 +579,7 @@ pub struct KEnv { /// 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, + pub profile_sink: Option, } impl Default for KEnv { @@ -375,7 +596,7 @@ impl Drop for KEnv { if super::perf::enabled() { let summary = self.perf.summary(); if !summary.is_empty() { - eprint!("{summary}"); + log::info!("{summary}"); } } } @@ -405,6 +626,7 @@ impl KEnv { 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(), recursor_cache: FxHashMap::default(), @@ -412,6 +634,7 @@ impl KEnv { rec_majors_cache: FxHashMap::default(), block_peer_agreement_cache: FxHashSet::default(), block_check_results: FxHashMap::default(), + prim_family_cache: FxHashMap::default(), next_fvar_id: 0, perf: PerfCounters::default(), profile_sink: None, @@ -502,11 +725,14 @@ impl KEnv { self.blocks.clear(); self.intern.univs.clear(); self.intern.exprs.clear(); + self.intern.canon_exprs.clear(); + self.intern.canon_univs.clear(); // Scratch buffers retain entries from the most recent subst/lift call; // emptying them releases the KExpr Arc references they hold so the // intern.exprs cleanup above can actually drop ExprData allocations. self.intern.subst_scratch.clear(); self.intern.lift_scratch.clear(); + self.intern.clo_scratch_pool.clear(); let _ = self.prims.take(); self.whnf_cache.clear(); self.whnf_no_delta_cache.clear(); @@ -519,12 +745,14 @@ impl KEnv { 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.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; } @@ -580,12 +808,14 @@ impl KEnv { 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.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; } @@ -610,6 +840,7 @@ impl KEnv { 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(); } } @@ -619,7 +850,7 @@ mod tests { use super::super::mode::Anon; use super::super::primitive::PrimAddrs; use super::*; - use crate::ix::address::Address; + use ix_common::address::Address; fn mk_addr(s: &str) -> Address { Address::hash(s.as_bytes()) diff --git a/src/ix/kernel/equiv.rs b/crates/kernel/src/equiv.rs similarity index 96% rename from src/ix/kernel/equiv.rs rename to crates/kernel/src/equiv.rs index aeec2727..2bde4d1c 100644 --- a/src/ix/kernel/equiv.rs +++ b/crates/kernel/src/equiv.rs @@ -6,10 +6,10 @@ use rustc_hash::FxHashMap; -use super::env::Addr; +use super::env::{Addr, CtxAddr}; /// Composite key: (expression content hash, context content hash). -pub type EqKey = (Addr, Addr); +pub type EqKey = (Addr, CtxAddr); /// Union-find structure for tracking definitional equality between /// (expr_hash, ctx_hash) pairs. @@ -141,13 +141,17 @@ mod tests { use super::*; fn addr(n: u64) -> Addr { + n + } + + fn ctx(n: u64) -> CtxAddr { blake3::hash(&n.to_le_bytes()) } #[test] fn test_basic_equiv() { let mut em = EquivManager::new(); - let zero = addr(0); + let zero = ctx(0); assert!(!em.is_equiv(&(addr(100), zero), &(addr(200), zero))); em.add_equiv((addr(100), zero), (addr(200), zero)); assert!(em.is_equiv(&(addr(100), zero), &(addr(200), zero))); @@ -157,7 +161,7 @@ mod tests { #[test] fn test_transitivity() { let mut em = EquivManager::new(); - let zero = addr(0); + let zero = ctx(0); em.add_equiv((addr(100), zero), (addr(200), zero)); em.add_equiv((addr(200), zero), (addr(300), zero)); assert!(em.is_equiv(&(addr(100), zero), &(addr(300), zero))); @@ -166,8 +170,8 @@ mod tests { #[test] fn test_context_isolation() { let mut em = EquivManager::new(); - let ctx1 = addr(1); - let ctx2 = addr(2); + let ctx1 = ctx(1); + let ctx2 = ctx(2); em.add_equiv((addr(100), ctx1), (addr(200), ctx1)); assert!(em.is_equiv(&(addr(100), ctx1), &(addr(200), ctx1))); assert!(!em.is_equiv(&(addr(100), ctx2), &(addr(200), ctx2))); diff --git a/src/ix/kernel/error.rs b/crates/kernel/src/error.rs similarity index 99% rename from src/ix/kernel/error.rs rename to crates/kernel/src/error.rs index 0d253436..44ad4852 100644 --- a/src/ix/kernel/error.rs +++ b/crates/kernel/src/error.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; -use crate::ix::address::Address; +use ix_common::address::Address; use super::expr::KExpr; use super::mode::KernelMode; diff --git a/src/ix/kernel/expr.rs b/crates/kernel/src/expr.rs similarity index 73% rename from src/ix/kernel/expr.rs rename to crates/kernel/src/expr.rs index 009bcf82..33a59916 100644 --- a/src/ix/kernel/expr.rs +++ b/crates/kernel/src/expr.rs @@ -6,12 +6,9 @@ use std::fmt; use std::sync::Arc; -use crate::ix::address::Address; -use crate::ix::env::{ - BinderInfo, DataValue, EALL, EAPP, EFVAR, ELAM, ELET, ENAT, EPRJ, EREF, - ESORT, ESTR, EVAR, Name, -}; -use lean_ffi::nat::Nat; +use bignat::Nat; +use ix_common::address::Address; +use ix_common::env::{BinderInfo, DataValue, Name}; use super::env::Addr; use super::id::KId; @@ -48,7 +45,7 @@ pub struct ExprInfo { } /// Per-`TypeChecker` unique identifier for a free variable. Generated by -/// [`crate::ix::kernel::lctx::NameGenerator`] and embedded into the blake3 +/// [`crate::lctx::NameGenerator`] and 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 @@ -69,7 +66,7 @@ pub enum ExprData { /// 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::ix::kernel::lctx::LocalContext`], not on the node. + /// the active [`crate::lctx::LocalContext`], not on the node. FVar(FVarId, M::MField, ExprInfo), Sort(KUniv, ExprInfo), Const(KId, Box<[KUniv]>, ExprInfo), @@ -138,8 +135,10 @@ impl KExpr { &self.info().mdata } - /// Content-addressed key for cache lookups. Returns the blake3 hash - /// by value — `Addr` is `Copy`, so this is a 32-byte memcpy. + /// Canonical identity key for cache lookups: the intern-assigned uid. + /// `Addr` is a plain `u64`, allocated process-globally and never reused, + /// so uid equality implies "same construction event or same intern-table + /// canonical value" — strictly, structural equality. pub fn hash_key(&self) -> Addr { *self.addr() } @@ -148,25 +147,73 @@ impl KExpr { Arc::ptr_eq(&self.0, &other.0) } - /// Content-addressed equality with a layered fast path. - /// - /// 1. `ptr_eq` on the outer `KExpr` Arc — fires when both sides - /// came through the [`InternTable`](super::env::InternTable). - /// 2. 32-byte Blake3 hash compare — sound on its own (collisions - /// require an adversarial preimage attack), and a single AVX2 - /// cycle on modern x86. Earlier revisions interposed an - /// `Arc::ptr_eq` fast path on a process-globally-interned `Addr`, - /// but that intern table dominated RSS at mathlib scale; the - /// pure-content compare keeps the same correctness with no - /// process-global state. + /// Canonical-identity equality: `ptr_eq` or equal intern uid. Sound in + /// the affirmative (equal uid ⇒ structurally equal); INCOMPLETE in the + /// negative — two structurally equal expressions built without sharing + /// an [`InternTable`](super::env::InternTable) carry distinct uids. + /// Callers that need a definitive verdict use `==` (structural with this + /// as the fast path); callers where a false negative merely costs a + /// cache miss or a def-eq fallback use this directly. pub fn hash_eq(&self, other: &KExpr) -> bool { self.ptr_eq(other) || self.addr() == other.addr() } + + /// Structural equality mirroring exactly what the intern identity + /// covers: display names, binder info, and mdata are NOT compared + /// (matching the historical content-hash semantics). Equal interned + /// subtrees prune at the uid fast path, so canonical-vs-canonical + /// comparison is O(1). + fn structural_eq(&self, other: &Self) -> bool { + if self.hash_eq(other) { + return true; + } + match (self.data(), other.data()) { + (ExprData::Var(i, _, _), ExprData::Var(j, _, _)) => i == j, + (ExprData::FVar(x, _, _), ExprData::FVar(y, _, _)) => x == y, + (ExprData::Sort(u, _), ExprData::Sort(v, _)) => u == v, + (ExprData::Const(id1, us1, _), ExprData::Const(id2, us2, _)) => { + id1.addr == id2.addr + && us1.len() == us2.len() + && us1.iter().zip(us2.iter()).all(|(a, b)| a == b) + }, + (ExprData::App(f1, a1, _), ExprData::App(f2, a2, _)) => { + f1.structural_eq(f2) && a1.structural_eq(a2) + }, + (ExprData::Lam(_, _, t1, b1, _), ExprData::Lam(_, _, t2, b2, _)) + | (ExprData::All(_, _, t1, b1, _), ExprData::All(_, _, t2, b2, _)) => { + t1.structural_eq(t2) && b1.structural_eq(b2) + }, + ( + ExprData::Let(_, t1, v1, b1, nd1, _), + ExprData::Let(_, t2, v2, b2, nd2, _), + ) => { + nd1 == nd2 + && t1.structural_eq(t2) + && v1.structural_eq(v2) + && b1.structural_eq(b2) + }, + (ExprData::Prj(id1, f1, v1, _), ExprData::Prj(id2, f2, v2, _)) => { + id1.addr == id2.addr && f1 == f2 && v1.structural_eq(v2) + }, + // Literals: the blob address IS the identity (mirroring the old + // content hash, which hashed only the address). The value conjunct + // is defense in depth: load-time blob verification makes it + // redundant, and if that invariant were ever violated it degrades + // to inequality — the conservative direction. + (ExprData::Nat(v1, ba1, _), ExprData::Nat(v2, ba2, _)) => { + ba1 == ba2 && v1 == v2 + }, + (ExprData::Str(v1, ba1, _), ExprData::Str(v2, ba2, _)) => { + ba1 == ba2 && v1 == v2 + }, + _ => false, + } + } } impl PartialEq for KExpr { fn eq(&self, other: &Self) -> bool { - self.hash_eq(other) + self.structural_eq(other) } } @@ -186,18 +233,40 @@ fn mk_info( ExprInfo { addr, lbr, count_0, has_fvars, mdata } } +/// Process-global uid allocator for expression/universe identity. +/// +/// Every constructed node takes a fresh uid, so uid equality means "same +/// construction event" — or, after [`InternTable`](super::env::InternTable) +/// hash-consing, "same canonical value". Uids are NEVER reused (the counter +/// is global, not per-table), so cache keys built from uids stay sound +/// across intern-table clears: a stale key can only miss, never alias. +/// +/// This replaces the per-node blake3 content hash: profiling on the Zisk +/// guest put `app_hash` + the blake3 wrapper at ~20% of cycles on +/// reduction-heavy constants, all of it spent computing identity that the +/// intern table can assign in one atomic increment. +static NEXT_UID: std::sync::atomic::AtomicU64 = + std::sync::atomic::AtomicU64::new(1); + +#[inline] +pub(crate) fn fresh_uid() -> Addr { + let uid = NEXT_UID.fetch_add(1, 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!(uid < u64::MAX, "kernel uid counter exhausted"); + uid +} + // ============================================================================= -// Hash-first interning: each `*_mdata` constructor is split into a -// hash-only function (no allocation) and a `*_mdata_with_addr` builder -// that takes a precomputed canonical [`Addr`]. The plain `*_mdata` form is -// kept as a convenience wrapper for callers that don't pre-check the -// intern table. -// -// Hot-path callers in `ingress.rs` use the split form so they can ask -// `InternTable::try_get_expr(&hash)` *before* paying the -// blake3-hash + `intern_addr` + `Arc` allocation cost — a -// significant win because >60% of constructed values are immediately -// discarded for an existing canonical Arc on the intern table. +// Constructors: each `*_mdata` form allocates a fresh uid; the +// `*_mdata_with_addr` builders remain for the intern table and callers +// that already hold a fresh uid. Structural identity (what used to be the +// content hash) is decided by `InternTable`'s shallow keys — see +// `env.rs::ExprKey` — which mirror what the historical hash functions +// covered: names, binder info, and mdata are excluded. // ============================================================================= impl KExpr { @@ -207,21 +276,7 @@ impl KExpr { /// Compute the content hash for [`KExpr::var_mdata`] without allocating. /// - /// `name` is descriptive metadata only and intentionally NOT hashed — - /// two `Var(i)` nodes with different display names are content-equal, - /// keeping hash equality alpha-invariant even in `Meta` mode. - pub fn var_hash( - idx: u64, - _name: &M::MField, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[EVAR]); - h.update(&idx.to_le_bytes()); - h.finalize() - } - - pub fn var_mdata_with_addr( + fn var_mdata_with_addr( idx: u64, name: M::MField, mdata: M::MField>, @@ -236,22 +291,7 @@ impl KExpr { Self::fvar_mdata(id, name, no_mdata::()) } - /// Compute the content hash for [`KExpr::fvar_mdata`] without allocating. - /// Includes the [`FVarId`] so distinct fvars produce distinct hashes — the - /// soundness lever for keying caches by expression alone. `name` is - /// descriptive only and intentionally NOT hashed. - pub fn fvar_hash( - id: FVarId, - _name: &M::MField, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[EFVAR]); - h.update(&id.0.to_le_bytes()); - h.finalize() - } - - pub fn fvar_mdata_with_addr( + fn fvar_mdata_with_addr( id: FVarId, name: M::MField, mdata: M::MField>, @@ -268,7 +308,7 @@ impl KExpr { name: M::MField, mdata: M::MField>, ) -> Self { - let addr = Self::fvar_hash(id, &name, &mdata); + let addr = fresh_uid(); Self::fvar_mdata_with_addr(id, name, mdata, addr) } @@ -277,7 +317,7 @@ impl KExpr { name: M::MField, mdata: M::MField>, ) -> Self { - let addr = Self::var_hash(idx, &name, &mdata); + let addr = fresh_uid(); Self::var_mdata_with_addr(idx, name, mdata, addr) } @@ -285,17 +325,7 @@ impl KExpr { Self::sort_mdata(u, no_mdata::()) } - pub fn sort_hash( - u: &KUniv, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[ESORT]); - h.update(u.addr().as_bytes()); - h.finalize() - } - - pub fn sort_mdata_with_addr( + fn sort_mdata_with_addr( u: KUniv, mdata: M::MField>, addr: Addr, @@ -304,7 +334,7 @@ impl KExpr { } pub fn sort_mdata(u: KUniv, mdata: M::MField>) -> Self { - let addr = Self::sort_hash(&u, &mdata); + let addr = fresh_uid(); Self::sort_mdata_with_addr(u, mdata, addr) } @@ -312,25 +342,7 @@ impl KExpr { Self::cnst_mdata(id, univs, no_mdata::()) } - /// `id.addr` is the constant's content-address — its identity. The - /// `id.name` field is display-only metadata, intentionally NOT hashed, - /// so two references to the same address with different display names - /// remain content-equal. - pub fn cnst_hash( - id: &KId, - univs: &[KUniv], - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[EREF]); - h.update(id.addr.as_bytes()); - for u in univs.iter() { - h.update(u.addr().as_bytes()); - } - h.finalize() - } - - pub fn cnst_mdata_with_addr( + fn cnst_mdata_with_addr( id: KId, univs: Box<[KUniv]>, mdata: M::MField>, @@ -348,7 +360,7 @@ impl KExpr { univs: Box<[KUniv]>, mdata: M::MField>, ) -> Self { - let addr = Self::cnst_hash(&id, &univs, &mdata); + let addr = fresh_uid(); Self::cnst_mdata_with_addr(id, univs, mdata, addr) } @@ -356,19 +368,7 @@ impl KExpr { Self::app_mdata(f, a, no_mdata::()) } - pub fn app_hash( - f: &KExpr, - a: &KExpr, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[EAPP]); - h.update(f.addr().as_bytes()); - h.update(a.addr().as_bytes()); - h.finalize() - } - - pub fn app_mdata_with_addr( + fn app_mdata_with_addr( f: KExpr, a: KExpr, mdata: M::MField>, @@ -389,7 +389,7 @@ impl KExpr { a: KExpr, mdata: M::MField>, ) -> Self { - let addr = Self::app_hash(&f, &a, &mdata); + let addr = fresh_uid(); Self::app_mdata_with_addr(f, a, mdata, addr) } @@ -404,26 +404,7 @@ impl KExpr { /// Compute the content hash for [`KExpr::lam_mdata`]. /// - /// Binder `name` and `bi` are display/elaboration metadata only and are - /// intentionally NOT hashed. The kernel does not distinguish lambdas - /// that differ only in binder name or binder info; this keeps hash - /// equality structural and alpha-invariant in `Meta` mode (matching - /// `Anon` mode where these fields are erased). - pub fn lam_hash( - _name: &M::MField, - _bi: &M::MField, - ty: &KExpr, - body: &KExpr, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[ELAM]); - h.update(ty.addr().as_bytes()); - h.update(body.addr().as_bytes()); - h.finalize() - } - - pub fn lam_mdata_with_addr( + fn lam_mdata_with_addr( name: M::MField, bi: M::MField, ty: KExpr, @@ -448,7 +429,7 @@ impl KExpr { body: KExpr, mdata: M::MField>, ) -> Self { - let addr = Self::lam_hash(&name, &bi, &ty, &body, &mdata); + let addr = fresh_uid(); Self::lam_mdata_with_addr(name, bi, ty, body, mdata, addr) } @@ -461,22 +442,7 @@ impl KExpr { Self::all_mdata(name, bi, ty, body, no_mdata::()) } - /// See [`KExpr::lam_hash`] — binder `name`/`bi` intentionally not hashed. - pub fn all_hash( - _name: &M::MField, - _bi: &M::MField, - ty: &KExpr, - body: &KExpr, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[EALL]); - h.update(ty.addr().as_bytes()); - h.update(body.addr().as_bytes()); - h.finalize() - } - - pub fn all_mdata_with_addr( + fn all_mdata_with_addr( name: M::MField, bi: M::MField, ty: KExpr, @@ -501,7 +467,7 @@ impl KExpr { body: KExpr, mdata: M::MField>, ) -> Self { - let addr = Self::all_hash(&name, &bi, &ty, &body, &mdata); + let addr = fresh_uid(); Self::all_mdata_with_addr(name, bi, ty, body, mdata, addr) } @@ -515,28 +481,7 @@ impl KExpr { Self::let_mdata(name, ty, val, body, non_dep, no_mdata::()) } - /// See [`KExpr::lam_hash`] — binder `name` is intentionally not hashed. - /// `non_dep` IS hashed: dropping it would intern two letEs that differ only - /// in `non_dep` to the same KExpr, and egress would then return whichever - /// `non_dep` was interned first, breaking Ixon roundtrip fidelity. - pub fn let_hash( - _name: &M::MField, - ty: &KExpr, - val: &KExpr, - body: &KExpr, - non_dep: bool, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[ELET]); - h.update(ty.addr().as_bytes()); - h.update(val.addr().as_bytes()); - h.update(body.addr().as_bytes()); - h.update(&[u8::from(non_dep)]); - h.finalize() - } - - pub fn let_mdata_with_addr( + fn let_mdata_with_addr( name: M::MField, ty: KExpr, val: KExpr, @@ -563,7 +508,7 @@ impl KExpr { non_dep: bool, mdata: M::MField>, ) -> Self { - let addr = Self::let_hash(&name, &ty, &val, &body, non_dep, &mdata); + let addr = fresh_uid(); Self::let_mdata_with_addr(name, ty, val, body, non_dep, mdata, addr) } @@ -571,22 +516,7 @@ impl KExpr { Self::prj_mdata(id, field, val, no_mdata::()) } - /// `id.name` is display-only metadata, intentionally NOT hashed. - pub fn prj_hash( - id: &KId, - field: u64, - val: &KExpr, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[EPRJ]); - h.update(id.addr.as_bytes()); - h.update(&field.to_le_bytes()); - h.update(val.addr().as_bytes()); - h.finalize() - } - - pub fn prj_mdata_with_addr( + fn prj_mdata_with_addr( id: KId, field: u64, val: KExpr, @@ -604,7 +534,7 @@ impl KExpr { val: KExpr, mdata: M::MField>, ) -> Self { - let addr = Self::prj_hash(&id, field, &val, &mdata); + let addr = fresh_uid(); Self::prj_mdata_with_addr(id, field, val, mdata, addr) } @@ -612,17 +542,7 @@ impl KExpr { Self::nat_mdata(val, blob_addr, no_mdata::()) } - pub fn nat_hash( - blob_addr: &Address, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[ENAT]); - h.update(blob_addr.as_bytes()); - h.finalize() - } - - pub fn nat_mdata_with_addr( + fn nat_mdata_with_addr( val: Nat, blob_addr: Address, mdata: M::MField>, @@ -640,7 +560,7 @@ impl KExpr { blob_addr: Address, mdata: M::MField>, ) -> Self { - let addr = Self::nat_hash(&blob_addr, &mdata); + let addr = fresh_uid(); Self::nat_mdata_with_addr(val, blob_addr, mdata, addr) } @@ -648,17 +568,7 @@ impl KExpr { Self::str_mdata(val, blob_addr, no_mdata::()) } - pub fn str_hash( - blob_addr: &Address, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[ESTR]); - h.update(blob_addr.as_bytes()); - h.finalize() - } - - pub fn str_mdata_with_addr( + fn str_mdata_with_addr( val: String, blob_addr: Address, mdata: M::MField>, @@ -676,7 +586,7 @@ impl KExpr { blob_addr: Address, mdata: M::MField>, ) -> Self { - let addr = Self::str_hash(&blob_addr, &mdata); + let addr = fresh_uid(); Self::str_mdata_with_addr(val, blob_addr, mdata, addr) } } @@ -802,8 +712,8 @@ fn collect_spine(e: &KExpr) -> (KExpr, Vec>) { mod tests { use super::super::mode::{Anon, Meta}; use super::*; - use crate::ix::address::Address; - use crate::ix::env::BinderInfo; + use ix_common::address::Address; + use ix_common::env::BinderInfo; type ME = KExpr; type AE = KExpr; @@ -826,7 +736,9 @@ mod tests { #[test] fn var_hash_deterministic() { - assert_eq!(AE::var(0, ()).addr(), AE::var(0, ()).addr()); + // Identity is intern-assigned now: independently built nodes carry + // distinct uids but remain structurally equal (`==`). + assert_eq!(AE::var(0, ()), AE::var(0, ())); } #[test] @@ -839,10 +751,7 @@ mod tests { // Binder names are descriptive metadata only — they do NOT contribute // to the content hash, so two `Var(0)` nodes with different display // names are content-equal. Keeps hash equality alpha-invariant. - assert_eq!( - ME::var(0, mk_name("x")).addr(), - ME::var(0, mk_name("y")).addr() - ); + assert_eq!(ME::var(0, mk_name("x")), ME::var(0, mk_name("y"))); } #[test] @@ -867,7 +776,7 @@ mod tests { // their display names. let a = ME::cnst(KId::new(mk_addr("Nat"), mk_name("Nat")), Box::new([])); let b = ME::cnst(KId::new(mk_addr("Nat"), mk_name("Int")), Box::new([])); - assert_eq!(a.addr(), b.addr()); + assert_eq!(a, b); } #[test] @@ -894,7 +803,7 @@ mod tests { let a = ME::lam(mk_name("x"), BinderInfo::Default, ty.clone(), body.clone()); let b = ME::lam(mk_name("y"), BinderInfo::Default, ty, body); - assert_eq!(a.addr(), b.addr()); + assert_eq!(a, b); } #[test] @@ -907,7 +816,7 @@ mod tests { let a = ME::lam(mk_name("x"), BinderInfo::Default, ty.clone(), body.clone()); let b = ME::lam(mk_name("x"), BinderInfo::Implicit, ty, body); - assert_eq!(a.addr(), b.addr()); + assert_eq!(a, b); } #[test] diff --git a/src/ix/kernel/id.rs b/crates/kernel/src/id.rs similarity index 98% rename from src/ix/kernel/id.rs rename to crates/kernel/src/id.rs index 621efdf3..36316984 100644 --- a/src/ix/kernel/id.rs +++ b/crates/kernel/src/id.rs @@ -1,8 +1,8 @@ use std::fmt; use std::hash::{Hash, Hasher}; -use crate::ix::address::Address; -use crate::ix::env::Name; +use ix_common::address::Address; +use ix_common::env::Name; use super::mode::{KernelMode, MetaDisplay, MetaHash}; diff --git a/src/ix/kernel/inductive.rs b/crates/kernel/src/inductive.rs similarity index 98% rename from src/ix/kernel/inductive.rs rename to crates/kernel/src/inductive.rs index 4cba8c1c..9773d554 100644 --- a/src/ix/kernel/inductive.rs +++ b/crates/kernel/src/inductive.rs @@ -4,9 +4,7 @@ //! constraints, return types) and generates canonical recursors following //! lean4lean's constructive approach, then compares with provided recursors. -use std::sync::LazyLock; - -use crate::ix::address::Address; +use ix_common::address::Address; use super::constant::KConst; use super::env::{GeneratedRecursor, RecursorAuxOrder}; @@ -23,14 +21,14 @@ use super::tc::{TypeChecker, collect_app_spine, expr_mentions_any_addr}; /// 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: LazyLock = - LazyLock::new(|| std::env::var("IX_TYPE_DIFF").is_ok()); +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: LazyLock> = LazyLock::new(|| { - std::env::var("IX_RECURSOR_DUMP").ok().filter(|s| !s.is_empty()) +static IX_RECURSOR_DUMP: crate::EnvString = crate::EnvString::new(|| { + crate::env_var("IX_RECURSOR_DUMP").ok().filter(|s| !s.is_empty()) }); /// A member of the "flat" mutual block used for recursor generation. @@ -496,14 +494,14 @@ impl TypeChecker<'_, M> { n_rec_params: u64, univ_offset: u64, ) -> Result>, TcError> { - let anon = || M::meta_field(crate::ix::env::Name::anon()); + 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(); // (ext_ind_addr, spec_params content hashes) for dedup. // Uses [u8; 32] blake3 digest for structural equality. - let mut aux_seen: Vec<(Address, Vec<[u8; 32]>)> = Vec::new(); + let mut aux_seen: Vec<(Address, Vec>)> = Vec::new(); // Seed with original block inductives. for ind_id in block_inds { @@ -624,7 +622,7 @@ impl TypeChecker<'_, M> { dom: &KExpr, block_addrs: &[Address], flat: &mut Vec>, - aux_seen: &mut Vec<(Address, Vec<[u8; 32]>)>, + aux_seen: &mut Vec<(Address, Vec>)>, univ_offset: u64, param_depth: usize, // depth at the param context (before field locals) n_rec_params: u64, // number of inductive parameters (valid Var refs in spec_params) @@ -715,9 +713,9 @@ impl TypeChecker<'_, M> { } // Dedup: check if we've already seen this (ext_ind, spec_params) pair. - // Use blake3 content hash (addr) for structural dedup. - let spec_hashes: Vec<[u8; 32]> = - spec_params.iter().map(|e| *e.addr().as_bytes()).collect(); + // Structural comparison (uid fast path + recursive fallback) so + // equal-but-separately-built spec params still collapse. + let spec_hashes: Vec> = spec_params.clone(); if aux_seen.iter().any(|(a, s)| { *a == head_id.addr && s.len() == spec_hashes.len() @@ -919,7 +917,7 @@ impl TypeChecker<'_, M> { continue; } - let anon = || M::meta_field(crate::ix::env::Name::anon()); + let anon = || M::meta_field(ix_common::env::Name::anon()); let mut result = self.env.intern.intern_expr(KExpr::cnst( aux_ids[idx].clone(), block_us.to_vec().into_boxed_slice(), @@ -954,8 +952,8 @@ impl TypeChecker<'_, M> { n_block_params: u64, ) -> Result< Vec<( - M::MField, - M::MField, + M::MField, + M::MField, KExpr, )>, TcError, @@ -994,8 +992,8 @@ impl TypeChecker<'_, M> { &mut self, body: KExpr, binders: &[( - M::MField, - M::MField, + M::MField, + M::MField, KExpr, )], ) -> KExpr { @@ -1045,13 +1043,11 @@ impl TypeChecker<'_, M> { aux: &[FlatBlockMember], n_block_params: u64, block_us: &[KUniv], - all0_name: Option<&crate::ix::env::Name>, + all0_name: Option<&ix_common::env::Name>, block_first_id: Option<&KId>, ) -> Result, TcError> { - use crate::ix::env::Name; - use crate::ix::kernel::canonical_check::{ - KMutCtx, sort_kconsts_with_seed_key, - }; + use crate::canonical_check::{KMutCtx, sort_kconsts_with_seed_key}; + use ix_common::env::Name; use rustc_hash::FxHashMap; // Build synthetic Indc + Ctor views for each aux. @@ -1071,7 +1067,7 @@ impl TypeChecker<'_, M> { // the block's first inductive is unavailable, the wrap is empty (a no-op). let block_param_binders: Vec<( M::MField, - M::MField, + M::MField, KExpr, )> = match block_first_id { Some(id) if n_block_params > 0 => { @@ -1110,10 +1106,10 @@ impl TypeChecker<'_, M> { h.update(&(source_idx as u64).to_le_bytes()); h.update(member.id.addr.as_bytes()); for sp in &member.spec_params { - h.update(sp.addr().as_bytes()); + h.update(&sp.addr().to_le_bytes()); } for u in member.occurrence_us.iter() { - h.update(u.addr().as_bytes()); + h.update(&u.addr().to_le_bytes()); } let aux_addr = Address::from_blake3_hash(h.finalize()); let aux_id = KId::new(aux_addr.clone(), M::meta_field(seed_name.clone())); @@ -1309,7 +1305,7 @@ impl TypeChecker<'_, M> { }); if dump_canonical { - eprintln!( + log::info!( "[canonical_aux_order.dump] all0={:?} n_aux={} n_block_params={}", all0_name.map(Name::pretty), pairs.len(), @@ -1317,7 +1313,7 @@ impl TypeChecker<'_, M> { ); for (i, (kid, kconst)) in pairs.iter().enumerate() { let seed = aux_seed_names.get(i).cloned().unwrap_or_else(Name::anon); - eprintln!( + log::info!( " pre-sort[{}] addr={} seed={} member_id_addr={}", i, &kid.addr.hex()[..8], @@ -1325,12 +1321,12 @@ impl TypeChecker<'_, M> { &aux[i].id.addr.hex()[..8] ); if let KConst::Indc { ty, ctors, .. } = kconst { - eprintln!(" indc.ty={ty}"); + log::info!(" indc.ty={ty}"); for (ci, ctor_kid) in ctors.iter().enumerate() { if let Some(KConst::Ctor { ty, .. }) = all_ctor_lookup.get(&ctor_kid.addr) { - eprintln!(" ctor[{ci}].ty={ty}"); + log::info!(" ctor[{ci}].ty={ty}"); } } } @@ -1349,10 +1345,10 @@ impl TypeChecker<'_, M> { )?; if dump_canonical { - eprintln!("[canonical_aux_order.dump] post-sort classes:"); + log::info!("[canonical_aux_order.dump] post-sort classes:"); for (ci, class) in classes.iter().enumerate() { for (mi, (kid, _)) in class.iter().enumerate() { - eprintln!(" class[{ci}][{mi}] addr={}", &kid.addr.hex()[..8]); + log::info!(" class[{ci}][{mi}] addr={}", &kid.addr.hex()[..8]); } } } @@ -1411,7 +1407,7 @@ impl TypeChecker<'_, M> { if !self.recursor_dump_matches_block(block_id, flat) { return; } - eprintln!( + log::info!( "[recursor.dump] {label} flat aux order for {block_id}: originals={} aux={}", n_originals, flat.len().saturating_sub(n_originals) @@ -1419,9 +1415,11 @@ impl TypeChecker<'_, M> { for (aux_i, member) in flat.iter().skip(n_originals).enumerate() { let spec = member.spec_params.iter().map(|e| format!("{e}")).collect::>(); - eprintln!( + log::info!( " aux[{aux_i:2}] id={} own_params={} indices={} spec={spec:?}", - member.id, member.own_params, member.n_indices + member.id, + member.own_params, + member.n_indices ); } } @@ -1532,18 +1530,18 @@ impl TypeChecker<'_, M> { failed_gen_major: Option<&KExpr>, failed_stored_major: Option<&KExpr>, ) { - eprintln!( + log::info!( "[recursor.align] FAIL ind_block={ind_block_id} rec_block={rec_block_id} \ peers={} flat={} rec_ids={} failed_gi={failed_gi}", generated_snapshot.len(), flat.len(), rec_ids.len() ); - eprintln!( + log::info!( " failed gen major: {}", Self::major_domain_signature_text(failed_gen_major) ); - eprintln!( + log::info!( " failed stored major: {}", Self::major_domain_signature_text(failed_stored_major) ); @@ -1573,18 +1571,18 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", None => None, }; let mark = if gi == failed_gi { "!!" } else { " " }; - eprintln!( + log::info!( " {mark} peer[{gi:2}] flat.id={} target={}… aux={} ind={}…", flat[gi].id, &target_addr.hex()[..8], flat[gi].is_aux, &gen_rec.ind_addr.hex()[..8] ); - eprintln!( + log::info!( " gen : {}", Self::major_domain_signature_text(gen_major.as_ref()) ); - eprintln!( + log::info!( " sto : {} (rid={})", Self::major_domain_signature_text(stored_major.as_ref()), rid @@ -1603,9 +1601,9 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", return Ok(false); } if depth > 80 { - eprintln!("[rule rhs diff] first diff {path}: recursion limit"); - eprintln!(" gen: {lhs}"); - eprintln!(" sto: {rhs}"); + log::info!("[rule rhs diff] first diff {path}: recursion limit"); + log::info!(" gen: {lhs}"); + log::info!(" sto: {rhs}"); return Ok(true); } @@ -1621,9 +1619,9 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", ExprData::All(_, _, rty, rbody, _), ) => { if !self.is_def_eq(lty, rty)? { - eprintln!("[rule rhs diff] first diff {path}.dom"); - eprintln!(" gen: {lty}"); - eprintln!(" sto: {rty}"); + log::info!("[rule rhs diff] first diff {path}.dom"); + log::info!(" gen: {lty}"); + log::info!(" sto: {rty}"); return Ok(true); } let saved = self.lctx.len(); @@ -1651,9 +1649,9 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", self.dump_rule_rhs_first_diff(la, ra, &format!("{path}.arg"), depth + 1) }, _ => { - eprintln!("[rule rhs diff] first diff {path}"); - eprintln!(" gen: {lw}"); - eprintln!(" sto: {rw}"); + log::info!("[rule rhs diff] first diff {path}"); + log::info!(" gen: {lw}"); + log::info!(" sto: {rw}"); Ok(true) }, } @@ -2028,7 +2026,7 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", } for (i, u) in us.iter().enumerate() { let expected = - KUniv::param(i as u64, M::meta_field(crate::ix::env::Name::anon())); + 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!( @@ -2258,7 +2256,7 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", let is_large = self.is_large_eliminator(&result_level, &ind_infos)?; let univ_offset: u64 = if is_large { 1 } else { 0 }; let elim_level = if is_large { - KUniv::param(0, M::meta_field(crate::ix::env::Name::anon())) + KUniv::param(0, M::meta_field(ix_common::env::Name::anon())) } else { KUniv::zero() }; @@ -2295,7 +2293,7 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", block_first_id.as_ref(), )?; if self.recursor_dump_matches_block(block_id, &flat) { - eprintln!("[recursor.dump] canonical_order={canonical_order:?}"); + log::info!("[recursor.dump] canonical_order={canonical_order:?}"); } // Apply the permutation produced by sort_consts: each canonical // class index k maps to one representative aux from the original @@ -2395,7 +2393,7 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", let n_motives = flat.len() as u64; let n_minors: u64 = flat.iter().map(|m| m.ctors.len() as u64).sum(); let prefix_skip = n_params + n_motives + n_minors; - eprintln!( + log::info!( "[recursor.dump] generated recursors for {block_id}: count={} prefix_skip={prefix_skip}", generated.len() ); @@ -2405,7 +2403,7 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", prefix_skip, &g.ind_addr, )?; - eprintln!( + log::info!( " gen[{gi:2}] ind_addr={} {}", &g.ind_addr.hex()[..8], Self::major_domain_signature_text(major.as_ref()) @@ -2482,8 +2480,8 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", elim_level: &KUniv, _univ_offset: u64, ) -> Result, TcError> { - let anon = || M::meta_field(crate::ix::env::Name::anon()); - let bi_default = || M::meta_field(crate::ix::env::BinderInfo::Default); + 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). @@ -2616,8 +2614,8 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", }, }; let (ctor_ty_raw, _ctor_lvls) = ctor; - let anon = || M::meta_field(crate::ix::env::Name::anon()); - let bi_default = || M::meta_field(crate::ix::env::BinderInfo::Default); + 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). @@ -2829,8 +2827,8 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", field_domains: &[KExpr], block_addrs: &[Address], ) -> Result, TcError> { - let anon = || M::meta_field(crate::ix::env::Name::anon()); - let bi_default = || M::meta_field(crate::ix::env::BinderInfo::Default); + 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). @@ -3043,8 +3041,8 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", 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(crate::ix::env::Name::anon()); - let bi_default = || M::meta_field(crate::ix::env::BinderInfo::Default); + 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(); @@ -3212,7 +3210,7 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", fn mk_ind_univs(&mut self, ind_lvls: u64, offset: u64) -> Box<[KUniv]> { (0..ind_lvls) .map(|i| { - KUniv::param(i + offset, M::meta_field(crate::ix::env::Name::anon())) + KUniv::param(i + offset, M::meta_field(ix_common::env::Name::anon())) }) .collect::>() .into_iter() @@ -3584,8 +3582,8 @@ re-run with `IX_RECURSOR_DUMP={}` for the full breakdown.", is_large: bool, _univ_offset: u64, ) -> Result, TcError> { - let anon = || M::meta_field(crate::ix::env::Name::anon()); - let bi_default = || M::meta_field(crate::ix::env::BinderInfo::Default); + 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(), @@ -3852,8 +3850,8 @@ re-run with `IX_RECURSOR_DUMP={}` for the full breakdown.", is_large: bool, dom: &KExpr, ) -> Result, TcError> { - let anon = || M::meta_field(crate::ix::env::Name::anon()); - let bi_default = || M::meta_field(crate::ix::env::BinderInfo::Default); + 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)?; @@ -3872,7 +3870,7 @@ re-run with `IX_RECURSOR_DUMP={}` for the full breakdown.", }, }; let rec_lvls: Box<[KUniv]> = (0..peer_rec_lvls) - .map(|i| KUniv::param(i, M::meta_field(crate::ix::env::Name::anon()))) + .map(|i| KUniv::param(i, M::meta_field(ix_common::env::Name::anon()))) .collect(); // Peel foralls from the domain to detect wrapping. @@ -4218,15 +4216,17 @@ re-run with `IX_RECURSOR_DUMP={}` for the full breakdown.", .or_else(|| generated.iter().position(|g| g.ind_addr == ind_id.addr)); if self.recursor_dump_matches_id(id) { - eprintln!( + log::info!( "[recursor.dump] check {} rec_block={} resolved_block={} stored_pos={stored_pos:?} selected_idx={selected_idx:?}", - id, rec_block, resolved_block + id, + rec_block, + resolved_block ); - eprintln!( + log::info!( "[recursor.dump] stored major: {}", Self::major_domain_signature_text(stored_major.as_ref()) ); - eprintln!("[recursor.dump] signature_matches={signature_matches:?}"); + log::info!("[recursor.dump] signature_matches={signature_matches:?}"); for (gi, g) in generated.iter().enumerate() { if g.ind_addr != ind_id.addr { continue; @@ -4236,7 +4236,7 @@ re-run with `IX_RECURSOR_DUMP={}` for the full breakdown.", prefix_skip, &g.ind_addr, )?; - eprintln!( + log::info!( " cand[{gi:2}] {}", Self::major_domain_signature_text(major.as_ref()) ); @@ -4289,11 +4289,11 @@ re-run with `IX_RECURSOR_DUMP={}` for the full breakdown.", } else { "idx/major" }; - eprintln!( + log::info!( "[type diff] binder {bi} ({label}) DIFFERS (p={params} m={motives} min={minors})" ); - eprintln!(" gen: {gd}"); - eprintln!(" sto: {sd}"); + log::info!(" gen: {gd}"); + log::info!(" sto: {sd}"); break; } let _ = self.push_fvar_decl_anon(gd.clone()); @@ -4302,9 +4302,9 @@ re-run with `IX_RECURSOR_DUMP={}` for the full breakdown.", bi += 1; }, _ => { - eprintln!("[type diff] return differs at {bi}"); - eprintln!(" gen: {gc}"); - eprintln!(" sto: {sc}"); + log::info!("[type diff] return differs at {bi}"); + log::info!(" gen: {gc}"); + log::info!(" sto: {sc}"); break; }, } @@ -4380,12 +4380,12 @@ re-run with `IX_RECURSOR_DUMP={}` for the full breakdown.", "rhs", 0, ); - eprintln!( + log::info!( "[rule rhs diff] rule {ri} RHS mismatch (fields={})", gen_rule.fields ); - eprintln!(" gen: {}", gen_rule.rhs); - eprintln!(" sto: {}", stored_rule.rhs); + log::info!(" gen: {}", gen_rule.rhs); + log::info!(" sto: {}", stored_rule.rhs); } return Err(TcError::Other(format!( "check_recursor: rule {ri} RHS mismatch" @@ -4505,7 +4505,7 @@ mod tests { use super::super::level::KUniv; use super::super::mode::Anon; use super::super::tc::TypeChecker; - use crate::ix::address::Address; + use ix_common::address::Address; type AE = KExpr; type AU = KUniv; diff --git a/src/ix/kernel/infer.rs b/crates/kernel/src/infer.rs similarity index 92% rename from src/ix/kernel/infer.rs rename to crates/kernel/src/infer.rs index 95f0232b..cbe061c0 100644 --- a/src/ix/kernel/infer.rs +++ b/crates/kernel/src/infer.rs @@ -1,7 +1,5 @@ //! Type inference. -use std::sync::LazyLock; - use super::constant::KConst; use super::error::{TcError, u64_to_usize}; use super::expr::{ExprData, KExpr}; @@ -18,23 +16,23 @@ use super::tc::{TypeChecker, collect_app_spine}; /// 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: LazyLock = - LazyLock::new(|| std::env::var("IX_APP_DIFF").is_ok()); +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: LazyLock = - LazyLock::new(|| std::env::var("IX_INFER_APP_FORALL_DUMP").is_ok()); +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: LazyLock = - LazyLock::new(|| std::env::var("IX_INFER_COUNT_LOG").is_ok()); +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); @@ -44,7 +42,7 @@ impl TypeChecker<'_, M> { if *IX_INFER_COUNT_LOG { let n = INFER_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); if n.is_multiple_of(100_000) && n > 0 { - eprintln!("[infer] count={n}"); + log::info!("[infer] count={n}"); } } let infer_only = self.infer_only; @@ -113,27 +111,27 @@ impl TypeChecker<'_, M> { let f_ty = self.infer(f)?; let (dom, cod) = self.ensure_forall(&f_ty).inspect_err(|_err| { if *IX_INFER_APP_FORALL_DUMP && self.debug_label_matches_env() { - eprintln!("[infer App] ensure_forall FAILED"); - eprintln!( + log::info!("[infer App] ensure_forall FAILED"); + log::info!( " const: {}", self.debug_label.as_deref().unwrap_or("") ); - eprintln!(" f: {f}"); - eprintln!(" f_ty: {f_ty}"); - eprintln!(" f_ty addr: {:?}", f_ty.addr()); - eprintln!(" a: {a}"); + log::info!(" f: {f}"); + log::info!(" f_ty: {f_ty}"); + log::info!(" f_ty addr: {:?}", f_ty.addr()); + log::info!(" a: {a}"); if let ExprData::App(ff, fa, _) = f.data() { - eprintln!(" ff: {ff}"); - eprintln!(" ff addr: {:?}", ff.addr()); + log::info!(" ff: {ff}"); + log::info!(" ff addr: {:?}", ff.addr()); if let Ok(ff_ty) = self.infer(ff) { - eprintln!(" ff_ty: {ff_ty}"); - eprintln!(" ff_ty addr: {:?}", ff_ty.addr()); + log::info!(" ff_ty: {ff_ty}"); + log::info!(" ff_ty addr: {:?}", ff_ty.addr()); if let Ok((dom2, cod2)) = self.ensure_forall(&ff_ty) { - eprintln!(" ff_ty dom: {dom2}"); - eprintln!(" ff_ty cod: {cod2}"); + log::info!(" ff_ty dom: {dom2}"); + log::info!(" ff_ty cod: {cod2}"); } } - eprintln!(" fa: {fa}"); + log::info!(" fa: {fa}"); } } })?; @@ -156,31 +154,31 @@ impl TypeChecker<'_, M> { // strategy. let a_whnf = self.whnf(&a_ty); let d_whnf = self.whnf(&dom); - let depth = std::env::var("IX_APP_DIFF_DEPTH") + let depth = crate::env_var("IX_APP_DIFF_DEPTH") .ok() .and_then(|s| s.parse::().ok()) .unwrap_or(2); - eprintln!( + log::info!( "[app diff] AppTypeMismatch at depth={}", self.ctx.len() ); - eprintln!(" f: {}", compact_expr(f)); - eprintln!(" a: {}", compact_expr(a)); - eprintln!(" a_ty: {}", compact_expr_deep(&a_ty, depth)); - eprintln!(" dom: {}", compact_expr_deep(&dom, depth)); - eprintln!(" a_ty data: {:?}", a_ty.data()); - eprintln!(" dom data: {:?}", dom.data()); + log::info!(" f: {}", compact_expr(f)); + log::info!(" a: {}", compact_expr(a)); + log::info!(" a_ty: {}", compact_expr_deep(&a_ty, depth)); + log::info!(" dom: {}", compact_expr_deep(&dom, depth)); + log::info!(" a_ty data: {:?}", a_ty.data()); + log::info!(" dom data: {:?}", dom.data()); match &a_whnf { Ok(w) => { - eprintln!(" a_ty whnf: {}", compact_expr_deep(w, depth)) + log::info!(" a_ty whnf: {}", compact_expr_deep(w, depth)) }, - Err(e) => eprintln!(" a_ty whnf: ERR {e}"), + Err(e) => log::info!(" a_ty whnf: ERR {e}"), } match &d_whnf { Ok(w) => { - eprintln!(" dom whnf: {}", compact_expr_deep(w, depth)) + log::info!(" dom whnf: {}", compact_expr_deep(w, depth)) }, - Err(e) => eprintln!(" dom whnf: ERR {e}"), + Err(e) => log::info!(" dom whnf: ERR {e}"), } } return Err(TcError::AppTypeMismatch { @@ -229,8 +227,8 @@ impl TypeChecker<'_, M> { abstract_fvars(&mut self.env.intern, &body_ty, &[fv_id]); self.lctx.truncate(saved); self.intern(KExpr::all( - M::meta_field(crate::ix::env::Name::anon()), - M::meta_field(crate::ix::env::BinderInfo::Default), + M::meta_field(ix_common::env::Name::anon()), + M::meta_field(ix_common::env::BinderInfo::Default), ty.clone(), abstracted, )) @@ -242,15 +240,15 @@ impl TypeChecker<'_, M> { let saved = self.lctx.len(); let fv_id = self.fresh_fvar_id(); let fv = self.intern(KExpr::fvar(fv_id, name.clone())); - if std::env::var("IX_FVAR_TRACE").is_ok() { - eprintln!( + 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(), self.ctx.len(), body.lbr(), ); - eprintln!(" ty data: {:?}", ty.data()); + log::info!(" ty data: {:?}", ty.data()); } self.lctx.push( fv_id, @@ -316,8 +314,8 @@ impl TypeChecker<'_, M> { self.infer_proj(&struct_id, *field, val, &val_ty)? }, - ExprData::Nat(..) => self.infer_nat_type()?, - ExprData::Str(..) => self.infer_str_type()?, + ExprData::Nat(..) => self.infer_nat_type(), + ExprData::Str(..) => self.infer_str_type(), }; if !infer_only { @@ -477,12 +475,12 @@ impl TypeChecker<'_, M> { } } - fn infer_nat_type(&mut self) -> Result, TcError> { - Ok(self.intern(KExpr::cnst(self.prims.nat.clone(), Box::new([])))) + fn infer_nat_type(&mut self) -> KExpr { + self.intern(KExpr::cnst(self.prims.nat.clone(), Box::new([]))) } - fn infer_str_type(&mut self) -> Result, TcError> { - Ok(self.intern(KExpr::cnst(self.prims.string.clone(), Box::new([])))) + fn infer_str_type(&mut self) -> KExpr { + self.intern(KExpr::cnst(self.prims.string.clone(), Box::new([]))) } fn inductive_app_is_prop( @@ -600,7 +598,7 @@ fn compact_head(e: &KExpr) -> String { } fn short_addr(e: &KExpr) -> String { - e.addr().to_hex().chars().take(12).collect() + format!("uid{}", e.addr()) } #[cfg(test)] @@ -614,10 +612,10 @@ mod tests { use super::super::level::KUniv; use super::super::mode::Anon; use super::super::tc::TypeChecker; - use crate::ix::address::Address; - use crate::ix::env::{DefinitionSafety, ReducibilityHints}; - use crate::ix::ixon::constant::DefKind; - use lean_ffi::nat::Nat; + use bignat::Nat; + use ix_common::address::Address; + use ix_common::env::{DefinitionSafety, ReducibilityHints}; + use ixon::constant::DefKind; type AE = KExpr; type AU = KUniv; diff --git a/src/ix/kernel/ingress.rs b/crates/kernel/src/ingress.rs similarity index 91% rename from src/ix/kernel/ingress.rs rename to crates/kernel/src/ingress.rs index 38bedb6e..e3c16b63 100644 --- a/src/ix/kernel/ingress.rs +++ b/crates/kernel/src/ingress.rs @@ -6,10 +6,12 @@ //! to avoid stack overflow on deeply nested expressions. use std::cell::Cell; +#[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, }; @@ -17,25 +19,29 @@ use rustc_hash::{FxHashMap, FxHashSet}; use dashmap::DashMap; -use crate::ix::address::Address; -use crate::ix::env::{ - BinderInfo, ConstantInfo as LeanCI, DefinitionSafety, Env as LeanEnv, Name, - ReducibilityHints, +use bignat::Nat; +use ix_common::address::Address; +#[cfg(not(target_arch = "riscv64"))] +use ix_common::env::ConstantInfo as LeanCI; +#[cfg(not(target_arch = "riscv64"))] +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 crate::ix::ixon::constant::{ - Constant, ConstantInfo as IxonCI, DefKind, MutConst as IxonMutConst, -}; -use crate::ix::ixon::env::Env as IxonEnv; -use crate::ix::ixon::expr::Expr as IxonExpr; -use crate::ix::ixon::metadata::{ +use ixon::env::Env as IxonEnv; +use ixon::expr::Expr as IxonExpr; +use ixon::metadata::{ ConstantMeta, ConstantMetaInfo, ExprMeta, ExprMetaData, resolve_kvmap, }; -use crate::ix::ixon::univ::Univ as IxonUniv; -use crate::ix::kernel::env::Addr; -use lean_ffi::nat::Nat; +use ixon::univ::Univ as IxonUniv; use super::constant::{KConst, RecRule}; -use super::env::{InternTable, KEnv}; +use super::env::{CtxAddr, InternTable, KEnv}; use super::expr::{KExpr, MData}; use super::id::KId; use super::level::KUniv; @@ -250,7 +256,7 @@ fn timed_intern_univ( return intern.intern_univ(u); } let t0 = Instant::now(); - let key = *u.addr(); + let key = super::env::univ_key(&u); let result = if let Some(existing) = intern.try_get_univ(&key) { stats.intern_univ_get_hits += 1; existing @@ -262,56 +268,45 @@ fn timed_intern_univ( result } -/// Hash-first interning. Precomputes the content hash, asks the intern -/// table for an existing canonical KExpr; only on a miss does it call -/// `build(addr)` to allocate a new KExpr. -/// -/// Why this exists: profiling on Mathlib shows `kexpr_construct` (the -/// blake3 hash + `Arc` allocation pair) is ~45% of `convert` -/// worker-sum, of which ~62% is wasted because the intern table -/// already has the same canonical value. By computing just the hash up -/// front and skipping construction entirely on a hit, we avoid the -/// allocation in the majority case. -/// -/// The `build` closure receives the precomputed `Addr` (a `blake3::Hash` -/// by value) and is expected to call one of the -/// `KExpr::*_mdata_with_addr` constructors so it can plug the hash into -/// `ExprInfo` without re-hashing. +/// 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). /// -/// Stats accounting (when enabled): the hit path bumps -/// `intern_expr_get_hits`. The miss path also bumps `kexpr_construct_*` -/// for the cost of the closure body. `intern_expr_ns` covers the -/// surrounding DashMap traffic on both paths but excludes the -/// closure-internal time. +/// Why this exists: profiling on Mathlib showed the construction + +/// interning pair dominates `convert` worker-sum, and most constructed +/// values are immediately discarded for an existing canonical Arc. By +/// probing with just the shallow key we skip construction entirely on a +/// hit. (Historically the probe key was a precomputed blake3 content +/// hash; the shallow key preserves the skip while removing the hash.) #[inline] fn timed_intern_or_build( intern: &mut InternTable, - hash: blake3::Hash, - build: impl FnOnce(Addr) -> KExpr, + key: &super::env::ExprKey, + build: impl FnOnce() -> KExpr, stats: &mut ConvertStats, ) -> KExpr { if !stats.enabled { - if let Some(existing) = intern.try_get_expr(&hash) { + if let Some(existing) = intern.try_get_expr(key) { return existing; } - return intern.intern_expr(build(hash)); + return intern.intern_expr(build()); } let t0 = Instant::now(); - if let Some(existing) = intern.try_get_expr(&hash) { + if let Some(existing) = intern.try_get_expr(key) { stats.intern_expr_get_hits += 1; stats.intern_expr_calls += 1; stats.intern_expr_ns += elapsed_ns(t0); return existing; } - let addr = hash; let kc_t0 = Instant::now(); - let new = build(addr); + let new = build(); let kc_elapsed = elapsed_ns(kc_t0); stats.kexpr_construct_ns += kc_elapsed; stats.kexpr_construct_calls += 1; let interned = intern.intern_expr(new); let total = elapsed_ns(t0); - // Account for the DashMap traffic only — the closure body's time is + // Account for the map traffic only — the closure body's time is // already in `kexpr_construct_ns`. stats.intern_expr_ns += total.saturating_sub(kc_elapsed); stats.intern_expr_calls += 1; @@ -704,25 +699,21 @@ fn ingress_expr( if mdata_layers.is_empty() { let name_field = M::meta_field(name); let mdata_field: M::MField> = M::meta_field(vec![]); - let hash = KExpr::::var_hash(*idx, &name_field, &mdata_field); + let key = super::env::ExprKey::Var(*idx); values.push(timed_intern_or_build( intern, - hash, - |addr| { - KExpr::var_mdata_with_addr(*idx, name_field, mdata_field, addr) - }, + &key, + || KExpr::var_mdata(*idx, name_field, mdata_field), stats, )); } else { let name_field = M::meta_field(name); let mdata_field = M::meta_field(mdata_layers); - let hash = KExpr::::var_hash(*idx, &name_field, &mdata_field); + let key = super::env::ExprKey::Var(*idx); values.push(timed_intern_or_build( intern, - hash, - |addr| { - KExpr::var_mdata_with_addr(*idx, name_field, mdata_field, addr) - }, + &key, + || KExpr::var_mdata(*idx, name_field, mdata_field), stats, )); } @@ -755,11 +746,11 @@ fn ingress_expr( })?) .ok_or_else(|| format!("invalid Sort univ index {idx}"))?; let zu = ingress_univ(u, ctx, intern, univ_cache, stats); - let hash = KExpr::::sort_hash(&zu, &mdata); + let key = super::env::ExprKey::Sort(*zu.addr()); values.push(timed_intern_or_build( intern, - hash, - |addr| KExpr::sort_mdata_with_addr(zu, mdata, addr), + &key, + || KExpr::sort_mdata(zu, mdata), stats, )); }, @@ -795,11 +786,14 @@ fn ingress_expr( let univs = ingress_univ_args(univ_idxs, ctx, intern, univ_cache, stats)?; let id = KId::new(addr, name_field); - let hash = KExpr::::cnst_hash(&id, &univs, &mdata); + let key = super::env::ExprKey::Const( + id.addr.clone(), + univs.iter().map(|u| *u.addr()).collect(), + ); values.push(timed_intern_or_build( intern, - hash, - |a| KExpr::cnst_mdata_with_addr(id, univs, mdata, a), + &key, + || KExpr::cnst_mdata(id, univs, mdata), stats, )); }, @@ -816,11 +810,14 @@ fn ingress_expr( .clone(); let univs = ingress_univ_args(univ_idxs, ctx, intern, univ_cache, stats)?; - let hash = KExpr::::cnst_hash(&mid, &univs, &mdata); + let key = super::env::ExprKey::Const( + mid.addr.clone(), + univs.iter().map(|u| *u.addr()).collect(), + ); values.push(timed_intern_or_build( intern, - hash, - |a| KExpr::cnst_mdata_with_addr(mid, univs, mdata, a), + &key, + || KExpr::cnst_mdata(mid, univs, mdata), stats, )); }, @@ -923,11 +920,14 @@ fn ingress_expr( let id = KId::new(addr, M::meta_field(name)); let mdata_field: M::MField> = M::meta_field(vec![]); - let hash = KExpr::::cnst_hash(&id, &univs, &mdata_field); + let key = super::env::ExprKey::Const( + id.addr.clone(), + univs.iter().map(|u| *u.addr()).collect(), + ); timed_intern_or_build( intern, - hash, - |a| KExpr::cnst_mdata_with_addr(id, univs, mdata_field, a), + &key, + || KExpr::cnst_mdata(id, univs, mdata_field), stats, ) }, @@ -950,11 +950,14 @@ fn ingress_expr( )?; let mdata_field: M::MField> = M::meta_field(vec![]); - let hash = KExpr::::cnst_hash(&mid, &univs, &mdata_field); + let key = super::env::ExprKey::Const( + mid.addr.clone(), + univs.iter().map(|u| *u.addr()).collect(), + ); timed_intern_or_build( intern, - hash, - |a| KExpr::cnst_mdata_with_addr(mid, univs, mdata_field, a), + &key, + || KExpr::cnst_mdata(mid, univs, mdata_field), stats, ) }, @@ -1181,11 +1184,11 @@ fn ingress_expr( format!("invalid UTF-8 in Str blob at addr {}: {e}", addr.hex()) })?; let blob_addr = addr.clone(); - let hash = KExpr::::str_hash(&blob_addr, &mdata); + let key = super::env::ExprKey::Str(blob_addr.clone()); values.push(timed_intern_or_build( intern, - hash, - |a| KExpr::str_mdata_with_addr(s, blob_addr, mdata, a), + &key, + || KExpr::str_mdata(s, blob_addr, mdata), stats, )); }, @@ -1208,11 +1211,11 @@ fn ingress_expr( } let n = Nat::from_le_bytes(&blob); let blob_addr = addr.clone(); - let hash = KExpr::::nat_hash(&blob_addr, &mdata); + let key = super::env::ExprKey::Nat(blob_addr.clone()); values.push(timed_intern_or_build( intern, - hash, - |a| KExpr::nat_mdata_with_addr(n, blob_addr, mdata, a), + &key, + || KExpr::nat_mdata(n, blob_addr, mdata), stats, )); }, @@ -1234,11 +1237,11 @@ fn ingress_expr( let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; let a = values.pop().unwrap(); let f = values.pop().unwrap(); - let hash = KExpr::::app_hash(&f, &a, &mdata); + let key = super::env::ExprKey::App(*f.addr(), *a.addr()); values.push(timed_intern_or_build( intern, - hash, - |addr| KExpr::app_mdata_with_addr(f, a, mdata, addr), + &key, + || KExpr::app_mdata(f, a, mdata), stats, )); if let Some(t0) = cont_t0 { @@ -1257,11 +1260,11 @@ fn ingress_expr( let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; let body = values.pop().unwrap(); let ty = values.pop().unwrap(); - let hash = KExpr::::lam_hash(&name, &bi, &ty, &body, &mdata); + let key = super::env::ExprKey::Lam(*ty.addr(), *body.addr()); values.push(timed_intern_or_build( intern, - hash, - |addr| KExpr::lam_mdata_with_addr(name, bi, ty, body, mdata, addr), + &key, + || KExpr::lam_mdata(name, bi, ty, body, mdata), stats, )); if let Some(t0) = cont_t0 { @@ -1280,11 +1283,11 @@ fn ingress_expr( let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; let body = values.pop().unwrap(); let ty = values.pop().unwrap(); - let hash = KExpr::::all_hash(&name, &bi, &ty, &body, &mdata); + let key = super::env::ExprKey::All(*ty.addr(), *body.addr()); values.push(timed_intern_or_build( intern, - hash, - |addr| KExpr::all_mdata_with_addr(name, bi, ty, body, mdata, addr), + &key, + || KExpr::all_mdata(name, bi, ty, body, mdata), stats, )); if let Some(t0) = cont_t0 { @@ -1305,13 +1308,12 @@ fn ingress_expr( let body = values.pop().unwrap(); let val = values.pop().unwrap(); let ty = values.pop().unwrap(); - let hash = KExpr::::let_hash(&name, &ty, &val, &body, nd, &mdata); + let key = + super::env::ExprKey::Let(*ty.addr(), *val.addr(), *body.addr(), nd); values.push(timed_intern_or_build( intern, - hash, - |addr| { - KExpr::let_mdata_with_addr(name, ty, val, body, nd, mdata, addr) - }, + &key, + || KExpr::let_mdata(name, ty, val, body, nd, mdata), stats, )); if let Some(t0) = cont_t0 { @@ -1335,11 +1337,12 @@ fn ingress_expr( ExprFrame::PrjDone { type_id, field_idx, mdata } => { let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; let s = values.pop().unwrap(); - let hash = KExpr::::prj_hash(&type_id, field_idx, &s, &mdata); + let key = + super::env::ExprKey::Prj(type_id.addr.clone(), field_idx, *s.addr()); values.push(timed_intern_or_build( intern, - hash, - |addr| KExpr::prj_mdata_with_addr(type_id, field_idx, s, mdata, addr), + &key, + || KExpr::prj_mdata(type_id, field_idx, s, mdata), stats, )); if let Some(t0) = cont_t0 { @@ -1372,7 +1375,7 @@ fn ingress_expr( #[allow(clippy::too_many_arguments)] fn ingress_defn( - def: &crate::ix::ixon::constant::Definition, + def: &ixon::constant::Definition, self_id: KId, meta: &ConstantMeta, ixon_env: &IxonEnv, @@ -1492,7 +1495,7 @@ fn ingress_defn( #[allow(clippy::too_many_arguments)] fn ingress_recursor( - rec: &crate::ix::ixon::constant::Recursor, + rec: &ixon::constant::Recursor, self_id: KId, meta: &ConstantMeta, ixon_env: &IxonEnv, @@ -1778,7 +1781,7 @@ fn ingress_standalone( #[allow(clippy::too_many_arguments)] fn ingress_muts_inductive( - ind: &crate::ix::ixon::constant::Inductive, + ind: &ixon::constant::Inductive, self_id: &KId, meta: &ConstantMeta, ixon_env: &IxonEnv, @@ -2085,9 +2088,11 @@ fn ingress_muts_block( let resolve_ctor = |cid: &KId| -> Option> { results_ref.iter().find(|(rid, _)| rid == cid).map(|(_, c)| c.clone()) }; - crate::ix::kernel::canonical_check::validate_canonical_block_single_pass::< - M, - >(entry_addr, &indcs, &resolve_ctor) + crate::canonical_check::validate_canonical_block_single_pass::( + entry_addr, + &indcs, + &resolve_ctor, + ) .map_err(|e| format!("{e}"))?; } @@ -2098,7 +2103,7 @@ fn ingress_muts_block( // Lightweight LeanExpr → KExpr ingress (compile-side) // ============================================================================ -use crate::ix::env::{ +use ix_common::env::{ Expr as LeanExpr, ExprData as LeanExprData, Level, LevelData, }; @@ -2165,7 +2170,7 @@ pub fn resolve_lean_name_addr( /// Compute a stable hash for a `param_names` slice, used as part of the /// ingress cache key. Two calls with the same param names (in the same /// order) produce the same hash. -pub fn param_names_hash(param_names: &[Name]) -> Addr { +pub fn param_names_hash(param_names: &[Name]) -> CtxAddr { let mut hasher = blake3::Hasher::new(); hasher.update(&(param_names.len() as u64).to_le_bytes()); for n in param_names { @@ -2245,8 +2250,8 @@ pub fn lean_expr_to_zexpr_cached( intern: &mut InternTable, n2a: Option<&DashMap>, aux_n2a: Option<&DashMap>, - mut cache: Option<&mut FxHashMap<(Addr, Addr), KExpr>>, - pn_hash: Option<&Addr>, + mut cache: Option<&mut FxHashMap<(CtxAddr, CtxAddr), KExpr>>, + pn_hash: Option<&CtxAddr>, ) -> KExpr { // Check cache if let (Some(cache), Some(pn_hash)) = (cache.as_ref(), pn_hash) { @@ -2286,8 +2291,8 @@ fn lean_expr_to_zexpr_raw( intern: &mut InternTable, n2a: Option<&DashMap>, aux_n2a: Option<&DashMap>, - mut cache: Option<&mut FxHashMap<(Addr, Addr), KExpr>>, - pn_hash: Option<&Addr>, + mut cache: Option<&mut FxHashMap<(CtxAddr, CtxAddr), KExpr>>, + pn_hash: Option<&CtxAddr>, ) -> KExpr { // Walk through any consecutive `Mdata` wrappers first, accumulating them // as kernel-side `MData` layers. Lean represents `Mdata(a, Mdata(b, e))` @@ -2483,7 +2488,7 @@ fn lean_expr_to_zexpr_raw( KExpr::prj_mdata(zid, idx.to_u64().unwrap_or(0), e_k, mdata_layers) }, LeanExprData::Lit(lit, _) => { - use crate::ix::env::Literal; + use ix_common::env::Literal; match lit { Literal::NatVal(n) => { // Address must match the Ixon-side blob address for this Nat, @@ -2659,6 +2664,7 @@ pub fn ingress_compiled_names( /// 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), @@ -2672,6 +2678,7 @@ fn lean_constant_all(ci: &LeanCI) -> Option<&Vec> { /// 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) } @@ -2691,6 +2698,7 @@ fn lean_member_idx(name: &Name, all: Option<&Vec>) -> u64 { /// (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 @@ -2719,6 +2727,7 @@ pub fn build_leon_addr_map(lean_env: &LeanEnv) -> DashMap { /// 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()) } @@ -2726,6 +2735,7 @@ fn leon_addr_of(name: &Name, n2a: &DashMap) -> Address { /// 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>, @@ -2736,6 +2746,7 @@ fn lean_block_id( } /// 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() } @@ -2744,6 +2755,7 @@ fn lean_all_ids(all: &[Name], n2a: &DashMap) -> Vec> { /// `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). +#[cfg(not(target_arch = "riscv64"))] fn lean_const_to_kconst( self_name: &Name, ci: &LeanCI, @@ -2754,7 +2766,7 @@ fn lean_const_to_kconst( // 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: &crate::ix::env::Expr, pn: &[Name]| -> KExpr { + let mut expr_to_k = |e: &ix_common::env::Expr, pn: &[Name]| -> KExpr { lean_expr_to_zexpr_with_kenv(e, pn, kenv, Some(n2a), None) }; @@ -2937,9 +2949,10 @@ fn lean_const_to_kconst( /// **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_raw` too. +#[cfg(not(target_arch = "riscv64"))] pub fn lean_ingress(lean_env: &LeanEnv) -> KEnv { use std::time::Instant; - let quiet = std::env::var("IX_QUIET").is_ok(); + let quiet = crate::env_var("IX_QUIET").is_ok(); let mut kenv = KEnv::::new_with_recursor_aux_order( super::env::RecursorAuxOrder::Source, ); @@ -2952,7 +2965,7 @@ pub fn lean_ingress(lean_env: &LeanEnv) -> KEnv { let t = Instant::now(); let n2a = build_leon_addr_map(lean_env); if !quiet { - eprintln!( + log::info!( "[lean_ingress] build_leon_addr_map: {:.2}s ({} names)", t.elapsed().as_secs_f32(), n2a.len() @@ -2967,7 +2980,7 @@ pub fn lean_ingress(lean_env: &LeanEnv) -> KEnv { kenv.insert(kid, kc); } if !quiet { - eprintln!( + log::info!( "[lean_ingress] pass 1 (serial ingress): {:.2}s", t.elapsed().as_secs_f32() ); @@ -3048,7 +3061,7 @@ pub fn lean_ingress(lean_env: &LeanEnv) -> KEnv { kenv.blocks.insert(block_id, members); } if !quiet { - eprintln!( + log::info!( "[lean_ingress] phase A (block seed): {:.2}s", t.elapsed().as_secs_f32() ); @@ -3082,7 +3095,7 @@ pub fn lean_ingress(lean_env: &LeanEnv) -> KEnv { } } if !quiet { - eprintln!( + log::info!( "[lean_ingress] phase B (ctor/rec append): {:.2}s", t.elapsed().as_secs_f32() ); @@ -3097,8 +3110,7 @@ pub fn lean_ingress(lean_env: &LeanEnv) -> KEnv { // 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::ix::kernel::primitive::Primitives::from_env_orig(&kenv)); + let _ = kenv.set_prims(crate::primitive::Primitives::from_env_orig(&kenv)); kenv } @@ -3458,6 +3470,7 @@ fn timed_drop_ns(value: T) -> u64 { /// (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, @@ -3475,6 +3488,7 @@ where /// 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(); @@ -3484,14 +3498,30 @@ fn timed_drop_fxmap_par(map: FxHashMap) -> u64 { /// 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 { - std::env::var_os("IX_SEQ_IXON_DROP").is_some() + 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 { - std::env::var_os("IX_INGRESS_CONVERT_STATS").is_some() + 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, @@ -3500,7 +3530,10 @@ fn drop_ingress_lookups( 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 @@ -3512,20 +3545,29 @@ fn drop_ingress_lookups( name_to_addr_ns: timed_drop_ns(name_to_addr), } } else { - LookupDropTiming { - names_ns: timed_drop_fxmap_par(names), - name_to_addr_ns: timed_drop_fxmap_par(name_to_addr), + #[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 { - eprintln!( + #[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" }, - rayon::current_num_threads(), + threads, seconds(timing.names_ns), names_len, seconds(timing.name_to_addr_ns), @@ -3540,17 +3582,25 @@ fn insert_standalone_entries( ) -> IngressInsertTiming { let mut 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()); } - timing.blocks_ns = elapsed_ns(phase_start); + #[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); } - timing.consts_ns = elapsed_ns(phase_start); + #[cfg(not(target_arch = "riscv64"))] + { + timing.consts_ns = elapsed_ns(phase_start); + } timing } @@ -3561,6 +3611,7 @@ fn insert_muts_entries( ) -> IngressInsertTiming { let mut 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, .. } @@ -3573,13 +3624,20 @@ fn insert_muts_entries( if let Some(bid) = block_id { zenv.blocks.insert(bid, member_ids); } - timing.blocks_ns = elapsed_ns(phase_start); + #[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); } - timing.consts_ns = elapsed_ns(phase_start); + #[cfg(not(target_arch = "riscv64"))] + { + timing.consts_ns = elapsed_ns(phase_start); + } timing } @@ -3599,12 +3657,18 @@ pub fn ixon_ingress( pub fn ixon_ingress_owned( ixon_env: IxonEnv, ) -> Result<(KEnv, InternTable), String> { - let quiet = std::env::var_os("IX_QUIET").is_some(); + let quiet = crate::env_var_os("IX_QUIET").is_some(); 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 a small FxHashMap (one entry per Def from the .ixe's @@ -3623,7 +3687,10 @@ fn drop_ixon_env(ixon_env: IxonEnv, quiet: bool) { // 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), @@ -3633,23 +3700,32 @@ fn drop_ixon_env(ixon_env: IxonEnv, quiet: bool) { comms_ns: timed_drop_ns(comms), } } else { - 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(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 { - eprintln!( + #[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" }, - rayon::current_num_threads(), + threads, seconds(timing.consts_ns), consts_len, seconds(timing.named_ns), @@ -3667,22 +3743,23 @@ fn drop_ixon_env(ixon_env: IxonEnv, quiet: bool) { fn ixon_ingress_inner( ixon_env: &IxonEnv, ) -> Result<(KEnv, InternTable), String> { - let quiet = std::env::var_os("IX_QUIET").is_some(); + #[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)?; - if !quiet { - eprintln!( - "[ixon_ingress] validate_reserved: {:.2}s", - phase_start.elapsed().as_secs_f32() - ); - } + #[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() { @@ -3692,18 +3769,18 @@ fn ixon_ingress_inner( for entry in ixon_env.named.iter() { name_to_addr.insert(entry.key().clone(), entry.value().addr.clone()); } - if !quiet { - eprintln!( - "[ixon_ingress] build lookups: {:.2}s ({} names, {} named)", - phase_start.elapsed().as_secs_f32(), - names.len(), - name_to_addr.len() - ); - } + #[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(); let mut standalone_count = 0usize; @@ -3750,17 +3827,17 @@ fn ixon_ingress_inner( }, } } - if !quiet { - eprintln!( - "[ixon_ingress] partition work: {:.2}s ({} standalone, {} muts)", - phase_start.elapsed().as_secs_f32(), - standalone_count, - muts_count - ); - } + #[cfg(not(target_arch = "riscv64"))] + log::info!( + "[ixon_ingress] partition work: {:.2}s ({} standalone, {} muts)", + phase_start.elapsed().as_secs_f32(), + standalone_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(); @@ -3771,26 +3848,38 @@ fn ixon_ingress_inner( 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"))?; - timing.lookup_ns += elapsed_ns(lookup_start); + #[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) => { - timing.const_get_ns += elapsed_ns(const_start); + #[cfg(not(target_arch = "riscv64"))] + { + timing.const_get_ns += elapsed_ns(const_start); + } c }, None => { - timing.const_get_ns += elapsed_ns(const_start); + #[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, @@ -3804,22 +3893,33 @@ fn ixon_ingress_inner( &mut convert_stats, ) .map_err(|e| format!("{const_name}: {e}"))?; - timing.convert_ns += elapsed_ns(convert_start); + #[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); - timing.insert_ns += elapsed_ns(insert_start); + #[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"))?; - timing.lookup_ns += elapsed_ns(lookup_start); + #[cfg(not(target_arch = "riscv64"))] + { + timing.lookup_ns += elapsed_ns(lookup_start); + } let all = match &named.meta.info { ConstantMetaInfo::Muts { all, .. } => all, @@ -3829,6 +3929,7 @@ fn ixon_ingress_inner( continue; }, }; + #[cfg(not(target_arch = "riscv64"))] let convert_start = Instant::now(); let entries = ingress_muts_block( &entry_name, @@ -3841,12 +3942,19 @@ fn ixon_ingress_inner( &mut convert_stats, ) .map_err(|e| format!("{entry_name}: {e}"))?; - timing.convert_ns += elapsed_ns(convert_start); + #[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); - timing.insert_ns += elapsed_ns(insert_start); + #[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; }, @@ -3854,107 +3962,110 @@ fn ixon_ingress_inner( timing.convert_stats = convert_stats; stream = stream.merge(&timing); } - if !quiet { - eprintln!( - "[ixon_ingress] stream ingress+insert: {:.2}s", - phase_start.elapsed().as_secs_f32() + #[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 ); - eprintln!( - "[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 + log::info!( + "[ixon_ingress] convert nodes: sort {} var {} ref {} rec {} app {} lam {} all {} let {} prj {} str {} nat {} callsites {} args {}", + 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, + cs.callsites, + cs.callsite_args ); - let cs = &stream.convert_stats; - if cs.enabled { - let cache_lookups = cs.expr_cache_hits + cs.expr_cache_misses; - eprintln!( - "[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 - ); - eprintln!( - "[ixon_ingress] convert nodes: sort {} var {} ref {} rec {} app {} lam {} all {} let {} prj {} str {} nat {} callsites {} args {}", - 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, - cs.callsites, - cs.callsite_args - ); - eprintln!( - "[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; - eprintln!( - "[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) - ); - } - eprintln!( - "[ixon_ingress] complete: {:.2}s ({} consts, {} blocks)", - total_start.elapsed().as_secs_f32(), - zenv.len(), - zenv.blocks.len() + 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, quiet); + drop_ingress_lookups(names, name_to_addr, false); Ok((zenv, intern)) } @@ -4014,7 +4125,7 @@ fn validate_no_reserved_marker_addresses( // discards `named`/`names`/`comms` sections. The helpers below do not // depend on those sections being empty — they simply never consult them. -use crate::ix::kernel::mode::Anon; +use crate::mode::Anon; /// Verify that a projection address computed from a block's structure /// is actually present in the env's consts. Wrapped here so the four @@ -4050,27 +4161,27 @@ fn verify_proj_addr_in_env( /// Deterministic IPrj content address for member `idx` of `block`. /// -/// Thin re-export of `crate::ix::ixon::constant::indc_proj_address` — +/// 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 { - crate::ix::ixon::constant::indc_proj_address(idx, block) + 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 { - crate::ix::ixon::constant::defn_proj_address(idx, block) + 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 { - crate::ix::ixon::constant::recr_proj_address(idx, block) + 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 { - crate::ix::ixon::constant::ctor_proj_address(idx, cidx, block) + ixon::constant::ctor_proj_address(idx, cidx, block) } /// Compute deterministic ctor projection addresses for every constructor of @@ -4078,7 +4189,7 @@ pub fn anon_ctor_proj_addr(block: &Address, idx: u64, cidx: u64) -> Address { fn anon_ctor_addrs( block_addr: &Address, indc_idx: u64, - ind: &crate::ix::ixon::constant::Inductive, + ind: &ixon::constant::Inductive, ) -> Vec
{ (0..ind.ctors.len() as u64) .map(|cidx| anon_ctor_proj_addr(block_addr, indc_idx, cidx)) @@ -4168,7 +4279,7 @@ fn ingress_anon_standalone( /// for the inductive and every ctor. #[allow(clippy::too_many_arguments)] fn ingress_anon_inductive( - ind: &crate::ix::ixon::constant::Inductive, + ind: &ixon::constant::Inductive, self_id: &KId, anon_env: &IxonEnv, block_constant: &Constant, @@ -4488,10 +4599,10 @@ pub fn ingress_anon_addr_shallow( #[cfg(test)] mod tests { use super::*; - use crate::ix::env::{self, BinderInfo}; - use crate::ix::ixon::metadata::CallSiteEntry; - use crate::ix::kernel::expr::ExprData; - use crate::ix::kernel::level::UnivData; + use crate::expr::ExprData; + use crate::level::UnivData; + use ix_common::env::{self, BinderInfo}; + use ixon::metadata::CallSiteEntry; fn mk_name(s: &str) -> Name { let mut n = Name::anon(); @@ -4646,10 +4757,10 @@ mod tests { #[test] fn ixon_ingress_rejects_reserved_marker_named_addr() { let env = IxonEnv::new(); - let marker = crate::ix::kernel::primitive::PrimAddrs::new().eager_reduce; + let marker = crate::primitive::PrimAddrs::new().eager_reduce; env.register_name( mk_name("Evil.marker"), - crate::ix::ixon::env::Named::with_addr(marker), + ixon::env::Named::with_addr(marker), ); let err = match ixon_ingress::(&env) { @@ -4663,15 +4774,13 @@ mod tests { #[test] fn ixon_ingress_rejects_reserved_marker_refs() { let env = IxonEnv::new(); - let marker = crate::ix::kernel::primitive::PrimAddrs::new().eager_reduce; + let marker = crate::primitive::PrimAddrs::new().eager_reduce; let constant = Constant::with_tables( - crate::ix::ixon::constant::ConstantInfo::Axio( - crate::ix::ixon::constant::Axiom { - is_unsafe: false, - lvls: 0, - typ: IxonExpr::sort(0), - }, - ), + ixon::constant::ConstantInfo::Axio(ixon::constant::Axiom { + is_unsafe: false, + lvls: 0, + typ: IxonExpr::sort(0), + }), vec![], vec![marker], vec![], @@ -5062,7 +5171,7 @@ mod tests { let list_addr = Address::hash(b"arbitrary"); ie.named.insert( list_name.clone(), - crate::ix::ixon::env::Named::with_addr(list_addr.clone()), + ixon::env::Named::with_addr(list_addr.clone()), ); let (name_map, addr_map) = build_ingress_lookups(&ie); @@ -5102,7 +5211,7 @@ mod tests { // 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 crate::ix::ixon::constant::{ + use ixon::constant::{ Constant, ConstantInfo, ConstructorProj, DefinitionProj, InductiveProj, RecursorProj, }; diff --git a/src/ix/kernel/lctx.rs b/crates/kernel/src/lctx.rs similarity index 98% rename from src/ix/kernel/lctx.rs rename to crates/kernel/src/lctx.rs index 32c13347..d522e714 100644 --- a/src/ix/kernel/lctx.rs +++ b/crates/kernel/src/lctx.rs @@ -20,7 +20,7 @@ use rustc_hash::FxHashMap; -use crate::ix::env::{BinderInfo, Name}; +use ix_common::env::{BinderInfo, Name}; use super::env::InternTable; use super::expr::{ExprData, FVarId, KExpr}; @@ -254,8 +254,8 @@ pub fn is_fvar(e: &KExpr) -> bool { #[cfg(test)] mod tests { use super::*; - use crate::ix::kernel::level::KUniv; - use crate::ix::kernel::mode::Anon; + use crate::level::KUniv; + use crate::mode::Anon; type AE = KExpr; type AU = KUniv; diff --git a/src/ix/kernel/level.rs b/crates/kernel/src/level.rs similarity index 94% rename from src/ix/kernel/level.rs rename to crates/kernel/src/level.rs index 58d2b25d..1c899314 100644 --- a/src/ix/kernel/level.rs +++ b/crates/kernel/src/level.rs @@ -37,7 +37,7 @@ use std::collections::BTreeMap; use std::fmt; use std::sync::Arc; -use crate::ix::env::{Name, UIMAX, UMAX, UPARAM, USUCC, UZERO}; +use ix_common::env::Name; use super::env::Addr; use super::mode::{KernelMode, MetaDisplay}; @@ -81,11 +81,33 @@ impl KUniv { Arc::ptr_eq(&self.0, &other.0) } - /// Structural equality by Merkle hash (pointer-first fast path). + /// Canonical-identity equality: `ptr_eq` or equal intern uid. Sound in + /// the affirmative; incomplete in the negative (independently built + /// equal levels carry distinct uids) — `==` adds the structural + /// fallback. pub fn hash_eq(&self, other: &KUniv) -> bool { self.ptr_eq(other) || self.addr() == other.addr() } + /// Structural equality mirroring the intern identity: `Param` display + /// names are NOT compared (matching the historical hash semantics). + /// Equal interned subtrees prune at the uid fast path. + fn structural_eq(&self, other: &Self) -> bool { + if self.hash_eq(other) { + return true; + } + match (self.data(), other.data()) { + (UnivData::Zero(_), UnivData::Zero(_)) => true, + (UnivData::Succ(a, _), UnivData::Succ(b, _)) => a.structural_eq(b), + (UnivData::Max(a1, b1, _), UnivData::Max(a2, b2, _)) + | (UnivData::IMax(a1, b1, _), UnivData::IMax(a2, b2, _)) => { + a1.structural_eq(a2) && b1.structural_eq(b2) + }, + (UnivData::Param(i, _, _), UnivData::Param(j, _, _)) => i == j, + _ => false, + } + } + /// True if this level is definitionally zero (Prop). pub fn is_zero(&self) -> bool { matches!(self.data(), UnivData::Zero(_)) @@ -130,14 +152,11 @@ impl KUniv { impl KUniv { pub fn zero() -> Self { - KUniv::new(UnivData::Zero(blake3::hash(&[UZERO]))) + KUniv::new(UnivData::Zero(super::expr::fresh_uid())) } pub fn succ(inner: KUniv) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[USUCC]); - hasher.update(inner.addr().as_bytes()); - KUniv::new(UnivData::Succ(inner, hasher.finalize())) + KUniv::new(UnivData::Succ(inner, super::expr::fresh_uid())) } /// Construct `max(a, b)` with Lean-style simplifications: @@ -193,11 +212,7 @@ impl KUniv { /// Raw `Max` constructor without simplification. Used by `max()` after /// all simplification opportunities are exhausted. fn max_raw(a: KUniv, b: KUniv) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[UMAX]); - hasher.update(a.addr().as_bytes()); - hasher.update(b.addr().as_bytes()); - KUniv::new(UnivData::Max(a, b, hasher.finalize())) + KUniv::new(UnivData::Max(a, b, super::expr::fresh_uid())) } /// Construct `imax(a, b)` with Lean-style simplifications: @@ -228,25 +243,18 @@ impl KUniv { return a; // imax(a, a) = a } // No simplification — construct raw IMax node. - let mut hasher = blake3::Hasher::new(); - hasher.update(&[UIMAX]); - hasher.update(a.addr().as_bytes()); - hasher.update(b.addr().as_bytes()); - KUniv::new(UnivData::IMax(a, b, hasher.finalize())) + KUniv::new(UnivData::IMax(a, b, super::expr::fresh_uid())) } pub fn param(idx: u64, name: M::MField) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[UPARAM]); - hasher.update(&idx.to_le_bytes()); - KUniv::new(UnivData::Param(idx, name, hasher.finalize())) + KUniv::new(UnivData::Param(idx, name, super::expr::fresh_uid())) } } -// Structural equality by Merkle hash. +// Structural equality (uid fast path, recursive fallback). impl PartialEq for KUniv { fn eq(&self, other: &Self) -> bool { - self.hash_eq(other) + self.structural_eq(other) } } @@ -708,7 +716,7 @@ pub fn univ_geq(u: &KUniv, v: &KUniv) -> bool { mod tests { use super::super::mode::{Anon, Meta}; use super::*; - use crate::ix::env::Name; + use ix_common::env::Name; type MU = KUniv; type AU = KUniv; @@ -725,8 +733,10 @@ mod tests { #[test] fn zero_hash_deterministic() { - assert_eq!(MU::zero().addr(), MU::zero().addr()); - assert_eq!(AU::zero().addr(), AU::zero().addr()); + // Identity is intern-assigned now: independent constructions carry + // distinct uids but remain structurally equal (`==`). + assert_eq!(MU::zero(), MU::zero()); + assert_eq!(AU::zero(), AU::zero()); } #[test] @@ -774,14 +784,14 @@ mod tests { fn meta_param_name_does_not_affect_hash() { let a = MU::param(0, mk_name("u")); let b = MU::param(0, mk_name("v")); - assert_eq!(a.addr(), b.addr()); + assert_eq!(a, b); } #[test] fn meta_param_same_name_same_hash() { let a = MU::param(0, mk_name("u")); let b = MU::param(0, mk_name("u")); - assert_eq!(a.addr(), b.addr()); + assert_eq!(a, b); } // ---- Anon mode: names erased ---- @@ -790,23 +800,31 @@ mod tests { fn anon_param_same_index_same_hash() { let a = AU::param(0, ()); let b = AU::param(0, ()); - assert_eq!(a.addr(), b.addr()); + assert_eq!(a, b); } // ---- Anon vs Meta structural hash matches (metadata erased) ---- #[test] fn anon_vs_meta_named_param_match() { + // Cross-mode structural identity is visible through the shallow + // intern keys, which are mode-independent. let anon = AU::param(0, ()); let meta = MU::param(0, mk_name("u")); - assert_eq!(anon.addr(), meta.addr()); + assert_eq!( + super::super::env::univ_key(&anon), + super::super::env::univ_key(&meta) + ); } #[test] fn anon_vs_meta_anon_param_same() { let anon = AU::param(0, ()); let meta = MU::param(0, Name::anon()); - assert_eq!(anon.addr(), meta.addr()); + assert_eq!( + super::super::env::univ_key(&anon), + super::super::env::univ_key(&meta) + ); } // ---- PartialEq ---- @@ -1011,8 +1029,8 @@ mod tests { // Same structure, different names — semantically equal let a = MU::param(0, mk_name("u")); let b = MU::param(0, mk_name("v")); - // Hashes are metadata-erased, and Géran comparison sees the same index. - assert_eq!(a.addr(), b.addr()); + // Identity is metadata-erased, and Géran comparison sees the same index. + assert_eq!(a, b); assert!(univ_eq(&a, &b)); } diff --git a/crates/kernel/src/lib.rs b/crates/kernel/src/lib.rs new file mode 100644 index 00000000..488c47e9 --- /dev/null +++ b/crates/kernel/src/lib.rs @@ -0,0 +1,173 @@ +// Diagnostic env-var helpers. zkVM std targets (`target_os = "zkvm"` — Zisk, +// SP1, risc0, …) 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; +pub mod canonical_check; +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 id; +pub mod inductive; +pub mod infer; +pub mod ingress; +pub mod lctx; +pub mod level; +pub mod mode; +pub mod perf; +pub mod primitive; +// 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; +#[cfg(not(target_arch = "riscv64"))] +pub mod shard; +pub mod subst; +pub mod tc; +pub mod whnf; + +#[cfg(test)] +pub mod testing; +#[cfg(test)] +mod tutorial; diff --git a/src/ix/kernel/mode.rs b/crates/kernel/src/mode.rs similarity index 99% rename from src/ix/kernel/mode.rs rename to crates/kernel/src/mode.rs index 0477e8f1..cbaf1aa6 100644 --- a/src/ix/kernel/mode.rs +++ b/crates/kernel/src/mode.rs @@ -13,7 +13,7 @@ use std::fmt::{self, Debug}; use std::hash::Hash; -use crate::ix::env::{BinderInfo, DataValue, Name, NameData}; +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. @@ -44,7 +44,7 @@ impl MetaHash for BinderInfo { impl MetaHash for DataValue { fn meta_hash(&self, hasher: &mut blake3::Hasher) { - crate::ix::env::hash_data_value(self, hasher); + ix_common::env::hash_data_value(self, hasher); } } diff --git a/src/ix/kernel/perf.rs b/crates/kernel/src/perf.rs similarity index 86% rename from src/ix/kernel/perf.rs rename to crates/kernel/src/perf.rs index baf04e45..8f1bcf88 100644 --- a/src/ix/kernel/perf.rs +++ b/crates/kernel/src/perf.rs @@ -27,11 +27,10 @@ //! atomic op. use std::fmt; -use std::sync::LazyLock; use std::sync::atomic::{AtomicU64, Ordering}; -static PERF_ENABLED: LazyLock = - LazyLock::new(|| std::env::var_os("IX_PERF_COUNTERS").is_some()); +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 @@ -301,6 +300,56 @@ impl PerfCounters { } } +// -- 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, diff --git a/src/ix/kernel/primitive.rs b/crates/kernel/src/primitive.rs similarity index 98% rename from src/ix/kernel/primitive.rs rename to crates/kernel/src/primitive.rs index 270ebea6..3ffa1c48 100644 --- a/src/ix/kernel/primitive.rs +++ b/crates/kernel/src/primitive.rs @@ -15,7 +15,7 @@ use std::sync::LazyLock; -use crate::ix::address::Address; +use ix_common::address::Address; use super::env::KEnv; use super::id::KId; @@ -1006,7 +1006,7 @@ impl Primitives { /// `@` fallbacks. pub fn from_addr_names(mut name_for_addr: F) -> Self where - F: FnMut(&Address) -> Option, + F: FnMut(&Address) -> Option, { Self::from_addrs_with(&PrimAddrs::new(), |addr| { name_for_addr(addr) @@ -1036,16 +1036,16 @@ impl Primitives { let mut r = |addr: &Address| -> KId { resolve(addr).unwrap_or_else(|| { let hex = addr.hex(); - let name = crate::ix::env::Name::str( - crate::ix::env::Name::anon(), + 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 = crate::ix::env::Name::str( - crate::ix::env::Name::anon(), + let name = ix_common::env::Name::str( + ix_common::env::Name::anon(), format!("@{marker_name}"), ); KId::new(addr.clone(), M::meta_field(name)) @@ -1158,12 +1158,12 @@ mod tests { use std::collections::HashMap; use super::*; - use crate::ix::env::Name; - use crate::ix::kernel::constant::KConst; - use crate::ix::kernel::expr::KExpr; - use crate::ix::kernel::id::KId; - use crate::ix::kernel::level::KUniv; - use crate::ix::kernel::mode::Anon; + 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. @@ -1360,7 +1360,7 @@ mod tests { // 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 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 @@ -1398,7 +1398,7 @@ mod tests { #[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 env = KEnv::::new(); let p = Primitives::from_env_orig(&env); let orig = PrimAddrs::new_orig(); let canon = PrimAddrs::new(); @@ -1413,7 +1413,7 @@ mod tests { // 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 env = KEnv::::new(); let p = Primitives::from_env_orig(&env); // Name of `p.nat` should be `@`. let orig = PrimAddrs::new_orig(); diff --git a/src/ix/profile.rs b/crates/kernel/src/profile.rs similarity index 99% rename from src/ix/profile.rs rename to crates/kernel/src/profile.rs index ed05c539..c12e062a 100644 --- a/src/ix/profile.rs +++ b/crates/kernel/src/profile.rs @@ -27,7 +27,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; -use crate::ix::address::Address; +use ix_common::address::Address; /// Magic bytes at the head of every `.ixesp` file. const MAGIC: &[u8; 8] = b"IXESP\0\0\0"; diff --git a/src/ix/shard.rs b/crates/kernel/src/shard.rs similarity index 76% rename from src/ix/shard.rs rename to crates/kernel/src/shard.rs index 0fd9682e..8524e207 100644 --- a/src/ix/shard.rs +++ b/crates/kernel/src/shard.rs @@ -58,8 +58,8 @@ use std::collections::BinaryHeap; use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; use std::time::Instant; -use crate::ix::address::Address; -use crate::ix::profile::BlockProfile; +use crate::profile::BlockProfile; +use ix_common::address::Address; /// Recurse the two halves of a bisection in parallel (rayon) when the parent /// sub-problem is at least this large; below it the join overhead isn't worth @@ -184,9 +184,23 @@ impl Hypergraph { /// the per-bisection balance tolerance (e.g. `0.05` for ±5%). `num_shards` /// need not be a power of two. pub fn partition(&self, num_shards: usize, epsilon: f64) -> Vec { + self.partition_with_tree(num_shards, epsilon).0 + } + + /// Like [`Self::partition`], but also returns the **bisection tree** — the + /// binary tree of min-cut splits whose leaves are the shard ids. Reusing this + /// as the proof-aggregation tree (rather than an arbitrary flat fold) + /// discharges each cross-shard assumption at the lowest common ancestor of the + /// shards that share it; the per-bisection min-cut means sibling subtrees have + /// the thinnest possible interface, so most discharge happens low in the tree. + pub fn partition_with_tree( + &self, + num_shards: usize, + epsilon: f64, + ) -> (Vec, AggNode) { let n = self.num_vertices(); if num_shards <= 1 { - return vec![0u32; n]; // everything in shard 0 + return (vec![0u32; n], AggNode::Leaf(0)); // everything in shard 0 } // Cap each block's balance weight at the ideal per-shard heartbeats // (total / num_shards). This keeps balancing heartbeat-aware while a balanced @@ -202,9 +216,152 @@ impl Hypergraph { // they can be partitioned on separate threads without their writes aliasing. let shard_of: Vec = (0..n).map(|_| AtomicU32::new(0)).collect(); let prog = PartitionProgress::new(num_shards); - rec_bisect(&sub, num_shards, 0, epsilon, &shard_of, &prog); + let tree = rec_bisect(&sub, num_shards, 0, epsilon, &shard_of, &prog); prog.done(); - shard_of.into_iter().map(AtomicU32::into_inner).collect() + let assignment = shard_of.into_iter().map(AtomicU32::into_inner).collect(); + (assignment, tree) + } +} + +// ============================================================================ +// Bisection / aggregation tree +// ============================================================================ + +/// The bisection tree produced by recursive min-cut partitioning: leaves are +/// shard ids, internal nodes are the two halves of a min-cut split. Reused as +/// the proof-aggregation tree so cross-shard assumptions discharge at the lowest +/// common ancestor of the shards that share them (see module docs and +/// [`agg_plan`]). Serialized into the `.ixes` manifest so the prover can fold +/// along it. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum AggNode { + /// A single shard, identified by its id. + Leaf(u32), + /// A min-cut split into a left and right subtree. + Internal(Box, Box), +} + +impl AggNode { + /// Number of shard leaves under this node. + pub fn num_leaves(&self) -> usize { + match self { + AggNode::Leaf(_) => 1, + AggNode::Internal(l, r) => l.num_leaves() + r.num_leaves(), + } + } + + /// The smallest shard id under this node — its left-to-right position. Used to + /// keep aggregation children in shard order (so the subject merkle-fold stays + /// the left-associative order the agg guest reproduces). + pub fn min_leaf(&self) -> u32 { + match self { + AggNode::Leaf(id) => *id, + AggNode::Internal(l, _) => l.min_leaf(), + } + } + + /// Prune to the leaves selected by `keep`: dropped leaves disappear and an + /// internal node that loses one child collapses to the survivor. Returns + /// `None` when nothing remains. Lets a prover fold along the tree even when + /// only a subset of shards was proven (empty shards, `--only-shard`). + pub fn prune(&self, keep: &impl Fn(u32) -> bool) -> Option { + match self { + AggNode::Leaf(id) => keep(*id).then_some(AggNode::Leaf(*id)), + AggNode::Internal(l, r) => match (l.prune(keep), r.prune(keep)) { + (Some(a), Some(b)) => Some(AggNode::Internal(Box::new(a), Box::new(b))), + (Some(a), None) | (None, Some(a)) => Some(a), + (None, None) => None, + }, + } + } + + /// Preorder serialization: `0u8, id(4)` for a leaf; `1u8, , ` for + /// an internal node. + fn put(&self, out: &mut Vec) { + match self { + AggNode::Leaf(id) => { + out.push(0); + out.extend_from_slice(&id.to_le_bytes()); + }, + AggNode::Internal(l, r) => { + out.push(1); + l.put(out); + r.put(out); + }, + } + } + + /// Inverse of [`Self::put`], reading from a cursor. + fn get(c: &mut Cur<'_>) -> Result { + match c.u8()? { + 0 => Ok(AggNode::Leaf(c.u32()?)), + 1 => { + let l = AggNode::get(c)?; + let r = AggNode::get(c)?; + Ok(AggNode::Internal(Box::new(l), Box::new(r))) + }, + t => Err(format!("bad AggNode tag {t}")), + } + } +} + +/// One step of an aggregation fold plan, in post order: every `Agg`'s child +/// indices refer to *earlier* entries in the plan, and the last entry is the +/// root. The prover materializes a slot per entry — `Leaf(id)` is shard `id`'s +/// leaf proof; `Agg(children)` folds those slots' proofs in one agg call. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FoldOp { + Leaf(u32), + Agg(Vec), +} + +/// Lower the bisection tree to an arity-bounded fold plan. The binary tree is +/// *collapsed* so each agg call folds up to `arity` whole subtrees (never +/// splitting a subtree across calls), keeping the agg-proof count ~`N/(arity-1)` +/// — a strict binary fold would be ~`N` agg proofs, dominating cost. Children of +/// each agg are emitted in shard order so the subject merkle-fold matches the +/// guest's left-associative join. +pub fn agg_plan(tree: &AggNode, arity: usize) -> Vec { + let arity = arity.max(2); + let mut ops: Vec = Vec::new(); + build_plan(tree, arity, &mut ops); + ops +} + +/// Emit `node`'s plan entries; return the index of the entry representing it. +fn build_plan(node: &AggNode, arity: usize, ops: &mut Vec) -> usize { + match node { + AggNode::Leaf(id) => { + ops.push(FoldOp::Leaf(*id)); + ops.len() - 1 + }, + AggNode::Internal(l, r) => { + // Collapse the binary subtree into up to `arity` child subtrees: start + // from the two halves and repeatedly split the *largest* still-internal + // frontier node until we have `arity` children or all are leaves. This + // keeps tightly-coupled siblings together while bounding the fan-in. + let mut frontier: Vec<&AggNode> = vec![l, r]; + while frontier.len() < arity { + let largest = frontier + .iter() + .enumerate() + .filter(|(_, nd)| matches!(nd, AggNode::Internal(_, _))) + .max_by_key(|(_, nd)| nd.num_leaves()) + .map(|(i, _)| i); + let Some(i) = largest else { break }; // all leaves: cannot expand + let AggNode::Internal(l, r) = frontier.swap_remove(i) else { + unreachable!() + }; + frontier.push(l); + frontier.push(r); + } + // Shard order keeps the merkle subject-fold left-associative. + frontier.sort_by_key(|nd| nd.min_leaf()); + let child_idxs: Vec = + frontier.iter().map(|c| build_plan(c, arity, ops)).collect(); + ops.push(FoldOp::Agg(child_idxs)); + ops.len() - 1 + }, } } @@ -340,14 +497,14 @@ fn rec_bisect( epsilon: f64, shard_of: &[AtomicU32], prog: &PartitionProgress, -) { +) -> AggNode { if k <= 1 || sub.num_vertices() <= 1 { // A leaf (or a subtree with ≤1 vertex): everything here is one shard. for &g in &sub.global { shard_of[g as usize].store(base, Ordering::Relaxed); } prog.leaf(); - return; + return AggNode::Leaf(base); } prog.bisecting(sub.num_vertices(), k); let side = bisect(sub, epsilon); @@ -365,7 +522,7 @@ fn rec_bisect( shard_of[g as usize].store(base, Ordering::Relaxed); } prog.leaf(); - return; + return AggNode::Leaf(base); } // Allocate leaves ~proportional to heartbeat mass, clamped to [1, side size]. // `lo`/`hi` are feasible (lo ≤ hi) because nl + nr ≥ k with nl, nr ≥ 1. @@ -379,16 +536,21 @@ fn rec_bisect( let rbase = base + k_left as u32; // The two halves are independent (disjoint blocks) — recurse them in parallel // when the work is large enough to amortize the join. The result is identical - // to serial execution (each subtree is deterministic). - if nl + nr >= PARALLEL_THRESHOLD { + // to serial execution (each subtree is deterministic). Each call returns its + // subtree's `AggNode`; the two combine into this node's binary split — the + // bisection tree mirrors the recursion exactly. + let (ln, rn) = if nl + nr >= PARALLEL_THRESHOLD { rayon::join( || rec_bisect(&left, k_left, base, epsilon, shard_of, prog), || rec_bisect(&right, k - k_left, rbase, epsilon, shard_of, prog), - ); + ) } else { - rec_bisect(&left, k_left, base, epsilon, shard_of, prog); - rec_bisect(&right, k - k_left, rbase, epsilon, shard_of, prog); - } + ( + rec_bisect(&left, k_left, base, epsilon, shard_of, prog), + rec_bisect(&right, k - k_left, rbase, epsilon, shard_of, prog), + ) + }; + AggNode::Internal(Box::new(ln), Box::new(rn)) } /// Progress reporter for [`Hypergraph::partition`]. The partitioner is silent @@ -1082,6 +1244,10 @@ pub struct ShardManifest { pub shards: Vec, /// Total cross-shard ingress bytes (the km1 objective). pub total_cross_ingress: u128, + /// The bisection tree over the shard ids, for proof aggregation. `None` on + /// manifests written before this section existed (the prover then falls back + /// to a flat tree-fold). Set by [`Self::with_tree`]. + pub tree: Option, } impl ShardManifest { @@ -1139,9 +1305,18 @@ impl ShardManifest { num_shards: num_shards as u32, shards, total_cross_ingress: total_cross, + tree: None, } } + /// Attach the bisection tree (from [`Hypergraph::partition_with_tree`]) for + /// tree-aligned aggregation. + #[must_use] + pub fn with_tree(mut self, tree: AggNode) -> Self { + self.tree = Some(tree); + self + } + /// A human-readable what-if summary line. pub fn summary(&self) -> String { let hbs: Vec = self.shards.iter().map(|s| s.heartbeats).collect(); @@ -1203,6 +1378,16 @@ impl ShardManifest { put_addrs(&mut out, &sh.blocks); put_addrs(&mut out, &sh.foreign_blocks); } + // Trailing optional bisection-tree section: presence byte then preorder + // tree. Appended after the shards so older readers that stop at the shard + // count simply ignore it, and `from_bytes` treats end-of-input as `None`. + match &self.tree { + Some(t) => { + out.push(1); + t.put(&mut out); + }, + None => out.push(0), + } out } @@ -1233,10 +1418,18 @@ impl ShardManifest { assumption_root, }); } + // Optional trailing tree section. Absent (end-of-input) on pre-tree + // manifests, or an explicit `0` presence byte. + let tree = if c.pos < c.buf.len() { + if c.u8()? == 1 { Some(AggNode::get(&mut c)?) } else { None } + } else { + None + }; Ok(ShardManifest { num_shards: num_shards as u32, shards, total_cross_ingress, + tree, }) } } @@ -1304,11 +1497,12 @@ pub fn shard_esp( let profile = BlockProfile::from_bytes(&bytes) .map_err(|e| format!("parse {esp_path}: {e}"))?; let h = Hypergraph::from_profile(&profile); - let shard_of = h.partition(num_shards, balance); - let mut manifest = ShardManifest::build(&profile, &shard_of, num_shards); + let (shard_of, tree) = h.partition_with_tree(num_shards, balance); + let mut manifest = + ShardManifest::build(&profile, &shard_of, num_shards).with_tree(tree); for shard in &mut manifest.shards { shard.assumption_root = - crate::ix::ixon::merkle::merkle_root_canonical(&shard.foreign_blocks); + ixon::merkle::merkle_root_canonical(&shard.foreign_blocks); } if let Some(op) = out_path { std::fs::write(op, manifest.to_bytes()) @@ -1341,10 +1535,142 @@ pub fn shard_esp( )) } +/// The per-shard cycle cap implied by a machine's total RAM — so callers can +/// size shards straight from `MemTotal` without ever picking a budget. Inverts +/// the measured single-leaf prover model on this setup +/// (`peak_RAM_GB ≈ 45 + 32 × cycles_billions`) at ~78% of RAM (reserving the +/// rest for the OS + run-to-run variance). Returns 0 when the box can't even +/// hold the ~45 GB prover base (nothing will prove). Approximate by design — +/// pair with [`partition_for_cycle_cap`] to get N. +pub fn cycle_cap_for_ram(ram_gb: f64) -> u64 { + const BASE_GB: f64 = 45.0; // fixed prover witness/contribution buffers + const GB_PER_BCYCLE: f64 = 32.0; // RAM per billion guest cycles + const USABLE_FRAC: f64 = 0.78; // headroom for OS + variance + let headroom = ram_gb * USABLE_FRAC - BASE_GB; + if headroom <= 0.0 { + return 0; + } + (headroom / GB_PER_BCYCLE * 1e9) as u64 +} + +/// A partition sized to a per-shard Zisk **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 heartbeat cap derived from the cycle cap + /// (`max_cycles / cycles_per_heartbeat`). + pub heartbeat_cap: u64, + /// Heaviest shard's heartbeats in the chosen partition. + pub max_shard_heartbeats: u64, + /// Largest single (atomic) block's heartbeats — the hard floor on + /// `max_shard_heartbeats` that no shard count can beat. + pub largest_block_heartbeats: 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** budget, then partition. +/// +/// `max_cycles` is the ceiling on a single shard's in-circuit Zisk guest steps +/// — the leaf prover's trace size, which is what sets peak prover RAM. Convert +/// it from a host-RAM budget with the empirical single-leaf model measured on +/// this prover: +/// +/// ```text +/// peak_RAM_GB ≈ 45 + 32 × cycles_billions +/// ⇒ max_cycles ≈ (target_peak_GB − 45) / 32 × 1e9 +/// ``` +/// +/// e.g. a 256 GB box with a ~195 GB safe target (≈ 50 GB headroom for OS + +/// run-to-run variance) → `(195 − 45) / 32 ≈ 4.7` → `max_cycles ≈ 4.5e9`. +/// +/// `cycles_per_heartbeat` converts the planner's heartbeat balance metric to +/// guest cycles. Measured across 12 envs: large shardable envs cluster at +/// 194–208k whole-env, and the per-shard ratio runs ~5–8% higher (a shard +/// memoizes less) — mergesort's heaviest shard is ≈ 216k, so ~215k is the +/// conservative default for the regime that needs sharding. It is genuinely +/// workload-dependent though (tiny arithmetic envs ~130–160k; heavy-def-eq envs +/// like array/string-assoc ~258k), so recalibrate per environment with one +/// `--execute`: a shard's reported steps ÷ that shard's heartbeats. The cycle +/// cap's RAM headroom (target well under the wall) absorbs the residual error. +/// +/// Picks the smallest `N` whose heaviest shard fits `max_cycles / +/// cycles_per_heartbeat`, growing `N` proportionally to any overshoot and +/// re-partitioning (partitioning is milliseconds-to-seconds, negligible beside +/// proving). Stops at the **atomic-block floor**: a mutual block cannot be +/// split, so once the heaviest shard is pinned to the largest block, more shards +/// only worsen balance — `infeasible_atomic_floor` then flags that the cap is +/// unreachable and the block must be dealt with upstream. +pub fn partition_for_cycle_cap( + profile: &BlockProfile, + max_cycles: u64, + cycles_per_heartbeat: u64, + epsilon: f64, +) -> BudgetPlan { + let h = Hypergraph::from_profile(profile); + let hb_cap = (max_cycles / cycles_per_heartbeat.max(1)).max(1); + let largest_block = + profile.blocks().iter().map(|b| b.heartbeats).max().unwrap_or(0); + let total_hb = profile.total_heartbeats(); + let nblocks = profile.num_blocks().max(1); + + let max_shard_hb_of = |shard_of: &[u32], n: usize| -> u64 { + let mut sums = vec![0u64; n]; + for (b, &s) in shard_of.iter().enumerate() { + sums[s as usize] = + sums[s as usize].saturating_add(profile.block(b as u32).heartbeats); + } + sums.into_iter().max().unwrap_or(0) + }; + + // Initial estimate from the average load; refine against the real partition. + let mut n = ((total_hb / u128::from(hb_cap)).max(1) as usize).min(nblocks); + let (mut shard_of, mut tree) = h.partition_with_tree(n, epsilon); + let mut max_shard_hb = max_shard_hb_of(&shard_of, n); + + // Grow N until the heaviest shard fits the cap, or we hit the atomic-block + // floor / the block count. Proportional growth converges in a few re-cuts. + let mut guard = 0; + while max_shard_hb > hb_cap + && n < nblocks + && max_shard_hb > largest_block.saturating_mul(11) / 10 + && guard < 24 + { + // Next estimate: scale N by the overshoot ratio (so a 2× overshoot doubles + // N), but always advance by ≥1 to guarantee progress. Re-checking the real + // partition each round converges to the *minimal* N that fits. + let scaled = + ((n as f64) * (max_shard_hb as f64 / hb_cap as f64)).ceil() as usize; + n = scaled.max(n + 1).min(nblocks); + let (so, t) = h.partition_with_tree(n, epsilon); + shard_of = so; + tree = t; + max_shard_hb = max_shard_hb_of(&shard_of, n); + guard += 1; + } + + BudgetPlan { + num_shards: n, + shard_of, + tree, + heartbeat_cap: hb_cap, + max_shard_heartbeats: max_shard_hb, + largest_block_heartbeats: largest_block, + infeasible_atomic_floor: largest_block > hb_cap, + } +} + #[cfg(test)] mod tests { use super::*; - use crate::ix::profile::ProfileBuilder; + use crate::profile::ProfileBuilder; fn addr(byte: u8) -> Address { Address::from_slice(&[byte; 32]).unwrap() @@ -1641,4 +1967,129 @@ mod tests { ); } } + + // ---- Bisection / aggregation tree ---- + + fn leaf(id: u32) -> AggNode { + AggNode::Leaf(id) + } + fn node(l: AggNode, r: AggNode) -> AggNode { + AggNode::Internal(Box::new(l), Box::new(r)) + } + + #[test] + fn partition_with_tree_leaves_match_shards() { + // 360 blocks → 4 shards. The tree's leaves must be exactly shard ids 0..4. + let p = two_big_clusters(180); + let h = Hypergraph::from_profile(&p); + let (shard_of, tree) = h.partition_with_tree(4, 0.10); + assert_eq!(tree.num_leaves(), 4); + let mut ids = Vec::new(); + fn collect(n: &AggNode, out: &mut Vec) { + match n { + AggNode::Leaf(id) => out.push(*id), + AggNode::Internal(l, r) => { + collect(l, out); + collect(r, out); + }, + } + } + collect(&tree, &mut ids); + ids.sort_unstable(); + assert_eq!(ids, vec![0, 1, 2, 3]); + // Every shard id assigned to some block must appear as a leaf. + let used: std::collections::BTreeSet = + shard_of.iter().copied().collect(); + assert!(used.iter().all(|s| ids.contains(s))); + } + + #[test] + fn agg_plan_arity_and_coverage() { + // Balanced 8-leaf tree: ((0 1)(2 3)) ((4 5)(6 7)). + let t = node( + node(node(leaf(0), leaf(1)), node(leaf(2), leaf(3))), + node(node(leaf(4), leaf(5)), node(leaf(6), leaf(7))), + ); + for arity in [2usize, 3, 4, 8] { + let plan = agg_plan(&t, arity); + // Every Agg has 2..=arity children; the last op is the root Agg. + let mut seen_leaves: Vec = Vec::new(); + for op in &plan { + match op { + FoldOp::Leaf(id) => seen_leaves.push(*id), + FoldOp::Agg(ch) => { + assert!( + ch.len() >= 2 && ch.len() <= arity, + "arity {arity}: {ch:?}" + ); + // children reference earlier entries (post order) + assert!(ch.iter().all(|&i| i < plan.len())); + }, + } + } + assert!(matches!(plan.last(), Some(FoldOp::Agg(_)))); + seen_leaves.sort_unstable(); + assert_eq!(seen_leaves, (0..8).collect::>(), "arity {arity}"); + } + } + + #[test] + fn agg_plan_children_in_shard_order() { + // Children of each agg must be emitted in increasing min-leaf order so the + // subject merkle-fold stays left-associative. + let t = node(node(leaf(0), leaf(1)), node(leaf(2), leaf(3))); + let plan = agg_plan(&t, 2); + // Root folds two subtrees; the left subtree's leaves precede the right's. + if let Some(FoldOp::Agg(ch)) = plan.last() { + // Map each child slot to its min leaf and assert ascending. + let mins: Vec = ch + .iter() + .map(|&i| match &plan[i] { + FoldOp::Leaf(id) => *id, + FoldOp::Agg(g) => g + .iter() + .map( + |&j| if let FoldOp::Leaf(x) = &plan[j] { *x } else { u32::MAX }, + ) + .min() + .unwrap_or(u32::MAX), + }) + .collect(); + let mut sorted = mins.clone(); + sorted.sort_unstable(); + assert_eq!(mins, sorted); + } else { + panic!("root must be an Agg"); + } + } + + #[test] + fn prune_collapses_and_preserves() { + // ((0 1)(2 3)): pruning leaf 1 collapses its parent to leaf 0; pruning a + // whole side collapses the root; keep-all is identity; keep-none is None. + let t = node(node(leaf(0), leaf(1)), node(leaf(2), leaf(3))); + assert_eq!(t.prune(&|_| true), Some(t.clone())); + assert_eq!(t.prune(&|_| false), None); + assert_eq!( + t.prune(&|id| id != 1), + Some(node(leaf(0), node(leaf(2), leaf(3)))) + ); + assert_eq!(t.prune(&|id| id >= 2), Some(node(leaf(2), leaf(3)))); + assert_eq!(t.prune(&|id| id == 3), Some(leaf(3))); + } + + #[test] + fn manifest_tree_roundtrip() { + let p = two_clusters(); + let h = Hypergraph::from_profile(&p); + let (shard_of, tree) = h.partition_with_tree(2, 0.10); + let m = ShardManifest::build(&p, &shard_of, 2).with_tree(tree.clone()); + let bytes = m.to_bytes(); + let q = ShardManifest::from_bytes(&bytes).unwrap(); + assert_eq!(q.tree, Some(tree)); + // A pre-tree manifest (tree = None) still roundtrips. + let m0 = ShardManifest::build(&p, &shard_of, 2); + let q0 = ShardManifest::from_bytes(&m0.to_bytes()).unwrap(); + assert_eq!(q0.tree, None); + } } diff --git a/src/ix/kernel/subst.rs b/crates/kernel/src/subst.rs similarity index 87% rename from src/ix/kernel/subst.rs rename to crates/kernel/src/subst.rs index 04a7dbaf..32ac5cac 100644 --- a/src/ix/kernel/subst.rs +++ b/crates/kernel/src/subst.rs @@ -11,7 +11,8 @@ //! uses a `PtrMap Expr Expr` for the same reason (see //! `refs/lean4lean/Lean4Lean/Expr.lean:14`). -use std::sync::LazyLock; +use std::cell::OnceCell; +use std::sync::Arc; use rustc_hash::FxHashMap; @@ -25,8 +26,8 @@ use super::mode::KernelMode; /// 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: LazyLock = - LazyLock::new(|| std::env::var("IX_SUBST_COUNT_LOG").is_ok()); +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); @@ -53,7 +54,7 @@ pub fn subst( if *IX_SUBST_COUNT_LOG && depth == 0 { let n = SUBST_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); if n.is_multiple_of(100_000) && n > 0 { - eprintln!("[subst] count={n}"); + log::info!("[subst] count={n}"); } } // Fast path: no loose bound vars at or below `depth` means nothing to @@ -281,7 +282,7 @@ fn simul_subst_cached( cache.insert(key, r.clone()); return r; } else if i >= depth + n { - KExpr::var(i - n, M::meta_field(crate::ix::env::Name::anon())) + KExpr::var(i - n, M::meta_field(ix_common::env::Name::anon())) } else { let r = body.clone(); cache.insert(key, r.clone()); @@ -490,6 +491,217 @@ fn lift_cached( interned } +// ============================================================================ +// Closures for the WHNF environment machine (see `whnf.rs` machine loop and +// `docs/env_machine_whnf.md`). Beta/zeta there are O(1) pushes onto an +// `MEnv`; substitution happens only here, at machine exit points +// ("readback"), and only for the parts of the term the reduction actually +// hands to a plain-expression consumer. +// ============================================================================ + +/// Expression closed over a machine environment: `Clo { e, env }` denotes +/// `e[Var(i) := env[i]]` for `i < env.len()`, with `Var(i)` for +/// `i >= env.len()` shifting DOWN by `env.len()` into the ambient context. +/// WHNF never reduces under binders, so environments never need lifting. +pub(crate) struct Clo { + pub(crate) e: KExpr, + pub(crate) env: MEnv, + /// Memoized depth-0 readback. Closures are shared (one env entry may be + /// referenced by many variables; spine args survive multiple machine + /// re-entries), and without a global content-addressed memo this is + /// what keeps each shared closure's substitution from re-running. + readback: OnceCell>, +} + +impl Clo { + pub(crate) fn new(e: KExpr, env: MEnv) -> Self { + Clo { e, env, readback: OnceCell::new() } + } + + /// Closure over the empty environment (a plain expression). + pub(crate) fn closed(e: KExpr) -> Self { + Clo::new(e, MEnv::empty()) + } +} + +struct MEnvNode { + head: Arc>, + tail: MEnv, +} + +/// Persistent cons-list environment: O(1) push with structural sharing +/// across the closures captured at each binder. `len` is carried on the +/// handle — recomputing it per suffix was a measured cost on the IxVM +/// port of this machine. +pub(crate) struct MEnv { + node: Option>>, + len: u64, +} + +// Manual impl: `#[derive(Clone)]` would demand `M: Clone`. +impl Clone for MEnv { + fn clone(&self) -> Self { + MEnv { node: self.node.clone(), len: self.len } + } +} + +impl MEnv { + pub(crate) fn empty() -> Self { + MEnv { node: None, len: 0 } + } + + pub(crate) fn len(&self) -> u64 { + self.len + } + + pub(crate) fn push(&self, c: Arc>) -> Self { + MEnv { + node: Some(Arc::new(MEnvNode { head: c, tail: self.clone() })), + len: self.len + 1, + } + } + + /// O(i) cons-list walk; `i` must be `< self.len()`. Machine variable + /// lookups are typically near the front (recently pushed args). + pub(crate) fn get(&self, i: u64) -> &Arc> { + let mut node = self.node.as_ref().expect("MEnv::get out of range"); + let mut i = i; + while i > 0 { + node = node.tail.node.as_ref().expect("MEnv::get out of range"); + i -= 1; + } + &node.head + } +} + +/// Materialize a closure into a plain expression: +/// `e[Var(i) := readback(env[i])]` for `i < env.len()`, `Var(j)` above +/// shifted down by `env.len()`. Memoized per closure. +pub(crate) fn clo_readback( + intern: &mut InternTable, + c: &Clo, +) -> KExpr { + if c.env.len() == 0 { + return c.e.clone(); + } + if let Some(v) = c.readback.get() { + return v.clone(); + } + let v = clo_subst(intern, &c.e, &c.env, 0); + let _ = c.readback.set(v.clone()); + v +} + +/// Simultaneous environment substitution at binder depth `depth` — the +/// machine's only substitution ("readback"). Var arm semantics: +/// `i < depth` → unchanged (locally bound) +/// `depth <= i < depth + n` → `lift(readback(env[i - depth]), depth)` +/// `i >= depth + n` → `Var(i - n)` +/// where `n = env.len()`. lbr-guarded at every node (a no-op subtree +/// returns its original Arc), per-call memoized by `(uid, depth)`, and +/// results interned — the same discipline as `subst`/`simul_subst`. +/// +/// The memo scratch comes from a pool (`clo_scratch_pool`) rather than a +/// single buffer because the Var arm re-enters `clo_subst` through +/// `clo_readback` of environment entries, each under a *different* +/// environment; nesting levels must not share memo entries. +pub(crate) fn clo_subst( + intern: &mut InternTable, + e: &KExpr, + env: &MEnv, + depth: u64, +) -> KExpr { + if env.len() == 0 || e.lbr() <= depth { + return e.clone(); + } + let mut cache = intern.clo_scratch_pool.pop().unwrap_or_default(); + let result = clo_subst_cached(intern, e, env, depth, &mut cache); + cache.clear(); + intern.clo_scratch_pool.push(cache); + result +} + +fn clo_subst_cached( + intern: &mut InternTable, + e: &KExpr, + env: &MEnv, + depth: u64, + cache: &mut FxHashMap<(Addr, u64), KExpr>, +) -> KExpr { + if e.lbr() <= depth { + return e.clone(); + } + + let key = (e.hash_key(), depth); + if let Some(cached) = cache.get(&key) { + return cached.clone(); + } + + let n = env.len(); + + let result = match e.data() { + ExprData::Var(i, name, _) => { + let i = *i; + if i < depth + n { + // `i < depth` is unreachable under the outer lbr guard, so this + // is an env hit: substitute the entry's readback, lifted over + // the local binders we are under. + let c = env.get(i - depth).clone(); + let v = clo_readback(intern, &c); + let r = lift(intern, &v, depth, 0); + cache.insert(key, r.clone()); + return r; + } + KExpr::var(i - n, name.clone()) + }, + + ExprData::App(f, x, _) => { + let f2 = clo_subst_cached(intern, f, env, depth, cache); + let x2 = clo_subst_cached(intern, x, env, depth, cache); + KExpr::app(f2, x2) + }, + + ExprData::Lam(name, bi, ty, inner, _) => { + let ty2 = clo_subst_cached(intern, ty, env, depth, cache); + let inner2 = clo_subst_cached(intern, inner, env, depth + 1, cache); + KExpr::lam(name.clone(), bi.clone(), ty2, inner2) + }, + + ExprData::All(name, bi, ty, inner, _) => { + let ty2 = clo_subst_cached(intern, ty, env, depth, cache); + let inner2 = clo_subst_cached(intern, inner, env, depth + 1, cache); + KExpr::all(name.clone(), bi.clone(), ty2, inner2) + }, + + ExprData::Let(name, ty, val, inner, nd, _) => { + let ty2 = clo_subst_cached(intern, ty, env, depth, cache); + let val2 = clo_subst_cached(intern, val, env, depth, cache); + let inner2 = clo_subst_cached(intern, inner, env, depth + 1, cache); + KExpr::let_(name.clone(), ty2, val2, inner2, *nd) + }, + + ExprData::Prj(id, field, val, _) => { + let val2 = clo_subst_cached(intern, val, env, depth, cache); + KExpr::prj(id.clone(), *field, val2) + }, + + ExprData::FVar(..) + | ExprData::Sort(..) + | ExprData::Const(..) + | ExprData::Nat(..) + | ExprData::Str(..) => { + // Closed atoms — unreachable under the lbr guard; defensive. + let r = e.clone(); + cache.insert(key, r.clone()); + return r; + }, + }; + + let interned = intern.intern_expr(result); + cache.insert(key, interned.clone()); + interned +} + /// Cheap beta reduction: peephole-reduce `App(λ...λ. body, args)` shapes /// without invoking the full [`subst`] machinery in trivial cases. /// @@ -661,7 +873,7 @@ fn instantiate_rev_cached( return r; } else if i >= depth + n { // Free variable above the instantiated range: shift down by `n`. - KExpr::var(i - n, M::meta_field(crate::ix::env::Name::anon())) + 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(); @@ -780,7 +992,7 @@ fn abstract_fvars_cached( // 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(crate::ix::env::Name::anon())); + 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; @@ -874,11 +1086,11 @@ impl ExprData { #[cfg(test)] mod tests { use super::*; - use crate::ix::address::Address; - use crate::ix::kernel::id::KId; - use crate::ix::kernel::level::KUniv; - use crate::ix::kernel::mode::Anon; - use lean_ffi::nat::Nat; + use crate::id::KId; + use crate::level::KUniv; + use crate::mode::Anon; + use bignat::Nat; + use ix_common::address::Address; type AE = KExpr; diff --git a/src/ix/kernel/tc.rs b/crates/kernel/src/tc.rs similarity index 94% rename from src/ix/kernel/tc.rs rename to crates/kernel/src/tc.rs index 3cccfa8d..53d1c814 100644 --- a/src/ix/kernel/tc.rs +++ b/crates/kernel/src/tc.rs @@ -6,16 +6,14 @@ //! WHNF, type inference, def-eq, and constant checking are in separate modules //! that add `impl TypeChecker` blocks. -use std::sync::LazyLock; - use rustc_hash::FxHashMap; use rustc_hash::FxHashSet; -use crate::ix::address::Address; -use crate::ix::ixon::env::Env as IxonEnv; +use ix_common::address::Address; +use ixon::env::Env as IxonEnv; use super::constant::{KConst, RecRule}; -use super::env::{Addr, KEnv}; +use super::env::{Addr, CtxAddr, KEnv}; use super::equiv::EquivManager; use super::error::{TcError, u64_to_usize}; use super::expr::{ExprData, FVarId, KExpr}; @@ -31,9 +29,9 @@ use super::primitive::Primitives; use super::subst::{instantiate_rev, lift}; /// Content-addressed context identity for the empty context (no bindings). -pub fn empty_ctx_addr() -> Addr { +pub fn empty_ctx_addr() -> CtxAddr { use std::sync::LazyLock; - static ADDR: LazyLock = + static ADDR: LazyLock = LazyLock::new(|| blake3::hash(b"ix.kernel.ctx.empty")); *ADDR } @@ -58,15 +56,15 @@ pub const MAX_DEF_EQ_DEPTH: u32 = 2_000; /// for bisecting suspected loops. pub const MAX_REC_FUEL: u64 = 10_000_000; -static IX_MAX_REC_FUEL: LazyLock> = LazyLock::new(|| { - std::env::var("IX_MAX_REC_FUEL").ok().and_then(|s| s.parse().ok()) +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: LazyLock = - LazyLock::new(|| std::env::var("IX_HOT_MISSES").is_ok()); +static IX_HOT_MISSES: crate::EnvFlag = + crate::EnvFlag::new(|| crate::env_var("IX_HOT_MISSES").is_ok()); -static IX_HOT_MISS_CTX: LazyLock = - LazyLock::new(|| std::env::var("IX_HOT_MISS_CTX").is_ok()); +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) @@ -128,9 +126,9 @@ pub struct TypeChecker<'a, M: KernelMode> { pub num_let_bindings: usize, /// Content-addressed context identity: a blake3 hash derived from the /// binding-type chain. Immune to the ABA pointer-reuse problem. - pub ctx_id: Addr, + pub ctx_id: CtxAddr, /// Stack of previous ctx_ids for O(1) pop. - ctx_id_stack: Vec, + ctx_id_stack: Vec, // -- Thread-local optimization -- /// Union-find for transitive def-eq caching (lean4lean EquivManager). @@ -185,7 +183,7 @@ pub struct TypeChecker<'a, M: KernelMode> { /// equal in the suffix-relevant prefix (`ctx_id` content-addresses the /// full context). The cache lifetime is the `TypeChecker` (one per /// `check_const`), so it is automatically reclaimed. - ctx_addr_cache: FxHashMap<(Addr, u64), Addr>, + ctx_addr_cache: FxHashMap<(CtxAddr, u64), CtxAddr>, // -- Free-variable infrastructure -- /// Local context for fvar-based binder opening. Some validation paths still @@ -357,7 +355,7 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { /// Sharing two distinct outer contexts that share a relevant suffix is the /// payoff: the same WHNF subterm can hit cache across them. #[inline] - pub fn whnf_key(&mut self, e: &KExpr) -> (Addr, Addr) { + pub fn whnf_key(&mut self, e: &KExpr) -> (Addr, CtxAddr) { (e.hash_key(), self.ctx_addr_for_lbr(e.lbr())) } @@ -368,7 +366,7 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { /// dependencies, so two equal open subterms can share an infer result across /// different outer binders when the relevant local suffix is identical. #[inline] - pub fn infer_key(&mut self, e: &KExpr) -> (Addr, Addr) { + pub fn infer_key(&mut self, e: &KExpr) -> (Addr, CtxAddr) { (e.hash_key(), self.ctx_addr_for_lbr(e.lbr())) } @@ -380,11 +378,11 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { /// expressions, so the relevant context is the suffix needed by the larger /// `lbr`. #[inline] - pub fn def_eq_ctx_key(&mut self, a: &KExpr, b: &KExpr) -> Addr { + pub fn def_eq_ctx_key(&mut self, a: &KExpr, b: &KExpr) -> CtxAddr { self.ctx_addr_for_lbr(a.lbr().max(b.lbr())) } - pub(crate) fn ctx_addr_for_lbr(&mut self, lbr: u64) -> Addr { + pub(crate) fn ctx_addr_for_lbr(&mut self, lbr: u64) -> CtxAddr { if lbr == 0 || self.ctx.is_empty() { return empty_ctx_addr(); } @@ -430,12 +428,12 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { match &self.let_vals[i] { Some(val) => { h.update(b"let"); - h.update(self.ctx[i].addr().as_bytes()); - h.update(val.addr().as_bytes()); + h.update(&self.ctx[i].addr().to_le_bytes()); + h.update(&val.addr().to_le_bytes()); }, None => { h.update(b"local"); - h.update(self.ctx[i].addr().as_bytes()); + h.update(&self.ctx[i].addr().to_le_bytes()); }, } } @@ -447,10 +445,13 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { } /// Push a local variable type (lambda/forall binding, no let-value). + /// The type is interned first: ctx suffix hashing keys on uids, so a + /// canonical frame is what lets equal suffixes share cache entries. pub fn push_local(&mut self, ty: KExpr) { + let ty = self.env.intern.intern_expr(ty); let mut h = blake3::Hasher::new(); h.update(b"ctx.local"); - h.update(ty.addr().as_bytes()); + h.update(&ty.addr().to_le_bytes()); h.update(self.ctx_id.as_bytes()); self.ctx_id_stack.push(self.ctx_id); self.ctx_id = h.finalize(); @@ -461,10 +462,12 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { /// Push a let-bound variable (type + value). WHNF will zeta-reduce references /// to this variable by substituting the value (lean4lean withExtendedLetCtx). pub fn push_let(&mut self, ty: KExpr, val: KExpr) { + let ty = self.env.intern.intern_expr(ty); + let val = self.env.intern.intern_expr(val); let mut h = blake3::Hasher::new(); h.update(b"ctx.let"); - h.update(ty.addr().as_bytes()); - h.update(val.addr().as_bytes()); + h.update(&ty.addr().to_le_bytes()); + h.update(&val.addr().to_le_bytes()); h.update(self.ctx_id.as_bytes()); self.ctx_id_stack.push(self.ctx_id); self.ctx_id = h.finalize(); @@ -538,8 +541,8 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { /// the binder scope. pub fn open_binder( &mut self, - name: M::MField, - bi: M::MField, + name: M::MField, + bi: M::MField, ty: KExpr, body: &KExpr, ) -> (KExpr, FVarId) { @@ -559,8 +562,8 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { ty: KExpr, body: &KExpr, ) -> (KExpr, FVarId) { - let name = M::meta_field(crate::ix::env::Name::anon()); - let bi = M::meta_field(crate::ix::env::BinderInfo::Default); + 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) } @@ -569,8 +572,8 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { /// abstract_fvars / structural identity comparisons). pub fn open_binder_with_fv( &mut self, - name: M::MField, - bi: M::MField, + name: M::MField, + bi: M::MField, ty: KExpr, body: &KExpr, ) -> (KExpr, KExpr, FVarId) { @@ -588,8 +591,8 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { ty: KExpr, body: &KExpr, ) -> (KExpr, KExpr, FVarId) { - let name = M::meta_field(crate::ix::env::Name::anon()); - let bi = M::meta_field(crate::ix::env::BinderInfo::Default); + 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) } @@ -599,7 +602,7 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { /// for downstream WHNF zeta-reduction). pub fn open_let( &mut self, - name: M::MField, + name: M::MField, ty: KExpr, val: KExpr, body: &KExpr, @@ -617,8 +620,8 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { /// 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(crate::ix::env::Name::anon()); - let bi = M::meta_field(crate::ix::env::BinderInfo::Default); + 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 }); @@ -852,7 +855,7 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { } pub fn debug_label_matches_env(&self) -> bool { - match std::env::var("IX_KERNEL_DEBUG_CONST") { + 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)) @@ -865,10 +868,10 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { #[inline] pub fn tick(&mut self) -> Result<(), TcError> { if self.rec_fuel == 0 { - if std::env::var("IX_REC_FUEL_DUMP").is_ok() + if crate::env_var("IX_REC_FUEL_DUMP").is_ok() && self.debug_label_matches_env() { - eprintln!( + log::info!( "[rec fuel] exhausted const={} depth={} def_eq_depth={} infer_only={} native_reduce={} eager_reduce={}", self.debug_label.as_deref().unwrap_or(""), self.depth(), @@ -878,7 +881,7 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { self.eager_reduce ); self.dump_hot_misses(); - eprintln!("{}", std::backtrace::Backtrace::force_capture()); + log::info!("{}", std::backtrace::Backtrace::force_capture()); } return Err(TcError::MaxRecFuel); } @@ -928,6 +931,7 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { /// 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()); } @@ -983,7 +987,7 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { let ctx = self.ctx_addr_for_lbr(e.lbr()); key.push_str(&format!( " ctx={} depth={}", - short_addr(&ctx), + short_ctx_addr(&ctx), self.depth() )); } @@ -1000,7 +1004,7 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { let ctx = self.def_eq_ctx_key(a, b); key.push_str(&format!( " ctx={} depth={}", - short_addr(&ctx), + short_ctx_addr(&ctx), self.depth() )); } @@ -1013,9 +1017,9 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { } let mut entries: Vec<_> = self.hot_misses.iter().collect(); entries.sort_unstable_by(|a, b| b.1.cmp(a.1).then_with(|| a.0.cmp(b.0))); - eprintln!("[hot misses] top {}:", entries.len().min(25)); + log::info!("[hot misses] top {}:", entries.len().min(25)); for (key, count) in entries.into_iter().take(25) { - eprintln!(" {count:>8} {key}"); + log::info!(" {count:>8} {key}"); } } } @@ -1156,6 +1160,10 @@ fn hot_expr_shape(e: &KExpr) -> String { } fn short_addr(addr: &Addr) -> String { + format!("uid{addr}") +} + +fn short_ctx_addr(addr: &CtxAddr) -> String { addr.to_hex().chars().take(12).collect() } @@ -1165,8 +1173,8 @@ mod tests { apps, cnst, mk_addr, mk_id, mk_name, pi, sort0, sort1, uzero, var, }; use super::*; - use crate::ix::address::Address; - use crate::ix::kernel::mode::Meta; + use crate::mode::Meta; + use ix_common::address::Address; fn new_tc() -> TypeChecker<'static, Meta> { let env = Box::leak(Box::new(KEnv::::new())); @@ -1260,13 +1268,19 @@ mod tests { #[test] fn ctx_id_same_pushes_yield_same_hash() { - let mut tc1 = new_tc(); - let mut tc2 = new_tc(); - tc1.push_local(sort0()); - tc1.push_local(sort1()); + // ctx hashing keys on intern uids, so the sharing property holds for + // checkers over the SAME env (which is also the cache-sharing scope). + let env: *mut KEnv = Box::leak(Box::new(KEnv::::new())); + let id1 = { + let mut tc1 = TypeChecker::new(unsafe { &mut *env }); + tc1.push_local(sort0()); + tc1.push_local(sort1()); + tc1.ctx_id + }; + let mut tc2 = TypeChecker::new(unsafe { &mut *env }); tc2.push_local(sort0()); tc2.push_local(sort1()); - assert_eq!(tc1.ctx_id, tc2.ctx_id); + assert_eq!(id1, tc2.ctx_id); } #[test] @@ -1355,19 +1369,22 @@ mod tests { // outer frame. A `var(0)` with lbr=1 should key only by the inner // suffix, so the two `whnf_key`s should match even though the outer // contexts (and hence ctx_ids) differ. - let mut tc1 = new_tc(); - tc1.push_local(sort0()); // outer A - tc1.push_local(sort1()); // inner X - - let mut tc2 = new_tc(); + let env: *mut KEnv = Box::leak(Box::new(KEnv::::new())); + let e = var(0); // lbr = 1, depends only on innermost frame + let (h1, ctx1, outer1) = { + let mut tc1 = TypeChecker::new(unsafe { &mut *env }); + tc1.push_local(sort0()); // outer A + tc1.push_local(sort1()); // inner X + let (h, c) = tc1.whnf_key(&e); + (h, c, tc1.ctx_id) + }; + let mut tc2 = TypeChecker::new(unsafe { &mut *env }); tc2.push_local(sort1()); // outer B (different from A) tc2.push_local(sort1()); // inner X (same as tc1's inner) // ctx_ids differ (different outer frames). - assert_ne!(tc1.ctx_id, tc2.ctx_id); + assert_ne!(outer1, tc2.ctx_id); - let e = var(0); // lbr = 1, depends only on innermost frame - let (h1, ctx1) = tc1.whnf_key(&e); let (h2, ctx2) = tc2.whnf_key(&e); assert_eq!(h1, h2); assert_eq!( @@ -1682,7 +1699,7 @@ mod tests { // Deep embedding: λ x. app target (var 0) let e = KExpr::::lam( mk_name("x"), - crate::ix::env::BinderInfo::Default, + ix_common::env::BinderInfo::Default, sort0(), KExpr::app(target, var(0)), ); diff --git a/src/ix/kernel/testing.rs b/crates/kernel/src/testing.rs similarity index 97% rename from src/ix/kernel/testing.rs rename to crates/kernel/src/testing.rs index 2fa6e9c9..8c29a274 100644 --- a/src/ix/kernel/testing.rs +++ b/crates/kernel/src/testing.rs @@ -3,9 +3,9 @@ //! Provides convenience constructors for `KExpr`, `KUniv`, `KId`, //! and `KConst` to reduce boilerplate in hand-built test environments. -use crate::ix::address::Address; -use crate::ix::env::{BinderInfo, DefinitionSafety, Name, ReducibilityHints}; -use crate::ix::ixon::constant::DefKind; +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; diff --git a/src/ix/kernel/tutorial/basic.rs b/crates/kernel/src/tutorial/basic.rs similarity index 99% rename from src/ix/kernel/tutorial/basic.rs rename to crates/kernel/src/tutorial/basic.rs index e460fdd2..adbc562d 100644 --- a/src/ix/kernel/tutorial/basic.rs +++ b/crates/kernel/src/tutorial/basic.rs @@ -3,10 +3,10 @@ #[cfg(test)] mod tests { - use crate::ix::env::ReducibilityHints; - use crate::ix::kernel::env::KEnv; - use crate::ix::kernel::mode::Meta; - use crate::ix::kernel::testing::*; + 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) diff --git a/src/ix/kernel/tutorial/defeq.rs b/crates/kernel/src/tutorial/defeq.rs similarity index 98% rename from src/ix/kernel/tutorial/defeq.rs rename to crates/kernel/src/tutorial/defeq.rs index 4c09634b..b97f9ee7 100644 --- a/src/ix/kernel/tutorial/defeq.rs +++ b/crates/kernel/src/tutorial/defeq.rs @@ -3,11 +3,11 @@ #[cfg(test)] mod tests { - use crate::ix::env::Name; - use crate::ix::kernel::constant::{KConst, RecRule}; - use crate::ix::kernel::env::KEnv; - use crate::ix::kernel::mode::Meta; - use crate::ix::kernel::testing::*; + 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) @@ -50,7 +50,7 @@ mod tests { vec![], ty, val, - crate::ix::env::ReducibilityHints::Abbrev, + ix_common::env::ReducibilityHints::Abbrev, ); env.insert(id.clone(), c); check_accepts(&mut env, &id); @@ -811,7 +811,7 @@ mod tests { // Value: fun α r a h p => Eq.refl p (BOGUS — claims reduction happened) let val = ME::lam( mk_name("α"), - crate::ix::env::BinderInfo::Implicit, + ix_common::env::BinderInfo::Implicit, sort1(), nlam( "r", @@ -1069,11 +1069,11 @@ mod tests { let minor_r = app(app(var(0), var(1)), eq_refl_r); let rule_rhs = ME::lam( mk_name("α"), - crate::ix::env::BinderInfo::Implicit, + ix_common::env::BinderInfo::Implicit, sort(param(1)), ME::lam( mk_name("a"), - crate::ix::env::BinderInfo::Implicit, + ix_common::env::BinderInfo::Implicit, var(0), nlam("motive", motive_ty_r, nlam("refl", minor_r, var(0))), ), @@ -1396,7 +1396,7 @@ mod tests { vec![], ty, val, - crate::ix::env::ReducibilityHints::Opaque, + ix_common::env::ReducibilityHints::Opaque, ); env.insert(id.clone(), c); check_rejects(&mut env, &id); @@ -1499,7 +1499,7 @@ mod tests { vec![], ty, val, - crate::ix::env::ReducibilityHints::Opaque, + ix_common::env::ReducibilityHints::Opaque, ); env.insert(id.clone(), c); check_rejects(&mut env, &id); @@ -1523,11 +1523,11 @@ mod tests { // fun {a} {b} (h : And a b) => .proj And 0 h let val = ME::lam( mk_name("a"), - crate::ix::env::BinderInfo::Implicit, + ix_common::env::BinderInfo::Implicit, sort0(), ME::lam( mk_name("b"), - crate::ix::env::BinderInfo::Implicit, + ix_common::env::BinderInfo::Implicit, sort0(), nlam("h", and_ab, ME::prj(mk_id("And"), 0, var(0))), ), @@ -1539,7 +1539,7 @@ mod tests { vec![], ty, val, - crate::ix::env::ReducibilityHints::Abbrev, + ix_common::env::ReducibilityHints::Abbrev, ); env.insert(id.clone(), c); check_accepts(&mut env, &id); @@ -1554,11 +1554,11 @@ mod tests { let val = ME::lam( mk_name("a"), - crate::ix::env::BinderInfo::Implicit, + ix_common::env::BinderInfo::Implicit, sort0(), ME::lam( mk_name("b"), - crate::ix::env::BinderInfo::Implicit, + ix_common::env::BinderInfo::Implicit, sort0(), nlam("h", and_ab, ME::prj(mk_id("And"), 1, var(0))), ), @@ -1570,7 +1570,7 @@ mod tests { vec![], ty, val, - crate::ix::env::ReducibilityHints::Abbrev, + ix_common::env::ReducibilityHints::Abbrev, ); env.insert(id.clone(), c); check_accepts(&mut env, &id); @@ -1903,7 +1903,7 @@ mod tests { vec![], ty, val, - crate::ix::env::ReducibilityHints::Opaque, + ix_common::env::ReducibilityHints::Opaque, ); env.insert(id.clone(), c); id @@ -2071,7 +2071,7 @@ mod tests { vec![], ty, val, - crate::ix::env::ReducibilityHints::Opaque, + ix_common::env::ReducibilityHints::Opaque, ); env.insert(id.clone(), c); check_rejects(&mut env, &id); @@ -2279,7 +2279,7 @@ mod tests { vec![], ty, val, - crate::ix::env::ReducibilityHints::Opaque, + ix_common::env::ReducibilityHints::Opaque, ); env.insert(id.clone(), c); check_rejects(&mut env, &id); diff --git a/src/ix/kernel/tutorial/inductive.rs b/crates/kernel/src/tutorial/inductive.rs similarity index 99% rename from src/ix/kernel/tutorial/inductive.rs rename to crates/kernel/src/tutorial/inductive.rs index 08df9731..67591714 100644 --- a/src/ix/kernel/tutorial/inductive.rs +++ b/crates/kernel/src/tutorial/inductive.rs @@ -3,11 +3,11 @@ #[cfg(test)] mod tests { - use crate::ix::env::{Name, ReducibilityHints}; - use crate::ix::kernel::constant::{KConst, RecRule}; - use crate::ix::kernel::env::KEnv; - use crate::ix::kernel::mode::Meta; - use crate::ix::kernel::testing::*; + 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) diff --git a/src/ix/kernel/tutorial/mod.rs b/crates/kernel/src/tutorial/mod.rs similarity index 100% rename from src/ix/kernel/tutorial/mod.rs rename to crates/kernel/src/tutorial/mod.rs diff --git a/src/ix/kernel/tutorial/reduction.rs b/crates/kernel/src/tutorial/reduction.rs similarity index 98% rename from src/ix/kernel/tutorial/reduction.rs rename to crates/kernel/src/tutorial/reduction.rs index e57e4ae7..aa482201 100644 --- a/src/ix/kernel/tutorial/reduction.rs +++ b/crates/kernel/src/tutorial/reduction.rs @@ -3,12 +3,12 @@ #[cfg(test)] mod tests { - use crate::ix::env::{Name, ReducibilityHints}; - use crate::ix::kernel::constant::KConst; - use crate::ix::kernel::constant::RecRule; - use crate::ix::kernel::env::KEnv; - use crate::ix::kernel::mode::Meta; - use crate::ix::kernel::testing::*; + 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) @@ -985,8 +985,8 @@ mod tests { // 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 crate::ix::address::Address; - use lean_ffi::nat::Nat; + 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); @@ -1005,8 +1005,8 @@ mod tests { let mut env = nat_env(); let nat = || cnst("N", &[]); - use crate::ix::address::Address; - use lean_ffi::nat::Nat; + 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( @@ -1214,11 +1214,11 @@ mod tests { // mk_case fst snd = app(app(var(2), var(1)), var(0)) let rule_rhs = ME::lam( mk_name("α"), - crate::ix::env::BinderInfo::Implicit, + ix_common::env::BinderInfo::Implicit, sort(usucc(param(0))), ME::lam( mk_name("β"), - crate::ix::env::BinderInfo::Implicit, + ix_common::env::BinderInfo::Implicit, sort(usucc(param(1))), nlam( "motive", @@ -1468,7 +1468,7 @@ mod tests { let mut env = KEnv::::new(); add_eq_inductive(&mut env); - use crate::ix::env::QuotKind; + use ix_common::env::QuotKind; // Quot.{u} : {α : Sort u} → (α → α → Prop) → Sort u // depth 1 (inside α): α = var(0) @@ -1627,9 +1627,7 @@ mod tests { env } - fn quot_prims( - env: &KEnv, - ) -> crate::ix::kernel::primitive::Primitives { + 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"); diff --git a/src/ix/kernel/whnf.rs b/crates/kernel/src/whnf.rs similarity index 82% rename from src/ix/kernel/whnf.rs rename to crates/kernel/src/whnf.rs index 16d74f4d..dc7319f7 100644 --- a/src/ix/kernel/whnf.rs +++ b/crates/kernel/src/whnf.rs @@ -2,26 +2,26 @@ //! //! Multi-phase: whnf_core (beta, iota, zeta) → proj → nat → quot → delta. -use std::sync::LazyLock; +use std::sync::{Arc, LazyLock}; use rustc_hash::FxHashSet; -use crate::ix::address::Address; -use crate::ix::ixon::constant::DefKind; +use ix_common::address::Address; +use ixon::constant::DefKind; /// 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: LazyLock> = - LazyLock::new(|| std::env::var("IX_IOTA_STUCK").ok()); +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: LazyLock = - LazyLock::new(|| std::env::var("IX_NAT_EXPAND_LOG").is_ok()); +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 @@ -29,34 +29,34 @@ static IX_NAT_EXPAND_LOG: LazyLock = static NAT_EXPAND_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); -static IX_NAT_IOTA_TRACE: LazyLock = - LazyLock::new(|| std::env::var("IX_NAT_IOTA_TRACE").is_ok()); +static IX_NAT_IOTA_TRACE: crate::EnvFlag = + crate::EnvFlag::new(|| crate::env_var("IX_NAT_IOTA_TRACE").is_ok()); static NAT_IOTA_TRACE_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); -static IX_NAT_LINEAR_REC_TRACE: LazyLock = - LazyLock::new(|| std::env::var("IX_NAT_LINEAR_REC_TRACE").is_ok()); +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: LazyLock = - LazyLock::new(|| std::env::var("IX_WHNF_COUNT_LOG").is_ok()); +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: LazyLock> = - LazyLock::new(|| std::env::var("IX_DELTA_TRACE").ok()); +static IX_DELTA_TRACE: crate::EnvString = + crate::EnvString::new(|| crate::env_var("IX_DELTA_TRACE").ok()); -static IX_PROJ_TRACE: LazyLock> = - LazyLock::new(|| std::env::var("IX_PROJ_TRACE").ok()); +static IX_PROJ_TRACE: crate::EnvString = + crate::EnvString::new(|| crate::env_var("IX_PROJ_TRACE").ok()); -static IX_NAT_TRACE: LazyLock> = - LazyLock::new(|| std::env::var("IX_NAT_TRACE").ok()); +static IX_NAT_TRACE: crate::EnvString = + crate::EnvString::new(|| crate::env_var("IX_NAT_TRACE").ok()); const NAT_REDUCER_OPEN_ARG_REC_FUEL: u64 = 4096; @@ -66,10 +66,12 @@ use super::expr::{ExprData, KExpr}; use super::id::KId; use super::level::KUniv; use super::mode::KernelMode; -use super::subst::{simul_subst, subst, subst_no_intern}; +use super::subst::{ + Clo, MEnv, clo_readback, clo_subst, subst, subst_no_intern, +}; use super::tc::{IotaInfo, MAX_WHNF_FUEL, TypeChecker, collect_app_spine}; -use lean_ffi::nat::Nat; +use bignat::Nat; /// Reduction policy for structural WHNF. /// @@ -107,6 +109,80 @@ struct NatRecLiteralParts { major: Nat, base_idx: usize, step_idx: usize, + major_idx: usize, +} + +/// Which primitive-reducer family a head constant belongs to. The WHNF +/// loops used to probe every reducer per iteration — each collecting its +/// own app spine and running its own gauntlet of 32-byte address +/// compares. Classifying the head ONCE (memoized per address in +/// `KEnv::prim_family_cache`) lets an iteration call at most one family +/// reducer and skip everything for ordinary constants. Mirrors the IxVM +/// `prim_family` dispatch memo. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PrimFamily { + Native, + BitVec, + Nat, + Decidable, + Str, + Other, +} + +fn prim_family_uncached( + p: &Primitives, + addr: &Address, +) -> PrimFamily { + if *addr == p.nat_succ.addr + || *addr == p.nat_add.addr + || *addr == p.nat_sub.addr + || *addr == p.nat_mul.addr + || *addr == p.nat_div.addr + || *addr == p.nat_mod.addr + || *addr == p.nat_pow.addr + || *addr == p.nat_gcd.addr + || *addr == p.nat_land.addr + || *addr == p.nat_lor.addr + || *addr == p.nat_xor.addr + || *addr == p.nat_shift_left.addr + || *addr == p.nat_shift_right.addr + || *addr == p.nat_beq.addr + || *addr == p.nat_ble.addr + { + return PrimFamily::Nat; + } + if *addr == p.nat_dec_le.addr + || *addr == p.nat_dec_eq.addr + || *addr == p.nat_dec_lt.addr + || *addr == p.int_dec_le.addr + || *addr == p.int_dec_eq.addr + || *addr == p.int_dec_lt.addr + { + return PrimFamily::Decidable; + } + if *addr == p.bit_vec_to_nat.addr + || *addr == p.bit_vec_ult.addr + || *addr == p.decidable_decide.addr + { + return PrimFamily::BitVec; + } + if *addr == p.punit_size_of_1.addr + || *addr == p.subtype_val.addr + || *addr == p.size_of_size_of.addr + || *addr == p.system_platform_num_bits.addr + || *addr == p.reduce_bool.addr + || *addr == p.reduce_nat.addr + { + return PrimFamily::Native; + } + if *addr == p.string_back.addr + || *addr == p.string_legacy_back.addr + || *addr == p.string_utf8_byte_size.addr + || *addr == p.string_to_byte_array.addr + { + return PrimFamily::Str; + } + PrimFamily::Other } impl TypeChecker<'_, M> { @@ -116,14 +192,14 @@ impl TypeChecker<'_, M> { original: &KExpr, current: &KExpr, ) { - if std::env::var("IX_WHNF_FUEL_DUMP").is_err() + 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); - eprintln!( + log::info!( "[whnf fuel] {phase} const={} depth={} original_head={} original_args={} current_head={} current_args={}", self.debug_label.as_deref().unwrap_or(""), self.depth(), @@ -132,8 +208,8 @@ impl TypeChecker<'_, M> { cur_head, cur_args.len() ); - eprintln!(" original: {original}"); - eprintln!(" current: {current}"); + log::info!(" original: {original}"); + log::info!(" current: {current}"); } fn dump_delta_trace(&self, id: &KId, arity: usize, e: &KExpr) { @@ -147,7 +223,7 @@ impl TypeChecker<'_, M> { if !filter.is_empty() && !id_s.contains(filter) { return; } - eprintln!( + log::info!( "[delta] const={} depth={} head={} args={arity} expr={}", self.debug_label.as_deref().unwrap_or(""), self.depth(), @@ -176,7 +252,7 @@ impl TypeChecker<'_, M> { } let (head, args) = collect_app_spine(wval); match result { - Some(result) => eprintln!( + Some(result) => log::info!( "[proj] const={} depth={} proj={} field={} struct_head={} struct_args={} ctor_params={:?} result={}", self.debug_label.as_deref().unwrap_or(""), self.depth(), @@ -187,7 +263,7 @@ impl TypeChecker<'_, M> { ctor_params, result ), - None => eprintln!( + None => log::info!( "[proj] const={} depth={} proj={} field={} struct_head={} struct_args={} ctor_params={:?} result=", self.debug_label.as_deref().unwrap_or(""), self.depth(), @@ -212,7 +288,7 @@ impl TypeChecker<'_, M> { if !filter.is_empty() && !head_s.contains(filter) { return; } - eprintln!( + log::info!( "[nat] const={} depth={} phase={} head={} args={} expr={}", self.debug_label.as_deref().unwrap_or(""), self.depth(), @@ -236,7 +312,7 @@ impl TypeChecker<'_, M> { if *IX_WHNF_COUNT_LOG { let n = WHNF_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); if n.is_multiple_of(100_000) && n > 0 { - eprintln!("[whnf] count={n}"); + log::info!("[whnf] count={n}"); } } // Quick exit for non-reducing forms. @@ -290,10 +366,17 @@ impl TypeChecker<'_, M> { break; } - // Native reduction: Lean.reduceBool, Lean.reduceNat, System.Platform.numBits - // (mirrors lean4 `type_checker.cpp:667-672` and lean4lean - // `TypeChecker.lean:438` — `reduce_native` runs before `reduce_nat`). - if let Some(reduced) = self.try_reduce_native(&cur)? { + // Primitive reduction, dispatched by memoized head family — the + // five recognizers below each collect their own spine and run their + // own address gauntlet, so probing all of them every iteration was + // a measurable tax on ordinary (Other-headed) terms. Semantics are + // unchanged: the families' head-address sets are disjoint, so at + // most one recognizer could fire anyway. Reference order preserved + // (native before nat: lean4 `type_checker.cpp:667-672`). + let family = self.head_prim_family(&cur); + if family == PrimFamily::Native + && let Some(reduced) = self.try_reduce_native(&cur)? + { cur = reduced; continue; } @@ -301,7 +384,9 @@ impl TypeChecker<'_, M> { // BitVec definitions reduce through Nat comparisons. Keep this before // delta so small definitional facts such as `x < 0#w` collapse // without unfolding the full Fin-backed representation of BitVec. - if let Some(reduced) = self.try_reduce_bitvec(&cur)? { + if family == PrimFamily::BitVec + && let Some(reduced) = self.try_reduce_bitvec(&cur)? + { cur = reduced; continue; } @@ -309,25 +394,46 @@ impl TypeChecker<'_, M> { // Nat primitive reduction in main WHNF loop (lean4lean TypeChecker.lean:439). // Must run BEFORE delta_unfold_one, so that Nat.sub/Nat.pow/etc. get // short-circuited before their bodies (which use Nat.rec) are exposed. - if let Some(reduced) = - self.try_reduce_nat_with_succ_mode(&cur, nat_succ_mode)? + if family == PrimFamily::Nat + && let Some(reduced) = + self.try_reduce_nat_with_succ_mode(&cur, nat_succ_mode)? { cur = reduced; continue; } // Nat decidability: Nat.decLe/decEq/decLt on literals → Decidable.isTrue/isFalse. // Must run BEFORE delta, so the body (which uses dite/Nat.rec) is never exposed. - if let Some(reduced) = self.try_reduce_decidable(&cur)? { + if family == PrimFamily::Decidable + && let Some(reduced) = self.try_reduce_decidable(&cur)? + { cur = reduced; continue; } // String literal primitives such as `String.back ""`. - if let Some(reduced) = self.try_reduce_string(&cur)? { + if family == PrimFamily::Str + && let Some(reduced) = self.try_reduce_string(&cur) + { cur = reduced; continue; } + // Keep `Nat.add base (Lit n)` (symbolic base, n > 0) and + // `Nat.div/mod base (Lit k)` (k ≥ 2) STUCK as a compact offset instead + // of delta-unfolding: `Nat.add` would materialize succ^n(base) — O(n) + // substitution per layer — and `Nat.div/mod` would expand the division + // algorithm, even though both are irreducible for a symbolic base. + // (`Nat.shiftRight x k` unfolds to k nested `Nat.div _ 2`, which now + // stay stuck.) Iota over such a major still works via + // `cleanup_nat_offset_major`; def-eq decides offset pairs in + // `try_def_eq_offset`. Mirrors IxVM 5dcab7f/f7cfe23. + if family == PrimFamily::Nat + && let Some(stuck) = self.try_nat_offset_stuck(&cur)? + { + cur = stuck; + break; + } + if let Some(unfolded) = self.delta_unfold_one(&cur)? { cur = unfolded; continue; @@ -538,31 +644,15 @@ impl TypeChecker<'_, M> { let (f0, args) = collect_app_spine(&cur); let f = self.whnf_core_with_flags(&f0, flags)?; - // Multi-arg beta + // Beta: enter the environment machine. Subsequent betas/zetas are + // O(1) environment pushes; substitution materializes only at the + // machine's exit, which re-enters this loop as the new `cur` (so + // ambient zeta / iota / prim dispatch see exactly what the old + // eager-substitution path produced). Entry is gated on an actual + // beta firing — Const-headed terms (e.g. literal recursor loops) + // never pay the closure-wrap + readback overhead. if matches!(f.data(), ExprData::Lam(..)) { - let mut body = f; - // Pre-size: at most one arg is consumed per outer Lam, capped by - // `args.len()`. Pre-sizing skips the first growth reallocation - // for non-trivial spines on this hot path. - let mut consumed_args = Vec::with_capacity(args.len()); - while consumed_args.len() < args.len() { - if let ExprData::Lam(_, _, _, inner, _) = body.data() { - let inner = inner.clone(); - consumed_args.push(args[consumed_args.len()].clone()); - body = inner; - } else { - break; - } - } - let remaining_start = consumed_args.len(); - if !consumed_args.is_empty() { - consumed_args.reverse(); - body = simul_subst(&mut self.env.intern, &body, &consumed_args, 0); - } - for arg in &args[remaining_start..] { - body = self.intern(KExpr::app(body, arg.clone())); - } - cur = body; + cur = self.machine_whnf(f, &args, &mut fuel, flags)?; continue; } @@ -589,6 +679,324 @@ impl TypeChecker<'_, M> { } } + /// The WHNF environment machine (Krivine-style, structural fragment). + /// See `docs/env_machine_whnf.md`; mirrors the IxVM `mwhnf_spine`. + /// + /// Entered from the App arm of [`Self::whnf_core_with_flags_uncached`] + /// when a beta is about to fire: `head` is the already-whnf_core'd + /// `Lam`, `args` the plain argument spine nearest-first. Machine state + /// is `(head, env, spine)`: `head` a raw subterm of the program, `env` + /// the closure environment its loose `Var`s refer to, and `spine` the + /// pending argument closures used as a stack — NEAREST argument last. + /// + /// Transitions (each O(1), no substitution): + /// `App(f, a)` push `Clo{a, env}`; descend to `f` + /// `Lam` + pending arg pop spine, push env (**beta**) + /// `Let` push `Clo{val, env}` (**zeta**) + /// `Var(i)`, `i < n` jump into `env[i]`'s closure + /// + /// Everything else EXITS: the head and remaining spine read back to a + /// plain expression (`clo_subst` — work proportional to what the + /// reduction actually consumes), and the caller's loop continues with + /// it. That exit contract keeps ambient let/LDecl zeta (the outer + /// Var/FVar arms), iota, projection reduction under the `flags` cheap + /// policy, and prim dispatch byte-identical to the eager path the + /// machine replaces. + /// + /// Fuel: beta and zeta charge the caller's budget — the same + /// one-substitution-event-per-tick granularity as the eager path. + /// The transitions between charges (App peels, var jumps) are bounded + /// by program structure reachable from the fueled pushes, so total + /// work stays fuel-bounded. + fn machine_whnf( + &mut self, + head: KExpr, + args: &[KExpr], + fuel: &mut u32, + flags: WhnfFlags, + ) -> Result, TcError> { + let mut head = head; + let mut env: MEnv = MEnv::empty(); + let mut spine: Vec>> = + args.iter().rev().map(|a| Arc::new(Clo::closed(a.clone()))).collect(); + + loop { + match head.data() { + ExprData::App(f, a, _) => { + let c = Arc::new(Clo::new(a.clone(), env.clone())); + let f = f.clone(); + spine.push(c); + head = f; + }, + + ExprData::Lam(name, bi, ty, body, _) => { + if let Some(c) = spine.pop() { + // Beta: O(1) environment push. + if *fuel == 0 { + return Err(TcError::MaxRecDepth); + } + *fuel -= 1; + let body = body.clone(); + env = env.push(c); + head = body; + } else { + // Value. Read back under one binder. + if env.len() == 0 { + return Ok(head.clone()); + } + let ty2 = clo_subst(&mut self.env.intern, ty, &env, 0); + let body2 = clo_subst(&mut self.env.intern, body, &env, 1); + return Ok(self.intern(KExpr::lam( + name.clone(), + bi.clone(), + ty2, + body2, + ))); + } + }, + + ExprData::Let(_, _, val, body, _, _) => { + // Zeta: O(1) environment push. + if *fuel == 0 { + return Err(TcError::MaxRecDepth); + } + *fuel -= 1; + let c = Arc::new(Clo::new(val.clone(), env.clone())); + let body = body.clone(); + env = env.push(c); + head = body; + }, + + ExprData::Var(i, name, _) => { + let i = *i; + if i < env.len() { + // Machine-bound variable: jump into its closure. This is + // where laziness pays — the entry was never substituted. + let c = env.get(i).clone(); + head = c.e.clone(); + env = c.env.clone(); + } else { + // Ambient variable: shift below the machine binders and + // exit stuck; the outer loop's Var arm handles legacy + // let-bound zeta on it. + let h = self.intern(KExpr::var(i - env.len(), name.clone())); + return Ok(self.machine_exit(h, &spine)); + } + }, + + // Closure-iota (Phase B; mirrors IxVM try_iota_c): a recursor + // head consumes the spine LAZILY. On the main ctor-rule path the + // rule RHS re-enters the machine with the params/motives/minors + // and post-major arguments as their ORIGINAL closures (plus the + // ctor's fields wrapped closed) — the rule's Lam-chain betas push + // them straight into an environment, so unselected minors + // (dropped match/Decidable branches) are never substituted and + // never read back. Anything off the main path misses to the + // plain readback exit, whose `try_iota` redoes the major's whnf + // against a warm cache. Gated like the eager path: cheap_rec + // mode never runs full iota from inside the machine. + ExprData::Const(id, us, _) => { + if !flags.cheap_rec { + let id = id.clone(); + let us: Vec> = us.to_vec(); + if let Some((rhs, new_spine)) = + self.try_iota_clo(&id, &us, &spine)? + { + if *fuel == 0 { + return Err(TcError::MaxRecDepth); + } + *fuel -= 1; + head = rhs; + env = MEnv::empty(); + spine = new_spine; + continue; + } + } + let h = head.clone(); + return Ok(self.machine_exit(h, &spine)); + }, + + // Stuck or dispatch-owned heads (no loose Vars of their own): + // exit; the outer loop applies prim dispatch / LDecl zeta + // exactly as before. + ExprData::FVar(..) + | ExprData::Sort(..) + | ExprData::Nat(..) + | ExprData::Str(..) => { + let h = head.clone(); + return Ok(self.machine_exit(h, &spine)); + }, + + // Pi value (or ill-typed Pi application): read back; outer loop + // returns it (spine empty) or leaves it stuck (non-empty), as + // the eager path did. + ExprData::All(name, bi, ty, body, _) => { + let h = if env.len() == 0 { + head.clone() + } else { + let ty2 = clo_subst(&mut self.env.intern, ty, &env, 0); + let body2 = clo_subst(&mut self.env.intern, body, &env, 1); + self.intern(KExpr::all(name.clone(), bi.clone(), ty2, body2)) + }; + return Ok(self.machine_exit(h, &spine)); + }, + + // Projection: read the scrutinee back and exit; the outer loop + // reduces the projection under the caller's `flags` cheap/full + // policy (its Prj arm, or the App arm's head reduction when the + // spine is non-empty). + ExprData::Prj(id, field, val, _) => { + let h = if env.len() == 0 { + head.clone() + } else { + let val2 = clo_subst(&mut self.env.intern, val, &env, 0); + self.intern(KExpr::prj(id.clone(), *field, val2)) + }; + return Ok(self.machine_exit(h, &spine)); + }, + } + } + } + + /// Read the machine's remaining spine back onto an exit head: + /// `h a₁ … aₙ` with each argument materialized via [`clo_readback`]. + /// `spine` holds the nearest argument LAST. + fn machine_exit(&mut self, h: KExpr, spine: &[Arc>]) -> KExpr { + let mut cur = h; + for c in spine.iter().rev() { + let a = clo_readback(&mut self.env.intern, c); + cur = self.intern(KExpr::app(cur, a)); + } + cur + } + + /// Closure-spine iota for the machine's `Const` exit: the main + /// ctor-rule path of [`Self::try_iota_with_flags`], consuming the + /// machine spine lazily. Returns the level-instantiated rule RHS and + /// the machine spine to re-enter with — the params/motives/minors and + /// post-major arguments ride through as their original closures + /// (never read back; only the major materializes), the ctor's field + /// arguments are wrapped closed. + /// + /// Everything off the main path returns `None`, and the caller falls + /// back to the plain readback exit whose `try_iota` is complete: + /// - K recursors (nullary-ctor synthesis needs infer + def-eq), + /// - `Nat` literal majors (the transient-work discipline must keep + /// literal succ-chains out of the interner and the whnf caches, and + /// the linear-rec/offset shortcuts live on the plain path), + /// - `Str` literal majors (ctor coercion + re-whnf), + /// - struct-eta candidates and genuinely stuck majors. + /// + /// A miss costs one readback of the major closure; the plain path's + /// own whnf of that major then hits a warm cache. + fn try_iota_clo( + &mut self, + rec_id: &KId, + rec_us: &[KUniv], + spine: &[Arc>], + ) -> Result, Vec>>)>, TcError> { + let recr = match self.try_get_const(rec_id)? { + Some(KConst::Recr { + k, + params, + motives, + minors, + indices, + rules, + lvls, + .. + }) => { + if k { + return Ok(None); + } + let major_idx = u64_to_usize::(params + motives + minors + indices)?; + if spine.len() <= major_idx { + return Ok(None); + } + // H6: level params arity (lean4lean Reduce.lean:76). + if rec_us.len() as u64 != lvls { + return Ok(None); + } + IotaInfo { + k, + params: u64_to_usize::(params)?, + motives: u64_to_usize::(motives)?, + minors: u64_to_usize::(minors)?, + indices: u64_to_usize::(indices)?, + major_idx, + rules, + lvls, + } + }, + _ => return Ok(None), + }; + + // Materialize ONLY the major. `spine` is nearest-LAST, so the i-th + // argument nearest-first sits at `spine[len - 1 - i]`. + let len = spine.len(); + let major_clo = spine[len - 1 - recr.major_idx].clone(); + let major = clo_readback(&mut self.env.intern, &major_clo); + // Mirror the eager path's pre- and post-whnf offset cleanups so + // `Nat.add base k` majors expose a `Nat.succ` layer here instead of + // missing to a second full attempt. + let major = match self.cleanup_nat_offset_major(&major)? { + Some(cleaned) => cleaned, + None => major, + }; + let mut major_whnf = self.whnf(&major)?; + if matches!(major_whnf.data(), ExprData::Nat(..)) { + return Ok(None); + } + if let Some(cleaned) = self.cleanup_nat_offset_major(&major_whnf)? { + major_whnf = cleaned; + } + if matches!(major_whnf.data(), ExprData::Str(..)) { + return Ok(None); + } + + let (ctor_head, ctor_args) = collect_app_spine(&major_whnf); + let ctor_id = match ctor_head.data() { + ExprData::Const(id, _, _) => id.clone(), + _ => return Ok(None), + }; + let (cidx, ctor_fields) = match self.try_get_const(&ctor_id)? { + Some(KConst::Ctor { cidx, fields, .. }) => { + (u64_to_usize::(cidx)?, u64_to_usize::(fields)?) + }, + _ => return Ok(None), + }; + if cidx >= recr.rules.len() { + return Ok(None); + } + // H5: nfields ≤ major args (lean4lean Reduce.lean:75). + if ctor_fields > ctor_args.len() { + return Ok(None); + } + + crate::perf::record_iota_histo(&rec_id.addr); + let rule = &recr.rules[cidx]; + let rec_us_vec: Vec<_> = rec_us.to_vec(); + let rhs = self.instantiate_univ_params(&rule.rhs, &rec_us_vec)?; + + // New machine spine, nearest-LAST. Nearest-first the rule sees + // `pmm ++ ctor fields ++ post-major`; in machine order that is the + // post-major prefix of `spine` (indices below the major's slot), + // then the fields reversed, then the pmm suffix of `spine`. The + // index arguments between pmm and the major are dropped, as in the + // eager path. + let pmm_end = recr.params + recr.motives + recr.minors; + let field_start = ctor_args.len() - ctor_fields; + let post_len = len - 1 - recr.major_idx; + let mut new_spine: Vec>> = + Vec::with_capacity(post_len + ctor_fields + pmm_end); + new_spine.extend_from_slice(&spine[..post_len]); + for arg in ctor_args[field_start..].iter().rev() { + new_spine.push(Arc::new(Clo::closed(arg.clone()))); + } + new_spine.extend_from_slice(&spine[len - pmm_end..]); + Ok(Some((rhs, new_spine))) + } + /// WHNF without delta: whnf_core → proj-app → nat/native/string → quot. /// Projection values use full WHNF, preserving the public/full semantics. pub fn whnf_no_delta( @@ -696,15 +1104,23 @@ impl TypeChecker<'_, M> { continue; } + // Primitive reduction, dispatched by memoized head family (see the + // main WHNF loop) — family head sets are disjoint, so dispatching + // replaces probe-everything without changing semantics. + let family = self.head_prim_family(&cur); + // BitVec.toNat/ult reductions are definitional wrappers around Nat. - if let Some(reduced) = self.try_reduce_bitvec(&cur)? { + if family == PrimFamily::BitVec + && let Some(reduced) = self.try_reduce_bitvec(&cur)? + { cur = reduced; continue; } // Nat primitive reduction - if let Some(reduced) = - self.try_reduce_nat_with_succ_mode(&cur, nat_succ_mode)? + if family == PrimFamily::Nat + && let Some(reduced) = + self.try_reduce_nat_with_succ_mode(&cur, nat_succ_mode)? { cur = reduced; continue; @@ -715,13 +1131,17 @@ impl TypeChecker<'_, M> { // `Subtype.val` and `String.toByteArray` are projection definitions; // once rewritten to `Prj`, the cheap primitive recognizers no longer // see the original head. - if let Some(reduced) = self.try_reduce_native(&cur)? { + if family == PrimFamily::Native + && let Some(reduced) = self.try_reduce_native(&cur)? + { cur = reduced; continue; } // String literal primitives. - if let Some(reduced) = self.try_reduce_string(&cur)? { + if family == PrimFamily::Str + && let Some(reduced) = self.try_reduce_string(&cur) + { cur = reduced; continue; } @@ -885,7 +1305,9 @@ impl TypeChecker<'_, M> { minors: u64_to_usize::(minors)?, indices: u64_to_usize::(indices)?, major_idx, - rules: rules.clone(), + // `rules` is already owned here (moved out of the KConst clone + // `try_get_const` returned) — do not clone it again. + rules, lvls, } }, @@ -934,7 +1356,7 @@ impl TypeChecker<'_, M> { let n = NAT_IOTA_TRACE_COUNT .fetch_add(1, std::sync::atomic::Ordering::Relaxed); if n < 32 { - eprintln!( + log::info!( "[nat_iota_trace] rec={} major_bits={} spine={} major_idx={}", rec_id, val.0.bits(), @@ -978,13 +1400,14 @@ impl TypeChecker<'_, M> { if !is_ctor && let Some(filter) = IX_IOTA_STUCK.as_ref() { let rec_name = format!("{rec_id}"); if filter.is_empty() || rec_name.contains(filter) { - eprintln!("[iota stuck] rec={rec_name}"); - eprintln!("[iota stuck] major: {major}"); - eprintln!("[iota stuck] major whnf: {major_whnf}"); + log::info!("[iota stuck] rec={rec_name}"); + log::info!("[iota stuck] major: {major}"); + log::info!("[iota stuck] major whnf: {major_whnf}"); } } if is_ctor { + crate::perf::record_iota_histo(&rec_id.addr); let ctor_id = match ctor_head.data() { ExprData::Const(id, _, _) => id, _ => unreachable!(), @@ -1065,23 +1488,43 @@ impl TypeChecker<'_, M> { /// Nat literal iota can create a long chain of distinct predecessor terms. /// These terms are useful only while the current WHNF is executing; keeping /// each one in the global WHNF caches makes RSS linear in the literal. + /// Allocation-free spine probe: head expression and arg count without + /// materializing the spine. The transient-nat probes below run on + /// *every* whnf call **before** the cache lookup, so they must not + /// heap-allocate on the (overwhelmingly common) non-Nat-recursor path — + /// the previous implementation paid two `collect_app_spine` Vec + /// allocations plus a `KConst::Recr` clone per call, defeating the + /// cache on the hottest path in a full check. (Ported from jcb/fixes + /// H-15.) + fn spine_head_and_len(e: &KExpr) -> (&KExpr, usize) { + let mut cur = e; + let mut n = 0usize; + while let ExprData::App(f, _, _) = cur.data() { + n += 1; + cur = f; + } + (cur, n) + } + fn is_transient_nat_literal_work( &mut self, e: &KExpr, ) -> Result> { - if self.is_nat_literal_recursor_app(e)? { - return Ok(true); - } - - let (head, args) = collect_app_spine(e); + let (head, nargs) = Self::spine_head_and_len(e); let ExprData::Const(id, _, _) = head.data() else { return Ok(false); }; - - if id.addr == self.prims.nat_succ.addr && args.len() == 1 { - return self.is_nat_literal_recursor_app(&args[0]); + if id.addr == self.prims.nat_rec.addr + || id.addr == self.prims.nat_cases_on.addr + { + return self.is_nat_literal_recursor_app(e); + } + if id.addr == self.prims.nat_succ.addr + && nargs == 1 + && let ExprData::App(_, arg, _) = e.data() + { + return self.is_nat_literal_recursor_app(arg); } - Ok(false) } @@ -1089,7 +1532,9 @@ impl TypeChecker<'_, M> { &mut self, e: &KExpr, ) -> Result> { - let (head, spine) = collect_app_spine(e); + // Cheap pre-filter first; only fall through to the allocating spine + // collection + recursor lookup when the head can actually match. + let (head, _) = Self::spine_head_and_len(e); let ExprData::Const(id, _, _) = head.data() else { return Ok(false); }; @@ -1104,6 +1549,7 @@ impl TypeChecker<'_, M> { else { return Ok(false); }; + let (_, spine) = collect_app_spine(e); let major_idx = u64_to_usize::(params + motives + minors + indices)?; Ok( spine @@ -1232,6 +1678,114 @@ impl TypeChecker<'_, M> { None } + /// If `e` is `Nat.add base (Lit n)` (n > 0) or `Nat.div/mod base (Lit k)` + /// (k ≥ 2) with a non-literal base, return the same operation in canonical + /// compact form so the WHNF loop can leave it stuck instead of + /// delta-unfolding. Thresholds keep `x + 0`, `x / 1`, `x / 0` (and mod) + /// reducing through the normal path. `None` means "not this shape — + /// proceed normally". + fn try_nat_offset_stuck( + &mut self, + e: &KExpr, + ) -> Result>, TcError> { + // Allocation-free quick reject: this probe runs once per delta-unfold + // loop iteration, so don't collect a spine Vec unless the head constant + // is one of the three Nat primitives. + { + let mut cur = e; + loop { + match cur.data() { + ExprData::App(f, _, _) => cur = f, + ExprData::Const(id, _, _) => { + if id.addr != self.prims.nat_add.addr + && id.addr != self.prims.nat_div.addr + && id.addr != self.prims.nat_mod.addr + { + return Ok(None); + } + break; + }, + _ => return Ok(None), + } + } + } + let (head, args) = collect_app_spine(e); + let ExprData::Const(id, _, _) = head.data() else { + return Ok(None); + }; + let is_add = id.addr == self.prims.nat_add.addr; + let is_divmod = + id.addr == self.prims.nat_div.addr || id.addr == self.prims.nat_mod.addr; + if (!is_add && !is_divmod) || args.len() != 2 { + return Ok(None); + } + let Some(wb) = self.whnf_nat_reducer_arg(&args[1])? else { + return Ok(None); + }; + let Some(n) = extract_nat_value(&wb, &self.prims) else { + return Ok(None); + }; + if n.0 == num_bigint::BigUint::ZERO { + return Ok(None); + } + if is_divmod && n.0 == num_bigint::BigUint::from(1u64) { + return Ok(None); + } + let Some(wa) = self.whnf_nat_reducer_arg(&args[0])? else { + return Ok(None); + }; + if extract_nat_value(&wa, &self.prims).is_some() { + // Both sides literal: this is closed arithmetic for `try_reduce_nat`, + // not a stuck offset. + return Ok(None); + } + let lit = self.nat_expr_from_value(n); + let inner = self.intern(KExpr::app(head.clone(), wa)); + Ok(Some(self.intern(KExpr::app(inner, lit)))) + } + + /// Decompose a (whnf'd) Nat term into `(base, offset)` for offset-aware + /// def-eq: `Lit n` → `(None, n)`; `succ^j(Nat.add core (Lit m))` → + /// `(Some(core), j + m)` read in O(1) per layer via [`Self::nat_offset`] + /// instead of peeled one `is_def_eq` recursion level at a time. + /// `base = None` means the core is literal zero (the term IS a numeral). + /// `None` (the outer Option) means "not offset-shaped". + pub(super) fn nat_offset_decompose( + &mut self, + e: &KExpr, + ) -> Result>, Nat)>, TcError> { + if let Some(v) = extract_nat_value(e, &self.prims) { + return Ok(Some((None, v))); + } + match self.nat_offset(e, 0)? { + Some((base, off)) if off.0 > num_bigint::BigUint::ZERO => { + if let Some(bv) = extract_nat_value(&base, &self.prims) { + Ok(Some((None, Nat(bv.0 + off.0)))) + } else { + Ok(Some((Some(base), off))) + } + }, + _ => Ok(None), + } + } + + /// Rebuild `base + r` in the compact offset form used by + /// [`Self::try_nat_offset_stuck`]. + pub(super) fn nat_offset_rebuild( + &mut self, + base: Option>, + r: Nat, + ) -> KExpr { + match base { + None => self.nat_expr_from_value(r), + Some(b) if r.0 == num_bigint::BigUint::ZERO => b, + Some(b) => { + let lit = self.nat_expr_from_value(r); + self.mk_nat_add(b, lit) + }, + } + } + fn mk_nat_succ(&mut self, pred: KExpr) -> KExpr { let succ = KExpr::cnst(self.prims.nat_succ.clone(), Box::new([])); KExpr::app(succ, pred) @@ -1672,7 +2226,7 @@ impl TypeChecker<'_, M> { let n = NAT_EXPAND_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); if n.is_multiple_of(10_000) { - eprintln!("[nat_to_constructor] count={n} val_bits={}", val.0.bits()); + log::info!("[nat_to_constructor] count={n} val_bits={}", val.0.bits()); } } if val.0 == BigUint::ZERO { @@ -1693,6 +2247,26 @@ impl TypeChecker<'_, M> { } /// Nat primitive reduction (add, sub, mul, div, mod, pow, gcd, bitwise, predicates). + /// Classify `e`'s head constant into a primitive-reducer family. + /// Allocation-free app-chain walk + per-address memo. + pub(super) fn head_prim_family(&mut self, e: &KExpr) -> PrimFamily { + let mut cur = e; + loop { + match cur.data() { + ExprData::App(f, _, _) => cur = f, + ExprData::Const(id, _, _) => { + if let Some(&fam) = self.env.prim_family_cache.get(&id.addr) { + return fam; + } + let fam = prim_family_uncached(&self.prims, &id.addr); + self.env.prim_family_cache.insert(id.addr.clone(), fam); + return fam; + }, + _ => return PrimFamily::Other, + } + } + } + pub(super) fn try_reduce_nat( &mut self, e: &KExpr, @@ -1792,6 +2366,18 @@ impl TypeChecker<'_, M> { &mut self, arg: &KExpr, ) -> Result>, TcError> { + // 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(); @@ -1814,11 +2400,24 @@ impl TypeChecker<'_, M> { && id.addr == self.prims.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); } } @@ -1842,7 +2441,7 @@ impl TypeChecker<'_, M> { .fetch_add(1, std::sync::atomic::Ordering::Relaxed); if n < 8 { let step_whnf = self.whnf(step)?; - eprintln!( + log::info!( "[nat_linear_rec] major_bits={} base_idx={} step_idx={} spine={} step_whnf={}", parts.major.0.bits(), parts.base_idx, @@ -1859,7 +2458,20 @@ impl TypeChecker<'_, M> { let base = base.clone(); let base_whnf = self.whnf(&base)?; let Some(base_val) = extract_nat_value(&base_whnf, &self.prims) else { - return Ok(None); + // Symbolic base: collapse `succ^offset(Nat.rec base succ-step (Lit n))` + // to the compact offset `Nat.add base (Lit (n + offset))` rather than + // declining into n iota steps that materialize succ^n(base). Keeps the + // value in the same `base + k` form a literal already has, so def-eq + // converges instead of descending n unary succ layers. Conservative: + // only when the recursor application carries no post-major arguments. + // Mirrors IxVM dbc4177. + if parts.spine.len() != parts.major_idx + 1 { + return Ok(None); + } + let total = Nat(&parts.major.0 + offset); + let lit = self.nat_expr_from_value(total); + let result = self.mk_nat_add(base_whnf, lit); + return Ok(Some(result)); }; let mut total = base_val.0; @@ -1906,7 +2518,7 @@ impl TypeChecker<'_, M> { }; let major = major.clone(); - Ok(Some(NatRecLiteralParts { spine, major, base_idx, step_idx })) + Ok(Some(NatRecLiteralParts { spine, major, base_idx, step_idx, major_idx })) } fn is_nat_succ_ih_step( @@ -2756,46 +3368,43 @@ impl TypeChecker<'_, M> { // String primitive reduction // ----------------------------------------------------------------------- - pub(super) fn try_reduce_string( - &mut self, - e: &KExpr, - ) -> Result>, TcError> { + pub(super) fn try_reduce_string(&mut self, e: &KExpr) -> Option> { let (head, args) = collect_app_spine(e); if args.len() != 1 { - return Ok(None); + return None; } let ExprData::Const(id, _, _) = head.data() else { - return Ok(None); + return None; }; let is_back = id.addr == self.prims.string_back.addr || id.addr == self.prims.string_legacy_back.addr; let is_utf8_byte_size = id.addr == self.prims.string_utf8_byte_size.addr; let is_to_byte_array = id.addr == self.prims.string_to_byte_array.addr; if !is_back && !is_utf8_byte_size && !is_to_byte_array { - return Ok(None); + return None; } let s = match args[0].data() { ExprData::Str(s, _, _) => s, - _ => return Ok(None), + _ => return 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)))); + return Some(self.intern(KExpr::nat(n, addr))); } if is_to_byte_array { if s.is_empty() { - return Ok(Some(self.intern(KExpr::cnst( + return Some(self.intern(KExpr::cnst( self.prims.byte_array_empty.clone(), Box::new([]), - )))); + ))); } - return Ok(None); + return None; } let codepoint = s.chars().last().map_or(65u32, u32::from); - Ok(Some(self.char_of_nat_expr(u64::from(codepoint)))) + Some(self.char_of_nat_expr(u64::from(codepoint))) } fn char_of_nat_expr(&mut self, n: u64) -> KExpr { @@ -2885,6 +3494,15 @@ fn compute_nat_bin( b: &Nat, ) -> Option { use num_bigint::BigUint; + // Output-size cap for natively-computed results. `shiftLeft` and `pow` + // amplify a tiny term into a result exponentially larger than the input + // bytes (`Nat.shiftLeft 1 (1 << 40)` is a ~12-byte term demanding a + // ~128 GiB allocation). Refuse to materialize anything wider and leave + // the term stuck, exactly like the `pow` exponent guard below. 2^26 bits + // (8 MiB) keeps every `2^e, e <= ReducePowMaxExp` reduction the C++ + // kernel performs while bounding the allocation an adversarial term can + // force. (Ported from jcb/fixes H-12.) + const MAX_NAT_REDUCE_BITS: u64 = 1 << 26; let zero = BigUint::ZERO; let r = if *addr == p.nat_add.addr { &a.0 + &b.0 @@ -2901,7 +3519,14 @@ fn compute_nat_bin( const REDUCE_POW_MAX_EXP: u64 = 1 << 24; // 16_777_216 match b.to_u64() { #[allow(clippy::cast_possible_truncation)] // guarded: exp <= 2^24 - Some(exp) if exp <= REDUCE_POW_MAX_EXP => a.0.pow(exp as u32), + Some(exp) if exp <= REDUCE_POW_MAX_EXP => { + // The exponent cap alone does not bound the *result*: a wide base + // with an allowed exponent still explodes (bits(a) * exp). + if a.0.bits().saturating_mul(exp) > MAX_NAT_REDUCE_BITS { + return None; + } + a.0.pow(exp as u32) + }, _ => return None, // too large to compute } } else if *addr == p.nat_gcd.addr { @@ -2913,8 +3538,16 @@ fn compute_nat_bin( } else if *addr == p.nat_xor.addr { &a.0 ^ &b.0 } else if *addr == p.nat_shift_left.addr { - let shift = usize::try_from(b.to_u64()?).ok()?; - &a.0 << shift + let shift = b.to_u64()?; + if a.0 == zero { + zero + } else if a.0.bits().saturating_add(shift) > MAX_NAT_REDUCE_BITS { + // Result has exactly bits(a) + shift bits — refuse to materialize. + return None; + } else { + let shift = usize::try_from(shift).ok()?; + &a.0 << shift + } } else if *addr == p.nat_shift_right.addr { let shift = usize::try_from(b.to_u64()?).ok()?; &a.0 >> shift @@ -3027,9 +3660,9 @@ mod tests { use super::super::primitive::Primitives; use super::super::tc::TypeChecker; use super::*; - use crate::ix::address::Address; - use crate::ix::env::{DefinitionSafety, ReducibilityHints}; - use crate::ix::ixon::constant::DefKind; + use ix_common::address::Address; + use ix_common::env::{DefinitionSafety, ReducibilityHints}; + use ixon::constant::DefKind; type AE = KExpr; type AU = KUniv; @@ -3172,6 +3805,166 @@ mod tests { assert_eq!(tc.whnf(&app).unwrap(), sort0()); } + // ---- environment-machine readback paths ---- + + #[test] + fn whnf_machine_open_term_shift() { + let mut env = env_with_id(); + let mut tc = TypeChecker::new(&mut env); + // (λ x. x #3) opaque → opaque #2: the ambient Var must shift down + // past the consumed machine binder during spine readback. + let opaque = AE::cnst(mk_id("opaque"), Box::new([])); + let body = AE::app(AE::var(0, ()), AE::var(3, ())); + let lam = AE::lam((), (), sort0(), body); + let app = AE::app(lam, opaque.clone()); + let expected = AE::app(opaque, AE::var(2, ())); + assert_eq!(tc.whnf(&app).unwrap(), expected); + } + + #[test] + fn whnf_machine_partial_app_value() { + let mut env = env_with_id(); + let mut tc = TypeChecker::new(&mut env); + // (λ x y. x y) opaque → λ y. opaque y: Lam value exit reads the body + // back at binder depth 1. + let opaque = AE::cnst(mk_id("opaque"), Box::new([])); + let body = AE::app(AE::var(1, ()), AE::var(0, ())); + let lam2 = AE::lam((), (), sort0(), AE::lam((), (), sort0(), body)); + let app = AE::app(lam2, opaque.clone()); + let expected = AE::lam((), (), sort0(), AE::app(opaque, AE::var(0, ()))); + assert_eq!(tc.whnf(&app).unwrap(), expected); + } + + #[test] + fn whnf_machine_env_entry_lifts_under_binder() { + let mut env = env_with_id(); + let mut tc = TypeChecker::new(&mut env); + // (λ x y. x) #3 → λ y. #4: substituting an OPEN argument under the + // surviving binder must lift it (Var arm of clo_subst at depth 1). + let lam2 = + AE::lam((), (), sort0(), AE::lam((), (), sort0(), AE::var(1, ()))); + let app = AE::app(lam2, AE::var(3, ())); + let expected = AE::lam((), (), sort0(), AE::var(4, ())); + assert_eq!(tc.whnf(&app).unwrap(), expected); + } + + #[test] + fn whnf_machine_zeta_under_beta() { + let mut env = env_with_id(); + let mut tc = TypeChecker::new(&mut env); + // (λ x. let z := x in z) opaque → opaque: machine zeta push. + let opaque = AE::cnst(mk_id("opaque"), Box::new([])); + let body = AE::let_((), sort0(), AE::var(0, ()), AE::var(0, ()), true); + let lam = AE::lam((), (), sort0(), body); + let app = AE::app(lam, opaque.clone()); + assert_eq!(tc.whnf(&app).unwrap(), opaque); + } + + #[test] + fn whnf_machine_closure_iota_via_beta() { + use super::super::constant::RecRule; + // A beta whose body is a recursor application: the machine's Const + // exit must take the closure-iota path (rule args ride through as + // closures) and produce the same result as the eager iota. + // + // Unit-like inductive `U` with one ctor `U.mk` (no params/fields) + // and recursor `U.rec : (motive) (minor) (major) → minor`. + let u_id = mk_id("Test.U"); + let u_mk_id = mk_id("Test.U.mk"); + let u_rec_id = mk_id("Test.U.rec"); + let mut env = env_with_id(); + env.insert( + u_id.clone(), + KConst::Indc { + name: (), + level_params: (), + lvls: 0, + params: 0, + indices: 0, + is_rec: true, // disable struct-eta so the ctor path is the only route + is_refl: false, + is_unsafe: false, + nested: 0, + 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(); @@ -3860,7 +4653,13 @@ mod tests { } #[test] - fn whnf_nat_add_symbolic_literal_rhs_exposes_succ() { + fn whnf_nat_add_symbolic_literal_rhs_stays_compact() { + // `Nat.add x (Lit 2)` with a symbolic base must stay STUCK in compact + // offset form (`try_nat_offset_stuck`) instead of delta-unfolding into + // `succ(succ(x))` — that tower is O(n) substitution per layer and the + // dominant cost on Nat-arithmetic proofs. Iota over such a major still + // works via `cleanup_nat_offset_major`; def-eq decides offset pairs in + // `try_def_eq_offset`. let mut env = nat_env(); let empty = KEnv::new(); let prims = Primitives::from_env(&empty); @@ -3868,10 +4667,29 @@ mod tests { let mut tc = TypeChecker::new(&mut env); let add = AE::cnst(tc.prims.nat_add.clone(), Box::new([])); - let expr = app(app(add, var(0)), mk_nat(2)); + let expr = app(app(add.clone(), var(0)), mk_nat(2)); let result = tc.whnf(&expr).unwrap(); + assert_eq!(result, app(app(add, var(0)), mk_nat(2))); + } + + #[test] + fn def_eq_nat_offset_succ_tower_vs_compact_add() { + // `succ(succ(x))` ≡ `Nat.add x (Lit 2)` must be decided by the offset + // decomposition in `try_def_eq_offset` (equal offsets → compare bases), + // and `succ(x) ≟ Nat.add x (Lit 2)` must NOT be equal. + let mut env = nat_env(); + let empty = KEnv::new(); + let prims = Primitives::from_env(&empty); + insert_nat_add_model(&mut env, prims.nat_add.clone()); + + let mut tc = TypeChecker::new(&mut env); + let add = AE::cnst(tc.prims.nat_add.clone(), Box::new([])); let succ = AE::cnst(tc.prims.nat_succ.clone(), Box::new([])); - assert_eq!(result, app(succ.clone(), app(succ, var(0)))); + let tower = app(succ.clone(), app(succ.clone(), var(0))); + let compact = app(app(add, var(0)), mk_nat(2)); + assert!(tc.is_def_eq(&tower, &compact).unwrap()); + let one = app(succ, var(0)); + assert!(!tc.is_def_eq(&one, &compact).unwrap()); } #[test] @@ -4151,7 +4969,7 @@ mod tests { }, ExprData::App(..) => { // Might be Nat.succ chain — that's also acceptable - eprintln!("Nat.rec result is App chain (not folded to literal)"); + log::info!("Nat.rec result is App chain (not folded to literal)"); }, other => panic!("unexpected Nat.rec result: {:?}", other), } diff --git a/docs/env_machine_whnf.md b/docs/env_machine_whnf.md new file mode 100644 index 00000000..3fa1bcfd --- /dev/null +++ b/docs/env_machine_whnf.md @@ -0,0 +1,259 @@ +# Design: environment-machine WHNF for the Rust kernel + +Port of the IxVM environment machine (`~/ix-aiur` commits `d0d3375` +Phase A, `b4c4ea3` Phase B; `Ix/IxVM/Kernel/Whnf.lean:209-460`, +`Subst.lean:352-400`) to `crates/kernel/src/whnf.rs`. Status: +IMPLEMENTED — Phase A in `7825e0b` (machine loop in `whnf.rs`, +`Clo`/`MEnv`/`clo_subst` in `subst.rs`), Phase B closure-iota in +`acd0780` (`try_iota_clo`); guest cycle benchmarks pending. One +deviation from §7: machine-native delta (the IxVM Phase C1.5 win) +does not port at this layer — our machine lives inside `whnf_core`, +which must stay delta-free for def-eq's lazy-delta unfold ordering, +whereas the IxVM machine sits in a whnf that includes delta. Spanning +`whnf`'s delta loop with closures is a separate, larger design. + +## 1. Problem and evidence + +Eager substitution is the dominant residual cost on reduction-heavy +shards. Every beta materializes the substituted body via +`simul_subst` (a full walk + intern of every reachable subterm), and +every iota substitutes a whole recursor rule — including subterms the +reduction then discards (unselected `Decidable`/match branches, +unused minor premises, arguments dropped by projections). + +Current guest profiles (`/tmp/prof3-*.log`, cumulative): + +- `Vector.extract_append._proof_1` (4.95B steps): `simul_subst` 49%, + `try_same_head_spine` 71%, `whnf_core` 73%. +- UTF-8 codec proof (1.16B steps): `try_iota_with_flags` **85.6%**, + `try_same_head_spine` 85.7% — one deep def-eq descent with iota at + every level, substituting giant match trees whose branches are + mostly dropped. +- mergesort shard (2.3B steps): `whnf_core` 38%, `simul_subst` 16%. + +The same wall was hit independently on the Aiur side ("the +substitution wall is THE remaining structural frontier — needs a +representation-level fix, not another stuck-form"), and the machine +is its proven fix, measured there (FFT cost): + +- Phase A: Int16 −33.8%, Vector._proof_2 −17.1%, mergeSortBench + −6.8%, String.append −7.7%, gcd −5.0%; **synthetic foldAdd_2000 + +14.7%** (see Risks). +- Phase B (closure-iota): a further −3.4% Int16, −2.0% mergeSort; + headline payoff (dropped-branch laziness on UTF-8-class) not + represented in that suite. + +## 2. Core idea + +A Krivine-style abstract machine for the *structural* fragment of +WHNF (beta, zeta, app-spine, bound-variable lookup): + +- **Beta and zeta are O(1) environment pushes.** No substitution + walk, no materialized intermediate body. +- **Substitution happens only at exit points** ("readback"), and only + for the parts of the term the reduction actually hands to a + consumer. A beta chain ending in another beta never materializes + its intermediate bodies; an iota that selects rule 3 never touches + rules 0–2. +- WHNF never reduces under binders, so environments **never need + lifting** — no full explicit-substitution calculus required. A + `Lam`/`All` value reads back with `clo_subst` at binder depth 1. + +## 3. Data types (Rust) + +```rust +/// 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. Environments are built only by the machine's +/// beta/zeta pushes. +struct Clo { + e: KExpr, + env: MEnv, +} + +/// Persistent cons-list environment: O(1) push, structural sharing +/// across the closures captured at each binder. `len` is cached — the +/// IxVM port found recomputing it per suffix cost ~94M rows on the +/// UTF-8 check. +struct MEnvNode { head: Arc>, tail: MEnv } +type MEnv = Option>>; // + len: u32 carried alongside + +/// Machine spine: args nearest-first, each a closure over the env in +/// force where the App layer was peeled. Also a persistent cons list +/// (the machine only pushes at the front and pops from the front). +type CSpine = MEnv-like list of Arc>; +``` + +No `CWhnf` enum in Phase A: the Rust machine is a private loop inside +`whnf_core_with_flags_uncached`, and every exit reads back to a plain +`KExpr` that re-enters the *existing* loop (see §5). The symbolic +result type only becomes necessary for Phase C (closure-aware +def-eq), which this design defers. + +## 4. The machine loop + +A single iterative loop (NOT recursion — machine depth equals +reduction depth and must not consume native stack; every IxVM +transition is tail-call-shaped, so the loop is mechanical). State: +`(head: KExpr, env: MEnv, n: u32, spine: CSpine)`. Fuel: decrements +the same `MAX_WHNF_FUEL` budget as the enclosing `whnf_core` loop — +unlike IxVM we KEEP fuel (adversarial-input posture; see +`docs/kernel_identity.md` for the threat model). + +Transitions, mirroring `mwhnf_spine`: + +| head | action | +|---|---| +| `App(f, a)` | push `Clo{a, env}` onto spine; `head = f` (O(1) per layer, no `collect_app_spine` Vec) | +| `Lam(_, _, _, body)` | spine non-empty → pop `c`, push onto env, `head = body` (**beta, O(1)**); spine empty → EXIT: read back `Lam` via `clo_subst` (a value) | +| `Let(_, _, val, body)` | push `Clo{val, env}` onto env, `head = body` (**zeta, O(1)**) | +| `Var(i)` with `i < n` | fetch `Clo{e2, env2}` = env[i]; `head, env = e2, env2` (O(1) jump — this is where laziness pays) | +| `Var(i)` with `i >= n` | EXIT stuck: read back spine onto `Var(i - n)`; the *outer* loop's Var arm then does ambient `lookup_let_val` zeta | +| `FVar(_)` | EXIT stuck (readback); outer loop's FVar arm does LDecl zeta | +| `All(..)` | spine empty → EXIT readback; non-empty → ill-typed application, readback stuck (mirror IxVM) | +| `Const(..)` | EXIT: read back spine, rebuild application, hand to the existing dispatch (prim families, iota, projection-definitions, delta in the enclosing loops). Phase B refines the `Recr` case — see §7 | +| `Prj(id, f, val)` | scrutinee = `Clo{val, env}`: whnf it through the machine **respecting `flags.cheap_proj`** (cheap → machine + structural-only exits; full → full `whnf`), then the existing `try_proj_reduce` on the result; spine read back | +| `Sort/Nat/Str` | EXIT readback (stuck application of a non-function — outer loop handles/returns) | + +**Exit contract:** every machine exit produces a plain `KExpr` and +*re-enters `whnf_core_with_flags_uncached`'s loop* as the new `cur` +(or returns, where the old code returned). This keeps EVERY existing +behavior — ambient let/LDecl zeta, iota, K-synthesis, +`cleanup_nat_offset_major`, string/nat literal exposure, +proj-definitions, the nat-offset-stuck probe, delta in `whnf` — byte +identical in semantics. The machine replaces only the +App-collect/beta/zeta core. + +## 5. Entry gating (the hybrid — load-bearing) + +IxVM measured a machine-everywhere variant at **+26% on beta-free +literal-recursor loops** (closure-wrap + readback overhead with no +beta to amortize it). The shipped design enters the machine **only +when a beta actually fires**: + +In `whnf_core_with_flags_uncached`'s App arm (whnf.rs:641ff), after +`f = whnf_core(f0)`: + +- `f` is `Lam` → wrap the already-collected `args` as empty-env + closures (`spine_wrap`) and run the machine starting from the beta. + This REPLACES the current multi-lambda peel + `simul_subst` block. +- otherwise → existing path unchanged (rebuild-if-changed, iota, + return). + +Everything outside the App arm (Const-headed terms, literal recursor +loops, the whole no-delta/delta loop structure) never touches the +machine. This is also why the existing *caches need no changes*: the +machine is invisible between `whnf_core` entry and exit, and cache +keys/values remain plain interned `KExpr`s. + +## 6. Readback: `clo_subst` + +New functions in `subst.rs`, mirroring IxVM `Subst.lean:352-400`: + +```rust +/// e[Var(depth + i) := lift(readback(env[i]), depth)] for i < n, +/// Var(j) for j >= depth + n shifted down by n. lbr-guarded at every +/// node (the no-op-subtree guard measured −68% substitution entries +/// on the IxVM side). Uses a per-call memo scratch keyed (uid, depth) +/// (same pattern as subst_scratch — a NEW scratch map on InternTable; +/// the two must not share entries). Results interned per node, like +/// every other subst. +fn clo_subst(intern, e, env, n, depth) -> KExpr +fn clo_readback(intern, c: &Clo) -> KExpr // clo_subst(e, env, n, 0) +``` + +`clo_subst`'s Var arm composes `clo_readback` of the env entry with +the existing `lift` (subst.rs:341) at non-zero depth. Spine readback +maps `clo_readback` over the spine and rebuilds Apps via interning — +exactly what the current post-beta rebuild does, so uid/interning +discipline (see `docs/kernel_identity.md`) is unchanged: readback +goes through the same constructors + intern table as today's +substitution output. + +## 7. Phases + +**Phase A — machine for beta/zeta (this design's deliverable 1).** +Scope: `whnf.rs` App arm + the machine loop + `subst.rs::clo_subst`. +All Const/Proj exits read back and use existing machinery. Expected +from IxVM Phase A scaled to our profiles: vector double-digit +(its 49% `simul_subst` is the direct target), utf8 and mergesort +mid-single-digit, intRxc small. + +**Phase B — closure-iota (`try_iota_c`).** The `Recr` Const-exit no +longer reads its spine back: the major whnfs through its closure +(`clo_whnf`), and on the main ctor-rule hit the rule RHS returns with +params/motives/minors + ctor fields + post-major args as CLOSURES — +the rule's Lam-chain betas push them straight into an env, so +**unselected rules and dropped branches are never substituted and +never read back**. Off-main-path cases (K-flag synthesis, the +nat linear-rec shortcut, struct-eta, quot, `cleanup_nat_offset_major` +interplay) MISS to the existing plain `try_iota_with_flags` on a +readback spine — semantics preserved by construction. This phase is +the UTF-8-class payoff (its 85% iota share is mostly discarded-branch +substitution). `flags.cheap_rec` gates exactly as today (cheap mode +skips the machine-iota entirely and misses to the plain path). + +**Phase C (deferred, separate design):** closure-aware def-eq (the +IxVM working tree's `CWhnf` capture route) and env-trimming for the +curried-sharing regression. Do not start until A+B are measured. + +## 8. What explicitly does NOT change + +- All caches (whnf/whnf_core/cheap variants, infer, def-eq, unfold, + equiv, failure) — keys, values, clearing. The machine lives strictly + between one cache miss and its fill. +- The delta loop in `whnf`, prim-family dispatch, nat-offset-stuck + probes, transient-nat cache exclusion, native/string/decidable + reducers. +- def-eq tier structure; `whnf_core_for_def_eq` keeps cheap flags, + which the machine consults only at its Proj exit (A) and Recr exit + (B). +- Fuel and error semantics (`MaxRecDepth`). +- `simul_subst` itself stays — instantiation sites outside whnf + (infer's pi-instantiation etc.) still use it. + +## 9. Risks and mitigations + +1. **Curried-sharing regression (measured +14.7% on IxVM's synthetic + foldAdd_2000):** eager sequential substitution shared + constant-prefix substitutions across loop iterations; per-iteration + env closures do not. Our suite lacks a foldAdd-shaped input — + **add one before implementing** (e.g. dump `natfoldsucc.ixe` / + `natreclinear.ixe` from the repo root into the bench suite) so the + regression class is visible. Mitigation if it bites: env-trimming + at readback to each subterm's visible slice (IxVM's designed + fix), or tighten the entry gate (enter only on multi-arg beta). +2. **Native stack:** the machine MUST be an iterative loop; + `clo_subst` recursion depth is term depth (same exposure as + today's `subst`). +3. **Memory:** env chains are Arc-shared cons cells; worst case holds + alive every closure of a deep reduction until exit. Guest RAM is + 512MB; the same terms today materialize MORE memory (full + substituted bodies), so this should be net-negative pressure, but + watch the RAM column in `ziskemu -m` on vector/utf8. +4. **Cheap-flag fidelity:** the def-eq false-negative discipline + (cheap results must not enter full caches) is keyed at the + `whnf_core_with_flags` wrapper, which is untouched; the machine + only needs to route its Proj/Recr exits by `flags`. Test: the + existing 604 suite covers cheap/full splits. +5. **Behavioral drift in exits:** any machine exit that fails to + re-enter the outer loop (instead of returning) silently loses an + ambient zeta/iota step. The exit contract in §4 is the review + checklist: every exit either returns where the old code returned, + or sets `cur` and continues. + +## 10. Test & acceptance plan + +Per phase: 604 kernel + 200 ixon tests; native `check_one` on +Vector._proof_1, Int16, UTF-8; full 11-input guest cycle suite + +`natfoldsucc`/`natreclinear` additions; per the acceptance bar, keep +a phase only if the suite nets ≥1% (expected: well above). +Phase A first, committed alone; Phase B on top, committed alone. + +## 11. Effort + +Phase A: ~350–500 lines (machine loop ~150, clo_subst ~120, App-arm +rewire ~50, spine helpers, tests). Phase B: ~200 more +(try_iota_c + clo_whnf + Recr exit). The IxVM source is the line-by- +line reference for both. diff --git a/docs/kernel_identity.md b/docs/kernel_identity.md new file mode 100644 index 00000000..ffe6af7a --- /dev/null +++ b/docs/kernel_identity.md @@ -0,0 +1,169 @@ +# Kernel term identity vs. Ixon content addressing + +Ix has **two distinct identity layers**, and they serve different masters. +This document records the boundary between them — in particular why the +kernel's switch from per-node blake3 hashing to intern-assigned uids +(`1e3029d`, 2026-06) does not affect the proof-carrying-code story, and +what a future feature must do if it ever needs cryptographic identity at +sub-constant granularity. + +## Layer 1: Ixon content addresses (external, cryptographic, in proofs) + +The unit of cryptographic identity is the **constant**. A constant's +`Address` is the blake3 hash of its alpha-invariant Ixon serialization +(see `docs/Ixon.md`); blobs (the bytes behind `Nat`/`String` literals) +are addressed the same way. Everything that leaves the kernel speaks +this language: + +- `Constant.refs` point at constant and blob `Address`es. +- `KId.addr` — the identity the typechecker certifies — is the Ixon + `Address`. +- The Zisk guest's committed claim is built from `Address`es: + `subject_root` / `assumptions_root` are `merkle_root_canonical` over + the certified / assumed constant addresses (`crates/kernel/src/claim.rs`, + `zisk/guest/src/main.rs`), and `env_hash` is blake3 over the exact env + payload. +- Aggregation (`Claim::CheckEnv`, the `Contains` discharge) resolves + assumption leaves against subject roots **by address**, and the + cross-run proof store is keyed by address (content-addressed: the same + constant has the same address in every env). +- Address↔bytes binding is enforced by `Address::hash(bytes) == addr`, + verified at first materialization (`ixon::lazy::LazyConstant::get`). + Every constant the kernel certifies is necessarily materialized, so + everything that can enter a subject root has passed the check. + +**Merkle inclusion proofs for individual constants live entirely in this +layer** and are unaffected by anything below: proving "address `A` is in +this proof's subject root" is an inclusion path in a tree whose leaves +are constant addresses. + +The trust chain, end to end: + +``` +claim roots ──Merkle──▶ constant Addresses + ──verify──▶ serialized constant bytes (blake3 binding) + ──parse───▶ terms the kernel typechecked +``` + +## Layer 2: kernel node identity (internal, ephemeral, never serialized) + +Inside one checker run, the kernel needs cheap identity for the +**in-memory expression/universe nodes** (`KExpr`/`KUniv`) churned out by +substitution, WHNF, and def-eq — for the hash-consing intern table, the +whnf/infer/def-eq cache keys, and quick equality. These objects are +ephemeral: they are never serialized, never compared against Ixon +addresses, and are torn down when the `KEnv` clears. + +Historically this layer ALSO used blake3: every constructed node hashed +`(variant tag ‖ child hashes)`. That was a *separate scheme* from Ixon +addressing (a Merkle-DAG over node tags, not blake3-of-serialization) — +the two were never interchangeable — and it cost ~20% of all guest +cycles on reduction-heavy constants in the Zisk prover. + +Since `1e3029d`, layer-2 identity is an **intern-assigned `u64` uid** +(`crates/kernel/src/env.rs::Addr`): + +- Uids come from a process-global counter and are **never reused**, so + `uid(a) == uid(b)` implies "same construction event or same + intern-table canonical value" — i.e. structural equality. Stale cache + keys can only miss, never alias. +- The intern table keys on **shallow structural keys** + (`ExprKey`/`UnivKey`: variant tag + child uids + semantic payload), + mirroring exactly what the old content hash covered — display names, + binder info, and mdata are excluded, so interning semantics are + unchanged. +- `PartialEq` for `KExpr`/`KUniv` is structural with the uid fast path: + canonical-vs-canonical comparison is O(1), and completeness-critical + `==` sites are unaffected. `hash_eq` remains the fast-but-incomplete + uid check for cache/quick-path callers, where a false negative only + costs a fallback. +- Soundness direction: caches and def-eq quick paths rely only on the + affirmative ("equal uid ⇒ equal term"), which holds by construction. + An imperfect intern hit-rate degrades performance, not correctness. + +Nat/Str literal nodes keep their blake3 **blob** `Address` (that is +layer-1 data riding on the node, used by `refs`/assumption filtering). + +## The boundary rule + +> Anything that crosses the kernel boundary — claims, public values, +> proof-store entries, assumption sets — is identified by Ixon +> `Address`. Kernel uids must never escape into a serialized artifact. + +This held before the change (nothing consumed the old expr hashes +outside the kernel) and is now structurally enforced by the type split: +`Address` (32-byte content hash) vs `Addr = u64` (kernel uid) vs +`CtxAddr` (blake3 digests for local-context cache keys, fed by uids, +also internal-only). + +## Adversarial model: why uid identity cannot be cache-poisoned + +The natural worry about replacing blake3 with `u64` identity is hash +collision: *if two subterms had the same internal hash, a def-eq/whnf +cache hit could return a result for the wrong term.* The design avoids +this not by making collisions unlikely but by making them impossible — +**uids are assigned, not computed**: + +- A uid comes from a process-global sequential counter + (`expr.rs::fresh_uid`), never from hashing input content. There is no + function from term content to uid for an adversary to find collisions + in — and no birthday bound either: uids are handed out once each, not + sampled from a space two terms could land in. Two `KExpr` values share + a uid only if they are clones of one construction event or the + canonical node the intern table returned for the same shallow key. + `fresh_uid` aborts on counter exhaustion (which would require >2^67 + guest cycles) rather than wrap, and the only constructors that accept + a caller-supplied uid (`*_mdata_with_addr`) are private to `expr.rs`, + so no code outside the module can copy one node's uid into a + different structure. +- **Between shards** (separate guest processes), uid collisions are + certain — every process counts from 1 — and harmless by design: a uid + is meaningless outside its process. Nothing serializes a uid (the + committed publics are blake3 addresses and Merkle roots; intern tables + and caches die with the process; manifests and the proof store carry + `Address`es only), so there is no channel through which shard A's + uid 42 could meet shard B's. All cross-shard identity — claims, + assumption resolution, proof reuse — lives in the Ixon `Address` + layer. Any future feature that wants to persist or ship a + reduction/intern cache MUST re-key it by content (e.g. Ixon + serialization hashes) first; see the boundary rule above. +- The intern table is keyed by [`ExprKey`]/[`UnivKey`] — exact + structural keys compared by full `Eq` (variant tag, child uids, + complete 32-byte payload addresses; never truncated, never a digest). + `FxHashMap` bucketing uses a weak hash, but a bucket collision only + causes an extra key comparison, not a wrong hit. Children in a key are + canonical by induction, so key equality ⇔ structural equality. Debug + builds assert structural equality on every intern hit. +- Reduction caches key on `(uid, CtxAddr)`; a hit requires uid equality, + hence structural equality. `CtxAddr` (local-context digests) remains + blake3 — collision-resistant — fed with uid bytes. +- An adversarial env cannot choose uids at all: deserialization never + produces a uid; every node built from input takes the next counter + value. Compared to the old scheme — where cache keys were blake3 over + attacker-chosen content, so an attacker could at least grind contents + to flood `FxHashMap` buckets of their choosing — sequential uids give + the adversary strictly *less* influence over key distribution. + +The one place adversarial content does enter identity is **literals**: +`ExprKey::Nat/Str` and structural equality key on the blob `Address` +(mirroring the old content hash, which also hashed only the address). +That is sound only if an address is bound to its bytes — so all three +env deserializers verify `Address::hash(bytes)` for every blob at load +(the constants' equivalent check runs lazily at first materialization), +and the literal arms of structural equality compare values as well as +addresses as defense in depth. Residual exposure is limited to DoS +(bucket flooding of maps keyed by attacker-chosen `Address`es slows the +prover, which is spending its own cycles), not soundness. + +## Future: sub-constant commitments + +If a feature ever needs cryptographic identity for **subterms** (e.g. +Merkle inclusion of a specific expression inside a constant's body, +rather than inclusion of a constant in a claim), expression-level +content hashes no longer exist precomputed. The right move is to compute +them at that boundary: a one-time egress pass over the relevant term — +either the old `(tag ‖ child hashes)` Merkle-DAG scheme or blake3 over +the Ixon serialization of the subterm. That is cheap where it is needed +and keeps the per-node cost out of the prover's hottest loop. No current +artifact (claims, aggregation, reuse, inclusion proofs at constant +granularity) needs it. diff --git a/flake.lock b/flake.lock index 74d923ce..7468797d 100644 --- a/flake.lock +++ b/flake.lock @@ -76,6 +76,21 @@ "type": "github" } }, + "crane_3": { + "locked": { + "lastModified": 1774313767, + "narHash": "sha256-hy0XTQND6avzGEUFrJtYBBpFa/POiiaGBr2vpU6Y9tY=", + "owner": "ipetkov", + "repo": "crane", + "rev": "3d9df76e29656c679c744968b17fbaf28f0e923d", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, "fenix": { "inputs": { "nixpkgs": [ @@ -121,6 +136,28 @@ "type": "github" } }, + "fenix_3": { + "inputs": { + "nixpkgs": [ + "zisk", + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src_3" + }, + "locked": { + "lastModified": 1774596377, + "narHash": "sha256-DiSLMxyTwIUAlhOe34r6kKNQRv6PTF+vf0MG45mAyn4=", + "owner": "nix-community", + "repo": "fenix", + "rev": "a88a1c8cf2f094da6347fcec54089f4bcb518409", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, "flake-parts": { "inputs": { "nixpkgs-lib": "nixpkgs-lib" @@ -175,6 +212,42 @@ "type": "github" } }, + "flake-parts_4": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_4" + }, + "locked": { + "lastModified": 1772408722, + "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_5": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_5" + }, + "locked": { + "lastModified": 1772408722, + "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "lean4-nix": { "inputs": { "flake-parts": "flake-parts_3", @@ -255,6 +328,52 @@ "type": "github" } }, + "nixpkgs-lib_4": { + "locked": { + "lastModified": 1772328832, + "narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs-lib_5": { + "locked": { + "lastModified": 1772328832, + "narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1774386573, + "narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "blake3-lean": "blake3-lean", @@ -265,7 +384,9 @@ "nixpkgs": [ "lean4-nix", "nixpkgs" - ] + ], + "sp1": "sp1", + "zisk": "zisk" } }, "rust-analyzer-src": { @@ -301,6 +422,66 @@ "repo": "rust-analyzer", "type": "github" } + }, + "rust-analyzer-src_3": { + "flake": false, + "locked": { + "lastModified": 1774569884, + "narHash": "sha256-E8iWEPzg7OnE0XXXjo75CX7xFauqzJuGZ5wSO9KS8Ek=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "443ddcddd0c73b07b799d052f5ef3b448c2f3508", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + }, + "sp1": { + "inputs": { + "flake-parts": "flake-parts_4", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1778884899, + "narHash": "sha256-kQahZHfD2f6tEaqD6WBDHDnjSr5m8x/IIS8VKu5glTw=", + "owner": "argumentcomputer", + "repo": "sp1.nix", + "rev": "048405ea52a9acb3fdd4eb7ad8c82346e93c4924", + "type": "github" + }, + "original": { + "owner": "argumentcomputer", + "repo": "sp1.nix", + "type": "github" + } + }, + "zisk": { + "inputs": { + "crane": "crane_3", + "fenix": "fenix_3", + "flake-parts": "flake-parts_5", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1778885240, + "narHash": "sha256-hjbDg9V2q5fwoeBBjE/14a3YwQGwMImV/Apu0CMYSVY=", + "owner": "argumentcomputer", + "repo": "zisk.nix", + "rev": "d7fd3b8353dada23f9530716d1d8265b10c7808e", + "type": "github" + }, + "original": { + "owner": "argumentcomputer", + "ref": "fixups", + "repo": "zisk.nix", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index ad7c254b..3188ce4c 100644 --- a/flake.nix +++ b/flake.nix @@ -35,18 +35,30 @@ # System packages, follows lean4-nix so we stay in sync inputs.lean4-nix.follows = "lean4-nix"; }; + + # Zisk dev shell (cargo-zisk, ziskemu, RISC-V toolchain) for `zisk-guest`. + zisk.url = "github:argumentcomputer/zisk.nix/fixups"; + + # SP1 dev shell (cargo-prove + succinct Rust toolchain) for `sp1/guest`. + sp1 = { + url = "github:argumentcomputer/sp1.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; - outputs = inputs @ { - nixpkgs, - flake-parts, - lean4-nix, - fenix, - crane, - blake3-lean, - ... - }: - flake-parts.lib.mkFlake {inherit inputs;} { + outputs = + inputs@{ + nixpkgs, + flake-parts, + lean4-nix, + fenix, + crane, + blake3-lean, + zisk, + sp1, + ... + }: + flake-parts.lib.mkFlake { inherit inputs; } { # Systems we want to build for systems = [ "aarch64-darwin" @@ -55,149 +67,197 @@ "x86_64-linux" ]; - perSystem = { - system, - pkgs, - ... - }: let - # Pins the Rust toolchain - rustToolchain = fenix.packages.${system}.fromToolchainFile { - file = ./rust-toolchain.toml; - sha256 = "sha256-sqSWJDUxc+zaz1nBWMAJKTAGBuGWP25GCftIOlCEAtA="; - }; + perSystem = + { + system, + pkgs, + ... + }: + let + # Pins the Rust toolchain + rustToolchain = fenix.packages.${system}.fromToolchainFile { + file = ./rust-toolchain.toml; + sha256 = "sha256-sqSWJDUxc+zaz1nBWMAJKTAGBuGWP25GCftIOlCEAtA="; + }; + + # Rust package + craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain; + src = craneLib.cleanCargoSource ./.; + craneArgs = { + inherit src; + pname = "ix"; + version = "0.1.0"; + strictDeps = true; - # Rust package - craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain; - src = craneLib.cleanCargoSource ./.; - craneArgs = { - inherit src; - strictDeps = true; - - # build.rs uses LEAN_SYSROOT to locate lean/lean.h for bindgen - LEAN_SYSROOT = "${pkgs.lean.lean-all}"; - # bindgen needs libclang to parse C headers - LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; - - buildInputs = - [] - ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ - # Additional darwin specific inputs can be set here - pkgs.libiconv + # build.rs uses LEAN_SYSROOT to locate lean/lean.h for bindgen + LEAN_SYSROOT = "${pkgs.lean.lean-all}"; + # bindgen needs libclang to parse C headers + LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; + + buildInputs = + [ ] + ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ + # Additional darwin specific inputs can be set here + pkgs.libiconv + ]; + }; + cargoArtifacts = craneLib.buildDepsOnly craneArgs; + + # Test build: parallel + test-ffi (only used by ixTest) + rustPkg = craneLib.buildPackage ( + craneArgs + // { + inherit cargoArtifacts; + cargoExtraArgs = "--locked --features parallel,test-ffi"; + } + ); + + # Release build without test-ffi (for distribution) + rustPkgRelease = craneLib.buildPackage ( + craneArgs + // { + inherit cargoArtifacts; + cargoExtraArgs = "--locked --features parallel"; + } + ); + + # Lake package + lake2nix = pkgs.callPackage lean4-nix.lake { }; + lakeDeps = lake2nix.buildDeps { + src = ./.; + depOverrideDeriv = { + Blake3 = blake3-lean.packages.${system}.rust; + }; + }; + # Shared Lake build args: patches out the Cargo build (Crane handles it) + mkLakeBuildArgs = rustLib: { + inherit lakeDeps; + src = ./.; + # Don't build the `ix_rs` static lib with Lake, since we build it with Crane + postPatch = '' + substituteInPlace lakefile.lean --replace-fail 'proc { cmd := "cargo"' '--proc { cmd := "cargo"' + ''; + # Symlink the Crane-built static lib to where Lake expects it + postConfigure = '' + mkdir -p target/release + ln -s ${rustLib}/lib/libix_ffi.a target/release/ + ''; + buildInputs = [ + pkgs.gmp + pkgs.lean.lean-all + pkgs.rsync ]; - }; - cargoArtifacts = craneLib.buildDepsOnly craneArgs; - - # Test build: parallel + test-ffi (only used by ixTest) - rustPkg = craneLib.buildPackage (craneArgs - // { - inherit cargoArtifacts; - cargoExtraArgs = "--locked --features parallel,test-ffi"; - }); - - # Release build without test-ffi (for distribution) - rustPkgRelease = craneLib.buildPackage (craneArgs - // { - inherit cargoArtifacts; - cargoExtraArgs = "--locked --features parallel"; - }); - - # Lake package - lake2nix = pkgs.callPackage lean4-nix.lake {}; - lakeDeps = lake2nix.buildDeps { - src = ./.; - depOverrideDeriv = { - Blake3 = blake3-lean.packages.${system}.rust; }; - }; - # Shared Lake build args: patches out the Cargo build (Crane handles it) - mkLakeBuildArgs = rustLib: { - inherit lakeDeps; - src = ./.; - # Don't build the `ix_rs` static lib with Lake, since we build it with Crane - postPatch = '' - substituteInPlace lakefile.lean --replace-fail 'proc { cmd := "cargo"' '--proc { cmd := "cargo"' - ''; - # Symlink the Crane-built static lib to where Lake expects it - postConfigure = '' - mkdir -p target/release - ln -s ${rustLib}/lib/libix_rs.a target/release/ - ''; - buildInputs = [pkgs.gmp pkgs.lean.lean-all pkgs.rsync]; - }; - # Release build args (no test-ffi symbols) - lakeBuildArgs = mkLakeBuildArgs rustPkgRelease; - # Test build args (includes test-ffi symbols) - lakeTestBuildArgs = mkLakeBuildArgs rustPkg; - - ixLib = lake2nix.mkPackage (lakeBuildArgs - // { - name = "Ix"; - buildLibrary = true; - }); - lakeBinArgs = - lakeBuildArgs - // { + # Release build args (no test-ffi symbols) + lakeBuildArgs = mkLakeBuildArgs rustPkgRelease; + # Test build args (includes test-ffi symbols) + lakeTestBuildArgs = mkLakeBuildArgs rustPkg; + + ixLib = lake2nix.mkPackage ( + lakeBuildArgs + // { + name = "Ix"; + buildLibrary = true; + } + ); + lakeBinArgs = lakeBuildArgs // { lakeArtifacts = ixLib; # Binaries that import Ix.Meta need .olean files at runtime via LEAN_PATH installArtifacts = true; }; - leanPath = pkgs.lib.concatStringsSep ":" ( - map (d: "${d}/.lake/build/lib/lean") ([ixLib] ++ builtins.attrValues lakeDeps) - ); - wrapBin = drv: - pkgs.runCommand drv.name {nativeBuildInputs = [pkgs.makeWrapper];} '' - mkdir -p $out/bin - for f in ${drv}/bin/*; do - [ -x "$f" ] || continue - makeWrapper "$f" "$out/bin/$(basename "$f")" \ - --set LEAN_SYSROOT "${pkgs.lean.lean-all}" \ - --set LEAN_PATH "${drv}/.lake/build/lib/lean:${leanPath}" - done - ''; - ixCLI = wrapBin (lake2nix.mkPackage (lakeBinArgs // {name = "ix";})); - # Test binary links rustPkg (with test-ffi) instead of rustPkgRelease - ixTest = wrapBin (lake2nix.mkPackage (lakeTestBuildArgs - // { - name = "IxTests"; - installArtifacts = true; - })); - ZKVotingProver = wrapBin (lake2nix.mkPackage (lakeBinArgs - // { - name = "Apps.ZKVoting.Prover"; - installArtifacts = true; - })); - in { - # Lean overlay - _module.args.pkgs = import nixpkgs { - inherit system; - overlays = [(lean4-nix.readToolchainFile ./lean-toolchain)]; - }; + leanPath = pkgs.lib.concatStringsSep ":" ( + map (d: "${d}/.lake/build/lib/lean") ([ ixLib ] ++ builtins.attrValues lakeDeps) + ); + wrapBin = + drv: + pkgs.runCommand drv.name { nativeBuildInputs = [ pkgs.makeWrapper ]; } '' + mkdir -p $out/bin + for f in ${drv}/bin/*; do + [ -x "$f" ] || continue + makeWrapper "$f" "$out/bin/$(basename "$f")" \ + --set LEAN_SYSROOT "${pkgs.lean.lean-all}" \ + --set LEAN_PATH "${drv}/.lake/build/lib/lean:${leanPath}" + done + ''; + ixCLI = wrapBin (lake2nix.mkPackage (lakeBinArgs // { name = "ix"; })); + # Test binary links rustPkg (with test-ffi) instead of rustPkgRelease + ixTest = wrapBin ( + lake2nix.mkPackage ( + lakeTestBuildArgs + // { + name = "IxTests"; + installArtifacts = true; + } + ) + ); + ZKVotingProver = wrapBin ( + lake2nix.mkPackage ( + lakeBinArgs + // { + name = "Apps.ZKVoting.Prover"; + installArtifacts = true; + } + ) + ); + in + { + # Lean overlay + _module.args.pkgs = import nixpkgs { + inherit system; + overlays = [ (lean4-nix.readToolchainFile ./lean-toolchain) ]; + }; - packages = { - default = ixLib; - ix = ixCLI; - test = ixTest; - zkv-prover = ZKVotingProver // {meta.mainProgram = "Apps-ZKVoting-Prover";}; - }; + packages = { + default = ixLib; + ix = ixCLI; + test = ixTest; + zkv-prover = ZKVotingProver // { + meta.mainProgram = "Apps-ZKVoting-Prover"; + }; + }; - # Provide a unified dev shell with Lean + Rust - devShells.default = pkgs.mkShell { - # Add libclang for FFI with rust-bindgen - LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; - packages = with pkgs; [ - pkg-config - openssl - clang - rustToolchain - rust-analyzer - lean.lean-all # Includes Lean compiler, lake, stdlib, etc. - cargo-deny - valgrind - ]; - }; + # Lean + Rust shell for host development (`cargo build`, `lake build`). + devShells.default = pkgs.mkShell { + LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; + packages = with pkgs; [ + pkg-config + openssl + clang + rustToolchain + rust-analyzer + lean.lean-all + cargo-deny + valgrind + ]; + }; - formatter = pkgs.alejandra; - }; + # Zisk shell for `zisk-guest/` (cargo-zisk, ziskemu, RISC-V toolchain). + # Kept separate from `default`: merging cross-pollinates NIX_CFLAGS_COMPILE + # between zisk.nix's and this flake's nixpkgs, which breaks bindgen on + # `lean.h`. + devShells.zisk = zisk.devShells.${system}.default; + + # SP1 shell for `sp1/host` + `sp1/guest`: host Rust toolchain plus + # cargo-prove and the succinct Rust toolchain (~/.sp1) from sp1.nix. + # `rustup-shim` wraps the host `rustc` to dispatch to the succinct + # toolchain when `RUSTUP_TOOLCHAIN=succinct` (set by `sp1-build`); the + # plain host rustc doesn't know `riscv64im-succinct-zkvm-elf`. + # `sp1-prover-types`'s build script needs `protoc`. + devShells.sp1 = pkgs.mkShell { + name = "sp1"; + inputsFrom = [ sp1.devShells.${system}.default ]; + LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; + packages = with pkgs; [ + pkg-config + openssl + protobuf + clang + (sp1.packages.${system}.rustup-shim.override { inherit rustToolchain; }) + ]; + }; + + formatter = pkgs.alejandra; + }; }; } diff --git a/lake-manifest.json b/lake-manifest.json index 05e505ee..2c6d163c 100644 --- a/lake-manifest.json +++ b/lake-manifest.json @@ -25,10 +25,10 @@ "type": "git", "subDir": null, "scope": "", - "rev": "aaf530784082a2c00b0a93648741429d274102ca", + "rev": "d15f36cf76eb5834b0e623e02b97fd4d95e56cc7", "name": "Blake3", "manifestFile": "lake-manifest.json", - "inputRev": "aaf530784082a2c00b0a93648741429d274102ca", + "inputRev": "d15f36cf76eb5834b0e623e02b97fd4d95e56cc7", "inherited": false, "configFile": "lakefile.lean"}, {"url": "https://github.com/argumentcomputer/LSpec", diff --git a/lakefile.lean b/lakefile.lean index 5362daa8..8e2305f7 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -8,7 +8,7 @@ require LSpec from git "https://github.com/argumentcomputer/LSpec" @ "d3c15b93a1dd4e7c8d5c0c3825c9555737e55c3e" require Blake3 from git - "https://github.com/argumentcomputer/Blake3.lean" @ "aaf530784082a2c00b0a93648741429d274102ca" + "https://github.com/argumentcomputer/Blake3.lean" @ "d15f36cf76eb5834b0e623e02b97fd4d95e56cc7" require Cli from git "https://github.com/leanprover/lean4-cli" @ "v4.29.0" @@ -40,7 +40,7 @@ def cargoArgs (testFfi : Bool := false) (net : Bool := false) : IO (Array String if ixNoPar != some "1" then features := features.push "parallel" if net && !System.Platform.isOSX then features := features.push "net" if testFfi then features := features.push "test-ffi" - let buildArgs := #["build", "--release"] + let buildArgs := #["build", "--release", "-p", "ix-ffi"] if features.isEmpty then return buildArgs else return buildArgs ++ #["--features", ",".intercalate features.toList] @@ -48,7 +48,7 @@ def cargoArgs (testFfi : Bool := false) (net : Bool := false) : IO (Array String target ix_rs pkg : FilePath := do let args ← cargoArgs proc { cmd := "cargo", args, cwd := pkg.dir } (quiet := true) - inputBinFile $ pkg.dir / "target" / "release" / nameToStaticLib "ix_rs" + inputBinFile $ pkg.dir / "target" / "release" / nameToStaticLib "ix_ffi" /-- Rebuild the Rust static lib with `test-ffi`. Only triggered by `lake test` (via `moreLinkObjs` on `IxTests`). @@ -57,7 +57,7 @@ target ix_rs_test pkg : FilePath := do let _ ← ix_rs.fetch let args ← cargoArgs (testFfi := true) proc { cmd := "cargo", args, cwd := pkg.dir } (quiet := true) - inputBinFile $ pkg.dir / "target" / "release" / nameToStaticLib "ix_rs" + inputBinFile $ pkg.dir / "target" / "release" / nameToStaticLib "ix_ffi" /-- Build the Rust static lib with `net` for the `ix` CLI. Fetches `ix_rs` first to guarantee ordering before overwriting the lib. -/ @@ -65,7 +65,7 @@ target ix_rs_net pkg : FilePath := do let _ ← ix_rs.fetch let args ← cargoArgs (net := true) proc { cmd := "cargo", args, cwd := pkg.dir } (quiet := true) - inputBinFile $ pkg.dir / "target" / "release" / nameToStaticLib "ix_rs" + inputBinFile $ pkg.dir / "target" / "release" / nameToStaticLib "ix_ffi" end FFI @@ -122,6 +122,9 @@ lean_exe «bench-check-nat-add-comm» where root := `Benchmarks.CheckNatAddComm supportInterpreter := true +lean_exe «bench-compile-init» where + root := `Benchmarks.CompileInit + end Benchmarks section IxApplications diff --git a/sp1/.cargo/config.toml b/sp1/.cargo/config.toml new file mode 100644 index 00000000..9818452d --- /dev/null +++ b/sp1/.cargo/config.toml @@ -0,0 +1,9 @@ +[build] +rustflags = ["-C", "target-cpu=native"] + +# Wraps every binary with scripts/cuda-runner.sh, which cleans orphaned +# sp1-gpu-server sockets and retries on the SP1 SDK's CUDA-init race. +# Pass-through (no overhead, no retries) when SP1_PROVER != "cuda". +# Path is relative to cwd; this expects cargo to be invoked from sp1/. +[target.x86_64-unknown-linux-gnu] +runner = "./scripts/cuda-runner.sh" diff --git a/sp1/Cargo.lock b/sp1/Cargo.lock new file mode 100644 index 00000000..a5f9903d --- /dev/null +++ b/sp1/Cargo.lock @@ -0,0 +1,5920 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addchain" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e33f6a175ec6a9e0aca777567f9ff7c3deefc255660df887e7fa3585e9801d8" +dependencies = [ + "num-bigint 0.3.3", + "num-integer", + "num-traits", +] + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-scoped" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4042078ea593edffc452eef14e99fdb2b120caa4ad9618bcdeabc4a023b98740" +dependencies = [ + "futures", + "pin-project", + "tokio", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.5.3", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "serde", + "windows-link", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bignat" +version = "0.1.0" +source = "git+https://github.com/argumentcomputer/lean-ffi.git?rev=839b405de708b80504fda39abe1402114b4135a5#839b405de708b80504fda39abe1402114b4135a5" +dependencies = [ + "num-bigint 0.4.6", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array 0.14.9", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" +dependencies = [ + "const_format_proc_macros", + "konst", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crash-context" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031ed29858d90cfdf27fe49fae28028a1f20466db97962fa2f4ea34809aeebf3" +dependencies = [ + "cfg-if", + "libc", + "mach2", +] + +[[package]] +name = "crash-handler" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df5c9639f4942eb7702b964b3f9adf03a55724a57558cc177407388a8b936e2" +dependencies = [ + "cfg-if", + "crash-context", + "libc", + "mach2", + "parking_lot", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array 0.14.9", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array 0.14.9", + "typenum", +] + +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", + "rayon", +] + +[[package]] +name = "dashu" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b3e5ac1e23ff1995ef05b912e2b012a8784506987a2651552db2c73fb3d7e0" +dependencies = [ + "dashu-base", + "dashu-float", + "dashu-int", + "dashu-macros", + "dashu-ratio", + "rustversion", +] + +[[package]] +name = "dashu-base" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b80bf6b85aa68c58ffea2ddb040109943049ce3fbdf4385d0380aef08ef289" + +[[package]] +name = "dashu-float" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85078445a8dbd2e1bd21f04a816f352db8d333643f0c9b78ca7c3d1df71063e7" +dependencies = [ + "dashu-base", + "dashu-int", + "num-modular", + "num-order", + "rustversion", + "static_assertions", +] + +[[package]] +name = "dashu-int" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee99d08031ca34a4d044efbbb21dff9b8c54bb9d8c82a189187c0651ffdb9fbf" +dependencies = [ + "cfg-if", + "dashu-base", + "num-modular", + "num-order", + "rustversion", + "static_assertions", +] + +[[package]] +name = "dashu-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93381c3ef6366766f6e9ed9cf09e4ef9dec69499baf04f0c60e70d653cf0ab10" +dependencies = [ + "dashu-base", + "dashu-float", + "dashu-int", + "dashu-ratio", + "paste", + "proc-macro2", + "quote", + "rustversion", +] + +[[package]] +name = "dashu-ratio" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e33b04dd7ce1ccf8a02a69d3419e354f2bbfdf4eb911a0b7465487248764c9" +dependencies = [ + "dashu-base", + "dashu-float", + "dashu-int", + "num-modular", + "num-order", + "rustversion", +] + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + +[[package]] +name = "deepsize2" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b5184084af9beed35eecbf4c36baf6e26b9dc47b61b74e02f930c72a58e71b" +dependencies = [ + "deepsize_derive2", + "hashbrown 0.14.5", +] + +[[package]] +name = "deepsize_derive2" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f8817865cacf3b93b943ca06b0fc5fd8e99eabfdb7ea5d296efcbc4afc4f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive-where" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dynasm" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7d4c414c94bc830797115b8e5f434d58e7e80cb42ba88508c14bc6ea270625" +dependencies = [ + "bitflags", + "byteorder", + "lazy_static", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dynasmrt" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602f7458a3859195fb840e6e0cce5f4330dd9dfbfece0edaf31fe427af346f55" +dependencies = [ + "byteorder", + "dynasm", + "fnv", + "memmap2", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +dependencies = [ + "serde", +] + +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array 0.14.9", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", + "serde", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom", + "pin-project-lite", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "byteorder", + "ff_derive", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "ff_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f10d12652036b0e99197587c6ba87a8fc3031986499973c030d8b44fcc151b60" +dependencies = [ + "addchain", + "num-bigint 0.3.3", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "gecko_profile" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "890852c7e1e02bc6758e325d6b1e0236e4fbf21b492f585ce4d4715be54b4c6a" +dependencies = [ + "debugid", + "serde", + "serde_json", +] + +[[package]] +name = "gen_ops" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "304de19db7028420975a296ab0fcbbc8e69438c4ed254a1e41e2a7f37d5f0e0a" + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "generic-array" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96512db27971c2c3eece70a1e106fbe6c87760234e31e8f7e5634912fe52794a" +dependencies = [ + "serde", + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "human-repr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58b778a5761513caf593693f8951c97a5b610841e754788400f32102eefdff1" + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.3", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "indicatif" +version = "0.17.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", + "web-time", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "ix-common" +version = "0.1.0" +dependencies = [ + "bignat", + "blake3", + "indexmap 2.14.0", + "rustc-hash", +] + +[[package]] +name = "ix-kernel" +version = "0.1.0" +dependencies = [ + "bignat", + "blake3", + "dashmap", + "indexmap 2.14.0", + "itertools 0.14.0", + "ix-common", + "ixon", + "log", + "num-bigint 0.4.6", + "rayon", + "rustc-hash", +] + +[[package]] +name = "ixon" +version = "0.1.0" +dependencies = [ + "bignat", + "blake3", + "dashmap", + "indexmap 2.14.0", + "ix-common", + "memmap2", + "num-bigint 0.4.6", + "rayon", + "rustc-hash", + "sha2", + "tiny-keccak", +] + +[[package]] +name = "js-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "sha2", + "signature", +] + +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memfd" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" +dependencies = [ + "rustix", +] + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mti" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9563a7d5556636e74bbd8773241fbcbc5c89b9f6bfdc97b29b56e740c2c74b9" +dependencies = [ + "typeid_prefix", + "typeid_suffix", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint 0.4.6", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-modular" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" + +[[package]] +name = "num-order" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" +dependencies = [ + "num-modular", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint 0.4.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "crc32fast", + "flate2", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "memchr", + "ruzstd", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "opentelemetry" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b69a91d4893e713e06f724597ad630f1fa76057a5e1026c0ca67054a9032a76" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror 1.0.69", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p3-air" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a5de20a2301bf2530de1ceb13768ec3a80f729fbf8b72f813e30bc54c5bce2" +dependencies = [ + "p3-field", + "p3-matrix", + "serde", +] + +[[package]] +name = "p3-baby-bear" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69e6e9af4eaaaa60f7bb9f0e0f73ebcbaefe7e00974d97ad0fa542d6a4f0890" +dependencies = [ + "cfg-if", + "num-bigint 0.4.6", + "p3-field", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", + "rand 0.8.6", + "rustc_version", + "serde", +] + +[[package]] +name = "p3-bn254-fr" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2077757c7cb514202ccb5368f521f23f5709c720599e6545c683c66e0a52d2d8" +dependencies = [ + "ff", + "num-bigint 0.4.6", + "p3-field", + "p3-poseidon2", + "p3-symmetric", + "rand 0.8.6", + "serde", +] + +[[package]] +name = "p3-challenger" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a908924d43e4cfb93fb41c8346cac211b70314385a9037e9241f5b7f3eaf77" +dependencies = [ + "p3-field", + "p3-maybe-rayon", + "p3-symmetric", + "p3-util", + "serde", + "tracing", +] + +[[package]] +name = "p3-commit" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50acacc7219fce6c01db938f82c1b21b5e7133990b7fff861f91534aeb569419" +dependencies = [ + "itertools 0.12.1", + "p3-challenger", + "p3-field", + "p3-matrix", + "p3-util", + "serde", +] + +[[package]] +name = "p3-dft" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be6408b10a2c27eb13a7d5580c546c2179a8dc7dbc10a990657311891f9b41c0" +dependencies = [ + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "tracing", +] + +[[package]] +name = "p3-field" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dc75969ca3ac847f43e632ab979d59ff7a68f9eac8dbf8edcbba47fc2e1d3aa" +dependencies = [ + "itertools 0.12.1", + "num-bigint 0.4.6", + "num-traits", + "p3-util", + "rand 0.8.6", + "serde", +] + +[[package]] +name = "p3-fri" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbc4965ee488f3247867b7ec4bb005b8afa72cb0d461a4dcb1387ecab6426d5" +dependencies = [ + "itertools 0.12.1", + "p3-challenger", + "p3-commit", + "p3-dft", + "p3-field", + "p3-interpolation", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "serde", + "tracing", +] + +[[package]] +name = "p3-interpolation" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ad7e9f08c336d7ea39d12e11951188473542565323bac2a6535e536b58487d" +dependencies = [ + "p3-field", + "p3-matrix", + "p3-util", +] + +[[package]] +name = "p3-keccak-air" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f5bf177d56740078b5a5a842fa2393427796283dc8174b4a1a325c2d0b042de" +dependencies = [ + "p3-air", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "tracing", +] + +[[package]] +name = "p3-koala-bear" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9683cd0ef68100df7c62490533047bcf19c04c4a0fa1efc9d7c1e03e31f6b3" +dependencies = [ + "cfg-if", + "num-bigint 0.4.6", + "p3-field", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", + "rand 0.8.6", + "rustc_version", + "serde", +] + +[[package]] +name = "p3-matrix" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c3f150ceb90e09539413bf481e618d05ee19210b4e467d2902eb82d2e15281" +dependencies = [ + "itertools 0.12.1", + "p3-field", + "p3-maybe-rayon", + "p3-util", + "rand 0.8.6", + "serde", + "tracing", +] + +[[package]] +name = "p3-maybe-rayon" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0641952b42da45e1dfa2d4a2a3163e330f944ad9740942f35026c0a71a605f1" +dependencies = [ + "rayon", +] + +[[package]] +name = "p3-mds" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4a5f250e174dcfca5cbeac6ad75713924e7e7320e0a335e3c50b8b1f4fe8ec" +dependencies = [ + "itertools 0.12.1", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-symmetric", + "p3-util", + "rand 0.8.6", +] + +[[package]] +name = "p3-merkle-tree" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5703d9229d52a8c09970e4d722c3a8b4d37e688c306c3a1c03b872efcd204e6" +dependencies = [ + "itertools 0.12.1", + "p3-commit", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-symmetric", + "p3-util", + "serde", + "tracing", +] + +[[package]] +name = "p3-poseidon2" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "522986377b2164c5f94f2dae88e0e0a3d169cc6239202ef4aeb4322d60feffd0" +dependencies = [ + "gcd", + "p3-field", + "p3-mds", + "p3-symmetric", + "rand 0.8.6", + "serde", +] + +[[package]] +name = "p3-symmetric" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9047ce85c086a9b3f118e10078f10636f7bfeed5da871a04da0b61400af8793a" +dependencies = [ + "itertools 0.12.1", + "p3-field", + "serde", +] + +[[package]] +name = "p3-uni-stark" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc3dfdeba14d8db621c4e52dd63973384ff35f353fd750154ff88397f4ea5adf" +dependencies = [ + "itertools 0.12.1", + "p3-air", + "p3-challenger", + "p3-commit", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "serde", + "tracing", +] + +[[package]] +name = "p3-util" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff962f8eaa5f36e0447cee7c241f6b4b475fadf3ee61f154327a26bb4e009ba" +dependencies = [ + "serde", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap 2.14.0", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.11+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.117", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.3", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "range-set-blaze" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8421b5d459262eabbe49048d362897ff3e3830b44eac6cfe341d6acb2f0f13d2" +dependencies = [ + "gen_ops", + "itertools 0.12.1", + "num-integer", + "num-traits", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rayon-scan" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f87cc11a0140b4b0da0ffc889885760c61b13672d80a908920b2c0df078fa14" +dependencies = [ + "rayon", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower 0.5.3", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rrs-succinct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd079cd303257a4cb4e5aadfa79a7fe23f3c8301aa4740ccc3a99673485a352" +dependencies = [ + "downcast-rs", + "num_enum", + "paste", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ruzstd" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7c1c839d570d835527c9a5e4db7cb2198683a988cb9d7293fc8674e6bd58fc8" +dependencies = [ + "twox-hash", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "scale-info" +version = "2.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" +dependencies = [ + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", +] + +[[package]] +name = "scale-info-derive" +version = "2.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array 0.14.9", + "pkcs8", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_arrays" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a16b99c5ea4fe3daccd14853ad260ec00ea043b2708d1fd1da3106dcd8d9df" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "serial_test" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f" +dependencies = [ + "futures-executor", + "futures-util", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slop-air" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "p3-air", +] + +[[package]] +name = "slop-algebra" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "itertools 0.14.0", + "p3-field", + "serde", +] + +[[package]] +name = "slop-alloc" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "serde", + "slop-algebra", + "thiserror 1.0.69", +] + +[[package]] +name = "slop-baby-bear" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "lazy_static", + "p3-baby-bear", + "serde", + "slop-algebra", + "slop-challenger", + "slop-poseidon2", + "slop-symmetric", +] + +[[package]] +name = "slop-basefold" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "derive-where", + "itertools 0.14.0", + "serde", + "slop-algebra", + "slop-alloc", + "slop-baby-bear", + "slop-bn254", + "slop-challenger", + "slop-koala-bear", + "slop-merkle-tree", + "slop-multilinear", + "slop-primitives", + "slop-tensor", + "slop-utils", + "thiserror 1.0.69", +] + +[[package]] +name = "slop-basefold-prover" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "derive-where", + "itertools 0.14.0", + "rand 0.8.6", + "serde", + "slop-algebra", + "slop-alloc", + "slop-baby-bear", + "slop-basefold", + "slop-bn254", + "slop-challenger", + "slop-commit", + "slop-dft", + "slop-fri", + "slop-futures", + "slop-koala-bear", + "slop-merkle-tree", + "slop-multilinear", + "slop-tensor", + "thiserror 1.0.69", +] + +[[package]] +name = "slop-bn254" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "ff", + "p3-bn254-fr", + "serde", + "slop-algebra", + "slop-challenger", + "slop-poseidon2", + "slop-symmetric", +] + +[[package]] +name = "slop-challenger" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "futures", + "p3-challenger", + "serde", + "slop-algebra", + "slop-symmetric", +] + +[[package]] +name = "slop-commit" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "p3-commit", + "serde", + "slop-alloc", +] + +[[package]] +name = "slop-dft" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "p3-dft", + "serde", + "slop-algebra", + "slop-alloc", + "slop-matrix", + "slop-tensor", +] + +[[package]] +name = "slop-fri" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "p3-fri", +] + +[[package]] +name = "slop-futures" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "crossbeam", + "futures", + "pin-project", + "rayon", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "slop-jagged" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "derive-where", + "futures", + "itertools 0.14.0", + "num_cpus", + "rand 0.8.6", + "rayon", + "serde", + "slop-algebra", + "slop-alloc", + "slop-baby-bear", + "slop-basefold", + "slop-basefold-prover", + "slop-bn254", + "slop-challenger", + "slop-commit", + "slop-futures", + "slop-koala-bear", + "slop-merkle-tree", + "slop-multilinear", + "slop-stacked", + "slop-sumcheck", + "slop-symmetric", + "slop-tensor", + "slop-utils", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "slop-keccak-air" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "p3-keccak-air", +] + +[[package]] +name = "slop-koala-bear" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "lazy_static", + "p3-koala-bear", + "serde", + "slop-algebra", + "slop-challenger", + "slop-poseidon2", + "slop-symmetric", +] + +[[package]] +name = "slop-matrix" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "p3-matrix", +] + +[[package]] +name = "slop-maybe-rayon" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "p3-maybe-rayon", +] + +[[package]] +name = "slop-merkle-tree" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "derive-where", + "itertools 0.14.0", + "p3-merkle-tree", + "serde", + "slop-algebra", + "slop-alloc", + "slop-baby-bear", + "slop-bn254", + "slop-challenger", + "slop-commit", + "slop-futures", + "slop-koala-bear", + "slop-matrix", + "slop-poseidon2", + "slop-symmetric", + "slop-tensor", + "slop-utils", + "thiserror 1.0.69", +] + +[[package]] +name = "slop-multilinear" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "derive-where", + "futures", + "num_cpus", + "rand 0.8.6", + "rayon", + "serde", + "slop-algebra", + "slop-alloc", + "slop-challenger", + "slop-commit", + "slop-futures", + "slop-matrix", + "slop-tensor", +] + +[[package]] +name = "slop-poseidon2" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "p3-poseidon2", +] + +[[package]] +name = "slop-primitives" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "slop-algebra", +] + +[[package]] +name = "slop-stacked" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "derive-where", + "futures", + "itertools 0.14.0", + "serde", + "slop-algebra", + "slop-alloc", + "slop-basefold", + "slop-basefold-prover", + "slop-challenger", + "slop-commit", + "slop-futures", + "slop-merkle-tree", + "slop-multilinear", + "slop-tensor", + "thiserror 1.0.69", +] + +[[package]] +name = "slop-sumcheck" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "futures", + "itertools 0.14.0", + "rayon", + "serde", + "slop-algebra", + "slop-alloc", + "slop-baby-bear", + "slop-challenger", + "slop-multilinear", + "thiserror 1.0.69", +] + +[[package]] +name = "slop-symmetric" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "p3-symmetric", +] + +[[package]] +name = "slop-tensor" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "arrayvec", + "derive-where", + "itertools 0.14.0", + "rand 0.8.6", + "rayon", + "serde", + "slop-algebra", + "slop-alloc", + "slop-futures", + "slop-matrix", + "thiserror 1.0.69", + "transpose", +] + +[[package]] +name = "slop-uni-stark" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "p3-uni-stark", +] + +[[package]] +name = "slop-utils" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "p3-util", + "tracing-forest", + "tracing-subscriber", +] + +[[package]] +name = "slop-whir" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "derive-where", + "futures", + "itertools 0.14.0", + "rand 0.8.6", + "rayon", + "serde", + "slop-algebra", + "slop-alloc", + "slop-baby-bear", + "slop-basefold", + "slop-challenger", + "slop-commit", + "slop-dft", + "slop-jagged", + "slop-koala-bear", + "slop-matrix", + "slop-merkle-tree", + "slop-multilinear", + "slop-stacked", + "slop-tensor", + "slop-utils", + "thiserror 1.0.69", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "snowbridge-amcl" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460a9ed63cdf03c1b9847e8a12a5f5ba19c4efd5869e4a737e05be25d7c427e5" +dependencies = [ + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "sp1-build" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "anyhow", + "cargo_metadata", + "chrono", + "clap", + "dirs", + "sp1-primitives", +] + +[[package]] +name = "sp1-core-executor" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "bincode", + "bytemuck", + "cfg-if", + "clap", + "deepsize2", + "elf", + "enum-map", + "eyre", + "gecko_profile", + "hashbrown 0.14.5", + "hex", + "indicatif", + "itertools 0.14.0", + "memmap2", + "num", + "object", + "rrs-succinct", + "rustc-demangle", + "serde", + "serde_arrays", + "serde_json", + "slop-air", + "slop-algebra", + "slop-maybe-rayon", + "slop-symmetric", + "sp1-curves", + "sp1-hypercube", + "sp1-jit", + "sp1-primitives", + "strum", + "subenum", + "thiserror 1.0.69", + "tiny-keccak", + "tracing", + "typenum", + "vec_map", +] + +[[package]] +name = "sp1-core-executor-runner" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "base64", + "bincode", + "cargo_metadata", + "hashbrown 0.14.5", + "hex", + "libc", + "sha2", + "sp1-core-executor", + "sp1-core-executor-runner-binary", + "sp1-jit", + "sp1-primitives", + "sysinfo", + "tracing", + "uuid", +] + +[[package]] +name = "sp1-core-executor-runner-binary" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "bincode", + "crash-handler", + "libc", + "serde", + "sp1-core-executor", + "sp1-jit", + "tracing-subscriber", +] + +[[package]] +name = "sp1-core-machine" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "bincode", + "cfg-if", + "enum-map", + "futures", + "generic-array 1.1.0", + "hashbrown 0.14.5", + "itertools 0.14.0", + "num", + "num_cpus", + "rayon", + "rayon-scan", + "rrs-succinct", + "serde", + "serde_json", + "slop-air", + "slop-algebra", + "slop-challenger", + "slop-futures", + "slop-keccak-air", + "slop-matrix", + "slop-maybe-rayon", + "slop-uni-stark", + "snowbridge-amcl", + "sp1-core-executor", + "sp1-core-executor-runner", + "sp1-curves", + "sp1-derive", + "sp1-hypercube", + "sp1-jit", + "sp1-primitives", + "static_assertions", + "struct-reflection", + "strum", + "sysinfo", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tracing", + "tracing-forest", + "tracing-subscriber", + "typenum", +] + +[[package]] +name = "sp1-cuda" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "bincode", + "bytes", + "reqwest", + "serde", + "serde_json", + "sp1-core-executor", + "sp1-core-machine", + "sp1-hypercube", + "sp1-primitives", + "sp1-prover", + "sp1-prover-types", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "sp1-curves" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "cfg-if", + "dashu", + "elliptic-curve", + "generic-array 1.1.0", + "itertools 0.14.0", + "k256", + "num", + "p256", + "serde", + "slop-algebra", + "snowbridge-amcl", + "sp1-primitives", + "typenum", +] + +[[package]] +name = "sp1-derive" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sp1-host" +version = "0.1.0" +dependencies = [ + "anyhow", + "bincode", + "clap", + "human-repr", + "ix-common", + "ix-kernel", + "ixon", + "sp1-build", + "sp1-sdk", + "tokio", +] + +[[package]] +name = "sp1-hypercube" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "arrayref", + "deepsize2", + "derive-where", + "futures", + "hashbrown 0.14.5", + "itertools 0.14.0", + "num-bigint 0.4.6", + "num-traits", + "num_cpus", + "rayon", + "rayon-scan", + "serde", + "slop-air", + "slop-algebra", + "slop-alloc", + "slop-basefold", + "slop-basefold-prover", + "slop-bn254", + "slop-challenger", + "slop-commit", + "slop-futures", + "slop-jagged", + "slop-koala-bear", + "slop-matrix", + "slop-merkle-tree", + "slop-multilinear", + "slop-poseidon2", + "slop-stacked", + "slop-sumcheck", + "slop-symmetric", + "slop-tensor", + "slop-uni-stark", + "slop-whir", + "sp1-derive", + "sp1-primitives", + "struct-reflection", + "strum", + "thiserror 1.0.69", + "thousands", + "tokio", + "tracing", +] + +[[package]] +name = "sp1-jit" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "dynasmrt", + "hashbrown 0.14.5", + "libc", + "memfd", + "memmap2", + "serde", + "sp1-primitives", + "tracing", + "uuid", +] + +[[package]] +name = "sp1-primitives" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "bincode", + "blake3", + "elf", + "hex", + "itertools 0.14.0", + "lazy_static", + "num-bigint 0.4.6", + "serde", + "sha2", + "slop-algebra", + "slop-bn254", + "slop-challenger", + "slop-koala-bear", + "slop-poseidon2", + "slop-primitives", + "slop-symmetric", +] + +[[package]] +name = "sp1-prover" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "anyhow", + "bincode", + "clap", + "dirs", + "either", + "enum-map", + "eyre", + "futures", + "hashbrown 0.14.5", + "hex", + "indicatif", + "itertools 0.14.0", + "lru", + "mti", + "num-bigint 0.4.6", + "opentelemetry", + "pin-project", + "rand 0.8.6", + "reqwest", + "serde", + "serde_json", + "serial_test", + "sha2", + "slop-air", + "slop-algebra", + "slop-basefold", + "slop-bn254", + "slop-challenger", + "slop-futures", + "slop-jagged", + "slop-multilinear", + "slop-stacked", + "slop-symmetric", + "sp1-core-executor", + "sp1-core-executor-runner", + "sp1-core-machine", + "sp1-derive", + "sp1-hypercube", + "sp1-jit", + "sp1-primitives", + "sp1-prover-types", + "sp1-recursion-circuit", + "sp1-recursion-compiler", + "sp1-recursion-executor", + "sp1-recursion-gnark-ffi", + "sp1-recursion-machine", + "sp1-verifier", + "static_assertions", + "sysinfo", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tonic", + "tracing", + "tracing-appender", + "tracing-subscriber", +] + +[[package]] +name = "sp1-prover-types" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "anyhow", + "async-scoped", + "bincode", + "chrono", + "futures-util", + "hashbrown 0.14.5", + "mti", + "prost", + "serde", + "sp1-core-machine", + "sp1-hypercube", + "sp1-primitives", + "tokio", + "tonic", + "tonic-build", + "tracing", +] + +[[package]] +name = "sp1-recursion-circuit" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "bincode", + "itertools 0.14.0", + "rand 0.8.6", + "rayon", + "serde", + "slop-air", + "slop-algebra", + "slop-alloc", + "slop-basefold", + "slop-basefold-prover", + "slop-bn254", + "slop-challenger", + "slop-commit", + "slop-jagged", + "slop-koala-bear", + "slop-matrix", + "slop-merkle-tree", + "slop-multilinear", + "slop-stacked", + "slop-sumcheck", + "slop-symmetric", + "slop-tensor", + "slop-whir", + "sp1-core-executor", + "sp1-core-machine", + "sp1-derive", + "sp1-hypercube", + "sp1-primitives", + "sp1-recursion-compiler", + "sp1-recursion-executor", + "sp1-recursion-machine", + "tracing", +] + +[[package]] +name = "sp1-recursion-compiler" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "backtrace", + "cfg-if", + "itertools 0.14.0", + "serde", + "slop-algebra", + "slop-bn254", + "slop-symmetric", + "sp1-core-machine", + "sp1-hypercube", + "sp1-primitives", + "sp1-recursion-executor", + "tracing", + "vec_map", +] + +[[package]] +name = "sp1-recursion-executor" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "backtrace", + "cfg-if", + "hashbrown 0.14.5", + "itertools 0.14.0", + "range-set-blaze", + "serde", + "slop-algebra", + "slop-maybe-rayon", + "slop-poseidon2", + "slop-symmetric", + "smallvec", + "sp1-derive", + "sp1-hypercube", + "static_assertions", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "sp1-recursion-gnark-ffi" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "anyhow", + "bincode", + "cfg-if", + "hex", + "num-bigint 0.4.6", + "serde", + "serde_json", + "sha2", + "slop-algebra", + "slop-symmetric", + "sp1-hypercube", + "sp1-primitives", + "sp1-recursion-compiler", + "sp1-verifier", + "tempfile", + "tracing", +] + +[[package]] +name = "sp1-recursion-machine" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "itertools 0.14.0", + "rand 0.8.6", + "slop-air", + "slop-algebra", + "slop-basefold", + "slop-matrix", + "slop-maybe-rayon", + "slop-symmetric", + "sp1-derive", + "sp1-hypercube", + "sp1-primitives", + "sp1-recursion-executor", + "strum", + "tracing", +] + +[[package]] +name = "sp1-sdk" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "anyhow", + "async-trait", + "bincode", + "cfg-if", + "dirs", + "eventsource-stream", + "futures", + "hex", + "indicatif", + "itertools 0.14.0", + "k256", + "num-bigint 0.4.6", + "serde", + "sha2", + "sp1-build", + "sp1-core-executor", + "sp1-core-executor-runner", + "sp1-core-machine", + "sp1-cuda", + "sp1-hypercube", + "sp1-primitives", + "sp1-prover", + "sp1-prover-types", + "sp1-recursion-executor", + "sp1-verifier", + "strum", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "sp1-verifier" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "bincode", + "blake3", + "cfg-if", + "dirs", + "hex", + "lazy_static", + "serde", + "sha2", + "slop-algebra", + "slop-challenger", + "slop-primitives", + "slop-symmetric", + "sp1-hypercube", + "sp1-primitives", + "sp1-recursion-executor", + "sp1-recursion-machine", + "strum", + "substrate-bn-succinct-rs", + "thiserror 2.0.18", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "struct-reflection" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "701b671d1ad68e250e05718f95dae3014a17f4e69cbe51842531c30495ff3301" +dependencies = [ + "struct-reflection-derive", +] + +[[package]] +name = "struct-reflection-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ab74230a0592602e361bd63c645413fa8cbe4500d10274e849179e5c72548f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "subenum" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee3fb942ed39f3971438fcc7e05e20717e599e14c5c7cb50edd0df2a44b274" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "substrate-bn-succinct-rs" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a241fd7c1016fb8ad30fcf5a20986c0c4538e8f15a1b41a1761516299e377ec1" +dependencies = [ + "bytemuck", + "byteorder", + "cfg-if", + "crunchy", + "lazy_static", + "num-bigint 0.4.6", + "rand 0.8.6", + "rustc-hex", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sysinfo" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "windows", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thousands" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 0.6.11", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.3", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", +] + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "rustls-pemfile", + "socket2 0.5.10", + "tokio", + "tokio-rustls", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.6", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower 0.5.3", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror 2.0.18", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-forest" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee40835db14ddd1e3ba414292272eddde9dad04d3d4b65509656414d1c42592f" +dependencies = [ + "ansi_term", + "smallvec", + "thiserror 1.0.69", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + +[[package]] +name = "typeid_prefix" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9da1387307fdee46aa441e4f08a1b491e659fcac1aca9cd71f2c624a0de5d1b" + +[[package]] +name = "typeid_suffix" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b55e96f110c6db5d1a2f24072552537f0091dc90cebeaa679540bac93e7405" +dependencies = [ + "uuid", +] + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +dependencies = [ + "atomic", + "getrandom 0.4.2", + "js-sys", + "md-5", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +dependencies = [ + "serde", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.14.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.14.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/sp1/Cargo.toml b/sp1/Cargo.toml new file mode 100644 index 00000000..91757491 --- /dev/null +++ b/sp1/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = ["host"] +# `guest/` is built via `sp1-build` for the RISC-V zkVM target and only +# compiles under `cargo-prove`; excluded so host workspace ops don't pick it up. +exclude = ["guest"] +resolver = "2" diff --git a/sp1/guest/Cargo.lock b/sp1/guest/Cargo.lock new file mode 100644 index 00000000..45d6d00e --- /dev/null +++ b/sp1/guest/Cargo.lock @@ -0,0 +1,1271 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addchain" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e33f6a175ec6a9e0aca777567f9ff7c3deefc255660df887e7fa3585e9801d8" +dependencies = [ + "num-bigint 0.3.3", + "num-integer", + "num-traits", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bignat" +version = "0.1.0" +source = "git+https://github.com/argumentcomputer/lean-ffi.git?rev=839b405de708b80504fda39abe1402114b4135a5#839b405de708b80504fda39abe1402114b4135a5" +dependencies = [ + "num-bigint 0.4.6", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake3" +version = "1.8.5" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", + "rayon", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + +[[package]] +name = "embedded-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd" +dependencies = [ + "const-default", + "critical-section", + "linked_list_allocator", + "rlsf", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "byteorder", + "ff_derive", + "rand_core", + "subtle", +] + +[[package]] +name = "ff_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f10d12652036b0e99197587c6ba87a8fc3031986499973c030d8b44fcc151b60" +dependencies = [ + "addchain", + "num-bigint 0.3.3", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "ix-common" +version = "0.1.0" +dependencies = [ + "bignat", + "blake3", + "indexmap", + "rustc-hash", +] + +[[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", + "rayon", + "rustc-hash", +] + +[[package]] +name = "ixon" +version = "0.1.0" +dependencies = [ + "bignat", + "blake3", + "dashmap", + "indexmap", + "ix-common", + "memmap2", + "num-bigint 0.4.6", + "rayon", + "rustc-hash", + "sha2", + "tiny-keccak", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linked_list_allocator" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b23ac50abb8261cb38c6e2a7192d3302e0836dac1628f6a93b82b4fad185897" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "p3-bn254-fr" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2077757c7cb514202ccb5368f521f23f5709c720599e6545c683c66e0a52d2d8" +dependencies = [ + "ff", + "num-bigint 0.4.6", + "p3-field", + "p3-poseidon2", + "p3-symmetric", + "rand", + "serde", +] + +[[package]] +name = "p3-challenger" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a908924d43e4cfb93fb41c8346cac211b70314385a9037e9241f5b7f3eaf77" +dependencies = [ + "p3-field", + "p3-maybe-rayon", + "p3-symmetric", + "p3-util", + "serde", + "tracing", +] + +[[package]] +name = "p3-dft" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be6408b10a2c27eb13a7d5580c546c2179a8dc7dbc10a990657311891f9b41c0" +dependencies = [ + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "tracing", +] + +[[package]] +name = "p3-field" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dc75969ca3ac847f43e632ab979d59ff7a68f9eac8dbf8edcbba47fc2e1d3aa" +dependencies = [ + "itertools 0.12.1", + "num-bigint 0.4.6", + "num-traits", + "p3-util", + "rand", + "serde", +] + +[[package]] +name = "p3-koala-bear" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9683cd0ef68100df7c62490533047bcf19c04c4a0fa1efc9d7c1e03e31f6b3" +dependencies = [ + "cfg-if", + "num-bigint 0.4.6", + "p3-field", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", + "rand", + "rustc_version", + "serde", +] + +[[package]] +name = "p3-matrix" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c3f150ceb90e09539413bf481e618d05ee19210b4e467d2902eb82d2e15281" +dependencies = [ + "itertools 0.12.1", + "p3-field", + "p3-maybe-rayon", + "p3-util", + "rand", + "serde", + "tracing", +] + +[[package]] +name = "p3-maybe-rayon" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0641952b42da45e1dfa2d4a2a3163e330f944ad9740942f35026c0a71a605f1" + +[[package]] +name = "p3-mds" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4a5f250e174dcfca5cbeac6ad75713924e7e7320e0a335e3c50b8b1f4fe8ec" +dependencies = [ + "itertools 0.12.1", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-symmetric", + "p3-util", + "rand", +] + +[[package]] +name = "p3-poseidon2" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "522986377b2164c5f94f2dae88e0e0a3d169cc6239202ef4aeb4322d60feffd0" +dependencies = [ + "gcd", + "p3-field", + "p3-mds", + "p3-symmetric", + "rand", + "serde", +] + +[[package]] +name = "p3-symmetric" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9047ce85c086a9b3f118e10078f10636f7bfeed5da871a04da0b61400af8793a" +dependencies = [ + "itertools 0.12.1", + "p3-field", + "serde", +] + +[[package]] +name = "p3-util" +version = "0.4.3-succinct" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff962f8eaa5f36e0447cee7c241f6b4b475fadf3ee61f154327a26bb4e009ba" +dependencies = [ + "serde", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rlsf" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1646a59a9734b8b7a0ac51689388a60fe1625d4b956348e9de07591a1478457a" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "rustversion", + "svgbobdoc", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slop-algebra" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "itertools 0.14.0", + "p3-field", + "serde", +] + +[[package]] +name = "slop-bn254" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "ff", + "p3-bn254-fr", + "serde", + "slop-algebra", + "slop-challenger", + "slop-poseidon2", + "slop-symmetric", +] + +[[package]] +name = "slop-challenger" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "futures", + "p3-challenger", + "serde", + "slop-algebra", + "slop-symmetric", +] + +[[package]] +name = "slop-koala-bear" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "lazy_static", + "p3-koala-bear", + "serde", + "slop-algebra", + "slop-challenger", + "slop-poseidon2", + "slop-symmetric", +] + +[[package]] +name = "slop-poseidon2" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "p3-poseidon2", +] + +[[package]] +name = "slop-primitives" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "slop-algebra", +] + +[[package]] +name = "slop-symmetric" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "p3-symmetric", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "sp1-guest" +version = "0.1.0" +dependencies = [ + "ix-common", + "ix-kernel", + "ixon", + "sp1-zkvm", +] + +[[package]] +name = "sp1-lib" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "bincode", + "serde", + "sp1-primitives", +] + +[[package]] +name = "sp1-primitives" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "bincode", + "blake3", + "elf", + "hex", + "itertools 0.14.0", + "lazy_static", + "num-bigint 0.4.6", + "serde", + "sha2", + "slop-algebra", + "slop-bn254", + "slop-challenger", + "slop-koala-bear", + "slop-poseidon2", + "slop-primitives", + "slop-symmetric", +] + +[[package]] +name = "sp1-zkvm" +version = "6.2.2" +source = "git+https://github.com/argumentcomputer/sp1?branch=blake3-precompile#ab7cda20b0e2f892bb5c5cb0ccf5c1ce5ba12c6b" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-alloc", + "getrandom 0.2.17", + "getrandom 0.3.4", + "lazy_static", + "libm", + "rand", + "sha2", + "sp1-lib", + "sp1-primitives", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "svgbobdoc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" +dependencies = [ + "base64", + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-width", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] diff --git a/sp1/guest/Cargo.toml b/sp1/guest/Cargo.toml new file mode 100644 index 00000000..0c9587b0 --- /dev/null +++ b/sp1/guest/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "sp1-guest" +version = "0.1.0" +edition = "2024" + +[dependencies] +ixon = { path = "../../crates/ixon" } +ix-kernel = { path = "../../crates/kernel" } +ix-common = { path = "../../crates/common" } +sp1-zkvm = { git = "https://github.com/argumentcomputer/sp1", branch = "blake3-precompile" } + +# Force blake3 to the patched copy that routes `compress_in_place` / +# `compress_xof` through SP1's BLAKE3_COMPRESS precompile on the zkVM target. +# The guest's transitive blake3 (via ix-kernel) would otherwise resolve to +# the registry version because `sp1-build` doesn't extend the host workspace. +# +# Stays a local path because the patched blake3 fork hasn't been published to a +# git remote (no argumentcomputer/blake3 repo exists yet). Switch to a +# `git = ..., branch = "blake3-precompile"` ref once it's pushed. +[patch.crates-io] +blake3 = { path = "../../../blake3-sp1" } diff --git a/sp1/guest/src/main.rs b/sp1/guest/src/main.rs new file mode 100644 index 00000000..ff1e9207 --- /dev/null +++ b/sp1/guest/src/main.rs @@ -0,0 +1,192 @@ +//! SP1 guest binary: prove that every kernel-checkable constant in a +//! serialized Ixon environment typechecks under the Ix kernel. +//! +//! Input (two reads, in order): +//! 1. `u8`: kernel mode flag (0 = Anon default, 1 = Meta) via `read::`. +//! 2. `Vec`: Ixon-serialized environment via `read_vec`. +//! +//! Output (via `sp1_zkvm::io::commit_slice`, fixed 104-byte layout): +//! - `u32` failures (offset 0; 0 = all pass) +//! - `[u8; 32]` subject_root — canonical merkle root over the consts +//! this proof certified (Anon mode; zero in Meta) +//! - `[u8; 32]` assumptions_root — merkle root over their direct refs not +//! certified here (the conditional claim's external assumptions) +//! - `u32` checked_count +//! - `[u8; 32]` env_hash +//! +//! `(subject_root, assumptions_root)` form `Claim::CheckEnv { root, assumptions }` +//! — the same content-addressed conditional claim the Zisk leaf guest emits. +//! +//! Anon mode enumerates the work set in-guest via +//! [`ix_kernel::anon_work::build_anon_work`] — the same enumeration +//! that drives `rs_kernel_check_anon` in `crates/ffi/src/kernel.rs`. +//! The proof commits to "every kernel-checkable constant in this env +//! typechecks," matching Aiur's `kernel_check_test` semantics. +//! +//! Meta mode walks `env.named` for the kid set (preserves Lean names +//! + runs the dup-level-param-name check); slightly more constrained +//! and slightly more expensive in cycles. +//! +//! Both modes typecheck the same underlying structural set; they +//! differ only in which metadata fields the kernel carries through +//! `KEnv`. + +#![no_main] +sp1_zkvm::entrypoint!(main); + +use ix_common::address::Address; +use ix_kernel::anon_work::build_anon_work; +use ix_kernel::env::KEnv; +use ix_kernel::id::KId; +use ix_kernel::ingress::ixon_ingress_owned; +use ix_kernel::mode::{Anon, Meta}; +use ix_kernel::tc::TypeChecker; +use ixon::env::Env as IxonEnv; +use ixon::merkle::{merkle_root_canonical, zero_address}; +use ixon::metadata::ConstantMetaInfo; + +// SP1's cycle tracker (gated by the SDK `profiling` feature on the host +// side — see `sp1/host/Cargo.toml`) consumes `println!`s of the form +// `cycle-tracker-report-{start,end}: ` from stdout and accumulates +// total cycles per label across all invocations of that label. Each +// `println!` is a single `syscall_write`, so per-iteration brackets in +// hot loops add measurable overhead — keep these at coarse phase +// boundaries. To get per-constant cycle attribution (e.g. for a one-off +// "which `kid_NNN` dominates?" investigation), temporarily wrap the +// `tc.check_const(kid)` call below in the same pair of `println!`s; pair +// the output with `crates/kernel/examples/perf_check.rs` to map indices +// to constant names. +macro_rules! tic { ($name:expr) => { println!("cycle-tracker-report-start: {}", $name) } } +macro_rules! toc { ($name:expr) => { println!("cycle-tracker-report-end: {}", $name) } } + +pub fn main() { + let meta_mode: u8 = sp1_zkvm::io::read(); + let env_bytes: Vec = sp1_zkvm::io::read_vec(); + // `Address::hash` is blake3 via the precompile shim (same path the kernel + // uses) — binds the proof to the exact env payload. + let env_hash: [u8; 32] = *Address::hash(&env_bytes).as_bytes(); + + // Conditional-claim outputs: populated in Anon mode (the claim/resolution + // path, mirroring the Zisk leaf guest); left zero in Meta mode. + let mut subject_root = zero_address(); + let mut assumptions_root = zero_address(); + let mut checked_count: u32 = 0; + + let failures = if meta_mode == 1 { + // ---- Meta mode: full env load + eager ingress ---- + // + // `ixon_ingress_inner` walks `env.named` to enumerate work items, + // so we need the full env (with the metadata sections intact). + tic!("ixon_decode_env"); + let env = + IxonEnv::get(&mut &env_bytes[..]).expect("invalid Ixon environment"); + toc!("ixon_decode_env"); + + let mut kids: Vec> = Vec::with_capacity(env.named.len()); + kids.extend( + env + .named + .iter() + .filter(|e| !matches!(e.value().meta.info, ConstantMetaInfo::Muts { .. })) + .map(|e| KId::::new(e.value().addr.clone(), e.key().clone())), + ); + + tic!("ingress"); + let (mut kenv, _intern) = + ixon_ingress_owned::(env).expect("ingress failed"); + toc!("ingress"); + + let mut failures: u32 = 0; + tic!("check_const_loop"); + let mut tc = TypeChecker::new(&mut kenv); + for kid in &kids { + if tc.check_const(kid).is_err() { + failures = failures.saturating_add(1); + } + } + toc!("check_const_loop"); + failures + } else { + // ---- Anon mode: get_anon + lazy on-demand ingress ---- + // + // `Env::get_anon` parses-and-discards the metadata sections + // (named/names/comms) so the steady-state env carries only + // `consts`/`blobs`/`anon_hints`. `build_anon_work` enumerates the + // canonical kernel-checkable work set from `env.consts` alone + // (no metadata) — same logic the FFI's `rs_kernel_check_anon` + // uses. Each `tc.check_const(primary)` either typechecks one + // standalone or drives the kernel's block coordination to cover + // every member + ctor of a Muts block. + tic!("ixon_decode_env"); + let env = + IxonEnv::get_anon(&mut &env_bytes[..]).expect("invalid Ixon environment"); + toc!("ixon_decode_env"); + + tic!("build_anon_work"); + let work = build_anon_work(&env).expect("build_anon_work"); + toc!("build_anon_work"); + + let mut kenv = KEnv::::new(); + let mut failures: u32 = 0; + let mut checked_covered: Vec
= Vec::new(); + tic!("check_const_loop"); + let mut tc = TypeChecker::::new_with_lazy_anon(&mut kenv, &env); + for item in &work { + let kid = KId::::new(item.primary().clone(), ()); + if tc.check_const(&kid).is_err() { + failures = failures.saturating_add(1); + } + // Certify in `consts`-key space (block addr + projections) — the same + // address space `Constant.refs` use, so the assumption set and its + // cross-proof resolution line up. + checked_covered.extend(item.proven_targets()); + } + toc!("check_const_loop"); + + // ---- Conditional claim: CheckEnv { subject, assumptions } ---- + // + // subject = canonical merkle root over the consts certified here. + // assumptions = canonical merkle root over their DIRECT refs not + // certified here — this whole-env proof's external dependencies + // (ultimately the declared axioms). Mirrors the Zisk leaf guest so + // both backends emit the same content-addressed claim. + // + // A `Constant.refs` entry points at either another CONSTANT (a genuine + // typing obligation) or a literal DATA blob (the bytes behind an + // `Expr::Nat`/`Expr::Str` — e.g. the numbers 0, 1). Literals are + // well-typed by construction with content-pinned bytes, so they are NOT + // assumptions; skip refs that resolve to a blob (must match Zisk). + tic!("claim_roots"); + checked_count = checked_covered.len() as u32; + subject_root = + merkle_root_canonical(&checked_covered).unwrap_or_else(zero_address); + let mut sorted = checked_covered.clone(); + sorted.sort_unstable(); + sorted.dedup(); + let mut assumptions: Vec
= Vec::new(); + for t in &checked_covered { + if let Some(c) = env.get_const(t) { + for r in &c.refs { + if sorted.binary_search(r).is_err() && env.get_blob(r).is_none() { + assumptions.push(r.clone()); + } + } + } + } + assumptions_root = + merkle_root_canonical(&assumptions).unwrap_or_else(zero_address); + toc!("claim_roots"); + failures + }; + + // Fixed 104-byte public output: failures(4) + subject_root(32) + + // assumptions_root(32) + checked_count(4) + env_hash(32). `failures` stays + // at offset 0 so existing readers keep working. + let mut out = Vec::with_capacity(104); + out.extend_from_slice(&failures.to_le_bytes()); + out.extend_from_slice(subject_root.as_bytes()); + out.extend_from_slice(assumptions_root.as_bytes()); + out.extend_from_slice(&checked_count.to_le_bytes()); + out.extend_from_slice(&env_hash); + sp1_zkvm::io::commit_slice(&out); +} diff --git a/sp1/host/Cargo.toml b/sp1/host/Cargo.toml new file mode 100644 index 00000000..f45c1d3c --- /dev/null +++ b/sp1/host/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "sp1-host" +version = "0.1.0" +edition = "2024" +default-run = "sp1-host" + +[[bin]] +name = "sp1-host" +path = "src/main.rs" + +[dependencies] +ixon = { path = "../../crates/ixon" } +ix-common = { path = "../../crates/common" } +# Used only to count work items for human-readable output via +# `build_anon_work` — same enumeration the guest runs in-circuit. +ix-kernel = { path = "../../crates/kernel" } +# `profiling` turns on the SP1 executor's cycle-tracker: it reads +# `cycle-tracker-report-{start,end}: