Generalize NormalizationPlan suffix detection to accept zero post-loop assignments: Goal: Improve entry point consistency by allowing `loop + assign* + return` (N >= 0) Implementation: - Modified plan_box.rs detection logic (only file changed) - Removed `post_assign_count >= 1` requirement - Unified Phase 131 (loop + return) and Phase 132-133 (loop + assign+ + return) paths Changes: - src/mir/builder/control_flow/normalization/plan_box.rs: - Removed assignment count constraint - Unified pattern detection: `loop + assign* + return` (N >= 0) - apps/tests/phase135_loop_true_break_once_post_empty_return_min.hako (new fixture) - tools/smokes/v2/profiles/integration/apps/phase135_*.sh (new smoke tests) Pattern support: - Phase 131/135: loop + return only (consumed: 2, post_assign_count: 0) ✅ - Phase 132: loop + 1 assign + return (consumed: 3, post_assign_count: 1) ✅ - Phase 133: loop + N assigns + return (consumed: 2+N, post_assign_count: N) ✅ Design principles maintained: - **Minimal change**: Only plan_box.rs modified (execute_box unchanged) - **SSOT**: Detection logic centralized in plan_box.rs - **Box-First**: Responsibility separation preserved (Plan/Execute) Test results: - Unit tests (plan_box): 9/9 PASS (2 new tests added) - Phase 135 VM/LLVM EXE: PASS (exit code 1) - Phase 131 regression: 2/2 PASS (path now unified) - Phase 133 regression: 2/2 PASS - cargo test --lib: PASS Benefits: - Unified entry point for all loop + post patterns - Easier maintenance (single detection logic) - Future extensibility (easy to add new patterns) - Clear separation of Phase 131 and Phase 132-135 paths Default behavior unchanged: Dev-only guard maintained Related: Phase 135 normalization pattern consistency improvement 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
7.5 KiB
7.5 KiB
Normalization Entry Point Consolidation (Phase 134 P0)
Date: 2025-12-18 Status: In Progress Scope: Unified entry point for Normalized shadow detection and execution
Purpose
Consolidate the two separate entry points for Normalized shadow processing into a single, well-defined system:
-
Before: Dual entry points with scattered responsibility
try_normalized_shadow()in routing.rs (loop-only)suffix_router_boxin patterns/policies/ (loop + post statements)- Decision logic ("what to lower") is duplicated and inconsistent
-
After: Single decision point using Box-First architecture
- NormalizationPlanBox: Detects pattern and plans consumption (SSOT for "what")
- NormalizationExecuteBox: Executes the plan (SSOT for "how")
- Both entry points use the same PlanBox for consistent decisions
Architecture
Entry Points
Two callers, one SSOT decision:
routing.rs::try_normalized_shadow(): Loop-only patterns (Phase 131)suffix_router_box::try_lower_loop_suffix(): Loop + post patterns (Phase 132-133)- Both call
NormalizationPlanBox::plan_block_suffix()for detection
Box Responsibilities
-
NormalizationPlanBox (
plan_box.rs)- Responsibility: Pattern detection and planning
- API:
plan_block_suffix(builder, remaining, func_name, debug) -> Result<Option<NormalizationPlan>> - Returns: Plan with consumed count and kind, or None if not applicable
-
NormalizationExecuteBox (
execute_box.rs)- Responsibility: Execute the plan (StepTree build, lowering, merge)
- API:
execute(builder, plan, remaining, func_name, debug) -> Result<()> - Uses: StepTreeBuilderBox, NormalizedShadowLowererBox, merge logic
Data Structures
NormalizationPlan (plan.rs):
pub struct NormalizationPlan {
pub consumed: usize, // Number of statements to consume from remaining
pub kind: PlanKind, // What to lower
pub requires_return: bool, // Whether pattern includes return (unreachable detection)
}
pub enum PlanKind {
LoopOnly, // Phase 131: loop(true) { ... break } alone
LoopWithPost { // Phase 132-133: loop + post assigns + return
post_assign_count: usize,
},
}
Design Principles
Box-First
- Plan Box: Single responsibility for detection (no execution)
- Execute Box: Single responsibility for execution (no detection)
- Clear separation enables independent testing and evolution
SSOT (Single Source of Truth)
- Entry: NormalizationPlanBox is the only place that decides normalization applicability
- Contract: Documented in this README (normative SSOT)
- No duplication: Suffix router and routing.rs both delegate to the same PlanBox
Fail-Fast
- Invalid patterns return
Err(not silent fallback) - STRICT mode (JOINIR_DEV_STRICT) treats contract violations as panics
- Clear error messages with hints for debugging
Legacy Preservation
- Existing behavior unchanged (dev-only guard)
- Non-normalized patterns return
Ok(None)→ legacy fallback - No breaking changes to existing smokes
Pattern Detection (Phase 131-135)
Phase 131: Loop-Only
- Pattern:
loop(true) { ... break }(single statement, no return) - Consumed: 1 statement
- Kind:
PlanKind::LoopOnly - Example:
loop(true) { x = 1; break }
Phase 132: Loop + Single Post
- Pattern:
loop(true) { ... break }; <assign>; return <expr> - Consumed: 3 statements (loop, assign, return)
- Kind:
PlanKind::LoopWithPost { post_assign_count: 1 } - Example:
loop(true) { x = 1; break }; x = x + 2; return x
Phase 133: Loop + Multiple Post
- Pattern:
loop(true) { ... break }; <assign>+; return <expr> - Consumed: 2 + N statements (loop, N assigns, return)
- Kind:
PlanKind::LoopWithPost { post_assign_count: N } - Example:
loop(true) { x = 1; break }; x = x + 2; x = x + 3; return x
Phase 135: Loop + Return (Zero Post Assigns) NEW
- Pattern:
loop(true) { ... break }; return <expr>(0 post-loop assignments) - Consumed: 2 statements (loop, return)
- Kind:
PlanKind::LoopWithPost { post_assign_count: 0 } - Example:
loop(true) { x = 1; break }; return x - Improvement: Unifies Phase 131 and Phase 132-133 patterns under
LoopWithPostenum - Compatibility: Phase 131 (loop-only, no return) remains as
PlanKind::LoopOnly
Contract
NormalizationPlanBox::plan_block_suffix()
Inputs:
builder: Current MirBuilder state (for variable_map access)remaining: Block suffix to analyze (AST statements)func_name: Function name (for tracing)debug: Enable debug logging
Returns:
Ok(Some(plan)): Pattern detected, plan specifies what to doOk(None): Not a normalized pattern, use legacy fallbackErr(msg): Internal error (should not happen in well-formed AST)
Invariants:
consumed <= remaining.len()(never consume more than available)- If
requires_returnis true,remaining[consumed-1]must be Return - Post-assign patterns require
consumed >= 2(loop + return minimum, Phase 135+)
NormalizationExecuteBox::execute()
Inputs:
builder: Current MirBuilder state (mutable, will be modified)plan: Normalization plan from PlanBoxremaining: Same AST slice used for planningfunc_name: Function name (for tracing)debug: Enable debug logging
Returns:
Ok(()): Successfully executed and mergedErr(msg): Lowering or merge failed
Side Effects:
- Modifies
builderstate (adds blocks, instructions, PHI) - May emit return statement (for suffix patterns)
- Updates variable_map with exit values (DirectValue mode)
Integration Points
routing.rs
try_normalized_shadow(): Call PlanBox, if LoopOnly → ExecuteBox, return ValueId- Legacy path: If PlanBox returns None, continue with existing fallback
suffix_router_box.rs
try_lower_loop_suffix(): Call PlanBox, if LoopWithPost → ExecuteBox, return consumed- Legacy path: If PlanBox returns None, return None (existing behavior)
build_block() (stmts.rs)
- Existing while loop unchanged
- After suffix_router returns consumed, advance idx by that amount
- No change to default behavior
Testing Strategy
Unit Tests (plan_box.rs)
- Phase 131 pattern detection (loop-only)
- Phase 132 pattern detection (loop + single post)
- Phase 133 pattern detection (loop + multiple post)
- Phase 135 pattern detection (loop + zero post) NEW
- Return boundary detection (consumed stops at return)
- Return boundary with trailing statements NEW
- Non-matching patterns (returns None)
Regression Smokes
- Phase 135:
phase135_loop_true_break_once_post_empty_return_{vm,llvm_exe}.shNEW - Phase 133:
phase133_loop_true_break_once_post_multi_add_{vm,llvm_exe}.sh - Phase 132:
phase132_loop_true_break_once_post_add_llvm_exe.sh - Phase 131:
phase131_loop_true_break_once_{vm,llvm_exe}.sh - Phase 97:
phase97_next_non_ws_llvm_exe.sh
Acceptance Criteria
- Entry SSOT: This README documents the normative contract
- By-name avoidance: Uses boundary contract SSOT (no variable name guessing)
- cargo test --lib: All unit tests PASS
- Phase 133 smokes: 2/2 PASS (VM + LLVM EXE)
- Phase 131/132 regression: PASS
- Phase 97 regression: PASS
- Default behavior unchanged (dev-only guard)
References
- Normalized shadow:
src/mir/control_tree/normalized_shadow/ - Boundary contract:
src/mir/join_ir/lowering/inline_boundary.rs - StepTree:
src/mir/control_tree/step_tree/ - Merge logic:
src/mir/builder/control_flow/joinir/merge/