Skip to content

4 段階 IR パイプライン

wirespec コンパイラは 4 つの中間表現(IR)を使い、段階的にマシン出力へ近づけていきます。この分離により言語セマンティクスがコード生成から独立し、パーサや型検査器を変更せずに新しいバックエンドを追加できます。

このページはコンパイラ内部を理解・変更したいコントリビューター向けです。

なぜ 4 ステージか

コンパイラは段階的に低レベルになる IR の連鎖として構成されています。これにより言語のセマンティクスがコード生成対象から独立します。

実用上の意味は以下の通りです。

  • AST = プログラマが書いたもの(構文)
  • Semantic IR = その意味(バックエンド非依存のセマンティクス)
  • Layout IR = バイトがワイヤ上でどう並ぶか
  • Codec IR = そのバイト列をどうパース/シリアライズするか(バックエンド固有の要素を含む)

Rust と C のバックエンドは最初の 3 ステージを共有し、Codec IR で初めて分岐します。ターゲット型の名前(uint32_t vs u32)やバックエンド固有のイディオムが異なるのはこの段階です。

データフロー

AST ─── wirespec-sema ──→ Semantic IR

                          wirespec-layout


                          Layout IR

                          wirespec-codec


                          Codec IR

                ┌───────────────┴───────────────┐
        wirespec-backend-c            wirespec-backend-rust
                │                               │
                ▼                               ▼
      .h + .c output files              .rs output file

ステージ 1: AST

生成元: wirespec-syntax クレート 主要な型: WireFilePacketDefFrameDefCapsuleDefTypeDefStateMachineDefExpr

AST はソーステキストの直接的な構造表現です。何も解決されていません。式中のフィールド参照は単なる名前文字列、型参照は未解決の識別子のままです。

主要な型:

概要
WireFileトップレベルコンテナ: モジュール宣言、インポート、定義
PacketDefpacket Foo { ... } 定義
FrameDefframe Foo = match tag: T { ... } 定義
CapsuleDefcapsule Foo { ... within ... } 定義
TypeDeftype Foo = ...(エイリアスまたは計算型)
StateMachineDefstate machine Foo { ... }
Expr式 AST ノード: NameExprLiteralExprBinaryExprCoalesceExpr など

AST は構文指向であり、バックエンドが直接使ってはなりません。意味解決が必要な場合、バックエンドは Semantic IR 以下を使用してください。

ステージ 2: Semantic IR

生成元: wirespec-sema クレート 主要な型: SemanticModuleSemanticStructSemanticFrameStateMachineSemanticVarIntSemField

プログラムの意味が完全に確定する最初のステージです。すべての名前が解決され、すべての型が判明します。

WireType 列挙型

全フィールドに WireType バリアントが割り当てられます。

バリアント意味
U8U16U24U32U64符号なし整数
I8I16I32I64符号あり整数
VARINTプレフィックスマッチ可変長整数
CONT_VARINT継続ビット可変長整数
BOOLセマンティックブール(導出フィールド/ガード専用)
BYTESバイトシーケンス(固定、長さプレフィックス付き、remaining)
BITSサブバイトフィールド(bits[N]
BIT単一ビットフィールド
ARRAY同種配列 [T; count]
STRUCT名前付きパケット/フレーム/カプセルへの参照
ENUM名前付き列挙型への参照
FLAGS名前付きフラグへの参照

SemExpr が AST Expr を置換

生の Expr ノードは型付きの SemExpr バリアントに置き換えられます。

SemExpr概要
SemFieldRef解決済みの型を持つフィールド参照
SemLiteral整数、文字列、ブールリテラル
SemBinaryOp型付きオペランドの二項演算
SemCoalesce?? コアレスク(Option[T] + デフォルト値)
SemInState状態機械ガード用 in_state(S) 述語
SemAllall(collection, predicate) 量化子
SemSlicefield[start..end] 半開スライス
SemSubscriptfield[index] 配列添字

Option[T] の追跡

条件付きフィールド(if COND { T })は Semantic IR で Option[T] として型付けされます。そのようなフィールドを式で参照するには ?? を使うかガード内に置く必要があり、ここで強制されます。

状態機械の検証

状態機械定義はこのステージで検証されます。

  • 遷移で参照される全状態が存在すること
  • on 節のイベントのパラメータ型が一貫していること
  • action ブロックがデフォルト値のない dst フィールドをすべて初期化していること
  • delegateaction が同一遷移に共存していないこと

主要な Rust 型

rust
SemanticModule     // .wspec ファイルごとに 1 つ: 構造体、フレーム、状態機械、インポート
SemanticStruct     // パケットまたはフレームブランチ: 名前、フィールド、制約
SemanticFrame      // タグ付きユニオン: タグフィールド + SemanticStruct ブランチのリスト
StateMachine       // 状態機械: 状態、遷移、初期状態
SemanticVarInt     // 計算型(プレフィックスマッチまたは継続ビット)
SemField           // WireType、名前、オプショナルな SemExpr 条件を持つ単一フィールド

ステージ 3: Layout IR

生成元: wirespec-layout クレート 主要な型: LayoutModuleLayoutFieldBitGroupEndianness

Layout IR はワイヤ上のバイト形状を記述します。バイトがどの順序で並び、ビットがどうパックされているかを表現します。

エンディアンネスの解決

各フィールドに EndiannessBigLittleNone)が割り当てられます。

  1. 明示的な型サフィックスが最優先: u16leLittle
  2. モジュールの @endian アノテーションがフォールバック
  3. 型エイリアスは基底型まで追跡

ビットグループの折りたたみ

パケット、フレームブランチ、カプセル内の連続する bits[N]/bit フィールドは単一の BitGroup に折りたたまれます。

wire
packet IPv4Header {
    version: bits[4],
    ihl: bits[4],
    dscp: bits[6],
    ecn: bits[2],
    ...
}

最初の 2 フィールドが 1 バイト読み取りの BitGroup、次の 2 フィールドも別の 1 バイト BitGroup になります。C バックエンドは uint8_t 1 回読み取り + シフト/マスク展開を生成します。

主要な Rust 型

rust
LayoutModule      // モジュール全体のレイアウトラッパー
LayoutField       // エンディアンネスを持つ単一フィールド
BitGroup          // 連続 bits[N] フィールドのグループ + トータルバイト幅
Endianness        // Big | Little | None

Layout IR はコード生成の関心事を意図的に含みません。uint32_t や関数シグネチャについては一切言及しません。それは Codec IR の仕事です。

ステージ 4: Codec IR

生成元: wirespec-codec クレート 主要な型: CodecModuleCodecStructCodecFrameCodecFieldFieldStrategy

Codec IR は最終的なバックエンド非依存の表現で、全フィールドに FieldStrategy とターゲット型を割り当てます。

FieldStrategy

戦略内容
PrimitiveN バイト読み取り + エンディアン変換
VarIntプレフィックスマッチ可変長整数のデコード
ContVarInt継続ビット可変長整数のデコード
BytesFixedゼロコピーバイトスライス(ポインタ + 長さ)
BytesLength長さプレフィックス付きバイトスライス
BytesRemaining現在のスコープの残りを全消費
Arrayカウントフィールドを読み取り、N 要素をループでパース
BitGroup1 回読み取り + フィールドごとのシフト/マスク
Structネスト構造体のパース/シリアライズ呼び出し
Conditional条件を評価し、真ならパース
Checksumパース時に検証、シリアライズ時に自動計算
Derivedlet フィールド — 他フィールドから計算、ワイヤ上には存在しない
Constraintrequire 式 — 実行時チェックのみ

MemoryTier

3 層メモリモデル:

戦略
Abytes[length]ゼロコピー: 入力バッファへのポインタ + 長さビュー
B[u16le; N]実体化: 固定配列への memcpy + バイトスワップ
C[AckRange; N]実体化: 事前確保された構造体配列に各要素をパース

主要な Rust 型

rust
CodecModule       // モジュール全体のコーデック表現
CodecStruct       // コーデックフィールドを持つ 1 構造体(パケットまたはフレームブランチ)
CodecFrame        // タグ付きユニオン: タグコーデック + ブランチリスト
CodecField        // 1 フィールド: 戦略、ワイヤ型、レイアウト参照、セマンティック参照
FieldStrategy     // パース/シリアライズ戦略の列挙型(上記参照)

パイプラインのプログラム的利用

wirespec-driver クレートがフルパイプラインコンパイルのエントリポイントを提供します:

rust
use wirespec_driver::{compile, CompileRequest};

let result = compile(&CompileRequest {
    entry: "examples/quic/varint.wspec".into(),
    include_paths: vec!["examples/".into()],
    profile: wirespec_sema::ComplianceProfile::default(),
});

match result {
    Ok(result) => {
        for module in &result.modules {
            // module.codec はバックエンド消費可能な CodecModule
            println!("Module: {}", module.module_name);
        }
    }
    Err(e) => eprintln!("error: {e}"),
}

インポート解決なしの単一モジュールコンパイル:

rust
use wirespec_driver::compile_module;

let source = std::fs::read_to_string("examples/net/udp.wspec").unwrap();
let compiled = compile_module(
    &source,
    wirespec_sema::ComplianceProfile::default(),
    &Default::default(),
).unwrap();
// compiled.codec が CodecModule

パイプラインのデバッグ

開発中に各ステージを検査するには:

rust
use wirespec_syntax;
use wirespec_sema;
use wirespec_layout;
use wirespec_codec;

let source = std::fs::read_to_string("examples/quic/varint.wspec").unwrap();

let ast = wirespec_syntax::parse(&source).unwrap();
// AST ノードを検査

let sem = wirespec_sema::analyze(
    &ast,
    wirespec_sema::ComplianceProfile::default(),
    &Default::default(),
).unwrap();
// SemanticModule を検査: sem.packets、sem.frames など

let layout = wirespec_layout::lower(&sem).unwrap();
// LayoutModule を検査

let codec = wirespec_codec::lower(&layout).unwrap();
// CodecModule を検査: codec.packets、codec.frames など

各 IR はプレーンな Rust 構造体で、Debug トレイトが実装されているので検査が可能です。