refactor(joinir): boxify Pattern2 routing and schedule facts
This commit is contained in:
@ -75,7 +75,8 @@ use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
|
||||
use crate::mir::join_ir::lowering::step_schedule::{
|
||||
build_pattern2_schedule_from_decision, decide_pattern2_schedule, Pattern2StepKind,
|
||||
build_pattern2_schedule_from_decision, decide_pattern2_schedule, Pattern2ScheduleFactsBox,
|
||||
Pattern2StepKind,
|
||||
};
|
||||
use crate::mir::join_ir::lowering::update_env::UpdateEnv;
|
||||
use crate::mir::loop_canonicalizer::LoopSkeleton;
|
||||
@ -381,12 +382,18 @@ pub(crate) fn lower_loop_with_break_minimal(
|
||||
// Decide evaluation order (header/body-init/break/updates/tail) up-front.
|
||||
// Phase 93 P0: Pass condition_only_recipe existence to schedule context.
|
||||
// When recipe exists, body-init must happen before break check.
|
||||
let schedule_decision = decide_pattern2_schedule(
|
||||
let has_allowed_body_locals_in_conditions = loop_cond_scope.has_loop_body_local()
|
||||
&& allowed_body_locals_for_conditions
|
||||
.map(|allow| !allow.is_empty())
|
||||
.unwrap_or(false);
|
||||
let schedule_facts = Pattern2ScheduleFactsBox::gather(
|
||||
body_local_env.as_ref().map(|env| &**env),
|
||||
carrier_info,
|
||||
condition_only_recipe.is_some(),
|
||||
body_local_derived_recipe.is_some(),
|
||||
has_allowed_body_locals_in_conditions,
|
||||
);
|
||||
let schedule_decision = decide_pattern2_schedule(&schedule_facts);
|
||||
let schedule = build_pattern2_schedule_from_decision(&schedule_decision);
|
||||
|
||||
// Collect fragments per step; append them according to the schedule below.
|
||||
|
||||
@ -91,7 +91,49 @@ pub(crate) struct ScheduleDebugContext {
|
||||
pub has_body_local_derived_recipe: bool,
|
||||
}
|
||||
|
||||
/// Decide Pattern2 schedule based on loop characteristics
|
||||
/// Facts about Pattern2 that drive step scheduling.
|
||||
///
|
||||
/// This struct is intentionally "facts only" (no decision).
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Pattern2ScheduleFacts {
|
||||
pub has_body_local_init: bool,
|
||||
pub has_loop_local_carrier: bool,
|
||||
pub has_condition_only_recipe: bool,
|
||||
pub has_body_local_derived_recipe: bool,
|
||||
}
|
||||
|
||||
pub(crate) struct Pattern2ScheduleFactsBox;
|
||||
|
||||
impl Pattern2ScheduleFactsBox {
|
||||
pub(crate) fn gather(
|
||||
body_local_env: Option<&LoopBodyLocalEnv>,
|
||||
carrier_info: &CarrierInfo,
|
||||
has_condition_only_recipe: bool,
|
||||
has_body_local_derived_recipe: bool,
|
||||
has_allowed_body_locals_in_conditions: bool,
|
||||
) -> Pattern2ScheduleFacts {
|
||||
// NOTE: `body_local_env` may be empty here because it's populated after schedule
|
||||
// decision (Phase 191 body-local init lowering happens later).
|
||||
//
|
||||
// For Phase 92+ patterns where conditions reference allowed body-local variables
|
||||
// (e.g., `ch == ""`), we must still schedule BodyInit before BreakCheck.
|
||||
let has_body_local_init = body_local_env.map(|env| !env.is_empty()).unwrap_or(false)
|
||||
|| has_allowed_body_locals_in_conditions;
|
||||
let has_loop_local_carrier = carrier_info
|
||||
.carriers
|
||||
.iter()
|
||||
.any(|c| matches!(c.init, CarrierInit::LoopLocalZero));
|
||||
|
||||
Pattern2ScheduleFacts {
|
||||
has_body_local_init,
|
||||
has_loop_local_carrier,
|
||||
has_condition_only_recipe,
|
||||
has_body_local_derived_recipe,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Decide Pattern2 schedule based on loop characteristics (SSOT).
|
||||
///
|
||||
/// Phase 93 Refactoring: Single source of truth for schedule decisions
|
||||
///
|
||||
@ -111,30 +153,19 @@ pub(crate) struct ScheduleDebugContext {
|
||||
/// # Returns
|
||||
///
|
||||
/// `ScheduleDecision` with decision, reason, and debug context
|
||||
pub(crate) fn decide_pattern2_schedule(
|
||||
body_local_env: Option<&LoopBodyLocalEnv>,
|
||||
carrier_info: &CarrierInfo,
|
||||
has_condition_only_recipe: bool,
|
||||
has_body_local_derived_recipe: bool,
|
||||
) -> ScheduleDecision {
|
||||
let has_body_local_init = body_local_env.map(|env| !env.is_empty()).unwrap_or(false);
|
||||
let has_loop_local_carrier = carrier_info
|
||||
.carriers
|
||||
.iter()
|
||||
.any(|c| matches!(c.init, CarrierInit::LoopLocalZero));
|
||||
pub(crate) fn decide_pattern2_schedule(facts: &Pattern2ScheduleFacts) -> ScheduleDecision {
|
||||
let body_init_first = facts.has_condition_only_recipe
|
||||
|| facts.has_body_local_derived_recipe
|
||||
|| facts.has_body_local_init
|
||||
|| facts.has_loop_local_carrier;
|
||||
|
||||
let body_init_first = has_condition_only_recipe
|
||||
|| has_body_local_derived_recipe
|
||||
|| has_body_local_init
|
||||
|| has_loop_local_carrier;
|
||||
|
||||
let reason = if has_condition_only_recipe {
|
||||
let reason = if facts.has_condition_only_recipe {
|
||||
"ConditionOnly requires body-init before break"
|
||||
} else if has_body_local_derived_recipe {
|
||||
} else if facts.has_body_local_derived_recipe {
|
||||
"BodyLocalDerived requires body-init before break"
|
||||
} else if has_body_local_init {
|
||||
} else if facts.has_body_local_init {
|
||||
"body-local variables require init before break"
|
||||
} else if has_loop_local_carrier {
|
||||
} else if facts.has_loop_local_carrier {
|
||||
"loop-local carrier requires init before break"
|
||||
} else {
|
||||
"default schedule"
|
||||
@ -144,10 +175,10 @@ pub(crate) fn decide_pattern2_schedule(
|
||||
body_init_first,
|
||||
reason,
|
||||
debug_ctx: ScheduleDebugContext {
|
||||
has_body_local_init,
|
||||
has_loop_local_carrier,
|
||||
has_condition_only_recipe,
|
||||
has_body_local_derived_recipe,
|
||||
has_body_local_init: facts.has_body_local_init,
|
||||
has_loop_local_carrier: facts.has_loop_local_carrier,
|
||||
has_condition_only_recipe: facts.has_condition_only_recipe,
|
||||
has_body_local_derived_recipe: facts.has_body_local_derived_recipe,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -263,7 +294,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn default_schedule_break_before_body_init() {
|
||||
let decision = decide_pattern2_schedule(None, &carrier_info(vec![]), false, false);
|
||||
let facts = Pattern2ScheduleFactsBox::gather(None, &carrier_info(vec![]), false, false, false);
|
||||
let decision = decide_pattern2_schedule(&facts);
|
||||
let schedule = build_pattern2_schedule_from_decision(&decision);
|
||||
assert_eq!(
|
||||
schedule.steps(),
|
||||
@ -283,12 +315,14 @@ mod tests {
|
||||
let mut body_env = LoopBodyLocalEnv::new();
|
||||
body_env.insert("tmp".to_string(), ValueId(5));
|
||||
|
||||
let decision = decide_pattern2_schedule(
|
||||
let facts = Pattern2ScheduleFactsBox::gather(
|
||||
Some(&body_env),
|
||||
&carrier_info(vec![carrier(false)]),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
let decision = decide_pattern2_schedule(&facts);
|
||||
let schedule = build_pattern2_schedule_from_decision(&decision);
|
||||
assert_eq!(
|
||||
schedule.steps(),
|
||||
@ -305,12 +339,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn loop_local_carrier_triggers_body_first() {
|
||||
let decision = decide_pattern2_schedule(
|
||||
None,
|
||||
&carrier_info(vec![carrier(true)]),
|
||||
false,
|
||||
false,
|
||||
);
|
||||
let facts =
|
||||
Pattern2ScheduleFactsBox::gather(None, &carrier_info(vec![carrier(true)]), false, false, false);
|
||||
let decision = decide_pattern2_schedule(&facts);
|
||||
let schedule = build_pattern2_schedule_from_decision(&decision);
|
||||
assert_eq!(
|
||||
schedule.steps(),
|
||||
@ -329,7 +360,8 @@ mod tests {
|
||||
#[test]
|
||||
fn condition_only_recipe_triggers_body_first() {
|
||||
// Empty body_local_env but has condition_only_recipe
|
||||
let decision = decide_pattern2_schedule(None, &carrier_info(vec![]), true, false);
|
||||
let facts = Pattern2ScheduleFactsBox::gather(None, &carrier_info(vec![]), true, false, false);
|
||||
let decision = decide_pattern2_schedule(&facts);
|
||||
let schedule = build_pattern2_schedule_from_decision(&decision);
|
||||
assert_eq!(
|
||||
schedule.steps(),
|
||||
@ -344,6 +376,24 @@ mod tests {
|
||||
assert_eq!(schedule.reason(), "ConditionOnly requires body-init before break");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allowed_body_local_deps_trigger_body_first_even_if_env_empty() {
|
||||
let facts = Pattern2ScheduleFactsBox::gather(None, &carrier_info(vec![]), false, false, true);
|
||||
let decision = decide_pattern2_schedule(&facts);
|
||||
let schedule = build_pattern2_schedule_from_decision(&decision);
|
||||
assert_eq!(
|
||||
schedule.steps(),
|
||||
&[
|
||||
Pattern2StepKind::HeaderCond,
|
||||
Pattern2StepKind::BodyInit,
|
||||
Pattern2StepKind::BreakCheck,
|
||||
Pattern2StepKind::Updates,
|
||||
Pattern2StepKind::Tail
|
||||
]
|
||||
);
|
||||
assert_eq!(schedule.reason(), "body-local variables require init before break");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pattern3_if_sum_schedule() {
|
||||
let schedule = pattern3_if_sum_schedule();
|
||||
|
||||
Reference in New Issue
Block a user