10
10
//! [fatal interaction with optimizer][misopt].
11
11
//! This crate does not suffer from the misoptimization (covered in `tests/smoke.rs`).
12
12
//!
13
- //! ⚠️ We admit that since it's not yet undefined to force unwind Rust POFs and/or
14
- //! longjmp's half execution semantic, `long_jump` is still technically undefined behavior.
13
+ //! ⚠️ We admit that since it's not yet undefined to force unwind Rust POFs,
14
+ //! `long_jump` is still technically undefined behavior.
15
15
//! But this crate is an attempt to make a semantically-correct abstraction free from
16
16
//! misoptimization, and you accept the risk by using this crate.
17
17
//! If you find any misoptimization in practice, please open an issue.
25
25
//! Single `usize` `JumpPoint`. Let optimizer save only necessary states rather than bulk saving
26
26
//! all callee-saved registers. Inline-able `set_jump` without procedure call cost.
27
27
//!
28
- //! - 2.3ns `set_jump` setup and 3.2ns `long_jump` on a modern x86\_64 CPU.
28
+ //! - 2.4ns `set_jump` setup and 2.9ns `long_jump` on a modern x86\_64 CPU.
29
29
//! ~300-490x faster than `catch_unwind`-`panic_any!`.
30
30
//!
31
31
//! - No std.
90
90
//!
91
91
//! - [`sjlj`](https://crates.io/crates/sjlj)
92
92
//!
93
- //! - Uses inline assembly but involving an un-inline-able call instruction.
94
93
//! - Only x86\_64 is supported.
95
- //! - Suffers from [misoptimization][misopt].
94
+ //! - Suffers from [misoptimization][misopt] due to multi-return .
96
95
//! - Slower `long_jump` because of more register restoring.
97
96
//!
98
97
//! [misopt]: https://github.com/rust-lang/rfcs/issues/2625
99
98
#![ cfg_attr( feature = "unstable-asm-goto" , feature( asm_goto) ) ]
100
99
#![ cfg_attr( feature = "unstable-asm-goto" , feature( asm_goto_with_outputs) ) ]
101
100
#![ cfg_attr( not( test) , no_std) ]
102
101
use core:: marker:: PhantomData ;
103
- use core:: mem:: { size_of , MaybeUninit } ;
102
+ use core:: mem:: ManuallyDrop ;
104
103
use core:: num:: NonZero ;
105
104
106
105
#[ cfg( target_arch = "x86_64" ) ]
@@ -147,8 +146,6 @@ mod imp {
147
146
148
147
compile_error ! ( "sjlj2: unsupported platform" ) ;
149
148
150
- pub type Buf = [ usize ; 0 ] ;
151
-
152
149
macro_rules! set_jump_raw {
153
150
( $val: tt, $( $tt: tt) * ) => {
154
151
$val = 0 as _
@@ -206,7 +203,7 @@ impl JumpPoint<'_> {
206
203
}
207
204
208
205
/// Alias of [`set_jump`].
209
- #[ inline( always ) ]
206
+ #[ inline]
210
207
pub fn set_jump < T , F , G > ( ordinary : F , lander : G ) -> T
211
208
where
212
209
F : FnOnce ( JumpPoint < ' _ > ) -> T ,
@@ -220,7 +217,7 @@ impl JumpPoint<'_> {
220
217
/// # Safety
221
218
///
222
219
/// See [`long_jump`].
223
- #[ inline( always ) ]
220
+ #[ inline]
224
221
pub unsafe fn long_jump ( self , result : NonZero < usize > ) -> ! {
225
222
long_jump ( self , result)
226
223
}
@@ -237,18 +234,25 @@ impl JumpPoint<'_> {
237
234
/// Since it is implemented with [`core::mem::needs_drop`], it may generates false positive
238
235
/// compile errors.
239
236
///
240
- /// This function is even inline-able without procedure-call cost!
237
+ /// This function is inline-able but `ordinary` is called in a C function wrapper because it must
238
+ /// has its own stack frame to avoid [misoptimization][misopt2].
239
+ ///
240
+ /// [misopt2]: https://github.com/rust-lang/libc/issues/1596
241
241
///
242
242
/// # Safety
243
243
///
244
244
/// Yes, this function is actually safe to use. [`long_jump`] is unsafe, however.
245
245
///
246
246
/// # Panics
247
247
///
248
- /// It is possible to unwind from `ordinary` or `lander` closure.
248
+ /// It is allowed to panic in `ordinary` or `lander` closure. But panics in `ordinary` cannot
249
+ /// unwind across `set_jump`, or it will abort the process.
250
+ /// Panics in `lander` can unwind across `set_jump` though.
249
251
#[ doc( alias = "setjmp" ) ]
250
- #[ inline( always) ]
251
- pub fn set_jump < T , F , G > ( mut ordinary : F , lander : G ) -> T
252
+ #[ inline]
253
+ // For better logical order.
254
+ #[ allow( clippy:: items_after_statements) ]
255
+ pub fn set_jump < T , F , G > ( ordinary : F , lander : G ) -> T
252
256
where
253
257
F : FnOnce ( JumpPoint < ' _ > ) -> T ,
254
258
G : FnOnce ( NonZero < usize > ) -> T ,
@@ -266,40 +270,51 @@ where
266
270
// unwind between its return from F or G and the return from `set_jump`.
267
271
}
268
272
269
- let mut buf = <MaybeUninit < imp:: Buf > >:: uninit ( ) ;
270
- let ptr = buf. as_mut_ptr ( ) ;
271
- let mut val: usize ;
273
+ union Data < T , F > {
274
+ func : ManuallyDrop < F > ,
275
+ ret : ManuallyDrop < T > ,
276
+ }
272
277
273
- // Show the optimizer that `ordinary` may be called in both ordinary and landing paths.
274
- // So it cannot assume that variables that are captured by `ordinary` are unchanged in the
275
- // lander path -- they must be reloaded.
276
- // This fixes <https://github.com/rust-lang/rfcs/issues/2625>.
277
- if size_of :: < F > ( ) == 0 {
278
- // F must not mutate local states because it captures nothing.
279
- } else {
280
- unsafe {
281
- core:: arch:: asm!(
282
- "/*{}*/" ,
283
- in( reg) core:: ptr:: addr_of_mut!( ordinary) ,
284
- // May access memory.
285
- ) ;
278
+ struct AbortGuard ;
279
+ impl Drop for AbortGuard {
280
+ fn drop ( & mut self ) {
281
+ // Manually trigger a double-panic abort.
282
+ // Workaround: <https://github.com/rust-lang/rust/issues/67952>
283
+ panic ! ( "panic cannot unwind across set_jump" ) ;
286
284
}
287
285
}
288
286
287
+ unsafe extern "C" fn wrap < T , F : FnOnce ( JumpPoint < ' _ > ) -> T > (
288
+ data : & mut Data < T , F > ,
289
+ jp : * mut ( ) ,
290
+ ) -> usize {
291
+ let guard = AbortGuard ;
292
+ let f = unsafe { ManuallyDrop :: take ( & mut data. func ) } ;
293
+ let ret = f ( unsafe { JumpPoint :: from_raw ( jp) } ) ;
294
+ data. ret = ManuallyDrop :: new ( ret) ;
295
+ core:: mem:: forget ( guard) ;
296
+ 0
297
+ }
298
+
299
+ let mut data = Data :: < T , F > {
300
+ func : ManuallyDrop :: new ( ordinary) ,
301
+ } ;
302
+ let val: usize ;
303
+
289
304
#[ cfg( feature = "unstable-asm-goto" ) ]
290
305
unsafe {
291
- set_jump_raw ! ( val, ptr , {
306
+ set_jump_raw ! ( val, wrap :: < T , F > , & mut data , {
292
307
unsafe { return lander( NonZero :: new_unchecked( val) ) }
293
308
} ) ;
294
- ordinary ( JumpPoint :: from_raw ( ptr . cast ( ) ) )
309
+ ManuallyDrop :: take ( & mut data . ret )
295
310
}
296
311
297
312
#[ cfg( not( feature = "unstable-asm-goto" ) ) ]
298
- unsafe {
299
- set_jump_raw ! ( val, ptr ) ;
313
+ {
314
+ unsafe { set_jump_raw ! ( val, wrap :: < T , F > , & mut data ) } ;
300
315
match NonZero :: new ( val) {
301
- None => ordinary ( JumpPoint :: from_raw ( ptr . cast ( ) ) ) ,
302
- Some ( val ) => lander ( val ) ,
316
+ None => unsafe { ManuallyDrop :: take ( & mut data . ret ) } ,
317
+ Some ( v ) => lander ( v ) ,
303
318
}
304
319
}
305
320
}
0 commit comments