diff --git a/ceno_emul/src/vm_state.rs b/ceno_emul/src/vm_state.rs index b478fc5ff..778c77839 100644 --- a/ceno_emul/src/vm_state.rs +++ b/ceno_emul/src/vm_state.rs @@ -53,8 +53,8 @@ impl VMState { self.succeeded } - pub fn tracer(&mut self) -> &mut Tracer { - &mut self.tracer + pub fn tracer(&self) -> &Tracer { + &self.tracer } /// Set a word in memory without side-effects. @@ -75,7 +75,7 @@ impl VMState { fn step(&mut self, emu: &Emulator) -> Result { emu.step(self)?; - let step = self.tracer().advance(); + let step = self.tracer.advance(); if step.is_busy_loop() && !self.succeeded() { Err(anyhow!("Stuck in loop {}", "{}")) } else { diff --git a/ceno_emul/tests/data/README.md b/ceno_emul/tests/data/README.md index 746886507..f098e90a6 100644 --- a/ceno_emul/tests/data/README.md +++ b/ceno_emul/tests/data/README.md @@ -3,5 +3,5 @@ ```bash cd ceno_rt cargo build --release --examples -cp ../target/riscv32im-unknown-none-elf/release/examples/ceno_rt_{mini,panic,mem} ../ceno_emul/tests/data/ +cp ../target/riscv32im-unknown-none-elf/release/examples/ceno_rt_{mini,panic,mem,alloc} ../ceno_emul/tests/data/ ``` \ No newline at end of file diff --git a/ceno_emul/tests/data/ceno_rt_alloc b/ceno_emul/tests/data/ceno_rt_alloc new file mode 100755 index 000000000..f8783e7f9 Binary files /dev/null and b/ceno_emul/tests/data/ceno_rt_alloc differ diff --git a/ceno_emul/tests/test_elf.rs b/ceno_emul/tests/test_elf.rs index 74733f89a..6f21cea63 100644 --- a/ceno_emul/tests/test_elf.rs +++ b/ceno_emul/tests/test_elf.rs @@ -25,11 +25,36 @@ fn test_ceno_rt_mem() -> Result<()> { let mut state = VMState::new_from_elf(CENO_PLATFORM, program_elf)?; let _steps = run(&mut state)?; - let value = state.peek_memory(ByteAddr(CENO_PLATFORM.ram_start()).waddr()); + 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 = include_bytes!("./data/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 = include_bytes!("./data/ceno_rt_io"); diff --git a/ceno_rt/examples/ceno_rt_alloc.rs b/ceno_rt/examples/ceno_rt_alloc.rs new file mode 100644 index 000000000..9545a41ab --- /dev/null +++ b/ceno_rt/examples/ceno_rt_alloc.rs @@ -0,0 +1,30 @@ +#![no_main] +#![no_std] +use core::ptr::{addr_of, read_volatile}; + +#[allow(unused_imports)] +use ceno_rt; + +extern crate alloc; +use alloc::{vec, vec::Vec}; + +static mut OUTPUT: u32 = 0; + +#[no_mangle] +fn main() { + // Test writing to a global variable. + unsafe { + OUTPUT = 0xf00d; + black_box(addr_of!(OUTPUT)); + } + + // Test writing to the heap. + let mut v: Vec = vec![]; + v.push(0xbeef); + black_box(&v[0]); +} + +/// Prevent compiler optimizations. +fn black_box(x: *const T) -> T { + unsafe { read_volatile(x) } +} diff --git a/ceno_rt/src/allocator.rs b/ceno_rt/src/allocator.rs new file mode 100644 index 000000000..51c71cbd0 --- /dev/null +++ b/ceno_rt/src/allocator.rs @@ -0,0 +1,51 @@ +//! A bump allocator. +//! Based on https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html + +use core::{ + alloc::{GlobalAlloc, Layout}, + cell::UnsafeCell, + ptr::null_mut, +}; + +const ARENA_SIZE: usize = 128 * 1024; +const MAX_SUPPORTED_ALIGN: usize = 4096; +#[repr(C, align(4096))] // 4096 == MAX_SUPPORTED_ALIGN +struct SimpleAllocator { + arena: UnsafeCell<[u8; ARENA_SIZE]>, + remaining: UnsafeCell, // we allocate from the top, counting down +} + +#[global_allocator] +static ALLOCATOR: SimpleAllocator = SimpleAllocator { + arena: UnsafeCell::new([0; ARENA_SIZE]), + remaining: UnsafeCell::new(ARENA_SIZE), +}; + +unsafe impl Sync for SimpleAllocator {} + +unsafe impl GlobalAlloc for SimpleAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let size = layout.size(); + let align = layout.align(); + + // `Layout` contract forbids making a `Layout` with align=0, or align not power of 2. + // So we can safely use a mask to ensure alignment without worrying about UB. + let align_mask_to_round_down = !(align - 1); + + if align > MAX_SUPPORTED_ALIGN { + return null_mut(); + } + + let remaining = self.remaining.get(); + if size > *remaining { + return null_mut(); + } + *remaining -= size; + *remaining &= align_mask_to_round_down; + + self.arena.get().cast::().add(*remaining) + } + + /// Never deallocate. + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {} +} diff --git a/ceno_rt/src/lib.rs b/ceno_rt/src/lib.rs index ce79c7f7d..a79ebefb8 100644 --- a/ceno_rt/src/lib.rs +++ b/ceno_rt/src/lib.rs @@ -1,10 +1,12 @@ #![no_std] +use core::arch::{asm, global_asm}; + +mod allocator; + mod io; pub use io::info_out; -use core::arch::{asm, global_asm}; - mod params; pub use params::*;