diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index 5d74ebebdf3ff..884bd23e8cce9 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -1597,6 +1597,7 @@ supported_targets! { ("x86_64-unikraft-linux-musl", x86_64_unikraft_linux_musl), ("riscv32i-unknown-none-elf", riscv32i_unknown_none_elf), + ("riscv32im-risc0-zkvm-elf", riscv32im_risc0_zkvm_elf), ("riscv32im-unknown-none-elf", riscv32im_unknown_none_elf), ("riscv32imc-unknown-none-elf", riscv32imc_unknown_none_elf), ("riscv32imc-esp-espidf", riscv32imc_esp_espidf), diff --git a/compiler/rustc_target/src/spec/targets/riscv32im_risc0_zkvm_elf.rs b/compiler/rustc_target/src/spec/targets/riscv32im_risc0_zkvm_elf.rs new file mode 100644 index 0000000000000..bf819de413311 --- /dev/null +++ b/compiler/rustc_target/src/spec/targets/riscv32im_risc0_zkvm_elf.rs @@ -0,0 +1,36 @@ +use crate::spec::{Cc, LinkerFlavor, Lld, PanicStrategy, RelocModel}; +use crate::spec::{Target, TargetOptions}; + +pub fn target() -> Target { + Target { + data_layout: "e-m:e-p:32:32-i64:64-n32-S128".into(), + llvm_target: "riscv32".into(), + pointer_width: 32, + arch: "riscv32".into(), + + options: TargetOptions { + os: "zkvm".into(), + vendor: "risc0".into(), + linker_flavor: LinkerFlavor::Gnu(Cc::No, Lld::Yes), + linker: Some("rust-lld".into()), + cpu: "generic-rv32".into(), + + // Some crates (*cough* crossbeam) assume you have 64 bit + // atomics if the target name is not in a hardcoded list. + // Since zkvm is singlethreaded and all operations are + // atomic, I guess we can just say we support 64-bit + // atomics. + max_atomic_width: Some(64), + atomic_cas: true, + + features: "+m".into(), + executables: true, + 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/src/lib.rs b/library/panic_abort/src/lib.rs index 8fd64279ac5a7..c44f23eea8094 100644 --- a/library/panic_abort/src/lib.rs +++ b/library/panic_abort/src/lib.rs @@ -19,6 +19,9 @@ #[cfg(target_os = "android")] mod android; +#[cfg(target_os = "zkvm")] +mod zkvm; + use core::any::Any; use core::panic::PanicPayload; @@ -34,6 +37,8 @@ pub unsafe fn __rust_start_panic(_payload: &mut dyn PanicPayload) -> u32 { // Android has the ability to attach a message as part of the abort. #[cfg(target_os = "android")] android::android_set_abort_message(_payload); + #[cfg(target_os = "zkvm")] + zkvm::zkvm_set_abort_message(_payload); abort(); diff --git a/library/panic_abort/src/zkvm.rs b/library/panic_abort/src/zkvm.rs new file mode 100644 index 0000000000000..a6a02abf10976 --- /dev/null +++ b/library/panic_abort/src/zkvm.rs @@ -0,0 +1,24 @@ +use alloc::string::String; +use core::panic::PanicPayload; + +// Forward the abort message to zkVM's sys_panic. This is implemented by RISC Zero's +// platform crate which exposes system calls specifically for the zkVM. +pub(crate) unsafe fn zkvm_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; + } + + extern "C" { + fn sys_panic(msg_ptr: *const u8, len: usize) -> !; + } + + sys_panic(msg.as_ptr(), msg.len()); +} diff --git a/library/std/build.rs b/library/std/build.rs index 0f5068b59b7e5..60c097db2f4bf 100644 --- a/library/std/build.rs +++ b/library/std/build.rs @@ -35,6 +35,7 @@ fn main() { || target.contains("hurd") || target.contains("uefi") || target.contains("teeos") + || target.contains("zkvm") // See src/bootstrap/synthetic_targets.rs || env::var("RUSTC_BOOTSTRAP_SYNTHETIC_TARGET").is_ok() { diff --git a/library/std/src/sys/pal/common/alloc.rs b/library/std/src/sys/pal/common/alloc.rs index b7357460f3931..8cf9ef6804724 100644 --- a/library/std/src/sys/pal/common/alloc.rs +++ b/library/std/src/sys/pal/common/alloc.rs @@ -16,7 +16,7 @@ use crate::ptr; target_arch = "sparc", target_arch = "wasm32", target_arch = "hexagon", - all(target_arch = "riscv32", not(target_os = "espidf")), + all(target_arch = "riscv32", not(any(target_os = "espidf", target_os = "zkvm"))), all(target_arch = "xtensa", not(target_os = "espidf")), ))] pub const MIN_ALIGN: usize = 8; @@ -32,11 +32,11 @@ pub const MIN_ALIGN: usize = 8; target_arch = "wasm64", ))] pub const MIN_ALIGN: usize = 16; -// The allocator on the esp-idf platform guarantees 4 byte alignment. -#[cfg(any( - all(target_arch = "riscv32", target_os = "espidf"), +// The allocator on the esp-idf and zkvm platforms guarantee 4 byte alignment. +#[cfg(all(any( + all(target_arch = "riscv32", any(target_os = "espidf", target_os = "zkvm")), all(target_arch = "xtensa", target_os = "espidf"), -))] +)))] pub const MIN_ALIGN: usize = 4; pub unsafe fn realloc_fallback( diff --git a/library/std/src/sys/pal/mod.rs b/library/std/src/sys/pal/mod.rs index 66b2a4b888502..041b7c355822a 100644 --- a/library/std/src/sys/pal/mod.rs +++ b/library/std/src/sys/pal/mod.rs @@ -55,6 +55,9 @@ cfg_if::cfg_if! { } else if #[cfg(target_os = "teeos")] { mod teeos; pub use self::teeos::*; + } else if #[cfg(target_os = "zkvm")] { + mod zkvm; + pub use self::zkvm::*; } else { mod unsupported; pub use self::unsupported::*; diff --git a/library/std/src/sys/pal/zkvm/abi.rs b/library/std/src/sys/pal/zkvm/abi.rs new file mode 100644 index 0000000000000..53332d90e02c0 --- /dev/null +++ b/library/std/src/sys/pal/zkvm/abi.rs @@ -0,0 +1,55 @@ +//! ABI definitions for symbols exported by risc0-zkvm-platform. + +// Included here so we don't have to depend on risc0-zkvm-platform. +// +// FIXME: Should we move this to the "libc" crate? It seems like other +// architectures put a lot of this kind of stuff there. But there's +// currently no risc0 fork of the libc crate, so we'd either have to +// fork it or upstream it. + +#![allow(dead_code)] +pub const DIGEST_WORDS: usize = 8; + +/// 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; +} + +extern "C" { + // Wrappers around syscalls provided by risc0-zkvm-platform: + pub fn sys_halt(); + pub fn sys_output(output_id: u32, output_value: u32); + pub fn sys_sha_compress( + out_state: *mut [u32; DIGEST_WORDS], + in_state: *const [u32; DIGEST_WORDS], + block1_ptr: *const [u32; DIGEST_WORDS], + block2_ptr: *const [u32; DIGEST_WORDS], + ); + pub fn sys_sha_buffer( + out_state: *mut [u32; DIGEST_WORDS], + in_state: *const [u32; DIGEST_WORDS], + buf: *const u8, + count: u32, + ); + 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_cycle_count() -> 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/zkvm/alloc.rs b/library/std/src/sys/pal/zkvm/alloc.rs new file mode 100644 index 0000000000000..fd333f1215150 --- /dev/null +++ b/library/std/src/sys/pal/zkvm/alloc.rs @@ -0,0 +1,15 @@ +use super::abi; +use crate::alloc::{GlobalAlloc, Layout, System}; + +#[stable(feature = "alloc_system_type", since = "1.28.0")] +unsafe impl GlobalAlloc for System { + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + 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/pal/zkvm/args.rs b/library/std/src/sys/pal/zkvm/args.rs new file mode 100644 index 0000000000000..7753cf63840e3 --- /dev/null +++ b/library/std/src/sys/pal/zkvm/args.rs @@ -0,0 +1,80 @@ +use super::{abi, WORD_SIZE}; +use crate::ffi::OsString; +use crate::fmt; +use crate::sys_common::FromInner; + +pub struct Args { + i_forward: usize, + i_back: usize, + count: usize, +} + +pub fn args() -> Args { + let count = unsafe { abi::sys_argc() }; + Args { i_forward: 0, i_back: 0, count } +} + +impl Args { + /// Use sys_argv to get the arg at the requested index. Does not check that i is less than argc + /// and will not return if the index is out of bounds. + fn argv(i: usize) -> OsString { + let arg_len = unsafe { abi::sys_argv(crate::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); + + // Convert to OsString. + // + // FIXME: We can probably get rid of the extra copy here if we + // reimplement "os_str" instead of just using the generic unix + // "os_str". + let arg_bytes: &[u8] = + unsafe { crate::slice::from_raw_parts(words.cast() as *const u8, arg_len) }; + OsString::from_inner(super::os_str::Buf { inner: arg_bytes.to_vec() }) + } +} + +impl fmt::Debug for Args { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().finish() + } +} + +impl Iterator for Args { + type Item = OsString; + + fn next(&mut self) -> Option { + if self.i_forward >= self.count - self.i_back { + None + } else { + let arg = Self::argv(self.i_forward); + self.i_forward += 1; + Some(arg) + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.count, Some(self.count)) + } +} + +impl ExactSizeIterator for Args { + fn len(&self) -> usize { + self.count + } +} + +impl DoubleEndedIterator for Args { + fn next_back(&mut self) -> Option { + if self.i_back >= self.count - self.i_forward { + None + } else { + let arg = Self::argv(self.count - 1 - self.i_back); + self.i_back += 1; + Some(arg) + } + } +} diff --git a/library/std/src/sys/pal/zkvm/env.rs b/library/std/src/sys/pal/zkvm/env.rs new file mode 100644 index 0000000000000..b85153642b1c9 --- /dev/null +++ b/library/std/src/sys/pal/zkvm/env.rs @@ -0,0 +1,9 @@ +pub mod os { + pub const FAMILY: &str = ""; + pub const OS: &str = ""; + 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"; +} diff --git a/library/std/src/sys/pal/zkvm/mod.rs b/library/std/src/sys/pal/zkvm/mod.rs new file mode 100644 index 0000000000000..7f221dc4fd98a --- /dev/null +++ b/library/std/src/sys/pal/zkvm/mod.rs @@ -0,0 +1,93 @@ +//! System bindings for the risc0 zkvm platform +//! +//! This module contains the facade (aka platform-specific) implementations of +//! OS level functionality for zkvm. +//! +//! 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. + +const WORD_SIZE: usize = core::mem::size_of::(); + +pub mod alloc; +#[path = "../zkvm/args.rs"] +pub mod args; +#[path = "../unix/cmath.rs"] +pub mod cmath; +pub mod env; +#[path = "../unsupported/fs.rs"] +pub mod fs; +#[path = "../unsupported/io.rs"] +pub mod io; +#[path = "../unsupported/net.rs"] +pub mod net; +#[path = "../unsupported/once.rs"] +pub mod once; +pub mod os; +#[path = "../unix/os_str.rs"] +pub mod os_str; +#[path = "../unix/path.rs"] +pub mod path; +#[path = "../unsupported/pipe.rs"] +pub mod pipe; +#[path = "../unsupported/process.rs"] +pub mod process; +pub mod stdio; +pub mod thread_local_key; +#[path = "../unsupported/time.rs"] +pub mod time; + +#[path = "../unsupported/locks/mod.rs"] +pub mod locks; +#[path = "../unsupported/thread.rs"] +pub mod thread; + +#[path = "../unsupported/thread_parking.rs"] +pub mod thread_parking; + +mod abi; + +use crate::io as std_io; + +pub mod memchr { + pub use core::slice::memchr::{memchr, memrchr}; +} + +// 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::const_io_error!( + std_io::ErrorKind::Unsupported, + "operation not supported on this platform", + ) +} + +pub fn is_interrupted(_code: i32) -> bool { + false +} + +pub fn decode_error_kind(_code: i32) -> crate::io::ErrorKind { + crate::io::ErrorKind::Uncategorized +} + +pub fn abort_internal() -> ! { + core::intrinsics::abort(); +} + +pub fn hashmap_random_keys() -> (u64, u64) { + let mut buf = [0u32; 4]; + unsafe { + abi::sys_rand(buf.as_mut_ptr(), 4); + }; + ((buf[0] as u64) << 32 + buf[1] as u64, (buf[2] as u64) << 32 + buf[3] as u64) +} diff --git a/library/std/src/sys/pal/zkvm/os.rs b/library/std/src/sys/pal/zkvm/os.rs new file mode 100644 index 0000000000000..d8739ee38246e --- /dev/null +++ b/library/std/src/sys/pal/zkvm/os.rs @@ -0,0 +1,139 @@ +use super::{abi, unsupported, WORD_SIZE}; +use crate::error::Error as StdError; +use crate::ffi::{OsStr, OsString}; +use crate::fmt; +use crate::io; +use crate::marker::PhantomData; +use crate::path::{self, PathBuf}; +use crate::sys_common::FromInner; + +pub fn errno() -> i32 { + 0 +} + +pub fn error_string(_errno: i32) -> String { + "operation successful".to_string() +} + +pub fn getcwd() -> io::Result { + unsupported() +} + +pub fn chdir(_: &path::Path) -> io::Result<()> { + unsupported() +} + +pub struct SplitPaths<'a>(!, PhantomData<&'a ()>); + +pub fn split_paths(_unparsed: &OsStr) -> SplitPaths<'_> { + panic!("unsupported") +} + +impl<'a> Iterator for SplitPaths<'a> { + type Item = PathBuf; + fn next(&mut self) -> Option { + self.0 + } +} + +#[derive(Debug)] +pub struct JoinPathsError; + +pub fn join_paths(_paths: I) -> Result +where + I: Iterator, + T: AsRef, +{ + Err(JoinPathsError) +} + +impl fmt::Display for JoinPathsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + "not supported on this platform yet".fmt(f) + } +} + +impl StdError for JoinPathsError { + #[allow(deprecated)] + fn description(&self) -> &str { + "not supported on this platform yet" + } +} + +pub fn current_exe() -> io::Result { + unsupported() +} + +pub struct Env(!); + +impl Iterator for Env { + type Item = (OsString, OsString); + fn next(&mut self) -> Option<(OsString, OsString)> { + self.0 + } +} + +pub fn env() -> Env { + panic!("not supported on this platform") +} + +impl Env { + pub fn str_debug(&self) -> impl fmt::Debug + '_ { + let Self(inner) = self; + match *inner {} + } +} + +impl fmt::Debug for Env { + fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self(inner) = self; + match *inner {} + } +} + +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. + // + // FIXME: We can probably get rid of the extra copy here if we + // reimplement "os_str" instead of just using the generic unix + // "os_str". + let u8s: &[u8] = unsafe { crate::slice::from_raw_parts(words.cast() as *const u8, nbytes) }; + Some(OsString::from_inner(super::os_str::Buf { inner: u8s.to_vec() })) +} + +pub fn setenv(_: &OsStr, _: &OsStr) -> io::Result<()> { + Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot set env vars on this platform")) +} + +pub fn unsetenv(_: &OsStr) -> io::Result<()> { + Err(io::const_io_error!(io::ErrorKind::Unsupported, "cannot unset env vars on this platform")) +} + +pub fn temp_dir() -> PathBuf { + panic!("no filesystem on this platform") +} + +pub fn home_dir() -> Option { + None +} + +pub fn exit(_code: i32) -> ! { + crate::intrinsics::abort() +} + +pub fn getpid() -> u32 { + panic!("no pids on this platform") +} diff --git a/library/std/src/sys/pal/zkvm/stdio.rs b/library/std/src/sys/pal/zkvm/stdio.rs new file mode 100644 index 0000000000000..e771ed0de28db --- /dev/null +++ b/library/std/src/sys/pal/zkvm/stdio.rs @@ -0,0 +1,64 @@ +use super::{abi, abi::fileno}; +use crate::io; + +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()) }) + } +} + +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_common::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/pal/zkvm/thread_local_key.rs b/library/std/src/sys/pal/zkvm/thread_local_key.rs new file mode 100644 index 0000000000000..3ffe6247344e8 --- /dev/null +++ b/library/std/src/sys/pal/zkvm/thread_local_key.rs @@ -0,0 +1,23 @@ +use crate::alloc::{alloc, Layout}; + +pub type Key = usize; + +#[inline] +pub unsafe fn create(_dtor: Option) -> Key { + alloc(Layout::new::<*mut u8>()) as _ +} + +#[inline] +pub unsafe fn set(key: Key, value: *mut u8) { + let key: *mut *mut u8 = core::ptr::from_exposed_addr_mut(key); + *key = value; +} + +#[inline] +pub unsafe fn get(key: Key) -> *mut u8 { + let key: *mut *mut u8 = core::ptr::from_exposed_addr_mut(key); + *key +} + +#[inline] +pub unsafe fn destroy(_key: Key) {} diff --git a/library/test/src/console.rs b/library/test/src/console.rs index bbeb944e8b11b..09aa3bfb6aac3 100644 --- a/library/test/src/console.rs +++ b/library/test/src/console.rs @@ -323,7 +323,8 @@ pub fn run_tests_console(opts: &TestOpts, tests: Vec) -> io::Resu // Prevent the usage of `Instant` in some cases: // - It's currently not supported for wasm targets. // - We disable it for miri because it's not available when isolation is enabled. - let is_instant_supported = !cfg!(target_family = "wasm") && !cfg!(miri); + let is_instant_supported = + !cfg!(target_family = "wasm") && !cfg!(target_os = "zkvm") && !cfg!(miri); let start_time = is_instant_supported.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 2e93796d98152..5969d6b772c34 100644 --- a/library/test/src/lib.rs +++ b/library/test/src/lib.rs @@ -540,7 +540,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_family = "wasm") || cfg!(target_os = "zkvm")) && !cfg!(target_os = "emscripten"); if force_ignore || desc.ignore || ignore_because_no_process_support { diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs index 30824f5852283..1336abf6c7aba 100644 --- a/src/bootstrap/src/lib.rs +++ b/src/bootstrap/src/lib.rs @@ -90,6 +90,10 @@ const EXTRA_CHECK_CFGS: &[(Option, &str, Option<&[&'static str]>)] = &[ /* Extra values not defined in the built-in targets yet, but used in std */ (Some(Mode::Std), "target_env", Some(&["libnx"])), // (Some(Mode::Std), "target_os", Some(&[])), + // #[cfg(bootstrap)] zkvm + (Some(Mode::Std), "target_os", Some(&["zkvm"])), + // #[cfg(bootstrap)] risc0 + (Some(Mode::Std), "target_vendor", Some(&["risc0"])), (Some(Mode::Std), "target_arch", Some(&["spirv", "nvptx", "xtensa"])), /* Extra names used by dependencies */ // FIXME: Used by serde_json, but we should not be triggering on external dependencies. @@ -721,6 +725,11 @@ impl Build { if self.config.profiler_enabled(target) { features.push_str(" profiler"); } + // Generate memcpy, etc. FIXME: Remove this once compiler-builtins + // automatically detects this target. + if target.contains("zkvm") { + features.push_str(" compiler-builtins-mem"); + } features } diff --git a/src/doc/rustc/src/SUMMARY.md b/src/doc/rustc/src/SUMMARY.md index 1c8e1d1e316b7..1998b008dc811 100644 --- a/src/doc/rustc/src/SUMMARY.md +++ b/src/doc/rustc/src/SUMMARY.md @@ -47,6 +47,7 @@ - [mipsisa\*r6\*-unknown-linux-gnu\*](platform-support/mips-release-6.md) - [nvptx64-nvidia-cuda](platform-support/nvptx64-nvidia-cuda.md) - [powerpc64-ibm-aix](platform-support/aix.md) + - [riscv32im-risc0-zkvm-elf](platform-support/riscv32im-risc0-zkvm-elf.md) - [riscv32imac-unknown-xous-elf](platform-support/riscv32imac-unknown-xous-elf.md) - [riscv32*-unknown-none-elf](platform-support/riscv32imac-unknown-none-elf.md) - [sparc-unknown-none-elf](./platform-support/sparc-unknown-none-elf.md) diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md index 9e5c1ed87489e..f648a60b6c48d 100644 --- a/src/doc/rustc/src/platform-support.md +++ b/src/doc/rustc/src/platform-support.md @@ -338,6 +338,7 @@ target | std | host | notes [`powerpc64-ibm-aix`](platform-support/aix.md) | ? | | 64-bit AIX (7.2 and newer) `riscv32gc-unknown-linux-gnu` | | | RISC-V Linux (kernel 5.4, glibc 2.33) `riscv32gc-unknown-linux-musl` | | | RISC-V Linux (kernel 5.4, musl + RISCV32 support patches) +[`riscv32im-risc0-zkvm-elf`](platform-support/riscv32im-risc0-zkvm-elf.md) | ? | | RISC Zero's zero-knowledge Virtual Machine (RV32IM ISA) [`riscv32imac-unknown-xous-elf`](platform-support/riscv32imac-unknown-xous-elf.md) | ? | | RISC-V Xous (RV32IMAC ISA) [`riscv32imc-esp-espidf`](platform-support/esp-idf.md) | ✓ | | RISC-V ESP-IDF [`riscv32imac-esp-espidf`](platform-support/esp-idf.md) | ✓ | | RISC-V ESP-IDF diff --git a/src/doc/rustc/src/platform-support/riscv32im-risc0-zkvm-elf.md b/src/doc/rustc/src/platform-support/riscv32im-risc0-zkvm-elf.md new file mode 100644 index 0000000000000..1fdd594012c22 --- /dev/null +++ b/src/doc/rustc/src/platform-support/riscv32im-risc0-zkvm-elf.md @@ -0,0 +1,86 @@ +# `riscv32im-risc0-zkvm-elf` + +**Tier: 3** + +RISC Zero's Zero Knowledge Virtual Machine (zkVM) implementing the RV32IM instruction set. + +## Target maintainers + +- Frank Laub, `frank@risczero.com`, https://github.com/flaub +- Jeremy Bruestle, `jeremy@risczero.com`, https://github.com/jbruestle +- Erik Kaneda, `erik@risczero.com`, https://github.com/SchmErik + +## Background + +This target is an execution environment to produce a proof of execution of +a RISC-V ELF binary and any output that the developer of the binary wishes to +display publicly. In order to do this, the target will execute the ELF to +generate a receipt containing the output of the computation along with a +cryptographic seal. This receipt can be verified to ensure the integrity of the +computation and its result. This target is implemented as software only; it has +no hardware implementation. + +We have a cargo extension called [cargo-risczero] that allow users to generate +project templates, install tools for improved user experience, build the binary +using a docker environment and test programs. + +## Requirements + +The target only supports cross compilation and no host tools. The target +supports `alloc` with a default allocator and has experimental support for +`std`. The target expects the binaries to be in ELF. + +The target's execution environment is single threaded, non-preemptive, and does +not support any privileged instructions, nor unaligned accesses. At the time of +writing the VM has 192 MB of memory and text/data, heap, and stack need to be +with in the address range `0x400` - `0x0C000000`. The binaries themselves 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. + +Calling `extern "C"` on the target uses the C calling convention outlined in the +[RISC-V specification]. + +## Building for the zkVM + +Programs for the zkVM could be built by adding it to the `target` list in +`config.toml`. However, we recommend building programs in our starter template +generated by the [cargo-risczero] utility and the [risc0-build] crate. This +crate calls `rustc` with `-C "link-arg=-Ttext=` so that it maps the text in the +appropriate location as well as generating variables that represent the ELF and +a unique ID associated with the ELF. The starter template provides developers +with system calls that are useful to zero knowledge computing such as writing to +the public output, hashing using sha256, and multiply big integers. + +## Building Rust programs + +Rust does not yet ship pre-compiled artifacts for this target. To compile for +this target, you will either need to build Rust with the target enabled (see +"Building the target" above). We do not recommend using `build-std` as we have +run into issues building core in the past on our starter template. An alternate +solution is to download the risc0 tool chain by running `cargo risczero install`. + +## Testing + +Note: the target is implemented as a software emulator called the zkVM and there +is no hardware implementation of the target. + +The most practical way to test the target program is to use our starter template +that can be generated by using the `cargo risczero new` command. The template +generates a sample "host" and "guest" code. The guest code compiled to the +target (which is RV32IM) whereas the "host" code is compiled to run on the +programmer's machine running either a Linux distribution or macOS. The host +program is responsible for running the guest binary on the zkVM and retrieving +its public output. + +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 RV32IM +target. On clang and ld.lld linker, it can be generated using the +`-march=rv32im`, `-mabi=ilp32` with llvm features flag `features=+m` and llvm +target `riscv32-unknown-none`. + +[RISC-V specification]: https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf +[cargo-risczero]: https://docs.rs/cargo-risczero/latest/cargo_risczero/ +[risc0-build]: https://crates.io/crates/risc0-build diff --git a/src/tools/build-manifest/src/main.rs b/src/tools/build-manifest/src/main.rs index 9ac97236f19c8..1ef8cf7de3cd5 100644 --- a/src/tools/build-manifest/src/main.rs +++ b/src/tools/build-manifest/src/main.rs @@ -121,6 +121,7 @@ static TARGETS: &[&str] = &[ "powerpc64-unknown-linux-gnu", "powerpc64le-unknown-linux-gnu", "riscv32i-unknown-none-elf", + "riscv32im-risc0-zkvm-elf", "riscv32im-unknown-none-elf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf", diff --git a/tests/assembly/targets/targets-elf.rs b/tests/assembly/targets/targets-elf.rs index 72a35f38ecadb..6eec05e85ac71 100644 --- a/tests/assembly/targets/targets-elf.rs +++ b/tests/assembly/targets/targets-elf.rs @@ -402,6 +402,9 @@ // revisions: riscv32i_unknown_none_elf // [riscv32i_unknown_none_elf] compile-flags: --target riscv32i-unknown-none-elf // [riscv32i_unknown_none_elf] needs-llvm-components: riscv +// revisions: riscv32im_risc0_zkvm_elf +// [riscv32im_risc0_zkvm_elf] compile-flags: --target riscv32im-risc0-zkvm-elf +// [riscv32im_risc0_zkvm_elf] needs-llvm-components: riscv // revisions: riscv32im_unknown_none_elf // [riscv32im_unknown_none_elf] compile-flags: --target riscv32im-unknown-none-elf // [riscv32im_unknown_none_elf] needs-llvm-components: riscv diff --git a/tests/ui/check-cfg/well-known-values.stderr b/tests/ui/check-cfg/well-known-values.stderr index 9d134dcfcfd88..814d473619777 100644 --- a/tests/ui/check-cfg/well-known-values.stderr +++ b/tests/ui/check-cfg/well-known-values.stderr @@ -179,7 +179,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE` LL | target_os = "_UNEXPECTED_VALUE", | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: expected values for `target_os` are: `aix`, `android`, `cuda`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `macos`, `netbsd`, `none`, `nto`, `openbsd`, `psp`, `redox`, `solaris`, `solid_asp3`, `teeos`, `tvos`, `uefi`, `unknown`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous` + = note: expected values for `target_os` are: `aix`, `android`, `cuda`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `macos`, `netbsd`, `none`, `nto`, `openbsd`, `psp`, `redox`, `solaris`, `solid_asp3`, `teeos`, `tvos`, `uefi`, `unknown`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`, `zkvm` = note: see for more information about checking conditional configuration warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE` @@ -208,7 +208,7 @@ warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE` LL | target_vendor = "_UNEXPECTED_VALUE", | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: expected values for `target_vendor` are: `apple`, `espressif`, `fortanix`, `ibm`, `kmc`, `nintendo`, `nvidia`, `pc`, `sony`, `sun`, `unikraft`, `unknown`, `uwp`, `win7`, `wrs` + = note: expected values for `target_vendor` are: `apple`, `espressif`, `fortanix`, `ibm`, `kmc`, `nintendo`, `nvidia`, `pc`, `risc0`, `sony`, `sun`, `unikraft`, `unknown`, `uwp`, `win7`, `wrs` = note: see for more information about checking conditional configuration warning: unexpected `cfg` condition value: `_UNEXPECTED_VALUE` @@ -252,7 +252,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`, `android`, `cuda`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `macos`, `netbsd`, `none`, `nto`, `openbsd`, `psp`, `redox`, `solaris`, `solid_asp3`, `teeos`, `tvos`, `uefi`, `unknown`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous` + = note: expected values for `target_os` are: `aix`, `android`, `cuda`, `dragonfly`, `emscripten`, `espidf`, `freebsd`, `fuchsia`, `haiku`, `hermit`, `horizon`, `hurd`, `illumos`, `ios`, `l4re`, `linux`, `macos`, `netbsd`, `none`, `nto`, `openbsd`, `psp`, `redox`, `solaris`, `solid_asp3`, `teeos`, `tvos`, `uefi`, `unknown`, `vita`, `vxworks`, `wasi`, `watchos`, `windows`, `xous`, `zkvm` = note: see for more information about checking conditional configuration warning: 26 warnings emitted