Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ebf2fc1
Compile spill steps 0+1: accumulator instrumentation and demote mode
samuelburnham Jul 8, 2026
c879d2d
Compile spill step 2: mmap-backed accumulator bytes
samuelburnham Jul 8, 2026
6a564f9
Compile spill: RSS decomposition instrumentation + worker kenv clearing
samuelburnham Jul 9, 2026
e7c232b
Compile spill lever 2: demote named metadata to serialized bytes
samuelburnham Jul 9, 2026
adb9a02
Compile spill lever 4: stream the .ixe from Rust (IX_COMPILE_STREAM=1)
samuelburnham Jul 9, 2026
f5d97ae
Compile spill lever 1: lazy LeanEnv decode (IX_COMPILE_LEAN_ENV=lazy)
samuelburnham Jul 9, 2026
87a96f2
Compile: fuse the three whole-env setup sweeps into one scan
samuelburnham Jul 9, 2026
2d15020
Compile spill: move design-doc content into module docs
samuelburnham Jul 10, 2026
bde1789
Compile memory: consolidate to two knobs, always-on defaults
samuelburnham Jul 10, 2026
a29a34e
Compile FFI: rs_compile_env compiles straight to a file
samuelburnham Jul 10, 2026
d25fba3
Compile: decode-time counters for the lazy env
samuelburnham Jul 10, 2026
f973793
Compile: fix all-features clippy and test-ffi fallout
samuelburnham Jul 10, 2026
f216728
Compile: drop the measurement instrumentation
samuelburnham Jul 10, 2026
fc29c3f
Compile: size the lazy-env cache by sweep, 65536 -> 16384 entries
samuelburnham Jul 10, 2026
1379125
sp1 guest: read Named metadata through the accessor
samuelburnham Jul 10, 2026
b4446f5
Compile: parallelize put_file's named-section encode
samuelburnham Jul 11, 2026
eade07d
Compile: IX_COMPILE_EAGER=1 knob for RAM-rich machines
samuelburnham Jul 11, 2026
011107f
Compile: pin the hottest constants in the lazy env
samuelburnham Jul 11, 2026
3059268
bench-pr: IX_COMPILE_* knob passthrough to the measured compile
samuelburnham Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions .github/workflows/bench-pr.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,10 @@
# BENCH_SHARD=1 # restrict to the multi-shard target constants
# BENCH_PHASES=1 # add per-constant phase drill-downs to the comment
# RUST_LOG=info # passthrough env (allowlisted)
# IX_COMPILE_EAGER=1 # compile-knob passthrough (also IX_COMPILE_DEMOTE /
# # IX_COMPILE_WORKERS); reaches the measured `ix
# # compile` and keys its caches, so a knob run gets
# # its own row instead of the default run's
#
# Mode defaults per backend (the registry's defaultMode): `aiur` runs
# `prove` — the real-workload simulation, whose report also carries the
Expand DownExpand Up@@ -197,14 +201,22 @@ jobs:
ref: ${{ inputs.head-sha }}
# The job runs PR code; never leave the token in .git.
persist-credentials: false
# Re-running !benchmark on the same commit: the .ixe is already
# published — nothing to do.
# Allowlisted KEY=VALUE lines from the !benchmark comment. Applied
# here so IX_COMPILE_* knobs reach the measured compile, and staged
# to a file so the cache keys below can hash it — a knob run must
# not reuse (or overwrite) the default run's published .ixe/row.
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Re-running !benchmark on the same commit with the same config: the
# .ixe is already published — nothing to do.
- name: Check for published .ixe
id: pr-ixe
uses: actions/cache/restore@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
lookup-only: true
- name: Restore PR binaries
if: steps.pr-ixe.outputs.cache-hit != 'true'
Expand DownExpand Up@@ -242,15 +254,15 @@ jobs:
uses: actions/cache/save@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
# The measured row: the compile cell reuses it as its PR side (same
# runner class, same binaries, same command it would run itself).
- name: Publish compile row
if: steps.pr-ixe.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: compile.json
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}

benchmark:
# Explicit name: the default would append EVERY matrix value (backend,
Expand DownExpand Up@@ -294,7 +306,7 @@ jobs:
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' >> "$GITHUB_ENV"
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Restore the once-built PR binaries (see the build job) into the PR
# tree's own bin dir: `ix bench run` resolves the measured tools from
# <repo>/.lake/build/bin first, then PATH, so staging in-tree keeps the
Expand DownExpand Up@@ -334,15 +346,15 @@ jobs:
uses: actions/cache/restore@v5
with:
path: ${{ matrix.cell.env }}.ixe
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# Compile cells reuse the compile job's measured row as their PR side.
- name: Restore compile row
if: matrix.cell.backend == 'compile'
uses: actions/cache/restore@v5
with:
path: compile.json
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# zkVM cells additionally need the Rust toolchain + the backend's toolchain
# and system deps (the shared composite install actions).
Expand Down
14 changes: 10 additions & 4 deletions Ix/Cli/BenchReport.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -595,9 +595,13 @@ def parseError (msg : String) : IO UInt32 := do
BENCH_FULL=1 (full curated set, not just primary)
BENCH_SHARD=1 (only the multi-shard target constants)
BENCH_PHASES=1 / RUST_LOG=… / WITHOUT_VK_VERIFICATION=… /
RUSTFLAGS=… (passthrough; BENCH_PHASES=1 adds the
RUSTFLAGS=… / IX_COMPILE_EAGER=… / IX_COMPILE_DEMOTE=… /
IX_COMPILE_WORKERS=… (passthrough; BENCH_PHASES=1 adds the
per-constant phase drill-downs to the
comment)
comment; the IX_COMPILE_* knobs reach
the measured `ix compile` and key its
caches, so knob runs don't reuse a
default run's published row)

The KEY=VALUE config may sit on its own lines below the command (the
comment form) or inline on the command line, whitespace-separated
Expand DownExpand Up@@ -694,13 +698,15 @@ def runParseCmd (p : Cli.Parsed) : IO UInt32 := do
| "BENCH_FULL" => if val == "1" then full := "1"
| k =>
if ["BENCH_PHASES", "RUST_LOG", "WITHOUT_VK_VERIFICATION",
"RUSTFLAGS"].contains k then
"RUSTFLAGS", "IX_COMPILE_EAGER", "IX_COMPILE_DEMOTE",
"IX_COMPILE_WORKERS"].contains k then
passthrough := passthrough.push s!"{k}={val}"
else if strict then
return ← parseError s!"unknown config key `{k}` in the \
benchmark command (expected BENCH_ENVS / BENCH_FULL / \
BENCH_SHARD, or passthrough: BENCH_PHASES, RUST_LOG, \
WITHOUT_VK_VERIFICATION, RUSTFLAGS)"
WITHOUT_VK_VERIFICATION, RUSTFLAGS, IX_COMPILE_EAGER, \
IX_COMPILE_DEMOTE, IX_COMPILE_WORKERS)"
| [] => continue
if envs.isEmpty then envs := #["InitStd"]

Expand Down
24 changes: 10 additions & 14 deletions Ix/Cli/CompileCmd.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -134,11 +134,17 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
if benched then
TracingTexray.startSampler
TracingTexray.resetPeakTreeRss

-- Rust compiles and writes the `.ixe` directly (streamed — no
-- env-sized ByteArray crosses the FFI; `<out>.tmp` + atomic rename).
-- The file is the canonical `Ixon.Env::put` format and round-trips
-- through `Ixon.Env::get`, so later runs (e.g. `ix check-ixon`) can
-- skip the Lean → IxOn compile step.
let start ← IO.monoMsNow
let bytes ← Ix.CompileM.rsCompileEnvBytesFFI constList
let size ← Ix.CompileM.rsCompileEnvBytesFFI constList outPath
let elapsed := (← IO.monoMsNow) - start

println! "Compiled {fmtBytes bytes.size} env in {elapsed.formatMs}"
println! "Compiled and wrote {fmtBytes size} env to {outPath} in {elapsed.formatMs}"
IO.println s!"##benchmark## {elapsed} {size} {totalConsts}"
if let some flag := p.flag? "json" then
let key := (p.flag? "json-name").map (·.as! String)
|>.getD ((FilePath.mk pathStr).fileStem.getD "env")
Expand All@@ -148,20 +154,10 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
let peakRss ← TracingTexray.peakTreeRssBytes
Ix.Benchmark.Results.writeRow (flag.as! String) key "ok"
[ ("compile-time", Ix.Benchmark.Results.jsonRound 3 secs)
, ("file-size", Lean.toJson bytes.size)
, ("file-size", Lean.toJson size)
, ("constants", Lean.toJson totalConsts)
, ("throughput", Ix.Benchmark.Results.jsonRound 2 tput)
, ("peak-rss", Lean.toJson peakRss) ]

-- Persist the serialized IxonEnv (`Env::put` bytes) to disk so subsequent
-- runs (e.g. `ix check-ixon`) can skip the Lean → IxOn compile step. The
-- resulting file is the canonical streaming format produced by
-- `Ixon.Env::put` (see `src/ix/ixon/serialize.rs:1093-1297`); it round-trips
-- through `Ixon.Env::get`.
let writeStart ← IO.monoMsNow
IO.FS.writeBinFile outPath bytes
let writeMs := (← IO.monoMsNow) - writeStart
println! "Wrote {fmtBytes bytes.size} to {outPath} in {writeMs.formatMs}"
return 0


Expand Down
17 changes: 12 additions & 5 deletions Ix/CompileM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -1916,9 +1916,14 @@ def compileEnvParallel (env : Ix.Environment) (blocks : Ix.CondensedBlocks)

/-! ## Rust Compilation FFI -/

/-- FFI: Compile a Lean environment to serialized Ixon.Env bytes using Rust. -/
/-- FFI: Compile a Lean environment and write the serialized Ixon.Env
bytes straight to `outPath` from Rust (streamed; no env-sized
ByteArray crosses the FFI). Writes to `<outPath>.tmp` then renames,
so a crash cannot leave a truncated file. Returns the byte count
written. -/
@[extern "rs_compile_env"]
opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO ByteArray
opaque rsCompileEnvBytesFFI
: @& List (Lean.Name × Lean.ConstantInfo) → @& String → IO Nat

/-- FFI: 8-phase validation of the aux_gen compile pipeline (compile +
decompile + roundtrip + alpha-equivalence + nested-detect checks).
Expand All@@ -1932,10 +1937,12 @@ opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO By
opaque rsCompileValidateAuxFFI
: @& List (Lean.Name × Lean.ConstantInfo) → USize

/-- Compile a Lean environment to Ixon.Env bytes using the Rust compiler. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) : IO ByteArray := do
/-- Compile a Lean environment and write the serialized Ixon.Env bytes
to `outPath` using the Rust compiler. Returns the byte count. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) (outPath : String)
: IO Nat := do
let constList := leanEnv.constants.toList
rsCompileEnvBytesFFI constList
rsCompileEnvBytesFFI constList outPath

-- Re-export RawEnv types from Ixon for backwards compatibility
export Ixon (RawConst RawNamed RawBlob RawComm RawEnv)
Expand Down
6 changes: 5 additions & 1 deletion Tests/Main.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,10 +60,14 @@ def ignoredSuites : Std.HashMap String (List LSpec.TestSeq) := .ofList [
("parallel-canon-roundtrip", Tests.CanonM.parallelSuiteIO),
("graph-cross", Tests.Ix.GraphM.suiteIO),
("condense-cross", Tests.Ix.CondenseM.suiteIO),
-- Lean compilation & kernel tests currently broken, disabled
-- Lean-side compilation/decompilation currently broken, disabled
--("compile", Tests.Compile.compileSuiteIO),
--("decompile", Tests.Decompile.decompileSuiteIO),
("rust-serialize", Tests.RustSerialize.rustSerializeSuiteIO),
-- Rust decompile of synthesized `_sparseCasesOn` aux constants fails
-- ("missing Ref metadata": their aux_gen metadata arena misaligns
-- with the serialized expr, and pure-aux constants have no
-- `Named.original` sidecar to recover from), disabled
--("rust-decompile", Tests.RustDecompile.rustDecompileSuiteIO),
("commit-io", Tests.Commit.suiteIO),
("kernel-ixon-roundtrip", Tests.Ix.Kernel.Roundtrip.suite),
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ebf2fc1
Compile spill steps 0+1: accumulator instrumentation and demote mode
samuelburnham Jul 8, 2026
c879d2d
Compile spill step 2: mmap-backed accumulator bytes
samuelburnham Jul 8, 2026
6a564f9
Compile spill: RSS decomposition instrumentation + worker kenv clearing
samuelburnham Jul 9, 2026
e7c232b
Compile spill lever 2: demote named metadata to serialized bytes
samuelburnham Jul 9, 2026
adb9a02
Compile spill lever 4: stream the .ixe from Rust (IX_COMPILE_STREAM=1)
samuelburnham Jul 9, 2026
f5d97ae
Compile spill lever 1: lazy LeanEnv decode (IX_COMPILE_LEAN_ENV=lazy)
samuelburnham Jul 9, 2026
87a96f2
Compile: fuse the three whole-env setup sweeps into one scan
samuelburnham Jul 9, 2026
2d15020
Compile spill: move design-doc content into module docs
samuelburnham Jul 10, 2026
bde1789
Compile memory: consolidate to two knobs, always-on defaults
samuelburnham Jul 10, 2026
a29a34e
Compile FFI: rs_compile_env compiles straight to a file
samuelburnham Jul 10, 2026
d25fba3
Compile: decode-time counters for the lazy env
samuelburnham Jul 10, 2026
f973793
Compile: fix all-features clippy and test-ffi fallout
samuelburnham Jul 10, 2026
f216728
Compile: drop the measurement instrumentation
samuelburnham Jul 10, 2026
fc29c3f
Compile: size the lazy-env cache by sweep, 65536 -> 16384 entries
samuelburnham Jul 10, 2026
1379125
sp1 guest: read Named metadata through the accessor
samuelburnham Jul 10, 2026
b4446f5
Compile: parallelize put_file's named-section encode
samuelburnham Jul 11, 2026
eade07d
Compile: IX_COMPILE_EAGER=1 knob for RAM-rich machines
samuelburnham Jul 11, 2026
011107f
Compile: pin the hottest constants in the lazy env
samuelburnham Jul 11, 2026
3059268
bench-pr: IX_COMPILE_* knob passthrough to the measured compile
samuelburnham Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions .github/workflows/bench-pr.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,10 @@
# BENCH_SHARD=1 # restrict to the multi-shard target constants
# BENCH_PHASES=1 # add per-constant phase drill-downs to the comment
# RUST_LOG=info # passthrough env (allowlisted)
# IX_COMPILE_EAGER=1 # compile-knob passthrough (also IX_COMPILE_DEMOTE /
# # IX_COMPILE_WORKERS); reaches the measured `ix
# # compile` and keys its caches, so a knob run gets
# # its own row instead of the default run's
#
# Mode defaults per backend (the registry's defaultMode): `aiur` runs
# `prove` — the real-workload simulation, whose report also carries the
Expand DownExpand Up@@ -197,14 +201,22 @@ jobs:
ref: ${{ inputs.head-sha }}
# The job runs PR code; never leave the token in .git.
persist-credentials: false
# Re-running !benchmark on the same commit: the .ixe is already
# published — nothing to do.
# Allowlisted KEY=VALUE lines from the !benchmark comment. Applied
# here so IX_COMPILE_* knobs reach the measured compile, and staged
# to a file so the cache keys below can hash it — a knob run must
# not reuse (or overwrite) the default run's published .ixe/row.
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Re-running !benchmark on the same commit with the same config: the
# .ixe is already published — nothing to do.
- name: Check for published .ixe
id: pr-ixe
uses: actions/cache/restore@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
lookup-only: true
- name: Restore PR binaries
if: steps.pr-ixe.outputs.cache-hit != 'true'
Expand DownExpand Up@@ -242,15 +254,15 @@ jobs:
uses: actions/cache/save@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
# The measured row: the compile cell reuses it as its PR side (same
# runner class, same binaries, same command it would run itself).
- name: Publish compile row
if: steps.pr-ixe.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: compile.json
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}

benchmark:
# Explicit name: the default would append EVERY matrix value (backend,
Expand DownExpand Up@@ -294,7 +306,7 @@ jobs:
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' >> "$GITHUB_ENV"
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Restore the once-built PR binaries (see the build job) into the PR
# tree's own bin dir: `ix bench run` resolves the measured tools from
# <repo>/.lake/build/bin first, then PATH, so staging in-tree keeps the
Expand DownExpand Up@@ -334,15 +346,15 @@ jobs:
uses: actions/cache/restore@v5
with:
path: ${{ matrix.cell.env }}.ixe
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# Compile cells reuse the compile job's measured row as their PR side.
- name: Restore compile row
if: matrix.cell.backend == 'compile'
uses: actions/cache/restore@v5
with:
path: compile.json
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# zkVM cells additionally need the Rust toolchain + the backend's toolchain
# and system deps (the shared composite install actions).
Expand Down
14 changes: 10 additions & 4 deletions Ix/Cli/BenchReport.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -595,9 +595,13 @@ def parseError (msg : String) : IO UInt32 := do
BENCH_FULL=1 (full curated set, not just primary)
BENCH_SHARD=1 (only the multi-shard target constants)
BENCH_PHASES=1 / RUST_LOG=… / WITHOUT_VK_VERIFICATION=… /
RUSTFLAGS=… (passthrough; BENCH_PHASES=1 adds the
RUSTFLAGS=… / IX_COMPILE_EAGER=… / IX_COMPILE_DEMOTE=… /
IX_COMPILE_WORKERS=… (passthrough; BENCH_PHASES=1 adds the
per-constant phase drill-downs to the
comment)
comment; the IX_COMPILE_* knobs reach
the measured `ix compile` and key its
caches, so knob runs don't reuse a
default run's published row)

The KEY=VALUE config may sit on its own lines below the command (the
comment form) or inline on the command line, whitespace-separated
Expand DownExpand Up@@ -694,13 +698,15 @@ def runParseCmd (p : Cli.Parsed) : IO UInt32 := do
| "BENCH_FULL" => if val == "1" then full := "1"
| k =>
if ["BENCH_PHASES", "RUST_LOG", "WITHOUT_VK_VERIFICATION",
"RUSTFLAGS"].contains k then
"RUSTFLAGS", "IX_COMPILE_EAGER", "IX_COMPILE_DEMOTE",
"IX_COMPILE_WORKERS"].contains k then
passthrough := passthrough.push s!"{k}={val}"
else if strict then
return ← parseError s!"unknown config key `{k}` in the \
benchmark command (expected BENCH_ENVS / BENCH_FULL / \
BENCH_SHARD, or passthrough: BENCH_PHASES, RUST_LOG, \
WITHOUT_VK_VERIFICATION, RUSTFLAGS)"
WITHOUT_VK_VERIFICATION, RUSTFLAGS, IX_COMPILE_EAGER, \
IX_COMPILE_DEMOTE, IX_COMPILE_WORKERS)"
| [] => continue
if envs.isEmpty then envs := #["InitStd"]

Expand Down
24 changes: 10 additions & 14 deletions Ix/Cli/CompileCmd.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -134,11 +134,17 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
if benched then
TracingTexray.startSampler
TracingTexray.resetPeakTreeRss

-- Rust compiles and writes the `.ixe` directly (streamed — no
-- env-sized ByteArray crosses the FFI; `<out>.tmp` + atomic rename).
-- The file is the canonical `Ixon.Env::put` format and round-trips
-- through `Ixon.Env::get`, so later runs (e.g. `ix check-ixon`) can
-- skip the Lean → IxOn compile step.
let start ← IO.monoMsNow
let bytes ← Ix.CompileM.rsCompileEnvBytesFFI constList
let size ← Ix.CompileM.rsCompileEnvBytesFFI constList outPath
let elapsed := (← IO.monoMsNow) - start

println! "Compiled {fmtBytes bytes.size} env in {elapsed.formatMs}"
println! "Compiled and wrote {fmtBytes size} env to {outPath} in {elapsed.formatMs}"
IO.println s!"##benchmark## {elapsed} {size} {totalConsts}"
if let some flag := p.flag? "json" then
let key := (p.flag? "json-name").map (·.as! String)
|>.getD ((FilePath.mk pathStr).fileStem.getD "env")
Expand All@@ -148,20 +154,10 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
let peakRss ← TracingTexray.peakTreeRssBytes
Ix.Benchmark.Results.writeRow (flag.as! String) key "ok"
[ ("compile-time", Ix.Benchmark.Results.jsonRound 3 secs)
, ("file-size", Lean.toJson bytes.size)
, ("file-size", Lean.toJson size)
, ("constants", Lean.toJson totalConsts)
, ("throughput", Ix.Benchmark.Results.jsonRound 2 tput)
, ("peak-rss", Lean.toJson peakRss) ]

-- Persist the serialized IxonEnv (`Env::put` bytes) to disk so subsequent
-- runs (e.g. `ix check-ixon`) can skip the Lean → IxOn compile step. The
-- resulting file is the canonical streaming format produced by
-- `Ixon.Env::put` (see `src/ix/ixon/serialize.rs:1093-1297`); it round-trips
-- through `Ixon.Env::get`.
let writeStart ← IO.monoMsNow
IO.FS.writeBinFile outPath bytes
let writeMs := (← IO.monoMsNow) - writeStart
println! "Wrote {fmtBytes bytes.size} to {outPath} in {writeMs.formatMs}"
return 0


Expand Down
17 changes: 12 additions & 5 deletions Ix/CompileM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -1916,9 +1916,14 @@ def compileEnvParallel (env : Ix.Environment) (blocks : Ix.CondensedBlocks)

/-! ## Rust Compilation FFI -/

/-- FFI: Compile a Lean environment to serialized Ixon.Env bytes using Rust. -/
/-- FFI: Compile a Lean environment and write the serialized Ixon.Env
bytes straight to `outPath` from Rust (streamed; no env-sized
ByteArray crosses the FFI). Writes to `<outPath>.tmp` then renames,
so a crash cannot leave a truncated file. Returns the byte count
written. -/
@[extern "rs_compile_env"]
opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO ByteArray
opaque rsCompileEnvBytesFFI
: @& List (Lean.Name × Lean.ConstantInfo) → @& String → IO Nat

/-- FFI: 8-phase validation of the aux_gen compile pipeline (compile +
decompile + roundtrip + alpha-equivalence + nested-detect checks).
Expand All@@ -1932,10 +1937,12 @@ opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO By
opaque rsCompileValidateAuxFFI
: @& List (Lean.Name × Lean.ConstantInfo) → USize

/-- Compile a Lean environment to Ixon.Env bytes using the Rust compiler. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) : IO ByteArray := do
/-- Compile a Lean environment and write the serialized Ixon.Env bytes
to `outPath` using the Rust compiler. Returns the byte count. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) (outPath : String)
: IO Nat := do
let constList := leanEnv.constants.toList
rsCompileEnvBytesFFI constList
rsCompileEnvBytesFFI constList outPath

-- Re-export RawEnv types from Ixon for backwards compatibility
export Ixon (RawConst RawNamed RawBlob RawComm RawEnv)
Expand Down
6 changes: 5 additions & 1 deletion Tests/Main.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,10 +60,14 @@ def ignoredSuites : Std.HashMap String (List LSpec.TestSeq) := .ofList [
("parallel-canon-roundtrip", Tests.CanonM.parallelSuiteIO),
("graph-cross", Tests.Ix.GraphM.suiteIO),
("condense-cross", Tests.Ix.CondenseM.suiteIO),
-- Lean compilation & kernel tests currently broken, disabled
-- Lean-side compilation/decompilation currently broken, disabled
--("compile", Tests.Compile.compileSuiteIO),
--("decompile", Tests.Decompile.decompileSuiteIO),
("rust-serialize", Tests.RustSerialize.rustSerializeSuiteIO),
-- Rust decompile of synthesized `_sparseCasesOn` aux constants fails
-- ("missing Ref metadata": their aux_gen metadata arena misaligns
-- with the serialized expr, and pure-aux constants have no
-- `Named.original` sidecar to recover from), disabled
--("rust-decompile", Tests.RustDecompile.rustDecompileSuiteIO),
("commit-io", Tests.Commit.suiteIO),
("kernel-ixon-roundtrip", Tests.Ix.Kernel.Roundtrip.suite),
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ebf2fc1
Compile spill steps 0+1: accumulator instrumentation and demote mode
samuelburnham Jul 8, 2026
c879d2d
Compile spill step 2: mmap-backed accumulator bytes
samuelburnham Jul 8, 2026
6a564f9
Compile spill: RSS decomposition instrumentation + worker kenv clearing
samuelburnham Jul 9, 2026
e7c232b
Compile spill lever 2: demote named metadata to serialized bytes
samuelburnham Jul 9, 2026
adb9a02
Compile spill lever 4: stream the .ixe from Rust (IX_COMPILE_STREAM=1)
samuelburnham Jul 9, 2026
f5d97ae
Compile spill lever 1: lazy LeanEnv decode (IX_COMPILE_LEAN_ENV=lazy)
samuelburnham Jul 9, 2026
87a96f2
Compile: fuse the three whole-env setup sweeps into one scan
samuelburnham Jul 9, 2026
2d15020
Compile spill: move design-doc content into module docs
samuelburnham Jul 10, 2026
bde1789
Compile memory: consolidate to two knobs, always-on defaults
samuelburnham Jul 10, 2026
a29a34e
Compile FFI: rs_compile_env compiles straight to a file
samuelburnham Jul 10, 2026
d25fba3
Compile: decode-time counters for the lazy env
samuelburnham Jul 10, 2026
f973793
Compile: fix all-features clippy and test-ffi fallout
samuelburnham Jul 10, 2026
f216728
Compile: drop the measurement instrumentation
samuelburnham Jul 10, 2026
fc29c3f
Compile: size the lazy-env cache by sweep, 65536 -> 16384 entries
samuelburnham Jul 10, 2026
1379125
sp1 guest: read Named metadata through the accessor
samuelburnham Jul 10, 2026
b4446f5
Compile: parallelize put_file's named-section encode
samuelburnham Jul 11, 2026
eade07d
Compile: IX_COMPILE_EAGER=1 knob for RAM-rich machines
samuelburnham Jul 11, 2026
011107f
Compile: pin the hottest constants in the lazy env
samuelburnham Jul 11, 2026
3059268
bench-pr: IX_COMPILE_* knob passthrough to the measured compile
samuelburnham Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions .github/workflows/bench-pr.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,10 @@
# BENCH_SHARD=1 # restrict to the multi-shard target constants
# BENCH_PHASES=1 # add per-constant phase drill-downs to the comment
# RUST_LOG=info # passthrough env (allowlisted)
# IX_COMPILE_EAGER=1 # compile-knob passthrough (also IX_COMPILE_DEMOTE /
# # IX_COMPILE_WORKERS); reaches the measured `ix
# # compile` and keys its caches, so a knob run gets
# # its own row instead of the default run's
#
# Mode defaults per backend (the registry's defaultMode): `aiur` runs
# `prove` — the real-workload simulation, whose report also carries the
Expand DownExpand Up@@ -197,14 +201,22 @@ jobs:
ref: ${{ inputs.head-sha }}
# The job runs PR code; never leave the token in .git.
persist-credentials: false
# Re-running !benchmark on the same commit: the .ixe is already
# published — nothing to do.
# Allowlisted KEY=VALUE lines from the !benchmark comment. Applied
# here so IX_COMPILE_* knobs reach the measured compile, and staged
# to a file so the cache keys below can hash it — a knob run must
# not reuse (or overwrite) the default run's published .ixe/row.
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Re-running !benchmark on the same commit with the same config: the
# .ixe is already published — nothing to do.
- name: Check for published .ixe
id: pr-ixe
uses: actions/cache/restore@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
lookup-only: true
- name: Restore PR binaries
if: steps.pr-ixe.outputs.cache-hit != 'true'
Expand DownExpand Up@@ -242,15 +254,15 @@ jobs:
uses: actions/cache/save@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
# The measured row: the compile cell reuses it as its PR side (same
# runner class, same binaries, same command it would run itself).
- name: Publish compile row
if: steps.pr-ixe.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: compile.json
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}

benchmark:
# Explicit name: the default would append EVERY matrix value (backend,
Expand DownExpand Up@@ -294,7 +306,7 @@ jobs:
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' >> "$GITHUB_ENV"
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Restore the once-built PR binaries (see the build job) into the PR
# tree's own bin dir: `ix bench run` resolves the measured tools from
# <repo>/.lake/build/bin first, then PATH, so staging in-tree keeps the
Expand DownExpand Up@@ -334,15 +346,15 @@ jobs:
uses: actions/cache/restore@v5
with:
path: ${{ matrix.cell.env }}.ixe
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# Compile cells reuse the compile job's measured row as their PR side.
- name: Restore compile row
if: matrix.cell.backend == 'compile'
uses: actions/cache/restore@v5
with:
path: compile.json
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# zkVM cells additionally need the Rust toolchain + the backend's toolchain
# and system deps (the shared composite install actions).
Expand Down
14 changes: 10 additions & 4 deletions Ix/Cli/BenchReport.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -595,9 +595,13 @@ def parseError (msg : String) : IO UInt32 := do
BENCH_FULL=1 (full curated set, not just primary)
BENCH_SHARD=1 (only the multi-shard target constants)
BENCH_PHASES=1 / RUST_LOG=… / WITHOUT_VK_VERIFICATION=… /
RUSTFLAGS=… (passthrough; BENCH_PHASES=1 adds the
RUSTFLAGS=… / IX_COMPILE_EAGER=… / IX_COMPILE_DEMOTE=… /
IX_COMPILE_WORKERS=… (passthrough; BENCH_PHASES=1 adds the
per-constant phase drill-downs to the
comment)
comment; the IX_COMPILE_* knobs reach
the measured `ix compile` and key its
caches, so knob runs don't reuse a
default run's published row)

The KEY=VALUE config may sit on its own lines below the command (the
comment form) or inline on the command line, whitespace-separated
Expand DownExpand Up@@ -694,13 +698,15 @@ def runParseCmd (p : Cli.Parsed) : IO UInt32 := do
| "BENCH_FULL" => if val == "1" then full := "1"
| k =>
if ["BENCH_PHASES", "RUST_LOG", "WITHOUT_VK_VERIFICATION",
"RUSTFLAGS"].contains k then
"RUSTFLAGS", "IX_COMPILE_EAGER", "IX_COMPILE_DEMOTE",
"IX_COMPILE_WORKERS"].contains k then
passthrough := passthrough.push s!"{k}={val}"
else if strict then
return ← parseError s!"unknown config key `{k}` in the \
benchmark command (expected BENCH_ENVS / BENCH_FULL / \
BENCH_SHARD, or passthrough: BENCH_PHASES, RUST_LOG, \
WITHOUT_VK_VERIFICATION, RUSTFLAGS)"
WITHOUT_VK_VERIFICATION, RUSTFLAGS, IX_COMPILE_EAGER, \
IX_COMPILE_DEMOTE, IX_COMPILE_WORKERS)"
| [] => continue
if envs.isEmpty then envs := #["InitStd"]

Expand Down
24 changes: 10 additions & 14 deletions Ix/Cli/CompileCmd.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -134,11 +134,17 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
if benched then
TracingTexray.startSampler
TracingTexray.resetPeakTreeRss

-- Rust compiles and writes the `.ixe` directly (streamed — no
-- env-sized ByteArray crosses the FFI; `<out>.tmp` + atomic rename).
-- The file is the canonical `Ixon.Env::put` format and round-trips
-- through `Ixon.Env::get`, so later runs (e.g. `ix check-ixon`) can
-- skip the Lean → IxOn compile step.
let start ← IO.monoMsNow
let bytes ← Ix.CompileM.rsCompileEnvBytesFFI constList
let size ← Ix.CompileM.rsCompileEnvBytesFFI constList outPath
let elapsed := (← IO.monoMsNow) - start

println! "Compiled {fmtBytes bytes.size} env in {elapsed.formatMs}"
println! "Compiled and wrote {fmtBytes size} env to {outPath} in {elapsed.formatMs}"
IO.println s!"##benchmark## {elapsed} {size} {totalConsts}"
if let some flag := p.flag? "json" then
let key := (p.flag? "json-name").map (·.as! String)
|>.getD ((FilePath.mk pathStr).fileStem.getD "env")
Expand All@@ -148,20 +154,10 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
let peakRss ← TracingTexray.peakTreeRssBytes
Ix.Benchmark.Results.writeRow (flag.as! String) key "ok"
[ ("compile-time", Ix.Benchmark.Results.jsonRound 3 secs)
, ("file-size", Lean.toJson bytes.size)
, ("file-size", Lean.toJson size)
, ("constants", Lean.toJson totalConsts)
, ("throughput", Ix.Benchmark.Results.jsonRound 2 tput)
, ("peak-rss", Lean.toJson peakRss) ]

-- Persist the serialized IxonEnv (`Env::put` bytes) to disk so subsequent
-- runs (e.g. `ix check-ixon`) can skip the Lean → IxOn compile step. The
-- resulting file is the canonical streaming format produced by
-- `Ixon.Env::put` (see `src/ix/ixon/serialize.rs:1093-1297`); it round-trips
-- through `Ixon.Env::get`.
let writeStart ← IO.monoMsNow
IO.FS.writeBinFile outPath bytes
let writeMs := (← IO.monoMsNow) - writeStart
println! "Wrote {fmtBytes bytes.size} to {outPath} in {writeMs.formatMs}"
return 0


Expand Down
17 changes: 12 additions & 5 deletions Ix/CompileM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -1916,9 +1916,14 @@ def compileEnvParallel (env : Ix.Environment) (blocks : Ix.CondensedBlocks)

/-! ## Rust Compilation FFI -/

/-- FFI: Compile a Lean environment to serialized Ixon.Env bytes using Rust. -/
/-- FFI: Compile a Lean environment and write the serialized Ixon.Env
bytes straight to `outPath` from Rust (streamed; no env-sized
ByteArray crosses the FFI). Writes to `<outPath>.tmp` then renames,
so a crash cannot leave a truncated file. Returns the byte count
written. -/
@[extern "rs_compile_env"]
opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO ByteArray
opaque rsCompileEnvBytesFFI
: @& List (Lean.Name × Lean.ConstantInfo) → @& String → IO Nat

/-- FFI: 8-phase validation of the aux_gen compile pipeline (compile +
decompile + roundtrip + alpha-equivalence + nested-detect checks).
Expand All@@ -1932,10 +1937,12 @@ opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO By
opaque rsCompileValidateAuxFFI
: @& List (Lean.Name × Lean.ConstantInfo) → USize

/-- Compile a Lean environment to Ixon.Env bytes using the Rust compiler. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) : IO ByteArray := do
/-- Compile a Lean environment and write the serialized Ixon.Env bytes
to `outPath` using the Rust compiler. Returns the byte count. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) (outPath : String)
: IO Nat := do
let constList := leanEnv.constants.toList
rsCompileEnvBytesFFI constList
rsCompileEnvBytesFFI constList outPath

-- Re-export RawEnv types from Ixon for backwards compatibility
export Ixon (RawConst RawNamed RawBlob RawComm RawEnv)
Expand Down
6 changes: 5 additions & 1 deletion Tests/Main.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,10 +60,14 @@ def ignoredSuites : Std.HashMap String (List LSpec.TestSeq) := .ofList [
("parallel-canon-roundtrip", Tests.CanonM.parallelSuiteIO),
("graph-cross", Tests.Ix.GraphM.suiteIO),
("condense-cross", Tests.Ix.CondenseM.suiteIO),
-- Lean compilation & kernel tests currently broken, disabled
-- Lean-side compilation/decompilation currently broken, disabled
--("compile", Tests.Compile.compileSuiteIO),
--("decompile", Tests.Decompile.decompileSuiteIO),
("rust-serialize", Tests.RustSerialize.rustSerializeSuiteIO),
-- Rust decompile of synthesized `_sparseCasesOn` aux constants fails
-- ("missing Ref metadata": their aux_gen metadata arena misaligns
-- with the serialized expr, and pure-aux constants have no
-- `Named.original` sidecar to recover from), disabled
--("rust-decompile", Tests.RustDecompile.rustDecompileSuiteIO),
("commit-io", Tests.Commit.suiteIO),
("kernel-ixon-roundtrip", Tests.Ix.Kernel.Roundtrip.suite),
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ebf2fc1
Compile spill steps 0+1: accumulator instrumentation and demote mode
samuelburnham Jul 8, 2026
c879d2d
Compile spill step 2: mmap-backed accumulator bytes
samuelburnham Jul 8, 2026
6a564f9
Compile spill: RSS decomposition instrumentation + worker kenv clearing
samuelburnham Jul 9, 2026
e7c232b
Compile spill lever 2: demote named metadata to serialized bytes
samuelburnham Jul 9, 2026
adb9a02
Compile spill lever 4: stream the .ixe from Rust (IX_COMPILE_STREAM=1)
samuelburnham Jul 9, 2026
f5d97ae
Compile spill lever 1: lazy LeanEnv decode (IX_COMPILE_LEAN_ENV=lazy)
samuelburnham Jul 9, 2026
87a96f2
Compile: fuse the three whole-env setup sweeps into one scan
samuelburnham Jul 9, 2026
2d15020
Compile spill: move design-doc content into module docs
samuelburnham Jul 10, 2026
bde1789
Compile memory: consolidate to two knobs, always-on defaults
samuelburnham Jul 10, 2026
a29a34e
Compile FFI: rs_compile_env compiles straight to a file
samuelburnham Jul 10, 2026
d25fba3
Compile: decode-time counters for the lazy env
samuelburnham Jul 10, 2026
f973793
Compile: fix all-features clippy and test-ffi fallout
samuelburnham Jul 10, 2026
f216728
Compile: drop the measurement instrumentation
samuelburnham Jul 10, 2026
fc29c3f
Compile: size the lazy-env cache by sweep, 65536 -> 16384 entries
samuelburnham Jul 10, 2026
1379125
sp1 guest: read Named metadata through the accessor
samuelburnham Jul 10, 2026
b4446f5
Compile: parallelize put_file's named-section encode
samuelburnham Jul 11, 2026
eade07d
Compile: IX_COMPILE_EAGER=1 knob for RAM-rich machines
samuelburnham Jul 11, 2026
011107f
Compile: pin the hottest constants in the lazy env
samuelburnham Jul 11, 2026
3059268
bench-pr: IX_COMPILE_* knob passthrough to the measured compile
samuelburnham Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions .github/workflows/bench-pr.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,10 @@
# BENCH_SHARD=1 # restrict to the multi-shard target constants
# BENCH_PHASES=1 # add per-constant phase drill-downs to the comment
# RUST_LOG=info # passthrough env (allowlisted)
# IX_COMPILE_EAGER=1 # compile-knob passthrough (also IX_COMPILE_DEMOTE /
# # IX_COMPILE_WORKERS); reaches the measured `ix
# # compile` and keys its caches, so a knob run gets
# # its own row instead of the default run's
#
# Mode defaults per backend (the registry's defaultMode): `aiur` runs
# `prove` — the real-workload simulation, whose report also carries the
Expand DownExpand Up@@ -197,14 +201,22 @@ jobs:
ref: ${{ inputs.head-sha }}
# The job runs PR code; never leave the token in .git.
persist-credentials: false
# Re-running !benchmark on the same commit: the .ixe is already
# published — nothing to do.
# Allowlisted KEY=VALUE lines from the !benchmark comment. Applied
# here so IX_COMPILE_* knobs reach the measured compile, and staged
# to a file so the cache keys below can hash it — a knob run must
# not reuse (or overwrite) the default run's published .ixe/row.
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Re-running !benchmark on the same commit with the same config: the
# .ixe is already published — nothing to do.
- name: Check for published .ixe
id: pr-ixe
uses: actions/cache/restore@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
lookup-only: true
- name: Restore PR binaries
if: steps.pr-ixe.outputs.cache-hit != 'true'
Expand DownExpand Up@@ -242,15 +254,15 @@ jobs:
uses: actions/cache/save@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
# The measured row: the compile cell reuses it as its PR side (same
# runner class, same binaries, same command it would run itself).
- name: Publish compile row
if: steps.pr-ixe.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: compile.json
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}

benchmark:
# Explicit name: the default would append EVERY matrix value (backend,
Expand DownExpand Up@@ -294,7 +306,7 @@ jobs:
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' >> "$GITHUB_ENV"
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Restore the once-built PR binaries (see the build job) into the PR
# tree's own bin dir: `ix bench run` resolves the measured tools from
# <repo>/.lake/build/bin first, then PATH, so staging in-tree keeps the
Expand DownExpand Up@@ -334,15 +346,15 @@ jobs:
uses: actions/cache/restore@v5
with:
path: ${{ matrix.cell.env }}.ixe
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# Compile cells reuse the compile job's measured row as their PR side.
- name: Restore compile row
if: matrix.cell.backend == 'compile'
uses: actions/cache/restore@v5
with:
path: compile.json
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# zkVM cells additionally need the Rust toolchain + the backend's toolchain
# and system deps (the shared composite install actions).
Expand Down
14 changes: 10 additions & 4 deletions Ix/Cli/BenchReport.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -595,9 +595,13 @@ def parseError (msg : String) : IO UInt32 := do
BENCH_FULL=1 (full curated set, not just primary)
BENCH_SHARD=1 (only the multi-shard target constants)
BENCH_PHASES=1 / RUST_LOG=… / WITHOUT_VK_VERIFICATION=… /
RUSTFLAGS=… (passthrough; BENCH_PHASES=1 adds the
RUSTFLAGS=… / IX_COMPILE_EAGER=… / IX_COMPILE_DEMOTE=… /
IX_COMPILE_WORKERS=… (passthrough; BENCH_PHASES=1 adds the
per-constant phase drill-downs to the
comment)
comment; the IX_COMPILE_* knobs reach
the measured `ix compile` and key its
caches, so knob runs don't reuse a
default run's published row)

The KEY=VALUE config may sit on its own lines below the command (the
comment form) or inline on the command line, whitespace-separated
Expand DownExpand Up@@ -694,13 +698,15 @@ def runParseCmd (p : Cli.Parsed) : IO UInt32 := do
| "BENCH_FULL" => if val == "1" then full := "1"
| k =>
if ["BENCH_PHASES", "RUST_LOG", "WITHOUT_VK_VERIFICATION",
"RUSTFLAGS"].contains k then
"RUSTFLAGS", "IX_COMPILE_EAGER", "IX_COMPILE_DEMOTE",
"IX_COMPILE_WORKERS"].contains k then
passthrough := passthrough.push s!"{k}={val}"
else if strict then
return ← parseError s!"unknown config key `{k}` in the \
benchmark command (expected BENCH_ENVS / BENCH_FULL / \
BENCH_SHARD, or passthrough: BENCH_PHASES, RUST_LOG, \
WITHOUT_VK_VERIFICATION, RUSTFLAGS)"
WITHOUT_VK_VERIFICATION, RUSTFLAGS, IX_COMPILE_EAGER, \
IX_COMPILE_DEMOTE, IX_COMPILE_WORKERS)"
| [] => continue
if envs.isEmpty then envs := #["InitStd"]

Expand Down
24 changes: 10 additions & 14 deletions Ix/Cli/CompileCmd.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -134,11 +134,17 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
if benched then
TracingTexray.startSampler
TracingTexray.resetPeakTreeRss

-- Rust compiles and writes the `.ixe` directly (streamed — no
-- env-sized ByteArray crosses the FFI; `<out>.tmp` + atomic rename).
-- The file is the canonical `Ixon.Env::put` format and round-trips
-- through `Ixon.Env::get`, so later runs (e.g. `ix check-ixon`) can
-- skip the Lean → IxOn compile step.
let start ← IO.monoMsNow
let bytes ← Ix.CompileM.rsCompileEnvBytesFFI constList
let size ← Ix.CompileM.rsCompileEnvBytesFFI constList outPath
let elapsed := (← IO.monoMsNow) - start

println! "Compiled {fmtBytes bytes.size} env in {elapsed.formatMs}"
println! "Compiled and wrote {fmtBytes size} env to {outPath} in {elapsed.formatMs}"
IO.println s!"##benchmark## {elapsed} {size} {totalConsts}"
if let some flag := p.flag? "json" then
let key := (p.flag? "json-name").map (·.as! String)
|>.getD ((FilePath.mk pathStr).fileStem.getD "env")
Expand All@@ -148,20 +154,10 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
let peakRss ← TracingTexray.peakTreeRssBytes
Ix.Benchmark.Results.writeRow (flag.as! String) key "ok"
[ ("compile-time", Ix.Benchmark.Results.jsonRound 3 secs)
, ("file-size", Lean.toJson bytes.size)
, ("file-size", Lean.toJson size)
, ("constants", Lean.toJson totalConsts)
, ("throughput", Ix.Benchmark.Results.jsonRound 2 tput)
, ("peak-rss", Lean.toJson peakRss) ]

-- Persist the serialized IxonEnv (`Env::put` bytes) to disk so subsequent
-- runs (e.g. `ix check-ixon`) can skip the Lean → IxOn compile step. The
-- resulting file is the canonical streaming format produced by
-- `Ixon.Env::put` (see `src/ix/ixon/serialize.rs:1093-1297`); it round-trips
-- through `Ixon.Env::get`.
let writeStart ← IO.monoMsNow
IO.FS.writeBinFile outPath bytes
let writeMs := (← IO.monoMsNow) - writeStart
println! "Wrote {fmtBytes bytes.size} to {outPath} in {writeMs.formatMs}"
return 0


Expand Down
17 changes: 12 additions & 5 deletions Ix/CompileM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -1916,9 +1916,14 @@ def compileEnvParallel (env : Ix.Environment) (blocks : Ix.CondensedBlocks)

/-! ## Rust Compilation FFI -/

/-- FFI: Compile a Lean environment to serialized Ixon.Env bytes using Rust. -/
/-- FFI: Compile a Lean environment and write the serialized Ixon.Env
bytes straight to `outPath` from Rust (streamed; no env-sized
ByteArray crosses the FFI). Writes to `<outPath>.tmp` then renames,
so a crash cannot leave a truncated file. Returns the byte count
written. -/
@[extern "rs_compile_env"]
opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO ByteArray
opaque rsCompileEnvBytesFFI
: @& List (Lean.Name × Lean.ConstantInfo) → @& String → IO Nat

/-- FFI: 8-phase validation of the aux_gen compile pipeline (compile +
decompile + roundtrip + alpha-equivalence + nested-detect checks).
Expand All@@ -1932,10 +1937,12 @@ opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO By
opaque rsCompileValidateAuxFFI
: @& List (Lean.Name × Lean.ConstantInfo) → USize

/-- Compile a Lean environment to Ixon.Env bytes using the Rust compiler. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) : IO ByteArray := do
/-- Compile a Lean environment and write the serialized Ixon.Env bytes
to `outPath` using the Rust compiler. Returns the byte count. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) (outPath : String)
: IO Nat := do
let constList := leanEnv.constants.toList
rsCompileEnvBytesFFI constList
rsCompileEnvBytesFFI constList outPath

-- Re-export RawEnv types from Ixon for backwards compatibility
export Ixon (RawConst RawNamed RawBlob RawComm RawEnv)
Expand Down
6 changes: 5 additions & 1 deletion Tests/Main.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,10 +60,14 @@ def ignoredSuites : Std.HashMap String (List LSpec.TestSeq) := .ofList [
("parallel-canon-roundtrip", Tests.CanonM.parallelSuiteIO),
("graph-cross", Tests.Ix.GraphM.suiteIO),
("condense-cross", Tests.Ix.CondenseM.suiteIO),
-- Lean compilation & kernel tests currently broken, disabled
-- Lean-side compilation/decompilation currently broken, disabled
--("compile", Tests.Compile.compileSuiteIO),
--("decompile", Tests.Decompile.decompileSuiteIO),
("rust-serialize", Tests.RustSerialize.rustSerializeSuiteIO),
-- Rust decompile of synthesized `_sparseCasesOn` aux constants fails
-- ("missing Ref metadata": their aux_gen metadata arena misaligns
-- with the serialized expr, and pure-aux constants have no
-- `Named.original` sidecar to recover from), disabled
--("rust-decompile", Tests.RustDecompile.rustDecompileSuiteIO),
("commit-io", Tests.Commit.suiteIO),
("kernel-ixon-roundtrip", Tests.Ix.Kernel.Roundtrip.suite),
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ebf2fc1
Compile spill steps 0+1: accumulator instrumentation and demote mode
samuelburnham Jul 8, 2026
c879d2d
Compile spill step 2: mmap-backed accumulator bytes
samuelburnham Jul 8, 2026
6a564f9
Compile spill: RSS decomposition instrumentation + worker kenv clearing
samuelburnham Jul 9, 2026
e7c232b
Compile spill lever 2: demote named metadata to serialized bytes
samuelburnham Jul 9, 2026
adb9a02
Compile spill lever 4: stream the .ixe from Rust (IX_COMPILE_STREAM=1)
samuelburnham Jul 9, 2026
f5d97ae
Compile spill lever 1: lazy LeanEnv decode (IX_COMPILE_LEAN_ENV=lazy)
samuelburnham Jul 9, 2026
87a96f2
Compile: fuse the three whole-env setup sweeps into one scan
samuelburnham Jul 9, 2026
2d15020
Compile spill: move design-doc content into module docs
samuelburnham Jul 10, 2026
bde1789
Compile memory: consolidate to two knobs, always-on defaults
samuelburnham Jul 10, 2026
a29a34e
Compile FFI: rs_compile_env compiles straight to a file
samuelburnham Jul 10, 2026
d25fba3
Compile: decode-time counters for the lazy env
samuelburnham Jul 10, 2026
f973793
Compile: fix all-features clippy and test-ffi fallout
samuelburnham Jul 10, 2026
f216728
Compile: drop the measurement instrumentation
samuelburnham Jul 10, 2026
fc29c3f
Compile: size the lazy-env cache by sweep, 65536 -> 16384 entries
samuelburnham Jul 10, 2026
1379125
sp1 guest: read Named metadata through the accessor
samuelburnham Jul 10, 2026
b4446f5
Compile: parallelize put_file's named-section encode
samuelburnham Jul 11, 2026
eade07d
Compile: IX_COMPILE_EAGER=1 knob for RAM-rich machines
samuelburnham Jul 11, 2026
011107f
Compile: pin the hottest constants in the lazy env
samuelburnham Jul 11, 2026
3059268
bench-pr: IX_COMPILE_* knob passthrough to the measured compile
samuelburnham Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions .github/workflows/bench-pr.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,10 @@
# BENCH_SHARD=1 # restrict to the multi-shard target constants
# BENCH_PHASES=1 # add per-constant phase drill-downs to the comment
# RUST_LOG=info # passthrough env (allowlisted)
# IX_COMPILE_EAGER=1 # compile-knob passthrough (also IX_COMPILE_DEMOTE /
# # IX_COMPILE_WORKERS); reaches the measured `ix
# # compile` and keys its caches, so a knob run gets
# # its own row instead of the default run's
#
# Mode defaults per backend (the registry's defaultMode): `aiur` runs
# `prove` — the real-workload simulation, whose report also carries the
Expand DownExpand Up@@ -197,14 +201,22 @@ jobs:
ref: ${{ inputs.head-sha }}
# The job runs PR code; never leave the token in .git.
persist-credentials: false
# Re-running !benchmark on the same commit: the .ixe is already
# published — nothing to do.
# Allowlisted KEY=VALUE lines from the !benchmark comment. Applied
# here so IX_COMPILE_* knobs reach the measured compile, and staged
# to a file so the cache keys below can hash it — a knob run must
# not reuse (or overwrite) the default run's published .ixe/row.
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Re-running !benchmark on the same commit with the same config: the
# .ixe is already published — nothing to do.
- name: Check for published .ixe
id: pr-ixe
uses: actions/cache/restore@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
lookup-only: true
- name: Restore PR binaries
if: steps.pr-ixe.outputs.cache-hit != 'true'
Expand DownExpand Up@@ -242,15 +254,15 @@ jobs:
uses: actions/cache/save@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
# The measured row: the compile cell reuses it as its PR side (same
# runner class, same binaries, same command it would run itself).
- name: Publish compile row
if: steps.pr-ixe.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: compile.json
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}

benchmark:
# Explicit name: the default would append EVERY matrix value (backend,
Expand DownExpand Up@@ -294,7 +306,7 @@ jobs:
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' >> "$GITHUB_ENV"
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Restore the once-built PR binaries (see the build job) into the PR
# tree's own bin dir: `ix bench run` resolves the measured tools from
# <repo>/.lake/build/bin first, then PATH, so staging in-tree keeps the
Expand DownExpand Up@@ -334,15 +346,15 @@ jobs:
uses: actions/cache/restore@v5
with:
path: ${{ matrix.cell.env }}.ixe
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# Compile cells reuse the compile job's measured row as their PR side.
- name: Restore compile row
if: matrix.cell.backend == 'compile'
uses: actions/cache/restore@v5
with:
path: compile.json
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# zkVM cells additionally need the Rust toolchain + the backend's toolchain
# and system deps (the shared composite install actions).
Expand Down
14 changes: 10 additions & 4 deletions Ix/Cli/BenchReport.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -595,9 +595,13 @@ def parseError (msg : String) : IO UInt32 := do
BENCH_FULL=1 (full curated set, not just primary)
BENCH_SHARD=1 (only the multi-shard target constants)
BENCH_PHASES=1 / RUST_LOG=… / WITHOUT_VK_VERIFICATION=… /
RUSTFLAGS=… (passthrough; BENCH_PHASES=1 adds the
RUSTFLAGS=… / IX_COMPILE_EAGER=… / IX_COMPILE_DEMOTE=… /
IX_COMPILE_WORKERS=… (passthrough; BENCH_PHASES=1 adds the
per-constant phase drill-downs to the
comment)
comment; the IX_COMPILE_* knobs reach
the measured `ix compile` and key its
caches, so knob runs don't reuse a
default run's published row)

The KEY=VALUE config may sit on its own lines below the command (the
comment form) or inline on the command line, whitespace-separated
Expand DownExpand Up@@ -694,13 +698,15 @@ def runParseCmd (p : Cli.Parsed) : IO UInt32 := do
| "BENCH_FULL" => if val == "1" then full := "1"
| k =>
if ["BENCH_PHASES", "RUST_LOG", "WITHOUT_VK_VERIFICATION",
"RUSTFLAGS"].contains k then
"RUSTFLAGS", "IX_COMPILE_EAGER", "IX_COMPILE_DEMOTE",
"IX_COMPILE_WORKERS"].contains k then
passthrough := passthrough.push s!"{k}={val}"
else if strict then
return ← parseError s!"unknown config key `{k}` in the \
benchmark command (expected BENCH_ENVS / BENCH_FULL / \
BENCH_SHARD, or passthrough: BENCH_PHASES, RUST_LOG, \
WITHOUT_VK_VERIFICATION, RUSTFLAGS)"
WITHOUT_VK_VERIFICATION, RUSTFLAGS, IX_COMPILE_EAGER, \
IX_COMPILE_DEMOTE, IX_COMPILE_WORKERS)"
| [] => continue
if envs.isEmpty then envs := #["InitStd"]

Expand Down
24 changes: 10 additions & 14 deletions Ix/Cli/CompileCmd.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -134,11 +134,17 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
if benched then
TracingTexray.startSampler
TracingTexray.resetPeakTreeRss

-- Rust compiles and writes the `.ixe` directly (streamed — no
-- env-sized ByteArray crosses the FFI; `<out>.tmp` + atomic rename).
-- The file is the canonical `Ixon.Env::put` format and round-trips
-- through `Ixon.Env::get`, so later runs (e.g. `ix check-ixon`) can
-- skip the Lean → IxOn compile step.
let start ← IO.monoMsNow
let bytes ← Ix.CompileM.rsCompileEnvBytesFFI constList
let size ← Ix.CompileM.rsCompileEnvBytesFFI constList outPath
let elapsed := (← IO.monoMsNow) - start

println! "Compiled {fmtBytes bytes.size} env in {elapsed.formatMs}"
println! "Compiled and wrote {fmtBytes size} env to {outPath} in {elapsed.formatMs}"
IO.println s!"##benchmark## {elapsed} {size} {totalConsts}"
if let some flag := p.flag? "json" then
let key := (p.flag? "json-name").map (·.as! String)
|>.getD ((FilePath.mk pathStr).fileStem.getD "env")
Expand All@@ -148,20 +154,10 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
let peakRss ← TracingTexray.peakTreeRssBytes
Ix.Benchmark.Results.writeRow (flag.as! String) key "ok"
[ ("compile-time", Ix.Benchmark.Results.jsonRound 3 secs)
, ("file-size", Lean.toJson bytes.size)
, ("file-size", Lean.toJson size)
, ("constants", Lean.toJson totalConsts)
, ("throughput", Ix.Benchmark.Results.jsonRound 2 tput)
, ("peak-rss", Lean.toJson peakRss) ]

-- Persist the serialized IxonEnv (`Env::put` bytes) to disk so subsequent
-- runs (e.g. `ix check-ixon`) can skip the Lean → IxOn compile step. The
-- resulting file is the canonical streaming format produced by
-- `Ixon.Env::put` (see `src/ix/ixon/serialize.rs:1093-1297`); it round-trips
-- through `Ixon.Env::get`.
let writeStart ← IO.monoMsNow
IO.FS.writeBinFile outPath bytes
let writeMs := (← IO.monoMsNow) - writeStart
println! "Wrote {fmtBytes bytes.size} to {outPath} in {writeMs.formatMs}"
return 0


Expand Down
17 changes: 12 additions & 5 deletions Ix/CompileM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -1916,9 +1916,14 @@ def compileEnvParallel (env : Ix.Environment) (blocks : Ix.CondensedBlocks)

/-! ## Rust Compilation FFI -/

/-- FFI: Compile a Lean environment to serialized Ixon.Env bytes using Rust. -/
/-- FFI: Compile a Lean environment and write the serialized Ixon.Env
bytes straight to `outPath` from Rust (streamed; no env-sized
ByteArray crosses the FFI). Writes to `<outPath>.tmp` then renames,
so a crash cannot leave a truncated file. Returns the byte count
written. -/
@[extern "rs_compile_env"]
opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO ByteArray
opaque rsCompileEnvBytesFFI
: @& List (Lean.Name × Lean.ConstantInfo) → @& String → IO Nat

/-- FFI: 8-phase validation of the aux_gen compile pipeline (compile +
decompile + roundtrip + alpha-equivalence + nested-detect checks).
Expand All@@ -1932,10 +1937,12 @@ opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO By
opaque rsCompileValidateAuxFFI
: @& List (Lean.Name × Lean.ConstantInfo) → USize

/-- Compile a Lean environment to Ixon.Env bytes using the Rust compiler. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) : IO ByteArray := do
/-- Compile a Lean environment and write the serialized Ixon.Env bytes
to `outPath` using the Rust compiler. Returns the byte count. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) (outPath : String)
: IO Nat := do
let constList := leanEnv.constants.toList
rsCompileEnvBytesFFI constList
rsCompileEnvBytesFFI constList outPath

-- Re-export RawEnv types from Ixon for backwards compatibility
export Ixon (RawConst RawNamed RawBlob RawComm RawEnv)
Expand Down
6 changes: 5 additions & 1 deletion Tests/Main.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,10 +60,14 @@ def ignoredSuites : Std.HashMap String (List LSpec.TestSeq) := .ofList [
("parallel-canon-roundtrip", Tests.CanonM.parallelSuiteIO),
("graph-cross", Tests.Ix.GraphM.suiteIO),
("condense-cross", Tests.Ix.CondenseM.suiteIO),
-- Lean compilation & kernel tests currently broken, disabled
-- Lean-side compilation/decompilation currently broken, disabled
--("compile", Tests.Compile.compileSuiteIO),
--("decompile", Tests.Decompile.decompileSuiteIO),
("rust-serialize", Tests.RustSerialize.rustSerializeSuiteIO),
-- Rust decompile of synthesized `_sparseCasesOn` aux constants fails
-- ("missing Ref metadata": their aux_gen metadata arena misaligns
-- with the serialized expr, and pure-aux constants have no
-- `Named.original` sidecar to recover from), disabled
--("rust-decompile", Tests.RustDecompile.rustDecompileSuiteIO),
("commit-io", Tests.Commit.suiteIO),
("kernel-ixon-roundtrip", Tests.Ix.Kernel.Roundtrip.suite),
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ebf2fc1
Compile spill steps 0+1: accumulator instrumentation and demote mode
samuelburnham Jul 8, 2026
c879d2d
Compile spill step 2: mmap-backed accumulator bytes
samuelburnham Jul 8, 2026
6a564f9
Compile spill: RSS decomposition instrumentation + worker kenv clearing
samuelburnham Jul 9, 2026
e7c232b
Compile spill lever 2: demote named metadata to serialized bytes
samuelburnham Jul 9, 2026
adb9a02
Compile spill lever 4: stream the .ixe from Rust (IX_COMPILE_STREAM=1)
samuelburnham Jul 9, 2026
f5d97ae
Compile spill lever 1: lazy LeanEnv decode (IX_COMPILE_LEAN_ENV=lazy)
samuelburnham Jul 9, 2026
87a96f2
Compile: fuse the three whole-env setup sweeps into one scan
samuelburnham Jul 9, 2026
2d15020
Compile spill: move design-doc content into module docs
samuelburnham Jul 10, 2026
bde1789
Compile memory: consolidate to two knobs, always-on defaults
samuelburnham Jul 10, 2026
a29a34e
Compile FFI: rs_compile_env compiles straight to a file
samuelburnham Jul 10, 2026
d25fba3
Compile: decode-time counters for the lazy env
samuelburnham Jul 10, 2026
f973793
Compile: fix all-features clippy and test-ffi fallout
samuelburnham Jul 10, 2026
f216728
Compile: drop the measurement instrumentation
samuelburnham Jul 10, 2026
fc29c3f
Compile: size the lazy-env cache by sweep, 65536 -> 16384 entries
samuelburnham Jul 10, 2026
1379125
sp1 guest: read Named metadata through the accessor
samuelburnham Jul 10, 2026
b4446f5
Compile: parallelize put_file's named-section encode
samuelburnham Jul 11, 2026
eade07d
Compile: IX_COMPILE_EAGER=1 knob for RAM-rich machines
samuelburnham Jul 11, 2026
011107f
Compile: pin the hottest constants in the lazy env
samuelburnham Jul 11, 2026
3059268
bench-pr: IX_COMPILE_* knob passthrough to the measured compile
samuelburnham Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions .github/workflows/bench-pr.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,10 @@
# BENCH_SHARD=1 # restrict to the multi-shard target constants
# BENCH_PHASES=1 # add per-constant phase drill-downs to the comment
# RUST_LOG=info # passthrough env (allowlisted)
# IX_COMPILE_EAGER=1 # compile-knob passthrough (also IX_COMPILE_DEMOTE /
# # IX_COMPILE_WORKERS); reaches the measured `ix
# # compile` and keys its caches, so a knob run gets
# # its own row instead of the default run's
#
# Mode defaults per backend (the registry's defaultMode): `aiur` runs
# `prove` — the real-workload simulation, whose report also carries the
Expand DownExpand Up@@ -197,14 +201,22 @@ jobs:
ref: ${{ inputs.head-sha }}
# The job runs PR code; never leave the token in .git.
persist-credentials: false
# Re-running !benchmark on the same commit: the .ixe is already
# published — nothing to do.
# Allowlisted KEY=VALUE lines from the !benchmark comment. Applied
# here so IX_COMPILE_* knobs reach the measured compile, and staged
# to a file so the cache keys below can hash it — a knob run must
# not reuse (or overwrite) the default run's published .ixe/row.
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Re-running !benchmark on the same commit with the same config: the
# .ixe is already published — nothing to do.
- name: Check for published .ixe
id: pr-ixe
uses: actions/cache/restore@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
lookup-only: true
- name: Restore PR binaries
if: steps.pr-ixe.outputs.cache-hit != 'true'
Expand DownExpand Up@@ -242,15 +254,15 @@ jobs:
uses: actions/cache/save@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
# The measured row: the compile cell reuses it as its PR side (same
# runner class, same binaries, same command it would run itself).
- name: Publish compile row
if: steps.pr-ixe.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: compile.json
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}

benchmark:
# Explicit name: the default would append EVERY matrix value (backend,
Expand DownExpand Up@@ -294,7 +306,7 @@ jobs:
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' >> "$GITHUB_ENV"
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Restore the once-built PR binaries (see the build job) into the PR
# tree's own bin dir: `ix bench run` resolves the measured tools from
# <repo>/.lake/build/bin first, then PATH, so staging in-tree keeps the
Expand DownExpand Up@@ -334,15 +346,15 @@ jobs:
uses: actions/cache/restore@v5
with:
path: ${{ matrix.cell.env }}.ixe
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# Compile cells reuse the compile job's measured row as their PR side.
- name: Restore compile row
if: matrix.cell.backend == 'compile'
uses: actions/cache/restore@v5
with:
path: compile.json
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# zkVM cells additionally need the Rust toolchain + the backend's toolchain
# and system deps (the shared composite install actions).
Expand Down
14 changes: 10 additions & 4 deletions Ix/Cli/BenchReport.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -595,9 +595,13 @@ def parseError (msg : String) : IO UInt32 := do
BENCH_FULL=1 (full curated set, not just primary)
BENCH_SHARD=1 (only the multi-shard target constants)
BENCH_PHASES=1 / RUST_LOG=… / WITHOUT_VK_VERIFICATION=… /
RUSTFLAGS=… (passthrough; BENCH_PHASES=1 adds the
RUSTFLAGS=… / IX_COMPILE_EAGER=… / IX_COMPILE_DEMOTE=… /
IX_COMPILE_WORKERS=… (passthrough; BENCH_PHASES=1 adds the
per-constant phase drill-downs to the
comment)
comment; the IX_COMPILE_* knobs reach
the measured `ix compile` and key its
caches, so knob runs don't reuse a
default run's published row)

The KEY=VALUE config may sit on its own lines below the command (the
comment form) or inline on the command line, whitespace-separated
Expand DownExpand Up@@ -694,13 +698,15 @@ def runParseCmd (p : Cli.Parsed) : IO UInt32 := do
| "BENCH_FULL" => if val == "1" then full := "1"
| k =>
if ["BENCH_PHASES", "RUST_LOG", "WITHOUT_VK_VERIFICATION",
"RUSTFLAGS"].contains k then
"RUSTFLAGS", "IX_COMPILE_EAGER", "IX_COMPILE_DEMOTE",
"IX_COMPILE_WORKERS"].contains k then
passthrough := passthrough.push s!"{k}={val}"
else if strict then
return ← parseError s!"unknown config key `{k}` in the \
benchmark command (expected BENCH_ENVS / BENCH_FULL / \
BENCH_SHARD, or passthrough: BENCH_PHASES, RUST_LOG, \
WITHOUT_VK_VERIFICATION, RUSTFLAGS)"
WITHOUT_VK_VERIFICATION, RUSTFLAGS, IX_COMPILE_EAGER, \
IX_COMPILE_DEMOTE, IX_COMPILE_WORKERS)"
| [] => continue
if envs.isEmpty then envs := #["InitStd"]

Expand Down
24 changes: 10 additions & 14 deletions Ix/Cli/CompileCmd.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -134,11 +134,17 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
if benched then
TracingTexray.startSampler
TracingTexray.resetPeakTreeRss

-- Rust compiles and writes the `.ixe` directly (streamed — no
-- env-sized ByteArray crosses the FFI; `<out>.tmp` + atomic rename).
-- The file is the canonical `Ixon.Env::put` format and round-trips
-- through `Ixon.Env::get`, so later runs (e.g. `ix check-ixon`) can
-- skip the Lean → IxOn compile step.
let start ← IO.monoMsNow
let bytes ← Ix.CompileM.rsCompileEnvBytesFFI constList
let size ← Ix.CompileM.rsCompileEnvBytesFFI constList outPath
let elapsed := (← IO.monoMsNow) - start

println! "Compiled {fmtBytes bytes.size} env in {elapsed.formatMs}"
println! "Compiled and wrote {fmtBytes size} env to {outPath} in {elapsed.formatMs}"
IO.println s!"##benchmark## {elapsed} {size} {totalConsts}"
if let some flag := p.flag? "json" then
let key := (p.flag? "json-name").map (·.as! String)
|>.getD ((FilePath.mk pathStr).fileStem.getD "env")
Expand All@@ -148,20 +154,10 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
let peakRss ← TracingTexray.peakTreeRssBytes
Ix.Benchmark.Results.writeRow (flag.as! String) key "ok"
[ ("compile-time", Ix.Benchmark.Results.jsonRound 3 secs)
, ("file-size", Lean.toJson bytes.size)
, ("file-size", Lean.toJson size)
, ("constants", Lean.toJson totalConsts)
, ("throughput", Ix.Benchmark.Results.jsonRound 2 tput)
, ("peak-rss", Lean.toJson peakRss) ]

-- Persist the serialized IxonEnv (`Env::put` bytes) to disk so subsequent
-- runs (e.g. `ix check-ixon`) can skip the Lean → IxOn compile step. The
-- resulting file is the canonical streaming format produced by
-- `Ixon.Env::put` (see `src/ix/ixon/serialize.rs:1093-1297`); it round-trips
-- through `Ixon.Env::get`.
let writeStart ← IO.monoMsNow
IO.FS.writeBinFile outPath bytes
let writeMs := (← IO.monoMsNow) - writeStart
println! "Wrote {fmtBytes bytes.size} to {outPath} in {writeMs.formatMs}"
return 0


Expand Down
17 changes: 12 additions & 5 deletions Ix/CompileM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -1916,9 +1916,14 @@ def compileEnvParallel (env : Ix.Environment) (blocks : Ix.CondensedBlocks)

/-! ## Rust Compilation FFI -/

/-- FFI: Compile a Lean environment to serialized Ixon.Env bytes using Rust. -/
/-- FFI: Compile a Lean environment and write the serialized Ixon.Env
bytes straight to `outPath` from Rust (streamed; no env-sized
ByteArray crosses the FFI). Writes to `<outPath>.tmp` then renames,
so a crash cannot leave a truncated file. Returns the byte count
written. -/
@[extern "rs_compile_env"]
opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO ByteArray
opaque rsCompileEnvBytesFFI
: @& List (Lean.Name × Lean.ConstantInfo) → @& String → IO Nat

/-- FFI: 8-phase validation of the aux_gen compile pipeline (compile +
decompile + roundtrip + alpha-equivalence + nested-detect checks).
Expand All@@ -1932,10 +1937,12 @@ opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO By
opaque rsCompileValidateAuxFFI
: @& List (Lean.Name × Lean.ConstantInfo) → USize

/-- Compile a Lean environment to Ixon.Env bytes using the Rust compiler. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) : IO ByteArray := do
/-- Compile a Lean environment and write the serialized Ixon.Env bytes
to `outPath` using the Rust compiler. Returns the byte count. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) (outPath : String)
: IO Nat := do
let constList := leanEnv.constants.toList
rsCompileEnvBytesFFI constList
rsCompileEnvBytesFFI constList outPath

-- Re-export RawEnv types from Ixon for backwards compatibility
export Ixon (RawConst RawNamed RawBlob RawComm RawEnv)
Expand Down
6 changes: 5 additions & 1 deletion Tests/Main.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,10 +60,14 @@ def ignoredSuites : Std.HashMap String (List LSpec.TestSeq) := .ofList [
("parallel-canon-roundtrip", Tests.CanonM.parallelSuiteIO),
("graph-cross", Tests.Ix.GraphM.suiteIO),
("condense-cross", Tests.Ix.CondenseM.suiteIO),
-- Lean compilation & kernel tests currently broken, disabled
-- Lean-side compilation/decompilation currently broken, disabled
--("compile", Tests.Compile.compileSuiteIO),
--("decompile", Tests.Decompile.decompileSuiteIO),
("rust-serialize", Tests.RustSerialize.rustSerializeSuiteIO),
-- Rust decompile of synthesized `_sparseCasesOn` aux constants fails
-- ("missing Ref metadata": their aux_gen metadata arena misaligns
-- with the serialized expr, and pure-aux constants have no
-- `Named.original` sidecar to recover from), disabled
--("rust-decompile", Tests.RustDecompile.rustDecompileSuiteIO),
("commit-io", Tests.Commit.suiteIO),
("kernel-ixon-roundtrip", Tests.Ix.Kernel.Roundtrip.suite),
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ebf2fc1
Compile spill steps 0+1: accumulator instrumentation and demote mode
samuelburnham Jul 8, 2026
c879d2d
Compile spill step 2: mmap-backed accumulator bytes
samuelburnham Jul 8, 2026
6a564f9
Compile spill: RSS decomposition instrumentation + worker kenv clearing
samuelburnham Jul 9, 2026
e7c232b
Compile spill lever 2: demote named metadata to serialized bytes
samuelburnham Jul 9, 2026
adb9a02
Compile spill lever 4: stream the .ixe from Rust (IX_COMPILE_STREAM=1)
samuelburnham Jul 9, 2026
f5d97ae
Compile spill lever 1: lazy LeanEnv decode (IX_COMPILE_LEAN_ENV=lazy)
samuelburnham Jul 9, 2026
87a96f2
Compile: fuse the three whole-env setup sweeps into one scan
samuelburnham Jul 9, 2026
2d15020
Compile spill: move design-doc content into module docs
samuelburnham Jul 10, 2026
bde1789
Compile memory: consolidate to two knobs, always-on defaults
samuelburnham Jul 10, 2026
a29a34e
Compile FFI: rs_compile_env compiles straight to a file
samuelburnham Jul 10, 2026
d25fba3
Compile: decode-time counters for the lazy env
samuelburnham Jul 10, 2026
f973793
Compile: fix all-features clippy and test-ffi fallout
samuelburnham Jul 10, 2026
f216728
Compile: drop the measurement instrumentation
samuelburnham Jul 10, 2026
fc29c3f
Compile: size the lazy-env cache by sweep, 65536 -> 16384 entries
samuelburnham Jul 10, 2026
1379125
sp1 guest: read Named metadata through the accessor
samuelburnham Jul 10, 2026
b4446f5
Compile: parallelize put_file's named-section encode
samuelburnham Jul 11, 2026
eade07d
Compile: IX_COMPILE_EAGER=1 knob for RAM-rich machines
samuelburnham Jul 11, 2026
011107f
Compile: pin the hottest constants in the lazy env
samuelburnham Jul 11, 2026
3059268
bench-pr: IX_COMPILE_* knob passthrough to the measured compile
samuelburnham Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions .github/workflows/bench-pr.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,10 @@
# BENCH_SHARD=1 # restrict to the multi-shard target constants
# BENCH_PHASES=1 # add per-constant phase drill-downs to the comment
# RUST_LOG=info # passthrough env (allowlisted)
# IX_COMPILE_EAGER=1 # compile-knob passthrough (also IX_COMPILE_DEMOTE /
# # IX_COMPILE_WORKERS); reaches the measured `ix
# # compile` and keys its caches, so a knob run gets
# # its own row instead of the default run's
#
# Mode defaults per backend (the registry's defaultMode): `aiur` runs
# `prove` — the real-workload simulation, whose report also carries the
Expand DownExpand Up@@ -197,14 +201,22 @@ jobs:
ref: ${{ inputs.head-sha }}
# The job runs PR code; never leave the token in .git.
persist-credentials: false
# Re-running !benchmark on the same commit: the .ixe is already
# published — nothing to do.
# Allowlisted KEY=VALUE lines from the !benchmark comment. Applied
# here so IX_COMPILE_* knobs reach the measured compile, and staged
# to a file so the cache keys below can hash it — a knob run must
# not reuse (or overwrite) the default run's published .ixe/row.
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Re-running !benchmark on the same commit with the same config: the
# .ixe is already published — nothing to do.
- name: Check for published .ixe
id: pr-ixe
uses: actions/cache/restore@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
lookup-only: true
- name: Restore PR binaries
if: steps.pr-ixe.outputs.cache-hit != 'true'
Expand DownExpand Up@@ -242,15 +254,15 @@ jobs:
uses: actions/cache/save@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
# The measured row: the compile cell reuses it as its PR side (same
# runner class, same binaries, same command it would run itself).
- name: Publish compile row
if: steps.pr-ixe.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: compile.json
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}

benchmark:
# Explicit name: the default would append EVERY matrix value (backend,
Expand DownExpand Up@@ -294,7 +306,7 @@ jobs:
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' >> "$GITHUB_ENV"
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Restore the once-built PR binaries (see the build job) into the PR
# tree's own bin dir: `ix bench run` resolves the measured tools from
# <repo>/.lake/build/bin first, then PATH, so staging in-tree keeps the
Expand DownExpand Up@@ -334,15 +346,15 @@ jobs:
uses: actions/cache/restore@v5
with:
path: ${{ matrix.cell.env }}.ixe
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# Compile cells reuse the compile job's measured row as their PR side.
- name: Restore compile row
if: matrix.cell.backend == 'compile'
uses: actions/cache/restore@v5
with:
path: compile.json
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# zkVM cells additionally need the Rust toolchain + the backend's toolchain
# and system deps (the shared composite install actions).
Expand Down
14 changes: 10 additions & 4 deletions Ix/Cli/BenchReport.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -595,9 +595,13 @@ def parseError (msg : String) : IO UInt32 := do
BENCH_FULL=1 (full curated set, not just primary)
BENCH_SHARD=1 (only the multi-shard target constants)
BENCH_PHASES=1 / RUST_LOG=… / WITHOUT_VK_VERIFICATION=… /
RUSTFLAGS=… (passthrough; BENCH_PHASES=1 adds the
RUSTFLAGS=… / IX_COMPILE_EAGER=… / IX_COMPILE_DEMOTE=… /
IX_COMPILE_WORKERS=… (passthrough; BENCH_PHASES=1 adds the
per-constant phase drill-downs to the
comment)
comment; the IX_COMPILE_* knobs reach
the measured `ix compile` and key its
caches, so knob runs don't reuse a
default run's published row)

The KEY=VALUE config may sit on its own lines below the command (the
comment form) or inline on the command line, whitespace-separated
Expand DownExpand Up@@ -694,13 +698,15 @@ def runParseCmd (p : Cli.Parsed) : IO UInt32 := do
| "BENCH_FULL" => if val == "1" then full := "1"
| k =>
if ["BENCH_PHASES", "RUST_LOG", "WITHOUT_VK_VERIFICATION",
"RUSTFLAGS"].contains k then
"RUSTFLAGS", "IX_COMPILE_EAGER", "IX_COMPILE_DEMOTE",
"IX_COMPILE_WORKERS"].contains k then
passthrough := passthrough.push s!"{k}={val}"
else if strict then
return ← parseError s!"unknown config key `{k}` in the \
benchmark command (expected BENCH_ENVS / BENCH_FULL / \
BENCH_SHARD, or passthrough: BENCH_PHASES, RUST_LOG, \
WITHOUT_VK_VERIFICATION, RUSTFLAGS)"
WITHOUT_VK_VERIFICATION, RUSTFLAGS, IX_COMPILE_EAGER, \
IX_COMPILE_DEMOTE, IX_COMPILE_WORKERS)"
| [] => continue
if envs.isEmpty then envs := #["InitStd"]

Expand Down
24 changes: 10 additions & 14 deletions Ix/Cli/CompileCmd.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -134,11 +134,17 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
if benched then
TracingTexray.startSampler
TracingTexray.resetPeakTreeRss

-- Rust compiles and writes the `.ixe` directly (streamed — no
-- env-sized ByteArray crosses the FFI; `<out>.tmp` + atomic rename).
-- The file is the canonical `Ixon.Env::put` format and round-trips
-- through `Ixon.Env::get`, so later runs (e.g. `ix check-ixon`) can
-- skip the Lean → IxOn compile step.
let start ← IO.monoMsNow
let bytes ← Ix.CompileM.rsCompileEnvBytesFFI constList
let size ← Ix.CompileM.rsCompileEnvBytesFFI constList outPath
let elapsed := (← IO.monoMsNow) - start

println! "Compiled {fmtBytes bytes.size} env in {elapsed.formatMs}"
println! "Compiled and wrote {fmtBytes size} env to {outPath} in {elapsed.formatMs}"
IO.println s!"##benchmark## {elapsed} {size} {totalConsts}"
if let some flag := p.flag? "json" then
let key := (p.flag? "json-name").map (·.as! String)
|>.getD ((FilePath.mk pathStr).fileStem.getD "env")
Expand All@@ -148,20 +154,10 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
let peakRss ← TracingTexray.peakTreeRssBytes
Ix.Benchmark.Results.writeRow (flag.as! String) key "ok"
[ ("compile-time", Ix.Benchmark.Results.jsonRound 3 secs)
, ("file-size", Lean.toJson bytes.size)
, ("file-size", Lean.toJson size)
, ("constants", Lean.toJson totalConsts)
, ("throughput", Ix.Benchmark.Results.jsonRound 2 tput)
, ("peak-rss", Lean.toJson peakRss) ]

-- Persist the serialized IxonEnv (`Env::put` bytes) to disk so subsequent
-- runs (e.g. `ix check-ixon`) can skip the Lean → IxOn compile step. The
-- resulting file is the canonical streaming format produced by
-- `Ixon.Env::put` (see `src/ix/ixon/serialize.rs:1093-1297`); it round-trips
-- through `Ixon.Env::get`.
let writeStart ← IO.monoMsNow
IO.FS.writeBinFile outPath bytes
let writeMs := (← IO.monoMsNow) - writeStart
println! "Wrote {fmtBytes bytes.size} to {outPath} in {writeMs.formatMs}"
return 0


Expand Down
17 changes: 12 additions & 5 deletions Ix/CompileM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -1916,9 +1916,14 @@ def compileEnvParallel (env : Ix.Environment) (blocks : Ix.CondensedBlocks)

/-! ## Rust Compilation FFI -/

/-- FFI: Compile a Lean environment to serialized Ixon.Env bytes using Rust. -/
/-- FFI: Compile a Lean environment and write the serialized Ixon.Env
bytes straight to `outPath` from Rust (streamed; no env-sized
ByteArray crosses the FFI). Writes to `<outPath>.tmp` then renames,
so a crash cannot leave a truncated file. Returns the byte count
written. -/
@[extern "rs_compile_env"]
opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO ByteArray
opaque rsCompileEnvBytesFFI
: @& List (Lean.Name × Lean.ConstantInfo) → @& String → IO Nat

/-- FFI: 8-phase validation of the aux_gen compile pipeline (compile +
decompile + roundtrip + alpha-equivalence + nested-detect checks).
Expand All@@ -1932,10 +1937,12 @@ opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO By
opaque rsCompileValidateAuxFFI
: @& List (Lean.Name × Lean.ConstantInfo) → USize

/-- Compile a Lean environment to Ixon.Env bytes using the Rust compiler. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) : IO ByteArray := do
/-- Compile a Lean environment and write the serialized Ixon.Env bytes
to `outPath` using the Rust compiler. Returns the byte count. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) (outPath : String)
: IO Nat := do
let constList := leanEnv.constants.toList
rsCompileEnvBytesFFI constList
rsCompileEnvBytesFFI constList outPath

-- Re-export RawEnv types from Ixon for backwards compatibility
export Ixon (RawConst RawNamed RawBlob RawComm RawEnv)
Expand Down
6 changes: 5 additions & 1 deletion Tests/Main.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,10 +60,14 @@ def ignoredSuites : Std.HashMap String (List LSpec.TestSeq) := .ofList [
("parallel-canon-roundtrip", Tests.CanonM.parallelSuiteIO),
("graph-cross", Tests.Ix.GraphM.suiteIO),
("condense-cross", Tests.Ix.CondenseM.suiteIO),
-- Lean compilation & kernel tests currently broken, disabled
-- Lean-side compilation/decompilation currently broken, disabled
--("compile", Tests.Compile.compileSuiteIO),
--("decompile", Tests.Decompile.decompileSuiteIO),
("rust-serialize", Tests.RustSerialize.rustSerializeSuiteIO),
-- Rust decompile of synthesized `_sparseCasesOn` aux constants fails
-- ("missing Ref metadata": their aux_gen metadata arena misaligns
-- with the serialized expr, and pure-aux constants have no
-- `Named.original` sidecar to recover from), disabled
--("rust-decompile", Tests.RustDecompile.rustDecompileSuiteIO),
("commit-io", Tests.Commit.suiteIO),
("kernel-ixon-roundtrip", Tests.Ix.Kernel.Roundtrip.suite),
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ebf2fc1
Compile spill steps 0+1: accumulator instrumentation and demote mode
samuelburnham Jul 8, 2026
c879d2d
Compile spill step 2: mmap-backed accumulator bytes
samuelburnham Jul 8, 2026
6a564f9
Compile spill: RSS decomposition instrumentation + worker kenv clearing
samuelburnham Jul 9, 2026
e7c232b
Compile spill lever 2: demote named metadata to serialized bytes
samuelburnham Jul 9, 2026
adb9a02
Compile spill lever 4: stream the .ixe from Rust (IX_COMPILE_STREAM=1)
samuelburnham Jul 9, 2026
f5d97ae
Compile spill lever 1: lazy LeanEnv decode (IX_COMPILE_LEAN_ENV=lazy)
samuelburnham Jul 9, 2026
87a96f2
Compile: fuse the three whole-env setup sweeps into one scan
samuelburnham Jul 9, 2026
2d15020
Compile spill: move design-doc content into module docs
samuelburnham Jul 10, 2026
bde1789
Compile memory: consolidate to two knobs, always-on defaults
samuelburnham Jul 10, 2026
a29a34e
Compile FFI: rs_compile_env compiles straight to a file
samuelburnham Jul 10, 2026
d25fba3
Compile: decode-time counters for the lazy env
samuelburnham Jul 10, 2026
f973793
Compile: fix all-features clippy and test-ffi fallout
samuelburnham Jul 10, 2026
f216728
Compile: drop the measurement instrumentation
samuelburnham Jul 10, 2026
fc29c3f
Compile: size the lazy-env cache by sweep, 65536 -> 16384 entries
samuelburnham Jul 10, 2026
1379125
sp1 guest: read Named metadata through the accessor
samuelburnham Jul 10, 2026
b4446f5
Compile: parallelize put_file's named-section encode
samuelburnham Jul 11, 2026
eade07d
Compile: IX_COMPILE_EAGER=1 knob for RAM-rich machines
samuelburnham Jul 11, 2026
011107f
Compile: pin the hottest constants in the lazy env
samuelburnham Jul 11, 2026
3059268
bench-pr: IX_COMPILE_* knob passthrough to the measured compile
samuelburnham Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions .github/workflows/bench-pr.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,10 @@
# BENCH_SHARD=1 # restrict to the multi-shard target constants
# BENCH_PHASES=1 # add per-constant phase drill-downs to the comment
# RUST_LOG=info # passthrough env (allowlisted)
# IX_COMPILE_EAGER=1 # compile-knob passthrough (also IX_COMPILE_DEMOTE /
# # IX_COMPILE_WORKERS); reaches the measured `ix
# # compile` and keys its caches, so a knob run gets
# # its own row instead of the default run's
#
# Mode defaults per backend (the registry's defaultMode): `aiur` runs
# `prove` — the real-workload simulation, whose report also carries the
Expand DownExpand Up@@ -197,14 +201,22 @@ jobs:
ref: ${{ inputs.head-sha }}
# The job runs PR code; never leave the token in .git.
persist-credentials: false
# Re-running !benchmark on the same commit: the .ixe is already
# published — nothing to do.
# Allowlisted KEY=VALUE lines from the !benchmark comment. Applied
# here so IX_COMPILE_* knobs reach the measured compile, and staged
# to a file so the cache keys below can hash it — a knob run must
# not reuse (or overwrite) the default run's published .ixe/row.
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Re-running !benchmark on the same commit with the same config: the
# .ixe is already published — nothing to do.
- name: Check for published .ixe
id: pr-ixe
uses: actions/cache/restore@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
lookup-only: true
- name: Restore PR binaries
if: steps.pr-ixe.outputs.cache-hit != 'true'
Expand DownExpand Up@@ -242,15 +254,15 @@ jobs:
uses: actions/cache/save@v5
with:
path: ${{ matrix.env }}.ixe
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-ixe-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}
# The measured row: the compile cell reuses it as its PR side (same
# runner class, same binaries, same command it would run itself).
- name: Publish compile row
if: steps.pr-ixe.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: compile.json
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}
key: bench-pr-row-${{ inputs.head-sha }}-${{ matrix.env }}-${{ hashFiles('ptenv.txt') }}

benchmark:
# Explicit name: the default would append EVERY matrix value (backend,
Expand DownExpand Up@@ -294,7 +306,7 @@ jobs:
- name: Apply passthrough env
env:
PTENV: ${{ needs.build.outputs.passthrough-env }}
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' >> "$GITHUB_ENV"
run: printf '%s\n' "$PTENV" | sed '/^[[:space:]]*$/d' | tee ptenv.txt >> "$GITHUB_ENV"
# Restore the once-built PR binaries (see the build job) into the PR
# tree's own bin dir: `ix bench run` resolves the measured tools from
# <repo>/.lake/build/bin first, then PATH, so staging in-tree keeps the
Expand DownExpand Up@@ -334,15 +346,15 @@ jobs:
uses: actions/cache/restore@v5
with:
path: ${{ matrix.cell.env }}.ixe
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-ixe-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# Compile cells reuse the compile job's measured row as their PR side.
- name: Restore compile row
if: matrix.cell.backend == 'compile'
uses: actions/cache/restore@v5
with:
path: compile.json
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}
key: bench-pr-row-${{ env.HEAD_SHA }}-${{ matrix.cell.env }}-${{ hashFiles('ptenv.txt') }}
fail-on-cache-miss: true
# zkVM cells additionally need the Rust toolchain + the backend's toolchain
# and system deps (the shared composite install actions).
Expand Down
14 changes: 10 additions & 4 deletions Ix/Cli/BenchReport.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -595,9 +595,13 @@ def parseError (msg : String) : IO UInt32 := do
BENCH_FULL=1 (full curated set, not just primary)
BENCH_SHARD=1 (only the multi-shard target constants)
BENCH_PHASES=1 / RUST_LOG=… / WITHOUT_VK_VERIFICATION=… /
RUSTFLAGS=… (passthrough; BENCH_PHASES=1 adds the
RUSTFLAGS=… / IX_COMPILE_EAGER=… / IX_COMPILE_DEMOTE=… /
IX_COMPILE_WORKERS=… (passthrough; BENCH_PHASES=1 adds the
per-constant phase drill-downs to the
comment)
comment; the IX_COMPILE_* knobs reach
the measured `ix compile` and key its
caches, so knob runs don't reuse a
default run's published row)

The KEY=VALUE config may sit on its own lines below the command (the
comment form) or inline on the command line, whitespace-separated
Expand DownExpand Up@@ -694,13 +698,15 @@ def runParseCmd (p : Cli.Parsed) : IO UInt32 := do
| "BENCH_FULL" => if val == "1" then full := "1"
| k =>
if ["BENCH_PHASES", "RUST_LOG", "WITHOUT_VK_VERIFICATION",
"RUSTFLAGS"].contains k then
"RUSTFLAGS", "IX_COMPILE_EAGER", "IX_COMPILE_DEMOTE",
"IX_COMPILE_WORKERS"].contains k then
passthrough := passthrough.push s!"{k}={val}"
else if strict then
return ← parseError s!"unknown config key `{k}` in the \
benchmark command (expected BENCH_ENVS / BENCH_FULL / \
BENCH_SHARD, or passthrough: BENCH_PHASES, RUST_LOG, \
WITHOUT_VK_VERIFICATION, RUSTFLAGS)"
WITHOUT_VK_VERIFICATION, RUSTFLAGS, IX_COMPILE_EAGER, \
IX_COMPILE_DEMOTE, IX_COMPILE_WORKERS)"
| [] => continue
if envs.isEmpty then envs := #["InitStd"]

Expand Down
24 changes: 10 additions & 14 deletions Ix/Cli/CompileCmd.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -134,11 +134,17 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
if benched then
TracingTexray.startSampler
TracingTexray.resetPeakTreeRss

-- Rust compiles and writes the `.ixe` directly (streamed — no
-- env-sized ByteArray crosses the FFI; `<out>.tmp` + atomic rename).
-- The file is the canonical `Ixon.Env::put` format and round-trips
-- through `Ixon.Env::get`, so later runs (e.g. `ix check-ixon`) can
-- skip the Lean → IxOn compile step.
let start ← IO.monoMsNow
let bytes ← Ix.CompileM.rsCompileEnvBytesFFI constList
let size ← Ix.CompileM.rsCompileEnvBytesFFI constList outPath
let elapsed := (← IO.monoMsNow) - start

println! "Compiled {fmtBytes bytes.size} env in {elapsed.formatMs}"
println! "Compiled and wrote {fmtBytes size} env to {outPath} in {elapsed.formatMs}"
IO.println s!"##benchmark## {elapsed} {size} {totalConsts}"
if let some flag := p.flag? "json" then
let key := (p.flag? "json-name").map (·.as! String)
|>.getD ((FilePath.mk pathStr).fileStem.getD "env")
Expand All@@ -148,20 +154,10 @@ def runCompileCmd (p : Cli.Parsed) : IO UInt32 := do
let peakRss ← TracingTexray.peakTreeRssBytes
Ix.Benchmark.Results.writeRow (flag.as! String) key "ok"
[ ("compile-time", Ix.Benchmark.Results.jsonRound 3 secs)
, ("file-size", Lean.toJson bytes.size)
, ("file-size", Lean.toJson size)
, ("constants", Lean.toJson totalConsts)
, ("throughput", Ix.Benchmark.Results.jsonRound 2 tput)
, ("peak-rss", Lean.toJson peakRss) ]

-- Persist the serialized IxonEnv (`Env::put` bytes) to disk so subsequent
-- runs (e.g. `ix check-ixon`) can skip the Lean → IxOn compile step. The
-- resulting file is the canonical streaming format produced by
-- `Ixon.Env::put` (see `src/ix/ixon/serialize.rs:1093-1297`); it round-trips
-- through `Ixon.Env::get`.
let writeStart ← IO.monoMsNow
IO.FS.writeBinFile outPath bytes
let writeMs := (← IO.monoMsNow) - writeStart
println! "Wrote {fmtBytes bytes.size} to {outPath} in {writeMs.formatMs}"
return 0


Expand Down
17 changes: 12 additions & 5 deletions Ix/CompileM.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -1916,9 +1916,14 @@ def compileEnvParallel (env : Ix.Environment) (blocks : Ix.CondensedBlocks)

/-! ## Rust Compilation FFI -/

/-- FFI: Compile a Lean environment to serialized Ixon.Env bytes using Rust. -/
/-- FFI: Compile a Lean environment and write the serialized Ixon.Env
bytes straight to `outPath` from Rust (streamed; no env-sized
ByteArray crosses the FFI). Writes to `<outPath>.tmp` then renames,
so a crash cannot leave a truncated file. Returns the byte count
written. -/
@[extern "rs_compile_env"]
opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO ByteArray
opaque rsCompileEnvBytesFFI
: @& List (Lean.Name × Lean.ConstantInfo) → @& String → IO Nat

/-- FFI: 8-phase validation of the aux_gen compile pipeline (compile +
decompile + roundtrip + alpha-equivalence + nested-detect checks).
Expand All@@ -1932,10 +1937,12 @@ opaque rsCompileEnvBytesFFI : @& List (Lean.Name × Lean.ConstantInfo) → IO By
opaque rsCompileValidateAuxFFI
: @& List (Lean.Name × Lean.ConstantInfo) → USize

/-- Compile a Lean environment to Ixon.Env bytes using the Rust compiler. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) : IO ByteArray := do
/-- Compile a Lean environment and write the serialized Ixon.Env bytes
to `outPath` using the Rust compiler. Returns the byte count. -/
def rsCompileEnvBytes (leanEnv : Lean.Environment) (outPath : String)
: IO Nat := do
let constList := leanEnv.constants.toList
rsCompileEnvBytesFFI constList
rsCompileEnvBytesFFI constList outPath

-- Re-export RawEnv types from Ixon for backwards compatibility
export Ixon (RawConst RawNamed RawBlob RawComm RawEnv)
Expand Down
6 changes: 5 additions & 1 deletion Tests/Main.lean
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,10 +60,14 @@ def ignoredSuites : Std.HashMap String (List LSpec.TestSeq) := .ofList [
("parallel-canon-roundtrip", Tests.CanonM.parallelSuiteIO),
("graph-cross", Tests.Ix.GraphM.suiteIO),
("condense-cross", Tests.Ix.CondenseM.suiteIO),
-- Lean compilation & kernel tests currently broken, disabled
-- Lean-side compilation/decompilation currently broken, disabled
--("compile", Tests.Compile.compileSuiteIO),
--("decompile", Tests.Decompile.decompileSuiteIO),
("rust-serialize", Tests.RustSerialize.rustSerializeSuiteIO),
-- Rust decompile of synthesized `_sparseCasesOn` aux constants fails
-- ("missing Ref metadata": their aux_gen metadata arena misaligns
-- with the serialized expr, and pure-aux constants have no
-- `Named.original` sidecar to recover from), disabled
--("rust-decompile", Tests.RustDecompile.rustDecompileSuiteIO),
("commit-io", Tests.Commit.suiteIO),
("kernel-ixon-roundtrip", Tests.Ix.Kernel.Roundtrip.suite),
Expand Down
Loading