22//! operation ([`Thread::park`]).
33use crate :: utils:: Atomic ;
44use std:: {
5+ cell:: Cell ,
56 mem:: MaybeUninit ,
67 os:: raw:: c_int,
7- ptr:: null_mut,
8+ ptr:: { null_mut, NonNull } ,
89 sync:: {
910 atomic:: { AtomicPtr , AtomicUsize , Ordering } ,
1011 Arc , Once ,
@@ -14,10 +15,15 @@ use std::{
1415
1516pub use self :: thread:: ThreadId ;
1617
18+ thread_local ! {
19+ static EXIT_JMP_BUF : Cell <Option <JmpBuf >> = Cell :: new( None ) ;
20+ }
21+
1722pub 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) } ;
2127}
2228
2329/// [`std::thread::JoinHandle`] with extra functionalities.
@@ -28,7 +34,7 @@ pub struct JoinHandle<T> {
2834}
2935
3036/// 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 < ( ) > {
3238 let parent_thread = thread:: current ( ) ;
3339
3440 let data = Arc :: new ( ThreadData :: new ( ) ) ;
@@ -43,10 +49,14 @@ pub fn spawn<T: 'static + Send>(f: impl FnOnce() -> T + Send + 'static) -> JoinH
4349 // Move `data2` into `THREAD_DATA`
4450 THREAD_DATA . store ( Arc :: into_raw ( data2) as _ , Ordering :: Relaxed ) ;
4551
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) ;
4857
49- f ( )
58+ f ( )
59+ } ) ;
5060 } ) ;
5161
5262 let thread = Thread {
@@ -311,6 +321,106 @@ fn ok_or_errno(x: c_int) -> Result<c_int, errno::Errno> {
311321 }
312322}
313323
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+
314424#[ cfg( test) ]
315425mod tests {
316426 use super :: * ;
@@ -352,4 +462,28 @@ mod tests {
352462 // `jh` should be the sole owner of `ThreadData` now
353463 assert_eq ! ( Arc :: strong_count( & jh. thread. data) , 1 ) ;
354464 }
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+ }
355489}
0 commit comments