Skip to content

feat(v2.1-rv64): integrate riscv64im-unknown-openvm-elf target into build system#2765

Merged
shuklaayush merged 20 commits into
develop-v2.1.0-rv64from
rv64-openvm-target-integration
May 21, 2026
Merged

feat(v2.1-rv64): integrate riscv64im-unknown-openvm-elf target into build system#2765
shuklaayush merged 20 commits into
develop-v2.1.0-rv64from
rv64-openvm-target-integration

Conversation

@Tuanlinh12312
Copy link
Copy Markdown
Contributor

@Tuanlinh12312 Tuanlinh12312 commented Apr 30, 2026

Summary

Integrate the built-in Rust target riscv64im-unknown-openvm-elf (target_os = "openvm") into the OpenVM build system and guest crates, enabling std support for guest programs.

The target itself was added to the OpenVM Rust fork in openvm-org/rust#3. Prebuilt toolchain tarballs are published on the fork's Releases page — install via cargo openvm toolchain install (this PR) or the CI helper script.

Changes

  • Build system (crates/toolchain/build/src/lib.rs):

    • Set DEFAULT_RUSTC_TARGET = "riscv64im-unknown-openvm-elf" (overridable via OPENVM_TARGET).
    • Set DEFAULT_RUSTUP_TOOLCHAIN_NAME = "openvm-nightly-2026-01-18" (overridable via OPENVM_RUST_TOOLCHAIN); guest builds invoke cargo +<toolchain> build --target <target> directly, using the prebuilt std/core/alloc/panic_abort rlibs that ship in the toolchain.
    • Add ensure_openvm_toolchain_linked() — verifies the linked toolchain has the guest target's prebuilt rlibs in its sysroot before invoking cargo.
  • Guest runtime (crates/toolchain/openvm/):

    • New pal_abi.rs: provides sys_write, sys_panic, sys_rand, sys_halt, sys_argc/sys_argv, sys_getenv, sys_read, and sys_alloc_aligned symbols that the upstream std PAL declares. Bridges to OpenVM's custom RISC-V instructions via openvm_riscv_guest. sys_rand uses a byte-level ABI (*mut u8, nbytes: usize) with a hint_buffer_bytes helper that handles non-dword-aligned tails via a stack scratch buffer; sys_getenv returns usize::MAX per the upstream "unset" contract.
    • Simplify the entry! macro: a single cfg(openvm_intrinsics) branch emits #[no_mangle] fn main() for both std and no_std guests. The custom _start defined via global_asm! (now using asm_const for STACK_TOP) replaces std's default entry.
  • Platform (crates/toolchain/platform/):

    • Update target_os = "none" cfg gates to any(target_os = "none", target_os = "openvm") so target-gated deps (e.g. libm) resolve under either form.
    • Change fileno constants from u32 to i32 to match POSIX RawFd / the PAL's sys_write(fd: i32, …) signature. Drop unused JOURNAL fd.
  • CLI (crates/cli/src/commands/toolchain.rs):

    • New cargo openvm toolchain {install, uninstall, list} subcommand. install downloads the prebuilt tarball from openvm-org/rust Releases for the host triple, extracts it under ~/.openvm/toolchains/, and runs rustup toolchain link. Supports --version <tag> and --force; idempotent re-installs skip the relink via an is_link_to check.
  • CI helper (ci/install-openvm-toolchain.sh):

    • Bash equivalent of cargo openvm toolchain install for repo workflows. Linux/macOS host detection, idempotent installs (skips the download when the target's rlib dir already exists). Wired into the extension-tests, continuations, sdk.cuda, guest-lib-tests, riscv, and cli workflows.
  • Guest programs (~100 files):

    • Update no_main cfg gate to cfg_attr(any(target_os = "none", target_os = "openvm"), no_main).
    • Update 45 guest Cargo.tomls' unexpected_cfgs check-cfg to include cfg(openvm_intrinsics) and cfg(target_os, values("openvm")) so stable-rustc host checks recognise the new target_os value.
  • riscv-guest (extensions/riscv/guest/src/io.rs):

    • New hint_buffer_bytes(*mut u8, nbytes) — byte-granular wrapper over the dword-granular hint_buffer_chunked, with a stack scratch dword for the trailing 0..8 bytes.
    • Mark hint_buffer_chunked unsafe fn (raw pointer write) and add SAFETY blocks at the three callers in crates/toolchain/openvm/src/io/{mod,read}.rs; existing callers in moduli-macros codegen and the pairing libs were already inside unsafe blocks.

Test plan

Install the toolchain

cargo openvm toolchain install         # or `bash ci/install-openvm-toolchain.sh`

This downloads rust-toolchain-<host>.tar.gz from the openvm-org/rust release and links it as openvm-nightly-2026-01-18.

Test results

  • cargo nextest run -p openvm-riscv-integration-tests --run-ignored all — local pass.

  • Guest builds: cargo openvm build on examples/algebra (moduli-macros path) and examples/pairing (direct hint_buffer_chunked callers) compile clean against the linked toolchain.

  • CI: all guest-touching workflows (extension-tests, continuations, sdk.cuda, guest-lib-tests, riscv, cli) install the toolchain via the script and pass on this branch.

  • Benchmarks show no regression.

  • Toolchain install path works end-to-end via both the CLI and the bash helper

  • Guest builds succeed with cargo openvm build

  • CI passes (all non-CUDA-gated checks)

🤖 Generated with Claude Code

Resolves INT-7635, INT-7654

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Comment thread crates/toolchain/openvm/src/pal_abi.rs
@Tuanlinh12312 Tuanlinh12312 force-pushed the rv64-openvm-target-integration branch from aafca0a to e8e2e8f Compare April 30, 2026 22:18
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@shuklaayush shuklaayush force-pushed the develop-v2.1.0-rv64 branch 2 times, most recently from d6e8a8d to 9a37c32 Compare May 13, 2026 13:34
@shuklaayush shuklaayush force-pushed the rv64-openvm-target-integration branch from e8e2e8f to 0958ffc Compare May 13, 2026 14:45
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@shuklaayush shuklaayush force-pushed the rv64-openvm-target-integration branch from a8a77ba to 3ae8395 Compare May 13, 2026 19:55
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@shuklaayush shuklaayush force-pushed the rv64-openvm-target-integration branch from 3ae8395 to d9d95dc Compare May 13, 2026 20:26
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@shuklaayush shuklaayush force-pushed the rv64-openvm-target-integration branch from d9d95dc to 235f041 Compare May 13, 2026 20:41
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@shuklaayush shuklaayush force-pushed the rv64-openvm-target-integration branch from 235f041 to de4ffe3 Compare May 13, 2026 20:58
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@shuklaayush shuklaayush force-pushed the rv64-openvm-target-integration branch from de4ffe3 to 397f469 Compare May 13, 2026 21:12
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@shuklaayush shuklaayush force-pushed the rv64-openvm-target-integration branch from 397f469 to 19d5a2c Compare May 14, 2026 05:19
@github-actions

This comment has been minimized.

Comment thread crates/toolchain/openvm/src/pal_abi.rs
Comment thread crates/cli/src/commands/toolchain.rs
Comment thread crates/toolchain/openvm/src/lib.rs
Comment thread AGENTS.md
Copy link
Copy Markdown
Contributor

@jonathanpwang jonathanpwang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commented on some guest UX issue.

Other than that, the main changes LGTM. However, I think we should add a separate parallel CI to test the full CLI against the riscv64im-unknown-none-elf target to make sure things still work smoothly there. It doesn't need to be the main recommended path, but I'd still like to make sure using this non-custom target has good support.

Make `cargo openvm build` smoothly switch between the openvm-fork's custom
`riscv64im-unknown-openvm-elf` target and the upstream tier-3
`riscv64im-unknown-none-elf` target via env var overrides.

- `target_has_prebuilt_std()`: new helper that checks
  `<rustc parent>/../lib/rustlib/<target>/lib/` to decide whether the
  toolchain ships prebuilt rlibs for the configured target. The openvm-fork
  ships them; the upstream tier-3 none target does not (per rust-lang.org
  platform-support docs).
- `cargo_command()`: when prebuilts are missing, automatically pass
  `-Z build-std=core,alloc,panic_abort` plus
  `-Z build-std-features=compiler-builtins-mem` so cargo builds std from
  source via `rust-src`. This is the existing flow restored, but now
  scoped only to targets that need it.
- `ensure_openvm_toolchain_linked()`: missing prebuilts is no longer
  fatal — it's now the `-Z build-std` path. The function instead checks
  for the `rust-src` component (`lib/rustlib/src/rust/library`) and prints
  install hints that branch on `is_openvm_toolchain(toolchain)`:
    * openvm-fork toolchain missing rlibs → `cargo openvm toolchain install --force`
    * upstream toolchain missing rust-src → `rustup component add rust-src --toolchain <X>`
- `is_openvm_toolchain()`: new helper exposing the `openvm-` name-prefix
  check used by the install-hint branching, also reusable from other
  callers that need to distinguish the two flows.
- Renames the env var consts and reorgs the file head: all `const`s
  (public then private) grouped at the top, then helpers, then the rest.
- AGENTS.md: env var rename `OPENVM_TARGET` → `OPENVM_RUSTC_TARGET`,
  with new wording covering the prebuilt-rlibs-OR-rust-src requirement.
- CLI post-install hint now shows the `RUSTFLAGS="-Cpasses=lower-atomic"`
  envelope for the direct `cargo +<toolchain> build` invocation.

CI:
- New `.github/workflows/cli.yml` job `app-level-cli-none-elf` runs
  parallel to the main `app-level-cli`. Installs stock
  `nightly-2026-01-18` + `rust-src`, sets
  `OPENVM_RUST_TOOLCHAIN=nightly-2026-01-18`,
  `OPENVM_RUSTC_TARGET=riscv64im-unknown-none-elf`, then runs
  `cargo openvm init` + `build` + `run` to keep the upstream-target path
  exercised. Doesn't run the full extension test matrix because some
  deps (alloy etc.) won't compile against tier-3 / max_atomic_width=0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

This comment was marked as outdated.

Use openvm-1.94.0 as the default guest Rust toolchain in openvm-build, cargo openvm toolchain install, and the CI install helper.

Update docs to describe the default as a stable OpenVM release, and clarify that users can install other published toolchain tags, including selected nightly variants, with cargo openvm toolchain install --version <tag>.
@github-actions

This comment was marked as outdated.

@socket-security
Copy link
Copy Markdown

socket-security Bot commented May 21, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcargo/​tar@​0.4.4510010093100100
Addedcargo/​indicatif@​0.17.1110010094100100

View full report

shuklaayush and others added 2 commits May 21, 2026 21:53
Previous run failed at runtime with
  VM error: phantom error: EndOfInputStream
because the cargo-openvm-init template's `pub fn main()` calls
`openvm::io::read::<u64>()` as its first instruction, and the smoke
job was invoking `cargo openvm run` with no input. The hint stream
was empty, so the very first read hit end-of-stream and the VM
errored out.

Pass `fib_10_input="0x010A00000000000000"` — the `0x01` "raw bytes"
prefix followed by 8 little-endian bytes for `n = 10u64`. That's the
same input I verified locally; the guest computes fibonacci(10) = 55
and reveals it. Build steps were already succeeding; this only
affects runtime.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A handful of small fixes scattered across the PR that fell out of the
default-flip and the smoke-test investigation:

- `crates/toolchain/build/src/lib.rs`: when the configured toolchain
  is an upstream (non-`openvm-`-prefixed) channel and the target has
  no prebuilt rlibs, explicitly check that the toolchain reports as
  nightly before claiming the build can proceed. `-Z build-std` is
  nightly-only; on stable we'd silently emit it and fail at the
  cargo level. The new error message points the user at
  `rustup toolchain install nightly --component rust-src` with a
  worked `OPENVM_RUST_TOOLCHAIN=nightly` invocation. Also tighten
  the openvm-toolchain-missing-prebuilts branch — that case is
  always a broken/truncated tarball so the only useful hint is
  `cargo openvm toolchain install --force`.
- `crates/toolchain/openvm/src/pal_abi.rs`: tidy a few user-visible
  strings. Drop the noisy "sys_argc is todo; returning 0" println
  that fires on every std program startup. Reword
  "sys_X is todo" → "unsupported OpenVM syscall: sys_X" so the
  guest log makes sense when a real program hits these. Replace one
  `write!(... "\n")` with `writeln!(...)`.
- `extensions/riscv/guest/src/io.rs`: in the `hint_buffer_chunked` /
  `hint_buffer_bytes` helpers (both already `unsafe fn`), use
  `ptr.add(...)` instead of `wrapping_add(...)` since the offsets
  are guaranteed in-bounds by the caller's safety contract. The
  non-wrapping variant gives stronger UB guarantees + slightly
  better optimization.
- `extensions/riscv/tests/programs/examples/std_collections.rs`:
  align the `#![cfg_attr(... no_main)]` gate with what
  `cargo openvm init` now generates
  (`all(not(feature = "std"), any(openvm_intrinsics, target_os = "openvm"))`).
- `docs/.../quickstart.mdx` + `docs/.../writing-a-program.mdx`:
  update the example `no_main` cfg_attr lines to the same form
  (matching the init template + the test programs). Tighten the
  prose around "openvm guest" vs "no_main openvm guest".
- `AGENTS.md`: tighten the `OPENVM_RUSTC_TARGET` env var description
  to spell out that an upstream toolchain without prebuilts must be
  nightly + have `rust-src`.

`cargo +nightly fmt` and `cargo check -p openvm-build -p openvm`
clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

This comment was marked as outdated.

@github-actions
Copy link
Copy Markdown

group app.proof_time_ms app.cycles leaf.proof_time_ms
fibonacci 1,866 4,000,051 514
keccak 13,624 14,365,133 2,232
sha2_bench 9,524 11,167,961 1,411
regex 1,558 4,090,656 356
ecrecover 604 112,210 266
pairing 742 592,827 257
kitchen_sink 1,878 1,979,971 406

Note: cells_used metrics omitted because CUDA tracegen does not expose unpadded trace heights.

Commit: 95590ce

Benchmark Workflow

…p run

Two debuggability fixes:

- `.github/workflows/cli.yml`: `cargo openvm build` invokes an inner
  `cargo build` under a `tty_println`-style wrapper that writes its
  output to `/dev/tty` (or the `OPENVM_GUEST_LOGFILE` env var if set).
  In GHA the wrapper has no tty, so previous build failures dropped
  the inner cargo output on the floor and showed nothing actionable
  in the workflow log. Set `OPENVM_GUEST_LOGFILE` to a file and `cat`
  it on `cargo openvm build` failure so the inner errors are visible.
- `crates/toolchain/build/src/lib.rs`: switch the nightly-check
  invocation from the `rustup +<toolchain>` proxy shorthand to the
  explicit `rustup run <toolchain> rustc --version` form. Output
  format is identical, so the `contains("nightly")` check still
  works; the explicit form is more conventional and avoids any
  argument-parsing ambiguity if `<toolchain>` ever contains an
  unusual character.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 21, 2026

Claude finished @shuklaayush's task in 3m 3s —— View job


Code Review: PR #2765 — Integrate riscv64im-unknown-openvm-elf target

  • Read and analyze core build system changes (crates/toolchain/build/src/lib.rs)
  • Review CLI toolchain command (crates/cli/src/commands/toolchain.rs)
  • Review PAL ABI implementation (crates/toolchain/openvm/src/pal_abi.rs)
  • Review guest runtime changes (crates/toolchain/openvm/src/lib.rs, io/mod.rs, io/read.rs)
  • Review platform changes (crates/toolchain/platform/)
  • Review CI helper script (ci/install-openvm-toolchain.sh)
  • Review guest program updates (cfg gates, Cargo.toml changes)
  • Review AGENTS.md and documentation updates
  • Check for security issues, correctness, and edge cases
  • Post final review

Overall this is a well-structured PR with clean separation of concerns. The build system changes are solid — the ensure_openvm_toolchain_linked() flow provides excellent error messages, the fallback to -Z build-std for non-openvm targets is a smart design, and the hint_buffer_bytes addition properly handles the trailing-byte edge case. Benchmarks show no regression.

Below are the issues I found, organized by severity.


Issues

1. export-pal-abi feature referenced but not defined (bug)

crates/toolchain/platform/Cargo.toml:25 has:

[package.metadata.docs.rs]
features = ["rust-runtime", "panic-handler", "export-libm", "export-pal-abi"]

But export-pal-abi is not defined in [features]. This will cause cargo doc --features export-pal-abi (and docs.rs builds) to fail. Either define the feature or remove it from the docs.rs metadata.

Fix this →

2. sys_write terminates without returning ! (correctness)

crates/toolchain/openvm/src/pal_abi.rs:76-86:

pub unsafe extern "C" fn sys_write(fd: i32, write_ptr: *const u8, nbytes: usize) {
    if fd == STDOUT || fd == STDERR {
        raw_print_str_from_bytes(write_ptr, nbytes);
    } else {
        // ...
        terminate::<{ exit_code::UNIMP }>();
        // falls through after terminate — UB if terminate doesn't halt
    }
}

The else branch calls terminate but the function returns (), not !. If a future refactor makes terminate not actually diverge (e.g. in host mode), this silently returns without writing. Consider adding unreachable!() after terminate in the else branch for safety parity with sys_read and sys_argv, which both have it.

3. sys_rand hint count may over-request (minor)

crates/toolchain/openvm/src/pal_abi.rs:36-38:

pub unsafe extern "C" fn sys_rand(recv_buf: *mut u8, nbytes: usize) {
    hint_random(nbytes.div_ceil(HINT_WORD_BYTES));
    hint_buffer_bytes(recv_buf, nbytes);
}

This requests ceil(nbytes / 8) random dwords from the host, then reads exactly nbytes from the hint stream. The hint_buffer_bytes correctly handles the trailing partial dword using a scratch buffer. However, when nbytes == 0, hint_random(0) is called followed by hint_buffer_bytes(ptr, 0) which returns early. This is technically fine (no-op), but you may want a guard for clarity if the host's HintRandom phantom instruction has any side effects on a zero-length request.

4. CI script: \b word-boundary may not work in all grep versions (robustness)

ci/install-openvm-toolchain.sh:19:

if [ -d "$DIR/lib/rustlib/riscv64im-unknown-openvm-elf/lib" ] && rustup toolchain list | grep -q "^${TAG}\b";

\b is a GNU grep extension (PCRE-like). On macOS with BSD grep (or minimal Alpine containers), this may not match correctly. Consider using grep -q "^${TAG} " or grep -qE "^${TAG}( |$)" for portability.

Fix this →

5. toolchains_root() uses HOME only — no Windows support (limitation, acceptable)

crates/cli/src/commands/toolchain.rs:179-182:

fn toolchains_root() -> Result<PathBuf> {
    let home = env::var_os("HOME").ok_or_else(|| eyre::eyre!("HOME environment variable is not set"))?;
    Ok(PathBuf::from(home).join(".openvm").join("toolchains"))
}

SUPPORTED_HOSTS already excludes Windows, so this is fine for now, but if Windows support is ever added, this would need USERPROFILE or dirs::home_dir().

6. HTTP download doesn't verify integrity (security consideration)

crates/cli/src/commands/toolchain.rs:193-224: The download_with_progress function downloads a tarball over HTTPS and extracts it without checksum verification. HTTPS provides transport security, but doesn't protect against a compromised GitHub release. For a toolchain installer, adding SHA-256 verification (e.g. downloading a .sha256 sidecar and comparing) would be defense-in-depth. Not a blocker, but worth tracking.


Observations (non-blocking)

Well done:

  • The ensure_openvm_toolchain_linked function (build/src/lib.rs:548-644) has excellent progressive error messages — distinguishing between openvm toolchains missing prebuilt rlibs vs. upstream toolchains needing nightly + rust-src. This is great developer UX.
  • The asm_const migration for _start (lib.rs:129-132) is a clean improvement — eliminates the static indirection and linker relocation.
  • The hint_buffer_bytes implementation in riscv-guest (extensions/riscv/guest/src/io.rs:61-80) correctly handles the trailing-byte edge case with a stack scratch dword.
  • The none-elf smoke test CI job (cli.yml:108-159) directly addresses jonathanpwang's review request for parallel CI testing of the upstream tier-3 target path.
  • The entry! macro simplification in lib.rs:76-98 cleanly handles the std/no_std split.

cfg gate consistency: All ~100 guest programs consistently use the same pattern:

#![cfg_attr(all(not(feature = "std"), any(openvm_intrinsics, target_os = "openvm")), no_main)]
#![cfg_attr(not(feature = "std"), no_std)]

This is correct and the AGENTS.md documentation explains the rationale well. I agree with jonathanpwang's comment that this is verbose for users, but that's a follow-up UX concern.

fileno type change from u32 to i32: This correctly matches the POSIX RawFd type and the PAL's sys_write(fd: i32, ...) signature. The removed JOURNAL fd had no remaining callers.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 21, 2026

Claude finished @shuklaayush's task in 3m 3s —— View job


Code Review: PR #2765 — Integrate riscv64im-unknown-openvm-elf target

  • Gather context and understand the full diff
  • Review build system changes (crates/toolchain/build/src/lib.rs)
  • Review CLI toolchain command (crates/cli/src/commands/toolchain.rs)
  • Review PAL ABI implementation (crates/toolchain/openvm/src/pal_abi.rs)
  • Review guest runtime changes (crates/toolchain/openvm/src/lib.rs, io modules)
  • Review platform changes (crates/toolchain/platform/)
  • Review CI helper script (ci/install-openvm-toolchain.sh)
  • Review guest program cfg gate updates
  • Review documentation and AGENTS.md updates
  • Post final review

Overall this is a well-structured, well-motivated PR. The architecture is sound — the built-in target eliminates the custom JSON spec + -Z unstable-options + -Z build-std requirements for the default path, while the fallback for upstream riscv64im-unknown-none-elf is seamlessly handled by target_has_prebuilt_std(). The ensure_openvm_toolchain_linked() function provides excellent progressive error messages. Benchmarks show no regression.

The previous Claude review's issue #1 (export-pal-abi not defined) has already been fixed in commit 95590ce. Below are the remaining issues I found.


Issues

1. terminate() does not return ! — callers that don't add unreachable!() silently fall through (soundness)

crates/toolchain/platform/src/rust_rt.rs:18-31: terminate() has return type (), not !. Its #[cfg(not(...))] arm calls unimplemented!() (which diverges), but the #[cfg(any(openvm_intrinsics, ...))] arm emits a custom instruction and returns normally from the compiler's perspective — the function signature doesn't guarantee divergence.

In pal_abi.rs, most functions that call terminate() correctly follow it with unreachable!(), but this is a fragile pattern. If anyone adds a new caller and forgets the unreachable!(), the compiler won't catch it since terminate() returns ().

Recommendation: Change terminate() to return ! and add unreachable!() (or core::hint::unreachable_unchecked()) inside it. This would make the divergence a compile-time guarantee rather than a convention. Not a blocker for this PR since existing callers are correct, but worth tracking.

2. sys_rand calls hint_random before checking nbytes == 0 (minor, already addressed)

Good catch in pal_abi.rs:37-39 — the early return for nbytes == 0 was correctly added. This is clean.

3. build-std for non-openvm targets doesn't include std (correctness, by design)

crates/toolchain/build/src/lib.rs:304: For upstream tier-3 targets the fallback uses -Z build-std=core,alloc,panic_abort. This is correct for no_std guests, but means --features std on a riscv64im-unknown-none-elf target would fail to link. This is by-design (target_os = "none" can't link std), but might be confusing to users who try OPENVM_RUSTC_TARGET=riscv64im-unknown-none-elf with --features std. Consider a note in the AGENTS.md or error message in ensure_openvm_toolchain_linked for this case. Non-blocking.

4. Hardcoded target in post-install hint (nit)

crates/cli/src/commands/toolchain.rs:289:

println!("      cargo +{tag} build --target riscv64im-unknown-openvm-elf");

This hardcodes the target string. If DEFAULT_RUSTC_TARGET changes, this hint won't update. Consider using the constant:

println!("      cargo +{tag} build --target {}", openvm_build::DEFAULT_RUSTC_TARGET);

Fix this →

5. hint_buffer_chunked callers in io/mod.rs are in unsafe block but lack SAFETY comment at one callsite

crates/toolchain/openvm/src/io/mod.rs:80: The hint_buffer_chunked call has a SAFETY comment at the top of the block (line 79). But io/read.rs:37 and io/read.rs:61 also have proper SAFETY comments, so this is consistent. Good.

However, the callers in extensions/algebra/moduli-macros/src/lib.rs:894,920 and guest-libs/pairing/src/bls12_381/pairing.rs:304 / guest-libs/pairing/src/bn254/pairing.rs:338 do not have inline SAFETY comments — they rely on being inside a broader unsafe block. This is pre-existing and out of scope for this PR, but worth noting.


Observations (positive, non-blocking)

Build system design — The target_has_prebuilt_std() check (build/src/lib.rs:59-65) is a clean heuristic: if the sysroot has rlibs for the target, skip -Z build-std; otherwise fall back to building from rust-src. This makes switching between riscv64im-unknown-openvm-elf (prebuilt) and riscv64im-unknown-none-elf (build-std) seamless via env vars.

ensure_openvm_toolchain_linked (build/src/lib.rs:548-644) — Excellent progressive error messages that distinguish:

  1. Toolchain not installed → suggests cargo openvm toolchain install or rustup toolchain install
  2. OpenVM toolchain missing rlibs → suggests --force reinstall
  3. Non-nightly upstream toolchain → explains -Z build-std requires nightly
  4. Missing rust-src component → suggests rustup component add

This is great developer UX.

asm_const migration (lib.rs:129-132) — Clean improvement: eliminates the static STACK_TOP indirection, saving a data section entry and a load instruction. The asm_const feature was stabilized in Rust 1.82, so it's safe to use with 1.91.1+.

hint_buffer_bytes (extensions/riscv/guest/src/io.rs:61-80) — Correctly handles the trailing-byte edge case with a stack scratch dword and copy_nonoverlapping. The nbytes == 0 early return is clean.

entry! macro simplification (lib.rs:76-98) — The two-branch macro (std vs no_std with intrinsics) is cleaner than before. The no_std variant generates a #[no_mangle] fn main() wrapper, while the std variant is a no-op since Rust's own runtime handles main. Correct.

CI: none-elf smoke test (.github/workflows/cli.yml:108-159) — The parallel CI job for riscv64im-unknown-none-elf directly addresses jonathanpwang's review request and ensures the upstream tier-3 target path stays tested.

cfg gate consistency — All ~100 guest programs consistently use:

#![cfg_attr(all(not(feature = "std"), any(openvm_intrinsics, target_os = "openvm")), no_main)]
#![cfg_attr(not(feature = "std"), no_std)]

The AGENTS.md documentation explains the rationale well with the two-predicate model.

fileno type changeu32 → i32 correctly matches POSIX RawFd and the PAL's sys_write(fd: i32, ...) signature. The removed JOURNAL fd has no remaining callers in the codebase.

Deleted target JSON spec — Removing riscv64im-openvm-none-elf.json and the RUST_TARGET_PATH env var is correct since the target is now built into the toolchain.


Summary

The PR is well-executed with solid error handling and developer UX. The main substantive comment is about terminate() not having a divergent return type (issue #1), which is pre-existing but made more visible by the PAL ABI additions. The hardcoded target string (issue #4) is a trivial fix. Everything else looks correct and well-tested.

@github-actions
Copy link
Copy Markdown

group app.proof_time_ms app.cycles leaf.proof_time_ms
fibonacci 1,926 4,000,051 529
keccak 13,540 14,365,133 2,210
sha2_bench 9,478 11,167,961 1,415
regex 1,575 4,090,656 357
ecrecover 602 112,210 268
pairing 750 592,827 259
kitchen_sink 1,868 1,979,971 408

Note: cells_used metrics omitted because CUDA tracegen does not expose unpadded trace heights.

Commit: 8122b5c

Benchmark Workflow

Comment thread docs/vocs/docs/pages/book/writing-apps/writing-a-program.mdx
Copy link
Copy Markdown
Contributor

@jonathanpwang jonathanpwang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, the is_openvm build is handled nicely

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 21, 2026

Claude finished @shuklaayush's task in 2m 48s —— View job


Code Review: PR #2765 — Integrate riscv64im-unknown-openvm-elf target

  • Gather context and review the full diff
  • Review build system changes (crates/toolchain/build/src/lib.rs)
  • Review CLI toolchain command (crates/cli/src/commands/toolchain.rs)
  • Review PAL ABI implementation (crates/toolchain/openvm/src/pal_abi.rs)
  • Review guest runtime changes (crates/toolchain/openvm/src/lib.rs, io/mod.rs, io/read.rs)
  • Review platform changes (crates/toolchain/platform/)
  • Review CI helper script (ci/install-openvm-toolchain.sh)
  • Review guest program cfg gate updates
  • Review riscv-guest changes (extensions/riscv/guest/src/io.rs)
  • Post final review

Overall this is a well-structured, well-motivated PR. The architecture is sound — the built-in target eliminates the custom JSON spec + -Z unstable-options + -Z build-std requirements for the default path, while the fallback for upstream riscv64im-unknown-none-elf is seamlessly handled by target_has_prebuilt_std(). The ensure_openvm_toolchain_linked() function provides excellent progressive error messages. Benchmarks show no regression. The previous Claude reviews' issue #1 (export-pal-abi) and terminate() -> ! concerns have both been addressed.

Below are the remaining issues I found, organized by severity.


Issues

1. process::exit() and process::panic() return () despite internally diverging (pre-existing, now more visible)

crates/toolchain/openvm/src/process.rs:4-11:

pub fn exit() {
    openvm_platform::rust_rt::terminate::<0>();
}
pub fn panic() {
    openvm_platform::rust_rt::terminate::<1>();
}

Now that terminate() returns !, these functions could also return !. Currently, the caller at lib.rs:114 needs a trailing unreachable!() because exit() claims to return (). Changing the signature to -> ! would be a clean follow-up since terminate() now guarantees divergence at the type level. Not blocking.

2. sys_halt and other PAL functions have dead unreachable!() after terminate() -> ! (cosmetic)

crates/toolchain/openvm/src/pal_abi.rs:26-27:

terminate::<{ exit_code::HALT }>();
unreachable!()

Since terminate() now returns !, every unreachable!() after a terminate() call is dead code. There are 6 such instances in pal_abi.rs and 1 in lib.rs:159. The compiler will optimize these away, and they serve as defensive documentation, so this is purely cosmetic — but cleaning them up would make the code slightly tighter. Non-blocking.

3. Tarball extraction doesn't strip path prefix (potential brittleness)

crates/cli/src/commands/toolchain.rs:226-235:

fn extract_tarball(archive: &Path, dest: &Path) -> Result<()> {
    // ...
    ar.unpack(dest)?;
}

Archive::unpack preserves the tarball's internal directory structure as-is. If the tarball layout ever adds a top-level directory wrapper (common in GitHub release artifacts), the extracted files would be nested one level deeper than expected, and rustup toolchain link would point to the wrong path. The CI script (ci/install-openvm-toolchain.sh:28) uses tar -xz -C "$DIR" which has the same assumption.

This works correctly today because the tarball is flat, but it's fragile. Consider adding a post-extraction sanity check (e.g., verify dest.join("bin/rustc") exists) or documenting the expected tarball layout. Non-blocking.

4. download_with_progress doesn't handle redirects explicitly (minor)

crates/cli/src/commands/toolchain.rs:193-224:

GitHub release download URLs redirect through objects.githubusercontent.com. The reqwest client follows redirects by default (up to 10), so this works. However, content_length() on the initial response may return None or the redirect page size before the final response. In practice, reqwest handles this correctly by returning content_length from the final response after following redirects, so the progress bar should be accurate. Just noting this for awareness — no action needed.

5. sys_write has inconsistent return semantics vs POSIX (by-design, document)

crates/toolchain/openvm/src/pal_abi.rs:79:

pub unsafe extern "C" fn sys_write(fd: i32, write_ptr: *const u8, nbytes: usize) {

The function returns () rather than a byte count. The upstream PAL ABI defines this as void-returning, so it matches the contract. Worth noting that a caller expecting POSIX write() semantics (returns ssize_t) would be surprised — but this is dictated by the fork's PAL spec, not this PR.


Observations (positive, non-blocking)

Build system design — The target_has_prebuilt_std() check (build/src/lib.rs:59-65) is a clean heuristic: if the sysroot has rlibs for the target, skip -Z build-std; otherwise fall back to building from rust-src. This makes switching between riscv64im-unknown-openvm-elf (prebuilt) and riscv64im-unknown-none-elf (build-std) seamless via env vars.

ensure_openvm_toolchain_linked (build/src/lib.rs:548-644) — Excellent progressive error messages that distinguish:

  1. Toolchain not installed → suggests cargo openvm toolchain install or rustup toolchain install
  2. OpenVM toolchain missing rlibs → suggests --force reinstall
  3. Non-nightly upstream toolchain → explains -Z build-std requires nightly
  4. Missing rust-src component → suggests rustup component add

This is great developer UX.

asm_const migration (lib.rs:129-132) — Clean improvement: eliminates the static STACK_TOP indirection, saving a data section entry and a load instruction. The asm_const feature was stabilized in Rust 1.82, so it's safe to use with 1.91.1+.

hint_buffer_bytes (extensions/riscv/guest/src/io.rs:61-80) — Correctly handles the trailing-byte edge case with a stack scratch dword and copy_nonoverlapping. The nbytes == 0 early return and the sys_rand's nbytes == 0 guard (pal_abi.rs:37-39) are both clean.

entry! macro simplification (lib.rs:76-98) — The two-branch macro (no_std+intrinsics vs everything else) is cleaner than the previous version. The no_std variant generates a #[no_mangle] fn main() wrapper, while the std variant is a no-op since Rust's own runtime handles main. Correct.

CI: none-elf smoke test (.github/workflows/cli.yml:108-159) — The parallel CI job for riscv64im-unknown-none-elf directly addresses jonathanpwang's review request and ensures the upstream tier-3 target path stays tested. The setup correctly uses nightly-2026-01-18 with --component rust-src.

CI script portability fix — The \b word-boundary grep issue from the first Claude review (ci/install-openvm-toolchain.sh:19) has been replaced with a POSIX-portable awk command. Good.

terminate() -> ! — The return type was changed from () to ! in this PR (rust_rt.rs:18), addressing the concern raised in the prior review. The #[cfg(any(openvm_intrinsics, target_os = "openvm"))] arm now has an explicit unreachable!() after the custom_insn_i! call, and the #[cfg(not(...))] arm calls unimplemented!() which already returns !.

cfg gate consistency — All ~100 guest programs consistently use:

#![cfg_attr(all(not(feature = "std"), any(openvm_intrinsics, target_os = "openvm")), no_main)]
#![cfg_attr(not(feature = "std"), no_std)]

The cargo openvm init template (crates/cli/templates/main.rs) and add_openvm_dependency (init.rs:188-191) both correctly emit the check-cfg declarations for openvm_intrinsics and target_os = "openvm".

fileno type changeu32 → i32 correctly matches POSIX RawFd and the PAL's sys_write(fd: i32, ...) signature. The removed JOURNAL fd has no remaining callers.

Deleted target JSON spec — Removing riscv64im-openvm-none-elf.json and the RUST_TARGET_PATH env var is correct since the target is now built into the toolchain. The removal of -Z unstable-options from encode_rust_flags is also correct since it was only needed for the custom JSON spec.

Default toolchain switched to stableDEFAULT_RUSTUP_TOOLCHAIN_NAME changed from nightly-2026-01-18 to openvm-1.94.0, matching jonathanpwang's question about why nightly was used. Since prebuilt rlibs ship with the openvm toolchain, -Z build-std (which requires nightly) is no longer needed on the default path.


Summary

The PR is well-executed with solid error handling, progressive developer UX for toolchain issues, and clean separation of the openvm-target vs. upstream-target code paths. The issues found are all non-blocking — mostly cosmetic cleanups (unreachable!() after -> !, process::exit() return type) and a defensive improvement suggestion for tarball extraction. The previous reviews' concerns have been addressed.

@github-actions
Copy link
Copy Markdown

group app.proof_time_ms app.cycles leaf.proof_time_ms
fibonacci 1,878 4,000,051 512
keccak 13,709 14,365,133 2,231
sha2_bench 9,663 11,167,961 1,455
regex 1,575 4,090,656 359
ecrecover 604 112,210 268
pairing 742 592,827 260
kitchen_sink 1,868 1,979,971 409

Note: cells_used metrics omitted because CUDA tracegen does not expose unpadded trace heights.

Commit: de73e22

Benchmark Workflow

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 21, 2026

Claude finished @shuklaayush's task in 3m 16s —— View job


Code Review: PR #2765 — Integrate riscv64im-unknown-openvm-elf target

  • Gather context and review the full diff
  • Review build system changes (crates/toolchain/build/src/lib.rs)
  • Review CLI toolchain command (crates/cli/src/commands/toolchain.rs)
  • Review PAL ABI implementation (crates/toolchain/openvm/src/pal_abi.rs)
  • Review guest runtime changes (crates/toolchain/openvm/src/lib.rs, io modules)
  • Review platform changes (crates/toolchain/platform/)
  • Review CI helper script and workflow changes
  • Review riscv-guest hint_buffer_bytes (extensions/riscv/guest/src/io.rs)
  • Review guest program cfg gate updates (sampling)
  • Post final review

This is a well-structured PR. The architecture is sound — the built-in target removes the custom JSON spec + -Z unstable-options + -Z build-std requirements for the default path, while the fallback for upstream riscv64im-unknown-none-elf is seamlessly handled by target_has_prebuilt_std(). The ensure_openvm_toolchain_linked() error messages are excellent developer UX, progressively distinguishing between missing toolchains, missing rlibs, non-nightly toolchains, and missing rust-src. Benchmarks show no regression, and the previous reviews' issues (export-pal-abi, terminate() -> !, process::exit() -> !, \b grep portability) have all been addressed.

I confirmed from the latest code that terminate() returns ! (rust_rt.rs:18), process::exit() and process::panic() return ! (process.rs:4,9), export-pal-abi was removed from docs.rs metadata (platform/Cargo.toml:25), and the CI script uses awk instead of \b (install-openvm-toolchain.sh:19).

Below are the remaining items I found.


Issues

1. sys_write silently falls through for unsupported fds if terminate return type ever regresses (minor, defensive)

crates/toolchain/openvm/src/pal_abi.rs:76-86:

pub unsafe extern "C" fn sys_write(fd: i32, write_ptr: *const u8, nbytes: usize) {
    if fd == STDOUT || fd == STDERR {
        raw_print_str_from_bytes(write_ptr, nbytes);
    } else {
        // ...
        terminate::<{ exit_code::UNIMP }>()  // returns !, but function returns ()
    }
}

terminate() now returns !, so this is correct. But sys_write's return type is (), not !, so the compiler can't enforce that the else branch diverges — if terminate's return type ever regresses (e.g., someone removes the unreachable!() inside terminate), this function would silently fall through.

Compare with sys_read (pal_abi.rs:67-70) and sys_argv (pal_abi.rs:137-146), which also return from terminate() — those functions do return !, so the compiler would catch a regression there.

Suggestion: Either make sys_write return ! (breaking the extern "C" ABI contract with std, so probably not ideal), or add a trailing unreachable!() after the else-branch terminate call for parity. Not blocking since terminate -> ! makes this safe today.

2. Tarball extraction has no post-extraction integrity check for completeness (robustness)

crates/cli/src/commands/toolchain.rs:226-253: The extract_tarball function unpacks via Archive::unpack and then calls ensure_toolchain_layout, which checks for bin/rustc and lib/rustlib/<target>/lib. This is good defensive checking. However, the download itself (download_with_progress, line 193) doesn't verify integrity (no SHA-256 checksum comparison). This is acceptable since HTTPS provides transport security, but worth tracking as a hardening opportunity for a toolchain installer. Non-blocking.

3. download_with_progress progress bar may show 0/0 when content-length is missing (cosmetic)

crates/cli/src/commands/toolchain.rs:201-202:

let total = resp.content_length().unwrap_or(0);
let pb = ProgressBar::new(total);

When content_length() returns None (e.g., chunked transfer encoding from a CDN), total is 0 and the progress bar shows 0/0 bytes. Consider using ProgressBar::new_spinner() for the unknown-length case or setting total to a reasonable estimate. Non-blocking cosmetic issue.

4. read_vec_by_len allocates but doesn't initialize before hint_buffer_chunked (pre-existing, safe)

crates/toolchain/openvm/src/io/mod.rs:75-81:

let mut bytes = Vec::with_capacity(capacity);
unsafe {
    hint_buffer_chunked(bytes.as_mut_ptr(), num_words as usize);
    bytes.set_len(len);
}

The Vec is allocated with with_capacity (uninitialized memory), then hint_buffer_chunked writes into it, then set_len is called. This is correct because hint_buffer_chunked writes all num_words * WORD_SIZE bytes before set_len(len) makes len bytes observable. The SAFETY comment on line 79 explains this. Pre-existing pattern, not introduced by this PR.


Positive observations

  • ensure_openvm_toolchain_linked (build/src/lib.rs:548-644) — Excellent progressive error messages. The four-case error flow (not installed → missing rlibs → non-nightly → missing rust-src) with actionable fix commands is top-tier DX.

  • asm_const migration (lib.rs:128-132) — Clean improvement. li sp, {STACK_TOP} with const eliminates the static STACK_TOP indirection (la + ld), removing a data section entry and a linker relocation. asm_const was stabilized in Rust 1.82, safe with 1.91.1+.

  • hint_buffer_bytes (extensions/riscv/guest/src/io.rs:61-80) — The trailing-byte edge case is handled correctly with a stack scratch dword and copy_nonoverlapping. The nbytes == 0 early return is clean.

  • entry! macro simplification (lib.rs:76-98) — The two-branch split (no_std+intrinsics emits #[no_mangle] fn main(), std variant is a no-op) is clean and correct.

  • none-elf smoke test (.github/workflows/cli.yml:108-159) — Directly addresses jonathanpwang's request for parallel CI testing of the upstream tier-3 target path. Uses nightly-2026-01-18 + --component rust-src correctly.

  • ensure_toolchain_layout (toolchain.rs:238-253) — Post-extraction sanity check for bin/rustc and the target rlib directory. Addresses the tarball-layout brittleness concern from prior reviews.

  • is_link_to (toolchain.rs:280-296) — Smart idempotency check that avoids wasteful uninstall + relink cycles.

  • cfg gate consistency — All ~100+ guest programs use the identical pattern with matching check-cfg declarations. The template (crates/cli/templates/main.rs) and cargo openvm init (init.rs:188-191) both emit the correct cfg.

  • terminate() -> ! and process::exit() -> ! — Both addressed from prior reviews, making divergence a compile-time guarantee.


Summary

The PR is well-executed. The build system design cleanly separates the openvm-target path (prebuilt rlibs) from the upstream-target fallback (-Z build-std), the toolchain installer is robust with idempotent installs and post-extraction validation, and the PAL ABI correctly bridges std's syscall layer to OpenVM's custom instructions. The issues found are all non-blocking — a defensive unreachable!() in sys_write, cosmetic progress bar handling, and future hardening with checksum verification.

@github-actions
Copy link
Copy Markdown

group app.proof_time_ms app.cycles leaf.proof_time_ms
fibonacci 1,879 4,000,051 512
keccak 13,317 14,365,133 2,173
sha2_bench 9,466 11,167,961 1,409
regex 1,564 4,090,656 360
ecrecover 594 112,210 263
pairing 736 592,827 257
kitchen_sink 1,873 1,979,971 410

Note: cells_used metrics omitted because CUDA tracegen does not expose unpadded trace heights.

Commit: f8732f6

Benchmark Workflow

@shuklaayush shuklaayush merged commit 4554ab0 into develop-v2.1.0-rv64 May 21, 2026
59 checks passed
@shuklaayush shuklaayush deleted the rv64-openvm-target-integration branch May 21, 2026 21:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants