# FRB — Five Runtime Binary > Why Five uses FRB instead of Harbour's HRB Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com). All rights reserved. ## Overview FRB (Five Runtime Binary) is Five's dynamic module format for loading and executing compiled PRG code at runtime. It replaces Harbour's HRB (Harbour Runtime Binary) with a dual-mode architecture: **native compilation** for maximum performance and **pcode interpretation** for maximum portability. ## The Problem with HRB Harbour's HRB format stores pcode bytecode — an intermediate representation that must be interpreted by the Harbour Virtual Machine at runtime: ``` PRG Source → Harbour Compiler → HRB (pcode bytecode) → VM Interpreter → Execution ``` This architecture has inherent limitations: 1. **Performance**: Every instruction passes through the interpreter loop — decode, dispatch, execute. For compute-heavy code, this overhead is significant. 2. **No optimization**: Pcode bypasses CPU branch prediction, register allocation, and instruction-level parallelism. 3. **No concurrency**: HRB modules cannot use threads safely due to Harbour's chronic threading limitations. 4. **No native integration**: HRB cannot call Go (or C) functions directly without marshaling through the VM layer. ## The FRB Solution Five's FRB provides **two execution modes** in a single format: ### Mode 1: Native (Go Plugin) ``` PRG Source → Five Compiler → Go Source → Go Compiler → Native Plugin (.so) ↓ FRB Container (4.7 MB) ``` The `.frb` file contains a compiled Go shared library. When loaded, code executes at full native speed — identical to a statically compiled binary. ### Mode 2: Pcode (Interpreter) ``` PRG Source → Five Compiler → Pcode Bytecode → FRB Container (175 bytes) ↓ Five Pcode Interpreter ``` The `.frb` file contains compact bytecode. No Go compiler needed on the target machine. The pcode interpreter calls the same Thread operations as native code, ensuring identical behavior. ## Dual-Mode Architecture The key insight: **the same PRG source compiles to both modes**. The developer chooses at build time; the runtime transparently handles either format. ```bash # Native mode — maximum performance (requires Go on build machine only) five frb module.prg -o module.frb # Pcode mode — maximum portability (runs anywhere Five runs) five frb module.prg -o module.frb --pcode ``` Both produce valid `.frb` files. `FrbLoad()` detects the mode automatically. ## Architecture Comparison | Aspect | HRB (Harbour) | FRB Native | FRB Pcode | |--------|---------------|------------|-----------| | **Content** | Harbour pcode | Go native .so | Five pcode | | **Execution** | Harbour VM | Direct CPU | Five interpreter | | **Speed** | Baseline | 10-100x faster | ~1x (same class) | | **File size** | Small | ~4.7 MB | **175 bytes** | | **Go needed (build)** | N/A | Yes | Yes (five CLI) | | **Go needed (run)** | No | No | **No** | | **Platform (run)** | All Harbour | Linux | **All Five** | | **Goroutines** | No | Yes | Yes | | **Go interop** | No | Native | Via RTL | ## File Format ``` Offset Size Field Description 0 4 Magic 0xC0 'F' 'R' 'B' 4 2 Version uint16 LE (currently 2) 6 1 Mode 0x01 = Native, 0x02 = Pcode 7 1 Reserved 0x00 8 4 SymCount uint32 LE (number of functions) 12 ... Payload Mode-dependent content ``` **Native payload**: Embedded Go plugin binary (ELF .so) **Pcode payload**: Serialized function table: ``` uint16 funcCount For each function: uint16 nameLen + name (null-free) uint16 params uint16 locals uint32 codeLen + bytecode ``` The FRB header is deliberately similar to HRB's `0xC0 'H' 'R' 'B'` for familiarity, with `'F'` replacing `'H'` to indicate the Five format. ## Pcode Instruction Set Five's pcode maps 1:1 to Thread stack operations, making the bytecode a direct serialization of what the native compiler generates as Go function calls: | Opcode | Hex | Description | |--------|-----|-------------| | PcOpPushNil | 0x01 | Push NIL | | PcOpPushInt | 0x04 | Push int64 (8 bytes LE) | | PcOpPushString | 0x06 | Push string (uint16 len + bytes) | | PcOpPushLocal | 0x07 | Push local variable | | PcOpPopLocal | 0x08 | Pop to local variable | | PcOpPlus | 0x10 | Add top two stack values | | PcOpEqual | 0x20 | Compare equality | | PcOpJumpFalse | 0x31 | Conditional jump | | PcOpPushSymbol | 0x40 | Push function symbol by name | | PcOpFunction | 0x42 | Call function with N args | | PcOpReturn | 0x33 | Return from function | Full opcode set: 40+ opcodes covering arithmetic, comparison, logic, flow control, function calls, OOP, arrays, and blocks. ## API Reference ### Command Line ```bash # Build native FRB (maximum speed) five frb mymodule.prg -o mylib.frb # Build pcode FRB (maximum portability, no Go needed to run) five frb mymodule.prg -o mylib.frb --pcode ``` ### File-Based Loading ```harbour // Load FRB module (auto-detects native vs pcode) pMod := FrbLoad("mylib.frb") // Call functions from loaded module result := FrbDo(pMod, "MYFUNC", arg1, arg2) // Unload when done FrbUnload(pMod) ``` ### In-Memory Compilation ```harbour // Compile PRG source string at runtime // Falls back to pcode mode automatically if Go is not installed cSource := 'FUNCTION Double(n)' + Chr(10) + ; ' RETURN n * 2' pMod := FrbCompile(cSource) ? FrbDo(pMod, "DOUBLE", 21) // → 42 FrbUnload(pMod) ``` ### One-Shot Execution ```harbour // Compile + run Main() + unload in one call cProgram := 'FUNCTION Main()' + Chr(10) + ; ' RETURN 6 * 7' ? FrbExec(cProgram) // → 42 ``` ## Function Reference | Function | Description | |----------|-------------| | `FrbLoad(cFile)` | Load .frb file (native or pcode), return module handle | | `FrbDo(pMod, cFunc, ...)` | Call function in loaded module | | `FrbUnload(pMod)` | Unload module, free resources | | `FrbRun(cFile, ...)` | Load + run Main() + unload | | `FrbCompile(cSource)` | Compile PRG source string (auto-selects mode) | | `FrbExec(cSource, ...)` | Compile + run Main() + unload | ## Use Cases ### Plugin Architecture ```harbour // Application loads plugins at startup LOCAL aPlugins := Directory("plugins/*.frb") FOR EACH cFile IN aPlugins LOCAL pPlugin := FrbLoad("plugins/" + cFile[1]) FrbDo(pPlugin, "INIT") NEXT ``` ### Hot Code Reload ```harbour // Read PRG source from database or network cSource := MemoRead("custom_report.prg") pMod := FrbCompile(cSource) FrbDo(pMod, "GENERATEREPORT", dStart, dEnd) FrbUnload(pMod) ``` ### User-Defined Business Rules ```harbour // Store business rules as PRG text in database cRule := GetRuleFromDB("DISCOUNT_CALC") pRule := FrbCompile(cRule) nDiscount := FrbDo(pRule, "CALCULATE", nAmount, cCustomerType) FrbUnload(pRule) ``` ### Dynamic Code with Goroutines ```harbour // Compile a worker function at runtime, run it in a goroutine cWorker := 'FUNCTION Worker(ch, n)' + Chr(10) + ; ' ChSend(ch, n * n)' + Chr(10) + ; ' RETURN NIL' FrbCompile(cWorker) ch := Channel(1) Go("WORKER", ch, 42) ? ChReceive(ch) // → 1764 ``` ## Deployment Strategy | Scenario | Recommended Mode | Reason | |----------|-----------------|--------| | Performance-critical server | Native | Maximum speed | | End-user distribution | **Pcode** | No Go dependency | | Development / testing | Native | Faster iteration | | Cross-platform plugins | **Pcode** | Works everywhere | | Embedded business rules | **Pcode** | Tiny file size | | Compute-heavy algorithms | Native | CPU-bound benefit | ### Recommended Workflow 1. **Development**: Use native mode for fast debugging with full Go optimization 2. **Distribution**: Ship pcode `.frb` files alongside the compiled Five binary 3. **Hot reload**: Use `FrbCompile()` — auto-falls back to pcode if Go unavailable ## Symbol Scoping and Isolation FRB modules operate in an isolated scope to prevent name collisions between the host program and loaded modules. This is critical for plugin architectures where multiple modules may define functions with the same name. ### Scoping Rules | Scenario | Behavior | |----------|----------| | `FrbDo(pMod, "FUNC")` | Module scope first, then VM global | | Module defines `Main()` | Always module-local; never registered in VM | | Module function = host function (same name) | Host function preserved; module function accessible only via `FrbDo()` | | Module function = new name (not in host) | Registered in VM global scope; callable directly from host | | `FrbUnload(pMod)` | Newly registered symbols removed; overwritten symbols restored | ### How It Works When `FrbLoad()` loads a module: 1. All module functions are stored in the module's **local symbol table**. 2. `Main()` is **never** exported to the VM — it stays module-private. 3. For each non-Main function: - If a function with the same name already exists in the VM: **skip** (host function protected). - If the name is new: **register** in the VM global scope. 4. The module records what it registered and what it would have overwritten. When `FrbDo(pMod, "FUNC", ...)` is called: 1. First searches the **module's local scope** — finds module-private functions. 2. If not found locally, falls back to the **VM global scope**. 3. This means `FrbDo()` always reaches the module's version of a function, even if the host has a different function with the same name. When `FrbUnload(pMod)` is called: 1. All symbols the module registered globally are **removed** from the VM. 2. Any host symbols that were saved are **restored** to their original state. 3. The VM returns to exactly the state it had before `FrbLoad()`. ### Example: Name Collision ```harbour // Host program defines Add() as (a+b)*10 FUNCTION Add(a, b) RETURN (a + b) * 10 FUNCTION Main() ? Add(1, 2) // → 30 (host function) pMod := FrbLoad("mathlib.frb") // Module also defines Add() as a+b ? Add(1, 2) // → 30 (host function still works!) ? FrbDo(pMod, "ADD", 100, 200) // → 300 (module's Add via FrbDo) FrbUnload(pMod) ? Add(1, 2) // → 30 (fully restored) RETURN NIL ``` ### Comparison with Harbour HRB Binding Modes | Harbour HRB | Five FRB | Description | |-------------|----------|-------------| | `HB_HRB_BIND_DEFAULT` | Default behavior | Don't overwrite existing functions | | `HB_HRB_BIND_OVERLOAD` | (not needed) | FrbDo() always reaches module scope | | `HB_HRB_BIND_FORCELOCAL` | Default for Main() | Entry point always module-private | Five simplifies Harbour's binding modes into a single intuitive behavior: module functions are always accessible via `FrbDo()`, host functions are always protected, and `FrbUnload()` cleanly restores the original state. ## Limitations ### Native Mode - Linux only (Go plugin limitation) - FRB and host binary must use same Go version - Go plugins cannot be truly unloaded from memory - Larger file size (~4.7 MB per module) ### Pcode Mode - Slower than native (interpreter overhead) - Advanced features may have limited pcode support - No direct Go interop from pcode (uses RTL functions) ## Migration from Harbour HRB | Harbour | Five | |---------|------| | `hb_hrbLoad(cFile)` | `FrbLoad(cFile)` | | `hb_hrbDo(pHrb, ...)` | `FrbDo(pMod, cFunc, ...)` | | `hb_hrbUnload(pHrb)` | `FrbUnload(pMod)` | | `hb_hrbRun(cFile, ...)` | `FrbRun(cFile, ...)` | | `hb_compileFromBuf(cSrc)` | `FrbCompile(cSrc)` | | N/A | `FrbExec(cSrc, ...)` | | `.hrb` extension | `.frb` extension | | Pcode only | **Native + Pcode dual mode** | The API is deliberately similar to Harbour's for easy migration. Existing HRB workflows translate directly to FRB with the added benefit of choosing between native speed and universal portability. ## Verified Test Results ``` === FRB Pcode Mode Test === Hello: Hello, World! (from FRB module) 175 bytes, no Go needed Add: 300 Arithmetic works Factorial: 3628800 Recursion works === FRB Native Mode Test === Hello: Hello, Five! (from FRB module) 4.7 MB, native speed Add(100, 200): 300 Direct Go execution Factorial(10): 3628800 Compiled recursion ```