phase29ap(p10): coreplan adopt nested minimal (strict/dev)

This commit is contained in:
2025-12-31 07:49:48 +09:00
parent b209e17f3b
commit efe5e2deed
19 changed files with 1032 additions and 12 deletions

View File

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

View File

@ -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`

View File

@ -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再発防止の土台

View File

@ -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 < <int>)` with step `i = i + 1` (step=1 only).
- Inner loop: `loop(j < <int>)` 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`

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Option<CorePlan>, 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<ASTNode>) {
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());
}
}

View File

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

View File

@ -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<Option<ShadowAdoptOutcome>, 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,

View File

@ -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<Pattern3IfPhiFacts>,
pub pattern4_continue: Option<Pattern4ContinueFacts>,
pub pattern5_infinite_early_exit: Option<Pattern5InfiniteEarlyExitFacts>,
pub pattern6_nested_minimal: Option<Pattern6NestedMinimalFacts>,
pub pattern8_bool_predicate_scan: Option<Pattern8BoolPredicateScanFacts>,
pub pattern9_accum_const_loop: Option<Pattern9AccumConstLoopFacts>,
pub pattern2_break: Option<Pattern2BreakFacts>,
@ -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,

View File

@ -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;

View File

@ -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<Option<Pattern6NestedMinimalFacts>, 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<String> {
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<i64> {
match node {
ASTNode::Literal {
value: LiteralValue::Integer(value),
..
} => Some(*value),
_ => None,
}
}
fn extract_increment_step_one(value: &ASTNode, loop_var: &str) -> Option<ASTNode> {
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<i64> {
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());
}
}

View File

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

View File

@ -97,7 +97,7 @@ pub(super) fn create_phi_bindings(bindings: &[(&str, ValueId)]) -> BTreeMap<Stri
impl super::PlanNormalizer {
/// Helper: Lower Compare AST to (lhs ValueId, op, rhs ValueId, const_effects)
pub(super) fn lower_compare_ast(
pub(in crate::mir::builder) fn lower_compare_ast(
ast: &crate::ast::ASTNode,
builder: &mut MirBuilder,
phi_bindings: &BTreeMap<String, ValueId>,
@ -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<String, ValueId>,

View File

@ -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");

View File

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

View File

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