#setjmp #longjmp #no-alloc

no-std sjlj2

Safer, cheaper and more ergonomic setjmp/longjmp in Rust

1 unstable release

new 0.2.0 Mar 15, 2025
0.1.0 Mar 14, 2025

#778 in Embedded development

47 downloads per month

MIT/Apache

30KB
576 lines

Safer[^1], cheaper and more ergonomic setjmp/longjmp in Rust[^2]

[^1]: long_jump is still unsafe and is technically UB, though. See more about safety in docs of long_jump.

[^2]: ...and assembly. No C trampoline is involved!

crates.io docs.rs CI

See more in documentations.


lib.rs:

Safer[^1], cheaper and more ergonomic setjmp/longjmp in Rust[^2].

[^1]: long_jump is still unsafe and is technically UB, though. See more about safety in long_jump. [^2]: ...and assembly. No C trampoline is involved!

  • Ergonomic and safer* Rusty API for typical usages. Closure API instead of multiple-return.

    Multiple-return functions are undefined behaviors due to fatal interaction with optimizer. This crate does not suffer from the misoptimization (covered in tests/smoke.rs).

    ⚠️ We admit that since it's not yet undefined to force unwind Rust POFs and/or longjmp's half execution semantic, long_jump is still technically undefined behavior. But this crate is an attempt to make a semantically-correct abstraction free from misoptimization, and you accept the risk by using this crate. If you find any misoptimization in practice, please open an issue.

  • Single-use jump checkpoint.

    No jump-after-jump disaster. No coroutine-at-home.

  • Minimal memory and performance footprint.

    Single usize JumpPoint. Let optimizer save only necessary states rather than bulk saving all callee-saved registers. Inline-able set_jump without procedure call cost.

    • 2.3ns set_jump setup and 3.2ns long_jump on a modern x86_64 CPU. ~300-490x faster than catch_unwind-panic_any!.
  • No std.

    This crate is #[no_std] and does not use alloc either. It is suitable for embedded environment.

use std::num::NonZero;
use sjlj2::JumpPoint;

let mut a = 42;
// Execute with a jump checkpoint. Both closures can return a value.
let b = JumpPoint::set_jump(
    // The ordinary path that is always executed.
    |jump_point| {
        a = 13;
        // Jump back to the alternative path with a `NonZero<usize>` value.
        // SAFETY: All frames between `set_jump` and `long_jump` are POFs.
        unsafe {
            jump_point.long_jump(NonZero::new(99).unwrap());
        }
    },
    // The alternative path which is only executed once `long_jump` is called.
    |v| {
        // The carried value.
        v.get()
    }
);
assert_eq!(a, 13);
assert_eq!(b, 99);

Features

  • unstable-asm-goto: enable use of asm_goto and asm_goto_with_outputs unstable features.

    This requires a nightly rustc, but produces more optimal code with one-less conditional jump.

    ⚠️ Warning: asm_goto_with_outputs is reported to be buggy in some cases. It is unknown that if our code is affected. Do NOT enable this feature unless you accept the risk. aarch64-apple-darwin is known to be buggy with this feature, thus is incompatible.

Supported architecture

  • x86 (i686)
  • x86_64
  • riscv64
  • riscv32 (with and without E-extension)
  • aarch64 (ARMv8)
  • arm

Similar crates

  • setjmp

    • Generates from C thus needs a correctly-setup C compiler to build.
    • Unknown performance because it fails to build for me. (Poor compatibility?)
    • Suffers from misoptimization.
  • sjlj

    • Uses inline assembly but involving an un-inline-able call instruction.
    • Only x86_64 is supported.
    • Suffers from misoptimization.
    • Slower long_jump because of more register restoring.

No runtime deps