cranelift-interpreter: Implement call_indirect and return_call_indirect (#5877)

* cranelift-interpreter: Implement `call_indirect`

* cranelift: Fix typo

* riscv64: Enable `call_indirect` tests
This commit is contained in:
Afonso Bordado
2023-02-25 13:16:59 +00:00
committed by GitHub
parent 36e92add6f
commit e9095050be
6 changed files with 321 additions and 69 deletions

View File

@@ -2,7 +2,7 @@
//! [InstructionContext]; the interpretation is generic over [Value]s.
use crate::address::{Address, AddressSize};
use crate::instruction::InstructionContext;
use crate::state::{MemoryError, State};
use crate::state::{InterpreterFunctionRef, MemoryError, State};
use crate::value::{Value, ValueConversionKind, ValueError, ValueResult};
use cranelift_codegen::data_value::DataValue;
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
@@ -276,35 +276,12 @@ where
}
};
// Perform a call operation.
//
// The returned `ControlFlow` variant is determined by the given function
// argument, which should make either a `ControlFlow::Call` or a
// `ControlFlow::ReturnCall`.
let do_call = |make_ctrl_flow: fn(&'a Function, SmallVec<[V; 1]>) -> ControlFlow<'a, V>|
// Calls a function reference with the given arguments.
let call_func = |func_ref: InterpreterFunctionRef<'a>,
args: SmallVec<[V; 1]>,
make_ctrl_flow: fn(&'a Function, SmallVec<[V; 1]>) -> ControlFlow<'a, V>|
-> Result<ControlFlow<'a, V>, StepError> {
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()?;
let signature = func_ref.signature();
// Check the types of the arguments. This is usually done by the verifier, but nothing
// guarantees that the user has ran that.
@@ -315,17 +292,16 @@ where
)));
}
Ok(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))?;
make_ctrl_flow(function, args)
}
ExternalName::LibCall(libcall) => {
debug_assert_ne!(inst.opcode(), Opcode::ReturnCall, "Cannot tail call to libcalls");
Ok(match func_ref {
InterpreterFunctionRef::Function(func) => make_ctrl_flow(func, args),
InterpreterFunctionRef::LibCall(libcall) => {
debug_assert!(
!matches!(
inst.opcode(),
Opcode::ReturnCall | Opcode::ReturnCallIndirect,
),
"Cannot tail call to libcalls"
);
let libcall_handler = state.get_libcall_handler();
// We don't transfer control to a libcall, we just execute it and return the results
@@ -342,7 +318,6 @@ where
ControlFlow::Trap(CraneliftTrap::User(TrapCode::BadSignature))
}
}
ExternalName::KnownSymbol(_) => unimplemented!(),
})
};
@@ -398,11 +373,83 @@ where
Opcode::Trapnz => trap_when(arg(0)?.into_bool()?, CraneliftTrap::User(trap_code())),
Opcode::ResumableTrapnz => trap_when(arg(0)?.into_bool()?, CraneliftTrap::Resumable),
Opcode::Return => ControlFlow::Return(args()?),
Opcode::Call => do_call(ControlFlow::Call)?,
Opcode::CallIndirect => unimplemented!("CallIndirect"),
Opcode::ReturnCall => do_call(ControlFlow::ReturnCall)?,
Opcode::ReturnCallIndirect => unimplemented!("ReturnCallIndirect"),
Opcode::FuncAddr => unimplemented!("FuncAddr"),
Opcode::Call | Opcode::ReturnCall => {
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 args = args()?;
let func = 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))?;
InterpreterFunctionRef::Function(function)
}
ExternalName::LibCall(libcall) => InterpreterFunctionRef::LibCall(libcall),
ExternalName::KnownSymbol(_) => unimplemented!(),
};
let make_control_flow = match inst.opcode() {
Opcode::Call => ControlFlow::Call,
Opcode::ReturnCall => ControlFlow::ReturnCall,
_ => unreachable!(),
};
call_func(func, args, make_control_flow)?
}
Opcode::CallIndirect | Opcode::ReturnCallIndirect => {
let args = args()?;
let addr_dv = DataValue::U64(arg(0)?.into_int()? as u64);
let addr = Address::try_from(addr_dv.clone()).map_err(StepError::MemoryError)?;
let func = state
.get_function_from_address(addr)
.ok_or_else(|| StepError::MemoryError(MemoryError::InvalidAddress(addr_dv)))?;
let call_args: SmallVec<[V; 1]> = SmallVec::from(&args[1..]);
let make_control_flow = match inst.opcode() {
Opcode::CallIndirect => ControlFlow::Call,
Opcode::ReturnCallIndirect => ControlFlow::ReturnCall,
_ => unreachable!(),
};
call_func(func, call_args, make_control_flow)?
}
Opcode::FuncAddr => {
let func_ref = if let InstructionData::FuncAddr { func_ref, .. } = inst {
func_ref
} else {
unreachable!()
};
let ext_data = state
.get_current_function()
.dfg
.ext_funcs
.get(func_ref)
.ok_or(StepError::UnknownFunction(func_ref))?;
let addr_ty = inst_context.controlling_type().unwrap();
assign_or_memtrap({
AddressSize::try_from(addr_ty).and_then(|addr_size| {
let addr = state.function_address(addr_size, &ext_data.name)?;
let dv = DataValue::try_from(addr)?;
Ok(dv.into())
})
})
}
Opcode::Load
| Opcode::Uload8
| Opcode::Sload8