feat(joinir): Phase 171-fix ConditionEnv/ConditionBinding architecture

Proper HOST↔JoinIR ValueId separation for condition variables:

- Add ConditionEnv struct (name → JoinIR-local ValueId mapping)
- Add ConditionBinding struct (HOST/JoinIR ValueId pairs)
- Modify condition_to_joinir to use ConditionEnv instead of builder.variable_map
- Update Pattern2 lowerer to build ConditionEnv and ConditionBindings
- Extend JoinInlineBoundary with condition_bindings field
- Update BoundaryInjector to inject Copy instructions for condition variables

This fixes the undefined ValueId errors where HOST ValueIds were being
used directly in JoinIR instructions. Programs now execute (RC: 0),
though loop variable exit values still need Phase 172 work.

Key invariants established:
1. JoinIR uses ONLY JoinIR-local ValueIds
2. HOST↔JoinIR bridging is ONLY through JoinInlineBoundary
3. condition_to_joinir NEVER accesses builder.variable_map

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-07 01:45:03 +09:00
parent b8a9d08894
commit e30116f53d
27 changed files with 5419 additions and 85 deletions

View File

@ -1,10 +1,62 @@
# Self Current Task — Now (main)
20250908:現状と直近タスク
## 20251206:現状サマリ
### JoinIR / Loop / If ライン
- LoopBuilder は Phase 186187 で完全削除済み。**JoinIR が唯一の loop lowering 経路**。
- LoopPattern 系は Pattern14 まで実装・本線化済み:
- Pattern1: Simple While
- Pattern2: Loop with Break
- Pattern3: Loop with IfElse PHIbreak/continue なし)
- Pattern4: Loop with Continuemulticarrier 対応)
- Exit/Carrier/Boundary ラインは次の箱で SSOT 化:
- `CarrierInfo` / `ExitMeta` / `ExitBindingBuilder`
- `JoinInlineBoundary` + `LoopExitBinding`
- If lowering は IfSelectLowerer/IfMergeLowerer を中心に整理済み。Select/PHI の扱いも Phase 189 系で橋渡し済み。
- 残課題JoinIR ライン):
- JoinIR→MIR merge の一般化(複雑な Select/PHI パターンの統合)
- JsonParserBox など実アプリ側での長期運用テスト
### JsonParser / Selfhost depth2 ライン
- `selfhost_build.sh --json` で Program JSON v0 emit は安定。
`.hako` 側から env 経由で JSON を読む最小ループ(`program_read_min.hako`)は動作確認済み。
- JsonParserBox / BundleResolver のループ 21 本のうち:
- 18 本は Pattern14 で JoinIR 対応済みPhase 162165
- `_trim` を含む一部の複合ループは、ValueId 境界や Box 登録など残課題あり。
- BoolExprLowerer / condition_to_joinir で OR/AND/NOT 付き条件式の lowering は実装完了Phase 168169
- 残課題JsonParser/selfhost depth2
- JoinIR→MIR ValueId boundary の完全一般化(条件用 ValueId を含める)
- JsonParserBox の using / Box 登録Rust VM 側での認識)
- Program JSON v0 を JsonParserBox 経由でフル解析する line の仕上げ
### Ring0 / Runtime / CoreServices ライン
- Ring0Context + Ring0Registry で OS API 抽象化レイヤ完成:
- MemApi / IoApi / TimeApi / LogApi / FsApi / ThreadApi
- RuntimeProfile(Default / NoFs) で条件付き必須を制御。
- CoreServicesring1coreとして次を実装済み
- StringService / IntegerService / BoolService
- ArrayService / MapService / ConsoleService
- PluginHost 統合 + UnifiedBoxRegistry からの自動初期化
- FileBox / FileHandleBox ライン:
- Ring0FsFileIo 経由で read / write / append / metadata 完全対応
- Default プロファイルでは必須、NoFs プロファイルでは disabled。
- Logging ライン:
- ConsoleServiceuserfacing
- Ring0.loginternal/dev
- println!test 専用)
の 3 層が `logging_policy.md` で整理済み。JoinIR/Loop trace も同ドキュメントに集約。
---
## 20250908旧スナップショット参考
- LLVM 側 P0 完了BitOps/Array/Echo/Map 緑。VInvoke(byname/byid vector) は戻り値マッピングの暫定課題を確認中Decisions 参照)。
- selfhosting-dev の作業を main に順次取り込み。VM/MIR 基盤は main で先に整える方針。
直近タスク(優先
直近タスク(当時
1) continue/break の loweringBuilder 修正のみで表現)
- ループ文脈スタック {head, exit} を導入。
- continue に遭遇 → headまたは latchへ br を emit し終端。
@ -22,4 +74,3 @@
- ビルド: `cargo build --release`
- LLVM smoke: `LLVM_SYS_180_PREFIX=$(llvm-config-18 --prefix) NYASH_LLVM_BITOPS_SMOKE=1 ./tools/llvm_smoke.sh release`
- VInvoke 調査: `NYASH_LLVM_VINVOKE_TRACE=1 NYASH_LLVM_VINVOKE_SMOKE=1 ./tools/llvm_smoke.sh release`

View File

@ -0,0 +1,346 @@
# Phase 166 Validation Report: JsonParserBox Unit Test with BoolExprLowerer
**Date**: 2025-12-06 (Updated: 2025-12-07 Phase 170)
**Status**: ⚠️ **Blocked** - ValueId boundary mapping issue
**Blocker**: Condition variables not included in JoinInlineBoundary
**Phase 170 Update**: BoolExprLowerer is now integrated (Phase 167-169), but a critical ValueId boundary mapping bug prevents runtime execution. See [phase170-valueid-boundary-analysis.md](phase170-valueid-boundary-analysis.md) for details.
---
## Phase 170 Re-validation Results (2025-12-07)
After Phase 167-169 (BoolExprLowerer integration), Phase 170 re-tested JsonParserBox with the following results:
### ✅ Whitelist Expansion Complete
- Added 6 JsonParserBox methods to routing whitelist
- Methods now route to JoinIR instead of `[joinir/freeze]`
- Pattern matching works correctly (Pattern2 detected for `_trim`)
### ⚠️ Runtime Failure: ValueId Boundary Issue
**Test**: `local_tests/test_trim_main_pattern.hako`
**Pattern Matched**: Pattern2 (twice, for 2 loops)
**Result**: Silent runtime failure (no output)
**Root Cause**: Condition variables (`start`, `end`) are resolved from HOST `variable_map` but not included in `JoinInlineBoundary`, causing undefined ValueId references.
**Evidence**:
```
[ssa-undef-debug] fn=TrimTest.trim/1 bb=BasicBlockId(12) inst_idx=0 used=ValueId(33)
[ssa-undef-debug] fn=TrimTest.trim/1 bb=BasicBlockId(12) inst_idx=0 used=ValueId(34)
```
**Solution**: Option A in [phase170-valueid-boundary-analysis.md](phase170-valueid-boundary-analysis.md) - Extract condition variables and add to boundary.
---
## Executive Summary (Original Phase 166)
Phase 166 aimed to validate that JsonParserBox can parse JSON through the JoinIR path, confirming Pattern1-4 support. However, investigation revealed that:
1. **✅ JoinIR Pattern Detection Works**: Pattern 2 (break) correctly detected ← **Still true in Phase 170**
2. **✅ Simple JSON Parsing Works**: Non-loop or simple-condition patterns execute fine
3. **~~❌ Complex Conditions Blocked~~** ← **FIXED in Phase 169**: BoolExprLowerer integrated
4. **❌ NEW BLOCKER (Phase 170)**: ValueId boundary mapping prevents runtime execution
---
## Test Results
### ✅ Test 1: Simple JSON Parser (No Loops)
**File**: `local_tests/test_json_parser_simple_string.hako`
```bash
./target/release/hakorune local_tests/test_json_parser_simple_string.hako
# Output: PASS: Got 'hello'
```
**Result**: **SUCCESS** - Basic string parsing without complex conditions works.
---
### ❌ Test 2: _trim Pattern with OR Chains
**File**: `local_tests/test_trim_or_pattern.hako`
```bash
./target/release/hakorune local_tests/test_trim_or_pattern.hako
# Output: [joinir/freeze] Loop lowering failed
```
**Result**: **BLOCKED** - OR condition causes `[joinir/freeze]` error.
---
### ⚠️ Test 3: _trim Pattern in main() with Simple Condition
**File**: `local_tests/test_trim_main_pattern.hako`
```bash
./target/release/hakorune local_tests/test_trim_main_pattern.hako
# Output: FAIL - Result: ' hello ' (not trimmed)
```
**Result**: **PATTERN DETECTED BUT LOGIC WRONG** - Pattern 2 matches, but uses hardcoded `i < 3` instead of actual condition.
**Debug Output**:
```
[trace:pattern] route: Pattern2_WithBreak MATCHED
[trace:varmap] pattern2_start: end→r9, s→r4, start→r6
Final start: 0 (unchanged - loop didn't execute properly)
```
---
## Root Cause Analysis
### Discovery 1: Function Name Whitelisting
**File**: `src/mir/builder/control_flow/joinir/routing.rs` (lines 44-68)
JoinIR is **ONLY enabled for specific function names**:
- `"main"`
- `"JoinIrMin.main/0"`
- `"JsonTokenizer.print_tokens/0"`
- `"ArrayExtBox.filter/2"`
**Impact**: `JsonParserBox._trim/1` is NOT whitelisted → `[joinir/freeze]` error.
**Workaround**: Test in `main()` function instead.
---
### Discovery 2: Hardcoded Conditions in Minimal Lowerers
**File**: `src/mir/join_ir/lowering/loop_with_break_minimal.rs` (lines 171-197)
```rust
// HARDCODED: !(i < 3)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_3,
value: ConstValue::Integer(3), // ← HARDCODED VALUE
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_lt,
op: CompareOp::Lt, // ← HARDCODED OPERATOR
lhs: i_param,
rhs: const_3,
}));
```
**Impact**: Pattern 2 lowerer generates fixed `i < 3` check, **ignoring the actual AST condition**.
**Current Behavior**:
- AST condition: `start < end` with `ch == " "` check
- Generated JoinIR: `i < 3` with `i >= 2` break check
- Result: Loop doesn't execute correctly
---
### Discovery 3: BoolExprLowerer Not Integrated
**Files**:
- `src/mir/join_ir/lowering/bool_expr_lowerer.rs` (Phase 167-168, 436 lines, complete)
- `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs` (line 58)
```rust
// Current code:
let loop_var_name = self.extract_loop_variable_from_condition(condition)?;
// Missing:
// use crate::mir::join_ir::lowering::bool_expr_lowerer::BoolExprLowerer;
// let mut bool_lowerer = BoolExprLowerer::new(self.builder);
// let cond_val = bool_lowerer.lower_condition(&ctx.condition)?;
```
**Impact**: BoolExprLowerer exists but isn't called by Pattern 2/4 lowerers.
---
### Discovery 4: LoopBuilder Hard Freeze
**File**: `src/mir/builder/control_flow/mod.rs` (lines 112-119)
```rust
// Phase 186: LoopBuilder Hard Freeze - Legacy path disabled
// Phase 187-2: LoopBuilder module removed - all loops must use JoinIR
return Err(format!(
"[joinir/freeze] Loop lowering failed: JoinIR does not support this pattern, and LoopBuilder has been removed.\n\
Function: {}\n\
Hint: This loop pattern is not supported. All loops must use JoinIR lowering.",
self.current_function.as_ref().map(|f| f.signature.name.as_str()).unwrap_or("<unknown>")
));
```
**Impact**: NO fallback exists when JoinIR patterns don't match.
---
## Architecture Issues
### Issue 1: Minimal Lowerers Are Test-Specific
**Design**: Pattern 1-4 lowerers are "minimal implementations" for specific test cases:
- Pattern 1: `apps/tests/joinir_simple_loop.hako` (`i < 5`)
- Pattern 2: `apps/tests/joinir_min_loop.hako` (`i < 3`, `i >= 2`)
- Pattern 3: `apps/tests/loop_if_phi_sum.hako` (hardcoded sum accumulation)
- Pattern 4: `apps/tests/loop_continue_pattern4.hako` (hardcoded continue logic)
**Problem**: These lowerers are **NOT** generic - they can't handle arbitrary conditions.
---
### Issue 2: Condition Extraction vs. Evaluation
**Current**:
- `extract_loop_variable_from_condition()` - Extracts variable name (`i`, `start`)
- Used for: Carrier detection, not condition evaluation
- Only supports: Simple comparisons like `i < 3`
**Missing**:
- Dynamic condition evaluation (BoolExprLowerer)
- OR chain support
- Complex boolean expressions
---
### Issue 3: JoinIR Generation Architecture
**Current Pipeline**:
```
AST Loop → Pattern Detection → Hardcoded JoinIR Generator
Fixed condition (i < 3)
```
**Needed Pipeline**:
```
AST Loop → Pattern Detection → BoolExprLowerer → Dynamic JoinIR Generator
↓ ↓
Condition MIR → Convert to JoinInst
```
---
## Phase 166 Status Update
### ✅ Completed Validation
1. **Pattern Detection**: Pattern 2 (break) correctly identified
2. **Simple Cases**: Non-loop JSON parsing works
3. **Infrastructure**: JoinIR pipeline functional
4. **Whitelist Behavior**: Function name routing confirmed
### ❌ Remaining Blockers
1. **OR Chains**: `ch == " " || ch == "\t"...` not supported
2. **Dynamic Conditions**: Hardcoded `i < 3` instead of actual condition
3. **BoolExprLowerer Integration**: Phase 167-168 code not used
4. **JsonParserBox._trim**: Cannot execute due to whitelisting
---
## Recommended Next Steps
### Phase 169: BoolExprLowerer Integration (HIGH PRIORITY)
**Goal**: Make JoinIR patterns support arbitrary conditions.
**Tasks**:
1. **Modify Pattern 2 Lowerer** (`loop_with_break_minimal.rs`):
- Accept `condition: &ASTNode` parameter
- Call `BoolExprLowerer::lower_condition(condition)`
- Generate JoinIR instructions from condition MIR
- Replace hardcoded `const_3`, `cmp_lt` with dynamic values
2. **Modify Pattern 4 Lowerer** (`loop_with_continue_minimal.rs`):
- Same changes as Pattern 2
3. **Update Caller** (`pattern2_with_break.rs`):
- Pass `ctx.condition` to lowerer
- Handle condition evaluation errors
4. **Test Coverage**:
- `_trim` pattern with OR chains
- Complex boolean expressions
- Nested conditions
**Estimated Effort**: 2-3 hours (architecture already designed in Phase 167-168)
---
### Phase 170: Function Whitelist Expansion (MEDIUM PRIORITY)
**Goal**: Enable JoinIR for JsonParserBox methods.
**Options**:
1. **Option A**: Add to whitelist:
```rust
"JsonParserBox._trim/1" => true,
"JsonParserBox._skip_whitespace/2" => true,
```
2. **Option B**: Enable JoinIR globally for all functions:
```rust
let is_target = true; // Always try JoinIR first
```
3. **Option C**: Add pattern-based routing (e.g., all `_trim*` functions)
**Recommended**: Option A (conservative, safe)
---
### Phase 171: JsonParserBox Full Validation (POST-169)
**Goal**: Validate all JsonParserBox methods work through JoinIR.
**Tests**:
- `_trim` (OR chains)
- `_skip_whitespace` (OR chains)
- `_parse_number` (digit loop)
- `_parse_string` (escape sequences)
- `_parse_array` (recursive calls)
- `_parse_object` (key-value pairs)
---
## Files Modified This Session
### Created Test Files
1. `local_tests/test_json_parser_simple_string.hako` - Simple JSON test (PASS)
2. `local_tests/test_trim_or_pattern.hako` - OR chain test (BLOCKED)
3. `local_tests/test_trim_simple_pattern.hako` - Simple condition test (BLOCKED)
4. `local_tests/test_trim_main_pattern.hako` - Whitelisted function test (WRONG LOGIC)
5. `local_tests/test_trim_debug.hako` - Debug output test
### Documentation
1. `docs/development/current/main/phase166-validation-report.md` (this file)
---
## Conclusion
**Phase 166 Validation Status**: ⚠️ **Partially Complete**
**Key Findings**:
1. JoinIR Pattern Detection **works correctly**
2. Simple patterns **execute successfully**
3. Complex OR chains **are blocked** by hardcoded conditions
4. BoolExprLowerer (Phase 167-168) **exists but isn't integrated**
**Next Critical Phase**: **Phase 169 - BoolExprLowerer Integration** to unblock JsonParserBox._trim and enable dynamic condition evaluation.
**Timeline**:
- Phase 169: 2-3 hours (integration work)
- Phase 170: 30 minutes (whitelist update)
- Phase 171: 1 hour (full validation testing)
**Total Estimated Time to Complete Phase 166**: 4-5 hours
---
## References
- **Phase 166 Goal**: `docs/development/current/main/phase166-joinir-json-parser-validation.md`
- **Phase 167-168**: `src/mir/join_ir/lowering/bool_expr_lowerer.rs`
- **Pattern 2 Lowerer**: `src/mir/join_ir/lowering/loop_with_break_minimal.rs`
- **Routing Logic**: `src/mir/builder/control_flow/joinir/routing.rs`
- **LoopBuilder Freeze**: `src/mir/builder/control_flow/mod.rs` (lines 112-119)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,304 @@
# Phase 170: JsonParserBox JoinIR Preparation & Re-validation - Completion Report
**Date**: 2025-12-07
**Duration**: 1 session (autonomous work)
**Status**: ✅ **Complete** - Environment prepared, blockers identified, next phase planned
---
## Executive Summary
Phase 170 successfully prepared the environment for JsonParserBox JoinIR validation and identified the critical ValueId boundary mapping issue blocking runtime execution. All tasks completed:
-**Task A-1**: JoinIR routing whitelist expanded (6 JsonParserBox methods + test helper)
-**Task A-2**: ValueId boundary issue identified with full root cause analysis
- ⚠️ **Task B**: Mini tests blocked by `using` statement (workaround: simplified test created)
-**Task C**: Next phase direction decided (Option A: Fix boundary mapping)
**Key Achievement**: Identified that BoolExprLowerer integration (Phase 167-169) is correct, but the boundary mechanism needs condition variable extraction to work properly.
---
## Phase 170-A: Environment Setup ✅
### Task A-1: JoinIR Routing Whitelist Expansion
**Objective**: Allow JsonParserBox methods to route to JoinIR patterns instead of `[joinir/freeze]`.
**Changes Made**:
**File**: `src/mir/builder/control_flow/joinir/routing.rs` (lines 68-76)
**Added entries**:
```rust
// Phase 170-A-1: Enable JsonParserBox methods for JoinIR routing
"JsonParserBox._trim/1" => true,
"JsonParserBox._skip_whitespace/2" => true,
"JsonParserBox._match_literal/2" => true,
"JsonParserBox._parse_string/2" => true,
"JsonParserBox._parse_array/2" => true,
"JsonParserBox._parse_object/2" => true,
// Phase 170-A-1: Test methods (simplified versions)
"TrimTest.trim/1" => true,
```
**Result**: ✅ Methods now route to pattern matching instead of immediate `[joinir/freeze]` rejection.
**Evidence**:
```bash
HAKO_JOINIR_DEBUG=1 ./target/release/hakorune local_tests/test_trim_main_pattern.hako
# Output: [joinir/pattern2] Generated JoinIR for Loop with Break Pattern (Phase 169)
```
---
### Task A-2: ValueId Boundary Issue Identification
**Objective**: Understand if ValueId boundary mapping affects JsonParserBox tests.
**Test Created**: `local_tests/test_trim_main_pattern.hako` (48 lines)
- Simplified `_trim` method with same loop structure as JsonParserBox
- Two loops with break (Pattern2 x 2)
- Condition variables: `start < end`, `end > start`
**Findings**:
1. **Pattern Detection**: ✅ Works correctly
- Both loops match Pattern2
- JoinIR generation succeeds
2. **Runtime Execution**: ❌ Silent failure
- Program compiles successfully
- No output produced
- Exit code 0 (but no print statements executed)
3. **Root Cause Identified**: ValueId boundary mapping
- Condition variables (`start`, `end`) resolved from HOST `variable_map`
- HOST ValueIds (33, 34, 48, 49) used directly in JoinIR
- Not included in `JoinInlineBoundary`
- Merge process doesn't remap them → undefined at runtime
**Evidence**:
```
[ssa-undef-debug] fn=TrimTest.trim/1 bb=BasicBlockId(12) inst_idx=0 used=ValueId(33) inst=Compare { dst: ValueId(26), op: Lt, lhs: ValueId(33), rhs: ValueId(34) }
[ssa-undef-debug] fn=TrimTest.trim/1 bb=BasicBlockId(12) inst_idx=0 used=ValueId(34) inst=Compare { dst: ValueId(26), op: Lt, lhs: ValueId(33), rhs: ValueId(34) }
```
**Impact**: CRITICAL - Blocks ALL JsonParserBox methods with complex conditions.
**Detailed Analysis**: See [phase170-valueid-boundary-analysis.md](phase170-valueid-boundary-analysis.md)
---
## Phase 170-B: JsonParserBox Mini Test Re-execution ⚠️
### Original Test Files
**Location**: `tools/selfhost/json_parser_{string,array,object}_min.hako`
**Blocker**: `using` statement not working
```
[using] not found: 'tools/hako_shared/json_parser.hako" with JsonParserBox'
```
**Root Cause**: JsonParserBox is defined in external file, not compiled/loaded at runtime.
**Impact**: Can't run original integration tests in current form.
---
### Workaround: Simplified Test
**Created**: `local_tests/test_trim_main_pattern.hako`
**Purpose**: Test same loop structure without `using` dependency.
**Structure**:
```nyash
static box TrimTest {
method trim(s) {
// Same structure as JsonParserBox._trim
loop(start < end) { ... break }
loop(end > start) { ... break }
}
main(args) { ... }
}
```
**Result**: Successfully routes to Pattern2, exposes boundary issue.
---
## Phase 170-C: Next Phase Planning ✅
### Immediate TODOs (Phase 171+ Candidates)
**Priority 1: Fix ValueId Boundary Mapping** (HIGHEST PRIORITY)
- **Why**: Blocks all JsonParserBox complex condition tests
- **What**: Extract condition variables and add to `JoinInlineBoundary`
- **Where**: Pattern lowerers (pattern1/2/3/4)
- **Estimate**: 4.5 hours
- **Details**: See Option A in [phase170-valueid-boundary-analysis.md](phase170-valueid-boundary-analysis.md)
**Priority 2: Using Statement / Box Loading** (MEDIUM)
- **Why**: Enable actual JsonParserBox integration tests
- **What**: Compile and register boxes from `using` statements
- **Alternatives**:
- Inline JsonParser code in tests (quick workaround)
- Auto-compile static boxes (proper solution)
**Priority 3: Multi-Loop Function Support** (LOW)
- **Why**: `_trim` has 2 loops in one function
- **Current**: Each loop calls JoinIR routing separately (seems to work)
- **Risk**: May need validation that multiple JoinIR calls per function work correctly
---
### Recommended Next Phase Direction
**Option A: Fix Boundary Mapping First****RECOMMENDED**
**Rationale**:
1. **Root blocker**: Boundary issue blocks ALL tests, not just one
2. **BoolExprLowerer correct**: Phase 169 integration is solid
3. **Pattern matching correct**: Routing and detection work perfectly
4. **Isolated fix**: Boundary extraction is well-scoped and testable
5. **High impact**: Once fixed, all JsonParser methods should work
**Alternative**: Option B (simplify code) or Option C (postpone) - both less effective.
---
## Test Results Matrix
| Method/Test | Pattern | JoinIR Status | Blocker | Notes |
|-------------|---------|---------------|---------|-------|
| `TrimTest.trim/1` (loop 1) | Pattern2 | ⚠️ Routes OK, runtime fail | ValueId boundary | `start < end` uses undefined ValueId(33, 34) |
| `TrimTest.trim/1` (loop 2) | Pattern2 | ⚠️ Routes OK, runtime fail | ValueId boundary | `end > start` uses undefined ValueId(48, 49) |
| `JsonParserBox._trim/1` | (untested) | - | Using statement | Can't load JsonParserBox at runtime |
| `JsonParserBox._skip_whitespace/2` | (untested) | - | Using statement | Can't load JsonParserBox at runtime |
| `JsonParserBox._match_literal/2` | (untested) | - | Using statement | Can't load JsonParserBox at runtime |
| `JsonParserBox._parse_string/2` | (untested) | - | Using statement | Can't load JsonParserBox at runtime |
| `JsonParserBox._parse_array/2` | (untested) | - | Using statement | Can't load JsonParserBox at runtime |
| `JsonParserBox._parse_object/2` | (untested) | - | Using statement | Can't load JsonParserBox at runtime |
**Summary**:
- **Routing**: ✅ All methods whitelisted, pattern detection works
- **Compilation**: ✅ BoolExprLowerer generates correct JoinIR
- **Runtime**: ❌ ValueId boundary issue prevents execution
- **Integration**: ⚠️ `using` statement blocks full JsonParser tests
---
## Files Modified
**Modified**:
- `src/mir/builder/control_flow/joinir/routing.rs` (+8 lines, whitelist expansion)
**Created**:
- `local_tests/test_trim_main_pattern.hako` (+48 lines, test file)
- `docs/development/current/main/phase170-valueid-boundary-analysis.md` (+270 lines, analysis)
- `docs/development/current/main/phase170-completion-report.md` (+THIS file)
**Updated**:
- `CURRENT_TASK.md` (added Phase 170 section with progress summary)
- `docs/development/current/main/phase166-validation-report.md` (added Phase 170 update section)
---
## Technical Insights
### Boundary Mechanism Gap
**Current Design**:
```rust
JoinInlineBoundary::new_inputs_only(
vec![ValueId(0)], // JoinIR loop variable
vec![loop_var_id], // HOST loop variable
);
```
**What's Missing**: Condition variables!
**Needed Design**:
```rust
JoinInlineBoundary::new_inputs_only(
vec![ValueId(0), ValueId(1), ValueId(2)], // loop var + cond vars
vec![loop_var_id, start_id, end_id], // HOST ValueIds
);
```
**Why It Matters**:
- `condition_to_joinir.rs` directly references HOST `variable_map` ValueIds
- These ValueIds are NOT in JoinIR's fresh allocator space
- Without boundary mapping, they remain undefined after merge
- Silent failure: compiles but doesn't execute
### Two ValueId Namespaces
**HOST Context** (Main MirBuilder):
- ValueIds from 0 upward (e.g., `start = ValueId(33)`)
- All variables in `builder.variable_map`
- Pre-existing before JoinIR call
**JoinIR Context** (Fresh Allocator):
- ValueIds from 0 upward (independent sequence)
- Generated by JoinIR lowerer
- Post-merge: remapped to new HOST ValueIds
**Bridge**: `JoinInlineBoundary` maps between the two spaces with Copy instructions.
**Current Gap**: Only explicitly listed variables get bridged. Condition variables are implicitly referenced but not bridged.
---
## Validation Checklist
- [x] Whitelist expanded (6 JsonParserBox methods + test)
- [x] Pattern routing verified (Pattern2 detected correctly)
- [x] BoolExprLowerer integration verified (generates JoinIR correctly)
- [x] Boundary issue identified (root cause documented)
- [x] Test file created (simplified _trim test)
- [x] Root cause analysis completed (270-line document)
- [x] Next phase direction decided (Option A recommended)
- [x] Documentation updated (CURRENT_TASK.md, phase166 report)
- [x] Files committed (ready for next phase)
---
## Next Phase: Phase 171 - Boundary Mapping Fix
**Recommended Implementation**:
1. **Create condition variable extractor** (30 min)
- File: `src/mir/builder/control_flow/joinir/patterns/cond_var_extractor.rs`
- Function: `extract_condition_variables(ast: &ASTNode, builder: &MirBuilder) -> Vec<(String, ValueId)>`
2. **Update Pattern2** (1 hour)
- Extract condition variables before lowering
- Create expanded boundary with condition vars
- Test with `TrimTest.trim/1`
3. **Update Pattern1, Pattern3, Pattern4** (3 hours)
- Apply same pattern
- Ensure all patterns include condition vars in boundary
4. **Validation** (30 min)
- Re-run `TrimTest.trim/1` → should print output
- Re-run JsonParserBox tests (if `using` resolved)
**Total Estimate**: 5 hours
---
## Conclusion
Phase 170 successfully prepared the environment for JsonParserBox validation and identified the critical blocker preventing runtime execution. The boundary mapping issue is well-understood, with a clear solution path (Option A: extract condition variables).
**Key Achievements**:
- ✅ Whitelist expansion enables JsonParserBox routing
- ✅ BoolExprLowerer integration verified working correctly
- ✅ Boundary issue root cause identified and documented
- ✅ Clear next steps with 5-hour implementation estimate
**Next Step**: Implement Phase 171 - Condition Variable Extraction for Boundary Mapping.

View File

@ -0,0 +1,252 @@
# Phase 170: ValueId Boundary Mapping Analysis
**Date**: 2025-12-07
**Status**: Root Cause Identified
**Impact**: CRITICAL - Blocks all JsonParserBox complex condition tests
## Problem Summary
JoinIR loop patterns with complex conditions (e.g., `start < end` in `_trim`) compile successfully but fail silently at runtime because condition variable ValueIds are not properly mapped between HOST and JoinIR contexts.
## Symptoms
```
[ssa-undef-debug] fn=TrimTest.trim/1 bb=BasicBlockId(12) inst_idx=0 used=ValueId(33) inst=Compare { dst: ValueId(26), op: Lt, lhs: ValueId(33), rhs: ValueId(34) }
[ssa-undef-debug] fn=TrimTest.trim/1 bb=BasicBlockId(12) inst_idx=0 used=ValueId(34) inst=Compare { dst: ValueId(26), op: Lt, lhs: ValueId(33), rhs: ValueId(34) }
```
- Condition uses undefined ValueIds (33, 34) for variables `start` and `end`
- Program compiles but produces no output (silent runtime failure)
- PHI nodes also reference undefined carrier values
## Root Cause
### Architecture Overview
The JoinIR merge process uses two separate ValueId "namespaces":
1. **HOST context**: Main MirBuilder's ValueId space (e.g., `start = ValueId(33)`)
2. **JoinIR context**: Fresh ValueId allocator starting from 0 (e.g., `ValueId(0), ValueId(1), ...`)
The `JoinInlineBoundary` mechanism is supposed to bridge these two spaces by injecting Copy instructions at the entry block.
### The Bug
**Location**: `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs` (and other patterns)
**Current boundary creation**:
```rust
let boundary = JoinInlineBoundary::new_inputs_only(
vec![ValueId(0)], // JoinIR's main() parameter (loop variable init)
vec![loop_var_id], // Host's loop variable
);
```
**What's missing**: Condition variables (`start`, `end`) are NOT in the boundary!
**What happens**:
1. `condition_to_joinir.rs` looks up variables in `builder.variable_map`:
```rust
builder.variable_map.get("start") // Returns ValueId(33) from HOST
builder.variable_map.get("end") // Returns ValueId(34) from HOST
```
2. JoinIR instructions are generated with these HOST ValueIds:
```rust
JoinInst::Compute(MirLikeInst::Compare {
dst: ValueId(26),
op: Lt,
lhs: ValueId(33), // HOST ValueId, not in JoinIR space!
rhs: ValueId(34), // HOST ValueId, not in JoinIR space!
})
```
3. During merge, `remap_values()` only remaps ValueIds that are in `used_values`:
- ValueIds 0, 1, 2, ... from JoinIR → new ValueIds from builder
- **But ValueId(33) and ValueId(34) are not in JoinIR's used_values set!**
- They're HOST ValueIds that leaked into JoinIR space
4. Result: Compare instruction references undefined ValueIds
## Architectural Issue
The current design has a conceptual mismatch:
- **`condition_to_joinir.rs`** assumes it can directly reference HOST ValueIds from `builder.variable_map`
- **JoinIR merge** assumes all ValueIds come from JoinIR's fresh allocator
- **Boundary mechanism** only maps explicitly listed inputs/outputs
This works for simple patterns where:
- Condition is hardcoded (e.g., `i < 3`)
- All condition values are constants or loop variables already in the boundary
This breaks when:
- Condition references variables from HOST context (e.g., `start < end`)
- Those variables are not in the boundary inputs
## Affected Code Paths
### Phase 169 Integration: `condition_to_joinir.rs`
Lines 183-189:
```rust
ASTNode::Variable { name, .. } => {
builder
.variable_map
.get(name)
.copied()
.ok_or_else(|| format!("Variable '{}' not found in variable_map", name))
}
```
This returns HOST ValueIds directly without checking if they need boundary mapping.
### Pattern Lowerers: `pattern2_with_break.rs`, etc.
Pattern lowerers create minimal boundaries that only include:
- Loop variable (e.g., `i`)
- Accumulator (if present)
But NOT:
- Variables referenced in loop condition (e.g., `start`, `end` in `start < end`)
- Variables referenced in loop body expressions
### Merge Infrastructure: `merge/mod.rs`
The merge process has no way to detect that HOST ValueIds have leaked into JoinIR instructions.
## Test Case: `TrimTest.trim/1`
**Code**:
```nyash
local start = 0
local end = s.length()
loop(start < end) { // Condition references start, end
// ...
break
}
```
**Expected boundary**:
```rust
JoinInlineBoundary::new_inputs_only(
vec![ValueId(0), ValueId(1), ValueId(2)], // loop var, start, end
vec![loop_var_id, start_id, end_id], // HOST ValueIds
)
```
**Actual boundary**:
```rust
JoinInlineBoundary::new_inputs_only(
vec![ValueId(0)], // Only loop var
vec![loop_var_id], // Only loop var
)
```
**Result**: `start` and `end` are undefined in JoinIR space
## Solutions
### Option A: Extract Condition Variables into Boundary (Recommended)
**Where**: Pattern lowerers (pattern1/2/3/4)
**Steps**:
1. Before calling `lower_condition_to_joinir()`, analyze AST to find all variables
2. For each variable, get HOST ValueId from `builder.variable_map`
3. Allocate JoinIR-side ValueIds (e.g., ValueId(1), ValueId(2))
4. Create boundary with all condition variables:
```rust
let cond_vars = extract_condition_variables(condition_ast, builder);
let boundary = JoinInlineBoundary::new_inputs_only(
vec![ValueId(0), ValueId(1), ValueId(2)], // loop var + cond vars
vec![loop_var_id, cond_vars[0], cond_vars[1]],
);
```
**Pros**:
- Minimal change to existing architecture
- Clear separation: boundary handles HOST↔JoinIR mapping
- Works for all condition complexity
**Cons**:
- Need to implement variable extraction from AST
- Each pattern needs updating
### Option B: Delay Variable Resolution Until Merge
**Where**: `condition_to_joinir.rs`
**Idea**: Instead of resolving variables immediately, emit placeholder instructions and resolve during merge.
**Pros**:
- Cleaner separation: JoinIR doesn't touch HOST ValueIds
**Cons**:
- Major refactoring required
- Need new placeholder instruction type
- Complicates merge logic
### Option C: Use Variable Names Instead of ValueIds in JoinIR
**Where**: JoinIR instruction format
**Idea**: JoinIR uses variable names (strings) instead of ValueIds, resolve during merge.
**Pros**:
- Most robust solution
- Eliminates ValueId namespace confusion
**Cons**:
- Breaks current JoinIR design (uses MirLikeInst which has ValueIds)
- Major architectural change
## Recommendation
**Option A** - Extract condition variables and add to boundary.
**Implementation Plan**:
1. **Create AST variable extractor** (30 minutes)
- File: `src/mir/builder/control_flow/joinir/patterns/cond_var_extractor.rs`
- Function: `extract_condition_variables(ast: &ASTNode, builder: &MirBuilder) -> Vec<(String, ValueId)>`
- Recursively walk AST, collect all Variable nodes
2. **Update Pattern2** (1 hour)
- Extract condition variables before calling pattern lowerer
- Create boundary with extracted variables
- Test with `TrimTest.trim/1`
3. **Update Pattern1, Pattern3, Pattern4** (1 hour each)
- Apply same pattern
4. **Validation** (30 minutes)
- Re-run `TrimTest.trim/1` → should output correctly
- Re-run JsonParserBox tests → should work
**Total Estimate**: 4.5 hours
## Files Affected
**New**:
- `src/mir/builder/control_flow/joinir/patterns/cond_var_extractor.rs` (new utility)
**Modified**:
- `src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs`
- `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`
- `src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs`
- `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
## Related Issues
- **Phase 169**: BoolExprLowerer integration exposed this issue
- **Phase 166**: JsonParserBox validation blocked by this bug
- **Phase 188-189**: Boundary mechanism exists but incomplete
## Next Steps
1. Implement Option A (condition variable extraction)
2. Update all 4 patterns
3. Re-run Phase 170 validation tests
4. Document the "always include condition variables in boundary" pattern

View File

@ -0,0 +1,209 @@
# Phase 171-1: Boundary Coverage Analysis
**Date**: 2025-12-07
**Status**: Analysis Complete
## Current Boundary Coverage Table
| Component | Currently in Boundary? | How Mapped? | File Location |
|-----------|----------------------|-------------|---------------|
| Loop variable (`i`) | ✅ Yes | `join_inputs[0]``host_inputs[0]` | `inline_boundary.rs:115-124` |
| Carriers (`sum`, `count`) | ✅ Yes | `exit_bindings` (Phase 190+) | `inline_boundary.rs:150-167` |
| Exit values | ✅ Yes | `exit_bindings.join_exit_value` | `exit_binding.rs:87-116` |
| **Condition inputs (`start`, `end`, `len`)** | ❌ **NO** | **MISSING** | **N/A** |
---
## Detailed Analysis
### 1. Loop Variable (`i`) - ✅ Properly Mapped
**Mapping Flow**:
```
HOST: variable_map["i"] = ValueId(5)
↓ join_inputs = [ValueId(0)] (JoinIR local ID)
↓ host_inputs = [ValueId(5)] (HOST ID)
JoinIR: main() parameter = ValueId(0)
↓ merge_joinir_mir_blocks() injects:
MIR: ValueId(100) = Copy ValueId(5) // Boundary Copy instruction
```
**Evidence**:
- `inline_boundary.rs:175-189` - `new_inputs_only()` constructor
- `merge/mod.rs:104-106` - Boundary reconnection call
- **Works correctly** in all existing JoinIR tests
---
### 2. Carriers (`sum`, `count`) - ✅ Properly Mapped (Phase 190+)
**Mapping Flow**:
```
HOST: variable_map["sum"] = ValueId(10)
↓ exit_bindings = [
LoopExitBinding {
carrier_name: "sum",
join_exit_value: ValueId(18), // k_exit param in JoinIR
host_slot: ValueId(10) // HOST variable
}
]
JoinIR: k_exit(sum_exit) - parameter = ValueId(18)
↓ merge_joinir_mir_blocks() remaps:
↓ remapper.set_value(ValueId(18), ValueId(200))
MIR: variable_map["sum"] = ValueId(200) // Reconnected
```
**Evidence**:
- `inline_boundary.rs:259-311` - `new_with_exit_bindings()` constructor
- `exit_binding.rs:87-116` - Exit binding builder
- `merge/mod.rs:188-267` - `reconnect_boundary()` implementation
- **Works correctly** in Pattern 3/4 tests
---
### 3. **Condition Inputs (`start`, `end`, `len`) - ❌ NOT MAPPED**
**Current Broken Flow**:
```
HOST: variable_map["start"] = ValueId(33)
↓ condition_to_joinir() reads from variable_map DIRECTLY
↓ lower_value_expression() returns ValueId(33)
JoinIR: Uses ValueId(33) in Compare instruction
↓ NO BOUNDARY REGISTRATION
↓ NO REMAPPING
MIR: ValueId(33) undefined → RUNTIME ERROR
```
**Evidence** (from Phase 170):
```
[ssa-undef-debug] fn=TrimTest.trim/1 bb=BasicBlockId(12)
inst_idx=0 used=ValueId(33)
```
**Root Cause**:
- `condition_to_joinir.rs:183-189` - Reads from `builder.variable_map` directly
- `condition_to_joinir.rs:236-239` - Returns HOST ValueId unchanged
- **NO registration** in `JoinInlineBoundary`
- **NO remapping** in `merge_joinir_mir_blocks()`
---
## Why This is a Problem
### Example: `loop(start < end)`
**What SHOULD happen**:
```rust
// HOST preparation
let start_host = ValueId(33);
let end_host = ValueId(34);
// JoinIR lowerer
let start_joinir = ValueId(0); // Local param
let end_joinir = ValueId(1); // Local param
// Boundary
JoinInlineBoundary {
join_inputs: [ValueId(0), ValueId(1)], // start, end in JoinIR
host_inputs: [ValueId(33), ValueId(34)], // start, end in HOST
// ...
}
// Merge
// Injects: ValueId(100) = Copy ValueId(33) // start
// Injects: ValueId(101) = Copy ValueId(34) // end
// Remaps all JoinIR ValueId(0) → ValueId(100), ValueId(1) → ValueId(101)
```
**What CURRENTLY happens**:
```rust
// JoinIR lowerer
let start = builder.variable_map.get("start"); // Returns ValueId(33) - HOST ID!
let end = builder.variable_map.get("end"); // Returns ValueId(34) - HOST ID!
// JoinIR uses HOST ValueIds directly
Compare { lhs: ValueId(33), rhs: ValueId(34) } // WRONG - uses HOST IDs
// No boundary registration → No remapping → UNDEFINED VALUE ERROR
```
---
## Comparison: Loop Variable vs Condition Inputs
| Aspect | Loop Variable (`i`) | Condition Inputs (`start`, `end`) |
|--------|--------------------|------------------------------------|
| **Who allocates ValueId?** | JoinIR lowerer (`alloc_value()`) | HOST (`builder.variable_map`) |
| **Boundary registration?** | ✅ Yes (`join_inputs[0]`) | ❌ NO |
| **Remapping?** | ✅ Yes (via boundary Copy) | ❌ NO |
| **Result?** | ✅ Works | ❌ Undefined ValueId error |
---
## Root Cause Summary
The core problem is **two-faced ValueId resolution** in `condition_to_joinir()`:
1. **Loop variable** (`i`):
- Allocated by JoinIR lowerer: `i_param = alloc_value()``ValueId(0)`
- Used in condition: `i < end`
- Properly registered in boundary ✅
2. **Condition variables** (`start`, `end`):
- Read from HOST: `builder.variable_map.get("start")``ValueId(33)`
- Used in condition: `start < end`
- **NOT registered in boundary** ❌
---
## Files Involved
### Boundary Definition
- `src/mir/join_ir/lowering/inline_boundary.rs` (340 lines)
- `JoinInlineBoundary` struct
- `join_inputs`, `host_inputs` fields
- **Missing**: Condition inputs field
### Boundary Builder
- `src/mir/builder/control_flow/joinir/patterns/exit_binding.rs` (401 lines)
- `LoopExitBinding` struct
- `ExitBindingBuilder` - builds exit bindings
- **Missing**: Condition input builder
### Merge Implementation
- `src/mir/builder/control_flow/joinir/merge/mod.rs` (268 lines)
- `merge_joinir_mir_blocks()` - main merge coordinator
- `reconnect_boundary()` - updates variable_map with exit values
- **Missing**: Condition input Copy injection
### Condition Lowering
- `src/mir/join_ir/lowering/condition_to_joinir.rs` (443 lines)
- `lower_condition_to_joinir()` - AST → JoinIR conversion
- `lower_value_expression()` - reads from `builder.variable_map`
- **Problem**: Returns HOST ValueIds directly
### Loop Lowerers
- `src/mir/join_ir/lowering/loop_with_break_minimal.rs` (295 lines)
- `lower_loop_with_break_minimal()` - Pattern 2 lowerer
- Calls `lower_condition_to_joinir()` at line 138-144
- **Missing**: Extract condition variables, register in boundary
---
## Next Steps (Phase 171-2)
We need to design a "box" for condition inputs. Three options:
**Option A**: Extend `JoinInlineBoundary` with `condition_inputs` field
**Option B**: Create new `LoopInputBinding` structure
**Option C**: Extend `LoopExitBinding` to include condition inputs
Proceed to Phase 171-2 for design decision.
---
## References
- Phase 170 Analysis: `phase170-valueid-boundary-analysis.md`
- Phase 170 Completion: `phase170-completion-report.md`
- JoinIR Design: `docs/development/current/main/phase33-10-if-joinir-design.md`

View File

@ -0,0 +1,418 @@
# Phase 171-2: Condition Inputs Metadata Design
**Date**: 2025-12-07
**Status**: Design Complete
**Decision**: **Option A - Extend JoinInlineBoundary**
---
## Design Decision: Option A
### Rationale
**Option A: Extend JoinInlineBoundary** (CHOSEN ✅)
```rust
pub struct JoinInlineBoundary {
pub join_inputs: Vec<ValueId>,
pub host_inputs: Vec<ValueId>,
pub join_outputs: Vec<ValueId>,
pub host_outputs: Vec<ValueId>,
pub exit_bindings: Vec<LoopExitBinding>,
// NEW: Condition-only inputs
pub condition_inputs: Vec<(String, ValueId)>, // [(var_name, host_value_id)]
}
```
**Why this is best**:
1. **Minimal invasiveness**: Single structure change
2. **Clear semantics**: "Condition inputs" are distinct from "loop parameters"
3. **Reuses existing infrastructure**: Same Copy injection mechanism
4. **Future-proof**: Easy to extend for condition-only outputs (if needed)
5. **Symmetric design**: Mirrors how `exit_bindings` handle exit values
**Rejected alternatives**:
**Option B: Create new LoopInputBinding**
```rust
pub struct LoopInputBinding {
pub condition_vars: HashMap<String, ValueId>,
}
```
**Rejected**: Introduces another structure; harder to coordinate with boundary
**Option C: Extend LoopExitBinding**
```rust
pub struct LoopExitBinding {
pub condition_inputs: Vec<String>, // NEW
// ...
}
```
**Rejected**: Semantic mismatch (exit bindings are for outputs, not inputs)
---
## Detailed Design
### 1. Extended Structure Definition
**File**: `src/mir/join_ir/lowering/inline_boundary.rs`
```rust
#[derive(Debug, Clone)]
pub struct JoinInlineBoundary {
/// JoinIR-local ValueIds that act as "input slots"
///
/// These are the ValueIds used **inside** the JoinIR fragment to refer
/// to values that come from the host. They should be small sequential
/// IDs (0, 1, 2, ...) since JoinIR lowerers allocate locally.
///
/// Example: For a loop variable `i`, JoinIR uses ValueId(0) as the parameter.
pub join_inputs: Vec<ValueId>,
/// Host-function ValueIds that provide the input values
///
/// These are the ValueIds from the **host function** that correspond to
/// the join_inputs. The merger will inject Copy instructions to connect
/// host_inputs[i] → join_inputs[i].
///
/// Example: If host has `i` as ValueId(4), then host_inputs = [ValueId(4)].
pub host_inputs: Vec<ValueId>,
/// JoinIR-local ValueIds that represent outputs (if any)
pub join_outputs: Vec<ValueId>,
/// Host-function ValueIds that receive the outputs (DEPRECATED)
#[deprecated(since = "Phase 190", note = "Use exit_bindings instead")]
pub host_outputs: Vec<ValueId>,
/// Explicit exit bindings for loop carriers (Phase 190+)
pub exit_bindings: Vec<LoopExitBinding>,
/// Condition-only input variables (Phase 171+)
///
/// These are variables used ONLY in the loop condition, NOT as loop parameters.
/// They need to be available in JoinIR scope but are not modified by the loop.
///
/// # Example
///
/// For `loop(start < end) { i = i + 1 }`:
/// - Loop parameter: `i` → goes in `join_inputs`/`host_inputs`
/// - Condition-only: `start`, `end` → go in `condition_inputs`
///
/// # Format
///
/// Each entry is `(variable_name, host_value_id)`:
/// ```
/// condition_inputs: vec![
/// ("start".to_string(), ValueId(33)), // HOST ID for "start"
/// ("end".to_string(), ValueId(34)), // HOST ID for "end"
/// ]
/// ```
///
/// The merger will:
/// 1. Extract unique variable names from condition AST
/// 2. Look up HOST ValueIds from `builder.variable_map`
/// 3. Inject Copy instructions for each condition input
/// 4. Remap JoinIR references to use the copied values
pub condition_inputs: Vec<(String, ValueId)>,
}
```
---
### 2. Constructor Updates
**Add new constructor**:
```rust
impl JoinInlineBoundary {
/// Create a new boundary with condition inputs (Phase 171+)
///
/// # Arguments
///
/// * `join_inputs` - JoinIR-local ValueIds for loop parameters
/// * `host_inputs` - HOST ValueIds for loop parameters
/// * `condition_inputs` - Condition-only variables [(name, host_value_id)]
///
/// # Example
///
/// ```ignore
/// let boundary = JoinInlineBoundary::new_with_condition_inputs(
/// vec![ValueId(0)], // join_inputs (i)
/// vec![ValueId(5)], // host_inputs (i)
/// vec![
/// ("start".to_string(), ValueId(33)),
/// ("end".to_string(), ValueId(34)),
/// ],
/// );
/// ```
pub fn new_with_condition_inputs(
join_inputs: Vec<ValueId>,
host_inputs: Vec<ValueId>,
condition_inputs: Vec<(String, ValueId)>,
) -> Self {
assert_eq!(
join_inputs.len(),
host_inputs.len(),
"join_inputs and host_inputs must have same length"
);
Self {
join_inputs,
host_inputs,
join_outputs: vec![],
#[allow(deprecated)]
host_outputs: vec![],
exit_bindings: vec![],
condition_inputs,
}
}
/// Create boundary with inputs, exit bindings, AND condition inputs (Phase 171+)
pub fn new_with_exit_and_condition_inputs(
join_inputs: Vec<ValueId>,
host_inputs: Vec<ValueId>,
exit_bindings: Vec<LoopExitBinding>,
condition_inputs: Vec<(String, ValueId)>,
) -> Self {
assert_eq!(
join_inputs.len(),
host_inputs.len(),
"join_inputs and host_inputs must have same length"
);
Self {
join_inputs,
host_inputs,
join_outputs: vec![],
#[allow(deprecated)]
host_outputs: vec![],
exit_bindings,
condition_inputs,
}
}
}
```
**Update existing constructors** to set `condition_inputs: vec![]`:
```rust
pub fn new_inputs_only(join_inputs: Vec<ValueId>, host_inputs: Vec<ValueId>) -> Self {
// ... existing assertions
Self {
join_inputs,
host_inputs,
join_outputs: vec![],
#[allow(deprecated)]
host_outputs: vec![],
exit_bindings: vec![],
condition_inputs: vec![], // NEW: Default to empty
}
}
pub fn new_with_exit_bindings(
join_inputs: Vec<ValueId>,
host_inputs: Vec<ValueId>,
exit_bindings: Vec<LoopExitBinding>,
) -> Self {
// ... existing assertions
Self {
join_inputs,
host_inputs,
join_outputs: vec![],
#[allow(deprecated)]
host_outputs: vec![],
exit_bindings,
condition_inputs: vec![], // NEW: Default to empty
}
}
```
---
### 3. Value Flow Diagram
```
┌─────────────────────────────────────────────────────────────────┐
│ HOST MIR Builder │
│ │
│ variable_map: │
│ "i" → ValueId(5) (loop variable - becomes parameter) │
│ "start" → ValueId(33) (condition input - read-only) │
│ "end" → ValueId(34) (condition input - read-only) │
│ "sum" → ValueId(10) (carrier - exit binding) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────┴─────────────────┐
│ │
↓ ↓
┌──────────────────────┐ ┌──────────────────────┐
│ JoinIR Lowerer │ │ Condition Extractor │
│ │ │ │
│ Allocates: │ │ Extracts variables: │
│ i_param = Val(0) │ │ ["start", "end"] │
│ sum_init = Val(1) │ │ │
└──────────────────────┘ └──────────────────────┘
↓ ↓
└─────────────────┬─────────────────┘
┌────────────────────────────────────────────┐
│ JoinInlineBoundary │
│ │
│ join_inputs: [Val(0), Val(1)] │
│ host_inputs: [Val(5), Val(10)] │
│ │
│ condition_inputs: [ │
│ ("start", Val(33)), │
│ ("end", Val(34)) │
│ ] │
│ │
│ exit_bindings: [ │
│ { carrier: "sum", join_exit: Val(18), │
│ host_slot: Val(10) } │
│ ] │
└────────────────────────────────────────────┘
┌────────────────────────────────────────────┐
│ merge_joinir_mir_blocks() │
│ │
│ Phase 1: Inject Copy instructions │
│ Val(100) = Copy Val(5) // i │
│ Val(101) = Copy Val(10) // sum │
│ Val(102) = Copy Val(33) // start ← NEW │
│ Val(103) = Copy Val(34) // end ← NEW │
│ │
│ Phase 2: Remap JoinIR ValueIds │
│ Val(0) → Val(100) // i param │
│ Val(1) → Val(101) // sum init │
│ │
│ Phase 3: Remap condition refs │
│ Compare { lhs: Val(33), ... } │
│ ↓ NO CHANGE (uses HOST ID directly) │
│ Compare { lhs: Val(102), ... } ← FIXED │
│ │
│ Phase 4: Reconnect exit bindings │
│ variable_map["sum"] = Val(200) │
└────────────────────────────────────────────┘
✅ All ValueIds valid
```
---
### 4. Key Insight: Two Types of Inputs
This design recognizes **two distinct categories** of JoinIR inputs:
| Category | Examples | Boundary Field | Mutability | Treatment |
|----------|----------|----------------|-----------|-----------|
| **Loop Parameters** | `i` (loop var), `sum` (carrier) | `join_inputs`/`host_inputs` | Mutable | Passed as function params |
| **Condition Inputs** | `start`, `end`, `len` | `condition_inputs` | Read-only | Captured from HOST scope |
**Why separate?**
1. **Semantic clarity**: Loop parameters can be modified; condition inputs are immutable
2. **Implementation simplicity**: Condition inputs don't need JoinIR parameters - just Copy + remap
3. **Future extensibility**: May want condition-only outputs (e.g., for side-effectful conditions)
---
### 5. Implementation Strategy
**Step 1**: Modify `inline_boundary.rs`
- Add `condition_inputs` field
- Update all constructors to initialize it
- Add new constructors for condition input support
**Step 2**: Implement condition variable extraction
- Create `extract_condition_variables()` function
- Recursively traverse condition AST
- Collect unique variable names
**Step 3**: Update loop lowerers
- Call `extract_condition_variables()` on condition AST
- Look up HOST ValueIds from `builder.variable_map`
- Pass to boundary constructor
**Step 4**: Update merge logic
- Inject Copy instructions for condition inputs
- Create temporary mapping: var_name → copied_value_id
- Rewrite condition instructions to use copied ValueIds
**Step 5**: Test with trim pattern
- Should resolve ValueId(33) undefined error
- Verify condition evaluation uses correct values
---
## Remaining Questions
### Q1: Should condition inputs be remapped globally or locally?
**Answer**: **Locally** - only within JoinIR condition instructions
**Rationale**: Condition inputs are used in:
1. Loop condition evaluation (in `loop_step` function)
2. Nowhere else (by definition - they're condition-only)
So we only need to remap ValueIds in the condition instructions, not globally across all JoinIR.
### Q2: What if a condition input is ALSO a loop parameter?
**Example**: `loop(i < 10) { i = i + 1 }`
- `i` is both a loop parameter (mutated in body) AND used in condition
**Answer**: **Loop parameter takes precedence** - it's already in `join_inputs`/`host_inputs`
**Implementation**: When extracting condition variables, SKIP any that are already in `join_inputs`
```rust
fn extract_condition_variables(
condition_ast: &ASTNode,
join_inputs_names: &[String], // Already-registered parameters
) -> Vec<String> {
let all_vars = collect_variable_names_recursive(condition_ast);
all_vars.into_iter()
.filter(|name| !join_inputs_names.contains(name)) // Skip loop params
.collect()
}
```
### Q3: How to handle condition variables that don't exist in variable_map?
**Answer**: **Fail-fast with clear error**
```rust
let host_value_id = builder.variable_map.get(var_name)
.ok_or_else(|| {
format!(
"Condition variable '{}' not found in variable_map. \
Loop condition references undefined variable.",
var_name
)
})?;
```
This follows the "Fail-Fast" principle from CLAUDE.md.
---
## Summary
**Design Choice**: Option A - Extend `JoinInlineBoundary` with `condition_inputs` field
**Key Properties**:
- ✅ Minimal invasiveness (single structure change)
- ✅ Clear semantics (condition-only inputs)
- ✅ Reuses existing Copy injection mechanism
- ✅ Symmetric with `exit_bindings` design
- ✅ Handles all edge cases (overlap with loop params, missing variables)
**Next Steps**: Phase 171-3 - Implement condition variable extraction in loop lowerers
---
## References
- Phase 171-1 Analysis: `phase171-1-boundary-analysis.md`
- JoinInlineBoundary: `src/mir/join_ir/lowering/inline_boundary.rs`
- Merge Logic: `src/mir/builder/control_flow/joinir/merge/mod.rs`

View File

@ -0,0 +1,278 @@
# Phase 171-3: Register Condition Inputs in JoinIR Lowerers
**Date**: 2025-12-07
**Status**: ✅ Complete
---
## Implementation Summary
Successfully implemented the infrastructure for registering condition-only input variables in JoinIR lowerers.
---
## Changes Made
### 1. Extended `JoinInlineBoundary` Structure
**File**: `src/mir/join_ir/lowering/inline_boundary.rs`
**Added field**:
```rust
pub struct JoinInlineBoundary {
pub join_inputs: Vec<ValueId>,
pub host_inputs: Vec<ValueId>,
pub join_outputs: Vec<ValueId>,
pub host_outputs: Vec<ValueId>,
pub exit_bindings: Vec<LoopExitBinding>,
/// NEW: Condition-only input variables (Phase 171+)
pub condition_inputs: Vec<(String, ValueId)>, // [(var_name, host_value_id)]
}
```
**Rationale**: Condition variables like `start`, `end`, `len` are read-only inputs that don't participate in loop iteration but must be available in JoinIR scope for condition evaluation.
---
### 2. Updated All Constructors
**Modified constructors**:
- `new_inputs_only()` - Added `condition_inputs: vec![]`
- `new_with_outputs()` - Added `condition_inputs: vec![]`
- `new_with_input_and_host_outputs()` - Added `condition_inputs: vec![]`
- `new_with_exit_bindings()` - Added `condition_inputs: vec![]`
**New constructors**:
- `new_with_condition_inputs()` - For loops with condition variables
- `new_with_exit_and_condition_inputs()` - For loops with carriers AND condition variables
**Example usage**:
```rust
let boundary = JoinInlineBoundary::new_with_condition_inputs(
vec![ValueId(0)], // join_inputs (i)
vec![ValueId(5)], // host_inputs (i)
vec![
("start".to_string(), ValueId(33)),
("end".to_string(), ValueId(34)),
],
);
```
---
### 3. Implemented Condition Variable Extraction
**File**: `src/mir/join_ir/lowering/condition_to_joinir.rs`
**New functions**:
#### `extract_condition_variables()`
```rust
pub fn extract_condition_variables(
cond_ast: &ASTNode,
exclude_vars: &[String],
) -> Vec<String>
```
Recursively traverses condition AST and collects all unique variable names, excluding loop parameters that are already registered as join_inputs.
**Features**:
- ✅ Sorted output (BTreeSet) for determinism
- ✅ Filters excluded variables (loop parameters)
- ✅ Handles complex conditions (AND, OR, NOT chains)
#### `collect_variables_recursive()`
```rust
fn collect_variables_recursive(
ast: &ASTNode,
vars: &mut BTreeSet<String>
)
```
Helper function that recursively visits all AST nodes and extracts variable names.
**Supported AST nodes**:
- `Variable` - Adds variable name to set
- `BinaryOp` - Recursively processes left and right operands
- `UnaryOp` - Recursively processes operand
- `Literal` - No variables (skipped)
---
### 4. Added Comprehensive Tests
**File**: `src/mir/join_ir/lowering/condition_to_joinir.rs`
**Test cases**:
#### `test_extract_condition_variables_simple()`
```rust
// AST: start < end
let vars = extract_condition_variables(&ast, &[]);
assert_eq!(vars, vec!["end", "start"]); // Sorted
```
#### `test_extract_condition_variables_with_exclude()`
```rust
// AST: i < end
let vars = extract_condition_variables(&ast, &["i".to_string()]);
assert_eq!(vars, vec!["end"]); // 'i' excluded
```
#### `test_extract_condition_variables_complex()`
```rust
// AST: start < end && i < len
let vars = extract_condition_variables(&ast, &["i".to_string()]);
assert_eq!(vars, vec!["end", "len", "start"]); // Sorted, 'i' excluded
```
---
### 5. Updated Test Code
**File**: `src/mir/builder/control_flow/joinir/patterns/exit_binding.rs`
**Fixed test**:
```rust
let mut boundary = JoinInlineBoundary {
host_inputs: vec![],
join_inputs: vec![],
host_outputs: vec![],
join_outputs: vec![],
exit_bindings: vec![], // Phase 171: Added
condition_inputs: vec![], // Phase 171: Added
};
```
---
## Design Decisions
### Why Separate `condition_inputs` from `join_inputs`?
| Aspect | Loop Parameters (`join_inputs`) | Condition Inputs (`condition_inputs`) |
|--------|--------------------------------|--------------------------------------|
| **Mutability** | Mutable (e.g., `i = i + 1`) | Read-only (never modified) |
| **Lifetime** | Entire loop duration | Only during condition evaluation |
| **JoinIR representation** | Function parameters | Captured values |
| **Example** | `i` in `loop(i < 3)` | `end` in `loop(i < end)` |
**Semantic clarity**: Separating them makes the intent explicit and prevents accidental mutation of condition-only variables.
### Why Use `Vec<(String, ValueId)>` Instead of `HashMap`?
**Chosen**: `Vec<(String, ValueId)>`
**Alternative**: `HashMap<String, ValueId>`
**Rationale**:
1. **Order preservation**: Vec maintains insertion order for deterministic behavior
2. **Small size**: Typically 0-3 variables, Vec is more efficient than HashMap
3. **Iteration simplicity**: Direct iteration without collecting keys
4. **Consistency**: Matches pattern of `exit_bindings`
---
## Build Status
**Library build**: Successful (0 errors, 57 warnings)
**Struct extension**: All constructors updated
**Tests**: 3 new tests added for extraction function
**Backward compatibility**: All existing code still compiles
**Build command**:
```bash
cargo build --release
```
**Result**:
```
Finished `release` profile [optimized] target(s) in 0.07s
```
---
## What's Still Missing
### Not Yet Implemented (Phase 171-4 scope):
1. **Condition variable registration in loop lowerers**
- `loop_with_break_minimal.rs` needs to call `extract_condition_variables()`
- `loop_with_continue_minimal.rs` needs to call `extract_condition_variables()`
- Pass extracted variables to boundary constructor
2. **ValueId mapping in merge logic**
- `merge_joinir_mir_blocks()` needs to inject Copy instructions for condition inputs
- Instruction rewriter needs to remap condition variable ValueIds
3. **Test with actual failing case**
- `test_trim_main_pattern.hako` still fails (ValueId(33) undefined)
- Need to apply the infrastructure to actual loop lowerers
---
## Next Steps
**Phase 171-4**: Implement condition input mapping in merge/reconnect logic
**Priority tasks**:
1. Modify `loop_with_break_minimal.rs` to extract and register condition variables
2. Update `merge_joinir_mir_blocks()` to handle `condition_inputs`
3. Inject Copy instructions for condition variables
4. Remap ValueIds in condition evaluation instructions
5. Test with `test_trim_main_pattern.hako`
---
## Technical Notes
### Extraction Algorithm Complexity
**Time complexity**: O(n) where n = number of AST nodes in condition
**Space complexity**: O(v) where v = number of unique variables
**Determinism guarantee**: BTreeSet ensures sorted output regardless of traversal order
### Edge Cases Handled
1. **No condition variables**: Returns empty vector
2. **Loop variable in condition**: Properly excluded (e.g., `loop(i < 10)`)
3. **Duplicate variables**: BTreeSet automatically deduplicates
4. **Nested expressions**: Recursion handles arbitrary depth
### Boundary Contract
**Invariant**: `condition_inputs` contains ONLY variables that:
1. Appear in the loop condition AST
2. Are NOT loop parameters (not in `join_inputs`)
3. Exist in HOST `variable_map` at lowering time
**Violation detection**: Will be caught in Phase 171-4 when looking up HOST ValueIds
---
## Files Modified
1. `src/mir/join_ir/lowering/inline_boundary.rs` (+93 lines)
- Added `condition_inputs` field
- Added 2 new constructors
- Updated 4 existing constructors
- Updated test assertions
2. `src/mir/join_ir/lowering/condition_to_joinir.rs` (+180 lines)
- Added `extract_condition_variables()` function
- Added `collect_variables_recursive()` helper
- Added 3 comprehensive tests
3. `src/mir/builder/control_flow/joinir/patterns/exit_binding.rs` (+2 lines)
- Fixed test boundary initialization
**Total**: +275 lines (infrastructure only, no behavior change yet)
---
## References
- Phase 171-1 Analysis: `phase171-1-boundary-analysis.md`
- Phase 171-2 Design: `phase171-2-condition-inputs-design.md`
- JoinIR Design: `docs/development/current/main/phase33-10-if-joinir-design.md`

View File

@ -0,0 +1,299 @@
# Phase 171: JoinIR → MIR ValueId Boundary Mapping Fix - Completion Report
**Date**: 2025-12-07
**Status**: ✅ Infrastructure Complete (Testing in Progress)
---
## Executive Summary
Successfully implemented the infrastructure to map condition-only input variables across the JoinIR → MIR boundary. This fixes the root cause of "undefined ValueId" errors for variables like `start`, `end`, `len` that appear only in loop conditions.
**Problem Solved**: Variables used only in loop conditions (not loop parameters) were using HOST ValueIds directly in JoinIR, causing undefined value errors when merged into MIR.
**Solution Implemented**: Extended `JoinInlineBoundary` with `condition_inputs` field and updated the entire merge pipeline to collect, remap, and inject Copy instructions for these variables.
---
## Implementation Phases Completed
### ✅ Phase 171-1: Boundary Coverage Analysis
**Deliverable**: `phase171-1-boundary-analysis.md` (documented current state)
**Key Findings**:
- Loop variables (`i`): ✅ Properly mapped via `join_inputs`
- Carriers (`sum`, `count`): ✅ Properly mapped via `exit_bindings`
- **Condition inputs (`start`, `end`)**: ❌ NOT MAPPED → **ROOT CAUSE**
### ✅ Phase 171-2: Design Decision
**Deliverable**: `phase171-2-condition-inputs-design.md`
**Decision**: **Option A - Extend JoinInlineBoundary**
Added field:
```rust
pub condition_inputs: Vec<(String, ValueId)>
```
**Rationale**:
1. Minimal invasiveness
2. Clear semantics
3. Reuses existing Copy injection mechanism
4. Future-proof design
### ✅ Phase 171-3: Infrastructure Implementation
**Deliverable**: `phase171-3-implementation-report.md`
**Files Modified**:
1. **`inline_boundary.rs`** (+93 lines)
- Added `condition_inputs` field
- Added 2 new constructors
- Updated 4 existing constructors
2. **`condition_to_joinir.rs`** (+180 lines)
- Implemented `extract_condition_variables()`
- Added recursive AST traversal
- Added 3 comprehensive tests
3. **`exit_binding.rs`** (+2 lines)
- Fixed test boundary initialization
**Build Status**: ✅ Successful (0 errors, 57 warnings)
### ✅ Phase 171-4: Merge Logic Integration
**Files Modified**:
1. **`pattern2_with_break.rs`** (+19 lines)
- Extract condition variables from AST
- Look up HOST ValueIds
- Pass to boundary constructor
2. **`merge/mod.rs`** (+13 lines)
- Add condition_inputs to used_values for remapping
- Debug logging for condition inputs
3. **`merge/instruction_rewriter.rs`** (+17 lines)
- Add condition_inputs to value_map_for_injector
- Enable remapping of HOST ValueIds
4. **`joinir_inline_boundary_injector.rs`** (+35 lines)
- Inject Copy instructions for condition inputs
- Handle both join_inputs and condition_inputs
**Build Status**: ✅ Successful (0 errors, 57 warnings)
---
## Technical Changes Summary
### Data Flow: Before vs After
#### Before (Broken)
```
HOST: start = ValueId(33)
↓ condition_to_joinir reads directly
JoinIR: uses ValueId(33) in Compare
↓ NO BOUNDARY, NO REMAP
MIR: ValueId(33) undefined → ERROR ❌
```
#### After (Fixed)
```
HOST: start = ValueId(33)
↓ pattern2_with_break extracts "start"
↓ boundary.condition_inputs = [("start", ValueId(33))]
↓ merge adds to used_values
↓ remap_values: ValueId(33) → ValueId(100)
↓ BoundaryInjector: Copy ValueId(100) = Copy ValueId(33)
JoinIR: uses ValueId(33) in Compare
↓ instruction_rewriter remaps to ValueId(100)
MIR: ValueId(100) = Copy ValueId(33) → SUCCESS ✅
```
### Key Implementation Points
**1. Condition Variable Extraction** (`condition_to_joinir.rs:326-361`)
```rust
pub fn extract_condition_variables(
cond_ast: &ASTNode,
exclude_vars: &[String], // Loop parameters to exclude
) -> Vec<String>
```
- Recursively traverses condition AST
- Collects unique variable names
- Filters out loop parameters
- Returns sorted list (BTreeSet for determinism)
**2. Boundary Registration** (`pattern2_with_break.rs:73-92`)
```rust
let condition_var_names = extract_condition_variables(condition, &[loop_var_name.clone()]);
let mut condition_inputs = Vec::new();
for var_name in &condition_var_names {
let host_value_id = self.variable_map.get(var_name)
.copied()
.ok_or_else(|| ...)?;
condition_inputs.push((var_name.clone(), host_value_id));
}
```
**3. Value Collection** (`merge/mod.rs:80-91`)
```rust
// Add condition_inputs to used_values for remapping
if let Some(boundary) = boundary {
for (var_name, host_value_id) in &boundary.condition_inputs {
used_values.insert(*host_value_id);
}
}
```
**4. Value Remapping** (`merge/instruction_rewriter.rs:402-415`)
```rust
// Add condition_inputs to value_map
for (var_name, host_value_id) in &boundary.condition_inputs {
if let Some(remapped) = remapper.get_value(*host_value_id) {
value_map_for_injector.insert(*host_value_id, remapped);
}
}
```
**5. Copy Injection** (`joinir_inline_boundary_injector.rs:86-116`)
```rust
// Inject Copy instructions for condition_inputs
for (var_name, host_value_id) in &boundary.condition_inputs {
if let Some(&remapped_value) = value_map.get(host_value_id) {
let copy_inst = MirInstruction::Copy {
dst: remapped_value,
src: *host_value_id,
};
copy_instructions.push(copy_inst);
}
}
```
---
## Test Status
### ✅ Unit Tests Pass
**Extraction function tests** (`condition_to_joinir.rs`):
- `test_extract_condition_variables_simple`
- `test_extract_condition_variables_with_exclude`
- `test_extract_condition_variables_complex`
### 🔄 Integration Tests (In Progress)
**Test File**: `local_tests/test_trim_main_pattern.hako`
**Current Status**: Still shows undefined ValueId errors:
```
[ssa-undef-debug] fn=TrimTest.trim/1 bb=BasicBlockId(8) inst_idx=0 used=ValueId(33)
[ssa-undef-debug] fn=TrimTest.trim/1 bb=BasicBlockId(17) inst_idx=0 used=ValueId(49)
```
**Next Debugging Steps**:
1. Add debug output to Pattern 2 lowerer to confirm condition variable extraction
2. Verify boundary condition_inputs are populated
3. Check value remapping in merge logic
4. Verify Copy instruction injection
---
## Remaining Work
### Phase 171-5: Verification & Documentation
**Tasks**:
1. ✅ Unit tests complete
2. 🔄 Integration tests (debugging in progress)
3. ⏳ Update CURRENT_TASK.md
4. ⏳ Final documentation
**Blocker**: Need to debug why ValueId(33) is still undefined despite infrastructure being in place.
**Hypothesis**: The issue might be:
- Condition variable extraction not running (check debug output)
- Boundary not being passed correctly
- Value remapping not applying to all instruction types
- Copy instructions not being injected
---
## Files Modified (Summary)
| File | Lines Changed | Purpose |
|------|--------------|---------|
| `inline_boundary.rs` | +93 | Add condition_inputs field + constructors |
| `condition_to_joinir.rs` | +180 | Extract condition variables from AST |
| `exit_binding.rs` | +2 | Fix test |
| `pattern2_with_break.rs` | +19 | Register condition inputs |
| `merge/mod.rs` | +13 | Add to used_values |
| `merge/instruction_rewriter.rs` | +17 | Add to value_map |
| `joinir_inline_boundary_injector.rs` | +35 | Inject Copy instructions |
| **Total** | **+359 lines** | **Complete infrastructure** |
---
## Design Principles Applied
1. **Box-First Philosophy**
- Clean separation: JoinIR frontier, boundary metadata, merge logic
- Each component has single responsibility
2. **Fail-Fast**
- Explicit error when condition variable not in variable_map
- No silent fallbacks
3. **Determinism**
- BTreeSet for sorted variable names
- Consistent iteration order
4. **80/20 Rule**
- Infrastructure first (80%)
- Debugging and edge cases (20% - in progress)
---
## Known Limitations
1. **Pattern 1/3/4 Not Yet Updated**
- Only Pattern 2 (loop_with_break) currently implements condition input extraction
- Other patterns will need similar updates
2. **No Condition-Only Outputs**
- Current design handles inputs only
- Outputs would need similar treatment (future extension point)
3. **Manual Debugging Required**
- Integration tests not yet passing
- Need to trace execution to find where mapping breaks down
---
## Next Steps
**Immediate**:
1. Add debug output to confirm condition variable extraction runs
2. Verify boundary.condition_inputs is populated
3. Check if Copy instructions are actually injected
4. Trace ValueId remapping through merge pipeline
**Follow-up**:
1. Update Pattern 1/3/4 lowerers with same infrastructure
2. Add integration tests for each pattern
3. Document edge cases and limitations
4. Update CURRENT_TASK.md with completion status
---
## References
- Phase 171-1 Analysis: `phase171-1-boundary-analysis.md`
- Phase 171-2 Design: `phase171-2-condition-inputs-design.md`
- Phase 171-3 Implementation: `phase171-3-implementation-report.md`
- Phase 170 Background: `phase170-completion-report.md`