openvm_rv32im_circuit/adapters/
mod.rs

1use std::ops::Mul;
2
3use openvm_circuit::{
4    arch::{execution_mode::ExecutionCtxTrait, VmStateMut},
5    system::memory::{
6        merkle::public_values::PUBLIC_VALUES_AS,
7        online::{GuestMemory, TracingMemory},
8    },
9};
10use openvm_instructions::riscv::{RV32_MEMORY_AS, RV32_REGISTER_AS};
11use openvm_stark_backend::p3_field::{PrimeCharacteristicRing, PrimeField32};
12
13mod alu;
14mod branch;
15mod jalr;
16mod loadstore;
17mod mul;
18mod rdwrite;
19
20pub use alu::*;
21pub use branch::*;
22pub use jalr::*;
23pub use loadstore::*;
24pub use mul::*;
25pub use openvm_instructions::riscv::{RV32_CELL_BITS, RV32_REGISTER_NUM_LIMBS};
26pub use rdwrite::*;
27
28/// 256-bit heap integer stored as 32 bytes (32 limbs of 8-bits)
29pub const INT256_NUM_LIMBS: usize = 32;
30
31// For soundness, should be <= 16
32pub const RV_IS_TYPE_IMM_BITS: usize = 12;
33
34// Branch immediate value is in [-2^12, 2^12)
35pub const RV_B_TYPE_IMM_BITS: usize = 13;
36
37pub const RV_J_TYPE_IMM_BITS: usize = 21;
38
39/// Convert the RISC-V register data (32 bits represented as 4 bytes, where each byte is represented
40/// as a field element) back into its value as u32.
41pub fn compose<F: PrimeField32>(ptr_data: [F; RV32_REGISTER_NUM_LIMBS]) -> u32 {
42    let mut val = 0;
43    for (i, limb) in ptr_data.map(|x| x.as_canonical_u32()).iter().enumerate() {
44        val += limb << (i * 8);
45    }
46    val
47}
48
49/// inverse of `compose`
50pub fn decompose<F: PrimeField32>(value: u32) -> [F; RV32_REGISTER_NUM_LIMBS] {
51    std::array::from_fn(|i| {
52        F::from_u32((value >> (RV32_CELL_BITS * i)) & ((1 << RV32_CELL_BITS) - 1))
53    })
54}
55
56#[inline(always)]
57pub fn imm_to_bytes(imm: u32) -> [u8; RV32_REGISTER_NUM_LIMBS] {
58    debug_assert_eq!(imm >> 24, 0);
59    let mut imm_le = imm.to_le_bytes();
60    imm_le[3] = imm_le[2];
61    imm_le
62}
63
64#[inline(always)]
65pub fn memory_read<const N: usize>(memory: &GuestMemory, address_space: u32, ptr: u32) -> [u8; N] {
66    debug_assert!(
67        address_space == RV32_REGISTER_AS
68            || address_space == RV32_MEMORY_AS
69            || address_space == PUBLIC_VALUES_AS,
70    );
71
72    // SAFETY:
73    // - address spaces `RV32_REGISTER_AS`, `RV32_MEMORY_AS`, `PUBLIC_VALUES_AS` have cell type `u8`
74    //   and use the fixed 4-byte VM memory access granularity
75    unsafe { memory.read::<u8, N>(address_space, ptr) }
76}
77
78#[inline(always)]
79pub fn memory_write<const N: usize>(
80    memory: &mut GuestMemory,
81    address_space: u32,
82    ptr: u32,
83    data: [u8; N],
84) {
85    debug_assert!(
86        address_space == RV32_REGISTER_AS
87            || address_space == RV32_MEMORY_AS
88            || address_space == PUBLIC_VALUES_AS
89    );
90
91    // SAFETY:
92    // - address spaces `RV32_REGISTER_AS`, `RV32_MEMORY_AS`, `PUBLIC_VALUES_AS` have cell type `u8`
93    //   and use the fixed 4-byte VM memory access granularity
94    unsafe { memory.write::<u8, N>(address_space, ptr, data) }
95}
96
97/// Atomic read operation which increments the timestamp by 1.
98/// Returns `(t_prev, [ptr:4]_{address_space})` where `t_prev` is the timestamp of the last memory
99/// access.
100#[inline(always)]
101pub fn timed_read<const N: usize>(
102    memory: &mut TracingMemory,
103    address_space: u32,
104    ptr: u32,
105) -> (u32, [u8; N]) {
106    debug_assert!(
107        address_space == RV32_REGISTER_AS
108            || address_space == RV32_MEMORY_AS
109            || address_space == PUBLIC_VALUES_AS
110    );
111
112    // SAFETY:
113    // - address spaces `RV32_REGISTER_AS`, `RV32_MEMORY_AS`, `PUBLIC_VALUES_AS` have cell type `u8`
114    //   and use the fixed 4-byte VM memory access granularity
115    unsafe { memory.read::<u8, N>(address_space, ptr) }
116}
117
118#[inline(always)]
119pub fn timed_write<const N: usize>(
120    memory: &mut TracingMemory,
121    address_space: u32,
122    ptr: u32,
123    data: [u8; N],
124) -> (u32, [u8; N]) {
125    debug_assert!(
126        address_space == RV32_REGISTER_AS
127            || address_space == RV32_MEMORY_AS
128            || address_space == PUBLIC_VALUES_AS
129    );
130
131    // SAFETY:
132    // - address spaces `RV32_REGISTER_AS`, `RV32_MEMORY_AS`, `PUBLIC_VALUES_AS` have cell type `u8`
133    //   and use the fixed 4-byte VM memory access granularity
134    unsafe { memory.write::<u8, N>(address_space, ptr, data) }
135}
136
137/// Reads register value at `reg_ptr` from memory and records the memory access in mutable buffer.
138/// Trace generation relevant to this memory access can be done fully from the recorded buffer.
139#[inline(always)]
140pub fn tracing_read<const N: usize>(
141    memory: &mut TracingMemory,
142    address_space: u32,
143    ptr: u32,
144    prev_timestamp: &mut u32,
145) -> [u8; N] {
146    let (t_prev, data) = timed_read(memory, address_space, ptr);
147    *prev_timestamp = t_prev;
148    data
149}
150
151#[inline(always)]
152pub fn tracing_read_imm(
153    memory: &mut TracingMemory,
154    imm: u32,
155    imm_mut: &mut u32,
156) -> [u8; RV32_REGISTER_NUM_LIMBS] {
157    *imm_mut = imm;
158    debug_assert_eq!(imm >> 24, 0); // highest byte should be zero to prevent overflow
159
160    memory.increment_timestamp();
161
162    let mut imm_le = imm.to_le_bytes();
163    // Important: we set the highest byte equal to the second highest byte, using the assumption
164    // that imm is at most 24 bits
165    imm_le[3] = imm_le[2];
166    imm_le
167}
168
169/// Writes `reg_ptr, reg_val` into memory and records the memory access in mutable buffer.
170/// Trace generation relevant to this memory access can be done fully from the recorded buffer.
171#[inline(always)]
172pub fn tracing_write<const N: usize>(
173    memory: &mut TracingMemory,
174    address_space: u32,
175    ptr: u32,
176    data: [u8; N],
177    prev_timestamp: &mut u32,
178    prev_data: &mut [u8; N],
179) {
180    let (t_prev, data_prev) = timed_write(memory, address_space, ptr, data);
181    *prev_timestamp = t_prev;
182    *prev_data = data_prev;
183}
184
185#[inline(always)]
186pub fn memory_read_from_state<F, Ctx, const N: usize>(
187    state: &mut VmStateMut<F, GuestMemory, Ctx>,
188    address_space: u32,
189    ptr: u32,
190) -> [u8; N]
191where
192    Ctx: ExecutionCtxTrait,
193{
194    state.ctx.on_memory_operation(address_space, ptr, N as u32);
195
196    memory_read(state.memory, address_space, ptr)
197}
198
199#[inline(always)]
200pub fn memory_write_from_state<F, Ctx, const N: usize>(
201    state: &mut VmStateMut<F, GuestMemory, Ctx>,
202    address_space: u32,
203    ptr: u32,
204    data: [u8; N],
205) where
206    Ctx: ExecutionCtxTrait,
207{
208    state.ctx.on_memory_operation(address_space, ptr, N as u32);
209
210    memory_write(state.memory, address_space, ptr, data)
211}
212
213#[inline(always)]
214pub fn read_rv32_register_from_state<F, Ctx>(
215    state: &mut VmStateMut<F, GuestMemory, Ctx>,
216    ptr: u32,
217) -> u32
218where
219    Ctx: ExecutionCtxTrait,
220{
221    u32::from_le_bytes(memory_read_from_state(state, RV32_REGISTER_AS, ptr))
222}
223
224#[inline(always)]
225pub fn read_rv32_register(memory: &GuestMemory, ptr: u32) -> u32 {
226    u32::from_le_bytes(memory_read(memory, RV32_REGISTER_AS, ptr))
227}
228
229pub fn abstract_compose<T: PrimeCharacteristicRing, V: Mul<T, Output = T>>(
230    data: [V; RV32_REGISTER_NUM_LIMBS],
231) -> T {
232    data.into_iter()
233        .enumerate()
234        .fold(T::ZERO, |acc, (i, limb)| {
235            acc + limb * T::from_u32(1 << (i * RV32_CELL_BITS))
236        })
237}
238
239// TEMP[jpw]
240pub fn tmp_convert_to_u8s<F: PrimeField32, const N: usize>(data: [F; N]) -> [u8; N] {
241    data.map(|x| x.as_canonical_u32() as u8)
242}