diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index a3a573c6..ba05b376 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -40,6 +40,104 @@ ## 1. 最近完了した重要タスク +### 1-00v. Phase 29 L-5.3 — JoinIR generic_case_a との統合 (Phase 1)(**完了** 2025-11-26) + +**目的** +- generic_case_a lowering に progress carrier チェックを追加 +- 無限ループの可能性があるループ(progress carrier なし)を事前にフォールバック +- 保守的アプローチで minimal 4 ケースの安定性確保 + +**実装内容** +1. `src/mir/join_ir/lowering/loop_to_join.rs` に `has_safe_progress()` helper 追加(Phase 1 実装) + - `scope.progress_carrier.is_some()` で保守的チェック + - Phase 2 で MirQuery による Add 命令チェック予定 +2. `is_supported_case_a_loop_view()` の末尾に progress guard 追加(4 番目のチェック) + - progress carrier なしループは reject してフォールバック + - デバッグログで reject 理由を出力 + +**テスト結果** +- ✅ **25 passed; 0 failed** — JoinIR 関連テスト全通過 +- ✅ skip_ws / trim / append_defs / Stage‑1 UsingResolver の全ケース PASS +- ✅ 既存テストへの影響なし(progress carrier が設定されているループは全て通過) + +**技術メモ** +- Phase 1 は `progress_carrier.is_some()` だけチェック(保守的) +- LoopScopeShape で progress_carrier を carriers の先頭(典型的には 'i')として設定済み +- ignored テスト(joinir_vm_bridge_skip_ws Route A)の失敗は MIR 自体の PHI バグで、本変更とは無関係(git stash で確認済み) + +**次のステップ** +- Phase 2: MirQuery で header→latch 間に Add 命令があるかの詳細チェック追加 +- verify.rs の `verify_progress_for_skip_ws()` ロジックを MIR レベルに適用 + +**関連ファイル** +- `src/mir/join_ir/lowering/loop_to_join.rs` (has_safe_progress + progress guard) +- `docs/private/roadmap2/phases/phase-29-longterm-joinir-full/TASKS.md` (L-5.3 完了記録) + +--- + +### 1-00u. Phase 32 L-4.3a — llvmlite ハーネスでの JoinIR 実験(**完了** 2025-11-26) + +**目的** +- LLVM 経路で JoinIR が PHI 問題を解決できることを実行レベルで実証 +- Route A (MIR→LLVM) vs Route B (MIR→JoinIR→MIR'→LLVM) の比較検証 + +**実装内容** +1. `src/config/env.rs` に `joinir_llvm_experiment_enabled()` 追加 + - `NYASH_JOINIR_LLVM_EXPERIMENT=1` で有効化 +2. `src/runner/modes/llvm.rs` に JoinIR フック追加 + - `inject_method_ids` の直後で JoinIR 変換を試行 + - `Main.skip/1` を JoinIR 経由で変換し、元のモジュールとマージ + - 戦略: 元の `Main.skip/1`(PHI問題あり)を削除 → `join_func_0` を `Main.skip/1` にリネーム + +**テスト結果** +- **Route A** (MIR→LLVM): PHI error ❌ + ``` + PHINode should have one entry for each predecessor of its parent basic block! + %phi_11 = phi i64 [ %.1, %bb2 ], [ %.1, %bb5 ] + ``` +- **Route B** (MIR→JoinIR→MIR'→LLVM): **成功 ✅** + ``` + [joinir/llvm] ✅ Merged module (6 functions) + ✅ LLVM (harness) execution completed (exit=0) + ``` + +**使用方法** +```bash +env NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 \ + NYASH_DISABLE_PLUGINS=1 NYASH_LLVM_USE_HARNESS=1 \ + NYASH_JOINIR_EXPERIMENT=1 NYASH_JOINIR_LLVM_EXPERIMENT=1 \ + ./target/release/hakorune --backend llvm apps/tests/minimal_ssa_skip_ws.hako +``` + +**成果**: JoinIR が LLVM 経路でも PHI 問題を設計的に解決できることを実証 + +--- + +### 1-00t. Phase 32 L-3.2 — Medium 優先度 PHI 箱の削除検討(**完了** 2025-11-26) + +**目的** +- Medium 優先度の PHI 箱(if_phi.rs / if_body_local_merge.rs / phi_invariants.rs / conservative.rs)の削除可能性を調査 +- 各箱の call site を A/B/C に分類(A: テスト専用、B: JoinIR 候補、C: 本線必須) + +**調査結果** +1. **if_phi.rs**: 9箇所で使用(すべてカテゴリ C) + - lifecycle.rs, if_form.rs, phi.rs, conservative.rs, loop_builder.rs で MIR Builder の If/Else PHI 生成に必須 +2. **if_body_local_merge.rs**: phi_builder_box.rs で使用(PhiBuilderBox に依存) +3. **phi_invariants.rs**: phi_builder_box.rs で検証用として使用(PhiBuilderBox に依存) +4. **conservative.rs**: phi_merge.rs, phi.rs で MIR Builder の PHI マージに必須(カテゴリ C) + +**結論: Phase 32 時点では削除不可** +- すべての箱が MIR Builder の If/Else PHI 生成で必須(カテゴリ C) +- if_body_local_merge.rs と phi_invariants.rs は PhiBuilderBox(High 優先度)に依存 +- JoinIR は **Loop 専門**で、If PHI はカバーしていない +- 削除は **Phase 33+ で If JoinIR 対応後**に再検討 + +**ドキュメント更新** +- `PHI_BOX_INVENTORY.md` に L-3.2 詳細分析を追記 +- 各箱の call site 分類テーブルと削除不可理由を明文化 + +--- + ### 1-00v. Phase 31 — LoopToJoinLowerer 統一箱(**完了** 2025-11-26) **目的** @@ -202,6 +300,20 @@ LoopScopeShape → CaseAContext::from_scope() → lower_case_a_X_core() → Join - `joinir_runner_standalone_skip_ws` / `joinir_runner_standalone_trim` / `joinir_vm_bridge_trim_*` / JSON v0 スナップショット系テストは全て PASS。 - Stage‑1 / Stage‑B については、`NYASH_JOINIR_LOWER_GENERIC` 有効時も lowering 自体は通ることを確認(意味論は簡略版のままなので VM 実行はまだ有効化しない)。 +5. **ExitGroup ベースの Case-A 判定 refinement(L-1.4 完了 2025-11-26)** + - `src/mir/control_form.rs` に `ExitGroup { target, edges, has_break }` と `ExitAnalysis { loop_exit_groups, nonlocal_exits }` を追加し、ExitEdge の生配列から「出口ブロック単位のグループ」と「非局所 exit(Return/Throw 等)」を切り出すビューを実装。 + - `analyze_exits(exits: &[ExitEdge]) -> ExitAnalysis` で: + - `loop_exit_groups`: ループ本体からループ外の同じ after-block へ出る出口グループ(ExitKind::ConditionFalse / Break のみ) + - `nonlocal_exits`: ExitKind::Return / Throw / outer break など非局所 exit + に分類。 + - `loop_to_join.rs` の `is_supported_case_a_loop_view` を ExitAnalysis ベースに差し替え: + - 旧ロジック: `control.exits.len() == 1` で単一 ExitEdge を要求していたため、複雑条件 break(短絡 && でブロックが分かれる)を Case-A から外していた。 + - 新ロジック: `exit_analysis.loop_exit_groups.len() == 1`(出口ブロック集合が 1 種類)かつ `exit_analysis.nonlocal_exits.is_empty()`(非局所 exit なし)であれば Case-A とみなすように変更。 + - 効果: + - `if c != ' ' && c != '\t' && c != '\n' { break }` のような複雑条件 break を含むループでも、出口ブロックが 1 種類であれば Case-A として LoopToJoinLowerer/JoinIR lowering の対象にできるようになった。 + - Stage‑1 JoinIR VM bridge テスト 4 件(`joinir_vm_bridge_stage1_*`)は全て PASS を維持。 + - skip_ws 実物については、JoinIR 経由の PHI 形状は引き続き正しく表現できているが、VM 実行側では `StepBudgetExceeded`(既知の別問題)が残っているため、挙動は L-4/L-5(VM / progress carrier まわり)のフェーズで継続調査する。 + **成果物(L-2.1: Stage‑1 UsingResolver 本線ループ)** 1. **本線ループを LoopToJoinLowerer 対象に昇格** - 対象: `Stage1UsingResolverBox.resolve_for_source/5` 内の entries ループ(`lang/src/compiler/entry/using_resolver_box.hako:46-91`)。 @@ -284,7 +396,28 @@ LoopScopeShape → CaseAContext::from_scope() → lower_case_a_X_core() → Join - 最小ケース `minimal_ssa_skip_ws.hako` で `Main.skip(" abc")` を実行 - Route A(VM 直接): 結果 `0` ❌(PHI バグで値消失) - Route B(JoinIR): 結果 `Int(3)` ✅(正解) - - L-3 / L-4: これから(PHI レガシー削除と「JoinIR→VM/LLVM 前提」ランナー構造への仕上げ)。 + - L-3.1 Step-1: **完了(2025-11-26)** — PHI レガシー箱の call site 分析 + - PHI_BOX_INVENTORY.md を Phase 32 向けに更新(削除優先度 High/Medium/Low 分類) + - PhiBuilderBox / PhiInputCollector / LoopSnapshotMergeBox の call site を A/B/C 分類 + - 結果: カテゴリ A(テスト専用)はコメント参照 1 箇所のみ + - L-3.1 Step-2: **完了(2025-11-26)** — PHI 経路削除可能性分析 + - loopform_builder.rs: PhiInputCollector × 3, LoopSnapshotMergeBox × 1 → 非 JoinIR 経路で必須(削除不可) + - json_v0_bridge/loop_.rs: PhiInputCollector × 1 → selfhost 経路で必須(削除不可) + - **結論**: JoinIR は既に PHI 箱をバイパス。削除は L-4(JoinIR 本線化)完了後 + - L-3.2 / L-3.3: これから(JoinIR 本線化後に PHI 箱削除)。 + - L-4.1/L-4.2: **完了(2025-11-26)** — VM Bridge テーブル化 & 本線明示 + - `join_ir_vm_bridge_dispatch.rs` に Descriptor テーブル(`JOINIR_TARGETS`)を導入し、関数名→役割のマッピングを一元管理。 + - `JoinIrBridgeKind` 列挙型: **Exec**(JoinIR→VM 実行まで対応)と **LowerOnly**(lowering 検証のみ、実行は VM Route A)を明確に区別。 + - 対象関数ごとの状態: + | 関数 | Kind | デフォルト有効 | 状態 | + |-----|------|---------------|------| + | `Main.skip/1` | Exec | ❌ | PHI canary のため本線化しない(env 必須) | + | `FuncScannerBox.trim/1` | Exec | ✅ | **唯一の本線昇格候補**(A/B 実証済み) | + | `Stage1UsingResolverBox.resolve_for_source/5` | LowerOnly | ❌ | 構造検証のみ | + | `StageBBodyExtractorBox.build_body_src/2` | LowerOnly | ❌ | 構造検証のみ | + | `StageBFuncScannerBox.scan_all_boxes/1` | LowerOnly | ❌ | 構造検証のみ | + - **結論**: 現状は **trim だけが「JoinIR 実行まで含めて安全」として昇格候補**。skip は PHI canary として残し、Stage-1/Stage-B は ArrayBox/MapBox 引数の JoinValue 対応が揃うまで LowerOnly。 + - L-4.3: これから(LLVM ライン JoinIR 実験: まず llvmlite ハーネスで JoinIR→MIR'→LLVM の最小ケース 1 本を通し、その後 llvmc 側にミラーする計画。いずれも dev トグル前提の実験扱い)。 --- diff --git a/docs/private b/docs/private index 93927b86..79cd258b 160000 --- a/docs/private +++ b/docs/private @@ -1 +1 @@ -Subproject commit 93927b869abd35acdaa74e79c83049be5a81cae1 +Subproject commit 79cd258b4534318586cece48e89d8a1a6cec09f2 diff --git a/src/mir/join_ir/lowering/loop_to_join.rs b/src/mir/join_ir/lowering/loop_to_join.rs index 2a568abe..2c05c0e2 100644 --- a/src/mir/join_ir/lowering/loop_to_join.rs +++ b/src/mir/join_ir/lowering/loop_to_join.rs @@ -15,7 +15,7 @@ //! let join_module = lowerer.lower(func, &loop_form, &query)?; //! ``` -use crate::mir::control_form::{LoopControlShape, LoopId, LoopRegion}; +use crate::mir::control_form::{analyze_exits, ExitEdge, LoopControlShape, LoopId, LoopRegion}; use crate::mir::join_ir::lowering::generic_case_a; use crate::mir::join_ir::lowering::loop_form_intake::intake_loop_form; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; @@ -123,8 +123,8 @@ impl LoopToJoinLowerer { ); } - // Phase 32 L-1.1: View ベースの Case-A サポートチェック(構造判定強化) - if !self.is_supported_case_a_loop_view(func, ®ion, &control, &scope) { + // Phase 32 L-1.4: ExitAnalysis ベースの Case-A サポートチェック + if !self.is_supported_case_a_loop_view(func, ®ion, &control, &exit_edges, &scope) { if self.debug { eprintln!( "[LoopToJoinLowerer] rejected by view-based check: {:?}", @@ -158,22 +158,61 @@ impl LoopToJoinLowerer { self.lower_with_scope(scope, func_name) } + /// Phase 29 L-5.3: Progress carrier の安全性をチェック + /// + /// 無限ループの可能性があるループ(progress carrier が無い、または更新されない) + /// を事前に弾く。 + /// + /// # Phase 1 実装(保守的) + /// + /// - `scope.progress_carrier.is_some()` をチェック + /// - progress_carrier が設定されていればループは進捗すると仮定 + /// + /// # Phase 2 (future) + /// + /// - MirQuery で header→latch 間に Add 命令があるかチェック + /// - skip_ws verifier のロジックを MIR レベルで簡略化して適用 + /// + /// # Arguments + /// + /// - `scope`: LoopScopeShape(progress_carrier 情報を持つ) + /// - `func`: MIR 関数(将来の MirQuery 用) + /// - `region`: LoopRegion(将来の header→latch チェック用) + /// + /// # Returns + /// + /// - `true`: Progress carrier あり(safe) + /// - `false`: Progress carrier なし(unsafe、fallback すべき) + fn has_safe_progress( + scope: &LoopScopeShape, + _func: &MirFunction, // Phase 2 で使用予定 + _region: &LoopRegion, // Phase 2 で使用予定 + ) -> bool { + // Phase 1: 保守的チェック + // progress_carrier が設定されていれば、ループは進捗すると仮定 + // (典型的には 'i' のような loop index) + scope.progress_carrier.is_some() + } + /// Case-A ループとしてサポートされているかチェック(View ベース版) /// - /// Phase 32 L-1.2: 純粋な構造チェックのみ(関数名フィルタは lower() 側で適用) + /// Phase 32 L-1.4: ExitGroup ベースの判定に強化 /// /// # Case-A の定義 /// - /// - 単一出口(exits が 1 箇所以下) + /// - **単一出口グループ**: 全ての出口辺が同じターゲットブロックに向かう + /// (例: `if c != ' ' && c != '\t' { break }` は複数 ExitEdge だが、 + /// 同じ exit ブロックに向かうので Case-A として許可) + /// - **非ローカル出口なし**: Return/Throw がない /// - ヘッダブロックの succ が 2 つ(cond true/false) /// - ループ変数または固定変数が存在 - /// - ネストループなし(将来チェック追加予定) /// /// # Arguments /// /// - `func`: MIR 関数(ヘッダ succ チェック用) /// - `region`: LoopRegion(ブロック構造) /// - `control`: LoopControlShape(制御フロー辺) + /// - `exit_edges`: ExitEdge のリスト(グループ化分析用) /// - `scope`: LoopScopeShape(変数分類用) /// /// # Returns @@ -184,25 +223,39 @@ impl LoopToJoinLowerer { &self, func: &MirFunction, region: &LoopRegion, - control: &LoopControlShape, + _control: &LoopControlShape, // Phase 32 L-1.4: ExitEdge ベースに移行したため未使用 + exit_edges: &[ExitEdge], scope: &LoopScopeShape, ) -> bool { - // Phase 32 L-1.2: 純粋な構造チェックのみ - // 関数名フィルタは lower() 側で generic_case_a_enabled() に応じて適用 + // Phase 32 L-1.4: ExitAnalysis ベースの出口判定 + let exit_analysis = analyze_exits(exit_edges); - // 1) 単一出口 (Case-A 限定: exits が 1 箇所以下) - // Note: break_targets ベースなので、条件 false による自然な出口は含まない - // 将来 Case-B/C で複数出口を許可する場合はここを緩める - if control.exits.len() > 1 { + // 1) 単一出口グループ + 非ローカル出口なし + // 複数の ExitEdge でも、同じターゲットに向かうなら Case-A として許可 + if !exit_analysis.is_single_exit_group() { if self.debug { eprintln!( - "[LoopToJoinLowerer] rejected: multiple exits ({}) - Case-A requires single exit", - control.exits.len() + "[LoopToJoinLowerer] rejected: not single exit group (groups={}, nonlocal={})", + exit_analysis.loop_exit_groups.len(), + exit_analysis.nonlocal_exits.len() ); + // 詳細ログ: 各グループのターゲットを出力 + for (i, group) in exit_analysis.loop_exit_groups.iter().enumerate() { + eprintln!( + " group[{}]: target={:?}, edges={}, has_break={}", + i, + group.target, + group.edges.len(), + group.has_break + ); + } } return false; } + // Note: control.exits は ExitEdge の数(辺の数)なので、 + // 複数でも単一グループなら OK という新しいロジック + // 2) ヘッダブロックの succ が 2 つ(cond true → body, cond false → exit) // これにより while(cond) 形式のループのみを対象とする if let Some(header_block) = func.blocks.get(®ion.header) { @@ -236,6 +289,18 @@ impl LoopToJoinLowerer { return false; } + // 4) Phase 29 L-5.3: Progress carrier チェック + // 無限ループの可能性があるループを事前に弾く + if !Self::has_safe_progress(scope, func, region) { + if self.debug { + eprintln!( + "[LoopToJoinLowerer] rejected: no safe progress carrier (progress_carrier={:?})", + scope.progress_carrier + ); + } + return false; + } + true }