Architecture

VM Architecture Overview

Lambda C uses a compact, three-layer architecture optimized for embedded systems:

System Layers

1. Host Application (C)

2. Lambda C VM (Compact)

3. Script Layer (C subset)


4-Byte Instruction Format

Why 4 Bytes?

Lambda C's 4-byte instruction format is optimized for embedded systems with strict size and performance constraints.

What This Achieves:

Instruction Encoding

Each instruction is packed into 4 bytes:

[31:24] Opcode (8 bits) - Instruction type
[23:16] Operand 1 (8 bits) - Register/immediate
[15:8]  Operand 2 (8 bits) - Register/immediate
[7:0]   Operand 3 (8 bits) - Register/immediate

Example: Integer Addition

ADD r1, r2, r3  →  [0x01][r1][r2][r3]

Example: FFI Call

CALL func_id, argc  →  [0x20][func_id][argc][0x00]

Comparison: Bytecode Density

ProgramLambda C (4-byte)Lua 5.4MicroPython
Fibonacci (25 iter)~200 bytes~450 bytes~800 bytes
1225 instructions~5 KB~12 KB~20 KB

Memory Architecture

Three Stack Model

Lambda C uses three independent stacks for safety and efficiency:

1. VM Stack (vm->stack)

Purpose: Script variables and temporary values

Structure:

typedef union {
    int i;
    double d;
    void *p;
    char bytes[8];
} vm_stack_cell_t;

Characteristics:

2. C Stack

Purpose: VM internal function calls, intrinsics

Characteristics:

3. Type Stack (vm->type_stack)

Purpose: Runtime type tracking for Compact VM

Characteristics:

Memory Layout Example (Cortex-M0+, 32KB RAM)

0x20000000  ┌─────────────────────┐
            │  C Stack (4KB)      │  OS, interrupts, VM calls
0x20001000  ├─────────────────────┤
            │  VM Stack (8KB)     │  Script variables
0x20003000  ├─────────────────────┤
            │  Global Area (8KB)  │  Global variables
0x20005000  ├─────────────────────┤
            │  Heap (12KB)        │  malloc (arena)
0x20008000  └─────────────────────┘

FFI (Foreign Function Interface)

Load-Time Linking

Lambda C resolves function symbols once at load time, enabling O(1) constant-time dispatch during execution. This eliminates repeated symbol lookups and provides predictable FFI call overhead.

Linking Process

Step 1: Registration (Host)

lcvm_register_function_ex("get_time",   ffi_get_time,   0, "");
lcvm_register_function_ex("gpio_write", ffi_gpio_write, 2, "ii");

Step 2: Load Bytecode

lcvm_compact_load("script.lcbc", &prog, 0);

Step 3: Link (Symbol → ID conversion)

lcvm_link_program(&prog);  // "get_time" → function ID 0

Step 4: Runtime Dispatch

CALL 0, 0  →  Direct jump to ffi_get_time (no lookup!)

FFI Dispatch Performance

OperationTimeMechanism
Lambda C~100nsO(1) table lookup
Lua 5.4~200nsHash table lookup
MicroPython~500nsString comparison

Type Marshalling

Lambda C automatically converts between C types and VM types:

From Script to Host (FFI call)

// Script
gpio_write(13, 1);

// Host FFI implementation
void ffi_gpio_write(LcvmState *vm) {
    int value, pin;
    lcvm_pop_v(&value, sizeof(int));  // Pop in reverse order
    lcvm_pop_v(&pin, sizeof(int));

    HAL_GPIO_WritePin(GPIO_PORT, pin, value);
}

From Host to Script (return value)

// Host FFI
void ffi_get_time(LcvmState *vm) {
    double t = system_time_ms() / 1000.0;
    lcvm_push_double(vm, t);  // Return to script
}

// Script
double t = get_time();

Sandbox Mode

Security Isolation

Sandbox mode restricts pointer access to prevent memory corruption and unauthorized hardware access. It is enabled by default — every memory access through the VM is range-checked unless you explicitly turn it off:

lcvm_set_sandbox_mode(0);   // disable (or set LCVM_SANDBOX=0)

The default-on state means scripts cannot stray outside the VM-managed regions without a deliberate opt-out.

Allowed Memory Regions

While the sandbox is on (the default), pointer access is permitted to:

Forbidden:

Hardware Access Pattern

// WRONG: Direct hardware access (blocked in sandbox)
int *GPIO_REGISTER = (int *)0x40020000;
*GPIO_REGISTER = 1;  // Runtime error!

// CORRECT: Via FFI
gpio_write(13, 1);  // Calls validated FFI function

Sandbox Use Cases

  1. Safety-Critical: Prevent accidental hardware damage and memory corruption from script bugs
  2. Fault containment: A buggy script can corrupt VM data but cannot reach OS memory, MMIO, or other tasks

The sandbox is a memory-safety mechanism, not a defense against hostile bytecode: there is no instruction-level verifier, and .lcbc is trusted to come from the operator's own toolchain over a trusted channel. Lambda C does not target multi-tenant execution or untrusted-code isolation — see the homepage trust model.


Hot-Reload Mechanism

Zero-Downtime Update

Lambda C supports reloading scripts without restarting the VM or losing state.

Hot-Reload Sequence

void vm_hot_reload(LcvmState *vm, compact_program_t *old_prog,
                   const char *new_script_path) {
    compact_program_t new_prog;

    // 1. Load new bytecode
    lcvm_compact_load(new_script_path, &new_prog, 0);

    // 2. Re-link FFI
    lcvm_link_program(&new_prog);

    // 3. Reset VM state
    lcvm_reset(vm);
    lcvm_compact_init_garea(vm, &new_prog);

    // 4. Free old program, swap in new
    lcvm_compact_free(old_prog);
    *old_prog = new_prog;

    // 5. Continue execution
    lcvm_run_compact(vm, old_prog, 0);
}

Use Case: Game Development

while (game_running) {
    if (key_pressed('R')) {
        // Recompile script
        system("lcvmc -oc gameplay.lcbc gameplay.c");

        // Hot-reload
        vm_hot_reload(&vm, &prog, "gameplay.lcbc");
    }

    // Run game logic
    lcvm_run_compact(&vm, &prog, 0);
    render_frame();
}

Watchdog System

Dual Protection

Lambda C provides two watchdog mechanisms:

1. Time-Based Watchdog

Prevents infinite loops from hanging the system:

lcvm_set_watchdog_ms(100);  // Max 100ms execution time

Example:

// Script with infinite loop
while (1) {
    // ... work ...
}
// VM aborts after 100ms with LCVM_ERR_WATCHDOG_TIME

2. Instruction Count Watchdog

CPU-speed independent protection:

lcvm_set_watchdog_ic(100000);  // Max 100,000 instructions

Example:

for (i = 0; i < 1000000; i++) {
    // ... work ...
}
// VM aborts after 100,000 instructions
Application TypeTime LimitInstruction Limit
Real-time control (10ms cycle)8ms50,000
Periodic task (1s cycle)800ms500,000
Batch processing5000ms1,000,000+
Development/Debug0 (disabled)0 (disabled)

Error Recovery

Graceful Degradation

Lambda C uses setjmp/longjmp for non-local error handling:

int lcvm_run_compact(LcvmState *vm, compact_program_t *prog, int trace) {
    if (setjmp(vm->error_jmp) != 0) {
        // Error occurred - safely return
        return -1;
    }

    // Normal execution
    // ...
}

Error Handling Pattern

int ret = lcvm_run_compact(&vm, &prog, 0);

if (ret != 0) {
    // Get detailed error info
    LcvmErrorCode code = lcvm_get_error_code(&vm);
    const char *msg = lcvm_get_error_message(&vm);
    int pc = lcvm_get_error_pc(&vm);
    int line = lcvm_get_error_line(&vm);

    fprintf(stderr, "Error: %s at PC=%d, line=%d\n", msg, pc, line);

    // Take corrective action
    switch (code) {
        case LCVM_ERR_DIV_ZERO:
            // Log and reset
            break;
        case LCVM_ERR_HEAP_EXHAUSTED:
            lcvm_heap_reset();
            break;
    }
}

Static Type Mode

~2x Speedup

Compile with -Os option to omit type information on the stack (12 bytes/entry), significantly improving execution speed:

lcvmc -Os -oc output.lcbc source.c

Benchmark (fib(25))

ModeExecution Time
Normal mode27ms
Static type mode13ms

Technical Background

In normal mode, the VM tracks type information for each value on the stack. Static type mode skips this type tracking for integer-focused programs where types are determined at compile time.

Limitations:


Function Pointer Support

Callback Pattern

Lambda C fully supports function pointers in Compact VM:

typedef int (*operation_t)(int, int);

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

int apply(operation_t op, int x, int y) {
    return op(x, y);
}

int main() {
    int r1 = apply(add, 10, 5);  // 15
    int r2 = apply(sub, 10, 5);  // 5
    return 0;
}

State Transition Table

Efficient state management using function pointer arrays:

typedef void (*state_handler_t)();

void state_idle()   { /* ... */ }
void state_run()    { /* ... */ }
void state_stop()   { /* ... */ }

state_handler_t handlers[3] = { state_idle, state_run, state_stop };

void execute_state(int state) {
    handlers[state]();
}

Performance Characteristics

Benchmark Results

Fibonacci(25) Calculation

Instruction Throughput

Memory Efficiency

Typical Runtime Memory Usage

Scalability


Design Decisions

Why 4 Bytes?

Alternatives Considered:

4-byte Advantages:

Why Arena Allocator?

No free() by Design:

Trade-off: Must call lcvm_heap_reset() periodically

Why C Subset?

Advantages over Lua/Python:

Designed for Orchestration: Lambda C is intentionally a subset, not a general-purpose language. For embedded orchestration scenarios, simplicity is a strength: