SBBE has no exception mechanism. Error handling is implemented by the frontend using return values, tagged unions, or extern calls to runtime support functions.

Result types (return codes)

The simplest approach: functions return an error code (0 = success, nonzero = error). This is how C and Go handle most errors.

func $parse_int(ptr) -> i32 {
    // Returns 0 on success (result in a global), -1 on error
entry:
    ldl 0
    ldm.u8             // load first byte
    dup

    // Check if it's a digit (0x30-0x39)
    ldi 0x30
    lt.u i32
    jmp.if error

    // ... parsing logic ...
    ldi 0              // success
    ret

error:
    drop
    ldi -1             // error code
    ret
}

Result/Either types (tagged return)

For richer error handling, return a tagged union (see the unions guide) where tag 0 = success with a value and tag 1 = error with an error code or message.

// Result layout:
//   offset 0: i32 tag (0 = Ok, 1 = Err)
//   offset 4: i32 value_or_error

func $divide(i32, i32, ptr) -> i32 {
    // params: a=0, b=1, result_ptr=2
    // Returns 0 on success, 1 on error. Writes to result_ptr.
entry:
    ldl 1
    eqz i32
    jmp.if div_by_zero

    // Success: write tag=0, value=a/b
    ldl 2
    ldi 0
    stm i32            // tag = 0 (Ok)

    ldl 2
    ldi 4
    add.s i64
    ldl 0
    ldl 1
    div.s i32
    stm i32            // value = a / b

    ldi 0              // return success
    ret

div_by_zero:
    // Error: write tag=1, error_code=1
    ldl 2
    ldi 1
    stm i32            // tag = 1 (Err)

    ldl 2
    ldi 4
    add.s i64
    ldi 1
    stm i32            // error_code = 1 (division by zero)

    ldi 1              // return error
    ret
}

setjmp / longjmp (non-local return)

For languages with exceptions or setjmp/longjmp, the frontend can use extern functions to interface with the C runtime:

extern func $setjmp(ptr) -> i32
extern func $longjmp(ptr, i32)

func $try_something(ptr) -> i32 {
    // param 0: jmp_buf pointer
entry:
    ldl 0
    call $setjmp       // returns 0 on first call, nonzero on longjmp
    dup
    eqz i32
    jmp.if try_body
    jmp catch

try_body:
    drop               // discard the 0 from setjmp
    // ... do risky work ...
    // If something goes wrong:
    ldl 0
    ldi 1              // error code
    call $longjmp      // jumps back to setjmp, which returns 1
    ret                // unreachable

catch:
    // setjmp returned nonzero — an error occurred
    // error code is on the stack
    ret
}

Key takeaways