//! Carrier variable metadata for JoinIR loop lowering //! //! This module defines metadata structures for tracking carrier variables //! in loop lowering. This enables dynamic generation of exit bindings //! without hardcoded variable names or ValueIds. //! //! Phase 193-2: Enhanced builder methods for flexible construction use crate::mir::ValueId; use std::collections::HashMap; /// Information about a single carrier variable #[derive(Debug, Clone)] pub struct CarrierVar { /// Variable name (e.g., "sum", "printed") pub name: String, /// Host ValueId for this variable pub host_id: ValueId, } /// Complete carrier information for a loop #[derive(Debug, Clone)] pub struct CarrierInfo { /// Loop control variable name (e.g., "i") pub loop_var_name: String, /// Loop control variable ValueId in host pub loop_var_id: ValueId, /// Additional carrier variables (e.g., sum, printed) pub carriers: Vec, } impl CarrierInfo { /// Phase 193-2: Create CarrierInfo from a variable_map /// /// Automatically extracts all non-loop-control variables from the host's /// variable_map. This eliminates manual carrier listing for simple cases. /// /// # Arguments /// /// * `loop_var_name` - Name of the loop control variable (e.g., "i") /// * `variable_map` - Host function's variable_map (String → ValueId) /// /// # Returns /// /// CarrierInfo with loop_var and all other variables as carriers /// /// # Example /// /// ```ignore /// let carrier_info = CarrierInfo::from_variable_map( /// "i".to_string(), /// &variable_map // {"i": ValueId(5), "sum": ValueId(10), "count": ValueId(11)} /// )?; /// // Result: CarrierInfo with loop_var="i", carriers=[sum, count] /// ``` pub fn from_variable_map( loop_var_name: String, variable_map: &HashMap, ) -> Result { // Find loop variable let loop_var_id = variable_map .get(&loop_var_name) .copied() .ok_or_else(|| { format!( "Loop variable '{}' not found in variable_map", loop_var_name ) })?; // Collect all non-loop-var variables as carriers let mut carriers: Vec = variable_map .iter() .filter(|(name, _)| *name != &loop_var_name) .map(|(name, &id)| CarrierVar { name: name.clone(), host_id: id, }) .collect(); // Sort for determinism carriers.sort_by(|a, b| a.name.cmp(&b.name)); Ok(CarrierInfo { loop_var_name, loop_var_id, carriers, }) } /// Phase 193-2: Create CarrierInfo with explicit carrier list /// /// Useful when you have specific carriers in mind and want explicit control /// over which variables are treated as carriers. /// /// # Arguments /// /// * `loop_var_name` - Name of the loop control variable /// * `loop_var_id` - ValueId of the loop variable /// * `carrier_names` - Names of carrier variables (will look up in variable_map) /// * `variable_map` - Host function's variable_map for lookups /// /// # Returns /// /// CarrierInfo with only the specified carriers /// /// # Example /// /// ```ignore /// let carrier_info = CarrierInfo::with_explicit_carriers( /// "i".to_string(), /// ValueId(5), /// vec!["sum".to_string(), "count".to_string()], /// &variable_map /// )?; /// ``` pub fn with_explicit_carriers( loop_var_name: String, loop_var_id: ValueId, carrier_names: Vec, variable_map: &HashMap, ) -> Result { let mut carriers = Vec::new(); for name in carrier_names { let host_id = variable_map.get(&name).copied().ok_or_else(|| { format!("Carrier variable '{}' not found in variable_map", name) })?; carriers.push(CarrierVar { name, host_id, }); } // Sort for determinism carriers.sort_by(|a, b| a.name.cmp(&b.name)); Ok(CarrierInfo { loop_var_name, loop_var_id, carriers, }) } /// Phase 193-2: Create CarrierInfo with manual CarrierVar list /// /// Most explicit construction method - you provide everything directly. /// Useful when you already have CarrierVar structs built elsewhere. /// /// # Arguments /// /// * `loop_var_name` - Name of the loop control variable /// * `loop_var_id` - ValueId of the loop variable /// * `carriers` - Vec of already-constructed CarrierVar structs pub fn with_carriers( loop_var_name: String, loop_var_id: ValueId, mut carriers: Vec, ) -> Self { // Sort for determinism carriers.sort_by(|a, b| a.name.cmp(&b.name)); Self { loop_var_name, loop_var_id, carriers, } } /// Phase 193-2: Get carrier count /// /// Convenience method for checking how many carriers this info has. pub fn carrier_count(&self) -> usize { self.carriers.len() } /// Phase 193-2: Check if this has multiple carriers /// /// Useful for pattern matching: "is this a multi-carrier loop?" pub fn is_multi_carrier(&self) -> bool { self.carriers.len() > 1 } /// Phase 193-2: Find a carrier by name /// /// Lookup a specific carrier variable by name. pub fn find_carrier(&self, name: &str) -> Option<&CarrierVar> { self.carriers.iter().find(|c| c.name == name) } } /// Exit metadata returned by lowerers /// /// This structure captures the mapping from JoinIR exit values to /// carrier variable names, enabling dynamic binding generation. #[derive(Debug, Clone)] pub struct ExitMeta { /// Exit value bindings: (carrier_name, join_exit_value_id) /// /// Example for Pattern 4: /// ``` /// vec![("sum".to_string(), ValueId(15))] /// ``` /// where ValueId(15) is the k_exit parameter in JoinIR-local space. pub exit_values: Vec<(String, ValueId)>, } /// Phase 33-14: JoinFragmentMeta - Distinguishes expr result from carrier updates /// /// ## Purpose /// /// Separates two distinct use cases for JoinIR loops: /// /// 1. **Expr Result Pattern** (joinir_min_loop.hako): /// ```nyash /// local result = loop(...) { ... } // Loop used as expression /// return result /// ``` /// Here, the k_exit return value is the "expr result" that should go to exit_phi_inputs. /// /// 2. **Carrier Update Pattern** (trim pattern): /// ```nyash /// loop(...) { start = start + 1 } // Loop used for side effects /// print(start) // Use carrier after loop /// ``` /// Here, there's no "expr result" - only carrier variable updates. /// /// ## SSA Correctness /// /// Previously, exit_phi_inputs mixed expr results with carrier updates, causing: /// - PHI inputs that referenced undefined remapped values /// - SSA-undef errors in VM execution /// /// With JoinFragmentMeta: /// - `expr_result`: Only goes to exit_phi_inputs (generates PHI for expr value) /// - `exit_meta`: Only goes to carrier_inputs (updates variable_map via carrier PHIs) /// /// ## Example: Pattern 2 (joinir_min_loop.hako) /// /// ```rust /// JoinFragmentMeta { /// expr_result: Some(i_exit), // k_exit returns i as expr value /// exit_meta: ExitMeta::single("i".to_string(), i_exit), // Also a carrier /// } /// ``` /// /// ## Example: Pattern 3 (trim pattern) /// /// ```rust /// JoinFragmentMeta { /// expr_result: None, // Loop doesn't return a value /// exit_meta: ExitMeta::multiple(vec![ /// ("start".to_string(), start_exit), /// ("end".to_string(), end_exit), /// ]), /// } /// ``` #[derive(Debug, Clone)] pub struct JoinFragmentMeta { /// Expression result ValueId from k_exit (JoinIR-local) /// /// - `Some(vid)`: Loop is used as expression, k_exit's return value → exit_phi_inputs /// - `None`: Loop is used for side effects only, no PHI for expr value pub expr_result: Option, /// Carrier variable exit bindings (existing ExitMeta) /// /// Maps carrier names to their JoinIR-local exit values. /// These go to carrier_inputs for carrier PHI generation. pub exit_meta: ExitMeta, } impl JoinFragmentMeta { /// Create JoinFragmentMeta for expression result pattern /// /// Use when the loop returns a value (like `return loop(...)`). pub fn with_expr_result(expr_result: ValueId, exit_meta: ExitMeta) -> Self { Self { expr_result: Some(expr_result), exit_meta, } } /// Create JoinFragmentMeta for carrier-only pattern /// /// Use when the loop only updates carriers (like trim pattern). pub fn carrier_only(exit_meta: ExitMeta) -> Self { Self { expr_result: None, exit_meta, } } /// Create empty JoinFragmentMeta (no expr result, no carriers) pub fn empty() -> Self { Self { expr_result: None, exit_meta: ExitMeta::empty(), } } /// Check if this fragment has an expression result pub fn has_expr_result(&self) -> bool { self.expr_result.is_some() } /// Phase 33-14: Backward compatibility - convert to ExitMeta /// /// During migration, some code may still expect ExitMeta. /// This extracts just the carrier bindings. #[deprecated(since = "33-14", note = "Use exit_meta directly for carrier access")] pub fn to_exit_meta(&self) -> ExitMeta { self.exit_meta.clone() } } impl ExitMeta { /// Create new ExitMeta with no exit values pub fn empty() -> Self { Self { exit_values: vec![], } } /// Create ExitMeta with a single exit value pub fn single(carrier_name: String, join_value: ValueId) -> Self { Self { exit_values: vec![(carrier_name, join_value)], } } /// Create ExitMeta with multiple exit values pub fn multiple(exit_values: Vec<(String, ValueId)>) -> Self { Self { exit_values } } /// Phase 193-2: Get the count of exit bindings /// /// Useful for checking if this ExitMeta has any exit values. pub fn binding_count(&self) -> usize { self.exit_values.len() } /// Phase 193-2: Check if this has any exit values pub fn is_empty(&self) -> bool { self.exit_values.is_empty() } /// Phase 193-2: Find a binding by carrier name /// /// Lookup a specific exit value by carrier name. pub fn find_binding(&self, carrier_name: &str) -> Option { self.exit_values .iter() .find(|(name, _)| name == carrier_name) .map(|(_, value_id)| *value_id) } /// Phase 193-2: Add a binding to ExitMeta /// /// Convenient way to build ExitMeta incrementally. pub fn with_binding(mut self, carrier_name: String, join_value: ValueId) -> Self { self.exit_values.push((carrier_name, join_value)); self } }