diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3c3caeea5abf..52f27bd40d7d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -50,7 +50,7 @@ jobs: name: Doc - build the API documentation runs-on: ubuntu-latest env: - RUSTDOCFLAGS: -Dintra-doc-link-resolution-failure + RUSTDOCFLAGS: -Dbroken_intra_doc_links steps: - uses: actions/checkout@v2 with: diff --git a/Cargo.lock b/Cargo.lock index 30fca096e4e1..9899b590dbc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -413,6 +413,7 @@ dependencies = [ "cranelift-frontend", "cranelift-reader", "log", + "smallvec", "thiserror", ] @@ -1884,9 +1885,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3757cb9d89161a2f24e1cf78efa0c1fcff485d18e3f55e0aa3480824ddaa0f3f" +checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" [[package]] name = "souper-ir" diff --git a/cranelift/codegen/src/data_value.rs b/cranelift/codegen/src/data_value.rs index 6ffcb99842d8..193607f392bf 100644 --- a/cranelift/codegen/src/data_value.rs +++ b/cranelift/codegen/src/data_value.rs @@ -1,9 +1,10 @@ //! This module gives users to instantiate values that Cranelift understands. These values are used, //! for example, during interpretation and for wrapping immediates. -use crate::ir::immediates::{Ieee32, Ieee64, Imm64, Offset32}; +use crate::ir::immediates::{Ieee32, Ieee64, Offset32}; use crate::ir::{types, ConstantData, Type}; use core::convert::TryInto; use core::fmt::{self, Display, Formatter}; +use core::ptr; use thiserror::Error; /// Represent a data value. Where [Value] is an SSA reference, [DataValue] is the type + value @@ -11,27 +12,32 @@ use thiserror::Error; /// /// [Value]: crate::ir::Value #[allow(missing_docs)] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, PartialOrd)] pub enum DataValue { B(bool), I8(i8), I16(i16), I32(i32), I64(i64), + U8(u8), + U16(u16), + U32(u32), + U64(u64), F32(Ieee32), F64(Ieee64), V128([u8; 16]), } impl DataValue { - /// Try to cast an immediate integer ([Imm64]) to the given Cranelift [Type]. - pub fn from_integer(imm: Imm64, ty: Type) -> Result { + /// Try to cast an immediate integer (a wrapped `i64` on most Cranelift instructions) to the + /// given Cranelift [Type]. + pub fn from_integer(imm: i64, ty: Type) -> Result { match ty { - types::I8 => Ok(DataValue::I8(imm.bits() as i8)), - types::I16 => Ok(DataValue::I16(imm.bits() as i16)), - types::I32 => Ok(DataValue::I32(imm.bits() as i32)), - types::I64 => Ok(DataValue::I64(imm.bits())), - _ => Err(DataValueCastFailure::FromImm64(imm, ty)), + types::I8 => Ok(DataValue::I8(imm as i8)), + types::I16 => Ok(DataValue::I16(imm as i16)), + types::I32 => Ok(DataValue::I32(imm as i32)), + types::I64 => Ok(DataValue::I64(imm)), + _ => Err(DataValueCastFailure::FromInteger(imm, ty)), } } @@ -39,10 +45,10 @@ impl DataValue { pub fn ty(&self) -> Type { match self { DataValue::B(_) => types::B8, // A default type. - DataValue::I8(_) => types::I8, - DataValue::I16(_) => types::I16, - DataValue::I32(_) => types::I32, - DataValue::I64(_) => types::I64, + DataValue::I8(_) | DataValue::U8(_) => types::I8, + DataValue::I16(_) | DataValue::U16(_) => types::I16, + DataValue::I32(_) | DataValue::U32(_) => types::I32, + DataValue::I64(_) | DataValue::U64(_) => types::I64, DataValue::F32(_) => types::F32, DataValue::F64(_) => types::F64, DataValue::V128(_) => types::I8X16, // A default type. @@ -56,6 +62,38 @@ impl DataValue { _ => false, } } + + /// Write a [DataValue] to a memory location. + pub unsafe fn write_value_to(&self, p: *mut u128) { + match self { + DataValue::B(b) => ptr::write(p as *mut bool, *b), + DataValue::I8(i) => ptr::write(p as *mut i8, *i), + DataValue::I16(i) => ptr::write(p as *mut i16, *i), + DataValue::I32(i) => ptr::write(p as *mut i32, *i), + DataValue::I64(i) => ptr::write(p as *mut i64, *i), + DataValue::F32(f) => ptr::write(p as *mut Ieee32, *f), + DataValue::F64(f) => ptr::write(p as *mut Ieee64, *f), + DataValue::V128(b) => ptr::write(p as *mut [u8; 16], *b), + _ => unimplemented!(), + } + } + + /// Read a [DataValue] from a memory location using a given [Type]. + pub unsafe fn read_value_from(p: *const u128, ty: Type) -> Self { + match ty { + types::I8 => DataValue::I8(ptr::read(p as *const i8)), + types::I16 => DataValue::I16(ptr::read(p as *const i16)), + types::I32 => DataValue::I32(ptr::read(p as *const i32)), + types::I64 => DataValue::I64(ptr::read(p as *const i64)), + types::F32 => DataValue::F32(ptr::read(p as *const Ieee32)), + types::F64 => DataValue::F64(ptr::read(p as *const Ieee64)), + _ if ty.is_bool() => DataValue::B(ptr::read(p as *const bool)), + _ if ty.is_vector() && ty.bytes() == 16 => { + DataValue::V128(ptr::read(p as *const [u8; 16])) + } + _ => unimplemented!(), + } + } } /// Record failures to cast [DataValue]. @@ -64,8 +102,8 @@ impl DataValue { pub enum DataValueCastFailure { #[error("unable to cast data value of type {0} to type {1}")] TryInto(Type, Type), - #[error("unable to cast Imm64({0}) to a data value of type {1}")] - FromImm64(Imm64, Type), + #[error("unable to cast i64({0}) to a data value of type {1}")] + FromInteger(i64, Type), } /// Helper for creating conversion implementations for [DataValue]. @@ -97,6 +135,10 @@ build_conversion_impl!(i8, I8, I8); build_conversion_impl!(i16, I16, I16); build_conversion_impl!(i32, I32, I32); build_conversion_impl!(i64, I64, I64); +build_conversion_impl!(u8, U8, I8); +build_conversion_impl!(u16, U16, I16); +build_conversion_impl!(u32, U32, I32); +build_conversion_impl!(u64, U64, I64); build_conversion_impl!(Ieee32, F32, F32); build_conversion_impl!(Ieee64, F64, F64); build_conversion_impl!([u8; 16], V128, I8X16); @@ -114,6 +156,10 @@ impl Display for DataValue { DataValue::I16(dv) => write!(f, "{}", dv), DataValue::I32(dv) => write!(f, "{}", dv), DataValue::I64(dv) => write!(f, "{}", dv), + DataValue::U8(dv) => write!(f, "{}", dv), + DataValue::U16(dv) => write!(f, "{}", dv), + DataValue::U32(dv) => write!(f, "{}", dv), + DataValue::U64(dv) => write!(f, "{}", dv), // The Ieee* wrappers here print the expected syntax. DataValue::F32(dv) => write!(f, "{}", dv), DataValue::F64(dv) => write!(f, "{}", dv), diff --git a/cranelift/codegen/src/ir/immediates.rs b/cranelift/codegen/src/ir/immediates.rs index 0320676be20b..f575ea361f5e 100644 --- a/cranelift/codegen/src/ir/immediates.rs +++ b/cranelift/codegen/src/ir/immediates.rs @@ -5,6 +5,7 @@ //! `cranelift-codegen/meta/src/shared/immediates` crate in the meta language. use alloc::vec::Vec; +use core::cmp::Ordering; use core::fmt::{self, Display, Formatter}; use core::str::FromStr; use core::{i32, u32}; @@ -739,6 +740,17 @@ impl Ieee32 { pub fn bits(self) -> u32 { self.0 } + + /// Check if the value is a NaN. + pub fn is_nan(&self) -> bool { + f32::from_bits(self.0).is_nan() + } +} + +impl PartialOrd for Ieee32 { + fn partial_cmp(&self, other: &Self) -> Option { + f32::from_bits(self.0).partial_cmp(&f32::from_bits(other.0)) + } } impl Display for Ieee32 { @@ -812,6 +824,18 @@ impl Ieee64 { pub fn bits(self) -> u64 { self.0 } + + /// Check if the value is a NaN. For [Ieee64], this means checking that the 11 exponent bits are + /// all set. + pub fn is_nan(&self) -> bool { + f64::from_bits(self.0).is_nan() + } +} + +impl PartialOrd for Ieee64 { + fn partial_cmp(&self, other: &Self) -> Option { + f64::from_bits(self.0).partial_cmp(&f64::from_bits(other.0)) + } } impl Display for Ieee64 { diff --git a/cranelift/filetests/src/function_runner.rs b/cranelift/filetests/src/function_runner.rs index e798e4d072a2..ed8411f44824 100644 --- a/cranelift/filetests/src/function_runner.rs +++ b/cranelift/filetests/src/function_runner.rs @@ -237,6 +237,7 @@ impl UnboxedValues { DataValue::F32(f) => ptr::write(p as *mut Ieee32, *f), DataValue::F64(f) => ptr::write(p as *mut Ieee64, *f), DataValue::V128(b) => ptr::write(p as *mut [u8; 16], *b), + _ => unimplemented!(), } } diff --git a/cranelift/filetests/src/test_interpret.rs b/cranelift/filetests/src/test_interpret.rs index 84398c77d039..7374d84c241a 100644 --- a/cranelift/filetests/src/test_interpret.rs +++ b/cranelift/filetests/src/test_interpret.rs @@ -5,8 +5,9 @@ use crate::subtest::{Context, SubTest}; use cranelift_codegen::{self, ir}; -use cranelift_interpreter::environment::Environment; -use cranelift_interpreter::interpreter::{ControlFlow, Interpreter}; +use cranelift_interpreter::environment::FunctionStore; +use cranelift_interpreter::interpreter::{Interpreter, InterpreterState}; +use cranelift_interpreter::step::ControlFlow; use cranelift_reader::{parse_run_command, TestCommand}; use log::trace; use std::borrow::Cow; @@ -39,16 +40,16 @@ impl SubTest for TestInterpret { if let Some(command) = parse_run_command(comment.text, &func.signature)? { trace!("Parsed run command: {}", command); - let mut env = Environment::default(); - env.add(func.name.to_string(), func.clone().into_owned()); - let interpreter = Interpreter::new(env); + let mut env = FunctionStore::default(); + env.add(func.name.to_string(), &func); command .run(|func_name, args| { // Because we have stored function names with a leading %, we need to re-add it. let func_name = &format!("%{}", func_name); - match interpreter.call_by_name(func_name, args) { - Ok(ControlFlow::Return(results)) => Ok(results), + let state = InterpreterState::default().with_function_store(env); + match Interpreter::new(state).call_by_name(func_name, args) { + Ok(ControlFlow::Return(results)) => Ok(results.to_vec()), Ok(_) => { panic!("Unexpected returned control flow--this is likely a bug.") } diff --git a/cranelift/interpreter/Cargo.toml b/cranelift/interpreter/Cargo.toml index fce384404403..64f49f9030f3 100644 --- a/cranelift/interpreter/Cargo.toml +++ b/cranelift/interpreter/Cargo.toml @@ -11,10 +11,11 @@ readme = "README.md" edition = "2018" [dependencies] -cranelift-codegen = { path = "../codegen", version = "0.67.0", default-features = false } +cranelift-codegen = { path = "../codegen", version = "0.67.0", features = ["all-arch"] } cranelift-entity = { path = "../entity", version = "0.67.0" } cranelift-reader = { path = "../reader", version = "0.67.0" } log = { version = "0.4.8", default-features = false } +smallvec = "1.4.2" thiserror = "1.0.15" [dev-dependencies] diff --git a/cranelift/interpreter/src/environment.rs b/cranelift/interpreter/src/environment.rs index 8ace55b486dd..b3b9dade6032 100644 --- a/cranelift/interpreter/src/environment.rs +++ b/cranelift/interpreter/src/environment.rs @@ -3,14 +3,14 @@ use cranelift_codegen::ir::{FuncRef, Function}; use std::collections::HashMap; -#[derive(Default)] -pub struct Environment { - functions: HashMap, +#[derive(Default, Clone)] +pub struct FunctionStore<'a> { + functions: HashMap, function_name_to_func_ref: HashMap, } -impl From for Environment { - fn from(f: Function) -> Self { +impl<'a> From<&'a Function> for FunctionStore<'a> { + fn from(f: &'a Function) -> Self { let func_ref = FuncRef::from_u32(0); let mut function_name_to_func_ref = HashMap::new(); function_name_to_func_ref.insert(f.name.to_string(), func_ref); @@ -23,9 +23,9 @@ impl From for Environment { } } -impl Environment { +impl<'a> FunctionStore<'a> { /// Add a function by name. - pub fn add(&mut self, name: String, function: Function) { + pub fn add(&mut self, name: String, function: &'a Function) { let func_ref = FuncRef::with_number(self.function_name_to_func_ref.len() as u32) .expect("a valid function reference"); self.function_name_to_func_ref.insert(name, func_ref); @@ -38,12 +38,12 @@ impl Environment { } /// Retrieve a function by its function reference. - pub fn get_by_func_ref(&self, func_ref: FuncRef) -> Option<&Function> { - self.functions.get(&func_ref) + pub fn get_by_func_ref(&self, func_ref: FuncRef) -> Option<&'a Function> { + self.functions.get(&func_ref).cloned() } /// Retrieve a function by its name. - pub fn get_by_name(&self, name: &str) -> Option<&Function> { + pub fn get_by_name(&self, name: &str) -> Option<&'a Function> { let func_ref = self.index_of(name)?; self.get_by_func_ref(func_ref) } @@ -57,17 +57,17 @@ mod tests { #[test] fn addition() { - let mut env = Environment::default(); + let mut env = FunctionStore::default(); let a = "a"; let f = Function::new(); - env.add(a.to_string(), f); + env.add(a.to_string(), &f); assert!(env.get_by_name(a).is_some()); } #[test] fn nonexistence() { - let env = Environment::default(); + let env = FunctionStore::default(); assert!(env.get_by_name("a").is_none()); } @@ -75,8 +75,8 @@ mod tests { fn from() { let name = ExternalName::testcase("test"); let signature = Signature::new(CallConv::Fast); - let func = Function::with_name_signature(name, signature); - let env: Environment = func.into(); + let func = &Function::with_name_signature(name, signature); + let env: FunctionStore = func.into(); assert_eq!(env.index_of("%test"), FuncRef::with_number(0)); } } diff --git a/cranelift/interpreter/src/frame.rs b/cranelift/interpreter/src/frame.rs index 1e87ab2da3a4..d6f3065c4728 100644 --- a/cranelift/interpreter/src/frame.rs +++ b/cranelift/interpreter/src/frame.rs @@ -32,16 +32,16 @@ impl<'a> Frame<'a> { /// Retrieve the actual value associated with an SSA reference. #[inline] - pub fn get(&self, name: &ValueRef) -> &DataValue { + pub fn get(&self, name: ValueRef) -> &DataValue { trace!("Get {}", name); self.registers - .get(name) + .get(&name) .unwrap_or_else(|| panic!("unknown value: {}", name)) } /// Retrieve multiple SSA references; see `get`. pub fn get_all(&self, names: &[ValueRef]) -> Vec { - names.iter().map(|r| self.get(r)).cloned().collect() + names.iter().map(|r| self.get(*r)).cloned().collect() } /// Assign `value` to the SSA reference `name`. @@ -108,7 +108,7 @@ mod tests { let a = ValueRef::with_number(1).unwrap(); let fortytwo = DataValue::I32(42); frame.set(a, fortytwo.clone()); - assert_eq!(frame.get(&a), &fortytwo); + assert_eq!(frame.get(a), &fortytwo); } #[test] @@ -118,6 +118,6 @@ mod tests { let frame = Frame::new(&func); let a = ValueRef::with_number(1).unwrap(); - frame.get(&a); + frame.get(a); } } diff --git a/cranelift/interpreter/src/instruction.rs b/cranelift/interpreter/src/instruction.rs new file mode 100644 index 000000000000..c4f552cec350 --- /dev/null +++ b/cranelift/interpreter/src/instruction.rs @@ -0,0 +1,42 @@ +//! The [InstructionContext] trait describes a Cranelift instruction; a default implementation is +//! provided with [DfgInstructionContext] +use cranelift_codegen::ir::{DataFlowGraph, Inst, InstructionData, Type, Value}; + +/// Exposes the necessary information for understanding a single Cranelift instruction. It would be +/// nice if [InstructionData] contained everything necessary for interpreting the instruction, but +/// Cranelift's current design requires looking at other structures. A default implementation using +/// a reference to a [DataFlowGraph] is provided in [DfgInstructionContext]. +pub trait InstructionContext { + fn data(&self) -> InstructionData; + fn args(&self) -> &[Value]; + fn type_of(&self, v: Value) -> Option; + fn controlling_type(&self) -> Option; +} + +/// Since [InstructionContext] is likely used within a Cranelift context in which a [DataFlowGraph] +/// is available, a default implementation is provided--[DfgInstructionContext]. +pub struct DfgInstructionContext<'a>(Inst, &'a DataFlowGraph); + +impl<'a> DfgInstructionContext<'a> { + pub fn new(inst: Inst, dfg: &'a DataFlowGraph) -> Self { + Self(inst, dfg) + } +} + +impl InstructionContext for DfgInstructionContext<'_> { + fn data(&self) -> InstructionData { + self.1[self.0].clone() + } + + fn args(&self) -> &[Value] { + self.1.inst_args(self.0) + } + + fn type_of(&self, v: Value) -> Option { + Some(self.1.value_type(v)) + } + + fn controlling_type(&self) -> Option { + Some(self.1.ctrl_typevar(self.0)) + } +} diff --git a/cranelift/interpreter/src/interpreter.rs b/cranelift/interpreter/src/interpreter.rs index b4a9c6f2d1af..f6032b7cdb78 100644 --- a/cranelift/interpreter/src/interpreter.rs +++ b/cranelift/interpreter/src/interpreter.rs @@ -1,110 +1,65 @@ //! Cranelift IR interpreter. //! -//! This module contains the logic for interpreting Cranelift instructions. +//! This module partially contains the logic for interpreting Cranelift IR. -use crate::environment::Environment; +use crate::environment::FunctionStore; use crate::frame::Frame; -use crate::interpreter::Trap::InvalidType; -use cranelift_codegen::data_value::{DataValue, DataValueCastFailure}; -use cranelift_codegen::ir::condcodes::IntCC; -use cranelift_codegen::ir::{ - Block, FuncRef, Function, Inst, InstructionData, InstructionData::*, Opcode, Opcode::*, Type, - Value as ValueRef, ValueList, -}; +use crate::instruction::DfgInstructionContext; +use crate::state::{MemoryError, State}; +use crate::step::{step, ControlFlow, StepError}; +use crate::value::ValueError; +use cranelift_codegen::data_value::DataValue; +use cranelift_codegen::ir::condcodes::{FloatCC, IntCC}; +use cranelift_codegen::ir::{Block, FuncRef, Function, Type, Value as ValueRef}; use log::trace; -use std::ops::{Add, Mul, Sub}; +use std::collections::HashSet; +use std::fmt::Debug; use thiserror::Error; -/// The valid control flow states. -pub enum ControlFlow { - Continue, - ContinueAt(Block, Vec), - Return(Vec), +/// The Cranelift interpreter; this contains some high-level functions to control the interpreter's +/// flow. The interpreter state is defined separately (see [InterpreterState]) as the execution +/// semantics for each Cranelift instruction (see [step]). +pub struct Interpreter<'a> { + state: InterpreterState<'a>, } -impl ControlFlow { - /// For convenience, we can unwrap the [ControlFlow] state assuming that it is a - /// [ControlFlow::Return], panicking otherwise. - pub fn unwrap_return(self) -> Vec { - if let ControlFlow::Return(values) = self { - values - } else { - panic!("expected the control flow to be in the return state") - } - } -} - -/// The ways interpretation can fail. -#[derive(Error, Debug)] -pub enum Trap { - #[error("unknown trap")] - Unknown, - #[error("invalid type for {1}: expected {0}")] - InvalidType(String, ValueRef), - #[error("invalid cast")] - InvalidCast(#[from] DataValueCastFailure), - #[error("the instruction is not implemented (perhaps for the given types): {0}")] - Unsupported(Inst), - #[error("reached an unreachable statement")] - Unreachable, - #[error("invalid control flow: {0}")] - InvalidControlFlow(String), - #[error("invalid function reference: {0}")] - InvalidFunctionReference(FuncRef), - #[error("invalid function name: {0}")] - InvalidFunctionName(String), -} - -/// The Cranelift interpreter; it contains immutable elements such as the function environment and -/// implements the Cranelift IR semantics. -#[derive(Default)] -pub struct Interpreter { - pub env: Environment, -} - -/// Helper for more concise matching. -macro_rules! binary_op { - ( $op:path[$arg1:ident, $arg2:ident]; [ $( $data_value_ty:ident ),* ]; $inst:ident ) => { - match ($arg1, $arg2) { - $( (DataValue::$data_value_ty(a), DataValue::$data_value_ty(b)) => { Ok(DataValue::$data_value_ty($op(a, b))) } )* - _ => Err(Trap::Unsupported($inst)), - } - }; -} - -impl Interpreter { - /// Construct a new [Interpreter] using the given [Environment]. - pub fn new(env: Environment) -> Self { - Self { env } +impl<'a> Interpreter<'a> { + pub fn new(state: InterpreterState<'a>) -> Self { + Self { state } } /// Call a function by name; this is a helpful proxy for [Interpreter::call_by_index]. pub fn call_by_name( - &self, + &mut self, func_name: &str, arguments: &[DataValue], - ) -> Result { + ) -> Result, InterpreterError> { let func_ref = self - .env + .state + .functions .index_of(func_name) - .ok_or_else(|| Trap::InvalidFunctionName(func_name.to_string()))?; + .ok_or_else(|| InterpreterError::UnknownFunctionName(func_name.to_string()))?; self.call_by_index(func_ref, arguments) } - /// Call a function by its index in the [Environment]; this is a proxy for [Interpreter::call]. + /// Call a function by its index in the [FunctionStore]; this is a proxy for [Interpreter::call]. pub fn call_by_index( - &self, + &mut self, func_ref: FuncRef, arguments: &[DataValue], - ) -> Result { - match self.env.get_by_func_ref(func_ref) { - None => Err(Trap::InvalidFunctionReference(func_ref)), + ) -> Result, InterpreterError> { + match self.state.get_function(func_ref) { + None => Err(InterpreterError::UnknownFunctionReference(func_ref)), Some(func) => self.call(func, arguments), } } /// Interpret a call to a [Function] given its [DataValue] arguments. - fn call(&self, function: &Function, arguments: &[DataValue]) -> Result { + fn call( + &mut self, + function: &'a Function, + arguments: &[DataValue], + ) -> Result, InterpreterError> { trace!("Call: {}({:?})", function.name, arguments); let first_block = function .layout @@ -112,241 +67,184 @@ impl Interpreter { .next() .expect("to have a first block"); let parameters = function.dfg.block_params(first_block); - let mut frame = Frame::new(function); - frame.set_all(parameters, arguments.to_vec()); - self.block(&mut frame, first_block) + self.state.push_frame(function); + self.state + .current_frame_mut() + .set_all(parameters, arguments.to_vec()); + self.block(first_block) } /// Interpret a [Block] in a [Function]. This drives the interpretation over sequences of /// instructions, which may continue in other blocks, until the function returns. - fn block(&self, frame: &mut Frame, block: Block) -> Result { + fn block(&mut self, block: Block) -> Result, InterpreterError> { trace!("Block: {}", block); - let layout = &frame.function.layout; + let function = self.state.current_frame_mut().function; + let layout = &function.layout; let mut maybe_inst = layout.first_inst(block); while let Some(inst) = maybe_inst { - match self.inst(frame, inst)? { + let inst_context = DfgInstructionContext::new(inst, &function.dfg); + match step(&mut self.state, inst_context)? { + ControlFlow::Assign(values) => { + self.state + .current_frame_mut() + .set_all(function.dfg.inst_results(inst), values.to_vec()); + maybe_inst = layout.next_inst(inst) + } ControlFlow::Continue => maybe_inst = layout.next_inst(inst), - ControlFlow::ContinueAt(block, old_names) => { + ControlFlow::ContinueAt(block, block_arguments) => { trace!("Block: {}", block); - let new_names = frame.function.dfg.block_params(block); - frame.rename(&old_names, new_names); + self.state + .current_frame_mut() + .set_all(function.dfg.block_params(block), block_arguments.to_vec()); maybe_inst = layout.first_inst(block) } - ControlFlow::Return(rs) => return Ok(ControlFlow::Return(rs)), + ControlFlow::Call(function, arguments) => { + let returned_arguments = self.call(function, &arguments)?.unwrap_return(); + self.state + .current_frame_mut() + .set_all(function.dfg.inst_results(inst), returned_arguments); + maybe_inst = layout.next_inst(inst) + } + ControlFlow::Return(returned_values) => { + self.state.pop_frame(); + return Ok(ControlFlow::Return(returned_values)); + } + ControlFlow::Trap(trap) => return Ok(ControlFlow::Trap(trap)), } } - Err(Trap::Unreachable) + Err(InterpreterError::Unreachable) } +} - /// Interpret a single [instruction](Inst). This contains a `match`-based dispatch to the - /// implementations. - fn inst(&self, frame: &mut Frame, inst: Inst) -> Result { - use ControlFlow::{Continue, ContinueAt}; - trace!("Inst: {}", &frame.function.dfg.display_inst(inst, None)); - - let data = &frame.function.dfg[inst]; - match data { - Binary { opcode, args } => { - let arg1 = frame.get(&args[0]); - let arg2 = frame.get(&args[1]); - let result = match opcode { - Iadd => binary_op!(Add::add[arg1, arg2]; [I8, I16, I32, I64]; inst), - Isub => binary_op!(Sub::sub[arg1, arg2]; [I8, I16, I32, I64]; inst), - Imul => binary_op!(Mul::mul[arg1, arg2]; [I8, I16, I32, I64]; inst), - // TODO re-enable by importing something like rustc_apfloat for correctness. - // Fadd => binary_op!(Add::add[arg1, arg2]; [F32, F64]; inst), - // Fsub => binary_op!(Sub::sub[arg1, arg2]; [F32, F64]; inst), - // Fmul => binary_op!(Mul::mul[arg1, arg2]; [F32, F64]; inst), - // Fdiv => binary_op!(Div::div[arg1, arg2]; [F32, F64]; inst), - _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), - }?; - frame.set(first_result(frame.function, inst), result); - Ok(Continue) - } - - BinaryImm64 { opcode, arg, imm } => { - let imm = DataValue::from_integer(*imm, type_of(*arg, frame.function))?; - let arg = frame.get(&arg); - let result = match opcode { - IaddImm => binary_op!(Add::add[arg, imm]; [I8, I16, I32, I64]; inst), - IrsubImm => binary_op!(Sub::sub[imm, arg]; [I8, I16, I32, I64]; inst), - ImulImm => binary_op!(Mul::mul[arg, imm]; [I8, I16, I32, I64]; inst), - _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), - }?; - frame.set(first_result(frame.function, inst), result); - Ok(Continue) - } +/// The ways interpretation can fail. +#[derive(Error, Debug)] +pub enum InterpreterError { + #[error("failed to interpret instruction")] + StepError(#[from] StepError), + #[error("reached an unreachable statement")] + Unreachable, + #[error("unknown function reference (has it been added to the function store?): {0}")] + UnknownFunctionReference(FuncRef), + #[error("unknown function with name (has it been added to the function store?): {0}")] + UnknownFunctionName(String), + #[error("value error")] + ValueError(#[from] ValueError), +} - Branch { - opcode, - args, - destination, - } => match opcode { - Brnz => { - let mut args = value_refs(frame.function, args); - let first = args.remove(0); - match frame.get(&first) { - DataValue::B(false) - | DataValue::I8(0) - | DataValue::I16(0) - | DataValue::I32(0) - | DataValue::I64(0) => Ok(Continue), - DataValue::B(true) - | DataValue::I8(_) - | DataValue::I16(_) - | DataValue::I32(_) - | DataValue::I64(_) => Ok(ContinueAt(*destination, args)), - _ => Err(Trap::InvalidType("boolean or integer".to_string(), args[0])), - } - } - _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), - }, - InstructionData::Call { args, func_ref, .. } => { - // Find the function to call. - let func_name = function_name_of_func_ref(*func_ref, frame.function); - - // Call function. - let args = frame.get_all(args.as_slice(&frame.function.dfg.value_lists)); - let result = self.call_by_name(&func_name, &args)?; - - // Save results. - if let ControlFlow::Return(returned_values) = result { - let ssa_values = frame.function.dfg.inst_results(inst); - assert_eq!( - ssa_values.len(), - returned_values.len(), - "expected result length ({}) to match SSA values length ({}): {}", - returned_values.len(), - ssa_values.len(), - frame.function.dfg.display_inst(inst, None) - ); - frame.set_all(ssa_values, returned_values); - Ok(Continue) - } else { - Err(Trap::InvalidControlFlow(format!( - "did not return from: {}", - frame.function.dfg.display_inst(inst, None) - ))) - } - } - InstructionData::Jump { - opcode, - destination, - args, - } => match opcode { - Opcode::Fallthrough => { - Ok(ContinueAt(*destination, value_refs(frame.function, args))) - } - Opcode::Jump => Ok(ContinueAt(*destination, value_refs(frame.function, args))), - _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), - }, - IntCompareImm { - opcode, - arg, - cond, - imm, - } => match opcode { - IcmpImm => { - let arg_value = match *frame.get(arg) { - DataValue::I8(i) => Ok(i as i64), - DataValue::I16(i) => Ok(i as i64), - DataValue::I32(i) => Ok(i as i64), - DataValue::I64(i) => Ok(i), - _ => Err(InvalidType("integer".to_string(), *arg)), - }?; - let imm_value = (*imm).into(); - let result = match cond { - IntCC::UnsignedLessThanOrEqual => arg_value <= imm_value, - IntCC::Equal => arg_value == imm_value, - _ => unimplemented!( - "interpreter does not support condition code yet: {}", - cond - ), - }; - let res = first_result(frame.function, inst); - frame.set(res, DataValue::B(result)); - Ok(Continue) - } - _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), - }, - MultiAry { opcode, args } => match opcode { - Return => { - let rs: Vec = args - .as_slice(&frame.function.dfg.value_lists) - .iter() - .map(|r| frame.get(r).clone()) - .collect(); - Ok(ControlFlow::Return(rs)) - } - _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), - }, - NullAry { opcode } => match opcode { - Nop => Ok(Continue), - _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), - }, - UnaryImm { opcode, imm } => match opcode { - Iconst => { - let res = first_result(frame.function, inst); - let imm_value = DataValue::from_integer(*imm, type_of(res, frame.function))?; - frame.set(res, imm_value); - Ok(Continue) - } - _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), - }, - UnaryBool { opcode, imm } => match opcode { - Bconst => { - let res = first_result(frame.function, inst); - frame.set(res, DataValue::B(*imm)); - Ok(Continue) - } - _ => unimplemented!("interpreter does not support opcode yet: {}", opcode), - }, +/// Maintains the [Interpreter]'s state, implementing the [State] trait. +pub struct InterpreterState<'a> { + pub functions: FunctionStore<'a>, + pub frame_stack: Vec>, + pub heap: Vec, + pub iflags: HashSet, + pub fflags: HashSet, +} - _ => unimplemented!("interpreter does not support instruction yet: {:?}", data), +impl Default for InterpreterState<'_> { + fn default() -> Self { + Self { + functions: FunctionStore::default(), + frame_stack: vec![], + heap: vec![0; 1024], + iflags: HashSet::new(), + fflags: HashSet::new(), } } } -/// Return the first result of an instruction. -/// -/// This helper cushions the interpreter from changes to the [Function] API. -#[inline] -fn first_result(function: &Function, inst: Inst) -> ValueRef { - function.dfg.first_result(inst) -} +impl<'a> InterpreterState<'a> { + pub fn with_function_store(self, functions: FunctionStore<'a>) -> Self { + Self { functions, ..self } + } -/// Return a list of IR values as a vector. -/// -/// This helper cushions the interpreter from changes to the [Function] API. -#[inline] -fn value_refs(function: &Function, args: &ValueList) -> Vec { - args.as_slice(&function.dfg.value_lists).to_vec() -} + fn current_frame_mut(&mut self) -> &mut Frame<'a> { + let num_frames = self.frame_stack.len(); + match num_frames { + 0 => panic!("unable to retrieve the current frame because no frames were pushed"), + _ => &mut self.frame_stack[num_frames - 1], + } + } -/// Return the (external) function name of `func_ref` in a local `function`. Note that this may -/// be truncated. -/// -/// This helper cushions the interpreter from changes to the [Function] API. -#[inline] -fn function_name_of_func_ref(func_ref: FuncRef, function: &Function) -> String { - function - .dfg - .ext_funcs - .get(func_ref) - .expect("function to exist") - .name - .to_string() + fn current_frame(&self) -> &Frame<'a> { + let num_frames = self.frame_stack.len(); + match num_frames { + 0 => panic!("unable to retrieve the current frame because no frames were pushed"), + _ => &self.frame_stack[num_frames - 1], + } + } } -/// Helper for calculating the type of an IR value. TODO move to Frame? -#[inline] -fn type_of(value: ValueRef, function: &Function) -> Type { - function.dfg.value_type(value) +impl<'a> State<'a, DataValue> for InterpreterState<'a> { + fn get_function(&self, func_ref: FuncRef) -> Option<&'a Function> { + self.functions.get_by_func_ref(func_ref) + } + fn push_frame(&mut self, function: &'a Function) { + self.frame_stack.push(Frame::new(function)); + } + fn pop_frame(&mut self) { + self.frame_stack.pop(); + } + + fn get_value(&self, name: ValueRef) -> Option { + Some(self.current_frame().get(name).clone()) // TODO avoid clone? + } + + fn set_value(&mut self, name: ValueRef, value: DataValue) -> Option { + self.current_frame_mut().set(name, value) + } + + fn has_iflag(&self, flag: IntCC) -> bool { + self.iflags.contains(&flag) + } + + fn has_fflag(&self, flag: FloatCC) -> bool { + self.fflags.contains(&flag) + } + + fn set_iflag(&mut self, flag: IntCC) { + self.iflags.insert(flag); + } + + fn set_fflag(&mut self, flag: FloatCC) { + self.fflags.insert(flag); + } + + fn clear_flags(&mut self) { + self.iflags.clear(); + self.fflags.clear() + } + + fn load_heap(&self, offset: usize, ty: Type) -> Result { + if offset + 16 < self.heap.len() { + let pointer = self.heap[offset..offset + 16].as_ptr() as *const _ as *const u128; + Ok(unsafe { DataValue::read_value_from(pointer, ty) }) + } else { + Err(MemoryError::InsufficientMemory(offset, self.heap.len())) + } + } + + fn store_heap(&mut self, offset: usize, v: DataValue) -> Result<(), MemoryError> { + if offset + 16 < self.heap.len() { + let pointer = self.heap[offset..offset + 16].as_mut_ptr() as *mut _ as *mut u128; + Ok(unsafe { v.write_value_to(pointer) }) + } else { + Err(MemoryError::InsufficientMemory(offset, self.heap.len())) + } + } + + fn load_stack(&self, _offset: usize, _ty: Type) -> Result { + unimplemented!() + } + + fn store_stack(&mut self, _offset: usize, _v: DataValue) -> Result<(), MemoryError> { + unimplemented!() + } } #[cfg(test)] mod tests { use super::*; + use cranelift_codegen::ir::immediates::Ieee32; use cranelift_reader::parse_functions; // Most interpreter tests should use the more ergonomic `test interpret` filetest but this @@ -364,14 +262,39 @@ mod tests { }"; let func = parse_functions(code).unwrap().into_iter().next().unwrap(); - let mut env = Environment::default(); - env.add(func.name.to_string(), func); - let interpreter = Interpreter::new(env); - let result = interpreter + let mut env = FunctionStore::default(); + env.add(func.name.to_string(), &func); + let state = InterpreterState::default().with_function_store(env); + let result = Interpreter::new(state) .call_by_name("%test", &[]) .unwrap() .unwrap_return(); assert_eq!(result, vec![DataValue::B(true)]) } + + #[test] + fn state_heap_roundtrip() -> Result<(), MemoryError> { + let mut state = InterpreterState::default(); + let mut roundtrip = |dv: DataValue| { + state.store_heap(0, dv.clone())?; + assert_eq!(dv, state.load_heap(0, dv.ty())?); + Ok(()) + }; + + roundtrip(DataValue::B(true))?; + roundtrip(DataValue::I64(42))?; + roundtrip(DataValue::F32(Ieee32::from(0.42))) + } + + #[test] + fn state_flags() { + let mut state = InterpreterState::default(); + let flag = IntCC::Overflow; + assert!(!state.has_iflag(flag)); + state.set_iflag(flag); + assert!(state.has_iflag(flag)); + state.clear_flags(); + assert!(!state.has_iflag(flag)); + } } diff --git a/cranelift/interpreter/src/lib.rs b/cranelift/interpreter/src/lib.rs index cf100607c6f0..9ebbcdc0b7c1 100644 --- a/cranelift/interpreter/src/lib.rs +++ b/cranelift/interpreter/src/lib.rs @@ -4,4 +4,8 @@ pub mod environment; pub mod frame; +pub mod instruction; pub mod interpreter; +pub mod state; +pub mod step; +pub mod value; diff --git a/cranelift/interpreter/src/state.rs b/cranelift/interpreter/src/state.rs new file mode 100644 index 000000000000..d18e4536c80d --- /dev/null +++ b/cranelift/interpreter/src/state.rs @@ -0,0 +1,140 @@ +//! Cranelift instructions modify the state of the machine; the [State] trait describes these +//! ways this can happen. +use cranelift_codegen::ir::condcodes::{FloatCC, IntCC}; +use cranelift_codegen::ir::{FuncRef, Function, Type, Value}; +use cranelift_entity::PrimaryMap; +use smallvec::SmallVec; +use thiserror::Error; + +/// This trait manages the state necessary to interpret a single Cranelift instruction--it describes +/// all of the ways a Cranelift interpreter can interact with its virtual state. This makes it +/// possible to use the [Interpreter](crate::interpreter::Interpreter) in a range of situations: +/// - when interpretation requires understanding all of the ways state can change (e.g. loading and +/// storing from the heap) we will use a full-fledged state, like +/// [InterpreterState](crate::interpreter::InterpreterState). +/// - when interpretation can ignore some state changes (e.g. abstract interpretation of arithmetic +/// instructions--no heap knowledge required), we can partially implement this trait. See +/// [ImmutableRegisterState] for an example of this: it only exposes the values referenced by the +/// SSA references in the current frame and not much else. +pub trait State<'a, V> { + /// Retrieve a reference to a [Function]. + fn get_function(&self, func_ref: FuncRef) -> Option<&'a Function>; + /// Record that an interpreter has called into a new [Function]. + fn push_frame(&mut self, function: &'a Function); + /// Record that an interpreter has returned from a called [Function]. + fn pop_frame(&mut self); + + /// Retrieve a value `V` by its [value reference](cranelift_codegen::ir::Value) from the + /// virtual register file. + fn get_value(&self, name: Value) -> Option; + /// Assign a value `V` to its [value reference](cranelift_codegen::ir::Value) in the + /// virtual register file. + fn set_value(&mut self, name: Value, value: V) -> Option; + /// Collect a list of values `V` by their [value references](cranelift_codegen::ir::Value); + /// this is a convenience method for `get_value`. If no value is found for a value reference, + /// return an `Err` containing the offending reference. + fn collect_values(&self, names: &[Value]) -> Result, Value> { + let mut values = SmallVec::with_capacity(names.len()); + for &n in names { + match self.get_value(n) { + None => return Err(n), + Some(v) => values.push(v), + } + } + Ok(values) + } + + /// Check if an [IntCC] flag has been set. + fn has_iflag(&self, flag: IntCC) -> bool; + /// Set an [IntCC] flag. + fn set_iflag(&mut self, flag: IntCC); + /// Check if a [FloatCC] flag has been set. + fn has_fflag(&self, flag: FloatCC) -> bool; + /// Set a [FloatCC] flag. + fn set_fflag(&mut self, flag: FloatCC); + /// Clear all [IntCC] and [FloatCC] flags. + fn clear_flags(&mut self); + + /// Retrieve a value `V` from the heap at the given `offset`; the number of bytes loaded + /// corresponds to the specified [Type]. + fn load_heap(&self, offset: usize, ty: Type) -> Result; + /// Store a value `V` into the heap at the given `offset`. The [Type] of `V` will determine + /// the number of bytes stored. + fn store_heap(&mut self, offset: usize, v: V) -> Result<(), MemoryError>; + + /// Retrieve a value `V` from the stack at the given `offset`; the number of bytes loaded + /// corresponds to the specified [Type]. + fn load_stack(&self, offset: usize, ty: Type) -> Result; + /// Store a value `V` on the stack at the given `offset`. The [Type] of `V` will determine + /// the number of bytes stored. + fn store_stack(&mut self, offset: usize, v: V) -> Result<(), MemoryError>; +} + +#[derive(Error, Debug)] +pub enum MemoryError { + #[error("insufficient memory: asked for address {0} in memory of size {1}")] + InsufficientMemory(usize, usize), +} + +/// This dummy state allows interpretation over an immutable mapping of values in a single frame. +pub struct ImmutableRegisterState<'a, V>(&'a PrimaryMap); +impl<'a, V> ImmutableRegisterState<'a, V> { + pub fn new(values: &'a PrimaryMap) -> Self { + Self(values) + } +} + +impl<'a, V> State<'a, V> for ImmutableRegisterState<'a, V> +where + V: Clone, +{ + fn get_function(&self, _func_ref: FuncRef) -> Option<&'a Function> { + None + } + + fn push_frame(&mut self, _function: &'a Function) { + unimplemented!() + } + + fn pop_frame(&mut self) { + unimplemented!() + } + + fn get_value(&self, name: Value) -> Option { + self.0.get(name).cloned() + } + + fn set_value(&mut self, _name: Value, _value: V) -> Option { + None + } + + fn has_iflag(&self, _flag: IntCC) -> bool { + false + } + + fn has_fflag(&self, _flag: FloatCC) -> bool { + false + } + + fn set_iflag(&mut self, _flag: IntCC) {} + + fn set_fflag(&mut self, _flag: FloatCC) {} + + fn clear_flags(&mut self) {} + + fn load_heap(&self, _offset: usize, _ty: Type) -> Result { + unimplemented!() + } + + fn store_heap(&mut self, _offset: usize, _v: V) -> Result<(), MemoryError> { + unimplemented!() + } + + fn load_stack(&self, _offset: usize, _ty: Type) -> Result { + unimplemented!() + } + + fn store_stack(&mut self, _offset: usize, _v: V) -> Result<(), MemoryError> { + unimplemented!() + } +} diff --git a/cranelift/interpreter/src/step.rs b/cranelift/interpreter/src/step.rs new file mode 100644 index 000000000000..79357e3408bb --- /dev/null +++ b/cranelift/interpreter/src/step.rs @@ -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, 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 { + 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, 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| -> Result, 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::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, + left: V, + right: V| + -> ValueResult> { 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, left: V, right: V| -> ValueResult> { + 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 { + 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, 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 { + if condition { + ControlFlow::Trap(trap) + } else { + ControlFlow::Continue + } + }; + + // Helper for summing a sequence of values. + fn sum(head: V, tail: SmallVec<[V; 1]>) -> ValueResult { + 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 { + 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(code: IntCC, left: &V, right: &V) -> ValueResult +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(code: FloatCC, left: &V, right: &V) -> ValueResult +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)? + } + }) +} diff --git a/cranelift/interpreter/src/value.rs b/cranelift/interpreter/src/value.rs new file mode 100644 index 000000000000..18dcf818ce98 --- /dev/null +++ b/cranelift/interpreter/src/value.rs @@ -0,0 +1,329 @@ +//! The [Value] trait describes what operations can be performed on interpreter values. The +//! interpreter usually executes using [DataValue]s so an implementation is provided here. The fact +//! that [Value] is a trait, however, allows interpretation of Cranelift IR on other kinds of +//! values. +use core::convert::TryFrom; +use core::fmt::{self, Display, Formatter}; +use cranelift_codegen::data_value::DataValue; +use cranelift_codegen::ir::immediates::{Ieee32, Ieee64}; +use cranelift_codegen::ir::{types, Type}; +use thiserror::Error; + +pub type ValueResult = Result; + +pub trait Value: Clone + From { + // Identity. + fn ty(&self) -> Type; + fn int(n: i64, ty: Type) -> ValueResult; + fn into_int(self) -> ValueResult; + fn float(n: u64, ty: Type) -> ValueResult; + fn into_float(self) -> ValueResult; + fn is_nan(&self) -> ValueResult; + fn bool(b: bool, ty: Type) -> ValueResult; + fn into_bool(self) -> ValueResult; + fn vector(v: [u8; 16], ty: Type) -> ValueResult; + fn convert(self, kind: ValueConversionKind) -> ValueResult; + + // Comparison. + fn eq(&self, other: &Self) -> ValueResult; + fn gt(&self, other: &Self) -> ValueResult; + fn ge(&self, other: &Self) -> ValueResult { + Ok(self.eq(other)? || self.gt(other)?) + } + fn lt(&self, other: &Self) -> ValueResult { + other.gt(self) + } + fn le(&self, other: &Self) -> ValueResult { + Ok(other.eq(self)? || other.gt(self)?) + } + fn uno(&self, other: &Self) -> ValueResult; + + // Arithmetic. + fn add(self, other: Self) -> ValueResult; + fn sub(self, other: Self) -> ValueResult; + fn mul(self, other: Self) -> ValueResult; + fn div(self, other: Self) -> ValueResult; + fn rem(self, other: Self) -> ValueResult; + + // Bitwise. + fn shl(self, other: Self) -> ValueResult; + fn ushr(self, other: Self) -> ValueResult; + fn ishr(self, other: Self) -> ValueResult; + fn rotl(self, other: Self) -> ValueResult; + fn rotr(self, other: Self) -> ValueResult; + fn and(self, other: Self) -> ValueResult; + fn or(self, other: Self) -> ValueResult; + fn xor(self, other: Self) -> ValueResult; + fn not(self) -> ValueResult; +} + +#[derive(Error, Debug)] +pub enum ValueError { + #[error("unable to convert type {1} into class {0}")] + InvalidType(ValueTypeClass, Type), + #[error("unable to convert value into type {0}")] + InvalidValue(Type), + #[error("unable to convert to primitive integer")] + InvalidInteger(#[from] std::num::TryFromIntError), +} + +#[derive(Debug)] +pub enum ValueTypeClass { + Integer, + Boolean, + Float, +} + +impl Display for ValueTypeClass { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + ValueTypeClass::Integer => write!(f, "integer"), + ValueTypeClass::Boolean => write!(f, "boolean"), + ValueTypeClass::Float => write!(f, "float"), + } + } +} + +#[derive(Debug)] +pub enum ValueConversionKind { + /// Throw a [ValueError] if an exact conversion to [Type] is not possible; e.g. in `i32` to + /// `i16`, convert `0x00001234` to `0x1234`. + Exact(Type), + /// Truncate the value to fit into the specified [Type]; e.g. in `i16` to `i8`, `0x1234` becomes + /// `0x34`. + Truncate(Type), + /// Convert to a larger integer type, extending the sign bit; e.g. in `i8` to `i16`, `0xff` + /// becomes `0xffff`. + SignExtend(Type), + /// Convert to a larger integer type, extending with zeroes; e.g. in `i8` to `i16`, `0xff` + /// becomes `0x00ff`. + ZeroExtend(Type), + /// Convert a signed integer to its unsigned value of the same size; e.g. in `i8` to `u8`, + /// `0xff` (`-1`) becomes `0xff` (`255`). + ToUnsigned, + /// Convert an unsigned integer to its signed value of the same size; e.g. in `u8` to `i8`, + /// `0xff` (`255`) becomes `0xff` (`-1`). + ToSigned, + /// Convert a floating point number by rounding to the nearest possible value with ties to even. + /// See `fdemote`, e.g. + RoundNearestEven(Type), +} + +/// Helper for creating match expressions over [DataValue]. +macro_rules! unary_match { + ( $op:tt($arg1:expr); [ $( $data_value_ty:ident ),* ] ) => { + match $arg1 { + $( DataValue::$data_value_ty(a) => { Ok(DataValue::$data_value_ty($op a)) } )* + _ => unimplemented!() + } + }; +} +macro_rules! binary_match { + ( $op:tt($arg1:expr, $arg2:expr); [ $( $data_value_ty:ident ),* ] ) => { + match ($arg1, $arg2) { + $( (DataValue::$data_value_ty(a), DataValue::$data_value_ty(b)) => { Ok(DataValue::$data_value_ty(a $op b)) } )* + _ => unimplemented!() + } + }; + ( $op:tt($arg1:expr, $arg2:expr); unsigned integers ) => { + match ($arg1, $arg2) { + (DataValue::I8(a), DataValue::I8(b)) => { Ok(DataValue::I8((u8::try_from(*a)? $op u8::try_from(*b)?) as i8)) } + (DataValue::I16(a), DataValue::I16(b)) => { Ok(DataValue::I16((u16::try_from(*a)? $op u16::try_from(*b)?) as i16)) } + (DataValue::I32(a), DataValue::I32(b)) => { Ok(DataValue::I32((u32::try_from(*a)? $op u32::try_from(*b)?) as i32)) } + (DataValue::I64(a), DataValue::I64(b)) => { Ok(DataValue::I64((u64::try_from(*a)? $op u64::try_from(*b)?) as i64)) } + _ => { Err(ValueError::InvalidType(ValueTypeClass::Integer, if !($arg1).ty().is_int() { ($arg1).ty() } else { ($arg2).ty() })) } + } + }; +} +macro_rules! comparison_match { + ( $op:path[$arg1:expr, $arg2:expr]; [ $( $data_value_ty:ident ),* ] ) => { + match ($arg1, $arg2) { + $( (DataValue::$data_value_ty(a), DataValue::$data_value_ty(b)) => { Ok($op(a, b)) } )* + _ => unimplemented!("comparison: {:?}, {:?}", $arg1, $arg2) + } + }; +} + +impl Value for DataValue { + fn ty(&self) -> Type { + self.ty() + } + + fn int(n: i64, ty: Type) -> ValueResult { + if ty.is_int() && !ty.is_vector() { + DataValue::from_integer(n, ty).map_err(|_| ValueError::InvalidValue(ty)) + } else { + Err(ValueError::InvalidType(ValueTypeClass::Integer, ty)) + } + } + + fn into_int(self) -> ValueResult { + match self { + DataValue::I8(n) => Ok(n as i64), + DataValue::I16(n) => Ok(n as i64), + DataValue::I32(n) => Ok(n as i64), + DataValue::I64(n) => Ok(n), + _ => Err(ValueError::InvalidType(ValueTypeClass::Integer, self.ty())), + } + } + + fn float(bits: u64, ty: Type) -> ValueResult { + match ty { + types::F32 => Ok(DataValue::F32(Ieee32::with_bits(u32::try_from(bits)?))), + types::F64 => Ok(DataValue::F64(Ieee64::with_bits(bits))), + _ => Err(ValueError::InvalidType(ValueTypeClass::Float, ty)), + } + } + + fn into_float(self) -> ValueResult { + unimplemented!() + } + + fn is_nan(&self) -> ValueResult { + match self { + DataValue::F32(f) => Ok(f.is_nan()), + DataValue::F64(f) => Ok(f.is_nan()), + _ => Err(ValueError::InvalidType(ValueTypeClass::Float, self.ty())), + } + } + + fn bool(b: bool, ty: Type) -> ValueResult { + assert!(ty.is_bool()); + Ok(DataValue::B(b)) + } + + fn into_bool(self) -> ValueResult { + match self { + DataValue::B(b) => Ok(b), + _ => Err(ValueError::InvalidType(ValueTypeClass::Boolean, self.ty())), + } + } + + fn vector(_v: [u8; 16], _ty: Type) -> ValueResult { + unimplemented!() + } + + fn convert(self, kind: ValueConversionKind) -> ValueResult { + Ok(match kind { + ValueConversionKind::Exact(ty) => match (self, ty) { + // TODO a lot to do here: from bmask to ireduce to raw_bitcast... + (DataValue::I64(n), types::I32) => DataValue::I32(i32::try_from(n)?), + (DataValue::B(b), t) if t.is_bool() => DataValue::B(b), + (dv, _) => unimplemented!("conversion: {} -> {:?}", dv.ty(), kind), + }, + ValueConversionKind::Truncate(ty) => match (self.ty(), ty) { + (types::I64, types::I32) => unimplemented!(), + (types::I64, types::I16) => unimplemented!(), + (types::I64, types::I8) => unimplemented!(), + (types::I32, types::I16) => unimplemented!(), + (types::I32, types::I8) => unimplemented!(), + (types::I16, types::I8) => unimplemented!(), + _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), + }, + ValueConversionKind::SignExtend(ty) => match (self.ty(), ty) { + (types::I8, types::I16) => unimplemented!(), + (types::I8, types::I32) => unimplemented!(), + (types::I8, types::I64) => unimplemented!(), + (types::I16, types::I32) => unimplemented!(), + (types::I16, types::I64) => unimplemented!(), + (types::I32, types::I64) => unimplemented!(), + _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), + }, + ValueConversionKind::ZeroExtend(ty) => match (self.ty(), ty) { + (types::I8, types::I16) => unimplemented!(), + (types::I8, types::I32) => unimplemented!(), + (types::I8, types::I64) => unimplemented!(), + (types::I16, types::I32) => unimplemented!(), + (types::I16, types::I64) => unimplemented!(), + (types::I32, types::I64) => unimplemented!(), + _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), + }, + ValueConversionKind::ToUnsigned => match self { + DataValue::I8(n) => DataValue::U8(n as u8), + DataValue::I16(n) => DataValue::U16(n as u16), + DataValue::I32(n) => DataValue::U32(n as u32), + DataValue::I64(n) => DataValue::U64(n as u64), + _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), + }, + ValueConversionKind::ToSigned => match self { + DataValue::U8(n) => DataValue::I8(n as i8), + DataValue::U16(n) => DataValue::I16(n as i16), + DataValue::U32(n) => DataValue::I32(n as i32), + DataValue::U64(n) => DataValue::I64(n as i64), + _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), + }, + ValueConversionKind::RoundNearestEven(ty) => match (self.ty(), ty) { + (types::F64, types::F32) => unimplemented!(), + _ => unimplemented!("conversion: {} -> {:?}", self.ty(), kind), + }, + }) + } + + fn eq(&self, other: &Self) -> ValueResult { + comparison_match!(PartialEq::eq[&self, &other]; [I8, I16, I32, I64, U8, U16, U32, U64, F32, F64]) + } + + fn gt(&self, other: &Self) -> ValueResult { + comparison_match!(PartialOrd::gt[&self, &other]; [I8, I16, I32, I64, U8, U16, U32, U64, F32, F64]) + } + + fn uno(&self, other: &Self) -> ValueResult { + Ok(self.is_nan()? || other.is_nan()?) + } + + fn add(self, other: Self) -> ValueResult { + binary_match!(+(&self, &other); [I8, I16, I32, I64]) // TODO: floats must handle NaNs, +/-0 + } + + fn sub(self, other: Self) -> ValueResult { + binary_match!(-(&self, &other); [I8, I16, I32, I64]) // TODO: floats must handle NaNs, +/-0 + } + + fn mul(self, other: Self) -> ValueResult { + binary_match!(*(&self, &other); [I8, I16, I32, I64]) + } + + fn div(self, other: Self) -> ValueResult { + binary_match!(/(&self, &other); [I8, I16, I32, I64]) + } + + fn rem(self, other: Self) -> ValueResult { + binary_match!(%(&self, &other); [I8, I16, I32, I64]) + } + + fn shl(self, other: Self) -> ValueResult { + binary_match!(<<(&self, &other); [I8, I16, I32, I64]) + } + + fn ushr(self, other: Self) -> ValueResult { + binary_match!(>>(&self, &other); unsigned integers) + } + + fn ishr(self, other: Self) -> ValueResult { + binary_match!(>>(&self, &other); [I8, I16, I32, I64]) + } + + fn rotl(self, _other: Self) -> ValueResult { + unimplemented!() + } + + fn rotr(self, _other: Self) -> ValueResult { + unimplemented!() + } + + fn and(self, other: Self) -> ValueResult { + binary_match!(&(&self, &other); [I8, I16, I32, I64]) + } + + fn or(self, other: Self) -> ValueResult { + binary_match!(|(&self, &other); [I8, I16, I32, I64]) + } + + fn xor(self, other: Self) -> ValueResult { + binary_match!(^(&self, &other); [I8, I16, I32, I64]) + } + + fn not(self) -> ValueResult { + unary_match!(!(&self); [I8, I16, I32, I64]) + } +} diff --git a/cranelift/src/interpret.rs b/cranelift/src/interpret.rs index b15e6dace6cc..20738ac8edda 100644 --- a/cranelift/src/interpret.rs +++ b/cranelift/src/interpret.rs @@ -1,8 +1,9 @@ //! CLI tool to interpret Cranelift IR files. use crate::utils::iterate_files; -use cranelift_interpreter::environment::Environment; -use cranelift_interpreter::interpreter::{ControlFlow, Interpreter}; +use cranelift_interpreter::environment::FunctionStore; +use cranelift_interpreter::interpreter::{Interpreter, InterpreterState}; +use cranelift_interpreter::step::ControlFlow; use cranelift_reader::{parse_run_command, parse_test, ParseError, ParseOptions}; use log::debug; use std::path::PathBuf; @@ -109,10 +110,10 @@ impl FileInterpreter { .map_err(|e| FileInterpreterFailure::ParsingClif(self.path(), e))?; // collect functions - let mut env = Environment::default(); + let mut env = FunctionStore::default(); let mut commands = vec![]; - for (func, details) in test.functions.into_iter() { - for comment in details.comments { + for (func, details) in test.functions.iter() { + for comment in &details.comments { if let Some(command) = parse_run_command(comment.text, &func.signature) .map_err(|e| FileInterpreterFailure::ParsingClif(self.path(), e))? { @@ -124,14 +125,14 @@ impl FileInterpreter { } // Run assertion commands - let interpreter = Interpreter::new(env); for command in commands { command .run(|func_name, args| { // Because we have stored function names with a leading %, we need to re-add it. let func_name = &format!("%{}", func_name); - match interpreter.call_by_name(func_name, args) { - Ok(ControlFlow::Return(results)) => Ok(results), + let state = InterpreterState::default().with_function_store(env.clone()); + match Interpreter::new(state).call_by_name(func_name, args) { + Ok(ControlFlow::Return(results)) => Ok(results.to_vec()), Ok(_) => panic!("Unexpected returned control flow--this is likely a bug."), Err(t) => Err(t.to_string()), }