Implements LoopBodyLocal condition promotion for Pattern4/continue patterns.
Previously, loops with LoopBodyLocal in conditions would Fail-Fast.
Now, safe Trim/skip_whitespace patterns (Category A-3) are promoted and lowering continues.
## Implementation
- **LoopBodyCondPromoter Box** (loop_body_cond_promoter.rs)
- extract_continue_condition(): Extract if-condition from continue branches
- try_promote_for_condition(): Thin wrapper delegating to LoopBodyCarrierPromoter
- ConditionPromotionRequest/Result API for Pattern2/4 integration
- **Pattern4 Integration** (pattern4_with_continue.rs)
- Analyze both header condition + continue condition
- On promotion success: merge carrier_info → continue lowering
- On promotion failure: Fail-Fast with clear error message
- **Unit Tests** (5 tests, all PASS)
- test_cond_promoter_skip_whitespace_pattern
- test_cond_promoter_break_condition
- test_cond_promoter_non_substring_pattern
- test_cond_promoter_no_scope_shape
- test_cond_promoter_no_body_locals
- **E2E Test** (phase223_p4_skip_whitespace_min.hako)
- Category A-3 pattern: local ch = s.substring(...); if ch == " " { continue }
- Verifies promotion succeeds and Pattern4 lowering proceeds
## Documentation
- joinir-architecture-overview.md: Updated LoopBodyCondPromoter section (Phase 223-3 完了)
- CURRENT_TASK.md: Added Phase 223-3 completion summary
- PHASE_223_SUMMARY.md: Phase overview and status tracking
- phase223-loopbodylocal-condition-*.md: Design docs and inventory
## Achievement
- **Before**: LoopBodyLocal in condition → Fail-Fast
- **Now**: Trim/skip_whitespace patterns promoted → Pattern4 lowering continues
- **Remaining**: Phase 172+ JoinIR Trim lowering for complete RC correctness
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
54 KiB
JoinIR Architecture Overview (2025‑12‑08)
このドキュメントは、JoinIR ライン全体(Loop/If lowering, ExitLine, Boundary, 条件式 lowering)の 「箱」と「契約」を横串でまとめた設計図だよ。selfhost / JsonParser / hako_check など、 どの呼び出し元から見てもここを見れば「JoinIR 層の責務と流れ」が分かるようにしておく。
変更があったら、Phase ドキュメントではなく このファイルを随時更新する 方針。
1. 不変条件(Invariants)
JoinIR ラインで守るべきルールを先に書いておくよ:
-
JoinIR 内部は JoinIR ValueId だけ
- JoinIR lowering が発行する
JoinInstのValueIdは、すべてalloc_value()で割り当てるローカル ID。 - Rust/MIR 側の ValueId(
builder.variable_mapに入っている ID)は、JoinIR には直接持ち込まない。
- JoinIR lowering が発行する
-
host ↔ join の橋渡しは JoinInlineBoundary 系だけ
- host から JoinIR への入力(ループ変数 / 条件専用変数)は
JoinInlineBoundary.join_inputs + host_inputsJoinInlineBoundary.condition_bindings(ConditionBinding) だけで接続する。
- 出力(キャリアの出口)は
JoinInlineBoundary.exit_bindingsに一本化する。
- host から JoinIR への入力(ループ変数 / 条件専用変数)は
-
LoopHeader PHI を SSA の単一源泉にする
- ループ変数とキャリアは LoopHeaderPhiBuilder/LoopHeaderPhiInfo でヘッダ PHI を作り、これを「現在値」の SSOT にする。
- exit 用の PHI も組むが、変数再接続や expr_result 収集はヘッダ PHI を経由して行う(SSA‑undef 防止)。
-
式としての戻り値とキャリア更新を分離する
- 「ループが式として値を返す」ケース(例:
let r = loop_min_while(...))の出口は exit_phi_builder が扱う。 - 「ループが状態更新だけする」ケース(例:
trimのstart/end)の出口は ExitLine(ExitMeta / ExitBinding / ExitLineReconnector) だけが扱う。
- 「ループが式として値を返す」ケース(例:
-
ループ制御 vs 条件式の分離
- ループの「形」(Pattern1–4, LoopFeatures)は control-flow 専用の箱が担当。
- 条件式(
i < len && (ch == " " || ch == "\t")等)は BoolExprLowerer / condition_to_joinir が担当し、 ループパターンは boolean ValueId だけを受け取る。
-
Fail‑Fast
- JoinIR が対応していないループパターン / if パターンは、必ず
[joinir/freeze]等で明示的にエラーにする。 - LoopBuilder 等へのサイレントフォールバックは禁止。
- JoinIR が対応していないループパターン / if パターンは、必ず
-
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 として明示する予定。
- JoinIR 側で扱うパラメータは概念的に 3 種類に分かれる:
-
LoopHeader PHI dst は予約領域(上書き禁止)
- LoopHeaderPhiBuilder が生成したヘッダ PHI の dst ValueId は「現在のループ値」の SSOT として扱い、
BoundaryInjector や InstructionRewriter が
Copyなどで二度書きすることを禁止する。 - merge ラインでは「ヘッダ PHI dst に対する新しい定義が出てきたら debug モードで panic する」ことで契約違反を早期検出する。
- LoopHeaderPhiBuilder が生成したヘッダ PHI の dst ValueId は「現在のループ値」の SSOT として扱い、
BoundaryInjector や InstructionRewriter が
-
ParamRole の不変条件(Phase 200-A 追加)
- Condition 役のパラメータは「PHI dst にしてはいけない」
- 理由: 条件専用変数はループ内で更新されない(例:
digitsin_atoi()) - LoopHeaderPhiBuilder は Condition 役の変数に対して header PHI を生成しない
- 理由: 条件専用変数はループ内で更新されない(例:
- Condition 役のパラメータは「ExitLine の対象にも入れない」
- 理由: 条件専用変数はループ外で使われない(ループ内でのみ参照)
- ExitLineReconnector は Condition 役の変数を exit_bindings から除外
- ParamRole の分類:
LoopParam: ループ制御変数(例:iinloop(i < len))→ header PHI + exit_bindingsCondition: 条件専用変数(例:digitsindigits.indexOf(ch))→ condition_bindings のみCarrier: 状態更新変数(例:sum,count)→ header PHI + exit_bindingsExprResult: ループ戻り値 → exit_phi_builder で処理
- Condition 役のパラメータは「PHI dst にしてはいけない」
-
JoinIR Core は常時 ON
- LoopBuilder は物理削除済み。JoinIR を OFF にする経路やフォールバックは存在しない。
NYASH_JOINIR_COREは deprecated(0 を指定しても警告して無視)。JoinIR の OFF トグルは提供しない。
-
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 設計原則
-
領域の固定境界
- 明確な境界(100, 1000)で領域を分離
- デバッグが容易(ValueId を見ればどの領域か一目瞭然)
- アロケータ間の調整不要
-
reserve_phi() vs alloc_phi()
- PHI dst ID は MirBuilder(host 側)から来るため、JoinValueSpace は割り当てない
reserve_phi()はマーカーのみ(「この ID を上書きするな」という契約)
-
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 機能:
-
衝突検出(debug-only)
- 全ての割り当てられた ValueId を追跡(
allocated_ids: HashSet<u32>) - 重複割り当てを即座に検出し panic(Fail-Fast 原則)
check_collision()で実装
- 全ての割り当てられた ValueId を追跡(
-
領域検証(debug-only)
verify_region(id, expected_region)で ValueId が期待される領域にいるか検証- 違反時は明確なエラーメッセージと修正ヒントを提供
- 例: "ValueId(500) is in Param region, expected Local. Hint: Use alloc_local() for JoinIR values"
-
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)内
- 全ての
- 場所:
-
明示的な領域定数
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.rssrc/mir/builder/control_flow/joinir/patterns/router.rssrc/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 のみ)。
- LoopScopeShape / AST / LoopFeatures を入力として JoinIR の
- ファイル例:
-
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 で保持。
- 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 でパターン固有データ管理)
- 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.rssrc/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.rssrc/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 として扱う)。
- ファイル:
-
ConditionPatternBox(Phase 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 へフォールバック。
- ファイル:
-
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 の汎用性実証)。
- Phase 173 で
- ファイル:
-
LoopBodyCondPromoter(Phase 223-3 実装完了)
- ファイル:
src/mir/loop_pattern_detection/loop_body_cond_promoter.rs - 責務:
- ループ条件(header/break/continue)に出てくる LoopBodyLocal を carrier に昇格する統一 API。
- P0(Category A-3: _skip_whitespace): 単一変数の Trim パターンのみ対応。
- Pattern 2/Pattern 4 両対応の薄いコーディネーター箱(detection は LoopBodyCarrierPromoter に委譲)。
- Phase 223-3 実装内容:
extract_continue_condition(): body 内の if 文から continue 条件を抽出。try_promote_for_condition(): LoopBodyCarrierPromoter を使った昇格処理。- Pattern4 への統合完了: LoopBodyLocal 条件の昇格成功時に lowering を続行(以前は Fail-Fast)。
- 設計原則:
- Thin coordinator: LoopBodyCarrierPromoter に promotion ロジックを委譲し、metadata のみ返す。
- Pattern-agnostic: Pattern2 (break) / Pattern4 (continue) の統一入口として機能。
- Fail-Fast: 複雑パターン(Category B: 多段 if / method chain)は引き続き reject。
- 入出力:
- 入力:
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-3 実装時):
- Pattern4: promotion-first(昇格試行 → 成功なら CarrierInfo merge → 失敗なら Fail-Fast)
- Pattern2: 既存 TrimLoopLowerer 経由で間接利用(将来的に統一可能)
- ファイル:
-
ContinueBranchNormalizer / LoopUpdateAnalyzer
- ファイル:
src/mir/join_ir/lowering/continue_branch_normalizer.rssrc/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.rssrc/mir/join_ir/lowering/update_env.rssrc/mir/join_ir/lowering/carrier_update_emitter.rssrc/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 の順で変数解決
- 対応済み init 式: 整数リテラル、変数参照、二項演算(
- 設計原則:
- 箱理論: 各 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 回だけ定義
- ループ内で再代入なし
- 安全な初期式(文字列/整数リテラル)のみ
- 結果: 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) が必要
- phase200_digits_atoi_min.hako: body-local
-
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.rssrc/mir/join_ir/lowering/loop_update_analyzer.rssrc/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.rsloop_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.rsexit_line/meta_collector.rsexit_line/reconnector.rs
- 責務:
- ExitMeta から exit_bindings を構築(Collector)。
- 変数再接続はヘッダ PHI の dst を使って
builder.variable_mapを更新(Reconnector)。 - expr 用の PHI には一切触れない(carrier 専用ライン)。
- ファイル:
-
ExprResultResolver(Phase 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 行のネット削減。
- ファイル:
-
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"inJsonParser._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 を使用)。
- JoinIR fragment が
- ファイル:
-
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
- PHI の入力 ValueId は必ず
- ファイル:
3. JoinIR → MIR 統合の全体フロー
- Pattern router が AST/LoopFeatures から Pattern1–4 を選択し、各 lowerer が
(JoinModule, JoinFragmentMeta)を生成。 JoinInlineBoundaryが:- ループ入力(join_inputs/host_inputs)
- 条件変数(condition_bindings)
- キャリア出口(exit_bindings) を保持。
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)_trimleading/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 つの制約が必要:
- LoopBodyLocal 変数の扱い(
ch,digit_pos,posなど)- 現状は Trim pattern 専用の carrier 昇格を試みてエラーになる
- P1/P2 では純粋なローカル変数(昇格不要)として扱うべき
- 文字列連結フィルタ(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)は増やさない。
- 数値の桁展開(Phase 190 設計完了)
v = v * 10 + digitのような NumberAccumulation パターンを UpdateKind で whitelist 制御。- 型制約: Integer のみ許可(String は StringAppendChar 使用)。
- 検出: AST 構造解析(名前依存禁止)、Complex パターンは Fail-Fast で reject。
- LoopBodyLocal 変数の扱い(
- Pattern1 (Simple):
- 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 tempin loop body)- Phase 183 では "Trim promotion しない" 判定まで完了
- 実際の MIR 生成インフラは Phase 184 で実装済み(Pattern2/4 への統合は次フェーズ)
- 設計: LoopBodyLocal を 2 カテゴリに分類:
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 221 時点)
Phase 210–221 で「数値ループ+if-sum」を実戦投入し、JoinIR インフラが 本番級に動作する ことを確認したよ:
-
実戦確認済みループ(9/13 loops ≒ 69%):
- ✅
_skip_whitespace(P2 + P5 Trim, Phase 173) - ✅
_trimleading/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 210–221 の成果:
- 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 は→ Phase 222 で解決済み✅var CmpOp literalのみ(i > 0は OK、0 < iやi > jは NG)
-
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 達成
- ✅ if condition pattern: ConditionPatternBox 正規化で左リテラル・変数同士の比較をサポート
-
残りループ (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 詳細:
- 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) の位置づけと、今後追加候補のパターン一覧がまとまっている。
- JoinIR ループパターン空間の整理メモ。
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 キャリアで実ループ動作確認)。
- JsonParser
- 完了内容:
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 に追記してから、小さな箱で補う方針。
- P1–P4(構造)+ P5(body‑local 条件を昇格する補助パス)を「骨格」とみなし、
- LoopBuilder の復活や、JoinIR 以外の別ラインによるループ lowering
- LoopBuilder 系は Phase 186–187 で完全に削除済み。
ループに関する新しい要件はすべて JoinIR 側のパターン/箱の拡張で扱う。
- LoopBuilder 系は Phase 186–187 で完全に削除済み。
- JoinIR の中に言語固有のハードコード(特定 Box 名や変数名)を戻すこと
- Trim/JsonParser 系は、構造(パターン)と補助箱(Promoter/Helper)で扱い、
「sum」「ch」など名前ベースの判定は LoopUpdateSummary / TrimLoopHelper の内部に閉じ込める。
- Trim/JsonParser 系は、構造(パターン)と補助箱(Promoter/Helper)で扱い、
この 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+ で対応予定)
-
✅ body-local 変数の init + update lowering → Phase 191 完了
local digit = i + 1のような body-local 変数の JoinIR/MIR 生成完了- 対応済み: 整数リテラル、変数参照、二項演算
- テスト:
phase191_body_local_atoi.hako→ 期待値 123 ✅
-
✅ 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 で対応予定
-
✅ 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+
-
✅ 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
-
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
-
✅ JoinIR 実戦適用(軽量ループ検証) → Phase 197 完了 ✅
- 目的: Phase 196 までの安定基盤を実戦の小さいループで検証
- 対象ループ(5本):
_match_literal(P1) - JsonParser 単純 while ✅_skip_whitespace(P2) - JsonParser break パターン ✅phase195_sum_count.hako(P3 multi-carrier) ✅loop_if_phi.hako(P3 single-carrier) ✅loop_min_while.hako(P1 minimal) ✅
- 結果:
- routing 確認: 全ループ whitelisted ✅
- E2E 実行: 4/5 ループで期待値出力、1/5 routing 確認 ✅
- 退行なし: Phase 190-196 テスト全 PASS ✅
- 詳細: phase197-lightweight-loops-deployment.md
-
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 確認済み
-
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+)
-
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
- Phase 213: If-sum パターン AST ベース lowerer 実装
-
Phase 218: JsonParser If-Sum 適用調査 → 🔍 調査完了
- 目的: JsonParser-style if-sum パターン (
sum = sum + digit) への Pattern 3 適用 - 結果: パターン認識ギャップを発見
- Phantom
countcarrier が誤検出され、is_if_sum_pattern()が false - AST ベース lowerer は実装済みだが起動されていない
- 根本原因: carrier 検出ロジックの name heuristic が脆弱
- Phantom
- 次フェーズ: Carrier 検出修正(Phase 219)
- 詳細: phase218-jsonparser-if-sum-min.md