implement unwinding abi's (RFC 2945)
### Changes This commit implements unwind ABI's, specified in RFC 2945. We adjust the `rustc_middle::ty::layout::fn_can_unwind` function, used to compute whether or not a `FnAbi` object represents a function that should be able to unwind when `panic=unwind` is in use. Changes are also made to `rustc_mir_build::build::should_abort_on_panic` so that the function ABI is used to determind whether it should abort, assuming that the `panic=unwind` strategy is being used, and no explicit unwind attribute was provided. ### Tests Unit tests, checking that the behavior is correct for `C-unwind`, `stdcall-unwind`, `system-unwind`, and `thiscall-unwind`, are included. These alternative `unwind` ABI strings are specified in RFC 2945, in the "_Other `unwind` ABI strings_" section. Additionally, a test case is included to assert that the LLVM IR generated for an external function defined with the `C-unwind` ABI will be appropriately labeled with the `nounwind` LLVM attribute when the `panic=abort` compilation flag is used. ### Ignore Directives This commit uses `ignore-*` directives in two of our `*-unwind` ABI test cases. Specifically, the `stdcall-unwind` and `thiscall-unwind` test cases ignore architectures that do not support `stdcall` and `thiscall`, respectively. These directives are cribbed from `src/test/ui/c-variadic/variadic-ffi-1.rs` for `stdcall`, and `src/test/ui/extern/extern-thiscall.rs` for `thiscall`.
This commit is contained in:
parent
df45c579de
commit
0f33e9f281
7 changed files with 176 additions and 16 deletions
|
@ -2562,6 +2562,7 @@ fn fn_can_unwind(
|
||||||
panic_strategy: PanicStrategy,
|
panic_strategy: PanicStrategy,
|
||||||
codegen_fn_attr_flags: CodegenFnAttrFlags,
|
codegen_fn_attr_flags: CodegenFnAttrFlags,
|
||||||
call_conv: Conv,
|
call_conv: Conv,
|
||||||
|
abi: SpecAbi,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if panic_strategy != PanicStrategy::Unwind {
|
if panic_strategy != PanicStrategy::Unwind {
|
||||||
// In panic=abort mode we assume nothing can unwind anywhere, so
|
// In panic=abort mode we assume nothing can unwind anywhere, so
|
||||||
|
@ -2586,17 +2587,16 @@ fn fn_can_unwind(
|
||||||
//
|
//
|
||||||
// 2. A Rust item using a non-Rust ABI (like `extern "C" fn foo() { ... }`).
|
// 2. A Rust item using a non-Rust ABI (like `extern "C" fn foo() { ... }`).
|
||||||
//
|
//
|
||||||
// Foreign items (case 1) are assumed to not unwind; it is
|
// In both of these cases, we should refer to the ABI to determine whether or not we
|
||||||
// UB otherwise. (At least for now; see also
|
// should unwind. See Rust RFC 2945 for more information on this behavior, here:
|
||||||
// rust-lang/rust#63909 and Rust RFC 2753.)
|
// https://github.com/rust-lang/rfcs/blob/master/text/2945-c-unwind-abi.md
|
||||||
//
|
use SpecAbi::*;
|
||||||
// Items defined in Rust with non-Rust ABIs (case 2) are also
|
match abi {
|
||||||
// not supposed to unwind. Whether this should be enforced
|
C { unwind } | Stdcall { unwind } | System { unwind } | Thiscall { unwind } => {
|
||||||
// (versus stating it is UB) and *how* it would be enforced
|
unwind
|
||||||
// is currently under discussion; see rust-lang/rust#58794.
|
}
|
||||||
//
|
_ => false,
|
||||||
// In either case, we mark item as explicitly nounwind.
|
}
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2823,7 +2823,12 @@ where
|
||||||
c_variadic: sig.c_variadic,
|
c_variadic: sig.c_variadic,
|
||||||
fixed_count: inputs.len(),
|
fixed_count: inputs.len(),
|
||||||
conv,
|
conv,
|
||||||
can_unwind: fn_can_unwind(cx.tcx().sess.panic_strategy(), codegen_fn_attr_flags, conv),
|
can_unwind: fn_can_unwind(
|
||||||
|
cx.tcx().sess.panic_strategy(),
|
||||||
|
codegen_fn_attr_flags,
|
||||||
|
conv,
|
||||||
|
sig.abi,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
fn_abi.adjust_for_abi(cx, sig.abi);
|
fn_abi.adjust_for_abi(cx, sig.abi);
|
||||||
debug!("FnAbi::new_internal = {:?}", fn_abi);
|
debug!("FnAbi::new_internal = {:?}", fn_abi);
|
||||||
|
|
|
@ -548,7 +548,7 @@ macro_rules! unpack {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_abort_on_panic(tcx: TyCtxt<'_>, fn_def_id: LocalDefId, _abi: Abi) -> bool {
|
fn should_abort_on_panic(tcx: TyCtxt<'_>, fn_def_id: LocalDefId, abi: Abi) -> bool {
|
||||||
// Validate `#[unwind]` syntax regardless of platform-specific panic strategy.
|
// Validate `#[unwind]` syntax regardless of platform-specific panic strategy.
|
||||||
let attrs = &tcx.get_attrs(fn_def_id.to_def_id());
|
let attrs = &tcx.get_attrs(fn_def_id.to_def_id());
|
||||||
let unwind_attr = attr::find_unwind_attr(&tcx.sess, attrs);
|
let unwind_attr = attr::find_unwind_attr(&tcx.sess, attrs);
|
||||||
|
@ -558,12 +558,26 @@ fn should_abort_on_panic(tcx: TyCtxt<'_>, fn_def_id: LocalDefId, _abi: Abi) -> b
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a special case: some functions have a C abi but are meant to
|
|
||||||
// unwind anyway. Don't stop them.
|
|
||||||
match unwind_attr {
|
match unwind_attr {
|
||||||
None => false, // FIXME(#58794); should be `!(abi == Abi::Rust || abi == Abi::RustCall)`
|
// If an `#[unwind]` attribute was found, we should adhere to it.
|
||||||
Some(UnwindAttr::Allowed) => false,
|
Some(UnwindAttr::Allowed) => false,
|
||||||
Some(UnwindAttr::Aborts) => true,
|
Some(UnwindAttr::Aborts) => true,
|
||||||
|
// If no attribute was found and the panic strategy is `unwind`, then we should examine
|
||||||
|
// the function's ABI string to determine whether it should abort upon panic.
|
||||||
|
None => {
|
||||||
|
use Abi::*;
|
||||||
|
match abi {
|
||||||
|
// In the case of ABI's that have an `-unwind` equivalent, check whether the ABI
|
||||||
|
// permits unwinding. If so, we should not abort. Otherwise, we should.
|
||||||
|
C { unwind } | Stdcall { unwind } | System { unwind } | Thiscall { unwind } => {
|
||||||
|
!unwind
|
||||||
|
}
|
||||||
|
// Rust and `rust-call` functions are allowed to unwind, and should not abort.
|
||||||
|
Rust | RustCall => false,
|
||||||
|
// Other ABI's should abort.
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
src/test/codegen/unwind-abis/c-unwind-abi-panic-abort.rs
Normal file
18
src/test/codegen/unwind-abis/c-unwind-abi-panic-abort.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// compile-flags: -C panic=abort -C opt-level=0
|
||||||
|
|
||||||
|
// Test that `nounwind` atributes are applied to `C-unwind` extern functions when the
|
||||||
|
// code is compiled with `panic=abort`. We disable optimizations above to prevent LLVM from
|
||||||
|
// inferring the attribute.
|
||||||
|
|
||||||
|
#![crate_type = "lib"]
|
||||||
|
#![feature(c_unwind)]
|
||||||
|
|
||||||
|
// CHECK: @rust_item_that_can_unwind() unnamed_addr #0 {
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C-unwind" fn rust_item_that_can_unwind() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, make sure that the LLVM attributes for this functions are correct. First, make
|
||||||
|
// sure that the first item is correctly marked with the `nounwind` attribute:
|
||||||
|
//
|
||||||
|
// CHECK: attributes #0 = { {{.*}}nounwind{{.*}} }
|
29
src/test/codegen/unwind-abis/c-unwind-abi.rs
Normal file
29
src/test/codegen/unwind-abis/c-unwind-abi.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// compile-flags: -C opt-level=0
|
||||||
|
|
||||||
|
// Test that `nounwind` atributes are correctly applied to exported `C` and `C-unwind` extern
|
||||||
|
// functions. `C-unwind` functions MUST NOT have this attribute. We disable optimizations above
|
||||||
|
// to prevent LLVM from inferring the attribute.
|
||||||
|
|
||||||
|
#![crate_type = "lib"]
|
||||||
|
#![feature(c_unwind)]
|
||||||
|
|
||||||
|
// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 {
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn rust_item_that_cannot_unwind() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 {
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C-unwind" fn rust_item_that_can_unwind() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, make some assertions that the LLVM attributes for these functions are correct. First, make
|
||||||
|
// sure that the first item is correctly marked with the `nounwind` attribute:
|
||||||
|
//
|
||||||
|
// CHECK: attributes #0 = { {{.*}}nounwind{{.*}} }
|
||||||
|
//
|
||||||
|
// Next, let's assert that the second item, which CAN unwind, does not have this attribute.
|
||||||
|
//
|
||||||
|
// CHECK: attributes #1 = {
|
||||||
|
// CHECK-NOT: nounwind
|
||||||
|
// CHECK: }
|
32
src/test/codegen/unwind-abis/stdcall-unwind-abi.rs
Normal file
32
src/test/codegen/unwind-abis/stdcall-unwind-abi.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// compile-flags: -C opt-level=0
|
||||||
|
// ignore-arm stdcall isn't supported
|
||||||
|
// ignore-aarch64 stdcall isn't supported
|
||||||
|
// ignore-riscv64 stdcall isn't supported
|
||||||
|
|
||||||
|
// Test that `nounwind` atributes are correctly applied to exported `stdcall` and `stdcall-unwind`
|
||||||
|
// extern functions. `stdcall-unwind` functions MUST NOT have this attribute. We disable
|
||||||
|
// optimizations above to prevent LLVM from inferring the attribute.
|
||||||
|
|
||||||
|
#![crate_type = "lib"]
|
||||||
|
#![feature(c_unwind)]
|
||||||
|
|
||||||
|
// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 {
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "stdcall" fn rust_item_that_cannot_unwind() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 {
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "stdcall-unwind" fn rust_item_that_can_unwind() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, make some assertions that the LLVM attributes for these functions are correct. First, make
|
||||||
|
// sure that the first item is correctly marked with the `nounwind` attribute:
|
||||||
|
//
|
||||||
|
// CHECK: attributes #0 = { {{.*}}nounwind{{.*}} }
|
||||||
|
//
|
||||||
|
// Next, let's assert that the second item, which CAN unwind, does not have this attribute.
|
||||||
|
//
|
||||||
|
// CHECK: attributes #1 = {
|
||||||
|
// CHECK-NOT: nounwind
|
||||||
|
// CHECK: }
|
29
src/test/codegen/unwind-abis/system-unwind-abi.rs
Normal file
29
src/test/codegen/unwind-abis/system-unwind-abi.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// compile-flags: -C opt-level=0
|
||||||
|
|
||||||
|
// Test that `nounwind` atributes are correctly applied to exported `system` and `system-unwind`
|
||||||
|
// extern functions. `system-unwind` functions MUST NOT have this attribute. We disable
|
||||||
|
// optimizations above to prevent LLVM from inferring the attribute.
|
||||||
|
|
||||||
|
#![crate_type = "lib"]
|
||||||
|
#![feature(c_unwind)]
|
||||||
|
|
||||||
|
// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 {
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system" fn rust_item_that_cannot_unwind() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 {
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "system-unwind" fn rust_item_that_can_unwind() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, make some assertions that the LLVM attributes for these functions are correct. First, make
|
||||||
|
// sure that the first item is correctly marked with the `nounwind` attribute:
|
||||||
|
//
|
||||||
|
// CHECK: attributes #0 = { {{.*}}nounwind{{.*}} }
|
||||||
|
//
|
||||||
|
// Next, let's assert that the second item, which CAN unwind, does not have this attribute.
|
||||||
|
//
|
||||||
|
// CHECK: attributes #1 = {
|
||||||
|
// CHECK-NOT: nounwind
|
||||||
|
// CHECK: }
|
33
src/test/codegen/unwind-abis/thiscall-unwind-abi.rs
Normal file
33
src/test/codegen/unwind-abis/thiscall-unwind-abi.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// compile-flags: -C opt-level=0
|
||||||
|
// ignore-arm thiscall isn't supported
|
||||||
|
// ignore-aarch64 thiscall isn't supported
|
||||||
|
// ignore-riscv64 thiscall isn't supported
|
||||||
|
|
||||||
|
// Test that `nounwind` atributes are correctly applied to exported `thiscall` and
|
||||||
|
// `thiscall-unwind` extern functions. `thiscall-unwind` functions MUST NOT have this attribute. We
|
||||||
|
// disable optimizations above to prevent LLVM from inferring the attribute.
|
||||||
|
|
||||||
|
#![crate_type = "lib"]
|
||||||
|
#![feature(abi_thiscall)]
|
||||||
|
#![feature(c_unwind)]
|
||||||
|
|
||||||
|
// CHECK: @rust_item_that_cannot_unwind() unnamed_addr #0 {
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "thiscall" fn rust_item_that_cannot_unwind() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: @rust_item_that_can_unwind() unnamed_addr #1 {
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "thiscall-unwind" fn rust_item_that_can_unwind() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, make some assertions that the LLVM attributes for these functions are correct. First, make
|
||||||
|
// sure that the first item is correctly marked with the `nounwind` attribute:
|
||||||
|
//
|
||||||
|
// CHECK: attributes #0 = { {{.*}}nounwind{{.*}} }
|
||||||
|
//
|
||||||
|
// Next, let's assert that the second item, which CAN unwind, does not have this attribute.
|
||||||
|
//
|
||||||
|
// CHECK: attributes #1 = {
|
||||||
|
// CHECK-NOT: nounwind
|
||||||
|
// CHECK: }
|
Loading…
Add table
Add a link
Reference in a new issue