Files
hakorune/docs/development/current/main/joinir-architecture-overview.md
nyash-codex 297258f963 docs(joinir): Phase 43/245B Normalized JoinIR completion summary
Complete documentation consolidation for Phases 26-45 Normalized JoinIR
infrastructure. All components are implemented and tested (937/937 PASS).

Key additions:
- PHASE_43_245B_NORMALIZED_COMPLETION.md: Master completion summary
  - Architecture components (Mode, Capability, Carriers, DigitPos, etc.)
  - Supported loop patterns (P1, P2 variants including JsonParser)
  - Complete file reference guide
  - Test coverage documentation
  - Future expansion roadmap

Updates:
- CURRENT_TASK.md: Moved Phase 43/245B to completed section
- joinir-architecture-overview.md: Phases 43-45 marked complete
- Historical docs: Added completion banners with links
  - phase223-loopbodylocal-condition-design.md
  - phase223-loopbodylocal-condition-inventory.md
  - PHASE_223_SUMMARY.md
  - phase245b-num_str-carrier-design.md
  - phase245c-function-param-capture-summary.md

Documentation hierarchy established:
- Master summary → individual design docs
- Historical docs → completion summary
- Cross-references complete across all docs

Status: Phase 43/245B Normalized infrastructure COMPLETE 
2025-12-12 04:19:06 +09:00

1318 lines
91 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 が発行する `JoinInst``ValueId` は、すべて `alloc_value()` で割り当てるローカル ID。
- Rust/MIR 側の ValueId`builder.variable_map` に入っている IDは、JoinIR には直接持ち込まない。
2. **host ↔ join の橋渡しは JoinInlineBoundary 系だけ**
- host から JoinIR への入力(ループ変数 / 条件専用変数)は
- `JoinInlineBoundary.join_inputs + host_inputs`
- `JoinInlineBoundary.condition_bindings`ConditionBinding
だけで接続する。
- 出力(キャリアの出口)は `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** が扱う。
- 「ループが状態更新だけする」ケース(例: `trim``start/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. **明示的な領域定数**
```rust
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.rs` と `phase205-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.rs`Simple while
- `pattern2_with_break.rs`break 付き / Trim 昇格パスを含む)
- `pattern3_with_if_phi.rs`ifphi キャリア)
- `pattern4_with_continue.rs`continue を 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<T> で保持。
- **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<T> でパターン固有データ管理)
- 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 < i` → `i > 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.rs` の `lower_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_whitespace`JsonParserが 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 の MethodCall`substring` 等)の 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 順で試行し、両方失敗なら明示的エラー。
- 入出力:
- 入力: `ConditionPromotionRequest`loop_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.rs`467 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_pos` → `is_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`)。
- 入出力:
- 入力: `DigitPosPromotionRequest`cond_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.rs`173 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.rs`Phase 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 addend`v = 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::Other`method 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_name`expr result / ヘッダ PHI 生成用のメタ情報
- 責務:
- 「host ↔ JoinIR」の境界情報の SSOT。各パターン lowerer がここに全て詰めてから merge する。
- **BoundaryInjector**
- ファイル: `src/mir/builder/joinir_inline_boundary_injector.rs`
- 責務:
- `join_inputs` と `condition_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.rs`debug_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](./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.rs` と
`phase-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_whitespace`P2 + 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 拡張**
- 責務: `LoopUpdateSummary`UpdateKindに応じて、int 系キャリア更新を JoinIR 命令に変換する。
- 変更点: 直接 `variable_map` を読むのではなく、`UpdateEnv` 経由で名前解決するように変更。
- 効果: 本体専用の LoopBodyLocal 変数(`temp` 等を、Pattern2/4 から安全に扱える土台が整った。
このフェーズではあくまで「ストレージ・名前解決・emit の箱」までで止めてあり、
Pattern2/4 への統合(実際に Body-local 更新を使うループを JoinIR 経路に載せるは次フェーズPhase 185 以降)の仕事として分離している。
- 構造的に P1P4 で対応可能(代表例):
- `_parse_number` / `_atoi`P2 Break- Phase 182 でブロッカー特定済み
- `_match_literal`P1 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 < i` や `i > j` は NG~~ → **Phase 222 で解決済み✅**
- **Phase 222 解決済み制約** (2025-12-10):
- ✅ **if condition pattern**: ConditionPatternBox 正規化で左リテラル・変数同士の比較をサポート
- `0 < i`, `len > i` → `i > 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) ✅
- 結果:
- [x] routing 確認: 全ループ whitelisted ✅
- [x] E2E 実行: 4/5 ループで期待値出力、1/5 routing 確認 ✅
- [x] 退行なし: 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 など)を参照。
8. **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+)
9. **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
10. **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 としてまとめる。
パイプラインのイメージ:
```text
AST
JoinIR(Structured) // Pattern15, CarrierInfo, ConditionEnv/UpdateEnv, Boundary/ExitLine
↓ JoinIR パス A: 正規化JoinIR → JoinIR
JoinIR(Normalized) // 関数 + 継続 + Env のみTailCall-only
↓ JoinIR→MIR bridge
MIR
```
型レベルでは、次の二案のどちらかで扱う想定だよ(詳細は今後の Phase で選択):
- **案① 型を分ける**
- `JoinModuleRaw`Structured、`JoinModuleCps`Normalizedを別型にする。
- BridgeJoinIR→MIRは `JoinModuleCps` だけを受け取る。
- **案② 型は 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。
- フィールド種別の例:
- `Carrier`LoopState キャリア: i, sum, result, num_state 等)
- `Derived`DigitPos 二重値, 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.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.json``i/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.json`docs/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.json` を `shape_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](./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](./PHASE_43_245B_NORMALIZED_COMPLETION.md)
**設計詳細**: [phase44-shape-capabilities-design.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](./PHASE_43_245B_NORMALIZED_COMPLETION.md)
**設計詳細**: [phase45-norm-mode-design.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