cranelift: Add LibCalls to the interpreter (#4782)

* cranelift: Add libcall handlers to interpreter

* cranelift: Fuzz IshlI64 libcall

* cranelift: Revert back to fuzzing udivi64

* cranelift: Use sdiv as a fuzz libcall

* cranelift: Register Sdiv in fuzzgen

* cranelift: Add multiple libcalls to fuzzer

* cranelift: Register a single libcall handler

* cranelift: Simplify args checking in interpreter

* cranelift: Remove unused LibCalls

* cranelift: Cleanup interpreter libcall types

* cranelift: Fix Interpreter Docs
This commit is contained in:
Afonso Bordado
2022-08-29 21:36:33 +01:00
committed by GitHub
parent a6eb24bd4f
commit 9a8bd5be02
9 changed files with 178 additions and 109 deletions

View File

@@ -12,10 +12,11 @@ use crate::value::{Value, ValueError};
use cranelift_codegen::data_value::DataValue;
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
use cranelift_codegen::ir::{
ArgumentPurpose, Block, FuncRef, Function, GlobalValue, GlobalValueData, Heap, StackSlot, Type,
Value as ValueRef,
ArgumentPurpose, Block, FuncRef, Function, GlobalValue, GlobalValueData, Heap, LibCall,
StackSlot, TrapCode, Type, Value as ValueRef,
};
use log::trace;
use smallvec::SmallVec;
use std::collections::HashSet;
use std::convert::{TryFrom, TryInto};
use std::fmt::Debug;
@@ -191,9 +192,13 @@ pub enum HeapInit {
FromBacking(HeapBacking),
}
pub type LibCallValues<V> = SmallVec<[V; 1]>;
pub type LibCallHandler<V> = fn(LibCall, LibCallValues<V>) -> Result<LibCallValues<V>, TrapCode>;
/// Maintains the [Interpreter]'s state, implementing the [State] trait.
pub struct InterpreterState<'a> {
pub functions: FunctionStore<'a>,
pub libcall_handler: LibCallHandler<DataValue>,
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,
@@ -208,6 +213,7 @@ impl Default for InterpreterState<'_> {
fn default() -> Self {
Self {
functions: FunctionStore::default(),
libcall_handler: |_, _| Err(TrapCode::UnreachableCodeReached),
frame_stack: vec![],
frame_offset: 0,
stack: Vec::with_capacity(1024),
@@ -224,6 +230,12 @@ impl<'a> InterpreterState<'a> {
Self { functions, ..self }
}
/// Registers a libcall handler
pub fn with_libcall_handler(mut self, handler: LibCallHandler<DataValue>) -> Self {
self.libcall_handler = handler;
self
}
/// Registers a static heap and returns a reference to it
///
/// This heap reference can be used to generate a heap pointer, which
@@ -301,6 +313,10 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> {
self.current_frame().function
}
fn get_libcall_handler(&self) -> LibCallHandler<DataValue> {
self.libcall_handler
}
fn push_frame(&mut self, function: &'a Function) {
if let Some(frame) = self.frame_stack.iter().last() {
self.frame_offset += frame.function.fixed_stack_size() as usize;
@@ -612,9 +628,11 @@ impl<'a> State<'a, DataValue> for InterpreterState<'a> {
mod tests {
use super::*;
use crate::step::CraneliftTrap;
use cranelift_codegen::ir::immediates::Ieee32;
use cranelift_codegen::ir::types::I64;
use cranelift_codegen::ir::TrapCode;
use cranelift_reader::parse_functions;
use smallvec::smallvec;
// Most interpreter tests should use the more ergonomic `test interpret` filetest but this
// unit test serves as a sanity check that the interpreter still works without all of the
@@ -1045,4 +1063,34 @@ mod tests {
assert_eq!(trap, CraneliftTrap::User(TrapCode::IntegerOverflow));
}
#[test]
fn libcall() {
let code = "function %test() -> i64 {
fn0 = colocated %CeilF32 (f32) -> f32 fast
block0:
v1 = f32const 0x0.5
v2 = call fn0(v1)
return v2
}";
let func = parse_functions(code).unwrap().into_iter().next().unwrap();
let mut env = FunctionStore::default();
env.add(func.name.to_string(), &func);
let state = InterpreterState::default()
.with_function_store(env)
.with_libcall_handler(|libcall, args| {
Ok(smallvec![match (libcall, &args[..]) {
(LibCall::CeilF32, [DataValue::F32(a)]) => DataValue::F32(a.ceil()),
_ => panic!("Unexpected args"),
}])
});
let result = Interpreter::new(state)
.call_by_name("%test", &[])
.unwrap()
.unwrap_return();
assert_eq!(result, vec![DataValue::F32(Ieee32::with_float(1.0))])
}
}