phase29ap(p10): coreplan adopt nested minimal (strict/dev)
This commit is contained in:
@ -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)
|
||||
|
||||
|
||||
@ -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`
|
||||
|
||||
|
||||
@ -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(再発防止の土台)
|
||||
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user