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:
@ -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
|
||||
|
||||
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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())
|
||||
|
||||
Reference in New Issue
Block a user