# ランタイム実行エンジン — run.c・library.c・api.c

> run.c の pre_run / run_code / post_run ライフサイクル、library.c のライブラリディスパッチテーブル (kmbasic_library)、api.c の表示プリミティブ (printchar/printstr 等)、error.c のエラー停止フロー、exception.c の ARM ハードウェア例外ハンドラを詳説します。

- Repository: machikania/phyllosoma
- GitHub: https://github.com/machikania/phyllosoma
- Human wiki: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c
- Complete Markdown: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/llms-full.txt

## Source Files

- `run.c`
- `library.c`
- `api.c`
- `api.h`
- `error.c`
- `exception.c`

---

<details>
<summary>関連するソースファイル</summary>
このWikiページの生成に使用したファイル:

- [run.c](run.c)
- [library.c](library.c)
- [api.c](api.c)
- [api.h](api.h)
- [error.c](error.c)
- [exception.c](exception.c)
- [compiler.c](compiler.c)
- [compiler.h](compiler.h)
- [globalvars.c](globalvars.c)
</details>

# ランタイム実行エンジン — run.c・library.c・api.c

phyllosoma は Raspberry Pi Pico (RP2040/ARM Cortex-M0+) 向け KM-BASIC インタプリタの実行基盤です。コンパイラが生成した Thumb-2 バイナリコード (`kmbasic_object`) を実際に走らせるための「実行エンジン」は、**run.c**（ライフサイクル制御・レジスタ初期化）、**library.c**（ランタイムライブラリのディスパッチテーブル）、**api.c / api.h**（出力プリミティブの二重出力レイヤ）、**error.c**（エラー停止フロー）、**exception.c**（ARM ハードフォルト例外ハンドラ）の 5 ファイルで構成されます。

これら 5 ファイルは密結合しており、BASIC プログラムの開始から終了まで、さらには予期しないハードウェア例外の後始末までを一貫して担います。ARMv6-M の制約（高レベルスタックフレームなし、Thumb-2 のみ）に対応するため、多くの箇所でインラインアセンブリが用いられています。

---

## run.c — 実行ライフサイクル

### ライフサイクル概観

```text
┌───────────────────────────────────────────────────────┐
│  pre_run()        — メモリ・デバイス初期化            │
│  run_code()       — ARM レジスタ設定 → kmbasic_object │
│  post_run()       — リソース解放・クロック復元        │
└───────────────────────────────────────────────────────┘
```

### pre_run() — 実行前初期化

```c
// run.c
void pre_run(void){
    init_memory();
    g_read_point=&kmbasic_object[0];
    g_rnd_seed=0x92D68CA2;
    kmbasic_data[2]=(int)&kmbasic_var_size[0];
    close_all_files();
    video_init();  io_init();  init_music();
    g_interrupt_code=0;
    lib_display(0,0,RESET_STATIC_VARS);
    lib_spi(0,0,RESET_STATIC_VARS);
    handle_exception(1);
    pre_run_wifi();
    g_clock_hz_default=clock_get_hz(clk_sys);
    g_cpu_voltage_default=vreg_get_voltage();
}
```

`pre_run()` は以下を順に実行します。

| 処理 | 説明 |
|------|------|
| `init_memory()` | メモリアロケータ初期化 |
| `g_read_point` 設定 | DATA/CDATA 読み出しポインタをオブジェクト先頭に初期化 |
| 乱数シード設定 | Xorshift 用初期値 `0x92D68CA2` をセット |
| `kmbasic_data[2]` | `&kmbasic_var_size[0]` を格納（変数サイズ管理配列） |
| デバイス初期化 | ファイル・ビデオ・I/O・音楽をリセット |
| 静的変数リセット | `RESET_STATIC_VARS (32767)` を使って `lib_display`/`lib_spi` の static 変数をクリア |
| 例外ハンドラ登録 | `handle_exception(1)` で HardFault ハンドラを差し替え |
| CPU ベースライン保存 | `g_clock_hz_default`, `g_cpu_voltage_default` を保存 |

Sources: [run.c:116-140]()

### run_code() — コード実行コア

`run_code()` は生成済みバイナリ (`kmbasic_object`) を呼び出す前に、BASICランタイムが前提とする **専用レジスタレイアウト** を構築します。

```c
// run.c — 専用レジスタの設定部分
asm("ldr r5,=kmbasic_variables"); // R5: 変数配列へのポインタ
asm("ldr r6,=g_r6_array");        // R6: 引数配列へのポインタ
asm("ldr r7,=kmbasic_data");      // R7: ランタイムデータ配列
asm("ldr r0,=kmbasic_library");
asm("mov r8,r0");                 // R8: ライブラリディスパッチャ
asm("mov r0,sp");
asm("str r0,[r7,#0]");            // kmbasic_data[0] に SP を保存
asm("sub sp,#508");
asm("sub sp,#508");               // ライブラリ用スタック 1016 バイト確保
asm("ldr r1,=kmbasic_object+1");
asm("bx r1");                     // kmbasic_object へジャンプ
```

呼び出しの前後で R0–R12 全レジスタをプッシュ/ポップし、C ランタイムの状態を保護します。

#### ARM レジスタ割り当て表

| レジスタ | 値 / 役割 |
|---------|-----------|
| R5 | `&kmbasic_variables[0]` — BASIC 変数値配列 |
| R6 | `g_r6_array` — 引数配列（R6[0]=オブジェクトポインタ, R6[1]=前の R6, R6[2]=引数数, R6[3+]=各引数） |
| R7 | `&kmbasic_data[0]` — ランタイムデータ配列（下表参照） |
| R8 | `kmbasic_library` 関数ポインタ |

#### kmbasic_data[] インデックス

| インデックス | 内容 |
|-------------|------|
| `[0]` | プログラム終了時に復元する SP 値 |
| `[1]` | プログラム終了時の復帰アドレス |
| `[2]` | `&kmbasic_var_size[0]` |
| `[3]` | ライブラリ関数呼び出し直前の PC（エラー表示でのライン特定に使用） |
| `[4]` | エラーハンドリング用 |

Sources: [run.c:18-57](), [compiler.c:18-31]()

### post_run() — 実行後クリーンアップ

```c
void post_run(void){
    lib_system(200,1,0);    // ビデオ出力を再開
    reset_memory();
    close_all_files();
    io_init();  lib_keys(63,0,0);
    timer_init();  stop_music();  stopPCG();
    if (!g_active_usb_keyboard) stop_core1();
    g_interrupt_code=0;
    cancel_all_interrupts();
    handle_exception(0);    // 元のHardFaultハンドラに戻す
    post_run_wifi();
    // CPU クロック・電圧を実行前の値に復元
    ...
}
```

CPU 周波数と電圧を `g_clock_hz_default` / `g_cpu_voltage_default` と比較しながら安全に復元します。上げた場合と下げた場合で復元順を変えることで、不正な電圧・周波数の組み合わせを回避しています。

Sources: [run.c:142-183]()

### call_interrupt_function() と machikania_snprintf()

`call_interrupt_function(void* r0)` は割り込みコールバック用のラッパーで、`run_code()` と同様のレジスタセットアップを行いつつ `g_interrupt_code` フラグを保存・復元します。

`machikania_snprintf()` は `snprintf` の呼び出し前にスタックポインタをライブラリ専用スタック領域 (`kmbasic_data[0]`) に切り替え、割り込みを無効化してから実行します。これは `snprintf` が大きなスタックを消費するためのリエントラント対策です。

Sources: [run.c:63-110](), [run.c:199-215]()

---

## library.c — ライブラリディスパッチテーブル

### 二段階のディスパッチ構造

```mermaid
flowchart TD
    A["kmbasic_library(r0,r1,r2,r3)"] --> B{"r3 < 128?"}
    B -- "Yes (Quick Library)" --> C["lib_list1[r3](r0,r1,r2)"]
    B -- "No (Statement Library)" --> D["statement_library(r0,r1,r2,r3)"]
    D --> E["lib_list2[r3-128](r0,r1,r2)"]
    E --> F["check_break() → lib_end() if Ctrl-Z"]
    C --> G["戻り値 r0"]
    F --> G
```

`kmbasic_library()` はエントリ時に LR を `kmbasic_data[3]` に格納します。これにより例外・エラー発生時にどのライブラリ呼び出し中に問題が起きたかを特定できます。

```c
int kmbasic_library(int r0, int r1, int r2, int r3){
    asm("mov r4,lr");
    asm("str r4,[r7,#12]");  // kmbasic_data[3] = LR
    int (*f)(int r0, int r1, int r2) = lib_list1[r3];
    if (r3<128) return f(r0,r1,r2);
    else return statement_library(r0,r1,r2,r3);
}
```

Sources: [library.c:878-887]()

### クイックライブラリ (lib_list1, 0–32)

Break チェックやガベージコレクションフラグの設定を行わない高速パス。演算・文字列・数値変換など頻度の高い処理が入ります。

| インデックス | 定数 | 関数 | 役割 |
|-------------|------|------|------|
| 0 | `LIB_CALC` | `lib_calc` | 整数除算・剰余 |
| 1 | `LIB_CALC_FLOAT` | `lib_calc_float` | 浮動小数点演算 |
| 2 | `LIB_HEX` | `lib_hex` | 16進数文字列変換 |
| 3 | `LIB_ADD_STRING` | `lib_add_string` | 文字列連結 |
| 8 | `LIB_RND` | `lib_rnd` | 乱数（Xorshift） |
| 11 | `LIB_MATH` | `lib_math` | 数学関数群 (sin/cos/sqrt等) |
| 17 | `LIB_READ` | `lib_read` | DATA 読み出し |
| 22 | `LIB_DISPLAY_FUNCTION` | `lib_display` | グラフィック関数 |
| 23 | `LIB_INKEY` | `lib_inkey` | キー入力（非ブロッキング） |

Sources: [library.c:818-851](), [compiler.h:70-105]()

### ステートメントライブラリ (lib_list2, 128–157)

`statement_library()` 経由で呼ばれ、実行後に Break キー (`Ctrl-Z`) をチェックします。BASIC 文（PRINT, DIM, FOR等）および I/O・通信系が入ります。

| インデックス | 定数 | 関数 | 役割 |
|-------------|------|------|------|
| 129 | `LIB_PRINT` | `lib_print` | PRINT 文 |
| 131 | `LIB_END` | `lib_end` | プログラム終了・SP復元 |
| 132 | `LIB_LINE_NUM` | `lib_line_num` | GOTO/GOSUB ライン解決 |
| 133 | `LIB_DIM` | `lib_dim` | 配列確保 |
| 137 | `LIB_DISPLAY` | `lib_display` | グラフィック描画文 |
| 138 | `LIB_WAIT` | `lib_wait` | フレーム同期ウェイト |
| 139 | `LIB_SYSTEM` | `lib_system` | システム情報・設定 |
| 145 | `LIB_INTERRUPT` | `lib_interrupt` | 割り込み設定 |
| 152 | `LIB_MUSIC` | `lib_music` | 音楽・効果音 |
| 156 | `LIB_WIFI` | `lib_wifi` | TCP/IP通信 |

Sources: [library.c:853-876](), [compiler.h:109-133]()

### lib_end() — プログラム終了

`lib_end()` は例外なく最も重要な内部関数です。`kmbasic_data[0]` に保存した元の SP と `kmbasic_data[1]` の復帰アドレスを使い、`run_code()` の呼び出し元へ直接ジャンプして戻ります。

```c
int lib_end(int r0, int r1, int r2){
    asm("ldr r7,=kmbasic_data");
    asm("ldr r0, [r7, #0]");   // kmbasic_data[0] = 保存済みSP
    asm("mov sp, r0");
    asm("ldr r0, [r7, #4]");   // kmbasic_data[1] = 復帰アドレス
    asm("bx r0");
    return r0;
}
```

Sources: [library.c:205-213]()

### use_lib_stack マクロ

`lib_print`, `lib_fprint`, `lib_float_str`, `lib_sprintf` はスタックを多く消費する `snprintf` / `machikania_snprintf` を内部で呼ぶため、`use_lib_stack` マクロで実際の処理関数を呼び出します。これにより呼び出し規約を保ちつつ安全に処理できます。

```c
#define use_lib_stack(funcname) \
    asm("push {r4,lr}"); \
    asm("bl "funcname); \
    asm("pop {r4,pc}")
```

Sources: [library.c:29-36]()

---

## api.c / api.h — 表示プリミティブの二重出力レイヤ

### アーキテクチャ

phyllosoma は LCD/ビデオ出力と USB シリアル出力を同時にサポートします。api.c はこの二重出力を実現するラッパー層です。

```text
BASIC コード
    │
    ▼
_printchar() / _printstr() / _printnum() etc.  [api.c]
    │                        │
    ▼                        ▼
printchar() / printstr()   putchar() / printf()
[interface/graphlib — LCD]  [pico/stdlib — USB Serial]
    │                        │
 g_disable_lcd_out で制御  g_disable_printf で制御
```

Sources: [api.c:37-50](), [api.h:35-56]()

### api.h のマクロリダイレクト

`api.h` では `graphlib.h` が宣言する `printchar`・`printstr` などを `#undef` してから、api.c が提供するアンダースコア付き実装にマクロで置き換えます。

```c
// api.h
#undef printchar
#undef printstr
#define printchar(a) _printchar(a)
#define printstr(a)  _printstr(a)
```

これにより、コードベース全体で `printchar(c)` と書くだけで LCD と Serial の両方に出力されます。

Sources: [api.h:35-56]()

### 主要関数一覧

| 関数 | シグネチャ | 説明 |
|------|-----------|------|
| `_printchar` | `(unsigned char c)` | 1 文字出力。BS(0x08)/CR(0x0d)/LF(0x0a) を適切に処理し `g_cursor` を更新 |
| `_printstr` | `(unsigned char *s)` | NULL 終端文字列出力 |
| `_printnum` | `(unsigned int n)` | 符号なし整数を10進表示 |
| `_printnum2` | `(unsigned int n, unsigned char e)` | 幅 `e` 桁の右詰め整数表示 |
| `_cls` | `(void)` | 画面クリア（LCD + ANSI エスケープ 24 行分スクロール） |
| `_setcursor` | `(x, y, c)` | カーソル移動（LCD + ANSI ESC シーケンス） |
| `printint` | `(int i)` | 符号付き整数出力（`_printnum` のラッパー） |
| `printhex32` | `(unsigned int i)` | 32 bit 16進表示（`printhex16` × 2） |

Sources: [api.c:30-145]()

### カーソル位置管理

`g_cursor` は 80 桁幅の仮想端末上での絶対位置（列 + 行×80）として管理されます。`_setcursor()` は現在位置から目標位置への差を計算し、ANSI カーソル移動エスケープシーケンス（`ESC[A/B/C/D`）を発行します。

---

## error.c — エラー停止フロー

### エラーコードテーブル

`g_error_text[]` にはコンパイル時・実行時エラー 27 種のメッセージが格納されており、エラーコード値（負整数）の絶対値をインデックスとして参照します。

| エラーコード | メッセージ |
|------------|-----------|
| -1 | `Syntax error` |
| -9 | `Out of memory` |
| -11 | `DATA not found` |
| -24 | `Exception` |
| -26 | `Unsupported clock frequency` |

Sources: [error.c:12-38]()

### stop_with_error() — 実行時エラーの停止フロー

```mermaid
stateDiagram-v2
    [*] --> 実行時エラー発生
    実行時エラー発生 --> メッセージ表示: stop_with_error(e)
    メッセージ表示 --> ライン番号検索: line_number_from_address(kmbasic_data[3])
    ライン番号検索 --> ライン番号表示: 見つかった場合
    ライン番号検索 --> アドレス表示: 見つからない場合
    ライン番号表示 --> プログラム終了: lib_end(0,0,0)
    アドレス表示 --> プログラム終了: lib_end(0,0,0)
    プログラム終了 --> [*]
```

`line_number_from_address()` は `cmpdata` テーブル（`CMPDATA_LINENUM` 型エントリ）を走査し、`kmbasic_data[3]`（直前のライブラリ呼び出しの PC）に対応する BASIC ライン番号を探します。

```c
void stop_with_error(int e){
    e=-e;
    printstr(g_error_text[e]);
    e=line_number_from_address(kmbasic_data[3]);
    if (e<0) { printstr(" at "); printhex32(kmbasic_data[3]); }
    else { printstr(" in line "); printint(e); }
    lib_end(0,0,0);
}
```

Sources: [error.c:88-115]()

### コンパイル時エラー — throw_error() / show_error()

コンパイル時エラーは `throw_error(e, line, file)` でファイル名・行番号を保存し、`show_error(e, pos)` でソース位置とともに表示します。`MACHIKANIA_DEBUG_MODE` 定義時にはソースファイル名と行番号も出力されます。

---

## exception.c — ARM ハードウェア例外ハンドラ

### ハンドラのフロー

```mermaid
sequenceDiagram
    participant CPU as ARM CPU (HardFault)
    participant EH as exception_handler()
    participant EHM as exception_handler_main()
    participant SW as stop_with_error()

    CPU->>EH: HardFault 割り込み
    EH->>EH: MOV r0,sp / LDR r1,ICSR
    EH->>EHM: B exception_handler_main
    EHM->>EHM: EXCEPTION_MODE_RESTART? → software_reset()
    EHM->>EHM: レジスタダンプ表示 (R0-R3,R12,LR,PC,SP,PSR,ICSR)
    EHM->>EHM: スクリーンショット/ダンプ保存 (オプション)
    EHM->>EHM: sp[0]=ERROR_EXCEPTION, sp[6]=stop_with_error
    Note over EHM: 例外フレームの R0,PC を書き換え
    EHM-->>CPU: 例外リターン
    CPU->>SW: stop_with_error(ERROR_EXCEPTION) が呼ばれる
    SW-->>CPU: lib_end() でプログラム終了
```

### exception_handler() — アセンブリエントリポイント

```c
void exception_handler(void){
    asm("mov r0,sp");
    asm("ldr r1,=0xE000ED04"); // ICSR (Interrupt Control and State Register)
    asm("ldr r1,[r1]");
    asm("b exception_handler_main");
}
```

ARM の HardFault 例外発生時、CPU はスタックに R0-R3, R12, LR, PC, PSR を自動的に積みます。この関数はその SP をそのまま `r0` として `exception_handler_main()` に渡します。

Sources: [exception.c:97-104]()

### exception_handler_main() — 本体処理

スタックフレームポインタ `sp` と ICSR の値を受け取り、以下を実行します。

1. `EXCEPTION_MODE_RESTART` フラグが立っている場合は即座に `software_reset()`
2. レジスタ情報（PSR, ICSR, SP, R0-R3, R12, LR, PC）を表示
3. フォルト発生地点前後の命令コードを 8 ハーフワード分表示
4. `EXCEPTION_MODE_SCREENSHOT` が設定されていれば TVRAM を SD カードに保存
5. `EXCEPTION_MODE_DUMP` が設定されていれば RAM (0x20000000〜) をファイルにダンプ
6. **スタックフレームの R0 と PC を書き換え**: `sp[0]=ERROR_EXCEPTION`, `sp[6]=stop_with_error`

ステップ 6 の手法が巧妙です。例外リターン後に CPU が復元するレジスタを改ざんすることで、フォルトの発生アドレスには戻らず `stop_with_error(ERROR_EXCEPTION)` が呼ばれます。

```c
// exception.c
sp[0]=(int)ERROR_EXCEPTION;       // R0 = エラーコード
sp[6]=(int)stop_with_error;       // PC = stop_with_error 関数
```

Sources: [exception.c:64-115]()

### ini_file_exception() — 動作設定

`.INI` ファイルの解析時に呼ばれ、例外発生時の挙動を設定します。

| キーワード | 設定内容 |
|-----------|---------|
| `EXCRESET` | 例外発生時に即座に再起動 |
| `EXCSCREENSHOT=ファイル名` | TVRAM の内容をファイルに保存 |
| `EXCDUMP=ファイル名` | RAM 0x20000000〜0x20042000 をファイルにダンプ |

Sources: [exception.c:16-50]()

### handle_exception() — ハンドラの差し替え

```c
void handle_exception(int set){
    static exception_handler_t org_handler=0;
    if (!org_handler)
        org_handler=exception_get_vtable_handler(HARDFAULT_EXCEPTION);
    if (set)
        exception_set_exclusive_handler(HARDFAULT_EXCEPTION,exception_handler);
    else
        exception_restore_handler(HARDFAULT_EXCEPTION,org_handler);
}
```

`pre_run()` で `handle_exception(1)` を呼び BASIC 実行中専用のハンドラに差し替え、`post_run()` で `handle_exception(0)` を呼んで元のハンドラに戻します。これにより BASIC プログラムの外（メニュー画面など）でのフォルトには影響を与えません。

Sources: [exception.c:106-119]()

---

## まとめ

phyllosoma の実行エンジンは、BASIC プログラムの実行開始から終了までを `pre_run → run_code → post_run` の 3 段階ライフサイクルで管理します。`run_code()` が設定する ARM 専用レジスタレイアウト（R5=変数配列, R6=引数配列, R7=ランタイムデータ, R8=ライブラリディスパッチャ）は、コンパイラが生成するすべての Thumb-2 コードが前提とする固定プロトコルです。`kmbasic_library()` は番号ベースのディスパッチで 158 種のライブラリ関数を 2 段階（クイック/ステートメント）に分類して提供し、ステートメント層では Break キーチェックを自動実行します。`api.c` の二重出力レイヤは LCD とシリアルの同時出力を透過的に行い、`error.c` と `exception.c` は通常エラーとハードウェア例外の両方を `stop_with_error()` → `lib_end()` の共通フローに収束させます。
