@@ -17,8 +17,8 @@ previously-undefined cases when an unwind operation reaches a Rust function
17
17
boundary with a non-` "Rust" ` , non-` "C unwind" ` ABI.
18
18
19
19
As part of this specification, we introduce the term [ "Plain Old Frame"
20
- (POF)] [ POF-definition ] . These are frames that may be safely deallocated with
21
- ` longjmp ` .
20
+ (POF)] [ POF-definition ] . These are frames that have no pending destructors and
21
+ can be trivially deallocated .
22
22
23
23
This RFC does not define the behavior of ` catch_unwind ` in a Rust frame being
24
24
unwound by a foreign exception. This is something the [ project
@@ -40,12 +40,6 @@ unwinding mechanism, the current `rustc` implementation assumes that `extern
40
40
"C"` functions cannot unwind, which permits LLVM to optimize with the
41
41
assumption that such unwinding constitutes undefined behavior.
42
42
43
- Additionally, there are libraries such as ` rlua ` that rely on ` longjmp ` across
44
- Rust frames; on Windows, ` longjmp ` is implemented via [ forced
45
- unwinding] [ forced-unwinding ] . The current ` rustc ` implementation makes it safe
46
- to ` longjmp ` across Rust [ POFs] [ POF-definition ] (frames without ` Drop ` types),
47
- but this is not formally specified in an RFC or by the Reference.
48
-
49
43
The desire for this feature has been previously discussed on other RFCs,
50
44
including [ #2699 ] [ rfc-2699 ] and [ #2753 ] [ rfc-2753 ] .
51
45
@@ -57,11 +51,12 @@ several requirements for any cross-language unwinding design.
57
51
The [ "Analysis of key design goals"] [ analysis-of-design-goals ] section analyzes
58
52
how well the current design satisfies these constraints.
59
53
60
- * ** Changing from panic=unwind to panic=abort cannot cause UB:** We
61
- wish to ensure that choosing ` panic=abort ` doesn't ever create
62
- undefined behavior (relate to ` panic=unwind ` ), even if one is
63
- relying on a library that triggers a panic or a foreign exception.
64
- * ** Optimization with panic=abort:** when using ` -Cpanic=abort ` , we
54
+ * ** Changing from ` panic=unwind ` to ` panic=abort ` cannot cause undefined
55
+ behavior:** We wish to ensure that changing from ` panic=unwind ` to
56
+ ` panic=abort ` never creates undefined behavior (relate to ` panic=unwind ` ),
57
+ even if one is relying on a library that triggers a panic or a foreign
58
+ exception.
59
+ * ** Optimization with ` panic=abort ` :** when using ` panic=abort ` , we
65
60
wish to enable as many code-size optimizations as possible. This
66
61
means that we shouldn't have to generate unwinding tables or other
67
62
such constructs, at least in most cases.
@@ -78,19 +73,24 @@ how well the current design satisfies these constraints.
78
73
languages) to raise exceptions that will propagate through Rust
79
74
frames "as if" they were Rust panics (i.e., running destrutors or,
80
75
in the case of ` unwind=abort ` , aborting the program).
81
- * ** Enable error handling with ` longjmp ` :** As mentioned above, some existing
82
- Rust libraries use ` longjmp ` . Despite the fact that ` longjmp ` on Windows is
83
- [ technically a form of unwinding] [ forced-unwinding ] , using ` longjmp ` across
84
- Rust [ POFs] [ POF-definition ] [ is safe] [ longjmp-pr ] with the current
85
- implementation of ` rustc ` , and we want to specify that this will remain safe.
76
+ * ** Enable error handling with ` longjmp ` :**
77
+ As mentioned above, some existing Rust libraries rely on the ability to
78
+ ` longjmp ` across Rust frames to interoperate with Ruby, Lua, and other C
79
+ APIs. The behavior of ` longjmp ` traversing Rust frames is not specified or
80
+ guaranteed to be safe; in the current implementation of ` rustc ` ,
81
+ however, it [ is safe] [ longjmp-pr ] . On Windows, ` longjmp ` is implemented as a
82
+ form of unwinding called [ "forced unwinding"] [ forced-unwinding ] , so any
83
+ specification of the behavior of forced unwinding across FFI boundaries
84
+ should be forward-compatible with a [ future RFC] [ unresolved-questions ] that
85
+ will provide a well-defined way to interoperate with longjmp-based APIs.
86
86
* ** Do not change the ABI of functions in the ` libc ` crate:** Some ` libc `
87
87
functions may invoke ` pthread_exit ` , which uses [ a form of
88
88
unwinding] [ forced-unwinding ] in the GNU libc implementation. Such functions
89
89
must be safe to use with the existing ` "C" ` ABI, because changing the types
90
90
of these functions would be a breaking change.
91
91
92
- [ 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
93
- [ longjmp-pr ] : https://github.com/rust-lang/rust/pull/48572
92
+ [ 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
93
+ [ longjmp-pr ] : https://github.com/rust-lang/rust/pull/48572
94
94
95
95
# Guide-level explanation
96
96
[ guide-level-explanation ] : #guide-level-explanation
@@ -170,41 +170,10 @@ types). In other words, a forced unwind operation on one platform will simply
170
170
deallocate Rust frames without true unwinding on other platforms.
171
171
172
172
This RFC specifies that, regardless of the platform or the ABI string (` "C" ` or
173
- ` "C unwind" ` ), any platform features that may rely on forced unwinding are:
174
-
175
- * _ undefined behavior_ if they cross non-[ POFs] [ POF-definition ]
176
- * _ defined behavior_ when all unwound frames are POFs
177
-
178
- As an example:
179
-
180
- ``` rust
181
- fn foo <D : Drop >(c : bool , d : D ) {
182
- if c {
183
- drop (d );
184
- }
185
- longjmp_if_true (c );
186
- }
187
-
188
- /// Calls `longjmp` if `c` is true; otherwise returns normally.
189
- extern " C" fn longjmp_if_true (c : bool );
190
- ```
191
-
192
- If a ` longjmp ` occurs, it can safely traverse the ` foo ` frame, which will be a
193
- POF because ` d ` has already been dropped.
194
-
195
- Since ` longjmp_if_true ` function is using the ` "C" ` rather than the `"C
196
- unwind"` ABI, the optimizer may assume that it cannot unwind; on LLVM, this is
197
- represented by the ` nounwind ` attribute. On most platforms, ` longjmp ` is not a
198
- form of unwinding: the ` foo ` frame is simply discarded. On Windows, ` longjmp `
199
- is implemented as a forced unwind, which is permitted to traverse ` nounwind `
200
- frames. Since ` foo ` contains a ` Drop ` type the forced unwind will include a
201
- call to the frame's cleanup logic, but that logic will not produce any
202
- observable effect; in particular, ` D::drop() ` will not be called again. The
203
- observable behavior should therefore be the same on all platforms.
204
-
205
- Conversely, if, due to a bug, ` longjmp ` were called unconditionally, then this
206
- code would have undefined behavior on all platforms when ` c ` is false, because
207
- ` foo ` would not be a POF.
173
+ ` "C unwind" ` ), any platform features that may rely on forced unwinding are
174
+ undefined behavior if they cross non-[ POFs] [ POF-definition ] . For now, however,
175
+ we do not specify the conditions required to use forced unwinding safely; we
176
+ will specify this in [ a future RFC] [ unresolved-questions ] .
208
177
209
178
[ inside-rust-forced ] : https://blog.rust-lang.org/inside-rust/2020/02/27/ffi-unwind-design-meeting.html#forced-unwinding
210
179
@@ -216,29 +185,20 @@ boundary, either from a `panic!` "escaping" from a Rust function defined with
216
185
` extern "C" ` or by entering Rust from another language via an entrypoint
217
186
declared with ` extern "C" ` , caused undefined behavior.
218
187
219
- This RFC retains most of that undefined behavior, with two exceptions:
220
-
221
- * With the ` panic=unwind ` runtime, ` panic! ` will cause an ` abort ` if it would
222
- otherwise "escape" from a function defined with ` extern "C" ` .
223
- * Forced unwinding is safe with ` extern "C" ` as long as only
224
- * [ POFs] [ POF-definition ] are unwound. This is to keep behavior of
225
- ` pthread_exit ` and ` longjmp ` consistent across platforms.
188
+ This RFC retains most of that undefined behavior, with one exception: with the
189
+ ` panic=unwind ` runtime, ` panic! ` will cause an ` abort ` if it would otherwise
190
+ "escape" from a function defined with ` extern "C" ` .
226
191
227
192
## Interaction with ` panic=abort `
228
193
229
194
If a non-forced foreign unwind would enter a Rust frame via an `extern "C
230
195
unwind"` ABI boundary, but the Rust code is compiled with ` panic=abort`, the
231
196
unwind will be caught and the process aborted.
232
197
233
- There are some types of unwinding that are not guaranteed to cause the program
234
- to abort with ` panic=abort ` , though:
235
-
236
- * Forced unwinding: Rust provides no mechanism to catch this type of unwinding.
237
- This is safe with either the ` "C" ` ABI or the new ` "C unwind" ` ABI, as long
238
- as only [ POFs] [ POF-definition ] are unwound.
239
- * Unwinding from another language into Rust if the entrypoint to that language
240
- is declared with ` extern "C" ` (contrary to the guidelines above): this is
241
- always undefined behavior.
198
+ With the exception of the above case, however, unwinding from another language
199
+ into Rust through an FFI entrypoint declared with ` extern "C" ` is always
200
+ undefined behavior, and is not guaranteed to cause the program to abort under
201
+ ` panic=abort ` .
242
202
243
203
# Reference-level explanation
244
204
[ reference-level-explanation ] : #reference-level-explanation
@@ -255,13 +215,15 @@ behavior. `"C"`-like ABIs are `"C"` itself but also related ABIs such as
255
215
| ` panic=abort ` | ` "C unwind" ` | ` panic! ` aborts | abort |
256
216
| ` panic=abort ` | ` "C" ` -like | ` panic! ` aborts (no unwinding occurs) | UB |
257
217
258
- Regardless of the panic runtime, ABI, or platform, the interaction of Rust
259
- frames with C functions that deallocate frames (i.e. functions that may use
260
- forced unwinding on specific platforms) is specified as follows:
218
+ The interaction of Rust frames with C functions that deallocate frames (i.e.
219
+ functions that may use forced unwinding on specific platforms) is independent
220
+ of the panic runtime, ABI, or platform.
261
221
262
- * ** When deallocating Rust [ POFs] [ POF-definition ] :** frames are safely
263
- deallocated; no undefined behavior
264
- * ** When deallocating Rust non-POFs:** undefined behavior
222
+ * ** When deallocating Rust non-POFs:** this is explicitly undefined behavior.
223
+ * ** When deallocating Rust [ POFs] [ POF-definition ] :** for now, this is not
224
+ specified, and must be considered undefined behavior. However, we do plan to
225
+ specify a safe way to deallocate POFs with ` longjmp ` or ` pthread_exit ` in [ a
226
+ future RFC] [ unresolved-questions ] .
265
227
266
228
No subtype relationship is defined between functions or function pointers using
267
229
different ABIs. This RFC also does not define coercions between ` "C" ` and
@@ -283,7 +245,7 @@ This design imposes some burden on existing codebases (mentioned
283
245
[ above] [ motivation ] ) to change their ` extern ` annotations to use the new ABI.
284
246
285
247
Having separate ABIs for ` "C" ` and ` "C unwind" ` may make interface design more
286
- difficult, especially since this RFC [ postpones] [ future-possibilities ]
248
+ difficult, especially since this RFC [ postpones] [ unresolved-questions ]
287
249
introducing coercions between function types using different ABIs.
288
250
289
251
A single ABI that "just works" with C++ (or any other language that may throw
@@ -348,7 +310,7 @@ Our reasons for preferring the current proposal are:
348
310
This section revisits the key design goals to assess how well they
349
311
are met by the proposed design.
350
312
351
- ### Changing from panic=unwind to panic=abort cannot cause UB
313
+ ### Changing from ` panic=unwind ` to ` panic=abort ` cannot cause UB
352
314
353
315
This constraint is met:
354
316
@@ -389,14 +351,11 @@ future work.
389
351
390
352
### Enable error handling with ` longjmp `
391
353
392
- This constraint is met: ` longjmp ` is treated the same across all platforms, and
393
- is safe as long as only [ POFs] [ POF-definition ] are deallocated.
354
+ This constraint has been [ deferred] [ unresolved-questions ] .
394
355
395
356
### Do not change the ABI of functions in the ` libc ` crate
396
357
397
- This constraint is met: ` libc ` functions will continue to use the ` "C" ` ABI.
398
- ` pthread_exit ` will be treated the same across all platforms, and will be safe
399
- as long as only [ POFs] [ POF-definition ] are deallocated.
358
+ This constraint has been [ deferred] [ unresolved-questions ] .
400
359
401
360
# Prior art
402
361
[ prior-art ] : #prior-art
@@ -485,9 +444,15 @@ provide a well-defined behavior for this case, which will probably be either to
485
444
let the exception pass through uncaught or to catch some or all foreign
486
445
exceptions.
487
446
447
+ We would also like to specify conditions under which ` longjmp ` and
448
+ ` pthread_exit ` may safely deallocate Rust frames. This RFC specifies that
449
+ frames deallocated in this way [ must be POFs] [ reference-level-explanation ] .
450
+ However, this condition is merely necessary rather than sufficient to ensure
451
+ well-defined behavior.
452
+
488
453
Within the context of this RFC and in discussions among members of the
489
454
[ FFI-unwind project group] [ project-group ] , this class of formally-undefined
490
- behavior which we plan to define at later date is referred to as "TBD
455
+ behavior which we plan to define in future RFCs is referred to as "TBD
491
456
behavior".
492
457
493
458
# Future possibilities
0 commit comments