Language Reference

BearVM’s input format is a textual IR. Programs consist of optional struct definitions followed by one or more function definitions. The entry point is a function named main.

File Extension

BearVM source files use the .bear extension.

Types

The IR supports the following types:

  • int — 64-bit signed integer.

  • float — single-precision floating point.

  • double — double-precision floating point.

  • string — string value.

Constants

The const keyword marks a value as a constant. Float and double literals require an explicit type annotation:

%n = const 42
%s = const "hello"
%f = const 3.14: float
%d = const 2.0: double

Type Casts

The cast instruction converts a value to another type:

%dbl          = cast double %sum
%back_to_float = cast float %dbl
%int_from_float = cast int %f_result

Any numeric type (int, float, double) can be cast to any other numeric type.

Struct Definitions

Structs are defined at the top level, before any functions:

struct Person {
    name: string
    age: int
}

Fields may be of type int, float, double, or string. Struct definitions must appear before any function that uses them.

Function Definitions

Functions are declared with @name, an optional parameter list, and an optional return type:

@main(): int {
    ret 0
}

@add(%a: int, %b: int): int {
    %result = add %a, %b
    ret %result
}

Functions with no return value omit the return type annotation:

@print_something {
    call puts("hello")
    ret 0
}

Registers

All locals are registers, prefixed with %:

%x = const 10
%y = add %x, 1

Registers have no declared type; their type is inferred from the instruction that produces them. A register may be reassigned within the high-level while sugar, but in explicit SSA form each name is assigned exactly once and phi is used at merge points.

Explicit Basic Blocks (SSA form)

Functions can be written with explicit labeled basic blocks. Labels are declared with a name followed by a colon. Control flow between blocks uses jmp and br_if. Values that flow across block boundaries are resolved with phi:

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

loop_cond:
    %cond0 = lt %i0, %n
    br_if %cond0, loop_body, loop_end

loop_body:
    %tmp0 = add %a0, %b0
    %a1   = %b0
    %b1   = %tmp0
    %i1   = add %i0, 1
    %a0   = %a1
    %b0   = %b1
    %i0   = %i1
    jmp loop_cond

loop_end:
    ret %a0
}

The phi instruction selects a value based on which block was the predecessor. Its syntax is:

%x = phi [label1: %reg1, label2: %reg2]

The following example shows a float-returning function alongside the main entry point using cast and putf:

@float_example(): float {
entry:
    %f1  = const 3.14: float
    %f2  = const 2.0: float
    %sum = add %f1, %f2
    %dbl = cast double %sum
    %back_to_float = cast float %dbl
    ret %back_to_float
}

@main(): int {
entry:
    %n        = const 10
    %fib_result = call fib(%n)
    call puts(%fib_result)
    %f_result = call float_example()
    call putf(%f_result)
    %int_from_float = cast int %f_result
    call puts(%int_from_float)
    call flush()
    ret 0
}

While Sugar

For simpler control flow, the while construct is available:

@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
}

while is the only high-level control flow construct. It is designed to make lowering complex control flow easier. It lowers to the same representation as the explicit basic-block form.

Struct Literals

A struct literal initialises all fields inline:

store %p_ptr, { name: "Alice", age: 25 }

This form is used with store to initialise a heap-allocated struct.

Field Access

Fields of a struct stored in a register can be accessed and mutated with set:

%p = Person {
    name: "Alice"
    age: 25
}

call puts(%p.name)
set %p.age = add %p.age, 1

For heap-allocated structs, use get_field_ref followed by load or store (see Instruction Reference).

Named Constants

Two named constants are defined for use with the open builtin:

  • READ — opens a file for reading.

  • WRITE — opens a file for writing.

Entry Point

Every program must define a @main function. The interpreter reports an error if it is absent.

Comments

Comments begin with ; and extend to the end of the line:

; this is a comment
%x = const 1  ; inline comment