Files
hakorune/docs/development/current/main/design/loop-canonicalizer.md
nyash-codex ec1ff5b766 docs: update Phase 137-1 completion status and Phase 2 roadmap
## Summary
Loop Canonicalizer Phase 1(型定義)の完了と、Phase 2(dev-only 観測)への
ロードマップを記録。

## Changes

### loop-canonicalizer.md
- Status: Design (P0) → Phase 1 done(型定義まで)
- 実装の入口を明記(src/mir/loop_canonicalizer/mod.rs)
- NYASH_LOOP_ROUTING_TRACE → joinir_dev_enabled() に変更
  (新 ENV を増やさず、既存のスイッチに寄せる)

### CURRENT_TASK.md
- Phase 137-1 完了を追記
- P0 を「設計」→「Phase 2(dev-only 観測)」へ更新

### phases/README.md
- Phase 137 を追加

### phases/phase-137/README.md (NEW)
- Phase 1 完了サマリ
- Phase 2 予定を最小で記録

### joinir-design-map.md / 01-JoinIR-Selfhost-INDEX.md
- loop-canonicalizer への導線を追加

## Next Steps

Phase 2: dev-only 観測の導入
- canonicalize_loop_expr() の薄い入口を追加
- joinir_dev_enabled() 配下でログ出し
- 既定挙動は完全不変

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-16 05:10:29 +09:00

11 KiB
Raw Blame History

Loop Canonicalizer設計 SSOT

Status: Phase 1 done型定義まで
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

目的

  • 実アプリ由来のループ形を、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 は定型の語彙で出す(ログ/デバッグ/統計で集約可能にする)

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 1型定義のみの実装はここ

  • src/mir/loop_canonicalizer/mod.rs

注意:

  • ここは「型と語彙の SSOT」を置く場所で、routing/lowering にはまだ介入しない。
  • Phase 2 以降で canonicalize(loop_ast) -> (LoopSkeleton, RoutingDecision) を導入し、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
    pub chosen: Option<LoopPattern>,

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

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

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

出力先マッピング

出力先 条件 用途
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 に追記してから実装