Files
five/docs/frb.md
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline
- Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch
- RTL: 351 Harbour-compatible functions
- RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization
- Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec)
- HB_FUNC API: Full Harbour C API compatible Go bridge
- Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT
- Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST
- Macro Compiler: Runtime AST parsing and evaluation
- Debugger: TUI debugger with source display, breakpoints, stepping
- FRB: Native + Pcode dual mode runtime binary
- Tests: 13 packages ALL PASS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 09:41:50 +09:00

378 lines
12 KiB
Markdown

# 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
```