はじめに

クイックスタート

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");

シグネチャ文字

文字
iint
ddouble
ffloat
ppointer
sstring

使用例

// 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

制限事項

デバッグ支援

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サイクル)8ms50,000
定期タスク(1秒サイクル)800ms500,000
バッチ処理5000ms1,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);

これにより、ポインタアクセスは以下に制限されます:

ハードウェアアクセスは検証済みFFI関数を経由する必要があります。


高度なトピック

以下を含む高度なトピック:

詳細はアーキテクチャページをご覧ください。

商用展開ベストプラクティス:

詳細はベストプラクティスページをご覧ください。