Make cranelift-interpreter non-generic over value (#6178)

* Make cranelift-interpreter non-generic over value

Fixes #5793

* Review suggestion

Co-authored-by: Jamey Sharp <jamey@minilop.net>

* Fix fuzz target

* Update doc comments

---------

Co-authored-by: Jamey Sharp <jamey@minilop.net>
This commit is contained in:
bjorn3
2023-04-11 13:13:29 +02:00
committed by GitHub
parent 3ff6e0fe03
commit 96a60aa26b
5 changed files with 441 additions and 449 deletions

View File

@@ -8,7 +8,7 @@ use crate::frame::Frame;
use crate::instruction::DfgInstructionContext;
use crate::state::{InterpreterFunctionRef, MemoryError, State};
use crate::step::{step, ControlFlow, StepError};
use crate::value::{Value, ValueError};
use crate::value::{DataValueExt, ValueError};
use cranelift_codegen::data_value::DataValue;
use cranelift_codegen::ir::{
ArgumentPurpose, Block, Endianness, ExternalName, FuncRef, Function, GlobalValue,
@@ -46,7 +46,7 @@ impl<'a> Interpreter<'a> {
&mut self,
func_name: &str,
arguments: &[DataValue],
) -> Result<ControlFlow<'a, DataValue>, InterpreterError> {
) -> Result<ControlFlow<'a>, InterpreterError> {
let index = self
.state
.functions
@@ -61,7 +61,7 @@ impl<'a> Interpreter<'a> {
&mut self,
index: FuncIndex,
arguments: &[DataValue],
) -> Result<ControlFlow<'a, DataValue>, InterpreterError> {
) -> Result<ControlFlow<'a>, InterpreterError> {
match self.state.functions.get_by_index(index) {
None => Err(InterpreterError::UnknownFunctionIndex(index)),
Some(func) => self.call(func, arguments),
@@ -73,7 +73,7 @@ impl<'a> Interpreter<'a> {
&mut self,
function: &'a Function,
arguments: &[DataValue],
) -> Result<ControlFlow<'a, DataValue>, InterpreterError> {
) -> Result<ControlFlow<'a>, InterpreterError> {
trace!("Call: {}({:?})", function.name, arguments);
let first_block = function
.layout
@@ -91,7 +91,7 @@ impl<'a> Interpreter<'a> {
/// 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(&mut self, block: Block) -> Result<ControlFlow<'a, DataValue>, InterpreterError> {
fn block(&mut self, block: Block) -> Result<ControlFlow<'a>, InterpreterError> {
trace!("Block: {}", block);
let function = self.state.current_frame_mut().function();
let layout = &function.layout;
@@ -192,13 +192,13 @@ pub enum InterpreterError {
FuelExhausted,
}
pub type LibCallValues<V> = SmallVec<[V; 1]>;
pub type LibCallHandler<V> = fn(LibCall, LibCallValues<V>) -> Result<LibCallValues<V>, TrapCode>;
pub type LibCallValues = SmallVec<[DataValue; 1]>;
pub type LibCallHandler = fn(LibCall, LibCallValues) -> Result<LibCallValues, TrapCode>;
/// Maintains the [Interpreter]'s state, implementing the [State] trait.
pub struct InterpreterState<'a> {
pub functions: FunctionStore<'a>,
pub libcall_handler: LibCallHandler<DataValue>,
pub libcall_handler: LibCallHandler,
pub frame_stack: Vec<Frame<'a>>,
/// Number of bytes from the bottom of the stack where the current frame's stack space is
pub frame_offset: usize,
@@ -232,7 +232,7 @@ impl<'a> InterpreterState<'a> {
}
/// Registers a libcall handler
pub fn with_libcall_handler(mut self, handler: LibCallHandler<DataValue>) -> Self {
pub fn with_libcall_handler(mut self, handler: LibCallHandler) -> Self {
self.libcall_handler = handler;
self
}
@@ -254,7 +254,7 @@ impl<'a> InterpreterState<'a> {
}
}
impl<'a> State<'a, DataValue> for InterpreterState<'a> {
impl<'a> State<'a> for InterpreterState<'a> {
fn get_function(&self, func_ref: FuncRef) -> Option<&'a Function> {
self.functions
.get_from_func_ref(func_ref, self.frame_stack.last().unwrap().function())
@@ -263,7 +263,7 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> {
self.current_frame().function()
}
fn get_libcall_handler(&self) -> LibCallHandler<DataValue> {
fn get_libcall_handler(&self) -> LibCallHandler {
self.libcall_handler
}

View File

@@ -22,13 +22,13 @@ use thiserror::Error;
/// 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> {
pub trait State<'a> {
/// Retrieve a reference to a [Function].
fn get_function(&self, func_ref: FuncRef) -> Option<&'a Function>;
/// Retrieve a reference to the currently executing [Function].
fn get_current_function(&self) -> &'a Function;
/// Retrieve the handler callback for a [LibCall](cranelift_codegen::ir::LibCall)
fn get_libcall_handler(&self) -> LibCallHandler<V>;
fn get_libcall_handler(&self) -> LibCallHandler;
/// 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].
@@ -36,14 +36,14 @@ pub trait State<'a, V> {
/// 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>;
fn get_value(&self, name: Value) -> Option<DataValue>;
/// 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>;
fn set_value(&mut self, name: Value, value: DataValue) -> Option<DataValue>;
/// 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> {
fn collect_values(&self, names: &[Value]) -> Result<SmallVec<[DataValue; 1]>, Value> {
let mut values = SmallVec::with_capacity(names.len());
for &n in names {
match self.get_value(n) {
@@ -68,13 +68,13 @@ pub trait State<'a, V> {
address: Address,
ty: Type,
mem_flags: MemFlags,
) -> Result<V, MemoryError>;
) -> Result<DataValue, MemoryError>;
/// Store a value `V` into memory at the given `address`, checking if it belongs either to the
/// stack or to one of the heaps; the number of bytes stored corresponds to the specified [Type].
fn checked_store(
&mut self,
address: Address,
v: V,
v: DataValue,
mem_flags: MemFlags,
) -> Result<(), MemoryError>;
@@ -90,12 +90,12 @@ pub trait State<'a, V> {
/// Given a global value, compute the final value for that global value, applying all operations
/// in intermediate global values.
fn resolve_global_value(&self, gv: GlobalValue) -> Result<V, MemoryError>;
fn resolve_global_value(&self, gv: GlobalValue) -> Result<DataValue, MemoryError>;
/// Retrieves the current pinned reg value
fn get_pinned_reg(&self) -> V;
fn get_pinned_reg(&self) -> DataValue;
/// Sets a value for the pinned reg
fn set_pinned_reg(&mut self, v: V);
fn set_pinned_reg(&mut self, v: DataValue);
}
pub enum InterpreterFunctionRef<'a> {
@@ -146,17 +146,14 @@ pub enum MemoryError {
}
/// 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 {
pub struct ImmutableRegisterState<'a>(&'a PrimaryMap<Value, DataValue>);
impl<'a> ImmutableRegisterState<'a> {
pub fn new(values: &'a PrimaryMap<Value, DataValue>) -> Self {
Self(values)
}
}
impl<'a, V> State<'a, V> for ImmutableRegisterState<'a, V>
where
V: Clone,
{
impl<'a> State<'a> for ImmutableRegisterState<'a> {
fn get_function(&self, _func_ref: FuncRef) -> Option<&'a Function> {
None
}
@@ -165,7 +162,7 @@ where
unimplemented!()
}
fn get_libcall_handler(&self) -> LibCallHandler<V> {
fn get_libcall_handler(&self) -> LibCallHandler {
unimplemented!()
}
@@ -177,11 +174,11 @@ where
unimplemented!()
}
fn get_value(&self, name: Value) -> Option<V> {
fn get_value(&self, name: Value) -> Option<DataValue> {
self.0.get(name).cloned()
}
fn set_value(&mut self, _name: Value, _value: V) -> Option<V> {
fn set_value(&mut self, _name: Value, _value: DataValue) -> Option<DataValue> {
None
}
@@ -199,14 +196,14 @@ where
_addr: Address,
_ty: Type,
_mem_flags: MemFlags,
) -> Result<V, MemoryError> {
) -> Result<DataValue, MemoryError> {
unimplemented!()
}
fn checked_store(
&mut self,
_addr: Address,
_v: V,
_v: DataValue,
_mem_flags: MemFlags,
) -> Result<(), MemoryError> {
unimplemented!()
@@ -224,15 +221,15 @@ where
unimplemented!()
}
fn resolve_global_value(&self, _gv: GlobalValue) -> Result<V, MemoryError> {
fn resolve_global_value(&self, _gv: GlobalValue) -> Result<DataValue, MemoryError> {
unimplemented!()
}
fn get_pinned_reg(&self) -> V {
fn get_pinned_reg(&self) -> DataValue {
unimplemented!()
}
fn set_pinned_reg(&mut self, _v: V) {
fn set_pinned_reg(&mut self, _v: DataValue) {
unimplemented!()
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
//! 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.
//! The [DataValueExt] trait is an extension trait for [DataValue]. It provides a lot of functions
//! used by the rest of the interpreter.
use core::convert::TryFrom;
use core::fmt::{self, Display, Formatter};
use cranelift_codegen::data_value::{DataValue, DataValueCastFailure};
@@ -11,9 +10,8 @@ use thiserror::Error;
pub type ValueResult<T> = Result<T, ValueError>;
pub trait Value: Clone + From<DataValue> {
pub trait DataValueExt: Sized {
// Identity.
fn ty(&self) -> Type;
fn int(n: i128, ty: Type) -> ValueResult<Self>;
fn into_int(self) -> ValueResult<i128>;
fn float(n: u64, ty: Type) -> ValueResult<Self>;
@@ -34,17 +32,6 @@ pub trait Value: Clone + From<DataValue> {
fn min(self, other: Self) -> 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.
@@ -240,11 +227,7 @@ macro_rules! bitop {
};
}
impl Value for DataValue {
fn ty(&self) -> Type {
self.ty()
}
impl DataValueExt for DataValue {
fn int(n: i128, ty: Type) -> ValueResult<Self> {
if ty.is_int() && !ty.is_vector() {
DataValue::from_integer(n, ty).map_err(|_| ValueError::InvalidValue(ty))
@@ -507,7 +490,7 @@ impl Value for DataValue {
}
fn max(self, other: Self) -> ValueResult<Self> {
if Value::gt(&self, &other)? {
if self > other {
Ok(self)
} else {
Ok(other)
@@ -515,21 +498,13 @@ impl Value for DataValue {
}
fn min(self, other: Self) -> ValueResult<Self> {
if Value::lt(&self, &other)? {
if self < other {
Ok(self)
} else {
Ok(other)
}
}
fn eq(&self, other: &Self) -> ValueResult<bool> {
Ok(self == other)
}
fn gt(&self, other: &Self) -> ValueResult<bool> {
Ok(self > other)
}
fn uno(&self, other: &Self) -> ValueResult<bool> {
Ok(self.is_nan()? || other.is_nan()?)
}
@@ -566,7 +541,7 @@ impl Value for DataValue {
let denominator = other.clone().into_int()?;
// Check if we are dividing INT_MIN / -1. This causes an integer overflow trap.
let min = Value::int(1i128 << (self.ty().bits() - 1), self.ty())?;
let min = DataValueExt::int(1i128 << (self.ty().bits() - 1), self.ty())?;
if self == min && denominator == -1 {
return Err(ValueError::IntegerOverflow);
}
@@ -582,7 +557,7 @@ impl Value for DataValue {
let denominator = other.clone().into_int()?;
// Check if we are dividing INT_MIN / -1. This causes an integer overflow trap.
let min = Value::int(1i128 << (self.ty().bits() - 1), self.ty())?;
let min = DataValueExt::int(1i128 << (self.ty().bits() - 1), self.ty())?;
if self == min && denominator == -1 {
return Err(ValueError::IntegerOverflow);
}