diff --git a/docs/development/current/main/joinir-architecture-overview.md b/docs/development/current/main/joinir-architecture-overview.md index d7dec8fc..462a857f 100644 --- a/docs/development/current/main/joinir-architecture-overview.md +++ b/docs/development/current/main/joinir-architecture-overview.md @@ -1032,3 +1032,110 @@ JoinIR は Rust 側だけでなく、将来的に .hako selfhost コンパイラ - 根本原因: carrier 検出ロジックの name heuristic が脆弱 - **次フェーズ**: Carrier 検出修正(Phase 219) - 詳細: phase218-jsonparser-if-sum-min.md + +--- + +## 3. JoinIR → JoinIR 正規化レイヤ(構想 / Phase 26-H ライン) + +### 3.1 Structured JoinIR と Normalized JoinIR(概念レベル) + +JoinIR ラインは、今後は「同じ JoinIR をフェーズごとに正規化していく」二段構成で扱う想定だよ: + +- **Structured JoinIR(現行層)** + - Pattern1–5 / CarrierInfo / ConditionEnv / UpdateEnv / Boundary / ExitLine までを終えた状態。 + - while/if/break/continue/return がまだ「構造」として残っていてよい層。 + - いまの JoinIR lowering(Pattern lowerer 群)が出力しているものはここ。 + +- **Normalized JoinIR(JoinIR’ / CPS 風層)** + - 制御構造をすべて「関数+継続+ Env」の形に正規化した状態。 + - ループは `loop_step(env, k_exit)` と exit 継続のペア、if は `if_branch(env, k_then, k_else)` + join 継続で表現。 + - while/break/continue/return は **TailCallFn/TailCallKont + If** だけに還元され、goto/生の branch は現れない。 + - Env は「ループキャリア + DigitPos/num_str などの Derived + captured 変数」を 1 つの struct としてまとめる。 + +パイプラインのイメージ: + +```text +AST + ↓ +JoinIR(Structured) // Pattern1–5, CarrierInfo, ConditionEnv/UpdateEnv, Boundary/ExitLine + ↓ JoinIR パス A: 正規化(JoinIR → JoinIR) +JoinIR(Normalized) // 関数 + 継続 + Env のみ(TailCall-only) + ↓ JoinIR→MIR bridge +MIR +``` + +型レベルでは、次の二案のどちらかで扱う想定だよ(詳細は今後の Phase で選択): + +- **案① 型を分ける** + - `JoinModuleRaw`(Structured)、`JoinModuleCps`(Normalized)を別型にする。 + - Bridge(JoinIR→MIR)は `JoinModuleCps` だけを受け取る。 +- **案② 型は 1 つ+フェーズフラグ** + - `JoinModule { phase: JoinIrPhase, ... }` として、`phase = Structured / Normalized` をメタデータで持つ。 + - JoinIR パスはすべて `fn run(&mut JoinModule)`(= joinir→joinir)で構成し、 + Verifier が「phase=Normalized なら while/if/break/continue/return 禁止」などの不変条件をチェックする。 + +どちらの案でも本質は同じで、「JoinIR の下に JoinIR’(= 正規形フェーズ)を 1 段挟む」という設計、というのがポイントだよ。 + +### 3.2 Normalized JoinIR の基本モデル(ラフスケッチ) + +Normalized JoinIR では、制御構造を次の 3 要素だけで表現する想定だよ: + +- **Env(環境)** + - そのループ/if に必要な情報を 1 つにまとめた struct。 + - フィールド種別の例: + - `Carrier`(LoopState キャリア: i, sum, result, num_state 等) + - `Derived`(DigitPos 二重値, NumberAccumulation 等) + - `Captured`(外側のローカルや関数パラメータ: s, len 等) + +- **Fn(通常の関数)** + - 典型例: `loop_step(env, k_exit)` / `loop_body(env, k_exit)`。 + - 末尾は常に `TailCallFn` または `TailCallKont`。 + +- **Kont(継続)** + - 典型例: ループ exit 後の処理、if 後の join、関数 return など。 + - 末尾は別の Fn/Kont への tail-call。 + +制御の不変条件(Normalized フェーズ): + +1. 制御フローは `TailCallFn` / `TailCallKont` / `If(cond, then_k, else_k, env)` のみで表現する。 +2. 各ループは「`loop_step(env, k_exit)` 関数 + `k_exit(env)` 継続」のペアとして現れる(break は必ず k_exit へ TailCall)。 +3. continue は `loop_step` への TailCall として現れる(ヘッダ PHI 相当は Env の書き込み順で表現)。 +4. return は専用の `return_kont(env)` への TailCall で表現される。 +5. EnvLayoutVerifier(検証箱)が、常に正しい EnvLayout が使われていることをチェックする。 + +これにより、現在 JoinIR 層で苦労している「PHI 配線」「exit_bindings/jump_args 整合性」「評価順のねじれ」は、 +Normalized JoinIR 側では「Env フィールドの更新順」と「どの継続を呼ぶか」に還元される想定だよ。 + +### 3.3 Phase 26-H のフェーズ分割(案) + +この正規化レイヤを一度に入れるのは重いので、Phase 26-H ラインとして小さく段階分けして進める方針だよ。 +(番号は仮で、実際の Phase 番号は current の進行に合わせて調整する想定) + +1. **Phase 26-H.A – モデル定義と Doc 固定(コード変更最小)** + - JoinIR Architecture Overview(このファイル)に Structured / Normalized の二層構造を明文化(本節)。 + - `JoinIrPhase` などのメタ情報だけを追加し、既存 lowering / Bridge の挙動は変えない。 + - Normalized JoinIR の不変条件(TailCall-only / EnvLayout 整合性)を Verifier の設計として書き出す。 + +2. **Phase 26-H.B – 最小サブセットでの JoinIR→JoinIR パス導入** + - Pattern1(単純 while, break/continue なし)だけを対象にした Normalized パスを実装。 + - パイプライン: + - 既存: `JoinIR(Structured) → MIR` + - 新規: `JoinIR(Structured) → JoinIR(Normalized, small subset) → MIR` + - 新規パスは dev フラグの裏側で比較用にのみ使い、E2E で結果が一致することを確認(既定経路は従来のまま)。 + +3. **Phase 26-H.C – Pattern2–4 / DigitPos / JsonParser への拡張** + - Pattern2/3/4 と DigitPos / NumberAccumulation / Trim を Normalized パスの対象に広げる。 + - JsonParser `_parse_number` / `_atoi` を優先対象にし、JoinIR(Normalized) 経由での実行を dev フラグ付きで有効化。 + - JoinIR(Structured) → JoinIR(Normalized) の各ステップに構造テストを追加し、「Env の更新」「継続の呼び出し順」が期待どおりかを固定。 + +4. **Phase 26-H.D – Normalized JoinIR を canonical route に昇格** + - JoinIR→MIR Bridge が「Normalized フェーズの JoinIR だけ」を受け取るように変更。 + - Structured JoinIR → MIR の直接パスは「比較テスト専用」として残すか、段階的に削除。 + - selfhost / JsonParser / hako_check の代表ループはすべて Normalized JoinIR 経由で通ることを確認。 + +各サブフェーズでは「既存意味論を変えない」「Fail-Fast 原則を維持する」「新旧経路の比較テストを先に用意する」をガードとして運用するよ。 +詳細な API/型設計は、Phase 26-H.A/B の中で `EnvLayoutBox` / `LoopStepSynthesizer` / `JpVerifier` 等の箱として段階的に固めていく想定。 + +### 3.4 Phase 27-CLEAN – Pattern2–4 の軽量整理 + +- Pattern2〜4/loop_with_break_minimal まわりで可視性・ログ・補助関数を整理し、joinir_dev フラグ配下のデバッグログに寄せる。意味論は変えずに「読みやすさ」「追いやすさ」を優先するクリーンアップフェーズだよ。 diff --git a/src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs b/src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs index 7595d7c6..33bc5663 100644 --- a/src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs +++ b/src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs @@ -20,8 +20,8 @@ //! - Delegates to specialized analyzers for break/continue logic use crate::ast::ASTNode; -use crate::mir::loop_pattern_detection::LoopFeatures; use crate::mir::loop_pattern_detection::break_condition_analyzer::BreakConditionAnalyzer; +use crate::mir::loop_pattern_detection::LoopFeatures; /// Detect if a loop body contains continue statements /// @@ -37,7 +37,7 @@ use crate::mir::loop_pattern_detection::break_condition_analyzer::BreakCondition /// /// This is a simple recursive scan that doesn't handle nested loops perfectly, /// but is sufficient for initial pattern detection. -pub fn detect_continue_in_body(body: &[ASTNode]) -> bool { +pub(crate) fn detect_continue_in_body(body: &[ASTNode]) -> bool { for stmt in body { if has_continue_node(stmt) { return true; @@ -55,7 +55,7 @@ pub fn detect_continue_in_body(body: &[ASTNode]) -> bool { /// # Returns /// /// `true` if at least one break statement is found in the body or nested structures -pub fn detect_break_in_body(body: &[ASTNode]) -> bool { +pub(crate) fn detect_break_in_body(body: &[ASTNode]) -> bool { for stmt in body { if has_break_node(stmt) { return true; @@ -78,11 +78,7 @@ pub fn detect_break_in_body(body: &[ASTNode]) -> bool { /// # Returns /// /// A LoopFeatures struct containing all detected structural characteristics -pub fn extract_features( - body: &[ASTNode], - has_continue: bool, - has_break: bool, -) -> LoopFeatures { +pub(crate) fn extract_features(body: &[ASTNode], has_continue: bool, has_break: bool) -> LoopFeatures { // Phase 212.5: Detect ANY if statement in loop body (structural detection) let has_if = detect_if_in_body(body); @@ -147,8 +143,12 @@ fn detect_if_else_phi_in_body(body: &[ASTNode]) -> bool { } = node { // Check if both branches have assignments - let then_has_assign = then_body.iter().any(|n| matches!(n, ASTNode::Assignment { .. })); - let else_has_assign = else_body.iter().any(|n| matches!(n, ASTNode::Assignment { .. })); + let then_has_assign = then_body + .iter() + .any(|n| matches!(n, ASTNode::Assignment { .. })); + let else_has_assign = else_body + .iter() + .any(|n| matches!(n, ASTNode::Assignment { .. })); if then_has_assign && else_has_assign { return true; } @@ -193,7 +193,11 @@ fn count_carriers_in_body(body: &[ASTNode]) -> usize { } } // Return at least 1 if we have assignments, otherwise 0 - if count > 0 { 1 } else { 0 } + if count > 0 { + 1 + } else { + 0 + } } /// Recursive helper to check if AST node contains continue @@ -248,7 +252,7 @@ fn has_break_node(node: &ASTNode) -> bool { /// # Returns /// /// `true` if an `if ... else { break }` pattern is found -pub fn has_break_in_else_clause(body: &[ASTNode]) -> bool { +pub(crate) fn has_break_in_else_clause(body: &[ASTNode]) -> bool { BreakConditionAnalyzer::has_break_in_else_clause(body) } @@ -286,7 +290,7 @@ pub fn has_break_in_else_clause(body: &[ASTNode]) -> bool { /// // <- Returns the "!(ch == " ")" condition (negated) /// } /// ``` -pub fn extract_break_condition(body: &[ASTNode]) -> Option<&ASTNode> { +pub(crate) fn extract_break_condition(body: &[ASTNode]) -> Option<&ASTNode> { BreakConditionAnalyzer::extract_break_condition(body).ok() } @@ -296,13 +300,17 @@ mod tests { #[test] fn test_detect_continue_simple() { - let continue_node = ASTNode::Continue { span: crate::ast::Span::unknown() }; + let continue_node = ASTNode::Continue { + span: crate::ast::Span::unknown(), + }; assert!(has_continue_node(&continue_node)); } #[test] fn test_detect_break_simple() { - let break_node = ASTNode::Break { span: crate::ast::Span::unknown() }; + let break_node = ASTNode::Break { + span: crate::ast::Span::unknown(), + }; assert!(has_break_node(&break_node)); } 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 8f4b4c8f..f86283c7 100644 --- a/src/mir/builder/control_flow/joinir/patterns/common_init.rs +++ b/src/mir/builder/control_flow/joinir/patterns/common_init.rs @@ -32,13 +32,13 @@ //! This module is now a thin wrapper around `CarrierInfo::from_variable_map()`. //! The primary logic lives in `carrier_info.rs` for consistency across MIR and JoinIR contexts. -use crate::mir::builder::MirBuilder; -use crate::mir::ValueId; use crate::ast::ASTNode; +use crate::mir::builder::MirBuilder; use crate::mir::join_ir::lowering::carrier_info::CarrierInfo; +use crate::mir::ValueId; use std::collections::BTreeMap; -pub struct CommonPatternInitializer; +pub(crate) struct CommonPatternInitializer; impl CommonPatternInitializer { /// Initialize pattern context: extract loop var, build CarrierInfo @@ -84,28 +84,24 @@ impl CommonPatternInitializer { ) -> Result<(String, ValueId, CarrierInfo), String> { // Step 1: Extract loop variable from condition let loop_var_name = builder.extract_loop_variable_from_condition(condition)?; - let loop_var_id = variable_map - .get(&loop_var_name) - .copied() - .ok_or_else(|| { - format!( - "[common_init] Loop variable '{}' not found in variable_map", - loop_var_name - ) - })?; + let loop_var_id = variable_map.get(&loop_var_name).copied().ok_or_else(|| { + format!( + "[common_init] Loop variable '{}' not found in variable_map", + loop_var_name + ) + })?; // Phase 183-2: Delegate to CarrierInfo::from_variable_map for consistency // Phase 222.5-D: Direct BTreeMap usage (no conversion needed) // Step 2: Use CarrierInfo::from_variable_map as primary initialization method - let mut carrier_info = CarrierInfo::from_variable_map( - loop_var_name.clone(), - variable_map, - )?; + let mut carrier_info = CarrierInfo::from_variable_map(loop_var_name.clone(), variable_map)?; // Step 3: Apply exclusions if provided (Pattern 2 specific) if let Some(excluded) = exclude_carriers { - carrier_info.carriers.retain(|c| !excluded.contains(&c.name.as_str())); + carrier_info + .carriers + .retain(|c| !excluded.contains(&c.name.as_str())); } Ok((loop_var_name, loop_var_id, carrier_info)) @@ -150,28 +146,33 @@ impl CommonPatternInitializer { _loop_var_name: &str, _variable_map: &BTreeMap, ) -> bool { - use crate::mir::join_ir::lowering::loop_update_analyzer::{LoopUpdateAnalyzer, UpdateExpr, UpdateRhs}; use crate::mir::join_ir::lowering::carrier_info::CarrierVar; + use crate::mir::join_ir::lowering::loop_update_analyzer::{ + LoopUpdateAnalyzer, UpdateExpr, UpdateRhs, + }; // Create dummy carriers from body assignment targets for analysis - let dummy_carriers: Vec = body.iter().filter_map(|node| { - match node { - ASTNode::Assignment { target, .. } => { - if let ASTNode::Variable { name, .. } = target.as_ref() { - Some(CarrierVar { + let dummy_carriers: Vec = body + .iter() + .filter_map(|node| { + match node { + ASTNode::Assignment { target, .. } => { + if let ASTNode::Variable { name, .. } = target.as_ref() { + Some(CarrierVar { name: name.clone(), host_id: ValueId(0), // Dummy join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, // Phase 227: Default init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228: Default }) - } else { - None + } else { + None + } } + _ => None, } - _ => None, - } - }).collect(); + }) + .collect(); let updates = LoopUpdateAnalyzer::analyze_carrier_updates(body, &dummy_carriers); 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 a02ba1f6..e4ad3e9b 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 @@ -35,7 +35,7 @@ use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv; use crate::mir::ValueId; use std::collections::BTreeMap; -pub struct ConditionEnvBuilder; +pub(crate) struct ConditionEnvBuilder; impl ConditionEnvBuilder { /// Phase 201: Build ConditionEnv using JoinValueSpace (disjoint ValueId regions) @@ -65,10 +65,8 @@ impl ConditionEnvBuilder { space: &mut JoinValueSpace, ) -> Result<(ConditionEnv, Vec, ValueId), String> { // Extract all variables used in the condition (excluding loop parameter) - let condition_var_names = extract_condition_variables( - break_condition, - &[loop_var_name.to_string()], - ); + let condition_var_names = + extract_condition_variables(break_condition, &[loop_var_name.to_string()]); let mut env = ConditionEnv::new(); let mut bindings = Vec::new(); @@ -79,16 +77,13 @@ impl ConditionEnvBuilder { // For each condition variable, allocate JoinIR-local ValueId and build binding for var_name in &condition_var_names { - let host_id = variable_map - .get(var_name) - .copied() - .ok_or_else(|| { - format!( - "Condition variable '{}' not found in variable_map. \ + let host_id = variable_map.get(var_name).copied().ok_or_else(|| { + format!( + "Condition variable '{}' not found in variable_map. \ Loop condition references undefined variable.", - var_name - ) - })?; + var_name + ) + })?; // Phase 201: Allocate from Param region to avoid collision with locals let join_id = space.alloc_param(); @@ -108,7 +103,10 @@ impl ConditionEnvBuilder { /// /// Uses JoinValueSpace to allocate the loop parameter ValueId. #[allow(dead_code)] - pub fn build_loop_param_only_v2(loop_var_name: &str, space: &mut JoinValueSpace) -> (ConditionEnv, ValueId) { + pub fn build_loop_param_only_v2( + loop_var_name: &str, + space: &mut JoinValueSpace, + ) -> (ConditionEnv, ValueId) { let mut env = ConditionEnv::new(); let loop_var_join_id = space.alloc_param(); env.insert(loop_var_name.to_string(), loop_var_join_id); @@ -177,7 +175,10 @@ impl ConditionEnvBuilder { let debug = env::var("NYASH_CAPTURE_DEBUG").is_ok(); if debug { - eprintln!("[capture/env_builder] Building ConditionEnv with {} captured vars", captured.vars.len()); + eprintln!( + "[capture/env_builder] Building ConditionEnv with {} captured vars", + captured.vars.len() + ); } // Step 1: Build base ConditionEnv with loop params using v2 API (Phase 222.5-B) @@ -197,17 +198,25 @@ impl ConditionEnvBuilder { }; // 2b: Add to boundary with Condition role - boundary.add_param_with_role(&var.name, host_id, crate::mir::join_ir::lowering::inline_boundary_builder::ParamRole::Condition); + boundary.add_param_with_role( + &var.name, + host_id, + crate::mir::join_ir::lowering::inline_boundary_builder::ParamRole::Condition, + ); // 2c: Get JoinIR ValueId from boundary - let join_id = boundary.get_condition_binding(&var.name) + let join_id = boundary + .get_condition_binding(&var.name) .expect("captured var should be in boundary after add_param_with_role"); // 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={:?}", var.name, host_id, join_id); + eprintln!( + "[capture/env_builder] Added captured var '{}': host={:?}, join={:?}", + var.name, host_id, join_id + ); } } @@ -224,7 +233,11 @@ impl ConditionEnvBuilder { if debug { let param_count = env.iter().count(); - eprintln!("[capture/env_builder] Final ConditionEnv: {} params, {} captured", param_count, env.captured.len()); + eprintln!( + "[capture/env_builder] Final ConditionEnv: {} params, {} captured", + param_count, + env.captured.len() + ); } (env, loop_var_join_id) @@ -351,9 +364,7 @@ mod tests { // Should return error assert!(result.is_err()); - assert!(result - .unwrap_err() - .contains("undefined_var")); + assert!(result.unwrap_err().contains("undefined_var")); } /// Phase 201: Test that v2 API uses JoinValueSpace correctly @@ -410,7 +421,8 @@ mod tests { use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; let mut space = JoinValueSpace::new(); - let (env, loop_var_join_id) = ConditionEnvBuilder::build_loop_param_only_v2("i", &mut space); + let (env, loop_var_join_id) = + ConditionEnvBuilder::build_loop_param_only_v2("i", &mut space); // Phase 201: Should use Param region assert_eq!(loop_var_join_id, ValueId(100)); diff --git a/src/mir/builder/control_flow/joinir/patterns/exit_binding_applicator.rs b/src/mir/builder/control_flow/joinir/patterns/exit_binding_applicator.rs index 3606cef1..207bd437 100644 --- a/src/mir/builder/control_flow/joinir/patterns/exit_binding_applicator.rs +++ b/src/mir/builder/control_flow/joinir/patterns/exit_binding_applicator.rs @@ -3,11 +3,9 @@ //! Applies exit bindings to JoinInlineBoundary. //! Single-responsibility box for boundary application logic. -use crate::mir::ValueId; -use crate::mir::join_ir::lowering::inline_boundary::{ - JoinInlineBoundary, LoopExitBinding, -}; use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta}; +use crate::mir::join_ir::lowering::inline_boundary::{JoinInlineBoundary, LoopExitBinding}; +use crate::mir::ValueId; use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism /// Apply bindings to JoinInlineBoundary @@ -27,7 +25,7 @@ use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for deter /// # Returns /// /// Success or error if boundary cannot be updated -pub fn apply_exit_bindings_to_boundary( +pub(crate) fn apply_exit_bindings_to_boundary( carrier_info: &CarrierInfo, exit_meta: &ExitMeta, variable_map: &BTreeMap, // Phase 222.5-D: HashMap → BTreeMap for determinism @@ -40,13 +38,14 @@ pub fn apply_exit_bindings_to_boundary( let mut join_outputs = vec![carrier_info.loop_var_id]; // legacy field for compatibility for carrier in &carrier_info.carriers { - let post_loop_id = variable_map.get(&carrier.name).copied().ok_or_else(|| { - format!("Post-loop ValueId not found for carrier '{}'", carrier.name) - })?; + let post_loop_id = variable_map + .get(&carrier.name) + .copied() + .ok_or_else(|| format!("Post-loop ValueId not found for carrier '{}'", carrier.name))?; - let join_exit_id = exit_meta.find_binding(&carrier.name).ok_or_else(|| { - format!("Exit value not found for carrier '{}'", carrier.name) - })?; + let join_exit_id = exit_meta + .find_binding(&carrier.name) + .ok_or_else(|| format!("Exit value not found for carrier '{}'", carrier.name))?; bindings.push(LoopExitBinding { carrier_name: carrier.name.clone(), @@ -83,7 +82,7 @@ pub fn apply_exit_bindings_to_boundary( /// # Returns /// /// LoopExitBinding for the loop variable -pub fn create_loop_var_exit_binding(carrier_info: &CarrierInfo) -> LoopExitBinding { +pub(crate) fn create_loop_var_exit_binding(carrier_info: &CarrierInfo) -> LoopExitBinding { use crate::mir::join_ir::lowering::carrier_info::CarrierRole; LoopExitBinding { carrier_name: carrier_info.loop_var_name.clone(), @@ -126,16 +125,16 @@ mod tests { let mut boundary = JoinInlineBoundary { host_inputs: vec![], join_inputs: vec![], - exit_bindings: vec![], // Phase 171: Add missing field + exit_bindings: vec![], // Phase 171: Add missing field #[allow(deprecated)] - host_outputs: vec![], // legacy, unused in new assertions + host_outputs: vec![], // legacy, unused in new assertions join_outputs: vec![], #[allow(deprecated)] - condition_inputs: vec![], // Phase 171: Add missing field - condition_bindings: vec![], // Phase 171-fix: Add missing field - expr_result: None, // Phase 33-14: Add missing field - loop_var_name: None, // Phase 33-16: Add missing field - carrier_info: None, // Phase 228: Add missing field + condition_inputs: vec![], // Phase 171: Add missing field + condition_bindings: vec![], // Phase 171-fix: Add missing field + expr_result: None, // Phase 33-14: Add missing field + loop_var_name: None, // Phase 33-16: Add missing field + carrier_info: None, // Phase 228: Add missing field }; apply_exit_bindings_to_boundary(&carrier_info, &exit_meta, &variable_map, &mut boundary) @@ -155,11 +154,7 @@ mod tests { #[test] fn test_loop_var_exit_binding() { - let carrier_info = CarrierInfo::with_carriers( - "i".to_string(), - ValueId(5), - vec![], - ); + let carrier_info = CarrierInfo::with_carriers("i".to_string(), ValueId(5), vec![]); let binding = create_loop_var_exit_binding(&carrier_info); assert_eq!(binding.carrier_name, "i"); diff --git a/src/mir/builder/control_flow/joinir/patterns/exit_binding_constructor.rs b/src/mir/builder/control_flow/joinir/patterns/exit_binding_constructor.rs index 40284cc3..3d1d118d 100644 --- a/src/mir/builder/control_flow/joinir/patterns/exit_binding_constructor.rs +++ b/src/mir/builder/control_flow/joinir/patterns/exit_binding_constructor.rs @@ -3,9 +3,9 @@ //! Constructs loop exit bindings and allocates post-loop ValueIds. //! Single-responsibility box for binding construction logic. -use crate::mir::ValueId; -use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding; use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta}; +use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding; +use crate::mir::ValueId; use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism /// Generate loop exit bindings @@ -24,7 +24,7 @@ use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for deter /// # Returns /// /// Vec of LoopExitBinding, one per carrier, sorted by carrier name -pub fn build_loop_exit_bindings( +pub(crate) fn build_loop_exit_bindings( carrier_info: &CarrierInfo, exit_meta: &ExitMeta, variable_map: &mut BTreeMap, // Phase 222.5-D: HashMap → BTreeMap for determinism @@ -33,7 +33,8 @@ pub fn build_loop_exit_bindings( // Process each carrier in sorted order for carrier in &carrier_info.carriers { - let join_exit_id = exit_meta.find_binding(&carrier.name) + let join_exit_id = exit_meta + .find_binding(&carrier.name) .ok_or_else(|| format!("Carrier '{}' missing in ExitMeta", carrier.name))?; bindings.push(LoopExitBinding { @@ -64,12 +65,10 @@ pub fn build_loop_exit_bindings( /// # Returns /// /// Newly allocated ValueId -pub fn allocate_new_value_id(variable_map: &BTreeMap) -> ValueId { // Phase 222.5-D: HashMap → BTreeMap for determinism +pub(crate) fn allocate_new_value_id(variable_map: &BTreeMap) -> ValueId { + // Phase 222.5-D: HashMap → BTreeMap for determinism // Find the maximum ValueId in current variable_map - let max_id = variable_map.values() - .map(|v| v.0) - .max() - .unwrap_or(0); + let max_id = variable_map.values().map(|v| v.0).max().unwrap_or(0); // Allocate next sequential ID // Note: This is a temporary strategy and should be replaced with @@ -131,14 +130,14 @@ mod tests { host_id: ValueId(11), join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, - init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 }, CarrierVar { name: "sum".to_string(), host_id: ValueId(10), join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, - init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 }, ], ); diff --git a/src/mir/builder/control_flow/joinir/patterns/exit_binding_validator.rs b/src/mir/builder/control_flow/joinir/patterns/exit_binding_validator.rs index d1d03f51..a9b56b71 100644 --- a/src/mir/builder/control_flow/joinir/patterns/exit_binding_validator.rs +++ b/src/mir/builder/control_flow/joinir/patterns/exit_binding_validator.rs @@ -20,7 +20,7 @@ use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta}; /// # Returns /// /// Ok(()) if validation passes, Err with descriptive message if validation fails -pub fn validate_exit_binding( +pub(crate) fn validate_exit_binding( carrier_info: &CarrierInfo, exit_meta: &ExitMeta, ) -> Result<(), String> { @@ -44,10 +44,7 @@ pub fn validate_exit_binding( // Validate that all carriers in CarrierInfo have exit values for carrier in &carrier_info.carriers { if exit_meta.find_binding(&carrier.name).is_none() { - return Err(format!( - "Carrier '{}' missing in ExitMeta", - carrier.name - )); + return Err(format!("Carrier '{}' missing in ExitMeta", carrier.name)); } } @@ -91,14 +88,14 @@ mod tests { host_id: ValueId(11), join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, - init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 }, CarrierVar { name: "sum".to_string(), host_id: ValueId(10), join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, - init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 }, ], ); diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs b/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs index e1942fd9..88fdf533 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs @@ -1,9 +1,9 @@ //! Pattern 1: Simple While Loop minimal lowerer +use super::super::trace; use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::ValueId; -use super::super::trace; /// Phase 194: Detection function for Pattern 1 /// @@ -11,7 +11,7 @@ use super::super::trace; /// /// Pattern 1 matches: /// - Pattern kind is Pattern1SimpleWhile (no break, no continue, no if-else PHI) -pub fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool { +pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool { use crate::mir::loop_pattern_detection::LoopPatternKind; ctx.pattern_kind == LoopPatternKind::Pattern1SimpleWhile } @@ -19,7 +19,7 @@ pub fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) /// Phase 194: Lowering function for Pattern 1 /// /// Wrapper around cf_loop_pattern1_minimal to match router signature -pub fn lower( +pub(crate) fn lower( builder: &mut MirBuilder, ctx: &super::router::LoopPatternContext, ) -> Result, String> { @@ -51,12 +51,7 @@ impl MirBuilder { // Phase 179-B: Use PatternPipelineContext for unified preprocessing use super::pattern_pipeline::{build_pattern_context, PatternVariant}; - let ctx = build_pattern_context( - self, - condition, - body, - PatternVariant::Pattern1, - )?; + let ctx = build_pattern_context(self, condition, body, PatternVariant::Pattern1)?; // Phase 195: Use unified trace trace::trace().varmap("pattern1_start", &self.variable_map); @@ -85,7 +80,7 @@ impl MirBuilder { vec![ValueId(0)], // JoinIR's main() parameter (loop variable) vec![ctx.loop_var_id], // Host's loop variable ) - .with_loop_var_name(Some(ctx.loop_var_name.clone())) // Phase 33-16: Enable header PHI generation for SSA correctness + .with_loop_var_name(Some(ctx.loop_var_name.clone())) // Phase 33-16: Enable header PHI generation for SSA correctness .build(); // Phase 33-22: Use JoinIRConversionPipeline for unified conversion flow @@ -116,7 +111,10 @@ impl MirBuilder { let void_val = crate::mir::builder::emission::constant::emit_void(self); // Phase 195: Use unified trace - trace::trace().debug("pattern1", &format!("Loop complete, returning Void {:?}", void_val)); + trace::trace().debug( + "pattern1", + &format!("Loop complete, returning Void {:?}", void_val), + ); Ok(Some(void_val)) } 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 362c8729..e497d95c 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 @@ -1,11 +1,533 @@ //! Pattern 2: Loop with Conditional Break minimal lowerer +use super::super::trace; use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; -use crate::mir::ValueId; -use super::super::trace; +use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierInit}; +use crate::mir::join_ir::lowering::condition_env::{ConditionBinding, ConditionEnv}; +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::loop_pattern_detection::function_scope_capture::CapturedEnv; use crate::mir::loop_pattern_detection::error_messages; -use crate::mir::join_ir::lowering::carrier_info::CarrierInit; +use crate::mir::ValueId; + +fn log_pattern2(verbose: bool, tag: &str, message: impl AsRef) { + if verbose { + eprintln!("[joinir/pattern2/{tag}] {}", message.as_ref()); + } +} + +struct Pattern2Inputs { + loop_var_name: String, + loop_var_id: ValueId, + carrier_info: CarrierInfo, + scope: LoopScopeShape, + captured_env: CapturedEnv, + join_value_space: JoinValueSpace, + env: ConditionEnv, + condition_bindings: Vec, + body_local_env: LoopBodyLocalEnv, + break_condition_node: ASTNode, +} + +fn prepare_pattern2_inputs( + builder: &MirBuilder, + condition: &ASTNode, + body: &[ASTNode], + fn_body: Option<&[ASTNode]>, + ctx: &super::pattern_pipeline::PatternPipelineContext, + verbose: bool, +) -> Result { + use super::condition_env_builder::ConditionEnvBuilder; + use crate::mir::loop_pattern_detection::function_scope_capture::{analyze_captured_vars_v2, CapturedEnv}; + + let loop_var_name = ctx.loop_var_name.clone(); + let loop_var_id = ctx.loop_var_id; + let carrier_info = ctx.carrier_info.clone(); + let scope = ctx.loop_scope.clone(); + + log_pattern2( + verbose, + "init", + format!( + "PatternPipelineContext: loop_var='{}', loop_var_id={:?}, carriers={}", + loop_var_name, + loop_var_id, + carrier_info.carriers.len() + ), + ); + + // Capture analysis + log_pattern2( + verbose, + "phase200c", + format!( + "fn_body is {}", + if fn_body.is_some() { "SOME" } else { "NONE" } + ), + ); + let captured_env = if let Some(fn_body_ref) = fn_body { + log_pattern2( + verbose, + "phase200c", + format!("fn_body has {} nodes", fn_body_ref.len()), + ); + analyze_captured_vars_v2(fn_body_ref, condition, body, &scope) + } else { + log_pattern2( + verbose, + "phase200c", + "fn_body is None, using empty CapturedEnv", + ); + CapturedEnv::new() + }; + if verbose { + log_pattern2( + verbose, + "capture", + format!("Phase 200-C: Captured {} variables", captured_env.vars.len()), + ); + for var in &captured_env.vars { + log_pattern2( + verbose, + "capture", + format!( + " '{}': host_id={:?}, immutable={}", + var.name, var.host_id, var.is_immutable + ), + ); + } + } + + // Value space + condition env + let mut join_value_space = JoinValueSpace::new(); + let (mut env, mut condition_bindings, _loop_var_join_id) = + ConditionEnvBuilder::build_for_break_condition_v2( + condition, + &loop_var_name, + &builder.variable_map, + loop_var_id, + &mut join_value_space, + )?; + + log_pattern2( + verbose, + "phase201", + format!( + "Using JoinValueSpace: loop_var '{}' → {:?}", + loop_var_name, + env.get(&loop_var_name) + ), + ); + + // Add captured vars + for var in &captured_env.vars { + if let Some(&host_id) = builder.variable_map.get(&var.name) { + let join_id = join_value_space.alloc_param(); + env.insert(var.name.clone(), join_id); + condition_bindings.push(ConditionBinding { + name: var.name.clone(), + host_value: host_id, + join_value: join_id, + }); + log_pattern2( + verbose, + "capture", + format!( + "Phase 201: Added captured '{}': host={:?}, join={:?}", + var.name, host_id, join_id + ), + ); + } + } + + let body_local_env = LoopBodyLocalEnv::new(); + log_pattern2( + verbose, + "body-local", + format!( + "Phase 201: Created empty body-local environment (param_count={})", + join_value_space.param_count() + ), + ); + if verbose { + log_pattern2( + verbose, + "cond-env", + format!("Phase 201: ConditionEnv contains {} variables:", env.len()), + ); + log_pattern2( + verbose, + "cond-env", + format!( + " Loop param '{}' → JoinIR {:?}", + loop_var_name, + env.get(&loop_var_name) + ), + ); + if !condition_bindings.is_empty() { + log_pattern2( + verbose, + "cond-env", + format!(" {} condition-only bindings:", condition_bindings.len()), + ); + for binding in &condition_bindings { + log_pattern2( + verbose, + "cond-env", + format!( + " '{}': HOST {:?} → JoinIR {:?}", + binding.name, binding.host_value, binding.join_value + ), + ); + } + } else { + log_pattern2(verbose, "cond-env", " No condition-only variables"); + } + } + + // Break condition extraction + use super::ast_feature_extractor; + let break_condition_raw = + ast_feature_extractor::extract_break_condition(body).ok_or_else(|| { + "[cf_loop/pattern2] Failed to extract break condition from loop body".to_string() + })?; + let break_in_else = ast_feature_extractor::has_break_in_else_clause(body); + use crate::ast::UnaryOperator; + let break_condition_node = if break_in_else { + let span = crate::ast::Span::unknown(); + ASTNode::UnaryOp { + operator: UnaryOperator::Not, + operand: Box::new(break_condition_raw.clone()), + span, + } + } else { + break_condition_raw.clone() + }; + + Ok(Pattern2Inputs { + loop_var_name, + loop_var_id, + carrier_info, + scope, + captured_env, + join_value_space, + env, + condition_bindings, + body_local_env, + break_condition_node, + }) +} + +fn promote_and_prepare_carriers( + builder: &mut MirBuilder, + condition: &ASTNode, + body: &[ASTNode], + inputs: &mut Pattern2Inputs, + debug: bool, + 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( + &inputs.loop_var_name, + &vec![condition, &inputs.break_condition_node], + Some(&inputs.scope), + ); + + 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, + }; + + match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) { + ConditionPromotionResult::Promoted { + carrier_info: promoted_carrier, + promoted_var, + carrier_name, + } => { + log_pattern2( + verbose, + "cond_promoter", + format!( + "LoopBodyLocal '{}' promoted to carrier '{}'", + promoted_var, carrier_name + ), + ); + + inputs + .carrier_info + .promoted_loopbodylocals + .push(promoted_var.clone()); + inputs.carrier_info.merge_from(&promoted_carrier); + + log_pattern2( + verbose, + "cond_promoter", + format!( + "Merged carrier '{}' into CarrierInfo (total carriers: {})", + carrier_name, + inputs.carrier_info.carrier_count() + ), + ); + log_pattern2( + verbose, + "cond_promoter", + format!( + "Phase 224: Recorded promoted variable '{}' in carrier_info.promoted_loopbodylocals", + promoted_var + ), + ); + + 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, + "cond_promoter", + format!( + "Carrier: '{}', original var: '{}', whitespace chars: {:?}", + helper.carrier_name, helper.original_var, helper.whitespace_chars + ), + ); + } else { + return Err(error_messages::format_error_pattern2_trim_not_safe( + &helper.carrier_name, + helper.whitespace_count(), + )); + } + } + + inputs.break_condition_node = DigitPosConditionNormalizer::normalize( + &inputs.break_condition_node, + &promoted_var, + &carrier_name, + ); + + log_pattern2( + verbose, + "phase224e", + format!( + "Normalized break condition for promoted variable '{}' → carrier '{}'", + promoted_var, carrier_name + ), + ); + } + ConditionPromotionResult::CannotPromote { reason, vars } => { + return Err(error_messages::format_error_pattern2_promotion_failed( + &vars, &reason, + )); + } + } + } + + log_pattern2( + verbose, + "phase224d", + format!( + "Allocating join_ids for {} carriers", + inputs.carrier_info.carriers.len() + ), + ); + for carrier in &mut inputs.carrier_info.carriers { + let carrier_join_id = inputs.join_value_space.alloc_param(); + carrier.join_id = Some(carrier_join_id); + log_pattern2( + verbose, + "phase224d", + format!( + "Allocated carrier '{}' param ID: {:?}", + carrier.name, carrier_join_id + ), + ); + } + + for promoted_var in &inputs.carrier_info.promoted_loopbodylocals { + let candidate_names = vec![ + format!("is_{}", promoted_var), + format!("is_{}_match", promoted_var), + ]; + + for carrier_name in candidate_names { + if carrier_name == inputs.carrier_info.loop_var_name { + if let Some(join_id) = inputs.env.get(&inputs.carrier_info.loop_var_name) { + inputs + .env + .insert(promoted_var.clone(), join_id); + log_pattern2( + verbose, + "phase229", + format!( + "Dynamically resolved promoted '{}' → loop_var '{}' (join_id={:?})", + promoted_var, inputs.carrier_info.loop_var_name, join_id + ), + ); + break; + } + } + + if let Some(carrier) = inputs + .carrier_info + .carriers + .iter() + .find(|c| c.name == carrier_name) + { + if let Some(join_id) = carrier.join_id { + inputs.env.insert(promoted_var.clone(), join_id); + log_pattern2( + verbose, + "phase229", + format!( + "Dynamically resolved promoted '{}' → carrier '{}' (join_id={:?})", + promoted_var, carrier_name, join_id + ), + ); + break; + } + } + } + } + + // ExprLowerer validation (unchanged) + { + use crate::mir::join_ir::lowering::expr_lowerer::{ + ExprContext, ExprLowerer, ExprLoweringError, + }; + use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; + + let scope_manager = Pattern2ScopeManager { + condition_env: &inputs.env, + loop_body_local_env: Some(&inputs.body_local_env), + captured_env: Some(&inputs.captured_env), + carrier_info: &inputs.carrier_info, + }; + + match ExprLowerer::new(&scope_manager, ExprContext::Condition, builder) + .with_debug(debug) + .lower(&inputs.break_condition_node) + { + Ok(_value_id) => { + log_pattern2( + verbose, + "phase231", + "ExprLowerer successfully validated break condition", + ); + } + Err(ExprLoweringError::UnsupportedNode(msg)) => { + log_pattern2( + verbose, + "phase231", + format!("ExprLowerer fallback (unsupported): {}", msg), + ); + } + Err(e) => { + log_pattern2( + verbose, + "phase231", + format!("ExprLowerer validation error: {}", e), + ); + } + } + } + + Ok(()) +} + +fn apply_trim_and_normalize( + builder: &mut MirBuilder, + condition: &ASTNode, + body: &[ASTNode], + inputs: &mut Pattern2Inputs, + verbose: bool, +) -> Result<(ASTNode, Option>), String> { + let mut alloc_join_value = || inputs.join_value_space.alloc_param(); + + let effective_break_condition = if let Some(trim_result) = + super::trim_loop_lowering::TrimLoopLowerer::try_lower_trim_like_loop( + builder, + &inputs.scope, + condition, + &inputs.break_condition_node, + body, + &inputs.loop_var_name, + &mut inputs.carrier_info, + &mut alloc_join_value, + )? { + log_pattern2( + verbose, + "trim", + "TrimLoopLowerer processed Trim pattern successfully", + ); + inputs.carrier_info = trim_result.carrier_info; + inputs + .condition_bindings + .extend(trim_result.condition_bindings.iter().cloned()); + for binding in &trim_result.condition_bindings { + inputs.env.insert(binding.name.clone(), binding.join_value); + } + log_pattern2( + verbose, + "trim", + format!( + "Extended condition_bindings with {} Trim bindings", + trim_result.condition_bindings.len() + ), + ); + trim_result.condition + } else { + inputs.break_condition_node.clone() + }; + + use crate::mir::join_ir::lowering::complex_addend_normalizer::{ + ComplexAddendNormalizer, NormalizationResult, + }; + + let mut normalized_body = Vec::new(); + let mut has_normalization = false; + + for node in body { + match ComplexAddendNormalizer::normalize_assign(node) { + NormalizationResult::Normalized { + temp_def, + new_assign, + temp_name, + } => { + log_pattern2( + verbose, + "phase192", + format!( + "Normalized complex addend: temp='{}' inserted before update", + temp_name + ), + ); + normalized_body.push(temp_def); + normalized_body.push(new_assign); + has_normalization = true; + } + NormalizationResult::Unchanged => { + normalized_body.push(node.clone()); + } + } + } + + let normalized_body = if has_normalization { + Some(normalized_body) + } else { + None + }; + + Ok((effective_break_condition, normalized_body)) +} /// Phase 185-2: Collect body-local variable declarations from loop body /// @@ -27,13 +549,18 @@ fn collect_body_local_variables( alloc_join_value: &mut dyn FnMut() -> ValueId, ) -> Vec<(String, ValueId)> { let mut locals = Vec::new(); + let verbose = crate::config::env::joinir_dev_enabled(); 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)); - eprintln!("[pattern2/body-local] Collected local '{}' → {:?}", name, value_id); + log_pattern2( + verbose, + "body-local", + format!("Collected local '{}' → {:?}", name, value_id), + ); } } } @@ -49,9 +576,9 @@ fn collect_body_local_variables( /// Pattern 2 matches: /// - Pattern kind is Pattern2Break (has break, no continue) /// - No string/complex carrier updates (JoinIR doesn't support string concat) -pub fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool { - use crate::mir::loop_pattern_detection::LoopPatternKind; +pub(crate) fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool { use super::common_init::CommonPatternInitializer; + use crate::mir::loop_pattern_detection::LoopPatternKind; // Basic pattern check if ctx.pattern_kind != LoopPatternKind::Pattern2Break { @@ -87,11 +614,17 @@ pub fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternContext) /// /// Wrapper around cf_loop_pattern2_with_break to match router signature /// Phase 200-C: Pass fn_body to cf_loop_pattern2_with_break -pub fn lower( +pub(crate) fn lower( builder: &mut MirBuilder, ctx: &super::router::LoopPatternContext, ) -> Result, String> { - builder.cf_loop_pattern2_with_break_impl(ctx.condition, ctx.body, ctx.func_name, ctx.debug, ctx.fn_body) + builder.cf_loop_pattern2_with_break_impl( + ctx.condition, + ctx.body, + ctx.func_name, + ctx.debug, + ctx.fn_body, + ) } impl MirBuilder { @@ -132,483 +665,121 @@ impl MirBuilder { ) -> Result, String> { use crate::mir::join_ir::lowering::loop_with_break_minimal::lower_loop_with_break_minimal; + let verbose = debug || crate::config::env::joinir_dev_enabled(); + // Phase 195: Use unified trace trace::trace().debug("pattern2", "Calling Pattern 2 minimal lowerer"); // Phase 179-B: Use PatternPipelineContext for unified preprocessing // Note: Pattern 2 still needs inline processing for Trim/carrier logic use super::pattern_pipeline::{build_pattern_context, PatternVariant}; - let ctx = build_pattern_context( - self, - condition, - _body, - PatternVariant::Pattern2, - )?; + let ctx = build_pattern_context(self, condition, _body, PatternVariant::Pattern2)?; - // Extract from context - let loop_var_name = ctx.loop_var_name.clone(); - let loop_var_id = ctx.loop_var_id; - let mut carrier_info = ctx.carrier_info.clone(); - let scope = ctx.loop_scope.clone(); - - eprintln!( - "[pattern2/init] PatternPipelineContext: loop_var='{}', loop_var_id={:?}, carriers={}", - loop_var_name, loop_var_id, carrier_info.carriers.len() - ); - - // Phase 195: Use unified trace trace::trace().varmap("pattern2_start", &self.variable_map); - // Phase 201: Create JoinValueSpace for unified ValueId allocation - // This ensures Param IDs (100+) never collide with Local IDs (1000+) - use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; - let mut join_value_space = JoinValueSpace::new(); - - // Phase 200-C: Integrate capture analysis - use crate::mir::loop_pattern_detection::function_scope_capture::{analyze_captured_vars_v2, CapturedEnv}; - use super::condition_env_builder::ConditionEnvBuilder; - use crate::mir::join_ir::lowering::condition_env::ConditionBinding; - - eprintln!("[pattern2/phase200c] fn_body is {}", if fn_body.is_some() { "SOME" } else { "NONE" }); - - let captured_env = if let Some(fn_body_ref) = fn_body { - eprintln!("[pattern2/phase200c] fn_body has {} nodes", fn_body_ref.len()); - - // Phase 200-C: Use v2 API with structural matching - // Pass condition and body directly instead of constructing loop AST - analyze_captured_vars_v2(fn_body_ref, condition, _body, &scope) - } else { - eprintln!("[pattern2/phase200c] fn_body is None, using empty CapturedEnv"); - // fn_body not available - use empty CapturedEnv - CapturedEnv::new() - }; - - eprintln!("[pattern2/capture] Phase 200-C: Captured {} variables", - captured_env.vars.len()); - for var in &captured_env.vars { - eprintln!("[pattern2/capture] '{}': host_id={:?}, immutable={}", - var.name, var.host_id, var.is_immutable); - } - - // Phase 201: Use v2 API with JoinValueSpace - // This allocates loop param and condition vars from Param region (100+) - let (mut env, mut condition_bindings, _loop_var_join_id) = ConditionEnvBuilder::build_for_break_condition_v2( - condition, - &loop_var_name, - &self.variable_map, - loop_var_id, - &mut join_value_space, - )?; - - eprintln!("[pattern2/phase201] Using JoinValueSpace: loop_var '{}' → {:?}", - loop_var_name, env.get(&loop_var_name)); - - // Phase 201: Add captured variables using JoinValueSpace - for var in &captured_env.vars { - if let Some(&host_id) = self.variable_map.get(&var.name) { - // Phase 201: Allocate from Param region to avoid collision with locals - let join_id = join_value_space.alloc_param(); - env.insert(var.name.clone(), join_id); - - // Add to condition_bindings for boundary processing - condition_bindings.push(ConditionBinding { - name: var.name.clone(), - host_value: host_id, - join_value: join_id, - }); - - eprintln!("[pattern2/capture] Phase 201: Added captured '{}': host={:?}, join={:?}", - var.name, host_id, join_id); - } - } - - // Phase 191: Create empty body-local environment - // LoopBodyLocalInitLowerer will populate it during init lowering - use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; - let mut body_local_env = LoopBodyLocalEnv::new(); - - eprintln!("[pattern2/body-local] Phase 201: Created empty body-local environment (param_count={})", - join_value_space.param_count()); - - // Debug: Log condition bindings - eprintln!("[cf_loop/pattern2] Phase 201: ConditionEnv contains {} variables:", env.len()); - eprintln!(" Loop param '{}' → JoinIR {:?}", loop_var_name, env.get(&loop_var_name)); - if !condition_bindings.is_empty() { - eprintln!(" {} condition-only bindings:", condition_bindings.len()); - for binding in &condition_bindings { - eprintln!(" '{}': HOST {:?} → JoinIR {:?}", binding.name, binding.host_value, binding.join_value); - } - } else { - eprintln!(" No condition-only variables"); - } - - // Phase 170-B: Extract break condition from loop body - use super::ast_feature_extractor; - let break_condition_raw = ast_feature_extractor::extract_break_condition(_body) - .ok_or_else(|| "[cf_loop/pattern2] Failed to extract break condition from loop body".to_string())?; - - // Phase 170-B: Check if break is in else clause (requires negation) - let break_in_else = ast_feature_extractor::has_break_in_else_clause(_body); - - // Wrap condition in UnaryOp Not if break is in else clause - use crate::ast::UnaryOperator; - let mut break_condition_node = if break_in_else { - // Extract span from the raw condition node (use unknown as default) - let span = crate::ast::Span::unknown(); - - ASTNode::UnaryOp { - operator: UnaryOperator::Not, - operand: Box::new(break_condition_raw.clone()), - span, - } - } else { - break_condition_raw.clone() - }; - - // Phase 223.5: LoopBodyLocal Condition Promotion - // - // Check for LoopBodyLocal in loop/break conditions and attempt promotion. - // Safe Trim patterns (Category A-3/A-4) are promoted to carriers. - { - use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox; - use crate::mir::loop_pattern_detection::loop_body_cond_promoter::{ - LoopBodyCondPromoter, ConditionPromotionRequest, ConditionPromotionResult, - }; - - // Analyze both header condition and break condition for LoopBodyLocal - let conditions_to_analyze: Vec<&ASTNode> = vec![condition, &break_condition_node]; - - let cond_scope = LoopConditionScopeBox::analyze( - &loop_var_name, - &conditions_to_analyze, - Some(&scope), - ); - - if cond_scope.has_loop_body_local() { - // Try promotion using LoopBodyCondPromoter - let promotion_req = ConditionPromotionRequest { - loop_param_name: &loop_var_name, - cond_scope: &cond_scope, - scope_shape: Some(&scope), - break_cond: Some(&break_condition_node), // Pattern 2 has break - continue_cond: None, // Pattern 2 has no continue - loop_body: _body, - }; - - match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) { - ConditionPromotionResult::Promoted { - carrier_info: promoted_carrier, - promoted_var, - carrier_name, - } => { - eprintln!( - "[pattern2/cond_promoter] LoopBodyLocal '{}' promoted to carrier '{}'", - promoted_var, carrier_name - ); - - // Phase 224: Record promoted variable before merging - carrier_info.promoted_loopbodylocals.push(promoted_var.clone()); - - // Merge promoted carrier into existing CarrierInfo - carrier_info.merge_from(&promoted_carrier); - - eprintln!( - "[pattern2/cond_promoter] Merged carrier '{}' into CarrierInfo (total carriers: {})", - carrier_name, - carrier_info.carrier_count() - ); - eprintln!( - "[pattern2/cond_promoter] Phase 224: Recorded promoted variable '{}' in carrier_info.promoted_loopbodylocals", - promoted_var - ); - - // Check if this is a safe Trim pattern - if let Some(helper) = carrier_info.trim_helper() { - if helper.is_safe_trim() { - eprintln!( - "[pattern2/cond_promoter] Safe Trim pattern detected" - ); - eprintln!( - "[pattern2/cond_promoter] Carrier: '{}', original var: '{}', whitespace chars: {:?}", - helper.carrier_name, helper.original_var, helper.whitespace_chars - ); - // Continue with Pattern2 lowering (fall through) - } else { - return Err(error_messages::format_error_pattern2_trim_not_safe( - &helper.carrier_name, - helper.whitespace_count() - )); - } - } - - // Phase 224-E: Normalize digit_pos condition before lowering - // Transform: digit_pos < 0 → !is_digit_pos - use crate::mir::join_ir::lowering::digitpos_condition_normalizer::DigitPosConditionNormalizer; - break_condition_node = DigitPosConditionNormalizer::normalize( - &break_condition_node, - &promoted_var, - &carrier_name, - ); - - eprintln!( - "[pattern2/phase224e] Normalized break condition for promoted variable '{}' → carrier '{}'", - promoted_var, carrier_name - ); - - // Carrier promoted and merged, proceed with normal lowering - } - ConditionPromotionResult::CannotPromote { reason, vars } => { - // Fail-Fast on promotion failure - return Err(error_messages::format_error_pattern2_promotion_failed(&vars, &reason)); - } - } - } - } - - // Phase 224-D: Allocate join_ids for ALL carriers (including newly merged ones) - // This must happen AFTER promotion/merge to include promoted carriers - eprintln!("[pattern2/phase224d] Allocating join_ids for {} carriers", carrier_info.carriers.len()); - for carrier in &mut carrier_info.carriers { - let carrier_join_id = join_value_space.alloc_param(); - carrier.join_id = Some(carrier_join_id); - eprintln!("[pattern2/phase224d] Allocated carrier '{}' param ID: {:?}", - carrier.name, carrier_join_id); - } - - // Phase 229: Dynamic condition variable resolution for promoted variables - // Instead of using condition_aliases, we dynamically resolve promoted variables - // using promoted_loopbodylocals + naming convention - for promoted_var in &carrier_info.promoted_loopbodylocals { - // Try both naming conventions: - // 1. DigitPos pattern: "var" → "is_var" - // 2. Trim pattern: "var" → "is_var_match" - - let candidate_names = vec![ - format!("is_{}", promoted_var), // DigitPos pattern - format!("is_{}_match", promoted_var), // Trim pattern - ]; - - for carrier_name in candidate_names { - // Check if it's the loop variable - if carrier_name == carrier_info.loop_var_name { - if let Some(join_id) = env.get(&carrier_info.loop_var_name) { - env.insert(promoted_var.clone(), join_id); - eprintln!( - "[pattern2/phase229] Dynamically resolved promoted '{}' → loop_var '{}' (join_id={:?})", - promoted_var, carrier_info.loop_var_name, join_id - ); - break; // Found resolution - } - } - - // Otherwise check carriers - if let Some(carrier) = carrier_info.carriers.iter().find(|c| c.name == carrier_name) { - if let Some(join_id) = carrier.join_id { - env.insert(promoted_var.clone(), join_id); - eprintln!( - "[pattern2/phase229] Dynamically resolved promoted '{}' → carrier '{}' (join_id={:?})", - promoted_var, carrier_name, join_id - ); - break; // Found resolution - } - } - } - } - - // Phase 201: Create alloc_join_value closure using JoinValueSpace - // This ensures all param allocations go through the unified space - // NOTE: Must be created AFTER all direct join_value_space.alloc_param() calls - let mut alloc_join_value = || join_value_space.alloc_param(); - - // Phase 180-3: Delegate Trim/P5 processing to TrimLoopLowerer - let effective_break_condition = if let Some(trim_result) = super::trim_loop_lowering::TrimLoopLowerer::try_lower_trim_like_loop( + let mut inputs = + prepare_pattern2_inputs(self, condition, _body, fn_body, &ctx, verbose)?; + promote_and_prepare_carriers( self, - &scope, condition, - &break_condition_node, _body, - &loop_var_name, - &mut carrier_info, - &mut alloc_join_value, - )? { - // Trim pattern detected and lowered - eprintln!("[pattern2/trim] TrimLoopLowerer processed Trim pattern successfully"); + &mut inputs, + debug, + verbose, + )?; + let (effective_break_condition, normalized_body) = + apply_trim_and_normalize(self, condition, _body, &mut inputs, verbose)?; + let analysis_body = normalized_body.as_deref().unwrap_or(_body); - // Update carrier_info from result - carrier_info = trim_result.carrier_info; - - // Extend condition_bindings with Trim bindings - condition_bindings.extend(trim_result.condition_bindings.iter().cloned()); - - // Add bindings to env for JoinIR - for binding in &trim_result.condition_bindings { - env.insert(binding.name.clone(), binding.join_value); - } - - eprintln!("[pattern2/trim] Extended condition_bindings with {} Trim bindings", trim_result.condition_bindings.len()); - - // Use Trim break condition - trim_result.condition - } else { - // Not a Trim pattern - use original break condition - break_condition_node.clone() - }; - - // Phase 192: Normalize complex addend patterns in loop body - // This transforms patterns like `result = result * 10 + digits.indexOf(ch)` - // into `local temp = digits.indexOf(ch); result = result * 10 + temp` - use crate::mir::join_ir::lowering::complex_addend_normalizer::{ - ComplexAddendNormalizer, NormalizationResult - }; - - let mut normalized_body = Vec::new(); - let mut has_normalization = false; - - for node in _body { - match ComplexAddendNormalizer::normalize_assign(node) { - NormalizationResult::Normalized { temp_def, new_assign, temp_name } => { - eprintln!( - "[pattern2/phase192] Normalized complex addend: temp='{}' inserted before update", - temp_name - ); - normalized_body.push(temp_def); - normalized_body.push(new_assign); - has_normalization = true; - } - NormalizationResult::Unchanged => { - normalized_body.push(node.clone()); - } - } - } - - // Use normalized body for analysis (only if normalization occurred) - let analysis_body = if has_normalization { - &normalized_body - } else { - _body - }; - - // Phase 176-3: Analyze carrier updates from loop body use crate::mir::join_ir::lowering::loop_update_analyzer::LoopUpdateAnalyzer; - let carrier_updates = LoopUpdateAnalyzer::analyze_carrier_updates(analysis_body, &carrier_info.carriers); - - eprintln!( - "[cf_loop/pattern2] Phase 176-3: Analyzed {} carrier updates", - carrier_updates.len() + let carrier_updates = LoopUpdateAnalyzer::analyze_carrier_updates( + analysis_body, + &inputs.carrier_info.carriers, ); - // Phase 176-4: Filter carriers to only include those with actual updates - // Issue: CommonPatternInitializer includes all variables in variable_map as carriers, - // but only variables with updates in the loop body are true carriers. - // Condition-only variables (like 'len', 's') should be excluded. - // - // Phase 227: Keep ConditionOnly carriers even if they don't have updates - // (they're used in loop/break conditions, not updated) - // Phase 247-EX: Keep FromHost carriers (e.g., digit_value) even if they don't have updates - // (they're initialized from loop body and used in update expressions) - let original_carrier_count = carrier_info.carriers.len(); - carrier_info.carriers.retain(|carrier| { - use crate::mir::join_ir::lowering::carrier_info::{CarrierRole, CarrierInit}; + log_pattern2( + verbose, + "updates", + format!( + "Phase 176-3: Analyzed {} carrier updates", + carrier_updates.len() + ), + ); + + let original_carrier_count = inputs.carrier_info.carriers.len(); + inputs.carrier_info.carriers.retain(|carrier| { + use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole}; carrier_updates.contains_key(&carrier.name) || carrier.role == CarrierRole::ConditionOnly - || carrier.init == CarrierInit::FromHost // Phase 247-EX - || carrier.init == CarrierInit::LoopLocalZero // Phase 247-EX: Derived carrier (digit_value) + || carrier.init == CarrierInit::FromHost + || carrier.init == CarrierInit::LoopLocalZero }); - eprintln!( - "[cf_loop/pattern2] Phase 176-4: Filtered carriers: {} → {} (kept only carriers with updates)", - original_carrier_count, - carrier_info.carriers.len() + log_pattern2( + verbose, + "updates", + format!( + "Phase 176-4: Filtered carriers: {} → {} (kept only carriers with updates)", + original_carrier_count, + inputs.carrier_info.carriers.len() + ), ); - // Phase 176-5: Add body-only carriers to ConditionEnv - // Issue: Carriers that are updated in the loop body but not used in the condition - // need to be added to ConditionEnv with their initial values. - for carrier in &carrier_info.carriers { - if env.get(&carrier.name).is_none() { - // Use the carrier's assigned param ID when available to keep IDs aligned + for carrier in &inputs.carrier_info.carriers { + if inputs.env.get(&carrier.name).is_none() { let join_value = carrier .join_id - .unwrap_or_else(|| alloc_join_value()); + .unwrap_or_else(|| inputs.join_value_space.alloc_param()); - // Add to ConditionEnv - env.insert(carrier.name.clone(), join_value); + inputs.env.insert(carrier.name.clone(), join_value); - // Add to condition_bindings for later processing - // Loop-local carriers (e.g., digit_value) have no host slot; skip binding to avoid bogus copies. if carrier.init != CarrierInit::LoopLocalZero { - condition_bindings.push(ConditionBinding { + inputs.condition_bindings.push(ConditionBinding { name: carrier.name.clone(), host_value: carrier.host_id, join_value, }); } else { - eprintln!( - "[cf_loop/pattern2] Phase 247-EX: Skipping host binding for loop-local carrier '{}' (init=LoopLocalZero)", - carrier.name + log_pattern2( + verbose, + "updates", + format!( + "Phase 247-EX: Skipping host binding for loop-local carrier '{}' (init=LoopLocalZero)", + carrier.name + ), ); } - eprintln!( - "[cf_loop/pattern2] Phase 176-5: Added body-only carrier '{}' to ConditionEnv: HOST {:?} → JoinIR {:?}", - carrier.name, carrier.host_id, join_value + log_pattern2( + verbose, + "updates", + format!( + "Phase 176-5: Added body-only carrier '{}' to ConditionEnv: HOST {:?} → JoinIR {:?}", + carrier.name, carrier.host_id, join_value + ), ); } } - // Phase 231: ExprLowerer pilot - pre-validate break condition - // This is a VALIDATION-ONLY step. We check if ExprLowerer can handle the condition, - // but still use the existing proven lowering path. Future phases will replace actual lowering. - { - use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager; - use crate::mir::join_ir::lowering::expr_lowerer::{ExprLowerer, ExprContext, ExprLoweringError}; - - let scope_manager = Pattern2ScopeManager { - condition_env: &env, - loop_body_local_env: Some(&body_local_env), - captured_env: Some(&captured_env), - carrier_info: &carrier_info, - }; - - // Try ExprLowerer validation (doesn't affect actual lowering yet) - // Phase 231: This is data-gathering only - we want to see which patterns work - match ExprLowerer::new(&scope_manager, ExprContext::Condition, self) - .with_debug(debug) - .lower(&effective_break_condition) - { - Ok(_value_id) => { - eprintln!("[pattern2/phase231] ✓ ExprLowerer successfully validated break condition"); - } - Err(ExprLoweringError::UnsupportedNode(msg)) => { - eprintln!("[pattern2/phase231] ℹ ExprLowerer fallback (unsupported): {}", msg); - // This is expected for complex patterns - not an error - } - Err(e) => { - eprintln!("[pattern2/phase231] ⚠ ExprLowerer validation error: {}", e); - // Unexpected error - log but don't fail (legacy path will handle it) - } - } - } - - // Phase 169 / Phase 171-fix / Phase 172-3 / Phase 170-B: Call Pattern 2 lowerer with break_condition - // Phase 33-14: Now returns (JoinModule, JoinFragmentMeta) for expr_result + carrier separation - // Phase 176-3: Multi-carrier support - pass carrier_info and carrier_updates - // Phase 231: ExprLowerer validated above, but we still use proven legacy lowering - eprintln!( - "[pattern2/before_lowerer] About to call lower_loop_with_break_minimal with carrier_info.loop_var_name='{}'", - carrier_info.loop_var_name + log_pattern2( + verbose, + "before_lowerer", + format!( + "About to call lower_loop_with_break_minimal with carrier_info.loop_var_name='{}'", + inputs.carrier_info.loop_var_name + ), ); let (join_module, fragment_meta) = match lower_loop_with_break_minimal( - scope, + inputs.scope, condition, &effective_break_condition, - &env, - &carrier_info, + &inputs.env, + &inputs.carrier_info, &carrier_updates, - analysis_body, // Phase 191/192: Pass normalized body AST for init lowering - Some(&mut body_local_env), // Phase 191: Pass mutable body-local environment - &mut join_value_space, // Phase 201: Unified ValueId allocation (Local region) + 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) ) { Ok((module, meta)) => (module, meta), Err(e) => { @@ -624,23 +795,28 @@ impl MirBuilder { // Phase 33-10: Collect exit bindings from ExitMeta using Box // Phase 228-8: Pass carrier_info to include ConditionOnly carriers use crate::mir::builder::control_flow::joinir::merge::exit_line::ExitMetaCollector; - let exit_bindings = ExitMetaCollector::collect(self, &exit_meta, Some(&carrier_info), debug); + let exit_bindings = + ExitMetaCollector::collect(self, &exit_meta, Some(&inputs.carrier_info), debug); // Phase 176-3: Build input mappings for all carriers // JoinIR main() params: [ValueId(0), ValueId(1), ValueId(2), ...] for (i, carrier1, carrier2, ...) // Host values: [loop_var_id, carrier1_host_id, carrier2_host_id, ...] let mut join_input_slots = vec![ValueId(0)]; // Loop variable - let mut host_input_values = vec![loop_var_id]; // Loop variable + let mut host_input_values = vec![inputs.loop_var_id]; // Loop variable - for (idx, carrier) in carrier_info.carriers.iter().enumerate() { + for (idx, carrier) in inputs.carrier_info.carriers.iter().enumerate() { join_input_slots.push(ValueId((idx + 1) as u32)); host_input_values.push(carrier.host_id); } - eprintln!( - "[cf_loop/pattern2] Phase 176-3: Boundary inputs - {} JoinIR slots, {} host values", - join_input_slots.len(), - host_input_values.len() + log_pattern2( + verbose, + "boundary", + format!( + "Phase 176-3: Boundary inputs - {} JoinIR slots, {} host values", + join_input_slots.len(), + host_input_values.len() + ), ); // Phase 200-2: Use JoinInlineBoundaryBuilder for clean construction @@ -648,14 +824,14 @@ impl MirBuilder { use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder; let boundary = JoinInlineBoundaryBuilder::new() .with_inputs( - join_input_slots, // JoinIR's main() parameters (loop variable + carriers) - host_input_values, // Host's loop variable + carrier values + join_input_slots, // JoinIR's main() parameters (loop variable + carriers) + host_input_values, // Host's loop variable + carrier values ) - .with_condition_bindings(condition_bindings) + .with_condition_bindings(inputs.condition_bindings) .with_exit_bindings(exit_bindings.clone()) .with_expr_result(fragment_meta.expr_result) // Phase 33-14: Pass expr_result to merger - .with_loop_var_name(Some(loop_var_name.clone())) // Phase 33-16: For LoopHeaderPhiBuilder - .with_carrier_info(carrier_info.clone()) // Phase 228-6: Pass carrier_info for ConditionOnly header PHI init + .with_loop_var_name(Some(inputs.loop_var_name.clone())) // Phase 33-16: For LoopHeaderPhiBuilder + .with_carrier_info(inputs.carrier_info.clone()) // Phase 228-6: Pass carrier_info for ConditionOnly header PHI init .build(); // Phase 33-22: Use JoinIRConversionPipeline for unified conversion flow @@ -673,7 +849,10 @@ impl MirBuilder { let void_val = crate::mir::builder::emission::constant::emit_void(self); // Phase 195: Use unified trace - trace::trace().debug("pattern2", &format!("Loop complete, returning Void {:?}", void_val)); + trace::trace().debug( + "pattern2", + &format!("Loop complete, returning Void {:?}", void_val), + ); Ok(Some(void_val)) } @@ -727,21 +906,13 @@ mod tests { }, ASTNode::Assignment { target: Box::new(var("p")), - value: Box::new(bin( - BinaryOperator::Add, - var("p"), - lit_i(1), - )), + value: Box::new(bin(BinaryOperator::Add, var("p"), lit_i(1))), span: span(), }, // num_str = num_str + ch (string append is allowed by CommonPatternInitializer) ASTNode::Assignment { target: Box::new(var("num_str")), - value: Box::new(bin( - BinaryOperator::Add, - var("num_str"), - var("ch"), - )), + value: Box::new(bin(BinaryOperator::Add, var("num_str"), var("ch"))), span: span(), }, ]; @@ -795,8 +966,11 @@ mod tests { let builder = MirBuilder::new(); // Verify pattern classification - assert_eq!(ctx.pattern_kind, LoopPatternKind::Pattern2Break, - "_atoi loop should be classified as Pattern2Break"); + assert_eq!( + ctx.pattern_kind, + LoopPatternKind::Pattern2Break, + "_atoi loop should be classified as Pattern2Break" + ); // Verify Pattern2 lowerer accepts it assert!( 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 ccbd6f2b..384dd643 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 @@ -15,11 +15,11 @@ //! - Hardcoded loop condition (i <= 5), if condition (i % 2 == 1) //! - Kept for backward compatibility with existing tests +use super::super::merge::exit_line::meta_collector::ExitMetaCollector; +use super::super::trace; use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; use crate::mir::ValueId; -use super::super::merge::exit_line::meta_collector::ExitMetaCollector; -use super::super::trace; // Phase 213: Hardcoded ValueIds removed - now using ExitMeta-based exit binding generation // See: ExitMetaCollector usage below (lines 115-135) @@ -32,7 +32,7 @@ use super::super::trace; /// - Pattern kind is Pattern3IfPhi (has if-else with PHI, no break/continue) /// /// NOTE: Priority is now handled by pattern classification, not router order -pub fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool { +pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool { use crate::mir::loop_pattern_detection::LoopPatternKind; ctx.pattern_kind == LoopPatternKind::Pattern3IfPhi } @@ -40,7 +40,7 @@ pub fn can_lower(_builder: &MirBuilder, ctx: &super::router::LoopPatternContext) /// Phase 194: Lowering function for Pattern 3 /// /// Wrapper around cf_loop_pattern3_with_if_phi to match router signature -pub fn lower( +pub(crate) fn lower( builder: &mut MirBuilder, ctx: &super::router::LoopPatternContext, ) -> Result, String> { @@ -72,22 +72,23 @@ impl MirBuilder { ) -> Result, String> { // Phase 179-B: Use PatternPipelineContext for unified preprocessing use super::pattern_pipeline::{build_pattern_context, PatternVariant}; - let ctx = build_pattern_context( - self, - condition, - body, - PatternVariant::Pattern3, - )?; + let ctx = build_pattern_context(self, condition, body, PatternVariant::Pattern3)?; // Phase 213: AST-based if-sum pattern detection // Phase 242-EX-A: Legacy mode removed - all if-sum patterns now handled dynamically if !ctx.is_if_sum_pattern() { // Not an if-sum pattern → let router try other patterns or fall back - trace::trace().debug("pattern3", "Not an if-sum pattern, returning None to try other patterns"); + trace::trace().debug( + "pattern3", + "Not an if-sum pattern, returning None to try other patterns", + ); return Ok(None); } - trace::trace().debug("pattern3", "Detected if-sum pattern, using AST-based lowerer"); + trace::trace().debug( + "pattern3", + "Detected if-sum pattern, using AST-based lowerer", + ); self.lower_pattern3_if_sum(&ctx, condition, body, debug) } @@ -134,35 +135,34 @@ impl MirBuilder { for binding in &condition_bindings { trace::trace().debug( "pattern3/if-sum", - &format!(" '{}': HOST {:?} → JoinIR {:?}", binding.name, binding.host_value, binding.join_value), + &format!( + " '{}': HOST {:?} → JoinIR {:?}", + binding.name, binding.host_value, binding.join_value + ), ); } // Call AST-based if-sum lowerer with ConditionEnv - let (join_module, fragment_meta) = lower_if_sum_pattern( - condition, - if_stmt, - body, - &cond_env, - &mut join_value_space, - )?; + let (join_module, fragment_meta) = + lower_if_sum_pattern(condition, if_stmt, body, &cond_env, &mut join_value_space)?; let exit_meta = &fragment_meta.exit_meta; trace::trace().debug( "pattern3/if-sum", - &format!("ExitMeta: {} exit values", exit_meta.exit_values.len()) + &format!("ExitMeta: {} exit values", exit_meta.exit_values.len()), ); for (carrier_name, join_value) in &exit_meta.exit_values { trace::trace().debug( "pattern3/if-sum", - &format!(" {} → ValueId({})", carrier_name, join_value.0) + &format!(" {} → ValueId({})", carrier_name, join_value.0), ); } // Build exit bindings using ExitMetaCollector // Phase 228-8: Pass carrier_info to include ConditionOnly carriers - let exit_bindings = ExitMetaCollector::collect(self, exit_meta, Some(&ctx.carrier_info), debug); + let exit_bindings = + ExitMetaCollector::collect(self, exit_meta, Some(&ctx.carrier_info), debug); // Build boundary with carrier inputs use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder; @@ -174,9 +174,7 @@ impl MirBuilder { // Allocate join_inputs dynamically from JoinValueSpace // These ValueIds (0, 1, 2, ...) represent JoinIR function parameters - let join_inputs: Vec = (0..total_inputs) - .map(|i| ValueId(i as u32)) - .collect(); + let join_inputs: Vec = (0..total_inputs).map(|i| ValueId(i as u32)).collect(); // Build host_inputs: loop_var + exit_bindings (in order) let mut host_inputs = vec![ctx.loop_var_id]; @@ -197,15 +195,16 @@ impl MirBuilder { "pattern3/if-sum", &format!( "Boundary inputs: {} total (loop_var + {} exit bindings)", - total_inputs, exit_bindings.len() - ) + total_inputs, + exit_bindings.len() + ), ); // Phase 215-2: Pass expr_result to boundary // Phase 220-D: Pass condition_bindings for variable remapping let mut boundary_builder = JoinInlineBoundaryBuilder::new() .with_inputs(join_inputs, host_inputs) - .with_condition_bindings(condition_bindings) // Phase 220-D: Map condition-only vars + .with_condition_bindings(condition_bindings) // Phase 220-D: Map condition-only vars .with_exit_bindings(exit_bindings) .with_loop_var_name(Some(ctx.loop_var_name.clone())); @@ -213,7 +212,7 @@ impl MirBuilder { if let Some(expr_id) = fragment_meta.expr_result { trace::trace().debug( "pattern3/if-sum", - &format!("Passing expr_result={:?} to boundary", expr_id) + &format!("Passing expr_result={:?} to boundary", expr_id), ); boundary_builder = boundary_builder.with_expr_result(Some(expr_id)); } @@ -234,7 +233,7 @@ impl MirBuilder { if let Some(expr_val) = merge_result { trace::trace().debug( "pattern3/if-sum", - &format!("Loop complete, returning expr_result {:?}", expr_val) + &format!("Loop complete, returning expr_result {:?}", expr_val), ); Ok(Some(expr_val)) } else { @@ -243,7 +242,7 @@ impl MirBuilder { let void_val = constant::emit_void(self); trace::trace().debug( "pattern3/if-sum", - &format!("Loop complete, returning Void {:?}", void_val) + &format!("Loop complete, returning Void {:?}", void_val), ); Ok(Some(void_val)) } diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern4_carrier_analyzer.rs b/src/mir/builder/control_flow/joinir/patterns/pattern4_carrier_analyzer.rs index 833eaa72..77a2b051 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern4_carrier_analyzer.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern4_carrier_analyzer.rs @@ -19,7 +19,7 @@ use crate::mir::join_ir::lowering::continue_branch_normalizer::ContinueBranchNor use crate::mir::join_ir::lowering::loop_update_analyzer::{LoopUpdateAnalyzer, UpdateExpr}; use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism -pub struct Pattern4CarrierAnalyzer; +pub(crate) struct Pattern4CarrierAnalyzer; impl Pattern4CarrierAnalyzer { /// Analyze and filter carriers for continue pattern @@ -54,10 +54,8 @@ impl Pattern4CarrierAnalyzer { all_carriers: &CarrierInfo, ) -> Result { // Identify which carriers are updated in loop body - let carrier_updates = LoopUpdateAnalyzer::analyze_carrier_updates( - loop_body, - &all_carriers.carriers, - ); + let carrier_updates = + LoopUpdateAnalyzer::analyze_carrier_updates(loop_body, &all_carriers.carriers); // Filter carriers: only keep those that have update expressions let updated_carriers: Vec = all_carriers @@ -87,7 +85,8 @@ impl Pattern4CarrierAnalyzer { pub fn analyze_carrier_updates( loop_body: &[ASTNode], carriers: &[CarrierVar], - ) -> BTreeMap { // Phase 222.5-D: HashMap → BTreeMap for determinism + ) -> BTreeMap { + // Phase 222.5-D: HashMap → BTreeMap for determinism LoopUpdateAnalyzer::analyze_carrier_updates(loop_body, carriers) } @@ -155,9 +154,9 @@ impl Pattern4CarrierAnalyzer { .. } => { then_body.iter().any(|n| Self::has_continue(n)) - || else_body.as_ref().map_or(false, |body| { - body.iter().any(|n| Self::has_continue(n)) - }) + || else_body + .as_ref() + .map_or(false, |body| body.iter().any(|n| Self::has_continue(n))) } ASTNode::Loop { body, .. } => body.iter().any(|n| Self::has_continue(n)), _ => false, @@ -331,9 +330,7 @@ mod tests { }), span: span.clone(), }], - else_body: Some(vec![ASTNode::Continue { - span: span.clone(), - }]), + else_body: Some(vec![ASTNode::Continue { span: span.clone() }]), span: span.clone(), }]; diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs b/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs index a228a949..5aaf6210 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs @@ -31,12 +31,12 @@ //! - **ExitMeta**: Maps final carrier values to host variable slots //! - **Phase 33-21 fix**: Correct remapping of function parameters to header PHI dsts +use super::super::trace; use crate::ast::ASTNode; use crate::mir::builder::MirBuilder; -use crate::mir::ValueId; -use super::super::trace; -use crate::mir::loop_pattern_detection::error_messages; use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr; +use crate::mir::loop_pattern_detection::error_messages; +use crate::mir::ValueId; use std::collections::BTreeMap; /// Phase 194+: Detection function for Pattern 4 @@ -62,9 +62,9 @@ use std::collections::BTreeMap; /// 3. **Phase 178**: No string/complex carrier updates (JoinIR doesn't support string concat) /// /// If all conditions are met, Pattern 4 is detected. -pub fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool { - use crate::mir::loop_pattern_detection::LoopPatternKind; +pub(crate) fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool { use super::common_init::CommonPatternInitializer; + use crate::mir::loop_pattern_detection::LoopPatternKind; // Basic pattern check if ctx.pattern_kind != LoopPatternKind::Pattern4Continue { @@ -97,17 +97,12 @@ pub fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternContext) /// 4. Create JoinInlineBoundary for input/output mapping /// 5. Merge MIR blocks into current_function /// 6. Return loop result (first carrier value) -pub fn lower( +pub(crate) fn lower( builder: &mut MirBuilder, ctx: &super::router::LoopPatternContext, ) -> Result, String> { // Phase 33-19: Connect stub to actual implementation - builder.cf_loop_pattern4_with_continue( - ctx.condition, - ctx.body, - ctx.func_name, - ctx.debug, - ) + builder.cf_loop_pattern4_with_continue(ctx.condition, ctx.body, ctx.func_name, ctx.debug) } impl MirBuilder { @@ -170,10 +165,10 @@ fn prepare_pattern4_context( ) -> Result { use super::pattern4_carrier_analyzer::Pattern4CarrierAnalyzer; use super::pattern_pipeline::{build_pattern_context, PatternVariant}; - use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox; use crate::mir::loop_pattern_detection::loop_body_cond_promoter::{ - LoopBodyCondPromoter, ConditionPromotionRequest, ConditionPromotionResult, + ConditionPromotionRequest, ConditionPromotionResult, LoopBodyCondPromoter, }; + use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox; // Normalize continue branches for analysis/lowering let normalized_body = Pattern4CarrierAnalyzer::normalize_continue_branches(body); @@ -195,28 +190,33 @@ fn prepare_pattern4_context( &normalized_body, &carrier_info_prelim.carriers, ); - let mut carrier_info = Pattern4CarrierAnalyzer::analyze_carriers( - &normalized_body, - &carrier_info_prelim, - )?; + let mut carrier_info = + Pattern4CarrierAnalyzer::analyze_carriers(&normalized_body, &carrier_info_prelim)?; trace::trace().debug( "pattern4", &format!( "CarrierInfo: loop_var={}, carriers={:?}", carrier_info.loop_var_name, - carrier_info.carriers.iter().map(|c| &c.name).collect::>() - ) + carrier_info + .carriers + .iter() + .map(|c| &c.name) + .collect::>() + ), ); trace::trace().debug( "pattern4", - &format!("Analyzed {} carrier update expressions", carrier_updates.len()) + &format!( + "Analyzed {} carrier update expressions", + carrier_updates.len() + ), ); for (carrier_name, update_expr) in &carrier_updates { trace::trace().debug( "pattern4", - &format!(" {} → {:?}", carrier_name, update_expr) + &format!(" {} → {:?}", carrier_name, update_expr), ); } @@ -228,18 +228,15 @@ fn prepare_pattern4_context( vec![condition] }; - let cond_scope = LoopConditionScopeBox::analyze( - &loop_var_name, - &conditions_to_analyze, - Some(&loop_scope), - ); + let cond_scope = + LoopConditionScopeBox::analyze(&loop_var_name, &conditions_to_analyze, Some(&loop_scope)); if cond_scope.has_loop_body_local() { let promotion_req = ConditionPromotionRequest { loop_param_name: &loop_var_name, cond_scope: &cond_scope, scope_shape: Some(&loop_scope), - break_cond: None, // Pattern 4 has no break + break_cond: None, // Pattern 4 has no break continue_cond, loop_body: &normalized_body, }; @@ -285,13 +282,15 @@ fn prepare_pattern4_context( } else { return Err(error_messages::format_error_pattern4_trim_not_safe( &helper.carrier_name, - helper.whitespace_count() + helper.whitespace_count(), )); } } } ConditionPromotionResult::CannotPromote { reason, vars } => { - return Err(error_messages::format_error_pattern4_promotion_failed(&vars, &reason)); + return Err(error_messages::format_error_pattern4_promotion_failed( + &vars, &reason, + )); } } } @@ -311,11 +310,11 @@ fn lower_pattern4_joinir( prepared: &Pattern4Prepared, debug: bool, ) -> Result, String> { + use super::super::merge::exit_line::meta_collector::ExitMetaCollector; + use super::conversion_pipeline::JoinIRConversionPipeline; use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; use crate::mir::join_ir::lowering::loop_with_continue_minimal::lower_loop_with_continue_minimal; - use super::super::merge::exit_line::meta_collector::ExitMetaCollector; use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder; - use super::conversion_pipeline::JoinIRConversionPipeline; trace::trace().varmap("pattern4_start", &builder.variable_map); @@ -338,12 +337,12 @@ fn lower_pattern4_joinir( trace::trace().debug( "pattern4", - &format!("ExitMeta: {} exit bindings", exit_meta.exit_values.len()) + &format!("ExitMeta: {} exit bindings", exit_meta.exit_values.len()), ); for (carrier_name, join_value) in &exit_meta.exit_values { trace::trace().debug( "pattern4", - &format!(" {} → ValueId({})", carrier_name, join_value.0) + &format!(" {} → ValueId({})", carrier_name, join_value.0), ); } @@ -356,7 +355,9 @@ fn lower_pattern4_joinir( for carrier in &prepared.carrier_info.carriers { if !exit_bindings.iter().any(|b| b.carrier_name == carrier.name) { - return Err(error_messages::format_error_pattern4_carrier_not_found(&carrier.name)); + return Err(error_messages::format_error_pattern4_carrier_not_found( + &carrier.name, + )); } } @@ -368,21 +369,27 @@ fn lower_pattern4_joinir( trace::trace().debug( "pattern4", - &format!("host_inputs: {:?}", host_inputs.iter().map(|v| v.0).collect::>()) + &format!( + "host_inputs: {:?}", + host_inputs.iter().map(|v| v.0).collect::>() + ), ); - let mut join_inputs = vec![ValueId(0)]; // ValueId(0) = i_init in JoinIR + let mut join_inputs = vec![ValueId(0)]; // ValueId(0) = i_init in JoinIR for idx in 0..prepared.carrier_info.carriers.len() { - join_inputs.push(ValueId((idx + 1) as u32)); // ValueId(1..N) = carrier inits + join_inputs.push(ValueId((idx + 1) as u32)); // ValueId(1..N) = carrier inits } trace::trace().debug( "pattern4", - &format!("join_inputs: {:?}", join_inputs.iter().map(|v| v.0).collect::>()) + &format!( + "join_inputs: {:?}", + join_inputs.iter().map(|v| v.0).collect::>() + ), ); let boundary = JoinInlineBoundaryBuilder::new() - .with_inputs(join_inputs, host_inputs) // Dynamic carrier count + .with_inputs(join_inputs, host_inputs) // Dynamic carrier count .with_exit_bindings(exit_bindings) .with_loop_var_name(Some(prepared.loop_var_name.clone())) .with_carrier_info(prepared.carrier_info.clone()) @@ -397,7 +404,10 @@ fn lower_pattern4_joinir( )?; let void_val = crate::mir::builder::emission::constant::emit_void(builder); - trace::trace().debug("pattern4", &format!("Loop complete, returning Void {:?}", void_val)); + trace::trace().debug( + "pattern4", + &format!("Loop complete, returning Void {:?}", void_val), + ); Ok(Some(void_val)) } diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern_pipeline.rs b/src/mir/builder/control_flow/joinir/patterns/pattern_pipeline.rs index 2a97fa5b..53a327ec 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern_pipeline.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern_pipeline.rs @@ -34,14 +34,14 @@ use crate::ast::ASTNode; 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_env::ConditionBinding; +use crate::mir::join_ir::lowering::condition_env::ConditionEnv; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr; -use crate::mir::join_ir::lowering::loop_update_summary::LoopUpdateSummary; // Phase 213 +use crate::mir::join_ir::lowering::loop_update_summary::LoopUpdateSummary; // Phase 213 use crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper; -use crate::mir::ValueId; use crate::mir::BasicBlockId; +use crate::mir::ValueId; use std::collections::{BTreeMap, BTreeSet}; // Phase 222.5-D: HashMap → BTreeMap for determinism use super::common_init::CommonPatternInitializer; @@ -72,9 +72,8 @@ use super::loop_scope_shape_builder::LoopScopeShapeBuilder; /// let join_module = lower_simple_while_minimal(ctx.loop_scope)?; /// ``` #[derive(Debug, Clone)] -pub struct PatternPipelineContext { +pub(crate) struct PatternPipelineContext { // === Common Data (All Patterns) === - /// Loop variable name (e.g., "i") pub loop_var_name: String, @@ -88,7 +87,6 @@ pub struct PatternPipelineContext { pub loop_scope: LoopScopeShape, // === Pattern 2/4: Break/Continue Condition === - /// Condition environment (variable → JoinIR ValueId mapping) /// Used by Pattern 2 (break condition) and Pattern 4 (continue condition) #[allow(dead_code)] @@ -105,21 +103,18 @@ pub struct PatternPipelineContext { pub carrier_updates: Option>, // Phase 222.5-D: HashMap → BTreeMap for determinism // === Pattern 2/4: Trim Pattern Support === - /// Trim loop helper (if Trim pattern detected during promotion) /// Used by Pattern 2 (string trim) - Pattern 4 support TBD #[allow(dead_code)] pub trim_helper: Option, // === Pattern 2: Break Condition === - /// Effective break condition (may be modified for Trim pattern) /// Used only by Pattern 2 #[allow(dead_code)] pub break_condition: Option, // === Pattern 3: If-Sum Generalization (Phase 213) === - /// Loop condition AST node /// Used by Pattern 3 for dynamic loop condition lowering #[allow(dead_code)] @@ -138,7 +133,7 @@ pub struct PatternPipelineContext { /// Pattern variant selector #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PatternVariant { +pub(crate) enum PatternVariant { /// Pattern 1: Simple while loop (no break, no continue, no if-else PHI) Pattern1, /// Pattern 2: Loop with break statement @@ -193,7 +188,7 @@ impl PatternPipelineContext { // Complex conditions (e.g., i % 2 == 1) → fallback to legacy mode if let Some(ASTNode::If { condition, .. }) = if_stmt { use crate::mir::join_ir::lowering::condition_pattern::{ - analyze_condition_pattern, normalize_comparison, ConditionPattern + analyze_condition_pattern, normalize_comparison, ConditionPattern, }; // (a) Pattern check: must be SimpleComparison @@ -213,7 +208,10 @@ impl PatternPipelineContext { // Phase 219: Use assignment-based carrier detection // (1 counter like "i" + 1-2 accumulators like "sum", "count") use crate::mir::join_ir::lowering::loop_update_summary::analyze_loop_updates_from_ast; - let carrier_names: Vec = self.carrier_info.carriers.iter() + let carrier_names: Vec = self + .carrier_info + .carriers + .iter() .map(|c| c.name.clone()) .collect(); @@ -232,9 +230,9 @@ impl PatternPipelineContext { /// /// Returns the first if statement found in loop_body, if any. pub fn extract_if_statement(&self) -> Option<&ASTNode> { - self.loop_body.as_ref().and_then(|body| { - body.iter().find(|stmt| matches!(stmt, ASTNode::If { .. })) - }) + self.loop_body + .as_ref() + .and_then(|body| body.iter().find(|stmt| matches!(stmt, ASTNode::If { .. }))) } } @@ -263,20 +261,19 @@ impl PatternPipelineContext { /// - Loop variable not found in variable_map /// - Condition variable not found (Pattern 2/4) /// - Trim pattern promotion fails (Pattern 2/4) -pub fn build_pattern_context( +pub(crate) fn build_pattern_context( builder: &mut MirBuilder, condition: &ASTNode, body: &[ASTNode], variant: PatternVariant, ) -> Result { // Step 1: Common initialization (all patterns) - let (loop_var_name, loop_var_id, carrier_info) = - CommonPatternInitializer::initialize_pattern( - builder, - condition, - &builder.variable_map, - None, // No exclusions for now (Pattern 2/4 will filter carriers later) - )?; + let (loop_var_name, loop_var_id, carrier_info) = CommonPatternInitializer::initialize_pattern( + builder, + condition, + &builder.variable_map, + None, // No exclusions for now (Pattern 2/4 will filter carriers later) + )?; // Step 2: Build LoopScopeShape let loop_scope = match variant { @@ -304,39 +301,46 @@ pub fn build_pattern_context( }; // Step 3: Pattern-specific preprocessing - let (condition_env, condition_bindings, carrier_updates, trim_helper, break_condition, - loop_condition, loop_body, loop_update_summary) = - match variant { - PatternVariant::Pattern1 => { - // Pattern 1: No additional preprocessing needed - (None, None, None, None, None, None, None, None) - } - PatternVariant::Pattern3 => { - // Pattern 3: Phase 213 - Store loop condition and body for AST-based lowering - ( - None, // No condition_env - None, // No condition_bindings - None, // No carrier_updates (old style) - None, // No trim_helper - None, // No break_condition - Some(condition.clone()), // loop_condition (Phase 213) - Some(body.to_vec()), // loop_body (Phase 213) - None, // loop_update_summary (TODO: Phase 213-2-3) - ) - } - PatternVariant::Pattern2 | PatternVariant::Pattern4 => { - // Pattern 2/4: Full preprocessing will be handled by existing code - // For now, return empty values (will be populated by pattern-specific logic) - // - // Note: Pattern 2/4 have complex preprocessing that includes: - // - Break/continue condition analysis - // - Carrier update analysis - // - Trim pattern promotion - // These will remain in pattern2/pattern4.rs for now and will be - // gradually migrated into this pipeline in future phases. - (None, None, None, None, None, None, None, None) - } - }; + let ( + condition_env, + condition_bindings, + carrier_updates, + trim_helper, + break_condition, + loop_condition, + loop_body, + loop_update_summary, + ) = match variant { + PatternVariant::Pattern1 => { + // Pattern 1: No additional preprocessing needed + (None, None, None, None, None, None, None, None) + } + PatternVariant::Pattern3 => { + // Pattern 3: Phase 213 - Store loop condition and body for AST-based lowering + ( + None, // No condition_env + None, // No condition_bindings + None, // No carrier_updates (old style) + None, // No trim_helper + None, // No break_condition + Some(condition.clone()), // loop_condition (Phase 213) + Some(body.to_vec()), // loop_body (Phase 213) + None, // loop_update_summary (TODO: Phase 213-2-3) + ) + } + PatternVariant::Pattern2 | PatternVariant::Pattern4 => { + // Pattern 2/4: Full preprocessing will be handled by existing code + // For now, return empty values (will be populated by pattern-specific logic) + // + // Note: Pattern 2/4 have complex preprocessing that includes: + // - Break/continue condition analysis + // - Carrier update analysis + // - Trim pattern promotion + // These will remain in pattern2/pattern4.rs for now and will be + // gradually migrated into this pipeline in future phases. + (None, None, None, None, None, None, None, None) + } + }; Ok(PatternPipelineContext { loop_var_name, @@ -348,9 +352,9 @@ pub fn build_pattern_context( carrier_updates, trim_helper, break_condition, - loop_condition, // Phase 213 - loop_body, // Phase 213 - loop_update_summary, // Phase 213 + loop_condition, // Phase 213 + loop_body, // Phase 213 + loop_update_summary, // Phase 213 }) } @@ -398,14 +402,14 @@ mod tests { host_id: ValueId(10), join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, - init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 }, CarrierVar { name: "count".to_string(), host_id: ValueId(11), join_id: None, role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, - init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 + init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 }, ], trim_helper: None, @@ -423,9 +427,9 @@ mod tests { carrier_updates: None, trim_helper: None, break_condition: None, - loop_condition: None, // Phase 213 - loop_body: None, // Phase 213 - loop_update_summary: None, // Phase 213 + loop_condition: None, // Phase 213 + loop_body: None, // Phase 213 + loop_update_summary: None, // Phase 213 }; assert_eq!(ctx.carrier_count(), 2); @@ -466,9 +470,9 @@ mod tests { whitespace_chars: vec![" ".to_string(), "\t".to_string()], }), break_condition: None, - loop_condition: None, // Phase 213 - loop_body: None, // Phase 213 - loop_update_summary: None, // Phase 213 + loop_condition: None, // Phase 213 + loop_body: None, // Phase 213 + loop_update_summary: None, // Phase 213 }; assert!(ctx.is_trim_pattern()); diff --git a/src/mir/builder/control_flow/joinir/patterns/router.rs b/src/mir/builder/control_flow/joinir/patterns/router.rs index 3cd2cfec..3da3cfbb 100644 --- a/src/mir/builder/control_flow/joinir/patterns/router.rs +++ b/src/mir/builder/control_flow/joinir/patterns/router.rs @@ -30,7 +30,7 @@ use crate::mir::loop_pattern_detection::{LoopFeatures, LoopPatternKind}; use super::ast_feature_extractor as ast_features; /// Context passed to pattern detect/lower functions -pub struct LoopPatternContext<'a> { +pub(crate) struct LoopPatternContext<'a> { /// Loop condition AST node pub condition: &'a ASTNode, @@ -69,7 +69,7 @@ impl<'a> LoopPatternContext<'a> { /// Phase 194+: Automatically detects continue/break statements in body /// Phase 192: Extract features and classify pattern from AST /// Phase 193: Feature extraction delegated to ast_feature_extractor module - pub fn new( + pub(crate) fn new( condition: &'a ASTNode, body: &'a [ASTNode], func_name: &'a str, @@ -99,7 +99,7 @@ impl<'a> LoopPatternContext<'a> { } /// Phase 200-C: Create context with fn_body for capture analysis - pub fn with_fn_body( + pub(crate) fn with_fn_body( condition: &'a ASTNode, body: &'a [ASTNode], func_name: &'a str, @@ -117,19 +117,19 @@ impl<'a> LoopPatternContext<'a> { /// Entry in the loop pattern router table. /// Each pattern registers a detect function and a lower function. -pub struct LoopPatternEntry { +pub(crate) struct LoopPatternEntry { /// Human-readable pattern name for debugging - pub name: &'static str, + pub(crate) name: &'static str, /// Priority (lower = tried first). Pattern1=10, Pattern2=20, Pattern3=30 #[allow(dead_code)] - pub priority: u8, + pub(crate) priority: u8, /// Detection function: returns true if this pattern matches - pub detect: fn(&MirBuilder, &LoopPatternContext) -> bool, + pub(crate) detect: fn(&MirBuilder, &LoopPatternContext) -> bool, /// Lowering function: performs the actual JoinIR generation - pub lower: fn(&mut MirBuilder, &LoopPatternContext) -> Result, String>, + pub(crate) lower: fn(&mut MirBuilder, &LoopPatternContext) -> Result, String>, } /// Static table of all registered loop patterns. @@ -156,16 +156,16 @@ pub struct LoopPatternEntry { /// - Structure: has_break && !has_continue /// /// Note: func_name is now only used for debug logging, not pattern detection -pub static LOOP_PATTERNS: &[LoopPatternEntry] = &[ +pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[ LoopPatternEntry { name: "Pattern4_WithContinue", - priority: 5, // Highest priority - continue is most specific + priority: 5, // Highest priority - continue is most specific detect: super::pattern4_with_continue::can_lower, lower: super::pattern4_with_continue::lower, }, LoopPatternEntry { name: "Pattern3_WithIfPhi", - priority: 30, // NOTE: Pattern 3 must be checked BEFORE Pattern 1 (both use "main") + priority: 30, // NOTE: Pattern 3 must be checked BEFORE Pattern 1 (both use "main") detect: super::pattern3_with_if_phi::can_lower, lower: super::pattern3_with_if_phi::lower, }, @@ -195,7 +195,7 @@ pub static LOOP_PATTERNS: &[LoopPatternEntry] = &[ /// - Pattern detection: `ctx.pattern_kind` (from `loop_pattern_detection::classify`) /// - No redundant pattern detection in detect functions /// - All patterns use structure-based classification -pub fn route_loop_pattern( +pub(crate) fn route_loop_pattern( builder: &mut MirBuilder, ctx: &LoopPatternContext, ) -> Result, String> { @@ -217,7 +217,13 @@ pub fn route_loop_pattern( // No pattern matched - return None (caller will handle error) // Phase 187-2: Legacy LoopBuilder removed, all loops must use JoinIR if ctx.debug { - trace::trace().debug("route", &format!("No pattern matched for function '{}' (pattern_kind={:?})", ctx.func_name, ctx.pattern_kind)); + trace::trace().debug( + "route", + &format!( + "No pattern matched for function '{}' (pattern_kind={:?})", + ctx.func_name, ctx.pattern_kind + ), + ); } Ok(None) } 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 c3ba876f..ec573365 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 @@ -50,7 +50,7 @@ use crate::mir::ValueId; /// Trim pattern lowering orchestrator /// /// Phase 180: Single entry point for all Trim/P5 lowering operations. -pub struct TrimLoopLowerer; +pub(crate) struct TrimLoopLowerer; /// Result of successful Trim lowering preprocessing /// @@ -59,7 +59,7 @@ pub struct TrimLoopLowerer; /// - Updated carrier info with promoted carrier /// - Condition environment bindings /// - Trim helper for pattern-specific operations -pub struct TrimLoweringResult { +pub(crate) struct TrimLoweringResult { /// Replaced break condition (e.g., `!is_carrier`) /// /// Pattern2/4 will use this instead of the original break condition @@ -98,12 +98,14 @@ impl TrimLoopLowerer { Self::is_var_used_in_condition(var_name, left) || Self::is_var_used_in_condition(var_name, right) } - ASTNode::UnaryOp { operand, .. } => { - Self::is_var_used_in_condition(var_name, operand) - } - ASTNode::MethodCall { object, arguments, .. } => { + ASTNode::UnaryOp { operand, .. } => Self::is_var_used_in_condition(var_name, operand), + ASTNode::MethodCall { + object, arguments, .. + } => { Self::is_var_used_in_condition(var_name, object) - || arguments.iter().any(|arg| Self::is_var_used_in_condition(var_name, arg)) + || arguments + .iter() + .any(|arg| Self::is_var_used_in_condition(var_name, arg)) } // Add other node types as needed _ => false, @@ -173,11 +175,8 @@ impl TrimLoopLowerer { // TODO: Phase 180-3 will implement full logic from Pattern2 // Step 1: Check if condition references LoopBodyLocal variables - let cond_scope = LoopConditionScopeBox::analyze( - loop_var_name, - &[loop_cond, break_cond], - Some(scope), - ); + let cond_scope = + LoopConditionScopeBox::analyze(loop_var_name, &[loop_cond, break_cond], Some(scope)); eprintln!( "[TrimLoopLowerer] Analyzing condition scope: {} variables", @@ -194,7 +193,9 @@ impl TrimLoopLowerer { // Phase 183-2: Filter to only condition LoopBodyLocal (skip body-only) use crate::mir::loop_pattern_detection::loop_condition_scope::CondVarScope; - let condition_body_locals: Vec<_> = cond_scope.vars.iter() + let condition_body_locals: Vec<_> = cond_scope + .vars + .iter() .filter(|v| v.scope == CondVarScope::LoopBodyLocal) .filter(|v| { // Check if variable is actually used in break condition @@ -213,7 +214,10 @@ impl TrimLoopLowerer { eprintln!( "[TrimLoopLowerer] Phase 183: Found {} condition LoopBodyLocal variables: {:?}", condition_body_locals.len(), - condition_body_locals.iter().map(|v| &v.name).collect::>() + condition_body_locals + .iter() + .map(|v| &v.name) + .collect::>() ); // Step 2: Try promotion via LoopBodyCarrierPromoter @@ -242,14 +246,12 @@ impl TrimLoopLowerer { ); // Step 4: Safety check via TrimLoopHelper - let trim_helper = carrier_info - .trim_helper() - .ok_or_else(|| { - format!( - "[TrimLoopLowerer] Promoted but no TrimLoopHelper attached (carrier: '{}')", - trim_info.carrier_name - ) - })?; + let trim_helper = carrier_info.trim_helper().ok_or_else(|| { + format!( + "[TrimLoopLowerer] Promoted but no TrimLoopHelper attached (carrier: '{}')", + trim_info.carrier_name + ) + })?; if !trim_helper.is_safe_trim() { return Err(format!( @@ -262,15 +264,13 @@ 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 + trim_helper.carrier_name, + trim_helper.original_var, + trim_helper.whitespace_chars ); // Step 5: Generate carrier initialization code - Self::generate_carrier_initialization( - builder, - body, - trim_helper, - )?; + Self::generate_carrier_initialization(builder, body, trim_helper)?; eprintln!( "[TrimLoopLowerer] Registered carrier '{}' in variable_map", @@ -286,11 +286,8 @@ impl TrimLoopLowerer { ); // Step 7: Setup ConditionEnv bindings - let condition_bindings = Self::setup_condition_env_bindings( - builder, - trim_helper, - alloc_join_value, - )?; + let condition_bindings = + Self::setup_condition_env_bindings(builder, trim_helper, alloc_join_value)?; eprintln!( "[TrimLoopLowerer] Added {} condition bindings", @@ -333,13 +330,14 @@ impl TrimLoopLowerer { use crate::mir::builder::control_flow::joinir::patterns::trim_pattern_validator::TrimPatternValidator; // Extract substring pattern from body - let (s_name, start_expr) = TrimPatternValidator::extract_substring_args(body, &trim_helper.original_var) - .ok_or_else(|| { - format!( + let (s_name, start_expr) = + TrimPatternValidator::extract_substring_args(body, &trim_helper.original_var) + .ok_or_else(|| { + format!( "[TrimLoopLowerer] Failed to extract substring pattern for Trim carrier '{}'", trim_helper.carrier_name ) - })?; + })?; eprintln!( "[TrimLoopLowerer] Extracted substring pattern: s='{}', start={:?}", @@ -347,11 +345,10 @@ impl TrimLoopLowerer { ); // Get ValueIds for string and start - let s_id = builder - .variable_map - .get(&s_name) - .copied() - .ok_or_else(|| format!("[TrimLoopLowerer] String variable '{}' not found", s_name))?; + let s_id = + builder.variable_map.get(&s_name).copied().ok_or_else(|| { + format!("[TrimLoopLowerer] String variable '{}' not found", s_name) + })?; // Compile start expression to get ValueId let start_id = builder.build_expression_impl(*start_expr)?; @@ -378,11 +375,17 @@ impl TrimLoopLowerer { vec![start_id, start_plus_1], )?; - eprintln!("[TrimLoopLowerer] Generated initial substring call: ch0 = {:?}", ch0); + eprintln!( + "[TrimLoopLowerer] Generated initial substring call: ch0 = {:?}", + ch0 + ); // Generate: is_ch_match0 = (ch0 == " " || ch0 == "\t" || ...) - let is_ch_match0 = - TrimPatternValidator::emit_whitespace_check(builder, ch0, &trim_helper.whitespace_chars)?; + let is_ch_match0 = TrimPatternValidator::emit_whitespace_check( + builder, + ch0, + &trim_helper.whitespace_chars, + )?; eprintln!( "[TrimLoopLowerer] Generated initial whitespace check: is_ch_match0 = {:?}", @@ -486,7 +489,9 @@ mod tests { }; assert!(TrimLoopLowerer::is_var_used_in_condition("ch", &var_node)); - assert!(!TrimLoopLowerer::is_var_used_in_condition("other", &var_node)); + assert!(!TrimLoopLowerer::is_var_used_in_condition( + "other", &var_node + )); } #[test] @@ -506,7 +511,9 @@ mod tests { }; assert!(TrimLoopLowerer::is_var_used_in_condition("ch", &cond_node)); - assert!(!TrimLoopLowerer::is_var_used_in_condition("other", &cond_node)); + assert!(!TrimLoopLowerer::is_var_used_in_condition( + "other", &cond_node + )); } #[test] @@ -525,8 +532,13 @@ mod tests { span: Span::unknown(), }; - assert!(TrimLoopLowerer::is_var_used_in_condition("digit_pos", &cond_node)); - assert!(!TrimLoopLowerer::is_var_used_in_condition("other", &cond_node)); + assert!(TrimLoopLowerer::is_var_used_in_condition( + "digit_pos", + &cond_node + )); + assert!(!TrimLoopLowerer::is_var_used_in_condition( + "other", &cond_node + )); } #[test] @@ -566,6 +578,8 @@ mod tests { }; assert!(TrimLoopLowerer::is_var_used_in_condition("ch", &or_node)); - assert!(!TrimLoopLowerer::is_var_used_in_condition("other", &or_node)); + assert!(!TrimLoopLowerer::is_var_used_in_condition( + "other", &or_node + )); } } diff --git a/src/mir/join_ir_runner.rs b/src/mir/join_ir_runner.rs index 1c6d9f77..90a29264 100644 --- a/src/mir/join_ir_runner.rs +++ b/src/mir/join_ir_runner.rs @@ -53,6 +53,8 @@ fn execute_function( mut current_func: JoinFuncId, mut current_args: Vec, ) -> Result { + let verbose = crate::config::env::joinir_dev_enabled(); + 'exec: loop { let func = module.functions.get(¤t_func).ok_or_else(|| { JoinRuntimeError::new(format!("Function {:?} not found", current_func)) @@ -137,10 +139,12 @@ fn execute_function( } => { // 1. Evaluate cond (Bool or Int) let cond_value = read_var(&locals, *cond)?; - eprintln!( - "[SELECT DEBUG] cond={:?}, cond_value={:?}", - cond, cond_value - ); + if verbose { + eprintln!( + "[joinir/runner/select] cond={:?}, cond_value={:?}", + cond, cond_value + ); + } let cond_bool = match cond_value { JoinValue::Bool(b) => b, JoinValue::Int(i) => i != 0, // Int も許す(0=false, それ以外=true) @@ -155,17 +159,21 @@ fn execute_function( // 2. Select then_val or else_val let then_value = read_var(&locals, *then_val)?; let else_value = read_var(&locals, *else_val)?; - eprintln!( - "[SELECT DEBUG] cond_bool={}, then_val={:?}={:?}, else_val={:?}={:?}", - cond_bool, then_val, then_value, else_val, else_value - ); + if verbose { + eprintln!( + "[joinir/runner/select] cond_bool={}, then_val={:?}={:?}, else_val={:?}={:?}", + cond_bool, then_val, then_value, else_val, else_value + ); + } let selected_id = if cond_bool { *then_val } else { *else_val }; let selected_value = read_var(&locals, selected_id)?; - eprintln!( - "[SELECT DEBUG] selected_id={:?}, selected_value={:?}", - selected_id, selected_value - ); + if verbose { + eprintln!( + "[joinir/runner/select] selected_id={:?}, selected_value={:?}", + selected_id, selected_value + ); + } // 3. Write to dst locals.insert(*dst, selected_value);