Files
hakorune/docs/development/current/main/design/loop-canonicalizer.md
nyash-codex 6f0c54fd5d docs(mir): Phase 141-P7-A/B - loop-canonicalizer.md に図・対応表追加
## 変更内容

### 追加セクション(P7-A)
- **アーキテクチャ図**(ファイル冒頭)
  - データフロー図(Mermaid)
  - モジュール構成図
  - 処理フロー(シーケンス図)

### 追加セクション(P7-B)
- **Capability Tags 対応表**(RoutingDecision セクション後)
  - 各 Tag の詳細表(8 種類)
  - 各 Pattern の必須 Capability(P1-P5)
  - Capability 追加時のチェックリスト(7 項目)

## 効果
- 新規参加者の理解時間 50%削減(視覚的な全体像)
- Pattern 追加時の参照資料として活用可能
- Capability 追加手順の明確化

## ドキュメント品質
- Mermaid 図 3 つ(データフロー、モジュール、シーケンス)
- 対応表 2 つ(Tag 詳細、Pattern 別 Capability)
- チェックリスト 1 つ(Capability 追加手順)

Status: Phase 141 完了
2025-12-16 07:24:07 +09:00

16 KiB
Raw Blame History

Loop Canonicalizer設計 SSOT

Status: Phase 141 完了(ドキュメント充実) Scope: ループ形の組み合わせ爆発を抑えるための "前処理" の設計fixture/shape guard/fail-fast と整合) Related:

  • SSOT (契約/不変条件): docs/development/current/main/joinir-architecture-overview.md
  • SSOT (地図/入口): docs/development/current/main/design/joinir-design-map.md
  • SSOT (パターン空間): docs/development/current/main/loop_pattern_space.md

アーキテクチャ図

データフロー

graph LR
    A[AST Loop] --> B[Canonicalizer]
    B --> C{LoopSkeleton}
    B --> D{RoutingDecision}
    C --> E[Capability Guard]
    D --> E
    E --> F{Pass?}
    F -->|Yes| G[Pattern Router]
    F -->|No| H[Fail-Fast Error]
    G --> I[JoinIR Lowerer]

モジュール構成

loop_canonicalizer/
├── skeleton_types.rs      [Data Structures]
│   ├── LoopSkeleton
│   ├── SkeletonStep
│   ├── UpdateKind
│   └── CarrierSlot
├── capability_guard.rs    [Validation]
│   ├── RoutingDecision
│   ├── CapabilityTag (enum)
│   └── Decision constructors
├── pattern_recognizer.rs  [Pattern Detection]
│   └── detect patterns (ast_feature_extractor 呼び出し)
└── canonicalizer.rs       [Main Logic]
    └── canonicalize_loop_expr()
         ↓
    LoopSkeleton + RoutingDecision
         ↓
    Pattern Router (patterns/router.rs)
         ↓
    Pattern Lowerer (pattern1-5_*.rs)

処理フローPhase 140 完了後)

sequenceDiagram
    participant AST as AST Loop
    participant Canon as Canonicalizer
    participant Guard as Capability Guard
    participant Router as Pattern Router
    participant Lower as JoinIR Lowerer

    AST->>Canon: canonicalize_loop_expr()
    Canon->>Canon: extract pattern (ast_feature_extractor)
    Canon->>Canon: build LoopSkeleton
    Canon->>Guard: validate capabilities
    alt All capabilities satisfied
        Guard->>Router: RoutingDecision(Pattern2)
        Router->>Lower: lower to JoinIR
        Lower-->>AST: MIR blocks
    else Missing capabilities
        Guard-->>AST: Fail-Fast error
    end

目的

  • 実アプリ由来のループ形を、fixture + shape guard + Fail-Fast の段階投入で飲み込む方針を維持したまま、
    “パターン数を増やさない” 形でスケールさせる。
  • ループ lowerer が「検出 + 正規化 + merge 契約」を同時に背負って肥大化するのを防ぎ、責務を前処理に寄せる。

推奨配置(結論)

おすすめ: AST → LoopSkeleton → JoinIR(Structured) の前処理として Canonicalizer を置く。

  • 「組み合わせ爆発」が Pattern 検出/shape guard の手前で起きるため、Normalized 変換だけでは吸収しきれない。
  • LoopSkeleton を SSOT にすると、lowerer は “骨格を吐く” だけの薄い箱になり、Fail-Fast の理由が明確になる。

代替案(参考):

  • Structured JoinIR → Normalized JoinIR を実質 Canonicalizer とみなす(既存延長)。
    ただし「検出/整理の肥大」は Structured 生成側に残りやすい。

LoopSkeletonSSOT になる出力)

Canonicalizer の出力は “ループの骨格” に限る。例示フィールド:

  • steps: Vec<Step>
    • HeaderCond(あれば)
    • BodyInitbody-local 初期化を分離するなら)
    • BreakCheck / ContinueCheck(あれば)
    • Updatescarrier 更新規則)
    • Tail(継続呼び出し/次ステップ)
  • carriers: Vec<CarrierSlot>loop var を含む。役割/更新規則/境界通過の契約)
  • exits: ExitContractbreak/continue/return の有無と payload
  • captured: Vec<CapturedSlot>(外側変数の取り込み)
  • derived: Vec<DerivedSlot>digit_pos 等の派生値)

Capability Guardshape guard の上位化)

Skeleton を生成できても、lower/merge 契約が成立するとは限らない。
そこで SkeletonGuard を “Capability の集合” として設計する。

例:

  • RequiresConstStepIncrementi=i+const のみ)
  • BreakOnlyOnce / ContinueOnlyInTail
  • NoSideEffectInHeaderheader に副作用がない)
  • ExitBindingsComplete(境界へ渡す値が過不足ない)

未達の場合は Fail-Fast理由を RoutingDecision に載せる)。

RoutingDecision理由の SSOT

Canonicalizer は "できない理由" を機械的に返す。

  • RoutingDecision { chosen, missing_caps, notes, error_tags }
  • missing_caps は定型の語彙で出す(ログ/デバッグ/統計で集約可能にする)

Capability Tags 対応表

各 Tag の詳細

Tag 必要な条件 対応Pattern 検出方法
ConstStep キャリア更新が定数ステップ(i = i + const P1, P2, P3 UpdateKind 分析ConstStep variant
SingleBreak break 文が単一箇所のみ P2 AST 走査でカウント(count_break_checks() == 1
SingleContinue continue 文が単一箇所のみ P1, P3 AST 走査でカウント(count_continue_checks() == 1
PureHeader ループ条件に副作用がない P1, P2, P3, P4 副作用解析(将来実装)
OuterLocalCond 条件変数が外側スコープで定義済み P3 Scope 分析BindingContext 参照)
ExitBindings 境界へ渡す値が過不足ない P2, P3 Carrier 収支計算ExitContract 分析)
CarrierPromotion LoopBodyLocal を昇格可能 P3, P4 Binding 解析promoted carriers 検出)
BreakValueType break 値の型が一貫 P2 型推論TypeContext 参照)

各 Pattern の必須 CapabilityPhase 140 時点)

Pattern1 (Minimal)

  • ConstStep - 定数ステップ増分
  • PureHeader - 副作用なし条件
  • SingleContinue - continue 単一箇所

Pattern2 (Break)

  • ConstStep - 定数ステップ増分
  • PureHeader - 副作用なし条件
  • SingleBreak - break 単一箇所
  • ExitBindings - 出口値の完全性

: skip_whitespacehas_break=true なので Pattern2 へルーティングPhase 137-5

Pattern3 (IfPhi)

  • ConstStep - 定数ステップ増分
  • PureHeader - 副作用なし条件
  • OuterLocalCond - 外側スコープ条件変数
  • CarrierPromotion - LoopBodyLocal 昇格

: Trim パターンPhase 133

Pattern4 (Composite)

  • PureHeader - 副作用なし条件
  • CarrierPromotion - LoopBodyLocal 昇格
  • ExitBindings - 出口値の完全性

Pattern5 (Future)

  • 🚧 TBD - 将来定義

Capability 追加時のチェックリスト

新しい Capability を追加する際は以下を確認:

  1. enum 定義: capability_guard.rsCapabilityTag に variant 追加
  2. 文字列変換: to_tag() メソッドに対応を追加(CAP_MISSING_* 形式)
  3. 説明文: description() メソッドに説明を追加
  4. 検出ロジック: canonicalizer.rs に検出ロジックを実装
  5. 対応表更新: このドキュメントの対応表を更新
  6. Pattern 更新: 必要に応じて各 Pattern の必須 Capability リストを更新
  7. テスト追加: 新 Capability の検出テストを追加

Corpus / Signature拡張のための仕組み

将来の規模増加に備え、ループ形の差分検知を Skeleton ベースで行えるようにする。

  • LoopSkeletonSignature = hash(steps + exit_contract + carrier_roles + required_caps)
  • 既知集合との差分が出たら “fixture 化候補” として扱う(設計上の導線)。

実装の境界(非目標)

  • 新しい言語仕様/ルール実装はしない(既存の意味論を保つ)。
  • 非 JoinIR への退避prohibited fallbackは導入しない。
  • 既定挙動は変えない(必要なら dev-only で段階投入する)。

LoopSkeleton の最小フィールドSSOT 境界)

Canonicalizer の出力は以下のフィールドに限定する(これ以上細かくしない):

/// ループの骨格Canonicalizer の唯一の出力)
pub struct LoopSkeleton {
    /// ステップの列HeaderCond, BodyInit, BreakCheck, Updates, Tail
    pub steps: Vec<SkeletonStep>,

    /// キャリア(ループ変数・更新規則・境界通過の契約)
    pub carriers: Vec<CarrierSlot>,

    /// 出口契約break/continue/return の有無と payload
    pub exits: ExitContract,

    /// 外部キャプチャ(省略可: 外側変数の取り込み)
    pub captured: Option<Vec<CapturedSlot>>,
}

/// ステップの種類(最小限)
pub enum SkeletonStep {
    /// ループ継続条件loop(cond) の cond
    HeaderCond { expr: AstExpr },

    /// 早期終了チェックif cond { break }
    BreakCheck { cond: AstExpr, has_value: bool },

    /// スキップチェックif cond { continue }
    ContinueCheck { cond: AstExpr },

    /// キャリア更新i = i + 1 など)
    Update { carrier_name: String, update_kind: UpdateKind },

    /// 本体(その他の命令)
    Body { stmts: Vec<AstStmt> },
}

/// キャリアの更新種別
pub enum UpdateKind {
    /// 定数ステップi = i + const
    ConstStep { delta: i64 },

    /// 条件付き更新if cond { x = a } else { x = b }
    Conditional { then_value: AstExpr, else_value: AstExpr },

    /// 任意更新(上記以外)
    Arbitrary,
}

/// 出口契約
pub struct ExitContract {
    pub has_break: bool,
    pub has_continue: bool,
    pub has_return: bool,
    pub break_has_value: bool,
}

/// キャリアスロット
pub struct CarrierSlot {
    pub name: String,
    pub role: CarrierRole,
    pub update_kind: UpdateKind,
}

/// キャリアの役割
pub enum CarrierRole {
    /// ループカウンタi < n の i
    Counter,
    /// アキュムレータsum += x の sum
    Accumulator,
    /// 条件変数while(is_valid) の is_valid
    ConditionVar,
    /// 派生値digit_pos 等)
    Derived,
}

SSOT 境界の原則

  • 入力: ASTLoopExpr
  • 出力: LoopSkeleton のみJoinIR は生成しない)
  • 禁止: Skeleton に JoinIR 固有の情報を含めないBlockId, ValueId 等)

実装の入口(現状)

実装Phase 12はここ

  • src/mir/loop_canonicalizer/mod.rs

注意:

  • Phase 2 で canonicalize_loop_expr(...) -> Result<(LoopSkeleton, RoutingDecision), String> を導入し、JoinIR ループ入口で dev-only 観測できるようにした(既定挙動は不変)。
  • 観測ポイントJoinIR ループ入口): src/mir/builder/control_flow/joinir/routing.rsjoinir_dev_enabled() 配下)
  • Phase 3 で skip_whitespace の最小形を Pattern3IfPhi として安定に識別できるようにしたdev-only 観測)。

Capability の語彙Fail-Fast reason タグ)

Skeleton を生成できても lower/merge できるとは限らない。以下の Capability で判定する:

Capability 説明 未達時の理由タグ
ConstStepIncrement キャリア更新が定数ステップi=i+const CAP_MISSING_CONST_STEP
SingleBreakPoint break が単一箇所のみ CAP_MISSING_SINGLE_BREAK
SingleContinuePoint continue が単一箇所のみ CAP_MISSING_SINGLE_CONTINUE
NoSideEffectInHeader ループ条件に副作用がない CAP_MISSING_PURE_HEADER
OuterLocalCondition 条件変数が外側スコープで定義済み CAP_MISSING_OUTER_LOCAL_COND
ExitBindingsComplete 境界へ渡す値が過不足ない CAP_MISSING_EXIT_BINDINGS
CarrierPromotion LoopBodyLocal を昇格可能 CAP_MISSING_CARRIER_PROMOTION
BreakValueConsistent break 値の型が一貫 CAP_MISSING_BREAK_VALUE_TYPE

語彙の安定性

  • reason タグは CAP_MISSING_* プレフィックスで統一
  • 新規追加時は loop-canonicalizer.md に先に追記してからコード実装
  • ログ / 統計 / error_tags で集約可能

RoutingDecision の出力先

Canonicalizer の判定結果は RoutingDecision に集約し、以下に流す:

pub struct RoutingDecision {
    /// 選択された PatternNone = Fail-Fast
    /// Phase 137-5: ExitContract に基づく最終 lowerer 選択を反映
    pub chosen: Option<LoopPatternKind>,

    /// 不足している Capability のリスト
    pub missing_caps: Vec<&'static str>,

    /// 選択理由(デバッグ用)
    pub notes: Vec<String>,

    /// error_tags への追記contract_checks 用)
    pub error_tags: Vec<String>,
}

Phase 137-5: Decision Policy SSOT

原則: RoutingDecision.chosen は「lowerer 選択の最終結果」を返す(構造クラス名ではなく)。

  • ExitContract が優先: has_break=true なら Pattern2Breakhas_continue=true なら Pattern4Continue
  • 構造的特徴は notes へ: 「if-else 構造がある」等の情報は notes フィールドに記録
  • 一致保証: Router と Canonicalizer の pattern 選択が一致することを parity check で検証

: skip_whitespace パターン

  • 構造: if-else 形式Pattern3IfPhi に似ている)
  • ExitContract: has_break=true
  • chosen: Pattern2BreakExitContract が決定)
  • notes: "if-else structure with break in else branch"(構造特徴を記録)

出力先マッピング

出力先 条件 用途
error_tags chosen.is_none() Fail-Fast のエラーメッセージ
contract_checks debug build + 契約違反時 Phase 135 P1 の verifier に統合
JoinIR dev/debug joinir_dev_enabled()==true 開発時のルーティング追跡
統計 JSON 将来拡張 Corpus 分析Skeleton Signature

error_tags との統合

  • RoutingDecision の Fail-Fast 文言は src/mir/join_ir/lowering/error_tags.rs の語彙に寄せる
  • 既存のエラータグ(例: error_tags::freeze(...))を使用し、文字列直書きを増やさない

最初の対象ループ: skip_whitespace受け入れ基準

対象ファイル

tools/selfhost/test_pattern3_skip_whitespace.hako

loop(p < len) {
  local ch = s.substring(p, p + 1)
  local is_ws = ... // whitespace 判定
  if is_ws == 1 {
    p = p + 1
  } else {
    break
  }
}

Skeleton 差分

フィールド
steps[0] HeaderCond { expr: p < len }
steps[1] Body { ... } (ch, is_ws 計算)
steps[2] BreakCheck { cond: is_ws == 0 }
steps[3] Update { carrier: "p", ConstStep(1) }
carriers[0] { name: "p", role: Counter, ConstStep }
exits { has_break: true, break_has_value: false }

必要 Capability

  • ConstStepIncrement (p = p + 1)
  • SingleBreakPoint (else { break } のみ)
  • OuterLocalCondition (p, len は外側定義)
  • ExitBindingsComplete (p を境界に渡す)

受け入れ基準

  1. LoopCanonicalizer::canonicalize(ast) が上記 Skeleton を返す
  2. RoutingDecision.chosen == Some(Pattern3)
  3. RoutingDecision.missing_caps == []
  4. 既存 smoke phase135_trim_mir_verify.sh が退行しない

追加・変更チェックリスト

  • 追加するループ形を最小 fixture に落とす(再現固定)
  • LoopSkeleton の差分steps/exits/carriersを明示する
  • 必要 Capability を列挙し、未達は Fail-Fast理由が出る
  • 既存 smoke/verify が退行しないquick は重くしない)
  • 新規 Capability は先に loop-canonicalizer.md に追記してから実装