はじめに
クイックスタート
1. スクリプトを書く
// hello.c
double get_time();
int main() {
double t = get_time();
printf("Time: %.2f\n", t);
return 0;
}2. FFI関数を登録
// host.c
#include "common.h"
#include "bytecode.h"
static double g_time = 0.0;
// FFI関数実装
static void ffi_get_time(LcvmState *vm) {
lcvm_push_double(vm, g_time);
}
int main() {
LcvmState vm;
compact_program_t prog;
// VM初期化
lcvm_init(&vm);
// FFI関数登録(型シグネチャ付き)
lcvm_register_function_ex("get_time", ffi_get_time, 0, "");
// スクリプトコンパイル
system("lcvmc -oc hello.c.lcbc hello.c");
// ロード&リンク
lcvm_compact_load("hello.c.lcbc", &prog, 0);
lcvm_link_program(&prog);
// 実行
g_time = 123.45;
lcvm_run_compact(&vm, &prog, 0);
// クリーンアップ
lcvm_compact_free(&prog);
return 0;
}3. コンパイル&実行
gcc -o host host.c -I./src -L./lib -llcvm ../../src/*.o
./host
出力:
Time: 123.45初期化
パターンA: 自動メモリ確保(簡易版)
#include "common.h"
#include "bytecode.h"
int main() {
LcvmState vm;
compact_program_t prog;
/* 1. VM初期化(デフォルト: 100KB stack + 100KB garea) */
lcvm_init(&vm);
/* 2. バイトコードロード */
lcvm_compact_load("script.lcbc", &prog, 0);
/* 3. FFI関数登録 */
lcvm_register_function("my_func", ffi_my_func, 1);
/* 4. FFIリンク */
lcvm_link_program(&prog);
/* 5. グローバル変数初期化(重要!) */
lcvm_compact_init_garea(&vm, &prog);
/* 6. 実行 */
lcvm_run_compact(&vm, &prog, 0);
/* 7. 後処理 */
lcvm_compact_free(&prog);
return 0;
}パターンB: 組み込み向け(静的メモリ管理)
#include "common.h"
#include "bytecode.h"
/* Cortex-M0+(32KB RAM)の場合 */
#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. ヒープ初期化 */
lcvm_heap_init(vm_heap, VM_HEAP_SIZE);
/* 2. VM初期化(外部バッファ使用) */
lcvm_init_with_memory(&vm, vm_stack, VM_STACK_SIZE,
vm_garea, VM_GAREA_SIZE);
/* 3. 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. FFI登録・リンク */
lcvm_register_function("gpio_write", ffi_gpio_write, 2);
lcvm_link_program(&prog);
/* 5. グローバル変数初期化 */
lcvm_compact_init_garea(&vm, &prog);
/* 6. 実行 */
return lcvm_run_compact(&vm, &prog, 0);
}FFI登録(型シグネチャ)
DEBUGビルドで引数の型チェックを有効にするには lcvm_register_function_ex() を使用します:
// 基本登録
lcvm_register_function("func", ffi_func, 2);
// 型シグネチャ付き登録
lcvm_register_function_ex("func", ffi_func, 2, "id");シグネチャ文字
| 文字 | 型 |
|---|---|
i | int |
d | double |
f | float |
p | pointer |
s | string |
使用例
// int と double を受け取る関数
static void ffi_add(LcvmState *vm) {
double b = lcvm_pop_double(vm);
int a = lcvm_pop_int(vm);
lcvm_push_double(vm, a + b);
}
lcvm_register_function_ex("add", ffi_add, 2, "id");
型ミスマッチ時のログ出力:
FFI add: arg 0 type mismatch (expected i, got pointer)Heap Watermark API
一時的な大量メモリ確保と一括解放のパターンに最適:
// 現在のヒープ位置を記録
size_t mark = lcvm_heap_mark();
// 一時メモリを確保
char *buf = lcvm_malloc(64 * 1024);
// ... 処理 ...
// マーク位置まで一括解放(O(1)、フラグメンテーションなし)
lcvm_heap_release(mark);スクリプト側からの利用
int main() {
int mark = heap_mark();
// 一時的な処理...
heap_release(mark); // 一括解放
return 0;
}静的型モード
-Os オプションでコンパイルすると、約2倍の高速化を実現:
# 通常モード
lcvmc -oc output.lcbc source.c
# 静的型モード(高速化)
lcvmc -Os -oc output.lcbc source.c制限事項
- 整数演算に特化(浮動小数点は通常モード推奨)
- printf/sprintf対応済み。組み込み向けに
LCVM_PRINTF_BUFSIZEでバッファサイズ設定可能(デフォルト: 1024バイト)
デバッグ支援
Watchpoint
グローバル変数の書き換えを自動検出:
// LCVM_WATCH_ENABLED=1 でビルド
lcvm_watch_add("player_hp"); // 監視開始
lcvm_watch_add("game_state");
// ... 実行 ...
lcvm_watch_clear(); // 監視終了GDB Pretty Printer
(gdb) source tools/gdb_lcvm.py
(gdb) p vm
$1 = LcvmState { pc=42, sp=8, error=NONE, ... }Postmortemツール
// ホスト側でダンプ出力
lcvm_dump_diagnostic_binary(&vm, "crash.bin");# PC側で解析
python tools/lcvm_postmortem.py crash.binエラー処理
基本パターン
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; // 成功
}
// エラー情報を取得
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);
// エラー種別に応じた処理
switch (code) {
case LCVM_ERR_HEAP_EXHAUSTED:
// リセットして再実行
lcvm_reset(vm);
lcvm_compact_init_garea(vm, prog);
continue;
default:
return -1;
}
}
}
return -1;
}トラブルシューティング
Q1: グローバル変数が初期化されない
症状: スクリプトを再実行すると、前回の値が残っている
原因: lcvm_reset() はグローバル変数を初期化しない(仕様)
解決方法:
lcvm_reset(&vm);
lcvm_compact_init_garea(&vm, &prog); // これを追加Q2: アライメントエラー(Bus Error / HardFault)
症状: ARM Cortex-M0 で実行時にクラッシュ
原因: vm_stack_cell_t を使わず、直接キャストしている
解決方法: VM_STACK_CELL() マクロを使用
// 悪い例
int *p = (int *)&vm.stack[vm.sp];
// 良い例
VM_STACK_CELL(vm.stack, vm.sp)->i = 42;Q3: ヒープが枯渇する
症状: lcvm_malloc() が NULL を返す
原因: Arena Allocator は free() しない
解決方法:
// 全体リセット
lcvm_heap_reset();
// または部分解放(Heap Watermark)
size_t mark = lcvm_heap_mark();
// ... 処理 ...
lcvm_heap_release(mark);Q4: FFI関数が見つからない
症状: "Failed to link program"
原因: lcvm_link_program() の前に関数登録していない
解決方法:
lcvm_register_function("my_func", ffi_my_func, 1); // 先にこれ
lcvm_link_program(&prog); // その後にリンク
商用展開
推奨メモリ設定
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
// 残り4KB: Cスタック、OS、ドライバ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
// 残り8KB: Cスタック、OS、ドライバESP32 (320KB RAM)
#define VM_STACK_SIZE (32 * 1024) // 32KB
#define VM_GAREA_SIZE (32 * 1024) // 32KB
#define VM_HEAP_SIZE (128 * 1024) // 128KB
// 残り: WiFi/BLE、FreeRTOS等
パフォーマンスチューニング
ウォッチドッグ設定
無限ループを防止し、適時実行を保証します:
時間ベースウォッチドッグ:
lcvm_set_watchdog_ms(100); // 最大100ms実行時間
命令数ベースウォッチドッグ:
lcvm_set_watchdog_ic(100000); // 最大100,000命令推奨設定
| アプリケーション種別 | 時間制限 | 命令数制限 |
|---|---|---|
| リアルタイム制御(10msサイクル) | 8ms | 50,000 |
| 定期タスク(1秒サイクル) | 800ms | 500,000 |
| バッチ処理 | 5000ms | 1,000,000+ |
| 開発/デバッグ | 0(無効) | 0(無効) |
長時間稼働時の運用
24時間365日運用のための定期メンテナンス:
void periodic_maintenance(LcvmState *vm, compact_program_t *prog) {
static int cycle_count = 0;
cycle_count++;
// 1000サイクルごとにヒープをリセット
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);
}
}サンドボックスモード
セーフティクリティカルまたは信頼できないコード向けのサンドボックス:
lcvm_set_sandbox_mode(1);
これにより、ポインタアクセスは以下に制限されます:
- VMスタック(
vm->stack) - グローバル領域(
vm->garea) - ヒープ領域のみ
ハードウェアアクセスは検証済みFFI関数を経由する必要があります。
高度なトピック
以下を含む高度なトピック:
- アーキテクチャ深掘り
- FFIディスパッチ内部構造
- メモリレイアウト最適化
- ホットリロード機構
詳細はアーキテクチャページをご覧ください。
商用展開ベストプラクティス:
- エラー回復パターン
- Cスタック深度管理
- セーフティクリティカル展開
詳細はベストプラクティスページをご覧ください。