use super::{Executor, InstructionPtr};
use crate::{
engine::{utils::unreachable_unchecked, ResumableOutOfFuelError},
errors::MemoryError,
ir::{
index::{Data, Memory},
Op,
Slot,
},
store::{PrunedStore, StoreError, StoreInner},
Error,
TrapCode,
};
impl Executor<'_> {
fn fetch_memory_index(&self, offset: usize) -> Memory {
let mut addr: InstructionPtr = self.ip;
addr.add(offset);
match *addr.get() {
Op::MemoryIndex { index } => index,
unexpected => {
unsafe {
unreachable_unchecked!("expected `Op::MemoryIndex` but found: {unexpected:?}")
}
}
}
}
fn fetch_data_segment_index(&self, offset: usize) -> Data {
let mut addr: InstructionPtr = self.ip;
addr.add(offset);
match *addr.get() {
Op::DataIndex { index } => index,
unexpected => {
unsafe {
unreachable_unchecked!("expected `Op::DataIndex` but found: {unexpected:?}")
}
}
}
}
pub fn execute_data_drop(&mut self, store: &mut StoreInner, segment_index: Data) {
let segment = self.get_data_segment(segment_index);
store.resolve_data_mut(&segment).drop_bytes();
self.next_instr();
}
pub fn execute_memory_size(&mut self, store: &StoreInner, result: Slot, memory: Memory) {
self.execute_memory_size_impl(store, result, memory);
self.next_instr()
}
fn execute_memory_size_impl(&mut self, store: &StoreInner, result: Slot, memory: Memory) {
let memory = self.get_memory(memory);
let size = store.resolve_memory(&memory).size();
self.set_stack_slot(result, size);
}
pub fn execute_memory_grow(
&mut self,
store: &mut PrunedStore,
result: Slot,
delta: Slot,
) -> Result<(), Error> {
let delta: u64 = self.get_stack_slot_as(delta);
let memory = self.fetch_memory_index(1);
if delta == 0 {
self.execute_memory_size_impl(store.inner(), result, memory);
return self.try_next_instr_at(2);
}
let memory = self.get_memory(memory);
let return_value = match store.grow_memory(&memory, delta) {
Ok(return_value) => {
unsafe { self.cache.update_memory(store.inner_mut()) };
return_value
}
Err(StoreError::External(
MemoryError::OutOfBoundsGrowth | MemoryError::OutOfSystemMemory,
)) => {
let memory_ty = store.inner().resolve_memory(&memory).ty();
match memory_ty.is_64() {
true => u64::MAX,
false => u64::from(u32::MAX),
}
}
Err(StoreError::External(MemoryError::OutOfFuel { required_fuel })) => {
return Err(Error::from(ResumableOutOfFuelError::new(required_fuel)))
}
Err(StoreError::External(MemoryError::ResourceLimiterDeniedAllocation)) => {
return Err(Error::from(TrapCode::GrowthOperationLimited))
}
Err(error) => {
panic!("`table.grow`: internal interpreter error: {error}")
}
};
self.set_stack_slot(result, return_value);
self.try_next_instr_at(2)
}
pub fn execute_memory_copy(
&mut self,
store: &mut StoreInner,
dst: Slot,
src: Slot,
len: Slot,
) -> Result<(), Error> {
let dst: u64 = self.get_stack_slot_as(dst);
let src: u64 = self.get_stack_slot_as(src);
let len: u64 = self.get_stack_slot_as(len);
let Ok(dst_index) = usize::try_from(dst) else {
return Err(Error::from(TrapCode::MemoryOutOfBounds));
};
let Ok(src_index) = usize::try_from(src) else {
return Err(Error::from(TrapCode::MemoryOutOfBounds));
};
let Ok(len) = usize::try_from(len) else {
return Err(Error::from(TrapCode::MemoryOutOfBounds));
};
let dst_memory = self.fetch_memory_index(1);
let src_memory = self.fetch_memory_index(2);
if src_memory == dst_memory {
return self
.execute_memory_copy_within_impl(store, src_memory, dst_index, src_index, len);
}
let (src_memory, dst_memory, fuel) = store.resolve_memory_pair_and_fuel(
&self.get_memory(src_memory),
&self.get_memory(dst_memory),
);
let src_bytes = src_memory
.data()
.get(src_index..)
.and_then(|memory| memory.get(..len))
.ok_or(TrapCode::MemoryOutOfBounds)?;
let dst_bytes = dst_memory
.data_mut()
.get_mut(dst_index..)
.and_then(|memory| memory.get_mut(..len))
.ok_or(TrapCode::MemoryOutOfBounds)?;
fuel.consume_fuel_if(|costs| costs.fuel_for_copying_bytes(len as u64))?;
dst_bytes.copy_from_slice(src_bytes);
self.try_next_instr_at(3)
}
fn execute_memory_copy_within_impl(
&mut self,
store: &mut StoreInner,
memory: Memory,
dst_index: usize,
src_index: usize,
len: usize,
) -> Result<(), Error> {
let memory = self.get_memory(memory);
let (memory, fuel) = store.resolve_memory_and_fuel_mut(&memory);
let bytes = memory.data_mut();
bytes
.get(src_index..)
.and_then(|memory| memory.get(..len))
.ok_or(TrapCode::MemoryOutOfBounds)?;
bytes
.get(dst_index..)
.and_then(|memory| memory.get(..len))
.ok_or(TrapCode::MemoryOutOfBounds)?;
fuel.consume_fuel_if(|costs| costs.fuel_for_copying_bytes(len as u64))?;
bytes.copy_within(src_index..src_index.wrapping_add(len), dst_index);
self.try_next_instr_at(3)
}
pub fn execute_memory_fill(
&mut self,
store: &mut StoreInner,
dst: Slot,
value: Slot,
len: Slot,
) -> Result<(), Error> {
let dst: u64 = self.get_stack_slot_as(dst);
let value: u8 = self.get_stack_slot_as(value);
let len: u64 = self.get_stack_slot_as(len);
self.execute_memory_fill_impl(store, dst, value, len)
}
pub fn execute_memory_fill_imm(
&mut self,
store: &mut StoreInner,
dst: Slot,
value: u8,
len: Slot,
) -> Result<(), Error> {
let dst: u64 = self.get_stack_slot_as(dst);
let len: u64 = self.get_stack_slot_as(len);
self.execute_memory_fill_impl(store, dst, value, len)
}
#[inline(never)]
fn execute_memory_fill_impl(
&mut self,
store: &mut StoreInner,
dst: u64,
value: u8,
len: u64,
) -> Result<(), Error> {
let Ok(dst) = usize::try_from(dst) else {
return Err(Error::from(TrapCode::MemoryOutOfBounds));
};
let Ok(len) = usize::try_from(len) else {
return Err(Error::from(TrapCode::MemoryOutOfBounds));
};
let memory = self.fetch_memory_index(1);
let memory = self.get_memory(memory);
let (memory, fuel) = store.resolve_memory_and_fuel_mut(&memory);
let slice = memory
.data_mut()
.get_mut(dst..)
.and_then(|memory| memory.get_mut(..len))
.ok_or(TrapCode::MemoryOutOfBounds)?;
fuel.consume_fuel_if(|costs| costs.fuel_for_copying_bytes(len as u64))?;
slice.fill(value);
self.try_next_instr_at(2)
}
pub fn execute_memory_init(
&mut self,
store: &mut StoreInner,
dst: Slot,
src: Slot,
len: Slot,
) -> Result<(), Error> {
let dst: u64 = self.get_stack_slot_as(dst);
let src: u32 = self.get_stack_slot_as(src);
let len: u32 = self.get_stack_slot_as(len);
let Ok(dst_index) = usize::try_from(dst) else {
return Err(Error::from(TrapCode::MemoryOutOfBounds));
};
let Ok(src_index) = usize::try_from(src) else {
return Err(Error::from(TrapCode::MemoryOutOfBounds));
};
let Ok(len) = usize::try_from(len) else {
return Err(Error::from(TrapCode::MemoryOutOfBounds));
};
let memory_index: Memory = self.fetch_memory_index(1);
let data_index: Data = self.fetch_data_segment_index(2);
let (memory, data, fuel) = store.resolve_memory_init_params(
&self.get_memory(memory_index),
&self.get_data_segment(data_index),
);
let memory = memory
.data_mut()
.get_mut(dst_index..)
.and_then(|memory| memory.get_mut(..len))
.ok_or(TrapCode::MemoryOutOfBounds)?;
let data = data
.bytes()
.get(src_index..)
.and_then(|data| data.get(..len))
.ok_or(TrapCode::MemoryOutOfBounds)?;
fuel.consume_fuel_if(|costs| costs.fuel_for_copying_bytes(len as u64))?;
memory.copy_from_slice(data);
self.try_next_instr_at(3)
}
}