diff --git a/src/macro/ast_json.rs b/src/macro/ast_json.rs index 302899b3..a5e019c7 100644 --- a/src/macro/ast_json.rs +++ b/src/macro/ast_json.rs @@ -65,7 +65,8 @@ pub fn ast_to_json(ast: &ASTNode) -> Value { // For single-variable declarations, add "name" and "expr" for JoinIR compatibility let (name, expr) = if variables.len() == 1 { let n = variables[0].clone(); - let e = initial_values.get(0) + let e = initial_values + .get(0) .and_then(|opt| opt.as_ref()) .map(|v| ast_to_json(v)); (Some(n), e) @@ -73,7 +74,8 @@ pub fn ast_to_json(ast: &ASTNode) -> Value { (None, None) }; - let inits: Vec<_> = initial_values.into_iter() + let inits: Vec<_> = initial_values + .into_iter() .map(|opt| opt.map(|v| ast_to_json(&v))) .collect(); @@ -147,7 +149,11 @@ pub fn ast_to_json(ast: &ASTNode) -> Value { } => { let op_str = bin_to_str(&operator); // JoinIR Frontend distinguishes between Binary (arithmetic) and Compare - let type_str = if is_compare_op(&operator) { "Compare" } else { "Binary" }; + let type_str = if is_compare_op(&operator) { + "Compare" + } else { + "Binary" + }; json!({ "kind": "BinaryOp", "type": type_str, diff --git a/src/mir/builder/control_flow.rs b/src/mir/builder/control_flow.rs index eeffed31..5b55b058 100644 --- a/src/mir/builder/control_flow.rs +++ b/src/mir/builder/control_flow.rs @@ -81,10 +81,16 @@ impl super::MirBuilder { // Note: Arity does NOT include implicit `me` receiver let is_target = match func_name.as_str() { "JsonTokenizer.print_tokens/0" => { - std::env::var("HAKO_JOINIR_PRINT_TOKENS_MAIN").ok().as_deref() == Some("1") + std::env::var("HAKO_JOINIR_PRINT_TOKENS_MAIN") + .ok() + .as_deref() + == Some("1") } "ArrayExtBox.filter/2" => { - std::env::var("HAKO_JOINIR_ARRAY_FILTER_MAIN").ok().as_deref() == Some("1") + std::env::var("HAKO_JOINIR_ARRAY_FILTER_MAIN") + .ok() + .as_deref() + == Some("1") } _ => false, }; @@ -133,10 +139,10 @@ impl super::MirBuilder { debug: bool, ) -> Result, String> { use super::loop_frontend_binding::LoopFrontendBinding; - use crate::r#macro::ast_json::ast_to_json; use crate::mir::join_ir::frontend::{AstToJoinIrLowerer, JoinFuncMetaMap}; use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta; use crate::mir::types::ConstValue; + use crate::r#macro::ast_json::ast_to_json; // Phase 50: Create appropriate binding based on function name let binding = match func_name { @@ -156,16 +162,13 @@ impl super::MirBuilder { if debug { eprintln!( "[cf_loop/joinir] Using binding: counter={}, acc={:?}, pattern={:?}", - binding.counter_var, - binding.accumulator_var, - binding.pattern + binding.counter_var, binding.accumulator_var, binding.pattern ); } // Step 1: Convert condition and body to JSON let condition_json = ast_to_json(condition); - let mut body_json: Vec = - body.iter().map(|s| ast_to_json(s)).collect(); + let mut body_json: Vec = body.iter().map(|s| ast_to_json(s)).collect(); // Phase 50: Rename variables in body (e.g., "out" → "acc" for filter) binding.rename_body_variables(&mut body_json); @@ -192,7 +195,10 @@ impl super::MirBuilder { continue; } if debug { - eprintln!("[cf_loop/joinir] Adding '{}' to params (external_ref)", ext_ref); + eprintln!( + "[cf_loop/joinir] Adding '{}' to params (external_ref)", + ext_ref + ); } params.push(serde_json::json!(ext_ref)); } @@ -370,7 +376,8 @@ impl super::MirBuilder { } // 3. Collect all ValueIds used in JoinIR function - let mut used_values: std::collections::BTreeSet = std::collections::BTreeSet::new(); + let mut used_values: std::collections::BTreeSet = + std::collections::BTreeSet::new(); for block in join_func.blocks.values() { Self::collect_values_in_block(block, &mut used_values); } @@ -420,7 +427,9 @@ impl super::MirBuilder { ); } } - MirInstruction::Jump { target: exit_block_id } + MirInstruction::Jump { + target: exit_block_id, + } } _ => Self::remap_instruction(term, &value_map, &block_map), }; @@ -503,7 +512,9 @@ impl super::MirBuilder { values.insert(*value); values.insert(*ptr); } - MirInstruction::Call { dst, func, args, .. } => { + MirInstruction::Call { + dst, func, args, .. + } => { if let Some(d) = dst { values.insert(*d); } @@ -512,7 +523,9 @@ impl super::MirBuilder { values.insert(*arg); } } - MirInstruction::BoxCall { dst, box_val, args, .. } => { + MirInstruction::BoxCall { + dst, box_val, args, .. + } => { if let Some(d) = dst { values.insert(*d); } @@ -595,24 +608,39 @@ impl super::MirBuilder { value: remap_value(*value), ptr: remap_value(*ptr), }, - MirInstruction::Call { dst, func, callee, args, effects } => MirInstruction::Call { + MirInstruction::Call { + dst, + func, + callee, + args, + effects, + } => MirInstruction::Call { dst: dst.map(remap_value), func: remap_value(*func), callee: callee.clone(), args: args.iter().map(|a| remap_value(*a)).collect(), effects: *effects, }, - MirInstruction::BoxCall { dst, box_val, method, method_id, args, effects } => { - MirInstruction::BoxCall { - dst: dst.map(remap_value), - box_val: remap_value(*box_val), - method: method.clone(), - method_id: *method_id, - args: args.iter().map(|a| remap_value(*a)).collect(), - effects: *effects, - } - } - MirInstruction::Branch { condition, then_bb, else_bb } => MirInstruction::Branch { + MirInstruction::BoxCall { + dst, + box_val, + method, + method_id, + args, + effects, + } => MirInstruction::BoxCall { + dst: dst.map(remap_value), + box_val: remap_value(*box_val), + method: method.clone(), + method_id: *method_id, + args: args.iter().map(|a| remap_value(*a)).collect(), + effects: *effects, + }, + MirInstruction::Branch { + condition, + then_bb, + else_bb, + } => MirInstruction::Branch { condition: remap_value(*condition), then_bb: remap_block(*then_bb), else_bb: remap_block(*else_bb), @@ -634,7 +662,11 @@ impl super::MirBuilder { dst: remap_value(*dst), src: remap_value(*src), }, - MirInstruction::NewBox { dst, box_type, args } => MirInstruction::NewBox { + MirInstruction::NewBox { + dst, + box_type, + args, + } => MirInstruction::NewBox { dst: remap_value(*dst), box_type: box_type.clone(), args: args.iter().map(|a| remap_value(*a)).collect(), diff --git a/src/mir/builder/loop_frontend_binding.rs b/src/mir/builder/loop_frontend_binding.rs index 5319cd64..32812ded 100644 --- a/src/mir/builder/loop_frontend_binding.rs +++ b/src/mir/builder/loop_frontend_binding.rs @@ -44,6 +44,7 @@ pub struct LoopFrontendBinding { /// ループ上限の表現 #[derive(Debug, Clone)] +#[allow(dead_code)] pub enum BoundExpr { /// 変数名 (e.g., "n") Variable(String), diff --git a/src/mir/join_ir/frontend/ast_lowerer/expr.rs b/src/mir/join_ir/frontend/ast_lowerer/expr.rs index 0a22848d..4477744d 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/expr.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/expr.rs @@ -248,9 +248,7 @@ impl AstToJoinIrLowerer { // Phase 56: Unary 対応(not 等) "Unary" => { - let op = expr["op"] - .as_str() - .expect("Unary must have 'op' field"); + let op = expr["op"].as_str().expect("Unary must have 'op' field"); let operand_expr = &expr["operand"]; // operand を再帰的に extract_value diff --git a/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/mod.rs b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/mod.rs index 6fe88b2d..35b3fedd 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/mod.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/lowering/mod.rs @@ -1,7 +1,7 @@ //! Phase P1: If in Loop Lowering - 各パターンの lowering 実装 -pub mod empty; -pub mod single_var_then; -pub mod single_var_both; pub mod conditional_effect; +pub mod empty; +pub mod single_var_both; +pub mod single_var_then; pub mod unsupported; diff --git a/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/mod.rs b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/mod.rs index 8da83632..41dc5c02 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/mod.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/mod.rs @@ -3,8 +3,8 @@ //! ループ内の If ステートメントを JoinIR に変換する。 //! 5 つのパターンに分類し、それぞれに適した lowering 戦略を適用する。 -pub mod pattern; pub mod lowering; +pub mod pattern; use super::{AstToJoinIrLowerer, ExtractCtx, JoinInst, StatementEffect}; use pattern::IfInLoopPattern; @@ -14,7 +14,7 @@ impl AstToJoinIrLowerer { /// /// 元の lower_if_stmt_in_loop() を箱化モジュール化したエントリーポイント。 /// パターン検出 → 適切な lowering 関数に委譲する。 - pub fn lower_if_stmt_in_loop_boxified( + pub(crate) fn lower_if_stmt_in_loop_boxified( &mut self, stmt: &serde_json::Value, ctx: &mut ExtractCtx, @@ -24,7 +24,7 @@ impl AstToJoinIrLowerer { let else_body = stmt["else"].as_array(); // 条件を評価 - let (cond_id, mut insts) = self.extract_value(cond_expr, ctx); + let (cond_id, insts) = self.extract_value(cond_expr, ctx); // then/else のステートメント配列を取得 let then_stmts = then_body.map(|v| v.as_slice()).unwrap_or(&[]); @@ -35,30 +35,13 @@ impl AstToJoinIrLowerer { // パターンごとに lowering match pattern { - IfInLoopPattern::Empty => { - lowering::empty::lower(insts) - } + IfInLoopPattern::Empty => lowering::empty::lower(insts), IfInLoopPattern::SingleVarThen { var_name } => { - lowering::single_var_then::lower( - self, - ctx, - insts, - cond_id, - &var_name, - then_stmts, - ) - } - IfInLoopPattern::SingleVarBoth { var_name } => { - lowering::single_var_both::lower( - self, - ctx, - insts, - cond_id, - &var_name, - then_stmts, - else_stmts, - ) + lowering::single_var_then::lower(self, ctx, insts, cond_id, &var_name, then_stmts) } + IfInLoopPattern::SingleVarBoth { var_name } => lowering::single_var_both::lower( + self, ctx, insts, cond_id, &var_name, then_stmts, else_stmts, + ), IfInLoopPattern::ConditionalEffect { receiver_name, method_name, diff --git a/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/pattern.rs b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/pattern.rs index 1b706560..862a1c72 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/pattern.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/if_in_loop/pattern.rs @@ -14,15 +14,11 @@ pub enum IfInLoopPattern { /// ケース 2: then のみ単一変数更新、else は空 /// `if cond { x = expr }` → `x = cond ? expr : x` - SingleVarThen { - var_name: String, - }, + SingleVarThen { var_name: String }, /// ケース 3: then/else 両方が同じ変数への単一更新 /// `if cond { x = a } else { x = b }` → `x = cond ? a : b` - SingleVarBoth { - var_name: String, - }, + SingleVarBoth { var_name: String }, /// ケース 4: 条件付き側効果(filter パターン) /// `if pred(v) { acc.push(v) }` → ConditionalMethodCall @@ -83,10 +79,7 @@ impl IfInLoopPattern { let method_name = stmt["method"].as_str(); if let (Some(receiver_expr), Some(method_name)) = (receiver_expr, method_name) { - let receiver_name = receiver_expr["name"] - .as_str() - .unwrap_or("acc") - .to_string(); + let receiver_name = receiver_expr["name"].as_str().unwrap_or("acc").to_string(); return Self::ConditionalEffect { receiver_name, diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_frontend_binding.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_frontend_binding.rs index 4f92f6e3..9931b5f7 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/loop_frontend_binding.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_frontend_binding.rs @@ -24,7 +24,6 @@ //! ``` use super::loop_patterns::{self, LoopPattern, LoweringError}; -use super::loop_patterns_old; use super::{AstToJoinIrLowerer, JoinModule}; /// 関数名から LoopPattern を検出 diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/break_pattern.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/break_pattern.rs index 0f241b6c..78c6c8a6 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/break_pattern.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/break_pattern.rs @@ -21,8 +21,8 @@ //! - k_exit 関数: Return(acc) use super::common::{ - build_join_module, build_step_params, create_k_exit_function, create_loop_context, - parse_program_json, process_local_inits, + build_join_module, create_k_exit_function, create_loop_context, parse_program_json, + process_local_inits, }; use super::{AstToJoinIrLowerer, JoinModule, LoweringError}; use crate::mir::join_ir::{JoinFunction, JoinInst}; @@ -72,8 +72,13 @@ pub fn lower( let entry_func = create_entry_function_break(&ctx, &parsed, init_insts, &mut entry_ctx); // 6. loop_step 関数を生成 - let loop_step_func = - create_loop_step_function_break(lowerer, &ctx, &parsed.func_name, break_cond_expr, loop_body)?; + let loop_step_func = create_loop_step_function_break( + lowerer, + &ctx, + &parsed.func_name, + break_cond_expr, + loop_body, + )?; // 7. k_exit 関数を生成 let k_exit_func = create_k_exit_function(&ctx, &parsed.func_name); diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/common.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/common.rs index c48f3911..3a45b066 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/common.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/common.rs @@ -13,7 +13,7 @@ //! - `create_k_exit_function()`: k_exit 関数生成 use super::{AstToJoinIrLowerer, JoinModule}; -use crate::mir::join_ir::{ConstValue, JoinFuncId, JoinFunction, JoinInst}; +use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinInst}; use crate::mir::ValueId; use std::collections::BTreeMap; diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/continue_pattern.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/continue_pattern.rs index c2c17858..a4a194bc 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/continue_pattern.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/continue_pattern.rs @@ -178,9 +178,7 @@ fn create_loop_step_function_continue( // 2. Continue pattern 特有: i のインクリメントが先 let first_local = loop_body .iter() - .find(|stmt| { - stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("i") - }) + .find(|stmt| stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("i")) .ok_or_else(|| LoweringError::InvalidLoopBody { message: "Continue pattern must have i increment as first Local".to_string(), })?; @@ -198,9 +196,7 @@ fn create_loop_step_function_continue( // 4. acc の更新値を計算 let acc_update_local = loop_body .iter() - .find(|stmt| { - stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("acc") - }) + .find(|stmt| stmt["type"].as_str() == Some("Local") && stmt["name"].as_str() == Some("acc")) .ok_or_else(|| LoweringError::InvalidLoopBody { message: "Continue pattern must have acc update Local".to_string(), })?; diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/mod.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/mod.rs index 9e2d4d5d..73ecddcd 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/mod.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/mod.rs @@ -58,6 +58,7 @@ pub enum LoopPattern { /// ループパターン lowering エラー #[derive(Debug, Clone)] +#[allow(dead_code)] pub enum LoweringError { /// 未実装のパターン UnimplementedPattern { @@ -73,6 +74,7 @@ pub enum LoweringError { /// LoopPattern lowering の統一インターフェース /// /// 各パターンの lowering モジュールはこの trait を実装する。 +#[allow(dead_code)] pub trait LoopPatternLowerer { /// LoopPattern を JoinModule に変換 /// diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/simple.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/simple.rs index 6e520c6b..4f47ab64 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/simple.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns/simple.rs @@ -27,9 +27,9 @@ use super::common::{ create_k_exit_function, create_loop_context, create_step_ctx, parse_program_json, process_local_inits, }; -use super::{AstToJoinIrLowerer, JoinModule, LoweringError, LoopPattern}; -use crate::mir::join_ir::{ConstValue, JoinFunction, JoinInst, MirLikeInst}; +use super::{AstToJoinIrLowerer, JoinModule, LoweringError}; use crate::mir::join_ir::CompareOp; +use crate::mir::join_ir::{ConstValue, JoinFunction, JoinInst, MirLikeInst}; /// Simple パターンを JoinModule に変換 /// @@ -58,11 +58,12 @@ pub fn lower( // 5. Loop ノードを取得 let loop_node = &parsed.stmts[parsed.loop_node_idx]; let loop_cond_expr = &loop_node["cond"]; - let loop_body_stmts = loop_node["body"] - .as_array() - .ok_or_else(|| LoweringError::InvalidLoopBody { - message: "Loop must have 'body' array".to_string(), - })?; + let loop_body_stmts = + loop_node["body"] + .as_array() + .ok_or_else(|| LoweringError::InvalidLoopBody { + message: "Loop must have 'body' array".to_string(), + })?; // 6. loop_step 関数を生成 let loop_step_func = create_loop_step_function( diff --git a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns_old.rs b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns_old.rs index c2345a4c..af9d405d 100644 --- a/src/mir/join_ir/frontend/ast_lowerer/loop_patterns_old.rs +++ b/src/mir/join_ir/frontend/ast_lowerer/loop_patterns_old.rs @@ -280,9 +280,7 @@ impl AstToJoinIrLowerer { // loop_step 関数: (i, acc, n) → Jump(k_exit, cond=!(i, } diff --git a/src/mir/join_ir/lowering/if_phi_spec.rs b/src/mir/join_ir/lowering/if_phi_spec.rs index e24534ee..c4747106 100644 --- a/src/mir/join_ir/lowering/if_phi_spec.rs +++ b/src/mir/join_ir/lowering/if_phi_spec.rs @@ -2,8 +2,8 @@ //! //! JoinInst(Select/IfMerge)から、どの変数がPHIを必要とするかを計算する。 -use crate::mir::join_ir::JoinInst; use crate::mir::join_ir::lowering::if_phi_context::IfPhiContext; +use crate::mir::join_ir::JoinInst; use crate::mir::ValueId; use std::collections::{BTreeMap, BTreeSet}; @@ -55,7 +55,7 @@ pub fn compute_phi_spec_from_joinir(ctx: &IfPhiContext, join_inst: &JoinInst) -> let mut spec = PhiSpec::new(); match join_inst { - JoinInst::Select { dst, .. } => { + JoinInst::Select { .. } => { // Select命令: 単一変数のPHI // carrier_namesに含まれる変数をheader PHIとして扱う // TODO Phase 61-3: dstからvariable_nameを逆引き(MIR Builderのvariable_map参照) @@ -124,27 +124,15 @@ pub fn compare_and_log_phi_specs(joinir_spec: &PhiSpec, builder_spec: &PhiSpec) if matches { eprintln!("[Phase 61-2] ✅ PHI spec matches!"); - eprintln!( - "[Phase 61-2] Header PHIs: {}", - joinir_spec.header_count() - ); - eprintln!( - "[Phase 61-2] Exit PHIs: {}", - joinir_spec.exit_count() - ); + eprintln!("[Phase 61-2] Header PHIs: {}", joinir_spec.header_count()); + eprintln!("[Phase 61-2] Exit PHIs: {}", joinir_spec.exit_count()); } else { eprintln!("[Phase 61-2] ❌ PHI spec mismatch detected!"); eprintln!("[Phase 61-2] JoinIR spec:"); - eprintln!( - "[Phase 61-2] Header: {:?}", - joinir_spec.header_phis - ); + eprintln!("[Phase 61-2] Header: {:?}", joinir_spec.header_phis); eprintln!("[Phase 61-2] Exit: {:?}", joinir_spec.exit_phis); eprintln!("[Phase 61-2] PhiBuilderBox spec:"); - eprintln!( - "[Phase 61-2] Header: {:?}", - builder_spec.header_phis - ); + eprintln!("[Phase 61-2] Header: {:?}", builder_spec.header_phis); eprintln!("[Phase 61-2] Exit: {:?}", builder_spec.exit_phis); } diff --git a/src/mir/join_ir/lowering/if_select.rs b/src/mir/join_ir/lowering/if_select.rs index 1006c9c4..b5aef67b 100644 --- a/src/mir/join_ir/lowering/if_select.rs +++ b/src/mir/join_ir/lowering/if_select.rs @@ -19,6 +19,7 @@ use super::if_phi_context::IfPhiContext; pub struct IfSelectLowerer { debug_level: u8, // Phase 61-1: If-in-loop context (None = Pure If) + #[allow(dead_code)] context: Option, } diff --git a/src/mir/join_ir/lowering/loop_scope_shape.rs b/src/mir/join_ir/lowering/loop_scope_shape.rs index daba01ac..2acbe931 100644 --- a/src/mir/join_ir/lowering/loop_scope_shape.rs +++ b/src/mir/join_ir/lowering/loop_scope_shape.rs @@ -285,7 +285,14 @@ impl LoopScopeShape { let exit_live_box = LoopExitLivenessBox::new(); // 既存の from_existing_boxes を呼び出し - Self::from_existing_boxes(loop_form, intake, &var_classes, &exit_live_box, query, func_name) + Self::from_existing_boxes( + loop_form, + intake, + &var_classes, + &exit_live_box, + query, + func_name, + ) } /// Check if a variable needs header PHI diff --git a/src/mir/join_ir_runner.rs b/src/mir/join_ir_runner.rs index 34d1f2a6..b5fcc4f2 100644 --- a/src/mir/join_ir_runner.rs +++ b/src/mir/join_ir_runner.rs @@ -335,25 +335,25 @@ fn eval_compute( MirLikeInst::UnaryOp { dst, op, operand } => { let operand_val = read_var(locals, *operand)?; let result = match op { - crate::mir::join_ir::UnaryOp::Not => { - match operand_val { - JoinValue::Bool(b) => JoinValue::Bool(!b), - JoinValue::Int(i) => JoinValue::Bool(i == 0), - _ => return Err(JoinRuntimeError::new(format!( + crate::mir::join_ir::UnaryOp::Not => match operand_val { + JoinValue::Bool(b) => JoinValue::Bool(!b), + JoinValue::Int(i) => JoinValue::Bool(i == 0), + _ => { + return Err(JoinRuntimeError::new(format!( "Cannot apply 'not' to {:?}", operand_val - ))), + ))) } - } - crate::mir::join_ir::UnaryOp::Neg => { - match operand_val { - JoinValue::Int(i) => JoinValue::Int(-i), - _ => return Err(JoinRuntimeError::new(format!( + }, + crate::mir::join_ir::UnaryOp::Neg => match operand_val { + JoinValue::Int(i) => JoinValue::Int(-i), + _ => { + return Err(JoinRuntimeError::new(format!( "Cannot apply '-' to {:?}", operand_val - ))), + ))) } - } + }, }; locals.insert(*dst, result); } diff --git a/src/mir/loop_builder.rs b/src/mir/loop_builder.rs deleted file mode 100644 index 76e2149e..00000000 --- a/src/mir/loop_builder.rs +++ /dev/null @@ -1,1515 +0,0 @@ -/*! - * MIR Loop Builder - SSA形式でのループ構築専用モジュール - * - * Sealed/Unsealed blockとPhi nodeを使った正しいループ実装。 - * - * LoopForm v2 の「形」をここで固定している: - * - preheader: ループに入る直前のブロック(初期値の copy 発生源) - * - header : ループ条件を評価するブロック(`loop(cond)` の `cond` 部分) - * - body : ループ本体(ユーザーコードが書いたブロック) - * - latch : body の末尾から header へ戻る backedge 用ブロック - * - exit : ループ脱出先(`break` / `cond == false` が合流するブロック) - * - * 典型パターン(ControlForm::LoopShape): - * - Case A: header-cond + header→exit + body→exit(`loop(i < n) { if (...) break }`) - * - Case B: constant-true + body→exit のみ(`loop(1 == 1) { if (...) break }`) - * - この場合、header→exit のエッジは存在しないので、exit PHI に header 値を入れてはいけない。 - * - Case C: continue_merge を経由して header に戻る経路あり(`continue` を含むループ)。 - * - * それぞれのケースは ControlForm / LoopSnapshotMergeBox / ExitPhiBuilder に伝搬され、 - * exit PHI の入力選択や BodyLocalInternal 変数の扱いに反映される。 - */ - -use super::{BasicBlockId, ConstValue, MirInstruction, ValueId}; -use crate::ast::ASTNode; -use crate::mir::control_form::{is_control_form_trace_on, ControlForm, IfShape, LoopShape}; -use crate::mir::phi_core::loopform_builder::{LoopFormBuilder, LoopFormOps}; -// Phase 59b: PhiInputCollector は loop_builder.rs 内にインライン化済み -// use crate::mir::phi_core::phi_input_collector::PhiInputCollector; -use std::collections::{BTreeMap, BTreeSet}; // Phase 25.1: 決定性確保 - -// Phase 15 段階的根治戦略:制御フローユーティリティ -use super::utils::{capture_actual_predecessor_and_jump, is_current_block_terminated}; - -/// ループ脱出の種類(箱化・共通化のための型) -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum LoopExitKind { - /// break文(exit blockへジャンプ) - Break, - /// continue文(header blockへジャンプ) - Continue, -} - -/// ループビルダー - SSA形式でのループ構築を管理 -pub struct LoopBuilder<'a> { - /// 親のMIRビルダーへの参照 - parent_builder: &'a mut super::builder::MirBuilder, - - /// ブロックごとの変数マップ(スコープ管理) - /// Phase 25.1: BTreeMap → BTreeMap(決定性確保) - #[allow(dead_code)] - block_var_maps: BTreeMap>, - - /// ループヘッダーID(continue 先の既定値として使用) - loop_header: Option, - - /// continue 文がジャンプするターゲットブロック - /// - 既定: header と同一 - /// - 将来: canonical continue merge ブロックに差し替えるためのフック - continue_target: Option, - - /// continue文からの変数スナップショット - continue_snapshots: Vec<(BasicBlockId, BTreeMap)>, - - /// break文からの変数スナップショット(exit PHI生成用) - exit_snapshots: Vec<(BasicBlockId, BTreeMap)>, - // フェーズM: no_phi_modeフィールド削除(常にPHI使用) -} - -impl<'a> LoopBuilder<'a> { - /// Find the source value of a Copy instruction in a given block - /// If `dst` is defined by a Copy instruction `dst = copy src`, return Some(src) - /// Otherwise return None - - // ============================================================= - // Control Helpers — break/continue/jumps/unreachable handling - // ============================================================= - - /// Emit a jump to `target` from the current block and record predecessor metadata. - fn jump_with_pred(&mut self, target: BasicBlockId) -> Result<(), String> { - let cur_block = self.current_block()?; - self.emit_jump(target)?; - let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, target, cur_block); - Ok(()) - } - - /// [LoopForm] 【箱化】ループ脱出の共通処理(break/continue統一化) - /// - /// Phase 25.1o: break と continue の共通パターンを抽出し、 - /// LoopExitKind で振る舞いを切り替える統一メソッド。 - /// - /// # 処理フロー - /// 1. 現在の変数マップをスナップショット - /// 2. [LoopForm] スナップショット保存(Break → exit_snapshots, Continue → continue_snapshots) - /// 3. Void を定義(戻り値用のダミー) - /// 4. [LoopForm] ターゲットブロックへジャンプ(Break → exit, Continue → header/continue_merge) - /// 5. 現在ブロックはジャンプで終端済みのまま維持(新しい unreachable ブロックは作らない) - fn do_loop_exit(&mut self, kind: LoopExitKind) -> Result { - // 1. スナップショット取得(共通処理) - let snapshot = self.get_current_variable_map(); - let cur_block = self.current_block()?; - - // 2. [LoopForm] exit-break path: スナップショット保存(exit PHI入力用) - // [LoopForm] continue-backedge path: スナップショット保存(continue_merge → header) - match kind { - LoopExitKind::Break => { - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!( - "[DEBUG/do_break] Saved snapshot from block {:?}, vars: {:?}", - cur_block, - snapshot.keys().collect::>() - ); - } - self.exit_snapshots.push((cur_block, snapshot)); - } - LoopExitKind::Continue => { - self.block_var_maps.insert(cur_block, snapshot.clone()); - self.continue_snapshots.push((cur_block, snapshot)); - } - } - - // 3. 戻り値用のダミーを定義(現在ブロック内で完結させる) - let void_id = self.new_value(); - self.emit_const(void_id, ConstValue::Void)?; - - // 4. ターゲットブロックへジャンプ(kind別処理) - match kind { - LoopExitKind::Break => { - if let Some(exit_bb) = crate::mir::builder::loops::current_exit(self.parent_builder) - { - self.jump_with_pred(exit_bb)?; - } - } - LoopExitKind::Continue => { - // 既定では header にジャンプするが、canonical continue merge を導入した場合は - // continue_target 側を優先する。 - if let Some(target) = self.continue_target.or(self.loop_header) { - self.jump_with_pred(target)?; - } - } - } - - // 5. 現在ブロックは jump で終端済み。新しい unreachable ブロックは作らない。 - Ok(void_id) - } - - /// Handle a `break` statement: jump to loop exit and continue in a fresh unreachable block. - /// 【箱化】do_loop_exit() への thin wrapper - fn do_break(&mut self) -> Result { - self.do_loop_exit(LoopExitKind::Break) - } - - /// Handle a `continue` statement: snapshot vars, jump to loop header, then continue in a fresh unreachable block. - /// 【箱化】do_loop_exit() への thin wrapper - fn do_continue(&mut self) -> Result { - self.do_loop_exit(LoopExitKind::Continue) - } - - // ============================================================= - // Lifecycle — create builder, main loop construction - // ============================================================= - /// 新しいループビルダーを作成 - pub fn new(parent: &'a mut super::builder::MirBuilder) -> Self { - Self { - parent_builder: parent, - block_var_maps: BTreeMap::new(), - loop_header: None, - continue_target: None, - continue_snapshots: Vec::new(), - exit_snapshots: Vec::new(), // exit PHI用のスナップショット - } - } - - /// SSA形式でループを構築 (LoopForm v2 only) - pub fn build_loop( - &mut self, - condition: ASTNode, - body: Vec, - ) -> Result { - // Phase 7-F: Legacy loop builder removed - LoopForm v2 is now the only implementation - self.build_loop_with_loopform(condition, body) - } - - /// SSA形式でループを構築 (LoopFormBuilder implementation) - fn build_loop_with_loopform( - &mut self, - condition: ASTNode, - body: Vec, - ) -> Result { - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!("[build_loop_with_loopform] === ENTRY ==="); - if let Some(ref func) = self.parent_builder.current_function { - eprintln!( - "[build_loop_with_loopform] fn='{}', counter={}, func_ptr={:p}", - func.signature.name, func.next_value_id, func as *const _ - ); - } - eprintln!("[build_loop_with_loopform] condition={:?}", condition); - eprintln!("[build_loop_with_loopform] body.len()={}", body.len()); - } - // Create loop structure blocks following LLVM canonical form - // We need a dedicated preheader block to materialize loop entry copies - let before_loop_id = self.current_block()?; - - // Capture variable snapshot BEFORE creating new blocks (at loop entry point) - let current_vars = self.get_current_variable_map(); - - // DEBUG: Show variable map before guard check - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!( - "[loopform] before_loop_id={:?}, variable_map size={}", - before_loop_id, - current_vars.len() - ); - for (name, value) in ¤t_vars { - eprintln!(" {} -> {:?}", name, value); - } - } - - // Phase 25.3: GUARD check removed - ValueId(0) is valid for first parameters - // Previous code incorrectly assumed ValueId(0) always meant uninitialized variables, - // but it's actually the correct ID for the first parameter in functions like: - // skip_whitespace(s, idx) -> s=ValueId(0), idx=ValueId(1) - // This caused loops in such functions to be entirely skipped. - - let preheader_id = self.new_block(); - let header_id = self.new_block(); - let body_id = self.new_block(); - let latch_id = self.new_block(); - let exit_id = self.new_block(); - // Phase 25.1q: canonical continue merge block - // All continue 文は一度このブロックに集約してから header へ戻る。 - let continue_merge_id = self.new_block(); - - // Jump from current block to preheader - let entry_block = self.current_block()?; - self.emit_jump(preheader_id)?; - // 📦 Hotfix 6: Add CFG predecessor for preheader (same as legacy version) - crate::mir::builder::loops::add_predecessor( - self.parent_builder, - preheader_id, - entry_block, - )?; - - // Initialize LoopFormBuilder with preheader and header blocks - let mut loopform = LoopFormBuilder::new(preheader_id, header_id); - - // Pass 1: Prepare structure (allocate all ValueIds upfront) - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!("[loopform] Block IDs: preheader={:?}, header={:?}, body={:?}, latch={:?}, exit={:?}", - preheader_id, header_id, body_id, latch_id, exit_id); - eprintln!( - "[loopform] variable_map at loop entry (size={}):", - current_vars.len() - ); - let mut loop_count = 0; - for (name, value) in ¤t_vars { - loop_count += 1; - eprintln!(" [{}] {} -> {:?}", loop_count, name, value); - // Phase 26-A-4: ValueIdベース判定に変更(名前ベース → 型安全) - let is_param = self.is_parameter(*value); - eprintln!(" param={}", is_param); - } - eprintln!("[loopform] iterated {} times", loop_count); - if let Some(ref func) = self.parent_builder.current_function { - eprintln!( - "[loopform] BEFORE prepare_structure: fn='{}', counter={}, func_ptr={:p}", - func.signature.name, func.next_value_id, func as *const _ - ); - } else { - eprintln!("[loopform] BEFORE prepare_structure: current_function=None"); - } - } - loopform.prepare_structure(self, ¤t_vars)?; - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - if let Some(ref func) = self.parent_builder.current_function { - eprintln!( - "[loopform] AFTER prepare_structure: fn='{}', counter={}, func_ptr={:p}", - func.signature.name, func.next_value_id, func as *const _ - ); - } else { - eprintln!("[loopform] AFTER prepare_structure: current_function=None"); - } - } - - // Pass 2: Emit preheader (copies and jump to header) - loopform.emit_preheader(self)?; - // 📦 Hotfix 6: Add CFG predecessor for header from preheader (same as legacy version) - crate::mir::builder::loops::add_predecessor(self.parent_builder, header_id, preheader_id)?; - - // Pass 3: Emit header PHIs (incomplete, only preheader edge) - self.set_current_block(header_id)?; - - // Ensure header block exists before emitting PHIs - self.parent_builder.ensure_block_exists(header_id)?; - - // Phase 27.4-C Refactor: JoinIR Loop φ バイパスフラグ統一取得 - let fn_name = self - .parent_builder - .current_function - .as_ref() - .map(|f| f.signature.name.clone()) - .unwrap_or_default(); - - let bypass_flags = crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name); - - if bypass_flags.header { - // Phase 27.4-C: JoinIR 実験経路では Header φ を生成しない。 - // Pinned/Carrier の値は preheader の copy をそのまま使う。 - // - // ⚠️ 重要: このモードでは MIR は不完全(φ 抜け)であり、VM で実行できない。 - // JoinIR runner 専用モードであることに注意。 - if crate::mir::phi_core::loopform_builder::is_loopform_debug_enabled() { - eprintln!("[loopform/27.4-C] Header φ bypass active for: {}", fn_name); - eprintln!("[loopform/27.4-C] Skipping emit_header_phis() - using preheader values directly"); - } - } else { - // 従来どおり HeaderPhiBuilder を使って φ を準備 - loopform.emit_header_phis(self)?; - } - - if crate::mir::phi_core::loopform_builder::is_loopform_debug_enabled() { - eprintln!("[loopform] variable_map after emit_header_phis:"); - for (name, value) in self.get_current_variable_map().iter() { - eprintln!(" {} -> {:?}", name, value); - } - } - - // Set up loop context for break/continue - crate::mir::builder::loops::push_loop_context(self.parent_builder, header_id, exit_id); - self.loop_header = Some(header_id); - // 既定の continue 先を canonical continue_merge ブロックにする。 - // ここを差し替えることで do_loop_exit(Continue) のターゲットを一元化する。 - self.continue_target = Some(continue_merge_id); - self.continue_snapshots.clear(); - self.exit_snapshots.clear(); - - // [LoopForm] header-cond: cond true → body, false → exit (Case A/B) - // - Case A: loop(i < n) → header can branch to exit directly - // - Case B: loop(1 == 1) → header always enters body, exit only via break - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!( - "[loopform/condition] BEFORE build_expression: current_block={:?}", - self.current_block()? - ); - if let Some(ref func) = self.parent_builder.current_function { - eprintln!( - "[loopform/condition] BEFORE: fn='{}', counter={}, func_ptr={:p}", - func.signature.name, func.next_value_id, func as *const _ - ); - } - } - let cond_value = self.parent_builder.build_expression(condition)?; - // Capture the ACTUAL block that emits the branch (might differ from header_id - // if build_expression created new blocks) - let branch_source_block = self.current_block()?; - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!( - "[loopform/condition] AFTER build_expression: branch_source_block={:?}", - branch_source_block - ); - if let Some(ref func) = self.parent_builder.current_function { - eprintln!( - "[loopform/condition] AFTER: fn='{}', counter={}, func_ptr={:p}", - func.signature.name, func.next_value_id, func as *const _ - ); - } - } - self.emit_branch(cond_value, body_id, exit_id)?; - // 📦 Hotfix 6: Add CFG predecessors for branch targets (Cytron et al. 1991 requirement) - // This ensures exit_block.predecessors is populated before Exit PHI generation - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!( - "[loopform/condition] BEFORE add_predecessor: exit_id={:?}, branch_source={:?}", - exit_id, branch_source_block - ); - } - crate::mir::builder::loops::add_predecessor( - self.parent_builder, - body_id, - branch_source_block, - )?; - crate::mir::builder::loops::add_predecessor( - self.parent_builder, - exit_id, - branch_source_block, - )?; - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!( - "[loopform/condition] AFTER emit_branch: current_block={:?}", - self.current_block()? - ); - eprintln!( - "[loopform/condition] Added predecessors: body={:?} exit={:?} from={:?}", - body_id, exit_id, branch_source_block - ); - // Verify predecessors were added - if let Some(ref func) = self.parent_builder.current_function { - if let Some(exit_block) = func.blocks.get(&exit_id) { - eprintln!( - "[loopform/condition] exit_block.predecessors = {:?}", - exit_block.predecessors - ); - } - } - } - - // Lower loop body - self.set_current_block(body_id)?; - for stmt in body { - self.build_statement(stmt)?; - if is_current_block_terminated(self.parent_builder)? { - break; - } - } - - // Capture variable snapshot at end of body (before jumping to latch) - let body_end_vars = self.get_current_variable_map(); - - // Step 5-1: Writes集合収集(選択肢2+3統合: Snapshot比較で再代入検出) - // current_vars (preheader) と body_end_vars を比較し、ValueId が変わった変数を特定 - use std::collections::HashSet; - let mut writes = HashSet::new(); - for (name, &body_value) in &body_end_vars { - // Skip __pin$ temporary variables - they are always BodyLocalInternal - // (Task先生の発見: これらをcarrier扱いすると未定義ValueIdエラーの原因になる) - if name.starts_with("__pin$") && name.contains("$@") { - continue; - } - - if let Some(&base_value) = current_vars.get(name) { - if body_value != base_value { - writes.insert(name.clone()); - } - } - // else: body で新規定義された変数(body-local)、header PHI 不要 - } - - // DEBUG: Log writes collection - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!("[loopform/writes] === WRITES COLLECTION (Step 5-1) ==="); - eprintln!( - "[loopform/writes] {} variables modified in loop body", - writes.len() - ); - let mut sorted_writes: Vec<_> = writes.iter().collect(); - sorted_writes.sort(); - for name in &sorted_writes { - eprintln!("[loopform/writes] WRITE: {}", name); - } - } - - // Jump to latch if not already terminated - let actual_latch_id = if !is_current_block_terminated(self.parent_builder)? { - self.emit_jump(latch_id)?; - latch_id - } else { - // Body is terminated (break/continue), use current block as latch - self.current_block()? - }; - - // Latch: jump back to header - self.set_current_block(latch_id)?; - - // Update variable map with body end values for sealing - for (name, value) in &body_end_vars { - self.update_variable(name.clone(), *value); - } - - self.emit_jump(header_id)?; - // 📦 Hotfix 6: Add CFG predecessor for header from latch (same as legacy version) - crate::mir::builder::loops::add_predecessor(self.parent_builder, header_id, latch_id)?; - - // Phase 25.1c/k: body-local 変数の PHI 生成 - // BreakFinderBox / FuncScannerBox 等で、loop body 内で新規宣言された local 変数が - // loop header に戻った時に undefined になる問題を修正 - // - // Step 5-5-B: EXPERIMENTAL - Body-local Header PHI generation DISABLED - // Reason: Option C design states body-local variables should NOT have header PHIs - // - BodyLocalExit: needs EXIT PHI only, NOT header PHI - // - BodyLocalInternal: needs NO PHI at all - // - // TODO Step 5-3: Integrate Option C classification (LoopVarClassBox) here - // - // TEMPORARY DISABLE to test hypothesis that header PHIs are the root cause - let trace_loop_phi = std::env::var("HAKO_LOOP_PHI_TRACE").ok().as_deref() == Some("1"); - - // DISABLED: Body-local header PHI generation - // This code was causing undefined value errors because it created header PHIs - // for variables that should only have exit PHIs (or no PHIs at all) - if false { // Disabled for Step 5-5-B experiment - // [Original code removed - see git history if needed] - } - - // Pass 4: Generate continue_merge PHIs first, then seal header PHIs - // Phase 25.1c/k: canonical continue_merge ブロックで PHI を生成してから seal_phis を呼ぶ - let raw_continue_snaps = self.continue_snapshots.clone(); - - // Step 1: continue_merge ブロックで PHI 生成(merged_snapshot を作る) - // Phase 25.2: LoopSnapshotMergeBox を使って整理 - self.set_current_block(continue_merge_id)?; - - let merged_snapshot: BTreeMap = if !raw_continue_snaps.is_empty() { - if trace_loop_phi { - eprintln!( - "[loop-phi/continue-merge] Generating PHI nodes for {} continue paths", - raw_continue_snaps.len() - ); - } - - // すべての continue snapshot に現れる変数を収集 - let mut all_vars: BTreeMap> = BTreeMap::new(); - for (continue_bb, snapshot) in &raw_continue_snaps { - for (var_name, &value) in snapshot { - all_vars - .entry(var_name.clone()) - .or_default() - .push((*continue_bb, value)); - } - } - - // 各変数について PHI ノードを生成 - // ======================================== - // Phase 59b: PhiInputCollector インライン化 - // ======================================== - let mut merged = BTreeMap::new(); - for (var_name, inputs) in all_vars { - // Step 1: sanitize (BTreeMap で重複削除&ソート) - let mut sanitized: BTreeMap = BTreeMap::new(); - for (bb, val) in &inputs { - sanitized.insert(*bb, *val); - } - let final_inputs: Vec<(BasicBlockId, ValueId)> = sanitized.into_iter().collect(); - - // Step 2: optimize_same_value - let same_value = if final_inputs.is_empty() { - None - } else if final_inputs.len() == 1 { - Some(final_inputs[0].1) - } else { - let first_val = final_inputs[0].1; - if final_inputs.iter().all(|(_, val)| *val == first_val) { - Some(first_val) - } else { - None - } - }; - - // Step 3: PHI 生成 or 同一値を使用 - let result_value = if let Some(same_val) = same_value { - // 全て同じ値 or 単一入力 → PHI 不要 - same_val - } else { - // 異なる値を持つ場合は PHI ノードを生成 - let phi_id = self.new_value(); - - if let Some(ref mut func) = self.parent_builder.current_function { - if let Some(merge_block) = func.blocks.get_mut(&continue_merge_id) { - merge_block.add_instruction(MirInstruction::Phi { - dst: phi_id, - inputs: final_inputs, - }); - } - } - - if trace_loop_phi { - eprintln!( - "[loop-phi/continue-merge] Generated PHI for '{}': {:?}", - var_name, phi_id - ); - } - phi_id - }; - - merged.insert(var_name, result_value); - } - - // Note: 変数マップへの反映は seal_phis に委譲(干渉を避ける) - - if trace_loop_phi { - eprintln!( - "[loop-phi/continue-merge] Merged {} variables from {} paths", - merged.len(), - raw_continue_snaps.len() - ); - } - merged - } else { - BTreeMap::new() - }; - - self.emit_jump(header_id)?; - crate::mir::builder::loops::add_predecessor( - self.parent_builder, - header_id, - continue_merge_id, - )?; - - // Step 2: merged_snapshot を使って seal_phis を呼ぶ - // Phase 25.3: Continue merge PHI実装(Task先生の発見!) - // - continueが無いループでも、Latchブロックの値をHeader PHIに伝播する必要がある - // - これにより、Exit PHIがHeader PHI経由で正しい値を受け取れる - let continue_snaps: Vec<(BasicBlockId, BTreeMap)> = { - // まず、merged_snapshot(continue merge PHI結果)を追加 - let mut snaps = if !merged_snapshot.is_empty() { - vec![(continue_merge_id, merged_snapshot.clone())] - } else { - vec![] - }; - - // continueが無い場合でも、Latchブロックのスナップショットを追加 - // これにより、seal_phis()がLatchからの値をHeader PHIに正しく接続できる - if raw_continue_snaps.is_empty() { - // continue文が無い場合、Latchブロックの現在の変数マップをキャプチャ - // Note: このタイミングでは current_block == exit_id だが、 - // variable_map はLatch実行後の状態を保持している - let latch_snapshot = self.get_current_variable_map(); - snaps.push((actual_latch_id, latch_snapshot)); - } - - snaps - }; - - // Phase 27.4C Refactor: Header φ バイパスフラグを統一取得(seal_phis に渡す) - // Note: fn_name は既に line 299-304 で取得済み、String として保持されている - let bypass_flags_for_seal = - crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name); - - // Step 5-1/5-2: Pass writes 集合 for PHI縮約 - // Phase 27.4C: header_bypass フラグも渡す - loopform.seal_phis( - self, - actual_latch_id, - &continue_snaps, - &writes, - bypass_flags_for_seal.header, - )?; - - // Step 3: seal body-local PHIs (complete the inputs) - // Step 5-5-A: REMOVED - PHIs now created complete with both inputs upfront - // Old sealing code was overwriting our preheader+latch inputs with latch-only, - // causing "phi pred mismatch" errors. - // - // Body-local PHIs are now created at line 408-456 with BOTH inputs: - // - preheader: poison value (variable doesn't exist yet) - // - latch: actual value from loop body - // - // No further sealing is needed! - - // Exit block - self.set_current_block(exit_id)?; - - // Phase 25.1h: ControlForm統合版に切り替え - // continue / break のターゲットブロックをユニーク化して収集 - // Phase 25.1: HashSet → BTreeSet(決定性確保) - let mut break_set: BTreeSet = BTreeSet::new(); - for (bb, _) in &self.exit_snapshots { - break_set.insert(*bb); - } - // LoopShape の continue_targets は「header への canonical backedge」を表す。 - // continue が一つ以上存在する場合は continue_merge_id を 1 つだけ登録する。 - let continue_targets: Vec = if self.continue_snapshots.is_empty() { - Vec::new() - } else { - vec![continue_merge_id] - }; - let break_targets: Vec = break_set.into_iter().collect(); - - let loop_shape = LoopShape { - preheader: preheader_id, - header: header_id, - body: body_id, - latch: latch_id, - exit: exit_id, - continue_targets, - break_targets, - }; - let form = ControlForm::from_loop(loop_shape.clone()); - - // Region/GC 観測レイヤ(Phase 25.1l): - // NYASH_REGION_TRACE=1 のときだけ、Stage‑B 周辺ループの - // Region 情報(entry/exit/slots)をログに出すよ。 - crate::mir::region::observer::observe_control_form(self.parent_builder, &form); - - // Phase 27.6-2: JoinIR Exit φ バイパスチェック - let fn_name = self - .parent_builder - .current_function - .as_ref() - .map(|f| f.signature.name.as_str()) - .unwrap_or(""); - - let exit_bypass = crate::mir::phi_core::loopform_builder::joinir_exit_bypass_enabled() - && crate::mir::phi_core::loopform_builder::is_joinir_exit_bypass_target(fn_name); - - if exit_bypass { - // Phase 27.6-2: JoinIR 実験経路では Exit φ を生成しない。 - // ループ内で定義された値だけで exit 後を構成する(JoinIR の k_exit 引数として表現)。 - // - // ⚠️ 重要: このモードでは MIR は不完全(Exit φ 抜け)であり、VM で実行できない。 - // JoinIR runner 専用モードであることに注意。 - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!( - "[loopform/exit-bypass] func={} exit={:?} header={:?} (JoinIR experiment only)", - fn_name, exit_id, header_id - ); - } - } else { - // [LoopForm] exit PHI for Case A/B (uses exit_snapshots + exit_preds) - // - Case A: header+break → exit PHI includes both paths - // - Case B: break-only → exit PHI excludes header (not a predecessor) - let exit_snaps = self.exit_snapshots.clone(); - crate::mir::phi_core::loopform_builder::build_exit_phis_for_control( - &loopform, - self, - &form, - &exit_snaps, - branch_source_block, - )?; - } - - // Pop loop context - crate::mir::builder::loops::pop_loop_context(self.parent_builder); - - // ControlForm 観測: 環境フラグ(未設定時は既定ON)のとき LoopShape をダンプ - if is_control_form_trace_on() { - form.debug_dump(); - #[cfg(debug_assertions)] - if let Some(ref func) = self.parent_builder.current_function { - loop_shape.debug_validate(func); - } - } - - // Return void value - let void_dst = self.new_value(); - self.emit_const(void_dst, ConstValue::Void)?; - Ok(void_dst) - } - - // ============================================================= - // PHI Helpers — prepare/finalize PHIs and block sealing - // ============================================================= - /// ループ変数の準備(事前検出または遅延生成) - /// - /// ポリシー: - /// - ループキャリア(ループ本体で再代入される変数)と pinned 変数のみを PHI 対象とする。 - /// - ループ不変のローカル(text_len / pattern_len など)は preheader 値をそのまま使い、 - /// 不要な PHI を張らないことで SSA 破綻(同一 ValueId の二重定義)を防ぐ。 - - /// ブロックをシールし、不完全なPhi nodeを完成させる - - /// Exitブロックで変数のPHIを生成(breakポイントでの値を統一) - - // --- ヘルパーメソッド(親ビルダーへの委譲) --- - - fn current_block(&self) -> Result { - self.parent_builder - .current_block - .ok_or_else(|| "No current block".to_string()) - } - - fn new_block(&mut self) -> BasicBlockId { - self.parent_builder.block_gen.next() - } - - fn new_value(&mut self) -> ValueId { - // Use function-local allocator via MirBuilder helper to keep - // ValueId ranges consistent within the current function. - self.parent_builder.next_value_id() - } - - fn set_current_block(&mut self, block_id: BasicBlockId) -> Result<(), String> { - self.parent_builder.start_new_block(block_id) - } - - fn emit_jump(&mut self, target: BasicBlockId) -> Result<(), String> { - self.parent_builder - .emit_instruction(MirInstruction::Jump { target }) - } - - fn emit_branch( - &mut self, - condition: ValueId, - then_bb: BasicBlockId, - else_bb: BasicBlockId, - ) -> Result<(), String> { - // LocalSSA: ensure condition is materialized in the current block - let condition_local = self.parent_builder.local_ssa_ensure(condition, 4); - self.parent_builder - .emit_instruction(MirInstruction::Branch { - condition: condition_local, - then_bb, - else_bb, - }) - } - - fn emit_const(&mut self, dst: ValueId, value: ConstValue) -> Result<(), String> { - self.parent_builder - .emit_instruction(MirInstruction::Const { dst, value }) - } - - /// ブロック先頭に PHI 命令を挿入(不変条件: PHI は常にブロック先頭) - fn emit_phi_at_block_start( - &mut self, - block_id: BasicBlockId, - dst: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - let dbg = std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1"); - if dbg { - eprintln!( - "[DEBUG] LoopBuilder::emit_phi_at_block_start: block={}, dst=%{}, inputs={:?}", - block_id, dst.0, inputs - ); - } - // Phi nodeをブロックの先頭に挿入 - if let Some(ref mut function) = self.parent_builder.current_function { - if let Some(block) = function.get_block_mut(block_id) { - if dbg { - eprintln!( - "[DEBUG] Block {} current instructions count: {}", - block_id, - block.instructions.len() - ); - } - // Phi命令は必ずブロックの先頭に配置。ただし同一dstの既存PHIがある場合は差し替える。 - let mut replaced = false; - let mut idx = 0; - let span = self.parent_builder.current_span; - while idx < block.instructions.len() { - match &mut block.instructions[idx] { - MirInstruction::Phi { - dst: d, - inputs: ins, - } if *d == dst => { - *ins = inputs.clone(); - if block.instruction_spans.len() <= idx { - // Backfill missing spans to preserve alignment after legacy inserts. - while block.instruction_spans.len() <= idx { - block.instruction_spans.push(crate::ast::Span::unknown()); - } - } - block.instruction_spans[idx] = span; - replaced = true; - break; - } - MirInstruction::Phi { .. } => { - idx += 1; - } - _ => break, - } - } - if !replaced { - let phi_inst = MirInstruction::Phi { - dst, - inputs: inputs.clone(), - }; - block.instructions.insert(0, phi_inst); - block.instruction_spans.insert(0, span); - } - if dbg { - eprintln!("[DEBUG] ✅ PHI instruction inserted at position 0"); - eprintln!( - "[DEBUG] Block {} after insert instructions count: {}", - block_id, - block.instructions.len() - ); - } - // Verify PHI is still there - if let Some(first_inst) = block.instructions.get(0) { - match first_inst { - MirInstruction::Phi { dst: phi_dst, .. } => { - if dbg { - eprintln!( - "[DEBUG] Verified: First instruction is PHI dst=%{}", - phi_dst.0 - ); - } - } - other => { - if dbg { - eprintln!( - "[DEBUG] ⚠️ WARNING: First instruction is NOT PHI! It's {:?}", - other - ); - } - } - } - } - Ok(()) - } else { - if dbg { - eprintln!("[DEBUG] ❌ Block {} not found!", block_id); - } - Err(format!("Block {} not found", block_id)) - } - } else { - if dbg { - eprintln!("[DEBUG] ❌ No current function!"); - } - Err("No current function".to_string()) - } - } - - // ============================================================= - // Variable Map Utilities — snapshots and rebinding - // ============================================================= - fn get_current_variable_map(&self) -> BTreeMap { - // Phase 25.1: BTreeMap化 - self.parent_builder.variable_map.clone() - } - - fn update_variable(&mut self, name: String, value: ValueId) { - if std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") { - eprintln!( - "[DEBUG] LoopBuilder::update_variable: name={}, value=%{}", - name, value.0 - ); - } - self.parent_builder.variable_map.insert(name, value); - } - - fn get_variable_at_block(&self, name: &str, block_id: BasicBlockId) -> Option { - // まずブロックごとのスナップショットを優先 - if let Some(map) = self.block_var_maps.get(&block_id) { - if let Some(v) = map.get(name) { - return Some(*v); - } - } - // フォールバック:現在の変数マップ(単純ケース用) - self.parent_builder.variable_map.get(name).copied() - } - - fn build_statement(&mut self, stmt: ASTNode) -> Result { - // Preserve the originating span for loop-local control instructions (break/continue/phi). - self.parent_builder.current_span = stmt.span(); - match stmt { - // Ensure nested bare blocks inside loops are lowered with loop-aware semantics - ASTNode::Program { statements, .. } => { - let mut last = None; - for s in statements.into_iter() { - last = Some(self.build_statement(s)?); - // フェーズS修正:統一終端検出ユーティリティ使用 - if is_current_block_terminated(self.parent_builder)? { - break; - } - } - Ok(last.unwrap_or_else(|| { - let void_id = self.new_value(); - // Emit a void const to keep SSA consistent when block is empty - let _ = self.emit_const(void_id, ConstValue::Void); - void_id - })) - } - ASTNode::If { - condition, - then_body, - else_body, - .. - } => self.lower_if_in_loop(*condition, then_body, else_body), - ASTNode::Break { .. } => self.do_break(), - ASTNode::Continue { .. } => self.do_continue(), - other => self.parent_builder.build_expression(other), - } - } - - /// Lower an if-statement inside a loop, preserving continue/break semantics and emitting PHIs per assigned variable. - fn lower_if_in_loop( - &mut self, - condition: ASTNode, - then_body: Vec, - else_body: Option>, - ) -> Result { - // Reserve a deterministic join id for debug region labeling (nested inside loop) - let join_id = self.parent_builder.debug_next_join_id(); - // Pre-pin heuristic was deprecated; leave operands untouched for clarity. - // Evaluate condition and create blocks - let cond_val = self.parent_builder.build_expression(condition)?; - let then_bb = self.new_block(); - let else_bb = self.new_block(); - let merge_bb = self.new_block(); - let pre_branch_bb = self.current_block()?; - self.emit_branch(cond_val, then_bb, else_bb)?; - - // Capture pre-if variable map (used for phi normalization) - let pre_if_var_map = self.get_current_variable_map(); - let trace_if = std::env::var("NYASH_IF_TRACE").ok().as_deref() == Some("1"); - // (legacy) kept for earlier merge style; now unified helpers compute deltas directly. - - // then branch - self.set_current_block(then_bb)?; - // Debug region: join then-branch (inside loop) - self.parent_builder - .debug_push_region(format!("join#{}", join_id) + "/then"); - // Materialize all variables at entry via single-pred Phi (correctness-first) - let names_then: Vec = self - .parent_builder - .variable_map - .keys() - .filter(|n| !n.starts_with("__pin$")) - .cloned() - .collect(); - for name in names_then { - if let Some(&pre_v) = pre_if_var_map.get(&name) { - let phi_val = self.new_value(); - self.emit_phi_at_block_start(then_bb, phi_val, vec![(pre_branch_bb, pre_v)])?; - let name_for_log = name.clone(); - self.update_variable(name, phi_val); - if trace_if { - eprintln!( - "[if-trace] then-entry phi var={} pre={:?} -> dst={:?}", - name_for_log, pre_v, phi_val - ); - } - } - } - for s in then_body.iter().cloned() { - let _ = self.build_statement(s)?; - // フェーズS修正:統一終端検出ユーティリティ使用 - if is_current_block_terminated(self.parent_builder)? { - break; - } - } - let then_var_map_end = self.get_current_variable_map(); - // フェーズS修正:最強モード指摘の「実到達predecessor捕捉」を統一 - let _then_pred_to_merge = - capture_actual_predecessor_and_jump(self.parent_builder, merge_bb)?; - // Pop then-branch debug region - self.parent_builder.debug_pop_region(); - - // else branch - self.set_current_block(else_bb)?; - // Debug region: join else-branch (inside loop) - self.parent_builder - .debug_push_region(format!("join#{}", join_id) + "/else"); - // Materialize all variables at entry via single-pred Phi (correctness-first) - let names2: Vec = self - .parent_builder - .variable_map - .keys() - .filter(|n| !n.starts_with("__pin$")) - .cloned() - .collect(); - for name in names2 { - if let Some(&pre_v) = pre_if_var_map.get(&name) { - let phi_val = self.new_value(); - self.emit_phi_at_block_start(else_bb, phi_val, vec![(pre_branch_bb, pre_v)])?; - let name_for_log = name.clone(); - self.update_variable(name, phi_val); - if trace_if { - eprintln!( - "[if-trace] else-entry phi var={} pre={:?} -> dst={:?}", - name_for_log, pre_v, phi_val - ); - } - } - } - let mut else_var_map_end_opt: Option> = None; - if let Some(es) = else_body.clone() { - for s in es.into_iter() { - let _ = self.build_statement(s)?; - // フェーズS修正:統一終端検出ユーティリティ使用 - if is_current_block_terminated(self.parent_builder)? { - break; - } - } - else_var_map_end_opt = Some(self.get_current_variable_map()); - } - // フェーズS修正:else branchでも統一実到達predecessor捕捉 - let _else_pred_to_merge = - capture_actual_predecessor_and_jump(self.parent_builder, merge_bb)?; - // Pop else-branch debug region - self.parent_builder.debug_pop_region(); - - // Continue at merge - self.set_current_block(merge_bb)?; - // Debug region: join merge (inside loop) - self.parent_builder - .debug_push_region(format!("join#{}", join_id) + "/join"); - - // Phase 25.1: HashSet → BTreeSet(決定性確保) - // Phase 40-4.1: JoinIR経路をデフォルト化(collect_assigned_vars削除) - let _vars: std::collections::BTreeSet = - crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir( - &then_body, - else_body.as_ref(), - ); - - // Phase 26-E: PhiBuilderBox 統合 - // Phase 57: PhiMergeOps impl 削除(デッドコード、2025-11-29) - // - PhiBuilderOps に統一され、PhiMergeOps は不要になった - struct Ops<'b, 'a>(&'b mut LoopBuilder<'a>); - - // Phase 26-E: PhiBuilderOps trait 実装(箱理論統一) - impl<'b, 'a> crate::mir::phi_core::phi_builder_box::PhiBuilderOps for Ops<'b, 'a> { - fn new_value(&mut self) -> ValueId { - self.0.new_value() - } - fn emit_phi( - &mut self, - block: BasicBlockId, - dst: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - self.0.emit_phi_at_block_start(block, dst, inputs) - } - fn update_var(&mut self, name: String, value: ValueId) { - self.0.parent_builder.variable_map.insert(name, value); - } - fn get_block_predecessors(&self, block: BasicBlockId) -> Vec { - if let Some(ref func) = self.0.parent_builder.current_function { - func.blocks - .get(&block) - .map(|bb| bb.predecessors.iter().copied().collect()) - .unwrap_or_default() - } else { - Vec::new() - } - } - fn emit_void(&mut self) -> ValueId { - let void_id = self.0.new_value(); - let _ = self.0.emit_const(void_id, ConstValue::Void); - void_id - } - - // Phase 3-A: Loop PHI生成用メソッド実装 - fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> { - self.0.parent_builder.current_block = Some(block); - Ok(()) - } - - fn block_exists(&self, block: BasicBlockId) -> bool { - if let Some(ref func) = self.0.parent_builder.current_function { - func.blocks.contains_key(&block) - } else { - false - } - } - } - - // Phase 25.1h: ControlForm統合版に切り替え - let if_shape = IfShape { - cond_block: pre_branch_bb, - then_block: then_bb, - else_block: Some(else_bb), - merge_block: merge_bb, - }; - let form = ControlForm::from_if(if_shape.clone()); - - // Region/GC 観測レイヤ(Phase 25.1l): - // NYASH_REGION_TRACE=1 のときだけ、Stage‑B 周辺 If 構造の - // Region 情報(entry/exit/slots)をログに出すよ。 - crate::mir::region::observer::observe_control_form(self.parent_builder, &form); - - // Phase 61-1: If-in-loop JoinIR化(開発フラグ制御) - // carrier_namesを作成(両経路で共通) - let carrier_names: std::collections::BTreeSet = pre_if_var_map - .keys() - .filter(|name| !name.starts_with("__pin$")) // 一時変数除外 - .cloned() - .collect(); - - // Phase 61-2: JoinIR dry-run検証モード - // dry-run用: JoinInstとPhiSpecを保存(A/B比較用) - let mut joinir_phi_spec_opt: Option< - crate::mir::join_ir::lowering::if_phi_spec::PhiSpec, - > = None; - - let joinir_success = if crate::config::env::joinir_if_select_enabled() { - // IfPhiContext作成 - let if_phi_context = - crate::mir::join_ir::lowering::if_phi_context::IfPhiContext::for_loop_body( - carrier_names.clone(), - ); - - // JoinIR経路を試行 - if let Some(ref func) = self.parent_builder.current_function { - match crate::mir::join_ir::lowering::try_lower_if_to_joinir( - func, - pre_branch_bb, - false, // debug - Some(&if_phi_context), - ) { - Some(join_inst) => { - eprintln!("[Phase 61-2] ✅ If-in-loop lowered via JoinIR: {:?}", join_inst); - - // Phase 61-2: dry-runモードでPHI仕様を検証 - if crate::config::env::joinir_if_in_loop_dryrun_enabled() { - eprintln!("[Phase 61-2] 🔍 dry-run mode enabled"); - eprintln!("[Phase 61-2] Carrier variables: {:?}", carrier_names); - eprintln!( - "[Phase 61-2] JoinInst type: {}", - match &join_inst { - crate::mir::join_ir::JoinInst::Select { .. } => "Select", - crate::mir::join_ir::JoinInst::IfMerge { .. } => "IfMerge", - _ => "Other" - } - ); - - // Phase 61-2.3: JoinInstからPhiSpecを計算 - let joinir_spec = crate::mir::join_ir::lowering::if_phi_spec::compute_phi_spec_from_joinir( - &if_phi_context, - &join_inst, - ); - eprintln!( - "[Phase 61-2] JoinIR PhiSpec: header={}, exit={}", - joinir_spec.header_count(), - joinir_spec.exit_count() - ); - - // A/B比較用に保存 - joinir_phi_spec_opt = Some(joinir_spec); - } - - false // Phase 61-2では検証のみ、本番切り替えはPhase 61-3 - } - None => { - if crate::config::env::joinir_if_in_loop_dryrun_enabled() { - eprintln!("[Phase 61-2] ⏭️ JoinIR pattern not matched, using fallback"); - } - false - } - } - } else { - false - } - } else { - false - }; - - let mut ops = Ops(self); - - // Phase 26-F-2: BodyLocalPhiBuilder削除 - // Phase 35-5: if_body_local_merge.rs削除、PhiBuilderBoxに吸収 - // 理由: 箱理論による責務分離(ループスコープ分析 vs if-merge専用処理) - - // フォールバック: PhiBuilderBox経路(既存) - if !joinir_success { - // Phase 26-E: PhiBuilderBox SSOT統合(If PHI生成) - // Legacy: merge_modified_with_control() → New: PhiBuilderBox::generate_phis() - let mut phi_builder = crate::mir::phi_core::phi_builder_box::PhiBuilderBox::new(); - - // Phase 26-F-3: ループ内if-mergeコンテキスト設定(ChatGPT設計) - phi_builder.set_if_context( - true, // in_loop_body = true - carrier_names.clone(), - ); - - // Phase 35-5: if_body_local_merge.rs削除、ロジックはPhiBuilderBox内に統合 - let post_snapshots = if let Some(ref else_map) = else_var_map_end_opt { - vec![then_var_map_end.clone(), else_map.clone()] - } else { - vec![then_var_map_end.clone()] - }; - phi_builder.generate_phis(&mut ops, &form, &pre_if_var_map, &post_snapshots)?; - - // Phase 61-2: A/B比較(JoinIR vs PhiBuilderBox) - if crate::config::env::joinir_if_in_loop_dryrun_enabled() { - if let Some(ref joinir_spec) = joinir_phi_spec_opt { - // PhiBuilderBox経路でのPhiSpecを抽出 - let builder_spec = - crate::mir::join_ir::lowering::if_phi_spec::extract_phi_spec_from_builder( - &pre_if_var_map, - &post_snapshots, - &carrier_names, - ); - - eprintln!( - "[Phase 61-2] PhiBuilderBox PhiSpec: header={}, exit={}", - builder_spec.header_count(), - builder_spec.exit_count() - ); - - // A/B比較実行 - let _matches = crate::mir::join_ir::lowering::if_phi_spec::compare_and_log_phi_specs( - joinir_spec, - &builder_spec, - ); - } - } - } - - // Phase 26-E-4: PHI生成後に variable_map をリセット(ChatGPT/Task先生指示) - // 理由: else_var_map_end_opt が正しい snapshot を保持したまま PHI 生成に渡す必要がある - // 修正前: PHI生成前にリセット → else ブロック内定義変数が消失 → domination error - // 修正後: PHI生成後にリセット → 正しいPHI入力 → SSA保証 - self.parent_builder.variable_map = pre_if_var_map.clone(); - - // ControlForm 観測: 環境フラグ(未設定時は既定ON)のとき IfShape をダンプ - if is_control_form_trace_on() { - form.debug_dump(); - #[cfg(debug_assertions)] - if let Some(ref func) = self.parent_builder.current_function { - if_shape.debug_validate(func); - } - } - let void_id = self.new_value(); - self.emit_const(void_id, ConstValue::Void)?; - // Pop merge debug region - self.parent_builder.debug_pop_region(); - Ok(void_id) - } -} - -// Phase 30 F-2.1: LoopPhiOps 実装削除(loop_phi.rs 削除に伴う) -// LoopFormOps が SSOT として機能しているため、レガシー互換層は不要 - -// Implement LoopFormOps trait for LoopBuilder to support LoopFormBuilder integration -impl<'a> LoopFormOps for LoopBuilder<'a> { - fn new_value(&mut self) -> ValueId { - // CRITICAL: Must use MirFunction's next_value_id(), not MirBuilder's value_gen - // Otherwise we get SSA violations because the two counters diverge - let id = if let Some(ref mut func) = self.parent_builder.current_function { - let before = func.next_value_id; - let id = func.next_value_id(); - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!( - "[LoopFormOps::new_value] fn='{}' counter: {} -> {}, allocated: {:?}", - func.signature.name, before, func.next_value_id, id - ); - } - id - } else { - // Fallback (should never happen in practice) - let id = self.parent_builder.value_gen.next(); - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!( - "[LoopFormOps::new_value] FALLBACK value_gen, allocated: {:?}", - id - ); - } - id - }; - id - } - - fn ensure_counter_after(&mut self, max_id: u32) -> Result<(), String> { - if let Some(ref mut func) = self.parent_builder.current_function { - // 📦 Hotfix 1: Consider both parameter count and existing ValueIds - let param_count = func.signature.params.len() as u32; - let min_counter = param_count.max(max_id + 1); - - if func.next_value_id < min_counter { - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!("[LoopFormOps::ensure_counter_after] fn='{}' params={}, max_id={}, adjusting counter {} -> {}", - func.signature.name, param_count, max_id, func.next_value_id, min_counter); - } - func.next_value_id = min_counter; - } - Ok(()) - } else { - Err("No current function to adjust counter".to_string()) - } - } - - fn block_exists(&self, block: BasicBlockId) -> bool { - // 📦 Hotfix 2: Check if block exists in current function's CFG - if let Some(ref func) = self.parent_builder.current_function { - func.blocks.contains_key(&block) - } else { - false - } - } - - fn get_block_predecessors( - &self, - block: BasicBlockId, - ) -> std::collections::HashSet { - // 📦 Hotfix 6: Get actual CFG predecessors for PHI validation - if let Some(ref func) = self.parent_builder.current_function { - if let Some(bb) = func.blocks.get(&block) { - bb.predecessors.clone() - } else { - std::collections::HashSet::new() // Non-existent blocks have no predecessors - } - } else { - std::collections::HashSet::new() - } - } - - /// Phase 26-A-4: ValueIdベースのパラメータ判定(GUARD Bug Prevention) - /// - /// 旧実装(名前ベース)の問題点: - /// - ValueId(0) を「常に未初期化」と誤判定 - /// - パラメータ s=ValueId(0) も弾いてしまうGUARDバグ - /// - /// 新実装(型ベース)の利点: - /// - MirValueKindで型安全判定 - /// - ValueId(0)でもParameter(0)なら正しく判定 - fn is_parameter(&self, value_id: ValueId) -> bool { - // Phase 26-A-4: 型安全なパラメータ判定を使用 - // parent_builder.is_value_parameter() は Phase 26-A-2 で実装済み - let is_param = self.parent_builder.is_value_parameter(value_id); - - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!( - "[is_parameter] ValueId({}) -> {} (kind = {:?})", - value_id.0, - is_param, - self.parent_builder.get_value_kind(value_id) - ); - } - - is_param - } - - fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> { - self.parent_builder.start_new_block(block) - } - - fn emit_copy(&mut self, dst: ValueId, src: ValueId) -> Result<(), String> { - self.parent_builder - .emit_instruction(MirInstruction::Copy { dst, src }) - } - - fn emit_jump(&mut self, target: BasicBlockId) -> Result<(), String> { - self.emit_jump(target) - } - - fn emit_phi( - &mut self, - dst: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - self.emit_phi_at_block_start(self.current_block()?, dst, inputs) - } - - fn update_phi_inputs( - &mut self, - block: BasicBlockId, - phi_id: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - self.parent_builder - .update_phi_instruction(block, phi_id, inputs) - } - - fn update_var(&mut self, name: String, value: ValueId) { - self.parent_builder.variable_map.insert(name, value); - } - - fn get_variable_at_block(&self, name: &str, block: BasicBlockId) -> Option { - // Use the inherent method to avoid recursion - LoopBuilder::get_variable_at_block(self, name, block) - } - - fn mir_function(&self) -> &crate::mir::MirFunction { - self.parent_builder - .current_function - .as_ref() - .expect("LoopBuilder requires current_function") - } -} - -// Phase 26-E-3: PhiBuilderOps 委譲実装(has-a設計) -// -// **Design Rationale:** -// - PhiBuilderOps = 低レベル「PHI命令発行」道具箱 -// - LoopFormOps = 高レベル「ループ構造構築」作業場 -// - 関係: has-a(委譲) - LoopBuilder は両方のインターフェースを提供 -// -// **実装方針:** -// - LoopFormOps の既存実装に委譲(重複なし) -// - HashSet → Vec 変換 + ソート(決定性保証) -// - emit_phi: block 引数差を吸収(current_block 設定) -impl<'a> crate::mir::phi_core::phi_builder_box::PhiBuilderOps for LoopBuilder<'a> { - fn new_value(&mut self) -> ValueId { - // 委譲: LoopFormOps の既存実装を使用 - ::new_value(self) - } - - fn emit_phi( - &mut self, - block: BasicBlockId, - dst: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - // PhiBuilderOps: block 引数あり - // LoopFormOps: block 引数なし(current_block 使用) - // 差を吸収: current_block を設定してから LoopFormOps::emit_phi を呼ぶ - ::set_current_block(self, block)?; - ::emit_phi(self, dst, inputs) - } - - fn update_var(&mut self, name: String, value: ValueId) { - // 委譲: LoopFormOps の既存実装を使用 - ::update_var(self, name, value) - } - - fn get_block_predecessors(&self, block: BasicBlockId) -> Vec { - // LoopFormOps: HashSet 返却 - // PhiBuilderOps: Vec 返却 - // 変換: HashSet → Vec + ソート(決定性保証) - let pred_set = ::get_block_predecessors(self, block); - let mut pred_vec: Vec = pred_set.into_iter().collect(); - pred_vec.sort_by_key(|bb| bb.0); // bb.0 = BasicBlockId の内部値 - pred_vec - } - - fn emit_void(&mut self) -> ValueId { - // void 定数を発行 - let void_id = ::new_value(self); - let _ = self.emit_const(void_id, ConstValue::Void); - void_id - } - - fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> { - // 委譲: LoopFormOps の既存実装を使用 - ::set_current_block(self, block) - } - - fn block_exists(&self, block: BasicBlockId) -> bool { - // 委譲: LoopFormOps の既存実装を使用 - ::block_exists(self, block) - } -} diff --git a/src/mir/loop_builder/README.md b/src/mir/loop_builder/README.md new file mode 100644 index 00000000..bb4adc82 --- /dev/null +++ b/src/mir/loop_builder/README.md @@ -0,0 +1,15 @@ +# loop_builder + +SSA loop lowering for LoopForm v2. This module owns the block layout (preheader/header/body/latch/continue_merge/exit) and delegates PHI construction to `phi_core`. + +## Boundaries +- Handles loop CFG + variable snapshots only; no name解決やコード生成 beyond MIR emission. +- Uses `phi_core` boxes for PHI wiring; avoid duplicating PHI logic here. +- Debug/experimental flags remain centralized in `loop_form.rs`. + +## Submodules +- `control.rs`: break/continue capture + predecessor bookkeeping +- `loop_form.rs`: main loop lowering pipeline +- `statements.rs`: loop-body statement lowering entry point +- `if_lowering.rs`: in-loop `if` lowering with JoinIR/PHI bridge +- `phi_ops.rs`: PHI emit helpers + LoopFormOps/PhiBuilderOps impls diff --git a/src/mir/loop_builder/control.rs b/src/mir/loop_builder/control.rs new file mode 100644 index 00000000..a2b073d1 --- /dev/null +++ b/src/mir/loop_builder/control.rs @@ -0,0 +1,93 @@ +use super::{ConstValue, LoopBuilder, ValueId}; +use crate::mir::BasicBlockId; + +/// ループ脱出の種類(箱化・共通化のための型) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum LoopExitKind { + /// break文(exit blockへジャンプ) + Break, + /// continue文(header blockへジャンプ) + Continue, +} + +impl<'a> LoopBuilder<'a> { + /// Emit a jump to `target` from the current block and record predecessor metadata. + fn jump_with_pred(&mut self, target: BasicBlockId) -> Result<(), String> { + let cur_block = self.current_block()?; + self.emit_jump(target)?; + let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, target, cur_block); + Ok(()) + } + + /// [LoopForm] 【箱化】ループ脱出の共通処理(break/continue統一化) + /// + /// Phase 25.1o: break と continue の共通パターンを抽出し、 + /// LoopExitKind で振る舞いを切り替える統一メソッド。 + /// + /// # 処理フロー + /// 1. 現在の変数マップをスナップショット + /// 2. [LoopForm] スナップショット保存(Break → exit_snapshots, Continue → continue_snapshots) + /// 3. Void を定義(戻り値用のダミー) + /// 4. [LoopForm] ターゲットブロックへジャンプ(Break → exit, Continue → header/continue_merge) + /// 5. 現在ブロックはジャンプで終端済みのまま維持(新しい unreachable ブロックは作らない) + fn do_loop_exit(&mut self, kind: LoopExitKind) -> Result { + // 1. スナップショット取得(共通処理) + let snapshot = self.get_current_variable_map(); + let cur_block = self.current_block()?; + + // 2. [LoopForm] exit-break path: スナップショット保存(exit PHI入力用) + // [LoopForm] continue-backedge path: スナップショット保存(continue_merge → header) + match kind { + LoopExitKind::Break => { + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!( + "[DEBUG/do_break] Saved snapshot from block {:?}, vars: {:?}", + cur_block, + snapshot.keys().collect::>() + ); + } + self.exit_snapshots.push((cur_block, snapshot)); + } + LoopExitKind::Continue => { + self.block_var_maps.insert(cur_block, snapshot.clone()); + self.continue_snapshots.push((cur_block, snapshot)); + } + } + + // 3. 戻り値用のダミーを定義(現在ブロック内で完結させる) + let void_id = self.new_value(); + self.emit_const(void_id, ConstValue::Void)?; + + // 4. ターゲットブロックへジャンプ(kind別処理) + match kind { + LoopExitKind::Break => { + if let Some(exit_bb) = crate::mir::builder::loops::current_exit(self.parent_builder) + { + self.jump_with_pred(exit_bb)?; + } + } + LoopExitKind::Continue => { + // 既定では header にジャンプするが、canonical continue merge を導入した場合は + // continue_target 側を優先する。 + if let Some(target) = self.continue_target.or(self.loop_header) { + self.jump_with_pred(target)?; + } + } + } + + // 5. 現在ブロックは jump で終端済み。新しい unreachable ブロックは作らない。 + Ok(void_id) + } + + /// Handle a `break` statement: jump to loop exit and continue in a fresh unreachable block. + /// 【箱化】do_loop_exit() への thin wrapper + pub(super) fn do_break(&mut self) -> Result { + self.do_loop_exit(LoopExitKind::Break) + } + + /// Handle a `continue` statement: snapshot vars, jump to loop header, then continue in a fresh unreachable block. + /// 【箱化】do_loop_exit() への thin wrapper + pub(super) fn do_continue(&mut self) -> Result { + self.do_loop_exit(LoopExitKind::Continue) + } +} diff --git a/src/mir/loop_builder/if_lowering.rs b/src/mir/loop_builder/if_lowering.rs new file mode 100644 index 00000000..6af429cc --- /dev/null +++ b/src/mir/loop_builder/if_lowering.rs @@ -0,0 +1,348 @@ +use super::{LoopBuilder, ValueId}; +use crate::ast::ASTNode; +use crate::mir::control_form::{is_control_form_trace_on, ControlForm, IfShape}; +use crate::mir::utils::{capture_actual_predecessor_and_jump, is_current_block_terminated}; +use crate::mir::{BasicBlockId, ConstValue}; +use std::collections::{BTreeMap, BTreeSet}; + +impl<'a> LoopBuilder<'a> { + /// Lower an if-statement inside a loop, preserving continue/break semantics and emitting PHIs per assigned variable. + pub(super) fn lower_if_in_loop( + &mut self, + condition: ASTNode, + then_body: Vec, + else_body: Option>, + ) -> Result { + // Reserve a deterministic join id for debug region labeling (nested inside loop) + let join_id = self.parent_builder.debug_next_join_id(); + // Pre-pin heuristic was deprecated; leave operands untouched for clarity. + // Evaluate condition and create blocks + let cond_val = self.parent_builder.build_expression(condition)?; + let then_bb = self.new_block(); + let else_bb = self.new_block(); + let merge_bb = self.new_block(); + let pre_branch_bb = self.current_block()?; + self.emit_branch(cond_val, then_bb, else_bb)?; + + // Capture pre-if variable map (used for phi normalization) + let pre_if_var_map = self.get_current_variable_map(); + let trace_if = std::env::var("NYASH_IF_TRACE").ok().as_deref() == Some("1"); + // (legacy) kept for earlier merge style; now unified helpers compute deltas directly. + + // then branch + self.set_current_block(then_bb)?; + // Debug region: join then-branch (inside loop) + self.parent_builder + .debug_push_region(format!("join#{}", join_id) + "/then"); + // Materialize all variables at entry via single-pred Phi (correctness-first) + let names_then: Vec = self + .parent_builder + .variable_map + .keys() + .filter(|n| !n.starts_with("__pin$")) + .cloned() + .collect(); + for name in names_then { + if let Some(&pre_v) = pre_if_var_map.get(&name) { + let phi_val = self.new_value(); + self.emit_phi_at_block_start(then_bb, phi_val, vec![(pre_branch_bb, pre_v)])?; + let name_for_log = name.clone(); + self.update_variable(name, phi_val); + if trace_if { + eprintln!( + "[if-trace] then-entry phi var={} pre={:?} -> dst={:?}", + name_for_log, pre_v, phi_val + ); + } + } + } + for s in then_body.iter().cloned() { + let _ = self.build_statement(s)?; + // フェーズS修正:統一終端検出ユーティリティ使用 + if is_current_block_terminated(self.parent_builder)? { + break; + } + } + let then_var_map_end = self.get_current_variable_map(); + // フェーズS修正:最強モード指摘の「実到達predecessor捕捉」を統一 + let _then_pred_to_merge = + capture_actual_predecessor_and_jump(self.parent_builder, merge_bb)?; + // Pop then-branch debug region + self.parent_builder.debug_pop_region(); + + // else branch + self.set_current_block(else_bb)?; + // Debug region: join else-branch (inside loop) + self.parent_builder + .debug_push_region(format!("join#{}", join_id) + "/else"); + // Materialize all variables at entry via single-pred Phi (correctness-first) + let names2: Vec = self + .parent_builder + .variable_map + .keys() + .filter(|n| !n.starts_with("__pin$")) + .cloned() + .collect(); + for name in names2 { + if let Some(&pre_v) = pre_if_var_map.get(&name) { + let phi_val = self.new_value(); + self.emit_phi_at_block_start(else_bb, phi_val, vec![(pre_branch_bb, pre_v)])?; + let name_for_log = name.clone(); + self.update_variable(name, phi_val); + if trace_if { + eprintln!( + "[if-trace] else-entry phi var={} pre={:?} -> dst={:?}", + name_for_log, pre_v, phi_val + ); + } + } + } + let mut else_var_map_end_opt: Option> = None; + if let Some(es) = else_body.clone() { + for s in es.into_iter() { + let _ = self.build_statement(s)?; + // フェーズS修正:統一終端検出ユーティリティ使用 + if is_current_block_terminated(self.parent_builder)? { + break; + } + } + else_var_map_end_opt = Some(self.get_current_variable_map()); + } + // フェーズS修正:else branchでも統一実到達predecessor捕捉 + let _else_pred_to_merge = + capture_actual_predecessor_and_jump(self.parent_builder, merge_bb)?; + // Pop else-branch debug region + self.parent_builder.debug_pop_region(); + + // Continue at merge + self.set_current_block(merge_bb)?; + // Debug region: join merge (inside loop) + self.parent_builder + .debug_push_region(format!("join#{}", join_id) + "/join"); + + // Phase 25.1: HashSet → BTreeSet(決定性確保) + // Phase 40-4.1: JoinIR経路をデフォルト化(collect_assigned_vars削除) + let _vars: BTreeSet = + crate::mir::phi_core::if_phi::collect_assigned_vars_via_joinir( + &then_body, + else_body.as_ref(), + ); + + // Phase 26-E: PhiBuilderBox 統合 + // Phase 57: PhiMergeOps impl 削除(デッドコード、2025-11-29) + // - PhiBuilderOps に統一され、PhiMergeOps は不要になった + struct Ops<'b, 'a>(&'b mut LoopBuilder<'a>); + + // Phase 26-E: PhiBuilderOps trait 実装(箱理論統一) + impl<'b, 'a> crate::mir::phi_core::phi_builder_box::PhiBuilderOps for Ops<'b, 'a> { + fn new_value(&mut self) -> ValueId { + self.0.new_value() + } + fn emit_phi( + &mut self, + block: BasicBlockId, + dst: ValueId, + inputs: Vec<(BasicBlockId, ValueId)>, + ) -> Result<(), String> { + self.0.emit_phi_at_block_start(block, dst, inputs) + } + fn update_var(&mut self, name: String, value: ValueId) { + self.0.parent_builder.variable_map.insert(name, value); + } + fn get_block_predecessors(&self, block: BasicBlockId) -> Vec { + if let Some(ref func) = self.0.parent_builder.current_function { + func.blocks + .get(&block) + .map(|bb| bb.predecessors.iter().copied().collect()) + .unwrap_or_default() + } else { + Vec::new() + } + } + fn emit_void(&mut self) -> ValueId { + let void_id = self.0.new_value(); + let _ = self.0.emit_const(void_id, ConstValue::Void); + void_id + } + + // Phase 3-A: Loop PHI生成用メソッド実装 + fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> { + self.0.parent_builder.current_block = Some(block); + Ok(()) + } + + fn block_exists(&self, block: BasicBlockId) -> bool { + if let Some(ref func) = self.0.parent_builder.current_function { + func.blocks.contains_key(&block) + } else { + false + } + } + } + + // Phase 25.1h: ControlForm統合版に切り替え + let if_shape = IfShape { + cond_block: pre_branch_bb, + then_block: then_bb, + else_block: Some(else_bb), + merge_block: merge_bb, + }; + let form = ControlForm::from_if(if_shape.clone()); + + // Region/GC 観測レイヤ(Phase 25.1l): + // NYASH_REGION_TRACE=1 のときだけ、Stage‑B 周辺 If 構造の + // Region 情報(entry/exit/slots)をログに出すよ。 + crate::mir::region::observer::observe_control_form(self.parent_builder, &form); + + // Phase 61-1: If-in-loop JoinIR化(開発フラグ制御) + // carrier_namesを作成(両経路で共通) + let carrier_names: BTreeSet = pre_if_var_map + .keys() + .filter(|name| !name.starts_with("__pin$")) // 一時変数除外 + .cloned() + .collect(); + + // Phase 61-2: JoinIR dry-run検証モード + // dry-run用: JoinInstとPhiSpecを保存(A/B比較用) + let mut joinir_phi_spec_opt: Option = + None; + + let joinir_success = if crate::config::env::joinir_if_select_enabled() { + // IfPhiContext作成 + let if_phi_context = + crate::mir::join_ir::lowering::if_phi_context::IfPhiContext::for_loop_body( + carrier_names.clone(), + ); + + // JoinIR経路を試行 + if let Some(ref func) = self.parent_builder.current_function { + match crate::mir::join_ir::lowering::try_lower_if_to_joinir( + func, + pre_branch_bb, + false, // debug + Some(&if_phi_context), + ) { + Some(join_inst) => { + eprintln!( + "[Phase 61-2] ✅ If-in-loop lowered via JoinIR: {:?}", + join_inst + ); + + // Phase 61-2: dry-runモードでPHI仕様を検証 + if crate::config::env::joinir_if_in_loop_dryrun_enabled() { + eprintln!("[Phase 61-2] 🔍 dry-run mode enabled"); + eprintln!("[Phase 61-2] Carrier variables: {:?}", carrier_names); + eprintln!( + "[Phase 61-2] JoinInst type: {}", + match &join_inst { + crate::mir::join_ir::JoinInst::Select { .. } => "Select", + crate::mir::join_ir::JoinInst::IfMerge { .. } => "IfMerge", + _ => "Other", + } + ); + + // Phase 61-2.3: JoinInstからPhiSpecを計算 + let joinir_spec = crate::mir::join_ir::lowering::if_phi_spec::compute_phi_spec_from_joinir( + &if_phi_context, + &join_inst, + ); + eprintln!( + "[Phase 61-2] JoinIR PhiSpec: header={}, exit={}", + joinir_spec.header_count(), + joinir_spec.exit_count() + ); + + // A/B比較用に保存 + joinir_phi_spec_opt = Some(joinir_spec); + } + + false // Phase 61-2では検証のみ、本番切り替えはPhase 61-3 + } + None => { + if crate::config::env::joinir_if_in_loop_dryrun_enabled() { + eprintln!("[Phase 61-2] ⏭️ JoinIR pattern not matched, using fallback"); + } + false + } + } + } else { + false + } + } else { + false + }; + + let mut ops = Ops(self); + + // Phase 26-F-2: BodyLocalPhiBuilder削除 + // Phase 35-5: if_body_local_merge.rs削除、PhiBuilderBoxに吸収 + // 理由: 箱理論による責務分離(ループスコープ分析 vs if-merge専用処理) + + // フォールバック: PhiBuilderBox経路(既存) + if !joinir_success { + // Phase 26-E: PhiBuilderBox SSOT統合(If PHI生成) + // Legacy: merge_modified_with_control() → New: PhiBuilderBox::generate_phis() + let mut phi_builder = crate::mir::phi_core::phi_builder_box::PhiBuilderBox::new(); + + // Phase 26-F-3: ループ内if-mergeコンテキスト設定(ChatGPT設計) + phi_builder.set_if_context( + true, // in_loop_body = true + carrier_names.clone(), + ); + + // Phase 35-5: if_body_local_merge.rs削除、ロジックはPhiBuilderBox内に統合 + let post_snapshots = if let Some(ref else_map) = else_var_map_end_opt { + vec![then_var_map_end.clone(), else_map.clone()] + } else { + vec![then_var_map_end.clone()] + }; + phi_builder.generate_phis(&mut ops, &form, &pre_if_var_map, &post_snapshots)?; + + // Phase 61-2: A/B比較(JoinIR vs PhiBuilderBox) + if crate::config::env::joinir_if_in_loop_dryrun_enabled() { + if let Some(ref joinir_spec) = joinir_phi_spec_opt { + // PhiBuilderBox経路でのPhiSpecを抽出 + let builder_spec = + crate::mir::join_ir::lowering::if_phi_spec::extract_phi_spec_from_builder( + &pre_if_var_map, + &post_snapshots, + &carrier_names, + ); + + eprintln!( + "[Phase 61-2] PhiBuilderBox PhiSpec: header={}, exit={}", + builder_spec.header_count(), + builder_spec.exit_count() + ); + + // A/B比較実行 + let _matches = + crate::mir::join_ir::lowering::if_phi_spec::compare_and_log_phi_specs( + joinir_spec, + &builder_spec, + ); + } + } + } + + // Phase 26-E-4: PHI生成後に variable_map をリセット(ChatGPT/Task先生指示) + // 理由: else_var_map_end_opt が正しい snapshot を保持したまま PHI 生成に渡す必要がある + // 修正前: PHI生成前にリセット → else ブロック内定義変数が消失 → domination error + // 修正後: PHI生成後にリセット → 正しいPHI入力 → SSA保証 + self.parent_builder.variable_map = pre_if_var_map.clone(); + + // ControlForm 観測: 環境フラグ(未設定時は既定ON)のとき IfShape をダンプ + if is_control_form_trace_on() { + form.debug_dump(); + #[cfg(debug_assertions)] + if let Some(ref func) = self.parent_builder.current_function { + if_shape.debug_validate(func); + } + } + let void_id = self.new_value(); + self.emit_const(void_id, ConstValue::Void)?; + // Pop merge debug region + self.parent_builder.debug_pop_region(); + Ok(void_id) + } +} diff --git a/src/mir/loop_builder/loop_form.rs b/src/mir/loop_builder/loop_form.rs new file mode 100644 index 00000000..acaf6e44 --- /dev/null +++ b/src/mir/loop_builder/loop_form.rs @@ -0,0 +1,575 @@ +use super::{ConstValue, LoopBuilder, ValueId}; +use crate::ast::ASTNode; +use crate::mir::control_form::{is_control_form_trace_on, ControlForm, LoopShape}; +use crate::mir::phi_core::loopform_builder::{LoopFormBuilder, LoopFormOps}; +use crate::mir::utils::is_current_block_terminated; +use crate::mir::{BasicBlockId, MirInstruction}; +use std::collections::{BTreeMap, BTreeSet}; + +impl<'a> LoopBuilder<'a> { + /// SSA形式でループを構築 (LoopForm v2 only) + pub fn build_loop( + &mut self, + condition: ASTNode, + body: Vec, + ) -> Result { + // Phase 7-F: Legacy loop builder removed - LoopForm v2 is now the only implementation + self.build_loop_with_loopform(condition, body) + } + + /// SSA形式でループを構築 (LoopFormBuilder implementation) + fn build_loop_with_loopform( + &mut self, + condition: ASTNode, + body: Vec, + ) -> Result { + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!("[build_loop_with_loopform] === ENTRY ==="); + if let Some(ref func) = self.parent_builder.current_function { + eprintln!( + "[build_loop_with_loopform] fn='{}', counter={}, func_ptr={:p}", + func.signature.name, func.next_value_id, func as *const _ + ); + } + eprintln!("[build_loop_with_loopform] condition={:?}", condition); + eprintln!("[build_loop_with_loopform] body.len()={}", body.len()); + } + // Create loop structure blocks following LLVM canonical form + // We need a dedicated preheader block to materialize loop entry copies + let before_loop_id = self.current_block()?; + + // Capture variable snapshot BEFORE creating new blocks (at loop entry point) + let current_vars = self.get_current_variable_map(); + + // DEBUG: Show variable map before guard check + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!( + "[loopform] before_loop_id={:?}, variable_map size={}", + before_loop_id, + current_vars.len() + ); + for (name, value) in ¤t_vars { + eprintln!(" {} -> {:?}", name, value); + } + } + + // Phase 25.3: GUARD check removed - ValueId(0) is valid for first parameters + // Previous code incorrectly assumed ValueId(0) always meant uninitialized variables, + // but it's actually the correct ID for the first parameter in functions like: + // skip_whitespace(s, idx) -> s=ValueId(0), idx=ValueId(1) + // This caused loops in such functions to be entirely skipped. + + let preheader_id = self.new_block(); + let header_id = self.new_block(); + let body_id = self.new_block(); + let latch_id = self.new_block(); + let exit_id = self.new_block(); + // Phase 25.1q: canonical continue merge block + // All continue 文は一度このブロックに集約してから header へ戻る。 + let continue_merge_id = self.new_block(); + + // Jump from current block to preheader + let entry_block = self.current_block()?; + self.emit_jump(preheader_id)?; + // 📦 Hotfix 6: Add CFG predecessor for preheader (same as legacy version) + crate::mir::builder::loops::add_predecessor( + self.parent_builder, + preheader_id, + entry_block, + )?; + + // Initialize LoopFormBuilder with preheader and header blocks + let mut loopform = LoopFormBuilder::new(preheader_id, header_id); + + // Pass 1: Prepare structure (allocate all ValueIds upfront) + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!("[loopform] Block IDs: preheader={:?}, header={:?}, body={:?}, latch={:?}, exit={:?}", + preheader_id, header_id, body_id, latch_id, exit_id); + eprintln!( + "[loopform] variable_map at loop entry (size={}):", + current_vars.len() + ); + let mut loop_count = 0; + for (name, value) in ¤t_vars { + loop_count += 1; + eprintln!(" [{}] {} -> {:?}", loop_count, name, value); + // Phase 26-A-4: ValueIdベース判定に変更(名前ベース → 型安全) + let is_param = self.is_parameter(*value); + eprintln!(" param={}", is_param); + } + eprintln!("[loopform] iterated {} times", loop_count); + if let Some(ref func) = self.parent_builder.current_function { + eprintln!( + "[loopform] BEFORE prepare_structure: fn='{}', counter={}, func_ptr={:p}", + func.signature.name, func.next_value_id, func as *const _ + ); + } else { + eprintln!("[loopform] BEFORE prepare_structure: current_function=None"); + } + } + loopform.prepare_structure(self, ¤t_vars)?; + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + if let Some(ref func) = self.parent_builder.current_function { + eprintln!( + "[loopform] AFTER prepare_structure: fn='{}', counter={}, func_ptr={:p}", + func.signature.name, func.next_value_id, func as *const _ + ); + } else { + eprintln!("[loopform] AFTER prepare_structure: current_function=None"); + } + } + + // Pass 2: Emit preheader (copies and jump to header) + loopform.emit_preheader(self)?; + // 📦 Hotfix 6: Add CFG predecessor for header from preheader (same as legacy version) + crate::mir::builder::loops::add_predecessor(self.parent_builder, header_id, preheader_id)?; + + // Pass 3: Emit header PHIs (incomplete, only preheader edge) + self.set_current_block(header_id)?; + + // Ensure header block exists before emitting PHIs + self.parent_builder.ensure_block_exists(header_id)?; + + // Phase 27.4-C Refactor: JoinIR Loop φ バイパスフラグ統一取得 + let fn_name = self + .parent_builder + .current_function + .as_ref() + .map(|f| f.signature.name.clone()) + .unwrap_or_default(); + + let bypass_flags = crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name); + + if bypass_flags.header { + // Phase 27.4-C: JoinIR 実験経路では Header φ を生成しない。 + // Pinned/Carrier の値は preheader の copy をそのまま使う。 + // + // ⚠️ 重要: このモードでは MIR は不完全(φ 抜け)であり、VM で実行できない。 + // JoinIR runner 専用モードであることに注意。 + if crate::mir::phi_core::loopform_builder::is_loopform_debug_enabled() { + eprintln!("[loopform/27.4-C] Header φ bypass active for: {}", fn_name); + eprintln!("[loopform/27.4-C] Skipping emit_header_phis() - using preheader values directly"); + } + } else { + // 従来どおり HeaderPhiBuilder を使って φ を準備 + loopform.emit_header_phis(self)?; + } + + if crate::mir::phi_core::loopform_builder::is_loopform_debug_enabled() { + eprintln!("[loopform] variable_map after emit_header_phis:"); + for (name, value) in self.get_current_variable_map().iter() { + eprintln!(" {} -> {:?}", name, value); + } + } + + // Set up loop context for break/continue + crate::mir::builder::loops::push_loop_context(self.parent_builder, header_id, exit_id); + self.loop_header = Some(header_id); + // 既定の continue 先を canonical continue_merge ブロックにする。 + // ここを差し替えることで do_loop_exit(Continue) のターゲットを一元化する。 + self.continue_target = Some(continue_merge_id); + self.continue_snapshots.clear(); + self.exit_snapshots.clear(); + + // [LoopForm] header-cond: cond true → body, false → exit (Case A/B) + // - Case A: loop(i < n) → header can branch to exit directly + // - Case B: loop(1 == 1) → header always enters body, exit only via break + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!( + "[loopform/condition] BEFORE build_expression: current_block={:?}", + self.current_block()? + ); + if let Some(ref func) = self.parent_builder.current_function { + eprintln!( + "[loopform/condition] BEFORE: fn='{}', counter={}, func_ptr={:p}", + func.signature.name, func.next_value_id, func as *const _ + ); + } + } + let cond_value = self.parent_builder.build_expression(condition)?; + // Capture the ACTUAL block that emits the branch (might differ from header_id + // if build_expression created new blocks) + let branch_source_block = self.current_block()?; + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!( + "[loopform/condition] AFTER build_expression: branch_source_block={:?}", + branch_source_block + ); + if let Some(ref func) = self.parent_builder.current_function { + eprintln!( + "[loopform/condition] AFTER: fn='{}', counter={}, func_ptr={:p}", + func.signature.name, func.next_value_id, func as *const _ + ); + } + } + self.emit_branch(cond_value, body_id, exit_id)?; + // 📦 Hotfix 6: Add CFG predecessors for branch targets (Cytron et al. 1991 requirement) + // This ensures exit_block.predecessors is populated before Exit PHI generation + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!( + "[loopform/condition] BEFORE add_predecessor: exit_id={:?}, branch_source={:?}", + exit_id, branch_source_block + ); + } + crate::mir::builder::loops::add_predecessor( + self.parent_builder, + body_id, + branch_source_block, + )?; + crate::mir::builder::loops::add_predecessor( + self.parent_builder, + exit_id, + branch_source_block, + )?; + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!( + "[loopform/condition] AFTER emit_branch: current_block={:?}", + self.current_block()? + ); + eprintln!( + "[loopform/condition] Added predecessors: body={:?} exit={:?} from={:?}", + body_id, exit_id, branch_source_block + ); + // Verify predecessors were added + if let Some(ref func) = self.parent_builder.current_function { + if let Some(exit_block) = func.blocks.get(&exit_id) { + eprintln!( + "[loopform/condition] exit_block.predecessors = {:?}", + exit_block.predecessors + ); + } + } + } + + // Lower loop body + self.set_current_block(body_id)?; + for stmt in body { + self.build_statement(stmt)?; + if is_current_block_terminated(self.parent_builder)? { + break; + } + } + + // Capture variable snapshot at end of body (before jumping to latch) + let body_end_vars = self.get_current_variable_map(); + + // Step 5-1: Writes集合収集(選択肢2+3統合: Snapshot比較で再代入検出) + // current_vars (preheader) と body_end_vars を比較し、ValueId が変わった変数を特定 + use std::collections::HashSet; + let mut writes = HashSet::new(); + for (name, &body_value) in &body_end_vars { + // Skip __pin$ temporary variables - they are always BodyLocalInternal + // (Task先生の発見: これらをcarrier扱いすると未定義ValueIdエラーの原因になる) + if name.starts_with("__pin$") && name.contains("$@") { + continue; + } + + if let Some(&base_value) = current_vars.get(name) { + if body_value != base_value { + writes.insert(name.clone()); + } + } + // else: body で新規定義された変数(body-local)、header PHI 不要 + } + + // DEBUG: Log writes collection + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!("[loopform/writes] === WRITES COLLECTION (Step 5-1) ==="); + eprintln!( + "[loopform/writes] {} variables modified in loop body", + writes.len() + ); + let mut sorted_writes: Vec<_> = writes.iter().collect(); + sorted_writes.sort(); + for name in &sorted_writes { + eprintln!("[loopform/writes] WRITE: {}", name); + } + } + + // Jump to latch if not already terminated + let actual_latch_id = if !is_current_block_terminated(self.parent_builder)? { + self.emit_jump(latch_id)?; + latch_id + } else { + // Body is terminated (break/continue), use current block as latch + self.current_block()? + }; + + // Latch: jump back to header + self.set_current_block(latch_id)?; + + // Update variable map with body end values for sealing + for (name, value) in &body_end_vars { + self.update_variable(name.clone(), *value); + } + + self.emit_jump(header_id)?; + // 📦 Hotfix 6: Add CFG predecessor for header from latch (same as legacy version) + crate::mir::builder::loops::add_predecessor(self.parent_builder, header_id, latch_id)?; + + // Phase 25.1c/k: body-local 変数の PHI 生成 + // BreakFinderBox / FuncScannerBox 等で、loop body 内で新規宣言された local 変数が + // loop header に戻った時に undefined になる問題を修正 + // + // Step 5-5-B: EXPERIMENTAL - Body-local Header PHI generation DISABLED + // Reason: Option C design states body-local variables should NOT have header PHIs + // - BodyLocalExit: needs EXIT PHI only, NOT header PHI + // - BodyLocalInternal: needs NO PHI at all + // + // TODO Step 5-3: Integrate Option C classification (LoopVarClassBox) here + // + // TEMPORARY DISABLE to test hypothesis that header PHIs are the root cause + let trace_loop_phi = std::env::var("HAKO_LOOP_PHI_TRACE").ok().as_deref() == Some("1"); + + // DISABLED: Body-local header PHI generation + // This code was causing undefined value errors because it created header PHIs + // for variables that should only have exit PHIs (or no PHIs at all) + if false { // Disabled for Step 5-5-B experiment + // [Original code removed - see git history if needed] + } + + // Pass 4: Generate continue_merge PHIs first, then seal header PHIs + // Phase 25.1c/k: canonical continue_merge ブロックで PHI を生成してから seal_phis を呼ぶ + let raw_continue_snaps = self.continue_snapshots.clone(); + + // Step 1: continue_merge ブロックで PHI 生成(merged_snapshot を作る) + // Phase 25.2: LoopSnapshotMergeBox を使って整理 + self.set_current_block(continue_merge_id)?; + + let merged_snapshot: BTreeMap = if !raw_continue_snaps.is_empty() { + if trace_loop_phi { + eprintln!( + "[loop-phi/continue-merge] Generating PHI nodes for {} continue paths", + raw_continue_snaps.len() + ); + } + + // すべての continue snapshot に現れる変数を収集 + let mut all_vars: BTreeMap> = BTreeMap::new(); + for (continue_bb, snapshot) in &raw_continue_snaps { + for (var_name, &value) in snapshot { + all_vars + .entry(var_name.clone()) + .or_default() + .push((*continue_bb, value)); + } + } + + // 各変数について PHI ノードを生成 + // ======================================== + // Phase 59b: PhiInputCollector インライン化 + // ======================================== + let mut merged = BTreeMap::new(); + for (var_name, inputs) in all_vars { + // Step 1: sanitize (BTreeMap で重複削除&ソート) + let mut sanitized: BTreeMap = BTreeMap::new(); + for (bb, val) in &inputs { + sanitized.insert(*bb, *val); + } + let final_inputs: Vec<(BasicBlockId, ValueId)> = sanitized.into_iter().collect(); + + // Step 2: optimize_same_value + let same_value = if final_inputs.is_empty() { + None + } else if final_inputs.len() == 1 { + Some(final_inputs[0].1) + } else { + let first_val = final_inputs[0].1; + if final_inputs.iter().all(|(_, val)| *val == first_val) { + Some(first_val) + } else { + None + } + }; + + // Step 3: PHI 生成 or 同一値を使用 + let result_value = if let Some(same_val) = same_value { + // 全て同じ値 or 単一入力 → PHI 不要 + same_val + } else { + // 異なる値を持つ場合は PHI ノードを生成 + let phi_id = self.new_value(); + + if let Some(ref mut func) = self.parent_builder.current_function { + if let Some(merge_block) = func.blocks.get_mut(&continue_merge_id) { + merge_block.add_instruction(MirInstruction::Phi { + dst: phi_id, + inputs: final_inputs, + }); + } + } + + if trace_loop_phi { + eprintln!( + "[loop-phi/continue-merge] Generated PHI for '{}': {:?}", + var_name, phi_id + ); + } + phi_id + }; + + merged.insert(var_name, result_value); + } + + // Note: 変数マップへの反映は seal_phis に委譲(干渉を避ける) + + if trace_loop_phi { + eprintln!( + "[loop-phi/continue-merge] Merged {} variables from {} paths", + merged.len(), + raw_continue_snaps.len() + ); + } + merged + } else { + BTreeMap::new() + }; + + self.emit_jump(header_id)?; + crate::mir::builder::loops::add_predecessor( + self.parent_builder, + header_id, + continue_merge_id, + )?; + + // Step 2: merged_snapshot を使って seal_phis を呼ぶ + // Phase 25.3: Continue merge PHI実装(Task先生の発見!) + // - continueが無いループでも、Latchブロックの値をHeader PHIに伝播する必要がある + // - これにより、Exit PHIがHeader PHI経由で正しい値を受け取れる + let continue_snaps: Vec<(BasicBlockId, BTreeMap)> = { + // まず、merged_snapshot(continue merge PHI結果)を追加 + let mut snaps = if !merged_snapshot.is_empty() { + vec![(continue_merge_id, merged_snapshot.clone())] + } else { + vec![] + }; + + // continueが無い場合でも、Latchブロックのスナップショットを追加 + // これにより、seal_phis()がLatchからの値をHeader PHIに正しく接続できる + if raw_continue_snaps.is_empty() { + // continue文が無い場合、Latchブロックの現在の変数マップをキャプチャ + // Note: このタイミングでは current_block == exit_id だが、 + // variable_map はLatch実行後の状態を保持している + let latch_snapshot = self.get_current_variable_map(); + snaps.push((actual_latch_id, latch_snapshot)); + } + + snaps + }; + + // Phase 27.4C Refactor: Header φ バイパスフラグを統一取得(seal_phis に渡す) + // Note: fn_name は既に line 299-304 で取得済み、String として保持されている + let bypass_flags_for_seal = + crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name); + + // Step 5-1/5-2: Pass writes 集合 for PHI縮約 + // Phase 27.4C: header_bypass フラグも渡す + loopform.seal_phis( + self, + actual_latch_id, + &continue_snaps, + &writes, + bypass_flags_for_seal.header, + )?; + + // Step 3: seal body-local PHIs (complete the inputs) + // Step 5-5-A: REMOVED - PHIs now created complete with both inputs upfront + // Old sealing code was overwriting our preheader+latch inputs with latch-only, + // causing "phi pred mismatch" errors. + // + // Body-local PHIs are now created at line 408-456 with BOTH inputs: + // - preheader: poison value (variable doesn't exist yet) + // - latch: actual value from loop body + // + // No further sealing is needed! + + // Exit block + self.set_current_block(exit_id)?; + + // Phase 25.1h: ControlForm統合版に切り替え + // continue / break のターゲットブロックをユニーク化して収集 + // Phase 25.1: HashSet → BTreeSet(決定性確保) + let mut break_set: BTreeSet = BTreeSet::new(); + for (bb, _) in &self.exit_snapshots { + break_set.insert(*bb); + } + // LoopShape の continue_targets は「header への canonical backedge」を表す。 + // continue が一つ以上存在する場合は continue_merge_id を 1 つだけ登録する。 + let continue_targets: Vec = if self.continue_snapshots.is_empty() { + Vec::new() + } else { + vec![continue_merge_id] + }; + let break_targets: Vec = break_set.into_iter().collect(); + + let loop_shape = LoopShape { + preheader: preheader_id, + header: header_id, + body: body_id, + latch: latch_id, + exit: exit_id, + continue_targets, + break_targets, + }; + let form = ControlForm::from_loop(loop_shape.clone()); + + // Region/GC 観測レイヤ(Phase 25.1l): + // NYASH_REGION_TRACE=1 のときだけ、Stage‑B 周辺ループの + // Region 情報(entry/exit/slots)をログに出すよ。 + crate::mir::region::observer::observe_control_form(self.parent_builder, &form); + + // Phase 27.6-2: JoinIR Exit φ バイパスチェック + let fn_name = self + .parent_builder + .current_function + .as_ref() + .map(|f| f.signature.name.as_str()) + .unwrap_or(""); + + let exit_bypass = crate::mir::phi_core::loopform_builder::joinir_exit_bypass_enabled() + && crate::mir::phi_core::loopform_builder::is_joinir_exit_bypass_target(fn_name); + + if exit_bypass { + // Phase 27.6-2: JoinIR 実験経路では Exit φ を生成しない。 + // ループ内で定義された値だけで exit 後を構成する(JoinIR の k_exit 引数として表現)。 + // + // ⚠️ 重要: このモードでは MIR は不完全(Exit φ 抜け)であり、VM で実行できない。 + // JoinIR runner 専用モードであることに注意。 + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!( + "[loopform/exit-bypass] func={} exit={:?} header={:?} (JoinIR experiment only)", + fn_name, exit_id, header_id + ); + } + } else { + // [LoopForm] exit PHI for Case A/B (uses exit_snapshots + exit_preds) + // - Case A: header+break → exit PHI includes both paths + // - Case B: break-only → exit PHI excludes header (not a predecessor) + let exit_snaps = self.exit_snapshots.clone(); + crate::mir::phi_core::loopform_builder::build_exit_phis_for_control( + &loopform, + self, + &form, + &exit_snaps, + branch_source_block, + )?; + } + + // Pop loop context + crate::mir::builder::loops::pop_loop_context(self.parent_builder); + + // ControlForm 観測: 環境フラグ(未設定時は既定ON)のとき LoopShape をダンプ + if is_control_form_trace_on() { + form.debug_dump(); + #[cfg(debug_assertions)] + if let Some(ref func) = self.parent_builder.current_function { + loop_shape.debug_validate(func); + } + } + + // Return void value + let void_dst = self.new_value(); + self.emit_const(void_dst, ConstValue::Void)?; + Ok(void_dst) + } +} diff --git a/src/mir/loop_builder/mod.rs b/src/mir/loop_builder/mod.rs new file mode 100644 index 00000000..d32dc43e --- /dev/null +++ b/src/mir/loop_builder/mod.rs @@ -0,0 +1,153 @@ +/*! + * MIR Loop Builder - SSA形式でのループ構築専用モジュール + * + * Sealed/Unsealed blockとPhi nodeを使った正しいループ実装。 + * + * LoopForm v2 の「形」をここで固定している: + * - preheader: ループに入る直前のブロック(初期値の copy 発生源) + * - header : ループ条件を評価するブロック(`loop(cond)` の `cond` 部分) + * - body : ループ本体(ユーザーコードが書いたブロック) + * - latch : body の末尾から header へ戻る backedge 用ブロック + * - exit : ループ脱出先(`break` / `cond == false` が合流するブロック) + * + * 典型パターン(ControlForm::LoopShape): + * - Case A: header-cond + header→exit + body→exit(`loop(i < n) { if (...) break }`) + * - Case B: constant-true + body→exit のみ(`loop(1 == 1) { if (...) break }`) + * - この場合、header→exit のエッジは存在しないので、exit PHI に header 値を入れてはいけない。 + * - Case C: continue_merge を経由して header に戻る経路あり(`continue` を含むループ)。 + * + * それぞれのケースは ControlForm / LoopSnapshotMergeBox / ExitPhiBuilder に伝搬され、 + * exit PHI の入力選択や BodyLocalInternal 変数の扱いに反映される。 + * + * モジュール構成: + * - control.rs: break/continue 導線の共通化と predecessor 記録 + * - loop_form.rs: LoopForm v2 本体の構築(preheader/header/body/latch/exit) + * - statements.rs: ループ内ステートメントの lowering(if は if_lowering.rs に委譲) + * - if_lowering.rs: ループ内 if の JoinIR/PHI 統合 + * - phi_ops.rs: PHI 生成ヘルパーと LoopForm/PhiBuilder trait 実装 + */ + +mod control; +mod if_lowering; +mod loop_form; +mod phi_ops; +mod statements; + +use super::{BasicBlockId, ConstValue, MirInstruction, ValueId}; +use std::collections::BTreeMap; // Phase 25.1: 決定性確保 + +/// ループビルダー - SSA形式でのループ構築を管理 +pub struct LoopBuilder<'a> { + /// 親のMIRビルダーへの参照 + pub(super) parent_builder: &'a mut super::builder::MirBuilder, + + /// ブロックごとの変数マップ(スコープ管理) + /// Phase 25.1: BTreeMap → BTreeMap(決定性確保) + #[allow(dead_code)] + pub(super) block_var_maps: BTreeMap>, + + /// ループヘッダーID(continue 先の既定値として使用) + pub(super) loop_header: Option, + + /// continue 文がジャンプするターゲットブロック + /// - 既定: header と同一 + /// - 将来: canonical continue merge ブロックに差し替えるためのフック + pub(super) continue_target: Option, + + /// continue文からの変数スナップショット + pub(super) continue_snapshots: Vec<(BasicBlockId, BTreeMap)>, + + /// break文からの変数スナップショット(exit PHI生成用) + pub(super) exit_snapshots: Vec<(BasicBlockId, BTreeMap)>, + // フェーズM: no_phi_modeフィールド削除(常にPHI使用) +} + +impl<'a> LoopBuilder<'a> { + /// 新しいループビルダーを作成 + pub fn new(parent: &'a mut super::builder::MirBuilder) -> Self { + Self { + parent_builder: parent, + block_var_maps: BTreeMap::new(), + loop_header: None, + continue_target: None, + continue_snapshots: Vec::new(), + exit_snapshots: Vec::new(), // exit PHI用のスナップショット + } + } + + pub(super) fn current_block(&self) -> Result { + self.parent_builder + .current_block + .ok_or_else(|| "No current block".to_string()) + } + + pub(super) fn new_block(&mut self) -> BasicBlockId { + self.parent_builder.block_gen.next() + } + + pub(super) fn new_value(&mut self) -> ValueId { + // Use function-local allocator via MirBuilder helper to keep + // ValueId ranges consistent within the current function. + self.parent_builder.next_value_id() + } + + pub(super) fn set_current_block(&mut self, block_id: BasicBlockId) -> Result<(), String> { + self.parent_builder.start_new_block(block_id) + } + + pub(super) fn emit_jump(&mut self, target: BasicBlockId) -> Result<(), String> { + self.parent_builder + .emit_instruction(MirInstruction::Jump { target }) + } + + pub(super) fn emit_branch( + &mut self, + condition: ValueId, + then_bb: BasicBlockId, + else_bb: BasicBlockId, + ) -> Result<(), String> { + // LocalSSA: ensure condition is materialized in the current block + let condition_local = self.parent_builder.local_ssa_ensure(condition, 4); + self.parent_builder + .emit_instruction(MirInstruction::Branch { + condition: condition_local, + then_bb, + else_bb, + }) + } + + pub(super) fn emit_const(&mut self, dst: ValueId, value: ConstValue) -> Result<(), String> { + self.parent_builder + .emit_instruction(MirInstruction::Const { dst, value }) + } + + pub(super) fn get_current_variable_map(&self) -> BTreeMap { + // Phase 25.1: BTreeMap化 + self.parent_builder.variable_map.clone() + } + + pub(super) fn update_variable(&mut self, name: String, value: ValueId) { + if std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") { + eprintln!( + "[DEBUG] LoopBuilder::update_variable: name={}, value=%{}", + name, value.0 + ); + } + self.parent_builder.variable_map.insert(name, value); + } + + pub(super) fn get_variable_at_block( + &self, + name: &str, + block_id: BasicBlockId, + ) -> Option { + // まずブロックごとのスナップショットを優先 + if let Some(map) = self.block_var_maps.get(&block_id) { + if let Some(v) = map.get(name) { + return Some(*v); + } + } + // フォールバック:現在の変数マップ(単純ケース用) + self.parent_builder.variable_map.get(name).copied() + } +} diff --git a/src/mir/loop_builder/phi_ops.rs b/src/mir/loop_builder/phi_ops.rs new file mode 100644 index 00000000..6aaf647a --- /dev/null +++ b/src/mir/loop_builder/phi_ops.rs @@ -0,0 +1,321 @@ +use super::{LoopBuilder, ValueId}; +use crate::mir::phi_core::loopform_builder::LoopFormOps; +use crate::mir::{BasicBlockId, ConstValue, MirInstruction}; + +impl<'a> LoopBuilder<'a> { + /// ブロック先頭に PHI 命令を挿入(不変条件: PHI は常にブロック先頭) + pub(super) fn emit_phi_at_block_start( + &mut self, + block_id: BasicBlockId, + dst: ValueId, + inputs: Vec<(BasicBlockId, ValueId)>, + ) -> Result<(), String> { + let dbg = std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1"); + if dbg { + eprintln!( + "[DEBUG] LoopBuilder::emit_phi_at_block_start: block={}, dst=%{}, inputs={:?}", + block_id, dst.0, inputs + ); + } + // Phi nodeをブロックの先頭に挿入 + if let Some(ref mut function) = self.parent_builder.current_function { + if let Some(block) = function.get_block_mut(block_id) { + if dbg { + eprintln!( + "[DEBUG] Block {} current instructions count: {}", + block_id, + block.instructions.len() + ); + } + // Phi命令は必ずブロックの先頭に配置。ただし同一dstの既存PHIがある場合は差し替える。 + let mut replaced = false; + let mut idx = 0; + let span = self.parent_builder.current_span; + while idx < block.instructions.len() { + match &mut block.instructions[idx] { + MirInstruction::Phi { + dst: d, + inputs: ins, + } if *d == dst => { + *ins = inputs.clone(); + if block.instruction_spans.len() <= idx { + // Backfill missing spans to preserve alignment after legacy inserts. + while block.instruction_spans.len() <= idx { + block.instruction_spans.push(crate::ast::Span::unknown()); + } + } + block.instruction_spans[idx] = span; + replaced = true; + break; + } + MirInstruction::Phi { .. } => { + idx += 1; + } + _ => break, + } + } + if !replaced { + let phi_inst = MirInstruction::Phi { + dst, + inputs: inputs.clone(), + }; + block.instructions.insert(0, phi_inst); + block.instruction_spans.insert(0, span); + } + if dbg { + eprintln!("[DEBUG] ✅ PHI instruction inserted at position 0"); + eprintln!( + "[DEBUG] Block {} after insert instructions count: {}", + block_id, + block.instructions.len() + ); + } + // Verify PHI is still there + if let Some(first_inst) = block.instructions.get(0) { + match first_inst { + MirInstruction::Phi { dst: phi_dst, .. } => { + if dbg { + eprintln!( + "[DEBUG] Verified: First instruction is PHI dst=%{}", + phi_dst.0 + ); + } + } + other => { + if dbg { + eprintln!( + "[DEBUG] ⚠️ WARNING: First instruction is NOT PHI! It's {:?}", + other + ); + } + } + } + } + Ok(()) + } else { + if dbg { + eprintln!("[DEBUG] ❌ Block {} not found!", block_id); + } + Err(format!("Block {} not found", block_id)) + } + } else { + if dbg { + eprintln!("[DEBUG] ❌ No current function!"); + } + Err("No current function".to_string()) + } + } +} + +// Phase 30 F-2.1: LoopPhiOps 実装削除(loop_phi.rs 削除に伴う) +// LoopFormOps が SSOT として機能しているため、レガシー互換層は不要 + +// Implement LoopFormOps trait for LoopBuilder to support LoopFormBuilder integration +impl<'a> LoopFormOps for LoopBuilder<'a> { + fn new_value(&mut self) -> ValueId { + // CRITICAL: Must use MirFunction's next_value_id(), not MirBuilder's value_gen + // Otherwise we get SSA violations because the two counters diverge + let id = if let Some(ref mut func) = self.parent_builder.current_function { + let before = func.next_value_id; + let id = func.next_value_id(); + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!( + "[LoopFormOps::new_value] fn='{}' counter: {} -> {}, allocated: {:?}", + func.signature.name, before, func.next_value_id, id + ); + } + id + } else { + // Fallback (should never happen in practice) + let id = self.parent_builder.value_gen.next(); + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!( + "[LoopFormOps::new_value] FALLBACK value_gen, allocated: {:?}", + id + ); + } + id + }; + id + } + + fn ensure_counter_after(&mut self, max_id: u32) -> Result<(), String> { + if let Some(ref mut func) = self.parent_builder.current_function { + // 📦 Hotfix 1: Consider both parameter count and existing ValueIds + let param_count = func.signature.params.len() as u32; + let min_counter = param_count.max(max_id + 1); + + if func.next_value_id < min_counter { + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!("[LoopFormOps::ensure_counter_after] fn='{}' params={}, max_id={}, adjusting counter {} -> {}", + func.signature.name, param_count, max_id, func.next_value_id, min_counter); + } + func.next_value_id = min_counter; + } + Ok(()) + } else { + Err("No current function to adjust counter".to_string()) + } + } + + fn block_exists(&self, block: BasicBlockId) -> bool { + // 📦 Hotfix 2: Check if block exists in current function's CFG + if let Some(ref func) = self.parent_builder.current_function { + func.blocks.contains_key(&block) + } else { + false + } + } + + fn get_block_predecessors( + &self, + block: BasicBlockId, + ) -> std::collections::HashSet { + // 📦 Hotfix 6: Get actual CFG predecessors for PHI validation + if let Some(ref func) = self.parent_builder.current_function { + if let Some(bb) = func.blocks.get(&block) { + bb.predecessors.clone() + } else { + std::collections::HashSet::new() // Non-existent blocks have no predecessors + } + } else { + std::collections::HashSet::new() + } + } + + /// Phase 26-A-4: ValueIdベースのパラメータ判定(GUARD Bug Prevention) + /// + /// 旧実装(名前ベース)の問題点: + /// - ValueId(0) を「常に未初期化」と誤判定 + /// - パラメータ s=ValueId(0) も弾いてしまうGUARDバグ + /// + /// 新実装(型ベース)の利点: + /// - MirValueKindで型安全判定 + /// - ValueId(0)でもParameter(0)なら正しく判定 + fn is_parameter(&self, value_id: ValueId) -> bool { + // Phase 26-A-4: 型安全なパラメータ判定を使用 + // parent_builder.is_value_parameter() は Phase 26-A-2 で実装済み + let is_param = self.parent_builder.is_value_parameter(value_id); + + if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + eprintln!( + "[is_parameter] ValueId({}) -> {} (kind = {:?})", + value_id.0, + is_param, + self.parent_builder.get_value_kind(value_id) + ); + } + + is_param + } + + fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> { + self.parent_builder.start_new_block(block) + } + + fn emit_copy(&mut self, dst: ValueId, src: ValueId) -> Result<(), String> { + self.parent_builder + .emit_instruction(MirInstruction::Copy { dst, src }) + } + + fn emit_jump(&mut self, target: BasicBlockId) -> Result<(), String> { + self.emit_jump(target) + } + + fn emit_phi( + &mut self, + dst: ValueId, + inputs: Vec<(BasicBlockId, ValueId)>, + ) -> Result<(), String> { + self.emit_phi_at_block_start(self.current_block()?, dst, inputs) + } + + fn update_phi_inputs( + &mut self, + block: BasicBlockId, + phi_id: ValueId, + inputs: Vec<(BasicBlockId, ValueId)>, + ) -> Result<(), String> { + self.parent_builder + .update_phi_instruction(block, phi_id, inputs) + } + + fn update_var(&mut self, name: String, value: ValueId) { + self.parent_builder.variable_map.insert(name, value); + } + + fn get_variable_at_block(&self, name: &str, block: BasicBlockId) -> Option { + // Use the inherent method to avoid recursion + LoopBuilder::get_variable_at_block(self, name, block) + } + + fn mir_function(&self) -> &crate::mir::MirFunction { + self.parent_builder + .current_function + .as_ref() + .expect("LoopBuilder requires current_function") + } +} + +// Phase 26-E-3: PhiBuilderOps 委譲実装(has-a設計) +// +// **Design Rationale:** +// - PhiBuilderOps = 低レベル「PHI命令発行」道具箱 +// - LoopFormOps = 高レベル「ループ構造構築」作業場 +// - 関係: has-a(委譲) - LoopBuilder は両方のインターフェースを提供 +// +// **実装方針:** +// - LoopFormOps の既存実装に委譲(重複なし) +// - HashSet → Vec 変換 + ソート(決定性保証) +// - emit_phi: block 引数差を吸収(current_block 設定) +impl<'a> crate::mir::phi_core::phi_builder_box::PhiBuilderOps for LoopBuilder<'a> { + fn new_value(&mut self) -> ValueId { + // 委譲: LoopFormOps の既存実装を使用 + ::new_value(self) + } + + fn emit_phi( + &mut self, + block: BasicBlockId, + dst: ValueId, + inputs: Vec<(BasicBlockId, ValueId)>, + ) -> Result<(), String> { + // PhiBuilderOps: block 引数あり + // LoopFormOps: block 引数なし(current_block 使用) + // 差を吸収: current_block を設定してから LoopFormOps::emit_phi を呼ぶ + ::set_current_block(self, block)?; + ::emit_phi(self, dst, inputs) + } + + fn update_var(&mut self, name: String, value: ValueId) { + // 委譲: LoopFormOps の既存実装を使用 + ::update_var(self, name, value) + } + + fn get_block_predecessors(&self, block: BasicBlockId) -> Vec { + // LoopFormOps: HashSet 返却 + // PhiBuilderOps: Vec 返却 + // 変換: HashSet → Vec + ソート(決定性保証) + let pred_set = ::get_block_predecessors(self, block); + let mut pred_vec: Vec = pred_set.into_iter().collect(); + pred_vec.sort_by_key(|bb| bb.0); // bb.0 = BasicBlockId の内部値 + pred_vec + } + + fn emit_void(&mut self) -> ValueId { + // void 定数を発行 + let void_id = ::new_value(self); + let _ = self.emit_const(void_id, ConstValue::Void); + void_id + } + + fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> { + // 委譲: LoopFormOps の既存実装を使用 + ::set_current_block(self, block) + } + + fn block_exists(&self, block: BasicBlockId) -> bool { + // 委譲: LoopFormOps の既存実装を使用 + ::block_exists(self, block) + } +} diff --git a/src/mir/loop_builder/statements.rs b/src/mir/loop_builder/statements.rs new file mode 100644 index 00000000..8e78f7c0 --- /dev/null +++ b/src/mir/loop_builder/statements.rs @@ -0,0 +1,39 @@ +use super::{LoopBuilder, ValueId}; +use crate::ast::ASTNode; +use crate::mir::utils::is_current_block_terminated; +use crate::mir::ConstValue; + +impl<'a> LoopBuilder<'a> { + pub(super) fn build_statement(&mut self, stmt: ASTNode) -> Result { + // Preserve the originating span for loop-local control instructions (break/continue/phi). + self.parent_builder.current_span = stmt.span(); + match stmt { + // Ensure nested bare blocks inside loops are lowered with loop-aware semantics + ASTNode::Program { statements, .. } => { + let mut last = None; + for s in statements.into_iter() { + last = Some(self.build_statement(s)?); + // フェーズS修正:統一終端検出ユーティリティ使用 + if is_current_block_terminated(self.parent_builder)? { + break; + } + } + Ok(last.unwrap_or_else(|| { + let void_id = self.new_value(); + // Emit a void const to keep SSA consistent when block is empty + let _ = self.emit_const(void_id, ConstValue::Void); + void_id + })) + } + ASTNode::If { + condition, + then_body, + else_body, + .. + } => self.lower_if_in_loop(*condition, then_body, else_body), + ASTNode::Break { .. } => self.do_break(), + ASTNode::Continue { .. } => self.do_continue(), + other => self.parent_builder.build_expression(other), + } + } +} diff --git a/src/tests/joinir/mainline_phase49.rs b/src/tests/joinir/mainline_phase49.rs index c75f02c0..faa1e69e 100644 --- a/src/tests/joinir/mainline_phase49.rs +++ b/src/tests/joinir/mainline_phase49.rs @@ -61,8 +61,7 @@ static box Main { } "#; - let ast: ASTNode = NyashParser::parse_from_string(src) - .expect("phase49: parse failed"); + let ast: ASTNode = NyashParser::parse_from_string(src).expect("phase49: parse failed"); let mut mc = MirCompiler::with_options(false); let result = mc.compile(ast); @@ -117,8 +116,7 @@ static box Main { } "#; - let ast: ASTNode = NyashParser::parse_from_string(src) - .expect("phase49 fallback: parse failed"); + let ast: ASTNode = NyashParser::parse_from_string(src).expect("phase49 fallback: parse failed"); let mut mc = MirCompiler::with_options(false); let result = mc.compile(ast); @@ -172,8 +170,8 @@ static box Main { std::env::set_var("HAKO_PARSER_STAGE3", "1"); std::env::set_var("NYASH_DISABLE_PLUGINS", "1"); - let ast_a: ASTNode = NyashParser::parse_from_string(src) - .expect("phase49 A/B: parse failed (Route A)"); + let ast_a: ASTNode = + NyashParser::parse_from_string(src).expect("phase49 A/B: parse failed (Route A)"); let mut mc_a = MirCompiler::with_options(false); let result_a = mc_a.compile(ast_a); assert!( @@ -182,11 +180,7 @@ static box Main { result_a.err() ); let module_a = result_a.unwrap().module; - let blocks_a: usize = module_a - .functions - .values() - .map(|f| f.blocks.len()) - .sum(); + let blocks_a: usize = module_a.functions.values().map(|f| f.blocks.len()).sum(); // Route B: JoinIR Frontend path (flag ON) // Re-set parser flags to ensure they're active @@ -195,8 +189,8 @@ static box Main { std::env::set_var("NYASH_DISABLE_PLUGINS", "1"); std::env::set_var("HAKO_JOINIR_PRINT_TOKENS_MAIN", "1"); - let ast_b: ASTNode = NyashParser::parse_from_string(src) - .expect("phase49 A/B: parse failed (Route B)"); + let ast_b: ASTNode = + NyashParser::parse_from_string(src).expect("phase49 A/B: parse failed (Route B)"); let mut mc_b = MirCompiler::with_options(false); let result_b = mc_b.compile(ast_b); assert!( @@ -205,11 +199,7 @@ static box Main { result_b.err() ); let module_b = result_b.unwrap().module; - let blocks_b: usize = module_b - .functions - .values() - .map(|f| f.blocks.len()) - .sum(); + let blocks_b: usize = module_b.functions.values().map(|f| f.blocks.len()).sum(); // Log block counts for debugging eprintln!( @@ -269,8 +259,7 @@ static box Main { } "#; - let ast: ASTNode = NyashParser::parse_from_string(src) - .expect("phase49-4: parse failed"); + let ast: ASTNode = NyashParser::parse_from_string(src).expect("phase49-4: parse failed"); let mut mc = MirCompiler::with_options(false); let result = mc.compile(ast); @@ -323,8 +312,8 @@ static box Main { } "#; - let ast: ASTNode = NyashParser::parse_from_string(src) - .expect("phase49-4 fallback: parse failed"); + let ast: ASTNode = + NyashParser::parse_from_string(src).expect("phase49-4 fallback: parse failed"); let mut mc = MirCompiler::with_options(false); let result = mc.compile(ast); @@ -375,8 +364,8 @@ static box Main { std::env::set_var("HAKO_PARSER_STAGE3", "1"); std::env::set_var("NYASH_DISABLE_PLUGINS", "1"); - let ast_a: ASTNode = NyashParser::parse_from_string(src) - .expect("phase49-4 A/B: parse failed (Route A)"); + let ast_a: ASTNode = + NyashParser::parse_from_string(src).expect("phase49-4 A/B: parse failed (Route A)"); let mut mc_a = MirCompiler::with_options(false); let result_a = mc_a.compile(ast_a); assert!( @@ -385,11 +374,7 @@ static box Main { result_a.err() ); let module_a = result_a.unwrap().module; - let blocks_a: usize = module_a - .functions - .values() - .map(|f| f.blocks.len()) - .sum(); + let blocks_a: usize = module_a.functions.values().map(|f| f.blocks.len()).sum(); // Route B: JoinIR Frontend path (flag ON) std::env::set_var("NYASH_PARSER_STAGE3", "1"); @@ -397,8 +382,8 @@ static box Main { std::env::set_var("NYASH_DISABLE_PLUGINS", "1"); std::env::set_var("HAKO_JOINIR_ARRAY_FILTER_MAIN", "1"); - let ast_b: ASTNode = NyashParser::parse_from_string(src) - .expect("phase49-4 A/B: parse failed (Route B)"); + let ast_b: ASTNode = + NyashParser::parse_from_string(src).expect("phase49-4 A/B: parse failed (Route B)"); let mut mc_b = MirCompiler::with_options(false); let result_b = mc_b.compile(ast_b); assert!( @@ -407,11 +392,7 @@ static box Main { result_b.err() ); let module_b = result_b.unwrap().module; - let blocks_b: usize = module_b - .functions - .values() - .map(|f| f.blocks.len()) - .sum(); + let blocks_b: usize = module_b.functions.values().map(|f| f.blocks.len()).sum(); // Log block counts for debugging eprintln!( diff --git a/src/tests/llvm_bitops_test.rs b/src/tests/llvm_bitops_test.rs index 9225cde3..c66456c0 100644 --- a/src/tests/llvm_bitops_test.rs +++ b/src/tests/llvm_bitops_test.rs @@ -2,8 +2,8 @@ fn llvm_bitops_compile_and_exec() { use crate::backend::VM; use crate::mir::{ - BinaryOp, BasicBlockId, ConstValue, FunctionSignature, MirFunction, - MirInstruction, MirModule, MirType, + BasicBlockId, BinaryOp, ConstValue, FunctionSignature, MirFunction, MirInstruction, + MirModule, MirType, }; // Build MIR: compute sum of bitwise/shift ops -> 48