## Summary
Observed PHI dst ValueId distribution to determine if verifier can enforce
reserved region (0-99). **Conclusion: Verifier strengthening NOT recommended.**
## Key Finding
PHI dst allocation does NOT architecturally respect reserved region:
- PHI dst comes from `builder.next_value_id()` (host MirBuilder)
- Reserved region (0-99) is a JoinValueSpace contract for JoinIR lowering
- These are separate allocation pools with no enforcement mechanism
- Current stability is accidental (ValueId allocation ordering)
## Evidence
Manual verification (`apps/tests/loop_min_while.hako`):
- PHI dst = %3 (ValueId(3)) ✅ in reserved region
- Works because PHI allocated early in function (0-20 typical)
- JoinValueSpace allocates high (100+, 1000+)
- Accidental separation, not enforced
## Implementation
Added observation infrastructure (debug-only):
- `src/mir/join_ir/verify_phi_reserved.rs` (266 lines)
- PHI dst observer with distribution analyzer
- Region classifier (Reserved/Param/Local)
- Human-readable report generator
- Instrumentation at PHI allocation points:
- loop_header_phi_builder.rs:94, 151
- Debug-only `observe_phi_dst()` calls
## Test Results
- Unit tests: ✅ 4/4 PASS (verify_phi_reserved module)
- Lib tests: ✅ 950/950 PASS, 56 ignored
- Normalized tests: ✅ 54/54 PASS
- Manual verification: ✅ PHI dst in 0-99 observed
## Decision: Document, Don't Enforce
**Rationale**:
1. No architectural mechanism to enforce PHI dst ∈ [0, 99]
2. Adding verifier creates false assumptions about allocation order
3. Current system stable through separate pools (950/950 tests)
4. Future architectural fix possible (Phase 73+) but not urgent
## Documentation
- PHASE_72_SUMMARY.md: Executive summary and implementation record
- phase72-phi-reserved-observation.md: Detailed findings and analysis
- CURRENT_TASK.md: Phase 72 completion entry
## Next Steps
- Phase 73: Update architecture overview with Phase 72 findings
- Optional: Explicit PHI reserved pool (future enhancement)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extend CapturedEnv to include function parameters used in loop conditions,
enabling ExprLowerer to resolve variables like `s` in `loop(p < s.length())`.
Phase 245C changes:
- function_scope_capture.rs: Add collect_names_in_loop_parts() helper
- function_scope_capture.rs: Extend analyze_captured_vars_v2() with param capture logic
- function_scope_capture.rs: Add 4 new comprehensive tests
Test fix:
- expr_lowerer/ast_support.rs: Accept all MethodCall nodes for syntax support
(validation happens during lowering in MethodCallLowerer)
Problem solved: "Variable not found: s" errors in loop conditions
Test results: 924/924 PASS (+13 from baseline 911)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove legacy hardcoded 'sum' carrier validation that was blocking
array_filter patterns with different accumulator names (e.g., 'out').
Before: Pattern3 required carrier named 'sum' to exist
After: Pattern3 uses carrier_info generically (any carrier name works)
Test results:
- phase49_joinir_array_filter_smoke: PASS ✅
- phase49_joinir_array_filter_fallback: PASS ✅
- phase49_joinir_array_filter_ab_comparison: PASS ✅
- Full suite: 909/909 PASS, 0 FAIL
Also: Archive old roadmap documentation (67k lines moved to docs/archive/)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Pilot implementation of unified expression lowering for Pattern2 break conditions:
New files:
- scope_manager.rs (280 lines) - ScopeManager trait + Pattern2ScopeManager
- expr_lowerer.rs (455 lines) - ExprLowerer with Condition context support
Features:
- Unified variable lookup across ConditionEnv/LoopBodyLocalEnv/CapturedEnv/CarrierInfo
- Pre-validation of condition AST before lowering
- Fail-safe design with fallback to legacy path
- 8 new unit tests (all pass)
Integration:
- Pattern2 break condition uses ExprLowerer for pre-validation
- Existing proven lowering path preserved
- Zero impact on existing functionality (890/897 tests pass)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add DigitPosConditionNormalizer to transform integer comparison to boolean:
- `digit_pos < 0` → `!is_digit_pos`
This eliminates the type error caused by comparing Bool carrier with Integer:
- Before: Bool(is_digit_pos) < Integer(0) → Type error
- After: !is_digit_pos → Boolean expression, no type mismatch
Implementation:
- New Box: digitpos_condition_normalizer.rs (173 lines)
- Pattern2 integration: normalize after promotion success
- 5 unit tests (all pass)
- E2E test passes (type error eliminated)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace static alias mapping with dynamic condition variable resolution using:
- promoted_loopbodylocals as source of truth
- Naming conventions: is_<var> or is_<var>_match
- Pattern-aware inference during lowering
Benefits:
- Simpler data structure (6 fields → 5)
- Single source of truth
- Self-documenting with explicit naming conventions
- Fewer maintenance points
Net: -25 lines (60 additions, 85 deletions)
Tests: 877/884 PASS (no regressions)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- ExitMetaCollector includes ConditionOnly carriers in exit_bindings (for latch)
- ExitLineReconnector skips ConditionOnly in variable_map updates
- Pattern 2/3/4 pass carrier_info to ExitMetaCollector
- Resolves "has no latch incoming set" error
Role separation:
- Header PHI: entry + latch for both LoopState and ConditionOnly
- Exit PHI: LoopState only (ConditionOnly excluded)
- variable_map: LoopState only (ConditionOnly skipped)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add CarrierInit enum (FromHost/BoolConst) for explicit initialization policy
- LoopHeaderPhiBuilder generates Const instruction for BoolConst carriers
- merge/mod.rs uses carrier_info to include ALL carriers in header PHI
- Pattern 2/4 pass carrier_info to boundary builder
- ConditionOnly carriers (is_digit_pos) now included in header PHI
Remaining: latch incoming for ConditionOnly carriers (Phase 228-8)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add CarrierRole enum to distinguish state carriers from condition-only carriers
- ConditionOnly carriers (is_digit_pos) skip exit PHI but keep header PHI
- Update all test struct literals with role field
- 877/884 tests PASS (7 pre-existing failures unrelated)
Remaining: ValueId(0) undefined - header PHI initialization for ConditionOnly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove hardcoded method whitelist from loop_body_local_init.rs
- Remove hardcoded box name matching
- Delegate to MethodCallLowerer for all MethodCall handling
- CoreMethodId metadata is now SSOT for init method validation
- substring now works in body-local init (-82 net lines)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ConditionAlias type to CarrierInfo (old_name → carrier_name)
- Record aliases in DigitPosPromoter and TrimPatternInfo
- Resolve aliases in Pattern2 ConditionEnv building
- digit_pos now correctly resolves to is_digit_pos carrier
Fixes "Variable 'digit_pos' not bound in ConditionEnv" error.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add promoted_loopbodylocals field to CarrierInfo
- Record promoted variables in Pattern2 promotion handler
- Filter promoted variables in lowerer LoopBodyLocal check
- digit_pos → is_digit_pos promotion now continues to lowering
Closes Phase 224 lowerer integration gap.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 222: If Condition Normalization - Part 3
Goal: Integrate normalization into if-sum pattern detection and lowering
Changes:
1. pattern_pipeline.rs (is_if_sum_pattern):
- Updated to use analyze_condition_pattern() + normalize_comparison()
- Added two-step validation:
(a) Pattern must be SimpleComparison
(b) Condition must be normalizable
- Replaced is_simple_comparison() with Phase 222 API
2. loop_with_if_phi_if_sum.rs (extract_loop_condition):
- Added Phase 222 normalization
- Normalize condition to canonical form (var on left)
- Support both literal and variable on right-hand side
- Added mir::CompareOp → join_ir::CompareOp conversion
- Handles:
* Phase 219: var CmpOp literal (e.g., i > 0)
* Phase 222: literal CmpOp var (e.g., 0 < i) → normalized
* Phase 222: var CmpOp var (e.g., i > j)
Status: Integration complete, ready for E2E testing
Next: Phase 222-4 - verify regression tests and add new test cases
Modified extract_loop_condition() to use ConditionEnv for variable lookup:
- Now returns ValueId instead of i64 for loop limit
- Uses lower_value_expression() for both literals and variables
- Integrated ConditionEnvBuilder in Pattern 3 if-sum path
This enables realistic loop patterns like `loop(i < len)` in if-sum.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Introduced ConditionPatternBox to detect if condition complexity:
- Simple comparisons (var CmpOp literal): use AST-based if-sum lowerer
- Complex conditions (BinaryOp, etc.): fallback to legacy P3 lowerer
This fixes loop_if_phi.hako which was broken by Phase 219's
is_if_sum_pattern() changes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add expr_result handling in merge_joinir_mir_blocks
- When expr_result matches a carrier, return carrier PHI dst
- Enables expr-position loops to properly return accumulator values
Note: Phase 219 regression (loop_if_phi.hako) to be fixed in next commit
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace sequential value_counter (0u32) with JoinValueSpace allocator
in Pattern 3 (If-PHI) lowering for unified ValueId management.
Changes:
- loop_with_if_phi_minimal.rs: Replace value_counter with join_value_space.alloc_local()
- pattern3_with_if_phi.rs: Create JoinValueSpace and pass to lowerer
- loop_patterns/with_if_phi.rs: Update legacy wrapper to use JoinValueSpace
Benefits:
- Consistent with Pattern 2 implementation (Phase 201)
- Prevents ValueId collision in Local region (1000+)
- Clean separation: Param region (100-999) vs Local region (1000+)
Test status:
- All unit tests pass (5/5)
- E2E tests pass: loop_if_phi.hako, loop_if_phi_continue.hako
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Migrated Pattern 1 (Simple While) to use JoinValueSpace for unified
ValueId allocation, following the same pattern as Pattern 2 (Phase 201).
Changes:
- simple_while_minimal.rs: Added join_value_space parameter, replaced
value_counter with join_value_space.alloc_local()
- pattern1_minimal.rs: Create JoinValueSpace before calling lowerer
- loop_view_builder.rs: Create JoinValueSpace in try_pattern1()
Pattern 1 uses Local region (1000+) only, since it doesn't need
ConditionEnv (no Param region allocation required).
Tested:
- cargo build --release --lib: Success (0 errors, 4 warnings)
- cargo test --release --lib pattern: 119 passed
- E2E test apps/tests/loop_min_while.hako: Outputs "0 1 2" correctly
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Key changes to prevent ValueId collision between frontend and lowerer:
1. loop_step params now use ConditionEnv ValueIds (Param region: 100+)
- i_param = env.get(loop_var_name) - ensures condition lowering works
- carrier_param_ids from CarrierInfo.join_id
2. main() params and intermediate values use alloc_local() (Local region: 1000+)
- No collision with frontend's param allocations
3. CarrierInfo.join_id is now properly set in frontend
- carrier.join_id = Some(carrier_join_id) during allocation
This fixes the "use of undefined value" error where frontend allocated
ValueId(100+) but lowerer used ValueId(0+), causing remapper mismatch.
Test results:
- All 821 library tests pass
- E2E: phase200d_capture_minimal.hako outputs 30 ✓
- Pattern 4: loop_continue_pattern4.hako outputs 25 ✓
- Multi-carrier: loop_continue_multi_carrier.hako outputs 100,10 ✓
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>