コンパイラアーキテクチャ
wirespec は Rust で実装されたフルスクラッチのコンパイラです。パーサージェネレータ、テンプレートエンジン、標準的な Rust クレート以外の外部依存は一切使っていません。
パイプラインの概要
Source (.wspec)
→ AST (wirespec-syntax)
→ Semantic IR (wirespec-sema)
→ Layout IR (wirespec-layout)
→ Codec IR (wirespec-codec)
→ C Code (wirespec-backend-c)
→ Rust Code (wirespec-backend-rust)各ステージが表現をマシン出力に段階的に近い形へ変換します。バックエンド(C、Rust)は Codec IR を消費し、AST に直接触れることはありません。
クレート構成
wirespec/
├── Cargo.toml # ワークスペースルート
├── crates/
│ ├── wirespec-syntax/ # レキサ + パーサ → AST
│ ├── wirespec-sema/ # セマンティック解析 → Semantic IR
│ ├── wirespec-layout/ # ワイヤ形状 → Layout IR
│ ├── wirespec-codec/ # パース/シリアライズ戦略 → Codec IR
│ ├── wirespec-backend-api/ # バックエンドトレイト + コントラクト
│ ├── wirespec-backend-c/ # C コード生成
│ ├── wirespec-backend-rust/ # Rust コード生成
│ └── wirespec-driver/ # モジュールリゾルバ + CLI
├── examples/ # .wspec/.wspec プロトコル定義
├── docs/ # 設計ドキュメントとプラン
└── protospec/ # Python リファレンス実装(レガシー)| クレート | 説明 |
|---|---|
wirespec-syntax | 手書きレキサ + 再帰下降パーサ、AST ノード型、スパン追跡 |
wirespec-sema | セマンティック解析: 名前解決、型検査、バリデーションルール |
wirespec-layout | レイアウト下降: ワイヤフィールド順序、ビットグループパッキング、エンディアンネス |
wirespec-codec | コーデック下降: パース/シリアライズ戦略、ゼロコピー判定、キャパシティチェック |
wirespec-backend-api | バックエンドトレイト定義(Backend、BackendDyn、ArtifactSink、チェックサムバインディング) |
wirespec-backend-c | C コードジェネレータ: ヘッダ + ソース、ビットグループシフト/マスク、チェックサム検証/計算 |
wirespec-backend-rust | Rust コードジェネレータ: 単一 .rs ファイル、ライフタイム追跡、フレーム用 Rust enum |
wirespec-driver | コンパイルドライバ: モジュール解決、依存グラフ、マルチモジュールパイプライン、CLI バイナリ |
各ステージの詳細
パーサ(wirespec-syntax)
レキサと再帰下降パーサを単一クレートにまとめています。文法規則ごとに 1 つのパースメソッドが対応します。出力は AST で、ソーステキストの構造をそのまま反映します。名前解決はここでは行わず、式中のフィールド参照はこの段階では単なる名前文字列です。
セマンティックアナライザ(wirespec-sema)
コンパイラ内で最大のクレートです。AST を受け取り、以下を含む SemanticModule を生成します。
- 全名前の定義への解決
- 条件付きフィールドの
Option[T]化 - 全フィールドへのワイヤ型割り当て
- 型付きセマンティック式による生の AST 式の置換
- 状態機械の検証(到達可能性、アクションの完全カバレッジ)
require制約の型妥当性チェック
言語のセマンティクスを理解する最後のステージです。以降のパスはこれを唯一の真実源として扱います。
レイアウトパス(wirespec-layout)
セマンティック型をワイヤ上のバイト形状に変換します。
- フィールドごとのエンディアンネス解決(型サフィックス
u16le、@endianモジュールアノテーション、型エイリアスチェーンから) - 連続
bits[N]フィールドを 1 回の読み取り操作にグループ化 —BitGroupとして統合し、シフト & マスク展開 - コード生成ロジックは含まない — 純粋に記述的
コーデックパス(wirespec-codec)
全フィールドにフィールド戦略を割り当てます。
Primitive— N バイト読み取り + エンディアン変換VarInt— プレフィックスマッチ可変長整数ContVarInt— 継続ビット可変長整数(MQTT 方式)BytesFixed— ゼロコピーバイトスライスBytesLength— 長さプレフィックス付きバイトスライスBytesRemaining— スコープの残り全体を消費Array— 先行フィールドのカウントでループBitGroup— 1 回読み取り + シフト/マスクStruct— ネスト構造体のパース呼び出しConditional—if COND { T }オプショナルフィールドChecksum— パース時に検証、シリアライズ時に計算
C コードジェネレータ(wirespec-backend-c)
CodecModule から .h + .c ファイルを生成します。生成されるすべての関数は同一の API 規約に従います。
wirespec_result_t PREFIX_parse(
const uint8_t *buf, size_t len,
PREFIX_t *out, size_t *consumed);
wirespec_result_t PREFIX_serialize(
const PREFIX_t *in,
uint8_t *buf, size_t cap, size_t *written);
size_t PREFIX_serialized_len(const PREFIX_t *in);ヒープアロケーションなし。バッファはすべて呼び出し元が用意します。この不変条件はコードレビューで担保し、-Wall -Wextra -Werror でのコンパイルで検証しています。
Rust コードジェネレータ(wirespec-backend-rust)
CodecModule から単一の .rs ファイルを、C バックエンドと同じ構造化エミッタアプローチで生成します。Rust バックエンドは完全実装済みで、コード生成テストとエンドツーエンドテストでカバーされています。
モジュールリゾルバ(wirespec-driver)
マルチファイルコンパイルを処理します。
- エントリ
.wspecファイルをパースしてimport宣言を収集 -Iインクルードパスを使ってディスク上のモジュールを探索- インポート先モジュールを再帰的にパース
- 循環参照を検出(見つかればエラー)
- トポロジカル順序でモジュールを返す(依存先が先)
コンパイラはこの順序でモジュールを処理し、下流モジュールが使うエクスポート済み型を収集していきます。
マルチモジュールコンパイルの流れ
Entry .wspec file
└─ wirespec-driver: find + parse all imports (depth-first, topo sorted)
└─ For each module (dependencies first):
wirespec-syntax → AST
wirespec-sema → Semantic IR (with imported types injected)
wirespec-layout → Layout IR
wirespec-codec → Codec IR
wirespec-backend-c → .h + .c (-t c)
wirespec-backend-rust → .rs (-t rust)下流モジュールは自身のセマンティック解析の前に、上流モジュールからエクスポートされた型を受け取ります。これにより、quic.frames で import quic.varint.VarInt とした VarInt が名前付き型として利用可能になります。
テストの実行
# 全テスト(8 クレートにまたがる 933 以上のテスト)
cargo test --workspace
# 特定クレートのテスト
cargo test -p wirespec-sema
# コンパイラのビルド
cargo build --release
# .wspec ファイルのコンパイル
./target/release/wirespec compile examples/quic/varint.wspec -t c -o build/
# 生成 C のコンパイルとテスト
cd build && gcc -Wall -Wextra -Werror -O2 -std=c11 \
-o test_varint quic_varint.c tests/test_varint.c && ./test_varint設計原則
- 生成される C コードはヒープ割り当てなし。 すべてのバッファは呼び出し元が提供します。スタックとゼロコピービューのみ使用。
- 生成コードは
gcc -Wall -Wextra -Werror -std=c11で警告なしにコンパイル。 - バックエンドは Codec IR のみを消費 — 名前解決と型検査はコード生成の前に完了しています。