Files
hakorune/docs/development/current/main/joinir-architecture-overview.md
nyash-codex 4e00edcea5 feat(joinir): Phase 224-D - ConditionAlias for promoted variable resolution
- Add ConditionAlias type to CarrierInfo (old_name → carrier_name)
- Record aliases in DigitPosPromoter and TrimPatternInfo
- Resolve aliases in Pattern2 ConditionEnv building
- digit_pos now correctly resolves to is_digit_pos carrier

Fixes "Variable 'digit_pos' not bound in ConditionEnv" error.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 18:45:04 +09:00

940 lines
58 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 実装完了)**
- ファイル: `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 P0: 引数なしメソッドのみ対応(`s.length()`, `arr.length()` 等)。
- 設計原則:
- **メソッド名ハードコード禁止**: CoreMethodId メタデータのみ参照。
- **Fail-Fast**: ホワイトリストにないメソッドは即座にエラー。
- **Box-First**: 単一責任("このMethodCallをJoinIRにできるか")だけを担当。
- 使用箇所:
- `condition_lowerer.rs` の `lower_value_expression()` から呼び出し。
- Pattern 2/3/4 のループ条件式で `s.length()` 等をサポート可能。
- 次ステップ: Phase 224-C で引数付きメソッド(`substring(i, i+1)`, `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 条件を分析し昇格を試みる。
- A-4digit_posテスト追加: cascading LoopBodyLocal パターンで Fail-Fast 動作を確認。
- error_messages.rs に Pattern2 用エラー関数追加: `format_error_pattern2_promotion_failed()` など。
- 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 自体は完璧動作)。
- **Phase 224-D**: ConditionAlias/CarrierInfo との連携により、昇格済み LoopBodyLocal 名(`digit_pos` 等)を ConditionEnv から見えるようにブリッジ。
- **残りの制約**: 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-continuation で対応予定):
- **Promotion は成功**: LoopBodyCondPromoter → DigitPosPromoter → Promoted ✅
- **Lowerer integration gap**: lower_loop_with_break_minimal が昇格済み変数を認識せず、break condition AST に元の変数名が残っているため独立チェックでエラー。
- **Solution**: Option Bpromoted variable trackingで昇格済み変数リストを lowerer に渡す1-2h 実装予定)。
- 参考:
- 設計ドキュメント: `docs/development/current/main/phase224-digitpos-promoter-design.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 専用ライン)。
- **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 拡張が候補
---
## 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 完了
- `local digit = digits.indexOf(ch)` のような MethodCall init の lowering 完了
- LoopBodyLocalInitLowerer 拡張BoxCall emission
- メソッド whitelist: indexOf, get, toString 対応
- 制約: 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 更新)
| 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 | ⚠️ Deferred | ConditionEnv 制約Phase 200+|
| `_atoi` | P2 | ⚠️ Deferred | ConditionEnv 制約Phase 200+|
| `_parse_string` | P3 | ⚠️ Deferred | 複雑キャリアPhase 195+ 拡張後)|
| `_unescape_string` | P3 | ⚠️ Deferred | 複雑キャリアPhase 195+ 拡張後)|
| `_parse_array` | - | ⚠️ Deferred | 複数 MethodCallPhase 195+|
| `_parse_object` | - | ⚠️ Deferred | 複数 MethodCallPhase 195+|
**Coverage**: 7/13 ループ JoinIR 対応済み54%
**Verification**: 4/7 ループ E2E PASS、3/7 structural/routing 確認済み
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