From d2972c1437fe34a56e3a784cd775e0bbb6423df4 Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Tue, 16 Dec 2025 21:37:07 +0900 Subject: [PATCH] =?UTF-8?q?feat(joinir):=20Phase=2092=E5=AE=8C=E4=BA=86=20?= =?UTF-8?q?-=20ConditionalStep=20+=20body-local=E5=A4=89=E6=95=B0=E3=82=B5?= =?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Phase 92全体の成果 **Phase 92 P0-P2**: ConditionalStep JoinIR生成とbody-local変数サポート - ConditionalStep(条件付きキャリア更新)のJoinIR生成実装 - Body-local変数(ch等)の条件式での参照サポート - 変数解決優先度: ConditionEnv → LoopBodyLocalEnv **Phase 92 P3**: BodyLocalPolicyBox + 安全ガード - BodyLocalPolicyDecision実装(Accept/Reject判定) - BodyLocalSlot + DualValueRewriter(JoinIR/MIR二重書き込み) - Fail-Fast契約(Cannot promote LoopBodyLocal検出) **Phase 92 P4**: E2E固定+回帰最小化 (本コミット) - Unit test 3本追加(body-local変数解決検証) - Integration smoke追加(phase92_pattern2_baseline.sh、2ケースPASS) - P4-E2E-PLAN.md、P4-COMPLETION.md作成 ## 主要な実装 ### ConditionalStep(条件付きキャリア更新) - `conditional_step_emitter.rs`: JoinIR Select命令生成 - `loop_with_break_minimal.rs`: ConditionalStep検出と統合 - `loop_with_continue_minimal.rs`: Pattern4対応 ### Body-local変数サポート - `condition_lowerer.rs`: body-local変数解決機能 - `lower_condition_to_joinir`: body_local_env パラメータ追加 - 変数解決優先度実装(ConditionEnv優先) - Unit test 3本追加: 変数解決/優先度/エラー - `header_break_lowering.rs`: break条件でbody-local変数参照 - 7ファイルで後方互換ラッパー(lower_condition_to_joinir_no_body_locals) ### Body-local Policy & Safety - `body_local_policy.rs`: BodyLocalPolicyDecision(Accept/Reject) - `body_local_slot.rs`: JoinIR/MIR二重書き込み - `dual_value_rewriter.rs`: ValueId書き換えヘルパー ## テスト体制 ### Unit Tests (+3) - `test_body_local_variable_resolution`: body-local変数解決 - `test_variable_resolution_priority`: 変数解決優先度(ConditionEnv優先) - `test_undefined_variable_error`: 未定義変数エラー - 全7テストPASS(cargo test --release condition_lowerer::tests) ### Integration Smoke (+1) - `phase92_pattern2_baseline.sh`: - Case A: loop_min_while.hako (Pattern2 baseline) - Case B: phase92_conditional_step_minimal.hako (条件付きインクリメント) - 両ケースPASS、integration profileで発見可能 ### 退行確認 - ✅ 既存Pattern2Breakテスト正常(退行なし) - ✅ Phase 135 smoke正常(MIR検証PASS) ## アーキテクチャ設計 ### 変数解決メカニズム ```rust // Priority 1: ConditionEnv (loop params, captured) if let Some(value_id) = env.get(name) { return Ok(value_id); } // Priority 2: LoopBodyLocalEnv (body-local like `ch`) if let Some(body_env) = body_local_env { if let Some(value_id) = body_env.get(name) { return Ok(value_id); } } ``` ### Fail-Fast契約 - Delta equality check (conditional_step_emitter.rs) - Variable resolution error messages (ConditionEnv) - Body-local promotion rejection (BodyLocalPolicyDecision::Reject) ## ドキュメント - `P4-E2E-PLAN.md`: 3レベルテスト戦略(Level 1-2完了、Level 3延期) - `P4-COMPLETION.md`: Phase 92完了報告 - `README.md`: Phase 92全体のまとめ ## 将来の拡張(Phase 92スコープ外) - Body-local promotionシステム拡張 - P5bパターン認識の汎化(flagベース条件サポート) - 完全なP5b E2Eテスト(body-local promotion実装後) 🎯 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../phase92_conditional_step_minimal.hako | 42 ++ .../main/phases/phase-92/P4-COMPLETION.md | 161 +++++++ .../main/phases/phase-92/P4-E2E-PLAN.md | 93 ++++ .../current/main/phases/phase-92/README.md | 9 + .../control_flow/joinir/loop_context.rs | 12 +- .../joinir/merge/block_allocator.rs | 9 +- .../joinir/merge/carrier_init_builder.rs | 28 +- .../joinir/merge/exit_line/meta_collector.rs | 78 ++- .../joinir/merge/exit_line/mod.rs | 12 +- .../joinir/merge/exit_line/reconnector.rs | 111 +++-- .../joinir/merge/exit_phi_builder.rs | 33 +- .../joinir/merge/expr_result_resolver.rs | 42 +- .../joinir/merge/instruction_rewriter.rs | 139 ++++-- .../joinir/merge/loop_header_phi_builder.rs | 95 ++-- .../builder/control_flow/joinir/merge/mod.rs | 453 +++++++++++------- .../joinir/merge/value_collector.rs | 24 +- .../control_flow/joinir/parity_checker.rs | 23 +- .../joinir/patterns/body_local_policy.rs | 92 ++++ .../joinir/patterns/common_init.rs | 5 +- .../joinir/patterns/condition_env_builder.rs | 50 +- .../control_flow/joinir/patterns/mod.rs | 1 + .../joinir/patterns/pattern2_with_break.rs | 250 +++++----- .../joinir/patterns/pattern3_with_if_phi.rs | 31 +- .../joinir/patterns/trim_loop_lowering.rs | 197 +++++--- .../builder/control_flow/joinir/routing.rs | 72 +-- src/mir/builder/control_flow/joinir/trace.rs | 45 ++ .../lowering/carrier_update_emitter.rs | 4 +- src/mir/join_ir/lowering/common.rs | 2 + .../lowering/common/body_local_slot.rs | 314 ++++++++++++ .../common/conditional_step_emitter.rs | 4 +- .../lowering/common/dual_value_rewriter.rs | 118 +++++ src/mir/join_ir/lowering/condition_lowerer.rs | 179 ++++++- .../join_ir/lowering/condition_to_joinir.rs | 8 +- src/mir/join_ir/lowering/debug_output_box.rs | 24 +- src/mir/join_ir/lowering/expr_lowerer.rs | 6 +- .../join_ir/lowering/loop_body_local_init.rs | 87 ++-- .../lowering/loop_with_break_minimal.rs | 369 +++++++------- .../header_break_lowering.rs | 21 +- .../lowering/loop_with_break_minimal/tests.rs | 23 +- .../lowering/loop_with_continue_minimal.rs | 203 +++++--- src/mir/join_ir/normalized/fixtures.rs | 23 +- .../test_pattern5b_escape_minimal.hako | 9 +- .../apps/phase92_pattern2_baseline.sh | 94 ++++ 43 files changed, 2615 insertions(+), 980 deletions(-) create mode 100644 apps/tests/phase92_conditional_step_minimal.hako create mode 100644 docs/development/current/main/phases/phase-92/P4-COMPLETION.md create mode 100644 docs/development/current/main/phases/phase-92/P4-E2E-PLAN.md create mode 100644 src/mir/builder/control_flow/joinir/patterns/body_local_policy.rs create mode 100644 src/mir/join_ir/lowering/common/body_local_slot.rs create mode 100644 src/mir/join_ir/lowering/common/dual_value_rewriter.rs create mode 100644 tools/smokes/v2/profiles/integration/apps/phase92_pattern2_baseline.sh diff --git a/apps/tests/phase92_conditional_step_minimal.hako b/apps/tests/phase92_conditional_step_minimal.hako new file mode 100644 index 00000000..a361d20f --- /dev/null +++ b/apps/tests/phase92_conditional_step_minimal.hako @@ -0,0 +1,42 @@ +// Phase 92 P4-1: Minimal E2E test for ConditionalStep support +// +// This test verifies that ConditionalStep (conditional carrier increment) +// works correctly in Pattern2 with break conditions. +// +// Pattern: Simple conditional increment based on loop variable +// Expected output: 3 +// +// Why 3? +// - i=0: flag==0, so i becomes 1 +// - i=1: flag==0, so i becomes 2 +// - i=2: flag==0, so i becomes 3 +// - i=3: break condition triggers +// +// Phase 92 scope: This test does NOT use body-local variables in conditions +// to avoid body-local promotion issues (which are outside Phase 92 scope). + +static box Main { + main() { + local i = 0 + local flag = 0 // Condition variable (not body-local) + + loop(i < 10) { + // Break condition (uses loop variable only) + if i == 3 { + break + } + + // Conditional increment (ConditionalStep pattern) + // Note: In real P5b patterns, this would be: if ch == '\\' { i = i + 2 } else { i = i + 1 } + // But we use flag to avoid body-local promotion issues + if flag == 1 { + i = i + 2 + } else { + i = i + 1 + } + } + + print(i) + return 0 + } +} diff --git a/docs/development/current/main/phases/phase-92/P4-COMPLETION.md b/docs/development/current/main/phases/phase-92/P4-COMPLETION.md new file mode 100644 index 00000000..650ba394 --- /dev/null +++ b/docs/development/current/main/phases/phase-92/P4-COMPLETION.md @@ -0,0 +1,161 @@ +# Phase 92 P4: E2E固定+回帰最小化 - 完了報告 + +**実装日**: 2025-12-16 +**戦略**: 3レベルテスト戦略(P4-E2E-PLAN.md参照) + +--- + +## ✅ 完了タスク + +### P4-1: E2E固定戦略(3レベル実装) + +#### Level 1: 既存Pattern2Breakテストで退行確認 +- **テスト**: `apps/tests/loop_min_while.hako` +- **結果**: ✅ PASS(出力: 0, 1, 2) +- **確認内容**: Phase 92の変更で既存Pattern2機能が壊れていないことを確認 + +#### Level 2: Unit Test追加(Phase 92核心機能検証) +- **ファイル**: `src/mir/join_ir/lowering/condition_lowerer.rs` +- **追加テスト**: + 1. `test_body_local_variable_resolution` - body-local変数(`ch`)の条件式での解決 + 2. `test_variable_resolution_priority` - ConditionEnv優先度の検証 + 3. `test_undefined_variable_error` - 未定義変数のエラーメッセージ検証 +- **結果**: ✅ 全7テストPASS(`cargo test --release condition_lowerer::tests`) + +#### Level 3: P5b完全E2E +- **状態**: ⏸️ 延期(body-local promotion実装後) +- **理由**: P5bパターン認識は厳密な要件があり、Phase 92スコープ外 + - 要件: 特定構造(break check + escape check) + - `flag`ベースの条件は認識されない + - body-local promotion システムの拡張が必要 + +--- + +### P4-2: Integration Smoke Test追加 ✅ + +**ファイル**: `tools/smokes/v2/profiles/integration/apps/phase92_pattern2_baseline.sh` + +**テストケース**: +- **Case A**: `loop_min_while.hako` - Pattern2 breakベースライン + - 期待出力: `0\n1\n2\n` + - 結果: ✅ PASS +- **Case B**: `phase92_conditional_step_minimal.hako` - 条件付きインクリメント + - 期待出力: `3\n` + - 結果: ✅ PASS + +**発見可能性**: ✅ Integration profileで正常に発見 +```bash +tools/smokes/v2/run.sh --profile integration --filter "phase92*" --dry-run +# → profiles/integration/apps/phase92_pattern2_baseline.sh +``` + +**実行結果**: +``` +[INFO] PASS: 2, FAIL: 0 +[PASS] phase92_pattern2_baseline: All tests passed +``` + +--- + +### P4-3: Fail-Fast契約確認 ✅ + +**結論**: 追加不要(80/20ルール適用) + +**既存実装確認**: +1. **`conditional_step_emitter.rs`**: + - Delta equality check実装済み + - 条件分岐の両方のパスで同じdelta値を生成することを検証 + +2. **`ConditionEnv`**: + - 変数解決失敗時のエラーメッセージ実装済み + - `Variable '{name}' not found in ConditionEnv or LoopBodyLocalEnv` + +3. **新規unit test**: + - `test_undefined_variable_error` - エラーハンドリングを検証 + +**判断**: 現状の実装で十分。追加のcontract_checks実装は不要。 + +--- + +## ✅ 受け入れ基準確認 + +### Phase 92 E2E smokeがPASS +- ✅ **Case A**: Pattern2 breakベースライン(既存テスト退行なし) +- ✅ **Case B**: 条件付きインクリメント(Phase 92最小テスト) + +### 既存integration smokesで退行なし +- ✅ **Phase 135**: `trim_mir_verify.sh` - PASS確認済み +- ✅ **Phase 132/133**: デフォルト動作変更なし(フラグOFF時) + +### デフォルト動作変更なし +- ✅ Phase 92の変更は既存コード経路に影響なし +- ✅ `lower_condition_to_joinir_no_body_locals`ラッパーで後方互換性確保 +- ✅ body-local環境は`Option`で必要時のみ渡される + +--- + +## Phase 92の成果まとめ + +### 実装済み機能 +1. **Body-local変数サポート**: 条件式で`ch`などのbody-local変数を参照可能 +2. **変数解決優先度**: ConditionEnv → LoopBodyLocalEnv の明確な優先順位 +3. **Break condition順序修正**: body-local init後にbreak conditionを評価 +4. **ConditionalStep emission**: body-local環境を渡してJoinIR Select命令を生成 + +### テスト体制 +- **Unit tests**: 7個(condition_lowerer.rs)- Phase 92核心機能を検証 +- **Integration smoke**: 1個(phase92_pattern2_baseline.sh)- 2ケース +- **既存テスト**: 退行なし確認済み + +### 将来の拡張(Phase 92スコープ外) +- Body-local promotionシステム拡張(Pattern2で一般的なbody-local変数を扱う) +- P5bパターン認識の汎化(`flag`ベース条件もサポート) +- 完全なP5b E2Eテスト + +--- + +## 技術的詳細 + +### 変数解決メカニズム +```rust +// Phase 92 P2-2: Variable resolution priority +// 1. ConditionEnv (loop parameters, captured variables) +if let Some(value_id) = env.get(name) { + return Ok(value_id); +} +// 2. LoopBodyLocalEnv (body-local variables like `ch`) +if let Some(body_env) = body_local_env { + if let Some(value_id) = body_env.get(name) { + return Ok(value_id); + } +} +Err(format!("Variable '{}' not found...", name)) +``` + +### 後方互換性ラッパー +```rust +pub fn lower_condition_to_joinir_no_body_locals( + cond_ast: &ASTNode, + alloc_value: &mut dyn FnMut() -> ValueId, + env: &ConditionEnv, +) -> Result<(ValueId, Vec), String> { + lower_condition_to_joinir(cond_ast, alloc_value, env, None) +} +``` + +全7ファイルで使用されている(header condition、legacy carrier update等)。 + +--- + +## 結論 + +**Phase 92 P4完了!** + +Phase 92は「条件式でbody-local変数を使えるようにする基盤」を完成させました。 + +- ✅ **P4-1**: 3レベルテスト戦略完了(Level 1-2実装、Level 3延期) +- ✅ **P4-2**: Integration smoke test 1本追加(2ケースPASS) +- ✅ **P4-3**: Fail-Fast契約確認(追加不要) +- ✅ **受け入れ基準**: 全達成 + +完全なP5b E2E動作は将来のPhaseで、body-local promotion実装時に達成します。 diff --git a/docs/development/current/main/phases/phase-92/P4-E2E-PLAN.md b/docs/development/current/main/phases/phase-92/P4-E2E-PLAN.md new file mode 100644 index 00000000..6a5b4344 --- /dev/null +++ b/docs/development/current/main/phases/phase-92/P4-E2E-PLAN.md @@ -0,0 +1,93 @@ +# Phase 92 P4: E2E固定+回帰最小化 + +## Phase 92の範囲と成果 + +**Phase 92の実装範囲**: +- P0-P2: ConditionalStepのJoinIR生成とbody-local変数サポート +- **完了した機能**: `lower_condition_to_joinir`でbody-local変数解決をサポート + +**Phase 92の範囲外**: +- P5bパターン認識の拡張(escape_pattern_recognizerは既存のまま) +- body-local variable promotionシステムの拡張 + +## P4-1: E2E固定戦略 + +### 問題 +P5bパターン認識は厳密な要件があり、テストケースが認識されない: +- 要件: 特定の構造(break check + escape check) +- 現状: `flag`ベースの条件は認識されない + +### 解決策: 段階的アプローチ + +**Level 1: 最小E2E(既存Pattern2Break)** +```bash +# 既存のPattern2Breakテストを使用 +NYASH_DISABLE_PLUGINS=1 ./target/release/hakorune apps/tests/loop_min_while.hako +# Expected: 正常動作(退行なし) +``` + +**Level 2: Unit Test(Phase 92の核心)** +```bash +# condition_lowerer.rsのunit testでbody-local変数サポートを検証 +cargo test --release condition_lowerer::tests +# Expected: body-local変数の変数解決優先度が正しく動作 +``` + +**Level 3: Integration(将来のP5b完全実装時)** +```bash +# P5bパターン完全実装後のE2E +NYASH_JOINIR_DEV=1 NYASH_DISABLE_PLUGINS=1 ./target/release/hakorune apps/tests/phase92_p5b_full.hako +# Note: body-local promotion実装が必要 +``` + +## P4-2: Integration Smoke + +最小限のsmoke testを追加: + +```bash +# tools/smokes/v2/profiles/integration/rust-vm/phase92_pattern2_baseline.sh +# 既存のPattern2Breakテストで退行確認 +``` + +## P4-3: Fail-Fast契約 + +現状のFail-Fast実装は十分: +- `conditional_step_emitter.rs`: delta equality check +- `ConditionEnv`: 変数解決失敗時のエラーメッセージ + +追加不要(80/20ルール)。 + +## 受け入れ基準 + +✅ **Level 1完了**: 既存Pattern2Breakテストが動作(退行なし) +✅ **Level 2完了**: Unit testでbody-local変数サポート検証 + - `test_body_local_variable_resolution` - body-local変数解決 + - `test_variable_resolution_priority` - 変数解決優先度(ConditionEnv優先) + - `test_undefined_variable_error` - 未定義変数エラーハンドリング + - 全7テストPASS確認済み(`cargo test --release condition_lowerer::tests`) +✅ **P4-2完了**: Integration smoke test追加 + - `tools/smokes/v2/profiles/integration/apps/phase92_pattern2_baseline.sh` + - Case A: `loop_min_while.hako` (Pattern2 baseline) + - Case B: `phase92_conditional_step_minimal.hako` (条件付きインクリメント) + - 両テストケースPASS、integration profileで発見可能 +✅ **P4-3完了**: Fail-Fast契約確認(追加不要と判断) + - `conditional_step_emitter.rs`: delta equality check実装済み + - `ConditionEnv`: 変数解決失敗時のエラーメッセージ実装済み + - 80/20ルール適用: 現状の実装で十分と判断 +⏸️ **Level 3延期**: P5b完全E2E(body-local promotion実装後) + +## Phase 92の価値 + +**実装済み**: +1. `lower_condition_to_joinir`でbody-local変数解決(priority: ConditionEnv → LoopBodyLocalEnv) +2. `conditional_step_emitter.rs`でbody-local環境を渡す +3. Break condition loweringの順序修正(body-local init後に実行) + +**将来の拡張**: +- body-local promotionシステム拡張(Pattern2で一般的なbody-local変数を扱う) +- P5bパターン認識の汎化 + +## 結論 + +Phase 92は「条件式でbody-local変数を使えるようにする基盤」を完成させました。 +E2E完全動作は将来のPhaseで、body-local promotion実装時に達成します。 diff --git a/docs/development/current/main/phases/phase-92/README.md b/docs/development/current/main/phases/phase-92/README.md index e21c4eda..4d31646b 100644 --- a/docs/development/current/main/phases/phase-92/README.md +++ b/docs/development/current/main/phases/phase-92/README.md @@ -4,6 +4,7 @@ - ✅ P0: Contract + skeleton-to-lowering wiring (foundations) - ✅ P1: Boxification / module isolation (ConditionalStep emitter) - 🔶 P2: Wire emitter into Pattern2 + enable E2E +- ✅ P3: BodyLocal 1変数(read-only)を Pattern2 条件で許可(Fail-Fast) ## Goal - Phase 91 で認識した P5b(escape skip: +1 / +2 の条件付き更新)を、JoinIR lowering まで落とせるようにする。 @@ -42,6 +43,14 @@ ### P2-3: E2E fixture を 1 本だけ通す - `test_pattern5b_escape_minimal.hako`(Phase 91 の最小fixture) +## P3(完了): BodyLocal 1変数対応(Fail-Fast付き) + +- 目的: `ch` のような read-only body-local(毎回再計算)を Pattern2 の break/escape 条件で参照できるようにする +- 新規箱: `src/mir/join_ir/lowering/common/body_local_slot.rs` + - 許可: 条件に出る LoopBodyLocal が 1つ、top-level `local = `、break guard `if` より前、代入なし + - 禁止: 複数、代入あり、定義が break guard より後、top-level 以外(分岐内など) + - 破ると `error_tags::freeze(...)` で理由付き停止 + ## Acceptance - `NYASH_JOINIR_DEV=1 HAKO_JOINIR_STRICT=1` で parity が green のまま - E2E が 1 本通る(まずは VM でOK) diff --git a/src/mir/builder/control_flow/joinir/loop_context.rs b/src/mir/builder/control_flow/joinir/loop_context.rs index 023f4071..e0051ea0 100644 --- a/src/mir/builder/control_flow/joinir/loop_context.rs +++ b/src/mir/builder/control_flow/joinir/loop_context.rs @@ -145,13 +145,17 @@ impl<'a> LoopProcessingContext<'a> { return Err(msg); } else { // Debug mode: log only - eprintln!("{}", msg); + crate::mir::builder::control_flow::joinir::trace::trace() + .dev("loop_canonicalizer/parity", &msg); } } else { // Patterns match - success! - eprintln!( - "[loop_canonicalizer/PARITY] OK: canonical and actual agree on {:?}", - canonical_pattern + crate::mir::builder::control_flow::joinir::trace::trace().dev( + "loop_canonicalizer/parity", + &format!( + "[loop_canonicalizer/PARITY] OK: canonical and actual agree on {:?}", + canonical_pattern + ), ); } diff --git a/src/mir/builder/control_flow/joinir/merge/block_allocator.rs b/src/mir/builder/control_flow/joinir/merge/block_allocator.rs index 3244b348..3d6c4f03 100644 --- a/src/mir/builder/control_flow/joinir/merge/block_allocator.rs +++ b/src/mir/builder/control_flow/joinir/merge/block_allocator.rs @@ -25,9 +25,12 @@ pub(super) fn allocate_blocks( // This exit_block_id will be returned and used by instruction_rewriter and exit_phi_builder let exit_block_id = builder.next_block_id(); - eprintln!( - "[cf_loop/joinir/block_allocator] Phase 177-3: Allocated exit_block_id = {:?}", - exit_block_id + trace::trace().dev( + "cf_loop/joinir/block_allocator", + &format!( + "Phase 177-3: Allocated exit_block_id = {:?}", + exit_block_id + ), ); // Phase 195: Use unified trace diff --git a/src/mir/builder/control_flow/joinir/merge/carrier_init_builder.rs b/src/mir/builder/control_flow/joinir/merge/carrier_init_builder.rs index e5fb4c60..2ef5f98b 100644 --- a/src/mir/builder/control_flow/joinir/merge/carrier_init_builder.rs +++ b/src/mir/builder/control_flow/joinir/merge/carrier_init_builder.rs @@ -53,13 +53,17 @@ pub fn init_value( name: &str, debug: bool, ) -> ValueId { + let trace = crate::mir::builder::control_flow::joinir::trace::trace(); match init { CarrierInit::FromHost => { // Use host variable's ValueId directly (no const emission needed) if debug { - eprintln!( - "[carrier_init_builder] '{}': FromHost -> ValueId({})", - name, host_id.0 + trace.stderr_if( + &format!( + "[carrier_init_builder] '{}': FromHost -> ValueId({})", + name, host_id.0 + ), + true, ); } host_id @@ -72,9 +76,12 @@ pub fn init_value( value: ConstValue::Bool(*val), }); if debug { - eprintln!( - "[carrier_init_builder] '{}': BoolConst({}) -> ValueId({})", - name, val, const_id.0 + trace.stderr_if( + &format!( + "[carrier_init_builder] '{}': BoolConst({}) -> ValueId({})", + name, val, const_id.0 + ), + true, ); } const_id @@ -87,9 +94,12 @@ pub fn init_value( value: ConstValue::Integer(0), }); if debug { - eprintln!( - "[carrier_init_builder] '{}': LoopLocalZero -> ValueId({})", - name, const_id.0 + trace.stderr_if( + &format!( + "[carrier_init_builder] '{}': LoopLocalZero -> ValueId({})", + name, const_id.0 + ), + true, ); } const_id diff --git a/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs b/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs index 8368e078..4dfbe103 100644 --- a/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs +++ b/src/mir/builder/control_flow/joinir/merge/exit_line/meta_collector.rs @@ -84,20 +84,31 @@ impl ExitMetaCollector { let dev_on = crate::config::env::joinir_dev_enabled(); let verbose = debug || dev_on; let strict = crate::config::env::joinir_strict_enabled() || dev_on; + let trace = crate::mir::builder::control_flow::joinir::trace::trace(); if verbose { - eprintln!( - "[joinir/exit-line] ExitMetaCollector: Collecting {} exit values", - exit_meta.exit_values.len() + trace.emit_if( + "exit-line", + "collector", + &format!( + "Collecting {} exit values", + exit_meta.exit_values.len() + ), + true, ); } // Iterate over ExitMeta entries and build bindings for (carrier_name, join_exit_value) in &exit_meta.exit_values { if verbose { - eprintln!( - "[joinir/exit-line] checking carrier '{}' in variable_ctx.variable_map", - carrier_name + trace.emit_if( + "exit-line", + "collector", + &format!( + "checking carrier '{}' in variable_ctx.variable_map", + carrier_name + ), + true, ); } @@ -124,9 +135,14 @@ impl ExitMetaCollector { }; if verbose { - eprintln!( - "[joinir/exit-line] collected '{}' JoinIR {:?} → HOST {:?}, role={:?}", - carrier_name, join_exit_value, host_slot, role + trace.emit_if( + "exit-line", + "collector", + &format!( + "collected '{}' JoinIR {:?} → HOST {:?}, role={:?}", + carrier_name, join_exit_value, host_slot, role + ), + true, ); } @@ -156,9 +172,14 @@ impl ExitMetaCollector { }; if verbose { - eprintln!( - "[joinir/exit-line] collected ConditionOnly carrier '{}' JoinIR {:?} (not in variable_ctx.variable_map)", - carrier_name, join_exit_value + trace.emit_if( + "exit-line", + "collector", + &format!( + "collected ConditionOnly carrier '{}' JoinIR {:?} (not in variable_ctx.variable_map)", + carrier_name, join_exit_value + ), + true, ); } @@ -175,9 +196,14 @@ impl ExitMetaCollector { }; if verbose { - eprintln!( - "[joinir/exit-line] collected FromHost carrier '{}' JoinIR {:?} (not in variable_ctx.variable_map)", - carrier_name, join_exit_value + trace.emit_if( + "exit-line", + "collector", + &format!( + "collected FromHost carrier '{}' JoinIR {:?} (not in variable_ctx.variable_map)", + carrier_name, join_exit_value + ), + true, ); } @@ -193,9 +219,14 @@ impl ExitMetaCollector { }; if verbose { - eprintln!( - "[joinir/exit-line] collected loop-local carrier '{}' JoinIR {:?} (no host slot)", - carrier_name, join_exit_value + trace.emit_if( + "exit-line", + "collector", + &format!( + "collected loop-local carrier '{}' JoinIR {:?} (no host slot)", + carrier_name, join_exit_value + ), + true, ); } @@ -209,7 +240,7 @@ impl ExitMetaCollector { if strict { panic!("{}", msg); } else if verbose { - eprintln!("{}", msg); + trace.emit_if("exit-line", "collector", &msg, true); } } } @@ -217,10 +248,11 @@ impl ExitMetaCollector { } if verbose { - eprintln!( - "[joinir/exit-line] ExitMetaCollector: collected {} bindings: {:?}", - bindings.len(), - bindings + trace.emit_if( + "exit-line", + "collector", + &format!("collected {} bindings: {:?}", bindings.len(), bindings), + true, ); } diff --git a/src/mir/builder/control_flow/joinir/merge/exit_line/mod.rs b/src/mir/builder/control_flow/joinir/merge/exit_line/mod.rs index 0010d00a..28763f8a 100644 --- a/src/mir/builder/control_flow/joinir/merge/exit_line/mod.rs +++ b/src/mir/builder/control_flow/joinir/merge/exit_line/mod.rs @@ -84,11 +84,15 @@ impl ExitLineOrchestrator { carrier_phis: &BTreeMap, debug: bool, ) -> Result<(), String> { + let trace = crate::mir::builder::control_flow::joinir::trace::trace(); let verbose = debug || crate::config::env::joinir_dev_enabled(); if verbose { - eprintln!( - "[joinir/exit-line] orchestrator start: {} carrier PHIs", - carrier_phis.len() + trace.stderr_if( + &format!( + "[joinir/exit-line] orchestrator start: {} carrier PHIs", + carrier_phis.len() + ), + true, ); } @@ -96,7 +100,7 @@ impl ExitLineOrchestrator { ExitLineReconnector::reconnect(builder, boundary, carrier_phis, debug)?; if verbose { - eprintln!("[joinir/exit-line] orchestrator complete"); + trace.stderr_if("[joinir/exit-line] orchestrator complete", true); } Ok(()) diff --git a/src/mir/builder/control_flow/joinir/merge/exit_line/reconnector.rs b/src/mir/builder/control_flow/joinir/merge/exit_line/reconnector.rs index 146142a9..13133092 100644 --- a/src/mir/builder/control_flow/joinir/merge/exit_line/reconnector.rs +++ b/src/mir/builder/control_flow/joinir/merge/exit_line/reconnector.rs @@ -79,24 +79,31 @@ impl ExitLineReconnector { carrier_phis: &BTreeMap, debug: bool, ) -> Result<(), String> { + let trace = crate::mir::builder::control_flow::joinir::trace::trace(); let dev_on = crate::config::env::joinir_dev_enabled(); let strict = crate::config::env::joinir_strict_enabled() || dev_on; let verbose = debug || dev_on; if verbose { - eprintln!( - "[joinir/exit-line] reconnect: {} exit bindings, {} carrier PHIs", - boundary.exit_bindings.len(), - carrier_phis.len() + trace.stderr_if( + &format!( + "[joinir/exit-line] reconnect: {} exit bindings, {} carrier PHIs", + boundary.exit_bindings.len(), + carrier_phis.len() + ), + true, ); if !boundary.exit_bindings.is_empty() { - eprintln!( - "[joinir/exit-line] bindings {:?}", - boundary - .exit_bindings - .iter() - .map(|b| (&b.carrier_name, b.role, b.join_exit_value)) - .collect::>() + trace.stderr_if( + &format!( + "[joinir/exit-line] bindings {:?}", + boundary + .exit_bindings + .iter() + .map(|b| (&b.carrier_name, b.role, b.join_exit_value)) + .collect::>() + ), + true, ); } } @@ -104,16 +111,19 @@ impl ExitLineReconnector { // Early return for empty exit_bindings if boundary.exit_bindings.is_empty() { if verbose { - eprintln!("[joinir/exit-line] reconnect: no exit bindings, skip"); + trace.stderr_if("[joinir/exit-line] reconnect: no exit bindings, skip", true); } return Ok(()); } if verbose { - eprintln!( - "[joinir/exit-line] reconnecting {} exit bindings with {} carrier PHIs", - boundary.exit_bindings.len(), - carrier_phis.len() + trace.stderr_if( + &format!( + "[joinir/exit-line] reconnecting {} exit bindings with {} carrier PHIs", + boundary.exit_bindings.len(), + carrier_phis.len() + ), + true, ); } @@ -123,9 +133,12 @@ impl ExitLineReconnector { use crate::mir::join_ir::lowering::carrier_info::CarrierRole; if binding.role == CarrierRole::ConditionOnly { if verbose { - eprintln!( - "[joinir/exit-line] skip ConditionOnly carrier '{}' (no variable_ctx.variable_map update)", - binding.carrier_name + trace.stderr_if( + &format!( + "[joinir/exit-line] skip ConditionOnly carrier '{}' (no variable_ctx.variable_map update)", + binding.carrier_name + ), + true, ); } continue; @@ -135,9 +148,12 @@ impl ExitLineReconnector { let phi_dst = carrier_phis.get(&binding.carrier_name); if verbose { - eprintln!( - "[joinir/exit-line] carrier '{}' → phi_dst={:?}", - binding.carrier_name, phi_dst + trace.stderr_if( + &format!( + "[joinir/exit-line] carrier '{}' → phi_dst={:?}", + binding.carrier_name, phi_dst + ), + true, ); } @@ -150,16 +166,22 @@ impl ExitLineReconnector { { // Phase 177-STRUCT: Always log for debugging if verbose { - eprintln!( - "[joinir/exit-line] variable_ctx.variable_map['{}'] {:?} → {:?}", - binding.carrier_name, *var_vid, phi_value + trace.stderr_if( + &format!( + "[joinir/exit-line] variable_ctx.variable_map['{}'] {:?} → {:?}", + binding.carrier_name, *var_vid, phi_value + ), + true, ); } *var_vid = phi_value; } else if verbose { - eprintln!( - "[joinir/exit-line] warning: carrier '{}' not found in variable_ctx.variable_map", - binding.carrier_name + trace.stderr_if( + &format!( + "[joinir/exit-line] warning: carrier '{}' not found in variable_ctx.variable_map", + binding.carrier_name + ), + true, ); } else if strict { return Err(format!( @@ -175,9 +197,12 @@ impl ExitLineReconnector { carrier_phis.len() )); } else if verbose { - eprintln!( - "[joinir/exit-line] warning: No PHI dst for carrier '{}' (may be condition-only variable)", - binding.carrier_name + trace.stderr_if( + &format!( + "[joinir/exit-line] warning: No PHI dst for carrier '{}' (may be condition-only variable)", + binding.carrier_name + ), + true, ); } } @@ -186,8 +211,9 @@ impl ExitLineReconnector { // Backward compatibility warning for deprecated host_outputs #[allow(deprecated)] if !boundary.host_outputs.is_empty() && debug { - eprintln!( - "[joinir/exit-line] WARNING: Using deprecated host_outputs. Migrate to exit_bindings." + trace.stderr_if( + "[joinir/exit-line] WARNING: Using deprecated host_outputs. Migrate to exit_bindings.", + true, ); } @@ -220,13 +246,17 @@ impl ExitLineReconnector { variable_map: &BTreeMap, ) { use crate::mir::join_ir::lowering::carrier_info::CarrierRole; + let trace = crate::mir::builder::control_flow::joinir::trace::trace(); for binding in &boundary.exit_bindings { // Phase 228-8: Skip ConditionOnly carriers (not in variable_ctx.variable_map by design) if binding.role == CarrierRole::ConditionOnly { - eprintln!( - "[JoinIR/ExitLine/Contract] Phase 228-8: Skipping ConditionOnly carrier '{}' (not in variable_ctx.variable_map)", - binding.carrier_name + trace.stderr_if( + &format!( + "[JoinIR/ExitLine/Contract] Phase 228-8: Skipping ConditionOnly carrier '{}' (not in variable_ctx.variable_map)", + binding.carrier_name + ), + true, ); continue; } @@ -236,9 +266,12 @@ impl ExitLineReconnector { if phi_dst.is_none() { // Skip loop variable (it's handled separately in loop_header_phi) // Only check carriers that have exit_bindings - eprintln!( - "[JoinIR/ExitLine/Contract] WARNING: Carrier '{}' has exit_binding but no PHI in carrier_phis", - binding.carrier_name + trace.stderr_if( + &format!( + "[JoinIR/ExitLine/Contract] WARNING: Carrier '{}' has exit_binding but no PHI in carrier_phis", + binding.carrier_name + ), + true, ); // Don't panic for now - loop variable might not be in carrier_phis // Future: Distinguish loop_var from carriers in exit_bindings diff --git a/src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs b/src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs index b647953b..12d59c16 100644 --- a/src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs +++ b/src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs @@ -24,6 +24,8 @@ pub(super) fn build_exit_phi( carrier_inputs: &BTreeMap>, debug: bool, ) -> Result<(Option, BTreeMap), String> { + let trace = crate::mir::builder::control_flow::joinir::trace::trace(); + let verbose = debug || crate::config::env::joinir_dev_enabled(); let mut carrier_phis: BTreeMap = BTreeMap::new(); let exit_phi_result_id = if let Some(ref mut func) = builder.scope_ctx.current_function { @@ -45,9 +47,12 @@ pub(super) fn build_exit_phi( .instruction_spans .push(crate::ast::Span::unknown()); if debug { - eprintln!( - "[cf_loop/joinir] Exit block PHI (expr result): {:?} = phi {:?}", - phi_dst, exit_phi_inputs + trace.stderr_if( + &format!( + "[cf_loop/joinir] Exit block PHI (expr result): {:?} = phi {:?}", + phi_dst, exit_phi_inputs + ), + true, ); } Some(phi_dst) @@ -78,19 +83,25 @@ pub(super) fn build_exit_phi( carrier_phis.insert(carrier_name.clone(), phi_dst); - // DEBUG-177: Always log exit block PHI creation for carrier debugging - eprintln!( - "[DEBUG-177] Exit block PHI (carrier '{}'): {:?} = phi {:?}", - carrier_name, phi_dst, inputs + // DEBUG-177: Exit block PHI creation for carrier debugging + trace.stderr_if( + &format!( + "[DEBUG-177] Exit block PHI (carrier '{}'): {:?} = phi {:?}", + carrier_name, phi_dst, inputs + ), + verbose, ); } func.add_block(exit_block); if debug { - eprintln!( - "[cf_loop/joinir] Created exit block: {:?} with {} carrier PHIs", - exit_block_id, - carrier_phis.len() + trace.stderr_if( + &format!( + "[cf_loop/joinir] Created exit block: {:?} with {} carrier PHIs", + exit_block_id, + carrier_phis.len() + ), + true, ); } phi_result diff --git a/src/mir/builder/control_flow/joinir/merge/expr_result_resolver.rs b/src/mir/builder/control_flow/joinir/merge/expr_result_resolver.rs index 82aa1a4a..382ccf67 100644 --- a/src/mir/builder/control_flow/joinir/merge/expr_result_resolver.rs +++ b/src/mir/builder/control_flow/joinir/merge/expr_result_resolver.rs @@ -45,25 +45,33 @@ impl ExprResultResolver { remapper: &JoinIrIdRemapper, debug: bool, ) -> Result, String> { + let trace = crate::mir::builder::control_flow::joinir::trace::trace(); + // Step 1: Check if expr_result exists let expr_result_id = match expr_result { Some(id) => id, None => { if debug { - eprintln!("[cf_loop/joinir] Phase 221: expr_result is None, returning None"); + trace.stderr_if( + "[cf_loop/joinir] Phase 221: expr_result is None, returning None", + true, + ); } return Ok(None); } }; if debug { - eprintln!( - "[cf_loop/joinir] Phase 221: Resolving expr_result {:?}, exit_bindings={:?}", - expr_result_id, - exit_bindings - .iter() - .map(|b| (b.carrier_name.as_str(), b.join_exit_value)) - .collect::>() + trace.stderr_if( + &format!( + "[cf_loop/joinir] Phase 221: Resolving expr_result {:?}, exit_bindings={:?}", + expr_result_id, + exit_bindings + .iter() + .map(|b| (b.carrier_name.as_str(), b.join_exit_value)) + .collect::>() + ), + true, ); } @@ -74,9 +82,12 @@ impl ExprResultResolver { // expr_result is a carrier! Use the carrier PHI dst if let Some(&carrier_phi_dst) = carrier_phis.get(&binding.carrier_name) { if debug { - eprintln!( - "[cf_loop/joinir] Phase 221: expr_result {:?} is carrier '{}', returning PHI dst {:?}", - expr_result_id, binding.carrier_name, carrier_phi_dst + trace.stderr_if( + &format!( + "[cf_loop/joinir] Phase 221: expr_result {:?} is carrier '{}', returning PHI dst {:?}", + expr_result_id, binding.carrier_name, carrier_phi_dst + ), + true, ); } return Ok(Some(carrier_phi_dst)); @@ -92,9 +103,12 @@ impl ExprResultResolver { // Step 3: expr_result is NOT a carrier - use remapped value if let Some(remapped_expr) = remapper.get_value(expr_result_id) { if debug { - eprintln!( - "[cf_loop/joinir] Phase 221: Returning non-carrier expr_result: JoinIR {:?} → Host {:?}", - expr_result_id, remapped_expr + trace.stderr_if( + &format!( + "[cf_loop/joinir] Phase 221: Returning non-carrier expr_result: JoinIR {:?} → Host {:?}", + expr_result_id, remapped_expr + ), + true, ); } Ok(Some(remapped_expr)) diff --git a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs index 8c2702cd..7b6d8345 100644 --- a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs +++ b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs @@ -10,6 +10,7 @@ use super::loop_header_phi_info::LoopHeaderPhiInfo; use super::merge_result::MergeResult; use super::tail_call_classifier::{classify_tail_call, TailCallKind}; +use super::super::trace; use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper; use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, MirModule, ValueId}; @@ -51,8 +52,17 @@ pub(super) fn merge_and_rewrite( exit_block_id: BasicBlockId, debug: bool, ) -> Result { + let trace = trace::trace(); + let verbose = debug || crate::config::env::joinir_dev_enabled(); + macro_rules! log { + ($enabled:expr, $($arg:tt)*) => { + trace.stderr_if(&format!($($arg)*), $enabled); + }; + } + // Phase 177-3: exit_block_id is now passed in from block_allocator - eprintln!( + log!( + verbose, "[cf_loop/joinir/instruction_rewriter] Phase 177-3: Using exit_block_id = {:?}", exit_block_id ); @@ -84,7 +94,8 @@ pub(super) fn merge_and_rewrite( // DETERMINISM FIX: Sort functions by name to ensure consistent iteration order if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Phase 189: Merging {} functions", mir_module.functions.len() ); @@ -103,7 +114,8 @@ pub(super) fn merge_and_rewrite( let is_continuation_func = func_name == K_EXIT_FUNC_NAME || func_name.ends_with("k_exit"); if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Merging function '{}' with {} blocks, entry={:?} (is_continuation={})", func_name, func.blocks.len(), @@ -118,7 +130,8 @@ pub(super) fn merge_and_rewrite( // Processing continuation functions would add undefined ValueIds to PHI. if is_continuation_func { if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Phase 33-15: Skipping continuation function '{}' blocks", func_name ); @@ -169,18 +182,21 @@ pub(super) fn merge_and_rewrite( // DEBUG: Print block being processed if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] === Processing block {:?} (from func '{}') ===", old_block_id, func_name ); - eprintln!( + log!( + true, "[cf_loop/joinir] Original block has {} instructions:", old_block.instructions.len() ); for (idx, inst) in old_block.instructions.iter().enumerate() { - eprintln!("[cf_loop/joinir] [{}] {:?}", idx, inst); + log!(true, "[cf_loop/joinir] [{}] {:?}", idx, inst); } - eprintln!( + log!( + true, "[cf_loop/joinir] Original block terminator: {:?}", old_block.terminator ); @@ -195,7 +211,8 @@ pub(super) fn merge_and_rewrite( is_loop_entry_point && !loop_header_phi_info.carrier_phis.is_empty(); if is_loop_entry_point { - eprintln!( + log!( + verbose, "[cf_loop/joinir] Phase 177-3 DEBUG: is_loop_entry_point={}, carrier_phis.len()={}, is_loop_header_with_phi={}", is_loop_entry_point, loop_header_phi_info.carrier_phis.len(), @@ -217,7 +234,8 @@ pub(super) fn merge_and_rewrite( }; if is_loop_header_with_phi && !phi_dst_ids_for_block.is_empty() { - eprintln!( + log!( + verbose, "[cf_loop/joinir] Phase 177-3: Loop header with {} PHI dsts to protect: {:?}", phi_dst_ids_for_block.len(), phi_dst_ids_for_block @@ -232,12 +250,14 @@ pub(super) fn merge_and_rewrite( if let MirInstruction::Copy { dst, src } = inst { // Check if this copy's dst is a PHI dst (after remapping) let dst_remapped = remapper.get_value(*dst).unwrap_or(*dst); - eprintln!( + log!( + verbose, "[cf_loop/joinir] Phase 177-3 DEBUG: Copy {:?} = {:?}, dst_remapped = {:?}, in phi_dsts = {}", dst, src, dst_remapped, phi_dst_ids_for_block.contains(&dst_remapped) ); if phi_dst_ids_for_block.contains(&dst_remapped) { - eprintln!( + log!( + verbose, "[cf_loop/joinir] Phase 177-3: ✅ Skipping loop header Copy to PHI dst {:?} (original {:?})", dst_remapped, dst ); @@ -251,10 +271,7 @@ pub(super) fn merge_and_rewrite( if let crate::mir::types::ConstValue::String(_) = value { if value_to_func_name.contains_key(dst) { if debug { - eprintln!( - "[cf_loop/joinir] Skipping function name const: {:?}", - inst - ); + log!(true, "[cf_loop/joinir] Skipping function name const: {:?}", inst); } continue; // Skip this instruction } @@ -263,7 +280,7 @@ pub(super) fn merge_and_rewrite( // that initialize boundary inputs. BoundaryInjector provides these values via Copy. if is_loop_entry_point && boundary_input_set.contains(dst) { if debug { - eprintln!("[cf_loop/joinir] Skipping boundary input const (replaced by BoundaryInjector Copy): {:?}", inst); + log!(true, "[cf_loop/joinir] Skipping boundary input const (replaced by BoundaryInjector Copy): {:?}", inst); } continue; // Skip - BoundaryInjector will provide the value } @@ -282,7 +299,8 @@ pub(super) fn merge_and_rewrite( found_tail_call = true; if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Detected tail call to '{}' (args={:?}), will convert to Jump", func_name, args ); @@ -306,7 +324,8 @@ pub(super) fn merge_and_rewrite( if is_header_phi_dst { if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Phase 33-20: Skipping Copy that would overwrite header PHI dst {:?}", remapped_dst ); @@ -354,7 +373,8 @@ pub(super) fn merge_and_rewrite( if debug { match inst { MirInstruction::BoxCall { .. } => { - eprintln!( + log!( + true, "[cf_loop/joinir] Adding BoxCall to block {:?}: {:?}", new_block_id, inst ); @@ -368,12 +388,13 @@ pub(super) fn merge_and_rewrite( // DEBUG: Print what was added to the block after first pass if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] After first pass, new_block has {} instructions", new_block.instructions.len() ); for (idx, inst) in new_block.instructions.iter().enumerate() { - eprintln!("[cf_loop/joinir] [{}] {:?}", idx, inst); + log!(true, "[cf_loop/joinir] [{}] {:?}", idx, inst); } } @@ -381,7 +402,8 @@ pub(super) fn merge_and_rewrite( // Phase 188-Impl-3: Use actual parameter ValueIds from target function if let Some((target_block, args)) = tail_call_target { if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Inserting param bindings for tail call to {:?}", target_block ); @@ -412,7 +434,8 @@ pub(super) fn merge_and_rewrite( // %phi_dst = copy %undefined ← ❌ This overwrites the PHI! if is_loop_entry_point { if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Phase 33-21: Skip param bindings in header block (PHIs define carriers)" ); } @@ -434,7 +457,8 @@ pub(super) fn merge_and_rewrite( if is_header_phi_dst { if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Phase 131-6: Skip param binding to PHI dst {:?} (PHI receives value via incoming edge)", param_val_remapped ); @@ -449,7 +473,8 @@ pub(super) fn merge_and_rewrite( }); if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Param binding: arg {:?} → param {:?}", arg_val_remapped, param_val_remapped ); @@ -479,7 +504,8 @@ pub(super) fn merge_and_rewrite( ); if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Phase 33-16: Set latch incoming for '{}': block={:?}, value={:?}", loop_var_name, new_block_id, latch_value ); @@ -497,7 +523,8 @@ pub(super) fn merge_and_rewrite( if let Some(ref loop_var) = b.loop_var_name { if &binding.carrier_name == loop_var { if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Phase 176-4: Skipping loop variable '{}' in exit_bindings (handled separately)", binding.carrier_name ); @@ -516,14 +543,16 @@ pub(super) fn merge_and_rewrite( ); if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Phase 176-4: Set latch incoming for carrier '{}': block={:?}, value={:?} (arg[{}])", binding.carrier_name, new_block_id, latch_value, carrier_arg_idx ); } carrier_arg_idx += 1; } else if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Phase 33-20 WARNING: No arg for carrier '{}' at index {}", binding.carrier_name, carrier_arg_idx ); @@ -547,7 +576,8 @@ pub(super) fn merge_and_rewrite( TailCallKind::BackEdge => { // Back edge: redirect to header block where PHIs are if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Phase 33-16: BackEdge detected, redirecting from {:?} to header {:?}", target_block, loop_header_phi_info.header_block ); @@ -557,7 +587,8 @@ pub(super) fn merge_and_rewrite( TailCallKind::LoopEntry => { // Loop entry: no redirect (entry block IS the header) if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Phase 33-16: LoopEntry detected, using direct target {:?}", target_block ); @@ -576,19 +607,20 @@ pub(super) fn merge_and_rewrite( // DEBUG: Print final state after adding parameter bindings if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] After adding param bindings, new_block has {} instructions", new_block.instructions.len() ); for (idx, inst) in new_block.instructions.iter().enumerate() { - eprintln!("[cf_loop/joinir] [{}] {:?}", idx, inst); + log!(true, "[cf_loop/joinir] [{}] {:?}", idx, inst); } } } new_block.instruction_spans = old_block.instruction_spans.clone(); if debug { - eprintln!("[cf_loop/joinir] Span sync: new_block.instructions.len()={}, old_block.instruction_spans.len()={}, new_block.instruction_spans.len()={}", + log!(true, "[cf_loop/joinir] Span sync: new_block.instructions.len()={}, old_block.instruction_spans.len()={}, new_block.instruction_spans.len()={}", new_block.instructions.len(), old_block.instruction_spans.len(), new_block.instruction_spans.len() @@ -613,7 +645,8 @@ pub(super) fn merge_and_rewrite( if let Some(_ret_val) = value { // Phase 246-EX: Check if this block has jump_args metadata if let Some(ref jump_args) = old_block.jump_args { - eprintln!( + log!( + verbose, "[DEBUG-177] Phase 246-EX: Block {:?} has jump_args metadata: {:?}", old_block.id, jump_args ); @@ -624,7 +657,8 @@ pub(super) fn merge_and_rewrite( .map(|&arg| remapper.remap_value(arg)) .collect(); - eprintln!( + log!( + verbose, "[DEBUG-177] Phase 246-EX: Remapped jump_args: {:?}", remapped_args ); @@ -643,7 +677,7 @@ pub(super) fn merge_and_rewrite( if strict_exit { return Err(msg); } else { - eprintln!("[DEBUG-177] {}", msg); + log!(verbose, "[DEBUG-177] {}", msg); } } } @@ -651,7 +685,8 @@ pub(super) fn merge_and_rewrite( // First arg is the loop variable (expr_result) if let Some(&loop_var_exit) = remapped_args.first() { exit_phi_inputs.push((new_block_id, loop_var_exit)); - eprintln!( + log!( + verbose, "[DEBUG-177] Phase 246-EX: exit_phi_inputs from jump_args[0]: ({:?}, {:?})", new_block_id, loop_var_exit ); @@ -663,7 +698,8 @@ pub(super) fn merge_and_rewrite( .entry(loop_var_name.clone()) .or_insert_with(Vec::new) .push((new_block_id, loop_var_exit)); - eprintln!( + log!( + verbose, "[DEBUG-177] Phase 246-EX-P5: Added loop_var '{}' to carrier_inputs: ({:?}, {:?})", loop_var_name, new_block_id, loop_var_exit ); @@ -685,7 +721,8 @@ pub(super) fn merge_and_rewrite( { // Phase 227: Skip ConditionOnly carriers if carrier.role == crate::mir::join_ir::lowering::carrier_info::CarrierRole::ConditionOnly { - eprintln!( + log!( + verbose, "[DEBUG-177] Phase 227: Skipping ConditionOnly carrier '{}' from exit PHI", carrier.name ); @@ -701,7 +738,8 @@ pub(super) fn merge_and_rewrite( .entry(carrier.name.clone()) .or_insert_with(Vec::new) .push((new_block_id, carrier_exit)); - eprintln!( + log!( + verbose, "[DEBUG-177] Phase 246-EX-FIX: Collecting carrier '{}': from {:?} using jump_args[{}] = {:?}", carrier.name, new_block_id, jump_args_idx, carrier_exit ); @@ -713,7 +751,8 @@ pub(super) fn merge_and_rewrite( if strict_exit { return Err(msg); } else { - eprintln!( + log!( + verbose, "[DEBUG-177] Phase 246-EX WARNING: No jump_args entry for carrier '{}' at index {}", carrier.name, jump_args_idx ); @@ -721,12 +760,16 @@ pub(super) fn merge_and_rewrite( } } } else { - eprintln!("[DEBUG-177] Phase 246-EX WARNING: No carrier_info in boundary!"); + log!( + verbose, + "[DEBUG-177] Phase 246-EX WARNING: No carrier_info in boundary!" + ); } } } else { // Fallback: Use header PHI dst (old behavior for blocks without jump_args) - eprintln!( + log!( + verbose, "[DEBUG-177] Phase 246-EX: Block {:?} has NO jump_args, using header PHI fallback", old_block.id ); @@ -738,7 +781,8 @@ pub(super) fn merge_and_rewrite( { exit_phi_inputs.push((new_block_id, phi_dst)); if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Phase 246-EX fallback: Using header PHI dst {:?} for exit (loop_var='{}')", phi_dst, loop_var_name ); @@ -850,7 +894,8 @@ pub(super) fn merge_and_rewrite( if let Some(remapped) = remapper.get_value(binding.join_value) { value_map_for_injector.insert(binding.join_value, remapped); if debug { - eprintln!( + log!( + true, "[cf_loop/joinir] Phase 171-fix: Condition binding '{}': JoinIR {:?} → remapped {:?} (HOST {:?})", binding.name, binding.join_value, remapped, binding.host_value ); diff --git a/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs b/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs index e046a4ff..a420097b 100644 --- a/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs +++ b/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs @@ -28,6 +28,7 @@ //! (instruction_rewriter). use super::loop_header_phi_info::{CarrierPhiEntry, LoopHeaderPhiInfo}; +use super::super::trace; use crate::mir::{BasicBlockId, MirInstruction, ValueId}; /// Builder for loop header PHIs @@ -73,14 +74,21 @@ impl LoopHeaderPhiBuilder { expr_result_is_loop_var: bool, debug: bool, ) -> Result { + let trace = trace::trace(); if debug { - eprintln!( - "[cf_loop/joinir] Phase 33-16: Building header PHIs at {:?}", - header_block + trace.stderr_if( + &format!( + "[cf_loop/joinir] Phase 33-16: Building header PHIs at {:?}", + header_block + ), + true, ); - eprintln!( - "[cf_loop/joinir] Loop var '{}' init={:?}, entry_block={:?}", - loop_var_name, loop_var_init, entry_block + trace.stderr_if( + &format!( + "[cf_loop/joinir] Loop var '{}' init={:?}, entry_block={:?}", + loop_var_name, loop_var_init, entry_block + ), + true, ); } @@ -101,14 +109,15 @@ impl LoopHeaderPhiBuilder { .value_types .insert(loop_var_phi_dst, init_type.clone()); - if debug || std::env::var("NYASH_CARRIER_PHI_DEBUG").ok().as_deref() == Some("1") { - eprintln!( + trace.stderr_if( + &format!( "[carrier/phi] Loop var '{}': dst=%{} entry_type={:?} (backedge ignored)", loop_var_name, loop_var_phi_dst.as_u32(), init_type - ); - } + ), + debug || std::env::var("NYASH_CARRIER_PHI_DEBUG").ok().as_deref() == Some("1"), + ); } info.carrier_phis.insert( @@ -124,9 +133,12 @@ impl LoopHeaderPhiBuilder { info.carrier_order.push(loop_var_name.to_string()); if debug { - eprintln!( - "[cf_loop/joinir] Loop var PHI: {:?} = phi [(from {:?}, {:?}), (latch TBD)]", - loop_var_phi_dst, entry_block, loop_var_init + trace.stderr_if( + &format!( + "[cf_loop/joinir] Loop var PHI: {:?} = phi [(from {:?}, {:?}), (latch TBD)]", + loop_var_phi_dst, entry_block, loop_var_init + ), + true, ); } @@ -150,14 +162,15 @@ impl LoopHeaderPhiBuilder { .value_types .insert(phi_dst, init_type.clone()); - if debug || std::env::var("NYASH_CARRIER_PHI_DEBUG").ok().as_deref() == Some("1") { - eprintln!( + trace.stderr_if( + &format!( "[carrier/phi] Carrier '{}': dst=%{} entry_type={:?} (backedge ignored)", name, phi_dst.as_u32(), init_type - ); - } + ), + debug || std::env::var("NYASH_CARRIER_PHI_DEBUG").ok().as_deref() == Some("1"), + ); } info.carrier_phis.insert( @@ -173,9 +186,12 @@ impl LoopHeaderPhiBuilder { info.carrier_order.push(name.clone()); if debug { - eprintln!( - "[cf_loop/joinir] Carrier '{}' PHI: {:?} = phi [(from {:?}, {:?}), (latch TBD)], role={:?}", - name, phi_dst, entry_block, init_value, role + trace.stderr_if( + &format!( + "[cf_loop/joinir] Carrier '{}' PHI: {:?} = phi [(from {:?}, {:?}), (latch TBD)], role={:?}", + name, phi_dst, entry_block, init_value, role + ), + true, ); } } @@ -184,9 +200,12 @@ impl LoopHeaderPhiBuilder { if expr_result_is_loop_var { info.expr_result_phi = Some(loop_var_phi_dst); if debug { - eprintln!( - "[cf_loop/joinir] expr_result = {:?} (loop var PHI)", - loop_var_phi_dst + trace.stderr_if( + &format!( + "[cf_loop/joinir] expr_result = {:?} (loop var PHI)", + loop_var_phi_dst + ), + true, ); } } @@ -208,11 +227,15 @@ impl LoopHeaderPhiBuilder { info: &LoopHeaderPhiInfo, debug: bool, ) -> Result<(), String> { + let trace = trace::trace(); let dev_debug = debug || crate::config::env::joinir_dev_enabled(); if debug { - eprintln!( - "[cf_loop/joinir] Phase 33-16: Finalizing header PHIs at {:?}", - info.header_block + trace.stderr_if( + &format!( + "[cf_loop/joinir] Phase 33-16: Finalizing header PHIs at {:?}", + info.header_block + ), + true, ); } @@ -260,10 +283,13 @@ impl LoopHeaderPhiBuilder { phi_instructions.push(phi); if dev_debug { - eprintln!( - "[joinir/header-phi] Finalized carrier '{}' PHI: {:?} = phi [({:?}, {:?}), ({:?}, {:?})]", - name, entry.phi_dst, entry_block, entry_val, latch_block, latch_val - ); + trace.stderr_if( + &format!( + "[joinir/header-phi] Finalized carrier '{}' PHI: {:?} = phi [({:?}, {:?}), ({:?}, {:?})]", + name, entry.phi_dst, entry_block, entry_val, latch_block, latch_val + ), + true, + ); } } @@ -280,9 +306,12 @@ impl LoopHeaderPhiBuilder { header_block.instruction_spans = new_spans; if dev_debug { - eprintln!( - "[joinir/header-phi] Header block now has {} instructions", - header_block.instructions.len() + trace.stderr_if( + &format!( + "[joinir/header-phi] Header block now has {} instructions", + header_block.instructions.len() + ), + true, ); } diff --git a/src/mir/builder/control_flow/joinir/merge/mod.rs b/src/mir/builder/control_flow/joinir/merge/mod.rs index 9f9922dc..d928b65d 100644 --- a/src/mir/builder/control_flow/joinir/merge/mod.rs +++ b/src/mir/builder/control_flow/joinir/merge/mod.rs @@ -30,6 +30,7 @@ mod value_collector; pub use loop_header_phi_builder::LoopHeaderPhiBuilder; pub use loop_header_phi_info::LoopHeaderPhiInfo; +use super::trace; use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary; use crate::mir::{MirModule, ValueId}; @@ -77,13 +78,15 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( debug: bool, ) -> Result, String> { let verbose = debug || crate::config::env::joinir_dev_enabled(); + let trace = trace::trace(); - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] merge_joinir_mir_blocks called with {} functions", mir_module.functions.len() - ); - } + ), + debug, + ); if verbose { if let Some(boundary) = boundary { @@ -109,31 +112,43 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( }) .collect(); - eprintln!( - "[cf_loop/joinir] Boundary join_inputs={:?} host_inputs={:?}", - boundary.join_inputs, boundary.host_inputs + trace.stderr_if( + &format!( + "[cf_loop/joinir] Boundary join_inputs={:?} host_inputs={:?}", + boundary.join_inputs, boundary.host_inputs + ), + true, ); - eprintln!( - "[cf_loop/joinir] Boundary exit_bindings ({}): {}", - boundary.exit_bindings.len(), - exit_summary.join(", ") + trace.stderr_if( + &format!( + "[cf_loop/joinir] Boundary exit_bindings ({}): {}", + boundary.exit_bindings.len(), + exit_summary.join(", ") + ), + true, ); if !cond_summary.is_empty() { - eprintln!( - "[cf_loop/joinir] Boundary condition_bindings ({}): {}", - cond_summary.len(), - cond_summary.join(", ") + trace.stderr_if( + &format!( + "[cf_loop/joinir] Boundary condition_bindings ({}): {}", + cond_summary.len(), + cond_summary.join(", ") + ), + true, ); } if let Some(ci) = &boundary.carrier_info { let carriers: Vec = ci.carriers.iter().map(|c| c.name.clone()).collect(); - eprintln!( - "[cf_loop/joinir] Boundary carrier_info: loop_var='{}', carriers={:?}", - ci.loop_var_name, carriers + trace.stderr_if( + &format!( + "[cf_loop/joinir] Boundary carrier_info: loop_var='{}', carriers={:?}", + ci.loop_var_name, carriers + ), + true, ); } } else { - eprintln!("[cf_loop/joinir] No boundary provided"); + trace.stderr_if("[cf_loop/joinir] No boundary provided", true); } } @@ -149,23 +164,25 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // Phase 171-fix: Add condition_bindings' join_values to used_values for remapping if let Some(boundary) = boundary { for binding in &boundary.condition_bindings { - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 171-fix: Adding condition binding '{}' JoinIR {:?} to used_values", binding.name, binding.join_value - ); - } + ), + debug, + ); used_values.insert(binding.join_value); } // Phase 172-3: Add exit_bindings' join_exit_values to used_values for remapping for binding in &boundary.exit_bindings { - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 172-3: Adding exit binding '{}' JoinIR {:?} to used_values", binding.carrier_name, binding.join_exit_value - ); - } + ), + debug, + ); used_values.insert(binding.join_exit_value); } } @@ -237,20 +254,24 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( .collect() }; - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 201-A: Pre-building header PHIs for loop_var='{}' at {:?}", loop_var_name, entry_block_remapped - ); - eprintln!( + ), + debug, + ); + trace.stderr_if( + &format!( "[cf_loop/joinir] loop_var_init={:?}, carriers={:?}", loop_var_init, other_carriers .iter() .map(|(n, _, _, _)| n.as_str()) .collect::>() - ); - } + ), + debug, + ); // Build PHI info (this allocates PHI dst ValueIds) LoopHeaderPhiBuilder::build( @@ -286,22 +307,24 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // Phase 201-A: Get reserved PHI dst ValueIds and set in MirBuilder let reserved_phi_dsts = loop_header_phi_info.reserved_value_ids(); - if debug && !reserved_phi_dsts.is_empty() { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 201-A: Reserved PHI dsts: {:?}", reserved_phi_dsts - ); - } + ), + debug && !reserved_phi_dsts.is_empty(), + ); // Phase 201-A: Set reserved IDs in MirBuilder so next_value_id() skips them // This protects against carrier corruption when break conditions emit Const instructions builder.comp_ctx.reserved_value_ids = reserved_phi_dsts.clone(); - if debug && !builder.comp_ctx.reserved_value_ids.is_empty() { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 201-A: Set builder.comp_ctx.reserved_value_ids = {:?}", builder.comp_ctx.reserved_value_ids - ); - } + ), + debug && !builder.comp_ctx.reserved_value_ids.is_empty(), + ); // Phase 3: Remap ValueIds (with reserved PHI dsts protection) remap_values( @@ -313,28 +336,40 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( )?; // Phase 177-3 DEBUG: Verify remapper state after Phase 3 - eprintln!("[DEBUG-177] === Remapper state after Phase 3 ==="); - eprintln!("[DEBUG-177] used_values count: {}", used_values.len()); + trace.stderr_if("[DEBUG-177] === Remapper state after Phase 3 ===", verbose); + trace.stderr_if( + &format!("[DEBUG-177] used_values count: {}", used_values.len()), + verbose, + ); for value_id in &used_values { if let Some(remapped) = remapper.get_value(*value_id) { - eprintln!("[DEBUG-177] JoinIR {:?} → Host {:?}", value_id, remapped); + trace.stderr_if( + &format!("[DEBUG-177] JoinIR {:?} → Host {:?}", value_id, remapped), + verbose, + ); } else { - eprintln!("[DEBUG-177] JoinIR {:?} → NOT FOUND ❌", value_id); + trace.stderr_if( + &format!("[DEBUG-177] JoinIR {:?} → NOT FOUND ❌", value_id), + verbose, + ); } } // Check condition_bindings specifically if let Some(boundary) = boundary { - eprintln!("[DEBUG-177] === Condition bindings check ==="); + trace.stderr_if("[DEBUG-177] === Condition bindings check ===", verbose); for binding in &boundary.condition_bindings { let lookup_result = remapper.get_value(binding.join_value); - eprintln!( - "[DEBUG-177] '{}': JoinIR {:?} → {:?}", - binding.name, binding.join_value, lookup_result + trace.stderr_if( + &format!( + "[DEBUG-177] '{}': JoinIR {:?} → {:?}", + binding.name, binding.join_value, lookup_result + ), + verbose, ); } } - eprintln!("[DEBUG-177] =============================="); + trace.stderr_if("[DEBUG-177] ==============================", verbose); // Phase 3.5: Override remapper for function parameters to use PHI dsts // @@ -390,15 +425,21 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( .collect(); if !condition_binding_ids.is_empty() { - eprintln!( - "[cf_loop/joinir] Phase 177-3: Protected ValueIds (condition-only, not carriers): {:?}", - condition_binding_ids + trace.stderr_if( + &format!( + "[cf_loop/joinir] Phase 177-3: Protected ValueIds (condition-only, not carriers): {:?}", + condition_binding_ids + ), + verbose, ); for cb in &boundary.condition_bindings { let is_carrier = carrier_names.contains(cb.name.as_str()); - eprintln!( - "[cf_loop/joinir] Phase 177-3: '{}': JoinIR {:?} (carrier={})", - cb.name, cb.join_value, is_carrier + trace.stderr_if( + &format!( + "[cf_loop/joinir] Phase 177-3: '{}': JoinIR {:?} (carrier={})", + cb.name, cb.join_value, is_carrier + ), + verbose, ); } } @@ -407,25 +448,34 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( let loop_step_func_name = "join_func_1"; if function_params.get(main_func_name).is_none() { - eprintln!( - "[cf_loop/joinir] WARNING: function_params.get('{}') returned None. Available keys: {:?}", - main_func_name, - function_params.keys().collect::>() + trace.stderr_if( + &format!( + "[cf_loop/joinir] WARNING: function_params.get('{}') returned None. Available keys: {:?}", + main_func_name, + function_params.keys().collect::>() + ), + verbose, ); } if let Some(main_params) = function_params.get(main_func_name) { - eprintln!( - "[DEBUG-177] Phase 33-21: main ({}) params: {:?}", - main_func_name, main_params + trace.stderr_if( + &format!( + "[DEBUG-177] Phase 33-21: main ({}) params: {:?}", + main_func_name, main_params + ), + verbose, ); - eprintln!( - "[DEBUG-177] Phase 33-21: carrier_phis count: {}, names: {:?}", - loop_header_phi_info.carrier_phis.len(), - loop_header_phi_info - .carrier_phis - .iter() - .map(|(n, _)| n.as_str()) - .collect::>() + trace.stderr_if( + &format!( + "[DEBUG-177] Phase 33-21: carrier_phis count: {}, names: {:?}", + loop_header_phi_info.carrier_phis.len(), + loop_header_phi_info + .carrier_phis + .iter() + .map(|(n, _)| n.as_str()) + .collect::>() + ), + verbose, ); // Map main's parameters to header PHI dsts // main params: [i_init, carrier1_init, ...] @@ -436,15 +486,21 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( if let Some(&main_param) = main_params.get(idx) { // Phase 177-3: Don't override condition_bindings if condition_binding_ids.contains(&main_param) { - eprintln!( - "[cf_loop/joinir] Phase 177-3: Skipping override for condition_binding {:?} ('{}')", - main_param, carrier_name + trace.stderr_if( + &format!( + "[cf_loop/joinir] Phase 177-3: Skipping override for condition_binding {:?} ('{}')", + main_param, carrier_name + ), + verbose, ); continue; } - eprintln!( - "[DEBUG-177] Phase 33-21: REMAP main param[{}] {:?} → {:?} ('{}')", - idx, main_param, entry.phi_dst, carrier_name + trace.stderr_if( + &format!( + "[DEBUG-177] Phase 33-21: REMAP main param[{}] {:?} → {:?} ('{}')", + idx, main_param, entry.phi_dst, carrier_name + ), + verbose, ); remapper.set_value(main_param, entry.phi_dst); } @@ -466,9 +522,12 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( continue; } // This is a body-only carrier - remap it to PHI dst - eprintln!( - "[cf_loop/joinir] Phase 177-3-B: Body-only carrier '{}': JoinIR {:?} → PHI {:?}", - carrier_name, binding.join_value, entry.phi_dst + trace.stderr_if( + &format!( + "[cf_loop/joinir] Phase 177-3-B: Body-only carrier '{}': JoinIR {:?} → PHI {:?}", + carrier_name, binding.join_value, entry.phi_dst + ), + verbose, ); remapper.set_value(binding.join_value, entry.phi_dst); } @@ -476,22 +535,31 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // Map loop_step's parameters // DEBUG-177: Always log function_params keys to diagnose multi-carrier issue - eprintln!( - "[DEBUG-177] Phase 33-21: function_params keys: {:?}", - function_params.keys().collect::>() + trace.stderr_if( + &format!( + "[DEBUG-177] Phase 33-21: function_params keys: {:?}", + function_params.keys().collect::>() + ), + verbose, ); if function_params.get(loop_step_func_name).is_none() { - eprintln!( - "[cf_loop/joinir] WARNING: function_params.get('{}') returned None. Available keys: {:?}", - loop_step_func_name, - function_params.keys().collect::>() + trace.stderr_if( + &format!( + "[cf_loop/joinir] WARNING: function_params.get('{}') returned None. Available keys: {:?}", + loop_step_func_name, + function_params.keys().collect::>() + ), + verbose, ); } if let Some(loop_step_params) = function_params.get(loop_step_func_name) { // DEBUG-177: Always log loop_step params - eprintln!( - "[DEBUG-177] Phase 33-21: loop_step ({}) params: {:?}", - loop_step_func_name, loop_step_params + trace.stderr_if( + &format!( + "[DEBUG-177] Phase 33-21: loop_step ({}) params: {:?}", + loop_step_func_name, loop_step_params + ), + verbose, ); // Phase 177-FIX: Process loop_step params but skip if already mapped // @@ -501,9 +569,12 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( for loop_step_param in loop_step_params { // Phase 177-3: Don't override condition_bindings if condition_binding_ids.contains(loop_step_param) { - eprintln!( - "[DEBUG-177] Phase 177-FIX: Skipping condition_binding {:?}", - loop_step_param + trace.stderr_if( + &format!( + "[DEBUG-177] Phase 177-FIX: Skipping condition_binding {:?}", + loop_step_param + ), + verbose, ); continue; } @@ -517,9 +588,12 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( .any(|(name, _)| name == &cb.name) }); if already_mapped { - eprintln!( - "[DEBUG-177] Phase 177-FIX: Skipping {:?} (already mapped by Phase 177-3-B)", - loop_step_param + trace.stderr_if( + &format!( + "[DEBUG-177] Phase 177-FIX: Skipping {:?} (already mapped by Phase 177-3-B)", + loop_step_param + ), + verbose, ); continue; } @@ -537,9 +611,12 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( loop_header_phi_info.get_carrier_at_index(param_idx), loop_header_phi_info.get_entry_at_index(param_idx), ) { - eprintln!( - "[DEBUG-177] Phase 177-STRUCT-2: REMAP loop_step param[{}] {:?} → {:?} (carrier '{}')", - param_idx, loop_step_param, entry.phi_dst, carrier_name + trace.stderr_if( + &format!( + "[DEBUG-177] Phase 177-STRUCT-2: REMAP loop_step param[{}] {:?} → {:?} (carrier '{}')", + param_idx, loop_step_param, entry.phi_dst, carrier_name + ), + verbose, ); remapper.set_value(*loop_step_param, entry.phi_dst); } @@ -556,15 +633,17 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // Phase 177-3: Don't override condition_bindings if !condition_binding_ids.contains(&ValueId(0)) { remapper.set_value(ValueId(0), phi_dst); - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 33-16 fallback: Override remap ValueId(0) → {:?} (PHI dst)", phi_dst - ); - } + ), + debug, + ); } else { - eprintln!( - "[cf_loop/joinir] Phase 177-3 fallback: Skipping override for condition_binding ValueId(0)" + trace.stderr_if( + "[cf_loop/joinir] Phase 177-3 fallback: Skipping override for condition_binding ValueId(0)", + verbose, ); } } @@ -581,28 +660,35 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // Phase 177-3: Don't override condition_bindings if !condition_binding_ids.contains(&join_value_id) { remapper.set_value(join_value_id, entry.phi_dst); - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 33-20 fallback: Override remap {:?} → {:?} (carrier '{}' PHI dst)", join_value_id, entry.phi_dst, carrier_name - ); - } + ), + debug, + ); } else { - eprintln!( - "[cf_loop/joinir] Phase 177-3 fallback: Skipping override for condition_binding {:?} ('{}')", - join_value_id, carrier_name + trace.stderr_if( + &format!( + "[cf_loop/joinir] Phase 177-3 fallback: Skipping override for condition_binding {:?} ('{}')", + join_value_id, carrier_name + ), + verbose, ); } } } // Phase 177-3 DEBUG: Check remapper after Phase 33-21 overrides - eprintln!("[DEBUG-177] === Remapper state after Phase 33-21 ==="); + trace.stderr_if("[DEBUG-177] === Remapper state after Phase 33-21 ===", verbose); for binding in &boundary.condition_bindings { let lookup_result = remapper.get_value(binding.join_value); - eprintln!( - "[DEBUG-177] '{}': JoinIR {:?} → {:?} (after 33-21)", - binding.name, binding.join_value, lookup_result + trace.stderr_if( + &format!( + "[DEBUG-177] '{}': JoinIR {:?} → {:?} (after 33-21)", + binding.name, binding.join_value, lookup_result + ), + verbose, ); } @@ -630,12 +716,13 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // By now, instruction_rewriter has set latch_incoming for all carriers. // We can finalize the PHIs and insert them into the header block. if !loop_header_phi_info.carrier_phis.is_empty() { - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 4.5: Finalizing {} header PHIs", loop_header_phi_info.carrier_phis.len() - ); - } + ), + debug, + ); LoopHeaderPhiBuilder::finalize(builder, &loop_header_phi_info, debug)?; } @@ -673,12 +760,16 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // The exit PHI correctly merges values from both exit paths, giving us the final result. let carrier_phis = &exit_carrier_phis; - if debug && !carrier_phis.is_empty() { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 246-EX: Using EXIT PHI dsts for variable_map (not header): {:?}", - carrier_phis.iter().map(|(n, v)| (n.as_str(), v)).collect::>() - ); - } + carrier_phis + .iter() + .map(|(n, v)| (n.as_str(), v)) + .collect::>() + ), + debug && !carrier_phis.is_empty(), + ); // Phase 6: Reconnect boundary (if specified) // Phase 197-B: Pass remapper to enable per-carrier exit value lookup @@ -694,40 +785,49 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // The header_block in loop_header_phi_info is the remapped entry block let entry_block = loop_header_phi_info.header_block; - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Entry block (from loop_header_phi_info): {:?}", entry_block - ); - eprintln!( + ), + debug, + ); + trace.stderr_if( + &format!( "[cf_loop/joinir] Current block before emit_jump: {:?}", builder.current_block - ); - eprintln!( + ), + debug, + ); + trace.stderr_if( + &format!( "[cf_loop/joinir] Jumping to entry block: {:?}", entry_block - ); - } + ), + debug, + ); crate::mir::builder::emission::branch::emit_jump(builder, entry_block)?; - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] After emit_jump, current_block: {:?}", builder.current_block - ); - } + ), + debug, + ); // Switch to exit block for subsequent code builder.start_new_block(exit_block_id)?; - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 189: Merge complete: {} functions merged, continuing from {:?}", mir_module.functions.len(), exit_block_id - ); - } + ), + debug, + ); // Phase 200-3: Verify JoinIR contracts (debug only) #[cfg(debug_assertions)] @@ -742,21 +842,23 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( boundary, ); } - if debug { - eprintln!("[cf_loop/joinir] Phase 200-3: Contract verification passed"); - } + trace.stderr_if( + "[cf_loop/joinir] Phase 200-3: Contract verification passed", + debug, + ); } } // Phase 201-A: Clear reserved ValueIds after merge completes // Future loops will set their own reserved IDs if !builder.comp_ctx.reserved_value_ids.is_empty() { - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 201-A: Clearing reserved_value_ids (was {:?})", builder.comp_ctx.reserved_value_ids - ); - } + ), + debug, + ); builder.comp_ctx.reserved_value_ids.clear(); } @@ -777,12 +879,13 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( if let Some(binding) = loop_var_binding { if binding.join_exit_value == expr_result_id { // expr_result is the loop variable! Use exit_phi_result_id - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 246-EX-FIX: expr_result {:?} is loop variable '{}', using exit_phi_result_id {:?}", expr_result_id, loop_var_name, exit_phi_result_id - ); - } + ), + debug, + ); exit_phi_result_id } else { // expr_result is not the loop variable, resolve as carrier @@ -823,21 +926,23 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks( // Return expr_result if present, otherwise fall back to exit_phi_result_id if let Some(resolved) = expr_result_value { - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 246-EX-FIX: Returning expr_result_value {:?}", resolved - ); - } + ), + debug, + ); Ok(Some(resolved)) } else { // Fallback: return exit_phi_result_id (for legacy patterns or carrier-only loops) - if debug && exit_phi_result_id.is_some() { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 221-R: Returning exit_phi_result_id (fallback): {:?}", exit_phi_result_id - ); - } + ), + debug && exit_phi_result_id.is_some(), + ); Ok(exit_phi_result_id) } } @@ -854,13 +959,15 @@ fn remap_values( reserved_ids: &std::collections::HashSet, debug: bool, ) -> Result<(), String> { - if debug { - eprintln!( + let trace = trace::trace(); + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 3: Remapping {} ValueIds (reserved: {})", used_values.len(), reserved_ids.len() - ); - } + ), + debug, + ); for old_value in used_values { // Phase 201-A: Allocate new ValueId, skipping reserved PHI dsts @@ -870,21 +977,23 @@ fn remap_values( break candidate; } // Skip reserved ID - will try next one - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Phase 201-A: Skipping reserved PHI dst {:?}", candidate - ); - } + ), + debug, + ); }; remapper.set_value(*old_value, new_value); - if debug { - eprintln!( + trace.stderr_if( + &format!( "[cf_loop/joinir] Value remap: {:?} → {:?}", old_value, new_value - ); - } + ), + debug, + ); } Ok(()) diff --git a/src/mir/builder/control_flow/joinir/merge/value_collector.rs b/src/mir/builder/control_flow/joinir/merge/value_collector.rs index 35c2bb9d..583466da 100644 --- a/src/mir/builder/control_flow/joinir/merge/value_collector.rs +++ b/src/mir/builder/control_flow/joinir/merge/value_collector.rs @@ -27,8 +27,12 @@ pub(super) fn collect_values( ), String, > { + let trace = crate::mir::builder::control_flow::joinir::trace::trace(); if debug { - eprintln!("[cf_loop/joinir] Phase 189: Collecting value IDs from all functions"); + trace.stderr_if( + "[cf_loop/joinir] Phase 189: Collecting value IDs from all functions", + true, + ); } let mut used_values: BTreeSet = BTreeSet::new(); @@ -63,9 +67,12 @@ pub(super) fn collect_values( // Without this, subsequent instructions referencing dst will fail used_values.insert(*dst); if debug { - eprintln!( - "[cf_loop/joinir] Found function name constant: {:?} = '{}'", - dst, s + trace.stderr_if( + &format!( + "[cf_loop/joinir] Found function name constant: {:?} = '{}'", + dst, s + ), + true, ); } } @@ -95,9 +102,12 @@ pub(super) fn collect_values( } if debug { - eprintln!( - "[cf_loop/joinir] Collected {} unique values", - used_values.len() + trace.stderr_if( + &format!( + "[cf_loop/joinir] Collected {} unique values", + used_values.len() + ), + true, ); } diff --git a/src/mir/builder/control_flow/joinir/parity_checker.rs b/src/mir/builder/control_flow/joinir/parity_checker.rs index 37ef10be..86833910 100644 --- a/src/mir/builder/control_flow/joinir/parity_checker.rs +++ b/src/mir/builder/control_flow/joinir/parity_checker.rs @@ -62,25 +62,30 @@ impl MirBuilder { Err(msg) } else { // Debug mode: log only - eprintln!("{}", msg); + super::trace::trace().dev("loop_canonicalizer/parity", &msg); Ok(()) } } else { // Patterns match - success! - eprintln!( - "[loop_canonicalizer/PARITY] OK in function '{}': \ - canonical and actual agree on {:?}", - func_name, canonical_pattern + super::trace::trace().dev( + "loop_canonicalizer/parity", + &format!( + "[loop_canonicalizer/PARITY] OK in function '{}': canonical and actual agree on {:?}", + func_name, canonical_pattern + ), ); Ok(()) } } else { // Canonicalizer failed (Fail-Fast) // Log but don't error - router might still handle it - eprintln!( - "[loop_canonicalizer/PARITY] Canonicalizer failed for '{}': {}", - func_name, - decision.notes.join("; ") + super::trace::trace().dev( + "loop_canonicalizer/parity", + &format!( + "[loop_canonicalizer/PARITY] Canonicalizer failed for '{}': {}", + func_name, + decision.notes.join("; ") + ), ); Ok(()) }; diff --git a/src/mir/builder/control_flow/joinir/patterns/body_local_policy.rs b/src/mir/builder/control_flow/joinir/patterns/body_local_policy.rs new file mode 100644 index 00000000..7c64ef80 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/patterns/body_local_policy.rs @@ -0,0 +1,92 @@ +//! Phase 92 P3: BodyLocal policy routing (Box) +//! +//! Purpose: make the "promotion vs read-only slot vs reject" decision explicit, +//! so Pattern2 code does not look like it "falls back" after failure. + +use crate::ast::ASTNode; +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; +use crate::mir::join_ir::lowering::common::body_local_slot::{ + ReadOnlyBodyLocalSlot, ReadOnlyBodyLocalSlotBox, +}; +use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; +use crate::mir::loop_pattern_detection::loop_body_cond_promoter::{ + ConditionPromotionRequest, ConditionPromotionResult, LoopBodyCondPromoter, +}; +use crate::mir::loop_pattern_detection::loop_condition_scope::{CondVarScope, LoopConditionScope}; + +/// Explicit routing policy for LoopBodyLocal variables used in Pattern2 conditions. +/// +/// This is a "route" decision (not a fallback): we choose exactly one of the supported +/// strategies and reject otherwise. +pub enum BodyLocalPolicyDecision { + UsePromotion { + promoted_carrier: CarrierInfo, + promoted_var: String, + carrier_name: String, + }, + UseReadOnlySlot(ReadOnlyBodyLocalSlot), + Reject { reason: String, vars: Vec }, +} + +pub fn classify_for_pattern2( + _builder: &MirBuilder, + loop_var_name: &str, + scope: &LoopScopeShape, + break_condition_node: &ASTNode, + cond_scope: &LoopConditionScope, + body: &[ASTNode], +) -> BodyLocalPolicyDecision { + let vars: Vec = cond_scope + .vars + .iter() + .filter(|v| v.scope == CondVarScope::LoopBodyLocal) + .map(|v| v.name.clone()) + .collect(); + + let promotion_req = ConditionPromotionRequest { + loop_param_name: loop_var_name, + cond_scope, + scope_shape: Some(scope), + break_cond: Some(break_condition_node), + continue_cond: None, + loop_body: body, + #[cfg(feature = "normalized_dev")] + binding_map: Some(&_builder.binding_map), + }; + + match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) { + ConditionPromotionResult::Promoted { + carrier_info: promoted_carrier, + promoted_var, + carrier_name, + } => BodyLocalPolicyDecision::UsePromotion { + promoted_carrier, + promoted_var, + carrier_name, + }, + ConditionPromotionResult::CannotPromote { reason, .. } => { + match extract_body_local_inits_for_conditions(&vars, body) { + Ok(Some(slot)) => BodyLocalPolicyDecision::UseReadOnlySlot(slot), + Ok(None) => BodyLocalPolicyDecision::Reject { reason, vars }, + Err(slot_err) => BodyLocalPolicyDecision::Reject { + reason: format!("{reason}; read-only-slot rejected: {slot_err}"), + vars, + }, + } + } + } +} + +fn extract_body_local_inits_for_conditions( + body_local_names_in_conditions: &[String], + body: &[ASTNode], +) -> Result, String> { + if body_local_names_in_conditions.is_empty() { + return Ok(None); + } + Ok(Some(ReadOnlyBodyLocalSlotBox::extract_single( + body_local_names_in_conditions, + body, + )?)) +} diff --git a/src/mir/builder/control_flow/joinir/patterns/common_init.rs b/src/mir/builder/control_flow/joinir/patterns/common_init.rs index 654978ea..a2b72bbd 100644 --- a/src/mir/builder/control_flow/joinir/patterns/common_init.rs +++ b/src/mir/builder/control_flow/joinir/patterns/common_init.rs @@ -200,7 +200,10 @@ impl CommonPatternInitializer { } UpdateRhs::Other => { // Phase 188: Complex update (method call, nested BinOp) - reject - eprintln!("[common_init/check_carriers] Phase 188: Complex update detected (UpdateRhs::Other), rejecting pattern"); + crate::mir::builder::control_flow::joinir::trace::trace().dev( + "common_init/check_carriers", + "Phase 188: Complex update detected (UpdateRhs::Other), rejecting pattern", + ); return false; } } diff --git a/src/mir/builder/control_flow/joinir/patterns/condition_env_builder.rs b/src/mir/builder/control_flow/joinir/patterns/condition_env_builder.rs index 0f587cbc..bbaf0897 100644 --- a/src/mir/builder/control_flow/joinir/patterns/condition_env_builder.rs +++ b/src/mir/builder/control_flow/joinir/patterns/condition_env_builder.rs @@ -175,16 +175,14 @@ impl ConditionEnvBuilder { variable_map: &BTreeMap, space: &mut JoinValueSpace, ) -> (ConditionEnv, ValueId) { - use std::env; - - let debug = env::var("NYASH_CAPTURE_DEBUG").is_ok(); - - if debug { - eprintln!( - "[capture/env_builder] Building ConditionEnv with {} captured vars", + let trace = crate::mir::builder::control_flow::joinir::trace::trace(); + trace.capture( + "env_builder", + &format!( + "Building ConditionEnv with {} captured vars", captured.vars.len() - ); - } + ), + ); // Step 1: Build base ConditionEnv with loop params using v2 API (Phase 222.5-B) let (mut env, loop_var_join_id) = Self::build_loop_param_only_v2(loop_var_name, space); @@ -195,9 +193,13 @@ impl ConditionEnvBuilder { let host_id = match variable_map.get(&var.name) { Some(&id) => id, None => { - if debug { - eprintln!("[capture/env_builder] WARNING: Captured var '{}' not found in variable_map, skipping", var.name); - } + trace.capture( + "env_builder", + &format!( + "WARNING: Captured var '{}' not found in variable_map, skipping", + var.name + ), + ); continue; } }; @@ -217,12 +219,13 @@ impl ConditionEnvBuilder { // 2d: Add to ConditionEnv.captured map env.captured.insert(var.name.clone(), join_id); - if debug { - eprintln!( - "[capture/env_builder] Added captured var '{}': host={:?}, join={:?}", + trace.capture( + "env_builder", + &format!( + "Added captured var '{}': host={:?}, join={:?}", var.name, host_id, join_id - ); - } + ), + ); } // Step 3: Debug guard - Condition params must NOT be in PHI candidates @@ -236,14 +239,15 @@ impl ConditionEnvBuilder { } } - if debug { - let param_count = env.iter().count(); - eprintln!( - "[capture/env_builder] Final ConditionEnv: {} params, {} captured", + let param_count = env.iter().count(); + trace.capture( + "env_builder", + &format!( + "Final ConditionEnv: {} params, {} captured", param_count, env.captured.len() - ); - } + ), + ); (env, loop_var_join_id) } diff --git a/src/mir/builder/control_flow/joinir/patterns/mod.rs b/src/mir/builder/control_flow/joinir/patterns/mod.rs index 36b3f6ab..6889848c 100644 --- a/src/mir/builder/control_flow/joinir/patterns/mod.rs +++ b/src/mir/builder/control_flow/joinir/patterns/mod.rs @@ -46,6 +46,7 @@ //! - Extracted from ast_feature_extractor for improved modularity pub(in crate::mir::builder) mod ast_feature_extractor; +pub(in crate::mir::builder) mod body_local_policy; // Phase 92 P3: promotion vs slot routing pub(in crate::mir::builder) mod escape_pattern_recognizer; // Phase 91 P5b pub(in crate::mir::builder) mod common_init; pub(in crate::mir::builder) mod condition_env_builder; diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs index 924fc994..995c538b 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs @@ -5,18 +5,36 @@ use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierInit}; use crate::mir::join_ir::lowering::condition_env::{ConditionBinding, ConditionEnv}; +use super::body_local_policy::{classify_for_pattern2, BodyLocalPolicyDecision}; +use crate::mir::join_ir::lowering::common::body_local_slot::ReadOnlyBodyLocalSlot; +use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox; use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; +use crate::mir::join_ir::lowering::loop_with_break_minimal::LoopWithBreakLoweringInputs; use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr; use crate::mir::loop_pattern_detection::error_messages; use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv; use crate::mir::ValueId; use std::collections::BTreeMap; -fn log_pattern2(verbose: bool, tag: &str, message: impl AsRef) { - if verbose { - eprintln!("[joinir/pattern2/{tag}] {}", message.as_ref()); +struct Pattern2DebugLog { + verbose: bool, + debug: DebugOutputBox, +} + +impl Pattern2DebugLog { + fn new(verbose: bool) -> Self { + Self { + verbose, + debug: DebugOutputBox::new_with_enabled("joinir/pattern2", verbose), + } + } + + fn log(&self, tag: &str, message: impl AsRef) { + if self.verbose { + self.debug.log(tag, message.as_ref()); + } } } @@ -30,6 +48,11 @@ struct Pattern2Inputs { env: ConditionEnv, condition_bindings: Vec, body_local_env: LoopBodyLocalEnv, + /// Phase 92 P3: Allow-list of LoopBodyLocal variable names permitted in conditions. + /// This must stay minimal (1 variable) and is validated by ReadOnlyBodyLocalSlotBox. + allowed_body_locals_for_conditions: Vec, + /// Phase 92 P3: Diagnostics / debug metadata for the allow-listed variable. + read_only_body_local_slot: Option, break_condition_node: ASTNode, } @@ -41,6 +64,7 @@ fn prepare_pattern2_inputs( ctx: &super::pattern_pipeline::PatternPipelineContext, verbose: bool, ) -> Result { + let log = Pattern2DebugLog::new(verbose); use super::condition_env_builder::ConditionEnvBuilder; use crate::mir::loop_pattern_detection::function_scope_capture::{ analyze_captured_vars_v2, CapturedEnv, @@ -51,8 +75,7 @@ fn prepare_pattern2_inputs( let carrier_info = ctx.carrier_info.clone(); let scope = ctx.loop_scope.clone(); - log_pattern2( - verbose, + log.log( "init", format!( "PatternPipelineContext: loop_var='{}', loop_var_id={:?}, carriers={}", @@ -63,8 +86,7 @@ fn prepare_pattern2_inputs( ); // Capture analysis - log_pattern2( - verbose, + log.log( "phase200c", format!( "fn_body is {}", @@ -72,23 +94,20 @@ fn prepare_pattern2_inputs( ), ); let captured_env = if let Some(fn_body_ref) = fn_body { - log_pattern2( - verbose, + log.log( "phase200c", format!("fn_body has {} nodes", fn_body_ref.len()), ); analyze_captured_vars_v2(fn_body_ref, condition, body, &scope) } else { - log_pattern2( - verbose, + log.log( "phase200c", "fn_body is None, using empty CapturedEnv", ); CapturedEnv::new() }; if verbose { - log_pattern2( - verbose, + log.log( "capture", format!( "Phase 200-C: Captured {} variables", @@ -96,8 +115,7 @@ fn prepare_pattern2_inputs( ), ); for var in &captured_env.vars { - log_pattern2( - verbose, + log.log( "capture", format!( " '{}': host_id={:?}, immutable={}", @@ -123,8 +141,7 @@ fn prepare_pattern2_inputs( // Phase 136 Step 4/7: Use binding_ctx for lookup if let Some(loop_var_bid) = builder.binding_ctx.lookup(&loop_var_name) { env.register_loop_var_binding(loop_var_bid, _loop_var_join_id); - log_pattern2( - verbose, + log.log( "phase79", format!( "Registered loop var BindingId: '{}' BindingId({}) → ValueId({})", @@ -133,8 +150,7 @@ fn prepare_pattern2_inputs( ); } - log_pattern2( - verbose, + log.log( "phase201", format!( "Using JoinValueSpace: loop_var '{}' → {:?}", @@ -153,8 +169,7 @@ fn prepare_pattern2_inputs( host_value: host_id, join_value: join_id, }); - log_pattern2( - verbose, + log.log( "capture", format!( "Phase 201: Added captured '{}': host={:?}, join={:?}", @@ -165,8 +180,7 @@ fn prepare_pattern2_inputs( } let body_local_env = LoopBodyLocalEnv::new(); - log_pattern2( - verbose, + log.log( "body-local", format!( "Phase 201: Created empty body-local environment (param_count={})", @@ -174,13 +188,11 @@ fn prepare_pattern2_inputs( ), ); if verbose { - log_pattern2( - verbose, + log.log( "cond-env", format!("Phase 201: ConditionEnv contains {} variables:", env.len()), ); - log_pattern2( - verbose, + log.log( "cond-env", format!( " Loop param '{}' → JoinIR {:?}", @@ -189,14 +201,12 @@ fn prepare_pattern2_inputs( ), ); if !condition_bindings.is_empty() { - log_pattern2( - verbose, + log.log( "cond-env", format!(" {} condition-only bindings:", condition_bindings.len()), ); for binding in &condition_bindings { - log_pattern2( - verbose, + log.log( "cond-env", format!( " '{}': HOST {:?} → JoinIR {:?}", @@ -205,7 +215,7 @@ fn prepare_pattern2_inputs( ); } } else { - log_pattern2(verbose, "cond-env", " No condition-only variables"); + log.log("cond-env", " No condition-only variables"); } } @@ -238,6 +248,8 @@ fn prepare_pattern2_inputs( env, condition_bindings, body_local_env, + allowed_body_locals_for_conditions: Vec::new(), + read_only_body_local_slot: None, break_condition_node, }) } @@ -251,9 +263,6 @@ fn promote_and_prepare_carriers( verbose: bool, ) -> Result<(), String> { use crate::mir::join_ir::lowering::digitpos_condition_normalizer::DigitPosConditionNormalizer; - use crate::mir::loop_pattern_detection::loop_body_cond_promoter::{ - ConditionPromotionRequest, ConditionPromotionResult, LoopBodyCondPromoter, - }; use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox; let cond_scope = LoopConditionScopeBox::analyze( @@ -262,23 +271,20 @@ fn promote_and_prepare_carriers( Some(&inputs.scope), ); + let log = Pattern2DebugLog::new(verbose); let mut promoted_pairs: Vec<(String, String)> = Vec::new(); if cond_scope.has_loop_body_local() { - let promotion_req = ConditionPromotionRequest { - loop_param_name: &inputs.loop_var_name, - cond_scope: &cond_scope, - scope_shape: Some(&inputs.scope), - break_cond: Some(&inputs.break_condition_node), - continue_cond: None, - loop_body: body, - #[cfg(feature = "normalized_dev")] - binding_map: Some(&builder.binding_map), - }; - - match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) { - ConditionPromotionResult::Promoted { - carrier_info: promoted_carrier, + match classify_for_pattern2( + builder, + &inputs.loop_var_name, + &inputs.scope, + &inputs.break_condition_node, + &cond_scope, + body, + ) { + BodyLocalPolicyDecision::UsePromotion { + promoted_carrier, promoted_var, carrier_name, } => { @@ -312,8 +318,7 @@ fn promote_and_prepare_carriers( inputs.carrier_info.merge_from(&promoted_carrier); } - log_pattern2( - verbose, + log.log( "cond_promoter", format!( "LoopBodyLocal '{}' promoted to carrier '{}'", @@ -326,8 +331,7 @@ fn promote_and_prepare_carriers( .promoted_loopbodylocals .push(promoted_var.clone()); - log_pattern2( - verbose, + log.log( "cond_promoter", format!( "Merged carrier '{}' into CarrierInfo (total carriers: {})", @@ -335,8 +339,7 @@ fn promote_and_prepare_carriers( inputs.carrier_info.carrier_count() ), ); - log_pattern2( - verbose, + log.log( "cond_promoter", format!( "Phase 224: Recorded promoted variable '{}' in carrier_info.promoted_loopbodylocals", @@ -346,17 +349,15 @@ fn promote_and_prepare_carriers( if let Some(helper) = inputs.carrier_info.trim_helper() { if helper.is_safe_trim() { - log_pattern2(verbose, "cond_promoter", "Safe Trim pattern detected"); - log_pattern2( - verbose, + log.log("cond_promoter", "Safe Trim pattern detected"); + log.log( "cond_promoter", format!( "Carrier: '{}', original var: '{}', whitespace chars: {:?}", helper.carrier_name, helper.original_var, helper.whitespace_chars ), ); - log_pattern2( - verbose, + log.log( "phase133", format!( "Phase 133 P1: Trim promotion deferred to TrimLoopLowerer (SSOT for env/join_id)" @@ -379,8 +380,7 @@ fn promote_and_prepare_carriers( &carrier_name, ); - log_pattern2( - verbose, + log.log( "phase224e", format!( "Normalized break condition for promoted variable '{}' → carrier '{}'", @@ -389,7 +389,18 @@ fn promote_and_prepare_carriers( ); } } - ConditionPromotionResult::CannotPromote { reason, vars } => { + BodyLocalPolicyDecision::UseReadOnlySlot(slot) => { + log.log( + "body_local_slot", + format!( + "Phase 92 P3: BodyLocalPolicy=UseReadOnlySlot var='{}' (decl@{}, break_if@{}, init={:?})", + slot.name, slot.decl_stmt_index, slot.break_guard_stmt_index, &slot.init_expr + ), + ); + inputs.allowed_body_locals_for_conditions = vec![slot.name.clone()]; + inputs.read_only_body_local_slot = Some(slot); + } + BodyLocalPolicyDecision::Reject { reason, vars } => { return Err(error_messages::format_error_pattern2_promotion_failed( &vars, &reason, )); @@ -397,8 +408,7 @@ fn promote_and_prepare_carriers( } } - log_pattern2( - verbose, + log.log( "phase224d", format!( "Allocating join_ids for {} carriers", @@ -417,8 +427,7 @@ fn promote_and_prepare_carriers( inputs .env .register_condition_binding(binding_id, carrier_join_id); - log_pattern2( - verbose, + log.log( "phase79", format!( "Registered condition-only carrier '{}' BindingId({}) → ValueId({})", @@ -430,8 +439,7 @@ fn promote_and_prepare_carriers( inputs .env .register_carrier_binding(binding_id, carrier_join_id); - log_pattern2( - verbose, + log.log( "phase79", format!( "Registered loop-state carrier '{}' BindingId({}) → ValueId({})", @@ -441,8 +449,7 @@ fn promote_and_prepare_carriers( } } } - log_pattern2( - verbose, + log.log( "phase224d", format!( "Allocated carrier '{}' param ID: {:?}", @@ -463,8 +470,7 @@ fn promote_and_prepare_carriers( ) })?; inputs.env.insert(promoted_var.clone(), join_id); - log_pattern2( - verbose, + log.log( "phase229", format!( "Resolved promoted '{}' → carrier '{}' (join_id={:?})", @@ -492,25 +498,13 @@ fn promote_and_prepare_carriers( .lower(&inputs.break_condition_node) { Ok(_value_id) => { - log_pattern2( - verbose, - "phase231", - "ExprLowerer successfully validated break condition", - ); + log.log("phase231", "ExprLowerer successfully validated break condition"); } Err(ExprLoweringError::UnsupportedNode(msg)) => { - log_pattern2( - verbose, - "phase231", - format!("ExprLowerer fallback (unsupported): {}", msg), - ); + log.log("phase231", format!("ExprLowerer fallback (unsupported): {}", msg)); } Err(e) => { - log_pattern2( - verbose, - "phase231", - format!("ExprLowerer validation error: {}", e), - ); + log.log("phase231", format!("ExprLowerer validation error: {}", e)); } } } @@ -525,6 +519,7 @@ fn apply_trim_and_normalize( inputs: &mut Pattern2Inputs, verbose: bool, ) -> Result<(ASTNode, Option>), String> { + let log = Pattern2DebugLog::new(verbose); let mut alloc_join_value = || inputs.join_value_space.alloc_param(); let effective_break_condition = if let Some(trim_result) = @@ -538,11 +533,7 @@ fn apply_trim_and_normalize( &mut inputs.carrier_info, &mut alloc_join_value, )? { - log_pattern2( - verbose, - "trim", - "TrimLoopLowerer processed Trim pattern successfully", - ); + log.log("trim", "TrimLoopLowerer processed Trim pattern successfully"); inputs.carrier_info = trim_result.carrier_info; inputs .condition_bindings @@ -550,8 +541,7 @@ fn apply_trim_and_normalize( for binding in &trim_result.condition_bindings { inputs.env.insert(binding.name.clone(), binding.join_value); } - log_pattern2( - verbose, + log.log( "trim", format!( "Extended condition_bindings with {} Trim bindings", @@ -577,8 +567,7 @@ fn apply_trim_and_normalize( new_assign, temp_name, } => { - log_pattern2( - verbose, + log.log( "phase192", format!( "Normalized complex addend: temp='{}' inserted before update", @@ -625,17 +614,14 @@ fn collect_body_local_variables( ) -> Vec<(String, ValueId)> { let mut locals = Vec::new(); let verbose = crate::config::env::joinir_dev_enabled(); + let log = Pattern2DebugLog::new(verbose); for node in body { if let ASTNode::Local { variables, .. } = node { // Local declaration can have multiple variables (e.g., local a, b, c) for name in variables { let value_id = alloc_join_value(); locals.push((name.clone(), value_id)); - log_pattern2( - verbose, - "body-local", - format!("Collected local '{}' → {:?}", name, value_id), - ); + log.log("body-local", format!("Collected local '{}' → {:?}", name, value_id)); } } } @@ -791,6 +777,7 @@ impl MirBuilder { use crate::mir::join_ir::lowering::loop_with_break_minimal::lower_loop_with_break_minimal; let verbose = debug || crate::config::env::joinir_dev_enabled(); + let log = Pattern2DebugLog::new(verbose); // Phase 195: Use unified trace trace::trace().debug("pattern2", "Calling Pattern 2 minimal lowerer"); @@ -814,8 +801,7 @@ impl MirBuilder { &inputs.carrier_info.carriers, ); - log_pattern2( - verbose, + log.log( "updates", format!( "Phase 176-3: Analyzed {} carrier updates", @@ -826,8 +812,7 @@ impl MirBuilder { let original_carrier_count = inputs.carrier_info.carriers.len(); filter_carriers_for_updates(&mut inputs.carrier_info, &carrier_updates); - log_pattern2( - verbose, + log.log( "updates", format!( "Phase 176-4: Filtered carriers: {} → {} (kept only carriers with updates/condition-only/loop-local-zero)", @@ -851,8 +836,7 @@ impl MirBuilder { join_value, }); } else { - log_pattern2( - verbose, + log.log( "updates", format!( "Phase 247-EX: Skipping host binding for loop-local carrier '{}' (init=LoopLocalZero)", @@ -861,8 +845,7 @@ impl MirBuilder { ); } - log_pattern2( - verbose, + log.log( "updates", format!( "Phase 176-5: Added body-only carrier '{}' to ConditionEnv: HOST {:?} → JoinIR {:?}", @@ -872,8 +855,7 @@ impl MirBuilder { } } - log_pattern2( - verbose, + log.log( "before_lowerer", format!( "About to call lower_loop_with_break_minimal with carrier_info.loop_var_name='{}'", @@ -881,22 +863,31 @@ impl MirBuilder { ), ); - let (join_module, fragment_meta) = match lower_loop_with_break_minimal( - inputs.scope, - condition, - &effective_break_condition, - &inputs.env, - &inputs.carrier_info, - &carrier_updates, - analysis_body, // Phase 191/192: Pass normalized body AST for init lowering - Some(&mut inputs.body_local_env), // Phase 191: Pass mutable body-local environment - &mut inputs.join_value_space, // Phase 201: Unified ValueId allocation (Local region) - skeleton, // Phase 92 P0-3: Pass skeleton for ConditionalStep support - ) { - Ok((module, meta)) => (module, meta), - Err(e) => { - // Phase 195: Use unified trace - trace::trace().debug("pattern2", &format!("Pattern 2 lowerer failed: {}", e)); + let body_local_env = &mut inputs.body_local_env; + let join_value_space = &mut inputs.join_value_space; + let lowering_inputs = LoopWithBreakLoweringInputs { + scope: inputs.scope, + condition, + break_condition: &effective_break_condition, + env: &inputs.env, + carrier_info: &inputs.carrier_info, + carrier_updates: &carrier_updates, + body_ast: analysis_body, + body_local_env: Some(body_local_env), + allowed_body_locals_for_conditions: if inputs.allowed_body_locals_for_conditions.is_empty() { + None + } else { + Some(inputs.allowed_body_locals_for_conditions.as_slice()) + }, + join_value_space, + skeleton, + }; + + let (join_module, fragment_meta) = match lower_loop_with_break_minimal(lowering_inputs) { + Ok((module, meta)) => (module, meta), + Err(e) => { + // Phase 195: Use unified trace + trace::trace().debug("pattern2", &format!("Pattern 2 lowerer failed: {}", e)); return Err(format!("[cf_loop/pattern2] Lowering failed: {}", e)); } }; @@ -921,8 +912,7 @@ impl MirBuilder { host_input_values.push(carrier.host_id); } - log_pattern2( - verbose, + log.log( "boundary", format!( "Phase 176-3: Boundary inputs - {} JoinIR slots, {} host values", diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs b/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs index d1145234..93023ab3 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs @@ -137,9 +137,14 @@ impl MirBuilder { if let Some(bid) = self.binding_ctx.lookup(&loop_var_name) { cond_env.register_loop_var_binding(bid, _loop_var_join_id); if debug { - eprintln!( - "[phase80/p3] Registered loop var '{}' BindingId({}) -> ValueId({})", - loop_var_name, bid.0, _loop_var_join_id.0 + super::super::trace::trace().emit_if( + "phase80", + "p3", + &format!( + "Registered loop var '{}' BindingId({}) -> ValueId({})", + loop_var_name, bid.0, _loop_var_join_id.0 + ), + true, ); } } @@ -152,9 +157,14 @@ impl MirBuilder { if let Some(bid) = self.binding_ctx.lookup(&binding.name) { cond_env.register_condition_binding(bid, binding.join_value); if debug { - eprintln!( - "[phase80/p3] Registered condition binding '{}' BindingId({}) -> ValueId({})", - binding.name, bid.0, binding.join_value.0 + super::super::trace::trace().emit_if( + "phase80", + "p3", + &format!( + "Registered condition binding '{}' BindingId({}) -> ValueId({})", + binding.name, bid.0, binding.join_value.0 + ), + true, ); } } @@ -202,7 +212,8 @@ impl MirBuilder { &ctx.carrier_info, &condition_binding_names, ) { - eprintln!("[phase64/ownership] Consistency check failed: {}", e); + super::super::trace::trace() + .dev("phase64/ownership", &format!("Consistency check failed: {}", e)); return Err(e); } @@ -217,9 +228,9 @@ impl MirBuilder { ); } Err(e) => { - eprintln!( - "[phase64/ownership] Analysis failed (continuing with legacy): {}", - e + super::super::trace::trace().dev( + "phase64/ownership", + &format!("Analysis failed (continuing with legacy): {}", e), ); // Don't fail - analysis is optional in Phase 64 } diff --git a/src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs b/src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs index 52100fae..551f7789 100644 --- a/src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs +++ b/src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs @@ -171,6 +171,9 @@ impl TrimLoopLowerer { carrier_info: &mut CarrierInfo, alloc_join_value: &mut dyn FnMut() -> ValueId, ) -> Result, String> { + let trace = crate::mir::builder::control_flow::joinir::trace::trace(); + let verbose = crate::config::env::joinir_dev_enabled() || trace.is_joinir_enabled(); + // Phase 180-2: Skeleton implementation // TODO: Phase 180-3 will implement full logic from Pattern2 @@ -178,18 +181,33 @@ impl TrimLoopLowerer { let cond_scope = LoopConditionScopeBox::analyze(loop_var_name, &[loop_cond, break_cond], Some(scope)); - eprintln!( - "[TrimLoopLowerer] Analyzing condition scope: {} variables", - cond_scope.vars.len() + trace.emit_if( + "trim", + "scope", + &format!( + "Analyzing condition scope: {} variables", + cond_scope.vars.len() + ), + verbose, ); if !cond_scope.has_loop_body_local() { // Not a Trim pattern - normal loop - eprintln!("[TrimLoopLowerer] No LoopBodyLocal detected, skipping Trim lowering"); + trace.emit_if( + "trim", + "scope", + "No LoopBodyLocal detected, skipping Trim lowering", + verbose, + ); return Ok(None); } - eprintln!("[TrimLoopLowerer] LoopBodyLocal detected in condition scope"); + trace.emit_if( + "trim", + "scope", + "LoopBodyLocal detected in condition scope", + verbose, + ); // Phase 183-2: Filter to only condition LoopBodyLocal (skip body-only) use crate::mir::loop_pattern_detection::loop_condition_scope::CondVarScope; @@ -205,19 +223,27 @@ impl TrimLoopLowerer { if condition_body_locals.is_empty() { // All LoopBodyLocal are body-only (not in conditions) → Not a Trim pattern - eprintln!( - "[TrimLoopLowerer] Phase 183: All LoopBodyLocal are body-only (not in conditions), skipping Trim lowering" + trace.emit_if( + "trim", + "phase183", + "All LoopBodyLocal are body-only (not in conditions), skipping Trim lowering", + verbose, ); return Ok(None); } - eprintln!( - "[TrimLoopLowerer] Phase 183: Found {} condition LoopBodyLocal variables: {:?}", - condition_body_locals.len(), - condition_body_locals - .iter() - .map(|v| &v.name) - .collect::>() + trace.emit_if( + "trim", + "phase183", + &format!( + "Found {} condition LoopBodyLocal variables: {:?}", + condition_body_locals.len(), + condition_body_locals + .iter() + .map(|v| &v.name) + .collect::>() + ), + verbose, ); // Step 2: Try promotion via LoopBodyCarrierPromoter @@ -233,9 +259,14 @@ impl TrimLoopLowerer { match LoopBodyCarrierPromoter::try_promote(&request) { PromotionResult::Promoted { trim_info } => { - eprintln!( - "[TrimLoopLowerer] LoopBodyLocal '{}' promoted to carrier '{}'", - trim_info.var_name, trim_info.carrier_name + trace.emit_if( + "trim", + "promote", + &format!( + "LoopBodyLocal '{}' promoted to carrier '{}'", + trim_info.var_name, trim_info.carrier_name + ), + verbose, ); // Step 3: Convert to CarrierInfo and merge @@ -247,10 +278,15 @@ impl TrimLoopLowerer { let promoted_carrier = trim_info.to_carrier_info(); carrier_info.merge_from(&promoted_carrier); - eprintln!( - "[TrimLoopLowerer] Merged carrier '{}' into CarrierInfo (total carriers: {})", - trim_info.carrier_name, - carrier_info.carrier_count() + trace.emit_if( + "trim", + "promote", + &format!( + "Merged carrier '{}' into CarrierInfo (total carriers: {})", + trim_info.carrier_name, + carrier_info.carrier_count() + ), + verbose, ); // Step 4: Safety check via TrimLoopHelper @@ -269,37 +305,62 @@ impl TrimLoopLowerer { )); } - eprintln!("[TrimLoopLowerer] Safe Trim pattern detected, implementing lowering"); - eprintln!( - "[TrimLoopLowerer] Carrier: '{}', original var: '{}', whitespace chars: {:?}", - trim_helper.carrier_name, - trim_helper.original_var, - trim_helper.whitespace_chars + trace.emit_if( + "trim", + "safe", + "Safe Trim pattern detected, implementing lowering", + verbose, + ); + trace.emit_if( + "trim", + "safe", + &format!( + "Carrier: '{}', original var: '{}', whitespace chars: {:?}", + trim_helper.carrier_name, + trim_helper.original_var, + trim_helper.whitespace_chars + ), + verbose, ); // Step 5: Generate carrier initialization code Self::generate_carrier_initialization(builder, body, trim_helper)?; - eprintln!( - "[TrimLoopLowerer] Registered carrier '{}' in variable_ctx.variable_map", - trim_helper.carrier_name + trace.emit_if( + "trim", + "init", + &format!( + "Registered carrier '{}' in variable_ctx.variable_map", + trim_helper.carrier_name + ), + verbose, ); // Step 6: Generate Trim break condition let trim_break_condition = Self::generate_trim_break_condition(trim_helper); - eprintln!( - "[TrimLoopLowerer] Replaced break condition with !{}", - trim_helper.carrier_name + trace.emit_if( + "trim", + "cond", + &format!( + "Replaced break condition with !{}", + trim_helper.carrier_name + ), + verbose, ); // Step 7: Setup ConditionEnv bindings let condition_bindings = Self::setup_condition_env_bindings(builder, trim_helper, alloc_join_value)?; - eprintln!( - "[TrimLoopLowerer] Added {} condition bindings", - condition_bindings.len() + trace.emit_if( + "trim", + "cond", + &format!( + "Added {} condition bindings", + condition_bindings.len() + ), + verbose, ); // Step 8: Return result with all updates @@ -313,9 +374,14 @@ impl TrimLoopLowerer { // Phase 196: Treat non-trim loops as normal loops. // If promotion fails, simply skip Trim lowering and let the caller // continue with the original break condition. - eprintln!( - "[TrimLoopLowerer] Cannot promote LoopBodyLocal variables {:?}: {}; skipping Trim lowering", - vars, reason + trace.emit_if( + "trim", + "reject", + &format!( + "Cannot promote LoopBodyLocal variables {:?}: {}; skipping Trim lowering", + vars, reason + ), + verbose, ); Ok(None) } @@ -336,6 +402,8 @@ impl TrimLoopLowerer { trim_helper: &crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper, ) -> Result<(), String> { use crate::mir::builder::control_flow::joinir::patterns::trim_pattern_validator::TrimPatternValidator; + let trace = crate::mir::builder::control_flow::joinir::trace::trace(); + let verbose = crate::config::env::joinir_dev_enabled() || trace.is_joinir_enabled(); // Extract substring pattern from body let (s_name, start_expr) = @@ -347,9 +415,11 @@ impl TrimLoopLowerer { ) })?; - eprintln!( - "[TrimLoopLowerer] Extracted substring pattern: s='{}', start={:?}", - s_name, start_expr + trace.emit_if( + "trim", + "init", + &format!("Extracted substring pattern: s='{}', start={:?}", s_name, start_expr), + verbose, ); // Get ValueIds for string and start @@ -387,9 +457,11 @@ impl TrimLoopLowerer { vec![start_id, start_plus_1], )?; - eprintln!( - "[TrimLoopLowerer] Generated initial substring call: ch0 = {:?}", - ch0 + trace.emit_if( + "trim", + "init", + &format!("Generated initial substring call: ch0 = {:?}", ch0), + verbose, ); // Generate: is_ch_match0 = (ch0 == " " || ch0 == "\t" || ...) @@ -399,9 +471,14 @@ impl TrimLoopLowerer { &trim_helper.whitespace_chars, )?; - eprintln!( - "[TrimLoopLowerer] Generated initial whitespace check: is_ch_match0 = {:?}", - is_ch_match0 + trace.emit_if( + "trim", + "init", + &format!( + "Generated initial whitespace check: is_ch_match0 = {:?}", + is_ch_match0 + ), + verbose, ); // Register carrier in variable_ctx.variable_map @@ -438,6 +515,8 @@ impl TrimLoopLowerer { alloc_join_value: &mut dyn FnMut() -> ValueId, ) -> Result, String> { use crate::mir::builder::control_flow::joinir::patterns::trim_pattern_lowerer::TrimPatternLowerer; + let trace = crate::mir::builder::control_flow::joinir::trace::trace(); + let verbose = crate::config::env::joinir_dev_enabled() || trace.is_joinir_enabled(); let mut bindings = Vec::new(); @@ -454,9 +533,14 @@ impl TrimLoopLowerer { alloc_join_value, )?; - eprintln!( - "[TrimLoopLowerer] Added carrier '{}' to ConditionEnv: HOST {:?} → JoinIR {:?}", - trim_helper.carrier_name, binding.host_value, binding.join_value + trace.emit_if( + "trim", + "cond-env", + &format!( + "Added carrier '{}' to ConditionEnv: HOST {:?} → JoinIR {:?}", + trim_helper.carrier_name, binding.host_value, binding.join_value + ), + verbose, ); bindings.push(binding.clone()); @@ -470,9 +554,14 @@ impl TrimLoopLowerer { join_value: binding.join_value, }; - eprintln!( - "[TrimLoopLowerer] Phase 176-6: Also mapped original var '{}' → JoinIR {:?}", - trim_helper.original_var, binding.join_value + trace.emit_if( + "trim", + "cond-env", + &format!( + "Phase 176-6: Also mapped original var '{}' → JoinIR {:?}", + trim_helper.original_var, binding.join_value + ), + verbose, ); bindings.push(original_binding); diff --git a/src/mir/builder/control_flow/joinir/routing.rs b/src/mir/builder/control_flow/joinir/routing.rs index 6a22fc54..d960e3f8 100644 --- a/src/mir/builder/control_flow/joinir/routing.rs +++ b/src/mir/builder/control_flow/joinir/routing.rs @@ -54,13 +54,16 @@ pub(in crate::mir::builder) fn choose_pattern_kind( panic!("{}", msg); } else { // debug mode: ログのみ - eprintln!("{}", msg); + trace::trace().dev("choose_pattern_kind/parity", &msg); } } else { // Patterns match - success! - eprintln!( - "[choose_pattern_kind/PARITY] OK: canonical and actual agree on {:?}", - canonical_choice + trace::trace().dev( + "choose_pattern_kind/parity", + &format!( + "[choose_pattern_kind/PARITY] OK: canonical and actual agree on {:?}", + canonical_choice + ), ); } @@ -218,38 +221,47 @@ impl MirBuilder { match canonicalize_loop_expr(&loop_ast) { Ok((skeleton, decision)) => { - eprintln!("[loop_canonicalizer] Function: {}", func_name); - eprintln!( - "[loop_canonicalizer] Skeleton steps: {}", - skeleton.steps.len() + trace::trace().dev( + "loop_canonicalizer", + &format!("Function: {}", func_name), ); - eprintln!( - "[loop_canonicalizer] Carriers: {}", - skeleton.carriers.len() + trace::trace().dev( + "loop_canonicalizer", + &format!(" Skeleton steps: {}", skeleton.steps.len()), ); - eprintln!( - "[loop_canonicalizer] Has exits: {}", - skeleton.exits.has_any_exit() + trace::trace().dev( + "loop_canonicalizer", + &format!(" Carriers: {}", skeleton.carriers.len()), ); - eprintln!( - "[loop_canonicalizer] Decision: {}", - if decision.is_success() { - "SUCCESS" - } else { - "FAIL_FAST" - } + trace::trace().dev( + "loop_canonicalizer", + &format!(" Has exits: {}", skeleton.exits.has_any_exit()), + ); + trace::trace().dev( + "loop_canonicalizer", + &format!( + " Decision: {}", + if decision.is_success() { + "SUCCESS" + } else { + "FAIL_FAST" + } + ), ); if let Some(pattern) = decision.chosen { - eprintln!("[loop_canonicalizer] Chosen pattern: {:?}", pattern); + trace::trace().dev( + "loop_canonicalizer", + &format!(" Chosen pattern: {:?}", pattern), + ); } - eprintln!( - "[loop_canonicalizer] Missing caps: {:?}", - decision.missing_caps + trace::trace().dev( + "loop_canonicalizer", + &format!(" Missing caps: {:?}", decision.missing_caps), ); if decision.is_fail_fast() { - eprintln!( - "[loop_canonicalizer] Reason: {}", - decision.notes.join("; ") + trace::trace().dev( + "loop_canonicalizer", + &format!(" Reason: {}", decision.notes.join("; ")), ); } @@ -268,8 +280,8 @@ impl MirBuilder { } } Err(e) => { - eprintln!("[loop_canonicalizer] Function: {}", func_name); - eprintln!("[loop_canonicalizer] Error: {}", e); + trace::trace().dev("loop_canonicalizer", &format!("Function: {}", func_name)); + trace::trace().dev("loop_canonicalizer", &format!(" Error: {}", e)); } } } diff --git a/src/mir/builder/control_flow/joinir/trace.rs b/src/mir/builder/control_flow/joinir/trace.rs index e4173bb6..8a30c11b 100644 --- a/src/mir/builder/control_flow/joinir/trace.rs +++ b/src/mir/builder/control_flow/joinir/trace.rs @@ -59,6 +59,10 @@ pub struct JoinLoopTrace { mainline_enabled: bool, /// Whether LoopForm debug is enabled (NYASH_LOOPFORM_DEBUG) loopform_enabled: bool, + /// Whether JoinIR dev mode is enabled (NYASH_JOINIR_DEV) + dev_enabled: bool, + /// Whether capture/ConditionEnv construction debug is enabled (NYASH_CAPTURE_DEBUG) + capture_enabled: bool, } impl JoinLoopTrace { @@ -71,6 +75,8 @@ impl JoinLoopTrace { phi_enabled: std::env::var("NYASH_OPTION_C_DEBUG").is_ok(), mainline_enabled: std::env::var("NYASH_JOINIR_MAINLINE_DEBUG").is_ok(), loopform_enabled: std::env::var("NYASH_LOOPFORM_DEBUG").is_ok(), + dev_enabled: crate::config::env::joinir_dev_enabled(), + capture_enabled: std::env::var("NYASH_CAPTURE_DEBUG").is_ok(), } } @@ -81,6 +87,8 @@ impl JoinLoopTrace { || self.phi_enabled || self.mainline_enabled || self.loopform_enabled + || self.dev_enabled + || self.capture_enabled } /// Check if varmap tracing is enabled @@ -199,6 +207,43 @@ impl JoinLoopTrace { } } + /// Dev-only trace message (NYASH_JOINIR_DEV=1). + /// + /// This is for diagnostics that should never appear in default runs, but are + /// useful while developing JoinIR lowering. + pub fn dev(&self, tag: &str, msg: &str) { + if self.dev_enabled { + eprintln!("[trace:dev] {}: {}", tag, msg); + } + } + + /// Capture/debug output (NYASH_CAPTURE_DEBUG=1). + pub fn capture(&self, tag: &str, msg: &str) { + if self.capture_enabled { + eprintln!("[trace:capture] {}: {}", tag, msg); + } + } + + /// Emit a message when the caller explicitly enables it (no env checks). + /// + /// This is useful for routing `debug: bool` parameters through a single formatting point, + /// instead of scattering ad-hoc `eprintln!`. + pub fn emit_if(&self, channel: &str, tag: &str, msg: &str, enabled: bool) { + if enabled { + eprintln!("[trace:{}] {}: {}", channel, tag, msg); + } + } + + /// Emit a raw line to stderr when enabled (no formatting). + /// + /// Use this to preserve existing log formats while consolidating the actual `eprintln!` + /// call sites into this tracer. + pub fn stderr_if(&self, msg: &str, enabled: bool) { + if enabled { + eprintln!("{}", msg); + } + } + /// Trace function routing decisions /// /// # Arguments diff --git a/src/mir/join_ir/lowering/carrier_update_emitter.rs b/src/mir/join_ir/lowering/carrier_update_emitter.rs index 3ed90a62..6bbd7b6f 100644 --- a/src/mir/join_ir/lowering/carrier_update_emitter.rs +++ b/src/mir/join_ir/lowering/carrier_update_emitter.rs @@ -367,7 +367,7 @@ pub fn emit_carrier_update( // ============================================================================ use crate::ast::ASTNode; -use crate::mir::join_ir::lowering::condition_lowerer::lower_condition_to_joinir; +use crate::mir::join_ir::lowering::condition_lowerer::lower_condition_to_joinir_no_body_locals; use crate::mir::join_ir::VarId; use crate::mir::MirType; @@ -409,7 +409,7 @@ pub fn emit_conditional_step_update( ) -> Result { // Step 1: Lower the condition expression // Phase 92 P2-2: No body-local support in legacy emitter (use common/conditional_step_emitter instead) - let (cond_id, cond_insts) = lower_condition_to_joinir(cond_ast, alloc_value, env, None)?; + let (cond_id, cond_insts) = lower_condition_to_joinir_no_body_locals(cond_ast, alloc_value, env)?; instructions.extend(cond_insts); // Step 2: Get carrier parameter ValueId from env diff --git a/src/mir/join_ir/lowering/common.rs b/src/mir/join_ir/lowering/common.rs index f13baa7d..fbcf9c91 100644 --- a/src/mir/join_ir/lowering/common.rs +++ b/src/mir/join_ir/lowering/common.rs @@ -4,6 +4,8 @@ pub mod case_a; pub mod conditional_step_emitter; // Phase 92 P1-1: ConditionalStep emission module +pub mod body_local_slot; // Phase 92 P3: Read-only body-local slot for conditions +pub mod dual_value_rewriter; // Phase 246-EX/247-EX: name-based dual-value rewrites use crate::mir::loop_form::LoopForm; use crate::mir::query::{MirQuery, MirQueryBox}; diff --git a/src/mir/join_ir/lowering/common/body_local_slot.rs b/src/mir/join_ir/lowering/common/body_local_slot.rs new file mode 100644 index 00000000..559acff3 --- /dev/null +++ b/src/mir/join_ir/lowering/common/body_local_slot.rs @@ -0,0 +1,314 @@ +//! Phase 92 P3: ReadOnlyBodyLocalSlot Box +//! +//! Purpose: support the minimal case where a loop condition/break condition +//! references a loop-body-local variable (e.g., `ch`) that is recomputed every +//! iteration and is read-only (no assignment). +//! +//! This box is intentionally narrow and fail-fast: +//! - Supports exactly 1 body-local variable used in conditions. +//! - Requires a top-level `local = ` before the break-guard `if`. +//! - Forbids any assignment to that variable (including in nested blocks). +//! +//! NOTE: This box does NOT lower the init expression itself. +//! Lowering is handled by `LoopBodyLocalInitLowerer` (Phase 186). +//! This box only validates the contract and provides an allow-list for +//! condition lowering checks. + +use crate::ast::ASTNode; +use crate::mir::join_ir::lowering::error_tags; + +#[derive(Debug, Clone)] +pub struct ReadOnlyBodyLocalSlot { + pub name: String, + pub init_expr: ASTNode, + pub decl_stmt_index: usize, + pub break_guard_stmt_index: usize, +} + +/// A tiny "box" API: analyze loop body and decide whether we can allow a single +/// loop-body-local variable to be referenced from Pattern2 break conditions. +pub struct ReadOnlyBodyLocalSlotBox; + +impl ReadOnlyBodyLocalSlotBox { + /// Extract and validate a single read-only body-local slot used in conditions. + /// + /// # Contract (Fail-Fast) + /// - `names_in_conditions` must contain exactly 1 name. + /// - A top-level `local = ` must exist in `body`. + /// - The declaration statement must appear before the first top-level `if` that contains `break`. + /// - No assignment to `` may exist anywhere in the loop body (including nested statements). + pub fn extract_single( + names_in_conditions: &[String], + body: &[ASTNode], + ) -> Result { + if names_in_conditions.is_empty() { + return Err(error_tags::freeze( + "[pattern2/body_local_slot/internal/empty_names] extract_single called with empty names_in_conditions", + )); + } + if names_in_conditions.len() != 1 { + return Err(error_tags::freeze(&format!( + "[pattern2/body_local_slot/contract/multiple_vars] Unsupported: multiple LoopBodyLocal variables in condition: {:?}", + names_in_conditions + ))); + } + + let name = names_in_conditions[0].clone(); + + let break_guard_stmt_index = find_first_top_level_break_guard_if(body).ok_or_else(|| { + error_tags::freeze( + "[pattern2/body_local_slot/contract/missing_break_guard] Missing top-level `if (...) { break }` (Pattern2 break guard)", + ) + })?; + + let (decl_stmt_index, init_expr) = + find_top_level_local_init(body, &name).ok_or_else(|| { + error_tags::freeze(&format!( + "[pattern2/body_local_slot/contract/missing_local_init] Missing top-level `local {} = ` for LoopBodyLocal used in condition", + name + )) + })?; + + if decl_stmt_index >= break_guard_stmt_index { + return Err(error_tags::freeze(&format!( + "[pattern2/body_local_slot/contract/decl_after_break_guard] `local {}` must appear before the break guard if-statement (decl_index={}, break_if_index={})", + name, decl_stmt_index, break_guard_stmt_index + ))); + } + + if contains_assignment_to_name(body, &name) { + return Err(error_tags::freeze(&format!( + "[pattern2/body_local_slot/contract/not_readonly] `{}` must be read-only (assignment detected in loop body)", + name + ))); + } + + Ok(ReadOnlyBodyLocalSlot { + name, + init_expr, + decl_stmt_index, + break_guard_stmt_index, + }) + } +} + +fn find_first_top_level_break_guard_if(body: &[ASTNode]) -> Option { + for (idx, stmt) in body.iter().enumerate() { + if let ASTNode::If { + then_body, + else_body, + .. + } = stmt + { + if then_body.iter().any(|n| matches!(n, ASTNode::Break { .. })) { + return Some(idx); + } + if let Some(else_body) = else_body { + if else_body.iter().any(|n| matches!(n, ASTNode::Break { .. })) { + return Some(idx); + } + } + } + } + None +} + +fn find_top_level_local_init(body: &[ASTNode], name: &str) -> Option<(usize, ASTNode)> { + for (idx, stmt) in body.iter().enumerate() { + if let ASTNode::Local { + variables, + initial_values, + .. + } = stmt + { + // Keep Phase 92 P3 minimal: the statement must be a 1-variable local. + if variables.len() != 1 { + continue; + } + if variables[0] != name { + continue; + } + let init = initial_values + .get(0) + .and_then(|v| v.as_ref()) + .map(|b| (*b.clone()).clone())?; + return Some((idx, init)); + } + } + None +} + +fn contains_assignment_to_name(body: &[ASTNode], name: &str) -> bool { + body.iter().any(|stmt| contains_assignment_to_name_in_node(stmt, name)) +} + +fn contains_assignment_to_name_in_node(node: &ASTNode, name: &str) -> bool { + match node { + ASTNode::Assignment { target, value, .. } => { + if matches!(&**target, ASTNode::Variable { name: n, .. } if n == name) { + return true; + } + contains_assignment_to_name_in_node(target, name) + || contains_assignment_to_name_in_node(value, name) + } + ASTNode::Nowait { variable, .. } => variable == name, + ASTNode::If { + condition, + then_body, + else_body, + .. + } => { + contains_assignment_to_name_in_node(condition, name) + || then_body + .iter() + .any(|n| contains_assignment_to_name_in_node(n, name)) + || else_body.as_ref().is_some_and(|e| { + e.iter() + .any(|n| contains_assignment_to_name_in_node(n, name)) + }) + } + ASTNode::Loop { condition, body, .. } => { + contains_assignment_to_name_in_node(condition, name) + || body + .iter() + .any(|n| contains_assignment_to_name_in_node(n, name)) + } + ASTNode::While { condition, body, .. } => { + contains_assignment_to_name_in_node(condition, name) + || body + .iter() + .any(|n| contains_assignment_to_name_in_node(n, name)) + } + ASTNode::ForRange { body, .. } => body + .iter() + .any(|n| contains_assignment_to_name_in_node(n, name)), + ASTNode::TryCatch { + try_body, + catch_clauses, + finally_body, + .. + } => { + try_body + .iter() + .any(|n| contains_assignment_to_name_in_node(n, name)) + || catch_clauses.iter().any(|c| { + c.body + .iter() + .any(|n| contains_assignment_to_name_in_node(n, name)) + }) + || finally_body.as_ref().is_some_and(|b| { + b.iter() + .any(|n| contains_assignment_to_name_in_node(n, name)) + }) + } + ASTNode::ScopeBox { body, .. } => body + .iter() + .any(|n| contains_assignment_to_name_in_node(n, name)), + _ => false, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::{BinaryOperator, LiteralValue, Span}; + + fn span() -> Span { + Span::unknown() + } + + fn var(name: &str) -> ASTNode { + ASTNode::Variable { + name: name.to_string(), + span: span(), + } + } + + fn lit_i(value: i64) -> ASTNode { + ASTNode::Literal { + value: LiteralValue::Integer(value), + span: span(), + } + } + + fn bin(op: BinaryOperator, left: ASTNode, right: ASTNode) -> ASTNode { + ASTNode::BinaryOp { + operator: op, + left: Box::new(left), + right: Box::new(right), + span: span(), + } + } + + #[test] + fn extract_single_ok() { + // local ch = 0; if (ch < 1) { break } + let body = vec![ + ASTNode::Local { + variables: vec!["ch".to_string()], + initial_values: vec![Some(Box::new(lit_i(0)))], + span: span(), + }, + ASTNode::If { + condition: Box::new(bin(BinaryOperator::Less, var("ch"), lit_i(1))), + then_body: vec![ASTNode::Break { span: span() }], + else_body: None, + span: span(), + }, + ]; + + let slot = ReadOnlyBodyLocalSlotBox::extract_single(&[String::from("ch")], &body).unwrap(); + assert_eq!(slot.name, "ch"); + assert_eq!(slot.decl_stmt_index, 0); + assert_eq!(slot.break_guard_stmt_index, 1); + } + + #[test] + fn extract_single_reject_assignment() { + let body = vec![ + ASTNode::Local { + variables: vec!["ch".to_string()], + initial_values: vec![Some(Box::new(lit_i(0)))], + span: span(), + }, + ASTNode::Assignment { + target: Box::new(var("ch")), + value: Box::new(lit_i(1)), + span: span(), + }, + ASTNode::If { + condition: Box::new(bin(BinaryOperator::Less, var("ch"), lit_i(1))), + then_body: vec![ASTNode::Break { span: span() }], + else_body: None, + span: span(), + }, + ]; + + let err = ReadOnlyBodyLocalSlotBox::extract_single(&[String::from("ch")], &body) + .unwrap_err(); + assert!(err.contains("[joinir/freeze]")); + assert!(err.contains("read-only")); + } + + #[test] + fn extract_single_reject_decl_after_break_if() { + let body = vec![ + ASTNode::If { + condition: Box::new(bin(BinaryOperator::Less, var("ch"), lit_i(1))), + then_body: vec![ASTNode::Break { span: span() }], + else_body: None, + span: span(), + }, + ASTNode::Local { + variables: vec!["ch".to_string()], + initial_values: vec![Some(Box::new(lit_i(0)))], + span: span(), + }, + ]; + + let err = ReadOnlyBodyLocalSlotBox::extract_single(&[String::from("ch")], &body) + .unwrap_err(); + assert!(err.contains("[joinir/freeze]")); + assert!(err.contains("must appear before")); + } +} diff --git a/src/mir/join_ir/lowering/common/conditional_step_emitter.rs b/src/mir/join_ir/lowering/common/conditional_step_emitter.rs index 0d8b32b0..de4ad149 100644 --- a/src/mir/join_ir/lowering/common/conditional_step_emitter.rs +++ b/src/mir/join_ir/lowering/common/conditional_step_emitter.rs @@ -15,7 +15,6 @@ //! 2. Condition must be pure expression (no side effects) use crate::ast::ASTNode; -use crate::mir::join_ir::lowering::carrier_info::CarrierVar; use crate::mir::join_ir::lowering::condition_env::ConditionEnv; use crate::mir::join_ir::lowering::condition_lowerer::lower_condition_to_joinir; use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; // Phase 92 P2-2 @@ -181,6 +180,7 @@ mod tests { 1, // else_delta (normal: i + 1) &mut alloc_value, &env, + None, &mut instructions, ); @@ -223,6 +223,7 @@ mod tests { 2, // else_delta (SAME! Should fail) &mut alloc_value, &env, + None, &mut instructions, ); @@ -256,6 +257,7 @@ mod tests { 1, // else_delta &mut alloc_value, &env, + None, &mut instructions, ); diff --git a/src/mir/join_ir/lowering/common/dual_value_rewriter.rs b/src/mir/join_ir/lowering/common/dual_value_rewriter.rs new file mode 100644 index 00000000..02af28e8 --- /dev/null +++ b/src/mir/join_ir/lowering/common/dual_value_rewriter.rs @@ -0,0 +1,118 @@ +//! Phase 246-EX / 247-EX: Dual-value rewrite helpers (Box) +//! +//! Purpose: isolate name-based rewrite rules for promoted condition carriers +//! and loop-local derived carriers, so Pattern2 lowering remains structural. +//! +//! This module is intentionally narrow and fail-fast-ish: +//! - It only performs rewrites when it can prove the required body-local source exists. +//! - Otherwise it leaves the original instructions/behavior unchanged. + +use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; +use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; +use crate::mir::join_ir::{CompareOp, ConstValue, JoinInst, MirLikeInst, UnaryOp}; +use crate::mir::ValueId; + +/// Rewrite a lowered break-condition instruction stream to use fresh body-local values +/// instead of stale promoted carrier parameters. +/// +/// Current supported rewrite: +/// - `!` where operand matches a carrier Join ValueId, and body-local provides `` +/// derived from `carrier.name.strip_prefix("is_")`. +pub fn rewrite_break_condition_insts( + insts: Vec, + carrier_info: &CarrierInfo, + body_local_env: Option<&LoopBodyLocalEnv>, + alloc_value: &mut dyn FnMut() -> ValueId, +) -> Vec { + let mut out = Vec::with_capacity(insts.len()); + + for inst in insts.into_iter() { + match inst { + JoinInst::Compute(MirLikeInst::UnaryOp { + op: UnaryOp::Not, + operand, + dst, + }) => { + let mut operand_value = operand; + + // Check if operand is a promoted carrier (e.g., is_digit) + for carrier in &carrier_info.carriers { + if carrier.join_id == Some(operand_value) { + if let Some(stripped) = carrier.name.strip_prefix("is_") { + let source_name = stripped.to_string(); + if let Some(src_val) = body_local_env.and_then(|env| env.get(&source_name)) { + // Emit fresh comparison: is_* = (source >= 0) + let zero = alloc_value(); + out.push(JoinInst::Compute(MirLikeInst::Const { + dst: zero, + value: ConstValue::Integer(0), + })); + + let fresh_bool = alloc_value(); + out.push(JoinInst::Compute(MirLikeInst::Compare { + dst: fresh_bool, + op: CompareOp::Ge, + lhs: src_val, + rhs: zero, + })); + + operand_value = fresh_bool; + } + } + } + } + + out.push(JoinInst::Compute(MirLikeInst::UnaryOp { + dst, + op: UnaryOp::Not, + operand: operand_value, + })); + } + other => out.push(other), + } + } + + out +} + +/// Try to derive an updated value for a loop-local derived carrier (e.g., `_value`) +/// from a body-local `_pos` value. +pub fn try_derive_looplocal_from_bodylocal_pos( + carrier_name: &str, + body_local_env: Option<&LoopBodyLocalEnv>, +) -> Option { + let stripped = carrier_name.strip_suffix("_value")?; + let source_name = format!("{}_pos", stripped); + body_local_env.and_then(|env| env.get(&source_name)) +} + +/// Try to derive a condition-only boolean carrier (e.g., `is_`) from a body-local `_pos`. +/// +/// Emits: `cmp = (_pos >= 0)` and returns `cmp` ValueId. +pub fn try_derive_conditiononly_is_from_bodylocal_pos( + carrier_name: &str, + body_local_env: Option<&LoopBodyLocalEnv>, + alloc_value: &mut dyn FnMut() -> ValueId, + out: &mut Vec, +) -> Option { + let stripped = carrier_name.strip_prefix("is_")?; + let source_name = format!("{}_pos", stripped); + let src_val = body_local_env.and_then(|env| env.get(&source_name))?; + + let zero = alloc_value(); + out.push(JoinInst::Compute(MirLikeInst::Const { + dst: zero, + value: ConstValue::Integer(0), + })); + + let cmp = alloc_value(); + out.push(JoinInst::Compute(MirLikeInst::Compare { + dst: cmp, + op: CompareOp::Ge, + lhs: src_val, + rhs: zero, + })); + + Some(cmp) +} + diff --git a/src/mir/join_ir/lowering/condition_lowerer.rs b/src/mir/join_ir/lowering/condition_lowerer.rs index a095b8d3..00470a50 100644 --- a/src/mir/join_ir/lowering/condition_lowerer.rs +++ b/src/mir/join_ir/lowering/condition_lowerer.rs @@ -86,6 +86,15 @@ pub fn lower_condition_to_joinir( Ok((result_value, instructions)) } +/// Convenience wrapper: lower a condition without body-local support. +pub fn lower_condition_to_joinir_no_body_locals( + cond_ast: &ASTNode, + alloc_value: &mut dyn FnMut() -> ValueId, + env: &ConditionEnv, +) -> Result<(ValueId, Vec), String> { + lower_condition_to_joinir(cond_ast, alloc_value, env, None) +} + /// Recursive helper for condition lowering /// /// Handles all supported AST node types and emits appropriate JoinIR instructions. @@ -437,7 +446,7 @@ mod tests { span: Span::unknown(), }; - let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env); + let result = lower_condition_to_joinir_no_body_locals(&ast, &mut alloc_value, &env); assert!(result.is_ok(), "Simple comparison should succeed"); let (_cond_value, instructions) = result.unwrap(); @@ -472,7 +481,7 @@ mod tests { span: Span::unknown(), }; - let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env); + let result = lower_condition_to_joinir_no_body_locals(&ast, &mut alloc_value, &env); assert!(result.is_ok(), "Comparison with literal should succeed"); let (_cond_value, instructions) = result.unwrap(); @@ -523,7 +532,7 @@ mod tests { span: Span::unknown(), }; - let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env); + let result = lower_condition_to_joinir_no_body_locals(&ast, &mut alloc_value, &env); assert!(result.is_ok(), "OR expression should succeed"); let (_cond_value, instructions) = result.unwrap(); @@ -559,11 +568,173 @@ mod tests { span: Span::unknown(), }; - let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env); + let result = lower_condition_to_joinir_no_body_locals(&ast, &mut alloc_value, &env); assert!(result.is_ok(), "NOT operator should succeed"); let (_cond_value, instructions) = result.unwrap(); // Should have: Compare, UnaryOp(Not) assert_eq!(instructions.len(), 2, "Should generate Compare + Not"); } + + /// Phase 92 P4 Level 2: Test body-local variable resolution + /// + /// This test verifies that conditions can reference body-local variables + /// (e.g., `ch == '\\'` in escape sequence patterns). + /// + /// Variable resolution priority: + /// 1. ConditionEnv (loop parameters, captured variables) + /// 2. LoopBodyLocalEnv (body-local variables like `ch`) + #[test] + fn test_body_local_variable_resolution() { + // Setup ConditionEnv with loop variable + let mut env = ConditionEnv::new(); + env.insert("i".to_string(), ValueId(100)); + + // Setup LoopBodyLocalEnv with body-local variable + let mut body_local_env = LoopBodyLocalEnv::new(); + body_local_env.insert("ch".to_string(), ValueId(200)); + + let mut value_counter = 300u32; + let mut alloc_value = || { + let id = ValueId(value_counter); + value_counter += 1; + id + }; + + // AST: ch == "\\" + let ast = ASTNode::BinaryOp { + operator: BinaryOperator::Equal, + left: Box::new(ASTNode::Variable { + name: "ch".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::String("\\".to_string()), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + // Phase 92 P2-2: Use lower_condition_to_joinir with body_local_env + let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env)); + assert!( + result.is_ok(), + "Body-local variable resolution should succeed" + ); + + let (cond_value, instructions) = result.unwrap(); + // Should have: Const("\\"), Compare(ch == "\\") + assert_eq!( + instructions.len(), + 2, + "Should generate Const + Compare for body-local variable" + ); + + // Verify the comparison uses the body-local variable's ValueId(200) + if let Some(JoinInst::Compute(MirLikeInst::Compare { lhs, .. })) = instructions.get(1) { + assert_eq!( + *lhs, + ValueId(200), + "Compare should use body-local variable ValueId(200)" + ); + } else { + panic!("Expected Compare instruction at position 1"); + } + + assert!(cond_value.0 >= 300, "Result should use newly allocated ValueId"); + } + + /// Phase 92 P4 Level 2: Test variable resolution priority (ConditionEnv takes precedence) + /// + /// When a variable exists in both ConditionEnv and LoopBodyLocalEnv, + /// ConditionEnv should take priority. + #[test] + fn test_variable_resolution_priority() { + // Setup both environments with overlapping variable "x" + let mut env = ConditionEnv::new(); + env.insert("x".to_string(), ValueId(100)); // ConditionEnv priority + + let mut body_local_env = LoopBodyLocalEnv::new(); + body_local_env.insert("x".to_string(), ValueId(200)); // Should be shadowed + + let mut value_counter = 300u32; + let mut alloc_value = || { + let id = ValueId(value_counter); + value_counter += 1; + id + }; + + // AST: x == 42 + let ast = ASTNode::BinaryOp { + operator: BinaryOperator::Equal, + left: Box::new(ASTNode::Variable { + name: "x".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(42), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env)); + assert!(result.is_ok(), "Variable resolution should succeed"); + + let (_cond_value, instructions) = result.unwrap(); + + // Verify the comparison uses ConditionEnv's ValueId(100), not LoopBodyLocalEnv's ValueId(200) + if let Some(JoinInst::Compute(MirLikeInst::Compare { lhs, .. })) = instructions.get(1) { + assert_eq!( + *lhs, + ValueId(100), + "ConditionEnv should take priority over LoopBodyLocalEnv" + ); + } else { + panic!("Expected Compare instruction at position 1"); + } + } + + /// Phase 92 P4 Level 2: Test error handling for undefined variables + /// + /// Variables not found in either environment should produce clear error messages. + #[test] + fn test_undefined_variable_error() { + let env = ConditionEnv::new(); + let body_local_env = LoopBodyLocalEnv::new(); + + let mut value_counter = 300u32; + let mut alloc_value = || { + let id = ValueId(value_counter); + value_counter += 1; + id + }; + + // AST: undefined_var == 42 + let ast = ASTNode::BinaryOp { + operator: BinaryOperator::Equal, + left: Box::new(ASTNode::Variable { + name: "undefined_var".to_string(), + span: Span::unknown(), + }), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(42), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env, Some(&body_local_env)); + assert!(result.is_err(), "Undefined variable should fail"); + + let err = result.unwrap_err(); + assert!( + err.contains("undefined_var"), + "Error message should mention the undefined variable name" + ); + assert!( + err.contains("not found"), + "Error message should indicate variable was not found" + ); + } } diff --git a/src/mir/join_ir/lowering/condition_to_joinir.rs b/src/mir/join_ir/lowering/condition_to_joinir.rs index 5cedefff..433a886d 100644 --- a/src/mir/join_ir/lowering/condition_to_joinir.rs +++ b/src/mir/join_ir/lowering/condition_to_joinir.rs @@ -30,7 +30,9 @@ // Re-export public API from specialized modules pub use super::condition_env::{ConditionBinding, ConditionEnv}; -pub use super::condition_lowerer::{lower_condition_to_joinir, lower_value_expression}; +pub use super::condition_lowerer::{ + lower_condition_to_joinir, lower_condition_to_joinir_no_body_locals, lower_value_expression, +}; pub use super::condition_var_extractor::extract_condition_variables; // Re-export JoinIR types for convenience @@ -86,7 +88,7 @@ mod api_tests { span: Span::unknown(), }; - let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env); + let result = lower_condition_to_joinir_no_body_locals(&ast, &mut alloc_value, &env); assert!(result.is_ok()); } @@ -143,7 +145,7 @@ mod api_tests { id }; - let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env); + let result = lower_condition_to_joinir_no_body_locals(&ast, &mut alloc_value, &env); assert!(result.is_ok()); let (_cond_value, instructions) = result.unwrap(); diff --git a/src/mir/join_ir/lowering/debug_output_box.rs b/src/mir/join_ir/lowering/debug_output_box.rs index 66a0da15..ae7a32b3 100644 --- a/src/mir/join_ir/lowering/debug_output_box.rs +++ b/src/mir/join_ir/lowering/debug_output_box.rs @@ -22,7 +22,7 @@ //! - Feature-gated (no-op in production) //! - Zero runtime cost when disabled -use crate::config::env::is_joinir_debug; +use crate::config::env::{is_joinir_debug, joinir_dev_enabled}; /// DebugOutputBox: Centralized debug output for JoinIR lowering /// @@ -50,6 +50,28 @@ impl DebugOutputBox { } } + /// Create a DebugOutputBox with an explicit enabled flag. + /// + /// Use this when the caller already has a higher-level gate (e.g. a `verbose` flag) + /// and wants consistent formatting without re-checking env vars. + pub fn new_with_enabled(context_tag: impl Into, enabled: bool) -> Self { + Self { + enabled, + context_tag: context_tag.into(), + } + } + + /// Create a DebugOutputBox enabled by JoinIR dev mode (NYASH_JOINIR_DEV=1). + /// + /// This is useful for "developer convenience" logs that should not require + /// explicitly setting HAKO_JOINIR_DEBUG, but still must stay opt-in. + pub fn new_dev(context_tag: impl Into) -> Self { + Self { + enabled: joinir_dev_enabled(), + context_tag: context_tag.into(), + } + } + /// Log a debug message with category /// /// Output format: `[context_tag/category] message` diff --git a/src/mir/join_ir/lowering/expr_lowerer.rs b/src/mir/join_ir/lowering/expr_lowerer.rs index 93edd467..bb3aceb0 100644 --- a/src/mir/join_ir/lowering/expr_lowerer.rs +++ b/src/mir/join_ir/lowering/expr_lowerer.rs @@ -15,7 +15,7 @@ //! **Fail-Safe**: Unsupported AST nodes return explicit errors, allowing callers //! to fall back to legacy paths. -use super::condition_lowerer::lower_condition_to_joinir; +use super::condition_lowerer::lower_condition_to_joinir_no_body_locals; use super::scope_manager::ScopeManager; use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; @@ -211,7 +211,7 @@ impl<'env, 'builder, S: ScopeManager> ExprLowerer<'env, 'builder, S> { }; let (result_value, instructions) = - lower_condition_to_joinir(ast, &mut alloc_value, &condition_env, None) // Phase 92 P2-2 + lower_condition_to_joinir_no_body_locals(ast, &mut alloc_value, &condition_env) // Phase 92 P2-2 .map_err(|e| ExprLoweringError::LoweringError(e))?; // Phase 235: 保存しておき、テストから観察できるようにする @@ -297,7 +297,7 @@ impl<'env, 'builder, S: ScopeManager> ConditionLoweringBox for ExprLowerer<'e // Delegate to the well-tested lowerer, but use the caller-provided allocator (SSOT). let (result_value, instructions) = - lower_condition_to_joinir(condition, &mut *context.alloc_value, &condition_env, None) // Phase 92 P2-2 + lower_condition_to_joinir_no_body_locals(condition, &mut *context.alloc_value, &condition_env) // Phase 92 P2-2 .map_err(|e| e.to_string())?; self.last_instructions = instructions; diff --git a/src/mir/join_ir/lowering/loop_body_local_init.rs b/src/mir/join_ir/lowering/loop_body_local_init.rs index 32aa8eb7..611a2bd1 100644 --- a/src/mir/join_ir/lowering/loop_body_local_init.rs +++ b/src/mir/join_ir/lowering/loop_body_local_init.rs @@ -36,6 +36,7 @@ use crate::ast::ASTNode; use crate::mir::join_ir::lowering::condition_env::ConditionEnv; +use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox; use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; use crate::mir::join_ir::lowering::method_call_lowerer::MethodCallLowerer; use crate::mir::join_ir::{BinOpKind, ConstValue, JoinInst, MirLikeInst}; @@ -131,6 +132,7 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { body_ast: &[ASTNode], env: &mut LoopBodyLocalEnv, ) -> Result<(), String> { + let debug = DebugOutputBox::new_dev("loop_body_local_init"); for node in body_ast { if let ASTNode::Local { variables, @@ -138,7 +140,7 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { .. } = node { - self.lower_single_init(variables, initial_values, env)?; + self.lower_single_init(variables, initial_values, env, &debug)?; } } Ok(()) @@ -160,40 +162,29 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { variables: &[String], initial_values: &[Option>], env: &mut LoopBodyLocalEnv, + debug: &DebugOutputBox, ) -> Result<(), String> { // Handle each variable-value pair for (var_name, maybe_init_expr) in variables.iter().zip(initial_values.iter()) { // Skip if already has JoinIR ValueId (avoid duplicate lowering) if env.get(var_name).is_some() { - eprintln!( - "[loop_body_local_init] Skipping '{}' (already has ValueId)", - var_name - ); + debug.log("skip", &format!("'{}' (already has ValueId)", var_name)); continue; } // Skip if no initialization expression (e.g., `local temp` without `= ...`) let Some(init_expr) = maybe_init_expr else { - eprintln!( - "[loop_body_local_init] Skipping '{}' (no init expression)", - var_name - ); + debug.log("skip", &format!("'{}' (no init expression)", var_name)); continue; }; - eprintln!( - "[loop_body_local_init] Lowering init for '{}': {:?}", - var_name, init_expr - ); + debug.log_if_enabled(|| format!("lower '{}' = {:?}", var_name, init_expr)); // Lower init expression to JoinIR // Phase 226: Pass env for cascading LoopBodyLocal support let value_id = self.lower_init_expr(init_expr, env)?; - eprintln!( - "[loop_body_local_init] Stored '{}' → {:?}", - var_name, value_id - ); + debug.log("store", &format!("'{}' -> {:?}", var_name, value_id)); // Store in env env.insert(var_name.clone(), value_id); @@ -229,6 +220,7 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { expr: &ASTNode, env: &LoopBodyLocalEnv, ) -> Result { + let debug = DebugOutputBox::new_dev("loop_body_local_init"); match expr { // Constant literal: 42, 0, 1, "string" (use Literal with value) ASTNode::Literal { value, .. } => { @@ -239,10 +231,7 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { dst: vid, value: ConstValue::Integer(*i), })); - eprintln!( - "[loop_body_local_init] Const({}) → {:?}", - i, vid - ); + debug.log("const", &format!("Int({}) -> {:?}", i, vid)); Ok(vid) } // Phase 193: String literal support (for method args like "0") @@ -252,10 +241,7 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { dst: vid, value: ConstValue::String(s.clone()), })); - eprintln!( - "[loop_body_local_init] Const(\"{}\") → {:?}", - s, vid - ); + debug.log("const", &format!("String({:?}) -> {:?}", s, vid)); Ok(vid) } _ => Err(format!( @@ -273,16 +259,13 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { name ) })?; - eprintln!( - "[loop_body_local_init] Variable({}) → {:?}", - name, vid - ); + debug.log("var", &format!("Variable({}) → {:?}", name, vid)); Ok(vid) } // Binary operation: pos - start, i * 2, etc. ASTNode::BinaryOp { operator, left, right, .. } => { - eprintln!("[loop_body_local_init] BinaryOp({:?})", operator); + debug.log("binop", &format!("BinaryOp({:?})", operator)); // Recursively lower operands // Phase 226: Pass env for cascading support @@ -301,9 +284,9 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { rhs, })); - eprintln!( - "[loop_body_local_init] BinOp({:?}, {:?}, {:?}) → {:?}", - op_kind, lhs, rhs, result + debug.log( + "binop", + &format!("BinOp({:?}, {:?}, {:?}) → {:?}", op_kind, lhs, rhs, result), ); Ok(result) } @@ -420,14 +403,18 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { instructions: &mut Vec, alloc: &mut dyn FnMut() -> ValueId, ) -> Result { - eprintln!( - "[loop_body_local_init] MethodCall: {}.{}(...)", - if let ASTNode::Variable { name, .. } = receiver { - name - } else { - "?" - }, - method + let debug = DebugOutputBox::new_dev("loop_body_local_init"); + debug.log( + "method_call", + &format!( + "MethodCall: {}.{}(...)", + if let ASTNode::Variable { name, .. } = receiver { + name + } else { + "?" + }, + method + ), ); // 1. Resolve receiver (check LoopBodyLocalEnv first, then ConditionEnv) @@ -436,15 +423,15 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { ASTNode::Variable { name, .. } => { // Try LoopBodyLocalEnv first (for cascading cases like `digit_pos = digits.indexOf(ch)` where `digits` might be body-local) if let Some(vid) = body_local_env.get(name) { - eprintln!( - "[loop_body_local_init] Receiver '{}' found in LoopBodyLocalEnv → {:?}", - name, vid + debug.log( + "method_call", + &format!("Receiver '{}' found in LoopBodyLocalEnv → {:?}", name, vid), ); vid } else if let Some(vid) = cond_env.get(name) { - eprintln!( - "[loop_body_local_init] Receiver '{}' found in ConditionEnv → {:?}", - name, vid + debug.log( + "method_call", + &format!("Receiver '{}' found in ConditionEnv → {:?}", name, vid), ); vid } else { @@ -485,9 +472,9 @@ impl<'a> LoopBodyLocalInitLowerer<'a> { instructions, )?; - eprintln!( - "[loop_body_local_init] MethodCallLowerer completed → {:?}", - result_id + debug.log( + "method_call", + &format!("MethodCallLowerer completed → {:?}", result_id), ); Ok(result_id) diff --git a/src/mir/join_ir/lowering/loop_with_break_minimal.rs b/src/mir/join_ir/lowering/loop_with_break_minimal.rs index 584c6a21..d8296a33 100644 --- a/src/mir/join_ir/lowering/loop_with_break_minimal.rs +++ b/src/mir/join_ir/lowering/loop_with_break_minimal.rs @@ -77,19 +77,36 @@ use crate::mir::join_ir::lowering::step_schedule::{ build_pattern2_schedule, Pattern2ScheduleContext, Pattern2StepKind, }; use crate::mir::join_ir::lowering::update_env::UpdateEnv; +use crate::mir::loop_canonicalizer::LoopSkeleton; use crate::mir::join_ir::{ - BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst, + BinOpKind, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst, UnaryOp, }; use crate::mir::loop_pattern_detection::error_messages::{ - extract_body_local_names, format_unsupported_condition_error, + extract_body_local_names, }; use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox; use crate::mir::ValueId; +use crate::mir::join_ir::lowering::error_tags; +use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox; use boundary_builder::build_fragment_meta; use header_break_lowering::{lower_break_condition, lower_header_condition}; use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism +pub(crate) struct LoopWithBreakLoweringInputs<'a> { + pub scope: LoopScopeShape, + pub condition: &'a ASTNode, + pub break_condition: &'a ASTNode, + pub env: &'a ConditionEnv, + pub carrier_info: &'a CarrierInfo, + pub carrier_updates: &'a BTreeMap, + pub body_ast: &'a [ASTNode], + pub body_local_env: Option<&'a mut LoopBodyLocalEnv>, + pub allowed_body_locals_for_conditions: Option<&'a [String]>, + pub join_value_space: &'a mut JoinValueSpace, + pub skeleton: Option<&'a LoopSkeleton>, +} + /// Lower Pattern 2 (Loop with Conditional Break) to JoinIR /// /// # Phase 188-Impl-2: Pure JoinIR Fragment Generation @@ -150,17 +167,24 @@ use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for deter /// * `join_value_space` - Phase 201: Unified JoinIR ValueId allocator (Local region: 1000+) /// * `skeleton` - Phase 92 P0-3: Optional LoopSkeleton for ConditionalStep support pub(crate) fn lower_loop_with_break_minimal( - _scope: LoopScopeShape, - condition: &ASTNode, - break_condition: &ASTNode, - env: &ConditionEnv, - carrier_info: &CarrierInfo, - carrier_updates: &BTreeMap, // Phase 222.5-D: HashMap → BTreeMap for determinism - body_ast: &[ASTNode], - mut body_local_env: Option<&mut LoopBodyLocalEnv>, - join_value_space: &mut JoinValueSpace, - skeleton: Option<&crate::mir::loop_canonicalizer::LoopSkeleton>, + inputs: LoopWithBreakLoweringInputs<'_>, ) -> Result<(JoinModule, JoinFragmentMeta), String> { + let LoopWithBreakLoweringInputs { + scope: _scope, + condition, + break_condition, + env, + carrier_info, + carrier_updates, + body_ast, + body_local_env, + allowed_body_locals_for_conditions, + join_value_space, + skeleton, + } = inputs; + + let mut body_local_env = body_local_env; + let dev_log = DebugOutputBox::new_dev("joinir/pattern2"); // Phase 170-D-impl-3: Validate that conditions only use supported variable scopes // LoopConditionScopeBox checks that loop conditions don't reference loop-body-local variables let loop_var_name = &carrier_info.loop_var_name; // Phase 176-3: Extract from CarrierInfo @@ -174,32 +198,38 @@ pub(crate) fn lower_loop_with_break_minimal( let unpromoted_locals: Vec<&String> = body_local_names .iter() .filter(|name| !carrier_info.promoted_loopbodylocals.contains(*name)) + .filter(|name| { + allowed_body_locals_for_conditions + .map(|allow| !allow.iter().any(|s| s.as_str() == (*name).as_str())) + .unwrap_or(true) + }) .copied() .collect(); if !unpromoted_locals.is_empty() { - eprintln!( - "[joinir/pattern2] Phase 224: {} body-local variables after promotion filter: {:?}", - unpromoted_locals.len(), - unpromoted_locals - ); - return Err(format_unsupported_condition_error( - "pattern2", - &unpromoted_locals, - )); + return Err(error_tags::freeze(&format!( + "[pattern2/body_local_slot/contract/unhandled_vars] Unsupported LoopBodyLocal variables in condition: {:?} (promoted={:?}, allowed={:?})", + unpromoted_locals, + carrier_info.promoted_loopbodylocals, + allowed_body_locals_for_conditions.unwrap_or(&[]) + ))); } - eprintln!( - "[joinir/pattern2] Phase 224: All {} body-local variables were promoted: {:?}", - body_local_names.len(), - body_local_names - ); + dev_log.log_if_enabled(|| { + format!( + "Phase 224: All {} body-local variables were handled (promoted or allowed): {:?}", + body_local_names.len(), + body_local_names + ) + }); } - eprintln!( - "[joinir/pattern2] Phase 170-D: Condition variables verified: {:?}", - loop_cond_scope.var_names() - ); + dev_log.log_if_enabled(|| { + format!( + "Phase 170-D: Condition variables verified: {:?}", + loop_cond_scope.var_names() + ) + }); // Phase 201: Use JoinValueSpace for unified ValueId allocation // - Local region (1000+) ensures no collision with Param region (100-999) @@ -221,15 +251,17 @@ pub(crate) fn lower_loop_with_break_minimal( // Phase 176-3: Multi-carrier support - allocate parameters for all carriers let carrier_count = carrier_info.carriers.len(); - eprintln!( - "[joinir/pattern2] Phase 176-3: Generating JoinIR for {} carriers: {:?}", - carrier_count, - carrier_info - .carriers - .iter() - .map(|c| &c.name) - .collect::>() - ); + dev_log.log_if_enabled(|| { + format!( + "Phase 176-3: Generating JoinIR for {} carriers: {:?}", + carrier_count, + carrier_info + .carriers + .iter() + .map(|c| &c.name) + .collect::>() + ) + }); // Phase 201: main() parameters use Local region (entry point slots) // These don't need to match ConditionEnv - they're just input slots for main @@ -262,13 +294,15 @@ pub(crate) fn lower_loop_with_break_minimal( carrier_param_ids.push(carrier_join_id); } - eprintln!( - "[joinir/pattern2] Phase 201: loop_step params - i_param={:?}, carrier_params={:?}", - i_param, carrier_param_ids - ); - if crate::config::env::joinir_dev_enabled() || crate::config::env::joinir_test_debug_enabled() { - eprintln!( - "[joinir/pattern2/debug] loop_var='{}' env.get(loop_var)={:?}, carriers={:?}", + dev_log.log_if_enabled(|| { + format!( + "Phase 201: loop_step params - i_param={:?}, carrier_params={:?}", + i_param, carrier_param_ids + ) + }); + dev_log.log_if_enabled(|| { + format!( + "loop_var='{}' env.get(loop_var)={:?}, carriers={:?}", loop_var_name, env.get(loop_var_name), carrier_info @@ -276,8 +310,8 @@ pub(crate) fn lower_loop_with_break_minimal( .iter() .map(|c| (c.name.clone(), c.join_id)) .collect::>() - ); - } + ) + }); // Phase 169 / Phase 171-fix / Phase 240-EX / Phase 244: Lower condition let (cond_value, mut cond_instructions) = lower_header_condition( @@ -398,10 +432,12 @@ pub(crate) fn lower_loop_with_break_minimal( init_lowerer.lower_inits_for_loop(body_ast, body_env)?; - eprintln!( - "[joinir/pattern2] Phase 191/246-EX: Lowered {} body-local init expressions (scheduled block before break)", - body_env.len() - ); + dev_log.log_if_enabled(|| { + format!( + "Phase 191/246-EX: Lowered {} body-local init expressions (scheduled block before break)", + body_env.len() + ) + }); } // ------------------------------------------------------------------ @@ -427,66 +463,14 @@ pub(crate) fn lower_loop_with_break_minimal( // and lowered before body-local init. It references the carrier param which has stale values. // // Solution: Replace references to promoted carriers with fresh body-local computations. - // For "!is_digit_pos", we replace "is_digit_pos" with a fresh comparison of "digit_pos >= 0". - for inst in break_cond_instructions.into_iter() { - if let JoinInst::Compute(MirLikeInst::UnaryOp { - op: UnaryOp::Not, - operand, - dst, - }) = inst - { - let mut operand_value = operand; - // Check if operand is a promoted carrier (e.g., is_digit_pos) - for carrier in &carrier_info.carriers { - if carrier.join_id == Some(operand_value) { - if let Some(stripped) = carrier.name.strip_prefix("is_") { - // Phase 246-EX: "is_digit_pos" → "digit_pos" (no additional suffix needed) - let source_name = stripped.to_string(); - if let Some(src_val) = body_local_env - .as_ref() - .and_then(|env| env.get(&source_name)) - { - eprintln!( - "[joinir/pattern2] Phase 246-EX: Rewriting break condition - replacing carrier '{}' ({:?}) with fresh body-local '{}' ({:?})", - carrier.name, operand_value, source_name, src_val - ); - - // Emit fresh comparison: is_digit_pos = (digit_pos >= 0) - let zero = alloc_value(); - break_block.push(JoinInst::Compute(MirLikeInst::Const { - dst: zero, - value: ConstValue::Integer(0), - })); - - let fresh_bool = alloc_value(); - break_block.push(JoinInst::Compute(MirLikeInst::Compare { - dst: fresh_bool, - op: CompareOp::Ge, - lhs: src_val, - rhs: zero, - })); - - // Update the UnaryOp to use the fresh boolean - operand_value = fresh_bool; - - eprintln!( - "[joinir/pattern2] Phase 246-EX: Break condition now uses fresh value {:?} instead of stale carrier param {:?}", - fresh_bool, carrier.join_id - ); - } - } - } - } - - break_block.push(JoinInst::Compute(MirLikeInst::UnaryOp { - dst, - op: UnaryOp::Not, - operand: operand_value, - })); - } else { - break_block.push(inst); - } - } + // (See common::dual_value_rewriter for the name-based rules.) + use crate::mir::join_ir::lowering::common::dual_value_rewriter::rewrite_break_condition_insts; + break_block.extend(rewrite_break_condition_insts( + break_cond_instructions, + carrier_info, + body_local_env.as_ref().map(|e| &**e), + &mut alloc_value, + )); // Phase 176-3: Multi-carrier support - Jump includes all carrier values // Jump(k_exit, [i, carrier1, carrier2, ...], cond=break_cond) // Break exit path @@ -510,24 +494,13 @@ pub(crate) fn lower_loop_with_break_minimal( // Phase 247-EX: Loop-local derived carriers (e.g., digit_value) take the body-local // computed value (digit_pos) as their update source each iteration. if carrier.init == CarrierInit::LoopLocalZero { - if let Some(stripped) = carrier_name.strip_suffix("_value") { - let source_name = format!("{}_pos", stripped); - if let Some(src_val) = body_local_env - .as_ref() - .and_then(|env| env.get(&source_name)) - { - updated_carrier_values.push(src_val); - eprintln!( - "[loop/carrier_update] Phase 247-EX: Loop-local carrier '{}' updated from body-local '{}' → {:?}", - carrier_name, source_name, src_val - ); - continue; - } else { - eprintln!( - "[loop/carrier_update] Phase 247-EX WARNING: loop-local carrier '{}' could not find body-local source '{}'", - carrier_name, source_name - ); - } + use crate::mir::join_ir::lowering::common::dual_value_rewriter::try_derive_looplocal_from_bodylocal_pos; + if let Some(src_val) = try_derive_looplocal_from_bodylocal_pos( + carrier_name, + body_local_env.as_ref().map(|e| &**e), + ) { + updated_carrier_values.push(src_val); + continue; } } @@ -538,33 +511,15 @@ pub(crate) fn lower_loop_with_break_minimal( use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole}; if carrier.role == CarrierRole::ConditionOnly { // Phase 247-EX: If this is a promoted digit_pos boolean carrier, derive from body-local digit_pos - if let Some(stripped) = carrier_name.strip_prefix("is_") { - let source_name = format!("{}_pos", stripped); - if let Some(src_val) = body_local_env - .as_ref() - .and_then(|env| env.get(&source_name)) - { - let zero = alloc_value(); - carrier_update_block.push(JoinInst::Compute(MirLikeInst::Const { - dst: zero, - value: ConstValue::Integer(0), - })); - - let cmp = alloc_value(); - carrier_update_block.push(JoinInst::Compute(MirLikeInst::Compare { - dst: cmp, - op: CompareOp::Ge, - lhs: src_val, - rhs: zero, - })); - - updated_carrier_values.push(cmp); - eprintln!( - "[loop/carrier_update] Phase 247-EX: ConditionOnly carrier '{}' derived from body-local '{}' → {:?}", - carrier_name, source_name, cmp - ); - continue; - } + use crate::mir::join_ir::lowering::common::dual_value_rewriter::try_derive_conditiononly_is_from_bodylocal_pos; + if let Some(cmp) = try_derive_conditiononly_is_from_bodylocal_pos( + carrier_name, + body_local_env.as_ref().map(|e| &**e), + &mut alloc_value, + &mut carrier_update_block, + ) { + updated_carrier_values.push(cmp); + continue; } // ConditionOnly carrier fallback: just pass through the current value @@ -573,10 +528,12 @@ pub(crate) fn lower_loop_with_break_minimal( format!("ConditionOnly carrier '{}' not found in env", carrier_name) })?; updated_carrier_values.push(current_value); - eprintln!( - "[loop/carrier_update] Phase 227: ConditionOnly carrier '{}' passthrough: {:?}", - carrier_name, current_value - ); + dev_log.log_if_enabled(|| { + format!( + "[carrier_update] Phase 227: ConditionOnly '{}' passthrough: {:?}", + carrier_name, current_value + ) + }); continue; } @@ -590,10 +547,12 @@ pub(crate) fn lower_loop_with_break_minimal( .get(carrier_name) .ok_or_else(|| format!("FromHost carrier '{}' not found in env", carrier_name))?; updated_carrier_values.push(current_value); - eprintln!( - "[loop/carrier_update] Phase 247-EX: FromHost carrier '{}' passthrough: {:?}", - carrier_name, current_value - ); + dev_log.log_if_enabled(|| { + format!( + "[carrier_update] Phase 247-EX: FromHost '{}' passthrough: {:?}", + carrier_name, current_value + ) + }); continue; } @@ -602,10 +561,12 @@ pub(crate) fn lower_loop_with_break_minimal( if let Some(carrier_slot) = skel.carriers.iter().find(|c| c.name == *carrier_name) { if let UpdateKind::ConditionalStep { cond, then_delta, else_delta } = &carrier_slot.update_kind { // Phase 92 P2-1: Use ConditionalStepEmitter (dedicated module) - eprintln!( - "[joinir/pattern2] Phase 92 P2-1: ConditionalStep detected for carrier '{}': then={}, else={}", - carrier_name, then_delta, else_delta - ); + dev_log.log_if_enabled(|| { + format!( + "Phase 92 P2-1: ConditionalStep detected for carrier '{}': then={}, else={}", + carrier_name, then_delta, else_delta + ) + }); // Phase 92 P2-1: Get carrier parameter ValueId (must be set by header PHI) let carrier_param = carrier.join_id.ok_or_else(|| { @@ -628,10 +589,12 @@ pub(crate) fn lower_loop_with_break_minimal( &mut carrier_update_block, ).map_err(|e| format!("[pattern2/conditional_step] {}", e))?; updated_carrier_values.push(updated_value); - eprintln!( - "[joinir/pattern2] Phase 92 P2-1: ConditionalStep carrier '{}' updated → {:?}", - carrier_name, updated_value - ); + dev_log.log_if_enabled(|| { + format!( + "Phase 92 P2-1: ConditionalStep carrier '{}' updated -> {:?}", + carrier_name, updated_value + ) + }); continue; // Skip normal carrier update } } @@ -670,10 +633,12 @@ pub(crate) fn lower_loop_with_break_minimal( updated_carrier_values.push(updated_value); - eprintln!( - "[joinir/pattern2] Phase 176-3: Carrier '{}' update: {:?} -> {:?}", - carrier_name, carrier_param_ids[idx], updated_value - ); + dev_log.log_if_enabled(|| { + format!( + "Phase 176-3: Carrier '{}' update: {:?} -> {:?}", + carrier_name, carrier_param_ids[idx], updated_value + ) + }); } // Phase 176-3: Multi-carrier support - tail call includes all updated carriers @@ -696,11 +661,13 @@ pub(crate) fn lower_loop_with_break_minimal( let mut tail_call_args = vec![i_next]; tail_call_args.extend(updated_carrier_values.iter().copied()); - eprintln!( - "[joinir/pattern2/debug] Tail call args count: {}, updated_carrier_values: {:?}", - tail_call_args.len(), - updated_carrier_values - ); + dev_log.log_if_enabled(|| { + format!( + "tail call args count: {}, updated_carrier_values: {:?}", + tail_call_args.len(), + updated_carrier_values + ) + }); tail_block.push(JoinInst::Call { func: loop_step_id, @@ -746,16 +713,16 @@ pub(crate) fn lower_loop_with_break_minimal( let debug_dump = crate::config::env::joinir_debug_level() > 0; if debug_dump { - eprintln!( - "[joinir/pattern2] k_exit param layout: i_exit={:?}, carrier_exit_ids={:?}", - i_exit, carrier_exit_ids - ); + let strict_debug = DebugOutputBox::new("joinir/pattern2"); + strict_debug.log_if_enabled(|| { + format!( + "k_exit param layout: i_exit={:?}, carrier_exit_ids={:?}", + i_exit, carrier_exit_ids + ) + }); for (idx, carrier) in carrier_info.carriers.iter().enumerate() { let exit_id = carrier_exit_ids.get(idx).copied().unwrap_or(ValueId(0)); - eprintln!( - "[joinir/pattern2] carrier '{}' exit → {:?}", - carrier.name, exit_id - ); + strict_debug.log("k_exit", &format!("carrier '{}' exit -> {:?}", carrier.name, exit_id)); } } @@ -777,19 +744,21 @@ pub(crate) fn lower_loop_with_break_minimal( // Set entry point join_module.entry = Some(main_id); - eprintln!("[joinir/pattern2] Generated JoinIR for Loop with Break Pattern (Phase 170-B)"); - eprintln!("[joinir/pattern2] Functions: main, loop_step, k_exit"); - eprintln!("[joinir/pattern2] Loop condition from AST (delegated to condition_to_joinir)"); - eprintln!("[joinir/pattern2] Break condition from AST (delegated to condition_to_joinir)"); - eprintln!("[joinir/pattern2] Exit PHI: k_exit receives i from both natural exit and break"); + dev_log.log_simple("Generated JoinIR for Loop with Break Pattern (Phase 170-B)"); + dev_log.log_simple("Functions: main, loop_step, k_exit"); + dev_log.log_simple("Loop condition from AST (delegated to condition_to_joinir)"); + dev_log.log_simple("Break condition from AST (delegated to condition_to_joinir)"); + dev_log.log_simple("Exit PHI: k_exit receives i from both natural exit and break"); let fragment_meta = build_fragment_meta(carrier_info, loop_var_name, i_exit, &carrier_exit_ids); - eprintln!( - "[joinir/pattern2] Phase 33-14/176-3: JoinFragmentMeta {{ expr_result: {:?}, carriers: {} }}", - i_exit, - carrier_info.carriers.len() - ); + dev_log.log_if_enabled(|| { + format!( + "Phase 33-14/176-3: JoinFragmentMeta {{ expr_result: {:?}, carriers: {} }}", + i_exit, + carrier_info.carriers.len() + ) + }); Ok((join_module, fragment_meta)) } diff --git a/src/mir/join_ir/lowering/loop_with_break_minimal/header_break_lowering.rs b/src/mir/join_ir/lowering/loop_with_break_minimal/header_break_lowering.rs index 4fb66ac4..ed8fa49a 100644 --- a/src/mir/join_ir/lowering/loop_with_break_minimal/header_break_lowering.rs +++ b/src/mir/join_ir/lowering/loop_with_break_minimal/header_break_lowering.rs @@ -3,7 +3,8 @@ use crate::mir::builder::MirBuilder; use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; use crate::mir::join_ir::lowering::condition_env::ConditionEnv; use crate::mir::join_ir::lowering::condition_lowering_box::ConditionContext; -use crate::mir::join_ir::lowering::condition_to_joinir::lower_condition_to_joinir; +use crate::mir::join_ir::lowering::condition_to_joinir::lower_condition_to_joinir_no_body_locals; +use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox; use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer}; use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; @@ -37,6 +38,8 @@ pub(crate) fn lower_header_condition( ) -> Result<(ValueId, Vec), String> { use crate::mir::join_ir::lowering::condition_lowering_box::ConditionLoweringBox; + let debug = DebugOutputBox::new_dev("joinir/pattern2"); + let empty_body_env = LoopBodyLocalEnv::new(); let empty_captured_env = CapturedEnv::new(); let scope_manager = make_scope_manager( @@ -61,9 +64,12 @@ pub(crate) fn lower_header_condition( match expr_lowerer.lower_condition(condition, &mut context) { Ok(value_id) => { let instructions = expr_lowerer.take_last_instructions(); - eprintln!( - "[joinir/pattern2/phase244] Header condition via ConditionLoweringBox: {} instructions", - instructions.len() + debug.log( + "phase244", + &format!( + "Header condition via ConditionLoweringBox: {} instructions", + instructions.len() + ), ); Ok((value_id, instructions)) } @@ -73,11 +79,12 @@ pub(crate) fn lower_header_condition( )), } } else { - eprintln!( - "[joinir/pattern2/phase244] Header condition via legacy path (not yet supported by ConditionLoweringBox)" + debug.log( + "phase244", + "Header condition via legacy path (not yet supported by ConditionLoweringBox)", ); let mut shim = || alloc_value(); - lower_condition_to_joinir(condition, &mut shim, env, None) // Phase 92 P2-2: No body-local for header + lower_condition_to_joinir_no_body_locals(condition, &mut shim, env) // Phase 92 P2-2: No body-local for header } } diff --git a/src/mir/join_ir/lowering/loop_with_break_minimal/tests.rs b/src/mir/join_ir/lowering/loop_with_break_minimal/tests.rs index e7b2367e..cc4e9d8f 100644 --- a/src/mir/join_ir/lowering/loop_with_break_minimal/tests.rs +++ b/src/mir/join_ir/lowering/loop_with_break_minimal/tests.rs @@ -134,18 +134,19 @@ fn test_pattern2_header_condition_via_exprlowerer() { let carrier_updates = BTreeMap::new(); let mut join_value_space = JoinValueSpace::new(); - let result = lower_loop_with_break_minimal( + let result = lower_loop_with_break_minimal(super::LoopWithBreakLoweringInputs { scope, - &loop_cond, - &break_cond, - &condition_env, - &carrier_info, - &carrier_updates, - &[], - None, - &mut join_value_space, - None, // Phase 92 P0-3: skeleton=None for backward compatibility - ); + condition: &loop_cond, + break_condition: &break_cond, + env: &condition_env, + carrier_info: &carrier_info, + carrier_updates: &carrier_updates, + body_ast: &[], + body_local_env: None, + allowed_body_locals_for_conditions: None, + join_value_space: &mut join_value_space, + skeleton: None, // Phase 92 P0-3: skeleton=None for backward compatibility + }); assert!(result.is_ok(), "ExprLowerer header path should succeed"); diff --git a/src/mir/join_ir/lowering/loop_with_continue_minimal.rs b/src/mir/join_ir/lowering/loop_with_continue_minimal.rs index 0f7b0ee2..0acc79f8 100644 --- a/src/mir/join_ir/lowering/loop_with_continue_minimal.rs +++ b/src/mir/join_ir/lowering/loop_with_continue_minimal.rs @@ -60,7 +60,10 @@ use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta}; -use crate::mir::join_ir::lowering::condition_to_joinir::{lower_condition_to_joinir, ConditionEnv}; +use crate::mir::join_ir::lowering::condition_to_joinir::{ + lower_condition_to_joinir_no_body_locals, ConditionEnv, +}; +use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox; use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; // Phase 244 use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; @@ -144,22 +147,29 @@ pub(crate) fn lower_loop_with_continue_minimal( )); } - eprintln!( - "[joinir/pattern4] Phase 170-D: Condition variables verified: {:?}", - loop_cond_scope.var_names() + let debug = DebugOutputBox::new_dev("joinir/pattern4"); + debug.log( + "phase170d", + &format!( + "Condition variables verified: {:?}", + loop_cond_scope.var_names() + ), ); let mut join_module = JoinModule::new(); let carrier_count = carrier_info.carriers.len(); - eprintln!( - "[joinir/pattern4] Phase 202-C: Generating JoinIR for {} carriers: {:?}", - carrier_count, - carrier_info - .carriers - .iter() - .map(|c| &c.name) - .collect::>() + debug.log( + "phase202c", + &format!( + "Generating JoinIR for {} carriers: {:?}", + carrier_count, + carrier_info + .carriers + .iter() + .map(|c| &c.name) + .collect::>() + ), ); // ================================================================== @@ -208,13 +218,18 @@ pub(crate) fn lower_loop_with_continue_minimal( // Phase 80-C (P2): Register BindingIds for condition variables (dev-only) #[cfg(feature = "normalized_dev")] if let Some(binding_map) = binding_map { + let debug = DebugOutputBox::new_dev("phase80/p4"); + // Register loop variable BindingId if let Some(bid) = binding_map.get(&loop_var_name) { env.register_loop_var_binding(*bid, i_param); #[cfg(debug_assertions)] - eprintln!( - "[phase80/p4] Registered loop var '{}' BindingId({}) -> ValueId({})", - loop_var_name, bid.0, i_param.0 + debug.log( + "register", + &format!( + "Registered loop var '{}' BindingId({}) -> ValueId({})", + loop_var_name, bid.0, i_param.0 + ), ); } @@ -223,9 +238,12 @@ pub(crate) fn lower_loop_with_continue_minimal( if let Some(bid) = binding_map.get(var_name) { env.register_condition_binding(*bid, *join_id); #[cfg(debug_assertions)] - eprintln!( - "[phase80/p4] Registered condition binding '{}' BindingId({}) -> ValueId({})", - var_name, bid.0, join_id.0 + debug.log( + "register", + &format!( + "Registered condition binding '{}' BindingId({}) -> ValueId({})", + var_name, bid.0, join_id.0 + ), ); } } @@ -244,6 +262,8 @@ pub(crate) fn lower_loop_with_continue_minimal( use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer}; use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; + let debug = DebugOutputBox::new_dev("joinir/pattern4"); + // Build minimal ScopeManager for header condition let empty_body_env = LoopBodyLocalEnv::new(); let empty_captured_env = CapturedEnv::new(); @@ -271,7 +291,13 @@ pub(crate) fn lower_loop_with_continue_minimal( match expr_lowerer.lower_condition(condition, &mut context) { Ok(value_id) => { let instructions = expr_lowerer.take_last_instructions(); - eprintln!("[joinir/pattern4/phase244] Header condition via ConditionLoweringBox: {} instructions", instructions.len()); + debug.log( + "phase244", + &format!( + "Header condition via ConditionLoweringBox: {} instructions", + instructions.len() + ), + ); (value_id, instructions) } Err(e) => { @@ -283,8 +309,11 @@ pub(crate) fn lower_loop_with_continue_minimal( } } else { // Legacy path: condition_to_joinir (for complex conditions not yet supported) - eprintln!("[joinir/pattern4/phase244] Header condition via legacy path (not yet supported by ConditionLoweringBox)"); - lower_condition_to_joinir(condition, &mut alloc_value, &env, None)? // Phase 92 P2-2: No body-local for header + debug.log( + "phase244", + "Header condition via legacy path (not yet supported by ConditionLoweringBox)", + ); + lower_condition_to_joinir_no_body_locals(condition, &mut alloc_value, &env)? // Phase 92 P2-2: No body-local for header } }; @@ -443,20 +472,26 @@ pub(crate) fn lower_loop_with_continue_minimal( let carrier_name = &carrier_info.carriers[idx].name; // Phase 197: Extract RHS from update expression metadata - eprintln!( - "[loop_with_continue_minimal] Processing carrier '{}' (idx={})", - carrier_name, idx + debug.log( + "carrier_update", + &format!("Processing carrier '{}' (idx={})", carrier_name, idx), ); let rhs = if let Some(update_expr) = carrier_updates.get(carrier_name) { - eprintln!( - "[loop_with_continue_minimal] Found update expr: {:?}", - update_expr + debug.log( + "carrier_update", + &format!("Found update expr: {:?}", update_expr), ); match update_expr { UpdateExpr::BinOp { op, rhs, .. } => { // Verify operator is Add (only supported for now) if *op != BinOpKind::Add { - eprintln!("[loop_with_continue_minimal] Warning: carrier '{}' uses unsupported operator {:?}, defaulting to Add", carrier_name, op); + debug.log( + "warn", + &format!( + "Carrier '{}' uses unsupported operator {:?}, defaulting to Add", + carrier_name, op + ), + ); } // Generate RHS value based on update expression @@ -466,7 +501,13 @@ pub(crate) fn lower_loop_with_continue_minimal( const_1 } else { // Need to allocate a new constant value - eprintln!("[loop_with_continue_minimal] Warning: carrier '{}' uses const {}, only const_1 is pre-allocated, using const_1", carrier_name, n); + debug.log( + "warn", + &format!( + "Carrier '{}' uses const {}, only const_1 is pre-allocated, using const_1", + carrier_name, n + ), + ); const_1 } } @@ -474,19 +515,34 @@ pub(crate) fn lower_loop_with_continue_minimal( if var_name == &carrier_info.loop_var_name { // sum = sum + i → use i_next (incremented value) // Because in the source: i = i + 1 happens FIRST, then sum = sum + i uses the NEW value - eprintln!("[loop_with_continue_minimal] Using i_next (ValueId({})) for variable '{}'", i_next.0, var_name); + debug.log( + "carrier_update", + &format!( + "Using i_next (ValueId({})) for variable '{}'", + i_next.0, var_name + ), + ); i_next } else { - eprintln!("[loop_with_continue_minimal] Warning: carrier '{}' updates with unknown variable '{}', using const_1", carrier_name, var_name); + debug.log( + "warn", + &format!( + "Carrier '{}' updates with unknown variable '{}', using const_1", + carrier_name, var_name + ), + ); const_1 } } // Phase 190: Number accumulation not supported in Pattern 4 yet // Skip JoinIR update - use Select passthrough to keep carrier_merged defined UpdateRhs::NumberAccumulation { .. } => { - eprintln!( - "[loop_with_continue_minimal] Phase 190: Carrier '{}' has number accumulation - not supported in Pattern 4, using Select passthrough", - carrier_name + debug.log( + "phase190", + &format!( + "Carrier '{}' has number accumulation - not supported in Pattern 4, using Select passthrough", + carrier_name + ), ); // Emit Select with same values: carrier_merged = Select(_, carrier_param, carrier_param) // This is effectively a passthrough (no JoinIR update) @@ -502,9 +558,12 @@ pub(crate) fn lower_loop_with_continue_minimal( // Phase 178: String updates detected but not lowered to JoinIR yet // Skip JoinIR update - use Select passthrough to keep carrier_merged defined UpdateRhs::StringLiteral(_) | UpdateRhs::Other => { - eprintln!( - "[loop_with_continue_minimal] Phase 178: Carrier '{}' has string/complex update - skipping BinOp, using Select passthrough", - carrier_name + debug.log( + "phase178", + &format!( + "Carrier '{}' has string/complex update - skipping BinOp, using Select passthrough", + carrier_name + ), ); // Emit Select with same values: carrier_merged = Select(_, carrier_param, carrier_param) // This is effectively a passthrough (no JoinIR update) @@ -524,21 +583,36 @@ pub(crate) fn lower_loop_with_continue_minimal( if *n == 1 { const_1 } else { - eprintln!("[loop_with_continue_minimal] Warning: carrier '{}' uses const {}, only const_1 is pre-allocated, using const_1", carrier_name, n); + debug.log( + "warn", + &format!( + "Carrier '{}' uses const {}, only const_1 is pre-allocated, using const_1", + carrier_name, n + ), + ); const_1 } } } } else { // No update expression found - fallback to const_1 (safe default) - eprintln!("[loop_with_continue_minimal] Warning: no update expression for carrier '{}', defaulting to +1", carrier_name); + debug.log( + "warn", + &format!( + "No update expression for carrier '{}', defaulting to +1", + carrier_name + ), + ); const_1 }; // carrier_next = carrier_param + rhs - eprintln!( - "[loop_with_continue_minimal] Generating: ValueId({}) = ValueId({}) + ValueId({})", - carrier_next.0, carrier_param.0, rhs.0 + debug.log( + "carrier_update", + &format!( + "Generating: ValueId({}) = ValueId({}) + ValueId({})", + carrier_next.0, carrier_param.0, rhs.0 + ), ); loop_step_func .body @@ -595,18 +669,21 @@ pub(crate) fn lower_loop_with_continue_minimal( // Set entry point join_module.entry = Some(main_id); - eprintln!("[joinir/pattern4] Phase 202-C: Generated JoinIR for Loop with Continue Pattern"); - eprintln!("[joinir/pattern4] Functions: main, loop_step, k_exit"); - eprintln!("[joinir/pattern4] Continue: Select-based skip"); - eprintln!("[joinir/pattern4] ValueId allocation: JoinValueSpace (Local region 1000+)"); - eprintln!( - "[joinir/pattern4] Carriers: {} ({:?})", - carrier_count, - carrier_info - .carriers - .iter() - .map(|c| c.name.as_str()) - .collect::>() + debug.log_simple("Phase 202-C: Generated JoinIR for Loop with Continue Pattern"); + debug.log_simple("Functions: main, loop_step, k_exit"); + debug.log_simple("Continue: Select-based skip"); + debug.log_simple("ValueId allocation: JoinValueSpace (Local region 1000+)"); + debug.log( + "summary", + &format!( + "Carriers: {} ({:?})", + carrier_count, + carrier_info + .carriers + .iter() + .map(|c| c.name.as_str()) + .collect::>() + ), ); // ================================================================== @@ -623,17 +700,23 @@ pub(crate) fn lower_loop_with_continue_minimal( // Phase 197-B: Use carrier_param_ids instead of carrier_exit_ids let exit_id = carrier_param_ids[idx]; exit_values.push((carrier.name.clone(), exit_id)); - eprintln!( - "[joinir/pattern4] ExitMeta: {} → ValueId({}) (carrier_param)", - carrier.name, exit_id.0 + debug.log( + "exit_meta", + &format!( + "ExitMeta: {} → ValueId({}) (carrier_param)", + carrier.name, exit_id.0 + ), ); } let exit_meta = ExitMeta::multiple(exit_values); - eprintln!( - "[joinir/pattern4] Phase 169: ExitMeta total: {} bindings (condition from AST)", - exit_meta.exit_values.len() + debug.log( + "phase169", + &format!( + "ExitMeta total: {} bindings (condition from AST)", + exit_meta.exit_values.len() + ), ); Ok((join_module, exit_meta)) diff --git a/src/mir/join_ir/normalized/fixtures.rs b/src/mir/join_ir/normalized/fixtures.rs index da19433f..59a167aa 100644 --- a/src/mir/join_ir/normalized/fixtures.rs +++ b/src/mir/join_ir/normalized/fixtures.rs @@ -123,16 +123,19 @@ pub fn build_pattern2_minimal_structured() -> JoinModule { let mut join_value_space = JoinValueSpace::new(); let (module, _) = lower_loop_with_break_minimal( - scope, - &loop_cond, - &break_cond, - &condition_env, - &carrier_info, - &carrier_updates, - &[], - None, - &mut join_value_space, - None, // Phase 92 P0-3: skeleton=None for backward compatibility + crate::mir::join_ir::lowering::loop_with_break_minimal::LoopWithBreakLoweringInputs { + scope, + condition: &loop_cond, + break_condition: &break_cond, + env: &condition_env, + carrier_info: &carrier_info, + carrier_updates: &carrier_updates, + body_ast: &[], + body_local_env: None, + allowed_body_locals_for_conditions: None, + join_value_space: &mut join_value_space, + skeleton: None, // Phase 92 P0-3: skeleton=None for backward compatibility + }, ) .expect("pattern2 minimal lowering should succeed"); diff --git a/tools/selfhost/test_pattern5b_escape_minimal.hako b/tools/selfhost/test_pattern5b_escape_minimal.hako index 108a2ab6..0cc87832 100644 --- a/tools/selfhost/test_pattern5b_escape_minimal.hako +++ b/tools/selfhost/test_pattern5b_escape_minimal.hako @@ -10,13 +10,12 @@ // - CSV parsers // - Template engines // - Escape sequence handlers +// +// Note: VM path uses `print(out)` instead of `ConsoleBox` here because ConsoleBox methods are +// not reliably available in this selfhost fixture; the test focus is JoinIR lowering semantics. static box Main { - console ConsoleBox - main() { - me.console = new ConsoleBox() - // Test data: string with escape sequence // Original: "hello\" world" // After parsing: hello" world @@ -52,7 +51,7 @@ static box Main { } // Expected output: hello" world (escape removed) - me.console.log(out) + print(out) return "OK" } diff --git a/tools/smokes/v2/profiles/integration/apps/phase92_pattern2_baseline.sh b/tools/smokes/v2/profiles/integration/apps/phase92_pattern2_baseline.sh new file mode 100644 index 00000000..b3a0523d --- /dev/null +++ b/tools/smokes/v2/profiles/integration/apps/phase92_pattern2_baseline.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# Phase 92: Pattern2 baseline regression test +# Tests: Verifies Pattern2 break functionality doesn't regress with Phase 92 changes +# +# Phase 92 scope: +# - Body-local variable support in condition lowering +# - Variable resolution priority: ConditionEnv → LoopBodyLocalEnv +# - Break condition lowering reordered (after body-local init) +# +# This test uses existing Pattern2Break tests as baseline (Level 1 in P4-E2E-PLAN.md) + +source "$(dirname "$0")/../../../lib/test_runner.sh" +export SMOKES_USE_PYVM=0 +require_env || exit 2 + +# Create tmp directory +mkdir -p "$NYASH_ROOT/tmp" + +PASS_COUNT=0 +FAIL_COUNT=0 +RUN_TIMEOUT_SECS=${RUN_TIMEOUT_SECS:-10} + +# ===== Case A: Simple while loop with break (Pattern2 baseline) ===== +echo "[INFO] Case A: loop_min_while.hako (Pattern2 baseline)" + +INPUT_A="$NYASH_ROOT/apps/tests/loop_min_while.hako" + +set +e +OUTPUT_A=$(timeout "$RUN_TIMEOUT_SECS" env NYASH_DISABLE_PLUGINS=1 "$NYASH_BIN" "$INPUT_A" 2>&1) +EXIT_CODE=$? +set -e + +if [ "$EXIT_CODE" -eq 124 ]; then + echo "[FAIL] Case A: hakorune timed out (>${RUN_TIMEOUT_SECS}s)" + FAIL_COUNT=$((FAIL_COUNT + 1)) +elif [ "$EXIT_CODE" -eq 0 ]; then + # Expected output: "0\n1\n2\n" (prints 0, 1, 2 then breaks at i==3) + if echo "$OUTPUT_A" | grep -q "^0$" && echo "$OUTPUT_A" | grep -q "^1$" && echo "$OUTPUT_A" | grep -q "^2$"; then + echo "[PASS] Case A: Pattern2 break baseline verified (output: 0, 1, 2)" + PASS_COUNT=$((PASS_COUNT + 1)) + else + echo "[FAIL] Case A: Unexpected output (expected lines: 0, 1, 2)" + echo "[INFO] Case A output:" + echo "$OUTPUT_A" | head -n 20 || true + FAIL_COUNT=$((FAIL_COUNT + 1)) + fi +else + echo "[FAIL] Case A: hakorune failed with exit code $EXIT_CODE" + echo "[INFO] Case A output (tail):" + echo "$OUTPUT_A" | tail -n 20 || true + FAIL_COUNT=$((FAIL_COUNT + 1)) +fi + +# ===== Case B: Conditional increment (Phase 92 minimal test) ===== +echo "[INFO] Case B: phase92_conditional_step_minimal.hako" + +INPUT_B="$NYASH_ROOT/apps/tests/phase92_conditional_step_minimal.hako" + +set +e +OUTPUT_B=$(timeout "$RUN_TIMEOUT_SECS" env NYASH_DISABLE_PLUGINS=1 "$NYASH_BIN" "$INPUT_B" 2>&1) +EXIT_CODE=$? +set -e + +if [ "$EXIT_CODE" -eq 124 ]; then + echo "[FAIL] Case B: hakorune timed out (>${RUN_TIMEOUT_SECS}s)" + FAIL_COUNT=$((FAIL_COUNT + 1)) +elif [ "$EXIT_CODE" -eq 0 ]; then + # Expected output: "3" (i increments: 0→1→2→3, then breaks) + if echo "$OUTPUT_B" | grep -q "^3$"; then + echo "[PASS] Case B: Conditional increment baseline verified (output: 3)" + PASS_COUNT=$((PASS_COUNT + 1)) + else + echo "[FAIL] Case B: Unexpected output (expected: 3)" + echo "[INFO] Case B output:" + echo "$OUTPUT_B" | head -n 20 || true + FAIL_COUNT=$((FAIL_COUNT + 1)) + fi +else + echo "[FAIL] Case B: hakorune failed with exit code $EXIT_CODE" + echo "[INFO] Case B output (tail):" + echo "$OUTPUT_B" | tail -n 20 || true + FAIL_COUNT=$((FAIL_COUNT + 1)) +fi + +# ===== Summary ===== +echo "[INFO] PASS: $PASS_COUNT, FAIL: $FAIL_COUNT" + +if [ "$FAIL_COUNT" -eq 0 ]; then + test_pass "phase92_pattern2_baseline: All tests passed" + exit 0 +else + test_fail "phase92_pattern2_baseline: $FAIL_COUNT test(s) failed" + exit 1 +fi