From 3c2d2ad2a859ff8913845eafcd48b9ba6174e7ef Mon Sep 17 00:00:00 2001 From: Kyle Strand Date: Tue, 16 Jun 2020 09:38:12 -0600 Subject: [PATCH 01/11] 'C unwind' ABI - drafted by FFI Unwind project group --- text/0000-c-unwind-abi.md | 475 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 475 insertions(+) create mode 100644 text/0000-c-unwind-abi.md diff --git a/text/0000-c-unwind-abi.md b/text/0000-c-unwind-abi.md new file mode 100644 index 00000000000..a6b874a641d --- /dev/null +++ b/text/0000-c-unwind-abi.md @@ -0,0 +1,475 @@ +- Feature Name: `"C unwind" ABI` +- Start Date: 2019-04-03 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) +- Project group: [FFI-unwind][project-group] + +[project-group]: https://github.com/rust-lang/project-ffi-unwind + +# Summary +[summary]: #summary + +We introduce a new ABI string, `"C unwind"`, to enable unwinding from other +languages (such as C++) into Rust frames and from Rust into other languages. + +Additionally, we define the behavior for a limited number of +previously-undefined cases when an unwind operation reaches a Rust function +boundary with a non-`"Rust"`, non-`"C unwind"` ABI. + +As part of this specification, we introduce the term ["Plain Old Frame" +(POF)][POF-definition]. These are frames that have no pending destructors and +can be trivially deallocated. + +This RFC does not define the behavior of `catch_unwind` in a Rust frame being +unwound by a foreign exception. This is something the [project +group][project-group] would like to specify in a future RFC; as such, it is +"TBD" (see ["Unresolved questions"][unresolved-questions]). + +# Motivation +[motivation]: #motivation + +There are some Rust projects that need cross-language unwinding to provide +their desired functionality. One major example is Wasm interpreters, including +the Lucet and Wasmer projects. + +There are also existing Rust crates (notably, wrappers around the `libpng` and +`libjpeg` C libraries) that `panic` across C frames. The safety of such +unwinding relies on compatibility between Rust's unwinding mechanism and the +native exception mechanisms in GCC, LLVM, and MSVC. Despite using a compatible +unwinding mechanism, the current `rustc` implementation assumes that `extern +"C"` functions cannot unwind, which permits LLVM to optimize with the +assumption that such unwinding constitutes undefined behavior. + +The desire for this feature has been previously discussed on other RFCs, +including [#2699][rfc-2699] and [#2753][rfc-2753]. + +## Key design goals + +As explained in [this Inside Rust blog post][inside-rust-requirements], we have +several requirements for any cross-language unwinding design. + +The ["Analysis of key design goals"][analysis-of-design-goals] section analyzes +how well the current design satisfies these constraints. + +* **Changing from `panic=unwind` to `panic=abort` cannot cause undefined + behavior:** We wish to ensure that changing from `panic=unwind` to + `panic=abort` never creates undefined behavior (relate to `panic=unwind`), + even if one is relying on a library that triggers a panic or a foreign + exception. +* **Optimization with `panic=abort`:** when using `panic=abort`, we + wish to enable as many code-size optimizations as possible. This + means that we shouldn't have to generate unwinding tables or other + such constructs, at least in most cases. +* **Preserve the ability to change how Rust panics are propagated when + using the Rust ABI:** Currently, Rust panics are propagated using + the native unwinding mechanism, but we would like to keep the + freedom to change that. +* **Enable Rust panics to traverse through foreign frames:** Several + projects would like the ability to have Rust panics propagate + through foreign frames. Those frames may or may not register + destructors of their own with the native unwinding mechanism. +* **Enable foreign exceptions to propagate through Rust frames:** + Similarly, we would like to make it possible for C++ code (or other + languages) to raise exceptions that will propagate through Rust + frames "as if" they were Rust panics (i.e., running destrutors or, + in the case of `unwind=abort`, aborting the program). +* **Enable error handling with `longjmp`:** + As mentioned above, some existing Rust libraries rely on the ability to + `longjmp` across Rust frames to interoperate with Ruby, Lua, and other C + APIs. The behavior of `longjmp` traversing Rust frames is not specified or + guaranteed to be safe; in the current implementation of `rustc`, + however, it [is safe][longjmp-pr]. On Windows, `longjmp` is implemented as a + form of unwinding called ["forced unwinding"][forced-unwinding], so any + specification of the behavior of forced unwinding across FFI boundaries + should be forward-compatible with a [future RFC][unresolved-questions] that + will provide a well-defined way to interoperate with longjmp-based APIs. +* **Do not change the ABI of functions in the `libc` crate:** Some `libc` + functions may invoke `pthread_exit`, which uses [a form of + unwinding][forced-unwinding] in the GNU libc implementation. Such functions + must be safe to use with the existing `"C"` ABI, because changing the types + of these functions would be a breaking change. + +[inside-rust-requirements]: https://blog.rust-lang.org/inside-rust/2020/02/27/ffi-unwind-design-meeting.html#requirements-for-any-cross-language-unwinding-specification +[longjmp-pr]: https://github.com/rust-lang/rust/pull/48572 + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Functions declared with `extern "C"` are generally not permitted to unwind +(with some very narrow exceptions, described [below][forced-unwinding]). +This means, for example, that such a function cannot throw an uncaught C++ +exception, and it also cannot invoke Rust code that may panic (unless that +panic is caught with `catch_unwind`). + +When declaring an external function that may unwind, such as an entrypoint to a +C++ library, use `"C unwind"` instead: + +``` +extern "C unwind" { + fn may_throw(); +} +``` + +Rust functions that call a possibly-unwinding external function should either +use the default Rust ABI (which can be made explicit with `extern "Rust"`) or +the `"C unwind"` ABI: + +``` +extern "C unwind" fn can_unwind() { + may_throw(); +} +``` + +Using the `"C unwind"` ABI to "sandwich" Rust frames between frames from +another language (such as C++) allows an exception initiated in a callee frame +in the other language to traverse the intermediate Rust frames before being +caught in the caller frames. I.e., a C++ exception may be thrown, +cross into Rust via an `extern "C unwind"` function declaration, safely unwind +the Rust frames, and cross back into C++ (where it may be caught) via a Rust +`"C unwind"` function definition. + +Conversely, languages that support the native unwinding mechanism, such as C++, +may be "sandwiched" between Rust frames, so that Rust `panic`s may safely +unwind the C++ frames, if the Rust code declares both the C++ entrypoint and +the Rust entrypoint using `"C unwind"`. + +## "Plain Old Frames" +[POF-definition]: #plain-old-frames + +A "POF", or "Plain Old Frame", is defined as a frame that can be trivially +deallocated: returning from or unwinding a POF cannot cause any +observable effects. This means that POFs do not contain any pending destructors +(live `Drop` objects) or `catch_unwind` calls. + +The terminology is intentionally akin to [C++'s "Plain Old Data" +types][cpp-POD-definition], which are types that, among other requirements, are +trivially destructible (their destructors do not cause any observable effects, +and may be elided as an optimization). + +Rust frames that do contain pending destructors or `catch_unwind` calls are +called non-POFs. + +Note that a non-POF may _become_ a POF, for instance if all `Drop` objects are +moved out of scope, or if its only `catch_unwind` call is in a code path that +will not be executed. The next section provides an example. + +[cpp-POD-definition]: https://en.cppreference.com/w/cpp/named_req/PODType + +## Forced unwinding +[forced-unwinding]: #forced-unwinding + +This is a special kind of unwinding used to implement `longjmp` on Windows and +`pthread_exit` in `glibc`. A brief explanation is provided in [this Inside Rust +blog post][inside-rust-forced]. This RFC distinguishes forced unwinding from +other types of foreign unwinding. + +Since language features and library functions implemented using forced +unwinding on some platforms use other mechanisms on other platforms, Rust code +cannot rely on forced unwinding to invoke destructors (calling `drop` on `Drop` +types). In other words, a forced unwind operation on one platform will simply +deallocate Rust frames without true unwinding on other platforms. + +This RFC specifies that, regardless of the platform or the ABI string (`"C"` or +`"C unwind"`), any platform features that may rely on forced unwinding are +undefined behavior if they cross non-[POFs][POF-definition]. For now, however, +we do not specify the conditions required to use forced unwinding safely; we +will specify this in [a future RFC][unresolved-questions]. + +[inside-rust-forced]: https://blog.rust-lang.org/inside-rust/2020/02/27/ffi-unwind-design-meeting.html#forced-unwinding + +## Changes to `extern "C"` behavior +[extern-c-behavior]: #changes-to-extern-c-behavior + +Prior to this RFC, any unwinding operation that crossed an `extern "C"` +boundary, either from a `panic!` "escaping" from a Rust function defined with +`extern "C"` or by entering Rust from another language via an entrypoint +declared with `extern "C"`, caused undefined behavior. + +This RFC retains most of that undefined behavior, with one exception: with the +`panic=unwind` runtime, `panic!` will cause an `abort` if it would otherwise +"escape" from a function defined with `extern "C"`. + +## Interaction with `panic=abort` + +If a non-forced foreign unwind would enter a Rust frame via an `extern "C +unwind"` ABI boundary, but the Rust code is compiled with `panic=abort`, the +unwind will be caught and the process aborted. + +With the exception of the above case, however, unwinding from another language +into Rust through an FFI entrypoint declared with `extern "C"` is always +undefined behavior, and is not guaranteed to cause the program to abort under +`panic=abort`. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +This table shows the behavior of an unwinding operation reaching each type of +ABI boundary (function declaration or definition). "UB" stands for undefined +behavior. `"C"`-like ABIs are `"C"` itself but also related ABIs such as +`"system"`. + +| panic runtime | ABI | `panic`-unwind | Unforced foreign unwind | +| -------------- | ------------ | ------------------------------------- | ----------------------- | +| `panic=unwind` | `"C unwind"` | unwind | unwind | +| `panic=unwind` | `"C"`-like | abort | UB | +| `panic=abort` | `"C unwind"` | `panic!` aborts | abort | +| `panic=abort` | `"C"`-like | `panic!` aborts (no unwinding occurs) | UB | + +The interaction of Rust frames with C functions that deallocate frames (i.e. +functions that may use forced unwinding on specific platforms) is independent +of the panic runtime, ABI, or platform. + +* **When deallocating Rust non-POFs:** this is explicitly undefined behavior. +* **When deallocating Rust [POFs][POF-definition]:** for now, this is not + specified, and must be considered undefined behavior. However, we do plan to + specify a safe way to deallocate POFs with `longjmp` or `pthread_exit` in [a + future RFC][unresolved-questions]. + +No subtype relationship is defined between functions or function pointers using +different ABIs. This RFC also does not define coercions between `"C"` and +`"C unwind"`. + +As noted in the [summary][summary], if a Rust frame containing a pending +`catch_unwind` call is unwound by a foreign exception, the behavior is +undefined for now. + +# Drawbacks +[drawbacks]: #drawbacks + +Forced unwinding is treated as universally unsafe across +[non-POFs][POF-definition], but on some platforms it could theoretically be +well-defined. As noted [above](forced-unwind), however, this would make the UB +inconsistent across platforms, which is not desirable. + +This design imposes some burden on existing codebases (mentioned +[above][motivation]) to change their `extern` annotations to use the new ABI. + +Having separate ABIs for `"C"` and `"C unwind"` may make interface design more +difficult, especially since this RFC [postpones][unresolved-questions] +introducing coercions between function types using different ABIs. + +A single ABI that "just works" with C++ (or any other language that may throw +exceptions) would be simpler to learn and use than two separate ABIs. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +## Other proposals discussed with the lang team +[alternatives]: #other-proposals-discussed-with-the-lang-team + +Two other potential designs have been discussed in depth; they are +explained in [this Inside Rust blog post][inside-rust-proposals]. The design in this +RFC is referred to as "option 2" in that post. + +"Option 1" in that blog post only differs from the current proposal in the +behavior of a forced unwind across a `"C unwind"` boundary under `panic=abort`. +Under the current proposal, this type of unwind is permitted, allowing +`longjmp` and `pthread_exit` to behave "normally" with both the `"C"` and the +`"C unwind"` ABI across all platforms regardless of panic runtime. If +[non-POFs][POF-definition] are unwound, this results in undefined behavior. +Under "option 1", however, all foreign unwinding, forced or unforced, is caught +at `"C unwind"` boundaries under `panic=abort`, and the process is aborted. +This gives `longjmp` and `pthread_exit` surprising behavior on some platforms, +but avoids that cause of undefined behavior in the current proposal. + +The other proposal in the blog post, "option 3", is dramatically different. In +that proposal, foreign exceptions are permitted to cross `extern "C"` +boundaries, and no new ABI is introduced. + +[inside-rust-proposals]: https://blog.rust-lang.org/inside-rust/2020/02/27/ffi-unwind-design-meeting.html#three-specific-proposals + +## Reasons for the current proposal +[rationale]: #reasons-for-the-current-proposal + +Our reasons for preferring the current proposal are: + +* Introducing a new ABI makes reliance on cross-language exception handling + more explicit. +* `panic=abort` can be safely used with `extern "C unwind"` (there is no + undefined behavior except with improperly used forced unwinding), but `extern + "C"` has more optimization potential (eliding landing pads). Having two ABIs + puts this choice in the hands of users. + * The single-ABI proposal ("option 3") causes any foreign exception entering + Rust to have undefined behavior under `panic=abort`, whereas the current + proposal does not permit the `panic=abort` runtime to introduce undefined + behavior to a program that is well-defined under `panic=unwind`. + * This optimization could be made available with a single ABI by means of a + function attribute indicating that a function cannot unwind (similar to C++'s + `noexcept`). Such attributes [are already available in nightly + Rust][nightly-attributes]. However, Rust does not yet support attributes + for function pointers, so until that feature is added, there would be no + way to indicate whether function pointers unwind using an attribute. +* This design has simpler forward compatibility with alternate `panic!` + implementations. Any well-defined cross-language unwinding will require shims + to translate between the Rust unwinding mechanism and the natively provided + mechanism. In this proposal, only `"C unwind"` boundaries would require shims. + +## Analysis of key design goals +[analysis-of-design-goals]: #analysis-of-design-goals + +This section revisits the key design goals to assess how well they +are met by the proposed design. + +### Changing from `panic=unwind` to `panic=abort` cannot cause UB + +This constraint is met: + +* Unwinding across a "C" boundary is UB regardless + of whether one is using `panic=unwind` or `panic=abort`. +* Unwinding across a "C unwind" boundary is always defined, + though it is defined to abort if `panic=abort` is used. +* Forced exceptions behave the same regardless of panic mode. + +### Optimization with panic=abort + +Using this proposal, the compiler is **almost always** able to reduce +overhead related to unwinding when using panic=abort. The one +exception is that invoking a "C unwind" ABI still requires some kind +of minimal landing pad to trigger an abort. The expectation is that +very few functions will use the "C unwind" boundary unless they truly +intend to unwind -- and, in that case, those functions are likely +using panic=unwind anyway, so this is not expected to make much +difference in practice. + +### Preserve the ability to change how Rust panics are propagated when using the Rust ABI + +This constraint is met. If we were to change Rust panics to a +different mechanism from the mechanism used by the native ABI, +however, there would have to be a conversion step that interconverts +between Rust panics and foreign exceptions at "C unwind" ABI +boundaries. + +### Enable Rust panics to traverse through foreign frames + +This constraint is met. + +### Enable foreign exceptions to propagate through Rust frame + +This constraint is partially met: the behavior of foreign exceptions +with respect to `catch_unwind` is currently undefined, and left for +future work. + +### Enable error handling with `longjmp` + +This constraint has been [deferred][unresolved-questions]. + +### Do not change the ABI of functions in the `libc` crate + +This constraint has been [deferred][unresolved-questions]. + +# Prior art +[prior-art]: #prior-art + +C++ as specified has no concept of "foreign" exceptions or of an underlying +exception mechanism. However, in practice, the C++ exception mechanism is the +"native" unwinding mechanism used by compilers. + +On Microsoft platforms, when using MSVC, unwinding is always supported for both +C++ and C code; this is very similar to "option 3" described in [the +inside-rust post][inside-rust-proposals] mentioned [above][alternatives]. + +On other platforms, GCC, LLVM, and any related compilers provide a flag, +`-fexceptions`, for explicitly ensuring that stack frames have unwinding +support regardless of the language being compiled. Conversely, +`-fno-exceptions` removes unwinding support even from C++. This is somewhat +similar to how Rust's `panic=unwind` and `panic=abort` work for `panic!` +unwinds, and under the "option 3" proposal, the behavior would be similar for +foreign exceptions as well. In the current proposal, though, such foreign +exception support is not enabled by default with `panic=unwind` but requires +the new `"C unwind"` ABI. + +## Attributes on nightly Rust and prior RFCs +[nightly-attributes]: #attributes-on-nightly-rust-and-prior-rfcs + +Currently, nightly Rust provides attributes, `#[unwind(allowed)]` and +`#[unwind(abort)]`, that permit users to select a well-defined behavior when a +`panic` reaches an `extern "C"` function boundary. Stabilization of these +attributes has [a tracking issue][attributes-tracking-issue], but most +of the discussion about whether this was the best approach took place in two +RFC PR threads, [#2699][rfc-2699] and [#2753][rfc-2753]. + +The attribute approach was deemed insufficient for the following reasons: + +* Currently, Rust does not support attributes on function pointers. This may + change in the future, but until then, attributes cannot provide any way to + differentiate function pointers that may unwind from those that are + guaranteed not to. Assuming that no function pointers may unwind is not + viable, because that severly limits the utility of cross-FFI unwinding. + Conversely, assuming that all `extern "C"` function pointers may unwind is + inconsistent with the no-unwind default for `extern "C"` functions. +* The existence of a compatible unwind mechanism on both sides of a function + invocation boundary is part of the binary interface for that invocation, so + the ABI string is a more appropriate part of the language syntax than + function attributes to indicate that unwinding may occur. +* The ability of a function to unwind must be part of the type system to ensure + that callers that cannot unwind don't invoke functions that can unwind. + Although attributes are sometimes part of a function's type, a function's ABI + string is always part of its type, so we are not introducing any new elements + to the type system. + +[attributes-tracking-issue]: https://github.com/rust-lang/rust/issues/58760 +[rfc-2699]: https://github.com/rust-lang/rfcs/pull/2699 +[rfc-2753]: https://github.com/rust-lang/rfcs/pull/2753 + +## Older discussions about unwinding through `extern "C"` boundaries + +As mentioned [above][motivation], it is currently undefined behavior for +`extern "C"` functions to unwind. As documented in [this +issue][abort-unwind-issue], the lang team has long intended to make `panic!` +cause the runtime to abort rather than unwind through an `extern "C"` boundary +(which the current proposal [also specifies][extern-c-behavior]). + +The abort-on-unwind behavior was [stabilized in 1.24][1.24-release] and +[reverted in 1.24.1][1.24.1-release]; the team originally planned to [stabilize +it again][1.33-stabilization] in 1.33, but ultimately [decided not +to][1.33-discussion]. Community discussion [on discourse][discourse-thread] was +largely concerned with the lack of any stable language feature to permit +unwinding across FFI boundaries, and this contributed to the decision to block +the re-stabilization of the abort-on-unwind behavior until such a feature could +be introduced. + +[abort-unwind-issue]: https://github.com/rust-lang/rust/issues/52652 +[1.24-release]: https://blog.rust-lang.org/2018/02/15/Rust-1.24.html#other-good-stuff +[1.24.1-release]: https://blog.rust-lang.org/2018/03/01/Rust-1.24.1.html#do-not-abort-when-unwinding-through-ffi +[1.33-stabilization]: https://github.com/rust-lang/rust/pull/55982 +[1.33-discussion]: https://github.com/rust-lang/rust/issues/58794 +[discourse-thread]: https://internals.rust-lang.org/t/unwinding-through-ffi-after-rust-1-33/9521?u=batmanaod + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +The behavior of `catch_unwind` when a foreign exception encounters it is +currently [left undefined][reference-level-explanation]. We would like to +provide a well-defined behavior for this case, which will probably be either to +let the exception pass through uncaught or to catch some or all foreign +exceptions. + +We would also like to specify conditions under which `longjmp` and +`pthread_exit` may safely deallocate Rust frames. This RFC specifies that +frames deallocated in this way [must be POFs][reference-level-explanation]. +However, this condition is merely necessary rather than sufficient to ensure +well-defined behavior. + +Within the context of this RFC and in discussions among members of the +[FFI-unwind project group][project-group], this class of formally-undefined +behavior which we plan to define in future RFCs is referred to as "TBD +behavior". + +# Future possibilities +[future-possibilities]: #future-possibilities + +The [FFI-unwind project group][project-group] intends to remain active at least +until all ["TBD behavior"][unresolved-questions] is defined. + +We may want to provide more means of interaction with foreign exceptions. For +instance, it may be possible to provide a way for Rust to catch C++ exceptions +and rethrow them from another thread. Such a mechanism may either be +incorporated into the functionality of `catch_unwind` or provided as a separate +language or standard library feature. + +Coercions between `"C unwind"` function types (such as function pointers) and +the other ABIs are not part of this RFC. However, they will probably be +indispensible for API design, so we plan to provide them in a future RFC. + +As mentioned [above][rationale], shims will be required if Rust changes its +unwind mechanism. From 2f2c9b7fa744964c15cf0d6523ca59ed160d6229 Mon Sep 17 00:00:00 2001 From: Kyle Strand Date: Thu, 25 Jun 2020 16:44:25 -0600 Subject: [PATCH 02/11] TODOs for myself --- text/0000-c-unwind-abi.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/text/0000-c-unwind-abi.md b/text/0000-c-unwind-abi.md index a6b874a641d..71acd649a5c 100644 --- a/text/0000-c-unwind-abi.md +++ b/text/0000-c-unwind-abi.md @@ -6,6 +6,19 @@ [project-group]: https://github.com/rust-lang/project-ffi-unwind + + # Summary [summary]: #summary @@ -215,6 +228,9 @@ behavior. `"C"`-like ABIs are `"C"` itself but also related ABIs such as | `panic=abort` | `"C unwind"` | `panic!` aborts | abort | | `panic=abort` | `"C"`-like | `panic!` aborts (no unwinding occurs) | UB | + + The interaction of Rust frames with C functions that deallocate frames (i.e. functions that may use forced unwinding on specific platforms) is independent of the panic runtime, ABI, or platform. From 23efb0a62582c2ca5ede436c13b5f6696eea5b5a Mon Sep 17 00:00:00 2001 From: Kyle Strand Date: Fri, 3 Jul 2020 22:04:17 -0600 Subject: [PATCH 03/11] Changes from review; mention 'system' and other ABIs --- text/0000-c-unwind-abi.md | 60 ++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/text/0000-c-unwind-abi.md b/text/0000-c-unwind-abi.md index 71acd649a5c..b6018e17eaf 100644 --- a/text/0000-c-unwind-abi.md +++ b/text/0000-c-unwind-abi.md @@ -6,19 +6,6 @@ [project-group]: https://github.com/rust-lang/project-ffi-unwind - - # Summary [summary]: #summary @@ -146,6 +133,19 @@ may be "sandwiched" between Rust frames, so that Rust `panic`s may safely unwind the C++ frames, if the Rust code declares both the C++ entrypoint and the Rust entrypoint using `"C unwind"`. +## Other `unwind` ABI strings + +Because the `C` ABI is not appropriate for all use cases, we also introduce +these `unwind` ABI strings, which will only differ from their non-`unwind` +variants by permitting unwinding, with the same semantics as `"C unwind"`: + +* `"system unwind"` - available on all platforms +* `"stdcall unwind"` and `"thiscall unwind"` - available only on platforms + where `"stdcall"` and `"thiscall"` are supported + +More `unwind` variants of existing ABI strings may be introduced, with the same +semantics, without an additional RFC. + ## "Plain Old Frames" [POF-definition]: #plain-old-frames @@ -190,7 +190,7 @@ will specify this in [a future RFC][unresolved-questions]. [inside-rust-forced]: https://blog.rust-lang.org/inside-rust/2020/02/27/ffi-unwind-design-meeting.html#forced-unwinding -## Changes to `extern "C"` behavior +## Changes to the behavior of existing ABI strings [extern-c-behavior]: #changes-to-extern-c-behavior Prior to this RFC, any unwinding operation that crossed an `extern "C"` @@ -202,6 +202,9 @@ This RFC retains most of that undefined behavior, with one exception: with the `panic=unwind` runtime, `panic!` will cause an `abort` if it would otherwise "escape" from a function defined with `extern "C"`. +This change will be applied to all ABI strings other than `"Rust"`, such as +`"system"`. + ## Interaction with `panic=abort` If a non-forced foreign unwind would enter a Rust frame via an `extern "C @@ -216,6 +219,8 @@ undefined behavior, and is not guaranteed to cause the program to abort under # Reference-level explanation [reference-level-explanation]: #reference-level-explanation +## ABI boundaries and unforced unwinding + This table shows the behavior of an unwinding operation reaching each type of ABI boundary (function declaration or definition). "UB" stands for undefined behavior. `"C"`-like ABIs are `"C"` itself but also related ABIs such as @@ -228,8 +233,11 @@ behavior. `"C"`-like ABIs are `"C"` itself but also related ABIs such as | `panic=abort` | `"C unwind"` | `panic!` aborts | abort | | `panic=abort` | `"C"`-like | `panic!` aborts (no unwinding occurs) | UB | - +In debug mode, the compiler could insert code to catch unwind attempts at +`extern "C"` boundaries and `abort`; this would provide a safe way to discover +(and fix) instances of this form of UB. + +## Frame deallocation and forced unwinding The interaction of Rust frames with C functions that deallocate frames (i.e. functions that may use forced unwinding on specific platforms) is independent @@ -241,6 +249,8 @@ of the panic runtime, ABI, or platform. specify a safe way to deallocate POFs with `longjmp` or `pthread_exit` in [a future RFC][unresolved-questions]. +## Additional limitations + No subtype relationship is defined between functions or function pointers using different ABIs. This RFC also does not define coercions between `"C"` and `"C unwind"`. @@ -249,6 +259,9 @@ As noted in the [summary][summary], if a Rust frame containing a pending `catch_unwind` call is unwound by a foreign exception, the behavior is undefined for now. +This RFC makes no attempt to define the behavior of asynchronous exceptions, +such as SEH on Windows, interrupting Rust code. + # Drawbacks [drawbacks]: #drawbacks @@ -262,11 +275,20 @@ This design imposes some burden on existing codebases (mentioned Having separate ABIs for `"C"` and `"C unwind"` may make interface design more difficult, especially since this RFC [postpones][unresolved-questions] -introducing coercions between function types using different ABIs. - -A single ABI that "just works" with C++ (or any other language that may throw +introducing coercions between function types using different ABIs. Conversely, +a single ABI that "just works" with C++ (or any other language that may throw exceptions) would be simpler to learn and use than two separate ABIs. +This RFC preserves an existing inconsistency between the `"Rust"` ABI (which is +the default for all functions without an explicit ABI string) and the other +existing ABIs: no ABI string without the word `unwind` will permit unwinding, +except the `"Rust"` ABI, which will permit unwinding, but only when compiled +with `panic=unwind`. Making other ABIs consistent with the `"Rust"` ABI by +permitting them to unwind by default (and possibly either introducing a new `"C +unwind"` ABI or an annotation akin to C++'s `noexcept` to explicitly prohibit +unwinding) would also be a safer default, since it would prevent undefined +behavior when interfacing with external libraries that may throw exceptions. + # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives From 49f061f4c5a13212ff51415e4b3287339bc47c83 Mon Sep 17 00:00:00 2001 From: Kyle Strand Date: Tue, 7 Jul 2020 16:20:16 -0600 Subject: [PATCH 04/11] Comments from RalfJ --- text/0000-c-unwind-abi.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/text/0000-c-unwind-abi.md b/text/0000-c-unwind-abi.md index b6018e17eaf..3a18c8a4773 100644 --- a/text/0000-c-unwind-abi.md +++ b/text/0000-c-unwind-abi.md @@ -95,14 +95,8 @@ how well the current design satisfies these constraints. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -Functions declared with `extern "C"` are generally not permitted to unwind -(with some very narrow exceptions, described [below][forced-unwinding]). -This means, for example, that such a function cannot throw an uncaught C++ -exception, and it also cannot invoke Rust code that may panic (unless that -panic is caught with `catch_unwind`). - When declaring an external function that may unwind, such as an entrypoint to a -C++ library, use `"C unwind"` instead: +C++ library, use `extern "C unwind"` instead of `extern "C"`: ``` extern "C unwind" { @@ -211,15 +205,19 @@ If a non-forced foreign unwind would enter a Rust frame via an `extern "C unwind"` ABI boundary, but the Rust code is compiled with `panic=abort`, the unwind will be caught and the process aborted. -With the exception of the above case, however, unwinding from another language -into Rust through an FFI entrypoint declared with `extern "C"` is always -undefined behavior, and is not guaranteed to cause the program to abort under -`panic=abort`. +Conversely, non-forced unwinding from another language into Rust through an FFI +entrypoint declared with `extern "C"` is always undefined behavior, and is not +guaranteed to cause the program to abort under `panic=abort`. As noted +[below][abi-boundaries-and-forced-unwinding], however, when compiling in debug +mode, the compiler may be able to guarantee an abort in this case. + +`panic=abort` will have no impact on the behavior of forced unwinding. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation ## ABI boundaries and unforced unwinding +[abi-boundaries-and-forced-unwinding]: #abi-boundaries-and-forced-unwinding This table shows the behavior of an unwinding operation reaching each type of ABI boundary (function declaration or definition). "UB" stands for undefined From fdbd98f09de814aadda0fea25f9c0f2a819c35a1 Mon Sep 17 00:00:00 2001 From: Kyle Strand Date: Mon, 13 Jul 2020 20:58:38 -0600 Subject: [PATCH 05/11] Revise limitations section --- text/0000-c-unwind-abi.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/text/0000-c-unwind-abi.md b/text/0000-c-unwind-abi.md index 3a18c8a4773..e7936fc5c2d 100644 --- a/text/0000-c-unwind-abi.md +++ b/text/0000-c-unwind-abi.md @@ -248,17 +248,20 @@ of the panic runtime, ABI, or platform. future RFC][unresolved-questions]. ## Additional limitations +[additional-limitations]: #additional-limitations -No subtype relationship is defined between functions or function pointers using -different ABIs. This RFC also does not define coercions between `"C"` and -`"C unwind"`. +In order to limit the scope of this RFC, the following limitations are imposed: -As noted in the [summary][summary], if a Rust frame containing a pending -`catch_unwind` call is unwound by a foreign exception, the behavior is -undefined for now. +* No subtype relationship is defined between functions or function pointers + using different ABIs. +* Coercions are not defined between `"C"` and `"C unwind"`. +* As noted in the [summary][summary], if a Rust frame containing a pending + `catch_unwind` call is unwound by a foreign exception, the behavior is + undefined for now. +* The behavior of asynchronous exceptions, such as SEH on Windows, interrupting + Rust code is not defined. -This RFC makes no attempt to define the behavior of asynchronous exceptions, -such as SEH on Windows, interrupting Rust code. +These may be addressed in [future RFCs][future-possibilities]. # Drawbacks [drawbacks]: #drawbacks @@ -495,7 +498,9 @@ behavior". [future-possibilities]: #future-possibilities The [FFI-unwind project group][project-group] intends to remain active at least -until all ["TBD behavior"][unresolved-questions] is defined. +until all ["TBD behavior"][unresolved-questions] is defined. We may also +address some or all of the current proposal's +[limitations][additional-limitations] in future RFCs. We may want to provide more means of interaction with foreign exceptions. For instance, it may be possible to provide a way for Rust to catch C++ exceptions From 89c7a2e405c363d755bb2b7be45882a0d121d254 Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Mon, 13 Jul 2020 20:59:42 -0600 Subject: [PATCH 06/11] Update text/0000-c-unwind-abi.md Co-authored-by: Ralf Jung --- text/0000-c-unwind-abi.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-c-unwind-abi.md b/text/0000-c-unwind-abi.md index e7936fc5c2d..45dd0d8e6f5 100644 --- a/text/0000-c-unwind-abi.md +++ b/text/0000-c-unwind-abi.md @@ -156,7 +156,7 @@ and may be elided as an optimization). Rust frames that do contain pending destructors or `catch_unwind` calls are called non-POFs. -Note that a non-POF may _become_ a POF, for instance if all `Drop` objects are +Note that a non-POF may _become_ a POF during execution of the corresponding function, for instance if all `Drop` objects are moved out of scope, or if its only `catch_unwind` call is in a code path that will not be executed. The next section provides an example. From 612880a1e697cbb3169bc5bef807a252eaac47aa Mon Sep 17 00:00:00 2001 From: Kyle Strand Date: Tue, 14 Jul 2020 13:57:36 -0600 Subject: [PATCH 07/11] POFs necessary but not sufficient --- text/0000-c-unwind-abi.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/text/0000-c-unwind-abi.md b/text/0000-c-unwind-abi.md index e7936fc5c2d..3c7b6bc287f 100644 --- a/text/0000-c-unwind-abi.md +++ b/text/0000-c-unwind-abi.md @@ -177,10 +177,12 @@ types). In other words, a forced unwind operation on one platform will simply deallocate Rust frames without true unwinding on other platforms. This RFC specifies that, regardless of the platform or the ABI string (`"C"` or -`"C unwind"`), any platform features that may rely on forced unwinding are -undefined behavior if they cross non-[POFs][POF-definition]. For now, however, -we do not specify the conditions required to use forced unwinding safely; we -will specify this in [a future RFC][unresolved-questions]. +`"C unwind"`), any platform features that may rely on forced unwinding will +always be considered undefined behavior if they cross +non-[POFs][POF-definition]. Crossing only POFs is necessary but not sufficient, +however, to make forced unwinding safe, and for now we do not specify any safe +form of forced unwinding; we will specify this in [a future +RFC][unresolved-questions]. [inside-rust-forced]: https://blog.rust-lang.org/inside-rust/2020/02/27/ffi-unwind-design-meeting.html#forced-unwinding From 803f79f18da23aaf11e1e299a0703d1a0aec3cfc Mon Sep 17 00:00:00 2001 From: Kyle Strand Date: Tue, 14 Jul 2020 13:59:35 -0600 Subject: [PATCH 08/11] line wraps --- text/0000-c-unwind-abi.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/text/0000-c-unwind-abi.md b/text/0000-c-unwind-abi.md index 37b2f18659c..3ff3e3bb1eb 100644 --- a/text/0000-c-unwind-abi.md +++ b/text/0000-c-unwind-abi.md @@ -156,9 +156,10 @@ and may be elided as an optimization). Rust frames that do contain pending destructors or `catch_unwind` calls are called non-POFs. -Note that a non-POF may _become_ a POF during execution of the corresponding function, for instance if all `Drop` objects are -moved out of scope, or if its only `catch_unwind` call is in a code path that -will not be executed. The next section provides an example. +Note that a non-POF may _become_ a POF during execution of the corresponding +function, for instance if all `Drop` objects are moved out of scope, or if its +only `catch_unwind` call is in a code path that will not be executed. The next +section provides an example. [cpp-POD-definition]: https://en.cppreference.com/w/cpp/named_req/PODType From cdca5d35916ab643048a48cd6b5b55ce732a3846 Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Fri, 24 Jul 2020 22:26:20 -0600 Subject: [PATCH 09/11] Use hyphen in ABI string (requested in PR comments) --- text/0000-c-unwind-abi.md | 56 +++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/text/0000-c-unwind-abi.md b/text/0000-c-unwind-abi.md index 3ff3e3bb1eb..6c7e6b4b0f5 100644 --- a/text/0000-c-unwind-abi.md +++ b/text/0000-c-unwind-abi.md @@ -1,4 +1,4 @@ -- Feature Name: `"C unwind" ABI` +- Feature Name: `"C-unwind" ABI` - Start Date: 2019-04-03 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) @@ -9,12 +9,12 @@ # Summary [summary]: #summary -We introduce a new ABI string, `"C unwind"`, to enable unwinding from other +We introduce a new ABI string, `"C-unwind"`, to enable unwinding from other languages (such as C++) into Rust frames and from Rust into other languages. Additionally, we define the behavior for a limited number of previously-undefined cases when an unwind operation reaches a Rust function -boundary with a non-`"Rust"`, non-`"C unwind"` ABI. +boundary with a non-`"Rust"`, non-`"C-unwind"` ABI. As part of this specification, we introduce the term ["Plain Old Frame" (POF)][POF-definition]. These are frames that have no pending destructors and @@ -96,42 +96,42 @@ how well the current design satisfies these constraints. [guide-level-explanation]: #guide-level-explanation When declaring an external function that may unwind, such as an entrypoint to a -C++ library, use `extern "C unwind"` instead of `extern "C"`: +C++ library, use `extern "C-unwind"` instead of `extern "C"`: ``` -extern "C unwind" { +extern "C-unwind" { fn may_throw(); } ``` Rust functions that call a possibly-unwinding external function should either use the default Rust ABI (which can be made explicit with `extern "Rust"`) or -the `"C unwind"` ABI: +the `"C-unwind"` ABI: ``` -extern "C unwind" fn can_unwind() { +extern "C-unwind" fn can_unwind() { may_throw(); } ``` -Using the `"C unwind"` ABI to "sandwich" Rust frames between frames from +Using the `"C-unwind"` ABI to "sandwich" Rust frames between frames from another language (such as C++) allows an exception initiated in a callee frame in the other language to traverse the intermediate Rust frames before being caught in the caller frames. I.e., a C++ exception may be thrown, -cross into Rust via an `extern "C unwind"` function declaration, safely unwind +cross into Rust via an `extern "C-unwind"` function declaration, safely unwind the Rust frames, and cross back into C++ (where it may be caught) via a Rust -`"C unwind"` function definition. +`"C-unwind"` function definition. Conversely, languages that support the native unwinding mechanism, such as C++, may be "sandwiched" between Rust frames, so that Rust `panic`s may safely unwind the C++ frames, if the Rust code declares both the C++ entrypoint and -the Rust entrypoint using `"C unwind"`. +the Rust entrypoint using `"C-unwind"`. ## Other `unwind` ABI strings Because the `C` ABI is not appropriate for all use cases, we also introduce these `unwind` ABI strings, which will only differ from their non-`unwind` -variants by permitting unwinding, with the same semantics as `"C unwind"`: +variants by permitting unwinding, with the same semantics as `"C-unwind"`: * `"system unwind"` - available on all platforms * `"stdcall unwind"` and `"thiscall unwind"` - available only on platforms @@ -178,7 +178,7 @@ types). In other words, a forced unwind operation on one platform will simply deallocate Rust frames without true unwinding on other platforms. This RFC specifies that, regardless of the platform or the ABI string (`"C"` or -`"C unwind"`), any platform features that may rely on forced unwinding will +`"C-unwind"`), any platform features that may rely on forced unwinding will always be considered undefined behavior if they cross non-[POFs][POF-definition]. Crossing only POFs is necessary but not sufficient, however, to make forced unwinding safe, and for now we do not specify any safe @@ -229,9 +229,9 @@ behavior. `"C"`-like ABIs are `"C"` itself but also related ABIs such as | panic runtime | ABI | `panic`-unwind | Unforced foreign unwind | | -------------- | ------------ | ------------------------------------- | ----------------------- | -| `panic=unwind` | `"C unwind"` | unwind | unwind | +| `panic=unwind` | `"C-unwind"` | unwind | unwind | | `panic=unwind` | `"C"`-like | abort | UB | -| `panic=abort` | `"C unwind"` | `panic!` aborts | abort | +| `panic=abort` | `"C-unwind"` | `panic!` aborts | abort | | `panic=abort` | `"C"`-like | `panic!` aborts (no unwinding occurs) | UB | In debug mode, the compiler could insert code to catch unwind attempts at @@ -257,7 +257,7 @@ In order to limit the scope of this RFC, the following limitations are imposed: * No subtype relationship is defined between functions or function pointers using different ABIs. -* Coercions are not defined between `"C"` and `"C unwind"`. +* Coercions are not defined between `"C"` and `"C-unwind"`. * As noted in the [summary][summary], if a Rust frame containing a pending `catch_unwind` call is unwound by a foreign exception, the behavior is undefined for now. @@ -277,7 +277,7 @@ inconsistent across platforms, which is not desirable. This design imposes some burden on existing codebases (mentioned [above][motivation]) to change their `extern` annotations to use the new ABI. -Having separate ABIs for `"C"` and `"C unwind"` may make interface design more +Having separate ABIs for `"C"` and `"C-unwind"` may make interface design more difficult, especially since this RFC [postpones][unresolved-questions] introducing coercions between function types using different ABIs. Conversely, a single ABI that "just works" with C++ (or any other language that may throw @@ -304,13 +304,13 @@ explained in [this Inside Rust blog post][inside-rust-proposals]. The design in RFC is referred to as "option 2" in that post. "Option 1" in that blog post only differs from the current proposal in the -behavior of a forced unwind across a `"C unwind"` boundary under `panic=abort`. +behavior of a forced unwind across a `"C-unwind"` boundary under `panic=abort`. Under the current proposal, this type of unwind is permitted, allowing `longjmp` and `pthread_exit` to behave "normally" with both the `"C"` and the -`"C unwind"` ABI across all platforms regardless of panic runtime. If +`"C-unwind"` ABI across all platforms regardless of panic runtime. If [non-POFs][POF-definition] are unwound, this results in undefined behavior. Under "option 1", however, all foreign unwinding, forced or unforced, is caught -at `"C unwind"` boundaries under `panic=abort`, and the process is aborted. +at `"C-unwind"` boundaries under `panic=abort`, and the process is aborted. This gives `longjmp` and `pthread_exit` surprising behavior on some platforms, but avoids that cause of undefined behavior in the current proposal. @@ -327,7 +327,7 @@ Our reasons for preferring the current proposal are: * Introducing a new ABI makes reliance on cross-language exception handling more explicit. -* `panic=abort` can be safely used with `extern "C unwind"` (there is no +* `panic=abort` can be safely used with `extern "C-unwind"` (there is no undefined behavior except with improperly used forced unwinding), but `extern "C"` has more optimization potential (eliding landing pads). Having two ABIs puts this choice in the hands of users. @@ -344,7 +344,7 @@ Our reasons for preferring the current proposal are: * This design has simpler forward compatibility with alternate `panic!` implementations. Any well-defined cross-language unwinding will require shims to translate between the Rust unwinding mechanism and the natively provided - mechanism. In this proposal, only `"C unwind"` boundaries would require shims. + mechanism. In this proposal, only `"C-unwind"` boundaries would require shims. ## Analysis of key design goals [analysis-of-design-goals]: #analysis-of-design-goals @@ -358,7 +358,7 @@ This constraint is met: * Unwinding across a "C" boundary is UB regardless of whether one is using `panic=unwind` or `panic=abort`. -* Unwinding across a "C unwind" boundary is always defined, +* Unwinding across a "C-unwind" boundary is always defined, though it is defined to abort if `panic=abort` is used. * Forced exceptions behave the same regardless of panic mode. @@ -366,9 +366,9 @@ This constraint is met: Using this proposal, the compiler is **almost always** able to reduce overhead related to unwinding when using panic=abort. The one -exception is that invoking a "C unwind" ABI still requires some kind +exception is that invoking a "C-unwind" ABI still requires some kind of minimal landing pad to trigger an abort. The expectation is that -very few functions will use the "C unwind" boundary unless they truly +very few functions will use the "C-unwind" boundary unless they truly intend to unwind -- and, in that case, those functions are likely using panic=unwind anyway, so this is not expected to make much difference in practice. @@ -378,7 +378,7 @@ difference in practice. This constraint is met. If we were to change Rust panics to a different mechanism from the mechanism used by the native ABI, however, there would have to be a conversion step that interconverts -between Rust panics and foreign exceptions at "C unwind" ABI +between Rust panics and foreign exceptions at "C-unwind" ABI boundaries. ### Enable Rust panics to traverse through foreign frames @@ -418,7 +418,7 @@ similar to how Rust's `panic=unwind` and `panic=abort` work for `panic!` unwinds, and under the "option 3" proposal, the behavior would be similar for foreign exceptions as well. In the current proposal, though, such foreign exception support is not enabled by default with `panic=unwind` but requires -the new `"C unwind"` ABI. +the new `"C-unwind"` ABI. ## Attributes on nightly Rust and prior RFCs [nightly-attributes]: #attributes-on-nightly-rust-and-prior-rfcs @@ -511,7 +511,7 @@ and rethrow them from another thread. Such a mechanism may either be incorporated into the functionality of `catch_unwind` or provided as a separate language or standard library feature. -Coercions between `"C unwind"` function types (such as function pointers) and +Coercions between `"C-unwind"` function types (such as function pointers) and the other ABIs are not part of this RFC. However, they will probably be indispensible for API design, so we plan to provide them in a future RFC. From 4c48398983110cd22d6c87e398883f98bdcbfb97 Mon Sep 17 00:00:00 2001 From: Kyle J Strand Date: Fri, 24 Jul 2020 22:31:30 -0600 Subject: [PATCH 10/11] Add hyphens at line breaks --- text/0000-c-unwind-abi.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/text/0000-c-unwind-abi.md b/text/0000-c-unwind-abi.md index 6c7e6b4b0f5..e24fec1c695 100644 --- a/text/0000-c-unwind-abi.md +++ b/text/0000-c-unwind-abi.md @@ -204,8 +204,8 @@ This change will be applied to all ABI strings other than `"Rust"`, such as ## Interaction with `panic=abort` -If a non-forced foreign unwind would enter a Rust frame via an `extern "C -unwind"` ABI boundary, but the Rust code is compiled with `panic=abort`, the +If a non-forced foreign unwind would enter a Rust frame via an `extern +"C-unwind"` ABI boundary, but the Rust code is compiled with `panic=abort`, the unwind will be caught and the process aborted. Conversely, non-forced unwinding from another language into Rust through an FFI @@ -288,10 +288,11 @@ the default for all functions without an explicit ABI string) and the other existing ABIs: no ABI string without the word `unwind` will permit unwinding, except the `"Rust"` ABI, which will permit unwinding, but only when compiled with `panic=unwind`. Making other ABIs consistent with the `"Rust"` ABI by -permitting them to unwind by default (and possibly either introducing a new `"C -unwind"` ABI or an annotation akin to C++'s `noexcept` to explicitly prohibit -unwinding) would also be a safer default, since it would prevent undefined -behavior when interfacing with external libraries that may throw exceptions. +permitting them to unwind by default (and possibly either introducing a new +`"C-unwind"` ABI or an annotation akin to C++'s `noexcept` to explicitly +prohibit unwinding) would also be a safer default, since it would prevent +undefined behavior when interfacing with external libraries that may throw +exceptions. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives From 9e856800f480aa49ebc2b95e165523ae36aebd11 Mon Sep 17 00:00:00 2001 From: Kyle Strand Date: Tue, 28 Jul 2020 19:54:42 -0600 Subject: [PATCH 11/11] Fix other 'unwind' ABIs --- text/0000-c-unwind-abi.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-c-unwind-abi.md b/text/0000-c-unwind-abi.md index e24fec1c695..d11b80b3104 100644 --- a/text/0000-c-unwind-abi.md +++ b/text/0000-c-unwind-abi.md @@ -133,8 +133,8 @@ Because the `C` ABI is not appropriate for all use cases, we also introduce these `unwind` ABI strings, which will only differ from their non-`unwind` variants by permitting unwinding, with the same semantics as `"C-unwind"`: -* `"system unwind"` - available on all platforms -* `"stdcall unwind"` and `"thiscall unwind"` - available only on platforms +* `"system-unwind"` - available on all platforms +* `"stdcall-unwind"` and `"thiscall-unwind"` - available only on platforms where `"stdcall"` and `"thiscall"` are supported More `unwind` variants of existing ABI strings may be introduced, with the same