Cranelift: Introduce the return_call and return_call_indirect instructions (#5679)

* Cranelift: Introduce the `tail` calling convention

This is an unstable-ABI calling convention that we will eventually use to
support Wasm tail calls.

Co-Authored-By: Jamey Sharp <jsharp@fastly.com>

* Cranelift: Introduce the `return_call` and `return_call_indirect` instructions

These will be used to implement tail calls for Wasm and any other language
targeting CLIF. The `return_call_indirect` instruction differs from the Wasm
instruction of the same name by taking a native address callee rather than a
Wasm function index.

Co-Authored-By: Jamey Sharp <jsharp@fastly.com>

* Cranelift: Implement verification rules for `return_call[_indirect]`

They must:

* have the same return types between the caller and callee,
* have the same calling convention between caller and callee,
* and that calling convention must support tail calls.

Co-Authored-By: Jamey Sharp <jsharp@fastly.com>

* cargo fmt

---------

Co-authored-by: Jamey Sharp <jsharp@fastly.com>
This commit is contained in:
Nick Fitzgerald
2023-02-01 13:20:35 -08:00
committed by GitHub
parent ffbbfbffce
commit bdfb746548
8 changed files with 298 additions and 101 deletions

View File

@@ -532,23 +532,15 @@ impl<'a> Verifier<'a> {
));
}
let num_fixed_results = inst_data.opcode().constraints().num_fixed_results();
// var_results is 0 if we aren't a call instruction
let var_results = dfg
.call_signature(inst)
.map_or(0, |sig| dfg.signatures[sig].returns.len());
let total_results = num_fixed_results + var_results;
let expected_num_results = dfg.num_expected_results_for_verifier(inst);
// All result values for multi-valued instructions are created
let got_results = dfg.inst_results(inst).len();
if got_results != total_results {
if got_results != expected_num_results {
return errors.fatal((
inst,
self.context(inst),
format!(
"expected {} result values, found {}",
total_results, got_results,
),
format!("expected {expected_num_results} result values, found {got_results}"),
));
}
@@ -1426,29 +1418,91 @@ impl<'a> Verifier<'a> {
}
fn typecheck_return(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult<()> {
if self.func.dfg.insts[inst].opcode().is_return() {
let args = self.func.dfg.inst_variable_args(inst);
let expected_types = &self.func.signature.returns;
if args.len() != expected_types.len() {
return errors.nonfatal((
match self.func.dfg.insts[inst] {
ir::InstructionData::MultiAry {
opcode: Opcode::Return,
args,
} => {
let types = args
.as_slice(&self.func.dfg.value_lists)
.iter()
.map(|v| self.func.dfg.value_type(*v));
self.typecheck_return_types(
inst,
types,
errors,
"arguments of return must match function signature",
)?;
}
ir::InstructionData::Call {
opcode: Opcode::ReturnCall,
func_ref,
..
} => {
let sig_ref = self.func.dfg.ext_funcs[func_ref].signature;
self.typecheck_tail_call(inst, sig_ref, errors)?;
}
ir::InstructionData::CallIndirect {
opcode: Opcode::ReturnCallIndirect,
sig_ref,
..
} => {
self.typecheck_tail_call(inst, sig_ref, errors)?;
}
inst => debug_assert!(!inst.opcode().is_return()),
}
Ok(())
}
fn typecheck_tail_call(
&self,
inst: Inst,
sig_ref: SigRef,
errors: &mut VerifierErrors,
) -> VerifierStepResult<()> {
let signature = &self.func.dfg.signatures[sig_ref];
let cc = signature.call_conv;
if !cc.supports_tail_calls() {
errors.report((
inst,
self.context(inst),
format!("calling convention `{cc}` does not support tail calls"),
));
}
if cc != self.func.signature.call_conv {
errors.report((
inst,
self.context(inst),
"callee's calling convention must match caller",
));
}
let types = signature.returns.iter().map(|param| param.value_type);
self.typecheck_return_types(inst, types, errors, "results of callee must match caller")?;
Ok(())
}
fn typecheck_return_types(
&self,
inst: Inst,
actual_types: impl ExactSizeIterator<Item = Type>,
errors: &mut VerifierErrors,
message: &str,
) -> VerifierStepResult<()> {
let expected_types = &self.func.signature.returns;
if actual_types.len() != expected_types.len() {
return errors.nonfatal((inst, self.context(inst), message));
}
for (i, (actual_type, &expected_type)) in actual_types.zip(expected_types).enumerate() {
if actual_type != expected_type.value_type {
errors.report((
inst,
self.context(inst),
"arguments of return must match function signature",
format!(
"result {i} has type {actual_type}, must match function signature of \
{expected_type}"
),
));
}
for (i, (&arg, &expected_type)) in args.iter().zip(expected_types).enumerate() {
let arg_type = self.func.dfg.value_type(arg);
if arg_type != expected_type.value_type {
errors.report((
inst,
self.context(inst),
format!(
"arg {} ({}) has type {}, must match function signature of {}",
i, arg, arg_type, expected_type
),
));
}
}
}
Ok(())
}