//! Phase 188-Impl-3: JoinInlineBoundary - Boundary information for JoinIR inlining //! //! This module defines the boundary between JoinIR fragments and the host MIR function. //! It enables clean separation of concerns: //! //! - **Box A**: JoinIR Frontend (doesn't know about host ValueIds) //! - **Box B**: Join→MIR Bridge (converts to MIR using local ValueIds) //! - **Box C**: JoinInlineBoundary (stores boundary info - THIS FILE) //! - **Box D**: JoinMirInlineMerger (injects Copy instructions at boundary) //! //! ## Design Philosophy //! //! The JoinIR lowerer should work with **local ValueIds** (0, 1, 2, ...) without //! knowing anything about the host function's ValueId space. This ensures: //! //! 1. **Modularity**: JoinIR lowerers are pure transformers //! 2. **Reusability**: Same lowerer can be used in different contexts //! 3. **Testability**: JoinIR can be tested independently //! 4. **Correctness**: SSA properties are maintained via explicit Copy instructions //! //! ## Example //! //! For `loop(i < 3) { print(i); i = i + 1 }`: //! //! ```text //! Host Function: //! ValueId(4) = Const 0 // i = 0 in host //! //! JoinIR Fragment (uses local IDs 0, 1, 2, ...): //! ValueId(0) = param // i_param (local to JoinIR) //! ValueId(1) = Const 3 //! ValueId(2) = Compare ... //! //! Boundary: //! join_inputs: [ValueId(0)] // JoinIR's param slot //! host_inputs: [ValueId(4)] // Host's `i` variable //! //! Merged MIR (with Copy injection): //! entry: //! ValueId(100) = Copy ValueId(4) // Connect host→JoinIR //! ValueId(101) = Const 3 //! ... //! ``` use crate::mir::ValueId; /// Boundary information for inlining a JoinIR fragment into a host function /// /// This structure captures the "interface" between a JoinIR fragment and the /// host function, allowing the merger to inject necessary Copy instructions /// to connect the two SSA value spaces. /// /// # Design Note /// /// This is a **pure data structure** with no logic. All transformation logic /// lives in the merger (merge_joinir_mir_blocks). #[derive(Debug, Clone)] pub struct JoinInlineBoundary { /// JoinIR-local ValueIds that act as "input slots" /// /// These are the ValueIds used **inside** the JoinIR fragment to refer /// to values that come from the host. They should be small sequential /// IDs (0, 1, 2, ...) since JoinIR lowerers allocate locally. /// /// Example: For a loop variable `i`, JoinIR uses ValueId(0) as the parameter. pub join_inputs: Vec, /// Host-function ValueIds that provide the input values /// /// These are the ValueIds from the **host function** that correspond to /// the join_inputs. The merger will inject Copy instructions to connect /// host_inputs[i] → join_inputs[i]. /// /// Example: If host has `i` as ValueId(4), then host_inputs = [ValueId(4)]. pub host_inputs: Vec, /// JoinIR-local ValueIds that represent outputs (if any) /// /// For loops that produce values (e.g., loop result), these are the /// JoinIR-local ValueIds that should be visible to the host after inlining. /// /// Currently unused for Pattern 1 (Simple While), reserved for future patterns. pub join_outputs: Vec, /// Host-function ValueIds that receive the outputs /// /// These are the destination ValueIds in the host function that should /// receive the values from join_outputs. /// /// Currently unused for Pattern 1 (Simple While), reserved for future patterns. pub host_outputs: Vec, } impl JoinInlineBoundary { /// Create a new boundary with input mappings only /// /// This is the common case for loops like Pattern 1 where: /// - Inputs: loop variables (e.g., `i` in `loop(i < 3)`) /// - Outputs: none (loop returns void/0) pub fn new_inputs_only(join_inputs: Vec, host_inputs: 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![], host_outputs: vec![], } } /// Create a new boundary with both inputs and outputs /// /// Reserved for future loop patterns that produce values. #[allow(dead_code)] pub fn new_with_outputs( join_inputs: Vec, host_inputs: Vec, join_outputs: Vec, host_outputs: Vec, ) -> Self { assert_eq!( join_inputs.len(), host_inputs.len(), "join_inputs and host_inputs must have same length" ); assert_eq!( join_outputs.len(), host_outputs.len(), "join_outputs and host_outputs must have same length" ); Self { join_inputs, host_inputs, join_outputs, host_outputs, } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_boundary_inputs_only() { let boundary = JoinInlineBoundary::new_inputs_only( vec![ValueId(0)], // JoinIR uses ValueId(0) for loop var vec![ValueId(4)], // Host has loop var at ValueId(4) ); assert_eq!(boundary.join_inputs.len(), 1); assert_eq!(boundary.host_inputs.len(), 1); assert_eq!(boundary.join_outputs.len(), 0); assert_eq!(boundary.host_outputs.len(), 0); } #[test] #[should_panic(expected = "join_inputs and host_inputs must have same length")] fn test_boundary_mismatched_inputs() { JoinInlineBoundary::new_inputs_only( vec![ValueId(0), ValueId(1)], vec![ValueId(4)], ); } }