feat(joinir): Phase 183 LoopBodyLocal role separation + test fixes
Phase 183 Implementation: - Added is_var_used_in_condition() helper for AST variable detection - Implemented LoopBodyLocal filtering in TrimLoopLowerer - Created 4 test files for P1/P2 patterns - Added 5 unit tests for variable detection Test Fixes: - Fixed test_is_outer_scope_variable_pinned (BasicBlockId import) - Fixed test_pattern2_accepts_loop_param_only (literal node usage) Refactoring: - Unified pattern detection documentation - Consolidated CarrierInfo initialization - Documented LoopScopeShape construction paths 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -60,7 +60,7 @@ impl ConditionEnvBuilder {
|
||||
break_condition: &ASTNode,
|
||||
loop_var_name: &str,
|
||||
variable_map: &BTreeMap<String, ValueId>,
|
||||
loop_var_id: ValueId,
|
||||
_loop_var_id: ValueId,
|
||||
) -> Result<(ConditionEnv, Vec<ConditionBinding>), String> {
|
||||
// Extract all variables used in the condition (excluding loop parameter)
|
||||
let condition_var_names = extract_condition_variables(
|
||||
|
||||
@ -6,23 +6,12 @@
|
||||
//! This box fully abstractifies loop exit binding generation for Pattern 3 & 4.
|
||||
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
|
||||
use crate::mir::join_ir::lowering::inline_boundary::{
|
||||
JoinInlineBoundary, LoopExitBinding,
|
||||
};
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Mapping from JoinIR exit value to host function variable
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LoopExitBinding {
|
||||
/// Carrier variable name (e.g., "sum", "printed")
|
||||
pub carrier_name: String,
|
||||
|
||||
/// Host-side ValueId for this carrier
|
||||
pub host_id: ValueId,
|
||||
|
||||
/// Join-side exit ValueId (from ExitMeta, in JoinIR space)
|
||||
pub join_exit_id: ValueId,
|
||||
}
|
||||
|
||||
/// Builder for generating loop exit bindings
|
||||
///
|
||||
/// Phase 193-4: Fully boxifies exit binding generation.
|
||||
@ -112,8 +101,8 @@ impl<'a> ExitBindingBuilder<'a> {
|
||||
|
||||
bindings.push(LoopExitBinding {
|
||||
carrier_name: carrier.name.clone(),
|
||||
host_id: carrier.host_id,
|
||||
join_exit_id,
|
||||
join_exit_value: join_exit_id,
|
||||
host_slot: carrier.host_id,
|
||||
});
|
||||
|
||||
// Allocate new ValueId for post-loop carrier value
|
||||
@ -127,7 +116,7 @@ impl<'a> ExitBindingBuilder<'a> {
|
||||
|
||||
/// Apply bindings to JoinInlineBoundary
|
||||
///
|
||||
/// Sets host_outputs and join_outputs based on loop_var + carriers.
|
||||
/// Sets exit_bindings (and join_outputs for legacy) based on loop_var + carriers.
|
||||
/// Must be called after build_loop_exit_bindings().
|
||||
///
|
||||
/// # Arguments
|
||||
@ -138,27 +127,38 @@ impl<'a> ExitBindingBuilder<'a> {
|
||||
///
|
||||
/// Success or error if boundary cannot be updated
|
||||
pub fn apply_to_boundary(&self, boundary: &mut JoinInlineBoundary) -> Result<(), String> {
|
||||
// Always include loop_var exit first
|
||||
let mut host_outputs = vec![self.carrier_info.loop_var_id];
|
||||
let mut join_outputs = vec![self.carrier_info.loop_var_id]; // Loop var exit id in JoinIR
|
||||
// Build explicit exit bindings (loop var + carriers)
|
||||
let mut bindings = Vec::new();
|
||||
bindings.push(self.loop_var_exit_binding());
|
||||
|
||||
let mut join_outputs = vec![self.carrier_info.loop_var_id]; // legacy field for compatibility
|
||||
|
||||
// Add carrier exits in sorted order
|
||||
for carrier in &self.carrier_info.carriers {
|
||||
let post_loop_id = self.variable_map.get(&carrier.name)
|
||||
.copied()
|
||||
.ok_or_else(|| {
|
||||
format!("Post-loop ValueId not found for carrier '{}'", carrier.name)
|
||||
})?;
|
||||
let post_loop_id = self.variable_map.get(&carrier.name).copied().ok_or_else(|| {
|
||||
format!("Post-loop ValueId not found for carrier '{}'", carrier.name)
|
||||
})?;
|
||||
|
||||
let join_exit_id = self.exit_meta.find_binding(&carrier.name)
|
||||
.ok_or_else(|| format!("Exit value not found for carrier '{}'", carrier.name))?;
|
||||
let join_exit_id = self.exit_meta.find_binding(&carrier.name).ok_or_else(|| {
|
||||
format!("Exit value not found for carrier '{}'", carrier.name)
|
||||
})?;
|
||||
|
||||
bindings.push(LoopExitBinding {
|
||||
carrier_name: carrier.name.clone(),
|
||||
host_slot: post_loop_id,
|
||||
join_exit_value: join_exit_id,
|
||||
});
|
||||
|
||||
host_outputs.push(post_loop_id);
|
||||
join_outputs.push(join_exit_id);
|
||||
}
|
||||
|
||||
boundary.host_outputs = host_outputs;
|
||||
boundary.exit_bindings = bindings;
|
||||
// Deprecated fields kept in sync for legacy consumers
|
||||
let join_outputs_clone = join_outputs.clone();
|
||||
boundary.join_outputs = join_outputs;
|
||||
#[allow(deprecated)]
|
||||
{
|
||||
boundary.host_outputs = join_outputs_clone;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -169,8 +169,8 @@ impl<'a> ExitBindingBuilder<'a> {
|
||||
pub fn loop_var_exit_binding(&self) -> LoopExitBinding {
|
||||
LoopExitBinding {
|
||||
carrier_name: self.carrier_info.loop_var_name.clone(),
|
||||
host_id: self.carrier_info.loop_var_id,
|
||||
join_exit_id: self.carrier_info.loop_var_id, // Loop var maps to itself
|
||||
join_exit_value: self.carrier_info.loop_var_id, // Loop var maps to itself
|
||||
host_slot: self.carrier_info.loop_var_id,
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,8 +226,8 @@ mod tests {
|
||||
|
||||
assert_eq!(bindings.len(), 1);
|
||||
assert_eq!(bindings[0].carrier_name, "sum");
|
||||
assert_eq!(bindings[0].host_id, ValueId(10));
|
||||
assert_eq!(bindings[0].join_exit_id, ValueId(15));
|
||||
assert_eq!(bindings[0].host_slot, ValueId(10));
|
||||
assert_eq!(bindings[0].join_exit_value, ValueId(15));
|
||||
|
||||
// Check that variable_map was updated with new post-loop ValueId
|
||||
assert!(variable_map.contains_key("sum"));
|
||||
@ -400,9 +400,11 @@ mod tests {
|
||||
let mut boundary = JoinInlineBoundary {
|
||||
host_inputs: vec![],
|
||||
join_inputs: vec![],
|
||||
host_outputs: vec![],
|
||||
join_outputs: vec![],
|
||||
exit_bindings: vec![], // Phase 171: Add missing field
|
||||
#[allow(deprecated)]
|
||||
host_outputs: vec![], // legacy, unused in new assertions
|
||||
join_outputs: vec![],
|
||||
#[allow(deprecated)]
|
||||
condition_inputs: vec![], // Phase 171: Add missing field
|
||||
condition_bindings: vec![], // Phase 171-fix: Add missing field
|
||||
expr_result: None, // Phase 33-14: Add missing field
|
||||
@ -412,11 +414,15 @@ mod tests {
|
||||
builder.apply_to_boundary(&mut boundary)
|
||||
.expect("Failed to apply to boundary");
|
||||
|
||||
// Should have loop_var + sum carrier
|
||||
assert_eq!(boundary.host_outputs.len(), 2);
|
||||
assert_eq!(boundary.join_outputs.len(), 2);
|
||||
// Should have loop_var + sum carrier in exit_bindings
|
||||
assert_eq!(boundary.exit_bindings.len(), 2);
|
||||
assert_eq!(boundary.exit_bindings[0].carrier_name, "i");
|
||||
assert_eq!(boundary.exit_bindings[0].host_slot, ValueId(5));
|
||||
assert_eq!(boundary.exit_bindings[0].join_exit_value, ValueId(5));
|
||||
|
||||
assert_eq!(boundary.host_outputs[0], ValueId(5)); // loop_var
|
||||
assert_eq!(boundary.join_outputs[0], ValueId(5)); // loop_var in JoinIR
|
||||
assert_eq!(boundary.exit_bindings[1].carrier_name, "sum");
|
||||
// Post-loop carrier id is freshly allocated (10 -> 11)
|
||||
assert_eq!(boundary.exit_bindings[1].host_slot, ValueId(11));
|
||||
assert_eq!(boundary.exit_bindings[1].join_exit_value, ValueId(15));
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,7 +192,7 @@ impl MirBuilder {
|
||||
|
||||
// Extract from context
|
||||
let loop_var_name = ctx.loop_var_name.clone();
|
||||
let loop_var_id = ctx.loop_var_id;
|
||||
let _loop_var_id = ctx.loop_var_id;
|
||||
let carrier_info_prelim = ctx.carrier_info.clone();
|
||||
let scope = ctx.loop_scope.clone();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user