use std::{
fs::File,
io::{BufWriter, Write},
sync::Arc,
};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use sp1_stark::SP1CoreOpts;
use thiserror::Error;
use crate::{
context::SP1Context,
dependencies::{emit_cpu_dependencies, emit_divrem_dependencies},
events::{
create_alu_lookup_id, create_alu_lookups, AluEvent, CpuEvent, LookupId,
MemoryAccessPosition, MemoryInitializeFinalizeEvent, MemoryLocalEvent, MemoryReadRecord,
MemoryRecord, MemoryWriteRecord, SyscallEvent,
},
hook::{HookEnv, HookRegistry},
memory::{Entry, PagedMemory},
record::{ExecutionRecord, MemoryAccessRecord},
report::ExecutionReport,
state::{ExecutionState, ForkState},
subproof::{DefaultSubproofVerifier, SubproofVerifier},
syscalls::{default_syscall_map, Syscall, SyscallCode, SyscallContext},
Instruction, Opcode, Program, Register,
};
#[repr(C)]
pub struct Executor<'a> {
pub program: Arc<Program>,
pub executor_mode: ExecutorMode,
pub unconstrained: bool,
pub print_report: bool,
pub shard_size: u32,
pub shard_batch_size: u32,
pub max_syscall_cycles: u32,
pub syscall_map: HashMap<SyscallCode, Arc<dyn Syscall>>,
pub opts: SP1CoreOpts,
pub memory_checkpoint: PagedMemory<Option<MemoryRecord>>,
pub uninitialized_memory_checkpoint: PagedMemory<bool>,
pub memory_accesses: MemoryAccessRecord,
pub max_cycles: Option<u64>,
pub state: ExecutionState,
pub record: ExecutionRecord,
pub records: Vec<ExecutionRecord>,
pub local_memory_access: HashMap<u32, MemoryLocalEvent>,
pub cycle_tracker: HashMap<String, (u64, u32)>,
pub io_buf: HashMap<u32, String>,
pub trace_buf: Option<BufWriter<File>>,
pub unconstrained_state: ForkState,
pub report: ExecutionReport,
pub subproof_verifier: Arc<dyn SubproofVerifier + 'a>,
pub hook_registry: HookRegistry<'a>,
pub maximal_shapes: Option<Vec<HashMap<String, usize>>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExecutorMode {
Simple,
Checkpoint,
Trace,
}
#[derive(Error, Debug, Serialize, Deserialize)]
pub enum ExecutionError {
#[error("execution failed with exit code {0}")]
HaltWithNonZeroExitCode(u32),
#[error("invalid memory access for opcode {0} and address {1}")]
InvalidMemoryAccess(Opcode, u32),
#[error("unimplemented syscall {0}")]
UnsupportedSyscall(u32),
#[error("breakpoint encountered")]
Breakpoint(),
#[error("exceeded cycle limit of {0}")]
ExceededCycleLimit(u64),
#[error("syscall called in unconstrained mode")]
InvalidSyscallUsage(u64),
#[error("got unimplemented as opcode")]
Unimplemented(),
#[error("program ended in unconstrained mode")]
EndInUnconstrained(),
}
macro_rules! assert_valid_memory_access {
($addr:expr, $position:expr) => {
#[cfg(not(debug_assertions))]
{}
};
}
impl<'a> Executor<'a> {
#[must_use]
pub fn new(program: Program, opts: SP1CoreOpts) -> Self {
Self::with_context(program, opts, SP1Context::default())
}
#[must_use]
pub fn with_context(program: Program, opts: SP1CoreOpts, context: SP1Context<'a>) -> Self {
let program = Arc::new(program);
let record = ExecutionRecord::new(program.clone());
let trace_buf = if let Ok(trace_file) = std::env::var("TRACE_FILE") {
let file = File::create(trace_file).unwrap();
Some(BufWriter::new(file))
} else {
None
};
let syscall_map = default_syscall_map();
let max_syscall_cycles =
syscall_map.values().map(|syscall| syscall.num_extra_cycles()).max().unwrap_or(0);
let subproof_verifier =
context.subproof_verifier.unwrap_or_else(|| Arc::new(DefaultSubproofVerifier::new()));
let hook_registry = context.hook_registry.unwrap_or_default();
Self {
record,
records: vec![],
state: ExecutionState::new(program.pc_start),
program,
memory_accesses: MemoryAccessRecord::default(),
shard_size: (opts.shard_size as u32) * 4,
shard_batch_size: opts.shard_batch_size as u32,
cycle_tracker: HashMap::new(),
io_buf: HashMap::new(),
trace_buf,
unconstrained: false,
unconstrained_state: ForkState::default(),
syscall_map,
executor_mode: ExecutorMode::Trace,
max_syscall_cycles,
report: ExecutionReport::default(),
print_report: false,
subproof_verifier,
hook_registry,
opts,
max_cycles: context.max_cycles,
memory_checkpoint: PagedMemory::new_preallocated(),
uninitialized_memory_checkpoint: PagedMemory::new_preallocated(),
local_memory_access: HashMap::new(),
maximal_shapes: None,
}
}
pub fn hook(&self, fd: u32, buf: &[u8]) -> eyre::Result<Vec<Vec<u8>>> {
Ok(self
.hook_registry
.get(fd)
.ok_or(eyre::eyre!("no hook found for file descriptor {}", fd))?
.invoke_hook(self.hook_env(), buf))
}
#[must_use]
pub fn hook_env<'b>(&'b self) -> HookEnv<'b, 'a> {
HookEnv { runtime: self }
}
#[must_use]
pub fn recover(program: Program, state: ExecutionState, opts: SP1CoreOpts) -> Self {
let mut runtime = Self::new(program, opts);
runtime.state = state;
runtime
}
#[allow(clippy::single_match_else)]
#[must_use]
pub fn registers(&mut self) -> [u32; 32] {
let mut registers = [0; 32];
for i in 0..32 {
let addr = Register::from_u32(i as u32) as u32;
let record = self.state.memory.get(addr);
if self.executor_mode == ExecutorMode::Checkpoint || self.unconstrained {
match record {
Some(record) => {
self.memory_checkpoint.entry(addr).or_insert_with(|| Some(*record));
}
None => {
self.memory_checkpoint.entry(addr).or_insert(None);
}
}
}
registers[i] = match record {
Some(record) => record.value,
None => 0,
};
}
registers
}
#[must_use]
pub fn register(&mut self, register: Register) -> u32 {
let addr = register as u32;
let record = self.state.memory.get(addr);
if self.executor_mode == ExecutorMode::Checkpoint || self.unconstrained {
match record {
Some(record) => {
self.memory_checkpoint.entry(addr).or_insert_with(|| Some(*record));
}
None => {
self.memory_checkpoint.entry(addr).or_insert(None);
}
}
}
match record {
Some(record) => record.value,
None => 0,
}
}
#[must_use]
pub fn word(&mut self, addr: u32) -> u32 {
#[allow(clippy::single_match_else)]
let record = self.state.memory.get(addr);
if self.executor_mode == ExecutorMode::Checkpoint || self.unconstrained {
match record {
Some(record) => {
self.memory_checkpoint.entry(addr).or_insert_with(|| Some(*record));
}
None => {
self.memory_checkpoint.entry(addr).or_insert(None);
}
}
}
match record {
Some(record) => record.value,
None => 0,
}
}
#[must_use]
pub fn byte(&mut self, addr: u32) -> u8 {
let word = self.word(addr - addr % 4);
(word >> ((addr % 4) * 8)) as u8
}
#[must_use]
pub const fn timestamp(&self, position: &MemoryAccessPosition) -> u32 {
self.state.clk + *position as u32
}
#[must_use]
#[inline]
pub fn shard(&self) -> u32 {
self.state.current_shard
}
pub fn mr(
&mut self,
addr: u32,
shard: u32,
timestamp: u32,
local_memory_access: Option<&mut HashMap<u32, MemoryLocalEvent>>,
) -> MemoryReadRecord {
let entry = self.state.memory.entry(addr);
if self.executor_mode == ExecutorMode::Checkpoint || self.unconstrained {
match entry {
Entry::Occupied(ref entry) => {
let record = entry.get();
self.memory_checkpoint.entry(addr).or_insert_with(|| Some(*record));
}
Entry::Vacant(_) => {
self.memory_checkpoint.entry(addr).or_insert(None);
}
}
}
if self.unconstrained {
let record = match entry {
Entry::Occupied(ref entry) => Some(entry.get()),
Entry::Vacant(_) => None,
};
self.unconstrained_state.memory_diff.entry(addr).or_insert(record.copied());
}
let record: &mut MemoryRecord = match entry {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
let value = self.state.uninitialized_memory.get(addr).unwrap_or(&0);
self.uninitialized_memory_checkpoint.entry(addr).or_insert_with(|| *value != 0);
entry.insert(MemoryRecord { value: *value, shard: 0, timestamp: 0 })
}
};
let prev_record = *record;
record.shard = shard;
record.timestamp = timestamp;
if !self.unconstrained {
let local_memory_access = if let Some(local_memory_access) = local_memory_access {
local_memory_access
} else {
&mut self.local_memory_access
};
local_memory_access
.entry(addr)
.and_modify(|e| {
e.final_mem_access = *record;
})
.or_insert(MemoryLocalEvent {
addr,
initial_mem_access: prev_record,
final_mem_access: *record,
});
}
MemoryReadRecord::new(
record.value,
record.shard,
record.timestamp,
prev_record.shard,
prev_record.timestamp,
)
}
pub fn mw(
&mut self,
addr: u32,
value: u32,
shard: u32,
timestamp: u32,
local_memory_access: Option<&mut HashMap<u32, MemoryLocalEvent>>,
) -> MemoryWriteRecord {
let entry = self.state.memory.entry(addr);
if self.executor_mode == ExecutorMode::Checkpoint || self.unconstrained {
match entry {
Entry::Occupied(ref entry) => {
let record = entry.get();
self.memory_checkpoint.entry(addr).or_insert_with(|| Some(*record));
}
Entry::Vacant(_) => {
self.memory_checkpoint.entry(addr).or_insert(None);
}
}
}
if self.unconstrained {
let record = match entry {
Entry::Occupied(ref entry) => Some(entry.get()),
Entry::Vacant(_) => None,
};
self.unconstrained_state.memory_diff.entry(addr).or_insert(record.copied());
}
let record: &mut MemoryRecord = match entry {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
let value = self.state.uninitialized_memory.get(addr).unwrap_or(&0);
self.uninitialized_memory_checkpoint.entry(addr).or_insert_with(|| *value != 0);
entry.insert(MemoryRecord { value: *value, shard: 0, timestamp: 0 })
}
};
let prev_record = *record;
record.value = value;
record.shard = shard;
record.timestamp = timestamp;
if !self.unconstrained {
let local_memory_access = if let Some(local_memory_access) = local_memory_access {
local_memory_access
} else {
&mut self.local_memory_access
};
local_memory_access
.entry(addr)
.and_modify(|e| {
e.final_mem_access = *record;
})
.or_insert(MemoryLocalEvent {
addr,
initial_mem_access: prev_record,
final_mem_access: *record,
});
}
MemoryWriteRecord::new(
record.value,
record.shard,
record.timestamp,
prev_record.value,
prev_record.shard,
prev_record.timestamp,
)
}
pub fn mr_cpu(&mut self, addr: u32, position: MemoryAccessPosition) -> u32 {
assert_valid_memory_access!(addr, position);
let record = self.mr(addr, self.shard(), self.timestamp(&position), None);
if !self.unconstrained && self.executor_mode == ExecutorMode::Trace {
match position {
MemoryAccessPosition::A => self.memory_accesses.a = Some(record.into()),
MemoryAccessPosition::B => self.memory_accesses.b = Some(record.into()),
MemoryAccessPosition::C => self.memory_accesses.c = Some(record.into()),
MemoryAccessPosition::Memory => self.memory_accesses.memory = Some(record.into()),
}
}
record.value
}
pub fn mw_cpu(&mut self, addr: u32, value: u32, position: MemoryAccessPosition) {
assert_valid_memory_access!(addr, position);
let record = self.mw(addr, value, self.shard(), self.timestamp(&position), None);
if !self.unconstrained && self.executor_mode == ExecutorMode::Trace {
match position {
MemoryAccessPosition::A => {
assert!(self.memory_accesses.a.is_none());
self.memory_accesses.a = Some(record.into());
}
MemoryAccessPosition::B => {
assert!(self.memory_accesses.b.is_none());
self.memory_accesses.b = Some(record.into());
}
MemoryAccessPosition::C => {
assert!(self.memory_accesses.c.is_none());
self.memory_accesses.c = Some(record.into());
}
MemoryAccessPosition::Memory => {
assert!(self.memory_accesses.memory.is_none());
self.memory_accesses.memory = Some(record.into());
}
}
}
}
pub fn rr(&mut self, register: Register, position: MemoryAccessPosition) -> u32 {
self.mr_cpu(register as u32, position)
}
pub fn rw(&mut self, register: Register, value: u32) {
if register == Register::X0 {
self.mw_cpu(register as u32, 0, MemoryAccessPosition::A);
} else {
self.mw_cpu(register as u32, value, MemoryAccessPosition::A);
}
}
#[allow(clippy::too_many_arguments)]
fn emit_cpu(
&mut self,
shard: u32,
clk: u32,
pc: u32,
next_pc: u32,
instruction: Instruction,
a: u32,
b: u32,
c: u32,
memory_store_value: Option<u32>,
record: MemoryAccessRecord,
exit_code: u32,
lookup_id: LookupId,
syscall_lookup_id: LookupId,
) {
let cpu_event = CpuEvent {
shard,
clk,
pc,
next_pc,
instruction,
a,
a_record: record.a,
b,
b_record: record.b,
c,
c_record: record.c,
memory: memory_store_value,
memory_record: record.memory,
exit_code,
alu_lookup_id: lookup_id,
syscall_lookup_id,
memory_add_lookup_id: create_alu_lookup_id(),
memory_sub_lookup_id: create_alu_lookup_id(),
branch_lt_lookup_id: create_alu_lookup_id(),
branch_gt_lookup_id: create_alu_lookup_id(),
branch_add_lookup_id: create_alu_lookup_id(),
jump_jal_lookup_id: create_alu_lookup_id(),
jump_jalr_lookup_id: create_alu_lookup_id(),
auipc_lookup_id: create_alu_lookup_id(),
};
self.record.cpu_events.push(cpu_event);
emit_cpu_dependencies(self, &cpu_event);
}
fn emit_alu(&mut self, clk: u32, opcode: Opcode, a: u32, b: u32, c: u32, lookup_id: LookupId) {
let event = AluEvent {
lookup_id,
shard: self.shard(),
clk,
opcode,
a,
b,
c,
sub_lookups: create_alu_lookups(),
};
match opcode {
Opcode::ADD => {
self.record.add_events.push(event);
}
Opcode::SUB => {
self.record.sub_events.push(event);
}
Opcode::XOR | Opcode::OR | Opcode::AND => {
self.record.bitwise_events.push(event);
}
Opcode::SLL => {
self.record.shift_left_events.push(event);
}
Opcode::SRL | Opcode::SRA => {
self.record.shift_right_events.push(event);
}
Opcode::SLT | Opcode::SLTU => {
self.record.lt_events.push(event);
}
Opcode::MUL | Opcode::MULHU | Opcode::MULHSU | Opcode::MULH => {
self.record.mul_events.push(event);
}
Opcode::DIVU | Opcode::REMU | Opcode::DIV | Opcode::REM => {
self.record.divrem_events.push(event);
emit_divrem_dependencies(self, event);
}
_ => {}
}
}
#[inline]
pub(crate) fn syscall_event(
&self,
clk: u32,
syscall_id: u32,
arg1: u32,
arg2: u32,
lookup_id: LookupId,
) -> SyscallEvent {
SyscallEvent {
shard: self.shard(),
clk,
syscall_id,
arg1,
arg2,
lookup_id,
nonce: self.record.nonce_lookup[&lookup_id],
}
}
fn emit_syscall(
&mut self,
clk: u32,
syscall_id: u32,
arg1: u32,
arg2: u32,
lookup_id: LookupId,
) {
let syscall_event = self.syscall_event(clk, syscall_id, arg1, arg2, lookup_id);
self.record.syscall_events.push(syscall_event);
}
fn alu_rr(&mut self, instruction: &Instruction) -> (Register, u32, u32) {
if !instruction.imm_c {
let (rd, rs1, rs2) = instruction.r_type();
let c = self.rr(rs2, MemoryAccessPosition::C);
let b = self.rr(rs1, MemoryAccessPosition::B);
(rd, b, c)
} else if !instruction.imm_b && instruction.imm_c {
let (rd, rs1, imm) = instruction.i_type();
let (rd, b, c) = (rd, self.rr(rs1, MemoryAccessPosition::B), imm);
(rd, b, c)
} else {
assert!(instruction.imm_b && instruction.imm_c);
let (rd, b, c) =
(Register::from_u32(instruction.op_a), instruction.op_b, instruction.op_c);
(rd, b, c)
}
}
fn alu_rw(
&mut self,
instruction: &Instruction,
rd: Register,
a: u32,
b: u32,
c: u32,
lookup_id: LookupId,
) {
self.rw(rd, a);
if self.executor_mode == ExecutorMode::Trace {
self.emit_alu(self.state.clk, instruction.opcode, a, b, c, lookup_id);
}
}
fn load_rr(&mut self, instruction: &Instruction) -> (Register, u32, u32, u32, u32) {
let (rd, rs1, imm) = instruction.i_type();
let (b, c) = (self.rr(rs1, MemoryAccessPosition::B), imm);
let addr = b.wrapping_add(c);
let memory_value = self.mr_cpu(align(addr), MemoryAccessPosition::Memory);
(rd, b, c, addr, memory_value)
}
fn store_rr(&mut self, instruction: &Instruction) -> (u32, u32, u32, u32, u32) {
let (rs1, rs2, imm) = instruction.s_type();
let c = imm;
let b = self.rr(rs2, MemoryAccessPosition::B);
let a = self.rr(rs1, MemoryAccessPosition::A);
let addr = b.wrapping_add(c);
let memory_value = self.word(align(addr));
(a, b, c, addr, memory_value)
}
fn branch_rr(&mut self, instruction: &Instruction) -> (u32, u32, u32) {
let (rs1, rs2, imm) = instruction.b_type();
let c = imm;
let b = self.rr(rs2, MemoryAccessPosition::B);
let a = self.rr(rs1, MemoryAccessPosition::A);
(a, b, c)
}
#[inline]
fn fetch(&self) -> Instruction {
let idx = ((self.state.pc - self.program.pc_base) / 4) as usize;
self.program.instructions[idx]
}
#[allow(clippy::too_many_lines)]
fn execute_instruction(&mut self, instruction: &Instruction) -> Result<(), ExecutionError> {
let mut pc = self.state.pc;
let mut clk = self.state.clk;
let mut exit_code = 0u32;
let mut next_pc = self.state.pc.wrapping_add(4);
let rd: Register;
let (a, b, c): (u32, u32, u32);
let (addr, memory_read_value): (u32, u32);
let mut memory_store_value: Option<u32> = None;
if self.executor_mode == ExecutorMode::Trace {
self.memory_accesses = MemoryAccessRecord::default();
}
let lookup_id = if self.executor_mode == ExecutorMode::Trace {
create_alu_lookup_id()
} else {
LookupId::default()
};
let syscall_lookup_id = if self.executor_mode == ExecutorMode::Trace {
create_alu_lookup_id()
} else {
LookupId::default()
};
if !self.unconstrained {
self.report.opcode_counts[instruction.opcode] += 1;
self.report.event_counts[instruction.opcode] += 1;
match instruction.opcode {
Opcode::LB | Opcode::LH | Opcode::LW | Opcode::LBU | Opcode::LHU => {
self.report.event_counts[Opcode::ADD] += 2;
}
Opcode::JAL | Opcode::JALR | Opcode::AUIPC => {
self.report.event_counts[Opcode::ADD] += 1;
}
Opcode::BEQ
| Opcode::BNE
| Opcode::BLT
| Opcode::BGE
| Opcode::BLTU
| Opcode::BGEU => {
self.report.event_counts[Opcode::ADD] += 1;
self.report.event_counts[Opcode::SLTU] += 2;
}
Opcode::DIVU | Opcode::REMU | Opcode::DIV | Opcode::REM => {
self.report.event_counts[Opcode::MUL] += 2;
self.report.event_counts[Opcode::ADD] += 2;
self.report.event_counts[Opcode::SLTU] += 1;
}
_ => {}
};
}
match instruction.opcode {
Opcode::ADD => {
(rd, b, c) = self.alu_rr(instruction);
a = b.wrapping_add(c);
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::SUB => {
(rd, b, c) = self.alu_rr(instruction);
a = b.wrapping_sub(c);
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::XOR => {
(rd, b, c) = self.alu_rr(instruction);
a = b ^ c;
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::OR => {
(rd, b, c) = self.alu_rr(instruction);
a = b | c;
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::AND => {
(rd, b, c) = self.alu_rr(instruction);
a = b & c;
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::SLL => {
(rd, b, c) = self.alu_rr(instruction);
a = b.wrapping_shl(c);
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::SRL => {
(rd, b, c) = self.alu_rr(instruction);
a = b.wrapping_shr(c);
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::SRA => {
(rd, b, c) = self.alu_rr(instruction);
a = (b as i32).wrapping_shr(c) as u32;
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::SLT => {
(rd, b, c) = self.alu_rr(instruction);
a = if (b as i32) < (c as i32) { 1 } else { 0 };
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::SLTU => {
(rd, b, c) = self.alu_rr(instruction);
a = if b < c { 1 } else { 0 };
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::LB => {
(rd, b, c, addr, memory_read_value) = self.load_rr(instruction);
let value = (memory_read_value).to_le_bytes()[(addr % 4) as usize];
a = ((value as i8) as i32) as u32;
memory_store_value = Some(memory_read_value);
self.rw(rd, a);
}
Opcode::LH => {
(rd, b, c, addr, memory_read_value) = self.load_rr(instruction);
if addr % 2 != 0 {
return Err(ExecutionError::InvalidMemoryAccess(Opcode::LH, addr));
}
let value = match (addr >> 1) % 2 {
0 => memory_read_value & 0x0000_FFFF,
1 => (memory_read_value & 0xFFFF_0000) >> 16,
_ => unreachable!(),
};
a = ((value as i16) as i32) as u32;
memory_store_value = Some(memory_read_value);
self.rw(rd, a);
}
Opcode::LW => {
(rd, b, c, addr, memory_read_value) = self.load_rr(instruction);
if addr % 4 != 0 {
return Err(ExecutionError::InvalidMemoryAccess(Opcode::LW, addr));
}
a = memory_read_value;
memory_store_value = Some(memory_read_value);
self.rw(rd, a);
}
Opcode::LBU => {
(rd, b, c, addr, memory_read_value) = self.load_rr(instruction);
let value = (memory_read_value).to_le_bytes()[(addr % 4) as usize];
a = value as u32;
memory_store_value = Some(memory_read_value);
self.rw(rd, a);
}
Opcode::LHU => {
(rd, b, c, addr, memory_read_value) = self.load_rr(instruction);
if addr % 2 != 0 {
return Err(ExecutionError::InvalidMemoryAccess(Opcode::LHU, addr));
}
let value = match (addr >> 1) % 2 {
0 => memory_read_value & 0x0000_FFFF,
1 => (memory_read_value & 0xFFFF_0000) >> 16,
_ => unreachable!(),
};
a = (value as u16) as u32;
memory_store_value = Some(memory_read_value);
self.rw(rd, a);
}
Opcode::SB => {
(a, b, c, addr, memory_read_value) = self.store_rr(instruction);
let value = match addr % 4 {
0 => (a & 0x0000_00FF) + (memory_read_value & 0xFFFF_FF00),
1 => ((a & 0x0000_00FF) << 8) + (memory_read_value & 0xFFFF_00FF),
2 => ((a & 0x0000_00FF) << 16) + (memory_read_value & 0xFF00_FFFF),
3 => ((a & 0x0000_00FF) << 24) + (memory_read_value & 0x00FF_FFFF),
_ => unreachable!(),
};
memory_store_value = Some(value);
self.mw_cpu(align(addr), value, MemoryAccessPosition::Memory);
}
Opcode::SH => {
(a, b, c, addr, memory_read_value) = self.store_rr(instruction);
if addr % 2 != 0 {
return Err(ExecutionError::InvalidMemoryAccess(Opcode::SH, addr));
}
let value = match (addr >> 1) % 2 {
0 => (a & 0x0000_FFFF) + (memory_read_value & 0xFFFF_0000),
1 => ((a & 0x0000_FFFF) << 16) + (memory_read_value & 0x0000_FFFF),
_ => unreachable!(),
};
memory_store_value = Some(value);
self.mw_cpu(align(addr), value, MemoryAccessPosition::Memory);
}
Opcode::SW => {
(a, b, c, addr, _) = self.store_rr(instruction);
if addr % 4 != 0 {
return Err(ExecutionError::InvalidMemoryAccess(Opcode::SW, addr));
}
let value = a;
memory_store_value = Some(value);
self.mw_cpu(align(addr), value, MemoryAccessPosition::Memory);
}
Opcode::BEQ => {
(a, b, c) = self.branch_rr(instruction);
if a == b {
next_pc = self.state.pc.wrapping_add(c);
}
}
Opcode::BNE => {
(a, b, c) = self.branch_rr(instruction);
if a != b {
next_pc = self.state.pc.wrapping_add(c);
}
}
Opcode::BLT => {
(a, b, c) = self.branch_rr(instruction);
if (a as i32) < (b as i32) {
next_pc = self.state.pc.wrapping_add(c);
}
}
Opcode::BGE => {
(a, b, c) = self.branch_rr(instruction);
if (a as i32) >= (b as i32) {
next_pc = self.state.pc.wrapping_add(c);
}
}
Opcode::BLTU => {
(a, b, c) = self.branch_rr(instruction);
if a < b {
next_pc = self.state.pc.wrapping_add(c);
}
}
Opcode::BGEU => {
(a, b, c) = self.branch_rr(instruction);
if a >= b {
next_pc = self.state.pc.wrapping_add(c);
}
}
Opcode::JAL => {
let (rd, imm) = instruction.j_type();
(b, c) = (imm, 0);
a = self.state.pc + 4;
self.rw(rd, a);
next_pc = self.state.pc.wrapping_add(imm);
}
Opcode::JALR => {
let (rd, rs1, imm) = instruction.i_type();
(b, c) = (self.rr(rs1, MemoryAccessPosition::B), imm);
a = self.state.pc + 4;
self.rw(rd, a);
next_pc = b.wrapping_add(c);
}
Opcode::AUIPC => {
let (rd, imm) = instruction.u_type();
(b, c) = (imm, imm);
a = self.state.pc.wrapping_add(b);
self.rw(rd, a);
}
Opcode::ECALL => {
let t0 = Register::X5;
let syscall_id = self.register(t0);
c = self.rr(Register::X11, MemoryAccessPosition::C);
b = self.rr(Register::X10, MemoryAccessPosition::B);
let syscall = SyscallCode::from_u32(syscall_id);
if self.print_report && !self.unconstrained {
self.report.syscall_counts[syscall] += 1;
}
if self.unconstrained
&& (syscall != SyscallCode::EXIT_UNCONSTRAINED && syscall != SyscallCode::WRITE)
{
return Err(ExecutionError::InvalidSyscallUsage(syscall_id as u64));
}
let syscall_for_count = syscall.count_map();
let syscall_count = self.state.syscall_counts.entry(syscall_for_count).or_insert(0);
let (threshold, multiplier) = match syscall_for_count {
SyscallCode::KECCAK_PERMUTE => (self.opts.split_opts.keccak, 24),
SyscallCode::SHA_EXTEND => (self.opts.split_opts.sha_extend, 48),
SyscallCode::SHA_COMPRESS => (self.opts.split_opts.sha_compress, 80),
_ => (self.opts.split_opts.deferred, 1),
};
let nonce = (((*syscall_count as usize) % threshold) * multiplier) as u32;
self.record.nonce_lookup.insert(syscall_lookup_id, nonce);
*syscall_count += 1;
let syscall_impl = self.get_syscall(syscall).cloned();
if syscall.should_send() != 0 {
self.emit_syscall(clk, syscall.syscall_id(), b, c, syscall_lookup_id);
}
let mut precompile_rt = SyscallContext::new(self);
precompile_rt.syscall_lookup_id = syscall_lookup_id;
let (precompile_next_pc, precompile_cycles, returned_exit_code) =
if let Some(syscall_impl) = syscall_impl {
let res = syscall_impl.execute(&mut precompile_rt, syscall, b, c);
if let Some(val) = res {
a = val;
} else {
a = syscall_id;
}
if syscall == SyscallCode::HALT && precompile_rt.exit_code != 0 {
return Err(ExecutionError::HaltWithNonZeroExitCode(
precompile_rt.exit_code,
));
}
(
precompile_rt.next_pc,
syscall_impl.num_extra_cycles(),
precompile_rt.exit_code,
)
} else {
return Err(ExecutionError::UnsupportedSyscall(syscall_id));
};
clk = self.state.clk;
pc = self.state.pc;
self.rw(t0, a);
next_pc = precompile_next_pc;
self.state.clk += precompile_cycles;
exit_code = returned_exit_code;
}
Opcode::EBREAK => {
return Err(ExecutionError::Breakpoint());
}
Opcode::MUL => {
(rd, b, c) = self.alu_rr(instruction);
a = b.wrapping_mul(c);
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::MULH => {
(rd, b, c) = self.alu_rr(instruction);
a = (((b as i32) as i64).wrapping_mul((c as i32) as i64) >> 32) as u32;
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::MULHU => {
(rd, b, c) = self.alu_rr(instruction);
a = ((b as u64).wrapping_mul(c as u64) >> 32) as u32;
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::MULHSU => {
(rd, b, c) = self.alu_rr(instruction);
a = (((b as i32) as i64).wrapping_mul(c as i64) >> 32) as u32;
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::DIV => {
(rd, b, c) = self.alu_rr(instruction);
if c == 0 {
a = u32::MAX;
} else {
a = (b as i32).wrapping_div(c as i32) as u32;
}
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::DIVU => {
(rd, b, c) = self.alu_rr(instruction);
if c == 0 {
a = u32::MAX;
} else {
a = b.wrapping_div(c);
}
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::REM => {
(rd, b, c) = self.alu_rr(instruction);
if c == 0 {
a = b;
} else {
a = (b as i32).wrapping_rem(c as i32) as u32;
}
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::REMU => {
(rd, b, c) = self.alu_rr(instruction);
if c == 0 {
a = b;
} else {
a = b.wrapping_rem(c);
}
self.alu_rw(instruction, rd, a, b, c, lookup_id);
}
Opcode::UNIMP => {
return Err(ExecutionError::Unimplemented());
}
}
self.state.pc = next_pc;
self.state.clk += 4;
if self.executor_mode == ExecutorMode::Trace {
self.emit_cpu(
self.shard(),
clk,
pc,
next_pc,
*instruction,
a,
b,
c,
memory_store_value,
self.memory_accesses,
exit_code,
lookup_id,
syscall_lookup_id,
);
};
Ok(())
}
#[inline]
#[allow(clippy::too_many_lines)]
fn execute_cycle(&mut self) -> Result<bool, ExecutionError> {
let instruction = self.fetch();
#[cfg(debug_assertions)]
self.log(&instruction);
self.execute_instruction(&instruction)?;
self.state.global_clk += 1;
if !self.unconstrained {
let cpu_exit = self.max_syscall_cycles + self.state.clk >= self.shard_size;
let mut shape_match_found = true;
if self.state.global_clk % 16 == 0 {
let addsub_count = (self.report.event_counts[Opcode::ADD]
+ self.report.event_counts[Opcode::SUB])
as usize;
let mul_count = (self.report.event_counts[Opcode::MUL]
+ self.report.event_counts[Opcode::MULH]
+ self.report.event_counts[Opcode::MULHU]
+ self.report.event_counts[Opcode::MULHSU])
as usize;
let bitwise_count = (self.report.event_counts[Opcode::XOR]
+ self.report.event_counts[Opcode::OR]
+ self.report.event_counts[Opcode::AND])
as usize;
let shift_left_count = self.report.event_counts[Opcode::SLL] as usize;
let shift_right_count = (self.report.event_counts[Opcode::SRL]
+ self.report.event_counts[Opcode::SRA])
as usize;
let divrem_count = (self.report.event_counts[Opcode::DIV]
+ self.report.event_counts[Opcode::DIVU]
+ self.report.event_counts[Opcode::REM]
+ self.report.event_counts[Opcode::REMU])
as usize;
let lt_count = (self.report.event_counts[Opcode::SLT]
+ self.report.event_counts[Opcode::SLTU])
as usize;
if let Some(maximal_shapes) = &self.maximal_shapes {
shape_match_found = false;
for shape in maximal_shapes.iter() {
let addsub_threshold = 1 << shape["AddSub"];
if addsub_count > addsub_threshold {
continue;
}
let addsub_distance = addsub_threshold - addsub_count;
let mul_threshold = 1 << shape["Mul"];
if mul_count > mul_threshold {
continue;
}
let mul_distance = mul_threshold - mul_count;
let bitwise_threshold = 1 << shape["Bitwise"];
if bitwise_count > bitwise_threshold {
continue;
}
let bitwise_distance = bitwise_threshold - bitwise_count;
let shift_left_threshold = 1 << shape["ShiftLeft"];
if shift_left_count > shift_left_threshold {
continue;
}
let shift_left_distance = shift_left_threshold - shift_left_count;
let shift_right_threshold = 1 << shape["ShiftRight"];
if shift_right_count > shift_right_threshold {
continue;
}
let shift_right_distance = shift_right_threshold - shift_right_count;
let divrem_threshold = 1 << shape["DivRem"];
if divrem_count > divrem_threshold {
continue;
}
let divrem_distance = divrem_threshold - divrem_count;
let lt_threshold = 1 << shape["Lt"];
if lt_count > lt_threshold {
continue;
}
let lt_distance = lt_threshold - lt_count;
let l_infinity = vec![
addsub_distance,
mul_distance,
bitwise_distance,
shift_left_distance,
shift_right_distance,
divrem_distance,
lt_distance,
]
.into_iter()
.min()
.unwrap();
if l_infinity >= 32 {
shape_match_found = true;
break;
}
}
if !shape_match_found {
log::warn!(
"stopping shard early due to no shapes fitting: \
nb_cycles={}, \
addsub_count={}, \
mul_count={}, \
bitwise_count={}, \
shift_left_count={}, \
shift_right_count={}, \
divrem_count={}, \
lt_count={}",
self.state.clk / 4,
log2_ceil_usize(addsub_count),
log2_ceil_usize(mul_count),
log2_ceil_usize(bitwise_count),
log2_ceil_usize(shift_left_count),
log2_ceil_usize(shift_right_count),
log2_ceil_usize(divrem_count),
log2_ceil_usize(lt_count),
);
}
}
}
if cpu_exit || !shape_match_found {
self.state.current_shard += 1;
self.state.clk = 0;
self.report.event_counts = Box::default();
self.bump_record();
}
}
if let Some(max_cycles) = self.max_cycles {
if self.state.global_clk >= max_cycles {
return Err(ExecutionError::ExceededCycleLimit(max_cycles));
}
}
let done = self.state.pc == 0
|| self.state.pc.wrapping_sub(self.program.pc_base)
>= (self.program.instructions.len() * 4) as u32;
if done && self.unconstrained {
log::error!("program ended in unconstrained mode at clk {}", self.state.global_clk);
return Err(ExecutionError::EndInUnconstrained());
}
Ok(done)
}
pub fn bump_record(&mut self) {
for (_, event) in self.local_memory_access.drain() {
self.record.cpu_local_memory_access.push(event);
}
let removed_record =
std::mem::replace(&mut self.record, ExecutionRecord::new(self.program.clone()));
let public_values = removed_record.public_values;
self.record.public_values = public_values;
self.records.push(removed_record);
}
pub fn execute_record(&mut self) -> Result<(Vec<ExecutionRecord>, bool), ExecutionError> {
self.executor_mode = ExecutorMode::Trace;
self.print_report = true;
let done = self.execute()?;
Ok((std::mem::take(&mut self.records), done))
}
pub fn execute_state(&mut self) -> Result<(ExecutionState, bool), ExecutionError> {
self.memory_checkpoint.clear();
self.executor_mode = ExecutorMode::Checkpoint;
let memory = std::mem::take(&mut self.state.memory);
let uninitialized_memory = std::mem::take(&mut self.state.uninitialized_memory);
let mut checkpoint = tracing::info_span!("clone").in_scope(|| self.state.clone());
self.state.memory = memory;
self.state.uninitialized_memory = uninitialized_memory;
let done = tracing::info_span!("execute").in_scope(|| self.execute())?;
tracing::info_span!("create memory checkpoint").in_scope(|| {
let memory_checkpoint = std::mem::take(&mut self.memory_checkpoint);
let uninitialized_memory_checkpoint =
std::mem::take(&mut self.uninitialized_memory_checkpoint);
if done {
checkpoint.memory.clone_from(&self.state.memory);
memory_checkpoint.into_iter().for_each(|(addr, record)| {
if let Some(record) = record {
checkpoint.memory.insert(addr, record);
} else {
checkpoint.memory.remove(addr);
}
});
checkpoint.uninitialized_memory = self.state.uninitialized_memory.clone();
for (addr, is_old) in uninitialized_memory_checkpoint {
if !is_old {
checkpoint.uninitialized_memory.remove(addr);
}
}
} else {
checkpoint.memory = memory_checkpoint
.into_iter()
.filter_map(|(addr, record)| record.map(|record| (addr, record)))
.collect();
checkpoint.uninitialized_memory = uninitialized_memory_checkpoint
.into_iter()
.filter(|&(_, has_value)| has_value)
.map(|(addr, _)| (addr, *self.state.uninitialized_memory.get(addr).unwrap()))
.collect();
}
});
Ok((checkpoint, done))
}
fn initialize(&mut self) {
self.state.clk = 0;
tracing::debug!("loading memory image");
for (&addr, value) in &self.program.memory_image {
self.state.memory.insert(addr, MemoryRecord { value: *value, shard: 0, timestamp: 0 });
}
}
pub fn run_fast(&mut self) -> Result<(), ExecutionError> {
self.executor_mode = ExecutorMode::Simple;
self.print_report = true;
while !self.execute()? {}
Ok(())
}
pub fn run(&mut self) -> Result<(), ExecutionError> {
self.executor_mode = ExecutorMode::Trace;
self.print_report = true;
while !self.execute()? {}
Ok(())
}
pub fn execute(&mut self) -> Result<bool, ExecutionError> {
let program = self.program.clone();
let start_shard = self.state.current_shard;
if self.state.global_clk == 0 {
self.initialize();
}
let mut done = false;
let mut current_shard = self.state.current_shard;
let mut num_shards_executed = 0;
loop {
if self.execute_cycle()? {
done = true;
break;
}
if self.shard_batch_size > 0 && current_shard != self.state.current_shard {
num_shards_executed += 1;
current_shard = self.state.current_shard;
if num_shards_executed == self.shard_batch_size {
break;
}
}
}
let public_values = self.record.public_values;
if done {
self.postprocess();
self.bump_record();
}
if !self.record.cpu_events.is_empty() {
self.bump_record();
}
let mut last_next_pc = 0;
let mut last_exit_code = 0;
for (i, record) in self.records.iter_mut().enumerate() {
record.program = program.clone();
record.public_values = public_values;
record.public_values.committed_value_digest = public_values.committed_value_digest;
record.public_values.deferred_proofs_digest = public_values.deferred_proofs_digest;
record.public_values.execution_shard = start_shard + i as u32;
if record.cpu_events.is_empty() {
record.public_values.start_pc = last_next_pc;
record.public_values.next_pc = last_next_pc;
record.public_values.exit_code = last_exit_code;
} else {
record.public_values.start_pc = record.cpu_events[0].pc;
record.public_values.next_pc = record.cpu_events.last().unwrap().next_pc;
record.public_values.exit_code = record.cpu_events.last().unwrap().exit_code;
last_next_pc = record.public_values.next_pc;
last_exit_code = record.public_values.exit_code;
}
}
Ok(done)
}
fn postprocess(&mut self) {
for (fd, buf) in &self.io_buf {
if !buf.is_empty() {
match fd {
1 => {
println!("stdout: {buf}");
}
2 => {
println!("stderr: {buf}");
}
_ => {}
}
}
}
if let Some(ref mut buf) = self.trace_buf {
buf.flush().unwrap();
}
if self.state.input_stream_ptr != self.state.input_stream.len() {
tracing::warn!("Not all input bytes were read.");
}
if self.executor_mode == ExecutorMode::Trace {
let memory_finalize_events = &mut self.record.global_memory_finalize_events;
let addr_0_record = self.state.memory.get(0);
let addr_0_final_record = match addr_0_record {
Some(record) => record,
None => &MemoryRecord { value: 0, shard: 0, timestamp: 1 },
};
memory_finalize_events
.push(MemoryInitializeFinalizeEvent::finalize_from_record(0, addr_0_final_record));
let memory_initialize_events = &mut self.record.global_memory_initialize_events;
let addr_0_initialize_event =
MemoryInitializeFinalizeEvent::initialize(0, 0, addr_0_record.is_some());
memory_initialize_events.push(addr_0_initialize_event);
self.report.touched_memory_addresses = 0;
for addr in self.state.memory.keys() {
self.report.touched_memory_addresses += 1;
if addr == 0 {
continue;
}
if !self.record.program.memory_image.contains_key(&addr) {
let initial_value = self.state.uninitialized_memory.get(addr).unwrap_or(&0);
memory_initialize_events.push(MemoryInitializeFinalizeEvent::initialize(
addr,
*initial_value,
true,
));
}
let record = *self.state.memory.get(addr).unwrap();
memory_finalize_events
.push(MemoryInitializeFinalizeEvent::finalize_from_record(addr, &record));
}
}
}
fn get_syscall(&mut self, code: SyscallCode) -> Option<&Arc<dyn Syscall>> {
self.syscall_map.get(&code)
}
#[inline]
#[cfg(debug_assertions)]
fn log(&mut self, _: &Instruction) {
if let Some(ref mut buf) = self.trace_buf {
if !self.unconstrained {
buf.write_all(&u32::to_be_bytes(self.state.pc)).unwrap();
}
}
if !self.unconstrained && self.state.global_clk % 10_000_000 == 0 {
log::info!("clk = {} pc = 0x{:x?}", self.state.global_clk, self.state.pc);
}
}
}
impl Default for ExecutorMode {
fn default() -> Self {
Self::Simple
}
}
#[must_use]
pub const fn align(addr: u32) -> u32 {
addr - addr % 4
}
fn log2_ceil_usize(n: usize) -> usize {
(usize::BITS - n.saturating_sub(1).leading_zeros()) as usize
}
#[cfg(test)]
mod tests {
use sp1_stark::SP1CoreOpts;
use crate::programs::tests::{
fibonacci_program, panic_program, simple_memory_program, simple_program,
ssz_withdrawals_program,
};
use crate::Register;
use super::{Executor, Instruction, Opcode, Program};
fn _assert_send<T: Send>() {}
fn _assert_runtime_is_send() {
_assert_send::<Executor>();
}
#[test]
fn test_simple_program_run() {
let program = simple_program();
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 42);
}
#[test]
fn test_fibonacci_program_run() {
let program = fibonacci_program();
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
}
#[test]
fn test_ssz_withdrawals_program_run() {
let program = ssz_withdrawals_program();
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
}
#[test]
#[should_panic]
fn test_panic() {
let program = panic_program();
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
}
#[test]
fn test_add() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::ADD, 30, 0, 37, false, true),
Instruction::new(Opcode::ADD, 31, 30, 29, false, false),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 42);
}
#[test]
fn test_sub() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::ADD, 30, 0, 37, false, true),
Instruction::new(Opcode::SUB, 31, 30, 29, false, false),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 32);
}
#[test]
fn test_xor() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::ADD, 30, 0, 37, false, true),
Instruction::new(Opcode::XOR, 31, 30, 29, false, false),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 32);
}
#[test]
fn test_or() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::ADD, 30, 0, 37, false, true),
Instruction::new(Opcode::OR, 31, 30, 29, false, false),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 37);
}
#[test]
fn test_and() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::ADD, 30, 0, 37, false, true),
Instruction::new(Opcode::AND, 31, 30, 29, false, false),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 5);
}
#[test]
fn test_sll() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::ADD, 30, 0, 37, false, true),
Instruction::new(Opcode::SLL, 31, 30, 29, false, false),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 1184);
}
#[test]
fn test_srl() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::ADD, 30, 0, 37, false, true),
Instruction::new(Opcode::SRL, 31, 30, 29, false, false),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 1);
}
#[test]
fn test_sra() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::ADD, 30, 0, 37, false, true),
Instruction::new(Opcode::SRA, 31, 30, 29, false, false),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 1);
}
#[test]
fn test_slt() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::ADD, 30, 0, 37, false, true),
Instruction::new(Opcode::SLT, 31, 30, 29, false, false),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 0);
}
#[test]
fn test_sltu() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::ADD, 30, 0, 37, false, true),
Instruction::new(Opcode::SLTU, 31, 30, 29, false, false),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 0);
}
#[test]
fn test_addi() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::ADD, 30, 29, 37, false, true),
Instruction::new(Opcode::ADD, 31, 30, 42, false, true),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 84);
}
#[test]
fn test_addi_negative() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::ADD, 30, 29, 0xFFFF_FFFF, false, true),
Instruction::new(Opcode::ADD, 31, 30, 4, false, true),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 5 - 1 + 4);
}
#[test]
fn test_xori() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::XOR, 30, 29, 37, false, true),
Instruction::new(Opcode::XOR, 31, 30, 42, false, true),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 10);
}
#[test]
fn test_ori() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::OR, 30, 29, 37, false, true),
Instruction::new(Opcode::OR, 31, 30, 42, false, true),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 47);
}
#[test]
fn test_andi() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::AND, 30, 29, 37, false, true),
Instruction::new(Opcode::AND, 31, 30, 42, false, true),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 0);
}
#[test]
fn test_slli() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 5, false, true),
Instruction::new(Opcode::SLL, 31, 29, 4, false, true),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 80);
}
#[test]
fn test_srli() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 42, false, true),
Instruction::new(Opcode::SRL, 31, 29, 4, false, true),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 2);
}
#[test]
fn test_srai() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 42, false, true),
Instruction::new(Opcode::SRA, 31, 29, 4, false, true),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 2);
}
#[test]
fn test_slti() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 42, false, true),
Instruction::new(Opcode::SLT, 31, 29, 37, false, true),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 0);
}
#[test]
fn test_sltiu() {
let instructions = vec![
Instruction::new(Opcode::ADD, 29, 0, 42, false, true),
Instruction::new(Opcode::SLTU, 31, 29, 37, false, true),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X31), 0);
}
#[test]
fn test_jalr() {
let instructions = vec![
Instruction::new(Opcode::ADD, 11, 11, 100, false, true),
Instruction::new(Opcode::JALR, 5, 11, 8, false, true),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.registers()[Register::X5 as usize], 8);
assert_eq!(runtime.registers()[Register::X11 as usize], 100);
assert_eq!(runtime.state.pc, 108);
}
fn simple_op_code_test(opcode: Opcode, expected: u32, a: u32, b: u32) {
let instructions = vec![
Instruction::new(Opcode::ADD, 10, 0, a, false, true),
Instruction::new(Opcode::ADD, 11, 0, b, false, true),
Instruction::new(opcode, 12, 10, 11, false, false),
];
let program = Program::new(instructions, 0, 0);
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.registers()[Register::X12 as usize], expected);
}
#[test]
#[allow(clippy::unreadable_literal)]
fn multiplication_tests() {
simple_op_code_test(Opcode::MULHU, 0x00000000, 0x00000000, 0x00000000);
simple_op_code_test(Opcode::MULHU, 0x00000000, 0x00000001, 0x00000001);
simple_op_code_test(Opcode::MULHU, 0x00000000, 0x00000003, 0x00000007);
simple_op_code_test(Opcode::MULHU, 0x00000000, 0x00000000, 0xffff8000);
simple_op_code_test(Opcode::MULHU, 0x00000000, 0x80000000, 0x00000000);
simple_op_code_test(Opcode::MULHU, 0x7fffc000, 0x80000000, 0xffff8000);
simple_op_code_test(Opcode::MULHU, 0x0001fefe, 0xaaaaaaab, 0x0002fe7d);
simple_op_code_test(Opcode::MULHU, 0x0001fefe, 0x0002fe7d, 0xaaaaaaab);
simple_op_code_test(Opcode::MULHU, 0xfe010000, 0xff000000, 0xff000000);
simple_op_code_test(Opcode::MULHU, 0xfffffffe, 0xffffffff, 0xffffffff);
simple_op_code_test(Opcode::MULHU, 0x00000000, 0xffffffff, 0x00000001);
simple_op_code_test(Opcode::MULHU, 0x00000000, 0x00000001, 0xffffffff);
simple_op_code_test(Opcode::MULHSU, 0x00000000, 0x00000000, 0x00000000);
simple_op_code_test(Opcode::MULHSU, 0x00000000, 0x00000001, 0x00000001);
simple_op_code_test(Opcode::MULHSU, 0x00000000, 0x00000003, 0x00000007);
simple_op_code_test(Opcode::MULHSU, 0x00000000, 0x00000000, 0xffff8000);
simple_op_code_test(Opcode::MULHSU, 0x00000000, 0x80000000, 0x00000000);
simple_op_code_test(Opcode::MULHSU, 0x80004000, 0x80000000, 0xffff8000);
simple_op_code_test(Opcode::MULHSU, 0xffff0081, 0xaaaaaaab, 0x0002fe7d);
simple_op_code_test(Opcode::MULHSU, 0x0001fefe, 0x0002fe7d, 0xaaaaaaab);
simple_op_code_test(Opcode::MULHSU, 0xff010000, 0xff000000, 0xff000000);
simple_op_code_test(Opcode::MULHSU, 0xffffffff, 0xffffffff, 0xffffffff);
simple_op_code_test(Opcode::MULHSU, 0xffffffff, 0xffffffff, 0x00000001);
simple_op_code_test(Opcode::MULHSU, 0x00000000, 0x00000001, 0xffffffff);
simple_op_code_test(Opcode::MULH, 0x00000000, 0x00000000, 0x00000000);
simple_op_code_test(Opcode::MULH, 0x00000000, 0x00000001, 0x00000001);
simple_op_code_test(Opcode::MULH, 0x00000000, 0x00000003, 0x00000007);
simple_op_code_test(Opcode::MULH, 0x00000000, 0x00000000, 0xffff8000);
simple_op_code_test(Opcode::MULH, 0x00000000, 0x80000000, 0x00000000);
simple_op_code_test(Opcode::MULH, 0x00000000, 0x80000000, 0x00000000);
simple_op_code_test(Opcode::MULH, 0xffff0081, 0xaaaaaaab, 0x0002fe7d);
simple_op_code_test(Opcode::MULH, 0xffff0081, 0x0002fe7d, 0xaaaaaaab);
simple_op_code_test(Opcode::MULH, 0x00010000, 0xff000000, 0xff000000);
simple_op_code_test(Opcode::MULH, 0x00000000, 0xffffffff, 0xffffffff);
simple_op_code_test(Opcode::MULH, 0xffffffff, 0xffffffff, 0x00000001);
simple_op_code_test(Opcode::MULH, 0xffffffff, 0x00000001, 0xffffffff);
simple_op_code_test(Opcode::MUL, 0x00001200, 0x00007e00, 0xb6db6db7);
simple_op_code_test(Opcode::MUL, 0x00001240, 0x00007fc0, 0xb6db6db7);
simple_op_code_test(Opcode::MUL, 0x00000000, 0x00000000, 0x00000000);
simple_op_code_test(Opcode::MUL, 0x00000001, 0x00000001, 0x00000001);
simple_op_code_test(Opcode::MUL, 0x00000015, 0x00000003, 0x00000007);
simple_op_code_test(Opcode::MUL, 0x00000000, 0x00000000, 0xffff8000);
simple_op_code_test(Opcode::MUL, 0x00000000, 0x80000000, 0x00000000);
simple_op_code_test(Opcode::MUL, 0x00000000, 0x80000000, 0xffff8000);
simple_op_code_test(Opcode::MUL, 0x0000ff7f, 0xaaaaaaab, 0x0002fe7d);
simple_op_code_test(Opcode::MUL, 0x0000ff7f, 0x0002fe7d, 0xaaaaaaab);
simple_op_code_test(Opcode::MUL, 0x00000000, 0xff000000, 0xff000000);
simple_op_code_test(Opcode::MUL, 0x00000001, 0xffffffff, 0xffffffff);
simple_op_code_test(Opcode::MUL, 0xffffffff, 0xffffffff, 0x00000001);
simple_op_code_test(Opcode::MUL, 0xffffffff, 0x00000001, 0xffffffff);
}
fn neg(a: u32) -> u32 {
u32::MAX - a + 1
}
#[test]
fn division_tests() {
simple_op_code_test(Opcode::DIVU, 3, 20, 6);
simple_op_code_test(Opcode::DIVU, 715_827_879, u32::MAX - 20 + 1, 6);
simple_op_code_test(Opcode::DIVU, 0, 20, u32::MAX - 6 + 1);
simple_op_code_test(Opcode::DIVU, 0, u32::MAX - 20 + 1, u32::MAX - 6 + 1);
simple_op_code_test(Opcode::DIVU, 1 << 31, 1 << 31, 1);
simple_op_code_test(Opcode::DIVU, 0, 1 << 31, u32::MAX - 1 + 1);
simple_op_code_test(Opcode::DIVU, u32::MAX, 1 << 31, 0);
simple_op_code_test(Opcode::DIVU, u32::MAX, 1, 0);
simple_op_code_test(Opcode::DIVU, u32::MAX, 0, 0);
simple_op_code_test(Opcode::DIV, 3, 18, 6);
simple_op_code_test(Opcode::DIV, neg(6), neg(24), 4);
simple_op_code_test(Opcode::DIV, neg(2), 16, neg(8));
simple_op_code_test(Opcode::DIV, neg(1), 0, 0);
simple_op_code_test(Opcode::DIV, 1 << 31, 1 << 31, neg(1));
simple_op_code_test(Opcode::REM, 0, 1 << 31, neg(1));
}
#[test]
fn remainder_tests() {
simple_op_code_test(Opcode::REM, 7, 16, 9);
simple_op_code_test(Opcode::REM, neg(4), neg(22), 6);
simple_op_code_test(Opcode::REM, 1, 25, neg(3));
simple_op_code_test(Opcode::REM, neg(2), neg(22), neg(4));
simple_op_code_test(Opcode::REM, 0, 873, 1);
simple_op_code_test(Opcode::REM, 0, 873, neg(1));
simple_op_code_test(Opcode::REM, 5, 5, 0);
simple_op_code_test(Opcode::REM, neg(5), neg(5), 0);
simple_op_code_test(Opcode::REM, 0, 0, 0);
simple_op_code_test(Opcode::REMU, 4, 18, 7);
simple_op_code_test(Opcode::REMU, 6, neg(20), 11);
simple_op_code_test(Opcode::REMU, 23, 23, neg(6));
simple_op_code_test(Opcode::REMU, neg(21), neg(21), neg(11));
simple_op_code_test(Opcode::REMU, 5, 5, 0);
simple_op_code_test(Opcode::REMU, neg(1), neg(1), 0);
simple_op_code_test(Opcode::REMU, 0, 0, 0);
}
#[test]
#[allow(clippy::unreadable_literal)]
fn shift_tests() {
simple_op_code_test(Opcode::SLL, 0x00000001, 0x00000001, 0);
simple_op_code_test(Opcode::SLL, 0x00000002, 0x00000001, 1);
simple_op_code_test(Opcode::SLL, 0x00000080, 0x00000001, 7);
simple_op_code_test(Opcode::SLL, 0x00004000, 0x00000001, 14);
simple_op_code_test(Opcode::SLL, 0x80000000, 0x00000001, 31);
simple_op_code_test(Opcode::SLL, 0xffffffff, 0xffffffff, 0);
simple_op_code_test(Opcode::SLL, 0xfffffffe, 0xffffffff, 1);
simple_op_code_test(Opcode::SLL, 0xffffff80, 0xffffffff, 7);
simple_op_code_test(Opcode::SLL, 0xffffc000, 0xffffffff, 14);
simple_op_code_test(Opcode::SLL, 0x80000000, 0xffffffff, 31);
simple_op_code_test(Opcode::SLL, 0x21212121, 0x21212121, 0);
simple_op_code_test(Opcode::SLL, 0x42424242, 0x21212121, 1);
simple_op_code_test(Opcode::SLL, 0x90909080, 0x21212121, 7);
simple_op_code_test(Opcode::SLL, 0x48484000, 0x21212121, 14);
simple_op_code_test(Opcode::SLL, 0x80000000, 0x21212121, 31);
simple_op_code_test(Opcode::SLL, 0x21212121, 0x21212121, 0xffffffe0);
simple_op_code_test(Opcode::SLL, 0x42424242, 0x21212121, 0xffffffe1);
simple_op_code_test(Opcode::SLL, 0x90909080, 0x21212121, 0xffffffe7);
simple_op_code_test(Opcode::SLL, 0x48484000, 0x21212121, 0xffffffee);
simple_op_code_test(Opcode::SLL, 0x00000000, 0x21212120, 0xffffffff);
simple_op_code_test(Opcode::SRL, 0xffff8000, 0xffff8000, 0);
simple_op_code_test(Opcode::SRL, 0x7fffc000, 0xffff8000, 1);
simple_op_code_test(Opcode::SRL, 0x01ffff00, 0xffff8000, 7);
simple_op_code_test(Opcode::SRL, 0x0003fffe, 0xffff8000, 14);
simple_op_code_test(Opcode::SRL, 0x0001ffff, 0xffff8001, 15);
simple_op_code_test(Opcode::SRL, 0xffffffff, 0xffffffff, 0);
simple_op_code_test(Opcode::SRL, 0x7fffffff, 0xffffffff, 1);
simple_op_code_test(Opcode::SRL, 0x01ffffff, 0xffffffff, 7);
simple_op_code_test(Opcode::SRL, 0x0003ffff, 0xffffffff, 14);
simple_op_code_test(Opcode::SRL, 0x00000001, 0xffffffff, 31);
simple_op_code_test(Opcode::SRL, 0x21212121, 0x21212121, 0);
simple_op_code_test(Opcode::SRL, 0x10909090, 0x21212121, 1);
simple_op_code_test(Opcode::SRL, 0x00424242, 0x21212121, 7);
simple_op_code_test(Opcode::SRL, 0x00008484, 0x21212121, 14);
simple_op_code_test(Opcode::SRL, 0x00000000, 0x21212121, 31);
simple_op_code_test(Opcode::SRL, 0x21212121, 0x21212121, 0xffffffe0);
simple_op_code_test(Opcode::SRL, 0x10909090, 0x21212121, 0xffffffe1);
simple_op_code_test(Opcode::SRL, 0x00424242, 0x21212121, 0xffffffe7);
simple_op_code_test(Opcode::SRL, 0x00008484, 0x21212121, 0xffffffee);
simple_op_code_test(Opcode::SRL, 0x00000000, 0x21212121, 0xffffffff);
simple_op_code_test(Opcode::SRA, 0x00000000, 0x00000000, 0);
simple_op_code_test(Opcode::SRA, 0xc0000000, 0x80000000, 1);
simple_op_code_test(Opcode::SRA, 0xff000000, 0x80000000, 7);
simple_op_code_test(Opcode::SRA, 0xfffe0000, 0x80000000, 14);
simple_op_code_test(Opcode::SRA, 0xffffffff, 0x80000001, 31);
simple_op_code_test(Opcode::SRA, 0x7fffffff, 0x7fffffff, 0);
simple_op_code_test(Opcode::SRA, 0x3fffffff, 0x7fffffff, 1);
simple_op_code_test(Opcode::SRA, 0x00ffffff, 0x7fffffff, 7);
simple_op_code_test(Opcode::SRA, 0x0001ffff, 0x7fffffff, 14);
simple_op_code_test(Opcode::SRA, 0x00000000, 0x7fffffff, 31);
simple_op_code_test(Opcode::SRA, 0x81818181, 0x81818181, 0);
simple_op_code_test(Opcode::SRA, 0xc0c0c0c0, 0x81818181, 1);
simple_op_code_test(Opcode::SRA, 0xff030303, 0x81818181, 7);
simple_op_code_test(Opcode::SRA, 0xfffe0606, 0x81818181, 14);
simple_op_code_test(Opcode::SRA, 0xffffffff, 0x81818181, 31);
}
#[test]
#[allow(clippy::unreadable_literal)]
fn test_simple_memory_program_run() {
let program = simple_memory_program();
let mut runtime = Executor::new(program, SP1CoreOpts::default());
runtime.run().unwrap();
assert_eq!(runtime.register(Register::X28), 0x12348765);
assert_eq!(runtime.register(Register::X27), 0x65);
assert_eq!(runtime.register(Register::X26), 0x87);
assert_eq!(runtime.register(Register::X25), 0x34);
assert_eq!(runtime.register(Register::X24), 0x12);
assert_eq!(runtime.register(Register::X23), 0x65);
assert_eq!(runtime.register(Register::X22), 0xffffff87);
assert_eq!(runtime.register(Register::X21), 0x8765);
assert_eq!(runtime.register(Register::X20), 0x1234);
assert_eq!(runtime.register(Register::X19), 0xffff8765);
assert_eq!(runtime.register(Register::X18), 0x1234);
assert_eq!(runtime.register(Register::X16), 0x12348725);
assert_eq!(runtime.register(Register::X15), 0x12342525);
assert_eq!(runtime.register(Register::X14), 0x12252525);
assert_eq!(runtime.register(Register::X13), 0x25252525);
assert_eq!(runtime.register(Register::X12), 0x12346525);
assert_eq!(runtime.register(Register::X11), 0x65256525);
}
}