2025-12-08 04:25:58 +09:00
|
|
|
//! Phase 200-2: JoinInlineBoundaryBuilder - Builder pattern for JoinInlineBoundary construction
|
|
|
|
|
//!
|
|
|
|
|
//! This module provides a fluent Builder API to construct JoinInlineBoundary objects,
|
|
|
|
|
//! reducing field manipulation scattering in pattern lowerers.
|
|
|
|
|
//!
|
|
|
|
|
//! # Design Philosophy
|
|
|
|
|
//!
|
|
|
|
|
//! - **Centralized Construction**: All boundary field manipulation in one place
|
|
|
|
|
//! - **Type Safety**: Builder ensures fields are set correctly
|
|
|
|
|
//! - **Readability**: Fluent API makes construction intent clear
|
|
|
|
|
//! - **Maintainability**: Changes to boundary structure isolated to this builder
|
|
|
|
|
//!
|
|
|
|
|
//! # Example Usage
|
|
|
|
|
//!
|
|
|
|
|
//! ```ignore
|
|
|
|
|
//! use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
|
|
|
|
|
//!
|
|
|
|
|
//! let boundary = JoinInlineBoundaryBuilder::new()
|
|
|
|
|
//! .with_inputs(vec![ValueId(0)], vec![loop_var_id])
|
|
|
|
|
//! .with_loop_var_name(Some(loop_param_name.to_string()))
|
|
|
|
|
//! .with_condition_bindings(condition_bindings)
|
|
|
|
|
//! .with_exit_bindings(exit_bindings)
|
|
|
|
|
//! .with_expr_result(fragment_meta.expr_result)
|
|
|
|
|
//! .build();
|
|
|
|
|
//! ```
|
|
|
|
|
|
|
|
|
|
use crate::mir::ValueId;
|
|
|
|
|
use super::inline_boundary::{JoinInlineBoundary, LoopExitBinding};
|
|
|
|
|
use super::condition_to_joinir::ConditionBinding;
|
|
|
|
|
|
|
|
|
|
/// Builder for constructing JoinInlineBoundary objects
|
|
|
|
|
///
|
|
|
|
|
/// Provides a fluent API to set boundary fields without direct field manipulation.
|
|
|
|
|
pub struct JoinInlineBoundaryBuilder {
|
|
|
|
|
boundary: JoinInlineBoundary,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl JoinInlineBoundaryBuilder {
|
|
|
|
|
/// Create a new builder with default empty boundary
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
boundary: JoinInlineBoundary {
|
|
|
|
|
join_inputs: vec![],
|
|
|
|
|
host_inputs: vec![],
|
|
|
|
|
join_outputs: vec![],
|
|
|
|
|
#[allow(deprecated)]
|
|
|
|
|
host_outputs: vec![],
|
|
|
|
|
exit_bindings: vec![],
|
|
|
|
|
#[allow(deprecated)]
|
|
|
|
|
condition_inputs: vec![],
|
|
|
|
|
condition_bindings: vec![],
|
|
|
|
|
expr_result: None,
|
|
|
|
|
loop_var_name: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set input mappings (JoinIR-local ↔ Host ValueIds)
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `join_inputs` - JoinIR-local ValueIds (e.g., [ValueId(0)])
|
|
|
|
|
/// * `host_inputs` - Host ValueIds (e.g., [loop_var_id])
|
|
|
|
|
///
|
|
|
|
|
/// # Panics
|
|
|
|
|
///
|
|
|
|
|
/// Panics if `join_inputs` and `host_inputs` have different lengths.
|
|
|
|
|
pub fn with_inputs(mut self, 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.boundary.join_inputs = join_inputs;
|
|
|
|
|
self.boundary.host_inputs = host_inputs;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set output mappings (JoinIR-local ↔ Host ValueIds) (DEPRECATED)
|
|
|
|
|
///
|
|
|
|
|
/// **DEPRECATED**: Use `with_exit_bindings` instead for explicit carrier naming.
|
|
|
|
|
#[deprecated(since = "Phase 200-2", note = "Use with_exit_bindings instead")]
|
|
|
|
|
pub fn with_outputs(mut self, join_outputs: Vec<ValueId>, host_outputs: Vec<ValueId>) -> Self {
|
|
|
|
|
self.boundary.join_outputs = join_outputs;
|
|
|
|
|
#[allow(deprecated)]
|
|
|
|
|
{
|
|
|
|
|
self.boundary.host_outputs = host_outputs;
|
|
|
|
|
}
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set condition bindings (Phase 171-fix)
|
|
|
|
|
///
|
|
|
|
|
/// Each binding explicitly maps:
|
|
|
|
|
/// - Variable name
|
|
|
|
|
/// - HOST ValueId → JoinIR ValueId
|
|
|
|
|
pub fn with_condition_bindings(mut self, bindings: Vec<ConditionBinding>) -> Self {
|
|
|
|
|
self.boundary.condition_bindings = bindings;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set exit bindings (Phase 190+)
|
|
|
|
|
///
|
|
|
|
|
/// Each binding explicitly names the carrier variable and its source/destination.
|
|
|
|
|
pub fn with_exit_bindings(mut self, bindings: Vec<LoopExitBinding>) -> Self {
|
|
|
|
|
self.boundary.exit_bindings = bindings;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set loop variable name (Phase 33-16)
|
|
|
|
|
///
|
|
|
|
|
/// Used for LoopHeaderPhiBuilder to track which PHI corresponds to the loop variable.
|
|
|
|
|
pub fn with_loop_var_name(mut self, name: Option<String>) -> Self {
|
|
|
|
|
self.boundary.loop_var_name = name;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set expression result (Phase 33-14)
|
|
|
|
|
///
|
|
|
|
|
/// If the loop is used as an expression, this is the JoinIR-local ValueId
|
|
|
|
|
/// of k_exit's return value.
|
|
|
|
|
pub fn with_expr_result(mut self, expr: Option<ValueId>) -> Self {
|
|
|
|
|
self.boundary.expr_result = expr;
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Build the final JoinInlineBoundary
|
|
|
|
|
pub fn build(self) -> JoinInlineBoundary {
|
|
|
|
|
self.boundary
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for JoinInlineBoundaryBuilder {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_builder_basic() {
|
|
|
|
|
let boundary = JoinInlineBoundaryBuilder::new()
|
|
|
|
|
.with_inputs(vec![ValueId(0)], vec![ValueId(4)])
|
|
|
|
|
.build();
|
|
|
|
|
|
|
|
|
|
assert_eq!(boundary.join_inputs, vec![ValueId(0)]);
|
|
|
|
|
assert_eq!(boundary.host_inputs, vec![ValueId(4)]);
|
|
|
|
|
assert_eq!(boundary.join_outputs.len(), 0);
|
|
|
|
|
assert_eq!(boundary.exit_bindings.len(), 0);
|
|
|
|
|
assert_eq!(boundary.condition_bindings.len(), 0);
|
|
|
|
|
assert_eq!(boundary.expr_result, None);
|
|
|
|
|
assert_eq!(boundary.loop_var_name, None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_builder_full() {
|
|
|
|
|
let condition_binding = ConditionBinding {
|
|
|
|
|
name: "start".to_string(),
|
|
|
|
|
host_value: ValueId(33),
|
|
|
|
|
join_value: ValueId(1),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let exit_binding = LoopExitBinding {
|
|
|
|
|
carrier_name: "sum".to_string(),
|
|
|
|
|
join_exit_value: ValueId(18),
|
|
|
|
|
host_slot: ValueId(5),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let boundary = JoinInlineBoundaryBuilder::new()
|
|
|
|
|
.with_inputs(vec![ValueId(0)], vec![ValueId(4)])
|
|
|
|
|
.with_loop_var_name(Some("i".to_string()))
|
|
|
|
|
.with_condition_bindings(vec![condition_binding])
|
|
|
|
|
.with_exit_bindings(vec![exit_binding])
|
|
|
|
|
.with_expr_result(Some(ValueId(20)))
|
|
|
|
|
.build();
|
|
|
|
|
|
|
|
|
|
assert_eq!(boundary.join_inputs, vec![ValueId(0)]);
|
|
|
|
|
assert_eq!(boundary.host_inputs, vec![ValueId(4)]);
|
|
|
|
|
assert_eq!(boundary.loop_var_name, Some("i".to_string()));
|
|
|
|
|
assert_eq!(boundary.condition_bindings.len(), 1);
|
|
|
|
|
assert_eq!(boundary.exit_bindings.len(), 1);
|
|
|
|
|
assert_eq!(boundary.expr_result, Some(ValueId(20)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
#[should_panic(expected = "join_inputs and host_inputs must have same length")]
|
|
|
|
|
fn test_builder_mismatched_inputs() {
|
|
|
|
|
JoinInlineBoundaryBuilder::new()
|
|
|
|
|
.with_inputs(vec![ValueId(0), ValueId(1)], vec![ValueId(4)])
|
|
|
|
|
.build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_builder_default() {
|
|
|
|
|
let builder = JoinInlineBoundaryBuilder::default();
|
|
|
|
|
let boundary = builder.build();
|
|
|
|
|
|
|
|
|
|
assert_eq!(boundary.join_inputs.len(), 0);
|
|
|
|
|
assert_eq!(boundary.host_inputs.len(), 0);
|
|
|
|
|
}
|
2025-12-08 06:14:03 +09:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_builder_pattern3_style() {
|
|
|
|
|
// Pattern3 style: Two carriers (i + sum), exit_bindings, loop_var_name
|
2025-12-08 23:43:26 +09:00
|
|
|
|
2025-12-08 06:14:03 +09:00
|
|
|
|
|
|
|
|
let boundary = JoinInlineBoundaryBuilder::new()
|
|
|
|
|
.with_inputs(vec![ValueId(0), ValueId(1)], vec![ValueId(100), ValueId(101)])
|
|
|
|
|
.with_exit_bindings(vec![
|
|
|
|
|
LoopExitBinding {
|
|
|
|
|
carrier_name: "sum".to_string(),
|
|
|
|
|
join_exit_value: ValueId(18),
|
|
|
|
|
host_slot: ValueId(101),
|
|
|
|
|
}
|
|
|
|
|
])
|
|
|
|
|
.with_loop_var_name(Some("i".to_string()))
|
|
|
|
|
.build();
|
|
|
|
|
|
|
|
|
|
assert_eq!(boundary.join_inputs.len(), 2);
|
|
|
|
|
assert_eq!(boundary.host_inputs.len(), 2);
|
|
|
|
|
assert_eq!(boundary.exit_bindings.len(), 1);
|
|
|
|
|
assert_eq!(boundary.exit_bindings[0].carrier_name, "sum");
|
|
|
|
|
assert_eq!(boundary.loop_var_name, Some("i".to_string()));
|
|
|
|
|
assert_eq!(boundary.expr_result, None);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_builder_pattern4_style() {
|
|
|
|
|
// Pattern4 style: Dynamic carrier count, continue support
|
|
|
|
|
let boundary = JoinInlineBoundaryBuilder::new()
|
|
|
|
|
.with_inputs(
|
|
|
|
|
vec![ValueId(0), ValueId(1), ValueId(2)], // i + 2 carriers
|
|
|
|
|
vec![ValueId(100), ValueId(101), ValueId(102)]
|
|
|
|
|
)
|
|
|
|
|
.with_exit_bindings(vec![
|
|
|
|
|
LoopExitBinding {
|
|
|
|
|
carrier_name: "i".to_string(),
|
|
|
|
|
join_exit_value: ValueId(11),
|
|
|
|
|
host_slot: ValueId(100),
|
|
|
|
|
},
|
|
|
|
|
LoopExitBinding {
|
|
|
|
|
carrier_name: "sum".to_string(),
|
|
|
|
|
join_exit_value: ValueId(20),
|
|
|
|
|
host_slot: ValueId(101),
|
|
|
|
|
}
|
|
|
|
|
])
|
|
|
|
|
.with_loop_var_name(Some("i".to_string()))
|
|
|
|
|
.build();
|
|
|
|
|
|
|
|
|
|
assert_eq!(boundary.exit_bindings.len(), 2);
|
|
|
|
|
assert!(boundary.loop_var_name.is_some());
|
|
|
|
|
assert_eq!(boundary.join_inputs.len(), 3);
|
|
|
|
|
assert_eq!(boundary.host_inputs.len(), 3);
|
|
|
|
|
}
|
2025-12-08 04:25:58 +09:00
|
|
|
}
|