refactor(control_flow): Phase 134 P0 - Normalization entry point SSOT
Consolidate dual entry points into unified NormalizationPlan system: Problem: - Dual entry points: try_normalized_shadow() + suffix_router_box - ~220 lines of duplicated pattern detection logic - Maintenance burden: changes required in two places Solution: - New normalization module with Box-First architecture - NormalizationPlanBox: Single source of truth for pattern detection - NormalizationExecuteBox: Single source of truth for execution Implementation: - src/mir/builder/control_flow/normalization/ (new module) - README.md: Design contract (SSOT) - plan.rs: NormalizationPlan data structure - plan_box.rs: Pattern detection (7 unit tests) - execute_box.rs: Execution logic - mod.rs: Module integration Refactored files: - routing.rs::try_normalized_shadow(): 165 → 87 lines (-78 lines) - suffix_router_box::try_lower_loop_suffix(): 258 → 116 lines (-142 lines) Pattern support maintained: - Phase 131: loop(true) only (consumed: 1) - Phase 132: loop + single post (consumed: 3) - Phase 133: loop + multiple post (consumed: 2+N) Box-First principles: - Plan Box: Detection responsibility only - Execute Box: Execution responsibility only - README.md: Contract documentation (SSOT) - Clear separation enables independent testing Test results: - cargo test --lib: 1186 PASS (10 new tests added) - Phase 133 VM/LLVM EXE: PASS (exit code 6) - Phase 132 LLVM EXE: PASS (exit code 3) - Phase 131 LLVM EXE: PASS (exit code 1) Benefits: - Code duplication eliminated (~220 lines) - Single source of truth for normalization decisions - Improved maintainability and testability - Future-proof extensibility (easy to add new patterns) Default behavior unchanged: Dev-only guard maintained Related: Phase 134 normalization infrastructure improvement 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -1,9 +1,10 @@
|
||||
//! Phase 132 P0.5: Suffix router box for loop(true) + post statements
|
||||
//! Phase 134 P0: Suffix router box - unified with NormalizationPlanBox
|
||||
//!
|
||||
//! ## Responsibility
|
||||
//!
|
||||
//! - Detect block suffix starting with loop(true) { ... break } + post statements
|
||||
//! - Lower the entire suffix to Normalized JoinModule via StepTree
|
||||
//! - Delegate to NormalizationPlanBox for pattern detection (SSOT)
|
||||
//! - Delegate to NormalizationExecuteBox for lowering and merge
|
||||
//! - Return consumed count to skip processed statements in build_block()
|
||||
//!
|
||||
//! ## Contract
|
||||
@ -12,19 +13,15 @@
|
||||
//! - Returns Ok(None): Pattern not matched, use default behavior
|
||||
//! - Returns Err(_): Internal error
|
||||
//!
|
||||
//! ## Design
|
||||
//! ## Design (Phase 134 P0)
|
||||
//!
|
||||
//! - Uses existing StepTree infrastructure (no StepNode modification)
|
||||
//! - Block SSOT: StepTree already supports Block([Loop, Assign, Return])
|
||||
//! - Responsibility separation: suffix router = detection + conversion, build_block = wiring
|
||||
//! - Uses NormalizationPlanBox for detection (no duplication)
|
||||
//! - Uses NormalizationExecuteBox for execution (shared logic)
|
||||
//! - Only handles suffix-specific logic (return statement emission)
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::control_tree::normalized_shadow::available_inputs_collector::AvailableInputsCollectorBox;
|
||||
use crate::mir::control_tree::normalized_shadow::StepTreeNormalizedShadowLowererBox;
|
||||
use crate::mir::control_tree::StepTreeBuilderBox;
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierRole, ExitReconnectMode};
|
||||
use crate::mir::join_ir::lowering::inline_boundary::{JoinInlineBoundary, LoopExitBinding};
|
||||
use crate::mir::builder::control_flow::normalization::{NormalizationPlanBox, NormalizationExecuteBox, PlanKind};
|
||||
|
||||
/// Box-First: Suffix router for normalized shadow lowering
|
||||
pub struct NormalizedShadowSuffixRouterBox;
|
||||
@ -32,6 +29,8 @@ pub struct NormalizedShadowSuffixRouterBox;
|
||||
impl NormalizedShadowSuffixRouterBox {
|
||||
/// Try to lower a block suffix starting with loop(true) + post statements
|
||||
///
|
||||
/// Phase 134 P0: Unified with NormalizationPlanBox for pattern detection
|
||||
///
|
||||
/// Returns:
|
||||
/// - Ok(Some(consumed)): Successfully processed remaining[..consumed]
|
||||
/// - Ok(None): Pattern not matched, use default behavior
|
||||
@ -42,218 +41,78 @@ impl NormalizedShadowSuffixRouterBox {
|
||||
func_name: &str,
|
||||
debug: bool,
|
||||
) -> Result<Option<usize>, String> {
|
||||
// Phase 132 P0.5 CRITICAL: Only match suffixes with POST-loop statements!
|
||||
//
|
||||
// This function is called on EVERY remaining block suffix, including:
|
||||
// - Phase 131: [Loop, Return] ← Should go through normal routing (NOT a suffix)
|
||||
// - Phase 132: [Loop, Assign, Return] ← This is our target (suffix with post-computation)
|
||||
//
|
||||
// We MUST NOT match Phase 131 patterns, as they're already handled correctly
|
||||
// by try_normalized_shadow() in routing.rs.
|
||||
let trace = crate::mir::builder::control_flow::joinir::trace::trace();
|
||||
|
||||
// Quick pattern check: need at least loop + assign + return (3 statements minimum)
|
||||
if remaining.len() < 3 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Check if first statement is a loop
|
||||
let is_loop = matches!(&remaining[0], ASTNode::Loop { .. });
|
||||
if !is_loop {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Check if there's an assignment after the loop (Phase 132-specific)
|
||||
let has_post_assignment = matches!(&remaining[1], ASTNode::Assignment { .. });
|
||||
if !has_post_assignment {
|
||||
return Ok(None); // Not Phase 132, let normal routing handle it
|
||||
}
|
||||
|
||||
// Suffix requires an explicit return statement (the router consumes it)
|
||||
let return_stmt = match &remaining[2] {
|
||||
ASTNode::Return { .. } => remaining[2].clone(),
|
||||
_ => return Ok(None),
|
||||
// Phase 134 P0: Delegate pattern detection to NormalizationPlanBox (SSOT)
|
||||
let plan = match NormalizationPlanBox::plan_block_suffix(builder, remaining, func_name, debug)? {
|
||||
Some(plan) => plan,
|
||||
None => {
|
||||
if debug {
|
||||
trace.routing(
|
||||
"suffix_router",
|
||||
func_name,
|
||||
"NormalizationPlanBox returned None (not a normalized pattern)",
|
||||
);
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
// Phase 132 P0 pattern detection:
|
||||
// - remaining[0]: Loop(true) { ... break }
|
||||
// - remaining[1]: Assign (post-loop computation)
|
||||
// - remaining[2]: Return
|
||||
//
|
||||
// Let StepTree handle the pattern validation (condition = true, break at end, etc.)
|
||||
|
||||
let trace = crate::mir::builder::control_flow::joinir::trace::trace();
|
||||
trace.routing(
|
||||
"suffix_router",
|
||||
func_name,
|
||||
&format!(
|
||||
"Detected potential loop suffix: {} statements starting with Loop",
|
||||
remaining.len()
|
||||
),
|
||||
);
|
||||
|
||||
// Build StepTree from the reachable suffix (up to and including the return).
|
||||
//
|
||||
// Even if the AST block contains extra statements after return, they are unreachable
|
||||
// and must not affect the structural decision.
|
||||
let suffix = &remaining[..3];
|
||||
let step_tree = StepTreeBuilderBox::build_from_block(suffix);
|
||||
|
||||
// Collect available inputs from MirBuilder state
|
||||
let available_inputs = AvailableInputsCollectorBox::collect(builder, None);
|
||||
|
||||
trace.routing(
|
||||
"suffix_router/normalized",
|
||||
func_name,
|
||||
&format!(
|
||||
"Trying Normalized shadow lowering (available_inputs: {})",
|
||||
available_inputs.len()
|
||||
),
|
||||
);
|
||||
|
||||
// Try Normalized lowering (loop(true) break-once with post statements)
|
||||
match StepTreeNormalizedShadowLowererBox::try_lower_if_only(&step_tree, &available_inputs) {
|
||||
Ok(Some((join_module, join_meta))) => {
|
||||
trace.routing(
|
||||
"suffix_router/normalized",
|
||||
func_name,
|
||||
&format!(
|
||||
"Normalized lowering succeeded ({} functions, {} exit bindings)",
|
||||
join_module.functions.len(),
|
||||
join_meta.exit_meta.exit_values.len()
|
||||
),
|
||||
);
|
||||
|
||||
// Phase 132 P0.5: Merge the JoinModule into MIR
|
||||
Self::merge_normalized_joinir(
|
||||
builder,
|
||||
join_module,
|
||||
join_meta,
|
||||
func_name,
|
||||
debug,
|
||||
)?;
|
||||
|
||||
// Phase 132 P1: Emit host-level return for the consumed suffix.
|
||||
//
|
||||
// JoinIR merge converts fragment Returns to Jump(exit_block_id) to keep the
|
||||
// “single-exit block” merge invariant. For suffix routing, we must still
|
||||
// terminate the host function at this point.
|
||||
if let ASTNode::Return { value, .. } = return_stmt {
|
||||
let _ = builder.build_return_statement(value)?;
|
||||
// Only handle suffix patterns (loop + post statements)
|
||||
// Loop-only patterns should go through try_normalized_shadow() instead
|
||||
match &plan.kind {
|
||||
PlanKind::LoopOnly => {
|
||||
if debug {
|
||||
trace.routing(
|
||||
"suffix_router",
|
||||
func_name,
|
||||
"Loop-only pattern detected, not a suffix (routing.rs should handle this)",
|
||||
);
|
||||
}
|
||||
|
||||
// Return consumed count (loop + post-assign + return)
|
||||
Ok(Some(3))
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(None) => {
|
||||
// Out of scope (not a Normalized pattern)
|
||||
trace.routing(
|
||||
"suffix_router/normalized",
|
||||
func_name,
|
||||
"Normalized lowering: out of scope",
|
||||
);
|
||||
Ok(None)
|
||||
PlanKind::LoopWithPost { post_assign_count } => {
|
||||
if debug {
|
||||
trace.routing(
|
||||
"suffix_router",
|
||||
func_name,
|
||||
&format!(
|
||||
"Loop+post pattern detected: {} post assigns",
|
||||
post_assign_count
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 134 P0: Delegate execution to NormalizationExecuteBox (SSOT)
|
||||
match NormalizationExecuteBox::execute(builder, &plan, remaining, func_name, debug) {
|
||||
Ok(_value_id) => {
|
||||
// ExecuteBox returns a void constant, we don't need it for suffix routing
|
||||
// The consumed count is what build_block() needs
|
||||
if debug {
|
||||
trace.routing(
|
||||
"suffix_router",
|
||||
func_name,
|
||||
&format!("Normalization succeeded, consumed {} statements", plan.consumed),
|
||||
);
|
||||
}
|
||||
Ok(Some(plan.consumed))
|
||||
}
|
||||
Err(e) => {
|
||||
// In scope but failed
|
||||
let msg = format!(
|
||||
"Phase 132/suffix_router: Failed to lower loop suffix in '{}': {}",
|
||||
func_name, e
|
||||
);
|
||||
if crate::config::env::joinir_dev::strict_enabled() {
|
||||
use crate::mir::join_ir::lowering::error_tags;
|
||||
return Err(error_tags::freeze_with_hint(
|
||||
"phase132/suffix_router/internal",
|
||||
"phase134/suffix_router/execute",
|
||||
&e,
|
||||
"Loop suffix should be supported by Normalized but conversion failed. \
|
||||
Check that pattern matches loop(true) { ... break } + post statements.",
|
||||
"Loop suffix should be supported by Normalized but execution failed",
|
||||
));
|
||||
}
|
||||
trace.routing("suffix_router/normalized/error", func_name, &msg);
|
||||
Ok(None) // Fallback to default behavior in non-strict mode
|
||||
trace.routing("suffix_router/error", func_name, &e);
|
||||
Ok(None) // Non-strict: fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge Normalized JoinModule into MIR builder (Phase 132 P0.5)
|
||||
///
|
||||
/// This is the shared merge logic extracted from routing.rs
|
||||
fn merge_normalized_joinir(
|
||||
builder: &mut MirBuilder,
|
||||
join_module: crate::mir::join_ir::JoinModule,
|
||||
join_meta: crate::mir::join_ir::lowering::carrier_info::JoinFragmentMeta,
|
||||
func_name: &str,
|
||||
debug: bool,
|
||||
) -> Result<(), String> {
|
||||
use crate::mir::builder::control_flow::joinir::merge;
|
||||
use crate::mir::join_ir::frontend::JoinFuncMetaMap;
|
||||
use crate::mir::join_ir_vm_bridge::bridge_joinir_to_mir_with_meta;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
let trace = crate::mir::builder::control_flow::joinir::trace::trace();
|
||||
|
||||
// Build exit_bindings from meta
|
||||
let exit_bindings: Vec<LoopExitBinding> = join_meta
|
||||
.exit_meta
|
||||
.exit_values
|
||||
.iter()
|
||||
.map(|(carrier_name, join_exit_value)| {
|
||||
// Get host_slot from variable_map
|
||||
let host_slot = builder
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(carrier_name)
|
||||
.copied()
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"[Phase 132 P0.5] Carrier '{}' not in variable_map (available: {:?})",
|
||||
carrier_name,
|
||||
builder.variable_ctx.variable_map.keys().collect::<Vec<_>>()
|
||||
)
|
||||
});
|
||||
|
||||
LoopExitBinding {
|
||||
carrier_name: carrier_name.clone(),
|
||||
join_exit_value: *join_exit_value,
|
||||
host_slot,
|
||||
role: CarrierRole::LoopState,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Create boundary with DirectValue mode
|
||||
let mut boundary = JoinInlineBoundary::new_with_exit_bindings(
|
||||
vec![], // No join_inputs for Normalized
|
||||
vec![], // No host_inputs for Normalized
|
||||
exit_bindings,
|
||||
);
|
||||
boundary.exit_reconnect_mode = ExitReconnectMode::DirectValue; // Phase 132 P0.5: No PHI
|
||||
boundary.continuation_func_ids = join_meta.continuation_funcs.clone();
|
||||
|
||||
// Bridge JoinIR to MIR
|
||||
let empty_meta: JoinFuncMetaMap = BTreeMap::new();
|
||||
let mir_module = bridge_joinir_to_mir_with_meta(&join_module, &empty_meta)
|
||||
.map_err(|e| format!("[suffix_router/normalized] MIR conversion failed: {:?}", e))?;
|
||||
|
||||
// Merge with boundary - this populates MergeResult.remapped_exit_values
|
||||
// and calls ExitLineOrchestrator with DirectValue mode
|
||||
let _exit_phi_result = merge::merge_joinir_mir_blocks(
|
||||
builder,
|
||||
&mir_module,
|
||||
Some(&boundary),
|
||||
debug,
|
||||
)?;
|
||||
|
||||
trace.routing(
|
||||
"suffix_router/normalized",
|
||||
func_name,
|
||||
&format!(
|
||||
"Normalized merge + reconnection completed ({} exit bindings)",
|
||||
boundary.exit_bindings.len()
|
||||
),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user