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

@ -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())