Skip to content

Commit b18ac3e

Browse files
committed
Support building pico guest program with stock rust compiler. Add compilation and execution unit tests.
WiP. Works but needs cleanup
1 parent 80ad68f commit b18ac3e

8 files changed

Lines changed: 325 additions & 26 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ere-pico/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ rust-version.workspace = true
66
license.workspace = true
77

88
[dependencies]
9+
anyhow.workspace = true
910
bincode.workspace = true
11+
cargo_metadata.workspace = true
1012
serde.workspace = true
1113
thiserror.workspace = true
1214

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use std::fs;
2+
use crate::error::PicoError;
3+
use std::path::Path;
4+
use std::process::Command;
5+
use cargo_metadata::MetadataCommand;
6+
7+
static CARGO_ENCODED_RUSTFLAGS_SEPARATOR: &str = "\x1f";
8+
9+
pub fn compile_pico_program_stock_rust(
10+
guest_directory: &Path,
11+
toolchain: &String,
12+
) -> Result<Vec<u8>, PicoError> {
13+
14+
let metadata = MetadataCommand::new().current_dir(guest_directory).exec()?;
15+
let package = metadata
16+
.root_package()
17+
.ok_or_else(|| PicoError::MissingPackageName {
18+
path: guest_directory.to_path_buf(),
19+
})?;
20+
21+
let target_name = "riscv32ima-unknown-none-elf";
22+
let plus_toolchain = format!("+{}", toolchain);
23+
24+
let args = [
25+
plus_toolchain.as_str(),
26+
"build",
27+
"--target",
28+
target_name,
29+
"--release",
30+
// For bare metal we have to build core and alloc
31+
"-Zbuild-std=core,alloc",
32+
];
33+
34+
let rust_flags = [
35+
"-C",
36+
"passes=lower-atomic", // Only for rustc > 1.81
37+
"-C",
38+
// Start of the code section
39+
"link-arg=-Ttext=0x00201000",
40+
"-C",
41+
// The lowest memory location that will be used when your program is loaded
42+
"link-arg=--image-base=0x00200800",
43+
"-C",
44+
"panic=abort",
45+
"--cfg",
46+
"getrandom_backend=\"custom\"",
47+
"-C",
48+
"llvm-args=-misched-prera-direction=bottomup",
49+
"-C",
50+
"llvm-args=-misched-postra-direction=bottomup",
51+
];
52+
53+
let encoded_rust_flags = rust_flags
54+
.into_iter()
55+
.collect::<Vec<_>>()
56+
.join(CARGO_ENCODED_RUSTFLAGS_SEPARATOR);
57+
58+
let result = Command::new("cargo")
59+
.current_dir(guest_directory)
60+
.env("CARGO_ENCODED_RUSTFLAGS", &encoded_rust_flags)
61+
.args(args)
62+
.stdout(std::process::Stdio::inherit())
63+
.stderr(std::process::Stdio::inherit())
64+
.status()
65+
.map_err(|source| PicoError::BuildFailure {
66+
source: source.into(),
67+
crate_path: guest_directory.to_path_buf()
68+
});
69+
70+
if result.is_err() {
71+
return Err(result.err().unwrap());
72+
}
73+
74+
let elf_path =
75+
guest_directory
76+
.join("target")
77+
.join(target_name)
78+
.join("release")
79+
.join(&package.name);
80+
81+
fs::read(&elf_path).map_err(|e| PicoError::ReadFile {
82+
path: elf_path,
83+
source: e,
84+
})
85+
}
86+
87+
#[cfg(test)]
88+
mod tests {
89+
use test_utils::host::testing_guest_directory;
90+
use crate::compile_stock_rust::compile_pico_program_stock_rust;
91+
92+
#[test]
93+
fn test_stock_compiler_impl() {
94+
let guest_directory = testing_guest_directory(
95+
"pico",
96+
"stock_nightly_no_std");
97+
let elf = compile_pico_program_stock_rust(&guest_directory, &"nightly".to_string());
98+
assert!(!elf.is_err(), "jolt compilation failed");
99+
assert!(!elf.unwrap().is_empty(), "ELF bytes should not be empty.");
100+
}
101+
}

crates/ere-pico/src/error.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,20 @@ pub enum PicoError {
3333
#[source]
3434
source: io::Error,
3535
},
36+
#[error("Pico build failure for {crate_path} failed: {source}")]
37+
BuildFailure {
38+
#[source]
39+
source: anyhow::Error,
40+
crate_path: PathBuf,
41+
},
42+
#[error("Could not find `[package].name` in guest Cargo.toml at {path}")]
43+
MissingPackageName { path: PathBuf },
44+
#[error("Failed to read file at {path}: {source}")]
45+
ReadFile {
46+
path: PathBuf,
47+
#[source]
48+
source: std::io::Error,
49+
},
50+
#[error("`cargo metadata` failed: {0}")]
51+
MetadataCommand(#[from] cargo_metadata::Error),
3652
}

crates/ere-pico/src/lib.rs

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
use pico_sdk::client::DefaultProverClient;
44
use pico_vm::emulator::stdin::EmulatorStdinBuilder;
55
use serde::de::DeserializeOwned;
6-
use std::{io::Read, path::Path, process::Command, time::Instant};
6+
use std::{env, io::Read, path::Path, process::Command, time::Instant};
77
use zkvm_interface::{
88
Compiler, Input, InputItem, ProgramExecutionReport, ProgramProvingReport, Proof,
99
ProverResourceType, PublicValues, zkVM, zkVMError,
1010
};
11+
use compile_stock_rust::compile_pico_program_stock_rust;
12+
13+
mod compile_stock_rust;
1114

1215
include!(concat!(env!("OUT_DIR"), "/name_and_sdk_version.rs"));
1316
mod error;
@@ -21,38 +24,48 @@ impl Compiler for PICO_TARGET {
2124

2225
type Program = Vec<u8>;
2326

24-
fn compile(&self, guest_path: &Path) -> Result<Self::Program, Self::Error> {
25-
// 1. Check guest path
26-
if !guest_path.exists() {
27-
return Err(PicoError::PathNotFound(guest_path.to_path_buf()));
27+
fn compile(&self, guest_directory: &Path) -> Result<Self::Program, Self::Error> {
28+
let toolchain =
29+
env::var("ERE_GUEST_TOOLCHAIN").unwrap_or_else(|_error| "risc0".into());
30+
match toolchain.as_str() {
31+
"risc0" => Ok(compile_pico_program(guest_directory)?),
32+
_ => Ok(compile_pico_program_stock_rust(guest_directory, &toolchain)?),
2833
}
34+
}
35+
}
2936

30-
// 2. Run `cargo pico build`
31-
let status = Command::new("cargo")
32-
.current_dir(guest_path)
33-
.env("RUST_LOG", "info")
34-
.args(["pico", "build"])
35-
.status()?; // From<io::Error> → Spawn
36-
37-
if !status.success() {
38-
return Err(PicoError::CargoFailed { status });
39-
}
37+
fn compile_pico_program(guest_directory: &Path) -> Result<Vec<u8>, PicoError>
38+
{
39+
// 1. Check guest path
40+
if !guest_directory.exists() {
41+
return Err(PicoError::PathNotFound(guest_directory.to_path_buf()));
42+
}
4043

41-
// 3. Locate the ELF file
42-
let elf_path = guest_path.join("elf/riscv32im-pico-zkvm-elf");
44+
// 2. Run `cargo pico build`
45+
let status = Command::new("cargo")
46+
.current_dir(guest_directory)
47+
.env("RUST_LOG", "info")
48+
.args(["pico", "build"])
49+
.status()?; // From<io::Error> → Spawn
4350

44-
if !elf_path.exists() {
45-
return Err(PicoError::ElfNotFound(elf_path));
46-
}
51+
if !status.success() {
52+
return Err(PicoError::CargoFailed { status });
53+
}
4754

48-
// 4. Read the ELF file
49-
let elf_bytes = std::fs::read(&elf_path).map_err(|e| PicoError::ReadElf {
50-
path: elf_path,
51-
source: e,
52-
})?;
55+
// 3. Locate the ELF file
56+
let elf_path = guest_directory.join("elf/riscv32im-pico-zkvm-elf");
5357

54-
Ok(elf_bytes)
58+
if !elf_path.exists() {
59+
return Err(PicoError::ElfNotFound(elf_path));
5560
}
61+
62+
// 4. Read the ELF file
63+
let elf_bytes = std::fs::read(&elf_path).map_err(|e| PicoError::ReadElf {
64+
path: elf_path,
65+
source: e,
66+
})?;
67+
68+
Ok(elf_bytes)
5669
}
5770

5871
pub struct ErePico {
@@ -194,6 +207,14 @@ mod tests {
194207
assert_eq!(io.deserialize_outputs(&zkvm, &public_values), io.outputs());
195208
}
196209

210+
#[test]
211+
fn test_execute_nightly() {
212+
let guest_directory = testing_guest_directory("pico", "stock_nightly_no_std");
213+
let program = compile_pico_program_stock_rust(&guest_directory, &"nightly".to_string()).unwrap();
214+
let zkvm = ErePico::new(program, ProverResourceType::Cpu);
215+
run_zkvm_execute(&zkvm, &Input::new());
216+
}
217+
197218
#[test]
198219
fn test_execute_invalid_inputs() {
199220
let program = basic_program();
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
name = "addition_no_std"
3+
edition = "2021"
4+
5+
[dependencies]
6+
7+
[workspace]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#![no_std]
2+
#![no_main]
3+
extern crate alloc;
4+
5+
use alloc::vec::Vec;
6+
use core::sync::atomic::Ordering;
7+
use core::sync::atomic::AtomicU16;
8+
9+
mod pico_rt;
10+
11+
fn main() {
12+
let a: AtomicU16 = core::hint::black_box(AtomicU16::new(5));
13+
let b: AtomicU16 = core::hint::black_box(AtomicU16::new(7));
14+
15+
if a.load(Ordering::SeqCst) + b.load(Ordering::SeqCst) != 12 {
16+
panic!("Something went wrong!");
17+
}
18+
19+
let mut v: Vec<AtomicU16> = Vec::new();
20+
v.push(AtomicU16::new(5));
21+
v.push(AtomicU16::new(7));
22+
23+
if v[0].load(Ordering::SeqCst) + v[1].load(Ordering::SeqCst) != 12 {
24+
panic!("Something went wrong!");
25+
}
26+
}

0 commit comments

Comments
 (0)