diff --git a/.github/workflows/build-toolchain.yml b/.github/workflows/build-toolchain.yml new file mode 100644 index 0000000000000..64ff302a34ee4 --- /dev/null +++ b/.github/workflows/build-toolchain.yml @@ -0,0 +1,107 @@ +name: Build Toolchain + +on: + workflow_call: + inputs: + runner: + required: true + type: string + workflow_dispatch: + inputs: + runner: + required: true + type: string + default: "ubuntu-22.04" + +jobs: + build: + name: Build + runs-on: ${{ inputs.runner }} + timeout-minutes: 180 + env: + GITHUB_ACTIONS: "false" + CARGO_TARGET_RISCV64IM_UNKNOWN_OPENVM_ELF_RUSTFLAGS: "-Cpasses=lower-atomic" + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: "recursive" + fetch-depth: 0 + + - name: Detect host triple + id: host + run: | + # Match rustc's host detection + ARCH=$(uname -m) + OS=$(uname -s) + case "$ARCH" in + x86_64) ARCH_TRIPLE="x86_64" ;; + arm64|aarch64) ARCH_TRIPLE="aarch64" ;; + *) echo "Unsupported arch: $ARCH"; exit 1 ;; + esac + case "$OS" in + Linux) HOST="${ARCH_TRIPLE}-unknown-linux-gnu" ;; + Darwin) HOST="${ARCH_TRIPLE}-apple-darwin" ;; + *) echo "Unsupported OS: $OS"; exit 1 ;; + esac + echo "triple=$HOST" >> "$GITHUB_OUTPUT" + echo "Detected host triple: $HOST" + + - name: Free disk space (Linux) + if: runner.os == 'Linux' + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /usr/local/lib/android + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache + df -h + + - name: Install dependencies (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y build-essential python3 curl cmake ninja-build pkg-config libssl-dev + + - name: Write bootstrap.toml + run: | + cat > bootstrap.toml << 'EOF' + [build] + extended = true + tools = ["cargo", "cargo-clippy", "clippy", "rustfmt"] + cargo-native-static = true + + [rust] + lld = true + llvm-tools = true + + [llvm] + download-ci-llvm = false + EOF + + - name: Create target sanity check workaround + run: | + mkdir -p /tmp/rustc-targets + cat > /tmp/rustc-targets/riscv64im-unknown-openvm-elf.json << 'EOF' + {"arch": "riscv64", "os": "openvm", "llvm-target": "riscv64"} + EOF + + - name: Build toolchain + env: + RUST_TARGET_PATH: /tmp/rustc-targets + run: | + python3 x.py build --stage 2 compiler/rustc library \ + --target ${{ steps.host.outputs.triple }},riscv64im-unknown-openvm-elf + + - name: Package toolchain + run: | + cd build/${{ steps.host.outputs.triple }}/stage2 + tar --exclude lib/rustlib/src \ + --exclude lib/rustlib/rustc-src \ + -hczvf ../../../rust-toolchain-${{ steps.host.outputs.triple }}.tar.gz . + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: rust-toolchain-${{ steps.host.outputs.triple }} + path: rust-toolchain-${{ steps.host.outputs.triple }}.tar.gz + retention-days: 7 diff --git a/.github/workflows/release-toolchain.yml b/.github/workflows/release-toolchain.yml new file mode 100644 index 0000000000000..c439b4eb6bf43 --- /dev/null +++ b/.github/workflows/release-toolchain.yml @@ -0,0 +1,71 @@ +name: Release Toolchain + +on: + push: + tags: + - "openvm-[0-9]+.[0-9]+.[0-9]+*" + workflow_dispatch: + inputs: + tag: + description: "Tag name for release (e.g., openvm-2.1.0-rv64)" + required: true + type: string + +permissions: + contents: write + +jobs: + build-x86_64-linux: + uses: ./.github/workflows/build-toolchain.yml + with: + runner: ubuntu-22.04 + + build-aarch64-darwin: + uses: ./.github/workflows/build-toolchain.yml + with: + runner: macos-14 + + build-x86_64-darwin: + uses: ./.github/workflows/build-toolchain.yml + with: + runner: macos-13 + + release: + name: Create Release + needs: [build-x86_64-linux, build-aarch64-darwin, build-x86_64-darwin] + runs-on: ubuntu-latest + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Determine tag + id: tag + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT" + else + echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" + fi + + - name: List artifacts + run: ls -R artifacts/ + + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + mkdir tars + find artifacts -name "*.tar.gz" -exec mv {} tars/ \; + if [ "$(ls -A tars)" ]; then + gh release create "${{ steps.tag.outputs.tag }}" \ + --repo "${{ github.repository }}" \ + --title "${{ steps.tag.outputs.tag }}" \ + --notes "Pre-built Rust toolchain with riscv64im-unknown-openvm-elf target for OpenVM." \ + --draft \ + tars/* + else + echo "No artifacts found. Exiting." + exit 1 + fi diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 74cf01e77754e..0dbaaa3a3e10a 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -1715,6 +1715,7 @@ supported_targets! { ("riscv32gc-unknown-linux-gnu", riscv32gc_unknown_linux_gnu), ("riscv32gc-unknown-linux-musl", riscv32gc_unknown_linux_musl), ("riscv64im-unknown-none-elf", riscv64im_unknown_none_elf), + ("riscv64im-unknown-openvm-elf", riscv64im_unknown_openvm_elf), ("riscv64imac-unknown-none-elf", riscv64imac_unknown_none_elf), ("riscv64gc-unknown-none-elf", riscv64gc_unknown_none_elf), ("riscv64gc-unknown-linux-gnu", riscv64gc_unknown_linux_gnu), @@ -2007,6 +2008,7 @@ crate::target_spec_enum! { Nto = "nto", NuttX = "nuttx", OpenBsd = "openbsd", + Openvm = "openvm", Psp = "psp", Psx = "psx", Qurt = "qurt", diff --git a/compiler/rustc_target/src/spec/targets/riscv64im_unknown_openvm_elf.rs b/compiler/rustc_target/src/spec/targets/riscv64im_unknown_openvm_elf.rs new file mode 100644 index 0000000000000..2501a5ddfd5c5 --- /dev/null +++ b/compiler/rustc_target/src/spec/targets/riscv64im_unknown_openvm_elf.rs @@ -0,0 +1,42 @@ +use crate::spec::{ + Arch, Cc, LinkerFlavor, Lld, LlvmAbi, Os, PanicStrategy, RelocModel, Target, TargetMetadata, + TargetOptions, +}; + +pub(crate) fn target() -> Target { + Target { + data_layout: "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128".into(), + llvm_target: "riscv64".into(), + metadata: TargetMetadata { + description: Some("OpenVM zero-knowledge Virtual Machine (RV64IM ISA)".into()), + tier: Some(3), + host_tools: Some(false), + std: Some(true), + }, + pointer_width: 64, + arch: Arch::RiscV64, + + options: TargetOptions { + os: Os::Openvm, + vendor: "unknown".into(), + linker_flavor: LinkerFlavor::Gnu(Cc::No, Lld::Yes), + linker: Some("rust-lld".into()), + cpu: "generic-rv64".into(), + + // We set atomic_width to 64 for compatibility with crates such as crossbeam, + // but this should never be triggered since compilation should always lower + // atomics and be single-threaded. + max_atomic_width: Some(64), + atomic_cas: true, + + features: "+m".into(), + llvm_abiname: LlvmAbi::Lp64, + panic_strategy: PanicStrategy::Abort, + relocation_model: RelocModel::Static, + emit_debug_gdb_scripts: false, + eh_frame_header: false, + singlethread: true, + ..Default::default() + }, + } +} diff --git a/library/panic_abort/Cargo.toml b/library/panic_abort/Cargo.toml index ecf043ac7071c..781b791ba16dd 100644 --- a/library/panic_abort/Cargo.toml +++ b/library/panic_abort/Cargo.toml @@ -17,5 +17,5 @@ core = { path = "../rustc-std-workspace-core", package = "rustc-std-workspace-co [target.'cfg(target_os = "android")'.dependencies] libc = { version = "0.2", default-features = false } -[target.'cfg(any(target_os = "android", target_os = "zkvm"))'.dependencies] +[target.'cfg(any(target_os = "android", target_os = "zkvm", target_os = "openvm"))'.dependencies] alloc = { path = "../alloc" } diff --git a/library/panic_abort/src/lib.rs b/library/panic_abort/src/lib.rs index d1706b6525295..bbd28609290c9 100644 --- a/library/panic_abort/src/lib.rs +++ b/library/panic_abort/src/lib.rs @@ -19,6 +19,9 @@ mod android; #[cfg(target_os = "zkvm")] mod zkvm; +#[cfg(target_os = "openvm")] +mod openvm; + use core::any::Any; use core::panic::PanicPayload; @@ -40,6 +43,10 @@ pub unsafe fn __rust_start_panic(_payload: &mut dyn PanicPayload) -> u32 { unsafe { zkvm::zkvm_set_abort_message(_payload); } + #[cfg(target_os = "openvm")] + unsafe { + openvm::openvm_set_abort_message(_payload); + } unsafe extern "Rust" { // This is defined in std::rt. diff --git a/library/panic_abort/src/openvm.rs b/library/panic_abort/src/openvm.rs new file mode 100644 index 0000000000000..01d93ef59d9b2 --- /dev/null +++ b/library/panic_abort/src/openvm.rs @@ -0,0 +1,25 @@ +use alloc::string::String; +use core::panic::PanicPayload; + +// Forward the abort message to OpenVM's sys_panic. +pub(crate) unsafe fn openvm_set_abort_message(payload: &mut dyn PanicPayload) { + let payload = payload.get(); + let msg = match payload.downcast_ref::<&'static str>() { + Some(msg) => msg.as_bytes(), + None => match payload.downcast_ref::() { + Some(msg) => msg.as_bytes(), + None => &[], + }, + }; + if msg.is_empty() { + return; + } + + unsafe extern "C" { + fn sys_panic(msg_ptr: *const u8, len: usize) -> !; + } + + unsafe { + sys_panic(msg.as_ptr(), msg.len()); + } +} diff --git a/library/std/build.rs b/library/std/build.rs index 5f2e441bf7d83..4a63f884843cb 100644 --- a/library/std/build.rs +++ b/library/std/build.rs @@ -53,6 +53,7 @@ fn main() { || target_os == "uefi" || target_os == "teeos" || target_os == "zkvm" + || target_os == "openvm" || target_os == "rtems" || target_os == "nuttx" || target_os == "cygwin" diff --git a/library/std/src/random.rs b/library/std/src/random.rs index a18dcf98ec7fc..989a8de4fb3d5 100644 --- a/library/std/src/random.rs +++ b/library/std/src/random.rs @@ -45,6 +45,7 @@ use crate::sys::random as sys; /// VxWorks | `randABytes` after waiting for `randSecure` to become ready /// WASI | [`random_get`](https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md#-random_getbuf-pointeru8-buf_len-size---result-errno) /// ZKVM | `sys_rand` +/// OpenVM | `sys_rand` /// /// Note that the sources used might change over time. /// diff --git a/library/std/src/sys/alloc/mod.rs b/library/std/src/sys/alloc/mod.rs index f2f1d1c7feceb..037f9fce48102 100644 --- a/library/std/src/sys/alloc/mod.rs +++ b/library/std/src/sys/alloc/mod.rs @@ -107,4 +107,7 @@ cfg_select! { target_os = "zkvm" => { mod zkvm; } + target_os = "openvm" => { + mod openvm; + } } diff --git a/library/std/src/sys/alloc/openvm.rs b/library/std/src/sys/alloc/openvm.rs new file mode 100644 index 0000000000000..a600cfa2220dd --- /dev/null +++ b/library/std/src/sys/alloc/openvm.rs @@ -0,0 +1,15 @@ +use crate::alloc::{GlobalAlloc, Layout, System}; +use crate::sys::pal::abi; + +#[stable(feature = "alloc_system_type", since = "1.28.0")] +unsafe impl GlobalAlloc for System { + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + unsafe { abi::sys_alloc_aligned(layout.size(), layout.align()) } + } + + #[inline] + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { + // this allocator never deallocates memory + } +} diff --git a/library/std/src/sys/args/mod.rs b/library/std/src/sys/args/mod.rs index fd5562a520b71..5469f8054209f 100644 --- a/library/std/src/sys/args/mod.rs +++ b/library/std/src/sys/args/mod.rs @@ -53,6 +53,10 @@ cfg_select! { mod zkvm; pub use zkvm::*; } + target_os = "openvm" => { + mod openvm; + pub use openvm::*; + } _ => { mod unsupported; pub use unsupported::*; diff --git a/library/std/src/sys/args/openvm.rs b/library/std/src/sys/args/openvm.rs new file mode 100644 index 0000000000000..d26bf1eaff91f --- /dev/null +++ b/library/std/src/sys/args/openvm.rs @@ -0,0 +1,94 @@ +use crate::ffi::{OsStr, OsString}; +use crate::num::NonZero; +use crate::sync::OnceLock; +use crate::sys::pal::{WORD_SIZE, abi}; +use crate::{fmt, ptr, slice}; + +pub fn args() -> Args { + Args { iter: ARGS.get_or_init(|| get_args()).iter() } +} + +fn get_args() -> Vec<&'static OsStr> { + let argc = unsafe { abi::sys_argc() }; + let mut args = Vec::with_capacity(argc); + + for i in 0..argc { + // Get the size of the argument then the data. + let arg_len = unsafe { abi::sys_argv(ptr::null_mut(), 0, i) }; + + let arg_len_words = (arg_len + WORD_SIZE - 1) / WORD_SIZE; + let words = unsafe { abi::sys_alloc_words(arg_len_words) }; + + let arg_len2 = unsafe { abi::sys_argv(words, arg_len_words, i) }; + debug_assert_eq!(arg_len, arg_len2); + + let arg_bytes = unsafe { slice::from_raw_parts(words.cast(), arg_len) }; + args.push(unsafe { OsStr::from_encoded_bytes_unchecked(arg_bytes) }); + } + args +} + +static ARGS: OnceLock> = OnceLock::new(); + +pub struct Args { + iter: slice::Iter<'static, &'static OsStr>, +} + +impl !Send for Args {} +impl !Sync for Args {} + +impl fmt::Debug for Args { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.iter.as_slice().fmt(f) + } +} + +impl Iterator for Args { + type Item = OsString; + + fn next(&mut self) -> Option { + self.iter.next().map(|arg| arg.to_os_string()) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } + + #[inline] + fn count(self) -> usize { + self.iter.len() + } + + fn last(self) -> Option { + self.iter.last().map(|arg| arg.to_os_string()) + } + + #[inline] + fn advance_by(&mut self, n: usize) -> Result<(), NonZero> { + self.iter.advance_by(n) + } +} + +impl DoubleEndedIterator for Args { + fn next_back(&mut self) -> Option { + self.iter.next_back().map(|arg| arg.to_os_string()) + } + + #[inline] + fn advance_back_by(&mut self, n: usize) -> Result<(), NonZero> { + self.iter.advance_back_by(n) + } +} + +impl ExactSizeIterator for Args { + #[inline] + fn len(&self) -> usize { + self.iter.len() + } + + #[inline] + fn is_empty(&self) -> bool { + self.iter.is_empty() + } +} diff --git a/library/std/src/sys/env/mod.rs b/library/std/src/sys/env/mod.rs index 89856516b6dce..039dfe7afa92e 100644 --- a/library/std/src/sys/env/mod.rs +++ b/library/std/src/sys/env/mod.rs @@ -55,6 +55,10 @@ cfg_select! { mod zkvm; pub use zkvm::*; } + target_os = "openvm" => { + mod openvm; + pub use openvm::*; + } _ => { mod unsupported; pub use unsupported::*; diff --git a/library/std/src/sys/env/openvm.rs b/library/std/src/sys/env/openvm.rs new file mode 100644 index 0000000000000..665c539ed0b6e --- /dev/null +++ b/library/std/src/sys/env/openvm.rs @@ -0,0 +1,27 @@ +#[expect(dead_code)] +#[path = "unsupported.rs"] +mod unsupported_env; +pub use unsupported_env::{Env, env, setenv, unsetenv}; + +use crate::ffi::{OsStr, OsString}; +use crate::sys::pal::{WORD_SIZE, abi}; +use crate::sys::{FromInner, os_str}; + +pub fn getenv(varname: &OsStr) -> Option { + let varname = varname.as_encoded_bytes(); + let nbytes = + unsafe { abi::sys_getenv(crate::ptr::null_mut(), 0, varname.as_ptr(), varname.len()) }; + if nbytes == usize::MAX { + return None; + } + + let nwords = (nbytes + WORD_SIZE - 1) / WORD_SIZE; + let words = unsafe { abi::sys_alloc_words(nwords) }; + + let nbytes2 = unsafe { abi::sys_getenv(words, nwords, varname.as_ptr(), varname.len()) }; + debug_assert_eq!(nbytes, nbytes2); + + // Convert to OsString. + let u8s: &[u8] = unsafe { crate::slice::from_raw_parts(words.cast() as *const u8, nbytes) }; + Some(OsString::from_inner(os_str::Buf { inner: u8s.to_vec() })) +} diff --git a/library/std/src/sys/env_consts.rs b/library/std/src/sys/env_consts.rs index 573f540483b1a..26de0527604c7 100644 --- a/library/std/src/sys/env_consts.rs +++ b/library/std/src/sys/env_consts.rs @@ -411,6 +411,17 @@ pub mod os { pub const EXE_EXTENSION: &str = "elf"; } +#[cfg(target_os = "openvm")] +pub mod os { + pub const FAMILY: &str = ""; + pub const OS: &str = "openvm"; + pub const DLL_PREFIX: &str = ""; + pub const DLL_SUFFIX: &str = ".elf"; + pub const DLL_EXTENSION: &str = "elf"; + pub const EXE_SUFFIX: &str = ".elf"; + pub const EXE_EXTENSION: &str = "elf"; +} + // The fallback when none of the other gates match. #[else] pub mod os { diff --git a/library/std/src/sys/io/error/mod.rs b/library/std/src/sys/io/error/mod.rs index 4fca658a7dcaa..ad8675db7c4b4 100644 --- a/library/std/src/sys/io/error/mod.rs +++ b/library/std/src/sys/io/error/mod.rs @@ -43,6 +43,7 @@ cfg_select! { target_os = "vexos", target_family = "wasm", target_os = "zkvm", + target_os = "openvm", ) => { mod generic; pub use generic::*; diff --git a/library/std/src/sys/pal/mod.rs b/library/std/src/sys/pal/mod.rs index 88d9d42059900..240ded8d368f7 100644 --- a/library/std/src/sys/pal/mod.rs +++ b/library/std/src/sys/pal/mod.rs @@ -60,6 +60,10 @@ cfg_select! { mod zkvm; pub use self::zkvm::*; } + target_os = "openvm" => { + mod openvm; + pub use self::openvm::*; + } _ => { mod unsupported; pub use self::unsupported::*; diff --git a/library/std/src/sys/pal/openvm/abi.rs b/library/std/src/sys/pal/openvm/abi.rs new file mode 100644 index 0000000000000..491fdb3c17897 --- /dev/null +++ b/library/std/src/sys/pal/openvm/abi.rs @@ -0,0 +1,35 @@ +//! ABI definitions for symbols exported by OpenVM. + +// We provide the ABI so that the OpenVM-specific implementations can be provided +// by linking the openvm crate without introducing the crate as a dependency here +#![allow(dead_code)] + +/// Standard IO file descriptors for use with sys_read and sys_write. +pub mod fileno { + pub const STDIN: u32 = 0; + pub const STDOUT: u32 = 1; + pub const STDERR: u32 = 2; + pub const JOURNAL: u32 = 3; +} + +unsafe extern "C" { + // Wrappers around syscalls provided by OpenVM: + pub fn sys_halt(); + pub fn sys_rand(recv_buf: *mut u32, words: usize); + pub fn sys_panic(msg_ptr: *const u8, len: usize) -> !; + pub fn sys_log(msg_ptr: *const u8, len: usize); + pub fn sys_read(fd: u32, recv_buf: *mut u8, nrequested: usize) -> usize; + pub fn sys_write(fd: u32, write_buf: *const u8, nbytes: usize); + pub fn sys_getenv( + recv_buf: *mut u32, + words: usize, + varname: *const u8, + varname_len: usize, + ) -> usize; + pub fn sys_argc() -> usize; + pub fn sys_argv(out_words: *mut u32, out_nwords: usize, arg_index: usize) -> usize; + + // Allocate memory from global HEAP. + pub fn sys_alloc_words(nwords: usize) -> *mut u32; + pub fn sys_alloc_aligned(nwords: usize, align: usize) -> *mut u8; +} diff --git a/library/std/src/sys/pal/openvm/mod.rs b/library/std/src/sys/pal/openvm/mod.rs new file mode 100644 index 0000000000000..c891241ff7852 --- /dev/null +++ b/library/std/src/sys/pal/openvm/mod.rs @@ -0,0 +1,35 @@ +//! System bindings for the OpenVM platform +//! +//! This module contains the facade (aka platform-specific) implementations of +//! OS level functionality for OpenVM. +//! +//! This is all super highly experimental and not actually intended for +//! wide/production use yet, it's still all in the experimental category. This +//! will likely change over time. +#![forbid(unsafe_op_in_unsafe_fn)] + +pub const WORD_SIZE: usize = size_of::(); + +pub mod abi; + +use crate::io as std_io; + +// SAFETY: must be called only once during runtime initialization. +// NOTE: this is not guaranteed to run, for example when Rust code is called externally. +pub unsafe fn init(_argc: isize, _argv: *const *const u8, _sigpipe: u8) {} + +// SAFETY: must be called only once during runtime cleanup. +// NOTE: this is not guaranteed to run, for example when the program aborts. +pub unsafe fn cleanup() {} + +pub fn unsupported() -> std_io::Result { + Err(unsupported_err()) +} + +pub fn unsupported_err() -> std_io::Error { + std_io::Error::UNSUPPORTED_PLATFORM +} + +pub fn abort_internal() -> ! { + core::intrinsics::abort(); +} diff --git a/library/std/src/sys/random/mod.rs b/library/std/src/sys/random/mod.rs index 12346ef5a2edd..501f5539fae35 100644 --- a/library/std/src/sys/random/mod.rs +++ b/library/std/src/sys/random/mod.rs @@ -102,6 +102,10 @@ cfg_select! { mod zkvm; pub use zkvm::fill_bytes; } + target_os = "openvm" => { + mod openvm; + pub use openvm::fill_bytes; + } any( all(target_family = "wasm", target_os = "unknown"), target_os = "xous", diff --git a/library/std/src/sys/random/openvm.rs b/library/std/src/sys/random/openvm.rs new file mode 100644 index 0000000000000..4e8bef8265707 --- /dev/null +++ b/library/std/src/sys/random/openvm.rs @@ -0,0 +1,21 @@ +use crate::sys::pal::abi; + +pub fn fill_bytes(bytes: &mut [u8]) { + let (pre, words, post) = unsafe { bytes.align_to_mut::() }; + if !words.is_empty() { + unsafe { + abi::sys_rand(words.as_mut_ptr(), words.len()); + } + } + + let mut buf = [0u32; 2]; + let len = (pre.len() + post.len()).div_ceil(size_of::()); + if len != 0 { + unsafe { abi::sys_rand(buf.as_mut_ptr(), len) }; + } + + let buf = buf.map(u32::to_ne_bytes); + let buf = buf.as_flattened(); + pre.copy_from_slice(&buf[..pre.len()]); + post.copy_from_slice(&buf[pre.len()..pre.len() + post.len()]); +} diff --git a/library/std/src/sys/stdio/mod.rs b/library/std/src/sys/stdio/mod.rs index 86d0f3fe49cb3..686a41f377c00 100644 --- a/library/std/src/sys/stdio/mod.rs +++ b/library/std/src/sys/stdio/mod.rs @@ -45,6 +45,10 @@ cfg_select! { mod zkvm; pub use zkvm::*; } + target_os = "openvm" => { + mod openvm; + pub use openvm::*; + } _ => { mod unsupported; pub use unsupported::*; diff --git a/library/std/src/sys/stdio/openvm.rs b/library/std/src/sys/stdio/openvm.rs new file mode 100644 index 0000000000000..84496ac937363 --- /dev/null +++ b/library/std/src/sys/stdio/openvm.rs @@ -0,0 +1,72 @@ +use crate::io::{self, BorrowedCursor}; +use crate::sys::pal::abi::{self, fileno}; + +pub struct Stdin; +pub struct Stdout; +pub struct Stderr; + +impl Stdin { + pub const fn new() -> Stdin { + Stdin + } +} + +impl io::Read for Stdin { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + Ok(unsafe { abi::sys_read(fileno::STDIN, buf.as_mut_ptr(), buf.len()) }) + } + + fn read_buf(&mut self, mut buf: BorrowedCursor<'_>) -> io::Result<()> { + unsafe { + let n = abi::sys_read(fileno::STDIN, buf.as_mut().as_mut_ptr().cast(), buf.capacity()); + buf.advance(n); + } + Ok(()) + } +} + +impl Stdout { + pub const fn new() -> Stdout { + Stdout + } +} + +impl io::Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + unsafe { abi::sys_write(fileno::STDOUT, buf.as_ptr(), buf.len()) } + + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl Stderr { + pub const fn new() -> Stderr { + Stderr + } +} + +impl io::Write for Stderr { + fn write(&mut self, buf: &[u8]) -> io::Result { + unsafe { abi::sys_write(fileno::STDERR, buf.as_ptr(), buf.len()) } + + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +pub const STDIN_BUF_SIZE: usize = crate::sys::io::DEFAULT_BUF_SIZE; + +pub fn is_ebadf(_err: &io::Error) -> bool { + true +} + +pub fn panic_output() -> Option { + Some(Stderr::new()) +} diff --git a/library/std/src/sys/thread_local/mod.rs b/library/std/src/sys/thread_local/mod.rs index e88011aa22dad..ca30fc8bfe236 100644 --- a/library/std/src/sys/thread_local/mod.rs +++ b/library/std/src/sys/thread_local/mod.rs @@ -28,6 +28,7 @@ cfg_select! { all(target_family = "wasm", not(target_feature = "atomics")), target_os = "uefi", target_os = "zkvm", + target_os = "openvm", target_os = "trusty", target_os = "vexos", ) => { @@ -98,6 +99,7 @@ pub(crate) mod guard { )), target_os = "uefi", target_os = "zkvm", + target_os = "openvm", target_os = "trusty", target_os = "vexos", ) => { diff --git a/library/test/src/console.rs b/library/test/src/console.rs index b1c5404a7160c..382155304db9d 100644 --- a/library/test/src/console.rs +++ b/library/test/src/console.rs @@ -320,9 +320,10 @@ pub fn run_tests_console(opts: &TestOpts, tests: TestList) -> io::Result { // Prevent the usage of `Instant` in some cases: // - It's currently not supported for wasm targets without Emscripten nor WASI. - // - It's currently not supported for zkvm targets. - let is_instant_unsupported = - (cfg!(target_family = "wasm") && cfg!(target_os = "unknown")) || cfg!(target_os = "zkvm"); + // - It's currently not supported for zkvm/openvm targets. + let is_instant_unsupported = (cfg!(target_family = "wasm") && cfg!(target_os = "unknown")) + || cfg!(target_os = "zkvm") + || cfg!(target_os = "openvm"); let start_time = (!is_instant_unsupported).then(Instant::now); run_tests(opts, tests, |x| on_test_event(&x, &mut st, &mut *out))?; diff --git a/library/test/src/lib.rs b/library/test/src/lib.rs index 791af5f8ee2a9..267cb7a668966 100644 --- a/library/test/src/lib.rs +++ b/library/test/src/lib.rs @@ -641,7 +641,7 @@ pub fn run_test( // Emscripten can catch panics but other wasm targets cannot let ignore_because_no_process_support = desc.should_panic != ShouldPanic::No - && (cfg!(target_family = "wasm") || cfg!(target_os = "zkvm")) + && (cfg!(target_family = "wasm") || cfg!(target_os = "zkvm") || cfg!(target_os = "openvm")) && !cfg!(target_os = "emscripten"); if force_ignore || desc.ignore || ignore_because_no_process_support { @@ -690,7 +690,8 @@ pub fn run_test( // level. let supports_threads = !cfg!(target_os = "emscripten") && !cfg!(target_family = "wasm") - && !cfg!(target_os = "zkvm"); + && !cfg!(target_os = "zkvm") + && !cfg!(target_os = "openvm"); if supports_threads { let cfg = thread::Builder::new().name(name.as_slice().to_owned()); let mut runtest = Arc::new(Mutex::new(Some(runtest))); diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index fd3d129c231d7..d57437fc5a383 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -844,8 +844,8 @@ impl Build { features.insert("profiler"); } - // If zkvm target, generate memcpy, etc. - if target.contains("zkvm") { + // If zkvm/openvm target, generate memcpy, etc. + if target.contains("zkvm") || target.contains("openvm") { features.insert("compiler-builtins-mem"); } diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index cc10f476780c5..dd854497cc9d7 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -115,6 +115,7 @@ - [riscv32im-risc0-zkvm-elf](platform-support/riscv32im-risc0-zkvm-elf.md) - [riscv32imac-unknown-xous-elf](platform-support/riscv32imac-unknown-xous-elf.md) - [riscv64im-unknown-none-elf](platform-support/riscv64im-unknown-none-elf.md) + - [riscv64im-unknown-openvm-elf](platform-support/riscv64im-unknown-openvm-elf.md) - [riscv64gc-unknown-linux-gnu](platform-support/riscv64gc-unknown-linux-gnu.md) - [riscv64gc-unknown-linux-musl](platform-support/riscv64gc-unknown-linux-musl.md) - [riscv64a23-unknown-linux-gnu](platform-support/riscv64a23-unknown-linux-gnu.md) diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 26dd6b31b8991..a450ee3f785bf 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -193,6 +193,7 @@ target | std | notes [`riscv64gc-unknown-linux-musl`](platform-support/riscv64gc-unknown-linux-musl.md) | ✓ |RISC-V Linux (kernel 4.20+, musl 1.2.5) `riscv64gc-unknown-none-elf` | * | Bare RISC-V (RV64IMAFDC ISA) [`riscv64im-unknown-none-elf`](platform-support/riscv64im-unknown-none-elf.md) | * | Bare RISC-V (RV64IM ISA) +[`riscv64im-unknown-openvm-elf`](platform-support/riscv64im-unknown-openvm-elf.md) | ✓ | OpenVM zkVM (RV64IM ISA) `riscv64imac-unknown-none-elf` | * | Bare RISC-V (RV64IMAC ISA) `sparc64-unknown-linux-gnu` | ✓ | SPARC Linux (kernel 4.4+, glibc 2.23) [`s390x-unknown-none-softfloat`](platform-support/s390x-unknown-none-softfloat.md) | * | Bare S390x (softfloat ABI) diff --git a/src/doc/rustc/src/platform-support/riscv64im-unknown-openvm-elf.md b/src/doc/rustc/src/platform-support/riscv64im-unknown-openvm-elf.md new file mode 100644 index 0000000000000..d2761d767078b --- /dev/null +++ b/src/doc/rustc/src/platform-support/riscv64im-unknown-openvm-elf.md @@ -0,0 +1,72 @@ +# `riscv64im-unknown-openvm-elf` + +**Tier: 3** + +Target for [OpenVM](https://github.com/openvm-org/openvm/) virtual machines with the RV64IM ISA and custom RISC-V extensions defined through OpenVM's extension framework. + +## Target maintainers + +[@jonathanpwang](https://github.com/jonathanpwang) +[@yi-sun](https://github.com/yi-sun) + +## Background + +This target is an execution environment to produce a verifiable cryptographic proof of execution of +a RISC‑V ELF binary and any output that the developer wishes to make public. +The execution environment is implemented as a virtual machine in software only. The target is not intended for bare metal hardware. The virtual machine may be extended to support custom RISC-V instruction sets, which may be invoked from Rust via the `asm!` macro. See the [OpenVM Book] for further documentation on the architecture and usage. + +We provide a cargo extension called [cargo-openvm] that provides tools for cross-compilation, execution, and generating cryptographic proofs of execution. + +## Requirements + +The target supports cross-compilation from any host and does not support host tools. It supports `alloc` with a +default allocator. Partial support for the Rust `std` library is provided using custom RISC-V instructions and requires the `openvm` crate with the `"std"` feature enabled. Further details and limitations of `std` support are documented [here](https://docs.openvm.dev/book/writing-apps/writing-a-program#rust-std-library-support). + +The target's execution environment is single-threaded, non-preemptive, and does not support +privileged instructions. At present, unaligned accesses are not supported and will result in execution traps. The binaries expect no operating system and can be thought +of as running on bare-metal. The target does not use `#[target_feature(...)]` or +`-C target-feature=` values. + +Binaries are expected to be ELF. + +Calling `extern "C"` uses the C calling convention outlined +in the [RISC-V specification]. + +## Building the target +The target can be built by enabling it for a `rustc` build. + +```toml +[build] +target = ["riscv64im-unknown-openvm-elf"] +``` + +## Building Rust programs + +Rust does not yet ship pre-compiled artifacts for this target. To compile for +this target, you will need to do one of the following: +- Build Rust with the target enabled (see "Building the target" above) +- Build your own copy of `core` by passing args `-Zbuild-std=alloc,core,proc_macro,panic_abort,std -Zbuild-std-features=compiler-builtins-mem` +- Use `cargo openvm build` provided by the cargo extension [cargo-openvm]. + +The `cargo-openvm` utility is a command-line interface that calls `cargo` with the `build-std` flags above together with `rustc` flags `-C passes=lower-atomic -C link-arg=-Ttext=` to map text to the appropriate location. The text start (presently `0x0020_0800`) must be set to start above the stack top, and heap begins right after the text. The utility also includes the `rustc` flag `--cfg getrandom_backend="custom"` to enable a custom backend for the `getrandom` crate. + +## Testing + +Note: this target is implemented as a software virtual machine; there is no hardware implementation. + +Guest programs cross-compiled to the target must be run on the host inside OpenVM virtual machines, which are software emulators. The most practical way to do this is via either the [cargo-openvm] command-line interface or the [OpenVM SDK]. + +The target currently does not support running the Rust test suite. + +## Cross-compilation toolchains and C code + +Compatible C code can be built for this target on any compiler that has a RV64IM +target. On clang and ld.lld linker, it can be generated using the +`-march=rv64im`, `-mabi=lp64` with llvm features flag `features=+m` and llvm +target `riscv64-unknown-none`. + +[RISC-V specification]: https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf +[OpenVM]: https://github.com/openvm-org/openvm/ +[OpenVM Book]: https://docs.openvm.dev/book/ +[OpenVM SDK]: https://docs.openvm.dev/book/advanced-usage/sdk +[cargo-openvm]: https://docs.openvm.dev/book/getting-started/install diff --git a/tests/assembly-llvm/targets/targets-elf.rs b/tests/assembly-llvm/targets/targets-elf.rs index ce6629b4f27ce..92d9eb84a73be 100644 --- a/tests/assembly-llvm/targets/targets-elf.rs +++ b/tests/assembly-llvm/targets/targets-elf.rs @@ -535,6 +535,9 @@ //@ revisions: riscv64im_unknown_none_elf //@ [riscv64im_unknown_none_elf] compile-flags: --target riscv64im-unknown-none-elf //@ [riscv64im_unknown_none_elf] needs-llvm-components: riscv +//@ revisions: riscv64im_unknown_openvm_elf +//@ [riscv64im_unknown_openvm_elf] compile-flags: --target riscv64im-unknown-openvm-elf +//@ [riscv64im_unknown_openvm_elf] needs-llvm-components: riscv //@ revisions: riscv64imac_unknown_none_elf //@ [riscv64imac_unknown_none_elf] compile-flags: --target riscv64imac-unknown-none-elf //@ [riscv64imac_unknown_none_elf] needs-llvm-components: riscv diff --git a/tests/ui/check-cfg/cfg-crate-features.stderr b/tests/ui/check-cfg/cfg-crate-features.stderr index 242883995488e..aeb4096486854 100644 --- a/tests/ui/check-cfg/cfg-crate-features.stderr +++ b/tests/ui/check-cfg/cfg-crate-features.stderr @@ -24,7 +24,7 @@ warning: unexpected `cfg` condition value: `does_not_exist` LL | #![cfg(not(target(os = "does_not_exist")))] | ^^^^^^^^^^^^^^^^^^^^^ | - = note: expected values for `target_os` are: `aix`, `amdhsa`, `android`, `cuda`, `cygwin`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `helenos`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `lynxos178`, `macos`, `managarm`, `motor`, `netbsd`, `none`, `nto`, `nuttx`, `openbsd`, `psp`, `psx`, `qurt`, `redox`, `rtems`, `solaris`, and `solid_asp3` and 14 more + = note: expected values for `target_os` are: `aix`, `amdhsa`, `android`, `cuda`, `cygwin`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `helenos`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `lynxos178`, `macos`, `managarm`, `motor`, `netbsd`, `none`, `nto`, `nuttx`, `openbsd`, `openvm`, `psp`, `psx`, `qurt`, `redox`, `rtems`, and `solaris` and 15 more = note: see for more information about checking conditional configuration = note: `#[warn(unexpected_cfgs)]` on by default diff --git a/tests/ui/check-cfg/well-known-values.stderr b/tests/ui/check-cfg/well-known-values.stderr index dd1b696b76cb2..b69554dbf4088 100644 --- a/tests/ui/check-cfg/well-known-values.stderr +++ b/tests/ui/check-cfg/well-known-values.stderr @@ -210,7 +210,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE` LL | target_os = "_UNEXPECTED_VALUE", | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: expected values for `target_os` are: `aix`, `amdhsa`, `android`, `cuda`, `cygwin`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `helenos`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `lynxos178`, `macos`, `managarm`, `motor`, `netbsd`, `none`, `nto`, `nuttx`, `openbsd`, `psp`, `psx`, `qurt`, `redox`, `rtems`, `solaris`, `solid_asp3`, `teeos`, `trusty`, `tvos`, `uefi`, `unknown`, `vexos`, `visionos`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`, and `zkvm` + = note: expected values for `target_os` are: `aix`, `amdhsa`, `android`, `cuda`, `cygwin`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `helenos`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `lynxos178`, `macos`, `managarm`, `motor`, `netbsd`, `none`, `nto`, `nuttx`, `openbsd`, `openvm`, `psp`, `psx`, `qurt`, `redox`, `rtems`, `solaris`, `solid_asp3`, `teeos`, `trusty`, `tvos`, `uefi`, `unknown`, `vexos`, `visionos`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`, and `zkvm` = note: see for more information about checking conditional configuration warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE` @@ -283,7 +283,7 @@ LL | #[cfg(target_os = "linuz")] // testing that we suggest `linux` | | | help: there is a expected value with a similar name: `"linux"` | - = note: expected values for `target_os` are: `aix`, `amdhsa`, `android`, `cuda`, `cygwin`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `helenos`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `lynxos178`, `macos`, `managarm`, `motor`, `netbsd`, `none`, `nto`, `nuttx`, `openbsd`, `psp`, `psx`, `qurt`, `redox`, `rtems`, `solaris`, `solid_asp3`, `teeos`, `trusty`, `tvos`, `uefi`, `unknown`, `vexos`, `visionos`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`, and `zkvm` + = note: expected values for `target_os` are: `aix`, `amdhsa`, `android`, `cuda`, `cygwin`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `helenos`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `lynxos178`, `macos`, `managarm`, `motor`, `netbsd`, `none`, `nto`, `nuttx`, `openbsd`, `openvm`, `psp`, `psx`, `qurt`, `redox`, `rtems`, `solaris`, `solid_asp3`, `teeos`, `trusty`, `tvos`, `uefi`, `unknown`, `vexos`, `visionos`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`, and `zkvm` = note: see for more information about checking conditional configuration warning: 29 warnings emitted