- Add expr_result handling in merge_joinir_mir_blocks - When expr_result matches a carrier, return carrier PHI dst - Enables expr-position loops to properly return accumulator values Note: Phase 219 regression (loop_if_phi.hako) to be fixed in next commit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
821 lines
49 KiB
Markdown
821 lines
49 KiB
Markdown
# JoinIR Architecture Overview (2025‑12‑08)
|
||
|
||
このドキュメントは、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 を経由して行う(SSA‑undef 防止)。
|
||
|
||
4. **式としての戻り値とキャリア更新を分離する**
|
||
- 「ループが式として値を返す」ケース(例: `let r = loop_min_while(...)`)の出口は **exit_phi_builder** が扱う。
|
||
- 「ループが状態更新だけする」ケース(例: `trim` の `start/end`)の出口は **ExitLine(ExitMeta / ExitBinding / ExitLineReconnector)** だけが扱う。
|
||
|
||
5. **ループ制御 vs 条件式の分離**
|
||
- ループの「形」(Pattern1–4, LoopFeatures)は control-flow 専用の箱が担当。
|
||
- 条件式(`i < len && (ch == " " || ch == "\t")` 等)は **BoolExprLowerer / condition_to_joinir** が担当し、
|
||
ループパターンは boolean ValueId だけを受け取る。
|
||
|
||
6. **Fail‑Fast**
|
||
- 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` は deprecated(0 を指定しても警告して無視)。JoinIR の OFF トグルは提供しない。
|
||
|
||
---
|
||
|
||
## 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 は MirBuilder(host 側)から来るため、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>`)
|
||
- 重複割り当てを即座に検出し panic(Fail-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/if‑else PHI などの特徴を抽出(ast_feature_extractor)。
|
||
- `classify(&LoopFeatures)` で Pattern1–4 に分類し、テーブル駆動の `LOOP_PATTERNS` でルーティング。
|
||
- ルータ順序は P4(continue) → P3(if‑phi) → P1(simple) → P2(break) で固定(優先度フィールドはデバッグ用)。
|
||
|
||
- **Pattern Lowerers (Pattern1–4)**
|
||
- ファイル例:
|
||
- `pattern1_minimal.rs`(Simple while)
|
||
- `pattern2_with_break.rs`(break 付き / Trim 昇格パスを含む)
|
||
- `pattern3_with_if_phi.rs`(if‑phi キャリア)
|
||
- `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 を確実に設定し、SSA‑undef を防ぐ。
|
||
|
||
- **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 variants(Option<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 に渡す。
|
||
|
||
- **LoopConditionScopeBox(Phase 170-D 実装済み)**
|
||
- ファイル: `src/mir/loop_pattern_detection/loop_condition_scope.rs`
|
||
- 責務:
|
||
- 条件式の各変数を LoopParam / OuterLocal / LoopBodyLocal に分類。
|
||
- 関数パラメータ誤分類バグは `condition_var_analyzer.rs` の修正で解消済み(OuterLocal として扱う)。
|
||
|
||
- **LoopBodyCarrierPromoter(Phase 171-C-2 実装済み)**
|
||
- ファイル: `src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs`
|
||
- 責務:
|
||
- LoopBodyLocal を Trim パターンとして bool キャリアへ昇格(substring + equality 連鎖を検出)。
|
||
- 昇格成功 → CarrierInfo に統合し Pattern 2/4 へ橋渡し。昇格失敗は Fail‑Fast。
|
||
- Pattern 2 は安全な Trim なら実際に前処理(substring 生成 + 空白比較の初期化)を emit してから JoinIR lowering。
|
||
- Pattern 4 は Trim 昇格が起きた場合はガード付きでエラーにし、未実装を明示(Fail‑Fast)。
|
||
- 汎用性:
|
||
- Phase 173 で `_skip_whitespace`(JsonParser)が Trim パターンで動作確認済み。
|
||
- Phase 174 で `_parse_string` 最小化版(終端クォート検出)でも動作確認済み。
|
||
- → 空白文字以外の文字比較ループにも対応可能(TrimLoopHelper の汎用性実証)。
|
||
|
||
- **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 / CarrierUpdateEmitter(Phase 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 検出: ✅ PASS(base, limit, n 等が正しく CapturedEnv に)
|
||
- ConditionEnv 統合: ✅ PASS(captured 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 専用ライン)。
|
||
|
||
- **JoinInlineBoundaryBuilder(Phase 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 箇所に集約)。
|
||
|
||
- **JoinIRVerifier(Phase 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 / CapturedEnv(Phase 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 enum(Phase 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_captures(Phase 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 PHI(ExitLine はヘッダ PHI を使用)。
|
||
|
||
- **InstructionRewriter**
|
||
- ファイル: `instruction_rewriter.rs`
|
||
- 責務:
|
||
- continuation 関数(k_exit)をスキップし、Return → exit ブロック Jump に変換。
|
||
- `JoinFragmentMeta.expr_result` と exit_bindings をヘッダ PHI 経由で収集し、`exit_phi_inputs` / `carrier_inputs` を復活させた(SSA‑undef 修正済み)。
|
||
- 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 から Pattern1–4 を選択し、各 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 170–181)
|
||
|
||
### 4.1 JsonParser ループ空間と P1–P5
|
||
|
||
Phase 181 で JsonParserBox 内の 11 ループを棚卸しした結果、
|
||
構造的には **すべて JoinIR Pattern1–4 (+ P5) で扱える** ことが分かったよ。
|
||
|
||
- 既に JoinIR 経路で動作しているもの:
|
||
- `_skip_whitespace`(P2 + P5 Trim)
|
||
- `_trim` leading/trailing(P2 + 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 のループ形(P1–P4)は増やさない。
|
||
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 以降)の仕事として分離している。
|
||
- 構造的に P1–P4 で対応可能(代表例):
|
||
- `_parse_number` / `_atoi`(P2 Break)- Phase 182 でブロッカー特定済み
|
||
- `_match_literal`(P1 Simple while)- Phase 182 で動作確認済み ✅
|
||
- `_parse_string` / `_parse_array` / `_parse_object`
|
||
(P2 + P4 + P5 の組み合わせで表現可能なことを設計上確認済み)
|
||
- 低優先度だが理論上は P1–P4 からの拡張で吸収可能:
|
||
- `_unescape_string` など、複雑な continue / 条件付き更新を含むループ
|
||
|
||
方針:
|
||
|
||
- **ループの「形」は P1–P4 から増やさない**。
|
||
複雑さ(LoopBodyLocal 条件、OR chain、continue 多用など)は BoolExprLowerer /
|
||
ContinueBranchNormalizer / TrimLoopLowerer (P5) といった補助箱側で吸収する。
|
||
- JsonParser 側の P5 適用(Trim / `_skip_whitespace` / `_parse_string` 最小版)は実証済み。
|
||
残りのループは Phase 17x–18x で、P1–P4+P5 の組み合わせとして段階的に実装していく。
|
||
|
||
### 4.3 JsonParser 実戦カバレッジ(Phase 210 時点)
|
||
|
||
Phase 210 で「軽量ループ 3 本」を実戦投入し、JoinIR インフラが **本番級に動作する** ことを確認したよ:
|
||
|
||
- **実戦確認済みループ**(7/13 loops ≒ 54%):
|
||
- ✅ `_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 210)
|
||
- ✅ `_parse_number` 最小版 (P2 Break, Multi-carrier, Phase 210)
|
||
|
||
- **Phase 210 の成果**:
|
||
- 3 本すべて JoinIR → MIR → Runtime 完全成功(RC 正常)
|
||
- Pattern1 & Pattern2 自動ルーティング正常動作
|
||
- NumberAccumulation (Mul+Add 2命令), Multi-carrier, PHI Contract, ValueId Regions すべて正常
|
||
- **制約発見ゼロ** - Phase 190/201/204/205 の統合が完璧に機能
|
||
|
||
- **Phase 211/212 の発見** (2025-12-09):
|
||
- Phase 211: if-sum パターン(ループ内 if 条件付き更新)の設計完了
|
||
- Phase 212: ⚠️ **AST→MIR 層の制約発見** - ループ内 if/else が MIR に変換されない問題を検出
|
||
- JoinIR Pattern3 (IfPHI) は動作可能だが、その前段階(AST→MIR)で if が消失
|
||
- Phase 212.5 で AST→MIR ループ内 if 修正が必要と判明
|
||
|
||
- **残りループ** (Phase 211+ で段階的対応予定):
|
||
- `_parse_array`, `_parse_object` (MethodCall 複数)
|
||
- `_unescape_string` (複雑なキャリア処理)
|
||
- その他 6 ループ(Phase 195/200+ 系設計で順次対応)
|
||
|
||
**結論**: JoinIR インフラ(P1-P5/JoinValueSpace/PHI契約)は **実戦投入可能な成熟度** に到達 ✨
|
||
**Phase 212 制約**: AST→MIR 層のループ内 if 変換修正が次の課題
|
||
|
||
---
|
||
|
||
## 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 詳細:
|
||
- 185–188: Strict mode / LoopBuilder 削除 / Pattern1–4 基盤
|
||
- 189–193: Multi-function merge / Select bridge / ExitLine 箱化
|
||
- 171–172 / 33‑10/13: ConditionEnv, ConditionBinding, JoinFragmentMeta, ExitLineRefactor 等
|
||
- `docs/development/current/main/loop_pattern_space.md`
|
||
- JoinIR ループパターン空間の整理メモ。
|
||
どの軸(継続条件 / break / continue / PHI / 条件変数スコープ / 更新パターン)でパターンを分けるか、
|
||
そして P1–P4 / Trim(P5) の位置づけと、今後追加候補のパターン一覧がまとまっている。
|
||
|
||
---
|
||
|
||
## 6. Roadmap(JoinIR の今後のゴール)
|
||
|
||
ここから先の JoinIR の「目指す形」を、箱レベルでざっくり書いておくよ。フェーズ詳細は各 phase ドキュメントに分散させて、このセクションは常に最新の方向性だけを保つ。
|
||
|
||
### 6.1 直近(Phase 176-177 まわり)
|
||
|
||
- **P5(Trim/JsonParser 系)ループの複数キャリア対応** ✅ Phase 176 完了 (2025-12-08)
|
||
- 完了内容:
|
||
- Pattern2 lowerer を全キャリア対応に拡張(ヘッダ PHI / ループ更新 / ExitLine)。
|
||
- CarrierUpdateLowerer ヘルパで UpdateExpr → JoinIR 変換を統一。
|
||
- 2キャリア(pos + result)E2E テスト完全成功。
|
||
- 技術的成果:
|
||
- 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 depth‑2 / JsonParser 本体)
|
||
|
||
- **JsonParserBox / Trim 系ループの本線化**
|
||
- 目標:
|
||
- `_trim` / `_skip_whitespace` / `_parse_string` / `_parse_array` などの主要ループが、すべて JoinIR Pattern1–4 + P5 で通ること。
|
||
- LoopConditionScopeBox + LoopBodyCarrierPromoter + TrimLoopHelper の上で安全に正規化できるループを広げていく。
|
||
- 方針:
|
||
- 「ループの形」は P1–P4 から増やさず、複雑さは BoolExprLowerer / ContinueBranchNormalizer / P5 系の補助箱で吸収する。
|
||
- LoopPatternSpace の P6/P7/P12 候補(break+continue 同時 / 複数キャリア条件更新 / early return)は、実アプリで必要になった順に小さく足す。
|
||
|
||
- **selfhost depth‑2(.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 当面やらないこと(Non‑Goals)
|
||
|
||
- ループパターンを闇雲に増やすこと
|
||
- P1–P4(構造)+ P5(body‑local 条件を昇格する補助パス)を「骨格」とみなし、
|
||
新しいパターンが必要になったときは LoopPatternSpace に追記してから、小さな箱で補う方針。
|
||
- LoopBuilder の復活や、JoinIR 以外の別ラインによるループ lowering
|
||
- LoopBuilder 系は Phase 186–187 で完全に削除済み。
|
||
ループに関する新しい要件はすべて 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 完了 ✅
|
||
- 目的: P3(If-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 | 複数 MethodCall(Phase 195+)|
|
||
| `_parse_object` | - | ⚠️ Deferred | 複数 MethodCall(Phase 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
|