mirror of
https://github.com/eth-act/ere.git
synced 2026-04-03 03:00:17 -04:00
pico: Guest program compilation with stock rust compiler. (#118)
Co-authored-by: kevaundray <kevtheappdev@gmail.com> Co-authored-by: Han <tinghan0110@gmail.com>
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
111
crates/ere-pico/src/compile_stock_rust.rs
Normal file
111
crates/ere-pico/src/compile_stock_rust.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use crate::error::PicoError;
|
||||
use cargo_metadata::MetadataCommand;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
static CARGO_ENCODED_RUSTFLAGS_SEPARATOR: &str = "\x1f";
|
||||
|
||||
const TARGET_TRIPLE: &str = "riscv32ima-unknown-none-elf";
|
||||
// According to https://github.com/brevis-network/pico/blob/v1.1.7/sdk/cli/src/build/build.rs#L104
|
||||
const RUSTFLAGS: &[&str] = &[
|
||||
// Replace atomic ops with nonatomic versions since the guest is single threaded.
|
||||
"-C",
|
||||
"passes=lower-atomic",
|
||||
// Specify where to start loading the program in
|
||||
// memory. The clang linker understands the same
|
||||
// command line arguments as the GNU linker does; see
|
||||
// https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html#SEC3
|
||||
// for details.
|
||||
"-C",
|
||||
"link-arg=-Ttext=0x00200800",
|
||||
// Apparently not having an entry point is only a linker warning(!), so
|
||||
// error out in this case.
|
||||
"-C",
|
||||
"link-arg=--fatal-warnings",
|
||||
"-C",
|
||||
"panic=abort",
|
||||
];
|
||||
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_pico_program_stock_rust(
|
||||
guest_directory: &Path,
|
||||
toolchain: &String,
|
||||
) -> Result<Vec<u8>, PicoError> {
|
||||
compile_program_stock_rust(guest_directory, toolchain)
|
||||
}
|
||||
|
||||
fn compile_program_stock_rust(
|
||||
guest_directory: &Path,
|
||||
toolchain: &String,
|
||||
) -> Result<Vec<u8>, 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 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| PicoError::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| PicoError::ReadFile {
|
||||
path: elf_path,
|
||||
source: e,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::compile_stock_rust::compile_pico_program_stock_rust;
|
||||
use test_utils::host::testing_guest_directory;
|
||||
|
||||
#[test]
|
||||
fn test_stock_compiler_impl() {
|
||||
let guest_directory = testing_guest_directory("pico", "stock_nightly_no_std");
|
||||
let result = compile_pico_program_stock_rust(&guest_directory, &"nightly".to_string());
|
||||
assert!(result.is_ok(), "Pico guest program compilation failure.");
|
||||
assert!(
|
||||
!result.unwrap().is_empty(),
|
||||
"ELF bytes should not be empty."
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
|
||||
use compile_stock_rust::compile_pico_program_stock_rust;
|
||||
use pico_sdk::client::DefaultProverClient;
|
||||
use pico_vm::{configs::stark_config::KoalaBearPoseidon2, 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,
|
||||
};
|
||||
|
||||
mod compile_stock_rust;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
|
||||
mod error;
|
||||
use error::PicoError;
|
||||
@@ -21,40 +24,51 @@ impl Compiler for PICO_TARGET {
|
||||
|
||||
type Program = Vec<u8>;
|
||||
|
||||
fn compile(&self, guest_path: &Path) -> Result<Self::Program, Self::Error> {
|
||||
// 1. Check guest path
|
||||
if !guest_path.exists() {
|
||||
return Err(PicoError::PathNotFound(guest_path.to_path_buf()));
|
||||
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
|
||||
let toolchain = env::var("ERE_GUEST_TOOLCHAIN").unwrap_or_else(|_error| "pico".into());
|
||||
match toolchain.as_str() {
|
||||
"pico" => 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<io::Error> → Spawn
|
||||
|
||||
if !status.success() {
|
||||
return Err(PicoError::CargoFailed { status });
|
||||
}
|
||||
|
||||
// 3. Locate the ELF file
|
||||
let elf_path = guest_path.join("elf/riscv32im-pico-zkvm-elf");
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_pico_program(guest_directory: &Path) -> Result<Vec<u8>, PicoError> {
|
||||
// 1. Check guest path
|
||||
if !guest_directory.exists() {
|
||||
return Err(PicoError::PathNotFound(guest_directory.to_path_buf()));
|
||||
}
|
||||
|
||||
// 2. Run `cargo pico build`
|
||||
let status = Command::new("cargo")
|
||||
.current_dir(guest_directory)
|
||||
.env("RUST_LOG", "info")
|
||||
.args(["pico", "build"])
|
||||
.status()?; // From<io::Error> → Spawn
|
||||
|
||||
if !status.success() {
|
||||
return Err(PicoError::CargoFailed { status });
|
||||
}
|
||||
|
||||
// 3. Locate the ELF file
|
||||
let elf_path = guest_directory.join("elf/riscv32im-pico-zkvm-elf");
|
||||
|
||||
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 {
|
||||
program: <PICO_TARGET as Compiler>::Program,
|
||||
}
|
||||
@@ -193,6 +207,17 @@ mod tests {
|
||||
run_zkvm_execute(&zkvm, &io);
|
||||
}
|
||||
|
||||
#[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);
|
||||
|
||||
let result = zkvm.execute(&BasicProgramIo::empty());
|
||||
assert!(result.is_ok(), "Pico execution failure");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_invalid_inputs() {
|
||||
let program = basic_program();
|
||||
|
||||
Reference in New Issue
Block a user