169 lines
5.7 KiB
Rust
169 lines
5.7 KiB
Rust
|
|
//! 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<ValueId>,
|
||
|
|
|
||
|
|
/// 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<ValueId>,
|
||
|
|
|
||
|
|
/// 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<ValueId>,
|
||
|
|
|
||
|
|
/// 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<ValueId>,
|
||
|
|
}
|
||
|
|
|
||
|
|
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<ValueId>, host_inputs: Vec<ValueId>) -> 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<ValueId>,
|
||
|
|
host_inputs: Vec<ValueId>,
|
||
|
|
join_outputs: Vec<ValueId>,
|
||
|
|
host_outputs: Vec<ValueId>,
|
||
|
|
) -> 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)],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|