Using setjmp longjmp intrinsics or is there another way?

I'd like to use setjmp/longjmp.

First, is there another way to do this in Rust? I'm calling assembly and I need to be able to bail out. So assembly would call Rust which would longjmp to unwind the stack back into Rust.

Ok, LLVM has setjmp/longjmp intrinsics:

http://llvm.org/docs/ExceptionHandling.html

And there is LLVMINT which bills itself as Raw Rust bindings to (essentially) all LLVM intrinsics.

https://github.com/huonw/llvmint

However, before I go down this path, I thought I'd pause and ask for advice.

Chris

2 Likes

I don't think Rust's ownership semantics are well-defined if you longjmp either into or out of Rust code.

The thing that leaps to mind for me is the Drop trait. The LLVM intrinsics have no knowledge of Rust's drop flags, so traversing, or leaving a stack frame with Droppable values in it using longjmp will leak the associated resources, while entering a frame that way will cause any Droppable values to be inappropriately drop()ped without being initialized first.

If you're careful to only ever longjmp out of code that cannot have such values on the stack, and to only ever longjmp into code prepared to be reentered in the middle in ways that aren't formally possible in Rust, you might be able to get away with it anyways, though.

You could, of course, perform the moral equivalent of a longjmp within your asm-language code to return to immediately inside of the call boundary, then return normally to Rust from there…

I guess when I said unwind the stack it sounded like I was longjmp'ing from deep in Rust+asm back into a top level routine. No, I'm calling asm and bailing out of that with a longjmp. So this falls under your careful to only ever longjmp out of code that cannot have such values on the stack category.

I don't want to craft my own longjmp because I'm also using setjmp/longjmp for normal signal handling.

So it looks like I'll be a little on the bleeding edge and use LLVMINT. longjmp is a pretty standard system programming tool and Rust is a systems programming language. I'll figure this out and write it up. I'm a little surprised it hasn't been done already.

2 Likes

I'm not sure that you need to talk to the LLVM intrinsics directly - couldn't you just bind to the libc functions?

I'm not seeing setjmp/longjmp in libc. Am I missing something? Obviously if they're in libc that's the preferred route.

https://github.com/rust-lang/libc

1 Like

You could start with writing your own local FFI bindings to these, and then propose them for the libc crate.

Yep, the only reason they wouldn't be there is that no one's done the work to bind them :slight_smile:

I'm kinda surprised that setjmp/longjmp are not already there.
Aren't these bindings generated by a script?
LLVMINT is generated by a script.

1 Like

That may imply that setjmp/longjmp are less common than you think they are?

The bindings are not script generated.

They're common insofar as being standard since C89, but it's the sort of black magic that I suspect few people actually dabble in.

Well, Rust is ostensibly a systems programming language.
For that sort of language, the black arts will never be very far away.

C++ is also a systems programming language that doesn't mix well with setjmp/longjmp. You can do it, if you're careful, but you can say the same about Rust. The fact that there aren't bindings is surely just because nobody wanted them yet.

Yeah, setjmp/longjmp are warts on any language, but necessary warts.
So I'm trying (with nightly) the LLVM intrinsics approach:

type jmp_buf = [c_int; 37];

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

But I'm getting:

error: linking to LLVM intrinsics is experimental (see issue #29602)

I can't figure out how to get rustc to accept that.

1 Like

Use a nightly compiler and #![feature(link_llvm_intrinsics)].

(edit: sorry, you did try to write that. but I think it has to be a plain crate-level attribute.)

I'm using nightly and I'm not writing a Crate. This is in main.rs and I'll self identify as being fairly new to Rust.
I moved the feature gate to the first line of main.rs:

#![feature(link_llvm_intrinsics)]

and now I get:

error[E0554]: #[feature] may not be used on the beta release channel

The other errors finally went away.

I know what nightly is but I don't know what the beta release channel is. Also, would this be the same error if I used LLVMINT as a crate rather cut+paste the few lines I need?

The main binary is still a crate, as far as this is concerned.

I have no idea why your nightly is acting like a beta though...

At least the declaration seems to work on the playground: Rust Playground

Yes, I pasted that text into a file and compiled it and got the same error:

error[E0554]: #[feature] may not be used on the beta release channel
 --> x.rs:1:1
  |
1 | #![feature(link_llvm_intrinsics)]
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

I'm googling the error message.

How about rustc -Vv?

rustc -Vv x.rs
rustc 1.15.0-beta.3 (a035041ba 2017-01-07)
binary: rustc
commit-hash: a035041ba450ce3061d78a2bdb9c446eb5321d0d
commit-date: 2017-01-07
host: x86_64-apple-darwin
release: 1.15.0-beta.3
LLVM version: 3.9

So indeed, that's a beta compiler, not nightly. Unstable features are only available on nightly builds.

2 Likes