Extend Phase 132's loop(true) + post-loop to accept multiple assignments:
Goal: `x=0; loop(true){ x=1; break }; x=x+2; x=x+3; return x` → exit code 6
Implementation:
- Extended loop_true_break_once.rs pattern detection (len() == 2 → len() >= 2)
- Added iterative assignment lowering (for loop over post_nodes)
- Reused Phase 130's lower_assign_stmt for each assignment
- Maintained ExitMeta DirectValue mode (PHI-free)
Changes:
- apps/tests/phase133_loop_true_break_once_post_multi_add_min.hako (new fixture)
- tools/smokes/v2/profiles/integration/apps/phase133_*_multi_add_*.sh (new smokes)
- src/mir/control_tree/normalized_shadow/loop_true_break_once.rs (+30 lines)
- docs/development/current/main/phases/phase-133/README.md (new documentation)
- docs/development/current/main/10-Now.md (Phase 133 entry added)
Scope (Phase 130 baseline):
- ✅ x = <int literal>
- ✅ x = y (variable copy)
- ✅ x = x + <int literal> (increment)
- ❌ Function calls / general expressions (future phases)
Design principles:
- Minimal change: ~30 lines added
- SSOT preservation: env_post_k remains single source of truth
- Reuse: Leveraged existing lower_assign_stmt
- Fail-Fast: Contract violations trigger freeze_with_hint
Test results:
- cargo test --lib: 1176 PASS
- Phase 133 VM: PASS (exit code 6)
- Phase 133 LLVM EXE: PASS (exit code 6)
- Phase 132 regression: PASS (exit code 3)
- Phase 131 regression: PASS (exit code 1)
- Phase 97 regression: PASS
Architecture maintained:
- 5-function structure unchanged (main/loop_step/loop_body/k_exit/post_k)
- PHI-free DirectValue mode
- Zero changes to ExitMeta, merge logic, or JoinIR contracts
Related: Phase 133 loop(true) + multiple post-loop assignments
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Phase 133: loop(true) break-once Multiple Post-Loop Assignments
Status: ✅ Implemented & Verified Date: 2025-12-18 Scope: P0 (Minimal)
Overview
Phase 133-P0 extends Phase 132-P4's loop(true) break-once pattern to accept multiple post-loop assignments before the final return statement. This enables more complex post-loop computation while maintaining the PHI-free Normalized shadow architecture.
Pattern
x = 0 // pre-loop init
loop(true) { // condition is Bool literal true
x = 1 // body assignment
break // break at end
}
x = x + 2 // first post-loop assignment
x = x + 3 // second post-loop assignment (NEW in P133)
return x // return updated value (1 + 2 + 3 = 6)
Implementation Changes
1. Pattern Detection (loop_true_break_once.rs)
Extended from Phase 132:
- Phase 132:
post_nodes == [Assign, Return](exactly 1 assign) - Phase 133:
post_nodes == [Assign+, Return](1+ assigns)
// Phase 133-P0: Detect multi-assign + return pattern (generalize Phase 132-P4)
let has_post_computation = if post_nodes.is_empty() {
false
} else if post_nodes.len() >= 2 {
// Check if all nodes except the last are Assign statements
let all_assigns = post_nodes[..post_nodes.len() - 1]
.iter()
.all(|n| matches!(n, StepNode::Stmt { kind: StepStmtKind::Assign { .. }, .. }));
// Check if the last node is a Return statement
let ends_with_return = matches!(
post_nodes.last(),
Some(StepNode::Stmt { kind: StepStmtKind::Return { .. }, .. })
);
all_assigns && ends_with_return
} else {
false
};
2. Post-Loop Lowering (loop_true_break_once.rs)
Iterative Assignment Processing:
// Phase 133-P0: Lower multiple post-loop assignments
// Split post_nodes into assigns and return (last element is return)
let assign_nodes = &post_nodes[..post_nodes.len() - 1];
let return_node = post_nodes.last().unwrap();
// Lower all assignment statements
for node in assign_nodes {
let StepNode::Stmt { kind: StepStmtKind::Assign { target, value_ast }, .. } = node else {
return Ok(None);
};
if LegacyLowerer::lower_assign_stmt(
target,
value_ast,
&mut post_k_func.body,
&mut next_value_id,
&mut env_post_k,
)
.is_err()
{
return Ok(None);
}
}
3. SSOT Maintenance
ExitMeta Contract:
- Unchanged: ExitMeta still uses
env_post_k's final values as SSOT - Iterative Updates: Each assignment updates
env_post_kmap - Final State: Last assignment's result becomes the exit value
JoinIR Structure (Unchanged from Phase 132)
5-Function Module:
main(env)→ TailCall(loop_step, env)loop_step(env)→ TailCall(loop_body, env)loop_body(env)→ → TailCall(k_exit, env)k_exit(env)→ TailCall(post_k, env)post_k(env)→ * → Ret(env[x]) ← NEW: Multiple assigns
Contract:
- PHI-free: All state passing via env arguments + continuations
- DirectValue mode: ExitMeta uses post_k's final env values
Scope Limitations (Phase 130 Baseline)
Allowed Assignments:
- ✅
x = <int literal>(e.g.,x = 0) - ✅
x = y(variable copy) - ✅
x = x + <int literal>(increment)
Not Allowed:
- ❌
x = call(...)(function calls) - ❌
x = a + b(general binary ops) - ❌
if,loop,printin post-loop
Test Coverage
Fixture
apps/tests/phase133_loop_true_break_once_post_multi_add_min.hako- Expected exit code: 6 (1 + 2 + 3)
Smoke Tests
- VM:
tools/smokes/v2/profiles/integration/apps/phase133_loop_true_break_once_post_multi_add_vm.sh - LLVM EXE:
tools/smokes/v2/profiles/integration/apps/phase133_loop_true_break_once_post_multi_add_llvm_exe.sh
Regression Tests
- ✅ Phase 132 (exit code 3)
- ✅ Phase 131 (exit code 1)
- ✅ Phase 97 (numeric output 2, -1, 3)
Verification Results
# Unit tests
cargo test --lib
# Result: 1176 passed; 0 failed
# Phase 133 smokes
bash tools/smokes/v2/profiles/integration/apps/phase133_loop_true_break_once_post_multi_add_vm.sh
# Result: PASS (exit code 6)
bash tools/smokes/v2/profiles/integration/apps/phase133_loop_true_break_once_post_multi_add_llvm_exe.sh
# Result: PASS (exit code 6)
# Regression smokes
bash tools/smokes/v2/profiles/integration/apps/phase132_loop_true_break_once_post_add_llvm_exe.sh
# Result: PASS (exit code 3)
bash tools/smokes/v2/profiles/integration/apps/phase131_loop_true_break_once_llvm_exe.sh
# Result: PASS (exit code 1)
bash tools/smokes/v2/profiles/integration/apps/phase97_next_non_ws_llvm_exe.sh
# Result: PASS (numeric output)
Design Principles
1. Minimal Change
- Extends Phase 132's detection logic from
len() == 2tolen() >= 2 - Replaces single assignment lowering with iterative loop
- Zero changes to JoinIR structure, ExitMeta, or merge logic
2. SSOT Preservation
env_post_kremains the single source of truth for exit values- Each assignment updates
env_post_kmap in order - ExitMeta collection happens after all assignments
3. Reuse
- Uses
LegacyLowerer::lower_assign_stmtfor each assignment - Leverages Phase 130's proven assignment handling
- No new lowering code required
4. Fail-Fast
- Contract violations trigger
freeze_with_hintin strict mode - Missing env fields → explicit error with hint
- Invalid assignment → fallback to legacy (Ok(None))
Dev Toggle
Required: NYASH_JOINIR_DEV=1 and HAKO_JOINIR_STRICT=1
This is a dev-only Normalized shadow feature. With toggle OFF, the pattern falls back to legacy lowering with zero impact on production code.
Files Modified
-
src/mir/control_tree/normalized_shadow/loop_true_break_once.rs(3 edits)- Extended pattern detection
- Iterative assignment lowering
- Updated debug logging
-
apps/tests/phase133_loop_true_break_once_post_multi_add_min.hako(new) -
tools/smokes/v2/profiles/integration/apps/phase133_loop_true_break_once_post_multi_add_vm.sh(new) -
tools/smokes/v2/profiles/integration/apps/phase133_loop_true_break_once_post_multi_add_llvm_exe.sh(new)
Acceptance Criteria
- ✅ cargo test --lib PASS (1176 tests)
- ✅ Phase 133 VM smoke: PASS (exit code 6)
- ✅ Phase 133 LLVM EXE smoke: PASS (exit code 6)
- ✅ Phase 132 regression: PASS (exit code 3)
- ✅ Phase 131 regression: PASS (exit code 1)
- ✅ Phase 97 regression: PASS (numeric output)
- ✅ Dev toggle OFF: Zero impact on production
Summary
Phase 133-P0 successfully generalizes Phase 132-P4's single post-loop assignment to support multiple post-loop assignments, enabling more complex post-loop computation patterns. The implementation maintains all architectural invariants (PHI-free, DirectValue mode, SSOT) while adding only ~30 lines of code.
Key Achievement: Transformed fixed-length pattern detection (len() == 2) to flexible pattern detection (len() >= 2) with zero impact on existing contracts.