Rewrite interpreter generically (#2323)

* Rewrite interpreter generically

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

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

View File

@@ -50,7 +50,7 @@ jobs:
name: Doc - build the API documentation name: Doc - build the API documentation
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
RUSTDOCFLAGS: -Dintra-doc-link-resolution-failure RUSTDOCFLAGS: -Dbroken_intra_doc_links
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:

5
Cargo.lock generated
View File

@@ -413,6 +413,7 @@ dependencies = [
"cranelift-frontend", "cranelift-frontend",
"cranelift-reader", "cranelift-reader",
"log", "log",
"smallvec",
"thiserror", "thiserror",
] ]
@@ -1884,9 +1885,9 @@ dependencies = [
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.4.1" version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3757cb9d89161a2f24e1cf78efa0c1fcff485d18e3f55e0aa3480824ddaa0f3f" checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
[[package]] [[package]]
name = "souper-ir" name = "souper-ir"

View File

@@ -1,9 +1,10 @@
//! This module gives users to instantiate values that Cranelift understands. These values are used, //! This module gives users to instantiate values that Cranelift understands. These values are used,
//! for example, during interpretation and for wrapping immediates. //! 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 crate::ir::{types, ConstantData, Type};
use core::convert::TryInto; use core::convert::TryInto;
use core::fmt::{self, Display, Formatter}; use core::fmt::{self, Display, Formatter};
use core::ptr;
use thiserror::Error; use thiserror::Error;
/// Represent a data value. Where [Value] is an SSA reference, [DataValue] is the type + value /// 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 /// [Value]: crate::ir::Value
#[allow(missing_docs)] #[allow(missing_docs)]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum DataValue { pub enum DataValue {
B(bool), B(bool),
I8(i8), I8(i8),
I16(i16), I16(i16),
I32(i32), I32(i32),
I64(i64), I64(i64),
U8(u8),
U16(u16),
U32(u32),
U64(u64),
F32(Ieee32), F32(Ieee32),
F64(Ieee64), F64(Ieee64),
V128([u8; 16]), V128([u8; 16]),
} }
impl DataValue { impl DataValue {
/// Try to cast an immediate integer ([Imm64]) to the given Cranelift [Type]. /// Try to cast an immediate integer (a wrapped `i64` on most Cranelift instructions) to the
pub fn from_integer(imm: Imm64, ty: Type) -> Result<DataValue, DataValueCastFailure> { /// given Cranelift [Type].
pub fn from_integer(imm: i64, ty: Type) -> Result<DataValue, DataValueCastFailure> {
match ty { match ty {
types::I8 => Ok(DataValue::I8(imm.bits() as i8)), types::I8 => Ok(DataValue::I8(imm as i8)),
types::I16 => Ok(DataValue::I16(imm.bits() as i16)), types::I16 => Ok(DataValue::I16(imm as i16)),
types::I32 => Ok(DataValue::I32(imm.bits() as i32)), types::I32 => Ok(DataValue::I32(imm as i32)),
types::I64 => Ok(DataValue::I64(imm.bits())), types::I64 => Ok(DataValue::I64(imm)),
_ => Err(DataValueCastFailure::FromImm64(imm, ty)), _ => Err(DataValueCastFailure::FromInteger(imm, ty)),
} }
} }
@@ -39,10 +45,10 @@ impl DataValue {
pub fn ty(&self) -> Type { pub fn ty(&self) -> Type {
match self { match self {
DataValue::B(_) => types::B8, // A default type. DataValue::B(_) => types::B8, // A default type.
DataValue::I8(_) => types::I8, DataValue::I8(_) | DataValue::U8(_) => types::I8,
DataValue::I16(_) => types::I16, DataValue::I16(_) | DataValue::U16(_) => types::I16,
DataValue::I32(_) => types::I32, DataValue::I32(_) | DataValue::U32(_) => types::I32,
DataValue::I64(_) => types::I64, DataValue::I64(_) | DataValue::U64(_) => types::I64,
DataValue::F32(_) => types::F32, DataValue::F32(_) => types::F32,
DataValue::F64(_) => types::F64, DataValue::F64(_) => types::F64,
DataValue::V128(_) => types::I8X16, // A default type. DataValue::V128(_) => types::I8X16, // A default type.
@@ -56,6 +62,38 @@ impl DataValue {
_ => false, _ => 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]. /// Record failures to cast [DataValue].
@@ -64,8 +102,8 @@ impl DataValue {
pub enum DataValueCastFailure { pub enum DataValueCastFailure {
#[error("unable to cast data value of type {0} to type {1}")] #[error("unable to cast data value of type {0} to type {1}")]
TryInto(Type, Type), TryInto(Type, Type),
#[error("unable to cast Imm64({0}) to a data value of type {1}")] #[error("unable to cast i64({0}) to a data value of type {1}")]
FromImm64(Imm64, Type), FromInteger(i64, Type),
} }
/// Helper for creating conversion implementations for [DataValue]. /// 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!(i16, I16, I16);
build_conversion_impl!(i32, I32, I32); build_conversion_impl!(i32, I32, I32);
build_conversion_impl!(i64, I64, I64); 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!(Ieee32, F32, F32);
build_conversion_impl!(Ieee64, F64, F64); build_conversion_impl!(Ieee64, F64, F64);
build_conversion_impl!([u8; 16], V128, I8X16); build_conversion_impl!([u8; 16], V128, I8X16);
@@ -114,6 +156,10 @@ impl Display for DataValue {
DataValue::I16(dv) => write!(f, "{}", dv), DataValue::I16(dv) => write!(f, "{}", dv),
DataValue::I32(dv) => write!(f, "{}", dv), DataValue::I32(dv) => write!(f, "{}", dv),
DataValue::I64(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. // The Ieee* wrappers here print the expected syntax.
DataValue::F32(dv) => write!(f, "{}", dv), DataValue::F32(dv) => write!(f, "{}", dv),
DataValue::F64(dv) => write!(f, "{}", dv), DataValue::F64(dv) => write!(f, "{}", dv),

View File

@@ -5,6 +5,7 @@
//! `cranelift-codegen/meta/src/shared/immediates` crate in the meta language. //! `cranelift-codegen/meta/src/shared/immediates` crate in the meta language.
use alloc::vec::Vec; use alloc::vec::Vec;
use core::cmp::Ordering;
use core::fmt::{self, Display, Formatter}; use core::fmt::{self, Display, Formatter};
use core::str::FromStr; use core::str::FromStr;
use core::{i32, u32}; use core::{i32, u32};
@@ -739,6 +740,17 @@ impl Ieee32 {
pub fn bits(self) -> u32 { pub fn bits(self) -> u32 {
self.0 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 { impl Display for Ieee32 {
@@ -812,6 +824,18 @@ impl Ieee64 {
pub fn bits(self) -> u64 { pub fn bits(self) -> u64 {
self.0 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 { impl Display for Ieee64 {

View File

@@ -237,6 +237,7 @@ impl UnboxedValues {
DataValue::F32(f) => ptr::write(p as *mut Ieee32, *f), DataValue::F32(f) => ptr::write(p as *mut Ieee32, *f),
DataValue::F64(f) => ptr::write(p as *mut Ieee64, *f), DataValue::F64(f) => ptr::write(p as *mut Ieee64, *f),
DataValue::V128(b) => ptr::write(p as *mut [u8; 16], *b), DataValue::V128(b) => ptr::write(p as *mut [u8; 16], *b),
_ => unimplemented!(),
} }
} }

View File

@@ -5,8 +5,9 @@
use crate::subtest::{Context, SubTest}; use crate::subtest::{Context, SubTest};
use cranelift_codegen::{self, ir}; use cranelift_codegen::{self, ir};
use cranelift_interpreter::environment::Environment; use cranelift_interpreter::environment::FunctionStore;
use cranelift_interpreter::interpreter::{ControlFlow, Interpreter}; use cranelift_interpreter::interpreter::{Interpreter, InterpreterState};
use cranelift_interpreter::step::ControlFlow;
use cranelift_reader::{parse_run_command, TestCommand}; use cranelift_reader::{parse_run_command, TestCommand};
use log::trace; use log::trace;
use std::borrow::Cow; use std::borrow::Cow;
@@ -39,16 +40,16 @@ impl SubTest for TestInterpret {
if let Some(command) = parse_run_command(comment.text, &func.signature)? { if let Some(command) = parse_run_command(comment.text, &func.signature)? {
trace!("Parsed run command: {}", command); trace!("Parsed run command: {}", command);
let mut env = Environment::default(); let mut env = FunctionStore::default();
env.add(func.name.to_string(), func.clone().into_owned()); env.add(func.name.to_string(), &func);
let interpreter = Interpreter::new(env);
command command
.run(|func_name, args| { .run(|func_name, args| {
// Because we have stored function names with a leading %, we need to re-add it. // Because we have stored function names with a leading %, we need to re-add it.
let func_name = &format!("%{}", func_name); let func_name = &format!("%{}", func_name);
match interpreter.call_by_name(func_name, args) { let state = InterpreterState::default().with_function_store(env);
Ok(ControlFlow::Return(results)) => Ok(results), match Interpreter::new(state).call_by_name(func_name, args) {
Ok(ControlFlow::Return(results)) => Ok(results.to_vec()),
Ok(_) => { Ok(_) => {
panic!("Unexpected returned control flow--this is likely a bug.") panic!("Unexpected returned control flow--this is likely a bug.")
} }

View File

@@ -11,10 +11,11 @@ readme = "README.md"
edition = "2018" edition = "2018"
[dependencies] [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-entity = { path = "../entity", version = "0.67.0" }
cranelift-reader = { path = "../reader", version = "0.67.0" } cranelift-reader = { path = "../reader", version = "0.67.0" }
log = { version = "0.4.8", default-features = false } log = { version = "0.4.8", default-features = false }
smallvec = "1.4.2"
thiserror = "1.0.15" thiserror = "1.0.15"
[dev-dependencies] [dev-dependencies]

View File

@@ -3,14 +3,14 @@
use cranelift_codegen::ir::{FuncRef, Function}; use cranelift_codegen::ir::{FuncRef, Function};
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Default)] #[derive(Default, Clone)]
pub struct Environment { pub struct FunctionStore<'a> {
functions: HashMap<FuncRef, Function>, functions: HashMap<FuncRef, &'a Function>,
function_name_to_func_ref: HashMap<String, FuncRef>, function_name_to_func_ref: HashMap<String, FuncRef>,
} }
impl From<Function> for Environment { impl<'a> From<&'a Function> for FunctionStore<'a> {
fn from(f: Function) -> Self { fn from(f: &'a Function) -> Self {
let func_ref = FuncRef::from_u32(0); let func_ref = FuncRef::from_u32(0);
let mut function_name_to_func_ref = HashMap::new(); let mut function_name_to_func_ref = HashMap::new();
function_name_to_func_ref.insert(f.name.to_string(), func_ref); 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. /// 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) let func_ref = FuncRef::with_number(self.function_name_to_func_ref.len() as u32)
.expect("a valid function reference"); .expect("a valid function reference");
self.function_name_to_func_ref.insert(name, func_ref); self.function_name_to_func_ref.insert(name, func_ref);
@@ -38,12 +38,12 @@ impl Environment {
} }
/// Retrieve a function by its function reference. /// Retrieve a function by its function reference.
pub fn get_by_func_ref(&self, func_ref: FuncRef) -> Option<&Function> { pub fn get_by_func_ref(&self, func_ref: FuncRef) -> Option<&'a Function> {
self.functions.get(&func_ref) self.functions.get(&func_ref).cloned()
} }
/// Retrieve a function by its name. /// 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)?; let func_ref = self.index_of(name)?;
self.get_by_func_ref(func_ref) self.get_by_func_ref(func_ref)
} }
@@ -57,17 +57,17 @@ mod tests {
#[test] #[test]
fn addition() { fn addition() {
let mut env = Environment::default(); let mut env = FunctionStore::default();
let a = "a"; let a = "a";
let f = Function::new(); let f = Function::new();
env.add(a.to_string(), f); env.add(a.to_string(), &f);
assert!(env.get_by_name(a).is_some()); assert!(env.get_by_name(a).is_some());
} }
#[test] #[test]
fn nonexistence() { fn nonexistence() {
let env = Environment::default(); let env = FunctionStore::default();
assert!(env.get_by_name("a").is_none()); assert!(env.get_by_name("a").is_none());
} }
@@ -75,8 +75,8 @@ mod tests {
fn from() { fn from() {
let name = ExternalName::testcase("test"); let name = ExternalName::testcase("test");
let signature = Signature::new(CallConv::Fast); let signature = Signature::new(CallConv::Fast);
let func = Function::with_name_signature(name, signature); let func = &Function::with_name_signature(name, signature);
let env: Environment = func.into(); let env: FunctionStore = func.into();
assert_eq!(env.index_of("%test"), FuncRef::with_number(0)); assert_eq!(env.index_of("%test"), FuncRef::with_number(0));
} }
} }

View File

@@ -32,16 +32,16 @@ impl<'a> Frame<'a> {
/// Retrieve the actual value associated with an SSA reference. /// Retrieve the actual value associated with an SSA reference.
#[inline] #[inline]
pub fn get(&self, name: &ValueRef) -> &DataValue { pub fn get(&self, name: ValueRef) -> &DataValue {
trace!("Get {}", name); trace!("Get {}", name);
self.registers self.registers
.get(name) .get(&name)
.unwrap_or_else(|| panic!("unknown value: {}", name)) .unwrap_or_else(|| panic!("unknown value: {}", name))
} }
/// Retrieve multiple SSA references; see `get`. /// Retrieve multiple SSA references; see `get`.
pub fn get_all(&self, names: &[ValueRef]) -> Vec<DataValue> { 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`. /// Assign `value` to the SSA reference `name`.
@@ -108,7 +108,7 @@ mod tests {
let a = ValueRef::with_number(1).unwrap(); let a = ValueRef::with_number(1).unwrap();
let fortytwo = DataValue::I32(42); let fortytwo = DataValue::I32(42);
frame.set(a, fortytwo.clone()); frame.set(a, fortytwo.clone());
assert_eq!(frame.get(&a), &fortytwo); assert_eq!(frame.get(a), &fortytwo);
} }
#[test] #[test]
@@ -118,6 +118,6 @@ mod tests {
let frame = Frame::new(&func); let frame = Frame::new(&func);
let a = ValueRef::with_number(1).unwrap(); let a = ValueRef::with_number(1).unwrap();
frame.get(&a); frame.get(a);
} }
} }

View 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))
}
}

View File

@@ -1,110 +1,65 @@
//! Cranelift IR interpreter. //! 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::frame::Frame;
use crate::interpreter::Trap::InvalidType; use crate::instruction::DfgInstructionContext;
use cranelift_codegen::data_value::{DataValue, DataValueCastFailure}; use crate::state::{MemoryError, State};
use cranelift_codegen::ir::condcodes::IntCC; use crate::step::{step, ControlFlow, StepError};
use cranelift_codegen::ir::{ use crate::value::ValueError;
Block, FuncRef, Function, Inst, InstructionData, InstructionData::*, Opcode, Opcode::*, Type, use cranelift_codegen::data_value::DataValue;
Value as ValueRef, ValueList, use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
}; use cranelift_codegen::ir::{Block, FuncRef, Function, Type, Value as ValueRef};
use log::trace; use log::trace;
use std::ops::{Add, Mul, Sub}; use std::collections::HashSet;
use std::fmt::Debug;
use thiserror::Error; use thiserror::Error;
/// The valid control flow states. /// The Cranelift interpreter; this contains some high-level functions to control the interpreter's
pub enum ControlFlow { /// flow. The interpreter state is defined separately (see [InterpreterState]) as the execution
Continue, /// semantics for each Cranelift instruction (see [step]).
ContinueAt(Block, Vec<ValueRef>), pub struct Interpreter<'a> {
Return(Vec<DataValue>), state: InterpreterState<'a>,
} }
impl ControlFlow { impl<'a> Interpreter<'a> {
/// For convenience, we can unwrap the [ControlFlow] state assuming that it is a pub fn new(state: InterpreterState<'a>) -> Self {
/// [ControlFlow::Return], panicking otherwise. Self { state }
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 }
} }
/// Call a function by name; this is a helpful proxy for [Interpreter::call_by_index]. /// Call a function by name; this is a helpful proxy for [Interpreter::call_by_index].
pub fn call_by_name( pub fn call_by_name(
&self, &mut self,
func_name: &str, func_name: &str,
arguments: &[DataValue], arguments: &[DataValue],
) -> Result<ControlFlow, Trap> { ) -> Result<ControlFlow<'a, DataValue>, InterpreterError> {
let func_ref = self let func_ref = self
.env .state
.functions
.index_of(func_name) .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) 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( pub fn call_by_index(
&self, &mut self,
func_ref: FuncRef, func_ref: FuncRef,
arguments: &[DataValue], arguments: &[DataValue],
) -> Result<ControlFlow, Trap> { ) -> Result<ControlFlow<'a, DataValue>, InterpreterError> {
match self.env.get_by_func_ref(func_ref) { match self.state.get_function(func_ref) {
None => Err(Trap::InvalidFunctionReference(func_ref)), None => Err(InterpreterError::UnknownFunctionReference(func_ref)),
Some(func) => self.call(func, arguments), Some(func) => self.call(func, arguments),
} }
} }
/// Interpret a call to a [Function] given its [DataValue] 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); trace!("Call: {}({:?})", function.name, arguments);
let first_block = function let first_block = function
.layout .layout
@@ -112,241 +67,184 @@ impl Interpreter {
.next() .next()
.expect("to have a first block"); .expect("to have a first block");
let parameters = function.dfg.block_params(first_block); let parameters = function.dfg.block_params(first_block);
let mut frame = Frame::new(function); self.state.push_frame(function);
frame.set_all(parameters, arguments.to_vec()); self.state
self.block(&mut frame, first_block) .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 /// Interpret a [Block] in a [Function]. This drives the interpretation over sequences of
/// instructions, which may continue in other blocks, until the function returns. /// 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); 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); let mut maybe_inst = layout.first_inst(block);
while let Some(inst) = maybe_inst { 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::Continue => maybe_inst = layout.next_inst(inst),
ControlFlow::ContinueAt(block, old_names) => { ControlFlow::ContinueAt(block, block_arguments) => {
trace!("Block: {}", block); trace!("Block: {}", block);
let new_names = frame.function.dfg.block_params(block); self.state
frame.rename(&old_names, new_names); .current_frame_mut()
.set_all(function.dfg.block_params(block), block_arguments.to_vec());
maybe_inst = layout.first_inst(block) 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 fn current_frame_mut(&mut self) -> &mut Frame<'a> {
/// implementations. let num_frames = self.frame_stack.len();
fn inst(&self, frame: &mut Frame, inst: Inst) -> Result<ControlFlow, Trap> { match num_frames {
use ControlFlow::{Continue, ContinueAt}; 0 => panic!("unable to retrieve the current frame because no frames were pushed"),
trace!("Inst: {}", &frame.function.dfg.display_inst(inst, None)); _ => &mut self.frame_stack[num_frames - 1],
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])),
} }
} }
_ => 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. fn current_frame(&self) -> &Frame<'a> {
let args = frame.get_all(args.as_slice(&frame.function.dfg.value_lists)); let num_frames = self.frame_stack.len();
let result = self.call_by_name(&func_name, &args)?; match num_frames {
0 => panic!("unable to retrieve the current frame because no frames were pushed"),
_ => &self.frame_stack[num_frames - 1],
}
}
}
// Save results. impl<'a> State<'a, DataValue> for InterpreterState<'a> {
if let ControlFlow::Return(returned_values) = result { fn get_function(&self, func_ref: FuncRef) -> Option<&'a Function> {
let ssa_values = frame.function.dfg.inst_results(inst); self.functions.get_by_func_ref(func_ref)
assert_eq!( }
ssa_values.len(), fn push_frame(&mut self, function: &'a Function) {
returned_values.len(), self.frame_stack.push(Frame::new(function));
"expected result length ({}) to match SSA values length ({}): {}", }
returned_values.len(), fn pop_frame(&mut self) {
ssa_values.len(), self.frame_stack.pop();
frame.function.dfg.display_inst(inst, None) }
);
frame.set_all(ssa_values, returned_values); fn get_value(&self, name: ValueRef) -> Option<DataValue> {
Ok(Continue) 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 { } else {
Err(Trap::InvalidControlFlow(format!( Err(MemoryError::InsufficientMemory(offset, self.heap.len()))
"did not return from: {}",
frame.function.dfg.display_inst(inst, None)
)))
} }
} }
InstructionData::Jump {
opcode,
destination,
args,
} => match opcode {
Opcode::Fallthrough => {
Ok(ContinueAt(*destination, value_refs(frame.function, args)))
}
Opcode::Jump => Ok(ContinueAt(*destination, value_refs(frame.function, args))),
_ => unimplemented!("interpreter does not support opcode yet: {}", opcode),
},
IntCompareImm {
opcode,
arg,
cond,
imm,
} => match opcode {
IcmpImm => {
let arg_value = match *frame.get(arg) {
DataValue::I8(i) => Ok(i as i64),
DataValue::I16(i) => Ok(i as i64),
DataValue::I32(i) => Ok(i as i64),
DataValue::I64(i) => Ok(i),
_ => Err(InvalidType("integer".to_string(), *arg)),
}?;
let imm_value = (*imm).into();
let result = match cond {
IntCC::UnsignedLessThanOrEqual => arg_value <= imm_value,
IntCC::Equal => arg_value == imm_value,
_ => unimplemented!(
"interpreter does not support condition code yet: {}",
cond
),
};
let res = first_result(frame.function, inst);
frame.set(res, DataValue::B(result));
Ok(Continue)
}
_ => unimplemented!("interpreter does not support opcode yet: {}", opcode),
},
MultiAry { opcode, args } => match opcode {
Return => {
let rs: Vec<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. fn load_stack(&self, _offset: usize, _ty: Type) -> Result<DataValue, MemoryError> {
/// unimplemented!()
/// 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)
}
/// Return a list of IR values as a vector. fn store_stack(&mut self, _offset: usize, _v: DataValue) -> Result<(), MemoryError> {
/// unimplemented!()
/// 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)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use cranelift_codegen::ir::immediates::Ieee32;
use cranelift_reader::parse_functions; use cranelift_reader::parse_functions;
// Most interpreter tests should use the more ergonomic `test interpret` filetest but this // 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 func = parse_functions(code).unwrap().into_iter().next().unwrap();
let mut env = Environment::default(); let mut env = FunctionStore::default();
env.add(func.name.to_string(), func); env.add(func.name.to_string(), &func);
let interpreter = Interpreter::new(env); let state = InterpreterState::default().with_function_store(env);
let result = interpreter let result = Interpreter::new(state)
.call_by_name("%test", &[]) .call_by_name("%test", &[])
.unwrap() .unwrap()
.unwrap_return(); .unwrap_return();
assert_eq!(result, vec![DataValue::B(true)]) 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));
}
} }

View File

@@ -4,4 +4,8 @@
pub mod environment; pub mod environment;
pub mod frame; pub mod frame;
pub mod instruction;
pub mod interpreter; pub mod interpreter;
pub mod state;
pub mod step;
pub mod value;

View 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!()
}
}

View File

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

View 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])
}
}

View File

@@ -1,8 +1,9 @@
//! CLI tool to interpret Cranelift IR files. //! CLI tool to interpret Cranelift IR files.
use crate::utils::iterate_files; use crate::utils::iterate_files;
use cranelift_interpreter::environment::Environment; use cranelift_interpreter::environment::FunctionStore;
use cranelift_interpreter::interpreter::{ControlFlow, Interpreter}; use cranelift_interpreter::interpreter::{Interpreter, InterpreterState};
use cranelift_interpreter::step::ControlFlow;
use cranelift_reader::{parse_run_command, parse_test, ParseError, ParseOptions}; use cranelift_reader::{parse_run_command, parse_test, ParseError, ParseOptions};
use log::debug; use log::debug;
use std::path::PathBuf; use std::path::PathBuf;
@@ -109,10 +110,10 @@ impl FileInterpreter {
.map_err(|e| FileInterpreterFailure::ParsingClif(self.path(), e))?; .map_err(|e| FileInterpreterFailure::ParsingClif(self.path(), e))?;
// collect functions // collect functions
let mut env = Environment::default(); let mut env = FunctionStore::default();
let mut commands = vec![]; let mut commands = vec![];
for (func, details) in test.functions.into_iter() { for (func, details) in test.functions.iter() {
for comment in details.comments { for comment in &details.comments {
if let Some(command) = parse_run_command(comment.text, &func.signature) if let Some(command) = parse_run_command(comment.text, &func.signature)
.map_err(|e| FileInterpreterFailure::ParsingClif(self.path(), e))? .map_err(|e| FileInterpreterFailure::ParsingClif(self.path(), e))?
{ {
@@ -124,14 +125,14 @@ impl FileInterpreter {
} }
// Run assertion commands // Run assertion commands
let interpreter = Interpreter::new(env);
for command in commands { for command in commands {
command command
.run(|func_name, args| { .run(|func_name, args| {
// Because we have stored function names with a leading %, we need to re-add it. // Because we have stored function names with a leading %, we need to re-add it.
let func_name = &format!("%{}", func_name); let func_name = &format!("%{}", func_name);
match interpreter.call_by_name(func_name, args) { let state = InterpreterState::default().with_function_store(env.clone());
Ok(ControlFlow::Return(results)) => Ok(results), 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."), Ok(_) => panic!("Unexpected returned control flow--this is likely a bug."),
Err(t) => Err(t.to_string()), Err(t) => Err(t.to_string()),
} }