Skip to content

test(DON'T MERGE): develop-v2.1.0 - #3013

Draft
shuklaayush wants to merge 199 commits into
mainfrom
develop-v2.1.0
Draft

test(DON'T MERGE): develop-v2.1.0#3013
shuklaayush wants to merge 199 commits into
mainfrom
develop-v2.1.0

Conversation

@shuklaayush

Copy link
Copy Markdown
Collaborator

No description provided.

shuklaayush and others added 30 commits July 13, 2026 22:48
makes the basic framework for using rvr extensions in openvm

- adds an rvr feature flag
- defines `RvrExtensionCtx` struct to provide mappings between
opcode/executor/air indices
- defines `VmRvrExtension` trait that extensions can implement to be
registered
- updated macro so that rvr `ExtensionRegistry` can be auto-generated in
`SdkVmConfig`

closes INT-7474, INT-7475, INT-7479
#2730)

Moves the rvr files related to compiling and execution into
openvm-circuit. Those rvr files previously depended on openvm-circuit
and in order to enable rvr execution through the openvm pipeline, they
had to be made a part of openvm-circuit to prevent circular
dependencies.

closes INT-7537
- Vm execution instance is made to use rvr execution, depending on the
feature. Helper functions to convert between the existing `VmState` and
the rvr state are also added.
- The `VmConfig` macro now has a `create_rvr_extensions` method
implementation, but instead of defining a new `VmRvrConfig` trait, the
`create_rvr_extensions` method piggybacks on the existing
`VmExecutionConfig`. This is to avoid complex feature-gated trait
bounds.

closes INT-6810, INT-7476
Enables running benchmarks through rvr extension. Benchmark tests do not check execution correctness and currently execution involving extensions other than RV32IM fail because `VmRvrExtension` trait implementation is not properly wired.

closes INT-7480
Previously rvr execution had to use `executor_idx_to_air_idx` information in order to construct `ExtensionRegistry`. This was a problem for pure execution which didn't need air indices so the interface diverged between rvr and aot/interpreted. Now for rvr pure execution, dummy index values of `NO_CHIP` are used instead to keep the interface consistent.

towards INT-7611
Removes the rvr tests and instead adds rvr comparison steps in existing openvm tests in a similar way to aot. Unlike aot, metered cost execution is also run and compared for rvr and interpreted modes.

closes INT-7627
- Introduces a new `Rv32IoExtension` in rvr that handles the rv32io
instructions (hint_storew, hint_buffer, reveal). This is mainly to have
a struct managing the hint_store chip index.
- Adds rvr tests to the CI file in the same way as aot.

closes INT-7466
Implements the rvr feature for the keccak256 extension and also adds rvr tests to CI. Now extensions don't take a `staticlib_path` argument manually and instead uses the auto-built staticlib made by a build.rs file.

closes INT-7468
Implements the rvr feature for the Algebra extension. The rvr side of the Algebra extension is now also split into `ModularRvrExtension` and `Fp2RvrExtension`. A notable change is to have the C code for the Algebra extension which uses `libsecp256k1` to also unconditionally contain the C code needed in the ECC extension, since they are closely related and doing so would avoid configuration dependencies.

closes INT-7470, INT-7704
Implements the rvr feature for all extensions that are left - BigInt, Sha2, ECC, Pairing, Deferral. Code for tests and CI are also updated. Changes for the Deferral extension includes additions to the VM state used in rvr execution.

closes INT-7465
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Changed rvr execution to use the existing openvm `VmState` instead of
defining a new state struct and copying data between the two forms. The
references and pointers to the fields in `VmState` are passed to the rvr
execution functions so they can be used in C code.
- The Deferral extension now uses a callback registration system to
expose the Deferral related data to C code instead of piggybacking on
the same mechanism of `OpenVmHostCallbacks`. This is enabled for each
extension so that they can have per-extension data and state that is
maintained separately.

closes INT-7572
- Makes rvr metered execution use the existing `SegmentationCtx` of openvm instead of its own new structs and code. This fixes the discrepancy between rvr and interpreted/aot segmentation logic and resolves the issue of rvr making too many segments. (https://github.com/axiom-crypto/openvm-eth/actions/runs/26112543464)
- Fixes the calculation of `num_insns` in segments that are used as segment boundaries. The instruction counts were recorded as multiples of `segment_check_insns` (1000) that didn't map to the actual basic block boundaries. Addresses the problem of overflowing GPU memory. (https://github.com/axiom-crypto/openvm-eth/actions/runs/26127167098)

closes INT-7835
moves the rvr compilation stage for metered and metered cost out of execution and into instance construction

closes INT-7626
Air indices are now represented as an enum in rvr code. The `AirIndex`
enum has `Uninitialized` and `NoChip` variants that replace the previous
`NO_CHIP = u32::MAX`. `AirIndex::Uninitialized` is only used in pure
execution where air indices don't matter and causes a panic in rvr
metered and metered cost execution.

closes INT-7611
…#2807)

- **RVR metered execution can now suspend at segment boundaries.**
Previously only the interpreter and AOT backends supported
segment-by-segment metered runs; RVR ran metered execution straight to
termination. This branch adds a parallel `RvrMeteredSegmentInstance`
(`RvrMeteredInstanceWith<F, SegmentBoundary>`) whose
`execute_metered_until_segment_boundary` returns after the metered
segmentation callback creates a segment, mirroring the suspend/resume
shape the other two backends already expose. The tracer countdown is
carried across calls by checkpointing `tracer.check_counter` into
`segmentation_ctx.instrets_until_check` on suspend and restoring it on
entry; both values are `try_from`-validated against u32 at the entry
point (new `ExecuteError::InvalidMeteredContext`) and the hot
C-callback's matching cast is guarded by `debug_assert_eq!`. Mid-segment
suspension is out of scope: `initialize_segment_memory` resets the
per-segment page-indices checkpoint buffer assuming the page buffers
have already been flushed at a segment boundary.

- **Generated-C surface reorganized by policy.** Block-begin and
suspender helpers move into
`c/block/{instret,metered,metered_segment}.h` and
`c/suspender/{none,instret_limit,segment_boundary}.h`; tracer headers
move under `c/tracer/`. A new `SuspendPolicy` enum drives which pair is
included, with `compile_impl` rejecting incoherent combinations
(`Metered` × `InstretLimit`, `Pure|MeteredCost` × `SegmentBoundary`) at
compile time. Compile-time selection without preprocessor directives in
the generated C, per the AGENTS.md guidance.

- **`MeteredCtx` round-trip via `MeteredCtxParts`.** `SegmentationState`
now carries the full `MemoryCtx` and `suspend_on_segment` flag, so a
suspended metered run can be converted back to a `MeteredCtx`
(`into_metered_ctx`) and resumed without losing page-tracking or
segmentation state. A new test exercises the field-by-field round-trip.

- **All RVR codegen inputs embedded at compile time.** Removes every
`CARGO_MANIFEST_DIR` runtime dependency from the RVR project-emit
pipeline so binaries (Docker images, etc.) no longer need the source
tree to invoke `compile_impl`. Core C files (`openvm_io.{c,h}`,
`rvr_ext_wrappers.c`) switch from `fs::copy` to
`fs::write(include_str!(…))`. Extension `.a` staticlibs migrate from
`staticlib_path() -> &Path` to `staticlib_file() -> (&'static str,
&'static [u8])` via `include_bytes!(env!("RVR_*_FFI_STATICLIB"))`, with
a new `write_extension_staticlibs` helper writing them to the temp
project for `make` to link. Modular's libsecp256k1 amalgamation include
(~85 `.c`/`.h` files, with test/bench/ctime/valgrind files filtered out)
is collected by `extensions/algebra/rvr/build.rs` into a generated
`SECP256K1_C_FILES` const and returned via the new
`RvrExtension::extra_c_include_files()` hook (for files written but not
compiled as their own TUs); `extra_cflags` switches to relative
`-Isecp256k1/src` / `-Isecp256k1` against the temp project root. Trait
return types are tightened from `&str` to `&'static str`.

- **Up-front toolchain detection.** `compile_impl` probes the C
compiler, linker, and `make` in PATH before building and reports all
missing tools at once via `RuntimeToolchainError`. Adds `RVR_MAKE`
override, forwards `HOST_OS` to the Makefile (replacing its `uname -s`
shell-out), and threads path context into `CompileError` I/O variants.

- **Metrics consolidation.** The four near-identical `Instant::now() …
counter!().absolute() … gauge!().set()` blocks across interpreter / AOT
/ RVR are replaced by a single `ExecutionMetricTimer` helper in
`arch::execution_metrics` (guarded against div-by-zero on
sub-microsecond runs). A complementary `CompilationTimer`
(`arch::compilation_metrics`) wraps every `*_instance` constructor and
emits a `compile_{pure,metered,metered_cost,metered_segment}_ms` gauge
labeled by backend (`interpreter` / `aot` / `rvr`).

- **E1/E2/E3 jargon dropped.** `execute_e1` span/metric names become
`execute_pure`; `terminate_execute_e12_*` → `terminate_execute_*`; const
generic `E1` → `PURE_EXECUTION`. Comment references to "(E1)/(E2)/(E3)"
are removed in favor of "pure/metered/preflight".

- **Metric names.** `execute_e1_insns` → `execute_pure_insns`,
`execute_e1_insn_mi/s` → `execute_pure_insn_mi/s`. Dashboards or
alerting keyed on the old names need to be updated.
- **`RvrExtension` trait surface.** `extra_c_source_paths() ->
Vec<PathBuf>` → `extra_c_sources() -> Vec<(&'static str, &'static
str)>`; `staticlib_path/paths()` → `staticlib_file/files()` returning
embedded bytes; new optional `extra_c_include_files()` for files written
but not compiled as TUs. Existing impls need a one-time conversion to
`include_str!` / `include_bytes!`.
- **`ExecutorInventory` generic param renames** (`E1`/`E2`/`E3` →
`CombinedE`/`NewE`/`TargetE`) are visible in error messages but
compatible.
- **`CompileError` shape.** `CProject(io::Error)` → `CProject { path,
source }`; `Toolchain(String)` → `Toolchain(#[from]
RuntimeToolchainError)`; new `ToolchainCommand { command, source }`.
Callers matching on these variants need to update.
- **Binary size.** Embedded `.a` staticlibs and libsecp256k1 sources
grow the binary by roughly 5–30 MB depending on enabled extensions.

resolves int-7917

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Splits the `GenericSdk::execute_*` methods into `GenericSdk::compile_*`
and `GenericSdk::execute_compiled_*` methods for pure, metered and
metered cost. This is to be able to reuse the "compiled" instance which,
for rvr, takes a long time to create.

closes INT-7842
Three correctness fixes in the RVR backend so that its per-segment VM
state is byte-identical to the Rust preflight executor's, plus a
test-coverage fix that closes the gap that hid all three bugs from
`check_rvr_equivalence`.

equivalence check`

- `check_rvr_equivalence` (and its AOT sibling) only walked AS=1
(registers) byte-by-byte, silently missing any divergence in RV32 main
memory (AS=2), public values (AS=3), and deferral (AS=4). All three
correctness fixes below live in AS=2 or AS=4 and would have surfaced on
the first `air_test`-style run if the check had walked every address
space.

Extracted the closure into a `check_vm_state_eq(lhs, rhs) ->
eyre::Result<()>` free function shared by both the RVR and AOT
equivalence checks, replacing the AS=1-only loop with a slice-level diff
over every `LinearMemory`. Short-circuits at the first mismatch and
reports `(AS, byte offset, lhs, rhs)`. Microseconds on typical test VM
configs.

- `SegmentationState::on_periodic_check` was bumping
`segmentation_ctx.instret` by a full `segment_check_insns` interval
up-front, then incrementing `tracer.check_counter` by the same delta on
the way out. The anchor and the countdown ended up ahead of the actual
VM by exactly `remaining_counter`, so the next interval inherited an
inconsistent baseline.

In termination paths this could let the segmenter seal a non-terminal
block as the final segment. The callback now:
- computes the actual block-boundary instret directly: `prev_anchor +
(segment_check_insns - remaining_counter)`,
- writes that back as the new anchor,
- resets `check_counter` to a full fresh interval rather than
incrementing.

This matches the Rust metered executor's behavior at the same point.

Mod-builder evaluates `SymbolicExpr` inputs **modulo the configured
prime**. For `SETUP_ADDSUB` / `SETUP_MULDIV` and their Fp2 counterparts,
the compute formula resolves to `Input(0)`, which during setup is the
modulus `p` itself — so the variable is `p % p = 0`. The VM writes 32
zero bytes (64 for Fp2) to `rd`.

`rvr_ext_mod_setup` and `rvr_ext_fp2_setup` were copying `rs1`'s bytes
(the modulus) to `rd`. Those bytes then leaked into the guest's stack as
register-loaded values, propagating downstream as a memory divergence
between RVR and preflight at later segment boundaries.

The FFI now traces the `rs1`/`rs2` reads (still required for chip
metering) but writes zero bytes to `rd`.

The deferral CALL FFI in RVR only traced AS=4 access for metering and
never updated the `(input_acc, output_acc)` accumulator bytes. The Rust
preflight executor (`DeferralCallExecutor::execute_e12_impl`) hashes
each `(old_acc, commit)` pair via poseidon2 and writes the new
accumulator F's to DEFERRAL_AS. Every deferral CALL therefore left RVR's
AS=4 a hash-round behind preflight, producing a memory divergence that
cascaded through subsequent CALLs.

Plumbed a `(*mut F, len_in_F_units)` alias of DEFERRAL_AS through
`OpenVmIoState` (via a new `deferral_memory_ptr` helper in `bridge.rs`
with a debug-mode alignment check on the `u8 → F` cast) and registered a
`DeferralCompressFn` poseidon2 closure on the host side.
`host_deferral_call_lookup` now hashes the accumulators and writes the
new F bytes into AS=4 in F-element units that exactly match preflight's
`vm_write::<F, BLOCK_SIZE>` layout. `F::from_u32` is bijective with the
perm output for `MontyField31`, so the stored bytes are byte-identical
to what the preflight executor writes.

resolves int-7974
Memory read and write functions now have an optional `check_bounds`
invocation before accessing the memory. `check_bounds` checks that the
access lies within the VM's addressable memory region and aborts
otherwise. The same is applied for `openvm_io.h` functions that work
with the user IO address space in data memory.

To turn off protected mode, add the `openvm-cli/unprotected` feature.

Mirrors the interpreter's `check_bounds` and `panic_oob` functions, and
`unprotected` Cargo feature.

closes INT-7702

---------

Co-authored-by: Ayush Shukla <ayush@axiom.xyz>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…2820)

A perf pass on RVR metered execution. The main change isolates the rare
segment-check callback into a cold per-block helper so the hot block
stays frameless.

## Cold per-block segment-check helper

Hot metered RVR blocks were paying a stack-frame cost because the rare
`on_check` callback could fire from the same C function. Generated asm
showed every metered block — even single-instruction ones — getting an
entry prologue and stack spills to preserve guest-register parameters
across the possible callback.

Hot blocks now only test `check_counter < block_insn_count` inline. On
underflow they musttail-jump to a cold per-block
`block_0xPC_checkpoint(...)` helper that runs the segment-check
callback, suspends/exits if needed, or musttails back to the hot block
with a refreshed counter. Same semantics; the hot path is frameless
again.

## Cleanup landing in the same patch

- **`uses_page_tracking()` IR predicate.** Only blocks that can touch
memory emit AS_MEMORY page-tracking locals. ~59% of blocks in the reth
benchmark didn't need them. Extension emitters default to `true`;
`HintNonQrInstr` opts out, plain host-only phantoms (`HintInput`,
`HintRandom`, `PrintStr`) don't trigger page locals.
- **Metered block ABI hoist.** `check_counter` (`_cc`) and
`trace_heights` (`_th`) are passed as block parameters, removing
`state->tracer` loads from every block. Metered mode uses 8 hot guest
registers instead of 10 to fit the new parameters.
- **`CompileOptions::keep_artifacts`.** Retains the generated RVR C
tempdir on success and logs the path. Useful for codegen / asm audits.
- Per-width fast traced memory helpers and clang-format / formatting
cleanups across the FFI C/Rust crates.

results in a modest ~100ms (out of 1.7s) improvement in metered
execution of [reth
benchmark](https://github.com/axiom-crypto/openvm-eth/actions/runs/26514539244)

on my laptop, the improvement is much more significant (~20%)
In `rvr`, some constants are redefined or set as variables. Resolved
some of the dependency issues (e.g. circular dependency) to import the
constants instead.

Related constants:
1) `WORD_SIZE`: imported from `openvm_platform::WORD_SIZE`

2) `AS_MEMORY`: imported from
`openvm_instructions::riscv::RV32_MEMORY_AS`

3) `AS_REGISTER`: imported from
`openvm_instructions::riscv::RV32_REGISTER_AS`

4) `AS_PUBLIC_VALUES`: imported from
`openvm_instructions::PUBLIC_VALUES_AS` (moved to `openvm_instructions`
from `openvm-circuit`. Is it right choice????)

5) `DEFERRAL_AS`: imported from `openvm_instructions::DEFERRAL_AS`

6) `MAX_BLOCK_INSNS` (`rvr-openvm-lift/src/cfg.rs`): was `let`, now
`const`

The following ones are kept redefined:
1) `CHUNK`, `DEFERRAL_DIGEST_SIZE`: logically are from
`openvm-stark-sdk`. `openvm-circuit` and `openvm-recursion-circuit`
already redefine CHUNK. (can not import from them due to circular
dependency).

2) `DEFAULT_PAGE_BITS`, `DEFAULT_SEGMENT_CHECK_INSNS`: logically are
from `openvm-circuit::arch::execution_mode::metered::{ctx,
segment_ctx}`. These are host-side metered-execution defaults. (can not
import from them due to circular dependency)

towards INT-7571
`MAX_MEM_PAGES_PER_INSN ` is a worst-case number of pages a single
instruction can touch. The worst-case unique pages per instruction
(`HINT_BUFFER`) is `MAX_HINT_BUFFER_WORDS * WORD_SIZE` bytes divided by
page size. One page covers `CHUNK * 2^PAGE_BITS` bytes.

So the formula is:
`MAX_MEM_PAGES_PER_INSN = div_ceil(MAX_HINT_BUFFER_WORDS * WORD_SIZE,
CHUNK * 2^PAGE_BITS) + 1`

`+1` misalignment.

closes INT-7462
Add save and load compiled artifacts feature in `rvr` mode. The feature
consist of having the ability to save compilation artifacts on disk and
load them into the sdk to execute (part 1). Reusing of the persisted
artifacts whenever possible instead of recompiling based on some
metadata (part 2) will be done in separate PR.

This PR is related to the part 1. The following methods were added:
1) `Sdk::load_compiled_pure`, `Sdk::load_compiled_metered`,
`Sdk::load_compiled_metered_cost` and related methods for loading pure,
metered and metered cost `.so` files

2) `RvrPureInstance::save`, `RvrMeteredInstance::save`,
`RvrMeteredCostInstance::save` and related methods for saving `.so` file

towards INT-7843
mansur20478 and others added 30 commits July 17, 2026 02:40
This PR adds misaligned memory access support. The current solution
handles misaligned memory accesses by dividing it depending on:
1. block crossing: meaning it requires a second adjacent memory block
for reading/writing from Memory AS.
2. cell crossing: due to cells being in u16, odd shifts (such as 1, 3,
5, 7) require lower and upper 8bits of adjacent cells.

Since block crossing affects only 2, 4, and 8 byte load/stores, there
are two adapters per load/store: aligned and misaligned. The difference
between aligned and misaligned is additional logic for reading/writing a
second adjacent memory block.

For cell crossing problem, we handle it by storing lower 8 bits of
touched cells - cells that are needed for successfully loading/storing.
Using lower 8 bits, it is possible to derive upper 8 bits of touched
cells.

Also, as a part of optimization, we decided to remove `is_valid` column
as it is derivable from the shift selector.

resolves INT-7672

---------

Co-authored-by: GunaDD <gunadigan@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Ayush Shukla <ayush@axiom.xyz>
## Summary

This PR is mostly refactoring and cleanup. The shared tracer and
suspender abstraction had run its course: it made every RVR execution
mode carry the same accounting and suspension machinery, which blocked
further pure-execution performance work. Giving each mode its own state
removes that constraint and unlocks a smaller hot path.

- Pure RVR keeps only the state needed to run to completion. Its
generated blocks have no instruction-retirement accounting or suspension
check.
- Pure execution with instruction tracking is a separate compile mode.
It carries the remaining instruction count through generated block calls
and writes the retired count when execution returns.
- Metered and metered-cost execution keep their own mode data directly
in the RVR state.
- The shared tracer and suspender layers are removed.
- Loaded RVR artifacts are checked against the execution mode requested
by the caller.

## API changes

The public API now makes each capability explicit.

- `sdk.compile(...)` returns a completion-only pure artifact.
- `sdk.compile_with_instret_tracking(...)` returns a tracked pure
artifact that supports `execute_for` and `execute_from_state_for`.
- Bounded execution returns `ExecutionOutcome::Terminated` or
`ExecutionOutcome::Suspended`.
- Completion-only execution returns the final VM state directly. Tracked
completion also returns the instructions retired by that call.
- Interpreter and preflight bounded calls use `_for` methods instead of
optional instruction limits.
- Metered segment execution uses the same explicit
terminated-or-suspended outcome.

## Metrics

- Tracked pure execution continues to report the instructions retired by
the current call.
- Completion-only pure RVR does not report instruction-count or
throughput metrics because it does not track retired instructions.
- Full metered RVR reports the current call's instruction delta instead
of the cumulative instruction count.

## Validation

- Built the SDK with and without RVR.
- Built the RVR and metrics feature combination.
- Ran Clippy with warnings denied on an x86 host.
- Ran the RVR suspension and resume integration test.
- Ran the tracked artifact save and load SDK test.
- Checked the public API use in openvm-eth and rvr-openvm; neither needs
a source change.
Executable ELF segments can contain embedded data such as jump tables or
constant pools. Previously, transpilation failed whenever no registered
extension could decode one of these words, even if it was never
executed.

This change emits an `unimp` instruction at the corresponding program
slot instead. The original word remains unchanged in initialized memory,
so embedded data can still be read normally; the program terminates only
if control flow reaches that slot. Ambiguous instruction decodings
remain an error.

Includes a regression test with an undecodable word between valid RV64
instructions.
- BLS12-381 RVR arithmetic previously crossed the standard Rust FFI for
each specialized Fp, Fr, Fp2, and G1 operation.
- It now builds a pinned `blst` submodule and links direct
`preserve_most` C wrappers for Fp, Fr, and Fp2 arithmetic and non-setup
G1 add and double, using architecture-specific assembly where supported.
- P-256, BN254, setup operations, and generic BigUint fallbacks remain
on their existing paths. New RVR equivalence tests cover reduction and
arithmetic in Fp, Fr, and Fp2 plus G1 add and double.

[Benchmark
comparison](https://github.com/openvm-org/rvr-openvm/actions/runs/29756429770)

Resolves INT-8834
… shift instructions (#2973)

## Summary

- Adds separate chips for `XORI`, `ORI`, `ANDI`, `SLTI`, `SLTIU`,
`SLLI`, `SRLI`, and `SRAI`. These instructions previously shared chips
with their register versions.
- Splits the shared adapter into one adapter for register instructions
and another for immediate instructions. The immediate adapter reads one
register and takes the second input directly from the instruction.
- Updates the opcodes, transpiler, executor, RVR, CPU and CUDA trace
generation, and tests to use the new chips.

---------

Co-authored-by: Ayush Shukla <ayush@axiom.xyz>
## Summary

- Splits `ADDIW`, `SLLIW`, `SRLIW`, and `SRAIW` from their register-form
word ALU chips, with dedicated immediate opcodes and adapters.
- Updates the transpiler, RVR lifting, CPU/CUDA registration and trace
generation, and adds boundary coverage for the new word-immediate paths.
- Specializes the ADDI core range checks so the most-significant output
limb is not checked redundantly when the adapter already constrains it.

Resolves INT-8508
- `hint_input` used to copy the whole input into another buffer before
the guest could read it. This added an expensive `memcpy` for large Reth
inputs.
- It now moves the input `Vec` into `HintStream`. The length prefix and
zero padding are added as the guest reads the input.
- The interpreter and RVR use the same `HintStream`. Other hints are
still returned unchanged.

[Benchmark
comparison](https://github.com/openvm-org/rvr-openvm/actions/runs/29757990761)

Resolves INT-8835
- trap when rvr reaches an invalid control-flow target or a host
callback rejects its input
- keep rv64 register operands full-width across host I/O
- enable stricter generated C warnings and add boundary regression tests

Resolves INT-8837
…ons (#3006)

## Summary

GPU trace-generation and host-to-device (H2D) transfer optimizations for
the VM
prover, consolidated into one PR. Each logical change is a separate
commit.

## Changes

- **Pinned record-arena pool** — dense record arenas are served from a
pinned
host-memory pool with a background cleaner thread, so async DMAs from
pinned
  buffers stay in flight instead of blocking on a pageable staging copy.
- **Device-side boundary→Merkle record conversion** — memory boundary
records
  are converted to Merkle records on the GPU, with pinned host-region
  registration for the transport.
- **Pinned memory-image staging** — the per-segment memory-image upload
is
  staged through pinned buffers.
- **Tracegen tail trims** — merged inputs, a host-computed merged record
count,
exec-frequency upload via a pooled pinned buffer plus a device kernel,
and
  span/metric cost gated to non-empty arenas only.
- **`repr(C)` TouchedBlock layout** — the touched-block struct is laid
out to
match the CUDA inventory-record struct, enabling a zero-conversion bulk
copy.
- **Drop hidden stream sync** — remove the hidden stream synchronization
in the
  CPU-to-GPU proving-context transport.

System trace generation keeps the original system-first ordering.

## Results

Measured on the openvm-eth standard block, RTX Pro 6000, single sandbox
(device pool capped at 15 GiB):

| metric | baseline | this PR |
| --- | --- | --- |
| trace generation | 7.79 s | 5.01 s (−36%) |
| record H2D transport | 3.15 s | 0.13 s (−96%) |
| app proving (end-to-end) | 86.97 s | 80.10 s (−7.9%) |

Baseline and this PR were measured back-to-back on the same container
under
matched conditions (proving wall 818.8 s vs 818.6 s).

[Benchmark
comparison](https://github.com/axiom-crypto/openvm-eth/actions/runs/29834295151)

---------

Co-authored-by: Yi Sun <5178036+yi-sun@users.noreply.github.com>
Co-authored-by: GunaDD <gunadigan@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Ayush Shukla <ayush@axiom.xyz>
- fold fixed chip costs and extension rows into generated code
- omit tracing that an execution mode does not use
- tighten callback and chip-mapping validation without adding hot-path
checks

Resolves INT-8837
…#3042)

- store finalized field expression programs directly in executors
- keep range buses and carry checks in the air-only wrapper
- remove fake range buses from interpreter, preflight, hybrid, and rvr
execution

Resolves INT-8194
- build rvr ecc setup programs from the configured curve
- reject guest-provided modulus or coefficient values that do not match
- cache each curve program and cover incorrect P-256 setup values

Resolves INT-8836
- Adds a state-owned way to rebuild sparse H2D transfer metadata after
untracked execution.
- Reuses the optimized nonzero-byte scan in touched-page reconstruction
and CPU Merkle initialization.
- Moves RV64I, RV64M, and RISC-V I/O instruction lifting, CFG behavior,
and C emission into the RISC-V RVR extension.
- Defines target-neutral RVR variables, control-flow effects,
instruction emission, runtime hooks, and extension registration.
- Resolves instruction handlers during artifact generation and emits
direct C calls.
- Adds bounded main-memory page accounting and buffer draining for
variable-size extension accesses.
- Adds coverage for lifting, CFG analysis, generated code, metering, and
RISC-V integration.

Resolves INT-7700

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- Metered segmentation checks used to scan every AIR to recompute
proving-memory and interaction totals, even when many AIR trace heights
are fixed.
- The segmentation context now preaggregates contributions from
fixed-height AIRs once and scans only variable AIRs at each checkpoint;
fixed heights are preserved when starting the next segment.
- Metered RVR artifacts now record and validate their AIR count,
executor-to-AIR mapping, cost widths, and C/Rust metering layout before
execution. Generated tracing also buffers large memory ranges for
checkpoint expansion and adds explicit range and buffer-capacity checks.

Resolves INT-8833
…3064)

## Problem

  The `MeteredSegment` checkpoint codegen emits

  ```c
  if (unlikely(checkpoint.suspend_signal)) { ... }
```

  suspend_signal is a uint32_t (c/block/openvm_block_metered_segment.h) but
  unlikely() takes an int (c/openvm_util.h), so this passes an unsigned
  argument to a signed parameter. Under the Makefile's -Weverything -Werror
  that's a hard error:

```
  error: implicit conversion changes signedness: 'uint32_t' to 'int'
         [-Werror,-Wsign-conversion]
```

  Every other unlikely(...) call site passes a comparison (already int), so
  this is the only spot that trips it.
- BLS12-381 pairing witnesses previously used `blstrs` through an unsafe
assumption that its Fp12 memory layout matched Halo2curves.
- Witness exponentiation now uses public `blst` Fp12 operations and
converts field elements through canonical encodings, so it does not
depend on either library's private memory layout.
- Tests compare the `blst` path with the existing implementation for
zero, one, and random Fp12 inputs.

[Benchmark
comparison](https://github.com/openvm-org/rvr-openvm/actions/runs/29871734886)
Sanitizers landed default-on in #3065 and slowed generated-code
execution
)

This PR updated `PersistentBoundaryAir` to combine both initial and
final states. This is done by storing initial and final values. On top
of it, there is new addition of is_valid and is_dirty flags. This PR
focuses on updating PersistentBoundaryAir columns.

The second PR properly adds is_dirty flag
(#3055). In this PR, for
development purposes, it is set to one.

towards INT-8828

---------

Co-authored-by: Ayush Shukla <ayush@axiom.xyz>
This PR adds leaf level dirty bit tracking and modifies MemoryMerkleAir
to only have final rows for dirty nodes.

The updated design tracks written blocks during preflight mode and
derives dirty leaves from that knowledge. Depending on the leaf
dirtiness, its parent is also considered dirty (and parent's parent
too). By observing that only dirty nodes would have different final
state at the end of the segment (w.r.t. the start of the segment), we
only store dirty final rows for MemoryMerkleAir.

Consequently, by removing non-dirty final rows, we need to balance out
the number of interactions that the removed non-dirty rows did. This is
done by repurposing `direction_different` columns as `child_mode`, a
column that acts as `direction_different` for final rows and interaction
adjustment for initial rows.

In addition, as MerkleMemoryAir trace is no longer a fixed two rows per
node, during GPU trace generation the device can't place the row by the
static index. To mitigate it, we first compute the total height
beforehand and actually compute were the row would go during tracegen.
Both CPU and GPU tracegen are stored in interleaved fashion where first
is final row and then initial row per node, except for the root where
the first one is initial and second one is final. Not sure if it was
originally intended to be in this format, maybe would be better to have
initial and then final row? Might be later pr

This is continuation of PersistentBoundaryAir update PR
(#3051). The next step, metered
mode tracking is done in the follow up PR
(#3061).

towards INT-8828

---------

Co-authored-by: Ayush Shukla <ayush@axiom.xyz>
- Replaces the guest `memcpy` and `memset` assembly with Rust
implementations optimized for OpenVM memory access costs.
- Adds `memmove`, `memcmp`, and `bcmp` through the dedicated
`openvm-mem` crate.
- Adds host property tests and an RV64 guest integration test for
lengths, offsets, overlap, and comparison ordering.

Supersedes #3071

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Summary

- Derives the memory-bus receive timestamp from each access timestamp
and its range-checked decomposition.
- Removes one trace column and one equality constraint from every memory
access, reducing `MemoryReadAuxCols` from 3 to 2 columns and
`MemoryWriteAuxCols` from 7 to 6.
- Keeps the maximum interaction degree unchanged and updates the Rust
and CUDA trace layouts.
- Renames `LessThanAuxCols::lower_decomp` to `decomp` to describe its
use in both less-than sub-AIRs.

Resolves INT-8882
- BN254 pairing witnesses previously evaluated four large Fp12
exponentiations with Halo2curves.
- The RVR pairing FFI now evaluates the same signed-NAF exponentiations
through the public `mcl_rust` API while keeping the witness algorithm
and raw Miller loop unchanged.
- OpenVM uses MCL through Cargo and no longer carries a pairing-specific
MCL submodule or C++ adapter. The dependency is pinned to a fork branch
configured without MCL's test-only GMP support.
- The implementation initializes MCL's Ethereum BN254 preset and
verifies the 32-byte base-field serialization used by OpenVM.
- OpenVM obtains the static library's native C++ link requirements from
rustc and forwards them to the generated RVR shared-library link.
- Tests compare MCL with Halo2curves for every production exponent, the
complete pairing hint, each root-selection branch, malformed NAF input,
and zero-input error handling.

[Benchmark
comparison](https://github.com/openvm-org/rvr-openvm/actions/runs/29878536875)
Depends on
herumi/mcl-rust#11 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-benchmark triggers benchmark workflows on the pr run-benchmark-e2e run-sdk-tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants