Files
hakorune/docs/development/current/main/phase171-3-implementation-report.md
nyash-codex e30116f53d feat(joinir): Phase 171-fix ConditionEnv/ConditionBinding architecture
Proper HOST↔JoinIR ValueId separation for condition variables:

- Add ConditionEnv struct (name → JoinIR-local ValueId mapping)
- Add ConditionBinding struct (HOST/JoinIR ValueId pairs)
- Modify condition_to_joinir to use ConditionEnv instead of builder.variable_map
- Update Pattern2 lowerer to build ConditionEnv and ConditionBindings
- Extend JoinInlineBoundary with condition_bindings field
- Update BoundaryInjector to inject Copy instructions for condition variables

This fixes the undefined ValueId errors where HOST ValueIds were being
used directly in JoinIR instructions. Programs now execute (RC: 0),
though loop variable exit values still need Phase 172 work.

Key invariants established:
1. JoinIR uses ONLY JoinIR-local ValueIds
2. HOST↔JoinIR bridging is ONLY through JoinInlineBoundary
3. condition_to_joinir NEVER accesses builder.variable_map

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 01:45:03 +09:00

8.1 KiB

Phase 171-3: Register Condition Inputs in JoinIR Lowerers

Date: 2025-12-07 Status: Complete


Implementation Summary

Successfully implemented the infrastructure for registering condition-only input variables in JoinIR lowerers.


Changes Made

1. Extended JoinInlineBoundary Structure

File: src/mir/join_ir/lowering/inline_boundary.rs

Added field:

pub struct JoinInlineBoundary {
    pub join_inputs: Vec<ValueId>,
    pub host_inputs: Vec<ValueId>,
    pub join_outputs: Vec<ValueId>,
    pub host_outputs: Vec<ValueId>,
    pub exit_bindings: Vec<LoopExitBinding>,

    /// NEW: Condition-only input variables (Phase 171+)
    pub condition_inputs: Vec<(String, ValueId)>,  // [(var_name, host_value_id)]
}

Rationale: Condition variables like start, end, len are read-only inputs that don't participate in loop iteration but must be available in JoinIR scope for condition evaluation.


2. Updated All Constructors

Modified constructors:

  • new_inputs_only() - Added condition_inputs: vec![]
  • new_with_outputs() - Added condition_inputs: vec![]
  • new_with_input_and_host_outputs() - Added condition_inputs: vec![]
  • new_with_exit_bindings() - Added condition_inputs: vec![]

New constructors:

  • new_with_condition_inputs() - For loops with condition variables
  • new_with_exit_and_condition_inputs() - For loops with carriers AND condition variables

Example usage:

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)),
    ],
);

3. Implemented Condition Variable Extraction

File: src/mir/join_ir/lowering/condition_to_joinir.rs

New functions:

extract_condition_variables()

pub fn extract_condition_variables(
    cond_ast: &ASTNode,
    exclude_vars: &[String],
) -> Vec<String>

Recursively traverses condition AST and collects all unique variable names, excluding loop parameters that are already registered as join_inputs.

Features:

  • Sorted output (BTreeSet) for determinism
  • Filters excluded variables (loop parameters)
  • Handles complex conditions (AND, OR, NOT chains)

collect_variables_recursive()

fn collect_variables_recursive(
    ast: &ASTNode,
    vars: &mut BTreeSet<String>
)

Helper function that recursively visits all AST nodes and extracts variable names.

Supported AST nodes:

  • Variable - Adds variable name to set
  • BinaryOp - Recursively processes left and right operands
  • UnaryOp - Recursively processes operand
  • Literal - No variables (skipped)

4. Added Comprehensive Tests

File: src/mir/join_ir/lowering/condition_to_joinir.rs

Test cases:

test_extract_condition_variables_simple()

// AST: start < end
let vars = extract_condition_variables(&ast, &[]);
assert_eq!(vars, vec!["end", "start"]);  // Sorted

test_extract_condition_variables_with_exclude()

// AST: i < end
let vars = extract_condition_variables(&ast, &["i".to_string()]);
assert_eq!(vars, vec!["end"]);  // 'i' excluded

test_extract_condition_variables_complex()

// AST: start < end && i < len
let vars = extract_condition_variables(&ast, &["i".to_string()]);
assert_eq!(vars, vec!["end", "len", "start"]);  // Sorted, 'i' excluded

5. Updated Test Code

File: src/mir/builder/control_flow/joinir/patterns/exit_binding.rs

Fixed test:

let mut boundary = JoinInlineBoundary {
    host_inputs: vec![],
    join_inputs: vec![],
    host_outputs: vec![],
    join_outputs: vec![],
    exit_bindings: vec![],       // Phase 171: Added
    condition_inputs: vec![],    // Phase 171: Added
};

Design Decisions

Why Separate condition_inputs from join_inputs?

Aspect Loop Parameters (join_inputs) Condition Inputs (condition_inputs)
Mutability Mutable (e.g., i = i + 1) Read-only (never modified)
Lifetime Entire loop duration Only during condition evaluation
JoinIR representation Function parameters Captured values
Example i in loop(i < 3) end in loop(i < end)

Semantic clarity: Separating them makes the intent explicit and prevents accidental mutation of condition-only variables.

Why Use Vec<(String, ValueId)> Instead of HashMap?

Chosen: Vec<(String, ValueId)> Alternative: HashMap<String, ValueId>

Rationale:

  1. Order preservation: Vec maintains insertion order for deterministic behavior
  2. Small size: Typically 0-3 variables, Vec is more efficient than HashMap
  3. Iteration simplicity: Direct iteration without collecting keys
  4. Consistency: Matches pattern of exit_bindings

Build Status

Library build: Successful (0 errors, 57 warnings) Struct extension: All constructors updated Tests: 3 new tests added for extraction function Backward compatibility: All existing code still compiles

Build command:

cargo build --release

Result:

Finished `release` profile [optimized] target(s) in 0.07s

What's Still Missing

Not Yet Implemented (Phase 171-4 scope):

  1. Condition variable registration in loop lowerers

    • loop_with_break_minimal.rs needs to call extract_condition_variables()
    • loop_with_continue_minimal.rs needs to call extract_condition_variables()
    • Pass extracted variables to boundary constructor
  2. ValueId mapping in merge logic

    • merge_joinir_mir_blocks() needs to inject Copy instructions for condition inputs
    • Instruction rewriter needs to remap condition variable ValueIds
  3. Test with actual failing case

    • test_trim_main_pattern.hako still fails (ValueId(33) undefined)
    • Need to apply the infrastructure to actual loop lowerers

Next Steps

Phase 171-4: Implement condition input mapping in merge/reconnect logic

Priority tasks:

  1. Modify loop_with_break_minimal.rs to extract and register condition variables
  2. Update merge_joinir_mir_blocks() to handle condition_inputs
  3. Inject Copy instructions for condition variables
  4. Remap ValueIds in condition evaluation instructions
  5. Test with test_trim_main_pattern.hako

Technical Notes

Extraction Algorithm Complexity

Time complexity: O(n) where n = number of AST nodes in condition Space complexity: O(v) where v = number of unique variables

Determinism guarantee: BTreeSet ensures sorted output regardless of traversal order

Edge Cases Handled

  1. No condition variables: Returns empty vector
  2. Loop variable in condition: Properly excluded (e.g., loop(i < 10))
  3. Duplicate variables: BTreeSet automatically deduplicates
  4. Nested expressions: Recursion handles arbitrary depth

Boundary Contract

Invariant: condition_inputs contains ONLY variables that:

  1. Appear in the loop condition AST
  2. Are NOT loop parameters (not in join_inputs)
  3. Exist in HOST variable_map at lowering time

Violation detection: Will be caught in Phase 171-4 when looking up HOST ValueIds


Files Modified

  1. src/mir/join_ir/lowering/inline_boundary.rs (+93 lines)

    • Added condition_inputs field
    • Added 2 new constructors
    • Updated 4 existing constructors
    • Updated test assertions
  2. src/mir/join_ir/lowering/condition_to_joinir.rs (+180 lines)

    • Added extract_condition_variables() function
    • Added collect_variables_recursive() helper
    • Added 3 comprehensive tests
  3. src/mir/builder/control_flow/joinir/patterns/exit_binding.rs (+2 lines)

    • Fixed test boundary initialization

Total: +275 lines (infrastructure only, no behavior change yet)


References

  • Phase 171-1 Analysis: phase171-1-boundary-analysis.md
  • Phase 171-2 Design: phase171-2-condition-inputs-design.md
  • JoinIR Design: docs/development/current/main/phase33-10-if-joinir-design.md