feat(joinir): Phase 200-A ConditionEnv extension infrastructure
Added type and skeleton infrastructure for function-scoped variable
capture, preparing for Phase 200-B integration with ConditionEnv.
New Types:
- CapturedVar: { name, host_id, is_immutable }
- CapturedEnv: Collection of captured variables
- ParamRole: { LoopParam, Condition, Carrier, ExprResult }
New Functions (Skeletons):
- analyze_captured_vars(): Detects function-scoped "constants"
- build_with_captures(): ConditionEnvBuilder v2 entry point
- add_param_with_role(): Role-based parameter routing
New File:
- src/mir/loop_pattern_detection/function_scope_capture.rs
Design Principles:
- Infra only: Types and skeletons, no behavior changes
- Existing behavior maintained: All current loops work identically
- Box-first: New responsibilities in new file
- Documentation: Future implementation plans in code comments
Test Results:
- 6 new unit tests (function_scope_capture: 3, param_role: 3)
- All 804 existing tests PASS (0 regressions)
Next: Phase 200-B (actual capture detection and integration)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -28,6 +28,50 @@ use crate::mir::ValueId;
|
||||
use super::inline_boundary::{JoinInlineBoundary, LoopExitBinding};
|
||||
use super::condition_to_joinir::ConditionBinding;
|
||||
|
||||
/// Role of a parameter in JoinIR lowering (Phase 200-A)
|
||||
///
|
||||
/// This enum explicitly classifies parameters to ensure correct routing
|
||||
/// during JoinIR → MIR lowering and boundary construction.
|
||||
///
|
||||
/// # Invariants
|
||||
///
|
||||
/// - **LoopParam**: Participates in header PHI, updated in loop body
|
||||
/// - Example: `i` in `loop(i < len)` - iteration variable
|
||||
/// - Routing: join_inputs + host_inputs + header PHI + exit_bindings
|
||||
///
|
||||
/// - **Condition**: Used in condition only, NOT in header PHI, NOT in ExitLine
|
||||
/// - Example: `digits` in `digits.indexOf(ch)` - function-scoped constant
|
||||
/// - Routing: condition_bindings ONLY (no PHI, no exit_bindings)
|
||||
/// - Rationale: Condition-only vars are immutable and not updated in loop
|
||||
///
|
||||
/// - **Carrier**: Updated in loop body, participates in header PHI and ExitLine
|
||||
/// - Example: `sum`, `count` in accumulation loops
|
||||
/// - Routing: join_inputs + host_inputs + header PHI + exit_bindings
|
||||
///
|
||||
/// - **ExprResult**: Return value of the loop expression
|
||||
/// - Example: Loop result in `return loop(...)`
|
||||
/// - Routing: Handled by exit_phi_builder (set_expr_result)
|
||||
///
|
||||
/// # Phase 200-A Status
|
||||
///
|
||||
/// Enum is defined but not yet used for routing. Routing implementation
|
||||
/// will be added in Phase 200-B when CapturedEnv integration is complete.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ParamRole {
|
||||
/// Loop iteration variable (e.g., `i` in `loop(i < len)`)
|
||||
LoopParam,
|
||||
|
||||
/// Condition-only parameter (e.g., `digits` in `digits.indexOf(ch)`)
|
||||
/// NOT included in header PHI or ExitLine
|
||||
Condition,
|
||||
|
||||
/// State carried across iterations (e.g., `sum`, `count`)
|
||||
Carrier,
|
||||
|
||||
/// Expression result returned by the loop
|
||||
ExprResult,
|
||||
}
|
||||
|
||||
/// Builder for constructing JoinInlineBoundary objects
|
||||
///
|
||||
/// Provides a fluent API to set boundary fields without direct field manipulation.
|
||||
@ -128,6 +172,70 @@ impl JoinInlineBoundaryBuilder {
|
||||
pub fn build(self) -> JoinInlineBoundary {
|
||||
self.boundary
|
||||
}
|
||||
|
||||
/// Add a parameter with explicit role (Phase 200-A)
|
||||
///
|
||||
/// This method allows adding parameters with explicit role classification,
|
||||
/// ensuring correct routing during JoinIR → MIR lowering.
|
||||
///
|
||||
/// # Phase 200-A Status
|
||||
///
|
||||
/// Currently stores parameters based on role but does not use role for advanced routing.
|
||||
/// Full role-based routing will be implemented in Phase 200-B.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `name` - Variable name (e.g., "i", "digits", "sum")
|
||||
/// * `host_id` - Host function's ValueId for this variable
|
||||
/// * `role` - Parameter role (LoopParam / Condition / Carrier / ExprResult)
|
||||
///
|
||||
/// # Routing Rules (Phase 200-B+)
|
||||
///
|
||||
/// - **LoopParam**: add_input (join_inputs + host_inputs)
|
||||
/// - **Condition**: add to condition_bindings (no PHI, no exit_bindings)
|
||||
/// - **Carrier**: add_input + exit_bindings
|
||||
/// - **ExprResult**: set_expr_result (handled separately)
|
||||
///
|
||||
/// # Example (Future Phase 200-B)
|
||||
///
|
||||
/// ```ignore
|
||||
/// builder.add_param_with_role("i", ValueId(100), ParamRole::LoopParam);
|
||||
/// builder.add_param_with_role("digits", ValueId(42), ParamRole::Condition);
|
||||
/// builder.add_param_with_role("sum", ValueId(101), ParamRole::Carrier);
|
||||
/// ```
|
||||
pub fn add_param_with_role(&mut self, _name: &str, host_id: ValueId, role: ParamRole) {
|
||||
// Phase 200-A: Basic routing only
|
||||
// TODO(Phase 200-B): Implement full role-based routing
|
||||
//
|
||||
// Routing implementation:
|
||||
// - LoopParam: join_inputs + host_inputs
|
||||
// - Condition: condition_bindings (with JoinIR-local ValueId allocation)
|
||||
// - Carrier: join_inputs + host_inputs + exit_bindings
|
||||
// - ExprResult: Handled by set_expr_result
|
||||
|
||||
match role {
|
||||
ParamRole::LoopParam | ParamRole::Carrier => {
|
||||
// Existing behavior: add to join_inputs
|
||||
// Note: In Phase 200-A, we don't have a simple add_input method
|
||||
// that takes a name. This is a skeleton implementation.
|
||||
// In Phase 200-B, we'll need to allocate JoinIR-local ValueIds.
|
||||
let join_id = ValueId(self.boundary.join_inputs.len() as u32);
|
||||
self.boundary.join_inputs.push(join_id);
|
||||
self.boundary.host_inputs.push(host_id);
|
||||
}
|
||||
ParamRole::Condition => {
|
||||
// Phase 200-A: Log only
|
||||
// TODO(Phase 200-B): Add to condition_bindings without PHI
|
||||
// 1. Allocate JoinIR-local ValueId
|
||||
// 2. Create ConditionBinding { name, host_id, join_id }
|
||||
// 3. Add to self.boundary.condition_bindings
|
||||
}
|
||||
ParamRole::ExprResult => {
|
||||
// Handled separately by set_expr_result
|
||||
// No action needed here
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for JoinInlineBoundaryBuilder {
|
||||
@ -255,4 +363,40 @@ mod tests {
|
||||
assert_eq!(boundary.join_inputs.len(), 3);
|
||||
assert_eq!(boundary.host_inputs.len(), 3);
|
||||
}
|
||||
|
||||
// Phase 200-A: ParamRole tests
|
||||
#[test]
|
||||
fn test_param_role_loop_param() {
|
||||
let mut builder = JoinInlineBoundaryBuilder::new();
|
||||
builder.add_param_with_role("i", ValueId(100), ParamRole::LoopParam);
|
||||
|
||||
let boundary = builder.build();
|
||||
assert_eq!(boundary.join_inputs.len(), 1);
|
||||
assert_eq!(boundary.host_inputs.len(), 1);
|
||||
assert_eq!(boundary.host_inputs[0], ValueId(100));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_param_role_condition() {
|
||||
let mut builder = JoinInlineBoundaryBuilder::new();
|
||||
// Phase 200-A: Condition role is logged but not yet routed
|
||||
builder.add_param_with_role("digits", ValueId(42), ParamRole::Condition);
|
||||
|
||||
let boundary = builder.build();
|
||||
// Phase 200-A: No action for Condition role yet
|
||||
// Phase 200-B: This will add to condition_bindings
|
||||
assert_eq!(boundary.join_inputs.len(), 0);
|
||||
assert_eq!(boundary.condition_bindings.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_param_role_carrier() {
|
||||
let mut builder = JoinInlineBoundaryBuilder::new();
|
||||
builder.add_param_with_role("sum", ValueId(101), ParamRole::Carrier);
|
||||
|
||||
let boundary = builder.build();
|
||||
assert_eq!(boundary.join_inputs.len(), 1);
|
||||
assert_eq!(boundary.host_inputs.len(), 1);
|
||||
assert_eq!(boundary.host_inputs[0], ValueId(101));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user