From a063344dae6bc86a4f0896a71270976deebcdcf6 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 29 Aug 2025 20:57:40 +0200 Subject: [PATCH] risc0: Guest program compilation with stock rust compiler. (#114) --- Cargo.lock | 1 + Cargo.toml | 1 + crates/ere-risc0/Cargo.toml | 1 + crates/ere-risc0/src/compile.rs | 2 +- crates/ere-risc0/src/compile_stock_rust.rs | 123 +++++++++++++++++ crates/ere-risc0/src/error.rs | 10 +- crates/ere-risc0/src/kernel_elf/v1compat.elf | Bin 0 -> 9136 bytes crates/ere-risc0/src/lib.rs | 23 +++- docker/risc0/Dockerfile | 3 + tests/risc0/stock_nightly_no_std/Cargo.toml | 7 + tests/risc0/stock_nightly_no_std/src/main.rs | 26 ++++ .../stock_nightly_no_std/src/risc0_rt.rs | 128 ++++++++++++++++++ 12 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 crates/ere-risc0/src/compile_stock_rust.rs create mode 100755 crates/ere-risc0/src/kernel_elf/v1compat.elf create mode 100644 tests/risc0/stock_nightly_no_std/Cargo.toml create mode 100644 tests/risc0/stock_nightly_no_std/src/main.rs create mode 100644 tests/risc0/stock_nightly_no_std/src/risc0_rt.rs diff --git a/Cargo.lock b/Cargo.lock index 4dad802..c4171c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2351,6 +2351,7 @@ dependencies = [ "build-utils", "bytemuck", "cargo_metadata 0.19.2", + "risc0-binfmt", "risc0-build", "risc0-zkvm", "serde", diff --git a/Cargo.toml b/Cargo.toml index 4f8fbf6..5a9e2bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,6 +74,7 @@ pico-sdk = { git = "https://github.com/brevis-network/pico.git", tag = "v1.1.7" # Risc0 dependencies risc0-build = "3.0.1" risc0-zkvm = { version = "3.0.1", default-features = false } +risc0-binfmt = { version = "3.0.1", default-features = false } # SP1 dependencies sp1-sdk = "5.1.0" diff --git a/crates/ere-risc0/Cargo.toml b/crates/ere-risc0/Cargo.toml index 205654f..8c05a9a 100644 --- a/crates/ere-risc0/Cargo.toml +++ b/crates/ere-risc0/Cargo.toml @@ -17,6 +17,7 @@ tracing.workspace = true # Risc0 dependencies risc0-build = { workspace = true, features = ["unstable"] } risc0-zkvm = { workspace = true, features = ["client", "unstable"] } +risc0-binfmt.workspace = true # Local dependencies zkvm-interface.workspace = true diff --git a/crates/ere-risc0/src/compile.rs b/crates/ere-risc0/src/compile.rs index b2f4b37..156eb67 100644 --- a/crates/ere-risc0/src/compile.rs +++ b/crates/ere-risc0/src/compile.rs @@ -26,7 +26,7 @@ pub fn compile_risc0_program(guest_directory: &Path) -> Result 1.81 + "-C", + // Start of the code section + "link-arg=-Ttext=0x00200800", + "-C", + "link-arg=--fatal-warnings", + "-C", + "panic=abort", + "--cfg", + "getrandom_backend=\"custom\"", +]; +const CARGO_ARGS: &[&str] = &[ + "build", + "--target", + TARGET_TRIPLE, + "--release", + // For bare metal we have to build core and alloc + "-Zbuild-std=core,alloc", +]; + +pub fn compile_risc0_program_stock_rust( + guest_directory: &Path, + toolchain: &String, +) -> Result { + wrap_into_risc0_program(compile_program_stock_rust(guest_directory, toolchain)?) +} + +fn compile_program_stock_rust( + guest_directory: &Path, + toolchain: &String, +) -> Result, CompileError> { + let metadata = MetadataCommand::new().current_dir(guest_directory).exec()?; + let package = metadata + .root_package() + .ok_or_else(|| CompileError::MissingPackageName { + path: guest_directory.to_path_buf(), + })?; + + let plus_toolchain = format!("+{}", toolchain); + let mut cargo_args = [plus_toolchain.as_str()].to_vec(); + cargo_args.append(&mut CARGO_ARGS.to_vec()); + + let encoded_rust_flags = RUSTFLAGS.to_vec().join(CARGO_ENCODED_RUSTFLAGS_SEPARATOR); + + let target_direcotry = guest_directory + .join("target") + .join(TARGET_TRIPLE) + .join("release"); + + // Remove target directory. + if target_direcotry.exists() { + fs::remove_dir_all(&target_direcotry).unwrap(); + } + + let result = Command::new("cargo") + .current_dir(guest_directory) + .env("CARGO_ENCODED_RUSTFLAGS", &encoded_rust_flags) + .args(cargo_args) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .status() + .map_err(|source| CompileError::BuildFailure { + source: source.into(), + crate_path: guest_directory.to_path_buf(), + }); + + if result.is_err() { + return Err(result.err().unwrap()); + } + + let elf_path = target_direcotry.join(&package.name); + + fs::read(&elf_path).map_err(|e| CompileError::ReadFile { + path: elf_path, + source: e, + }) +} + +fn wrap_into_risc0_program(elf: Vec) -> Result { + let program = ProgramBinary::new(elf.as_slice(), V1COMPAT_ELF); + let image_id = program.compute_image_id()?; + info!("Risc0 program compiled OK - {} bytes", elf.len()); + info!("Image ID - {image_id}"); + + Ok(Risc0Program { + elf: program.encode(), + image_id, + }) +} + +#[cfg(test)] +mod tests { + use crate::compile_stock_rust::compile_risc0_program_stock_rust; + use test_utils::host::testing_guest_directory; + + #[test] + fn test_stock_compiler_impl() { + let guest_directory = testing_guest_directory("risc0", "stock_nightly_no_std"); + let result = compile_risc0_program_stock_rust(&guest_directory, &"nightly".to_string()); + assert!(result.is_ok(), "Risc0 guest program compilation failure."); + assert!( + !result.unwrap().elf.is_empty(), + "ELF bytes should not be empty." + ); + } +} diff --git a/crates/ere-risc0/src/error.rs b/crates/ere-risc0/src/error.rs index cd8e2fb..dbb5522 100644 --- a/crates/ere-risc0/src/error.rs +++ b/crates/ere-risc0/src/error.rs @@ -20,13 +20,21 @@ pub enum CompileError { #[error("Could not find `[package].name` in guest Cargo.toml at {path}")] MissingPackageName { path: PathBuf }, #[error("`risc0_build::build_package` for {crate_path} failed: {source}")] - Risc0BuildFailure { + BuildFailure { #[source] source: anyhow::Error, crate_path: PathBuf, }, #[error("`risc0_build::build_package` succeeded but failed to find guest")] Risc0BuildMissingGuest, + #[error("Failed to read file at {path}: {source}")] + ReadFile { + path: PathBuf, + #[source] + source: std::io::Error, + }, + #[error("ELF binary image calculation failure : {0}")] + ImageIDCalculationFailure(#[from] anyhow::Error), } impl CompileError { diff --git a/crates/ere-risc0/src/kernel_elf/v1compat.elf b/crates/ere-risc0/src/kernel_elf/v1compat.elf new file mode 100755 index 0000000000000000000000000000000000000000..250672c9083903ba76ec9f2d933a4882b2413920 GIT binary patch literal 9136 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 O`tjd)s|pPIy#E9P8Q~QG literal 0 HcmV?d00001 diff --git a/crates/ere-risc0/src/lib.rs b/crates/ere-risc0/src/lib.rs index e96a5e1..90c06d1 100644 --- a/crates/ere-risc0/src/lib.rs +++ b/crates/ere-risc0/src/lib.rs @@ -2,6 +2,7 @@ use crate::{ compile::{Risc0Program, compile_risc0_program}, + compile_stock_rust::compile_risc0_program_stock_rust, error::Risc0Error, output::deserialize_from, }; @@ -20,6 +21,8 @@ use zkvm_interface::{ include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs")); mod compile; +mod compile_stock_rust; + mod error; mod output; @@ -58,7 +61,14 @@ impl Compiler for RV32_IM_RISC0_ZKVM_ELF { type Program = Risc0Program; fn compile(&self, guest_directory: &Path) -> Result { - compile_risc0_program(guest_directory).map_err(Risc0Error::from) + let toolchain = env::var("ERE_GUEST_TOOLCHAIN").unwrap_or_else(|_error| "risc0".into()); + match toolchain.as_str() { + "risc0" => Ok(compile_risc0_program(guest_directory)?), + _ => Ok(compile_risc0_program_stock_rust( + guest_directory, + &toolchain, + )?), + } } } @@ -284,6 +294,17 @@ mod tests { run_zkvm_execute(&zkvm, &io); } + #[test] + fn test_execute_nightly() { + let guest_directory = testing_guest_directory("risc0", "stock_nightly_no_std"); + let program = + compile_risc0_program_stock_rust(&guest_directory, &"nightly".to_string()).unwrap(); + let zkvm = EreRisc0::new(program, ProverResourceType::Cpu).unwrap(); + + let result = zkvm.execute(&BasicProgramIo::empty()); + assert!(result.is_ok(), "Risc0 execution failure"); + } + #[test] fn test_execute_invalid_inputs() { let program = basic_program(); diff --git a/docker/risc0/Dockerfile b/docker/risc0/Dockerfile index 30c2bc3..4f93810 100644 --- a/docker/risc0/Dockerfile +++ b/docker/risc0/Dockerfile @@ -38,6 +38,9 @@ ENV RISC0_VERSION="3.0.1" \ RISC0_CPP_VERSION="2024.1.5" \ RISC0_RUST_VERSION="1.88.0" +# Add `rust-src` component to enable std build for nightly rust. +RUN rustup +nightly component add rust-src + # Run the Risc0 SDK installation script # It will use the RISC0_VERSION, RISC0_CPP_VERSION and RISC0_RUST_VERSION defined above. RUN /tmp/install_risc0_sdk.sh && rm /tmp/install_risc0_sdk.sh diff --git a/tests/risc0/stock_nightly_no_std/Cargo.toml b/tests/risc0/stock_nightly_no_std/Cargo.toml new file mode 100644 index 0000000..c00518f --- /dev/null +++ b/tests/risc0/stock_nightly_no_std/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "addition_no_std" +edition = "2021" + +[dependencies] + +[workspace] diff --git a/tests/risc0/stock_nightly_no_std/src/main.rs b/tests/risc0/stock_nightly_no_std/src/main.rs new file mode 100644 index 0000000..338be42 --- /dev/null +++ b/tests/risc0/stock_nightly_no_std/src/main.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] +extern crate alloc; + +use alloc::vec::Vec; +use core::sync::atomic::Ordering; +use core::sync::atomic::AtomicU16; + +mod risc0_rt; + +fn main() { + let a: AtomicU16 = core::hint::black_box(AtomicU16::new(5)); + let b: AtomicU16 = core::hint::black_box(AtomicU16::new(7)); + + if a.load(Ordering::SeqCst) + b.load(Ordering::SeqCst) != 12 { + panic!("Something went wrong!"); + } + + let mut v: Vec = Vec::new(); + v.push(AtomicU16::new(5)); + v.push(AtomicU16::new(7)); + + if v[0].load(Ordering::SeqCst) + v[1].load(Ordering::SeqCst) != 12 { + panic!("Something went wrong!"); + } +} diff --git a/tests/risc0/stock_nightly_no_std/src/risc0_rt.rs b/tests/risc0/stock_nightly_no_std/src/risc0_rt.rs new file mode 100644 index 0000000..f8088ed --- /dev/null +++ b/tests/risc0/stock_nightly_no_std/src/risc0_rt.rs @@ -0,0 +1,128 @@ +use core::alloc::{GlobalAlloc, Layout}; +// Import user `main` function +use crate::main; + +// 1. Init global pointer (GP). It's used to optimize jumps by linker. Linker can change jumping from PC(Program Counter) based to GP based. +// 2. Init stack pointer to the value STACK_TOP. It's stored in sp register. +// 3. Call __start function defined below. +// `__global_pointer$` is set by the linker. Its value depends on linker optimization. https://www.programmersought.com/article/77722901592/ +core::arch::global_asm!( + r#" +.section .text._start; +.globl _start; +_start: + .option push; + .option norelax; + la gp, __global_pointer$; + .option pop; + la sp, {0} + lw sp, 0(sp) + call __start; +"#, + sym STACK_TOP +); + +static STACK_TOP: u32 = 0x0020_0400; + +// 1. Call `main` user function +// 2. Call system halt environment function. It's defined by sp1 vm. +#[unsafe(no_mangle)] +fn __start(_argc: isize, _argv: *const *const u8) -> isize { + main(); + + const EMPTY_OUTPUT: [u32; 8] = [0; 8]; + unsafe { + core::arch::asm!( + "ecall", + in("t0") 0, + in("a0") 0, + in("a1") &EMPTY_OUTPUT, + ) + }; + + unreachable!() +} + +// Implement panic handling by calling undefined instruction. To be fixed. We need to support `fence` to be able to use e.i. `portable_atomic` lib. +#[panic_handler] +fn panic_impl(_panic_info: &core::panic::PanicInfo) -> ! { + unsafe { core::arch::asm!("fence", options(noreturn)) }; +} + +/// A simple heap allocator. +/// +/// Allocates memory from left to right, without any deallocation. +struct SimpleAlloc; + +unsafe impl GlobalAlloc for SimpleAlloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + unsafe { + sys_alloc_aligned(layout.size(), layout.align()) + } + } + + unsafe fn dealloc(&self, _: *mut u8, _: Layout) {} +} + +#[global_allocator] +static HEAP: SimpleAlloc = SimpleAlloc; + +pub const MAX_MEMORY: usize = 0x78000000; +static mut HEAP_POS: usize = 0; +#[allow(clippy::missing_safety_doc)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sys_alloc_aligned(bytes: usize, align: usize) -> *mut u8 { + unsafe extern "C" { + // https://lld.llvm.org/ELF/linker_script.html#sections-command + // `_end` is the last global variable defined by the linker. Its address is the beginning of heap data. + unsafe static _end: u8; + } + + // SAFETY: Single threaded, so nothing else can touch this while we're working. + let mut heap_pos = unsafe { HEAP_POS }; + + if heap_pos == 0 { + heap_pos = unsafe { (&_end) as *const u8 as usize }; + } + + let offset = heap_pos & (align - 1); + if offset != 0 { + heap_pos += align - offset; + } + + let ptr = heap_pos as *mut u8; + let (heap_pos, overflowed) = heap_pos.overflowing_add(bytes); + + if overflowed || MAX_MEMORY < heap_pos { + panic!("Memory limit exceeded (0x78000000)"); + } + + unsafe { HEAP_POS = heap_pos }; + ptr +} + +// Assume single-threaded. +#[cfg(all(target_arch = "riscv32", target_feature = "a"))] +#[unsafe(no_mangle)] +fn _critical_section_1_0_acquire() -> u32 +{ + return 0; +} + +#[cfg(all(target_arch = "riscv32", target_feature = "a"))] +#[unsafe(no_mangle)] +fn _critical_section_1_0_release(_: u32) +{} + +// Assume single-threaded. +#[cfg(all(target_arch = "riscv64", target_feature = "a"))] +#[unsafe(no_mangle)] +fn _critical_section_1_0_acquire() -> u64 +{ + return 0; +} + +#[cfg(all(target_arch = "riscv64", target_feature = "a"))] +#[unsafe(no_mangle)] +fn _critical_section_1_0_release(_: u64) +{}