feat(joinir): Phase 245C - Function parameter capture + test fix
Extend CapturedEnv to include function parameters used in loop conditions, enabling ExprLowerer to resolve variables like `s` in `loop(p < s.length())`. Phase 245C changes: - function_scope_capture.rs: Add collect_names_in_loop_parts() helper - function_scope_capture.rs: Extend analyze_captured_vars_v2() with param capture logic - function_scope_capture.rs: Add 4 new comprehensive tests Test fix: - expr_lowerer/ast_support.rs: Accept all MethodCall nodes for syntax support (validation happens during lowering in MethodCallLowerer) Problem solved: "Variable not found: s" errors in loop conditions Test results: 924/924 PASS (+13 from baseline 911) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -22,6 +22,8 @@ mod loop_header_phi_builder;
|
||||
mod tail_call_classifier;
|
||||
mod merge_result;
|
||||
mod expr_result_resolver;
|
||||
#[cfg(debug_assertions)]
|
||||
mod contract_checks;
|
||||
|
||||
// Phase 33-17: Re-export for use by other modules
|
||||
pub use loop_header_phi_info::LoopHeaderPhiInfo;
|
||||
@ -691,204 +693,6 @@ fn remap_values(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Phase 200-3: JoinIR Contract Verification
|
||||
// ============================================================================
|
||||
|
||||
/// Verify loop header PHI consistency
|
||||
///
|
||||
/// # Checks
|
||||
///
|
||||
/// 1. If loop_var_name is Some, header block must have corresponding PHI
|
||||
/// 2. All carriers in LoopHeaderPhiInfo should have PHIs in the header block
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics in debug mode if contract violations are detected.
|
||||
#[cfg(debug_assertions)]
|
||||
fn verify_loop_header_phis(
|
||||
func: &crate::mir::MirFunction,
|
||||
header_block: crate::mir::BasicBlockId,
|
||||
loop_info: &LoopHeaderPhiInfo,
|
||||
boundary: &JoinInlineBoundary,
|
||||
) {
|
||||
// Check 1: Loop variable PHI existence
|
||||
if let Some(ref loop_var_name) = boundary.loop_var_name {
|
||||
let header_block_data = func.blocks.get(&header_block).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"[JoinIRVerifier] Header block {} not found ({} blocks in func)",
|
||||
header_block,
|
||||
func.blocks.len()
|
||||
)
|
||||
});
|
||||
let has_loop_var_phi = header_block_data
|
||||
.instructions
|
||||
.iter()
|
||||
.any(|instr| matches!(instr, crate::mir::MirInstruction::Phi { .. }));
|
||||
|
||||
if !has_loop_var_phi && !loop_info.carrier_phis.is_empty() {
|
||||
panic!(
|
||||
"[JoinIRVerifier] Loop variable '{}' in boundary but no PHI in header block {} (has {} carrier PHIs)",
|
||||
loop_var_name, header_block.0, loop_info.carrier_phis.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check 2: Carrier PHI existence
|
||||
if !loop_info.carrier_phis.is_empty() {
|
||||
let header_block_data = func.blocks.get(&header_block).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"[JoinIRVerifier] Header block {} not found ({} blocks in func)",
|
||||
header_block,
|
||||
func.blocks.len()
|
||||
)
|
||||
});
|
||||
let phi_count = header_block_data
|
||||
.instructions
|
||||
.iter()
|
||||
.filter(|instr| matches!(instr, crate::mir::MirInstruction::Phi { .. }))
|
||||
.count();
|
||||
|
||||
if phi_count == 0 {
|
||||
panic!(
|
||||
"[JoinIRVerifier] LoopHeaderPhiInfo has {} PHIs but header block {} has none",
|
||||
loop_info.carrier_phis.len(),
|
||||
header_block.0
|
||||
);
|
||||
}
|
||||
|
||||
// Verify each carrier has a corresponding PHI
|
||||
for (carrier_name, entry) in &loop_info.carrier_phis {
|
||||
let phi_exists = header_block_data.instructions.iter().any(|instr| {
|
||||
if let crate::mir::MirInstruction::Phi { dst, .. } = instr {
|
||||
*dst == entry.phi_dst
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if !phi_exists {
|
||||
panic!(
|
||||
"[JoinIRVerifier] Carrier '{}' has PHI dst {:?} but PHI not found in header block {}",
|
||||
carrier_name, entry.phi_dst, header_block.0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify exit line consistency
|
||||
///
|
||||
/// # Checks
|
||||
///
|
||||
/// 1. All exit_bindings in boundary should have corresponding values
|
||||
/// 2. Exit block should exist and be in range
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics in debug mode if contract violations are detected.
|
||||
#[cfg(debug_assertions)]
|
||||
fn verify_exit_line(
|
||||
func: &crate::mir::MirFunction,
|
||||
exit_block: crate::mir::BasicBlockId,
|
||||
boundary: &JoinInlineBoundary,
|
||||
) {
|
||||
// Check 1: Exit block exists
|
||||
if !func.blocks.contains_key(&exit_block) {
|
||||
panic!(
|
||||
"[JoinIRVerifier] Exit block {} out of range (func has {} blocks)",
|
||||
exit_block.0,
|
||||
func.blocks.len()
|
||||
);
|
||||
}
|
||||
|
||||
// Check 2: Exit bindings reference valid values
|
||||
if !boundary.exit_bindings.is_empty() {
|
||||
for binding in &boundary.exit_bindings {
|
||||
// Verify host_slot is reasonable (basic sanity check)
|
||||
// We can't verify the exact value since it's from the host's value space,
|
||||
// but we can check it's not obviously invalid
|
||||
if binding.host_slot.0 >= 1000000 {
|
||||
// Arbitrary large number check
|
||||
panic!(
|
||||
"[JoinIRVerifier] Exit binding '{}' has suspiciously large host_slot {:?}",
|
||||
binding.carrier_name, binding.host_slot
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 205-4: Verify ValueId regions follow JoinValueSpace contracts
|
||||
///
|
||||
/// # Checks
|
||||
///
|
||||
/// 1. All `boundary.join_inputs` are in Param region (100-999)
|
||||
/// 2. All `carrier_phis[].phi_dst` are within valid range (<= LOCAL_MAX)
|
||||
/// 3. All `condition_bindings[].join_value` are in Param region
|
||||
///
|
||||
/// # Rationale
|
||||
///
|
||||
/// JoinValueSpace enforces disjoint regions (Param: 100-999, Local: 1000+)
|
||||
/// to prevent ValueId collisions. This verifier ensures that the boundary
|
||||
/// contracts are respected after JoinIR generation.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics in debug mode if any ValueId is in an unexpected region.
|
||||
#[cfg(debug_assertions)]
|
||||
fn verify_valueid_regions(
|
||||
loop_info: &LoopHeaderPhiInfo,
|
||||
boundary: &JoinInlineBoundary,
|
||||
) {
|
||||
use crate::mir::join_ir::lowering::join_value_space::{PARAM_MIN, PARAM_MAX, LOCAL_MAX};
|
||||
|
||||
// Helper to classify region
|
||||
fn region_name(id: ValueId) -> &'static str {
|
||||
if id.0 < PARAM_MIN {
|
||||
"PHI Reserved"
|
||||
} else if id.0 <= PARAM_MAX {
|
||||
"Param"
|
||||
} else if id.0 <= LOCAL_MAX {
|
||||
"Local"
|
||||
} else {
|
||||
"Invalid (> LOCAL_MAX)"
|
||||
}
|
||||
}
|
||||
|
||||
// Check 1: Boundary join_inputs must be in Param region
|
||||
for join_id in &boundary.join_inputs {
|
||||
if join_id.0 < PARAM_MIN || join_id.0 > PARAM_MAX {
|
||||
panic!(
|
||||
"[RegionVerifier] Boundary input {:?} is in {} region, expected Param (100-999)",
|
||||
join_id, region_name(*join_id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check 2: Condition bindings must be in Param region
|
||||
for binding in &boundary.condition_bindings {
|
||||
let join_value = binding.join_value;
|
||||
if join_value.0 < PARAM_MIN || join_value.0 > PARAM_MAX {
|
||||
panic!(
|
||||
"[RegionVerifier] Condition binding '{}' join_value {:?} is in {} region, expected Param (100-999)",
|
||||
binding.name, join_value, region_name(join_value)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check 3: PHI dst must be within valid range
|
||||
for (carrier_name, entry) in &loop_info.carrier_phis {
|
||||
let phi_dst = entry.phi_dst;
|
||||
if phi_dst.0 > LOCAL_MAX {
|
||||
panic!(
|
||||
"[RegionVerifier] Carrier '{}' PHI dst {:?} exceeds LOCAL_MAX ({})",
|
||||
carrier_name, phi_dst, LOCAL_MAX
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify that PHI dst values are not overwritten by later instructions (Phase 204-2)
|
||||
///
|
||||
/// # Checks
|
||||
@ -1059,9 +863,9 @@ fn verify_joinir_contracts(
|
||||
loop_info: &LoopHeaderPhiInfo,
|
||||
boundary: &JoinInlineBoundary,
|
||||
) {
|
||||
verify_loop_header_phis(func, header_block, loop_info, boundary);
|
||||
contract_checks::verify_loop_header_phis(func, header_block, loop_info, boundary);
|
||||
verify_no_phi_dst_overwrite(func, header_block, loop_info); // Phase 204-2
|
||||
verify_phi_inputs_defined(func, header_block); // Phase 204-3
|
||||
verify_exit_line(func, exit_block, boundary);
|
||||
verify_valueid_regions(loop_info, boundary); // Phase 205-4
|
||||
contract_checks::verify_exit_line(func, exit_block, boundary);
|
||||
contract_checks::verify_valueid_regions(loop_info, boundary); // Phase 205-4
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user