use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use crate::error::ValidationError;
use crate::ops::{
BinaryOp, CmpOp, ConvOp, DivZero, FloatToInt, OpError, Overflow, Policy, Shift, UnaryOp,
};
use crate::types::{PointerWidth, Type};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Val {
ty: Type,
bits: u64,
}
fn mask_for(ty: Type, ptr: PointerWidth) -> u64 {
let w = ty.bits(ptr);
if w >= 64 { u64::MAX } else { (1u64 << w) - 1 }
}
impl Val {
#[must_use]
pub fn new(ty: Type, bits: u64) -> Val {
Val {
ty,
bits: bits & mask_for(ty, PointerWidth::P64),
}
}
#[must_use]
pub fn i8(x: i8) -> Val {
Val::new(Type::I8, x as u8 as u64)
}
#[must_use]
pub fn i16(x: i16) -> Val {
Val::new(Type::I16, x as u16 as u64)
}
#[must_use]
pub fn i32(x: i32) -> Val {
Val::new(Type::I32, x as u32 as u64)
}
#[must_use]
pub fn i64(x: i64) -> Val {
Val::new(Type::I64, x as u64)
}
#[must_use]
pub fn u8(x: u8) -> Val {
Val::new(Type::U8, u64::from(x))
}
#[must_use]
pub fn u16(x: u16) -> Val {
Val::new(Type::U16, u64::from(x))
}
#[must_use]
pub fn u32(x: u32) -> Val {
Val::new(Type::U32, u64::from(x))
}
#[must_use]
pub fn u64(x: u64) -> Val {
Val::new(Type::U64, x)
}
#[must_use]
pub fn f32(x: f32) -> Val {
Val::new(Type::F32, u64::from(x.to_bits()))
}
#[must_use]
pub fn f64(x: f64) -> Val {
Val::new(Type::F64, x.to_bits())
}
#[must_use]
pub fn bool(x: bool) -> Val {
Val::new(Type::Bool, u64::from(x))
}
#[must_use]
pub fn ptr(addr: u64) -> Val {
Val::new(Type::Ptr, addr)
}
#[must_use]
pub fn ty(self) -> Type {
self.ty
}
#[must_use]
pub fn bits(self) -> u64 {
self.bits
}
#[must_use]
pub fn as_i128(self) -> Option<i128> {
self.ty.int_bits().map(|_| sval(self.ty, self.bits))
}
#[must_use]
pub fn as_f64(self) -> Option<f64> {
match self.ty {
Type::F64 => Some(f64::from_bits(self.bits)),
Type::F32 => Some(f64::from(f32::from_bits(self.bits as u32))),
_ => None,
}
}
#[must_use]
pub fn as_bool(self) -> Option<bool> {
(self.ty == Type::Bool).then_some(self.bits != 0)
}
#[must_use]
pub fn same_as(self, other: Val) -> bool {
if self.ty != other.ty {
return false;
}
match (self.as_f64(), other.as_f64()) {
(Some(a), Some(b)) if a.is_nan() && b.is_nan() => true,
_ => self.bits == other.bits,
}
}
}
impl fmt::Display for Val {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
crate::print::write_const(f, self.ty, self.bits, PointerWidth::P64)?;
write!(f, ": {}", self.ty)
}
}
fn sval(ty: Type, bits: u64) -> i128 {
let w = ty.int_bits().unwrap_or(64);
if ty.is_signed_int() {
let shift = 64 - w;
i128::from(((bits << shift) as i64) >> shift)
} else {
i128::from(bits)
}
}
fn int_range(ty: Type) -> (i128, i128) {
let w = ty.int_bits().unwrap_or(64);
if ty.is_signed_int() {
(-(1i128 << (w - 1)), (1i128 << (w - 1)) - 1)
} else {
(0, (1i128 << w) - 1)
}
}
fn wrap(ty: Type, v: i128) -> u64 {
(v as u64) & mask_for(ty, PointerWidth::P64)
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub enum OpFault {
Error(OpError),
Trap(OpError),
}
fn overflow(policy: Policy, wrapped: u64) -> Result<u64, OpFault> {
match policy.overflow {
Some(Overflow::Wrap) | None => Ok(wrapped),
Some(Overflow::Trap) => Err(OpFault::Trap(OpError::ArithOverflow)),
Some(Overflow::Error) => Err(OpFault::Error(OpError::ArithOverflow)),
}
}
fn div_zero(policy: Policy) -> OpFault {
match policy.div_zero {
Some(DivZero::Trap) => OpFault::Trap(OpError::DivByZero),
_ => OpFault::Error(OpError::DivByZero),
}
}
fn exact(ty: Type, policy: Policy, v: Option<i128>, wrapped: u64) -> Result<u64, OpFault> {
let (lo, hi) = int_range(ty);
match v {
Some(v) if v >= lo && v <= hi => Ok(wrap(ty, v)),
_ => overflow(policy, wrapped),
}
}
fn float_op(
ty: Type,
a: u64,
b: u64,
f32op: fn(f32, f32) -> f32,
f64op: fn(f64, f64) -> f64,
) -> u64 {
if ty == Type::F32 {
u64::from(f32op(f32::from_bits(a as u32), f32::from_bits(b as u32)).to_bits())
} else {
f64op(f64::from_bits(a), f64::from_bits(b)).to_bits()
}
}
fn ieee_remainder(x: f64, y: f64) -> f64 {
if y == 0.0 || x.is_infinite() || x.is_nan() || y.is_nan() {
return f64::NAN;
}
if y.is_infinite() {
return x;
}
let negative = x.is_sign_negative();
let p = y.abs();
let mut r = if p <= f64::MAX / 2.0 { x % (p + p) } else { x };
r = r.abs();
if p < 2.0 * f64::MIN_POSITIVE {
if r + r > p {
r -= p;
if r + r >= p {
r -= p;
}
}
} else {
let half = 0.5 * p;
if r > half {
r -= p;
if r >= half {
r -= p;
}
}
}
if negative { -r } else { r }
}
fn fmin(a: f64, b: f64) -> f64 {
if a.is_nan() || b.is_nan() {
f64::NAN
} else if a < b {
a
} else if b < a {
b
} else if a.is_sign_negative() {
a
} else {
b
}
}
fn fmax(a: f64, b: f64) -> f64 {
if a.is_nan() || b.is_nan() {
f64::NAN
} else if a > b || (a == b && b.is_sign_negative()) {
a
} else {
b
}
}
fn fmin32(a: f32, b: f32) -> f32 {
fmin(f64::from(a), f64::from(b)) as f32
}
fn fmax32(a: f32, b: f32) -> f32 {
fmax(f64::from(a), f64::from(b)) as f32
}
pub fn eval_unary(op: UnaryOp, policy: Policy, ty: Type, a: u64) -> Result<u64, OpFault> {
if ty.is_int() {
let x = sval(ty, a);
return match op {
UnaryOp::Neg => exact(ty, policy, Some(-x), wrap(ty, 0i128.wrapping_sub(x))),
UnaryOp::Abs => exact(ty, policy, Some(x.abs()), wrap(ty, x.abs())),
_ => Ok(!a & mask_for(ty, PointerWidth::P64)),
};
}
if ty == Type::Bool {
return Ok(a ^ 1);
}
let sign = if ty == Type::F32 {
1u64 << 31
} else {
1u64 << 63
};
match op {
UnaryOp::Neg => return Ok(a ^ sign),
UnaryOp::Abs => return Ok(a & !sign),
_ => {}
}
if ty == Type::F32 {
let x = f32::from_bits(a as u32);
let r = match op {
UnaryOp::Sqrt => x.sqrt(),
UnaryOp::Floor => x.floor(),
UnaryOp::Ceil => x.ceil(),
UnaryOp::Trunc => x.trunc(),
UnaryOp::Round => x.round(),
_ => x.round_ties_even(),
};
Ok(u64::from(r.to_bits()))
} else {
let x = f64::from_bits(a);
let r = match op {
UnaryOp::Sqrt => x.sqrt(),
UnaryOp::Floor => x.floor(),
UnaryOp::Ceil => x.ceil(),
UnaryOp::Trunc => x.trunc(),
UnaryOp::Round => x.round(),
_ => x.round_ties_even(),
};
Ok(r.to_bits())
}
}
fn int_binary(op: BinaryOp, policy: Policy, ty: Type, a: u64, b: u64) -> Result<u64, OpFault> {
let (x, y) = (sval(ty, a), sval(ty, b));
let m = mask_for(ty, PointerWidth::P64);
let w = ty.int_bits().unwrap_or(64);
match op {
BinaryOp::Add => exact(ty, policy, x.checked_add(y), a.wrapping_add(b) & m),
BinaryOp::Sub => exact(ty, policy, x.checked_sub(y), a.wrapping_sub(b) & m),
BinaryOp::Mul => exact(ty, policy, x.checked_mul(y), a.wrapping_mul(b) & m),
BinaryOp::Div | BinaryOp::FloorDiv => {
if y == 0 {
return Err(div_zero(policy));
}
let mut q = x / y;
if op == BinaryOp::FloorDiv && x % y != 0 && ((x < 0) != (y < 0)) {
q -= 1;
}
exact(ty, policy, Some(q), wrap(ty, q))
}
BinaryOp::Rem | BinaryOp::FloorMod => {
if y == 0 {
return Err(div_zero(policy));
}
let mut r = x % y;
if op == BinaryOp::FloorMod && r != 0 && ((r < 0) != (y < 0)) {
r += y;
}
Ok(wrap(ty, r))
}
BinaryOp::And => Ok(a & b),
BinaryOp::Or => Ok(a | b),
BinaryOp::Xor => Ok(a ^ b),
BinaryOp::Shl | BinaryOp::Shr => {
let n = if y < 0 || y >= i128::from(w) {
match policy.shift {
Some(Shift::Mask) => (b & u64::from(w - 1)) as u32,
Some(Shift::Saturate) if y >= 0 => {
return Ok(if op == BinaryOp::Shr && x < 0 { m } else { 0 });
}
_ => return Err(OpFault::Error(OpError::ShiftOutOfRange)),
}
} else {
y as u32
};
if op == BinaryOp::Shl {
Ok(a.checked_shl(n).unwrap_or(0) & m)
} else if ty.is_signed_int() {
Ok(wrap(ty, x >> n))
} else {
Ok(a >> n)
}
}
BinaryOp::Min => Ok(if x <= y { a } else { b }),
BinaryOp::Max => Ok(if x >= y { a } else { b }),
BinaryOp::Pow => int_pow(ty, policy, x, y, a),
BinaryOp::IeeeRem | BinaryOp::TotalCmp => Ok(0),
}
}
fn int_pow(ty: Type, policy: Policy, x: i128, e: i128, a: u64) -> Result<u64, OpFault> {
if e < 0 {
return Err(OpFault::Error(OpError::NegativeExponent));
}
let m = mask_for(ty, PointerWidth::P64);
let (mut wrapped, mut wbase) = (1u64, a);
let (mut power, mut base) = (Some(1i128), Some(x));
let mut e = e as u128;
loop {
if e & 1 == 1 {
wrapped = wrapped.wrapping_mul(wbase);
power = match (power, base) {
(Some(r), Some(b)) => r.checked_mul(b),
_ => None,
};
}
e >>= 1;
if e == 0 {
break;
}
wbase = wbase.wrapping_mul(wbase);
base = base.and_then(|b| b.checked_mul(b));
}
exact(ty, policy, power, wrapped & m)
}
pub fn eval_binary(op: BinaryOp, policy: Policy, ty: Type, a: u64, b: u64) -> Result<u64, OpFault> {
if ty.is_int() {
return int_binary(op, policy, ty, a, b);
}
if ty == Type::Bool {
return Ok(match op {
BinaryOp::And => a & b,
BinaryOp::Or => a | b,
_ => a ^ b,
});
}
Ok(match op {
BinaryOp::Add => float_op(ty, a, b, |x, y| x + y, |x, y| x + y),
BinaryOp::Sub => float_op(ty, a, b, |x, y| x - y, |x, y| x - y),
BinaryOp::Mul => float_op(ty, a, b, |x, y| x * y, |x, y| x * y),
BinaryOp::Div => float_op(ty, a, b, |x, y| x / y, |x, y| x / y),
BinaryOp::Rem => float_op(ty, a, b, |x, y| x % y, |x, y| x % y),
BinaryOp::IeeeRem => float_op(
ty,
a,
b,
|x, y| ieee_remainder(f64::from(x), f64::from(y)) as f32,
ieee_remainder,
),
BinaryOp::Min => float_op(ty, a, b, fmin32, fmin),
BinaryOp::Max => float_op(ty, a, b, fmax32, fmax),
BinaryOp::Pow => float_op(
ty,
a,
b,
|x, y| crate::pow::ls_pow(f64::from(x), f64::from(y)) as f32,
crate::pow::ls_pow,
),
BinaryOp::TotalCmp => {
let ord = if ty == Type::F32 {
f32::from_bits(a as u32).total_cmp(&f32::from_bits(b as u32))
} else {
f64::from_bits(a).total_cmp(&f64::from_bits(b))
};
wrap(Type::I8, ord as i128)
}
_ => 0,
})
}
#[must_use]
pub fn eval_compare(op: CmpOp, ty: Type, a: u64, b: u64) -> bool {
if ty.is_float() {
let (x, y) = if ty == Type::F32 {
(
f64::from(f32::from_bits(a as u32)),
f64::from(f32::from_bits(b as u32)),
)
} else {
(f64::from_bits(a), f64::from_bits(b))
};
return match op {
CmpOp::Eq => x == y,
CmpOp::Ne => x != y,
CmpOp::Lt => x < y,
CmpOp::Le => x <= y,
CmpOp::Gt => x > y,
CmpOp::Ge => x >= y,
};
}
let (x, y) = if ty.is_int() {
(sval(ty, a), sval(ty, b))
} else {
(i128::from(a), i128::from(b))
};
match op {
CmpOp::Eq => x == y,
CmpOp::Ne => x != y,
CmpOp::Lt => x < y,
CmpOp::Le => x <= y,
CmpOp::Gt => x > y,
CmpOp::Ge => x >= y,
}
}
#[must_use]
pub fn eval_fma(ty: Type, a: u64, b: u64, c: u64) -> u64 {
if ty == Type::F32 {
let r =
f32::from_bits(a as u32).mul_add(f32::from_bits(b as u32), f32::from_bits(c as u32));
u64::from(r.to_bits())
} else {
f64::from_bits(a)
.mul_add(f64::from_bits(b), f64::from_bits(c))
.to_bits()
}
}
pub fn eval_convert(
op: ConvOp,
policy: Policy,
from: Type,
to: Type,
a: u64,
) -> Result<u64, OpFault> {
let tm = mask_for(to, PointerWidth::P64);
match op {
ConvOp::IntCast => {
let v = sval(from, a);
exact(to, policy, Some(v), wrap(to, v))
}
ConvOp::Zext
| ConvOp::Narrow
| ConvOp::Bitcast
| ConvOp::BoolToInt
| ConvOp::PtrToInt
| ConvOp::IntToPtr
| ConvOp::RefToPtr => Ok(a & tm),
ConvOp::Sext => {
let signed = Type::int(from.int_bits().unwrap_or(64), true).unwrap_or(Type::I64);
Ok(wrap(to, sval(signed, a)))
}
ConvOp::IntToFloat => {
let v = sval(from, a);
Ok(if to == Type::F32 {
u64::from((v as f32).to_bits())
} else {
(v as f64).to_bits()
})
}
ConvOp::FloatToInt => {
let x = if from == Type::F32 {
f64::from(f32::from_bits(a as u32))
} else {
f64::from_bits(a)
};
let w = to.int_bits().unwrap_or(64);
let (lo, hi_excl) = if to.is_signed_int() {
(-pow2(w - 1), pow2(w - 1))
} else {
(0.0, pow2(w))
};
let t = x.trunc();
if !x.is_nan() && t >= lo && t < hi_excl {
return Ok(wrap(to, t as i128));
}
match policy.float_to_int {
Some(FloatToInt::Saturate) => {
let (min, max) = int_range(to);
Ok(if x.is_nan() {
0
} else if x < 0.0 {
wrap(to, min)
} else {
wrap(to, max)
})
}
_ => Err(OpFault::Error(OpError::InvalidConversion)),
}
}
ConvOp::FloatCast => Ok(if to == Type::F64 {
f64::from(f32::from_bits(a as u32)).to_bits()
} else {
u64::from((f64::from_bits(a) as f32).to_bits())
}),
ConvOp::CharFromU32 => {
if a <= 0x10_FFFF && !(0xD800..=0xDFFF).contains(&a) {
Ok(a)
} else {
Err(OpFault::Error(OpError::InvalidChar))
}
}
}
}
fn pow2(n: u32) -> f64 {
let mut x = 1.0f64;
for _ in 0..n {
x *= 2.0;
}
x
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum InterpError {
Invalid(ValidationError),
UnresolvedImport {
name: String,
},
BadArguments,
FuelExhausted,
StackOverflow,
OutOfMemory,
MemoryFault {
addr: u64,
len: u64,
},
Misaligned {
addr: u64,
align: u64,
},
Overlap,
InvalidBool,
UnreachableReached,
StaleRef,
BadFunctionPointer {
addr: u64,
},
UncheckedError,
Host(String),
}
impl fmt::Display for InterpError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InterpError::Invalid(e) => write!(f, "invalid module: {e}"),
InterpError::UnresolvedImport { name } => write!(f, "unresolved import `{name}`"),
InterpError::BadArguments => f.write_str("arguments do not match the signature"),
InterpError::FuelExhausted => f.write_str("fuel exhausted"),
InterpError::StackOverflow => f.write_str("call depth limit reached"),
InterpError::OutOfMemory => f.write_str("out of memory"),
InterpError::MemoryFault { addr, len } => {
write!(f, "invalid access of {len} bytes at {addr:#x}")
}
InterpError::Misaligned { addr, align } => {
write!(f, "address {addr:#x} is not aligned to {align}")
}
InterpError::Overlap => f.write_str("memcpy ranges overlap"),
InterpError::InvalidBool => f.write_str("loaded bool is not 0 or 1"),
InterpError::UnreachableReached => f.write_str("reached unreachable"),
InterpError::StaleRef => f.write_str("stale or invalid GC reference"),
InterpError::BadFunctionPointer { addr } => write!(f, "bad function pointer {addr:#x}"),
InterpError::UncheckedError => f.write_str("error policy outside a check"),
InterpError::Host(msg) => write!(f, "host: {msg}"),
}
}
}
impl core::error::Error for InterpError {}
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum Outcome {
Return(Vec<Val>),
Unwind(Val),
Trap(u32),
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Limits {
pub fuel: u64,
pub max_depth: u32,
pub memory: u64,
}
impl Default for Limits {
fn default() -> Self {
Limits {
fuel: 10_000_000,
max_depth: 1000,
memory: 4 << 20,
}
}
}
const NULL_GUARD: u64 = 4096;
#[derive(Clone, Debug)]
pub struct Memory {
low: Vec<u8>,
high: Vec<u8>,
size: u64,
ptr: PointerWidth,
globals_end: u64,
stack_base: u64,
sp: u64,
heap_floor: u64,
epoch: u64,
objects: Vec<(u64, u64)>,
}
impl Memory {
fn new(size: u64, ptr: PointerWidth) -> Result<Memory, InterpError> {
let cap = match ptr {
PointerWidth::P32 => 0xE000_0000,
PointerWidth::P64 => 1 << 32,
};
let size = size.min(cap).max(NULL_GUARD);
Ok(Memory {
low: Vec::new(),
high: Vec::new(),
size,
ptr,
globals_end: NULL_GUARD,
stack_base: NULL_GUARD,
sp: NULL_GUARD,
heap_floor: size,
epoch: 0,
objects: Vec::new(),
})
}
fn size(&self) -> u64 {
self.size
}
fn grow_low(&mut self, end: u64) -> Result<(), InterpError> {
let end = usize::try_from(end).map_err(|_| InterpError::OutOfMemory)?;
if end > self.low.len() {
let want = end
.max(self.low.len().saturating_mul(2))
.min(self.size as usize);
self.low
.try_reserve_exact(want - self.low.len())
.map_err(|_| InterpError::OutOfMemory)?;
self.low.resize(want, 0);
}
Ok(())
}
fn grow_high(&mut self, floor: u64) -> Result<(), InterpError> {
let need = usize::try_from(self.size - floor).map_err(|_| InterpError::OutOfMemory)?;
if need > self.high.len() {
let want = need
.max(self.high.len().saturating_mul(2))
.min(self.size as usize);
let mut grown = Vec::new();
grown
.try_reserve_exact(want)
.map_err(|_| InterpError::OutOfMemory)?;
grown.resize(want - self.high.len(), 0);
grown.extend_from_slice(&self.high);
self.high = grown;
}
Ok(())
}
fn claim_stack(&mut self, addr: u64, end: u64) -> Result<(), InterpError> {
self.grow_low(end)?;
if let Some(bytes) = self.low.get_mut(addr as usize..end as usize) {
bytes.fill(0);
}
Ok(())
}
fn live(&self, addr: u64, len: u64) -> bool {
let Some(end) = addr.checked_add(len) else {
return false;
};
let within = |lo: u64, hi: u64| addr >= lo && end <= hi;
within(NULL_GUARD, self.globals_end)
|| within(self.stack_base, self.sp)
|| within(self.heap_floor, self.size())
}
fn range(&self, addr: u64, len: u64) -> Option<&[u8]> {
if addr < self.heap_floor {
self.low.get(addr as usize..(addr + len) as usize)
} else {
let base = self.size - self.high.len() as u64;
let start = (addr - base) as usize;
self.high.get(start..start + len as usize)
}
}
fn range_mut(&mut self, addr: u64, len: u64) -> Option<&mut [u8]> {
if addr < self.heap_floor {
self.low.get_mut(addr as usize..(addr + len) as usize)
} else {
let base = self.size - self.high.len() as u64;
let start = (addr - base) as usize;
self.high.get_mut(start..start + len as usize)
}
}
pub fn read(&self, addr: u64, len: u64) -> Result<&[u8], InterpError> {
if !self.live(addr, len) {
return Err(InterpError::MemoryFault { addr, len });
}
self.range(addr, len)
.ok_or(InterpError::MemoryFault { addr, len })
}
pub fn write(&mut self, addr: u64, data: &[u8]) -> Result<(), InterpError> {
let len = data.len() as u64;
if !self.live(addr, len) {
return Err(InterpError::MemoryFault { addr, len });
}
self.range_mut(addr, len)
.ok_or(InterpError::MemoryFault { addr, len })?
.copy_from_slice(data);
Ok(())
}
pub fn alloc(&mut self, size: u64, align: u64) -> Result<u64, InterpError> {
let align = align.max(1).next_power_of_two();
let floor = self
.heap_floor
.checked_sub(size)
.ok_or(InterpError::OutOfMemory)?
& !(align - 1);
if floor < self.sp.max(self.globals_end) {
return Err(InterpError::OutOfMemory);
}
self.grow_high(floor)?;
self.heap_floor = floor;
Ok(floor)
}
pub fn alloc_object(&mut self, size: u64) -> Result<Val, InterpError> {
let addr = self.alloc(size.max(1), 16)?;
self.objects.push((addr, size));
let index = self.objects.len() as u64;
Ok(Val::new(Type::Ref, self.encode_ref(index)))
}
fn idx_bits(&self) -> u32 {
match self.ptr {
PointerWidth::P32 => 20,
PointerWidth::P64 => 32,
}
}
fn encode_ref(&self, index: u64) -> u64 {
if index == 0 {
return 0;
}
let bits = self.idx_bits();
let epoch_mask = (1u64 << (self.ptr.bits() - bits)) - 1;
((self.epoch & epoch_mask) << bits) | index
}
fn decode_ref(&self, bits: u64) -> Result<u64, InterpError> {
if bits == 0 {
return Ok(0);
}
let ib = self.idx_bits();
let epoch_mask = (1u64 << (self.ptr.bits() - ib)) - 1;
let index = bits & ((1u64 << ib) - 1);
if bits >> ib != self.epoch & epoch_mask || index == 0 || index > self.objects.len() as u64
{
return Err(InterpError::StaleRef);
}
Ok(index)
}
pub fn ref_address(&self, r: Val) -> Result<u64, InterpError> {
let index = self.decode_ref(r.bits())?;
if index == 0 {
return Ok(0);
}
self.objects
.get(index as usize - 1)
.map(|o| o.0)
.ok_or(InterpError::StaleRef)
}
fn load(&self, ty: Type, addr: u64) -> Result<u64, InterpError> {
let size = u64::from(ty.size(self.ptr));
let raw = self.read(addr, size)?;
let mut bits = 0u64;
for (i, &b) in raw.iter().enumerate() {
bits |= u64::from(b) << (8 * i);
}
match ty {
Type::Bool if bits > 1 => Err(InterpError::InvalidBool),
Type::Ref => {
if bits > self.objects.len() as u64 {
return Err(InterpError::StaleRef);
}
Ok(self.encode_ref(bits))
}
_ => Ok(bits),
}
}
fn store(&mut self, ty: Type, addr: u64, bits: u64) -> Result<(), InterpError> {
let size = ty.size(self.ptr) as usize;
let bits = if ty == Type::Ref {
self.decode_ref(bits)?
} else {
bits
};
let le = bits.to_le_bytes();
self.write(addr, le.get(..size).unwrap_or(&le))
}
fn new_epoch(&mut self) {
self.epoch = self.epoch.wrapping_add(1);
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum HostReturn {
Values(Vec<Val>),
Unwind(Val),
}
pub trait Host {
fn call(
&mut self,
name: &str,
args: &[Val],
mem: &mut Memory,
) -> Result<HostReturn, InterpError>;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct NoHost;
impl Host for NoHost {
fn call(&mut self, name: &str, _: &[Val], _: &mut Memory) -> Result<HostReturn, InterpError> {
Err(InterpError::UnresolvedImport { name: name.into() })
}
}
mod exec;
pub use exec::{Interpreter, run};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ieee_remainder_ties_to_even() {
assert_eq!(ieee_remainder(5.0, 2.0), 1.0); assert_eq!(ieee_remainder(7.0, 2.0), -1.0); assert_eq!(ieee_remainder(-5.0, 2.0), -1.0);
assert!(ieee_remainder(1.0, 0.0).is_nan());
assert!(ieee_remainder(f64::INFINITY, 1.0).is_nan());
assert_eq!(ieee_remainder(3.0, f64::INFINITY), 3.0);
assert!(ieee_remainder(f64::MAX, 3.0).abs() <= 1.5);
let tiny = f64::MIN_POSITIVE;
assert_eq!(ieee_remainder(3.0 * tiny, 2.0 * tiny), -tiny);
}
#[test]
fn test_float_min_max_signed_zero() {
assert!(fmin(-0.0, 0.0).is_sign_negative());
assert!(fmin(0.0, -0.0).is_sign_negative());
assert!(fmax(-0.0, 0.0).is_sign_positive());
assert!(fmax(0.0, -0.0).is_sign_positive());
assert!(fmin(f64::NAN, 1.0).is_nan());
assert_eq!(fmax(2.0, 1.0), 2.0);
assert_eq!(fmax(1.0, 2.0), 2.0);
}
#[test]
fn test_ref_encoding_detects_stale_epoch() {
let Ok(mut m) = Memory::new(1 << 16, PointerWidth::P64) else {
return;
};
let Ok(r) = m.alloc_object(8) else { return };
assert!(m.ref_address(r).is_ok());
m.new_epoch();
assert_eq!(m.ref_address(r), Err(InterpError::StaleRef));
assert_eq!(m.ref_address(Val::new(Type::Ref, 0)), Ok(0));
assert_eq!(
m.ref_address(Val::new(Type::Ref, 99)),
Err(InterpError::StaleRef)
);
}
#[test]
fn test_32_bit_refs_fit_the_address_width() {
let Ok(mut m) = Memory::new(1 << 16, PointerWidth::P32) else {
return;
};
for _ in 0..5000 {
m.new_epoch();
}
let Ok(r) = m.alloc_object(4) else { return };
assert!(r.bits() <= u64::from(u32::MAX));
assert!(m.ref_address(r).is_ok());
}
#[test]
fn test_memory_rejects_null_overflow_and_unallocated_ranges() {
let Ok(mut m) = Memory::new(1 << 16, PointerWidth::P64) else {
return;
};
assert!(m.read(0, 1).is_err());
assert!(m.read(NULL_GUARD, 1).is_err()); assert!(m.read(u64::MAX, 2).is_err());
let Ok(a) = m.alloc(16, 8) else { return };
assert!(m.write(a, &[1; 16]).is_ok());
assert!(m.write(a, &[1; 17]).is_err());
assert_eq!(m.alloc(1 << 20, 8), Err(InterpError::OutOfMemory));
}
#[test]
fn test_val_display_and_accessors() {
assert_eq!(Val::i16(-2).to_string(), "-2: i16");
assert_eq!(Val::bool(true).as_bool(), Some(true));
assert_eq!(Val::u8(3).as_f64(), None);
assert_eq!(Val::f32(1.5).as_f64(), Some(1.5));
assert!(!Val::i32(1).same_as(Val::u32(1)));
}
}