#setjmp #longjmp #no-alloc #jump-point

no-std sjlj2

Safer, cheaper and more ergonomic setjmp/longjmp in Rust

2 releases

new 0.3.1 Apr 23, 2025
0.3.0 Mar 25, 2025
0.2.0 Mar 15, 2025
0.1.0 Mar 14, 2025

#1057 in Embedded development

Download history 219/week @ 2025-03-12 57/week @ 2025-03-19 70/week @ 2025-03-26 2/week @ 2025-04-02 4/week @ 2025-04-09

139 downloads per month

MIT/Apache

34KB
718 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, 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 and long_jump.

    • 2.4ns set_jump setup and 2.9ns long_jump on a modern x86_64 CPU. ~300-490x faster than catch_unwind-panic_any!.
  • no_std support.

    By default, 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

No feature is enabled by default.

  • unwind: Enables unwinding across set_jump boundary from its ordinary closure, by catching and resuming it. This feature requires std.

  • unstable-asm-goto: Enables uses 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.

Supported architectures

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

FIXME: i686-pc-windows-msvc is known to be crashy.

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

    • Only x86_64 is supported.
    • Suffers from misoptimization due to multi-return.
    • Slower long_jump because of more register restoring.

No runtime deps