**Task 1: Continuation SSOT 一本化** ✅ - Add JoinInlineBoundary::default_continuations() - Replace all BTreeSet::from([JoinFuncId::new(2)]) hardcoding (7 locations) - Single source of truth for continuation function IDs **Task 2: merge 契約 docs SSOT 化** ✅ - New: src/mir/builder/control_flow/joinir/merge/README.md - Document continuation contracts, skip conditions, forbidden behaviors - Prohibit by-name/by-id classification **Task 3: テスト配置正規化** ✅ - New: src/mir/builder/control_flow/joinir/merge/tests/continuation_contract.rs - Move tests from instruction_rewriter.rs to dedicated test file - Add 4 test cases (Case A-D) **Task 4: legacy 導線隔離** ✅ - New: src/mir/builder/control_flow/joinir/legacy/ - Move routing_legacy_binding.rs → legacy/routing_legacy_binding.rs - Add legacy/README.md with removal conditions - No cfg(feature="legacy") (docs-only isolation for now) **Task 5: ノイズ除去** ✅ - Remove unused imports (ConstValue, MirInstruction) - Clean warnings in touched files Changes: - src/mir/join_ir/lowering/inline_boundary.rs: +default_continuations() - src/mir/builder/control_flow/joinir/merge/README.md: +140 lines - src/mir/builder/control_flow/joinir/merge/tests/: +180 lines - src/mir/builder/control_flow/joinir/legacy/: +3 files Test results: - cargo test --lib: 1176 PASS - All Phase 131/132/97 smokes: PASS Benefits: - Continuation definition centralized (SSOT) - Merge contracts documented and tested - Legacy code path clearly isolated - Code quality improved (warnings reduced) Related: Phase 132 infrastructure improvements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
633 lines
24 KiB
Rust
633 lines
24 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 **JoinIR-side ValueIds** allocated via
|
|
//! `JoinValueSpace` (Param: 100-999, Local: 1000+) 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:
|
|
//! ValueId(100) = param // i_param (JoinIR Param region)
|
|
//! ValueId(1000) = Const 3
|
|
//! ValueId(1001) = Compare ...
|
|
//!
|
|
//! Boundary:
|
|
//! join_inputs: [ValueId(100)] // JoinIR's param slot (Param region)
|
|
//! 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 super::carrier_info::{CarrierRole, ExitReconnectMode};
|
|
use crate::mir::join_ir::JoinFuncId;
|
|
use crate::mir::ValueId;
|
|
use std::collections::BTreeSet;
|
|
|
|
/// Explicit binding between JoinIR exit value and host variable
|
|
///
|
|
/// This structure formalizes the connection between a JoinIR exit PHI value
|
|
/// and the host variable it should update. This eliminates implicit assumptions
|
|
/// about which variable a ValueId represents.
|
|
///
|
|
/// # Pattern 3 Example
|
|
///
|
|
/// For `loop(i < 3) { sum = sum + i; i = i + 1 }`:
|
|
///
|
|
/// ```text
|
|
/// LoopExitBinding {
|
|
/// carrier_name: "sum",
|
|
/// join_exit_value: ValueId(18), // k_exit's return value (JoinIR-local)
|
|
/// host_slot: ValueId(5), // variable_map["sum"] in host
|
|
/// }
|
|
/// ```
|
|
///
|
|
/// # Multi-Carrier Support (Pattern 4+)
|
|
///
|
|
/// Multiple carriers can be represented as a vector:
|
|
///
|
|
/// ```text
|
|
/// vec![
|
|
/// LoopExitBinding { carrier_name: "sum", join_exit_value: ValueId(18), host_slot: ValueId(5), role: LoopState },
|
|
/// LoopExitBinding { carrier_name: "count", join_exit_value: ValueId(19), host_slot: ValueId(6), role: LoopState },
|
|
/// ]
|
|
/// ```
|
|
#[derive(Debug, Clone)]
|
|
pub struct LoopExitBinding {
|
|
/// Carrier variable name (e.g., "sum", "count", "is_digit_pos")
|
|
///
|
|
/// This is the variable name in the host's variable_map that should
|
|
/// receive the exit value.
|
|
pub carrier_name: String,
|
|
|
|
/// JoinIR-side ValueId from k_exit (or exit parameter)
|
|
///
|
|
/// This is the **JoinIR-local** ValueId that represents the exit value.
|
|
/// It will be remapped when merged into the host function.
|
|
pub join_exit_value: ValueId,
|
|
|
|
/// Host-side variable_map slot to reconnect
|
|
///
|
|
/// This is the host function's ValueId for the variable that should be
|
|
/// updated with the exit PHI result.
|
|
pub host_slot: ValueId,
|
|
|
|
/// Phase 227: Role of this carrier (LoopState or ConditionOnly)
|
|
///
|
|
/// Determines whether this carrier should participate in exit PHI:
|
|
/// - LoopState: Needs exit PHI (value used after loop)
|
|
/// - ConditionOnly: No exit PHI (only used in loop condition)
|
|
pub role: CarrierRole,
|
|
}
|
|
|
|
/// 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 in the JoinValueSpace
|
|
/// Param region (100-999). (They are typically allocated sequentially.)
|
|
///
|
|
/// Example: For a loop variable `i`, JoinIR uses ValueId(100) 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.
|
|
///
|
|
/// Phase 188/189 ではまだ利用していないが、将来的な Multi-carrier パターン
|
|
/// (複数の変数を一度に返すループ) のために予約している。
|
|
pub join_outputs: Vec<ValueId>,
|
|
|
|
/// Host-function ValueIds that receive the outputs (DEPRECATED)
|
|
///
|
|
/// **DEPRECATED**: Use `exit_bindings` instead for explicit carrier naming.
|
|
///
|
|
/// These are the destination ValueIds in the host function that should
|
|
/// receive the values from join_outputs, or (Pattern 3 のような単一
|
|
/// キャリアのケースでは) ループ exit PHI の結果を受け取るホスト側の
|
|
/// SSA スロットを表す。
|
|
///
|
|
/// Phase 188-Impl-3 までは未使用だったが、Phase 189 で
|
|
/// loop_if_phi.hako の sum のような「ループの出口で更新されるキャリア」の
|
|
/// 再接続に利用する。
|
|
#[deprecated(since = "Phase 190", note = "Use exit_bindings instead")]
|
|
pub host_outputs: Vec<ValueId>,
|
|
|
|
/// Explicit exit bindings for loop carriers (Phase 190+)
|
|
///
|
|
/// Each binding explicitly names which variable is being updated and
|
|
/// where the value comes from. This eliminates ambiguity and prepares
|
|
/// for multi-carrier support.
|
|
///
|
|
/// For Pattern 3 (single carrier "sum"):
|
|
/// ```
|
|
/// exit_bindings: vec![
|
|
/// LoopExitBinding {
|
|
/// carrier_name: "sum",
|
|
/// join_exit_value: ValueId(18), // k_exit return value
|
|
/// host_slot: ValueId(5), // variable_map["sum"]
|
|
/// }
|
|
/// ]
|
|
/// ```
|
|
pub exit_bindings: Vec<LoopExitBinding>,
|
|
|
|
/// Condition-only input variables (Phase 171+ / Phase 171-fix)
|
|
///
|
|
/// **DEPRECATED**: Use `condition_bindings` instead (Phase 171-fix).
|
|
///
|
|
/// These are variables used ONLY in the loop condition, NOT as loop parameters.
|
|
/// They need to be available in JoinIR scope but are not modified by the loop.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// For `loop(start < end) { i = i + 1 }`:
|
|
/// - Loop parameter: `i` → goes in `join_inputs`/`host_inputs`
|
|
/// - Condition-only: `start`, `end` → go in `condition_inputs`
|
|
///
|
|
/// # Format
|
|
///
|
|
/// Each entry is `(variable_name, host_value_id)`:
|
|
/// ```
|
|
/// condition_inputs: vec![
|
|
/// ("start".to_string(), ValueId(33)), // HOST ID for "start"
|
|
/// ("end".to_string(), ValueId(34)), // HOST ID for "end"
|
|
/// ]
|
|
/// ```
|
|
///
|
|
/// The merger will:
|
|
/// 1. Extract unique variable names from condition AST
|
|
/// 2. Look up HOST ValueIds from `builder.variable_map`
|
|
/// 3. Inject Copy instructions for each condition input
|
|
/// 4. Remap JoinIR references to use the copied values
|
|
#[deprecated(since = "Phase 171-fix", note = "Use condition_bindings instead")]
|
|
pub condition_inputs: Vec<(String, ValueId)>,
|
|
|
|
/// Phase 171-fix: Condition bindings with explicit JoinIR ValueIds
|
|
///
|
|
/// Each binding explicitly specifies:
|
|
/// - Variable name
|
|
/// - HOST ValueId (source)
|
|
/// - JoinIR ValueId (destination)
|
|
///
|
|
/// This replaces `condition_inputs` to ensure proper ValueId separation.
|
|
pub condition_bindings: Vec<super::condition_to_joinir::ConditionBinding>,
|
|
|
|
/// Phase 33-14: Expression result ValueId (JoinIR-local)
|
|
///
|
|
/// If the loop is used as an expression (like `return loop(...)`), this field
|
|
/// contains the JoinIR-local ValueId of k_exit's return value.
|
|
///
|
|
/// - `Some(ValueId)`: Loop returns a value → k_exit return goes to exit_phi_inputs
|
|
/// - `None`: Loop only updates carriers → no exit_phi_inputs generation
|
|
///
|
|
/// # Example: joinir_min_loop.hako (expr result pattern)
|
|
///
|
|
/// ```nyash
|
|
/// loop(i < 3) { if (i >= 2) { break } i = i + 1 }
|
|
/// return i
|
|
/// ```
|
|
///
|
|
/// Here, `expr_result = Some(i_exit)` because the loop's result is used.
|
|
///
|
|
/// # Example: trim pattern (carrier-only)
|
|
///
|
|
/// ```nyash
|
|
/// loop(start < end) { start = start + 1 }
|
|
/// print(start) // Uses carrier after loop
|
|
/// ```
|
|
///
|
|
/// Here, `expr_result = None` because the loop doesn't return a value.
|
|
pub expr_result: Option<crate::mir::ValueId>,
|
|
|
|
/// Phase 33-16: Loop variable name (for LoopHeaderPhiBuilder)
|
|
///
|
|
/// The name of the loop control variable (e.g., "i" in `loop(i < 3)`).
|
|
/// Used to track which PHI corresponds to the loop variable.
|
|
pub loop_var_name: Option<String>,
|
|
|
|
/// Phase 228: Carrier metadata (for header PHI generation)
|
|
///
|
|
/// Contains full carrier information including initialization policies.
|
|
/// This allows header PHI generation to handle ConditionOnly carriers
|
|
/// with explicit bool initialization.
|
|
///
|
|
/// - `Some(CarrierInfo)`: Full carrier metadata available
|
|
/// - `None`: Legacy path (derive carriers from exit_bindings)
|
|
pub carrier_info: Option<super::carrier_info::CarrierInfo>,
|
|
|
|
/// Phase 132 P1: Continuation contract (SSOT)
|
|
///
|
|
/// JoinIR merge must not infer/guess continuation functions. The router/lowerer
|
|
/// must declare continuation JoinFuncIds here.
|
|
///
|
|
/// Merge may still choose to *skip* a continuation function if it is a pure
|
|
/// exit stub (structural check), but it must never skip based on name/ID alone.
|
|
pub continuation_func_ids: BTreeSet<JoinFuncId>,
|
|
|
|
/// Phase 131 P1.5: Exit reconnection mode
|
|
///
|
|
/// Controls whether exit values are reconnected via PHI generation (Phi)
|
|
/// or direct variable_map update (DirectValue).
|
|
///
|
|
/// - `Phi` (default): Existing loop patterns use exit PHI generation
|
|
/// - `DirectValue`: Normalized shadow uses direct value wiring
|
|
pub exit_reconnect_mode: ExitReconnectMode,
|
|
}
|
|
|
|
impl JoinInlineBoundary {
|
|
/// Phase 132-R0 Task 1: SSOT for default continuation function IDs
|
|
///
|
|
/// Returns the default set of continuation functions (k_exit = join_func_2).
|
|
/// This is the single source of truth for continuation function identification.
|
|
///
|
|
/// # Rationale
|
|
///
|
|
/// - Router/lowerer must declare continuation functions explicitly
|
|
/// - Merge must NOT infer continuations by name or ID
|
|
/// - This method centralizes the default continuation contract
|
|
///
|
|
/// # Usage
|
|
///
|
|
/// Use this method when constructing JoinInlineBoundary objects:
|
|
///
|
|
/// ```ignore
|
|
/// JoinInlineBoundary {
|
|
/// // ...
|
|
/// continuation_func_ids: JoinInlineBoundary::default_continuations(),
|
|
/// // ...
|
|
/// }
|
|
/// ```
|
|
pub fn default_continuations() -> BTreeSet<JoinFuncId> {
|
|
BTreeSet::from([JoinFuncId::new(2)])
|
|
}
|
|
|
|
/// 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![],
|
|
#[allow(deprecated)]
|
|
host_outputs: vec![],
|
|
exit_bindings: vec![],
|
|
#[allow(deprecated)]
|
|
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
|
|
condition_bindings: vec![], // Phase 171-fix: Default to empty
|
|
expr_result: None, // Phase 33-14: Default to carrier-only pattern
|
|
loop_var_name: None, // Phase 33-16
|
|
carrier_info: None, // Phase 228: Default to None
|
|
continuation_func_ids: Self::default_continuations(),
|
|
exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi
|
|
}
|
|
}
|
|
|
|
/// Create a new boundary with both inputs and outputs (DEPRECATED)
|
|
///
|
|
/// **DEPRECATED**: Use `new_with_exit_bindings` instead.
|
|
///
|
|
|
|
/// Create a new boundary with inputs and **host outputs only** (DEPRECATED)
|
|
///
|
|
/// **DEPRECATED**: Use `new_with_exit_bindings` instead for explicit carrier naming.
|
|
///
|
|
/// JoinIR 側の exit 値 (k_exit の引数など) を 1 つの PHI にまとめ、
|
|
/// その PHI 結果をホスト側の変数スロットへ再接続したい場合に使う。
|
|
///
|
|
/// 典型例: Pattern 3 (loop_if_phi.hako)
|
|
/// - join_inputs : [i_init, sum_init]
|
|
/// - host_inputs : [host_i, host_sum]
|
|
/// - host_outputs : [host_sum] // ループ exit 時に上書きしたい変数
|
|
#[deprecated(since = "Phase 190", note = "Use new_with_exit_bindings instead")]
|
|
pub fn new_with_input_and_host_outputs(
|
|
join_inputs: Vec<ValueId>,
|
|
host_inputs: Vec<ValueId>,
|
|
host_outputs: 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![],
|
|
#[allow(deprecated)]
|
|
host_outputs,
|
|
exit_bindings: vec![],
|
|
#[allow(deprecated)]
|
|
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
|
|
condition_bindings: vec![], // Phase 171-fix: Default to empty
|
|
expr_result: None, // Phase 33-14
|
|
loop_var_name: None, // Phase 33-16
|
|
carrier_info: None, // Phase 228
|
|
continuation_func_ids: Self::default_continuations(),
|
|
exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi
|
|
}
|
|
}
|
|
|
|
/// Create a new boundary with explicit exit bindings (Phase 190+)
|
|
///
|
|
/// This is the recommended constructor for loops with exit carriers.
|
|
/// Each exit binding explicitly names the carrier variable and its
|
|
/// source/destination values.
|
|
///
|
|
/// # Example: Pattern 3 (single carrier)
|
|
///
|
|
/// ```ignore
|
|
/// let boundary = JoinInlineBoundary::new_with_exit_bindings(
|
|
/// vec![ValueId(0), ValueId(1)], // join_inputs (i, sum init)
|
|
/// vec![loop_var_id, sum_var_id], // host_inputs
|
|
/// vec![
|
|
/// LoopExitBinding {
|
|
/// carrier_name: "sum".to_string(),
|
|
/// join_exit_value: ValueId(18), // k_exit return value
|
|
/// host_slot: sum_var_id, // variable_map["sum"]
|
|
/// }
|
|
/// ],
|
|
/// );
|
|
/// ```
|
|
///
|
|
/// # Example: Pattern 4+ (multiple carriers)
|
|
///
|
|
/// ```ignore
|
|
/// let boundary = JoinInlineBoundary::new_with_exit_bindings(
|
|
/// vec![ValueId(0), ValueId(1), ValueId(2)], // join_inputs
|
|
/// vec![i_id, sum_id, count_id], // host_inputs
|
|
/// vec![
|
|
/// LoopExitBinding { carrier_name: "sum".to_string(), ... },
|
|
/// LoopExitBinding { carrier_name: "count".to_string(), ... },
|
|
/// ],
|
|
/// );
|
|
/// ```
|
|
pub fn new_with_exit_bindings(
|
|
join_inputs: Vec<ValueId>,
|
|
host_inputs: Vec<ValueId>,
|
|
exit_bindings: Vec<LoopExitBinding>,
|
|
) -> 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![],
|
|
#[allow(deprecated)]
|
|
host_outputs: vec![],
|
|
exit_bindings,
|
|
#[allow(deprecated)]
|
|
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
|
|
condition_bindings: vec![], // Phase 171-fix: Default to empty
|
|
expr_result: None, // Phase 33-14
|
|
loop_var_name: None, // Phase 33-16
|
|
carrier_info: None, // Phase 228
|
|
continuation_func_ids: Self::default_continuations(),
|
|
exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi
|
|
}
|
|
}
|
|
|
|
/// Create a new boundary with condition inputs (Phase 171+)
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `join_inputs` - JoinIR-local ValueIds for loop parameters
|
|
/// * `host_inputs` - HOST ValueIds for loop parameters
|
|
/// * `condition_inputs` - Condition-only variables [(name, host_value_id)]
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```ignore
|
|
/// let boundary = JoinInlineBoundary::new_with_condition_inputs(
|
|
/// vec![ValueId(0)], // join_inputs (i)
|
|
/// vec![ValueId(5)], // host_inputs (i)
|
|
/// vec![
|
|
/// ("start".to_string(), ValueId(33)),
|
|
/// ("end".to_string(), ValueId(34)),
|
|
/// ],
|
|
/// );
|
|
/// ```
|
|
pub fn new_with_condition_inputs(
|
|
join_inputs: Vec<ValueId>,
|
|
host_inputs: Vec<ValueId>,
|
|
condition_inputs: Vec<(String, 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![],
|
|
#[allow(deprecated)]
|
|
host_outputs: vec![],
|
|
exit_bindings: vec![],
|
|
#[allow(deprecated)]
|
|
condition_inputs,
|
|
condition_bindings: vec![], // Phase 171-fix: Will be populated by new constructor
|
|
expr_result: None, // Phase 33-14
|
|
loop_var_name: None, // Phase 33-16
|
|
carrier_info: None, // Phase 228
|
|
continuation_func_ids: Self::default_continuations(),
|
|
exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi
|
|
}
|
|
}
|
|
|
|
/// Create boundary with inputs, exit bindings, AND condition inputs (Phase 171+)
|
|
///
|
|
/// This is the most complete constructor for loops with carriers and condition variables.
|
|
///
|
|
/// # Example: Pattern 3 with condition variables
|
|
///
|
|
/// ```ignore
|
|
/// let boundary = JoinInlineBoundary::new_with_exit_and_condition_inputs(
|
|
/// vec![ValueId(0), ValueId(1)], // join_inputs (i, sum)
|
|
/// vec![ValueId(5), ValueId(10)], // host_inputs
|
|
/// vec![
|
|
/// LoopExitBinding {
|
|
/// carrier_name: "sum".to_string(),
|
|
/// join_exit_value: ValueId(18),
|
|
/// host_slot: ValueId(10),
|
|
/// }
|
|
/// ],
|
|
/// vec![
|
|
/// ("start".to_string(), ValueId(33)),
|
|
/// ("end".to_string(), ValueId(34)),
|
|
/// ],
|
|
/// );
|
|
/// ```
|
|
pub fn new_with_exit_and_condition_inputs(
|
|
join_inputs: Vec<ValueId>,
|
|
host_inputs: Vec<ValueId>,
|
|
exit_bindings: Vec<LoopExitBinding>,
|
|
condition_inputs: Vec<(String, 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![],
|
|
#[allow(deprecated)]
|
|
host_outputs: vec![],
|
|
exit_bindings,
|
|
#[allow(deprecated)]
|
|
condition_inputs,
|
|
condition_bindings: vec![], // Phase 171-fix: Will be populated by new constructor
|
|
expr_result: None, // Phase 33-14
|
|
loop_var_name: None, // Phase 33-16
|
|
carrier_info: None, // Phase 228
|
|
continuation_func_ids: Self::default_continuations(),
|
|
exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi
|
|
}
|
|
}
|
|
|
|
/// Phase 171-fix: Create boundary with ConditionBindings (NEW constructor)
|
|
///
|
|
/// This is the recommended constructor that uses ConditionBindings instead of
|
|
/// the deprecated condition_inputs.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `join_inputs` - JoinIR-local ValueIds for loop parameters
|
|
/// * `host_inputs` - HOST ValueIds for loop parameters
|
|
/// * `condition_bindings` - Explicit HOST ↔ JoinIR mappings for condition variables
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```ignore
|
|
/// let boundary = JoinInlineBoundary::new_with_condition_bindings(
|
|
/// vec![ValueId(0)], // join_inputs (loop param i)
|
|
/// vec![ValueId(5)], // host_inputs (loop param i)
|
|
/// vec![
|
|
/// ConditionBinding {
|
|
/// name: "start".to_string(),
|
|
/// host_value: ValueId(33), // HOST
|
|
/// join_value: ValueId(1), // JoinIR
|
|
/// },
|
|
/// ConditionBinding {
|
|
/// name: "end".to_string(),
|
|
/// host_value: ValueId(34), // HOST
|
|
/// join_value: ValueId(2), // JoinIR
|
|
/// },
|
|
/// ],
|
|
/// );
|
|
/// ```
|
|
pub fn new_with_condition_bindings(
|
|
join_inputs: Vec<ValueId>,
|
|
host_inputs: Vec<ValueId>,
|
|
condition_bindings: Vec<super::condition_to_joinir::ConditionBinding>,
|
|
) -> 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![],
|
|
#[allow(deprecated)]
|
|
host_outputs: vec![],
|
|
exit_bindings: vec![],
|
|
#[allow(deprecated)]
|
|
condition_inputs: vec![], // Deprecated, use condition_bindings instead
|
|
condition_bindings,
|
|
expr_result: None, // Phase 33-14
|
|
loop_var_name: None, // Phase 33-16
|
|
carrier_info: None, // Phase 228
|
|
continuation_func_ids: Self::default_continuations(),
|
|
exit_reconnect_mode: ExitReconnectMode::default(), // Phase 131 P1.5: Default to Phi
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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);
|
|
#[allow(deprecated)]
|
|
{
|
|
assert_eq!(boundary.host_outputs.len(), 0);
|
|
assert_eq!(boundary.condition_inputs.len(), 0); // Phase 171: Deprecated field
|
|
}
|
|
assert_eq!(boundary.condition_bindings.len(), 0); // Phase 171-fix: New field
|
|
}
|
|
|
|
#[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)]);
|
|
}
|
|
}
|