Auto merge of #76570 - cratelyn:implement-rfc-2945-c-unwind-abi, r=Amanieu

Implement RFC 2945: "C-unwind" ABI

## Implement RFC 2945: "C-unwind" ABI

This branch implements [RFC 2945]. The tracking issue for this RFC is #74990.

The feature gate for the issue is `#![feature(c_unwind)]`.

This RFC was created as part of the ffi-unwind project group tracked at rust-lang/lang-team#19.

### Changes

Further details will be provided in commit messages, but a high-level overview
of the changes follows:

* A boolean `unwind` payload is added to the `C`, `System`, `Stdcall`,
and `Thiscall` variants, marking whether unwinding across FFI boundaries is
acceptable. The cases where each of these variants' `unwind` member is true
correspond with the `C-unwind`, `system-unwind`, `stdcall-unwind`, and
`thiscall-unwind` ABI strings introduced in RFC 2945 [3].

* This commit adds a `c_unwind` feature gate for the new ABI strings.
Tests for this feature gate are included in `src/test/ui/c-unwind/`, which
ensure that this feature gate works correctly for each of the new ABIs.
A new language features entry in the unstable book is added as well.

* 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.

[RFC 2945]: https://github.com/rust-lang/rfcs/blob/master/text/2945-c-unwind-abi.md
This commit is contained in:
bors 2021-03-10 16:44:04 +00:00
commit 17a07d71bf
43 changed files with 661 additions and 70 deletions

View File

@ -319,10 +319,10 @@ impl<'hir> LoweringContext<'_, 'hir> {
},
ItemKind::ForeignMod(ref fm) => {
if fm.abi.is_none() {
self.maybe_lint_missing_abi(span, id, abi::Abi::C);
self.maybe_lint_missing_abi(span, id, abi::Abi::C { unwind: false });
}
hir::ItemKind::ForeignMod {
abi: fm.abi.map_or(abi::Abi::C, |abi| self.lower_abi(abi)),
abi: fm.abi.map_or(abi::Abi::C { unwind: false }, |abi| self.lower_abi(abi)),
items: self
.arena
.alloc_from_iter(fm.items.iter().map(|x| self.lower_foreign_item_ref(x))),
@ -1334,8 +1334,8 @@ impl<'hir> LoweringContext<'_, 'hir> {
match ext {
Extern::None => abi::Abi::Rust,
Extern::Implicit => {
self.maybe_lint_missing_abi(span, id, abi::Abi::C);
abi::Abi::C
self.maybe_lint_missing_abi(span, id, abi::Abi::C { unwind: false });
abi::Abi::C { unwind: false }
}
Extern::Explicit(abi) => self.lower_abi(abi),
}

View File

@ -164,6 +164,38 @@ impl<'a> PostExpansionVisitor<'a> {
"C-cmse-nonsecure-call ABI is experimental and subject to change"
);
}
"C-unwind" => {
gate_feature_post!(
&self,
c_unwind,
span,
"C-unwind ABI is experimental and subject to change"
);
}
"stdcall-unwind" => {
gate_feature_post!(
&self,
c_unwind,
span,
"stdcall-unwind ABI is experimental and subject to change"
);
}
"system-unwind" => {
gate_feature_post!(
&self,
c_unwind,
span,
"system-unwind ABI is experimental and subject to change"
);
}
"thiscall-unwind" => {
gate_feature_post!(
&self,
c_unwind,
span,
"thiscall-unwind ABI is experimental and subject to change"
);
}
abi => self
.sess
.parse_sess

View File

@ -476,8 +476,11 @@ pub(crate) fn codegen_terminator_call<'tcx>(
// FIXME find a cleaner way to support varargs
if fn_sig.c_variadic {
if fn_sig.abi != Abi::C {
fx.tcx.sess.span_fatal(span, &format!("Variadic call for non-C abi {:?}", fn_sig.abi));
if !matches!(fn_sig.abi, Abi::C { .. }) {
fx.tcx.sess.span_fatal(
span,
&format!("Variadic call for non-C abi {:?}", fn_sig.abi),
);
}
let sig_ref = fx.bcx.func.dfg.call_signature(call_inst).unwrap();
let abi_params = call_args

View File

@ -641,6 +641,9 @@ declare_features! (
/// Allows associated types in inherent impls.
(active, inherent_associated_types, "1.52.0", Some(8995), None),
/// Allows `extern "C-unwind" fn` to enable unwinding across ABI boundaries.
(active, c_unwind, "1.52.0", Some(74990), None),
// -------------------------------------------------------------------------
// feature-group-end: actual feature gates
// -------------------------------------------------------------------------

View File

@ -2562,6 +2562,7 @@ fn fn_can_unwind(
panic_strategy: PanicStrategy,
codegen_fn_attr_flags: CodegenFnAttrFlags,
call_conv: Conv,
abi: SpecAbi,
) -> bool {
if panic_strategy != PanicStrategy::Unwind {
// In panic=abort mode we assume nothing can unwind anywhere, so
@ -2586,17 +2587,34 @@ fn fn_can_unwind(
//
// 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
// UB otherwise. (At least for now; see also
// rust-lang/rust#63909 and Rust RFC 2753.)
//
// Items defined in Rust with non-Rust ABIs (case 2) are also
// not supposed to unwind. Whether this should be enforced
// (versus stating it is UB) and *how* it would be enforced
// is currently under discussion; see rust-lang/rust#58794.
//
// In either case, we mark item as explicitly nounwind.
false
// In both of these cases, we should refer to the ABI to determine whether or not we
// should unwind. See Rust RFC 2945 for more information on this behavior, here:
// https://github.com/rust-lang/rfcs/blob/master/text/2945-c-unwind-abi.md
use SpecAbi::*;
match abi {
C { unwind } | Stdcall { unwind } | System { unwind } | Thiscall { unwind } => {
unwind
}
Cdecl
| Fastcall
| Vectorcall
| Aapcs
| Win64
| SysV64
| PtxKernel
| Msp430Interrupt
| X86Interrupt
| AmdGpuKernel
| EfiApi
| AvrInterrupt
| AvrNonBlockingInterrupt
| CCmseNonSecureCall
| RustIntrinsic
| PlatformIntrinsic
| Unadjusted => false,
// In the `if` above, we checked for functions with the Rust calling convention.
Rust | RustCall => unreachable!(),
}
}
}
}
@ -2654,14 +2672,14 @@ where
RustIntrinsic | PlatformIntrinsic | Rust | RustCall => Conv::Rust,
// It's the ABI's job to select this, not ours.
System => bug!("system abi should be selected elsewhere"),
System { .. } => bug!("system abi should be selected elsewhere"),
EfiApi => bug!("eficall abi should be selected elsewhere"),
Stdcall => Conv::X86Stdcall,
Stdcall { .. } => Conv::X86Stdcall,
Fastcall => Conv::X86Fastcall,
Vectorcall => Conv::X86VectorCall,
Thiscall => Conv::X86ThisCall,
C => Conv::C,
Thiscall { .. } => Conv::X86ThisCall,
C { .. } => Conv::C,
Unadjusted => Conv::C,
Win64 => Conv::X86_64Win64,
SysV64 => Conv::X86_64SysV,
@ -2823,7 +2841,12 @@ where
c_variadic: sig.c_variadic,
fixed_count: inputs.len(),
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);
debug!("FnAbi::new_internal = {:?}", fn_abi);

View File

@ -248,9 +248,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
};
if normalize_abi(caller_abi) != normalize_abi(callee_abi) {
throw_ub_format!(
"calling a function with ABI {:?} using caller ABI {:?}",
callee_abi,
caller_abi
"calling a function with ABI {} using caller ABI {}",
callee_abi.name(),
caller_abi.name()
)
}
}

View File

@ -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.
let attrs = &tcx.get_attrs(fn_def_id.to_def_id());
let unwind_attr = attr::find_unwind_attr(&tcx.sess, attrs);
@ -558,12 +558,42 @@ fn should_abort_on_panic(tcx: TyCtxt<'_>, fn_def_id: LocalDefId, _abi: Abi) -> b
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 {
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::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.
Cdecl
| Fastcall
| Vectorcall
| Aapcs
| Win64
| SysV64
| PtxKernel
| Msp430Interrupt
| X86Interrupt
| AmdGpuKernel
| EfiApi
| AvrInterrupt
| AvrNonBlockingInterrupt
| CCmseNonSecureCall
| RustIntrinsic
| PlatformIntrinsic
| Unadjusted => true,
}
}
}
}

View File

@ -330,6 +330,7 @@ symbols! {
bridge,
bswap,
c_str,
c_unwind,
c_variadic,
call,
call_mut,

View File

@ -440,7 +440,7 @@ impl Printer<'tcx> for SymbolMangler<'tcx> {
}
match sig.abi {
Abi::Rust => {}
Abi::C => cx.push("KC"),
Abi::C { unwind: false } => cx.push("KC"),
abi => {
cx.push("K");
let name = abi.name();

View File

@ -8,24 +8,21 @@ mod tests;
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug)]
#[derive(HashStable_Generic, Encodable, Decodable)]
pub enum Abi {
// N.B., this ordering MUST match the AbiDatas array below.
// (This is ensured by the test indices_are_correct().)
// Multiplatform / generic ABIs
//
// These ABIs come first because every time we add a new ABI, we
// have to re-bless all the hashing tests. These are used in many
// places, so giving them stable values reduces test churn. The
// specific values are meaningless.
Rust = 0,
C = 1,
Rust,
C { unwind: bool },
// Single platform ABIs
Cdecl,
Stdcall,
Stdcall { unwind: bool },
Fastcall,
Vectorcall,
Thiscall,
Thiscall { unwind: bool },
Aapcs,
Win64,
SysV64,
@ -39,7 +36,7 @@ pub enum Abi {
CCmseNonSecureCall,
// Multiplatform / generic ABIs
System,
System { unwind: bool },
RustIntrinsic,
RustCall,
PlatformIntrinsic,
@ -61,13 +58,16 @@ pub struct AbiData {
const AbiDatas: &[AbiData] = &[
// Cross-platform ABIs
AbiData { abi: Abi::Rust, name: "Rust", generic: true },
AbiData { abi: Abi::C, name: "C", generic: true },
AbiData { abi: Abi::C { unwind: false }, name: "C", generic: true },
AbiData { abi: Abi::C { unwind: true }, name: "C-unwind", generic: true },
// Platform-specific ABIs
AbiData { abi: Abi::Cdecl, name: "cdecl", generic: false },
AbiData { abi: Abi::Stdcall, name: "stdcall", generic: false },
AbiData { abi: Abi::Stdcall { unwind: false }, name: "stdcall", generic: false },
AbiData { abi: Abi::Stdcall { unwind: true }, name: "stdcall-unwind", generic: false },
AbiData { abi: Abi::Fastcall, name: "fastcall", generic: false },
AbiData { abi: Abi::Vectorcall, name: "vectorcall", generic: false },
AbiData { abi: Abi::Thiscall, name: "thiscall", generic: false },
AbiData { abi: Abi::Thiscall { unwind: false }, name: "thiscall", generic: false },
AbiData { abi: Abi::Thiscall { unwind: true }, name: "thiscall-unwind", generic: false },
AbiData { abi: Abi::Aapcs, name: "aapcs", generic: false },
AbiData { abi: Abi::Win64, name: "win64", generic: false },
AbiData { abi: Abi::SysV64, name: "sysv64", generic: false },
@ -84,7 +84,8 @@ const AbiDatas: &[AbiData] = &[
},
AbiData { abi: Abi::CCmseNonSecureCall, name: "C-cmse-nonsecure-call", generic: false },
// Cross-platform ABIs
AbiData { abi: Abi::System, name: "system", generic: true },
AbiData { abi: Abi::System { unwind: false }, name: "system", generic: true },
AbiData { abi: Abi::System { unwind: true }, name: "system-unwind", generic: true },
AbiData { abi: Abi::RustIntrinsic, name: "rust-intrinsic", generic: true },
AbiData { abi: Abi::RustCall, name: "rust-call", generic: true },
AbiData { abi: Abi::PlatformIntrinsic, name: "platform-intrinsic", generic: true },
@ -103,7 +104,52 @@ pub fn all_names() -> Vec<&'static str> {
impl Abi {
#[inline]
pub fn index(self) -> usize {
self as usize
// N.B., this ordering MUST match the AbiDatas array above.
// (This is ensured by the test indices_are_correct().)
use Abi::*;
let i = match self {
// Cross-platform ABIs
Rust => 0,
C { unwind: false } => 1,
C { unwind: true } => 2,
// Platform-specific ABIs
Cdecl => 3,
Stdcall { unwind: false } => 4,
Stdcall { unwind: true } => 5,
Fastcall => 6,
Vectorcall => 7,
Thiscall { unwind: false } => 8,
Thiscall { unwind: true } => 9,
Aapcs => 10,
Win64 => 11,
SysV64 => 12,
PtxKernel => 13,
Msp430Interrupt => 14,
X86Interrupt => 15,
AmdGpuKernel => 16,
EfiApi => 17,
AvrInterrupt => 18,
AvrNonBlockingInterrupt => 19,
CCmseNonSecureCall => 20,
// Cross-platform ABIs
System { unwind: false } => 21,
System { unwind: true } => 22,
RustIntrinsic => 23,
RustCall => 24,
PlatformIntrinsic => 25,
Unadjusted => 26,
};
debug_assert!(
AbiDatas
.iter()
.enumerate()
.find(|(_, AbiData { abi, .. })| *abi == self)
.map(|(index, _)| index)
.expect("abi variant has associated data")
== i,
"Abi index did not match `AbiDatas` ordering"
);
i
}
#[inline]
@ -122,6 +168,8 @@ impl Abi {
impl fmt::Display for Abi {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\"{}\"", self.name())
match self {
abi => write!(f, "\"{}\"", abi.name()),
}
}
}

View File

@ -2,5 +2,14 @@ use crate::spec::abi::Abi;
// All the calling conventions trigger an assertion(Unsupported calling convention) in llvm on arm
pub fn unsupported_abis() -> Vec<Abi> {
vec![Abi::Stdcall, Abi::Fastcall, Abi::Vectorcall, Abi::Thiscall, Abi::Win64, Abi::SysV64]
vec![
Abi::Stdcall { unwind: false },
Abi::Stdcall { unwind: true },
Abi::Fastcall,
Abi::Vectorcall,
Abi::Thiscall { unwind: false },
Abi::Thiscall { unwind: true },
Abi::Win64,
Abi::SysV64,
]
}

View File

@ -23,10 +23,12 @@ pub fn target() -> Target {
panic_strategy: PanicStrategy::Abort,
relocation_model: RelocModel::Static,
unsupported_abis: vec![
Abi::Stdcall,
Abi::Stdcall { unwind: false },
Abi::Stdcall { unwind: true },
Abi::Fastcall,
Abi::Vectorcall,
Abi::Thiscall,
Abi::Thiscall { unwind: false },
Abi::Thiscall { unwind: true },
Abi::Win64,
Abi::SysV64,
],

View File

@ -1284,24 +1284,31 @@ impl Target {
/// Given a function ABI, turn it into the correct ABI for this target.
pub fn adjust_abi(&self, abi: Abi) -> Abi {
match abi {
Abi::System => {
Abi::System { unwind } => {
if self.is_like_windows && self.arch == "x86" {
Abi::Stdcall
Abi::Stdcall { unwind }
} else {
Abi::C
Abi::C { unwind }
}
}
// These ABI kinds are ignored on non-x86 Windows targets.
// See https://docs.microsoft.com/en-us/cpp/cpp/argument-passing-and-naming-conventions
// and the individual pages for __stdcall et al.
Abi::Stdcall | Abi::Fastcall | Abi::Vectorcall | Abi::Thiscall => {
if self.is_like_windows && self.arch != "x86" { Abi::C } else { abi }
Abi::Stdcall { unwind } | Abi::Thiscall { unwind } => {
if self.is_like_windows && self.arch != "x86" { Abi::C { unwind } } else { abi }
}
Abi::Fastcall | Abi::Vectorcall => {
if self.is_like_windows && self.arch != "x86" {
Abi::C { unwind: false }
} else {
abi
}
}
Abi::EfiApi => {
if self.arch == "x86_64" {
Abi::Win64
} else {
Abi::C
Abi::C { unwind: false }
}
}
abi => abi,

View File

@ -49,10 +49,12 @@ pub fn target() -> Target {
// create the tests for this.
unsupported_abis: vec![
Abi::Cdecl,
Abi::Stdcall,
Abi::Stdcall { unwind: false },
Abi::Stdcall { unwind: true },
Abi::Fastcall,
Abi::Vectorcall,
Abi::Thiscall,
Abi::Thiscall { unwind: false },
Abi::Thiscall { unwind: true },
Abi::Aapcs,
Abi::Win64,
Abi::SysV64,

View File

@ -5,10 +5,12 @@ use crate::spec::abi::Abi;
pub fn unsupported_abis() -> Vec<Abi> {
vec![
Abi::Cdecl,
Abi::Stdcall,
Abi::Stdcall { unwind: false },
Abi::Stdcall { unwind: true },
Abi::Fastcall,
Abi::Vectorcall,
Abi::Thiscall,
Abi::Thiscall { unwind: false },
Abi::Thiscall { unwind: true },
Abi::Aapcs,
Abi::Win64,
Abi::SysV64,

View File

@ -2666,7 +2666,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
} else if tcx.sess.check_name(attr, sym::used) {
codegen_fn_attrs.flags |= CodegenFnAttrFlags::USED;
} else if tcx.sess.check_name(attr, sym::cmse_nonsecure_entry) {
if tcx.fn_sig(id).abi() != abi::Abi::C {
if !matches!(tcx.fn_sig(id).abi(), abi::Abi::C { .. }) {
struct_span_err!(
tcx.sess,
attr.span,

View File

@ -118,14 +118,19 @@ use astconv::AstConv;
use bounds::Bounds;
fn require_c_abi_if_c_variadic(tcx: TyCtxt<'_>, decl: &hir::FnDecl<'_>, abi: Abi, span: Span) {
if decl.c_variadic && !(abi == Abi::C || abi == Abi::Cdecl) {
let mut err = struct_span_err!(
tcx.sess,
span,
E0045,
"C-variadic function must have C or cdecl calling convention"
);
err.span_label(span, "C-variadics require C or cdecl calling convention").emit();
match (decl.c_variadic, abi) {
// The function has the correct calling convention, or isn't a "C-variadic" function.
(false, _) | (true, Abi::C { .. }) | (true, Abi::Cdecl) => {}
// The function is a "C-variadic" function with an incorrect calling convention.
(true, _) => {
let mut err = struct_span_err!(
tcx.sess,
span,
E0045,
"C-variadic function must have C or cdecl calling convention"
);
err.span_label(span, "C-variadics require C or cdecl calling convention").emit();
}
}
}

View File

@ -0,0 +1,15 @@
# `c_unwind`
The tracking issue for this feature is: [#74990]
[#74990]: https://github.com/rust-lang/rust/issues/74990
------------------------
Introduces four new ABI strings: "C-unwind", "stdcall-unwind",
"thiscall-unwind", and "system-unwind". These enable unwinding from other
languages (such as C++) into Rust frames and from Rust into other languages.
See [RFC 2945] for more information.
[RFC 2945]: https://github.com/rust-lang/rfcs/blob/master/text/2945-c-unwind-abi.md

View 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{{.*}} }

View 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: }

View 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: }

View 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: }

View 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: }

View File

@ -0,0 +1,30 @@
-include ../tools.mk
all: archive
# Compile `main.rs`, which will link into our library, and run it.
$(RUSTC) main.rs
$(call RUN,main)
ifdef IS_MSVC
archive: add.o panic.o
# Now, create an archive using these two objects.
$(AR) crus $(TMPDIR)/add.lib $(TMPDIR)/add.o $(TMPDIR)/panic.o
else
archive: add.o panic.o
# Now, create an archive using these two objects.
$(AR) crus $(TMPDIR)/libadd.a $(TMPDIR)/add.o $(TMPDIR)/panic.o
endif
# Compile `panic.rs` into an object file.
#
# Note that we invoke `rustc` directly, so we may emit an object rather
# than an archive. We'll do that later.
panic.o:
$(BARE_RUSTC) $(RUSTFLAGS) \
--out-dir $(TMPDIR) \
--emit=obj panic.rs
# Compile `add.c` into an object file.
add.o:
$(call COMPILE_OBJ,$(TMPDIR)/add.o,add.c)

View File

@ -0,0 +1,12 @@
#ifdef _WIN32
__declspec(dllexport)
#endif
// An external function, defined in Rust.
extern void panic_if_greater_than_10(unsigned x);
unsigned add_small_numbers(unsigned a, unsigned b) {
unsigned c = a + b;
panic_if_greater_than_10(c);
return c;
}

View File

@ -0,0 +1,35 @@
//! A test for calling `C-unwind` functions across foreign function boundaries.
//!
//! This test triggers a panic in a Rust library that our foreign function invokes. This shows
//! that we can unwind through the C code in that library, and catch the underlying panic.
#![feature(c_unwind)]
use std::panic::{catch_unwind, AssertUnwindSafe};
fn main() {
// Call `add_small_numbers`, passing arguments that will NOT trigger a panic.
let (a, b) = (9, 1);
let c = unsafe { add_small_numbers(a, b) };
assert_eq!(c, 10);
// Call `add_small_numbers`, passing arguments that will trigger a panic, and catch it.
let caught_unwind = catch_unwind(AssertUnwindSafe(|| {
let (a, b) = (10, 1);
let _c = unsafe { add_small_numbers(a, b) };
unreachable!("should have unwound instead of returned");
}));
// Assert that we did indeed panic, then unwrap and downcast the panic into the sum.
assert!(caught_unwind.is_err());
let panic_obj = caught_unwind.unwrap_err();
let msg = panic_obj.downcast_ref::<String>().unwrap();
assert_eq!(msg, "11");
}
#[link(name = "add", kind = "static")]
extern "C-unwind" {
/// An external function, defined in C.
///
/// Returns the sum of two numbers, or panics if the sum is greater than 10.
fn add_small_numbers(a: u32, b: u32) -> u32;
}

View File

@ -0,0 +1,12 @@
#![crate_type = "staticlib"]
#![feature(c_unwind)]
/// This function will panic if `x` is greater than 10.
///
/// This function is called by `add_small_numbers`.
#[no_mangle]
pub extern "C-unwind" fn panic_if_greater_than_10(x: u32) {
if x > 10 {
panic!("{}", x); // That is too big!
}
}

View File

@ -0,0 +1,5 @@
-include ../tools.mk
all: $(call NATIVE_STATICLIB,add)
$(RUSTC) main.rs
$(call RUN,main) || exit 1

View File

@ -0,0 +1,12 @@
#ifdef _WIN32
__declspec(dllexport)
#endif
// An external function, defined in Rust.
extern void panic_if_greater_than_10(unsigned x);
unsigned add_small_numbers(unsigned a, unsigned b) {
unsigned c = a + b;
panic_if_greater_than_10(c);
return c;
}

View File

@ -0,0 +1,44 @@
//! A test for calling `C-unwind` functions across foreign function boundaries.
//!
//! This test triggers a panic when calling a foreign function that calls *back* into Rust.
#![feature(c_unwind)]
use std::panic::{catch_unwind, AssertUnwindSafe};
fn main() {
// Call `add_small_numbers`, passing arguments that will NOT trigger a panic.
let (a, b) = (9, 1);
let c = unsafe { add_small_numbers(a, b) };
assert_eq!(c, 10);
// Call `add_small_numbers`, passing arguments that will trigger a panic, and catch it.
let caught_unwind = catch_unwind(AssertUnwindSafe(|| {
let (a, b) = (10, 1);
let _c = unsafe { add_small_numbers(a, b) };
unreachable!("should have unwound instead of returned");
}));
// Assert that we did indeed panic, then unwrap and downcast the panic into the sum.
assert!(caught_unwind.is_err());
let panic_obj = caught_unwind.unwrap_err();
let msg = panic_obj.downcast_ref::<String>().unwrap();
assert_eq!(msg, "11");
}
#[link(name = "add", kind = "static")]
extern "C-unwind" {
/// An external function, defined in C.
///
/// Returns the sum of two numbers, or panics if the sum is greater than 10.
fn add_small_numbers(a: u32, b: u32) -> u32;
}
/// This function will panic if `x` is greater than 10.
///
/// This function is called by `add_small_numbers`.
#[no_mangle]
pub extern "C-unwind" fn panic_if_greater_than_10(x: u32) {
if x > 10 {
panic!("{}", x); // That is too big!
}
}

View File

@ -7,7 +7,7 @@
// compile-flags:-C panic=abort
// aux-build:helper.rs
#![feature(start, rustc_private, new_uninit, panic_info_message)]
#![feature(start, rustc_private, new_uninit, panic_info_message, lang_items)]
#![feature(alloc_error_handler)]
#![no_std]
@ -84,6 +84,13 @@ fn panic(panic_info: &core::panic::PanicInfo) -> ! {
}
}
// Because we are compiling this code with `-C panic=abort`, this wouldn't normally be needed.
// However, `core` and `alloc` are both compiled with `-C panic=unwind`, which means that functions
// in these libaries will refer to `rust_eh_personality` if LLVM can not *prove* the contents won't
// unwind. So, for this test case we will define the symbol.
#[lang = "eh_personality"]
extern fn rust_eh_personality() {}
#[derive(Debug)]
struct Page([[u64; 32]; 16]);

View File

@ -8,7 +8,7 @@
// aux-build:helper.rs
// gate-test-default_alloc_error_handler
#![feature(start, rustc_private, new_uninit, panic_info_message)]
#![feature(start, rustc_private, new_uninit, panic_info_message, lang_items)]
#![feature(default_alloc_error_handler)]
#![no_std]
@ -71,6 +71,13 @@ fn panic(panic_info: &core::panic::PanicInfo) -> ! {
}
}
// Because we are compiling this code with `-C panic=abort`, this wouldn't normally be needed.
// However, `core` and `alloc` are both compiled with `-C panic=unwind`, which means that functions
// in these libaries will refer to `rust_eh_personality` if LLVM can not *prove* the contents won't
// unwind. So, for this test case we will define the symbol.
#[lang = "eh_personality"]
extern fn rust_eh_personality() {}
#[derive(Debug)]
struct Page([[u64; 32]; 16]);

View File

@ -4,7 +4,7 @@ error[E0703]: invalid ABI: found `路濫狼á́́`
LL | extern "路濫狼á́́" fn foo() {}
| ^^^^^^^^^ invalid ABI
|
= help: valid ABIs: Rust, C, cdecl, stdcall, fastcall, vectorcall, thiscall, aapcs, win64, sysv64, ptx-kernel, msp430-interrupt, x86-interrupt, amdgpu-kernel, efiapi, avr-interrupt, avr-non-blocking-interrupt, C-cmse-nonsecure-call, system, rust-intrinsic, rust-call, platform-intrinsic, unadjusted
= help: valid ABIs: Rust, C, C-unwind, cdecl, stdcall, stdcall-unwind, fastcall, vectorcall, thiscall, thiscall-unwind, aapcs, win64, sysv64, ptx-kernel, msp430-interrupt, x86-interrupt, amdgpu-kernel, efiapi, avr-interrupt, avr-non-blocking-interrupt, C-cmse-nonsecure-call, system, system-unwind, rust-intrinsic, rust-call, platform-intrinsic, unadjusted
error: aborting due to previous error

View File

@ -4,7 +4,7 @@ error[E0703]: invalid ABI: found `invalid-ab_isize`
LL | "invalid-ab_isize"
| ^^^^^^^^^^^^^^^^^^ invalid ABI
|
= help: valid ABIs: Rust, C, cdecl, stdcall, fastcall, vectorcall, thiscall, aapcs, win64, sysv64, ptx-kernel, msp430-interrupt, x86-interrupt, amdgpu-kernel, efiapi, avr-interrupt, avr-non-blocking-interrupt, C-cmse-nonsecure-call, system, rust-intrinsic, rust-call, platform-intrinsic, unadjusted
= help: valid ABIs: Rust, C, C-unwind, cdecl, stdcall, stdcall-unwind, fastcall, vectorcall, thiscall, thiscall-unwind, aapcs, win64, sysv64, ptx-kernel, msp430-interrupt, x86-interrupt, amdgpu-kernel, efiapi, avr-interrupt, avr-non-blocking-interrupt, C-cmse-nonsecure-call, system, system-unwind, rust-intrinsic, rust-call, platform-intrinsic, unadjusted
error: aborting due to previous error

View File

@ -0,0 +1,12 @@
// Test that the "C-unwind" ABI is feature-gated, and *can* be used when the
// `c_unwind` feature gate is enabled.
// check-pass
#![feature(c_unwind)]
extern "C-unwind" fn f() {}
fn main() {
f();
}

View File

@ -0,0 +1,9 @@
// Test that the "C-unwind" ABI is feature-gated, and cannot be used when the
// `c_unwind` feature gate is not used.
extern "C-unwind" fn f() {}
//~^ ERROR C-unwind ABI is experimental and subject to change [E0658]
fn main() {
f();
}

View File

@ -0,0 +1,12 @@
error[E0658]: C-unwind ABI is experimental and subject to change
--> $DIR/feature-gate-c-unwind.rs:4:8
|
LL | extern "C-unwind" fn f() {}
| ^^^^^^^^^^
|
= note: see issue #74990 <https://github.com/rust-lang/rust/issues/74990> for more information
= help: add `#![feature(c_unwind)]` to the crate attributes to enable
error: aborting due to previous error
For more information about this error, try `rustc --explain E0658`.

View File

@ -0,0 +1,13 @@
// ignore-arm stdcall isn't supported
// ignore-aarch64 stdcall isn't supported
// ignore-riscv64 stdcall isn't supported
// Test that the "stdcall-unwind" ABI is feature-gated, and cannot be used when
// the `c_unwind` feature gate is not used.
extern "stdcall-unwind" fn f() {}
//~^ ERROR stdcall-unwind ABI is experimental and subject to change [E0658]
fn main() {
f();
}

View File

@ -0,0 +1,12 @@
error[E0658]: stdcall-unwind ABI is experimental and subject to change
--> $DIR/feature-gate-stdcall-unwind.rs:8:8
|
LL | extern "stdcall-unwind" fn f() {}
| ^^^^^^^^^^^^^^^^
|
= note: see issue #74990 <https://github.com/rust-lang/rust/issues/74990> for more information
= help: add `#![feature(c_unwind)]` to the crate attributes to enable
error: aborting due to previous error
For more information about this error, try `rustc --explain E0658`.

View File

@ -0,0 +1,9 @@
// Test that the "system-unwind" ABI is feature-gated, and cannot be used when
// the `c_unwind` feature gate is not used.
extern "system-unwind" fn f() {}
//~^ ERROR system-unwind ABI is experimental and subject to change [E0658]
fn main() {
f();
}

View File

@ -0,0 +1,12 @@
error[E0658]: system-unwind ABI is experimental and subject to change
--> $DIR/feature-gate-system-unwind.rs:4:8
|
LL | extern "system-unwind" fn f() {}
| ^^^^^^^^^^^^^^^
|
= note: see issue #74990 <https://github.com/rust-lang/rust/issues/74990> for more information
= help: add `#![feature(c_unwind)]` to the crate attributes to enable
error: aborting due to previous error
For more information about this error, try `rustc --explain E0658`.

View File

@ -0,0 +1,13 @@
// ignore-arm thiscall isn't supported
// ignore-aarch64 thiscall isn't supported
// ignore-riscv64 thiscall isn't supported
// Test that the "thiscall-unwind" ABI is feature-gated, and cannot be used when
// the `c_unwind` feature gate is not used.
extern "thiscall-unwind" fn f() {}
//~^ ERROR thiscall-unwind ABI is experimental and subject to change [E0658]
fn main() {
f();
}

View File

@ -0,0 +1,12 @@
error[E0658]: thiscall-unwind ABI is experimental and subject to change
--> $DIR/feature-gate-thiscall-unwind.rs:8:8
|
LL | extern "thiscall-unwind" fn f() {}
| ^^^^^^^^^^^^^^^^^
|
= note: see issue #74990 <https://github.com/rust-lang/rust/issues/74990> for more information
= help: add `#![feature(c_unwind)]` to the crate attributes to enable
error: aborting due to previous error
For more information about this error, try `rustc --explain E0658`.