RFC-026b: yx-bindgen ツールチェーン
親 RFC: RFC-026: FFI コアメカニズム
依存: この RFC の実装は RFC-026 のコア FFI メカニズムが先に実装されることに依存しています。
抄録
yx-bindgen は C ヘッダファイルを機械的に .yx FFI バインディングファイルに変換し、ライブラリバインディング、型の二分(不透明ハンドル / 透明型)、関数宣言、メソッドバインディングを生成します。
2つのコア原則:
- 機械的に草稿を出力し、所有権を推測しない——所有権は YaoXiang の型が赋予し、ユーザーは C のドキュメントで確認する
- プラットフォーム正しいレイアウト保証——透明型のフィールドサイズ、アライメントはターゲットプラットフォームに基づいて計算され、C struct とのバイナリ整合性を保証する
動機
手作業での FFI バインディング記述は枯燥で間違いやすい——C ライブラリには数十から数百の関数がある。さらに危険なのは透明型のレイアウトの問題:手書きで
Timespec: Type = { tv_sec: Int64, tv_nsec: Int64 }を作成する際、フィールドサイズ、アライメント、パディングはターゲットプラットフォームの C struct timespec と正確に一致しなければならない、さもなくば C が誤ったレイアウトで読み書きして境界外アクセスが発生する(RFC-026 §2.2 の信頼保証)。yx-bindgen は .h からレイアウトを機械的に計算し、この手作業による保証のリスクを排除する。
提案
1. 使用法
yx-bindgen --header /usr/include/sqlite3.h --lib sqlite3 --output sqlite3_bindings.yx--lib はリンクするライブラリ名を指定し、Native.c("libsqlite3") ヘッダを生成する。
2. 生成内容
yx-bindgen は4種類のコンテンツを生成する:
- ライブラリバインディングヘッダ:
lib = Native.c("libxxx") - 型の二分: 黒盒ポインタ → 不透明ハンドル; データ構造体 → 透明型(レイアウト付き)
- 関数宣言:
lib("symbol")バインディング - メソッドバインディング:
Type.methodまたは[N]
3. 型の二分の自動判定
yx-bindgen は C 型の使用方式に基づいて所属を判定する(RFC-026 §2):
| C 型 | 判定 | yx-bindgen 出力 |
|---|---|---|
typedef struct T T;(不完全型/ポインタのみ使用) | 黒盒 → 不透明ハンドル | T: Type = lib("T") |
struct T { fields };(フィールド可視、読み書きされる) | データ → 透明型 | T: Type = { ...レイアウト } |
int/long/float/double | 値 | Int32/Int64/Float32/Float64 |
char*(引数/戻り値) | 値(コピー) | String |
void* | 型なし | *Void(システムレベル) |
不完全型(typedef struct sqlite3 sqlite3; 定義体なし)→ 必然的に黒盒ハンドル。完全に定義された struct → 透明型、フィールドレイアウトは機械的に計算される。
4. レイアウト計算(透明型の鍵)
完全に定義された struct について、yx-bindgen はターゲットプラットフォームの ABI に基づいて各フィールドのオフセット、サイズ、アライメントを計算する:
struct timespec {
time_t tv_sec; // プラットフォーム依存:Linux x86_64 = 8 バイト
long tv_nsec; // 8 バイト
};// ターゲットプラットフォーム Linux x86_64
Timespec: Type = {
tv_sec: Int64, // offset 0, size 8
tv_nsec: Int64 // offset 8, size 8
}
// 合計サイズ 16、アライメント 8 —— C struct とバイナリ整合プラットフォーム差異: time_t、long、size_t のサイズはプラットフォームによって異なる。yx-bindgen は --target に基づいて正しいマッピングを選択し、生成された透明型がターゲットプラットフォームの C struct とバイト単位で一致することを保証する。これは RFC-026 §2.2 の手作業レイアウト保証リスク消除のコアバリューである。
5. 生成例
入力(sqlite3.h の抽象):
typedef struct sqlite3 sqlite3; // 不完全 → 黒盒ハンドル
typedef struct sqlite3_stmt sqlite3_stmt;
int sqlite3_open(const char *filename, sqlite3 **ppDb);
int sqlite3_close(sqlite3 *db);
int sqlite3_exec(sqlite3 *db, const char *sql, ...);出力(sqlite3_bindings.yx):
// sqlite3_bindings.yx —— 自動生成、手動編集禁止
// ============================================================================
// ライブラリバインディング
// ============================================================================
sqlite3 = Native.c("libsqlite3")
// ============================================================================
// 型(不完全型 → 不透明ハンドル)
// ============================================================================
SqliteDb: Type = sqlite3("sqlite3")
SqliteStmt: Type = sqlite3("sqlite3_stmt")
// ============================================================================
// 関数 + メソッドバインディング
// ============================================================================
SqliteDb.open: (filename: String) -> ?SqliteDb = sqlite3("sqlite3_open")
SqliteDb.exec: (sql: String) -> Int32 = sqlite3("sqlite3_exec")
SqliteDb.close: () -> Int32 = sqlite3("sqlite3_close")
// ============================================================================
// デストラクト(オプション、ユーザーが確認)
// ============================================================================
SqliteDb.drop = SqliteDb.close6. ユーザー調整
生成されたバインディングは草稿であり、ユーザーは C ライブラリのドキュメントに基づいて所有権セマンティクスを確認する(yx-bindgen は推測しない):
// 生成:デフォルト String(コピー)—— ほとんどの場合正しい
SqliteDb.errmsg: () -> String = sqlite3("sqlite3_errmsg")
// getenv は静的領域を返す、コピーも借用もしない —— ユーザーは裸ポインタに変更
getenv: (name: String) -> *const U8 = Native.c("libc")("getenv")
// ライブラリが所有するハンドル、ユーザーが .drop をバインディングするかどうか確認
SqliteDb.drop = SqliteDb.close // 生成された提案、ユーザーが確認鍵: レイアウトは yx-bindgen が機械的に保証する(プラットフォーム正しい)が、所有権セマンティクス(.drop かどうか、char* をコピー还是裸ポインタか)はユーザーが C のドキュメントに基づいて確認する。ツールは機械的に正しく、ユーザーはセマンティクス的に正しいを担当する。
权衡
メリット
- レイアウト保証の自動化: 透明型レイアウトはプラットフォームに基づいて機械的に計算され、手作業による padding/アライメント錯誤を消除
- 型二分自動判定: 不完全型 → ハンドル、完全に定義された struct → 透明型
- 監査可能: 出力は通常の
.yxであり、ユーザーは読んで、改変して、バージョン管理にコミットできる
デメリット
- 所有権はまだ手動確認が必要: yx-bindgen は
.dropを推測せず、char*セマンティクスを推測しない - C ヘッダ解析への依存: libclang または tree-sitter-c が必要
- プラットフォーム特化: 異なる
--targetは異なるレイアウトを生成し、クロスプラットフォームパッケージは複数バージョンまたは実行時選択が必要
実装戦略
- [ ] C ヘッダファイル解析(libclang)
- [ ] 型二分判定(不完全型 vs 完全に定義された struct)
- [ ] プラットフォーム ABI レイアウト計算(offset/size/align、
--targetに基づく) - [ ] コード生成(ライブラリバインディング + 型 + 関数 + メソッド)
- [ ] 統合テスト(sqlite3、libcurl、複数プラットフォームレイアウト検証)
他の RFC との関係
- RFC-026(親): FFI コアメカニズム——生成されたバインディングはその
Native.c("lib")("sym")構文と型二分を使用 - RFC-026a: 拡張可能な FFI メカニズム——将来的には
Native.wasmなど他のメカニズムのバインディング生成を拡張可能
設計決定記録
| 決定 | 決定 | 理由 | 日付 |
|---|---|---|---|
| 位置づけ | 機械的に草稿を出力し、所有権を推測しない | 所有権は YaoXiang の型が赋予し、ユーザーは C のドキュメントで確認する | 2026-07-03 |
| レイアウト保証 | --target に基づいて offset/size/align を機械的に計算 | 手作業レイアウトの境界外アクセスのリスクを消除(RFC-026 §2.2) | 2026-07-03 |
| 型判定 | 不完全型→ハンドル、完全に定義された struct→透明型 | RFC-026 の型二分りに整合 | 2026-07-03 |
ライフサイクルと行き先
| ステータス | 位置 | 説明 |
|---|---|---|
| 草案 | docs/design/rfc/draft/ | RFC-026 が先に実装されることに依存 |
| 承認済み | docs/design/rfc/accepted/ | 正式な設計ドキュメント |
