Using setjmp longjmp intrinsics or is there another way?

This is from my shell history:

curl -s https://static.rust-lang.org/rustup.sh | sh -s -- --channel=nightly

rustup toolchain list?

rustup toolchain list
stable-x86_64-apple-darwin
beta-x86_64-apple-darwin (default)
nightly-x86_64-apple-darwin

OK, so you can either run rustup default nightly to make a global change, or rustup override set nightly to affect just the current directory.

You might also want a general rustup update, because your beta version is not current, and maybe the others have updates too.

That does get things to compile.`

Still, it doesn't seem like I did anything wrong.
How could I end up with a beta channel?

Did need the feature gate. Forgot I put it on the first line.

How can I know your history? :wink: It appears at some point you installed stable, beta, and nightly -- probably beta first since that was your default.

 2076  curl -s https://static.rust-lang.org/rustup.sh | sh -s -- --channel=nightly
 2087  rustc x.rs
 2092  rustc --help
 2093  rustc -Vv x.rs
 2097  rustup toolchain list
 2098  rustup update
 2099  rustup default nightly
 2100  rustc -Vv x.rs
 2101  rustc x.rs

I installed normally and when the latest stable was announced I did a rustup for that. I haven't worked with nightly before today.

I tried this script but control-c'd out when it wanted my root password:

2074 curl -sf https://raw.githubusercontent.com/brson/multirust/master/blastoff.sh | sh

This is beginning to work with the trivial test app but it breaks the compiler on my real app:

  %600 = invoke i32 @llvm.setjmp(i8* %599)
          to label %bb258 unwind label %cleanup36, !dbg !7338
Cannot invoke an intrinsic other than donothing, patchpoint or statepoint
  %600 = invoke i32 @llvm.setjmp(i8* %599)
          to label %bb258 unwind label %cleanup36, !dbg !7338
LLVM ERROR: Broken function found, compilation aborted!

The solution (for now) may be to isolate setjmp/longjmp into a separate source file.
But if there are any compiler folks reading this, that probably shouldn't happen.

File a bug report to let the "compiler folks" know. AFAIK, the compiler should never fail in such a way. They might also be able to find a workaround for you.

https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports

Directly calling into LLVM intrinsics is a bit of an exception to that rule, though.

1 Like

It's likely much easier just to return a flag to the caller that indicates abnormal termination (just like Result would do).

No, it isn't easier. That means that my exception handler would have to rationalize the stack itself which is in fact the purpose of setjmp/longjmp. These are POSIX functions and really it's not so unusual to expect them or their functionality to be available. If Rust has a more Rust way of dealing with the exceptional I'll use it. But until then unsafe {}.

That said, it makes a lot more sense for setjmp/longjmp to be in libc than for me to use LLVM bindings. That was an expediency to be revisited later.

BTW, there's a good article on HN, POSIX Has Become Outdated:

http://www.cs.columbia.edu/~vatlidak/resources/POSIXmagazine.pdf

What I was trying to say is that your code becomes much easier to reason about if you make the control flow explicit. This is especially true if longjmp is used to return farther up the stack, across multiple function calls.

And until the Rust compiler knows about setjmp, the function will not work correctly in all cases because I doubt that the compiler authors currently assume that certain functions can return twice within the same activation frame. (This is why LLVM has intrinsics: to recognize these function calls and disable optimizations which are incorrect in the presence of such calls.)

Just in case anyone stumbles across this thread and wonders, "so how do I access setjmp/longjmp (via LLVM's SJLJ intrinsics), here is a snippet (playground link) that should help.

#![feature(link_llvm_intrinsics)]
extern crate libc; // 0.2.42

use std::mem;
use std::time::{SystemTime};

const WIDTH: usize = mem::size_of::<usize>()*8;
type Checkpoint = [i8; WIDTH];

static mut JUMP_POINT: Checkpoint = [0; WIDTH];

extern {
    #[link_name = "llvm.setjmp"]
    pub fn setjmp(a: *mut i8) -> i32;
    #[link_name = "llvm.longjmp"]
    pub fn longjmp(a: *mut i8, b: i32) -> ();
}

fn now() {
    let now = SystemTime::now();
    println!("{:?}", now);
    
    let ptr: *mut Checkpoint = unsafe { &mut JUMP_POINT };
    let franken_pointer: *mut i8 = unsafe { mem::transmute(ptr) };
    unsafe { longjmp(franken_pointer, 1) };
}

fn main() {
    //let mut buff: Checkpoint = [0; WIDTH];
    let ptr: *mut Checkpoint = unsafe { &mut JUMP_POINT } ;
    let franken_pointer: *mut i8 = unsafe { mem::transmute(ptr) };
    
    let rc = unsafe { setjmp(franken_pointer) };
    print!("{}\n", rc);
    if rc != 0 {
        println!("early return!");
    } else {
        println!("jump point was successfully set.");
        now();
    }
}

Some remarks:

I've used the type alias Checkpoint rather than setjmp_buf (as per GCC's docs) because it feels more descriptive to me.

Checkpoint is wider than strictly necessary. LLVM's documentation says that it only needs a "five word buffer".

i32 @llvm.eh.sjlj.setjmp(i8* %setjmp_buf)

...

The single parameter is a pointer to a five word buffer in which the calling context is saved. The front end places the frame pointer in the first word, and the target implementation of this intrinsic should place the destination address for a llvm.eh.sjlj.longjmp in the second word. The following three words are available for use in a target-specific manner.

3 Likes