Skip to content

Commit 94ec88e

Browse files
authored
Cranelift: initial try_call / try_call_indirect (exception) support. (#10510)
* Cranelift: initial try_call / try_call_indirect (exception) support. This PR adds `try_call` and `try_call_indirect` instructions, and lowerings on four of five ISAs (x86-64, aarch64, riscv64, pulley; s390x has its own non-shared ABI code that will need separate work). It extends CLIF to support these instructions as new kinds of branches, and extends block-calls to accept `retN` and `exnN` block-call args that carry the normal return values or exception payloads (respectively) into the appropriate successor blocks. It wires up the "normal return path" so that it continues to work. It updates the ABI so that unwinding is possible without an initial register state at throw: specifically, as per our RFC, all registers are clobbered. It also includes metadata in the `MachBuffer` that describes exception-catch destinations. However, no unwinder exists to interpret these catch-destinations yet, so they are untested. * Add try_call_indirect lowering as well.
1 parent 16f069c commit 94ec88e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+4199
-423
lines changed

cranelift/codegen/meta/src/gen_inst.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ fn gen_instruction_data_impl(formats: &[Rc<InstructionFormat>], fmt: &mut Format
336336
fmtln!(fmt, "::core::hash::Hash::hash(&{len}, state);");
337337
fmt.add_block(&format!("for &block in {blocks}"), |fmt| {
338338
fmtln!(fmt, "::core::hash::Hash::hash(&block.block(pool), state);");
339-
fmt.add_block("for &arg in block.args_slice(pool)", |fmt| {
339+
fmt.add_block("for arg in block.args(pool)", |fmt| {
340340
fmtln!(fmt, "::core::hash::Hash::hash(&arg, state);");
341341
});
342342
});
@@ -964,6 +964,7 @@ fn gen_inst_builder(inst: &Instruction, format: &InstructionFormat, fmt: &mut Fo
964964
let mut tmpl_types = Vec::new();
965965
let mut into_args = Vec::new();
966966
let mut block_args = Vec::new();
967+
let mut lifetime_param = None;
967968
for op in &inst.operands_in {
968969
if op.kind.is_block() {
969970
args.push(format!("{}_label: {}", op.name, "ir::Block"));
@@ -972,7 +973,14 @@ fn gen_inst_builder(inst: &Instruction, format: &InstructionFormat, fmt: &mut Fo
972973
op.name, "Destination basic block"
973974
));
974975

975-
args.push(format!("{}_args: {}", op.name, "&[Value]"));
976+
let lifetime = *lifetime_param.get_or_insert_with(|| {
977+
tmpl_types.insert(0, "'a".to_string());
978+
"'a"
979+
});
980+
args.push(format!(
981+
"{}_args: impl IntoIterator<Item = &{} BlockArg>",
982+
op.name, lifetime,
983+
));
976984
args_doc.push(format!("- {}_args: {}", op.name, "Block arguments"));
977985

978986
block_args.push(op);

cranelift/codegen/meta/src/shared/entities.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ pub(crate) struct EntityRefs {
4343
/// A reference to a jump table declared in the function preamble.
4444
pub(crate) jump_table: OperandKind,
4545

46+
/// A reference to an exception table declared in the function preamble.
47+
pub(crate) exception_table: OperandKind,
48+
4649
/// A variable-sized list of value operands. Use for Block and function call arguments.
4750
pub(crate) varargs: OperandKind,
4851
}
@@ -84,6 +87,8 @@ impl EntityRefs {
8487

8588
jump_table: new("table", "ir::JumpTable", "A jump table."),
8689

90+
exception_table: new("exception", "ir::ExceptionTable", "An exception table."),
91+
8792
varargs: OperandKind::new(
8893
"",
8994
"&[Value]",

cranelift/codegen/meta/src/shared/formats.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ pub(crate) struct Formats {
1212
pub(crate) brif: Rc<InstructionFormat>,
1313
pub(crate) call: Rc<InstructionFormat>,
1414
pub(crate) call_indirect: Rc<InstructionFormat>,
15+
pub(crate) try_call: Rc<InstructionFormat>,
16+
pub(crate) try_call_indirect: Rc<InstructionFormat>,
1517
pub(crate) cond_trap: Rc<InstructionFormat>,
1618
pub(crate) float_compare: Rc<InstructionFormat>,
1719
pub(crate) func_addr: Rc<InstructionFormat>,
@@ -133,6 +135,18 @@ impl Formats {
133135
.varargs()
134136
.build(),
135137

138+
try_call: Builder::new("TryCall")
139+
.imm(&entities.func_ref)
140+
.varargs()
141+
.imm(&&entities.exception_table)
142+
.build(),
143+
144+
try_call_indirect: Builder::new("TryCallIndirect")
145+
.value()
146+
.varargs()
147+
.imm(&&entities.exception_table)
148+
.build(),
149+
136150
func_addr: Builder::new("FuncAddr").imm(&entities.func_ref).build(),
137151

138152
atomic_rmw: Builder::new("AtomicRmw")

cranelift/codegen/meta/src/shared/instructions.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,75 @@ fn define_control_flow(
307307
.with_doc("function to call, declared by `function`")])
308308
.operands_out(vec![Operand::new("addr", iAddr)]),
309309
);
310+
311+
ig.push(
312+
Inst::new(
313+
"try_call",
314+
r#"
315+
Call a function, catching the specified exceptions.
316+
317+
Call the function pointed to by `callee` with the given arguments. On
318+
normal return, branch to the first target, with function returns
319+
available as `retN` block arguments. On exceptional return,
320+
look up the thrown exception tag in the provided exception table;
321+
if the tag matches one of the targets, branch to the matching
322+
target with the exception payloads available as `exnN` block arguments.
323+
If no tag matches, then propagate the exception up the stack.
324+
325+
It is the Cranelift embedder's responsibility to define the meaning
326+
of tags: they are accepted by this instruction and passed through
327+
to unwind metadata tables in Cranelift's output. Actual unwinding is
328+
outside the purview of the core Cranelift compiler.
329+
330+
Payload values on exception are passed in fixed register(s) that are
331+
defined by the platform and ABI. See the documentation on `CallConv`
332+
for details.
333+
"#,
334+
&formats.try_call,
335+
)
336+
.operands_in(vec![
337+
Operand::new("callee", &entities.func_ref)
338+
.with_doc("function to call, declared by `function`"),
339+
Operand::new("args", &entities.varargs).with_doc("call arguments"),
340+
Operand::new("ET", &entities.exception_table).with_doc("exception table"),
341+
])
342+
.call()
343+
.branches(),
344+
);
345+
346+
ig.push(
347+
Inst::new(
348+
"try_call_indirect",
349+
r#"
350+
Call a function, catching the specified exceptions.
351+
352+
Call the function pointed to by `callee` with the given arguments. On
353+
normal return, branch to the first target, with function returns
354+
available as `retN` block arguments. On exceptional return,
355+
look up the thrown exception tag in the provided exception table;
356+
if the tag matches one of the targets, branch to the matching
357+
target with the exception payloads available as `exnN` block arguments.
358+
If no tag matches, then propagate the exception up the stack.
359+
360+
It is the Cranelift embedder's responsibility to define the meaning
361+
of tags: they are accepted by this instruction and passed through
362+
to unwind metadata tables in Cranelift's output. Actual unwinding is
363+
outside the purview of the core Cranelift compiler.
364+
365+
Payload values on exception are passed in fixed register(s) that are
366+
defined by the platform and ABI. See the documentation on `CallConv`
367+
for details.
368+
"#,
369+
&formats.try_call_indirect,
370+
)
371+
.operands_in(vec![
372+
Operand::new("callee", iAddr).with_doc("address of function to call"),
373+
Operand::new("args", &entities.varargs).with_doc("call arguments"),
374+
Operand::new("ET", &entities.exception_table).with_doc("exception table"),
375+
])
376+
.call()
377+
.branches(),
378+
);
310379
}
311380

312381
#[inline(never)]

cranelift/codegen/src/dominator_tree.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ mod tests {
644644
cur.insert_block(block1);
645645
let v1 = cur.ins().iconst(I32, 1);
646646
let v2 = cur.ins().iadd(v0, v1);
647-
cur.ins().jump(block0, &[v2]);
647+
cur.ins().jump(block0, &[v2.into()]);
648648

649649
cur.insert_block(block2);
650650
cur.ins().return_(&[v0]);

cranelift/codegen/src/dominator_tree/simple.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ mod tests {
365365
cur.insert_block(block1);
366366
let v1 = cur.ins().iconst(I32, 1);
367367
let v2 = cur.ins().iadd(v0, v1);
368-
cur.ins().jump(block0, &[v2]);
368+
cur.ins().jump(block0, &[v2.into()]);
369369

370370
cur.insert_block(block2);
371371
cur.ins().return_(&[v0]);

cranelift/codegen/src/inst_predicates.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,16 @@ pub(crate) fn visit_block_succs<F: FnMut(Inst, Block, bool)>(
200200
}
201201
}
202202

203+
ir::InstructionData::TryCall { exception, .. }
204+
| ir::InstructionData::TryCallIndirect { exception, .. } => {
205+
let pool = &f.dfg.value_lists;
206+
let exdata = &f.stencil.dfg.exception_tables[*exception];
207+
208+
for dest in exdata.all_branches() {
209+
visit(inst, dest.block(pool), false);
210+
}
211+
}
212+
203213
inst => debug_assert!(!inst.opcode().is_branch()),
204214
}
205215
}

cranelift/codegen/src/ir/builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
use crate::ir;
77
use crate::ir::instructions::InstructionFormat;
88
use crate::ir::types;
9+
use crate::ir::{BlockArg, Inst, Opcode, Type, Value};
910
use crate::ir::{DataFlowGraph, InstructionData};
10-
use crate::ir::{Inst, Opcode, Type, Value};
1111

1212
/// Base trait for instruction builders.
1313
///

cranelift/codegen/src/ir/dfg.rs

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ use crate::ir::instructions::{CallInfo, InstructionData};
88
use crate::ir::pcc::Fact;
99
use crate::ir::user_stack_maps::{UserStackMapEntry, UserStackMapEntryVec};
1010
use crate::ir::{
11-
types, Block, BlockCall, ConstantData, ConstantPool, DynamicType, ExtFuncData, FuncRef,
12-
Immediate, Inst, JumpTables, RelSourceLoc, SigRef, Signature, Type, Value,
13-
ValueLabelAssignments, ValueList, ValueListPool,
11+
types, Block, BlockArg, BlockCall, ConstantData, ConstantPool, DynamicType, ExceptionTables,
12+
ExtFuncData, FuncRef, Immediate, Inst, JumpTables, RelSourceLoc, SigRef, Signature, Type,
13+
Value, ValueLabelAssignments, ValueList, ValueListPool,
1414
};
1515
use crate::packed_option::ReservedValue;
1616
use crate::write::write_operands;
@@ -158,6 +158,9 @@ pub struct DataFlowGraph {
158158

159159
/// Jump tables used in this function.
160160
pub jump_tables: JumpTables,
161+
162+
/// Exception tables used in this function.
163+
pub exception_tables: ExceptionTables,
161164
}
162165

163166
impl DataFlowGraph {
@@ -178,6 +181,7 @@ impl DataFlowGraph {
178181
constants: ConstantPool::new(),
179182
immediates: PrimaryMap::new(),
180183
jump_tables: JumpTables::new(),
184+
exception_tables: ExceptionTables::new(),
181185
}
182186
}
183187

@@ -226,8 +230,12 @@ impl DataFlowGraph {
226230
}
227231

228232
/// Make a BlockCall, bundling together the block and its arguments.
229-
pub fn block_call(&mut self, block: Block, args: &[Value]) -> BlockCall {
230-
BlockCall::new(block, args, &mut self.value_lists)
233+
pub fn block_call<'a>(
234+
&mut self,
235+
block: Block,
236+
args: impl IntoIterator<Item = &'a BlockArg>,
237+
) -> BlockCall {
238+
BlockCall::new(block, args.into_iter().copied(), &mut self.value_lists)
231239
}
232240

233241
/// Get the total number of values.
@@ -437,13 +445,18 @@ impl DataFlowGraph {
437445

438446
// Rewrite InstructionData in `self.insts`.
439447
for inst in self.insts.0.values_mut() {
440-
inst.map_values(&mut self.value_lists, &mut self.jump_tables, |arg| {
441-
if let ValueData::Alias { original, .. } = self.values[arg].into() {
442-
original
443-
} else {
444-
arg
445-
}
446-
});
448+
inst.map_values(
449+
&mut self.value_lists,
450+
&mut self.jump_tables,
451+
&mut self.exception_tables,
452+
|arg| {
453+
if let ValueData::Alias { original, .. } = self.values[arg].into() {
454+
original
455+
} else {
456+
arg
457+
}
458+
},
459+
);
447460
}
448461

449462
// - `results` and block-params in `blocks` are not aliases, by
@@ -843,23 +856,29 @@ impl DataFlowGraph {
843856
&'dfg self,
844857
inst: Inst,
845858
) -> impl DoubleEndedIterator<Item = Value> + 'dfg {
846-
self.inst_args(inst)
847-
.iter()
848-
.chain(
849-
self.insts[inst]
850-
.branch_destination(&self.jump_tables)
851-
.into_iter()
852-
.flat_map(|branch| branch.args_slice(&self.value_lists).iter()),
853-
)
854-
.copied()
859+
self.inst_args(inst).iter().copied().chain(
860+
self.insts[inst]
861+
.branch_destination(&self.jump_tables, &self.exception_tables)
862+
.into_iter()
863+
.flat_map(|branch| {
864+
branch
865+
.args(&self.value_lists)
866+
.filter_map(|arg| arg.as_value())
867+
}),
868+
)
855869
}
856870

857871
/// Map a function over the values of the instruction.
858872
pub fn map_inst_values<F>(&mut self, inst: Inst, body: F)
859873
where
860874
F: FnMut(Value) -> Value,
861875
{
862-
self.insts[inst].map_values(&mut self.value_lists, &mut self.jump_tables, body);
876+
self.insts[inst].map_values(
877+
&mut self.value_lists,
878+
&mut self.jump_tables,
879+
&mut self.exception_tables,
880+
body,
881+
);
863882
}
864883

865884
/// Overwrite the instruction's value references with values from the iterator.
@@ -869,9 +888,12 @@ impl DataFlowGraph {
869888
where
870889
I: Iterator<Item = Value>,
871890
{
872-
self.insts[inst].map_values(&mut self.value_lists, &mut self.jump_tables, |_| {
873-
values.next().unwrap()
874-
});
891+
self.insts[inst].map_values(
892+
&mut self.value_lists,
893+
&mut self.jump_tables,
894+
&mut self.exception_tables,
895+
|_| values.next().unwrap(),
896+
);
875897
}
876898

877899
/// Get all value arguments on `inst` as a slice.
@@ -1078,26 +1100,30 @@ impl DataFlowGraph {
10781100
/// Get the call signature of a direct or indirect call instruction.
10791101
/// Returns `None` if `inst` is not a call instruction.
10801102
pub fn call_signature(&self, inst: Inst) -> Option<SigRef> {
1081-
match self.insts[inst].analyze_call(&self.value_lists) {
1103+
match self.insts[inst].analyze_call(&self.value_lists, &self.exception_tables) {
10821104
CallInfo::NotACall => None,
10831105
CallInfo::Direct(f, _) => Some(self.ext_funcs[f].signature),
1106+
CallInfo::DirectWithSig(_, s, _) => Some(s),
10841107
CallInfo::Indirect(s, _) => Some(s),
10851108
}
10861109
}
10871110

1088-
/// Like `call_signature` but returns none for tail call instructions.
1089-
fn non_tail_call_signature(&self, inst: Inst) -> Option<SigRef> {
1111+
/// Like `call_signature` but returns none for tail call
1112+
/// instructions and try-call (exception-handling invoke)
1113+
/// instructions.
1114+
fn non_tail_call_or_try_call_signature(&self, inst: Inst) -> Option<SigRef> {
10901115
let sig = self.call_signature(inst)?;
10911116
match self.insts[inst].opcode() {
10921117
ir::Opcode::ReturnCall | ir::Opcode::ReturnCallIndirect => None,
1118+
ir::Opcode::TryCall | ir::Opcode::TryCallIndirect => None,
10931119
_ => Some(sig),
10941120
}
10951121
}
10961122

10971123
// Only for use by the verifier. Everyone else should just use
10981124
// `dfg.inst_results(inst).len()`.
10991125
pub(crate) fn num_expected_results_for_verifier(&self, inst: Inst) -> usize {
1100-
match self.non_tail_call_signature(inst) {
1126+
match self.non_tail_call_or_try_call_signature(inst) {
11011127
Some(sig) => self.signatures[sig].returns.len(),
11021128
None => {
11031129
let constraints = self.insts[inst].opcode().constraints();
@@ -1112,7 +1138,7 @@ impl DataFlowGraph {
11121138
inst: Inst,
11131139
ctrl_typevar: Type,
11141140
) -> impl iter::ExactSizeIterator<Item = Type> + 'a {
1115-
return match self.non_tail_call_signature(inst) {
1141+
return match self.non_tail_call_or_try_call_signature(inst) {
11161142
Some(sig) => InstResultTypes::Signature(self, sig, 0),
11171143
None => {
11181144
let constraints = self.insts[inst].opcode().constraints();

0 commit comments

Comments
 (0)