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:
566
CURRENT_TASK.md
566
CURRENT_TASK.md
@ -1,5 +1,517 @@
|
||||
# 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)
|
||||
|
||||
**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)
|
||||
|
||||
**Status**: ✅ **Complete** – Pattern 4 の更新式を AST ベースで汎用化。`loop_continue_multi_carrier.hako` が正しく `25` `5` を出力。
|
||||
|
||||
@ -1,10 +1,62 @@
|
||||
# Self Current Task — Now (main)
|
||||
|
||||
2025‑09‑08:現状と直近タスク
|
||||
## 2025‑12‑06:現状サマリ
|
||||
|
||||
### JoinIR / Loop / If ライン
|
||||
|
||||
- LoopBuilder は Phase 186‑187 で完全削除済み。**JoinIR が唯一の loop lowering 経路**。
|
||||
- LoopPattern 系は Pattern1–4 まで実装・本線化済み:
|
||||
- Pattern1: Simple While
|
||||
- Pattern2: Loop with Break
|
||||
- Pattern3: Loop with If‑Else PHI(break/continue なし)
|
||||
- Pattern4: Loop with Continue(multi‑carrier 対応)
|
||||
- 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 depth‑2 ライン
|
||||
|
||||
- `selfhost_build.sh --json` で Program JSON v0 emit は安定。
|
||||
`.hako` 側から env 経由で JSON を読む最小ループ(`program_read_min.hako`)は動作確認済み。
|
||||
- JsonParserBox / BundleResolver のループ 21 本のうち:
|
||||
- 18 本は Pattern1–4 で JoinIR 対応済み(Phase 162–165)。
|
||||
- `_trim` を含む一部の複合ループは、ValueId 境界や Box 登録など残課題あり。
|
||||
- BoolExprLowerer / condition_to_joinir で OR/AND/NOT 付き条件式の lowering は実装完了(Phase 168–169)。
|
||||
- 残課題(JsonParser/selfhost depth‑2):
|
||||
- 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) で条件付き必須を制御。
|
||||
- CoreServices(ring1‑core)として次を実装済み:
|
||||
- StringService / IntegerService / BoolService
|
||||
- ArrayService / MapService / ConsoleService
|
||||
- PluginHost 統合 + UnifiedBoxRegistry からの自動初期化
|
||||
- FileBox / FileHandleBox ライン:
|
||||
- Ring0FsFileIo 経由で read / write / append / metadata 完全対応
|
||||
- Default プロファイルでは必須、NoFs プロファイルでは disabled。
|
||||
- Logging ライン:
|
||||
- ConsoleService(user‑facing)
|
||||
- Ring0.log(internal/dev)
|
||||
- println!(test 専用)
|
||||
の 3 層が `logging_policy.md` で整理済み。JoinIR/Loop trace も同ドキュメントに集約。
|
||||
|
||||
---
|
||||
|
||||
## 2025‑09‑08:旧スナップショット(参考)
|
||||
|
||||
- LLVM 側 P0 完了(BitOps/Array/Echo/Map 緑)。VInvoke(by‑name/by‑id vector) は戻り値マッピングの暫定課題を確認中(Decisions 参照)。
|
||||
- selfhosting-dev の作業を main に順次取り込み。VM/MIR 基盤は main で先に整える方針。
|
||||
|
||||
直近タスク(優先)
|
||||
直近タスク(当時)
|
||||
1) continue/break の lowering(Builder 修正のみで表現)
|
||||
- ループ文脈スタック {head, exit} を導入。
|
||||
- continue に遭遇 → head(または latch)へ br を emit し終端。
|
||||
@ -22,4 +74,3 @@
|
||||
- ビルド: `cargo build --release`
|
||||
- LLVM smoke: `LLVM_SYS_180_PREFIX=$(llvm-config-18 --prefix) NYASH_LLVM_BITOPS_SMOKE=1 ./tools/llvm_smoke.sh release`
|
||||
- VInvoke 調査: `NYASH_LLVM_VINVOKE_TRACE=1 NYASH_LLVM_VINVOKE_SMOKE=1 ./tools/llvm_smoke.sh release`
|
||||
|
||||
|
||||
346
docs/development/current/main/phase166-validation-report.md
Normal file
346
docs/development/current/main/phase166-validation-report.md
Normal 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)
|
||||
1076
docs/development/current/main/phase167_boolexpr_lowerer_design.md
Normal file
1076
docs/development/current/main/phase167_boolexpr_lowerer_design.md
Normal file
File diff suppressed because it is too large
Load Diff
304
docs/development/current/main/phase170-completion-report.md
Normal file
304
docs/development/current/main/phase170-completion-report.md
Normal 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.
|
||||
@ -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
|
||||
209
docs/development/current/main/phase171-1-boundary-analysis.md
Normal file
209
docs/development/current/main/phase171-1-boundary-analysis.md
Normal 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`
|
||||
@ -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`
|
||||
@ -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`
|
||||
299
docs/development/current/main/phase171-completion-report.md
Normal file
299
docs/development/current/main/phase171-completion-report.md
Normal 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`
|
||||
@ -391,12 +391,29 @@ pub(super) fn merge_and_rewrite(
|
||||
|
||||
// Create HashMap from remapper for BoundaryInjector (temporary adapter)
|
||||
let mut value_map_for_injector = HashMap::new();
|
||||
|
||||
// Phase 171-fix: Add join_inputs to value_map
|
||||
for join_in in &boundary.join_inputs {
|
||||
if let Some(remapped) = remapper.get_value(*join_in) {
|
||||
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
|
||||
if let Some(ref mut current_func) = builder.current_function {
|
||||
BoundaryInjector::inject_boundary_copies(
|
||||
|
||||
@ -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)?;
|
||||
|
||||
// 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)?;
|
||||
|
||||
// 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
|
||||
remap_values(builder, &used_values, &mut remapper, debug)?;
|
||||
|
||||
|
||||
@ -385,6 +385,8 @@ mod tests {
|
||||
join_inputs: vec![],
|
||||
host_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)
|
||||
|
||||
@ -70,6 +70,61 @@ impl MirBuilder {
|
||||
// Phase 195: Use unified trace
|
||||
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)
|
||||
// Pattern 2 lowerer ignores the scope anyway, so this is just a placeholder
|
||||
use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||
@ -86,13 +141,13 @@ impl MirBuilder {
|
||||
variable_definitions: BTreeMap::new(),
|
||||
};
|
||||
|
||||
// Call Pattern 2 lowerer
|
||||
let join_module = match lower_loop_with_break_minimal(scope) {
|
||||
Some(module) => module,
|
||||
None => {
|
||||
// Phase 169 / Phase 171-fix: Call Pattern 2 lowerer with ConditionEnv
|
||||
let join_module = match lower_loop_with_break_minimal(scope, condition, &env) {
|
||||
Ok(module) => module,
|
||||
Err(e) => {
|
||||
// Phase 195: Use unified trace
|
||||
trace::trace().debug("pattern2", "Pattern 2 lowerer returned None");
|
||||
return Ok(None);
|
||||
trace::trace().debug("pattern2", &format!("Pattern 2 lowerer failed: {}", e));
|
||||
return Err(format!("[cf_loop/pattern2] Lowering failed: {}", e));
|
||||
}
|
||||
};
|
||||
|
||||
@ -119,11 +174,19 @@ impl MirBuilder {
|
||||
);
|
||||
|
||||
// Merge JoinIR blocks into current function
|
||||
// Phase 188-Impl-2: Create and pass JoinInlineBoundary for Pattern 2
|
||||
let boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only(
|
||||
// Phase 171-fix: Create boundary with ConditionBindings
|
||||
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![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)
|
||||
let _ = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;
|
||||
|
||||
|
||||
@ -181,13 +181,13 @@ impl MirBuilder {
|
||||
variable_definitions: BTreeMap::new(),
|
||||
};
|
||||
|
||||
// Call Pattern 4 lowerer (Phase 197: pass carrier_updates for semantic correctness)
|
||||
let (join_module, exit_meta) = match lower_loop_with_continue_minimal(scope, &carrier_info, &carrier_updates) {
|
||||
Some(result) => result,
|
||||
None => {
|
||||
// Phase 169: Call Pattern 4 lowerer with condition AST
|
||||
let (join_module, exit_meta) = match lower_loop_with_continue_minimal(scope, condition, self, &carrier_info, &carrier_updates) {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
// Phase 195: Use unified trace
|
||||
trace::trace().debug("pattern4", "Pattern 4 lowerer returned None");
|
||||
return Ok(None);
|
||||
trace::trace().debug("pattern4", &format!("Pattern 4 lowerer failed: {}", e));
|
||||
return Err(format!("[cf_loop/pattern4] Lowering failed: {}", e));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -40,6 +40,7 @@ impl MirBuilder {
|
||||
// - Core OFF では従来通り dev フラグで opt-in
|
||||
// Note: Arity does NOT include implicit `me` receiver
|
||||
// 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 is_target = match func_name.as_str() {
|
||||
"main" => true, // Phase 188-Impl-1: Enable JoinIR for main function (Pattern 1)
|
||||
@ -64,6 +65,16 @@ impl MirBuilder {
|
||||
== 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,
|
||||
};
|
||||
|
||||
|
||||
@ -33,15 +33,18 @@ impl BoundaryInjector {
|
||||
value_map: &HashMap<ValueId, ValueId>,
|
||||
debug: bool,
|
||||
) -> Result<(), String> {
|
||||
// Boundary が空の場合はスキップ
|
||||
if boundary.join_inputs.is_empty() {
|
||||
// Phase 171-fix: Check both join_inputs and condition_bindings
|
||||
let total_inputs = boundary.join_inputs.len() + boundary.condition_bindings.len();
|
||||
if total_inputs == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if debug {
|
||||
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.condition_bindings.len(),
|
||||
entry_block_id
|
||||
);
|
||||
}
|
||||
@ -54,6 +57,7 @@ impl BoundaryInjector {
|
||||
// Copy instructions を生成して挿入
|
||||
let mut copy_instructions = Vec::new();
|
||||
|
||||
// Phase 171: Inject Copy instructions for join_inputs (loop parameters)
|
||||
for (join_input, host_input) in boundary
|
||||
.join_inputs
|
||||
.iter()
|
||||
@ -73,12 +77,36 @@ impl BoundaryInjector {
|
||||
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[BoundaryInjector] Copy {:?} = Copy {:?}",
|
||||
"[BoundaryInjector] Join input: Copy {:?} = Copy {:?}",
|
||||
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 を挿入
|
||||
// Reverse order to preserve original order when inserting at position 0
|
||||
// Phase 189 FIX: Also insert corresponding spans
|
||||
|
||||
370
src/mir/join_ir/lowering/bool_expr_lowerer.rs
Normal file
370
src/mir/join_ir/lowering/bool_expr_lowerer.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
596
src/mir/join_ir/lowering/condition_to_joinir.rs
Normal file
596
src/mir/join_ir/lowering/condition_to_joinir.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@ -164,6 +164,47 @@ pub struct JoinInlineBoundary {
|
||||
/// ]
|
||||
/// ```
|
||||
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 {
|
||||
@ -185,6 +226,9 @@ impl JoinInlineBoundary {
|
||||
#[allow(deprecated)]
|
||||
host_outputs: 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)]
|
||||
host_outputs,
|
||||
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)]
|
||||
host_outputs,
|
||||
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)]
|
||||
host_outputs: vec![],
|
||||
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.host_inputs.len(), 1);
|
||||
assert_eq!(boundary.join_outputs.len(), 0);
|
||||
#[allow(deprecated)]
|
||||
{
|
||||
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]
|
||||
|
||||
@ -341,7 +341,9 @@ pub fn lower_loop_with_break_to_joinir(
|
||||
};
|
||||
|
||||
// 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()
|
||||
// This function (lower_loop_with_break_to_joinir) is currently not used by the router.
|
||||
|
||||
@ -55,6 +55,8 @@
|
||||
//!
|
||||
//! 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::{
|
||||
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule,
|
||||
@ -85,11 +87,13 @@ use crate::mir::ValueId;
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `_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
|
||||
///
|
||||
/// * `Some(JoinModule)` - Successfully lowered to JoinIR
|
||||
/// * `None` - Pattern not matched (fallback to other lowerers)
|
||||
/// * `Ok(JoinModule)` - Successfully lowered to JoinIR
|
||||
/// * `Err(String)` - Pattern not matched or lowering error
|
||||
///
|
||||
/// # Boundary Contract
|
||||
///
|
||||
@ -97,7 +101,16 @@ use crate::mir::ValueId;
|
||||
/// - **Input slot**: ValueId(0) in main function represents the loop variable init
|
||||
/// - **Output slot**: k_exit returns the final loop variable value
|
||||
/// - **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)
|
||||
// JoinIR has NO knowledge of host ValueIds - boundary handled separately
|
||||
let mut value_counter = 0u32;
|
||||
@ -125,16 +138,24 @@ pub fn lower_loop_with_break_minimal(_scope: LoopScopeShape) -> Option<JoinModul
|
||||
|
||||
// loop_step locals
|
||||
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
|
||||
let exit_cond = alloc_value(); // ValueId(5) - !(i < 3)
|
||||
let const_2 = alloc_value(); // ValueId(6) - break limit
|
||||
let break_cond = alloc_value(); // ValueId(7) - i >= 2
|
||||
let const_1 = alloc_value(); // ValueId(8) - increment constant
|
||||
let i_next = alloc_value(); // ValueId(9) - i + 1
|
||||
|
||||
// Phase 169 / Phase 171-fix: Lower condition using condition_to_joinir helper with ConditionEnv
|
||||
// This will allocate ValueIds dynamically based on condition complexity
|
||||
let (cond_value, mut cond_instructions) = lower_condition_to_joinir(
|
||||
condition,
|
||||
&mut alloc_value,
|
||||
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
|
||||
let i_exit = alloc_value(); // ValueId(10) - exit parameter (PHI)
|
||||
let i_exit = alloc_value(); // Exit parameter (PHI)
|
||||
|
||||
// ==================================================================
|
||||
// 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
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_3,
|
||||
value: ConstValue::Integer(3),
|
||||
}));
|
||||
// Insert all condition evaluation instructions
|
||||
loop_step_func.body.append(&mut cond_instructions);
|
||||
|
||||
// Step 2: cmp_lt = (i < 3)
|
||||
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
|
||||
// Negate the condition for exit check: exit_cond = !cond_value
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst: exit_cond,
|
||||
op: UnaryOp::Not,
|
||||
operand: cmp_lt,
|
||||
operand: cond_value,
|
||||
}));
|
||||
|
||||
// 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
|
||||
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] Condition from AST (not hardcoded)");
|
||||
eprintln!("[joinir/pattern2] Exit PHI: k_exit receives i from both natural exit and break");
|
||||
|
||||
Some(join_module)
|
||||
Ok(join_module)
|
||||
}
|
||||
|
||||
@ -44,7 +44,10 @@
|
||||
//!
|
||||
//! 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::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_update_analyzer::{UpdateExpr, UpdateRhs};
|
||||
use crate::mir::join_ir::{
|
||||
@ -77,13 +80,15 @@ use std::collections::HashMap;
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `_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_updates` - Phase 197: Update expressions for each carrier variable
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Some((JoinModule, ExitMeta))` - Successfully lowered to JoinIR with exit metadata
|
||||
/// * `None` - Pattern not matched (fallback to other lowerers)
|
||||
/// * `Ok((JoinModule, ExitMeta))` - Successfully lowered to JoinIR with exit metadata
|
||||
/// * `Err(String)` - Pattern not matched or lowering error
|
||||
///
|
||||
/// # Boundary Contract (Phase 196 updated)
|
||||
///
|
||||
@ -94,9 +99,11 @@ use std::collections::HashMap;
|
||||
/// - **Caller responsibility**: Create JoinInlineBoundary to map ValueIds
|
||||
pub fn lower_loop_with_continue_minimal(
|
||||
_scope: LoopScopeShape,
|
||||
condition: &ASTNode,
|
||||
builder: &mut MirBuilder,
|
||||
carrier_info: &CarrierInfo,
|
||||
carrier_updates: &HashMap<String, UpdateExpr>,
|
||||
) -> Option<(JoinModule, ExitMeta)> {
|
||||
) -> Result<(JoinModule, ExitMeta), String> {
|
||||
// Phase 196: Use local ValueId allocator (sequential from 0)
|
||||
// JoinIR has NO knowledge of host ValueIds - boundary handled separately
|
||||
let mut value_counter = 0u32;
|
||||
@ -140,9 +147,34 @@ pub fn lower_loop_with_continue_minimal(
|
||||
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
|
||||
let const_10 = alloc_value();
|
||||
let cmp_lt = alloc_value();
|
||||
let exit_cond = alloc_value();
|
||||
let const_1 = 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
|
||||
.body
|
||||
.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,
|
||||
}));
|
||||
// Insert all condition evaluation instructions
|
||||
loop_step_func.body.append(&mut cond_instructions);
|
||||
|
||||
// Negate the condition for exit check: exit_cond = !cond_value
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst: exit_cond,
|
||||
op: UnaryOp::Not,
|
||||
operand: cmp_lt,
|
||||
operand: cond_value,
|
||||
}));
|
||||
|
||||
// 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);
|
||||
|
||||
eprintln!(
|
||||
"[joinir/pattern4] ExitMeta total: {} bindings",
|
||||
"[joinir/pattern4] Phase 169: ExitMeta total: {} bindings (condition from AST)",
|
||||
exit_meta.exit_values.len()
|
||||
);
|
||||
|
||||
Some((join_module, exit_meta))
|
||||
Ok((join_module, exit_meta))
|
||||
}
|
||||
|
||||
@ -14,9 +14,12 @@
|
||||
//! - `funcscanner_append_defs.rs`: FuncScannerBox._append_defs/2 の配列結合 lowering(Phase 27.14)
|
||||
//! - `if_select.rs`: Phase 33 If/Else → Select lowering
|
||||
//! - `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 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 exit_args_resolver;
|
||||
pub mod funcscanner_append_defs;
|
||||
|
||||
76
tools/selfhost/json_parser_array_min.hako
Normal file
76
tools/selfhost/json_parser_array_min.hako
Normal 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
|
||||
}
|
||||
}
|
||||
71
tools/selfhost/json_parser_object_min.hako
Normal file
71
tools/selfhost/json_parser_object_min.hako
Normal 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
|
||||
}
|
||||
}
|
||||
54
tools/selfhost/json_parser_string_min.hako
Normal file
54
tools/selfhost/json_parser_string_min.hako
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user