Skip to content

Commit 3c3fb35

Browse files
authored
Clarify docs around exceptions in Cranelift (#11601)
* Clarify docs around exceptions in Cranelift This is a result of today's Cranelift meeting with some of my questions around the ABI bits here and there. Notably: * Cranelift is audited and now documented to always consider the callee calling convention in `try_call`, disregarding the caller calling convention. * Wasmtime's exception throw now explicitly names the `tailcc` in the name to indicate that it's only compatible with the tail calling convention. * The verifier test for exceptions is expanded with a few more cases here and there that I could think of. * Backends now assert that the size of the calling-convention list of payload types is the same as the size of the list of registers the backend places results into. * Fix a typo
1 parent 789f037 commit 3c3fb35

7 files changed

Lines changed: 113 additions & 5 deletions

File tree

cranelift/codegen/src/isa/call_conv.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ impl CallConv {
9090
}
9191

9292
/// What types do the exception payload value(s) have?
93+
///
94+
/// Note that this function applies to the *callee* of a `try_call`
95+
/// instruction. The calling convention of the callee may differ from the
96+
/// caller, but the exceptional payload types available are defined by the
97+
/// callee calling convention.
98+
///
99+
/// Also note that individual backends are responsible for reporting
100+
/// register destinations for exceptional types. Internally Cranelift
101+
/// asserts that the backend supports the exact same number of register
102+
/// destinations as this return value.
93103
pub fn exception_payload_types(&self, pointer_ty: Type) -> &[Type] {
94104
match self {
95105
CallConv::Tail | CallConv::SystemV => match pointer_ty {

cranelift/codegen/src/machinst/abi.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,12 @@ pub trait ABIMachineSpec {
601601

602602
/// Get the exception payload registers, if any, for a calling
603603
/// convention.
604-
fn exception_payload_regs(_call_conv: isa::CallConv) -> &'static [Reg] {
604+
///
605+
/// Note that the argument here is the calling convention of the *callee*.
606+
/// This might differ from the caller but the exceptional payloads that are
607+
/// available are defined by the callee, not the caller.
608+
fn exception_payload_regs(callee_conv: isa::CallConv) -> &'static [Reg] {
609+
let _ = callee_conv;
605610
&[]
606611
}
607612
}
@@ -2021,6 +2026,15 @@ impl<M: ABIMachineSpec> Callee<M> {
20212026
assert!(outputs.next().is_none());
20222027

20232028
if let Some(try_call_payloads) = try_call_payloads {
2029+
// Let `M` say where the payload values are going to end up and then
2030+
// double-check it's the same size as the calling convention's
2031+
// reported number of exception types.
2032+
let pregs = M::exception_payload_regs(callee_conv);
2033+
assert_eq!(
2034+
callee_conv.exception_payload_types(M::word_type()).len(),
2035+
pregs.len()
2036+
);
2037+
20242038
// We need to update `defs` to contain the exception
20252039
// payload regs as well. We have two sources of info that
20262040
// we join:
@@ -2040,7 +2054,6 @@ impl<M: ABIMachineSpec> Callee<M> {
20402054
// handle the two cases below for each payload register:
20412055
// overlaps a return value (and we alias to it) or not
20422056
// (and we add a def).
2043-
let pregs = M::exception_payload_regs(callee_conv);
20442057
for (i, &preg) in pregs.iter().enumerate() {
20452058
let vreg = try_call_payloads[i];
20462059
if let Some(existing) = defs.iter().find(|def| match def.location {

cranelift/codegen/src/machinst/lower.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,10 @@ impl<'func, I: VCodeInst> Lower<'func, I> {
437437
try_call_rets.insert(inst, rets);
438438

439439
let mut payloads = smallvec![];
440+
// Note that this is intentionally using the calling
441+
// convention of the callee to determine what payload types
442+
// are available. The callee defines that, not the calling
443+
// convention of the caller.
440444
for &ty in sig
441445
.call_conv
442446
.exception_payload_types(I::ABIMachineSpec::word_type())

cranelift/filetests/filetests/verifier/exceptions.clif

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,81 @@ function %f5(i32) -> i32 {
131131
v10 = iconst.i32 3
132132
return v10
133133
}
134+
135+
;; Use the payloads of the callee, not the caller
136+
function %f5() windows_fastcall {
137+
sig0 = () tail
138+
fn0 = %g() tail
139+
140+
block0():
141+
try_call fn0(), sig0, block1(), [ tag1: block2(exn0), default: block3(exn1) ]
142+
143+
block1():
144+
return
145+
146+
block2(v0: i64):
147+
return
148+
149+
block3(v1: i64):
150+
return
151+
}
152+
153+
;; Out-of-bounds `exnN` paylaod
154+
function %f5() {
155+
sig0 = () tail
156+
fn0 = %g() tail
157+
158+
block0():
159+
try_call fn0(), sig0, block1(), [ default: block2(exn2) ] ; error: out-of-bounds `exnN` block argument
160+
161+
block1():
162+
return
163+
164+
block2(v1: i64):
165+
return
166+
}
167+
168+
;; `exnN` only allowed in exceptional blocks
169+
function %f5() {
170+
sig0 = () tail
171+
fn0 = %g() tail
172+
173+
block0():
174+
try_call fn0(), sig0, block1(exn0), [ default: block2() ] ; error: `exnN` block argument used outside normal-return target of `try_call`
175+
176+
block1(v1: i64):
177+
return
178+
179+
block2():
180+
return
181+
}
182+
183+
;; `retN` only allowed in normal return
184+
function %f5() {
185+
sig0 = () -> i32 tail
186+
fn0 = %g() -> i32 tail
187+
188+
block0():
189+
try_call fn0(), sig0, block1(ret0), [ default: block2(ret0) ] ; error: `retN` block argument used outside normal-return target of `try_call`
190+
191+
block1(v1: i32):
192+
return
193+
194+
block2(v2: i32):
195+
return
196+
}
197+
198+
;; `system_v` has two payload slots
199+
function %f5() {
200+
sig0 = () system_v
201+
fn0 = %g() system_v
202+
203+
block0():
204+
try_call fn0(), sig0, block1(), [ default: block2(exn0, exn1) ]
205+
206+
block1():
207+
return
208+
209+
block2(v1: i64, v2: i64):
210+
return
211+
}

cranelift/filetests/src/function_runner.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@ extern "C-unwind" fn __cranelift_throw(
682682
exit_fp,
683683
entry_fp,
684684
) {
685-
Some(handler) => handler.resume(payload1, payload2),
685+
Some(handler) => handler.resume_tailcc(payload1, payload2),
686686
None => {
687687
panic!("Expected a handler to exit for throw of tag {tag} at pc {exit_pc:x}");
688688
}

crates/unwinder/src/arch/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ cfg_if::cfg_if! {
6262
/// - The Rust frames between the unwind destination and this
6363
/// frame to be unwind-safe: that is, they cannot have `Drop`
6464
/// handlers for which safety requires that they run.
65-
pub unsafe fn resume(
65+
///
66+
/// - The Cranelift-generated `try_call` that we're unwinding to was
67+
/// invoking the callee with the `tail` calling convention.
68+
pub unsafe fn resume_tailcc(
6669
&self,
6770
payload1: usize,
6871
payload2: usize,

crates/wasmtime/src/runtime/vm/traphandlers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ impl CallThreadState {
944944
r.resume_to_exception_handler(handler, payload1, payload2)
945945
}
946946
#[cfg(has_host_compiler_backend)]
947-
ExecutorRef::Native => handler.resume(payload1, payload2),
947+
ExecutorRef::Native => handler.resume_tailcc(payload1, payload2),
948948
}
949949
}
950950
}

0 commit comments

Comments
 (0)