diff --git a/src/mir/builder/control_flow/joinir/merge/carrier_init_builder.rs b/src/mir/builder/control_flow/joinir/merge/carrier_init_builder.rs new file mode 100644 index 00000000..ea5f2217 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/merge/carrier_init_builder.rs @@ -0,0 +1,197 @@ +/// CarrierInitBuilder: Centralized CarrierInit value generation +/// +/// # Purpose +/// Provides single source of truth for generating MIR values from CarrierInit enum. +/// Eliminates scattered match patterns across header PHI, exit line, boundary injection. +/// +/// # Phase 86 Context +/// - Consolidates ~100 lines of duplicated CarrierInit matching logic +/// - Used by: loop_header_phi_builder, exit_line/meta_collector, boundary_builder +/// - Ensures consistent const generation and debug output +/// +/// # Design Principles +/// - **SSOT**: Single function for all CarrierInit → ValueId generation +/// - **Testability**: Pure function, easy to unit test +/// - **Consistency**: Uniform debug output format + +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::lowering::carrier_info::CarrierInit; +use crate::mir::value_id::ValueId; +use crate::mir::types::ConstValue; +use crate::mir::MirInstruction; + +/// Generate a ValueId for the given CarrierInit policy +/// +/// # Arguments +/// * `builder` - MIR builder for emitting const instructions +/// * `init` - Initialization policy (FromHost, BoolConst, LoopLocalZero) +/// * `host_id` - Host variable's ValueId (used for FromHost) +/// * `name` - Carrier variable name (for debug output) +/// * `debug` - Enable debug output +/// +/// # Returns +/// * `ValueId` - Either host_id (FromHost) or newly emitted const +/// +/// # Examples +/// ``` +/// // FromHost: Returns host_id directly +/// let value = init_value(&mut builder, &CarrierInit::FromHost, host_id, "counter", false); +/// // value == host_id +/// +/// // BoolConst: Emits new const instruction +/// let value = init_value(&mut builder, &CarrierInit::BoolConst(true), host_id, "flag", true); +/// // Emits: %N = Const { dst: ValueId(N), value: Bool(true) } +/// // Debug: "[carrier_init_builder] 'flag': BoolConst(true) -> ValueId(N)" +/// +/// // LoopLocalZero: Emits Integer(0) const +/// let value = init_value(&mut builder, &CarrierInit::LoopLocalZero, host_id, "digit", false); +/// // Emits: %N = Const { dst: ValueId(N), value: Integer(0) } +/// ``` +pub fn init_value( + builder: &mut MirBuilder, + init: &CarrierInit, + host_id: ValueId, + name: &str, + debug: bool, +) -> ValueId { + match init { + CarrierInit::FromHost => { + // Use host variable's ValueId directly (no const emission needed) + if debug { + eprintln!( + "[carrier_init_builder] '{}': FromHost -> ValueId({})", + name, host_id.0 + ); + } + host_id + } + CarrierInit::BoolConst(val) => { + // Generate explicit bool constant (used for ConditionOnly carriers) + let const_id = builder.next_value_id(); + let _ = builder.emit_instruction(MirInstruction::Const { + dst: const_id, + value: ConstValue::Bool(*val), + }); + if debug { + eprintln!( + "[carrier_init_builder] '{}': BoolConst({}) -> ValueId({})", + name, val, const_id.0 + ); + } + const_id + } + CarrierInit::LoopLocalZero => { + // Generate Integer(0) const for loop-local derived carriers (no host slot) + let const_id = builder.next_value_id(); + let _ = builder.emit_instruction(MirInstruction::Const { + dst: const_id, + value: ConstValue::Integer(0), + }); + if debug { + eprintln!( + "[carrier_init_builder] '{}': LoopLocalZero -> ValueId({})", + name, const_id.0 + ); + } + const_id + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Test FromHost returns host_id directly without emitting instructions + #[test] + fn test_from_host_returns_host_id() { + let mut builder = MirBuilder::new(); + let host_id = ValueId(42); + + let result = init_value(&mut builder, &CarrierInit::FromHost, host_id, "test", false); + + assert_eq!(result, host_id, "FromHost should return host_id directly"); + // No instruction should be emitted for FromHost + } + + /// Test BoolConst(true) emits new ValueId (not host_id) + #[test] + fn test_bool_const_true_emits_new_value() { + let mut builder = MirBuilder::new(); + let host_id = ValueId(999); // Dummy host_id (not used for BoolConst) + + let result = init_value(&mut builder, &CarrierInit::BoolConst(true), host_id, "test", false); + + assert_ne!(result, host_id, "BoolConst should emit new ValueId"); + } + + /// Test BoolConst(false) emits new ValueId + #[test] + fn test_bool_const_false_emits_new_value() { + let mut builder = MirBuilder::new(); + let host_id = ValueId(999); + + let result = init_value(&mut builder, &CarrierInit::BoolConst(false), host_id, "flag", false); + + assert_ne!(result, host_id, "BoolConst should emit new ValueId"); + } + + /// Test LoopLocalZero emits Integer(0) const + #[test] + fn test_loop_local_zero_emits_new_value() { + let mut builder = MirBuilder::new(); + let host_id = ValueId(999); + + let result = init_value(&mut builder, &CarrierInit::LoopLocalZero, host_id, "digit", false); + + assert_ne!(result, host_id, "LoopLocalZero should emit new ValueId"); + } + + /// Test multiple calls produce different ValueIds + #[test] + fn test_multiple_calls_produce_unique_values() { + let mut builder = MirBuilder::new(); + let host_id = ValueId(100); + + let result1 = init_value(&mut builder, &CarrierInit::BoolConst(true), host_id, "flag1", false); + let result2 = init_value(&mut builder, &CarrierInit::BoolConst(false), host_id, "flag2", false); + let result3 = init_value(&mut builder, &CarrierInit::LoopLocalZero, host_id, "counter", false); + + assert_ne!(result1, result2, "Different BoolConst calls should produce different ValueIds"); + assert_ne!(result2, result3, "BoolConst and LoopLocalZero should produce different ValueIds"); + assert_ne!(result1, result3, "All ValueIds should be unique"); + } + + /// Test debug output doesn't crash (no assertion, just execution) + #[test] + fn test_debug_output_from_host() { + let mut builder = MirBuilder::new(); + let host_id = ValueId(42); + + let _ = init_value(&mut builder, &CarrierInit::FromHost, host_id, "debug_test", true); + // Expected stderr output: "[carrier_init_builder] 'debug_test': FromHost -> ValueId(42)" + // (This test just verifies it doesn't crash) + } + + /// Test debug output for BoolConst doesn't crash + #[test] + fn test_debug_output_bool_const() { + let mut builder = MirBuilder::new(); + let host_id = ValueId(999); + + let _result = init_value(&mut builder, &CarrierInit::BoolConst(true), host_id, "debug_bool", true); + // Expected stderr output: "[carrier_init_builder] 'debug_bool': BoolConst(true) -> ValueId(N)" + // (This test just verifies it doesn't crash) + } + + /// Test debug output for LoopLocalZero doesn't crash + #[test] + fn test_debug_output_loop_local_zero() { + let mut builder = MirBuilder::new(); + let host_id = ValueId(999); + + let _result = init_value(&mut builder, &CarrierInit::LoopLocalZero, host_id, "debug_zero", true); + // Expected stderr output: "[carrier_init_builder] 'debug_zero': LoopLocalZero -> ValueId(N)" + // (This test just verifies it doesn't crash) + } +} diff --git a/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs b/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs index fbc2e9b4..3610f86d 100644 --- a/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs +++ b/src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs @@ -114,40 +114,14 @@ impl LoopHeaderPhiBuilder { // Allocate PHIs for other carriers for (name, host_id, init, role) in carriers { - // Phase 228-5: Generate explicit const for BoolConst, use host_id for FromHost - let init_value = match init { - crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost => *host_id, - crate::mir::join_ir::lowering::carrier_info::CarrierInit::BoolConst(val) => { - // Phase 228: Generate explicit bool constant for ConditionOnly carriers - let const_id = builder.next_value_id(); - let _ = builder.emit_instruction(MirInstruction::Const { - dst: const_id, - value: crate::mir::types::ConstValue::Bool(*val), - }); - if debug { - eprintln!( - "[cf_loop/joinir] Phase 228: Generated const {:?} = Bool({}) for ConditionOnly carrier '{}'", - const_id, val, name - ); - } - const_id - } - crate::mir::join_ir::lowering::carrier_info::CarrierInit::LoopLocalZero => { - // Loop-local derived carrier (e.g., digit_value) starts from 0 inside the loop - let const_id = builder.next_value_id(); - let _ = builder.emit_instruction(MirInstruction::Const { - dst: const_id, - value: crate::mir::types::ConstValue::Integer(0), - }); - if debug { - eprintln!( - "[cf_loop/joinir] Phase 247-EX: Generated const {:?} = Int(0) for loop-local carrier '{}'", - const_id, name - ); - } - const_id - } - }; + // Phase 86: Use centralized CarrierInit builder + let init_value = super::carrier_init_builder::init_value( + builder, + &init, + *host_id, + &name, + debug, + ); let phi_dst = builder.next_value_id(); diff --git a/src/mir/builder/control_flow/joinir/merge/mod.rs b/src/mir/builder/control_flow/joinir/merge/mod.rs index 8ccabff6..b9460ae8 100644 --- a/src/mir/builder/control_flow/joinir/merge/mod.rs +++ b/src/mir/builder/control_flow/joinir/merge/mod.rs @@ -13,6 +13,7 @@ //! Phase 4 Refactoring: Breaking down 714-line merge_joinir_mir_blocks() into focused modules mod block_allocator; +mod carrier_init_builder; #[cfg(debug_assertions)] mod contract_checks; pub mod exit_line; diff --git a/src/mir/builder/control_flow/mod.rs b/src/mir/builder/control_flow/mod.rs index 1765c8b7..4aa94b30 100644 --- a/src/mir/builder/control_flow/mod.rs +++ b/src/mir/builder/control_flow/mod.rs @@ -110,12 +110,13 @@ impl super::MirBuilder { // Phase 186: LoopBuilder Hard Freeze - Legacy path disabled // Phase 187-2: LoopBuilder module removed - all loops must use JoinIR - return Err(format!( - "[joinir/freeze] Loop lowering failed: JoinIR does not support this pattern, and LoopBuilder has been removed.\n\ + use crate::mir::join_ir::lowering::error_tags; + return Err(error_tags::freeze(&format!( + "Loop lowering failed: JoinIR does not support this pattern, and LoopBuilder has been removed.\n\ Function: {}\n\ Hint: This loop pattern is not supported. All loops must use JoinIR lowering.", self.current_function.as_ref().map(|f| f.signature.name.as_str()).unwrap_or("") - )); + ))); } /// Phase 49: Try JoinIR Frontend for mainline integration diff --git a/src/mir/join_ir/lowering/carrier_info.rs b/src/mir/join_ir/lowering/carrier_info.rs index 9504f767..37102c0d 100644 --- a/src/mir/join_ir/lowering/carrier_info.rs +++ b/src/mir/join_ir/lowering/carrier_info.rs @@ -650,8 +650,14 @@ impl CarrierInfo { /// we integrate BindingId tracking into the promotion pipeline. #[cfg(feature = "normalized_dev")] pub fn record_promoted_binding(&mut self, original_binding: BindingId, promoted_binding: BindingId) { - use crate::config::env::is_joinir_debug; - if is_joinir_debug() || std::env::var("JOINIR_TEST_DEBUG").is_ok() { + use super::debug_output_box::DebugOutputBox; + + // Phase 86: Use DebugOutputBox for consistent debug output + // Allow JOINIR_TEST_DEBUG override for test-specific diagnostics + let test_debug = std::env::var("JOINIR_TEST_DEBUG").is_ok(); + let debug = DebugOutputBox::new("binding_pilot/promoted_bindings"); + + if debug.is_enabled() || test_debug { eprintln!( "[binding_pilot/promoted_bindings] {} → {}", original_binding, promoted_binding diff --git a/src/mir/join_ir/lowering/error_tags.rs b/src/mir/join_ir/lowering/error_tags.rs new file mode 100644 index 00000000..b3c4e1ab --- /dev/null +++ b/src/mir/join_ir/lowering/error_tags.rs @@ -0,0 +1,136 @@ +//! Phase 86: Centralized JoinIR error tag generation +//! +//! ## Purpose +//! Provides SSOT for error message formatting to ensure consistency across +//! all JoinIR lowering error paths. +//! +//! ## Benefits +//! - **Consistency**: All error tags use standardized formatting +//! - **Discoverability**: Easy to audit all error types in one place +//! - **Typo prevention**: Compiler catches typos vs. string literals +//! - **Maintenance**: Change tag format in one place +//! +//! ## Usage +//! ```rust,ignore +//! // Before: +//! return Err(format!("[joinir/freeze] Pattern not supported: {}", reason)); +//! +//! // After: +//! use crate::mir::join_ir::lowering::error_tags; +//! return Err(error_tags::freeze(&format!("Pattern not supported: {}", reason))); +//! ``` + +/// JoinIR freeze error - Pattern not supported by current lowering implementation +/// +/// Used when a JoinIR pattern cannot be lowered to MIR due to limitations +/// in the current implementation. +/// +/// # Example +/// ```rust,ignore +/// return Err(freeze("Loop lowering failed: unsupported break pattern")); +/// // Output: "[joinir/freeze] Loop lowering failed: unsupported break pattern" +/// ``` +pub fn freeze(diagnostic: &str) -> String { + format!("[joinir/freeze] {}", diagnostic) +} + +/// ExitLine contract violation - Phase 81+ exit metadata contract broken +/// +/// Used when exit line reconnection or exit PHI construction violates +/// the established contract (e.g., missing carrier in variable_map). +/// +/// # Example +/// ```rust,ignore +/// return Err(exit_line_contract("Carrier missing from variable_map")); +/// // Output: "[JoinIR/ExitLine/Contract] Carrier missing from variable_map" +/// ``` +pub fn exit_line_contract(detail: &str) -> String { + format!("[JoinIR/ExitLine/Contract] {}", detail) +} + +/// Ownership relay runtime unsupported - Multi-hop relay not executable +/// +/// Used when ownership plan validation detects a multi-hop relay pattern +/// that cannot be executed at runtime (implementation limitation). +/// +/// # Example +/// ```rust,ignore +/// return Err(ownership_relay_unsupported(&format!( +/// "Multihop relay: var='{}', path={:?}", var, path +/// ))); +/// // Output: "[ownership/relay:runtime_unsupported] Multihop relay: var='x', path=[...]" +/// ``` +pub fn ownership_relay_unsupported(diagnostic: &str) -> String { + format!("[ownership/relay:runtime_unsupported] {}", diagnostic) +} + +/// Pattern detection failure - JoinIR pattern could not be classified +/// +/// Used when pattern detection fails to match a loop structure to any +/// known Pattern 1-4 template. +/// +/// # Example +/// ```rust,ignore +/// return Err(pattern_detection_failed("pattern3", "Missing if-phi")); +/// // Output: "[joinir/pattern/pattern3] Detection failed: Missing if-phi" +/// ``` +pub fn pattern_detection_failed(pattern_name: &str, reason: &str) -> String { + format!( + "[joinir/pattern/{}] Detection failed: {}", + pattern_name, reason + ) +} + +/// Generic JoinIR lowering error with custom tag +/// +/// Used for one-off error cases that don't fit the common patterns. +/// +/// # Example +/// ```rust,ignore +/// return Err(lowering_error("boundary/injection", "Invalid boundary block")); +/// // Output: "[joinir/lowering/boundary/injection] Invalid boundary block" +/// ``` +pub fn lowering_error(subsystem: &str, detail: &str) -> String { + format!("[joinir/lowering/{}] {}", subsystem, detail) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_freeze_tag() { + let err = freeze("Test error"); + assert!(err.starts_with("[joinir/freeze]")); + assert!(err.contains("Test error")); + } + + #[test] + fn test_exit_line_contract_tag() { + let err = exit_line_contract("Missing carrier"); + assert!(err.starts_with("[JoinIR/ExitLine/Contract]")); + assert!(err.contains("Missing carrier")); + } + + #[test] + fn test_ownership_relay_unsupported_tag() { + let err = ownership_relay_unsupported("Multihop not supported"); + assert!(err.starts_with("[ownership/relay:runtime_unsupported]")); + assert!(err.contains("Multihop not supported")); + } + + #[test] + fn test_pattern_detection_failed_tag() { + let err = pattern_detection_failed("pattern2", "Missing break"); + assert!(err.contains("[joinir/pattern/pattern2]")); + assert!(err.contains("Detection failed")); + assert!(err.contains("Missing break")); + } + + #[test] + fn test_lowering_error_tag() { + let err = lowering_error("boundary", "Invalid block"); + assert!(err.contains("[joinir/lowering/boundary]")); + assert!(err.contains("Invalid block")); + } +} diff --git a/src/mir/join_ir/lowering/mod.rs b/src/mir/join_ir/lowering/mod.rs index 2149a21f..590a5f1e 100644 --- a/src/mir/join_ir/lowering/mod.rs +++ b/src/mir/join_ir/lowering/mod.rs @@ -26,6 +26,7 @@ pub(crate) mod carrier_update_emitter; // Phase 179: Carrier update instruction pub(crate) mod common; // Internal lowering utilities pub mod complex_addend_normalizer; // Phase 192: Complex addend normalization (AST preprocessing) pub mod debug_output_box; // Phase 85: Centralized debug output management +pub mod error_tags; // Phase 86: Centralized error message formatting pub mod condition_env; // Phase 171-fix: Condition expression environment pub(crate) mod condition_lowerer; // Phase 171-fix: Core condition lowering logic pub mod condition_lowering_box; // Phase 244: Unified condition lowering interface (trait-based) diff --git a/src/mir/join_ir/ownership/plan_validator.rs b/src/mir/join_ir/ownership/plan_validator.rs index c1fa42cb..800c7ec1 100644 --- a/src/mir/join_ir/ownership/plan_validator.rs +++ b/src/mir/join_ir/ownership/plan_validator.rs @@ -72,10 +72,11 @@ impl OwnershipPlanValidator { // Multihop: check if it's a supported pattern if !Self::is_supported_multihop_pattern(plan, relay) { - return Err(format!( - "[ownership/relay:runtime_unsupported] Multihop relay not executable yet: var='{}', owner={:?}, relay_path={:?}", + use crate::mir::join_ir::lowering::error_tags; + return Err(error_tags::ownership_relay_unsupported(&format!( + "Multihop relay not executable yet: var='{}', owner={:?}, relay_path={:?}", relay.name, relay.owner_scope, relay.relay_path - )); + ))); } } Ok(())