Skip to content

Commit

Permalink
cranelift: Add LibCalls to the interpreter (#4782)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
afonso360 authored Aug 29, 2022
1 parent a6eb24b commit 9a8bd5b
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 108 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 1 addition & 76 deletions cranelift/codegen/src/ir/libcall.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Naming well-known routines in the runtime library.

use crate::{
ir::{types, AbiParam, ExternalName, FuncRef, Function, Opcode, Signature, Type},
ir::{types, AbiParam, ExternalName, FuncRef, Function, Signature},
isa::CallConv,
};
use core::fmt;
Expand All @@ -23,20 +23,6 @@ pub enum LibCall {
/// probe for stack overflow. These are emitted for functions which need
/// when the `enable_probestack` setting is true.
Probestack,
/// udiv.i64
UdivI64,
/// sdiv.i64
SdivI64,
/// urem.i64
UremI64,
/// srem.i64
SremI64,
/// ishl.i64
IshlI64,
/// ushr.i64
UshrI64,
/// sshr.i64
SshrI64,
/// ceil.f32
CeilF32,
/// ceil.f64
Expand Down Expand Up @@ -85,13 +71,6 @@ impl FromStr for LibCall {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Probestack" => Ok(Self::Probestack),
"UdivI64" => Ok(Self::UdivI64),
"SdivI64" => Ok(Self::SdivI64),
"UremI64" => Ok(Self::UremI64),
"SremI64" => Ok(Self::SremI64),
"IshlI64" => Ok(Self::IshlI64),
"UshrI64" => Ok(Self::UshrI64),
"SshrI64" => Ok(Self::SshrI64),
"CeilF32" => Ok(Self::CeilF32),
"CeilF64" => Ok(Self::CeilF64),
"FloorF32" => Ok(Self::FloorF32),
Expand All @@ -115,54 +94,11 @@ impl FromStr for LibCall {
}

impl LibCall {
/// Get the well-known library call name to use as a replacement for an instruction with the
/// given opcode and controlling type variable.
///
/// Returns `None` if no well-known library routine name exists for that instruction.
pub fn for_inst(opcode: Opcode, ctrl_type: Type) -> Option<Self> {
Some(match ctrl_type {
types::I64 => match opcode {
Opcode::Udiv => Self::UdivI64,
Opcode::Sdiv => Self::SdivI64,
Opcode::Urem => Self::UremI64,
Opcode::Srem => Self::SremI64,
Opcode::Ishl => Self::IshlI64,
Opcode::Ushr => Self::UshrI64,
Opcode::Sshr => Self::SshrI64,
_ => return None,
},
types::F32 => match opcode {
Opcode::Ceil => Self::CeilF32,
Opcode::Floor => Self::FloorF32,
Opcode::Trunc => Self::TruncF32,
Opcode::Nearest => Self::NearestF32,
Opcode::Fma => Self::FmaF32,
_ => return None,
},
types::F64 => match opcode {
Opcode::Ceil => Self::CeilF64,
Opcode::Floor => Self::FloorF64,
Opcode::Trunc => Self::TruncF64,
Opcode::Nearest => Self::NearestF64,
Opcode::Fma => Self::FmaF64,
_ => return None,
},
_ => return None,
})
}

/// Get a list of all known `LibCall`'s.
pub fn all_libcalls() -> &'static [LibCall] {
use LibCall::*;
&[
Probestack,
UdivI64,
SdivI64,
UremI64,
SremI64,
IshlI64,
UshrI64,
SshrI64,
CeilF32,
CeilF64,
FloorF32,
Expand All @@ -188,17 +124,6 @@ impl LibCall {
let mut sig = Signature::new(call_conv);

match self {
LibCall::UdivI64
| LibCall::SdivI64
| LibCall::UremI64
| LibCall::SremI64
| LibCall::IshlI64
| LibCall::UshrI64
| LibCall::SshrI64 => {
sig.params.push(AbiParam::new(I64));
sig.params.push(AbiParam::new(I64));
sig.returns.push(AbiParam::new(I64));
}
LibCall::CeilF32 | LibCall::FloorF32 | LibCall::TruncF32 | LibCall::NearestF32 => {
sig.params.push(AbiParam::new(F32));
sig.returns.push(AbiParam::new(F32));
Expand Down
27 changes: 21 additions & 6 deletions cranelift/fuzzgen/src/function_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,16 @@ const OPCODE_SIGNATURES: &'static [(
(Opcode::Call, &[], &[], insert_call),
];

/// These libcalls need a interpreter implementation in `cranelift-fuzzgen.rs`
const ALLOWED_LIBCALLS: &'static [LibCall] = &[
LibCall::CeilF32,
LibCall::CeilF64,
LibCall::FloorF32,
LibCall::FloorF64,
LibCall::TruncF32,
LibCall::TruncF64,
];

pub struct FunctionGenerator<'r, 'data>
where
'data: 'r,
Expand Down Expand Up @@ -506,6 +516,12 @@ where
Ok(CallConv::SystemV)
}

fn system_callconv(&mut self) -> CallConv {
// TODO: This currently only runs on linux, so this is the only choice
// We should improve this once we generate flags and targets
CallConv::SystemV
}

fn generate_type(&mut self) -> Result<Type> {
// TODO: It would be nice if we could get these directly from cranelift
let scalars = [
Expand Down Expand Up @@ -833,12 +849,11 @@ where
let signature = self.generate_signature()?;
(name, signature)
} else {
// Use udivi64 as an example of a libcall function.
let mut signature = Signature::new(CallConv::Fast);
signature.params.push(AbiParam::new(I64));
signature.params.push(AbiParam::new(I64));
signature.returns.push(AbiParam::new(I64));
(ExternalName::LibCall(LibCall::UdivI64), signature)
let libcall = *self.u.choose(ALLOWED_LIBCALLS)?;
// TODO: Use [CallConv::for_libcall] once we generate flags.
let callconv = self.system_callconv();
let signature = libcall.signature(callconv);
(ExternalName::LibCall(libcall), signature)
};

let sig_ref = builder.import_signature(sig.clone());
Expand Down
52 changes: 50 additions & 2 deletions cranelift/interpreter/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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),
Expand All @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))])
}
}
7 changes: 7 additions & 0 deletions cranelift/interpreter/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Cranelift instructions modify the state of the machine; the [State] trait describes these
//! ways this can happen.
use crate::address::{Address, AddressSize};
use crate::interpreter::LibCallHandler;
use cranelift_codegen::data_value::DataValue;
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
use cranelift_codegen::ir::{FuncRef, Function, GlobalValue, Heap, StackSlot, Type, Value};
Expand All @@ -23,6 +24,8 @@ pub trait State<'a, V> {
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>;
/// 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].
Expand Down Expand Up @@ -129,6 +132,10 @@ where
unimplemented!()
}

fn get_libcall_handler(&self) -> LibCallHandler<V> {
unimplemented!()
}

fn push_frame(&mut self, _function: &'a Function) {
unimplemented!()
}
Expand Down
Loading

0 comments on commit 9a8bd5b

Please sign in to comment.