Files
hakorune/docs/development/current/main/joinir-architecture-overview.md
nyash-codex 23ebb86e6e docs(joinir): Phase 47 - Normalized P3 (If-Sum) design
Complete design for extending Normalized JoinIR to Pattern3 (if-sum) loops,
reusing 90% of existing P2 infrastructure.

Key insight: P3 already uses P2's Structured JoinIR foundation (Phase 220
ConditionEnv integration), so Normalized extension is straightforward.

Design documents:
- phase47-norm-p3-design.md: Complete P3 Normalized design
  - Infrastructure reuse analysis (ConditionEnv, CarrierInfo, ExitLine)
  - Representative loops identified (phase212_if_sum_min primary target)
  - StepScheduleBox extension (IfCond, ThenUpdates, ElseUpdates)
  - JpInst reuse (If instruction already exists)
  - Implementation strategy (dev-only → canonical)
  - File impact estimate (~365 lines, pure additive)

- phase47-test-files-inventory.md: P3 test file catalog
  - 3 P3 test files analyzed
  - phase212_if_sum_min.hako: Single carrier (PRIMARY)
  - phase217_if_sum_multi_min.hako: Multi-carrier (sum + count)
  - phase218_json_if_sum_min.hako: Variable accumulation (sum + i)

Architecture:
- Unified Normalized for P1/P2/P3 (same pipeline)
- Conditional updates only difference vs P2
- No P2 code changes needed (pure additive)

Benefits:
- 90% infrastructure reuse from P2
- Proven incremental rollout (dev → canonical)
- Clear path to P4 (continue pattern)

Implementation roadmap:
- Phase 47-A: Minimal sum_count (dev-only)
- Phase 47-B: array_filter (body-local + method calls)
- Phase 47-C: Canonical promotion

Updates:
- joinir-architecture-overview.md: Added Phase 47 section
- CURRENT_TASK.md: Updated Phase 47 entry (Design Complete)

Status: Design phase complete, ready for implementation
2025-12-12 04:50:26 +09:00

95 KiB
Raw Blame History

JoinIR Architecture Overview (20251208)

このドキュメントは、JoinIR ライン全体Loop/If lowering, ExitLine, Boundary, 条件式 loweringの 「箱」と「契約」を横串でまとめた設計図だよ。selfhost / JsonParser / hako_check など、 どの呼び出し元から見てもここを見れば「JoinIR 層の責務と流れ」が分かるようにしておく。

変更があったら、Phase ドキュメントではなく このファイルを随時更新する 方針。


1. 不変条件Invariants

JoinIR ラインで守るべきルールを先に書いておくよ:

  1. JoinIR 内部は JoinIR ValueId だけ

    • JoinIR lowering が発行する JoinInstValueId は、すべて alloc_value() で割り当てるローカル ID。
    • Rust/MIR 側の ValueIdbuilder.variable_map に入っている IDは、JoinIR には直接持ち込まない。
  2. host ↔ join の橋渡しは JoinInlineBoundary 系だけ

    • host から JoinIR への入力(ループ変数 / 条件専用変数)は
      • JoinInlineBoundary.join_inputs + host_inputs
      • JoinInlineBoundary.condition_bindingsConditionBinding だけで接続する。
    • 出力(キャリアの出口)は JoinInlineBoundary.exit_bindings に一本化する。
  3. LoopHeader PHI を SSA の単一源泉にする

    • ループ変数とキャリアは LoopHeaderPhiBuilder/LoopHeaderPhiInfo でヘッダ PHI を作り、これを「現在値」の SSOT にする。
    • exit 用の PHI も組むが、変数再接続や expr_result 収集はヘッダ PHI を経由して行うSSAundef 防止)。
  4. 式としての戻り値とキャリア更新を分離する

    • 「ループが式として値を返す」ケース(例: let r = loop_min_while(...))の出口は exit_phi_builder が扱う。
    • 「ループが状態更新だけする」ケース(例: trimstart/end)の出口は ExitLineExitMeta / ExitBinding / ExitLineReconnector だけが扱う。
  5. ループ制御 vs 条件式の分離

    • ループの「形」Pattern14, LoopFeaturesは control-flow 専用の箱が担当。
    • 条件式(i < len && (ch == " " || ch == "\t") 等)は BoolExprLowerer / condition_to_joinir が担当し、 ループパターンは boolean ValueId だけを受け取る。
  6. FailFast

    • JoinIR が対応していないループパターン / if パターンは、必ず [joinir/freeze] 等で明示的にエラーにする。
    • LoopBuilder 等へのサイレントフォールバックは禁止。
  7. Param の役割ParamRoleを分ける

    • JoinIR 側で扱うパラメータは概念的に 3 種類に分かれる:
      • 条件専用Condition param: 継続条件や break 条件だけに使う値
      • キャリアCarrier param: ループ状態pos/result など)として更新される値
      • 式結果Expr param: ループが式として返す値
    • ExitLine / Header PHI / InstructionRewriter は Carrier param だけ を対象にし、Condition/Expr param は上書きしない。
    • 現状は ConditionBinding/ExitMeta/JoinFragmentMeta で役割を区別しており、将来 ParamRole enum として明示する予定。
  8. LoopHeader PHI dst は予約領域(上書き禁止)

    • LoopHeaderPhiBuilder が生成したヘッダ PHI の dst ValueId は「現在のループ値」の SSOT として扱い、 BoundaryInjector や InstructionRewriter が Copy などで二度書きすることを禁止する。
    • merge ラインでは「ヘッダ PHI dst に対する新しい定義が出てきたら debug モードで panic する」ことで契約違反を早期検出する。
  9. ParamRole の不変条件Phase 200-A 追加)

    • Condition 役のパラメータは「PHI dst にしてはいけない」
      • 理由: 条件専用変数はループ内で更新されない(例: digits in _atoi()
      • LoopHeaderPhiBuilder は Condition 役の変数に対して header PHI を生成しない
    • Condition 役のパラメータは「ExitLine の対象にも入れない」
      • 理由: 条件専用変数はループ外で使われない(ループ内でのみ参照)
      • ExitLineReconnector は Condition 役の変数を exit_bindings から除外
    • ParamRole の分類:
      • LoopParam: ループ制御変数(例: i in loop(i < len))→ header PHI + exit_bindings
      • Condition: 条件専用変数(例: digits in digits.indexOf(ch))→ condition_bindings のみ
      • Carrier: 状態更新変数(例: sum, count)→ header PHI + exit_bindings
      • ExprResult: ループ戻り値 → exit_phi_builder で処理
  10. JoinIR Core は常時 ON

    • LoopBuilder は物理削除済み。JoinIR を OFF にする経路やフォールバックは存在しない。
    • NYASH_JOINIR_CORE は deprecated0 を指定しても警告して無視。JoinIR の OFF トグルは提供しない。
  11. Phase 220: P3 if-sum の ConditionEnv 統合完了

    • LoopBuilder へのフォールバック経路は Phase 187 で完全削除済み
    • P3 if-sum には ConditionPatternBox + ConditionEnv が必須:
      • 単純条件(var CmpOp literal)のみ if-sum mode へルーティングPhase 219-fix
      • 複雑条件(i % 2 == 1 等)は legacy P3 経路へフォールバック。
    • Phase 220-D で loop 条件の変数サポート完了:
      • loop(i < len) のような変数条件を ConditionEnv で解決。
      • extract_loop_condition() を ValueOrLiteral 対応に拡張。
    • Phase 220 で if-sum の expr-result exit contract が P2 と揃った:
      • ExprResultResolver Box で expr_result routing を統一化Phase 221-R
      • phase212_if_sum_min.hako → RC=2 達成。

1.9 ValueId Space Management (Phase 201)

JoinIR の ValueId 割り当ては JoinValueSpace で一元管理され、3つの領域に分離されているよ

1.9.1 ValueId 空間のダイアグラム

JoinValueSpace Memory Layout:

 0          100        1000                     u32::MAX
 ├──────────┼──────────┼──────────────────────────┤
 │  PHI     │  Param   │       Local             │
 │  Reserved│  Region  │       Region            │
 └──────────┴──────────┴──────────────────────────┘

PHI Reserved (0-99):
  - LoopHeader PHI dst 用の予約領域
  - reserve_phi(id) で特定 ID をマーク

Param Region (100-999):
  - alloc_param() で割り当て
  - 使用箇所: ConditionEnv, CarrierInfo.join_id, CapturedEnv

Local Region (1000+):
  - alloc_local() で割り当て
  - 使用箇所: Pattern lowerers (Const, BinOp, etc.)

1.9.2 JoinValueSpace の役割

  • 単一の真実源 (SSOT): すべての JoinIR ValueId 割り当てを一箇所で管理
  • 領域分離: Param ID、Local ID、PHI dst が決して重複しない
  • 契約検証: デバッグモードで違反を早期検出
  • 後方互換性: 既存 API は継続動作

1.9.3 各コンポーネントと ValueId 領域の対応表

コンポーネント 使用領域 割り当て方法 用途
ConditionEnv Param (100-999) alloc_param() ループ条件変数の JoinIR ValueId
CarrierInfo.join_id Param (100-999) alloc_param() キャリア変数の JoinIR ValueId
CapturedEnv Param (100-999) alloc_param() 関数スコープ変数の JoinIR ValueId
Pattern 1 lowerer Local (1000+) alloc_local() 中間値Const, Compare, etc.
Pattern 2 lowerer Local (1000+) alloc_local() 中間値Const, BinOp, etc.
Pattern 3 lowerer Local (1000+) alloc_local() 中間値PHI, Select, etc.
Pattern 4 lowerer Local (1000+) alloc_local() 中間値Select, BinOp, etc.
LoopHeaderPhiBuilder PHI Reserved (0-99) reserve_phi() PHI dst ID 保護(上書き防止)

1.9.4 設計原則

  1. 領域の固定境界

    • 明確な境界100, 1000で領域を分離
    • デバッグが容易ValueId を見ればどの領域か一目瞭然)
    • アロケータ間の調整不要
  2. reserve_phi() vs alloc_phi()

    • PHI dst ID は MirBuilderhost 側から来るため、JoinValueSpace は割り当てない
    • reserve_phi() はマーカーのみ(「この ID を上書きするな」という契約)
  3. value_id_ranges.rs との関係

    • value_id_ranges.rs: モジュールレベルの分離min_loop, skip_ws 等の各モジュールに大きな固定範囲を割り当て)
    • JoinValueSpace: lowering 内部の分離param vs local vs PHI
    • 両者は相補的な役割

1.9.5 Phase 205: 領域契約の検証強化

追加された Box-First 機能:

  1. 衝突検出debug-only

    • 全ての割り当てられた ValueId を追跡(allocated_ids: HashSet<u32>
    • 重複割り当てを即座に検出し panicFail-Fast 原則)
    • check_collision() で実装
  2. 領域検証debug-only

    • verify_region(id, expected_region) で ValueId が期待される領域にいるか検証
    • 違反時は明確なエラーメッセージと修正ヒントを提供
    • 例: "ValueId(500) is in Param region, expected Local. Hint: Use alloc_local() for JoinIR values"
  3. RegionVerifier Box

    • 場所: src/mir/builder/control_flow/joinir/merge/mod.rs::verify_valueid_regions()
    • 責務: merge 時に boundary と loop_info の ValueId 領域契約を検証
    • 検証項目:
      • 全ての boundary.join_inputs が Param 領域100-999にいる
      • 全ての condition_bindings[].join_value が Param 領域にいる
      • 全ての carrier_phis[].phi_dst が有効範囲(<= LOCAL_MAX
  4. 明示的な領域定数

    pub const PHI_RESERVED_MIN: u32 = 0;
    pub const PHI_RESERVED_MAX: u32 = 99;
    pub const PARAM_MIN: u32 = 100;
    pub const PARAM_MAX: u32 = 999;
    pub const LOCAL_MIN: u32 = 1000;
    pub const LOCAL_MAX: u32 = 100000;
    

Fail-Fast 原則の実装:

  • 領域違反は即座に panicデバッグモード
  • フォールバックやサイレント修正は一切行わない
  • エラーメッセージに具体的な修正方法を含める

詳細は src/mir/join_ir/lowering/join_value_space.rsphase205-valueid-regions-design.md を参照。


2. 主な箱と責務

2.1 Loop 構造・検出ライン

  • LoopFeatures / LoopPatternKind / router

    • ファイル:
      • src/mir/loop_pattern_detection.rs
      • src/mir/builder/control_flow/joinir/patterns/router.rs
      • src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs
    • 責務:
      • AST から break/continue/ifelse PHI などの特徴を抽出ast_feature_extractor
      • classify(&LoopFeatures) で Pattern14 に分類し、テーブル駆動の LOOP_PATTERNS でルーティング。
      • ルータ順序は P4(continue) → P3(ifphi) → P1(simple) → P2(break) で固定(優先度フィールドはデバッグ用)。
  • Pattern Lowerers (Pattern14)

    • ファイル例:
      • pattern1_minimal.rsSimple while
      • pattern2_with_break.rsbreak 付き / Trim 昇格パスを含む)
      • pattern3_with_if_phi.rsifphi キャリア)
      • pattern4_with_continue.rscontinue を Select で表現)
    • 責務:
      • LoopScopeShape / AST / LoopFeatures を入力として JoinIR の JoinModule を構築。
      • JoinFragmentMeta{ expr_result, exit_meta } を返し、出口情報を ExitLine に渡す。
      • host/MIR の ValueId は一切扱わないJoinIR ローカルの ValueId のみ)。
  • Scope / Env Builders

    • loop_scope_shape_builder.rs: ループ本体ローカルの収集、LoopScopeShape 統一生成。
    • condition_env_builder.rs: 条件専用変数の環境と ConditionBinding を一括構築。
  • CommonPatternInitializer (Phase 33-22)

    • ファイル: src/mir/builder/control_flow/joinir/patterns/common_init.rs
    • 責務:
      • 全 Pattern 共通の初期化ロジック統一化(ループ変数抽出 + CarrierInfo 構築)。
      • 全パターンで boundary.loop_var_name を確実に設定し、SSAundef を防ぐ。
  • PatternPipelineContext (Phase 179-B)

    • ファイル: src/mir/builder/control_flow/joinir/patterns/pattern_pipeline.rs
    • 責務:
      • 全 Pattern の前処理結果を格納する「解析済みコンテキスト箱」。
      • CommonPatternInitializer + LoopScopeShapeBuilder の結果を統一的に保持。
      • Pattern 1-4 の共通データloop_var_name, loop_var_id, carrier_info, loop_scopeを提供。
      • Pattern 2/4 専用データcondition_env, carrier_updates, trim_helperは Option で保持。
      • Analyzer-only dependencies: 解析ロジックのみ依存、JoinIR emission ロジックは含まない。
  • TrimLoopLowerer (P5 Dedicated Module) (Phase 180)

    • ファイル: src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs
    • 責務:
      • Trim/CharComparison (Pattern 5) 専用の lowering ロジックを一箇所に集約。
      • Pattern2/4 から呼ばれ、LoopBodyLocal 変数を carrier に昇格し、Trim パターンの break 条件を置き換える。
      • TrimPatternValidator/TrimPatternLowerer を内部で利用し、carrier 初期化コード生成・条件式置換を実行。
    • 入力:
      • MirBuilder, LoopScopeShape, loop_cond, break_cond, body, loop_var_name, carrier_info, alloc_join_value
    • 出力:
      • Some(TrimLoweringResult): Trim パターン検出・lowering 成功時(置換後条件、更新 carrier_info、condition_bindings
      • None: Trim パターンでない場合(通常ループ処理に戻る)
      • Err: Trim パターン検出したが lowering 失敗時
    • 使用元:
      • Pattern2 (pattern2_with_break.rs): Trim/P5 ロジックを完全委譲(~160 行削減)
      • Pattern4 (pattern4_with_continue.rs): 将来の Phase 172+ で Trim lowering 実装時に利用予定
    • デザイン原則:
      • Pure analysis container前処理結果のみ保持、emission なし)
      • Pattern-specific variantsOption でパターン固有データ管理)
      • Single source of truth全パターンが同じ前処理経路を使用
  • JoinIRConversionPipeline (Phase 33-22)

    • ファイル: src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs
    • 責務:
      • JoinIR → MIR 変換フロー統一化JoinModule → MirModule → merge_joinir_mir_blocks
      • JoinIR/MIR の関数数・ブロック数をログ出力し、全パターンが同じ入口でマージする。

2.2 条件式ライン(式の箱)

  • BoolExprLowerer / condition_to_joinir

    • ファイル:
      • src/mir/join_ir/lowering/bool_expr_lowerer.rs
      • src/mir/join_ir/lowering/condition_to_joinir.rs
    • 責務:
      • 通常の if/while 条件を MIR Compare/BinOp/UnaryOp へ lowering。
      • ループ lowerer 用の「AST 条件 → JoinIR Compute 命令列」を ConditionEnv とセットで構築。
  • ConditionEnv/ConditionBinding + ConditionEnvBuilder

    • ファイル:
      • src/mir/join_ir/lowering/condition_env.rs
      • src/mir/builder/control_flow/joinir/patterns/condition_env_builder.rs
    • 責務:
      • 変数名→JoinIR ValueId の環境を組み立て、host↔join の橋渡しを ConditionBinding に明示する。
      • Pattern 2 では break 条件の全変数をスキャンし、JoinInlineBoundary.condition_bindings に渡す。
  • LoopConditionScopeBoxPhase 170-D 実装済み)

    • ファイル: src/mir/loop_pattern_detection/loop_condition_scope.rs
    • 責務:
      • 条件式の各変数を LoopParam / OuterLocal / LoopBodyLocal に分類。
      • 関数パラメータ誤分類バグは condition_var_analyzer.rs の修正で解消済みOuterLocal として扱う)。
  • ConditionPatternBoxPhase 219-fix / Phase 222 拡張完了)

    • ファイル: src/mir/join_ir/lowering/condition_pattern.rs
    • 責務:
      • if 条件の複雑度を判定SimpleComparison vs Complex
      • Phase 222: 条件を正規化var on left の canonical form
        • normalize_comparison() で左右反転をサポート:
          • 0 < ii > 0 (literal < var → var > literal)
          • i > j (var CmpOp var) - そのまま受理
      • 複雑条件(i % 2 == 1、MethodCall 等)は false を返し、legacy P3 経路へルーティング。
    • 使用箇所:
      • PatternPipelineContext.is_if_sum_pattern() で条件複雑度をチェック。
      • P3 if-sum mode は単純比較のみ受理、複雑条件は PoC lowerer へフォールバック。
  • MethodCallLowererPhase 224-B / 224-C / 225 実装完了)

    • ファイル: src/mir/join_ir/lowering/method_call_lowerer.rs
    • 責務:
      • AST MethodCall ノードを JoinIR BoxCall に loweringメタデータ駆動
      • CoreMethodId の is_pure(), allowed_in_condition(), allowed_in_init() などのメタでホワイトリスト判定。
      • Phase 224-B: 引数なしメソッド(length() 等)対応。
      • Phase 224-C: 引数付きメソッド(substring(i,j), indexOf(ch) 等)対応。
      • Phase 225: body-local init 用の MethodCall lowering も完全に CoreMethodId メタ駆動に統一(メソッド名ハードコード/Box 名ハードコードを削除)。
    • 設計原則:
      • メソッド名ハードコード禁止: CoreMethodId メタデータのみ参照。
      • Fail-Fast: ホワイトリストにないメソッドは即座にエラー。
      • Box-First: 単一責任("このMethodCallをJoinIRにできるか")だけを担当。
    • 使用箇所:
      • condition_lowerer.rslower_value_expression() から呼び出し。
      • loop_body_local_init.rs の init lowering からも呼び出され、bodylocal init での substring/indexOf などを lowering。
      • Pattern 2/3/4 のループ条件式や bodylocal init で s.length(), s.substring(...), digits.indexOf(ch) 等をサポート可能(メタ条件を満たす範囲で)。
  • LoopBodyCarrierPromoterPhase 171-C-2 実装済み)

    • ファイル: src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs
    • 責務:
      • LoopBodyLocal を Trim パターンとして bool キャリアへ昇格substring + equality 連鎖を検出)。
      • 昇格成功 → CarrierInfo に統合し Pattern 2/4 へ橋渡し。昇格失敗は FailFast。
      • Pattern 2 は安全な Trim なら実際に前処理substring 生成 + 空白比較の初期化)を emit してから JoinIR lowering。
      • Pattern 4 は Trim 昇格が起きた場合はガード付きでエラーにし、未実装を明示FailFast
    • 汎用性:
      • Phase 173 で _skip_whitespaceJsonParserが Trim パターンで動作確認済み。
      • Phase 174 で _parse_string 最小化版(終端クォート検出)でも動作確認済み。
      • → 空白文字以外の文字比較ループにも対応可能TrimLoopHelper の汎用性実証)。
  • LoopBodyCondPromoterPhase 223-3 + 223.5 + 224 実装完了)

    • ファイル: src/mir/loop_pattern_detection/loop_body_cond_promoter.rs
    • 責務:
      • ループ条件header/break/continueに出てくる LoopBodyLocal を carrier に昇格する統一 API。
      • Pattern 2/Pattern 4 両対応の薄いコーディネーター箱detection は専門 Promoter に委譲)。
      • Phase 224: Two-tier strategy - A-3 Trim → A-4 DigitPos フォールバック型オーケストレーション。
    • Phase 223-3 実装内容:
      • extract_continue_condition(): body 内の if 文から continue 条件を抽出。
      • try_promote_for_condition(): LoopBodyCarrierPromoter を使った昇格処理。
      • Pattern4 への統合完了: LoopBodyLocal 条件の昇格成功時に lowering を続行(以前は Fail-Fast
    • Phase 223.5 実装内容:
      • Pattern2 への統合完了: header/break 条件を分析し昇格を試みる。
  • ScopeManager / ExprLowererPhase 231 パイロット実装完了)

    • ファイル:
      • src/mir/join_ir/lowering/scope_manager.rs
      • src/mir/join_ir/lowering/expr_lowerer.rs
    • 責務:
      • ScopeManager trait: 変数参照を統一的に扱う traitConditionEnv / LoopBodyLocalEnv / CapturedEnv / CarrierInfo を統合)。
      • Pattern2ScopeManager: Pattern2 専用の薄いラッパーpromoted_loopbodylocals 対応含む)。
      • ExprLowerer: 式 lowering を1箇所に集約Phase 231: Condition context のみ、General context は将来実装)。
    • Phase 231 実装内容:
      • Pattern2 break 条件の pre-validation として ExprLowerer を試行fallback 完備)。
      • 簡単な条件式(i >= 5 など)を正常に検証、複雑なパターンは UnsupportedNode エラーで legacy path へ fallback。
      • 箱化・モジュール化の原則に準拠ScopeManager は trait、ExprLowerer は再利用可能)。
    • 設計原則:
      • Box-First: ScopeManager は trait-based "box" で変数解決を抽象化。
      • Fail-Safe: 未対応 AST ノードは明示的エラーで fallback 可能(実行時エラーにしない)。
      • Incremental Adoption: Phase 231 は検証専用、Phase 232+ で実際の lowering 置き換え予定。
    • 使用箇所:
      • pattern2_with_break.rs の break 条件 lowering 前に pre-validation として実行。
      • 将来は Pattern1/Pattern3/Pattern4 にも拡大予定Phase 232
  • DigitPosConditionNormalizerPhase 224-E 実装完了)

    • ファイル: src/mir/join_ir/lowering/digitpos_condition_normalizer.rs
    • 責務:
      • digit_pos 条件を正規化(digit_pos < 0!is_digit_pos)。
      • Pattern2 の break 条件 lowering 前に呼び出され、promoted variable の条件を bool 形式に変換。
    • Phase 224 実装内容Core Implementation Complete ⚠️:
      • Two-tier promotion: Step1 で A-3 Trim 試行 → 失敗なら Step2 で A-4 DigitPos 試行 → 両方失敗で Fail-Fast。
      • DigitPosPromoter 統合: cascading indexOf パターンsubstring → indexOf → comparisonの昇格をサポート。
      • Unit test 完全成功: 6/6 PASSpromoter 自体は完璧動作)。
      • 残りの制約: body-local init の MethodCallsubstring 等)の lowering は Phase 193/224-B/C のスコープ外で、今後の Phase で対応。
    • 設計原則:
      • Thin coordinator: 専門 PromoterLoopBodyCarrierPromoter / DigitPosPromoterに昇格ロジックを委譲。
      • Pattern-agnostic: Pattern2 (break) / Pattern4 (continue) の統一入口として機能。
      • Fail-Fast with clear routing: A-3 → A-4 順で試行し、両方失敗なら明示的エラー。
    • 入出力:
      • 入力: ConditionPromotionRequestloop_param_name, cond_scope, break_cond/continue_cond, loop_body
      • 出力: ConditionPromotionResult::Promoted { carrier_info, promoted_var, carrier_name } または CannotPromote { reason, vars }
    • 使用元Phase 223.5 実装完了、Phase 224 拡張済み):
      • Pattern4: promotion-first昇格試行 → 成功なら CarrierInfo merge → 失敗なら Fail-Fast
      • Pattern2: promotion-first同上、break条件を分析対象とする
  • DigitPosPromoterPhase 224 実装完了 ⚠️ Core Complete

    • ファイル: src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs467 lines
    • 責務:
      • A-4 pattern: Cascading LoopBodyLocal with indexOfsubstring → indexOf → comparisonの昇格。
      • Pattern detection: local ch = s.substring(...); local digit_pos = digits.indexOf(ch); if digit_pos < 0 { break }
      • Comparison operators: <, >, <=, >=, != をサポートequality == は A-3 Trim 領域)。
      • Dependency validation: indexOf() が別の LoopBodyLocal に依存していることを検証cascading pattern
    • Phase 224 実装内容:
      • try_promote(): A-4 パターン検出 & bool carrier 昇格(digit_posis_digit_pos)。
      • Unit tests: 6/6 PASSbasic pattern, non-indexOf rejection, no dependency rejection, comparison operators, equality rejection
      • Integration: LoopBodyCondPromoter から A-3 Trim フォールバック後に呼ばれる。
    • 設計原則:
      • One Box, One Question: A-4 DigitPos パターン専用A-3 Trim は LoopBodyCarrierPromoter に残す)。
      • Separation of Concerns: Trimequality-basedと DigitPoscomparison-basedを分離。
      • Bool carrier consistency: A-3 Trim と同じく bool carrier に昇格(is_digit_pos)。
    • 入出力:
      • 入力: DigitPosPromotionRequestcond_scope, break_cond/continue_cond, loop_body
      • 出力: DigitPosPromotionResult::Promoted { carrier_info, promoted_var, carrier_name } または CannotPromote { reason, vars }
    • Phase 224-E 完了AST 条件正規化):
      • DigitPosConditionNormalizer Box: digit_pos < 0!is_digit_pos の AST 変換。
      • 実装箇所: src/mir/join_ir/lowering/digitpos_condition_normalizer.rs173 lines
      • 統合: Pattern2 で promotion 成功後に自動適用(pattern2_with_break.rs line 332-344
      • 単体テスト: 5/5 PASShappy path, wrong operator/variable/constant, non-binary-op
      • E2E テスト: phase2235_p2_digit_pos_min.hako で型エラー解消確認。
      • 回帰テスト: digitpos (11 tests), trim (32 tests) 全て PASS。
      • digit_pos 正規化ライン: DigitPosPromoter + CarrierInfo.promoted_loopbodylocals + DigitPosConditionNormalizer で digit_pos < 0 を bool キャリア is_digit_pos ベースの条件(!is_digit_pos)に直してから ConditionEnv / ConditionPatternBox / ExprLowerer 系の条件 lowering に渡す。
    • 参考:
      • 設計ドキュメント: docs/development/current/main/phase224-digitpos-condition-normalizer.md
      • 実装サマリ: docs/development/current/main/PHASE_224_SUMMARY.md
      • テストケース: apps/tests/phase2235_p2_digit_pos_min.hako
  • ContinueBranchNormalizer / LoopUpdateAnalyzer

    • ファイル:
      • src/mir/join_ir/lowering/continue_branch_normalizer.rs
      • src/mir/join_ir/lowering/loop_update_analyzer.rs
    • 責務:
      • else-continue を then-continue へ正規化し、Select ベースの continue を簡潔にする。
      • ループ本体で実際に更新されるキャリアだけを抽出Pattern 4 で不要キャリアを排除)。
  • LoopBodyLocalEnv / UpdateEnv / CarrierUpdateEmitterPhase 184, 191統合完了

    • ファイル:
      • src/mir/join_ir/lowering/loop_body_local_env.rs
      • src/mir/join_ir/lowering/update_env.rs
      • src/mir/join_ir/lowering/carrier_update_emitter.rs
      • src/mir/join_ir/lowering/loop_with_break_minimal.rsPhase 191統合
    • 責務:
      • LoopBodyLocalEnv: ループ本体で宣言された body-local 変数の名前→ValueId マッピングを管理(箱化設計)。
      • UpdateEnv: 条件変数ConditionEnvと body-local 変数LoopBodyLocalEnvを統合した変数解決層。
        • Priority order: 1. Condition variables高優先度 → 2. Body-local variablesフォールバック
      • CarrierUpdateEmitter: UpdateExpr を JoinIR 命令に変換する際、UpdateEnv を使用して body-local 変数をサポート。
        • emit_carrier_update_with_env(): UpdateEnv 版Phase 184 新規)
        • emit_carrier_update(): ConditionEnv 版(後方互換)
      • LoopBodyLocalInitLowerer: Phase 191 で Pattern2 に統合完了。
        • 対応済み init 式: 整数リテラル、変数参照、二項演算(local digit = i + 1
        • UpdateEnv の優先順位により ConditionEnv → LoopBodyLocalEnv の順で変数解決
    • 設計原則:
      • 箱理論: 各 Box が単一責任を持ち、境界明確。
      • 決定性: BTreeMap 使用で一貫した順序保証PHI 生成の決定性)。
    • Phase 192完了: Complex addendv = v*10 + f(x))は ComplexAddendNormalizer で temp に分解してから NumberAccumulation に載せる。
      • complex_addend_normalizer.rs: Pure AST transformer前処理箱
      • Pattern2 統合完了、emission ライン再利用(変更なし)
      • 制約: MethodCall を含む init 式は Phase 193 で対応予定

2.3 キャリア / Exit / Boundary ライン

  • Phase 200-B: FunctionScopeCaptureAnalyzer (完了)

    • ファイル: src/mir/loop_pattern_detection/function_scope_capture.rs
    • 責務: 関数スコープの「実質定数」を検出
    • 判定条件:
      1. 関数トップレベルで 1 回だけ定義
      2. ループ内で再代入なし
      3. 安全な初期式(文字列/整数リテラル)のみ
    • 結果: CapturedEnv に name, host_id, is_immutable を格納
    • ConditionEnvBuilder v2:
      • 責務: CapturedEnv から ParamRole::Condition として ConditionEnv に追加
      • 経路: analyze_captured_vars → build_with_captures → ConditionEnv.captured
      • 不変条件: Condition role は Header PHI / ExitLine の対象にならない
    • Pattern 2 統合: Phase 200-C で完了
      • MirBuilder.fn_body_ast フィールド追加
      • LoopPatternContext.fn_body 経由で Pattern 2 lowerer に渡す
      • analyze_captured_vars_v2() で構造的ループ検索(ポインタ比較 → AST 構造比較)
  • Phase 200-C: digits.indexOf E2E 連携 (完了)

    • 目的: 200-A/B インフラを実際に Pattern 2 経路に統合
    • 実装:
      • fn_body を MirBuilder → LoopPatternContext → Pattern 2 に渡す
      • analyze_captured_vars_v2() で構造的マッチングAST Debug 文字列比較)
      • digits / s 等の関数ローカル定数が CapturedEnv に正しく捕捉される
    • 検証結果:
      • capture 検出: PASS
      • E2E 実行: BLOCKEDテストケースが Pattern 5+ 必要)
    • テストケース制約:
      • phase200_digits_atoi_min.hako: body-local pos を条件 if pos < 0 で使用
      • → Pattern 5 (body-local promotion) が必要
  • Phase 200-D: digits capture "実戦 1 本" 検証 (完了)

    • 目的: capture 経路の E2E 検証body-local なしのシンプルケース)
    • 検証結果:
      • capture 検出: PASSbase, limit, n 等が正しく CapturedEnv に)
      • ConditionEnv 統合: PASScaptured vars が ConditionEnv.captured に追加)
      • 実行: ⚠️ 別の制約でブロックsubstring 未対応、キャリア更新型問題)
    • 成果:
      • capture 経路analyze_captured_vars_v2 → ConditionEnv → Pattern 2が正常動作
      • 関数スコープ定数が正しく検出・統合される
    • テストファイル: phase200d_capture_minimal.hako, phase200d_capture_in_condition.hako
  • CarrierInfo / LoopUpdateAnalyzer / CarrierUpdateEmitter

    • ファイル:
      • src/mir/join_ir/lowering/carrier_info.rs
      • src/mir/join_ir/lowering/loop_update_analyzer.rs
      • src/mir/join_ir/lowering/carrier_update_emitter.rs
    • 責務:
      • ループで更新される変数carrierを検出し、UpdateExpr を保持。
      • Pattern 4 では実際に更新されるキャリアだけを残す。
      • Phase 188 完了 : String 更新StringAppendChar/StringAppendLiteralを UpdateRhs ベースのホワイトリストで受理し、JoinIR BinOp を emit。
        • 許可: UpdateRhs::Const, UpdateRhs::Variable, UpdateRhs::StringLiteral
        • 拒否: UpdateRhs::Othermethod call / nested BinOp 等の複雑パターンのみ)
        • Pattern2/4 の can_lower() で選別、carrier_update_emitter.rs で JoinIR 生成
  • ExitMeta / JoinFragmentMeta

    • ファイル: carrier_info.rs
    • 責務:
      • JoinIR lowerer が出口の JoinIR ValueId を記録expr_result とキャリアを明確に分離)。
  • LoopHeader PHI Builder

    • ファイル:
      • src/mir/builder/control_flow/joinir/merge/loop_header_phi_info.rs
      • loop_header_phi_builder.rs
    • 責務:
      • ループ変数とキャリアの PHI をヘッダブロックに生成し、entry/latch の 2 入力で SSA を確立。
      • instruction_rewriter が latch 側を埋めた後に finalize して挿入する。
  • JoinInlineBoundary

    • ファイル: src/mir/join_ir/lowering/inline_boundary.rs
    • 主フィールド:
      • join_inputs / host_inputs:ループパラメータの橋渡し
      • condition_bindings条件専用変数の橋渡しJoinIR ValueId を明示)
      • exit_bindingsキャリア出口の橋渡しcarrier 名を明示)
      • expr_result / loop_var_nameexpr result / ヘッダ PHI 生成用のメタ情報
    • 責務:
      • 「host ↔ JoinIR」の境界情報の SSOT。各パターン lowerer がここに全て詰めてから merge する。
  • BoundaryInjector

    • ファイル: src/mir/builder/joinir_inline_boundary_injector.rs
    • 責務:
      • join_inputscondition_bindings を entry block に Copy で注入し、JoinIR ローカル ID と host ID を接続。
  • ExitLine (ExitMetaCollector / ExitLineReconnector / ExitLineOrchestrator)

    • ファイル:
      • src/mir/builder/control_flow/joinir/merge/exit_line/mod.rs
      • exit_line/meta_collector.rs
      • exit_line/reconnector.rs
    • 責務:
      • ExitMeta から exit_bindings を構築Collector
      • 変数再接続はヘッダ PHI の dst を使って builder.variable_map を更新Reconnector
      • expr 用の PHI には一切触れないcarrier 専用ライン)。
    • ConditionOnly キャリア: header PHI の entry は CarrierInitBoolConst(false) 等を起点にし、ExitLine では variable_map や ExprResult への書き戻しを行わずヘッダ PHI 経由に限定。
  • ExprResultResolverPhase 221-R 実装済み)

    • ファイル: src/mir/builder/control_flow/joinir/merge/expr_result_resolver.rs
    • 責務:
      • expr_result ValueId を exit_bindings/carrier_phis と照合し、適切な出口値を解決。
      • expr_result が carrier の場合: carrier PHI dst を返す(変数再接続と統一)。
      • expr_result が非 carrier の場合: remapped ValueId を返すexpr-only の値)。
    • 特徴:
      • Phase 33 モジュール化パターン準拠ExitMetaCollector/ExitLineReconnector と同様)。
      • 4つのシナリオを unit test でカバーcarrier/non-carrier/None/error
      • merge/mod.rs から 64 行を抽出、-37 行のネット削減。
  • JoinInlineBoundaryBuilderPhase 200-2 / Phase 201 完了)

    • ファイル: src/mir/join_ir/lowering/inline_boundary_builder.rs
    • 責務:
      • JoinInlineBoundary の構築を Builder パターンで統一化。
      • フィールド直書きの散乱を防ぎ、inputs/outputs/condition_bindings/exit_bindings/loop_var_name/expr_result の設定を fluent API で実施。
      • Phase 201 で Pattern1/2/3/4 全てに適用完了(境界情報組み立てを 1 箇所に集約)。
  • JoinIRVerifierPhase 200-3 追加)

    • ファイル: src/mir/builder/control_flow/joinir/merge/mod.rsdebug_assertions 専用関数)
    • 責務:
      • LoopHeader PHI / ExitLine 契約をデバッグビルドで検証する門番。
      • verify_loop_header_phis(): loop_var_name がある場合にヘッダ PHI が存在するか確認。
      • verify_exit_line(): exit_bindings が exit block に対応しているか確認。
      • verify_joinir_contracts(): merge_joinir_mir_blocks() の最後で全契約を一括チェック。
      • release ビルドでは完全に除去される(#[cfg(debug_assertions)])。
  • FunctionScopeCaptureAnalyzer / CapturedEnvPhase 200-A 追加)

    • ファイル: src/mir/loop_pattern_detection/function_scope_capture.rs
    • 責務:
      • 関数スコープで宣言され、ループ内で不変な変数("実質定数")を検出。
      • 例: local digits = "0123456789" in JsonParser._atoi()
      • CapturedVar: { name, host_id, is_immutable }
      • CapturedEnv: 検出された変数のコレクション
    • Phase 200-A: 型と空実装のみskeleton
    • Phase 200-B: 実際の検出ロジックを実装予定AST スキャン + 再代入チェック)。
  • ParamRole enumPhase 200-A 追加)

    • ファイル: src/mir/join_ir/lowering/inline_boundary_builder.rs
    • 責務:
      • JoinInlineBoundary のパラメータ役割を明示的に区別。
      • LoopParam / Condition / Carrier / ExprResult の 4 種類。
    • 不変条件:
      • Condition 役: PHI dst にしてはいけない(ループ内で更新されない)。
      • Condition 役: ExitLine の対象にも入れない(ループ外で使われない)。
      • 理由: 条件専用変数(例: digits)はループ内でのみ参照され、不変。
    • Phase 200-A: enum 定義のみ。
    • Phase 200-B: ルーティングロジック実装予定CapturedEnv 統合時)。
  • ConditionEnvBuilder::build_with_capturesPhase 200-A 追加)

    • ファイル: src/mir/builder/control_flow/joinir/patterns/condition_env_builder.rs
    • 責務:
      • 将来 CapturedEnv を受け取り、ConditionEnv に統合する v2 入口。
    • Phase 200-A: 既存実装に委譲する skeleton。
    • Phase 200-B: CapturedEnv の変数を condition_bindings に追加する実装予定。

2.4 expr result ライン(式としての戻り値)

  • exit_phi_builder

    • ファイル: src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs
    • 責務:
      • JoinIR fragment が expr_result を持つときに exit ブロックへ PHI を生成。
      • carrier_inputs も受け取り exit ブロックに PHI を作るが、再接続の SSOT は LoopHeader PHIExitLine はヘッダ PHI を使用)。
  • InstructionRewriter

    • ファイル: instruction_rewriter.rs
    • 責務:
      • continuation 関数k_exitをスキップし、Return → exit ブロック Jump に変換。
      • JoinFragmentMeta.expr_result と exit_bindings をヘッダ PHI 経由で収集し、exit_phi_inputs / carrier_inputs を復活させたSSAundef 修正済み)。
      • tail call を Branch/Jump に書き換えつつ、LoopHeaderPhiInfo に latch 入力を記録する。
    • Select 展開の不変条件Phase 196:
      • PHI の入力 ValueId は必ず remapper.remap_instruction() で remap 済みの MIR ValueId を使用。
      • InstructionRewriter では ValueId の二重 remap を行わないblock ID のみ remap
      • 詳細: phase196-select-bug-analysis.md

3. JoinIR → MIR 統合の全体フロー

  1. Pattern router が AST/LoopFeatures から Pattern14 を選択し、各 lowerer が (JoinModule, JoinFragmentMeta) を生成。
  2. JoinInlineBoundary が:
    • ループ入力join_inputs/host_inputs
    • 条件変数condition_bindings
    • キャリア出口exit_bindings を保持。
  3. merge_joinir_mir_blocks が(マルチ関数対応):
    • 全関数の BlockID を再割り当てblock_allocatorし、ValueId はパラメータを除外して収集。
    • Boundary の condition/exit Bindings の JoinIR ValueId も remap 対象に追加。
    • LoopHeader PHI を生成loop_header_phi_builderし、latch 側は instruction_rewriter が埋める。
    • instruction_rewriter で関数をマージしつつ Call→Jump に変換、k_exit 関数はスキップ。
    • BoundaryInjector で entry block に Copy を注入join_inputs + condition_bindings
    • Header PHI を finalize → exit_phi_builder で expr_result/carrier の exit PHI を構築。
    • ExitLineOrchestrator がヘッダ PHI dst を使って variable_map を更新。
    • host の現在ブロックから JoinIR entry へ jump を張り、exit ブロックに切り替える。

この全体フローの詳細は src/mir/builder/control_flow/joinir/merge/mod.rsphase-189-multi-function-mir-merge/README.md を参照。


4. JsonParser / Trim / P5 ループの現在地Phase 170181

4.1 JsonParser ループ空間と P1P5

Phase 181 で JsonParserBox 内の 11 ループを棚卸しした結果、 構造的には すべて JoinIR Pattern14 (+ P5) で扱える ことが分かったよ。

  • 既に JoinIR 経路で動作しているもの:
    • _skip_whitespaceP2 + P5 Trim
    • _trim leading/trailingP2 + P5 Trim
  • Phase 182 で P1/P2 パターン検証完了 :
    • Pattern1 (Simple): _match_literal 系ループで動作確認済みapps/tests/phase182_p1_match_literal.hako
    • Pattern2 (Break): 整数演算ループで動作確認済みapps/tests/phase182_p2_break_integer.hako
    • ブロッカー発見: 実際の JsonParser ループには 2 つの制約が必要:
      1. LoopBodyLocal 変数の扱い(ch, digit_pos, pos など)
        • 現状は Trim pattern 専用の carrier 昇格を試みてエラーになる
        • P1/P2 では純粋なローカル変数(昇格不要)として扱うべき
      2. 文字列連結フィルタPhase 178
        • num_str = num_str + ch のような string concat を保守的に reject
        • JsonParser では必須の操作なので段階的に有効化が必要
        • 設計原則:
          • string は「特別扱いのパターン」ではなく、あくまで MirType の 1 種類として扱う。
          • Pattern2/4 側で型名や変数名("result", "num_str" など)に依存した分岐は入れない。
          • LoopUpdateAnalyzer の UpdateKind / UpdateRhs で「安全な更新パターン」を列挙し、 そのうち string にも適用可能なものだけを ホワイトリストで許可する。
          • 実際の lowering は CarrierUpdateLowerer / 式 Lowerer 側で行い、JoinIR のループ形P1P4は増やさない。
      3. 数値の桁展開Phase 190 設計完了)
        • v = v * 10 + digit のような NumberAccumulation パターンを UpdateKind で whitelist 制御。
        • 型制約: Integer のみ許可String は StringAppendChar 使用)。
        • 検出: AST 構造解析名前依存禁止、Complex パターンは Fail-Fast で reject。
  • Phase 183 で LoopBodyLocal 役割分離完了 :
    • 設計: LoopBodyLocal を 2 カテゴリに分類:
      • Condition LoopBodyLocal: ループ条件header/break/continueで使用 → Trim 昇格対象
      • Body-only LoopBodyLocal: ループ本体のみで使用 → 昇格不要、pure local 扱い
    • 実装: TrimLoopLowerer に is_var_used_in_condition() ヘルパー追加
      • 条件で使われていない LoopBodyLocal は Trim 昇格スキップ
      • 5 つの unit test で変数検出ロジックを検証
    • テスト: apps/tests/phase183_body_only_loopbodylocal.hako で動作確認
      • [TrimLoopLowerer] No LoopBodyLocal detected トレース出力で body-only 判定成功
    • 次の課題→Phase 184 で対応): body-local 変数の MIR lowering 対応(local temp in loop body
      • Phase 183 では "Trim promotion しない" 判定まで完了
      • 実際の MIR 生成インフラは Phase 184 で実装済みPattern2/4 への統合は次フェーズ)

4.2 Body-local 変数の MIR lowering 基盤Phase 184

Phase 184 では、「条件には出てこない LoopBodyLocal 変数」を安全に JoinIR→MIR に落とすためのインフラ箱だけを追加したよ。

  • LoopBodyLocalEnv

    • 責務: ループ本体内で local 定義された変数の「JoinIR 側 ValueId のみ」を管理する。
    • 入力: ループ本体 AST / JoinIR ビルダー。
    • 出力: name -> join_value_id のマップ。
    • 特徴: host 側との対応は持たない。ConditionEnv とは完全に分離された「本体専用ローカル環境」。
  • UpdateEnv

    • 責務: UpdateExpr lowering 時の変数解決順序をカプセル化する。
    • 仕様: ConditionEnv(条件・キャリア)と LoopBodyLocalEnv(本体ローカル)を中で束ねて、 resolve(name) で「条件→ローカル」の順に ValueId を返す。
    • 利用箇所: CarrierUpdateEmitter / CarrierUpdateLowerer が、変数名ベースで UpdateExpr を JoinIR に落とす時に利用。
  • CarrierUpdateEmitter 拡張

    • 責務: LoopUpdateSummaryUpdateKindに応じて、int 系キャリア更新を JoinIR 命令に変換する。
    • 変更点: 直接 variable_map を読むのではなく、UpdateEnv 経由で名前解決するように変更。
    • 効果: 本体専用の LoopBodyLocal 変数(tempを、Pattern2/4 から安全に扱える土台が整った。

このフェーズではあくまで「ストレージ・名前解決・emit の箱」までで止めてあり、 Pattern2/4 への統合(実際に Body-local 更新を使うループを JoinIR 経路に載せるは次フェーズPhase 185 以降)の仕事として分離している。

  • 構造的に P1P4 で対応可能(代表例):
    • _parse_number / _atoiP2 Break- Phase 182 でブロッカー特定済み
    • _match_literalP1 Simple while- Phase 182 で動作確認済み
    • _parse_string / _parse_array / _parse_object P2 + P4 + P5 の組み合わせで表現可能なことを設計上確認済み)
  • 低優先度だが理論上は P1P4 からの拡張で吸収可能:
    • _unescape_string など、複雑な continue / 条件付き更新を含むループ

方針:

  • ループの「形」は P1P4 から増やさない。 複雑さLoopBodyLocal 条件、OR chain、continue 多用など)は BoolExprLowerer / ContinueBranchNormalizer / TrimLoopLowerer (P5) といった補助箱側で吸収する。
  • JsonParser 側の P5 適用Trim / _skip_whitespace / _parse_string 最小版)は実証済み。 残りのループは Phase 17x18x で、P1P4+P5 の組み合わせとして段階的に実装していく。

4.3 JsonParser 実戦カバレッジPhase 221 時点)

Phase 210221 で「数値ループif-sum」を実戦投入し、JoinIR インフラが 本番級に動作する ことを確認したよ:

  • 実戦確認済みループ9/13 loops ≒ 69%:

    • _skip_whitespace (P2 + P5 Trim, Phase 173)
    • _trim leading/trailing (P2 + P5 Trim, Phase 171/172)
    • _match_literal 最小版 (P1 Simple, Phase 210)
    • _atoi 最小版 (P2 Break, NumberAccumulation, Phase 190)
    • _parse_number 最小版 (P2 Break, NumberAccumulation, Phase 190)
    • if-sum 最小版 (P3 IfPHI, variable condition, Phase 212/220)
    • captured vars 最小版 (P2 Break, function-local const, Phase 200-D)
    • digits accumulate 最小版 (P2 Simple accumulation, Phase 200-D)
  • Phase 210221 の成果:

    • 8 本すべて JoinIR → MIR → Runtime 完全成功RC 正常)
    • Pattern1/2/3 自動ルーティング正常動作
    • NumberAccumulation (Mul+Add), if-sum (if条件付き更新), captured vars すべて正常
    • ConditionEnv/ConditionPatternBox/ExprResultResolver 統合完了
    • 制約発見ゼロ(基本パス) - Phase 190/200/220 の統合が完璧に機能
  • Phase 221 制約発見 (2025-12-09):

    • ⚠️ LoopBodyLocal in condition: break/continue 条件で loop-body-local 変数を使用Pattern 5+ 必要)
    • ⚠️ MethodCall whitelist: body-local init で substring 未対応Phase 193: indexOf/get/toString のみ)
    • ⚠️ if condition pattern: if-sum mode は var CmpOp literal のみ(i > 0 は OK、0 < ii > j は NGPhase 222 で解決済み
  • Phase 222 解決済み制約 (2025-12-10):

    • if condition pattern: ConditionPatternBox 正規化で左リテラル・変数同士の比較をサポート
      • 0 < i, len > ii > 0, i < len に正規化
      • i > j (var CmpOp var) - 直接サポート
      • テスト: phase222_if_cond_left_literal_min.hako → RC=2 達成
  • 残りループ (Phase 222+ で段階的対応予定):

    • _parse_array, _parse_object (MethodCall 複数)
    • _unescape_string (複雑なキャリア処理)
    • _atoi/_parse_number 本体LoopBodyLocal in condition 対応後)

結論:

  • JoinIR 数値ループ基盤NumberAccumulation + captured const + if-sum実戦投入可能な成熟度 に到達
  • Phase 221 で 3 種の既知制約を整理 - 次フェーズで Pattern 5+ 拡張 / MethodCall whitelist 拡張が候補

6. RoadmapJoinIR の今後のゴール)

ここから先の JoinIR の「目指す形」を、箱レベルでざっくり書いておくよ。フェーズ詳細は各 phase ドキュメントに分散させて、このセクションは常に最新の方向性だけを保つ。

6.1 直近Phase 176-177 まわり)

  • P5Trim/JsonParser 系)ループの複数キャリア対応 Phase 176 完了 (2025-12-08)
    • 完了内容:
      • Pattern2 lowerer を全キャリア対応に拡張(ヘッダ PHI / ループ更新 / ExitLine
      • CarrierUpdateLowerer ヘルパで UpdateExpr → JoinIR 変換を統一。
      • 2キャリアpos + resultE2E テスト完全成功。
    • 技術的成果:
      • CarrierInfo / ExitMeta / ExitLine / LoopHeaderPhiBuilder の multi-carrier 対応を Pattern2 lowerer で完全活用。
      • Trim pattern の「キャリア = ループ変数」という誤解を解消loop_var は特殊キャリア)。
    • 次のステップ (Phase 177):
      • JsonParser _parse_string 本体を P2+P5 で通すpos + result の 2 キャリアで実ループ動作確認)。

6.2 中期selfhost depth2 / JsonParser 本体)

  • JsonParserBox / Trim 系ループの本線化

    • 目標:
      • _trim / _skip_whitespace / _parse_string / _parse_array などの主要ループが、すべて JoinIR Pattern14 + P5 で通ること。
      • LoopConditionScopeBox + LoopBodyCarrierPromoter + TrimLoopHelper の上で安全に正規化できるループを広げていく。
    • 方針:
      • 「ループの形」は P1P4 から増やさず、複雑さは BoolExprLowerer / ContinueBranchNormalizer / P5 系の補助箱で吸収する。
      • LoopPatternSpace の P6/P7/P12 候補break+continue 同時 / 複数キャリア条件更新 / early returnは、実アプリで必要になった順に小さく足す。
  • selfhost depth2.hako JoinIR/MIR Frontend

    • 目標:
      • .hako → JsonParserBox → Program/MIR JSON → MirAnalyzerBox/JoinIrAnalyzerBox → VM/LLVM の深度 2 ループを、日常的に回せるようにする。
      • Rust 側の JoinIR は「JSON を受け取って実行・検証するランナー層」、.hako 側が「JoinIR/MIR を構築・解析する言語側 SSOT」という役割分担に近づける。
  • Phase 230ExprLowerer / ScopeManager 設計フェーズ)

    • 目標: 条件式 / init 式 / carrier 更新式の lowering を将来ひとつの ExprLowerer + ScopeManager に統合できるよう、既存の散在する lowering/API/Env を設計レベルで整理する(このフェーズではコード変更なし)。
  • Phase 233loop_update_summary テスト刷新フェーズ)

    • 目標: deprecated analyze_loop_updates() 依存のテストを廃止し、analyze_loop_updates_from_ast() に揃えたユニットで if-sum 判定ラインの期待値を固定する。
  • Phase 232Failing Tests の棚卸しフェーズ)

    • 目標: cargo test --release で残っている 7 件の FAIL を「どの箱 / どのパターン / どのレイヤ」の問題かで整理し、P0/P1/P2 に分類して次フェーズのターゲットを決めるcore バグを見つけるフェーズではなく、未対応領域の可視化フェーズ)。
  • Phase 234ArrayFilter / Pattern3 設計フェーズ)

    • 目標: ArrayExtBox.filter 系 3 テストを P3 if-PHI の正式対象に含めるか、当面は PoC 領域として Fail-Fast を仕様として維持するかを docs ベースで決める(コード変更なし)。
  • Phase 235ExprLowerer / ScopeManager パイロット実装 & テスト)

    • 目標: ExprLowerer/ScopeManager の Condition 文脈を小さな範囲でユニットテストしつつ、Pattern2 の break 条件で validation-only の経路として動かし、既存の legacy lowering と挙動が一致することを確認する(本番 lowering の置き換えは後続フェーズ)。
  • Phase 236-EXExprLowerer / ScopeManager 本番導入 - Pattern2 break 条件)

    • 目標: Pattern2 の break 条件 lowering を ExprLowerer/ScopeManager 経由の本番経路に切り替え、既存の condition_to_joinir ベース実装と RC / JoinIR 構造 / ログ挙動が一致することを確認する(影響範囲は Pattern2 break 条件のみに限定)。
  • Phase 237-EXExprLowerer 条件パターン棚卸しフェーズ)

    • 目標: JsonParser / selfhost のループ条件・break/continue 条件をカタログ化し、ExprLowerer/ScopeManager で優先的に扱うパターンと後回しにするパターンを SSOT 化する(コード変更なし)。
  • Phase 238-EXExprLowerer / ScopeManager Scope Boundaries

    • 目標: ExprLowerer / ScopeManager / ConditionEnv / LoopBodyLocalEnv / UpdateEnv の責務と参照範囲を文書化し、「誰がどこまで見てよいか」を SSOT として固定する(コード変更なし、ガイドライン整備)。***

5. selfhost / .hako JoinIR Frontend との関係

JoinIR は Rust 側だけでなく、将来的に .hako selfhost コンパイラ側でも生成・解析される予定だよ:

  • .hako 側の JsonParser/分析箱は、Program JSON / MIR JSON v1 を読んで JoinIR/MIR を解析する。
  • Rust 側 JoinIR ラインの設計変更(特に ValueId/ExitLine/Boundary 周り)は、 必ずこのファイルを更新してから .hako 側にも段階的に反映する方針。

「JoinIR の仕様」「箱の責務」「境界の契約」は、このファイルを SSOT として運用していく。


5. 関連ドキュメント

  • docs/development/current/main/10-Now.md
    • 全体の「いまどこ」を短くまとめたダッシュボード。
  • docs/private/roadmap2/phases/phase-180-joinir-unification-before-selfhost/README.md
    • JoinIR 統一フェーズ全体のロードマップと進捗。
  • 各 Phase 詳細:
    • 185188: Strict mode / LoopBuilder 削除 / Pattern14 基盤
    • 189193: Multi-function merge / Select bridge / ExitLine 箱化
    • 171172 / 3310/13: ConditionEnv, ConditionBinding, JoinFragmentMeta, ExitLineRefactor 等
  • docs/development/current/main/loop_pattern_space.md
    • JoinIR ループパターン空間の整理メモ。
      どの軸(継続条件 / break / continue / PHI / 条件変数スコープ / 更新パターン)でパターンを分けるか、
      そして P1P4 / Trim(P5) の位置づけと、今後追加候補のパターン一覧がまとまっている。

6. RoadmapJoinIR の今後のゴール)

ここから先の JoinIR の「目指す形」を、箱レベルでざっくり書いておくよ。フェーズ詳細は各 phase ドキュメントに分散させて、このセクションは常に最新の方向性だけを保つ。

6.1 直近Phase 176-177 まわり)

  • P5Trim/JsonParser 系)ループの複数キャリア対応 Phase 176 完了 (2025-12-08)
    • 完了内容:
      • Pattern2 lowerer を全キャリア対応に拡張(ヘッダ PHI / ループ更新 / ExitLine
      • CarrierUpdateLowerer ヘルパで UpdateExpr → JoinIR 変換を統一。
      • 2キャリアpos + resultE2E テスト完全成功。
    • 技術的成果:
      • CarrierInfo / ExitMeta / ExitLine / LoopHeaderPhiBuilder の multi-carrier 対応を Pattern2 lowerer で完全活用。
      • Trim pattern の「キャリア = ループ変数」という誤解を解消loop_var は特殊キャリア)。
    • 次のステップ (Phase 177):
      • JsonParser _parse_string 本体を P2+P5 で通すpos + result の 2 キャリアで実ループ動作確認)。

6.2 中期selfhost depth2 / JsonParser 本体)

  • JsonParserBox / Trim 系ループの本線化

    • 目標:
      • _trim / _skip_whitespace / _parse_string / _parse_array などの主要ループが、すべて JoinIR Pattern14 + P5 で通ること。
      • LoopConditionScopeBox + LoopBodyCarrierPromoter + TrimLoopHelper の上で安全に正規化できるループを広げていく。
    • 方針:
      • 「ループの形」は P1P4 から増やさず、複雑さは BoolExprLowerer / ContinueBranchNormalizer / P5 系の補助箱で吸収する。
      • LoopPatternSpace の P6/P7/P12 候補break+continue 同時 / 複数キャリア条件更新 / early returnは、実アプリで必要になった順に小さく足す。
  • selfhost depth2.hako JoinIR/MIR Frontend

    • 目標:
      • .hako → JsonParserBox → Program/MIR JSON → MirAnalyzerBox/JoinIrAnalyzerBox → VM/LLVM の深度 2 ループを、日常的に回せるようにする。
      • Rust 側の JoinIR は「JSON を受け取って実行・検証するランナー層」、.hako 側が「JoinIR/MIR を構築・解析する言語側 SSOT」という役割分担に近づける。
    • 前提:
      • 本ドキュメントjoinir-architecture-overview.mdを .hako 側の JoinIR 実装の参照設計として維持し、仕様変更は必ずここを更新してから .hako にも反映する。

6.3 当面やらないことNonGoals

  • ループパターンを闇雲に増やすこと
    • P1P4構造 P5bodylocal 条件を昇格する補助パス)を「骨格」とみなし、
      新しいパターンが必要になったときは LoopPatternSpace に追記してから、小さな箱で補う方針。
  • LoopBuilder の復活や、JoinIR 以外の別ラインによるループ lowering
    • LoopBuilder 系は Phase 186187 で完全に削除済み。
      ループに関する新しい要件はすべて JoinIR 側のパターン/箱の拡張で扱う。
  • JoinIR の中に言語固有のハードコード(特定 Box 名や変数名)を戻すこと
    • Trim/JsonParser 系は、構造パターンと補助箱Promoter/Helperで扱い、
      「sum」「ch」など名前ベースの判定は LoopUpdateSummary / TrimLoopHelper の内部に閉じ込める。

この Roadmap は、JoinIR 層の変更や selfhost 深度を進めるたびに更新していくよ。


7. JoinIR 第1章基盤完成サマリ2025-12-09 時点)

7.1 現在の対応状況

Pattern サポート:

Pattern 説明 状態
P1 Simple loop(cond) { body } 完成
P2 Break loop(cond) { if(...) break } 完成
P3 If-PHI loop { if(...) a else b; use(φ) } 完成
P4 Continue loop { if(...) continue } 完成
P5 Trim LoopBodyLocal → bool carrier 昇格 基本完成

UpdateKind サポート:

UpdateKind 状態
CounterLike i = i + 1
AccumulationLike sum = sum + x
StringAppendChar s = s + ch
StringAppendLiteral s = s + "lit"
NumberAccumulation v = v * 10 + digit (Phase 190)
Complex method call 含む Fail-Fast

アーキテクチャ SSOT ライン:

  • LoopHeaderPhiBuilder: ループ変数・キャリアの PHI を SSOT で生成
  • ExitLineReconnector: PHI dst → variable_map 接続
  • JoinInlineBoundaryBuilder: 全パターンで Builder パターン統一
  • JoinIRVerifier: デバッグビルドで契約検証
  • ExitLine Contract Verifier: PHI 配線検証Phase 190-impl-D

7.2 残タスクPhase 192+ で対応予定)

  1. body-local 変数の init + update lowering → Phase 191 完了

    • local digit = i + 1 のような body-local 変数の JoinIR/MIR 生成完了
    • 対応済み: 整数リテラル、変数参照、二項演算
    • テスト: phase191_body_local_atoi.hako → 期待値 123
  2. Complex addend 対応 → Phase 192 完了

    • v = v * 10 + digits.indexOf(ch) のような method call を含む NumberAccumulation対応
    • ComplexAddendNormalizer で temp = f(x) に分解してから NumberAccumulation に載せる実装完了
    • テスト: phase192_normalization_demo.hako → 123
    • 制約: MethodCall を含む init 式は Phase 193 で対応予定
  3. MethodCall を含む init 式の対応 → Phase 193 完了、Phase 225 でメタ駆動化

    • local digit = digits.indexOf(ch) のような MethodCall init の lowering 完了
    • LoopBodyLocalInitLowerer 拡張BoxCall emission
    • Phase 193: ハードコードされた whitelist: indexOf, get, toString 対応
    • Phase 225: CoreMethodId メタ駆動化によりハードコード完全削除
      • SUPPORTED_INIT_METHODS 定数削除(メソッド名 whitelist
      • Box 名の match 文削除(indexOf → StringBox 等のハードコード)
      • MethodCallLowerer への委譲により単一責任原則達成
      • allowed_in_init() メタデータで whitelist 管理
      • substring メソッド追加: Phase 225 で substring が body-local init で使用可能に
      • コード削減: -82 行158削除 - 76追加
    • 制約: body-local init のみ対応、condition 内の MethodCall は Phase 200+
  4. JsonParser 実戦投入P1/P2/P5 検証) → Phase 194 完了

    • 4/10 ループが JoinIR 経路で動作確認 (40% coverage)
    • Target loops: _skip_whitespace, _trim (x2), _match_literal
    • Deferred loops: _parse_number, _atoi (ConditionEnv constraint)
    • Deferred loops: _parse_string, _unescape_string (complex carriers)
    • Deferred loops: _parse_array, _parse_object (multiple MethodCalls)
    • 詳細: phase194-loop-inventory.md, phase194-jsonparser-deployment.md
  5. Pattern 3 拡張(複数キャリア対応) → Phase 195 + 196 完了

    • 目的: P3If-Else PHIで 2-3 個の Carrier を同時処理
    • Phase 195: Lowerer 側完了multi-carrier PHI 生成: sum + count
    • Phase 196: Select 二重 remap バグ修正
      • 根本原因: instruction_rewriter.rs で PHI inputs を二重 remap
      • 修正: block ID のみ remap、ValueId は既に remap 済み
    • E2E 結果: phase195_sum_count.hako → 93
    • 詳細: phase196-select-bug-analysis.md
  6. JoinIR 実戦適用(軽量ループ検証) → Phase 197 完了

    • 目的: Phase 196 までの安定基盤を実戦の小さいループで検証
    • 対象ループ5本:
      1. _match_literal (P1) - JsonParser 単純 while
      2. _skip_whitespace (P2) - JsonParser break パターン
      3. phase195_sum_count.hako (P3 multi-carrier)
      4. loop_if_phi.hako (P3 single-carrier)
      5. loop_min_while.hako (P1 minimal)
    • 結果:
      • routing 確認: 全ループ whitelisted
      • E2E 実行: 4/5 ループで期待値出力、1/5 routing 確認
      • 退行なし: Phase 190-196 テスト全 PASS
    • 詳細: phase197-lightweight-loops-deployment.md
  7. JsonParser/selfhost 実戦 JoinIR 適用状況 (2025-12-09 更新 → Phase 42 で棚卸し済み)

Function Pattern Status Note
_match_literal P1 JoinIR OK Phase 197 検証済みE2E PASS
_skip_whitespace P2 JoinIR OK Phase 197 routing 確認whitelisted
_trim (leading) P5 JoinIR OK Phase 173 実証済み
_trim (trailing) P5 JoinIR OK Phase 173 実証済み
phase195_sum_count P3 JoinIR OK Phase 196 検証済みmulti-carrier
loop_if_phi P3 JoinIR OK Phase 196 検証済みsingle-carrier
loop_min_while P1 JoinIR OK Phase 165 基本検証済み
_parse_number P2 JoinIR OK Phase 245B-IMPL: P2-Midnum_str LoopState キャリア)を Structured→Normalized(dev, direct) で固定
_atoi P2 JoinIR OK Phase 246-EX で NumberAccumulation パターンとして統合P2-Mid、Normalized: mini + real(dev, 符号対応) / canonical 準備中)
_parse_string P3 ⚠️ Deferred 複雑キャリアPhase 195+ 拡張後)
_unescape_string P3 ⚠️ Deferred 複雑キャリアPhase 195+ 拡張後)
_parse_array - ⚠️ Deferred 複数 MethodCallPhase 195+
_parse_object - ⚠️ Deferred 複数 MethodCallPhase 195+

Coverage: 9/13 ループ JoinIR 対応済み(約 69% Verification: 代表ループP1/P2 Core + Trim/P3については E2E テストで挙動確認済み。詳細なケースごとの状況は各 Phase ドキュメントPhase 197/245/246 など)を参照。

  1. JsonParser 残り複雑ループへの適用Phase 198+, 200+

    • Phase 200+: ConditionEnv 拡張 (function-scoped variables) → _parse_number, _atoi
    • Phase 198+: Pattern 3 拡張 (multi-flag carriers) → _parse_string, _unescape_string
    • Phase 198+: MethodCall 拡張 (multiple calls in body) → _parse_array, _parse_object
    • selfhost .hako コンパイラの全ループを JoinIR で処理 (Phase 210+)
  2. Pattern 3 If-Sum AST ベース実装 → Phase 213-217 完了

    • Phase 213: If-sum パターン AST ベース lowerer 実装
      • loop_with_if_phi_if_sum.rs: AST 抽出 + JoinIR 生成
      • Dual-mode 構成: if-sum mode / legacy mode
    • Phase 214: Dynamic join inputs 対応hardcoded 3-input 除去)
    • Phase 215: ExprResult exit contract 確立
    • Phase 216: Selfhost if-sum production test 検証
    • Phase 217: Multi-carrier if-sum 動作確認(追加実装ゼロ行)
    • 詳細: phase213-if-sum-implementation.md, phase217-if-sum-multi.md
  3. Phase 218: JsonParser If-Sum 適用調査🔍 調査完了

  • 目的: JsonParser-style if-sum パターン (sum = sum + digit) への Pattern 3 適用
  • 結果: パターン認識ギャップを発見
    • Phantom count carrier が誤検出され、is_if_sum_pattern() が false
    • AST ベース lowerer は実装済みだが起動されていない
    • 根本原因: carrier 検出ロジックの name heuristic が脆弱
  • 次フェーズ: Carrier 検出修正Phase 219
  • 詳細: phase218-jsonparser-if-sum-min.md

3. JoinIR → JoinIR 正規化レイヤ(構想 / Phase 26-H ライン)

3.1 Structured JoinIR と Normalized JoinIR概念レベル

JoinIR ラインは、今後は「同じ JoinIR をフェーズごとに正規化していく」二段構成で扱う想定だよ:

  • Structured JoinIR現行層

    • Pattern15 / CarrierInfo / ConditionEnv / UpdateEnv / Boundary / ExitLine までを終えた状態。
    • while/if/break/continue/return がまだ「構造」として残っていてよい層。
    • いまの JoinIR loweringPattern lowerer 群)が出力しているものはここ。
  • Normalized JoinIRJoinIR / CPS 風層)

    • 制御構造をすべて「関数+継続+ Env」の形に正規化した状態。
    • ループは loop_step(env, k_exit) と exit 継続のペア、if は if_branch(env, k_then, k_else) join 継続で表現。
    • while/break/continue/return は TailCallFn/TailCallKont + If だけに還元され、goto/生の branch は現れない。
    • Env は「ループキャリア + DigitPos/num_str などの Derived + captured 変数」を 1 つの struct としてまとめる。

パイプラインのイメージ:

AST
  ↓
JoinIR(Structured)   // Pattern15, CarrierInfo, ConditionEnv/UpdateEnv, Boundary/ExitLine
  ↓ JoinIR パス A: 正規化JoinIR → JoinIR
JoinIR(Normalized)   // 関数 + 継続 + Env のみTailCall-only
  ↓ JoinIR→MIR bridge
MIR

型レベルでは、次の二案のどちらかで扱う想定だよ(詳細は今後の Phase で選択):

  • 案① 型を分ける
    • JoinModuleRawStructuredJoinModuleCpsNormalizedを別型にする。
    • BridgeJoinIR→MIRJoinModuleCps だけを受け取る。
  • 案② 型は 1 つ+フェーズフラグ
    • JoinModule { phase: JoinIrPhase, ... } として、phase = Structured / Normalized をメタデータで持つ。
    • JoinIR パスはすべて fn run(&mut JoinModule)= joinir→joinirで構成し、 Verifier が「phase=Normalized なら while/if/break/continue/return 禁止」などの不変条件をチェックする。

どちらの案でも本質は同じで、「JoinIR の下に JoinIR= 正規形フェーズ)を 1 段挟む」という設計、というのがポイントだよ。

3.2 Normalized JoinIR の基本モデル(ラフスケッチ)

Normalized JoinIR では、制御構造を次の 3 要素だけで表現する想定だよ:

  • Env環境

    • そのループ/if に必要な情報を 1 つにまとめた struct。
    • フィールド種別の例:
      • CarrierLoopState キャリア: i, sum, result, num_state 等)
      • DerivedDigitPos 二重値, NumberAccumulation 等)
      • Captured(外側のローカルや関数パラメータ: s, len 等)
  • Fn通常の関数

    • 典型例: loop_step(env, k_exit) / loop_body(env, k_exit)
    • 末尾は常に TailCallFn または TailCallKont
  • Kont継続

    • 典型例: ループ exit 後の処理、if 後の join、関数 return など。
    • 末尾は別の Fn/Kont への tail-call。

制御の不変条件Normalized フェーズ):

  1. 制御フローは TailCallFn / TailCallKont / If(cond, then_k, else_k, env) のみで表現する。
  2. 各ループは「loop_step(env, k_exit) 関数 + k_exit(env) 継続」のペアとして現れるbreak は必ず k_exit へ TailCall
  3. continue は loop_step への TailCall として現れる(ヘッダ PHI 相当は Env の書き込み順で表現)。
  4. return は専用の return_kont(env) への TailCall で表現される。
  5. EnvLayoutVerifier検証箱が、常に正しい EnvLayout が使われていることをチェックする。

これにより、現在 JoinIR 層で苦労している「PHI 配線」「exit_bindings/jump_args 整合性」「評価順のねじれ」は、 Normalized JoinIR 側では「Env フィールドの更新順」と「どの継続を呼ぶか」に還元される想定だよ。

3.2.1 P1〜P4 と Normalized ループ形

Normalized JoinIR のゴールは、「P1〜P4 すべてのループを同じループ骨格(loop_step(env, k_exit) + 継続)で表現する」ことだよ:

  • P1 Simple:
    • loop_step(env, k_exit) だけを持つ最小形break/continue なし)。
    • 条件が false になったら k_exit(env) に TailCall、true なら body を 1 ステップ進めて再び loop_step を呼ぶ。
  • P2 Break:
    • P1 の形に「body 後の break 判定」と k_exit(env) への分岐が加わったもの。
    • break/continue/return 自体はすべて継続呼び出し(TailCallKont / TailCallFn)として表現され、型レベルでは P1 の上位互換。
  • P3 IfPHI:
    • ループ本体に if_branch(env, k_then, k_else) join 継続を挟んだ形として表現し、PHI は「Env のフィールド更新順+ join 継続」で表す。
  • P4 Continue:
    • continue は「更新後に loop_step(env', k_exit) へ TailCall」するだけの構造として扱い、break/return と同じく継続レベルで閉じる。

設計としては Normalized IR の型と不変条件は P1〜P4 で共通で、違いは Structured 層からどのように loop_step / 継続を合成するか(LoopCpsSynthesizer 側の分岐)だけに閉じ込める前提だよ。

  • Phase 26H / 2843 では、まず P1/P2特に JsonParser の P2-Core/P2-Midだけを Normalized に載せて設計を固定した。
  • P3/P4 の Normalized 対応は今後のフェーズNORMP3 / NORMP4 ライン)で、上の骨格に揃える形で段階的に進める。

この方針により、「先に P1/P2 だけ Normalized を実装しても、後から P3/P4 を同じ器に寄せられる」ことを文書上で保証しておく。

3.3 Normalized JoinIR 導入のメリットと初期コスト

Normalized JoinIR を 1 段挟むと、開発の手触りがどう変わるかをここでまとめておくよ。

3.3.1 楽になるポイント

  • デバッグ時の「問いの分離」

    • Structured JoinIR だけで _parse_number / _atoi を追うときは、常に同時に 4 つくらいの問いを抱えることになる:
      1. Pattern 判定は正しいかP1/P2/P3/P4/P5
      2. CarrierInfo / LoopState / LoopLocalZero / FromHost の設計は妥当か
      3. 評価順header → body-init → break → updates → tailは正しいか
      4. PHI / exit_bindings / jump_args / variable_map の整合は取れているか
    • Normalized JoinIR を挟むと、これが層ごとに分割される:
      • Structured: Pattern 判定 + CarrierInfo/Condition/Update の設計
      • Normalized: EnvLayout + step/kont 合成(制御骨格)
      • Bridge: TailCallEnv から MIR/VM の PHI/branch を作る
    • これにより、「これは Pattern/Carrier 問題」「これは Env継続の組み立て問題」「これは Bridge 問題」と、デバッグ時にレイヤ単位で切り分けやすくなる。
  • PHI まわりの思考負荷軽減

    • Structured JoinIR では、LoopHeader PHI / Exit PHI / Select 展開 PHI と、exit_bindings/jump_args/variable_map 再接続が絡み合う。
    • Normalized フェーズでは、キャリアや DigitPos/NumberAccumulation などはすべて Env フィールドになり、PHI は JoinIR には現れない:
      • 「どこから値が来たか?」は「どの Env フィールドがどの順番で更新されたか」を見るだけでよくなる。
      • PHI/exit_bindings は Bridge 側の責務として明示的に分離される。
  • while/if/break/continue/return の骨格が共通化される

    • Pattern15 ごとに似たロジックが散らばりがちな現在と違い、Normalized では:
      • ループ: loop_step(env, k_exit) loop_body(env, k_exit)
      • if: 条件判定 → then/else 継続 → join 継続
      • break: TailCallKont(k_exit, env)
      • continue: TailCallFn(loop_step, env', k_exit)
    • こうした「世界共通のテンプレ」を 1 箱にまとめておけるので、新しい Pattern や JsonParser/selfhost のループを追加するときも、構造箱Structured側だけで頑張ればよくなる。
  • 「1箱 = 1質問」を守りやすくなる

    • Structured JoinIR では、PatternLowerer が一時的に「構造評価順PHI 配線exit_bindings」を抱える瞬間がある。
    • Normalized を挟むと:
      • Structured: 「どの値を Env に載せるか」を決める箱
      • Normalized: 「Env と継続にどう分解するか」を決める箱
      • Bridge: 「TailCallEnv を MIR/VM にどう落とすか」を決める箱
    • という分担にできるので、「この箱は何の質問に答えるか」が清潔に保ちやすくなる。

3.3.2 初期コストと注意点

  • 新しい IR モデルを 1 セット覚える必要がある

    • EnvFnKontTailCall の世界観を JoinIR 層の前提として追加することになる。
    • 一度定着すれば Structured より思考負荷は下がるが、導入初期は「型・不変条件・ダンプの読み方」に慣れるまで少しコストが乗る。
  • 移行期間中は 2 経路の比較が発生する

    • AST → JoinIR(Structured) → MIR
    • AST → JoinIR(Structured) → JoinIR(Normalized) → MIR
    • の 2 経路をしばらく併走させ、dev で結果比較をするフェーズが必要になる。
    • Phase 26-H / 28-NORM-P2 のように「小さいサブセットから比較用にだけ回す」方針で進めることで、コストは制御可能にする。
  • 導入フェーズごとに Fail-Fast とテストが必須

    • Structured→Normalized は対応範囲外の Structured JoinModule については必ず Fail-Fastpanic/Errし、サイレントに正規化を試みない。
    • normalized_dev feature 配下で、Structured↔Normalized↔Structured の roundtrip と VM 実行結果比較を常に追加していく運用にする。

全体として、短期的には「新しいレイヤと比較テストを抱えるコスト」が増えるけれど、中長期では Pattern2〜4 や JsonParser 系の「評価順PHIDigitPos/num_str」が絡むループで、デバッグと設計の負荷をかなり下げることを狙った設計だよ。

3.4 Phase 26-H のフェーズ分割(案)

この正規化レイヤを一度に入れるのは重いので、Phase 26-H ラインとして小さく段階分けして進める方針だよ。 (番号は仮で、実際の Phase 番号は current の進行に合わせて調整する想定)

  1. Phase 26-H.A モデル定義と Doc 固定(コード変更最小)

    • JoinIR Architecture Overviewこのファイルに Structured / Normalized の二層構造を明文化(本節)。
    • JoinIrPhase などのメタ情報だけを追加し、既存 lowering / Bridge の挙動は変えない。
    • Normalized JoinIR の不変条件TailCall-only / EnvLayout 整合性)を Verifier の設計として書き出す。
  2. Phase 26-H.B 最小サブセットでの JoinIR→JoinIR パス導入

    • Pattern1単純 while, break/continue なし)だけを対象にした Normalized パスを実装。
    • パイプライン:
      • 既存: JoinIR(Structured) → MIR
      • 新規: JoinIR(Structured) → JoinIR(Normalized, small subset) → MIR
    • 新規パスは dev フラグの裏側で比較用にのみ使い、E2E で結果が一致することを確認(既定経路は従来のまま)。
  3. Phase 26-H.C Pattern24 / DigitPos / JsonParser への拡張

    • Pattern2/3/4 と DigitPos / NumberAccumulation / Trim を Normalized パスの対象に広げる。
    • JsonParser _parse_number / _atoi を優先対象にし、JoinIR(Normalized) 経由での実行を dev フラグ付きで有効化。
    • JoinIR(Structured) → JoinIR(Normalized) の各ステップに構造テストを追加し、「Env の更新」「継続の呼び出し順」が期待どおりかを固定。
  4. Phase 26-H.D Normalized JoinIR を canonical route に昇格

    • JoinIR→MIR Bridge が「Normalized フェーズの JoinIR だけ」を受け取るように変更。
    • Structured JoinIR → MIR の直接パスは「比較テスト専用」として残すか、段階的に削除。
    • selfhost / JsonParser / hako_check の代表ループはすべて Normalized JoinIR 経由で通ることを確認。

各サブフェーズでは「既存意味論を変えない」「Fail-Fast 原則を維持する」「新旧経路の比較テストを先に用意する」をガードとして運用するよ。 詳細な API/型設計は、Phase 26-H.A/B の中で EnvLayoutBox / LoopStepSynthesizer / JpVerifier 等の箱として段階的に固めていく想定。

3.5 Phase 27-CLEAN Pattern24 の軽量整理

  • Pattern2〜4/loop_with_break_minimal まわりで可視性・ログ・補助関数を整理し、joinir_dev フラグ配下のデバッグログに寄せる。意味論は変えずに「読みやすさ」「追いやすさ」を優先するクリーンアップフェーズだよ。

3.6 Phase 28-NORM-P2 Normalized JoinIR プロトタイプ拡張dev-only

  • Phase 26-H で用意した Normalized JoinIR の極小サブセットを、Pattern1 に続いて Pattern2最小 break ループ)まで拡張。
  • Structured → Normalized → Structured の往復と VM 実行比較を dev フィーチャ (normalized_dev + debug) でテスト済み。
  • 対象は joinir_min_loop 相当の「ループ変数1つbreakのみ」のミニケースに限定し、本番経路Structured→MIRは不変。
  • normalize_pattern2_minimal は対応外の Structured JoinModule では Fail-Fast するようにガードを追加し、対応範囲をテストで固定。

3.7 Phase 29-NORM-P2-APPLY Pattern2 実ループへの dev 適用

  • Phase 34 の fixture loop_frontend_break.program.jsoni/acc/n のシンプル break ループ)を Structured→Normalized→Structured の往復経路に載せ、VM 実行結果が Structured 直経路と一致することを dev テストで確認。
  • normalize_pattern2_minimal のガードを 3 パラメータloop var + acc + hostまで許容する形に緩めつつ、DigitPos/Trim などの heavy carrier は依然として非対応に固定。
  • すべて normalized_dev feature + debug_assertions 配下の実験経路に閉じ、本番 Structured→MIR パスの挙動は不変。

3.8 Phase 30-NORM-P2-DEV-RUN runner で Normalized を試走dev

  • JoinIR runner に dev 専用の NYASH_JOINIR_NORMALIZED_DEV_RUN=1 スイッチを追加し、Pattern1/2 のミニケースだけ Structured→Normalized→Structured を噛ませてから実行できるようにした(normalized_dev feature + debug ビルド限定)。
  • runner 経路でも Structured 直実行との stdout/結果が一致することをテストloop_min_while と Phase 34 break fixtureで確認。フラグ OFF 時の挙動は従来と同じ。

3.9 Phase 31-NORM-JP-MINI JsonParser ミニ P2 を Normalized dev 経由で試走

  • JsonParser 系のシンプルな P2 ループskip_whitespace 簡易版)を Structured→Normalized→Structured の dev ランナー経路に載せ、通常経路との実行結果一致を比較。
  • jsonparser_skip_ws_mini.program.jsondocs/private/roadmap2/phases/normalized_dev/fixtures 配下)由来の JoinModule を使い、NYASH_JOINIR_NORMALIZED_DEV_RUN=1 + normalized_dev + debug 限定で切替可能にした。
  • 本番経路Structured→MIRは引き続き不変で、Normalized は dev 比較専用のまま。

3.10 Phase 32-NORM-CANON-PREP Normalized 本番導入の下地づくり

  • JoinIR→MIR ブリッジに Structured/Normalized の入口を分けた bridge_joinir_to_mir_* を用意し、conversion pipeline / VM ランナーがこの 1 箇所で dev roundtrip を切替できるように整理。
  • Normalized dev スイッチを normalized_dev_enabled() に集約feature normalized_dev + NYASH_JOINIR_NORMALIZED_DEV_RUN=1。P1/P2 ミニ + JsonParser mini で env ON/OFF の比較テストを追加し、いつでも canonical route に昇格できる状態を固めた。

3.11 Phase 33-NORM-CANON-TEST P1/P2/JP mini をテスト必須ラインへ

  • bridge_joinir_to_mir / JoinIR runner は shape_guard で P1/P2 ミニ + JsonParser skip_ws mini を検知した場合、normalized_dev_enabled() が ON なら必ず Structured→Normalized→Structured の dev roundtrip を経由(正規化失敗は dev panic。未対応形状は静かに Structured 直通。
  • tests/normalized_joinir_min.rs を Phase 33 前提に拡張し、P1/P2/JP mini の runner/VM 比較テストを env ON で実行。Normalized が壊れればこのスイートが必ず赤になる構造にしたfeature OFF の CI は従来どおり無関係)。
  • 本番 CLI 挙動は Structured→MIR のまま維持しつつ、Normalized を canonical に昇格させる前段階として dev テストで SSOT 相当の役割を担わせている。

3.12 Phase 34-NORM-ATOI-DEV JsonParser _atoi ミニを dev 正規化経路へ

  • JsonParser _atoi の最小 P2 ループdigit_pos → digit_value + NumberAccumulationを normalized_dev で Structured→Normalized→Structured に往復させ、VM 実行結果を Structured 直経路と比較するテストを追加。
  • フィクスチャ jsonparser_atoi_mini.program.jsonshape_guard::JsonparserAtoiMini で検知し、dev roundtrip が必ず通るようにした(正規化失敗は dev panic。本番 CLI は引き続き Structured→MIR 既定のまま。

3.13 Phase 35-NORM-BRIDGE-MINI Normalized→MIR 直ブリッジP1/P2 ミニ + JP mini/atoi

  • normalized_dev 有効時に、P1/P2 ミニ・JsonParser skip_ws/atoi ミニを Structured→Normalized→MIRStructured に戻さない)で実行する dev 専用ブリッジを追加。
  • bridge_joinir_to_mir が shape_guard 対応形状では Normalized→MIR 直経路を使い、従来の Structured→MIR と VM 実行結果が一致することを比較テストで固定env OFF 時は従来経路のまま)。

3.14 Phase 36-NORM-BRIDGE-DIRECT Normalized→MIR を direct 実装に収束P1/P2 ミニ + JP mini/atoi

  • Normalized ブリッジを direct 実装と Structured 再構成の二段に分離し、shape_guard で direct 対象P1/P2 ミニ + JsonParser skip_ws/atoi ミニ)だけを Normalized→MIR 直接生成に切り替えた。
  • direct 経路は normalized_bridge::direct に閉じ込め、非対応形状は [joinir/normalized-bridge/fallback] ログ付きで Structured 再構成経路に落とす構造に整理。dev テストでは direct 経路の VM 出力が従来経路と一致することを固定。

3.15 Phase 37-NORM-JP-REAL JsonParser _skip_whitespace 本体を dev Normalized で比較

  • JsonParser 本体の _skip_whitespace ループを Program(JSON) フィクスチャ化し、shape_guard で real 版を検知して Structured→Normalized→MIR(direct) の dev 経路に通すように拡張。extract_value&&/|| を BinOp として受け付けるようにした。
  • Break パターンのパラメータ推定を柔軟化loop_var/acc/n が無いケースでも loop_var を優先し、acc が無ければ同一キャリアとして扱うし、skip_ws real の構造で panic しないようにした。
  • tests/normalized_joinir_min.rs に _skip_whitespace real フィクスチャの VM 比較テストを追加し、env ON 時は Structured→Normalized→MIR(direct) と Structured 直経路の stdout が一致することを固定env OFF は既存経路のまま)。
  • normalized_dev 用フィクスチャは docs/private/roadmap2/phases/normalized_dev/fixtures/ に配置し、Program(JSON) から AstToJoinIrLowerer で読み込む運用に統一した。

3.16 Phase 38-NORM-OBS Normalized dev ログ/FailFast の整備

  • Normalized/JoinIR dev 経路のログカテゴリを [joinir/normalized-bridge/*] / [joinir/normalized-dev/shape] に統一し、JOINIR_TEST_DEBUG フラグ下のみ詳細を出すよう静音化。Verifier/FailFast メッセージも shape/役割付きに整理してデバッグ観測性を強化。

3.17 Phase 40-NORM-CANON-TESTS テスト側で Normalized を“当たり前”に通す

  • normalized_dev_enabled() と env ガードを整理し、P1/P2 ミニ + JsonParser skip_ws/atoi ミニ/real の代表テストは「Normalized dev 経路が必ず通る」前提にする(壊れたら normalized_* スイートが赤になる)。
  • 既存の Structured 直経路は比較用に維持しつつ、tests/normalized_joinir_min.rs 経路では Structured→Normalized→MIR(direct) が第一観測点になるように整備(本番 CLI は Structured→MIR のまま)。

3.18 Phase 41-NORM-CANON-P2-CORE Pattern2 コアケースの canonical Normalized 化

  • Pattern2 のコアセットP2 ミニ + JsonParser skip_ws/atoi ミニ/realについて、JoinIR→MIR Bridge の既定を Normalized→MIR に寄せ、Structured→MIR は比較テスト用/フォールバック用の位置づけにするFailFast ポリシーは維持)。
  • shape_guard で「Normalized 対応と宣言した P2 コアループ」は常に Normalized 経路を通すようにし、Normalized 側の invariant 破損は dev では panic、本番では明示エラーで早期検出する設計に寄せる。

3.19 Phase 42-NORM-P2-INVENTORY P2 コア/ミドル/ヘビーの棚卸し

  • JsonParser / selfhost で 現役の P2 ループ を洗い出し、次の 3 クラスに整理した:
    • P2-Core(すでに Normalized canonical なもの)
      • test fixture 系: loop_min_while P2 ミニ, Phase 34 break fixture (i/acc/n)
      • JsonParser 系: _skip_whitespace mini/real, _atoi mini
      • これらは Phase 3641 で Structured→Normalized→MIR(direct) が canonical になっており、bridge_joinir_to_mir でも優先的に Normalized 経路が選ばれる。
    • P2-Mid(次に Normalized を当てる候補)
      • JsonParser: _parse_number, _atoi 本体, _atof_loop
      • いずれも Pattern2 Break で JoinIR(Structured) には載っておりPhase 245/246 系、Normalized への写像は今後の拡張対象として扱う。
    • P2-Heavy(複数 MethodCall / 複雑キャリアを持つもの)
      • JsonParser: _parse_string, _parse_array, _parse_object, _unescape_string
      • P2/P3/P4 が混在し、複雑なキャリアや MethodCall 多数のため、Phase 43 以降の後続フェーズで設計する。
  • P2-Core については Phase 41 で canonical Normalized 化が完了しており、Structured→MIR は比較テスト用 / フォールバック用の経路として扱う。
  • P2-Mid のうち、Phase 43 ではまず _parse_number を第 1 候補、_atoi 本体を第 2 候補として扱い、Normalized→MIR(direct) に必要な追加インフラEnvLayout 拡張 / JpInst パターン拡張)を段階的に入れていく前提を整理した。

3.20 Phase 43-NORM-CANON-P2-MID JsonParser 本命 P2_parse_number/_atoiへの適用 COMPLETE

完全サマリ: PHASE_43_245B_NORMALIZED_COMPLETION.md

  • JsonParser _parse_number / _atoi 本体の Pattern2 ループを、既存インフラDigitPos dual 値, LoopLocalZero, StepScheduleBox, ExprLowerer/MethodCall, Normalized ブリッジ)上で Structured→Normalized→MIR(direct) に載せる。
  • dev で Structured 直経路との VM 実行結果一致を固定した上で、段階的に「この関数だけ Normalized canonical」とみなすプロファイル/フラグを導入し、最終的に JsonParser P2 の canonical route を Normalized 側に寄せるための足場にする。
  • Phase 43-Adev 専用): _atoi 本体を Program(JSON) フィクスチャ jsonparser_atoi_real で Structured→Normalized→MIR(direct) に通し、Structured 直経路との VM 出力一致を比較テストで固定(符号あり/なしの簡易パスまで対応。canonical 化は後続フェーズで検討)。
  • Phase 43-Cdev 専用): _parse_number 本体を Program(JSON) フィクスチャ jsonparser_parse_number_real で Structured→Normalized→MIR(direct) に通し、num_str = num_str + ch の LoopState キャリアを含めた状態で Structured 直経路との VM 出力一致を比較テストで固定。

3.21 Phase 44-SHAPE-CAP shape_guard の能力ベース化 COMPLETE (2025-12-12)

完全サマリ: PHASE_43_245B_NORMALIZED_COMPLETION.md 設計詳細: phase44-shape-capabilities-design.md

  • ShapeCapabilityKind 導入完了: P2CoreSimple / P2CoreSkipWs / P2CoreAtoi / P2MidParseNumber の 4 種類
  • Capability-based API: capability_for_shape(), is_canonical_shape(), is_p2_core_capability(), is_supported_by_normalized()
  • Shape-level と Capability-level の二層 API: 正確なマッチング vs 広い能力ファミリ判定
  • 拡張性確保: 将来の carrier_roles, method_calls フィールド用の struct 設計
  • 既存挙動完全保持: 937/937 tests PASS

3.22 Phase 45-NORM-MODE JoinIR モードの一本化 COMPLETE (2025-12-12)

完全サマリ: PHASE_43_245B_NORMALIZED_COMPLETION.md 設計詳細: phase45-norm-mode-design.md

  • JoinIrMode enum 導入完了: StructuredOnly / NormalizedDev / NormalizedCanonical
  • current_joinir_mode() 統一: バラバラだったフラグ/feature を一箇所に集約
  • Canonical-first routing: P2-Core canonical shapes は mode 無視で常に Normalized→MIR(direct)
  • Mode-based 分岐統一: bridge/runner の pattern matching で一貫した挙動
  • 既存挙動完全保持: 937/937 tests PASS

3.23 Phase 46-NORM-CANON-P2-MID Normalized Canonical P2-Mid 昇格 COMPLETE (2025-12-12)

設計詳細: phase46-norm-canon-p2-mid.md

P2-Mid パターン_atoi real, _parse_number realを canonical Normalized→MIR(direct) ルートに昇格。

Canonical set 拡張Phase 46:

  • P2-Core: Pattern2Mini, skip_ws mini/real, atoi mini
  • P2-Mid: atoi real, parse_number real (NEW)

JsonParser _skip_whitespace / _atoi / _parse_number がすべて canonical Normalized に。Structured→MIR は P2 ラインにおいてレガシー/比較用途のみ。

スコープ外: P3/P4 Normalized 対応NORM-P3/NORM-P4 フェーズで実施)。

変更ファイル:

  • shape_guard.rs: is_canonical_shape() 拡張(+2 パターン)
  • bridge.rs: コメント更新Phase 41 → Phase 46
  • normalized_joinir_min.rs: canonical set 検証テスト追加

テスト: 937/937 PASS

3.24 Phase 47-NORM-P3 Normalized P3 (If-Sum) Support 🏗️ DESIGN (2025-12-12)

設計詳細: phase47-norm-p3-design.md

Pattern3 (if-sum) ループを Normalized JoinIR に対応させる。P2 と同じ ConditionEnv/CarrierInfo/ExitLine インフラを再利用。

Phase 220 基盤: P3 if-sum は Phase 220 で ConditionEnv 統合済み(既に P2 と同じ Structured JoinIR foundation を持つ)

Key difference: P3 は conditional carrier updatesif 内でのみキャリア更新vs P2 の unconditional updates before break

Phase 47-A: Minimal sum_count (dev-only Normalized)

  • EnvLayout: P2 と同じ構造carrier フィールド)
  • StepScheduleBox: IfCond, ThenUpdates, ElseUpdates ステップ追加
  • JpInst: 既存の If 命令を再利用
  • 🎯 Test: phase212_if_sum_min.hako → Normalized→MIR(direct) 検証

Phase 47-B: array_filter (dev-only, body-local + method calls)

Phase 47-C: Canonical promotion (P3 minimal → canonical)

スコープ外: P4 (continue) 対応NORM-P4 フェーズで実施、Complex P3 patterns後続フェーズ