diff --git a/src/mir/builder/control_flow/joinir/patterns/mod.rs b/src/mir/builder/control_flow/joinir/patterns/mod.rs index b1c1903d..32e03c5b 100644 --- a/src/mir/builder/control_flow/joinir/patterns/mod.rs +++ b/src/mir/builder/control_flow/joinir/patterns/mod.rs @@ -57,6 +57,7 @@ pub(in crate::mir::builder) mod common_init; pub(in crate::mir::builder) mod loop_true_counter_extractor; // Phase 104: loop(true) counter extraction for Pattern2 pub(in crate::mir::builder) mod read_digits_break_condition_box; // Phase 104: break cond normalization for read_digits(loop(true)) pub(in crate::mir::builder) mod pattern2_break_condition_policy_router; // Phase 105: policy router box for Pattern2 break condition +pub(in crate::mir::builder) mod pattern2_policy_router; // Phase 108: unified Pattern2 policy router (balanced/read_digits/default) pub(in crate::mir::builder) mod pattern2_inputs_facts_box; // Phase 105: Pattern2 input facts (analysis only) pub(in crate::mir::builder) mod pattern2_lowering_orchestrator; // Phase 105: Pattern2 orchestration (wiring/emission) pub(in crate::mir::builder) mod pattern2_steps; // Phase 106: Pattern2 step boxes (pipeline SSOT) diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_inputs_facts_box.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_inputs_facts_box.rs index 0606c106..b2ac3efb 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_inputs_facts_box.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_inputs_facts_box.rs @@ -94,7 +94,7 @@ pub(in crate::mir::builder) struct Pattern2Inputs { pub carrier_updates_override: Option>, /// Phase 107: Post-loop early return plan for return-in-loop normalization. pub post_loop_early_return: Option< - crate::mir::builder::control_flow::joinir::patterns::policies::balanced_depth_scan_policy::PostLoopEarlyReturnPlan, + crate::mir::builder::control_flow::joinir::patterns::policies::post_loop_early_return_plan::PostLoopEarlyReturnPlan, >, } diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_policy_router.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_policy_router.rs new file mode 100644 index 00000000..8067323e --- /dev/null +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_policy_router.rs @@ -0,0 +1,80 @@ +//! Pattern2 policy router (Phase 108) +//! +//! Responsibility (SSOT): +//! - Decide which Pattern2 policy route applies (balanced depth-scan / loop(true) read-digits / default). +//! - Normalize the outputs into a single routing struct consumed by ApplyPolicyStepBox. +//! +//! NOTE: This box does not emit JoinIR. It only provides "what to do" facts. + +use crate::ast::ASTNode; +use crate::mir::join_ir::lowering::common::body_local_slot::ReadOnlyBodyLocalSlotBox; + +use super::pattern2_break_condition_policy_router::Pattern2BreakConditionPolicyRouterBox; +use super::pattern2_inputs_facts_box::BodyLocalHandlingPolicy; +use super::policies::balanced_depth_scan_policy_box::BalancedDepthScanPolicyBox; +use super::policies::PolicyDecision; + +#[derive(Debug)] +pub(crate) struct Pattern2PolicyRouting { + pub allowed_body_locals_for_conditions: Vec, + pub body_local_handling: BodyLocalHandlingPolicy, + pub read_only_body_local_slot: Option, + pub break_condition_node: ASTNode, + pub is_loop_true_read_digits: bool, + pub balanced_depth_scan_recipe: + Option, + pub carrier_updates_override: Option< + std::collections::BTreeMap, + >, + pub post_loop_early_return: Option< + crate::mir::builder::control_flow::joinir::patterns::policies::post_loop_early_return_plan::PostLoopEarlyReturnPlan, + >, +} + +pub(crate) struct Pattern2PolicyRouterBox; + +impl Pattern2PolicyRouterBox { + pub(crate) fn route(condition: &ASTNode, body: &[ASTNode]) -> Result { + // Route 1 (Phase 107): balanced depth-scan (return-in-loop normalization). + match BalancedDepthScanPolicyBox::decide(condition, body) { + PolicyDecision::Use(result) => { + return Ok(Pattern2PolicyRouting { + allowed_body_locals_for_conditions: result.allowed_body_locals_for_conditions, + body_local_handling: BodyLocalHandlingPolicy::SkipPromotion, + read_only_body_local_slot: None, + break_condition_node: result.break_condition_node, + is_loop_true_read_digits: false, + balanced_depth_scan_recipe: Some(result.derived_recipe), + carrier_updates_override: Some(result.carrier_updates_override), + post_loop_early_return: Some(result.post_loop_early_return), + }); + } + PolicyDecision::Reject(reason) => return Err(reason), + PolicyDecision::None => {} + } + + // Route 2 (Phase 105): loop(true) read-digits family + default break-cond SSOT. + let break_routing = Pattern2BreakConditionPolicyRouterBox::route(condition, body)?; + + let read_only_body_local_slot = if break_routing.allowed_body_locals_for_conditions.is_empty() { + None + } else { + Some(ReadOnlyBodyLocalSlotBox::extract_single( + &break_routing.allowed_body_locals_for_conditions, + body, + )?) + }; + + Ok(Pattern2PolicyRouting { + allowed_body_locals_for_conditions: break_routing.allowed_body_locals_for_conditions, + body_local_handling: BodyLocalHandlingPolicy::DefaultPromotion, + read_only_body_local_slot, + break_condition_node: break_routing.break_condition_node, + is_loop_true_read_digits: break_routing.is_loop_true_read_digits, + balanced_depth_scan_recipe: None, + carrier_updates_override: None, + post_loop_early_return: None, + }) + } +} + diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_steps/apply_policy_step_box.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_steps/apply_policy_step_box.rs index eff90f3e..ef8c19b7 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_steps/apply_policy_step_box.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_steps/apply_policy_step_box.rs @@ -4,60 +4,14 @@ use crate::ast::ASTNode; -use super::super::pattern2_break_condition_policy_router::Pattern2BreakConditionPolicyRouterBox; +use super::super::pattern2_policy_router::Pattern2PolicyRouterBox; use super::super::pattern2_inputs_facts_box::{Pattern2Facts, Pattern2Inputs}; pub(crate) struct ApplyPolicyStepBox; impl ApplyPolicyStepBox { pub(crate) fn apply(condition: &ASTNode, body: &[ASTNode], facts: Pattern2Facts) -> Result { - use super::super::policies::balanced_depth_scan_policy_box::BalancedDepthScanPolicyBox; - use super::super::policies::PolicyDecision; - use crate::mir::builder::control_flow::joinir::patterns::pattern2_inputs_facts_box::BodyLocalHandlingPolicy; - use crate::mir::join_ir::lowering::common::body_local_slot::ReadOnlyBodyLocalSlotBox; - - // Phase 107: balanced depth-scan (return-in-loop) policy. - // This route provides its own break-cond + derived recipe + post-loop early return plan. - match BalancedDepthScanPolicyBox::decide(condition, body) { - PolicyDecision::Use(result) => { - return Ok(Pattern2Inputs { - loop_var_name: facts.loop_var_name, - loop_var_id: facts.loop_var_id, - carrier_info: facts.carrier_info, - scope: facts.scope, - captured_env: facts.captured_env, - join_value_space: facts.join_value_space, - env: facts.env, - condition_bindings: facts.condition_bindings, - body_local_env: facts.body_local_env, - allowed_body_locals_for_conditions: result.allowed_body_locals_for_conditions, - body_local_handling: BodyLocalHandlingPolicy::SkipPromotion, - read_only_body_local_slot: None, - break_condition_node: result.break_condition_node, - is_loop_true_read_digits: false, - condition_only_recipe: None, - body_local_derived_recipe: None, - balanced_depth_scan_recipe: Some(result.derived_recipe), - carrier_updates_override: Some(result.carrier_updates_override), - post_loop_early_return: Some(result.post_loop_early_return), - }); - } - PolicyDecision::Reject(reason) => { - return Err(reason); - } - PolicyDecision::None => {} - } - - let break_routing = Pattern2BreakConditionPolicyRouterBox::route(condition, body)?; - - let read_only_body_local_slot = if break_routing.allowed_body_locals_for_conditions.is_empty() { - None - } else { - Some(ReadOnlyBodyLocalSlotBox::extract_single( - &break_routing.allowed_body_locals_for_conditions, - body, - )?) - }; + let policy = Pattern2PolicyRouterBox::route(condition, body)?; Ok(Pattern2Inputs { loop_var_name: facts.loop_var_name, @@ -69,16 +23,16 @@ impl ApplyPolicyStepBox { env: facts.env, condition_bindings: facts.condition_bindings, body_local_env: facts.body_local_env, - allowed_body_locals_for_conditions: break_routing.allowed_body_locals_for_conditions, - body_local_handling: BodyLocalHandlingPolicy::DefaultPromotion, - read_only_body_local_slot, - break_condition_node: break_routing.break_condition_node, - is_loop_true_read_digits: break_routing.is_loop_true_read_digits, + allowed_body_locals_for_conditions: policy.allowed_body_locals_for_conditions, + body_local_handling: policy.body_local_handling, + read_only_body_local_slot: policy.read_only_body_local_slot, + break_condition_node: policy.break_condition_node, + is_loop_true_read_digits: policy.is_loop_true_read_digits, condition_only_recipe: None, body_local_derived_recipe: None, - balanced_depth_scan_recipe: None, - carrier_updates_override: None, - post_loop_early_return: None, + balanced_depth_scan_recipe: policy.balanced_depth_scan_recipe, + carrier_updates_override: policy.carrier_updates_override, + post_loop_early_return: policy.post_loop_early_return, }) } } diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern2_steps/post_loop_early_return_step_box.rs b/src/mir/builder/control_flow/joinir/patterns/pattern2_steps/post_loop_early_return_step_box.rs index 30ff174d..13b04061 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern2_steps/post_loop_early_return_step_box.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern2_steps/post_loop_early_return_step_box.rs @@ -4,7 +4,7 @@ //! early-return guard. This keeps JoinIR lowering "break-only" while preserving //! source semantics for the recognized family. -use crate::ast::{ASTNode, BinaryOperator, Span}; +use crate::ast::{ASTNode, Span}; use crate::mir::builder::MirBuilder; pub(crate) struct PostLoopEarlyReturnStepBox; @@ -13,35 +13,18 @@ impl PostLoopEarlyReturnStepBox { pub(crate) fn maybe_emit( builder: &mut MirBuilder, plan: Option< - &crate::mir::builder::control_flow::joinir::patterns::policies::balanced_depth_scan_policy::PostLoopEarlyReturnPlan, + &crate::mir::builder::control_flow::joinir::patterns::policies::post_loop_early_return_plan::PostLoopEarlyReturnPlan, >, ) -> Result<(), String> { let Some(plan) = plan else { return Ok(()); }; - // if i < n { return i } - let cond = ASTNode::BinaryOp { - operator: BinaryOperator::Less, - left: Box::new(ASTNode::Variable { - name: plan.loop_counter_name.clone(), - span: Span::unknown(), - }), - right: Box::new(ASTNode::Variable { - name: plan.bound_name.clone(), - span: Span::unknown(), - }), - span: Span::unknown(), - }; - let ret_stmt = ASTNode::Return { - value: Some(Box::new(ASTNode::Variable { - name: plan.loop_counter_name.clone(), - span: Span::unknown(), - })), + value: Some(Box::new(plan.ret_expr.clone())), span: Span::unknown(), }; builder.build_statement(ASTNode::If { - condition: Box::new(cond), + condition: Box::new(plan.cond.clone()), then_body: vec![ret_stmt], else_body: None, span: Span::unknown(), diff --git a/src/mir/builder/control_flow/joinir/patterns/policies/balanced_depth_scan_policy.rs b/src/mir/builder/control_flow/joinir/patterns/policies/balanced_depth_scan_policy.rs index fd444027..3d3ae879 100644 --- a/src/mir/builder/control_flow/joinir/patterns/policies/balanced_depth_scan_policy.rs +++ b/src/mir/builder/control_flow/joinir/patterns/policies/balanced_depth_scan_policy.rs @@ -12,14 +12,9 @@ use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs} use crate::mir::join_ir::BinOpKind; use super::PolicyDecision; +use super::post_loop_early_return_plan::PostLoopEarlyReturnPlan; use std::collections::BTreeMap; -#[derive(Debug, Clone)] -pub(crate) struct PostLoopEarlyReturnPlan { - pub loop_counter_name: String, - pub bound_name: String, -} - #[derive(Debug, Clone)] pub(crate) struct BalancedDepthScanPolicyResult { pub break_condition_node: ASTNode, @@ -101,8 +96,13 @@ fn classify_balanced_depth_scan( depth_next_name, }, post_loop_early_return: PostLoopEarlyReturnPlan { - loop_counter_name, - bound_name, + cond: ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(var(&loop_counter_name)), + right: Box::new(var(&bound_name)), + span: Span::unknown(), + }, + ret_expr: var(&loop_counter_name), }, }) } @@ -618,8 +618,28 @@ mod tests { other => panic!("expected Use, got {:?}", other), }; - assert_eq!(result.post_loop_early_return.loop_counter_name, "i"); - assert_eq!(result.post_loop_early_return.bound_name, "n"); + assert!( + matches!( + &result.post_loop_early_return.cond, + ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left, + right, + .. + } if matches!(left.as_ref(), ASTNode::Variable { name, .. } if name == "i") + && matches!(right.as_ref(), ASTNode::Variable { name, .. } if name == "n") + ), + "post-loop cond must be `i < n`, got {:?}", + result.post_loop_early_return.cond + ); + assert!( + matches!( + &result.post_loop_early_return.ret_expr, + ASTNode::Variable { name, .. } if name == "i" + ), + "post-loop ret_expr must be `i`, got {:?}", + result.post_loop_early_return.ret_expr + ); assert!(result.allowed_body_locals_for_conditions.contains(&"ch".to_string())); assert!(result.allowed_body_locals_for_conditions.contains(&"depth_next".to_string())); assert!(result.carrier_updates_override.contains_key("i")); diff --git a/src/mir/builder/control_flow/joinir/patterns/policies/mod.rs b/src/mir/builder/control_flow/joinir/patterns/policies/mod.rs index 9e0144ec..23f69ce3 100644 --- a/src/mir/builder/control_flow/joinir/patterns/policies/mod.rs +++ b/src/mir/builder/control_flow/joinir/patterns/policies/mod.rs @@ -35,3 +35,4 @@ pub(in crate::mir::builder) mod trim_policy; pub(in crate::mir::builder) mod loop_true_read_digits_policy; pub(in crate::mir::builder) mod balanced_depth_scan_policy; pub(in crate::mir::builder) mod balanced_depth_scan_policy_box; +pub(in crate::mir::builder) mod post_loop_early_return_plan; diff --git a/src/mir/builder/control_flow/joinir/patterns/policies/post_loop_early_return_plan.rs b/src/mir/builder/control_flow/joinir/patterns/policies/post_loop_early_return_plan.rs new file mode 100644 index 00000000..a0b38e69 --- /dev/null +++ b/src/mir/builder/control_flow/joinir/patterns/policies/post_loop_early_return_plan.rs @@ -0,0 +1,15 @@ +//! Post-loop early return plan (policy-level SSOT) +//! +//! Responsibility: +//! - Describe a post-loop guard that emulates an in-loop `return` without making +//! Pattern2 lowering itself return-in-loop aware. +//! - Keep the plan policy-agnostic so multiple Pattern2 families can reuse it. + +use crate::ast::ASTNode; + +#[derive(Debug, Clone)] +pub(crate) struct PostLoopEarlyReturnPlan { + pub cond: ASTNode, + pub ret_expr: ASTNode, +} +