From 17152bafff585b7ce898ebf99194369b0917f3ba Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Tue, 9 Dec 2025 18:56:07 +0900 Subject: [PATCH] feat(joinir): Phase 201-5 Pattern 2 lowerer uses JoinValueSpace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../joinir/patterns/pattern2_with_break.rs | 11 +++-- .../lowering/loop_with_break_minimal.rs | 44 ++++++++++++------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs index 48748ea8..1636abe9 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs @@ -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 - for carrier in &carrier_info.carriers { - let _carrier_join_id = join_value_space.alloc_param(); + // CRITICAL: We must set join_id so the lowerer can access it + 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: {:?}", - carrier.name, _carrier_join_id); + carrier.name, carrier_join_id); } // Phase 191: Create empty body-local environment @@ -396,6 +398,7 @@ impl MirBuilder { &carrier_updates, analysis_body, // Phase 191/192: Pass normalized body AST for init lowering 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), Err(e) => { diff --git a/src/mir/join_ir/lowering/loop_with_break_minimal.rs b/src/mir/join_ir/lowering/loop_with_break_minimal.rs index bd573385..25b4e618 100644 --- a/src/mir/join_ir/lowering/loop_with_break_minimal.rs +++ b/src/mir/join_ir/lowering/loop_with_break_minimal.rs @@ -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_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::join_value_space::JoinValueSpace; 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::loop_scope_shape::LoopScopeShape; @@ -131,6 +132,7 @@ use std::collections::HashMap; /// * `carrier_updates` - Phase 176-3: Update expressions for each carrier variable /// * `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 +/// * `join_value_space` - Phase 201: Unified JoinIR ValueId allocator (Local region: 1000+) pub(crate) fn lower_loop_with_break_minimal( _scope: LoopScopeShape, condition: &ASTNode, @@ -140,6 +142,7 @@ pub(crate) fn lower_loop_with_break_minimal( carrier_updates: &HashMap, body_ast: &[ASTNode], mut body_local_env: Option<&mut LoopBodyLocalEnv>, + join_value_space: &mut JoinValueSpace, ) -> Result<(JoinModule, JoinFragmentMeta), String> { // 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 @@ -160,14 +163,10 @@ pub(crate) fn lower_loop_with_break_minimal( loop_cond_scope.var_names() ); - // Phase 188-Impl-2: Use local ValueId allocator (sequential from 0) - // JoinIR has NO knowledge of host ValueIds - boundary handled separately - let mut value_counter = 0u32; - let mut alloc_value = || { - let id = ValueId(value_counter); - value_counter += 1; - id - }; + // Phase 201: Use JoinValueSpace for unified ValueId allocation + // - Local region (1000+) ensures no collision with Param region (100-999) + // - Param region is used by ConditionEnv, CarrierInfo, CapturedEnv + let mut alloc_value = || join_value_space.alloc_local(); 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); // ================================================================== - // 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 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::>() ); - // main() parameters: [i_init, carrier1_init, carrier2_init, ...] - let i_init = alloc_value(); // ValueId(0) - loop init value + // Phase 201: main() parameters use Local region (entry point slots) + // 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 = Vec::new(); for _ in 0..carrier_count { carrier_init_ids.push(alloc_value()); } let loop_result = alloc_value(); // result from loop_step - // loop_step() parameters: [i_param, carrier1_param, carrier2_param, ...] - let i_param = alloc_value(); // Parameter for loop variable + // Phase 201: loop_step() parameters MUST match ConditionEnv's ValueIds! + // 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 = Vec::new(); - for _ in 0..carrier_count { - carrier_param_ids.push(alloc_value()); + for carrier in &carrier_info.carriers { + 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 // This will allocate ValueIds dynamically based on condition complexity let (cond_value, mut cond_instructions) = lower_condition_to_joinir(