phase29ai(p4): first LoopFacts + scan_with_init candidate
This commit is contained in:
@ -11,6 +11,7 @@ use crate::ast::ASTNode;
|
||||
|
||||
use super::scan_shapes::{ConditionShape, StepShape};
|
||||
use crate::mir::builder::control_flow::plan::planner::Freeze;
|
||||
use crate::ast::{BinaryOperator, LiteralValue};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::mir::builder) struct LoopFacts {
|
||||
@ -19,8 +20,171 @@ pub(in crate::mir::builder) struct LoopFacts {
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) fn try_build_loop_facts(
|
||||
_condition: &ASTNode,
|
||||
_body: &[ASTNode],
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
) -> Result<Option<LoopFacts>, Freeze> {
|
||||
Ok(None)
|
||||
let Some(condition_shape) = try_extract_condition_shape(condition)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(step_shape) = try_extract_step_shape(body)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Phase 29ai P4: minimal canonical guard (avoid false-positive facts).
|
||||
if let (
|
||||
ConditionShape::VarLessVar { left, .. },
|
||||
StepShape::AssignAddConst { var, k: 1 },
|
||||
) = (&condition_shape, &step_shape)
|
||||
{
|
||||
if var != left {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(LoopFacts {
|
||||
condition_shape,
|
||||
step_shape,
|
||||
}))
|
||||
}
|
||||
|
||||
fn try_extract_condition_shape(condition: &ASTNode) -> Result<Option<ConditionShape>, Freeze> {
|
||||
let ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} = condition
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let ASTNode::Variable { name: left, .. } = left.as_ref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let ASTNode::Variable { name: right, .. } = right.as_ref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(Some(ConditionShape::VarLessVar {
|
||||
left: left.clone(),
|
||||
right: right.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn try_extract_step_shape(body: &[ASTNode]) -> Result<Option<StepShape>, Freeze> {
|
||||
let Some(last) = body.last() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let ASTNode::Assignment { target, value, .. } = last else {
|
||||
return Ok(None);
|
||||
};
|
||||
let ASTNode::Variable { name: var, .. } = target.as_ref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} = value.as_ref()
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let ASTNode::Variable { name: lhs, .. } = left.as_ref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
if lhs != var {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let ASTNode::Literal { value, .. } = right.as_ref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let LiteralValue::Integer(k) = value else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
if *k != 1 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some(StepShape::AssignAddConst {
|
||||
var: var.clone(),
|
||||
k: *k,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::Span;
|
||||
|
||||
fn v(name: &str) -> ASTNode {
|
||||
ASTNode::Variable {
|
||||
name: name.to_string(),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopfacts_ok_some_for_canonical_scan_with_init_minimal() {
|
||||
let condition = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(v("i")),
|
||||
right: Box::new(v("n")),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let step = ASTNode::Assignment {
|
||||
target: Box::new(v("i")),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(v("i")),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let facts = try_build_loop_facts(&condition, &[step]).expect("Ok");
|
||||
assert!(facts.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopfacts_ok_none_when_condition_not_supported() {
|
||||
let condition = v("i"); // not `i < n`
|
||||
let facts = try_build_loop_facts(&condition, &[]).expect("Ok");
|
||||
assert!(facts.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopfacts_ok_none_when_step_var_differs_from_condition_var() {
|
||||
let condition = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(v("i")),
|
||||
right: Box::new(v("n")),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
let step = ASTNode::Assignment {
|
||||
target: Box::new(v("j")),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(v("j")),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let facts = try_build_loop_facts(&condition, &[step]).expect("Ok");
|
||||
assert!(facts.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,3 +10,4 @@ pub(in crate::mir::builder) mod loop_facts;
|
||||
pub(in crate::mir::builder) mod scan_shapes;
|
||||
|
||||
pub(in crate::mir::builder) use loop_facts::{try_build_loop_facts, LoopFacts};
|
||||
pub(in crate::mir::builder) use scan_shapes::{ConditionShape, StepShape};
|
||||
|
||||
@ -2,12 +2,14 @@
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(in crate::mir::builder) enum StepShape {
|
||||
AssignAddConst { var: String, k: i64 },
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(in crate::mir::builder) enum ConditionShape {
|
||||
VarLessVar { left: String, right: String },
|
||||
Unknown,
|
||||
}
|
||||
|
||||
@ -8,7 +8,9 @@ use crate::mir::builder::control_flow::plan::facts::try_build_loop_facts;
|
||||
use crate::mir::builder::control_flow::plan::normalize::{canonicalize_loop_facts, CanonicalLoopFacts};
|
||||
|
||||
use super::candidates::CandidateSet;
|
||||
use super::{Freeze, Plan};
|
||||
use super::candidates::PlanCandidate;
|
||||
use super::{Freeze, Plan, PlanKind};
|
||||
use crate::mir::builder::control_flow::plan::facts::{ConditionShape, StepShape};
|
||||
|
||||
/// Phase 29ai P0: External-ish SSOT entrypoint (skeleton)
|
||||
///
|
||||
@ -25,7 +27,7 @@ pub(in crate::mir::builder) fn build_plan(
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) fn build_plan_from_facts(
|
||||
_facts: CanonicalLoopFacts,
|
||||
facts: CanonicalLoopFacts,
|
||||
) -> Result<Option<Plan>, Freeze> {
|
||||
// Phase 29ai P3: CandidateSet-based boundary (SSOT)
|
||||
//
|
||||
@ -33,13 +35,32 @@ pub(in crate::mir::builder) fn build_plan_from_facts(
|
||||
// unreachable in normal execution today. We still implement the SSOT
|
||||
// boundary here so that future Facts work cannot drift.
|
||||
|
||||
let candidates = CandidateSet::new();
|
||||
let mut candidates = CandidateSet::new();
|
||||
|
||||
// No candidates in P3 (unreachable today); boundary remains stable.
|
||||
match candidates.finalize()? {
|
||||
Some(plan) => Ok(Some(plan)),
|
||||
None => Err(Freeze::unsupported(
|
||||
"facts were provided but no planner rule produced a candidate (P3 placeholder)",
|
||||
)),
|
||||
if matches_canonical_scan_with_init(&facts)? {
|
||||
candidates.push(PlanCandidate {
|
||||
kind: PlanKind::ScanWithInit,
|
||||
rule: "loop/scan_with_init",
|
||||
});
|
||||
}
|
||||
|
||||
candidates.finalize()
|
||||
}
|
||||
|
||||
fn matches_canonical_scan_with_init(facts: &CanonicalLoopFacts) -> Result<bool, Freeze> {
|
||||
let (ConditionShape::VarLessVar { left, right: _ }, StepShape::AssignAddConst { var, k }) = (
|
||||
&facts.facts.condition_shape,
|
||||
&facts.facts.step_shape,
|
||||
) else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
if var != left {
|
||||
return Ok(false);
|
||||
}
|
||||
if *k != 1 {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ pub(in crate::mir::builder) struct Plan {
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::mir::builder) enum PlanKind {
|
||||
Placeholder,
|
||||
ScanWithInit,
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) use build::build_plan;
|
||||
|
||||
Reference in New Issue
Block a user