openvm_rv32im_circuit/common/mod.rs
1#[cfg(feature = "aot")]
2pub(crate) use aot::*;
3
4#[cfg(feature = "aot")]
5mod aot {
6 use std::mem::offset_of;
7
8 pub(crate) use openvm_circuit::arch::aot::common::*;
9 use openvm_circuit::{
10 arch::{
11 execution_mode::{metered::memory_ctx::MemoryCtx, MeteredCtx},
12 AotError, SystemConfig, VmExecState, ADDR_SPACE_OFFSET,
13 },
14 system::memory::{online::GuestMemory, CHUNK},
15 };
16 /// This is DIRTY because PAGE_BITS is a generic parameter of E2 context.
17 const DEFAULT_PAGE_BITS: usize = 6;
18
19 pub(crate) fn gpr_to_rv32_register(gpr: &str, rv32_reg: u8) -> String {
20 let xmm_map_reg = rv32_reg / 2;
21 if rv32_reg.is_multiple_of(2) {
22 format!(" pinsrd xmm{xmm_map_reg}, {gpr}, 0\n")
23 } else {
24 format!(" pinsrd xmm{xmm_map_reg}, {gpr}, 1\n")
25 }
26 }
27
28 pub(crate) fn address_space_start_to_gpr(address_space: u32, gpr: &str) -> String {
29 if address_space == 2 {
30 if REG_AS2_PTR != gpr {
31 return format!(" mov {gpr}, r15\n");
32 }
33 return "".to_string();
34 }
35
36 let xmm_map_reg = match address_space {
37 1 => "xmm0",
38 3 => "xmm1",
39 4 => "xmm2",
40 _ => unreachable!("Only address space 1, 2, 3, 4 is supported"),
41 };
42 format!(" pextrq {gpr}, {xmm_map_reg}, 1\n")
43 }
44
45 /*
46 input:
47 - riscv register number
48 - gpr register to write into
49 - is_gpr_force_write boolean
50
51 output:
52 - string representing the general purpose register that stores the value of register number `rv32_reg`
53 - emitted assembly string that performs the move
54 */
55 pub(crate) fn xmm_to_gpr(
56 rv32_reg: u8,
57 gpr: &str,
58 is_gpr_force_write: bool,
59 ) -> (String, String) {
60 if let Some(override_reg) = RISCV_TO_X86_OVERRIDE_MAP[rv32_reg as usize] {
61 // a/4 is overridden, b/4 is overridden
62 if is_gpr_force_write {
63 return (gpr.to_string(), format!(" mov {gpr}, {override_reg}\n"));
64 }
65 return (override_reg.to_string(), "".to_string());
66 }
67 let xmm_map_reg = rv32_reg / 2;
68 if rv32_reg.is_multiple_of(2) {
69 (
70 gpr.to_string(),
71 format!(" pextrd {gpr}, xmm{xmm_map_reg}, 0\n"),
72 )
73 } else {
74 (
75 gpr.to_string(),
76 format!(" pextrd {gpr}, xmm{xmm_map_reg}, 1\n"),
77 )
78 }
79 }
80
81 pub(crate) fn gpr_to_xmm(gpr: &str, rv32_reg: u8) -> String {
82 if let Some(override_reg) = RISCV_TO_X86_OVERRIDE_MAP[rv32_reg as usize] {
83 if gpr == override_reg {
84 //already in correct location
85 return "".to_string();
86 }
87 return format!(" mov {override_reg}, {gpr}\n");
88 }
89 let xmm_map_reg = rv32_reg / 2;
90 if rv32_reg.is_multiple_of(2) {
91 format!(" pinsrd xmm{xmm_map_reg}, {gpr}, 0\n")
92 } else {
93 format!(" pinsrd xmm{xmm_map_reg}, {gpr}, 1\n")
94 }
95 }
96
97 /// Generate ASM code for updating the boundary merkle heights.
98 ///
99 /// # Arguments
100 ///
101 /// * `config` - The system configuration.
102 /// * `address_space` - The address space.
103 /// * `pc` - The program counter of the current instruction.
104 /// * `ptr_reg` - The register to store the accessed pointer. The caller should not expect the
105 /// value of this register to be preserved.
106 /// * `reg1` - A register to store the intermediate result.
107 /// * `reg2` - A register to store the intermediate result.
108 ///
109 /// # Returns
110 ///
111 /// The ASM code for updating the boundary merkle heights.
112 pub(crate) fn update_boundary_merkle_heights_asm<F>(
113 config: &SystemConfig,
114 address_space: u32,
115 pc: u32,
116 ptr_reg: &str,
117 reg1: &str,
118 reg2: &str,
119 ) -> Result<String, AotError> {
120 // `update_boundary_merkle_heights_asm` rewrites the following code in ASM for
121 // `on_memory_operation`: ```
122 // pub fn label_to_index((addr_space, block_id): (u32, u32)) -> u64 {
123 // (((addr_space - ADDR_SPACE_OFFSET) as u64) << self.address_height) + block_id as u64
124 // }
125 //
126 // pub(crate) fn update_boundary_merkle_heights(
127 // &mut self,
128 // address_space: u32,
129 // ptr: u32,
130 // size: u32,
131 // ) {
132 // let num_blocks = (size + self.chunk - 1) >> self.chunk_bits;
133 // let start_chunk_id = ptr >> self.chunk_bits;
134 // let start_block_id = if self.chunk == 1 {
135 // start_chunk_id
136 // } else {
137 // self.memory_dimensions
138 // .label_to_index((address_space, start_chunk_id)) as u32
139 // };
140 // // Because `self.chunk == 1 << self.chunk_bits`
141 // let end_block_id = start_block_id + num_blocks;
142 // let start_page_id = start_block_id >> PAGE_BITS;
143 // let end_page_id = ((end_block_id - 1) >> PAGE_BITS) + 1;
144
145 // for page_id in start_page_id..end_page_id {
146 // // Append page_id to page_indices_since_checkpoint
147 // let len = self.page_indices_since_checkpoint_len;
148 // // SAFETY: len is within bounds, and we extend length by 1 after writing.
149 // unsafe {
150 // *self.page_indices_since_checkpoint.as_mut_ptr().add(len) = page_id;
151 // }
152 // self.page_indices_since_checkpoint_len = len + 1;
153 //
154 // if self.page_indices.insert(page_id as usize) {
155 // // SAFETY: address_space passed is usually a hardcoded constant or derived
156 // from an // Instruction where it is bounds checked before passing
157 // unsafe {
158 // *self
159 // .addr_space_access_count
160 // .get_unchecked_mut(address_space as usize) += 1;
161 // }
162 // }
163 // }
164 // }
165 // ```
166 // For a specific RV32 instruction, the variables can be treated as constants at AOT compilation time:
167 // Inputs:
168 // - `chunk`: always CHUNK (8), the merkle leaf size.
169 // - `address_space`: always a constant because it is derived from an Instruction
170 // - `size`: RV32 instruction always read 4 bytes(in the AIR level).
171 // - `self.memory_dimensions.address_height`: known at AOT compilation time because it is derived from the memory configuration.
172 // Inside the function body:
173 // - `num_blocks`: `(size + chunk - 1) >> chunk_bits = (4 + 8 - 1) >> 3 = 1`
174 // - `as_offset = (addr_space - ADDR_SPACE_OFFSET) as u64) << self.address_height)`: constant because `address_space` and `address_height` constant
175 // - `chunk_idx`: `ptr >> chunk_bits`
176 // - `start_block_id`: `chunk_idx + as_offset`
177 // - `end_block_id`: `start_block_id + num_blocks = start_block_id +1`
178 // - `start_page_id`: `start_block_id >> PAGE_BITS`
179 // - `end_page_id`: ((end_block_id - 1) >> PAGE_BITS) + 1 = start_block_id >> PAGE_BITS + 1;
180 //
181 // Therefore the loop only iterates once for `page_id = start_page_id`.
182
183 let chunk_bits = CHUNK.ilog2();
184 let as_offset = ((address_space - ADDR_SPACE_OFFSET) as u64)
185 << (config.memory_config.memory_dimensions().address_height);
186
187 let mut asm_str = String::new();
188 // `chunk_idx`: `ptr >> chunk_bits`
189 asm_str += &format!(" shr {ptr_reg}, {chunk_bits}\n");
190 // `start_block_id`: `chunk_idx + as_offset`
191 asm_str += &format!(" add {ptr_reg}, {as_offset}\n");
192 // `start_page_id`: `start_block_id >> PAGE_BITS`
193 // NOTE: This is DIRTY because PAGE_BITS is a generic parameter of E2 context.
194 asm_str += &format!(" shr {ptr_reg}, {DEFAULT_PAGE_BITS}\n");
195
196 let memory_ctx_offset = offset_of!(VmExecState<F, GuestMemory, MeteredCtx>, ctx)
197 + offset_of!(MeteredCtx, memory_ctx);
198 let page_indices_ptr_offset =
199 memory_ctx_offset + offset_of!(MemoryCtx<DEFAULT_PAGE_BITS>, page_indices);
200 let addr_space_access_count_ptr_offset =
201 memory_ctx_offset + offset_of!(MemoryCtx<DEFAULT_PAGE_BITS>, addr_space_access_count);
202 let page_indices_since_checkpoint_ptr_offset = memory_ctx_offset
203 + offset_of!(MemoryCtx<DEFAULT_PAGE_BITS>, page_indices_since_checkpoint);
204 let page_indices_since_checkpoint_len_offset = memory_ctx_offset
205 + offset_of!(
206 MemoryCtx<DEFAULT_PAGE_BITS>,
207 page_indices_since_checkpoint_len
208 );
209 let inserted_label = format!(".asm_execute_pc_{pc}_inserted");
210
211 // Append page_id to page_indices_since_checkpoint
212 asm_str += &format!(
213 " mov {reg1}, [{REG_EXEC_STATE_PTR} + {page_indices_since_checkpoint_len_offset}]\n"
214 );
215 asm_str += &format!(
216 " mov {reg2}, [{REG_EXEC_STATE_PTR} + {page_indices_since_checkpoint_ptr_offset}]\n"
217 );
218 let ptr_reg_32 = convert_x86_reg(ptr_reg, Width::W32).ok_or_else(|| {
219 AotError::Other(format!("unsupported ptr_reg for 32-bit store: {ptr_reg}"))
220 })?;
221 asm_str += &format!(" mov dword ptr [{reg2} + {reg1} * 4], {ptr_reg_32}\n");
222 asm_str += &format!(" add {reg1}, 1\n");
223 asm_str += &format!(
224 " mov [{REG_EXEC_STATE_PTR} + {page_indices_since_checkpoint_len_offset}], {reg1}\n"
225 );
226
227 // The next section is the implementation of `BitSet::insert` in ASM.
228 // pub fn insert(&mut self, index: usize) -> bool {
229 // let word_index = index >> 6;
230 // let bit_index = index & 63;
231 // let mask = 1u64 << bit_index;
232 // let word = unsafe { self.words.get_unchecked_mut(word_index) };
233 // let was_set = (*word & mask) != 0;
234 // *word |= mask;
235 // !was_set
236 // }
237
238 // Start with `ptr_reg = index`
239 // `reg1 = word_index`
240 asm_str += &format!(" mov {reg1}, {ptr_reg}\n");
241 asm_str += &format!(" shr {reg1}, 6\n");
242 // `ptr_reg = bit_index = index & 63`
243 asm_str += &format!(" and {ptr_reg}, 63\n");
244 // `reg2 = mask = 1u64 << bit_index`
245 asm_str += &format!(" mov {reg2}, 1\n");
246 asm_str += &format!(" shlx {reg2}, {reg2}, {ptr_reg}\n");
247 // `ptr_reg = self.page_indices.ptr`
248 asm_str +=
249 &format!(" mov {ptr_reg}, [{REG_EXEC_STATE_PTR} + {page_indices_ptr_offset}]\n");
250
251 // `reg1 = word_ptr = &self.words.get_unchecked_mut(word_index)`
252 asm_str += &format!(" lea {reg1}, [{ptr_reg} + {reg1} * 8]\n");
253 // `ptr_reg = word = *word_ptr`
254 asm_str += &format!(" mov {ptr_reg}, [{reg1}]\n");
255
256 // `test (*word & mask)`
257 asm_str += &format!(" test {ptr_reg}, {reg2}\n");
258 asm_str += &format!(" jnz {inserted_label}\n");
259 // When (*word & mask) == 0
260 // `*word += mask`
261 asm_str += &format!(" add {ptr_reg}, {reg2}\n");
262 asm_str += &format!(" mov [{reg1}], {ptr_reg}\n");
263 // reg1 = &addr_space_access_count.as_ptr()
264 asm_str += &format!(
265 " lea {reg1}, [{REG_EXEC_STATE_PTR} + {addr_space_access_count_ptr_offset}]\n"
266 );
267 asm_str += &format!(" mov {reg1}, [{reg1}]\n");
268 // self.addr_space_access_count[address_space] += 1;
269 asm_str += &format!(" add dword ptr [{reg1} + {address_space} * 4], 1\n");
270 asm_str += &format!("{inserted_label}:\n");
271 // Inserted, do nothing
272
273 Ok(asm_str)
274 }
275
276 /// Assumption: `REG_TRACE_HEIGHT` is the pointer of `trace_heights``.
277 pub(crate) fn update_height_change_asm(
278 chip_idx: usize,
279 height_delta: u32,
280 ) -> Result<String, AotError> {
281 let mut asm_str = String::new();
282 // `update_height_change_asm` rewrites the following code in ASM for `on_height_change`:
283 // ```
284 // pub fn on_height_change(&mut self, chip_idx: usize, height_delta: u32) {
285 // self.trace_heights[chip_idx] += height_delta;
286 // }
287 // ```
288 asm_str +=
289 &format!(" add dword ptr [{REG_TRACE_HEIGHT} + {chip_idx} * 4], {height_delta}\n");
290 Ok(asm_str)
291 }
292}