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
- Local variables total + call overhead × nesting depth
- Safety margin: 1.5-2x calculated value
2. Global Area Size: Total global variables
- Recorded in compiler output (
.lcbcheader) - Verify with
lcvm_var_dump_all()
3. Heap Size: Maximum dynamic allocation
- Sum of all
malloc()calls between resets (the arena does notfreeindividual blocks, andreallocis not provided) - Safety margin: 2x for error tolerance
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:
- Partial memory release without full reset
- O(1) release time
- No fragmentation concerns
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:
- VM Stack (
vm->stack) - monitored, overflow detected - C Stack - OS-managed, NOT monitored by VM
- 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:
- General PC: 100-200
- Embedded (32KB RAM): 50-100
- Low-end (8KB RAM): 20-50
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 valuesAdaptive 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 regionsHardware 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 thisQ2: 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 periodicallyQ4: Frequent Watchdog Timeout
Symptom: "watchdog: time limit exceeded"
Cause: Threshold too strict or inefficient script
Solution:
- Increase threshold:
lcvm_set_watchdog_ms(200); - 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 linkQ6: 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
-
Called
lcvm_init()orlcvm_init_with_memory() -
Initialized globals with
lcvm_compact_init_garea() -
Registered FFI functions with
lcvm_register_function_ex()(type signatures) -
Linked program with
lcvm_link_program()
Memory
- Stack/global/heap sizes appropriate
-
Alignment handling (
vm_stack_cell_tusage) implemented -
Periodic
lcvm_heap_reset()implemented
Error Handling
-
Check
lcvm_run_compact()return value -
Get details with
lcvm_has_error()on error - Failsafe behavior (safe stop) implemented
Safety
- Watchdog properly configured
- Sandbox setting confirmed (on by default; disable only for trusted bytecode)
- Pointer validation in FFI functions
Long-Term Operation
- Periodic reset mechanism implemented
- Memory usage monitoring implemented
- Error logging mechanism implemented
Testing
- Worst-case testing (max depth, max memory)
- Long-term testing (24+ hours)
- Error injection testing (divide by zero, NULL deref, etc.)