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:
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -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:
|
||||
|
||||
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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<DataValue, DataValueCastFailure> {
|
||||
/// 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<DataValue, DataValueCastFailure> {
|
||||
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),
|
||||
|
||||
@@ -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<Ordering> {
|
||||
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<Ordering> {
|
||||
f64::from_bits(self.0).partial_cmp(&f64::from_bits(other.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ieee64 {
|
||||
|
||||
@@ -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!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
use cranelift_codegen::ir::{FuncRef, Function};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Environment {
|
||||
functions: HashMap<FuncRef, Function>,
|
||||
#[derive(Default, Clone)]
|
||||
pub struct FunctionStore<'a> {
|
||||
functions: HashMap<FuncRef, &'a Function>,
|
||||
function_name_to_func_ref: HashMap<String, FuncRef>,
|
||||
}
|
||||
|
||||
impl From<Function> 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<Function> 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DataValue> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
42
cranelift/interpreter/src/instruction.rs
Normal file
42
cranelift/interpreter/src/instruction.rs
Normal file
@@ -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<Type>;
|
||||
fn controlling_type(&self) -> Option<Type>;
|
||||
}
|
||||
|
||||
/// 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<Type> {
|
||||
Some(self.1.value_type(v))
|
||||
}
|
||||
|
||||
fn controlling_type(&self) -> Option<Type> {
|
||||
Some(self.1.ctrl_typevar(self.0))
|
||||
}
|
||||
}
|
||||
@@ -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<ValueRef>),
|
||||
Return(Vec<DataValue>),
|
||||
/// 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<DataValue> {
|
||||
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<ControlFlow, Trap> {
|
||||
) -> Result<ControlFlow<'a, DataValue>, 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<ControlFlow, Trap> {
|
||||
match self.env.get_by_func_ref(func_ref) {
|
||||
None => Err(Trap::InvalidFunctionReference(func_ref)),
|
||||
) -> Result<ControlFlow<'a, DataValue>, 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<ControlFlow, Trap> {
|
||||
fn call(
|
||||
&mut self,
|
||||
function: &'a Function,
|
||||
arguments: &[DataValue],
|
||||
) -> Result<ControlFlow<'a, DataValue>, 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<ControlFlow, Trap> {
|
||||
fn block(&mut self, block: Block) -> Result<ControlFlow<'a, DataValue>, 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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),
|
||||
}
|
||||
|
||||
/// Maintains the [Interpreter]'s state, implementing the [State] trait.
|
||||
pub struct InterpreterState<'a> {
|
||||
pub functions: FunctionStore<'a>,
|
||||
pub frame_stack: Vec<Frame<'a>>,
|
||||
pub heap: Vec<u8>,
|
||||
pub iflags: HashSet<IntCC>,
|
||||
pub fflags: HashSet<FloatCC>,
|
||||
}
|
||||
|
||||
impl Default for InterpreterState<'_> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
functions: FunctionStore::default(),
|
||||
frame_stack: vec![],
|
||||
heap: vec![0; 1024],
|
||||
iflags: HashSet::new(),
|
||||
fflags: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> InterpreterState<'a> {
|
||||
pub fn with_function_store(self, functions: FunctionStore<'a>) -> Self {
|
||||
Self { functions, ..self }
|
||||
}
|
||||
|
||||
/// Interpret a single [instruction](Inst). This contains a `match`-based dispatch to the
|
||||
/// implementations.
|
||||
fn inst(&self, frame: &mut Frame, inst: Inst) -> Result<ControlFlow, Trap> {
|
||||
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)
|
||||
}
|
||||
|
||||
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])),
|
||||
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],
|
||||
}
|
||||
}
|
||||
_ => 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)?;
|
||||
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],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
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<DataValue> {
|
||||
Some(self.current_frame().get(name).clone()) // TODO avoid clone?
|
||||
}
|
||||
|
||||
fn set_value(&mut self, name: ValueRef, value: DataValue) -> Option<DataValue> {
|
||||
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<DataValue, MemoryError> {
|
||||
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(Trap::InvalidControlFlow(format!(
|
||||
"did not return from: {}",
|
||||
frame.function.dfg.display_inst(inst, None)
|
||||
)))
|
||||
Err(MemoryError::InsufficientMemory(offset, self.heap.len()))
|
||||
}
|
||||
}
|
||||
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<DataValue> = 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),
|
||||
},
|
||||
|
||||
_ => unimplemented!("interpreter does not support instruction yet: {:?}", data),
|
||||
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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
fn load_stack(&self, _offset: usize, _ty: Type) -> Result<DataValue, MemoryError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// 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<ValueRef> {
|
||||
args.as_slice(&function.dfg.value_lists).to_vec()
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// 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)
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
140
cranelift/interpreter/src/state.rs
Normal file
140
cranelift/interpreter/src/state.rs
Normal file
@@ -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<V>;
|
||||
/// 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<V>;
|
||||
/// 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<SmallVec<[V; 1]>, 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<V, MemoryError>;
|
||||
/// 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<V, MemoryError>;
|
||||
/// 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<Value, V>);
|
||||
impl<'a, V> ImmutableRegisterState<'a, V> {
|
||||
pub fn new(values: &'a PrimaryMap<Value, V>) -> 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<V> {
|
||||
self.0.get(name).cloned()
|
||||
}
|
||||
|
||||
fn set_value(&mut self, _name: Value, _value: V) -> Option<V> {
|
||||
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<V, MemoryError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn store_heap(&mut self, _offset: usize, _v: V) -> Result<(), MemoryError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn load_stack(&self, _offset: usize, _ty: Type) -> Result<V, MemoryError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn store_stack(&mut self, _offset: usize, _v: V) -> Result<(), MemoryError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
735
cranelift/interpreter/src/step.rs
Normal file
735
cranelift/interpreter/src/step.rs
Normal 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)?
|
||||
}
|
||||
})
|
||||
}
|
||||
329
cranelift/interpreter/src/value.rs
Normal file
329
cranelift/interpreter/src/value.rs
Normal file
@@ -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<T> = Result<T, ValueError>;
|
||||
|
||||
pub trait Value: Clone + From<DataValue> {
|
||||
// Identity.
|
||||
fn ty(&self) -> Type;
|
||||
fn int(n: i64, ty: Type) -> ValueResult<Self>;
|
||||
fn into_int(self) -> ValueResult<i64>;
|
||||
fn float(n: u64, ty: Type) -> ValueResult<Self>;
|
||||
fn into_float(self) -> ValueResult<f64>;
|
||||
fn is_nan(&self) -> ValueResult<bool>;
|
||||
fn bool(b: bool, ty: Type) -> ValueResult<Self>;
|
||||
fn into_bool(self) -> ValueResult<bool>;
|
||||
fn vector(v: [u8; 16], ty: Type) -> ValueResult<Self>;
|
||||
fn convert(self, kind: ValueConversionKind) -> ValueResult<Self>;
|
||||
|
||||
// Comparison.
|
||||
fn eq(&self, other: &Self) -> ValueResult<bool>;
|
||||
fn gt(&self, other: &Self) -> ValueResult<bool>;
|
||||
fn ge(&self, other: &Self) -> ValueResult<bool> {
|
||||
Ok(self.eq(other)? || self.gt(other)?)
|
||||
}
|
||||
fn lt(&self, other: &Self) -> ValueResult<bool> {
|
||||
other.gt(self)
|
||||
}
|
||||
fn le(&self, other: &Self) -> ValueResult<bool> {
|
||||
Ok(other.eq(self)? || other.gt(self)?)
|
||||
}
|
||||
fn uno(&self, other: &Self) -> ValueResult<bool>;
|
||||
|
||||
// Arithmetic.
|
||||
fn add(self, other: Self) -> ValueResult<Self>;
|
||||
fn sub(self, other: Self) -> ValueResult<Self>;
|
||||
fn mul(self, other: Self) -> ValueResult<Self>;
|
||||
fn div(self, other: Self) -> ValueResult<Self>;
|
||||
fn rem(self, other: Self) -> ValueResult<Self>;
|
||||
|
||||
// Bitwise.
|
||||
fn shl(self, other: Self) -> ValueResult<Self>;
|
||||
fn ushr(self, other: Self) -> ValueResult<Self>;
|
||||
fn ishr(self, other: Self) -> ValueResult<Self>;
|
||||
fn rotl(self, other: Self) -> ValueResult<Self>;
|
||||
fn rotr(self, other: Self) -> ValueResult<Self>;
|
||||
fn and(self, other: Self) -> ValueResult<Self>;
|
||||
fn or(self, other: Self) -> ValueResult<Self>;
|
||||
fn xor(self, other: Self) -> ValueResult<Self>;
|
||||
fn not(self) -> ValueResult<Self>;
|
||||
}
|
||||
|
||||
#[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<Self> {
|
||||
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<i64> {
|
||||
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<Self> {
|
||||
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<f64> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn is_nan(&self) -> ValueResult<bool> {
|
||||
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<Self> {
|
||||
assert!(ty.is_bool());
|
||||
Ok(DataValue::B(b))
|
||||
}
|
||||
|
||||
fn into_bool(self) -> ValueResult<bool> {
|
||||
match self {
|
||||
DataValue::B(b) => Ok(b),
|
||||
_ => Err(ValueError::InvalidType(ValueTypeClass::Boolean, self.ty())),
|
||||
}
|
||||
}
|
||||
|
||||
fn vector(_v: [u8; 16], _ty: Type) -> ValueResult<Self> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn convert(self, kind: ValueConversionKind) -> ValueResult<Self> {
|
||||
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<bool> {
|
||||
comparison_match!(PartialEq::eq[&self, &other]; [I8, I16, I32, I64, U8, U16, U32, U64, F32, F64])
|
||||
}
|
||||
|
||||
fn gt(&self, other: &Self) -> ValueResult<bool> {
|
||||
comparison_match!(PartialOrd::gt[&self, &other]; [I8, I16, I32, I64, U8, U16, U32, U64, F32, F64])
|
||||
}
|
||||
|
||||
fn uno(&self, other: &Self) -> ValueResult<bool> {
|
||||
Ok(self.is_nan()? || other.is_nan()?)
|
||||
}
|
||||
|
||||
fn add(self, other: Self) -> ValueResult<Self> {
|
||||
binary_match!(+(&self, &other); [I8, I16, I32, I64]) // TODO: floats must handle NaNs, +/-0
|
||||
}
|
||||
|
||||
fn sub(self, other: Self) -> ValueResult<Self> {
|
||||
binary_match!(-(&self, &other); [I8, I16, I32, I64]) // TODO: floats must handle NaNs, +/-0
|
||||
}
|
||||
|
||||
fn mul(self, other: Self) -> ValueResult<Self> {
|
||||
binary_match!(*(&self, &other); [I8, I16, I32, I64])
|
||||
}
|
||||
|
||||
fn div(self, other: Self) -> ValueResult<Self> {
|
||||
binary_match!(/(&self, &other); [I8, I16, I32, I64])
|
||||
}
|
||||
|
||||
fn rem(self, other: Self) -> ValueResult<Self> {
|
||||
binary_match!(%(&self, &other); [I8, I16, I32, I64])
|
||||
}
|
||||
|
||||
fn shl(self, other: Self) -> ValueResult<Self> {
|
||||
binary_match!(<<(&self, &other); [I8, I16, I32, I64])
|
||||
}
|
||||
|
||||
fn ushr(self, other: Self) -> ValueResult<Self> {
|
||||
binary_match!(>>(&self, &other); unsigned integers)
|
||||
}
|
||||
|
||||
fn ishr(self, other: Self) -> ValueResult<Self> {
|
||||
binary_match!(>>(&self, &other); [I8, I16, I32, I64])
|
||||
}
|
||||
|
||||
fn rotl(self, _other: Self) -> ValueResult<Self> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn rotr(self, _other: Self) -> ValueResult<Self> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn and(self, other: Self) -> ValueResult<Self> {
|
||||
binary_match!(&(&self, &other); [I8, I16, I32, I64])
|
||||
}
|
||||
|
||||
fn or(self, other: Self) -> ValueResult<Self> {
|
||||
binary_match!(|(&self, &other); [I8, I16, I32, I64])
|
||||
}
|
||||
|
||||
fn xor(self, other: Self) -> ValueResult<Self> {
|
||||
binary_match!(^(&self, &other); [I8, I16, I32, I64])
|
||||
}
|
||||
|
||||
fn not(self) -> ValueResult<Self> {
|
||||
unary_match!(!(&self); [I8, I16, I32, I64])
|
||||
}
|
||||
}
|
||||
@@ -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()),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user