diff --git a/docs/development/current/main/phases/phase-286/P2.6.1-INSTRUCTIONS.md b/docs/development/current/main/phases/phase-286/P2.6.1-INSTRUCTIONS.md new file mode 100644 index 00000000..1b36356c --- /dev/null +++ b/docs/development/current/main/phases/phase-286/P2.6.1-INSTRUCTIONS.md @@ -0,0 +1,120 @@ +Status: Active +Date: 2025-12-26 +Scope: Phase 286 P2.6.1(Pattern3 Plan line を “完走” させる)手順書。実装は小刻みに、quick smoke green を維持する。 +Related: +- docs/development/current/main/phases/phase-286/README.md +- docs/development/current/main/design/joinir-plan-frag-ssot.md +- docs/development/current/main/design/edgecfg-fragments.md + +# Phase 286 P2.6.1: Pattern3(Loop with If‑Phi)Plan 完走 + +## 目的 + +Pattern3(Loop body 内の if/else により carrier が条件更新される形)を、 +legacy JoinIR line(JoinIR→bridge→merge)ではなく Plan/Frag SSOT(CorePlan→Frag→emit_frag)で完走させる。 + +### 成功条件(必須) + +- `bash tools/smokes/v2/profiles/integration/apps/phase118_pattern3_if_sum_vm.sh` が PASS(期待値 12) +- `./tools/smokes/v2/run.sh --profile quick` が 0 FAILED(154/154 PASS 維持) +- `HAKO_JOINIR_DEBUG=1` で `route=plan ... Pattern3_IfPhi` が出て、legacy fallback に落ちない + +## 現状(P2.6 途中の状態) + +- `extract_pattern3_plan()` は実装済み(DomainPlan を返す) +- `PlanNormalizer::normalize_pattern3_if_phi()` は stub(`Err(...)` を返す) +- router は Pattern3 のみ “stub fallback” 特例を持つ(`continue` して legacy へ) + +→ P2.6.1 では **normalizer を実装**し、router の特例を撤去して **Fail-Fast** に揃える。 + +## 実装方針(重要な制約) + +- terminator 直生成は禁止(SSOT: `Frag + compose::* + emit_frag()`) +- extractor が `Some` を返したら、normalize/lower 失敗は Err(silent fallback 禁止) +- Pattern1 の誤マッチ再発を防ぐ(既に router の `pattern_kind` ガードあり) + +## 1) Pattern3 の CFG(Plan 側の正本) + +Pattern3 を “Loop + If + Merge(PHI) + Step” として正規化する。 + +```text +preheader → header(PHI: i_current, carrier_current) → body(if_condition) + ↓ ↓ + after then | else + ↓ ↓ + merge(PHI: carrier_next) + ↓ + step(i_next) + ↓ + back-edge to header +``` + +ポイント: + +- header に loop_var と carrier の PHI(2本) +- then/else で carrier 更新値を作る +- merge で carrier_next を PHI で合流 +- step で i_next を作り、header に戻す(carrier_next も header PHI に渡る) + +## 2) 実装タスク + +### 2.1 normalizer 実装 + +対象: `src/mir/builder/control_flow/plan/normalizer.rs` + +- `normalize_pattern3_if_phi(...)` を stub から実装へ置換する +- 既存の Pattern1/4/9/8 で確立した部品を流用する + - `alloc_typed(...)` + - `phi_bindings`(PHI dst を AST lowering 時に優先参照) + - `lower_value_ast / lower_compare_ast / lower_binop_ast`(phi_bindings 伝播) + +最低限の生成物: + +- blocks: `preheader_bb, header_bb, body_bb, then_bb, else_bb, merge_bb, step_bb, after_bb` +- header PHI: + - `loop_var_current` inputs: `(preheader_bb, i_init)`, `(step_bb, i_next)` + - `carrier_current` inputs: `(preheader_bb, carrier_init)`, `(step_bb, carrier_next)` +- merge PHI: + - `carrier_next` inputs: `(then_bb, carrier_then)`, `(else_bb, carrier_else)` +- frag branches/wires: + - header: `if cond_loop then body else after` + - body: `if if_condition then then_bb else else_bb` + - then_bb: jump → merge_bb + - else_bb: jump → merge_bb + - merge_bb: jump → step_bb + - step_bb: jump → header_bb(back-edge) +- final_values: loop_var と carrier は header PHI の値を返す(既存パターンに合わせる) + +### 2.2 router の Pattern3 特例撤去(Fail-Fast 統一) + +対象: `src/mir/builder/control_flow/joinir/patterns/router.rs` + +現状の特例(Pattern3 だけ normalize Err を握りつぶす)を削除し、以下へ統一する: + +- extractor が `Some(domain_plan)` を返したら `lower_via_plan(...)` の Err はそのまま伝播 +- fallback は extractor が `Ok(None)` の場合のみ + +## 3) 検証手順(コマンド) + +### 3.1 Pattern3 の integration smoke(VM) + +`bash tools/smokes/v2/profiles/integration/apps/phase118_pattern3_if_sum_vm.sh` + +### 3.2 quick regression + +`./tools/smokes/v2/run.sh --profile quick` + +### 3.3 Plan 完走ログ(任意) + +`HAKO_JOINIR_DEBUG=1 ./target/release/hakorune --backend vm apps/tests/phase118_pattern3_if_sum_min.hako 2>&1 | rg \"route=plan.*Pattern3\"` + +## 4) ドキュメント更新(完了時) + +完走したら、以下を更新する(本文は短く、成果物リンク中心): + +- `docs/development/current/main/phases/phase-286/README.md` + - P2.6 の Status を `COMPLETE` に更新 + - “router の Pattern3 特例撤去(Fail-Fast 統一)” を設計決定として記載 +- `docs/development/current/main/10-Now.md` + - Current Focus の次対象を更新(例: Pattern2 の設計相談 / Pattern5 / PatternX など) + diff --git a/src/mir/builder/control_flow/joinir/patterns/router.rs b/src/mir/builder/control_flow/joinir/patterns/router.rs index 9d527f44..f49d2c52 100644 --- a/src/mir/builder/control_flow/joinir/patterns/router.rs +++ b/src/mir/builder/control_flow/joinir/patterns/router.rs @@ -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 diff --git a/src/mir/builder/control_flow/plan/normalizer.rs b/src/mir/builder/control_flow/plan/normalizer.rs index 63ab72ad..d5e3ba35 100644 --- a/src/mir/builder/control_flow/plan/normalizer.rs +++ b/src/mir/builder/control_flow/plan/normalizer.rs @@ -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 { - // 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 = 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 = 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)) } }