refactor(joinir): Phase 108 unify Pattern2 policy routing

This commit is contained in:
nyash-codex
2025-12-17 23:30:08 +09:00
parent 9fa2f5a8ad
commit 27e8e0f16a
8 changed files with 142 additions and 88 deletions

View File

@ -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 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 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_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_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_lowering_orchestrator; // Phase 105: Pattern2 orchestration (wiring/emission)
pub(in crate::mir::builder) mod pattern2_steps; // Phase 106: Pattern2 step boxes (pipeline SSOT) pub(in crate::mir::builder) mod pattern2_steps; // Phase 106: Pattern2 step boxes (pipeline SSOT)

View File

@ -94,7 +94,7 @@ pub(in crate::mir::builder) struct Pattern2Inputs {
pub carrier_updates_override: Option<std::collections::BTreeMap<String, UpdateExpr>>, pub carrier_updates_override: Option<std::collections::BTreeMap<String, UpdateExpr>>,
/// Phase 107: Post-loop early return plan for return-in-loop normalization. /// Phase 107: Post-loop early return plan for return-in-loop normalization.
pub post_loop_early_return: Option< 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,
>, >,
} }

View File

@ -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<String>,
pub body_local_handling: BodyLocalHandlingPolicy,
pub read_only_body_local_slot: Option<crate::mir::join_ir::lowering::common::body_local_slot::ReadOnlyBodyLocalSlot>,
pub break_condition_node: ASTNode,
pub is_loop_true_read_digits: bool,
pub balanced_depth_scan_recipe:
Option<crate::mir::join_ir::lowering::common::balanced_depth_scan_emitter::BalancedDepthScanRecipe>,
pub carrier_updates_override: Option<
std::collections::BTreeMap<String, crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr>,
>,
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<Pattern2PolicyRouting, String> {
// 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,
})
}
}

View File

@ -4,60 +4,14 @@
use crate::ast::ASTNode; 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}; use super::super::pattern2_inputs_facts_box::{Pattern2Facts, Pattern2Inputs};
pub(crate) struct ApplyPolicyStepBox; pub(crate) struct ApplyPolicyStepBox;
impl ApplyPolicyStepBox { impl ApplyPolicyStepBox {
pub(crate) fn apply(condition: &ASTNode, body: &[ASTNode], facts: Pattern2Facts) -> Result<Pattern2Inputs, String> { pub(crate) fn apply(condition: &ASTNode, body: &[ASTNode], facts: Pattern2Facts) -> Result<Pattern2Inputs, String> {
use super::super::policies::balanced_depth_scan_policy_box::BalancedDepthScanPolicyBox; let policy = Pattern2PolicyRouterBox::route(condition, body)?;
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,
)?)
};
Ok(Pattern2Inputs { Ok(Pattern2Inputs {
loop_var_name: facts.loop_var_name, loop_var_name: facts.loop_var_name,
@ -69,16 +23,16 @@ impl ApplyPolicyStepBox {
env: facts.env, env: facts.env,
condition_bindings: facts.condition_bindings, condition_bindings: facts.condition_bindings,
body_local_env: facts.body_local_env, body_local_env: facts.body_local_env,
allowed_body_locals_for_conditions: break_routing.allowed_body_locals_for_conditions, allowed_body_locals_for_conditions: policy.allowed_body_locals_for_conditions,
body_local_handling: BodyLocalHandlingPolicy::DefaultPromotion, body_local_handling: policy.body_local_handling,
read_only_body_local_slot, read_only_body_local_slot: policy.read_only_body_local_slot,
break_condition_node: break_routing.break_condition_node, break_condition_node: policy.break_condition_node,
is_loop_true_read_digits: break_routing.is_loop_true_read_digits, is_loop_true_read_digits: policy.is_loop_true_read_digits,
condition_only_recipe: None, condition_only_recipe: None,
body_local_derived_recipe: None, body_local_derived_recipe: None,
balanced_depth_scan_recipe: None, balanced_depth_scan_recipe: policy.balanced_depth_scan_recipe,
carrier_updates_override: None, carrier_updates_override: policy.carrier_updates_override,
post_loop_early_return: None, post_loop_early_return: policy.post_loop_early_return,
}) })
} }
} }

View File

@ -4,7 +4,7 @@
//! early-return guard. This keeps JoinIR lowering "break-only" while preserving //! early-return guard. This keeps JoinIR lowering "break-only" while preserving
//! source semantics for the recognized family. //! source semantics for the recognized family.
use crate::ast::{ASTNode, BinaryOperator, Span}; use crate::ast::{ASTNode, Span};
use crate::mir::builder::MirBuilder; use crate::mir::builder::MirBuilder;
pub(crate) struct PostLoopEarlyReturnStepBox; pub(crate) struct PostLoopEarlyReturnStepBox;
@ -13,35 +13,18 @@ impl PostLoopEarlyReturnStepBox {
pub(crate) fn maybe_emit( pub(crate) fn maybe_emit(
builder: &mut MirBuilder, builder: &mut MirBuilder,
plan: Option< 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> { ) -> Result<(), String> {
let Some(plan) = plan else { return Ok(()); }; 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 { let ret_stmt = ASTNode::Return {
value: Some(Box::new(ASTNode::Variable { value: Some(Box::new(plan.ret_expr.clone())),
name: plan.loop_counter_name.clone(),
span: Span::unknown(),
})),
span: Span::unknown(), span: Span::unknown(),
}; };
builder.build_statement(ASTNode::If { builder.build_statement(ASTNode::If {
condition: Box::new(cond), condition: Box::new(plan.cond.clone()),
then_body: vec![ret_stmt], then_body: vec![ret_stmt],
else_body: None, else_body: None,
span: Span::unknown(), span: Span::unknown(),

View File

@ -12,14 +12,9 @@ use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs}
use crate::mir::join_ir::BinOpKind; use crate::mir::join_ir::BinOpKind;
use super::PolicyDecision; use super::PolicyDecision;
use super::post_loop_early_return_plan::PostLoopEarlyReturnPlan;
use std::collections::BTreeMap; use std::collections::BTreeMap;
#[derive(Debug, Clone)]
pub(crate) struct PostLoopEarlyReturnPlan {
pub loop_counter_name: String,
pub bound_name: String,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct BalancedDepthScanPolicyResult { pub(crate) struct BalancedDepthScanPolicyResult {
pub break_condition_node: ASTNode, pub break_condition_node: ASTNode,
@ -101,8 +96,13 @@ fn classify_balanced_depth_scan(
depth_next_name, depth_next_name,
}, },
post_loop_early_return: PostLoopEarlyReturnPlan { post_loop_early_return: PostLoopEarlyReturnPlan {
loop_counter_name, cond: ASTNode::BinaryOp {
bound_name, 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), other => panic!("expected Use, got {:?}", other),
}; };
assert_eq!(result.post_loop_early_return.loop_counter_name, "i"); assert!(
assert_eq!(result.post_loop_early_return.bound_name, "n"); 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(&"ch".to_string()));
assert!(result.allowed_body_locals_for_conditions.contains(&"depth_next".to_string())); assert!(result.allowed_body_locals_for_conditions.contains(&"depth_next".to_string()));
assert!(result.carrier_updates_override.contains_key("i")); assert!(result.carrier_updates_override.contains_key("i"));

View File

@ -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 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;
pub(in crate::mir::builder) mod balanced_depth_scan_policy_box; pub(in crate::mir::builder) mod balanced_depth_scan_policy_box;
pub(in crate::mir::builder) mod post_loop_early_return_plan;

View File

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