Rewrite interpreter generically (#2323)

* Rewrite interpreter generically

This change re-implements the Cranelift interpreter to use generic values; this makes it possible to do abstract interpretation of Cranelift instructions. In doing so, the interpretation state is extracted from the `Interpreter` structure and is accessed via a `State` trait; this makes it possible to not only more clearly observe the interpreter's state but also to interpret using a dummy state (e.g. `ImmutableRegisterState`). This addition made it possible to implement more of the Cranelift instructions (~70%, ignoring the x86-specific instructions).

* Replace macros with closures
This commit is contained in:
Andrew Brown
2020-11-02 12:28:07 -08:00
committed by GitHub
parent 59a2ce4d34
commit 6d50099816
16 changed files with 1590 additions and 342 deletions

View File

@@ -0,0 +1,735 @@
//! The [step] function interprets a single Cranelift instruction given its [State] and
//! [InstructionContext]; the interpretation is generic over [Value]s.
use crate::instruction::InstructionContext;
use crate::state::{MemoryError, State};
use crate::value::{Value, ValueConversionKind, ValueError, ValueResult};
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
use cranelift_codegen::ir::{
types, Block, FuncRef, Function, InstructionData, Opcode, TrapCode, Value as ValueRef,
};
use log::trace;
use smallvec::{smallvec, SmallVec};
use std::ops::RangeFrom;
use thiserror::Error;
/// Interpret a single Cranelift instruction. Note that program traps and interpreter errors are
/// distinct: a program trap results in `Ok(Flow::Trap(...))` whereas an interpretation error (e.g.
/// the types of two values are incompatible) results in `Err(...)`.
#[allow(unused_variables)]
pub fn step<'a, V, I>(
state: &mut dyn State<'a, V>,
inst_context: I,
) -> Result<ControlFlow<'a, V>, StepError>
where
V: Value,
I: InstructionContext,
{
let inst = inst_context.data();
let ctrl_ty = inst_context.controlling_type().unwrap();
trace!(
"Step: {}{}",
inst.opcode(),
if ctrl_ty.is_invalid() {
String::new()
} else {
format!(".{}", ctrl_ty)
}
);
// The following closures make the `step` implementation much easier to express. Note that they
// frequently close over the `state` or `inst_context` for brevity.
// Retrieve the current value for an instruction argument.
let arg = |index: usize| -> Result<V, StepError> {
let value_ref = inst_context.args()[index];
state
.get_value(value_ref)
.ok_or(StepError::UnknownValue(value_ref))
};
// Retrieve the current values for all of an instruction's arguments.
let args = || -> Result<SmallVec<[V; 1]>, StepError> {
state
.collect_values(inst_context.args())
.map_err(|v| StepError::UnknownValue(v))
};
// Retrieve the current values for a range of an instruction's arguments.
let args_range = |indexes: RangeFrom<usize>| -> Result<SmallVec<[V; 1]>, StepError> {
Ok(SmallVec::<[V; 1]>::from(&args()?[indexes]))
};
// Retrieve the immediate value for an instruction, expecting it to exist.
let imm = || -> V { V::from(inst.imm_value().unwrap()) };
// Retrieve the immediate value for an instruction and convert it to the controlling type of the
// instruction. For example, since `InstructionData` stores all integer immediates in a 64-bit
// size, this will attempt to convert `iconst.i8 ...` to an 8-bit size.
let imm_as_ctrl_ty =
|| -> Result<V, ValueError> { V::convert(imm(), ValueConversionKind::Exact(ctrl_ty)) };
// Indicate that the result of a step is to assign a single value to an instruction's results.
let assign = |value: V| ControlFlow::Assign(smallvec![value]);
// Interpret a binary instruction with the given `op`, assigning the resulting value to the
// instruction's results.
let binary = |op: fn(V, V) -> ValueResult<V>,
left: V,
right: V|
-> ValueResult<ControlFlow<V>> { Ok(assign(op(left, right)?)) };
// Same as `binary`, but converts the values to their unsigned form before the operation and
// back to signed form afterwards. Since Cranelift types have no notion of signedness, this
// enables operations that depend on sign.
let binary_unsigned =
|op: fn(V, V) -> ValueResult<V>, left: V, right: V| -> ValueResult<ControlFlow<V>> {
Ok(assign(
op(
left.convert(ValueConversionKind::ToUnsigned)?,
right.convert(ValueConversionKind::ToUnsigned)?,
)?
.convert(ValueConversionKind::ToSigned)?,
))
};
// Choose whether to assign `left` or `right` to the instruction's result based on a `condition`.
let choose = |condition: bool, left: V, right: V| -> ControlFlow<V> {
assign(if condition { left } else { right })
};
// Retrieve an instruction's branch destination; expects the instruction to be a branch.
let branch = || -> Block { inst.branch_destination().unwrap() };
// Based on `condition`, indicate where to continue the control flow.
let branch_when = |condition: bool| -> Result<ControlFlow<V>, StepError> {
Ok(if condition {
ControlFlow::ContinueAt(branch(), args_range(1..)?)
} else {
ControlFlow::Continue
})
};
// Retrieve an instruction's trap code; expects the instruction to be a trap.
let trap_code = || -> TrapCode { inst.trap_code().unwrap() };
// Based on `condition`, either trap or not.
let trap_when = |condition: bool, trap: CraneliftTrap| -> ControlFlow<V> {
if condition {
ControlFlow::Trap(trap)
} else {
ControlFlow::Continue
}
};
// Helper for summing a sequence of values.
fn sum<V: Value>(head: V, tail: SmallVec<[V; 1]>) -> ValueResult<i64> {
let mut acc = head;
for t in tail {
acc = Value::add(acc, t)?;
}
acc.into_int()
}
// Interpret a Cranelift instruction.
Ok(match inst.opcode() {
Opcode::Jump | Opcode::Fallthrough => ControlFlow::ContinueAt(branch(), args()?),
Opcode::Brz => branch_when(!arg(0)?.into_bool()?)?,
Opcode::Brnz => branch_when(arg(0)?.into_bool()?)?,
Opcode::BrIcmp => branch_when(icmp(inst.cond_code().unwrap(), &arg(1)?, &arg(2)?)?)?,
Opcode::Brif => branch_when(state.has_iflag(inst.cond_code().unwrap()))?,
Opcode::Brff => branch_when(state.has_fflag(inst.fp_cond_code().unwrap()))?,
Opcode::BrTable => unimplemented!("BrTable"),
Opcode::JumpTableEntry => unimplemented!("JumpTableEntry"),
Opcode::JumpTableBase => unimplemented!("JumpTableBase"),
Opcode::IndirectJumpTableBr => unimplemented!("IndirectJumpTableBr"),
Opcode::Trap => ControlFlow::Trap(CraneliftTrap::User(trap_code())),
Opcode::Debugtrap => ControlFlow::Trap(CraneliftTrap::Debug),
Opcode::ResumableTrap => ControlFlow::Trap(CraneliftTrap::Resumable),
Opcode::Trapz => trap_when(!arg(0)?.into_bool()?, CraneliftTrap::User(trap_code())),
Opcode::Trapnz => trap_when(arg(0)?.into_bool()?, CraneliftTrap::User(trap_code())),
Opcode::ResumableTrapnz => trap_when(arg(0)?.into_bool()?, CraneliftTrap::Resumable),
Opcode::Trapif => trap_when(
state.has_iflag(inst.cond_code().unwrap()),
CraneliftTrap::User(trap_code()),
),
Opcode::Trapff => trap_when(
state.has_fflag(inst.fp_cond_code().unwrap()),
CraneliftTrap::User(trap_code()),
),
Opcode::Return => ControlFlow::Return(args()?),
Opcode::FallthroughReturn => ControlFlow::Return(args()?),
Opcode::Call => {
if let InstructionData::Call { func_ref, .. } = inst {
let function = state
.get_function(func_ref)
.ok_or(StepError::UnknownFunction(func_ref))?;
ControlFlow::Call(function, args()?)
} else {
unreachable!()
}
}
Opcode::CallIndirect => unimplemented!("CallIndirect"),
Opcode::FuncAddr => unimplemented!("FuncAddr"),
Opcode::Load
| Opcode::LoadComplex
| Opcode::Uload8
| Opcode::Uload8Complex
| Opcode::Sload8
| Opcode::Sload8Complex
| Opcode::Uload16
| Opcode::Uload16Complex
| Opcode::Sload16
| Opcode::Sload16Complex
| Opcode::Uload32
| Opcode::Uload32Complex
| Opcode::Sload32
| Opcode::Sload32Complex
| Opcode::Uload8x8
| Opcode::Uload8x8Complex
| Opcode::Sload8x8
| Opcode::Sload8x8Complex
| Opcode::Uload16x4
| Opcode::Uload16x4Complex
| Opcode::Sload16x4
| Opcode::Sload16x4Complex
| Opcode::Uload32x2
| Opcode::Uload32x2Complex
| Opcode::Sload32x2
| Opcode::Sload32x2Complex => {
let address = sum(imm(), args()?)? as usize;
let ctrl_ty = inst_context.controlling_type().unwrap();
let (load_ty, kind) = match inst.opcode() {
Opcode::Load | Opcode::LoadComplex => (ctrl_ty, None),
Opcode::Uload8 | Opcode::Uload8Complex => {
(types::I8, Some(ValueConversionKind::ZeroExtend(ctrl_ty)))
}
Opcode::Sload8 | Opcode::Sload8Complex => {
(types::I8, Some(ValueConversionKind::SignExtend(ctrl_ty)))
}
Opcode::Uload16 | Opcode::Uload16Complex => {
(types::I16, Some(ValueConversionKind::ZeroExtend(ctrl_ty)))
}
Opcode::Sload16 | Opcode::Sload16Complex => {
(types::I16, Some(ValueConversionKind::SignExtend(ctrl_ty)))
}
Opcode::Uload32 | Opcode::Uload32Complex => {
(types::I32, Some(ValueConversionKind::ZeroExtend(ctrl_ty)))
}
Opcode::Sload32 | Opcode::Sload32Complex => {
(types::I32, Some(ValueConversionKind::SignExtend(ctrl_ty)))
}
Opcode::Uload8x8
| Opcode::Uload8x8Complex
| Opcode::Sload8x8
| Opcode::Sload8x8Complex
| Opcode::Uload16x4
| Opcode::Uload16x4Complex
| Opcode::Sload16x4
| Opcode::Sload16x4Complex
| Opcode::Uload32x2
| Opcode::Uload32x2Complex
| Opcode::Sload32x2
| Opcode::Sload32x2Complex => unimplemented!(),
_ => unreachable!(),
};
let loaded = state.load_heap(address, load_ty)?;
let extended = if let Some(c) = kind {
loaded.convert(c)?
} else {
loaded
};
ControlFlow::Assign(smallvec!(extended))
}
Opcode::Store
| Opcode::StoreComplex
| Opcode::Istore8
| Opcode::Istore8Complex
| Opcode::Istore16
| Opcode::Istore16Complex
| Opcode::Istore32
| Opcode::Istore32Complex => {
let address = sum(imm(), args_range(1..)?)? as usize;
let kind = match inst.opcode() {
Opcode::Store | Opcode::StoreComplex => None,
Opcode::Istore8 | Opcode::Istore8Complex => {
Some(ValueConversionKind::Truncate(types::I8))
}
Opcode::Istore16 | Opcode::Istore16Complex => {
Some(ValueConversionKind::Truncate(types::I16))
}
Opcode::Istore32 | Opcode::Istore32Complex => {
Some(ValueConversionKind::Truncate(types::I32))
}
_ => unreachable!(),
};
let reduced = if let Some(c) = kind {
arg(0)?.convert(c)?
} else {
arg(0)?
};
state.store_heap(address, reduced)?;
ControlFlow::Continue
}
Opcode::StackLoad => {
let address = sum(imm(), args_range(1..)?)? as usize;
let load_ty = inst_context.controlling_type().unwrap();
let loaded = state.load_stack(address, load_ty)?;
ControlFlow::Assign(smallvec!(loaded))
}
Opcode::StackStore => {
let address = sum(imm(), args_range(1..)?)? as usize;
let arg0 = arg(0)?;
state.store_stack(address, arg0)?;
ControlFlow::Continue
}
Opcode::StackAddr => unimplemented!("StackAddr"),
Opcode::GlobalValue => unimplemented!("GlobalValue"),
Opcode::SymbolValue => unimplemented!("SymbolValue"),
Opcode::TlsValue => unimplemented!("TlsValue"),
Opcode::HeapAddr => unimplemented!("HeapAddr"),
Opcode::GetPinnedReg => unimplemented!("GetPinnedReg"),
Opcode::SetPinnedReg => unimplemented!("SetPinnedReg"),
Opcode::TableAddr => unimplemented!("TableAddr"),
Opcode::Iconst => assign(Value::int(imm().into_int()?, ctrl_ty)?),
Opcode::F32const => assign(imm()),
Opcode::F64const => assign(imm()),
Opcode::Bconst => assign(imm()),
Opcode::Vconst => unimplemented!("Vconst"),
Opcode::ConstAddr => unimplemented!("ConstAddr"),
Opcode::Null => unimplemented!("Null"),
Opcode::Nop => ControlFlow::Continue,
Opcode::Select => choose(arg(0)?.into_bool()?, arg(1)?, arg(2)?),
Opcode::Selectif => choose(state.has_iflag(inst.cond_code().unwrap()), arg(1)?, arg(2)?),
Opcode::SelectifSpectreGuard => unimplemented!("SelectifSpectreGuard"),
Opcode::Bitselect => {
let mask_a = Value::and(arg(0)?, arg(1)?)?;
let mask_b = Value::and(Value::not(arg(0)?)?, arg(2)?)?;
assign(Value::or(mask_a, mask_b)?)
}
Opcode::Copy => assign(arg(0)?),
Opcode::Spill => unimplemented!("Spill"),
Opcode::Fill => unimplemented!("Fill"),
Opcode::FillNop => assign(arg(0)?),
Opcode::DummySargT => unimplemented!("DummySargT"),
Opcode::Regmove => ControlFlow::Continue,
Opcode::CopySpecial => ControlFlow::Continue,
Opcode::CopyToSsa => assign(arg(0)?),
Opcode::CopyNop => unimplemented!("CopyNop"),
Opcode::AdjustSpDown => unimplemented!("AdjustSpDown"),
Opcode::AdjustSpUpImm => unimplemented!("AdjustSpUpImm"),
Opcode::AdjustSpDownImm => unimplemented!("AdjustSpDownImm"),
Opcode::IfcmpSp => unimplemented!("IfcmpSp"),
Opcode::Regspill => unimplemented!("Regspill"),
Opcode::Regfill => unimplemented!("Regfill"),
Opcode::Safepoint => unimplemented!("Safepoint"),
Opcode::Icmp => assign(Value::bool(
icmp(inst.cond_code().unwrap(), &arg(0)?, &arg(1)?)?,
ctrl_ty.as_bool(),
)?),
Opcode::IcmpImm => assign(Value::bool(
icmp(inst.cond_code().unwrap(), &arg(0)?, &imm_as_ctrl_ty()?)?,
ctrl_ty.as_bool(),
)?),
Opcode::Ifcmp | Opcode::IfcmpImm => {
let arg0 = arg(0)?;
let arg1 = match inst.opcode() {
Opcode::Ifcmp => arg(1)?,
Opcode::IfcmpImm => imm_as_ctrl_ty()?,
_ => unreachable!(),
};
state.clear_flags();
for f in &[
IntCC::Equal,
IntCC::NotEqual,
IntCC::SignedLessThan,
IntCC::SignedGreaterThanOrEqual,
IntCC::SignedGreaterThan,
IntCC::SignedLessThanOrEqual,
IntCC::UnsignedLessThan,
IntCC::UnsignedGreaterThanOrEqual,
IntCC::UnsignedGreaterThan,
IntCC::UnsignedLessThanOrEqual,
] {
if icmp(*f, &arg0, &arg1)? {
state.set_iflag(*f);
}
}
ControlFlow::Continue
}
Opcode::Imin => choose(Value::gt(&arg(1)?, &arg(0)?)?, arg(0)?, arg(1)?),
Opcode::Umin => choose(
Value::gt(
&arg(1)?.convert(ValueConversionKind::ToUnsigned)?,
&arg(0)?.convert(ValueConversionKind::ToUnsigned)?,
)?,
arg(0)?,
arg(1)?,
),
Opcode::Imax => choose(Value::gt(&arg(0)?, &arg(1)?)?, arg(0)?, arg(1)?),
Opcode::Umax => choose(
Value::gt(
&arg(0)?.convert(ValueConversionKind::ToUnsigned)?,
&arg(1)?.convert(ValueConversionKind::ToUnsigned)?,
)?,
arg(0)?,
arg(1)?,
),
Opcode::AvgRound => {
let sum = Value::add(arg(0)?, arg(1)?)?;
let one = Value::int(1, arg(0)?.ty())?;
let inc = Value::add(sum, one)?;
let two = Value::int(2, arg(0)?.ty())?;
binary(Value::div, inc, two)?
}
Opcode::Iadd => binary(Value::add, arg(0)?, arg(1)?)?,
Opcode::UaddSat => unimplemented!("UaddSat"),
Opcode::SaddSat => unimplemented!("SaddSat"),
Opcode::Isub => binary(Value::sub, arg(0)?, arg(1)?)?,
Opcode::UsubSat => unimplemented!("UsubSat"),
Opcode::SsubSat => unimplemented!("SsubSat"),
Opcode::Ineg => binary(Value::sub, Value::int(0, ctrl_ty)?, arg(0)?)?,
Opcode::Iabs => unimplemented!("Iabs"),
Opcode::Imul => binary(Value::mul, arg(0)?, arg(1)?)?,
Opcode::Umulhi => unimplemented!("Umulhi"),
Opcode::Smulhi => unimplemented!("Smulhi"),
Opcode::Udiv => binary_unsigned(Value::div, arg(0)?, arg(1)?)?,
Opcode::Sdiv => binary(Value::div, arg(0)?, arg(1)?)?,
Opcode::Urem => binary_unsigned(Value::rem, arg(0)?, arg(1)?)?,
Opcode::Srem => binary(Value::rem, arg(0)?, arg(1)?)?,
Opcode::IaddImm => binary(Value::add, arg(0)?, imm_as_ctrl_ty()?)?,
Opcode::ImulImm => binary(Value::mul, arg(0)?, imm_as_ctrl_ty()?)?,
Opcode::UdivImm => binary_unsigned(Value::div, arg(0)?, imm())?,
Opcode::SdivImm => binary(Value::div, arg(0)?, imm_as_ctrl_ty()?)?,
Opcode::UremImm => binary_unsigned(Value::rem, arg(0)?, imm())?,
Opcode::SremImm => binary(Value::rem, arg(0)?, imm_as_ctrl_ty()?)?,
Opcode::IrsubImm => binary(Value::sub, imm_as_ctrl_ty()?, arg(0)?)?,
Opcode::IaddCin => unimplemented!("IaddCin"),
Opcode::IaddIfcin => unimplemented!("IaddIfcin"),
Opcode::IaddCout => unimplemented!("IaddCout"),
Opcode::IaddIfcout => unimplemented!("IaddIfcout"),
Opcode::IaddCarry => unimplemented!("IaddCarry"),
Opcode::IaddIfcarry => unimplemented!("IaddIfcarry"),
Opcode::IsubBin => unimplemented!("IsubBin"),
Opcode::IsubIfbin => unimplemented!("IsubIfbin"),
Opcode::IsubBout => unimplemented!("IsubBout"),
Opcode::IsubIfbout => unimplemented!("IsubIfbout"),
Opcode::IsubBorrow => unimplemented!("IsubBorrow"),
Opcode::IsubIfborrow => unimplemented!("IsubIfborrow"),
Opcode::Band => binary(Value::and, arg(0)?, arg(1)?)?,
Opcode::Bor => binary(Value::or, arg(0)?, arg(1)?)?,
Opcode::Bxor => binary(Value::xor, arg(0)?, arg(1)?)?,
Opcode::Bnot => assign(Value::not(arg(0)?)?),
Opcode::BandNot => binary(Value::and, arg(0)?, Value::not(arg(1)?)?)?,
Opcode::BorNot => binary(Value::or, arg(0)?, Value::not(arg(1)?)?)?,
Opcode::BxorNot => binary(Value::xor, arg(0)?, Value::not(arg(1)?)?)?,
Opcode::BandImm => binary(Value::and, arg(0)?, imm_as_ctrl_ty()?)?,
Opcode::BorImm => binary(Value::or, arg(0)?, imm_as_ctrl_ty()?)?,
Opcode::BxorImm => binary(Value::xor, arg(0)?, imm_as_ctrl_ty()?)?,
Opcode::Rotl => binary(Value::rotl, arg(0)?, arg(1)?)?,
Opcode::Rotr => binary(Value::rotr, arg(0)?, arg(1)?)?,
Opcode::RotlImm => binary(Value::rotl, arg(0)?, imm_as_ctrl_ty()?)?,
Opcode::RotrImm => binary(Value::rotr, arg(0)?, imm_as_ctrl_ty()?)?,
Opcode::Ishl => binary(Value::shl, arg(0)?, arg(1)?)?,
Opcode::Ushr => binary(Value::ushr, arg(0)?, arg(1)?)?,
Opcode::Sshr => binary(Value::ishr, arg(0)?, arg(1)?)?,
Opcode::IshlImm => binary(Value::shl, arg(0)?, imm_as_ctrl_ty()?)?,
Opcode::UshrImm => binary(Value::ushr, arg(0)?, imm_as_ctrl_ty()?)?,
Opcode::SshrImm => binary(Value::ishr, arg(0)?, imm_as_ctrl_ty()?)?,
Opcode::Bitrev => unimplemented!("Bitrev"),
Opcode::Clz => unimplemented!("Clz"),
Opcode::Cls => unimplemented!("Cls"),
Opcode::Ctz => unimplemented!("Ctz"),
Opcode::Popcnt => unimplemented!("Popcnt"),
Opcode::Fcmp => assign(Value::bool(
fcmp(inst.fp_cond_code().unwrap(), &arg(0)?, &arg(1)?)?,
ctrl_ty.as_bool(),
)?),
Opcode::Ffcmp => {
let arg0 = arg(0)?;
let arg1 = arg(1)?;
state.clear_flags();
for f in &[
FloatCC::Ordered,
FloatCC::Unordered,
FloatCC::Equal,
FloatCC::NotEqual,
FloatCC::OrderedNotEqual,
FloatCC::UnorderedOrEqual,
FloatCC::LessThan,
FloatCC::LessThanOrEqual,
FloatCC::GreaterThan,
FloatCC::GreaterThanOrEqual,
FloatCC::UnorderedOrLessThan,
FloatCC::UnorderedOrLessThanOrEqual,
FloatCC::UnorderedOrGreaterThan,
FloatCC::UnorderedOrGreaterThanOrEqual,
] {
if fcmp(*f, &arg0, &arg1)? {
state.set_fflag(*f);
}
}
ControlFlow::Continue
}
Opcode::Fadd => binary(Value::add, arg(0)?, arg(1)?)?,
Opcode::Fsub => binary(Value::sub, arg(0)?, arg(1)?)?,
Opcode::Fmul => binary(Value::mul, arg(0)?, arg(1)?)?,
Opcode::Fdiv => binary(Value::div, arg(0)?, arg(1)?)?,
Opcode::Sqrt => unimplemented!("Sqrt"),
Opcode::Fma => unimplemented!("Fma"),
Opcode::Fneg => binary(Value::sub, Value::float(0, ctrl_ty)?, arg(0)?)?,
Opcode::Fabs => unimplemented!("Fabs"),
Opcode::Fcopysign => unimplemented!("Fcopysign"),
Opcode::Fmin => choose(
Value::is_nan(&arg(0)?)? || Value::lt(&arg(0)?, &arg(1)?)?,
arg(0)?,
arg(1)?,
),
Opcode::FminPseudo => unimplemented!("FminPseudo"),
Opcode::Fmax => choose(
Value::is_nan(&arg(0)?)? || Value::gt(&arg(0)?, &arg(1)?)?,
arg(0)?,
arg(1)?,
),
Opcode::FmaxPseudo => unimplemented!("FmaxPseudo"),
Opcode::Ceil => unimplemented!("Ceil"),
Opcode::Floor => unimplemented!("Floor"),
Opcode::Trunc => unimplemented!("Trunc"),
Opcode::Nearest => unimplemented!("Nearest"),
Opcode::IsNull => unimplemented!("IsNull"),
Opcode::IsInvalid => unimplemented!("IsInvalid"),
Opcode::Trueif => choose(
state.has_iflag(inst.cond_code().unwrap()),
Value::bool(true, ctrl_ty)?,
Value::bool(false, ctrl_ty)?,
),
Opcode::Trueff => choose(
state.has_fflag(inst.fp_cond_code().unwrap()),
Value::bool(true, ctrl_ty)?,
Value::bool(false, ctrl_ty)?,
),
Opcode::Bitcast
| Opcode::RawBitcast
| Opcode::ScalarToVector
| Opcode::Breduce
| Opcode::Bextend
| Opcode::Bint
| Opcode::Bmask
| Opcode::Ireduce => assign(Value::convert(
arg(0)?,
ValueConversionKind::Exact(ctrl_ty),
)?),
Opcode::Snarrow => assign(Value::convert(
arg(0)?,
ValueConversionKind::Truncate(ctrl_ty),
)?),
Opcode::Sextend => assign(Value::convert(
arg(0)?,
ValueConversionKind::SignExtend(ctrl_ty),
)?),
Opcode::Unarrow => assign(Value::convert(
arg(0)?,
ValueConversionKind::Truncate(ctrl_ty),
)?),
Opcode::Uextend => assign(Value::convert(
arg(0)?,
ValueConversionKind::ZeroExtend(ctrl_ty),
)?),
Opcode::Fpromote => assign(Value::convert(
arg(0)?,
ValueConversionKind::Exact(ctrl_ty),
)?),
Opcode::Fdemote => assign(Value::convert(
arg(0)?,
ValueConversionKind::RoundNearestEven(ctrl_ty),
)?),
Opcode::Shuffle => unimplemented!("Shuffle"),
Opcode::Swizzle => unimplemented!("Swizzle"),
Opcode::Splat => unimplemented!("Splat"),
Opcode::LoadSplat => unimplemented!("LoadSplat"),
Opcode::Insertlane => unimplemented!("Insertlane"),
Opcode::Extractlane => unimplemented!("Extractlane"),
Opcode::VhighBits => unimplemented!("VhighBits"),
Opcode::Vsplit => unimplemented!("Vsplit"),
Opcode::Vconcat => unimplemented!("Vconcat"),
Opcode::Vselect => unimplemented!("Vselect"),
Opcode::VanyTrue => unimplemented!("VanyTrue"),
Opcode::VallTrue => unimplemented!("VallTrue"),
Opcode::SwidenLow => unimplemented!("SwidenLow"),
Opcode::SwidenHigh => unimplemented!("SwidenHigh"),
Opcode::UwidenLow => unimplemented!("UwidenLow"),
Opcode::UwidenHigh => unimplemented!("UwidenHigh"),
Opcode::FcvtToUint => unimplemented!("FcvtToUint"),
Opcode::FcvtToUintSat => unimplemented!("FcvtToUintSat"),
Opcode::FcvtToSint => unimplemented!("FcvtToSint"),
Opcode::FcvtToSintSat => unimplemented!("FcvtToSintSat"),
Opcode::FcvtFromUint => unimplemented!("FcvtFromUint"),
Opcode::FcvtFromSint => unimplemented!("FcvtFromSint"),
Opcode::Isplit => unimplemented!("Isplit"),
Opcode::Iconcat => unimplemented!("Iconcat"),
Opcode::AtomicRmw => unimplemented!("AtomicRmw"),
Opcode::AtomicCas => unimplemented!("AtomicCas"),
Opcode::AtomicLoad => unimplemented!("AtomicLoad"),
Opcode::AtomicStore => unimplemented!("AtomicStore"),
Opcode::Fence => unimplemented!("Fence"),
// TODO: these instructions should be removed once the new backend makes these obsolete
// (see https://github.com/bytecodealliance/wasmtime/issues/1936); additionally, the
// "all-arch" feature for cranelift-codegen would become unnecessary for this crate.
Opcode::X86Udivmodx
| Opcode::X86Sdivmodx
| Opcode::X86Umulx
| Opcode::X86Smulx
| Opcode::X86Cvtt2si
| Opcode::X86Vcvtudq2ps
| Opcode::X86Fmin
| Opcode::X86Fmax
| Opcode::X86Push
| Opcode::X86Pop
| Opcode::X86Bsr
| Opcode::X86Bsf
| Opcode::X86Pshufd
| Opcode::X86Pshufb
| Opcode::X86Pblendw
| Opcode::X86Pextr
| Opcode::X86Pinsr
| Opcode::X86Insertps
| Opcode::X86Punpckh
| Opcode::X86Punpckl
| Opcode::X86Movsd
| Opcode::X86Movlhps
| Opcode::X86Psll
| Opcode::X86Psrl
| Opcode::X86Psra
| Opcode::X86Pmullq
| Opcode::X86Pmuludq
| Opcode::X86Ptest
| Opcode::X86Pmaxs
| Opcode::X86Pmaxu
| Opcode::X86Pmins
| Opcode::X86Pminu
| Opcode::X86Palignr
| Opcode::X86ElfTlsGetAddr
| Opcode::X86MachoTlsGetAddr => unimplemented!("x86 instruction: {}", inst.opcode()),
})
}
#[derive(Error, Debug)]
pub enum StepError {
#[error("unable to retrieve value from SSA reference: {0}")]
UnknownValue(ValueRef),
#[error("unable to find the following function: {0}")]
UnknownFunction(FuncRef),
#[error("cannot step with these values")]
ValueError(#[from] ValueError),
#[error("failed to access memory")]
MemoryError(#[from] MemoryError),
}
/// Enumerate the ways in which the control flow can change based on a single step in a Cranelift
/// interpreter.
#[derive(Debug)]
pub enum ControlFlow<'a, V> {
/// Return one or more values from an instruction to be assigned to a left-hand side, e.g.:
/// in `v0 = iadd v1, v2`, the sum of `v1` and `v2` is assigned to `v0`.
Assign(SmallVec<[V; 1]>),
/// Continue to the next available instruction, e.g.: in `nop`, we expect to resume execution
/// at the instruction after it.
Continue,
/// Jump to another block with the given parameters, e.g.: in `brz v0, block42, [v1, v2]`, if
/// the condition is true, we continue execution at the first instruction of `block42` with the
/// values in `v1` and `v2` filling in the block parameters.
ContinueAt(Block, SmallVec<[V; 1]>),
/// Indicates a call the given [Function] with the supplied arguments.
Call(&'a Function, SmallVec<[V; 1]>),
/// Return from the current function with the given parameters, e.g.: `return [v1, v2]`.
Return(SmallVec<[V; 1]>),
/// Stop with a program-generated trap; note that these are distinct from errors that may occur
/// during interpretation.
Trap(CraneliftTrap),
}
impl<'a, V> ControlFlow<'a, V> {
/// For convenience, we can unwrap the [ControlFlow] state assuming that it is a
/// [ControlFlow::Return], panicking otherwise.
pub fn unwrap_return(self) -> Vec<V> {
if let ControlFlow::Return(values) = self {
values.into_vec()
} else {
panic!("expected the control flow to be in the return state")
}
}
}
#[derive(Error, Debug)]
pub enum CraneliftTrap {
#[error("user code: {0}")]
User(TrapCode),
#[error("user debug")]
Debug,
#[error("resumable")]
Resumable,
}
/// Compare two values using the given integer condition `code`.
fn icmp<V>(code: IntCC, left: &V, right: &V) -> ValueResult<bool>
where
V: Value,
{
Ok(match code {
IntCC::Equal => Value::eq(left, right)?,
IntCC::NotEqual => !Value::eq(left, right)?,
IntCC::SignedGreaterThan => Value::gt(left, right)?,
IntCC::SignedGreaterThanOrEqual => Value::ge(left, right)?,
IntCC::SignedLessThan => Value::lt(left, right)?,
IntCC::SignedLessThanOrEqual => Value::le(left, right)?,
IntCC::UnsignedGreaterThan => Value::gt(
&left.clone().convert(ValueConversionKind::ToUnsigned)?,
&right.clone().convert(ValueConversionKind::ToUnsigned)?,
)?,
IntCC::UnsignedGreaterThanOrEqual => Value::ge(
&left.clone().convert(ValueConversionKind::ToUnsigned)?,
&right.clone().convert(ValueConversionKind::ToUnsigned)?,
)?,
IntCC::UnsignedLessThan => Value::lt(
&left.clone().convert(ValueConversionKind::ToUnsigned)?,
&right.clone().convert(ValueConversionKind::ToUnsigned)?,
)?,
IntCC::UnsignedLessThanOrEqual => Value::le(
&left.clone().convert(ValueConversionKind::ToUnsigned)?,
&right.clone().convert(ValueConversionKind::ToUnsigned)?,
)?,
IntCC::Overflow => unimplemented!("IntCC::Overflow"),
IntCC::NotOverflow => unimplemented!("IntCC::NotOverflow"),
})
}
/// Compare two values using the given floating point condition `code`.
fn fcmp<V>(code: FloatCC, left: &V, right: &V) -> ValueResult<bool>
where
V: Value,
{
Ok(match code {
FloatCC::Ordered => {
Value::eq(left, right)? || Value::lt(left, right)? || Value::gt(left, right)?
}
FloatCC::Unordered => Value::uno(left, right)?,
FloatCC::Equal => Value::eq(left, right)?,
FloatCC::NotEqual => {
Value::lt(left, right)? || Value::gt(left, right)? || Value::uno(left, right)?
}
FloatCC::OrderedNotEqual => Value::lt(left, right)? || Value::gt(left, right)?,
FloatCC::UnorderedOrEqual => Value::eq(left, right)? || Value::uno(left, right)?,
FloatCC::LessThan => Value::lt(left, right)?,
FloatCC::LessThanOrEqual => Value::lt(left, right)? || Value::eq(left, right)?,
FloatCC::GreaterThan => Value::gt(left, right)?,
FloatCC::GreaterThanOrEqual => Value::gt(left, right)? || Value::eq(left, right)?,
FloatCC::UnorderedOrLessThan => Value::uno(left, right)? || Value::lt(left, right)?,
FloatCC::UnorderedOrLessThanOrEqual => {
Value::uno(left, right)? || Value::lt(left, right)? || Value::eq(left, right)?
}
FloatCC::UnorderedOrGreaterThan => Value::uno(left, right)? || Value::gt(left, right)?,
FloatCC::UnorderedOrGreaterThanOrEqual => {
Value::uno(left, right)? || Value::gt(left, right)? || Value::eq(left, right)?
}
})
}