feat(joinir): Phase 201 JoinValueSpace - unified ValueId allocation
Phase 201 introduces JoinValueSpace to prevent ValueId collisions between Pattern 2 frontend (alloc_join_value) and JoinIR lowering (alloc_value). ValueId Space Layout: - PHI Reserved (0-99): For LoopHeader PHI dst - Param Region (100-999): For ConditionEnv, CarrierInfo, CapturedEnv - Local Region (1000+): For Const, BinOp, etc. in pattern lowerers Changes: - Add join_value_space.rs with JoinValueSpace struct (10 tests) - Add ConditionEnvBuilder v2 API using JoinValueSpace - Wire Pattern 2 frontend to use JoinValueSpace for param allocation Note: E2E tests fail until Task 201-5 wires lowerers to alloc_local() 🤖 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,7 @@ use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::lowering::condition_env::{ConditionBinding, ConditionEnv};
|
||||
use crate::mir::join_ir::lowering::condition_to_joinir::extract_condition_variables;
|
||||
use crate::mir::join_ir::lowering::inline_boundary_builder::JoinInlineBoundaryBuilder;
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
@ -125,6 +126,82 @@ impl ConditionEnvBuilder {
|
||||
env
|
||||
}
|
||||
|
||||
/// Phase 201: Build ConditionEnv using JoinValueSpace (disjoint ValueId regions)
|
||||
///
|
||||
/// This method uses JoinValueSpace to allocate ValueIds, ensuring that
|
||||
/// param IDs (100+) never collide with local IDs (1000+) used by JoinIR lowering.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `break_condition` - AST node for the break condition
|
||||
/// * `loop_var_name` - Loop parameter name (excluded from condition-only variables)
|
||||
/// * `variable_map` - HOST function's variable_map (for looking up HOST ValueIds)
|
||||
/// * `loop_var_id` - HOST ValueId for the loop parameter
|
||||
/// * `space` - JoinValueSpace for unified ValueId allocation
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Tuple of:
|
||||
/// - ConditionEnv: Variable name → JoinIR ValueId mapping
|
||||
/// - Vec<ConditionBinding>: HOST↔JoinIR value mappings for merge
|
||||
/// - loop_var_join_id: The JoinIR ValueId allocated for the loop parameter
|
||||
pub fn build_for_break_condition_v2(
|
||||
break_condition: &ASTNode,
|
||||
loop_var_name: &str,
|
||||
variable_map: &BTreeMap<String, ValueId>,
|
||||
_loop_var_id: ValueId,
|
||||
space: &mut JoinValueSpace,
|
||||
) -> Result<(ConditionEnv, Vec<ConditionBinding>, ValueId), String> {
|
||||
// Extract all variables used in the condition (excluding loop parameter)
|
||||
let condition_var_names = extract_condition_variables(
|
||||
break_condition,
|
||||
&[loop_var_name.to_string()],
|
||||
);
|
||||
|
||||
let mut env = ConditionEnv::new();
|
||||
let mut bindings = Vec::new();
|
||||
|
||||
// Phase 201: Allocate loop parameter ValueId from JoinValueSpace (Param region)
|
||||
let loop_var_join_id = space.alloc_param();
|
||||
env.insert(loop_var_name.to_string(), loop_var_join_id);
|
||||
|
||||
// For each condition variable, allocate JoinIR-local ValueId and build binding
|
||||
for var_name in &condition_var_names {
|
||||
let host_id = variable_map
|
||||
.get(var_name)
|
||||
.copied()
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Condition variable '{}' not found in variable_map. \
|
||||
Loop condition references undefined variable.",
|
||||
var_name
|
||||
)
|
||||
})?;
|
||||
|
||||
// Phase 201: Allocate from Param region to avoid collision with locals
|
||||
let join_id = space.alloc_param();
|
||||
|
||||
env.insert(var_name.clone(), join_id);
|
||||
bindings.push(ConditionBinding {
|
||||
name: var_name.clone(),
|
||||
host_value: host_id,
|
||||
join_value: join_id,
|
||||
});
|
||||
}
|
||||
|
||||
Ok((env, bindings, loop_var_join_id))
|
||||
}
|
||||
|
||||
/// Phase 201: Build ConditionEnv with loop parameter only using JoinValueSpace
|
||||
///
|
||||
/// Uses JoinValueSpace to allocate the loop parameter ValueId.
|
||||
pub fn build_loop_param_only_v2(loop_var_name: &str, space: &mut JoinValueSpace) -> (ConditionEnv, ValueId) {
|
||||
let mut env = ConditionEnv::new();
|
||||
let loop_var_join_id = space.alloc_param();
|
||||
env.insert(loop_var_name.to_string(), loop_var_join_id);
|
||||
(env, loop_var_join_id)
|
||||
}
|
||||
|
||||
/// Build ConditionEnv with optional captured variables (Phase 200-B implementation)
|
||||
///
|
||||
/// # Phase 200-B Implementation
|
||||
@ -351,4 +428,65 @@ mod tests {
|
||||
.unwrap_err()
|
||||
.contains("undefined_var"));
|
||||
}
|
||||
|
||||
/// Phase 201: Test that v2 API uses JoinValueSpace correctly
|
||||
#[test]
|
||||
fn test_build_for_break_condition_v2_uses_param_region() {
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
|
||||
// Condition: i < max
|
||||
let condition = ASTNode::BinaryOp {
|
||||
operator: crate::ast::BinaryOperator::Less,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "i".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Variable {
|
||||
name: "max".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let mut variable_map = BTreeMap::new();
|
||||
variable_map.insert("i".to_string(), ValueId(100));
|
||||
variable_map.insert("max".to_string(), ValueId(200));
|
||||
|
||||
let mut space = JoinValueSpace::new();
|
||||
let (env, bindings, loop_var_join_id) = ConditionEnvBuilder::build_for_break_condition_v2(
|
||||
&condition,
|
||||
"i",
|
||||
&variable_map,
|
||||
ValueId(100),
|
||||
&mut space,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Phase 201: Loop param should be in Param region (100+)
|
||||
assert_eq!(loop_var_join_id, ValueId(100)); // First param allocation
|
||||
assert_eq!(env.get("i"), Some(ValueId(100)));
|
||||
|
||||
// Phase 201: Condition variable should also be in Param region
|
||||
assert_eq!(env.get("max"), Some(ValueId(101))); // Second param allocation
|
||||
assert_eq!(bindings.len(), 1);
|
||||
assert_eq!(bindings[0].join_value, ValueId(101));
|
||||
|
||||
// Phase 201: Verify no collision with Local region (1000+)
|
||||
let local_id = space.alloc_local();
|
||||
assert_eq!(local_id, ValueId(1000)); // Locals start at 1000
|
||||
assert_ne!(local_id, loop_var_join_id);
|
||||
assert_ne!(local_id, bindings[0].join_value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_loop_param_only_v2() {
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
|
||||
let mut space = JoinValueSpace::new();
|
||||
let (env, loop_var_join_id) = ConditionEnvBuilder::build_loop_param_only_v2("i", &mut space);
|
||||
|
||||
// Phase 201: Should use Param region
|
||||
assert_eq!(loop_var_join_id, ValueId(100));
|
||||
assert_eq!(env.get("i"), Some(ValueId(100)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,6 +144,11 @@ impl MirBuilder {
|
||||
// Phase 195: Use unified trace
|
||||
trace::trace().varmap("pattern2_start", &self.variable_map);
|
||||
|
||||
// Phase 201: Create JoinValueSpace for unified ValueId allocation
|
||||
// This ensures Param IDs (100+) never collide with Local IDs (1000+)
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
let mut join_value_space = JoinValueSpace::new();
|
||||
|
||||
// Phase 200-C: Integrate capture analysis
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::{analyze_captured_vars_v2, CapturedEnv};
|
||||
use super::condition_env_builder::ConditionEnvBuilder;
|
||||
@ -170,21 +175,24 @@ impl MirBuilder {
|
||||
var.name, var.host_id, var.is_immutable);
|
||||
}
|
||||
|
||||
// Phase 200-C: Use existing path and manually add captured variables
|
||||
// TODO Phase 200-D: Refactor to use build_with_captures with boundary builder
|
||||
let (mut env, mut condition_bindings) = ConditionEnvBuilder::build_for_break_condition(
|
||||
// Phase 201: Use v2 API with JoinValueSpace
|
||||
// This allocates loop param and condition vars from Param region (100+)
|
||||
let (mut env, mut condition_bindings, _loop_var_join_id) = ConditionEnvBuilder::build_for_break_condition_v2(
|
||||
condition,
|
||||
&loop_var_name,
|
||||
&self.variable_map,
|
||||
loop_var_id,
|
||||
&mut join_value_space,
|
||||
)?;
|
||||
|
||||
// Phase 200-C: Manually add captured variables to env for E2E testing
|
||||
// This is a temporary approach until Phase 200-D refactors the boundary creation
|
||||
eprintln!("[pattern2/phase201] Using JoinValueSpace: loop_var '{}' → {:?}",
|
||||
loop_var_name, env.get(&loop_var_name));
|
||||
|
||||
// Phase 201: Add captured variables using JoinValueSpace
|
||||
for var in &captured_env.vars {
|
||||
if let Some(&host_id) = self.variable_map.get(&var.name) {
|
||||
// Allocate a JoinIR ValueId for this captured variable
|
||||
let join_id = crate::mir::ValueId(env.len() as u32);
|
||||
// Phase 201: Allocate from Param region to avoid collision with locals
|
||||
let join_id = join_value_space.alloc_param();
|
||||
env.insert(var.name.clone(), join_id);
|
||||
|
||||
// Add to condition_bindings for boundary processing
|
||||
@ -194,46 +202,34 @@ impl MirBuilder {
|
||||
join_value: join_id,
|
||||
});
|
||||
|
||||
eprintln!("[pattern2/capture] Manually added captured '{}' to env: host={:?}, join={:?}",
|
||||
eprintln!("[pattern2/capture] Phase 201: Added captured '{}': host={:?}, join={:?}",
|
||||
var.name, host_id, join_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 190-impl-D: Calculate ValueId offset for body-local variables
|
||||
// JoinIR main() params are: [ValueId(0), ValueId(1), ...] for (loop_var, carrier1, carrier2, ...)
|
||||
// Body-local variables must start AFTER all carrier params to avoid collision.
|
||||
// At this point carrier_info.carriers contains all potential carriers (before filtering).
|
||||
// We reserve space for: env.len() (condition vars) + carrier_info.carriers.len() (carriers)
|
||||
let body_local_start_offset = (env.len() + carrier_info.carriers.len()) as u32;
|
||||
|
||||
// Create allocator for body-local variables (starts after reserved param space)
|
||||
let mut body_local_counter = body_local_start_offset;
|
||||
let mut alloc_body_local_value = || {
|
||||
let id = crate::mir::ValueId(body_local_counter);
|
||||
body_local_counter += 1;
|
||||
id
|
||||
};
|
||||
// Phase 201: Allocate carrier ValueIds from Param region
|
||||
// This ensures carriers are also in the safe Param region
|
||||
for carrier in &carrier_info.carriers {
|
||||
let _carrier_join_id = join_value_space.alloc_param();
|
||||
eprintln!("[pattern2/phase201] Allocated carrier '{}' param ID: {:?}",
|
||||
carrier.name, _carrier_join_id);
|
||||
}
|
||||
|
||||
// Phase 191: Create empty body-local environment
|
||||
// LoopBodyLocalInitLowerer will populate it during init lowering
|
||||
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||||
let mut body_local_env = LoopBodyLocalEnv::new();
|
||||
|
||||
eprintln!("[pattern2/body-local] Phase 191: Created empty body-local environment (offset={})",
|
||||
body_local_start_offset);
|
||||
eprintln!("[pattern2/body-local] Phase 201: Created empty body-local environment (param_count={})",
|
||||
join_value_space.param_count());
|
||||
|
||||
// Create allocator for other JoinIR-local ValueIds (Trim pattern, etc.)
|
||||
// Continues from where body_local_counter left off
|
||||
let mut join_value_counter = body_local_counter;
|
||||
let mut alloc_join_value = || {
|
||||
let id = crate::mir::ValueId(join_value_counter);
|
||||
join_value_counter += 1;
|
||||
id
|
||||
};
|
||||
// Phase 201: Create alloc_join_value closure using JoinValueSpace
|
||||
// This ensures all param allocations go through the unified space
|
||||
let mut alloc_join_value = || join_value_space.alloc_param();
|
||||
|
||||
// Debug: Log condition bindings
|
||||
eprintln!("[cf_loop/pattern2] Phase 171-172: ConditionEnv contains {} variables:", env.len());
|
||||
eprintln!(" Loop param '{}' → JoinIR ValueId(0)", loop_var_name);
|
||||
eprintln!("[cf_loop/pattern2] Phase 201: ConditionEnv contains {} variables:", env.len());
|
||||
eprintln!(" Loop param '{}' → JoinIR {:?}", loop_var_name, env.get(&loop_var_name));
|
||||
if !condition_bindings.is_empty() {
|
||||
eprintln!(" {} condition-only bindings:", condition_bindings.len());
|
||||
for binding in &condition_bindings {
|
||||
|
||||
Reference in New Issue
Block a user