diff --git a/.gitignore b/.gitignore index 3b7d6ad7..e4d2efc5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target -notes.md \ No newline at end of file +notes.md +.idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7fec2a34..e756a0bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2270,8 +2270,10 @@ dependencies = [ name = "ere-jolt" version = "0.0.11" dependencies = [ + "anyhow", "ark-serialize 0.5.0", "build-utils", + "cargo_metadata 0.19.2", "common", "jolt", "jolt-core", @@ -2281,6 +2283,7 @@ dependencies = [ "test-utils", "thiserror 2.0.12", "toml", + "tracing", "zkvm-interface", ] @@ -2303,7 +2306,9 @@ dependencies = [ name = "ere-openvm" version = "0.0.11" dependencies = [ + "anyhow", "build-utils", + "cargo_metadata 0.19.2", "openvm-build", "openvm-circuit", "openvm-continuations", @@ -2314,6 +2319,7 @@ dependencies = [ "test-utils", "thiserror 2.0.12", "toml", + "tracing", "zkvm-interface", ] @@ -2321,8 +2327,10 @@ dependencies = [ name = "ere-pico" version = "0.0.11" dependencies = [ + "anyhow", "bincode", "build-utils", + "cargo_metadata 0.19.2", "pico-sdk", "pico-vm", "serde", @@ -2340,6 +2348,7 @@ dependencies = [ "build-utils", "bytemuck", "cargo_metadata 0.19.2", + "risc0-binfmt", "risc0-build", "risc0-zkvm", "serde", @@ -2372,6 +2381,7 @@ dependencies = [ "bincode", "blake3", "build-utils", + "cargo_metadata 0.19.2", "dashmap", "serde", "serde_json", diff --git a/crates/ere-jolt/Cargo.toml b/crates/ere-jolt/Cargo.toml index 4f44b439..791be8e9 100644 --- a/crates/ere-jolt/Cargo.toml +++ b/crates/ere-jolt/Cargo.toml @@ -6,10 +6,13 @@ rust-version.workspace = true license.workspace = true [dependencies] +anyhow.workspace = true +cargo_metadata.workspace = true serde.workspace = true tempfile.workspace = true thiserror.workspace = true toml.workspace = true +tracing.workspace = true # Jolt dependencies ark-serialize = { workspace = true, features = ["derive"] } diff --git a/crates/ere-jolt/src/compile_stock_rust.rs b/crates/ere-jolt/src/compile_stock_rust.rs new file mode 100644 index 00000000..2cf8250a --- /dev/null +++ b/crates/ere-jolt/src/compile_stock_rust.rs @@ -0,0 +1,154 @@ +use std::fs; +use std::fs::File; +use crate::error::CompileError; +use std::io::Write; +use std::path::Path; +use std::process::{Command}; +use cargo_metadata::MetadataCommand; +use tempfile::TempDir; +use tracing::info; + +static CARGO_ENCODED_RUSTFLAGS_SEPARATOR: &str = "\x1f"; + +pub fn compile_jolt_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 target_name = "riscv32im-unknown-none-elf"; + let plus_toolchain = format!("+{}", toolchain); + + let args = [ + plus_toolchain.as_str(), + "build", + "--target", + target_name, + "--release", + // For bare metal we have to build core and alloc + "-Zbuild-std=core,alloc", + "--features", + "guest" + ]; + + let temp_output_dir = TempDir::new_in(guest_directory).unwrap(); + let temp_output_dir_path = temp_output_dir.path(); + + let linker_path = temp_output_dir_path.join(format!("{}.ld", &package.name)); + let linker_path_str = linker_path.to_str().unwrap(); + + let linker_script = LINKER_SCRIPT_TEMPLATE + .replace("{MEMORY_SIZE}", &DEFAULT_MEMORY_SIZE.to_string()) + .replace("{STACK_SIZE}", &DEFAULT_STACK_SIZE.to_string()); + + let mut file = File::create(&linker_path).expect("could not create linker file"); + file.write_all(linker_script.as_bytes()) + .expect("could not save linker"); + + let rust_flags = [ + "-C", + &format!("link-arg=-T{}", linker_path_str), + "-C", + "passes=lower-atomic", + "-C", + "panic=abort", + "-C", + "strip=symbols", + "-C", + "opt-level=z", + ]; + + let encoded_rust_flags = rust_flags + .into_iter() + .collect::>() + .join(CARGO_ENCODED_RUSTFLAGS_SEPARATOR); + + let result = Command::new("cargo") + .current_dir(guest_directory) + .env("CARGO_ENCODED_RUSTFLAGS", &encoded_rust_flags) + .args(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 = + guest_directory + .join("target") + .join(target_name) + .join("release") + .join(&package.name); + + let elf = fs::read(&elf_path).map_err(|e| CompileError::ReadElfFailed { + path: elf_path, + source: e, + })?; + + info!("Jolt program compiled (toolchain {}) OK - {} bytes", toolchain, elf.len()); + + Ok(elf) +} + +pub const DEFAULT_MEMORY_SIZE: u64 = 10 * 1024 * 1024; +pub const DEFAULT_STACK_SIZE: u64 = 4096; + +const LINKER_SCRIPT_TEMPLATE: &str = r#" +MEMORY { + program (rwx) : ORIGIN = 0x80000000, LENGTH = {MEMORY_SIZE} +} + +SECTIONS { + .text.boot : { + *(.text.boot) + } > program + + .text : { + *(.text) + } > program + + .data : { + *(.data) + } > program + + .bss : { + *(.bss) + } > program + + . = ALIGN(8); + . = . + {STACK_SIZE}; + _STACK_PTR = .; + . = ALIGN(8); + _HEAP_PTR = .; +} +"#; + + +#[cfg(test)] +mod tests { + use test_utils::host::testing_guest_directory; + use crate::compile_stock_rust::compile_jolt_program_stock_rust; + + #[test] + fn test_stock_compiler_impl() { + let guest_directory = testing_guest_directory( + "jolt", + "stock_nightly_no_std"); + let program = compile_jolt_program_stock_rust(&guest_directory, &"nightly".to_string()); + + assert!(!program.is_err(), "jolt compilation failed"); + assert!(!program.unwrap().is_empty(), "ELF bytes should not be empty."); + } +} diff --git a/crates/ere-jolt/src/error.rs b/crates/ere-jolt/src/error.rs index 1d7283d1..5025cce5 100644 --- a/crates/ere-jolt/src/error.rs +++ b/crates/ere-jolt/src/error.rs @@ -34,10 +34,20 @@ pub enum CompileError { }, #[error("Failed to build guest")] BuildFailed, + #[error("`openvm` build failure for {crate_path} failed: {source}")] + BuildFailure { + #[source] + source: anyhow::Error, + crate_path: PathBuf, + }, #[error("Failed to read elf at {path}: {source}")] ReadElfFailed { source: io::Error, path: PathBuf }, #[error("Failed to set current directory to {path}: {source}")] SetCurrentDirFailed { source: io::Error, path: PathBuf }, + #[error("`cargo metadata` failed: {0}")] + MetadataCommand(#[from] cargo_metadata::Error), + #[error("Could not find `[package].name` in guest Cargo.toml at {path}")] + MissingPackageName { path: PathBuf }, } #[derive(Debug, Error)] diff --git a/crates/ere-jolt/src/lib.rs b/crates/ere-jolt/src/lib.rs index 82bc40bc..2cb21eb7 100644 --- a/crates/ere-jolt/src/lib.rs +++ b/crates/ere-jolt/src/lib.rs @@ -21,11 +21,13 @@ use zkvm_interface::{ Compiler, Input, ProgramExecutionReport, ProgramProvingReport, Proof, ProverResourceType, PublicValues, zkVM, zkVMError, }; +use compile_stock_rust::compile_jolt_program_stock_rust; include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs")); mod error; mod jolt_methods; mod utils; +mod compile_stock_rust; #[allow(non_camel_case_types)] pub struct JOLT_TARGET; @@ -35,31 +37,40 @@ impl Compiler for JOLT_TARGET { type Program = Vec; - fn compile(&self, guest_dir: &Path) -> Result { - // Change current directory for `Program::build` to build guest program. - set_current_dir(guest_dir).map_err(|source| CompileError::SetCurrentDirFailed { - source, - path: guest_dir.to_path_buf(), - })?; - - let package_name = package_name_from_manifest(Path::new("Cargo.toml"))?; - - // Note that if this fails, it will panic, hence we need to catch it. - let elf_path = std::panic::catch_unwind(|| { - let mut program = Program::new(&package_name); - program.set_std(true); - program.build(DEFAULT_TARGET_DIR); - program.elf.unwrap() - }) + fn compile(&self, guest_directory: &Path) -> Result { + let toolchain = + env::var("ERE_GUEST_TOOLCHAIN").unwrap_or_else(|_error| "jolt".into()); + match toolchain.as_str() { + "jolt" => Ok(compile_jolt_program(guest_directory)?), + _ => Ok(compile_jolt_program_stock_rust(guest_directory, &toolchain)?), + } + } +} + +fn compile_jolt_program(guest_directory: &Path) -> Result, JoltError> { + // Change current directory for `Program::build` to build guest program. + set_current_dir(guest_directory).map_err(|source| CompileError::SetCurrentDirFailed { + source, + path: guest_directory.to_path_buf(), + })?; + + let package_name = package_name_from_manifest(Path::new("Cargo.toml"))?; + + // Note that if this fails, it will panic, hence we need to catch it. + let elf_path = std::panic::catch_unwind(|| { + let mut program = Program::new(&package_name); + program.set_std(true); + program.build(DEFAULT_TARGET_DIR); + program.elf.unwrap() + }) .map_err(|_| CompileError::BuildFailed)?; - let elf = fs::read(&elf_path).map_err(|source| CompileError::ReadElfFailed { - source, - path: elf_path.to_path_buf(), - })?; + let elf = fs::read(&elf_path).map_err(|source| CompileError::ReadElfFailed { + source, + path: elf_path.to_path_buf(), + })?; - Ok(elf) - } + Ok(elf) } #[derive(CanonicalSerialize, CanonicalDeserialize)] @@ -174,7 +185,7 @@ pub fn program(elf: &[u8]) -> Result<(TempDir, jolt::host::Program), zkVMError> mod tests { use super::*; use std::sync::OnceLock; - use test_utils::host::testing_guest_directory; + use test_utils::host::{run_zkvm_execute, testing_guest_directory}; static BASIC_PRORGAM: OnceLock> = OnceLock::new(); @@ -193,4 +204,19 @@ mod tests { let elf_bytes = basic_program(); assert!(!elf_bytes.is_empty(), "ELF bytes should not be empty."); } + + #[test] + fn test_execute() { + let elf_bytes = basic_program(); + let zkvm = EreJolt::new(elf_bytes, ProverResourceType::Cpu).unwrap(); + run_zkvm_execute(&zkvm, &Input::new()); + } + + #[test] + fn test_execute_nightly() { + let guest_directory = testing_guest_directory("jolt", "stock_nightly_no_std"); + let program = compile_jolt_program_stock_rust(&guest_directory, &"nightly".to_string()).unwrap(); + let zkvm = EreJolt::new(program, ProverResourceType::Cpu).unwrap(); + run_zkvm_execute(&zkvm, &Input::new()); + } } diff --git a/crates/ere-openvm/Cargo.toml b/crates/ere-openvm/Cargo.toml index 47e2b323..a07eccc8 100644 --- a/crates/ere-openvm/Cargo.toml +++ b/crates/ere-openvm/Cargo.toml @@ -6,8 +6,11 @@ rust-version.workspace = true license.workspace = true [dependencies] +anyhow.workspace = true serde = { workspace = true, features = ["derive"] } thiserror.workspace = true +cargo_metadata.workspace = true +tracing.workspace = true toml.workspace = true # OpenVM dependencies diff --git a/crates/ere-openvm/src/compile_stock_rust.rs b/crates/ere-openvm/src/compile_stock_rust.rs new file mode 100644 index 00000000..be97424c --- /dev/null +++ b/crates/ere-openvm/src/compile_stock_rust.rs @@ -0,0 +1,139 @@ +use std::fs; +use crate::error::CompileError; +use std::path::{Path}; +use std::process::{Command}; +use cargo_metadata::MetadataCommand; +use tracing::info; +use openvm_sdk::config::{AppConfig, SdkVmConfig, DEFAULT_APP_LOG_BLOWUP, DEFAULT_LEAF_LOG_BLOWUP}; +use openvm_stark_sdk::config::FriParameters; +use crate::OpenVMProgram; + +static CARGO_ENCODED_RUSTFLAGS_SEPARATOR: &str = "\x1f"; + +pub fn compile_openvm_program_stock_rust( + guest_directory: &Path, + toolchain: &String, +) -> Result { + + 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 target_name = "riscv32ima-unknown-none-elf"; + let plus_toolchain = format!("+{}", toolchain); + + let args = [ + plus_toolchain.as_str(), + "build", + "--target", + target_name, + "--release", + // For bare metal we have to build core and alloc + "-Zbuild-std=core,alloc", + ]; + + let rust_flags = [ + "-C", + "passes=lower-atomic", // Only for rustc > 1.81 + "-C", + // Start of the code section + "link-arg=-Ttext=0x00201000", + "-C", + // The lowest memory location that will be used when your program is loaded + "link-arg=--image-base=0x00200800", + "-C", + "panic=abort", + "--cfg", + "getrandom_backend=\"custom\"", + "-C", + "llvm-args=-misched-prera-direction=bottomup", + "-C", + "llvm-args=-misched-postra-direction=bottomup", + ]; + + let encoded_rust_flags = rust_flags + .into_iter() + .collect::>() + .join(CARGO_ENCODED_RUSTFLAGS_SEPARATOR); + + let result = Command::new("cargo") + .current_dir(guest_directory) + .env("CARGO_ENCODED_RUSTFLAGS", &encoded_rust_flags) + .args(args) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .status() + .map_err(|source| CompileError::OpenVMBuildFailure { + source: source.into(), + crate_path: guest_directory.to_path_buf() + }); + + if result.is_err() { + return Err(result.err().unwrap()); + } + + let elf_path = + guest_directory + .join("target") + .join(target_name) + .join("release") + .join(&package.name); + + let elf = fs::read(&elf_path).map_err(|e| CompileError::ReadElfFailed { + path: elf_path, + source: e, + })?; + + let app_config_path = guest_directory.join("openvm.toml"); + let app_config = if app_config_path.exists() { + let toml = fs::read_to_string(&app_config_path).map_err(|source| { + CompileError::ReadConfigFailed { + source, + path: app_config_path.to_path_buf(), + } + })?; + toml::from_str(&toml).map_err(CompileError::DeserializeConfigFailed)? + } else { + // The default `AppConfig` copied from https://github.com/openvm-org/openvm/blob/ca36de3/crates/cli/src/default.rs#L31. + AppConfig { + app_fri_params: FriParameters::standard_with_100_bits_conjectured_security( + DEFAULT_APP_LOG_BLOWUP, + ) + .into(), + // By default it supports RISCV32IM with IO but no precompiles. + app_vm_config: SdkVmConfig::builder() + .system(Default::default()) + .rv32i(Default::default()) + .rv32m(Default::default()) + .io(Default::default()) + .build(), + leaf_fri_params: FriParameters::standard_with_100_bits_conjectured_security( + DEFAULT_LEAF_LOG_BLOWUP, + ) + .into(), + compiler_options: Default::default(), + } + }; + + info!("Openvm program compiled (toolchain {}) OK - {} bytes", toolchain, elf.len()); + + Ok(OpenVMProgram { elf, app_config }) +} + +#[cfg(test)] +mod tests { + use test_utils::host::testing_guest_directory; + use crate::compile_stock_rust::compile_openvm_program_stock_rust; + + #[test] + fn test_stock_compiler_impl() { + let guest_directory = testing_guest_directory( + "openvm", + "stock_nightly_no_std"); + let program = compile_openvm_program_stock_rust(&guest_directory, &"nightly".to_string()).unwrap(); + assert!(!program.elf.is_empty(), "ELF bytes should not be empty."); + } +} diff --git a/crates/ere-openvm/src/error.rs b/crates/ere-openvm/src/error.rs index 27ff83f1..37e4db52 100644 --- a/crates/ere-openvm/src/error.rs +++ b/crates/ere-openvm/src/error.rs @@ -34,6 +34,12 @@ pub enum OpenVMError { pub enum CompileError { #[error("Failed to build guest, code: {0}")] BuildFailed(i32), + #[error("`openvm` build failure for {crate_path} failed: {source}")] + OpenVMBuildFailure { + #[source] + source: anyhow::Error, + crate_path: PathBuf, + }, #[error("Guest building skipped (OPENVM_SKIP_BUILD is set)")] BuildSkipped, #[error("Missing to find unique elf: {0}")] @@ -44,6 +50,10 @@ pub enum CompileError { ReadConfigFailed { source: io::Error, path: PathBuf }, #[error("Failed to deserialize OpenVM's config file: {0}")] DeserializeConfigFailed(toml::de::Error), + #[error("`cargo metadata` failed: {0}")] + MetadataCommand(#[from] cargo_metadata::Error), + #[error("Could not find `[package].name` in guest Cargo.toml at {path}")] + MissingPackageName { path: PathBuf }, } #[derive(Debug, Error)] diff --git a/crates/ere-openvm/src/lib.rs b/crates/ere-openvm/src/lib.rs index df123a79..0066128c 100644 --- a/crates/ere-openvm/src/lib.rs +++ b/crates/ere-openvm/src/lib.rs @@ -1,6 +1,7 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] use crate::error::{CommonError, CompileError, ExecuteError, OpenVMError, ProveError, VerifyError}; +use crate::compile_stock_rust::compile_openvm_program_stock_rust; use openvm_build::GuestOptions; use openvm_circuit::arch::instructions::exe::VmExe; use openvm_continuations::verifier::internal::types::VmStarkProof; @@ -14,12 +15,14 @@ use openvm_sdk::{ use openvm_stark_sdk::{config::FriParameters, openvm_stark_backend::p3_field::PrimeField32}; use openvm_transpiler::{elf::Elf, openvm_platform::memory::MEM_SIZE}; use serde::{Deserialize, Serialize, de::DeserializeOwned}; -use std::{fs, io::Read, path::Path, sync::Arc, time::Instant}; +use std::{env, fs, io::Read, path::Path, sync::Arc, time::Instant}; use zkvm_interface::{ Compiler, Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof, ProverResourceType, PublicValues, zkVM, zkVMError, }; +mod compile_stock_rust; + include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs")); mod error; @@ -37,56 +40,65 @@ impl Compiler for OPENVM_TARGET { type Program = OpenVMProgram; - // Inlining `openvm_sdk::Sdk::build` in order to get raw elf bytes. fn compile(&self, guest_directory: &Path) -> Result { - let pkg = openvm_build::get_package(guest_directory); - let guest_opts = GuestOptions::default().with_profile("release".to_string()); - let target_dir = match openvm_build::build_guest_package(&pkg, &guest_opts, None, &None) { - Ok(target_dir) => target_dir, - Err(Some(code)) => return Err(CompileError::BuildFailed(code))?, - Err(None) => return Err(CompileError::BuildSkipped)?, - }; - - let elf_path = openvm_build::find_unique_executable(guest_directory, target_dir, &None) - .map_err(|e| CompileError::UniqueElfNotFound(e.into()))?; - let elf = fs::read(&elf_path).map_err(|source| CompileError::ReadElfFailed { - source, - path: elf_path.to_path_buf(), - })?; + let toolchain = + env::var("ERE_GUEST_TOOLCHAIN").unwrap_or_else(|_error| "openvm".into()); + match toolchain.as_str() { + "openvm" => Ok(compile_openvm_program(guest_directory)?), + _ => Ok(compile_openvm_program_stock_rust(guest_directory, &toolchain)?), + } + } +} - let app_config_path = guest_directory.join("openvm.toml"); - let app_config = if app_config_path.exists() { - let toml = fs::read_to_string(&app_config_path).map_err(|source| { - CompileError::ReadConfigFailed { - source, - path: app_config_path.to_path_buf(), - } - })?; - toml::from_str(&toml).map_err(CompileError::DeserializeConfigFailed)? - } else { - // The default `AppConfig` copied from https://github.com/openvm-org/openvm/blob/ca36de3/crates/cli/src/default.rs#L31. - AppConfig { - app_fri_params: FriParameters::standard_with_100_bits_conjectured_security( - DEFAULT_APP_LOG_BLOWUP, - ) +// Inlining `openvm_sdk::Sdk::build` in order to get raw elf bytes. +fn compile_openvm_program(guest_directory: &Path) -> Result { + let pkg = openvm_build::get_package(guest_directory); + let guest_opts = GuestOptions::default().with_profile("release".to_string()); + let target_dir = match openvm_build::build_guest_package(&pkg, &guest_opts, None, &None) { + Ok(target_dir) => target_dir, + Err(Some(code)) => return Err(CompileError::BuildFailed(code))?, + Err(None) => return Err(CompileError::BuildSkipped)?, + }; + + let elf_path = openvm_build::find_unique_executable(guest_directory, target_dir, &None) + .map_err(|e| CompileError::UniqueElfNotFound(e.into()))?; + let elf = fs::read(&elf_path).map_err(|source| CompileError::ReadElfFailed { + source, + path: elf_path.to_path_buf(), + })?; + + let app_config_path = guest_directory.join("openvm.toml"); + let app_config = if app_config_path.exists() { + let toml = fs::read_to_string(&app_config_path).map_err(|source| { + CompileError::ReadConfigFailed { + source, + path: app_config_path.to_path_buf(), + } + })?; + toml::from_str(&toml).map_err(CompileError::DeserializeConfigFailed)? + } else { + // The default `AppConfig` copied from https://github.com/openvm-org/openvm/blob/ca36de3/crates/cli/src/default.rs#L31. + AppConfig { + app_fri_params: FriParameters::standard_with_100_bits_conjectured_security( + DEFAULT_APP_LOG_BLOWUP, + ) .into(), - // By default it supports RISCV32IM with IO but no precompiles. - app_vm_config: SdkVmConfig::builder() - .system(Default::default()) - .rv32i(Default::default()) - .rv32m(Default::default()) - .io(Default::default()) - .build(), - leaf_fri_params: FriParameters::standard_with_100_bits_conjectured_security( - DEFAULT_LEAF_LOG_BLOWUP, - ) + // By default it supports RISCV32IM with IO but no precompiles. + app_vm_config: SdkVmConfig::builder() + .system(Default::default()) + .rv32i(Default::default()) + .rv32m(Default::default()) + .io(Default::default()) + .build(), + leaf_fri_params: FriParameters::standard_with_100_bits_conjectured_security( + DEFAULT_LEAF_LOG_BLOWUP, + ) .into(), - compiler_options: Default::default(), - } - }; + compiler_options: Default::default(), + } + }; - Ok(OpenVMProgram { elf, app_config }) - } + Ok(OpenVMProgram { elf, app_config }) } pub struct EreOpenVM { @@ -246,6 +258,7 @@ mod tests { use test_utils::host::{ BasicProgramIo, Io, run_zkvm_execute, run_zkvm_prove, testing_guest_directory, }; + use crate::compile_stock_rust::compile_openvm_program_stock_rust; fn basic_program() -> OpenVMProgram { static PROGRAM: OnceLock = OnceLock::new(); @@ -278,6 +291,16 @@ mod tests { assert_eq!(io.deserialize_outputs(&zkvm, &public_values), io.outputs()); } + #[test] + fn test_execute_nightly() { + let guest_directory = testing_guest_directory("openvm", "stock_nightly_no_std"); + let program = compile_openvm_program_stock_rust(&guest_directory, &"nightly".to_string()).unwrap(); + let zkvm = EreOpenVM::new(program, ProverResourceType::Cpu).unwrap(); + + let result = zkvm.execute(&BasicProgramIo::empty()); + assert!(!result.is_err(), "Openvm execution failure"); + } + #[test] fn test_execute_invalid_inputs() { let zkvm = basic_program_ere_openvm(); diff --git a/crates/ere-pico/Cargo.toml b/crates/ere-pico/Cargo.toml index 5b1bceb8..32c2735f 100644 --- a/crates/ere-pico/Cargo.toml +++ b/crates/ere-pico/Cargo.toml @@ -6,7 +6,9 @@ rust-version.workspace = true license.workspace = true [dependencies] +anyhow.workspace = true bincode.workspace = true +cargo_metadata.workspace = true serde.workspace = true thiserror.workspace = true diff --git a/crates/ere-pico/src/compile_stock_rust.rs b/crates/ere-pico/src/compile_stock_rust.rs new file mode 100644 index 00000000..ea083097 --- /dev/null +++ b/crates/ere-pico/src/compile_stock_rust.rs @@ -0,0 +1,101 @@ +use std::fs; +use crate::error::PicoError; +use std::path::Path; +use std::process::Command; +use cargo_metadata::MetadataCommand; + +static CARGO_ENCODED_RUSTFLAGS_SEPARATOR: &str = "\x1f"; + +pub fn compile_pico_program_stock_rust( + guest_directory: &Path, + toolchain: &String, +) -> Result, PicoError> { + + let metadata = MetadataCommand::new().current_dir(guest_directory).exec()?; + let package = metadata + .root_package() + .ok_or_else(|| PicoError::MissingPackageName { + path: guest_directory.to_path_buf(), + })?; + + let target_name = "riscv32ima-unknown-none-elf"; + let plus_toolchain = format!("+{}", toolchain); + + let args = [ + plus_toolchain.as_str(), + "build", + "--target", + target_name, + "--release", + // For bare metal we have to build core and alloc + "-Zbuild-std=core,alloc", + ]; + + let rust_flags = [ + "-C", + "passes=lower-atomic", // Only for rustc > 1.81 + "-C", + // Start of the code section + "link-arg=-Ttext=0x00201000", + "-C", + // The lowest memory location that will be used when your program is loaded + "link-arg=--image-base=0x00200800", + "-C", + "panic=abort", + "--cfg", + "getrandom_backend=\"custom\"", + "-C", + "llvm-args=-misched-prera-direction=bottomup", + "-C", + "llvm-args=-misched-postra-direction=bottomup", + ]; + + let encoded_rust_flags = rust_flags + .into_iter() + .collect::>() + .join(CARGO_ENCODED_RUSTFLAGS_SEPARATOR); + + let result = Command::new("cargo") + .current_dir(guest_directory) + .env("CARGO_ENCODED_RUSTFLAGS", &encoded_rust_flags) + .args(args) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .status() + .map_err(|source| PicoError::BuildFailure { + source: source.into(), + crate_path: guest_directory.to_path_buf() + }); + + if result.is_err() { + return Err(result.err().unwrap()); + } + + let elf_path = + guest_directory + .join("target") + .join(target_name) + .join("release") + .join(&package.name); + + fs::read(&elf_path).map_err(|e| PicoError::ReadFile { + path: elf_path, + source: e, + }) +} + +#[cfg(test)] +mod tests { + use test_utils::host::testing_guest_directory; + use crate::compile_stock_rust::compile_pico_program_stock_rust; + + #[test] + fn test_stock_compiler_impl() { + let guest_directory = testing_guest_directory( + "pico", + "stock_nightly_no_std"); + let elf = compile_pico_program_stock_rust(&guest_directory, &"nightly".to_string()); + assert!(!elf.is_err(), "jolt compilation failed"); + assert!(!elf.unwrap().is_empty(), "ELF bytes should not be empty."); + } +} \ No newline at end of file diff --git a/crates/ere-pico/src/error.rs b/crates/ere-pico/src/error.rs index 3eb10969..f8fddcab 100644 --- a/crates/ere-pico/src/error.rs +++ b/crates/ere-pico/src/error.rs @@ -33,4 +33,20 @@ pub enum PicoError { #[source] source: io::Error, }, + #[error("Pico build failure for {crate_path} failed: {source}")] + BuildFailure { + #[source] + source: anyhow::Error, + crate_path: PathBuf, + }, + #[error("Could not find `[package].name` in guest Cargo.toml at {path}")] + MissingPackageName { path: PathBuf }, + #[error("Failed to read file at {path}: {source}")] + ReadFile { + path: PathBuf, + #[source] + source: std::io::Error, + }, + #[error("`cargo metadata` failed: {0}")] + MetadataCommand(#[from] cargo_metadata::Error), } diff --git a/crates/ere-pico/src/lib.rs b/crates/ere-pico/src/lib.rs index ebdda26f..51fb46e7 100644 --- a/crates/ere-pico/src/lib.rs +++ b/crates/ere-pico/src/lib.rs @@ -3,11 +3,14 @@ use pico_sdk::client::DefaultProverClient; use pico_vm::emulator::stdin::EmulatorStdinBuilder; use serde::de::DeserializeOwned; -use std::{io::Read, path::Path, process::Command, time::Instant}; +use std::{env, io::Read, path::Path, process::Command, time::Instant}; use zkvm_interface::{ Compiler, Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof, ProverResourceType, PublicValues, zkVM, zkVMError, }; +use compile_stock_rust::compile_pico_program_stock_rust; + +mod compile_stock_rust; include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs")); mod error; @@ -21,38 +24,48 @@ impl Compiler for PICO_TARGET { type Program = Vec; - fn compile(&self, guest_path: &Path) -> Result { - // 1. Check guest path - if !guest_path.exists() { - return Err(PicoError::PathNotFound(guest_path.to_path_buf())); + fn compile(&self, guest_directory: &Path) -> Result { + let toolchain = + env::var("ERE_GUEST_TOOLCHAIN").unwrap_or_else(|_error| "risc0".into()); + match toolchain.as_str() { + "risc0" => Ok(compile_pico_program(guest_directory)?), + _ => Ok(compile_pico_program_stock_rust(guest_directory, &toolchain)?), } + } +} - // 2. Run `cargo pico build` - let status = Command::new("cargo") - .current_dir(guest_path) - .env("RUST_LOG", "info") - .args(["pico", "build"]) - .status()?; // From → Spawn - - if !status.success() { - return Err(PicoError::CargoFailed { status }); - } +fn compile_pico_program(guest_directory: &Path) -> Result, PicoError> +{ + // 1. Check guest path + if !guest_directory.exists() { + return Err(PicoError::PathNotFound(guest_directory.to_path_buf())); + } - // 3. Locate the ELF file - let elf_path = guest_path.join("elf/riscv32im-pico-zkvm-elf"); + // 2. Run `cargo pico build` + let status = Command::new("cargo") + .current_dir(guest_directory) + .env("RUST_LOG", "info") + .args(["pico", "build"]) + .status()?; // From → Spawn - if !elf_path.exists() { - return Err(PicoError::ElfNotFound(elf_path)); - } + if !status.success() { + return Err(PicoError::CargoFailed { status }); + } - // 4. Read the ELF file - let elf_bytes = std::fs::read(&elf_path).map_err(|e| PicoError::ReadElf { - path: elf_path, - source: e, - })?; + // 3. Locate the ELF file + let elf_path = guest_directory.join("elf/riscv32im-pico-zkvm-elf"); - Ok(elf_bytes) + if !elf_path.exists() { + return Err(PicoError::ElfNotFound(elf_path)); } + + // 4. Read the ELF file + let elf_bytes = std::fs::read(&elf_path).map_err(|e| PicoError::ReadElf { + path: elf_path, + source: e, + })?; + + Ok(elf_bytes) } pub struct ErePico { @@ -194,6 +207,14 @@ mod tests { assert_eq!(io.deserialize_outputs(&zkvm, &public_values), io.outputs()); } + #[test] + fn test_execute_nightly() { + let guest_directory = testing_guest_directory("pico", "stock_nightly_no_std"); + let program = compile_pico_program_stock_rust(&guest_directory, &"nightly".to_string()).unwrap(); + let zkvm = ErePico::new(program, ProverResourceType::Cpu); + run_zkvm_execute(&zkvm, &Input::new()); + } + #[test] fn test_execute_invalid_inputs() { let program = basic_program(); diff --git a/crates/ere-risc0/Cargo.toml b/crates/ere-risc0/Cargo.toml index 205654f0..fbee2962 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 = { version = "3.0.1", default-features = false } # Local dependencies zkvm-interface.workspace = true diff --git a/crates/ere-risc0/src/compile_stock_rust.rs b/crates/ere-risc0/src/compile_stock_rust.rs new file mode 100644 index 00000000..5eb44924 --- /dev/null +++ b/crates/ere-risc0/src/compile_stock_rust.rs @@ -0,0 +1,112 @@ +use std::fs; +use crate::error::CompileError; +use crate::compile::Risc0Program; +use std::path::{Path}; +use std::process::{Command}; +use cargo_metadata::MetadataCommand; +use tracing::info; +use risc0_binfmt::ProgramBinary; + +static CARGO_ENCODED_RUSTFLAGS_SEPARATOR: &str = "\x1f"; +// TODO: Make this with `zkos` package building to avoid binary file storing in repo. +const V1COMPAT_ELF: &[u8] = include_bytes!("kernel_elf/v1compat.elf"); + +pub fn compile_risc0_program_stock_rust( + guest_directory: &Path, + toolchain: &String, +) -> Result { + + 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 target_name = "riscv32ima-unknown-none-elf"; + let plus_toolchain = format!("+{}", toolchain); + + let args = [ + plus_toolchain.as_str(), + "build", + "--target", + target_name, + "--release", + // For bare metal we have to build core and alloc + "-Zbuild-std=core,alloc", + ]; + + let rust_flags = [ + "-C", + "passes=lower-atomic", // Only for rustc > 1.81 + "-C", + // Start of the code section + "link-arg=-Ttext=0x00201000", + "-C", + // The lowest memory location that will be used when your program is loaded + "link-arg=--image-base=0x00200800", + "-C", + "panic=abort", + "--cfg", + "getrandom_backend=\"custom\"", + "-C", + "llvm-args=-misched-prera-direction=bottomup", + "-C", + "llvm-args=-misched-postra-direction=bottomup", + ]; + + let encoded_rust_flags = rust_flags + .into_iter() + .collect::>() + .join(CARGO_ENCODED_RUSTFLAGS_SEPARATOR); + + let result = Command::new("cargo") + .current_dir(guest_directory) + .env("CARGO_ENCODED_RUSTFLAGS", &encoded_rust_flags) + .args(args) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .status() + .map_err(|source| CompileError::Risc0BuildFailure { + source: source.into(), + crate_path: guest_directory.to_path_buf() + }); + + if result.is_err() { + return Err(result.err().unwrap()); + } + + let elf_path = + guest_directory + .join("target") + .join(target_name) + .join("release") + .join(&package.name); + + let elf = fs::read(&elf_path).map_err(|e| CompileError::ReadFile { + path: elf_path, + source: e, + })?; + + let program = ProgramBinary::new(elf.as_slice(), V1COMPAT_ELF); + let image_id = program.compute_image_id().unwrap(); + info!("Risc0 program compiled (toolchain {}) OK - {} bytes", toolchain, elf.len()); + info!("Image ID - {image_id}"); + + Ok(Risc0Program{elf: program.encode(), image_id}) +} + +#[cfg(test)] +mod tests { + use test_utils::host::testing_guest_directory; + use crate::compile_stock_rust::compile_risc0_program_stock_rust; + + #[test] + fn test_stock_compiler_impl() { + let guest_directory = testing_guest_directory( + "risc0", + "stock_nightly_no_std"); + let program = compile_risc0_program_stock_rust(&guest_directory, &"nightly".to_string()).unwrap(); + assert!(!program.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 cd8e2fb2..5e04cf20 100644 --- a/crates/ere-risc0/src/error.rs +++ b/crates/ere-risc0/src/error.rs @@ -27,6 +27,12 @@ pub enum CompileError { }, #[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, + }, } 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 00000000..250672c9 Binary files /dev/null and b/crates/ere-risc0/src/kernel_elf/v1compat.elf differ diff --git a/crates/ere-risc0/src/lib.rs b/crates/ere-risc0/src/lib.rs index 93604a4c..c30a6eb6 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, }; use borsh::{BorshDeserialize, BorshSerialize}; @@ -23,6 +24,8 @@ use zkvm_interface::{ include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs")); mod compile; +mod compile_stock_rust; + mod error; /// Default logarithmic segment size from [`DEFAULT_SEGMENT_LIMIT_PO2`]. @@ -60,7 +63,12 @@ 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)?), + } } } @@ -308,6 +316,15 @@ mod tests { assert_eq!(io.deserialize_outputs(&zkvm, &public_values), io.outputs()); } + #[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(); + + run_zkvm_execute(&zkvm, &Input::new()); + } + #[test] fn test_execute_invalid_inputs() { let program = basic_program(); diff --git a/crates/ere-zisk/Cargo.toml b/crates/ere-zisk/Cargo.toml index ff3b9e12..8ec28764 100644 --- a/crates/ere-zisk/Cargo.toml +++ b/crates/ere-zisk/Cargo.toml @@ -15,6 +15,7 @@ tempfile.workspace = true thiserror.workspace = true toml.workspace = true tracing.workspace = true +cargo_metadata.workspace = true # Local dependencies zkvm-interface.workspace = true diff --git a/crates/ere-zisk/src/compile_stock_rust.rs b/crates/ere-zisk/src/compile_stock_rust.rs new file mode 100644 index 00000000..37d4b2f5 --- /dev/null +++ b/crates/ere-zisk/src/compile_stock_rust.rs @@ -0,0 +1,88 @@ +use std::fs; +use crate::error::CompileError; +use std::path::{Path}; +use std::process::{Command}; +use cargo_metadata::MetadataCommand; +use tracing::info; + +// static CARGO_ENCODED_RUSTFLAGS_SEPARATOR: &str = "\x1f"; + +pub fn compile_zisk_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 target_name = "riscv32ima-unknown-none-elf"; + let plus_toolchain = format!("+{}", toolchain); + + let args = [ + plus_toolchain.as_str(), + "build", + "--target", + target_name, + "--release", + // For bare metal we have to build core and alloc + "-Zbuild-std=core,alloc", + ]; + + // let rust_flags = []; + // + // let encoded_rust_flags = rust_flags + // .into_iter() + // .collect::>() + // .join(CARGO_ENCODED_RUSTFLAGS_SEPARATOR); + // + let result = Command::new("cargo") + .current_dir(guest_directory) + //.env("CARGO_ENCODED_RUSTFLAGS", &encoded_rust_flags) + .args(args) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()) + .status() + .map_err(|source| CompileError::CargoBuild { + cwd: guest_directory.to_path_buf(), + source: source.into() + }); + + if result.is_err() { + return Err(result.err().unwrap()); + } + + let elf_path = + guest_directory + .join("target") + .join(target_name) + .join("release") + .join(&package.name); + + let elf = fs::read(&elf_path).map_err(|e| CompileError::ReadFile { + path: elf_path, + source: e, + })?; + + info!("Zisk program compiled (toolchain {}) OK - {} bytes", toolchain, elf.len()); + + Ok(elf) +} + +#[cfg(test)] +mod tests { + use test_utils::host::testing_guest_directory; + use crate::compile_stock_rust::compile_zisk_program_stock_rust; + + #[test] + fn test_stock_compiler_impl() { + let guest_directory = testing_guest_directory( + "zisk", + "stock_nightly_no_std"); + let elf = compile_zisk_program_stock_rust(&guest_directory, &"nightly".to_string()).unwrap(); + assert!(!elf.is_empty(), "ELF bytes should not be empty."); + } +} diff --git a/crates/ere-zisk/src/error.rs b/crates/ere-zisk/src/error.rs index 10ff3276..2fbb3262 100644 --- a/crates/ere-zisk/src/error.rs +++ b/crates/ere-zisk/src/error.rs @@ -43,6 +43,8 @@ pub enum CompileError { program_dir: PathBuf, manifest_path: PathBuf, }, + #[error("`cargo metadata` failed: {0}")] + MetadataCommand(#[from] cargo_metadata::Error), #[error("Could not find `[package].name` in guest Cargo.toml at {path}")] MissingPackageName { path: PathBuf }, #[error("Compiled ELF not found at expected path: {path}")] diff --git a/crates/ere-zisk/src/lib.rs b/crates/ere-zisk/src/lib.rs index a2d94d6d..a7e1954e 100644 --- a/crates/ere-zisk/src/lib.rs +++ b/crates/ere-zisk/src/lib.rs @@ -2,12 +2,14 @@ use crate::{ compile::compile_zisk_program, + compile_stock_rust::compile_zisk_program_stock_rust, error::{CommonError, ExecuteError, ProveError, VerifyError, ZiskError}, }; use blake3::Hash; use dashmap::{DashMap, Entry}; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use std::{ + env, fs, io::{self, BufRead, Read, Write}, os::unix::fs::symlink, @@ -26,6 +28,8 @@ use zkvm_interface::{ include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs")); mod compile; +mod compile_stock_rust; + mod error; /// Lock for the command `cargo-zisk check-setup` to avoid multiple runs. @@ -50,7 +54,12 @@ impl Compiler for RV64_IMA_ZISK_ZKVM_ELF { type Program = Vec; fn compile(&self, guest_directory: &Path) -> Result { - compile_zisk_program(guest_directory).map_err(ZiskError::Compile) + let toolchain = + env::var("ERE_GUEST_TOOLCHAIN").unwrap_or_else(|_error| "risc0".into()); + match toolchain.as_str() { + "zisk" => compile_zisk_program(guest_directory).map_err(ZiskError::Compile), + _ => Ok(compile_zisk_program_stock_rust(guest_directory, &toolchain)?), + } } } @@ -626,6 +635,15 @@ mod tests { assert_eq!(io.deserialize_outputs(&zkvm, &public_values), io.outputs()); } + // #[test] + // fn test_execute_nightly() { + // let guest_direcotry = testing_guest_directory("risc0", "stock_nightly_no_std"); + // let program = compile_zisk_program_stock_rust(&guest_direcotry, &"nightly".to_string()).unwrap(); + // let zkvm = EreZisk::new(program, ProverResourceType::Cpu); + // + // run_zkvm_execute(&zkvm, &Input::new()); + // } + #[test] fn test_execute_invalid_inputs() { let program = basic_program(); diff --git a/tests/jolt/stock_nightly_no_std/Cargo.toml b/tests/jolt/stock_nightly_no_std/Cargo.toml new file mode 100644 index 00000000..47691249 --- /dev/null +++ b/tests/jolt/stock_nightly_no_std/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "addition_no_std" +edition = "2021" + +[features] +guest = [] + +[dependencies] +jolt = { package = "jolt-sdk", git = "https://github.com/a16z/jolt", rev = "55b9830a3944dde55d33a55c42522b81dd49f87a" } + +[workspace] diff --git a/tests/jolt/stock_nightly_no_std/src/main.rs b/tests/jolt/stock_nightly_no_std/src/main.rs new file mode 100644 index 00000000..e5a9ee9e --- /dev/null +++ b/tests/jolt/stock_nightly_no_std/src/main.rs @@ -0,0 +1,25 @@ +#![cfg_attr(feature = "guest", no_std)] +#![no_main] +extern crate alloc; + +use alloc::vec::Vec; +use core::sync::atomic::Ordering; +use core::sync::atomic::AtomicU16; + +#[jolt::provable] +fn foo() { + 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/openvm/stock_nightly_no_std/Cargo.toml b/tests/openvm/stock_nightly_no_std/Cargo.toml new file mode 100644 index 00000000..c00518f7 --- /dev/null +++ b/tests/openvm/stock_nightly_no_std/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "addition_no_std" +edition = "2021" + +[dependencies] + +[workspace] diff --git a/tests/openvm/stock_nightly_no_std/src/main.rs b/tests/openvm/stock_nightly_no_std/src/main.rs new file mode 100644 index 00000000..416cedc1 --- /dev/null +++ b/tests/openvm/stock_nightly_no_std/src/main.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] +extern crate alloc; + +use alloc::vec::Vec; +use core::sync::atomic::Ordering; +use core::sync::atomic::AtomicU16; +mod openvm_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/openvm/stock_nightly_no_std/src/openvm_rt.rs b/tests/openvm/stock_nightly_no_std/src/openvm_rt.rs new file mode 100644 index 00000000..1be861a4 --- /dev/null +++ b/tests/openvm/stock_nightly_no_std/src/openvm_rt.rs @@ -0,0 +1,129 @@ +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(); + + terminate(); + + unreachable!() +} + +#[inline(always)] +fn terminate() { + unsafe { + core::arch::asm!( + ".insn 4, 0x000b" + ) + } +} + +// 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) +{} diff --git a/tests/pico/stock_nightly_no_std/Cargo.toml b/tests/pico/stock_nightly_no_std/Cargo.toml new file mode 100644 index 00000000..c00518f7 --- /dev/null +++ b/tests/pico/stock_nightly_no_std/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "addition_no_std" +edition = "2021" + +[dependencies] + +[workspace] diff --git a/tests/pico/stock_nightly_no_std/src/main.rs b/tests/pico/stock_nightly_no_std/src/main.rs new file mode 100644 index 00000000..920e413e --- /dev/null +++ b/tests/pico/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 pico_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/pico/stock_nightly_no_std/src/pico_rt.rs b/tests/pico/stock_nightly_no_std/src/pico_rt.rs new file mode 100644 index 00000000..9351b34d --- /dev/null +++ b/tests/pico/stock_nightly_no_std/src/pico_rt.rs @@ -0,0 +1,124 @@ +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(); + + unsafe { core::arch::asm!( + "ecall", + in("t0") 0x00_00_00_00, + in("a0") 0 + );} + 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; + +// https://docs.succinct.xyz/docs/sp1/security/rv32im-implementation#reserved-memory-regions +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) +{} \ No newline at end of file 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 00000000..c00518f7 --- /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 00000000..546dd181 --- /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; + +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.rs b/tests/risc0/stock_nightly_no_std/src/risc0.rs new file mode 100644 index 00000000..f8088ed0 --- /dev/null +++ b/tests/risc0/stock_nightly_no_std/src/risc0.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) +{} diff --git a/tests/zisk/stock_nightly_no_std/Cargo.toml b/tests/zisk/stock_nightly_no_std/Cargo.toml new file mode 100644 index 00000000..c00518f7 --- /dev/null +++ b/tests/zisk/stock_nightly_no_std/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "addition_no_std" +edition = "2021" + +[dependencies] + +[workspace] diff --git a/tests/zisk/stock_nightly_no_std/src/main.rs b/tests/zisk/stock_nightly_no_std/src/main.rs new file mode 100644 index 00000000..39fa5043 --- /dev/null +++ b/tests/zisk/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 zisk; + +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/zisk/stock_nightly_no_std/src/zisk.rs b/tests/zisk/stock_nightly_no_std/src/zisk.rs new file mode 100644 index 00000000..9351b34d --- /dev/null +++ b/tests/zisk/stock_nightly_no_std/src/zisk.rs @@ -0,0 +1,124 @@ +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(); + + unsafe { core::arch::asm!( + "ecall", + in("t0") 0x00_00_00_00, + in("a0") 0 + );} + 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; + +// https://docs.succinct.xyz/docs/sp1/security/rv32im-implementation#reserved-memory-regions +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) +{} \ No newline at end of file