2
2
//! operation ([`Thread::park`]).
3
3
use crate :: utils:: Atomic ;
4
4
use std:: {
5
+ cell:: Cell ,
5
6
mem:: MaybeUninit ,
6
7
os:: raw:: c_int,
7
- ptr:: null_mut,
8
+ ptr:: { null_mut, NonNull } ,
8
9
sync:: {
9
10
atomic:: { AtomicPtr , AtomicUsize , Ordering } ,
10
11
Arc , Once ,
@@ -14,10 +15,15 @@ use std::{
14
15
15
16
pub use self :: thread:: ThreadId ;
16
17
18
+ thread_local ! {
19
+ static EXIT_JMP_BUF : Cell <Option <JmpBuf >> = Cell :: new( None ) ;
20
+ }
21
+
17
22
pub unsafe fn exit_thread ( ) -> ! {
18
- unsafe {
19
- libc:: pthread_exit ( std:: ptr:: null_mut ( ) ) ;
20
- }
23
+ let jmp_buf = EXIT_JMP_BUF
24
+ . with ( |c| c. get ( ) )
25
+ . expect ( "this thread wasn't started by `threading::spawn`" ) ;
26
+ unsafe { longjmp ( jmp_buf) } ;
21
27
}
22
28
23
29
/// [`std::thread::JoinHandle`] with extra functionalities.
@@ -28,7 +34,7 @@ pub struct JoinHandle<T> {
28
34
}
29
35
30
36
/// Spawn a new thread.
31
- pub fn spawn < T : ' static + Send > ( f : impl FnOnce ( ) -> T + Send + ' static ) -> JoinHandle < T > {
37
+ pub fn spawn ( f : impl FnOnce ( ) + Send + ' static ) -> JoinHandle < ( ) > {
32
38
let parent_thread = thread:: current ( ) ;
33
39
34
40
let data = Arc :: new ( ThreadData :: new ( ) ) ;
@@ -43,10 +49,14 @@ pub fn spawn<T: 'static + Send>(f: impl FnOnce() -> T + Send + 'static) -> JoinH
43
49
// Move `data2` into `THREAD_DATA`
44
50
THREAD_DATA . store ( Arc :: into_raw ( data2) as _ , Ordering :: Relaxed ) ;
45
51
46
- parent_thread. unpark ( ) ;
47
- drop ( parent_thread) ;
52
+ catch_longjmp ( move |jmp_buf| {
53
+ EXIT_JMP_BUF . with ( |c| c. set ( Some ( jmp_buf) ) ) ;
54
+
55
+ parent_thread. unpark ( ) ;
56
+ drop ( parent_thread) ;
48
57
49
- f ( )
58
+ f ( )
59
+ } ) ;
50
60
} ) ;
51
61
52
62
let thread = Thread {
@@ -311,6 +321,106 @@ fn ok_or_errno(x: c_int) -> Result<c_int, errno::Errno> {
311
321
}
312
322
}
313
323
324
+ #[ derive( Copy , Clone ) ]
325
+ #[ repr( transparent) ]
326
+ struct JmpBuf {
327
+ sp : NonNull < ( ) > ,
328
+ }
329
+
330
+ /// Call `cb`, preserving the current context state in `JmpBuf`, which
331
+ /// can be later used by [`longjmp`] to immediately return from this function,
332
+ /// bypassing destructors and unwinding mechanisms such as
333
+ /// <https://github.com/rust-lang/rust/pull/70212>.
334
+ ///
335
+ /// [The native `setjmp`] isn't supported by Rust at the point of writing.
336
+ ///
337
+ /// [The native `setjmp`]: https://github.com/rust-lang/rfcs/issues/2625
338
+ #[ inline]
339
+ fn catch_longjmp < F : FnOnce ( JmpBuf ) > ( cb : F ) {
340
+ #[ inline( never) ] // ensure all caller-saved regs are trash-able
341
+ fn catch_longjmp_inner ( f : fn ( * mut ( ) , JmpBuf ) , ctx : * mut ( ) ) {
342
+ unsafe {
343
+ match ( ) {
344
+ #[ cfg( target_arch = "x86_64" ) ]
345
+ ( ) => {
346
+ asm ! (
347
+ "
348
+ # push context
349
+ push rbp
350
+ lea rbx, [rip + 0f]
351
+ push rbx
352
+
353
+ # do f(ctx, jmp_buf)
354
+ # [rdi = ctx, rsp = jmp_buf]
355
+ mov rsi, rsp
356
+ call {f}
357
+
358
+ jmp 1f
359
+ 0:
360
+ # longjmp called. restore context
361
+ mov rbp, [rsp + 8]
362
+
363
+ 1:
364
+ # discard context
365
+ add rsp, 16
366
+ " ,
367
+ f = inlateout( reg) f => _,
368
+ inlateout( "rdi" ) ctx => _,
369
+ lateout( "rsi" ) _,
370
+ // System V ABI callee-saved registers
371
+ // (note: Windows uses a different ABI)
372
+ out( "rbx" ) _,
373
+ lateout( "r12" ) _,
374
+ lateout( "r13" ) _,
375
+ lateout( "r14" ) _,
376
+ lateout( "r15" ) _,
377
+ ) ;
378
+ }
379
+ }
380
+ }
381
+ }
382
+
383
+ let mut cb = core:: mem:: ManuallyDrop :: new ( cb) ;
384
+
385
+ catch_longjmp_inner (
386
+ |ctx, jmp_buf| unsafe {
387
+ let ctx = ( ctx as * mut F ) . read ( ) ;
388
+ ctx ( jmp_buf) ;
389
+ } ,
390
+ ( & mut cb) as * mut _ as * mut ( ) ,
391
+ ) ;
392
+ }
393
+
394
+ /// Return from a call to [`catch_longjmp`] using the preserved context state in
395
+ /// `jmp_buf`.
396
+ ///
397
+ /// # Safety
398
+ ///
399
+ /// - This function bypasses all destructor calls that stand between the call
400
+ /// site of this function and the call to `catch_longjmp` corresponding to
401
+ /// the given `JmpBuf`.
402
+ ///
403
+ /// - The call to `catch_longjmp` corresponding to the given `JmpBuf` should be
404
+ /// still active (it must be in the call stack when this function is called).
405
+ ///
406
+ unsafe fn longjmp ( jmp_buf : JmpBuf ) -> ! {
407
+ unsafe {
408
+ match ( ) {
409
+ #[ cfg( target_arch = "x86_64" ) ]
410
+ ( ) => {
411
+ asm ! (
412
+ "
413
+ mov rsp, {}
414
+ jmp [rsp]
415
+ " ,
416
+ in( reg) jmp_buf. sp. as_ptr( ) ,
417
+ options( noreturn) ,
418
+ ) ;
419
+ }
420
+ }
421
+ }
422
+ }
423
+
314
424
#[ cfg( test) ]
315
425
mod tests {
316
426
use super :: * ;
@@ -352,4 +462,28 @@ mod tests {
352
462
// `jh` should be the sole owner of `ThreadData` now
353
463
assert_eq ! ( Arc :: strong_count( & jh. thread. data) , 1 ) ;
354
464
}
465
+
466
+ struct PanicOnDrop ;
467
+
468
+ impl Drop for PanicOnDrop {
469
+ fn drop ( & mut self ) {
470
+ unreachable ! ( ) ;
471
+ }
472
+ }
473
+
474
+ #[ test]
475
+ fn test_longjmp ( ) {
476
+ let mut buf = 42 ;
477
+ catch_longjmp ( |jmp_buf| {
478
+ let _hoge = PanicOnDrop ;
479
+ std:: panic:: catch_unwind ( std:: panic:: AssertUnwindSafe ( || loop {
480
+ buf += 1 ;
481
+ if buf == 50 {
482
+ unsafe { longjmp ( jmp_buf) } ;
483
+ }
484
+ } ) )
485
+ . unwrap ( ) ;
486
+ } ) ;
487
+ assert_eq ! ( buf, 50 ) ;
488
+ }
355
489
}
0 commit comments