BearVM

A clean and expressive IR that compiles to QBE and LLVM, runs interpreted at speed, or fires through an experimental JIT on Apple Silicon.

fib.bear
@fib(%n: int): int {
entry:
    %a0 = const 0
    %b0 = const 1
    %i0 = const 0
    jmp loop_cond

loop_cond:
    %a    = phi [entry: %a0, loop_body: %a_next]
    %b    = phi [entry: %b0, loop_body: %b_next]
    %i    = phi [entry: %i0, loop_body: %i_next]
    %cond = lt %i, %n
    br_if %cond, loop_body, loop_end

loop_body:
    %tmp    = add %a, %b
    %a_next = %b
    %b_next = %tmp
    %i_next = add %i, 1
    jmp loop_cond

loop_end:
    ret %a
}
@fib(%n: int): int {
entry:
    %cond0 = le %n, 1
    br_if %cond0, base_case, recursive_case

base_case:
    ret %n

recursive_case:
    %n1 = sub %n, 1
    %n2 = sub %n, 2

    %task1 = spawn call fib(%n1)
    %task2 = spawn call fib(%n2)

    %r1 = sync %task1
    %r2 = sync %task2

    %res = add %r1, %r2
    ret %res
}
@test_arena {
    %arena = arena_create

    %a = arena_alloc %arena, 64
    %b = arena_alloc %arena, 128
    %c = arena_alloc %arena, 32

    arena_destroy %arena
    ret 0
}

@test_free {
    %p       = alloc Person
    %age_ref = get_field_ref %p, age
    store %age_ref, 42
    %val     = load %age_ref
    call puts(%val)
    free %p
    ret 0
}
SSA IR Form
QBE + LLVM Compile Targets
2x faster than Lua Interpreted Speed
aarch64 JIT Target
Features
What's in the IR
SSA form

Static single-assignment throughout. Phi nodes, explicit basic blocks, and clean control flow. Optimization passes are straightforward to write and reason about.

Multiple backends

Lower to QBE for fast compilation, LLVM for optimized output, or run directly in the interpreter. Same source, different targets.

Experimental JIT

Direct aarch64 code generation targeting Apple Silicon. No intermediate steps. Further platforms are planned.

Task parallelism

First-class spawn and sync primitives. Express concurrent workloads directly in the IR.

Arena allocators

Built-in arena_create, arena_alloc, and arena_destroy alongside manual alloc / free.

Structs and arrays

Aggregate types with get_field_ref and get_index_ref. Load and store structs by pointer as in any systems IR.

Syntax
Explicit CFG or while-loop sugar

Both forms lower to the same internal representation.

explicit basic blocks + phi SSA
@fib(%n: int): int {
entry:
    %a0 = const 0
    %b0 = const 1
    %i0 = const 0
    jmp loop_cond
loop_cond:
    %a = phi [entry: %a0, loop_body: %a_next]
    %i = phi [entry: %i0, loop_body: %i_next]
    %cond = lt %i, %n
    br_if %cond, loop_body, loop_end
loop_body:
    %tmp    = add %a, %b
    %a_next = %b
    %i_next = add %i, 1
    jmp loop_cond
loop_end:
    ret %a
}
while-loop sugar high-level
@fib(%n: int): int {
    %a = const 0
    %b = const 1
    %i = const 0
    while (lt %i, %n) {
        %tmp = add %a, %b
        %a   = %b
        %b   = %tmp
        %i   = add %i, 1
    }
    ret %a
}

@main(): int {
    %s = call sum_to(1000000)
    %i = const 0
    while (lt %i, 10000) {
        %r = call fib(70)
        %i = add %i, 1
    }
    call puts("Done.")
    ret 0
}