feat(joinir): Phase 176 Pattern2 multi-carrier lowering complete

Task 176-1: Pattern2 limitation investigation
- Identified 10 limitation points where only position carrier was handled
- Added TODO markers for Phase 176-2/3 implementation
- Created phase176-pattern2-limitations.md documentation

Task 176-2: CarrierUpdateLowerer helper implementation
- Implemented emit_carrier_update() helper function
- Supports CounterLike and AccumulationLike UpdateExpr patterns
- Added 6 unit tests (all passing)
- Fail-Fast error handling for carrier/variable not found

Task 176-3: Pattern2 lowerer multi-carrier extension
- Extended header PHI generation for all carriers
- Implemented loop update for all carriers using emit_carrier_update()
- Extended ExitLine/ExitMeta construction for all carriers
- Updated function call/jump args to include all carriers
- 9/10 tests passing (1 pre-existing test issue)

Task 176-4: E2E testing and bug fixes
- Fixed Trim pattern loop_var_name overwrite bug (pattern2_with_break.rs)
- Fixed InstructionRewriter latch_incoming mapping bug
- All E2E tests passing (RC=0): pos + result dual-carrier loops work
- test_jsonparser_parse_string_min2.hako verified

Task 176-5: Documentation updates
- Created phase176-completion-report.md
- Updated phase175-multicarrier-design.md with completion status
- Updated joinir-architecture-overview.md roadmap
- Updated CURRENT_TASK.md with Phase 176 completion + Phase 177 TODO
- Updated loop_pattern_space.md F-axis (multi-carrier support complete)

Technical achievements:
- Pattern2 now handles single/multiple carriers uniformly
- CarrierInfo architecture proven to work end-to-end
- Two critical bugs fixed (loop_var overwrite, latch_incoming mapping)
- No regressions in existing tests

Next: Phase 177 - Apply to JsonParser _parse_string full implementation
This commit is contained in:
nyash-codex
2025-12-08 15:17:53 +09:00
parent 24aa8ced75
commit 99d329096f
13 changed files with 1345 additions and 55 deletions

View File

@ -82,6 +82,11 @@ impl ExitMetaCollector {
// Iterate over ExitMeta entries and build bindings
for (carrier_name, join_exit_value) in &exit_meta.exit_values {
eprintln!(
"[cf_loop/exit_line] ExitMetaCollector DEBUG: Checking carrier '{}' in variable_map",
carrier_name
);
// Look up host slot from variable_map
if let Some(&host_slot) = builder.variable_map.get(carrier_name) {
let binding = LoopExitBinding {
@ -90,15 +95,13 @@ impl ExitMetaCollector {
host_slot,
};
if debug {
eprintln!(
"[cf_loop/exit_line] ExitMetaCollector: Collected '{}' JoinIR {:?} → HOST {:?}",
carrier_name, join_exit_value, host_slot
);
}
eprintln!(
"[cf_loop/exit_line] ExitMetaCollector: Collected '{}' JoinIR {:?} → HOST {:?}",
carrier_name, join_exit_value, host_slot
);
bindings.push(binding);
} else if debug {
} else {
eprintln!(
"[cf_loop/exit_line] ExitMetaCollector DEBUG: Carrier '{}' not in variable_map (skip)",
carrier_name

View File

@ -394,11 +394,27 @@ pub(super) fn merge_and_rewrite(
}
// Phase 33-20: Also set latch incoming for other carriers from exit_bindings
// The exit_bindings are ordered to match args[1..] (after the loop variable)
for (idx, binding) in b.exit_bindings.iter().enumerate() {
let arg_idx = idx + 1; // +1 because args[0] is the loop variable
if arg_idx < args.len() {
let latch_value = args[arg_idx];
// Phase 176-4 FIX: exit_bindings may include the loop variable itself
// We need to skip it since it's already handled above via boundary.loop_var_name
// The remaining non-loop-variable carriers are ordered to match args[1..] (after the loop variable)
let mut carrier_arg_idx = 1; // Start from args[1] (args[0] is loop variable)
for binding in b.exit_bindings.iter() {
// Skip if this binding is for the loop variable (already handled)
if let Some(ref loop_var) = b.loop_var_name {
if &binding.carrier_name == loop_var {
if debug {
eprintln!(
"[cf_loop/joinir] Phase 176-4: Skipping loop variable '{}' in exit_bindings (handled separately)",
binding.carrier_name
);
}
continue; // Skip loop variable
}
}
// Process non-loop-variable carrier
if carrier_arg_idx < args.len() {
let latch_value = args[carrier_arg_idx];
loop_header_phi_info.set_latch_incoming(
&binding.carrier_name,
new_block_id,
@ -407,14 +423,15 @@ pub(super) fn merge_and_rewrite(
if debug {
eprintln!(
"[cf_loop/joinir] Phase 33-20: Set latch incoming for carrier '{}': block={:?}, value={:?} (arg[{}])",
binding.carrier_name, new_block_id, latch_value, arg_idx
"[cf_loop/joinir] Phase 176-4: Set latch incoming for carrier '{}': block={:?}, value={:?} (arg[{}])",
binding.carrier_name, new_block_id, latch_value, carrier_arg_idx
);
}
carrier_arg_idx += 1;
} else if debug {
eprintln!(
"[cf_loop/joinir] Phase 33-20 WARNING: No arg for carrier '{}' at index {}",
binding.carrier_name, arg_idx
binding.carrier_name, carrier_arg_idx
);
}
}

View File

@ -157,6 +157,18 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
// Phase 33-20: Extract other carriers from exit_bindings
// Skip the loop variable (it's handled separately) and collect other carriers
eprintln!(
"[cf_loop/joinir] Phase 33-20 DEBUG: exit_bindings count={}, loop_var_name={:?}",
boundary.exit_bindings.len(),
loop_var_name
);
for b in boundary.exit_bindings.iter() {
eprintln!(
"[cf_loop/joinir] Phase 33-20 DEBUG: exit_binding: carrier_name={:?}, host_slot={:?}",
b.carrier_name, b.host_slot
);
}
let other_carriers: Vec<(String, ValueId)> = boundary.exit_bindings
.iter()
.filter(|b| b.carrier_name != *loop_var_name)

View File

@ -65,6 +65,11 @@ impl MirBuilder {
None, // Pattern 2 handles break-triggered vars via condition_bindings
)?;
eprintln!(
"[pattern2/init] CommonPatternInitializer returned: loop_var='{}', loop_var_id={:?}, carriers={}",
loop_var_name, loop_var_id, carrier_info.carriers.len()
);
// Phase 195: Use unified trace
trace::trace().varmap("pattern2_start", &self.variable_map);
@ -175,8 +180,22 @@ impl MirBuilder {
// Phase 171-C-4: Convert to CarrierInfo and merge
let promoted_carrier = trim_info.to_carrier_info();
eprintln!(
"[pattern2/promoter] Phase 171-C-4 DEBUG: BEFORE merge - carrier_info.loop_var_name='{}'",
carrier_info.loop_var_name
);
eprintln!(
"[pattern2/promoter] Phase 171-C-4 DEBUG: promoted_carrier.loop_var_name='{}'",
promoted_carrier.loop_var_name
);
carrier_info.merge_from(&promoted_carrier);
eprintln!(
"[pattern2/promoter] Phase 171-C-4 DEBUG: AFTER merge - carrier_info.loop_var_name='{}'",
carrier_info.loop_var_name
);
eprintln!(
"[pattern2/promoter] Phase 171-C-4: Merged carrier '{}' into CarrierInfo (total carriers: {})",
trim_info.carrier_name,
@ -248,12 +267,12 @@ impl MirBuilder {
eprintln!("[pattern2/trim] Registered carrier '{}' in variable_map", carrier_name);
// Update carrier_info with actual ValueId
carrier_info.loop_var_id = is_ch_match0;
carrier_info.loop_var_name = carrier_name.clone();
// Note: DO NOT overwrite carrier_info.loop_var_id/loop_var_name here!
// The loop variable is 'pos' (counter), not 'is_ch_match' (carrier).
// carrier_info.loop_var_name should remain as the original loop variable.
eprintln!("[pattern2/trim] Updated carrier_info: loop_var='{}', loop_var_id={:?}",
carrier_info.loop_var_name, carrier_info.loop_var_id);
eprintln!("[pattern2/trim] Carrier registered. Loop var='{}' remains unchanged",
carrier_info.loop_var_name);
// Phase 172-4: Break condition will be replaced below after JoinIR generation
eprintln!("[pattern2/trim] Trim pattern lowering enabled, proceeding to JoinIR generation");
@ -298,6 +317,15 @@ impl MirBuilder {
eprintln!("[pattern2/trim] Added carrier '{}' to ConditionEnv: HOST {:?} → JoinIR {:?}",
helper.carrier_name, condition_bindings.last().unwrap().host_value, condition_bindings.last().unwrap().join_value);
// Phase 176-6: Also map the original variable name to the same JoinIR ValueId
// This allows the loop body to reference the original variable (e.g., 'ch')
// even though it was promoted to a carrier (e.g., 'is_ch_match')
let join_value = condition_bindings.last().unwrap().join_value;
env.insert(helper.original_var.clone(), join_value);
eprintln!("[pattern2/trim] Phase 176-6: Also mapped original var '{}' → JoinIR {:?}",
helper.original_var, join_value);
// Generate negated carrier check: !is_ch_match
let negated_carrier = TrimPatternLowerer::generate_trim_break_condition(helper);
@ -310,9 +338,71 @@ impl MirBuilder {
break_condition_node.clone()
};
// Phase 176-3: Analyze carrier updates from loop body
use crate::mir::join_ir::lowering::loop_update_analyzer::LoopUpdateAnalyzer;
let carrier_updates = LoopUpdateAnalyzer::analyze_carrier_updates(_body, &carrier_info.carriers);
eprintln!(
"[cf_loop/pattern2] Phase 176-3: Analyzed {} carrier updates",
carrier_updates.len()
);
// Phase 176-4: Filter carriers to only include those with actual updates
// Issue: CommonPatternInitializer includes all variables in variable_map as carriers,
// but only variables with updates in the loop body are true carriers.
// Condition-only variables (like 'len', 's') should be excluded.
let original_carrier_count = carrier_info.carriers.len();
carrier_info.carriers.retain(|carrier| {
carrier_updates.contains_key(&carrier.name)
});
eprintln!(
"[cf_loop/pattern2] Phase 176-4: Filtered carriers: {}{} (kept only carriers with updates)",
original_carrier_count,
carrier_info.carriers.len()
);
// Phase 176-5: Add body-only carriers to ConditionEnv
// Issue: Carriers that are updated in the loop body but not used in the condition
// need to be added to ConditionEnv with their initial values.
for carrier in &carrier_info.carriers {
if env.get(&carrier.name).is_none() {
// Allocate a new JoinIR ValueId for this carrier
let join_value = alloc_join_value();
// Add to ConditionEnv
env.insert(carrier.name.clone(), join_value);
// Add to condition_bindings for later processing
condition_bindings.push(ConditionBinding {
name: carrier.name.clone(),
host_value: carrier.host_id,
join_value,
});
eprintln!(
"[cf_loop/pattern2] Phase 176-5: Added body-only carrier '{}' to ConditionEnv: HOST {:?} → JoinIR {:?}",
carrier.name, carrier.host_id, join_value
);
}
}
// Phase 169 / Phase 171-fix / Phase 172-3 / Phase 170-B: Call Pattern 2 lowerer with break_condition
// Phase 33-14: Now returns (JoinModule, JoinFragmentMeta) for expr_result + carrier separation
let (join_module, fragment_meta) = match lower_loop_with_break_minimal(scope, condition, &effective_break_condition, &env, &loop_var_name) {
// Phase 176-3: Multi-carrier support - pass carrier_info and carrier_updates
eprintln!(
"[pattern2/before_lowerer] About to call lower_loop_with_break_minimal with carrier_info.loop_var_name='{}'",
carrier_info.loop_var_name
);
let (join_module, fragment_meta) = match lower_loop_with_break_minimal(
scope,
condition,
&effective_break_condition,
&env,
&carrier_info,
&carrier_updates,
) {
Ok((module, meta)) => (module, meta),
Err(e) => {
// Phase 195: Use unified trace
@ -328,13 +418,30 @@ impl MirBuilder {
use crate::mir::builder::control_flow::joinir::merge::exit_line::ExitMetaCollector;
let exit_bindings = ExitMetaCollector::collect(self, &exit_meta, debug);
// Phase 176-3: Build input mappings for all carriers
// JoinIR main() params: [ValueId(0), ValueId(1), ValueId(2), ...] for (i, carrier1, carrier2, ...)
// Host values: [loop_var_id, carrier1_host_id, carrier2_host_id, ...]
let mut join_input_slots = vec![ValueId(0)]; // Loop variable
let mut host_input_values = vec![loop_var_id]; // Loop variable
for (idx, carrier) in carrier_info.carriers.iter().enumerate() {
join_input_slots.push(ValueId((idx + 1) as u32));
host_input_values.push(carrier.host_id);
}
eprintln!(
"[cf_loop/pattern2] Phase 176-3: Boundary inputs - {} JoinIR slots, {} host values",
join_input_slots.len(),
host_input_values.len()
);
// Phase 200-2: Use JoinInlineBoundaryBuilder for clean construction
// Canonical Builder pattern - see docs/development/current/main/joinir-boundary-builder-pattern.md
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
let boundary = JoinInlineBoundaryBuilder::new()
.with_inputs(
vec![ValueId(0)], // JoinIR's main() parameter (loop variable init)
vec![loop_var_id], // Host's loop variable
join_input_slots, // JoinIR's main() parameters (loop variable + carriers)
host_input_values, // Host's loop variable + carrier values
)
.with_condition_bindings(condition_bindings)
.with_exit_bindings(exit_bindings.clone())