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,5 +1,517 @@
# Current Task # Current Task
(ざっくり現状サマリは `docs/development/current/main/10-Now.md` を参照。
ここには「直近フェーズの詳細」と「次にやる候補」だけを書く方針だよ。)
## 🔬 Phase 170: JsonParserBox JoinIR Preparation & Re-validation (In Progress - 2025-12-07)
**Status**: ⚠️ **In Progress** - Environment setup complete, ValueId boundary issue identified
### Goal
Prepare the environment and re-validate JsonParserBox with the newly integrated BoolExprLowerer (Phase 167-169), documenting what works and what blockers remain.
### Progress Summary
#### Phase 170-A: Environment Setup ✅
**Task 170-A-1: JoinIR Routing Whitelist Expansion**
- **Modified**: `src/mir/builder/control_flow/joinir/routing.rs`
- **Added entries**:
- `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
- `TrimTest.trim/1` → true (test helper)
- **Result**: Methods now route to JoinIR pattern matching instead of `[joinir/freeze]` with "no target"
**Task 170-A-2: ValueId Boundary Issue Identification** ⚠️
- **Test file**: `local_tests/test_trim_main_pattern.hako`
- **Pattern matched**: Pattern2 (Loop with Break) - twice for 2 loops
- **Issue identified**: ValueId boundary mapping problem
- Condition values (`start < end`, `end > start`) use undefined ValueIds
- Example: `ValueId(33), ValueId(34), ValueId(48), ValueId(49)` are undefined
- Error shown via `[ssa-undef-debug]` traces
- Compare instructions reference these undefined values
- PHI nodes also reference undefined carrier values
- **Impact**: Program compiles but produces no output (silent failure)
- **Location**: Likely in `src/mir/builder/control_flow.rs` merge logic or condition_to_joinir boundary handling
#### Phase 170-B: JsonParserBox Mini Test Re-execution (Blocked)
**Blocker**: Original test files require `using` statement support
- Test files: `tools/selfhost/json_parser_{string,array,object}_min.hako`
- Error: `[using] not found: 'tools/hako_shared/json_parser.hako" with JsonParserBox'`
- Root cause: JsonParserBox not compiled/loaded by VM at runtime
**Workaround**: Created simplified test without dependencies
- File: `local_tests/test_trim_main_pattern.hako`
- Contains same loop structure as `JsonParserBox._trim`
- Successfully routes to Pattern2 ✅
- Fails silently at runtime due to boundary issue ⚠️
### Test Results Matrix
| Method/Test | Pattern | JoinIR Status | Blocker | Notes |
|-------------|---------|---------------|---------|-------|
| `TrimTest.trim/1` (loop 1) | Pattern2 | ⚠️ Routes OK, runtime silent fail | ValueId boundary | `start < end` condition uses undefined ValueId(33, 34) |
| `TrimTest.trim/1` (loop 2) | Pattern2 | ⚠️ Routes OK, runtime silent fail | ValueId boundary | `end > start` condition uses undefined ValueId(48, 49) |
| `JsonParserBox._trim/1` | (untested) | - | Using statement | Can't load JsonParserBox |
| `JsonParserBox._skip_whitespace/2` | (untested) | - | Using statement | Can't load JsonParserBox |
| Other JsonParser methods | (untested) | - | Using statement | Can't load JsonParserBox |
### Technical Findings
#### ValueId Boundary Mapping Issue (Critical)
**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) }
```
**Root Cause Analysis Needed**:
- [ ] Check `condition_to_joinir.rs` boundary value mapping
- [ ] Check `merge_joinir_mir_blocks()` ValueId remapping logic
- [ ] Check `inline_boundary.rs` implementation
- [ ] Verify BoolExprLowerer's ValueId generation vs. builder's ValueId space
**Impact**: All JsonParserBox methods using complex conditions will fail silently
### Files Modified
- `src/mir/builder/control_flow/joinir/routing.rs` (+8 lines, whitelist expansion)
- `local_tests/test_trim_main_pattern.hako` (+48 lines, new test file)
### Next Steps (Phase 170-C)
**Immediate TODOs** (Phase 171+ candidates):
1. **Fix ValueId Boundary Mapping** (HIGHEST PRIORITY)
- Root cause: condition_to_joinir boundary value remapping
- Fix location: merge infrastructure or boundary inline logic
- Blocks: All JsonParserBox complex condition tests
2. **Using Statement / Box Loading**
- JsonParserBox needs to be compiled and registered before use
- Consider: static box auto-compilation from `using` statements?
- Alternative: test with inline JsonParser code instead
3. **Multi-Loop Function Support**
- `_trim` has 2 loops → need to handle multiple JoinIR calls per function
- Current routing might only handle one loop per function
**Recommended Next Phase Direction**: **Option A - Fix boundary mapping first**
**Rationale**:
- Boundary issue blocks ALL JsonParser tests, not just one
- BoolExprLowerer is implemented and integrated correctly
- Patterns are matching correctly
- The only blocker is the ValueId remapping between JoinIR and main builder context
- Once fixed, we can re-run all JsonParser tests and see real results
---
## 🚀 Phase 168: BoolExprLowerer Implementation (Complete - 2025-12-06)
**Status**: ✅ **Complete** - BoolExprLowerer 実装完了!複雑な条件式を SSA 形式に変換可能!
### Goal
Phase 167 で設計した BoolExprLowerer を実装し、`_trim` および `_skip_whitespace` の複雑な OR chain 条件式を処理可能にする。
### Implementation Summary
1. **BoolExprLowerer Module Created**
- File: `src/mir/join_ir/lowering/bool_expr_lowerer.rs`
- 436 lines of implementation + comprehensive tests
- Integrated with `mod.rs`
2. **Implemented Operators**
- **Comparisons**: `<`, `==`, `!=`, `<=`, `>=`, `>`
- **Logical**: `&&`, `||`, `!`
- **Variables and Literals**: Full delegation to MirBuilder
3. **Test Coverage**
- Simple comparison test (`i < 10`)
- OR chain test (`ch == " " || ch == "\t"`)
- Complex mixed condition (`i < len && (c == " " || c == "\t")`)
- NOT operator test (`!(i < 10)`)
4. **Architecture**
- Clean separation: BoolExprLowerer handles expressions, loop patterns handle structure
- Recursive AST traversal with ValueId return
- Proper type annotation (MirType::Bool) for all boolean results
### Files Modified/Created
- **Created**: `src/mir/join_ir/lowering/bool_expr_lowerer.rs` (436 lines)
- **Modified**: `src/mir/join_ir/lowering/mod.rs` (added module declaration)
### Usage Example
```rust
use crate::mir::join_ir::lowering::bool_expr_lowerer::BoolExprLowerer;
// In loop pattern or condition processing:
let mut bool_lowerer = BoolExprLowerer::new(builder);
let cond_val = bool_lowerer.lower_condition(&condition_ast)?;
// cond_val is a ValueId holding the boolean result (0/1)
```
### Current Status: Ready for Integration
The BoolExprLowerer is implemented and tested. It can now be integrated into actual loop patterns when they need to process complex conditions. Current loop patterns (Pattern1-4) use simple condition extraction, but future patterns or enhanced versions can use BoolExprLowerer for complex boolean expressions.
### Next Steps
- Phase 169+: Enhance BoolExprLowerer with additional operators if needed
- Integrate into JsonParserBox `_trim` / `_skip_whitespace` when those methods are ported to JoinIR
---
## 📐 Phase 167: BoolExprLowerer Design - 条件式 Lowerer 設計 (Complete - 2025-12-06)
**Status**: ✅ **Complete** - BoolExprLowerer の完全設計完了Pattern5 不要の革新的アプローチ確立!
### Goal
ループ骨格Pattern1-4を維持しつつ、複雑な論理式条件を別箱BoolExprLowererで処理する設計を確立。
### Design Philosophy: 箱理論による完璧な責務分離
```
【ループ制御の箱】Pattern1-4 → 制御構造だけを見る
【条件式の箱】BoolExprLowerer → 式の構造だけを見る
```
**革新的な発見**: Pattern5 を作らない!
→ ループパターンを増やすのではなく、**BoolExprLowerer の能力を上げる**
### What Was Done
1. **対象条件式の具体化** (Task 167-1) ✅ **COMPLETE**
- `_trim` メソッド2つのループの条件式を pseudo-code で書き起こし
- `_skip_whitespace` の条件式も同様に分析
- 共通パターン発見: OR chain with 4 comparisons
```
ch == " " || ch == "\t" || ch == "\n" || ch == "\r"
```
- AST 構造と期待する SSA/JoinIR 形を対で文書化
2. **BoolExprLowerer API 設計** (Task 167-2) ✅ **COMPLETE**
- モジュール配置決定: `src/mir/join_ir/lowering/bool_expr_lowerer.rs`
- API シグネチャ確定:
```rust
pub struct BoolExprLowerer<'a> {
builder: &'a mut MirBuilder,
}
impl<'a> BoolExprLowerer<'a> {
pub fn lower_condition(&mut self, cond_ast: &ASTNode) -> ValueId;
}
```
- Phase 167 対応範囲:
- ✅ Simple comparison (pass-through to existing logic)
- ✅ Logical OR chain (new logic)
- 🔜 Logical AND (Phase 168+)
- 🔜 Logical NOT (Phase 169+)
- エラーハンドリング戦略確立
- テスト戦略Unit + Integration定義
3. **LoopLowerer との分業明文化** (Task 167-3) ✅ **COMPLETE**
- 責務境界の完全定義:
- Loop Pattern Boxes: 制御構造のみbreak/continue/PHI/ExitBinding
- BoolExprLowerer: 式のみOR/AND/NOT/比較演算)
- インターフェース契約: `lower_condition(&ast) -> ValueId`
- Pattern2 統合例:
```rust
let mut bool_lowerer = BoolExprLowerer::new(self.builder);
let cond_val = bool_lowerer.lower_condition(&ctx.condition);
// Pattern2 はこの ValueId を使ってループ構造を構築
```
- **Pattern5 不要の証明**:
- 間違ったアプローチ: Pattern5_ComplexCondition を追加(❌)
- 正しいアプローチ: BoolExprLowerer を拡張(✅)
- Call graph 図解(誰が誰を呼ぶか)
4. **ドキュメント更新** (Task 167-4) ✅ **COMPLETE**
- `phase167_boolexpr_lowerer_design.md` 作成850+ lines
- CURRENT_TASK.md に Phase 167 セクション追加
### Key Findings
| Component | Responsibility | Integration Point |
|-----------|---------------|-------------------|
| **BoolExprLowerer** | AST → SSA (expressions only) | Returns `ValueId` |
| **Pattern1-4** | Loop structure (control flow) | Calls `lower_condition()` |
| **Separation Benefit** | Each box testable in isolation | No Pattern5 needed! |
### Design Document Highlights
**File**: `docs/development/current/main/phase167_boolexpr_lowerer_design.md`
**Contents**:
- Task 167-1: 対象条件式の具体化(`_trim`, `_skip_whitespace` 分析)
- Task 167-2: API 設計(構造体、メソッド、エラーハンドリング、テスト戦略)
- Task 167-3: 分業明文化責務境界、インターフェース契約、Pattern5 不要の証明)
- Condition Expression Taxonomy対応する式の分類
- AST → SSA 変換例OR chain の具体例)
- Success Criteria各タスクの完了基準
### Architectural Innovation: No Pattern5!
**従来の(誤った)アプローチ**:
```
Pattern5_ComplexCondition を追加
→ Pattern2 のロジックをコピー&ペースト
→ 複雑条件だけ違う処理
→ 保守性悪化、重複コード増加
```
**新しい(正しい)アプローチ**:
```
Pattern2 は変更なし(条件 lowering を委譲するだけ)
BoolExprLowerer の能力を上げる:
Phase 167: OR chain
Phase 168: AND
Phase 169: NOT
Phase 170: Mixed AND/OR
→ ループパターンはそのまま
→ 式の箱だけ拡張
→ 関心の分離完璧!
```
### Integration Strategy
**Before (Pattern2 handles everything)**:
```rust
fn lower_pattern2(&mut self, ctx: &LoopContext) {
match &ctx.condition {
BinOp { op: "<", .. } => { /* ... */ },
BinOp { op: "||", .. } => {
// ❌ Pattern2 が OR ロジックを理解する必要がある
},
// ❌ パターンごとに式処理が重複
}
}
```
**After (Delegation to BoolExprLowerer)**:
```rust
fn lower_pattern2(&mut self, ctx: &LoopContext) {
// ✅ 条件式処理は専門箱に委譲
let cond = BoolExprLowerer::new(self.builder)
.lower_condition(&ctx.condition)?;
// ✅ Pattern2 はループ構造だけを扱う
self.build_loop_structure(cond);
}
```
### Success Criteria
✅ **All Criteria Met**:
1. 対象条件式の具体化(`_trim` の OR chain 分析完了)
2. API 設計完了(`lower_condition` シグネチャ確定)
3. 責務分離明文化Loop vs Expression 境界明確)
4. Pattern5 不要の証明(設計文書に記載)
5. テスト戦略確立Unit + Integration
6. ドキュメント完備850+ lines design doc
### Next Steps (Phase 168)
**Goal**: BoolExprLowerer 実装フェーズ
**Tasks**:
1. `bool_expr_lowerer.rs` ファイル作成
2. OR chain lowering 実装
3. Pattern2_WithBreak との統合
4. `_trim` と `_skip_whitespace` で実証
**Expected Outcome**:
- `_trim` の `[joinir/freeze]` エラーが解消
- JsonParserBox の 3 メソッド(`_trim`×2, `_skip_whitespace`)が JoinIR で動作
- Phase 166 で作成した unit tests が実行可能に
### Files Created
| File | Lines | Description |
|------|-------|-------------|
| `phase167_boolexpr_lowerer_design.md` | 850+ | 完全設計ドキュメント |
### Technical Achievement
**箱理論の完璧な実践例**:
- ✅ 単一責務: 各箱が1つの関心事だけを持つ
- ✅ 明確な境界: `lower_condition(&ast) -> ValueId` インターフェース
- ✅ 拡張性: 新しい式を追加しても既存ループコードに影響なし
- ✅ テスタビリティ: 式ロジックを独立してテスト可能
- ✅ 保守性: ループと式が分離され、変更の影響範囲が限定
**ChatGPT との協働設計の成果**:
- ChatGPT: ultrathink による「式も箱にする」という発想
- Claude (me): 設計文書の詳細化、責務分離の明文化、Pattern5 不要の証明
- 結果: 完璧な箱理論設計、実装準備完了!
---
## 🔬 Phase 166: JsonParserBox Unit Test Validation (Validation Complete - 2025-12-06)
**Status**: ⚠️ **Partially Complete** - Simple patterns work, OR chains blocked by hardcoded conditions
**Detailed Report**: `docs/development/current/main/phase166-validation-report.md`
### Goal
Validate JsonParserBox can parse JSON through JoinIR path, confirming Pattern1-4 support and identifying any remaining unsupported patterns for Phase 167+.
### What Was Done
1. **JsonParserBox Unit Test Setup** (Task 166-1) ✅ **COMPLETE**
- Created 3 minimal unit tests:
- `json_parser_string_min.hako` - Simple string parsing ("hello", escapes, empty string)
- `json_parser_array_min.hako` - Array parsing ([1,2,3], ["hello","world"], [])
- `json_parser_object_min.hako` - Object parsing (simple, nested, empty)
- All tests use `using` statement to import JsonParserBox from `tools/hako_shared/json_parser.hako`
- Tests validate correct parsing with assertions
2. **JoinIR Path Validation** (Task 166-2) ✅ **COMPLETE**
- **Pattern1-4 Confirmed Working**:
- ✅ Pattern2_WithBreak: `test_pattern2_search.hako` → Pattern2 MATCHED
- ✅ Pattern3_WithIfPhi: `test_pattern3_if_phi_no_break.hako` → Pattern3 MATCHED
- ✅ Pattern4_WithContinue: `test_pattern4_simple_continue.hako` → Pattern4 MATCHED
- All patterns execute correctly with expected output
- **Zero `[joinir/freeze]` errors** for Pattern1-4 tests
- **JsonParserBox Discovery**:
- ❌ `JsonParserBox._trim/1` encounters `[joinir/freeze]` error
- Error: "Loop lowering failed: JoinIR does not support this pattern"
- Pattern analysis:
- `_trim` has TWO loops in one function (leading/trailing whitespace)
- Loop condition uses complex OR: `ch == " " || ch == "\t" || ch == "\n" || ch == "\r"`
- Loop body: if-else with PHI (`start = start + 1`) + break
- Conclusion: **JsonParserBox requires additional pattern support beyond Pattern1-4**
3. **Program JSON v0 Integration** (Task 166-3) ⏸️ **SKIPPED**
- Skipped due to JsonParserBox `[joinir/freeze]` blocker
- Cannot proceed with full JSON parsing until `_trim` pattern is supported
4. **Documentation Update** (Task 166-4) 🔄 **IN PROGRESS**
- This CURRENT_TASK.md update
### Key Findings
| Component | Status | Details |
|-----------|--------|---------|
| Pattern1-4 JoinIR | ✅ **WORKING** | All simplified patterns execute correctly |
| JsonParserBox._trim | ❌ **BLOCKED** | Unsupported: multiple loops + complex OR conditions |
| Unit Test Infrastructure | ✅ **COMPLETE** | 3 tests ready for when JsonParserBox is supported |
| [joinir/freeze] in Pattern1-4 | ✅ **ZERO ERRORS** | Complete elimination confirmed |
### JsonParserBox Loop Pattern Inventory
Based on `phase161_jsonparser_loop_inventory.md`:
| Method | Pattern | JoinIR Status | Blocker |
|--------|---------|---------------|---------|
| `_match_literal` | Pattern1 (Simple + return) | ❓ Untested | Needs validation |
| `_skip_whitespace` | Pattern2/3 (If-else + break) | ❓ Untested | May work, needs validation |
| **`_trim` (leading)** | **Pattern2/3 (OR condition + break)** | **❌ BLOCKED** | **`[joinir/freeze]`** |
| **`_trim` (trailing)** | **Pattern2/3 (OR condition + break)** | **❌ BLOCKED** | **`[joinir/freeze]`** |
| `_parse_string` | Pattern4 (Continue + return) | ❓ Untested | Needs validation |
| `_parse_array` | Pattern4 (Continue + multi-return) | ❓ Untested | Complex, needs validation |
| `_parse_object` | Pattern4 (Continue + multi-return) | ❓ Untested | Complex, needs validation |
### Files Created
| File | Description |
|------|-------------|
| `tools/selfhost/json_parser_string_min.hako` | String parsing unit test (3 cases) |
| `tools/selfhost/json_parser_array_min.hako` | Array parsing unit test (3 cases) |
| `tools/selfhost/json_parser_object_min.hako` | Object parsing unit test (3 cases) |
### Validation Results (2025-12-06 SESSION)
**✅ Successes**:
1. **Pattern Detection Works**: Pattern 2 (break) correctly identified in `main()` function
2. **Simple JSON Parsing Works**: Non-loop patterns execute successfully
3. **BoolExprLowerer Exists**: Phase 167-168 implementation complete (436 lines)
**❌ Critical Findings**:
1. **Hardcoded Conditions**: Pattern 2/4 lowerers generate fixed `i < 3` instead of actual AST condition
2. **Function Whitelist**: JoinIR only enabled for specific function names (`main`, `JoinIrMin.main/0`, etc.)
3. **BoolExprLowerer Not Integrated**: Exists but not called by Pattern 2/4 lowerers
4. **LoopBuilder Removed**: No fallback when JoinIR patterns don't match (Phase 186-187)
**Root Cause**:
- `lower_loop_with_break_minimal()` generates **hardcoded** JoinIR for test case `i < 3`
- Actual AST condition (`start < end`, `ch == " "`) is **ignored**
- Result: Loop executes with wrong logic
### Critical Blockers
1. **Hardcoded Condition Architecture** (CRITICAL - Phase 169 Blocker)
- **File**: `src/mir/join_ir/lowering/loop_with_break_minimal.rs` (lines 171-197)
- **Problem**: Generates fixed `!(i < 3)` check, ignores AST condition
- **Impact**: All Pattern 2 loops use test case logic instead of actual code
- **Solution**: Integrate BoolExprLowerer to generate dynamic condition evaluation
- **Estimated Effort**: 2-3 hours
2. **Function Name Whitelisting** (HIGH PRIORITY - Phase 170)
- **File**: `src/mir/builder/control_flow/joinir/routing.rs` (lines 44-68)
- **Problem**: JoinIR only enabled for 4 specific function names
- **Impact**: `JsonParserBox._trim/1` returns `Ok(None)` → `[joinir/freeze]`
- **Solution**: Add `JsonParserBox.*` to whitelist or enable globally
- **Estimated Effort**: 30 minutes
3. **OR Chain Conditions** (MEDIUM - Resolved by #1)
- **Pattern**: `ch == " " || ch == "\t" || ch == "\n" || ch == "\r"`
- **Current**: Unsupported by `extract_loop_variable_from_condition()`
- **Solution**: BoolExprLowerer handles this (Phase 169 integration)
4. **`using` Statement + Box Registration** (LOW PRIORITY - Separate Issue)
- **Issue**: `using` imports JsonParserBox but Rust VM reports "Unknown Box type"
- **Workaround**: Inline JsonParserBox code in tests
- **Long-term**: Box registration mechanism needed
### Next Steps
**Phase 169: BoolExprLowerer Integration** (CRITICAL - 2-3 hours)
1. Modify `lower_loop_with_break_minimal()` to accept `condition: &ASTNode`
2. Call `BoolExprLowerer::lower_condition()` to generate condition MIR
3. Convert condition MIR to JoinIR instructions
4. Replace hardcoded `const_3`, `cmp_lt` with dynamic values
5. Repeat for Pattern 4 (`lower_loop_with_continue_minimal()`)
6. Test with `_trim` pattern (OR chains)
**Phase 170: Function Whitelist Expansion** (30 minutes)
1. Add `JsonParserBox._trim/1` to routing whitelist
2. Add `JsonParserBox._skip_whitespace/2` to whitelist
3. Test JsonParserBox methods execute via JoinIR
**Phase 171: JsonParserBox Full Validation** (1 hour)
1. Run all Phase 166 unit tests
2. Validate `_trim`, `_skip_whitespace`, `_parse_number`, etc.
3. Confirm zero `[joinir/freeze]` errors
4. Document any remaining limitations
**Estimated Total Time**: 4-5 hours to complete Phase 166 validation fully
### Test Files Created This Session
| File | Purpose | Result |
|------|---------|--------|
| `local_tests/test_json_parser_simple_string.hako` | Simple JSON test (no loops) | ✅ PASS |
| `local_tests/test_trim_or_pattern.hako` | OR chain test | ❌ `[joinir/freeze]` |
| `local_tests/test_trim_simple_pattern.hako` | Simple condition test | ❌ `[joinir/freeze]` (not whitelisted) |
| `local_tests/test_trim_main_pattern.hako` | Whitelisted function test | ⚠️ Executes but wrong logic (hardcoded `i < 3`) |
| `local_tests/test_trim_debug.hako` | Debug output test | ⚠️ Loop doesn't execute (hardcoded condition)
---
# Current Task
## ✅ Phase 165: Pattern4 (continue系) 実ループ適用フェーズ (Completed - 2025-12-06) ## ✅ Phase 165: Pattern4 (continue系) 実ループ適用フェーズ (Completed - 2025-12-06)
**Status**: ✅ **Complete** - Pattern4_WithContinue が JoinIR で完全動作!全パターン対応達成! **Status**: ✅ **Complete** - Pattern4_WithContinue が JoinIR で完全動作!全パターン対応達成!
@ -727,6 +1239,60 @@ All four loop patterns are confirmed to route via structure-based detection and
--- ---
## ✅ Phase 167: BoolExprLowerer Design - 条件式 Lowerer 設計Completed - 2025-12-06
**Status**: ✅ 完了 ループ制御Pattern1-4と条件式 Lowering の責務分離を設計レベルで確立。Pattern5 を増やさず、BoolExprLowerer を拡張する方針に確定。
### Goal
ループ骨格Pattern1-4を維持しつつ、複雑な論理式条件特に JsonParserBox `_trim` 系の OR チェーン)を別箱で処理する設計を固める。
### What Was Done
1. **対象条件式の具体化Task 167-1**
- JsonParserBox の `_trim` / `_skip_whitespace` で使われている条件式を詳細に分析。
- 例:
```hako
loop (start < end) {
local ch = s.substring(start, start+1)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
start = start + 1
} else {
break
}
}
```
- `ch == " " || ch == "\t" || ch == "\n" || ch == "\r"` の OR チェーン構造を AST レベルで明文化。
2. **BoolExprLowerer の API 設計Task 167-2**
- 新箱 `BoolExprLowerer` のインターフェースを定義:
- `lower_condition(cond_ast: &ASTNode) -> ValueId` という薄い API に統一。
- モジュール配置:
- `src/mir/join_ir/lowering/bool_expr_lowerer.rs` として JoinIR lowering 層に置く方針を決定。
3. **LoopLowerer との分業の明文化Task 167-3**
- ループパターン箱Pattern1〜4は:
- break / continue / PHI / ExitBinding / キャリア だけを扱う。
- 条件式の中身には踏み込まず、「cond の ValueId を 1 つ受け取るだけ」に責務を限定。
- BoolExprLowerer は:
- AND / OR / NOT / 比較演算子など、条件式の木構造だけを扱う。
- 出力は常に「0/1 bool」を表す ValueId。
- この設計により、「Pattern5 を新設せず、BoolExprLowerer の表現力を上げる」方針を採用。
4. **ドキュメント更新Task 167-4**
- `docs/development/current/main/phase167_boolexpr_lowerer_design.md` に 850 行超の詳細設計を作成。
- CURRENT_TASK.md に本セクションを追加し、設計フェーズ完了を明示。
### Impact
- ループ制御Pattern1-4と条件式 Lowering の責務が完全に分離され、
- ループパターンを増やさずに、
- 将来の複雑条件AND/OR/NOT 混在)のサポートを BoolExprLowerer 側で完結できるようになった。
- JsonParserBox `_trim` などの「複雑条件を含むループ」に対しても、
- 「ループは Pattern2/4、条件は BoolExprLowerer」という構図で攻める設計が固まった。
---
## ✅ Phase 197: Pattern 4 Update Semantics & Multi-Carrier Exit Fix (Completed - 2025-12-06) ## ✅ Phase 197: Pattern 4 Update Semantics & Multi-Carrier Exit Fix (Completed - 2025-12-06)
**Status**: ✅ **Complete** Pattern 4 の更新式を AST ベースで汎用化。`loop_continue_multi_carrier.hako` が正しく `25` `5` を出力。 **Status**: ✅ **Complete** Pattern 4 の更新式を AST ベースで汎用化。`loop_continue_multi_carrier.hako` が正しく `25` `5` を出力。

View File

@ -1,10 +1,62 @@
# Self Current Task — Now (main) # 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 参照)。 - LLVM 側 P0 完了BitOps/Array/Echo/Map 緑。VInvoke(byname/byid vector) は戻り値マッピングの暫定課題を確認中Decisions 参照)。
- selfhosting-dev の作業を main に順次取り込み。VM/MIR 基盤は main で先に整える方針。 - selfhosting-dev の作業を main に順次取り込み。VM/MIR 基盤は main で先に整える方針。
直近タスク(優先 直近タスク(当時
1) continue/break の loweringBuilder 修正のみで表現) 1) continue/break の loweringBuilder 修正のみで表現)
- ループ文脈スタック {head, exit} を導入。 - ループ文脈スタック {head, exit} を導入。
- continue に遭遇 → headまたは latchへ br を emit し終端。 - continue に遭遇 → headまたは latchへ br を emit し終端。
@ -22,4 +74,3 @@
- ビルド: `cargo build --release` - ビルド: `cargo build --release`
- LLVM smoke: `LLVM_SYS_180_PREFIX=$(llvm-config-18 --prefix) NYASH_LLVM_BITOPS_SMOKE=1 ./tools/llvm_smoke.sh 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` - 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`

View File

@ -391,12 +391,29 @@ pub(super) fn merge_and_rewrite(
// Create HashMap from remapper for BoundaryInjector (temporary adapter) // Create HashMap from remapper for BoundaryInjector (temporary adapter)
let mut value_map_for_injector = HashMap::new(); let mut value_map_for_injector = HashMap::new();
// Phase 171-fix: Add join_inputs to value_map
for join_in in &boundary.join_inputs { for join_in in &boundary.join_inputs {
if let Some(remapped) = remapper.get_value(*join_in) { if let Some(remapped) = remapper.get_value(*join_in) {
value_map_for_injector.insert(*join_in, remapped); value_map_for_injector.insert(*join_in, remapped);
} }
} }
// Phase 171-fix: Add condition_bindings to value_map
// Each binding specifies JoinIR ValueId → HOST ValueId mapping
// We remap the JoinIR ValueId to a new MIR ValueId
for binding in &boundary.condition_bindings {
if let Some(remapped) = remapper.get_value(binding.join_value) {
value_map_for_injector.insert(binding.join_value, remapped);
if debug {
eprintln!(
"[cf_loop/joinir] Phase 171-fix: Condition binding '{}': JoinIR {:?} → remapped {:?} (HOST {:?})",
binding.name, binding.join_value, remapped, binding.host_value
);
}
}
}
// Use BoundaryInjector to inject Copy instructions // Use BoundaryInjector to inject Copy instructions
if let Some(ref mut current_func) = builder.current_function { if let Some(ref mut current_func) = builder.current_function {
BoundaryInjector::inject_boundary_copies( BoundaryInjector::inject_boundary_copies(

View File

@ -74,9 +74,22 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
let mut remapper = block_allocator::allocate_blocks(builder, mir_module, debug)?; let mut remapper = block_allocator::allocate_blocks(builder, mir_module, debug)?;
// Phase 2: Collect values from all functions // Phase 2: Collect values from all functions
let (used_values, value_to_func_name, function_params) = let (mut used_values, value_to_func_name, function_params) =
value_collector::collect_values(mir_module, &remapper, debug)?; value_collector::collect_values(mir_module, &remapper, debug)?;
// Phase 171-fix: Add condition_bindings' join_values to used_values for remapping
if let Some(boundary) = boundary {
for binding in &boundary.condition_bindings {
if debug {
eprintln!(
"[cf_loop/joinir] Phase 171-fix: Adding condition binding '{}' JoinIR {:?} to used_values",
binding.name, binding.join_value
);
}
used_values.insert(binding.join_value);
}
}
// Phase 3: Remap ValueIds // Phase 3: Remap ValueIds
remap_values(builder, &used_values, &mut remapper, debug)?; remap_values(builder, &used_values, &mut remapper, debug)?;

View File

@ -385,6 +385,8 @@ mod tests {
join_inputs: vec![], join_inputs: vec![],
host_outputs: vec![], host_outputs: vec![],
join_outputs: vec![], join_outputs: vec![],
exit_bindings: vec![], // Phase 171: Add missing field
condition_inputs: vec![], // Phase 171: Add missing field
}; };
builder.apply_to_boundary(&mut boundary) builder.apply_to_boundary(&mut boundary)

View File

@ -70,6 +70,61 @@ impl MirBuilder {
// Phase 195: Use unified trace // Phase 195: Use unified trace
trace::trace().varmap("pattern2_start", &self.variable_map); trace::trace().varmap("pattern2_start", &self.variable_map);
// Phase 171-fix: Build ConditionEnv and ConditionBindings
use crate::mir::join_ir::lowering::condition_to_joinir::{
extract_condition_variables, ConditionEnv, ConditionBinding,
};
let condition_var_names = extract_condition_variables(condition, &[loop_var_name.clone()]);
let mut env = ConditionEnv::new();
let mut condition_bindings = Vec::new();
// Phase 171-fix: Add loop parameter to env (ValueId(0) in JoinIR space)
// The loop parameter is NOT a condition binding (it's a join_input instead)
env.insert(loop_var_name.clone(), crate::mir::ValueId(0));
// Create a local allocator for JoinIR-local ValueIds for condition-only variables
let mut join_value_counter = 1u32; // Start from 1 (0 is reserved for loop param)
let mut alloc_join_value = || {
let id = crate::mir::ValueId(join_value_counter);
join_value_counter += 1;
id
};
// For each condition variable, allocate JoinIR-local ValueId and build binding
for var_name in &condition_var_names {
let host_id = self.variable_map.get(var_name)
.copied()
.ok_or_else(|| {
format!(
"[cf_loop/pattern2] Condition variable '{}' not found in variable_map. \
Loop condition references undefined variable.",
var_name
)
})?;
let join_id = alloc_join_value(); // Allocate JoinIR-local ValueId
env.insert(var_name.clone(), join_id);
condition_bindings.push(ConditionBinding {
name: var_name.clone(),
host_value: host_id,
join_value: join_id,
});
}
// Phase 171-fix Debug: Log condition bindings
eprintln!("[cf_loop/pattern2] Phase 171-fix: ConditionEnv contains {} variables:", env.name_to_join.len());
eprintln!(" Loop param '{}' → JoinIR ValueId(0)", loop_var_name);
if !condition_bindings.is_empty() {
eprintln!(" {} condition-only bindings:", condition_bindings.len());
for binding in &condition_bindings {
eprintln!(" '{}': HOST {:?} → JoinIR {:?}", binding.name, binding.host_value, binding.join_value);
}
} else {
eprintln!(" No condition-only variables");
}
// Create a minimal LoopScopeShape (Phase 188: hardcoded for joinir_min_loop.hako) // Create a minimal LoopScopeShape (Phase 188: hardcoded for joinir_min_loop.hako)
// Pattern 2 lowerer ignores the scope anyway, so this is just a placeholder // Pattern 2 lowerer ignores the scope anyway, so this is just a placeholder
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
@ -86,13 +141,13 @@ impl MirBuilder {
variable_definitions: BTreeMap::new(), variable_definitions: BTreeMap::new(),
}; };
// Call Pattern 2 lowerer // Phase 169 / Phase 171-fix: Call Pattern 2 lowerer with ConditionEnv
let join_module = match lower_loop_with_break_minimal(scope) { let join_module = match lower_loop_with_break_minimal(scope, condition, &env) {
Some(module) => module, Ok(module) => module,
None => { Err(e) => {
// Phase 195: Use unified trace // Phase 195: Use unified trace
trace::trace().debug("pattern2", "Pattern 2 lowerer returned None"); trace::trace().debug("pattern2", &format!("Pattern 2 lowerer failed: {}", e));
return Ok(None); return Err(format!("[cf_loop/pattern2] Lowering failed: {}", e));
} }
}; };
@ -119,11 +174,19 @@ impl MirBuilder {
); );
// Merge JoinIR blocks into current function // Merge JoinIR blocks into current function
// Phase 188-Impl-2: Create and pass JoinInlineBoundary for Pattern 2 // Phase 171-fix: Create boundary with ConditionBindings
let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only( let boundary = if condition_bindings.is_empty() {
crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only(
vec![ValueId(0)], // JoinIR's main() parameter (loop variable init) vec![ValueId(0)], // JoinIR's main() parameter (loop variable init)
vec![loop_var_id], // Host's loop variable vec![loop_var_id], // Host's loop variable
); )
} else {
crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_with_condition_bindings(
vec![ValueId(0)], // JoinIR's main() parameter (loop variable init)
vec![loop_var_id], // Host's loop variable
condition_bindings, // Phase 171-fix: ConditionBindings with JoinIR ValueIds
)
};
// Phase 189: Discard exit PHI result (Pattern 2 returns void) // Phase 189: Discard exit PHI result (Pattern 2 returns void)
let _ = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?; let _ = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;

View File

@ -181,13 +181,13 @@ impl MirBuilder {
variable_definitions: BTreeMap::new(), variable_definitions: BTreeMap::new(),
}; };
// Call Pattern 4 lowerer (Phase 197: pass carrier_updates for semantic correctness) // Phase 169: Call Pattern 4 lowerer with condition AST
let (join_module, exit_meta) = match lower_loop_with_continue_minimal(scope, &carrier_info, &carrier_updates) { let (join_module, exit_meta) = match lower_loop_with_continue_minimal(scope, condition, self, &carrier_info, &carrier_updates) {
Some(result) => result, Ok(result) => result,
None => { Err(e) => {
// Phase 195: Use unified trace // Phase 195: Use unified trace
trace::trace().debug("pattern4", "Pattern 4 lowerer returned None"); trace::trace().debug("pattern4", &format!("Pattern 4 lowerer failed: {}", e));
return Ok(None); return Err(format!("[cf_loop/pattern4] Lowering failed: {}", e));
} }
}; };

View File

@ -40,6 +40,7 @@ impl MirBuilder {
// - Core OFF では従来通り dev フラグで opt-in // - Core OFF では従来通り dev フラグで opt-in
// Note: Arity does NOT include implicit `me` receiver // Note: Arity does NOT include implicit `me` receiver
// Phase 188: Add "main" routing for loop pattern expansion // Phase 188: Add "main" routing for loop pattern expansion
// Phase 170: Add JsonParserBox methods for selfhost validation
let core_on = crate::config::env::joinir_core_enabled(); let core_on = crate::config::env::joinir_core_enabled();
let is_target = match func_name.as_str() { let is_target = match func_name.as_str() {
"main" => true, // Phase 188-Impl-1: Enable JoinIR for main function (Pattern 1) "main" => true, // Phase 188-Impl-1: Enable JoinIR for main function (Pattern 1)
@ -64,6 +65,16 @@ impl MirBuilder {
== Some("1") == Some("1")
} }
} }
// 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,
"Main.trim/1" => true, // Phase 171-fix: Main box variant
_ => false, _ => false,
}; };

View File

@ -33,15 +33,18 @@ impl BoundaryInjector {
value_map: &HashMap<ValueId, ValueId>, value_map: &HashMap<ValueId, ValueId>,
debug: bool, debug: bool,
) -> Result<(), String> { ) -> Result<(), String> {
// Boundary が空の場合はスキップ // Phase 171-fix: Check both join_inputs and condition_bindings
if boundary.join_inputs.is_empty() { let total_inputs = boundary.join_inputs.len() + boundary.condition_bindings.len();
if total_inputs == 0 {
return Ok(()); return Ok(());
} }
if debug { if debug {
eprintln!( eprintln!(
"[BoundaryInjector] Injecting {} Copy instructions at entry block {:?}", "[BoundaryInjector] Phase 171-fix: Injecting {} Copy instructions ({} join_inputs, {} condition_bindings) at entry block {:?}",
total_inputs,
boundary.join_inputs.len(), boundary.join_inputs.len(),
boundary.condition_bindings.len(),
entry_block_id entry_block_id
); );
} }
@ -54,6 +57,7 @@ impl BoundaryInjector {
// Copy instructions を生成して挿入 // Copy instructions を生成して挿入
let mut copy_instructions = Vec::new(); let mut copy_instructions = Vec::new();
// Phase 171: Inject Copy instructions for join_inputs (loop parameters)
for (join_input, host_input) in boundary for (join_input, host_input) in boundary
.join_inputs .join_inputs
.iter() .iter()
@ -73,12 +77,36 @@ impl BoundaryInjector {
if debug { if debug {
eprintln!( eprintln!(
"[BoundaryInjector] Copy {:?} = Copy {:?}", "[BoundaryInjector] Join input: Copy {:?} = Copy {:?}",
remapped_join, remapped_host remapped_join, remapped_host
); );
} }
} }
// Phase 171-fix: Inject Copy instructions for condition_bindings (condition-only variables)
// These variables are read-only and used ONLY in the loop condition.
// Each binding explicitly specifies HOST ValueId → JoinIR ValueId mapping.
// We inject Copy: remapped_join_value = Copy host_value
for binding in &boundary.condition_bindings {
// Look up the remapped JoinIR ValueId from value_map
let remapped_join = value_map.get(&binding.join_value).copied().unwrap_or(binding.join_value);
// Copy instruction: remapped_join_value = Copy host_value
let copy_inst = MirInstruction::Copy {
dst: remapped_join,
src: binding.host_value,
};
copy_instructions.push(copy_inst);
if debug {
eprintln!(
"[BoundaryInjector] Condition binding '{}': Copy {:?} = Copy {:?} (JoinIR {:?} → remapped {:?})",
binding.name, remapped_join, binding.host_value, binding.join_value, remapped_join
);
}
}
// Entry block の先頭に Copy instructions を挿入 // Entry block の先頭に Copy instructions を挿入
// Reverse order to preserve original order when inserting at position 0 // Reverse order to preserve original order when inserting at position 0
// Phase 189 FIX: Also insert corresponding spans // Phase 189 FIX: Also insert corresponding spans

View File

@ -0,0 +1,370 @@
//! Phase 168: BoolExprLowerer - Boolean Expression Lowering to SSA
//!
//! This module provides lowering of complex boolean expressions (AST → SSA)
//! for use within JoinIR loop patterns. It handles:
//! - Comparisons: `<`, `==`, `!=`, `<=`, `>=`, `>`
//! - Logical operators: `&&`, `||`, `!`
//! - Mixed conditions: `ch == " " || ch == "\t" || ch == "\n"`
//!
//! ## Design Philosophy
//!
//! BoolExprLowerer is a SEPARATE module from loop patterns (Pattern1-4).
//! It focuses purely on expression lowering, while loop patterns handle
//! control flow structure.
//!
//! **Separation of Concerns**:
//! - Loop patterns (Pattern1-4): Loop structure (header, body, exit)
//! - BoolExprLowerer: Expression evaluation (AST → SSA ValueId)
//!
//! ## Target Use Case
//!
//! JsonParserBox methods `_trim` and `_skip_whitespace` have OR chains:
//! ```nyash
//! ch == " " || ch == "\t" || ch == "\n" || ch == "\r"
//! ```
//!
//! This lowerer converts such expressions into SSA form:
//! ```text
//! %cmp1 = Compare Eq %ch " "
//! %cmp2 = Compare Eq %ch "\t"
//! %cmp3 = Compare Eq %ch "\n"
//! %cmp4 = Compare Eq %ch "\r"
//! %or1 = BinOp Or %cmp1 %cmp2
//! %or2 = BinOp Or %or1 %cmp3
//! %result = BinOp Or %or2 %cmp4
//! ```
use crate::ast::{ASTNode, BinaryOperator, UnaryOperator};
use crate::mir::builder::MirBuilder;
use crate::mir::{BinaryOp, CompareOp, MirInstruction, MirType, ValueId};
/// BoolExprLowerer - Converts boolean expression AST to SSA form
///
/// This box handles lowering of complex boolean expressions within loop conditions.
/// It produces ValueIds that can be used by loop patterns for control flow decisions.
pub struct BoolExprLowerer<'a> {
builder: &'a mut MirBuilder,
}
impl<'a> BoolExprLowerer<'a> {
/// Create a new BoolExprLowerer with access to MirBuilder
pub fn new(builder: &'a mut MirBuilder) -> Self {
BoolExprLowerer { builder }
}
/// Lower a boolean expression AST to SSA form
///
/// # Arguments
///
/// * `cond_ast` - AST node representing the boolean condition
///
/// # Returns
///
/// * `Result<ValueId, String>` - Register holding the result (bool 0/1), or error
///
/// # Supported Operators
///
/// - Comparisons: `<`, `==`, `!=`, `<=`, `>=`, `>`
/// - Logical: `&&`, `||`, `!`
/// - Variables and literals
pub fn lower_condition(&mut self, cond_ast: &ASTNode) -> Result<ValueId, String> {
match cond_ast {
// Comparison operations: <, ==, !=, <=, >=, >
ASTNode::BinaryOp {
operator,
left,
right,
..
} => match operator {
BinaryOperator::Less
| BinaryOperator::Equal
| BinaryOperator::NotEqual
| BinaryOperator::LessEqual
| BinaryOperator::GreaterEqual
| BinaryOperator::Greater => {
// Lower comparison operators
let lhs = self.lower_condition(left)?;
let rhs = self.lower_condition(right)?;
let dst = self.builder.next_value_id();
let cmp_op = match operator {
BinaryOperator::Less => CompareOp::Lt,
BinaryOperator::Equal => CompareOp::Eq,
BinaryOperator::NotEqual => CompareOp::Ne,
BinaryOperator::LessEqual => CompareOp::Le,
BinaryOperator::GreaterEqual => CompareOp::Ge,
BinaryOperator::Greater => CompareOp::Gt,
_ => unreachable!(),
};
// Emit Compare instruction
self.builder
.emit_instruction(MirInstruction::Compare {
dst,
op: cmp_op,
lhs,
rhs,
})?;
// Mark result type as Bool
self.builder.value_types.insert(dst, MirType::Bool);
Ok(dst)
}
BinaryOperator::And => {
// Logical AND: evaluate both sides and combine
let lhs = self.lower_condition(left)?;
let rhs = self.lower_condition(right)?;
let dst = self.builder.next_value_id();
// Emit BinOp And instruction
self.builder
.emit_instruction(MirInstruction::BinOp {
dst,
op: BinaryOp::BitAnd, // Use BitAnd for logical AND
lhs,
rhs,
})?;
// Mark result type as Bool
self.builder.value_types.insert(dst, MirType::Bool);
Ok(dst)
}
BinaryOperator::Or => {
// Logical OR: evaluate both sides and combine
let lhs = self.lower_condition(left)?;
let rhs = self.lower_condition(right)?;
let dst = self.builder.next_value_id();
// Emit BinOp Or instruction
self.builder
.emit_instruction(MirInstruction::BinOp {
dst,
op: BinaryOp::BitOr, // Use BitOr for logical OR
lhs,
rhs,
})?;
// Mark result type as Bool
self.builder.value_types.insert(dst, MirType::Bool);
Ok(dst)
}
_ => {
// Other operators (arithmetic, etc.) - delegate to builder
self.builder.build_expression(cond_ast.clone())
}
},
// Unary NOT operator
ASTNode::UnaryOp {
operator: UnaryOperator::Not,
operand,
..
} => {
let operand_val = self.lower_condition(operand)?;
let dst = self.builder.next_value_id();
// Emit UnaryOp Not instruction
self.builder
.emit_instruction(MirInstruction::UnaryOp {
dst,
op: crate::mir::UnaryOp::Not,
operand: operand_val,
})?;
// Mark result type as Bool
self.builder.value_types.insert(dst, MirType::Bool);
Ok(dst)
}
// Variables - delegate to builder
ASTNode::Variable { .. } => self.builder.build_expression(cond_ast.clone()),
// Literals - delegate to builder
ASTNode::Literal { .. } => self.builder.build_expression(cond_ast.clone()),
// Method calls - delegate to builder
ASTNode::MethodCall { .. } => self.builder.build_expression(cond_ast.clone()),
// Field access - delegate to builder
ASTNode::FieldAccess { .. } => self.builder.build_expression(cond_ast.clone()),
// Other operators - delegate to builder
_ => self.builder.build_expression(cond_ast.clone()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
use crate::mir::builder::MirBuilder;
use crate::mir::FunctionSignature;
/// Helper to create a test MirBuilder
fn create_test_builder() -> MirBuilder {
let mut builder = MirBuilder::new();
// Initialize a test function
let sig = FunctionSignature {
name: "test_function".to_string(),
params: vec!["i".to_string(), "ch".to_string()],
arity: 2,
return_type: crate::mir::MirType::Integer,
};
builder.start_function(sig);
builder.start_new_block();
builder
}
/// Test: Simple comparison (i < 10)
#[test]
fn test_simple_comparison() {
let mut builder = create_test_builder();
let mut lowerer = BoolExprLowerer::new(&mut builder);
// AST: i < 10
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(10),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lowerer.lower_condition(&ast);
assert!(result.is_ok(), "Simple comparison should succeed");
}
/// Test: OR chain (ch == " " || ch == "\t")
#[test]
fn test_or_chain() {
let mut builder = create_test_builder();
let mut lowerer = BoolExprLowerer::new(&mut builder);
// AST: ch == " " || ch == "\t"
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Or,
left: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(ASTNode::Variable {
name: "ch".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::String(" ".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
right: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(ASTNode::Variable {
name: "ch".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::String("\t".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lowerer.lower_condition(&ast);
assert!(result.is_ok(), "OR chain should succeed");
}
/// Test: Complex mixed condition (i < len && (c == " " || c == "\t"))
#[test]
fn test_complex_mixed_condition() {
let mut builder = create_test_builder();
let mut lowerer = BoolExprLowerer::new(&mut builder);
// AST: i < len && (c == " " || c == "\t")
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::And,
left: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: "len".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
right: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Or,
left: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(ASTNode::Variable {
name: "c".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::String(" ".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
right: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left: Box::new(ASTNode::Variable {
name: "c".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::String("\t".to_string()),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lowerer.lower_condition(&ast);
assert!(result.is_ok(), "Complex mixed condition should succeed");
}
/// Test: NOT operator (!condition)
#[test]
fn test_not_operator() {
let mut builder = create_test_builder();
let mut lowerer = BoolExprLowerer::new(&mut builder);
// AST: !(i < 10)
let ast = ASTNode::UnaryOp {
operator: crate::ast::UnaryOperator::Not,
operand: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(10),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lowerer.lower_condition(&ast);
assert!(result.is_ok(), "NOT operator should succeed");
}
}

View File

@ -0,0 +1,596 @@
//! Phase 169: JoinIR Condition Lowering Helper
//!
//! This module provides AST → JoinIR condition lowering for loop patterns.
//! Unlike BoolExprLowerer (which generates MIR), this generates JoinIR instructions.
//!
//! ## Design Philosophy
//!
//! **Separation of Concerns**:
//! - BoolExprLowerer: AST → MIR (for regular control flow)
//! - condition_to_joinir: AST → JoinIR (for loop lowerers)
//!
//! This dual approach maintains clean boundaries:
//! - Loop lowerers work in JoinIR space (pure functional transformation)
//! - Regular control flow uses MIR space (stateful builder)
//!
//! ## Usage Example
//!
//! ```rust
//! let mut value_counter = 0u32;
//! let mut alloc_value = || {
//! let id = ValueId(value_counter);
//! value_counter += 1;
//! id
//! };
//!
//! // Lower condition: i < end
//! let (cond_value, cond_insts) = lower_condition_to_joinir(
//! condition_ast,
//! &mut alloc_value,
//! builder,
//! )?;
//!
//! // cond_value: ValueId holding boolean result
//! // cond_insts: Vec<JoinInst::Compute> to evaluate condition
//! ```
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, UnaryOperator};
use crate::mir::join_ir::{BinOpKind, CompareOp, ConstValue, JoinInst, MirLikeInst};
use crate::mir::ValueId;
use std::collections::HashMap;
/// Phase 171-fix: Environment for condition expression lowering
/// Maps variable names to JoinIR-local ValueIds
#[derive(Debug, Clone, Default)]
pub struct ConditionEnv {
pub name_to_join: HashMap<String, ValueId>,
}
impl ConditionEnv {
pub fn new() -> Self {
Self { name_to_join: HashMap::new() }
}
pub fn insert(&mut self, name: String, join_id: ValueId) {
self.name_to_join.insert(name, join_id);
}
pub fn get(&self, name: &str) -> Option<ValueId> {
self.name_to_join.get(name).copied()
}
}
/// Phase 171-fix: Binding between HOST and JoinIR ValueIds for condition variables
#[derive(Debug, Clone)]
pub struct ConditionBinding {
pub name: String,
pub host_value: ValueId,
pub join_value: ValueId,
}
/// Lower an AST condition to JoinIR instructions
///
/// Phase 171-fix: Changed to use ConditionEnv instead of builder
///
/// # Arguments
///
/// * `cond_ast` - AST node representing the boolean condition
/// * `alloc_value` - ValueId allocator function
/// * `env` - ConditionEnv for variable resolution (JoinIR-local ValueIds)
///
/// # Returns
///
/// * `Ok((ValueId, Vec<JoinInst>))` - Condition result ValueId and evaluation instructions
/// * `Err(String)` - Lowering error message
///
/// # Supported Patterns
///
/// - Comparisons: `i < n`, `x == y`, `a != b`, `x <= y`, `x >= y`, `x > y`
/// - Logical: `a && b`, `a || b`, `!cond`
/// - Variables and literals
pub fn lower_condition_to_joinir<F>(
cond_ast: &ASTNode,
alloc_value: &mut F,
env: &ConditionEnv,
) -> Result<(ValueId, Vec<JoinInst>), String>
where
F: FnMut() -> ValueId,
{
let mut instructions = Vec::new();
let result_value = lower_condition_recursive(cond_ast, alloc_value, env, &mut instructions)?;
Ok((result_value, instructions))
}
/// Recursive helper for condition lowering
///
/// Phase 171-fix: Changed to use ConditionEnv instead of builder
fn lower_condition_recursive<F>(
cond_ast: &ASTNode,
alloc_value: &mut F,
env: &ConditionEnv,
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String>
where
F: FnMut() -> ValueId,
{
match cond_ast {
// Comparison operations: <, ==, !=, <=, >=, >
ASTNode::BinaryOp {
operator,
left,
right,
..
} => match operator {
BinaryOperator::Less
| BinaryOperator::Equal
| BinaryOperator::NotEqual
| BinaryOperator::LessEqual
| BinaryOperator::GreaterEqual
| BinaryOperator::Greater => {
// Lower left and right sides
let lhs = lower_value_expression(left, alloc_value, env, instructions)?;
let rhs = lower_value_expression(right, alloc_value, env, instructions)?;
let dst = alloc_value();
let cmp_op = match operator {
BinaryOperator::Less => CompareOp::Lt,
BinaryOperator::Equal => CompareOp::Eq,
BinaryOperator::NotEqual => CompareOp::Ne,
BinaryOperator::LessEqual => CompareOp::Le,
BinaryOperator::GreaterEqual => CompareOp::Ge,
BinaryOperator::Greater => CompareOp::Gt,
_ => unreachable!(),
};
// Emit Compare instruction
instructions.push(JoinInst::Compute(MirLikeInst::Compare {
dst,
op: cmp_op,
lhs,
rhs,
}));
Ok(dst)
}
BinaryOperator::And => {
// Logical AND: evaluate both sides and combine
let lhs = lower_condition_recursive(left, alloc_value, env, instructions)?;
let rhs = lower_condition_recursive(right, alloc_value, env, instructions)?;
let dst = alloc_value();
// Emit BinOp And instruction
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
dst,
op: BinOpKind::And,
lhs,
rhs,
}));
Ok(dst)
}
BinaryOperator::Or => {
// Logical OR: evaluate both sides and combine
let lhs = lower_condition_recursive(left, alloc_value, env, instructions)?;
let rhs = lower_condition_recursive(right, alloc_value, env, instructions)?;
let dst = alloc_value();
// Emit BinOp Or instruction
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
dst,
op: BinOpKind::Or,
lhs,
rhs,
}));
Ok(dst)
}
_ => {
// Other operators (arithmetic, etc.)
Err(format!(
"Unsupported binary operator in condition: {:?}",
operator
))
}
},
// Unary NOT operator
ASTNode::UnaryOp {
operator: UnaryOperator::Not,
operand,
..
} => {
let operand_val = lower_condition_recursive(operand, alloc_value, env, instructions)?;
let dst = alloc_value();
// Emit UnaryOp Not instruction
instructions.push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst,
op: crate::mir::join_ir::UnaryOp::Not,
operand: operand_val,
}));
Ok(dst)
}
// Variables - resolve from ConditionEnv (Phase 171-fix)
ASTNode::Variable { name, .. } => {
// Look up variable in ConditionEnv (JoinIR-local ValueIds)
env.get(name)
.ok_or_else(|| format!("Variable '{}' not bound in ConditionEnv", name))
}
// Literals - emit as constants
ASTNode::Literal { value, .. } => {
let dst = alloc_value();
let const_value = match value {
LiteralValue::Integer(n) => ConstValue::Integer(*n),
LiteralValue::String(s) => ConstValue::String(s.clone()),
LiteralValue::Bool(b) => ConstValue::Bool(*b),
LiteralValue::Float(_) => {
// Float literals not supported in JoinIR ConstValue yet
return Err("Float literals not supported in JoinIR conditions yet".to_string());
}
_ => {
return Err(format!("Unsupported literal type in condition: {:?}", value));
}
};
instructions.push(JoinInst::Compute(MirLikeInst::Const {
dst,
value: const_value,
}));
Ok(dst)
}
_ => Err(format!("Unsupported AST node in condition: {:?}", cond_ast)),
}
}
/// Lower a value expression (for comparison operands, etc.)
///
/// Phase 171-fix: Changed to use ConditionEnv instead of builder
///
/// This handles the common case where we need to evaluate a simple value
/// (variable or literal) as part of a comparison.
fn lower_value_expression<F>(
expr: &ASTNode,
alloc_value: &mut F,
env: &ConditionEnv,
instructions: &mut Vec<JoinInst>,
) -> Result<ValueId, String>
where
F: FnMut() -> ValueId,
{
match expr {
// Variables - look up in ConditionEnv (Phase 171-fix)
ASTNode::Variable { name, .. } => env
.get(name)
.ok_or_else(|| format!("Variable '{}' not bound in ConditionEnv", name)),
// Literals - emit as constants
ASTNode::Literal { value, .. } => {
let dst = alloc_value();
let const_value = match value {
LiteralValue::Integer(n) => ConstValue::Integer(*n),
LiteralValue::String(s) => ConstValue::String(s.clone()),
LiteralValue::Bool(b) => ConstValue::Bool(*b),
LiteralValue::Float(_) => {
// Float literals not supported in JoinIR ConstValue yet
return Err("Float literals not supported in JoinIR value expressions yet".to_string());
}
_ => {
return Err(format!("Unsupported literal type: {:?}", value));
}
};
instructions.push(JoinInst::Compute(MirLikeInst::Const {
dst,
value: const_value,
}));
Ok(dst)
}
// Binary operations (for arithmetic in conditions like i + 1 < n)
ASTNode::BinaryOp {
operator,
left,
right,
..
} => {
let lhs = lower_value_expression(left, alloc_value, env, instructions)?;
let rhs = lower_value_expression(right, alloc_value, env, instructions)?;
let dst = alloc_value();
let bin_op = match operator {
BinaryOperator::Add => BinOpKind::Add,
BinaryOperator::Subtract => BinOpKind::Sub,
BinaryOperator::Multiply => BinOpKind::Mul,
BinaryOperator::Divide => BinOpKind::Div,
BinaryOperator::Modulo => BinOpKind::Mod,
_ => {
return Err(format!("Unsupported binary operator in expression: {:?}", operator));
}
};
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
dst,
op: bin_op,
lhs,
rhs,
}));
Ok(dst)
}
_ => Err(format!("Unsupported expression in value context: {:?}", expr)),
}
}
/// Extract all variable names used in a condition AST (Phase 171)
///
/// This helper recursively traverses the condition AST and collects all
/// unique variable names. Used to determine which variables need to be
/// available in JoinIR scope.
///
/// # Arguments
///
/// * `cond_ast` - AST node representing the condition
/// * `exclude_vars` - Variable names to exclude (e.g., loop parameters already registered)
///
/// # Returns
///
/// Sorted vector of unique variable names found in the condition
///
/// # Example
///
/// ```ignore
/// // For condition: start < end && i < len
/// let vars = extract_condition_variables(
/// condition_ast,
/// &["i".to_string()], // Exclude loop variable 'i'
/// );
/// // Result: ["end", "len", "start"] (sorted, 'i' excluded)
/// ```
pub fn extract_condition_variables(
cond_ast: &ASTNode,
exclude_vars: &[String],
) -> Vec<String> {
use std::collections::BTreeSet;
let mut all_vars = BTreeSet::new();
collect_variables_recursive(cond_ast, &mut all_vars);
// Filter out excluded variables and return sorted list
all_vars.into_iter()
.filter(|name| !exclude_vars.contains(name))
.collect()
}
/// Recursive helper to collect variable names
fn collect_variables_recursive(ast: &ASTNode, vars: &mut std::collections::BTreeSet<String>) {
match ast {
ASTNode::Variable { name, .. } => {
vars.insert(name.clone());
}
ASTNode::BinaryOp { left, right, .. } => {
collect_variables_recursive(left, vars);
collect_variables_recursive(right, vars);
}
ASTNode::UnaryOp { operand, .. } => {
collect_variables_recursive(operand, vars);
}
ASTNode::Literal { .. } => {
// Literals have no variables
}
_ => {
// Other AST nodes not expected in conditions
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
/// Phase 171-fix: Helper to create a test ConditionEnv with variables
fn create_test_env() -> ConditionEnv {
let mut env = ConditionEnv::new();
// Register test variables (using JoinIR-local ValueIds)
env.insert("i".to_string(), ValueId(0));
env.insert("end".to_string(), ValueId(1));
env
}
#[test]
fn test_simple_comparison() {
let env = create_test_env();
let mut value_counter = 2u32; // Start after i=0, end=1
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
// AST: i < end
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: "end".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env);
assert!(result.is_ok(), "Simple comparison should succeed");
let (_cond_value, instructions) = result.unwrap();
assert_eq!(instructions.len(), 1, "Should generate 1 Compare instruction");
}
#[test]
fn test_comparison_with_literal() {
let env = create_test_env();
let mut value_counter = 2u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
// AST: i < 10
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(10),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env);
assert!(result.is_ok(), "Comparison with literal should succeed");
let (_cond_value, instructions) = result.unwrap();
// Should have: Const(10), Compare
assert_eq!(instructions.len(), 2, "Should generate Const + Compare");
}
#[test]
fn test_logical_or() {
let mut env = ConditionEnv::new();
env.insert("a".to_string(), ValueId(2));
env.insert("b".to_string(), ValueId(3));
let mut value_counter = 4u32;
let mut alloc_value = || {
let id = ValueId(value_counter);
value_counter += 1;
id
};
// AST: a < 5 || b < 5
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Or,
left: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "a".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(5),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
right: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "b".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Literal {
value: LiteralValue::Integer(5),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let result = lower_condition_to_joinir(&ast, &mut alloc_value, &env);
assert!(result.is_ok(), "OR expression should succeed");
let (_cond_value, instructions) = result.unwrap();
// Should have: Const(5), Compare(a<5), Const(5), Compare(b<5), BinOp(Or)
assert_eq!(instructions.len(), 5, "Should generate proper OR chain");
}
#[test]
fn test_extract_condition_variables_simple() {
// AST: start < end
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "start".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: "end".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let vars = extract_condition_variables(&ast, &[]);
assert_eq!(vars, vec!["end", "start"]); // Sorted order
}
#[test]
fn test_extract_condition_variables_with_exclude() {
// AST: i < end
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: "end".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let vars = extract_condition_variables(&ast, &["i".to_string()]);
assert_eq!(vars, vec!["end"]); // 'i' excluded
}
#[test]
fn test_extract_condition_variables_complex() {
// AST: start < end && i < len
let ast = ASTNode::BinaryOp {
operator: BinaryOperator::And,
left: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "start".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: "end".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
right: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(ASTNode::Variable {
name: "i".to_string(),
span: Span::unknown(),
}),
right: Box::new(ASTNode::Variable {
name: "len".to_string(),
span: Span::unknown(),
}),
span: Span::unknown(),
}),
span: Span::unknown(),
};
let vars = extract_condition_variables(&ast, &["i".to_string()]);
assert_eq!(vars, vec!["end", "len", "start"]); // Sorted, 'i' excluded
}
}

View File

@ -164,6 +164,47 @@ pub struct JoinInlineBoundary {
/// ] /// ]
/// ``` /// ```
pub exit_bindings: Vec<LoopExitBinding>, pub exit_bindings: Vec<LoopExitBinding>,
/// Condition-only input variables (Phase 171+ / Phase 171-fix)
///
/// **DEPRECATED**: Use `condition_bindings` instead (Phase 171-fix).
///
/// 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
#[deprecated(since = "Phase 171-fix", note = "Use condition_bindings instead")]
pub condition_inputs: Vec<(String, ValueId)>,
/// Phase 171-fix: Condition bindings with explicit JoinIR ValueIds
///
/// Each binding explicitly specifies:
/// - Variable name
/// - HOST ValueId (source)
/// - JoinIR ValueId (destination)
///
/// This replaces `condition_inputs` to ensure proper ValueId separation.
pub condition_bindings: Vec<super::condition_to_joinir::ConditionBinding>,
} }
impl JoinInlineBoundary { impl JoinInlineBoundary {
@ -185,6 +226,9 @@ impl JoinInlineBoundary {
#[allow(deprecated)] #[allow(deprecated)]
host_outputs: vec![], host_outputs: vec![],
exit_bindings: vec![], exit_bindings: vec![],
#[allow(deprecated)]
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
condition_bindings: vec![], // Phase 171-fix: Default to empty
} }
} }
@ -221,6 +265,9 @@ impl JoinInlineBoundary {
#[allow(deprecated)] #[allow(deprecated)]
host_outputs, host_outputs,
exit_bindings: vec![], exit_bindings: vec![],
#[allow(deprecated)]
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
condition_bindings: vec![], // Phase 171-fix: Default to empty
} }
} }
@ -253,6 +300,9 @@ impl JoinInlineBoundary {
#[allow(deprecated)] #[allow(deprecated)]
host_outputs, host_outputs,
exit_bindings: vec![], exit_bindings: vec![],
#[allow(deprecated)]
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
condition_bindings: vec![], // Phase 171-fix: Default to empty
} }
} }
@ -307,6 +357,153 @@ impl JoinInlineBoundary {
#[allow(deprecated)] #[allow(deprecated)]
host_outputs: vec![], host_outputs: vec![],
exit_bindings, exit_bindings,
#[allow(deprecated)]
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
condition_bindings: vec![], // Phase 171-fix: Default to empty
}
}
/// 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![],
#[allow(deprecated)]
condition_inputs,
condition_bindings: vec![], // Phase 171-fix: Will be populated by new constructor
}
}
/// Create boundary with inputs, exit bindings, AND condition inputs (Phase 171+)
///
/// This is the most complete constructor for loops with carriers and condition variables.
///
/// # Example: Pattern 3 with condition variables
///
/// ```ignore
/// let boundary = JoinInlineBoundary::new_with_exit_and_condition_inputs(
/// vec![ValueId(0), ValueId(1)], // join_inputs (i, sum)
/// vec![ValueId(5), ValueId(10)], // host_inputs
/// vec![
/// LoopExitBinding {
/// carrier_name: "sum".to_string(),
/// join_exit_value: ValueId(18),
/// host_slot: ValueId(10),
/// }
/// ],
/// vec![
/// ("start".to_string(), ValueId(33)),
/// ("end".to_string(), ValueId(34)),
/// ],
/// );
/// ```
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,
#[allow(deprecated)]
condition_inputs,
condition_bindings: vec![], // Phase 171-fix: Will be populated by new constructor
}
}
/// Phase 171-fix: Create boundary with ConditionBindings (NEW constructor)
///
/// This is the recommended constructor that uses ConditionBindings instead of
/// the deprecated condition_inputs.
///
/// # Arguments
///
/// * `join_inputs` - JoinIR-local ValueIds for loop parameters
/// * `host_inputs` - HOST ValueIds for loop parameters
/// * `condition_bindings` - Explicit HOST ↔ JoinIR mappings for condition variables
///
/// # Example
///
/// ```ignore
/// let boundary = JoinInlineBoundary::new_with_condition_bindings(
/// vec![ValueId(0)], // join_inputs (loop param i)
/// vec![ValueId(5)], // host_inputs (loop param i)
/// vec![
/// ConditionBinding {
/// name: "start".to_string(),
/// host_value: ValueId(33), // HOST
/// join_value: ValueId(1), // JoinIR
/// },
/// ConditionBinding {
/// name: "end".to_string(),
/// host_value: ValueId(34), // HOST
/// join_value: ValueId(2), // JoinIR
/// },
/// ],
/// );
/// ```
pub fn new_with_condition_bindings(
join_inputs: Vec<ValueId>,
host_inputs: Vec<ValueId>,
condition_bindings: Vec<super::condition_to_joinir::ConditionBinding>,
) -> 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![],
#[allow(deprecated)]
condition_inputs: vec![], // Deprecated, use condition_bindings instead
condition_bindings,
} }
} }
} }
@ -325,7 +522,12 @@ mod tests {
assert_eq!(boundary.join_inputs.len(), 1); assert_eq!(boundary.join_inputs.len(), 1);
assert_eq!(boundary.host_inputs.len(), 1); assert_eq!(boundary.host_inputs.len(), 1);
assert_eq!(boundary.join_outputs.len(), 0); assert_eq!(boundary.join_outputs.len(), 0);
#[allow(deprecated)]
{
assert_eq!(boundary.host_outputs.len(), 0); assert_eq!(boundary.host_outputs.len(), 0);
assert_eq!(boundary.condition_inputs.len(), 0); // Phase 171: Deprecated field
}
assert_eq!(boundary.condition_bindings.len(), 0); // Phase 171-fix: New field
} }
#[test] #[test]

View File

@ -341,7 +341,9 @@ pub fn lower_loop_with_break_to_joinir(
}; };
// Generate JoinIR module // Generate JoinIR module
let _join_module = lower_loop_with_break_minimal(placeholder_scope)?; // Phase 169: lower_loop_with_break_minimal now requires condition AST and builder
// This test stub is not used by the actual router, so commenting out for now
// let _join_module = lower_loop_with_break_minimal(placeholder_scope, condition, builder)?;
// Phase 188-Impl-2: Pattern 2 is now integrated in control_flow.rs via cf_loop_pattern2_with_break() // Phase 188-Impl-2: Pattern 2 is now integrated in control_flow.rs via cf_loop_pattern2_with_break()
// This function (lower_loop_with_break_to_joinir) is currently not used by the router. // This function (lower_loop_with_break_to_joinir) is currently not used by the router.

View File

@ -55,6 +55,8 @@
//! //!
//! Following the "80/20 rule" from CLAUDE.md - get it working first, generalize later. //! Following the "80/20 rule" from CLAUDE.md - get it working first, generalize later.
use crate::ast::ASTNode;
use crate::mir::join_ir::lowering::condition_to_joinir::{lower_condition_to_joinir, ConditionEnv};
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::join_ir::{ use crate::mir::join_ir::{
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule,
@ -85,11 +87,13 @@ use crate::mir::ValueId;
/// # Arguments /// # Arguments
/// ///
/// * `_scope` - LoopScopeShape (reserved for future generic implementation) /// * `_scope` - LoopScopeShape (reserved for future generic implementation)
/// * `condition` - Loop condition AST node (e.g., `i < end`)
/// * `env` - ConditionEnv for variable resolution (JoinIR-local ValueIds) - Phase 171-fix
/// ///
/// # Returns /// # Returns
/// ///
/// * `Some(JoinModule)` - Successfully lowered to JoinIR /// * `Ok(JoinModule)` - Successfully lowered to JoinIR
/// * `None` - Pattern not matched (fallback to other lowerers) /// * `Err(String)` - Pattern not matched or lowering error
/// ///
/// # Boundary Contract /// # Boundary Contract
/// ///
@ -97,7 +101,16 @@ use crate::mir::ValueId;
/// - **Input slot**: ValueId(0) in main function represents the loop variable init /// - **Input slot**: ValueId(0) in main function represents the loop variable init
/// - **Output slot**: k_exit returns the final loop variable value /// - **Output slot**: k_exit returns the final loop variable value
/// - **Caller responsibility**: Create JoinInlineBoundary to map ValueIds /// - **Caller responsibility**: Create JoinInlineBoundary to map ValueIds
pub fn lower_loop_with_break_minimal(_scope: LoopScopeShape) -> Option<JoinModule> { ///
/// # Phase 171-fix: ConditionEnv Usage
///
/// The caller must build a ConditionEnv that maps variable names to JoinIR-local ValueIds.
/// This ensures JoinIR never accesses HOST ValueIds directly.
pub fn lower_loop_with_break_minimal(
_scope: LoopScopeShape,
condition: &ASTNode,
env: &ConditionEnv,
) -> Result<JoinModule, String> {
// Phase 188-Impl-2: Use local ValueId allocator (sequential from 0) // Phase 188-Impl-2: Use local ValueId allocator (sequential from 0)
// JoinIR has NO knowledge of host ValueIds - boundary handled separately // JoinIR has NO knowledge of host ValueIds - boundary handled separately
let mut value_counter = 0u32; let mut value_counter = 0u32;
@ -125,16 +138,24 @@ pub fn lower_loop_with_break_minimal(_scope: LoopScopeShape) -> Option<JoinModul
// loop_step locals // loop_step locals
let i_param = alloc_value(); // ValueId(2) - parameter let i_param = alloc_value(); // ValueId(2) - parameter
let const_3 = alloc_value(); // ValueId(3) - natural exit limit
let cmp_lt = alloc_value(); // ValueId(4) - i < 3 // Phase 169 / Phase 171-fix: Lower condition using condition_to_joinir helper with ConditionEnv
let exit_cond = alloc_value(); // ValueId(5) - !(i < 3) // This will allocate ValueIds dynamically based on condition complexity
let const_2 = alloc_value(); // ValueId(6) - break limit let (cond_value, mut cond_instructions) = lower_condition_to_joinir(
let break_cond = alloc_value(); // ValueId(7) - i >= 2 condition,
let const_1 = alloc_value(); // ValueId(8) - increment constant &mut alloc_value,
let i_next = alloc_value(); // ValueId(9) - i + 1 env,
)?;
// After condition lowering, allocate remaining ValueIds
let exit_cond = alloc_value(); // Exit condition (negated loop condition)
let const_2 = alloc_value(); // Break limit (hardcoded for now)
let break_cond = alloc_value(); // Break condition (i >= 2)
let const_1 = alloc_value(); // Increment constant
let i_next = alloc_value(); // i + 1
// k_exit locals // k_exit locals
let i_exit = alloc_value(); // ValueId(10) - exit parameter (PHI) let i_exit = alloc_value(); // Exit parameter (PHI)
// ================================================================== // ==================================================================
// main() function // main() function
@ -168,33 +189,18 @@ pub fn lower_loop_with_break_minimal(_scope: LoopScopeShape) -> Option<JoinModul
); );
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// Natural Exit Condition Check: !(i < 3) // Natural Exit Condition Check (Phase 169: from AST)
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// Step 1: const 3 // Insert all condition evaluation instructions
loop_step_func loop_step_func.body.append(&mut cond_instructions);
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_3,
value: ConstValue::Integer(3),
}));
// Step 2: cmp_lt = (i < 3) // Negate the condition for exit check: exit_cond = !cond_value
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_lt,
op: CompareOp::Lt,
lhs: i_param,
rhs: const_3,
}));
// Step 3: exit_cond = !cmp_lt
loop_step_func loop_step_func
.body .body
.push(JoinInst::Compute(MirLikeInst::UnaryOp { .push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst: exit_cond, dst: exit_cond,
op: UnaryOp::Not, op: UnaryOp::Not,
operand: cmp_lt, operand: cond_value,
})); }));
// Jump(k_exit, [i], cond=exit_cond) // Natural exit path // Jump(k_exit, [i], cond=exit_cond) // Natural exit path
@ -283,9 +289,10 @@ pub fn lower_loop_with_break_minimal(_scope: LoopScopeShape) -> Option<JoinModul
// Set entry point // Set entry point
join_module.entry = Some(main_id); join_module.entry = Some(main_id);
eprintln!("[joinir/pattern2] Generated JoinIR for Loop with Break Pattern"); eprintln!("[joinir/pattern2] Generated JoinIR for Loop with Break Pattern (Phase 169)");
eprintln!("[joinir/pattern2] Functions: main, loop_step, k_exit"); eprintln!("[joinir/pattern2] Functions: main, loop_step, k_exit");
eprintln!("[joinir/pattern2] Condition from AST (not hardcoded)");
eprintln!("[joinir/pattern2] Exit PHI: k_exit receives i from both natural exit and break"); eprintln!("[joinir/pattern2] Exit PHI: k_exit receives i from both natural exit and break");
Some(join_module) Ok(join_module)
} }

View File

@ -44,7 +44,10 @@
//! //!
//! Following the "80/20 rule" from CLAUDE.md - now generalizing after working. //! Following the "80/20 rule" from CLAUDE.md - now generalizing after working.
use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta}; use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta};
use crate::mir::join_ir::lowering::condition_to_joinir::{lower_condition_to_joinir, ConditionEnv};
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape; use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs}; use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs};
use crate::mir::join_ir::{ use crate::mir::join_ir::{
@ -77,13 +80,15 @@ use std::collections::HashMap;
/// # Arguments /// # Arguments
/// ///
/// * `_scope` - LoopScopeShape (reserved for future generic implementation) /// * `_scope` - LoopScopeShape (reserved for future generic implementation)
/// * `condition` - Loop condition AST node (e.g., `i < end`) (Phase 169)
/// * `builder` - MirBuilder for variable resolution (Phase 169)
/// * `carrier_info` - Phase 196: Carrier metadata for dynamic multi-carrier support /// * `carrier_info` - Phase 196: Carrier metadata for dynamic multi-carrier support
/// * `carrier_updates` - Phase 197: Update expressions for each carrier variable /// * `carrier_updates` - Phase 197: Update expressions for each carrier variable
/// ///
/// # Returns /// # Returns
/// ///
/// * `Some((JoinModule, ExitMeta))` - Successfully lowered to JoinIR with exit metadata /// * `Ok((JoinModule, ExitMeta))` - Successfully lowered to JoinIR with exit metadata
/// * `None` - Pattern not matched (fallback to other lowerers) /// * `Err(String)` - Pattern not matched or lowering error
/// ///
/// # Boundary Contract (Phase 196 updated) /// # Boundary Contract (Phase 196 updated)
/// ///
@ -94,9 +99,11 @@ use std::collections::HashMap;
/// - **Caller responsibility**: Create JoinInlineBoundary to map ValueIds /// - **Caller responsibility**: Create JoinInlineBoundary to map ValueIds
pub fn lower_loop_with_continue_minimal( pub fn lower_loop_with_continue_minimal(
_scope: LoopScopeShape, _scope: LoopScopeShape,
condition: &ASTNode,
builder: &mut MirBuilder,
carrier_info: &CarrierInfo, carrier_info: &CarrierInfo,
carrier_updates: &HashMap<String, UpdateExpr>, carrier_updates: &HashMap<String, UpdateExpr>,
) -> Option<(JoinModule, ExitMeta)> { ) -> Result<(JoinModule, ExitMeta), String> {
// Phase 196: Use local ValueId allocator (sequential from 0) // Phase 196: Use local ValueId allocator (sequential from 0)
// JoinIR has NO knowledge of host ValueIds - boundary handled separately // JoinIR has NO knowledge of host ValueIds - boundary handled separately
let mut value_counter = 0u32; let mut value_counter = 0u32;
@ -140,9 +147,34 @@ pub fn lower_loop_with_continue_minimal(
carrier_param_ids.push(alloc_value()); carrier_param_ids.push(alloc_value());
} }
// Phase 171-fix: Build ConditionEnv for condition lowering
// TODO(Phase 171-fix): This is a temporary workaround. Pattern 3 lowerer should build
// ConditionEnv at the call site and pass it here, similar to Pattern 2.
// For now, we build it internally since this function still needs builder for carrier_info.
use crate::mir::join_ir::lowering::condition_to_joinir::extract_condition_variables;
let mut env = ConditionEnv::new();
// Add loop parameter to env (ValueId(0) in JoinIR space)
let loop_var_name = carrier_info.loop_var_name.clone();
env.insert(loop_var_name.clone(), ValueId(0)); // i_param is ValueId(0) equivalent
// Extract and add condition-only variables
let condition_var_names = extract_condition_variables(condition, &[loop_var_name.clone()]);
let mut join_value_counter = carrier_count as u32 + 1; // After loop param and carriers
for var_name in &condition_var_names {
let join_id = ValueId(join_value_counter);
join_value_counter += 1;
env.insert(var_name.clone(), join_id);
}
// Phase 169 / Phase 171-fix: Lower condition using condition_to_joinir helper with ConditionEnv
let (cond_value, mut cond_instructions) = lower_condition_to_joinir(
condition,
&mut alloc_value,
&env,
)?;
// Loop control temporaries // Loop control temporaries
let const_10 = alloc_value();
let cmp_lt = alloc_value();
let exit_cond = alloc_value(); let exit_cond = alloc_value();
let const_1 = alloc_value(); let const_1 = alloc_value();
let i_next = alloc_value(); let i_next = alloc_value();
@ -202,30 +234,18 @@ pub fn lower_loop_with_continue_minimal(
); );
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// Natural Exit Condition Check: !(i < 10) // Natural Exit Condition Check (Phase 169: from AST)
// ------------------------------------------------------------------ // ------------------------------------------------------------------
loop_step_func // Insert all condition evaluation instructions
.body loop_step_func.body.append(&mut cond_instructions);
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_10,
value: ConstValue::Integer(10),
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_lt,
op: CompareOp::Lt,
lhs: i_param,
rhs: const_10,
}));
// Negate the condition for exit check: exit_cond = !cond_value
loop_step_func loop_step_func
.body .body
.push(JoinInst::Compute(MirLikeInst::UnaryOp { .push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst: exit_cond, dst: exit_cond,
op: UnaryOp::Not, op: UnaryOp::Not,
operand: cmp_lt, operand: cond_value,
})); }));
// Jump(k_exit, [carrier1, carrier2, ...], cond=exit_cond) // Jump(k_exit, [carrier1, carrier2, ...], cond=exit_cond)
@ -455,9 +475,9 @@ pub fn lower_loop_with_continue_minimal(
let exit_meta = ExitMeta::multiple(exit_values); let exit_meta = ExitMeta::multiple(exit_values);
eprintln!( eprintln!(
"[joinir/pattern4] ExitMeta total: {} bindings", "[joinir/pattern4] Phase 169: ExitMeta total: {} bindings (condition from AST)",
exit_meta.exit_values.len() exit_meta.exit_values.len()
); );
Some((join_module, exit_meta)) Ok((join_module, exit_meta))
} }

View File

@ -14,9 +14,12 @@
//! - `funcscanner_append_defs.rs`: FuncScannerBox._append_defs/2 の配列結合 loweringPhase 27.14 //! - `funcscanner_append_defs.rs`: FuncScannerBox._append_defs/2 の配列結合 loweringPhase 27.14
//! - `if_select.rs`: Phase 33 If/Else → Select lowering //! - `if_select.rs`: Phase 33 If/Else → Select lowering
//! - `if_dry_runner.rs`: Phase 33-10 If lowering dry-run スキャナー(箱化版) //! - `if_dry_runner.rs`: Phase 33-10 If lowering dry-run スキャナー(箱化版)
//! - `bool_expr_lowerer.rs`: Phase 168 Boolean expression lowering (AST → SSA)
pub mod bool_expr_lowerer; // Phase 168: Boolean expression lowering for complex conditions
pub mod carrier_info; // Phase 196: Carrier metadata for loop lowering pub mod carrier_info; // Phase 196: Carrier metadata for loop lowering
pub mod common; pub mod common;
pub mod condition_to_joinir; // Phase 169: JoinIR condition lowering helper
pub mod loop_update_analyzer; // Phase 197: Update expression analyzer for carrier semantics pub mod loop_update_analyzer; // Phase 197: Update expression analyzer for carrier semantics
pub mod exit_args_resolver; pub mod exit_args_resolver;
pub mod funcscanner_append_defs; pub mod funcscanner_append_defs;

View File

@ -0,0 +1,76 @@
// Phase 166: JsonParserBox 単体テスト - Array parsing
// Goal: Validate JsonParserBox can parse simple JSON arrays through JoinIR
using "tools/hako_shared/json_parser.hako" with JsonParserBox
static box Main {
main(args) {
print("=== JsonParserBox Array Parsing Tests ===")
// Test 1: Simple array with numbers
print("Test 1: Simple number array [1, 2, 3]")
local json1 = "[1, 2, 3]"
local result1 = JsonParserBox.parse(json1)
if result1 == null {
print(" FAIL: parse returned null")
return 1
}
// Check if it's an ArrayBox by calling size()
local size1 = result1.size()
if size1 == 3 {
print(" PASS: array has 3 elements")
local elem0 = result1.get(0)
if elem0 == 1 {
print(" PASS: first element is 1")
} else {
print(" FAIL: first element should be 1")
return 1
}
} else {
print(" FAIL: array should have 3 elements")
return 1
}
// Test 2: Array with strings
print("Test 2: String array [\"hello\", \"world\"]")
local json2 = "[\"hello\", \"world\"]"
local result2 = JsonParserBox.parse(json2)
if result2 == null {
print(" FAIL: parse returned null")
return 1
}
local size2 = result2.size()
if size2 == 2 {
print(" PASS: array has 2 elements")
local elem0 = result2.get(0)
if elem0 == "hello" {
print(" PASS: first element is 'hello'")
} else {
print(" FAIL: first element should be 'hello'")
return 1
}
} else {
print(" FAIL: array should have 2 elements")
return 1
}
// Test 3: Empty array
print("Test 3: Empty array []")
local json3 = "[]"
local result3 = JsonParserBox.parse(json3)
if result3 == null {
print(" FAIL: parse returned null")
return 1
}
local size3 = result3.size()
if size3 == 0 {
print(" PASS: array is empty")
} else {
print(" FAIL: array should be empty")
return 1
}
print("=== All Array Tests PASSED ===")
return 0
}
}

View File

@ -0,0 +1,71 @@
// Phase 166: JsonParserBox 単体テスト - Object parsing
// Goal: Validate JsonParserBox can parse simple JSON objects through JoinIR
using "tools/hako_shared/json_parser.hako" with JsonParserBox
static box Main {
main(args) {
print("=== JsonParserBox Object Parsing Tests ===")
// Test 1: Simple object with string values
print("Test 1: Simple object {\"name\": \"Alice\", \"age\": \"30\"}")
local json1 = "{\"name\": \"Alice\", \"age\": \"30\"}"
local result1 = JsonParserBox.parse(json1)
if result1 == null {
print(" FAIL: parse returned null")
return 1
}
// Check if it's a MapBox by calling get()
local name = result1.get("name")
if name == "Alice" {
print(" PASS: got name='Alice'")
} else {
print(" FAIL: expected name='Alice'")
return 1
}
local age = result1.get("age")
if age == "30" {
print(" PASS: got age='30'")
} else {
print(" FAIL: expected age='30'")
return 1
}
// Test 2: Object with number values
print("Test 2: Object with numbers {\"x\": 10, \"y\": 20}")
local json2 = "{\"x\": 10, \"y\": 20}"
local result2 = JsonParserBox.parse(json2)
if result2 == null {
print(" FAIL: parse returned null")
return 1
}
local x = result2.get("x")
if x == 10 {
print(" PASS: got x=10")
} else {
print(" FAIL: expected x=10")
return 1
}
local y = result2.get("y")
if y == 20 {
print(" PASS: got y=20")
} else {
print(" FAIL: expected y=20")
return 1
}
// Test 3: Empty object
print("Test 3: Empty object {}")
local json3 = "{}"
local result3 = JsonParserBox.parse(json3)
if result3 == null {
print(" FAIL: parse returned null")
return 1
}
// Empty object should be a MapBox with no keys
print(" PASS: empty object parsed")
print("=== All Object Tests PASSED ===")
return 0
}
}

View File

@ -0,0 +1,54 @@
// Phase 166: JsonParserBox 単体テスト - String parsing
// Goal: Validate JsonParserBox can parse simple JSON strings through JoinIR
using "tools/hako_shared/json_parser.hako" with JsonParserBox
static box Main {
main(args) {
print("=== JsonParserBox String Parsing Tests ===")
// Test 1: Simple string
print("Test 1: Simple string")
local json1 = "\"hello\""
local result1 = JsonParserBox.parse(json1)
if result1 == null {
print(" FAIL: parse returned null")
return 1
}
if result1 == "hello" {
print(" PASS: got 'hello'")
} else {
print(" FAIL: expected 'hello', got different value")
return 1
}
// Test 2: String with escape sequences
print("Test 2: String with escapes (\\n, \\t)")
local json2 = "\"line1\\nline2\""
local result2 = JsonParserBox.parse(json2)
if result2 == null {
print(" FAIL: parse returned null")
return 1
}
// Result should contain newline
print(" PASS: escape sequences parsed")
// Test 3: Empty string
print("Test 3: Empty string")
local json3 = "\"\""
local result3 = JsonParserBox.parse(json3)
if result3 == null {
print(" FAIL: parse returned null")
return 1
}
if result3 == "" {
print(" PASS: got empty string")
} else {
print(" FAIL: expected empty string")
return 1
}
print("=== All String Tests PASSED ===")
return 0
}
}