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 クレート 主要な型: WireFile、PacketDef、FrameDef、CapsuleDef、TypeDef、StateMachineDef、Expr
AST はソーステキストの直接的な構造表現です。何も解決されていません。式中のフィールド参照は単なる名前文字列、型参照は未解決の識別子のままです。
主要な型:
| 型 | 概要 |
|---|---|
WireFile | トップレベルコンテナ: モジュール宣言、インポート、定義 |
PacketDef | packet Foo { ... } 定義 |
FrameDef | frame Foo = match tag: T { ... } 定義 |
CapsuleDef | capsule Foo { ... within ... } 定義 |
TypeDef | type Foo = ...(エイリアスまたは計算型) |
StateMachineDef | state machine Foo { ... } |
Expr | 式 AST ノード: NameExpr、LiteralExpr、BinaryExpr、CoalesceExpr など |
AST は構文指向であり、バックエンドが直接使ってはなりません。意味解決が必要な場合、バックエンドは Semantic IR 以下を使用してください。
ステージ 2: Semantic IR
生成元: wirespec-sema クレート 主要な型: SemanticModule、SemanticStruct、SemanticFrame、StateMachine、SemanticVarInt、SemField
プログラムの意味が完全に確定する最初のステージです。すべての名前が解決され、すべての型が判明します。
WireType 列挙型
全フィールドに WireType バリアントが割り当てられます。
| バリアント | 意味 |
|---|---|
U8、U16、U24、U32、U64 | 符号なし整数 |
I8、I16、I32、I64 | 符号あり整数 |
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) 述語 |
SemAll | all(collection, predicate) 量化子 |
SemSlice | field[start..end] 半開スライス |
SemSubscript | field[index] 配列添字 |
Option[T] の追跡
条件付きフィールド(if COND { T })は Semantic IR で Option[T] として型付けされます。そのようなフィールドを式で参照するには ?? を使うかガード内に置く必要があり、ここで強制されます。
状態機械の検証
状態機械定義はこのステージで検証されます。
- 遷移で参照される全状態が存在すること
on節のイベントのパラメータ型が一貫していることactionブロックがデフォルト値のないdstフィールドをすべて初期化していることdelegateとactionが同一遷移に共存していないこと
主要な Rust 型
SemanticModule // .wspec ファイルごとに 1 つ: 構造体、フレーム、状態機械、インポート
SemanticStruct // パケットまたはフレームブランチ: 名前、フィールド、制約
SemanticFrame // タグ付きユニオン: タグフィールド + SemanticStruct ブランチのリスト
StateMachine // 状態機械: 状態、遷移、初期状態
SemanticVarInt // 計算型(プレフィックスマッチまたは継続ビット)
SemField // WireType、名前、オプショナルな SemExpr 条件を持つ単一フィールドステージ 3: Layout IR
生成元: wirespec-layout クレート 主要な型: LayoutModule、LayoutField、BitGroup、Endianness
Layout IR はワイヤ上のバイト形状を記述します。バイトがどの順序で並び、ビットがどうパックされているかを表現します。
エンディアンネスの解決
各フィールドに Endianness(Big、Little、None)が割り当てられます。
- 明示的な型サフィックスが最優先:
u16le→Little - モジュールの
@endianアノテーションがフォールバック - 型エイリアスは基底型まで追跡
ビットグループの折りたたみ
パケット、フレームブランチ、カプセル内の連続する bits[N]/bit フィールドは単一の BitGroup に折りたたまれます。
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 型
LayoutModule // モジュール全体のレイアウトラッパー
LayoutField // エンディアンネスを持つ単一フィールド
BitGroup // 連続 bits[N] フィールドのグループ + トータルバイト幅
Endianness // Big | Little | NoneLayout IR はコード生成の関心事を意図的に含みません。uint32_t や関数シグネチャについては一切言及しません。それは Codec IR の仕事です。
ステージ 4: Codec IR
生成元: wirespec-codec クレート 主要な型: CodecModule、CodecStruct、CodecFrame、CodecField、FieldStrategy
Codec IR は最終的なバックエンド非依存の表現で、全フィールドに FieldStrategy とターゲット型を割り当てます。
FieldStrategy
| 戦略 | 内容 |
|---|---|
Primitive | N バイト読み取り + エンディアン変換 |
VarInt | プレフィックスマッチ可変長整数のデコード |
ContVarInt | 継続ビット可変長整数のデコード |
BytesFixed | ゼロコピーバイトスライス(ポインタ + 長さ) |
BytesLength | 長さプレフィックス付きバイトスライス |
BytesRemaining | 現在のスコープの残りを全消費 |
Array | カウントフィールドを読み取り、N 要素をループでパース |
BitGroup | 1 回読み取り + フィールドごとのシフト/マスク |
Struct | ネスト構造体のパース/シリアライズ呼び出し |
Conditional | 条件を評価し、真ならパース |
Checksum | パース時に検証、シリアライズ時に自動計算 |
Derived | let フィールド — 他フィールドから計算、ワイヤ上には存在しない |
Constraint | require 式 — 実行時チェックのみ |
MemoryTier
3 層メモリモデル:
| 層 | 例 | 戦略 |
|---|---|---|
| A | bytes[length] | ゼロコピー: 入力バッファへのポインタ + 長さビュー |
| B | [u16le; N] | 実体化: 固定配列への memcpy + バイトスワップ |
| C | [AckRange; N] | 実体化: 事前確保された構造体配列に各要素をパース |
主要な Rust 型
CodecModule // モジュール全体のコーデック表現
CodecStruct // コーデックフィールドを持つ 1 構造体(パケットまたはフレームブランチ)
CodecFrame // タグ付きユニオン: タグコーデック + ブランチリスト
CodecField // 1 フィールド: 戦略、ワイヤ型、レイアウト参照、セマンティック参照
FieldStrategy // パース/シリアライズ戦略の列挙型(上記参照)パイプラインのプログラム的利用
wirespec-driver クレートがフルパイプラインコンパイルのエントリポイントを提供します:
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}"),
}インポート解決なしの単一モジュールコンパイル:
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パイプラインのデバッグ
開発中に各ステージを検査するには:
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 トレイトが実装されているので検査が可能です。