diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 9eb8b680..e686422e 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -3,7 +3,7 @@ ## Current Focus - Phase: `docs/development/current/main/phases/phase-29ap/README.md` -- Next: Phase 29ap P10 (planned; see `docs/development/current/main/phases/phase-29ap/README.md`) +- Next: Phase 29ap P11 (planned; see `docs/development/current/main/phases/phase-29ap/README.md`) ## Gate (SSOT) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index 9fa00180..0ebf9730 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -5,7 +5,7 @@ Scope: 「次にやる候補」を短く列挙するメモ。入口は `docs/dev ## Active -- Phase 29ap: `docs/development/current/main/phases/phase-29ap/README.md` (Next: P10 planned) +- Phase 29ap: `docs/development/current/main/phases/phase-29ap/README.md` (Next: P11 planned) - JoinIR regression gate SSOT: `docs/development/current/main/phases/phase-29ae/README.md` - CorePlan hardening (docs-first): `docs/development/current/main/phases/phase-29al/README.md` diff --git a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md index ba337e2d..608b4165 100644 --- a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md +++ b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md @@ -34,7 +34,7 @@ Related: ## 1.1 Current (active) - Active phase: `docs/development/current/main/phases/phase-29ap/README.md` -- Next step: Phase 29ap P10 (planned) +- Next step: Phase 29ap P11 (planned) ## 2. すでに固めた SSOT(再発防止の土台) diff --git a/docs/development/current/main/phases/phase-29ap/P10-NESTED-MINIMAL-COREPLAN-SUBSET-INSTRUCTIONS.md b/docs/development/current/main/phases/phase-29ap/P10-NESTED-MINIMAL-COREPLAN-SUBSET-INSTRUCTIONS.md new file mode 100644 index 00000000..3bd8a032 --- /dev/null +++ b/docs/development/current/main/phases/phase-29ap/P10-NESTED-MINIMAL-COREPLAN-SUBSET-INSTRUCTIONS.md @@ -0,0 +1,40 @@ +# Phase 29ap P10: Pattern6_NestedLoopMinimal minimal CorePlan subset (strict/dev) + +## Goal + +- In strict/dev, adopt a minimal nested-loop CorePlan for the `phase1883_nested_minimal` shape. +- Keep release/default behavior unchanged (legacy JoinIR path remains). +- Fail-fast in strict/dev when nested loops are detected but the subset does not match. + +## Subset (SSOT) + +- Outer loop: `loop(i < )` with step `i = i + 1` (step=1 only). +- Inner loop: `loop(j < )` with body `sum = sum + 1; j = j + 1` (step=1 only). +- Inner init: `j = 0` (via `local j` + assignment, or `local j = 0`). +- No break/continue/return inside outer or inner loop. +- No value-join / exitmap / cleanup (presence must be empty). + +## Steps + +1. Facts (SSOT) + - Add `Pattern6NestedMinimalFacts` and wire it into `LoopFacts` (optional field). + - Keep `Ok(None)` for non-matches; no hardcode by-name. + +2. Composer v2 (strict/dev) + - Add `coreloop_v2_nested_minimal.rs` and compose a single CoreLoopPlan with a nested CFG. + - Body remains effect-only (j-init only); inner loop is encoded via extra blocks + Frag wiring. + +3. Strict/dev adopt + - If nested facts are present, adopt and emit tag: + - `[coreplan/shadow_adopt:pattern6_nested_minimal]` + - If nested facts are missing, keep the strict/dev freeze (fail-fast). + +4. Smoke gate + - Update `phase29ap_pattern6_nested_strict_shadow_vm.sh` to require the shadow-adopt tag. + +## Verification + +- `cargo build --release` +- `./tools/smokes/v2/run.sh --profile quick` +- `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` + diff --git a/docs/development/current/main/phases/phase-29ap/README.md b/docs/development/current/main/phases/phase-29ap/README.md index d2b95577..65b64c8e 100644 --- a/docs/development/current/main/phases/phase-29ap/README.md +++ b/docs/development/current/main/phases/phase-29ap/README.md @@ -108,6 +108,16 @@ Gate (SSOT): - Release/default behavior remains unchanged. - Gate stays green. +## P10: Pattern6_NestedLoopMinimal minimal CorePlan subset (strict/dev) ✅ + +- Scope: + - Add nested minimal facts (outer+inner loop subset) and project into canonical facts. + - Compose nested loop CorePlan v2 in strict/dev and emit shadow-adopt tag. + - Update the strict smoke to require the new tag. +- Guardrails: + - Release/default behavior remains unchanged (legacy JoinIR path stays). + - Gate stays green. + ## Next (planned) -- P10: Pattern6_NestedLoopMinimal migration/design (keep nested gate green) +- P11: Pattern6_NestedLoopMinimal release adopt (TBD; keep strict gate green) diff --git a/src/mir/builder/control_flow/joinir/patterns/router.rs b/src/mir/builder/control_flow/joinir/patterns/router.rs index 8e340dfb..a919efa0 100644 --- a/src/mir/builder/control_flow/joinir/patterns/router.rs +++ b/src/mir/builder/control_flow/joinir/patterns/router.rs @@ -295,6 +295,13 @@ pub(crate) fn route_loop_pattern( || crate::config::env::joinir_dev_enabled(); if strict_or_dev { + if let Some(adopt) = composer::try_shadow_adopt_nested_minimal(builder, ctx, &outcome)? { + let composer::ShadowAdoptOutcome { core_plan, tag } = adopt; + PlanVerifier::verify(&core_plan)?; + eprintln!("{}", tag); + return PlanLowerer::lower(builder, core_plan, ctx); + } + if let Some(err) = composer::strict_nested_loop_guard(&outcome, ctx) { eprintln!("{}", err); return Err(err); diff --git a/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs b/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs index 616c5902..fa0f4ee1 100644 --- a/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs +++ b/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs @@ -213,6 +213,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, } } @@ -289,6 +290,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -353,6 +355,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -424,6 +427,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -491,6 +495,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -557,6 +562,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -609,6 +615,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -681,6 +688,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -736,6 +744,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); diff --git a/src/mir/builder/control_flow/plan/composer/coreloop_v1.rs b/src/mir/builder/control_flow/plan/composer/coreloop_v1.rs index d04c111d..05ff6ce9 100644 --- a/src/mir/builder/control_flow/plan/composer/coreloop_v1.rs +++ b/src/mir/builder/control_flow/plan/composer/coreloop_v1.rs @@ -222,6 +222,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -290,6 +291,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -343,6 +345,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -393,6 +396,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -471,6 +475,7 @@ mod tests { loop_increment, }), pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -533,6 +538,7 @@ mod tests { loop_increment: lit_int(0), }), pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -606,6 +612,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -670,6 +677,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -753,6 +761,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); @@ -815,6 +824,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let mut builder = MirBuilder::new(); diff --git a/src/mir/builder/control_flow/plan/composer/coreloop_v2_nested_minimal.rs b/src/mir/builder/control_flow/plan/composer/coreloop_v2_nested_minimal.rs new file mode 100644 index 00000000..8b3563b1 --- /dev/null +++ b/src/mir/builder/control_flow/plan/composer/coreloop_v2_nested_minimal.rs @@ -0,0 +1,489 @@ +//! Phase 29ap P10: CoreLoopComposer v2 (nested minimal, strict/dev only) + +use crate::mir::basic_block::EdgeArgs; +use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag}; +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::control_flow::plan::composer::coreloop_gates::{ + coreloop_base_gate, exit_kinds_empty, +}; +use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts; +use crate::mir::builder::control_flow::plan::normalizer::PlanNormalizer; +use crate::mir::builder::control_flow::plan::{ + CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, +}; +use crate::mir::builder::MirBuilder; +use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout; +use crate::mir::{ConstValue, MirType}; +use std::collections::BTreeMap; + +pub(in crate::mir::builder) fn try_compose_core_loop_v2_nested_minimal( + builder: &mut MirBuilder, + facts: &CanonicalLoopFacts, + _ctx: &LoopPatternContext, +) -> Result, String> { + if !coreloop_base_gate(facts) { + return Ok(None); + } + if facts.value_join_needed { + return Ok(None); + } + if !facts.nested_loop { + return Ok(None); + } + if !exit_kinds_empty(facts) { + return Ok(None); + } + + let Some(nested) = facts.facts.pattern6_nested_minimal.as_ref() else { + return Ok(None); + }; + + let outer_loop_var_init = builder + .variable_ctx + .variable_map + .get(&nested.outer_loop_var) + .copied() + .ok_or_else(|| { + format!( + "[coreloop_v2_nested] outer loop var '{}' not in variable_map", + nested.outer_loop_var + ) + })?; + let acc_var_init = builder + .variable_ctx + .variable_map + .get(&nested.acc_var) + .copied() + .ok_or_else(|| { + format!( + "[coreloop_v2_nested] acc var '{}' not in variable_map", + nested.acc_var + ) + })?; + + let preheader_bb = builder + .current_block + .ok_or_else(|| "[coreloop_v2_nested] No current block for loop entry".to_string())?; + let header_bb = builder.next_block_id(); + let body_bb = builder.next_block_id(); + let inner_header_bb = builder.next_block_id(); + let inner_body_bb = builder.next_block_id(); + let inner_step_bb = builder.next_block_id(); + let inner_after_bb = builder.next_block_id(); + let step_bb = builder.next_block_id(); + let after_bb = builder.next_block_id(); + + let outer_loop_var_current = builder.alloc_typed(MirType::Integer); + let acc_outer_current = builder.alloc_typed(MirType::Integer); + let cond_outer = builder.alloc_typed(MirType::Bool); + let outer_loop_var_next = builder.alloc_typed(MirType::Integer); + + let inner_loop_var_current = builder.alloc_typed(MirType::Integer); + let acc_inner_current = builder.alloc_typed(MirType::Integer); + let cond_inner = builder.alloc_typed(MirType::Bool); + let acc_inner_next = builder.alloc_typed(MirType::Integer); + let inner_loop_var_next = builder.alloc_typed(MirType::Integer); + + let j_init_value = builder.alloc_typed(MirType::Integer); + let j_init_effect = CoreEffectPlan::Const { + dst: j_init_value, + value: ConstValue::Integer(nested.inner_init_lit), + }; + + let mut outer_phi_bindings = BTreeMap::new(); + outer_phi_bindings.insert(nested.outer_loop_var.clone(), outer_loop_var_current); + + let mut inner_phi_bindings = BTreeMap::new(); + inner_phi_bindings.insert(nested.inner_loop_var.clone(), inner_loop_var_current); + inner_phi_bindings.insert(nested.acc_var.clone(), acc_inner_current); + + let (outer_cond_lhs, outer_cond_op, outer_cond_rhs, mut outer_cond_consts) = + PlanNormalizer::lower_compare_ast(&nested.outer_condition, builder, &outer_phi_bindings)?; + outer_cond_consts.push(CoreEffectPlan::Compare { + dst: cond_outer, + lhs: outer_cond_lhs, + op: outer_cond_op, + rhs: outer_cond_rhs, + }); + + let (inner_cond_lhs, inner_cond_op, inner_cond_rhs, mut inner_cond_consts) = + PlanNormalizer::lower_compare_ast(&nested.inner_condition, builder, &inner_phi_bindings)?; + inner_cond_consts.push(CoreEffectPlan::Compare { + dst: cond_inner, + lhs: inner_cond_lhs, + op: inner_cond_op, + rhs: inner_cond_rhs, + }); + + let (outer_inc_lhs, outer_inc_op, outer_inc_rhs, mut outer_inc_consts) = + PlanNormalizer::lower_binop_ast(&nested.outer_increment, builder, &outer_phi_bindings)?; + outer_inc_consts.push(CoreEffectPlan::BinOp { + dst: outer_loop_var_next, + lhs: outer_inc_lhs, + op: outer_inc_op, + rhs: outer_inc_rhs, + }); + + let (acc_update_lhs, acc_update_op, acc_update_rhs, mut acc_update_consts) = + PlanNormalizer::lower_binop_ast(&nested.acc_update, builder, &inner_phi_bindings)?; + acc_update_consts.push(CoreEffectPlan::BinOp { + dst: acc_inner_next, + lhs: acc_update_lhs, + op: acc_update_op, + rhs: acc_update_rhs, + }); + + let (inner_inc_lhs, inner_inc_op, inner_inc_rhs, mut inner_inc_consts) = + PlanNormalizer::lower_binop_ast(&nested.inner_increment, builder, &inner_phi_bindings)?; + inner_inc_consts.push(CoreEffectPlan::BinOp { + dst: inner_loop_var_next, + lhs: inner_inc_lhs, + op: inner_inc_op, + rhs: inner_inc_rhs, + }); + + let mut inner_step_effects = acc_update_consts; + inner_step_effects.extend(inner_inc_consts); + + let block_effects = vec![ + (preheader_bb, vec![]), + (header_bb, outer_cond_consts), + (body_bb, vec![]), + (inner_header_bb, inner_cond_consts), + (inner_body_bb, vec![]), + (inner_step_bb, inner_step_effects), + (inner_after_bb, vec![]), + (step_bb, outer_inc_consts), + ]; + + let phis = vec![ + CorePhiInfo { + block: header_bb, + dst: outer_loop_var_current, + inputs: vec![(preheader_bb, outer_loop_var_init), (step_bb, outer_loop_var_next)], + tag: format!("loop_var_{}", nested.outer_loop_var), + }, + CorePhiInfo { + block: header_bb, + dst: acc_outer_current, + inputs: vec![(preheader_bb, acc_var_init), (step_bb, acc_inner_current)], + tag: format!("acc_var_{}", nested.acc_var), + }, + CorePhiInfo { + block: inner_header_bb, + dst: inner_loop_var_current, + inputs: vec![(body_bb, j_init_value), (inner_step_bb, inner_loop_var_next)], + tag: format!("inner_loop_var_{}", nested.inner_loop_var), + }, + CorePhiInfo { + block: inner_header_bb, + dst: acc_inner_current, + inputs: vec![(body_bb, acc_outer_current), (inner_step_bb, acc_inner_next)], + tag: format!("inner_acc_var_{}", nested.acc_var), + }, + ]; + + let empty_args = EdgeArgs { + layout: JumpArgsLayout::CarriersOnly, + values: vec![], + }; + + let branches = vec![ + BranchStub { + from: header_bb, + cond: cond_outer, + then_target: body_bb, + else_target: after_bb, + then_args: empty_args.clone(), + else_args: empty_args.clone(), + }, + BranchStub { + from: inner_header_bb, + cond: cond_inner, + then_target: inner_body_bb, + else_target: inner_after_bb, + then_args: empty_args.clone(), + else_args: empty_args.clone(), + }, + ]; + + let wires = vec![ + EdgeStub { + from: body_bb, + kind: ExitKind::Normal, + target: Some(inner_header_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: inner_body_bb, + kind: ExitKind::Normal, + target: Some(inner_step_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: inner_step_bb, + kind: ExitKind::Normal, + target: Some(inner_header_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: inner_after_bb, + kind: ExitKind::Normal, + target: Some(step_bb), + args: empty_args.clone(), + }, + EdgeStub { + from: step_bb, + kind: ExitKind::Normal, + target: Some(header_bb), + args: empty_args.clone(), + }, + ]; + + let frag = Frag { + entry: header_bb, + block_params: BTreeMap::new(), + exits: BTreeMap::new(), + wires, + branches, + }; + + let body_plans = vec![CorePlan::Effect(j_init_effect)]; + + let final_values = vec![ + (nested.outer_loop_var.clone(), outer_loop_var_current), + (nested.acc_var.clone(), acc_outer_current), + ]; + + let loop_plan = CoreLoopPlan { + preheader_bb, + header_bb, + body_bb, + step_bb, + after_bb, + found_bb: after_bb, + body: body_plans, + cond_loop: cond_outer, + cond_match: cond_inner, + block_effects, + phis, + frag, + final_values, + }; + + Ok(Some(CorePlan::Loop(loop_plan))) +} + +#[cfg(test)] +mod tests { + use super::try_compose_core_loop_v2_nested_minimal; + use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; + use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; + use crate::mir::builder::control_flow::plan::facts::feature_facts::{ + LoopFeatureFacts, ValueJoinFacts, + }; + use crate::mir::builder::control_flow::plan::facts::loop_facts::LoopFacts; + use crate::mir::builder::control_flow::plan::facts::pattern6_nested_minimal_facts:: + Pattern6NestedMinimalFacts; + use crate::mir::builder::control_flow::plan::facts::scan_shapes::{ + ConditionShape, StepShape, + }; + use crate::mir::builder::control_flow::plan::facts::skeleton_facts::{ + SkeletonFacts, SkeletonKind, + }; + use crate::mir::builder::control_flow::plan::normalize::canonicalize_loop_facts; + use crate::mir::builder::control_flow::plan::CorePlan; + use crate::mir::builder::MirBuilder; + use crate::mir::MirType; + + fn v(name: &str) -> ASTNode { + ASTNode::Variable { + name: name.to_string(), + span: Span::unknown(), + } + } + + fn lit_int(value: i64) -> ASTNode { + ASTNode::Literal { + value: LiteralValue::Integer(value), + span: Span::unknown(), + } + } + + fn condition_lt(loop_var: &str, bound: i64) -> ASTNode { + ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(v(loop_var)), + right: Box::new(lit_int(bound)), + span: Span::unknown(), + } + } + + fn increment_value(loop_var: &str, step: i64) -> ASTNode { + ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v(loop_var)), + right: Box::new(lit_int(step)), + span: Span::unknown(), + } + } + + fn nested_facts() -> (Pattern6NestedMinimalFacts, ASTNode, Vec) { + let outer_condition = condition_lt("i", 3); + let inner_condition = condition_lt("j", 3); + let acc_update = increment_value("sum", 1); + let outer_increment = increment_value("i", 1); + let inner_increment = increment_value("j", 1); + + let inner_loop = ASTNode::Loop { + condition: Box::new(inner_condition.clone()), + body: vec![ + ASTNode::Assignment { + target: Box::new(v("sum")), + value: Box::new(acc_update.clone()), + span: Span::unknown(), + }, + ASTNode::Assignment { + target: Box::new(v("j")), + value: Box::new(inner_increment.clone()), + span: Span::unknown(), + }, + ], + span: Span::unknown(), + }; + + let body = vec![ + ASTNode::Local { + variables: vec!["j".to_string()], + initial_values: vec![None], + span: Span::unknown(), + }, + ASTNode::Assignment { + target: Box::new(v("j")), + value: Box::new(lit_int(0)), + span: Span::unknown(), + }, + inner_loop, + ASTNode::Assignment { + target: Box::new(v("i")), + value: Box::new(outer_increment.clone()), + span: Span::unknown(), + }, + ]; + + ( + Pattern6NestedMinimalFacts { + outer_loop_var: "i".to_string(), + outer_condition: outer_condition.clone(), + outer_increment: outer_increment.clone(), + inner_loop_var: "j".to_string(), + inner_condition, + inner_increment, + acc_var: "sum".to_string(), + acc_update, + inner_init_lit: 0, + }, + outer_condition, + body, + ) + } + + #[test] + fn coreloop_v2_composes_nested_minimal() { + let mut builder = MirBuilder::new(); + builder.enter_function_for_test("coreloop_v2_nested".to_string()); + + let i_init = builder.alloc_typed(MirType::Integer); + let sum_init = builder.alloc_typed(MirType::Integer); + builder + .variable_ctx + .variable_map + .insert("i".to_string(), i_init); + builder + .variable_ctx + .variable_map + .insert("sum".to_string(), sum_init); + + let (nested, condition, body) = nested_facts(); + let facts = LoopFacts { + condition_shape: ConditionShape::Unknown, + step_shape: StepShape::Unknown, + skeleton: SkeletonFacts { + kind: SkeletonKind::Loop, + }, + features: LoopFeatureFacts { + nested_loop: true, + ..LoopFeatureFacts::default() + }, + scan_with_init: None, + split_scan: None, + pattern1_simplewhile: None, + pattern1_char_map: None, + pattern1_array_join: None, + pattern3_ifphi: None, + pattern4_continue: None, + pattern5_infinite_early_exit: None, + pattern6_nested_minimal: Some(nested), + pattern8_bool_predicate_scan: None, + pattern9_accum_const_loop: None, + pattern2_break: None, + pattern2_loopbodylocal: None, + }; + let canonical = canonicalize_loop_facts(facts); + let ctx = LoopPatternContext::new(&condition, &body, "coreloop_v2_nested", false, false); + + let composed = try_compose_core_loop_v2_nested_minimal(&mut builder, &canonical, &ctx) + .expect("Ok"); + assert!(matches!(composed, Some(CorePlan::Loop(_)))); + } + + #[test] + fn coreloop_v2_rejects_value_join() { + let mut builder = MirBuilder::new(); + builder.enter_function_for_test("coreloop_v2_nested_join".to_string()); + + let i_init = builder.alloc_typed(MirType::Integer); + let sum_init = builder.alloc_typed(MirType::Integer); + builder + .variable_ctx + .variable_map + .insert("i".to_string(), i_init); + builder + .variable_ctx + .variable_map + .insert("sum".to_string(), sum_init); + + let (nested, condition, body) = nested_facts(); + let facts = LoopFacts { + condition_shape: ConditionShape::Unknown, + step_shape: StepShape::Unknown, + skeleton: SkeletonFacts { + kind: SkeletonKind::Loop, + }, + features: LoopFeatureFacts { + nested_loop: true, + value_join: Some(ValueJoinFacts { needed: true }), + ..LoopFeatureFacts::default() + }, + scan_with_init: None, + split_scan: None, + pattern1_simplewhile: None, + pattern1_char_map: None, + pattern1_array_join: None, + pattern3_ifphi: None, + pattern4_continue: None, + pattern5_infinite_early_exit: None, + pattern6_nested_minimal: Some(nested), + pattern8_bool_predicate_scan: None, + pattern9_accum_const_loop: None, + pattern2_break: None, + pattern2_loopbodylocal: None, + }; + let canonical = canonicalize_loop_facts(facts); + let ctx = LoopPatternContext::new(&condition, &body, "coreloop_v2_nested_join", false, false); + + let composed = try_compose_core_loop_v2_nested_minimal(&mut builder, &canonical, &ctx) + .expect("Ok"); + assert!(composed.is_none()); + } +} diff --git a/src/mir/builder/control_flow/plan/composer/mod.rs b/src/mir/builder/control_flow/plan/composer/mod.rs index 06dc025f..eca8666e 100644 --- a/src/mir/builder/control_flow/plan/composer/mod.rs +++ b/src/mir/builder/control_flow/plan/composer/mod.rs @@ -3,6 +3,7 @@ pub(super) mod coreloop_gates; pub(super) mod coreloop_v0; pub(super) mod coreloop_v1; +pub(super) mod coreloop_v2_nested_minimal; mod shadow_adopt; use super::{normalizer::PlanNormalizer, CorePlan, DomainPlan, Pattern1SimpleWhilePlan}; @@ -14,6 +15,7 @@ use crate::mir::builder::MirBuilder; pub(in crate::mir::builder) use shadow_adopt::{ strict_nested_loop_guard, + try_shadow_adopt_nested_minimal, try_release_adopt_core_plan, try_shadow_adopt_core_plan, ShadowAdoptOutcome, @@ -139,6 +141,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let composed = @@ -182,6 +185,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = @@ -213,6 +217,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = @@ -256,6 +261,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let condition = canonical.facts.pattern1_simplewhile.as_ref().unwrap().condition.clone(); @@ -295,6 +301,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let condition = ASTNode::BinaryOp { @@ -347,6 +354,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let condition = canonical @@ -397,6 +405,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let condition = ASTNode::BinaryOp { @@ -461,6 +470,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let condition = canonical @@ -539,6 +549,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let condition = canonical @@ -628,6 +639,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let condition = canonical @@ -699,6 +711,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let condition = canonical diff --git a/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs b/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs index 0635a33a..6b8b2f0a 100644 --- a/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs +++ b/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs @@ -8,6 +8,7 @@ use super::coreloop_v1::{ try_compose_core_loop_v1_pattern5_infinite_early_exit, try_compose_core_loop_v1_scan_with_init, try_compose_core_loop_v1_split_scan, }; +use super::coreloop_v2_nested_minimal::try_compose_core_loop_v2_nested_minimal; use super::PlanNormalizer; use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; use crate::mir::builder::control_flow::plan::facts::feature_facts::detect_nested_loop; @@ -44,6 +45,29 @@ pub(in crate::mir::builder) fn strict_nested_loop_guard( Some(freeze.to_string()) } +pub(in crate::mir::builder) fn try_shadow_adopt_nested_minimal( + builder: &mut MirBuilder, + ctx: &LoopPatternContext, + outcome: &PlanBuildOutcome, +) -> Result, String> { + let Some(facts) = outcome.facts.as_ref() else { + return Ok(None); + }; + if facts.facts.pattern6_nested_minimal.is_none() { + return Ok(None); + } + + let core_plan = try_compose_core_loop_v2_nested_minimal(builder, facts, ctx)? + .ok_or_else(|| { + "pattern6 nested minimal strict/dev adopt failed: compose rejected".to_string() + })?; + + Ok(Some(ShadowAdoptOutcome { + core_plan, + tag: "[coreplan/shadow_adopt:pattern6_nested_minimal]", + })) +} + fn try_compose_core_loop_scan_with_init( builder: &mut MirBuilder, facts: &CanonicalLoopFacts, diff --git a/src/mir/builder/control_flow/plan/facts/loop_facts.rs b/src/mir/builder/control_flow/plan/facts/loop_facts.rs index 97a3967c..6d30f1a3 100644 --- a/src/mir/builder/control_flow/plan/facts/loop_facts.rs +++ b/src/mir/builder/control_flow/plan/facts/loop_facts.rs @@ -32,6 +32,9 @@ use super::pattern4_continue_facts::{ use super::pattern5_infinite_early_exit_facts::{ Pattern5InfiniteEarlyExitFacts, try_extract_pattern5_infinite_early_exit_facts, }; +use super::pattern6_nested_minimal_facts::{ + Pattern6NestedMinimalFacts, try_extract_pattern6_nested_minimal_facts, +}; use super::pattern8_bool_predicate_scan_facts::{ Pattern8BoolPredicateScanFacts, try_extract_pattern8_bool_predicate_scan_facts, }; @@ -59,6 +62,7 @@ pub(in crate::mir::builder) struct LoopFacts { pub pattern3_ifphi: Option, pub pattern4_continue: Option, pub pattern5_infinite_early_exit: Option, + pub pattern6_nested_minimal: Option, pub pattern8_bool_predicate_scan: Option, pub pattern9_accum_const_loop: Option, pub pattern2_break: Option, @@ -137,6 +141,8 @@ fn try_build_loop_facts_inner( let pattern4_continue = try_extract_pattern4_continue_facts(condition, body)?; let pattern5_infinite_early_exit = try_extract_pattern5_infinite_early_exit_facts(condition, body)?; + let pattern6_nested_minimal = + try_extract_pattern6_nested_minimal_facts(condition, body)?; let pattern8_bool_predicate_scan = if allow_pattern8 { try_extract_pattern8_bool_predicate_scan_facts( condition, @@ -159,6 +165,7 @@ fn try_build_loop_facts_inner( || pattern3_ifphi.is_some() || pattern4_continue.is_some() || pattern5_infinite_early_exit.is_some() + || pattern6_nested_minimal.is_some() || pattern8_bool_predicate_scan.is_some() || pattern9_accum_const_loop.is_some() || pattern2_break.is_some() @@ -190,6 +197,7 @@ fn try_build_loop_facts_inner( pattern3_ifphi, pattern4_continue, pattern5_infinite_early_exit, + pattern6_nested_minimal, pattern8_bool_predicate_scan, pattern9_accum_const_loop, pattern2_break, diff --git a/src/mir/builder/control_flow/plan/facts/mod.rs b/src/mir/builder/control_flow/plan/facts/mod.rs index 786fda2c..e173fd89 100644 --- a/src/mir/builder/control_flow/plan/facts/mod.rs +++ b/src/mir/builder/control_flow/plan/facts/mod.rs @@ -14,6 +14,7 @@ pub(in crate::mir::builder) mod pattern1_array_join_facts; pub(in crate::mir::builder) mod pattern3_ifphi_facts; pub(in crate::mir::builder) mod pattern4_continue_facts; pub(in crate::mir::builder) mod pattern5_infinite_early_exit_facts; +pub(in crate::mir::builder) mod pattern6_nested_minimal_facts; pub(in crate::mir::builder) mod pattern8_bool_predicate_scan_facts; pub(in crate::mir::builder) mod pattern9_accum_const_loop_facts; pub(in crate::mir::builder) mod pattern2_break_facts; diff --git a/src/mir/builder/control_flow/plan/facts/pattern6_nested_minimal_facts.rs b/src/mir/builder/control_flow/plan/facts/pattern6_nested_minimal_facts.rs new file mode 100644 index 00000000..26535fbc --- /dev/null +++ b/src/mir/builder/control_flow/plan/facts/pattern6_nested_minimal_facts.rs @@ -0,0 +1,380 @@ +//! Phase 29ap P10: Pattern6NestedMinimalFacts (Facts SSOT) + +use crate::ast::{ASTNode, BinaryOperator, LiteralValue}; +use crate::mir::builder::control_flow::plan::facts::pattern9_accum_const_loop_facts::try_extract_pattern9_accum_const_loop_facts; +use crate::mir::builder::control_flow::plan::planner::Freeze; + +#[derive(Debug, Clone)] +pub(in crate::mir::builder) struct Pattern6NestedMinimalFacts { + pub outer_loop_var: String, + pub outer_condition: ASTNode, + pub outer_increment: ASTNode, + pub inner_loop_var: String, + pub inner_condition: ASTNode, + pub inner_increment: ASTNode, + pub acc_var: String, + pub acc_update: ASTNode, + pub inner_init_lit: i64, +} + +pub(in crate::mir::builder) fn try_extract_pattern6_nested_minimal_facts( + condition: &ASTNode, + body: &[ASTNode], +) -> Result, Freeze> { + let Some(outer_loop_var) = extract_loop_var_for_subset(condition) else { + return Ok(None); + }; + + let (inner_idx, inner_loop) = match find_single_inner_loop(body) { + Some(loop_pair) => loop_pair, + None => return Ok(None), + }; + + let ASTNode::Loop { + condition: inner_condition, + body: inner_body, + .. + } = inner_loop + else { + return Ok(None); + }; + + let Some(inner_facts) = + try_extract_pattern9_accum_const_loop_facts(inner_condition, inner_body)? + else { + return Ok(None); + }; + + if inner_facts.loop_var == outer_loop_var + || inner_facts.acc_var == outer_loop_var + || inner_facts.acc_var == inner_facts.loop_var + { + return Ok(None); + } + + let Some(inner_step) = + extract_increment_step_one(&inner_facts.loop_increment, &inner_facts.loop_var) + else { + return Ok(None); + }; + + let Some(acc_step) = + extract_accum_add_const(&inner_facts.acc_update, &inner_facts.acc_var) + else { + return Ok(None); + }; + + if acc_step != 1 { + return Ok(None); + } + + let (inner_init_lit, outer_increment) = match scan_outer_body( + body, + inner_idx, + &outer_loop_var, + &inner_facts.loop_var, + ) { + Some(values) => values, + None => return Ok(None), + }; + + if inner_init_lit != 0 { + return Ok(None); + } + + if extract_increment_step_one(&outer_increment, &outer_loop_var).is_none() { + return Ok(None); + } + + Ok(Some(Pattern6NestedMinimalFacts { + outer_loop_var, + outer_condition: condition.clone(), + outer_increment, + inner_loop_var: inner_facts.loop_var, + inner_condition: inner_facts.condition, + inner_increment: inner_step, + acc_var: inner_facts.acc_var, + acc_update: inner_facts.acc_update, + inner_init_lit, + })) +} + +fn find_single_inner_loop(body: &[ASTNode]) -> Option<(usize, &ASTNode)> { + let mut found = None; + for (idx, stmt) in body.iter().enumerate() { + if matches!(stmt, ASTNode::Loop { .. }) { + if found.is_some() { + return None; + } + found = Some((idx, stmt)); + } + } + found +} + +fn scan_outer_body( + body: &[ASTNode], + inner_idx: usize, + outer_loop_var: &str, + inner_loop_var: &str, +) -> Option<(i64, ASTNode)> { + let mut inner_init_lit = None; + let mut outer_increment = None; + let mut outer_increment_idx = None; + + for (idx, stmt) in body.iter().enumerate() { + if matches!(stmt, ASTNode::Loop { .. }) { + if idx != inner_idx { + return None; + } + continue; + } + + match stmt { + ASTNode::Local { + variables, + initial_values, + .. + } => { + if variables.len() != 1 || variables[0] != inner_loop_var { + return None; + } + if idx > inner_idx { + return None; + } + if let Some(Some(init)) = initial_values.get(0) { + let lit = extract_int_literal(init)?; + if inner_init_lit.replace(lit).is_some() { + return None; + } + } + } + ASTNode::Assignment { target, value, .. } => { + let ASTNode::Variable { name, .. } = target.as_ref() else { + return None; + }; + if name == inner_loop_var { + if idx > inner_idx { + return None; + } + let lit = extract_int_literal(value)?; + if inner_init_lit.replace(lit).is_some() { + return None; + } + } else if name == outer_loop_var { + outer_increment_idx = Some(idx); + outer_increment = Some(value.as_ref().clone()); + } else { + return None; + } + } + _ => return None, + } + } + + let inner_init_lit = inner_init_lit?; + let outer_increment = outer_increment?; + let outer_increment_idx = outer_increment_idx?; + + if outer_increment_idx <= inner_idx { + return None; + } + if outer_increment_idx + 1 != body.len() { + return None; + } + + Some((inner_init_lit, outer_increment)) +} + +fn extract_loop_var_for_subset(condition: &ASTNode) -> Option { + let ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left, + right, + .. + } = condition + else { + return None; + }; + + let ASTNode::Variable { name, .. } = left.as_ref() else { + return None; + }; + + if !matches!( + right.as_ref(), + ASTNode::Literal { + value: LiteralValue::Integer(_), + .. + } + ) { + return None; + } + + Some(name.clone()) +} + +fn extract_int_literal(node: &ASTNode) -> Option { + match node { + ASTNode::Literal { + value: LiteralValue::Integer(value), + .. + } => Some(*value), + _ => None, + } +} + +fn extract_increment_step_one(value: &ASTNode, loop_var: &str) -> Option { + let ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left, + right, + .. + } = value + else { + return None; + }; + + let ASTNode::Variable { name, .. } = left.as_ref() else { + return None; + }; + if name != loop_var { + return None; + } + + if !matches!( + right.as_ref(), + ASTNode::Literal { + value: LiteralValue::Integer(1), + .. + } + ) { + return None; + } + + Some(value.clone()) +} + +fn extract_accum_add_const(update: &ASTNode, acc_var: &str) -> Option { + let ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left, + right, + .. + } = update + else { + return None; + }; + + let ASTNode::Variable { name, .. } = left.as_ref() else { + return None; + }; + if name != acc_var { + return None; + } + + extract_int_literal(right) +} + +#[cfg(test)] +mod tests { + use super::try_extract_pattern6_nested_minimal_facts; + use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; + + fn v(name: &str) -> ASTNode { + ASTNode::Variable { + name: name.to_string(), + span: Span::unknown(), + } + } + + fn lit_int(value: i64) -> ASTNode { + ASTNode::Literal { + value: LiteralValue::Integer(value), + span: Span::unknown(), + } + } + + fn condition_lt(loop_var: &str, bound: i64) -> ASTNode { + ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(v(loop_var)), + right: Box::new(lit_int(bound)), + span: Span::unknown(), + } + } + + fn increment(loop_var: &str, step: i64) -> ASTNode { + ASTNode::Assignment { + target: Box::new(v(loop_var)), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v(loop_var)), + right: Box::new(lit_int(step)), + span: Span::unknown(), + }), + span: Span::unknown(), + } + } + + fn accum_const(acc_var: &str, step: i64) -> ASTNode { + ASTNode::Assignment { + target: Box::new(v(acc_var)), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v(acc_var)), + right: Box::new(lit_int(step)), + span: Span::unknown(), + }), + span: Span::unknown(), + } + } + + #[test] + fn facts_extracts_nested_minimal_subset() { + let inner_loop = ASTNode::Loop { + condition: Box::new(condition_lt("j", 3)), + body: vec![accum_const("sum", 1), increment("j", 1)], + span: Span::unknown(), + }; + let body = vec![ + ASTNode::Local { + variables: vec!["j".to_string()], + initial_values: vec![None], + span: Span::unknown(), + }, + ASTNode::Assignment { + target: Box::new(v("j")), + value: Box::new(lit_int(0)), + span: Span::unknown(), + }, + inner_loop, + increment("i", 1), + ]; + let condition = condition_lt("i", 3); + + let facts = + try_extract_pattern6_nested_minimal_facts(&condition, &body).expect("Ok"); + let facts = facts.expect("Some"); + + assert_eq!(facts.outer_loop_var, "i"); + assert_eq!(facts.inner_loop_var, "j"); + assert_eq!(facts.acc_var, "sum"); + assert_eq!(facts.inner_init_lit, 0); + } + + #[test] + fn facts_rejects_missing_inner_init() { + let inner_loop = ASTNode::Loop { + condition: Box::new(condition_lt("j", 3)), + body: vec![accum_const("sum", 1), increment("j", 1)], + span: Span::unknown(), + }; + let body = vec![inner_loop, increment("i", 1)]; + let condition = condition_lt("i", 3); + + let facts = + try_extract_pattern6_nested_minimal_facts(&condition, &body).expect("Ok"); + assert!(facts.is_none()); + } +} diff --git a/src/mir/builder/control_flow/plan/normalize/canonicalize.rs b/src/mir/builder/control_flow/plan/normalize/canonicalize.rs index 5d292832..7461a833 100644 --- a/src/mir/builder/control_flow/plan/normalize/canonicalize.rs +++ b/src/mir/builder/control_flow/plan/normalize/canonicalize.rs @@ -110,6 +110,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); assert_eq!(canonical.skeleton_kind, SkeletonKind::Loop); @@ -149,6 +150,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); assert!(canonical.exit_kinds_present.is_empty()); @@ -193,6 +195,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); assert!(canonical.facts.pattern1_simplewhile.is_some()); diff --git a/src/mir/builder/control_flow/plan/normalizer/helpers.rs b/src/mir/builder/control_flow/plan/normalizer/helpers.rs index 57957175..272c5cd9 100644 --- a/src/mir/builder/control_flow/plan/normalizer/helpers.rs +++ b/src/mir/builder/control_flow/plan/normalizer/helpers.rs @@ -97,7 +97,7 @@ pub(super) fn create_phi_bindings(bindings: &[(&str, ValueId)]) -> BTreeMap, @@ -136,7 +136,7 @@ impl super::PlanNormalizer { } /// Helper: Lower BinOp AST to (lhs ValueId, op, rhs ValueId, const_effects) - pub(super) fn lower_binop_ast( + pub(in crate::mir::builder) fn lower_binop_ast( ast: &crate::ast::ASTNode, builder: &mut MirBuilder, phi_bindings: &BTreeMap, diff --git a/src/mir/builder/control_flow/plan/planner/build.rs b/src/mir/builder/control_flow/plan/planner/build.rs index 481e96ac..edc77a3a 100644 --- a/src/mir/builder/control_flow/plan/planner/build.rs +++ b/src/mir/builder/control_flow/plan/planner/build.rs @@ -537,6 +537,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); @@ -574,6 +575,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); @@ -606,6 +608,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); @@ -647,6 +650,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); @@ -682,6 +686,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts_ctx(&PlannerContext::default_for_legacy(), canonical) @@ -727,6 +732,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); @@ -786,6 +792,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); @@ -854,6 +861,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); @@ -906,6 +914,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); @@ -969,6 +978,7 @@ mod tests { loop_increment, }), pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); @@ -1018,6 +1028,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let _ = build_plan_from_facts(canonical).expect("Ok"); @@ -1083,6 +1094,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); @@ -1152,6 +1164,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); @@ -1211,6 +1224,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); @@ -1268,6 +1282,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); @@ -1329,6 +1344,7 @@ mod tests { }), pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); @@ -1407,6 +1423,7 @@ mod tests { i_var: "i".to_string(), }, }), + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); let plan = build_plan_from_facts(canonical).expect("Ok"); diff --git a/src/mir/builder/control_flow/plan/verifier.rs b/src/mir/builder/control_flow/plan/verifier.rs index 1a6fb7d9..0e42675d 100644 --- a/src/mir/builder/control_flow/plan/verifier.rs +++ b/src/mir/builder/control_flow/plan/verifier.rs @@ -639,6 +639,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); debug_assert_value_join_invariants(&canonical); @@ -673,6 +674,7 @@ mod tests { pattern9_accum_const_loop: None, pattern2_break: None, pattern2_loopbodylocal: None, + pattern6_nested_minimal: None, }; let canonical = canonicalize_loop_facts(facts); debug_assert_value_join_invariants(&canonical); diff --git a/tools/smokes/v2/profiles/integration/joinir/phase29ap_pattern6_nested_strict_shadow_vm.sh b/tools/smokes/v2/profiles/integration/joinir/phase29ap_pattern6_nested_strict_shadow_vm.sh index 5950e6bb..4eafd05e 100644 --- a/tools/smokes/v2/profiles/integration/joinir/phase29ap_pattern6_nested_strict_shadow_vm.sh +++ b/tools/smokes/v2/profiles/integration/joinir/phase29ap_pattern6_nested_strict_shadow_vm.sh @@ -2,15 +2,15 @@ # phase29ap_pattern6_nested_strict_shadow_vm.sh - Pattern6 nested minimal strict shadow gate (VM) # # Expected: -# - Exit code 1 -# - Tag: [plan/freeze:unstructured] +# - Exit code 9 +# - Tag: [coreplan/shadow_adopt:pattern6_nested_minimal] source "$(dirname "$0")/../../../lib/test_runner.sh" require_env || exit 2 FIXTURE="$NYASH_ROOT/apps/tests/phase1883_nested_minimal.hako" RUN_TIMEOUT_SECS=${RUN_TIMEOUT_SECS:-10} -TAG='[plan/freeze:unstructured]' +TAG='[coreplan/shadow_adopt:pattern6_nested_minimal]' set +e OUTPUT=$(timeout "$RUN_TIMEOUT_SECS" env HAKO_JOINIR_STRICT=1 "$NYASH_BIN" --backend vm "$FIXTURE" 2>&1) @@ -22,8 +22,8 @@ if [ "$EXIT_CODE" -eq 124 ]; then exit 1 fi -if [ "$EXIT_CODE" -ne 1 ]; then - echo "[FAIL] Expected exit 1, got $EXIT_CODE" +if [ "$EXIT_CODE" -ne 9 ]; then + echo "[FAIL] Expected exit 9, got $EXIT_CODE" echo "$OUTPUT" | tail -n 40 || true test_fail "phase29ap_pattern6_nested_strict_shadow_vm: Unexpected RC" exit 1 @@ -36,5 +36,12 @@ if ! echo "$OUTPUT" | grep -qF "$TAG"; then exit 1 fi -test_pass "phase29ap_pattern6_nested_strict_shadow_vm: PASS (exit=1, tag)" +if echo "$OUTPUT" | grep -qF '[plan/freeze:unstructured]'; then + echo "[FAIL] Unexpected freeze tag in strict shadow adopt" + echo "$OUTPUT" | tail -n 40 || true + test_fail "phase29ap_pattern6_nested_strict_shadow_vm: Unexpected freeze tag" + exit 1 +fi + +test_pass "phase29ap_pattern6_nested_strict_shadow_vm: PASS (exit=9, tag)" exit 0