From 53db5a54bf2618def5096590afa9b282d7f09521 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Thu, 5 Dec 2024 19:14:37 +0800 Subject: [PATCH 01/64] Hints in linker --- ceno_rt/ceno_link.x | 7 +++++++ ceno_rt/memory.x | 2 ++ ceno_rt/src/lib.rs | 2 ++ 3 files changed, 11 insertions(+) diff --git a/ceno_rt/ceno_link.x b/ceno_rt/ceno_link.x index 00b2ea282..2747b8109 100644 --- a/ceno_rt/ceno_link.x +++ b/ceno_rt/ceno_link.x @@ -1,5 +1,6 @@ _stack_start = ORIGIN(REGION_STACK) + LENGTH(REGION_STACK); +_hints_start = ORIGIN(REGION_HINTS); SECTIONS { @@ -34,4 +35,10 @@ SECTIONS . = ALIGN(4); _sheap = .; } > RAM + + /* Define a section for runtime-populated EEPROM-like HINTS data */ + .hints (NOLOAD) : ALIGN(4) + { + *(.hints .hints.*); + } > HINTS } diff --git a/ceno_rt/memory.x b/ceno_rt/memory.x index 712de56cd..7c2a2538b 100644 --- a/ceno_rt/memory.x +++ b/ceno_rt/memory.x @@ -2,6 +2,7 @@ MEMORY { RAM : ORIGIN = 0x80000000, LENGTH = 1024M ROM : ORIGIN = 0x20000000, LENGTH = 16M + HINTS: ORIGIN = 0x40000000, LENGTH = 1024M } REGION_ALIAS("REGION_TEXT", ROM); @@ -10,3 +11,4 @@ REGION_ALIAS("REGION_DATA", RAM); REGION_ALIAS("REGION_BSS", RAM); REGION_ALIAS("REGION_HEAP", RAM); REGION_ALIAS("REGION_STACK", RAM); +REGION_ALIAS("REGION_HINTS", HINTS); diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 8de456c41..9945057ec 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -94,4 +94,6 @@ extern "C" { static _stack_start: u8; // The address of this variable is the start of the heap (growing upwards). static _sheap: u8; + // The address of this variable is the start of the hints ROM. + static _hints_start: u8; } From 612befbda24679ab71ef63a38380fd84c7683205 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 6 Dec 2024 14:14:10 +0800 Subject: [PATCH 02/64] Snapshot --- Cargo.lock | 139 ++++++++++++++++++++++ Cargo.toml | 7 +- ceno_host/Cargo.toml | 12 ++ ceno_host/src/lib.rs | 250 +++++++++++++++++++++++++++++++++++++++ ceno_rt/Cargo.toml | 4 + ceno_rt/src/allocator.rs | 2 + ceno_rt/src/lib.rs | 5 + ceno_rt/src/mmio.rs | 109 +++++++++++++++++ examples/Cargo.lock | 136 +++++++++++++++++++++ 9 files changed, 661 insertions(+), 3 deletions(-) create mode 100644 ceno_host/Cargo.toml create mode 100644 ceno_host/src/lib.rs create mode 100644 ceno_rt/src/mmio.rs diff --git a/Cargo.lock b/Cargo.lock index c671c4904..9c09d9dc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,6 +203,29 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytecheck" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c8f430744b23b54ad15161fcbc22d82a29b73eacbe425fea23ec822600bc6f" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "rancor", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523363cbe1df49b68215efdf500b103ac3b0fb4836aed6d15689a076eadb8fff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "bytemuck" version = "1.19.0" @@ -249,11 +272,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "ceno_host" +version = "0.1.0" +dependencies = [ + "itertools 0.13.0", + "rand", + "rkyv", +] + [[package]] name = "ceno_rt" version = "0.1.0" dependencies = [ "riscv", + "rkyv", ] [[package]] @@ -1083,6 +1116,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "munge" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64142d38c84badf60abf06ff9bd80ad2174306a5b11bd4706535090a30a419df" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb5c1d8184f13f7d0ccbeeca0def2f9a181bce2624302793005f5ca8aa62e5e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "nix" version = "0.26.4" @@ -1439,6 +1492,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "ptr_meta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "quick-xml" version = "0.26.0" @@ -1463,6 +1536,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rancor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf5f7161924b9d1cea0e4cabc97c372cea92b5f927fc13c6bca67157a0ad947" +dependencies = [ + "ptr_meta", +] + [[package]] name = "rand" version = "0.8.5" @@ -1578,6 +1660,15 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rend" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35e8a6bf28cd121053a66aa2e6a2e3eaffad4a60012179f0e864aa5ffeff215" +dependencies = [ + "bytecheck", +] + [[package]] name = "rgb" version = "0.8.50" @@ -1617,6 +1708,33 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" +[[package]] +name = "rkyv" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b11a153aec4a6ab60795f8ebe2923c597b16b05bb1504377451e705ef1a45323" +dependencies = [ + "bytecheck", + "hashbrown 0.15.2", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beb382a4d9f53bd5c0be86b10d8179c3f8a14c30bf774ff77096ed6581e35981" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1710,6 +1828,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "smallvec" version = "1.13.2" @@ -1913,6 +2037,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tracing" version = "0.1.41" diff --git a/Cargo.toml b/Cargo.toml index 593fc5471..9ba75bed7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,14 +2,15 @@ exclude = ["examples"] members = [ "ceno_emul", - "examples-builder", + "ceno_host", "ceno_rt", + "ceno_zkvm", + "examples-builder", "mpcs", "multilinear_extensions", + "poseidon", "sumcheck", "transcript", - "ceno_zkvm", - "poseidon", ] resolver = "2" diff --git a/ceno_host/Cargo.toml b/ceno_host/Cargo.toml new file mode 100644 index 000000000..d72edce86 --- /dev/null +++ b/ceno_host/Cargo.toml @@ -0,0 +1,12 @@ +[package] +edition.workspace = true +license.workspace = true +name = "ceno_host" +version.workspace = true + +[dependencies] +itertools.workspace = true +rkyv = { version = "0.8.9", default-features = false, features = ["alloc", "bytecheck"] } + +[dev-dependencies] +rand.workspace = true diff --git a/ceno_host/src/lib.rs b/ceno_host/src/lib.rs new file mode 100644 index 000000000..0fa7293ee --- /dev/null +++ b/ceno_host/src/lib.rs @@ -0,0 +1,250 @@ +// See `make_stdin` and `consume` for the main entry points, and how this would look +// for the host and guest respectively for the user of our library. +// Everything else in here would be hidden. + +use itertools::izip; +use rkyv::{ + Portable, Serialize, + api::high::{HighSerializer, HighValidator}, + bytecheck::CheckBytes, + rancor::Error, + ser::allocator::ArenaHandle, + to_bytes, + util::AlignedVec, +}; + +#[derive(Default)] +pub struct CenoStdin { + pub items: Vec, +} + +pub struct SerialisedCenoStdin(pub AlignedVec); + +impl CenoStdin { + pub fn write_slice(&mut self, bytes: AlignedVec) { + self.items.push(bytes); + } + + pub fn write( + &mut self, + item: &impl for<'a> Serialize, Error>>, + ) -> Result<(), Error> { + let bytes = to_bytes::(item)?; + self.write_slice(bytes); + Ok(()) + } + + pub fn finalise(&self) -> SerialisedCenoStdin { + // TODO: perhaps don't hardcode 16 here. + // It's from rkyv's format, so we can probably take it from there somehow? + // TODO: clean this up. + let initial_offset = (size_of::() * self.items.len()).next_multiple_of(16); + println!("offset: {}", initial_offset); + let offsets: Vec = self + .items + .iter() + .scan(initial_offset, |acc, bytes| { + let output = (*acc + bytes.len()) as u32; + print!("len: {}\t", bytes.len()); + *acc += bytes.len().next_multiple_of(16); + println!("acc: {}", *acc); + Some(output) + }) + .collect(); + let offsets_u8: Vec = offsets.iter().copied().flat_map(u32::to_le_bytes).collect(); + let mut buf: AlignedVec = AlignedVec::new(); + buf.extend_from_slice(&offsets_u8); + println!("buf.len() after offsets: {}", buf.len()); + buf.extend_from_slice(&vec![0; buf.len().next_multiple_of(16) - buf.len()]); + println!("buf.len() after offset padding: {}", buf.len()); + for (offset, item) in izip!(offsets, &self.items) { + buf.extend_from_slice(item); + buf.extend_from_slice(&vec![0; buf.len().next_multiple_of(16) - buf.len()]); + assert_eq!(buf.len(), offset.next_multiple_of(16) as usize); + } + SerialisedCenoStdin(buf) + } +} + +pub struct SerialisedCenoStdinIter<'a> { + buf: &'a SerialisedCenoStdin, + next: usize, +} + +impl<'b> SerialisedCenoStdinIter<'b> { + pub fn read<'a, T>(&'a mut self) -> &'b T + where + T: Portable + for<'c> CheckBytes>, + { + rkyv::access::(self.read_slice()).unwrap() + } + + pub fn read_slice<'a>(&'a mut self) -> &'b [u8] { + self.next().unwrap() + } +} + +impl<'a> Iterator for SerialisedCenoStdinIter<'a> { + type Item = &'a [u8]; + fn next(&mut self) -> Option { + let len = u32::from_le_bytes( + self.buf.0[self.next..][..size_of::()] + .try_into() + .unwrap(), + ) as usize; + self.next += size_of::(); + Some(&self.buf.0[..len]) + } +} + +impl<'a> IntoIterator for &'a SerialisedCenoStdin { + type Item = &'a [u8]; + type IntoIter = SerialisedCenoStdinIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + SerialisedCenoStdinIter { next: 0, buf: self } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::Rng; + use rkyv::{ + Archive, Deserialize, deserialize, + rancor::{Error, Failure}, + to_bytes, + util::AlignedVec, + }; + + #[derive(Archive, Deserialize, Serialize, Debug, PartialEq)] + #[rkyv( + // This will generate a PartialEq impl between our unarchived + // and archived types + compare(PartialEq), + // Derives can be passed through to the generated type: + derive(Debug), + )] + struct Test { + int: u32, + string: String, + option: Option>, + } + + #[derive(Archive, Deserialize, Serialize, Debug, PartialEq)] + #[rkyv( + // This will generate a PartialEq impl between our unarchived + // and archived types + compare(PartialEq), + // Derives can be passed through to the generated type: + derive(Debug), + )] + struct Toast { + stuff: Option>, + } + + /// The equivalent of this function would run in the host. + /// + /// We create three different items, and show that we can read them back in `consume`. + pub fn make_stdin() -> SerialisedCenoStdin { + let mut stdin = CenoStdin::default(); + stdin + .write(&Test { + int: 0xDEAD_BEEF, + string: "hello world".to_string(), + option: Some(vec![1, 2, 3, 4]), + }) + .unwrap(); + stdin.write(&0xaf_u8).unwrap(); + stdin + .write(&Toast { + stuff: Some(vec!["hello scroll".to_string()]), + }) + .unwrap(); + stdin.finalise() + } + + /// The equivalent of this function would run in the guest. + /// + /// `stdin` would be the memory mapped region for private hints. + pub fn consume(stdin: SerialisedCenoStdin) { + println!("\nConsuming..."); + let mut iter: SerialisedCenoStdinIter = stdin.into_iter(); + let test1: &ArchivedTest = iter.read(); + assert_eq!(test1, &Test { + int: 0xDEAD_BEEF, + string: "hello world".to_string(), + option: Some(vec![1, 2, 3, 4]), + }); + let number: &u8 = iter.read(); + assert_eq!(number, &0xaf_u8); + let test2: &ArchivedToast = iter.read(); + assert_eq!(test2, &Toast { + stuff: Some(vec!["hello scroll".to_string()]), + }); + } + + #[test] + fn test_prepare_and_consume_items() { + let stdin = make_stdin(); + consume(stdin); + } + + #[test] + fn test_rkyv_padding() { + let value = Test { + int: 42, + string: "hello world".to_string(), + option: Some(vec![1, 2, 3, 4]), + }; + + // Serializing is as easy as a single function call + let bytes: AlignedVec = to_bytes::(&value).unwrap(); + + { + // Or you can customize your serialization for better performance or control + // over resource usage + use rkyv::{api::high::to_bytes_with_alloc, ser::allocator::Arena}; + + let mut arena = Arena::new(); + let _bytes = to_bytes_with_alloc::<_, Error>(&value, arena.acquire()).unwrap(); + } + // You can use the safe API for fast zero-copy deserialization + let archived = rkyv::access::(&bytes[..]).unwrap(); + assert_eq!(archived, &value); + + // And you can always deserialize back to the original type + let deserialized = deserialize::(archived).unwrap(); + assert_eq!(deserialized, value); + + let mut rng = rand::thread_rng(); + + { + // https://rkyv.org/format.html says: + // This deterministic layout means that you don't need to store the position of + // the root object in most cases. As long as your buffer ends right at the end of + // your root object, you can use `access` with your buffer. + + // Thus left padding should work. We add 1024 bytes of random junk to the left. + + let mut left_padded_bytes = vec![0; 1024]; + rng.fill(&mut left_padded_bytes[..]); + // Then add our original bytes to the end: + left_padded_bytes.extend_from_slice(&bytes); + + // we should be able to access as before: + let archived2 = rkyv::access::(&left_padded_bytes[..]).unwrap(); + assert_eq!(archived2, &value); + } + { + // The same but right padding junk should fail: + let mut right_padded_bytes = bytes.clone(); + let mut junk = vec![0; 1024]; + rng.fill(&mut junk[..]); + right_padded_bytes.extend_from_slice(&junk); + // we should not be able to access as before: + let _ = rkyv::access::(&right_padded_bytes[..]) + .expect_err("This should fail."); + } + } +} diff --git a/ceno_rt/Cargo.toml b/ceno_rt/Cargo.toml index dfdc87ad2..709e80f51 100644 --- a/ceno_rt/Cargo.toml +++ b/ceno_rt/Cargo.toml @@ -11,3 +11,7 @@ version.workspace = true [dependencies] riscv = "0.12" +rkyv = { version = "0.8", default-features = false, features = [ + "alloc", + "bytecheck", +] } diff --git a/ceno_rt/src/allocator.rs b/ceno_rt/src/allocator.rs index c5202f1de..5720ac676 100644 --- a/ceno_rt/src/allocator.rs +++ b/ceno_rt/src/allocator.rs @@ -1,6 +1,8 @@ //! A bump allocator. //! Based on https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html +// Plan: do the same memory trickery as for the allocator. + use core::alloc::{GlobalAlloc, Layout}; struct SimpleAllocator { diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 9945057ec..51856a2e1 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -6,6 +6,9 @@ use core::arch::{asm, global_asm}; mod allocator; +mod mmio; +pub use mmio::{read, read_slice}; + mod io; pub use io::info_out; @@ -80,6 +83,8 @@ macro_rules! entry { #[no_mangle] unsafe extern "C" fn _start_rust() -> ! { allocator::init_heap(); + mmio::init_hints(); + { extern "C" { fn bespoke_entrypoint(); diff --git a/ceno_rt/src/mmio.rs b/ceno_rt/src/mmio.rs new file mode 100644 index 000000000..37937d108 --- /dev/null +++ b/ceno_rt/src/mmio.rs @@ -0,0 +1,109 @@ +//! Memory-mapped I/O (MMIO) functions. + +use rkyv::{Portable, api::high::HighValidator, bytecheck::CheckBytes, rancor::Error}; + +use core::slice::from_raw_parts; + +use crate::_hints_start; + +static mut NEXT_HINT_LEN_AT: usize = 0x4000_0000; + +pub unsafe fn init_hints() { + NEXT_HINT_LEN_AT = core::ptr::from_ref::(&_hints_start).cast::() as usize; +} + +// pub const HINTS_START: usize = 0x4000_0000; + +// static mut STDIN: SerialisedCenoStdin = SerialisedCenoStdin(&[]); +// static mut STDIN_ITER: SerialisedCenoStdinIter = unsafe { SerialisedCenoStdinIter { +// next: 0, +// buf: &raw const STDIN, +// }}; + +// // This should only be called once at the start. +// // Similar to how we init the allocator. +// pub fn set_hints_slice() { +// unsafe { +// STDIN = SerialisedCenoStdin(from_raw_parts( +// HINTS.start as *const u8, +// HINTS.end - HINTS.start, +// )) +// }; +// } + +// // static HINTS_SLICE: &[u8] = unsafe { +// // slice::from_raw_parts(HINTS.start as *const u8, HINTS.end - HINTS.start) +// // }; + +// // const HINTS: Range = 0x4000_0000..0x5000_0000; + +// // static HINTS_SLICE: &'static [u8] = +// // unsafe { core::slice::from_raw_parts(HINTS.start as *const u8, HINTS.end - HINTS.start) }; + +// // #[derive(Default)] +// // pub struct CenoStdin { +// // pub items: Vec, +// // } + +// pub struct SerialisedCenoStdin<'a>(&'a [u8]); + +// pub struct SerialisedCenoStdinIter<'a> { +// buf: &'a SerialisedCenoStdin<'a>, +// next: usize, +// } + +// // pub fn read() { + +// // } + +pub fn read_slice<'a>() -> &'a [u8] { + unsafe { + let len: u32 = core::ptr::read(NEXT_HINT_LEN_AT as *const u32); + NEXT_HINT_LEN_AT += 4; + + let start: *const u8 = core::ptr::from_ref::(&crate::_hints_start).cast::(); + &from_raw_parts(start, 1 << 30)[..len as usize] + } +} + +pub fn read<'a, T>() -> &'a T +where + T: Portable + for<'c> CheckBytes>, +{ + rkyv::access::(read_slice()).unwrap() +} + +// impl<'b> SerialisedCenoStdinIter<'b> { +// pub fn read<'a, T>(&'a mut self) -> &'b T +// where +// T: Portable + for<'c> CheckBytes>, +// { +// rkyv::access::(self.read_slice()).unwrap() +// } + +// pub fn read_slice<'a>(&'a mut self) -> &'b [u8] { +// self.next().unwrap() +// } +// } + +// impl<'a> Iterator for SerialisedCenoStdinIter<'a> { +// type Item = &'a [u8]; +// fn next(&mut self) -> Option { +// let len = u32::from_le_bytes( +// self.buf.0[self.next..][..size_of::()] +// .try_into() +// .unwrap(), +// ) as usize; +// self.next += size_of::(); +// Some(&self.buf.0[..len]) +// } +// } + +// impl<'a> IntoIterator for &'a SerialisedCenoStdin<'a> { +// type Item = &'a [u8]; +// type IntoIter = SerialisedCenoStdinIter<'a>; + +// fn into_iter(self) -> Self::IntoIter { +// SerialisedCenoStdinIter { next: 0, buf: self } +// } +// } diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 359355930..a7532992d 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -2,11 +2,35 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "bytecheck" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c8f430744b23b54ad15161fcbc22d82a29b73eacbe425fea23ec822600bc6f" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "rancor", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523363cbe1df49b68215efdf500b103ac3b0fb4836aed6d15689a076eadb8fff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ceno_rt" version = "0.1.0" dependencies = [ "riscv", + "rkyv", ] [[package]] @@ -28,6 +52,32 @@ dependencies = [ "ceno_rt", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "munge" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64142d38c84badf60abf06ff9bd80ad2174306a5b11bd4706535090a30a419df" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb5c1d8184f13f7d0ccbeeca0def2f9a181bce2624302793005f5ca8aa62e5e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "paste" version = "1.0.15" @@ -43,6 +93,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "ptr_meta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.37" @@ -52,6 +122,24 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rancor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf5f7161924b9d1cea0e4cabc97c372cea92b5f927fc13c6bca67157a0ad947" +dependencies = [ + "ptr_meta", +] + +[[package]] +name = "rend" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35e8a6bf28cd121053a66aa2e6a2e3eaffad4a60012179f0e864aa5ffeff215" +dependencies = [ + "bytecheck", +] + [[package]] name = "riscv" version = "0.12.1" @@ -82,6 +170,39 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" +[[package]] +name = "rkyv" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b11a153aec4a6ab60795f8ebe2923c597b16b05bb1504377451e705ef1a45323" +dependencies = [ + "bytecheck", + "hashbrown", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beb382a4d9f53bd5c0be86b10d8179c3f8a14c30bf774ff77096ed6581e35981" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "syn" version = "2.0.82" @@ -93,6 +214,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "unicode-ident" version = "1.0.13" From efde5b3514cae333450f01db1cc87a52942dfecc Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 6 Dec 2024 14:18:08 +0800 Subject: [PATCH 03/64] Clean up --- ceno_host/Cargo.toml | 5 ++ ceno_host/src/lib.rs | 194 +------------------------------------------ ceno_rt/memory.x | 1 + 3 files changed, 9 insertions(+), 191 deletions(-) diff --git a/ceno_host/Cargo.toml b/ceno_host/Cargo.toml index d72edce86..e8e54d04e 100644 --- a/ceno_host/Cargo.toml +++ b/ceno_host/Cargo.toml @@ -1,7 +1,12 @@ [package] +categories.workspace = true +description = "Support for the host port of a Ceno application" edition.workspace = true +keywords.workspace = true license.workspace = true name = "ceno_host" +readme.workspace = true +repository.workspace = true version.workspace = true [dependencies] diff --git a/ceno_host/src/lib.rs b/ceno_host/src/lib.rs index 0fa7293ee..f23a9ce7f 100644 --- a/ceno_host/src/lib.rs +++ b/ceno_host/src/lib.rs @@ -4,12 +4,7 @@ use itertools::izip; use rkyv::{ - Portable, Serialize, - api::high::{HighSerializer, HighValidator}, - bytecheck::CheckBytes, - rancor::Error, - ser::allocator::ArenaHandle, - to_bytes, + Serialize, api::high::HighSerializer, rancor::Error, ser::allocator::ArenaHandle, to_bytes, util::AlignedVec, }; @@ -34,7 +29,7 @@ impl CenoStdin { Ok(()) } - pub fn finalise(&self) -> SerialisedCenoStdin { + pub fn finalise(&self) -> AlignedVec { // TODO: perhaps don't hardcode 16 here. // It's from rkyv's format, so we can probably take it from there somehow? // TODO: clean this up. @@ -62,189 +57,6 @@ impl CenoStdin { buf.extend_from_slice(&vec![0; buf.len().next_multiple_of(16) - buf.len()]); assert_eq!(buf.len(), offset.next_multiple_of(16) as usize); } - SerialisedCenoStdin(buf) - } -} - -pub struct SerialisedCenoStdinIter<'a> { - buf: &'a SerialisedCenoStdin, - next: usize, -} - -impl<'b> SerialisedCenoStdinIter<'b> { - pub fn read<'a, T>(&'a mut self) -> &'b T - where - T: Portable + for<'c> CheckBytes>, - { - rkyv::access::(self.read_slice()).unwrap() - } - - pub fn read_slice<'a>(&'a mut self) -> &'b [u8] { - self.next().unwrap() - } -} - -impl<'a> Iterator for SerialisedCenoStdinIter<'a> { - type Item = &'a [u8]; - fn next(&mut self) -> Option { - let len = u32::from_le_bytes( - self.buf.0[self.next..][..size_of::()] - .try_into() - .unwrap(), - ) as usize; - self.next += size_of::(); - Some(&self.buf.0[..len]) - } -} - -impl<'a> IntoIterator for &'a SerialisedCenoStdin { - type Item = &'a [u8]; - type IntoIter = SerialisedCenoStdinIter<'a>; - - fn into_iter(self) -> Self::IntoIter { - SerialisedCenoStdinIter { next: 0, buf: self } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use rand::Rng; - use rkyv::{ - Archive, Deserialize, deserialize, - rancor::{Error, Failure}, - to_bytes, - util::AlignedVec, - }; - - #[derive(Archive, Deserialize, Serialize, Debug, PartialEq)] - #[rkyv( - // This will generate a PartialEq impl between our unarchived - // and archived types - compare(PartialEq), - // Derives can be passed through to the generated type: - derive(Debug), - )] - struct Test { - int: u32, - string: String, - option: Option>, - } - - #[derive(Archive, Deserialize, Serialize, Debug, PartialEq)] - #[rkyv( - // This will generate a PartialEq impl between our unarchived - // and archived types - compare(PartialEq), - // Derives can be passed through to the generated type: - derive(Debug), - )] - struct Toast { - stuff: Option>, - } - - /// The equivalent of this function would run in the host. - /// - /// We create three different items, and show that we can read them back in `consume`. - pub fn make_stdin() -> SerialisedCenoStdin { - let mut stdin = CenoStdin::default(); - stdin - .write(&Test { - int: 0xDEAD_BEEF, - string: "hello world".to_string(), - option: Some(vec![1, 2, 3, 4]), - }) - .unwrap(); - stdin.write(&0xaf_u8).unwrap(); - stdin - .write(&Toast { - stuff: Some(vec!["hello scroll".to_string()]), - }) - .unwrap(); - stdin.finalise() - } - - /// The equivalent of this function would run in the guest. - /// - /// `stdin` would be the memory mapped region for private hints. - pub fn consume(stdin: SerialisedCenoStdin) { - println!("\nConsuming..."); - let mut iter: SerialisedCenoStdinIter = stdin.into_iter(); - let test1: &ArchivedTest = iter.read(); - assert_eq!(test1, &Test { - int: 0xDEAD_BEEF, - string: "hello world".to_string(), - option: Some(vec![1, 2, 3, 4]), - }); - let number: &u8 = iter.read(); - assert_eq!(number, &0xaf_u8); - let test2: &ArchivedToast = iter.read(); - assert_eq!(test2, &Toast { - stuff: Some(vec!["hello scroll".to_string()]), - }); - } - - #[test] - fn test_prepare_and_consume_items() { - let stdin = make_stdin(); - consume(stdin); - } - - #[test] - fn test_rkyv_padding() { - let value = Test { - int: 42, - string: "hello world".to_string(), - option: Some(vec![1, 2, 3, 4]), - }; - - // Serializing is as easy as a single function call - let bytes: AlignedVec = to_bytes::(&value).unwrap(); - - { - // Or you can customize your serialization for better performance or control - // over resource usage - use rkyv::{api::high::to_bytes_with_alloc, ser::allocator::Arena}; - - let mut arena = Arena::new(); - let _bytes = to_bytes_with_alloc::<_, Error>(&value, arena.acquire()).unwrap(); - } - // You can use the safe API for fast zero-copy deserialization - let archived = rkyv::access::(&bytes[..]).unwrap(); - assert_eq!(archived, &value); - - // And you can always deserialize back to the original type - let deserialized = deserialize::(archived).unwrap(); - assert_eq!(deserialized, value); - - let mut rng = rand::thread_rng(); - - { - // https://rkyv.org/format.html says: - // This deterministic layout means that you don't need to store the position of - // the root object in most cases. As long as your buffer ends right at the end of - // your root object, you can use `access` with your buffer. - - // Thus left padding should work. We add 1024 bytes of random junk to the left. - - let mut left_padded_bytes = vec![0; 1024]; - rng.fill(&mut left_padded_bytes[..]); - // Then add our original bytes to the end: - left_padded_bytes.extend_from_slice(&bytes); - - // we should be able to access as before: - let archived2 = rkyv::access::(&left_padded_bytes[..]).unwrap(); - assert_eq!(archived2, &value); - } - { - // The same but right padding junk should fail: - let mut right_padded_bytes = bytes.clone(); - let mut junk = vec![0; 1024]; - rng.fill(&mut junk[..]); - right_padded_bytes.extend_from_slice(&junk); - // we should not be able to access as before: - let _ = rkyv::access::(&right_padded_bytes[..]) - .expect_err("This should fail."); - } + buf } } diff --git a/ceno_rt/memory.x b/ceno_rt/memory.x index 7c2a2538b..0baf12fcd 100644 --- a/ceno_rt/memory.x +++ b/ceno_rt/memory.x @@ -11,4 +11,5 @@ REGION_ALIAS("REGION_DATA", RAM); REGION_ALIAS("REGION_BSS", RAM); REGION_ALIAS("REGION_HEAP", RAM); REGION_ALIAS("REGION_STACK", RAM); + REGION_ALIAS("REGION_HINTS", HINTS); From d7949a65027f93205df86cc46d64a0352fa132ae Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 6 Dec 2024 14:56:18 +0800 Subject: [PATCH 04/64] Snapshot --- Cargo.lock | 2 ++ Cargo.toml | 1 + ceno_emul/Cargo.toml | 2 +- ceno_host/Cargo.toml | 2 ++ ceno_host/src/lib.rs | 71 +++++++++++++++++++++++++++++++++++++++----- 5 files changed, 70 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c09d9dc1..5049c3f62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -276,6 +276,8 @@ dependencies = [ name = "ceno_host" version = "0.1.0" dependencies = [ + "anyhow", + "ceno_emul", "itertools 0.13.0", "rand", "rkyv", diff --git a/Cargo.toml b/Cargo.toml index 9ba75bed7..86a00b462 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ repository = "https://github.com/scroll-tech/ceno" version = "0.1.0" [workspace.dependencies] +anyhow = { version = "1.0", default-features = false } ark-std = "0.4" cfg-if = "1.0" criterion = { version = "0.5", features = ["html_reports"] } diff --git a/ceno_emul/Cargo.toml b/ceno_emul/Cargo.toml index 38f0a8bfd..127dc9f4c 100644 --- a/ceno_emul/Cargo.toml +++ b/ceno_emul/Cargo.toml @@ -10,7 +10,7 @@ repository.workspace = true version.workspace = true [dependencies] -anyhow = { version = "1.0", default-features = false } +anyhow.workspace = true elf = "0.7" itertools.workspace = true num-derive.workspace = true diff --git a/ceno_host/Cargo.toml b/ceno_host/Cargo.toml index e8e54d04e..eb3395aa4 100644 --- a/ceno_host/Cargo.toml +++ b/ceno_host/Cargo.toml @@ -10,8 +10,10 @@ repository.workspace = true version.workspace = true [dependencies] +anyhow.workspace = true itertools.workspace = true rkyv = { version = "0.8.9", default-features = false, features = ["alloc", "bytecheck"] } +ceno_emul = { path = "../ceno_emul" } [dev-dependencies] rand.workspace = true diff --git a/ceno_host/src/lib.rs b/ceno_host/src/lib.rs index f23a9ce7f..2a64bd0e8 100644 --- a/ceno_host/src/lib.rs +++ b/ceno_host/src/lib.rs @@ -2,6 +2,10 @@ // for the host and guest respectively for the user of our library. // Everything else in here would be hidden. +use std::iter::zip; + +use anyhow::Result; +use ceno_emul::{ByteAddr, EmuContext, IterAddresses, Platform, VMState}; use itertools::izip; use rkyv::{ Serialize, api::high::HighSerializer, rancor::Error, ser::allocator::ArenaHandle, to_bytes, @@ -29,34 +33,87 @@ impl CenoStdin { Ok(()) } - pub fn finalise(&self) -> AlignedVec { + pub fn finalise(&self) -> Vec { // TODO: perhaps don't hardcode 16 here. // It's from rkyv's format, so we can probably take it from there somehow? // TODO: clean this up. let initial_offset = (size_of::() * self.items.len()).next_multiple_of(16); - println!("offset: {}", initial_offset); + // println!("offset: {}", initial_offset); let offsets: Vec = self .items .iter() .scan(initial_offset, |acc, bytes| { let output = (*acc + bytes.len()) as u32; - print!("len: {}\t", bytes.len()); + // print!("len: {}\t", bytes.len()); *acc += bytes.len().next_multiple_of(16); - println!("acc: {}", *acc); + // println!("acc: {}", *acc); Some(output) }) .collect(); let offsets_u8: Vec = offsets.iter().copied().flat_map(u32::to_le_bytes).collect(); let mut buf: AlignedVec = AlignedVec::new(); buf.extend_from_slice(&offsets_u8); - println!("buf.len() after offsets: {}", buf.len()); + // println!("buf.len() after offsets: {}", buf.len()); buf.extend_from_slice(&vec![0; buf.len().next_multiple_of(16) - buf.len()]); - println!("buf.len() after offset padding: {}", buf.len()); + // println!("buf.len() after offset padding: {}", buf.len()); for (offset, item) in izip!(offsets, &self.items) { buf.extend_from_slice(item); buf.extend_from_slice(&vec![0; buf.len().next_multiple_of(16) - buf.len()]); assert_eq!(buf.len(), offset.next_multiple_of(16) as usize); } - buf + let (prefix, hints, postfix): (_, &[u32], _) = unsafe { buf.align_to() }; + assert_eq!(prefix, &[]); + assert_eq!(postfix, &[]); + hints.to_vec() + } +} + +// TODO: clean up, don't copy and paste. +const WORD_SIZE: usize = 4; +const INFO_OUT_ADDR: u32 = 0xC000_0000; + +fn read_all_messages(state: &VMState) -> Vec { + let mut all_messages = Vec::new(); + let mut word_offset = 0; + loop { + let out = read_message(state, word_offset); + if out.is_empty() { + break; + } + word_offset += out.len().div_ceil(WORD_SIZE) as u32 + 1; + all_messages.push(out); + } + all_messages +} + +fn read_message(state: &VMState, word_offset: u32) -> String { + let out_addr = ByteAddr(INFO_OUT_ADDR).waddr() + word_offset; + let byte_len = state.peek_memory(out_addr); + let word_len_up = byte_len.div_ceil(4); + + let mut info_out = Vec::with_capacity(WORD_SIZE * word_len_up as usize); + for i in 1..1 + word_len_up { + let value = state.peek_memory(out_addr + i); + info_out.extend_from_slice(&value.to_le_bytes()); + } + info_out.truncate(byte_len as usize); + String::from_utf8_lossy(&info_out).to_string() +} + +// TODO(Matthias): also return exit code (if any) +pub fn run(platform: Platform, elf: &[u8], hints: &CenoStdin) -> Vec { + let hints: Vec = hints.finalise(); + + let mut state = VMState::new_from_elf(platform.clone(), elf).expect("Failed to load ELF"); + + for (addr, value) in zip(platform.hints.iter_addresses(), hints) { + state.init_memory(addr.into(), value); } + + let steps = state + .iter_until_halt() + .collect::>>() + .expect("Failed to run the program"); + eprintln!("Emulator ran for {} steps.", steps.len()); + read_all_messages(&state) } From e002be2ca7c6f87f90824d847402373c27784d64 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 6 Dec 2024 15:06:27 +0800 Subject: [PATCH 05/64] Clean up --- ceno_host/src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ceno_host/src/lib.rs b/ceno_host/src/lib.rs index 2a64bd0e8..f9c74c4f8 100644 --- a/ceno_host/src/lib.rs +++ b/ceno_host/src/lib.rs @@ -1,7 +1,3 @@ -// See `make_stdin` and `consume` for the main entry points, and how this would look -// for the host and guest respectively for the user of our library. -// Everything else in here would be hidden. - use std::iter::zip; use anyhow::Result; From 8a4906ff459bd47873e4538c5746f7dd8ca4082d Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 6 Dec 2024 15:06:52 +0800 Subject: [PATCH 06/64] Clean up --- ceno_host/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ceno_host/src/lib.rs b/ceno_host/src/lib.rs index f9c74c4f8..cd82dc275 100644 --- a/ceno_host/src/lib.rs +++ b/ceno_host/src/lib.rs @@ -13,8 +13,6 @@ pub struct CenoStdin { pub items: Vec, } -pub struct SerialisedCenoStdin(pub AlignedVec); - impl CenoStdin { pub fn write_slice(&mut self, bytes: AlignedVec) { self.items.push(bytes); From b789f432f2a036b9b96ee128529542708708b5da Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 6 Dec 2024 15:07:43 +0800 Subject: [PATCH 07/64] Clean up --- ceno_host/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ceno_host/src/lib.rs b/ceno_host/src/lib.rs index cd82dc275..95d265d75 100644 --- a/ceno_host/src/lib.rs +++ b/ceno_host/src/lib.rs @@ -22,9 +22,7 @@ impl CenoStdin { &mut self, item: &impl for<'a> Serialize, Error>>, ) -> Result<(), Error> { - let bytes = to_bytes::(item)?; - self.write_slice(bytes); - Ok(()) + to_bytes::(item).map(|bytes| self.write_slice(bytes)) } pub fn finalise(&self) -> Vec { From d0af9d85822a1602575e624187c1bda52e2e82e5 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 6 Dec 2024 15:26:25 +0800 Subject: [PATCH 08/64] Clean up --- ceno_rt/src/allocator.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ceno_rt/src/allocator.rs b/ceno_rt/src/allocator.rs index 5720ac676..c5202f1de 100644 --- a/ceno_rt/src/allocator.rs +++ b/ceno_rt/src/allocator.rs @@ -1,8 +1,6 @@ //! A bump allocator. //! Based on https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html -// Plan: do the same memory trickery as for the allocator. - use core::alloc::{GlobalAlloc, Layout}; struct SimpleAllocator { From 83333317f9396dd1fbc4edcf3c8afdbeb1f39dc4 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 6 Dec 2024 15:27:11 +0800 Subject: [PATCH 09/64] Remove commented out code --- ceno_rt/src/mmio.rs | 79 --------------------------------------------- 1 file changed, 79 deletions(-) diff --git a/ceno_rt/src/mmio.rs b/ceno_rt/src/mmio.rs index 37937d108..052fabf63 100644 --- a/ceno_rt/src/mmio.rs +++ b/ceno_rt/src/mmio.rs @@ -12,50 +12,6 @@ pub unsafe fn init_hints() { NEXT_HINT_LEN_AT = core::ptr::from_ref::(&_hints_start).cast::() as usize; } -// pub const HINTS_START: usize = 0x4000_0000; - -// static mut STDIN: SerialisedCenoStdin = SerialisedCenoStdin(&[]); -// static mut STDIN_ITER: SerialisedCenoStdinIter = unsafe { SerialisedCenoStdinIter { -// next: 0, -// buf: &raw const STDIN, -// }}; - -// // This should only be called once at the start. -// // Similar to how we init the allocator. -// pub fn set_hints_slice() { -// unsafe { -// STDIN = SerialisedCenoStdin(from_raw_parts( -// HINTS.start as *const u8, -// HINTS.end - HINTS.start, -// )) -// }; -// } - -// // static HINTS_SLICE: &[u8] = unsafe { -// // slice::from_raw_parts(HINTS.start as *const u8, HINTS.end - HINTS.start) -// // }; - -// // const HINTS: Range = 0x4000_0000..0x5000_0000; - -// // static HINTS_SLICE: &'static [u8] = -// // unsafe { core::slice::from_raw_parts(HINTS.start as *const u8, HINTS.end - HINTS.start) }; - -// // #[derive(Default)] -// // pub struct CenoStdin { -// // pub items: Vec, -// // } - -// pub struct SerialisedCenoStdin<'a>(&'a [u8]); - -// pub struct SerialisedCenoStdinIter<'a> { -// buf: &'a SerialisedCenoStdin<'a>, -// next: usize, -// } - -// // pub fn read() { - -// // } - pub fn read_slice<'a>() -> &'a [u8] { unsafe { let len: u32 = core::ptr::read(NEXT_HINT_LEN_AT as *const u32); @@ -72,38 +28,3 @@ where { rkyv::access::(read_slice()).unwrap() } - -// impl<'b> SerialisedCenoStdinIter<'b> { -// pub fn read<'a, T>(&'a mut self) -> &'b T -// where -// T: Portable + for<'c> CheckBytes>, -// { -// rkyv::access::(self.read_slice()).unwrap() -// } - -// pub fn read_slice<'a>(&'a mut self) -> &'b [u8] { -// self.next().unwrap() -// } -// } - -// impl<'a> Iterator for SerialisedCenoStdinIter<'a> { -// type Item = &'a [u8]; -// fn next(&mut self) -> Option { -// let len = u32::from_le_bytes( -// self.buf.0[self.next..][..size_of::()] -// .try_into() -// .unwrap(), -// ) as usize; -// self.next += size_of::(); -// Some(&self.buf.0[..len]) -// } -// } - -// impl<'a> IntoIterator for &'a SerialisedCenoStdin<'a> { -// type Item = &'a [u8]; -// type IntoIter = SerialisedCenoStdinIter<'a>; - -// fn into_iter(self) -> Self::IntoIter { -// SerialisedCenoStdinIter { next: 0, buf: self } -// } -// } From 8597909a0edf8509d54ebfc546c5a8b7e1ab391d Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 6 Dec 2024 15:29:57 +0800 Subject: [PATCH 10/64] Clarify comment --- ceno_host/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ceno_host/src/lib.rs b/ceno_host/src/lib.rs index 95d265d75..c64bce07d 100644 --- a/ceno_host/src/lib.rs +++ b/ceno_host/src/lib.rs @@ -60,7 +60,9 @@ impl CenoStdin { } } -// TODO: clean up, don't copy and paste. +// TODO(Matthias): much of this is copied from `test_elf.rs` in Ceno. These are generally useful +// functions, so we should make them available for both crates, instead of copy-and-pasting here. + const WORD_SIZE: usize = 4; const INFO_OUT_ADDR: u32 = 0xC000_0000; From 1b14291d51265d81b7d698d3b4d5c8ccef02371a Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 6 Dec 2024 18:38:19 +0800 Subject: [PATCH 11/64] taplo fmt --- ceno_host/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceno_host/Cargo.toml b/ceno_host/Cargo.toml index eb3395aa4..e8907cef6 100644 --- a/ceno_host/Cargo.toml +++ b/ceno_host/Cargo.toml @@ -11,9 +11,9 @@ version.workspace = true [dependencies] anyhow.workspace = true +ceno_emul = { path = "../ceno_emul" } itertools.workspace = true rkyv = { version = "0.8.9", default-features = false, features = ["alloc", "bytecheck"] } -ceno_emul = { path = "../ceno_emul" } [dev-dependencies] rand.workspace = true From f44191315cab984d4ce27addc96e0fda5be9faa9 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Mon, 9 Dec 2024 14:48:43 +0800 Subject: [PATCH 12/64] Implement TODOs for Harvard Architecture PR --- Cargo.lock | 75 +++ ceno_emul/Cargo.toml | 1 + ceno_emul/src/disassemble/mod.rs | 392 ++++++++++++++++ ceno_emul/src/elf.rs | 8 +- ceno_emul/src/lib.rs | 6 +- ceno_emul/src/platform.rs | 5 - ceno_emul/src/rv32im.rs | 437 ++++-------------- ceno_emul/src/rv32im_encode.rs | 133 +----- ceno_emul/src/tracer.rs | 46 +- ceno_emul/src/vm_state.rs | 15 +- ceno_emul/tests/test_elf.rs | 2 +- ceno_emul/tests/test_vm_trace.rs | 61 +-- ceno_zkvm/examples/riscv_opcodes.rs | 58 +-- ceno_zkvm/src/e2e.rs | 11 +- ceno_zkvm/src/instructions/riscv.rs | 5 +- ceno_zkvm/src/instructions/riscv/arith_imm.rs | 7 +- .../src/instructions/riscv/branch/test.rs | 16 +- .../instructions/riscv/dummy/dummy_circuit.rs | 18 +- .../src/instructions/riscv/dummy/test.rs | 2 +- ceno_zkvm/src/instructions/riscv/ecall.rs | 2 +- .../src/instructions/riscv/ecall_insn.rs | 4 +- ceno_zkvm/src/instructions/riscv/jump.rs | 4 - .../src/instructions/riscv/jump/auipc.rs | 94 ---- ceno_zkvm/src/instructions/riscv/jump/lui.rs | 64 --- ceno_zkvm/src/instructions/riscv/jump/test.rs | 78 +--- .../riscv/logic_imm/logic_imm_circuit.rs | 4 +- .../src/instructions/riscv/memory/test.rs | 53 +-- ceno_zkvm/src/instructions/riscv/rv32im.rs | 24 +- ceno_zkvm/src/instructions/riscv/shift_imm.rs | 8 +- ceno_zkvm/src/instructions/riscv/slti.rs | 2 +- .../src/instructions/riscv/test_utils.rs | 23 - ceno_zkvm/src/instructions/riscv/u_insn.rs | 66 --- ceno_zkvm/src/scheme/mock_prover.rs | 35 +- ceno_zkvm/src/scheme/tests.rs | 43 +- ceno_zkvm/src/tables/program.rs | 65 +-- clippy.toml | 7 +- 36 files changed, 763 insertions(+), 1111 deletions(-) create mode 100644 ceno_emul/src/disassemble/mod.rs delete mode 100644 ceno_zkvm/src/instructions/riscv/jump/auipc.rs delete mode 100644 ceno_zkvm/src/instructions/riscv/jump/lui.rs delete mode 100644 ceno_zkvm/src/instructions/riscv/test_utils.rs delete mode 100644 ceno_zkvm/src/instructions/riscv/u_insn.rs diff --git a/Cargo.lock b/Cargo.lock index d84685eb4..865b783f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,6 +238,7 @@ dependencies = [ "itertools 0.13.0", "num-derive", "num-traits", + "rrs-succinct", "strum", "strum_macros", "tracing", @@ -583,6 +584,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "either" version = "1.13.0" @@ -1194,6 +1201,27 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "object" version = "0.36.5" @@ -1424,6 +1452,16 @@ dependencies = [ "uint", ] +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.92" @@ -1611,6 +1649,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" +[[package]] +name = "rrs-succinct" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3372685893a9f67d18e98e792d690017287fd17379a83d798d958e517d380fa9" +dependencies = [ + "downcast-rs", + "num_enum", + "paste", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1927,6 +1976,23 @@ dependencies = [ "serde_json", ] +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.41" @@ -2345,6 +2411,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/ceno_emul/Cargo.toml b/ceno_emul/Cargo.toml index 38f0a8bfd..74562142a 100644 --- a/ceno_emul/Cargo.toml +++ b/ceno_emul/Cargo.toml @@ -15,6 +15,7 @@ elf = "0.7" itertools.workspace = true num-derive.workspace = true num-traits.workspace = true +rrs_lib = { package = "rrs-succinct", version = "0.1.0" } strum.workspace = true strum_macros.workspace = true tracing.workspace = true diff --git a/ceno_emul/src/disassemble/mod.rs b/ceno_emul/src/disassemble/mod.rs new file mode 100644 index 000000000..d9fa3324e --- /dev/null +++ b/ceno_emul/src/disassemble/mod.rs @@ -0,0 +1,392 @@ +use itertools::izip; +use rrs_lib::{ + InstructionProcessor, + instruction_formats::{BType, IType, ITypeCSR, ITypeShamt, JType, RType, SType, UType}, + process_instruction, +}; + +use crate::rv32im::Instruction; +type InsnKind = crate::rv32im::InsnKind; + +/// A transpiler that converts the 32-bit encoded instructions into instructions. +pub(crate) struct InstructionTranspiler { + pc: u32, + word: u32, +} + +impl Instruction { + /// Create a new [`Instruction`] from an R-type instruction. + #[must_use] + pub const fn from_r_type(kind: InsnKind, dec_insn: &RType, raw: u32) -> Self { + Self { + kind, + rd: dec_insn.rd, + rs1: dec_insn.rs1, + rs2: dec_insn.rs2, + imm: 0, + raw, + } + } + + /// Create a new [`Instruction`] from an I-type instruction. + #[must_use] + pub const fn from_i_type(kind: InsnKind, dec_insn: &IType, raw: u32) -> Self { + Self { + kind, + rd: dec_insn.rd, + rs1: dec_insn.rs1, + imm: dec_insn.imm, + rs2: 0, + raw, + } + } + + /// Create a new [`Instruction`] from an I-type instruction with a shamt. + #[must_use] + pub const fn from_i_type_shamt(kind: InsnKind, dec_insn: &ITypeShamt, raw: u32) -> Self { + Self { + kind, + rd: dec_insn.rd, + rs1: dec_insn.rs1, + imm: dec_insn.shamt as i32, + rs2: 0, + raw, + } + } + + /// Create a new [`Instruction`] from an S-type instruction. + #[must_use] + pub const fn from_s_type(kind: InsnKind, dec_insn: &SType, raw: u32) -> Self { + Self { + kind, + rd: 0, + rs1: dec_insn.rs1, + rs2: dec_insn.rs2, + imm: dec_insn.imm, + raw, + } + } + + /// Create a new [`Instruction`] from a B-type instruction. + #[must_use] + pub const fn from_b_type(kind: InsnKind, dec_insn: &BType, raw: u32) -> Self { + Self { + kind, + rd: 0, + rs1: dec_insn.rs1, + rs2: dec_insn.rs2, + imm: dec_insn.imm, + raw, + } + } + + /// Create a new [`Instruction`] that is not implemented. + #[must_use] + pub const fn unimp(raw: u32) -> Self { + Self { + kind: InsnKind::INVALID, + rd: 0, + rs1: 0, + rs2: 0, + imm: 0, + raw, + } + } +} + +impl InstructionProcessor for InstructionTranspiler { + type InstructionResult = Instruction; + + fn process_add(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::ADD, &dec_insn, self.word) + } + + fn process_addi(&mut self, dec_insn: IType) -> Self::InstructionResult { + Instruction::from_i_type(InsnKind::ADDI, &dec_insn, self.word) + } + + fn process_sub(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::SUB, &dec_insn, self.word) + } + + fn process_xor(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::XOR, &dec_insn, self.word) + } + + fn process_xori(&mut self, dec_insn: IType) -> Self::InstructionResult { + Instruction::from_i_type(InsnKind::XORI, &dec_insn, self.word) + } + + fn process_or(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::OR, &dec_insn, self.word) + } + + fn process_ori(&mut self, dec_insn: IType) -> Self::InstructionResult { + Instruction::from_i_type(InsnKind::ORI, &dec_insn, self.word) + } + + fn process_and(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::AND, &dec_insn, self.word) + } + + fn process_andi(&mut self, dec_insn: IType) -> Self::InstructionResult { + Instruction::from_i_type(InsnKind::ANDI, &dec_insn, self.word) + } + + fn process_sll(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::SLL, &dec_insn, self.word) + } + + fn process_slli(&mut self, dec_insn: ITypeShamt) -> Self::InstructionResult { + Instruction::from_i_type_shamt(InsnKind::SLLI, &dec_insn, self.word) + } + + fn process_srl(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::SRL, &dec_insn, self.word) + } + + fn process_srli(&mut self, dec_insn: ITypeShamt) -> Self::InstructionResult { + Instruction::from_i_type_shamt(InsnKind::SRLI, &dec_insn, self.word) + } + + fn process_sra(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::SRA, &dec_insn, self.word) + } + + fn process_srai(&mut self, dec_insn: ITypeShamt) -> Self::InstructionResult { + Instruction::from_i_type_shamt(InsnKind::SRAI, &dec_insn, self.word) + } + + fn process_slt(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::SLT, &dec_insn, self.word) + } + + fn process_slti(&mut self, dec_insn: IType) -> Self::InstructionResult { + Instruction::from_i_type(InsnKind::SLTI, &dec_insn, self.word) + } + + fn process_sltu(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::SLTU, &dec_insn, self.word) + } + + fn process_sltui(&mut self, dec_insn: IType) -> Self::InstructionResult { + Instruction::from_i_type(InsnKind::SLTIU, &dec_insn, self.word) + } + + fn process_lb(&mut self, dec_insn: IType) -> Self::InstructionResult { + Instruction::from_i_type(InsnKind::LB, &dec_insn, self.word) + } + + fn process_lh(&mut self, dec_insn: IType) -> Self::InstructionResult { + Instruction::from_i_type(InsnKind::LH, &dec_insn, self.word) + } + + fn process_lw(&mut self, dec_insn: IType) -> Self::InstructionResult { + Instruction::from_i_type(InsnKind::LW, &dec_insn, self.word) + } + + fn process_lbu(&mut self, dec_insn: IType) -> Self::InstructionResult { + Instruction::from_i_type(InsnKind::LBU, &dec_insn, self.word) + } + + fn process_lhu(&mut self, dec_insn: IType) -> Self::InstructionResult { + Instruction::from_i_type(InsnKind::LHU, &dec_insn, self.word) + } + + fn process_sb(&mut self, dec_insn: SType) -> Self::InstructionResult { + Instruction::from_s_type(InsnKind::SB, &dec_insn, self.word) + } + + fn process_sh(&mut self, dec_insn: SType) -> Self::InstructionResult { + Instruction::from_s_type(InsnKind::SH, &dec_insn, self.word) + } + + fn process_sw(&mut self, dec_insn: SType) -> Self::InstructionResult { + Instruction::from_s_type(InsnKind::SW, &dec_insn, self.word) + } + + fn process_beq(&mut self, dec_insn: BType) -> Self::InstructionResult { + Instruction::from_b_type(InsnKind::BEQ, &dec_insn, self.word) + } + + fn process_bne(&mut self, dec_insn: BType) -> Self::InstructionResult { + Instruction::from_b_type(InsnKind::BNE, &dec_insn, self.word) + } + + fn process_blt(&mut self, dec_insn: BType) -> Self::InstructionResult { + Instruction::from_b_type(InsnKind::BLT, &dec_insn, self.word) + } + + fn process_bge(&mut self, dec_insn: BType) -> Self::InstructionResult { + Instruction::from_b_type(InsnKind::BGE, &dec_insn, self.word) + } + + fn process_bltu(&mut self, dec_insn: BType) -> Self::InstructionResult { + Instruction::from_b_type(InsnKind::BLTU, &dec_insn, self.word) + } + + fn process_bgeu(&mut self, dec_insn: BType) -> Self::InstructionResult { + Instruction::from_b_type(InsnKind::BGEU, &dec_insn, self.word) + } + + fn process_jal(&mut self, dec_insn: JType) -> Self::InstructionResult { + Instruction { + kind: InsnKind::JAL, + rd: dec_insn.rd, + rs1: 0, + rs2: 0, + imm: dec_insn.imm, + raw: self.word, + } + } + + fn process_jalr(&mut self, dec_insn: IType) -> Self::InstructionResult { + Instruction { + kind: InsnKind::JALR, + rd: dec_insn.rd, + rs1: dec_insn.rs1, + rs2: 0, + imm: dec_insn.imm, + raw: self.word, + } + } + + /// Convert LUI to ADDI. + fn process_lui(&mut self, dec_insn: UType) -> Self::InstructionResult { + // Verify assumption that the immediate is already shifted left by 12 bits. + assert_eq!(dec_insn.imm & 0xfff, 0); + Instruction { + kind: InsnKind::ADDI, + rd: dec_insn.rd, + rs1: 0, + rs2: 0, + imm: dec_insn.imm, + raw: self.word, + } + } + + /// Convert AUIPC to ADDI. + fn process_auipc(&mut self, dec_insn: UType) -> Self::InstructionResult { + let pc = self.pc; + // Verify our assumption that the immediate is already shifted left by 12 bits. + assert_eq!(dec_insn.imm & 0xfff, 0); + Instruction { + kind: InsnKind::ADDI, + rd: dec_insn.rd, + rs1: 0, + rs2: 0, + imm: dec_insn.imm.wrapping_add(pc as i32), + raw: self.word, + } + } + + fn process_ecall(&mut self) -> Self::InstructionResult { + Instruction { + kind: InsnKind::ECALL, + rd: 0, + rs1: 0, + rs2: 0, + imm: 0, + raw: self.word, + } + } + + fn process_ebreak(&mut self) -> Self::InstructionResult { + Instruction { + kind: InsnKind::EBREAK, + rd: 0, + rs1: 0, + rs2: 0, + imm: 0, + raw: self.word, + } + } + + fn process_mul(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::MUL, &dec_insn, self.word) + } + + fn process_mulh(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::MULH, &dec_insn, self.word) + } + + fn process_mulhu(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::MULHU, &dec_insn, self.word) + } + + fn process_mulhsu(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::MULHSU, &dec_insn, self.word) + } + + fn process_div(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::DIV, &dec_insn, self.word) + } + + fn process_divu(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::DIVU, &dec_insn, self.word) + } + + fn process_rem(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::REM, &dec_insn, self.word) + } + + fn process_remu(&mut self, dec_insn: RType) -> Self::InstructionResult { + Instruction::from_r_type(InsnKind::REMU, &dec_insn, self.word) + } + + fn process_csrrc(&mut self, _: ITypeCSR) -> Self::InstructionResult { + Instruction::unimp(self.word) + } + + fn process_csrrci(&mut self, _: ITypeCSR) -> Self::InstructionResult { + Instruction::unimp(self.word) + } + + fn process_csrrs(&mut self, _: ITypeCSR) -> Self::InstructionResult { + Instruction::unimp(self.word) + } + + fn process_csrrsi(&mut self, _: ITypeCSR) -> Self::InstructionResult { + Instruction::unimp(self.word) + } + + fn process_csrrw(&mut self, _: ITypeCSR) -> Self::InstructionResult { + Instruction::unimp(self.word) + } + + fn process_csrrwi(&mut self, _: ITypeCSR) -> Self::InstructionResult { + Instruction::unimp(self.word) + } + + fn process_fence(&mut self, _: IType) -> Self::InstructionResult { + Instruction::unimp(self.word) + } + + fn process_mret(&mut self) -> Self::InstructionResult { + Instruction::unimp(self.word) + } + + fn process_wfi(&mut self) -> Self::InstructionResult { + Instruction::unimp(self.word) + } +} + +/// Transpile the [`Instruction`]s from the 32-bit encoded instructions. +/// +/// # Panics +/// +/// This function will return an error if the [`Instruction`] cannot be processed. +#[must_use] +pub fn transpile(base: u32, instructions_u32: &[u32]) -> Vec { + let mut instructions = Vec::new(); + for (pc, &word) in izip!(enumerate(base, 4), instructions_u32) { + let instruction = + process_instruction(&mut InstructionTranspiler { pc, word }, word).unwrap(); + instructions.push(instruction); + } + instructions +} + +fn enumerate(start: u32, step: u32) -> impl Iterator { + std::iter::successors(Some(start), move |&i| Some(i + step)) +} diff --git a/ceno_emul/src/elf.rs b/ceno_emul/src/elf.rs index 82697c619..ee59d3de3 100644 --- a/ceno_emul/src/elf.rs +++ b/ceno_emul/src/elf.rs @@ -18,7 +18,7 @@ extern crate alloc; use alloc::collections::BTreeMap; -use crate::addr::WORD_SIZE; +use crate::{addr::WORD_SIZE, disassemble::transpile, rv32im::Instruction}; use anyhow::{Context, Result, anyhow, bail}; use elf::{ ElfBytes, @@ -35,7 +35,7 @@ pub struct Program { /// This is the lowest address of the program's executable code pub base_address: u32, /// The instructions of the program - pub instructions: Vec, + pub instructions: Vec, /// The initial memory image pub image: BTreeMap, } @@ -45,7 +45,7 @@ impl Program { pub fn new( entry: u32, base_address: u32, - instructions: Vec, + instructions: Vec, image: BTreeMap, ) -> Program { Self { @@ -162,6 +162,8 @@ impl Program { assert!(entry >= base_address); assert!((entry - base_address) as usize <= instructions.len() * WORD_SIZE); + let instructions = transpile(base_address, &instructions); + Ok(Program { entry, base_address, diff --git a/ceno_emul/src/lib.rs b/ceno_emul/src/lib.rs index c734b1794..d98a06f1c 100644 --- a/ceno_emul/src/lib.rs +++ b/ceno_emul/src/lib.rs @@ -12,10 +12,12 @@ mod vm_state; pub use vm_state::VMState; mod rv32im; -pub use rv32im::{DecodedInstruction, EmuContext, InsnCategory, InsnCodes, InsnFormat, InsnKind}; +pub use rv32im::{EmuContext, InsnCategory, InsnFormat, InsnKind, Instruction}; mod elf; pub use elf::Program; mod rv32im_encode; -pub use rv32im_encode::encode_rv32; +pub use rv32im_encode::{encode_rv32, encode_rv32u}; + +pub mod disassemble; diff --git a/ceno_emul/src/platform.rs b/ceno_emul/src/platform.rs index c28bcda40..fec7e0181 100644 --- a/ceno_emul/src/platform.rs +++ b/ceno_emul/src/platform.rs @@ -73,10 +73,6 @@ impl Platform { self.is_ram(addr) } - pub fn can_execute(&self, addr: Addr) -> bool { - self.is_rom(addr) - } - // Environment calls. /// Register containing the ecall function code. (x5, t0) @@ -113,7 +109,6 @@ mod tests { #[test] fn test_no_overlap() { let p = CENO_PLATFORM; - assert!(p.can_execute(p.pc_base())); // ROM and RAM do not overlap. assert!(!p.is_rom(p.ram.start)); assert!(!p.is_rom(p.ram.end - WORD_SIZE as Addr)); diff --git a/ceno_emul/src/rv32im.rs b/ceno_emul/src/rv32im.rs index 52fac1508..86ab20ed8 100644 --- a/ceno_emul/src/rv32im.rs +++ b/ceno_emul/src/rv32im.rs @@ -15,9 +15,7 @@ // limitations under the License. use anyhow::{Result, anyhow}; -use itertools::enumerate; use num_derive::ToPrimitive; -use std::sync::OnceLock; use strum_macros::{Display, EnumIter}; use super::addr::{ByteAddr, RegIdx, WORD_SIZE, Word, WordAddr}; @@ -29,11 +27,8 @@ pub trait EmuContext { // Handle a trap fn trap(&self, cause: TrapCause) -> Result; - // Callback when instructions are decoded - fn on_insn_decoded(&mut self, _decoded: &DecodedInstruction) {} - // Callback when instructions end normally - fn on_normal_end(&mut self, _decoded: &DecodedInstruction) {} + fn on_normal_end(&mut self, _decoded: &Instruction) {} // Get the program counter fn get_pc(&self) -> ByteAddr; @@ -60,14 +55,7 @@ pub trait EmuContext { fn peek_memory(&self, addr: WordAddr) -> Word; /// Load from instruction cache - // TODO(Matthias): this should really return `Result` - // because the instruction cache should contain instructions, not just words. - fn fetch(&mut self, pc: WordAddr) -> Option; - - // Check access for instruction load - fn check_insn_load(&self, _addr: ByteAddr) -> bool { - true - } + fn fetch(&mut self, pc: WordAddr) -> Option; // Check access for data load fn check_data_load(&self, _addr: ByteAddr) -> bool { @@ -81,9 +69,8 @@ pub trait EmuContext { } /// An implementation of the basic ISA (RV32IM), that is instruction decoding and functional units. -pub struct Emulator { - table: &'static FastDecodeTable, -} +#[derive(Default)] +pub struct Emulator {} #[derive(Debug)] pub enum TrapCause { @@ -98,17 +85,18 @@ pub enum TrapCause { EcallError, } -#[derive(Clone, Debug, Default)] -pub struct DecodedInstruction { - insn: u32, - top_bit: u32, - // The bit fields of the instruction encoding, regardless of the instruction format. - func7: u32, - rs2: u32, - rs1: u32, - func3: u32, - rd: u32, - opcode: u32, +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct Instruction { + pub kind: InsnKind, + pub rs1: RegIdx, + pub rs2: RegIdx, + pub rd: RegIdx, + pub imm: i32, + /// `raw` is there only to produce better logging and error messages. + /// + /// Set to 0, if you are creating an instruction directly, + /// instead of decoding it from a raw 32-bit `Word`. + pub raw: Word, } #[derive(Clone, Copy, Debug)] @@ -133,9 +121,12 @@ pub enum InsnFormat { } use InsnFormat::*; -#[derive(Clone, Copy, Display, Debug, PartialEq, Eq, PartialOrd, Ord, EnumIter, ToPrimitive)] +#[derive( + Clone, Copy, Display, Debug, PartialEq, Eq, PartialOrd, Ord, EnumIter, ToPrimitive, Default, +)] #[allow(clippy::upper_case_acronyms)] pub enum InsnKind { + #[default] INVALID, ADD, SUB, @@ -164,8 +155,6 @@ pub enum InsnKind { BGEU, JAL, JALR, - LUI, - AUIPC, MUL, MULH, MULHSU, @@ -182,335 +171,91 @@ pub enum InsnKind { SB, SH, SW, - /// ECALL and EBREAK etc. - EANY, + ECALL, + EBREAK, } use InsnKind::*; -impl InsnKind { - pub const fn codes(self) -> InsnCodes { - RV32IM_ISA[self as usize] +impl From for InsnCategory { + fn from(kind: InsnKind) -> Self { + match kind { + INVALID => Invalid, + ADD | SUB | XOR | OR | AND | SLL | SRL | SRA | SLT | SLTU | MUL | MULH | MULHSU + | MULHU | DIV | DIVU | REM | REMU => Compute, + ADDI | XORI | ORI | ANDI | SLLI | SRLI | SRAI | SLTI | SLTIU => Compute, + BEQ | BNE | BLT | BGE | BLTU | BGEU => Branch, + JAL | JALR => Compute, + LB | LH | LW | LBU | LHU => Load, + SB | SH | SW => Store, + ECALL | EBREAK => System, + } } } -#[derive(Clone, Copy, Debug)] -pub struct InsnCodes { - pub format: InsnFormat, - pub kind: InsnKind, - pub category: InsnCategory, - pub(crate) opcode: u32, - pub(crate) func3: u32, - pub(crate) func7: u32, -} - -impl DecodedInstruction { - /// A virtual register which absorbs the writes to x0. - pub const RD_NULL: u32 = 32; - - pub fn new(insn: u32) -> Self { - Self { - insn, - top_bit: (insn & 0x80000000) >> 31, - func7: (insn & 0xfe000000) >> 25, - rs2: (insn & 0x01f00000) >> 20, - rs1: (insn & 0x000f8000) >> 15, - func3: (insn & 0x00007000) >> 12, - rd: (insn & 0x00000f80) >> 7, - opcode: insn & 0x0000007f, +// For encoding, which is useful for tests. +impl From for InsnFormat { + fn from(kind: InsnKind) -> Self { + match kind { + ADD | SUB | XOR | OR | AND | SLL | SRL | SRA | SLT | SLTU | MUL | MULH | MULHSU + | MULHU | DIV | DIVU | REM | REMU => R, + ADDI | XORI | ORI | ANDI | SLLI | SRLI | SRAI | SLTI | SLTIU => I, + BEQ | BNE | BLT | BGE | BLTU | BGEU => B, + JAL => J, + JALR => I, + LB | LH | LW | LBU | LHU => I, + SB | SH | SW => S, + ECALL | EBREAK => I, + INVALID => I, } } +} - pub fn encoded(&self) -> u32 { - self.insn - } - - pub fn opcode(&self) -> u32 { - self.opcode - } - - /// The internal register destination. It is either the regular rd, or an internal RD_NULL if - /// the instruction does not write to a register or writes to x0. +impl Instruction { + pub const RD_NULL: u32 = 32; pub fn rd_internal(&self) -> u32 { - match self.codes().format { - R | I | U | J if self.rd != 0 => self.rd, + match InsnFormat::from(self.kind) { + R | I | U | J if self.rd != 0 => self.rd as u32, _ => Self::RD_NULL, } } - - /// Get the rs1 field, regardless of the instruction format. - pub fn rs1(&self) -> u32 { - self.rs1 - } - /// Get the register source 1, or zero if the instruction does not use rs1. pub fn rs1_or_zero(&self) -> u32 { - match self.codes().format { - R | I | S | B => self.rs1, + match InsnFormat::from(self.kind) { + R | I | S | B => self.rs1 as u32, _ => 0, } } - - /// Get the rs2 field, regardless of the instruction format. - pub fn rs2(&self) -> u32 { - self.rs2 - } - /// Get the register source 2, or zero if the instruction does not use rs2. pub fn rs2_or_zero(&self) -> u32 { - match self.codes().format { - R | S | B => self.rs2, + match InsnFormat::from(self.kind) { + R | S | B => self.rs2 as u32, _ => 0, } } - - pub fn immediate(&self) -> u32 { - match self.codes().format { - R => 0, - I => self.imm_i(), - S => self.imm_s(), - B => self.imm_b(), - U => self.imm_u(), - J => self.imm_j(), - } - } - - pub fn codes(&self) -> InsnCodes { - FastDecodeTable::get().lookup(self) - } - - fn imm_b(&self) -> u32 { - (self.top_bit * 0xfffff000) - | ((self.rd & 1) << 11) - | ((self.func7 & 0x3f) << 5) - | (self.rd & 0x1e) - } - - fn imm_i(&self) -> u32 { - (self.top_bit * 0xffff_f000) | (self.func7 << 5) | self.rs2 - } - - fn imm_s(&self) -> u32 { - (self.top_bit * 0xfffff000) | (self.func7 << 5) | self.rd - } - - fn imm_j(&self) -> u32 { - (self.top_bit * 0xfff00000) - | (self.rs1 << 15) - | (self.func3 << 12) - | ((self.rs2 & 1) << 11) - | ((self.func7 & 0x3f) << 5) - | (self.rs2 & 0x1e) - } - - fn imm_u(&self) -> u32 { - self.insn & 0xfffff000 - } -} - -const fn insn( - format: InsnFormat, - kind: InsnKind, - category: InsnCategory, - opcode: u32, - func3: i32, - func7: i32, -) -> InsnCodes { - InsnCodes { - format, - kind, - category, - opcode, - func3: func3 as u32, - func7: func7 as u32, - } -} - -type InstructionTable = [InsnCodes; 47]; -type FastInstructionTable = [u8; 1 << 10]; - -const RV32IM_ISA: InstructionTable = [ - insn(R, INVALID, Invalid, 0x00, 0x0, 0x00), - insn(R, ADD, Compute, 0x33, 0x0, 0x00), - insn(R, SUB, Compute, 0x33, 0x0, 0x20), - insn(R, XOR, Compute, 0x33, 0x4, 0x00), - insn(R, OR, Compute, 0x33, 0x6, 0x00), - insn(R, AND, Compute, 0x33, 0x7, 0x00), - insn(R, SLL, Compute, 0x33, 0x1, 0x00), - insn(R, SRL, Compute, 0x33, 0x5, 0x00), - insn(R, SRA, Compute, 0x33, 0x5, 0x20), - insn(R, SLT, Compute, 0x33, 0x2, 0x00), - insn(R, SLTU, Compute, 0x33, 0x3, 0x00), - insn(I, ADDI, Compute, 0x13, 0x0, -1), - insn(I, XORI, Compute, 0x13, 0x4, -1), - insn(I, ORI, Compute, 0x13, 0x6, -1), - insn(I, ANDI, Compute, 0x13, 0x7, -1), - insn(I, SLLI, Compute, 0x13, 0x1, 0x00), - insn(I, SRLI, Compute, 0x13, 0x5, 0x00), - insn(I, SRAI, Compute, 0x13, 0x5, 0x20), - insn(I, SLTI, Compute, 0x13, 0x2, -1), - insn(I, SLTIU, Compute, 0x13, 0x3, -1), - insn(B, BEQ, Branch, 0x63, 0x0, -1), - insn(B, BNE, Branch, 0x63, 0x1, -1), - insn(B, BLT, Branch, 0x63, 0x4, -1), - insn(B, BGE, Branch, 0x63, 0x5, -1), - insn(B, BLTU, Branch, 0x63, 0x6, -1), - insn(B, BGEU, Branch, 0x63, 0x7, -1), - insn(J, JAL, Compute, 0x6f, -1, -1), - insn(I, JALR, Compute, 0x67, 0x0, -1), - insn(U, LUI, Compute, 0x37, -1, -1), - insn(U, AUIPC, Compute, 0x17, -1, -1), - insn(R, MUL, Compute, 0x33, 0x0, 0x01), - insn(R, MULH, Compute, 0x33, 0x1, 0x01), - insn(R, MULHSU, Compute, 0x33, 0x2, 0x01), - insn(R, MULHU, Compute, 0x33, 0x3, 0x01), - insn(R, DIV, Compute, 0x33, 0x4, 0x01), - insn(R, DIVU, Compute, 0x33, 0x5, 0x01), - insn(R, REM, Compute, 0x33, 0x6, 0x01), - insn(R, REMU, Compute, 0x33, 0x7, 0x01), - insn(I, LB, Load, 0x03, 0x0, -1), - insn(I, LH, Load, 0x03, 0x1, -1), - insn(I, LW, Load, 0x03, 0x2, -1), - insn(I, LBU, Load, 0x03, 0x4, -1), - insn(I, LHU, Load, 0x03, 0x5, -1), - insn(S, SB, Store, 0x23, 0x0, -1), - insn(S, SH, Store, 0x23, 0x1, -1), - insn(S, SW, Store, 0x23, 0x2, -1), - insn(I, EANY, System, 0x73, 0x0, 0x00), -]; - -#[cfg(test)] -#[test] -fn test_isa_table() { - use strum::IntoEnumIterator; - for kind in InsnKind::iter() { - assert_eq!(kind.codes().kind, kind); - } -} - -// RISC-V instruction are determined by 3 parts: -// - Opcode: 7 bits -// - Func3: 3 bits -// - Func7: 7 bits -// In many cases, func7 and/or func3 is ignored. A standard trick is to decode -// via a table, but a 17 bit lookup table destroys L1 cache. Luckily for us, -// in practice the low 2 bits of opcode are always 11, so we can drop them, and -// also func7 is always either 0, 1, 0x20 or don't care, so we can reduce func7 -// to 2 bits, which gets us to 10 bits, which is only 1k. -struct FastDecodeTable { - table: FastInstructionTable, -} - -impl FastDecodeTable { - fn new() -> Self { - let mut table: FastInstructionTable = [0; 1 << 10]; - for (isa_idx, insn) in enumerate(&RV32IM_ISA) { - Self::add_insn(&mut table, insn, isa_idx); - } - Self { table } - } - - fn get() -> &'static Self { - FAST_DECODE_TABLE.get_or_init(Self::new) - } - - // Map to 10 bit format - fn map10(opcode: u32, func3: u32, func7: u32) -> usize { - let op_high = opcode >> 2; - // Map 0 -> 0, 1 -> 1, 0x20 -> 2, everything else to 3 - let func72bits = if func7 <= 1 { - func7 - } else if func7 == 0x20 { - 2 - } else { - 3 - }; - ((op_high << 5) | (func72bits << 3) | func3) as usize - } - - fn add_insn(table: &mut FastInstructionTable, insn: &InsnCodes, isa_idx: usize) { - let op_high = insn.opcode >> 2; - if (insn.func3 as i32) < 0 { - for f3 in 0..8 { - for f7b in 0..4 { - let idx = (op_high << 5) | (f7b << 3) | f3; - table[idx as usize] = isa_idx as u8; - } - } - } else if (insn.func7 as i32) < 0 { - for f7b in 0..4 { - let idx = (op_high << 5) | (f7b << 3) | insn.func3; - table[idx as usize] = isa_idx as u8; - } - } else { - table[Self::map10(insn.opcode, insn.func3, insn.func7)] = isa_idx as u8; - } - } - - fn lookup(&self, decoded: &DecodedInstruction) -> InsnCodes { - let isa_idx = self.table[Self::map10(decoded.opcode, decoded.func3, decoded.func7)]; - RV32IM_ISA[isa_idx as usize] - } } -static FAST_DECODE_TABLE: OnceLock = OnceLock::new(); - impl Emulator { - pub fn new() -> Self { - Self { - table: FastDecodeTable::get(), - } - } - pub fn step(&self, ctx: &mut C) -> Result<()> { let pc = ctx.get_pc(); - // TODO(Matthias): `check_insn_load` should be unnecessary: we can statically - // check in `fn new_from_elf` that the program only has instructions where - // our platform accepts them. - if !ctx.check_insn_load(pc) { + let Some(insn) = ctx.fetch(pc.waddr()) else { ctx.trap(TrapCause::InstructionAccessFault)?; - return Err(anyhow!("Fatal: could not fetch instruction at pc={:?}", pc)); - } - let Some(word) = ctx.fetch(pc.waddr()) else { - ctx.trap(TrapCause::InstructionAccessFault)?; - return Err(anyhow!("Fatal: could not fetch instruction at pc={:?}", pc)); - }; - - // TODO(Matthias): our `Program` that we are fetching from should really store - // already decoded instructions, instead of doing this weird, partial checking - // for `0x03` here. - // - // Note how we can fail here with an IllegalInstruction, and again further down - // when we match against the decoded instruction. We should centralise that. And - // our `step` function here shouldn't need to know anything about how instruction - // are encoded as numbers. - // - // One way to centralise is to do the check once when loading the program from the - // ELF. - if word & 0x03 != 0x03 { - // Opcode must end in 0b11 in RV32IM. - ctx.trap(TrapCause::IllegalInstruction(word))?; return Err(anyhow!( - "Fatal: illegal instruction at pc={:?}: 0x{:08x}", - pc, - word + "Fatal: could not fetch instruction at pc={pc:?}, ELF does not have instructions there." )); - } + }; - let decoded = DecodedInstruction::new(word); - let insn = self.table.lookup(&decoded); - ctx.on_insn_decoded(&decoded); tracing::trace!("pc: {:x}, kind: {:?}", pc.0, insn.kind); - if match insn.category { - InsnCategory::Compute => self.step_compute(ctx, insn.kind, &decoded)?, - InsnCategory::Branch => self.step_branch(ctx, insn.kind, &decoded)?, - InsnCategory::Load => self.step_load(ctx, insn.kind, &decoded)?, - InsnCategory::Store => self.step_store(ctx, insn.kind, &decoded)?, - InsnCategory::System => self.step_system(ctx, insn.kind, &decoded)?, - InsnCategory::Invalid => ctx.trap(TrapCause::IllegalInstruction(word))?, + if match InsnCategory::from(insn.kind) { + InsnCategory::Compute => self.step_compute(ctx, insn.kind, &insn)?, + InsnCategory::Branch => self.step_branch(ctx, insn.kind, &insn)?, + InsnCategory::Load => self.step_load(ctx, insn.kind, &insn)?, + InsnCategory::Store => self.step_store(ctx, insn.kind, &insn)?, + InsnCategory::System => self.step_system(ctx, insn.kind, &insn)?, + InsnCategory::Invalid => ctx.trap(TrapCause::IllegalInstruction(insn.raw))?, } { - ctx.on_normal_end(&decoded); + ctx.on_normal_end(&insn); }; Ok(()) @@ -520,25 +265,22 @@ impl Emulator { &self, ctx: &mut M, kind: InsnKind, - decoded: &DecodedInstruction, + decoded: &Instruction, ) -> Result { use InsnKind::*; let pc = ctx.get_pc(); let mut new_pc = pc + WORD_SIZE; - let imm_i = decoded.imm_i(); + let imm_i = decoded.imm as u32; let out = match kind { // Instructions that do not read rs1 nor rs2. JAL => { - new_pc = pc.wrapping_add(decoded.imm_j()); + new_pc = pc.wrapping_add(decoded.imm as u32); (pc + WORD_SIZE).0 } - LUI => decoded.imm_u(), - AUIPC => (pc.wrapping_add(decoded.imm_u())).0, - _ => { // Instructions that read rs1 but not rs2. - let rs1 = ctx.load_register(decoded.rs1 as usize)?; + let rs1 = ctx.load_register(decoded.rs1)?; match kind { ADDI => rs1.wrapping_add(imm_i), @@ -563,13 +305,13 @@ impl Emulator { } } JALR => { - new_pc = ByteAddr(rs1.wrapping_add(imm_i) & 0xfffffffe); + new_pc = ByteAddr(rs1.wrapping_add(imm_i) & !1); (pc + WORD_SIZE).0 } _ => { // Instructions that use rs1 and rs2. - let rs2 = ctx.load_register(decoded.rs2 as usize)?; + let rs2 = ctx.load_register(decoded.rs2)?; match kind { ADD => rs1.wrapping_add(rs2), @@ -648,7 +390,7 @@ impl Emulator { &self, ctx: &mut M, kind: InsnKind, - decoded: &DecodedInstruction, + decoded: &Instruction, ) -> Result { use InsnKind::*; @@ -667,7 +409,7 @@ impl Emulator { }; let new_pc = if taken { - pc.wrapping_add(decoded.imm_b()) + pc.wrapping_add(decoded.imm as u32) } else { pc + WORD_SIZE }; @@ -683,11 +425,11 @@ impl Emulator { &self, ctx: &mut M, kind: InsnKind, - decoded: &DecodedInstruction, + decoded: &Instruction, ) -> Result { - let rs1 = ctx.load_register(decoded.rs1 as usize)?; + let rs1 = ctx.load_register(decoded.rs1)?; // LOAD instructions do not read rs2. - let addr = ByteAddr(rs1.wrapping_add(decoded.imm_i())); + let addr = ByteAddr(rs1.wrapping_add_signed(decoded.imm)); if !ctx.check_data_load(addr) { return ctx.trap(TrapCause::LoadAccessFault(addr)); } @@ -735,11 +477,11 @@ impl Emulator { &self, ctx: &mut M, kind: InsnKind, - decoded: &DecodedInstruction, + decoded: &Instruction, ) -> Result { - let rs1 = ctx.load_register(decoded.rs1 as usize)?; - let rs2 = ctx.load_register(decoded.rs2 as usize)?; - let addr = ByteAddr(rs1.wrapping_add(decoded.imm_s())); + let rs1 = ctx.load_register(decoded.rs1)?; + let rs2 = ctx.load_register(decoded.rs2)?; + let addr = ByteAddr(rs1.wrapping_add(decoded.imm as u32)); let shift = 8 * (addr.0 & 3); if !ctx.check_data_store(addr) { tracing::error!("mstore: addr={:x?},rs1={:x}", addr, rs1); @@ -777,15 +519,12 @@ impl Emulator { &self, ctx: &mut M, kind: InsnKind, - decoded: &DecodedInstruction, + decoded: &Instruction, ) -> Result { match kind { - InsnKind::EANY => match decoded.rs2 { - 0 => ctx.ecall(), - 1 => ctx.trap(TrapCause::Breakpoint), - _ => ctx.trap(TrapCause::IllegalInstruction(decoded.insn)), - }, - _ => unreachable!(), + InsnKind::ECALL => ctx.ecall(), + InsnKind::EBREAK => ctx.trap(TrapCause::Breakpoint), + _ => ctx.trap(TrapCause::IllegalInstruction(decoded.raw)), } } } diff --git a/ceno_emul/src/rv32im_encode.rs b/ceno_emul/src/rv32im_encode.rs index b6a80f32b..8aeaf2b9c 100644 --- a/ceno_emul/src/rv32im_encode.rs +++ b/ceno_emul/src/rv32im_encode.rs @@ -1,116 +1,29 @@ -use crate::{InsnKind, rv32im::InsnFormat}; +use crate::{InsnKind, Instruction}; -const MASK_4_BITS: u32 = 0xF; -const MASK_5_BITS: u32 = 0x1F; -const MASK_6_BITS: u32 = 0x3F; -const MASK_7_BITS: u32 = 0x7F; -const MASK_8_BITS: u32 = 0xFF; -const MASK_10_BITS: u32 = 0x3FF; -const MASK_12_BITS: u32 = 0xFFF; - -/// Generate bit encoding of a RISC-V instruction. -/// -/// Values `rs1`, `rs2` and `rd1` are 5-bit register indices, and `imm` is of -/// bit length depending on the requirements of the instruction format type. +/// Convenience function to create an `Instruction` with the given fields. /// -/// Fields not required by the instruction's format type are ignored, so one can -/// safely pass an arbitrary value for these, say 0. -pub const fn encode_rv32(kind: InsnKind, rs1: u32, rs2: u32, rd: u32, imm: u32) -> u32 { - match kind.codes().format { - InsnFormat::R => encode_r(kind, rs1, rs2, rd), - InsnFormat::I => encode_i(kind, rs1, rd, imm), - InsnFormat::S => encode_s(kind, rs1, rs2, imm), - InsnFormat::B => encode_b(kind, rs1, rs2, imm), - InsnFormat::U => encode_u(kind, rd, imm), - InsnFormat::J => encode_j(kind, rd, imm), +/// Pass 0 for unused fields. +pub const fn encode_rv32(kind: InsnKind, rs1: u32, rs2: u32, rd: u32, imm: i32) -> Instruction { + Instruction { + kind, + rs1: rs1 as usize, + rs2: rs2 as usize, + rd: rd as usize, + imm, + raw: 0, } } -// R-Type -// 25 20 15 12 7 0 -// +------+-----+-----+--------+----+-------+ -// funct7 | rs2 | rs1 | funct3 | rd | opcode -const fn encode_r(kind: InsnKind, rs1: u32, rs2: u32, rd: u32) -> u32 { - let rs2 = rs2 & MASK_5_BITS; // 5-bits mask - let rs1 = rs1 & MASK_5_BITS; - let rd = rd & MASK_5_BITS; - let func7 = kind.codes().func7; - let func3 = kind.codes().func3; - let opcode = kind.codes().opcode; - func7 << 25 | rs2 << 20 | rs1 << 15 | func3 << 12 | rd << 7 | opcode -} - -// I-Type -// 20 15 12 7 0 -// +---------+-----+--------+----+-------+ -// imm[0:11] | rs1 | funct3 | rd | opcode -const fn encode_i(kind: InsnKind, rs1: u32, rd: u32, imm: u32) -> u32 { - let rs1 = rs1 & MASK_5_BITS; - let rd = rd & MASK_5_BITS; - let func3 = kind.codes().func3; - let opcode = kind.codes().opcode; - // SRLI/SRAI use a specialization of the I-type format with the shift type in imm[10]. - let is_arithmetic_right_shift = (matches!(kind, InsnKind::SRAI) as u32) << 10; - let imm = imm & MASK_12_BITS | is_arithmetic_right_shift; - imm << 20 | rs1 << 15 | func3 << 12 | rd << 7 | opcode -} - -// S-Type -// 25 20 15 12 7 0 -// +---------+-----+-----+--------+----------+-------+ -// imm[5:11] | rs2 | rs1 | funct3 | imm[0:4] | opcode -const fn encode_s(kind: InsnKind, rs1: u32, rs2: u32, imm: u32) -> u32 { - let rs2 = rs2 & MASK_5_BITS; - let rs1 = rs1 & MASK_5_BITS; - let func3 = kind.codes().func3; - let opcode = kind.codes().opcode; - let imm_lo = imm & MASK_5_BITS; - let imm_hi = (imm >> 5) & MASK_7_BITS; // 7-bits mask - imm_hi << 25 | rs2 << 20 | rs1 << 15 | func3 << 12 | imm_lo << 7 | opcode -} - -// B-Type -// 31 25 20 15 12 8 7 0 -// +-------+-----------+-----+-----+--------+----------+---------+-------+ -// imm[12] | imm[5:10] | rs2 | rs1 | funct3 | imm[1:4] | imm[11] | opcode -const fn encode_b(kind: InsnKind, rs1: u32, rs2: u32, imm: u32) -> u32 { - let rs2 = rs2 & MASK_5_BITS; - let rs1 = rs1 & MASK_5_BITS; - let func3 = kind.codes().func3; - let opcode = kind.codes().opcode; - let imm_1_4 = (imm >> 1) & MASK_4_BITS; // skip imm[0] - let imm_5_10 = (imm >> 5) & MASK_6_BITS; - ((imm >> 12) & 1) << 31 - | imm_5_10 << 25 - | rs2 << 20 - | rs1 << 15 - | func3 << 12 - | imm_1_4 << 8 - | ((imm >> 11) & 1) << 7 - | opcode -} - -// J-Type -// 31 21 20 12 7 0 -// +-------+-----------+---------+------------+----+-------+ -// imm[20] | imm[1:10] | imm[11] | imm[12:19] | rd | opcode -const fn encode_j(kind: InsnKind, rd: u32, imm: u32) -> u32 { - let rd = rd & MASK_5_BITS; - let opcode = kind.codes().opcode; - let imm_1_10 = (imm >> 1) & MASK_10_BITS; // skip imm[0] - let imm_12_19 = (imm >> 12) & MASK_8_BITS; - ((imm >> 20) & 1) << 31 - | imm_1_10 << 21 - | ((imm >> 11) & 1) << 20 - | imm_12_19 << 12 - | rd << 7 - | opcode -} - -// U-Type -// 12 7 0 -// +----------+----+--------+ -// imm[12:31] | rd | opcode -const fn encode_u(kind: InsnKind, rd: u32, imm: u32) -> u32 { - (imm >> 12) << 12 | (rd & MASK_5_BITS) << 7 | kind.codes().opcode +/// Convenience function to create an `Instruction` with the given fields. +/// +/// Pass 0 for unused fields. +pub const fn encode_rv32u(kind: InsnKind, rs1: u32, rs2: u32, rd: u32, imm: u32) -> Instruction { + Instruction { + kind, + rs1: rs1 as usize, + rs2: rs2 as usize, + rd: rd as usize, + imm: imm as i32, + raw: 0, + } } diff --git a/ceno_emul/src/tracer.rs b/ceno_emul/src/tracer.rs index a1df363e2..01ad33e01 100644 --- a/ceno_emul/src/tracer.rs +++ b/ceno_emul/src/tracer.rs @@ -1,10 +1,9 @@ use std::{collections::HashMap, fmt, mem}; use crate::{ - CENO_PLATFORM, InsnKind, PC_STEP_SIZE, Platform, + CENO_PLATFORM, InsnKind, Instruction, PC_STEP_SIZE, Platform, addr::{ByteAddr, Cycle, RegIdx, Word, WordAddr}, encode_rv32, - rv32im::DecodedInstruction, }; /// An instruction and its context in an execution trace. That is concrete values of registers and memory. @@ -22,7 +21,7 @@ use crate::{ pub struct StepRecord { cycle: Cycle, pc: Change, - insn_code: Word, + pub insn: Instruction, rs1: Option, rs2: Option, @@ -57,7 +56,7 @@ impl StepRecord { pub fn new_r_instruction( cycle: Cycle, pc: ByteAddr, - insn_code: u32, + insn_code: Instruction, rs1_read: Word, rs2_read: Word, rd: Change, @@ -79,7 +78,7 @@ impl StepRecord { pub fn new_b_instruction( cycle: Cycle, pc: Change, - insn_code: u32, + insn_code: Instruction, rs1_read: Word, rs2_read: Word, prev_cycle: Cycle, @@ -99,7 +98,7 @@ impl StepRecord { pub fn new_i_instruction( cycle: Cycle, pc: Change, - insn_code: u32, + insn_code: Instruction, rs1_read: Word, rd: Change, prev_cycle: Cycle, @@ -119,7 +118,7 @@ impl StepRecord { pub fn new_im_instruction( cycle: Cycle, pc: ByteAddr, - insn_code: u32, + insn_code: Instruction, rs1_read: Word, rd: Change, mem_op: ReadOp, @@ -148,7 +147,7 @@ impl StepRecord { pub fn new_u_instruction( cycle: Cycle, pc: ByteAddr, - insn_code: u32, + insn_code: Instruction, rd: Change, prev_cycle: Cycle, ) -> StepRecord { @@ -159,7 +158,7 @@ impl StepRecord { pub fn new_j_instruction( cycle: Cycle, pc: Change, - insn_code: u32, + insn_code: Instruction, rd: Change, prev_cycle: Cycle, ) -> StepRecord { @@ -169,7 +168,7 @@ impl StepRecord { pub fn new_s_instruction( cycle: Cycle, pc: ByteAddr, - insn_code: u32, + insn_code: Instruction, rs1_read: Word, rs2_read: Word, memory_op: WriteOp, @@ -194,7 +193,7 @@ impl StepRecord { Self::new_insn( cycle, Change::new(pc, pc + PC_STEP_SIZE), - encode_rv32(InsnKind::EANY, 0, 0, 0, 0), + encode_rv32(InsnKind::ECALL, 0, 0, 0, 0), Some(value), Some(value), Some(Change::new(value, value)), @@ -214,25 +213,23 @@ impl StepRecord { fn new_insn( cycle: Cycle, pc: Change, - insn_code: u32, + insn: Instruction, rs1_read: Option, rs2_read: Option, rd: Option>, memory_op: Option, previous_cycle: Cycle, ) -> StepRecord { - let insn = DecodedInstruction::new(insn_code); StepRecord { cycle, pc, - insn_code, rs1: rs1_read.map(|rs1| ReadOp { - addr: Platform::register_vma(insn.rs1() as RegIdx).into(), + addr: Platform::register_vma(insn.rs1).into(), value: rs1, previous_cycle, }), rs2: rs2_read.map(|rs2| ReadOp { - addr: Platform::register_vma(insn.rs2() as RegIdx).into(), + addr: Platform::register_vma(insn.rs2).into(), value: rs2, previous_cycle, }), @@ -241,6 +238,7 @@ impl StepRecord { value: rd, previous_cycle, }), + insn, memory_op, } } @@ -253,14 +251,9 @@ impl StepRecord { self.pc } - /// The instruction as a raw code. - pub fn insn_code(&self) -> Word { - self.insn_code - } - /// The instruction as a decoded structure. - pub fn insn(&self) -> DecodedInstruction { - DecodedInstruction::new(self.insn_code) + pub fn insn(&self) -> Instruction { + self.insn } pub fn rs1(&self) -> Option { @@ -327,12 +320,9 @@ impl Tracer { self.record.pc.after = pc; } - // TODO(Matthias): this should perhaps record `DecodedInstruction`s instead - // of raw codes, or perhaps only the pc, because we can always look up the - // instruction in the program. - pub fn fetch(&mut self, pc: WordAddr, value: Word) { + pub fn fetch(&mut self, pc: WordAddr, value: Instruction) { self.record.pc.before = pc.baddr(); - self.record.insn_code = value; + self.record.insn = value; } pub fn load_register(&mut self, idx: RegIdx, value: Word) { diff --git a/ceno_emul/src/vm_state.rs b/ceno_emul/src/vm_state.rs index 8f1746d79..23224c742 100644 --- a/ceno_emul/src/vm_state.rs +++ b/ceno_emul/src/vm_state.rs @@ -5,7 +5,7 @@ use crate::{ PC_STEP_SIZE, Program, WORD_SIZE, addr::{ByteAddr, RegIdx, Word, WordAddr}, platform::Platform, - rv32im::{DecodedInstruction, Emulator, TrapCause}, + rv32im::{Emulator, Instruction, TrapCause}, tracer::{Change, StepRecord, Tracer}, }; use anyhow::{Result, anyhow}; @@ -78,7 +78,7 @@ impl VMState { } pub fn iter_until_halt(&mut self) -> impl Iterator> + '_ { - let emu = Emulator::new(); + let emu = Emulator::default(); from_fn(move || { if self.halted() { None @@ -122,7 +122,7 @@ impl EmuContext for VMState { // Treat unknown ecalls as all powerful instructions: // Read two registers, write one register, write one memory word, and branch. tracing::warn!("ecall ignored: syscall_id={}", function); - self.store_register(DecodedInstruction::RD_NULL as RegIdx, 0)?; + self.store_register(Instruction::RD_NULL as RegIdx, 0)?; // Example ecall effect - any writable address will do. let addr = (self.platform.stack_top - WORD_SIZE as u32).into(); self.store_memory(addr, self.peek_memory(addr))?; @@ -137,7 +137,7 @@ impl EmuContext for VMState { Err(anyhow!("Trap {:?}", cause)) // Crash. } - fn on_normal_end(&mut self, _decoded: &DecodedInstruction) { + fn on_normal_end(&mut self, _decoded: &Instruction) { self.tracer.store_pc(ByteAddr(self.pc)); } @@ -190,8 +190,7 @@ impl EmuContext for VMState { *self.memory.get(&addr).unwrap_or(&0) } - // TODO(Matthias): this should really return `Result` - fn fetch(&mut self, pc: WordAddr) -> Option { + fn fetch(&mut self, pc: WordAddr) -> Option { let byte_pc: ByteAddr = pc.into(); let relative_pc = byte_pc.0.wrapping_sub(self.program.base_address); let idx = (relative_pc / WORD_SIZE as u32) as usize; @@ -207,8 +206,4 @@ impl EmuContext for VMState { fn check_data_store(&self, addr: ByteAddr) -> bool { self.platform.can_write(addr.0) } - - fn check_insn_load(&self, addr: ByteAddr) -> bool { - self.platform.can_execute(addr.0) - } } diff --git a/ceno_emul/tests/test_elf.rs b/ceno_emul/tests/test_elf.rs index ca7a14c1c..7448d4508 100644 --- a/ceno_emul/tests/test_elf.rs +++ b/ceno_emul/tests/test_elf.rs @@ -15,7 +15,7 @@ fn test_ceno_rt_panic() -> Result<()> { let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; let steps = run(&mut state)?; let last = steps.last().unwrap(); - assert_eq!(last.insn().codes().kind, InsnKind::EANY); + assert_eq!(last.insn().kind, InsnKind::ECALL); assert_eq!(last.rs1().unwrap().value, Platform::ecall_halt()); assert_eq!(last.rs2().unwrap().value, 1); // panic / halt(1) Ok(()) diff --git a/ceno_emul/tests/test_vm_trace.rs b/ceno_emul/tests/test_vm_trace.rs index d931a6a9c..d0cdfbf3d 100644 --- a/ceno_emul/tests/test_vm_trace.rs +++ b/ceno_emul/tests/test_vm_trace.rs @@ -3,8 +3,8 @@ use anyhow::Result; use std::collections::{BTreeMap, HashMap}; use ceno_emul::{ - CENO_PLATFORM, Cycle, EmuContext, InsnKind, Platform, Program, StepRecord, Tracer, VMState, - WORD_SIZE, WordAddr, + CENO_PLATFORM, Cycle, EmuContext, InsnKind, Instruction, Platform, Program, StepRecord, Tracer, + VMState, WordAddr, encode_rv32, }; #[test] @@ -12,17 +12,8 @@ fn test_vm_trace() -> Result<()> { let program = Program::new( CENO_PLATFORM.pc_base(), CENO_PLATFORM.pc_base(), - PROGRAM_FIBONACCI_20.to_vec(), - PROGRAM_FIBONACCI_20 - .iter() - .enumerate() - .map(|(insn_idx, &insn)| { - ( - CENO_PLATFORM.pc_base() + (WORD_SIZE * insn_idx) as u32, - insn, - ) - }) - .collect(), + program_fibonacci_20(), + Default::default(), ); let mut ctx = VMState::new(CENO_PLATFORM, program); @@ -33,7 +24,7 @@ fn test_vm_trace() -> Result<()> { assert_eq!(ctx.peek_register(2), x2); assert_eq!(ctx.peek_register(3), x3); - let ops: Vec = steps.iter().map(|step| step.insn().codes().kind).collect(); + let ops: Vec = steps.iter().map(|step| step.insn().kind).collect(); assert_eq!(ops, expected_ops_fibonacci_20()); assert_eq!( @@ -63,27 +54,25 @@ fn run(state: &mut VMState) -> Result> { } /// Example in RISC-V bytecode and assembly. -const PROGRAM_FIBONACCI_20: [u32; 7] = [ - // x1 = 10; - // x3 = 1; - // immediate rs1 f3 rd opcode - 0b_000000001010_00000_000_00001_0010011, // addi x1, x0, 10 - 0b_000000000001_00000_000_00011_0010011, // addi x3, x0, 1 - // loop { - // x1 -= 1; - // immediate rs1 f3 rd opcode - 0b_111111111111_00001_000_00001_0010011, // addi x1, x1, -1 - // x2 += x3; - // x3 += x2; - // zeros rs2 rs1 f3 rd opcode - 0b_0000000_00011_00010_000_00010_0110011, // add x2, x2, x3 - 0b_0000000_00011_00010_000_00011_0110011, // add x3, x2, x3 - // if x1 == 0 { break } - // imm rs2 rs1 f3 imm opcode - 0b_1_111111_00000_00001_001_1010_1_1100011, // bne x1, x0, -12 - // ecall HALT, SUCCESS - 0b_000000000000_00000_000_00000_1110011, -]; +pub fn program_fibonacci_20() -> Vec { + vec![ + // x1 = 10; + // x3 = 1; + encode_rv32(InsnKind::ADDI, 0, 0, 1, 10), + encode_rv32(InsnKind::ADDI, 0, 0, 3, 1), + // loop { + // x1 -= 1; + encode_rv32(InsnKind::ADDI, 1, 0, 1, -1), + // x2 += x3; + // x3 += x2; + encode_rv32(InsnKind::ADD, 2, 3, 2, 0), + encode_rv32(InsnKind::ADD, 2, 3, 3, 0), + // if x1 == 0 { break } + encode_rv32(InsnKind::BNE, 1, 0, 0, -12), + // ecall HALT, SUCCESS + encode_rv32(InsnKind::ECALL, 0, 0, 0, 0), + ] +} /// Rust version of the example. Reconstruct the output. fn expected_fibonacci_20() -> (u32, u32, u32) { @@ -112,7 +101,7 @@ fn expected_ops_fibonacci_20() -> Vec { for _ in 0..10 { ops.extend(&[ADDI, ADD, ADD, BNE]); } - ops.push(EANY); + ops.push(ECALL); ops } diff --git a/ceno_zkvm/examples/riscv_opcodes.rs b/ceno_zkvm/examples/riscv_opcodes.rs index 99b86173f..eafbdd955 100644 --- a/ceno_zkvm/examples/riscv_opcodes.rs +++ b/ceno_zkvm/examples/riscv_opcodes.rs @@ -1,7 +1,6 @@ use std::{panic, time::Instant}; use ceno_zkvm::{ - declare_program, instructions::riscv::{MemPadder, MmuConfig, Rv32imConfig, constants::EXIT_PC}, scheme::{mock_prover::MockProver, prover::ZKVMProver}, state::GlobalState, @@ -13,8 +12,9 @@ use clap::Parser; use ceno_emul::{ CENO_PLATFORM, EmuContext, - InsnKind::{ADD, BLTU, EANY, LUI, LW}, - PC_WORD_SIZE, Platform, Program, StepRecord, Tracer, VMState, Word, WordAddr, encode_rv32, + InsnKind::{ADD, ADDI, BLTU, ECALL, LW}, + Instruction, Platform, Program, StepRecord, Tracer, VMState, Word, WordAddr, encode_rv32, + encode_rv32u, }; use ceno_zkvm::{ scheme::{PublicValues, constants::MAX_NUM_VARIABLES, verifier::ZKVMVerifier}, @@ -28,33 +28,27 @@ use mpcs::{Basefold, BasefoldRSParams, PolynomialCommitmentScheme}; use sumcheck::macros::{entered_span, exit_span}; use tracing_subscriber::{EnvFilter, Registry, fmt, fmt::format::FmtSpan, layer::SubscriberExt}; use transcript::Transcript; -const PROGRAM_SIZE: usize = 16; + // For now, we assume registers // - x0 is not touched, // - x1 is initialized to 1, // - x2 is initialized to -1, // - x3 is initialized to loop bound. // we use x4 to hold the acc_sum. -#[allow(clippy::unusual_byte_groupings)] -const ECALL_HALT: u32 = 0b_000000000000_00000_000_00000_1110011; -#[allow(clippy::unusual_byte_groupings)] -const PROGRAM_CODE: [u32; PROGRAM_SIZE] = { - let mut program: [u32; PROGRAM_SIZE] = [ECALL_HALT; PROGRAM_SIZE]; - declare_program!( - program, - encode_rv32(LUI, 0, 0, 10, CENO_PLATFORM.public_io.start), // lui x10, public_io - encode_rv32(LW, 10, 0, 1, 0), // lw x1, 0(x10) - encode_rv32(LW, 10, 0, 2, 4), // lw x2, 4(x10) - encode_rv32(LW, 10, 0, 3, 8), // lw x3, 8(x10) +fn program_code() -> Vec { + vec![ + encode_rv32u(ADDI, 0, 0, 10, CENO_PLATFORM.public_io.start), // lui x10, public_io + encode_rv32(LW, 10, 0, 1, 0), // lw x1, 0(x10) + encode_rv32(LW, 10, 0, 2, 4), // lw x2, 4(x10) + encode_rv32(LW, 10, 0, 3, 8), // lw x3, 8(x10) // Main loop. - encode_rv32(ADD, 1, 4, 4, 0), // add x4, x1, x4 - encode_rv32(ADD, 2, 3, 3, 0), // add x3, x2, x3 - encode_rv32(BLTU, 0, 3, 0, -8_i32 as u32), // bltu x0, x3, -8 + encode_rv32(ADD, 1, 4, 4, 0), // add x4, x1, x4 + encode_rv32(ADD, 2, 3, 3, 0), // add x3, x2, x3 + encode_rv32(BLTU, 0, 3, 0, -8_i32), // bltu x0, x3, -8 // End. - ECALL_HALT, // ecall halt - ); - program -}; + encode_rv32(ECALL, 0, 0, 0, 0), // ecall (halt) + ] +} type ExampleProgramTableCircuit = ProgramTableCircuit; /// Simple program to greet a person @@ -74,21 +68,14 @@ fn main() { let args = Args::parse(); type E = GoldilocksExt2; type Pcs = Basefold; + let program_code = program_code(); + let program_size = program_code.len(); let program = Program::new( CENO_PLATFORM.pc_base(), CENO_PLATFORM.pc_base(), - PROGRAM_CODE.to_vec(), - PROGRAM_CODE - .iter() - .enumerate() - .map(|(insn_idx, &insn)| { - ( - (insn_idx * PC_WORD_SIZE) as u32 + CENO_PLATFORM.pc_base(), - insn, - ) - }) - .collect(), + program_code, + Default::default(), ); let mem_addresses = CENO_PLATFORM.ram.clone(); let io_addresses = CENO_PLATFORM.public_io.clone(); @@ -117,7 +104,7 @@ fn main() { let pcs_param = Pcs::setup(1 << MAX_NUM_VARIABLES).expect("Basefold PCS setup"); let (pp, vp) = Pcs::trim(pcs_param, 1 << MAX_NUM_VARIABLES).expect("Basefold trim"); let program_params = ProgramParams { - program_size: PROGRAM_SIZE, + program_size, ..Default::default() }; let mut zkvm_cs = ZKVMConstraintSystem::new_with_platform(program_params); @@ -193,8 +180,7 @@ fn main() { .iter() .rev() .find(|record| { - record.insn().codes().kind == EANY - && record.rs1().unwrap().value == Platform::ecall_halt() + record.insn().kind == ECALL && record.rs1().unwrap().value == Platform::ecall_halt() }) .expect("halt record not found"); diff --git a/ceno_zkvm/src/e2e.rs b/ceno_zkvm/src/e2e.rs index c8bfbe811..f72cd9fe7 100644 --- a/ceno_zkvm/src/e2e.rs +++ b/ceno_zkvm/src/e2e.rs @@ -9,8 +9,8 @@ use crate::{ tables::{MemFinalRecord, MemInitRecord, ProgramTableCircuit}, }; use ceno_emul::{ - ByteAddr, EmuContext, InsnKind::EANY, IterAddresses, Platform, Program, StepRecord, Tracer, - VMState, WORD_SIZE, WordAddr, + ByteAddr, EmuContext, InsnKind, IterAddresses, Platform, Program, StepRecord, Tracer, VMState, + WORD_SIZE, WordAddr, }; use ff_ext::ExtensionField; use itertools::{Itertools, MinMaxResult, chain, enumerate}; @@ -132,7 +132,7 @@ pub fn run_e2e_gen_witness let cycle_num = all_records.len(); tracing::info!("Proving {} execution steps", cycle_num); for (i, step) in enumerate(&all_records).rev().take(5).rev() { - tracing::trace!("Step {i}: {:?} - {:?}\n", step.insn().codes().kind, step); + tracing::trace!("Step {i}: {:?} - {:?}\n", step.insn().kind, step); } // Find the exit code from the HALT step, if halting at all. @@ -140,7 +140,7 @@ pub fn run_e2e_gen_witness .iter() .rev() .find(|record| { - record.insn().codes().kind == EANY + record.insn().kind == InsnKind::ECALL && record.rs1().unwrap().value == Platform::ecall_halt() }) .and_then(|halt_record| halt_record.rs2()) @@ -324,9 +324,8 @@ fn format_segments( fn format_segment(platform: &Platform, addr: u32) -> String { format!( - "{}{}{}", + "{}{}", if platform.can_read(addr) { "R" } else { "-" }, if platform.can_write(addr) { "W" } else { "-" }, - if platform.can_execute(addr) { "X" } else { "-" }, ) } diff --git a/ceno_zkvm/src/instructions/riscv.rs b/ceno_zkvm/src/instructions/riscv.rs index 0c8272846..45899e1ef 100644 --- a/ceno_zkvm/src/instructions/riscv.rs +++ b/ceno_zkvm/src/instructions/riscv.rs @@ -28,7 +28,6 @@ mod i_insn; mod insn_base; mod j_insn; mod r_insn; -mod u_insn; mod ecall_insn; @@ -37,15 +36,13 @@ mod memory; mod s_insn; #[cfg(test)] mod test; -#[cfg(test)] -mod test_utils; pub trait RIVInstruction { const INST_KIND: InsnKind; } pub use arith::{AddInstruction, SubInstruction}; -pub use jump::{AuipcInstruction, JalInstruction, JalrInstruction, LuiInstruction}; +pub use jump::{JalInstruction, JalrInstruction}; pub use memory::{ LbInstruction, LbuInstruction, LhInstruction, LhuInstruction, LwInstruction, SbInstruction, ShInstruction, SwInstruction, diff --git a/ceno_zkvm/src/instructions/riscv/arith_imm.rs b/ceno_zkvm/src/instructions/riscv/arith_imm.rs index 2508000f2..7c36618a5 100644 --- a/ceno_zkvm/src/instructions/riscv/arith_imm.rs +++ b/ceno_zkvm/src/instructions/riscv/arith_imm.rs @@ -89,7 +89,7 @@ mod test { use crate::{ circuit_builder::{CircuitBuilder, ConstraintSystem}, - instructions::{Instruction, riscv::test_utils::imm_i}, + instructions::Instruction, scheme::mock_prover::{MOCK_PC_START, MockProver}, }; @@ -110,7 +110,7 @@ mod test { .unwrap() .unwrap(); - let insn_code = encode_rv32(InsnKind::ADDI, 2, 0, 4, imm_i(3)); + let insn_code = encode_rv32(InsnKind::ADDI, 2, 0, 4, 3); let (raw_witin, lkm) = AddiInstruction::::assign_instances( &config, cb.cs.num_witin as usize, @@ -143,7 +143,8 @@ mod test { .unwrap() .unwrap(); - let insn_code = encode_rv32(InsnKind::ADDI, 2, 0, 4, imm_i(-3)); + let insn_code = encode_rv32(InsnKind::ADDI, 2, 0, 4, -3); + let (raw_witin, lkm) = AddiInstruction::::assign_instances( &config, cb.cs.num_witin as usize, diff --git a/ceno_zkvm/src/instructions/riscv/branch/test.rs b/ceno_zkvm/src/instructions/riscv/branch/test.rs index 9847c66d5..5b53ec904 100644 --- a/ceno_zkvm/src/instructions/riscv/branch/test.rs +++ b/ceno_zkvm/src/instructions/riscv/branch/test.rs @@ -5,7 +5,7 @@ use super::*; use crate::{ circuit_builder::{CircuitBuilder, ConstraintSystem}, error::ZKVMError, - instructions::{Instruction, riscv::test_utils::imm_b}, + instructions::Instruction, scheme::mock_prover::{MOCK_PC_START, MockProver}, }; @@ -32,7 +32,7 @@ fn impl_opcode_beq(equal: bool) { .unwrap() .unwrap(); - let insn_code = encode_rv32(InsnKind::BEQ, 2, 3, 0, imm_b(8)); + let insn_code = encode_rv32(InsnKind::BEQ, 2, 3, 0, 8); let pc_offset = if equal { 8 } else { PC_STEP_SIZE }; let (raw_witin, lkm) = BeqInstruction::assign_instances(&config, cb.cs.num_witin as usize, vec![ @@ -70,7 +70,7 @@ fn impl_opcode_bne(equal: bool) { .unwrap() .unwrap(); - let insn_code = encode_rv32(InsnKind::BNE, 2, 3, 0, imm_b(8)); + let insn_code = encode_rv32(InsnKind::BNE, 2, 3, 0, 8); let pc_offset = if equal { PC_STEP_SIZE } else { 8 }; let (raw_witin, lkm) = BneInstruction::assign_instances(&config, cb.cs.num_witin as usize, vec![ @@ -111,8 +111,8 @@ fn impl_bltu_circuit(taken: bool, a: u32, b: u32) -> Result<(), ZKVMError> { MOCK_PC_START + PC_STEP_SIZE }; - let insn_code = encode_rv32(InsnKind::BLTU, 2, 3, 0, imm_b(-8)); - println!("{:#b}", insn_code); + let insn_code = encode_rv32(InsnKind::BLTU, 2, 3, 0, -8); + println!("{:?}", insn_code); let (raw_witin, lkm) = BltuInstruction::assign_instances(&config, circuit_builder.cs.num_witin as usize, vec![ StepRecord::new_b_instruction( @@ -153,7 +153,7 @@ fn impl_bgeu_circuit(taken: bool, a: u32, b: u32) -> Result<(), ZKVMError> { MOCK_PC_START + PC_STEP_SIZE }; - let insn_code = encode_rv32(InsnKind::BGEU, 2, 3, 0, imm_b(-8)); + let insn_code = encode_rv32(InsnKind::BGEU, 2, 3, 0, -8); let (raw_witin, lkm) = BgeuInstruction::assign_instances(&config, circuit_builder.cs.num_witin as usize, vec![ StepRecord::new_b_instruction( @@ -195,7 +195,7 @@ fn impl_blt_circuit(taken: bool, a: i32, b: i32) -> Result<(), ZKVMError> { MOCK_PC_START + PC_STEP_SIZE }; - let insn_code = encode_rv32(InsnKind::BLT, 2, 3, 0, imm_b(-8)); + let insn_code = encode_rv32(InsnKind::BLT, 2, 3, 0, -8); let (raw_witin, lkm) = BltInstruction::assign_instances(&config, circuit_builder.cs.num_witin as usize, vec![ StepRecord::new_b_instruction( @@ -237,7 +237,7 @@ fn impl_bge_circuit(taken: bool, a: i32, b: i32) -> Result<(), ZKVMError> { MOCK_PC_START + PC_STEP_SIZE }; - let insn_code = encode_rv32(InsnKind::BGE, 2, 3, 0, imm_b(-8)); + let insn_code = encode_rv32(InsnKind::BGE, 2, 3, 0, -8); let (raw_witin, lkm) = BgeInstruction::assign_instances(&config, circuit_builder.cs.num_witin as usize, vec![ StepRecord::new_b_instruction( diff --git a/ceno_zkvm/src/instructions/riscv/dummy/dummy_circuit.rs b/ceno_zkvm/src/instructions/riscv/dummy/dummy_circuit.rs index bb85ad7c1..95e451002 100644 --- a/ceno_zkvm/src/instructions/riscv/dummy/dummy_circuit.rs +++ b/ceno_zkvm/src/instructions/riscv/dummy/dummy_circuit.rs @@ -34,13 +34,15 @@ impl Instruction for DummyInstruction, ) -> Result { - let codes = I::INST_KIND.codes(); + let kind = I::INST_KIND; + let format = InsnFormat::from(kind); + let category = InsnCategory::from(kind); // ECALL can do everything. - let is_ecall = matches!(codes.kind, InsnKind::EANY); + let is_ecall = matches!(kind, InsnKind::ECALL); // Regular instructions do what is implied by their format. - let (with_rs1, with_rs2, with_rd) = match codes.format { + let (with_rs1, with_rs2, with_rd) = match format { _ if is_ecall => (true, true, true), InsnFormat::R => (true, true, true), InsnFormat::I => (true, false, true), @@ -49,10 +51,10 @@ impl Instruction for DummyInstruction (false, false, true), InsnFormat::J => (false, false, true), }; - let with_mem_write = matches!(codes.category, InsnCategory::Store) || is_ecall; - let with_mem_read = matches!(codes.category, InsnCategory::Load); - let branching = matches!(codes.category, InsnCategory::Branch) - || matches!(codes.kind, InsnKind::JAL | InsnKind::JALR) + let with_mem_write = matches!(category, InsnCategory::Store) || is_ecall; + let with_mem_read = matches!(category, InsnCategory::Load); + let branching = matches!(category, InsnCategory::Branch) + || matches!(kind, InsnKind::JAL | InsnKind::JALR) || is_ecall; DummyConfig::construct_circuit( @@ -175,7 +177,7 @@ impl DummyConfig { // Fetch instruction // The register IDs of ECALL is fixed, not encoded. - let is_ecall = matches!(kind, InsnKind::EANY); + let is_ecall = matches!(kind, InsnKind::ECALL); let rs1_id = match &rs1 { Some((r, _)) if !is_ecall => r.id.expr(), _ => 0.into(), diff --git a/ceno_zkvm/src/instructions/riscv/dummy/test.rs b/ceno_zkvm/src/instructions/riscv/dummy/test.rs index df1eb0572..5f3a03bb9 100644 --- a/ceno_zkvm/src/instructions/riscv/dummy/test.rs +++ b/ceno_zkvm/src/instructions/riscv/dummy/test.rs @@ -30,7 +30,7 @@ fn test_dummy_ecall() { .unwrap(); let step = StepRecord::new_ecall_any(4, MOCK_PC_START); - let insn_code = step.insn_code(); + let insn_code = step.insn(); let (raw_witin, lkm) = EcallDummy::assign_instances(&config, cb.cs.num_witin as usize, vec![step]).unwrap(); diff --git a/ceno_zkvm/src/instructions/riscv/ecall.rs b/ceno_zkvm/src/instructions/riscv/ecall.rs index 0d7a3315a..261772638 100644 --- a/ceno_zkvm/src/instructions/riscv/ecall.rs +++ b/ceno_zkvm/src/instructions/riscv/ecall.rs @@ -7,7 +7,7 @@ use super::{RIVInstruction, dummy::DummyInstruction}; pub struct EcallOp; impl RIVInstruction for EcallOp { - const INST_KIND: InsnKind = InsnKind::EANY; + const INST_KIND: InsnKind = InsnKind::ECALL; } /// Unsafe. A dummy ecall circuit that ignores unimplemented functions. pub type EcallDummy = DummyInstruction; diff --git a/ceno_zkvm/src/instructions/riscv/ecall_insn.rs b/ceno_zkvm/src/instructions/riscv/ecall_insn.rs index 3bd2faa1e..af5ff9f8d 100644 --- a/ceno_zkvm/src/instructions/riscv/ecall_insn.rs +++ b/ceno_zkvm/src/instructions/riscv/ecall_insn.rs @@ -10,7 +10,7 @@ use crate::{ tables::InsnRecord, witness::LkMultiplicity, }; -use ceno_emul::{InsnKind::EANY, PC_STEP_SIZE, Platform, StepRecord, Tracer}; +use ceno_emul::{InsnKind::ECALL, PC_STEP_SIZE, Platform, StepRecord, Tracer}; use ff_ext::ExtensionField; use std::mem::MaybeUninit; @@ -39,7 +39,7 @@ impl EcallInstructionConfig { cb.lk_fetch(&InsnRecord::new( pc.expr(), - EANY.into(), + ECALL.into(), None, 0.into(), 0.into(), diff --git a/ceno_zkvm/src/instructions/riscv/jump.rs b/ceno_zkvm/src/instructions/riscv/jump.rs index 50708d07a..b57aadbbb 100644 --- a/ceno_zkvm/src/instructions/riscv/jump.rs +++ b/ceno_zkvm/src/instructions/riscv/jump.rs @@ -1,12 +1,8 @@ -mod auipc; mod jal; mod jalr; -mod lui; -pub use auipc::AuipcInstruction; pub use jal::JalInstruction; pub use jalr::JalrInstruction; -pub use lui::LuiInstruction; #[cfg(test)] mod test; diff --git a/ceno_zkvm/src/instructions/riscv/jump/auipc.rs b/ceno_zkvm/src/instructions/riscv/jump/auipc.rs deleted file mode 100644 index 28be09c66..000000000 --- a/ceno_zkvm/src/instructions/riscv/jump/auipc.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::{marker::PhantomData, mem::MaybeUninit}; - -use ceno_emul::InsnKind; -use ff_ext::ExtensionField; - -use crate::{ - Value, - circuit_builder::CircuitBuilder, - error::ZKVMError, - expression::{ToExpr, WitIn}, - instructions::{ - Instruction, - riscv::{constants::UInt, u_insn::UInstructionConfig}, - }, - set_val, - tables::InsnRecord, - utils::i64_to_base, - witness::LkMultiplicity, -}; - -pub struct AuipcConfig { - pub u_insn: UInstructionConfig, - pub imm: WitIn, - pub overflow_bit: WitIn, - pub rd_written: UInt, -} - -pub struct AuipcInstruction(PhantomData); - -/// AUIPC instruction circuit -impl Instruction for AuipcInstruction { - type InstructionConfig = AuipcConfig; - - fn name() -> String { - format!("{:?}", InsnKind::AUIPC) - } - - fn construct_circuit( - circuit_builder: &mut CircuitBuilder, - ) -> Result, ZKVMError> { - let imm = circuit_builder.create_witin(|| "imm"); - let rd_written = UInt::new(|| "rd_written", circuit_builder)?; - - let u_insn = UInstructionConfig::construct_circuit( - circuit_builder, - InsnKind::AUIPC, - &imm.expr(), - rd_written.register_expr(), - )?; - - let overflow_bit = circuit_builder.create_witin(|| "overflow_bit"); - circuit_builder.assert_bit(|| "is_bit", overflow_bit.expr())?; - - // assert: imm + pc = rd_written + overflow_bit * 2^32 - // valid formulation of mod 2^32 arithmetic because: - // - imm and pc are constrained to 4 bytes by instruction table lookup - // - rd_written is constrained to 4 bytes by UInt checked limbs - circuit_builder.require_equal( - || "imm+pc = rd_written+2^32*overflow", - imm.expr() + u_insn.vm_state.pc.expr(), - rd_written.value() + overflow_bit.expr() * (1u64 << 32), - )?; - - Ok(AuipcConfig { - u_insn, - imm, - overflow_bit, - rd_written, - }) - } - - fn assign_instance( - config: &Self::InstructionConfig, - instance: &mut [MaybeUninit], - lk_multiplicity: &mut LkMultiplicity, - step: &ceno_emul::StepRecord, - ) -> Result<(), ZKVMError> { - let pc: u32 = step.pc().before.0; - let imm = InsnRecord::imm_internal(&step.insn()); - let (sum, overflow) = pc.overflowing_add(imm as u32); - - set_val!(instance, config.imm, i64_to_base::(imm)); - set_val!(instance, config.overflow_bit, overflow as u64); - - let sum_limbs = Value::new(sum, lk_multiplicity); - config.rd_written.assign_value(instance, sum_limbs); - - config - .u_insn - .assign_instance(instance, lk_multiplicity, step)?; - - Ok(()) - } -} diff --git a/ceno_zkvm/src/instructions/riscv/jump/lui.rs b/ceno_zkvm/src/instructions/riscv/jump/lui.rs deleted file mode 100644 index 1ac67f64f..000000000 --- a/ceno_zkvm/src/instructions/riscv/jump/lui.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::{marker::PhantomData, mem::MaybeUninit}; - -use ceno_emul::InsnKind; -use ff_ext::ExtensionField; - -use crate::{ - Value, - circuit_builder::CircuitBuilder, - error::ZKVMError, - instructions::{ - Instruction, - riscv::{constants::UInt, u_insn::UInstructionConfig}, - }, - witness::LkMultiplicity, -}; - -pub struct LuiConfig { - pub u_insn: UInstructionConfig, - pub rd_written: UInt, -} - -pub struct LuiInstruction(PhantomData); - -/// LUI instruction circuit -impl Instruction for LuiInstruction { - type InstructionConfig = LuiConfig; - - fn name() -> String { - format!("{:?}", InsnKind::LUI) - } - - fn construct_circuit( - circuit_builder: &mut CircuitBuilder, - ) -> Result, ZKVMError> { - let rd_written = UInt::new(|| "rd_limbs", circuit_builder)?; - - // rd_written = imm, so just enforce that U-type immediate from program - // table is equal to rd_written value - let u_insn = UInstructionConfig::construct_circuit( - circuit_builder, - InsnKind::LUI, - &rd_written.value(), // instruction immediate for program table lookup - rd_written.register_expr(), - )?; - - Ok(LuiConfig { u_insn, rd_written }) - } - - fn assign_instance( - config: &Self::InstructionConfig, - instance: &mut [MaybeUninit], - lk_multiplicity: &mut LkMultiplicity, - step: &ceno_emul::StepRecord, - ) -> Result<(), ZKVMError> { - config - .u_insn - .assign_instance(instance, lk_multiplicity, step)?; - - let rd = Value::new(step.rd().unwrap().value.after, lk_multiplicity); - config.rd_written.assign_limbs(instance, rd.as_u16_limbs()); - - Ok(()) - } -} diff --git a/ceno_zkvm/src/instructions/riscv/jump/test.rs b/ceno_zkvm/src/instructions/riscv/jump/test.rs index 4453f4c3a..648b17f66 100644 --- a/ceno_zkvm/src/instructions/riscv/jump/test.rs +++ b/ceno_zkvm/src/instructions/riscv/jump/test.rs @@ -3,14 +3,11 @@ use goldilocks::GoldilocksExt2; use crate::{ circuit_builder::{CircuitBuilder, ConstraintSystem}, - instructions::{ - Instruction, - riscv::test_utils::{imm_j, imm_u}, - }, + instructions::Instruction, scheme::mock_prover::{MOCK_PC_START, MockProver}, }; -use super::{AuipcInstruction, JalInstruction, JalrInstruction, LuiInstruction}; +use super::{JalInstruction, JalrInstruction}; #[test] fn test_opcode_jal() { @@ -29,7 +26,7 @@ fn test_opcode_jal() { let pc_offset: i32 = -8i32; let new_pc: ByteAddr = ByteAddr(MOCK_PC_START.0.wrapping_add_signed(pc_offset)); - let insn_code = encode_rv32(InsnKind::JAL, 0, 0, 4, imm_j(pc_offset)); + let insn_code = encode_rv32(InsnKind::JAL, 0, 0, 4, pc_offset); let (raw_witin, lkm) = JalInstruction::::assign_instances( &config, cb.cs.num_witin as usize, @@ -64,7 +61,7 @@ fn test_opcode_jalr() { let imm = -15i32; let rs1_read: Word = 100u32; let new_pc: ByteAddr = ByteAddr(rs1_read.wrapping_add_signed(imm) & (!1)); - let insn_code = encode_rv32(InsnKind::JALR, 2, 0, 4, imm as u32); + let insn_code = encode_rv32(InsnKind::JALR, 2, 0, 4, imm); let (raw_witin, lkm) = JalrInstruction::::assign_instances( &config, @@ -79,72 +76,5 @@ fn test_opcode_jalr() { )], ) .unwrap(); - - MockProver::assert_satisfied_raw(&cb, raw_witin, &[insn_code], None, Some(lkm)); -} - -#[test] -fn test_opcode_lui() { - let mut cs = ConstraintSystem::::new(|| "riscv"); - let mut cb = CircuitBuilder::new(&mut cs); - let config = cb - .namespace( - || "lui", - |cb| { - let config = LuiInstruction::::construct_circuit(cb); - Ok(config) - }, - ) - .unwrap() - .unwrap(); - - let imm_value = imm_u(0x90005); - let insn_code = encode_rv32(InsnKind::LUI, 0, 0, 4, imm_value); - let (raw_witin, lkm) = LuiInstruction::::assign_instances( - &config, - cb.cs.num_witin as usize, - vec![StepRecord::new_u_instruction( - 4, - MOCK_PC_START, - insn_code, - Change::new(0, imm_value), - 0, - )], - ) - .unwrap(); - - MockProver::assert_satisfied_raw(&cb, raw_witin, &[insn_code], None, Some(lkm)); -} - -#[test] -fn test_opcode_auipc() { - let mut cs = ConstraintSystem::::new(|| "riscv"); - let mut cb = CircuitBuilder::new(&mut cs); - let config = cb - .namespace( - || "auipc", - |cb| { - let config = AuipcInstruction::::construct_circuit(cb); - Ok(config) - }, - ) - .unwrap() - .unwrap(); - - let imm_value = imm_u(0x90005); - let insn_code = encode_rv32(InsnKind::AUIPC, 0, 0, 4, imm_value); - let (raw_witin, lkm) = AuipcInstruction::::assign_instances( - &config, - cb.cs.num_witin as usize, - vec![StepRecord::new_u_instruction( - 4, - MOCK_PC_START, - insn_code, - Change::new(0, MOCK_PC_START.0.wrapping_add(imm_value)), - 0, - )], - ) - .unwrap(); - MockProver::assert_satisfied_raw(&cb, raw_witin, &[insn_code], None, Some(lkm)); } diff --git a/ceno_zkvm/src/instructions/riscv/logic_imm/logic_imm_circuit.rs b/ceno_zkvm/src/instructions/riscv/logic_imm/logic_imm_circuit.rs index b6a8bb690..79469b3c8 100644 --- a/ceno_zkvm/src/instructions/riscv/logic_imm/logic_imm_circuit.rs +++ b/ceno_zkvm/src/instructions/riscv/logic_imm/logic_imm_circuit.rs @@ -124,7 +124,7 @@ impl LogicConfig { #[cfg(test)] mod test { - use ceno_emul::{Change, InsnKind, PC_STEP_SIZE, StepRecord, encode_rv32}; + use ceno_emul::{Change, InsnKind, PC_STEP_SIZE, StepRecord, encode_rv32u}; use goldilocks::GoldilocksExt2; use crate::{ @@ -191,7 +191,7 @@ mod test { .unwrap() .unwrap(); - let insn_code = encode_rv32(I::INST_KIND, 2, 0, 4, imm); + let insn_code = encode_rv32u(I::INST_KIND, 2, 0, 4, imm); let (raw_witin, lkm) = LogicInstruction::::assign_instances( &config, cb.cs.num_witin as usize, diff --git a/ceno_zkvm/src/instructions/riscv/memory/test.rs b/ceno_zkvm/src/instructions/riscv/memory/test.rs index 6243f197b..4785994d3 100644 --- a/ceno_zkvm/src/instructions/riscv/memory/test.rs +++ b/ceno_zkvm/src/instructions/riscv/memory/test.rs @@ -74,7 +74,7 @@ fn load(mem_value: Word, insn: InsnKind, shift: u32) -> Word { } } -fn impl_opcode_store>(imm: u32) { +fn impl_opcode_store>(imm: i32) { let mut cs = ConstraintSystem::::new(|| "riscv"); let mut cb = CircuitBuilder::new(&mut cs); let config = cb @@ -92,7 +92,7 @@ fn impl_opcode_store sb(prev_mem_value, rs2_word, unaligned_addr.shift()), InsnKind::SH => sh(prev_mem_value, rs2_word, unaligned_addr.shift()), @@ -122,7 +122,7 @@ fn impl_opcode_store>(imm: u32) { +fn impl_opcode_load>(imm: i32) { let mut cs = ConstraintSystem::::new(|| "riscv"); let mut cb = CircuitBuilder::new(&mut cs); let config = cb @@ -140,7 +140,7 @@ fn impl_opcode_load>(imm) } -fn impl_opcode_sh(imm: u32) { +fn impl_opcode_sh(imm: i32) { assert_eq!(imm & 0x01, 0); impl_opcode_store::>(imm) } -fn impl_opcode_sw(imm: u32) { +fn impl_opcode_sw(imm: i32) { assert_eq!(imm & 0x03, 0); impl_opcode_store::>(imm) } @@ -187,9 +187,8 @@ fn test_sb() { impl_opcode_sb(10); impl_opcode_sb(15); - let neg_one = u32::MAX; - for i in 0..4 { - impl_opcode_sb(neg_one - i); + for i in -4..0 { + impl_opcode_sb(i); } } @@ -198,9 +197,8 @@ fn test_sh() { impl_opcode_sh(0); impl_opcode_sh(2); - let neg_two = u32::MAX - 1; - for i in [0, 2] { - impl_opcode_sh(neg_two - i) + for i in [-4, -2] { + impl_opcode_sh(i) } } @@ -209,8 +207,7 @@ fn test_sw() { impl_opcode_sw(0); impl_opcode_sw(4); - let neg_four = u32::MAX - 3; - impl_opcode_sw(neg_four); + impl_opcode_sw(-4); } #[test] @@ -220,10 +217,8 @@ fn test_lb() { impl_opcode_load::>(2); impl_opcode_load::>(3); - let neg_one = u32::MAX; - // imm = -1, -2, -3 - for i in 0..3 { - impl_opcode_load::>(neg_one - i); + for i in -3..0 { + impl_opcode_load::>(i); } } @@ -234,10 +229,8 @@ fn test_lbu() { impl_opcode_load::>(2); impl_opcode_load::>(3); - let neg_one = u32::MAX; - // imm = -1, -2, -3 - for i in 0..3 { - impl_opcode_load::>(neg_one - i); + for i in -3..0 { + impl_opcode_load::>(i); } } @@ -247,10 +240,8 @@ fn test_lh() { impl_opcode_load::>(2); impl_opcode_load::>(4); - let neg_two = u32::MAX - 1; - // imm = -2, -4 - for i in [0, 2] { - impl_opcode_load::>(neg_two - i); + for i in [-4, -2] { + impl_opcode_load::>(i); } } @@ -260,10 +251,8 @@ fn test_lhu() { impl_opcode_load::>(2); impl_opcode_load::>(4); - let neg_two = u32::MAX - 1; - // imm = -2, -4 - for i in [0, 2] { - impl_opcode_load::>(neg_two - i); + for i in [-4, -2] { + impl_opcode_load::>(i); } } @@ -271,5 +260,5 @@ fn test_lhu() { fn test_lw() { impl_opcode_load::>(0); impl_opcode_load::>(4); - impl_opcode_load::>(u32::MAX - 3); // imm = -4 + impl_opcode_load::>(-4); } diff --git a/ceno_zkvm/src/instructions/riscv/rv32im.rs b/ceno_zkvm/src/instructions/riscv/rv32im.rs index b11cbfee5..3afebe848 100644 --- a/ceno_zkvm/src/instructions/riscv/rv32im.rs +++ b/ceno_zkvm/src/instructions/riscv/rv32im.rs @@ -39,10 +39,7 @@ use std::collections::{BTreeMap, BTreeSet}; use strum::IntoEnumIterator; use super::{ - arith::AddInstruction, - branch::BltuInstruction, - ecall::HaltInstruction, - jump::{JalInstruction, LuiInstruction}, + arith::AddInstruction, branch::BltuInstruction, ecall::HaltInstruction, jump::JalInstruction, memory::LwInstruction, }; @@ -88,8 +85,6 @@ pub struct Rv32imConfig { // Jump Opcodes pub jal_config: as Instruction>::InstructionConfig, pub jalr_config: as Instruction>::InstructionConfig, - pub auipc_config: as Instruction>::InstructionConfig, - pub lui_config: as Instruction>::InstructionConfig, // Memory Opcodes pub lw_config: as Instruction>::InstructionConfig, @@ -155,10 +150,8 @@ impl Rv32imConfig { let bgeu_config = cs.register_opcode_circuit::>(); // jump opcodes - let lui_config = cs.register_opcode_circuit::>(); let jal_config = cs.register_opcode_circuit::>(); let jalr_config = cs.register_opcode_circuit::>(); - let auipc_config = cs.register_opcode_circuit::>(); // memory opcodes let lw_config = cs.register_opcode_circuit::>(); @@ -218,10 +211,8 @@ impl Rv32imConfig { bge_config, bgeu_config, // jump opcodes - lui_config, jal_config, jalr_config, - auipc_config, // memory opcodes sw_config, sh_config, @@ -287,8 +278,6 @@ impl Rv32imConfig { // jump fixed.register_opcode_circuit::>(cs); fixed.register_opcode_circuit::>(cs); - fixed.register_opcode_circuit::>(cs); - fixed.register_opcode_circuit::>(cs); // memory fixed.register_opcode_circuit::>(cs); fixed.register_opcode_circuit::>(cs); @@ -323,10 +312,10 @@ impl Rv32imConfig { .collect(); let mut halt_records = Vec::new(); steps.into_iter().for_each(|record| { - let insn_kind = record.insn().codes().kind; + let insn_kind = record.insn.kind; match insn_kind { // ecall / halt - EANY if record.rs1().unwrap().value == Platform::ecall_halt() => { + InsnKind::ECALL if record.rs1().unwrap().value == Platform::ecall_halt() => { halt_records.push(record); } // other type of ecalls are handled by dummy ecall instruction @@ -393,8 +382,6 @@ impl Rv32imConfig { // jump assign_opcode!(JAL, JalInstruction, jal_config); assign_opcode!(JALR, JalrInstruction, jalr_config); - assign_opcode!(AUIPC, AuipcInstruction, auipc_config); - assign_opcode!(LUI, LuiInstruction, lui_config); // memory assign_opcode!(LW, LwInstruction, lw_config); assign_opcode!(LB, LbInstruction, lb_config); @@ -411,7 +398,7 @@ impl Rv32imConfig { assert_eq!( all_records.keys().cloned().collect::>(), // these are opcodes that haven't been implemented - [INVALID, DIV, REM, REMU, EANY] + [INVALID, DIV, REM, REMU, ECALL, EBREAK] .into_iter() .map(|insn_kind| insn_kind as usize) .collect::>(), @@ -495,7 +482,8 @@ impl DummyExtraConfig { assign_opcode!(DIV, DivDummy, div_config); assign_opcode!(REM, RemDummy, rem_config); assign_opcode!(REMU, RemuDummy, remu_config); - assign_opcode!(EANY, EcallDummy, ecall_config); + assign_opcode!(ECALL, EcallDummy, ecall_config); + assign_opcode!(EBREAK, EcallDummy, ecall_config); let _ = steps.remove(&(INVALID as usize)); assert!(steps.is_empty()); diff --git a/ceno_zkvm/src/instructions/riscv/shift_imm.rs b/ceno_zkvm/src/instructions/riscv/shift_imm.rs index 4e2700914..ab870ef6b 100644 --- a/ceno_zkvm/src/instructions/riscv/shift_imm.rs +++ b/ceno_zkvm/src/instructions/riscv/shift_imm.rs @@ -188,7 +188,7 @@ impl Instruction for ShiftImmInstructio #[cfg(test)] mod test { - use ceno_emul::{Change, InsnKind, PC_STEP_SIZE, StepRecord, encode_rv32}; + use ceno_emul::{Change, InsnKind, PC_STEP_SIZE, StepRecord, encode_rv32u}; use goldilocks::GoldilocksExt2; use super::{ShiftImmInstruction, SlliOp, SraiOp, SrliOp}; @@ -255,17 +255,17 @@ mod test { let (prefix, insn_code, rd_written) = match I::INST_KIND { InsnKind::SLLI => ( "SLLI", - encode_rv32(InsnKind::SLLI, 2, 0, 4, imm), + encode_rv32u(InsnKind::SLLI, 2, 0, 4, imm), rs1_read << imm, ), InsnKind::SRAI => ( "SRAI", - encode_rv32(InsnKind::SRAI, 2, 0, 4, imm), + encode_rv32u(InsnKind::SRAI, 2, 0, 4, imm), (rs1_read as i32 >> imm as i32) as u32, ), InsnKind::SRLI => ( "SRLI", - encode_rv32(InsnKind::SRLI, 2, 0, 4, imm), + encode_rv32u(InsnKind::SRLI, 2, 0, 4, imm), rs1_read >> imm, ), _ => unreachable!(), diff --git a/ceno_zkvm/src/instructions/riscv/slti.rs b/ceno_zkvm/src/instructions/riscv/slti.rs index d88f0336c..522be2566 100644 --- a/ceno_zkvm/src/instructions/riscv/slti.rs +++ b/ceno_zkvm/src/instructions/riscv/slti.rs @@ -224,7 +224,7 @@ mod test { let mut cs = ConstraintSystem::::new(|| "riscv"); let mut cb = CircuitBuilder::new(&mut cs); - let insn_code = encode_rv32(I::INST_KIND, 2, 0, 4, imm as u32); + let insn_code = encode_rv32(I::INST_KIND, 2, 0, 4, imm); let config = cb .namespace( diff --git a/ceno_zkvm/src/instructions/riscv/test_utils.rs b/ceno_zkvm/src/instructions/riscv/test_utils.rs deleted file mode 100644 index 416ce628c..000000000 --- a/ceno_zkvm/src/instructions/riscv/test_utils.rs +++ /dev/null @@ -1,23 +0,0 @@ -pub fn imm_b(imm: i32) -> u32 { - // imm is 13 bits in B-type - imm_with_max_valid_bits(imm, 13) -} - -pub fn imm_i(imm: i32) -> u32 { - // imm is 12 bits in I-type - imm_with_max_valid_bits(imm, 12) -} - -pub fn imm_j(imm: i32) -> u32 { - // imm is 21 bits in J-type - imm_with_max_valid_bits(imm, 21) -} - -fn imm_with_max_valid_bits(imm: i32, bits: u32) -> u32 { - imm as u32 & !(u32::MAX << bits) -} - -pub fn imm_u(imm: u32) -> u32 { - // valid imm is imm[12:31] in U-type - imm << 12 -} diff --git a/ceno_zkvm/src/instructions/riscv/u_insn.rs b/ceno_zkvm/src/instructions/riscv/u_insn.rs deleted file mode 100644 index 8b00969f8..000000000 --- a/ceno_zkvm/src/instructions/riscv/u_insn.rs +++ /dev/null @@ -1,66 +0,0 @@ -use ceno_emul::{InsnKind, StepRecord}; -use ff_ext::ExtensionField; - -use crate::{ - chip_handler::RegisterExpr, - circuit_builder::CircuitBuilder, - error::ZKVMError, - expression::{Expression, ToExpr}, - instructions::riscv::insn_base::{StateInOut, WriteRD}, - tables::InsnRecord, - witness::LkMultiplicity, -}; -use core::mem::MaybeUninit; - -/// This config handles the common part of the U-type instruction: -/// - PC, cycle, fetch -/// - Register access -/// -/// It does not witness the output rd value or instruction immediate -#[derive(Debug)] -pub struct UInstructionConfig { - pub vm_state: StateInOut, - pub rd: WriteRD, -} - -impl UInstructionConfig { - pub fn construct_circuit( - circuit_builder: &mut CircuitBuilder, - insn_kind: InsnKind, - imm: &Expression, - rd_written: RegisterExpr, - ) -> Result { - // State in and out - let vm_state = StateInOut::construct_circuit(circuit_builder, false)?; - - // Registers - let rd = WriteRD::construct_circuit(circuit_builder, rd_written, vm_state.ts)?; - - // Fetch instruction - circuit_builder.lk_fetch(&InsnRecord::new( - vm_state.pc.expr(), - insn_kind.into(), - Some(rd.id.expr()), - 0.into(), - 0.into(), - imm.clone(), - ))?; - - Ok(UInstructionConfig { vm_state, rd }) - } - - pub fn assign_instance( - &self, - instance: &mut [MaybeUninit], - lk_multiplicity: &mut LkMultiplicity, - step: &StepRecord, - ) -> Result<(), ZKVMError> { - self.vm_state.assign_instance(instance, step)?; - self.rd.assign_instance(instance, lk_multiplicity, step)?; - - // Fetch the instruction. - lk_multiplicity.fetch(step.pc().before.0); - - Ok(()) - } -} diff --git a/ceno_zkvm/src/scheme/mock_prover.rs b/ceno_zkvm/src/scheme/mock_prover.rs index 70b543090..089ad2934 100644 --- a/ceno_zkvm/src/scheme/mock_prover.rs +++ b/ceno_zkvm/src/scheme/mock_prover.rs @@ -17,7 +17,7 @@ use crate::{ }; use ark_std::test_rng; use base64::{Engine, engine::general_purpose::STANDARD_NO_PAD}; -use ceno_emul::{ByteAddr, CENO_PLATFORM, PC_WORD_SIZE, Program}; +use ceno_emul::{ByteAddr, CENO_PLATFORM, Program}; use ff::Field; use ff_ext::ExtensionField; use generic_static::StaticTypeMap; @@ -26,7 +26,7 @@ use itertools::{Itertools, enumerate, izip}; use multilinear_extensions::{mle::IntoMLEs, virtual_poly_v2::ArcMultilinearExtension}; use rand::thread_rng; use std::{ - collections::{BTreeMap, HashMap, HashSet}, + collections::{HashMap, HashSet}, fs::File, hash::Hash, io::{BufReader, ErrorKind}, @@ -400,7 +400,7 @@ impl<'a, E: ExtensionField + Hash> MockProver { pub fn run( cb: &CircuitBuilder, wits_in: &[ArcMultilinearExtension<'a, E>], - programs: &[u32], + programs: &[ceno_emul::Instruction], lkm: Option, ) -> Result<(), Vec>> { Self::run_maybe_challenge(cb, wits_in, programs, &[], None, lkm) @@ -409,33 +409,16 @@ impl<'a, E: ExtensionField + Hash> MockProver { fn run_maybe_challenge( cb: &CircuitBuilder, wits_in: &[ArcMultilinearExtension<'a, E>], - input_programs: &[u32], + input_programs: &[ceno_emul::Instruction], pi: &[ArcMultilinearExtension<'a, E>], challenge: Option<[E; 2]>, lkm: Option, ) -> Result<(), Vec>> { - // fix the program table - let instructions = input_programs - .iter() - .cloned() - .chain(std::iter::repeat(0)) - .take(MOCK_PROGRAM_SIZE) - .collect_vec(); - let image = instructions - .iter() - .enumerate() - .map(|(insn_idx, &insn)| { - ( - CENO_PLATFORM.pc_base() + (insn_idx * PC_WORD_SIZE) as u32, - insn, - ) - }) - .collect::>(); let program = Program::new( CENO_PLATFORM.pc_base(), CENO_PLATFORM.pc_base(), - instructions, - image, + input_programs.to_vec(), + Default::default(), ); // load tables @@ -670,7 +653,7 @@ impl<'a, E: ExtensionField + Hash> MockProver { pub fn assert_with_expected_errors( cb: &CircuitBuilder, wits_in: &[ArcMultilinearExtension<'a, E>], - programs: &[u32], + programs: &[ceno_emul::Instruction], constraint_names: &[&str], challenge: Option<[E; 2]>, lkm: Option, @@ -722,7 +705,7 @@ Hints: pub fn assert_satisfied_raw( cb: &CircuitBuilder, raw_witin: RowMajorMatrix, - programs: &[u32], + programs: &[ceno_emul::Instruction], challenge: Option<[E; 2]>, lkm: Option, ) { @@ -738,7 +721,7 @@ Hints: pub fn assert_satisfied( cb: &CircuitBuilder, wits_in: &[ArcMultilinearExtension<'a, E>], - programs: &[u32], + programs: &[ceno_emul::Instruction], challenge: Option<[E; 2]>, lkm: Option, ) { diff --git a/ceno_zkvm/src/scheme/tests.rs b/ceno_zkvm/src/scheme/tests.rs index 260c17fae..eb90ff3fc 100644 --- a/ceno_zkvm/src/scheme/tests.rs +++ b/ceno_zkvm/src/scheme/tests.rs @@ -3,8 +3,8 @@ use std::{marker::PhantomData, mem::MaybeUninit}; use ark_std::test_rng; use ceno_emul::{ CENO_PLATFORM, - InsnKind::{ADD, EANY}, - PC_WORD_SIZE, Platform, Program, StepRecord, VMState, + InsnKind::{ADD, ECALL}, + Platform, Program, StepRecord, VMState, encode_rv32, }; use ff::Field; use ff_ext::ExtensionField; @@ -18,7 +18,6 @@ use transcript::Transcript; use crate::{ circuit_builder::CircuitBuilder, - declare_program, error::ZKVMError, expression::{ToExpr, WitIn}, instructions::{ @@ -187,23 +186,12 @@ fn test_rw_lk_expression_combination() { test_rw_lk_expression_combination_inner::<17, 61>(); } -const PROGRAM_SIZE: usize = 4; -#[allow(clippy::unusual_byte_groupings)] -const ECALL_HALT: u32 = 0b_000000000000_00000_000_00000_1110011; -#[allow(clippy::unusual_byte_groupings)] -const PROGRAM_CODE: [u32; PROGRAM_SIZE] = { - let mut program: [u32; PROGRAM_SIZE] = [ECALL_HALT; PROGRAM_SIZE]; - - declare_program!( - program, - // func7 rs2 rs1 f3 rd opcode - 0b_0000000_00100_00001_000_00100_0110011, // add x4, x4, x1 <=> addi x4, x4, 1 - ECALL_HALT, // ecall halt - ECALL_HALT, // ecall halt - ECALL_HALT, // ecall halt - ); - program -}; +const PROGRAM_CODE: [ceno_emul::Instruction; 4] = [ + encode_rv32(ADD, 4, 1, 4, 0), + encode_rv32(ECALL, 0, 0, 0, 0), + encode_rv32(ECALL, 0, 0, 0, 0), + encode_rv32(ECALL, 0, 0, 0, 0), +]; #[ignore = "this case is already tested in riscv_example as ecall_halt has only one instance"] #[test] @@ -216,16 +204,7 @@ fn test_single_add_instance_e2e() { CENO_PLATFORM.pc_base(), CENO_PLATFORM.pc_base(), PROGRAM_CODE.to_vec(), - PROGRAM_CODE - .iter() - .enumerate() - .map(|(insn_idx, &insn)| { - ( - (insn_idx * PC_WORD_SIZE) as u32 + CENO_PLATFORM.pc_base(), - insn, - ) - }) - .collect(), + Default::default(), ); let pcs_param = Pcs::setup(1 << MAX_NUM_VARIABLES).expect("Basefold PCS setup"); @@ -271,10 +250,10 @@ fn test_single_add_instance_e2e() { let mut add_records = vec![]; let mut halt_records = vec![]; all_records.into_iter().for_each(|record| { - let kind = record.insn().codes().kind; + let kind = record.insn().kind; match kind { ADD => add_records.push(record), - EANY => { + ECALL => { if record.rs1().unwrap().value == Platform::ecall_halt() { halt_records.push(record); } diff --git a/ceno_zkvm/src/tables/program.rs b/ceno_zkvm/src/tables/program.rs index da063545e..20d7d1cd1 100644 --- a/ceno_zkvm/src/tables/program.rs +++ b/ceno_zkvm/src/tables/program.rs @@ -12,27 +12,13 @@ use crate::{ witness::RowMajorMatrix, }; use ceno_emul::{ - DecodedInstruction, InsnCodes, InsnFormat::*, InsnKind::*, PC_STEP_SIZE, Program, WORD_SIZE, + InsnFormat, InsnFormat::*, InsnKind::*, Instruction, PC_STEP_SIZE, Program, WORD_SIZE, }; use ff_ext::ExtensionField; use goldilocks::SmallField; use itertools::Itertools; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; -#[macro_export] -macro_rules! declare_program { - ($program:ident, $($instr:expr),* $(,)?) => { - - { - let mut _i = 0; - $( - $program[_i] = $instr; - _i += 1; - )* - } - }; -} - /// This structure establishes the order of the fields in instruction records, common to the program table and circuit fetches. #[derive(Clone, Debug)] pub struct InsnRecord([T; 6]); @@ -42,7 +28,7 @@ impl InsnRecord { where T: From, { - let rd = rd.unwrap_or_else(|| T::from(DecodedInstruction::RD_NULL)); + let rd = rd.unwrap_or_else(|| T::from(Instruction::RD_NULL)); InsnRecord([pc, kind, rd, rs1, rs2, imm_internal]) } @@ -52,10 +38,10 @@ impl InsnRecord { } impl InsnRecord { - fn from_decoded(pc: u32, insn: &DecodedInstruction) -> Self { + fn from_decoded(pc: u32, insn: &Instruction) -> Self { InsnRecord([ (pc as u64).into(), - (insn.codes().kind as u64).into(), + (insn.kind as u64).into(), (insn.rd_internal() as u64).into(), (insn.rs1_or_zero() as u64).into(), (insn.rs2_or_zero() as u64).into(), @@ -72,25 +58,17 @@ impl InsnRecord<()> { /// - `as u32` and `as i32` as usual. /// - `i64_to_base(imm)` gives the field element going into the program table. /// - `as u64` in unsigned cases. - pub fn imm_internal(insn: &DecodedInstruction) -> i64 { - let imm: u32 = insn.immediate(); - match insn.codes() { + pub fn imm_internal(insn: &Instruction) -> i64 { + match (insn.kind, InsnFormat::from(insn.kind)) { // Prepare the immediate for ShiftImmInstruction. // The shift is implemented as a multiplication/division by 1 << immediate. - InsnCodes { - kind: SLLI | SRLI | SRAI, - .. - } => 1 << (imm & 0x1F), + (SLLI | SRLI | SRAI, _) => 1 << insn.imm, // Unsigned view. // For example, u32::MAX is `u32::MAX mod p` in the finite field. - InsnCodes { format: R | U, .. } - | InsnCodes { - kind: ADDI | SLTIU | ANDI | XORI | ORI, - .. - } => imm as u64 as i64, + (_, R | U) | (ADDI | SLTIU | ANDI | XORI | ORI, _) => insn.imm as u32 as i64, // Signed view. // For example, u32::MAX is `-1 mod p` in the finite field. - _ => imm as i32 as i64, + _ => insn.imm as i64, } } } @@ -166,7 +144,7 @@ impl TableCircuit for ProgramTableCircuit { .zip((0..num_instructions).into_par_iter()) .for_each(|(row, i)| { let pc = pc_base + (i * PC_STEP_SIZE) as u32; - let insn = DecodedInstruction::new(program.instructions[i]); + let insn = program.instructions[i]; let values = InsnRecord::from_decoded(pc, &insn); // Copy all the fields. @@ -232,29 +210,6 @@ mod tests { use ff::Field; use goldilocks::{Goldilocks as F, GoldilocksExt2 as E}; - #[test] - #[allow(clippy::identity_op)] - fn test_decode_imm() { - for (i, expected) in [ - // Example of I-type: ADDI. - // imm | rs1 | funct3 | rd | opcode - (89 << 20 | 1 << 15 | 0b000 << 12 | 1 << 7 | 0x13, 89), - // Shifts get a precomputed power of 2: SLLI, SRLI, SRAI. - (31 << 20 | 1 << 15 | 0b001 << 12 | 1 << 7 | 0x13, 1 << 31), - (31 << 20 | 1 << 15 | 0b101 << 12 | 1 << 7 | 0x13, 1 << 31), - ( - 1 << 30 | 31 << 20 | 1 << 15 | 0b101 << 12 | 1 << 7 | 0x13, - 1 << 31, - ), - // Example of R-type with funct7: SUB. - // funct7 | rs2 | rs1 | funct3 | rd | opcode - (0x20 << 25 | 1 << 20 | 1 << 15 | 0 << 12 | 1 << 7 | 0x33, 0), - ] { - let imm = InsnRecord::imm_internal(&DecodedInstruction::new(i)); - assert_eq!(imm, expected); - } - } - #[test] fn test_program_padding() { let mut cs = ConstraintSystem::::new(|| "riscv"); diff --git a/clippy.toml b/clippy.toml index 6e64e6f38..87fda2227 100644 --- a/clippy.toml +++ b/clippy.toml @@ -2,9 +2,10 @@ # Eg removing syn is blocked by ark-ff-asm cutting a new release # (https://github.com/arkworks-rs/algebra/issues/813) amongst other things. allowed-duplicate-crates = [ - "syn", - "windows-sys", + "hashbrown", + "itertools", "regex-automata", "regex-syntax", - "itertools", + "syn", + "windows-sys", ] From 80dcb44e0f7abf2a6a5c11afabeae5552514b1b7 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Mon, 9 Dec 2024 18:27:59 +0800 Subject: [PATCH 13/64] Remove unimplemented ebreak --- ceno_emul/src/disassemble/mod.rs | 9 +-------- ceno_emul/src/rv32im.rs | 6 ++---- ceno_zkvm/src/instructions/riscv/rv32im.rs | 20 +++++++++----------- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/ceno_emul/src/disassemble/mod.rs b/ceno_emul/src/disassemble/mod.rs index d9fa3324e..bfb4723c8 100644 --- a/ceno_emul/src/disassemble/mod.rs +++ b/ceno_emul/src/disassemble/mod.rs @@ -292,14 +292,7 @@ impl InstructionProcessor for InstructionTranspiler { } fn process_ebreak(&mut self) -> Self::InstructionResult { - Instruction { - kind: InsnKind::EBREAK, - rd: 0, - rs1: 0, - rs2: 0, - imm: 0, - raw: self.word, - } + Instruction::unimp(self.word) } fn process_mul(&mut self, dec_insn: RType) -> Self::InstructionResult { diff --git a/ceno_emul/src/rv32im.rs b/ceno_emul/src/rv32im.rs index 86ab20ed8..d6f5675c7 100644 --- a/ceno_emul/src/rv32im.rs +++ b/ceno_emul/src/rv32im.rs @@ -172,7 +172,6 @@ pub enum InsnKind { SH, SW, ECALL, - EBREAK, } use InsnKind::*; @@ -187,7 +186,7 @@ impl From for InsnCategory { JAL | JALR => Compute, LB | LH | LW | LBU | LHU => Load, SB | SH | SW => Store, - ECALL | EBREAK => System, + ECALL => System, } } } @@ -204,7 +203,7 @@ impl From for InsnFormat { JALR => I, LB | LH | LW | LBU | LHU => I, SB | SH | SW => S, - ECALL | EBREAK => I, + ECALL => I, INVALID => I, } } @@ -523,7 +522,6 @@ impl Emulator { ) -> Result { match kind { InsnKind::ECALL => ctx.ecall(), - InsnKind::EBREAK => ctx.trap(TrapCause::Breakpoint), _ => ctx.trap(TrapCause::IllegalInstruction(decoded.raw)), } } diff --git a/ceno_zkvm/src/instructions/riscv/rv32im.rs b/ceno_zkvm/src/instructions/riscv/rv32im.rs index 3afebe848..54248eff2 100644 --- a/ceno_zkvm/src/instructions/riscv/rv32im.rs +++ b/ceno_zkvm/src/instructions/riscv/rv32im.rs @@ -307,8 +307,8 @@ impl Rv32imConfig { witness: &mut ZKVMWitnesses, steps: Vec, ) -> Result { - let mut all_records: BTreeMap> = InsnKind::iter() - .map(|insn_kind| ((insn_kind as usize), Vec::new())) + let mut all_records: BTreeMap> = InsnKind::iter() + .map(|insn_kind| (insn_kind, Vec::new())) .collect(); let mut halt_records = Vec::new(); steps.into_iter().for_each(|record| { @@ -320,7 +320,6 @@ impl Rv32imConfig { } // other type of ecalls are handled by dummy ecall instruction _ => { - let insn_kind = insn_kind as usize; // it's safe to unwrap as all_records are initialized with Vec::new() all_records.get_mut(&insn_kind).unwrap().push(record); } @@ -342,7 +341,7 @@ impl Rv32imConfig { witness.assign_opcode_circuit::<$instruction>( cs, &self.$config, - all_records.remove(&($insn_kind as usize)).unwrap(), + all_records.remove(&($insn_kind)).unwrap(), )?; }; } @@ -398,9 +397,8 @@ impl Rv32imConfig { assert_eq!( all_records.keys().cloned().collect::>(), // these are opcodes that haven't been implemented - [INVALID, DIV, REM, REMU, ECALL, EBREAK] + [INVALID, DIV, REM, REMU, ECALL] .into_iter() - .map(|insn_kind| insn_kind as usize) .collect::>(), ); Ok(GroupedSteps(all_records)) @@ -426,7 +424,7 @@ impl Rv32imConfig { } /// Opaque type to pass unimplemented instructions from Rv32imConfig to DummyExtraConfig. -pub struct GroupedSteps(BTreeMap>); +pub struct GroupedSteps(BTreeMap>); /// Fake version of what is missing in Rv32imConfig, for some tests. pub struct DummyExtraConfig { @@ -474,7 +472,7 @@ impl DummyExtraConfig { witness.assign_opcode_circuit::<$instruction>( cs, &self.$config, - steps.remove(&($insn_kind as usize)).unwrap(), + steps.remove(&($insn_kind)).unwrap(), )?; }; } @@ -483,10 +481,10 @@ impl DummyExtraConfig { assign_opcode!(REM, RemDummy, rem_config); assign_opcode!(REMU, RemuDummy, remu_config); assign_opcode!(ECALL, EcallDummy, ecall_config); - assign_opcode!(EBREAK, EcallDummy, ecall_config); - let _ = steps.remove(&(INVALID as usize)); - assert!(steps.is_empty()); + let _ = steps.remove(&INVALID); + let keys: Vec<&InsnKind> = steps.keys().collect::>(); + assert!(steps.is_empty(), "unimplemented opcodes: {:?}", keys); Ok(()) } } From 9280a9fc690162ba3fbed9141a60fbab08a0bbc6 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 16:00:23 +0800 Subject: [PATCH 14/64] Clean up --- Cargo.lock | 8 ++++---- ceno_host/src/lib.rs | 32 +++++++++++++++++++------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6bfa8af1e..960028af5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -217,7 +217,7 @@ checksum = "523363cbe1df49b68215efdf500b103ac3b0fb4836aed6d15689a076eadb8fff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1129,7 +1129,7 @@ checksum = "1bb5c1d8184f13f7d0ccbeeca0def2f9a181bce2624302793005f5ca8aa62e5e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1505,7 +1505,7 @@ checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] @@ -1728,7 +1728,7 @@ checksum = "beb382a4d9f53bd5c0be86b10d8179c3f8a14c30bf774ff77096ed6581e35981" dependencies = [ "proc-macro2", "quote", - "syn 2.0.82", + "syn 2.0.90", ] [[package]] diff --git a/ceno_host/src/lib.rs b/ceno_host/src/lib.rs index c64bce07d..ac90f7d1d 100644 --- a/ceno_host/src/lib.rs +++ b/ceno_host/src/lib.rs @@ -8,6 +8,14 @@ use rkyv::{ util::AlignedVec, }; +// We want to get access to the default value of `AlignedVec::ALIGNMENT`, and using it directly like this +// pub const RKVY_ALIGNMENT: usize = rkyv::util::AlignedVec::ALIGNMENT; +// doesn't work: +pub const RKYV_ALIGNMENT: usize = { + type AlignedVec = rkyv::util::AlignedVec; + AlignedVec::ALIGNMENT +}; + #[derive(Default)] pub struct CenoStdin { pub items: Vec, @@ -26,32 +34,30 @@ impl CenoStdin { } pub fn finalise(&self) -> Vec { - // TODO: perhaps don't hardcode 16 here. - // It's from rkyv's format, so we can probably take it from there somehow? - // TODO: clean this up. - let initial_offset = (size_of::() * self.items.len()).next_multiple_of(16); - // println!("offset: {}", initial_offset); + let initial_offset = (size_of::() * self.items.len()).next_multiple_of(RKYV_ALIGNMENT); let offsets: Vec = self .items .iter() .scan(initial_offset, |acc, bytes| { let output = (*acc + bytes.len()) as u32; - // print!("len: {}\t", bytes.len()); - *acc += bytes.len().next_multiple_of(16); - // println!("acc: {}", *acc); + *acc += bytes.len().next_multiple_of(RKYV_ALIGNMENT); Some(output) }) .collect(); let offsets_u8: Vec = offsets.iter().copied().flat_map(u32::to_le_bytes).collect(); let mut buf: AlignedVec = AlignedVec::new(); buf.extend_from_slice(&offsets_u8); - // println!("buf.len() after offsets: {}", buf.len()); - buf.extend_from_slice(&vec![0; buf.len().next_multiple_of(16) - buf.len()]); - // println!("buf.len() after offset padding: {}", buf.len()); + buf.extend_from_slice(&vec![ + 0; + buf.len().next_multiple_of(RKYV_ALIGNMENT) - buf.len() + ]); for (offset, item) in izip!(offsets, &self.items) { buf.extend_from_slice(item); - buf.extend_from_slice(&vec![0; buf.len().next_multiple_of(16) - buf.len()]); - assert_eq!(buf.len(), offset.next_multiple_of(16) as usize); + buf.extend_from_slice(&vec![ + 0; + buf.len().next_multiple_of(RKYV_ALIGNMENT) - buf.len() + ]); + assert_eq!(buf.len(), offset.next_multiple_of(RKYV_ALIGNMENT) as usize); } let (prefix, hints, postfix): (_, &[u32], _) = unsafe { buf.align_to() }; assert_eq!(prefix, &[]); From 1ac5c7c4a6040f35cb431ee210ca1b63067712b3 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 16:04:28 +0800 Subject: [PATCH 15/64] Fix --- ceno_host/src/lib.rs | 5 ++++- ceno_rt/src/mmio.rs | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ceno_host/src/lib.rs b/ceno_host/src/lib.rs index ac90f7d1d..134225015 100644 --- a/ceno_host/src/lib.rs +++ b/ceno_host/src/lib.rs @@ -57,7 +57,10 @@ impl CenoStdin { 0; buf.len().next_multiple_of(RKYV_ALIGNMENT) - buf.len() ]); - assert_eq!(buf.len(), offset.next_multiple_of(RKYV_ALIGNMENT) as usize); + assert_eq!( + buf.len(), + (offset as usize).next_multiple_of(RKYV_ALIGNMENT) + ); } let (prefix, hints, postfix): (_, &[u32], _) = unsafe { buf.align_to() }; assert_eq!(prefix, &[]); diff --git a/ceno_rt/src/mmio.rs b/ceno_rt/src/mmio.rs index 052fabf63..49598f780 100644 --- a/ceno_rt/src/mmio.rs +++ b/ceno_rt/src/mmio.rs @@ -1,6 +1,6 @@ //! Memory-mapped I/O (MMIO) functions. -use rkyv::{Portable, api::high::HighValidator, bytecheck::CheckBytes, rancor::Error}; +use rkyv::{Portable, api::high::HighValidator, bytecheck::CheckBytes, rancor::Failure}; use core::slice::from_raw_parts; @@ -24,7 +24,7 @@ pub fn read_slice<'a>() -> &'a [u8] { pub fn read<'a, T>() -> &'a T where - T: Portable + for<'c> CheckBytes>, + T: Portable + for<'c> CheckBytes>, { - rkyv::access::(read_slice()).unwrap() + rkyv::access::(read_slice()).unwrap() } From c00a5cbb6a3b4584acc24c90420b90d6f2b33455 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 16:05:57 +0800 Subject: [PATCH 16/64] Expect --- ceno_rt/src/mmio.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceno_rt/src/mmio.rs b/ceno_rt/src/mmio.rs index 49598f780..aaaca16d2 100644 --- a/ceno_rt/src/mmio.rs +++ b/ceno_rt/src/mmio.rs @@ -26,5 +26,5 @@ pub fn read<'a, T>() -> &'a T where T: Portable + for<'c> CheckBytes>, { - rkyv::access::(read_slice()).unwrap() + rkyv::access::(read_slice()).expect("Deserialised access failed.") } From 2b2a48c672076ccc034222cbc788c8b647620fdf Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 16:33:34 +0800 Subject: [PATCH 17/64] Simpler --- ceno_rt/src/allocator.rs | 21 ++++++++++----------- ceno_rt/src/lib.rs | 15 +++------------ ceno_rt/src/mmio.rs | 18 +++++++++--------- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/ceno_rt/src/allocator.rs b/ceno_rt/src/allocator.rs index c5202f1de..da6fc5b53 100644 --- a/ceno_rt/src/allocator.rs +++ b/ceno_rt/src/allocator.rs @@ -4,7 +4,7 @@ use core::alloc::{GlobalAlloc, Layout}; struct SimpleAllocator { - next_alloc: usize, + next_alloc: *mut u8, } unsafe impl GlobalAlloc for SimpleAllocator { @@ -14,16 +14,14 @@ unsafe impl GlobalAlloc for SimpleAllocator { let align = layout.align(); // `Layout` contract forbids making a `Layout` with align=0, or align not power of 2. - // So we can safely use subtraction and a mask to ensure alignment without worrying about UB. - let offset = heap_pos & (align - 1); - if offset != 0 { - heap_pos = heap_pos.strict_add(align.strict_sub(offset)); - } + core::hint::assert_unchecked(align.is_power_of_two()); + core::hint::assert_unchecked(align != 0); + heap_pos = heap_pos.map_addr(|a| a.next_multiple_of(align)); - let ptr = heap_pos as *mut u8; + let ptr = heap_pos; // Panic on overflow. We don't want to wrap around, and overwrite stack etc. // (We could also return a null pointer, but only malicious programs would ever hit this.) - heap_pos = heap_pos.strict_add(layout.size()); + heap_pos = heap_pos.add(layout.size()); HEAP.next_alloc = heap_pos; ptr @@ -41,9 +39,10 @@ unsafe impl GlobalAlloc for SimpleAllocator { // We initialize `next_alloc` to 0xFFFF_FFFF to indicate that the heap has not been initialized. // The value is chosen to make any premature allocation fail. static mut HEAP: SimpleAllocator = SimpleAllocator { - next_alloc: 0xFFFF_FFFF, + next_alloc: &raw mut _sheap, }; -pub unsafe fn init_heap() { - HEAP.next_alloc = core::ptr::from_ref::(&crate::_sheap).cast::() as usize; +extern "C" { + // The address of this variable is the start of the heap (growing upwards). + static mut _sheap: u8; } diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 51856a2e1..c27f02145 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -82,23 +82,14 @@ macro_rules! entry { /// _start_rust is called by the assembly entry point and it calls the Rust main(). #[no_mangle] unsafe extern "C" fn _start_rust() -> ! { - allocator::init_heap(); - mmio::init_hints(); - - { - extern "C" { - fn bespoke_entrypoint(); - } - bespoke_entrypoint(); + extern "C" { + fn bespoke_entrypoint(); } + bespoke_entrypoint(); halt(0) } extern "C" { // The address of this variable is the start of the stack (growing downwards). static _stack_start: u8; - // The address of this variable is the start of the heap (growing upwards). - static _sheap: u8; - // The address of this variable is the start of the hints ROM. - static _hints_start: u8; } diff --git a/ceno_rt/src/mmio.rs b/ceno_rt/src/mmio.rs index aaaca16d2..769cd820b 100644 --- a/ceno_rt/src/mmio.rs +++ b/ceno_rt/src/mmio.rs @@ -4,21 +4,21 @@ use rkyv::{Portable, api::high::HighValidator, bytecheck::CheckBytes, rancor::Fa use core::slice::from_raw_parts; -use crate::_hints_start; - -static mut NEXT_HINT_LEN_AT: usize = 0x4000_0000; - -pub unsafe fn init_hints() { - NEXT_HINT_LEN_AT = core::ptr::from_ref::(&_hints_start).cast::() as usize; +extern "C" { + // The address of this variable is the start of the hints ROM. + static _hints_start: u8; } +static mut NEXT_HINT_LEN_AT: *const u8 = unsafe { &_hints_start }; +// next_alloc: &raw crate::_hints_start; +// unsafe { core::ptr::from_ref::(&_hints_start).cast::() }; + pub fn read_slice<'a>() -> &'a [u8] { unsafe { let len: u32 = core::ptr::read(NEXT_HINT_LEN_AT as *const u32); - NEXT_HINT_LEN_AT += 4; + NEXT_HINT_LEN_AT = NEXT_HINT_LEN_AT.wrapping_add(4); - let start: *const u8 = core::ptr::from_ref::(&crate::_hints_start).cast::(); - &from_raw_parts(start, 1 << 30)[..len as usize] + &from_raw_parts(&_hints_start, 1 << 30)[..len as usize] } } From 1ba96925b0516006a28774fda916ef529081ce81 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 16:42:38 +0800 Subject: [PATCH 18/64] Simpler --- ceno_rt/src/allocator.rs | 24 +++++++++++------------- ceno_rt/src/mmio.rs | 4 +--- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/ceno_rt/src/allocator.rs b/ceno_rt/src/allocator.rs index da6fc5b53..fc7064822 100644 --- a/ceno_rt/src/allocator.rs +++ b/ceno_rt/src/allocator.rs @@ -7,6 +7,16 @@ struct SimpleAllocator { next_alloc: *mut u8, } +extern "C" { + // The address of this variable is the start of the heap (growing upwards). + static mut _sheap: u8; +} + +#[global_allocator] +static mut HEAP: SimpleAllocator = SimpleAllocator { + next_alloc: &raw mut _sheap, +}; + unsafe impl GlobalAlloc for SimpleAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { // SAFETY: Single threaded, so nothing else can touch this while we're working. @@ -16,7 +26,7 @@ unsafe impl GlobalAlloc for SimpleAllocator { // `Layout` contract forbids making a `Layout` with align=0, or align not power of 2. core::hint::assert_unchecked(align.is_power_of_two()); core::hint::assert_unchecked(align != 0); - heap_pos = heap_pos.map_addr(|a| a.next_multiple_of(align)); + heap_pos = heap_pos.add(heap_pos.align_offset(align)); let ptr = heap_pos; // Panic on overflow. We don't want to wrap around, and overwrite stack etc. @@ -34,15 +44,3 @@ unsafe impl GlobalAlloc for SimpleAllocator { /// Never deallocate. unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {} } - -#[global_allocator] -// We initialize `next_alloc` to 0xFFFF_FFFF to indicate that the heap has not been initialized. -// The value is chosen to make any premature allocation fail. -static mut HEAP: SimpleAllocator = SimpleAllocator { - next_alloc: &raw mut _sheap, -}; - -extern "C" { - // The address of this variable is the start of the heap (growing upwards). - static mut _sheap: u8; -} diff --git a/ceno_rt/src/mmio.rs b/ceno_rt/src/mmio.rs index 769cd820b..af38d6d35 100644 --- a/ceno_rt/src/mmio.rs +++ b/ceno_rt/src/mmio.rs @@ -9,9 +9,7 @@ extern "C" { static _hints_start: u8; } -static mut NEXT_HINT_LEN_AT: *const u8 = unsafe { &_hints_start }; -// next_alloc: &raw crate::_hints_start; -// unsafe { core::ptr::from_ref::(&_hints_start).cast::() }; +static mut NEXT_HINT_LEN_AT: *const u8 = &raw const _hints_start; pub fn read_slice<'a>() -> &'a [u8] { unsafe { From 73b477900b7888f77e84e31621049c60b5a8b82a Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 16:47:08 +0800 Subject: [PATCH 19/64] Simplify guest's memory allocator Extracted from https://github.com/scroll-tech/ceno/pull/631 --- ceno_rt/src/allocator.rs | 35 ++++++++++++++++------------------- ceno_rt/src/lib.rs | 11 +++-------- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/ceno_rt/src/allocator.rs b/ceno_rt/src/allocator.rs index c5202f1de..fc7064822 100644 --- a/ceno_rt/src/allocator.rs +++ b/ceno_rt/src/allocator.rs @@ -4,9 +4,19 @@ use core::alloc::{GlobalAlloc, Layout}; struct SimpleAllocator { - next_alloc: usize, + next_alloc: *mut u8, } +extern "C" { + // The address of this variable is the start of the heap (growing upwards). + static mut _sheap: u8; +} + +#[global_allocator] +static mut HEAP: SimpleAllocator = SimpleAllocator { + next_alloc: &raw mut _sheap, +}; + unsafe impl GlobalAlloc for SimpleAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { // SAFETY: Single threaded, so nothing else can touch this while we're working. @@ -14,16 +24,14 @@ unsafe impl GlobalAlloc for SimpleAllocator { let align = layout.align(); // `Layout` contract forbids making a `Layout` with align=0, or align not power of 2. - // So we can safely use subtraction and a mask to ensure alignment without worrying about UB. - let offset = heap_pos & (align - 1); - if offset != 0 { - heap_pos = heap_pos.strict_add(align.strict_sub(offset)); - } + core::hint::assert_unchecked(align.is_power_of_two()); + core::hint::assert_unchecked(align != 0); + heap_pos = heap_pos.add(heap_pos.align_offset(align)); - let ptr = heap_pos as *mut u8; + let ptr = heap_pos; // Panic on overflow. We don't want to wrap around, and overwrite stack etc. // (We could also return a null pointer, but only malicious programs would ever hit this.) - heap_pos = heap_pos.strict_add(layout.size()); + heap_pos = heap_pos.add(layout.size()); HEAP.next_alloc = heap_pos; ptr @@ -36,14 +44,3 @@ unsafe impl GlobalAlloc for SimpleAllocator { /// Never deallocate. unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {} } - -#[global_allocator] -// We initialize `next_alloc` to 0xFFFF_FFFF to indicate that the heap has not been initialized. -// The value is chosen to make any premature allocation fail. -static mut HEAP: SimpleAllocator = SimpleAllocator { - next_alloc: 0xFFFF_FFFF, -}; - -pub unsafe fn init_heap() { - HEAP.next_alloc = core::ptr::from_ref::(&crate::_sheap).cast::() as usize; -} diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 8de456c41..4c488b920 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -79,19 +79,14 @@ macro_rules! entry { /// _start_rust is called by the assembly entry point and it calls the Rust main(). #[no_mangle] unsafe extern "C" fn _start_rust() -> ! { - allocator::init_heap(); - { - extern "C" { - fn bespoke_entrypoint(); - } - bespoke_entrypoint(); + extern "C" { + fn bespoke_entrypoint(); } + bespoke_entrypoint(); halt(0) } extern "C" { // The address of this variable is the start of the stack (growing downwards). static _stack_start: u8; - // The address of this variable is the start of the heap (growing upwards). - static _sheap: u8; } From 35bacc32e0a18c80b7930ac0aefdd2f3c288c968 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 16:49:44 +0800 Subject: [PATCH 20/64] Explain --- ceno_rt/src/allocator.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ceno_rt/src/allocator.rs b/ceno_rt/src/allocator.rs index fc7064822..5d26d7188 100644 --- a/ceno_rt/src/allocator.rs +++ b/ceno_rt/src/allocator.rs @@ -8,7 +8,9 @@ struct SimpleAllocator { } extern "C" { - // The address of this variable is the start of the heap (growing upwards). + /// The address of this variable is the start of the heap (growing upwards). + /// + /// It is defined in the linker script. static mut _sheap: u8; } From c27881fa1a3c18fe0dc605df499ab573aceebb2e Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 16:51:36 +0800 Subject: [PATCH 21/64] Explain --- ceno_rt/src/allocator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceno_rt/src/allocator.rs b/ceno_rt/src/allocator.rs index 5d26d7188..232bbc313 100644 --- a/ceno_rt/src/allocator.rs +++ b/ceno_rt/src/allocator.rs @@ -31,7 +31,7 @@ unsafe impl GlobalAlloc for SimpleAllocator { heap_pos = heap_pos.add(heap_pos.align_offset(align)); let ptr = heap_pos; - // Panic on overflow. We don't want to wrap around, and overwrite stack etc. + // We don't want to wrap around, and overwrite stack etc. // (We could also return a null pointer, but only malicious programs would ever hit this.) heap_pos = heap_pos.add(layout.size()); From ef10c0ad86ef0029fc58db13c518068ff7f6f84f Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 16:52:10 +0800 Subject: [PATCH 22/64] Minimise diff --- ceno_rt/src/allocator.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ceno_rt/src/allocator.rs b/ceno_rt/src/allocator.rs index 232bbc313..91c697b7f 100644 --- a/ceno_rt/src/allocator.rs +++ b/ceno_rt/src/allocator.rs @@ -7,18 +7,6 @@ struct SimpleAllocator { next_alloc: *mut u8, } -extern "C" { - /// The address of this variable is the start of the heap (growing upwards). - /// - /// It is defined in the linker script. - static mut _sheap: u8; -} - -#[global_allocator] -static mut HEAP: SimpleAllocator = SimpleAllocator { - next_alloc: &raw mut _sheap, -}; - unsafe impl GlobalAlloc for SimpleAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { // SAFETY: Single threaded, so nothing else can touch this while we're working. @@ -46,3 +34,15 @@ unsafe impl GlobalAlloc for SimpleAllocator { /// Never deallocate. unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {} } + +extern "C" { + /// The address of this variable is the start of the heap (growing upwards). + /// + /// It is defined in the linker script. + static mut _sheap: u8; +} + +#[global_allocator] +static mut HEAP: SimpleAllocator = SimpleAllocator { + next_alloc: &raw mut _sheap, +}; From e46fd0598a118a68f7f41fa56a73e9b8f9c16675 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 16:52:50 +0800 Subject: [PATCH 23/64] Fix --- ceno_rt/src/allocator.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/ceno_rt/src/allocator.rs b/ceno_rt/src/allocator.rs index fc7064822..91c697b7f 100644 --- a/ceno_rt/src/allocator.rs +++ b/ceno_rt/src/allocator.rs @@ -7,16 +7,6 @@ struct SimpleAllocator { next_alloc: *mut u8, } -extern "C" { - // The address of this variable is the start of the heap (growing upwards). - static mut _sheap: u8; -} - -#[global_allocator] -static mut HEAP: SimpleAllocator = SimpleAllocator { - next_alloc: &raw mut _sheap, -}; - unsafe impl GlobalAlloc for SimpleAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { // SAFETY: Single threaded, so nothing else can touch this while we're working. @@ -29,7 +19,7 @@ unsafe impl GlobalAlloc for SimpleAllocator { heap_pos = heap_pos.add(heap_pos.align_offset(align)); let ptr = heap_pos; - // Panic on overflow. We don't want to wrap around, and overwrite stack etc. + // We don't want to wrap around, and overwrite stack etc. // (We could also return a null pointer, but only malicious programs would ever hit this.) heap_pos = heap_pos.add(layout.size()); @@ -44,3 +34,15 @@ unsafe impl GlobalAlloc for SimpleAllocator { /// Never deallocate. unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {} } + +extern "C" { + /// The address of this variable is the start of the heap (growing upwards). + /// + /// It is defined in the linker script. + static mut _sheap: u8; +} + +#[global_allocator] +static mut HEAP: SimpleAllocator = SimpleAllocator { + next_alloc: &raw mut _sheap, +}; From fecac1afc0498ebb3cdc0ea3e98135a6a330307f Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 16:55:54 +0800 Subject: [PATCH 24/64] Minimise diff --- ceno_rt/src/allocator.rs | 33 +++++++++++++++++---------------- ceno_rt/src/lib.rs | 11 ++++++++--- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/ceno_rt/src/allocator.rs b/ceno_rt/src/allocator.rs index 91c697b7f..c5202f1de 100644 --- a/ceno_rt/src/allocator.rs +++ b/ceno_rt/src/allocator.rs @@ -4,7 +4,7 @@ use core::alloc::{GlobalAlloc, Layout}; struct SimpleAllocator { - next_alloc: *mut u8, + next_alloc: usize, } unsafe impl GlobalAlloc for SimpleAllocator { @@ -14,14 +14,16 @@ unsafe impl GlobalAlloc for SimpleAllocator { let align = layout.align(); // `Layout` contract forbids making a `Layout` with align=0, or align not power of 2. - core::hint::assert_unchecked(align.is_power_of_two()); - core::hint::assert_unchecked(align != 0); - heap_pos = heap_pos.add(heap_pos.align_offset(align)); - - let ptr = heap_pos; - // We don't want to wrap around, and overwrite stack etc. + // So we can safely use subtraction and a mask to ensure alignment without worrying about UB. + let offset = heap_pos & (align - 1); + if offset != 0 { + heap_pos = heap_pos.strict_add(align.strict_sub(offset)); + } + + let ptr = heap_pos as *mut u8; + // Panic on overflow. We don't want to wrap around, and overwrite stack etc. // (We could also return a null pointer, but only malicious programs would ever hit this.) - heap_pos = heap_pos.add(layout.size()); + heap_pos = heap_pos.strict_add(layout.size()); HEAP.next_alloc = heap_pos; ptr @@ -35,14 +37,13 @@ unsafe impl GlobalAlloc for SimpleAllocator { unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {} } -extern "C" { - /// The address of this variable is the start of the heap (growing upwards). - /// - /// It is defined in the linker script. - static mut _sheap: u8; -} - #[global_allocator] +// We initialize `next_alloc` to 0xFFFF_FFFF to indicate that the heap has not been initialized. +// The value is chosen to make any premature allocation fail. static mut HEAP: SimpleAllocator = SimpleAllocator { - next_alloc: &raw mut _sheap, + next_alloc: 0xFFFF_FFFF, }; + +pub unsafe fn init_heap() { + HEAP.next_alloc = core::ptr::from_ref::(&crate::_sheap).cast::() as usize; +} diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index c27f02145..4440cbcc0 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -82,14 +82,19 @@ macro_rules! entry { /// _start_rust is called by the assembly entry point and it calls the Rust main(). #[no_mangle] unsafe extern "C" fn _start_rust() -> ! { - extern "C" { - fn bespoke_entrypoint(); + allocator::init_heap(); + { + extern "C" { + fn bespoke_entrypoint(); + } + bespoke_entrypoint(); } - bespoke_entrypoint(); halt(0) } extern "C" { // The address of this variable is the start of the stack (growing downwards). static _stack_start: u8; + // The address of this variable is the start of the heap (growing upwards). + static _sheap: u8; } From 42da8da2b75c98a413f8a0da47488b3091141c55 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 16:56:37 +0800 Subject: [PATCH 25/64] Doc --- ceno_rt/src/mmio.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ceno_rt/src/mmio.rs b/ceno_rt/src/mmio.rs index af38d6d35..3994750b5 100644 --- a/ceno_rt/src/mmio.rs +++ b/ceno_rt/src/mmio.rs @@ -5,7 +5,9 @@ use rkyv::{Portable, api::high::HighValidator, bytecheck::CheckBytes, rancor::Fa use core::slice::from_raw_parts; extern "C" { - // The address of this variable is the start of the hints ROM. + /// The address of this variable is the start of the hints ROM. + /// + /// It is defined in the linker script. static _hints_start: u8; } From 8fdb803140992e38e5a90c0f7764f65d6741cfed Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 17:13:07 +0800 Subject: [PATCH 26/64] Don't hardcode the size of the region --- ceno_rt/ceno_link.x | 1 + ceno_rt/src/mmio.rs | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ceno_rt/ceno_link.x b/ceno_rt/ceno_link.x index 2747b8109..4c2e9e257 100644 --- a/ceno_rt/ceno_link.x +++ b/ceno_rt/ceno_link.x @@ -1,6 +1,7 @@ _stack_start = ORIGIN(REGION_STACK) + LENGTH(REGION_STACK); _hints_start = ORIGIN(REGION_HINTS); +_hints_end = ORIGIN(REGION_HINTS) + LENGTH(REGION_HINTS); SECTIONS { diff --git a/ceno_rt/src/mmio.rs b/ceno_rt/src/mmio.rs index 3994750b5..3a7a44557 100644 --- a/ceno_rt/src/mmio.rs +++ b/ceno_rt/src/mmio.rs @@ -9,6 +9,7 @@ extern "C" { /// /// It is defined in the linker script. static _hints_start: u8; + static _hints_end: u8; } static mut NEXT_HINT_LEN_AT: *const u8 = &raw const _hints_start; @@ -18,7 +19,12 @@ pub fn read_slice<'a>() -> &'a [u8] { let len: u32 = core::ptr::read(NEXT_HINT_LEN_AT as *const u32); NEXT_HINT_LEN_AT = NEXT_HINT_LEN_AT.wrapping_add(4); - &from_raw_parts(&_hints_start, 1 << 30)[..len as usize] + let hints_region = { + let total_length = (&raw const _hints_end).offset_from(&_hints_start) as usize; + from_raw_parts(&_hints_start, total_length) + }; + + &hints_region[..len as usize] } } From 1e16d24f9417b2e41e4e18f500e27b2491725009 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 17:13:37 +0800 Subject: [PATCH 27/64] Simpler --- ceno_rt/src/mmio.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceno_rt/src/mmio.rs b/ceno_rt/src/mmio.rs index 3a7a44557..1f7bddc44 100644 --- a/ceno_rt/src/mmio.rs +++ b/ceno_rt/src/mmio.rs @@ -17,7 +17,7 @@ static mut NEXT_HINT_LEN_AT: *const u8 = &raw const _hints_start; pub fn read_slice<'a>() -> &'a [u8] { unsafe { let len: u32 = core::ptr::read(NEXT_HINT_LEN_AT as *const u32); - NEXT_HINT_LEN_AT = NEXT_HINT_LEN_AT.wrapping_add(4); + NEXT_HINT_LEN_AT = NEXT_HINT_LEN_AT.add(4); let hints_region = { let total_length = (&raw const _hints_end).offset_from(&_hints_start) as usize; From 2f78d46a78cb1a939e8ffaec04e84a401e2d416e Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 17:24:01 +0800 Subject: [PATCH 28/64] Deduplicate --- ceno_emul/src/host_utils.rs | 32 ++++++++++++++++++++++++++++ ceno_emul/src/lib.rs | 2 ++ ceno_emul/tests/test_elf.rs | 42 +++++++------------------------------ ceno_host/src/lib.rs | 33 +---------------------------- 4 files changed, 42 insertions(+), 67 deletions(-) create mode 100644 ceno_emul/src/host_utils.rs diff --git a/ceno_emul/src/host_utils.rs b/ceno_emul/src/host_utils.rs new file mode 100644 index 000000000..d29a5d825 --- /dev/null +++ b/ceno_emul/src/host_utils.rs @@ -0,0 +1,32 @@ +use crate::{ByteAddr, EmuContext, VMState}; + +const WORD_SIZE: usize = 4; +const INFO_OUT_ADDR: u32 = 0xC000_0000; + +pub fn read_all_messages(state: &VMState) -> Vec { + let mut all_messages = Vec::new(); + let mut word_offset = 0; + loop { + let out = read_message(state, word_offset); + if out.is_empty() { + break; + } + word_offset += out.len().div_ceil(WORD_SIZE) as u32 + 1; + all_messages.push(out); + } + all_messages +} + +fn read_message(state: &VMState, word_offset: u32) -> String { + let out_addr = ByteAddr(INFO_OUT_ADDR).waddr() + word_offset; + let byte_len = state.peek_memory(out_addr); + let word_len_up = byte_len.div_ceil(4); + + let mut info_out = Vec::with_capacity(WORD_SIZE * word_len_up as usize); + for i in 1..1 + word_len_up { + let value = state.peek_memory(out_addr + i); + info_out.extend_from_slice(&value.to_le_bytes()); + } + info_out.truncate(byte_len as usize); + String::from_utf8_lossy(&info_out).to_string() +} diff --git a/ceno_emul/src/lib.rs b/ceno_emul/src/lib.rs index c734b1794..fd18f4c61 100644 --- a/ceno_emul/src/lib.rs +++ b/ceno_emul/src/lib.rs @@ -19,3 +19,5 @@ pub use elf::Program; mod rv32im_encode; pub use rv32im_encode::encode_rv32; + +pub mod host_utils; diff --git a/ceno_emul/tests/test_elf.rs b/ceno_emul/tests/test_elf.rs index ca7a14c1c..bc144d401 100644 --- a/ceno_emul/tests/test_elf.rs +++ b/ceno_emul/tests/test_elf.rs @@ -1,5 +1,8 @@ use anyhow::Result; -use ceno_emul::{ByteAddr, CENO_PLATFORM, EmuContext, InsnKind, Platform, StepRecord, VMState}; +use ceno_emul::{ + CENO_PLATFORM, EmuContext, InsnKind, Platform, StepRecord, VMState, + host_utils::read_all_messages, +}; #[test] fn test_ceno_rt_mini() -> Result<()> { @@ -65,10 +68,10 @@ fn test_ceno_rt_io() -> Result<()> { let all_messages = read_all_messages(&state); for msg in &all_messages { - print!("{}", String::from_utf8_lossy(msg)); + print!("{msg}"); } - assert_eq!(&all_messages[0], "📜📜📜 Hello, World!\n".as_bytes()); - assert_eq!(&all_messages[1], "🌏🌍🌎\n".as_bytes()); + assert_eq!(&all_messages[0], "📜📜📜 Hello, World!\n"); + assert_eq!(&all_messages[1], "🌏🌍🌎\n"); Ok(()) } @@ -77,34 +80,3 @@ fn run(state: &mut VMState) -> Result> { eprintln!("Emulator ran for {} steps.", steps.len()); Ok(steps) } - -const WORD_SIZE: usize = 4; -const INFO_OUT_ADDR: u32 = 0xC000_0000; - -fn read_all_messages(state: &VMState) -> Vec> { - let mut all_messages = Vec::new(); - let mut word_offset = 0; - loop { - let out = read_message(state, word_offset); - if out.is_empty() { - break; - } - word_offset += out.len().div_ceil(WORD_SIZE) as u32 + 1; - all_messages.push(out); - } - all_messages -} - -fn read_message(state: &VMState, word_offset: u32) -> Vec { - let out_addr = ByteAddr(INFO_OUT_ADDR).waddr() + word_offset; - let byte_len = state.peek_memory(out_addr); - let word_len_up = byte_len.div_ceil(4); - - let mut info_out = Vec::with_capacity(WORD_SIZE * word_len_up as usize); - for i in 1..1 + word_len_up { - let value = state.peek_memory(out_addr + i); - info_out.extend_from_slice(&value.to_le_bytes()); - } - info_out.truncate(byte_len as usize); - info_out -} diff --git a/ceno_host/src/lib.rs b/ceno_host/src/lib.rs index 134225015..92e52ccca 100644 --- a/ceno_host/src/lib.rs +++ b/ceno_host/src/lib.rs @@ -1,7 +1,7 @@ use std::iter::zip; use anyhow::Result; -use ceno_emul::{ByteAddr, EmuContext, IterAddresses, Platform, VMState}; +use ceno_emul::{IterAddresses, Platform, VMState, host_utils::read_all_messages}; use itertools::izip; use rkyv::{ Serialize, api::high::HighSerializer, rancor::Error, ser::allocator::ArenaHandle, to_bytes, @@ -72,37 +72,6 @@ impl CenoStdin { // TODO(Matthias): much of this is copied from `test_elf.rs` in Ceno. These are generally useful // functions, so we should make them available for both crates, instead of copy-and-pasting here. -const WORD_SIZE: usize = 4; -const INFO_OUT_ADDR: u32 = 0xC000_0000; - -fn read_all_messages(state: &VMState) -> Vec { - let mut all_messages = Vec::new(); - let mut word_offset = 0; - loop { - let out = read_message(state, word_offset); - if out.is_empty() { - break; - } - word_offset += out.len().div_ceil(WORD_SIZE) as u32 + 1; - all_messages.push(out); - } - all_messages -} - -fn read_message(state: &VMState, word_offset: u32) -> String { - let out_addr = ByteAddr(INFO_OUT_ADDR).waddr() + word_offset; - let byte_len = state.peek_memory(out_addr); - let word_len_up = byte_len.div_ceil(4); - - let mut info_out = Vec::with_capacity(WORD_SIZE * word_len_up as usize); - for i in 1..1 + word_len_up { - let value = state.peek_memory(out_addr + i); - info_out.extend_from_slice(&value.to_le_bytes()); - } - info_out.truncate(byte_len as usize); - String::from_utf8_lossy(&info_out).to_string() -} - // TODO(Matthias): also return exit code (if any) pub fn run(platform: Platform, elf: &[u8], hints: &CenoStdin) -> Vec { let hints: Vec = hints.finalise(); From 39280bf5cb5df3f1e821c9072e8f149b08a912e9 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 17:24:34 +0800 Subject: [PATCH 29/64] Done TODO --- ceno_host/src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ceno_host/src/lib.rs b/ceno_host/src/lib.rs index 92e52ccca..8dbdab0c3 100644 --- a/ceno_host/src/lib.rs +++ b/ceno_host/src/lib.rs @@ -69,10 +69,6 @@ impl CenoStdin { } } -// TODO(Matthias): much of this is copied from `test_elf.rs` in Ceno. These are generally useful -// functions, so we should make them available for both crates, instead of copy-and-pasting here. - -// TODO(Matthias): also return exit code (if any) pub fn run(platform: Platform, elf: &[u8], hints: &CenoStdin) -> Vec { let hints: Vec = hints.finalise(); From 31805f68635f7310b2c889782b5ab24d8179e156 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 17:46:43 +0800 Subject: [PATCH 30/64] Strings --- ceno_emul/src/addr.rs | 13 +++++++++++++ ceno_emul/src/host_utils.rs | 18 ++++++++++-------- ceno_emul/src/lib.rs | 1 + 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/ceno_emul/src/addr.rs b/ceno_emul/src/addr.rs index ff8f7c0a0..d9069e1a0 100644 --- a/ceno_emul/src/addr.rs +++ b/ceno_emul/src/addr.rs @@ -16,6 +16,7 @@ use std::{ fmt, + iter::Step, ops::{self, Range}, }; @@ -106,6 +107,18 @@ impl WordAddr { } } +impl Step for WordAddr { + fn steps_between(start: &Self, end: &Self) -> (usize, Option) { + u32::steps_between(&start.0, &end.0) + } + fn forward_checked(start: Self, count: usize) -> Option { + u32::forward_checked(start.0, count).map(Self) + } + fn backward_checked(start: Self, count: usize) -> Option { + u32::backward_checked(start.0, count).map(Self) + } +} + impl fmt::Debug for ByteAddr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "0x{:08x}", self.0) diff --git a/ceno_emul/src/host_utils.rs b/ceno_emul/src/host_utils.rs index d29a5d825..73c501b1b 100644 --- a/ceno_emul/src/host_utils.rs +++ b/ceno_emul/src/host_utils.rs @@ -19,14 +19,16 @@ pub fn read_all_messages(state: &VMState) -> Vec { fn read_message(state: &VMState, word_offset: u32) -> String { let out_addr = ByteAddr(INFO_OUT_ADDR).waddr() + word_offset; - let byte_len = state.peek_memory(out_addr); + let byte_len = state.peek_memory(out_addr) as usize; let word_len_up = byte_len.div_ceil(4); - let mut info_out = Vec::with_capacity(WORD_SIZE * word_len_up as usize); - for i in 1..1 + word_len_up { - let value = state.peek_memory(out_addr + i); - info_out.extend_from_slice(&value.to_le_bytes()); - } - info_out.truncate(byte_len as usize); - String::from_utf8_lossy(&info_out).to_string() + String::from_utf8_lossy( + &(out_addr + 1_usize..) + .take(word_len_up) + .map(|memory| state.peek_memory(memory)) + .flat_map(u32::to_le_bytes) + .take(byte_len) + .collect::>(), + ) + .to_string() } diff --git a/ceno_emul/src/lib.rs b/ceno_emul/src/lib.rs index fd18f4c61..c18fc232b 100644 --- a/ceno_emul/src/lib.rs +++ b/ceno_emul/src/lib.rs @@ -1,4 +1,5 @@ #![deny(clippy::cargo)] +#![feature(step_trait)] mod addr; pub use addr::*; From 5091d3dd7896e7ceb25f0d655eeb3afc0b513fbd Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 17:56:06 +0800 Subject: [PATCH 31/64] Just for fun --- ceno_emul/src/host_utils.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ceno_emul/src/host_utils.rs b/ceno_emul/src/host_utils.rs index 73c501b1b..ec2f1685d 100644 --- a/ceno_emul/src/host_utils.rs +++ b/ceno_emul/src/host_utils.rs @@ -1,24 +1,24 @@ -use crate::{ByteAddr, EmuContext, VMState}; +use std::iter::from_fn; + +use crate::{ByteAddr, EmuContext, VMState, WordAddr}; const WORD_SIZE: usize = 4; -const INFO_OUT_ADDR: u32 = 0xC000_0000; +const INFO_OUT_ADDR: WordAddr = ByteAddr(0xC000_0000).waddr(); pub fn read_all_messages(state: &VMState) -> Vec { - let mut all_messages = Vec::new(); - let mut word_offset = 0; - loop { - let out = read_message(state, word_offset); - if out.is_empty() { - break; + let mut offset: WordAddr = WordAddr::from(0); + from_fn(move || match read_message(state, offset) { + out if out.is_empty() => None, + out => { + offset += out.len().div_ceil(WORD_SIZE) as u32 + 1; + Some(out) } - word_offset += out.len().div_ceil(WORD_SIZE) as u32 + 1; - all_messages.push(out); - } - all_messages + }) + .collect() } -fn read_message(state: &VMState, word_offset: u32) -> String { - let out_addr = ByteAddr(INFO_OUT_ADDR).waddr() + word_offset; +fn read_message(state: &VMState, offset: WordAddr) -> String { + let out_addr = INFO_OUT_ADDR + offset; let byte_len = state.peek_memory(out_addr) as usize; let word_len_up = byte_len.div_ceil(4); From 10de0da6d94644fac0e4c6c7382e4bbb55c32d5b Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Tue, 10 Dec 2024 17:57:01 +0800 Subject: [PATCH 32/64] Simpler --- ceno_emul/src/host_utils.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ceno_emul/src/host_utils.rs b/ceno_emul/src/host_utils.rs index ec2f1685d..460635f83 100644 --- a/ceno_emul/src/host_utils.rs +++ b/ceno_emul/src/host_utils.rs @@ -20,11 +20,9 @@ pub fn read_all_messages(state: &VMState) -> Vec { fn read_message(state: &VMState, offset: WordAddr) -> String { let out_addr = INFO_OUT_ADDR + offset; let byte_len = state.peek_memory(out_addr) as usize; - let word_len_up = byte_len.div_ceil(4); String::from_utf8_lossy( &(out_addr + 1_usize..) - .take(word_len_up) .map(|memory| state.peek_memory(memory)) .flat_map(u32::to_le_bytes) .take(byte_len) From 3c095fdeff57e9d5dbe2840c5d14efde1273babb Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Thu, 12 Dec 2024 13:06:27 +0800 Subject: [PATCH 33/64] Snapshot --- ceno_emul/src/rv32im.rs | 19 ++++++++++++++----- ceno_rt/Cargo.toml | 3 +++ ceno_rt/src/lib.rs | 34 ++++++++++++++++++++++------------ 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/ceno_emul/src/rv32im.rs b/ceno_emul/src/rv32im.rs index 52fac1508..56c50e16e 100644 --- a/ceno_emul/src/rv32im.rs +++ b/ceno_emul/src/rv32im.rs @@ -15,9 +15,10 @@ // limitations under the License. use anyhow::{Result, anyhow}; +use core::fmt; use itertools::enumerate; use num_derive::ToPrimitive; -use std::sync::OnceLock; +use std::{fmt::Debug, sync::OnceLock}; use strum_macros::{Display, EnumIter}; use super::addr::{ByteAddr, RegIdx, WORD_SIZE, Word, WordAddr}; @@ -85,11 +86,19 @@ pub struct Emulator { table: &'static FastDecodeTable, } +pub struct Hex(u32); + +impl Debug for Hex { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "0x{:08x}", self.0) + } +} + #[derive(Debug)] pub enum TrapCause { InstructionAddressMisaligned, InstructionAccessFault, - IllegalInstruction(u32), + IllegalInstruction(Hex), Breakpoint, LoadAddressMisaligned, LoadAccessFault(ByteAddr), @@ -489,7 +498,7 @@ impl Emulator { // ELF. if word & 0x03 != 0x03 { // Opcode must end in 0b11 in RV32IM. - ctx.trap(TrapCause::IllegalInstruction(word))?; + ctx.trap(TrapCause::IllegalInstruction(Hex(word)))?; return Err(anyhow!( "Fatal: illegal instruction at pc={:?}: 0x{:08x}", pc, @@ -508,7 +517,7 @@ impl Emulator { InsnCategory::Load => self.step_load(ctx, insn.kind, &decoded)?, InsnCategory::Store => self.step_store(ctx, insn.kind, &decoded)?, InsnCategory::System => self.step_system(ctx, insn.kind, &decoded)?, - InsnCategory::Invalid => ctx.trap(TrapCause::IllegalInstruction(word))?, + InsnCategory::Invalid => ctx.trap(TrapCause::IllegalInstruction(Hex(word)))?, } { ctx.on_normal_end(&decoded); }; @@ -783,7 +792,7 @@ impl Emulator { InsnKind::EANY => match decoded.rs2 { 0 => ctx.ecall(), 1 => ctx.trap(TrapCause::Breakpoint), - _ => ctx.trap(TrapCause::IllegalInstruction(decoded.insn)), + _ => ctx.trap(TrapCause::IllegalInstruction(Hex(decoded.insn))), }, _ => unreachable!(), } diff --git a/ceno_rt/Cargo.toml b/ceno_rt/Cargo.toml index 709e80f51..7c13f1543 100644 --- a/ceno_rt/Cargo.toml +++ b/ceno_rt/Cargo.toml @@ -15,3 +15,6 @@ rkyv = { version = "0.8", default-features = false, features = [ "alloc", "bytecheck", ] } + +[features] +std = [] diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 4440cbcc0..a83ca5513 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -2,7 +2,7 @@ #![feature(strict_overflow_ops)] #![no_std] -use core::arch::{asm, global_asm}; +use core::arch::global_asm; mod allocator; @@ -15,7 +15,7 @@ pub use io::info_out; mod params; pub use params::*; -#[cfg(not(test))] +#[cfg(not(any(test, feature = "std")))] mod panic_handler { use core::panic::PanicInfo; @@ -26,20 +26,30 @@ mod panic_handler { } } -#[allow(asm_sub_register)] +// #[allow(asm_sub_register)] +// #[cfg(target_os = "mozakvm")] pub fn halt(exit_code: u32) -> ! { + #[cfg(target_arch = "riscv32")] unsafe { - asm!( - // Set the first argument. - "mv a0, {}", - // Set the ecall code HALT. - "li t0, 0x0", - in(reg) exit_code, + core::arch::asm!( + "ecall", + in ("a0") exit_code, + in ("t0") 0, ); - riscv::asm::ecall(); + unreachable!(); + // asm!( + // // Set the first argument. + // "add a0, x0, {}", + // // Set the ecall code HALT. + // "addi t0, x0, 0x0", + // in(reg) exit_code, + // ); + // riscv::asm::ecall(); } - #[allow(clippy::empty_loop)] - loop {} + #[cfg(not(target_arch = "riscv32"))] + panic!("Halt is not implemented for this target: {}", exit_code); + // #[allow(clippy::empty_loop)] + // loop {} } global_asm!( From f512d90c95477532b5ba975f20d4ae51eaa36b31 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Thu, 12 Dec 2024 13:27:50 +0800 Subject: [PATCH 34/64] Remove crap --- ceno_rt/src/lib.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index a83ca5513..2644804b2 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -37,14 +37,6 @@ pub fn halt(exit_code: u32) -> ! { in ("t0") 0, ); unreachable!(); - // asm!( - // // Set the first argument. - // "add a0, x0, {}", - // // Set the ecall code HALT. - // "addi t0, x0, 0x0", - // in(reg) exit_code, - // ); - // riscv::asm::ecall(); } #[cfg(not(target_arch = "riscv32"))] panic!("Halt is not implemented for this target: {}", exit_code); From 31e307704128c02d372ac7570306706a37ea9dc6 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Thu, 12 Dec 2024 13:28:38 +0800 Subject: [PATCH 35/64] Don't need riscv crate --- Cargo.lock | 43 --------------------------------------- ceno_rt/Cargo.toml | 1 - examples/Cargo.lock | 49 --------------------------------------------- 3 files changed, 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8036d166..f19eee9ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -282,7 +282,6 @@ dependencies = [ name = "ceno_rt" version = "0.1.0" dependencies = [ - "riscv", "rkyv", ] @@ -503,12 +502,6 @@ dependencies = [ "itertools 0.10.5", ] -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - [[package]] name = "crossbeam-channel" version = "0.5.13" @@ -637,12 +630,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" -[[package]] -name = "embedded-hal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" - [[package]] name = "encode_unicode" version = "1.0.0" @@ -1712,36 +1699,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "riscv" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea8ff73d3720bdd0a97925f0bf79ad2744b6da8ff36be3840c48ac81191d7a7" -dependencies = [ - "critical-section", - "embedded-hal", - "paste", - "riscv-macros", - "riscv-pac", -] - -[[package]] -name = "riscv-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f265be5d634272320a7de94cea15c22a3bfdd4eb42eb43edc528415f066a1f25" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.90", -] - -[[package]] -name = "riscv-pac" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" - [[package]] name = "rkyv" version = "0.8.9" diff --git a/ceno_rt/Cargo.toml b/ceno_rt/Cargo.toml index 7c13f1543..fa6519f47 100644 --- a/ceno_rt/Cargo.toml +++ b/ceno_rt/Cargo.toml @@ -10,7 +10,6 @@ repository.workspace = true version.workspace = true [dependencies] -riscv = "0.12" rkyv = { version = "0.8", default-features = false, features = [ "alloc", "bytecheck", diff --git a/examples/Cargo.lock b/examples/Cargo.lock index a7532992d..6b4123507 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -29,22 +29,9 @@ dependencies = [ name = "ceno_rt" version = "0.1.0" dependencies = [ - "riscv", "rkyv", ] -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "embedded-hal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" - [[package]] name = "examples" version = "0.1.0" @@ -78,12 +65,6 @@ dependencies = [ "syn", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "proc-macro2" version = "1.0.88" @@ -140,36 +121,6 @@ dependencies = [ "bytecheck", ] -[[package]] -name = "riscv" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea8ff73d3720bdd0a97925f0bf79ad2744b6da8ff36be3840c48ac81191d7a7" -dependencies = [ - "critical-section", - "embedded-hal", - "paste", - "riscv-macros", - "riscv-pac", -] - -[[package]] -name = "riscv-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f265be5d634272320a7de94cea15c22a3bfdd4eb42eb43edc528415f066a1f25" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "riscv-pac" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" - [[package]] name = "rkyv" version = "0.8.9" From e19ddbb3c6acef71b2e6188c2435bb54db789d7d Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Thu, 12 Dec 2024 13:29:16 +0800 Subject: [PATCH 36/64] Unimplemented --- ceno_emul/src/disassemble/mod.rs | 1 + ceno_rt/src/lib.rs | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ceno_emul/src/disassemble/mod.rs b/ceno_emul/src/disassemble/mod.rs index bfb4723c8..8b4d2219b 100644 --- a/ceno_emul/src/disassemble/mod.rs +++ b/ceno_emul/src/disassemble/mod.rs @@ -344,6 +344,7 @@ impl InstructionProcessor for InstructionTranspiler { } fn process_csrrw(&mut self, _: ITypeCSR) -> Self::InstructionResult { + eprintln!("csrrw: {:#010x}", self.word); Instruction::unimp(self.word) } diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 13c2c2d58..8d75573e0 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -39,9 +39,7 @@ pub fn halt(exit_code: u32) -> ! { unreachable!(); } #[cfg(not(target_arch = "riscv32"))] - panic!("Halt is not implemented for this target: {}", exit_code); - // #[allow(clippy::empty_loop)] - // loop {} + unimplemented!("Halt is not implemented for this target: {}", exit_code); } global_asm!( From 572cd22185cad5ee5b442bbfa52656a8f85c0922 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Thu, 12 Dec 2024 16:48:55 +0800 Subject: [PATCH 37/64] Rename as suggested --- ceno_emul/src/rv32im.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ceno_emul/src/rv32im.rs b/ceno_emul/src/rv32im.rs index d6f5675c7..09be3221c 100644 --- a/ceno_emul/src/rv32im.rs +++ b/ceno_emul/src/rv32im.rs @@ -264,22 +264,22 @@ impl Emulator { &self, ctx: &mut M, kind: InsnKind, - decoded: &Instruction, + insn: &Instruction, ) -> Result { use InsnKind::*; let pc = ctx.get_pc(); let mut new_pc = pc + WORD_SIZE; - let imm_i = decoded.imm as u32; + let imm_i = insn.imm as u32; let out = match kind { // Instructions that do not read rs1 nor rs2. JAL => { - new_pc = pc.wrapping_add(decoded.imm as u32); + new_pc = pc.wrapping_add(insn.imm as u32); (pc + WORD_SIZE).0 } _ => { // Instructions that read rs1 but not rs2. - let rs1 = ctx.load_register(decoded.rs1)?; + let rs1 = ctx.load_register(insn.rs1)?; match kind { ADDI => rs1.wrapping_add(imm_i), @@ -310,7 +310,7 @@ impl Emulator { _ => { // Instructions that use rs1 and rs2. - let rs2 = ctx.load_register(decoded.rs2)?; + let rs2 = ctx.load_register(insn.rs2)?; match kind { ADD => rs1.wrapping_add(rs2), @@ -380,7 +380,7 @@ impl Emulator { if !new_pc.is_aligned() { return ctx.trap(TrapCause::InstructionAddressMisaligned); } - ctx.store_register(decoded.rd_internal() as usize, out)?; + ctx.store_register(insn.rd_internal() as usize, out)?; ctx.set_pc(new_pc); Ok(true) } From 02f082807f605aaec3894b0330d440da9f64400d Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Thu, 12 Dec 2024 16:50:55 +0800 Subject: [PATCH 38/64] Move as suggested --- ceno_emul/src/lib.rs | 7 +++---- ceno_emul/src/rv32im.rs | 28 ++++++++++++++++++++++++++++ ceno_emul/src/rv32im_encode.rs | 29 ----------------------------- 3 files changed, 31 insertions(+), 33 deletions(-) delete mode 100644 ceno_emul/src/rv32im_encode.rs diff --git a/ceno_emul/src/lib.rs b/ceno_emul/src/lib.rs index d98a06f1c..1a855006e 100644 --- a/ceno_emul/src/lib.rs +++ b/ceno_emul/src/lib.rs @@ -12,12 +12,11 @@ mod vm_state; pub use vm_state::VMState; mod rv32im; -pub use rv32im::{EmuContext, InsnCategory, InsnFormat, InsnKind, Instruction}; +pub use rv32im::{ + EmuContext, InsnCategory, InsnFormat, InsnKind, Instruction, encode_rv32, encode_rv32u, +}; mod elf; pub use elf::Program; -mod rv32im_encode; -pub use rv32im_encode::{encode_rv32, encode_rv32u}; - pub mod disassemble; diff --git a/ceno_emul/src/rv32im.rs b/ceno_emul/src/rv32im.rs index 09be3221c..3a90cbbb1 100644 --- a/ceno_emul/src/rv32im.rs +++ b/ceno_emul/src/rv32im.rs @@ -20,6 +20,34 @@ use strum_macros::{Display, EnumIter}; use super::addr::{ByteAddr, RegIdx, WORD_SIZE, Word, WordAddr}; +/// Convenience function to create an `Instruction` with the given fields. +/// +/// Pass 0 for unused fields. +pub const fn encode_rv32(kind: InsnKind, rs1: u32, rs2: u32, rd: u32, imm: i32) -> Instruction { + Instruction { + kind, + rs1: rs1 as usize, + rs2: rs2 as usize, + rd: rd as usize, + imm, + raw: 0, + } +} + +/// Convenience function to create an `Instruction` with the given fields. +/// +/// Pass 0 for unused fields. +pub const fn encode_rv32u(kind: InsnKind, rs1: u32, rs2: u32, rd: u32, imm: u32) -> Instruction { + Instruction { + kind, + rs1: rs1 as usize, + rs2: rs2 as usize, + rd: rd as usize, + imm: imm as i32, + raw: 0, + } +} + pub trait EmuContext { // Handle environment call fn ecall(&mut self) -> Result; diff --git a/ceno_emul/src/rv32im_encode.rs b/ceno_emul/src/rv32im_encode.rs deleted file mode 100644 index 8aeaf2b9c..000000000 --- a/ceno_emul/src/rv32im_encode.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{InsnKind, Instruction}; - -/// Convenience function to create an `Instruction` with the given fields. -/// -/// Pass 0 for unused fields. -pub const fn encode_rv32(kind: InsnKind, rs1: u32, rs2: u32, rd: u32, imm: i32) -> Instruction { - Instruction { - kind, - rs1: rs1 as usize, - rs2: rs2 as usize, - rd: rd as usize, - imm, - raw: 0, - } -} - -/// Convenience function to create an `Instruction` with the given fields. -/// -/// Pass 0 for unused fields. -pub const fn encode_rv32u(kind: InsnKind, rs1: u32, rs2: u32, rd: u32, imm: u32) -> Instruction { - Instruction { - kind, - rs1: rs1 as usize, - rs2: rs2 as usize, - rd: rd as usize, - imm: imm as i32, - raw: 0, - } -} From 0349fa799db0c31cf88ff02ee008d3cea3fa6567 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Thu, 12 Dec 2024 16:51:45 +0800 Subject: [PATCH 39/64] formatting --- ceno_emul/src/disassemble/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceno_emul/src/disassemble/mod.rs b/ceno_emul/src/disassemble/mod.rs index bfb4723c8..45cc70e4e 100644 --- a/ceno_emul/src/disassemble/mod.rs +++ b/ceno_emul/src/disassemble/mod.rs @@ -1,3 +1,4 @@ +use crate::rv32im::Instruction; use itertools::izip; use rrs_lib::{ InstructionProcessor, @@ -5,7 +6,6 @@ use rrs_lib::{ process_instruction, }; -use crate::rv32im::Instruction; type InsnKind = crate::rv32im::InsnKind; /// A transpiler that converts the 32-bit encoded instructions into instructions. From 887a1435877e58dca7f92ecc05681a6ead21d1a8 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Thu, 12 Dec 2024 16:52:20 +0800 Subject: [PATCH 40/64] Instructions --- ceno_emul/src/disassemble/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ceno_emul/src/disassemble/mod.rs b/ceno_emul/src/disassemble/mod.rs index 45cc70e4e..70d74045c 100644 --- a/ceno_emul/src/disassemble/mod.rs +++ b/ceno_emul/src/disassemble/mod.rs @@ -1,4 +1,4 @@ -use crate::rv32im::Instruction; +use crate::rv32im::{InsnKind, Instruction}; use itertools::izip; use rrs_lib::{ InstructionProcessor, @@ -6,8 +6,6 @@ use rrs_lib::{ process_instruction, }; -type InsnKind = crate::rv32im::InsnKind; - /// A transpiler that converts the 32-bit encoded instructions into instructions. pub(crate) struct InstructionTranspiler { pc: u32, From df7f713bbcf2f0f5de0cb4c90d040a74a49af1c0 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Thu, 12 Dec 2024 16:54:37 +0800 Subject: [PATCH 41/64] Panic less --- ceno_emul/src/disassemble/mod.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/ceno_emul/src/disassemble/mod.rs b/ceno_emul/src/disassemble/mod.rs index 70d74045c..42b157977 100644 --- a/ceno_emul/src/disassemble/mod.rs +++ b/ceno_emul/src/disassemble/mod.rs @@ -363,16 +363,12 @@ impl InstructionProcessor for InstructionTranspiler { } /// Transpile the [`Instruction`]s from the 32-bit encoded instructions. -/// -/// # Panics -/// -/// This function will return an error if the [`Instruction`] cannot be processed. #[must_use] pub fn transpile(base: u32, instructions_u32: &[u32]) -> Vec { let mut instructions = Vec::new(); for (pc, &word) in izip!(enumerate(base, 4), instructions_u32) { - let instruction = - process_instruction(&mut InstructionTranspiler { pc, word }, word).unwrap(); + let instruction = process_instruction(&mut InstructionTranspiler { pc, word }, word) + .unwrap_or_else(|| Instruction::unimp(word)); instructions.push(instruction); } instructions From a4d7a3483fae76b98453cae56b01a795d17679ee Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Thu, 12 Dec 2024 16:56:00 +0800 Subject: [PATCH 42/64] Remove push --- ceno_emul/src/disassemble/mod.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ceno_emul/src/disassemble/mod.rs b/ceno_emul/src/disassemble/mod.rs index 42b157977..ca6161000 100644 --- a/ceno_emul/src/disassemble/mod.rs +++ b/ceno_emul/src/disassemble/mod.rs @@ -365,13 +365,12 @@ impl InstructionProcessor for InstructionTranspiler { /// Transpile the [`Instruction`]s from the 32-bit encoded instructions. #[must_use] pub fn transpile(base: u32, instructions_u32: &[u32]) -> Vec { - let mut instructions = Vec::new(); - for (pc, &word) in izip!(enumerate(base, 4), instructions_u32) { - let instruction = process_instruction(&mut InstructionTranspiler { pc, word }, word) - .unwrap_or_else(|| Instruction::unimp(word)); - instructions.push(instruction); - } - instructions + izip!(enumerate(base, 4), instructions_u32) + .map(|(pc, &word)| { + process_instruction(&mut InstructionTranspiler { pc, word }, word) + .unwrap_or(Instruction::unimp(word)) + }) + .collect() } fn enumerate(start: u32, step: u32) -> impl Iterator { From b1cfa65dbd8eed969380d22b1d84f577c98dedc7 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Thu, 12 Dec 2024 17:03:07 +0800 Subject: [PATCH 43/64] Remove Emulator --- ceno_emul/src/rv32im.rs | 496 ++++++++++++++++++-------------------- ceno_emul/src/vm_state.rs | 9 +- 2 files changed, 236 insertions(+), 269 deletions(-) diff --git a/ceno_emul/src/rv32im.rs b/ceno_emul/src/rv32im.rs index 3a90cbbb1..e63fa761b 100644 --- a/ceno_emul/src/rv32im.rs +++ b/ceno_emul/src/rv32im.rs @@ -96,10 +96,6 @@ pub trait EmuContext { } } -/// An implementation of the basic ISA (RV32IM), that is instruction decoding and functional units. -#[derive(Default)] -pub struct Emulator {} - #[derive(Debug)] pub enum TrapCause { InstructionAddressMisaligned, @@ -261,297 +257,269 @@ impl Instruction { } } -impl Emulator { - pub fn step(&self, ctx: &mut C) -> Result<()> { - let pc = ctx.get_pc(); - - let Some(insn) = ctx.fetch(pc.waddr()) else { - ctx.trap(TrapCause::InstructionAccessFault)?; - return Err(anyhow!( - "Fatal: could not fetch instruction at pc={pc:?}, ELF does not have instructions there." - )); - }; - - tracing::trace!("pc: {:x}, kind: {:?}", pc.0, insn.kind); - - if match InsnCategory::from(insn.kind) { - InsnCategory::Compute => self.step_compute(ctx, insn.kind, &insn)?, - InsnCategory::Branch => self.step_branch(ctx, insn.kind, &insn)?, - InsnCategory::Load => self.step_load(ctx, insn.kind, &insn)?, - InsnCategory::Store => self.step_store(ctx, insn.kind, &insn)?, - InsnCategory::System => self.step_system(ctx, insn.kind, &insn)?, - InsnCategory::Invalid => ctx.trap(TrapCause::IllegalInstruction(insn.raw))?, - } { - ctx.on_normal_end(&insn); - }; - - Ok(()) - } +pub fn step(ctx: &mut C) -> Result<()> { + let pc = ctx.get_pc(); + + let Some(insn) = ctx.fetch(pc.waddr()) else { + ctx.trap(TrapCause::InstructionAccessFault)?; + return Err(anyhow!( + "Fatal: could not fetch instruction at pc={pc:?}, ELF does not have instructions there." + )); + }; + + tracing::trace!("pc: {:x}, kind: {:?}", pc.0, insn.kind); + + if match InsnCategory::from(insn.kind) { + InsnCategory::Compute => step_compute(ctx, insn.kind, &insn)?, + InsnCategory::Branch => step_branch(ctx, insn.kind, &insn)?, + InsnCategory::Load => step_load(ctx, insn.kind, &insn)?, + InsnCategory::Store => step_store(ctx, insn.kind, &insn)?, + InsnCategory::System => step_system(ctx, insn.kind, &insn)?, + InsnCategory::Invalid => ctx.trap(TrapCause::IllegalInstruction(insn.raw))?, + } { + ctx.on_normal_end(&insn); + }; + + Ok(()) +} - fn step_compute( - &self, - ctx: &mut M, - kind: InsnKind, - insn: &Instruction, - ) -> Result { - use InsnKind::*; - - let pc = ctx.get_pc(); - let mut new_pc = pc + WORD_SIZE; - let imm_i = insn.imm as u32; - let out = match kind { - // Instructions that do not read rs1 nor rs2. - JAL => { - new_pc = pc.wrapping_add(insn.imm as u32); - (pc + WORD_SIZE).0 - } - _ => { - // Instructions that read rs1 but not rs2. - let rs1 = ctx.load_register(insn.rs1)?; - - match kind { - ADDI => rs1.wrapping_add(imm_i), - XORI => rs1 ^ imm_i, - ORI => rs1 | imm_i, - ANDI => rs1 & imm_i, - SLLI => rs1 << (imm_i & 0x1f), - SRLI => rs1 >> (imm_i & 0x1f), - SRAI => ((rs1 as i32) >> (imm_i & 0x1f)) as u32, - SLTI => { - if (rs1 as i32) < (imm_i as i32) { - 1 - } else { - 0 - } - } - SLTIU => { - if rs1 < imm_i { - 1 - } else { - 0 - } +fn step_compute(ctx: &mut M, kind: InsnKind, insn: &Instruction) -> Result { + use super::InsnKind::*; + + let pc = ctx.get_pc(); + let mut new_pc = pc + WORD_SIZE; + let imm_i = insn.imm as u32; + let out = match kind { + // Instructions that do not read rs1 nor rs2. + JAL => { + new_pc = pc.wrapping_add(insn.imm as u32); + (pc + WORD_SIZE).0 + } + _ => { + // Instructions that read rs1 but not rs2. + let rs1 = ctx.load_register(insn.rs1)?; + + match kind { + ADDI => rs1.wrapping_add(imm_i), + XORI => rs1 ^ imm_i, + ORI => rs1 | imm_i, + ANDI => rs1 & imm_i, + SLLI => rs1 << (imm_i & 0x1f), + SRLI => rs1 >> (imm_i & 0x1f), + SRAI => ((rs1 as i32) >> (imm_i & 0x1f)) as u32, + SLTI => { + if (rs1 as i32) < (imm_i as i32) { + 1 + } else { + 0 } - JALR => { - new_pc = ByteAddr(rs1.wrapping_add(imm_i) & !1); - (pc + WORD_SIZE).0 + } + SLTIU => { + if rs1 < imm_i { + 1 + } else { + 0 } + } + JALR => { + new_pc = ByteAddr(rs1.wrapping_add(imm_i) & !1); + (pc + WORD_SIZE).0 + } - _ => { - // Instructions that use rs1 and rs2. - let rs2 = ctx.load_register(insn.rs2)?; - - match kind { - ADD => rs1.wrapping_add(rs2), - SUB => rs1.wrapping_sub(rs2), - XOR => rs1 ^ rs2, - OR => rs1 | rs2, - AND => rs1 & rs2, - SLL => rs1 << (rs2 & 0x1f), - SRL => rs1 >> (rs2 & 0x1f), - SRA => ((rs1 as i32) >> (rs2 & 0x1f)) as u32, - SLT => { - if (rs1 as i32) < (rs2 as i32) { - 1 - } else { - 0 - } + _ => { + // Instructions that use rs1 and rs2. + let rs2 = ctx.load_register(insn.rs2)?; + + match kind { + ADD => rs1.wrapping_add(rs2), + SUB => rs1.wrapping_sub(rs2), + XOR => rs1 ^ rs2, + OR => rs1 | rs2, + AND => rs1 & rs2, + SLL => rs1 << (rs2 & 0x1f), + SRL => rs1 >> (rs2 & 0x1f), + SRA => ((rs1 as i32) >> (rs2 & 0x1f)) as u32, + SLT => { + if (rs1 as i32) < (rs2 as i32) { + 1 + } else { + 0 } - SLTU => { - if rs1 < rs2 { - 1 - } else { - 0 - } - } - MUL => rs1.wrapping_mul(rs2), - MULH => { - (sign_extend_u32(rs1).wrapping_mul(sign_extend_u32(rs2)) >> 32) - as u32 + } + SLTU => { + if rs1 < rs2 { + 1 + } else { + 0 } - MULHSU => (sign_extend_u32(rs1).wrapping_mul(rs2 as i64) >> 32) as u32, - MULHU => (((rs1 as u64).wrapping_mul(rs2 as u64)) >> 32) as u32, - DIV => { - if rs2 == 0 { - u32::MAX - } else { - ((rs1 as i32).wrapping_div(rs2 as i32)) as u32 - } + } + MUL => rs1.wrapping_mul(rs2), + MULH => { + (sign_extend_u32(rs1).wrapping_mul(sign_extend_u32(rs2)) >> 32) as u32 + } + MULHSU => (sign_extend_u32(rs1).wrapping_mul(rs2 as i64) >> 32) as u32, + MULHU => (((rs1 as u64).wrapping_mul(rs2 as u64)) >> 32) as u32, + DIV => { + if rs2 == 0 { + u32::MAX + } else { + ((rs1 as i32).wrapping_div(rs2 as i32)) as u32 } - DIVU => { - if rs2 == 0 { - u32::MAX - } else { - rs1 / rs2 - } + } + DIVU => { + if rs2 == 0 { + u32::MAX + } else { + rs1 / rs2 } - REM => { - if rs2 == 0 { - rs1 - } else { - ((rs1 as i32).wrapping_rem(rs2 as i32)) as u32 - } + } + REM => { + if rs2 == 0 { + rs1 + } else { + ((rs1 as i32).wrapping_rem(rs2 as i32)) as u32 } - REMU => { - if rs2 == 0 { - rs1 - } else { - rs1 % rs2 - } + } + REMU => { + if rs2 == 0 { + rs1 + } else { + rs1 % rs2 } - - _ => unreachable!("Illegal compute instruction: {:?}", kind), } + + _ => unreachable!("Illegal compute instruction: {:?}", kind), } } } - }; - if !new_pc.is_aligned() { - return ctx.trap(TrapCause::InstructionAddressMisaligned); } - ctx.store_register(insn.rd_internal() as usize, out)?; - ctx.set_pc(new_pc); - Ok(true) + }; + if !new_pc.is_aligned() { + return ctx.trap(TrapCause::InstructionAddressMisaligned); } + ctx.store_register(insn.rd_internal() as usize, out)?; + ctx.set_pc(new_pc); + Ok(true) +} - fn step_branch( - &self, - ctx: &mut M, - kind: InsnKind, - decoded: &Instruction, - ) -> Result { - use InsnKind::*; - - let pc = ctx.get_pc(); - let rs1 = ctx.load_register(decoded.rs1 as RegIdx)?; - let rs2 = ctx.load_register(decoded.rs2 as RegIdx)?; - - let taken = match kind { - BEQ => rs1 == rs2, - BNE => rs1 != rs2, - BLT => (rs1 as i32) < (rs2 as i32), - BGE => (rs1 as i32) >= (rs2 as i32), - BLTU => rs1 < rs2, - BGEU => rs1 >= rs2, - _ => unreachable!("Illegal branch instruction: {:?}", kind), - }; - - let new_pc = if taken { - pc.wrapping_add(decoded.imm as u32) - } else { - pc + WORD_SIZE - }; - - if !new_pc.is_aligned() { - return ctx.trap(TrapCause::InstructionAddressMisaligned); - } - ctx.set_pc(new_pc); - Ok(true) +fn step_branch(ctx: &mut M, kind: InsnKind, decoded: &Instruction) -> Result { + use super::InsnKind::*; + + let pc = ctx.get_pc(); + let rs1 = ctx.load_register(decoded.rs1 as RegIdx)?; + let rs2 = ctx.load_register(decoded.rs2 as RegIdx)?; + + let taken = match kind { + BEQ => rs1 == rs2, + BNE => rs1 != rs2, + BLT => (rs1 as i32) < (rs2 as i32), + BGE => (rs1 as i32) >= (rs2 as i32), + BLTU => rs1 < rs2, + BGEU => rs1 >= rs2, + _ => unreachable!("Illegal branch instruction: {:?}", kind), + }; + + let new_pc = if taken { + pc.wrapping_add(decoded.imm as u32) + } else { + pc + WORD_SIZE + }; + + if !new_pc.is_aligned() { + return ctx.trap(TrapCause::InstructionAddressMisaligned); } + ctx.set_pc(new_pc); + Ok(true) +} - fn step_load( - &self, - ctx: &mut M, - kind: InsnKind, - decoded: &Instruction, - ) -> Result { - let rs1 = ctx.load_register(decoded.rs1)?; - // LOAD instructions do not read rs2. - let addr = ByteAddr(rs1.wrapping_add_signed(decoded.imm)); - if !ctx.check_data_load(addr) { - return ctx.trap(TrapCause::LoadAccessFault(addr)); +fn step_load(ctx: &mut M, kind: InsnKind, decoded: &Instruction) -> Result { + let rs1 = ctx.load_register(decoded.rs1)?; + // LOAD instructions do not read rs2. + let addr = ByteAddr(rs1.wrapping_add_signed(decoded.imm)); + if !ctx.check_data_load(addr) { + return ctx.trap(TrapCause::LoadAccessFault(addr)); + } + let data = ctx.load_memory(addr.waddr())?; + let shift = 8 * (addr.0 & 3); + let out = match kind { + InsnKind::LB => { + let mut out = (data >> shift) & 0xff; + if out & 0x80 != 0 { + out |= 0xffffff00; + } + out } - let data = ctx.load_memory(addr.waddr())?; - let shift = 8 * (addr.0 & 3); - let out = match kind { - InsnKind::LB => { - let mut out = (data >> shift) & 0xff; - if out & 0x80 != 0 { - out |= 0xffffff00; - } - out + InsnKind::LH => { + if addr.0 & 0x01 != 0 { + return ctx.trap(TrapCause::LoadAddressMisaligned); } - InsnKind::LH => { - if addr.0 & 0x01 != 0 { - return ctx.trap(TrapCause::LoadAddressMisaligned); - } - let mut out = (data >> shift) & 0xffff; - if out & 0x8000 != 0 { - out |= 0xffff0000; - } - out + let mut out = (data >> shift) & 0xffff; + if out & 0x8000 != 0 { + out |= 0xffff0000; } - InsnKind::LW => { - if addr.0 & 0x03 != 0 { - return ctx.trap(TrapCause::LoadAddressMisaligned); - } - data + out + } + InsnKind::LW => { + if addr.0 & 0x03 != 0 { + return ctx.trap(TrapCause::LoadAddressMisaligned); } - InsnKind::LBU => (data >> shift) & 0xff, - InsnKind::LHU => { - if addr.0 & 0x01 != 0 { - return ctx.trap(TrapCause::LoadAddressMisaligned); - } - (data >> shift) & 0xffff + data + } + InsnKind::LBU => (data >> shift) & 0xff, + InsnKind::LHU => { + if addr.0 & 0x01 != 0 { + return ctx.trap(TrapCause::LoadAddressMisaligned); } - _ => unreachable!(), - }; - ctx.store_register(decoded.rd_internal() as usize, out)?; - ctx.set_pc(ctx.get_pc() + WORD_SIZE); - Ok(true) - } + (data >> shift) & 0xffff + } + _ => unreachable!(), + }; + ctx.store_register(decoded.rd_internal() as usize, out)?; + ctx.set_pc(ctx.get_pc() + WORD_SIZE); + Ok(true) +} - fn step_store( - &self, - ctx: &mut M, - kind: InsnKind, - decoded: &Instruction, - ) -> Result { - let rs1 = ctx.load_register(decoded.rs1)?; - let rs2 = ctx.load_register(decoded.rs2)?; - let addr = ByteAddr(rs1.wrapping_add(decoded.imm as u32)); - let shift = 8 * (addr.0 & 3); - if !ctx.check_data_store(addr) { - tracing::error!("mstore: addr={:x?},rs1={:x}", addr, rs1); - return ctx.trap(TrapCause::StoreAccessFault); +fn step_store(ctx: &mut M, kind: InsnKind, decoded: &Instruction) -> Result { + let rs1 = ctx.load_register(decoded.rs1)?; + let rs2 = ctx.load_register(decoded.rs2)?; + let addr = ByteAddr(rs1.wrapping_add(decoded.imm as u32)); + let shift = 8 * (addr.0 & 3); + if !ctx.check_data_store(addr) { + tracing::error!("mstore: addr={:x?},rs1={:x}", addr, rs1); + return ctx.trap(TrapCause::StoreAccessFault); + } + let mut data = ctx.peek_memory(addr.waddr()); + match kind { + InsnKind::SB => { + data ^= data & (0xff << shift); + data |= (rs2 & 0xff) << shift; } - let mut data = ctx.peek_memory(addr.waddr()); - match kind { - InsnKind::SB => { - data ^= data & (0xff << shift); - data |= (rs2 & 0xff) << shift; - } - InsnKind::SH => { - if addr.0 & 0x01 != 0 { - tracing::debug!("Misaligned SH"); - return ctx.trap(TrapCause::StoreAddressMisaligned(addr)); - } - data ^= data & (0xffff << shift); - data |= (rs2 & 0xffff) << shift; + InsnKind::SH => { + if addr.0 & 0x01 != 0 { + tracing::debug!("Misaligned SH"); + return ctx.trap(TrapCause::StoreAddressMisaligned(addr)); } - InsnKind::SW => { - if addr.0 & 0x03 != 0 { - tracing::debug!("Misaligned SW"); - return ctx.trap(TrapCause::StoreAddressMisaligned(addr)); - } - data = rs2; + data ^= data & (0xffff << shift); + data |= (rs2 & 0xffff) << shift; + } + InsnKind::SW => { + if addr.0 & 0x03 != 0 { + tracing::debug!("Misaligned SW"); + return ctx.trap(TrapCause::StoreAddressMisaligned(addr)); } - _ => unreachable!(), + data = rs2; } - ctx.store_memory(addr.waddr(), data)?; - ctx.set_pc(ctx.get_pc() + WORD_SIZE); - Ok(true) + _ => unreachable!(), } + ctx.store_memory(addr.waddr(), data)?; + ctx.set_pc(ctx.get_pc() + WORD_SIZE); + Ok(true) +} - fn step_system( - &self, - ctx: &mut M, - kind: InsnKind, - decoded: &Instruction, - ) -> Result { - match kind { - InsnKind::ECALL => ctx.ecall(), - _ => ctx.trap(TrapCause::IllegalInstruction(decoded.raw)), - } +fn step_system(ctx: &mut M, kind: InsnKind, decoded: &Instruction) -> Result { + match kind { + InsnKind::ECALL => ctx.ecall(), + _ => ctx.trap(TrapCause::IllegalInstruction(decoded.raw)), } } diff --git a/ceno_emul/src/vm_state.rs b/ceno_emul/src/vm_state.rs index b6367f3ab..838779979 100644 --- a/ceno_emul/src/vm_state.rs +++ b/ceno_emul/src/vm_state.rs @@ -5,7 +5,7 @@ use crate::{ PC_STEP_SIZE, Program, WORD_SIZE, addr::{ByteAddr, RegIdx, Word, WordAddr}, platform::Platform, - rv32im::{Emulator, Instruction, TrapCause}, + rv32im::{Instruction, TrapCause}, tracer::{Change, StepRecord, Tracer}, }; use anyhow::{Result, anyhow}; @@ -77,18 +77,17 @@ impl VMState { } pub fn iter_until_halt(&mut self) -> impl Iterator> + '_ { - let emu = Emulator::default(); from_fn(move || { if self.halted() { None } else { - Some(self.step(&emu)) + Some(self.step()) } }) } - fn step(&mut self, emu: &Emulator) -> Result { - emu.step(self)?; + fn step(&mut self) -> Result { + crate::rv32im::step(self)?; let step = self.tracer.advance(); if step.is_busy_loop() && !self.halted() { Err(anyhow!("Stuck in loop {}", "{}")) From 1443741ff4376ced62a19e03f7315a8efdfaaf5f Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 13 Dec 2024 03:47:07 +0800 Subject: [PATCH 44/64] Just for stdlib --- Cargo.lock | 5 -- Cargo.toml | 4 +- ceno_emul/Cargo.toml | 2 +- ceno_emul/src/rv32im.rs | 8 +- ceno_emul/tests/test_elf.rs | 146 ++++++++++++++++++------------------ ceno_rt/src/lib.rs | 50 ++++++------ examples-builder/build.rs | 10 +-- 7 files changed, 109 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f19eee9ec..bd79276a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -247,16 +247,11 @@ dependencies = [ "shlex", ] -[[package]] -name = "ceno-examples" -version = "0.1.0" - [[package]] name = "ceno_emul" version = "0.1.0" dependencies = [ "anyhow", - "ceno-examples", "elf", "itertools 0.13.0", "num-derive", diff --git a/Cargo.toml b/Cargo.toml index d35c565e8..4955b1052 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [workspace] -exclude = ["examples"] +exclude = ["examples", "examples-builder"] members = [ "ceno_emul", "ceno_host", "ceno_rt", "ceno_zkvm", - "examples-builder", + # "examples-builder", "mpcs", "multilinear_extensions", "poseidon", diff --git a/ceno_emul/Cargo.toml b/ceno_emul/Cargo.toml index 573419cb1..f8668f40b 100644 --- a/ceno_emul/Cargo.toml +++ b/ceno_emul/Cargo.toml @@ -21,7 +21,7 @@ strum_macros.workspace = true tracing.workspace = true [dev-dependencies] -ceno-examples = { path = "../examples-builder" } +# ceno-examples = { path = "../examples-builder" } [features] default = ["forbid_overflow"] diff --git a/ceno_emul/src/rv32im.rs b/ceno_emul/src/rv32im.rs index 112d72817..f39ab9269 100644 --- a/ceno_emul/src/rv32im.rs +++ b/ceno_emul/src/rv32im.rs @@ -271,11 +271,14 @@ pub fn step(ctx: &mut C) -> Result<()> { let pc = ctx.get_pc(); let Some(insn) = ctx.fetch(pc.waddr()) else { + eprintln!("pc: {:x}", pc.0); + ctx.trap(TrapCause::InstructionAccessFault)?; return Err(anyhow!( "Fatal: could not fetch instruction at pc={pc:?}, ELF does not have instructions there." )); }; + eprintln!("{:x}\t{insn:?}", pc.0); tracing::trace!("pc: {:x}, kind: {:?}", pc.0, insn.kind); @@ -332,7 +335,10 @@ fn step_compute(ctx: &mut M, kind: InsnKind, insn: &Instruction) } } JALR => { - new_pc = ByteAddr(rs1.wrapping_add(imm_i) & !1); + let target_raw = rs1.wrapping_add(imm_i); + let target = rs1.wrapping_add(imm_i) & !1; + eprintln!("JALR rs1: {rs1:x}\timm_i: {imm_i:x}\ttarget_raw: {target_raw:x}\ttarget: {target:x}"); + new_pc = ByteAddr(target); (pc + WORD_SIZE).0 } diff --git a/ceno_emul/tests/test_elf.rs b/ceno_emul/tests/test_elf.rs index b8eb7eff9..0b3700270 100644 --- a/ceno_emul/tests/test_elf.rs +++ b/ceno_emul/tests/test_elf.rs @@ -1,82 +1,82 @@ -use anyhow::Result; -use ceno_emul::{ - CENO_PLATFORM, EmuContext, InsnKind, Platform, StepRecord, VMState, - host_utils::read_all_messages, -}; +// use anyhow::Result; +// use ceno_emul::{ +// CENO_PLATFORM, EmuContext, InsnKind, Platform, StepRecord, VMState, +// host_utils::read_all_messages, +// }; -#[test] -fn test_ceno_rt_mini() -> Result<()> { - let program_elf = ceno_examples::ceno_rt_mini; - let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; - let _steps = run(&mut state)?; - Ok(()) -} +// #[test] +// fn test_ceno_rt_mini() -> Result<()> { +// let program_elf = ceno_examples::ceno_rt_mini; +// let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; +// let _steps = run(&mut state)?; +// Ok(()) +// } -#[test] -fn test_ceno_rt_panic() -> Result<()> { - let program_elf = ceno_examples::ceno_rt_panic; - let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; - let steps = run(&mut state)?; - let last = steps.last().unwrap(); - assert_eq!(last.insn().kind, InsnKind::ECALL); - assert_eq!(last.rs1().unwrap().value, Platform::ecall_halt()); - assert_eq!(last.rs2().unwrap().value, 1); // panic / halt(1) - Ok(()) -} +// #[test] +// fn test_ceno_rt_panic() -> Result<()> { +// let program_elf = ceno_examples::ceno_rt_panic; +// let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; +// let steps = run(&mut state)?; +// let last = steps.last().unwrap(); +// assert_eq!(last.insn().kind, InsnKind::ECALL); +// assert_eq!(last.rs1().unwrap().value, Platform::ecall_halt()); +// assert_eq!(last.rs2().unwrap().value, 1); // panic / halt(1) +// Ok(()) +// } -#[test] -fn test_ceno_rt_mem() -> Result<()> { - let program_elf = ceno_examples::ceno_rt_mem; - let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; - let _steps = run(&mut state)?; +// #[test] +// fn test_ceno_rt_mem() -> Result<()> { +// let program_elf = ceno_examples::ceno_rt_mem; +// let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; +// let _steps = run(&mut state)?; - let value = state.peek_memory(CENO_PLATFORM.ram.start.into()); - assert_eq!(value, 6765, "Expected Fibonacci 20, got {}", value); - Ok(()) -} +// let value = state.peek_memory(CENO_PLATFORM.ram.start.into()); +// assert_eq!(value, 6765, "Expected Fibonacci 20, got {}", value); +// Ok(()) +// } -#[test] -fn test_ceno_rt_alloc() -> Result<()> { - let program_elf = ceno_examples::ceno_rt_alloc; - let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; - let _steps = run(&mut state)?; +// #[test] +// fn test_ceno_rt_alloc() -> Result<()> { +// let program_elf = ceno_examples::ceno_rt_alloc; +// let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; +// let _steps = run(&mut state)?; - // Search for the RAM action of the test program. - let mut found = (false, false); - for &addr in state.tracer().final_accesses().keys() { - if !CENO_PLATFORM.is_ram(addr.into()) { - continue; - } - let value = state.peek_memory(addr); - if value == 0xf00d { - found.0 = true; - } - if value == 0xbeef { - found.1 = true; - } - } - assert!(found.0); - assert!(found.1); - Ok(()) -} +// // Search for the RAM action of the test program. +// let mut found = (false, false); +// for &addr in state.tracer().final_accesses().keys() { +// if !CENO_PLATFORM.is_ram(addr.into()) { +// continue; +// } +// let value = state.peek_memory(addr); +// if value == 0xf00d { +// found.0 = true; +// } +// if value == 0xbeef { +// found.1 = true; +// } +// } +// assert!(found.0); +// assert!(found.1); +// Ok(()) +// } -#[test] -fn test_ceno_rt_io() -> Result<()> { - let program_elf = ceno_examples::ceno_rt_io; - let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; - let _steps = run(&mut state)?; +// #[test] +// fn test_ceno_rt_io() -> Result<()> { +// let program_elf = ceno_examples::ceno_rt_io; +// let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; +// let _steps = run(&mut state)?; - let all_messages = read_all_messages(&state); - for msg in &all_messages { - print!("{msg}"); - } - assert_eq!(&all_messages[0], "📜📜📜 Hello, World!\n"); - assert_eq!(&all_messages[1], "🌏🌍🌎\n"); - Ok(()) -} +// let all_messages = read_all_messages(&state); +// for msg in &all_messages { +// print!("{msg}"); +// } +// assert_eq!(&all_messages[0], "📜📜📜 Hello, World!\n"); +// assert_eq!(&all_messages[1], "🌏🌍🌎\n"); +// Ok(()) +// } -fn run(state: &mut VMState) -> Result> { - let steps = state.iter_until_halt().collect::>>()?; - eprintln!("Emulator ran for {} steps.", steps.len()); - Ok(steps) -} +// fn run(state: &mut VMState) -> Result> { +// let steps = state.iter_until_halt().collect::>>()?; +// eprintln!("Emulator ran for {} steps.", steps.len()); +// Ok(steps) +// } diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 8d75573e0..00b83623e 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -1,6 +1,10 @@ #![deny(clippy::cargo)] #![feature(strict_overflow_ops)] -#![no_std] +#![feature(linkage)] + +#![cfg_attr(feature = "std", feature(panic_always_abort))] +// #![cfg_attr(not(feature = "std"), no_std)] +// #![cfg_attr(feature = "std", feature(restricted_std))] use core::arch::global_asm; @@ -15,6 +19,12 @@ pub use io::info_out; mod params; pub use params::*; +#[no_mangle] +#[linkage = "weak"] +pub extern "C" fn sys_write(_fd: i32, _buf: *const u8, _count: usize) -> isize { + unimplemented!(); +} + #[cfg(not(any(test, feature = "std")))] mod panic_handler { use core::panic::PanicInfo; @@ -26,8 +36,6 @@ mod panic_handler { } } -// #[allow(asm_sub_register)] -// #[cfg(target_os = "mozakvm")] pub fn halt(exit_code: u32) -> ! { #[cfg(target_arch = "riscv32")] unsafe { @@ -60,34 +68,18 @@ _start: mv fp, sp // Call the Rust start function. - jal zero, _start_rust - ", -); + // jal zero, _start_rust + call main -#[macro_export] -macro_rules! entry { - ($path:path) => { - // Type check the given path - const CENO_ENTRY: fn() = $path; - - mod ceno_generated_main { - #[no_mangle] - extern "C" fn bespoke_entrypoint() { - super::CENO_ENTRY(); - } - } - }; -} + // If we return from main, we halt with success: -/// _start_rust is called by the assembly entry point and it calls the Rust main(). -#[no_mangle] -unsafe extern "C" fn _start_rust() -> ! { - extern "C" { - fn bespoke_entrypoint(); - } - bespoke_entrypoint(); - halt(0) -} + // Set the ecall code HALT. + addi t0, x0, 0 + // Set successful exit code, ie 0: + addi a0, x0, 0 + ecall + ", +); extern "C" { // The address of this variable is the start of the stack (growing downwards). diff --git a/examples-builder/build.rs b/examples-builder/build.rs index 7ea9b9628..0f9475e53 100644 --- a/examples-builder/build.rs +++ b/examples-builder/build.rs @@ -9,11 +9,11 @@ use std::{ /// /// Contact Matthias, if your examples get complicated enough to need their own crates, instead of just being one file. const EXAMPLES: &[&str] = &[ - "ceno_rt_alloc", - "ceno_rt_io", - "ceno_rt_mem", - "ceno_rt_mini", - "ceno_rt_panic", + // "ceno_rt_alloc", + // "ceno_rt_io", + // "ceno_rt_mem", + // "ceno_rt_mini", + // "ceno_rt_panic", ]; const CARGO_MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); From ea77dbdae448cd6a8b905847479b4d57ec9796ad Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 13 Dec 2024 15:09:45 +0800 Subject: [PATCH 45/64] Show hint example --- {ceno_emul => ceno_host}/tests/test_elf.rs | 16 ++++++++++++++++ guest/examples/examples/hints.rs | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+) rename {ceno_emul => ceno_host}/tests/test_elf.rs (83%) create mode 100644 guest/examples/examples/hints.rs diff --git a/ceno_emul/tests/test_elf.rs b/ceno_host/tests/test_elf.rs similarity index 83% rename from ceno_emul/tests/test_elf.rs rename to ceno_host/tests/test_elf.rs index b8eb7eff9..17092cbaa 100644 --- a/ceno_emul/tests/test_elf.rs +++ b/ceno_host/tests/test_elf.rs @@ -3,6 +3,8 @@ use ceno_emul::{ CENO_PLATFORM, EmuContext, InsnKind, Platform, StepRecord, VMState, host_utils::read_all_messages, }; +use ceno_host::CenoStdin; +use itertools::enumerate; #[test] fn test_ceno_rt_mini() -> Result<()> { @@ -75,6 +77,20 @@ fn test_ceno_rt_io() -> Result<()> { Ok(()) } +#[test] +fn test_hints() { + let mut hints = CenoStdin::default(); + hints.write(&"This is my hint string.".to_string()).unwrap(); + hints.write(&1997_u32).unwrap(); + hints.write(&1999_u32).unwrap(); + + let all_messages = ceno_host::run(CENO_PLATFORM, ceno_examples::hints, &hints); + for (i, msg) in enumerate(&all_messages) { + println!("{i}: {msg}"); + } + assert_eq!(all_messages[0], "3992003"); +} + fn run(state: &mut VMState) -> Result> { let steps = state.iter_until_halt().collect::>>()?; eprintln!("Emulator ran for {} steps.", steps.len()); diff --git a/guest/examples/examples/hints.rs b/guest/examples/examples/hints.rs new file mode 100644 index 000000000..cf880f19f --- /dev/null +++ b/guest/examples/examples/hints.rs @@ -0,0 +1,20 @@ +#![no_main] +#![no_std] + +extern crate ceno_rt; +use ceno_rt::println; +use core::fmt::Write; +use rkyv::{Archived, string::ArchivedString}; + +ceno_rt::entry!(main); +fn main() { + let msg: &ArchivedString = ceno_rt::read(); + + let a: &Archived = ceno_rt::read(); + let b: &Archived = ceno_rt::read(); + let product: u32 = a * b; + + assert_eq!(product, 3992003); + println!("{product}"); + println!("This message is a hint: {msg}"); +} From c438b432e73dcd64d582ab41be358a9b3663383a Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 13 Dec 2024 15:10:38 +0800 Subject: [PATCH 46/64] Formatting --- Cargo.lock | 2 +- ceno_emul/Cargo.toml | 3 --- ceno_host/Cargo.toml | 1 + examples-builder/build.rs | 1 + guest/Cargo.lock | 1 + guest/examples/Cargo.toml | 4 ++++ 6 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03c1c4f69..90a1a0f4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,7 +256,6 @@ name = "ceno_emul" version = "0.1.0" dependencies = [ "anyhow", - "ceno-examples", "elf", "itertools 0.13.0", "num-derive", @@ -272,6 +271,7 @@ name = "ceno_host" version = "0.1.0" dependencies = [ "anyhow", + "ceno-examples", "ceno_emul", "itertools 0.13.0", "rand", diff --git a/ceno_emul/Cargo.toml b/ceno_emul/Cargo.toml index 573419cb1..c566df0f5 100644 --- a/ceno_emul/Cargo.toml +++ b/ceno_emul/Cargo.toml @@ -20,9 +20,6 @@ strum.workspace = true strum_macros.workspace = true tracing.workspace = true -[dev-dependencies] -ceno-examples = { path = "../examples-builder" } - [features] default = ["forbid_overflow"] forbid_overflow = [] diff --git a/ceno_host/Cargo.toml b/ceno_host/Cargo.toml index e8907cef6..ad2b6e044 100644 --- a/ceno_host/Cargo.toml +++ b/ceno_host/Cargo.toml @@ -16,4 +16,5 @@ itertools.workspace = true rkyv = { version = "0.8.9", default-features = false, features = ["alloc", "bytecheck"] } [dev-dependencies] +ceno-examples = { path = "../examples-builder" } rand.workspace = true diff --git a/examples-builder/build.rs b/examples-builder/build.rs index 0e8d90441..d11c176cf 100644 --- a/examples-builder/build.rs +++ b/examples-builder/build.rs @@ -14,6 +14,7 @@ const EXAMPLES: &[&str] = &[ "ceno_rt_mem", "ceno_rt_mini", "ceno_rt_panic", + "hints", ]; const CARGO_MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); diff --git a/guest/Cargo.lock b/guest/Cargo.lock index 40ea494f1..62c5b6521 100644 --- a/guest/Cargo.lock +++ b/guest/Cargo.lock @@ -50,6 +50,7 @@ name = "examples" version = "0.1.0" dependencies = [ "ceno_rt", + "rkyv", ] [[package]] diff --git a/guest/examples/Cargo.toml b/guest/examples/Cargo.toml index 397e743b1..24b25d313 100644 --- a/guest/examples/Cargo.toml +++ b/guest/examples/Cargo.toml @@ -11,3 +11,7 @@ version.workspace = true [dependencies] ceno_rt = { path = "../ceno_rt" } +rkyv = { version = "0.8", default-features = false, features = [ + "alloc", + "bytecheck", +] } From 9a746bc9d5009b26c6d194577f65df33c0d4d19c Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 13 Dec 2024 15:11:15 +0800 Subject: [PATCH 47/64] Remove pedantic --- ceno_emul/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/ceno_emul/src/lib.rs b/ceno_emul/src/lib.rs index 561fd7bda..ba4195815 100644 --- a/ceno_emul/src/lib.rs +++ b/ceno_emul/src/lib.rs @@ -1,5 +1,4 @@ #![deny(clippy::cargo)] -// #![deny(clippy::pedantic)] #![feature(step_trait)] mod addr; pub use addr::*; From 9d7626865162fb99580df588375f9b85d2db8ca2 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 13 Dec 2024 15:13:48 +0800 Subject: [PATCH 48/64] Use bool as test case --- ceno_host/tests/test_elf.rs | 10 ++++++---- guest/examples/examples/hints.rs | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ceno_host/tests/test_elf.rs b/ceno_host/tests/test_elf.rs index 17092cbaa..ceabe2413 100644 --- a/ceno_host/tests/test_elf.rs +++ b/ceno_host/tests/test_elf.rs @@ -78,17 +78,19 @@ fn test_ceno_rt_io() -> Result<()> { } #[test] -fn test_hints() { +fn test_hints() -> Result<()> { let mut hints = CenoStdin::default(); - hints.write(&"This is my hint string.".to_string()).unwrap(); - hints.write(&1997_u32).unwrap(); - hints.write(&1999_u32).unwrap(); + hints.write(&true)?; + hints.write(&"This is my hint string.".to_string())?; + hints.write(&1997_u32)?; + hints.write(&1999_u32)?; let all_messages = ceno_host::run(CENO_PLATFORM, ceno_examples::hints, &hints); for (i, msg) in enumerate(&all_messages) { println!("{i}: {msg}"); } assert_eq!(all_messages[0], "3992003"); + Ok(()) } fn run(state: &mut VMState) -> Result> { diff --git a/guest/examples/examples/hints.rs b/guest/examples/examples/hints.rs index cf880f19f..090c439a7 100644 --- a/guest/examples/examples/hints.rs +++ b/guest/examples/examples/hints.rs @@ -8,6 +8,8 @@ use rkyv::{Archived, string::ArchivedString}; ceno_rt::entry!(main); fn main() { + let condition: &bool = ceno_rt::read(); + assert!(*condition); let msg: &ArchivedString = ceno_rt::read(); let a: &Archived = ceno_rt::read(); From 863f3ad943e07da61ff04a7ee8da24213a80435d Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 13 Dec 2024 15:35:22 +0800 Subject: [PATCH 49/64] Suggestion --- ceno_host/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ceno_host/src/lib.rs b/ceno_host/src/lib.rs index 8dbdab0c3..149a525de 100644 --- a/ceno_host/src/lib.rs +++ b/ceno_host/src/lib.rs @@ -53,10 +53,7 @@ impl CenoStdin { ]); for (offset, item) in izip!(offsets, &self.items) { buf.extend_from_slice(item); - buf.extend_from_slice(&vec![ - 0; - buf.len().next_multiple_of(RKYV_ALIGNMENT) - buf.len() - ]); + buf.resize(buf.len().next_multiple_of(RKYV_ALIGNMENT), 0); assert_eq!( buf.len(), (offset as usize).next_multiple_of(RKYV_ALIGNMENT) From 4d8f9243c426b49db23c9ce9a355729a4264699c Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 13 Dec 2024 18:14:28 +0800 Subject: [PATCH 50/64] Reshuffle crates --- Cargo.toml | 2 +- {guest => ceno_rt}/.cargo/config.toml | 0 ceno_rt/Cargo.lock | 93 ++++++++++++++++ ceno_rt/Cargo.toml | 13 +++ {guest/ceno_rt => ceno_rt}/README.md | 0 {guest/ceno_rt => ceno_rt}/build.rs | 0 {guest/ceno_rt => ceno_rt}/ceno_link.x | 0 {guest/ceno_rt => ceno_rt}/memory.x | 0 {guest/ceno_rt => ceno_rt}/src/allocator.rs | 0 {guest/ceno_rt => ceno_rt}/src/io.rs | 0 {guest/ceno_rt => ceno_rt}/src/lib.rs | 0 {guest/ceno_rt => ceno_rt}/src/params.rs | 0 examples-builder/build.rs | 8 +- examples/.cargo/config.toml | 1 + examples/Cargo.lock | 100 ++++++++++++++++++ examples/Cargo.toml | 13 +++ .../examples/ceno_rt_alloc.rs | 0 .../examples/ceno_rt_io.rs | 0 .../examples/ceno_rt_mem.rs | 0 .../examples/ceno_rt_mini.rs | 0 .../examples/ceno_rt_panic.rs | 0 guest/ceno_rt/Cargo.toml | 13 --- guest/examples/Cargo.toml | 13 --- 23 files changed, 225 insertions(+), 31 deletions(-) rename {guest => ceno_rt}/.cargo/config.toml (100%) create mode 100644 ceno_rt/Cargo.lock create mode 100644 ceno_rt/Cargo.toml rename {guest/ceno_rt => ceno_rt}/README.md (100%) rename {guest/ceno_rt => ceno_rt}/build.rs (100%) rename {guest/ceno_rt => ceno_rt}/ceno_link.x (100%) rename {guest/ceno_rt => ceno_rt}/memory.x (100%) rename {guest/ceno_rt => ceno_rt}/src/allocator.rs (100%) rename {guest/ceno_rt => ceno_rt}/src/io.rs (100%) rename {guest/ceno_rt => ceno_rt}/src/lib.rs (100%) rename {guest/ceno_rt => ceno_rt}/src/params.rs (100%) create mode 120000 examples/.cargo/config.toml create mode 100644 examples/Cargo.lock create mode 100644 examples/Cargo.toml rename {guest/examples => examples}/examples/ceno_rt_alloc.rs (100%) rename {guest/examples => examples}/examples/ceno_rt_io.rs (100%) rename {guest/examples => examples}/examples/ceno_rt_mem.rs (100%) rename {guest/examples => examples}/examples/ceno_rt_mini.rs (100%) rename {guest/examples => examples}/examples/ceno_rt_panic.rs (100%) delete mode 100644 guest/ceno_rt/Cargo.toml delete mode 100644 guest/examples/Cargo.toml diff --git a/Cargo.toml b/Cargo.toml index 93cabd8d9..2fb832769 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -exclude = ["guest"] +exclude = ["ceno_rt", "examples"] members = [ "ceno_emul", "examples-builder", diff --git a/guest/.cargo/config.toml b/ceno_rt/.cargo/config.toml similarity index 100% rename from guest/.cargo/config.toml rename to ceno_rt/.cargo/config.toml diff --git a/ceno_rt/Cargo.lock b/ceno_rt/Cargo.lock new file mode 100644 index 000000000..feb0692ae --- /dev/null +++ b/ceno_rt/Cargo.lock @@ -0,0 +1,93 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ceno_rt" +version = "0.1.0" +dependencies = [ + "riscv", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "riscv" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea8ff73d3720bdd0a97925f0bf79ad2744b6da8ff36be3840c48ac81191d7a7" +dependencies = [ + "critical-section", + "embedded-hal", + "paste", + "riscv-macros", + "riscv-pac", +] + +[[package]] +name = "riscv-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f265be5d634272320a7de94cea15c22a3bfdd4eb42eb43edc528415f066a1f25" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "riscv-pac" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" diff --git a/ceno_rt/Cargo.toml b/ceno_rt/Cargo.toml new file mode 100644 index 000000000..2ba43c74a --- /dev/null +++ b/ceno_rt/Cargo.toml @@ -0,0 +1,13 @@ +[package] +categories = ["cryptography", "zk", "blockchain", "ceno"] +description = "Ceno runtime library" +edition = "2021" +keywords = ["cryptography", "zk", "blockchain", "ceno"] +license = "MIT OR Apache-2.0" +name = "ceno_rt" +readme = "README.md" +repository = "https://github.com/scroll-tech/ceno" +version = "0.1.0" + +[dependencies] +riscv = "0.12" diff --git a/guest/ceno_rt/README.md b/ceno_rt/README.md similarity index 100% rename from guest/ceno_rt/README.md rename to ceno_rt/README.md diff --git a/guest/ceno_rt/build.rs b/ceno_rt/build.rs similarity index 100% rename from guest/ceno_rt/build.rs rename to ceno_rt/build.rs diff --git a/guest/ceno_rt/ceno_link.x b/ceno_rt/ceno_link.x similarity index 100% rename from guest/ceno_rt/ceno_link.x rename to ceno_rt/ceno_link.x diff --git a/guest/ceno_rt/memory.x b/ceno_rt/memory.x similarity index 100% rename from guest/ceno_rt/memory.x rename to ceno_rt/memory.x diff --git a/guest/ceno_rt/src/allocator.rs b/ceno_rt/src/allocator.rs similarity index 100% rename from guest/ceno_rt/src/allocator.rs rename to ceno_rt/src/allocator.rs diff --git a/guest/ceno_rt/src/io.rs b/ceno_rt/src/io.rs similarity index 100% rename from guest/ceno_rt/src/io.rs rename to ceno_rt/src/io.rs diff --git a/guest/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs similarity index 100% rename from guest/ceno_rt/src/lib.rs rename to ceno_rt/src/lib.rs diff --git a/guest/ceno_rt/src/params.rs b/ceno_rt/src/params.rs similarity index 100% rename from guest/ceno_rt/src/params.rs rename to ceno_rt/src/params.rs diff --git a/examples-builder/build.rs b/examples-builder/build.rs index 0e8d90441..7ea9b9628 100644 --- a/examples-builder/build.rs +++ b/examples-builder/build.rs @@ -26,7 +26,7 @@ fn build_elfs() { // See git history for an attempt to do this. let output = Command::new("cargo") .args(["build", "--release", "--examples"]) - .current_dir("../guest/examples") + .current_dir("../examples") .env_clear() .envs(std::env::vars().filter(|x| !x.0.starts_with("CARGO_"))) .output() @@ -41,11 +41,11 @@ fn build_elfs() { dest, r#"#[allow(non_upper_case_globals)] pub const {example}: &[u8] = - include_bytes!(r"{CARGO_MANIFEST_DIR}/../guest/target/riscv32im-unknown-none-elf/release/examples/{example}");"# + include_bytes!(r"{CARGO_MANIFEST_DIR}/../examples/target/riscv32im-unknown-none-elf/release/examples/{example}");"# ).expect("failed to write vars.rs"); } - let input_path = "../guest/"; - let elfs_path = "../guest/target/riscv32im-unknown-none-elf/release/examples/"; + let input_path = "../examples/"; + let elfs_path = "../examples/target/riscv32im-unknown-none-elf/release/examples/"; println!("cargo:rerun-if-changed={input_path}"); println!("cargo:rerun-if-changed={elfs_path}"); diff --git a/examples/.cargo/config.toml b/examples/.cargo/config.toml new file mode 120000 index 000000000..bade0c189 --- /dev/null +++ b/examples/.cargo/config.toml @@ -0,0 +1 @@ +../../ceno_rt/.cargo/config.toml \ No newline at end of file diff --git a/examples/Cargo.lock b/examples/Cargo.lock new file mode 100644 index 000000000..84c6c3ab6 --- /dev/null +++ b/examples/Cargo.lock @@ -0,0 +1,100 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ceno_rt" +version = "0.1.0" +dependencies = [ + "riscv", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "examples" +version = "0.1.0" +dependencies = [ + "ceno_rt", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "riscv" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea8ff73d3720bdd0a97925f0bf79ad2744b6da8ff36be3840c48ac81191d7a7" +dependencies = [ + "critical-section", + "embedded-hal", + "paste", + "riscv-macros", + "riscv-pac", +] + +[[package]] +name = "riscv-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f265be5d634272320a7de94cea15c22a3bfdd4eb42eb43edc528415f066a1f25" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "riscv-pac" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 000000000..b3751d1f3 --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,13 @@ +[package] +categories = ["cryptography", "zk", "blockchain", "ceno"] +description = "Ceno RiscV guest examples" +edition = "2021" +keywords = ["cryptography", "zk", "blockchain", "ceno"] +license = "MIT OR Apache-2.0" +name = "examples" +readme = "README.md" +repository = "https://github.com/scroll-tech/ceno" +version = "0.1.0" + +[dependencies] +ceno_rt = { path = "../ceno_rt" } diff --git a/guest/examples/examples/ceno_rt_alloc.rs b/examples/examples/ceno_rt_alloc.rs similarity index 100% rename from guest/examples/examples/ceno_rt_alloc.rs rename to examples/examples/ceno_rt_alloc.rs diff --git a/guest/examples/examples/ceno_rt_io.rs b/examples/examples/ceno_rt_io.rs similarity index 100% rename from guest/examples/examples/ceno_rt_io.rs rename to examples/examples/ceno_rt_io.rs diff --git a/guest/examples/examples/ceno_rt_mem.rs b/examples/examples/ceno_rt_mem.rs similarity index 100% rename from guest/examples/examples/ceno_rt_mem.rs rename to examples/examples/ceno_rt_mem.rs diff --git a/guest/examples/examples/ceno_rt_mini.rs b/examples/examples/ceno_rt_mini.rs similarity index 100% rename from guest/examples/examples/ceno_rt_mini.rs rename to examples/examples/ceno_rt_mini.rs diff --git a/guest/examples/examples/ceno_rt_panic.rs b/examples/examples/ceno_rt_panic.rs similarity index 100% rename from guest/examples/examples/ceno_rt_panic.rs rename to examples/examples/ceno_rt_panic.rs diff --git a/guest/ceno_rt/Cargo.toml b/guest/ceno_rt/Cargo.toml deleted file mode 100644 index dfdc87ad2..000000000 --- a/guest/ceno_rt/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -categories.workspace = true -description = "Ceno runtime library" -edition.workspace = true -keywords.workspace = true -license.workspace = true -name = "ceno_rt" -readme = "README.md" -repository.workspace = true -version.workspace = true - -[dependencies] -riscv = "0.12" diff --git a/guest/examples/Cargo.toml b/guest/examples/Cargo.toml deleted file mode 100644 index 397e743b1..000000000 --- a/guest/examples/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -categories.workspace = true -description = "Ceno RiscV guest examples" -edition.workspace = true -keywords.workspace = true -license.workspace = true -name = "examples" -readme = "README.md" -repository.workspace = true -version.workspace = true - -[dependencies] -ceno_rt = { path = "../ceno_rt" } From 5a95f70e0a12456f5562ea7792c795fd3dd85f77 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 13 Dec 2024 18:50:23 +0800 Subject: [PATCH 51/64] Fix --- ceno_host/src/lib.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ceno_host/src/lib.rs b/ceno_host/src/lib.rs index 149a525de..49b58f3ee 100644 --- a/ceno_host/src/lib.rs +++ b/ceno_host/src/lib.rs @@ -1,7 +1,7 @@ -use std::iter::zip; +use std::{collections::HashSet, iter::zip, sync::Arc}; use anyhow::Result; -use ceno_emul::{IterAddresses, Platform, VMState, host_utils::read_all_messages}; +use ceno_emul::{IterAddresses, Platform, Program, VMState, host_utils::read_all_messages}; use itertools::izip; use rkyv::{ Serialize, api::high::HighSerializer, rancor::Error, ser::allocator::ArenaHandle, to_bytes, @@ -67,11 +67,18 @@ impl CenoStdin { } pub fn run(platform: Platform, elf: &[u8], hints: &CenoStdin) -> Vec { + let program = Program::load_elf(elf, u32::MAX).unwrap(); + let platform = Platform { + prog_data: Some(program.image.keys().copied().collect::>()), + ..platform + }; + let hints: Vec = hints.finalise(); + let hints_range = platform.hints.clone(); - let mut state = VMState::new_from_elf(platform.clone(), elf).expect("Failed to load ELF"); + let mut state = VMState::new(platform, Arc::new(program)); - for (addr, value) in zip(platform.hints.iter_addresses(), hints) { + for (addr, value) in zip(hints_range.iter_addresses(), hints) { state.init_memory(addr.into(), value); } From 5591e566efb98e034e2515bb1edc7ef756e28eae Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 14 Dec 2024 18:07:08 +0800 Subject: [PATCH 52/64] Remove `riscv` dependency --- guest/Cargo.lock | 86 ---------------------------------------- guest/ceno_rt/Cargo.toml | 3 -- guest/ceno_rt/src/lib.rs | 12 ++---- 3 files changed, 4 insertions(+), 97 deletions(-) diff --git a/guest/Cargo.lock b/guest/Cargo.lock index 84c6c3ab6..eccd3732e 100644 --- a/guest/Cargo.lock +++ b/guest/Cargo.lock @@ -5,21 +5,6 @@ version = 4 [[package]] name = "ceno_rt" version = "0.1.0" -dependencies = [ - "riscv", -] - -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "embedded-hal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" [[package]] name = "examples" @@ -27,74 +12,3 @@ version = "0.1.0" dependencies = [ "ceno_rt", ] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "proc-macro2" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "riscv" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea8ff73d3720bdd0a97925f0bf79ad2744b6da8ff36be3840c48ac81191d7a7" -dependencies = [ - "critical-section", - "embedded-hal", - "paste", - "riscv-macros", - "riscv-pac", -] - -[[package]] -name = "riscv-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f265be5d634272320a7de94cea15c22a3bfdd4eb42eb43edc528415f066a1f25" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "riscv-pac" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" - -[[package]] -name = "syn" -version = "2.0.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" diff --git a/guest/ceno_rt/Cargo.toml b/guest/ceno_rt/Cargo.toml index dfdc87ad2..12d4ddb7a 100644 --- a/guest/ceno_rt/Cargo.toml +++ b/guest/ceno_rt/Cargo.toml @@ -8,6 +8,3 @@ name = "ceno_rt" readme = "README.md" repository.workspace = true version.workspace = true - -[dependencies] -riscv = "0.12" diff --git a/guest/ceno_rt/src/lib.rs b/guest/ceno_rt/src/lib.rs index 8de456c41..7df336ede 100644 --- a/guest/ceno_rt/src/lib.rs +++ b/guest/ceno_rt/src/lib.rs @@ -27,16 +27,12 @@ mod panic_handler { pub fn halt(exit_code: u32) -> ! { unsafe { asm!( - // Set the first argument. - "mv a0, {}", - // Set the ecall code HALT. - "li t0, 0x0", - in(reg) exit_code, + "ecall", + in ("a0") exit_code, + in ("t0") 0, ); - riscv::asm::ecall(); } - #[allow(clippy::empty_loop)] - loop {} + unreachable!(); } global_asm!( From df0ab05b3fd774a66a893fd66d5c56291d72f7e6 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 14 Dec 2024 18:19:02 +0800 Subject: [PATCH 53/64] Restore cargo.lock --- ceno_rt/Cargo.lock | 180 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 ceno_rt/Cargo.lock diff --git a/ceno_rt/Cargo.lock b/ceno_rt/Cargo.lock new file mode 100644 index 000000000..8157a3858 --- /dev/null +++ b/ceno_rt/Cargo.lock @@ -0,0 +1,180 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bytecheck" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c8f430744b23b54ad15161fcbc22d82a29b73eacbe425fea23ec822600bc6f" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "rancor", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523363cbe1df49b68215efdf500b103ac3b0fb4836aed6d15689a076eadb8fff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ceno_rt" +version = "0.1.0" +dependencies = [ + "rkyv", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "munge" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64142d38c84badf60abf06ff9bd80ad2174306a5b11bd4706535090a30a419df" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb5c1d8184f13f7d0ccbeeca0def2f9a181bce2624302793005f5ca8aa62e5e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rancor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf5f7161924b9d1cea0e4cabc97c372cea92b5f927fc13c6bca67157a0ad947" +dependencies = [ + "ptr_meta", +] + +[[package]] +name = "rend" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35e8a6bf28cd121053a66aa2e6a2e3eaffad4a60012179f0e864aa5ffeff215" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b11a153aec4a6ab60795f8ebe2923c597b16b05bb1504377451e705ef1a45323" +dependencies = [ + "bytecheck", + "hashbrown", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beb382a4d9f53bd5c0be86b10d8179c3f8a14c30bf774ff77096ed6581e35981" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" From 6e8e04c7220a2e7af905c7523d47b1243c294d54 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 14 Dec 2024 18:20:51 +0800 Subject: [PATCH 54/64] Minimise diff --- ceno_emul/src/disassemble/mod.rs | 1 - ceno_emul/src/platform.rs | 4 ++-- ceno_emul/src/rv32im.rs | 16 +++------------- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/ceno_emul/src/disassemble/mod.rs b/ceno_emul/src/disassemble/mod.rs index ba1c88db2..94709e74b 100644 --- a/ceno_emul/src/disassemble/mod.rs +++ b/ceno_emul/src/disassemble/mod.rs @@ -369,7 +369,6 @@ impl InstructionProcessor for InstructionTranspiler { } fn process_csrrw(&mut self, _: ITypeCSR) -> Self::InstructionResult { - eprintln!("csrrw: {:#010x}", self.word); Instruction::unimp(self.word) } diff --git a/ceno_emul/src/platform.rs b/ceno_emul/src/platform.rs index 1d767ba94..5d7d669e1 100644 --- a/ceno_emul/src/platform.rs +++ b/ceno_emul/src/platform.rs @@ -76,11 +76,11 @@ impl Platform { // Permissions. pub fn can_read(&self, addr: Addr) -> bool { - self.is_ram(addr) || self.is_pub_io(addr) || self.is_hints(addr) + self.can_write(addr) } pub fn can_write(&self, addr: Addr) -> bool { - self.is_ram(addr) + self.is_ram(addr) || self.is_pub_io(addr) || self.is_hints(addr) } // Environment calls. diff --git a/ceno_emul/src/rv32im.rs b/ceno_emul/src/rv32im.rs index 548508e8d..e63fa761b 100644 --- a/ceno_emul/src/rv32im.rs +++ b/ceno_emul/src/rv32im.rs @@ -14,8 +14,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::fmt::{self, Debug}; - use anyhow::{Result, anyhow}; use num_derive::ToPrimitive; use strum_macros::{Display, EnumIter}; @@ -98,19 +96,11 @@ pub trait EmuContext { } } -pub struct Hex(u32); - -impl Debug for Hex { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "0x{:08x}", self.0) - } -} - #[derive(Debug)] pub enum TrapCause { InstructionAddressMisaligned, InstructionAccessFault, - IllegalInstruction(Hex), + IllegalInstruction(u32), Breakpoint, LoadAddressMisaligned, LoadAccessFault(ByteAddr), @@ -285,7 +275,7 @@ pub fn step(ctx: &mut C) -> Result<()> { InsnCategory::Load => step_load(ctx, insn.kind, &insn)?, InsnCategory::Store => step_store(ctx, insn.kind, &insn)?, InsnCategory::System => step_system(ctx, insn.kind, &insn)?, - InsnCategory::Invalid => ctx.trap(TrapCause::IllegalInstruction(Hex(insn.raw)))?, + InsnCategory::Invalid => ctx.trap(TrapCause::IllegalInstruction(insn.raw))?, } { ctx.on_normal_end(&insn); }; @@ -529,7 +519,7 @@ fn step_store(ctx: &mut M, kind: InsnKind, decoded: &Instruction) fn step_system(ctx: &mut M, kind: InsnKind, decoded: &Instruction) -> Result { match kind { InsnKind::ECALL => ctx.ecall(), - _ => ctx.trap(TrapCause::IllegalInstruction(Hex(decoded.raw))), + _ => ctx.trap(TrapCause::IllegalInstruction(decoded.raw)), } } From 6342e37a43221893af6cfbf656a6b160891e2ebd Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 14 Dec 2024 18:27:42 +0800 Subject: [PATCH 55/64] Minimise diff, clean up --- .gitignore | 1 + ceno_zkvm/proptest-regressions/instructions/riscv/slti.txt | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 ceno_zkvm/proptest-regressions/instructions/riscv/slti.txt diff --git a/.gitignore b/.gitignore index bc46f232f..e751f11fb 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ report.json table_cache_dev_* .DS_Store .env +proptest-regressions/ diff --git a/ceno_zkvm/proptest-regressions/instructions/riscv/slti.txt b/ceno_zkvm/proptest-regressions/instructions/riscv/slti.txt deleted file mode 100644 index f0bc34756..000000000 --- a/ceno_zkvm/proptest-regressions/instructions/riscv/slti.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Seeds for failure cases proptest has generated in the past. It is -# automatically read and these particular cases re-run before any -# novel cases are generated. -# -# It is recommended to check this file in to source control so that -# everyone who runs the test benefits from these saved cases. -cc a362ff41aeaf3da3d2310913a3c15d23462427c5648a26db5400e951609ffe47 # shrinks to a = -1, imm = 26191872 From 4503dba0e4f79fef95eb94997cedcac929f794b4 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 14 Dec 2024 18:28:49 +0800 Subject: [PATCH 56/64] Minimise diff --- ceno_zkvm/src/tables/program.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceno_zkvm/src/tables/program.rs b/ceno_zkvm/src/tables/program.rs index 7d90f08da..5695ee247 100644 --- a/ceno_zkvm/src/tables/program.rs +++ b/ceno_zkvm/src/tables/program.rs @@ -150,7 +150,7 @@ impl TableCircuit for ProgramTableCircuit { .for_each(|(row, i)| { let pc = pc_base + (i * PC_STEP_SIZE) as u32; let insn = program.instructions[i]; - let values = InsnRecord::from_decoded(pc, &insn); + let values: InsnRecord<_> = InsnRecord::from_decoded(pc, &insn); // Copy all the fields. for (col, val) in config.record.as_slice().iter().zip_eq(values.as_slice()) { From 369098e314f7fb9e5018ceeb7addf7c704058ef1 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 14 Dec 2024 18:57:49 +0800 Subject: [PATCH 57/64] Standard library support --- Cargo.lock | 5 ++ Cargo.toml | 4 +- ceno_host/Cargo.toml | 2 +- ceno_host/tests/test_elf.rs | 107 +++++++++++++++++++++++++++++ ceno_rt/.cargo/config.toml | 32 ++++++--- ceno_rt/Cargo.toml | 3 - ceno_rt/src/lib.rs | 19 +---- examples-builder/build.rs | 11 +-- examples/examples/ceno_rt_alloc.rs | 3 - examples/examples/ceno_rt_io.rs | 4 -- examples/examples/ceno_rt_mem.rs | 4 -- examples/examples/ceno_rt_mini.rs | 4 -- examples/examples/ceno_rt_panic.rs | 4 -- examples/examples/hints.rs | 4 -- 14 files changed, 147 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 638a8c995..e60101e45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -247,6 +247,10 @@ dependencies = [ "shlex", ] +[[package]] +name = "ceno-examples" +version = "0.1.0" + [[package]] name = "ceno_emul" version = "0.1.0" @@ -267,6 +271,7 @@ name = "ceno_host" version = "0.1.0" dependencies = [ "anyhow", + "ceno-examples", "ceno_emul", "itertools 0.13.0", "rand", diff --git a/Cargo.toml b/Cargo.toml index 42cc087d5..5a8c224d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [workspace] -exclude = ["examples", "examples-builder", "examples"] +exclude = ["ceno_rt", "examples"] members = [ "ceno_emul", "ceno_host", "ceno_zkvm", - # "examples-builder", + "examples-builder", "mpcs", "multilinear_extensions", "poseidon", diff --git a/ceno_host/Cargo.toml b/ceno_host/Cargo.toml index 7223760d8..ad2b6e044 100644 --- a/ceno_host/Cargo.toml +++ b/ceno_host/Cargo.toml @@ -16,5 +16,5 @@ itertools.workspace = true rkyv = { version = "0.8.9", default-features = false, features = ["alloc", "bytecheck"] } [dev-dependencies] -# ceno-examples = { path = "../examples-builder" } +ceno-examples = { path = "../examples-builder" } rand.workspace = true diff --git a/ceno_host/tests/test_elf.rs b/ceno_host/tests/test_elf.rs index e69de29bb..8ae8a35ce 100644 --- a/ceno_host/tests/test_elf.rs +++ b/ceno_host/tests/test_elf.rs @@ -0,0 +1,107 @@ +use std::{collections::HashSet, sync::Arc}; + +use anyhow::Result; +use ceno_emul::{ + CENO_PLATFORM, EmuContext, InsnKind, Platform, Program, StepRecord, VMState, + host_utils::read_all_messages, +}; +use ceno_host::CenoStdin; +use itertools::enumerate; + +#[test] +fn test_ceno_rt_mini() -> Result<()> { + let program_elf = ceno_examples::ceno_rt_mini; + let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; + let _steps = run(&mut state)?; + Ok(()) +} + +#[test] +fn test_ceno_rt_panic() -> Result<()> { + let program_elf = ceno_examples::ceno_rt_panic; + let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; + let steps = run(&mut state)?; + let last = steps.last().unwrap(); + assert_eq!(last.insn().kind, InsnKind::ECALL); + assert_eq!(last.rs1().unwrap().value, Platform::ecall_halt()); + assert_eq!(last.rs2().unwrap().value, 1); // panic / halt(1) + Ok(()) +} + +#[test] +fn test_ceno_rt_mem() -> Result<()> { + let program_elf = ceno_examples::ceno_rt_mem; + let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; + let _steps = run(&mut state)?; + + let value = state.peek_memory(CENO_PLATFORM.heap.start.into()); + assert_eq!(value, 6765, "Expected Fibonacci 20, got {}", value); + Ok(()) +} + +#[test] +fn test_ceno_rt_alloc() -> Result<()> { + let program_elf = ceno_examples::ceno_rt_alloc; + let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; + let _steps = run(&mut state)?; + + // Search for the RAM action of the test program. + let mut found = (false, false); + for &addr in state.tracer().final_accesses().keys() { + if !CENO_PLATFORM.is_ram(addr.into()) { + continue; + } + let value = state.peek_memory(addr); + if value == 0xf00d { + found.0 = true; + } + if value == 0xbeef { + found.1 = true; + } + } + assert!(found.0); + assert!(found.1); + Ok(()) +} + +#[test] +fn test_ceno_rt_io() -> Result<()> { + let program_elf = ceno_examples::ceno_rt_io; + let program = Program::load_elf(program_elf, u32::MAX)?; + let platform = Platform { + prog_data: Some(program.image.keys().copied().collect::>()), + ..CENO_PLATFORM + }; + let mut state = VMState::new(platform, Arc::new(program)); + let _steps = run(&mut state)?; + + let all_messages = read_all_messages(&state); + for msg in &all_messages { + print!("{msg}"); + } + assert_eq!(&all_messages[0], "📜📜📜 Hello, World!\n"); + assert_eq!(&all_messages[1], "🌏🌍🌎\n"); + Ok(()) +} + +#[test] +fn test_hints() -> Result<()> { + let mut hints = CenoStdin::default(); + hints.write(&true)?; + hints.write(&"This is my hint string.".to_string())?; + hints.write(&1997_u32)?; + hints.write(&1999_u32)?; + + let all_messages = ceno_host::run(CENO_PLATFORM, ceno_examples::hints, &hints); + for (i, msg) in enumerate(&all_messages) { + println!("{i}: {msg}"); + } + assert_eq!(all_messages[0], "3992003"); + Ok(()) +} + +fn run(state: &mut VMState) -> Result> { + let steps = state.iter_until_halt().collect::>>()?; + eprintln!("Emulator ran for {} steps.", steps.len()); + Ok(steps) +} diff --git a/ceno_rt/.cargo/config.toml b/ceno_rt/.cargo/config.toml index 9cea5ae7c..0b0339c7f 100644 --- a/ceno_rt/.cargo/config.toml +++ b/ceno_rt/.cargo/config.toml @@ -1,15 +1,29 @@ -[target.riscv32im-unknown-none-elf] +[unstable] +build-std = [ + "alloc", + "core", + "compiler_builtins", + "std", + "panic_abort", + "proc_macro", +] +build-std-features = [ + "compiler-builtins-mem", + "panic_immediate_abort", + "default", +] + +[profile.dev] +panic = "abort" + +[build] rustflags = [ "-C", "link-arg=-Tmemory.x", - #"-C", "link-arg=-Tlink.x", // Script from riscv_rt. "-C", "link-arg=-Tceno_link.x", + "-Zlocation-detail=none", + "-C", + "passes=lower-atomic", ] - -[build] -target = "riscv32im-unknown-none-elf" - -[profile.release] -lto = true -panic = "abort" +target = ".cargo/riscv32im-ceno-zkvm-elf.json" diff --git a/ceno_rt/Cargo.toml b/ceno_rt/Cargo.toml index 452dc0032..d9a85ab12 100644 --- a/ceno_rt/Cargo.toml +++ b/ceno_rt/Cargo.toml @@ -14,6 +14,3 @@ rkyv = { version = "0.8", default-features = false, features = [ "alloc", "bytecheck", ] } - -[features] -std = [] diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 9ef50838e..7f4fabec8 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -2,11 +2,8 @@ #![feature(strict_overflow_ops)] #![feature(linkage)] -#![cfg_attr(feature = "std", feature(panic_always_abort))] -// #![cfg_attr(not(feature = "std"), no_std)] -// #![cfg_attr(feature = "std", feature(restricted_std))] - -use core::arch::global_asm; +#[cfg(target_arch = "riscv32")] +use core::arch::{asm, global_asm}; mod allocator; @@ -25,17 +22,6 @@ pub extern "C" fn sys_write(_fd: i32, _buf: *const u8, _count: usize) -> isize { unimplemented!(); } -#[cfg(not(any(test, feature = "std")))] -mod panic_handler { - use core::panic::PanicInfo; - - #[panic_handler] - #[inline(never)] - fn panic_handler(_panic: &PanicInfo<'_>) -> ! { - super::halt(1) - } -} - pub fn halt(exit_code: u32) -> ! { #[cfg(target_arch = "riscv32")] unsafe { @@ -50,6 +36,7 @@ pub fn halt(exit_code: u32) -> ! { unimplemented!("Halt is not implemented for this target: {}", exit_code); } +#[cfg(target_arch = "riscv32")] global_asm!( " // The entry point for the program. diff --git a/examples-builder/build.rs b/examples-builder/build.rs index 0f9475e53..6a75b2408 100644 --- a/examples-builder/build.rs +++ b/examples-builder/build.rs @@ -9,11 +9,12 @@ use std::{ /// /// Contact Matthias, if your examples get complicated enough to need their own crates, instead of just being one file. const EXAMPLES: &[&str] = &[ - // "ceno_rt_alloc", - // "ceno_rt_io", - // "ceno_rt_mem", - // "ceno_rt_mini", - // "ceno_rt_panic", + "ceno_rt_alloc", + "ceno_rt_io", + "ceno_rt_mem", + "ceno_rt_mini", + "ceno_rt_panic", + "hints", ]; const CARGO_MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); diff --git a/examples/examples/ceno_rt_alloc.rs b/examples/examples/ceno_rt_alloc.rs index 3e169d9b3..71efbeb28 100644 --- a/examples/examples/ceno_rt_alloc.rs +++ b/examples/examples/ceno_rt_alloc.rs @@ -1,5 +1,3 @@ -#![no_main] -#![no_std] use core::ptr::{addr_of, read_volatile}; extern crate ceno_rt; @@ -9,7 +7,6 @@ use alloc::{vec, vec::Vec}; static mut OUTPUT: u32 = 0; -ceno_rt::entry!(main); fn main() { // Test writing to a global variable. unsafe { diff --git a/examples/examples/ceno_rt_io.rs b/examples/examples/ceno_rt_io.rs index 8d2afa6aa..76988c9a2 100644 --- a/examples/examples/ceno_rt_io.rs +++ b/examples/examples/ceno_rt_io.rs @@ -1,11 +1,7 @@ -#![no_main] -#![no_std] - extern crate ceno_rt; use ceno_rt::println; use core::fmt::Write; -ceno_rt::entry!(main); fn main() { println!("📜📜📜 Hello, World!"); println!("🌏🌍🌎"); diff --git a/examples/examples/ceno_rt_mem.rs b/examples/examples/ceno_rt_mem.rs index 2cc484f80..5adbcb130 100644 --- a/examples/examples/ceno_rt_mem.rs +++ b/examples/examples/ceno_rt_mem.rs @@ -1,13 +1,9 @@ -#![no_main] -#![no_std] - // Use volatile functions to prevent compiler optimizations. use core::ptr::{read_volatile, write_volatile}; extern crate ceno_rt; const OUTPUT_ADDRESS: u32 = 0x8000_0000; -ceno_rt::entry!(main); #[inline(never)] fn main() { test_data_section(); diff --git a/examples/examples/ceno_rt_mini.rs b/examples/examples/ceno_rt_mini.rs index 70ec64153..505d649ba 100644 --- a/examples/examples/ceno_rt_mini.rs +++ b/examples/examples/ceno_rt_mini.rs @@ -1,7 +1,3 @@ -#![no_main] -#![no_std] - extern crate ceno_rt; -ceno_rt::entry!(main); fn main() {} diff --git a/examples/examples/ceno_rt_panic.rs b/examples/examples/ceno_rt_panic.rs index dc8c2fa17..10eda5842 100644 --- a/examples/examples/ceno_rt_panic.rs +++ b/examples/examples/ceno_rt_panic.rs @@ -1,9 +1,5 @@ -#![no_main] -#![no_std] - extern crate ceno_rt; -ceno_rt::entry!(main); fn main() { panic!("This is a panic message!"); } diff --git a/examples/examples/hints.rs b/examples/examples/hints.rs index 090c439a7..b591758d6 100644 --- a/examples/examples/hints.rs +++ b/examples/examples/hints.rs @@ -1,12 +1,8 @@ -#![no_main] -#![no_std] - extern crate ceno_rt; use ceno_rt::println; use core::fmt::Write; use rkyv::{Archived, string::ArchivedString}; -ceno_rt::entry!(main); fn main() { let condition: &bool = ceno_rt::read(); assert!(*condition); From a105e43ceb5205655bcca4a7daeb9abd0658d06e Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 14 Dec 2024 18:58:48 +0800 Subject: [PATCH 58/64] json target --- examples/.cargo/riscv32im-ceno-zkvm-elf.json | 31 ++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 examples/.cargo/riscv32im-ceno-zkvm-elf.json diff --git a/examples/.cargo/riscv32im-ceno-zkvm-elf.json b/examples/.cargo/riscv32im-ceno-zkvm-elf.json new file mode 100644 index 000000000..be67a8bae --- /dev/null +++ b/examples/.cargo/riscv32im-ceno-zkvm-elf.json @@ -0,0 +1,31 @@ +{ + "arch": "riscv32", + "atomic-cas": true, + "cpu": "generic-rv32", + "crt-objects-fallback": "false", + "data-layout": "e-m:e-p:32:32-i64:64-n32-S128", + "eh-frame-header": false, + "emit-debug-gdb-scripts": false, + "env": "", + "executables": true, + "features": "+m,+forced-atomics", + "linker-flavor": "gnu-lld", + "linker": "rust-lld", + "llvm-abiname": "ilp32", + "llvm-target": "riscv32", + "main-needs-argc-argv": false, + "metadata": { + "description": null, + "host_tools": false, + "std": true, + "tier": 2 + }, + "max-atomic-width": 64, + "os": "zkvm", + "panic-strategy": "abort", + "relocation-model": "static", + "singlethread": true, + "target-c-int-width": "32", + "target-endian": "little", + "target-pointer-width": "32" +} From 804ce191a829d7800a3478cf0b2088ea7c21b95f Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 14 Dec 2024 19:00:43 +0800 Subject: [PATCH 59/64] Pseudo instructions --- ceno_rt/.cargo/config.toml | 29 ----------------------------- ceno_rt/src/lib.rs | 4 ++-- examples/.cargo/config.toml | 30 +++++++++++++++++++++++++++++- 3 files changed, 31 insertions(+), 32 deletions(-) delete mode 100644 ceno_rt/.cargo/config.toml mode change 120000 => 100644 examples/.cargo/config.toml diff --git a/ceno_rt/.cargo/config.toml b/ceno_rt/.cargo/config.toml deleted file mode 100644 index 0b0339c7f..000000000 --- a/ceno_rt/.cargo/config.toml +++ /dev/null @@ -1,29 +0,0 @@ -[unstable] -build-std = [ - "alloc", - "core", - "compiler_builtins", - "std", - "panic_abort", - "proc_macro", -] -build-std-features = [ - "compiler-builtins-mem", - "panic_immediate_abort", - "default", -] - -[profile.dev] -panic = "abort" - -[build] -rustflags = [ - "-C", - "link-arg=-Tmemory.x", - "-C", - "link-arg=-Tceno_link.x", - "-Zlocation-detail=none", - "-C", - "passes=lower-atomic", -] -target = ".cargo/riscv32im-ceno-zkvm-elf.json" diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 7f4fabec8..b8a4ae779 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -61,9 +61,9 @@ _start: // If we return from main, we halt with success: // Set the ecall code HALT. - addi t0, x0, 0 + li t0, 0 // Set successful exit code, ie 0: - addi a0, x0, 0 + li a0, 0 ecall ", ); diff --git a/examples/.cargo/config.toml b/examples/.cargo/config.toml deleted file mode 120000 index bade0c189..000000000 --- a/examples/.cargo/config.toml +++ /dev/null @@ -1 +0,0 @@ -../../ceno_rt/.cargo/config.toml \ No newline at end of file diff --git a/examples/.cargo/config.toml b/examples/.cargo/config.toml new file mode 100644 index 000000000..0b0339c7f --- /dev/null +++ b/examples/.cargo/config.toml @@ -0,0 +1,29 @@ +[unstable] +build-std = [ + "alloc", + "core", + "compiler_builtins", + "std", + "panic_abort", + "proc_macro", +] +build-std-features = [ + "compiler-builtins-mem", + "panic_immediate_abort", + "default", +] + +[profile.dev] +panic = "abort" + +[build] +rustflags = [ + "-C", + "link-arg=-Tmemory.x", + "-C", + "link-arg=-Tceno_link.x", + "-Zlocation-detail=none", + "-C", + "passes=lower-atomic", +] +target = ".cargo/riscv32im-ceno-zkvm-elf.json" From 32bb0edb201c77ae2a28f526c50ad7b7bba3aad5 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 14 Dec 2024 19:15:24 +0800 Subject: [PATCH 60/64] Fix --- examples-builder/build.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples-builder/build.rs b/examples-builder/build.rs index 6a75b2408..7d01eb3ad 100644 --- a/examples-builder/build.rs +++ b/examples-builder/build.rs @@ -42,13 +42,13 @@ fn build_elfs() { dest, r#"#[allow(non_upper_case_globals)] pub const {example}: &[u8] = - include_bytes!(r"{CARGO_MANIFEST_DIR}/../examples/target/riscv32im-unknown-none-elf/release/examples/{example}");"# + include_bytes!(r"{CARGO_MANIFEST_DIR}/../examples/target/riscv32im-ceno-zkvm-elf/release/examples/{example}");"# ).expect("failed to write vars.rs"); } - let input_path = "../examples/"; - let elfs_path = "../examples/target/riscv32im-unknown-none-elf/release/examples/"; - println!("cargo:rerun-if-changed={input_path}"); + println!("cargo:rerun-if-changed=../examples/"); + println!("cargo:rerun-if-changed=../ceno_rt/"); + let elfs_path = "../examples/target/riscv32im-ceno-zkvm-elf/release/examples/"; println!("cargo:rerun-if-changed={elfs_path}"); } From c24612a425a1f0a7f867d58895c2042e9ee10943 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 14 Dec 2024 19:29:02 +0800 Subject: [PATCH 61/64] Mostly works --- ceno_host/tests/test_elf.rs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/ceno_host/tests/test_elf.rs b/ceno_host/tests/test_elf.rs index 8ae8a35ce..d75eff2a5 100644 --- a/ceno_host/tests/test_elf.rs +++ b/ceno_host/tests/test_elf.rs @@ -11,7 +11,12 @@ use itertools::enumerate; #[test] fn test_ceno_rt_mini() -> Result<()> { let program_elf = ceno_examples::ceno_rt_mini; - let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; + let program = Program::load_elf(program_elf, u32::MAX)?; + let platform = Platform { + prog_data: Some(program.image.keys().copied().collect::>()), + ..CENO_PLATFORM + }; + let mut state = VMState::new(platform, Arc::new(program)); let _steps = run(&mut state)?; Ok(()) } @@ -19,7 +24,12 @@ fn test_ceno_rt_mini() -> Result<()> { #[test] fn test_ceno_rt_panic() -> Result<()> { let program_elf = ceno_examples::ceno_rt_panic; - let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; + let program = Program::load_elf(program_elf, u32::MAX)?; + let platform = Platform { + prog_data: Some(program.image.keys().copied().collect::>()), + ..CENO_PLATFORM + }; + let mut state = VMState::new(platform, Arc::new(program)); let steps = run(&mut state)?; let last = steps.last().unwrap(); assert_eq!(last.insn().kind, InsnKind::ECALL); @@ -31,10 +41,15 @@ fn test_ceno_rt_panic() -> Result<()> { #[test] fn test_ceno_rt_mem() -> Result<()> { let program_elf = ceno_examples::ceno_rt_mem; - let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; + let program = Program::load_elf(program_elf, u32::MAX)?; + let platform = Platform { + prog_data: Some(program.image.keys().copied().collect::>()), + ..CENO_PLATFORM + }; + let mut state = VMState::new(platform.clone(), Arc::new(program)); let _steps = run(&mut state)?; - let value = state.peek_memory(CENO_PLATFORM.heap.start.into()); + let value = state.peek_memory(platform.heap.start.into()); assert_eq!(value, 6765, "Expected Fibonacci 20, got {}", value); Ok(()) } @@ -42,7 +57,12 @@ fn test_ceno_rt_mem() -> Result<()> { #[test] fn test_ceno_rt_alloc() -> Result<()> { let program_elf = ceno_examples::ceno_rt_alloc; - let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; + let program = Program::load_elf(program_elf, u32::MAX)?; + let platform = Platform { + prog_data: Some(program.image.keys().copied().collect::>()), + ..CENO_PLATFORM + }; + let mut state = VMState::new(platform, Arc::new(program)); let _steps = run(&mut state)?; // Search for the RAM action of the test program. From 38f4065491c0a7e04b49f70d4de9afe3e912a34e Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 14 Dec 2024 19:32:00 +0800 Subject: [PATCH 62/64] Panic --- ceno_host/tests/test_elf.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ceno_host/tests/test_elf.rs b/ceno_host/tests/test_elf.rs index d75eff2a5..18bcd00e9 100644 --- a/ceno_host/tests/test_elf.rs +++ b/ceno_host/tests/test_elf.rs @@ -21,21 +21,25 @@ fn test_ceno_rt_mini() -> Result<()> { Ok(()) } +// TODO(Matthias): We are using Rust's standard library's default panic handler now, +// and they are indicated with a different instruction than our ecall. (But still work, +// as you can tell, because this tests panics.) However, we should adapt this test +// to properly check for the conventional Rust panic. #[test] -fn test_ceno_rt_panic() -> Result<()> { +#[should_panic] +fn test_ceno_rt_panic() { let program_elf = ceno_examples::ceno_rt_panic; - let program = Program::load_elf(program_elf, u32::MAX)?; + let program = Program::load_elf(program_elf, u32::MAX).unwrap(); let platform = Platform { prog_data: Some(program.image.keys().copied().collect::>()), ..CENO_PLATFORM }; let mut state = VMState::new(platform, Arc::new(program)); - let steps = run(&mut state)?; + let steps = run(&mut state).unwrap(); let last = steps.last().unwrap(); assert_eq!(last.insn().kind, InsnKind::ECALL); assert_eq!(last.rs1().unwrap().value, Platform::ecall_halt()); assert_eq!(last.rs2().unwrap().value, 1); // panic / halt(1) - Ok(()) } #[test] From a6f81a0ccfbd7dc1e8855654177a5bb41026552e Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 14 Dec 2024 19:33:14 +0800 Subject: [PATCH 63/64] Precise --- ceno_host/tests/test_elf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceno_host/tests/test_elf.rs b/ceno_host/tests/test_elf.rs index 18bcd00e9..62118f424 100644 --- a/ceno_host/tests/test_elf.rs +++ b/ceno_host/tests/test_elf.rs @@ -26,7 +26,7 @@ fn test_ceno_rt_mini() -> Result<()> { // as you can tell, because this tests panics.) However, we should adapt this test // to properly check for the conventional Rust panic. #[test] -#[should_panic] +#[should_panic(expected = "Trap IllegalInstruction")] fn test_ceno_rt_panic() { let program_elf = ceno_examples::ceno_rt_panic; let program = Program::load_elf(program_elf, u32::MAX).unwrap(); From 29e758ed9fdfe2bd2750696260a1e0d6f3023d36 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Sat, 14 Dec 2024 19:35:08 +0800 Subject: [PATCH 64/64] Need sources --- rust-toolchain.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index eb12bd38f..0d9c05484 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,3 @@ [toolchain] channel = "nightly-2024-12-06" +components = ["rust-src"]