diff --git a/docs/reference/environment-variables.md b/docs/reference/environment-variables.md index 88ba5169..ffa41c9c 100644 --- a/docs/reference/environment-variables.md +++ b/docs/reference/environment-variables.md @@ -210,6 +210,8 @@ env NYASH_FEATURES=stage3 NYASH_LLVM_USE_HARNESS=1 \ | `NYASH_VERIFY_EDGE_COPY_STRICT=1` | OFF | Any | Edge copy 検証を厳格化 | | `NYASH_VERIFY_RET_PURITY=1` | OFF | Any | return ブロックの純粋性検証 | | `NYASH_ME_CALL_ARITY_STRICT=1` | OFF | Any | me.method の arity 不一致でエラー | +| `NYASH_MIR_DISABLE_OPT=1` | OFF | Any | MIR Optimizer 全体を無効化(開発/診断用、`src/mir/optimizer.rs`) | +| `NYASH_TRACE_VARMAP=1` | OFF | Any | `MirBuilder.variable_map` の状態をトレース出力(`[varmap/] {name=ValueId(..),..}`)。JoinIR loop 統合のデバッグ用。 | --- diff --git a/src/mir/builder/control_flow.rs b/src/mir/builder/control_flow.rs index ced627ef..6a14580f 100644 --- a/src/mir/builder/control_flow.rs +++ b/src/mir/builder/control_flow.rs @@ -792,13 +792,19 @@ impl super::MirBuilder { } // Merge JoinIR blocks into current function - // Phase 188-Impl-3: Create and pass JoinInlineBoundary for Pattern 3 + // Phase 190: Use explicit LoopExitBinding for Pattern 3 // Pattern 3 has TWO carriers: i and sum self.trace_varmap("pattern3_before_merge"); - let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_with_input_and_host_outputs( + let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_with_exit_bindings( vec![ValueId(0), ValueId(1)], // JoinIR's main() parameters (i, sum init) vec![loop_var_id, sum_var_id], // Host's loop variables - vec![sum_var_id], // Host output slot to be updated with exit PHI + vec![ + crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding { + carrier_name: "sum".to_string(), + join_exit_value: ValueId(18), // k_exit's parameter (sum_final) + host_slot: sum_var_id, // variable_map["sum"] + } + ], ); let exit_phi_result = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?; self.trace_varmap("pattern3_after_merge"); @@ -1337,32 +1343,41 @@ impl super::MirBuilder { None }; - // Phase 189-Fix: Store exit PHI result in variable_map so host code can reference it - // The loop result should update the corresponding carrier variable(s) declared - // in JoinInlineBoundary.host_outputs. + // Phase 190: Use explicit LoopExitBinding to reconnect exit PHI to variable_map + // Each binding explicitly names the carrier variable and maps exit PHI to it. if let Some(phi_result) = exit_phi_result_id { if let Some(ref boundary) = boundary { - // Phase 189-Refine: 現時点では単一出力 (Pattern 3 の sum) のみを想定し、 - // host_outputs に登録された ValueId と同じ値を持つ variable_map の - // エントリを exit PHI 結果に差し替える。 - // - // 将来 Multi-carrier を扱う際は、join_outputs と host_outputs の - // ペアに対して同様の処理を行う。 - for &host_out in &boundary.host_outputs { - // variable_map は name → ValueId のマップなので、 - // 値が host_out になっているものを exit PHI に更新する。 - for (name, vid) in self.variable_map.iter_mut() { - if *vid == host_out { + // Phase 190: Use exit_bindings for explicit carrier naming + // This eliminates ambiguity about which variable is being updated + for binding in &boundary.exit_bindings { + // Find the variable in variable_map that matches the binding's host_slot + for (var_name, vid) in self.variable_map.iter_mut() { + if *vid == binding.host_slot { *vid = phi_result; if debug { eprintln!( - "[cf_loop/joinir] Updated variable_map['{}'] to exit PHI {:?}", - name, phi_result + "[cf_loop/joinir] Phase 190: Reconnected exit PHI {:?} to variable_map['{}'] (carrier: {})", + phi_result, var_name, binding.carrier_name + ); + } + // Validate carrier name matches + if var_name != &binding.carrier_name && debug { + eprintln!( + "[cf_loop/joinir] WARNING: Carrier name mismatch: expected '{}', found '{}'", + binding.carrier_name, var_name ); } } } } + + // Phase 190: Backward compatibility - also check deprecated host_outputs + #[allow(deprecated)] + if !boundary.host_outputs.is_empty() && debug { + eprintln!( + "[cf_loop/joinir] WARNING: Using deprecated host_outputs. Migrate to exit_bindings." + ); + } } } diff --git a/src/mir/join_ir/lowering/inline_boundary.rs b/src/mir/join_ir/lowering/inline_boundary.rs index 6bafe773..459bb052 100644 --- a/src/mir/join_ir/lowering/inline_boundary.rs +++ b/src/mir/join_ir/lowering/inline_boundary.rs @@ -44,6 +44,55 @@ use crate::mir::ValueId; +/// Explicit binding between JoinIR exit value and host variable +/// +/// This structure formalizes the connection between a JoinIR exit PHI value +/// and the host variable it should update. This eliminates implicit assumptions +/// about which variable a ValueId represents. +/// +/// # Pattern 3 Example +/// +/// For `loop(i < 3) { sum = sum + i; i = i + 1 }`: +/// +/// ```text +/// LoopExitBinding { +/// carrier_name: "sum", +/// join_exit_value: ValueId(18), // k_exit's return value (JoinIR-local) +/// host_slot: ValueId(5), // variable_map["sum"] in host +/// } +/// ``` +/// +/// # Multi-Carrier Support (Pattern 4+) +/// +/// Multiple carriers can be represented as a vector: +/// +/// ```text +/// vec![ +/// LoopExitBinding { carrier_name: "sum", join_exit_value: ValueId(18), host_slot: ValueId(5) }, +/// LoopExitBinding { carrier_name: "count", join_exit_value: ValueId(19), host_slot: ValueId(6) }, +/// ] +/// ``` +#[derive(Debug, Clone)] +pub struct LoopExitBinding { + /// Carrier variable name (e.g., "sum", "count") + /// + /// This is the variable name in the host's variable_map that should + /// receive the exit value. + pub carrier_name: String, + + /// JoinIR-side ValueId from k_exit (or exit parameter) + /// + /// This is the **JoinIR-local** ValueId that represents the exit value. + /// It will be remapped when merged into the host function. + pub join_exit_value: ValueId, + + /// Host-side variable_map slot to reconnect + /// + /// This is the host function's ValueId for the variable that should be + /// updated with the exit PHI result. + pub host_slot: ValueId, +} + /// Boundary information for inlining a JoinIR fragment into a host function /// /// This structure captures the "interface" between a JoinIR fragment and the @@ -83,7 +132,9 @@ pub struct JoinInlineBoundary { /// (複数の変数を一度に返すループ) のために予約している。 pub join_outputs: Vec, - /// Host-function ValueIds that receive the outputs + /// Host-function ValueIds that receive the outputs (DEPRECATED) + /// + /// **DEPRECATED**: Use `exit_bindings` instead for explicit carrier naming. /// /// These are the destination ValueIds in the host function that should /// receive the values from join_outputs, or (Pattern 3 のような単一 @@ -93,7 +144,26 @@ pub struct JoinInlineBoundary { /// Phase 188-Impl-3 までは未使用だったが、Phase 189 で /// loop_if_phi.hako の sum のような「ループの出口で更新されるキャリア」の /// 再接続に利用する。 + #[deprecated(since = "Phase 190", note = "Use exit_bindings instead")] pub host_outputs: Vec, + + /// Explicit exit bindings for loop carriers (Phase 190+) + /// + /// Each binding explicitly names which variable is being updated and + /// where the value comes from. This eliminates ambiguity and prepares + /// for multi-carrier support. + /// + /// For Pattern 3 (single carrier "sum"): + /// ``` + /// exit_bindings: vec![ + /// LoopExitBinding { + /// carrier_name: "sum", + /// join_exit_value: ValueId(18), // k_exit return value + /// host_slot: ValueId(5), // variable_map["sum"] + /// } + /// ] + /// ``` + pub exit_bindings: Vec, } impl JoinInlineBoundary { @@ -112,17 +182,22 @@ impl JoinInlineBoundary { join_inputs, host_inputs, join_outputs: vec![], + #[allow(deprecated)] host_outputs: vec![], + exit_bindings: vec![], } } - /// Create a new boundary with both inputs and outputs + /// Create a new boundary with both inputs and outputs (DEPRECATED) + /// + /// **DEPRECATED**: Use `new_with_exit_bindings` instead. /// /// Reserved for future loop patterns that produce values. /// /// 現在の実装では Multi-carrier 出力には未対応だが、型としては複数出力を /// 表現できるようにしておく。 #[allow(dead_code)] + #[deprecated(since = "Phase 190", note = "Use new_with_exit_bindings instead")] pub fn new_with_outputs( join_inputs: Vec, host_inputs: Vec, @@ -143,11 +218,15 @@ impl JoinInlineBoundary { join_inputs, host_inputs, join_outputs, + #[allow(deprecated)] host_outputs, + exit_bindings: vec![], } } - /// Create a new boundary with inputs and **host outputs only** + /// Create a new boundary with inputs and **host outputs only** (DEPRECATED) + /// + /// **DEPRECATED**: Use `new_with_exit_bindings` instead for explicit carrier naming. /// /// JoinIR 側の exit 値 (k_exit の引数など) を 1 つの PHI にまとめ、 /// その PHI 結果をホスト側の変数スロットへ再接続したい場合に使う。 @@ -156,6 +235,7 @@ impl JoinInlineBoundary { /// - join_inputs : [i_init, sum_init] /// - host_inputs : [host_i, host_sum] /// - host_outputs : [host_sum] // ループ exit 時に上書きしたい変数 + #[deprecated(since = "Phase 190", note = "Use new_with_exit_bindings instead")] pub fn new_with_input_and_host_outputs( join_inputs: Vec, host_inputs: Vec, @@ -170,7 +250,63 @@ impl JoinInlineBoundary { join_inputs, host_inputs, join_outputs: vec![], + #[allow(deprecated)] host_outputs, + exit_bindings: vec![], + } + } + + /// Create a new boundary with explicit exit bindings (Phase 190+) + /// + /// This is the recommended constructor for loops with exit carriers. + /// Each exit binding explicitly names the carrier variable and its + /// source/destination values. + /// + /// # Example: Pattern 3 (single carrier) + /// + /// ```ignore + /// let boundary = JoinInlineBoundary::new_with_exit_bindings( + /// vec![ValueId(0), ValueId(1)], // join_inputs (i, sum init) + /// vec![loop_var_id, sum_var_id], // host_inputs + /// vec![ + /// LoopExitBinding { + /// carrier_name: "sum".to_string(), + /// join_exit_value: ValueId(18), // k_exit return value + /// host_slot: sum_var_id, // variable_map["sum"] + /// } + /// ], + /// ); + /// ``` + /// + /// # Example: Pattern 4+ (multiple carriers) + /// + /// ```ignore + /// let boundary = JoinInlineBoundary::new_with_exit_bindings( + /// vec![ValueId(0), ValueId(1), ValueId(2)], // join_inputs + /// vec![i_id, sum_id, count_id], // host_inputs + /// vec![ + /// LoopExitBinding { carrier_name: "sum".to_string(), ... }, + /// LoopExitBinding { carrier_name: "count".to_string(), ... }, + /// ], + /// ); + /// ``` + pub fn new_with_exit_bindings( + join_inputs: Vec, + host_inputs: Vec, + exit_bindings: Vec, + ) -> Self { + assert_eq!( + join_inputs.len(), + host_inputs.len(), + "join_inputs and host_inputs must have same length" + ); + Self { + join_inputs, + host_inputs, + join_outputs: vec![], + #[allow(deprecated)] + host_outputs: vec![], + exit_bindings, } } }