Skip to content

support guest program "println!" in e2e flow #929

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,13 @@ jobs:
key: integration-${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- uses: dtolnay/rust-toolchain@nightly

- name: Run fibonacci
- name: Run fibonacci (debug)
env:
RUST_LOG: debug
RUSTFLAGS: "-C opt-level=3"
run: cargo run --package ceno_zkvm --bin e2e -- --platform=ceno --hints=10 --public-io=4191 examples/target/riscv32im-ceno-zkvm-elf/release/examples/fibonacci
run: cargo run --package ceno_zkvm --bin e2e -- --platform=ceno --hints=10 --public-io=4191 examples/target/riscv32im-ceno-zkvm-elf/debug/examples/fibonacci

- name: Run fibonacci (release)
env:
RUSTFLAGS: "-C opt-level=3"
run: cargo run --release --package ceno_zkvm --bin e2e -- --platform=ceno --hints=10 --public-io=4191 examples/target/riscv32im-ceno-zkvm-elf/release/examples/fibonacci
99 changes: 69 additions & 30 deletions ceno_emul/src/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ impl Program {
if segments.len() > 256 {
bail!("Too many program headers");
}
let symbols = collect_addr_symbols_mapping(&elf)?;
for (idx, segment) in segments
.iter()
.filter(|x| x.p_type == elf::abi::PT_LOAD)
Expand Down Expand Up @@ -148,29 +149,48 @@ impl Program {
.p_offset
.try_into()
.map_err(|err| anyhow!("offset is larger than 32 bits. {err}"))?;
for i in (0..mem_size).step_by(WORD_SIZE) {

// process initialized data
(0..file_size).step_by(WORD_SIZE).try_for_each(|i| {
let addr = vaddr.checked_add(i).context("Invalid segment vaddr")?;
if addr >= max_mem {
bail!(
"Address [0x{addr:08x}] exceeds maximum address for guest programs [0x{max_mem:08x}]"
);
bail!("Address [0x{addr:x}] exceeds max [0x{max_mem:x}]");
}
if i >= file_size {
// Past the file size and skip.
} else {
let mut word = 0;
// Don't read past the end of the file.
let len = core::cmp::min(file_size - i, WORD_SIZE as u32);
for j in 0..len {
let offset = (offset + i + j) as usize;
let byte = input.get(offset).context("Invalid segment offset")?;
word |= (*byte as u32) << (j * 8);
}
image.insert(addr, word);
if (segment.p_flags & PF_X) != 0 {
instructions.push(word);
}

let word = (0..WORD_SIZE as u32)
.take((file_size - i) as usize)
.enumerate()
.fold(0u32, |acc, (j, _)| {
let offset = (offset + i + j as u32) as usize;
let byte = *input.get(offset).unwrap_or(&0);
acc | ((byte as u32) << (j * 8))
});

image.insert(addr, word);
if (segment.p_flags & PF_X) != 0 {
instructions.push(word);
}

Ok(())
})?;

// only pad uninitialized region if a symbol exists in the range
if let Some((max_addr, _)) = find_max_symbol_in_range(
&symbols,
vaddr as u64,
vaddr.checked_add(mem_size).context("Invalid mem_size")? as u64,
) {
let zero_upper = (*max_addr as u32).saturating_sub(vaddr);
(file_size..=zero_upper)
.step_by(WORD_SIZE)
.try_for_each(|i| {
let addr = vaddr.checked_add(i).context("Invalid segment vaddr")?;
if addr >= max_mem {
bail!("zero-fill addr [0x{addr:x}] exceeds max [0x{max_mem:x}]");
}
image.insert(addr, 0);
Ok(())
})?;
}
}

Expand Down Expand Up @@ -220,17 +240,10 @@ impl Program {
}

// retrieve sheap from elf
let sheap = elf
.symbol_table()?
.and_then(|(symtab, strtab)| {
symtab.iter().find_map(|symbol| {
strtab
.get(symbol.st_name as usize)
.ok()
.filter(|&name| name == "_sheap")
.map(|_| symbol.st_value)
})
})
let sheap = symbols
.iter()
.find(|(_, v)| *v == "_sheap")
.map(|(k, _)| *k)
.ok_or_else(|| anyhow!("unable to find _sheap symbol"))? as u32;

// there should be no
Expand All @@ -249,3 +262,29 @@ impl Program {
})
}
}

fn collect_addr_symbols_mapping<'data>(
elf: &ElfBytes<'data, LittleEndian>,
) -> Result<BTreeMap<u64, String>> {
let mut symbols = BTreeMap::new();

if let Some((symtab, strtab)) = elf.symbol_table()? {
for symbol in symtab.iter() {
if let Ok(name) = strtab.get(symbol.st_name as usize) {
if !name.is_empty() && symbol.st_value != 0 {
symbols.insert(symbol.st_value, name.to_string());
}
}
}
}

Ok(symbols)
}

fn find_max_symbol_in_range(
symbols: &BTreeMap<u64, String>,
start: u64,
end: u64,
) -> Option<(&u64, &String)> {
symbols.range(start..end).max_by_key(|(&addr, _)| addr)
}
1 change: 1 addition & 0 deletions ceno_emul/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct Platform {
pub stack: Range<Addr>,
pub heap: Range<Addr>,
pub hints: Range<Addr>,

/// If true, ecall instructions are no-op instead of trap. Testing only.
pub unsafe_ecall_nop: bool,
}
Expand Down
2 changes: 1 addition & 1 deletion ceno_host/tests/test_elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ fn test_hints() -> Result<()> {
for (i, msg) in enumerate(&all_messages) {
println!("{i}: {msg}");
}
assert_eq!(all_messages[0], "3992003");
assert_eq!(all_messages[3], "3992003");
Ok(())
}

Expand Down
2 changes: 1 addition & 1 deletion ceno_rt/ceno_link.x
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

_stack_start = ORIGIN(REGION_STACK) + LENGTH(REGION_STACK);
_stack_start = ORIGIN(REGION_STACK) + 1024M;
_hints_start = ORIGIN(REGION_HINTS);
_hints_length = LENGTH(REGION_HINTS);
_lengths_of_hints_start = ORIGIN(REGION_HINTS);
Expand Down
8 changes: 4 additions & 4 deletions ceno_rt/memory.x
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
MEMORY
{
RAM : ORIGIN = 0x80000000, LENGTH = 1024M
ROM : ORIGIN = 0x20000000, LENGTH = 128M
HINTS : ORIGIN = 0x40000000, LENGTH = 256M
PUBIO : ORIGIN = 0x30000000, LENGTH = 1K
RAM (rw) : ORIGIN = 0x80000000, LENGTH = 0x40040000 /* 1024M heap/stack + 256k reserved for io */
ROM (rx) : ORIGIN = 0x20000000, LENGTH = 128M
HINTS (r) : ORIGIN = 0x40000000, LENGTH = 256M
PUBIO (r) : ORIGIN = 0x30000000, LENGTH = 1K
}

REGION_ALIAS("REGION_TEXT", ROM);
Expand Down
33 changes: 22 additions & 11 deletions ceno_rt/src/io.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
use crate::{INFO_OUT_ADDR, WORD_SIZE};
use crate::WORD_SIZE;
use core::{cell::Cell, fmt, mem::size_of, slice};

static INFO_OUT: IOWriter = IOWriter::new(INFO_OUT_ADDR);

pub fn info_out() -> &'static IOWriter {
&INFO_OUT
}

pub struct IOWriter {
cursor: Cell<*mut u32>,
}
Expand All @@ -16,6 +10,7 @@ pub struct IOWriter {
unsafe impl Sync for IOWriter {}

impl IOWriter {
#[cfg(debug_assertions)]
const fn new(addr: u32) -> Self {
assert!(addr % WORD_SIZE as u32 == 0);
IOWriter {
Expand Down Expand Up @@ -60,18 +55,34 @@ impl fmt::Write for &IOWriter {
}
}

#[cfg(debug_assertions)]
use crate::INFO_OUT_ADDR;
#[cfg(debug_assertions)]
static INFO_OUT: IOWriter = IOWriter::new(INFO_OUT_ADDR);

#[cfg(debug_assertions)]
pub fn info_out() -> &'static IOWriter {
&INFO_OUT
}

mod macros {
#[macro_export]
macro_rules! print {
macro_rules! debug_print {
($($arg:tt)*) => {
let _ = core::write!($crate::info_out(), $($arg)*);
#[cfg(debug_assertions)]
{
let _ = core::write!($crate::info_out(), $($arg)*);
}
};
}

#[macro_export]
macro_rules! println {
macro_rules! debug_println {
($($arg:tt)*) => {
let _ = core::writeln!($crate::info_out(), $($arg)*);
#[cfg(debug_assertions)]
{
let _ = core::writeln!($crate::info_out(), $($arg)*);
}
};
}
}
1 change: 1 addition & 0 deletions ceno_rt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod mmio;
pub use mmio::{commit, read, read_slice};

mod io;
#[cfg(debug_assertions)]
pub use io::info_out;

mod params;
Expand Down
3 changes: 1 addition & 2 deletions ceno_rt/src/params.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
pub const WORD_SIZE: usize = 4;

// Now it's defined within RAM
// TODO define a specific region for it and avoid mixup with ram to achieve non-uniform design on heap/stack
/// address defined in `memory.x` under RAM section.
pub const INFO_OUT_ADDR: u32 = 0xC000_0000;
28 changes: 24 additions & 4 deletions ceno_zkvm/src/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{
};
use ceno_emul::{
ByteAddr, CENO_PLATFORM, EmuContext, InsnKind, IterAddresses, Platform, Program, StepRecord,
Tracer, VMState, WORD_SIZE, WordAddr,
Tracer, VMState, WORD_SIZE, WordAddr, host_utils::read_all_messages,
};
use clap::ValueEnum;
use ff_ext::ExtensionField;
Expand Down Expand Up @@ -119,6 +119,17 @@ fn emulate_program(
.collect::<Result<Vec<StepRecord>, _>>()
.expect("vm exec failed");

if cfg!(debug_assertions) {
// show io message if have
let all_messages = &read_all_messages(&vm)
.iter()
.map(|msg| String::from_utf8_lossy(msg).to_string())
.collect::<Vec<String>>();
for msg in all_messages {
tracing::info!("{}", msg);
}
}

// Find the exit code from the HALT step, if halting at all.
let exit_code = all_records
.iter()
Expand Down Expand Up @@ -190,6 +201,7 @@ fn emulate_program(
})
.collect_vec();

// Find the final hints IO cycles.
let hints_final = hints_init
.iter()
.map(|rec| MemFinalRecord {
Expand All @@ -200,11 +212,11 @@ fn emulate_program(
.collect_vec();

// get stack access by min/max range
let stack_final = if let Some((start, end)) = vm
let stack_final = if let Some((start, _)) = vm
.tracer()
.probe_min_max_address_by_start_addr(ByteAddr::from(platform.stack.start).waddr())
{
(start..end)
(start..ByteAddr::from(platform.stack.end).waddr())
// stack record collect in reverse order
.rev()
.map(|vma| {
Expand All @@ -225,6 +237,7 @@ fn emulate_program(
.tracer()
.probe_min_max_address_by_start_addr(ByteAddr::from(platform.heap.start).waddr())
{
assert_eq!(start, ByteAddr::from(platform.heap.start).waddr());
(start..end)
.map(|vma| {
let byte_addr = vma.baddr();
Expand Down Expand Up @@ -282,7 +295,14 @@ pub fn setup_platform(
};

let prog_data = program.image.keys().copied().collect::<BTreeSet<_>>();
let stack = preset.stack.end - stack_size..preset.stack.end;

let stack = if cfg!(debug_assertions) {
// reserve some extra space for io
// thus memory consistent check could be satisfied
preset.stack.end - stack_size..(preset.stack.end + 0x4000)
} else {
preset.stack.end - stack_size..preset.stack.end
};

let heap = {
// Detect heap as starting after program data.
Expand Down
8 changes: 7 additions & 1 deletion ceno_zkvm/src/tables/ram/ram_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,13 @@ impl<DVRAM: DynVolatileRamTable + Send + Sync + Clone> DynVolatileRamTableConfig
.zip(final_mem)
.enumerate()
.for_each(|(i, ((row, structural_row), rec))| {
assert_eq!(rec.addr, DVRAM::addr(&self.params, i));
assert_eq!(
rec.addr,
DVRAM::addr(&self.params, i),
"rec.addr {:x} != expected {:x}",
rec.addr,
DVRAM::addr(&self.params, i),
);

if self.final_v.len() == 1 {
// Assign value directly.
Expand Down
18 changes: 12 additions & 6 deletions examples-builder/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,26 @@ fn build_elfs() {
let _ = remove_file(&dest_path);
let mut dest = File::create(&dest_path).expect("failed to create vars.rs");

// TODO(Matthias): skip building the elfs if we are in clippy or check mode.
// See git history for an attempt to do this.
let is_release = std::env::var("PROFILE").unwrap() == "release";
let mut args = vec!["build", "--examples", "--target-dir", "target"];
if is_release {
args.insert(1, "--release"); // insert --release after "build"
}

let output = Command::new("cargo")
.args(["build", "--release", "--examples", "--target-dir", "target"])
.args(args)
.current_dir("../examples")
.env_clear()
.envs(std::env::vars().filter(|x| !x.0.starts_with("CARGO_")))
.output()
.expect("cargo command failed to run");

if !output.status.success() {
io::stdout().write_all(&output.stdout).unwrap();
io::stderr().write_all(&output.stderr).unwrap();
panic!("cargo build of examples failed.");
}
// Contact Matthias, if your examples get complicated enough to need their own crates, instead of just being one file.

for example in glob("../examples/examples/*.rs")
.unwrap()
.map(Result::unwrap)
Expand All @@ -47,14 +52,15 @@ fn build_elfs() {
dest,
r#"#[allow(non_upper_case_globals)]
pub const {example}: &[u8] =
include_bytes!(r"{CARGO_MANIFEST_DIR}/../examples/target/riscv32im-ceno-zkvm-elf/release/examples/{example}");"#
).expect("failed to write vars.rs");
include_bytes!(r"{CARGO_MANIFEST_DIR}/../examples/target/riscv32im-ceno-zkvm-elf/{}/examples/{example}");"#,
std::env::var("PROFILE").unwrap()).expect("failed to write vars.rs");
}
rerun_all_but_target(Path::new("../examples"));
rerun_all_but_target(Path::new("../ceno_rt"));
}

fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=PROFILE");
build_elfs();
}
Loading