Best Practices

Production Deployment

Memory Configuration

Target-Specific Recommendations

Cortex-M0+ (32KB RAM)

#define VM_STACK_SIZE   (8 * 1024)   // 8KB
#define VM_GAREA_SIZE   (8 * 1024)   // 8KB
#define VM_HEAP_SIZE    (12 * 1024)  // 12KB
// Remaining 4KB: C stack, OS, drivers

Cortex-M3/M4 (64KB RAM)

#define VM_STACK_SIZE   (16 * 1024)  // 16KB
#define VM_GAREA_SIZE   (16 * 1024)  // 16KB
#define VM_HEAP_SIZE    (24 * 1024)  // 24KB
// Remaining 8KB: C stack, OS, drivers

ESP32 (320KB RAM)

#define VM_STACK_SIZE   (32 * 1024)  // 32KB
#define VM_GAREA_SIZE   (32 * 1024)  // 32KB
#define VM_HEAP_SIZE    (128 * 1024) // 128KB
// Remaining: WiFi/BLE, FreeRTOS, etc.

Memory Sizing Guidelines

1. Stack Size: Analyze deepest call path

2. Global Area Size: Total global variables

3. Heap Size: Maximum dynamic allocation


Initialization Best Practices

Pattern A: Automatic Allocation (Simple)

#include "common.h"
#include "bytecode.h"

int main() {
    LcvmState vm;
    compact_program_t prog;

    // 1. Initialize VM (default: 100KB stack + 100KB garea)
    lcvm_init(&vm);

    // 2. Load bytecode
    lcvm_compact_load("script.lcbc", &prog, 0);

    // 3. Register FFI functions (with type signature)
    lcvm_register_function_ex("my_func", ffi_my_func, 1, "i");

    // 4. Link FFI
    lcvm_link_program(&prog);

    // 5. Initialize global variables (IMPORTANT!)
    lcvm_compact_init_garea(&vm, &prog);

    // 6. Run
    lcvm_run_compact(&vm, &prog, 0);

    // 7. Cleanup
    lcvm_compact_free(&prog);
    return 0;
}

Pattern B: Embedded (Static Memory)

/* Cortex-M0+ (32KB RAM) configuration */
#define VM_STACK_SIZE   (8 * 1024)
#define VM_GAREA_SIZE   (8 * 1024)
#define VM_HEAP_SIZE    (12 * 1024)

static char vm_stack[VM_STACK_SIZE];
static char vm_garea[VM_GAREA_SIZE];
static unsigned char vm_heap[VM_HEAP_SIZE];

int embedded_main() {
    LcvmState vm;
    compact_program_t prog;

    // 1. Initialize heap
    lcvm_heap_init(vm_heap, VM_HEAP_SIZE);

    // 2. Initialize VM with external buffers
    lcvm_init_with_memory(&vm, vm_stack, VM_STACK_SIZE,
                          vm_garea, VM_GAREA_SIZE);

    // 3. Load bytecode from Flash ROM
    extern const unsigned char script_lcbc[];
    extern const int script_lcbc_size;

    lcvm_compact_load_mem(script_lcbc, script_lcbc_size, &prog, 0);

    // 4. Register & link FFI
    lcvm_register_function_ex("gpio_write", ffi_gpio_write, 2, "ii");
    lcvm_link_program(&prog);

    // 5. Initialize globals
    lcvm_compact_init_garea(&vm, &prog);

    // 6. Run
    return lcvm_run_compact(&vm, &prog, 0);
}

Reset and Reload

Full VM Reset

IMPORTANT: lcvm_reset() does NOT initialize global variables. This is intentional design (allows host to set custom values after reset).

void vm_full_reset(LcvmState *vm, compact_program_t *prog) {
    // 1. Reset VM state (sp, fp, ic to zero, clear stack/garea)
    lcvm_reset(vm);

    // 2. Restore globals to initial values (must call explicitly)
    lcvm_compact_init_garea(vm, prog);

    // 3. Reset heap (if needed)
    lcvm_heap_reset();
}

Hot-Reload (Script Update)

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

    // 1. Free old program
    lcvm_compact_free(old_prog);

    // 2. Load new bytecode
    if (lcvm_compact_load(new_script_path, &new_prog, 0) != 0) {
        fprintf(stderr, "Failed to load new script\n");
        return;
    }

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

    // 4. Reset VM state
    lcvm_reset(vm);

    // 5. Initialize globals with new values
    lcvm_compact_init_garea(vm, &new_prog);

    // 6. Swap programs
    *old_prog = new_prog;
}

Error Handling

Retry Pattern with Error Recovery

int vm_execute_with_retry(LcvmState *vm, compact_program_t *prog, int max_retries) {
    int ret, i;

    for (i = 0; i < max_retries; i++) {
        ret = lcvm_run_compact(vm, prog, 0);

        if (ret == 0) {
            return 0;  // Success
        }

        // Get error information
        if (lcvm_has_error(vm)) {
            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, "VM Error: %s (code=%d) at PC=%d, line=%d\n",
                    msg, code, pc, line);

            // Handle based on error type
            switch (code) {
                case LCVM_ERR_DIV_ZERO:
                case LCVM_ERR_NULL_PTR:
                    // Script bug - retry won't help
                    return -1;

                case LCVM_ERR_WATCHDOG_TIME:
                case LCVM_ERR_WATCHDOG_IC:
                    // Timeout - increase threshold or optimize script
                    fprintf(stderr, "Watchdog timeout, consider optimization\n");
                    return -1;

                case LCVM_ERR_HEAP_EXHAUSTED:
                    // Heap exhausted - reset and retry
                    fprintf(stderr, "Heap exhausted, resetting...\n");
                    lcvm_reset(vm);
                    lcvm_compact_init_garea(vm, prog);
                    continue;  // Retry

                default:
                    return -1;
            }
        }
    }

    fprintf(stderr, "VM execution failed after %d retries\n", max_retries);
    return -1;
}

Safety-Critical Error Handling

/* Drone control example */
int drone_control_loop(LcvmState *vm, compact_program_t *prog) {
    int ret = lcvm_run_compact(vm, prog, 0);

    if (ret != 0 || lcvm_has_error(vm)) {
        // Error occurred - immediate failsafe mode
        emergency_landing();
        log_error("VM error during flight control");
        return -1;
    }

    return 0;
}

Long-Running Operation

Periodic Heap Reset

Arena Allocator doesn't free(), so heap is exhaustible. Reset periodically:

void periodic_maintenance(LcvmState *vm, compact_program_t *prog) {
    static int cycle_count = 0;

    cycle_count++;

    // Reset heap every 1000 cycles
    if (cycle_count % 1000 == 0) {
        lcvm_reset(vm);
        lcvm_compact_init_garea(vm, prog);
        lcvm_heap_reset();
        printf("Heap reset at cycle %d\n", cycle_count);
    }
}

Memory Leak Monitoring

Monitor for unexpected heap exhaustion:

void check_heap_usage(void) {
    // Test if large malloc succeeds
    void *test = lcvm_malloc(1024);
    if (test == NULL) {
        fprintf(stderr, "Warning: Heap exhausted\n");
        // Reset recommended
    } else {
        lcvm_free(test);  // No-op in Arena, but call anyway
    }
}

Heap Watermark Usage

Ideal for temporary bulk allocation and batch release patterns:

void process_sensor_data(LcvmState *vm, compact_program_t *prog) {
    // Record current heap position
    size_t mark = lcvm_heap_mark();

    // Allocate temporary buffers
    char *buffer = lcvm_malloc(4096);
    int *sensor_array = lcvm_malloc(100 * sizeof(int));

    // Process sensor data
    read_sensors(sensor_array, 100);
    process_data(buffer, sensor_array);
    send_result(buffer);

    // Batch release to mark position (O(1), no fragmentation)
    lcvm_heap_release(mark);
}

Benefits:


Debug Support Features

Watchpoint Feature

Detect unexpected global variable modifications:

Watch range is specified by an offset and size into garea (size must be 1, 2, 4, or 8).

// Build with: LCVM_WATCH_ENABLED=1

void setup_debugging(LcvmState *vm,
                     int motor_off, int emerg_off, int sensor_off) {
    // Add watch targets (up to 8)
    lcvm_watch_add(vm, motor_off,  sizeof(int), "motor_speed");
    lcvm_watch_add(vm, emerg_off,  sizeof(int), "emergency_flag");
    lcvm_watch_add(vm, sensor_off, sizeof(int), "sensor_state");
}

void run_with_monitoring(LcvmState *vm, compact_program_t *prog,
                         int motor_off, int emerg_off, int sensor_off) {
    setup_debugging(vm, motor_off, emerg_off, sensor_off);

    lcvm_run_compact(vm, prog, 0);

    // Clear watches
    lcvm_watch_clear(vm);
}

Writes that overlap a watched range are automatically logged.

GDB Pretty Printer

For embedded debugging:

(gdb) source tools/gdb_lcvm.py
(gdb) p vm
$1 = LcvmState { pc=42, sp=8, fp=4, ic=12345, error=NONE }
(gdb) p vm->stack[0:8]
...

Shows detailed info (call stack, registers, opcode) on error.

Postmortem Tool

Diagnostic dump output and analysis on crash:

The function returns the number of bytes written to the buffer; the caller forwards them to Flash, serial, a file, etc.

void emergency_handler(LcvmState *vm) {
    if (lcvm_has_error(vm)) {
        unsigned char buf[256];
        int len = lcvm_dump_diagnostic_binary(vm, buf, sizeof(buf));
        if (len > 0) {
            // e.g. persist to non-volatile storage
            FILE *fp = fopen("crash.bin", "wb");
            if (fp) {
                fwrite(buf, 1, len, fp);
                fclose(fp);
            }
        }
    }
}

Analyze on PC:

python tools/lcvm_postmortem.py crash.bin

C Stack Depth Management

The Problem

Lambda C uses three stacks:

  1. VM Stack (vm->stack) - monitored, overflow detected
  2. C Stack - OS-managed, NOT monitored by VM
  3. Type Stack (vm->type_stack) - internal type tracking

Risk: Deep script recursion can overflow C stack without detection.

Solution 1: Compile-Time Limit

Adjust max parse depth in common.h:

#ifndef LCVM_MAX_PARSE_DEPTH
#define LCVM_MAX_PARSE_DEPTH 100  // Default: 100 levels
#endif

Recommended values:

Solution 2: Runtime Recursion Counter

static int recursion_depth = 0;
#define MAX_RECURSION_DEPTH 50

void ffi_user_function(LcvmState *vm) {
    // Check recursion depth
    if (recursion_depth >= MAX_RECURSION_DEPTH) {
        lcvm_record_error_ex(vm, LCVM_ERR_STACK_OVERFLOW,
                             "C stack depth limit exceeded", 0);
        return;
    }

    recursion_depth++;

    // Do work
    int result = expensive_calculation();
    lcvm_push_v(&result, sizeof(int));

    recursion_depth--;
}

Solution 3: Avoid Recursion in Scripts

BAD (deep recursion):

int fib(int n) {
    if (n <= 1) return n;
    return fib(n-1) + fib(n-2);  // Exponential stack usage
}

GOOD (iterative):

int fib(int n) {
    int a = 0, b = 1, c, i;
    if (n == 0) return a;
    for (i = 2; i <= n; i++) {
        c = a + b;
        a = b;
        b = c;
    }
    return b;
}

Watchdog Tuning

Initial Conservative Settings

// Start strict, tune based on measurements
lcvm_set_watchdog_ms(100);   // 100ms limit
lcvm_set_watchdog_ic(100000); // 100k instruction limit

// After measuring actual usage, set to 1.5-2x measured values

Adaptive Adjustment

void adaptive_watchdog(LcvmState *vm, compact_program_t *prog) {
    clock_t start, end;
    long elapsed_ms;

    start = clock();
    lcvm_run_compact(vm, prog, 0);
    end = clock();

    elapsed_ms = (end - start) * 1000 / CLOCKS_PER_SEC;

    // Set new threshold to 2x measured time
    lcvm_set_watchdog_ms(elapsed_ms * 2);
}

Sandbox Mode

Sandbox State

Sandbox mode is on by default after lcvm_init(), so pointer access is already range-checked. You only call the setter to disable it for trusted bytecode that needs a bare-metal escape hatch:

LcvmState vm;
lcvm_init(&vm);   // sandbox already enabled

// Opt out only for fully trusted bytecode (or set LCVM_SANDBOX=0):
// lcvm_set_sandbox_mode(0);

// While enabled (the default), pointer access is restricted to:
// - vm->stack
// - vm->garea
// - Heap regions

Hardware Access via FFI

/* Script side */
gpio_write(13, 1);  // GPIO pin 13 HIGH

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

    // Validate pin number
    if (pin < 0 || pin >= NUM_GPIO_PINS) {
        lcvm_record_error(vm, "Invalid GPIO pin", 0);
        return;
    }

    // Hardware access
    HAL_GPIO_WritePin(GPIO_PORT, pin, value);
}

Troubleshooting

Q1: Global Variables Not Initialized

Symptom: Previous values persist after script re-execution

Cause: lcvm_reset() does not initialize globals (by design)

Solution:

lcvm_reset(&vm);
lcvm_compact_init_garea(&vm, &prog);  // Add this

Q2: Alignment Error (Bus Error / HardFault)

Symptom: Crashes on ARM Cortex-M0

Cause: Direct casting instead of using vm_stack_cell_t

Solution: Use VM_STACK_CELL() macro

// BAD
int *p = (int *)&vm.stack[vm.sp];

// GOOD
VM_STACK_CELL(vm.stack, vm.sp)->i = 42;

Q3: Heap Exhaustion

Symptom: lcvm_malloc() returns NULL

Cause: Arena Allocator doesn't free()

Solution:

lcvm_heap_reset();  // Call periodically

Q4: Frequent Watchdog Timeout

Symptom: "watchdog: time limit exceeded"

Cause: Threshold too strict or inefficient script

Solution:

  1. Increase threshold: lcvm_set_watchdog_ms(200);
  2. Optimize script (reduce malloc in loops, etc.)

Q5: FFI Function Not Found

Symptom: "Failed to link program"

Cause: Function not registered before lcvm_link_program()

Solution:

lcvm_register_function_ex("my_func", ffi_my_func, 1, "i");  // First
lcvm_link_program(&prog);  // Then link

Q6: Float/Double Precision Mismatch

Symptom: Results differ between float and double builds

Cause: Compiler and VM precision settings don't match

Solution: Unify at compile time

# Float build
lcvmc -DLCVM_USE_FLOAT -oc script.lcbc script.c

# VM also float
gcc -DLCVM_USE_FLOAT -o lcvm lcvm.c ...

Pre-Production Checklist

Initialization

Memory

Error Handling

Safety

Long-Term Operation

Testing