Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cranelift: Add LibCalls to the interpreter #4782

Merged
merged 11 commits into from
Aug 29, 2022
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