feat(joinir): Phase 171-fix ConditionEnv/ConditionBinding architecture

Proper HOST↔JoinIR ValueId separation for condition variables:

- Add ConditionEnv struct (name → JoinIR-local ValueId mapping)
- Add ConditionBinding struct (HOST/JoinIR ValueId pairs)
- Modify condition_to_joinir to use ConditionEnv instead of builder.variable_map
- Update Pattern2 lowerer to build ConditionEnv and ConditionBindings
- Extend JoinInlineBoundary with condition_bindings field
- Update BoundaryInjector to inject Copy instructions for condition variables

This fixes the undefined ValueId errors where HOST ValueIds were being
used directly in JoinIR instructions. Programs now execute (RC: 0),
though loop variable exit values still need Phase 172 work.

Key invariants established:
1. JoinIR uses ONLY JoinIR-local ValueIds
2. HOST↔JoinIR bridging is ONLY through JoinInlineBoundary
3. condition_to_joinir NEVER accesses builder.variable_map

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-07 01:45:03 +09:00
parent b8a9d08894
commit e30116f53d
27 changed files with 5419 additions and 85 deletions

View File

@ -391,12 +391,29 @@ pub(super) fn merge_and_rewrite(
// Create HashMap from remapper for BoundaryInjector (temporary adapter)
let mut value_map_for_injector = HashMap::new();
// Phase 171-fix: Add join_inputs to value_map
for join_in in &boundary.join_inputs {
if let Some(remapped) = remapper.get_value(*join_in) {
value_map_for_injector.insert(*join_in, remapped);
}
}
// Phase 171-fix: Add condition_bindings to value_map
// Each binding specifies JoinIR ValueId → HOST ValueId mapping
// We remap the JoinIR ValueId to a new MIR ValueId
for binding in &boundary.condition_bindings {
if let Some(remapped) = remapper.get_value(binding.join_value) {
value_map_for_injector.insert(binding.join_value, remapped);
if debug {
eprintln!(
"[cf_loop/joinir] Phase 171-fix: Condition binding '{}': JoinIR {:?} → remapped {:?} (HOST {:?})",
binding.name, binding.join_value, remapped, binding.host_value
);
}
}
}
// Use BoundaryInjector to inject Copy instructions
if let Some(ref mut current_func) = builder.current_function {
BoundaryInjector::inject_boundary_copies(

View File

@ -74,9 +74,22 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
let mut remapper = block_allocator::allocate_blocks(builder, mir_module, debug)?;
// Phase 2: Collect values from all functions
let (used_values, value_to_func_name, function_params) =
let (mut used_values, value_to_func_name, function_params) =
value_collector::collect_values(mir_module, &remapper, debug)?;
// Phase 171-fix: Add condition_bindings' join_values to used_values for remapping
if let Some(boundary) = boundary {
for binding in &boundary.condition_bindings {
if debug {
eprintln!(
"[cf_loop/joinir] Phase 171-fix: Adding condition binding '{}' JoinIR {:?} to used_values",
binding.name, binding.join_value
);
}
used_values.insert(binding.join_value);
}
}
// Phase 3: Remap ValueIds
remap_values(builder, &used_values, &mut remapper, debug)?;

View File

@ -385,6 +385,8 @@ mod tests {
join_inputs: vec![],
host_outputs: vec![],
join_outputs: vec![],
exit_bindings: vec![], // Phase 171: Add missing field
condition_inputs: vec![], // Phase 171: Add missing field
};
builder.apply_to_boundary(&mut boundary)

View File

@ -70,6 +70,61 @@ impl MirBuilder {
// Phase 195: Use unified trace
trace::trace().varmap("pattern2_start", &self.variable_map);
// Phase 171-fix: Build ConditionEnv and ConditionBindings
use crate::mir::join_ir::lowering::condition_to_joinir::{
extract_condition_variables, ConditionEnv, ConditionBinding,
};
let condition_var_names = extract_condition_variables(condition, &[loop_var_name.clone()]);
let mut env = ConditionEnv::new();
let mut condition_bindings = Vec::new();
// Phase 171-fix: Add loop parameter to env (ValueId(0) in JoinIR space)
// The loop parameter is NOT a condition binding (it's a join_input instead)
env.insert(loop_var_name.clone(), crate::mir::ValueId(0));
// Create a local allocator for JoinIR-local ValueIds for condition-only variables
let mut join_value_counter = 1u32; // Start from 1 (0 is reserved for loop param)
let mut alloc_join_value = || {
let id = crate::mir::ValueId(join_value_counter);
join_value_counter += 1;
id
};
// For each condition variable, allocate JoinIR-local ValueId and build binding
for var_name in &condition_var_names {
let host_id = self.variable_map.get(var_name)
.copied()
.ok_or_else(|| {
format!(
"[cf_loop/pattern2] Condition variable '{}' not found in variable_map. \
Loop condition references undefined variable.",
var_name
)
})?;
let join_id = alloc_join_value(); // Allocate JoinIR-local ValueId
env.insert(var_name.clone(), join_id);
condition_bindings.push(ConditionBinding {
name: var_name.clone(),
host_value: host_id,
join_value: join_id,
});
}
// Phase 171-fix Debug: Log condition bindings
eprintln!("[cf_loop/pattern2] Phase 171-fix: ConditionEnv contains {} variables:", env.name_to_join.len());
eprintln!(" Loop param '{}' → JoinIR ValueId(0)", loop_var_name);
if !condition_bindings.is_empty() {
eprintln!(" {} condition-only bindings:", condition_bindings.len());
for binding in &condition_bindings {
eprintln!(" '{}': HOST {:?} → JoinIR {:?}", binding.name, binding.host_value, binding.join_value);
}
} else {
eprintln!(" No condition-only variables");
}
// Create a minimal LoopScopeShape (Phase 188: hardcoded for joinir_min_loop.hako)
// Pattern 2 lowerer ignores the scope anyway, so this is just a placeholder
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
@ -86,13 +141,13 @@ impl MirBuilder {
variable_definitions: BTreeMap::new(),
};
// Call Pattern 2 lowerer
let join_module = match lower_loop_with_break_minimal(scope) {
Some(module) => module,
None => {
// Phase 169 / Phase 171-fix: Call Pattern 2 lowerer with ConditionEnv
let join_module = match lower_loop_with_break_minimal(scope, condition, &env) {
Ok(module) => module,
Err(e) => {
// Phase 195: Use unified trace
trace::trace().debug("pattern2", "Pattern 2 lowerer returned None");
return Ok(None);
trace::trace().debug("pattern2", &format!("Pattern 2 lowerer failed: {}", e));
return Err(format!("[cf_loop/pattern2] Lowering failed: {}", e));
}
};
@ -119,11 +174,19 @@ impl MirBuilder {
);
// Merge JoinIR blocks into current function
// Phase 188-Impl-2: Create and pass JoinInlineBoundary for Pattern 2
let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only(
vec![ValueId(0)], // JoinIR's main() parameter (loop variable init)
vec![loop_var_id], // Host's loop variable
);
// Phase 171-fix: Create boundary with ConditionBindings
let boundary = if condition_bindings.is_empty() {
crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only(
vec![ValueId(0)], // JoinIR's main() parameter (loop variable init)
vec![loop_var_id], // Host's loop variable
)
} else {
crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_with_condition_bindings(
vec![ValueId(0)], // JoinIR's main() parameter (loop variable init)
vec![loop_var_id], // Host's loop variable
condition_bindings, // Phase 171-fix: ConditionBindings with JoinIR ValueIds
)
};
// Phase 189: Discard exit PHI result (Pattern 2 returns void)
let _ = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;

View File

@ -181,13 +181,13 @@ impl MirBuilder {
variable_definitions: BTreeMap::new(),
};
// Call Pattern 4 lowerer (Phase 197: pass carrier_updates for semantic correctness)
let (join_module, exit_meta) = match lower_loop_with_continue_minimal(scope, &carrier_info, &carrier_updates) {
Some(result) => result,
None => {
// Phase 169: Call Pattern 4 lowerer with condition AST
let (join_module, exit_meta) = match lower_loop_with_continue_minimal(scope, condition, self, &carrier_info, &carrier_updates) {
Ok(result) => result,
Err(e) => {
// Phase 195: Use unified trace
trace::trace().debug("pattern4", "Pattern 4 lowerer returned None");
return Ok(None);
trace::trace().debug("pattern4", &format!("Pattern 4 lowerer failed: {}", e));
return Err(format!("[cf_loop/pattern4] Lowering failed: {}", e));
}
};

View File

@ -40,6 +40,7 @@ impl MirBuilder {
// - Core OFF では従来通り dev フラグで opt-in
// Note: Arity does NOT include implicit `me` receiver
// Phase 188: Add "main" routing for loop pattern expansion
// Phase 170: Add JsonParserBox methods for selfhost validation
let core_on = crate::config::env::joinir_core_enabled();
let is_target = match func_name.as_str() {
"main" => true, // Phase 188-Impl-1: Enable JoinIR for main function (Pattern 1)
@ -64,6 +65,16 @@ impl MirBuilder {
== Some("1")
}
}
// Phase 170-A-1: Enable JsonParserBox methods for JoinIR routing
"JsonParserBox._trim/1" => true,
"JsonParserBox._skip_whitespace/2" => true,
"JsonParserBox._match_literal/2" => true,
"JsonParserBox._parse_string/2" => true,
"JsonParserBox._parse_array/2" => true,
"JsonParserBox._parse_object/2" => true,
// Phase 170-A-1: Test methods (simplified versions)
"TrimTest.trim/1" => true,
"Main.trim/1" => true, // Phase 171-fix: Main box variant
_ => false,
};