diff --git a/Cargo.lock b/Cargo.lock index 3bfef12f7..4b8d08b64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,6 +232,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + [[package]] name = "cast" version = "0.3.0" @@ -1699,13 +1705,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b11a153aec4a6ab60795f8ebe2923c597b16b05bb1504377451e705ef1a45323" dependencies = [ "bytecheck", + "bytes", "hashbrown 0.15.2", + "indexmap", "munge", "ptr_meta", "rancor", "rend", "rkyv_derive", "tinyvec", + "uuid", ] [[package]] diff --git a/ceno_host/Cargo.toml b/ceno_host/Cargo.toml index e589e5854..c6e0aca8c 100644 --- a/ceno_host/Cargo.toml +++ b/ceno_host/Cargo.toml @@ -13,7 +13,7 @@ version.workspace = true anyhow.workspace = true ceno_emul = { path = "../ceno_emul" } itertools.workspace = true -rkyv = { version = "0.8", default-features = false, features = ["alloc", "bytecheck"] } +rkyv = { version = "0.8", features = ["pointer_width_32"] } [dev-dependencies] ceno-examples = { path = "../examples-builder" } diff --git a/ceno_host/tests/test_elf.rs b/ceno_host/tests/test_elf.rs index 8ae8a35ce..a160c1e74 100644 --- a/ceno_host/tests/test_elf.rs +++ b/ceno_host/tests/test_elf.rs @@ -1,4 +1,8 @@ -use std::{collections::HashSet, sync::Arc}; +use std::{ + collections::{BTreeSet, HashSet}, + iter::from_fn, + sync::Arc, +}; use anyhow::Result; use ceno_emul::{ @@ -6,35 +10,54 @@ use ceno_emul::{ host_utils::read_all_messages, }; use ceno_host::CenoStdin; -use itertools::enumerate; +use itertools::{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(()) } +// 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(expected = "Trap IllegalInstruction")] +fn test_ceno_rt_panic() { 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 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).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] 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 +65,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. @@ -100,6 +128,113 @@ fn test_hints() -> Result<()> { Ok(()) } +#[test] +fn test_sorting() -> Result<()> { + use rand::Rng; + let mut hints = CenoStdin::default(); + let mut rng = rand::thread_rng(); + + // Provide some random numbers to sort. + hints.write(&(0..1_000).map(|_| rng.gen::()).collect::>())?; + + let all_messages = ceno_host::run(CENO_PLATFORM, ceno_examples::sorting, &hints); + for (i, msg) in enumerate(&all_messages) { + println!("{i}: {msg}"); + } + Ok(()) +} + +/// Sorting with hints +/// +/// This is an example of using non-deterministic computation in the guest (ie hints) to +/// sort faster. For one million numbers of input, this takes 25_750_500 cycles to run. +/// While `test_sorting` above takes around 302_674_458 cycles. That's a 10x speedup! +#[test] +fn test_sorting_with_hints() -> Result<()> { + use rand::Rng; + let mut hints = CenoStdin::default(); + let mut rng = rand::thread_rng(); + + // Provide some random numbers to sort. + let nums = (0..1_000).map(|_| rng.gen::()).collect::>(); + hints.write(&nums)?; + // Provide both the sorted answer, and the places where you can find the original number. + let (answer, places): (Vec<_>, Vec<_>) = enumerate(nums) + .map(|(i, x)| (x, i as u32)) + .sorted() + .collect::>() + .into_iter() + .unzip(); + hints.write(&answer)?; + hints.write(&places)?; + + let all_messages = ceno_host::run(CENO_PLATFORM, ceno_examples::sorting_with_hints, &hints); + for (i, msg) in enumerate(&all_messages) { + println!("{i}: {msg}"); + } + Ok(()) +} + +#[test] +fn test_median() -> Result<()> { + use rand::Rng; + let mut hints = CenoStdin::default(); + let mut rng = rand::thread_rng(); + + // Provide some random numbers to find the median of. + let mut nums = (0..1000).map(|_| rng.gen::()).collect::>(); + hints.write(&nums)?; + nums.sort(); + hints.write(&nums[nums.len() / 2])?; + + let all_messages = ceno_host::run(CENO_PLATFORM, ceno_examples::median, &hints); + assert!(!all_messages.is_empty()); + for (i, msg) in enumerate(&all_messages) { + println!("{i}: {msg}"); + } + Ok(()) +} + +#[test] +#[should_panic(expected = "Trap IllegalInstruction")] +fn test_hashing_fail() { + use rand::Rng; + let mut hints = CenoStdin::default(); + let mut rng = rand::thread_rng(); + + let mut nums = (0..1_000).map(|_| rng.gen::()).collect::>(); + // Add a duplicate number to make uniqueness check fail: + nums[211] = nums[907]; + hints.write(&nums).unwrap(); + + let _ = ceno_host::run(CENO_PLATFORM, ceno_examples::hashing, &hints); +} + +#[test] +fn test_hashing() -> Result<()> { + use rand::Rng; + let mut hints = CenoStdin::default(); + let mut rng = rand::thread_rng(); + + // Provide some unique random numbers to verify: + let uniques: Vec = { + let mut seen_so_far = BTreeSet::default(); + from_fn(move || Some(rng.gen::())) + .filter(|&item| seen_so_far.insert(item)) + .take(1_000) + .collect::>() + }; + + hints.write(&uniques)?; + let all_messages = ceno_host::run(CENO_PLATFORM, ceno_examples::hashing, &hints); + assert!(!all_messages.is_empty()); + for (i, msg) in enumerate(&all_messages) { + println!("{i}: {msg}"); + } + assert_eq!(all_messages[0], "The input is a set of unique numbers.\n"); + Ok(()) +} + fn run(state: &mut VMState) -> Result> { let steps = state.iter_until_halt().collect::>>()?; eprintln!("Emulator ran for {} steps.", steps.len()); diff --git a/ceno_rt/.cargo/config.toml b/ceno_rt/.cargo/config.toml deleted file mode 100644 index 9cea5ae7c..000000000 --- a/ceno_rt/.cargo/config.toml +++ /dev/null @@ -1,15 +0,0 @@ -[target.riscv32im-unknown-none-elf] -rustflags = [ - "-C", - "link-arg=-Tmemory.x", - #"-C", "link-arg=-Tlink.x", // Script from riscv_rt. - "-C", - "link-arg=-Tceno_link.x", -] - -[build] -target = "riscv32im-unknown-none-elf" - -[profile.release] -lto = true -panic = "abort" diff --git a/ceno_rt/Cargo.toml b/ceno_rt/Cargo.toml index d9a85ab12..2abc62c57 100644 --- a/ceno_rt/Cargo.toml +++ b/ceno_rt/Cargo.toml @@ -10,7 +10,4 @@ repository = "https://github.com/scroll-tech/ceno" version = "0.1.0" [dependencies] -rkyv = { version = "0.8", default-features = false, features = [ - "alloc", - "bytecheck", -] } +rkyv = { version = "0.8", features = ["pointer_width_32"] } diff --git a/ceno_rt/README.md b/ceno_rt/README.md index 72abeb763..2718e9447 100644 --- a/ceno_rt/README.md +++ b/ceno_rt/README.md @@ -1,6 +1,6 @@ # Ceno VM Runtime -This crate provides the runtime for program running on the Ceno VM. It provides: +This crate provides the runtime for programs running on the Ceno VM. It provides: - Configuration of compilation and linking. - Program startup and termination. @@ -8,18 +8,14 @@ This crate provides the runtime for program running on the Ceno VM. It provides: ### Build examples -```bash -rustup target add riscv32im-unknown-none-elf +See the [examples](../examples/) directory for example programs. -cargo build --release --examples -``` +### Updating the RISC-V target -### Development tools +From time to time the Rust compiler or LLVM change enough so that we need to update our configuration files for building. Especially [the JSON target specification](riscv32im-ceno-zkvm-elf.json). -```bash -cargo install cargo-binutils -rustup component add llvm-tools +Unfortunately, the exact details border on black magic. But you can generally try to follow [The Embedonomicon](https://docs.rust-embedded.org/embedonomicon/custom-target.html) and start with the output of this: -# Look at the disassembly of a compiled program. -cargo objdump --release --example ceno_rt_mini -- --all-headers --disassemble +``` +rustc +nightly -Z unstable-options --print target-spec-json --target riscv32im-unknown-none-elf ``` diff --git a/ceno_rt/riscv32im-ceno-zkvm-elf.json b/ceno_rt/riscv32im-ceno-zkvm-elf.json new file mode 100644 index 000000000..79f865260 --- /dev/null +++ b/ceno_rt/riscv32im-ceno-zkvm-elf.json @@ -0,0 +1,32 @@ +{ + "arch": "riscv32", + "atomic-cas": true, + "cpu": "generic-rv32", + "crt-objects-fallback": "false", + "data-layout": "e-m:e-p:32:32-i64:64-n32-S128", + "disable-redzone": false, + "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" +} diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index 7e3c8e1ba..feef82a1a 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -1,7 +1,8 @@ #![deny(clippy::cargo)] #![feature(strict_overflow_ops)] -#![no_std] +#![feature(linkage)] +#[cfg(target_arch = "riscv32")] use core::arch::{asm, global_asm}; mod allocator; @@ -15,29 +16,55 @@ pub use io::info_out; mod params; pub use params::*; -#[cfg(not(test))] -mod panic_handler { - use core::panic::PanicInfo; +#[no_mangle] +#[linkage = "weak"] +pub extern "C" fn sys_write(_fd: i32, _buf: *const u8, _count: usize) -> isize { + unimplemented!(); +} - #[panic_handler] - #[inline(never)] - fn panic_handler(_panic: &PanicInfo<'_>) -> ! { - super::halt(1) +/// Generates random bytes. +/// +/// # Safety +/// +/// Make sure that `buf` has at least `nwords` words. +/// This generator is terrible. :) +#[no_mangle] +#[linkage = "weak"] +pub unsafe extern "C" fn sys_rand(recv_buf: *mut u8, words: usize) { + unsafe fn step() -> u32 { + static mut X: u32 = 0xae569764; + // We are stealing Borland Delphi's random number generator. + // The random numbers here are only good enough to make eg + // HashMap works. + X = X.wrapping_mul(134775813) + 1; + X + } + // TODO(Matthias): this is a bit inefficient, + // we could fill whole u32 words at a time. + // But it's just for testing. + for i in 0..words { + let element = recv_buf.add(i); + // The lower bits ain't really random, so might as well take + // the higher order ones, if we are only using 8 bits. + *element = step().to_le_bytes()[3]; } } -#[allow(asm_sub_register)] pub fn halt(exit_code: u32) -> ! { + #[cfg(target_arch = "riscv32")] unsafe { asm!( "ecall", in ("a0") exit_code, in ("t0") 0, ); + unreachable!(); } - unreachable!(); + #[cfg(not(target_arch = "riscv32"))] + unimplemented!("Halt is not implemented for this target, exit_code: {}", exit_code); } +#[cfg(target_arch = "riscv32")] global_asm!( " // The entry point for the program. @@ -55,35 +82,18 @@ _start: la sp, _stack_start mv fp, sp - // Call the Rust start function. - jal zero, _start_rust - ", -); + // Call Rust's main function. + call main -#[macro_export] -macro_rules! entry { - ($path:path) => { - // Type check the given path - const CENO_ENTRY: fn() = $path; + // If we return from main, we halt with success: - mod ceno_generated_main { - #[no_mangle] - extern "C" fn bespoke_entrypoint() { - super::CENO_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(); - } - bespoke_entrypoint(); - halt(0) -} + // Set the ecall code HALT. + li t0, 0 + // Set successful exit code, ie 0: + li a0, 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 56f83f025..ae798177c 100644 --- a/examples-builder/build.rs +++ b/examples-builder/build.rs @@ -15,6 +15,10 @@ const EXAMPLES: &[&str] = &[ "ceno_rt_mini", "ceno_rt_panic", "hints", + "sorting", + "sorting_with_hints", + "median", + "hashing", ]; const CARGO_MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); @@ -42,12 +46,12 @@ 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"); } println!("cargo:rerun-if-changed=../examples/"); println!("cargo:rerun-if-changed=../ceno_rt/"); - let elfs_path = "../examples/target/riscv32im-unknown-none-elf/release/examples/"; + let elfs_path = "../examples/target/riscv32im-ceno-zkvm-elf/release/examples/"; println!("cargo:rerun-if-changed={elfs_path}"); } 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..0b79e4d7a --- /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 = "../ceno_rt/riscv32im-ceno-zkvm-elf.json" diff --git a/examples/Cargo.lock b/examples/Cargo.lock index c0c2418cc..481478df2 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -25,6 +25,12 @@ dependencies = [ "syn", ] +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + [[package]] name = "ceno_rt" version = "0.1.0" @@ -32,11 +38,24 @@ dependencies = [ "rkyv", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "examples" version = "0.1.0" dependencies = [ "ceno_rt", + "itertools", "rkyv", ] @@ -46,6 +65,25 @@ version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "indexmap" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "munge" version = "0.4.1" @@ -129,13 +167,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b11a153aec4a6ab60795f8ebe2923c597b16b05bb1504377451e705ef1a45323" dependencies = [ "bytecheck", + "bytes", "hashbrown", + "indexmap", "munge", "ptr_meta", "rancor", "rend", "rkyv_derive", "tinyvec", + "uuid", ] [[package]] @@ -186,3 +227,9 @@ name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 6be7ab795..12390f8e0 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -11,6 +11,7 @@ version = "0.1.0" [dependencies] ceno_rt = { path = "../ceno_rt" } +itertools = "0.13" rkyv = { version = "0.8", default-features = false, features = [ "alloc", "bytecheck", diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..23a6f5a2c --- /dev/null +++ b/examples/README.md @@ -0,0 +1,11 @@ +# Ceno VM examples guest programs + +You can directly build the RiscV examples by running the following commands: + +```bash +rustup target add riscv32im-unknown-none-elf +cargo build --release --examples +``` + +But that won't be very useful by itself. You probably want to execute and prove these examples. Have a look at [test\_elf.rs](../ceno_host/tests/test_elf.rs) +and the [examples-builder](../examples-builder/) for one way to run the examples from tests. Or see [the end-to-end integration tests](../.github/workflows/integration.yml) for how to run the examples as stand-alone ELF files. 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/hashing.rs b/examples/examples/hashing.rs new file mode 100644 index 000000000..8d9d2b9d3 --- /dev/null +++ b/examples/examples/hashing.rs @@ -0,0 +1,21 @@ +//! Here's an example that really makes use of the standard library and couldn't be done without. +//! +//! I mean `HashSet` really lives only in the proper standard library, and not in `alloc` or `core`. +//! You could, of course, rerwite the example to use `alloc::collections::btree_set::BTreeSet` +//! instead of `HashSet`. + +extern crate ceno_rt; +use ceno_rt::println; +use core::fmt::Write; +use rkyv::vec::ArchivedVec; +use std::collections::HashSet; + +/// Check that the input is a set of unique numbers. +fn main() { + let input: &ArchivedVec = ceno_rt::read(); + let mut set = HashSet::new(); + for i in input.iter() { + assert!(set.insert(i)); + } + println!("The input is a set of unique numbers."); +} 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); diff --git a/examples/examples/median.rs b/examples/examples/median.rs new file mode 100644 index 000000000..fd5d17a34 --- /dev/null +++ b/examples/examples/median.rs @@ -0,0 +1,16 @@ +//! Find the median of a collection of numbers. +//! +//! Of course, we are asking our good friend, the host, for help, but we still need to verify the answer. +extern crate ceno_rt; +use ceno_rt::println; +use core::fmt::Write; +use rkyv::{Archived, vec::ArchivedVec}; + +fn main() { + let numbers: &ArchivedVec = ceno_rt::read(); + let median_candidate: &Archived = ceno_rt::read(); + let median_candidate = &&median_candidate.to_native(); + let smaller = numbers.iter().filter(move |x| x < median_candidate).count(); + assert_eq!(smaller, numbers.len() / 2); + println!("{}", median_candidate); +} diff --git a/examples/examples/sorting.rs b/examples/examples/sorting.rs new file mode 100644 index 000000000..278992fec --- /dev/null +++ b/examples/examples/sorting.rs @@ -0,0 +1,12 @@ +extern crate ceno_rt; +use ceno_rt::println; +use core::fmt::Write; +use rkyv::vec::ArchivedVec; + +fn main() { + let input: &ArchivedVec = ceno_rt::read(); + let mut scratch: Vec = input.to_vec(); + scratch.sort(); + // Print any output you feel like, eg the first element of the sorted vector: + println!("{}", scratch[0]); +} diff --git a/examples/examples/sorting_with_hints.rs b/examples/examples/sorting_with_hints.rs new file mode 100644 index 000000000..eedb55ab5 --- /dev/null +++ b/examples/examples/sorting_with_hints.rs @@ -0,0 +1,33 @@ +extern crate ceno_rt; +use ceno_rt::println; +use core::fmt::Write; +use itertools::{Itertools, izip}; +use rkyv::vec::ArchivedVec; + +fn my_sort(input: &ArchivedVec) -> &ArchivedVec { + let answer: &ArchivedVec = ceno_rt::read(); + let places: &ArchivedVec = ceno_rt::read(); + + // Check that the answer is sorted. + for (prev, next) in answer.iter().tuple_windows() { + assert!(prev <= next); + } + + // Check that that `answer` is a permutation of the `input`, + // with the help of `places`. + let mut scratch: Vec = vec![true; input.len()]; + for (&place, answer) in izip!(places.iter(), answer.iter()) { + assert!(std::mem::replace(&mut scratch[place as usize], false)); + assert_eq!(input[place as usize], *answer); + } + answer +} + +fn main() { + // We take the input numbers from the hints for convenience. + // But just pretend that in the real world, the input would be + // committed to via public IO. + let input: &ArchivedVec = ceno_rt::read(); + my_sort(input); + println!("We successfully sorted the input!"); +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index eb12bd38f..c736f462f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,5 @@ [toolchain] channel = "nightly-2024-12-06" +targets = ["riscv32im-unknown-none-elf"] +# We need the sources for build-std. +components = ["rust-src"]