Skip to main content

sp1_core_executor/
estimating.rs

1use sp1_hypercube::air::PROOF_NONCE_NUM_WORDS;
2use sp1_jit::MinimalTrace;
3use sp1_primitives::consts::LOG_PAGE_SIZE;
4use std::{marker::PhantomData, sync::Arc};
5
6use crate::{
7    events::{MemoryReadRecord, MemoryRecord, MemoryWriteRecord, PageProtRecord},
8    vm::{
9        gas::ReportGenerator,
10        results::{
11            AluResult, BranchResult, CycleResult, FetchResult, JumpResult, LoadResult,
12            LoadResultSupervisor, MaybeImmediate, StoreResult, StoreResultSupervisor, TrapResult,
13            UTypeResult,
14        },
15        syscall::SyscallRuntime,
16        CoreVM,
17    },
18    ExecutionError, ExecutionMode, ExecutionReport, Instruction, Opcode, Program, Register,
19    SP1CoreOpts, SupervisorMode, SyscallCode, TrapError, UserMode,
20};
21
22/// A RISC-V VM that uses a [`MinimalTrace`] to create a [`ExecutionReport`].
23///
24/// The type parameter `M` determines whether page protection checks are enabled.
25pub struct GasEstimatingVM<'a, M: ExecutionMode> {
26    /// The core VM.
27    pub core: CoreVM<'a, M>,
28    /// The gas calculator for the VM.
29    pub gas_calculator: ReportGenerator,
30    /// The index of the hint lens the next shard will use.
31    pub hint_lens_idx: usize,
32    /// Phantom data for the execution mode.
33    _mode: PhantomData<M>,
34}
35
36impl GasEstimatingVM<'_, SupervisorMode> {
37    /// Execute the program until it halts.
38    pub fn execute(&mut self) -> Result<ExecutionReport, ExecutionError> {
39        if self.core.is_done() {
40            return Ok(self.gas_calculator.generate_report());
41        }
42
43        loop {
44            match self.execute_instruction()? {
45                CycleResult::Done(false) => {}
46                CycleResult::TraceEnd | CycleResult::ShardBoundary | CycleResult::Done(true) => {
47                    return Ok(self.gas_calculator.generate_report());
48                }
49            }
50        }
51    }
52
53    /// Execute the next instruction at the current PC.
54    fn execute_instruction(&mut self) -> Result<CycleResult, ExecutionError> {
55        let instruction = self.core.fetch();
56
57        match &instruction.opcode {
58            Opcode::ADD
59            | Opcode::ADDI
60            | Opcode::SUB
61            | Opcode::XOR
62            | Opcode::OR
63            | Opcode::AND
64            | Opcode::SLL
65            | Opcode::SLLW
66            | Opcode::SRL
67            | Opcode::SRA
68            | Opcode::SRLW
69            | Opcode::SRAW
70            | Opcode::SLT
71            | Opcode::SLTU
72            | Opcode::MUL
73            | Opcode::MULHU
74            | Opcode::MULHSU
75            | Opcode::MULH
76            | Opcode::MULW
77            | Opcode::DIVU
78            | Opcode::REMU
79            | Opcode::DIV
80            | Opcode::REM
81            | Opcode::DIVW
82            | Opcode::ADDW
83            | Opcode::SUBW
84            | Opcode::DIVUW
85            | Opcode::REMUW
86            | Opcode::REMW => {
87                self.execute_alu(&instruction);
88            }
89            Opcode::LB
90            | Opcode::LBU
91            | Opcode::LH
92            | Opcode::LHU
93            | Opcode::LW
94            | Opcode::LWU
95            | Opcode::LD => self.execute_load(&instruction)?,
96            Opcode::SB | Opcode::SH | Opcode::SW | Opcode::SD => {
97                self.execute_store(&instruction)?;
98            }
99            Opcode::JAL | Opcode::JALR => {
100                self.execute_jump(&instruction);
101            }
102            Opcode::BEQ | Opcode::BNE | Opcode::BLT | Opcode::BGE | Opcode::BLTU | Opcode::BGEU => {
103                self.execute_branch(&instruction);
104            }
105            Opcode::LUI | Opcode::AUIPC => {
106                self.execute_utype(&instruction);
107            }
108            Opcode::ECALL => self.execute_ecall(&instruction)?,
109            Opcode::EBREAK | Opcode::UNIMP => {
110                unreachable!("Invalid opcode for `execute_instruction`: {:?}", instruction.opcode)
111            }
112        }
113
114        Ok(self.core.advance())
115    }
116}
117
118impl GasEstimatingVM<'_, SupervisorMode> {
119    /// Execute a load instruction.
120    pub fn execute_load(&mut self, instruction: &Instruction) -> Result<(), ExecutionError> {
121        let LoadResultSupervisor { addr, rd, mr_record, rr_record, rw_record, rs1, .. } =
122            self.core.execute_load(instruction)?;
123
124        self.gas_calculator.handle_instruction(
125            instruction,
126            self.core.needs_bump_clk_high(),
127            rd == Register::X0,
128            self.core.needs_state_bump(instruction),
129        );
130
131        self.gas_calculator.handle_mem_event(addr, mr_record.prev_timestamp);
132        self.gas_calculator.handle_mem_event(rs1 as u64, rr_record.prev_timestamp);
133        self.gas_calculator.handle_mem_event(rd as u64, rw_record.prev_timestamp);
134
135        Ok(())
136    }
137
138    /// Execute a store instruction.
139    pub fn execute_store(&mut self, instruction: &Instruction) -> Result<(), ExecutionError> {
140        let StoreResultSupervisor { addr, mw_record, rs1_record, rs2_record, rs1, rs2, .. } =
141            self.core.execute_store(instruction)?;
142
143        self.gas_calculator.handle_instruction(
144            instruction,
145            self.core.needs_bump_clk_high(),
146            false,
147            self.core.needs_state_bump(instruction),
148        );
149
150        self.gas_calculator.handle_mem_event(addr, mw_record.prev_timestamp);
151        self.gas_calculator.handle_mem_event(rs1 as u64, rs1_record.prev_timestamp);
152        self.gas_calculator.handle_mem_event(rs2 as u64, rs2_record.prev_timestamp);
153
154        Ok(())
155    }
156}
157
158impl GasEstimatingVM<'_, UserMode> {
159    /// Execute the program until it halts.
160    pub fn execute(&mut self) -> Result<ExecutionReport, ExecutionError> {
161        if self.core.is_done() {
162            return Ok(self.gas_calculator.generate_report());
163        }
164
165        loop {
166            match self.execute_instruction()? {
167                CycleResult::Done(false) => {}
168                CycleResult::TraceEnd | CycleResult::ShardBoundary | CycleResult::Done(true) => {
169                    return Ok(self.gas_calculator.generate_report());
170                }
171            }
172        }
173    }
174
175    /// Execute the next instruction at the current PC.
176    fn execute_instruction(&mut self) -> Result<CycleResult, ExecutionError> {
177        let FetchResult { instruction, mr_record, pc, error } = self.core.fetch()?;
178
179        if let Some(error) = error {
180            self.handle_error(error)?;
181            self.gas_calculator.handle_page_prot_event(
182                pc >> LOG_PAGE_SIZE,
183                mr_record.unwrap().prev_page_prot_record.unwrap().timestamp,
184            );
185            self.gas_calculator.handle_page_prot_check();
186            self.gas_calculator.handle_trap_exec_event();
187            self.gas_calculator.handle_trap_events(self.core.needs_bump_clk_high());
188            self.gas_calculator.update_page_chip_counts();
189            return Ok(self.core.advance());
190        }
191
192        if instruction.is_none() {
193            unreachable!("Fetching the next instruction failed");
194        }
195
196        if let Some(mr_record) = mr_record {
197            self.gas_calculator.handle_untrusted_instruction();
198            self.gas_calculator.handle_mem_event(pc & !0b111, mr_record.prev_timestamp);
199            self.gas_calculator.handle_page_prot_event(
200                pc >> LOG_PAGE_SIZE,
201                mr_record.prev_page_prot_record.unwrap().timestamp,
202            );
203            self.gas_calculator.handle_page_prot_check();
204        }
205
206        // SAFETY: The instruction is guaranteed to be valid as we checked for `is_none` above.
207        let instruction = unsafe { instruction.unwrap_unchecked() };
208
209        match &instruction.opcode {
210            Opcode::ADD
211            | Opcode::ADDI
212            | Opcode::SUB
213            | Opcode::XOR
214            | Opcode::OR
215            | Opcode::AND
216            | Opcode::SLL
217            | Opcode::SLLW
218            | Opcode::SRL
219            | Opcode::SRA
220            | Opcode::SRLW
221            | Opcode::SRAW
222            | Opcode::SLT
223            | Opcode::SLTU
224            | Opcode::MUL
225            | Opcode::MULHU
226            | Opcode::MULHSU
227            | Opcode::MULH
228            | Opcode::MULW
229            | Opcode::DIVU
230            | Opcode::REMU
231            | Opcode::DIV
232            | Opcode::REM
233            | Opcode::DIVW
234            | Opcode::ADDW
235            | Opcode::SUBW
236            | Opcode::DIVUW
237            | Opcode::REMUW
238            | Opcode::REMW => {
239                self.execute_alu(&instruction);
240            }
241            Opcode::LB
242            | Opcode::LBU
243            | Opcode::LH
244            | Opcode::LHU
245            | Opcode::LW
246            | Opcode::LWU
247            | Opcode::LD => self.execute_load(&instruction)?,
248            Opcode::SB | Opcode::SH | Opcode::SW | Opcode::SD => {
249                self.execute_store(&instruction)?;
250            }
251            Opcode::JAL | Opcode::JALR => {
252                self.execute_jump(&instruction);
253            }
254            Opcode::BEQ | Opcode::BNE | Opcode::BLT | Opcode::BGE | Opcode::BLTU | Opcode::BGEU => {
255                self.execute_branch(&instruction);
256            }
257            Opcode::LUI | Opcode::AUIPC => {
258                self.execute_utype(&instruction);
259            }
260            Opcode::ECALL => self.execute_ecall(&instruction)?,
261            Opcode::EBREAK | Opcode::UNIMP => {
262                unreachable!("Invalid opcode for `execute_instruction`: {:?}", instruction.opcode)
263            }
264        }
265
266        self.gas_calculator.update_page_chip_counts();
267        Ok(self.core.advance())
268    }
269}
270
271impl GasEstimatingVM<'_, UserMode> {
272    /// Execute a load instruction.
273    ///
274    /// This method will update the local memory access for the memory read, the register read,
275    /// and the register write.
276    ///
277    /// It will also emit the memory instruction event and the events for the load instruction.
278    pub fn execute_load(&mut self, instruction: &Instruction) -> Result<(), ExecutionError> {
279        let LoadResult { addr, rd, mr_record, error, rr_record, rw_record, rs1, .. } =
280            self.core.execute_load(instruction)?;
281
282        if let Some(error) = error {
283            self.handle_error(error)?;
284            self.gas_calculator.handle_trap_mem_event();
285        } else {
286            self.gas_calculator.handle_mem_event(addr, mr_record.prev_timestamp);
287        }
288
289        if let Some(record) = mr_record.prev_page_prot_record {
290            self.gas_calculator.handle_page_prot_event(record.page_idx, record.timestamp);
291            self.gas_calculator.handle_page_prot_check();
292        }
293
294        self.gas_calculator.handle_mem_event(rs1 as u64, rr_record.prev_timestamp);
295        self.gas_calculator.handle_mem_event(rd as u64, rw_record.prev_timestamp);
296
297        self.gas_calculator.handle_instruction(
298            instruction,
299            self.core.needs_bump_clk_high(),
300            rd == Register::X0,
301            self.core.needs_state_bump(instruction),
302        );
303
304        Ok(())
305    }
306
307    /// Execute a store instruction.
308    ///
309    /// This method will update the local memory access for the memory read, the register read,
310    /// and the register write.
311    ///
312    /// It will also emit the memory instruction event and the events for the store instruction.
313    pub fn execute_store(&mut self, instruction: &Instruction) -> Result<(), ExecutionError> {
314        let StoreResult { addr, mw_record, error, rs1_record, rs2_record, rs1, rs2, .. } =
315            self.core.execute_store(instruction)?;
316
317        if let Some(error) = error {
318            self.handle_error(error)?;
319            self.gas_calculator.handle_trap_mem_event();
320        } else {
321            self.gas_calculator.handle_mem_event(addr, mw_record.prev_timestamp);
322        }
323
324        if let Some(record) = mw_record.prev_page_prot_record {
325            self.gas_calculator.handle_page_prot_event(record.page_idx, record.timestamp);
326            self.gas_calculator.handle_page_prot_check();
327        }
328
329        self.gas_calculator.handle_mem_event(rs1 as u64, rs1_record.prev_timestamp);
330        self.gas_calculator.handle_mem_event(rs2 as u64, rs2_record.prev_timestamp);
331
332        self.gas_calculator.handle_instruction(
333            instruction,
334            self.core.needs_bump_clk_high(),
335            false,
336            self.core.needs_state_bump(instruction),
337        );
338
339        Ok(())
340    }
341}
342
343impl<'a, M: ExecutionMode> GasEstimatingVM<'a, M> {
344    /// Create a new gas estimating VM from a minimal trace.
345    pub fn new<T: MinimalTrace>(
346        trace: &'a T,
347        program: Arc<Program>,
348        proof_nonce: [u32; PROOF_NONCE_NUM_WORDS],
349        opts: SP1CoreOpts,
350    ) -> Self {
351        let enable_untrusted_programs = program.enable_untrusted_programs;
352        Self {
353            core: CoreVM::new(trace, program, opts, proof_nonce),
354            gas_calculator: ReportGenerator::new(trace.clk_start(), enable_untrusted_programs),
355            hint_lens_idx: 0,
356            _mode: PhantomData,
357        }
358    }
359
360    /// Handles recoverable errors such as traps.
361    pub fn handle_error(&mut self, e: TrapError) -> Result<(), ExecutionError> {
362        let TrapResult { context, code_record, pc_record, handler_record } =
363            self.core.handle_error(e)?;
364
365        self.gas_calculator.handle_mem_event(context, handler_record.prev_timestamp);
366        self.gas_calculator.handle_mem_event(context + 8, code_record.prev_timestamp);
367        self.gas_calculator.handle_mem_event(context + 16, pc_record.prev_timestamp);
368
369        Ok(())
370    }
371
372    /// Execute an ALU instruction and emit the events.
373    #[inline]
374    pub fn execute_alu(&mut self, instruction: &Instruction) {
375        let AluResult { rd, rw_record, rs1, rs2, .. } = self.core.execute_alu(instruction);
376
377        self.gas_calculator.handle_mem_event(rd as u64, rw_record.prev_timestamp);
378
379        if let MaybeImmediate::Register(register, record) = rs1 {
380            self.gas_calculator.handle_mem_event(register as u64, record.prev_timestamp);
381        }
382
383        if let MaybeImmediate::Register(register, record) = rs2 {
384            self.gas_calculator.handle_mem_event(register as u64, record.prev_timestamp);
385        }
386
387        self.gas_calculator.handle_instruction(
388            instruction,
389            self.core.needs_bump_clk_high(),
390            false,
391            self.core.needs_state_bump(instruction),
392        );
393    }
394
395    /// Execute a jump instruction and emit the events.
396    #[inline]
397    pub fn execute_jump(&mut self, instruction: &Instruction) {
398        let JumpResult { rd, rd_record, rs1, .. } = self.core.execute_jump(instruction);
399
400        self.gas_calculator.handle_mem_event(rd as u64, rd_record.prev_timestamp);
401
402        if let MaybeImmediate::Register(register, record) = rs1 {
403            self.gas_calculator.handle_mem_event(register as u64, record.prev_timestamp);
404        }
405
406        self.gas_calculator.handle_instruction(
407            instruction,
408            self.core.needs_bump_clk_high(),
409            false,
410            self.core.needs_state_bump(instruction),
411        );
412    }
413
414    /// Execute a branch instruction and emit the events.
415    #[inline]
416    pub fn execute_branch(&mut self, instruction: &Instruction) {
417        let BranchResult { rs1, a_record, rs2, b_record, .. } =
418            self.core.execute_branch(instruction);
419
420        self.gas_calculator.handle_mem_event(rs1 as u64, a_record.prev_timestamp);
421        self.gas_calculator.handle_mem_event(rs2 as u64, b_record.prev_timestamp);
422
423        self.gas_calculator.handle_instruction(
424            instruction,
425            self.core.needs_bump_clk_high(),
426            false,
427            self.core.needs_state_bump(instruction),
428        );
429    }
430
431    /// Execute a U-type instruction and emit the events.
432    #[inline]
433    pub fn execute_utype(&mut self, instruction: &Instruction) {
434        let UTypeResult { rd, rw_record, .. } = self.core.execute_utype(instruction);
435
436        self.gas_calculator.handle_mem_event(rd as u64, rw_record.prev_timestamp);
437
438        self.gas_calculator.handle_instruction(
439            instruction,
440            self.core.needs_bump_clk_high(),
441            false,
442            self.core.needs_state_bump(instruction),
443        );
444    }
445
446    /// Execute an ecall instruction and emit the events.
447    #[inline]
448    pub fn execute_ecall(&mut self, instruction: &Instruction) -> Result<(), ExecutionError> {
449        let code = self.core.read_code();
450
451        if code == SyscallCode::HINT_LEN {
452            self.hint_lens_idx += 1;
453        }
454
455        let result = CoreVM::execute_ecall(self, instruction, code)?;
456
457        if let Some(error) = result.error {
458            self.handle_error(error)?;
459        }
460
461        if let Some(record) = result.sig_return_pc_record {
462            self.gas_calculator.handle_mem_event(result.b, record.prev_timestamp);
463        }
464
465        if code == SyscallCode::HALT {
466            self.gas_calculator.set_exit_code(result.b);
467        }
468
469        if code.should_send() == 1 {
470            if self.core.is_retained_syscall(code) {
471                self.gas_calculator.handle_retained_syscall(code);
472            } else {
473                self.gas_calculator.syscall_sent(code);
474            }
475        }
476
477        self.gas_calculator.handle_instruction(
478            instruction,
479            self.core.needs_bump_clk_high(),
480            false,
481            self.core.needs_state_bump(instruction),
482        );
483
484        Ok(())
485    }
486}
487
488impl<'a, M: ExecutionMode> SyscallRuntime<'a, M> for GasEstimatingVM<'a, M> {
489    const TRACING: bool = false;
490
491    fn core(&self) -> &CoreVM<'a, M> {
492        &self.core
493    }
494
495    fn core_mut(&mut self) -> &mut CoreVM<'a, M> {
496        &mut self.core
497    }
498
499    fn rr(&mut self, register: usize) -> MemoryReadRecord {
500        let record = SyscallRuntime::rr(self.core_mut(), register);
501        self.gas_calculator.handle_mem_event(register as u64, record.prev_timestamp);
502        record
503    }
504
505    fn rw(&mut self, register: usize, value: u64) -> MemoryWriteRecord {
506        let record = SyscallRuntime::rw(self.core_mut(), register, value);
507        self.gas_calculator.handle_mem_event(register as u64, record.prev_timestamp);
508        record
509    }
510
511    fn page_prot_write(&mut self, page_idx: u64, prot: u8) -> PageProtRecord {
512        let prev_page_prot_record = self.core_mut().page_prot_write(page_idx, prot);
513        self.gas_calculator.handle_page_prot_event(
514            prev_page_prot_record.page_idx,
515            prev_page_prot_record.timestamp,
516        );
517        prev_page_prot_record
518    }
519
520    fn page_prot_range_check(
521        &mut self,
522        start_page_idx: u64,
523        end_page_idx: u64,
524        page_prot_bitmap: u8,
525    ) -> (Vec<PageProtRecord>, Option<TrapError>) {
526        let (page_prot_records, error) =
527            self.core_mut().page_prot_range_check(start_page_idx, end_page_idx, page_prot_bitmap);
528        for record in page_prot_records.iter() {
529            self.gas_calculator.handle_page_prot_event(record.page_idx, record.timestamp);
530        }
531        (page_prot_records, error)
532    }
533
534    fn mr_without_prot(&mut self, addr: u64) -> MemoryReadRecord {
535        let record = self.core_mut().mr_without_prot(addr);
536        self.gas_calculator.handle_mem_event(addr, record.prev_timestamp);
537        record
538    }
539
540    fn mw_without_prot(&mut self, addr: u64) -> MemoryWriteRecord {
541        let record = self.core_mut().mw_without_prot(addr);
542        self.gas_calculator.handle_mem_event(addr, record.prev_timestamp);
543        record
544    }
545
546    fn mr_slice_without_prot(&mut self, addr: u64, len: usize) -> Vec<MemoryReadRecord> {
547        let records = self.core_mut().mr_slice_without_prot(addr, len);
548        for (i, record) in records.iter().enumerate() {
549            self.gas_calculator.handle_mem_event(addr + i as u64 * 8, record.prev_timestamp);
550        }
551
552        records
553    }
554
555    fn mw_slice_without_prot(&mut self, addr: u64, len: usize) -> Vec<MemoryWriteRecord> {
556        let records = self.core_mut().mw_slice_without_prot(addr, len);
557        for (i, record) in records.iter().enumerate() {
558            self.gas_calculator.handle_mem_event(addr + i as u64 * 8, record.prev_timestamp);
559        }
560
561        records
562    }
563}
564
565/// Wrapper enum to handle `GasEstimatingVM` with different execution modes at runtime.
566pub enum GasEstimatingVMEnum<'a> {
567    /// `GasEstimatingVM` for `SupervisorMode`.
568    Supervisor(GasEstimatingVM<'a, SupervisorMode>),
569    /// `GasEstimatingVM` for `UserMode`.
570    User(GasEstimatingVM<'a, UserMode>),
571}
572
573impl<'a> GasEstimatingVMEnum<'a> {
574    /// Create a new `GasEstimatingVMEnum` based on program's `enable_untrusted_programs` flag.
575    pub fn new<T: MinimalTrace>(
576        trace: &'a T,
577        program: Arc<Program>,
578        proof_nonce: [u32; PROOF_NONCE_NUM_WORDS],
579        opts: SP1CoreOpts,
580    ) -> Self {
581        if program.enable_untrusted_programs {
582            Self::User(GasEstimatingVM::<UserMode>::new(trace, program, proof_nonce, opts))
583        } else {
584            Self::Supervisor(GasEstimatingVM::<SupervisorMode>::new(
585                trace,
586                program,
587                proof_nonce,
588                opts,
589            ))
590        }
591    }
592
593    /// Execute the program until it halts.
594    pub fn execute(&mut self) -> Result<ExecutionReport, ExecutionError> {
595        match self {
596            Self::Supervisor(vm) => vm.execute(),
597            Self::User(vm) => vm.execute(),
598        }
599    }
600
601    /// Check if the VM has completed execution.
602    #[must_use]
603    pub fn is_done(&self) -> bool {
604        match self {
605            Self::Supervisor(vm) => vm.core.is_done(),
606            Self::User(vm) => vm.core.is_done(),
607        }
608    }
609
610    /// Get the current PC.
611    #[must_use]
612    pub fn pc(&self) -> u64 {
613        match self {
614            Self::Supervisor(vm) => vm.core.pc(),
615            Self::User(vm) => vm.core.pc(),
616        }
617    }
618
619    /// Get the registers.
620    #[must_use]
621    pub fn registers(&self) -> [MemoryRecord; 32] {
622        match self {
623            Self::Supervisor(vm) => *vm.core.registers(),
624            Self::User(vm) => *vm.core.registers(),
625        }
626    }
627
628    /// Get the exit code.
629    #[must_use]
630    pub fn exit_code(&self) -> u32 {
631        match self {
632            Self::Supervisor(vm) => vm.core.exit_code(),
633            Self::User(vm) => vm.core.exit_code(),
634        }
635    }
636
637    /// Get the current clock.
638    #[must_use]
639    pub fn clk(&self) -> u64 {
640        match self {
641            Self::Supervisor(vm) => vm.core.clk(),
642            Self::User(vm) => vm.core.clk(),
643        }
644    }
645
646    /// Get the public value digest.
647    #[must_use]
648    pub fn public_value_digest(&self) -> [u32; sp1_hypercube::air::PV_DIGEST_NUM_WORDS] {
649        match self {
650            Self::Supervisor(vm) => vm.core.public_value_digest,
651            Self::User(vm) => vm.core.public_value_digest,
652        }
653    }
654
655    /// Get the proof nonce.
656    #[must_use]
657    pub fn proof_nonce(&self) -> [u32; sp1_hypercube::air::PROOF_NONCE_NUM_WORDS] {
658        match self {
659            Self::Supervisor(vm) => vm.core.proof_nonce,
660            Self::User(vm) => vm.core.proof_nonce,
661        }
662    }
663}