phase29ao(p33): planner-derive pattern2 loopbodylocal smokes

This commit is contained in:
2025-12-30 15:58:19 +09:00
parent 363549b152
commit 59a29a86d3
12 changed files with 407 additions and 25 deletions

View File

@ -7,6 +7,9 @@ use crate::mir::builder::control_flow::plan::extractors::common_helpers::{
has_return_statement as common_has_return, ControlFlowDetector, extract_loop_increment_plan,
is_true_literal,
};
use crate::mir::builder::control_flow::plan::facts::pattern2_loopbodylocal_facts::{
try_extract_pattern2_loopbodylocal_facts, LoopBodyLocalShape,
};
#[derive(Debug, Clone)]
pub(in crate::mir::builder) struct Pattern2BreakFacts {
@ -27,6 +30,10 @@ pub(in crate::mir::builder) fn try_extract_pattern2_break_facts(
return Ok(Some(realworld));
}
if let Some(loopbodylocal) = try_extract_pattern2_break_loopbodylocal_subset(condition, body)? {
return Ok(Some(loopbodylocal));
}
let Some(loop_var) = extract_loop_var_for_plan_subset(condition) else {
return Ok(None);
};
@ -152,6 +159,207 @@ fn try_extract_pattern2_break_realworld_subset(
})
}
fn try_extract_pattern2_break_loopbodylocal_subset(
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Option<Pattern2BreakFacts>, Freeze> {
let Some(loop_var) = extract_loop_var_for_len_condition(condition) else {
return Ok(None);
};
let counts = count_control_flow(body, ControlFlowDetector::default());
if counts.break_count != 1 || counts.continue_count > 0 || counts.return_count > 0 {
return Ok(None);
}
if body.len() != 3 && body.len() != 4 {
return Ok(None);
}
let loopbodylocal = match try_extract_pattern2_loopbodylocal_facts(condition, body)? {
Some(facts) => facts,
None => return Ok(None),
};
if loopbodylocal.loop_var != loop_var {
return Ok(None);
}
let (break_idx, _, carrier_update_in_break) = match find_break_if_parts(body) {
Some(parts) => parts,
None => return Ok(None),
};
if carrier_update_in_break.is_some() {
return Ok(None);
}
if has_assignment_after(body, break_idx, &loopbodylocal.loopbodylocal_var) {
return Ok(None);
}
let break_condition = match loopbodylocal.shape {
LoopBodyLocalShape::TrimSeg { s_var, i_var } => {
if i_var != loop_var {
return Ok(None);
}
let seg_expr = substring_call(
&s_var,
var(&loop_var),
add(var(&loop_var), lit_int(1)),
);
let is_space = ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(seg_expr.clone()),
right: Box::new(lit_str(" ")),
span: Span::unknown(),
};
let is_tab = ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(seg_expr),
right: Box::new(lit_str("\t")),
span: Span::unknown(),
};
ASTNode::BinaryOp {
operator: BinaryOperator::Or,
left: Box::new(is_space),
right: Box::new(is_tab),
span: Span::unknown(),
}
}
LoopBodyLocalShape::DigitPos { digits_var, ch_var } => {
let ch_expr = match find_local_init_expr(body, &ch_var) {
Some(expr) => expr,
None => return Ok(None),
};
let index_expr = index_of_call_expr(&digits_var, ch_expr);
ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(index_expr),
right: Box::new(lit_int(0)),
span: Span::unknown(),
}
}
};
let loop_increment = match extract_loop_increment_at_end(body, &loop_var) {
Some(inc) => inc,
None => return Ok(None),
};
Ok(Some(Pattern2BreakFacts {
loop_var: loop_var.clone(),
carrier_var: loop_var,
loop_condition: condition.clone(),
break_condition,
carrier_update_in_break: None,
carrier_update_in_body: loop_increment.clone(),
loop_increment,
}))
}
fn extract_loop_var_for_len_condition(condition: &ASTNode) -> Option<String> {
let ASTNode::BinaryOp {
operator: BinaryOperator::Less | BinaryOperator::LessEqual,
left,
right,
..
} = condition
else {
return None;
};
let ASTNode::Variable { name, .. } = left.as_ref() else {
return None;
};
if !matches!(
right.as_ref(),
ASTNode::MethodCall { object, method, arguments, .. }
if method == "length"
&& arguments.is_empty()
&& matches!(object.as_ref(), ASTNode::Variable { .. })
) {
return None;
}
Some(name.clone())
}
fn find_break_if_parts(body: &[ASTNode]) -> Option<(usize, ASTNode, Option<ASTNode>)> {
for (idx, stmt) in body.iter().enumerate() {
if let Some(parts) = extract_break_if_parts(stmt) {
return Some((idx, parts.0, parts.1));
}
}
None
}
fn has_assignment_after(body: &[ASTNode], start_idx: usize, var_name: &str) -> bool {
for stmt in body.iter().skip(start_idx + 1) {
let ASTNode::Assignment { target, .. } = stmt else {
continue;
};
if matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == var_name) {
return true;
}
}
false
}
fn find_local_init_expr(body: &[ASTNode], name: &str) -> Option<ASTNode> {
for stmt in body {
let ASTNode::Local {
variables,
initial_values,
..
} = stmt
else {
continue;
};
if variables.len() != 1 || initial_values.len() != 1 {
continue;
}
if variables[0] != name {
continue;
}
let Some(expr) = initial_values[0].as_ref() else {
return None;
};
return Some((*expr.clone()).clone());
}
None
}
fn extract_loop_increment_at_end(body: &[ASTNode], loop_var: &str) -> Option<ASTNode> {
let last = body.last()?;
let ASTNode::Assignment { target, value, .. } = last else {
return None;
};
let ASTNode::Variable { name, .. } = target.as_ref() else {
return None;
};
if name != loop_var {
return None;
}
let ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left,
right,
..
} = value.as_ref()
else {
return None;
};
if !matches!(left.as_ref(), ASTNode::Variable { name, .. } if name == loop_var) {
return None;
}
if !matches!(
right.as_ref(),
ASTNode::Literal {
value: LiteralValue::Integer(_),
..
}
) {
return None;
}
Some(value.as_ref().clone())
}
fn extract_loop_var_for_plan_subset(condition: &ASTNode) -> Option<String> {
let ASTNode::BinaryOp {
operator: BinaryOperator::Less,
@ -505,6 +713,15 @@ fn var(name: &str) -> ASTNode {
}
}
fn add(left: ASTNode, right: ASTNode) -> ASTNode {
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(left),
right: Box::new(right),
span: Span::unknown(),
}
}
fn lit_int(value: i64) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::Integer(value),
@ -537,6 +754,15 @@ fn index_of_call(haystack: &str, sep: &str, loop_var: &str) -> ASTNode {
}
}
fn index_of_call_expr(haystack: &str, needle: ASTNode) -> ASTNode {
ASTNode::MethodCall {
object: Box::new(var(haystack)),
method: "indexOf".to_string(),
arguments: vec![needle],
span: Span::unknown(),
}
}
fn substring_call(haystack: &str, start: ASTNode, end: ASTNode) -> ASTNode {
ASTNode::MethodCall {
object: Box::new(var(haystack)),
@ -700,4 +926,76 @@ mod tests {
other => panic!("unexpected loop_increment: {:?}", other),
}
}
#[test]
fn extract_pattern2_break_loopbodylocal_trim_seg_subset() {
let condition = binop(
BinaryOperator::Less,
v("i"),
method_call("s", "length", vec![]),
);
let body = vec![
local(
"seg",
method_call(
"s",
"substring",
vec![v("i"), binop(BinaryOperator::Add, v("i"), lit_int(1))],
),
),
ASTNode::If {
condition: Box::new(binop(
BinaryOperator::Or,
binop(BinaryOperator::Equal, v("seg"), lit_str(" ")),
binop(BinaryOperator::Equal, v("seg"), lit_str("\t")),
)),
then_body: vec![ASTNode::Break { span: Span::unknown() }],
else_body: None,
span: Span::unknown(),
},
assign("i", binop(BinaryOperator::Add, v("i"), lit_int(1))),
];
let facts = try_extract_pattern2_break_facts(&condition, &body)
.expect("Ok")
.expect("Some facts");
assert_eq!(facts.loop_var, "i");
assert_eq!(facts.carrier_var, "i");
}
#[test]
fn extract_pattern2_break_loopbodylocal_digit_pos_subset() {
let condition = binop(
BinaryOperator::Less,
v("p"),
method_call("s", "length", vec![]),
);
let body = vec![
local(
"ch",
method_call(
"s",
"substring",
vec![v("p"), binop(BinaryOperator::Add, v("p"), lit_int(1))],
),
),
local(
"digit_pos",
method_call("digits", "indexOf", vec![v("ch")]),
),
ASTNode::If {
condition: Box::new(binop(BinaryOperator::Less, v("digit_pos"), lit_int(0))),
then_body: vec![ASTNode::Break { span: Span::unknown() }],
else_body: None,
span: Span::unknown(),
},
assign("p", binop(BinaryOperator::Add, v("p"), lit_int(1))),
];
let facts = try_extract_pattern2_break_facts(&condition, &body)
.expect("Ok")
.expect("Some facts");
assert_eq!(facts.loop_var, "p");
assert_eq!(facts.carrier_var, "p");
}
}

View File

@ -257,6 +257,18 @@ impl super::PlanNormalizer {
Ok((result_id, arg_effects))
}
ASTNode::BinaryOp { .. } => {
let (lhs, op, rhs, mut consts) =
Self::lower_binop_ast(ast, builder, phi_bindings)?;
let result_id = builder.alloc_typed(MirType::Integer);
consts.push(CoreEffectPlan::BinOp {
dst: result_id,
lhs,
op,
rhs,
});
Ok((result_id, consts))
}
_ => Err(format!("[normalizer] Unsupported value AST: {:?}", ast)),
}
}

View File

@ -7,7 +7,7 @@ use crate::mir::builder::control_flow::edgecfg::api::{
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::mir::{BinaryOp, ConstValue, MirType};
use crate::mir::{BinaryOp, ConstValue, MirType, ValueId};
use std::collections::BTreeMap;
impl super::PlanNormalizer {
@ -115,9 +115,6 @@ impl super::PlanNormalizer {
let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) =
Self::lower_compare_ast(&parts.loop_condition, builder, &phi_bindings)?;
let (break_cond_lhs, break_cond_op, break_cond_rhs, break_cond_consts) =
Self::lower_compare_ast(&parts.break_condition, builder, &phi_bindings)?;
let break_then_effects = if let Some(ref break_update_ast) = parts.carrier_update_in_break {
let (lhs, op, rhs, consts) =
Self::lower_binop_ast(break_update_ast, builder, &phi_bindings)?;
@ -162,15 +159,15 @@ impl super::PlanNormalizer {
// Step 7: Build body plans (break condition check)
let mut body_plans: Vec<CorePlan> = Vec::new();
for const_effect in break_cond_consts {
body_plans.push(CorePlan::Effect(const_effect));
let break_cond_effects = Self::lower_break_condition_effects(
&parts.break_condition,
builder,
&phi_bindings,
cond_break,
)?;
for effect in break_cond_effects {
body_plans.push(CorePlan::Effect(effect));
}
body_plans.push(CorePlan::Effect(CoreEffectPlan::Compare {
dst: cond_break,
lhs: break_cond_lhs,
op: break_cond_op,
rhs: break_cond_rhs,
}));
// Step 8: Build step_effects
let mut step_effects = carrier_consts;
@ -310,4 +307,59 @@ impl super::PlanNormalizer {
Ok(CorePlan::Loop(loop_plan))
}
fn lower_break_condition_effects(
ast: &crate::ast::ASTNode,
builder: &mut MirBuilder,
phi_bindings: &BTreeMap<String, ValueId>,
dst: ValueId,
) -> Result<Vec<CoreEffectPlan>, String> {
use crate::ast::{ASTNode, BinaryOperator};
match ast {
ASTNode::BinaryOp {
operator: BinaryOperator::Or,
left,
right,
..
} => {
let (lhs_l, op_l, rhs_l, consts_l) =
Self::lower_compare_ast(left, builder, phi_bindings)?;
let (lhs_r, op_r, rhs_r, consts_r) =
Self::lower_compare_ast(right, builder, phi_bindings)?;
let left_dst = builder.alloc_typed(MirType::Bool);
let right_dst = builder.alloc_typed(MirType::Bool);
let mut effects = Vec::new();
effects.extend(consts_l);
effects.push(CoreEffectPlan::Compare {
dst: left_dst,
lhs: lhs_l,
op: op_l,
rhs: rhs_l,
});
effects.extend(consts_r);
effects.push(CoreEffectPlan::Compare {
dst: right_dst,
lhs: lhs_r,
op: op_r,
rhs: rhs_r,
});
effects.push(CoreEffectPlan::BinOp {
dst,
lhs: left_dst,
op: BinaryOp::Or,
rhs: right_dst,
});
Ok(effects)
}
_ => {
let (lhs, op, rhs, consts) = Self::lower_compare_ast(ast, builder, phi_bindings)?;
let mut effects = consts;
effects.push(CoreEffectPlan::Compare { dst, lhs, op, rhs });
Ok(effects)
}
}
}
}