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

@@ -7,7 +7,8 @@ use crate::value::{Value, ValueConversionKind, ValueError, ValueResult};
use cranelift_codegen::data_value::DataValue;
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
use cranelift_codegen::ir::{
types, Block, FuncRef, Function, InstructionData, Opcode, TrapCode, Type, Value as ValueRef,
types, AbiParam, Block, ExternalName, FuncRef, Function, InstructionData, Opcode, TrapCode,
Type, Value as ValueRef,
};
use log::trace;
use smallvec::{smallvec, SmallVec};
@@ -16,6 +17,14 @@ use std::fmt::Debug;
use std::ops::RangeFrom;
use thiserror::Error;
/// Ensures that all types in args are the same as expected by the signature
fn validate_signature_params(sig: &[AbiParam], args: &[impl Value]) -> bool {
args.iter()
.map(|r| r.ty())
.zip(sig.iter().map(|r| r.value_type))
.all(|(a, b)| a == b)
}
/// 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(...)`.
@@ -25,7 +34,7 @@ pub fn step<'a, V, I>(
inst_context: I,
) -> Result<ControlFlow<'a, V>, StepError>
where
V: Value,
V: Value + Debug,
I: InstructionContext,
{
let inst = inst_context.data();
@@ -295,13 +304,65 @@ where
),
Opcode::Return => 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()?)
let func_ref = if let InstructionData::Call { func_ref, .. } = inst {
func_ref
} else {
unreachable!()
};
let curr_func = state.get_current_function();
let ext_data = curr_func
.dfg
.ext_funcs
.get(func_ref)
.ok_or(StepError::UnknownFunction(func_ref))?;
let signature = if let Some(sig) = curr_func.dfg.signatures.get(ext_data.signature) {
sig
} else {
return Ok(ControlFlow::Trap(CraneliftTrap::User(
TrapCode::BadSignature,
)));
};
let args = args()?;
// Check the types of the arguments. This is usually done by the verifier, but nothing
// guarantees that the user has ran that.
let args_match = validate_signature_params(&signature.params[..], &args[..]);
if !args_match {
return Ok(ControlFlow::Trap(CraneliftTrap::User(
TrapCode::BadSignature,
)));
}
match ext_data.name {
// These functions should be registered in the regular function store
ExternalName::User(_) | ExternalName::TestCase(_) => {
let function = state
.get_function(func_ref)
.ok_or(StepError::UnknownFunction(func_ref))?;
ControlFlow::Call(function, args)
}
ExternalName::LibCall(libcall) => {
let libcall_handler = state.get_libcall_handler();
// We don't transfer control to a libcall, we just execute it and return the results
let res = libcall_handler(libcall, args);
let res = match res {
Err(trap) => return Ok(ControlFlow::Trap(CraneliftTrap::User(trap))),
Ok(rets) => rets,
};
// Check that what the handler returned is what we expect.
if validate_signature_params(&signature.returns[..], &res[..]) {
ControlFlow::Assign(res)
} else {
ControlFlow::Trap(CraneliftTrap::User(TrapCode::BadSignature))
}
}
ExternalName::KnownSymbol(_) => unimplemented!(),
}
}
Opcode::CallIndirect => unimplemented!("CallIndirect"),