refactor(joinir): Phase 106 Pattern2 step boxes
This commit is contained in:
@ -59,6 +59,7 @@ pub(in crate::mir::builder) mod read_digits_break_condition_box; // Phase 104: b
|
||||
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_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)
|
||||
pub(in crate::mir::builder) mod condition_env_builder;
|
||||
pub(in crate::mir::builder) mod conversion_pipeline;
|
||||
pub(in crate::mir::builder) mod exit_binding;
|
||||
|
||||
@ -1,20 +1,17 @@
|
||||
//! Pattern2 input analysis (facts collection only)
|
||||
//!
|
||||
//! This box collects everything Pattern2 lowering needs *without* emitting JoinIR:
|
||||
//! This box collects everything Pattern2 lowering needs *without* emitting JoinIR
|
||||
//! and *without* applying policy routing:
|
||||
//! - capture/pinned local analysis
|
||||
//! - mutable accumulator promotion into carriers
|
||||
//! - condition env + JoinValueSpace initialization
|
||||
//! - break-condition routing (policy SSOT)
|
||||
//! - (optional) body-local allow-list + slot metadata (Phase 92 P3)
|
||||
//!
|
||||
//! The goal is to keep orchestration/emission code out of this module.
|
||||
|
||||
use super::pattern2_break_condition_policy_router::Pattern2BreakConditionPolicyRouterBox;
|
||||
//! Policy routing (break condition normalization + allow-list) is a separate step
|
||||
//! in Phase 106 (`pattern2_steps::ApplyPolicyStepBox`).
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierInit};
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierInfo;
|
||||
use crate::mir::join_ir::lowering::condition_env::{ConditionBinding, ConditionEnv};
|
||||
use crate::mir::join_ir::lowering::common::body_local_slot::ReadOnlyBodyLocalSlot;
|
||||
use crate::mir::join_ir::lowering::debug_output_box::DebugOutputBox;
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||||
@ -36,13 +33,25 @@ impl Pattern2DebugLog {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn log(&self, tag: &str, message: impl AsRef<str>) {
|
||||
pub(crate) fn log(&self, tag: &str, message: impl AsRef<str>) {
|
||||
if self.verbose {
|
||||
self.debug.log(tag, message.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) struct Pattern2Facts {
|
||||
pub loop_var_name: String,
|
||||
pub loop_var_id: ValueId,
|
||||
pub carrier_info: CarrierInfo,
|
||||
pub scope: LoopScopeShape,
|
||||
pub captured_env: CapturedEnv,
|
||||
pub join_value_space: JoinValueSpace,
|
||||
pub env: ConditionEnv,
|
||||
pub condition_bindings: Vec<ConditionBinding>,
|
||||
pub body_local_env: LoopBodyLocalEnv,
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) struct Pattern2Inputs {
|
||||
pub loop_var_name: String,
|
||||
pub loop_var_id: ValueId,
|
||||
@ -57,12 +66,10 @@ pub(in crate::mir::builder) struct Pattern2Inputs {
|
||||
/// This must stay minimal (1 variable) and is validated by ReadOnlyBodyLocalSlotBox.
|
||||
pub allowed_body_locals_for_conditions: Vec<String>,
|
||||
/// Phase 92 P3: Diagnostics / debug metadata for the allow-listed variable.
|
||||
pub read_only_body_local_slot: Option<ReadOnlyBodyLocalSlot>,
|
||||
pub read_only_body_local_slot: Option<crate::mir::join_ir::lowering::common::body_local_slot::ReadOnlyBodyLocalSlot>,
|
||||
/// Policy-routed "break when true" condition node.
|
||||
pub break_condition_node: ASTNode,
|
||||
/// loop(true) + break-only digits(read_digits_from family)
|
||||
///
|
||||
/// Policy-routed: multiple breaks are normalized into a single `break when true`
|
||||
/// condition, and Pattern2 must schedule body-init before break check.
|
||||
pub is_loop_true_read_digits: bool,
|
||||
/// Phase 93 P0: ConditionOnly recipe for derived slot recalculation
|
||||
pub condition_only_recipe: Option<
|
||||
@ -83,7 +90,7 @@ impl Pattern2InputsFactsBox {
|
||||
fn_body: Option<&[ASTNode]>,
|
||||
ctx: &super::pattern_pipeline::PatternPipelineContext,
|
||||
verbose: bool,
|
||||
) -> Result<Pattern2Inputs, String> {
|
||||
) -> Result<Pattern2Facts, String> {
|
||||
let log = Pattern2DebugLog::new(verbose);
|
||||
use super::condition_env_builder::ConditionEnvBuilder;
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::{analyze_captured_vars_v2, CapturedEnv};
|
||||
@ -359,11 +366,7 @@ impl Pattern2InputsFactsBox {
|
||||
}
|
||||
|
||||
let body_local_env = LoopBodyLocalEnv::new();
|
||||
|
||||
// Break condition extraction is policy-routed (SSOT).
|
||||
let break_routing = Pattern2BreakConditionPolicyRouterBox::route(condition, body)?;
|
||||
|
||||
let mut inputs = Pattern2Inputs {
|
||||
Ok(Pattern2Facts {
|
||||
loop_var_name,
|
||||
loop_var_id,
|
||||
carrier_info,
|
||||
@ -373,25 +376,6 @@ impl Pattern2InputsFactsBox {
|
||||
env,
|
||||
condition_bindings,
|
||||
body_local_env,
|
||||
allowed_body_locals_for_conditions: Vec::new(),
|
||||
read_only_body_local_slot: None,
|
||||
break_condition_node: break_routing.break_condition_node,
|
||||
is_loop_true_read_digits: break_routing.is_loop_true_read_digits,
|
||||
condition_only_recipe: None,
|
||||
body_local_derived_recipe: None,
|
||||
};
|
||||
|
||||
if !break_routing.allowed_body_locals_for_conditions.is_empty() {
|
||||
use crate::mir::join_ir::lowering::common::body_local_slot::ReadOnlyBodyLocalSlotBox;
|
||||
inputs.allowed_body_locals_for_conditions =
|
||||
break_routing.allowed_body_locals_for_conditions.clone();
|
||||
inputs.read_only_body_local_slot = Some(ReadOnlyBodyLocalSlotBox::extract_single(
|
||||
&inputs.allowed_body_locals_for_conditions,
|
||||
body,
|
||||
)?);
|
||||
}
|
||||
|
||||
Ok(inputs)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,29 +1,17 @@
|
||||
//! Pattern2 lowering orchestration (wiring + emission)
|
||||
//!
|
||||
//! This module is the "do the work" side of Pattern2:
|
||||
//! - promotion/slot routing for body-local vars in conditions
|
||||
//! - trim normalization (when enabled)
|
||||
//! - derived-slot routing (Phase 94)
|
||||
//! - carrier update analysis + filtering
|
||||
//! - invoking JoinIR lowerer + boundary merge pipeline
|
||||
//!
|
||||
//! It intentionally depends on `Pattern2InputsFactsBox` for analysis-only inputs.
|
||||
//! Phase 106: the orchestrator is intentionally thin.
|
||||
//! Most "do the work" logic lives in explicit step boxes under `pattern2_steps/`.
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierInit};
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionBinding;
|
||||
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
|
||||
use crate::mir::loop_pattern_detection::error_messages;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
use super::pattern2_inputs_facts_box::{Pattern2DebugLog, Pattern2Inputs, Pattern2InputsFactsBox};
|
||||
|
||||
use super::body_local_policy::{classify_for_pattern2, BodyLocalRoute};
|
||||
use super::policies::p5b_escape_derived_policy::{classify_p5b_escape_derived, P5bEscapeDerivedDecision};
|
||||
use super::policies::PolicyDecision;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use super::pattern2_steps::apply_policy_step_box::ApplyPolicyStepBox;
|
||||
use super::pattern2_steps::emit_joinir_step_box::EmitJoinIRStepBox;
|
||||
use super::pattern2_steps::gather_facts_step_box::GatherFactsStepBox;
|
||||
use super::pattern2_steps::merge_step_box::MergeStepBox;
|
||||
use super::pattern2_steps::promote_step_box::PromoteStepBox;
|
||||
|
||||
pub(crate) struct Pattern2LoweringOrchestrator;
|
||||
|
||||
@ -32,15 +20,12 @@ impl Pattern2LoweringOrchestrator {
|
||||
builder: &mut MirBuilder,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
func_name: &str,
|
||||
_func_name: &str,
|
||||
debug: bool,
|
||||
fn_body: Option<&[ASTNode]>,
|
||||
skeleton: Option<&crate::mir::loop_canonicalizer::LoopSkeleton>,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
use crate::mir::join_ir::lowering::loop_with_break_minimal::lower_loop_with_break_minimal;
|
||||
|
||||
let verbose = debug || crate::config::env::joinir_dev_enabled();
|
||||
let log = Pattern2DebugLog::new(verbose);
|
||||
|
||||
super::super::trace::trace().debug("pattern2", "Calling Pattern 2 minimal lowerer");
|
||||
|
||||
@ -49,355 +34,26 @@ impl Pattern2LoweringOrchestrator {
|
||||
|
||||
super::super::trace::trace().varmap("pattern2_start", &builder.variable_ctx.variable_map);
|
||||
|
||||
let mut inputs = Pattern2InputsFactsBox::analyze(builder, condition, body, fn_body, &ctx, verbose)?;
|
||||
let facts = GatherFactsStepBox::gather(builder, condition, body, fn_body, &ctx, verbose)?;
|
||||
let inputs = ApplyPolicyStepBox::apply(condition, body, facts)?;
|
||||
|
||||
promote_and_prepare_carriers(builder, condition, body, &mut inputs, debug, verbose)?;
|
||||
|
||||
let (effective_break_condition, normalized_body) =
|
||||
apply_trim_and_normalize(builder, condition, body, &mut inputs, verbose)?;
|
||||
let mut promoted = PromoteStepBox::run(builder, condition, body, inputs, debug, verbose)?;
|
||||
let normalized_body = promoted.normalized_body.take();
|
||||
let analysis_body = normalized_body.as_deref().unwrap_or(body);
|
||||
|
||||
// Phase 94: Detect P5b escape-derived (`ch` reassignment + escape counter).
|
||||
match classify_p5b_escape_derived(analysis_body, &inputs.loop_var_name) {
|
||||
P5bEscapeDerivedDecision::Use(recipe) => {
|
||||
log.log(
|
||||
"phase94",
|
||||
format!(
|
||||
"Phase 94: Enabled BodyLocalDerived for '{}' (counter='{}', pre_delta={}, post_delta={})",
|
||||
recipe.name, recipe.loop_counter_name, recipe.pre_delta, recipe.post_delta
|
||||
),
|
||||
);
|
||||
inputs.body_local_derived_recipe = Some(recipe);
|
||||
}
|
||||
P5bEscapeDerivedDecision::Reject(reason) => return Err(format!("[cf_loop/pattern2] {}", reason)),
|
||||
P5bEscapeDerivedDecision::None => {
|
||||
let has_ch_reassign = analysis_body.iter().any(|n| match n {
|
||||
ASTNode::Assignment { target, .. } => matches!(
|
||||
target.as_ref(),
|
||||
ASTNode::Variable { name, .. } if name == "ch"
|
||||
),
|
||||
_ => false,
|
||||
});
|
||||
if crate::config::env::joinir_dev::strict_enabled() && has_ch_reassign {
|
||||
return Err(format!(
|
||||
"[cf_loop/pattern2] {}",
|
||||
crate::mir::join_ir::lowering::error_tags::freeze(
|
||||
"[phase94/body_local_derived/contract/unhandled_reassign] Body-local reassignment to 'ch' detected but not supported by Phase 94 recipe"
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use crate::mir::join_ir::lowering::loop_update_analyzer::LoopUpdateAnalyzer;
|
||||
let carrier_updates =
|
||||
LoopUpdateAnalyzer::analyze_carrier_updates(analysis_body, &inputs.carrier_info.carriers);
|
||||
|
||||
log.log(
|
||||
"updates",
|
||||
format!("Phase 176-3: Analyzed {} carrier updates", carrier_updates.len()),
|
||||
);
|
||||
|
||||
let original_carrier_count = inputs.carrier_info.carriers.len();
|
||||
filter_carriers_for_updates(&mut inputs.carrier_info, &carrier_updates);
|
||||
|
||||
log.log(
|
||||
"updates",
|
||||
format!(
|
||||
"Phase 176-4: Filtered carriers: {} → {} (kept only carriers with updates/condition-only/loop-local-zero)",
|
||||
original_carrier_count,
|
||||
inputs.carrier_info.carriers.len()
|
||||
),
|
||||
);
|
||||
|
||||
// Ensure env has join-ids for carriers that are referenced only from body updates.
|
||||
for carrier in &inputs.carrier_info.carriers {
|
||||
if inputs.env.get(&carrier.name).is_none() {
|
||||
let join_value = carrier
|
||||
.join_id
|
||||
.unwrap_or_else(|| inputs.join_value_space.alloc_param());
|
||||
|
||||
inputs.env.insert(carrier.name.clone(), join_value);
|
||||
|
||||
if carrier.init != CarrierInit::LoopLocalZero {
|
||||
inputs.condition_bindings.push(ConditionBinding {
|
||||
name: carrier.name.clone(),
|
||||
host_value: carrier.host_id,
|
||||
join_value,
|
||||
});
|
||||
} else {
|
||||
log.log(
|
||||
"updates",
|
||||
format!(
|
||||
"Phase 247-EX: Skipping host binding for loop-local carrier '{}' (init=LoopLocalZero)",
|
||||
carrier.name
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let lowering_inputs = crate::mir::join_ir::lowering::loop_with_break_minimal::LoopWithBreakLoweringInputs {
|
||||
scope: inputs.scope,
|
||||
let effective_break_condition = promoted.effective_break_condition;
|
||||
let carrier_updates = promoted.carrier_updates;
|
||||
let emitted = EmitJoinIRStepBox::emit(
|
||||
builder,
|
||||
condition,
|
||||
break_condition: &effective_break_condition,
|
||||
env: &inputs.env,
|
||||
carrier_info: &inputs.carrier_info,
|
||||
carrier_updates: &carrier_updates,
|
||||
body_ast: analysis_body,
|
||||
body_local_env: Some(&mut inputs.body_local_env),
|
||||
allowed_body_locals_for_conditions: if inputs.allowed_body_locals_for_conditions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(inputs.allowed_body_locals_for_conditions.as_slice())
|
||||
},
|
||||
join_value_space: &mut inputs.join_value_space,
|
||||
analysis_body,
|
||||
&effective_break_condition,
|
||||
&carrier_updates,
|
||||
&mut promoted.inputs,
|
||||
debug,
|
||||
verbose,
|
||||
skeleton,
|
||||
condition_only_recipe: inputs.condition_only_recipe.as_ref(),
|
||||
body_local_derived_recipe: inputs.body_local_derived_recipe.as_ref(),
|
||||
};
|
||||
)?;
|
||||
|
||||
let (join_module, fragment_meta) = match lower_loop_with_break_minimal(lowering_inputs) {
|
||||
Ok((module, meta)) => (module, meta),
|
||||
Err(e) => {
|
||||
super::super::trace::trace().debug("pattern2", &format!("Pattern 2 lowerer failed: {}", e));
|
||||
return Err(format!("[cf_loop/pattern2] Lowering failed: {}", e));
|
||||
}
|
||||
};
|
||||
|
||||
let exit_meta = &fragment_meta.exit_meta;
|
||||
use crate::mir::builder::control_flow::joinir::merge::exit_line::ExitMetaCollector;
|
||||
let exit_bindings = ExitMetaCollector::collect(builder, exit_meta, Some(&inputs.carrier_info), debug);
|
||||
|
||||
// JoinIR main() params: [ValueId(0), ValueId(1), ...]
|
||||
let mut join_input_slots = vec![ValueId(0)];
|
||||
let mut host_input_values = vec![inputs.loop_var_id];
|
||||
for (idx, carrier) in inputs.carrier_info.carriers.iter().enumerate() {
|
||||
join_input_slots.push(ValueId((idx + 1) as u32));
|
||||
host_input_values.push(carrier.host_id);
|
||||
}
|
||||
|
||||
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
|
||||
let boundary = JoinInlineBoundaryBuilder::new()
|
||||
.with_inputs(join_input_slots, host_input_values)
|
||||
.with_condition_bindings(inputs.condition_bindings)
|
||||
.with_exit_bindings(exit_bindings.clone())
|
||||
.with_expr_result(fragment_meta.expr_result)
|
||||
.with_loop_var_name(Some(inputs.loop_var_name.clone()))
|
||||
.with_carrier_info(inputs.carrier_info.clone())
|
||||
.build();
|
||||
|
||||
use super::conversion_pipeline::JoinIRConversionPipeline;
|
||||
let _ = JoinIRConversionPipeline::execute(builder, join_module, Some(&boundary), "pattern2", debug)?;
|
||||
|
||||
let void_val = crate::mir::builder::emission::constant::emit_void(builder);
|
||||
super::super::trace::trace().debug("pattern2", &format!("Loop complete, returning Void {:?}", void_val));
|
||||
Ok(Some(void_val))
|
||||
MergeStepBox::merge(builder, emitted.join_module, emitted.boundary, debug)
|
||||
}
|
||||
}
|
||||
|
||||
/// Updated carriers filtering helper (shared by orchestrator + tests).
|
||||
fn filter_carriers_for_updates(carrier_info: &mut CarrierInfo, carrier_updates: &BTreeMap<String, UpdateExpr>) {
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole};
|
||||
carrier_info.carriers.retain(|carrier| {
|
||||
carrier_updates.contains_key(&carrier.name)
|
||||
|| carrier.role == CarrierRole::ConditionOnly
|
||||
|| carrier.init == CarrierInit::LoopLocalZero
|
||||
});
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) fn promote_and_prepare_carriers(
|
||||
builder: &mut MirBuilder,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
inputs: &mut Pattern2Inputs,
|
||||
debug: bool,
|
||||
verbose: bool,
|
||||
) -> Result<(), String> {
|
||||
use crate::mir::join_ir::lowering::digitpos_condition_normalizer::DigitPosConditionNormalizer;
|
||||
use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox;
|
||||
|
||||
let cond_scope = LoopConditionScopeBox::analyze(
|
||||
&inputs.loop_var_name,
|
||||
&vec![condition, &inputs.break_condition_node],
|
||||
Some(&inputs.scope),
|
||||
);
|
||||
|
||||
let log = Pattern2DebugLog::new(verbose);
|
||||
let mut promoted_pairs: Vec<(String, String)> = Vec::new();
|
||||
let cond_body_local_vars: Vec<String> = cond_scope
|
||||
.vars
|
||||
.iter()
|
||||
.filter(|v| matches!(v.scope, crate::mir::loop_pattern_detection::loop_condition_scope::CondVarScope::LoopBodyLocal))
|
||||
.map(|v| v.name.clone())
|
||||
.collect();
|
||||
|
||||
if cond_scope.has_loop_body_local() {
|
||||
if !inputs.is_loop_true_read_digits {
|
||||
match classify_for_pattern2(
|
||||
builder,
|
||||
&inputs.loop_var_name,
|
||||
&inputs.scope,
|
||||
&inputs.break_condition_node,
|
||||
&cond_scope,
|
||||
body,
|
||||
) {
|
||||
PolicyDecision::Use(BodyLocalRoute::Promotion {
|
||||
promoted_carrier,
|
||||
promoted_var,
|
||||
carrier_name,
|
||||
}) => {
|
||||
let is_trim_promotion = promoted_carrier.trim_helper().is_some();
|
||||
if !is_trim_promotion {
|
||||
promoted_pairs.push((promoted_var.clone(), carrier_name.clone()));
|
||||
}
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
{
|
||||
use crate::mir::join_ir::lowering::carrier_binding_assigner::CarrierBindingAssigner;
|
||||
let mut promoted_carrier = promoted_carrier;
|
||||
CarrierBindingAssigner::assign_promoted_binding(
|
||||
builder,
|
||||
&mut promoted_carrier,
|
||||
&promoted_var,
|
||||
&carrier_name,
|
||||
)
|
||||
.map_err(|e| format!("[phase78/binding_assign] {:?}", e))?;
|
||||
inputs.carrier_info.merge_from(&promoted_carrier);
|
||||
}
|
||||
#[cfg(not(feature = "normalized_dev"))]
|
||||
{
|
||||
inputs.carrier_info.merge_from(&promoted_carrier);
|
||||
}
|
||||
|
||||
inputs
|
||||
.carrier_info
|
||||
.promoted_loopbodylocals
|
||||
.push(promoted_var.clone());
|
||||
|
||||
if !is_trim_promotion {
|
||||
inputs.break_condition_node = DigitPosConditionNormalizer::normalize(
|
||||
&inputs.break_condition_node,
|
||||
&promoted_var,
|
||||
&carrier_name,
|
||||
);
|
||||
}
|
||||
}
|
||||
PolicyDecision::Use(BodyLocalRoute::ReadOnlySlot(slot)) => {
|
||||
inputs.allowed_body_locals_for_conditions = vec![slot.name.clone()];
|
||||
inputs.read_only_body_local_slot = Some(slot);
|
||||
}
|
||||
PolicyDecision::Reject(reason) => {
|
||||
return Err(error_messages::format_error_pattern2_promotion_failed(
|
||||
&cond_body_local_vars,
|
||||
&reason,
|
||||
));
|
||||
}
|
||||
PolicyDecision::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate join_ids for carriers and register bindings.
|
||||
for carrier in &mut inputs.carrier_info.carriers {
|
||||
let carrier_join_id = inputs.join_value_space.alloc_param();
|
||||
carrier.join_id = Some(carrier_join_id);
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
if let Some(binding_id) = carrier.binding_id {
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
|
||||
match carrier.role {
|
||||
CarrierRole::ConditionOnly => inputs.env.register_condition_binding(binding_id, carrier_join_id),
|
||||
CarrierRole::LoopState => inputs.env.register_carrier_binding(binding_id, carrier_join_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (promoted_var, promoted_carrier_name) in promoted_pairs {
|
||||
let join_id = inputs
|
||||
.carrier_info
|
||||
.find_carrier(&promoted_carrier_name)
|
||||
.and_then(|c| c.join_id)
|
||||
.ok_or_else(|| format!("[phase229] promoted carrier '{}' has no join_id", promoted_carrier_name))?;
|
||||
inputs.env.insert(promoted_var, join_id);
|
||||
}
|
||||
|
||||
// ExprLowerer validation (best-effort; unchanged behavior)
|
||||
{
|
||||
use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer, ExprLoweringError};
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
|
||||
let scope_manager = Pattern2ScopeManager {
|
||||
condition_env: &inputs.env,
|
||||
loop_body_local_env: Some(&inputs.body_local_env),
|
||||
captured_env: Some(&inputs.captured_env),
|
||||
carrier_info: &inputs.carrier_info,
|
||||
};
|
||||
|
||||
match ExprLowerer::new(&scope_manager, ExprContext::Condition, builder)
|
||||
.with_debug(debug)
|
||||
.lower(&inputs.break_condition_node)
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(ExprLoweringError::UnsupportedNode(_)) => {}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_trim_and_normalize(
|
||||
builder: &mut MirBuilder,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
inputs: &mut Pattern2Inputs,
|
||||
verbose: bool,
|
||||
) -> Result<(ASTNode, Option<Vec<ASTNode>>), String> {
|
||||
let log = Pattern2DebugLog::new(verbose);
|
||||
let mut alloc_join_value = || inputs.join_value_space.alloc_param();
|
||||
|
||||
let disable_trim = inputs.is_loop_true_read_digits;
|
||||
|
||||
let effective_break_condition = if !disable_trim {
|
||||
if let Some(trim_result) = super::trim_loop_lowering::TrimLoopLowerer::try_lower_trim_like_loop(
|
||||
builder,
|
||||
&inputs.scope,
|
||||
condition,
|
||||
&inputs.break_condition_node,
|
||||
body,
|
||||
&inputs.loop_var_name,
|
||||
&mut inputs.carrier_info,
|
||||
&mut alloc_join_value,
|
||||
)? {
|
||||
log.log("trim", "TrimLoopLowerer processed Trim pattern successfully");
|
||||
inputs.carrier_info = trim_result.carrier_info;
|
||||
inputs.condition_only_recipe = trim_result.condition_only_recipe;
|
||||
trim_result.condition
|
||||
} else {
|
||||
inputs.break_condition_node.clone()
|
||||
}
|
||||
} else {
|
||||
inputs.break_condition_node.clone()
|
||||
};
|
||||
|
||||
use crate::mir::join_ir::lowering::complex_addend_normalizer::{ComplexAddendNormalizer, NormalizationResult};
|
||||
let mut normalized_body = Vec::new();
|
||||
let mut has_normalization = false;
|
||||
|
||||
for node in body {
|
||||
match ComplexAddendNormalizer::normalize_assign(node) {
|
||||
NormalizationResult::Normalized { temp_def, new_assign, .. } => {
|
||||
normalized_body.push(temp_def);
|
||||
normalized_body.push(new_assign);
|
||||
has_normalization = true;
|
||||
}
|
||||
NormalizationResult::Unchanged => normalized_body.push(node.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
effective_break_condition,
|
||||
if has_normalization { Some(normalized_body) } else { None },
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
//! ApplyPolicyStepBox (Phase 106)
|
||||
//!
|
||||
//! Responsibility: apply policy routing for Pattern2 break condition + allow-list.
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
|
||||
use super::super::pattern2_break_condition_policy_router::Pattern2BreakConditionPolicyRouterBox;
|
||||
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<Pattern2Inputs, String> {
|
||||
use crate::mir::join_ir::lowering::common::body_local_slot::ReadOnlyBodyLocalSlotBox;
|
||||
|
||||
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 {
|
||||
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: break_routing.allowed_body_locals_for_conditions,
|
||||
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,
|
||||
condition_only_recipe: None,
|
||||
body_local_derived_recipe: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
//! EmitJoinIRStepBox (Phase 106)
|
||||
//!
|
||||
//! Responsibility: call Pattern2 JoinIR lowerer and build inline boundary.
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
use super::super::pattern2_inputs_facts_box::{Pattern2DebugLog, Pattern2Inputs};
|
||||
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub(crate) struct EmitJoinIRStepOutput {
|
||||
pub join_module: crate::mir::join_ir::JoinModule,
|
||||
pub boundary: JoinInlineBoundary,
|
||||
}
|
||||
|
||||
pub(crate) struct EmitJoinIRStepBox;
|
||||
|
||||
impl EmitJoinIRStepBox {
|
||||
pub(crate) fn emit(
|
||||
builder: &mut MirBuilder,
|
||||
condition: &ASTNode,
|
||||
body_ast: &[ASTNode],
|
||||
effective_break_condition: &ASTNode,
|
||||
carrier_updates: &BTreeMap<String, crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr>,
|
||||
inputs: &mut Pattern2Inputs,
|
||||
debug: bool,
|
||||
verbose: bool,
|
||||
skeleton: Option<&crate::mir::loop_canonicalizer::LoopSkeleton>,
|
||||
) -> Result<EmitJoinIRStepOutput, String> {
|
||||
use crate::mir::join_ir::lowering::loop_with_break_minimal::lower_loop_with_break_minimal;
|
||||
|
||||
let log = Pattern2DebugLog::new(verbose);
|
||||
|
||||
let lowering_inputs = crate::mir::join_ir::lowering::loop_with_break_minimal::LoopWithBreakLoweringInputs {
|
||||
scope: inputs.scope.clone(),
|
||||
condition,
|
||||
break_condition: effective_break_condition,
|
||||
env: &inputs.env,
|
||||
carrier_info: &inputs.carrier_info,
|
||||
carrier_updates,
|
||||
body_ast,
|
||||
body_local_env: Some(&mut inputs.body_local_env),
|
||||
allowed_body_locals_for_conditions: if inputs.allowed_body_locals_for_conditions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(inputs.allowed_body_locals_for_conditions.as_slice())
|
||||
},
|
||||
join_value_space: &mut inputs.join_value_space,
|
||||
skeleton,
|
||||
condition_only_recipe: inputs.condition_only_recipe.as_ref(),
|
||||
body_local_derived_recipe: inputs.body_local_derived_recipe.as_ref(),
|
||||
};
|
||||
|
||||
let (join_module, fragment_meta) = match lower_loop_with_break_minimal(lowering_inputs) {
|
||||
Ok((module, meta)) => (module, meta),
|
||||
Err(e) => {
|
||||
super::super::super::trace::trace().debug("pattern2", &format!("Pattern 2 lowerer failed: {}", e));
|
||||
return Err(format!("[cf_loop/pattern2] Lowering failed: {}", e));
|
||||
}
|
||||
};
|
||||
|
||||
let exit_meta = &fragment_meta.exit_meta;
|
||||
use crate::mir::builder::control_flow::joinir::merge::exit_line::ExitMetaCollector;
|
||||
let exit_bindings = ExitMetaCollector::collect(builder, exit_meta, Some(&inputs.carrier_info), debug);
|
||||
|
||||
// JoinIR main() params: [ValueId(0), ValueId(1), ...]
|
||||
let mut join_input_slots = vec![ValueId(0)];
|
||||
let mut host_input_values = vec![inputs.loop_var_id];
|
||||
for (idx, carrier) in inputs.carrier_info.carriers.iter().enumerate() {
|
||||
join_input_slots.push(ValueId((idx + 1) as u32));
|
||||
host_input_values.push(carrier.host_id);
|
||||
}
|
||||
|
||||
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
|
||||
let boundary = JoinInlineBoundaryBuilder::new()
|
||||
.with_inputs(join_input_slots, host_input_values)
|
||||
.with_condition_bindings(std::mem::take(&mut inputs.condition_bindings))
|
||||
.with_exit_bindings(exit_bindings.clone())
|
||||
.with_expr_result(fragment_meta.expr_result)
|
||||
.with_loop_var_name(Some(inputs.loop_var_name.clone()))
|
||||
.with_carrier_info(inputs.carrier_info.clone())
|
||||
.build();
|
||||
|
||||
log.log("emit", "JoinIR module + boundary constructed");
|
||||
|
||||
Ok(EmitJoinIRStepOutput { join_module, boundary })
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
//! GatherFactsStepBox (Phase 106)
|
||||
//!
|
||||
//! Responsibility: gather Pattern2 analysis-only inputs.
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
|
||||
use super::super::pattern2_inputs_facts_box::{Pattern2Facts, Pattern2InputsFactsBox};
|
||||
|
||||
pub(crate) struct GatherFactsStepBox;
|
||||
|
||||
impl GatherFactsStepBox {
|
||||
pub(crate) fn gather(
|
||||
builder: &MirBuilder,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
fn_body: Option<&[ASTNode]>,
|
||||
ctx: &super::super::pattern_pipeline::PatternPipelineContext,
|
||||
verbose: bool,
|
||||
) -> Result<Pattern2Facts, String> {
|
||||
Pattern2InputsFactsBox::analyze(builder, condition, body, fn_body, ctx, verbose)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
//! MergeStepBox (Phase 106)
|
||||
//!
|
||||
//! Responsibility: run JoinIR conversion pipeline (JoinIR → MIR merge) and return void.
|
||||
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
|
||||
|
||||
pub(crate) struct MergeStepBox;
|
||||
|
||||
impl MergeStepBox {
|
||||
pub(crate) fn merge(
|
||||
builder: &mut MirBuilder,
|
||||
join_module: crate::mir::join_ir::JoinModule,
|
||||
boundary: JoinInlineBoundary,
|
||||
debug: bool,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
use crate::mir::builder::emission::constant::emit_void;
|
||||
use super::super::conversion_pipeline::JoinIRConversionPipeline;
|
||||
|
||||
let _ = JoinIRConversionPipeline::execute(builder, join_module, Some(&boundary), "pattern2", debug)?;
|
||||
|
||||
let void_val = emit_void(builder);
|
||||
super::super::super::trace::trace().debug("pattern2", &format!("Loop complete, returning Void {:?}", void_val));
|
||||
Ok(Some(void_val))
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
//! Phase 106: Pattern2 Step Boxes (SSOT)
|
||||
//!
|
||||
//! Goal: keep `pattern2_with_break.rs` and the orchestrator thin by splitting
|
||||
//! the Pattern2 pipeline into explicit steps with clear boundaries.
|
||||
//!
|
||||
//! Each step should have a single responsibility and fail-fast with a clear tag
|
||||
//! at the step boundary.
|
||||
|
||||
pub(crate) mod apply_policy_step_box;
|
||||
pub(crate) mod emit_joinir_step_box;
|
||||
pub(crate) mod gather_facts_step_box;
|
||||
pub(crate) mod merge_step_box;
|
||||
pub(crate) mod promote_step_box;
|
||||
|
||||
@ -0,0 +1,342 @@
|
||||
//! PromoteStepBox (Phase 106)
|
||||
//!
|
||||
//! Responsibility:
|
||||
//! - promotion / read-only slot routing for body-local vars in conditions
|
||||
//! - trim normalization (when enabled)
|
||||
//! - derived-slot routing (Phase 94)
|
||||
//! - carrier update analysis + filtering
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierInit};
|
||||
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
|
||||
use crate::mir::loop_pattern_detection::error_messages;
|
||||
|
||||
use super::super::body_local_policy::{classify_for_pattern2, BodyLocalRoute};
|
||||
use super::super::pattern2_inputs_facts_box::{Pattern2DebugLog, Pattern2Inputs};
|
||||
use super::super::policies::p5b_escape_derived_policy::{classify_p5b_escape_derived, P5bEscapeDerivedDecision};
|
||||
use super::super::policies::PolicyDecision;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub(crate) struct PromoteStepResult {
|
||||
pub inputs: Pattern2Inputs,
|
||||
pub effective_break_condition: ASTNode,
|
||||
pub normalized_body: Option<Vec<ASTNode>>,
|
||||
pub carrier_updates: BTreeMap<String, UpdateExpr>,
|
||||
}
|
||||
|
||||
impl PromoteStepResult {
|
||||
pub(crate) fn analysis_body<'a>(&'a self, original_body: &'a [ASTNode]) -> &'a [ASTNode] {
|
||||
self.normalized_body.as_deref().unwrap_or(original_body)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PromoteStepBox;
|
||||
|
||||
impl PromoteStepBox {
|
||||
pub(crate) fn run(
|
||||
builder: &mut MirBuilder,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
mut inputs: Pattern2Inputs,
|
||||
debug: bool,
|
||||
verbose: bool,
|
||||
) -> Result<PromoteStepResult, String> {
|
||||
Self::promote_and_prepare_carriers(builder, condition, body, &mut inputs, debug, verbose)?;
|
||||
|
||||
let (effective_break_condition, normalized_body) =
|
||||
Self::apply_trim_and_normalize(builder, condition, body, &mut inputs, verbose)?;
|
||||
|
||||
let analysis_body = normalized_body.as_deref().unwrap_or(body);
|
||||
|
||||
// Phase 94: Detect P5b escape-derived (`ch` reassignment + escape counter).
|
||||
match classify_p5b_escape_derived(analysis_body, &inputs.loop_var_name) {
|
||||
P5bEscapeDerivedDecision::Use(recipe) => {
|
||||
Pattern2DebugLog::new(verbose).log(
|
||||
"phase94",
|
||||
format!(
|
||||
"Phase 94: Enabled BodyLocalDerived for '{}' (counter='{}', pre_delta={}, post_delta={})",
|
||||
recipe.name, recipe.loop_counter_name, recipe.pre_delta, recipe.post_delta
|
||||
),
|
||||
);
|
||||
inputs.body_local_derived_recipe = Some(recipe);
|
||||
}
|
||||
P5bEscapeDerivedDecision::Reject(reason) => return Err(format!("[cf_loop/pattern2] {}", reason)),
|
||||
P5bEscapeDerivedDecision::None => {
|
||||
let has_ch_reassign = analysis_body.iter().any(|n| match n {
|
||||
ASTNode::Assignment { target, .. } => matches!(
|
||||
target.as_ref(),
|
||||
ASTNode::Variable { name, .. } if name == "ch"
|
||||
),
|
||||
_ => false,
|
||||
});
|
||||
if crate::config::env::joinir_dev::strict_enabled() && has_ch_reassign {
|
||||
return Err(format!(
|
||||
"[cf_loop/pattern2] {}",
|
||||
crate::mir::join_ir::lowering::error_tags::freeze(
|
||||
"[phase94/body_local_derived/contract/unhandled_reassign] Body-local reassignment to 'ch' detected but not supported by Phase 94 recipe"
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use crate::mir::join_ir::lowering::loop_update_analyzer::LoopUpdateAnalyzer;
|
||||
let carrier_updates =
|
||||
LoopUpdateAnalyzer::analyze_carrier_updates(analysis_body, &inputs.carrier_info.carriers);
|
||||
Pattern2DebugLog::new(verbose).log(
|
||||
"updates",
|
||||
format!("Phase 176-3: Analyzed {} carrier updates", carrier_updates.len()),
|
||||
);
|
||||
|
||||
let original_carrier_count = inputs.carrier_info.carriers.len();
|
||||
filter_carriers_for_updates(&mut inputs.carrier_info, &carrier_updates);
|
||||
Pattern2DebugLog::new(verbose).log(
|
||||
"updates",
|
||||
format!(
|
||||
"Phase 176-4: Filtered carriers: {} → {} (kept only carriers with updates/condition-only/loop-local-zero)",
|
||||
original_carrier_count,
|
||||
inputs.carrier_info.carriers.len()
|
||||
),
|
||||
);
|
||||
|
||||
// Ensure env has join-ids for carriers that are referenced only from body updates.
|
||||
for carrier in &inputs.carrier_info.carriers {
|
||||
if inputs.env.get(&carrier.name).is_none() {
|
||||
let join_value = carrier
|
||||
.join_id
|
||||
.unwrap_or_else(|| inputs.join_value_space.alloc_param());
|
||||
|
||||
inputs.env.insert(carrier.name.clone(), join_value);
|
||||
|
||||
if carrier.init != CarrierInit::LoopLocalZero {
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionBinding;
|
||||
inputs.condition_bindings.push(ConditionBinding {
|
||||
name: carrier.name.clone(),
|
||||
host_value: carrier.host_id,
|
||||
join_value,
|
||||
});
|
||||
} else {
|
||||
Pattern2DebugLog::new(verbose).log(
|
||||
"updates",
|
||||
format!(
|
||||
"Phase 247-EX: Skipping host binding for loop-local carrier '{}' (init=LoopLocalZero)",
|
||||
carrier.name
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PromoteStepResult {
|
||||
inputs,
|
||||
effective_break_condition,
|
||||
normalized_body,
|
||||
carrier_updates,
|
||||
})
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) fn promote_and_prepare_carriers(
|
||||
builder: &mut MirBuilder,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
inputs: &mut Pattern2Inputs,
|
||||
debug: bool,
|
||||
verbose: bool,
|
||||
) -> Result<(), String> {
|
||||
use crate::mir::join_ir::lowering::digitpos_condition_normalizer::DigitPosConditionNormalizer;
|
||||
use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox;
|
||||
|
||||
let cond_scope = LoopConditionScopeBox::analyze(
|
||||
&inputs.loop_var_name,
|
||||
&vec![condition, &inputs.break_condition_node],
|
||||
Some(&inputs.scope),
|
||||
);
|
||||
|
||||
let mut promoted_pairs: Vec<(String, String)> = Vec::new();
|
||||
let cond_body_local_vars: Vec<String> = cond_scope
|
||||
.vars
|
||||
.iter()
|
||||
.filter(|v| matches!(v.scope, crate::mir::loop_pattern_detection::loop_condition_scope::CondVarScope::LoopBodyLocal))
|
||||
.map(|v| v.name.clone())
|
||||
.collect();
|
||||
|
||||
if cond_scope.has_loop_body_local() {
|
||||
if !inputs.is_loop_true_read_digits {
|
||||
match classify_for_pattern2(
|
||||
builder,
|
||||
&inputs.loop_var_name,
|
||||
&inputs.scope,
|
||||
&inputs.break_condition_node,
|
||||
&cond_scope,
|
||||
body,
|
||||
) {
|
||||
PolicyDecision::Use(BodyLocalRoute::Promotion {
|
||||
promoted_carrier,
|
||||
promoted_var,
|
||||
carrier_name,
|
||||
}) => {
|
||||
let is_trim_promotion = promoted_carrier.trim_helper().is_some();
|
||||
if !is_trim_promotion {
|
||||
promoted_pairs.push((promoted_var.clone(), carrier_name.clone()));
|
||||
}
|
||||
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
{
|
||||
use crate::mir::join_ir::lowering::carrier_binding_assigner::CarrierBindingAssigner;
|
||||
let mut promoted_carrier = promoted_carrier;
|
||||
CarrierBindingAssigner::assign_promoted_binding(
|
||||
builder,
|
||||
&mut promoted_carrier,
|
||||
&promoted_var,
|
||||
&carrier_name,
|
||||
)
|
||||
.map_err(|e| format!("[phase78/binding_assign] {:?}", e))?;
|
||||
inputs.carrier_info.merge_from(&promoted_carrier);
|
||||
}
|
||||
#[cfg(not(feature = "normalized_dev"))]
|
||||
{
|
||||
inputs.carrier_info.merge_from(&promoted_carrier);
|
||||
}
|
||||
|
||||
inputs
|
||||
.carrier_info
|
||||
.promoted_loopbodylocals
|
||||
.push(promoted_var.clone());
|
||||
|
||||
if !is_trim_promotion {
|
||||
inputs.break_condition_node = DigitPosConditionNormalizer::normalize(
|
||||
&inputs.break_condition_node,
|
||||
&promoted_var,
|
||||
&carrier_name,
|
||||
);
|
||||
}
|
||||
}
|
||||
PolicyDecision::Use(BodyLocalRoute::ReadOnlySlot(slot)) => {
|
||||
inputs.allowed_body_locals_for_conditions = vec![slot.name.clone()];
|
||||
inputs.read_only_body_local_slot = Some(slot);
|
||||
}
|
||||
PolicyDecision::Reject(reason) => {
|
||||
return Err(error_messages::format_error_pattern2_promotion_failed(
|
||||
&cond_body_local_vars,
|
||||
&reason,
|
||||
));
|
||||
}
|
||||
PolicyDecision::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate join_ids for carriers and register bindings.
|
||||
for carrier in &mut inputs.carrier_info.carriers {
|
||||
let carrier_join_id = inputs.join_value_space.alloc_param();
|
||||
carrier.join_id = Some(carrier_join_id);
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
if let Some(binding_id) = carrier.binding_id {
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierRole;
|
||||
match carrier.role {
|
||||
CarrierRole::ConditionOnly => inputs.env.register_condition_binding(binding_id, carrier_join_id),
|
||||
CarrierRole::LoopState => inputs.env.register_carrier_binding(binding_id, carrier_join_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (promoted_var, promoted_carrier_name) in promoted_pairs {
|
||||
let join_id = inputs
|
||||
.carrier_info
|
||||
.find_carrier(&promoted_carrier_name)
|
||||
.and_then(|c| c.join_id)
|
||||
.ok_or_else(|| format!("[phase229] promoted carrier '{}' has no join_id", promoted_carrier_name))?;
|
||||
inputs.env.insert(promoted_var, join_id);
|
||||
}
|
||||
|
||||
// ExprLowerer validation (best-effort; unchanged behavior)
|
||||
{
|
||||
use crate::mir::join_ir::lowering::expr_lowerer::{ExprContext, ExprLowerer, ExprLoweringError};
|
||||
use crate::mir::join_ir::lowering::scope_manager::Pattern2ScopeManager;
|
||||
|
||||
let scope_manager = Pattern2ScopeManager {
|
||||
condition_env: &inputs.env,
|
||||
loop_body_local_env: Some(&inputs.body_local_env),
|
||||
captured_env: Some(&inputs.captured_env),
|
||||
carrier_info: &inputs.carrier_info,
|
||||
};
|
||||
|
||||
match ExprLowerer::new(&scope_manager, ExprContext::Condition, builder)
|
||||
.with_debug(debug)
|
||||
.lower(&inputs.break_condition_node)
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(ExprLoweringError::UnsupportedNode(_)) => {}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_trim_and_normalize(
|
||||
builder: &mut MirBuilder,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
inputs: &mut Pattern2Inputs,
|
||||
verbose: bool,
|
||||
) -> Result<(ASTNode, Option<Vec<ASTNode>>), String> {
|
||||
let log = Pattern2DebugLog::new(verbose);
|
||||
let mut alloc_join_value = || inputs.join_value_space.alloc_param();
|
||||
|
||||
let disable_trim = inputs.is_loop_true_read_digits;
|
||||
|
||||
let effective_break_condition = if !disable_trim {
|
||||
if let Some(trim_result) = super::super::trim_loop_lowering::TrimLoopLowerer::try_lower_trim_like_loop(
|
||||
builder,
|
||||
&inputs.scope,
|
||||
condition,
|
||||
&inputs.break_condition_node,
|
||||
body,
|
||||
&inputs.loop_var_name,
|
||||
&mut inputs.carrier_info,
|
||||
&mut alloc_join_value,
|
||||
)? {
|
||||
log.log("trim", "TrimLoopLowerer processed Trim pattern successfully");
|
||||
inputs.carrier_info = trim_result.carrier_info;
|
||||
inputs.condition_only_recipe = trim_result.condition_only_recipe;
|
||||
trim_result.condition
|
||||
} else {
|
||||
inputs.break_condition_node.clone()
|
||||
}
|
||||
} else {
|
||||
inputs.break_condition_node.clone()
|
||||
};
|
||||
|
||||
use crate::mir::join_ir::lowering::complex_addend_normalizer::{ComplexAddendNormalizer, NormalizationResult};
|
||||
let mut normalized_body = Vec::new();
|
||||
let mut has_normalization = false;
|
||||
|
||||
for node in body {
|
||||
match ComplexAddendNormalizer::normalize_assign(node) {
|
||||
NormalizationResult::Normalized { temp_def, new_assign, .. } => {
|
||||
normalized_body.push(temp_def);
|
||||
normalized_body.push(new_assign);
|
||||
has_normalization = true;
|
||||
}
|
||||
NormalizationResult::Unchanged => normalized_body.push(node.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
effective_break_condition,
|
||||
if has_normalization { Some(normalized_body) } else { None },
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_carriers_for_updates(carrier_info: &mut CarrierInfo, carrier_updates: &BTreeMap<String, UpdateExpr>) {
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInit, CarrierRole};
|
||||
carrier_info.carriers.retain(|carrier| {
|
||||
carrier_updates.contains_key(&carrier.name)
|
||||
|| carrier.role == CarrierRole::ConditionOnly
|
||||
|| carrier.init == CarrierInit::LoopLocalZero
|
||||
});
|
||||
}
|
||||
@ -233,7 +233,8 @@ mod tests {
|
||||
use crate::ast::Span;
|
||||
use crate::mir::ValueId;
|
||||
use super::super::pattern2_inputs_facts_box::Pattern2InputsFactsBox;
|
||||
use super::super::pattern2_lowering_orchestrator::promote_and_prepare_carriers;
|
||||
use super::super::pattern2_steps::apply_policy_step_box::ApplyPolicyStepBox;
|
||||
use super::super::pattern2_steps::promote_step_box::PromoteStepBox;
|
||||
|
||||
let mut builder = MirBuilder::new();
|
||||
builder
|
||||
@ -311,10 +312,12 @@ mod tests {
|
||||
|
||||
let ctx = build_pattern_context(&mut builder, &condition, &body, PatternVariant::Pattern2)
|
||||
.expect("build_pattern_context");
|
||||
let mut inputs =
|
||||
let facts =
|
||||
Pattern2InputsFactsBox::analyze(&builder, &condition, &body, None, &ctx, false)
|
||||
.expect("Pattern2InputsFactsBox::analyze");
|
||||
promote_and_prepare_carriers(&mut builder, &condition, &body, &mut inputs, false, false)
|
||||
let mut inputs = ApplyPolicyStepBox::apply(&condition, &body, facts)
|
||||
.expect("ApplyPolicyStepBox::apply");
|
||||
PromoteStepBox::promote_and_prepare_carriers(&mut builder, &condition, &body, &mut inputs, false, false)
|
||||
.expect("promote_and_prepare_carriers");
|
||||
|
||||
assert!(
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
use super::PolicyDecision;
|
||||
use crate::ast::{ASTNode, BinaryOperator, Span, UnaryOperator};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct LoopTrueReadDigitsPolicyResult {
|
||||
pub break_condition_node: ASTNode,
|
||||
pub allowed_ch_var: String,
|
||||
@ -59,3 +60,137 @@ pub(crate) fn classify_loop_true_read_digits(
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{BinaryOperator, LiteralValue, Span, UnaryOperator};
|
||||
|
||||
fn span() -> Span {
|
||||
Span::unknown()
|
||||
}
|
||||
|
||||
fn bool_lit(v: bool) -> ASTNode {
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Bool(v),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn var(name: &str) -> ASTNode {
|
||||
ASTNode::Variable {
|
||||
name: name.to_string(),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn str_lit(s: &str) -> ASTNode {
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::String(s.to_string()),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn int_lit(n: i64) -> ASTNode {
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Integer(n),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn eq(left: ASTNode, right: ASTNode) -> ASTNode {
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Equal,
|
||||
left: Box::new(left),
|
||||
right: Box::new(right),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add(left: ASTNode, right: ASTNode) -> ASTNode {
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(left),
|
||||
right: Box::new(right),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn or(left: ASTNode, right: ASTNode) -> ASTNode {
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Or,
|
||||
left: Box::new(left),
|
||||
right: Box::new(right),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn not(node: ASTNode) -> ASTNode {
|
||||
ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand: Box::new(node),
|
||||
span: span(),
|
||||
}
|
||||
}
|
||||
|
||||
fn digit_chain(var_name: &str, digits: &[&str]) -> ASTNode {
|
||||
let mut it = digits.iter();
|
||||
let first = it
|
||||
.next()
|
||||
.expect("digits must be non-empty");
|
||||
let mut acc = eq(var(var_name), str_lit(first));
|
||||
for d in it {
|
||||
acc = or(acc, eq(var(var_name), str_lit(d)));
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_digits_loop_true_policy_returns_break_when_true_and_allowlist() {
|
||||
// loop(true) {
|
||||
// if ch == "" { break }
|
||||
// if is_digit(ch) { out = out + ch; i = i + 1 } else { break }
|
||||
// }
|
||||
let condition = bool_lit(true);
|
||||
|
||||
let eos_cond = eq(var("ch"), str_lit(""));
|
||||
let digit_cond = digit_chain(
|
||||
"ch",
|
||||
&["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
|
||||
);
|
||||
|
||||
let body = vec![
|
||||
ASTNode::If {
|
||||
condition: Box::new(eos_cond.clone()),
|
||||
then_body: vec![ASTNode::Break { span: span() }],
|
||||
else_body: None,
|
||||
span: span(),
|
||||
},
|
||||
ASTNode::If {
|
||||
condition: Box::new(digit_cond.clone()),
|
||||
then_body: vec![
|
||||
ASTNode::Assignment {
|
||||
target: Box::new(var("out")),
|
||||
value: Box::new(add(var("out"), var("ch"))),
|
||||
span: span(),
|
||||
},
|
||||
ASTNode::Assignment {
|
||||
target: Box::new(var("i")),
|
||||
value: Box::new(add(var("i"), int_lit(1))),
|
||||
span: span(),
|
||||
},
|
||||
],
|
||||
else_body: Some(vec![ASTNode::Break { span: span() }]),
|
||||
span: span(),
|
||||
},
|
||||
];
|
||||
|
||||
let decision = classify_loop_true_read_digits(&condition, &body);
|
||||
let result = match decision {
|
||||
PolicyDecision::Use(v) => v,
|
||||
other => panic!("expected PolicyDecision::Use, got {:?}", other),
|
||||
};
|
||||
|
||||
assert_eq!(result.allowed_ch_var, "ch");
|
||||
assert_eq!(result.break_condition_node, or(eos_cond, not(digit_cond)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user