feat(joinir): Phase 201-5 Pattern 2 lowerer uses JoinValueSpace

Key changes to prevent ValueId collision between frontend and lowerer:

1. loop_step params now use ConditionEnv ValueIds (Param region: 100+)
   - i_param = env.get(loop_var_name) - ensures condition lowering works
   - carrier_param_ids from CarrierInfo.join_id

2. main() params and intermediate values use alloc_local() (Local region: 1000+)
   - No collision with frontend's param allocations

3. CarrierInfo.join_id is now properly set in frontend
   - carrier.join_id = Some(carrier_join_id) during allocation

This fixes the "use of undefined value" error where frontend allocated
ValueId(100+) but lowerer used ValueId(0+), causing remapper mismatch.

Test results:
- All 821 library tests pass
- E2E: phase200d_capture_minimal.hako outputs 30 ✓
- Pattern 4: loop_continue_pattern4.hako outputs 25 ✓
- Multi-carrier: loop_continue_multi_carrier.hako outputs 100,10 ✓

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-09 18:56:07 +09:00
parent 1af53f82a4
commit 17152bafff
2 changed files with 36 additions and 19 deletions

View File

@ -207,12 +207,14 @@ impl MirBuilder {
} }
} }
// Phase 201: Allocate carrier ValueIds from Param region // Phase 201: Allocate carrier ValueIds from Param region and SET join_id
// This ensures carriers are also in the safe Param region // This ensures carriers are also in the safe Param region
for carrier in &carrier_info.carriers { // CRITICAL: We must set join_id so the lowerer can access it
let _carrier_join_id = join_value_space.alloc_param(); for carrier in &mut carrier_info.carriers {
let carrier_join_id = join_value_space.alloc_param();
carrier.join_id = Some(carrier_join_id);
eprintln!("[pattern2/phase201] Allocated carrier '{}' param ID: {:?}", eprintln!("[pattern2/phase201] Allocated carrier '{}' param ID: {:?}",
carrier.name, _carrier_join_id); carrier.name, carrier_join_id);
} }
// Phase 191: Create empty body-local environment // Phase 191: Create empty body-local environment
@ -396,6 +398,7 @@ impl MirBuilder {
&carrier_updates, &carrier_updates,
analysis_body, // Phase 191/192: Pass normalized body AST for init lowering analysis_body, // Phase 191/192: Pass normalized body AST for init lowering
Some(&mut body_local_env), // Phase 191: Pass mutable body-local environment Some(&mut body_local_env), // Phase 191: Pass mutable body-local environment
&mut join_value_space, // Phase 201: Unified ValueId allocation (Local region)
) { ) {
Ok((module, meta)) => (module, meta), Ok((module, meta)) => (module, meta),
Err(e) => { Err(e) => {

View File

@ -59,6 +59,7 @@ use crate::ast::ASTNode;
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta, JoinFragmentMeta}; use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta, JoinFragmentMeta};
use crate::mir::join_ir::lowering::carrier_update_emitter::{emit_carrier_update, emit_carrier_update_with_env}; use crate::mir::join_ir::lowering::carrier_update_emitter::{emit_carrier_update, emit_carrier_update_with_env};
use crate::mir::join_ir::lowering::condition_to_joinir::{lower_condition_to_joinir, ConditionEnv}; use crate::mir::join_ir::lowering::condition_to_joinir::{lower_condition_to_joinir, ConditionEnv};
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv; use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
use crate::mir::join_ir::lowering::update_env::UpdateEnv; use crate::mir::join_ir::lowering::update_env::UpdateEnv;
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
@ -131,6 +132,7 @@ use std::collections::HashMap;
/// * `carrier_updates` - Phase 176-3: Update expressions for each carrier variable /// * `carrier_updates` - Phase 176-3: Update expressions for each carrier variable
/// * `body_ast` - Phase 191: Loop body AST for body-local init lowering /// * `body_ast` - Phase 191: Loop body AST for body-local init lowering
/// * `body_local_env` - Phase 185-2: Optional mutable body-local variable environment for init expressions /// * `body_local_env` - Phase 185-2: Optional mutable body-local variable environment for init expressions
/// * `join_value_space` - Phase 201: Unified JoinIR ValueId allocator (Local region: 1000+)
pub(crate) fn lower_loop_with_break_minimal( pub(crate) fn lower_loop_with_break_minimal(
_scope: LoopScopeShape, _scope: LoopScopeShape,
condition: &ASTNode, condition: &ASTNode,
@ -140,6 +142,7 @@ pub(crate) fn lower_loop_with_break_minimal(
carrier_updates: &HashMap<String, UpdateExpr>, carrier_updates: &HashMap<String, UpdateExpr>,
body_ast: &[ASTNode], body_ast: &[ASTNode],
mut body_local_env: Option<&mut LoopBodyLocalEnv>, mut body_local_env: Option<&mut LoopBodyLocalEnv>,
join_value_space: &mut JoinValueSpace,
) -> Result<(JoinModule, JoinFragmentMeta), String> { ) -> Result<(JoinModule, JoinFragmentMeta), String> {
// Phase 170-D-impl-3: Validate that conditions only use supported variable scopes // Phase 170-D-impl-3: Validate that conditions only use supported variable scopes
// LoopConditionScopeBox checks that loop conditions don't reference loop-body-local variables // LoopConditionScopeBox checks that loop conditions don't reference loop-body-local variables
@ -160,14 +163,10 @@ pub(crate) fn lower_loop_with_break_minimal(
loop_cond_scope.var_names() loop_cond_scope.var_names()
); );
// Phase 188-Impl-2: Use local ValueId allocator (sequential from 0) // Phase 201: Use JoinValueSpace for unified ValueId allocation
// JoinIR has NO knowledge of host ValueIds - boundary handled separately // - Local region (1000+) ensures no collision with Param region (100-999)
let mut value_counter = 0u32; // - Param region is used by ConditionEnv, CarrierInfo, CapturedEnv
let mut alloc_value = || { let mut alloc_value = || join_value_space.alloc_local();
let id = ValueId(value_counter);
value_counter += 1;
id
};
let mut join_module = JoinModule::new(); let mut join_module = JoinModule::new();
@ -179,7 +178,7 @@ pub(crate) fn lower_loop_with_break_minimal(
let k_exit_id = JoinFuncId::new(2); let k_exit_id = JoinFuncId::new(2);
// ================================================================== // ==================================================================
// ValueId allocation (Phase 188-Impl-2: Sequential local IDs) // ValueId allocation (Phase 201: Coordinated with ConditionEnv/CarrierInfo)
// ================================================================== // ==================================================================
// Phase 176-3: Multi-carrier support - allocate parameters for all carriers // Phase 176-3: Multi-carrier support - allocate parameters for all carriers
let carrier_count = carrier_info.carriers.len(); let carrier_count = carrier_info.carriers.len();
@ -190,21 +189,36 @@ pub(crate) fn lower_loop_with_break_minimal(
carrier_info.carriers.iter().map(|c| &c.name).collect::<Vec<_>>() carrier_info.carriers.iter().map(|c| &c.name).collect::<Vec<_>>()
); );
// main() parameters: [i_init, carrier1_init, carrier2_init, ...] // Phase 201: main() parameters use Local region (entry point slots)
let i_init = alloc_value(); // ValueId(0) - loop init value // These don't need to match ConditionEnv - they're just input slots for main
let i_init = alloc_value(); // e.g., ValueId(1000)
let mut carrier_init_ids: Vec<ValueId> = Vec::new(); let mut carrier_init_ids: Vec<ValueId> = Vec::new();
for _ in 0..carrier_count { for _ in 0..carrier_count {
carrier_init_ids.push(alloc_value()); carrier_init_ids.push(alloc_value());
} }
let loop_result = alloc_value(); // result from loop_step let loop_result = alloc_value(); // result from loop_step
// loop_step() parameters: [i_param, carrier1_param, carrier2_param, ...] // Phase 201: loop_step() parameters MUST match ConditionEnv's ValueIds!
let i_param = alloc_value(); // Parameter for loop variable // This is critical because condition lowering uses ConditionEnv to resolve variables.
// If loop_step.params[0] != env.get(loop_var_name), the condition will reference wrong ValueIds.
let i_param = env.get(loop_var_name).ok_or_else(||
format!("Phase 201: ConditionEnv missing loop variable '{}' - required for loop_step param", loop_var_name)
)?;
// Phase 201: Carrier params for loop_step - use CarrierInfo's join_id
let mut carrier_param_ids: Vec<ValueId> = Vec::new(); let mut carrier_param_ids: Vec<ValueId> = Vec::new();
for _ in 0..carrier_count { for carrier in &carrier_info.carriers {
carrier_param_ids.push(alloc_value()); let carrier_join_id = carrier.join_id.ok_or_else(||
format!("Phase 201: CarrierInfo missing join_id for carrier '{}'", carrier.name)
)?;
carrier_param_ids.push(carrier_join_id);
} }
eprintln!(
"[joinir/pattern2] Phase 201: loop_step params - i_param={:?}, carrier_params={:?}",
i_param, carrier_param_ids
);
// Phase 169 / Phase 171-fix: Lower condition using condition_to_joinir helper with ConditionEnv // Phase 169 / Phase 171-fix: Lower condition using condition_to_joinir helper with ConditionEnv
// This will allocate ValueIds dynamically based on condition complexity // This will allocate ValueIds dynamically based on condition complexity
let (cond_value, mut cond_instructions) = lower_condition_to_joinir( let (cond_value, mut cond_instructions) = lower_condition_to_joinir(