Welcome to the BedRock language documentation. BedRock is a systems programming language that compiles to bare-metal MIPS-32 big-endian machine code. Every language construct maps directly and transparently to a fixed sequence of hardware instructions — no runtime, no OS abstraction, no magic.
This documentation covers the complete language specification, compiler internals, hardware interfacing primitives, and hard-won guidance on the compiler's known enforcement behaviors and edge cases.
TOC Documentation Sections
Core Philosophy
The three axioms that govern every design decision in the BedRock language and compiler.
1.1 "No Magic"
In BedRock, every operation maps to a known, fixed sequence of machine instructions. There are no hidden runtime allocations, no garbage collector, no implicit type coercions, and no virtual dispatch. What you write is exactly what executes.
This is enforced by design — the table below shows the exact 1-to-1 mapping for common constructs:
| BedRock Construct | Machine Code Behavior |
|---|---|
| let x = 5; | Exactly one sw instruction to a pre-assigned static address. No stack frame creation, no heap allocation. |
| let arr = [1, 2, 3]; | Compiler statically assigns a base address in the DATA segment, emits three sw instructions during initialization. All accesses are compile-time pointer arithmetic. |
| fn foo() { ... } | Exactly one prologue (addiu $sp, -32; sw $ra, 28($sp)) and one epilogue (lw $ra, 28($sp); addiu $sp, +32; jr $ra). Nothing else. |
.bin output to confirm.1.2 "Byte-Level Validation"
Numbers in BedRock are unsigned 64-bit integers at parse time, collapsed to 32-bit at code generation. All memory addresses are 32-bit values. There are no floating-point types, no signed arithmetic in the type system, and no implicit widening or truncation.
The compiler validates output at the byte level by:
- Encoding all instructions as big-endian 32-bit words via
to_be_bytes() - Writing a raw
.binfile with zero padding assumptions - Optionally writing a 1.44 MB floppy image (
bedrock_os.img) by embedding the binary at sector 0
1.3 "Direct Hardware Control"
BedRock provides five mechanisms for hardware access that map directly to bare-metal MIPS operations. There is no operating system abstraction layer.
| Construct | Hardware Operation | MIPS Instruction |
|---|---|---|
| poke(addr, val) | Write 32-bit word to physical address | sw $val, 0($addr) |
| peek(addr) | Read 32-bit word from physical address | lw $dest, 0($addr) |
| outb(port, val) | Memory-mapped I/O write | sw $val, 0($port) |
| inb(port) | Memory-mapped I/O read | lw $dest, 0($port) |
| asm("hex") | Emit raw machine instruction | Direct word emission |
Lexical Grammar
Reserved keywords, operator tokens, literal types, and preprocessor directives recognized by the BedRock lexer.
2.1 Keywords
The following identifiers are reserved and cannot be used as variable or function names.
| Keyword | Category | Description |
|---|---|---|
| fn | Declaration | Function definition |
| let | Declaration | Local/static variable declaration |
| root | Declaration | Hardware/environment constant declaration |
| return | Control Flow | Return from function |
| if | Control Flow | Conditional branch |
| else | Control Flow | Alternative branch |
| while | Control Flow | Conditional loop |
| loop | Control Flow | Infinite loop |
| break | Control Flow | Exit innermost loop |
| asm | Hardware | Emit raw machine instruction |
| outb | Hardware | Memory-mapped I/O write |
| inb | Hardware | Memory-mapped I/O read |
| poke | Hardware | Direct memory write |
| peek | Hardware | Direct memory read |
| in | Hardware | Wait for keyboard input (alias for WaitKey) |
| include | Preprocessor | Include another source file (textual substitution) |
| call | Hardware / Pointer | Jump to a runtime address via jalr — function pointer call |
2.2 Operators
Arithmetic Operators
| Symbol | Token | MIPS Instruction |
|---|---|---|
| + | Plus | addu |
| - | Minus | subu |
| * | Star | mult + mflo |
| / | Slash | div + mflo |
| ^ | Caret | xor |
Bitwise Operators
| Symbol | Token | MIPS Instruction |
|---|---|---|
| & | Ampersand | and |
| | | Pipe | or |
| ^ | Caret | xor |
| << | ShiftLeft | sllv |
| >> | ShiftRight | srlv |
Comparison Operators
| Symbol | Token | MIPS Sequence |
|---|---|---|
| == | EqEq | xor + sltiu $d, $d, 1 |
| != | NotEq | xor + sltu $d, $0, $d |
| > | Greater | slt (operands swapped) |
| < | Less | slt |
| >= | GreaterEq | slt + xori $d, $d, 1 |
| <= | LessEq | slt (swapped) + xori $d, $d, 1 |
Assignment
| Symbol | Token | Description |
|---|---|---|
| = | Equal | Assignment (not comparison — use == for equality testing) |
2.3 Literals
Integer Literals
BedRock supports decimal and hexadecimal integer literals. All integers are stored as u64 during lexing.
String Literals
Strings are delimited by double quotes, stored in the DATA segment as null-terminated byte arrays (C-style). Escape sequences are not processed — see §8.8.
Array Literals
Arrays are comma-separated lists of integer literals only enclosed in [ ]. Variables and expressions are not permitted as elements — see §8.2.
2.4 Comments & Include Directive
The include directive is a preprocessor feature handled before lexing. It performs textual substitution of the named file's content into the current file. Include paths are resolved relative to the source file's directory and processed recursively.
Memory Model
BedRock's flat 32-bit address space, segment layout, variable binding semantics, and stack frame structure.
3.1 Address Space
BedRock uses a flat 32-bit address space. The layout is determined by three root declarations:
| Root Symbol | Default Value | Description |
|---|---|---|
| BASE | 0x80000000 | Code segment start address |
| DATA | BASE + 0x10000 | Static data segment start address |
| STACK | BASE + 0x20000 | Initial stack pointer value |
3.2 Segment Layout
3.3 let — Static Variable
let declares a static variable. In global scope, the compiler allocates a fixed address in the DATA segment and emits sw (store word) instructions to initialize it. Variables do not occupy stack space in global scope.
let-declared variable's address is resolved entirely at compile time. There is no dynamic allocation.3.4 root — Hardware / Environment Constant
root declares a compile-time constant mapping a name to a fixed 32-bit address or value. Unlike let, a root symbol is never dereferenced. When used in an expression, it evaluates to the raw address value itself — not the value stored at that address.
| Declaration | Expression Behavior | Use Case |
|---|---|---|
| let x = 5 | Loads value 5 from memory address | Variables, counters, state |
| root ADDR = 0xB8000 | Loads the literal value 0xB8000 — no memory dereference | Hardware addresses, config constants |
root declaration must be a literal integer. Expressions are not evaluated. See §8.1.3.5 Stack Frame Structure
Every function call creates a 32-byte stack frame:
Prologue (emitted automatically)
Epilogue (emitted automatically)
3.6 Register Allocation
The IR backend (default) uses a pool of 18 physical registers (MIPS regs 8–25: $t0–$t9 and $s0–$s7) for virtual register allocation. The legacy bridge path (--bridge) uses 14 registers. Allocation is done by the RegAlloc struct — if the pool is exhausted, the variable is spilled to a stack slot and loaded/stored as needed.
| Register | MIPS Number | Role |
|---|---|---|
| $sp | 29 | Stack pointer |
| $ra | 31 | Return address (saved by JAL) |
| $v0 | 2 | Function return value |
| $a0–$a3 | 4–7 | Function arguments (caller convention) |
| $t0–$t7 | 8–15 | Temporary expression registers (pool of 8) |
alloc_reg() finds the pool empty, it silently returns register 8 ($t0), potentially corrupting a live value. See §8.9 for mitigation.Detailed Syntax Guide
Complete syntax reference for variables, arrays, strings, functions, control flow, and inline assembly.
4.1 Variable Declarations
4.2 Arrays & Strings
Array Definition & Access
Arrays are statically allocated in the DATA segment. Only integer literals are permitted as element values. The compiler scales all indices by 4 (sll idx, idx, 2) — each element is a 32-bit word.
String Definition
Strings are stored as null-terminated byte arrays in the DATA segment, padded to the next 4-byte boundary.
4.3 Arithmetic & Expressions
Precedence rules — three levels (highest to lowest):
High: *, /, <<, >> (handled in parse_factor)
Medium: +, -, &, |, ^ (handled in parse_term)
Low: ==, !=, >, <, >=, <= (handled in parse_expression)
Use parentheses liberally to make intent explicit — see §8.6.
4.4 Function Definitions
$a0–$a3. Return value is always in $v0 — see §8.4 and §8.5.4.5 Control Flow
if / else
while
loop / break
An infinite loop. Exits only via break or a return from the enclosing function.
4.6 Inline Assembly
Emits a single 32-bit MIPS instruction directly into the code stream. The argument must be the exact 32-bit encoding as a hex string — without the 0x prefix.
4.7 call() — Function Pointers
The call(expr) statement jumps to a runtime address stored in a variable or computed from an expression. Unlike a regular function call which uses an absolute jal, call() emits a jalr instruction — it loads the target address at runtime from whatever expression is given.
Statement::CallPtr in ast.rs, Token::Call in lexer.rs, callptr_stmt() in parser.rs, and the jalr emitter in codegen/mod.rs.Syntax
Basic Usage — Stored Address
MIPS Instructions Generated
Encoding of jalr $ra, $tX
| Register $tX | MIPS Number | jalr Encoding (hex) |
|---|---|---|
| $t0 | 8 | 0x0100F809 |
| $t1 | 9 | 0x0120F809 |
| $t2 | 10 | 0x0140F809 |
| $t3 | 11 | 0x0160F809 |
| $t4 | 12 | 0x0180F809 |
| $t5 | 13 | 0x01A0F809 |
| $t6 | 14 | 0x01C0F809 |
| $t7 | 15 | 0x01E0F809 |
Dynamic Dispatch — Computed Address
The expression inside call() can be any valid BedRock expression — not just a simple variable. The compiler evaluates the full expression and jumps to the resulting address.
call() is a statement — it does not capture $v0. If the called function returns a value, it will be in $v0 but the compiler does not assign it anywhere. Use a regular named function call (let r = func();) if you need the return value.call(expr) does not push arguments onto the stack. The callee must read its inputs directly from known memory addresses or registers if needed. For full argument passing, use a named function call.Comparison: call() vs Named Call
| Feature | call(ptr) | func_name(args) |
|---|---|---|
| Target resolution | Runtime (register) | Compile-time (absolute jal) |
| MIPS instruction | jalr $ra, $tX | jal 0xABCDEF |
| Argument passing | ❌ None | ✅ Via stack |
| Return value | ❌ Not captured | ✅ In $v0 / let |
| Dynamic dispatch | ✅ Any expression | ❌ Static only |
| Flash-safe (PIC) | ✅ If address is correct | ❌ Absolute address |
Instruction Generation
The compiler's eight-phase pipeline, MIPS encoding patterns, control flow generation, and binary output format.
5.1 Compiler Pipeline
The compiler operates in a single pass with eight sequential phases:
| Phase | Name | Action |
|---|---|---|
| Phase 0 | Root Symbol Collection | Resolves BASE, DATA, STACK addresses |
| Phase 1 | Bootloader Emission | NOP, li $sp STACK, J <main_start> (patched later) |
| Phase 2 | Static Data Allocation | Assigns addresses to arrays and strings in DATA segment |
| Phase 3 | Function Code Generation | Emits function bodies with prologue/epilogue |
| Phase 4 | Jump Patch | Back-patches the Phase 1 J instruction to point past functions |
| Phase 5 | Static Data Initialization | Emits sw instructions to write array/string data into memory |
| Phase 6 | Main Code Generation | Emits all non-root, non-function, non-static-data statements |
| Phase 7 | Halt | Emits J <self> infinite loop |
5.2 Load Immediate (emit_li)
All constant loading uses the following pattern based on the value's bit width:
5.3 Binary Operation Code Generation
All binary operations follow the pattern: allocate regs → gen left → gen right → emit instruction → free regs.
Arithmetic Encodings
| BedRock | MIPS Encoding | Notes |
|---|---|---|
| a + b | addu $d, $s, $t | 0x00000021 | ... |
| a - b | subu $d, $s, $t | 0x00000023 | ... |
| a * b | mult $s, $t + mflo $d | Result from LO register |
| a / b | div $s, $t + mflo $d | Quotient from LO register |
| a ^ b | xor $d, $s, $t | 0x00000026 | ... |
| a & b | and $d, $s, $t | 0x00000024 | ... |
| a | b | or $d, $s, $t | 0x00000025 | ... |
Comparison Encodings
| BedRock | MIPS Sequence | Result |
|---|---|---|
| a == b | xor $d,$s,$t + sltiu $d,$d,1 | 1 if equal |
| a != b | xor $d,$s,$t + sltu $d,$0,$d | 1 if not equal |
| a < b | slt $d,$s,$t | 1 if s < t |
| a > b | slt $d,$t,$s | Operands swapped |
| a >= b | slt $d,$s,$t + xori $d,$d,1 | Invert lt |
| a <= b | slt $d,$t,$s + xori $d,$d,1 | Invert gt |
5.4 Control Flow Code Generation
if Statement
while Loop
loop (Infinite)
Function Call (jal)
5.5 Memory Operations
| BedRock | MIPS | Description |
|---|---|---|
| poke(addr, val) | sw $val, 0($addr) | 32-bit store to physical address |
| peek(addr) | lw $dest, 0($addr) | 32-bit load from physical address |
| outb(port, val) | sw $val, 0($port) | MMIO write (identical to sw) |
| inb(port) | lw $dest, 0($port) | MMIO read (identical to lw) |
5.6 Binary Output Format
The compiler emits a flat binary of 32-bit big-endian MIPS instructions:
| Output File | Description |
|---|---|
| <source>.bin | Raw binary of the compiled program (flat MIPS words) |
| <source>.map.json | Source map: per-instruction line number, absolute address, and 32-bit opcode — useful for debugging and binary auditing |
| bedrock_os.img | 1,474,560-byte floppy disk image (1.44 MB), binary at offset 0, first sector = 512 bytes |
Hardware Interfacing
Complete reference for direct memory access, MMIO I/O port operations, VGA text mode, keyboard input, and raw instruction injection.
6.1 Direct Memory Writes — poke
poke writes a 32-bit value to an absolute physical address. No bounds checking occurs.
Machine Code Generated
6.2 Direct Memory Reads — peek
peek reads a 32-bit value from an absolute physical address and returns it as an expression value (in $v0 / the allocated register).
6.3 I/O Port Access — outb / inb
BedRock implements I/O as MMIO. outb and inb are functionally identical to poke and peek at the machine code level — both emit sw and lw instructions respectively. The distinction is semantic only, signaling programmer intent.
6.4 VGA Text Mode — Practical Reference
VGA text mode buffer begins at physical address 0xB8000. Each character cell is 2 bytes.
Color Constants (Foreground)
| Color | Value | Color | Value |
|---|---|---|---|
| Black | 0x0 | Light Gray | 0x7 |
| Blue | 0x1 | Dark Gray | 0x8 |
| Green | 0x2 | Light Blue | 0x9 |
| Cyan | 0x3 | Light Green | 0xA |
| Red | 0x4 | Light Cyan | 0xB |
| Magenta | 0x5 | Light Red | 0xC |
| Brown | 0x6 | White | 0xF |
Cell Address Formula
Example — Print 'H' in White on Black at (0,0)
Example — Clear Entire Screen
6.5 Keyboard Input — in
The keyword in (used as an expression) reads from the keyboard input address 0x80020000 (hardcoded in the compiler). Use inb with an explicit port address to override.
6.6 Raw Instruction Injection — asm
Use asm for instructions not expressible in BedRock's grammar. Always document the encoding.
Best Practices
Proven patterns for writing maintainable, auditable, and correct bare-metal BedRock programs.
7.1 The Address Library Pattern
Declare all hardware addresses as root constants in a dedicated include file. This creates a zero-cost hardware abstraction layer and eliminates magic numbers from your code.
❌ Without Address Library (Avoid)
✅ With Address Library (Preferred)
7.2 Write Pure Functions
Functions that only operate on their parameters and local variables are predictable, testable, and cacheable. Avoid functions that modify hardware state without naming what they target.
7.3 Use Loops for Bulk Hardware Operations
7.4 Always Set BASE, DATA, STACK Explicitly
7.5 Use asm Sparingly and Document It
Inline assembly breaks the readability contract of BedRock. When you must use it, always document the instruction's human-readable name and effect.
let variables at global scope are static and persist for the program lifetime. Prefer passing values through function parameters and return values to keep program state explicit and auditable.Compiler Enforcement & Edge Cases
Twelve critical behaviors, limitations, and silent failure modes that every BedRock developer must understand before writing production code.
8.1 root Requires Literal Number Values
The right-hand side of a root declaration must be a literal integer. Expressions are not evaluated.
8.2 Array Literals Allow Only Integer Literals
Array literal values must be compile-time integer constants. Variables and expressions are not permitted — the parser will panic.
8.3 Unresolved Variable Panics at Codegen
Referencing an undeclared variable causes a panic! in the codegen phase. There is no graceful error recovery — the compiler process terminates.
8.4 Function Call Arguments Are Positional Only
BedRock has no named parameters at call sites. Arguments are matched by position. The number of arguments passed is not validated by the parser.
8.5 Return Value Is Always $v0
There is only one return register ($v0 = register 2). Multiple return values are not supported. Calling two functions in a complex expression may overwrite $v0 before it is consumed.
8.6 No Operator Precedence for Bitwise / Comparison
BedRock has three precedence levels. High: *, /, <<, >>. Medium: +, -, &, |, ^. Low: ==, !=, >, <, >=, <=. Bitwise operators like & bind before comparisons, but after shifts — still use parentheses to make intent explicit.
8.7 ! Without = Is a Lexer Error
The lexer only recognizes !=. A lone ! produces Token::EOF and terminates lexing silently — not an error message.
8.8 String Literals Do Not Support Escape Sequences
The lexer reads string content character-by-character until the closing ". Backslash sequences are stored literally as two characters.
8.9 Register Pool Exhaustion (Legacy Bridge Path Only)
In the legacy bridge path (--bridge), the register pool contains 14 registers. If exhausted, alloc_reg() silently returns register 8 ($t0), corrupting a live value without any warning. The default IR pipeline spills to stack instead — no silent corruption.
8.10 Shift Operators (<< / >>)
BedRock includes native shift operators in the lexer and codegen. Both logical left-shift (<<) and logical right-shift (>>) are first-class operators and compile to the corresponding MIPS sllv / srlv instructions.
asm workaround for shifts is no longer necessary, though raw asm remains available for advanced shift amounts or coprocessor operations.8.11 Integer Overflow Is Silent
All arithmetic uses unsigned MIPS addu/subu. Overflow wraps silently at 2³² − 1. There are no overflow traps or checks at compile time or runtime.
0xFFFFFFFF + 1 produces 0x00000000 with no warning, no exception, and no way to detect it after the fact.8.12 Division by Zero Is Undefined
BedRock emits a raw MIPS div instruction. On MIPS, division by zero produces an unpredictable value in LO and does not raise an exception unless the hardware is explicitly configured to do so. There is no compile-time or runtime guard.
if guard or assert non-zero before any / expression where the denominator could be variable.Structs
Named composite types that group fields together at compile-time offsets. BedRock structs are purely a compiler layout mechanism — no runtime type information, no VTable, no padding beyond 4-byte word alignment.
9.1 Defining a Struct
Use the struct keyword followed by the type name and a list of fields. Each field has a name and a type.
field 0 → offset 0, field 1 → offset 4, field 2 → offset 8, and so on. There is no padding or alignment adjustment — every field is a 32-bit word.9.2 Instantiating a Struct
Use the StructInstance statement to bind a variable name to a struct type. The compiler allocates one virtual register per field, named varname__fieldname.
9.3 Field Access
Fields are accessed with dot notation. A read generates a get IR op (→ lw), a write generates a mov IR op (→ sw).
| BedRock Expression | IR Lowering | MIPS Output |
|---|---|---|
| pos.x = 10 | Mov %pos.x, 10 | li $tX, 10 / sw $tX, DATA+off |
| let v = pos.x | Get %tN, %pos__x | lw $tN, DATA+off |
9.4 Struct Internals (Compiler View)
Structs are a purely compile-time construct. The IrBuilder reads Statement::StructDefine to build an internal layout table mapping each field name to a byte offset. When a StructInstance is lowered, it emits one Const IR op per field, creating named virtual registers like p__x, p__y.
poke / peek and the offset values.Interrupt System
BedRock provides first-class support for hardware interrupt handlers via three dedicated keywords: int, int_enable, and int_disable. Handlers are written as ordinary BedRock function bodies but are registered as interrupt service routines at the MIPS exception vector level.
10.1 Defining an Interrupt Handler
Use the int keyword to define an interrupt service routine (ISR). The body executes in interrupt context and should return as quickly as possible. The compiler automatically appends an ERET (exception return) at the end.
int becomes a label in the output binary. The compiler automatically saves and restores $ra, $t8, and $s0–$s3 in the prologue/epilogue, then emits ERET (0x42000018) to return from the exception.10.2 Enabling Interrupts — int_enable
The int_enable(vector, handler) statement installs an ISR at a specific interrupt vector. The first argument must be a compile-time constant — the vector number. The second is the handler label defined with int.
10.3 Disabling Interrupts — int_disable
The int_disable statement (no arguments) globally disables all hardware interrupts by clearing the IE bit in the MIPS Status coprocessor register.
10.4 Complete Example
10.5 IR Lowering
The three interrupt keywords lower to the following IR operations:
| BedRock Statement | IR Op | Notes |
|---|---|---|
int name { ... } | Mk "name" + body + Ret | Handler body emitted as a labeled block |
int_enable(vec, handler) | Int vec, handler | The MIPS backend writes handler address to vector table |
int_disable | IntDisable | Backend emits mtc0 to clear IE bit in Status |
Pruner, Transformer, and Quantum optimizer passes all handle IntHandler and IntEnable nodes correctly — the body is traversed for live symbol analysis, constant folding, and instruction scheduling, just like a regular function body.Optimizer
BedRock includes a three-pass optimizer pipeline activated via the --optimize flag. Each pass operates on the AST before IR lowering. Three levels are available, each stacking additional passes.
11.1 Optimizer Levels
| Flag | Passes Applied | Description |
|---|---|---|
--optimize 1 | Pruner | Constant folding + dead code elimination |
--optimize 2 | Pruner → Transformer | + Loop-invariant code motion + strength reduction |
--optimize 3 | Pruner → Transformer → Quantum | + Register allocation + pipeline scheduling |
| (none) | — | No optimization; raw AST passed to IR builder |
11.2 Pass 1 — Pruner (Constant Folding & DCE)
The Pruner performs two transformations in sequence:
- Constant Folding: If both operands of a binary expression are compile-time constants, the compiler evaluates the expression at compile time and replaces it with a literal. All operators are supported: arithmetic, bitwise, shifts, and comparisons.
- Dead Code Elimination (DCE): Variables and arrays that are declared but never read (no use in any expression) are removed from the output. Function names and assignments to live variables are always preserved.
if condition folds to a constant, the dead branch is replaced with a no-op (Return(None)). A while(0) loop is similarly removed. This is safe to do at compile time.11.3 Pass 2 — Transformer (LICM & Strength Reduction)
The Transformer applies two analyses to every function and loop body:
- Loop-Invariant Code Motion (LICM): Statements whose right-hand-side operands are never modified inside a loop body are hoisted out to before the loop. This eliminates redundant recomputation on every iteration.
- Strength Reduction: Multiplications and divisions by powers of two are replaced with shift instructions (
*2ⁿ→<< n,/2ⁿ→>> n,%2ⁿ→& (2ⁿ-1)), which are faster and smaller on MIPS.
11.4 Pass 3 — Quantum (Register Allocation & Scheduling)
The Quantum pass implements a linear-scan register allocator and an instruction scheduler. It models 18 available machine registers.
- Live interval computation: Scans the entire program to determine the first-definition (start) and last-use (end) of every variable.
- Linear-scan allocation: Assigns physical registers to variables in order of their start time. When all 18 registers are busy, the variable with the longest remaining live range is spilled.
- Pipeline scheduling: Reorders statements within a block, placing independent operations (non-spilled variables) before spill-dependent ones to hide memory latency.
lw has a 1-cycle delay slot.IR Pipeline
BedRock now has a full intermediate representation (BedRock-IR) that decouples the frontend from the backend. The IR pipeline is the default compilation path since Rev 2.0 and enables multi-target output.
12.1 Pipeline Overview
12.2 IR Opcodes
The BedRock IR uses a simple three-address code format. Every instruction has an opcode and 0–3 operands.
| Opcode | Operands | Description |
|---|---|---|
Mov | dst, src | Copy value or immediate to virtual register |
Add/Sub/Mul/Div | dst, l, r | Arithmetic binary ops |
And/Orr/Xor/Shl/Shr | dst, l, r | Bitwise and shift ops |
Get | dst, addr | Load from memory address (→ lw) |
Bri | val, addr | Store to memory address (→ sw) |
Cal | label | Function call (→ jal) |
Ret | [val] | Return from function |
Psh / Pop | val / dst | Push/pop arguments for function calls |
Mk | label | Emit a label (function entry or branch target) |
Go | label | Unconditional jump (→ j) |
Cf | l, r | Compare two operands (sets condition) |
Jf | cond, label | Conditional jump if condition is false |
Df | label, val | Data definition (emits word in DATA segment) |
Inb / Outb | dst/val, port | Memory-mapped I/O read/write |
Int | vector, handler | Register interrupt handler at vector |
IntDisable | — | Disable all hardware interrupts |
Asm | raw hex str | Emit raw 32-bit instruction word |
Halt | — | Emit infinite jump-to-self (end of program) |
12.3 Targets & CLI Flags
| Flag | Effect |
|---|---|
--target mips | Default. IR → MIPS-32 big-endian .bin |
--target arm | IR → ARM backend (stub — exits with error) |
--target ir | IR → text IR listing .ir file |
--emit-ir | Print IR to stdout and exit (no binary written) |
--bridge | Skip IR; use legacy AST → MIPS codegen path |
--optimize N | Run N optimizer passes (1–3) |
--emit-ir or --target ir to audit the IR that your source compiles to before committing to a binary. This is the recommended debugging workflow for complex programs.12.4 Source Map
After compilation, the MIPS backend writes a .map.json file alongside the binary. Each entry records the BedRock source line, the output binary address, the raw 32-bit instruction word, and a human-readable description.
This source map is consumed by the VS Code debugger extension for live MIPS breakpoints correlated to BedRock source lines.