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}