feat(joinir): Phase 286 P2.6.1 - Pattern3 Plan 完走(normalizer 実装 + Fail-Fast 統一)

## 変更内容

### Router Fail-Fast 統一
- Pattern3 stub fallback 特例を撤去
- extract 成功 → normalize/lower 失敗は即 Err(他パターンと統一)

### normalize_pattern3_if_phi() 実装
- CFG 構造: 8 blocks (preheader, header, body, then, else, merge, step, after)
- PHI 構成: 3本(header×2 + merge×1)
  - Header: loop_var_current, carrier_current
  - Merge: carrier_next (if-else 合流)
- Frag: BranchStub×2 + EdgeStub×4(Pattern1 流儀の直接構築)

### 発見・修正
- lowerer は body_bb の block_effects を無視して loop_plan.body を emit
- body_bb effects は CorePlan::Effect(...) として loop_plan.body に配置

## テスト結果
- Phase 118 smoke: PASS (出力 12)
- quick: 154/154 PASS
- Plan line 完走確認: route=plan ... Pattern3_IfPhi MATCHED

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-26 04:39:36 +09:00
parent 3abca63ebe
commit 0fca2df5be
3 changed files with 416 additions and 26 deletions

View File

@ -281,25 +281,8 @@ fn try_plan_extractors(
let log_msg = format!("route=plan strategy=extract pattern={}", entry.name);
trace::trace().pattern("route", &log_msg, true);
// Phase 286 P2.6: Pattern3 PoC exception - allow fallback to legacy
// Pattern3 extractor validates structure, but normalizer is stub
if entry.name.contains("Pattern3") {
match lower_via_plan(builder, domain_plan, ctx) {
Ok(value_id) => return Ok(value_id),
Err(_) => {
if ctx.debug {
trace::trace().debug(
"route/plan",
"Pattern3 Plan normalization stub - fallback to legacy Pattern3",
);
}
continue; // Try next extractor (will eventually reach legacy Pattern3)
}
}
}
// Phase 286 P2.4.1: Fail-Fast - extract 成功 → normalize/lower 失敗は即 Err
// 構造的 Fail-Fast: 文字列判定なし、extract が Some なら fallback 禁止
// Phase 286 P2.6.1: Fail-Fast 統一 - extract 成功 → normalize/lower 失敗は即 Err
// Pattern3 stub fallback 撤去(normalizer 実装済み)
return lower_via_plan(builder, domain_plan, ctx);
} else {
// Extraction returned None - try next extractor

View File

@ -2066,14 +2066,301 @@ impl PlanNormalizer {
/// ↓
/// back-edge to header
/// ```
/// Phase 286 P2.6.1: Pattern3IfPhi → CorePlan 変換
///
/// Expands Pattern3 (Loop with If-Phi) semantics into generic CorePlan:
/// - CFG structure: preheader → header → body → then/else → merge → step → header
/// - 3 PHIs: 2 in header (loop_var, carrier), 1 in merge (carrier_next)
/// - If-else branching with PHI merge (no Select instruction)
fn normalize_pattern3_if_phi(
_builder: &mut MirBuilder,
_parts: Pattern3IfPhiPlan,
_ctx: &LoopPatternContext,
builder: &mut MirBuilder,
parts: Pattern3IfPhiPlan,
ctx: &LoopPatternContext,
) -> Result<CorePlan, String> {
// Phase 286 P2.6 PoC: Stub implementation
// Extractor validates Pattern3 structure, but normalization is deferred
// Fallback to legacy Pattern3 implementation via Ok(None) in router
Err("Pattern3 Plan normalization not yet implemented (PoC stub - fallback to legacy)".to_string())
use crate::mir::builder::control_flow::joinir::trace;
let trace_logger = trace::trace();
let debug = ctx.debug;
if debug {
trace_logger.debug(
"normalizer/pattern3_if_phi",
&format!(
"Phase 286 P2.6.1: Normalizing Pattern3IfPhi for {} (loop_var: {}, carrier_var: {})",
ctx.func_name, parts.loop_var, parts.carrier_var
),
);
}
// Step 1: Get host ValueIds for variables
let loop_var_init = builder
.variable_ctx
.variable_map
.get(&parts.loop_var)
.copied()
.ok_or_else(|| format!("[normalizer] Loop variable {} not found", parts.loop_var))?;
let carrier_init = builder
.variable_ctx
.variable_map
.get(&parts.carrier_var)
.copied()
.ok_or_else(|| format!("[normalizer] Carrier variable {} not found", parts.carrier_var))?;
// Step 2: Capture preheader block
let preheader_bb = builder
.current_block
.ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?;
// Step 3: Allocate BasicBlockIds for 8 blocks
let header_bb = builder.next_block_id();
let body_bb = builder.next_block_id();
let then_bb = builder.next_block_id();
let else_bb = builder.next_block_id();
let merge_bb = builder.next_block_id();
let step_bb = builder.next_block_id();
let after_bb = builder.next_block_id();
if debug {
trace_logger.debug(
"normalizer/pattern3_if_phi",
&format!(
"Allocated: preheader={:?}, header={:?}, body={:?}, then={:?}, else={:?}, merge={:?}, step={:?}, after={:?}",
preheader_bb, header_bb, body_bb, then_bb, else_bb, merge_bb, step_bb, after_bb
),
);
}
// Step 4: Allocate ValueIds for loop control
let loop_var_current = builder.alloc_typed(MirType::Integer); // header PHI dst
let carrier_current = builder.alloc_typed(MirType::Integer); // header PHI dst
let cond_loop = builder.alloc_typed(MirType::Bool); // header condition
let cond_if = builder.alloc_typed(MirType::Bool); // body condition
let carrier_then = builder.alloc_typed(MirType::Integer); // then update result
let carrier_else = builder.alloc_typed(MirType::Integer); // else update result
let carrier_next = builder.alloc_typed(MirType::Integer); // merge PHI dst
let loop_var_next = builder.alloc_typed(MirType::Integer); // step update result
// Step 5: Build phi_bindings - PHI dst takes precedence over variable_map
let mut phi_bindings: BTreeMap<String, crate::mir::ValueId> = BTreeMap::new();
phi_bindings.insert(parts.loop_var.clone(), loop_var_current);
phi_bindings.insert(parts.carrier_var.clone(), carrier_current);
// Step 6: Lower AST expressions
// Lower loop condition (e.g., `i < 3`)
let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) =
Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?;
// Lower if condition (e.g., `i > 0`)
let (if_cond_lhs, if_cond_op, if_cond_rhs, if_cond_consts) =
Self::lower_compare_ast(&parts.if_condition, builder, &phi_bindings)?;
// Lower then update (e.g., `sum + 1`)
let (then_lhs, then_op, then_rhs, then_consts) =
Self::lower_binop_ast(&parts.then_update, builder, &phi_bindings)?;
// Lower else update (e.g., `sum + 0`)
let (else_lhs, else_op, else_rhs, else_consts) =
Self::lower_binop_ast(&parts.else_update, builder, &phi_bindings)?;
// Lower loop increment (e.g., `i + 1`)
let (loop_inc_lhs, loop_inc_op, loop_inc_rhs, loop_inc_consts) =
Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?;
// Step 7: Build header_effects (loop condition)
let mut header_effects = loop_cond_consts;
header_effects.push(CoreEffectPlan::Compare {
dst: cond_loop,
lhs: loop_cond_lhs,
op: loop_cond_op,
rhs: loop_cond_rhs,
});
// Step 8: Build body plans (if condition) - goes into loop_plan.body, not block_effects
// Note: lowerer emits loop_plan.body for body_bb, ignoring block_effects
let mut body_plans: Vec<CorePlan> = Vec::new();
for const_effect in if_cond_consts {
body_plans.push(CorePlan::Effect(const_effect));
}
body_plans.push(CorePlan::Effect(CoreEffectPlan::Compare {
dst: cond_if,
lhs: if_cond_lhs,
op: if_cond_op,
rhs: if_cond_rhs,
}));
// Step 9: Build then_effects (carrier_then = carrier_current + 1)
let mut then_effects = then_consts;
then_effects.push(CoreEffectPlan::BinOp {
dst: carrier_then,
lhs: then_lhs,
op: then_op,
rhs: then_rhs,
});
// Step 10: Build else_effects (carrier_else = carrier_current + 0)
let mut else_effects = else_consts;
else_effects.push(CoreEffectPlan::BinOp {
dst: carrier_else,
lhs: else_lhs,
op: else_op,
rhs: else_rhs,
});
// Step 11: Build step_effects (loop_var_next = loop_var_current + 1)
let mut step_effects = loop_inc_consts;
step_effects.push(CoreEffectPlan::BinOp {
dst: loop_var_next,
lhs: loop_inc_lhs,
op: loop_inc_op,
rhs: loop_inc_rhs,
});
// Step 12: Build block_effects (7 blocks)
// Note: body_bb effects are in loop_plan.body, not here
let block_effects = vec![
(preheader_bb, vec![]),
(header_bb, header_effects),
(body_bb, vec![]), // Effects in loop_plan.body
(then_bb, then_effects),
(else_bb, else_effects),
(merge_bb, vec![]), // PHI only, no effects
(step_bb, step_effects),
];
// Step 13: Build PHIs (3 PHIs: 2 in header, 1 in merge)
let phis = vec![
// Header PHI 1: loop variable
CorePhiInfo {
block: header_bb,
dst: loop_var_current,
inputs: vec![
(preheader_bb, loop_var_init),
(step_bb, loop_var_next),
],
tag: format!("loop_var_{}", parts.loop_var),
},
// Header PHI 2: carrier (receives carrier_next from merge via step)
CorePhiInfo {
block: header_bb,
dst: carrier_current,
inputs: vec![
(preheader_bb, carrier_init),
(step_bb, carrier_next),
],
tag: format!("carrier_{}", parts.carrier_var),
},
// Merge PHI: if-else merge (carrier_next = phi(then:carrier_then, else:carrier_else))
CorePhiInfo {
block: merge_bb,
dst: carrier_next,
inputs: vec![
(then_bb, carrier_then),
(else_bb, carrier_else),
],
tag: format!("merge_{}", parts.carrier_var),
},
];
// Step 14: Build Frag (branches + wires)
let empty_args = EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![],
};
// 2 branches: header (loop cond), body (if cond)
let branches = vec![
// header: cond_loop → body/after
BranchStub {
from: header_bb,
cond: cond_loop,
then_target: body_bb,
then_args: empty_args.clone(),
else_target: after_bb,
else_args: empty_args.clone(),
},
// body: cond_if → then/else
BranchStub {
from: body_bb,
cond: cond_if,
then_target: then_bb,
then_args: empty_args.clone(),
else_target: else_bb,
else_args: empty_args.clone(),
},
];
// 4 wires: then→merge, else→merge, merge→step, step→header
let wires = vec![
// then → merge
EdgeStub {
from: then_bb,
kind: ExitKind::Normal,
target: Some(merge_bb),
args: empty_args.clone(),
},
// else → merge
EdgeStub {
from: else_bb,
kind: ExitKind::Normal,
target: Some(merge_bb),
args: empty_args.clone(),
},
// merge → step
EdgeStub {
from: merge_bb,
kind: ExitKind::Normal,
target: Some(step_bb),
args: empty_args.clone(),
},
// step → header (back-edge)
EdgeStub {
from: step_bb,
kind: ExitKind::Normal,
target: Some(header_bb),
args: empty_args.clone(),
},
];
let frag = Frag {
entry: header_bb,
exits: BTreeMap::new(),
wires,
branches,
};
// Step 15: Build final_values
let final_values = vec![
(parts.loop_var.clone(), loop_var_current),
(parts.carrier_var.clone(), carrier_current),
];
// Step 16: Build CoreLoopPlan
// found_bb = after_bb (no early exit)
// cond_match = cond_if (if condition for body branching)
let loop_plan = CoreLoopPlan {
preheader_bb,
header_bb,
body_bb,
step_bb,
after_bb,
found_bb: after_bb, // No early exit
body: body_plans, // Body effects (if condition)
cond_loop,
cond_match: cond_if, // If condition
block_effects,
phis,
frag,
final_values,
};
if debug {
trace_logger.debug(
"normalizer/pattern3_if_phi",
"CorePlan construction complete (7 blocks, 3 PHIs)",
);
}
Ok(CorePlan::Loop(loop_plan))
}
}