アーキテクチャ

VM アーキテクチャ概要

Lambda C は、組み込みシステムに最適化されたコンパクトな3層アーキテクチャを採用しています。

システム構成

1. ホストアプリケーション (C)

2. Lambda C VM (Compact)

3. スクリプト層 (C言語サブセット)


4バイト命令フォーマット

なぜ4バイトなのか?

Lambda Cの4バイト命令フォーマットは、厳しいサイズと性能制約を持つ組み込みシステム向けに最適化されています。

これにより実現されること:

命令エンコーディング

各命令は4バイトにパックされます:

[31:24] オペコード (8ビット) - 命令種別
[23:16] オペランド1 (8ビット) - レジスタ/即値
[15:8]  オペランド2 (8ビット) - レジスタ/即値
[7:0]   オペランド3 (8ビット) - レジスタ/即値

例: 整数加算

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

例: FFI呼び出し

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

比較: バイトコード密度

プログラムLambda C (4バイト)Lua 5.4MicroPython
Fibonacci (25回)~200バイト~450バイト~800バイト
1225命令~5 KB~12 KB~20 KB

メモリアーキテクチャ

3スタックモデル

Lambda Cは安全性と効率のため、3つの独立したスタックを使用します:

1. VMスタック (vm->stack)

用途: スクリプト変数と一時値

構造:

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

特性:

2. Cスタック

用途: VM内部の関数呼び出し、Intrinsic関数

特性:

3. 型スタック (vm->type_stack)

用途: Compact VMのランタイム型追跡

特性:

メモリレイアウト例 (Cortex-M0+, 32KB RAM)

0x20000000  ┌─────────────────────┐
            │  Cスタック (4KB)     │  OS、割り込み、VM呼び出し
0x20001000  ├─────────────────────┤
            │  VMスタック (8KB)    │  スクリプト変数
0x20003000  ├─────────────────────┤
            │  グローバル領域 (8KB)│  グローバル変数
0x20005000  ├─────────────────────┤
            │  ヒープ (12KB)       │  malloc/realloc
0x20008000  └─────────────────────┘

FFI (Foreign Function Interface)

ロード時リンク

Lambda Cは関数シンボルをロード時に一度だけ解決し、実行時はO(1)の定数時間ディスパッチを実現します。これにより、繰り返しのシンボル検索を排除し、予測可能なFFI呼び出しオーバーヘッドを提供します。

リンクプロセス

ステップ1: 登録(ホスト側)

lcvm_register_function("get_time", ffi_get_time, 0);
lcvm_register_function("gpio_write", ffi_gpio_write, 2);

ステップ2: バイトコードロード

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

ステップ3: リンク(シンボル→ID変換)

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

ステップ4: 実行時ディスパッチ

CALL 0, 0  →  ffi_get_timeへ直接ジャンプ(ルックアップ不要!)

FFIディスパッチ性能

操作時間機構
Lambda C~100nsO(1) テーブルルックアップ
Lua 5.4~200nsハッシュテーブルルックアップ
MicroPython~500ns文字列比較

型マーシャリング

Lambda CはC型とVM型を自動的に変換します:

スクリプトからホストへ(FFI呼び出し)

// スクリプト
gpio_write(13, 1);

// ホストFFI実装
void ffi_gpio_write(LcvmState *vm) {
    int value, pin;
    lcvm_pop_v(&value, sizeof(int));  // 逆順でポップ
    lcvm_pop_v(&pin, sizeof(int));

    HAL_GPIO_WritePin(GPIO_PORT, pin, value);
}

ホストからスクリプトへ(戻り値)

// ホストFFI
void ffi_get_time(LcvmState *vm) {
    double t = system_time_ms() / 1000.0;
    lcvm_push_double(vm, t);  // スクリプトへ返す
}

// スクリプト
double t = get_time();

サンドボックスモード

セキュリティ分離

サンドボックスモードは、メモリ破壊や不正なハードウェアアクセスを防ぐため、ポインタアクセスを制限します。

許可されるメモリ領域

サンドボックス有効時:

lcvm_set_sandbox_mode(1);

許可:

禁止:

ハードウェアアクセスパターン

// 誤り: 直接ハードウェアアクセス(サンドボックスでブロック)
int *GPIO_REGISTER = (int *)0x40020000;
*GPIO_REGISTER = 1;  // 実行時エラー!

// 正しい: FFI経由
gpio_write(13, 1);  // 検証済みFFI関数を呼び出し

サンドボックス利用シーン

  1. 信頼できないスクリプト: ユーザーアップロードコードの安全実行
  2. マルチテナント: 異なるソースのスクリプトを分離
  3. セーフティクリティカル: ハードウェア損傷の偶発的防止

ホットリロード機構

ゼロダウンタイム更新

Lambda Cは、VMを再起動したり状態を失うことなく、スクリプトをリロードできます。

ホットリロードシーケンス

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

    // 1. 新しいバイトコードをロード
    lcvm_compact_load(new_script_path, &new_prog, 0);

    // 2. FFIを再リンク
    lcvm_link_program(&new_prog);

    // 3. VM状態をリセット
    lcvm_reset(vm);
    lcvm_compact_init_garea(vm, &new_prog);

    // 4. 古いプログラムを解放し、新しいものにスワップ
    lcvm_compact_free(old_prog);
    *old_prog = new_prog;

    // 5. 実行を継続
    lcvm_run_compact(vm, old_prog, 0);
}

利用例: ゲーム開発

while (game_running) {
    if (key_pressed('R')) {
        // スクリプトを再コンパイル
        system("lcvmc -oc gameplay.lcbc gameplay.c");

        // ホットリロード
        vm_hot_reload(&vm, &prog, "gameplay.lcbc");
    }

    // ゲームロジックを実行
    lcvm_run_compact(&vm, &prog, 0);
    render_frame();
}

ウォッチドッグシステム

二重保護

Lambda Cは2つのウォッチドッグ機構を提供します:

1. 時間ベースウォッチドッグ

無限ループによるシステムハングを防止:

lcvm_set_watchdog_ms(100);  // 最大100ms実行時間

:

// 無限ループのスクリプト
while (1) {
    // ... 処理 ...
}
// 100ms後にLCVM_ERR_WATCHDOG_TIMEでVMが中断

2. 命令数ベースウォッチドッグ

CPU速度に依存しない保護:

lcvm_set_watchdog_ic(100000);  // 最大100,000命令

:

for (i = 0; i < 1000000; i++) {
    // ... 処理 ...
}
// 100,000命令後にVMが中断

推奨設定

アプリケーション種別時間制限命令数制限
リアルタイム制御(10msサイクル)8ms50,000
定期タスク(1秒サイクル)800ms500,000
バッチ処理5000ms1,000,000+
開発/デバッグ0(無効)0(無効)

エラー回復

グレースフルデグラデーション

Lambda Cは非局所的エラー処理のためsetjmp/longjmpを使用します:

int lcvm_run_compact(LcvmState *vm, compact_program_t *prog, int trace) {
    if (setjmp(vm->error_jmp) != 0) {
        // エラー発生 - 安全に復帰
        return -1;
    }

    // 通常実行
    // ...
}

エラー処理パターン

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

if (ret != 0) {
    // 詳細なエラー情報を取得
    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);

    // 是正措置
    switch (code) {
        case LCVM_ERR_DIV_ZERO:
            // ログ記録とリセット
            break;
        case LCVM_ERR_HEAP_EXHAUSTED:
            lcvm_heap_reset();
            break;
    }
}

静的型モード

約2倍の高速化

-Os オプションでコンパイルすると、スタック上の型情報(12バイト/エントリ)を省略し、実行速度を大幅に向上:

lcvmc -Os -oc output.lcbc source.c

ベンチマーク(fib(25))

モード実行時間
通常モード27ms
静的型モード13ms

技術的背景

通常モードでは、VMスタック上の各値に対して型情報を追跡します。静的型モードでは、コンパイル時に型が確定する整数演算プログラムにおいて、この型追跡をスキップします。

制限事項:


関数ポインタ対応

コールバックパターン

Lambda Cは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;
}

状態遷移テーブル

関数ポインタ配列を使った効率的な状態管理:

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]();
}

性能特性

ベンチマーク結果

Fibonacci(25) 計算

命令スループット

メモリ効率

典型的な実行時メモリ使用量

スケーラビリティ


設計判断

なぜ4バイトなのか?

検討した代替案:

4バイトの利点:

なぜArena Allocatorなのか?

設計上free()なし:

トレードオフ: lcvm_heap_reset()を定期的に呼び出す必要がある

なぜC言語サブセットなのか?

Lua/Pythonに対する利点:

オーケストレーション用途に最適化: Lambda Cは意図的にサブセットとして設計されており、汎用言語ではありません。組み込みオーケストレーションにおいて、シンプルさは強みです: