refactor(joinir): split Pattern2 facts from lowering orchestration
This commit is contained in:
@ -57,6 +57,8 @@ 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_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 condition_env_builder;
|
||||
pub(in crate::mir::builder) mod conversion_pipeline;
|
||||
pub(in crate::mir::builder) mod exit_binding;
|
||||
|
||||
@ -0,0 +1,397 @@
|
||||
//! Pattern2 input analysis (facts collection only)
|
||||
//!
|
||||
//! This box collects everything Pattern2 lowering needs *without* emitting JoinIR:
|
||||
//! - 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;
|
||||
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, 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;
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
use crate::mir::loop_pattern_detection::function_scope_capture::CapturedEnv;
|
||||
|
||||
pub(crate) struct Pattern2DebugLog {
|
||||
verbose: bool,
|
||||
debug: DebugOutputBox,
|
||||
}
|
||||
|
||||
impl Pattern2DebugLog {
|
||||
pub(crate) fn new(verbose: bool) -> Self {
|
||||
Self {
|
||||
verbose,
|
||||
debug: DebugOutputBox::new_with_enabled("joinir/pattern2", verbose),
|
||||
}
|
||||
}
|
||||
|
||||
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 Pattern2Inputs {
|
||||
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,
|
||||
/// Phase 92 P3: Allow-list of LoopBodyLocal variable names permitted in conditions.
|
||||
/// 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 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<
|
||||
crate::mir::join_ir::lowering::common::condition_only_emitter::ConditionOnlyRecipe,
|
||||
>,
|
||||
/// Phase 94: BodyLocalDerived recipe for P5b "ch" reassignment + escape counter.
|
||||
pub body_local_derived_recipe:
|
||||
Option<crate::mir::join_ir::lowering::common::body_local_derived_emitter::BodyLocalDerivedRecipe>,
|
||||
}
|
||||
|
||||
pub(crate) struct Pattern2InputsFactsBox;
|
||||
|
||||
impl Pattern2InputsFactsBox {
|
||||
pub(in crate::mir::builder) fn analyze(
|
||||
builder: &MirBuilder,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
fn_body: Option<&[ASTNode]>,
|
||||
ctx: &super::pattern_pipeline::PatternPipelineContext,
|
||||
verbose: bool,
|
||||
) -> Result<Pattern2Inputs, 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};
|
||||
|
||||
let loop_var_name = ctx.loop_var_name.clone();
|
||||
let loop_var_id = ctx.loop_var_id;
|
||||
let mut carrier_info =
|
||||
ctx.carrier_info.clone(); // Phase 100 P2-2: Make mutable for accumulator promotion
|
||||
let scope = ctx.loop_scope.clone();
|
||||
|
||||
log.log(
|
||||
"init",
|
||||
format!(
|
||||
"PatternPipelineContext: loop_var='{}', loop_var_id={:?}, carriers={}",
|
||||
loop_var_name,
|
||||
loop_var_id,
|
||||
carrier_info.carriers.len()
|
||||
),
|
||||
);
|
||||
|
||||
// Capture analysis
|
||||
log.log(
|
||||
"phase200c",
|
||||
format!(
|
||||
"fn_body is {}",
|
||||
if fn_body.is_some() { "SOME" } else { "NONE" }
|
||||
),
|
||||
);
|
||||
let captured_env = if let Some(fn_body_ref) = fn_body {
|
||||
log.log("phase200c", format!("fn_body has {} nodes", fn_body_ref.len()));
|
||||
analyze_captured_vars_v2(fn_body_ref, condition, body, &scope)
|
||||
} else {
|
||||
log.log("phase200c", "fn_body is None, using empty CapturedEnv");
|
||||
CapturedEnv::new()
|
||||
};
|
||||
if verbose {
|
||||
log.log(
|
||||
"capture",
|
||||
format!("Phase 200-C: Captured {} variables", captured_env.vars.len()),
|
||||
);
|
||||
for var in &captured_env.vars {
|
||||
log.log(
|
||||
"capture",
|
||||
format!(
|
||||
" '{}': host_id={:?}, immutable={}",
|
||||
var.name, var.host_id, var.is_immutable
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 100 P1-3: Pinned Local Analysis (Judgment Box)
|
||||
// Analyze loop body AST to identify pinned locals (read-only loop-outer locals)
|
||||
let mut captured_env = captured_env; // Make mutable for pinned insertions
|
||||
|
||||
// Collect candidate locals from variable_map (all variables defined before loop)
|
||||
let candidate_locals: std::collections::BTreeSet<String> =
|
||||
builder.variable_ctx.variable_map.keys().cloned().collect();
|
||||
|
||||
if !candidate_locals.is_empty() {
|
||||
use crate::mir::loop_pattern_detection::pinned_local_analyzer::analyze_pinned_locals;
|
||||
|
||||
match analyze_pinned_locals(body, &candidate_locals) {
|
||||
Ok(pinned_names) => {
|
||||
if verbose && !pinned_names.is_empty() {
|
||||
log.log(
|
||||
"phase100_p1",
|
||||
format!("Detected {} pinned locals", pinned_names.len()),
|
||||
);
|
||||
}
|
||||
|
||||
for pinned_name in pinned_names {
|
||||
if let Some(&host_id) = builder.variable_ctx.variable_map.get(&pinned_name) {
|
||||
if verbose {
|
||||
log.log(
|
||||
"phase100_p1",
|
||||
format!(
|
||||
"Wiring pinned local '{}' with host_id={:?}",
|
||||
pinned_name, host_id
|
||||
),
|
||||
);
|
||||
}
|
||||
captured_env.insert_pinned(pinned_name, host_id);
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Pinned local '{}' not found in variable_map (internal error)",
|
||||
pinned_name
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(format!("Pinned local analysis failed: {}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 100 P2-2: Mutable Accumulator Analysis
|
||||
use crate::mir::loop_pattern_detection::mutable_accumulator_analyzer::{
|
||||
AccumulatorKind, MutableAccumulatorAnalyzer, RhsExprKind,
|
||||
};
|
||||
|
||||
let mutable_spec = MutableAccumulatorAnalyzer::analyze(body)?;
|
||||
|
||||
if let Some(spec) = mutable_spec {
|
||||
if verbose {
|
||||
log.log(
|
||||
"phase100_p2",
|
||||
format!(
|
||||
"Detected mutable accumulator: '{}' = '{}' + '{}'",
|
||||
spec.target_name, spec.target_name, spec.rhs_var_or_lit
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let target_id = builder
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&spec.target_name)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"[joinir/mutable-acc] Target '{}' not found in variable_map",
|
||||
spec.target_name
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut refined_kind = spec.kind;
|
||||
if spec.rhs_expr_kind == RhsExprKind::Var && refined_kind == AccumulatorKind::Int {
|
||||
use crate::mir::MirType;
|
||||
if let Some(target_type) = builder.type_ctx.value_types.get(target_id) {
|
||||
match target_type {
|
||||
MirType::Box(box_name) if box_name == "StringBox" => {
|
||||
refined_kind = AccumulatorKind::String;
|
||||
if verbose {
|
||||
log.log(
|
||||
"phase100_p3",
|
||||
format!(
|
||||
"Refined accumulator kind: Int → String (target '{}' is StringBox)",
|
||||
spec.target_name
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
MirType::Integer => {
|
||||
if verbose {
|
||||
log.log(
|
||||
"phase100_p3",
|
||||
format!(
|
||||
"Confirmed accumulator kind: Int (target '{}' is Integer)",
|
||||
spec.target_name
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if verbose {
|
||||
log.log(
|
||||
"phase100_p3",
|
||||
format!(
|
||||
"Accumulator kind: Int (default, target '{}' type unknown: {:?})",
|
||||
spec.target_name, target_type
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if refined_kind == AccumulatorKind::String {
|
||||
match spec.rhs_expr_kind {
|
||||
RhsExprKind::Var => {
|
||||
if verbose {
|
||||
log.log(
|
||||
"phase100_p3",
|
||||
format!(
|
||||
"String accumulator '{}' = '{}' + '{}' (Variable RHS: OK)",
|
||||
spec.target_name, spec.target_name, spec.rhs_var_or_lit
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
RhsExprKind::Literal => {
|
||||
return Err(format!(
|
||||
"[joinir/mutable-acc] String accumulator '{}' with Literal RHS not supported in Phase 100 P3 (will be P3.1)",
|
||||
spec.target_name
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match spec.rhs_expr_kind {
|
||||
RhsExprKind::Literal => {}
|
||||
RhsExprKind::Var => {
|
||||
let rhs_name = &spec.rhs_var_or_lit;
|
||||
let in_captured = captured_env.vars.iter().any(|v| &v.name == rhs_name);
|
||||
let in_carrier = carrier_info.carriers.iter().any(|c| &c.name == rhs_name);
|
||||
|
||||
if in_carrier {
|
||||
return Err(format!(
|
||||
"[joinir/mutable-acc] RHS '{}' must be read-only (Condition/BodyLocal/Captured/Pinned), but found mutable Carrier",
|
||||
rhs_name
|
||||
));
|
||||
} else if !in_captured && !builder.variable_ctx.variable_map.contains_key(rhs_name) {
|
||||
if verbose {
|
||||
log.log(
|
||||
"phase100_p2",
|
||||
format!(
|
||||
"RHS '{}' not in captured/variable_map, assuming LoopBodyLocal (will validate later)",
|
||||
rhs_name
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if verbose {
|
||||
log.log(
|
||||
"phase100_p2",
|
||||
format!("Promoting '{}' to mutable LoopState carrier", spec.target_name),
|
||||
);
|
||||
}
|
||||
|
||||
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
|
||||
carrier_info
|
||||
.carriers
|
||||
.push(CarrierVar::new(spec.target_name.clone(), *target_id));
|
||||
} else if verbose {
|
||||
log.log("phase100_p2", "No mutable accumulator pattern detected");
|
||||
}
|
||||
|
||||
// Value space + condition env
|
||||
let mut join_value_space = JoinValueSpace::new();
|
||||
let (mut env, mut condition_bindings, _loop_var_join_id) =
|
||||
ConditionEnvBuilder::build_for_break_condition_v2(
|
||||
condition,
|
||||
&loop_var_name,
|
||||
&builder.variable_ctx.variable_map,
|
||||
loop_var_id,
|
||||
&mut join_value_space,
|
||||
)?;
|
||||
|
||||
// Phase 79-2: Register loop variable BindingId (dev-only)
|
||||
#[cfg(feature = "normalized_dev")]
|
||||
if let Some(loop_var_bid) = builder.binding_ctx.lookup(&loop_var_name) {
|
||||
env.register_loop_var_binding(loop_var_bid, _loop_var_join_id);
|
||||
log.log(
|
||||
"phase79",
|
||||
format!(
|
||||
"Registered loop var BindingId: '{}' BindingId({}) → ValueId({})",
|
||||
loop_var_name, loop_var_bid.0, _loop_var_join_id.0
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Add captured vars
|
||||
for var in &captured_env.vars {
|
||||
if let Some(&host_id) = builder.variable_ctx.variable_map.get(&var.name) {
|
||||
let join_id = join_value_space.alloc_param();
|
||||
env.insert(var.name.clone(), join_id);
|
||||
condition_bindings.push(ConditionBinding {
|
||||
name: var.name.clone(),
|
||||
host_value: host_id,
|
||||
join_value: join_id,
|
||||
});
|
||||
log.log(
|
||||
"capture",
|
||||
format!(
|
||||
"Phase 201: Added captured '{}': host={:?}, join={:?}",
|
||||
var.name, host_id, join_id
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let body_local_env = LoopBodyLocalEnv::new();
|
||||
|
||||
// Break condition extraction is policy-routed (SSOT).
|
||||
let break_routing = Pattern2BreakConditionPolicyRouterBox::route(condition, body)?;
|
||||
|
||||
let mut inputs = Pattern2Inputs {
|
||||
loop_var_name,
|
||||
loop_var_id,
|
||||
carrier_info,
|
||||
scope,
|
||||
captured_env,
|
||||
join_value_space,
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,403 @@
|
||||
//! 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.
|
||||
|
||||
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;
|
||||
|
||||
pub(crate) struct Pattern2LoweringOrchestrator;
|
||||
|
||||
impl Pattern2LoweringOrchestrator {
|
||||
pub(in crate::mir::builder) fn run(
|
||||
builder: &mut MirBuilder,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
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");
|
||||
|
||||
use super::pattern_pipeline::{build_pattern_context, PatternVariant};
|
||||
let ctx = build_pattern_context(builder, condition, body, PatternVariant::Pattern2)?;
|
||||
|
||||
super::super::trace::trace().varmap("pattern2_start", &builder.variable_ctx.variable_map);
|
||||
|
||||
let mut inputs = Pattern2InputsFactsBox::analyze(builder, condition, body, fn_body, &ctx, verbose)?;
|
||||
|
||||
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 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,
|
||||
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,
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 },
|
||||
))
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user