Skip to content

コンパイラアーキテクチャ

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バックエンドトレイト定義(BackendBackendDynArtifactSink、チェックサムバインディング)
wirespec-backend-cC コードジェネレータ: ヘッダ + ソース、ビットグループシフト/マスク、チェックサム検証/計算
wirespec-backend-rustRust コードジェネレータ: 単一 .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 — ネスト構造体のパース呼び出し
  • Conditionalif COND { T } オプショナルフィールド
  • Checksum — パース時に検証、シリアライズ時に計算

C コードジェネレータ(wirespec-backend-c

CodecModule から .h + .c ファイルを生成します。生成されるすべての関数は同一の API 規約に従います。

c
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

マルチファイルコンパイルを処理します。

  1. エントリ .wspec ファイルをパースして import 宣言を収集
  2. -I インクルードパスを使ってディスク上のモジュールを探索
  3. インポート先モジュールを再帰的にパース
  4. 循環参照を検出(見つかればエラー)
  5. トポロジカル順序でモジュールを返す(依存先が先)

コンパイラはこの順序でモジュールを処理し、下流モジュールが使うエクスポート済み型を収集していきます。

マルチモジュールコンパイルの流れ

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.framesimport quic.varint.VarInt とした VarInt が名前付き型として利用可能になります。

テストの実行

bash
# 全テスト(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 のみを消費 — 名前解決と型検査はコード生成の前に完了しています。