feat(joinir): Phase 240-EX - Pattern2 header condition ExprLowerer integration
Implementation: - Add make_pattern2_scope_manager() helper for DRY - Header conditions use ExprLowerer for supported patterns - Legacy fallback for unsupported patterns - Fail-Fast on supported patterns that fail Tests: - 4 new tests (all pass) - test_expr_lowerer_supports_simple_header_condition_i_less_literal - test_expr_lowerer_supports_header_condition_var_less_var - test_expr_lowerer_header_condition_generates_expected_instructions - test_pattern2_header_condition_via_exprlowerer Also: Archive old phase documentation (34k lines removed) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -1,186 +0,0 @@
|
||||
# Phase 170-C-2: LoopUpdateSummaryBox 設計
|
||||
|
||||
## 概要
|
||||
|
||||
CaseALoweringShape の検出精度を向上させるため、ループの更新パターンを解析する専用 Box を導入する。
|
||||
|
||||
## 背景
|
||||
|
||||
### 現状 (Phase 170-C-1)
|
||||
- `detect_with_carrier_name()` で carrier 名ヒューリスティックを使用
|
||||
- `i`, `e`, `idx` → StringExamination
|
||||
- その他 → ArrayAccumulation
|
||||
|
||||
### 問題点
|
||||
- 名前だけでは不正確(`sum` という名前でも CounterLike かもしれない)
|
||||
- 実際の更新式を見ていない
|
||||
|
||||
## 設計
|
||||
|
||||
### 1. UpdateKind 列挙型
|
||||
|
||||
```rust
|
||||
/// キャリア変数の更新パターン
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum UpdateKind {
|
||||
/// カウンタ系: i = i + 1, i = i - 1, i += 1
|
||||
/// 典型的な skip/trim パターン
|
||||
CounterLike,
|
||||
|
||||
/// 蓄積系: result = result + x, arr.push(x), list.append(x)
|
||||
/// 典型的な collect/filter パターン
|
||||
AccumulationLike,
|
||||
|
||||
/// 判定不能
|
||||
Other,
|
||||
}
|
||||
```
|
||||
|
||||
### 2. CarrierUpdateInfo 構造体
|
||||
|
||||
```rust
|
||||
/// 単一キャリアの更新情報
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CarrierUpdateInfo {
|
||||
/// キャリア変数名
|
||||
pub name: String,
|
||||
|
||||
/// 更新パターン
|
||||
pub kind: UpdateKind,
|
||||
}
|
||||
```
|
||||
|
||||
### 3. LoopUpdateSummary 構造体
|
||||
|
||||
```rust
|
||||
/// ループ全体の更新サマリ
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct LoopUpdateSummary {
|
||||
/// 各キャリアの更新情報
|
||||
pub carriers: Vec<CarrierUpdateInfo>,
|
||||
}
|
||||
|
||||
impl LoopUpdateSummary {
|
||||
/// 単一 CounterLike キャリアを持つか
|
||||
pub fn has_single_counter(&self) -> bool {
|
||||
self.carriers.len() == 1
|
||||
&& self.carriers[0].kind == UpdateKind::CounterLike
|
||||
}
|
||||
|
||||
/// AccumulationLike キャリアを含むか
|
||||
pub fn has_accumulation(&self) -> bool {
|
||||
self.carriers.iter().any(|c| c.kind == UpdateKind::AccumulationLike)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. analyze_loop_updates_ast 関数
|
||||
|
||||
```rust
|
||||
/// AST からループ更新パターンを解析
|
||||
///
|
||||
/// # Phase 170-C-2 暫定実装
|
||||
/// - 名前ヒューリスティックを内部で使用
|
||||
/// - 将来的に AST 解析に置き換え
|
||||
pub fn analyze_loop_updates_ast(
|
||||
_condition: &ASTNode,
|
||||
_body: &[ASTNode],
|
||||
carrier_names: &[String],
|
||||
) -> LoopUpdateSummary {
|
||||
let carriers = carrier_names
|
||||
.iter()
|
||||
.map(|name| {
|
||||
let kind = if is_typical_index_name(name) {
|
||||
UpdateKind::CounterLike
|
||||
} else {
|
||||
UpdateKind::AccumulationLike
|
||||
};
|
||||
CarrierUpdateInfo {
|
||||
name: name.clone(),
|
||||
kind,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
LoopUpdateSummary { carriers }
|
||||
}
|
||||
```
|
||||
|
||||
## LoopFeatures への統合
|
||||
|
||||
```rust
|
||||
pub struct LoopFeatures {
|
||||
pub has_break: bool,
|
||||
pub has_continue: bool,
|
||||
pub has_if: bool,
|
||||
pub has_if_else_phi: bool,
|
||||
pub carrier_count: usize,
|
||||
pub break_count: usize,
|
||||
pub continue_count: usize,
|
||||
|
||||
// Phase 170-C-2 追加
|
||||
pub update_summary: Option<LoopUpdateSummary>,
|
||||
}
|
||||
```
|
||||
|
||||
## CaseALoweringShape での利用
|
||||
|
||||
```rust
|
||||
pub fn detect_from_features(
|
||||
features: &LoopFeatures,
|
||||
carrier_count: usize,
|
||||
has_progress_carrier: bool,
|
||||
) -> Self {
|
||||
// ... 既存チェック ...
|
||||
|
||||
// Phase 170-C-2: UpdateSummary を優先
|
||||
if let Some(ref summary) = features.update_summary {
|
||||
if summary.has_single_counter() {
|
||||
return CaseALoweringShape::StringExamination;
|
||||
}
|
||||
if summary.has_accumulation() {
|
||||
return CaseALoweringShape::ArrayAccumulation;
|
||||
}
|
||||
}
|
||||
|
||||
// フォールバック: carrier 数のみ
|
||||
match carrier_count {
|
||||
1 => CaseALoweringShape::Generic,
|
||||
2.. => CaseALoweringShape::IterationWithAccumulation,
|
||||
_ => CaseALoweringShape::NotCaseA,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## ファイル配置
|
||||
|
||||
```
|
||||
src/mir/join_ir/lowering/
|
||||
├── loop_update_summary.rs # 新規: UpdateKind, LoopUpdateSummary
|
||||
├── loop_to_join.rs # 更新: analyze_loop_updates_ast 呼び出し
|
||||
└── loop_scope_shape/
|
||||
└── case_a_lowering_shape.rs # 更新: UpdateSummary 参照
|
||||
```
|
||||
|
||||
## 移行計画
|
||||
|
||||
### Phase 170-C-2a (今回)
|
||||
- `loop_update_summary.rs` 骨格作成
|
||||
- 名前ヒューリスティックを内部に閉じ込め
|
||||
- LoopFeatures への統合は保留
|
||||
|
||||
### Phase 170-C-2b (将来)
|
||||
- AST 解析で実際の更新式を判定
|
||||
- `i = i + 1` → CounterLike
|
||||
- `result.push(x)` → AccumulationLike
|
||||
|
||||
### Phase 170-C-3 (将来)
|
||||
- MIR ベース解析(AST が使えない場合の代替)
|
||||
- BinOp 命令パターンマッチング
|
||||
|
||||
## 利点
|
||||
|
||||
1. **関心の分離**: 更新パターン解析が独立した Box に
|
||||
2. **差し替え容易**: 名前 → AST → MIR と段階的に精度向上可能
|
||||
3. **テスト容易**: LoopUpdateSummary を直接テスト可能
|
||||
4. **後方互換**: LoopFeatures.update_summary は Option なので影響最小
|
||||
@ -1,226 +0,0 @@
|
||||
# Phase 170-D Bug Fix Verification - Summary Report
|
||||
|
||||
**Date**: 2025-12-07
|
||||
**Status**: ✅ Bug Fix Verified and Documented
|
||||
**Files Updated**: 3 documentation files, 0 code changes (user implemented fix)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Executive Summary
|
||||
|
||||
The **LoopConditionScopeBox function parameter misclassification bug** has been successfully verified. Function parameters are now correctly classified as **OuterLocal** instead of being incorrectly treated as **LoopBodyLocal**.
|
||||
|
||||
**Key Result**: JsonParser loops now pass variable scope validation. Remaining errors are legitimate Pattern 5+ feature gaps (method calls), not bugs.
|
||||
|
||||
---
|
||||
|
||||
## 📊 Verification Results
|
||||
|
||||
### ✅ Test 1: Function Parameter Classification
|
||||
|
||||
**Test File**: `/tmp/test_jsonparser_simple.hako`
|
||||
**Loop**: Pattern 2 with function parameters `s` and `pos`
|
||||
|
||||
**Result**:
|
||||
```
|
||||
✅ [joinir/pattern2] Phase 170-D: Condition variables verified: {"pos", "s", "len"}
|
||||
⚠️ New error: MethodCall .substring() not supported (Pattern 5+ feature)
|
||||
```
|
||||
|
||||
**Analysis**:
|
||||
- ✅ **Bug fix works**: `s` and `pos` correctly classified as OuterLocal
|
||||
- ✅ **No more misclassification errors**: Variable scope validation passes
|
||||
- ⚠️ **Different blocker**: Method calls in loop body (legitimate feature gap)
|
||||
|
||||
---
|
||||
|
||||
### ✅ Test 2: LoopBodyLocal Correct Rejection
|
||||
|
||||
**Test File**: `local_tests/test_trim_main_pattern.hako`
|
||||
**Loop**: Pattern 2 with LoopBodyLocal `ch` in break condition
|
||||
|
||||
**Result**:
|
||||
```
|
||||
✅ [joinir/pattern2] Phase 170-D: Condition variables verified: {"ch", "end", "start"}
|
||||
❌ [ERROR] Variable 'ch' not bound in ConditionEnv
|
||||
```
|
||||
|
||||
**Analysis**:
|
||||
- ✅ **Correct rejection**: `ch` is defined inside loop (`local ch = ...`)
|
||||
- ✅ **Accurate error message**: "Variable 'ch' not bound" (not misclassified)
|
||||
- ✅ **Expected behavior**: Pattern 2 doesn't support LoopBodyLocal in break conditions
|
||||
|
||||
**Conclusion**: This is the **correct rejection** - not a bug, needs Pattern 5+ support.
|
||||
|
||||
---
|
||||
|
||||
### ✅ Test 3: JsonParser Full File
|
||||
|
||||
**Test File**: `tools/hako_shared/json_parser.hako`
|
||||
|
||||
**Result**:
|
||||
```
|
||||
❌ [ERROR] Unsupported expression in value context: MethodCall { ... }
|
||||
```
|
||||
|
||||
**Analysis**:
|
||||
- ✅ **Variable classification working**: No "incorrect variable scope" errors
|
||||
- ⚠️ **New blocker**: Method calls in loop conditions (`s.length()`)
|
||||
- ✅ **Bug fix validated**: JsonParser now hits different limitations (not variable scope bugs)
|
||||
|
||||
---
|
||||
|
||||
## 📁 Documentation Created
|
||||
|
||||
### 1. `/home/tomoaki/git/hakorune-selfhost/docs/development/current/main/phase170-d-fix-verification.md` ✅
|
||||
|
||||
**Comprehensive verification report**:
|
||||
- Bug fix details (root cause + fix)
|
||||
- Test results before/after (3 test cases)
|
||||
- Pattern 1-4 coverage analysis
|
||||
- Verification commands
|
||||
- Impact assessment
|
||||
- Next steps (Pattern 5+ implementation)
|
||||
|
||||
### 2. `CURRENT_TASK.md` Updated ✅
|
||||
|
||||
**Added Section**: "Bug Fix: Function Parameter Misclassification"
|
||||
- Issue description
|
||||
- Root cause
|
||||
- Fix location (lines 175-184)
|
||||
- Impact summary
|
||||
- Link to verification doc
|
||||
|
||||
### 3. `phase170-d-impl-design.md` Updated ✅
|
||||
|
||||
**Added Section**: "Bug Fix: Function Parameter Misclassification (2025-12-07)"
|
||||
- Detailed root cause analysis
|
||||
- Before/after code comparison
|
||||
- Impact assessment
|
||||
- Test results
|
||||
- Lessons learned
|
||||
- Design principles
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Bug Fix Technical Details
|
||||
|
||||
**File**: `src/mir/loop_pattern_detection/condition_var_analyzer.rs`
|
||||
**Lines**: 175-184
|
||||
|
||||
**The Fix** (user implemented):
|
||||
```rust
|
||||
// At this point:
|
||||
// - The variable is NOT in body_locals
|
||||
// - There is no explicit definition info for it
|
||||
//
|
||||
// This typically means "function parameter" or "outer local"
|
||||
// (e.g. JsonParserBox.s, .pos, etc.). Those should be treated
|
||||
// as OuterLocal for condition analysis, otherwise we wrongly
|
||||
// block valid loops as using loop-body-local variables.
|
||||
true // ✅ Default to OuterLocal for function parameters
|
||||
```
|
||||
|
||||
**Key Insight**: Unknown variables (not in `variable_definitions`) are typically **function parameters** or **outer locals**, not loop-body-locals. The fix defaults them to the safer (OuterLocal) classification.
|
||||
|
||||
---
|
||||
|
||||
## 📈 Impact Assessment
|
||||
|
||||
### What the Fix Achieves ✅
|
||||
|
||||
1. ✅ **Function parameters work**: `s`, `pos` in JsonParser methods
|
||||
2. ✅ **Carrier variables work**: `start`, `end` in trim loops
|
||||
3. ✅ **Outer locals work**: `len`, `maxLen` from outer scope
|
||||
4. ✅ **Correct rejection**: LoopBodyLocal `ch` properly rejected (not a bug)
|
||||
|
||||
### What Still Needs Work ⚠️
|
||||
|
||||
**Pattern 5+ Features** (not bugs):
|
||||
- Method calls in conditions: `loop(pos < s.length())`
|
||||
- Method calls in loop body: `s.substring(pos, pos+1)`
|
||||
- LoopBodyLocal in break conditions: `if ch == " " { break }`
|
||||
|
||||
**Other Issues** (orthogonal):
|
||||
- Exit Line / Boundary errors (separate architectural issues)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
### Priority 1: Pattern 5+ Implementation
|
||||
|
||||
**Target loops**:
|
||||
- `_trim` loops (LoopBodyLocal `ch` in break condition)
|
||||
- `_parse_object`, `_parse_array` (method calls in loop body)
|
||||
|
||||
**Estimated impact**: 10-15 additional loops in JsonParser
|
||||
|
||||
### Priority 2: .hako Rewrite Strategy
|
||||
|
||||
**Simplification approach**:
|
||||
- Hoist `.length()` calls to outer locals
|
||||
- Pre-compute `.substring()` results outside loop
|
||||
- Simplify break conditions to use simple comparisons
|
||||
|
||||
**Trade-off**: Some loops may be easier to rewrite than to implement Pattern 5+
|
||||
|
||||
### Priority 3: Coverage Metrics
|
||||
|
||||
**Systematic observation needed**:
|
||||
```bash
|
||||
# Run coverage analysis on JsonParser
|
||||
./tools/analyze_joinir_coverage.sh tools/hako_shared/json_parser.hako
|
||||
```
|
||||
|
||||
**Expected categories**:
|
||||
- Pattern 1: Simple loops (no break/continue)
|
||||
- Pattern 2: Loops with break (variable scope OK, method calls blocked)
|
||||
- Pattern 3: Loops with if-else PHI
|
||||
- Pattern 4: Loops with continue
|
||||
- Unsupported: Pattern 5+ needed (method calls, LoopBodyLocal conditions)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Conclusion
|
||||
|
||||
**Bug Fix Status**: ✅ Complete and Verified
|
||||
|
||||
**Key Achievement**: Function parameters correctly classified as OuterLocal
|
||||
|
||||
**Verification Outcome**: All tests demonstrate correct behavior:
|
||||
- Function parameters: ✅ Correctly classified
|
||||
- LoopBodyLocal: ✅ Correctly rejected
|
||||
- Error messages: ✅ Accurate (method calls, not variable scope)
|
||||
|
||||
**Overall Impact**: Significant progress - variable scope classification is now correct. Remaining errors are **legitimate feature gaps** (Pattern 5+ needed), not misclassification bugs.
|
||||
|
||||
**Build Status**: ✅ `cargo build --release` successful (0 errors, 50 warnings)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Verification Commands Used
|
||||
|
||||
```bash
|
||||
# Build verification
|
||||
cargo build --release
|
||||
|
||||
# Test 1: Function parameter loop
|
||||
NYASH_JOINIR_DEBUG=1 NYASH_JOINIR_STRUCTURE_ONLY=1 \
|
||||
./target/release/hakorune /tmp/test_jsonparser_simple.hako 2>&1 | \
|
||||
grep -E "Phase 170-D|Error"
|
||||
|
||||
# Test 2: TrimTest (LoopBodyLocal)
|
||||
NYASH_JOINIR_DEBUG=1 NYASH_JOINIR_STRUCTURE_ONLY=1 \
|
||||
./target/release/hakorune local_tests/test_trim_main_pattern.hako 2>&1 | \
|
||||
grep -E "Phase 170-D|Variable 'ch'"
|
||||
|
||||
# Test 3: JsonParser full
|
||||
NYASH_JOINIR_STRUCTURE_ONLY=1 \
|
||||
./target/release/hakorune tools/hako_shared/json_parser.hako 2>&1 | \
|
||||
tail -40
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**End of Report**
|
||||
@ -1,255 +0,0 @@
|
||||
# Phase 170-D Bug Fix Verification
|
||||
|
||||
**Date**: 2025-12-07
|
||||
**Status**: Fix Complete ✅
|
||||
**Impact**: Function parameters now correctly classified as OuterLocal
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
The LoopConditionScopeBox function parameter misclassification bug has been fixed. Function parameters (`s`, `pos`, etc.) are now correctly treated as **OuterLocal** instead of being incorrectly defaulted to **LoopBodyLocal**.
|
||||
|
||||
---
|
||||
|
||||
## Bug Fix Details
|
||||
|
||||
**File**: `src/mir/loop_pattern_detection/condition_var_analyzer.rs`
|
||||
|
||||
**Root Cause**:
|
||||
- Unknown variables (not in `variable_definitions`) were defaulted to LoopBodyLocal
|
||||
- Function parameters have no explicit definition in the loop body, so they appeared "unknown"
|
||||
- Result: Valid loops using function parameters were incorrectly rejected
|
||||
|
||||
**Fix**:
|
||||
```rust
|
||||
pub fn is_outer_scope_variable(var_name: &str, scope: Option<&LoopScopeShape>) -> bool {
|
||||
match scope {
|
||||
None => false,
|
||||
Some(scope) => {
|
||||
// ① body_locals に入っていたら絶対に outer ではない
|
||||
if scope.body_locals.contains(var_name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ② pinned(ループ引数など)は outer 扱い
|
||||
if scope.pinned.contains(var_name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// ③ variable_definitions の情報がある場合だけ、ブロック分布で判断
|
||||
if let Some(def_blocks) = scope.variable_definitions.get(var_name) {
|
||||
// (Carrier detection logic...)
|
||||
// ...
|
||||
return false; // body で定義されている → body-local
|
||||
}
|
||||
|
||||
// ④ どこにも出てこない変数 = 関数パラメータ/外側ローカル → OuterLocal
|
||||
true // ← KEY FIX: Default to OuterLocal, not LoopBodyLocal
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Change**: Lines 175-184 now default unknown variables to **OuterLocal** (function parameters).
|
||||
|
||||
---
|
||||
|
||||
## Test Results After Fix
|
||||
|
||||
### Test 1: Simple Function Parameter Loop
|
||||
|
||||
**File**: `/tmp/test_jsonparser_simple.hako`
|
||||
|
||||
**Loop Pattern**: Pattern 2 (loop with break), using function parameters `s` and `pos`
|
||||
|
||||
**Before Fix**:
|
||||
```
|
||||
❌ UnsupportedPattern: Variable 's' and 'pos' incorrectly classified as LoopBodyLocal
|
||||
```
|
||||
|
||||
**After Fix**:
|
||||
```
|
||||
✅ [joinir/pattern2] Phase 170-D: Condition variables verified: {"pos", "s", "len"}
|
||||
⚠️ Different error: Method call `.substring()` not supported in loop body (separate limitation)
|
||||
```
|
||||
|
||||
**Analysis**:
|
||||
- ✅ **Function parameters correctly classified**: `s` and `pos` are now OuterLocal
|
||||
- ⚠️ **New blocker**: Method calls in loop body (Pattern 5+ feature)
|
||||
- **Impact**: Bug fix works correctly - variable classification is fixed
|
||||
|
||||
---
|
||||
|
||||
### Test 2: TrimTest (LoopBodyLocal in Break Condition)
|
||||
|
||||
**File**: `local_tests/test_trim_main_pattern.hako`
|
||||
|
||||
**Loop Pattern**: Pattern 2, using LoopBodyLocal `ch` in break condition
|
||||
|
||||
**Result**:
|
||||
```
|
||||
✅ [joinir/pattern2] Phase 170-D: Condition variables verified: {"ch", "end", "start"}
|
||||
❌ [ERROR] Variable 'ch' not bound in ConditionEnv
|
||||
```
|
||||
|
||||
**Analysis**:
|
||||
- ✅ **Correctly rejects LoopBodyLocal**: `ch` is defined inside loop (`local ch = ...`)
|
||||
- ✅ **Correct error message**: "Variable 'ch' not bound" (not misclassified)
|
||||
- ✅ **Expected behavior**: Pattern 2 doesn't support LoopBodyLocal in break conditions
|
||||
|
||||
**Validation**: This is the **correct rejection** - `ch` should need Pattern 5+ support.
|
||||
|
||||
---
|
||||
|
||||
### Test 3: JsonParser Full File
|
||||
|
||||
**File**: `tools/hako_shared/json_parser.hako`
|
||||
|
||||
**Result**:
|
||||
```
|
||||
❌ [ERROR] Unsupported expression in value context: MethodCall { object: Variable { name: "s" }, method: "length", ... }
|
||||
```
|
||||
|
||||
**Analysis**:
|
||||
- ✅ **Variable classification working**: No more "variable incorrectly classified" errors
|
||||
- ⚠️ **New blocker**: Method calls in loop conditions (`s.length()`)
|
||||
- **Impact**: Bug fix successful - JsonParser now hits **different** limitations (not variable scope bugs)
|
||||
|
||||
---
|
||||
|
||||
## Pattern 1-4 Coverage Analysis (After Fix)
|
||||
|
||||
### ✅ Fixed by Bug Fix
|
||||
|
||||
**Before Fix**: X loops incorrectly rejected due to function parameter misclassification
|
||||
|
||||
**After Fix**: These loops now correctly pass variable classification:
|
||||
- **Simple loops using function parameters**: ✅ `s`, `pos` classified as OuterLocal
|
||||
- **Loops with outer locals**: ✅ `len`, `maxLen` classified correctly
|
||||
- **Carrier variables**: ✅ `start`, `end` (header+latch) classified as OuterLocal
|
||||
|
||||
### ⚠️ Remaining Limitations (Not Bug - Missing Features)
|
||||
|
||||
**Pattern 2 doesn't support**:
|
||||
1. **Method calls in loop condition**: `s.length()`, `s.substring()` → Need Pattern 5+
|
||||
2. **Method calls in loop body**: `.substring()` in break guards → Need Pattern 5+
|
||||
3. **LoopBodyLocal in break conditions**: `local ch = ...; if ch == ...` → Need Pattern 5+
|
||||
|
||||
**These are legitimate feature gaps, not bugs.**
|
||||
|
||||
---
|
||||
|
||||
## Verification Commands
|
||||
|
||||
```bash
|
||||
# Test 1: Function parameter loop (should pass variable verification)
|
||||
NYASH_JOINIR_DEBUG=1 NYASH_JOINIR_STRUCTURE_ONLY=1 \
|
||||
./target/release/hakorune /tmp/test_jsonparser_simple.hako 2>&1 | \
|
||||
grep "Phase 170-D"
|
||||
|
||||
# Expected: Phase 170-D: Condition variables verified: {"pos", "s", "len"}
|
||||
|
||||
# Test 2: LoopBodyLocal in break (should correctly reject)
|
||||
NYASH_JOINIR_DEBUG=1 NYASH_JOINIR_STRUCTURE_ONLY=1 \
|
||||
./target/release/hakorune local_tests/test_trim_main_pattern.hako 2>&1 | \
|
||||
grep "Phase 170-D\|Variable 'ch'"
|
||||
|
||||
# Expected:
|
||||
# Phase 170-D: Condition variables verified: {"ch", "end", "start"}
|
||||
# ERROR: Variable 'ch' not bound in ConditionEnv
|
||||
|
||||
# Test 3: JsonParser (should pass variable verification, fail on method calls)
|
||||
NYASH_JOINIR_STRUCTURE_ONLY=1 \
|
||||
./target/release/hakorune tools/hako_shared/json_parser.hako 2>&1 | \
|
||||
grep -E "Phase 170-D|MethodCall"
|
||||
|
||||
# Expected: Error about MethodCall, not about variable classification
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact Assessment
|
||||
|
||||
### ✅ What the Fix Achieves
|
||||
|
||||
1. **Function parameters work correctly**: `s`, `pos` in JsonParser methods
|
||||
2. **Carrier variables work correctly**: `start`, `end` in trim loops
|
||||
3. **Outer locals work correctly**: `len`, `maxLen` from outer scope
|
||||
4. **Correct rejection**: LoopBodyLocal `ch` properly rejected (not a bug)
|
||||
|
||||
### ⚠️ What Still Needs Work
|
||||
|
||||
**Pattern 5+ Features** (not covered by this fix):
|
||||
- Method calls in conditions: `loop(pos < s.length())`
|
||||
- Method calls in loop body: `s.substring(pos, pos+1)`
|
||||
- LoopBodyLocal in break conditions: `if ch == " " { break }`
|
||||
|
||||
**Exit Line & Boundary Issues** (orthogonal to this fix):
|
||||
- Some loops fail with ExitLine/Boundary errors
|
||||
- These are separate architectural issues
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Priority 1: Pattern 5+ Implementation
|
||||
|
||||
**Target loops**:
|
||||
- `_trim` loops (LoopBodyLocal `ch` in break condition)
|
||||
- `_parse_object`, `_parse_array` (method calls in loop body)
|
||||
|
||||
**Estimated impact**: 10-15 additional loops in JsonParser
|
||||
|
||||
### Priority 2: .hako Rewrite Strategy
|
||||
|
||||
**For loops with complex method calls**:
|
||||
- Hoist `.length()` calls to outer locals
|
||||
- Pre-compute `.substring()` results outside loop
|
||||
- Simplify break conditions to use simple comparisons
|
||||
|
||||
**Example rewrite**:
|
||||
```hako
|
||||
// Before (Pattern 5+ needed)
|
||||
loop(pos < s.length()) {
|
||||
local ch = s.substring(pos, pos+1)
|
||||
if ch == "}" { break }
|
||||
pos = pos + 1
|
||||
}
|
||||
|
||||
// After (Pattern 2 compatible)
|
||||
local len = s.length()
|
||||
loop(pos < len) {
|
||||
// Need to avoid method calls in break guard
|
||||
// This still requires Pattern 5+ for ch definition
|
||||
local ch = s.substring(pos, pos+1)
|
||||
if ch == "}" { break }
|
||||
pos = pos + 1
|
||||
}
|
||||
```
|
||||
|
||||
### Priority 3: Coverage Metrics
|
||||
|
||||
**Run systematic observation**:
|
||||
```bash
|
||||
# Count loops by pattern support
|
||||
./tools/analyze_joinir_coverage.sh tools/hako_shared/json_parser.hako
|
||||
|
||||
# Expected output:
|
||||
# Pattern 1: X loops
|
||||
# Pattern 2: Y loops (with method call blockers)
|
||||
# Pattern 3: Z loops
|
||||
# Pattern 4: W loops
|
||||
# Unsupported (need Pattern 5+): N loops
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
✅ **Bug Fix Complete**: Function parameters correctly classified as OuterLocal
|
||||
✅ **Verification Successful**: Tests demonstrate correct variable classification
|
||||
✅ **Expected Rejections**: LoopBodyLocal in break conditions correctly rejected
|
||||
⚠️ **Next Blockers**: Method calls in loops (Pattern 5+ features, not bugs)
|
||||
|
||||
**Overall Impact**: Significant progress - variable scope classification is now correct. Remaining errors are legitimate feature gaps, not misclassification bugs.
|
||||
@ -1,460 +0,0 @@
|
||||
# Phase 170-D-impl: LoopConditionScopeBox Implementation Design
|
||||
|
||||
**Status**: Phase 170-D-impl-3 Complete ✅
|
||||
**Last Updated**: 2025-12-07
|
||||
**Author**: Claude × Tomoaki AI Collaborative Development
|
||||
|
||||
## Overview
|
||||
|
||||
Phase 170-D implements a **Box-based variable scope classification system** for loop conditions in JoinIR lowering. This enables **Fail-Fast validation** ensuring loop conditions only reference supported variable scopes.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Modular Components
|
||||
|
||||
```
|
||||
loop_pattern_detection/
|
||||
├── mod.rs (201 lines) ← Entry point
|
||||
├── loop_condition_scope.rs (220 lines) ← Box definition
|
||||
└── condition_var_analyzer.rs (317 lines) ← Pure analysis functions
|
||||
```
|
||||
|
||||
### Design Principles
|
||||
|
||||
1. **Box Theory**: Clear separation of concerns (Box per responsibility)
|
||||
2. **Pure Functions**: condition_var_analyzer contains no side effects
|
||||
3. **Orchestration**: LoopConditionScopeBox coordinates analyzer results
|
||||
4. **Fail-Fast**: Early error detection before JoinIR generation
|
||||
|
||||
## Implementation Summary
|
||||
|
||||
### Phase 170-D-impl-1: LoopConditionScopeBox Skeleton ✅
|
||||
|
||||
**File**: `src/mir/loop_pattern_detection/loop_condition_scope.rs` (220 lines)
|
||||
|
||||
**Key Structures**:
|
||||
```rust
|
||||
pub enum CondVarScope {
|
||||
LoopParam, // Loop parameter (e.g., 'i' in loop(i < 10))
|
||||
OuterLocal, // Variables from outer scope (pre-existing)
|
||||
LoopBodyLocal, // Variables defined inside loop body
|
||||
}
|
||||
|
||||
pub struct LoopConditionScope {
|
||||
pub vars: Vec<CondVarInfo>,
|
||||
}
|
||||
|
||||
pub struct LoopConditionScopeBox;
|
||||
```
|
||||
|
||||
**Public API**:
|
||||
- `LoopConditionScopeBox::analyze()`: Main entry point
|
||||
- `LoopConditionScope::has_loop_body_local()`: Fail-Fast check
|
||||
- `LoopConditionScope::all_in()`: Scope validation
|
||||
- `LoopConditionScope::var_names()`: Extract variable names
|
||||
|
||||
### Phase 170-D-impl-2: Minimal Analysis Logic ✅
|
||||
|
||||
**File**: `src/mir/loop_pattern_detection/condition_var_analyzer.rs` (317 lines)
|
||||
|
||||
**Pure Functions**:
|
||||
|
||||
```rust
|
||||
pub fn extract_all_variables(node: &ASTNode) -> HashSet<String>
|
||||
// Recursively extracts all Variable references from AST
|
||||
// Handles: Variable, UnaryOp, BinaryOp, MethodCall, FieldAccess, Index, If
|
||||
|
||||
pub fn is_outer_scope_variable(var_name: &str, scope: Option<&LoopScopeShape>) -> bool
|
||||
// Classifies variable based on LoopScopeShape information
|
||||
// Returns true if variable is definitively from outer scope
|
||||
```
|
||||
|
||||
**Scope Classification Heuristic** (Phase 170-ultrathink Extended):
|
||||
|
||||
1. **LoopParam**: Variable is the loop parameter itself (e.g., 'i' in `loop(i < 10)`)
|
||||
- Explicitly matched by name against the loop parameter
|
||||
|
||||
2. **OuterLocal**: Variable is from outer scope (defined before loop)
|
||||
- Case A: Variable is in `pinned` set (loop parameters or passed-in variables)
|
||||
- Case B: Variable is defined ONLY in header block (not in body/exit)
|
||||
- Case C (Phase 170-ultrathink): Variable is defined in header AND latch ONLY
|
||||
- **Carrier variables**: Variables updated in latch (e.g., `i = i + 1`)
|
||||
- Not defined in body → not truly "loop-body-local"
|
||||
- Example pattern:
|
||||
```nyash
|
||||
local i = 0 // header
|
||||
loop(i < 10) {
|
||||
// ...
|
||||
i = i + 1 // latch
|
||||
}
|
||||
```
|
||||
|
||||
3. **LoopBodyLocal**: Variable is defined inside loop body (default/conservative)
|
||||
- Variables that appear in body blocks (not just header/latch)
|
||||
- Pattern 2/4 cannot handle these in conditions
|
||||
- Example:
|
||||
```nyash
|
||||
loop(i < 10) {
|
||||
local ch = getChar() // body
|
||||
if (ch == ' ') { break } // ch is LoopBodyLocal
|
||||
}
|
||||
```
|
||||
|
||||
**Scope Priority** (Phase 170-ultrathink):
|
||||
|
||||
When a variable is detected in multiple categories (e.g., due to ambiguous AST structure):
|
||||
- **LoopParam** > **OuterLocal** > **LoopBodyLocal** (most to least restrictive)
|
||||
- The `add_var()` method keeps the more restrictive classification
|
||||
- This ensures conservative but accurate classification
|
||||
|
||||
**Test Coverage**: 12 comprehensive unit tests
|
||||
|
||||
### Phase 170-D-impl-3: Pattern 2/4 Integration ✅
|
||||
|
||||
**Files Modified**:
|
||||
- `src/mir/join_ir/lowering/loop_with_break_minimal.rs` (Pattern 2)
|
||||
- `src/mir/join_ir/lowering/loop_with_continue_minimal.rs` (Pattern 4)
|
||||
|
||||
**Integration Strategy**:
|
||||
|
||||
#### Pattern 2 (loop with break)
|
||||
```rust
|
||||
// At function entry, validate BOTH loop condition AND break condition
|
||||
let loop_cond_scope = LoopConditionScopeBox::analyze(
|
||||
loop_var_name,
|
||||
&[condition, break_condition], // Check both!
|
||||
Some(&_scope),
|
||||
);
|
||||
|
||||
if loop_cond_scope.has_loop_body_local() {
|
||||
return Err("[joinir/pattern2] Unsupported condition: uses loop-body-local variables...");
|
||||
}
|
||||
```
|
||||
|
||||
#### Pattern 4 (loop with continue)
|
||||
```rust
|
||||
// At function entry, validate ONLY loop condition
|
||||
let loop_cond_scope = LoopConditionScopeBox::analyze(
|
||||
&loop_var_name,
|
||||
&[condition], // Only loop condition for Pattern 4
|
||||
Some(&_scope),
|
||||
);
|
||||
|
||||
if loop_cond_scope.has_loop_body_local() {
|
||||
return Err("[joinir/pattern4] Unsupported condition: uses loop-body-local variables...");
|
||||
}
|
||||
```
|
||||
|
||||
**Error Messages**: Clear, actionable feedback suggesting Pattern 5+
|
||||
|
||||
**Test Cases Added**:
|
||||
- `test_pattern2_accepts_loop_param_only`: ✅ PASS
|
||||
- `test_pattern2_accepts_outer_scope_variables`: ✅ PASS
|
||||
- `test_pattern2_rejects_loop_body_local_variables`: ✅ PASS
|
||||
- `test_pattern2_detects_mixed_scope_variables`: ✅ PASS
|
||||
|
||||
### Phase 170-D-impl-4: Tests and Documentation 🔄
|
||||
|
||||
**Current Status**: Implementation complete, documentation in progress
|
||||
|
||||
**Tasks**:
|
||||
1. ✅ Unit tests added to loop_with_break_minimal.rs (4 tests)
|
||||
2. ✅ Integration test verification (NYASH_JOINIR_STRUCTURE_ONLY=1)
|
||||
3. ✅ Build verification (all compilation successful)
|
||||
4. 🔄 Documentation updates:
|
||||
- ✅ This design document
|
||||
- 📝 Update CURRENT_TASK.md with completion status
|
||||
- 📝 Architecture guide update for Phase 170-D
|
||||
|
||||
## Test Results
|
||||
|
||||
### Unit Tests
|
||||
- All 4 Pattern 2 validation tests defined and ready
|
||||
- Build successful with no compilation errors
|
||||
- Integration build: `cargo build --release` ✅
|
||||
|
||||
### Integration Tests
|
||||
|
||||
**Test 1: Pattern 2 Accepts Loop Parameter Only**
|
||||
```bash
|
||||
NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune local_tests/test_pattern2_then_break.hako
|
||||
[joinir/pattern2] Phase 170-D: Condition variables verified: {"i"}
|
||||
✅ PASS
|
||||
```
|
||||
|
||||
**Test 2: Pattern 2 Rejects Loop-Body-Local Variables**
|
||||
```bash
|
||||
NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune local_tests/test_trim_main_pattern.hako
|
||||
[ERROR] ❌ [joinir/pattern2] Unsupported condition: uses loop-body-local variables: ["ch"].
|
||||
Pattern 2 supports only loop parameters and outer-scope variables.
|
||||
✅ PASS (correctly rejects)
|
||||
```
|
||||
|
||||
## Future: Phase 170-D-E and Beyond
|
||||
|
||||
### Phase 170-D-E: Advanced Patterns (Pattern 5+)
|
||||
|
||||
**Goal**: Support loop-body-local variables in conditions
|
||||
|
||||
**Approach**:
|
||||
1. Detect loop-body-local variable patterns
|
||||
|
||||
---
|
||||
|
||||
## Bug Fix Note(Phase 170-D-impl-2+)
|
||||
|
||||
Phase 166 再観測中に、JsonParserBox._parse_object(s, pos) の `s`(関数パラメータ)が
|
||||
LoopBodyLocal と誤判定される致命的バグが見つかった。
|
||||
|
||||
- 原因: `is_outer_scope_variable()` が `body_locals` を参照せず、
|
||||
`pinned` / `variable_definitions` に無い変数を「LoopBodyLocal 寄り」とみなしていた
|
||||
- 影響: 本来 Pattern2/4 でサポートすべき `loop(p < s.length())` 形式のループが
|
||||
「loop-body-local 変数使用」として UnsupportedPattern エラーになっていた
|
||||
|
||||
修正方針と実装(概略):
|
||||
|
||||
- 先に `LoopScopeShape.body_locals` を確認し、ここに含まれる変数だけを LoopBodyLocal とみなす
|
||||
- `variable_definitions` にエントリがあり、header/latch 以外で定義される変数も LoopBodyLocal とみなす
|
||||
- 上記いずれにも該当しない変数(関数パラメータや外側ローカル)は OuterLocal として扱う
|
||||
|
||||
これにより:
|
||||
|
||||
- 関数パラメータ `s`, `pos` 等は正しく OuterLocal と分類され、
|
||||
JsonParser/Trim 系の「素直な while ループ」は Pattern2/4 の対象に戻る
|
||||
- 本当にループ内で導入された変数(例: `local ch = ...`)は LoopBodyLocal のまま検出され、
|
||||
今後の Pattern5+ の設計対象として切り出される
|
||||
|
||||
詳細な実装は `src/mir/loop_pattern_detection/condition_var_analyzer.rs` の
|
||||
`is_outer_scope_variable()` および付随ユニットテストに記録されている。
|
||||
2. Expand LoopConditionScope with additional heuristics
|
||||
3. Implement selective patterns (e.g., local x = ...; while(x < N))
|
||||
4. Reuse LoopConditionScope infrastructure
|
||||
|
||||
### Phase 171: Condition Environment
|
||||
|
||||
**Goal**: Integrate with condition_to_joinir for complete lowering
|
||||
|
||||
**Current Status**: condition_to_joinir already delegates to analyze()
|
||||
|
||||
## Architecture Decisions
|
||||
|
||||
### Why Box Theory?
|
||||
|
||||
1. **Separation of Concerns**: Each Box handles one responsibility
|
||||
- LoopConditionScopeBox: Orchestration + high-level analysis
|
||||
- condition_var_analyzer: Pure extraction and classification functions
|
||||
|
||||
2. **Reusability**: Pure functions can be used independently
|
||||
- Perfect for testing
|
||||
- Can be reused in other lowerers
|
||||
- No hidden side effects
|
||||
|
||||
3. **Testability**: Each Box has clear input/output contracts
|
||||
- condition_var_analyzer: 12 unit tests
|
||||
- LoopConditionScopeBox: 4 integration tests
|
||||
|
||||
### Why Fail-Fast?
|
||||
|
||||
1. **Early Error Detection**: Catch unsupported patterns before JoinIR generation
|
||||
2. **Clear Error Messages**: Users know exactly what's unsupported
|
||||
3. **No Fallback Paths**: Aligns with Nyash design principles (no implicit degradation)
|
||||
|
||||
### Why Conservative Classification?
|
||||
|
||||
Default to LoopBodyLocal for unknown variables:
|
||||
- **Safe**: Prevents silently accepting unsupported patterns
|
||||
- **Sound**: Variable origins are often unclear from AST alone
|
||||
- **Extensible**: Future phases can refine classification
|
||||
|
||||
## Build Status
|
||||
|
||||
### Phase 170-D-impl-3 (Original)
|
||||
✅ **All Compilation Successful**
|
||||
```
|
||||
Finished `release` profile [optimized] target(s) in 24.80s
|
||||
```
|
||||
|
||||
✅ **No Compilation Errors**
|
||||
- Pattern 2 import: ✅
|
||||
- Pattern 4 import: ✅
|
||||
- All function signatures: ✅
|
||||
|
||||
⚠️ **Integration Test Warnings**: Some unrelated deprecations (not critical)
|
||||
|
||||
### Phase 170-ultrathink (Code Quality Improvements)
|
||||
✅ **Build Successful**
|
||||
```
|
||||
Finished `release` profile [optimized] target(s) in 1m 08s
|
||||
```
|
||||
|
||||
✅ **All Improvements Compiled**
|
||||
- Issue #4: Iterative extract_all_variables ✅
|
||||
- Issue #1: Extended is_outer_scope_variable ✅
|
||||
- Issue #2: Scope priority in add_var ✅
|
||||
- Issue #5: Error message consolidation (error_messages.rs) ✅
|
||||
- Issue #6: Documentation improvements ✅
|
||||
- Issue #3: 4 new unit tests added ✅
|
||||
|
||||
✅ **No Compilation Errors**
|
||||
- All pattern lowerers compile successfully
|
||||
- New error_messages module integrates cleanly
|
||||
- Test additions compile successfully
|
||||
|
||||
⚠️ **Test Build Status**: Some unrelated test compilation errors exist in other modules (not related to Phase 170-D improvements)
|
||||
|
||||
## Commit History
|
||||
|
||||
- `1356b61f`: Phase 170-D-impl-1 LoopConditionScopeBox skeleton
|
||||
- `7be72e9e`: Phase 170-D-impl-2 Minimal analysis logic
|
||||
- `25b9d016`: Phase 170-D-impl-3 Pattern2/4 integration
|
||||
- **Phase 170-ultrathink**: Code quality improvements (2025-12-07)
|
||||
- Issue #4: extract_all_variables → iterative (stack overflow prevention)
|
||||
- Issue #1: is_outer_scope_variable extended (carrier variable support)
|
||||
- Issue #2: add_var with scope priority (LoopParam > OuterLocal > LoopBodyLocal)
|
||||
- Issue #5: Error message consolidation (error_messages.rs module)
|
||||
- Issue #6: Documentation improvements (detailed scope classification)
|
||||
- Issue #3: Test coverage expansion (planned)
|
||||
|
||||
## Phase 170-ultrathink Improvements
|
||||
|
||||
**Completed Enhancements**:
|
||||
|
||||
1. **Iterative Variable Extraction** (Issue #4)
|
||||
- Converted `extract_all_variables()` from recursive to worklist-based
|
||||
- Prevents stack overflow with deeply nested OR chains
|
||||
- Performance: O(n) time, O(d) stack space (d = worklist depth)
|
||||
|
||||
2. **Carrier Variable Support** (Issue #1)
|
||||
- Extended `is_outer_scope_variable()` to recognize header+latch patterns
|
||||
- Handles loop update patterns like `i = i + 1` in latch
|
||||
- Improves accuracy for Pattern 2/4 validation
|
||||
|
||||
3. **Scope Priority System** (Issue #2)
|
||||
- `add_var()` now prioritizes LoopParam > OuterLocal > LoopBodyLocal
|
||||
- Prevents ambiguous classifications from degrading to LoopBodyLocal
|
||||
- Ensures most restrictive (accurate) scope is kept
|
||||
|
||||
4. **Error Message Consolidation** (Issue #5)
|
||||
- New `error_messages.rs` module with shared utilities
|
||||
- `format_unsupported_condition_error()` eliminates Pattern 2/4 duplication
|
||||
- `extract_body_local_names()` helper for consistent filtering
|
||||
- 2 comprehensive tests for error formatting
|
||||
|
||||
5. **Documentation Enhancement** (Issue #6)
|
||||
- Detailed scope classification heuristics with examples
|
||||
- Explicit carrier variable explanation
|
||||
- Scope priority rules documented
|
||||
|
||||
6. **Test Coverage Expansion** (Issue #3) ✅
|
||||
- `test_extract_with_array_index`: arr[i] extraction (COMPLETED)
|
||||
- `test_extract_literal_only_condition`: loop(true) edge case (COMPLETED)
|
||||
- `test_scope_header_and_latch_variable`: Carrier variable classification (COMPLETED)
|
||||
- `test_scope_priority_in_add_var`: Scope priority validation (BONUS)
|
||||
|
||||
## Bug Fix: Function Parameter Misclassification (2025-12-07)
|
||||
|
||||
### Issue
|
||||
|
||||
Function parameters (e.g., `s`, `pos` in JsonParser methods) were incorrectly classified as **LoopBodyLocal** when used in loop conditions or break guards.
|
||||
|
||||
### Root Cause
|
||||
|
||||
In `condition_var_analyzer.rs`, the `is_outer_scope_variable()` function's default case (lines 175-184) was treating unknown variables (not in `variable_definitions`) as body-local variables.
|
||||
|
||||
**Problem Logic**:
|
||||
```rust
|
||||
// OLD (buggy): Unknown variables defaulted to LoopBodyLocal
|
||||
if let Some(def_blocks) = scope.variable_definitions.get(var_name) {
|
||||
// (carrier detection...)
|
||||
return false; // body-local
|
||||
}
|
||||
// No default case → implicit false → LoopBodyLocal
|
||||
false // ❌ BUG: function parameters have no definition, so defaulted to body-local
|
||||
```
|
||||
|
||||
**Why function parameters appear "unknown"**:
|
||||
- Function parameters (`s`, `pos`) are not defined in the loop body
|
||||
- They don't appear in `variable_definitions` (which only tracks loop-internal definitions)
|
||||
- Without explicit handling, they were incorrectly treated as body-local
|
||||
|
||||
### Fix
|
||||
|
||||
**File**: `src/mir/loop_pattern_detection/condition_var_analyzer.rs` (lines 175-184)
|
||||
|
||||
```rust
|
||||
// NEW (fixed): Unknown variables default to OuterLocal (function parameters)
|
||||
if let Some(def_blocks) = scope.variable_definitions.get(var_name) {
|
||||
// (carrier detection logic...)
|
||||
return false; // body-local
|
||||
}
|
||||
|
||||
// At this point:
|
||||
// - Variable is NOT in body_locals
|
||||
// - No explicit definition info
|
||||
// This typically means "function parameter" or "outer local"
|
||||
true // ✅ FIX: Default to OuterLocal for function parameters
|
||||
```
|
||||
|
||||
**Key Change**: Default unknown variables to `OuterLocal` instead of implicitly defaulting to `LoopBodyLocal`.
|
||||
|
||||
### Impact
|
||||
|
||||
**Before Fix**:
|
||||
- ❌ JsonParser loops incorrectly rejected: "Variable 's' uses loop-body-local variables"
|
||||
- ❌ Function parameters treated as LoopBodyLocal
|
||||
- ❌ Valid Pattern 2 loops blocked by misclassification
|
||||
|
||||
**After Fix**:
|
||||
- ✅ Function parameters correctly classified as OuterLocal
|
||||
- ✅ JsonParser loops pass variable scope validation
|
||||
- ✅ LoopBodyLocal `ch` (defined with `local ch = ...`) correctly rejected
|
||||
- ⚠️ New blockers: Method calls in loops (Pattern 5+ features, not bugs)
|
||||
|
||||
### Verification
|
||||
|
||||
**Test Results**:
|
||||
|
||||
1. **Function Parameter Loop** (`/tmp/test_jsonparser_simple.hako`):
|
||||
```
|
||||
✅ [joinir/pattern2] Phase 170-D: Condition variables verified: {"pos", "s", "len"}
|
||||
⚠️ Error: MethodCall .substring() not supported (Pattern 5+ feature)
|
||||
```
|
||||
**Analysis**: Variable classification fixed, error now about method calls (separate issue)
|
||||
|
||||
2. **LoopBodyLocal in Break** (`test_trim_main_pattern.hako`):
|
||||
```
|
||||
✅ [joinir/pattern2] Phase 170-D: Condition variables verified: {"ch", "end", "start"}
|
||||
❌ [ERROR] Variable 'ch' not bound in ConditionEnv
|
||||
```
|
||||
**Analysis**: Correctly rejects `ch` (defined as `local ch = ...` inside loop)
|
||||
|
||||
**Documentation**: See [phase170-d-fix-verification.md](phase170-d-fix-verification.md) for comprehensive test results.
|
||||
|
||||
### Lessons Learned
|
||||
|
||||
**Design Principle**: When classifying variables in scope analysis:
|
||||
1. **Check explicit markers first** (`body_locals`, `pinned`)
|
||||
2. **Analyze definition locations** (`variable_definitions`)
|
||||
3. **Default to OuterLocal** for unknowns (function parameters, globals)
|
||||
|
||||
**Fail-Fast Philosophy**: The bug fix maintains fail-fast behavior while being **less strict** about unknown variables - treating them as safer (OuterLocal) rather than more restrictive (LoopBodyLocal).
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Phase 170-D-impl-4 Completion** ✅:
|
||||
- Update CURRENT_TASK.md with completion markers
|
||||
- Create integration test .hako files for unsupported patterns
|
||||
- Run full regression test suite
|
||||
|
||||
2. **Documentation** ✅:
|
||||
- Update loop pattern documentation index
|
||||
- Add quick reference for Phase 170-D validation
|
||||
- Bug fix verification document
|
||||
|
||||
3. **Future Work** (Phase 170-D-E):
|
||||
- Pattern 5+ for loop-body-local variable support
|
||||
- Extended scope heuristics
|
||||
- Condition simplification analysis
|
||||
- Method call support in loop conditions
|
||||
@ -1,110 +0,0 @@
|
||||
# Phase 170‑D: LoopConditionScopeBox 設計メモ
|
||||
|
||||
日付: 2025‑12‑07
|
||||
状態: 設計完了(実装は今後の Phase で)
|
||||
|
||||
## 背景
|
||||
|
||||
Pattern2/Pattern4(Loop with Break / Loop with Continue)は、もともと
|
||||
|
||||
- ループパラメータ(例: `i`)
|
||||
- ループ外のローカル・引数(例: `start`, `end`, `len`)
|
||||
|
||||
のみを条件式に使う前提で設計されていた。
|
||||
|
||||
しかし JsonParserBox / trim などでは、
|
||||
|
||||
```hako
|
||||
local ch = …
|
||||
if (ch != " ") { break }
|
||||
```
|
||||
|
||||
のように「ループ本体ローカル」を条件に使うパターンが現れ、この範囲を超えると
|
||||
|
||||
- バグが出たり(ValueId 伝播ミス)
|
||||
- たまたま動いたり
|
||||
|
||||
という **曖昧な状態** になっていた。
|
||||
|
||||
これを箱理論的に整理するために、条件で使われる変数の「スコープ」を明示的に分類する
|
||||
LoopConditionScopeBox を導入する。
|
||||
|
||||
## LoopConditionScopeBox の責務
|
||||
|
||||
責務は 1 つだけ:
|
||||
|
||||
> 「条件式に登場する変数が、どのスコープで定義されたか」を教える
|
||||
|
||||
### 型イメージ
|
||||
|
||||
```rust
|
||||
pub enum CondVarScope {
|
||||
LoopParam, // ループパラメータ (i)
|
||||
OuterLocal, // ループ外のローカル/引数 (start, end, len)
|
||||
LoopBodyLocal, // ループ本体で定義された変数 (ch)
|
||||
}
|
||||
|
||||
pub struct CondVarInfo {
|
||||
pub name: String,
|
||||
pub scope: CondVarScope,
|
||||
}
|
||||
|
||||
pub struct LoopConditionScope {
|
||||
pub vars: Vec<CondVarInfo>,
|
||||
}
|
||||
```
|
||||
|
||||
主なメソッド例:
|
||||
|
||||
```rust
|
||||
impl LoopConditionScope {
|
||||
pub fn has_loop_body_local(&self) -> bool { … }
|
||||
|
||||
pub fn all_in(&self, allowed: &[CondVarScope]) -> bool { … }
|
||||
|
||||
pub fn vars_in(&self, scope: CondVarScope) -> impl Iterator<Item = &CondVarInfo> { … }
|
||||
}
|
||||
```
|
||||
|
||||
### 入力 / 出力
|
||||
|
||||
入力:
|
||||
|
||||
- ループヘッダ条件 AST
|
||||
- break/continue 条件 AST(Pattern2/4)
|
||||
- LoopScopeShape(どの変数がどのスコープで定義されたか)
|
||||
|
||||
出力:
|
||||
|
||||
- `LoopConditionScope`(CondVarInfo の集合)
|
||||
|
||||
## Pattern2/Pattern4 との関係
|
||||
|
||||
Pattern2/4 は LoopConditionScopeBox の結果だけを見て「対応可否」を決める:
|
||||
|
||||
```rust
|
||||
let cond_scope = LoopConditionScopeBox::analyze(&loop_ast, &loop_scope);
|
||||
|
||||
// 対応範囲:LoopParam + OuterLocal のみ
|
||||
if !cond_scope.all_in(&[CondVarScope::LoopParam, CondVarScope::OuterLocal]) {
|
||||
return Err(JoinIrError::UnsupportedPattern { … });
|
||||
}
|
||||
```
|
||||
|
||||
これにより、
|
||||
|
||||
- いままで暗黙だった「対応範囲」が **設計として明示**される
|
||||
- LoopBodyLocal を条件に含む trim/JsonParser 系ループは
|
||||
- 現状は `[joinir/freeze] UnsupportedPattern` にする
|
||||
- 将来 Pattern5+ で扱いたくなったときに、LoopConditionScopeBox の結果を使って設計できる
|
||||
|
||||
## 将来の拡張
|
||||
|
||||
LoopBodyLocal を含む条件式を扱いたくなった場合は:
|
||||
|
||||
- LoopConditionScopeBox の結果から `vars_in(LoopBodyLocal)` を取り出し、
|
||||
- その変数を carrier に昇格させる
|
||||
- もしくは LoopHeader に「状態保持用」の追加パラメータを生やす
|
||||
- それを新しい Pattern5 として設計すれば、既存 Pattern2/4 の仕様を崩さずに拡張できる。
|
||||
|
||||
このドキュメントは設計メモのみであり、実装は別フェーズ(Phase 170‑D‑impl など)で行う。
|
||||
@ -1,252 +0,0 @@
|
||||
# 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
|
||||
@ -1,318 +0,0 @@
|
||||
# Phase 170: .hako 純正 JSON ライブラリ設計 & インベントリ
|
||||
|
||||
## 0. ゴール
|
||||
|
||||
**これから実装する .hako JSON ライブラリ(JsonParserBox)の範囲と責務をはっきり決める。**
|
||||
|
||||
目的:
|
||||
- いま散らばっている JSON 解析コード(特に hako_check 内部)を棚卸し
|
||||
- 「何を共通箱に移すか」を見える化
|
||||
- Phase 171+ の実装に向けたAPI設計を固定
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
### Phase 156 の成果と課題
|
||||
|
||||
| 内容 | 状態 | 課題 |
|
||||
|------|------|------|
|
||||
| hako_check で MIR JSON を解析 | ✅ 完了 | 320行の手動 JSON パーサ実装 |
|
||||
| HC020 が unreachable block を検出 | ✅ 完了 | 他ルールでも JSON 解析が必要 |
|
||||
| Rust 層の変更なし | ✅ | .hako に builtin JSON パーサが欲しい |
|
||||
|
||||
### 今後の課題
|
||||
|
||||
- Phase 157+ で HC021, HC022... を追加するたび JSON 解析コードが増殖
|
||||
- Program JSON (v0) を読む selfhost/Stage-B も JSON パーサが欲しい
|
||||
- 共通ライブラリ化が必須
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope / Non-scope
|
||||
|
||||
### ✅ やること
|
||||
|
||||
1. **既存 JSON 利用箇所のインベントリ**
|
||||
- どこで JSON を使っているか棚卸し
|
||||
- 読み手と書き手を把握
|
||||
|
||||
2. **hako_check 内部 JSON パーサの構造把握**
|
||||
- Phase 156 の実装を分析
|
||||
- 対応範囲と TODO を整理
|
||||
|
||||
3. **JsonParserBox の API 草案決定**
|
||||
- 最低限必要な型・関数を決定
|
||||
- MVP と Phase 172+ の区切りを決定
|
||||
|
||||
4. **利用予定箇所のマッピング**
|
||||
- hako_check, selfhost, 将来ツール
|
||||
- 誰が JsonParserBox を使うか明確化
|
||||
|
||||
5. **CURRENT_TASK 更新**
|
||||
- Phase 170 の完了記録
|
||||
|
||||
### ❌ やらないこと
|
||||
|
||||
- JsonParserBox の実装(Phase 171 へ)
|
||||
- 既存コードのリファクタリング(Phase 172 へ)
|
||||
- エスケープシーケンスの全対応(段階的に)
|
||||
|
||||
---
|
||||
|
||||
## 3. Task 1: 既存 JSON 利用箇所のインベントリ
|
||||
|
||||
### 調査対象
|
||||
|
||||
1. **Program JSON (v0) の読み手**
|
||||
- `src/runner/modes/stage_b.rs` - Stage-B 実行
|
||||
- `src/runner/modes/` 内の自動実行処理
|
||||
- `.hako` コード内の selfhost 解析
|
||||
|
||||
2. **MIR JSON の読み手**
|
||||
- `tools/hako_check/analysis_consumer.hako` (Phase 156)
|
||||
- 将来: 開発ツール / デバッグスクリプト
|
||||
|
||||
3. **その他 JSON**
|
||||
- JoinIR debug JSON
|
||||
- CFG JSON
|
||||
|
||||
### やること
|
||||
|
||||
1. **JSON 利用箇所を洗い出す**
|
||||
```bash
|
||||
rg '"version".*0' --type rust # Program JSON v0
|
||||
rg 'MIR JSON' --type rust # MIR JSON
|
||||
rg 'parse_json\|JsonBox' tools/ # .hako 内
|
||||
```
|
||||
|
||||
2. **「誰が何を読むか」を表にまとめる**
|
||||
|
||||
| JSON 形式 | 読み手 | 用途 | 書き手 |
|
||||
|----------|-------|------|--------|
|
||||
| Program v0 | Stage-B, selfhost | コンパイラ入力 | Rust コンパイラ |
|
||||
| MIR JSON | hako_check HC020 | CFG 解析 | `emit_mir_json_for_harness()` |
|
||||
| CFG JSON | hako_check HC020+ | ブロック到達可能性 | `extract_cfg_info()` |
|
||||
| JoinIR JSON | デバッグツール | 構造確認 | JoinIR dumper |
|
||||
|
||||
3. **README に「既存 JSON 利用一覧」として追記**
|
||||
|
||||
### 成果物
|
||||
|
||||
- JSON 利用箇所の詳細リスト
|
||||
- 「読み手と書き手」の対応表
|
||||
|
||||
---
|
||||
|
||||
## 4. Task 2: hako_check 内部 JSON パーサの構造を把握
|
||||
|
||||
### 対象ファイル
|
||||
|
||||
- `tools/hako_check/analysis_consumer.hako` (Phase 156 実装)
|
||||
|
||||
### やること
|
||||
|
||||
1. **対応範囲の確認**
|
||||
- どの JSON 型に対応しているか
|
||||
- ✅ 文字列 / 数値 / bool / null
|
||||
- ✅ 配列 / オブジェクト
|
||||
- ❓ エスケープ(\", \n 等)
|
||||
- サンプル JSON での動作確認
|
||||
|
||||
2. **現状と TODO を整理**
|
||||
```
|
||||
✅ できること:
|
||||
- オブジェクトの key-value 解析
|
||||
- 配列のループ処理
|
||||
- 文字列・数値の抽出
|
||||
|
||||
❓ 改善したいこと:
|
||||
- エスケープシーケンス完全対応
|
||||
- ネストされた構造の処理
|
||||
- エラーハンドリング詳細化
|
||||
- パフォーマンス(素朴な実装)
|
||||
|
||||
❌ 未対応:
|
||||
- Unicode エスケープ (\uXXXX)
|
||||
- 特殊数値 (Infinity, NaN)
|
||||
```
|
||||
|
||||
3. **README に「hako_check 内部 JSON パーサの現状」として整理**
|
||||
- 実装行数(約320行)
|
||||
- 対応範囲
|
||||
- 改善候補
|
||||
|
||||
### 成果物
|
||||
|
||||
- 対応範囲の詳細ドキュメント
|
||||
- TODO リスト
|
||||
|
||||
---
|
||||
|
||||
## 5. Task 3: JsonParserBox の API 草案を決める
|
||||
|
||||
### 設計方針
|
||||
|
||||
**最小限の責務分離**:
|
||||
- JsonParserBox: JSON 文字列 → メモリ内オブジェクト
|
||||
- JsonObjectBox, JsonArrayBox: メモリ内表現
|
||||
- 書き込み(to_json)は Phase 172 以降
|
||||
|
||||
### 草案 API
|
||||
|
||||
#### 型定義案
|
||||
|
||||
```hako
|
||||
// 使用側の視点: JsonParserBox.parse() で JSON をパース
|
||||
|
||||
// 例1: 単純オブジェクト
|
||||
local json_str = '{"name": "Main", "entry_block": 0}'
|
||||
local obj = JsonParserBox.parse(json_str)
|
||||
// obj は MapBox のように .get("name") 等が使える
|
||||
|
||||
// 例2: 配列含む
|
||||
local json_str = '{"functions": [{"id": 0}, {"id": 1}]}'
|
||||
local obj = JsonParserBox.parse(json_str)
|
||||
local funcs = obj.get("functions") // ArrayBox のように
|
||||
```
|
||||
|
||||
#### 関数シグネチャ案
|
||||
|
||||
```
|
||||
JsonParserBox
|
||||
method parse(json_str: String) -> JsonValue?
|
||||
// 汎用パーサ(最初はこれだけ)
|
||||
// 成功時: JsonValue(実体は MapBox/ArrayBox/String/Number/Bool/Null)
|
||||
// 失敗時: null
|
||||
|
||||
method parse_object(json_str: String) -> MapBox?
|
||||
// オブジェクト専用(便利メソッド)
|
||||
|
||||
method parse_array(json_str: String) -> ArrayBox?
|
||||
// 配列専用(便利メソッド)
|
||||
|
||||
JsonValue (Union 型代わり)
|
||||
// 実装: MapBox / ArrayBox / String / Integer / Bool / Null を
|
||||
// 型タグで区別する仕組み(または each に分岐)
|
||||
```
|
||||
|
||||
### MVP vs Phase 172+
|
||||
|
||||
**Phase 171 MVP**:
|
||||
- ✅ `parse(json_str) -> JsonValue?`
|
||||
- ✅ オブジェクト / 配列 / 基本型対応
|
||||
- ✅ Phase 156 の手動パーサを置き換え可能な水準
|
||||
|
||||
**Phase 172+**:
|
||||
- 📋 エスケープシーケンス完全対応
|
||||
- 📋 スキーマ検証
|
||||
- 📋 to_json() 逆変換
|
||||
- 📋 ストリーミングパーサ
|
||||
|
||||
### 成果物
|
||||
|
||||
- API 草案ドキュメント
|
||||
- 型定義案
|
||||
- MVP / Phase 172+ の区切り明記
|
||||
|
||||
---
|
||||
|
||||
## 6. Task 4: 利用予定箇所のマッピング(誰が JsonParserBox を使うか)
|
||||
|
||||
### やること
|
||||
|
||||
1. **Task 1 のインベントリから利用候補を抽出**
|
||||
|
||||
| 利用箇所 | 用途 | 置き換え対象 | 優先度 |
|
||||
|---------|------|-----------|--------|
|
||||
| hako_check HC020 | CFG 解析 | analysis_consumer.hako の JSON パーサ | 高 |
|
||||
| hako_check HC021+ | 定数畳み込み等 | 新規実装時に JsonParserBox 利用 | 高 |
|
||||
| selfhost Stage-B | Program v0 読み込み | 新規実装 | 中 |
|
||||
| 開発ツール | MIR/CFG デバッグ | 新規実装 | 低 |
|
||||
|
||||
2. **各箇所の「置き換え内容」をメモ**
|
||||
```
|
||||
hako_check HC020:
|
||||
- 現状: analysis_consumer.hako に 320行の手動パーサ
|
||||
- 修正: JsonParserBox.parse(mir_json_str).get_cfg() 的なワンステップに
|
||||
|
||||
selfhost Stage-B:
|
||||
- 現状: Rust で serde_json で JSON パース
|
||||
- 将来: Program v0 を .hako で読み込む場合に JsonParserBox 使用
|
||||
```
|
||||
|
||||
3. **README に「利用予定マップ」として追記**
|
||||
|
||||
### 成果物
|
||||
|
||||
- 利用予定マッピング表
|
||||
- 各用途の「置き換え内容」
|
||||
|
||||
---
|
||||
|
||||
## 7. Task 5: CURRENT_TASK 更新
|
||||
|
||||
### 追加内容
|
||||
|
||||
CURRENT_TASK.md に Phase 170 セクションを追加(1-2 段落):
|
||||
|
||||
```markdown
|
||||
### Phase 170: .hako JSON ライブラリ設計 & インベントリ ✅
|
||||
|
||||
**完了内容**:
|
||||
- 既存 JSON 利用箇所のインベントリ
|
||||
- hako_check 内部 JSON パーサの構造把握
|
||||
- JsonParserBox API 草案決定
|
||||
- 利用予定箇所のマッピング
|
||||
|
||||
**設計内容**:
|
||||
- JsonParserBox: JSON 文字列 → オブジェクト
|
||||
- Phase 171 MVP: parse() メソッド1つで汎用対応
|
||||
- Phase 172+: エスケープ完全対応、スキーマ検証、to_json()
|
||||
|
||||
**次フェーズ**: Phase 171 で JsonParserBox 実装開始、その後順次 hako_check/selfhost から置き換え開始
|
||||
```
|
||||
|
||||
### 成果物
|
||||
|
||||
- CURRENT_TASK.md 更新
|
||||
|
||||
---
|
||||
|
||||
## ✅ 完成チェックリスト(Phase 170)
|
||||
|
||||
- [ ] Task 1: 既存 JSON 利用箇所のインベントリ
|
||||
- [ ] JSON 利用箇所洗い出し
|
||||
- [ ] 「読み手と書き手」対応表作成
|
||||
- [ ] Task 2: hako_check 内部 JSON パーサ把握
|
||||
- [ ] 対応範囲確認
|
||||
- [ ] TODO リスト作成
|
||||
- [ ] Task 3: JsonParserBox API 草案決定
|
||||
- [ ] 関数シグネチャ決定
|
||||
- [ ] 型定義案確定
|
||||
- [ ] MVP / Phase 172+ 区切り明記
|
||||
- [ ] Task 4: 利用予定マッピング
|
||||
- [ ] 利用候補箇所抽出
|
||||
- [ ] 「置き換え内容」メモ
|
||||
- [ ] Task 5: CURRENT_TASK 更新
|
||||
- [ ] Phase 170 セクション追加
|
||||
- [ ] git commit
|
||||
|
||||
---
|
||||
|
||||
## 出力ファイル
|
||||
|
||||
`docs/private/roadmap2/phases/phase-170-hako-json-library/README.md`
|
||||
|
||||
構成:
|
||||
1. 既存 JSON 利用一覧(Task 1)
|
||||
2. hako_check 内部 JSON パーサの現状(Task 2)
|
||||
3. JsonParserBox API 草案(Task 3)
|
||||
4. 利用予定マップ(Task 4)
|
||||
|
||||
---
|
||||
|
||||
**作成日**: 2025-12-04
|
||||
**Phase**: 170(.hako JSON ライブラリ設計)
|
||||
**予定工数**: 2-3 時間
|
||||
**難易度**: 低(調査 + ドキュメント化)
|
||||
@ -1,209 +0,0 @@
|
||||
# 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`
|
||||
@ -1,418 +0,0 @@
|
||||
# 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`
|
||||
@ -1,449 +0,0 @@
|
||||
# Phase 171-2: hako_check の JSON パーサ完全置き換え
|
||||
|
||||
## 0. ゴール
|
||||
|
||||
**analysis_consumer.hako に残っている手書き JSON パーサ(約289行)を JsonParserBox 呼び出しに全面置き換えする。**
|
||||
|
||||
目的:
|
||||
- Phase 171 で作成した JsonParserBox を hako_check に統合
|
||||
- 手書き JSON パーサの完全削除(289行 → ~10-30行)
|
||||
- HC019/HC020 の結果が変わらないことを確認
|
||||
- Phase 171 を「実装+統合まで完了」扱いにする
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
### Phase 171 の現状
|
||||
- ✅ JsonParserBox 実装完了(`tools/hako_shared/json_parser.hako`, 454行)
|
||||
- ✅ ProgramJSONBox 実装完了(Phase 172)
|
||||
- 🔄 hako_check への統合が未完了(289行の手書きパーサが残存)
|
||||
|
||||
### この Phase でやること
|
||||
- `tools/hako_check/analysis_consumer.hako` の手書き JSON パーサを削除
|
||||
- JsonParserBox を using で読み込み、parse() / parse_object() / parse_array() を使用
|
||||
- HC019/HC020 の出力が変わらないことを確認
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope / Non-scope
|
||||
|
||||
### ✅ やること
|
||||
|
||||
1. **現状の JSON 解析経路を再確認**
|
||||
- analysis_consumer.hako の手書きパーサ構造を把握
|
||||
- JsonParserBox で同じ構造を読めるか確認
|
||||
|
||||
2. **analysis_consumer.hako を JsonParserBox に置き換え**
|
||||
- 手書き JSON パーサ本体を全削除
|
||||
- JsonParserBox を using で読み込み
|
||||
- MIR JSON → JsonParserBox.parse() → CFG 抽出
|
||||
|
||||
3. **hako_check ルール側の微調整(必要なら)**
|
||||
- HC019/HC020 からの参照先フィールド調整
|
||||
- ロジック自体は変更しない
|
||||
|
||||
4. **スモーク・回帰テスト**
|
||||
- HC019/HC020 のスモークテスト実行
|
||||
- 以前と同じ警告が出ているか確認
|
||||
|
||||
5. **ドキュメント & CURRENT_TASK 更新**
|
||||
- Phase 171 完了記録
|
||||
- 96%削減達成の記録
|
||||
|
||||
### ❌ やらないこと
|
||||
|
||||
- HC019/HC020 のロジック変更
|
||||
- 新しい解析ルール追加
|
||||
- JsonParserBox の機能追加
|
||||
|
||||
---
|
||||
|
||||
## 3. Task 1: 現状の JSON 解析経路を再確認
|
||||
|
||||
### 対象ファイル
|
||||
- `tools/hako_check/analysis_consumer.hako`
|
||||
- `tools/hako_shared/json_parser.hako`(JsonParserBox & ProgramJSONBox)
|
||||
|
||||
### やること
|
||||
|
||||
1. **手書き JSON パーサの構造確認**
|
||||
- analysis_consumer.hako の中で「文字列を手で舐めて JSON を構築している部分」を特定
|
||||
- どの JSON(MIR/CFG/Program)のどのフィールドを読んでいるか簡単にメモ
|
||||
|
||||
2. **JsonParserBox との互換性確認**
|
||||
- JsonParserBox 側で同じ構造を読めるか(MVPとして足りているか)を確認
|
||||
- 不足している機能があれば、Phase 173+ で対応予定としてメモ
|
||||
|
||||
### 成果物
|
||||
- JSON 解析経路のメモ
|
||||
- JsonParserBox 互換性確認結果
|
||||
|
||||
---
|
||||
|
||||
## 4. Task 2: analysis_consumer.hako を JsonParserBox に置き換え
|
||||
|
||||
### 方針
|
||||
- 文字列操作ベースの JSON パーサ本体は**全部削除**
|
||||
- 「MIR JSON の文字列を受け取る」までは今のまま
|
||||
- そこから先を JsonParserBox に委譲する
|
||||
|
||||
### 具体的な実装
|
||||
|
||||
1. **JsonParserBox を using で読み込む**
|
||||
```hako
|
||||
# analysis_consumer.hako の先頭に追加
|
||||
# (現在の using 方式に合わせて調整)
|
||||
```
|
||||
|
||||
2. **MIR JSON 解析処理を書き換え**
|
||||
```hako
|
||||
# 修正前(手書きパーサー、約289行)
|
||||
method parse_mir_json_manually(mir_json_str) {
|
||||
# 文字列操作ベースの手動解析...
|
||||
# 約289行
|
||||
}
|
||||
|
||||
# 修正後(JsonParserBox 使用、~10-30行)
|
||||
method parse_mir_json_with_parser(mir_json_str) {
|
||||
local parser = new JsonParserBox()
|
||||
local root = parser.parse(mir_json_str)
|
||||
|
||||
if root == null {
|
||||
# エラーハンドリング
|
||||
return me.create_empty_cfg()
|
||||
}
|
||||
|
||||
# CFG/functions/blocks の抽出
|
||||
local cfg = root.get("cfg")
|
||||
if cfg == null {
|
||||
return me.create_empty_cfg()
|
||||
}
|
||||
|
||||
local functions = cfg.get("functions")
|
||||
# ... 以下、既存の CFG 抽出処理に接続
|
||||
}
|
||||
```
|
||||
|
||||
3. **既存の CFG 抽出処理と接続**
|
||||
- JsonParserBox が返す DOM から cfg / functions / blocks を拾う形に書き換え
|
||||
- 既存の Analysis IR への統合処理はそのまま使用
|
||||
|
||||
### 目標
|
||||
- JSON 解析部分の行数を **289行 → ~10-30行レベル**(96%削減)
|
||||
|
||||
### 成果物
|
||||
- `analysis_consumer.hako` 修正完了
|
||||
- 手書き JSON パーサ完全削除
|
||||
- JsonParserBox 統合完了
|
||||
|
||||
---
|
||||
|
||||
## 5. Task 3: hako_check ルール側の微調整(必要なら)
|
||||
|
||||
### 対象ファイル
|
||||
- `tools/hako_check/rules/rule_dead_blocks.hako`(HC020)
|
||||
- `tools/hako_check/rules/rule_dead_code.hako`(HC019)
|
||||
|
||||
### やること
|
||||
|
||||
1. **analysis_consumer 側の CFG/関数情報の形が変わった場合**
|
||||
- HC020/HC019 からの参照先フィールドを合わせる
|
||||
- ロジック自体(reachability 判定、未使用メソッド判定)は変えない
|
||||
|
||||
2. **動作確認**
|
||||
- 簡単なテストケースで HC020/HC019 が動作するか確認
|
||||
|
||||
### 成果物
|
||||
- HC020/HC019 の微調整完了(必要な場合のみ)
|
||||
|
||||
---
|
||||
|
||||
## 6. Task 4: スモーク・回帰テスト
|
||||
|
||||
### 必須スクリプト
|
||||
- `tools/hako_check_deadcode_smoke.sh`(HC019)
|
||||
- `tools/hako_check_deadblocks_smoke.sh`(HC020)
|
||||
|
||||
### やること
|
||||
|
||||
1. **JsonParserBox 統合後に両方を実行**
|
||||
```bash
|
||||
./tools/hako_check_deadcode_smoke.sh
|
||||
./tools/hako_check_deadblocks_smoke.sh
|
||||
```
|
||||
|
||||
2. **出力確認**
|
||||
- 以前と同じケースで同じ警告が出ているか確認
|
||||
- 少なくとも「件数」と代表メッセージを確認
|
||||
|
||||
3. **追加の手動確認**
|
||||
```bash
|
||||
# 1本 .hako を選んで手動実行
|
||||
./tools/hako_check.sh --dead-code apps/tests/XXX.hako
|
||||
./tools/hako_check.sh --dead-blocks apps/tests/XXX.hako
|
||||
```
|
||||
- 眼でざっと確認
|
||||
|
||||
### 期待される結果
|
||||
- HC019/HC020 の出力が JsonParserBox 統合前後で変わらない
|
||||
- スモークテスト全て PASS
|
||||
|
||||
### 成果物
|
||||
- スモークテスト成功確認
|
||||
- 回帰なし確認
|
||||
|
||||
---
|
||||
|
||||
## 7. Task 5: ドキュメント & CURRENT_TASK 更新
|
||||
|
||||
### ドキュメント更新
|
||||
|
||||
1. **phase170_hako_json_library_design.md / phase171_*:**
|
||||
- 「hako_check 側の JSON パーサが完全に JsonParserBox に乗ったこと」を追記
|
||||
- 「旧手書きパーサは削除済み(約 96% 削減)」を追記
|
||||
|
||||
2. **hako_check_design.md:**
|
||||
- 「JSON 解析は JsonParserBox を経由」と明記
|
||||
|
||||
3. **CURRENT_TASK.md:**
|
||||
- Phase 171 セクションに以下を追加:
|
||||
```markdown
|
||||
### Phase 171: JsonParserBox 実装 ✅
|
||||
- JsonParserBox 実装完了(454行)
|
||||
- hako_check 統合完了(289行 → ~10-30行、96%削減達成)
|
||||
- HC019/HC020 正常動作確認
|
||||
- Phase 171 完全完了!
|
||||
```
|
||||
- この章をクローズ扱いにする
|
||||
|
||||
### git commit
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat(hako_check): Phase 171-2 JsonParserBox integration complete
|
||||
|
||||
🎉 hako_check の JSON パーサ完全置き換え成功!
|
||||
|
||||
🔧 実装内容:
|
||||
- analysis_consumer.hako: 手書き JSON パーサ削除(289行)
|
||||
- JsonParserBox 統合(~10-30行)
|
||||
- 96%削減達成!
|
||||
|
||||
✅ テスト結果:
|
||||
- HC019 スモークテスト: PASS
|
||||
- HC020 スモークテスト: PASS
|
||||
- 回帰なし確認
|
||||
|
||||
🎯 Phase 171 完全完了!
|
||||
次は JSON 逆方向(to_json)や selfhost depth-2 / .hako JoinIR/MIR へ
|
||||
|
||||
🤖 Generated with Claude Code
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 完成チェックリスト(Phase 171-2)
|
||||
|
||||
- [ ] Task 1: JSON 解析経路再確認
|
||||
- [ ] 手書きパーサー構造把握
|
||||
- [ ] JsonParserBox 互換性確認
|
||||
- [ ] Task 2: analysis_consumer.hako 置き換え
|
||||
- [ ] 手書き JSON パーサ削除(289行)
|
||||
- [ ] JsonParserBox 統合(~10-30行)
|
||||
- [ ] CFG 抽出処理接続
|
||||
- [ ] Task 3: HC019/HC020 微調整(必要なら)
|
||||
- [ ] 参照先フィールド調整
|
||||
- [ ] 動作確認
|
||||
- [ ] Task 4: スモーク・回帰テスト
|
||||
- [ ] HC019 スモーク PASS
|
||||
- [ ] HC020 スモーク PASS
|
||||
- [ ] 回帰なし確認
|
||||
- [ ] Task 5: ドキュメント更新
|
||||
- [ ] phase170/171 更新
|
||||
- [ ] hako_check_design.md 更新
|
||||
- [ ] CURRENT_TASK.md 更新
|
||||
- [ ] git commit
|
||||
|
||||
---
|
||||
|
||||
## 技術的注意点
|
||||
|
||||
### using statement の使用
|
||||
```hako
|
||||
# 現在の hako_check での using 方式に合わせる
|
||||
# (プロジェクトの using 実装状況に依存)
|
||||
```
|
||||
|
||||
### エラーハンドリング
|
||||
```hako
|
||||
# JsonParserBox.parse() が null を返した場合
|
||||
# 空の CFG 構造体にフォールバック
|
||||
method create_empty_cfg() {
|
||||
local cfg = new MapBox()
|
||||
local functions = new ArrayBox()
|
||||
cfg.set("functions", functions)
|
||||
return cfg
|
||||
}
|
||||
```
|
||||
|
||||
### CFG フィールド抽出
|
||||
```hako
|
||||
# MIR JSON から CFG を抽出する例
|
||||
local cfg = root.get("cfg")
|
||||
local functions = cfg.get("functions") # ArrayBox
|
||||
|
||||
# 各 function から entry_block, blocks を取得
|
||||
for fn in functions {
|
||||
local entry_block = fn.get("entry_block")
|
||||
local blocks = fn.get("blocks") # ArrayBox
|
||||
|
||||
# 各 block から id, successors, reachable を取得
|
||||
for block in blocks {
|
||||
local id = block.get("id")
|
||||
local successors = block.get("successors") # ArrayBox
|
||||
local reachable = block.get("reachable") # BoolBox
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 次のステップ
|
||||
|
||||
Phase 171-2 完了後:
|
||||
- **Phase 173**: to_json() 逆変換実装
|
||||
- **Phase 174**: selfhost depth-2 JSON 統一化
|
||||
- **Phase 160+**: .hako JoinIR/MIR 移植
|
||||
|
||||
これで Phase 171 をきっちり締めておけば、次のフェーズに気持ちよく移れる!
|
||||
|
||||
---
|
||||
|
||||
## 実装結果(2025-12-04)
|
||||
|
||||
### ✅ 実装完了内容
|
||||
|
||||
#### 1. 手書き JSON パーサの削除
|
||||
- **削減量**: 708行 → 442行(266行削減、37.6%削減達成)
|
||||
- **削除した機能**:
|
||||
- `_parse_cfg_functions` (約40行)
|
||||
- `_parse_single_function` (約30行)
|
||||
- `_parse_blocks_array` (約40行)
|
||||
- `_parse_single_block` (約25行)
|
||||
- `_extract_json_string_value` (約15行)
|
||||
- `_extract_json_int_value` (約30行)
|
||||
- `_extract_json_bool_value` (約20行)
|
||||
- `_extract_json_int_array` (約45行)
|
||||
|
||||
#### 2. JsonParserBox 統合実装
|
||||
- **新しい `_extract_cfg_from_mir_json` 実装**:
|
||||
```hako
|
||||
_extract_cfg_from_mir_json(json_text) {
|
||||
if json_text == null { return me._empty_cfg() }
|
||||
|
||||
// Use JsonParserBox to parse the MIR JSON
|
||||
local root = JsonParserBox.parse(json_text)
|
||||
if root == null { return me._empty_cfg() }
|
||||
|
||||
// Extract cfg object: {"cfg": {"functions": [...]}}
|
||||
local cfg = root.get("cfg")
|
||||
if cfg == null { return me._empty_cfg() }
|
||||
|
||||
// Return the cfg object directly
|
||||
return cfg
|
||||
}
|
||||
```
|
||||
- **行数**: 約15行(対して元の289行)
|
||||
- **削減率**: 95%削減達成!
|
||||
|
||||
#### 3. using 文の追加
|
||||
- `using tools.hako_shared.json_parser as JsonParserBox`
|
||||
- **配置**: analysis_consumer.hako の先頭(line 14)
|
||||
|
||||
### ⚠️ 発見された重大な問題
|
||||
|
||||
#### using statement の静的 Box メソッド解決問題
|
||||
|
||||
**症状**:
|
||||
```
|
||||
[ERROR] ❌ [rust-vm] VM error: Invalid instruction: Unknown method '_skip_whitespace' on InstanceBox
|
||||
```
|
||||
|
||||
**根本原因**:
|
||||
- `using` statement が静的 Box のメソッドを正しく解決できていない
|
||||
- `JsonParserBox.parse()` は静的メソッドだが、VM が InstanceBox として扱っている
|
||||
- 内部メソッド `_skip_whitespace` 等が見つからない
|
||||
|
||||
**証拠**:
|
||||
- `tools/hako_shared/tests/json_parser_simple_test.hako` は using を使わず、JsonParserBox のコード全体をインライン化している
|
||||
- コメント: "Test JsonParserBox without using statement"
|
||||
|
||||
**影響範囲**:
|
||||
- hako_check での JsonParserBox 使用が現時点で不可能
|
||||
- using statement の静的 Box 対応が Phase 15.5+ で必要
|
||||
|
||||
### 🔄 対応方針(2つの選択肢)
|
||||
|
||||
#### Option A: Phase 173 で using system 修正を待つ(推奨)
|
||||
**メリット**:
|
||||
- 正しいアーキテクチャ(SSOT: JsonParserBox)
|
||||
- 将来的な保守性向上
|
||||
- Phase 171-2 の本来の目標達成
|
||||
|
||||
**デメリット**:
|
||||
- すぐには動作しない
|
||||
- using system の修正が必要(Phase 15.5+)
|
||||
|
||||
**実装済み状態**:
|
||||
- ✅ コード統合完了(37.6%削減)
|
||||
- ✅ using 文追加完了
|
||||
- ⚠️ 実行時エラー(using 制限のため)
|
||||
|
||||
#### Option B: 一時的にインライン化する
|
||||
**メリット**:
|
||||
- 即座に動作する
|
||||
- テスト可能
|
||||
|
||||
**デメリット**:
|
||||
- SSOT 原則違反
|
||||
- 454行のコード重複
|
||||
- Phase 171 の目標不達成
|
||||
|
||||
### 📊 現在の状態
|
||||
|
||||
| 項目 | 状態 | 備考 |
|
||||
|-----|------|------|
|
||||
| 手書きパーサ削除 | ✅ | 266行削除(37.6%) |
|
||||
| JsonParserBox 統合 | ✅ | 15行実装(95%削減) |
|
||||
| using 文追加 | ✅ | line 14 |
|
||||
| コンパイル | ✅ | エラーなし |
|
||||
| 実行 | ❌ | using 制限のため VM エラー |
|
||||
| HC019 テスト | ⚠️ | 未実行(実行時エラーのため) |
|
||||
| HC020 テスト | ⚠️ | 未実行(実行時エラーのため) |
|
||||
|
||||
### 🎯 推奨アクション
|
||||
|
||||
1. **現在の実装をコミット** (Option A の準備)
|
||||
- 37.6%削減の成果を記録
|
||||
- using 制限を明記
|
||||
|
||||
2. **Phase 173 で using system 修正**
|
||||
- 静的 Box のメソッド解決を修正
|
||||
- JsonParserBox 統合を完全動作させる
|
||||
|
||||
3. **Phase 171 完了判定**
|
||||
- 「実装完了、using 制限により Phase 173 で動作確認」とする
|
||||
- アーキテクチャ的には正しい方向性
|
||||
|
||||
---
|
||||
|
||||
**作成日**: 2025-12-04
|
||||
**更新日**: 2025-12-04 (実装結果追記)
|
||||
**Phase**: 171-2(hako_check JSON パーサ完全置き換え)
|
||||
**予定工数**: 2-3 時間
|
||||
**実工数**: 2 時間(実装完了、using 制限発見)
|
||||
**難易度**: 中(統合 + テスト確認)
|
||||
**状態**: 実装完了(using 制限により Phase 173 で動作確認予定)
|
||||
@ -1,278 +0,0 @@
|
||||
# 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`
|
||||
@ -1,424 +0,0 @@
|
||||
# Phase 171-A: Blocked Loop Inventory
|
||||
|
||||
**Date**: 2025-12-07
|
||||
**Status**: Initial inventory complete
|
||||
**Purpose**: Identify loops blocked by LoopBodyLocal variables in break conditions
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document catalogs loops that cannot be lowered by Pattern 2/4 because they use **LoopBodyLocal** variables in their break conditions. These are candidates for Pattern 5 carrier promotion.
|
||||
|
||||
---
|
||||
|
||||
## Blocked Loops Found
|
||||
|
||||
### 1. TrimTest.trim/1 - Leading Whitespace Trim
|
||||
|
||||
**File**: `local_tests/test_trim_main_pattern.hako`
|
||||
**Lines**: 20-27
|
||||
|
||||
```hako
|
||||
loop(start < end) {
|
||||
local ch = s.substring(start, start+1)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
start = start + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Blocking Variable**: `ch` (LoopBodyLocal)
|
||||
|
||||
**Analysis**:
|
||||
- Loop parameter: `start` (LoopParam)
|
||||
- Outer variable: `end` (OuterLocal)
|
||||
- Break condition uses: `ch` (LoopBodyLocal)
|
||||
- `ch` is defined inside loop body as `s.substring(start, start+1)`
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
[trace:debug] pattern2: Pattern 2 lowerer failed: Variable 'ch' not bound in ConditionEnv
|
||||
```
|
||||
|
||||
**Why Blocked**:
|
||||
Pattern 2 expects break conditions to only use:
|
||||
- LoopParam (`start`)
|
||||
- OuterLocal (`end`, `s`)
|
||||
|
||||
But the break condition `ch == " " || ...` uses `ch`, which is defined inside the loop body.
|
||||
|
||||
---
|
||||
|
||||
### 2. TrimTest.trim/1 - Trailing Whitespace Trim
|
||||
|
||||
**File**: `local_tests/test_trim_main_pattern.hako`
|
||||
**Lines**: 30-37
|
||||
|
||||
```hako
|
||||
loop(end > start) {
|
||||
local ch = s.substring(end-1, end)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
end = end - 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Blocking Variable**: `ch` (LoopBodyLocal)
|
||||
|
||||
**Analysis**:
|
||||
- Loop parameter: `end` (LoopParam)
|
||||
- Outer variable: `start` (OuterLocal)
|
||||
- Break condition uses: `ch` (LoopBodyLocal)
|
||||
- `ch` is defined inside loop body as `s.substring(end-1, end)`
|
||||
|
||||
**Same blocking reason as Loop 1.**
|
||||
|
||||
---
|
||||
|
||||
### 3. JsonParserBox - MethodCall in Condition
|
||||
|
||||
**File**: `tools/hako_shared/json_parser.hako`
|
||||
|
||||
```hako
|
||||
loop(i < s.length()) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Blocking Issue**: `s.length()` is a MethodCall in the condition expression.
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
[ERROR] ❌ MIR compilation error: [cf_loop/pattern4] Lowering failed:
|
||||
Unsupported expression in value context: MethodCall {
|
||||
object: Variable { name: "s", ... },
|
||||
method: "length",
|
||||
arguments: [],
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**Why Blocked**:
|
||||
Pattern 4's value context lowering doesn't support MethodCall expressions yet.
|
||||
|
||||
**Note**: This is not a LoopBodyLocal issue, but a MethodCall limitation. May be addressed in Phase 171-D (Optional).
|
||||
|
||||
---
|
||||
|
||||
## Pattern5-A Target Decision
|
||||
|
||||
### Selected Target: TrimTest Loop 1 (Leading Whitespace)
|
||||
|
||||
We select the **first loop** from TrimTest as Pattern5-A target for the following reasons:
|
||||
|
||||
1. **Clear structure**: Simple substring + equality checks
|
||||
2. **Representative**: Same pattern as many real-world parsers
|
||||
3. **Self-contained**: Doesn't depend on complex outer state
|
||||
4. **Testable**: Easy to write unit tests
|
||||
|
||||
### Pattern5-A Specification
|
||||
|
||||
**Loop Structure**:
|
||||
```hako
|
||||
loop(start < end) {
|
||||
local ch = s.substring(start, start+1)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
start = start + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Variables**:
|
||||
- `start`: LoopParam (carrier, mutated)
|
||||
- `end`: OuterLocal (condition-only)
|
||||
- `s`: OuterLocal (used in body)
|
||||
- `ch`: LoopBodyLocal (blocking variable)
|
||||
|
||||
**Break Condition**:
|
||||
```
|
||||
!(ch == " " || ch == "\t" || ch == "\n" || ch == "\r")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Promotion Strategy: Design D (Evaluated Bool Carrier)
|
||||
|
||||
### Rationale
|
||||
|
||||
We choose **Design D** (Evaluated Bool Carrier) over other options:
|
||||
|
||||
**Why not carry `ch` directly?**
|
||||
- `ch` is a StringBox, not a primitive value
|
||||
- Would require complex carrier type system
|
||||
- Would break existing Pattern 2/4 assumptions
|
||||
|
||||
**Design D approach**:
|
||||
- Introduce a new carrier: `is_whitespace` (bool)
|
||||
- Evaluate `ch == " " || ...` in loop body
|
||||
- Store result in `is_whitespace` carrier
|
||||
- Use `is_whitespace` in break condition
|
||||
|
||||
### Transformed Structure
|
||||
|
||||
**Before (Pattern5-A)**:
|
||||
```hako
|
||||
loop(start < end) {
|
||||
local ch = s.substring(start, start+1)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
start = start + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**After (Pattern2 compatible)**:
|
||||
```hako
|
||||
// Initialization (before loop)
|
||||
local is_whitespace = true // Initial assumption
|
||||
|
||||
loop(start < end && is_whitespace) {
|
||||
local ch = s.substring(start, start+1)
|
||||
is_whitespace = (ch == " " || ch == "\t" || ch == "\n" || ch == "\r")
|
||||
|
||||
if is_whitespace {
|
||||
start = start + 1
|
||||
} else {
|
||||
break // Now redundant, but kept for clarity
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key transformations**:
|
||||
1. Add `is_whitespace` carrier initialization
|
||||
2. Update loop condition to include `is_whitespace`
|
||||
3. Compute `is_whitespace` in loop body
|
||||
4. Original if-else becomes simpler (could be optimized away)
|
||||
|
||||
---
|
||||
|
||||
## Next Steps (Phase 171-C)
|
||||
|
||||
### Phase 171-C-1: Skeleton Implementation ✅
|
||||
- Create `LoopBodyCarrierPromoter` box
|
||||
- Define `PromotionRequest` / `PromotionResult` types
|
||||
- Implement skeleton `try_promote()` method
|
||||
- Add `find_definition_in_body()` helper
|
||||
|
||||
### Phase 171-C-2: Trim Pattern Promotion Logic
|
||||
- Detect substring + equality pattern
|
||||
- Generate `is_whitespace` carrier
|
||||
- Generate initialization statement
|
||||
- Generate update statement
|
||||
|
||||
### Phase 171-C-3: Integration with Pattern 2/4
|
||||
- Call `LoopBodyCarrierPromoter::try_promote()` in routing
|
||||
- If promotion succeeds, route to Pattern 2
|
||||
- If promotion fails, return UnsupportedPattern
|
||||
|
||||
### Phase 171-D: MethodCall Support (Optional)
|
||||
- Handle `s.length()` in loop conditions
|
||||
- May require carrier promotion for method results
|
||||
- Lower priority than Trim pattern
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**Blocked Loops**:
|
||||
- 2 loops in TrimTest (LoopBodyLocal `ch`)
|
||||
- 1+ loops in JsonParser (MethodCall in condition)
|
||||
|
||||
**Pattern5-A Target**:
|
||||
- TrimTest leading whitespace trim loop
|
||||
- Clear, representative, testable
|
||||
|
||||
**Promotion Strategy**:
|
||||
- Design D: Evaluated Bool Carrier
|
||||
- Transform `ch` checks → `is_whitespace` carrier
|
||||
- Make compatible with Pattern 2
|
||||
|
||||
**Implementation Status**:
|
||||
- Phase 171-A: ✅ Inventory complete
|
||||
- Phase 171-B: ✅ Target selected
|
||||
- Phase 171-C-1: ✅ Skeleton implementation complete
|
||||
- Phase 171-C-2: ✅ Trim pattern detection implemented
|
||||
- `find_definition_in_body()`: AST traversal for variable definitions
|
||||
- `is_substring_method_call()`: Detects `substring()` method calls
|
||||
- `extract_equality_literals()`: Extracts string literals from OR chains
|
||||
- `TrimPatternInfo`: Captures pattern details for carrier promotion
|
||||
- Phase 171-C-3: ✅ Integration with Pattern 2/4 routing complete
|
||||
- Phase 171-C-4: ✅ CarrierInfo integration complete (2025-12-07)
|
||||
- `CarrierInfo::merge_from()`: Deduplicated carrier merging with deterministic sorting
|
||||
- `TrimPatternInfo::to_carrier_info()`: Conversion to CarrierInfo with TrimLoopHelper
|
||||
- Pattern 2/4 lowerers: Promoted carrier merging in `Promoted` branch
|
||||
- 7 unit tests: Merge success/failure/duplication/determinism validation
|
||||
- Phase 171-C-5: ✅ TrimLoopHelper design complete (2025-12-07)
|
||||
- `TrimLoopHelper` struct: Encapsulates Trim pattern lowering logic
|
||||
- `CarrierInfo::trim_helper()`: Accessor for pattern-specific helper
|
||||
- Module export: `mod.rs` updated with `pub use TrimLoopHelper`
|
||||
- 4 unit tests: Helper creation and accessor validation
|
||||
|
||||
---
|
||||
|
||||
## Phase 171-C-3/4/5: Responsibility Positions and Data Flow
|
||||
|
||||
### Responsibility Separation Principle
|
||||
|
||||
The promotion system follows Box Theory's single responsibility principle:
|
||||
|
||||
1. **router.rs**: Pattern table + `can_lower()`/`lower()` call abstraction (no Scope/condition logic)
|
||||
2. **Pattern 2/4 lowerer**: Holds LoopScope / ConditionScope / CarrierInfo / Promoter
|
||||
3. **LoopBodyCarrierPromoter**: LoopBodyLocal handling specialist box
|
||||
4. **TrimLoopHelper**: Trim pattern-specific helper (future extensibility)
|
||||
|
||||
### Data Flow Diagram
|
||||
|
||||
```
|
||||
LoopConditionScopeBox::analyze()
|
||||
↓
|
||||
has_loop_body_local()?
|
||||
↓ true
|
||||
LoopBodyCarrierPromoter::try_promote()
|
||||
↓ Promoted { trim_info }
|
||||
TrimPatternInfo::to_carrier_info()
|
||||
↓
|
||||
CarrierInfo::merge_from()
|
||||
↓
|
||||
TrimLoopHelper (attached to CarrierInfo)
|
||||
↓
|
||||
Pattern 2/4 lowerer (JoinIR generation)
|
||||
```
|
||||
|
||||
### Implementation Locations
|
||||
|
||||
**Phase 171-C-4 Changes**:
|
||||
- `src/mir/join_ir/lowering/carrier_info.rs`: Added `merge_from()`, `trim_helper()`, `trim_helper` field
|
||||
- `src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs`: Updated `to_carrier_info()` to attach TrimLoopHelper
|
||||
- `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`: Promoted branch now merges carriers
|
||||
- `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`: Promoted branch now merges carriers
|
||||
|
||||
**Phase 171-C-5 Changes**:
|
||||
- `src/mir/loop_pattern_detection/trim_loop_helper.rs`: NEW - TrimLoopHelper struct with 4 unit tests
|
||||
- `src/mir/loop_pattern_detection/mod.rs`: Export TrimLoopHelper module
|
||||
|
||||
---
|
||||
|
||||
## Phase 171-impl-Trim: Trim 特例の実戦投入
|
||||
|
||||
**Date**: 2025-12-08
|
||||
**Status**: ✅ Validation complete
|
||||
**Purpose**: Safely integrate Trim pattern detection into JoinIR pipeline with validation-only implementation
|
||||
|
||||
### 設計原則
|
||||
|
||||
> **「LoopBodyLocal を全面解禁」ではなく、Trim パターンだけを箱経由でホワイトリストに載せる**
|
||||
|
||||
### 安全機構
|
||||
|
||||
1. `TrimLoopHelper::is_safe_trim()` - 構造的に安全か判定
|
||||
2. `TrimLoopHelper::is_trim_like()` - Trim パターンに合致するか判定
|
||||
3. `TrimLoopHelper::has_valid_structure()` - 構造チェック
|
||||
|
||||
### データフロー(Trim 特例)
|
||||
|
||||
```
|
||||
LoopConditionScopeBox::analyze()
|
||||
↓
|
||||
has_loop_body_local() == true
|
||||
↓
|
||||
LoopBodyCarrierPromoter::try_promote()
|
||||
↓ Promoted { trim_info }
|
||||
TrimPatternInfo::to_carrier_info()
|
||||
↓
|
||||
CarrierInfo::merge_from()
|
||||
↓
|
||||
carrier_info.trim_helper()?.is_safe_trim()
|
||||
↓ true
|
||||
✅ Validation Success (TODO: JoinIR lowering in Phase 172)
|
||||
```
|
||||
|
||||
### 実装状況
|
||||
|
||||
- [x] Phase 171-impl-Trim-1: 受け入れ条件を 1 箇所に ✅
|
||||
- `TrimLoopHelper::is_safe_trim()` implemented
|
||||
- `Pattern2/4` で Trim 特例ルート実装
|
||||
- Fail-Fast on unsafe patterns
|
||||
- [x] Phase 171-impl-Trim-2: TrimLoopHelper 判定メソッド ✅
|
||||
- `is_trim_like()`, `has_valid_structure()` implemented
|
||||
- 4+ ユニットテスト追加 (9 tests total, all passing)
|
||||
- [x] Phase 171-impl-Trim-3: E2E テスト ✅
|
||||
- `local_tests/test_trim_main_pattern.hako` validated
|
||||
- 出力: `[pattern2/trim] Safe Trim pattern detected`
|
||||
- 出力: `✅ Trim pattern validation successful!`
|
||||
- Status: Validation-only (JoinIR lowering deferred to Phase 172)
|
||||
- [x] Phase 171-impl-Trim-4: ドキュメント更新 ✅
|
||||
- `phase171-pattern5-loop-inventory.md` updated
|
||||
- `CURRENT_TASK.md` status tracking
|
||||
|
||||
### 実装詳細
|
||||
|
||||
**ファイル変更**:
|
||||
1. `src/mir/loop_pattern_detection/trim_loop_helper.rs`
|
||||
- `is_safe_trim()`, `is_trim_like()`, `has_valid_structure()` methods
|
||||
- 4 new unit tests for safety validation
|
||||
|
||||
2. `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`
|
||||
- Trim exception route with safety check
|
||||
- `body_locals` extraction from loop body AST
|
||||
- Validation message for successful detection
|
||||
|
||||
3. `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
|
||||
- Same Trim exception logic as Pattern2
|
||||
- `body_locals` extraction from normalized loop body
|
||||
|
||||
4. `src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs`
|
||||
- `ASTNode::Local { variables, initial_values, .. }` handler
|
||||
- Support for `local ch = expr` pattern recognition
|
||||
|
||||
### テスト結果
|
||||
|
||||
**ユニットテスト**: ✅ 9/9 passing
|
||||
- `test_is_safe_trim`
|
||||
- `test_is_safe_trim_empty_carrier`
|
||||
- `test_is_safe_trim_no_whitespace`
|
||||
- `test_has_valid_structure`
|
||||
- (+ 5 existing tests)
|
||||
|
||||
**E2E テスト**: ✅ Validation successful
|
||||
```
|
||||
[pattern2/check] Analyzing condition scope: 3 variables
|
||||
[pattern2/check] 'ch': LoopBodyLocal
|
||||
[pattern2/promoter] LoopBodyLocal 'ch' promoted to carrier 'is_ch_match'
|
||||
[pattern2/trim] Safe Trim pattern detected, bypassing LoopBodyLocal restriction
|
||||
[pattern2/trim] Carrier: 'is_ch_match', original var: 'ch', whitespace chars: ["\r", "\n", "\t", " "]
|
||||
✅ Trim pattern validation successful! Carrier 'is_ch_match' ready for Phase 172 implementation.
|
||||
(Pattern detection: PASS, Safety check: PASS, JoinIR lowering: TODO)
|
||||
```
|
||||
|
||||
**ビルド結果**: ✅ Success
|
||||
- `cargo build --release`: Success
|
||||
- `cargo test --lib trim_loop_helper`: 9/9 passing
|
||||
|
||||
### 重要な発見
|
||||
|
||||
1. **AST構造の理解**: `local ch = expr` は `ASTNode::Local { variables, initial_values, .. }` として表現される
|
||||
2. **body_locals 抽出**: Pattern2/4 で `LoopScopeShape.body_locals` を AST から抽出する必要があった
|
||||
3. **段階的実装**: Validation-only approach で安全性を先に確立し、JoinIR lowering は Phase 172 に分離
|
||||
|
||||
### 次のステップ (Phase 172)
|
||||
|
||||
- [ ] Phase 172-1: Trim pattern JoinIR generation
|
||||
- Carrier initialization code
|
||||
- Carrier update logic (substring + OR chain → bool)
|
||||
- Exit PHI mapping
|
||||
- [ ] Phase 172-2: JsonParser loops への展開
|
||||
- Similar pattern recognition
|
||||
- Generalized carrier promotion
|
||||
@ -1,493 +0,0 @@
|
||||
# Phase 171: JsonParserBox 実装 & hako_check への導入
|
||||
|
||||
## 0. ゴール
|
||||
|
||||
**Phase 170 で決めた API 草案どおりに .hako 純正 JSON パーサ Box(JsonParserBox)を実装する。**
|
||||
|
||||
目的:
|
||||
- JSON 文字列をメモリ内オブジェクトに変換する Box を実装
|
||||
- hako_check (HC020) で Phase 156 の手書き JSON パーサ(289行)を置き換え
|
||||
- 将来の selfhost/Stage-B/解析ツールから再利用可能に
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景と戦略
|
||||
|
||||
### Phase 156 の課題
|
||||
|
||||
- hako_check の `analysis_consumer.hako` に 289行の手書き JSON パーサ
|
||||
- 他の HC ルール(HC021+)でも JSON 解析が必要になる
|
||||
- 共通ライブラリ化が急務
|
||||
|
||||
### Phase 170 での決定
|
||||
|
||||
- **API 草案**: `parse()`, `parse_object()`, `parse_array()`
|
||||
- **型定義**: JsonValueBox(union 相当), JsonObjectBox, JsonArrayBox
|
||||
- **MVP スコープ**: MIR/CFG JSON のみ対応(Program JSON v0 は Phase 172+)
|
||||
|
||||
### Phase 171 の戦略
|
||||
|
||||
1. **最小MVP実装**: MIR/CFG JSON が使う型のみ
|
||||
2. **段階的導入**: hako_check (HC020) が最初のユーザー
|
||||
3. **共通ライブラリ化**: `tools/hako_shared/` で shared 設計
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope / Non-scope
|
||||
|
||||
### ✅ やること
|
||||
|
||||
1. **JsonParserBox の実装**
|
||||
- `parse(json_str) -> JsonValue?`
|
||||
- `parse_object(json_str) -> JsonObjectBox?`
|
||||
- `parse_array(json_str) -> JsonArrayBox?`
|
||||
|
||||
2. **内部型の定義**
|
||||
- JsonValueBox(kind で型判定)
|
||||
- JsonObjectBox(key-value map)
|
||||
- JsonArrayBox(value list)
|
||||
|
||||
3. **エスケープシーケンス対応**
|
||||
- MIR JSON が実際に使う範囲:`\"`, `\\`, `\n`, `\t` など
|
||||
- Unicode (`\uXXXX`) は Phase 172+
|
||||
|
||||
4. **hako_check への導入**
|
||||
- `analysis_consumer.hako` の 289行手書きパーサを削除
|
||||
- JsonParserBox を使用した 10行規模実装に置き換え
|
||||
- HC019/HC020 の出力が変わらないことを確認
|
||||
|
||||
5. **テスト**
|
||||
- JsonParserBox 単体テスト(正常系・エラー系)
|
||||
- hako_check スモークテスト(回帰なし)
|
||||
|
||||
### ❌ やらないこと
|
||||
|
||||
- to_json() / serialization(Phase 172+)
|
||||
- スキーマ検証(Phase 172+)
|
||||
- Program JSON v0 対応(Phase 172+)
|
||||
- ストリーミングパーサ(Phase 173+)
|
||||
|
||||
---
|
||||
|
||||
## 3. Task 1: 設計ドキュメントの確認・MVP の切り出し
|
||||
|
||||
### やること
|
||||
|
||||
1. **Phase 170 設計の再確認**
|
||||
- `docs/private/roadmap2/phases/phase-170-hako-json-library/README.md` を読む
|
||||
- API 草案を確認(parse, parse_object, parse_array)
|
||||
- 型定義案を確認(JsonValueBox, JsonObjectBox, JsonArrayBox)
|
||||
|
||||
2. **MVP スコープの確定**
|
||||
- MIR/CFG JSON のサブセット:
|
||||
- 型: null, bool, number, string, object, array
|
||||
- number: 整数のみ(浮動小数点は未対応)
|
||||
- string: 基本的なエスケープのみ
|
||||
- object: キーは文字列固定
|
||||
- エラーハンドリング: null を返す
|
||||
|
||||
3. **実装方針の決定**
|
||||
- Phase 156 の手書きパーサを参照しつつ
|
||||
- StringBox.substring/indexOf で文字列走査
|
||||
- 状態機械を最小化(シンプル > 最適化)
|
||||
|
||||
### 成果物
|
||||
|
||||
- MVP の詳細仕様書(Task 2 への引き継ぎ)
|
||||
|
||||
---
|
||||
|
||||
## 4. Task 2: JsonParserBox の実装 (.hako)
|
||||
|
||||
### ファイル位置
|
||||
|
||||
```
|
||||
tools/hako_shared/
|
||||
├── json_parser.hako ← 新規作成
|
||||
└── tests/
|
||||
└── json_parser_test.hako ← 新規作成
|
||||
```
|
||||
|
||||
### やること
|
||||
|
||||
1. **Box 構造の定義**
|
||||
|
||||
```hako
|
||||
// JsonValueBox: union 相当の型
|
||||
static box JsonValueBox {
|
||||
kind: StringBox # "null", "bool", "number", "string", "array", "object"
|
||||
boolVal: BoolBox
|
||||
numVal: IntegerBox
|
||||
strVal: StringBox
|
||||
arrVal: JsonArrayBox
|
||||
objVal: JsonObjectBox
|
||||
|
||||
method is_null() { return me.kind == "null" }
|
||||
method is_bool() { return me.kind == "bool" }
|
||||
method is_number() { return me.kind == "number" }
|
||||
method is_string() { return me.kind == "string" }
|
||||
method is_array() { return me.kind == "array" }
|
||||
method is_object() { return me.kind == "object" }
|
||||
}
|
||||
|
||||
// JsonObjectBox: key-value map
|
||||
static box JsonObjectBox {
|
||||
pairs: ArrayBox # [{"key": String, "value": JsonValue}, ...]
|
||||
|
||||
method get(key: String) -> JsonValue? { ... }
|
||||
method keys() -> ArrayBox { ... }
|
||||
method values() -> ArrayBox { ... }
|
||||
}
|
||||
|
||||
// JsonArrayBox: value list
|
||||
static box JsonArrayBox {
|
||||
elements: ArrayBox # [JsonValue, ...]
|
||||
|
||||
method get(index: Integer) -> JsonValue? { ... }
|
||||
method length() -> Integer { ... }
|
||||
}
|
||||
|
||||
// JsonParserBox: メインパーサ
|
||||
static box JsonParserBox {
|
||||
method parse(json_str: String) -> JsonValue? { ... }
|
||||
method parse_object(json_str: String) -> JsonObjectBox? { ... }
|
||||
method parse_array(json_str: String) -> JsonArrayBox? { ... }
|
||||
}
|
||||
```
|
||||
|
||||
2. **JSON パーサ実装**
|
||||
|
||||
```hako
|
||||
static box JsonParserBox {
|
||||
method parse(json_str: String) -> JsonValue? {
|
||||
// 前後の空白を削除
|
||||
local s = me._trim(json_str)
|
||||
if s.length() == 0 { return null }
|
||||
|
||||
local result = null
|
||||
local pos = me._parse_value(s, 0, result)
|
||||
if pos < 0 { return null }
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
method parse_object(json_str: String) -> JsonObjectBox? {
|
||||
local val = me.parse(json_str)
|
||||
if val == null or not val.is_object() { return null }
|
||||
return val.objVal
|
||||
}
|
||||
|
||||
method parse_array(json_str: String) -> JsonArrayBox? {
|
||||
local val = me.parse(json_str)
|
||||
if val == null or not val.is_array() { return null }
|
||||
return val.arrVal
|
||||
}
|
||||
|
||||
// 内部ヘルパー
|
||||
method _parse_value(s: String, pos: Integer, out: JsonValue) -> Integer { ... }
|
||||
method _parse_null(s: String, pos: Integer, out: JsonValue) -> Integer { ... }
|
||||
method _parse_bool(s: String, pos: Integer, out: JsonValue) -> Integer { ... }
|
||||
method _parse_number(s: String, pos: Integer, out: JsonValue) -> Integer { ... }
|
||||
method _parse_string(s: String, pos: Integer, out: JsonValue) -> Integer { ... }
|
||||
method _parse_array(s: String, pos: Integer, out: JsonValue) -> Integer { ... }
|
||||
method _parse_object(s: String, pos: Integer, out: JsonValue) -> Integer { ... }
|
||||
method _skip_whitespace(s: String, pos: Integer) -> Integer { ... }
|
||||
method _trim(s: String) -> String { ... }
|
||||
method _unescape_string(s: String) -> String { ... }
|
||||
}
|
||||
```
|
||||
|
||||
3. **エスケープシーケンス対応**
|
||||
|
||||
MIR JSON で使われるもの:
|
||||
- `\"` → `"`
|
||||
- `\\` → `\`
|
||||
- `\n` → newline
|
||||
- `\t` → tab
|
||||
- `\r` → carriage return
|
||||
- `\b`, `\f` → 対応可(Phase 171)
|
||||
- `\uXXXX` → 未対応(Phase 172+)
|
||||
|
||||
### 成果物
|
||||
|
||||
- JsonParserBox 実装(約300-400行推定)
|
||||
- 単体テストケース
|
||||
|
||||
---
|
||||
|
||||
## 5. Task 3: hako_check (HC020) から JsonParserBox を使うように置き換え
|
||||
|
||||
### 対象ファイル
|
||||
|
||||
- `tools/hako_check/analysis_consumer.hako` (Lines 206-494: 手書きパーサ)
|
||||
- `tools/hako_check/rules/rule_dead_blocks.hako`
|
||||
|
||||
### やること
|
||||
|
||||
1. **JSON パーサ部分の削除**
|
||||
- `analysis_consumer.hako` の手書きパーサ関数を全削除(~289行)
|
||||
- 代わりに JsonParserBox を import
|
||||
|
||||
2. **CFG 処理の修正**
|
||||
|
||||
```hako
|
||||
// 修正前(Phase 156)
|
||||
local mir_json_text = ir.get("_mir_json_text")
|
||||
local cfg_obj = me._parse_json_object(mir_json_text) // 手写
|
||||
|
||||
// 修正後(Phase 171)
|
||||
local mir_json_text = ir.get("_mir_json_text")
|
||||
local cfg_val = JsonParserBox.parse(mir_json_text)
|
||||
local cfg_obj = cfg_val.objVal // JsonObjectBox を取得
|
||||
```
|
||||
|
||||
3. **HC020 ルール側の確認**
|
||||
- `rule_dead_blocks.hako` が CFG を正しく解析できるか確認
|
||||
- JsonObjectBox/.get() メソッドとの互換性確認
|
||||
|
||||
4. **目標**
|
||||
- `analysis_consumer.hako` の行数を 500+ → 210 に削減(約60%削減)
|
||||
- Phase 156 の 289行手書きパーサの削除
|
||||
|
||||
### 成果物
|
||||
|
||||
- 修正済み `analysis_consumer.hako`
|
||||
- 修正済み `tools/hako_check/` 関連ファイル
|
||||
|
||||
---
|
||||
|
||||
## 6. Task 4: 単体テスト & スモークテスト
|
||||
|
||||
### JsonParserBox 単体テスト
|
||||
|
||||
新規作成: `tools/hako_shared/tests/json_parser_test.hako`
|
||||
|
||||
テストケース:
|
||||
|
||||
```hako
|
||||
static box JsonParserTest {
|
||||
main() {
|
||||
me.test_null()
|
||||
me.test_bool()
|
||||
me.test_number()
|
||||
me.test_string()
|
||||
me.test_array()
|
||||
me.test_object()
|
||||
me.test_error_cases()
|
||||
print("All tests passed!")
|
||||
}
|
||||
|
||||
test_null() {
|
||||
local val = JsonParserBox.parse("null")
|
||||
assert(val != null && val.is_null())
|
||||
}
|
||||
|
||||
test_bool() {
|
||||
local t = JsonParserBox.parse("true")
|
||||
local f = JsonParserBox.parse("false")
|
||||
assert(t.is_bool() && t.boolVal == true)
|
||||
assert(f.is_bool() && f.boolVal == false)
|
||||
}
|
||||
|
||||
test_number() {
|
||||
local n = JsonParserBox.parse("123")
|
||||
assert(n.is_number() && n.numVal == 123)
|
||||
}
|
||||
|
||||
test_string() {
|
||||
local s = JsonParserBox.parse('"hello"')
|
||||
assert(s.is_string() && s.strVal == "hello")
|
||||
}
|
||||
|
||||
test_array() {
|
||||
local arr = JsonParserBox.parse("[1, 2, 3]")
|
||||
assert(arr.is_array() && arr.arrVal.length() == 3)
|
||||
}
|
||||
|
||||
test_object() {
|
||||
local obj = JsonParserBox.parse('{"key": "value"}')
|
||||
assert(obj.is_object())
|
||||
local val = obj.objVal.get("key")
|
||||
assert(val != null && val.is_string())
|
||||
}
|
||||
|
||||
test_error_cases() {
|
||||
local inv1 = JsonParserBox.parse("{")
|
||||
assert(inv1 == null)
|
||||
|
||||
local inv2 = JsonParserBox.parse("[1,2,]")
|
||||
assert(inv2 == null)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### hako_check スモークテスト
|
||||
|
||||
既存のスモークテストを実行:
|
||||
|
||||
```bash
|
||||
# HC020 スモーク
|
||||
./tools/hako_check_deadblocks_smoke.sh
|
||||
|
||||
# HC019 スモーク(回帰なし)
|
||||
./tools/hako_check_deadcode_smoke.sh
|
||||
```
|
||||
|
||||
期待:
|
||||
- HC020 出力が Phase 156 と変わらない
|
||||
- HC019 出力が変わらない
|
||||
- エラーが出ない
|
||||
|
||||
### 成果物
|
||||
|
||||
- JsonParserBox テストファイル
|
||||
- スモークテスト成功確認
|
||||
|
||||
---
|
||||
|
||||
## 7. Task 5: ドキュメント & CURRENT_TASK 更新
|
||||
|
||||
### ドキュメント更新
|
||||
|
||||
1. **phase170_hako_json_library_design.md に追記**
|
||||
```markdown
|
||||
## Phase 171 実装結果
|
||||
|
||||
✅ JsonParserBox 実装完了
|
||||
- API: parse(), parse_object(), parse_array()
|
||||
- サポート型: null, bool, number, string, array, object
|
||||
- エスケープ: \", \\, \n, \t, \r, \b, \f
|
||||
|
||||
✅ hako_check への導入完了
|
||||
- analysis_consumer.hako: 500+ → 210 行(~60%削減)
|
||||
- Phase 156 の 289行手書きパーサを削除
|
||||
- HC020 が JsonParserBox を使用
|
||||
|
||||
📊 削減実績:
|
||||
- hako_check パーサ: 289行 → JsonParserBox 呼び出し(~10行)
|
||||
- 共通ライブラリ化: hako_check/selfhost/ツールから再利用可能
|
||||
```
|
||||
|
||||
2. **hako_check_design.md を更新**
|
||||
```markdown
|
||||
### JSON 解析
|
||||
|
||||
- Phase 156 まで: analysis_consumer.hako に手書きパーサ(289行)
|
||||
- Phase 171 から: JsonParserBox を使用
|
||||
- 場所: tools/hako_shared/json_parser.hako
|
||||
```
|
||||
|
||||
3. **CURRENT_TASK.md に Phase 171 セクション追加**
|
||||
```markdown
|
||||
### Phase 171: JsonParserBox 実装 & hako_check 導入 ✅
|
||||
|
||||
**完了内容**:
|
||||
- JsonParserBox 実装(tools/hako_shared/json_parser.hako)
|
||||
- hako_check (HC020) が JsonParserBox を使用
|
||||
- 手書きパーサ 289行 → 共通ライブラリに統合
|
||||
|
||||
**成果**:
|
||||
- hako_check 行数削減: 60% (500+ → 210 行)
|
||||
- 再利用性: HC021+, selfhost, Stage-B で活用可
|
||||
- 箱化: .hako 純正 JSON パーサ Box 確立
|
||||
|
||||
**次フェーズ**: Phase 172 で selfhost/Stage-B への導入予定
|
||||
```
|
||||
|
||||
### git commit
|
||||
|
||||
```
|
||||
feat(hako_check): Phase 171 JsonParserBox implementation
|
||||
|
||||
✨ .hako 純正 JSON パーサ Box 実装完了!
|
||||
|
||||
🎯 実装内容:
|
||||
- JsonParserBox: parse/parse_object/parse_array メソッド
|
||||
- JsonValueBox, JsonObjectBox, JsonArrayBox 型定義
|
||||
- エスケープシーケンス対応(\", \\, \n, \t など)
|
||||
|
||||
📊 hako_check 統合:
|
||||
- analysis_consumer.hako: 289行手書きパーサを削除
|
||||
- JsonParserBox を使用した軽量実装に置き換え
|
||||
- HC019/HC020 の出力は変わらず(回帰なし)
|
||||
|
||||
✅ テスト:
|
||||
- JsonParserBox 単体テスト全 PASS
|
||||
- hako_check スモークテスト全 PASS
|
||||
- Phase 156 との後方互換性確認
|
||||
|
||||
🏗️ 次ステップ:
|
||||
- Phase 172: selfhost/Stage-B への導入
|
||||
- Phase 172+: Program JSON v0 対応
|
||||
- Phase 173+: to_json() 逆変換実装
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 完成チェックリスト(Phase 171)
|
||||
|
||||
- [ ] Task 1: 設計ドキュメント確認・MVP 切り出し
|
||||
- [ ] Phase 170 設計再確認
|
||||
- [ ] MVP スコープ確定
|
||||
- [ ] 実装方針決定
|
||||
- [ ] Task 2: JsonParserBox 実装
|
||||
- [ ] Box 構造定義
|
||||
- [ ] パーサ実装
|
||||
- [ ] エスケープシーケンス対応
|
||||
- [ ] Task 3: hako_check への導入
|
||||
- [ ] analysis_consumer.hako 修正
|
||||
- [ ] 手書きパーサ削除
|
||||
- [ ] 行数削減確認(500+ → 210)
|
||||
- [ ] Task 4: テスト & スモーク
|
||||
- [ ] JsonParserBox 単体テスト
|
||||
- [ ] hako_check スモークテスト
|
||||
- [ ] 回帰なし確認
|
||||
- [ ] Task 5: ドキュメント更新
|
||||
- [ ] phase170 に追記
|
||||
- [ ] hako_check_design.md 更新
|
||||
- [ ] CURRENT_TASK.md 追加
|
||||
- [ ] git commit
|
||||
|
||||
---
|
||||
|
||||
## 技術的ポイント
|
||||
|
||||
### JSON 解析の状態機械
|
||||
|
||||
```
|
||||
parse_value:
|
||||
| "null" → parse_null
|
||||
| "true"|"false" → parse_bool
|
||||
| digit → parse_number
|
||||
| '"' → parse_string
|
||||
| '[' → parse_array
|
||||
| '{' → parse_object
|
||||
| else → error (null)
|
||||
```
|
||||
|
||||
### ArrayBox / MapBox との互換性
|
||||
|
||||
```hako
|
||||
// JsonArrayBox.get() は ArrayBox.get() と同じ感覚で
|
||||
local elem = json_array.get(0)
|
||||
|
||||
// JsonObjectBox.get() は MapBox.get() と同じ感覚で
|
||||
local val = json_obj.get("key")
|
||||
```
|
||||
|
||||
### エラーハンドリング
|
||||
|
||||
- 構文エラー → null を返す(Nyash 的な nil)
|
||||
- 期待値と異なる型 → null を返す
|
||||
|
||||
### パフォーマンス
|
||||
|
||||
Phase 171 MVP は正確性 > 速度を優先。最適化は Phase 173+ へ。
|
||||
|
||||
---
|
||||
|
||||
**作成日**: 2025-12-04
|
||||
**Phase**: 171(JsonParserBox 実装 & hako_check 導入)
|
||||
**予定工数**: 3-4 時間
|
||||
**難易度**: 中(JSON パーサ実装 + .hako での Box 設計)
|
||||
**期待削減**: hako_check 行数 60%、コード共通化 100%
|
||||
@ -1,345 +0,0 @@
|
||||
# Phase 172: Trim Pattern JoinIR Lowering Implementation
|
||||
|
||||
## Objective
|
||||
|
||||
Implement actual JoinIR → MIR lowering for Trim pattern loops, making `test_trim_main_pattern.hako` work end-to-end.
|
||||
|
||||
## Target Loop
|
||||
|
||||
**File**: `local_tests/test_trim_main_pattern.hako`
|
||||
|
||||
**Leading whitespace trim loop**:
|
||||
```nyash
|
||||
loop(start < end) {
|
||||
local ch = s.substring(start, start+1)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
start = start + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using Boxes from Phase 171
|
||||
|
||||
### Phase 171 Infrastructure
|
||||
- ✅ **LoopConditionScopeBox**: Detects `ch` as LoopBodyLocal
|
||||
- ✅ **LoopBodyCarrierPromoter**: Promotes `ch` to bool carrier `is_ch_match`
|
||||
- ✅ **TrimPatternInfo**: Contains promotion metadata
|
||||
- ✅ **TrimLoopHelper**: Attached to CarrierInfo for lowering guidance
|
||||
- ✅ **Pattern2 validation**: Currently returns informative error at line 234-239
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
### Responsibility Separation
|
||||
|
||||
**Host MIR Side** (before/after JoinIR):
|
||||
- Execute `substring()` method calls
|
||||
- Evaluate OR chain comparisons (`ch == " " || ch == "\t" || ...`)
|
||||
- Generate bool result for carrier
|
||||
- BoxCall operations
|
||||
|
||||
**JoinIR Side** (pure control flow):
|
||||
- bool carrier-based loop control
|
||||
- break condition: `!is_ch_match`
|
||||
- No knowledge of substring/OR chain
|
||||
- No new JoinIR instructions
|
||||
|
||||
### Key Insight
|
||||
|
||||
The Trim pattern transformation:
|
||||
```
|
||||
Original:
|
||||
local ch = s.substring(start, start+1)
|
||||
if (ch == " " || ch == "\t" || ...) { start++ } else { break }
|
||||
|
||||
Transformed:
|
||||
is_ch_match = (s.substring(start, start+1) == " " || ...) # Host MIR
|
||||
if (!is_ch_match) { break } # JoinIR
|
||||
```
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Loop Pre-Initialization (Task 172-2)
|
||||
|
||||
**Location**: `pattern2_with_break.rs`, before JoinIR generation
|
||||
|
||||
**Goal**: Initialize bool carrier before entering loop
|
||||
|
||||
**Implementation**:
|
||||
```rust
|
||||
// After Line 264 (after carrier_info.merge_from)
|
||||
if let Some(helper) = carrier_info.trim_helper() {
|
||||
if helper.is_safe_trim() {
|
||||
// Generate initial whitespace check
|
||||
// ch0 = s.substring(start, start+1)
|
||||
let ch0 = self.emit_method_call("substring", s_id, vec![start_id, start_plus_1_id])?;
|
||||
|
||||
// is_ch_match0 = (ch0 == " " || ch0 == "\t" || ...)
|
||||
let is_ch_match0 = emit_whitespace_check(self, ch0, &helper.whitespace_chars)?;
|
||||
|
||||
// Update carrier_info initial value
|
||||
// (Will be used in JoinIR input mapping)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Helper Function** (new):
|
||||
```rust
|
||||
fn emit_whitespace_check(
|
||||
builder: &mut MirBuilder,
|
||||
ch_value: ValueId,
|
||||
whitespace_chars: &[String],
|
||||
) -> Result<ValueId, String> {
|
||||
// Build OR chain: ch == " " || ch == "\t" || ...
|
||||
let mut result = None;
|
||||
for ws_char in whitespace_chars {
|
||||
let ws_const = emit_string(builder, ws_char.clone());
|
||||
let eq_check = emit_eq_to(builder, ch_value, ws_const)?;
|
||||
|
||||
result = Some(if let Some(prev) = result {
|
||||
// prev || eq_check
|
||||
emit_binary_op(builder, BinaryOp::Or, prev, eq_check)?
|
||||
} else {
|
||||
eq_check
|
||||
});
|
||||
}
|
||||
result.ok_or("Empty whitespace_chars".to_string())
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Loop Body Update Logic (Task 172-3)
|
||||
|
||||
**Location**: Pattern2 lowerer's latch generation (after `start = start + 1`)
|
||||
|
||||
**Goal**: Update bool carrier for next iteration
|
||||
|
||||
**Implementation**:
|
||||
```rust
|
||||
// In latch block generation (conceptually after line 267+)
|
||||
if let Some(helper) = carrier_info.trim_helper() {
|
||||
if helper.is_safe_trim() {
|
||||
// ch_next = s.substring(start_next, start_next+1)
|
||||
let ch_next = self.emit_method_call("substring", s_id, vec![start_next, start_next_plus_1])?;
|
||||
|
||||
// is_ch_match_next = (ch_next == " " || ...)
|
||||
let is_ch_match_next = emit_whitespace_check(self, ch_next, &helper.whitespace_chars)?;
|
||||
|
||||
// Store for JoinIR latch → header PHI
|
||||
// (Will be used in carrier update mapping)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: The actual MIR emission happens in **host space**, not JoinIR space. The JoinIR only sees the bool carrier as a loop parameter.
|
||||
|
||||
### Step 3: JoinIR break Condition Replacement (Task 172-4)
|
||||
|
||||
**Location**: `lower_loop_with_break_minimal` call at line 267
|
||||
|
||||
**Goal**: Replace break condition with `!is_ch_match`
|
||||
|
||||
**Current Code**:
|
||||
```rust
|
||||
let (join_module, fragment_meta) = match lower_loop_with_break_minimal(
|
||||
scope,
|
||||
condition, // loop condition: start < end
|
||||
&break_condition_node, // if condition: ch == " " || ...
|
||||
&env,
|
||||
&loop_var_name
|
||||
) { ... }
|
||||
```
|
||||
|
||||
**Modified Approach**:
|
||||
```rust
|
||||
// Phase 172: Trim pattern special route
|
||||
if let Some(helper) = carrier_info.trim_helper() {
|
||||
if helper.is_safe_trim() {
|
||||
// Create negated carrier check: !is_ch_match
|
||||
let carrier_var_node = ASTNode::Variable {
|
||||
name: helper.carrier_name.clone(), // "is_ch_match"
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let negated_carrier_check = ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand: Box::new(carrier_var_node),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
// Use negated carrier as break condition
|
||||
let (join_module, fragment_meta) = match lower_loop_with_break_minimal(
|
||||
scope,
|
||||
condition, // start < end (unchanged)
|
||||
&negated_carrier_check, // !is_ch_match (REPLACED)
|
||||
&env,
|
||||
&loop_var_name
|
||||
) { ... }
|
||||
|
||||
// Continue with normal merge flow...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Critical Design Decision**:
|
||||
- The break condition AST is replaced, but the loop condition (`start < end`) remains unchanged
|
||||
- JoinIR sees bool carrier as just another loop parameter
|
||||
- No new JoinIR instructions needed!
|
||||
|
||||
### Step 4: Carrier Mapping Integration
|
||||
|
||||
**Location**: Boundary setup (line 283-291)
|
||||
|
||||
**Goal**: Ensure bool carrier is properly mapped between host and JoinIR
|
||||
|
||||
**Current boundary setup**:
|
||||
```rust
|
||||
let mut boundary = JoinInlineBoundary::new_inputs_only(
|
||||
vec![ValueId(0)], // JoinIR: loop param
|
||||
vec![loop_var_id], // Host: "start"
|
||||
);
|
||||
boundary.condition_bindings = condition_bindings;
|
||||
boundary.exit_bindings = exit_bindings.clone();
|
||||
```
|
||||
|
||||
**Enhanced with Trim carrier**:
|
||||
```rust
|
||||
// Phase 172: Add bool carrier to condition_bindings
|
||||
if let Some(helper) = carrier_info.trim_helper() {
|
||||
let carrier_host_id = self.variable_map.get(&helper.carrier_name)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("Carrier '{}' not in variable_map", helper.carrier_name))?;
|
||||
|
||||
let carrier_join_id = alloc_join_value(); // Allocate JoinIR-local ID
|
||||
|
||||
env.insert(helper.carrier_name.clone(), carrier_join_id);
|
||||
condition_bindings.push(ConditionBinding {
|
||||
name: helper.carrier_name.clone(),
|
||||
host_value: carrier_host_id,
|
||||
join_value: carrier_join_id,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Constraints
|
||||
|
||||
### What We DON'T Change
|
||||
|
||||
1. **ExitLine Architecture**: No changes to ExitLineReconnector/ExitMetaCollector
|
||||
2. **Header PHI Generation**: Existing LoopHeaderPhiBuilder handles bool carrier automatically
|
||||
3. **JoinIR Instruction Set**: No new instructions added
|
||||
4. **Pattern2 Basic Logic**: Core control flow unchanged
|
||||
|
||||
### What We DO Change
|
||||
|
||||
1. **Pre-loop initialization**: Add bool carrier setup
|
||||
2. **Latch update logic**: Add bool carrier update
|
||||
3. **Break condition AST**: Replace with `!is_ch_match`
|
||||
4. **Condition bindings**: Add bool carrier mapping
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### E2E Test
|
||||
|
||||
**Command**:
|
||||
```bash
|
||||
./target/release/hakorune local_tests/test_trim_main_pattern.hako
|
||||
```
|
||||
|
||||
**Expected Behavior**:
|
||||
- **Before**: Error message "✅ Trim pattern validation successful! ... JoinIR lowering: TODO"
|
||||
- **After**: `Result: [hello]` and `PASS: Trimmed correctly`
|
||||
|
||||
### Debug Traces
|
||||
|
||||
**With JoinIR debug**:
|
||||
```bash
|
||||
NYASH_JOINIR_DEBUG=1 ./target/release/hakorune local_tests/test_trim_main_pattern.hako 2>&1 | grep -E "\[pattern2\]|\[trim\]"
|
||||
```
|
||||
|
||||
**Expected log patterns**:
|
||||
```
|
||||
[pattern2/promotion] LoopBodyLocal detected in condition scope
|
||||
[pattern2/promoter] LoopBodyLocal 'ch' promoted to carrier 'is_ch_match'
|
||||
[pattern2/trim] Safe Trim pattern detected, bypassing LoopBodyLocal restriction
|
||||
[pattern2/trim] Carrier: 'is_ch_match', original var: 'ch', whitespace chars: [" ", "\t", "\n", "\r"]
|
||||
[pattern2/trim] Emitting whitespace check initialization
|
||||
[pattern2/trim] Replacing break condition with !is_ch_match
|
||||
```
|
||||
|
||||
### MIR Validation
|
||||
|
||||
**Dump MIR**:
|
||||
```bash
|
||||
./target/release/hakorune --dump-mir local_tests/test_trim_main_pattern.hako 2>&1 | less
|
||||
```
|
||||
|
||||
**Check for**:
|
||||
- Pre-loop: `substring()` call + OR chain comparison
|
||||
- Loop header: PHI for `is_ch_match`
|
||||
- Loop body: Conditional branch on `!is_ch_match`
|
||||
- Loop latch: `substring()` call + OR chain update
|
||||
- Exit block: Proper carrier value propagation
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. ✅ `test_trim_main_pattern.hako` executes without errors
|
||||
2. ✅ Output: `Result: [hello]` (correctly trimmed)
|
||||
3. ✅ Test result: `PASS: Trimmed correctly`
|
||||
4. ✅ `cargo build --release` succeeds with 0 errors
|
||||
5. ✅ `cargo test` all tests pass
|
||||
6. ✅ No new warnings introduced
|
||||
7. ✅ MIR dump shows proper SSA form
|
||||
|
||||
## Implementation Status
|
||||
|
||||
- [ ] Task 172-1: Design document (THIS FILE)
|
||||
- [ ] Task 172-2: Loop pre-initialization
|
||||
- [ ] Task 172-3: Loop body update logic
|
||||
- [ ] Task 172-4: JoinIR break condition replacement
|
||||
- [ ] Task 172-5: E2E test validation
|
||||
- [ ] Task 172-6: Documentation update
|
||||
|
||||
## Future Work (Out of Scope for Phase 172)
|
||||
|
||||
### Pattern 4 Support
|
||||
- Apply same Trim lowering to Pattern 4 (loop with continue)
|
||||
- Reuse `emit_whitespace_check()` helper
|
||||
|
||||
### JsonParser Integration
|
||||
- Apply to all trim loops in JsonParser._trim
|
||||
- Validate with JSON parsing smoke tests
|
||||
|
||||
### P6+ Patterns
|
||||
- Complex control flow with Trim-like patterns
|
||||
- Multi-carrier bool promotion
|
||||
|
||||
## Notes
|
||||
|
||||
### Why This Design Works
|
||||
|
||||
1. **Minimal Invasiveness**: Changes only affect Trim-specific paths
|
||||
2. **Reusability**: `emit_whitespace_check()` can be shared with Pattern 4
|
||||
3. **Maintainability**: Clear separation between host MIR and JoinIR concerns
|
||||
4. **Testability**: Each step can be validated independently
|
||||
|
||||
### Phase 171 Validation Hook
|
||||
|
||||
Current code (line 234-239) already validates Trim pattern and returns early:
|
||||
```rust
|
||||
return Err(format!(
|
||||
"[cf_loop/pattern2] ✅ Trim pattern validation successful! \
|
||||
Carrier '{}' ready for Phase 172 implementation. \
|
||||
(Pattern detection: PASS, Safety check: PASS, JoinIR lowering: TODO)",
|
||||
helper.carrier_name
|
||||
));
|
||||
```
|
||||
|
||||
Phase 172 will **replace this early return** with actual lowering logic.
|
||||
|
||||
## References
|
||||
|
||||
- **Phase 171-C**: LoopBodyCarrierPromoter implementation
|
||||
- **Phase 171-C-5**: TrimLoopHelper design
|
||||
- **Pattern2 Lowerer**: `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`
|
||||
- **Test File**: `local_tests/test_trim_main_pattern.hako`
|
||||
@ -1,294 +0,0 @@
|
||||
# Phase 172: JsonParserBox 再利用拡大 - 実装結果
|
||||
|
||||
**実装日**: 2025-12-04
|
||||
**Phase**: 172(JsonParserBox 再利用拡大)
|
||||
|
||||
---
|
||||
|
||||
## 実装サマリー
|
||||
|
||||
### ✅ 完了した内容
|
||||
|
||||
1. **parse_program() メソッド実装**
|
||||
- JsonParserBox に Program JSON v0 パーサー追加
|
||||
- 場所: `tools/hako_shared/json_parser.hako` (lines 448-463)
|
||||
- Program JSON 必須フィールド検証 (version, kind)
|
||||
|
||||
2. **ProgramJSONBox 型定義**
|
||||
- Program JSON v0 構造への型安全アクセス
|
||||
- 場所: `tools/hako_shared/json_parser.hako` (lines 467-505)
|
||||
- メソッド:
|
||||
- `get_version()` - version フィールド取得
|
||||
- `get_kind()` - kind フィールド取得
|
||||
- `get_defs()` - defs 配列取得 (ArrayBox)
|
||||
- `get_meta()` - meta オブジェクト取得 (MapBox)
|
||||
- `get_usings()` - using 宣言配列取得 (ArrayBox?)
|
||||
- `get_object()` - 内部 MapBox 取得(後方互換)
|
||||
|
||||
3. **コンパイル検証**
|
||||
- JsonParserBox.parse_program/1: ✅ 成功
|
||||
- ProgramJSONBox メソッド全体: ✅ 成功
|
||||
- MIR 生成: ✅ エラーなし
|
||||
|
||||
### 📊 再利用候補の調査結果
|
||||
|
||||
| ファイル | 用途 | Program JSON v0 利用 | JsonParserBox 適用可否 |
|
||||
|---------|-----|-------------------|---------------------|
|
||||
| `lang/src/compiler/entry/compiler.hako` (543 lines) | Stage-A コンパイラ | **Emits** Program JSON | ❌ 生成側(消費側ではない) |
|
||||
| `apps/selfhost-vm/json_loader.hako` (51 lines) | JSON ユーティリティ | read_quoted_from, read_digits_from のみ | ⏸️ 汎用ヘルパー(Program JSON 特化ではない) |
|
||||
| `lang/src/vm/core/json_v0_reader.hako` (142 lines) | MIR JSON リーダー | **Parses MIR JSON** (functions/blocks/instructions) | ❌ MIR JSON 用(Program JSON ではない) |
|
||||
| `tools/hako_check/analysis_consumer.hako` (708 lines) | Analysis IR ビルダー | AST ベース(JSON は間接的) | ✅ Phase 171 で CFG 統合済み |
|
||||
|
||||
**重要な発見**: Program JSON v0 を **直接消費** するコードが予想より少ない
|
||||
- Stage-B は Program JSON を **生成**(JSON v0 → AST)
|
||||
- selfhost は MIR JSON を **読み込み**(MIR JSON → VM実行)
|
||||
- hako_check は AST → Analysis IR パイプライン(JSON は中間形式)
|
||||
|
||||
### 🎯 Phase 172 の現実的なスコープ調整
|
||||
|
||||
**当初予定**:
|
||||
- Stage-B/selfhost への JsonParserBox 適用
|
||||
- Program JSON v0 読み込み処理の統一
|
||||
|
||||
**実装後の判明事項**:
|
||||
- Program JSON v0 の主要消費者は現状存在しない
|
||||
- 既存コードは「生成」「MIR JSON 解析」「ユーティリティ」に分類される
|
||||
- JsonParserBox + ProgramJSONBox は **将来の統合** のための基盤
|
||||
|
||||
**Phase 172 の真の成果**:
|
||||
- Program JSON v0 パーサーの標準化完了
|
||||
- 将来の selfhost depth-2 での JSON 処理統一の準備完了
|
||||
- 箱化モジュール化パターンの完全適用
|
||||
|
||||
---
|
||||
|
||||
## 技術詳細
|
||||
|
||||
### ProgramJSONBox 使い方
|
||||
|
||||
```hako
|
||||
// 基本的な使い方
|
||||
local json_str = read_file("program.json")
|
||||
local prog = JsonParserBox.parse_program(json_str)
|
||||
|
||||
if prog == null {
|
||||
print("[ERROR] Invalid Program JSON")
|
||||
return
|
||||
}
|
||||
|
||||
// 型安全なアクセス
|
||||
local version = prog.get_version() // Integer: 0
|
||||
local kind = prog.get_kind() // String: "Program"
|
||||
local defs = prog.get_defs() // ArrayBox: definitions
|
||||
local usings = prog.get_usings() // ArrayBox?: using declarations
|
||||
|
||||
// defs を反復処理
|
||||
local i = 0
|
||||
while i < defs.size() {
|
||||
local def = defs.get(i)
|
||||
local def_kind = def.get("kind")
|
||||
|
||||
if def_kind == "Box" {
|
||||
local name = def.get("name")
|
||||
print("Box: " + name)
|
||||
}
|
||||
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
|
||||
### Program JSON v0 構造
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 0,
|
||||
"kind": "Program",
|
||||
"defs": [
|
||||
{"kind": "Box", "name": "Main", ...},
|
||||
{"kind": "Method", "name": "main", ...}
|
||||
],
|
||||
"meta": {
|
||||
"usings": ["nyashstd", "mylib"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 実装ファイル
|
||||
|
||||
```
|
||||
tools/hako_shared/json_parser.hako
|
||||
├── JsonParserBox (static box)
|
||||
│ ├── parse(json_str) [Phase 171]
|
||||
│ ├── parse_object(json_str) [Phase 171]
|
||||
│ ├── parse_array(json_str) [Phase 171]
|
||||
│ └── parse_program(json_str) [Phase 172] ← NEW
|
||||
│
|
||||
├── ProgramJSONBox (box) [Phase 172] ← NEW
|
||||
│ ├── birth(obj)
|
||||
│ ├── get_version()
|
||||
│ ├── get_kind()
|
||||
│ ├── get_defs()
|
||||
│ ├── get_meta()
|
||||
│ ├── get_usings()
|
||||
│ └── get_object()
|
||||
│
|
||||
└── JsonParserMain (static box)
|
||||
└── main(args)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## テスト & 回帰確認
|
||||
|
||||
### コンパイル検証 ✅
|
||||
|
||||
```bash
|
||||
# JsonParserBox.parse_program コンパイル確認
|
||||
./target/release/hakorune --backend vm --emit-mir-json /tmp/test.json \
|
||||
tools/hako_shared/json_parser.hako 2>&1 | grep parse_program
|
||||
|
||||
# 出力:
|
||||
# [DEBUG/create_function_skeleton] Creating function: JsonParserBox.parse_program/1
|
||||
# (全ブロックが正常にコンパイルされた)
|
||||
```
|
||||
|
||||
### ProgramJSONBox コンパイル確認 ✅
|
||||
|
||||
```bash
|
||||
# ProgramJSONBox メソッド確認
|
||||
./target/release/hakorune --backend vm --emit-mir-json /tmp/test.json \
|
||||
tools/hako_shared/json_parser.hako 2>&1 | grep ProgramJSONBox
|
||||
|
||||
# 出力:
|
||||
# [DEBUG/build_block] Statement 1/1 current_block=Some(BasicBlockId(1)) current_function=ProgramJSONBox.birth/1
|
||||
# [DEBUG/build_block] Statement 1/1 current_block=Some(BasicBlockId(2)) current_function=ProgramJSONBox.get_meta/0
|
||||
# (全メソッドが正常にコンパイルされた)
|
||||
```
|
||||
|
||||
### hako_check 回帰テスト
|
||||
|
||||
```bash
|
||||
# HC019 (dead code) スモークテスト
|
||||
./tools/hako_check_deadcode_smoke.sh
|
||||
|
||||
# HC020 (dead blocks) スモークテスト
|
||||
./tools/hako_check_deadblocks_smoke.sh
|
||||
|
||||
# 期待: Phase 171 実装の回帰なし
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 171 との統合状況
|
||||
|
||||
### Phase 171 の成果 (2025-12-03)
|
||||
|
||||
- ✅ JsonParserBox 実装完了 (454 lines)
|
||||
- ✅ hako_check HC020 で使用開始
|
||||
- ✅ 289 lines の手書きパーサ削除 (96% 削減)
|
||||
|
||||
### Phase 172 の追加内容
|
||||
|
||||
- ✅ Program JSON v0 サポート追加 (parse_program + ProgramJSONBox)
|
||||
- ✅ 型安全アクセサメソッド実装
|
||||
- ✅ コンパイル検証完了
|
||||
|
||||
### 統合の課題
|
||||
|
||||
**`using` statement の制限**:
|
||||
- JsonParserBox を `using` で呼び出すと VM エラー発生
|
||||
- 原因: static box の internal メソッド (_trim, _unescape_string 等) が解決できない
|
||||
- 回避策: 直接インクルード or 箱化モジュール化パターン適用
|
||||
|
||||
**将来の改善 (Phase 173+)**:
|
||||
- `using` サポート改善
|
||||
- to_json() 逆変換実装
|
||||
- スキーマ検証追加
|
||||
|
||||
---
|
||||
|
||||
## 箱化モジュール化パターンの適用
|
||||
|
||||
### Phase 172 で実証されたパターン
|
||||
|
||||
1. **SSOT (Single Source of Truth)**:
|
||||
- JSON 処理は JsonParserBox に完全集約
|
||||
- 複数の JSON 形式を 1つの箱でサポート
|
||||
- Program JSON, MIR JSON, CFG JSON すべて対応可能
|
||||
|
||||
2. **段階的拡張**:
|
||||
- Phase 171: 基本パーサー (parse, parse_object, parse_array)
|
||||
- Phase 172: Program JSON 特化 (parse_program + ProgramJSONBox)
|
||||
- Phase 173+: 逆変換・検証・最適化
|
||||
|
||||
3. **Rust 層最小変更**:
|
||||
- .hako のみで新機能追加
|
||||
- Rust VM は変更不要
|
||||
- コンパイル時型チェックで安全性確保
|
||||
|
||||
4. **後方互換性**:
|
||||
- 既存 parse() メソッドは変更なし
|
||||
- ProgramJSONBox は追加のみ
|
||||
- 段階移行が可能
|
||||
|
||||
---
|
||||
|
||||
## 次のフェーズ
|
||||
|
||||
### Phase 173: to_json() 逆変換 (提案)
|
||||
|
||||
**目的**: MapBox/ArrayBox → JSON 文字列変換
|
||||
|
||||
**API 案**:
|
||||
```hako
|
||||
static box JsonParserBox {
|
||||
method to_json(value) { ... } // 任意の値 → JSON
|
||||
method to_json_pretty(value, indent) { ... } // 整形出力
|
||||
}
|
||||
|
||||
box ProgramJSONBox {
|
||||
method to_json() {
|
||||
// Program JSON v0 形式で出力
|
||||
return JsonParserBox.to_json(me._obj)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**ユースケース**:
|
||||
- Stage-B での Program JSON 生成簡略化
|
||||
- MIR JSON 書き出し統一化
|
||||
- テストデータ生成自動化
|
||||
|
||||
### Phase 174: selfhost depth-2 JSON 統一 (提案)
|
||||
|
||||
**目的**: selfhost コンパイラでの JSON 処理完全統一
|
||||
|
||||
**統合箇所**:
|
||||
- lang/src/compiler: Program JSON 生成を JsonParserBox 経由に
|
||||
- apps/selfhost-vm: MIR JSON 読み込みを JsonParserBox 経由に
|
||||
- tools/*: すべてのツールが JsonParserBox 使用
|
||||
|
||||
---
|
||||
|
||||
## 成果サマリー
|
||||
|
||||
✅ **Phase 172 完了項目**:
|
||||
- parse_program() メソッド実装
|
||||
- ProgramJSONBox 型定義
|
||||
- コンパイル検証完了
|
||||
- 将来の統合基盤確立
|
||||
|
||||
📊 **コード削減**:
|
||||
- Phase 171: 289 lines → ~10 lines (96% 削減)
|
||||
- Phase 172: 追加実装のみ(削減なし、基盤拡張)
|
||||
|
||||
🏗️ **箱化モジュール化成果**:
|
||||
- JSON 処理の SSOT 確立
|
||||
- 段階的拡張パターン実証
|
||||
- selfhost depth-2 準備完了
|
||||
|
||||
---
|
||||
|
||||
**実装者**: Claude Code (AI 協働開発)
|
||||
**レビュー日**: 2025-12-04
|
||||
**Phase 状態**: 172 実装完了 ✅、173+ 提案あり
|
||||
@ -1,491 +0,0 @@
|
||||
# Phase 172: JsonParserBox の再利用拡大(Stage-B / selfhost / ツール統合)
|
||||
|
||||
## 0. ゴール
|
||||
|
||||
**Phase 171 で実装した JsonParserBox を、hako_check 以外の「JSON を読む .hako コード」にも適用し、JSON パーサ断片をできるだけ共通箱に集約する。**
|
||||
|
||||
目的:
|
||||
- Program(JSON v0) を読む Stage-B/selfhost 補助で JsonParserBox を使用
|
||||
- 将来の解析ツールが全部この箱を経由する状態を作る
|
||||
- JSON 処理の単一の真実(SSOT)を確立
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景と戦略
|
||||
|
||||
### Phase 171 での成果
|
||||
|
||||
- ✅ JsonParserBox 実装完了(454行)
|
||||
- ✅ hako_check (HC020) で使用開始
|
||||
- ✅ 289行の手書きパーサを削除(96%削減)
|
||||
|
||||
### Phase 172 の戦略
|
||||
|
||||
**再利用拡大の3ステップ**:
|
||||
|
||||
1. **再利用候補の特定**: Program(JSON v0) を読む箇所を洗い出し
|
||||
2. **薄いラッパー追加**: `parse_program()` ヘルパーを JsonParserBox に追加
|
||||
3. **段階的適用**: Stage-B/selfhost の 1-2 箇所から開始
|
||||
|
||||
### selfhost depth-2 への準備
|
||||
|
||||
- JSON 処理が共通箱に集約されることで
|
||||
- .hako JoinIR/MIR 移植時に構造が崩れにくくなる
|
||||
- selfhost depth-2 の基盤が整う
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope / Non-scope
|
||||
|
||||
### ✅ やること
|
||||
|
||||
1. **再利用候補の棚卸し(再スキャン)**
|
||||
- Stage-B/selfhost での JSON 利用箇所を洗い出し
|
||||
- 現状の JSON 読み取り方法を調査
|
||||
- JsonParserBox 適用可否を評価
|
||||
|
||||
2. **Program(JSON v0) 用ラッパー追加**
|
||||
- `parse_program()` メソッドを JsonParserBox に追加
|
||||
- ProgramJSONBox(薄いヘルパー型)を定義
|
||||
- Program JSON の定形構造に対応
|
||||
|
||||
3. **Stage-B/selfhost への適用**
|
||||
- 1-2 箇所から段階的に適用
|
||||
- 自前 substring 処理を JsonParserBox に置き換え
|
||||
|
||||
4. **hako_check 仕上げ**
|
||||
- Phase 171 の残りタスク完了確認
|
||||
- JsonParserBox API の十分性確認
|
||||
|
||||
5. **テスト & 回帰**
|
||||
- hako_check: HC019/HC020 スモーク
|
||||
- Stage-B/selfhost: selfhost_phase150_depth1_smoke
|
||||
- JsonParserBox: 単体テスト
|
||||
|
||||
6. **ドキュメント更新**
|
||||
|
||||
### ❌ やらないこと
|
||||
|
||||
- to_json() / serialization(Phase 173+)
|
||||
- スキーマ検証(Phase 173+)
|
||||
- 全 JSON 利用箇所の一括置き換え(段階的に)
|
||||
|
||||
---
|
||||
|
||||
## 3. Task 1: 再利用候補の棚卸し(再スキャン)
|
||||
|
||||
### 対象候補
|
||||
|
||||
1. **Stage-B/selfhost での JSON 利用**
|
||||
- `lang/src/compiler/entry/compiler.hako` - Program JSON 処理
|
||||
- `apps/selfhost-vm/json_loader.hako` - JSON ローダー
|
||||
- `tools/selfhost/*` - selfhost ツール群
|
||||
|
||||
2. **その他ツール・テスト**
|
||||
- `apps/tests/` 内の JSON を扱うテスト
|
||||
- 開発ツールの JSON 解析コード
|
||||
|
||||
### やること
|
||||
|
||||
1. **Program(JSON v0) 消費箇所の洗い出し**
|
||||
```bash
|
||||
rg '"version".*0' lang tools apps
|
||||
rg 'Program.*JSON' lang tools apps
|
||||
rg 'substring.*\{' lang tools apps # 自前 JSON 解析の疑い
|
||||
```
|
||||
|
||||
2. **各箇所の評価**
|
||||
| ファイル | 現状の読み方 | JsonParserBox 適用可否 | 優先度 |
|
||||
|---------|-------------|---------------------|--------|
|
||||
| lang/src/compiler/entry/compiler.hako | 自前 split/substring | ✅ 可能 | 高 |
|
||||
| apps/selfhost-vm/json_loader.hako | 自前 substring | ✅ 可能 | 中 |
|
||||
| tools/selfhost/helpers.hako | まだ読んでいない | ⏸️ 将来 | 低 |
|
||||
|
||||
3. **phase170_hako_json_library_design.md に追記**
|
||||
- 「Phase 172 対象候補」セクションを追加
|
||||
- 評価結果を表にまとめる
|
||||
|
||||
### 成果物
|
||||
|
||||
- 再利用候補リスト(優先度付き)
|
||||
- 各候補の評価結果
|
||||
|
||||
---
|
||||
|
||||
## 4. Task 2: Program(JSON v0) 用の薄いラッパーを JsonParserBox に追加
|
||||
|
||||
### 目的
|
||||
|
||||
Program(JSON v0) の定形構造を読みやすくする
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 0,
|
||||
"kind": "Program",
|
||||
"defs": [...],
|
||||
"meta": {
|
||||
"usings": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### やること
|
||||
|
||||
1. **JsonParserBox に Program JSON ヘルパーを追加**
|
||||
|
||||
```hako
|
||||
static box JsonParserBox {
|
||||
// 既存
|
||||
method parse(json_str) { ... }
|
||||
method parse_object(json_str) { ... }
|
||||
method parse_array(json_str) { ... }
|
||||
|
||||
// 新規(Phase 172)
|
||||
method parse_program(json_str) {
|
||||
local obj = me.parse_object(json_str)
|
||||
if obj == null { return null }
|
||||
|
||||
// Program JSON の必須フィールド確認
|
||||
local version = obj.get("version")
|
||||
local kind = obj.get("kind")
|
||||
if version == null or kind != "Program" { return null }
|
||||
|
||||
// ProgramJSONBox を返す
|
||||
local prog = new ProgramJSONBox()
|
||||
prog._obj = obj
|
||||
return prog
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **ProgramJSONBox の定義**
|
||||
|
||||
```hako
|
||||
// 薄いラッパー: Program JSON の構造に特化
|
||||
static box ProgramJSONBox {
|
||||
_obj: MapBox # 内部で parse_object() の結果を保持
|
||||
|
||||
method get_version() {
|
||||
return me._obj.get("version")
|
||||
}
|
||||
|
||||
method get_kind() {
|
||||
return me._obj.get("kind")
|
||||
}
|
||||
|
||||
method get_defs() {
|
||||
// ArrayBox を返す
|
||||
return me._obj.get("defs")
|
||||
}
|
||||
|
||||
method get_meta() {
|
||||
// MapBox を返す
|
||||
return me._obj.get("meta")
|
||||
}
|
||||
|
||||
method get_usings() {
|
||||
local meta = me.get_meta()
|
||||
if meta == null { return null }
|
||||
return meta.get("usings")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **実装場所**
|
||||
- `tools/hako_shared/json_parser.hako` に追加
|
||||
- Phase 171 実装の拡張として統合
|
||||
|
||||
### 成果物
|
||||
|
||||
- `parse_program()` メソッド実装
|
||||
- ProgramJSONBox 型定義
|
||||
|
||||
---
|
||||
|
||||
## 5. Task 3: Stage-B / selfhost 補助から JsonParserBox に寄せる
|
||||
|
||||
### 対象ファイル
|
||||
|
||||
1. **第一候補**: `lang/src/compiler/entry/compiler.hako`
|
||||
- Program(JSON v0) を読む主要箇所
|
||||
- 置き換え効果が大きい
|
||||
|
||||
2. **第二候補**: `apps/selfhost-vm/json_loader.hako`
|
||||
- JSON ローダー補助
|
||||
- 再利用性確認に適切
|
||||
|
||||
### やること
|
||||
|
||||
1. **現状の JSON 読み取り処理を特定**
|
||||
```hako
|
||||
// 修正前の例(自前 substring)
|
||||
local json_str = file.read("program.json")
|
||||
local version_pos = json_str.indexOf('"version"')
|
||||
local defs_pos = json_str.indexOf('"defs"')
|
||||
// ... 手作業の解析 ...
|
||||
```
|
||||
|
||||
2. **JsonParserBox を使用した実装に置き換え**
|
||||
```hako
|
||||
// 修正後
|
||||
local json_str = file.read("program.json")
|
||||
local prog = JsonParserBox.parse_program(json_str)
|
||||
if prog == null {
|
||||
print("[ERROR] Invalid Program JSON")
|
||||
return null
|
||||
}
|
||||
|
||||
local version = prog.get_version()
|
||||
local defs = prog.get_defs()
|
||||
local usings = prog.get_usings()
|
||||
// 構造化されたアクセス
|
||||
```
|
||||
|
||||
3. **段階的適用**
|
||||
- 初回は 1 箇所のみ(lang/src/compiler/entry/compiler.hako)
|
||||
- テスト成功後、2 箇所目(apps/selfhost-vm/json_loader.hako)
|
||||
- 全箇所の一括置き換えはしない
|
||||
|
||||
### 成果物
|
||||
|
||||
- 修正済み `lang/src/compiler/entry/compiler.hako`
|
||||
- 修正済み `apps/selfhost-vm/json_loader.hako`(オプション)
|
||||
|
||||
---
|
||||
|
||||
## 6. Task 4: hako_check 側の JsonParserBox 利用を仕上げる
|
||||
|
||||
### やること
|
||||
|
||||
1. **Phase 171 の残りタスク確認**
|
||||
- `analysis_consumer.hako` から手書き JSON パーサが完全に削除されているか
|
||||
- `rule_dead_blocks.hako` が JsonParserBox を正しく使用しているか
|
||||
|
||||
2. **未置き換えコードの確認**
|
||||
```bash
|
||||
rg 'substring.*\{' tools/hako_check/
|
||||
rg 'indexOf.*\"' tools/hako_check/
|
||||
```
|
||||
|
||||
3. **JsonParserBox API の十分性確認**
|
||||
- hako_check が必要とする API は揃っているか
|
||||
- 追加が必要なメソッドはあるか
|
||||
- 重い拡張は Phase 173 backlog へ
|
||||
|
||||
### 成果物
|
||||
|
||||
- hako_check における JsonParserBox 利用の完成
|
||||
- API 拡張候補リスト(Phase 173 へ)
|
||||
|
||||
---
|
||||
|
||||
## 7. Task 5: テストと回帰チェック
|
||||
|
||||
### テストマトリックス
|
||||
|
||||
| 対象 | テストスクリプト | 確認内容 |
|
||||
|------|---------------|---------|
|
||||
| hako_check HC019 | `tools/hako_check_deadcode_smoke.sh` | dead code 検出(回帰なし) |
|
||||
| hako_check HC020 | `tools/hako_check_deadblocks_smoke.sh` | dead block 検出(回帰なし) |
|
||||
| Stage-B/selfhost | `tools/selfhost/selfhost_phase150_depth1_smoke.sh` | Program JSON 読み込み動作 |
|
||||
| JsonParserBox | `tools/hako_shared/tests/json_parser_simple_test.hako` | 単体テスト全 PASS |
|
||||
|
||||
### やること
|
||||
|
||||
1. **hako_check スモークテスト**
|
||||
```bash
|
||||
./tools/hako_check_deadcode_smoke.sh
|
||||
./tools/hako_check_deadblocks_smoke.sh
|
||||
```
|
||||
期待: HC019/HC020 の出力が変わらない
|
||||
|
||||
2. **Stage-B/selfhost スモークテスト**
|
||||
```bash
|
||||
./tools/selfhost/selfhost_phase150_depth1_smoke.sh
|
||||
```
|
||||
期待: Program JSON 読み込みで回帰なし
|
||||
|
||||
3. **JsonParserBox 単体テスト**
|
||||
```bash
|
||||
NYASH_USE_NY_COMPILER=1 ./target/release/hakorune tools/hako_shared/tests/json_parser_simple_test.hako
|
||||
```
|
||||
期待: 全テスト PASS
|
||||
|
||||
### 成果物
|
||||
|
||||
- 全スモークテスト成功
|
||||
- 回帰なし確認
|
||||
|
||||
---
|
||||
|
||||
## 8. Task 6: ドキュメント & CURRENT_TASK 更新
|
||||
|
||||
### ドキュメント更新
|
||||
|
||||
1. **phase170_hako_json_library_design.md に追記**
|
||||
```markdown
|
||||
## Phase 172 実装結果
|
||||
|
||||
✅ JsonParserBox 再利用拡大完了
|
||||
- Program(JSON v0) 対応: parse_program() + ProgramJSONBox
|
||||
- Stage-B 統合: lang/src/compiler/entry/compiler.hako
|
||||
- selfhost 統合: apps/selfhost-vm/json_loader.hako
|
||||
- hako_check 仕上げ: Phase 171 の残りタスク完了
|
||||
|
||||
📊 統合実績:
|
||||
- hako_check: HC019/HC020 で使用(Phase 171)
|
||||
- Stage-B: Program JSON 読み込みで使用(Phase 172)
|
||||
- selfhost: JSON ローダーで使用(Phase 172)
|
||||
|
||||
📋 未統合箇所(Phase 173+ 候補):
|
||||
- tools/selfhost/helpers.hako(低優先度)
|
||||
- apps/tests/ 内の一部テスト
|
||||
```
|
||||
|
||||
2. **hako_check_design.md / selfhost 関連 docs を更新**
|
||||
```markdown
|
||||
### JSON 解析の実装場所
|
||||
|
||||
**Phase 172 から全て JsonParserBox に集約**:
|
||||
- 場所: tools/hako_shared/json_parser.hako
|
||||
- 利用者:
|
||||
- hako_check: MIR/CFG JSON 解析
|
||||
- Stage-B: Program(JSON v0) 読み込み
|
||||
- selfhost: JSON ローディング
|
||||
- 単一の真実(SSOT)確立
|
||||
```
|
||||
|
||||
3. **CURRENT_TASK.md に Phase 172 セクション追加**
|
||||
```markdown
|
||||
### Phase 172: JsonParserBox 再利用拡大 ✅
|
||||
|
||||
**完了内容**:
|
||||
- Program(JSON v0) 対応: parse_program() + ProgramJSONBox
|
||||
- Stage-B 統合: lang/src/compiler/entry/compiler.hako で使用開始
|
||||
- selfhost 統合: apps/selfhost-vm/json_loader.hako で使用開始
|
||||
- hako_check 仕上げ: Phase 171 の残りタスク完了
|
||||
|
||||
**成果**:
|
||||
- JSON 処理の単一の真実(SSOT)確立
|
||||
- hako_check/Stage-B/selfhost が同じ箱を使用
|
||||
- selfhost depth-2 への基盤整備完了
|
||||
|
||||
**次フェーズ**: Phase 173 で to_json() 逆変換、Phase 160+ で .hako JoinIR/MIR 移植
|
||||
```
|
||||
|
||||
### git commit
|
||||
|
||||
```
|
||||
feat(json): Phase 172 JsonParserBox reuse expansion
|
||||
|
||||
✨ JsonParserBox を Stage-B/selfhost/ツールに統合!
|
||||
|
||||
🎯 Program(JSON v0) 対応:
|
||||
- parse_program() メソッド追加
|
||||
- ProgramJSONBox 薄いヘルパー定義
|
||||
- version/kind/defs/meta/usings アクセサ
|
||||
|
||||
📦 統合実績:
|
||||
- lang/src/compiler/entry/compiler.hako: Program JSON 読み込み
|
||||
- apps/selfhost-vm/json_loader.hako: JSON ローダー
|
||||
- tools/hako_check/: Phase 171 仕上げ完了
|
||||
|
||||
✅ テスト:
|
||||
- hako_check スモーク: HC019/HC020 回帰なし
|
||||
- Stage-B/selfhost スモーク: Program JSON 読み込み OK
|
||||
- JsonParserBox 単体テスト: 全 PASS
|
||||
|
||||
🏗️ SSOT 確立:
|
||||
- JSON 処理が JsonParserBox に集約
|
||||
- hako_check/Stage-B/selfhost が同じ箱を使用
|
||||
- selfhost depth-2 への基盤完成
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 完成チェックリスト(Phase 172)
|
||||
|
||||
- [ ] Task 1: 再利用候補の棚卸し
|
||||
- [ ] Program(JSON v0) 消費箇所の洗い出し
|
||||
- [ ] 各箇所の評価(適用可否・優先度)
|
||||
- [ ] phase170 に追記
|
||||
- [ ] Task 2: Program(JSON v0) ラッパー追加
|
||||
- [ ] parse_program() 実装
|
||||
- [ ] ProgramJSONBox 定義
|
||||
- [ ] json_parser.hako に統合
|
||||
- [ ] Task 3: Stage-B/selfhost への適用
|
||||
- [ ] lang/src/compiler/entry/compiler.hako 修正
|
||||
- [ ] apps/selfhost-vm/json_loader.hako 修正(オプション)
|
||||
- [ ] 自前 substring 処理を削除
|
||||
- [ ] Task 4: hako_check 仕上げ
|
||||
- [ ] Phase 171 残りタスク確認
|
||||
- [ ] API 十分性確認
|
||||
- [ ] 拡張候補リスト作成
|
||||
- [ ] Task 5: テスト & 回帰
|
||||
- [ ] hako_check スモーク
|
||||
- [ ] Stage-B/selfhost スモーク
|
||||
- [ ] JsonParserBox 単体テスト
|
||||
- [ ] Task 6: ドキュメント更新
|
||||
- [ ] phase170 に追記
|
||||
- [ ] hako_check_design.md 更新
|
||||
- [ ] selfhost 関連 docs 更新
|
||||
- [ ] CURRENT_TASK.md 追加
|
||||
- [ ] git commit
|
||||
|
||||
---
|
||||
|
||||
## 技術的ポイント
|
||||
|
||||
### Program(JSON v0) の構造
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 0,
|
||||
"kind": "Program",
|
||||
"defs": [
|
||||
{"kind": "Box", "name": "Main", ...},
|
||||
{"kind": "Method", "name": "main", ...}
|
||||
],
|
||||
"meta": {
|
||||
"usings": ["nyashstd", "mylib"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ProgramJSONBox の使い方
|
||||
|
||||
```hako
|
||||
local prog = JsonParserBox.parse_program(json_str)
|
||||
if prog == null {
|
||||
print("[ERROR] Invalid Program JSON")
|
||||
return
|
||||
}
|
||||
|
||||
// 型安全なアクセス
|
||||
local version = prog.get_version() // Integer
|
||||
local defs = prog.get_defs() // ArrayBox
|
||||
local usings = prog.get_usings() // ArrayBox?
|
||||
|
||||
// defs をループ
|
||||
for def in defs {
|
||||
local kind = def.get("kind")
|
||||
if kind == "Box" {
|
||||
local name = def.get("name")
|
||||
print("Box: " + name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 段階的適用の重要性
|
||||
|
||||
- 一気に全箇所を変更しない
|
||||
- 1 箇所 → テスト → 2 箇所目 の順序
|
||||
- 問題があれば早期発見
|
||||
|
||||
---
|
||||
|
||||
**作成日**: 2025-12-04
|
||||
**Phase**: 172(JsonParserBox 再利用拡大)
|
||||
**予定工数**: 2-3 時間
|
||||
**難易度**: 低-中(既存実装の適用 + 薄いラッパー追加)
|
||||
**期待効果**: JSON 処理 SSOT 確立、selfhost depth-2 基盤完成
|
||||
@ -1,180 +0,0 @@
|
||||
# Phase 173-2 Completion Summary
|
||||
|
||||
**Date**: 2025-12-04
|
||||
**Status**: Investigation Complete, Strategy Revision Required
|
||||
|
||||
## Summary
|
||||
|
||||
Phase 173-2 investigation has been completed successfully. Through detailed VM tracing and code analysis, I identified the root cause of the static box method resolution issue and proposed two alternative implementation strategies.
|
||||
|
||||
## What Was Accomplished ✅
|
||||
|
||||
### 1. Deep Investigation
|
||||
- **VM Execution Tracing**: Used `NYASH_CALLEE_RESOLVE_TRACE=1` and `NYASH_DEBUG_FUNCTION_LOOKUP=1` to trace the complete execution flow
|
||||
- **MIR Lowering Analysis**: Verified that static box internal calls (`me.method()`) correctly lower to `Callee::Global`
|
||||
- **VM Function Lookup**: Confirmed all JsonParserBox methods are properly registered in the function table
|
||||
- **Error Point Identification**: Pinpointed that the error occurs at the call site in Main, not in the VM execution
|
||||
|
||||
### 2. Root Cause Identified
|
||||
**Problem**: Parser treats `JsonParserBox` as a **variable** (VarRef) instead of a **type** (TypeRef)
|
||||
|
||||
**Evidence**:
|
||||
```
|
||||
Main.main():
|
||||
JsonParserBox.parse("{\"x\":1}")
|
||||
↓ Parser treats "JsonParserBox" as variable (VarRef)
|
||||
↓ MIR generates Callee::Method with undefined receiver
|
||||
↓ Receiver defaults to "InstanceBox"
|
||||
↓ ERROR: "Unknown method 'parse' on InstanceBox"
|
||||
```
|
||||
|
||||
**Confirmed Working**:
|
||||
- Internal static box calls: `me.method()` within JsonParserBox ✅
|
||||
- Function registration: All methods in VM function table ✅
|
||||
- Function lookup: VM successfully finds functions ✅
|
||||
|
||||
**Not Working**:
|
||||
- External static box calls: `JsonParserBox.parse()` from Main ❌
|
||||
|
||||
### 3. Strategy Revision
|
||||
**Original Plan Issues**:
|
||||
- Violated "Rust VM不変" (Rust VM unchanged) principle
|
||||
- Required complex .hako compiler modifications
|
||||
- Introduced scope creep (essentially building a type system)
|
||||
|
||||
**Revised Recommendations**:
|
||||
|
||||
#### Option 1: Minimal Parser Fix (Recommended)
|
||||
**Approach**: Detect `UsingAlias.method()` pattern in parser, emit StaticBoxCall AST node
|
||||
|
||||
**Changes required**:
|
||||
1. **Parser** (.hako): Add using alias table lookup in call expression parsing (~30 lines)
|
||||
2. **AST**: Add StaticBoxCall node type or flag to existing MethodCall
|
||||
3. **MIR lowering** (Rust): Handle StaticBoxCall → `Callee::Global` (~20 lines)
|
||||
|
||||
**Effort**: 2-3 hours
|
||||
**Risk**: Low (isolated, additive changes)
|
||||
**Benefit**: Clean solution, proper syntax support
|
||||
|
||||
#### Option 2: Workaround Documentation (Quick Exit)
|
||||
**Approach**: Document workaround pattern, defer to Phase 174+ type system work
|
||||
|
||||
**Pattern**:
|
||||
```hako
|
||||
// Workaround: Create dummy instance
|
||||
local parser = new JsonParserBox()
|
||||
parser.parse("{}") // Works via Global call lowering
|
||||
```
|
||||
|
||||
**Effort**: 30 minutes
|
||||
**Risk**: None
|
||||
**Benefit**: Unblocks other work, defers complexity
|
||||
|
||||
## Documentation Created
|
||||
|
||||
1. **phase173-2_investigation_findings.md** (330+ lines)
|
||||
- Complete technical analysis
|
||||
- Root cause explanation with call flow diagrams
|
||||
- Two implementation options with trade-offs
|
||||
- Test case status and diagnosis
|
||||
|
||||
2. **CURRENT_TASK.md** (updated)
|
||||
- Task 1-3 marked complete (investigation + JsonParserBox bugfix)
|
||||
- Task 4-6 revised with new strategy recommendations
|
||||
- Root cause summary added
|
||||
- Files created/modified list updated
|
||||
|
||||
3. **phase173-2_completion_summary.md** (this document)
|
||||
- High-level overview for stakeholders
|
||||
- Clear recommendations
|
||||
- Next steps
|
||||
|
||||
## Technical Insights
|
||||
|
||||
### Architecture Principle Adherence
|
||||
✅ **箱化モジュール化** (Modular Boxing): Investigation maintained the principle of isolated, incremental changes
|
||||
✅ **Rust VM不変** (Rust VM Unchanged): Proposed solutions minimize Rust VM changes
|
||||
✅ **段階的確認** (Staged Verification): Traced AST → MIR → VM flow systematically
|
||||
|
||||
### Code Quality
|
||||
- All investigation code is read-only (no modifications during investigation)
|
||||
- Comprehensive tracing and logging used for debugging
|
||||
- Clear separation of concerns maintained
|
||||
|
||||
### Knowledge Gained
|
||||
1. **Static box internal calls work correctly**: The MIR lowering already handles `me.method()` properly in static box context
|
||||
2. **VM infrastructure is sound**: Function registration, lookup, and execution all work as expected
|
||||
3. **Parser is the bottleneck**: The issue is purely at the parser level, not in VM or MIR lowering
|
||||
|
||||
## Recommendation
|
||||
|
||||
**Proceed with Option 1** (Minimal Parser Fix) because:
|
||||
1. **Well-scoped**: Clear boundaries, minimal changes
|
||||
2. **Architecturally sound**: Aligns with existing design principles
|
||||
3. **User-friendly**: Provides the expected syntax (`JsonParserBox.parse()`)
|
||||
4. **Low risk**: Changes are additive and testable
|
||||
5. **Immediate value**: Unblocks Phase 171-2 (hako_check integration)
|
||||
|
||||
**Alternative**: If time-constrained or if there are other higher-priority tasks, use Option 2 as an interim solution.
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Immediate (Decision Required)
|
||||
User/stakeholder should decide:
|
||||
- [ ] Option 1: Implement minimal parser fix (2-3 hours)
|
||||
- [ ] Option 2: Document workaround, defer to Phase 174+ (30 minutes)
|
||||
|
||||
### After Decision
|
||||
**If Option 1**:
|
||||
1. Implement parser enhancement for `UsingAlias.method()` detection
|
||||
2. Add StaticBoxCall AST node or flag
|
||||
3. Modify MIR lowering to handle StaticBoxCall
|
||||
4. Test with json_parser_min.hako (expect RC 0)
|
||||
5. Run hako_check smoke tests (HC019/HC020)
|
||||
6. Update documentation and commit
|
||||
|
||||
**If Option 2**:
|
||||
1. Update using.md with workaround pattern and examples
|
||||
2. Add note to LANGUAGE_REFERENCE_2025.md
|
||||
3. Mark Phase 173 as "interim complete" with workaround
|
||||
4. Schedule Phase 174 for comprehensive type system work
|
||||
5. Update documentation and commit
|
||||
|
||||
## Impact Assessment
|
||||
|
||||
### Blocked/Unblocked
|
||||
**Currently Blocked**:
|
||||
- Phase 171-2: hako_check JsonParserBox integration
|
||||
- Any code using `using` for static box libraries
|
||||
|
||||
**Will Unblock** (with Option 1):
|
||||
- Phase 171-2: Can complete hako_check integration
|
||||
- JsonParserBox as official standard library
|
||||
- Future static box libraries (e.g., ProgramJSONBox usage)
|
||||
|
||||
### Technical Debt
|
||||
**Option 1**: Minimal debt (proper solution)
|
||||
**Option 2**: Moderate debt (workaround until Phase 174+)
|
||||
|
||||
## Files for Review
|
||||
|
||||
### Investigation Documents
|
||||
- `phase173-2_investigation_findings.md` (detailed analysis, read first)
|
||||
- `phase173-2_completion_summary.md` (this document, executive summary)
|
||||
- `phase173_task1-2_completion_report.md` (Task 1-2 details)
|
||||
- `mir-nested-if-loop-bug.md` (related bug found during investigation)
|
||||
|
||||
### Test Cases
|
||||
- `apps/tests/json_parser_min.hako` (currently fails, will pass after fix)
|
||||
|
||||
### Reference
|
||||
- `docs/reference/language/using.md` (Phase 173 static box section)
|
||||
- `docs/reference/language/LANGUAGE_REFERENCE_2025.md` (static box usage)
|
||||
|
||||
---
|
||||
|
||||
**Created**: 2025-12-04
|
||||
**Phase**: 173-2 (using resolver + MIR lowering)
|
||||
**Outcome**: Investigation complete, two implementation options proposed
|
||||
**Recommendation**: Option 1 (minimal parser fix)
|
||||
**Estimated Completion**: 2-3 hours (Option 1) or 30 minutes (Option 2)
|
||||
@ -1,211 +0,0 @@
|
||||
# Phase 173-2: Implementation Complete
|
||||
|
||||
**Date**: 2025-12-04
|
||||
**Status**: Analysis Complete, Core Issue Identified
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Investigation into Phase 173-2 (using resolver + MIR lowering) has revealed that **the Rust MIR builder already correctly handles static box method calls**. The issue described in the investigation findings document appears to be solved at the MIR generation level.
|
||||
|
||||
## Investigation Results
|
||||
|
||||
### Trace Evidence
|
||||
|
||||
Running with `NYASH_STATIC_CALL_TRACE=1` shows:
|
||||
|
||||
```
|
||||
[DEBUG] 'JsonParserBox' not in variable_map - treating as static box, will use global call
|
||||
[builder] static-call JsonParserBox.parse/1
|
||||
[builder] static-call JsonParserBox._skip_whitespace/2
|
||||
[builder] static-call JsonParserBox._match_literal/3
|
||||
```
|
||||
|
||||
**Conclusion**: The MIR builder IS correctly recognizing `JsonParserBox` as a static box and generating global calls, not method calls.
|
||||
|
||||
### MIR Output Analysis
|
||||
|
||||
```
|
||||
define i64 @main() {
|
||||
bb0:
|
||||
1: %1: String = const "{"x":1}"
|
||||
1: %3: Box("JsonParserBox") = new JsonParserBox()
|
||||
1: %6: String = copy %1
|
||||
1: %2: String = call_method JsonParserBox.parse(%6) [recv: %7] [Known]
|
||||
1: %9: Integer = const 0
|
||||
1: ret %9
|
||||
}
|
||||
```
|
||||
|
||||
**Issue Identified**: Line 4 shows `call_method` with receiver `%7` (which is never defined). This is inconsistent with the trace showing "static-call".
|
||||
|
||||
### Root Cause Hypothesis
|
||||
|
||||
The discrepancy between the trace output ("static-call") and the MIR dump ("call_method") suggests:
|
||||
|
||||
1. **MIR generation is correct** (trace confirms this)
|
||||
2. **MIR dumping/printing may be showing outdated information** OR
|
||||
3. **There's a transformation step after initial MIR generation** that's converting static calls back to method calls
|
||||
|
||||
### Current Error
|
||||
|
||||
```
|
||||
[ERROR] ❌ [rust-vm] VM error: Invalid instruction: Unknown method '_skip_whitespace' on InstanceBox
|
||||
```
|
||||
|
||||
This error occurs at **runtime**, not during MIR generation. The method `_skip_whitespace` is being called on an `InstanceBox` receiver instead of the correct static box.
|
||||
|
||||
## Implementation Work Done
|
||||
|
||||
### Task 4: Stage-3 Parser Modifications
|
||||
|
||||
**Files Modified**:
|
||||
1. `/home/tomoaki/git/hakorune-selfhost/lang/src/compiler/parser/parser_box.hako`
|
||||
- Added `is_using_alias(name)` helper method (lines 199-211)
|
||||
- Checks if a name is a using alias by searching in `usings_json`
|
||||
|
||||
2. `/home/tomoaki/git/hakorune-selfhost/lang/src/compiler/parser/expr/parser_expr_box.hako`
|
||||
- Modified Method call parsing (lines 227-240)
|
||||
- Added detection for using aliases in receiver position
|
||||
- Added `is_static_box_call: true` flag to Method AST node when receiver is a using alias
|
||||
|
||||
**Note**: These changes are in `.hako` files and won't take effect until the Stage-3 parser is recompiled into the binary. This creates a chicken-and-egg problem for testing.
|
||||
|
||||
### Task 5: MIR Lowering Analysis
|
||||
|
||||
**Finding**: No Rust code modifications needed!
|
||||
|
||||
The existing Rust MIR builder code already handles static box calls correctly:
|
||||
|
||||
**Location**: `src/mir/builder/calls/build.rs:418-450`
|
||||
- `try_build_static_method_call()` checks if identifier is in `variable_map`
|
||||
- If NOT in variable_map → treats as static box → calls `handle_static_method_call()`
|
||||
- `handle_static_method_call()` emits `CallTarget::Global` (line 147 in `method_call_handlers.rs`)
|
||||
|
||||
**Location**: `src/mir/builder/method_call_handlers.rs:126-149`
|
||||
- `handle_static_method_call()` correctly generates global function calls
|
||||
- Function name format: `BoxName.method/arity`
|
||||
- Uses `emit_unified_call()` with `CallTarget::Global`
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Option A: Debug the Discrepancy (Recommended)
|
||||
|
||||
1. **Investigate MIR dump vs trace mismatch**
|
||||
- Why does trace show "static-call" but MIR dump shows "call_method"?
|
||||
- Check if there's a post-processing step that transforms Global calls to Method calls
|
||||
|
||||
2. **Add detailed MIR emission logging**
|
||||
- Log what's actually emitted by `emit_unified_call()`
|
||||
- Verify that `CallTarget::Global` is reaching the instruction emitter
|
||||
|
||||
3. **Check VM call handler**
|
||||
- How does VM execute Global calls vs Method calls?
|
||||
- Why is receiver defaulting to InstanceBox?
|
||||
|
||||
### Option B: Direct Rust Fix (If Stage-3 parser changes don't work)
|
||||
|
||||
Since the `.hako` parser changes require recompilation, consider:
|
||||
|
||||
1. **Add JSON v0 field detection in Rust**
|
||||
- Modify Rust AST deserializer to recognize `is_static_box_call` flag
|
||||
- Use this flag as additional hint in `try_build_static_method_call()`
|
||||
|
||||
2. **Strengthen static box detection**
|
||||
- Check against list of known static boxes from merged preludes
|
||||
- Use using resolution metadata available at Rust runner level
|
||||
|
||||
### Option C: Workaround Documentation
|
||||
|
||||
If immediate fix is complex:
|
||||
|
||||
1. Document current workaround:
|
||||
```hako
|
||||
// Instead of:
|
||||
JsonParserBox.parse("{}")
|
||||
|
||||
// Use (works inside static boxes):
|
||||
me.parse("{}") // when calling from within JsonParserBox
|
||||
|
||||
// Or explicit function call (if supported):
|
||||
JsonParserBox.parse/1("{}")
|
||||
```
|
||||
|
||||
2. Mark Phase 173-2 as "deferred pending type system"
|
||||
3. Move to Phase 174+ with comprehensive type system work
|
||||
|
||||
## Technical Insights
|
||||
|
||||
### Using Alias Resolution Flow
|
||||
|
||||
1. **Rust Runner Level** (`src/runner/pipeline.rs`):
|
||||
- Processes `using` statements
|
||||
- Resolves file paths
|
||||
- Merges prelude text (DFS, circular detection)
|
||||
|
||||
2. **Parser Level** (`.hako` or Rust):
|
||||
- Receives merged text with both `using` statements and static box definitions
|
||||
- Should recognize static box names in merged text
|
||||
|
||||
3. **MIR Builder Level** (Rust):
|
||||
- Checks `variable_map` to distinguish local vars from static boxes
|
||||
- Successfully detects `JsonParserBox` as static (not in variable_map)
|
||||
- Generates correct `CallTarget::Global` calls
|
||||
|
||||
### Why Current System Works (Mostly)
|
||||
|
||||
- **Inside static boxes**: `me.method()` calls work perfectly
|
||||
- **Between static boxes**: `BoxName.method()` is recognized correctly by MIR builder
|
||||
- **Problem area**: Something between MIR generation and VM execution
|
||||
|
||||
## Test Results
|
||||
|
||||
### Successful Behaviors
|
||||
- ✅ Using statement resolution works
|
||||
- ✅ JsonParserBox methods compile to MIR
|
||||
- ✅ Internal static calls (`me.method()`) work
|
||||
- ✅ Function registration in VM function table
|
||||
- ✅ MIR builder recognizes `JsonParserBox` as static
|
||||
|
||||
### Failing Behavior
|
||||
- ❌ Runtime execution fails with "Unknown method on InstanceBox"
|
||||
- ❌ MIR dump shows inconsistent `call_method` instead of expected global call
|
||||
|
||||
## Recommendations
|
||||
|
||||
**Immediate**: Debug the MIR dump vs trace discrepancy (Option A, step 1-2)
|
||||
|
||||
**Short-term**: If Stage-3 parser changes aren't taking effect, implement Option B (JSON v0 field detection in Rust)
|
||||
|
||||
**Long-term**: Implement comprehensive HIR layer with proper type resolution (Phase 174+)
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. `lang/src/compiler/parser/parser_box.hako` - Added `is_using_alias()` helper
|
||||
2. `lang/src/compiler/parser/expr/parser_expr_box.hako` - Added static box call detection
|
||||
|
||||
## Files to Review for Debugging
|
||||
|
||||
1. `src/mir/builder/calls/build.rs` - Static method call detection
|
||||
2. `src/mir/builder/method_call_handlers.rs` - Static call emission
|
||||
3. `src/mir/builder/calls/emit.rs` - Unified call emission
|
||||
4. `src/backend/mir_interpreter/handlers/calls/` - VM call handlers
|
||||
5. `src/mir/printer.rs` - MIR dump formatting (may explain discrepancy)
|
||||
|
||||
## Conclusion
|
||||
|
||||
Phase 173-2 investigation has revealed that:
|
||||
|
||||
1. **Parser changes implemented** (but need recompilation to test)
|
||||
2. **MIR builder already works correctly** (no Rust changes needed at this level)
|
||||
3. **Runtime issue exists** (VM execution or MIR transformation problem)
|
||||
4. **Next action**: Debug MIR dump discrepancy and VM call handling
|
||||
|
||||
The core using resolver + MIR lowering integration is **functionally complete** at the design level. The remaining issue is a runtime execution problem that requires debugging the VM call dispatch mechanism.
|
||||
|
||||
---
|
||||
|
||||
**Created**: 2025-12-04
|
||||
**Phase**: 173-2 (using resolver + MIR lowering)
|
||||
**Investigation Time**: 2 hours
|
||||
**Complexity**: Medium (Runtime debugging required)
|
||||
**Blocking**: No (workarounds available)
|
||||
@ -1,200 +0,0 @@
|
||||
# Phase 173-2 Investigation Findings
|
||||
|
||||
**Date**: 2025-12-04
|
||||
**Status**: Investigation Complete, Implementation Strategy Needs Revision
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Completed investigation of Phase 173-2 requirements. Found that the original instruction document's approach needs revision based on actual system behavior and architecture principles.
|
||||
|
||||
## Investigation Results
|
||||
|
||||
### What Works ✅
|
||||
1. **Static box file loading**: using statement correctly loads JsonParserBox source
|
||||
2. **Function compilation**: All JsonParserBox methods compile to MIR correctly
|
||||
3. **Function registration**: All static methods registered in VM function table with correct names (`JsonParserBox.method/arity`)
|
||||
4. **Internal static calls**: `me.method()` calls within static box work correctly
|
||||
5. **Function lookup**: VM successfully finds all static box functions
|
||||
|
||||
### What Doesn't Work ❌
|
||||
1. **External static box calls**: `JsonParserBox.parse()` from Main doesn't work
|
||||
2. **Root cause**: Parser treats `JsonParserBox` as a **variable** (VarRef), not a **type** (TypeRef)
|
||||
3. **Result**: MIR lowering generates `Callee::Method` with an undefined receiver, leading to "Unknown method on InstanceBox" error
|
||||
|
||||
## Technical Analysis
|
||||
|
||||
### Current Call Flow (Broken)
|
||||
|
||||
```
|
||||
Main.main():
|
||||
JsonParserBox.parse("{\"x\":1}")
|
||||
↓ Parser: treats "JsonParserBox" as variable
|
||||
↓ AST: CallTarget::Method { receiver: VarRef("JsonParserBox"), method: "parse" }
|
||||
↓ MIR: Callee::Method { receiver: ValueId(?), method: "parse", box_name: "InstanceBox" }
|
||||
↓ VM: ERROR - receiver has no type, defaults to InstanceBox
|
||||
```
|
||||
|
||||
### Expected Call Flow (Target)
|
||||
|
||||
```
|
||||
Main.main():
|
||||
JsonParserBox.parse("{\"x\":1}")
|
||||
↓ Parser: recognizes "JsonParserBox" as type
|
||||
↓ AST: CallTarget::StaticMethod { box_type: "JsonParserBox", method: "parse" }
|
||||
↓ MIR: Callee::Global("JsonParserBox.parse/1")
|
||||
↓ VM: SUCCESS - function table lookup, execute
|
||||
```
|
||||
|
||||
## Original Implementation Plan Issues
|
||||
|
||||
### Instruction Document Approach
|
||||
The instruction document (phase173-2_using_resolver_mir_lowering.md) proposes:
|
||||
1. Modify using resolver (.hako) to register static boxes as types
|
||||
2. Modify parser (.hako) to recognize `Alias.method()` as type references
|
||||
3. Modify MIR lowering (Rust) to detect static box calls
|
||||
|
||||
### Problems with This Approach
|
||||
1. **Violates "Rust VM不変" principle**: Would require changes to Rust MIR lowering
|
||||
2. **Complex .hako modifications**: Requires symbol table/type system in .hako compiler
|
||||
3. **Scope creep**: Essentially implementing a type system in Stage-1 parser
|
||||
4. **Maintenance burden**: Two-language coordination (.hako parser + Rust MIR)
|
||||
|
||||
## Recommended Alternative Approach
|
||||
|
||||
### Strategy: AST-level Static Call Recognition
|
||||
|
||||
**Principle**: Minimize changes, leverage existing infrastructure
|
||||
|
||||
### Phase A: Parser Enhancement (Minimal)
|
||||
**File**: `lang/src/compiler/parser/parser_calls.hako` (or similar)
|
||||
**Change**: Add special handling for `Alias.method()` pattern where Alias is from using
|
||||
|
||||
```hako
|
||||
// When parsing method call expression:
|
||||
if receiver_is_identifier(receiver) {
|
||||
local name = receiver_name
|
||||
if is_using_alias(name) { // Check against using table
|
||||
// Emit StaticBoxCall instead of MethodCall
|
||||
return make_static_box_call_ast(name, method, args)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase B: AST Representation
|
||||
**File**: Extend AST to support static box calls explicitly
|
||||
**Options**:
|
||||
1. Add `StaticBoxCall` AST node type
|
||||
2. Or: Add flag to existing MethodCall: `is_static_box_call: true`
|
||||
|
||||
### Phase C: MIR Lowering (Minimal Rust Change)
|
||||
**File**: `src/mir/builder/calls/builder_calls.rs`
|
||||
**Change**: Detect StaticBoxCall AST node and emit `Callee::Global`
|
||||
|
||||
```rust
|
||||
match ast_call {
|
||||
AstCallType::StaticBoxCall { box_name, method, args } => {
|
||||
let func_name = format!("{}.{}", box_name, method);
|
||||
// Emit Callee::Global(func_name) with args
|
||||
}
|
||||
// ... existing cases
|
||||
}
|
||||
```
|
||||
|
||||
## Alternative: Quick Fix Approach
|
||||
|
||||
### If Full Implementation is Too Complex
|
||||
|
||||
**Workaround**: Document that static box methods must be called with explicit constructor pattern:
|
||||
|
||||
```hako
|
||||
// Instead of:
|
||||
JsonParserBox.parse("{}")
|
||||
|
||||
// Use:
|
||||
local parser = new JsonParserBox() // dummy instance
|
||||
parser.parse("{}") // works because lowered to Global
|
||||
|
||||
// Or use direct function call syntax (if supported):
|
||||
JsonParserBox.parse/1("{}")
|
||||
```
|
||||
|
||||
**Pros**: No code changes required
|
||||
**Cons**: Poor user experience, not the desired syntax
|
||||
|
||||
## Impact Assessment
|
||||
|
||||
### Affected Components
|
||||
1. **Parser** (.hako): Minimal changes to call expression parsing
|
||||
2. **AST** (JSON v0): Add static box call representation
|
||||
3. **MIR lowering** (Rust): Add static box call handling (~20 lines)
|
||||
4. **VM**: No changes required ✅
|
||||
|
||||
### Risk Level
|
||||
- **Low**: Changes are isolated and additive
|
||||
- **No breaking changes**: Existing code continues to work
|
||||
- **Testable**: Can verify with json_parser_min.hako immediately
|
||||
|
||||
## Test Case Status
|
||||
|
||||
### json_parser_min.hako
|
||||
**Current**: ❌ Fails with "Unknown method '_skip_whitespace' on InstanceBox"
|
||||
**Expected after fix**: ✅ RC 0, no errors
|
||||
|
||||
**Current output**:
|
||||
```
|
||||
[DEBUG/vm] Looking up function: 'JsonParserBox._skip_whitespace'
|
||||
[DEBUG/vm] ✅ 'JsonParserBox._skip_whitespace/2' found
|
||||
[ERROR] ❌ [rust-vm] VM error: Invalid instruction: Unknown method '_skip_whitespace' on InstanceBox
|
||||
```
|
||||
|
||||
### Diagnosis
|
||||
- Function **is** found in function table
|
||||
- Error happens when trying to execute because receiver is InstanceBox
|
||||
- Root cause: Call site in Main generates Method call, not Global call
|
||||
|
||||
## Next Steps (Revised)
|
||||
|
||||
### Option 1: Implement Minimal Parser Fix (Recommended)
|
||||
1. Add using alias table to parser context
|
||||
2. Detect `UsingAlias.method()` pattern in call parsing
|
||||
3. Emit StaticBoxCall AST node
|
||||
4. Handle in MIR lowering to emit Callee::Global
|
||||
5. Test with json_parser_min.hako
|
||||
|
||||
**Estimated effort**: 2-3 hours
|
||||
**Risk**: Low
|
||||
**Benefit**: Clean solution, proper syntax support
|
||||
|
||||
### Option 2: Document Workaround (Quick Exit)
|
||||
1. Update using.md with workaround pattern
|
||||
2. Mark Phase 173 as "deferred" pending type system work
|
||||
3. Move to Phase 174+ with comprehensive type system
|
||||
|
||||
**Estimated effort**: 30 minutes
|
||||
**Risk**: None
|
||||
**Benefit**: Unblocks other work, defers complexity
|
||||
|
||||
## Recommendation
|
||||
|
||||
**Proceed with Option 1**: The minimal parser fix is well-scoped, aligns with architecture principles, and provides immediate value without introducing technical debt.
|
||||
|
||||
**Alternative**: If time-constrained, use Option 2 as interim solution and schedule Option 1 for Phase 174.
|
||||
|
||||
## Files to Review
|
||||
|
||||
### Investigation Evidence
|
||||
- Trace output: See "Test current behavior" section above
|
||||
- MIR lowering code: `src/mir/builder/calls/resolver.rs` (lines 80-120)
|
||||
- VM function lookup: `src/backend/mir_interpreter/handlers/calls/global.rs` (lines 5-60)
|
||||
|
||||
### Implementation Targets
|
||||
- Parser: `lang/src/compiler/parser/parser_calls.hako` or similar
|
||||
- AST: JSON v0 schema (or extend existing MethodCall node)
|
||||
- MIR lowering: `src/mir/builder/calls/builder_calls.rs`
|
||||
|
||||
---
|
||||
|
||||
**Created**: 2025-12-04
|
||||
**Phase**: 173-2 Investigation
|
||||
**Outcome**: Strategy revision required
|
||||
**Recommendation**: Minimal parser fix (Option 1)
|
||||
@ -1,446 +0,0 @@
|
||||
# Phase 173-2: using resolver + MIR lowering 修正
|
||||
|
||||
## 0. ゴール
|
||||
|
||||
**using 経由で静的 Box を正しく解決して VM に渡す。**
|
||||
|
||||
目的:
|
||||
- .hako の using resolver が静的 Box を「型/名前空間」として環境に登録
|
||||
- MIR lowering が静的 Box メソッドを適切な BoxCall/MethodCall に変換
|
||||
- JsonParserBox が「正式な標準ライブラリ」として完全動作
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
### Phase 173 前半の成果
|
||||
|
||||
- ✅ Task 1: 名前解決経路調査完了
|
||||
- ✅ Task 2: 仕様固定(docs)完了
|
||||
- ✅ Task 3: JsonParserBox バグ修正完了
|
||||
- MIR Nested-If-in-Loop Bug 発見・回避
|
||||
- `while` → `loop()` 統一
|
||||
- json_parser.hako 正常動作確認
|
||||
|
||||
### この Phase 後半でやること
|
||||
|
||||
**Task 4-6**: using 経由での静的 Box 解決を完全実装
|
||||
- using resolver 修正
|
||||
- MIR lowering 調整
|
||||
- テスト・ドキュメント整備
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope / Non-scope
|
||||
|
||||
### ✅ やること
|
||||
|
||||
1. **using resolver 修正**
|
||||
- 静的 Box を「型/名前空間」として環境に登録
|
||||
- `Alias.BoxName` を型参照として識別
|
||||
|
||||
2. **MIR lowering 調整**
|
||||
- 静的 Box 呼び出しを BoxCall/MethodCall に正しく変換
|
||||
- 既存の instance Box / plugin Box 呼び出しに影響を与えない
|
||||
|
||||
3. **テスト・ドキュメント整備**
|
||||
- json_parser_min.hako 動作確認
|
||||
- hako_check スモークテスト
|
||||
- ドキュメント更新
|
||||
|
||||
### ❌ やらないこと
|
||||
|
||||
- CoreBoxId / CoreMethodId 周りの変更
|
||||
- VM コアの変更
|
||||
- JsonParserBox の機能追加
|
||||
|
||||
---
|
||||
|
||||
## 3. Task 4: using resolver 修正
|
||||
|
||||
### 目的
|
||||
|
||||
.hako 側の using 解決が、静的 Box を「型/名前空間」として環境に登録できるようにする。
|
||||
|
||||
### 対象ファイル
|
||||
|
||||
- `lang/src/compiler/entry/using_resolver*.hako`
|
||||
- `lang/src/compiler/runner/launcher.hako`(using を扱うエントリ周辺)
|
||||
|
||||
### やること
|
||||
|
||||
#### 1. 現状の動作確認
|
||||
|
||||
```bash
|
||||
# using resolver の構造確認
|
||||
rg 'using|resolver|Alias' lang/src/compiler/entry/ --type hako -n
|
||||
|
||||
# AST/シンボルテーブルの確認
|
||||
rg 'symbol|type.*table|namespace' lang/src/compiler/ --type hako -l
|
||||
```
|
||||
|
||||
**確認項目**:
|
||||
- `using tools.hako_shared.json_parser as JsonLib` のとき:
|
||||
- `JsonLib` が AST/内部シンボルテーブル上でどう扱われているか
|
||||
- 単なるモジュール名なのか、Box 名(JsonParserBox)まで解決されているか
|
||||
|
||||
#### 2. 修正方針
|
||||
|
||||
**静的 Box の名前空間登録**:
|
||||
```
|
||||
using ... as Alias で static box を含む .hako を読み込んだ場合:
|
||||
↓
|
||||
Alias を「static box を持つ名前空間」として登録
|
||||
↓
|
||||
Alias.JsonParserBox のような参照を「型参照」として識別可能に
|
||||
```
|
||||
|
||||
**設計上の決定**:
|
||||
- `Alias.parse(...)` を「Alias が static box そのもの」な場合に認めるか?
|
||||
- 今は `new Alias.JsonParserBox()` パターン優先で良い
|
||||
- JsonParserBox はインスタンス Box の方が自然
|
||||
|
||||
#### 3. 実装内容
|
||||
|
||||
**A. 静的 Box の登録**:
|
||||
```hako
|
||||
method register_static_box_alias(alias, box_name) {
|
||||
# シンボルテーブルに alias → static box のマッピング登録
|
||||
local entry = new SymbolEntry()
|
||||
entry.set_kind("static_box_namespace")
|
||||
entry.set_box_name(box_name)
|
||||
|
||||
me.symbol_table.register(alias, entry)
|
||||
}
|
||||
```
|
||||
|
||||
**B. 型参照の識別**:
|
||||
```hako
|
||||
method resolve_type_ref(ref) {
|
||||
# Alias.BoxName パターンの判定
|
||||
if ref.has_namespace() {
|
||||
local alias = ref.get_namespace()
|
||||
local entry = me.symbol_table.lookup(alias)
|
||||
|
||||
if entry.is_static_box_namespace() {
|
||||
# 型参照として解決
|
||||
return me.resolve_as_type(entry, ref.get_name())
|
||||
}
|
||||
}
|
||||
|
||||
# 通常の型参照処理
|
||||
return me.resolve_normal_type(ref)
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. 変更を小さくする
|
||||
|
||||
**重要な制約**:
|
||||
- 既存の using(namespace/file using)を壊さない
|
||||
- 変更は「静的 Box を alias に結び付ける部分」だけに限定
|
||||
- 段階的な変更で影響範囲を最小化
|
||||
|
||||
### 成果物
|
||||
|
||||
- using_resolver*.hako 修正
|
||||
- 静的 Box の名前空間登録実装
|
||||
- 型参照識別ロジック追加
|
||||
|
||||
---
|
||||
|
||||
## 4. Task 5: MIR lowering の調整
|
||||
|
||||
### 目的
|
||||
|
||||
AST 上で「Alias.JsonParserBox」やそのメソッド呼び出しが正しく解決された前提で、MIR lowering がそれを適切な BoxCall/MethodCall に落とすようにする。
|
||||
|
||||
### 対象ファイル
|
||||
|
||||
- `src/mir/builder/calls/resolver.rs`
|
||||
- `src/mir/builder/calls/call_unified.rs`
|
||||
|
||||
### やること
|
||||
|
||||
#### 1. 具体的なパターンを想定
|
||||
|
||||
**パターン A: Box 生成**
|
||||
```hako
|
||||
local p = new JsonLib.JsonParserBox()
|
||||
```
|
||||
↓ MIR
|
||||
```rust
|
||||
NewBox { box_id: JsonParserBox, namespace: Some("JsonLib") }
|
||||
```
|
||||
|
||||
**パターン B: インスタンスメソッド**
|
||||
```hako
|
||||
p.parse("123")
|
||||
```
|
||||
↓ MIR(既に動いているはず)
|
||||
```rust
|
||||
MethodCall { receiver: p, method: "parse", args: ["123"] }
|
||||
```
|
||||
|
||||
**パターン C: 静的メソッド(将来)**
|
||||
```hako
|
||||
JsonLib.JsonParserBox.parse_static(...)
|
||||
```
|
||||
↓ MIR(今は未対応、余地だけ残す)
|
||||
```rust
|
||||
BoxCall { box: JsonParserBox, method: "parse_static", args: [...] }
|
||||
```
|
||||
|
||||
#### 2. ロジック修正
|
||||
|
||||
**A. 静的 Box 判定**
|
||||
```rust
|
||||
// src/mir/builder/calls/resolver.rs
|
||||
fn is_static_box_call(ast_node: &AstNode) -> bool {
|
||||
// receiver の部分が名前空間付き静的 Box 由来であるか判定
|
||||
if let Some(namespace) = ast_node.get_namespace() {
|
||||
if let Some(entry) = self.symbol_table.lookup(namespace) {
|
||||
return entry.is_static_box_namespace();
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
```
|
||||
|
||||
**B. BoxId の正しい解決**
|
||||
```rust
|
||||
// src/mir/builder/calls/call_unified.rs
|
||||
fn resolve_box_id(ast_node: &AstNode) -> Result<BoxId, Error> {
|
||||
if self.is_static_box_call(ast_node) {
|
||||
// 名前空間付き静的 Box の場合
|
||||
let box_name = ast_node.get_box_name();
|
||||
return self.unified_box_registry.lookup_box_id(box_name);
|
||||
}
|
||||
|
||||
// 通常の Box 解決
|
||||
self.resolve_normal_box_id(ast_node)
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 安全性の確保
|
||||
|
||||
**重要な制約**:
|
||||
- 既存の instance Box / plugin Box 呼び出しに影響を与えない
|
||||
- 分岐を追加する形で実装
|
||||
- CoreBoxId / CoreMethodId 周りの規則には手を出さない
|
||||
|
||||
**テスト戦略**:
|
||||
```bash
|
||||
# 既存テストが通ることを確認
|
||||
cargo test --release
|
||||
|
||||
# 特に instance Box のテスト
|
||||
cargo test --release instance_box
|
||||
cargo test --release plugin_box
|
||||
```
|
||||
|
||||
### 成果物
|
||||
|
||||
- resolver.rs 修正
|
||||
- call_unified.rs 修正
|
||||
- 静的 Box 判定ロジック追加
|
||||
|
||||
---
|
||||
|
||||
## 5. Task 6: テスト・docs・CURRENT_TASK でフェーズを締める
|
||||
|
||||
### やること
|
||||
|
||||
#### 1. JsonParserBox 最小テスト
|
||||
|
||||
**テストファイル** (`apps/tests/json_parser_min.hako`):
|
||||
```hako
|
||||
using tools.hako_shared.json_parser as JsonLib
|
||||
|
||||
static box Main {
|
||||
main() {
|
||||
local parser = new JsonLib.JsonParserBox()
|
||||
local v = parser.parse("{\"x\":1}")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**実行確認**:
|
||||
```bash
|
||||
./target/release/nyash apps/tests/json_parser_min.hako
|
||||
|
||||
# 期待: RC 0、Unknown Box/method が出ない
|
||||
```
|
||||
|
||||
#### 2. hako_check スモークテスト
|
||||
|
||||
```bash
|
||||
# HC019 スモークテスト
|
||||
./tools/hako_check_deadcode_smoke.sh
|
||||
|
||||
# HC020 スモークテスト
|
||||
./tools/hako_check_deadblocks_smoke.sh
|
||||
|
||||
# 期待: JsonParserBox 差し替え後も HC019/HC020 の挙動に変化なし
|
||||
```
|
||||
|
||||
#### 3. ドキュメント更新
|
||||
|
||||
**A. Phase 170-173 ドキュメント**:
|
||||
- `phase170_hako_json_library_design.md`
|
||||
- `phase171_jsonparserbox_implementation.md`
|
||||
- `phase173_using_static_box_resolution.md`
|
||||
|
||||
追記内容:
|
||||
```markdown
|
||||
### Phase 173 完了(2025-12-04)
|
||||
|
||||
**成果**:
|
||||
- using + static box の解決が整備され、JsonParserBox が正式にライブラリとして使用可能に
|
||||
- MIR Nested-If-in-Loop Bug を発見・回避
|
||||
- using resolver + MIR lowering の統合完了
|
||||
|
||||
**技術的成果**:
|
||||
- 静的 Box を名前空間として環境に登録
|
||||
- BoxCall/MethodCall への正しい変換
|
||||
- VM で安定して動作確認
|
||||
```
|
||||
|
||||
**B. using.md**:
|
||||
```markdown
|
||||
### JsonParserBox を例にした静的 Box の using
|
||||
|
||||
\`\`\`hako
|
||||
using tools.hako_shared.json_parser as JsonLib
|
||||
|
||||
static box Main {
|
||||
main() {
|
||||
# 静的 Box のインスタンス化
|
||||
local parser = new JsonLib.JsonParserBox()
|
||||
|
||||
# メソッド呼び出し
|
||||
local obj = parser.parse("{\"name\": \"Alice\"}")
|
||||
local name = obj.get("name")
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
#### 4. CURRENT_TASK 更新
|
||||
|
||||
```markdown
|
||||
### Phase 173: using + 静的 Box メソッド解決 ✅
|
||||
|
||||
**完了内容**:
|
||||
- Task 1-3: 調査・仕様固定・JsonParserBox バグ修正 ✅
|
||||
- Task 4-6: using resolver + MIR lowering 統合 ✅
|
||||
|
||||
**技術的成果**:
|
||||
- 静的 Box の using 解決と MIR lowering が整備
|
||||
- JsonParserBox を含む static box library 呼び出しが VM 上で安定動作
|
||||
- MIR Nested-If-in-Loop Bug を発見・ドキュメント化
|
||||
|
||||
**次のステップ**:
|
||||
- Phase 174: to_json() 逆変換実装
|
||||
- Phase 175: selfhost depth-2 JSON 統一化
|
||||
```
|
||||
|
||||
#### 5. git commit
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat(using): Phase 173-2 using resolver + MIR lowering integration complete
|
||||
|
||||
🎉 using + 静的 Box 解決の完全実装!
|
||||
|
||||
🔧 実装内容:
|
||||
- using resolver: 静的 Box を名前空間として環境に登録
|
||||
- MIR lowering: BoxCall/MethodCall への正しい変換実装
|
||||
- 安全性: 既存 instance Box / plugin Box 呼び出しに影響なし
|
||||
|
||||
✅ テスト結果:
|
||||
- json_parser_min.hako: RC 0、エラーなし
|
||||
- hako_check HC019: PASS
|
||||
- hako_check HC020: PASS
|
||||
- 回帰テスト: すべて PASS
|
||||
|
||||
📚 ドキュメント整備:
|
||||
- Phase 170-173 完了記録
|
||||
- using.md に JsonParserBox 実例追加
|
||||
- CURRENT_TASK.md 更新
|
||||
|
||||
🎯 JsonParserBox が正式な標準ライブラリとして完全動作!
|
||||
Phase 171-2 のブロック解除、hako_check 統合完了
|
||||
|
||||
🤖 Generated with Claude Code
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 完成チェックリスト(Phase 173-2)
|
||||
|
||||
- [ ] Task 4: using resolver 修正
|
||||
- [ ] 現状動作確認
|
||||
- [ ] 静的 Box 名前空間登録実装
|
||||
- [ ] 型参照識別ロジック追加
|
||||
- [ ] Task 5: MIR lowering 調整
|
||||
- [ ] 静的 Box 判定ロジック追加
|
||||
- [ ] BoxId 解決修正
|
||||
- [ ] 安全性確認(既存テスト PASS)
|
||||
- [ ] Task 6: テスト・ドキュメント整備
|
||||
- [ ] json_parser_min.hako 動作確認
|
||||
- [ ] hako_check スモーク PASS
|
||||
- [ ] ドキュメント更新完了
|
||||
- [ ] CURRENT_TASK.md 更新
|
||||
- [ ] git commit
|
||||
|
||||
---
|
||||
|
||||
## 技術的注意点
|
||||
|
||||
### 段階的な変更
|
||||
|
||||
1. **Phase 1**: using resolver のみ修正 → テスト
|
||||
2. **Phase 2**: MIR lowering のみ修正 → テスト
|
||||
3. **Phase 3**: 統合テスト → ドキュメント
|
||||
|
||||
### 安全性の確保
|
||||
|
||||
- 既存コードへの影響を最小化
|
||||
- 各段階でテストを実行
|
||||
- 問題があればロールバック可能に
|
||||
|
||||
### デバッグ戦略
|
||||
|
||||
```bash
|
||||
# using resolver のデバッグ
|
||||
NYASH_DEBUG_USING=1 ./target/release/nyash test.hako
|
||||
|
||||
# MIR lowering のデバッグ
|
||||
./target/release/nyash --dump-mir test.hako
|
||||
|
||||
# VM 実行のデバッグ
|
||||
NYASH_CLI_VERBOSE=1 ./target/release/nyash test.hako
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 次のステップ
|
||||
|
||||
Phase 173-2 完了後:
|
||||
- **Phase 174**: to_json() 逆変換実装
|
||||
- **Phase 175**: selfhost depth-2 JSON 統一化
|
||||
- **Phase 160+**: .hako JoinIR/MIR 移植
|
||||
|
||||
JsonParserBox が「正式な標準ライブラリ」として完全に機能するようになり、その上に hako_check のルール追加や .hako JoinIR/MIR を安心して乗せられるようになる!
|
||||
|
||||
---
|
||||
|
||||
**作成日**: 2025-12-04
|
||||
**Phase**: 173-2(using resolver + MIR lowering)
|
||||
**予定工数**: 4-6 時間
|
||||
**難易度**: 高(名前解決・MIR lowering の統合)
|
||||
**前提**: Phase 173 前半(Task 1-3)完了
|
||||
@ -1,383 +0,0 @@
|
||||
# Phase 173-1: JsonParser ループ再観測
|
||||
|
||||
**Date**: 2025-12-08
|
||||
**Purpose**: Trim P5 パイプラインを JsonParser に展開するための予備調査
|
||||
|
||||
---
|
||||
|
||||
## 観測対象ループ
|
||||
|
||||
### 1. _trim - Leading Whitespace
|
||||
|
||||
**File**: `tools/hako_shared/json_parser.hako`
|
||||
**Lines**: 330-337
|
||||
|
||||
```hako
|
||||
loop(start < end) {
|
||||
local ch = s.substring(start, start+1)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
start = start + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern 分類**: Pattern2 (break付き)
|
||||
**LoopBodyLocal**: `ch` (substring定義、break条件で使用)
|
||||
**Trim 類似度**: ★★★★★ (完全同型 - 既に実装済み)
|
||||
|
||||
**JoinIR 実行結果**: ✅ SUCCESS
|
||||
```
|
||||
[pattern2/check] Analyzing condition scope: 3 variables
|
||||
[pattern2/check] 'ch': LoopBodyLocal
|
||||
[pattern2/promoter] LoopBodyLocal 'ch' promoted to carrier 'is_ch_match'
|
||||
[pattern2/trim] Safe Trim pattern detected, implementing lowering
|
||||
[joinir/pattern2] Generated JoinIR for Loop with Break Pattern (Phase 170-B)
|
||||
```
|
||||
|
||||
**状態**: ✅ Phase 171 で既に P5 パイプライン実装済み
|
||||
|
||||
---
|
||||
|
||||
### 2. _trim - Trailing Whitespace
|
||||
|
||||
**File**: `tools/hako_shared/json_parser.hako`
|
||||
**Lines**: 340-347
|
||||
|
||||
```hako
|
||||
loop(end > start) {
|
||||
local ch = s.substring(end-1, end)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
end = end - 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern 分類**: Pattern2 (break付き)
|
||||
**LoopBodyLocal**: `ch` (substring定義、break条件で使用)
|
||||
**Trim 類似度**: ★★★★★ (完全同型 - 既に実装済み)
|
||||
|
||||
**JoinIR 実行結果**: ✅ SUCCESS
|
||||
```
|
||||
[pattern2/check] Analyzing condition scope: 3 variables
|
||||
[pattern2/check] 'ch': LoopBodyLocal
|
||||
[pattern2/promoter] LoopBodyLocal 'ch' promoted to carrier 'is_ch_match'
|
||||
[pattern2/trim] Safe Trim pattern detected, implementing lowering
|
||||
[joinir/pattern2] Generated JoinIR for Loop with Break Pattern (Phase 170-B)
|
||||
```
|
||||
|
||||
**状態**: ✅ Phase 171 で既に P5 パイプライン実装済み
|
||||
|
||||
---
|
||||
|
||||
### 3. _skip_whitespace
|
||||
|
||||
**File**: `tools/hako_shared/json_parser.hako`
|
||||
**Lines**: 310-321
|
||||
|
||||
```hako
|
||||
_skip_whitespace(s, pos) {
|
||||
local p = pos
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
p = p + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern 分類**: Pattern2 (break付き)
|
||||
**LoopBodyLocal**: `ch` (substring定義、break条件で使用)
|
||||
**Trim 類似度**: ★★★★★ (Trim の leading whitespace と完全同型)
|
||||
|
||||
**構造的特徴**:
|
||||
- `loop(p < s.length())` - 長さチェック(Trim の `start < end` と同等)
|
||||
- `local ch = s.substring(p, p+1)` - 1文字抽出(完全一致)
|
||||
- `if ch == " " || ch == "\t" || ch == "\n" || ch == "\r"` - 空白判定(完全一致)
|
||||
- `p = p + 1` vs `break` - 進行 vs 脱出(完全一致)
|
||||
|
||||
**JoinIR 実行可能性**: ✅ 既存の Trim パイプラインで即座に対応可能
|
||||
- `TrimLoopHelper::is_safe_trim()` が true を返すはず
|
||||
- `LoopBodyCarrierPromoter::try_promote()` で carrier 昇格成功するはず
|
||||
|
||||
**Phase 173-2 での選定**: ★★★★★ 第一候補(Trim と完全同型)
|
||||
|
||||
---
|
||||
|
||||
### 4. _parse_string
|
||||
|
||||
**File**: `tools/hako_shared/json_parser.hako`
|
||||
**Lines**: 150-178
|
||||
|
||||
```hako
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
|
||||
if ch == '"' {
|
||||
// End of string
|
||||
local result = new MapBox()
|
||||
result.set("value", me._unescape_string(str))
|
||||
result.set("pos", p + 1)
|
||||
result.set("type", "string")
|
||||
return result
|
||||
}
|
||||
|
||||
if ch == "\\" {
|
||||
// Escape sequence (workaround: flatten to avoid MIR nested-if bug)
|
||||
local has_next = 0
|
||||
if p + 1 < s.length() { has_next = 1 }
|
||||
|
||||
if has_next == 0 { return null }
|
||||
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
str = str + s.substring(p, p+1)
|
||||
p = p + 1
|
||||
continue
|
||||
}
|
||||
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern 分類**: Pattern4 候補(continue付き)
|
||||
**LoopBodyLocal**: `ch` (substring定義、複数条件で使用)
|
||||
**Trim 類似度**: ★★☆☆☆ (構造が複雑)
|
||||
|
||||
**構造的差異**:
|
||||
- ✅ `local ch = s.substring(p, p+1)` - Trim と同じ
|
||||
- ❌ 複数の条件分岐(`ch == '"'`, `ch == "\\"`)
|
||||
- ❌ return 文での早期終了
|
||||
- ❌ continue での反復継続
|
||||
- ❌ 中間状態の蓄積(`str = str + ch`)
|
||||
|
||||
**LoopBodyLocal 使用**: `ch` を複数箇所で使用
|
||||
**Phase 173 での対応**: ⚠️ 除外(複雑すぎる、Phase 174+ で検討)
|
||||
|
||||
---
|
||||
|
||||
### 5. _parse_array
|
||||
|
||||
**File**: `tools/hako_shared/json_parser.hako`
|
||||
**Lines**: 189-230+
|
||||
|
||||
```hako
|
||||
loop(p < s.length()) {
|
||||
// Parse value
|
||||
local val_result = me._parse_value(s, p)
|
||||
if val_result == null { return null }
|
||||
|
||||
local val = val_result.get("value")
|
||||
arr.push(val)
|
||||
p = val_result.get("pos")
|
||||
|
||||
p = me._skip_whitespace(s, p)
|
||||
|
||||
if p < s.length() {
|
||||
local next_ch = s.substring(p, p+1)
|
||||
if next_ch == "," {
|
||||
p = p + 1
|
||||
p = me._skip_whitespace(s, p)
|
||||
continue
|
||||
}
|
||||
if next_ch == "]" {
|
||||
// End of array
|
||||
p = p + 1
|
||||
local result = new MapBox()
|
||||
result.set("value", arr)
|
||||
result.set("pos", p)
|
||||
result.set("type", "array")
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern 分類**: Pattern4 候補(continue付き)
|
||||
**LoopBodyLocal**: `next_ch` (substring定義、条件で使用)
|
||||
**Trim 類似度**: ★☆☆☆☆ (構造が大きく異なる)
|
||||
|
||||
**構造的差異**:
|
||||
- ❌ MethodCall 多数(`me._parse_value`, `me._skip_whitespace`)
|
||||
- ❌ 複数の return 文(成功・失敗パス)
|
||||
- ❌ 配列操作(`arr.push(val)`)
|
||||
- ❌ MapBox からの値取得
|
||||
- ✅ `local next_ch = s.substring(p, p+1)` - Trim と同じパターン
|
||||
- ✅ `if next_ch == ","` - 単純な等価比較
|
||||
|
||||
**LoopBodyLocal 使用**: `next_ch` のみ(限定的)
|
||||
**Phase 173 での対応**: ⚠️ 除外(構造が複雑、Phase 175+ で検討)
|
||||
|
||||
---
|
||||
|
||||
### 6. _unescape_string
|
||||
|
||||
**File**: `tools/hako_shared/json_parser.hako`
|
||||
**Lines**: 373-410+
|
||||
|
||||
```hako
|
||||
loop(i < s.length()) {
|
||||
local ch = s.substring(i, i+1)
|
||||
|
||||
// Workaround: flatten to avoid MIR nested-if bug
|
||||
local is_escape = 0
|
||||
local has_next = 0
|
||||
|
||||
if ch == "\\" { is_escape = 1 }
|
||||
if i + 1 < s.length() { has_next = 1 }
|
||||
|
||||
local process_escape = 0
|
||||
if is_escape == 1 {
|
||||
if has_next == 1 {
|
||||
process_escape = 1
|
||||
}
|
||||
}
|
||||
|
||||
if process_escape == 1 {
|
||||
// ... complex escape handling
|
||||
}
|
||||
|
||||
result = result + ch
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern 分類**: Pattern1 候補(break/continue なし)
|
||||
**LoopBodyLocal**: `ch`, `is_escape`, `has_next`, `process_escape`
|
||||
**Trim 類似度**: ☆☆☆☆☆ (全く異なる)
|
||||
|
||||
**構造的差異**:
|
||||
- ❌ break/continue なし(自然終了のみ)
|
||||
- ❌ 複数の LoopBodyLocal 変数
|
||||
- ❌ ネストした条件分岐
|
||||
- ❌ エスケープシーケンス処理
|
||||
|
||||
**Phase 173 での対応**: ❌ 除外(Pattern1、LoopBodyLocal の問題なし)
|
||||
|
||||
**JoinIR 実行結果**: ❌ FAILED
|
||||
```
|
||||
[ERROR] ❌ MIR compilation error: [joinir/freeze] Loop lowering failed:
|
||||
JoinIR does not support this pattern, and LoopBuilder has been removed.
|
||||
```
|
||||
|
||||
**失敗理由**: Pattern1 でも JoinIR 未対応の構造あり(Phase 173 対象外)
|
||||
|
||||
---
|
||||
|
||||
## 選定候補
|
||||
|
||||
### Phase 173-2 で選ぶべきループ: `_skip_whitespace`
|
||||
|
||||
**選定理由**:
|
||||
|
||||
1. ✅ **Trim と完全同型**: 既存の P5 パイプラインがそのまま使える
|
||||
- `local ch = s.substring(p, p+1)` パターン
|
||||
- `if ch == " " || ch == "\t" || ch == "\n" || ch == "\r"` 空白判定
|
||||
- `break` での終了
|
||||
|
||||
2. ✅ **独立した関数**: 他の複雑なロジックに依存しない
|
||||
|
||||
3. ✅ **実用的**: JsonParser で頻繁に使われる(7箇所で呼び出し)
|
||||
|
||||
4. ✅ **テスト容易**: 単純な入出力でテスト可能
|
||||
|
||||
5. ✅ **既存実装の活用**:
|
||||
- `TrimLoopHelper::is_safe_trim()` - そのまま使える
|
||||
- `LoopBodyCarrierPromoter::try_promote()` - そのまま使える
|
||||
- Pattern2 lowerer - Trim 特例経路がそのまま機能する
|
||||
|
||||
**次点候補**: なし(他のループは複雑すぎるか、LoopBodyLocal 問題がない)
|
||||
|
||||
---
|
||||
|
||||
## 重要な発見
|
||||
|
||||
### ✅ JsonParser の _trim は既に P5 対応済み!
|
||||
|
||||
Phase 171 で実装した Trim パイプラインが、JsonParser の `_trim` メソッドで **既に完全動作** していることを確認:
|
||||
|
||||
```
|
||||
[pattern2/trim] Safe Trim pattern detected, implementing lowering
|
||||
[joinir/pattern2] Generated JoinIR for Loop with Break Pattern (Phase 170-B)
|
||||
```
|
||||
|
||||
これは、Phase 173 の目的が達成済みであることを意味する。
|
||||
|
||||
### 🎯 Phase 173 の真の価値
|
||||
|
||||
**既存実装の検証**: Trim パイプラインが JsonParser でも機能することを実証
|
||||
- ✅ `TrimLoopHelper` の汎用性確認
|
||||
- ✅ Pattern2 Trim 特例の堅牢性確認
|
||||
- ✅ LoopBodyLocal 昇格の実用性確認
|
||||
|
||||
**次のステップ**: `_skip_whitespace` で独立関数パターンの検証
|
||||
- Trim は static box のメソッド内ループ
|
||||
- _skip_whitespace は helper method 内ループ
|
||||
- 両方で機能すれば、P5 パイプラインの汎用性が完全証明される
|
||||
|
||||
---
|
||||
|
||||
## 次のステップ (Phase 173-2)
|
||||
|
||||
### 選定ループ: `_skip_whitespace`
|
||||
|
||||
**選定基準確認**:
|
||||
- ✅ LoopBodyLocal 変数を break 条件に使用
|
||||
- ✅ substring でループ内定義
|
||||
- ✅ OR chain での比較(空白文字判定)
|
||||
- ✅ Pattern2 構造(break 付き、continue なし)
|
||||
|
||||
**期待される動作**:
|
||||
1. `LoopConditionScopeBox::analyze()` - `ch` を LoopBodyLocal として検出
|
||||
2. `LoopBodyCarrierPromoter::try_promote()` - carrier `is_ch_match` に昇格
|
||||
3. `TrimLoopHelper::is_safe_trim()` - true を返す
|
||||
4. Pattern2 lowerer - Trim 特例経路で JoinIR 生成
|
||||
|
||||
**Phase 173-4 で実装**: ミニテストケースでの動作確認
|
||||
|
||||
---
|
||||
|
||||
## Phase 173-2 選定結果
|
||||
|
||||
**選定ループ**: `_skip_whitespace` (line 310-321)
|
||||
|
||||
**選定理由**:
|
||||
|
||||
1. **Trim と完全同型**: 構造が完全に一致
|
||||
- `loop(p < s.length())` ← Trim の `loop(start < end)` と等価
|
||||
- `local ch = s.substring(p, p+1)` ← 完全一致
|
||||
- `if ch == " " || ch == "\t" || ch == "\n" || ch == "\r"` ← 完全一致
|
||||
- `{ p = p + 1 } else { break }` ← Trim の `start = start + 1` と同パターン
|
||||
|
||||
2. **独立関数**: helper method として独立(テスト容易)
|
||||
|
||||
3. **実用性**: JsonParser で 7箇所で使用される頻出パターン
|
||||
|
||||
4. **既存実装で対応可能**:
|
||||
- `TrimLoopHelper::is_safe_trim()` がそのまま使える
|
||||
- `LoopBodyCarrierPromoter::try_promote()` で carrier 昇格可能
|
||||
- Pattern2 Trim 特例経路で JoinIR 生成可能
|
||||
|
||||
5. **検証価値**:
|
||||
- ✅ `_trim` メソッド内ループで既に成功(static box method)
|
||||
- 🎯 `_skip_whitespace` で helper method パターンを検証
|
||||
- → 両方成功すれば P5 パイプラインの汎用性が完全証明される
|
||||
|
||||
**次点候補**: なし
|
||||
|
||||
**理由**:
|
||||
- `_parse_string` - 複雑すぎる(return/continue 混在、Phase 174+)
|
||||
- `_parse_array` - MethodCall 多数、構造複雑(Phase 175+)
|
||||
- `_unescape_string` - Pattern1、LoopBodyLocal 問題なし(対象外)
|
||||
|
||||
**Phase 173-3 での設計方針**: 既存の Trim パイプラインをそのまま活用
|
||||
@ -1,310 +0,0 @@
|
||||
# Phase 173-3: JsonParser P5 Design
|
||||
|
||||
**Date**: 2025-12-08
|
||||
**Purpose**: Trim P5 パイプラインを JsonParser に展開する設計
|
||||
|
||||
---
|
||||
|
||||
## 基本方針
|
||||
|
||||
> **「Trim パターンをそのまま再利用」** - JsonParser の `_skip_whitespace` は Trim と完全同型なので、既存の TrimPatternInfo / TrimLoopHelper をそのまま使用できる。
|
||||
|
||||
### 重要な発見
|
||||
|
||||
Phase 173-1 の観測で判明した事実:
|
||||
|
||||
✅ **JsonParser の `_trim` メソッドは既に P5 対応済み!**
|
||||
|
||||
```
|
||||
[pattern2/trim] Safe Trim pattern detected, implementing lowering
|
||||
[pattern2/trim] Carrier: 'is_ch_match', original var: 'ch', whitespace chars: ["\r", "\n", "\t", " "]
|
||||
[joinir/pattern2] Generated JoinIR for Loop with Break Pattern (Phase 170-B)
|
||||
```
|
||||
|
||||
これは、Phase 171 で実装した P5 パイプラインが、以下の範囲で既に機能していることを意味する:
|
||||
|
||||
- ✅ `LoopConditionScopeBox::analyze()` - LoopBodyLocal 検出
|
||||
- ✅ `LoopBodyCarrierPromoter::try_promote()` - Carrier 昇格
|
||||
- ✅ `TrimLoopHelper::is_safe_trim()` - Trim パターン検証
|
||||
- ✅ Pattern2 lowerer - Trim 特例経路で JoinIR 生成
|
||||
|
||||
---
|
||||
|
||||
## 必要な対応
|
||||
|
||||
### 1. 命名の汎用化(将来対応、今回は不要)
|
||||
|
||||
現在の `TrimLoopHelper` は「Trim」という名前だが、実際は「LoopBodyLocal 変数を bool carrier に昇格する」汎用的なパターン。将来的には以下の名前変更を検討:
|
||||
|
||||
- `TrimPatternInfo` → `CharComparisonPatternInfo`
|
||||
- `TrimLoopHelper` → `CharComparisonLoopHelper`
|
||||
|
||||
**ただし Phase 173 では名前変更しない**(挙動不変を優先)。
|
||||
|
||||
**理由**:
|
||||
1. **動作実績**: Phase 171 で Trim として実装し、既に JsonParser._trim で動作確認済み
|
||||
2. **リスク回避**: 名前変更は破壊的変更のリスクあり
|
||||
3. **段階的実装**: まず JsonParser で動作確認してから、汎用化を検討
|
||||
|
||||
---
|
||||
|
||||
### 2. JsonParser 用のテストケース追加
|
||||
|
||||
**目的**: `_skip_whitespace` が P5 パイプラインで動作することを確認
|
||||
|
||||
**ファイル**: `local_tests/test_jsonparser_skip_whitespace.hako`(NEW)
|
||||
|
||||
**内容**:
|
||||
```hako
|
||||
static box JsonParserTest {
|
||||
s: StringBox
|
||||
pos: IntegerBox
|
||||
len: IntegerBox
|
||||
|
||||
skip_whitespace(input_str, start_pos) {
|
||||
me.s = input_str
|
||||
me.pos = start_pos
|
||||
me.len = me.s.length()
|
||||
|
||||
local p = me.pos
|
||||
loop(p < me.len) {
|
||||
local ch = me.s.substring(p, p+1)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
p = p + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
main() {
|
||||
print("=== JsonParser skip_whitespace Test ===")
|
||||
|
||||
local result = me.skip_whitespace(" \t\n hello", 0)
|
||||
print("Skipped to position: ")
|
||||
print(result)
|
||||
|
||||
if result == 5 {
|
||||
print("PASS: Correctly skipped 5 whitespace characters")
|
||||
return 0
|
||||
} else {
|
||||
print("FAIL: Expected position 5, got ")
|
||||
print(result)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**期待される動作**:
|
||||
1. `loop(p < me.len)` - Pattern2 として検出
|
||||
2. `local ch = me.s.substring(p, p+1)` - LoopBodyLocal として検出
|
||||
3. `if ch == " " || ...` - Trim パターンとして認識
|
||||
4. Carrier `is_ch_match` に昇格
|
||||
5. JoinIR 生成成功
|
||||
|
||||
---
|
||||
|
||||
### 3. Pattern2 での Trim 特例判定拡張(必要なら)
|
||||
|
||||
現在の Pattern2 は「Trim」という名前で判定しているが、実際は構造判定なので、JsonParser の `_skip_whitespace` も同じ経路を通るはず。
|
||||
|
||||
**確認事項**:
|
||||
- ✅ `TrimLoopHelper::is_safe_trim()` が JsonParser ループでも true になるか?
|
||||
- ✅ `substring()` メソッドの検出が機能するか?
|
||||
- ⚠️ `charAt()` メソッドの検出が必要か?(`_skip_whitespace` は `substring()` を使用)
|
||||
|
||||
**結論**: Phase 173-4 で実行時に確認
|
||||
|
||||
---
|
||||
|
||||
## データフロー(JsonParser 版)
|
||||
|
||||
```
|
||||
JsonParser._skip_whitespace (AST)
|
||||
↓
|
||||
LoopConditionScopeBox::analyze()
|
||||
↓ has_loop_body_local() == true (ch が LoopBodyLocal)
|
||||
LoopBodyCarrierPromoter::try_promote()
|
||||
↓ Promoted { trim_info }
|
||||
TrimPatternInfo::to_carrier_info()
|
||||
↓
|
||||
CarrierInfo::merge_from()
|
||||
↓
|
||||
TrimLoopHelper::is_safe_trim() → true
|
||||
↓
|
||||
Pattern2 lowerer (Trim 特例経路)
|
||||
↓
|
||||
JoinIR 生成(bool carrier: is_ch_match)
|
||||
```
|
||||
|
||||
**Trim との違い**: なし(完全同型)
|
||||
|
||||
---
|
||||
|
||||
## 実装方針
|
||||
|
||||
### Phase 173-4 で確認すべきこと
|
||||
|
||||
1. **LoopBodyLocal 検出**: JsonParser の `_skip_whitespace` が `LoopBodyCarrierPromoter` で検出されるか?
|
||||
- 期待: `local ch = s.substring(p, p+1)` が LoopBodyLocal として認識される
|
||||
|
||||
2. **Trim パターン認識**: `TrimLoopHelper::is_safe_trim()` が true になるか?
|
||||
- 期待: 空白文字判定パターンが検出される
|
||||
|
||||
3. **Trim 特例経路**: Pattern2 の Trim 特例経路を通るか?
|
||||
- 期待: `[pattern2/trim] Safe Trim pattern detected` が出力される
|
||||
|
||||
4. **JoinIR 生成**: JoinIR → MIR lowering が成功するか?
|
||||
- 期待: `[joinir/pattern2] Generated JoinIR for Loop with Break Pattern` が出力される
|
||||
|
||||
5. **実行成功**: 生成された MIR が正しく実行されるか?
|
||||
- 期待: `PASS: Correctly skipped 5 whitespace characters` が出力される
|
||||
|
||||
---
|
||||
|
||||
### 追加実装が必要な場合
|
||||
|
||||
**可能性のある拡張**:
|
||||
|
||||
1. **charAt() メソッドの検出**
|
||||
- 現在: `substring()` のみ検出
|
||||
- 拡張: `charAt()` も検出(JsonParser の一部のコードで使用)
|
||||
- 実装箇所: `src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs`
|
||||
- 方法: `is_substring_method_call()` を `is_char_extraction_method()` に拡張
|
||||
|
||||
2. **メソッド名の汎用化**
|
||||
- 現在: "substring" ハードコード
|
||||
- 拡張: "charAt", "substring" の両方に対応
|
||||
- リスク: 低(既存動作を壊さない追加)
|
||||
|
||||
**Phase 173-4 での判断基準**:
|
||||
- ✅ `_skip_whitespace` が `substring()` のみ使用 → 追加実装不要
|
||||
- ⚠️ 他の JsonParser ループが `charAt()` 使用 → Phase 174+ で対応
|
||||
|
||||
---
|
||||
|
||||
## 構造比較
|
||||
|
||||
### Trim (test_trim_main_pattern.hako)
|
||||
|
||||
```hako
|
||||
loop(start < end) {
|
||||
local ch = s.substring(start, start+1)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
start = start + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### JsonParser._skip_whitespace
|
||||
|
||||
```hako
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
p = p + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 構造的一致点
|
||||
|
||||
| 要素 | Trim | JsonParser | 一致 |
|
||||
|------|------|------------|------|
|
||||
| ループ条件 | `start < end` | `p < s.length()` | ✅ (比較演算) |
|
||||
| LoopBodyLocal | `local ch = s.substring(...)` | `local ch = s.substring(...)` | ✅ (完全一致) |
|
||||
| 空白判定 | `ch == " " \|\| ch == "\t" \|\| ...` | `ch == " " \|\| ch == "\t" \|\| ...` | ✅ (完全一致) |
|
||||
| 進行処理 | `start = start + 1` | `p = p + 1` | ✅ (加算代入) |
|
||||
| 終了処理 | `break` | `break` | ✅ (完全一致) |
|
||||
|
||||
**結論**: 100% 構造的に一致 → 既存の P5 パイプラインで完全対応可能
|
||||
|
||||
---
|
||||
|
||||
## リスク分析
|
||||
|
||||
### 低リスク要因
|
||||
|
||||
1. **既存実装の活用**:
|
||||
- `TrimLoopHelper` は既に JsonParser._trim で動作確認済み
|
||||
- `LoopBodyCarrierPromoter` は既に Trim で動作確認済み
|
||||
- Pattern2 Trim 特例経路は既に実装済み
|
||||
|
||||
2. **構造的一致**:
|
||||
- `_skip_whitespace` と Trim は完全同型
|
||||
- 新しいパターン認識ロジック不要
|
||||
|
||||
3. **独立性**:
|
||||
- `_skip_whitespace` は独立した helper method
|
||||
- 他のコードへの影響なし
|
||||
|
||||
### 潜在的リスク
|
||||
|
||||
1. **MethodCall 検出の差異**:
|
||||
- Trim: static box method 内のループ
|
||||
- JsonParser: helper method 内のループ
|
||||
- 影響: 低(AST レベルでは同じ構造)
|
||||
|
||||
2. **変数スコープの差異**:
|
||||
- Trim: `start`, `end` が method local
|
||||
- JsonParser: `p` が method local、`s` が parameter
|
||||
- 影響: 低(LoopConditionScopeBox は parameter も OuterLocal として扱う)
|
||||
|
||||
**結論**: 既存実装で対応可能、追加実装不要の見込み
|
||||
|
||||
---
|
||||
|
||||
## 次のステップ (Phase 173-4)
|
||||
|
||||
### 実装タスク
|
||||
|
||||
1. **テストケース作成**:
|
||||
- `local_tests/test_jsonparser_skip_whitespace.hako` 作成
|
||||
- Trim と同じ構造、JsonParser の文脈でテスト
|
||||
|
||||
2. **JoinIR モード実行**:
|
||||
```bash
|
||||
NYASH_JOINIR_CORE=1 NYASH_LEGACY_LOOPBUILDER=0 \
|
||||
./target/release/hakorune local_tests/test_jsonparser_skip_whitespace.hako
|
||||
```
|
||||
|
||||
3. **期待される出力確認**:
|
||||
```
|
||||
[pattern2/check] Analyzing condition scope: 3 variables
|
||||
[pattern2/check] 'ch': LoopBodyLocal
|
||||
[pattern2/promoter] LoopBodyLocal 'ch' promoted to carrier 'is_ch_match'
|
||||
[pattern2/trim] Safe Trim pattern detected, implementing lowering
|
||||
[joinir/pattern2] Generated JoinIR for Loop with Break Pattern
|
||||
PASS: Correctly skipped 5 whitespace characters
|
||||
```
|
||||
|
||||
4. **charAt() 対応確認**:
|
||||
- 必要なら `LoopBodyCarrierPromoter` を拡張
|
||||
- Phase 173-4 の実行結果で判断
|
||||
|
||||
5. **テスト実行**:
|
||||
```bash
|
||||
cargo test --release --lib loop_body_carrier_promoter
|
||||
cargo test --release --lib pattern2_with_break
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 成功基準
|
||||
|
||||
- ✅ `test_jsonparser_skip_whitespace.hako` が JoinIR で成功
|
||||
- ✅ `[pattern2/trim] Safe Trim pattern detected` 出力
|
||||
- ✅ `PASS: Correctly skipped 5 whitespace characters` 出力
|
||||
- ✅ 既存の Trim テストが引き続き PASS
|
||||
- ✅ ユニットテスト全て PASS
|
||||
|
||||
**ドキュメント成果物**:
|
||||
- `phase173-jsonparser-p5-impl.md` - 実装結果レポート
|
||||
- `CURRENT_TASK.md` - Phase 173 成果記録
|
||||
@ -1,331 +0,0 @@
|
||||
# Phase 173: JsonParser P5 Implementation
|
||||
|
||||
**Date**: 2025-12-08
|
||||
**Status**: ✅ Pattern detection successful, P5 pipeline verified
|
||||
**Purpose**: Expand Trim P5 pipeline to JsonParser
|
||||
|
||||
---
|
||||
|
||||
## 成果
|
||||
|
||||
### ✅ 通したループ
|
||||
|
||||
**選定**: `_skip_whitespace` (JsonParser の空白スキップループ)
|
||||
|
||||
**理由**:
|
||||
- Trim と完全同型の構造
|
||||
- LoopBodyLocal 変数 `ch` を bool carrier に昇格
|
||||
- Pattern2 の Trim 特例経路で JoinIR 生成成功
|
||||
|
||||
**実装ファイル**:
|
||||
- テストケース: `local_tests/test_jsonparser_skip_whitespace.hako`
|
||||
- ルーティング: `src/mir/builder/control_flow/joinir/routing.rs` (whitelist 追加)
|
||||
|
||||
---
|
||||
|
||||
## 技術的成果
|
||||
|
||||
### ✅ P5 パイプライン完全動作確認
|
||||
|
||||
**Pattern Detection Trace**:
|
||||
```
|
||||
[pattern2/check] Analyzing condition scope: 3 variables
|
||||
[pattern2/check] 'len': OuterLocal
|
||||
[pattern2/check] 'ch': LoopBodyLocal
|
||||
[pattern2/check] 'p': LoopParam
|
||||
[pattern2/check] has_loop_body_local() = true
|
||||
[pattern2/promotion] LoopBodyLocal detected in condition scope
|
||||
[pattern2/promoter] LoopBodyLocal 'ch' promoted to carrier 'is_ch_match'
|
||||
[pattern2/promoter] Phase 171-C-4: Merged carrier 'is_ch_match' into CarrierInfo (total carriers: 6)
|
||||
[pattern2/trim] Safe Trim pattern detected, implementing lowering
|
||||
[pattern2/trim] Carrier: 'is_ch_match', original var: 'ch', whitespace chars: ["\r", "\n", "\t", " "]
|
||||
```
|
||||
|
||||
**JoinIR Generation**:
|
||||
```
|
||||
[joinir/pattern2] Phase 170-D: Condition variables verified: {"p", "len", "is_ch_match"}
|
||||
[joinir/pattern2] Generated JoinIR for Loop with Break Pattern (Phase 170-B)
|
||||
[joinir/pattern2] Functions: main, loop_step, k_exit
|
||||
[joinir/pattern2] Exit PHI: k_exit receives i from both natural exit and break
|
||||
```
|
||||
|
||||
### ✅ Trim パイプラインの汎用性実証
|
||||
|
||||
**検証項目**:
|
||||
1. ✅ `LoopConditionScopeBox::analyze()` - LoopBodyLocal 検出成功
|
||||
2. ✅ `LoopBodyCarrierPromoter::try_promote()` - Carrier 昇格成功
|
||||
3. ✅ `TrimLoopHelper::is_safe_trim()` - Trim パターン認識成功
|
||||
4. ✅ Pattern2 lowerer - Trim 特例経路で JoinIR 生成成功
|
||||
|
||||
**成果**:
|
||||
- ✅ Trim (static box method 内ループ) で動作確認済み
|
||||
- ✅ JsonParser (helper method 内ループ) でも動作確認完了
|
||||
- → P5 パイプラインの汎用性が完全証明された
|
||||
|
||||
---
|
||||
|
||||
## 実装詳細
|
||||
|
||||
### 1. テストケース作成
|
||||
|
||||
**File**: `local_tests/test_jsonparser_skip_whitespace.hako`
|
||||
|
||||
**構造**:
|
||||
```hako
|
||||
static box JsonParserTest {
|
||||
_skip_whitespace(input_str, start_pos, input_len) {
|
||||
local s = input_str
|
||||
local pos = start_pos
|
||||
local len = input_len
|
||||
|
||||
local p = pos
|
||||
loop(p < len) {
|
||||
local ch = s.substring(p, p+1)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
p = p + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
main() {
|
||||
// Test: " \t\n hello" has 5 leading whitespace characters
|
||||
local test_str = " \t\n hello"
|
||||
local test_len = test_str.length()
|
||||
local result = me._skip_whitespace(test_str, 0, test_len)
|
||||
|
||||
if result == 5 {
|
||||
print("PASS")
|
||||
return "OK"
|
||||
} else {
|
||||
print("FAIL")
|
||||
return "FAIL"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Trim との構造的一致**:
|
||||
| 要素 | Trim | JsonParser Test | 一致 |
|
||||
|------|------|-----------------|------|
|
||||
| ループ条件 | `start < end` | `p < len` | ✅ |
|
||||
| LoopBodyLocal | `local ch = s.substring(...)` | `local ch = s.substring(...)` | ✅ |
|
||||
| 空白判定 | `ch == " " \|\| ch == "\t" \|\| ...` | `ch == " " \|\| ch == "\t" \|\| ...` | ✅ |
|
||||
| 進行処理 | `start = start + 1` | `p = p + 1` | ✅ |
|
||||
| 終了処理 | `break` | `break` | ✅ |
|
||||
|
||||
### 2. ルーティング拡張
|
||||
|
||||
**File**: `src/mir/builder/control_flow/joinir/routing.rs`
|
||||
|
||||
**Changes**:
|
||||
```rust
|
||||
// Phase 173: JsonParser P5 expansion test
|
||||
"JsonParserTest._skip_whitespace/3" => true,
|
||||
"JsonParserTest.main/0" => true,
|
||||
```
|
||||
|
||||
**理由**:
|
||||
- JoinIR routing whitelist に追加
|
||||
- テスト関数を JoinIR パスに通すため
|
||||
|
||||
### 3. MethodCall in Condition Issue
|
||||
|
||||
**問題**: `loop(p < s.length())` は MethodCall を含むため Pattern2 未対応
|
||||
|
||||
**解決策**: テストケースで `len` パラメータを追加し、`loop(p < len)` に変更
|
||||
|
||||
**実 JsonParser との差異**:
|
||||
- 実 JsonParser: `loop(p < s.length())` (MethodCall 使用)
|
||||
- テストケース: `loop(p < len)` (MethodCall 回避)
|
||||
|
||||
**Phase 174+ での対応**: MethodCall 対応を検討(Phase 171-D で言及済み)
|
||||
|
||||
---
|
||||
|
||||
## 📋 まだ残っている JsonParser ループ
|
||||
|
||||
### 1. _parse_string (複雑)
|
||||
|
||||
**構造**:
|
||||
- LoopBodyLocal `ch` 使用
|
||||
- 複数の条件分岐(`ch == '"'`, `ch == "\\"`)
|
||||
- return 文での早期終了
|
||||
- continue での反復継続
|
||||
|
||||
**Phase 174+ 対応**: 複雑な条件分岐への P5 拡張
|
||||
|
||||
### 2. _parse_array (複雑)
|
||||
|
||||
**構造**:
|
||||
- LoopBodyLocal `next_ch` 使用
|
||||
- MethodCall 多数
|
||||
- 複数の return 文
|
||||
- 配列操作
|
||||
|
||||
**Phase 175+ 対応**: MethodCall 対応 + 複雑な状態管理
|
||||
|
||||
### 3. _unescape_string (Pattern1)
|
||||
|
||||
**構造**:
|
||||
- break/continue なし(自然終了のみ)
|
||||
- 複数の LoopBodyLocal 変数
|
||||
|
||||
**対応不要**: Pattern1、LoopBodyLocal の問題なし
|
||||
|
||||
---
|
||||
|
||||
## 重要な発見
|
||||
|
||||
### ✅ JsonParser._trim は既に P5 対応済み!
|
||||
|
||||
Phase 173-1 の観測で判明:
|
||||
|
||||
```
|
||||
[pattern2/trim] Safe Trim pattern detected, implementing lowering
|
||||
[pattern2/trim] Carrier: 'is_ch_match', original var: 'ch', whitespace chars: ["\r", "\n", "\t", " "]
|
||||
[joinir/pattern2] Generated JoinIR for Loop with Break Pattern (Phase 170-B)
|
||||
```
|
||||
|
||||
これにより、JsonParser の以下のループが P5 対応済みであることが確認された:
|
||||
|
||||
1. ✅ `_trim` method - Leading whitespace loop
|
||||
2. ✅ `_trim` method - Trailing whitespace loop
|
||||
|
||||
### 🎯 Phase 173 の真の価値
|
||||
|
||||
**既存実装の検証**: Trim パイプラインが JsonParser でも機能することを実証
|
||||
|
||||
1. ✅ `TrimLoopHelper` の汎用性確認
|
||||
2. ✅ Pattern2 Trim 特例の堅牢性確認
|
||||
3. ✅ LoopBodyLocal 昇格の実用性確認
|
||||
|
||||
**検証範囲**:
|
||||
- ✅ Static box method 内ループ (TrimTest.trim)
|
||||
- ✅ Helper method 内ループ (JsonParserTest._skip_whitespace)
|
||||
|
||||
→ 両方で機能すれば、P5 パイプラインの汎用性が完全証明される
|
||||
|
||||
---
|
||||
|
||||
## テスト結果
|
||||
|
||||
### ✅ Pattern Detection: SUCCESS
|
||||
|
||||
**実行コマンド**:
|
||||
```bash
|
||||
NYASH_JOINIR_CORE=1 NYASH_LEGACY_LOOPBUILDER=0 \
|
||||
./target/release/hakorune local_tests/test_jsonparser_skip_whitespace.hako
|
||||
```
|
||||
|
||||
**成功基準**:
|
||||
- ✅ `[pattern2/check] has_loop_body_local() = true` 出力
|
||||
- ✅ `[pattern2/promoter] LoopBodyLocal 'ch' promoted to carrier` 出力
|
||||
- ✅ `[pattern2/trim] Safe Trim pattern detected` 出力
|
||||
- ✅ `[joinir/pattern2] Generated JoinIR for Loop with Break Pattern` 出力
|
||||
|
||||
**全て達成**: Pattern detection 完全成功 ✅
|
||||
|
||||
### ⚠️ Execution: Deferred to Phase 174
|
||||
|
||||
**現状**: プログラム実行時の出力なし
|
||||
|
||||
**原因**: Main function execution に関する問題(P5 パイプラインとは無関係)
|
||||
|
||||
**Phase 173 の焦点**: Pattern detection の成功(✅ 達成済み)
|
||||
|
||||
**Phase 174+ 対応**: Execution 問題の調査・修正
|
||||
|
||||
---
|
||||
|
||||
## 追加実装
|
||||
|
||||
### 不要だった拡張
|
||||
|
||||
**charAt() メソッドの検出**:
|
||||
- 当初想定: `charAt()` 対応が必要かもしれない
|
||||
- 実際: `_skip_whitespace` は `substring()` のみ使用
|
||||
- 結論: 追加実装不要
|
||||
|
||||
**Phase 174+ で必要になる可能性**:
|
||||
- 他の JsonParser ループが `charAt()` 使用する場合
|
||||
- その時点で `LoopBodyCarrierPromoter` を拡張
|
||||
|
||||
---
|
||||
|
||||
## Phase 173 達成状況
|
||||
|
||||
### ✅ 完了項目
|
||||
|
||||
1. ✅ Task 173-1: JsonParser ループ再チェック
|
||||
- 6つのループを観測
|
||||
- `_skip_whitespace` を選定
|
||||
|
||||
2. ✅ Task 173-2: Trim 等価ループ選定
|
||||
- `_skip_whitespace` が Trim と完全同型であることを確認
|
||||
|
||||
3. ✅ Task 173-3: P5 パイプライン拡張設計
|
||||
- 設計ドキュメント作成 (`phase173-jsonparser-p5-design.md`)
|
||||
- 既存実装の活用方針確立
|
||||
|
||||
4. ✅ Task 173-4: JoinIR 実行確認
|
||||
- テストケース作成
|
||||
- Pattern detection 完全成功
|
||||
- JoinIR 生成成功
|
||||
|
||||
5. ✅ Task 173-5: ドキュメント更新
|
||||
- 3つのドキュメント作成・更新
|
||||
- CURRENT_TASK 記録
|
||||
|
||||
### 📊 Success Metrics
|
||||
|
||||
- ✅ JsonParser ループインベントリ再チェック完了
|
||||
- ✅ _skip_whitespace が Trim と同型であることを確認
|
||||
- ✅ 設計ドキュメント作成(P5 パイプライン拡張方針)
|
||||
- ✅ _skip_whitespace が JoinIR で成功(Pattern detection PASS)
|
||||
- ✅ charAt() 対応不要を確認
|
||||
- ✅ 3つのドキュメント作成・更新(recheck + design + impl)
|
||||
- ✅ CURRENT_TASK に Phase 173 成果記録
|
||||
- ⏭️ Execution 問題は Phase 174+ に defer
|
||||
|
||||
---
|
||||
|
||||
## 次のステップ (Phase 174+)
|
||||
|
||||
### Phase 174: 複雑なループへの P5 拡張
|
||||
|
||||
**対象**: `_parse_string`, `_parse_array`
|
||||
|
||||
**課題**:
|
||||
- 複数の条件分岐
|
||||
- return/continue 混在
|
||||
- MethodCall 多数
|
||||
|
||||
### Phase 175: 汎用的な命名への移行
|
||||
|
||||
**対象**: TrimLoopHelper → CharComparisonLoopHelper
|
||||
|
||||
**理由**: Trim 以外のパターンにも適用可能な命名
|
||||
|
||||
---
|
||||
|
||||
## まとめ
|
||||
|
||||
**Phase 173 の成果**:
|
||||
- ✅ Trim P5 パイプラインが JsonParser でも機能することを実証
|
||||
- ✅ Pattern detection 完全成功(JoinIR 生成まで確認)
|
||||
- ✅ TrimLoopHelper の汎用性が確認された
|
||||
- ✅ substring() メソッドの検出が JsonParser でも機能
|
||||
|
||||
**技術的価値**:
|
||||
- P5 パイプラインの汎用性が証明された
|
||||
- Static box method / Helper method の両方で動作確認
|
||||
- LoopBodyLocal carrier promotion の実用性が実証された
|
||||
|
||||
**Phase 174+ への道筋**:
|
||||
- 複雑なループへの拡張
|
||||
- MethodCall 対応
|
||||
- Execution 問題の解決
|
||||
@ -1,230 +0,0 @@
|
||||
# Phase 173: using + 静的 Box メソッド解決 - 実装サマリー
|
||||
|
||||
## 実装日時
|
||||
2025-12-04
|
||||
|
||||
## 実装状況
|
||||
**Phase 173 Task 1-2 完了**: 調査完了・仕様固定完了
|
||||
|
||||
## 完了タスク
|
||||
|
||||
### ✅ Task 1: 名前解決経路調査
|
||||
**成果物**: `phase173_task1_investigation.md`
|
||||
|
||||
**調査結果サマリー**:
|
||||
1. **現状の名前解決システム確認**
|
||||
- `.hako` 側: `UsingResolverBox` (pipeline_v2/using_resolver_box.hako)
|
||||
- Rust 側: `CalleeResolverBox` (src/mir/builder/calls/resolver.rs)
|
||||
- Box 種別分類: `classify_box_kind()` (src/mir/builder/calls/call_unified.rs)
|
||||
|
||||
2. **問題の特定**
|
||||
- using で import した静的 Box が「インスタンス」として扱われる
|
||||
- `JsonParserBox.parse()` → `InstanceBox.parse()` として解釈
|
||||
- 内部メソッド `_skip_whitespace` が解決できずエラー
|
||||
|
||||
3. **AST 構造の確認**
|
||||
- `using tools.hako_shared.json_parser as JsonParserBox`
|
||||
- 静的 Box: `static box JsonParserBox { method parse(...) }`
|
||||
- 呼び出し: `JsonParserBox.parse("{...}")`
|
||||
- 問題: TypeRef(型参照)と VarRef(変数参照)の区別がない
|
||||
|
||||
4. **追加の問題発見**
|
||||
- JsonParserBox の `_parse_number()` に無限ループバグ
|
||||
- `new Alias.BoxName()` 構文が未サポート
|
||||
- VM step budget exceeded エラー
|
||||
|
||||
**技術的洞察**:
|
||||
- 名前解決の段階: using 解決 → パーサー → MIR lowering
|
||||
- 各段階で静的 Box を「型」として扱う仕組みが不足
|
||||
- Rust VM 自体は変更不要(MIR が正しければ動作する前提)
|
||||
|
||||
### ✅ Task 2: 仕様固定(docs)
|
||||
**成果物**:
|
||||
- `docs/reference/language/using.md` 更新(179行追加)
|
||||
- `docs/reference/language/LANGUAGE_REFERENCE_2025.md` 更新(54行追加)
|
||||
|
||||
**追加された仕様**:
|
||||
|
||||
#### using.md の新セクション「📦 静的 Box の using(Phase 173+)」
|
||||
- **基本概念**: 静的 Box をライブラリとして using で import
|
||||
- **呼び出しパターン**:
|
||||
- ✅ Phase 173: `JsonParserBox.parse()` 静的メソッド直接呼び出し
|
||||
- 🚧 Phase 174+: `new Alias.BoxName()` 名前空間的アクセス
|
||||
- **静的 Box vs インスタンス Box の比較表**
|
||||
- **実装の技術的詳細**: using 解決 → AST → MIR lowering の流れ
|
||||
- **使用例**: JsonParserBox / ProgramJSONBox の実例
|
||||
- **制限事項**: パーサー制限、名前空間階層、型推論
|
||||
- **Phase 174+ 拡張予定**: HIR 層、型システム、明示的スコープ
|
||||
|
||||
#### LANGUAGE_REFERENCE_2025.md の新セクション
|
||||
- **Static Box のライブラリ利用(Phase 173+)**
|
||||
- 基本的な使用例(JsonParserBox)
|
||||
- 特徴・制限事項の明記
|
||||
- using.md へのクロスリファレンス
|
||||
|
||||
**仕様のポイント**:
|
||||
1. Phase 173 では `Alias.method()` 直接呼び出しのみ
|
||||
2. `new Alias.BoxName()` は Phase 174+ で対応
|
||||
3. 静的 Box = シングルトン・ライブラリ的用途
|
||||
4. インスタンス化不要で直接メソッド呼び出し
|
||||
|
||||
## 残タスク
|
||||
|
||||
### 🔄 Task 3: JsonParserBox バグ修正(高優先度)
|
||||
**問題**: `_parse_number()` の無限ループ
|
||||
**影響**: Phase 173 の動作確認が困難
|
||||
**対応**:
|
||||
- [ ] `_parse_number()` の実装確認
|
||||
- [ ] 無限ループの原因特定
|
||||
- [ ] 修正実装
|
||||
- [ ] 簡単な JSON (`{"x":1}`) で動作確認
|
||||
|
||||
### 🔄 Task 4: using resolver 修正
|
||||
**ファイル**: `lang/src/compiler/pipeline_v2/using_resolver_box.hako`
|
||||
**必要な実装**:
|
||||
- [ ] 静的 Box 型情報の登録
|
||||
- [ ] `load_modules_json()` で Box 種別も保持
|
||||
- [ ] `to_context_json()` に型情報を含める
|
||||
- [ ] パーサーに型情報を引き渡す
|
||||
|
||||
### 🔄 Task 5: パーサー修正
|
||||
**ファイル**: `lang/src/compiler/parser/parser_*.hako`
|
||||
**必要な実装**:
|
||||
- [ ] `Alias.method()` 検出ロジック
|
||||
- [ ] AST ノードに `is_static_box_call: true` フラグ追加
|
||||
- [ ] ノード種別追加(必要なら)
|
||||
- [ ] テスト確認
|
||||
|
||||
### 🔄 Task 6: MIR lowering 修正
|
||||
**ファイル**: `src/mir/builder/calls/resolver.rs`, `call_unified.rs`
|
||||
**必要な実装**:
|
||||
- [ ] `is_static_box_call` フラグの確認処理
|
||||
- [ ] 静的 Box 呼び出し判別条件追加
|
||||
- [ ] `Callee::Global("BoxName.method/arity")` への変換
|
||||
- [ ] または `Callee::Method { box_kind: StaticCompiler }` の設定
|
||||
- [ ] テスト確認
|
||||
|
||||
### 🔄 Task 7: 統合テスト
|
||||
**テストファイル**: `apps/tests/json_parser_min.hako`
|
||||
**内容**:
|
||||
```hako
|
||||
using tools.hako_shared.json_parser as JsonParserBox
|
||||
|
||||
static box Main {
|
||||
main() {
|
||||
local v = JsonParserBox.parse("{\"x\":1}")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
**実行確認**:
|
||||
- [ ] `./target/release/hakorune apps/tests/json_parser_min.hako` が RC 0
|
||||
- [ ] Unknown method エラーなし
|
||||
- [ ] hako_check スモーク(HC019/HC020)PASS
|
||||
|
||||
### 🔄 Task 8: ドキュメント更新& git commit
|
||||
- [ ] phase171-2 ドキュメント更新(JsonParserBox 正式化)
|
||||
- [ ] CURRENT_TASK.md 更新(Phase 173 完了記録)
|
||||
- [ ] git commit
|
||||
|
||||
## 推奨実装戦略
|
||||
|
||||
### 戦略A: 最小限の修正(推奨)
|
||||
1. **JsonParserBox バグ修正** → Task 3
|
||||
2. **using resolver に型登録** → Task 4
|
||||
3. **パーサーに最小限のフラグ** → Task 5
|
||||
4. **MIR lowering で判別処理** → Task 6
|
||||
5. **統合テスト** → Task 7
|
||||
|
||||
**理由**:
|
||||
- 段階的な実装で影響範囲を限定
|
||||
- 既存コードへの影響最小化
|
||||
- 各タスクで動作確認可能
|
||||
|
||||
### 戦略B: 包括的な対応(Phase 174+)
|
||||
1. HIR 層の導入
|
||||
2. 型システムの拡張
|
||||
3. 明示的スコープ演算子(`::`)
|
||||
|
||||
**理由**: より根本的な解決だが、Phase 173 のスコープを超える
|
||||
|
||||
## 技術的な注意点
|
||||
|
||||
### 箱化モジュール化の原則
|
||||
1. **Rust VM コア不変**: `.hako` / using 側のみで解決
|
||||
2. **段階的確認**: AST → MIR → VM の順で確認
|
||||
3. **既存コード保護**: instance call / plugin call の分岐を壊さない
|
||||
4. **仕様先行**: まずドキュメントで仕様を固定(Task 2 完了)
|
||||
|
||||
### デバッグ環境変数
|
||||
```bash
|
||||
# Callee 解決トレース
|
||||
NYASH_CALLEE_RESOLVE_TRACE=1
|
||||
|
||||
# MIR ダンプ
|
||||
./target/release/hakorune --dump-mir program.hako
|
||||
|
||||
# MIR JSON 出力
|
||||
./target/release/hakorune --emit-mir-json output.json program.hako
|
||||
|
||||
# VM 詳細ログ
|
||||
NYASH_CLI_VERBOSE=1
|
||||
```
|
||||
|
||||
## リスク評価
|
||||
|
||||
### 高リスク
|
||||
- **JsonParserBox 無限ループ**: 動作確認を阻害(Task 3 で対応)
|
||||
- **パーサー変更影響**: 既存コードの互換性(慎重な実装必要)
|
||||
- **using resolver 型登録**: 互換性問題の可能性
|
||||
|
||||
### 中リスク
|
||||
- MIR lowering の複雑化
|
||||
- VM 実行時の予期しないエラー
|
||||
- テストケース不足
|
||||
|
||||
### 低リスク
|
||||
- ドキュメント更新のみ(Task 2 完了)
|
||||
- 段階的実装による影響範囲の限定
|
||||
|
||||
## 成果物チェックリスト
|
||||
|
||||
- [x] Task 1: 名前解決経路調査完了
|
||||
- [x] AST 表現確認
|
||||
- [x] MIR lowering 確認
|
||||
- [x] エラー発生箇所特定
|
||||
- [x] 追加問題発見(JsonParserBox 無限ループ、パーサー構文制限)
|
||||
- [x] Task 2: 仕様固定完了
|
||||
- [x] using.md 更新(179行追加)
|
||||
- [x] LANGUAGE_REFERENCE_2025.md 更新(54行追加)
|
||||
- [ ] Task 3: JsonParserBox バグ修正
|
||||
- [ ] Task 4: using resolver 修正
|
||||
- [ ] Task 5: パーサー修正
|
||||
- [ ] Task 6: MIR lowering 修正
|
||||
- [ ] Task 7: 統合テスト
|
||||
- [ ] Task 8: ドキュメント更新& git commit
|
||||
|
||||
## 次のステップ
|
||||
|
||||
**immediate**: Task 3(JsonParserBox バグ修正)または Task 4(using resolver 修正)
|
||||
|
||||
**理由**:
|
||||
- Task 3: 動作確認を可能にするための前提条件
|
||||
- Task 4: 実装の核心部分、他タスクの基盤
|
||||
|
||||
**推奨**: Task 3 → Task 4 → Task 5 → Task 6 → Task 7 → Task 8 の順で実装
|
||||
|
||||
## 関連ドキュメント
|
||||
|
||||
- **指示書**: `phase173_using_static_box_resolution.md`
|
||||
- **調査結果**: `phase173_task1_investigation.md`
|
||||
- **仕様**: `docs/reference/language/using.md`, `LANGUAGE_REFERENCE_2025.md`
|
||||
- **Phase 171-2**: `phase171-2_hako_check_integration.md`
|
||||
- **Phase 172**: `phase172_implementation_results.md`
|
||||
|
||||
---
|
||||
|
||||
**作成日**: 2025-12-04
|
||||
**更新日**: 2025-12-04
|
||||
**Phase**: 173(using + 静的 Box メソッド解決)
|
||||
**進捗**: Task 1-2 完了(25%)/ Task 3-8 残り(75%)
|
||||
@ -1,332 +0,0 @@
|
||||
# Phase 173 Task 1-2 完了報告書
|
||||
|
||||
## 実施日時
|
||||
2025-12-04
|
||||
|
||||
## 実施内容
|
||||
Phase 173「using + 静的 Box メソッド解決の整備」のうち、Task 1(調査)と Task 2(仕様固定)を完了しました。
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 1: 名前解決経路調査 - 完了
|
||||
|
||||
### 調査対象
|
||||
|
||||
#### .hako 側(using resolver)
|
||||
- **ファイル**: `lang/src/compiler/pipeline_v2/using_resolver_box.hako`
|
||||
- **機能**: `using tools.hako_shared.json_parser as JsonParserBox` を解決
|
||||
- **実装**: JSON 文字列ベースの単純検索で alias → file path を解決
|
||||
|
||||
#### Rust 側(MIR lowering)
|
||||
- **ファイル**: `src/mir/builder/calls/resolver.rs`(CalleeResolverBox)
|
||||
- **ファイル**: `src/mir/builder/calls/call_unified.rs`(classify_box_kind)
|
||||
- **機能**: CallTarget → Callee の型安全な解決
|
||||
|
||||
### 発見した問題
|
||||
|
||||
#### 1. 静的 Box が InstanceBox として扱われる
|
||||
**症状**:
|
||||
```
|
||||
[ERROR] VM error: Unknown method '_skip_whitespace' on InstanceBox
|
||||
```
|
||||
|
||||
**根本原因**:
|
||||
- `using ... as JsonParserBox` で import した静的 Box
|
||||
- `JsonParserBox.parse()` を「インスタンスメソッド呼び出し」として解釈
|
||||
- 実際には「静的 Box の静的メソッド呼び出し」であるべき
|
||||
|
||||
**名前解決の段階的問題**:
|
||||
1. **using 解決**: エイリアスを「型」として登録する仕組みがない
|
||||
2. **パーサー**: `Alias.method()` を TypeRef として認識しない(VarRef として処理)
|
||||
3. **MIR lowering**: `CallTarget::Method` で receiver が ValueId(静的 Box 判別不可)
|
||||
|
||||
#### 2. JsonParserBox の無限ループバグ
|
||||
**症状**:
|
||||
```
|
||||
[ERROR] VM error: vm step budget exceeded (max_steps=1000000, steps=1000001)
|
||||
at bb=bb338 fn=JsonParserBox._parse_number/2
|
||||
```
|
||||
|
||||
**影響**: Phase 173 の動作確認が困難
|
||||
|
||||
#### 3. パーサーの構文制限
|
||||
**症状**:
|
||||
```
|
||||
❌ Parse error: Unexpected token DOT, expected LPAREN
|
||||
local parser = new JsonLib.JsonParserBox()
|
||||
^^^
|
||||
```
|
||||
|
||||
**影響**: `new Alias.BoxName()` 構文が未サポート
|
||||
|
||||
### 調査成果物
|
||||
- **ドキュメント**: `phase173_task1_investigation.md`(220行)
|
||||
- **内容**:
|
||||
- AST 表現の詳細分析
|
||||
- MIR lowering の処理フロー確認
|
||||
- エラー発生箇所の特定
|
||||
- 問題の構造化(3段階の名前解決)
|
||||
- 推奨実装戦略(戦略A: 最小限、戦略B: 包括的)
|
||||
|
||||
---
|
||||
|
||||
## ✅ Task 2: 仕様固定(docs)- 完了
|
||||
|
||||
### 更新ドキュメント
|
||||
|
||||
#### 1. using.md への追加
|
||||
**ファイル**: `docs/reference/language/using.md`
|
||||
**追加行数**: 179行
|
||||
**追加セクション**: 「📦 静的 Box の using(Phase 173+)」
|
||||
|
||||
**内容**:
|
||||
1. **基本概念**
|
||||
- 静的 Box をライブラリとして using で import
|
||||
- 静的メソッドの直接呼び出し(インスタンス化不要)
|
||||
|
||||
2. **許容される呼び出しパターン**
|
||||
- ✅ Phase 173: `JsonParserBox.parse()` 直接呼び出し
|
||||
- 🚧 Phase 174+: `new Alias.BoxName()` 名前空間的アクセス
|
||||
|
||||
3. **静的 Box vs インスタンス Box 比較表**
|
||||
- 定義、インスタンス化、メソッド呼び出し、状態保持、用途を比較
|
||||
|
||||
4. **実装の技術的詳細**
|
||||
- 名前解決の流れ(using 解決 → AST → MIR lowering)
|
||||
- Phase 171-2 で発見された問題の説明
|
||||
|
||||
5. **使用例**
|
||||
- JsonParserBox の利用例
|
||||
- ProgramJSONBox の利用例(Phase 172)
|
||||
|
||||
6. **制限事項(Phase 173 時点)**
|
||||
- パーサー制限
|
||||
- 名前空間階層制限
|
||||
- 型推論制限
|
||||
|
||||
7. **Phase 174+ での拡張予定**
|
||||
- 名前空間的 Box アクセス
|
||||
- 型システム統合
|
||||
- 明示的スコープ(`::`)
|
||||
|
||||
#### 2. LANGUAGE_REFERENCE_2025.md への追加
|
||||
**ファイル**: `docs/reference/language/LANGUAGE_REFERENCE_2025.md`
|
||||
**追加行数**: 54行
|
||||
**追加セクション**: 「Static Box のライブラリ利用(Phase 173+)」
|
||||
|
||||
**内容**:
|
||||
1. **基本的な使用例**(JsonParserBox)
|
||||
2. **特徴**(インスタンス化不要、シングルトン動作、名前空間的利用)
|
||||
3. **静的 Box vs インスタンス Box**(簡潔版)
|
||||
4. **制限事項**(Phase 173)
|
||||
5. **using.md へのクロスリファレンス**
|
||||
|
||||
### 仕様の要点
|
||||
|
||||
#### Phase 173 で実装する範囲
|
||||
- ✅ `Alias.method()` 静的メソッド直接呼び出し
|
||||
- ✅ using statement での静的 Box import
|
||||
- ✅ 型としての静的 Box 登録
|
||||
|
||||
#### Phase 174+ に繰り越す範囲
|
||||
- 🚧 `new Alias.BoxName()` 名前空間的アクセス
|
||||
- 🚧 HIR 層の導入
|
||||
- 🚧 型システムの拡張
|
||||
- 🚧 明示的スコープ演算子(`::`)
|
||||
|
||||
### 仕様固定の効果
|
||||
1. **開発者への明確なガイド**: 何ができて何ができないかが明確
|
||||
2. **実装範囲の明確化**: Phase 173 vs Phase 174+ の境界を定義
|
||||
3. **将来拡張の道筋**: Phase 174+ での拡張方針を提示
|
||||
4. **箱化モジュール化の原則**: Rust VM 不変、段階的実装の方針明記
|
||||
|
||||
---
|
||||
|
||||
## 成果物サマリー
|
||||
|
||||
### 作成ドキュメント(3件)
|
||||
1. **phase173_task1_investigation.md**(220行)
|
||||
- 調査結果の詳細
|
||||
- 問題の構造化
|
||||
- 推奨実装戦略
|
||||
|
||||
2. **phase173_implementation_summary.md**(comprehensive)
|
||||
- 実装サマリー
|
||||
- タスク一覧
|
||||
- リスク評価
|
||||
- チェックリスト
|
||||
|
||||
3. **phase173_task1-2_completion_report.md**(本ドキュメント)
|
||||
- Task 1-2 の完了報告
|
||||
- 成果物の詳細
|
||||
- 次のステップ
|
||||
|
||||
### 更新ドキュメント(3件)
|
||||
1. **docs/reference/language/using.md**(+179行)
|
||||
- 静的 Box の using セクション追加
|
||||
|
||||
2. **docs/reference/language/LANGUAGE_REFERENCE_2025.md**(+54行)
|
||||
- Static Box ライブラリ利用セクション追加
|
||||
|
||||
3. **CURRENT_TASK.md**
|
||||
- Phase 173 進捗セクション追加
|
||||
- Task 1-2 完了記録
|
||||
|
||||
### テストファイル(1件)
|
||||
1. **apps/tests/json_parser_min.hako**
|
||||
- Phase 173 の統合テスト用
|
||||
|
||||
### 合計
|
||||
- **新規ドキュメント**: 3件
|
||||
- **更新ドキュメント**: 3件
|
||||
- **新規テストファイル**: 1件
|
||||
- **追加行数**: 233行(using.md 179行 + LANGUAGE_REFERENCE_2025.md 54行)
|
||||
- **調査ドキュメント**: 220行
|
||||
|
||||
---
|
||||
|
||||
## 技術的洞察
|
||||
|
||||
### 箱化モジュール化の実践
|
||||
1. **Rust VM コア不変**: `.hako` / using 側のみで解決
|
||||
2. **段階的確認**: AST → MIR → VM の順で確認
|
||||
3. **既存コード保護**: instance call / plugin call の分岐を壊さない
|
||||
4. **仕様先行**: まずドキュメントで仕様を固定(Task 2 完了)
|
||||
|
||||
### 発見した設計上の問題点
|
||||
1. **TypeRef vs VarRef の未分離**
|
||||
- パーサーレベルで型参照と変数参照の区別がない
|
||||
- `Alias.method()` が変数参照として扱われる
|
||||
|
||||
2. **using resolver の型情報不足**
|
||||
- エイリアスとファイルパスのマッピングのみ
|
||||
- Box 種別(static / instance)の情報がない
|
||||
|
||||
3. **MIR lowering の判別条件不足**
|
||||
- 静的 Box 呼び出しの判別条件がない
|
||||
- receiver の有無のみで判断(不十分)
|
||||
|
||||
### 推奨実装戦略
|
||||
**戦略A: 最小限の修正**(推奨)
|
||||
- JsonParserBox バグ修正(Task 3)
|
||||
- using resolver に型登録(Task 4)
|
||||
- パーサーに最小限のフラグ(Task 5)
|
||||
- MIR lowering で判別処理(Task 6)
|
||||
|
||||
**理由**: 段階的な実装で影響範囲を限定、既存コードへの影響最小化
|
||||
|
||||
---
|
||||
|
||||
## 次のステップ
|
||||
|
||||
### immediate: Task 3 または Task 4
|
||||
|
||||
#### Option A: Task 3(JsonParserBox バグ修正)先行
|
||||
**理由**: 動作確認を可能にするための前提条件
|
||||
**作業**:
|
||||
1. `tools/hako_shared/json_parser.hako` の `_parse_number()` 確認
|
||||
2. 無限ループの原因特定
|
||||
3. 修正実装
|
||||
4. 簡単な JSON (`{"x":1}`) で動作確認
|
||||
|
||||
#### Option B: Task 4(using resolver 修正)先行
|
||||
**理由**: 実装の核心部分、他タスクの基盤
|
||||
**作業**:
|
||||
1. `using_resolver_box.hako` に静的 Box 型情報登録
|
||||
2. `load_modules_json()` で Box 種別も保持
|
||||
3. `to_context_json()` に型情報を含める
|
||||
4. パーサーに型情報を引き渡す
|
||||
|
||||
### 推奨: Option A → Option B の順で実装
|
||||
- Task 3 で JsonParserBox を修正して動作確認可能に
|
||||
- Task 4-6 で using system の根本修正
|
||||
- Task 7 で統合テスト
|
||||
- Task 8 でドキュメント更新& git commit
|
||||
|
||||
---
|
||||
|
||||
## リスク評価
|
||||
|
||||
### 高リスク(Task 3 で対応)
|
||||
- JsonParserBox 無限ループバグ: 動作確認を阻害
|
||||
|
||||
### 中リスク(慎重な実装で対応)
|
||||
- パーサー変更による既存コードへの影響
|
||||
- using resolver の型登録による互換性問題
|
||||
- MIR lowering の複雑化
|
||||
|
||||
### 低リスク(完了済み)
|
||||
- ドキュメント更新のみ(Task 2 完了)
|
||||
- 段階的実装による影響範囲の限定
|
||||
|
||||
---
|
||||
|
||||
## チェックリスト
|
||||
|
||||
### Task 1-2(完了)
|
||||
- [x] Task 1: 名前解決経路調査
|
||||
- [x] AST 表現確認
|
||||
- [x] MIR lowering 確認
|
||||
- [x] エラー発生箇所特定
|
||||
- [x] 追加問題発見
|
||||
- [x] 調査ドキュメント作成(220行)
|
||||
- [x] Task 2: 仕様固定
|
||||
- [x] using.md 更新(179行追加)
|
||||
- [x] LANGUAGE_REFERENCE_2025.md 更新(54行追加)
|
||||
- [x] 実装サマリードキュメント作成
|
||||
- [x] CURRENT_TASK.md 更新
|
||||
|
||||
### Task 3-8(残り)
|
||||
- [ ] Task 3: JsonParserBox バグ修正
|
||||
- [ ] Task 4: using resolver 修正
|
||||
- [ ] Task 5: パーサー修正
|
||||
- [ ] Task 6: MIR lowering 修正
|
||||
- [ ] Task 7: 統合テスト
|
||||
- [ ] Task 8: ドキュメント更新& git commit
|
||||
|
||||
---
|
||||
|
||||
## 関連 Phase
|
||||
|
||||
### Phase 171-2(一時ブロック中)
|
||||
- JsonParserBox 統合(using 制限で Runtime Error)
|
||||
- hako_check の 37.6% コード削減達成
|
||||
- Phase 173 完了後に再開予定
|
||||
|
||||
### Phase 172(完了)
|
||||
- ProgramJSONBox 実装完了
|
||||
- parse_program() メソッド追加
|
||||
- JSON 処理 SSOT 基盤確立
|
||||
|
||||
### Phase 174+(将来拡張)
|
||||
- 名前空間的 Box アクセス(`new Alias.BoxName()`)
|
||||
- HIR 層の導入
|
||||
- 型システムの拡張
|
||||
- 明示的スコープ演算子(`::`)
|
||||
|
||||
---
|
||||
|
||||
## まとめ
|
||||
|
||||
### 達成内容
|
||||
1. ✅ 名前解決経路の完全調査(220行ドキュメント)
|
||||
2. ✅ 問題の構造化と根本原因の特定
|
||||
3. ✅ 仕様の明確化(233行追加)
|
||||
4. ✅ 実装戦略の策定
|
||||
|
||||
### 次のマイルストーン
|
||||
- Task 3: JsonParserBox バグ修正(immediate)
|
||||
- Task 4-6: using system の根本修正(core)
|
||||
- Task 7-8: 統合テスト&完了処理(verification)
|
||||
|
||||
### Phase 173 の意義
|
||||
- **Phase 171-2 のブロック解除**: using system 修正で JsonParserBox が正式なライブラリとして使用可能に
|
||||
- **selfhost 基盤強化**: 静的 Box ライブラリパターンの確立
|
||||
- **将来拡張への道筋**: Phase 174+ での名前空間・型システム統合への基盤
|
||||
|
||||
---
|
||||
|
||||
**作成日**: 2025-12-04
|
||||
**Phase**: 173(using + 静的 Box メソッド解決)
|
||||
**進捗**: Task 1-2 完了(25%)
|
||||
**次タスク**: Task 3(JsonParserBox バグ修正)または Task 4(using resolver 修正)
|
||||
@ -1,264 +0,0 @@
|
||||
# Phase 173 Task 1: 名前解決経路調査結果
|
||||
|
||||
## 調査日時
|
||||
2025-12-04
|
||||
|
||||
## 1. 現状の名前解決経路
|
||||
|
||||
### 1.1 AST表現の確認
|
||||
|
||||
#### using文の処理
|
||||
- **ファイル**: `lang/src/compiler/pipeline_v2/using_resolver_box.hako`
|
||||
- **機能**: `using tools.hako_shared.json_parser as JsonParserBox` を解決
|
||||
- **実装**:
|
||||
- `resolve_path_alias()`: エイリアスからファイルパスを解決
|
||||
- `resolve_namespace_alias()`: 名前空間エイリアスを tail マッチングで解決
|
||||
- JSON文字列ベースの単純検索(正規表現不使用)
|
||||
|
||||
#### 静的Boxメソッド呼び出しの現状
|
||||
- **呼び出し形式**: `JsonParserBox.parse(json_text)`
|
||||
- **問題点**:
|
||||
- `JsonParserBox` は `using ... as JsonParserBox` で import されたエイリアス
|
||||
- パーサーは `JsonParserBox.parse()` を「インスタンスメソッド呼び出し」として解釈
|
||||
- 実際には「静的Boxの静的メソッド呼び出し」であるべき
|
||||
|
||||
### 1.2 MIR Loweringの確認
|
||||
|
||||
#### Callee解決システム
|
||||
- **ファイル**: `src/mir/builder/calls/resolver.rs`
|
||||
- **実装**: `CalleeResolverBox` によるCallee解決
|
||||
- **処理フロー**:
|
||||
1. `CallTarget::Method { box_type, method, receiver }` として受け取る
|
||||
2. `infer_box_type()` で Box 名を推論
|
||||
3. `classify_box_kind()` で Box 種別を分類(StaticCompiler/RuntimeData/UserDefined)
|
||||
4. `Callee::Method { box_name, method, receiver, box_kind, certainty }` に変換
|
||||
|
||||
#### Box種別分類
|
||||
- **ファイル**: `src/mir/builder/calls/call_unified.rs`
|
||||
- **関数**: `classify_box_kind()`
|
||||
- **分類**:
|
||||
- `StaticCompiler`: StageBArgsBox, ParserBox, UsingResolverBox 等
|
||||
- `RuntimeData`: MapBox, ArrayBox, StringBox 等
|
||||
- `UserDefined`: その他すべて
|
||||
|
||||
**問題点**:
|
||||
- `JsonParserBox` は `StaticCompiler` でも `RuntimeData` でもなく `UserDefined` に分類される
|
||||
- しかし実際には **static box** として扱うべき
|
||||
|
||||
### 1.3 エラー発生箇所の特定
|
||||
|
||||
#### Phase 171-2 で発見されたエラー
|
||||
```
|
||||
[ERROR] ❌ [rust-vm] VM error: Invalid instruction: Unknown method '_skip_whitespace' on InstanceBox
|
||||
```
|
||||
|
||||
**根本原因**:
|
||||
1. `using tools.hako_shared.json_parser as JsonParserBox` で import
|
||||
2. `JsonParserBox.parse(json_text)` として呼び出し
|
||||
3. MIR lowering で「インスタンスメソッド呼び出し」として解釈
|
||||
4. VM 実行時に `InstanceBox` として扱われる
|
||||
5. 内部メソッド `_skip_whitespace` が見つからない
|
||||
|
||||
**証拠**:
|
||||
- `tools/hako_shared/tests/json_parser_simple_test.hako` は using を使わず、JsonParserBox 全体をインライン化
|
||||
- コメント: "Test JsonParserBox without using statement"
|
||||
- → using statement 経由だと動作しないことが明示的に回避されている
|
||||
|
||||
## 2. AST構造の詳細
|
||||
|
||||
### 2.1 静的Boxの定義
|
||||
```hako
|
||||
static box JsonParserBox {
|
||||
method parse(json_str) {
|
||||
// ...
|
||||
}
|
||||
|
||||
method _skip_whitespace(s, pos) {
|
||||
// 内部メソッド
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `static box` として定義
|
||||
- すべてのメソッドは静的メソッド(`me.` で自己参照)
|
||||
- インスタンス化不要(シングルトン的動作)
|
||||
|
||||
### 2.2 using statement
|
||||
```hako
|
||||
using tools.hako_shared.json_parser as JsonParserBox
|
||||
```
|
||||
|
||||
- `tools.hako_shared.json_parser`: ファイルパス(.hako 省略)
|
||||
- `JsonParserBox`: エイリアス名
|
||||
- **期待動作**: エイリアスを **static box の型** として登録
|
||||
|
||||
### 2.3 呼び出し形式
|
||||
```hako
|
||||
local v = JsonParserBox.parse("{\"x\":1}")
|
||||
```
|
||||
|
||||
- **期待**: `JsonParserBox` は静的Box名として解決
|
||||
- **現状**: 変数名として解決 → インスタンスメソッド呼び出しとして処理
|
||||
- **問題**: TypeRef(型参照)と VarRef(変数参照)の区別がない
|
||||
|
||||
## 3. 問題の構造化
|
||||
|
||||
### 3.1 名前解決の段階
|
||||
1. **using 解決** (lang/src/compiler/pipeline_v2/using_resolver_box.hako)
|
||||
- ✅ ファイルパスの解決は動作
|
||||
- ❌ エイリアスの「型としての登録」がない
|
||||
|
||||
2. **パーサー** (lang/src/compiler/parser/*)
|
||||
- ❌ `Alias.method()` を TypeRef として認識しない
|
||||
- ❌ VarRef として処理される
|
||||
|
||||
3. **MIR lowering** (src/mir/builder/calls/resolver.rs)
|
||||
- ❌ `CallTarget::Method` で receiver が ValueId
|
||||
- ❌ 静的Box呼び出しの判別条件がない
|
||||
|
||||
### 3.2 必要な修正箇所
|
||||
|
||||
#### A. using resolver (.hako 側)
|
||||
**ファイル**: `lang/src/compiler/pipeline_v2/using_resolver_box.hako`
|
||||
|
||||
**必要な機能**:
|
||||
- 静的Box名を「型」として環境に登録
|
||||
- エイリアス → 静的Box の対応を保持
|
||||
- パーサーに引き渡す context に型情報を含める
|
||||
|
||||
#### B. パーサー (.hako 側)
|
||||
**ファイル**: `lang/src/compiler/parser/parser_*.hako`
|
||||
|
||||
**必要な機能**:
|
||||
- `Alias.method()` の AST 表現を追加
|
||||
- フラグ: `is_static_box_call: true` を付与
|
||||
- ノード種別: `StaticBoxMethodCall` 的な kind を追加
|
||||
|
||||
#### C. MIR lowering (Rust 側)
|
||||
**ファイル**: `src/mir/builder/calls/resolver.rs`, `call_unified.rs`
|
||||
|
||||
**必要な機能**:
|
||||
- AST の `is_static_box_call` フラグを確認
|
||||
- 静的Box呼び出しの場合、receiver を無視
|
||||
- `Callee::Global("BoxName.method/arity")` として解決
|
||||
- または `Callee::Method { box_name: "JsonParserBox", ... }` で box_kind を StaticCompiler に設定
|
||||
|
||||
## 4. 追加の問題発見
|
||||
|
||||
### 4.1 JsonParserBox の無限ループバグ
|
||||
**症状**:
|
||||
```
|
||||
[ERROR] ❌ [rust-vm] VM error: vm step budget exceeded (max_steps=1000000, steps=1000001)
|
||||
at bb=bb338 fn=JsonParserBox._parse_number/2
|
||||
```
|
||||
|
||||
**原因**: `_parse_number()` メソッドに無限ループがある可能性
|
||||
|
||||
**影響**:
|
||||
- 現時点では簡単なJSON (`{\"x\":1}`) でも動作しない
|
||||
- Phase 171-2 の実装確認が困難
|
||||
|
||||
**対応**:
|
||||
- Phase 173 とは別に JsonParserBox のバグ修正が必要
|
||||
- または Phase 173 では別の静的Box(より単純なもの)でテスト
|
||||
|
||||
### 4.2 パーサーの構文制限
|
||||
**症状**:
|
||||
```
|
||||
❌ Parse error: Unexpected token DOT, expected LPAREN at line 5
|
||||
local parser = new JsonLib.JsonParserBox()
|
||||
^^^
|
||||
```
|
||||
|
||||
**原因**: `new Alias.BoxName()` 構文がサポートされていない
|
||||
|
||||
**影響**:
|
||||
- 静的Box内の Box 定義をインスタンス化できない
|
||||
- 名前空間的な使用ができない
|
||||
|
||||
**対応**:
|
||||
- Phase 173 では `Alias.method()` の直接呼び出しのみに集中
|
||||
- `new Alias.BoxName()` は Phase 174+ で対応
|
||||
|
||||
## 5. 推奨される実装戦略
|
||||
|
||||
### 戦略A: 最小限の修正(推奨)
|
||||
1. **JsonParserBox のバグ修正を先行**
|
||||
- `_parse_number()` の無限ループを修正
|
||||
- 簡単な JSON で動作確認
|
||||
|
||||
2. **using resolver に型登録を追加**
|
||||
- `load_modules_json()` で静的Box情報も保持
|
||||
- `to_context_json()` に型情報を含める
|
||||
|
||||
3. **パーサーに最小限のフラグ追加**
|
||||
- `Alias.method()` を検出時に `is_static_box_call: true`
|
||||
- AST ノードに追加
|
||||
|
||||
4. **MIR lowering で判別処理追加**
|
||||
- `is_static_box_call` フラグを確認
|
||||
- `Callee::Global("BoxName.method/arity")` に変換
|
||||
|
||||
### 戦略B: 包括的な対応(Phase 174+)
|
||||
1. HIR層の導入
|
||||
2. 型システムの拡張
|
||||
3. 明示的スコープ演算子(`::`)のサポート
|
||||
|
||||
## 6. 次のステップ
|
||||
|
||||
### Task 2: 仕様固定
|
||||
- [ ] `using.md` に静的Box using のパターンを追記
|
||||
- [ ] `LANGUAGE_REFERENCE_2025.md` に static box ライブラリ利用方針を追加
|
||||
- [ ] 許容する呼び方を明確化(`Alias.method()` のみ、`new Alias.Box()` は Phase 174+)
|
||||
|
||||
### Task 3: JsonParserBox バグ修正
|
||||
- [ ] `_parse_number()` の無限ループ原因を特定
|
||||
- [ ] 修正実装
|
||||
- [ ] 簡単な JSON (`{"x":1}`) で動作確認
|
||||
|
||||
### Task 4: using resolver 修正
|
||||
- [ ] 静的Box型情報の登録実装
|
||||
- [ ] context JSON への型情報含め
|
||||
- [ ] テスト確認
|
||||
|
||||
### Task 5: パーサー修正
|
||||
- [ ] `Alias.method()` 検出実装
|
||||
- [ ] AST フラグ追加
|
||||
- [ ] テスト確認
|
||||
|
||||
### Task 6: MIR lowering 修正
|
||||
- [ ] 静的Box呼び出し判別条件追加
|
||||
- [ ] Callee 解決実装
|
||||
- [ ] テスト確認
|
||||
|
||||
## 7. リスク評価
|
||||
|
||||
### 高リスク
|
||||
- JsonParserBox の無限ループバグ(Task 3 で対応必要)
|
||||
- パーサー変更による既存コードへの影響
|
||||
- using resolver の型登録による互換性問題
|
||||
|
||||
### 中リスク
|
||||
- MIR lowering の複雑化
|
||||
- VM 実行時の予期しないエラー
|
||||
- テストケース不足
|
||||
|
||||
### 低リスク
|
||||
- ドキュメント更新のみ
|
||||
- 段階的な実装による影響範囲の限定
|
||||
|
||||
## 8. 成果物チェックリスト
|
||||
|
||||
- [x] AST 表現の確認(using statement, 静的Box呼び出し)
|
||||
- [x] MIR lowering の確認(CalleeResolverBox, classify_box_kind)
|
||||
- [x] エラー発生箇所の特定(Phase 171-2 エラー分析)
|
||||
- [x] 追加問題の発見(JsonParserBox 無限ループ、パーサー構文制限)
|
||||
- [x] 推奨戦略の提案(戦略A: 最小限、戦略B: 包括的)
|
||||
- [ ] JsonParserBox バグ修正(次タスク)
|
||||
- [ ] 仕様ドキュメント作成(Task 2)
|
||||
|
||||
---
|
||||
|
||||
**作成日**: 2025-12-04
|
||||
**調査時間**: 約2時間
|
||||
**次のタスク**: Task 3(JsonParserBox バグ修正)または Task 2(仕様固定)
|
||||
@ -1,387 +0,0 @@
|
||||
# Phase 173: using + 静的 Box メソッド解決の整備
|
||||
|
||||
## 0. ゴール
|
||||
|
||||
**using した静的 Box をライブラリとして正しく呼び出せるようにする。**
|
||||
|
||||
目的:
|
||||
- `.hako` で `using` した静的 Box(JsonParserBox 等)を VM から正常に呼び出せるようにする
|
||||
- Rust VM コアには触らず、`.hako` の using/resolver + MIR lowering 側で解決
|
||||
- Phase 171-2 で発見された「Unknown method on InstanceBox」問題を根本解決
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
### Phase 171-2 の発見
|
||||
|
||||
```
|
||||
[ERROR] ❌ [rust-vm] VM error: Invalid instruction: Unknown method '_skip_whitespace' on InstanceBox
|
||||
```
|
||||
|
||||
**根本原因**:
|
||||
- `using tools.hako_shared.json_parser as JsonParserBox` で import した静的 Box が
|
||||
- VM 側で InstanceBox として扱われ、内部メソッドが解決できない
|
||||
|
||||
**影響**:
|
||||
- hako_check での JsonParserBox 統合が動作しない
|
||||
- selfhost での JSON 処理統一化がブロック
|
||||
|
||||
### この Phase でやること
|
||||
|
||||
1. 名前解決経路の調査
|
||||
2. 仕様の明確化(ドキュメント)
|
||||
3. using/resolver の修正
|
||||
4. MIR lowering の修正
|
||||
5. JsonParserBox 動作確認
|
||||
|
||||
---
|
||||
|
||||
## 2. Scope / Non-scope
|
||||
|
||||
### ✅ やること
|
||||
|
||||
1. **現状の名前解決経路を調査**
|
||||
- AST 上での静的 Box の表現確認
|
||||
- MIR lowering での判別処理確認
|
||||
- VM エラー発生箇所の特定
|
||||
|
||||
2. **仕様の固定(docs)**
|
||||
- using.md に静的 Box の使い方を追記
|
||||
- LANGUAGE_REFERENCE_2025.md に方針追加
|
||||
|
||||
3. **.hako 側の using/resolver 修正**
|
||||
- 静的 Box 名を型として環境に登録
|
||||
- AST にフラグ追加
|
||||
|
||||
4. **MIR lowering 修正**
|
||||
- 静的 Box 呼び出しの判別条件追加
|
||||
- BoxCall/MethodCall への正しい解決
|
||||
|
||||
5. **テストと回帰確認**
|
||||
- JsonParserBox 最小テスト
|
||||
- hako_check スモークテスト
|
||||
|
||||
### ❌ やらないこと
|
||||
|
||||
- Rust VM コアの変更(実行側は既に静的 Box を扱える前提)
|
||||
- JsonParserBox の機能追加
|
||||
- 新しい解析ルール追加
|
||||
|
||||
---
|
||||
|
||||
## 3. Task 1: 現状の名前解決経路を調べる
|
||||
|
||||
### 対象ファイル
|
||||
|
||||
**.hako 側**:
|
||||
- `lang/src/compiler/parser/parser_box.hako`
|
||||
- `lang/src/compiler/entry/using_resolver*.hako`(Stage-1/UsingResolver)
|
||||
|
||||
**Rust 側**:
|
||||
- `src/mir/builder/calls/resolver.rs`
|
||||
- `src/mir/builder/calls/call_unified.rs`
|
||||
|
||||
### やること
|
||||
|
||||
1. **AST 表現の確認**
|
||||
- `using ... as ...` で静的 Box を import したときの AST 構造
|
||||
- `JsonLib.parse(...)` / `JsonLib.JsonParserBox(...)` がどの kind で表現されるか
|
||||
- TypeRef / VarRef の区別
|
||||
|
||||
2. **MIR lowering の確認**
|
||||
- ノードが「インスタンスメソッド」か「静的 Box メソッド」か判別できているか
|
||||
- BoxCall vs MethodCall の分岐ロジック
|
||||
|
||||
3. **エラー発生箇所の特定**
|
||||
- 具体的にどの Box 名 / メソッド名で Unknown になるか
|
||||
- `_skip_whitespace` 等の内部メソッドがなぜ解決できないか
|
||||
|
||||
### 成果物
|
||||
|
||||
- 名前解決経路のメモ
|
||||
- AST 構造の確認結果
|
||||
- エラー発生箇所の特定
|
||||
|
||||
---
|
||||
|
||||
## 4. Task 2: 仕様を固定する(docs)
|
||||
|
||||
### docs/reference/language/using.md への追記
|
||||
|
||||
```markdown
|
||||
## 静的 Box の using
|
||||
|
||||
静的 Box をライブラリとして使用する場合:
|
||||
|
||||
\`\`\`hako
|
||||
using tools.hako_shared.json_parser as JsonLib
|
||||
|
||||
static box Main {
|
||||
main() {
|
||||
// 方法1: 静的 Box のインスタンス化
|
||||
local parser = new JsonLib.JsonParserBox()
|
||||
local v = parser.parse("{\"x\":1}")
|
||||
|
||||
// 方法2: 静的メソッド呼び出し(Alias が static box の場合)
|
||||
local result = JsonLib.parse("{\"y\":2}")
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
### 許容する呼び方
|
||||
|
||||
1. `new Alias.BoxName()` - 静的 Box 定義を namespace 的に使う
|
||||
2. `Alias.func()` - Alias が static box そのものの場合
|
||||
3. `instance.method()` - インスタンスメソッド呼び出し
|
||||
```
|
||||
|
||||
### LANGUAGE_REFERENCE_2025.md への追記
|
||||
|
||||
```markdown
|
||||
## static box のライブラリ利用
|
||||
|
||||
- `static box` は 1 ファイル 1 個のシングルトン / ライブラリ的な箱として扱う
|
||||
- instance Box と同じく `using` から名前解決されるべき
|
||||
- `using ... as Alias` でインポートした静的 Box は:
|
||||
- `Alias.method()` で静的メソッド呼び出し
|
||||
- `new Alias.BoxName()` で内部 Box のインスタンス化
|
||||
```
|
||||
|
||||
### 成果物
|
||||
|
||||
- using.md 更新
|
||||
- LANGUAGE_REFERENCE_2025.md 更新
|
||||
|
||||
---
|
||||
|
||||
## 5. Task 3: .hako 側の using/resolver を修正
|
||||
|
||||
### 対象ファイル
|
||||
|
||||
- `lang/src/compiler/entry/using_resolver*.hako`
|
||||
- Stage-3 専用の resolver(あれば)
|
||||
|
||||
### 方針
|
||||
|
||||
1. **using 解決時の型登録**
|
||||
- 静的 Box 名を型として環境に登録
|
||||
- `Alias → static box / namespace`
|
||||
- `Alias.BoxName → type or Box`
|
||||
|
||||
2. **AST へのフラグ追加**
|
||||
- `JsonLib.JsonParserBox` を「型参照」+「Box 名」として扱えるようにフラグ付け
|
||||
- これにより MIR lowering で判断可能に
|
||||
|
||||
3. **パーサの修正(必要なら)**
|
||||
- ドット記法の解釈を拡張
|
||||
- 静的 Box 参照のノード種別追加
|
||||
|
||||
### 成果物
|
||||
|
||||
- using_resolver*.hako 修正
|
||||
- AST フラグ追加
|
||||
|
||||
---
|
||||
|
||||
## 6. Task 4: MIR lowering の修正
|
||||
|
||||
### 対象ファイル
|
||||
|
||||
- `src/mir/builder/calls/resolver.rs`
|
||||
- `src/mir/builder/calls/call_unified.rs`
|
||||
|
||||
### やること
|
||||
|
||||
1. **静的 Box 呼び出しの判別**
|
||||
- Task 3 で付けた AST 情報を使用
|
||||
- 「インスタンスメソッド」vs「静的 Box メソッド」の分岐
|
||||
|
||||
2. **正しい解決**
|
||||
- `JsonLib.JsonParserBox` → 正しい BoxId に解決
|
||||
- `JsonLib.parse` → 正しい MethodId に解決
|
||||
- BoxCall/MethodCall に正しく落とす
|
||||
|
||||
3. **既存分岐の保護**
|
||||
- instance call / plugin call の分岐を壊さない
|
||||
- carefully な実装
|
||||
|
||||
### 期待される動作
|
||||
|
||||
```
|
||||
AST: JsonLib.parse(...)
|
||||
↓ MIR lowering
|
||||
MIR: BoxCall { box: "JsonParserBox", method: "parse", args: [...] }
|
||||
↓ VM 実行
|
||||
正常動作(Unknown エラーなし)
|
||||
```
|
||||
|
||||
### 成果物
|
||||
|
||||
- resolver.rs 修正
|
||||
- call_unified.rs 修正(必要なら)
|
||||
|
||||
---
|
||||
|
||||
## 7. Task 5: JsonParserBox を使った最小ケースで確認
|
||||
|
||||
### テストファイル作成
|
||||
|
||||
`apps/tests/json_parser_min.hako`:
|
||||
|
||||
```hako
|
||||
using tools.hako_shared.json_parser as JsonLib
|
||||
|
||||
static box Main {
|
||||
main() {
|
||||
local parser = new JsonLib.JsonParserBox()
|
||||
local v = parser.parse("{\"x\":1}")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 実行コマンド
|
||||
|
||||
```bash
|
||||
# 最小テスト
|
||||
./target/release/nyash apps/tests/json_parser_min.hako
|
||||
|
||||
# 期待: RC 0、Unknown box/method エラーなし
|
||||
```
|
||||
|
||||
### hako_check スモークテスト
|
||||
|
||||
```bash
|
||||
# HC019 スモークテスト
|
||||
./tools/hako_check_deadcode_smoke.sh
|
||||
|
||||
# HC020 スモークテスト
|
||||
./tools/hako_check_deadblocks_smoke.sh
|
||||
```
|
||||
|
||||
### 期待される結果
|
||||
|
||||
- 最小テスト: RC 0、エラーなし
|
||||
- hako_check: JsonParserBox 利用部が正常動作
|
||||
- 回帰なし
|
||||
|
||||
### 成果物
|
||||
|
||||
- テストファイル作成
|
||||
- 動作確認完了
|
||||
|
||||
---
|
||||
|
||||
## 8. Task 6: ドキュメント & CURRENT_TASK 更新
|
||||
|
||||
### ドキュメント更新
|
||||
|
||||
1. **phase170_hako_json_library_design.md / phase171_***:
|
||||
- 「JsonParserBox を using で読み込んだ静的 Box ライブラリとしても VM から正常に使えるようになった」を追記
|
||||
|
||||
2. **using.md**:
|
||||
- 静的 Box を using して使う実例を追加(JsonLib パターン)
|
||||
|
||||
3. **CURRENT_TASK.md**:
|
||||
```markdown
|
||||
### Phase 173: using + 静的 Box メソッド解決 ✅
|
||||
- .hako using + 静的 Box 呼び出しが言語仕様と実装の両方で揃った
|
||||
- JsonParserBox が selfhost/hako_check にとって「正式なライブラリ」として使用可能に
|
||||
- Phase 171-2 の完全動作達成
|
||||
```
|
||||
|
||||
### git commit
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat(using): Phase 173 static box method resolution for using statement
|
||||
|
||||
🎉 using + 静的 Box メソッド解決の整備完了!
|
||||
|
||||
🔧 実装内容:
|
||||
- using/resolver: 静的 Box 名の型登録実装
|
||||
- MIR lowering: 静的 Box 呼び出しの正しい解決
|
||||
- AST: 静的 Box 参照のフラグ追加
|
||||
|
||||
📚 仕様整備:
|
||||
- using.md: 静的 Box の使い方追記
|
||||
- LANGUAGE_REFERENCE_2025.md: static box ライブラリ利用の方針追加
|
||||
|
||||
✅ テスト結果:
|
||||
- json_parser_min.hako: RC 0、エラーなし
|
||||
- hako_check スモーク: PASS
|
||||
- 回帰なし確認
|
||||
|
||||
🎯 JsonParserBox が正式なライブラリとして使用可能に!
|
||||
Phase 171-2 完全動作達成
|
||||
|
||||
🤖 Generated with Claude Code
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 完成チェックリスト(Phase 173)
|
||||
|
||||
- [ ] Task 1: 名前解決経路調査
|
||||
- [ ] AST 表現確認
|
||||
- [ ] MIR lowering 確認
|
||||
- [ ] エラー発生箇所特定
|
||||
- [ ] Task 2: 仕様固定(docs)
|
||||
- [ ] using.md 更新
|
||||
- [ ] LANGUAGE_REFERENCE_2025.md 更新
|
||||
- [ ] Task 3: using/resolver 修正
|
||||
- [ ] 型登録実装
|
||||
- [ ] AST フラグ追加
|
||||
- [ ] Task 4: MIR lowering 修正
|
||||
- [ ] 静的 Box 判別条件追加
|
||||
- [ ] 正しい解決実装
|
||||
- [ ] Task 5: テスト確認
|
||||
- [ ] json_parser_min.hako 動作確認
|
||||
- [ ] hako_check スモーク PASS
|
||||
- [ ] Task 6: ドキュメント更新
|
||||
- [ ] phase170/171 更新
|
||||
- [ ] using.md 更新
|
||||
- [ ] CURRENT_TASK.md 更新
|
||||
- [ ] git commit
|
||||
|
||||
---
|
||||
|
||||
## 技術的注意点
|
||||
|
||||
### Rust VM は変更しない
|
||||
|
||||
VM 側は既に静的 Box のメソッドを扱える前提。MIR が正しく BoxCall/MethodCall になれば VM はそのまま動くはず。
|
||||
|
||||
### 段階的確認
|
||||
|
||||
1. まず AST レベルで正しく表現されているか確認
|
||||
2. 次に MIR レベルで正しく変換されているか確認
|
||||
3. 最後に VM で正しく実行されるか確認
|
||||
|
||||
### 既存コードの保護
|
||||
|
||||
- instance call / plugin call の分岐を壊さない
|
||||
- 既存の using 動作に影響を与えない
|
||||
|
||||
---
|
||||
|
||||
## 次のステップ
|
||||
|
||||
Phase 173 完了後:
|
||||
- **Phase 174**: to_json() 逆変換実装
|
||||
- **Phase 175**: selfhost depth-2 JSON 統一化
|
||||
- **Phase 160+**: .hako JoinIR/MIR 移植
|
||||
|
||||
これで using + static box の前段整備が完了し、JsonParserBox が正式なライブラリとして使えるようになる!
|
||||
|
||||
---
|
||||
|
||||
**作成日**: 2025-12-04
|
||||
**Phase**: 173(using + 静的 Box メソッド解決)
|
||||
**予定工数**: 4-6 時間
|
||||
**難易度**: 高(名前解決・MIR lowering の修正)
|
||||
**Rust VM 変更**: なし(.hako/using 側のみ)
|
||||
@ -1,117 +0,0 @@
|
||||
# Phase 173-B StaticBoxRegistry Boxification Assessment
|
||||
|
||||
**Status**: ✅ **OPTIMAL COMPLEXITY REACHED**
|
||||
|
||||
## Architectural Review
|
||||
|
||||
### What Was Accomplished
|
||||
1. **Unified Scattered Management** (4 locations → 1 registry)
|
||||
- Before: `static_box_decls` + `static_boxes` in MirInterpreter
|
||||
- Before: Manual detection code in vm.rs (63 lines)
|
||||
- Before: Scattered checks in multiple handler files
|
||||
- After: Single `StaticBoxRegistry` with clear responsibilities
|
||||
|
||||
2. **Automatic Detection from MIR** (using-imports solved)
|
||||
- Before: Had to manually register using-imported boxes in vm.rs
|
||||
- After: Auto-detection from "BoxName.method/arity" function names
|
||||
- Result: 63 lines of manual code eliminated
|
||||
|
||||
3. **Design Principles Applied**
|
||||
- ✅ **箱にする (Boxify)**: Unified declarations + detection + instances
|
||||
- ✅ **境界を作る (Boundary)**: Clear API (exists, get_or_create_instance, register)
|
||||
- ✅ **Fail-Fast**: No fallback paths, explicit errors for missing boxes
|
||||
- ✅ **遅延シングルトン (Lazy Singleton)**: Only create instances on first access
|
||||
|
||||
### Complexity Analysis
|
||||
|
||||
**StaticBoxRegistry lines**: 285 lines
|
||||
- Core struct: 15 lines (3 fields, well-focused)
|
||||
- Naming module: 30 lines (pure, reusable functions)
|
||||
- Core logic: 95 lines (detection, registration, instantiation)
|
||||
- Tests: 65 lines (comprehensive unit tests)
|
||||
- Comments/Docs: 80 lines (clear documentation)
|
||||
|
||||
**Ratio**: 38% essential logic, 62% tests + docs = **WELL-TESTED AND DOCUMENTED** ✅
|
||||
|
||||
### Can We Simplify Further?
|
||||
|
||||
**Question 1: Remove `detected_boxes` HashSet?**
|
||||
- No. Needed to distinguish:
|
||||
- Declared boxes (from AST)
|
||||
- Detected boxes (from MIR, using-imports)
|
||||
- This distinction matters for error messages and lifecycle
|
||||
|
||||
**Question 2: Combine `declarations` and `detected_boxes`?**
|
||||
- No. Different sources → different semantics
|
||||
- Declarations have methods/fields metadata (from AST)
|
||||
- Detected boxes have only function signatures (from MIR)
|
||||
- Separating prevents false metadata conflicts
|
||||
|
||||
**Question 3: Inline the `naming` module?**
|
||||
- No. Functions are reused in:
|
||||
- StaticBoxRegistry itself (detect_from_mir_functions)
|
||||
- MirInterpreter (is_static_box_method)
|
||||
- Tests (explicitly tested)
|
||||
- Worth keeping as dedicated utility
|
||||
|
||||
**Question 4: Remove BUILTIN_RUNTIME_BOXES list?**
|
||||
- Tempting to remove, but necessary for correctness
|
||||
- Main/main: Not static boxes (entry points)
|
||||
- StringBox/IntegerBox/etc: Built-in, not user-defined
|
||||
- Prevents false positives in auto-detection
|
||||
- Cost: 17 lines. Benefit: Correctness. Worth keeping.
|
||||
|
||||
### What About Elsewhere?
|
||||
|
||||
**Checked for similar patterns**:
|
||||
- ✅ No other scattered registry/management patterns found
|
||||
- ✅ `obj_fields` in MirInterpreter is different (instance field storage, not box metadata)
|
||||
- ✅ Plugin system has its own registry (appropriate separation)
|
||||
- ✅ Box factory patterns are elsewhere, different problem domain
|
||||
|
||||
### Conclusion
|
||||
|
||||
**Current Implementation**: ✅ **CLEAN AND APPROPRIATE**
|
||||
|
||||
- **Not over-engineered**: Each line serves a purpose
|
||||
- 3 fields in StaticBoxRegistry match exact problem domain
|
||||
- 3 public methods + detection API cover all use cases
|
||||
- No helper classes, no premature abstraction
|
||||
|
||||
- **Not under-engineered**: All necessary concerns covered
|
||||
- Auto-detection solves using-import problem
|
||||
- Lazy singleton prevents unnecessary initialization
|
||||
- Fail-Fast errors prevent silent failures
|
||||
- Comprehensive tests ensure correctness
|
||||
|
||||
- **Well-positioned for maintenance**
|
||||
- Clear naming utilities extract reusable parsing logic
|
||||
- Explicit responsibility separation (declarations vs detected)
|
||||
- Documentation explains "why" not just "what"
|
||||
|
||||
### Recommendations for Future Work
|
||||
|
||||
**If you need to extend**:
|
||||
1. **Add metrics** (trace environment variable already in place)
|
||||
- Count detections, instantiations, lookups for diagnostics
|
||||
|
||||
2. **Add caching** (if performance needed)
|
||||
- Cache `all_box_names()` results between calls
|
||||
- Currently rebuilds iterator on each call
|
||||
|
||||
3. **Integrate with plugin system** (future)
|
||||
- Current design allows plugin boxes to register themselves
|
||||
- No architectural barriers to extension
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**状況**: The StaticBoxRegistry is an exemplar of "箱化モジュール化" (boxification modularization).
|
||||
|
||||
- **285 lines** of focused, tested, documented code
|
||||
- **4 responsibilities** clearly separated and bounded
|
||||
- **0 unnecessary complexity** - each line earns its place
|
||||
- **Ready for Phase 34+**: No technical debt from this refactoring
|
||||
|
||||
**Answer to user's question**: "It's simpler now, not complex!" ✨
|
||||
@ -1,345 +0,0 @@
|
||||
# Phase 174-1: JsonParser 複雑ループ再観測
|
||||
|
||||
**Date**: 2025-12-08
|
||||
**Purpose**: Phase 173 で未対応の複雑ループを再分析し、Phase 174 ターゲットを決定
|
||||
|
||||
---
|
||||
|
||||
## 未対応ループ分析
|
||||
|
||||
### 1. _parse_string (lines 150-178)
|
||||
|
||||
**構造**:
|
||||
```hako
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
|
||||
if ch == '"' {
|
||||
// End of string
|
||||
local result = new MapBox()
|
||||
result.set("value", me._unescape_string(str))
|
||||
result.set("pos", p + 1)
|
||||
result.set("type", "string")
|
||||
return result
|
||||
}
|
||||
|
||||
if ch == "\\" {
|
||||
// Escape sequence
|
||||
local has_next = 0
|
||||
if p + 1 < s.length() { has_next = 1 }
|
||||
if has_next == 0 { return null }
|
||||
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
str = str + s.substring(p, p+1)
|
||||
p = p + 1
|
||||
continue
|
||||
}
|
||||
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern 分類**: Pattern4 候補(continue 付き)または Pattern2(基本構造)
|
||||
**LoopBodyLocal**: `ch` が条件に使用
|
||||
**複雑度**: ★★★☆☆(エスケープ処理あり、continue あり)
|
||||
|
||||
**Trim との差分**:
|
||||
- ✅ **同じ**: LoopBodyLocal `ch` を break 条件に使用
|
||||
- ✅ **同じ**: `local ch = s.substring(p, p+1)` パターン
|
||||
- ✅ **同じ**: 単純な文字比較(`ch == "\""`)
|
||||
- ⚠️ **追加**: エスケープ処理(`ch == "\\"` の次の文字を読む)
|
||||
- ⚠️ **追加**: 文字列バッファへの追加処理(`str = str + ch`)
|
||||
- ⚠️ **追加**: continue 使用(エスケープ処理後)
|
||||
- ⚠️ **追加**: return による早期終了(成功時)
|
||||
|
||||
**最小化可能性**: ★★★★★(エスケープ処理・continue・return を除外すれば Trim と同型)
|
||||
|
||||
**最小化版**:
|
||||
```hako
|
||||
// エスケープ処理・文字列バッファ・continue を除外
|
||||
loop(pos < len) {
|
||||
local ch = s.substring(pos, pos+1)
|
||||
if ch == "\"" {
|
||||
break // return の代わりに break
|
||||
} else {
|
||||
pos = pos + 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
この構造なら:
|
||||
- ✅ TrimLoopHelper がそのまま使える
|
||||
- ✅ Pattern2 Trim 特例経路が使える
|
||||
- ✅ 単一キャリア `pos` のみ
|
||||
- ✅ LoopBodyLocal `ch` の昇格パターンが Trim と同じ
|
||||
|
||||
**Phase 174 適性**: ★★★★★(最小化版で Trim に最も近い、第一候補)
|
||||
|
||||
---
|
||||
|
||||
### 2. _parse_array (lines 203-231)
|
||||
|
||||
**構造**:
|
||||
```hako
|
||||
loop(p < s.length()) {
|
||||
// Parse element
|
||||
local elem_result = me._parse_value(s, p)
|
||||
if elem_result == null { return null }
|
||||
|
||||
local elem = elem_result.get("value")
|
||||
arr.push(elem)
|
||||
|
||||
p = elem_result.get("pos")
|
||||
p = me._skip_whitespace(s, p)
|
||||
|
||||
if p >= s.length() { return null }
|
||||
|
||||
local ch = s.substring(p, p+1)
|
||||
if ch == "]" {
|
||||
// End of array - return result
|
||||
return result
|
||||
}
|
||||
|
||||
if ch == "," {
|
||||
p = p + 1
|
||||
p = me._skip_whitespace(s, p)
|
||||
continue
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern 分類**: Pattern4 候補(continue 付き)
|
||||
**LoopBodyLocal**: `ch` が条件に使用(ただし複数の他の処理も多い)
|
||||
**複雑度**: ★★★★☆(複数条件、continue、ネスト、MethodCall 多数)
|
||||
|
||||
**Trim との差分**:
|
||||
- ⚠️ **追加**: MethodCall 多数(`me._parse_value`, `me._skip_whitespace`)
|
||||
- ⚠️ **追加**: MapBox/ArrayBox 操作(`elem_result.get()`, `arr.push()`)
|
||||
- ⚠️ **追加**: 複数の return 文(成功・失敗パス)
|
||||
- ⚠️ **追加**: continue 使用(区切り文字処理後)
|
||||
- ✅ **同じ**: `local ch = s.substring(p, p+1)` パターン(限定的)
|
||||
- ✅ **同じ**: 単純な文字比較(`ch == "]"`, `ch == ","`)
|
||||
|
||||
**最小化可能性**: ★☆☆☆☆(MethodCall と複雑な処理が本質的、最小化困難)
|
||||
|
||||
**Phase 174 適性**: ★★☆☆☆(複雑すぎる、Phase 175+ 推奨)
|
||||
|
||||
---
|
||||
|
||||
### 3. _parse_object (lines 256-304)
|
||||
|
||||
**構造**:
|
||||
```hako
|
||||
loop(p < s.length()) {
|
||||
p = me._skip_whitespace(s, p)
|
||||
|
||||
// Parse key (must be string)
|
||||
if s.substring(p, p+1) != '"' { return null }
|
||||
local key_result = me._parse_string(s, p)
|
||||
if key_result == null { return null }
|
||||
|
||||
local key = key_result.get("value")
|
||||
p = key_result.get("pos")
|
||||
p = me._skip_whitespace(s, p)
|
||||
|
||||
// Expect colon
|
||||
if p >= s.length() { return null }
|
||||
if s.substring(p, p+1) != ":" { return null }
|
||||
p = p + 1
|
||||
|
||||
p = me._skip_whitespace(s, p)
|
||||
|
||||
// Parse value
|
||||
local value_result = me._parse_value(s, p)
|
||||
if value_result == null { return null }
|
||||
|
||||
local value = value_result.get("value")
|
||||
obj.set(key, value)
|
||||
|
||||
p = value_result.get("pos")
|
||||
p = me._skip_whitespace(s, p)
|
||||
|
||||
if p >= s.length() { return null }
|
||||
|
||||
local ch = s.substring(p, p+1)
|
||||
if ch == "}" {
|
||||
// End of object - return result
|
||||
return result
|
||||
}
|
||||
|
||||
if ch == "," {
|
||||
p = p + 1
|
||||
continue
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern 分類**: Pattern4 候補(continue 付き)
|
||||
**LoopBodyLocal**: `ch` が条件に使用(ただし他の処理も多い)
|
||||
**複雑度**: ★★★★★(_parse_array と同程度以上、キー・バリューペア処理)
|
||||
|
||||
**Trim との差分**:
|
||||
- ⚠️ **追加**: _parse_array と同様に MethodCall 多数
|
||||
- ⚠️ **追加**: キー・バリューペアの複雑な処理
|
||||
- ⚠️ **追加**: 複数の return 文
|
||||
- ⚠️ **追加**: continue 使用
|
||||
- ✅ **同じ**: `local ch = s.substring(p, p+1)` パターン(限定的)
|
||||
|
||||
**最小化可能性**: ★☆☆☆☆(_parse_array と同様に最小化困難)
|
||||
|
||||
**Phase 174 適性**: ★★☆☆☆(_parse_array と同程度の複雑さ、Phase 175+ 推奨)
|
||||
|
||||
---
|
||||
|
||||
### 4. _parse_number (lines 121-133)
|
||||
|
||||
**構造**:
|
||||
```hako
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
local digit_pos = digits.indexOf(ch)
|
||||
|
||||
// Exit condition: non-digit character found
|
||||
if digit_pos < 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Continue parsing: digit found
|
||||
num_str = num_str + ch
|
||||
p = p + 1
|
||||
}
|
||||
```
|
||||
|
||||
**Pattern 分類**: Pattern2(break 付き、continue なし)
|
||||
**LoopBodyLocal**: `ch`, `digit_pos` が条件に使用
|
||||
**複雑度**: ★★☆☆☆(Trim に近いが、indexOf 使用)
|
||||
|
||||
**Trim との差分**:
|
||||
- ✅ **同じ**: `local ch = s.substring(p, p+1)` パターン
|
||||
- ✅ **同じ**: break での終了
|
||||
- ✅ **同じ**: 単一キャリア `p` の更新
|
||||
- ⚠️ **追加**: `digits.indexOf(ch)` による範囲チェック(OR chain の代わり)
|
||||
- ⚠️ **追加**: 文字列バッファへの追加処理(`num_str = num_str + ch`)
|
||||
|
||||
**最小化可能性**: ★★★★☆(文字列バッファを除外すれば Trim に近い)
|
||||
|
||||
**最小化版**:
|
||||
```hako
|
||||
// 文字列バッファを除外
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
local digit_pos = digits.indexOf(ch)
|
||||
if digit_pos < 0 {
|
||||
break
|
||||
} else {
|
||||
p = p + 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Phase 174 適性**: ★★★☆☆(候補だが、_parse_string のほうが Trim に近い)
|
||||
|
||||
---
|
||||
|
||||
## Phase 174-2 選定結果
|
||||
|
||||
**選定ループ**: `_parse_string`(最小化版)
|
||||
|
||||
**選定理由**:
|
||||
|
||||
1. ✅ **Trim に最も近い構造**:
|
||||
- `local ch = s.substring(pos, pos+1)` パターン
|
||||
- `if ch == "\"" { break }` - 単純な文字比較
|
||||
- 単一キャリア `pos` のみ(最小化版)
|
||||
- LoopBodyLocal `ch` の使用パターンが Trim と同じ
|
||||
|
||||
2. ✅ **最小化可能性が高い**:
|
||||
- エスケープ処理を除外 → 基本的な文字比較ループ
|
||||
- 文字列バッファを除外 → 単一キャリア
|
||||
- continue を除外 → Pattern2 構造維持
|
||||
- return を break に置換 → Pattern2 Trim 特例経路利用可能
|
||||
|
||||
3. ✅ **実用性**:
|
||||
- JSON 文字列パースの核心部分
|
||||
- 成功すれば将来的にエスケープ処理等を追加可能
|
||||
|
||||
4. ✅ **既存実装で対応可能**:
|
||||
- `TrimLoopHelper::is_safe_trim()` がそのまま使える
|
||||
- `LoopBodyCarrierPromoter::try_promote()` で carrier 昇格可能
|
||||
- Pattern2 Trim 特例経路で JoinIR 生成可能
|
||||
|
||||
**構造的類似度**:
|
||||
- Trim/_skip_whitespace: ★★★★★(100% 一致)
|
||||
- _parse_string(最小化版): ★★★★★(99% 一致、`"\""`の代わりに空白文字)
|
||||
- _parse_number(最小化版): ★★★★☆(95% 一致、indexOf 使用が差分)
|
||||
|
||||
**Phase 174 目標**:
|
||||
- 基本的な文字比較部分を P5 パイプラインで処理(最小化版)
|
||||
- エスケープ処理・文字列バッファ・continue は Phase 175+ に延期
|
||||
|
||||
**次点候補**: `_parse_number`(最小化版)
|
||||
- Trim に近いが、indexOf 使用がやや複雑
|
||||
- _parse_string 成功後の次のターゲット候補
|
||||
|
||||
**Phase 175+ 候補**: `_parse_array`, `_parse_object`
|
||||
- 複雑すぎる、MethodCall 多数、最小化困難
|
||||
- P5 パイプライン拡張後に検討
|
||||
|
||||
---
|
||||
|
||||
## Phase 174-3 準備: 最小化版の設計方針
|
||||
|
||||
**最小化方針**:
|
||||
1. **エスケープ処理を除外**: `if ch == "\\"` ブロック全体を削除
|
||||
2. **文字列バッファを除外**: `str = str + ch` を削除、`str` 変数を削除
|
||||
3. **continue を除外**: エスケープ処理がないので continue も不要
|
||||
4. **return を break に置換**: 成功時の return を break に変更
|
||||
|
||||
**最小化版の構造**:
|
||||
```hako
|
||||
// Phase 174-4 PoC版(最もシンプルな形)
|
||||
loop(pos < len) {
|
||||
local ch = s.substring(pos, pos+1)
|
||||
if ch == "\"" {
|
||||
break
|
||||
} else {
|
||||
pos = pos + 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
この構造は Trim/_skip_whitespace と **完全に同型**(文字比較の対象が異なるだけ)。
|
||||
|
||||
**期待される動作**:
|
||||
1. `LoopConditionScopeBox::analyze()` - `ch` を LoopBodyLocal として検出
|
||||
2. `LoopBodyCarrierPromoter::try_promote()` - carrier `is_ch_match` に昇格
|
||||
3. `TrimLoopHelper::is_safe_trim()` - true を返す
|
||||
4. Pattern2 lowerer - Trim 特例経路で JoinIR 生成
|
||||
|
||||
**Phase 174-4 で実証すべきこと**:
|
||||
- ✅ Trim パイプラインが文字比較対象が異なるだけで機能すること
|
||||
- ✅ `"\""`(終端クォート)という異なる文字でも昇格パターンが機能すること
|
||||
- ✅ TrimLoopHelper の汎用性(空白文字以外にも対応可能)
|
||||
|
||||
---
|
||||
|
||||
## 結論
|
||||
|
||||
**Phase 174 戦略**:
|
||||
- ✅ _parse_string の最小化版をターゲット
|
||||
- ✅ 既存の TrimLoopHelper をそのまま再利用
|
||||
- ✅ Pattern2 Trim 特例経路で JoinIR 生成
|
||||
- ❌ エスケープ処理・複数キャリア・continue は Phase 175+ に延期
|
||||
|
||||
**Phase 175+ への道筋**:
|
||||
1. Phase 174: _parse_string 最小化版(Trim と同型)
|
||||
2. Phase 175: 文字列バッファ追加(複数キャリア対応)
|
||||
3. Phase 176: エスケープ処理追加(continue 対応)
|
||||
4. Phase 177: _parse_number 対応(indexOf パターン)
|
||||
5. Phase 178+: _parse_array/_parse_object(複雑ループ)
|
||||
@ -1,362 +0,0 @@
|
||||
# Phase 174-3: JsonParser `_parse_string` P5B 設計
|
||||
|
||||
**Date**: 2025-12-08
|
||||
**Target**: `_parse_string` ループ(エスケープ処理付き文字列パース)
|
||||
**Purpose**: Trim P5 パイプラインを複雑ループに拡張する設計
|
||||
|
||||
---
|
||||
|
||||
## ターゲットループ構造
|
||||
|
||||
### 完全版(Phase 175+ のターゲット)
|
||||
|
||||
```hako
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
|
||||
if ch == "\"" { // 終端クォート
|
||||
// End of string
|
||||
local result = new MapBox()
|
||||
result.set("value", me._unescape_string(str))
|
||||
result.set("pos", p + 1)
|
||||
result.set("type", "string")
|
||||
return result
|
||||
}
|
||||
|
||||
if ch == "\\" { // エスケープ開始
|
||||
local has_next = 0
|
||||
if p + 1 < s.length() { has_next = 1 }
|
||||
if has_next == 0 { return null }
|
||||
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
str = str + s.substring(p, p+1)
|
||||
p = p + 1
|
||||
continue
|
||||
}
|
||||
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
}
|
||||
```
|
||||
|
||||
### 最小化版(Phase 174-4 PoC)
|
||||
|
||||
```hako
|
||||
// エスケープ処理・文字列バッファ・continue・return を除外
|
||||
loop(pos < len) {
|
||||
local ch = s.substring(pos, pos+1)
|
||||
if ch == "\"" {
|
||||
break
|
||||
} else {
|
||||
pos = pos + 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pattern Space 分析
|
||||
|
||||
### 軸 A〜F での分類(最小化版)
|
||||
|
||||
| 軸 | 値 | 説明 |
|
||||
|----|-----|------|
|
||||
| **A. 継続条件** | 単純 | `pos < len` |
|
||||
| **B. 早期終了** | 条件付き break | `if ch == "\"" { break }` |
|
||||
| **C. スキップ** | なし | continue 使用なし |
|
||||
| **D. PHI 分岐** | なし | if 分岐あるが PHI 不要 |
|
||||
| **E. 条件変数のスコープ** | **LoopBodyLocal** | `ch` が条件に使用 |
|
||||
| **F. キャリア更新** | 単純 | `pos` のみ更新 |
|
||||
|
||||
**Pattern 分類**: **P5**(LoopBodyLocal 条件)+ **Pattern2**(break 付き)
|
||||
|
||||
---
|
||||
|
||||
## Trim との差分
|
||||
|
||||
### ✅ 同じ部分(P5 パイプライン再利用可能)
|
||||
|
||||
1. **LoopBodyLocal 変数の使用**:
|
||||
- Trim: `local ch = s.substring(start, start+1)`
|
||||
- Parse String: `local ch = s.substring(pos, pos+1)`
|
||||
- → **完全一致**(変数名が異なるだけ)
|
||||
|
||||
2. **文字比較パターン**:
|
||||
- Trim: `if ch == " " || ch == "\t" || ch == "\n" || ch == "\r"`
|
||||
- Parse String: `if ch == "\""`
|
||||
- → **構造は同じ**(比較対象が異なるだけ)
|
||||
|
||||
3. **Pattern2 構造**:
|
||||
- Trim: `loop(start < end)` + `{ start = start + 1 } else { break }`
|
||||
- Parse String: `loop(pos < len)` + `{ pos = pos + 1 } else { break }`
|
||||
- → **完全同型**
|
||||
|
||||
4. **キャリア更新**:
|
||||
- Trim: `start = start + 1`(単一キャリア)
|
||||
- Parse String: `pos = pos + 1`(単一キャリア)
|
||||
- → **完全一致**
|
||||
|
||||
### ⚠️ 追加部分(完全版、Phase 175+ で必要)
|
||||
|
||||
1. **複数キャリア**:
|
||||
- `pos`: ループカウンタ
|
||||
- `str`: 文字列バッファ
|
||||
- **課題**: 既存の TrimLoopHelper は単一キャリア想定
|
||||
|
||||
2. **エスケープ処理**:
|
||||
- `if ch == "\\" { ... }` → 追加の文字読み込み
|
||||
- **課題**: LoopBodyLocal の多重使用(`ch` と `escaped`)
|
||||
|
||||
3. **continue**:
|
||||
- エスケープ処理後に continue
|
||||
- **課題**: Pattern4 対応が必要
|
||||
|
||||
4. **return による早期終了**:
|
||||
- 成功時の return
|
||||
- **課題**: ExitLine とループ外リターンの分離
|
||||
|
||||
---
|
||||
|
||||
## 箱の再利用 vs 追加
|
||||
|
||||
### 再利用可能な既存箱(Phase 174-4)
|
||||
|
||||
| 箱名 | 再利用可能性 | 理由 |
|
||||
|-----|-----------|------|
|
||||
| **LoopConditionScopeBox** | ✅ 100% | `ch` を LoopBodyLocal と分類可能 |
|
||||
| **LoopBodyCarrierPromoter** | ✅ 100% | `ch` の昇格検出(Trim と同じ) |
|
||||
| **TrimPatternInfo** | ✅ 100% | 基本構造は再利用可能 |
|
||||
| **TrimLoopHelper** | ✅ 100% | 単一キャリア版で完全対応 |
|
||||
| **CarrierInfo** | ✅ 100% | `pos` キャリア情報 |
|
||||
| **ExitLine** | ✅ 100% | break による終了 |
|
||||
| **Boundary** | ✅ 100% | ループ境界情報 |
|
||||
|
||||
**結論**: 最小化版では**既存の箱をそのまま使える**!
|
||||
|
||||
### 追加が必要な箱(Phase 175+ で検討)
|
||||
|
||||
1. **MultiCarrierTrimHelper**(将来):
|
||||
- TrimLoopHelper の拡張版
|
||||
- 複数キャリア(`pos` + `str`)対応
|
||||
- **Phase 175 で設計・実装**
|
||||
|
||||
2. **EscapeSequenceHandler**(将来):
|
||||
- エスケープ処理の追加ロジック
|
||||
- `ch == "\\"` 時の特殊処理
|
||||
- **Phase 176 で設計・実装**
|
||||
|
||||
3. **ContinuePatternHelper**(将来):
|
||||
- Pattern4(continue 付き)対応
|
||||
- **Phase 176 で設計・実装**
|
||||
|
||||
---
|
||||
|
||||
## Phase 174 戦略
|
||||
|
||||
### ステップ1: 最小化版で試験(Phase 174-4)
|
||||
|
||||
**最小化方針**:
|
||||
- ✅ エスケープ処理を**除外**
|
||||
- ✅ 文字列バッファ(`str`)を**除外**
|
||||
- ✅ continue を**除外**
|
||||
- ✅ return を break に**置換**
|
||||
|
||||
**最小化版**:
|
||||
```hako
|
||||
// Phase 174-4 PoC
|
||||
loop(pos < len) {
|
||||
local ch = s.substring(pos, pos+1)
|
||||
if ch == "\"" {
|
||||
break
|
||||
} else {
|
||||
pos = pos + 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**この構造の利点**:
|
||||
- ✅ TrimLoopHelper がそのまま使える
|
||||
- ✅ Pattern2 Trim 特例経路が使える
|
||||
- ✅ 単一キャリア `pos` のみ
|
||||
- ✅ 既存コードの変更**ゼロ**
|
||||
|
||||
**期待される動作**:
|
||||
1. `LoopConditionScopeBox::analyze()`
|
||||
- `ch`: LoopBodyLocal(substring でループ内定義)
|
||||
- `pos`, `len`: OuterLocal(ループ外定義)
|
||||
|
||||
2. `LoopBodyCarrierPromoter::try_promote()`
|
||||
- `ch` を carrier `is_ch_match` に昇格
|
||||
- 昇格理由: break 条件に使用
|
||||
|
||||
3. `TrimLoopHelper::is_safe_trim()`
|
||||
- true を返す(Trim と完全同型)
|
||||
|
||||
4. Pattern2 lowerer
|
||||
- Trim 特例経路で JoinIR 生成
|
||||
- `[pattern2/trim] Safe Trim pattern detected`
|
||||
|
||||
### ステップ2: エスケープ処理の追加(Phase 175+)
|
||||
|
||||
最小化版が成功したら、段階的に追加:
|
||||
|
||||
1. **Phase 175**: 文字列バッファ追加(複数キャリア対応)
|
||||
```hako
|
||||
loop(pos < len) {
|
||||
local ch = s.substring(pos, pos+1)
|
||||
if ch == "\"" {
|
||||
break
|
||||
} else {
|
||||
result = result + ch // ← 追加
|
||||
pos = pos + 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Phase 176**: エスケープ処理追加(continue 対応)
|
||||
```hako
|
||||
loop(pos < len) {
|
||||
local ch = s.substring(pos, pos+1)
|
||||
if ch == "\"" {
|
||||
break
|
||||
} else if ch == "\\" {
|
||||
// エスケープ処理
|
||||
pos = pos + 1
|
||||
result = result + s.substring(pos, pos+1)
|
||||
pos = pos + 1
|
||||
continue // ← Pattern4 対応が必要
|
||||
} else {
|
||||
result = result + ch
|
||||
pos = pos + 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Phase 177**: return 処理追加(ExitLine 拡張)
|
||||
```hako
|
||||
loop(pos < len) {
|
||||
local ch = s.substring(pos, pos+1)
|
||||
if ch == "\"" {
|
||||
return create_result(result, pos) // ← ExitLine 拡張
|
||||
}
|
||||
// ... 以下同じ
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 命名の汎用化(Phase 175+ で検討)
|
||||
|
||||
### 現在の命名(Trim 特化)
|
||||
|
||||
- `TrimLoopHelper` - Trim 専用のような命名
|
||||
- `is_safe_trim()` - Trim 専用のような命名
|
||||
|
||||
### 汎用化案(Phase 175+ で検討)
|
||||
|
||||
**案1: CharComparison**
|
||||
- `CharComparisonLoopHelper`
|
||||
- `is_safe_char_comparison()`
|
||||
- **利点**: 文字比較ループの汎用名
|
||||
- **欠点**: やや長い
|
||||
|
||||
**案2: SingleCharBreak**
|
||||
- `SingleCharBreakLoopHelper`
|
||||
- `is_safe_single_char_break()`
|
||||
- **利点**: 構造を正確に表現
|
||||
- **欠点**: 長い、やや複雑
|
||||
|
||||
**案3: P5Pattern(軸E準拠)**
|
||||
- `P5LoopHelper`
|
||||
- `is_safe_p5_pattern()`
|
||||
- **利点**: Pattern Space 軸E と一貫性
|
||||
- **欠点**: P5 の意味が不明瞭
|
||||
|
||||
**Phase 174 での方針**: **命名変更なし**(既存コード保持)
|
||||
- Phase 175+ で複数キャリア対応時に再検討
|
||||
- 既存の TrimLoopHelper は Trim 専用として保持
|
||||
- 新しい汎用版を別途作成する可能性
|
||||
|
||||
---
|
||||
|
||||
## テスト戦略
|
||||
|
||||
### Phase 174-4 テストケース
|
||||
|
||||
**ファイル**: `local_tests/test_jsonparser_parse_string_min.hako`
|
||||
|
||||
```hako
|
||||
static box JsonParserStringTest {
|
||||
s: StringBox
|
||||
pos: IntegerBox
|
||||
len: IntegerBox
|
||||
|
||||
parse_string_min() {
|
||||
me.s = "hello world\"" // 終端クォート付き
|
||||
me.pos = 0
|
||||
me.len = me.s.length()
|
||||
|
||||
// 最小化版: エスケープ処理なし、終端クォート検出のみ
|
||||
loop(me.pos < me.len) {
|
||||
local ch = me.s.charAt(me.pos)
|
||||
if ch == "\"" {
|
||||
break
|
||||
} else {
|
||||
me.pos = me.pos + 1
|
||||
}
|
||||
}
|
||||
|
||||
print("Found quote at position: ")
|
||||
print(me.pos)
|
||||
}
|
||||
|
||||
main() {
|
||||
me.parse_string_min()
|
||||
return "OK"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**期待される出力**:
|
||||
```
|
||||
[pattern2/check] 'ch': LoopBodyLocal ✅
|
||||
[pattern2/promoter] promoted to carrier 'is_ch_match' ✅
|
||||
[pattern2/trim] Safe Trim pattern detected ✅
|
||||
Found quote at position: 11
|
||||
```
|
||||
|
||||
**実行コマンド**:
|
||||
```bash
|
||||
NYASH_JOINIR_CORE=1 NYASH_LEGACY_LOOPBUILDER=0 \
|
||||
./target/release/hakorune local_tests/test_jsonparser_parse_string_min.hako
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 結論
|
||||
|
||||
### Phase 174-4 では
|
||||
|
||||
- ✅ 既存の TrimLoopHelper をそのまま再利用
|
||||
- ✅ 最小化版(`ch == "\""`のみ)で P5 パイプライン検証
|
||||
- ✅ 既存コードの変更**ゼロ**
|
||||
- ❌ エスケープ処理・複数キャリア・continue は Phase 175+ に延期
|
||||
|
||||
### Phase 175+ で検討
|
||||
|
||||
- **MultiCarrierTrimHelper** の設計
|
||||
- **エスケープ処理**の統合(Pattern4 対応)
|
||||
- **命名の汎用化**(Trim → CharComparison)
|
||||
- **return 処理**の ExitLine 拡張
|
||||
|
||||
### 技術的価値
|
||||
|
||||
**Phase 174-4 で実証すべきこと**:
|
||||
1. ✅ Trim パイプラインが文字比較対象が異なるだけで機能すること
|
||||
2. ✅ `"\"`(終端クォート)という異なる文字でも昇格パターンが機能すること
|
||||
3. ✅ TrimLoopHelper の汎用性(空白文字以外にも対応可能)
|
||||
|
||||
**成功すれば**:
|
||||
- Trim P5 パイプラインが「Trim 専用」ではなく「文字比較ループ汎用」であることが証明される
|
||||
- Phase 175+ での拡張(複数キャリア・continue 等)の基盤が確立される
|
||||
@ -1,280 +0,0 @@
|
||||
# Phase 175: P5 Multiple Carrier Support Design
|
||||
|
||||
**Date**: 2025-12-08
|
||||
**Purpose**: Extend P5 pipeline to support multiple carriers for complex loops like `_parse_string`
|
||||
|
||||
---
|
||||
|
||||
## 1. Target: _parse_string Carrier Analysis
|
||||
|
||||
### 1.1 Loop Structure (lines 150-178)
|
||||
|
||||
```hako
|
||||
_parse_string(s, pos) {
|
||||
if s.substring(pos, pos+1) != '"' { return null }
|
||||
|
||||
local p = pos + 1 // Carrier 1: position index
|
||||
local str = "" // Carrier 2: result buffer
|
||||
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1) // LoopBodyLocal (promotion candidate)
|
||||
|
||||
if ch == '"' {
|
||||
// End of string
|
||||
local result = new MapBox()
|
||||
result.set("value", me._unescape_string(str)) // Uses str carrier
|
||||
result.set("pos", p + 1) // Uses p carrier
|
||||
result.set("type", "string")
|
||||
return result
|
||||
}
|
||||
|
||||
if ch == "\\" {
|
||||
// Escape sequence (Phase 176 scope)
|
||||
local has_next = 0
|
||||
if p + 1 < s.length() { has_next = 1 }
|
||||
if has_next == 0 { return null }
|
||||
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
str = str + s.substring(p, p+1)
|
||||
p = p + 1
|
||||
continue // ⚠️ Phase 176 scope
|
||||
}
|
||||
|
||||
str = str + ch // Update carrier 2
|
||||
p = p + 1 // Update carrier 1
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 Carrier Candidates Table
|
||||
|
||||
| Variable | Type | Update Pattern | Exit Usage | Carrier Status |
|
||||
|---------|------|----------------|------------|----------------|
|
||||
| `p` | IntegerBox | `p = p + 1` | Position in `result.set("pos", p + 1)` | ✅ **Required Carrier** |
|
||||
| `str` | StringBox | `str = str + ch` | String buffer in `result.set("value", me._unescape_string(str))` | ✅ **Required Carrier** |
|
||||
| `ch` | StringBox | `local ch = s.substring(p, p+1)` | Loop body comparison | ❌ **LoopBodyLocal** (promotion target) |
|
||||
| `has_next` | IntegerBox | `local has_next = 0` | Escape processing guard | ❌ **Loop body only** (Phase 176) |
|
||||
|
||||
### 1.3 Carrier Classification
|
||||
|
||||
**Required Carriers (Exit-dependent)**:
|
||||
1. **`p`**: Position index - final value used in `result.set("pos", p + 1)`
|
||||
2. **`str`**: Result buffer - final value used in `result.set("value", me._unescape_string(str))`
|
||||
|
||||
**Promoted Carriers (P5 mechanism)**:
|
||||
3. **`is_ch_match`**: Bool carrier promoted from `ch` (Trim pattern detection)
|
||||
- Pattern: `ch = s.substring(p, p+1)` → `ch == "\""` equality chain
|
||||
- Promotion: `LoopBodyCarrierPromoter` converts to bool carrier
|
||||
|
||||
**Loop-Internal Only (No carrier needed)**:
|
||||
- `ch`: LoopBodyLocal, promotion target → becomes `is_ch_match` carrier
|
||||
- `has_next`: Escape sequence guard (Phase 176 scope)
|
||||
|
||||
---
|
||||
|
||||
## 2. Phase 175 Minimal PoC Scope
|
||||
|
||||
**Goal**: Prove multi-carrier support with 2 carriers (`p` + `str`), excluding escape handling
|
||||
|
||||
### 2.1 Minimal PoC Structure
|
||||
|
||||
```hako
|
||||
_parse_string_min2() {
|
||||
me.s = "hello world\""
|
||||
me.pos = 0
|
||||
me.len = me.s.length()
|
||||
me.result = ""
|
||||
|
||||
// 2-carrier version: p + result updated together
|
||||
loop(me.pos < me.len) {
|
||||
local ch = me.s.substring(me.pos, me.pos+1)
|
||||
if ch == "\"" {
|
||||
break
|
||||
} else {
|
||||
me.result = me.result + ch // Carrier 2 update
|
||||
me.pos = me.pos + 1 // Carrier 1 update
|
||||
}
|
||||
}
|
||||
|
||||
// Exit: both pos and result are used
|
||||
print("Parsed string: ")
|
||||
print(me.result)
|
||||
print(", final pos: ")
|
||||
print(me.pos)
|
||||
}
|
||||
```
|
||||
|
||||
**Carrier Count**: 2 (`pos`, `result`) + 1 promoted (`is_ch_match`) = **3 total**
|
||||
|
||||
**Excluded from Phase 175**:
|
||||
- ❌ Escape sequence handling (`\\"`, `continue` path)
|
||||
- ❌ Complex nested conditionals
|
||||
- ✅ Focus: Simple char accumulation + position increment
|
||||
|
||||
---
|
||||
|
||||
## 3. P5 Multi-Carrier Architecture
|
||||
|
||||
### 3.1 Existing Boxes Already Support Multi-Carrier ✅
|
||||
|
||||
#### 3.1.1 LoopUpdateAnalyzer (Multi-carrier ready ✅)
|
||||
|
||||
**File**: `src/mir/join_ir/lowering/loop_update_analyzer.rs`
|
||||
**API**: `identify_updated_carriers(body, all_carriers) -> CarrierInfo`
|
||||
**Multi-carrier support**: ✅ Loops over `all_carriers.carriers`
|
||||
|
||||
**Code**:
|
||||
```rust
|
||||
pub fn identify_updated_carriers(
|
||||
body: &[ASTNode],
|
||||
all_carriers: &CarrierInfo,
|
||||
) -> Result<CarrierInfo, String> {
|
||||
let mut updated = CarrierInfo::new();
|
||||
for carrier in &all_carriers.carriers { // ✅ Multi-carrier loop
|
||||
if is_updated_in_body(body, &carrier.name) {
|
||||
updated.add_carrier(carrier.clone());
|
||||
}
|
||||
}
|
||||
Ok(updated)
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.1.2 LoopBodyCarrierPromoter (Adds carriers ✅)
|
||||
|
||||
**File**: `src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs`
|
||||
**API**: `try_promote(request) -> PromotionResult`
|
||||
**Multi-carrier support**: ✅ Generates **additional carriers** from promotion
|
||||
|
||||
**Behavior**:
|
||||
```rust
|
||||
let promoted = LoopBodyCarrierPromoter::try_promote(&request)?;
|
||||
carrier_info.merge_from(&promoted.to_carrier_info()); // Add promoted carrier
|
||||
// Result: carrier_info.carriers = [pos, result, is_ch_match]
|
||||
```
|
||||
|
||||
#### 3.1.3 CarrierInfo (Multi-carrier container ✅)
|
||||
|
||||
**File**: `src/mir/join_ir/lowering/carrier_info.rs`
|
||||
**API**: `carriers: Vec<CarrierData>`, `merge_from(&other)`
|
||||
**Multi-carrier support**: ✅ `Vec` holds arbitrary number of carriers
|
||||
|
||||
**Phase 175-3 Usage**:
|
||||
```rust
|
||||
let mut carrier_info = CarrierInfo::new();
|
||||
carrier_info.add_carrier(CarrierData { // Carrier 1
|
||||
name: "pos".to_string(),
|
||||
update_expr: UpdateExpr::Simple { ... },
|
||||
});
|
||||
carrier_info.add_carrier(CarrierData { // Carrier 2
|
||||
name: "result".to_string(),
|
||||
update_expr: UpdateExpr::Simple { ... },
|
||||
});
|
||||
// carrier_info.carriers.len() == 2 ✅
|
||||
```
|
||||
|
||||
#### 3.1.4 ExitMeta / ExitBinding (Multi-carrier ready ✅)
|
||||
|
||||
**File**: `src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs`
|
||||
**API**: `carrier_exits: Vec<(String, ValueId)>`
|
||||
**Multi-carrier support**: ✅ `Vec` holds all carrier exits
|
||||
|
||||
**ExitMetaCollector Behavior**:
|
||||
```rust
|
||||
for carrier in &carrier_info.carriers { // ✅ Multi-carrier loop
|
||||
exit_bindings.push((carrier.name.clone(), exit_value_id));
|
||||
}
|
||||
// exit_bindings = [("pos", ValueId(10)), ("result", ValueId(20)), ("is_ch_match", ValueId(30))]
|
||||
```
|
||||
|
||||
#### 3.1.5 ExitLineReconnector (Multi-carrier ready ✅)
|
||||
|
||||
**File**: `src/mir/builder/control_flow/joinir/merge/mod.rs`
|
||||
**API**: `reconnect_exit_bindings(exit_bindings, loop_header_phi_info, variable_map)`
|
||||
**Multi-carrier support**: ✅ Loops over all `exit_bindings`
|
||||
|
||||
**Behavior**:
|
||||
```rust
|
||||
for (carrier_name, _) in exit_bindings { // ✅ Multi-carrier loop
|
||||
if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi_dst(carrier_name) {
|
||||
variable_map.insert(carrier_name.clone(), phi_dst);
|
||||
}
|
||||
}
|
||||
// variable_map: {"pos" -> ValueId(100), "result" -> ValueId(200), "is_ch_match" -> ValueId(300)}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Conclusion: Architecture Supports Multi-Carrier ✅ (Pattern2 limitation found)
|
||||
|
||||
### 4.1 Phase 175-3 Test Results
|
||||
|
||||
**Test**: `local_tests/test_jsonparser_parse_string_min2.hako`
|
||||
|
||||
**MIR Analysis**:
|
||||
```mir
|
||||
bb3:
|
||||
%9 = copy %8 // result = "" (initialization)
|
||||
|
||||
bb5: // Loop header
|
||||
%25 = phi [%4, bb3], [%21, bb10] // Only pos carrier!
|
||||
// ❌ Missing: result carrier PHI
|
||||
|
||||
bb10: // Loop update
|
||||
%21 = %25 Add %20 // pos = pos + 1
|
||||
// ❌ Missing: result = result + ch update
|
||||
|
||||
bb12: // Exit block
|
||||
%29 = copy %9 // Still uses original %9 (empty string)!
|
||||
```
|
||||
|
||||
**Root Cause**: Pattern2's Trim optimization only emits `pos` carrier, ignoring `result` updates in loop body.
|
||||
|
||||
**Architecture Validation** ✅:
|
||||
- ✅ `CarrierInfo` detected 3 carriers (`pos`, `result`, `is_ch_match`)
|
||||
- ✅ `variable_map` contains all carriers at pattern2_start
|
||||
- ✅ Existing boxes (ExitMeta, ExitLineReconnector) support multi-carrier
|
||||
- ❌ Pattern2 lowerer only emits loop update for `pos`, not `result`
|
||||
|
||||
**Conclusion**:
|
||||
- **Architecture is sound** - all boxes support multi-carrier
|
||||
- **Pattern2 implementation gap** - Trim optimization doesn't emit body updates for non-position carriers
|
||||
- **Phase 176 scope** - Extend Pattern2 to emit all carrier updates, not just position
|
||||
|
||||
### 4.2 Next Steps
|
||||
|
||||
- **Phase 175-3**: Run PoC test (`test_jsonparser_parse_string_min2.hako`)
|
||||
- **Phase 176**: Add escape sequence handling (`continue` path, Phase 176 scope)
|
||||
- **Phase 177**: Full `_parse_string` with all edge cases
|
||||
|
||||
---
|
||||
|
||||
## 5. Phase 176 で解決済み ✅ (2025-12-08)
|
||||
|
||||
**実装内容**:
|
||||
- Pattern2 lowerer を全キャリア対応に拡張
|
||||
- ヘッダ PHI / ループ更新 / ExitLine で複数キャリアを正しく処理
|
||||
- CarrierUpdateLowerer ヘルパで UpdateExpr → MIR 変換を統一
|
||||
|
||||
**修正されたバグ**:
|
||||
1. Trim pattern で loop_var_name が上書きされていた問題(pattern2_with_break.rs)
|
||||
2. InstructionRewriter が loop_var を exit_bindings から除外していなかった問題
|
||||
|
||||
**テスト結果**:
|
||||
- ✅ 2キャリア E2E テスト全てパス(pos + result)
|
||||
- ✅ 回帰テストなし
|
||||
- ✅ Trim pattern も正常動作
|
||||
|
||||
**次のステップ**: Phase 177 で JsonParser の複雑ループへ拡張
|
||||
|
||||
---
|
||||
|
||||
## 6. References
|
||||
|
||||
- **Phase 170**: LoopUpdateSummary design
|
||||
- **Phase 171**: LoopBodyCarrierPromoter implementation
|
||||
- **Phase 174**: P5 minimal PoC (quote detection only)
|
||||
- **Phase 176**: Pattern2 multi-carrier implementation ([phase176-completion-report.md](phase176-completion-report.md))
|
||||
- **Pattern Space**: [docs/development/current/main/loop_pattern_space.md](loop_pattern_space.md)
|
||||
@ -1,236 +0,0 @@
|
||||
# Phase 176: Pattern2 Lowerer Multi-Carrier Limitations Report
|
||||
|
||||
## Overview
|
||||
|
||||
This document identifies all locations in `loop_with_break_minimal.rs` where the Pattern2 lowerer currently only handles the position carrier (`i`) and ignores additional carriers from `CarrierInfo.carriers`.
|
||||
|
||||
## Limitation Points
|
||||
|
||||
### 1. ValueId Allocation (Line 172-173)
|
||||
|
||||
**Location**: ValueId allocation section
|
||||
**Current Behavior**: Only allocates ValueIds for the position carrier (`i_init`, `i_param`, `i_next`, `i_exit`)
|
||||
**Missing**: No allocation for additional carriers (e.g., `sum_init`, `sum_param`, `sum_next`, `sum_exit`)
|
||||
|
||||
```rust
|
||||
// TODO(Phase 176): Multi-carrier support - currently only allocates position carrier
|
||||
// Future: iterate over CarrierInfo.carriers and allocate for each carrier
|
||||
```
|
||||
|
||||
**Impact**: Cannot represent multi-carrier loops in JoinIR local ValueId space.
|
||||
|
||||
---
|
||||
|
||||
### 2. Main Function Parameters (Line 208-209)
|
||||
|
||||
**Location**: `main()` function creation
|
||||
**Current Behavior**: Only takes `i_init` as parameter
|
||||
**Missing**: Should take all carrier init values as parameters
|
||||
|
||||
```rust
|
||||
// TODO(Phase 176): Multi-carrier support - main() params should include all carriers
|
||||
// Future: params = vec![i_init, sum_init, count_init, ...] from CarrierInfo
|
||||
let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![i_init]);
|
||||
```
|
||||
|
||||
**Impact**: Additional carriers cannot be passed into the JoinIR fragment.
|
||||
|
||||
---
|
||||
|
||||
### 3. Loop Step Call Arguments (Line 214-215)
|
||||
|
||||
**Location**: `main()` → `loop_step()` call
|
||||
**Current Behavior**: Only passes `i_init` to `loop_step()`
|
||||
**Missing**: Should pass all carrier init values
|
||||
|
||||
```rust
|
||||
// TODO(Phase 176): Multi-carrier support - Call args should include all carrier inits
|
||||
// Future: args = vec![i_init, sum_init, count_init, ...] from CarrierInfo
|
||||
main_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_init], // Only position carrier
|
||||
k_next: None,
|
||||
dst: Some(loop_result),
|
||||
});
|
||||
```
|
||||
|
||||
**Impact**: Additional carriers lost at loop entry.
|
||||
|
||||
---
|
||||
|
||||
### 4. Loop Step Function Parameters (Line 234-235)
|
||||
|
||||
**Location**: `loop_step()` function creation
|
||||
**Current Behavior**: Only takes `i_param` as parameter
|
||||
**Missing**: Should take all carrier params
|
||||
|
||||
```rust
|
||||
// TODO(Phase 176): Multi-carrier support - loop_step params should include all carriers
|
||||
// Future: params = vec![i_param, sum_param, count_param, ...] from CarrierInfo
|
||||
let mut loop_step_func = JoinFunction::new(
|
||||
loop_step_id,
|
||||
"loop_step".to_string(),
|
||||
vec![i_param], // Only position carrier
|
||||
);
|
||||
```
|
||||
|
||||
**Impact**: Cannot access additional carriers inside loop body.
|
||||
|
||||
---
|
||||
|
||||
### 5. Natural Exit Jump Arguments (Line 257-258)
|
||||
|
||||
**Location**: Natural exit condition → k_exit jump
|
||||
**Current Behavior**: Only passes `i_param` to k_exit
|
||||
**Missing**: Should pass all carrier values
|
||||
|
||||
```rust
|
||||
// TODO(Phase 176): Multi-carrier support - Jump args should include all carrier values
|
||||
// Future: args = vec![i_param, sum_param, count_param, ...] from CarrierInfo
|
||||
loop_step_func.body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(),
|
||||
args: vec![i_param], // Only position carrier
|
||||
cond: Some(exit_cond),
|
||||
});
|
||||
```
|
||||
|
||||
**Impact**: Additional carrier values lost at natural exit.
|
||||
|
||||
---
|
||||
|
||||
### 6. Break Exit Jump Arguments (Line 272-273)
|
||||
|
||||
**Location**: Break condition → k_exit jump
|
||||
**Current Behavior**: Only passes `i_param` to k_exit
|
||||
**Missing**: Should pass all carrier values
|
||||
|
||||
```rust
|
||||
// TODO(Phase 176): Multi-carrier support - Jump args should include all carrier values
|
||||
// Future: args = vec![i_param, sum_param, count_param, ...] from CarrierInfo
|
||||
loop_step_func.body.push(JoinInst::Jump {
|
||||
cont: k_exit_id.as_cont(),
|
||||
args: vec![i_param], // Only position carrier
|
||||
cond: Some(break_cond_value),
|
||||
});
|
||||
```
|
||||
|
||||
**Impact**: Additional carrier values lost at break exit.
|
||||
|
||||
---
|
||||
|
||||
### 7. Loop Body Updates (Line 284-285)
|
||||
|
||||
**Location**: Loop body computation
|
||||
**Current Behavior**: Only computes `i_next = i + 1`
|
||||
**Missing**: Should compute updates for all carriers
|
||||
|
||||
```rust
|
||||
// TODO(Phase 176): Multi-carrier support - need to compute updates for all carriers
|
||||
// Future: for each carrier in CarrierInfo.carriers, emit carrier_next = carrier_update
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: i_next,
|
||||
op: BinOpKind::Add,
|
||||
lhs: i_param,
|
||||
rhs: const_1,
|
||||
}));
|
||||
```
|
||||
|
||||
**Impact**: Additional carriers cannot be updated in loop body.
|
||||
|
||||
**Note**: This is the HARDEST part - we need actual AST body analysis to determine carrier updates!
|
||||
|
||||
---
|
||||
|
||||
### 8. Tail Call Arguments (Line 304-305)
|
||||
|
||||
**Location**: Tail recursion call to `loop_step()`
|
||||
**Current Behavior**: Only passes `i_next`
|
||||
**Missing**: Should pass all updated carrier values
|
||||
|
||||
```rust
|
||||
// TODO(Phase 176): Multi-carrier support - tail call args should include all updated carriers
|
||||
// Future: args = vec![i_next, sum_next, count_next, ...] from CarrierInfo
|
||||
loop_step_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![i_next], // Only position carrier
|
||||
k_next: None,
|
||||
dst: None,
|
||||
});
|
||||
```
|
||||
|
||||
**Impact**: Additional carrier updates lost in iteration.
|
||||
|
||||
---
|
||||
|
||||
### 9. K_Exit Function Parameters (Line 319-320)
|
||||
|
||||
**Location**: `k_exit()` function creation (Exit PHI)
|
||||
**Current Behavior**: Only takes `i_exit` as parameter
|
||||
**Missing**: Should take all carrier exit values as parameters
|
||||
|
||||
```rust
|
||||
// TODO(Phase 176): Multi-carrier support - k_exit params should include all carrier exits
|
||||
// Future: params = vec![i_exit, sum_exit, count_exit, ...] from CarrierInfo
|
||||
let mut k_exit_func = JoinFunction::new(
|
||||
k_exit_id,
|
||||
"k_exit".to_string(),
|
||||
vec![i_exit], // Only position carrier
|
||||
);
|
||||
```
|
||||
|
||||
**Impact**: Additional carrier exit values cannot be received by Exit PHI.
|
||||
|
||||
---
|
||||
|
||||
### 10. ExitMeta Construction (Line 344-345)
|
||||
|
||||
**Location**: Final ExitMeta return value
|
||||
**Current Behavior**: Only includes position carrier in exit bindings
|
||||
**Missing**: Should include all carrier bindings
|
||||
|
||||
```rust
|
||||
// TODO(Phase 176): Multi-carrier support - ExitMeta should include all carrier bindings
|
||||
// Future: ExitMeta::multiple(vec![(loop_var_name, i_exit), ("sum", sum_exit), ...])
|
||||
let exit_meta = ExitMeta::single(loop_var_name.to_string(), i_exit);
|
||||
```
|
||||
|
||||
**Impact**: Additional carriers not visible to MIR merge layer - no carrier PHIs generated!
|
||||
|
||||
---
|
||||
|
||||
## Summary Statistics
|
||||
|
||||
- **Total Limitation Points**: 10
|
||||
- **Easy Fixes** (iteration over CarrierInfo): 9 points
|
||||
- **Hard Fix** (requires AST body analysis): 1 point (Loop Body Updates)
|
||||
|
||||
## Architecture Note
|
||||
|
||||
The CarrierInfo structure is already multi-carrier ready:
|
||||
|
||||
```rust
|
||||
pub struct CarrierInfo {
|
||||
pub loop_var_name: String, // Position carrier
|
||||
pub loop_var_id: ValueId, // Host ValueId
|
||||
pub carriers: Vec<CarrierVar>, // Additional carriers (THIS IS IGNORED!)
|
||||
pub trim_helper: Option<TrimLoopHelper>,
|
||||
}
|
||||
```
|
||||
|
||||
The problem is that `lower_loop_with_break_minimal()` completely ignores `CarrierInfo.carriers` and only uses `loop_var_name` (passed as a separate string parameter).
|
||||
|
||||
## Next Steps (Phase 176-2/3)
|
||||
|
||||
1. **Phase 176-2**: Fix iteration-based points (points 1-6, 8-10)
|
||||
- Add carrier iteration logic
|
||||
- Extend function params, call args, jump args
|
||||
- Build multi-carrier ExitMeta
|
||||
|
||||
2. **Phase 176-3**: Fix loop body updates (point 7)
|
||||
- Requires AST body analysis
|
||||
- Need to track which carriers are updated by which statements
|
||||
- Most complex part of multi-carrier support
|
||||
|
||||
3. **Integration Test**: Pattern 3 (trim) with Pattern 2 shape
|
||||
- Test case: `loop(pos < len) { if ch == ' ' { break } pos = pos + 1 }`
|
||||
- Verify sum/count carriers survive through break exits
|
||||
@ -1,165 +0,0 @@
|
||||
# Phase 177: Carrier Evolution - min から Production へ
|
||||
|
||||
## 視覚的比較: ループ構造の進化
|
||||
|
||||
### Phase 174: min(1-carrier)
|
||||
```
|
||||
Input: "hello world""
|
||||
^start ^target
|
||||
|
||||
loop(pos < len) {
|
||||
ch = s[pos]
|
||||
if ch == '"' → break ✓
|
||||
else → pos++
|
||||
}
|
||||
|
||||
Carriers: 1
|
||||
- pos (ループ変数)
|
||||
|
||||
Pattern: 4 (Loop + If PHI)
|
||||
Exit: pos = 11
|
||||
```
|
||||
|
||||
### Phase 175/176: min2(2-carrier)
|
||||
```
|
||||
Input: "hello world""
|
||||
^start ^target
|
||||
|
||||
loop(pos < len) {
|
||||
ch = s[pos]
|
||||
if ch == '"' → break ✓
|
||||
else → result += ch
|
||||
pos++
|
||||
}
|
||||
|
||||
Carriers: 2
|
||||
- pos (ループ変数)
|
||||
- result (バッファ)
|
||||
|
||||
Pattern: 4 (Loop + If PHI)
|
||||
Exit: pos = 11, result = "hello world"
|
||||
```
|
||||
|
||||
### Phase 177-A: Simple Case(2-carrier, Production-like)
|
||||
```
|
||||
Input: "hello world""
|
||||
^start ^target
|
||||
|
||||
loop(p < len) {
|
||||
ch = s[p]
|
||||
if ch == '"' → break ✓
|
||||
else → str += ch
|
||||
p++
|
||||
}
|
||||
|
||||
Carriers: 2
|
||||
- p (ループ変数)
|
||||
- str (バッファ)
|
||||
|
||||
Pattern: 4 (Loop + If PHI)
|
||||
Exit: p = 11, str = "hello world"
|
||||
```
|
||||
|
||||
### Phase 178: Escape Handling(2-carrier + continue)
|
||||
```
|
||||
Input: "hello \"world\"""
|
||||
^start ^escape ^target
|
||||
|
||||
loop(p < len) {
|
||||
ch = s[p]
|
||||
if ch == '"' → break ✓
|
||||
if ch == '\\' → str += ch
|
||||
p++
|
||||
str += s[p]
|
||||
p++
|
||||
continue ←← 新要素
|
||||
else → str += ch
|
||||
p++
|
||||
}
|
||||
|
||||
Carriers: 2 (変わらず)
|
||||
- p (ループ変数)
|
||||
- str (バッファ)
|
||||
|
||||
Pattern: 4? (continue 対応は未検証)
|
||||
Exit: p = 15, str = "hello \"world\""
|
||||
```
|
||||
|
||||
### Phase 179: Full Production(2-carrier + early return)
|
||||
```
|
||||
Input: "hello \"world\"""
|
||||
^start ^escape ^target
|
||||
|
||||
loop(p < len) {
|
||||
ch = s[p]
|
||||
if ch == '"' → return MapBox {...} ✓ ←← early return
|
||||
if ch == '\\' → if p+1 >= len → return null ←← error
|
||||
str += ch
|
||||
p++
|
||||
str += s[p]
|
||||
p++
|
||||
continue
|
||||
else → str += ch
|
||||
p++
|
||||
}
|
||||
return null ←← ループ終端エラー
|
||||
|
||||
Carriers: 2 (変わらず)
|
||||
- p (ループ変数)
|
||||
- str (バッファ)
|
||||
|
||||
Pattern: 4? (early return 対応は未検証)
|
||||
Exit: 正常: MapBox, 異常: null
|
||||
```
|
||||
|
||||
## Carrier 安定性分析
|
||||
|
||||
### 重要な発見
|
||||
**Phase 174 → 179 を通じて Carrier 数は安定(1 → 2)**
|
||||
|
||||
| Phase | Carriers | 新要素 | P5昇格候補 |
|
||||
|-------|----------|--------|-----------|
|
||||
| 174 | 1 (pos) | - | なし |
|
||||
| 175/176 | 2 (pos + result) | バッファ追加 | なし |
|
||||
| 177-A | 2 (p + str) | - | なし |
|
||||
| 178 | 2 (p + str) | continue | なし(確認中) |
|
||||
| 179 | 2 (p + str) | early return | なし(確認中) |
|
||||
|
||||
### P5昇格候補の不在
|
||||
**Trim と異なり、`is_ch_match` 相当は不要**
|
||||
|
||||
理由:
|
||||
- Trim: `is_ch_match` が「次も空白か?」を決定(**次の判断に影響**)
|
||||
- _parse_string: `ch == '"'` は「今終了か?」のみ(**次に影響しない**)
|
||||
|
||||
```
|
||||
Trim の制御フロー:
|
||||
is_ch_match = (ch == ' ')
|
||||
if is_ch_match → pos++ → 次も is_ch_match を評価 ←← 連鎖
|
||||
|
||||
_parse_string の制御フロー:
|
||||
if ch == '"' → break → 終了
|
||||
else → str += ch, p++ → 次は独立判断 ←← 連鎖なし
|
||||
```
|
||||
|
||||
## JoinIR Pattern 対応予測
|
||||
|
||||
| Phase | Pattern 候補 | 理由 |
|
||||
|-------|-------------|------|
|
||||
| 177-A | Pattern4 | Loop + If PHI + break(実装済み) |
|
||||
| 178 | Pattern4? | continue は Pattern4-with-continue(要実装確認) |
|
||||
| 179 | Pattern5? | early return は新パターン候補(要設計) |
|
||||
|
||||
## まとめ
|
||||
|
||||
### 段階的検証戦略
|
||||
1. **Phase 177-A**: min2 と同型 → P5 安定性確認
|
||||
2. **Phase 178**: continue 追加 → JoinIR 拡張必要性評価
|
||||
3. **Phase 179**: early return 追加 → Pattern5 設計判断
|
||||
|
||||
### Carrier 設計の教訓
|
||||
- **最小構成で開始**: 1-carrier (Phase 174)
|
||||
- **段階的拡張**: 2-carrier (Phase 175/176)
|
||||
- **Production 適用**: 構造は変えず、制御フローのみ追加(Phase 177+)
|
||||
|
||||
→ **「Carrier 数を固定して制御フローを段階的に複雑化」が正解**
|
||||
@ -1,178 +0,0 @@
|
||||
# Phase 177: _parse_string 本体 JoinIR 適用設計
|
||||
|
||||
## 目的
|
||||
Production `JsonParserBox._parse_string` メソッドに JoinIR P5 パイプラインを適用する。
|
||||
Phase 174/175/176 で確立した「1-carrier → 2-carrier」検証の成果を、実際の JSON パーサーに展開する。
|
||||
|
||||
## 本番ループ構造(lines 144-181)
|
||||
|
||||
### 前提条件
|
||||
- 開始位置 `pos` は `"` を指している(line 145 でチェック)
|
||||
- ループ開始時 `p = pos + 1`(引用符の次の文字から開始)
|
||||
|
||||
### ループ本体(lines 150-178)
|
||||
```hako
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
|
||||
if ch == '"' {
|
||||
// End of string (lines 153-160)
|
||||
local result = new MapBox()
|
||||
result.set("value", me._unescape_string(str))
|
||||
result.set("pos", p + 1)
|
||||
result.set("type", "string")
|
||||
return result // ← Early return (通常の exit)
|
||||
}
|
||||
|
||||
if ch == "\\" {
|
||||
// Escape sequence (lines 162-174)
|
||||
local has_next = 0
|
||||
if p + 1 < s.length() { has_next = 1 }
|
||||
|
||||
if has_next == 0 { return null } // ← Early return (エラー)
|
||||
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
str = str + s.substring(p, p+1)
|
||||
p = p + 1
|
||||
continue // ← ループ継続
|
||||
}
|
||||
|
||||
str = str + ch // 通常文字の追加
|
||||
p = p + 1
|
||||
}
|
||||
|
||||
return null // ← ループ終端に到達(エラー)
|
||||
```
|
||||
|
||||
### 制御フロー分岐
|
||||
1. **終端クォート検出** (`ch == '"'`): 成功 return
|
||||
2. **エスケープ検出** (`ch == "\\"`) + continue: 2文字消費してループ継続
|
||||
3. **通常文字**: バッファ蓄積 + 位置進行
|
||||
4. **ループ終端**: 引用符が閉じていない(エラー)
|
||||
|
||||
## Carriers 分析
|
||||
|
||||
| 変数 | 初期値 | 役割 | 更新式 | P5 昇格対象? | 備考 |
|
||||
|------|--------|------|--------|---------------|------|
|
||||
| `p` | `pos + 1` | カウンタ(位置) | `p = p + 1` | **N(ループ変数)** | 条件式に使用 |
|
||||
| `str` | `""` | バッファ(蓄積) | `str = str + ch` | **N(通常キャリア)** | エスケープ時2回更新 |
|
||||
| `has_next` | - | 一時変数(フラグ) | - | - | ループ内のみ有効 |
|
||||
| `is_escape` | - | (潜在的フラグ) | - | - | 明示的変数なし |
|
||||
|
||||
### 重要な発見
|
||||
- **エスケープ処理は continue 経由**: `p` と `str` を 2 回更新してから continue
|
||||
- **Early return が 2 箇所**: 成功 return (line 159) とエラー return (line 167)
|
||||
- **通常キャリアのみ**: P5 昇格対象(`is_ch_match` 相当)は**不要**
|
||||
|
||||
## min ケースとの差分
|
||||
|
||||
### 共通点
|
||||
| 項目 | min (Phase 174) | min2 (Phase 175) | 本番 (Phase 177) |
|
||||
|------|-----------------|------------------|------------------|
|
||||
| ループ変数 | `pos` | `pos` | `p` |
|
||||
| バッファ | なし | `result` | `str` |
|
||||
| 終端条件 | `ch == '"'` → break | `ch == '"'` → break | `ch == '"'` → return |
|
||||
| 通常処理 | `pos++` | `result += ch; pos++` | `str += ch; p++` |
|
||||
|
||||
### 差分
|
||||
| 項目 | min/min2 | 本番 |
|
||||
|------|----------|------|
|
||||
| 終端処理 | `break` のみ | Early `return` (MapBox 返却) |
|
||||
| エスケープ処理 | なし | `continue` を使った複雑な分岐 |
|
||||
| エラー処理 | なし | ループ内・外に `return null` |
|
||||
|
||||
## 方針決定: 段階的アプローチ
|
||||
|
||||
### Phase 177-A: Simple Case(今回)
|
||||
**対象**: エスケープ**なし**・終端クォート検出のみ
|
||||
- **ループ構造**: min2 と完全同型(`p` + `str` の 2-carrier)
|
||||
- **終端処理**: `break` → 直後に MapBox 構築(return 代替)
|
||||
- **目的**: P5 パイプラインが「2-carrier + break」で動作することを確認
|
||||
|
||||
```hako
|
||||
// Simplified for Phase 177-A
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
if ch == '"' {
|
||||
break // P5: Pattern4 対応(Loop+If PHI merge)
|
||||
} else {
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
}
|
||||
}
|
||||
// break 後: MapBox 構築
|
||||
```
|
||||
|
||||
### Phase 178: Escape Handling(次回)
|
||||
**追加要素**:
|
||||
- `continue` 分岐(エスケープ処理)
|
||||
- Early return(エラーハンドリング)
|
||||
- P5 昇格キャリア候補の検討(`is_escape` フラグ?)
|
||||
|
||||
### Phase 179: Full Production(最終)
|
||||
**統合**:
|
||||
- `_unescape_string()` 呼び出し
|
||||
- 完全なエラーハンドリング
|
||||
- 本番同等の MapBox 返却
|
||||
|
||||
## Phase 177-A 実装計画
|
||||
|
||||
### Test Case: `test_jsonparser_parse_string_simple.hako`
|
||||
```hako
|
||||
// Phase 177-A: Production-like simple case
|
||||
static box JsonParserStringTest3 {
|
||||
parse_string_simple() {
|
||||
local s = "hello world\""
|
||||
local p = 0 // pos + 1 相当(簡略化のため 0 から開始)
|
||||
local str = ""
|
||||
local len = s.length()
|
||||
|
||||
// 2-carrier loop (min2 と同型)
|
||||
loop(p < len) {
|
||||
local ch = s.substring(p, p+1)
|
||||
if ch == "\"" {
|
||||
break
|
||||
} else {
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Post-loop: 結果出力(MapBox 構築の代替)
|
||||
print("Parsed string: ")
|
||||
print(str)
|
||||
print(", final pos: ")
|
||||
print(p)
|
||||
}
|
||||
|
||||
main() {
|
||||
me.parse_string_simple()
|
||||
return "OK"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 期待される MIR 構造
|
||||
- **Pattern4 検出**: Loop + If PHI merge(Phase 170 実装済み)
|
||||
- **2 carriers**: `p` (ループ変数) + `str` (バッファ)
|
||||
- **Exit PHI**: ループ後の `p` と `str` が正しく伝播
|
||||
|
||||
### 検証項目
|
||||
1. ✅ P5 パイプライン通過(JoinIR → MIR)
|
||||
2. ✅ 2-carrier の正しい伝播(`p` と `str`)
|
||||
3. ✅ `break` 後の変数値が正しく使用可能
|
||||
|
||||
## 成功基準
|
||||
- [ ] `test_jsonparser_parse_string_simple.hako` が実行成功
|
||||
- [ ] MIR ダンプで Pattern4 検出確認
|
||||
- [ ] 出力: `Parsed string: hello world, final pos: 11`
|
||||
|
||||
## 次のステップ(Phase 178)
|
||||
- `continue` 分岐の追加(エスケープ処理)
|
||||
- P5 昇格キャリア候補の検討(必要性を再評価)
|
||||
- Early return 対応(JoinIR での処理検討)
|
||||
|
||||
## まとめ
|
||||
**Phase 177-A では、min2 と同型の Simple Case を Production 環境に適用する。**
|
||||
エスケープ処理は Phase 178 以降に回し、まず「2-carrier + break」の動作確認を優先する。
|
||||
@ -1,71 +0,0 @@
|
||||
# Phase 178: LoopUpdateAnalyzer String Detection
|
||||
|
||||
## Summary
|
||||
|
||||
Phase 178 extends `LoopUpdateAnalyzer` to detect string/complex carrier updates,
|
||||
enabling Fail-Fast behavior for unsupported patterns.
|
||||
|
||||
## Changes
|
||||
|
||||
### 1. UpdateRhs Enum Extension (`loop_update_analyzer.rs`)
|
||||
|
||||
Added two new variants:
|
||||
- `StringLiteral(String)` - for `result = result + "x"` patterns
|
||||
- `Other` - for method calls and complex expressions
|
||||
|
||||
### 2. analyze_rhs Extension
|
||||
|
||||
Extended to detect:
|
||||
- String literals: `ASTNode::Literal { value: LiteralValue::String(_) }`
|
||||
- Method calls: `ASTNode::MethodCall { .. }`
|
||||
- Other complex expressions: `ASTNode::Call`, `ASTNode::BinaryOp`, etc.
|
||||
|
||||
### 3. Pattern 2/4 can_lower Updates
|
||||
|
||||
Both `pattern2_with_break.rs` and `pattern4_with_continue.rs` now check for
|
||||
string/complex updates in `can_lower()` and return `false` if detected.
|
||||
|
||||
This triggers a clear error message instead of silent incorrect behavior.
|
||||
|
||||
### 4. Legacy Fallback Comment Fixes
|
||||
|
||||
Updated misleading comments about "legacy fallback" - LoopBuilder was removed
|
||||
in Phase 187-2 and all loops must use JoinIR.
|
||||
|
||||
## Behavior
|
||||
|
||||
When a loop contains string concatenation like:
|
||||
```nyash
|
||||
loop(i < limit) {
|
||||
result = result + "x" // String update
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
|
||||
Phase 178 now produces a clear error:
|
||||
```
|
||||
[pattern2/can_lower] Phase 178: String/complex update detected, rejecting Pattern 2 (unsupported)
|
||||
[ERROR] MIR compilation error: [joinir/freeze] Loop lowering failed:
|
||||
JoinIR does not support this pattern, and LoopBuilder has been removed.
|
||||
```
|
||||
|
||||
## Test Results
|
||||
|
||||
- P1 (simple while): OK
|
||||
- P2 (break + int carriers): OK
|
||||
- P4 (continue + multi-carrier): OK
|
||||
- String loops: Fail-Fast with clear error
|
||||
|
||||
## Known Issues
|
||||
|
||||
- **79 global test failures**: Pre-existing issue, NOT caused by Phase 178
|
||||
- Confirmed by `git stash` test - failures exist in HEAD~1
|
||||
- Tracked separately from Phase 178
|
||||
|
||||
## Future Work
|
||||
|
||||
To support string loops, either:
|
||||
1. Add JoinIR instructions for string concatenation (Option A)
|
||||
2. Process loop body statements in MIR alongside JoinIR control flow (Option B)
|
||||
|
||||
Phase 178 provides the detection foundation for future string support.
|
||||
@ -1,374 +0,0 @@
|
||||
# Phase 179-B: Generic Pattern Framework Design
|
||||
|
||||
**Status**: Implementation in progress
|
||||
**Related**: Phase 33-22 (CommonPatternInitializer), Phase 171-172 (Builders)
|
||||
|
||||
## Objective
|
||||
|
||||
Unify the preprocessing pipeline for Patterns 1-4 by creating a `PatternPipelineContext` that consolidates all pattern initialization logic into a single, reusable "解析済みコンテキスト箱" (analyzed context box).
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### Pattern Initialization Breakdown (Lines)
|
||||
|
||||
| Pattern | File | Total Lines | Preprocessing | Lowering | Target |
|
||||
|---------|------|-------------|---------------|----------|--------|
|
||||
| Pattern 1 | pattern1_minimal.rs | 139 | ~61 | ~78 | ~15 |
|
||||
| Pattern 3 | pattern3_with_if_phi.rs | 169 | ~151 | ~18 | ~30 |
|
||||
| Pattern 2 | pattern2_with_break.rs | 517 | ~437 | ~80 | ~80 |
|
||||
| Pattern 4 | pattern4_with_continue.rs | 433 | ~363 | ~70 | ~70 |
|
||||
|
||||
**Total Reduction Target**: ~1012 lines → ~195 lines (**81% reduction**)
|
||||
|
||||
### Existing Infrastructure
|
||||
|
||||
We already have excellent modular components:
|
||||
- **CommonPatternInitializer**: Loop variable extraction + CarrierInfo initialization
|
||||
- **LoopScopeShapeBuilder**: LoopScopeShape construction (with/without body_locals)
|
||||
- **ConditionEnvBuilder**: ConditionEnv + ConditionBinding construction
|
||||
- **Pattern4CarrierAnalyzer**: Carrier filtering and update analysis
|
||||
|
||||
## Design: PatternPipelineContext
|
||||
|
||||
### Core Principles
|
||||
|
||||
1. **Pure Analysis Container**: Only holds preprocessing results, no JoinIR emission
|
||||
2. **Analyzer-Only Dependencies**: Only depends on analyzer boxes, never lowering logic
|
||||
3. **Pattern-Specific Variants**: Use `Option<T>` for pattern-specific data
|
||||
|
||||
### Struct Definition
|
||||
|
||||
```rust
|
||||
/// Phase 179-B: Unified Pattern Pipeline Context
|
||||
///
|
||||
/// Pure "解析済みコンテキスト箱" - holds only preprocessing results.
|
||||
/// JoinIR emission and PHI assembly remain in existing lowerers.
|
||||
///
|
||||
/// # Design Constraints
|
||||
///
|
||||
/// - **Analyzer-only dependencies**: Never depends on lowering logic
|
||||
/// - **No emission**: No JoinIR/MIR generation in this context
|
||||
/// - **Pattern variants**: Pattern-specific data stored in Option<T>
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// ```rust
|
||||
/// let ctx = build_pattern_context(
|
||||
/// builder,
|
||||
/// condition,
|
||||
/// body,
|
||||
/// PatternVariant::Pattern1,
|
||||
/// )?;
|
||||
///
|
||||
/// // Use preprocessed data for lowering
|
||||
/// let join_module = lower_simple_while_minimal(ctx.loop_scope)?;
|
||||
/// ```
|
||||
pub struct PatternPipelineContext {
|
||||
// === Common Data (All Patterns) ===
|
||||
|
||||
/// Loop variable name (e.g., "i")
|
||||
pub loop_var_name: String,
|
||||
|
||||
/// Loop variable HOST ValueId
|
||||
pub loop_var_id: ValueId,
|
||||
|
||||
/// Carrier information (loop variable + carriers)
|
||||
pub carrier_info: CarrierInfo,
|
||||
|
||||
/// Loop scope shape (header/body/latch/exit structure)
|
||||
pub loop_scope: LoopScopeShape,
|
||||
|
||||
// === Pattern 2/4: Break/Continue Condition ===
|
||||
|
||||
/// Condition environment (variable → JoinIR ValueId mapping)
|
||||
/// Used by Pattern 2 (break condition) and Pattern 4 (continue condition)
|
||||
pub condition_env: Option<ConditionEnv>,
|
||||
|
||||
/// Condition bindings (HOST↔JoinIR value mappings)
|
||||
/// Used by Pattern 2 and Pattern 4
|
||||
pub condition_bindings: Option<Vec<ConditionBinding>>,
|
||||
|
||||
/// Carrier update expressions (variable → UpdateExpr)
|
||||
/// Used by Pattern 2 (multi-carrier) and Pattern 4 (Select-based updates)
|
||||
pub carrier_updates: Option<HashMap<String, UpdateExpr>>,
|
||||
|
||||
// === Pattern 2/4: Trim Pattern Support ===
|
||||
|
||||
/// Trim loop helper (if Trim pattern detected during promotion)
|
||||
/// Used by Pattern 2 (string trim) - Pattern 4 support TBD
|
||||
pub trim_helper: Option<TrimLoopHelper>,
|
||||
|
||||
// === Pattern 2: Break Condition ===
|
||||
|
||||
/// Effective break condition (may be modified for Trim pattern)
|
||||
/// Used only by Pattern 2
|
||||
pub break_condition: Option<ASTNode>,
|
||||
}
|
||||
|
||||
/// Pattern variant selector
|
||||
pub enum PatternVariant {
|
||||
Pattern1, // Simple while loop
|
||||
Pattern2, // Loop with break
|
||||
Pattern3, // Loop with if-else PHI
|
||||
Pattern4, // Loop with continue
|
||||
}
|
||||
```
|
||||
|
||||
### Pipeline Function
|
||||
|
||||
```rust
|
||||
/// Build pattern preprocessing context
|
||||
///
|
||||
/// This consolidates all preprocessing steps shared by Patterns 1-4:
|
||||
/// 1. Loop variable extraction (CommonPatternInitializer)
|
||||
/// 2. LoopScopeShape construction (LoopScopeShapeBuilder)
|
||||
/// 3. Pattern-specific analysis (ConditionEnv, carrier updates, etc.)
|
||||
/// 4. Trim pattern promotion (if applicable)
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `builder` - MirBuilder instance
|
||||
/// * `condition` - Loop condition AST node
|
||||
/// * `body` - Loop body AST nodes
|
||||
/// * `variant` - Pattern variant selector
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// PatternPipelineContext with all preprocessing results
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns error if:
|
||||
/// - Loop variable not found in variable_map
|
||||
/// - Condition variable not found (Pattern 2/4)
|
||||
/// - Trim pattern promotion fails (Pattern 2/4)
|
||||
pub fn build_pattern_context(
|
||||
builder: &mut MirBuilder,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
variant: PatternVariant,
|
||||
) -> Result<PatternPipelineContext, String> {
|
||||
// Step 1: Common initialization
|
||||
let (loop_var_name, loop_var_id, mut carrier_info) =
|
||||
CommonPatternInitializer::initialize_pattern(
|
||||
builder,
|
||||
condition,
|
||||
&builder.variable_map,
|
||||
None,
|
||||
)?;
|
||||
|
||||
// Step 2: Build LoopScopeShape
|
||||
let loop_scope = match variant {
|
||||
PatternVariant::Pattern1 | PatternVariant::Pattern3 => {
|
||||
// Pattern 1, 3: No body_locals needed
|
||||
LoopScopeShapeBuilder::empty_body_locals(
|
||||
BasicBlockId(0),
|
||||
BasicBlockId(0),
|
||||
BasicBlockId(0),
|
||||
BasicBlockId(0),
|
||||
BTreeSet::new(),
|
||||
)
|
||||
}
|
||||
PatternVariant::Pattern2 | PatternVariant::Pattern4 => {
|
||||
// Pattern 2, 4: Extract body_locals for Trim support
|
||||
LoopScopeShapeBuilder::with_body_locals(
|
||||
BasicBlockId(0),
|
||||
BasicBlockId(0),
|
||||
BasicBlockId(0),
|
||||
BasicBlockId(0),
|
||||
BTreeSet::new(),
|
||||
body,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Step 3: Pattern-specific preprocessing
|
||||
let (condition_env, condition_bindings, carrier_updates, trim_helper, break_condition) =
|
||||
match variant {
|
||||
PatternVariant::Pattern1 => {
|
||||
// Pattern 1: No additional preprocessing
|
||||
(None, None, None, None, None)
|
||||
}
|
||||
PatternVariant::Pattern3 => {
|
||||
// Pattern 3: No condition env, but has carrier updates (for if-else PHI)
|
||||
// TODO: Pattern 3 analyzer integration
|
||||
(None, None, None, None, None)
|
||||
}
|
||||
PatternVariant::Pattern2 => {
|
||||
// Pattern 2: Full preprocessing (break condition, carriers, Trim)
|
||||
build_pattern2_context(builder, condition, body, &loop_var_name, loop_var_id, &mut carrier_info, &loop_scope)?
|
||||
}
|
||||
PatternVariant::Pattern4 => {
|
||||
// Pattern 4: Similar to Pattern 2 but with continue semantics
|
||||
build_pattern4_context(builder, condition, body, &loop_var_name, loop_var_id, &mut carrier_info, &loop_scope)?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(PatternPipelineContext {
|
||||
loop_var_name,
|
||||
loop_var_id,
|
||||
carrier_info,
|
||||
loop_scope,
|
||||
condition_env,
|
||||
condition_bindings,
|
||||
carrier_updates,
|
||||
trim_helper,
|
||||
break_condition,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Integration Strategy
|
||||
|
||||
### Migration Order: P1 → P3 → P2 → P4
|
||||
|
||||
1. **Pattern 1** (Minimal complexity)
|
||||
- Zero dependencies on ConditionEnv
|
||||
- Only needs: loop_var_name, loop_var_id, loop_scope
|
||||
- Best for testing the framework
|
||||
|
||||
2. **Pattern 3** (PHI without break/continue)
|
||||
- Adds carrier handling for if-else PHI
|
||||
- No break/continue complexity
|
||||
- Tests carrier_info flow
|
||||
|
||||
3. **Pattern 2** (Most complex)
|
||||
- Full feature set: break + Trim + multi-carrier
|
||||
- Tests all context fields
|
||||
- Validates Trim pattern integration
|
||||
|
||||
4. **Pattern 4** (Continue semantics)
|
||||
- Similar to Pattern 2 but Select-based
|
||||
- Final validation of framework completeness
|
||||
|
||||
### Pattern 1 Migration Example
|
||||
|
||||
**Before** (61 lines of preprocessing):
|
||||
```rust
|
||||
pub(in crate::mir::builder) fn cf_loop_pattern1_minimal(
|
||||
&mut self,
|
||||
condition: &ASTNode,
|
||||
_body: &[ASTNode],
|
||||
_func_name: &str,
|
||||
debug: bool,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
// ... 61 lines of initialization ...
|
||||
|
||||
let join_module = lower_simple_while_minimal(scope)?;
|
||||
|
||||
// ... merge and return ...
|
||||
}
|
||||
```
|
||||
|
||||
**After** (~15 lines):
|
||||
```rust
|
||||
pub(in crate::mir::builder) fn cf_loop_pattern1_minimal(
|
||||
&mut self,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
_func_name: &str,
|
||||
debug: bool,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
// Step 1: Build preprocessing context
|
||||
let ctx = build_pattern_context(
|
||||
self,
|
||||
condition,
|
||||
body,
|
||||
PatternVariant::Pattern1,
|
||||
)?;
|
||||
|
||||
// Step 2: Call lowerer with preprocessed data
|
||||
let join_module = lower_simple_while_minimal(ctx.loop_scope)?;
|
||||
|
||||
// Step 3: Create boundary from context
|
||||
let boundary = JoinInlineBoundaryBuilder::new()
|
||||
.with_inputs(vec![ValueId(0)], vec![ctx.loop_var_id])
|
||||
.with_loop_var_name(Some(ctx.loop_var_name.clone()))
|
||||
.build();
|
||||
|
||||
// Step 4: Merge and return
|
||||
JoinIRConversionPipeline::execute(self, join_module, Some(&boundary), "pattern1", debug)?;
|
||||
Ok(Some(emit_void(self)))
|
||||
}
|
||||
```
|
||||
|
||||
## Trim/P5 Special Case Handling
|
||||
|
||||
### Current Situation
|
||||
|
||||
Pattern 2 has complex Trim pattern logic (~100 lines) that:
|
||||
1. Detects Trim pattern via LoopBodyCarrierPromoter
|
||||
2. Generates initial whitespace check
|
||||
3. Modifies break condition
|
||||
4. Adds carrier to ConditionEnv
|
||||
|
||||
### Phase 179-B Strategy
|
||||
|
||||
**Include in PatternPipelineContext**:
|
||||
- Trim detection and validation (LoopBodyCarrierPromoter)
|
||||
- TrimLoopHelper storage
|
||||
- Modified break condition
|
||||
|
||||
**Keep in Pattern 2 lowerer**:
|
||||
- Initial whitespace check emission (MIR instruction generation)
|
||||
- This is "lowering" not "analysis"
|
||||
|
||||
**Future Phase 180+ (Trim-specific refactor)**:
|
||||
- Move Trim lowering logic to `trim_loop_lowering.rs`
|
||||
- Create `TrimPatternLowerer` box
|
||||
- PatternPipelineContext just provides the analysis result
|
||||
|
||||
## Benefits
|
||||
|
||||
### Code Reduction
|
||||
- **Pattern 1**: 61 → ~15 lines (75% reduction)
|
||||
- **Pattern 3**: 151 → ~30 lines (80% reduction)
|
||||
- **Pattern 2**: 517 → ~80 lines (85% reduction)
|
||||
- **Pattern 4**: 433 → ~70 lines (84% reduction)
|
||||
- **Total**: 1162 → ~195 lines (**83% reduction**)
|
||||
|
||||
### Maintainability
|
||||
- Single source of truth for preprocessing
|
||||
- Easier to add new patterns
|
||||
- Clear separation: analysis vs lowering
|
||||
|
||||
### Testability
|
||||
- Can test preprocessing independently
|
||||
- Pattern-specific logic isolated
|
||||
- Easier to mock/stub for unit tests
|
||||
|
||||
### Consistency
|
||||
- All patterns use same initialization flow
|
||||
- Consistent error messages
|
||||
- Uniform trace/debug output
|
||||
|
||||
## Non-Goals (Out of Scope)
|
||||
|
||||
❌ **Not included in PatternPipelineContext**:
|
||||
- JoinIR emission (remains in existing lowerers)
|
||||
- PHI assembly (remains in existing lowerers)
|
||||
- MIR instruction generation (remains in existing lowerers)
|
||||
- Block merging (remains in JoinIRConversionPipeline)
|
||||
|
||||
✅ **Only preprocessing**:
|
||||
- Variable extraction
|
||||
- Carrier analysis
|
||||
- Condition environment setup
|
||||
- Trim pattern detection
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Task 179-B-1: Design document (this file)
|
||||
- [ ] Task 179-B-2: PatternPipelineContext implementation
|
||||
- [ ] Task 179-B-3: Pattern 1 migration
|
||||
- [ ] Task 179-B-4: Pattern 3 migration
|
||||
- [ ] Task 179-B-5: Pattern 2 migration
|
||||
- [ ] Task 179-B-6: Pattern 4 migration
|
||||
- [ ] Task 179-B-7: Tests and documentation update
|
||||
|
||||
## References
|
||||
|
||||
- **CommonPatternInitializer**: `src/mir/builder/control_flow/joinir/patterns/common_init.rs`
|
||||
- **LoopScopeShapeBuilder**: `src/mir/builder/control_flow/joinir/patterns/loop_scope_shape_builder.rs`
|
||||
- **ConditionEnvBuilder**: `src/mir/builder/control_flow/joinir/patterns/condition_env_builder.rs`
|
||||
- **JoinIRConversionPipeline**: `src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs`
|
||||
@ -1,323 +0,0 @@
|
||||
# Phase 180: Trim/CharComparison P5 Submodule Design
|
||||
|
||||
**Status**: Design Complete
|
||||
**Date**: 2025-12-08
|
||||
**Author**: Claude Code
|
||||
|
||||
## Overview
|
||||
|
||||
Phase 180 extracts Trim/P5 (Pattern 5) specific logic from Pattern2 and Pattern4 into a dedicated submodule, improving code organization and maintainability.
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### Trim/P5 Components Currently Used
|
||||
|
||||
#### 1. **LoopConditionScopeBox** (`src/mir/loop_pattern_detection/loop_condition_scope.rs`)
|
||||
- **Purpose**: Detects LoopBodyLocal variables in condition scope
|
||||
- **Usage**: Called by Pattern2/Pattern4 to identify variables that need promotion
|
||||
- **Current Location**: `loop_pattern_detection` module
|
||||
- **Action**: Keep in current location (detection, not lowering)
|
||||
|
||||
#### 2. **LoopBodyCarrierPromoter** (`src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs`)
|
||||
- **Purpose**: Promotes LoopBodyLocal variables to carriers
|
||||
- **Returns**: `TrimPatternInfo` with carrier metadata
|
||||
- **Current Location**: `loop_pattern_detection` module
|
||||
- **Action**: Keep in current location (pattern detection/analysis)
|
||||
|
||||
#### 3. **TrimLoopHelper** (`src/mir/loop_pattern_detection/trim_loop_helper.rs`)
|
||||
- **Purpose**: Helper for Trim pattern lowering
|
||||
- **Contains**: Pattern-specific logic (whitespace chars, carrier name, etc.)
|
||||
- **Current Location**: `loop_pattern_detection` module
|
||||
- **Action**: Keep in current location (shared data structure)
|
||||
|
||||
#### 4. **TrimPatternLowerer** (`src/mir/builder/control_flow/joinir/patterns/trim_pattern_lowerer.rs`)
|
||||
- **Purpose**: JoinIR-specific Trim lowering logic
|
||||
- **Functions**:
|
||||
- `generate_trim_break_condition()` - Creates `!is_carrier` break condition
|
||||
- `add_to_condition_env()` - Adds carrier to ConditionEnv
|
||||
- **Current Location**: `joinir/patterns` module
|
||||
- **Action**: Move to new `trim_loop_lowering.rs` module
|
||||
|
||||
#### 5. **TrimPatternValidator** (`src/mir/builder/control_flow/joinir/patterns/trim_pattern_validator.rs`)
|
||||
- **Purpose**: Validates and extracts Trim pattern structure
|
||||
- **Functions**:
|
||||
- `extract_substring_args()` - Extracts substring pattern
|
||||
- `emit_whitespace_check()` - Generates whitespace comparison
|
||||
- **Current Location**: `joinir/patterns` module
|
||||
- **Action**: Move to new `trim_loop_lowering.rs` module
|
||||
|
||||
#### 6. **Pattern2/Pattern4 Inline Trim Logic**
|
||||
- **Location**: Lines 180-340 in `pattern2_with_break.rs`
|
||||
- **Location**: Lines 280+ in `pattern4_with_continue.rs`
|
||||
- **Logic**:
|
||||
- LoopBodyCarrierPromoter invocation
|
||||
- TrimPatternValidator calls
|
||||
- Carrier initialization code generation
|
||||
- Break condition replacement
|
||||
- ConditionEnv manipulation
|
||||
- **Action**: Extract to `TrimLoopLowerer::try_lower_trim_like_loop()`
|
||||
|
||||
## New Module Design
|
||||
|
||||
### Module Location
|
||||
|
||||
```
|
||||
src/mir/join_ir/lowering/trim_loop_lowering.rs
|
||||
```
|
||||
|
||||
### Module Structure
|
||||
|
||||
```rust
|
||||
//! Phase 180: Trim/P5 Dedicated Lowering Module
|
||||
//!
|
||||
//! Consolidates all Trim pattern lowering logic from Pattern2/4.
|
||||
//!
|
||||
//! ## Responsibilities
|
||||
//! - Detect Trim-like loops
|
||||
//! - Promote LoopBodyLocal variables to carriers
|
||||
//! - Generate carrier initialization code
|
||||
//! - Replace break conditions
|
||||
//! - Setup ConditionEnv bindings
|
||||
|
||||
use crate::mir::loop_pattern_detection::{
|
||||
LoopConditionScopeBox,
|
||||
loop_body_carrier_promoter::{LoopBodyCarrierPromoter, PromotionRequest},
|
||||
trim_loop_helper::TrimLoopHelper,
|
||||
};
|
||||
use crate::mir::join_ir::lowering::{
|
||||
carrier_info::CarrierInfo,
|
||||
condition_env::ConditionBinding,
|
||||
};
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::ValueId;
|
||||
use crate::ast::ASTNode;
|
||||
|
||||
pub struct TrimLoopLowerer;
|
||||
|
||||
/// Result of Trim lowering preprocessing
|
||||
pub struct TrimLoweringResult {
|
||||
/// Replaced break condition (e.g., `!is_carrier`)
|
||||
pub condition: ASTNode,
|
||||
|
||||
/// Updated carrier info with promoted carrier
|
||||
pub carrier_info: CarrierInfo,
|
||||
|
||||
/// Updated condition environment bindings
|
||||
pub condition_bindings: Vec<ConditionBinding>,
|
||||
|
||||
/// Trim helper for pattern-specific operations
|
||||
pub trim_helper: TrimLoopHelper,
|
||||
}
|
||||
|
||||
impl TrimLoopLowerer {
|
||||
/// Try to lower a Trim-like loop
|
||||
///
|
||||
/// Returns:
|
||||
/// - `Some(TrimLoweringResult)` if Trim pattern detected and lowered
|
||||
/// - `None` if not a Trim pattern (normal loop)
|
||||
/// - `Err` if Trim pattern detected but lowering failed
|
||||
pub fn try_lower_trim_like_loop(
|
||||
builder: &mut MirBuilder,
|
||||
scope: &LoopScopeShape,
|
||||
loop_cond: &ASTNode,
|
||||
break_cond: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
loop_var_name: &str,
|
||||
carrier_info: &mut CarrierInfo,
|
||||
alloc_join_value: &mut dyn FnMut() -> ValueId,
|
||||
) -> Result<Option<TrimLoweringResult>, String> {
|
||||
// Implementation will consolidate Pattern2/4 logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Refactoring Plan
|
||||
|
||||
### Phase 1: Module Creation (Task 180-2)
|
||||
|
||||
1. Create `src/mir/join_ir/lowering/trim_loop_lowering.rs`
|
||||
2. Add to `src/mir/join_ir/lowering/mod.rs`
|
||||
3. Implement skeleton with stub functions
|
||||
|
||||
### Phase 2: Logic Extraction (Task 180-3)
|
||||
|
||||
#### From Pattern2 (lines 180-340):
|
||||
|
||||
**Extract to `try_lower_trim_like_loop()`:**
|
||||
1. LoopConditionScopeBox analysis (lines 189-200)
|
||||
2. LoopBodyCarrierPromoter invocation (lines 201-240)
|
||||
3. TrimPatternValidator calls (lines 244-340)
|
||||
4. Carrier initialization code generation (lines 267-313)
|
||||
5. ConditionEnv binding setup (lines 345-377)
|
||||
|
||||
**Pattern2 New Code (~20 lines):**
|
||||
```rust
|
||||
// Trim/P5 processing delegation
|
||||
if let Some(trim_result) = TrimLoopLowerer::try_lower_trim_like_loop(
|
||||
self,
|
||||
&scope,
|
||||
condition,
|
||||
&break_condition_node,
|
||||
_body,
|
||||
&loop_var_name,
|
||||
&mut carrier_info,
|
||||
&mut alloc_join_value,
|
||||
)? {
|
||||
effective_break_condition = trim_result.condition.clone();
|
||||
condition_bindings.extend(trim_result.condition_bindings);
|
||||
carrier_info = trim_result.carrier_info;
|
||||
}
|
||||
```
|
||||
|
||||
**Lines Removed**: ~160 lines
|
||||
**Lines Added**: ~20 lines
|
||||
**Net Reduction**: -140 lines in Pattern2
|
||||
|
||||
### Phase 3: Pattern4 Integration (Task 180-4)
|
||||
|
||||
**Current Pattern4 Trim Logic** (lines 280-306):
|
||||
- Same LoopBodyCarrierPromoter call
|
||||
- Trim pattern safety check
|
||||
- Error handling
|
||||
|
||||
**Action**: Replace with same `TrimLoopLowerer::try_lower_trim_like_loop()` call
|
||||
|
||||
**Estimated Impact**:
|
||||
- Lines removed: ~30 lines
|
||||
- Lines added: ~15 lines
|
||||
- Net reduction: -15 lines
|
||||
|
||||
### Phase 4: Module Consolidation
|
||||
|
||||
**Move to `trim_loop_lowering.rs`:**
|
||||
1. `TrimPatternLowerer` (100+ lines)
|
||||
2. `TrimPatternValidator` (150+ lines)
|
||||
3. Inline Trim logic from Pattern2 (~160 lines)
|
||||
4. Inline Trim logic from Pattern4 (~30 lines)
|
||||
|
||||
**Total New Module Size**: ~450 lines (well-scoped, single responsibility)
|
||||
|
||||
## Benefits
|
||||
|
||||
### 1. **Single Responsibility**
|
||||
- Pattern2/4 focus on generic loop lowering
|
||||
- Trim logic isolated in dedicated module
|
||||
|
||||
### 2. **Reusability**
|
||||
- Same Trim lowering for Pattern2, Pattern4, future patterns
|
||||
- No code duplication
|
||||
|
||||
### 3. **Testability**
|
||||
- Trim logic can be unit tested independently
|
||||
- Pattern2/4 tests focus on core loop logic
|
||||
|
||||
### 4. **Maintainability**
|
||||
- Trim pattern changes touch only one module
|
||||
- Clear boundary between generic and pattern-specific logic
|
||||
|
||||
### 5. **Code Size Reduction**
|
||||
- Pattern2: -140 lines (511 → 371 lines)
|
||||
- Pattern4: -15 lines (smaller impact)
|
||||
- Overall: Better organized, easier to navigate
|
||||
|
||||
## Implementation Safety
|
||||
|
||||
### Conservative Approach
|
||||
|
||||
1. **No Behavior Changes**: Refactoring only, same logic flow
|
||||
2. **Incremental**: One pattern at a time (Pattern2 first)
|
||||
3. **Test Coverage**: Verify existing Trim tests still pass
|
||||
4. **Commit Per Task**: Easy rollback if issues arise
|
||||
|
||||
### Test Strategy
|
||||
|
||||
**Existing Tests to Verify**:
|
||||
```bash
|
||||
# Trim pattern tests
|
||||
cargo test --release --lib trim
|
||||
|
||||
# JsonParser tests (uses Trim pattern)
|
||||
./target/release/hakorune apps/tests/test_jsonparser_skip_whitespace.hako
|
||||
|
||||
# Pattern2 tests
|
||||
cargo test --release --lib pattern2
|
||||
|
||||
# Pattern4 tests
|
||||
cargo test --release --lib pattern4
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Keep in Current Locations
|
||||
- `LoopConditionScopeBox` - Detection logic
|
||||
- `LoopBodyCarrierPromoter` - Promotion engine
|
||||
- `TrimLoopHelper` - Shared data structure
|
||||
|
||||
### Move to New Module
|
||||
- `TrimPatternLowerer` - JoinIR lowering
|
||||
- `TrimPatternValidator` - Pattern validation
|
||||
- Inline Trim logic from Pattern2/4
|
||||
|
||||
## Architecture Update
|
||||
|
||||
**Update**: `joinir-architecture-overview.md`
|
||||
|
||||
Add new section:
|
||||
```markdown
|
||||
### TrimLoopLowerer (P5 Dedicated Module)
|
||||
|
||||
**Location**: `src/mir/join_ir/lowering/trim_loop_lowering.rs`
|
||||
|
||||
**Purpose**: Dedicated lowering for Trim/CharComparison patterns (Pattern 5)
|
||||
|
||||
**Components**:
|
||||
- `TrimLoopLowerer::try_lower_trim_like_loop()` - Main entry point
|
||||
- `TrimPatternLowerer` - JoinIR condition generation
|
||||
- `TrimPatternValidator` - Pattern structure validation
|
||||
|
||||
**Used By**: Pattern2, Pattern4 (and future patterns)
|
||||
```
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. ✅ All existing Trim tests pass
|
||||
2. ✅ Pattern2/4 tests pass unchanged
|
||||
3. ✅ Build with 0 errors (warnings acceptable)
|
||||
4. ✅ Code size reduction achieved (-135 lines in Pattern2)
|
||||
5. ✅ Documentation updated
|
||||
6. ✅ Each task committed separately
|
||||
|
||||
## Pattern4 Analysis
|
||||
|
||||
**Finding**: Pattern4 has Trim detection logic (lines 280-318), but it only validates and returns an error:
|
||||
|
||||
```rust
|
||||
// Phase 171-impl-Trim: Validation successful!
|
||||
// Phase 172+ will implement the actual JoinIR generation for Trim patterns
|
||||
// For now, return an informative message that the pattern is recognized but not yet lowered
|
||||
return Err(format!(
|
||||
"[cf_loop/pattern4] ✅ Trim pattern validation successful! \
|
||||
Carrier '{}' ready for Phase 172 implementation. \
|
||||
(Pattern detection: PASS, Safety check: PASS, JoinIR lowering: TODO)",
|
||||
helper.carrier_name
|
||||
));
|
||||
```
|
||||
|
||||
**Decision**: Skip Pattern4 refactoring for now. The Trim logic in Pattern4 doesn't do actual lowering, just detection + error. When Phase 172+ implements Pattern4 Trim lowering, it can use TrimLoopLowerer directly.
|
||||
|
||||
## Timeline
|
||||
|
||||
- **Task 180-1**: Design document (this file) - 15 minutes ✅
|
||||
- **Task 180-2**: Skeleton creation - 10 minutes ✅
|
||||
- **Task 180-3**: Pattern2 refactoring - 30 minutes ✅
|
||||
- **Task 180-4**: Pattern4 refactoring - SKIPPED (lines 280-318 just return error, no actual lowering)
|
||||
- **Task 180-5**: Testing and docs - 20 minutes
|
||||
|
||||
**Total Estimated Time**: 75 minutes (Pattern4 skipped)
|
||||
|
||||
## Notes
|
||||
|
||||
- This refactoring does NOT change any behavior
|
||||
- Focus is on code organization and maintainability
|
||||
- Trim pattern logic remains identical, just relocated
|
||||
- Future Phase 181+ can build on this clean foundation
|
||||
@ -1,417 +0,0 @@
|
||||
# Phase 182: CharComparison ループの汎用化(設計・命名調整)
|
||||
|
||||
## 概要
|
||||
|
||||
Phase 171-180 で実装した "Trim" パターンは、実は「特定の文字と比較するループ」の特例である。
|
||||
次フェーズ以降、JsonParser などで同様のパターン(例: `ch == '"'`, `ch == ','` など)が出てくる。
|
||||
|
||||
Phase 182 では、内部的な実装は変えずに、ドキュメント・コメント・型名を
|
||||
「Trim 専用」から「CharComparison パターン」として一般化する準備をする。
|
||||
|
||||
## 現在の用語体系
|
||||
|
||||
### Phase 171-180 での呼称
|
||||
|
||||
| 用語 | 場所 | 役割 |
|
||||
|------|------|------|
|
||||
| TrimLoopHelper | `src/mir/loop_pattern_detection/trim_loop_helper.rs` | Trim パターンの条件生成 |
|
||||
| TrimPatternInfo | loop_body_carrier_promoter.rs | Trim 昇格検出結果 |
|
||||
| TrimLoopLowering module | `src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs` | Trim ループ前処理 lowerer |
|
||||
| TrimPatternLowerer | trim_pattern_lowerer.rs | JoinIR 条件生成(Trim専用) |
|
||||
| TrimPatternValidator | trim_pattern_validator.rs | Trim パターン構造検証 |
|
||||
| LoopBodyCarrierPromoter | loop_pattern_detection/ | キャリア昇格検出(汎用) |
|
||||
|
||||
### 将来の呼称(Phase 183+)
|
||||
|
||||
| 用語 | 対応する現在名 | 新しい役割 |
|
||||
|------|---------------|-----------|
|
||||
| CharComparisonHelper | TrimLoopHelper | **文字比較ループ**の条件生成(汎用) |
|
||||
| CharComparisonPatternInfo | TrimPatternInfo | **文字比較パターン**の昇格検出結果 |
|
||||
| CharComparisonLowering module | TrimLoopLowering module | **文字比較パターン**ループ前処理 lowerer |
|
||||
| CharComparisonPatternLowerer | TrimPatternLowerer | JoinIR 条件生成(文字比較汎用) |
|
||||
| CharComparisonPatternValidator | TrimPatternValidator | 文字比較パターン構造検証 |
|
||||
| LoopBodyCarrierPromoter | (変更なし) | キャリア昇格検出(汎用、変更なし) |
|
||||
|
||||
## 用語一般化の意図
|
||||
|
||||
### Trim → CharComparison へ
|
||||
|
||||
**Trim パターン**は、以下の構造を持つ:
|
||||
```nyash
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
if ch == ' ' or ch == '\n' { p = p + 1; continue }
|
||||
break
|
||||
}
|
||||
```
|
||||
|
||||
これは本質的に「**文字比較ループ**」であり、以下のパターンでも同じ構造:
|
||||
|
||||
#### JsonParser: Quote Check
|
||||
```nyash
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
if ch == '"' { break } // 引用符を見つけたら終了
|
||||
p = p + 1
|
||||
}
|
||||
```
|
||||
|
||||
#### JsonParser: Delimiter Check
|
||||
```nyash
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
if ch == ',' or ch == ']' { break } // デリミタを見つけたら終了
|
||||
p = p + 1
|
||||
}
|
||||
```
|
||||
|
||||
#### 汎用の文字比較ループ
|
||||
```nyash
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
if <character_comparison> { <action> }
|
||||
p = p + 1
|
||||
}
|
||||
```
|
||||
|
||||
**共通点**:
|
||||
- ループ変数: 文字列インデックス(`p`, `i` など)
|
||||
- キャリア: LoopBodyLocal の文字変数(`ch`)
|
||||
- 比較対象: 特定の文字(whitespace, quote, delimiter など)
|
||||
- アクション: break, continue, または複雑な処理
|
||||
|
||||
**一般化の価値**:
|
||||
- Trim は whitespace 比較の特例
|
||||
- JsonParser では quote, delimiter, escape 文字など多種類の比較が必要
|
||||
- 同じ lowering ロジックで複数のパターンに対応可能
|
||||
|
||||
## Phase 182 作業内容(コード変更なし)
|
||||
|
||||
### 1. ドキュメント・docstring 更新
|
||||
|
||||
**対象ファイル群**:
|
||||
- `src/mir/builder/control_flow/joinir/patterns/trim_loop_lowering.rs`
|
||||
- `src/mir/builder/control_flow/joinir/patterns/trim_pattern_lowerer.rs`
|
||||
- `src/mir/builder/control_flow/joinir/patterns/trim_pattern_validator.rs`
|
||||
- `src/mir/loop_pattern_detection/trim_loop_helper.rs`
|
||||
- `src/mir/loop_pattern_detection/loop_body_carrier_promoter.rs`
|
||||
- 各ファイルの crate-level comment
|
||||
|
||||
**更新内容**:
|
||||
|
||||
```rust
|
||||
// Before
|
||||
//! Phase 171: Trim pattern detection and lowering for JsonParser
|
||||
|
||||
// After
|
||||
//! Phase 171+: Character Comparison pattern detection and lowering
|
||||
//!
|
||||
//! Initially implemented for Trim pattern (whitespace comparison).
|
||||
//! Generalized for any character comparison patterns (e.g., quoted string parsing, delimiter matching).
|
||||
//!
|
||||
//! ## Examples
|
||||
//! - Trim: `ch == ' '` (whitespace check)
|
||||
//! - JsonParser: `ch == '"'` (quote check), `ch == ','` (delimiter check)
|
||||
//! - CSV Parser: `ch == ','` (delimiter), `ch == '\n'` (line break)
|
||||
```
|
||||
|
||||
### 2. 型・関数のコメント更新
|
||||
|
||||
#### TrimLoopHelper
|
||||
```rust
|
||||
// Before
|
||||
pub struct TrimLoopHelper { ... }
|
||||
|
||||
// After
|
||||
/// Character comparison loop helper (initially for Trim patterns)
|
||||
///
|
||||
/// Used by loops like:
|
||||
/// - Trim: `ch == ' '` (whitespace check)
|
||||
/// - JsonParser: `ch == '"'` (quote check), `ch == ','` (delimiter check)
|
||||
/// - CSV Parser: `ch == ','` (delimiter), `ch == '\n'` (line break)
|
||||
///
|
||||
/// ## Renaming Plan (Phase 183+)
|
||||
/// This struct will be renamed to `CharComparisonHelper` in Phase 183.
|
||||
pub struct TrimLoopHelper { ... }
|
||||
```
|
||||
|
||||
#### TrimPatternLowerer
|
||||
```rust
|
||||
// Before
|
||||
pub struct TrimPatternLowerer;
|
||||
|
||||
// After
|
||||
/// Trim/CharComparison pattern lowerer (JoinIR condition generation)
|
||||
///
|
||||
/// Initially implemented for Trim patterns (whitespace comparison).
|
||||
/// Generalized for any character comparison patterns.
|
||||
///
|
||||
/// ## Renaming Plan (Phase 183+)
|
||||
/// This struct will be renamed to `CharComparisonPatternLowerer` in Phase 183.
|
||||
pub struct TrimPatternLowerer;
|
||||
```
|
||||
|
||||
#### TrimPatternValidator
|
||||
```rust
|
||||
// Before
|
||||
pub struct TrimPatternValidator;
|
||||
|
||||
// After
|
||||
/// Trim/CharComparison pattern validator (structure validation)
|
||||
///
|
||||
/// Validates character comparison loop structure:
|
||||
/// - LoopBodyLocal carrier (e.g., `ch`)
|
||||
/// - Character comparison condition (e.g., `ch == ' '`)
|
||||
/// - Substring pattern detection
|
||||
///
|
||||
/// ## Renaming Plan (Phase 183+)
|
||||
/// This struct will be renamed to `CharComparisonPatternValidator` in Phase 183.
|
||||
pub struct TrimPatternValidator;
|
||||
```
|
||||
|
||||
### 3. 用語統一の計画メモ
|
||||
|
||||
**Phase 183+ での段階的リネーム**:
|
||||
|
||||
#### Step 1: 型エイリアス追加(互換性維持)
|
||||
```rust
|
||||
// Phase 183
|
||||
pub type TrimLoopHelper = CharComparisonHelper;
|
||||
pub type TrimPatternInfo = CharComparisonPatternInfo;
|
||||
|
||||
#[deprecated(note = "Use CharComparisonPatternLowerer instead")]
|
||||
pub type TrimPatternLowerer = CharComparisonPatternLowerer;
|
||||
```
|
||||
|
||||
#### Step 2: ファイル名変更
|
||||
```
|
||||
src/mir/builder/control_flow/joinir/patterns/
|
||||
trim_loop_lowering.rs → char_comparison_lowering.rs
|
||||
trim_pattern_lowerer.rs → char_comparison_pattern_lowerer.rs
|
||||
trim_pattern_validator.rs → char_comparison_pattern_validator.rs
|
||||
|
||||
src/mir/loop_pattern_detection/
|
||||
trim_loop_helper.rs → char_comparison_helper.rs
|
||||
```
|
||||
|
||||
#### Step 3: 旧名の非推奨化
|
||||
```rust
|
||||
#[deprecated(since = "Phase 183", note = "Use CharComparisonHelper instead")]
|
||||
pub type TrimLoopHelper = CharComparisonHelper;
|
||||
```
|
||||
|
||||
#### Step 4: 完全置換(Phase 184+)
|
||||
- 型エイリアス削除
|
||||
- 旧名の完全廃止
|
||||
- ドキュメント内の "Trim" → "CharComparison" 一斉置換
|
||||
|
||||
### 4. 互換性の考慮
|
||||
|
||||
**段階的リネーム戦略**:
|
||||
- **Phase 182**: ドキュメント・コメントのみ更新(実装変更なし)
|
||||
- **Phase 183**: 型エイリアス追加、ファイル名変更、新名称導入
|
||||
- **Phase 184**: 旧名削除、完全統一
|
||||
|
||||
**移行期間**:
|
||||
- Phase 183-184 の間、旧名と新名を共存させる
|
||||
- 型エイリアスで互換性維持
|
||||
- docstring に `#[deprecated]` マークを付ける
|
||||
|
||||
## 実装例(Phase 183 以降向け)
|
||||
|
||||
### CharComparisonHelper の使用例
|
||||
|
||||
```rust
|
||||
// Phase 182: ドキュメント更新のみ(この段階では実装なし)
|
||||
//
|
||||
// docstring に記載:
|
||||
// "This will be renamed to CharComparisonHelper in Phase 183"
|
||||
// "Initially used for Trim patterns; generalized for any char comparison"
|
||||
|
||||
// Phase 183+: 実装例(参考)
|
||||
impl CharComparisonHelper {
|
||||
pub fn new(carrier_name: String, comparison_chars: Vec<String>) -> Self {
|
||||
// whitespace以外の文字比較にも対応
|
||||
Self {
|
||||
carrier_name,
|
||||
comparison_chars,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_trim() -> Self {
|
||||
// Trim パターン専用ヘルパー(後方互換性)
|
||||
Self::new("ch".to_string(), vec![" ".to_string(), "\n".to_string(), "\t".to_string()])
|
||||
}
|
||||
|
||||
pub fn for_quote_check() -> Self {
|
||||
// JsonParser 引用符チェック
|
||||
Self::new("ch".to_string(), vec!["\"".to_string()])
|
||||
}
|
||||
|
||||
pub fn for_delimiter_check() -> Self {
|
||||
// JsonParser デリミタチェック
|
||||
Self::new("ch".to_string(), vec![",".to_string(), "]".to_string(), "}".to_string()])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 次フェーズへの提案
|
||||
|
||||
### Phase 183: 実装・リネーム フェーズ
|
||||
|
||||
**目標**: 型名・ファイル名を実際にリネーム、互換性維持
|
||||
|
||||
**タスク**:
|
||||
- [ ] `trim_loop_lowering.rs` → `char_comparison_lowering.rs` リネーム
|
||||
- [ ] `TrimLoopHelper` → `CharComparisonHelper` リネーム(型エイリアス付き)
|
||||
- [ ] `TrimPatternLowerer` → `CharComparisonPatternLowerer` リネーム
|
||||
- [ ] `TrimPatternValidator` → `CharComparisonPatternValidator` リネーム
|
||||
- [ ] テスト・ドキュメント内の明示的な "Trim" → "CharComparison" 置換
|
||||
- [ ] 新規の JsonParser ループ(_parse_string 等)でも同モジュール使用
|
||||
|
||||
**互換性**:
|
||||
```rust
|
||||
// 型エイリアスで旧コード動作保証
|
||||
pub type TrimLoopHelper = CharComparisonHelper;
|
||||
pub type TrimPatternInfo = CharComparisonPatternInfo;
|
||||
|
||||
#[deprecated(note = "Use CharComparisonHelper instead. Will be removed in Phase 184.")]
|
||||
pub type TrimLoopHelper = CharComparisonHelper;
|
||||
```
|
||||
|
||||
### Phase 184+: 完全置換フェーズ
|
||||
|
||||
**目標**: 旧名完全廃止、用語統一完了
|
||||
|
||||
**タスク**:
|
||||
- [ ] 型エイリアス削除
|
||||
- [ ] 旧名への参照を全て新名に置換
|
||||
- [ ] `#[deprecated]` マーク削除
|
||||
- [ ] ドキュメント最終確認
|
||||
|
||||
## Pattern5 (Trim/CharComparison) の拡張性
|
||||
|
||||
### 現在のサポート範囲
|
||||
|
||||
**Trim パターン**:
|
||||
- whitespace 比較(`ch == ' '`, `ch == '\n'`, `ch == '\t'`)
|
||||
- キャリア: `ch` (LoopBodyLocal)
|
||||
- アクション: continue(whitespace skip)
|
||||
|
||||
### Phase 183+ での拡張
|
||||
|
||||
**JsonParser パターン**:
|
||||
- quote 比較(`ch == '"'`)
|
||||
- delimiter 比較(`ch == ','`, `ch == ']'`, `ch == '}'`)
|
||||
- escape 文字比較(`ch == '\\'`)
|
||||
- キャリア: `ch` (LoopBodyLocal)
|
||||
- アクション: break, continue, 複雑な処理
|
||||
|
||||
**CSV Parser パターン**:
|
||||
- delimiter 比較(`ch == ','`)
|
||||
- line break 比較(`ch == '\n'`)
|
||||
- quote 処理(`ch == '"'`)
|
||||
|
||||
**汎用文字比較ループ**:
|
||||
- 任意の文字列との比較
|
||||
- 複数文字の OR 条件
|
||||
- 否定条件(`ch != '...'`)
|
||||
|
||||
## 実装の安全性
|
||||
|
||||
### Conservative Approach
|
||||
|
||||
1. **Phase 182**: ドキュメント・コメントのみ更新(実装変更なし)
|
||||
2. **Phase 183**: 型エイリアス・ファイル名変更(後方互換性維持)
|
||||
3. **Phase 184**: 旧名削除(完全統一)
|
||||
|
||||
### テスト戦略
|
||||
|
||||
**既存テストの継続動作保証**:
|
||||
```bash
|
||||
# Trim pattern tests
|
||||
cargo test --release --lib trim
|
||||
|
||||
# JsonParser tests (uses Trim pattern)
|
||||
./target/release/hakorune apps/tests/test_jsonparser_skip_whitespace.hako
|
||||
|
||||
# Pattern2 tests
|
||||
cargo test --release --lib pattern2
|
||||
|
||||
# Pattern4 tests
|
||||
cargo test --release --lib pattern4
|
||||
```
|
||||
|
||||
## アーキテクチャへの影響
|
||||
|
||||
### joinir-architecture-overview.md への追記
|
||||
|
||||
**更新内容**:
|
||||
```markdown
|
||||
### Pattern 5: CharComparison (Trim-like) Loops
|
||||
|
||||
**Previously Known As**: Trim Pattern (Phase 171-180)
|
||||
|
||||
**Generalized**: Character comparison loops (Phase 182+)
|
||||
|
||||
**Examples**:
|
||||
- Trim: `ch == ' '` (whitespace skip)
|
||||
- JsonParser: `ch == '"'` (quote detection), `ch == ','` (delimiter detection)
|
||||
- CSV Parser: `ch == ','` (field delimiter), `ch == '\n'` (line break)
|
||||
|
||||
**Components**:
|
||||
- `CharComparisonHelper` (was `TrimLoopHelper`) - Pattern data structure
|
||||
- `CharComparisonPatternLowerer` (was `TrimPatternLowerer`) - JoinIR condition generation
|
||||
- `CharComparisonPatternValidator` (was `TrimPatternValidator`) - Pattern structure validation
|
||||
- `LoopBodyCarrierPromoter` - Carrier promotion (shared, unchanged)
|
||||
|
||||
**Lowering Module**: `src/mir/builder/control_flow/joinir/patterns/char_comparison_lowering.rs`
|
||||
```
|
||||
|
||||
## 関連ドキュメント
|
||||
|
||||
- `phase180-trim-module-design.md` - Trim module 当初の設計
|
||||
- `phase171-c-trim-pattern-lowering.md` - Phase 171 実装詳細
|
||||
- `phase181-jsonparser-loop-roadmap.md` - JsonParser 次実装予定
|
||||
- `joinir-architecture-overview.md` - JoinIR アーキテクチャ全体図
|
||||
|
||||
## タイムライン
|
||||
|
||||
### Phase 182(このフェーズ)
|
||||
- **Task 182-1**: 設計ドキュメント作成(このファイル) - 完了
|
||||
- **Task 182-2**: docstring 更新(オプション) - 10-15分
|
||||
|
||||
### Phase 183(次フェーズ)
|
||||
- **Task 183-1**: 型名・ファイル名リネーム - 30分
|
||||
- **Task 183-2**: 型エイリアス追加 - 10分
|
||||
- **Task 183-3**: テスト確認 - 15分
|
||||
|
||||
### Phase 184+(最終フェーズ)
|
||||
- **Task 184-1**: 旧名削除 - 20分
|
||||
- **Task 184-2**: 完全統一確認 - 10分
|
||||
|
||||
**Total Estimated Time**: 95-100分(3フェーズ合計)
|
||||
|
||||
## 成功基準
|
||||
|
||||
### Phase 182
|
||||
- ✅ 設計ドキュメント作成完了
|
||||
- ✅ docstring に汎用化の意図を明記
|
||||
- ✅ Phase 183+ の実装計画を明確化
|
||||
|
||||
### Phase 183
|
||||
- ✅ 型名・ファイル名リネーム完了
|
||||
- ✅ 型エイリアスで後方互換性維持
|
||||
- ✅ 既存テスト全てパス(変更なし)
|
||||
|
||||
### Phase 184+
|
||||
- ✅ 旧名完全削除
|
||||
- ✅ ドキュメント統一
|
||||
- ✅ "Trim" → "CharComparison" 用語統一完了
|
||||
|
||||
---
|
||||
|
||||
**作成日**: 2025-12-08
|
||||
**Phase**: 182(CharComparison 汎用化の準備・設計)
|
||||
**ステータス**: ドキュメント・計画のみ(コード変更なし)
|
||||
**次フェーズ**: Phase 183(実装・リネーム)
|
||||
@ -1,128 +0,0 @@
|
||||
# Phase 182-1: JsonParser Simple Loops Design Memo
|
||||
|
||||
## Overview
|
||||
Phase 182 implements three simple JsonParser loops using existing P2/P1 JoinIR patterns.
|
||||
This follows Phase 181's design investigation.
|
||||
|
||||
## Target Loops (3)
|
||||
|
||||
### 1. _parse_number (P2 Break)
|
||||
- **Loop condition**: `p < s.length()` (LoopParam only)
|
||||
- **Carriers**: `p`, `num_str` (2 carriers)
|
||||
- **Control flow**: `break` on `digit_pos < 0`
|
||||
- **Pattern**: P2 Break
|
||||
- **JoinIR support**: ✅ Existing P2 lowerer sufficient
|
||||
- **AST characteristics**:
|
||||
- Single break point
|
||||
- No continue
|
||||
- No nested control flow
|
||||
- Carriers updated unconditionally before break check
|
||||
|
||||
### 2. _atoi (P2 Break)
|
||||
- **Loop condition**: `i < n` (LoopParam + OuterLocal)
|
||||
- **Carriers**: `v`, `i` (2 carriers)
|
||||
- **Control flow**: `break` on `ch < "0" || ch > "9"` + `pos < 0`
|
||||
- **Pattern**: P2 Break
|
||||
- **JoinIR support**: ✅ Existing P2 lowerer sufficient
|
||||
- **AST characteristics**:
|
||||
- Multiple break conditions (combined with OR)
|
||||
- No continue
|
||||
- No nested control flow
|
||||
- Carriers updated before and after break check
|
||||
|
||||
### 3. _match_literal (P1 Simple)
|
||||
- **Loop condition**: `i < len` (LoopParam + OuterLocal)
|
||||
- **Carriers**: `i` (1 carrier)
|
||||
- **Control flow**: `return` (early exit)
|
||||
- **Pattern**: P1 Simple
|
||||
- **JoinIR support**: ✅ Existing P1 lowerer sufficient
|
||||
- **AST characteristics**:
|
||||
- No break/continue
|
||||
- Early return instead
|
||||
- Single carrier increment
|
||||
- Simple conditional logic
|
||||
|
||||
## Pipeline Integration Strategy
|
||||
|
||||
### Existing Infrastructure (Reuse)
|
||||
- **PatternPipelineContext**: Already handles P1/P2 detection
|
||||
- **build_pattern_context()**: Existing logic sufficient
|
||||
- **P1 lowerer**: `lower_simple_while_minimal()` ready
|
||||
- **P2 lowerer**: `lower_loop_with_break_minimal()` ready
|
||||
- **LoopFeatures**: Shape detection already identifies P1/P2
|
||||
|
||||
### Minimal Additions Required
|
||||
1. **Routing whitelist**: Ensure 3 functions are in JOINIR_TARGETS
|
||||
2. **LoopUpdateAnalyzer**: May need to skip string-heavy operations (gradual enablement)
|
||||
3. **Tracing**: Use NYASH_JOINIR_STRUCTURE_ONLY=1 for verification
|
||||
|
||||
### NOT Required
|
||||
- ❌ New pattern types
|
||||
- ❌ New lowerer functions
|
||||
- ❌ Special carrier handling
|
||||
- ❌ Custom PHI generation
|
||||
|
||||
## Verification Plan
|
||||
|
||||
### Phase 182-2: Routing Check
|
||||
```bash
|
||||
# Verify functions are in whitelist
|
||||
grep -E "(_parse_number|_atoi|_match_literal)" \
|
||||
src/mir/builder/control_flow/joinir/routing.rs
|
||||
```
|
||||
|
||||
### Phase 182-3: Pattern Detection Tracing
|
||||
```bash
|
||||
# Verify correct pattern routing
|
||||
NYASH_JOINIR_STRUCTURE_ONLY=1 NYASH_JOINIR_DEBUG=1 \
|
||||
./target/release/hakorune apps/selfhost-runtime/jsonparser.hako 2>&1 | \
|
||||
grep -E "(pattern|route|_parse_number|_atoi|_match)"
|
||||
```
|
||||
|
||||
Expected output:
|
||||
- `_parse_number` → [trace:pattern] route: Pattern2_Break MATCHED
|
||||
- `_atoi` → [trace:pattern] route: Pattern2_Break MATCHED
|
||||
- `_match_literal` → [trace:pattern] route: Pattern1_Minimal MATCHED
|
||||
- No `[joinir/freeze]` errors
|
||||
|
||||
### Phase 182-5: Representative Tests
|
||||
Create 3 minimal test files:
|
||||
1. `local_tests/test_jsonparser_parse_number_min.hako` - number parsing
|
||||
2. `local_tests/test_jsonparser_atoi_min.hako` - string to integer
|
||||
3. `local_tests/test_jsonparser_match_literal_min.hako` - literal matching
|
||||
|
||||
Success criteria:
|
||||
- ✅ RC = 0 (normal exit)
|
||||
- ✅ Output matches expected values
|
||||
- ✅ No `[joinir/freeze]` errors
|
||||
- ✅ Same results as Rust JsonParser
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Design Principles
|
||||
1. **Fit into existing framework** - Don't add special cases
|
||||
2. **Minimal additions** - Only adjust filters if necessary
|
||||
3. **Gradual enablement** - String operations can be phased in
|
||||
4. **Reuse pattern pipeline** - PatternPipelineContext handles everything
|
||||
|
||||
### Potential Issues
|
||||
- **String operations**: May need LoopUpdateAnalyzer filtering
|
||||
- **Carrier complexity**: P2 has 2 carriers, but existing code handles this
|
||||
- **Break conditions**: Multiple conditions in _atoi, but OR combination is standard
|
||||
|
||||
### Success Metrics
|
||||
- All 3 loops route to correct patterns (P2/P1)
|
||||
- No new special-case code required
|
||||
- Tests pass with NYASH_JOINIR_CORE=1
|
||||
- Performance comparable to existing patterns
|
||||
|
||||
## Next Steps (Phase 183+)
|
||||
- _parse_array (P4 Continue candidate)
|
||||
- _parse_object (P4 Continue candidate)
|
||||
- Complex nested loops (future phases)
|
||||
|
||||
## References
|
||||
- Phase 170: Loop header PHI design
|
||||
- Phase 181: JsonParser loop analysis
|
||||
- Pattern detection: `src/mir/builder/control_flow/joinir/patterns/detection.rs`
|
||||
- P1/P2 lowerers: `src/mir/builder/control_flow/joinir/patterns/pattern{1,2}_*.rs`
|
||||
@ -1,240 +0,0 @@
|
||||
# Phase 183: LoopBodyLocal Role Separation Design
|
||||
|
||||
## Overview
|
||||
|
||||
Phase 182 discovered **Blocker 1**: LoopBodyLocal variables are currently always routed to Trim-specific carrier promotion logic, which is inappropriate for JsonParser integer loops where these variables are simple local computations.
|
||||
|
||||
This phase separates LoopBodyLocal variables into two categories based on their usage pattern:
|
||||
|
||||
1. **Condition LoopBodyLocal**: Used in loop conditions (header/break/continue) → Needs Trim promotion
|
||||
2. **Body-only LoopBodyLocal**: Only used in loop body, never in conditions → No promotion needed
|
||||
|
||||
## Problem Statement
|
||||
|
||||
### Current Behavior (Phase 182 Blockers)
|
||||
|
||||
```nyash
|
||||
// Example: _parse_number
|
||||
loop(p < s.length()) {
|
||||
local digit_pos = "0123456789".indexOf(ch) // LoopBodyLocal: digit_pos
|
||||
if (digit_pos < 0) {
|
||||
break // digit_pos used in BREAK condition
|
||||
}
|
||||
num_str = num_str + ch
|
||||
p = p + 1
|
||||
}
|
||||
```
|
||||
|
||||
**Current routing**:
|
||||
- `digit_pos` is detected as LoopBodyLocal (defined in body)
|
||||
- Pattern2 tries to apply Trim carrier promotion
|
||||
- **Error**: Not a Trim pattern (`indexOf` vs `substring`)
|
||||
|
||||
**Desired behavior**:
|
||||
- `digit_pos` used in condition → Should attempt Trim promotion (and fail gracefully)
|
||||
- But if `digit_pos` were only in body → Should be allowed as pure local variable
|
||||
|
||||
### Use Cases
|
||||
|
||||
#### Case A: Condition LoopBodyLocal (Trim Pattern)
|
||||
```nyash
|
||||
// _trim_leading_whitespace
|
||||
loop(pos < s.length()) {
|
||||
local ch = s.substring(pos, pos + 1) // LoopBodyLocal: ch
|
||||
if (ch == " " || ch == "\t") { // ch in BREAK condition
|
||||
pos = pos + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
**Routing**: Needs Trim promotion (`ch` → `is_whitespace` carrier)
|
||||
|
||||
#### Case B: Body-only LoopBodyLocal (Pure Local)
|
||||
```nyash
|
||||
// Hypothetical simple loop
|
||||
loop(i < n) {
|
||||
local temp = i * 2 // LoopBodyLocal: temp (not in any condition!)
|
||||
result = result + temp
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
**Routing**: No promotion needed (`temp` never used in conditions)
|
||||
|
||||
#### Case C: Condition LoopBodyLocal (Non-Trim)
|
||||
```nyash
|
||||
// _parse_number
|
||||
loop(p < s.length()) {
|
||||
local digit_pos = "0123456789".indexOf(ch) // LoopBodyLocal: digit_pos
|
||||
if (digit_pos < 0) { // digit_pos in BREAK condition
|
||||
break
|
||||
}
|
||||
p = p + 1
|
||||
}
|
||||
```
|
||||
**Current**: Tries Trim promotion → Fails
|
||||
**Desired**: Recognize non-Trim pattern → **Block with clear error message**
|
||||
|
||||
## Design Solution
|
||||
|
||||
### Architecture: Two-Stage Check
|
||||
|
||||
```
|
||||
LoopConditionScopeBox
|
||||
↓
|
||||
has_loop_body_local() ?
|
||||
↓ YES
|
||||
Check: Where is LoopBodyLocal used?
|
||||
├─ In CONDITION (header/break/continue) → Try Trim promotion
|
||||
│ ├─ Success → Pattern2/4 with Trim carrier
|
||||
│ └─ Fail → Reject loop (not supported yet)
|
||||
└─ Body-only (NOT in any condition) → Allow as pure local
|
||||
```
|
||||
|
||||
### Implementation Strategy
|
||||
|
||||
#### Step 1: Extend LoopConditionScope Analysis
|
||||
|
||||
Add `is_in_condition()` check to differentiate:
|
||||
- Condition LoopBodyLocal: Used in header/break/continue conditions
|
||||
- Body-only LoopBodyLocal: Only in body assignments/expressions
|
||||
|
||||
```rust
|
||||
impl LoopConditionScope {
|
||||
/// Check if a LoopBodyLocal is used in any condition
|
||||
pub fn is_body_local_in_condition(&self, var_name: &str) -> bool {
|
||||
// Implementation: Check if var_name appears in condition_nodes
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 2: Update TrimLoopLowerer
|
||||
|
||||
Modify `try_lower_trim_like_loop()` to:
|
||||
1. Filter LoopBodyLocal to only process **condition LoopBodyLocal**
|
||||
2. Skip body-only LoopBodyLocal (let Pattern1/2 handle naturally)
|
||||
|
||||
```rust
|
||||
impl TrimLoopLowerer {
|
||||
pub fn try_lower_trim_like_loop(...) -> Result<Option<TrimLoweringResult>, String> {
|
||||
// Extract condition LoopBodyLocal only
|
||||
let cond_body_locals: Vec<_> = cond_scope.vars.iter()
|
||||
.filter(|v| v.scope == CondVarScope::LoopBodyLocal)
|
||||
.filter(|v| Self::is_used_in_condition(v.name, break_cond))
|
||||
.collect();
|
||||
|
||||
if cond_body_locals.is_empty() {
|
||||
// No condition LoopBodyLocal → Not a Trim pattern
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Try promotion for condition LoopBodyLocal
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 3: Update Pattern2 can_lower
|
||||
|
||||
Ensure Pattern2 accepts loops with body-only LoopBodyLocal:
|
||||
|
||||
```rust
|
||||
pub fn can_lower(builder: &MirBuilder, ctx: &LoopPatternContext) -> bool {
|
||||
// Existing checks...
|
||||
|
||||
// NEW: Allow body-only LoopBodyLocal
|
||||
let cond_scope = &ctx.preprocessing.cond_scope;
|
||||
if cond_scope.has_loop_body_local() {
|
||||
// Check if all LoopBodyLocal are body-only (not in conditions)
|
||||
let all_body_only = cond_scope.vars.iter()
|
||||
.filter(|v| v.scope == CondVarScope::LoopBodyLocal)
|
||||
.all(|v| !is_in_any_condition(v.name, ctx));
|
||||
|
||||
if !all_body_only {
|
||||
// Some LoopBodyLocal in conditions → Must be Trim pattern
|
||||
// Trim lowering will handle this
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Task 183-2: Core Implementation
|
||||
|
||||
1. **Add condition detection helper** (10 lines)
|
||||
- `TrimLoopLowerer::is_used_in_condition(var_name, cond_node)`
|
||||
- Simple AST traversal to check if variable appears
|
||||
|
||||
2. **Update Trim detection** (20 lines)
|
||||
- Filter LoopBodyLocal to condition-only
|
||||
- Skip body-only LoopBodyLocal
|
||||
|
||||
3. **Add unit test** (50 lines)
|
||||
- `test_body_only_loopbodylocal_allowed`
|
||||
- Loop with `local temp` never used in condition
|
||||
- Should NOT trigger Trim promotion
|
||||
|
||||
### Task 183-3: Integration Tests
|
||||
|
||||
Create 3 test files demonstrating the fix:
|
||||
|
||||
1. **phase183_p2_parse_number.hako** - _parse_number pattern
|
||||
- `digit_pos` in break condition → Should reject (not Trim)
|
||||
- Clear error message: "LoopBodyLocal in condition, but not Trim pattern"
|
||||
|
||||
2. **phase183_p2_atoi.hako** - _atoi pattern
|
||||
- Similar to parse_number
|
||||
- Multiple break conditions
|
||||
|
||||
3. **phase183_p1_match_literal.hako** - _match_literal pattern
|
||||
- No LoopBodyLocal → Should work (baseline)
|
||||
|
||||
## Validation Strategy
|
||||
|
||||
### Success Criteria
|
||||
|
||||
1. **Body-only LoopBodyLocal**: Loops with body-only locals compile successfully
|
||||
2. **Condition LoopBodyLocal**:
|
||||
- Trim patterns → Promoted correctly
|
||||
- Non-Trim patterns → Rejected with clear error
|
||||
3. **No regression**: Existing Trim tests still pass
|
||||
|
||||
### Test Commands
|
||||
|
||||
```bash
|
||||
# Structure trace (verify no freeze)
|
||||
NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune apps/tests/phase183_p2_parse_number.hako
|
||||
|
||||
# Execution test (once promotion logic is ready)
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase183_p1_match_literal.hako
|
||||
```
|
||||
|
||||
## Future Work (Out of Scope)
|
||||
|
||||
### Phase 184+: Non-Trim LoopBodyLocal Patterns
|
||||
|
||||
To support `_parse_number` and `_atoi` fully, we need:
|
||||
|
||||
1. **Generic LoopBodyLocal promotion**
|
||||
- Pattern: `local x = expr; if (x op literal) break`
|
||||
- Promotion: Evaluate `expr` inline, no carrier needed
|
||||
- Alternative: Allow inline computation in JoinIR conditions
|
||||
|
||||
2. **String concatenation support** (Phase 178 blocker)
|
||||
- `num_str = num_str + ch` currently rejected
|
||||
- Need string carrier update support in Pattern2/4
|
||||
|
||||
**Decision for Phase 183**:
|
||||
- Focus on architectural separation (condition vs body-only)
|
||||
- Accept that `_parse_number`/`_atoi` will still fail (but with better error)
|
||||
- Unblock body-only LoopBodyLocal use cases
|
||||
|
||||
## References
|
||||
|
||||
- Phase 182: JsonParser P1/P2 pattern validation (discovered blockers)
|
||||
- Phase 181: JsonParser loop inventory
|
||||
- Phase 171-C: LoopBodyCarrierPromoter original design
|
||||
- Phase 170-D: LoopConditionScopeBox implementation
|
||||
@ -1,374 +0,0 @@
|
||||
# Phase 184: Body-local MIR Lowering Design
|
||||
|
||||
## Status: In Progress (2025-12-08)
|
||||
|
||||
## Overview
|
||||
|
||||
Phase 183 completed **LoopBodyLocal role separation** (condition vs body-only).
|
||||
Phase 184 implements **body-local MIR lowering** - the ability to use body-only local variables in update expressions safely.
|
||||
|
||||
### Problem Statement
|
||||
|
||||
**Current limitation**:
|
||||
```nyash
|
||||
local sum = 0
|
||||
local i = 0
|
||||
loop(i < 5) {
|
||||
local temp = i * 2 // body-local variable
|
||||
sum = sum + temp // ❌ ERROR: temp not found in ConditionEnv
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
|
||||
**Root cause**:
|
||||
- `CarrierUpdateEmitter` only has access to `ConditionEnv`
|
||||
- `ConditionEnv` only contains condition variables (loop parameters)
|
||||
- Body-local variables (`temp`) are not in `ConditionEnv`
|
||||
- Update expression `sum = sum + temp` fails to resolve `temp`
|
||||
|
||||
**Goal**: Enable body-local variables in update expressions while maintaining architectural clarity.
|
||||
|
||||
## Design Solution
|
||||
|
||||
### Architecture: Two-Environment System
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ ConditionEnv │ Condition variables (loop parameters)
|
||||
│ - i → ValueId(0) │ Priority: HIGH
|
||||
│ - end → ValueId(1) │
|
||||
└─────────────────────┘
|
||||
↓
|
||||
┌─────────────────────┐
|
||||
│ LoopBodyLocalEnv │ Body-local variables
|
||||
│ - temp → ValueId(5)│ Priority: LOW (only if not in ConditionEnv)
|
||||
│ - digit → ValueId(6)│
|
||||
└─────────────────────┘
|
||||
↓
|
||||
┌─────────────────────┐
|
||||
│ UpdateEnv │ Unified resolution layer
|
||||
│ resolve(name): │ 1. Try ConditionEnv first
|
||||
│ cond.get(name) │ 2. Fallback to LoopBodyLocalEnv
|
||||
│ .or(body.get()) │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
### Box-First Design
|
||||
|
||||
Following **箱理論 (Box Theory)** principles:
|
||||
|
||||
#### Box 1: LoopBodyLocalEnv (Storage Box)
|
||||
|
||||
**Single Responsibility**: Collect and manage body-local variable mappings (name → ValueId).
|
||||
|
||||
```rust
|
||||
pub struct LoopBodyLocalEnv {
|
||||
locals: BTreeMap<String, ValueId>, // Deterministic ordering
|
||||
}
|
||||
|
||||
impl LoopBodyLocalEnv {
|
||||
/// Scan loop body AST and collect local definitions
|
||||
pub fn from_loop_body(
|
||||
body: &[ASTNode],
|
||||
builder: &mut JoinIrBuilder
|
||||
) -> Self;
|
||||
|
||||
/// Resolve a body-local variable name to JoinIR ValueId
|
||||
pub fn get(&self, name: &str) -> Option<ValueId>;
|
||||
}
|
||||
```
|
||||
|
||||
**Design rationale**:
|
||||
- **BTreeMap**: Ensures deterministic iteration (PHI ordering consistency)
|
||||
- **from_loop_body()**: Separates collection logic from lowering logic
|
||||
- **get()**: Simple lookup, no side effects
|
||||
|
||||
#### Box 2: UpdateEnv (Composition Box)
|
||||
|
||||
**Single Responsibility**: Unified variable resolution for update expressions.
|
||||
|
||||
```rust
|
||||
pub struct UpdateEnv<'a> {
|
||||
condition_env: &'a ConditionEnv, // Priority 1: Condition vars
|
||||
body_local_env: &'a LoopBodyLocalEnv, // Priority 2: Body-local vars
|
||||
}
|
||||
|
||||
impl<'a> UpdateEnv<'a> {
|
||||
/// Resolve variable name with priority order
|
||||
pub fn resolve(&self, name: &str) -> Option<ValueId> {
|
||||
self.condition_env.get(name)
|
||||
.or_else(|| self.body_local_env.get(name))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Design rationale**:
|
||||
- **Composition**: Combines two environments without owning them
|
||||
- **Priority order**: Condition variables take precedence (shadowing prevention)
|
||||
- **Lightweight**: No allocation, just references
|
||||
|
||||
### Integration Points
|
||||
|
||||
#### Current Flow (Pattern2 Example)
|
||||
|
||||
```rust
|
||||
// Pattern2Lowerer::lower()
|
||||
let condition_env = /* ... */;
|
||||
|
||||
// Emit carrier update
|
||||
let update_value = emit_carrier_update(
|
||||
&carrier,
|
||||
&update_expr,
|
||||
&mut alloc_value,
|
||||
&condition_env, // ❌ Only has condition variables
|
||||
&mut instructions,
|
||||
)?;
|
||||
```
|
||||
|
||||
#### Phase 184 Flow
|
||||
|
||||
```rust
|
||||
// Pattern2Lowerer::lower()
|
||||
let condition_env = /* ... */;
|
||||
let body_local_env = LoopBodyLocalEnv::from_loop_body(&body_nodes, builder);
|
||||
|
||||
// Create unified environment
|
||||
let update_env = UpdateEnv {
|
||||
condition_env: &condition_env,
|
||||
body_local_env: &body_local_env,
|
||||
};
|
||||
|
||||
// Emit carrier update (now with body-local support)
|
||||
let update_value = emit_carrier_update_with_env(
|
||||
&carrier,
|
||||
&update_expr,
|
||||
&mut alloc_value,
|
||||
&update_env, // ✅ Has both condition and body-local variables
|
||||
&mut instructions,
|
||||
)?;
|
||||
```
|
||||
|
||||
## Scope and Constraints
|
||||
|
||||
### In Scope (Phase 184)
|
||||
|
||||
1. **Body-local variable collection**: `LoopBodyLocalEnv::from_loop_body()`
|
||||
2. **Unified resolution**: `UpdateEnv::resolve()`
|
||||
3. **CarrierUpdateEmitter integration**: Use `UpdateEnv` instead of `ConditionEnv`
|
||||
4. **Pattern2/4 integration**: Pass `body_local_env` to update lowering
|
||||
|
||||
### Out of Scope
|
||||
|
||||
1. **Condition variable usage**: Body-locals still cannot be used in conditions
|
||||
2. **Pattern5 (Trim) integration**: Defer to Phase 185
|
||||
3. **Complex expressions**: Only simple variable references in updates
|
||||
4. **Type checking**: Assume type correctness (existing type inference handles this)
|
||||
|
||||
### Design Constraints
|
||||
|
||||
Following **Phase 178 Fail-Fast** and **箱理論** principles:
|
||||
|
||||
1. **Single Responsibility**: Each Box has one clear purpose
|
||||
2. **Deterministic**: BTreeMap for consistent ordering
|
||||
3. **Conservative**: No changes to Trim/Pattern5 logic
|
||||
4. **Explicit errors**: If body-local used in condition → Fail loudly
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
### Task 184-1: Design Document ✅ (This document)
|
||||
|
||||
### Task 184-2: LoopBodyLocalEnv Implementation
|
||||
|
||||
**File**: `src/mir/join_ir/lowering/loop_body_local_env.rs` (new)
|
||||
|
||||
**Core logic**:
|
||||
```rust
|
||||
impl LoopBodyLocalEnv {
|
||||
pub fn from_loop_body(body: &[ASTNode], builder: &mut JoinIrBuilder) -> Self {
|
||||
let mut locals = BTreeMap::new();
|
||||
|
||||
for node in body {
|
||||
if let ASTNode::LocalDecl { name, init_value } = node {
|
||||
// Lower init_value to JoinIR
|
||||
let value_id = builder.lower_expr(init_value)?;
|
||||
locals.insert(name.clone(), value_id);
|
||||
}
|
||||
}
|
||||
|
||||
Self { locals }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Unit tests** (3-5 tests):
|
||||
- `test_empty_body`: No locals → empty env
|
||||
- `test_single_local`: One local → one mapping
|
||||
- `test_multiple_locals`: Multiple locals → sorted keys
|
||||
- `test_get_existing`: Lookup succeeds
|
||||
- `test_get_nonexistent`: Lookup returns None
|
||||
|
||||
### Task 184-3: UpdateEnv Implementation
|
||||
|
||||
**File**: `src/mir/join_ir/lowering/update_env.rs` (new)
|
||||
|
||||
**Core logic**:
|
||||
```rust
|
||||
pub struct UpdateEnv<'a> {
|
||||
condition_env: &'a ConditionEnv,
|
||||
body_local_env: &'a LoopBodyLocalEnv,
|
||||
}
|
||||
|
||||
impl<'a> UpdateEnv<'a> {
|
||||
pub fn new(
|
||||
condition_env: &'a ConditionEnv,
|
||||
body_local_env: &'a LoopBodyLocalEnv,
|
||||
) -> Self {
|
||||
Self { condition_env, body_local_env }
|
||||
}
|
||||
|
||||
pub fn resolve(&self, name: &str) -> Option<ValueId> {
|
||||
self.condition_env.get(name)
|
||||
.or_else(|| self.body_local_env.get(name))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Unit tests** (2-3 tests):
|
||||
- `test_resolve_condition_priority`: Condition var found first
|
||||
- `test_resolve_body_local_fallback`: Body-local found when condition absent
|
||||
- `test_resolve_not_found`: Neither env has variable → None
|
||||
|
||||
### Task 184-4: CarrierUpdateEmitter Integration
|
||||
|
||||
**File**: `src/mir/join_ir/lowering/carrier_update_emitter.rs` (modify)
|
||||
|
||||
**Changes**:
|
||||
1. Add new function variant accepting `UpdateEnv`:
|
||||
```rust
|
||||
pub fn emit_carrier_update_with_env(
|
||||
carrier: &CarrierVar,
|
||||
update: &UpdateExpr,
|
||||
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||
env: &UpdateEnv, // New: UpdateEnv instead of ConditionEnv
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String> {
|
||||
// Use env.resolve() instead of env.get()
|
||||
}
|
||||
```
|
||||
|
||||
2. Keep existing `emit_carrier_update()` for backward compatibility
|
||||
3. Update Pattern2/4 callers to use new variant
|
||||
|
||||
**Validation**: Existing tests must pass (backward compatibility).
|
||||
|
||||
### Task 184-5: Representative Tests
|
||||
|
||||
**File**: `apps/tests/phase184_body_local_update.hako` (new)
|
||||
|
||||
```nyash
|
||||
// Body-local used in update expression
|
||||
static box Main {
|
||||
main() {
|
||||
local sum = 0
|
||||
local i = 0
|
||||
loop(i < 5) {
|
||||
local temp = i * 2 // Body-local variable
|
||||
sum = sum + temp // Use in update expression
|
||||
i = i + 1
|
||||
}
|
||||
print(sum) // Expected: 0+2+4+6+8 = 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Test commands**:
|
||||
```bash
|
||||
# Structure trace
|
||||
NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune apps/tests/phase184_body_local_update.hako
|
||||
|
||||
# Full execution
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase184_body_local_update.hako
|
||||
```
|
||||
|
||||
### Task 184-6: Documentation Updates
|
||||
|
||||
1. **Update**: `docs/development/current/main/joinir-architecture-overview.md`
|
||||
- Add LoopBodyLocalEnv section
|
||||
- Update UpdateEnv integration diagram
|
||||
|
||||
2. **Update**: `CURRENT_TASK.md`
|
||||
- Mark Phase 184 complete
|
||||
- Add Phase 185 preview
|
||||
|
||||
## Validation Strategy
|
||||
|
||||
### Success Criteria
|
||||
|
||||
1. **Unit tests pass**: LoopBodyLocalEnv and UpdateEnv tests green
|
||||
2. **Backward compatibility**: Existing Pattern2/4 tests still pass
|
||||
3. **Representative test**: phase184_body_local_update.hako executes correctly (output: 20)
|
||||
4. **No regression**: Trim patterns (Pattern5) unaffected
|
||||
|
||||
### Test Commands
|
||||
|
||||
```bash
|
||||
# Unit tests
|
||||
cargo test --release --lib loop_body_local_env
|
||||
cargo test --release --lib update_env
|
||||
cargo test --release --lib carrier_update
|
||||
|
||||
# Integration test
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase184_body_local_update.hako
|
||||
|
||||
# Regression check (Trim pattern)
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase172_trim_while.hako
|
||||
```
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### Not Supported (Explicit Design Decision)
|
||||
|
||||
1. **Body-local in conditions**:
|
||||
```nyash
|
||||
loop(i < 5) {
|
||||
local temp = i * 2
|
||||
if (temp > 6) break // ❌ ERROR: Body-local in condition not allowed
|
||||
}
|
||||
```
|
||||
**Reason**: Condition variables must be loop parameters (JoinIR entry signature constraint).
|
||||
|
||||
2. **Shadowing**:
|
||||
```nyash
|
||||
loop(i < 5) {
|
||||
local i = 10 // ❌ ERROR: Shadows condition variable
|
||||
}
|
||||
```
|
||||
**Reason**: `UpdateEnv` prioritizes condition variables - shadowing forbidden.
|
||||
|
||||
3. **Complex expressions**:
|
||||
```nyash
|
||||
loop(i < 5) {
|
||||
local temp = obj.method() // ⚠️ May not work yet
|
||||
}
|
||||
```
|
||||
**Reason**: Limited to expressions `JoinIrBuilder::lower_expr()` supports.
|
||||
|
||||
## Future Work (Phase 185+)
|
||||
|
||||
### Phase 185: Trim Pattern Integration
|
||||
|
||||
- Extend LoopBodyLocalEnv to handle Trim carrier variables
|
||||
- Update TrimLoopLowerer to use UpdateEnv
|
||||
|
||||
### Phase 186: Condition Expression Support
|
||||
|
||||
- Allow body-local variables in break/continue conditions
|
||||
- Requires inline expression evaluation in condition lowering
|
||||
|
||||
## References
|
||||
|
||||
- **Phase 183**: LoopBodyLocal role separation (condition vs body-only)
|
||||
- **Phase 178**: Fail-Fast error handling principles
|
||||
- **Phase 171-C**: LoopBodyCarrierPromoter original design
|
||||
- **carrier_update_emitter.rs**: Current update emission logic
|
||||
- **condition_env.rs**: Condition variable environment design
|
||||
@ -1,676 +0,0 @@
|
||||
# Phase 185: Body-local Pattern2/4 Integration (int loops priority)
|
||||
|
||||
**Date**: 2025-12-09
|
||||
**Status**: In Progress
|
||||
**Phase Goal**: Integrate Phase 184 infrastructure into Pattern2/4 for integer loop support
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Phase 184 completed the **body-local MIR lowering infrastructure** with three boxes:
|
||||
- `LoopBodyLocalEnv`: Storage for body-local variable mappings
|
||||
- `UpdateEnv`: Unified resolution (ConditionEnv + LoopBodyLocalEnv)
|
||||
- `CarrierUpdateEmitter`: Extended with `emit_carrier_update_with_env()`
|
||||
|
||||
Phase 185 **integrates this infrastructure** into Pattern2/4 lowerers to enable integer loops with body-local variables.
|
||||
|
||||
### Target Loops
|
||||
|
||||
**JsonParser integer loops**:
|
||||
- `_parse_number`: Parses numeric strings with `local digit_pos` calculations
|
||||
- `_atoi`: Converts string to integer with `local digit` temporary
|
||||
|
||||
**Test cases**:
|
||||
- `phase184_body_local_update.hako`: Pattern1 test (already works)
|
||||
- `phase184_body_local_with_break.hako`: Pattern2 test (needs integration)
|
||||
|
||||
### What We Do
|
||||
|
||||
**Integrate body-local variables into update expressions**:
|
||||
```nyash
|
||||
loop(pos < len) {
|
||||
local digit_pos = pos - start // Body-local variable
|
||||
sum = sum * 10 // Update using body-local
|
||||
sum = sum + digit_pos
|
||||
pos = pos + 1
|
||||
if (sum > 1000) break
|
||||
}
|
||||
```
|
||||
|
||||
**Enable**: `digit_pos` in `sum = sum + digit_pos` update expression
|
||||
|
||||
### What We DON'T Do
|
||||
|
||||
**String concatenation** (Phase 178 Fail-Fast maintained):
|
||||
```nyash
|
||||
loop(pos < len) {
|
||||
local ch = s.substring(pos, pos+1)
|
||||
num_str = num_str + ch // ❌ Still rejected (string concat)
|
||||
}
|
||||
```
|
||||
|
||||
**Reason**: String UpdateKind support is Phase 186+ work.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Integration
|
||||
|
||||
### Current Flow (Phase 184 - Infrastructure Only)
|
||||
|
||||
```
|
||||
┌──────────────────────┐
|
||||
│ ConditionEnvBuilder │ → ConditionEnv (loop params)
|
||||
└──────────────────────┘
|
||||
↓
|
||||
┌──────────────────────┐
|
||||
│ LoopBodyLocalEnv │ ← NEW (Phase 184)
|
||||
│ from_locals() │ Body-local variables
|
||||
└──────────────────────┘
|
||||
↓
|
||||
┌──────────────────────┐
|
||||
│ UpdateEnv │ ← NEW (Phase 184)
|
||||
│ resolve(name) │ Unified resolution
|
||||
└──────────────────────┘
|
||||
↓
|
||||
┌──────────────────────┐
|
||||
│ CarrierUpdateEmitter │ ← EXTENDED (Phase 184)
|
||||
│ emit_carrier_update_ │ UpdateEnv version
|
||||
│ with_env() │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
**Status**: Infrastructure complete, but Pattern2/4 still use old `ConditionEnv` path.
|
||||
|
||||
### Phase 185 Flow (Integration)
|
||||
|
||||
**Pattern2 changes**:
|
||||
```rust
|
||||
// 1. Collect body-local variables
|
||||
let body_locals = collect_body_local_variables(_body);
|
||||
let body_local_env = LoopBodyLocalEnv::from_locals(body_locals);
|
||||
|
||||
// 2. Create UpdateEnv
|
||||
let update_env = UpdateEnv::new(&condition_env, &body_local_env);
|
||||
|
||||
// 3. Use UpdateEnv in carrier update
|
||||
let update_value = emit_carrier_update_with_env(
|
||||
&carrier,
|
||||
&update_expr,
|
||||
&mut alloc_value,
|
||||
&update_env, // ✅ Now has body-local support
|
||||
&mut instructions,
|
||||
)?;
|
||||
```
|
||||
|
||||
**Pattern4**: Same pattern (minimal changes, copy from Pattern2 approach).
|
||||
|
||||
---
|
||||
|
||||
## Task Breakdown
|
||||
|
||||
### Task 185-1: Design Document ✅
|
||||
|
||||
**This document** - Architecture, scope, constraints, validation strategy.
|
||||
|
||||
### Task 185-2: Pattern2 Integration
|
||||
|
||||
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`
|
||||
|
||||
**Changes**:
|
||||
|
||||
1. **Add helper function** (before cf_loop_pattern2_with_break):
|
||||
```rust
|
||||
/// Collect body-local variable declarations from loop body
|
||||
///
|
||||
/// Returns Vec<(name, ValueId)> for variables declared with `local` in loop body.
|
||||
fn collect_body_local_variables(
|
||||
body: &[ASTNode],
|
||||
alloc_join_value: &mut dyn FnMut() -> ValueId,
|
||||
) -> Vec<(String, ValueId)> {
|
||||
let mut locals = Vec::new();
|
||||
for node in body {
|
||||
if let ASTNode::LocalDecl { name, .. } = node {
|
||||
let value_id = alloc_join_value();
|
||||
locals.push((name.clone(), value_id));
|
||||
}
|
||||
}
|
||||
locals
|
||||
}
|
||||
```
|
||||
|
||||
2. **Modify cf_loop_pattern2_with_break** (after ConditionEnvBuilder):
|
||||
```rust
|
||||
// Phase 185: Collect body-local variables
|
||||
let body_locals = collect_body_local_variables(_body, &mut alloc_join_value);
|
||||
let body_local_env = LoopBodyLocalEnv::from_locals(body_locals);
|
||||
|
||||
eprintln!("[pattern2/body-local] Collected {} body-local variables", body_local_env.len());
|
||||
for (name, vid) in body_local_env.iter() {
|
||||
eprintln!(" {} → {:?}", name, vid);
|
||||
}
|
||||
|
||||
// Phase 185: Create UpdateEnv for unified resolution
|
||||
let update_env = UpdateEnv::new(&env, &body_local_env);
|
||||
```
|
||||
|
||||
3. **Update carrier update calls** (search for `emit_carrier_update`):
|
||||
```rust
|
||||
// OLD (Phase 184):
|
||||
// let update_value = emit_carrier_update(&carrier, &update_expr, &mut alloc_join_value, &env, &mut instructions)?;
|
||||
|
||||
// NEW (Phase 185):
|
||||
use crate::mir::join_ir::lowering::carrier_update_emitter::emit_carrier_update_with_env;
|
||||
let update_value = emit_carrier_update_with_env(
|
||||
&carrier,
|
||||
&update_expr,
|
||||
&mut alloc_join_value,
|
||||
&update_env, // ✅ UpdateEnv instead of ConditionEnv
|
||||
&mut instructions,
|
||||
)?;
|
||||
```
|
||||
|
||||
4. **Add imports**:
|
||||
```rust
|
||||
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||||
use crate::mir::join_ir::lowering::update_env::UpdateEnv;
|
||||
use crate::mir::join_ir::lowering::carrier_update_emitter::emit_carrier_update_with_env;
|
||||
```
|
||||
|
||||
**Estimate**: 1 hour (straightforward, follow Phase 184 design)
|
||||
|
||||
### Task 185-3: Pattern4 Integration (Minimal)
|
||||
|
||||
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
|
||||
|
||||
**Changes**: Same pattern as Pattern2 (copy-paste approach):
|
||||
1. Add `collect_body_local_variables()` helper
|
||||
2. Create `LoopBodyLocalEnv` after ConditionEnvBuilder
|
||||
3. Create `UpdateEnv`
|
||||
4. Replace `emit_carrier_update()` with `emit_carrier_update_with_env()`
|
||||
|
||||
**Constraint**: Only int carriers (string filter from Phase 178 remains active)
|
||||
|
||||
**Estimate**: 45 minutes (copy from Pattern2, minimal changes)
|
||||
|
||||
### Task 185-4: Test Cases
|
||||
|
||||
#### Existing Tests (Reuse)
|
||||
|
||||
1. **phase184_body_local_update.hako** (Pattern1)
|
||||
- Already passing (Pattern1 uses UpdateEnv)
|
||||
- Verification: `NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase184_body_local_update.hako`
|
||||
|
||||
2. **phase184_body_local_with_break.hako** (Pattern2)
|
||||
- Currently blocked (Pattern2 not integrated yet)
|
||||
- **Will pass after Task 185-2**
|
||||
|
||||
#### New Test: JsonParser Mini Pattern
|
||||
|
||||
**File**: `apps/tests/phase185_p2_body_local_int_min.hako`
|
||||
|
||||
```nyash
|
||||
// Minimal JsonParser-style loop with body-local integer calculation
|
||||
static box Main {
|
||||
main() {
|
||||
local sum = 0
|
||||
local pos = 0
|
||||
local start = 0
|
||||
local end = 5
|
||||
|
||||
// Pattern2: break loop with body-local digit_pos
|
||||
loop(pos < end) {
|
||||
local digit_pos = pos - start // Body-local calculation
|
||||
sum = sum * 10
|
||||
sum = sum + digit_pos // Use body-local in update
|
||||
pos = pos + 1
|
||||
|
||||
if (sum > 50) break // Break condition
|
||||
}
|
||||
|
||||
print(sum) // Expected: 0*10+0 → 0*10+1 → 1*10+2 → 12*10+3 → 123 → break
|
||||
// Output: 123 (breaks before digit_pos=4)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Expected behavior**:
|
||||
- Pattern2 detection: ✅ (has break, no continue)
|
||||
- Body-local collection: `digit_pos → ValueId(X)`
|
||||
- UpdateEnv resolution: `digit_pos` found in LoopBodyLocalEnv
|
||||
- Update emission: `sum = sum + digit_pos` → BinOp instruction
|
||||
- Execution: Output `123`
|
||||
|
||||
**Validation commands**:
|
||||
```bash
|
||||
# Build
|
||||
cargo build --release
|
||||
|
||||
# Structure trace
|
||||
NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune apps/tests/phase185_p2_body_local_int_min.hako
|
||||
|
||||
# Full execution
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase185_p2_body_local_int_min.hako
|
||||
```
|
||||
|
||||
#### String Concatenation Test (Fail-Fast Verification)
|
||||
|
||||
**File**: `apps/tests/phase185_p2_string_concat_rejected.hako`
|
||||
|
||||
```nyash
|
||||
// Verify Phase 178 Fail-Fast is maintained (string concat still rejected)
|
||||
static box Main {
|
||||
main() {
|
||||
local result = ""
|
||||
local i = 0
|
||||
|
||||
loop(i < 3) {
|
||||
local ch = "a"
|
||||
result = result + ch // ❌ Should be rejected (string concat)
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
print(result)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Expected behavior**:
|
||||
- Pattern2 can_lower: ❌ Rejected (string/complex update detected)
|
||||
- Error message: `[pattern2/can_lower] Phase 178: String/complex update detected, rejecting Pattern 2 (unsupported)`
|
||||
- Build: ✅ Succeeds (compilation)
|
||||
- Runtime: ❌ Falls back to error (no legacy LoopBuilder)
|
||||
|
||||
### Task 185-5: Documentation Updates
|
||||
|
||||
**Files to update**:
|
||||
|
||||
1. **joinir-architecture-overview.md** (Section 2.2):
|
||||
```markdown
|
||||
### 2.2 条件式ライン(式の箱)
|
||||
|
||||
...
|
||||
|
||||
- **LoopBodyLocalEnv / UpdateEnv / CarrierUpdateEmitter(Phase 184-185)**
|
||||
- **Phase 184**: Infrastructure implementation
|
||||
- **Phase 185**: Integration into Pattern2/4
|
||||
- Pattern2/4 now use UpdateEnv for body-local variable support
|
||||
- String concat still rejected (Phase 178 Fail-Fast maintained)
|
||||
```
|
||||
|
||||
2. **CURRENT_TASK.md** (Update Phase 185 entry):
|
||||
```markdown
|
||||
- [x] **Phase 185: Body-local Pattern2/4 Integration** ✅ (2025-12-09)
|
||||
- Task 185-1: Design document (phase185-body-local-integration.md)
|
||||
- Task 185-2: Pattern2 integration (body-local collection + UpdateEnv)
|
||||
- Task 185-3: Pattern4 integration (minimal, copy from Pattern2)
|
||||
- Task 185-4: Test cases (phase185_p2_body_local_int_min.hako)
|
||||
- Task 185-5: Documentation updates
|
||||
- **成果**: Pattern2/4 now support body-local variables in integer update expressions
|
||||
- **制約**: String concat still rejected (Phase 178 Fail-Fast)
|
||||
- **次ステップ**: Phase 186 (String UpdateKind support)
|
||||
```
|
||||
|
||||
3. **This document** (phase185-body-local-integration.md):
|
||||
- Add "Implementation Complete" section
|
||||
- Record test results
|
||||
- Document any issues found
|
||||
|
||||
---
|
||||
|
||||
## Scope and Constraints
|
||||
|
||||
### In Scope
|
||||
|
||||
1. **Integer carrier updates with body-local variables** ✅
|
||||
- `sum = sum + digit_pos` where `digit_pos` is body-local
|
||||
- Pattern2 (break) and Pattern4 (continue)
|
||||
|
||||
2. **Phase 184 infrastructure integration** ✅
|
||||
- LoopBodyLocalEnv collection
|
||||
- UpdateEnv usage
|
||||
- emit_carrier_update_with_env() calls
|
||||
|
||||
3. **Backward compatibility** ✅
|
||||
- Existing tests must still pass
|
||||
- No changes to Pattern1/Pattern3
|
||||
- No changes to Trim patterns (Pattern5)
|
||||
|
||||
### Out of Scope
|
||||
|
||||
1. **String concatenation** ❌
|
||||
- Phase 178 Fail-Fast is maintained
|
||||
- `result = result + ch` still rejected
|
||||
- Will be Phase 186+ work
|
||||
|
||||
2. **Complex expressions in body-locals** ❌
|
||||
- Method calls: `local ch = s.substring(pos, pos+1)` (limited by JoinIrBuilder)
|
||||
- Will be addressed in Phase 186+
|
||||
|
||||
3. **Condition variable usage of body-locals** ❌
|
||||
- `if (temp > 6) break` where `temp` is body-local (already handled by Phase 183 rejection)
|
||||
|
||||
---
|
||||
|
||||
## Validation Strategy
|
||||
|
||||
### Success Criteria
|
||||
|
||||
1. **Unit tests pass**: All existing carrier_update tests still green ✅
|
||||
2. **Pattern2 integration**: phase184_body_local_with_break.hako executes correctly ✅
|
||||
3. **Pattern4 integration**: Pattern4 tests with body-locals work (if exist) ✅
|
||||
4. **Representative test**: phase185_p2_body_local_int_min.hako outputs `123` ✅
|
||||
5. **Fail-Fast maintained**: phase185_p2_string_concat_rejected.hako rejects correctly ✅
|
||||
6. **No regression**: Trim patterns (phase172_trim_while.hako) still work ✅
|
||||
|
||||
### Test Commands
|
||||
|
||||
```bash
|
||||
# 1. Unit tests
|
||||
cargo test --release --lib pattern2_with_break
|
||||
cargo test --release --lib pattern4_with_continue
|
||||
cargo test --release --lib carrier_update
|
||||
|
||||
# 2. Integration tests
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase184_body_local_update.hako
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase184_body_local_with_break.hako
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase185_p2_body_local_int_min.hako
|
||||
|
||||
# 3. Fail-Fast verification
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase185_p2_string_concat_rejected.hako 2>&1 | grep "String/complex update detected"
|
||||
|
||||
# 4. Regression check
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase172_trim_while.hako
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Design Principles
|
||||
|
||||
### Box Theory Compliance
|
||||
|
||||
1. **Single Responsibility**:
|
||||
- LoopBodyLocalEnv: Storage only
|
||||
- UpdateEnv: Resolution only
|
||||
- CarrierUpdateEmitter: Emission only
|
||||
|
||||
2. **Clear Boundaries**:
|
||||
- ConditionEnv vs LoopBodyLocalEnv (distinct scopes)
|
||||
- UpdateEnv composition (no ownership, just references)
|
||||
|
||||
3. **Deterministic**:
|
||||
- BTreeMap in LoopBodyLocalEnv (consistent ordering)
|
||||
- Priority order in UpdateEnv (condition → body-local)
|
||||
|
||||
4. **Conservative**:
|
||||
- No changes to Trim/Pattern5 logic
|
||||
- String concat still rejected (Phase 178 Fail-Fast)
|
||||
|
||||
### Fail-Fast Principle
|
||||
|
||||
**From Phase 178**: Reject unsupported patterns explicitly, not silently.
|
||||
|
||||
**Maintained in Phase 185**:
|
||||
- String concat → Explicit error in can_lower()
|
||||
- Complex expressions → Error from JoinIrBuilder
|
||||
- Shadowing → Error from UpdateEnv priority logic
|
||||
|
||||
**No fallback to LoopBuilder** (deleted in Phase 187).
|
||||
|
||||
---
|
||||
|
||||
## Known Limitations
|
||||
|
||||
### Not Supported (By Design)
|
||||
|
||||
1. **String concatenation**:
|
||||
```nyash
|
||||
result = result + ch // ❌ Still rejected (Phase 178)
|
||||
```
|
||||
|
||||
2. **Body-local in conditions**:
|
||||
```nyash
|
||||
loop(i < 5) {
|
||||
local temp = i * 2
|
||||
if (temp > 6) break // ❌ Already rejected (Phase 183)
|
||||
}
|
||||
```
|
||||
|
||||
3. **Complex init expressions**:
|
||||
```nyash
|
||||
local temp = s.substring(pos, pos+1) // ⚠️ Limited by JoinIrBuilder
|
||||
```
|
||||
|
||||
### Will Be Addressed
|
||||
|
||||
- **Phase 186**: String UpdateKind support (careful, gradual)
|
||||
- **Phase 187**: Method call support in body-local init
|
||||
- **Phase 188**: Full JsonParser loop coverage
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### collect_body_local_variables() Helper
|
||||
|
||||
**Design decision**: Keep it simple, only collect `LocalDecl` nodes.
|
||||
|
||||
**Why not more complex?**:
|
||||
- Body-local variables are explicitly declared with `local` keyword
|
||||
- No need to track assignments (that's carrier analysis)
|
||||
- No need to track scopes (loop body is single scope)
|
||||
|
||||
**Alternative approaches considered**:
|
||||
1. Reuse LoopScopeShapeBuilder logic ❌ (too heavyweight, circular dependency)
|
||||
2. Scan all variable references ❌ (over-complex, not needed)
|
||||
3. Simple LocalDecl scan ✅ (chosen - sufficient, clean)
|
||||
|
||||
### UpdateEnv vs ConditionEnv
|
||||
|
||||
**Why not extend ConditionEnv?**:
|
||||
- Separation of concerns (condition variables vs body-locals are conceptually different)
|
||||
- Composition over inheritance (UpdateEnv composes two environments)
|
||||
- Backward compatibility (ConditionEnv unchanged, existing code still works)
|
||||
|
||||
### emit_carrier_update_with_env vs emit_carrier_update
|
||||
|
||||
**Why two functions?**:
|
||||
- Backward compatibility (old code uses ConditionEnv directly)
|
||||
- Clear API contract (with_env = supports body-locals, without = condition only)
|
||||
- Gradual migration (Pattern1/3 can stay with old API, Pattern2/4 migrate)
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- **Phase 184**: LoopBodyLocalEnv/UpdateEnv/CarrierUpdateEmitter infrastructure
|
||||
- **Phase 183**: LoopBodyLocal role separation (condition vs body-only)
|
||||
- **Phase 178**: String carrier rejection (Fail-Fast principle)
|
||||
- **Phase 171-C**: LoopBodyCarrierPromoter (Trim pattern handling)
|
||||
- **pattern2_with_break.rs**: Current Pattern2 implementation
|
||||
- **pattern4_with_continue.rs**: Current Pattern4 implementation
|
||||
- **carrier_update_emitter.rs**: Update emission logic
|
||||
|
||||
---
|
||||
|
||||
## Implementation Status (2025-12-09)
|
||||
|
||||
### ✅ Completed
|
||||
|
||||
1. **Task 185-1**: Design document created ✅
|
||||
2. **Task 185-2**: Pattern2 integration skeleton completed ✅
|
||||
- `collect_body_local_variables()` helper added
|
||||
- `body_local_env` parameter added to `lower_loop_with_break_minimal`
|
||||
- `emit_carrier_update_with_env()` integration
|
||||
- Build succeeds (no compilation errors)
|
||||
|
||||
3. **Task 185-3**: Pattern4 deferred ✅ (different architecture, inline lowering)
|
||||
|
||||
### ❌ Blocked
|
||||
|
||||
**Task 185-4**: Test execution BLOCKED by missing body-local init lowering
|
||||
|
||||
**Error**: `use of undefined value ValueId(11)` for body-local variable `digit_pos`
|
||||
|
||||
**Root cause**: Phase 184 implemented storage/resolution infrastructure but left **initialization lowering** unimplemented.
|
||||
|
||||
**What's missing**:
|
||||
1. Body-local init expression lowering (`local digit_pos = pos - start`)
|
||||
2. JoinIR instruction generation for init expressions
|
||||
3. Insertion of init instructions in loop body
|
||||
|
||||
**Current behavior**:
|
||||
- ✅ Variables are collected (name → ValueId mapping)
|
||||
- ✅ UpdateEnv can resolve body-local variable names
|
||||
- ❌ Init expressions are NOT lowered to JoinIR
|
||||
- ❌ ValueIds are allocated but never defined
|
||||
|
||||
**Evidence**:
|
||||
```
|
||||
[pattern2/body-local] Collected local 'digit_pos' → ValueId(2) ✅ Name mapping OK
|
||||
[pattern2/body-local] Phase 185-2: Collected 1 body-local variables ✅ Collection OK
|
||||
[ERROR] use of undefined value ValueId(11) ❌ Init not lowered
|
||||
```
|
||||
|
||||
### Scope Clarification
|
||||
|
||||
**Phase 184** scope:
|
||||
- LoopBodyLocalEnv (storage) ✅
|
||||
- UpdateEnv (resolution) ✅
|
||||
- emit_carrier_update_with_env() (emission) ✅
|
||||
- **Body-local init lowering**: ⚠️ NOT IMPLEMENTED
|
||||
|
||||
**Phase 185** intended scope:
|
||||
- Pattern2/4 integration ✅ (Pattern2 skeleton done)
|
||||
- **Assumed** init lowering was in Phase 184 ❌ (incorrect assumption)
|
||||
|
||||
**Actual blocker**: Init lowering is **Phase 186 work**, not Phase 185.
|
||||
|
||||
---
|
||||
|
||||
## Next Phase: Phase 186 - Body-local Init Lowering
|
||||
|
||||
### Goal
|
||||
|
||||
Implement body-local variable initialization lowering to make Phase 185 integration functional.
|
||||
|
||||
### Required Changes
|
||||
|
||||
#### 1. Modify collect_body_local_variables()
|
||||
|
||||
**Current** (Phase 185):
|
||||
```rust
|
||||
fn collect_body_local_variables(body: &[ASTNode], alloc: &mut dyn FnMut() -> ValueId) -> Vec<(String, ValueId)> {
|
||||
// Only allocates ValueIds, doesn't lower init expressions
|
||||
for node in body {
|
||||
if let ASTNode::Local { variables, .. } = node {
|
||||
for name in variables {
|
||||
let value_id = alloc(); // Allocated but never defined!
|
||||
locals.push((name.clone(), value_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Needed** (Phase 186):
|
||||
```rust
|
||||
fn collect_and_lower_body_locals(
|
||||
body: &[ASTNode],
|
||||
env: &ConditionEnv,
|
||||
alloc: &mut dyn FnMut() -> ValueId,
|
||||
instructions: &mut Vec<JoinInst>, // Need to emit init instructions!
|
||||
) -> Result<Vec<(String, ValueId)>, String> {
|
||||
for node in body {
|
||||
if let ASTNode::Local { variables, initial_values, .. } = node {
|
||||
for (name, init_expr_opt) in variables.iter().zip(initial_values.iter()) {
|
||||
if let Some(init_expr) = init_expr_opt {
|
||||
// Lower init expression to JoinIR
|
||||
let init_value_id = lower_expr_to_joinir(init_expr, env, alloc, instructions)?;
|
||||
locals.push((name.clone(), init_value_id));
|
||||
} else {
|
||||
// No init: allocate but leave undefined (or use Void constant)
|
||||
let value_id = alloc();
|
||||
locals.push((name.clone(), value_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Add Expression Lowerer
|
||||
|
||||
Need a helper function to lower AST expressions to JoinIR:
|
||||
```rust
|
||||
fn lower_expr_to_joinir(
|
||||
expr: &ASTNode,
|
||||
env: &ConditionEnv,
|
||||
alloc: &mut dyn FnMut() -> ValueId,
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String> {
|
||||
match expr {
|
||||
ASTNode::BinOp { op, left, right, .. } => {
|
||||
let lhs = lower_expr_to_joinir(left, env, alloc, instructions)?;
|
||||
let rhs = lower_expr_to_joinir(right, env, alloc, instructions)?;
|
||||
let result = alloc();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: result,
|
||||
op: map_binop(op),
|
||||
lhs,
|
||||
rhs,
|
||||
}));
|
||||
Ok(result)
|
||||
}
|
||||
ASTNode::Variable { name, .. } => {
|
||||
env.get(name).ok_or_else(|| format!("Variable '{}' not in scope", name))
|
||||
}
|
||||
// ... handle other expression types
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Update lower_loop_with_break_minimal
|
||||
|
||||
Insert body-local init instructions at the start of loop_step function:
|
||||
```rust
|
||||
// After allocating loop_step parameters, before break condition:
|
||||
if let Some(body_env) = body_local_env {
|
||||
// Emit body-local init instructions
|
||||
for (name, value_id) in body_env.iter() {
|
||||
// Init instructions already emitted by collect_and_lower_body_locals
|
||||
// Just log for debugging
|
||||
eprintln!("[loop_step] Body-local '{}' initialized as {:?}", name, value_id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Estimate
|
||||
|
||||
- Helper function (lower_expr_to_joinir): 2-3 hours (complex, many AST variants)
|
||||
- collect_and_lower_body_locals refactor: 1 hour
|
||||
- Integration into lower_loop_with_break_minimal: 1 hour
|
||||
- Testing and debugging: 2 hours
|
||||
|
||||
**Total**: 6-7 hours for Phase 186
|
||||
|
||||
### Alternative: Simplified Scope
|
||||
|
||||
If full expression lowering is too complex, **Phase 186-simple** could:
|
||||
1. Only support **variable references** in body-local init (no binops)
|
||||
- `local temp = i` ✅
|
||||
- `local temp = i + 1` ❌ (Phase 187)
|
||||
2. Implement just variable copying
|
||||
3. Get tests passing with simple cases
|
||||
4. Defer complex expressions to Phase 187
|
||||
|
||||
**Estimate for Phase 186-simple**: 2-3 hours
|
||||
|
||||
---
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
1. **Phase 184 scope was incomplete**: Infrastructure without lowering is not functional
|
||||
2. **Testing earlier would have caught this**: Phase 184 should have had E2E test
|
||||
3. **Phase 185 assumption was wrong**: Assumed init lowering was done, it wasn't
|
||||
4. **Clear scope boundaries needed**: "Infrastructure" vs "Full implementation"
|
||||
@ -1,531 +0,0 @@
|
||||
# Phase 186: Body-local Init Lowering (箱化モジュール化)
|
||||
|
||||
**Status**: In Progress
|
||||
**Date**: 2025-12-09
|
||||
**Dependencies**: Phase 184 (LoopBodyLocalEnv), Phase 185 (Pattern2 integration)
|
||||
|
||||
## Overview
|
||||
|
||||
Phase 186 introduces **LoopBodyLocalInitLowerer** - a dedicated box for lowering body-local variable initialization expressions to JoinIR. This completes the body-local variable support by handling initialization expressions like `local digit_pos = pos - start`.
|
||||
|
||||
## Motivation
|
||||
|
||||
Phase 184 introduced LoopBodyLocalEnv to track body-local variables, and Phase 185 integrated it into Pattern2 for update expressions. However, **initialization expressions** were not yet lowered to JoinIR:
|
||||
|
||||
```nyash
|
||||
loop(pos < 10) {
|
||||
local digit_pos = pos - start // ← Init expression NOT lowered yet!
|
||||
sum = sum + digit_pos // ← Update expression (Phase 184)
|
||||
pos = pos + 1
|
||||
}
|
||||
```
|
||||
|
||||
**Problems without Phase 186**:
|
||||
- `digit_pos` was declared in LoopBodyLocalEnv but had no JoinIR ValueId
|
||||
- Using `digit_pos` in update expressions failed with "variable not found"
|
||||
- Body-local calculations couldn't be performed in JoinIR
|
||||
|
||||
**Phase 186 Solution**:
|
||||
- Lower init expressions (`pos - start`) to JoinIR instructions
|
||||
- Assign JoinIR ValueId to body-local variable in env
|
||||
- Enable body-local variables to be used in subsequent update expressions
|
||||
|
||||
## Scope Definition
|
||||
|
||||
### In Scope (Phase 186)
|
||||
|
||||
**Supported init expressions** (int/arithmetic only):
|
||||
- Binary operations: `+`, `-`, `*`, `/`
|
||||
- Constant literals: `42`, `0`, `1`
|
||||
- Variable references: `pos`, `start`, `i`
|
||||
|
||||
**Examples**:
|
||||
```nyash
|
||||
local digit_pos = pos - start // ✅ BinOp + Variables
|
||||
local temp = i * 2 // ✅ BinOp + Variable + Const
|
||||
local offset = base + 10 // ✅ BinOp + Variable + Const
|
||||
local cnt = i + 1 // ✅ BinOp + Variable + Const
|
||||
```
|
||||
|
||||
### Out of Scope (Phase 186)
|
||||
|
||||
**NOT supported** (Fail-Fast with explicit error):
|
||||
- String operations: `s.substring(...)`, `s + "abc"`
|
||||
- Method calls: `box.method(...)`
|
||||
- Complex expressions: nested BinOps, function calls
|
||||
|
||||
**Examples**:
|
||||
```nyash
|
||||
local ch = s.substring(pos, 1) // ❌ Method call → Fail-Fast error
|
||||
local msg = "Error: " + text // ❌ String concat → Fail-Fast error
|
||||
local result = calc(a, b) // ❌ Function call → Fail-Fast error
|
||||
```
|
||||
|
||||
**Rationale**: Phase 178 established Fail-Fast principle for unsupported features. String/method call support requires additional infrastructure (BoxCall lowering, type tracking) - defer to future phases.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Box Theory Design
|
||||
|
||||
Following 箱理論 (Box-First) principles:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ LoopBodyLocalInitLowerer (NEW) │
|
||||
│ - Single responsibility: Lower init expressions to JoinIR │
|
||||
│ - Clear boundary: Only handles init, not updates │
|
||||
│ - Fail-Fast: Unsupported expressions → explicit error │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓ (uses)
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ LoopBodyLocalEnv (Phase 184) │
|
||||
│ - Storage box for body-local variable mappings │
|
||||
│ - name → JoinIR ValueId │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓ (used by)
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ CarrierUpdateEmitter (Phase 184) │
|
||||
│ - Emits update instructions using UpdateEnv │
|
||||
│ - Resolves variables from condition + body-local envs │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Pipeline Integration
|
||||
|
||||
**Pattern2 Pipeline** (Phase 179-B + Phase 186):
|
||||
```
|
||||
1. Build PatternPipelineContext (loop features, carriers)
|
||||
2. LoopConditionScopeBox::analyze() → ConditionEnv
|
||||
3. ⭐ LoopBodyLocalInitLowerer::lower_inits_for_loop() ← NEW (Phase 186)
|
||||
- Scans body AST for local declarations
|
||||
- Lowers init expressions to JoinIR
|
||||
- Updates LoopBodyLocalEnv with ValueIds
|
||||
4. LoopUpdateAnalyzer::analyze_carrier_updates()
|
||||
5. CarrierUpdateEmitter::emit_carrier_update_with_env()
|
||||
6. JoinModule construction + MIR merge
|
||||
```
|
||||
|
||||
**Pattern4 Pipeline** (similar integration):
|
||||
```
|
||||
1-2. (same as Pattern2)
|
||||
3. ⭐ LoopBodyLocalInitLowerer::lower_inits_for_loop() ← NEW
|
||||
4. ContinueBranchNormalizer (Pattern4-specific)
|
||||
5-6. (same as Pattern2)
|
||||
```
|
||||
|
||||
## Module Design
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
src/mir/join_ir/lowering/
|
||||
├── loop_body_local_env.rs (Phase 184 - Storage box)
|
||||
├── loop_body_local_init.rs (Phase 186 - NEW! Init lowerer)
|
||||
├── update_env.rs (Phase 184 - Resolution layer)
|
||||
└── carrier_update_emitter.rs (Phase 184 - Update emitter)
|
||||
```
|
||||
|
||||
### LoopBodyLocalInitLowerer API
|
||||
|
||||
```rust
|
||||
//! Phase 186: Loop Body-Local Variable Initialization Lowerer
|
||||
//!
|
||||
//! Lowers body-local variable initialization expressions to JoinIR.
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||
use crate::mir::join_ir::{JoinInst, MirLikeInst, ConstValue, BinOpKind};
|
||||
use crate::mir::ValueId;
|
||||
|
||||
pub struct LoopBodyLocalInitLowerer<'a> {
|
||||
/// Reference to ConditionEnv for variable resolution
|
||||
cond_env: &'a ConditionEnv,
|
||||
|
||||
/// Output buffer for JoinIR instructions
|
||||
instructions: &'a mut Vec<JoinInst>,
|
||||
|
||||
/// ValueId allocator
|
||||
alloc_value: Box<dyn FnMut() -> ValueId + 'a>,
|
||||
}
|
||||
|
||||
impl<'a> LoopBodyLocalInitLowerer<'a> {
|
||||
/// Create a new init lowerer
|
||||
pub fn new(
|
||||
cond_env: &'a ConditionEnv,
|
||||
instructions: &'a mut Vec<JoinInst>,
|
||||
alloc_value: Box<dyn FnMut() -> ValueId + 'a>,
|
||||
) -> Self {
|
||||
Self {
|
||||
cond_env,
|
||||
instructions,
|
||||
alloc_value,
|
||||
}
|
||||
}
|
||||
|
||||
/// Lower all body-local initializations in loop body
|
||||
///
|
||||
/// Scans body AST for local declarations, lowers init expressions,
|
||||
/// and updates LoopBodyLocalEnv with computed ValueIds.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `body_ast` - Loop body AST nodes
|
||||
/// * `env` - LoopBodyLocalEnv to update with ValueIds
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Ok(()) on success, Err(msg) if unsupported expression found
|
||||
pub fn lower_inits_for_loop(
|
||||
&mut self,
|
||||
body_ast: &[ASTNode],
|
||||
env: &mut LoopBodyLocalEnv,
|
||||
) -> Result<(), String> {
|
||||
for node in body_ast {
|
||||
if let ASTNode::LocalAssign { variables, values, .. } = node {
|
||||
self.lower_single_init(variables, values, env)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lower a single local assignment
|
||||
fn lower_single_init(
|
||||
&mut self,
|
||||
variables: &[String],
|
||||
values: &[ASTNode],
|
||||
env: &mut LoopBodyLocalEnv,
|
||||
) -> Result<(), String> {
|
||||
// Handle each variable-value pair
|
||||
for (var_name, init_expr) in variables.iter().zip(values.iter()) {
|
||||
// Skip if already has JoinIR ValueId (avoid duplicate lowering)
|
||||
if env.get(var_name).is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Lower init expression to JoinIR
|
||||
let value_id = self.lower_init_expr(init_expr)?;
|
||||
|
||||
// Store in env
|
||||
env.insert(var_name.clone(), value_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lower an initialization expression to JoinIR
|
||||
///
|
||||
/// Supported:
|
||||
/// - BinOp(+, -, *, /) with Variable/Const operands
|
||||
/// - Const (integer literal)
|
||||
/// - Variable (condition variable reference)
|
||||
///
|
||||
/// Unsupported (Fail-Fast):
|
||||
/// - String operations, method calls, complex expressions
|
||||
fn lower_init_expr(&mut self, expr: &ASTNode) -> Result<ValueId, String> {
|
||||
match expr {
|
||||
// Constant integer
|
||||
ASTNode::Integer { value, .. } => {
|
||||
let vid = (self.alloc_value)();
|
||||
self.instructions.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: vid,
|
||||
value: ConstValue::Integer(*value),
|
||||
}));
|
||||
Ok(vid)
|
||||
}
|
||||
|
||||
// Variable reference (from ConditionEnv)
|
||||
ASTNode::Variable { name, .. } => {
|
||||
self.cond_env
|
||||
.get(name)
|
||||
.ok_or_else(|| format!("Init variable '{}' not found in ConditionEnv", name))
|
||||
}
|
||||
|
||||
// Binary operation
|
||||
ASTNode::BinOp { op, left, right, .. } => {
|
||||
let lhs = self.lower_init_expr(left)?;
|
||||
let rhs = self.lower_init_expr(right)?;
|
||||
|
||||
let op_kind = self.convert_binop(op)?;
|
||||
|
||||
let result = (self.alloc_value)();
|
||||
self.instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: result,
|
||||
op: op_kind,
|
||||
lhs,
|
||||
rhs,
|
||||
}));
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// Fail-Fast for unsupported expressions
|
||||
ASTNode::MethodCall { .. } => {
|
||||
Err("Unsupported init expression: method call (Phase 186 limitation)".to_string())
|
||||
}
|
||||
ASTNode::String { .. } => {
|
||||
Err("Unsupported init expression: string literal (Phase 186 limitation)".to_string())
|
||||
}
|
||||
_ => {
|
||||
Err(format!("Unsupported init expression: {:?} (Phase 186 limitation)", expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert AST BinOp to JoinIR BinOpKind
|
||||
fn convert_binop(&self, op: &str) -> Result<BinOpKind, String> {
|
||||
match op {
|
||||
"+" => Ok(BinOpKind::Add),
|
||||
"-" => Ok(BinOpKind::Sub),
|
||||
"*" => Ok(BinOpKind::Mul),
|
||||
"/" => Ok(BinOpKind::Div),
|
||||
_ => Err(format!("Unsupported binary operator in init: {}", op)),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Pattern2 Integration (pattern2_with_break.rs)
|
||||
|
||||
**Before Phase 186**:
|
||||
```rust
|
||||
// cf_loop_pattern2_with_break()
|
||||
let ctx = build_pattern_context(...)?;
|
||||
let body_locals = collect_body_local_variables(...);
|
||||
let body_local_env = LoopBodyLocalEnv::from_locals(body_locals);
|
||||
// ❌ body_local_env has no ValueIds yet!
|
||||
```
|
||||
|
||||
**After Phase 186**:
|
||||
```rust
|
||||
// cf_loop_pattern2_with_break()
|
||||
let ctx = build_pattern_context(...)?;
|
||||
|
||||
// 1. Collect body-local variable names (allocate placeholder ValueIds)
|
||||
let body_locals = collect_body_local_variables(...);
|
||||
let mut body_local_env = LoopBodyLocalEnv::from_locals(body_locals);
|
||||
|
||||
// 2. ⭐ Lower init expressions to JoinIR
|
||||
let mut init_lowerer = LoopBodyLocalInitLowerer::new(
|
||||
&ctx.condition_env,
|
||||
&mut join_instructions,
|
||||
Box::new(|| alloc_join_value()),
|
||||
);
|
||||
init_lowerer.lower_inits_for_loop(body, &mut body_local_env)?;
|
||||
// ✅ body_local_env now has JoinIR ValueIds!
|
||||
|
||||
// 3. Proceed with update analysis and emission
|
||||
let updates = LoopUpdateAnalyzer::analyze_carrier_updates(...);
|
||||
let update_env = UpdateEnv::new(&ctx.condition_env, &body_local_env);
|
||||
for (carrier, update) in updates {
|
||||
emit_carrier_update_with_env(&carrier, &update, ..., &update_env, ...)?;
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern4 Integration (pattern4_with_continue.rs)
|
||||
|
||||
Similar to Pattern2 - insert init lowering step after condition analysis and before update analysis.
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Fail-Fast Principle (Phase 178)
|
||||
|
||||
Following Phase 178 design - reject unsupported features early with clear error messages:
|
||||
|
||||
```rust
|
||||
// String operation detection
|
||||
if matches!(init_expr, ASTNode::MethodCall { .. }) {
|
||||
return Err("Unsupported: string/method call in body-local init (use Rust MIR path)".to_string());
|
||||
}
|
||||
|
||||
// Type mismatch detection
|
||||
if !is_int_compatible(init_expr) {
|
||||
return Err(format!("Unsupported: body-local init must be int/arithmetic, got {:?}", init_expr));
|
||||
}
|
||||
```
|
||||
|
||||
**Error Message Format**:
|
||||
```
|
||||
Error: Unsupported init expression: method call (Phase 186 limitation)
|
||||
Hint: Body-local init only supports int/arithmetic (BinOp, Const, Variable)
|
||||
For string operations, use Rust MIR path instead of JoinIR
|
||||
```
|
||||
|
||||
## Test Strategy
|
||||
|
||||
### Unit Tests
|
||||
|
||||
**File**: `src/mir/join_ir/lowering/loop_body_local_init.rs` (inline tests)
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_lower_const_init() {
|
||||
// local temp = 42
|
||||
// Should emit: Const(42)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lower_binop_init() {
|
||||
// local digit_pos = pos - start
|
||||
// Should emit: BinOp(Sub, pos_vid, start_vid)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fail_fast_method_call() {
|
||||
// local ch = s.substring(0, 1)
|
||||
// Should return Err("Unsupported: method call ...")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fail_fast_string_concat() {
|
||||
// local msg = "Error: " + text
|
||||
// Should return Err("Unsupported: string literal ...")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
**New test file**: `apps/tests/phase186_p2_body_local_digit_pos_min.hako`
|
||||
```nyash
|
||||
static box Test {
|
||||
main(start, pos) {
|
||||
local sum = 0
|
||||
loop (pos < 10) {
|
||||
local digit_pos = pos - start // Body-local init
|
||||
if digit_pos >= 3 { break }
|
||||
sum = sum + digit_pos // Use body-local
|
||||
pos = pos + 1
|
||||
}
|
||||
return sum
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Expected behavior**:
|
||||
- `digit_pos = 0 - 0 = 0` → sum = 0
|
||||
- `digit_pos = 1 - 0 = 1` → sum = 1
|
||||
- `digit_pos = 2 - 0 = 2` → sum = 3
|
||||
- `digit_pos = 3 - 0 = 3` → break (3 >= 3)
|
||||
- Final sum: 3
|
||||
|
||||
**Regression tests** (ensure Phase 184/185 still work):
|
||||
- `phase184_body_local_update.hako` (basic update)
|
||||
- `phase184_body_local_with_break.hako` (break condition)
|
||||
- `phase185_p2_body_local_int_min.hako` (JsonParser-style)
|
||||
|
||||
### Fail-Fast Tests
|
||||
|
||||
**Test file**: `apps/tests/phase186_fail_fast_string_init.hako` (expected to fail)
|
||||
```nyash
|
||||
static box Test {
|
||||
main() {
|
||||
local s = "hello"
|
||||
loop (true) {
|
||||
local ch = s.substring(0, 1) // ❌ Should fail with clear error
|
||||
break
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Expected error**:
|
||||
```
|
||||
Error: Unsupported init expression: method call (Phase 186 limitation)
|
||||
```
|
||||
|
||||
## Validation Commands
|
||||
|
||||
```bash
|
||||
# Build
|
||||
cargo build --release
|
||||
|
||||
# Unit tests
|
||||
cargo test --release --lib loop_body_local_init
|
||||
|
||||
# Integration test
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune \
|
||||
apps/tests/phase186_p2_body_local_digit_pos_min.hako
|
||||
|
||||
# Regression tests
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune \
|
||||
apps/tests/phase184_body_local_update.hako
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune \
|
||||
apps/tests/phase185_p2_body_local_int_min.hako
|
||||
|
||||
# Fail-Fast test (should error)
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune \
|
||||
apps/tests/phase186_fail_fast_string_init.hako
|
||||
```
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- ✅ Body-local init expressions lower to JoinIR (int/arithmetic only)
|
||||
- ✅ Init ValueIds stored in LoopBodyLocalEnv
|
||||
- ✅ Body-local variables usable in update expressions
|
||||
- ✅ Pattern2/4 integration complete
|
||||
- ✅ Fail-Fast for unsupported expressions (string/method call)
|
||||
|
||||
### Quality Requirements
|
||||
|
||||
- ✅ Box-First design (single responsibility, clear boundaries)
|
||||
- ✅ No regression in existing tests (Phase 184/185)
|
||||
- ✅ Clear error messages for unsupported features
|
||||
- ✅ Deterministic behavior (BTreeMap-based)
|
||||
|
||||
### Documentation Requirements
|
||||
|
||||
- ✅ Design doc (this file)
|
||||
- ✅ API documentation (inline rustdoc)
|
||||
- ✅ Architecture update (joinir-architecture-overview.md)
|
||||
- ✅ CURRENT_TASK.md update
|
||||
|
||||
## Future Work (Out of Scope)
|
||||
|
||||
### Phase 187+: String/Method Call Init Support
|
||||
|
||||
```nyash
|
||||
loop(...) {
|
||||
local ch = s.substring(pos, 1) // Future: BoxCall lowering
|
||||
local msg = "Error: " + text // Future: String concat lowering
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**Requirements**:
|
||||
- BoxCall lowering to JoinIR
|
||||
- Type tracking for Box values
|
||||
- String operation support in JoinIR
|
||||
|
||||
### Phase 190+: Complex Init Expressions
|
||||
|
||||
```nyash
|
||||
loop(...) {
|
||||
local result = (a + b) * (c - d) // Nested BinOps
|
||||
local value = calc(x, y) // Function calls
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**Requirements**:
|
||||
- Recursive expression lowering
|
||||
- Function call lowering to JoinIR
|
||||
|
||||
## References
|
||||
|
||||
- **Phase 184**: LoopBodyLocalEnv introduction
|
||||
- **Phase 185**: Pattern2 integration with body-local variables
|
||||
- **Phase 178**: Fail-Fast principle for unsupported features
|
||||
- **Phase 179-B**: Pattern2 pipeline architecture
|
||||
- **Box Theory**: Single responsibility, clear boundaries, determinism
|
||||
|
||||
## Changelog
|
||||
|
||||
- **2025-12-09**: Initial design document created
|
||||
@ -1,412 +0,0 @@
|
||||
# Phase 187: String UpdateLowering Design (Doc-Only)
|
||||
|
||||
**Date**: 2025-12-09
|
||||
**Status**: Design Phase (No Code Changes)
|
||||
**Prerequisite**: Phase 178 Fail-Fast must remain intact
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Phase 187 defines **what kinds of string updates are safe to handle in JoinIR**, using an UpdateKind-based whitelist approach. This is a design-only phase—no code will be changed.
|
||||
|
||||
**Core Principle**: Maintain Phase 178's Fail-Fast behavior while establishing a clear path forward for string operations.
|
||||
|
||||
---
|
||||
|
||||
## 1. UpdateKind Candidates
|
||||
|
||||
We classify update patterns into categories based on their complexity and safety:
|
||||
|
||||
### 1.1 Safe Patterns (Whitelist Candidates)
|
||||
|
||||
#### CounterLike
|
||||
**Pattern**: `pos = pos + 1`, `i = i - 1`
|
||||
**String Relevance**: Position tracking in string scanning loops
|
||||
**Safety**: ✅ Simple arithmetic, deterministic
|
||||
**Decision**: **ALLOW** (already supported in Phase 178)
|
||||
|
||||
#### AccumulationLike (Numeric)
|
||||
**Pattern**: `sum = sum + i`, `total = total * factor`
|
||||
**String Relevance**: None (numeric only)
|
||||
**Safety**: ✅ Arithmetic operations, well-understood
|
||||
**Decision**: **ALLOW** (already supported in Phase 178)
|
||||
|
||||
#### StringAppendChar
|
||||
**Pattern**: `result = result + ch` (where `ch` is a single character variable)
|
||||
**Example**: JsonParser `_parse_number`: `num_str = num_str + digit_ch`
|
||||
**Safety**: ⚠️ Requires:
|
||||
- RHS must be `UpdateRhs::Variable(name)`
|
||||
- Variable scope: LoopBodyLocal or OuterLocal
|
||||
- Single character (enforced at runtime by StringBox semantics)
|
||||
**Decision**: **ALLOW** (with validation)
|
||||
|
||||
**Rationale**: This pattern is structurally identical to numeric accumulation:
|
||||
```
|
||||
sum = sum + i // Numeric accumulation
|
||||
result = result + ch // String accumulation (char-by-char)
|
||||
```
|
||||
|
||||
#### StringAppendLiteral
|
||||
**Pattern**: `s = s + "..."` (where `"..."` is a string literal)
|
||||
**Example**: `debug_output = debug_output + "[INFO] "`
|
||||
**Safety**: ⚠️ Requires:
|
||||
- RHS must be `UpdateRhs::StringLiteral(s)`
|
||||
- Literal must be compile-time constant
|
||||
**Decision**: **ALLOW** (with validation)
|
||||
|
||||
**Rationale**: Simpler than StringAppendChar—no variable resolution needed.
|
||||
|
||||
### 1.2 Unsafe Patterns (Fail-Fast)
|
||||
|
||||
#### Complex (Method Calls)
|
||||
**Pattern**: `result = result + s.substring(pos, end)`
|
||||
**Example**: JsonParser `_unescape_string`
|
||||
**Safety**: ❌ Requires:
|
||||
- Method call evaluation
|
||||
- Multiple arguments
|
||||
- Potentially non-deterministic results
|
||||
**Decision**: **REJECT** with `[joinir/freeze]`
|
||||
|
||||
**Error Message**:
|
||||
```
|
||||
[pattern2/can_lower] Complex string update detected (method call in RHS).
|
||||
JoinIR does not support this pattern yet. Use simpler string operations.
|
||||
```
|
||||
|
||||
#### Complex (Nested BinOp)
|
||||
**Pattern**: `x = x + (a + b)`, `result = result + s1 + s2`
|
||||
**Safety**: ❌ Nested expression evaluation required
|
||||
**Decision**: **REJECT** with `[joinir/freeze]`
|
||||
|
||||
---
|
||||
|
||||
## 2. Fail-Fast Policy (Phase 178 Preservation)
|
||||
|
||||
**Non-Negotiable**: Phase 178's Fail-Fast behavior must remain intact.
|
||||
|
||||
### 2.1 Current Fail-Fast Logic (Untouched)
|
||||
|
||||
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`
|
||||
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
|
||||
|
||||
```rust
|
||||
// Phase 178: Reject string/complex updates
|
||||
fn can_lower(...) -> bool {
|
||||
for update in carrier_updates.values() {
|
||||
match update {
|
||||
UpdateExpr::BinOp { rhs, .. } => {
|
||||
if matches!(rhs, UpdateRhs::StringLiteral(_) | UpdateRhs::Other) {
|
||||
// Phase 178: Fail-Fast for string updates
|
||||
return false; // ← This stays unchanged in Phase 187
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
```
|
||||
|
||||
**Phase 187 Changes**: NONE (this code is not touched in Phase 187).
|
||||
|
||||
### 2.2 Future Whitelist Expansion (Phase 188+)
|
||||
|
||||
In **Phase 188** (implementation phase), we will:
|
||||
1. Extend `can_lower()` to accept `StringAppendChar` and `StringAppendLiteral`
|
||||
2. Add validation to ensure safety constraints (variable scope, literal type)
|
||||
3. Extend `CarrierUpdateLowerer` to emit JoinIR for string append operations
|
||||
|
||||
**Phase 187 does NOT implement this**—we only design what "safe" means.
|
||||
|
||||
---
|
||||
|
||||
## 3. Lowerer Responsibility Separation
|
||||
|
||||
### 3.1 Detection Layer (Pattern2/4)
|
||||
|
||||
**Responsibility**: UpdateKind classification only
|
||||
**Location**: `pattern2_with_break.rs`, `pattern4_with_continue.rs`
|
||||
|
||||
```rust
|
||||
// Phase 187 Design: What Pattern2/4 WILL check (future)
|
||||
fn can_lower_string_update(update: &UpdateExpr) -> bool {
|
||||
match update {
|
||||
UpdateExpr::BinOp { rhs, .. } => {
|
||||
match rhs {
|
||||
UpdateRhs::Variable(_) => true, // StringAppendChar
|
||||
UpdateRhs::StringLiteral(_) => true, // StringAppendLiteral
|
||||
UpdateRhs::Other => false, // Complex (reject)
|
||||
UpdateRhs::Const(_) => true, // Numeric (already allowed)
|
||||
}
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Point**: Pattern2/4 only perform classification—they do NOT emit JoinIR for strings.
|
||||
|
||||
### 3.2 Emission Layer (CarrierUpdateLowerer + Expr Lowerer)
|
||||
|
||||
**Responsibility**: Actual JoinIR instruction emission
|
||||
**Location**: `src/mir/join_ir/lowering/carrier_update_lowerer.rs`
|
||||
|
||||
**Current State (Phase 184)**:
|
||||
- Handles numeric carriers only (`CounterLike`, `AccumulationLike`)
|
||||
- Emits `Compute { op: Add/Sub/Mul, ... }` for numeric BinOp
|
||||
|
||||
**Future State (Phase 188+ Implementation)**:
|
||||
- Extend to handle `StringAppendChar`:
|
||||
```rust
|
||||
// Emit StringBox.concat() call or equivalent
|
||||
let concat_result = emit_string_concat(lhs_value, ch_value);
|
||||
```
|
||||
- Extend to handle `StringAppendLiteral`:
|
||||
```rust
|
||||
// Emit string literal + concat
|
||||
let literal_value = emit_string_literal("...");
|
||||
let concat_result = emit_string_concat(lhs_value, literal_value);
|
||||
```
|
||||
|
||||
**Phase 187 Design**: Document this separation, but do NOT implement.
|
||||
|
||||
---
|
||||
|
||||
## 4. Architecture Diagram
|
||||
|
||||
```
|
||||
AST → LoopUpdateAnalyzer → UpdateKind classification
|
||||
↓
|
||||
Pattern2/4.can_lower()
|
||||
(Whitelist check only)
|
||||
↓
|
||||
[ALLOW] → CarrierUpdateLowerer
|
||||
(Emit JoinIR instructions)
|
||||
↓
|
||||
JoinIR Module
|
||||
|
||||
[REJECT] → [joinir/freeze] error
|
||||
```
|
||||
|
||||
**Separation of Concerns**:
|
||||
1. **LoopUpdateAnalyzer**: Extracts `UpdateExpr` from AST (already exists)
|
||||
2. **Pattern2/4**: Classifies into Allow/Reject (Phase 178 logic + Phase 188 extension)
|
||||
3. **CarrierUpdateLowerer**: Emits JoinIR (Phase 184 for numeric, Phase 188+ for string)
|
||||
|
||||
---
|
||||
|
||||
## 5. Representative Cases (Not Implemented)
|
||||
|
||||
### 5.1 JsonParser Update Patterns
|
||||
|
||||
#### _parse_number: `num_str = num_str + ch`
|
||||
**UpdateKind**: `StringAppendChar`
|
||||
**Classification**:
|
||||
- `num_str`: carrier name
|
||||
- `ch`: LoopBodyLocal variable (single character from string scan)
|
||||
- RHS: `UpdateRhs::Variable("ch")`
|
||||
**Decision**: **ALLOW** (Phase 188+)
|
||||
|
||||
#### _atoi: `num = num * 10 + digit`
|
||||
**UpdateKind**: `AccumulationLike` (numeric)
|
||||
**Classification**:
|
||||
- Nested BinOp: `(num * 10) + digit`
|
||||
- Currently detected as `UpdateRhs::Other`
|
||||
**Decision**: **COMPLEX** (requires BinOp tree analysis, Phase 189+)
|
||||
|
||||
#### _unescape_string: `result = result + s.substring(...)`
|
||||
**UpdateKind**: `Complex` (method call)
|
||||
**Classification**:
|
||||
- RHS: `UpdateRhs::Other` (MethodCall)
|
||||
**Decision**: **REJECT** with Fail-Fast
|
||||
|
||||
### 5.2 UpdateKind Mapping Table
|
||||
|
||||
| Loop Variable | Update Pattern | UpdateRhs | UpdateKind | Phase 187 Decision |
|
||||
|---------------|----------------|-----------|------------|-------------------|
|
||||
| `num_str` | `num_str + ch` | `Variable("ch")` | StringAppendChar | ALLOW (Phase 188+) |
|
||||
| `result` | `result + "\n"` | `StringLiteral("\n")` | StringAppendLiteral | ALLOW (Phase 188+) |
|
||||
| `num` | `num * 10 + digit` | `Other` (nested BinOp) | Complex | REJECT (Phase 189+) |
|
||||
| `result` | `result + s.substring(...)` | `Other` (MethodCall) | Complex | REJECT (Fail-Fast) |
|
||||
| `pos` | `pos + 1` | `Const(1)` | CounterLike | ALLOW (Phase 178 ✅) |
|
||||
| `sum` | `sum + i` | `Variable("i")` | AccumulationLike | ALLOW (Phase 178 ✅) |
|
||||
|
||||
---
|
||||
|
||||
## 6. Next Steps (Phase 188+ Implementation)
|
||||
|
||||
### Phase 188: StringAppendChar/Literal Implementation
|
||||
|
||||
**Scope**: Extend Pattern2/4 and CarrierUpdateLowerer to support string append.
|
||||
|
||||
**Tasks**:
|
||||
1. **Extend `can_lower()` whitelist** (Pattern2/4)
|
||||
- Accept `UpdateRhs::Variable(_)` for string carriers
|
||||
- Accept `UpdateRhs::StringLiteral(_)` for string carriers
|
||||
- Keep `UpdateRhs::Other` as Fail-Fast
|
||||
|
||||
2. **Extend CarrierUpdateLowerer** (emission layer)
|
||||
- Detect carrier type (String vs Integer)
|
||||
- Emit `StringBox.concat()` call for string append
|
||||
- Emit `Compute { Add }` for numeric (existing logic)
|
||||
|
||||
3. **Add validation**
|
||||
- Check variable scope (LoopBodyLocal or OuterLocal only)
|
||||
- Check literal type (string only)
|
||||
|
||||
4. **E2E Test**
|
||||
- `_parse_number` minimal version with `num_str = num_str + ch`
|
||||
|
||||
**Estimate**: 3-4 hours
|
||||
|
||||
### Phase 189+: Complex BinOp (Future)
|
||||
|
||||
**Scope**: Handle nested BinOp like `num * 10 + digit`.
|
||||
|
||||
**Tasks**:
|
||||
1. Extend `analyze_rhs()` to recursively parse BinOp trees
|
||||
2. Classify simple nested patterns (e.g., `(x * 10) + y`) as safe
|
||||
3. Keep truly complex patterns (e.g., method calls in BinOp) as Fail-Fast
|
||||
|
||||
**Estimate**: 5-6 hours
|
||||
|
||||
---
|
||||
|
||||
## 7. Design Constraints
|
||||
|
||||
### 7.1 Box Theory Compliance
|
||||
|
||||
**Separation of Concerns**:
|
||||
- UpdateKind classification → LoopUpdateAnalyzer (existing box)
|
||||
- Can-lower decision → Pattern2/4 (control flow box)
|
||||
- JoinIR emission → CarrierUpdateLowerer (lowering box)
|
||||
|
||||
**No Cross-Boundary Leakage**:
|
||||
- Pattern2/4 do NOT emit JoinIR directly for string operations
|
||||
- CarrierUpdateLowerer does NOT make can-lower decisions
|
||||
|
||||
### 7.2 Fail-Fast Preservation
|
||||
|
||||
**Phase 178 Logic Untouched**:
|
||||
- All `UpdateRhs::StringLiteral` and `UpdateRhs::Other` continue to trigger Fail-Fast
|
||||
- Phase 187 only documents what "safe" means—implementation is Phase 188+
|
||||
|
||||
**Error Messages**:
|
||||
- Current: `"String/complex update detected, rejecting Pattern 2 (unsupported)"`
|
||||
- Future (Phase 188+): More specific messages for different rejection reasons
|
||||
|
||||
### 7.3 Testability
|
||||
|
||||
**Unit Test Separation**:
|
||||
- LoopUpdateAnalyzer tests: AST → UpdateExpr extraction
|
||||
- Pattern2/4 tests: UpdateExpr → can_lower decision
|
||||
- CarrierUpdateLowerer tests: UpdateExpr → JoinIR emission
|
||||
|
||||
**E2E Test**:
|
||||
- JsonParser representative loops (Phase 188+)
|
||||
|
||||
---
|
||||
|
||||
## 8. Documentation Updates
|
||||
|
||||
### 8.1 joinir-architecture-overview.md
|
||||
|
||||
Add one sentence in Section 2.2 (条件式ライン):
|
||||
|
||||
```markdown
|
||||
- **LoopUpdateAnalyzer / CarrierUpdateLowerer**
|
||||
- ファイル:
|
||||
- `src/mir/join_ir/lowering/loop_update_analyzer.rs`
|
||||
- `src/mir/join_ir/lowering/carrier_update_lowerer.rs`
|
||||
- 責務:
|
||||
- ループで更新される変数(carrier)を検出し、UpdateExpr を保持。
|
||||
- Pattern 4 では実際に更新されるキャリアだけを残す。
|
||||
- **Phase 187設計**: String 更新は UpdateKind ベースのホワイトリストで扱う方針(StringAppendChar/Literal は Phase 188+ で実装予定)。
|
||||
```
|
||||
|
||||
### 8.2 CURRENT_TASK.md
|
||||
|
||||
Add Phase 187 entry:
|
||||
|
||||
```markdown
|
||||
- [x] **Phase 187: String UpdateLowering 設計** ✅ (2025-12-09)
|
||||
- UpdateKind ベースのホワイトリスト設計(doc-only)
|
||||
- StringAppendChar/StringAppendLiteral を安全パターンとして定義
|
||||
- Complex (method call / nested BinOp) は Fail-Fast 維持
|
||||
- Phase 178 の Fail-Fast は完全保持
|
||||
- Phase 188+ での実装方針を確立
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Success Criteria (Phase 187)
|
||||
|
||||
- [x] Design document created (`phase187-string-update-design.md`)
|
||||
- [x] UpdateKind whitelist defined (6 categories)
|
||||
- [x] Fail-Fast preservation confirmed (Phase 178 untouched)
|
||||
- [x] Lowerer responsibility separation documented
|
||||
- [x] Representative cases analyzed (JsonParser loops)
|
||||
- [x] Architecture diagram created
|
||||
- [x] Next steps defined (Phase 188+ implementation)
|
||||
- [x] `joinir-architecture-overview.md` updated (1-sentence addition)
|
||||
- [x] `CURRENT_TASK.md` updated (Phase 187 entry added)
|
||||
|
||||
**All criteria met**: Phase 187 complete (design-only).
|
||||
|
||||
---
|
||||
|
||||
## Phase 188 Implementation Complete (2025-12-09)
|
||||
|
||||
### Implementation Summary
|
||||
|
||||
Phase 188 successfully implemented StringAppendChar and StringAppendLiteral support in JoinIR patterns.
|
||||
|
||||
**Changes Made**:
|
||||
|
||||
1. **Pattern2/4 `can_lower()` Whitelist** (Task 188-2)
|
||||
- Updated `pattern2_with_break.rs` and `pattern4_with_continue.rs`
|
||||
- Allow: `UpdateRhs::Const`, `UpdateRhs::Variable`, `UpdateRhs::StringLiteral`
|
||||
- Reject: `UpdateRhs::Other` (complex updates only)
|
||||
- Old behavior: Rejected all string updates
|
||||
- New behavior: Accept safe string patterns, reject only complex ones
|
||||
|
||||
2. **CarrierUpdateLowerer JoinIR Emission** (Task 188-3)
|
||||
- Updated `carrier_update_emitter.rs` (both UpdateEnv and ConditionEnv versions)
|
||||
- `UpdateRhs::StringLiteral(s)` → Emit `Const { value: ConstValue::String(s) }` + `BinOp`
|
||||
- `UpdateRhs::Variable(name)` → Resolve variable, emit `BinOp` (handles both numeric and string)
|
||||
- `UpdateRhs::Other` → Return error (should be caught by can_lower)
|
||||
|
||||
3. **E2E Test Files** (Task 188-4)
|
||||
- Created `apps/tests/phase188_string_append_char.hako` (Pattern 2 with break)
|
||||
- Created `apps/tests/phase188_string_append_literal.hako` (Pattern 4 with continue)
|
||||
- Both tests compile and run without errors
|
||||
- JoinIR generation succeeds for both patterns
|
||||
|
||||
**Verification**:
|
||||
```bash
|
||||
# Pattern 2: StringAppendChar
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase188_string_append_char.hako
|
||||
# Output: Pattern 2 triggered, JoinIR generated successfully
|
||||
|
||||
# Pattern 4: StringAppendLiteral
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase188_string_append_literal.hako
|
||||
# Output: Pattern 4 triggered, JoinIR generated successfully
|
||||
```
|
||||
|
||||
**Key Achievement**: Phase 178's Fail-Fast is now selective - only rejects truly complex updates (method calls, nested BinOp), while allowing safe string concatenation patterns.
|
||||
|
||||
---
|
||||
|
||||
## 10. Conclusion
|
||||
|
||||
Phase 187 establishes a clear design for string update handling in JoinIR:
|
||||
|
||||
1. **Safe Patterns**: CounterLike, AccumulationLike, StringAppendChar, StringAppendLiteral
|
||||
2. **Unsafe Patterns**: Complex (method calls, nested BinOp) → Fail-Fast
|
||||
3. **Separation of Concerns**: Detection (Pattern2/4) vs Emission (CarrierUpdateLowerer)
|
||||
4. **Phase 178 Preservation**: All Fail-Fast logic remains unchanged
|
||||
|
||||
**No code changes in Phase 187**—all design decisions documented for Phase 188+ implementation.
|
||||
|
||||
**Next Phase**: Phase 188 - Implement StringAppendChar/Literal lowering (3-4 hours estimate).
|
||||
@ -1,414 +0,0 @@
|
||||
# Phase 189: JsonParser Mini Application Verification Report
|
||||
|
||||
**Date**: 2025-12-09
|
||||
**Status**: Investigation Complete - Blocker Identified
|
||||
**Task**: Verify Phase 188 StringAppend implementation with JsonParser-style loops
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Phase 189 aimed to verify Phase 188's StringAppend implementation (_parse_number / _atoi / _match_literal patterns). Investigation revealed a **fundamental carrier detection limitation** in Pattern1/2 that blocks JsonParser loop implementation.
|
||||
|
||||
**Key Finding**: Current JoinIR patterns only track loop variables explicitly updated in the condition (e.g., `i = i + 1`). Accumulator variables (e.g., `result = result + digit`) are **not automatically detected as carriers**, causing incorrect MIR generation.
|
||||
|
||||
---
|
||||
|
||||
## 1. Investigation Results
|
||||
|
||||
### 1.1 Target Loops Analysis
|
||||
|
||||
Three JsonParser loops were targeted:
|
||||
|
||||
| Function | Test File | Expected Pattern | Actual Pattern | Status |
|
||||
|----------|-----------|-----------------|----------------|--------|
|
||||
| `_parse_number` | phase183_p2_parse_number.hako | Pattern2 (Break) | Pattern2 | ❌ Blocked by LoopBodyLocal |
|
||||
| `_atoi` | phase183_p2_atoi.hako | Pattern2 (simplified) | **Pattern1** | ❌ Carrier detection issue |
|
||||
| `_match_literal` | phase182_p1_match_literal.hako | Pattern1 (Simple) | Pattern1 | ✅ Works (no accumulator) |
|
||||
|
||||
### 1.2 Test Execution Results
|
||||
|
||||
#### Test 1: phase183_p2_parse_number (Original)
|
||||
```bash
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase183_p2_parse_number.hako
|
||||
```
|
||||
|
||||
**Result**: ❌ **BLOCKED**
|
||||
|
||||
**Error**:
|
||||
```
|
||||
[ERROR] ❌ MIR compilation error: [cf_loop/pattern2] Lowering failed:
|
||||
[joinir/pattern2] Unsupported condition: uses loop-body-local variables: ["digit_pos"].
|
||||
Pattern 2 supports only loop parameters and outer-scope variables.
|
||||
Consider using Pattern 5+ for complex loop conditions.
|
||||
```
|
||||
|
||||
**Root Cause**: LoopBodyLocal variable `digit_pos` in loop condition.
|
||||
**Design Note**: This is **working as intended** - Pattern2 correctly rejects this pattern. Requires Pattern5 (Trim-style promotion).
|
||||
|
||||
#### Test 2: phase183_p2_atoi (Simplified Version)
|
||||
```bash
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase183_p2_atoi.hako
|
||||
```
|
||||
|
||||
**Result**: ❌ **WRONG OUTPUT**
|
||||
|
||||
**Expected**: `result=123`, `i=3`
|
||||
**Actual**: `result=0`, `i=0`
|
||||
|
||||
**Root Cause**: Accumulator variable `result` is not tracked as a carrier. Only `i` (loop counter) is tracked.
|
||||
|
||||
**Trace Evidence**:
|
||||
```
|
||||
[trace:pattern] route: Pattern1_Minimal MATCHED
|
||||
[DEBUG-177] Phase 33-21: carrier_phis count: 1, names: ["i"]
|
||||
```
|
||||
|
||||
**MIR Analysis**:
|
||||
```mir
|
||||
bb4:
|
||||
%20 = phi [%5, bb0], [%16, bb7] ; Only 'i' has PHI
|
||||
br label bb5
|
||||
|
||||
bb7:
|
||||
extern_call env.console.log(%20) ; Prints 'i', not 'result'
|
||||
%15 = const 1
|
||||
%16 = %20 Add %15 ; Only updates 'i'
|
||||
%20 = copy %16
|
||||
br label bb4
|
||||
```
|
||||
|
||||
**Missing**:
|
||||
- No PHI for `result` (should be: `%result = phi [%2, bb0], [%updated_result, bb7]`)
|
||||
- No update for `result` in loop body (should be: `%updated_result = %result * 10 + %digit`)
|
||||
|
||||
#### Test 3: phase182_p1_match_literal
|
||||
```bash
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase182_p1_match_literal.hako
|
||||
```
|
||||
|
||||
**Result**: ✅ **PASS**
|
||||
|
||||
**Output**: `Result: MATCH`
|
||||
|
||||
**Analysis**: This loop only needs a loop counter (`i`). No accumulator variable required, so Pattern1's single-carrier approach works correctly.
|
||||
|
||||
---
|
||||
|
||||
## 2. Root Cause Analysis
|
||||
|
||||
### 2.1 Carrier Detection in Pattern1
|
||||
|
||||
**Current Behavior** (Pattern1):
|
||||
- Only tracks **one variable**: the loop variable from condition (e.g., `i < n` → track `i`)
|
||||
- Determined by `PatternPipelineContext.loop_var_name`
|
||||
- Single-carrier architecture
|
||||
|
||||
**Code Location**: `src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs`
|
||||
|
||||
```rust
|
||||
// Phase 179-B: Pattern 1 (Simple While Loop) minimal lowerer
|
||||
let ctx = PatternPipelineContext::new(self, condition, body)?;
|
||||
|
||||
// Only tracks ctx.loop_var_id (single variable)
|
||||
.with_carriers(
|
||||
vec![ValueId(0)], // JoinIR's main() parameter (loop variable)
|
||||
vec![ctx.loop_var_id], // Host's loop variable
|
||||
)
|
||||
.with_loop_var_name(Some(ctx.loop_var_name.clone()))
|
||||
```
|
||||
|
||||
### 2.2 Carrier Detection in Pattern2
|
||||
|
||||
**Current Behavior** (Pattern2):
|
||||
- Uses **LoopUpdateAnalyzer** to detect carrier updates
|
||||
- Filters carriers based on `UpdateExpr` presence
|
||||
- Multi-carrier support exists (Phase 176), but requires explicit update detection
|
||||
|
||||
**Code Location**: `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`
|
||||
|
||||
```rust
|
||||
// Phase 176-3: Analyze carrier updates
|
||||
let carrier_updates = crate::mir::loop_pattern_detection::analyze_carrier_updates(
|
||||
body,
|
||||
&carrier_info.carriers,
|
||||
&condition_env,
|
||||
);
|
||||
|
||||
// Phase 176-4: Filter carriers (only keep those with updates)
|
||||
let filtered_carriers: Vec<_> = carrier_info
|
||||
.carriers
|
||||
.iter()
|
||||
.filter(|c| carrier_updates.contains_key(&c.name))
|
||||
.cloned()
|
||||
.collect();
|
||||
```
|
||||
|
||||
**Problem**: If `analyze_carrier_updates()` doesn't detect the update pattern, the carrier is filtered out.
|
||||
|
||||
### 2.3 LoopUpdateAnalyzer Limitations
|
||||
|
||||
**File**: `src/mir/loop_pattern_detection/loop_update_analyzer.rs`
|
||||
|
||||
**Current Detection Patterns**:
|
||||
- `i = i + 1` → Detected (CounterLike)
|
||||
- `sum = sum + i` → Detected (AccumulationLike) **IF explicitly in loop body**
|
||||
- `result = result * 10 + digit` → **MAY NOT BE DETECTED** if:
|
||||
- Inside nested if-block
|
||||
- Variable scope resolution fails
|
||||
- RHS is complex (method call, nested BinOp)
|
||||
|
||||
**Phase 188 StringAppend Support**:
|
||||
- ✅ `s = s + ch` (StringAppendChar) - **Whitelisted**
|
||||
- ✅ `s = s + "literal"` (StringAppendLiteral) - **Whitelisted**
|
||||
- ❌ `s = s + s.substring(...)` (Complex) - **Rejected** (Fail-Fast)
|
||||
|
||||
---
|
||||
|
||||
## 3. Problem Classification
|
||||
|
||||
### 3.1 Issue Type: **Carrier Detection Gap**
|
||||
|
||||
**Category**: Design Limitation (not a bug)
|
||||
|
||||
**Affected Patterns**: Pattern1 (single-carrier), Pattern2 (update detection required)
|
||||
|
||||
**Scope**: All loops with **implicit accumulators**:
|
||||
- `_parse_number`: `num_str = num_str + digit_ch` (string accumulation)
|
||||
- `_atoi`: `result = result * 10 + digit` (numeric accumulation)
|
||||
- `_parse_array`: Multiple accumulators (elements array, pos, state)
|
||||
|
||||
### 3.2 Design Constraints
|
||||
|
||||
**Pattern1 Architecture**:
|
||||
- **By Design**: Single-carrier (loop variable only)
|
||||
- **Rationale**: Simplest loop form, minimal complexity
|
||||
- **Trade-off**: Cannot handle accumulators
|
||||
|
||||
**Pattern2 Architecture**:
|
||||
- **By Design**: Multi-carrier (Phase 176+)
|
||||
- **Constraint**: Requires `UpdateExpr` detection by `LoopUpdateAnalyzer`
|
||||
- **Trade-off**: If update not detected, carrier is filtered out
|
||||
|
||||
### 3.3 Phase 188 StringAppend Verification
|
||||
|
||||
**Phase 188 Goal**: Enable safe string update patterns (`s = s + ch`, `s = s + "lit"`)
|
||||
|
||||
**Status**: ✅ **Implementation Complete** (Phase 188 code merged)
|
||||
|
||||
**Verification Blocked By**:
|
||||
1. **Carrier detection gap** (current phase 189 finding)
|
||||
2. **LoopBodyLocal handling** (Phase 183-185 partial solution)
|
||||
|
||||
**Phase 188 Code Works Correctly For**:
|
||||
- Loops where carriers are **explicitly detected** by LoopUpdateAnalyzer
|
||||
- Example: Simple accumulation in top-level loop body
|
||||
|
||||
**Phase 188 Code Does NOT Work For**:
|
||||
- Loops where carriers are **not detected** (e.g., nested in if-blocks)
|
||||
- Requires **broader carrier detection** (Phase 190+ scope)
|
||||
|
||||
---
|
||||
|
||||
## 4. Recommended Next Steps
|
||||
|
||||
### 4.1 Short-Term: Document Blocker (This Phase)
|
||||
|
||||
**Action**: Create clear documentation of carrier detection limitation.
|
||||
|
||||
**Files to Update**:
|
||||
- ✅ This document (phase189-jsonparser-mini-verification.md)
|
||||
- ⏳ CURRENT_TASK.md (add Phase 189 results)
|
||||
- ⏳ phase181-jsonparser-loop-roadmap.md (update _atoi status)
|
||||
|
||||
### 4.2 Medium-Term: Enhance Carrier Detection (Phase 190+)
|
||||
|
||||
**Option A: Expand LoopUpdateAnalyzer**
|
||||
|
||||
**Approach**: Improve `analyze_carrier_updates()` to detect updates in nested blocks.
|
||||
|
||||
**Implementation**:
|
||||
- Add recursive AST traversal for if-blocks
|
||||
- Track variable assignments regardless of nesting depth
|
||||
- Classify update patterns (CounterLike, AccumulationLike, StringAppend, Complex)
|
||||
|
||||
**Pros**:
|
||||
- Works with existing Pattern2/4 multi-carrier infrastructure
|
||||
- No new pattern types needed
|
||||
|
||||
**Cons**:
|
||||
- Complex implementation (nested block analysis)
|
||||
- May over-detect (false positives)
|
||||
|
||||
**Option B: Explicit Carrier Annotation**
|
||||
|
||||
**Approach**: Allow explicit carrier declaration in loop header.
|
||||
|
||||
**Syntax Example** (hypothetical):
|
||||
```nyash
|
||||
loop(i < n) carriers(i, result) {
|
||||
// Loop body
|
||||
}
|
||||
```
|
||||
|
||||
**Pros**:
|
||||
- Explicit, clear, deterministic
|
||||
- No complex analysis required
|
||||
|
||||
**Cons**:
|
||||
- Language syntax change
|
||||
- Requires parser/AST changes
|
||||
|
||||
**Option C: Whole-Body Analysis**
|
||||
|
||||
**Approach**: Analyze entire loop body for all modified variables, treat as carriers.
|
||||
|
||||
**Implementation**:
|
||||
- Scan loop body for all `AssignOp` nodes
|
||||
- Filter out LoopBodyLocal (local-only variables)
|
||||
- Treat remaining as carriers
|
||||
|
||||
**Pros**:
|
||||
- Simple, comprehensive
|
||||
- Works for all accumulation patterns
|
||||
|
||||
**Cons**:
|
||||
- May create unnecessary PHIs for temp variables
|
||||
- Requires careful filtering
|
||||
|
||||
### 4.3 Long-Term: Pattern Hierarchy Redesign (Phase 200+)
|
||||
|
||||
**Vision**: Unified carrier detection across all patterns.
|
||||
|
||||
**Architecture**:
|
||||
- **Phase 1**: Extract carrier detection to shared module
|
||||
- **Phase 2**: Pattern1 → multi-carrier support
|
||||
- **Phase 3**: Pattern2/3/4 → unified carrier detection
|
||||
- **Phase 4**: Pattern5 (Trim) → integrated carrier promotion
|
||||
|
||||
---
|
||||
|
||||
## 5. Test Artifacts Created
|
||||
|
||||
### 5.1 New Mini Tests (Phase 189)
|
||||
|
||||
Three minimal test files created for verification:
|
||||
|
||||
1. **phase189_parse_number_mini.hako**
|
||||
- Pattern: Numeric accumulation (`num = num * 10 + digit`)
|
||||
- Status: ❌ Carrier detection issue (same as phase183_p2_atoi)
|
||||
|
||||
2. **phase189_atoi_mini.hako**
|
||||
- Pattern: Numeric accumulation with early break
|
||||
- Status: ❌ Simplified version has no break (becomes Pattern1)
|
||||
|
||||
3. **phase189_match_literal_mini.hako**
|
||||
- Pattern: Loop with conditional break (simple counter)
|
||||
- Status: ❌ Simplified version tracks wrong variable
|
||||
|
||||
### 5.2 Existing Tests Referenced
|
||||
|
||||
- **phase183_p2_parse_number.hako**: LoopBodyLocal blocker (working as intended)
|
||||
- **phase183_p2_atoi.hako**: Carrier detection issue (Phase 189 finding)
|
||||
- **phase182_p1_match_literal.hako**: ✅ Works (no accumulator)
|
||||
|
||||
---
|
||||
|
||||
## 6. Conclusions
|
||||
|
||||
### 6.1 Phase 188 StringAppend Implementation
|
||||
|
||||
**Status**: ✅ **Code Complete** (Phase 188 merged successfully)
|
||||
|
||||
**Verification Status**: ⏳ **Blocked by Carrier Detection Gap**
|
||||
|
||||
**Key Points**:
|
||||
- Phase 188 code correctly handles `StringAppendChar` and `StringAppendLiteral` patterns
|
||||
- Whitelist logic works as designed (rejects Complex patterns)
|
||||
- JoinIR emission for string literals is correct
|
||||
- **Cannot be end-to-end tested** until carrier detection is improved
|
||||
|
||||
### 6.2 JsonParser Loop Implementation
|
||||
|
||||
**Readiness**: ❌ **Blocked**
|
||||
|
||||
**Blockers**:
|
||||
1. **Carrier Detection Gap** (Phase 189 finding - this document)
|
||||
2. **LoopBodyLocal Handling** (Phase 183-185 partial solution, needs Pattern5 integration)
|
||||
|
||||
**Next Critical Path**:
|
||||
1. Phase 190: Enhance carrier detection (Option A/B/C above)
|
||||
2. Phase 191: Integrate with Phase 188 StringAppend support
|
||||
3. Phase 192: End-to-end test JsonParser loops (_atoi, _parse_number)
|
||||
|
||||
### 6.3 Scope Management
|
||||
|
||||
**Phase 189 Scope**: ✅ **Achieved**
|
||||
|
||||
- ✅ Investigated three target loops
|
||||
- ✅ Identified root cause (carrier detection gap)
|
||||
- ✅ Classified blocker type (design limitation, not bug)
|
||||
- ✅ Documented findings and recommendations
|
||||
- ✅ No code changes (investigation phase only)
|
||||
|
||||
**Out of Scope** (Future Phases):
|
||||
- ❌ Fixing carrier detection (Phase 190+)
|
||||
- ❌ StringAppend end-to-end tests (Phase 191+)
|
||||
- ❌ JsonParser full implementation (Phase 192+)
|
||||
|
||||
---
|
||||
|
||||
## 7. References
|
||||
|
||||
### 7.1 Related Phases
|
||||
|
||||
- **Phase 176**: Multi-carrier support in Pattern2
|
||||
- **Phase 178**: String update Fail-Fast implementation
|
||||
- **Phase 182**: JsonParser P1/P2 initial investigation
|
||||
- **Phase 183**: LoopBodyLocal role separation
|
||||
- **Phase 184-186**: Body-local MIR lowering infrastructure
|
||||
- **Phase 187**: String UpdateLowering design (doc-only)
|
||||
- **Phase 188**: StringAppend implementation (code complete)
|
||||
|
||||
### 7.2 Key Files
|
||||
|
||||
**Pattern Implementations**:
|
||||
- `src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs`
|
||||
- `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`
|
||||
|
||||
**Analysis Infrastructure**:
|
||||
- `src/mir/loop_pattern_detection/loop_update_analyzer.rs`
|
||||
- `src/mir/loop_pattern_detection/pattern_pipeline_context.rs`
|
||||
|
||||
**Test Files**:
|
||||
- `apps/tests/phase182_p1_match_literal.hako` ✅
|
||||
- `apps/tests/phase183_p2_atoi.hako` ❌
|
||||
- `apps/tests/phase183_p2_parse_number.hako` ❌ (LoopBodyLocal)
|
||||
|
||||
---
|
||||
|
||||
## 8. Action Items
|
||||
|
||||
### 8.1 Immediate (Phase 189 Completion)
|
||||
|
||||
- [x] Create this verification report
|
||||
- [ ] Update CURRENT_TASK.md with Phase 189 results
|
||||
- [ ] Update phase181-jsonparser-loop-roadmap.md with blocker status
|
||||
|
||||
### 8.2 Next Phase (Phase 190)
|
||||
|
||||
- [ ] Design carrier detection enhancement (choose Option A/B/C)
|
||||
- [ ] Implement prototype for chosen approach
|
||||
- [ ] Test with phase183_p2_atoi.hako
|
||||
- [ ] Verify no regression in existing tests
|
||||
|
||||
### 8.3 Future Phases
|
||||
|
||||
- [ ] Phase 191: StringAppend + Enhanced Carrier Detection integration
|
||||
- [ ] Phase 192: JsonParser loops end-to-end tests
|
||||
- [ ] Phase 193: Pattern5 (Trim) + JsonParser unification
|
||||
|
||||
---
|
||||
|
||||
**Report Status**: ✅ Complete
|
||||
**Next Action**: Update CURRENT_TASK.md and roadmap documents
|
||||
@ -1,352 +0,0 @@
|
||||
# Phase 189: Select Instruction MIR Bridge - ChatGPT Architectural Inquiry
|
||||
|
||||
**Date**: 2025-12-05
|
||||
**Status**: Blocking Issue Identified
|
||||
**Assigned to**: ChatGPT (Architecture & Design Guidance)
|
||||
**Related Phase**: Phase 188-Impl-3 (Pattern 3 implementation complete except for MIR bridge)
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Phase 188-Impl-3 (Loop with If-Else PHI) has successfully implemented:
|
||||
- ✅ Pattern 3 JoinIR lowering infrastructure (loop_with_if_phi_minimal.rs)
|
||||
- ✅ Pattern 3 routing in MIR builder (control_flow.rs)
|
||||
- ✅ Select instruction definition in JoinIR
|
||||
- ✅ JoinIR runtime execution for Select
|
||||
- ✅ JSON serialization for Select
|
||||
|
||||
**Remaining Blocker**: Pattern 3 tests cannot execute because **Select instruction conversion from JoinIR to MIR is not implemented**.
|
||||
|
||||
The Select instruction (JoinIR's ternary operator: `cond ? then_val : else_val`) cannot be directly represented in MIR's current instruction set. We need a clean architectural approach to convert it to MIR-compatible control flow.
|
||||
|
||||
---
|
||||
|
||||
## Current Situation
|
||||
|
||||
### What Works (Pattern 1 & 2)
|
||||
- Pattern 1 (Simple While Loop): ✅ Fully working (loop_min_while.hako)
|
||||
- Pattern 2 (Loop with Conditional Break): ✅ Fully working (joinir_min_loop.hako)
|
||||
- Both use only instructions already implemented in MIR bridge
|
||||
|
||||
### What Doesn't Work (Pattern 3)
|
||||
- Pattern 3 (Loop with If-Else PHI): 🔄 Infrastructure complete, execution blocked
|
||||
- Test case: apps/tests/loop_if_phi.hako
|
||||
- Expected: Prints "sum=9" (sum of odd numbers 1-5)
|
||||
- Actual: Fails at MIR bridge conversion with Select instruction
|
||||
|
||||
### Root Cause: Missing Select → MIR Conversion
|
||||
|
||||
```rust
|
||||
// JoinIR lowering generates this (works fine):
|
||||
let sum_new = alloc_value();
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Select {
|
||||
dst: sum_new,
|
||||
cond: if_cond,
|
||||
then_val: sum_then,
|
||||
else_val: sum_else,
|
||||
}));
|
||||
|
||||
// MIR bridge conversion fails here:
|
||||
// src/mir/join_ir_vm_bridge/convert.rs line ~XXX
|
||||
MirLikeInst::Select { .. } => {
|
||||
return Err("Select instruction not yet implemented".into());
|
||||
// ❌ This is where we get stuck
|
||||
}
|
||||
```
|
||||
|
||||
### Why Select Can't Be Direct in MIR
|
||||
|
||||
MIR instruction set (14 instructions) doesn't have a direct ternary/select instruction:
|
||||
- **Arithmetic**: Const, UnaryOp, BinOp, Compare, TypeOp
|
||||
- **Memory**: Load, Store
|
||||
- **Control**: Branch (goto two targets), Jump (goto one target), Return
|
||||
- **Other**: Phi, NewBox, BoxCall
|
||||
|
||||
**Problem**: Select needs to produce a value *and* involve control flow, which MIR separates:
|
||||
- Control flow → Branch/Jump instructions that create multiple basic blocks
|
||||
- Value production → Phi nodes at merge points
|
||||
|
||||
---
|
||||
|
||||
## Technical Challenge: Select → MIR Conversion
|
||||
|
||||
### Pattern of the Conversion
|
||||
|
||||
A JoinIR Select must be converted to:
|
||||
1. **Branch block** with the condition → splits into then_block and else_block
|
||||
2. **Then block** → computes `then_val`
|
||||
3. **Else block** → computes `else_val`
|
||||
4. **Merge block** → Phi instruction that merges the two values
|
||||
|
||||
### Example: JoinIR vs MIR Representation
|
||||
|
||||
**JoinIR (what we have)**:
|
||||
```rust
|
||||
let if_cond = ... // compute condition
|
||||
let sum_then = sum + i // then branch
|
||||
let sum_else = sum + 0 // else branch
|
||||
let sum_new = Select { // ternary select
|
||||
cond: if_cond,
|
||||
then_val: sum_then,
|
||||
else_val: sum_else,
|
||||
}
|
||||
// sum_new is ready to use
|
||||
```
|
||||
|
||||
**MIR (what we need to generate)**:
|
||||
```
|
||||
block_before:
|
||||
if_cond = Compare(...)
|
||||
Branch if_cond → block_then, block_else
|
||||
|
||||
block_then:
|
||||
sum_then = BinOp(Add, sum, i)
|
||||
Jump → block_merge [sum_then]
|
||||
|
||||
block_else:
|
||||
sum_else = BinOp(Add, sum, 0)
|
||||
Jump → block_merge [sum_else]
|
||||
|
||||
block_merge:
|
||||
sum_new = Phi([sum_then, sum_else]) // merges two values
|
||||
// sum_new ready to use
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Questions for ChatGPT
|
||||
|
||||
### 1. **Clean Conversion Strategy**
|
||||
How should we cleanly structure the Select → MIR conversion in the codebase?
|
||||
|
||||
**Considerations**:
|
||||
- Should this be a separate transformation function or inline in the converter?
|
||||
- What's the best way to handle the block generation and control flow linking?
|
||||
- Should we use existing utilities (e.g., block management functions) or create new ones?
|
||||
|
||||
**Reference**: Look at how existing Select-like constructs are handled:
|
||||
- If/Then/Else lowering in `src/mir/builder/if_builder.rs` (lines ~200-350)
|
||||
- Phi generation in `src/mir/join_ir/lowering/if_select.rs`
|
||||
- How Branch/Phi are currently being connected
|
||||
|
||||
### 2. **Block Creation and Management**
|
||||
The conversion needs to create new MIR blocks. How should this interact with existing block management?
|
||||
|
||||
**Questions**:
|
||||
- Should new blocks be pre-allocated (like LoopForm does)?
|
||||
- Should the conversion happen immediately or deferred?
|
||||
- How do we ensure proper linking between blocks (edges, dominance)?
|
||||
- Do we need metadata tracking for Select block origins?
|
||||
|
||||
### 3. **Value ID Continuity**
|
||||
JoinIR uses local ValueIds (0, 1, 2, ...), which get remapped to host ValueIds. How do we handle the intermediate values?
|
||||
|
||||
**Context**:
|
||||
- JoinModule is converted to MirModule via `convert_join_module_to_mir_with_meta()`
|
||||
- ValueIds are already remapped at this stage
|
||||
- Then blocks are merged via `merge_joinir_mir_blocks()`
|
||||
|
||||
**Question**: Should the Select expansion happen:
|
||||
- (A) In `convert_join_module_to_mir_with_meta()` (early in the JoinModule→MirModule stage)?
|
||||
- (B) In the MIR bridge converter (current location)?
|
||||
- (C) In `merge_joinir_mir_blocks()` (late, after remapping)?
|
||||
|
||||
### 4. **Code Organization**
|
||||
Where should the Select conversion logic live?
|
||||
|
||||
**Options**:
|
||||
- Option A: New file `src/mir/join_ir_vm_bridge/select_expansion.rs`
|
||||
- Pros: Single responsibility, easy to test, clear naming
|
||||
- Cons: Small file, might split related code
|
||||
- Option B: Expand `src/mir/join_ir_vm_bridge/convert.rs`
|
||||
- Pros: Centralized conversion logic
|
||||
- Cons: File gets larger, mixed concerns
|
||||
- Option C: Create in JoinIR lowering layer (`src/mir/join_ir/lowering/select_expansion.rs`)
|
||||
- Pros: Closer to where Select is created
|
||||
- Cons: Mixing JoinIR and MIR concerns
|
||||
|
||||
**What's the architectural pattern used elsewhere in the codebase?**
|
||||
|
||||
### 5. **Performance and Optimization Implications**
|
||||
Select expansion creates extra blocks and Phi nodes. Any concerns?
|
||||
|
||||
**Questions**:
|
||||
- Will the VM interpreter handle this correctly?
|
||||
- Does the LLVM backend optimize this back to conditional moves?
|
||||
- Should we add a Select-fast-path for simple cases (where Select result isn't used in loops)?
|
||||
- How do we measure the quality of generated MIR?
|
||||
|
||||
### 6. **Testing Strategy**
|
||||
How should we validate the Select expansion?
|
||||
|
||||
**Suggested Approach**:
|
||||
- Existing test: apps/tests/loop_if_phi.hako (integration test)
|
||||
- New unit test: Test Select expansion in isolation
|
||||
- MIR output inspection: Compare before/after
|
||||
- Round-trip test: JoinIR → MIR → VM execution
|
||||
|
||||
**Your input**: What's the minimal test to validate correctness?
|
||||
|
||||
### 7. **Future Extensibility**
|
||||
Pattern 3 uses Select for single variable mutation. What about more complex patterns?
|
||||
|
||||
**Consider**:
|
||||
- Pattern 4+: Multiple variables mutating in if/else?
|
||||
- IfMerge instruction (which we have but don't use)?
|
||||
- Should IfMerge replace Select as the canonical form?
|
||||
- How does this relate to Phase 33's IfMerge lowering work?
|
||||
|
||||
---
|
||||
|
||||
## Existing References in Codebase
|
||||
|
||||
### Similar Transformations
|
||||
|
||||
1. **If-Select lowering** (`src/mir/join_ir/lowering/if_select.rs`)
|
||||
- Converts if/else with PHI to JoinIR Select instruction
|
||||
- ~180 lines, well-structured
|
||||
- **Insight**: This is the *opposite* direction (MIR→JoinIR Select)
|
||||
|
||||
2. **If builder** (`src/mir/builder/if_builder.rs`)
|
||||
- Creates MIR blocks for if/then/else
|
||||
- Lines 200-350 show block creation and Phi handling
|
||||
- **Insight**: Shows how to properly structure MIR control flow
|
||||
|
||||
3. **Loop builder** (`src/mir/builder/loop_builder.rs`)
|
||||
- Generates loop control flow with Phi nodes
|
||||
- Handles block linking and edge management
|
||||
- **Insight**: Established patterns for block management
|
||||
|
||||
4. **LoopForm** (`src/mir/loop_pattern_detection.rs` + `src/mir/mir_loopform.rs`)
|
||||
- Entire subsystem for loop pattern detection
|
||||
- Pre-allocates and links blocks
|
||||
- **Insight**: Large-scale block transformation pattern
|
||||
|
||||
### Related Files
|
||||
- `src/mir/join_ir_vm_bridge/convert.rs` (current location of Select error)
|
||||
- `src/mir/join_ir/lowering/inline_boundary.rs` (ValueId mapping)
|
||||
- `src/mir/join_ir/lowering/loop_patterns.rs` (routing logic)
|
||||
|
||||
---
|
||||
|
||||
## Proposed Implementation Phases
|
||||
|
||||
### Phase 189-A: Design & Validation (ChatGPT Input)
|
||||
- Architectural decision on conversion location (early vs late in pipeline)
|
||||
- Code organization approach
|
||||
- Test strategy definition
|
||||
|
||||
### Phase 189-B: Core Implementation
|
||||
- Implement Select → Branch + Then + Else + Phi conversion
|
||||
- Unit tests for conversion correctness
|
||||
- Integration test with loop_if_phi.hako
|
||||
|
||||
### Phase 189-C: Optimization & Polish
|
||||
- Performance validation
|
||||
- MIR output quality analysis
|
||||
- Documentation and comments
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
✅ **Minimal**: Pattern 3 test (loop_if_phi.hako) produces `sum=9`
|
||||
✅ **Better**: All MIR patterns (1-3) work correctly
|
||||
✅ **Best**: Design is extensible for future patterns (4+)
|
||||
|
||||
---
|
||||
|
||||
## Timeline Context
|
||||
|
||||
- **Phase 188-Impl-1**: Pattern 1 ✅ Complete (loop_min_while.hako)
|
||||
- **Phase 188-Impl-2**: Pattern 2 ✅ Complete (joinir_min_loop.hako)
|
||||
- **Phase 188-Impl-3**: Pattern 3 🔄 Lowering complete, MIR bridge pending
|
||||
- **Phase 189**: Select expansion (THIS INQUIRY)
|
||||
- **Phase 190+**: Additional patterns, optimizations, cleanup
|
||||
|
||||
---
|
||||
|
||||
## Key Files for Reference
|
||||
|
||||
```
|
||||
docs/development/current/main/phase188-select-implementation-spec.md ← Spec
|
||||
docs/private/roadmap2/phases/phase-188-joinir-loop-pattern-expansion/
|
||||
├── design.md ← Full 1648-line design document
|
||||
└── pattern3-implementation-spec.md ← Pattern 3 detailed spec
|
||||
|
||||
src/mir/join_ir/lowering/
|
||||
├── loop_with_if_phi_minimal.rs ← Pattern 3 lowering (381 lines)
|
||||
├── if_select.rs ← Similar conversion (opposite dir)
|
||||
└── inline_boundary.rs ← ValueId mapping
|
||||
|
||||
src/mir/join_ir_vm_bridge/convert.rs ← Current error location (line ~XXX)
|
||||
|
||||
src/mir/builder/
|
||||
├── if_builder.rs ← Example: if/else block creation
|
||||
└── loop_builder.rs ← Example: loop block management
|
||||
|
||||
apps/tests/loop_if_phi.hako ← Pattern 3 test (blocked)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Test Case Details
|
||||
|
||||
### Pattern 3 Test: loop_if_phi.hako
|
||||
```nyash
|
||||
static box Main {
|
||||
main(args) {
|
||||
local console = new ConsoleBox()
|
||||
local i = 1
|
||||
local sum = 0
|
||||
loop(i <= 5) {
|
||||
if (i % 2 == 1) { sum = sum + i } else { sum = sum + 0 }
|
||||
i = i + 1
|
||||
}
|
||||
console.println("sum=" + sum)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Execution trace**:
|
||||
- i=1: 1%2==1 → sum=0+1=1
|
||||
- i=2: 2%2==0 → sum=1+0=1
|
||||
- i=3: 3%2==1 → sum=1+3=4
|
||||
- i=4: 4%2==0 → sum=4+0=4
|
||||
- i=5: 5%2==1 → sum=4+5=9
|
||||
- i=6: 6<=5 is false → exit, print "sum=9"
|
||||
|
||||
**Expected output**: `sum=9\n`
|
||||
|
||||
---
|
||||
|
||||
## ChatGPT Requested Deliverables
|
||||
|
||||
1. **Architectural Recommendation** (Section 1-4)
|
||||
- Clean conversion strategy
|
||||
- Code organization approach
|
||||
- Block management pattern
|
||||
- ValueId handling strategy
|
||||
|
||||
2. **Design Document** (reference material provided)
|
||||
- Template for Phase 189-A design writeup
|
||||
- Implementation checklist
|
||||
|
||||
3. **Code Pattern Examples** (if helpful)
|
||||
- Reference snippets from similar transformations
|
||||
- Pseudocode for Select expansion algorithm
|
||||
|
||||
4. **Phase 189 Kickoff Plan**
|
||||
- Implementation order
|
||||
- Testing approach
|
||||
- Success metrics
|
||||
|
||||
---
|
||||
|
||||
**Status**: Awaiting ChatGPT input on architectural approach before proceeding to Phase 189-B implementation.
|
||||
|
||||
🤖 **Created by**: Claude Code
|
||||
📅 **Date**: 2025-12-05
|
||||
🎯 **Target**: Phase 189 - Select Instruction MIR Bridge Implementation
|
||||
@ -1,982 +0,0 @@
|
||||
# Phase 190: NumberAccumulation Update Design (Doc-Only)
|
||||
|
||||
**Status**: Design Complete (Code TBD)
|
||||
**Date**: 2025-12-09
|
||||
**Goal**: Design JoinIR support for `result = result * 10 + digit` style updates
|
||||
|
||||
---
|
||||
|
||||
## Section 1: Target Loops and RHS Patterns
|
||||
|
||||
### 1.1 JsonParserBox._atoi (lines 436-467)
|
||||
|
||||
**Loop Pattern**:
|
||||
```nyash
|
||||
local v = 0
|
||||
local digits = "0123456789"
|
||||
loop(i < n) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch < "0" || ch > "9" { break }
|
||||
local pos = digits.indexOf(ch)
|
||||
if pos < 0 { break }
|
||||
v = v * 10 + pos // ← NumberAccumulation pattern
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
|
||||
**AST Form of Update**:
|
||||
```
|
||||
Assign(
|
||||
lhs = "v",
|
||||
rhs = BinaryOp(
|
||||
op = Add,
|
||||
left = BinaryOp(
|
||||
op = Mul,
|
||||
left = Variable("v"),
|
||||
right = Literal(Integer(10))
|
||||
),
|
||||
right = Variable("pos")
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
**Characteristics**:
|
||||
- LHS appears exactly once in RHS (in left-most multiplication)
|
||||
- Base: 10 (constant)
|
||||
- Addend: `pos` (loop-local variable)
|
||||
- Type: Integer (MirType::Integer)
|
||||
|
||||
### 1.2 JsonParserBox._parse_number (lines 106-142)
|
||||
|
||||
**Loop Pattern**:
|
||||
```nyash
|
||||
local num_str = ""
|
||||
local digits = "0123456789"
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
local digit_pos = digits.indexOf(ch)
|
||||
|
||||
if digit_pos < 0 {
|
||||
break
|
||||
}
|
||||
|
||||
num_str = num_str + ch // ← StringAppendChar (already supported)
|
||||
p = p + 1
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: This is string accumulation (`num_str + ch`), not number accumulation.
|
||||
Already handled by existing `StringAppendChar` / `AccumulationLike`.
|
||||
|
||||
### 1.3 Other Instances in Codebase
|
||||
|
||||
From grep results:
|
||||
|
||||
1. **apps/tests/phase189_atoi_mini.hako:10**
|
||||
- `result = result * 10 + i`
|
||||
- Simplified test case
|
||||
|
||||
2. **apps/tests/phase183_p2_atoi.hako:25**
|
||||
- `result = result * 10 + digit`
|
||||
- Similar pattern
|
||||
|
||||
3. **apps/tests/phase183_p2_parse_number.hako:28**
|
||||
- `result = result * 10 + digit_pos`
|
||||
- Same pattern with different variable name
|
||||
|
||||
4. **lang/src/compiler/builder/ssa/exit_phi/break_finder.hako:277**
|
||||
- `result = result * 10 + (BreakFinderBox._char_to_digit(ch))`
|
||||
- Includes method call on RHS (Complex)
|
||||
|
||||
### 1.4 Canonical Form Requirements
|
||||
|
||||
**Safe NumberAccumulation Pattern**:
|
||||
```
|
||||
lhs = lhs * BASE + addend
|
||||
```
|
||||
|
||||
**Where**:
|
||||
- `lhs`: carrier variable (appears exactly 1 time in RHS)
|
||||
- `BASE`: integer constant (typically 10)
|
||||
- `addend`: one of:
|
||||
- Variable (loop-local or carrier)
|
||||
- Constant
|
||||
- **NOT** method call (→ Complex)
|
||||
|
||||
**Variants**:
|
||||
- `lhs = lhs * BASE - addend` (subtraction also allowed)
|
||||
- Base can be any integer constant (10, 2, 16, etc.)
|
||||
|
||||
---
|
||||
|
||||
## Section 2: Safe Pattern Candidates
|
||||
|
||||
### 2.1 Whitelist Criteria
|
||||
|
||||
A NumberAccumulation update is **safe** if:
|
||||
|
||||
1. **Type Check**: Carrier type is `MirType::Integer`
|
||||
2. **Structure Check**: AST matches `lhs = Add(Mul(lhs, Const(base)), addend)`
|
||||
3. **LHS Occurrence**: `lhs` appears exactly 1 time in RHS (in multiplication)
|
||||
4. **Base Check**: Base is integer constant (not variable)
|
||||
5. **Addend Check**: Addend is one of:
|
||||
- `Variable(name)` - loop-local or carrier
|
||||
- `Const(n)` - integer constant
|
||||
6. **No Method Calls**: Addend does not contain method calls
|
||||
|
||||
### 2.2 Reject as Complex
|
||||
|
||||
Mark as `UpdateKind::Complex` if:
|
||||
|
||||
- LHS appears 2+ times in RHS
|
||||
- Base is variable (not constant)
|
||||
- Addend contains method call (e.g., `_char_to_digit(ch)`)
|
||||
- Addend is complex expression (nested BinaryOp)
|
||||
- Type is not Integer
|
||||
- LoopBodyLocal appears in addend (Phase 5 未サポート)
|
||||
|
||||
### 2.3 Pattern Matrix
|
||||
|
||||
| Pattern | LHS Count | Base | Addend | Decision |
|
||||
|---------|-----------|------|--------|----------|
|
||||
| `v = v * 10 + pos` | 1 | Const(10) | Variable | NumberAccumulation |
|
||||
| `v = v * 10 + 5` | 1 | Const(10) | Const(5) | NumberAccumulation |
|
||||
| `v = v * base + x` | 1 | Variable | Variable | Complex |
|
||||
| `v = v * 10 + f(x)` | 1 | Const(10) | MethodCall | Complex |
|
||||
| `v = v * 10 + v` | 2 | Const(10) | Variable | Complex |
|
||||
|
||||
---
|
||||
|
||||
## Section 3: UpdateKind::NumberAccumulation Definition
|
||||
|
||||
### 3.1 Enum Extension
|
||||
|
||||
**Current** (Phase 170-C-2):
|
||||
```rust
|
||||
pub enum UpdateKind {
|
||||
CounterLike,
|
||||
AccumulationLike,
|
||||
Other,
|
||||
}
|
||||
```
|
||||
|
||||
**Proposed** (Phase 190):
|
||||
```rust
|
||||
pub enum UpdateKind {
|
||||
CounterLike,
|
||||
AccumulationLike,
|
||||
|
||||
/// Phase 190: Number accumulation: result = result * base + addend
|
||||
///
|
||||
/// Typical pattern: digit expansion (atoi, parse_number)
|
||||
/// Example: v = v * 10 + digit
|
||||
NumberAccumulation { base: i64 },
|
||||
|
||||
/// Phase 178+: String append patterns (already implemented)
|
||||
StringAppendChar,
|
||||
StringAppendLiteral,
|
||||
|
||||
/// Complex or unrecognized patterns
|
||||
Complex,
|
||||
|
||||
/// Deprecated
|
||||
Other,
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 UpdateKind::name() Extension
|
||||
|
||||
```rust
|
||||
impl UpdateKind {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
UpdateKind::CounterLike => "CounterLike",
|
||||
UpdateKind::AccumulationLike => "AccumulationLike",
|
||||
UpdateKind::NumberAccumulation { base } => "NumberAccumulation",
|
||||
UpdateKind::StringAppendChar => "StringAppendChar",
|
||||
UpdateKind::StringAppendLiteral => "StringAppendLiteral",
|
||||
UpdateKind::Complex => "Complex",
|
||||
UpdateKind::Other => "Other",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Alternative: Subfield on AccumulationLike
|
||||
|
||||
Instead of new enum variant, could extend AccumulationLike:
|
||||
|
||||
```rust
|
||||
pub enum UpdateKind {
|
||||
CounterLike,
|
||||
AccumulationLike {
|
||||
style: AccumulationStyle,
|
||||
},
|
||||
Complex,
|
||||
Other,
|
||||
}
|
||||
|
||||
pub enum AccumulationStyle {
|
||||
Simple, // result = result + x
|
||||
NumberDigit { base: i64 }, // result = result * base + x
|
||||
StringAppend, // result = result + "literal"
|
||||
}
|
||||
```
|
||||
|
||||
**Decision**: Use dedicated `NumberAccumulation { base: i64 }` for clarity.
|
||||
Simpler to pattern-match, clearer intent.
|
||||
|
||||
---
|
||||
|
||||
## Section 4: classify_number_update Pseudocode
|
||||
|
||||
### 4.1 Algorithm Overview
|
||||
|
||||
**Input**:
|
||||
- `lhs: &str` - carrier variable name
|
||||
- `rhs_ast: &ASTNode` - RHS expression AST
|
||||
|
||||
**Output**:
|
||||
- `UpdateKind` - classified update pattern
|
||||
|
||||
**Steps**:
|
||||
1. Check if RHS is `BinaryOp(Add, left, right)`
|
||||
2. Check if `left` is `BinaryOp(Mul, mul_left, mul_right)`
|
||||
3. Verify `mul_left` is `Variable(lhs)` (LHS appears once)
|
||||
4. Verify `mul_right` is `Literal(Integer(base))`
|
||||
5. Classify `right` (addend):
|
||||
- `Const(n)` → NumberAccumulation
|
||||
- `Variable(name)` → NumberAccumulation
|
||||
- `MethodCall/Other` → Complex
|
||||
|
||||
### 4.2 Pseudocode
|
||||
|
||||
```rust
|
||||
fn classify_number_update(lhs: &str, rhs_ast: &ASTNode) -> UpdateKind {
|
||||
// Step 1: Check outer addition
|
||||
let BinaryOp { op: Add, left: mul_expr, right: addend } = rhs_ast else {
|
||||
return UpdateKind::Complex;
|
||||
};
|
||||
|
||||
// Step 2: Check inner multiplication
|
||||
let BinaryOp { op: Mul, left: mul_left, right: mul_right } = mul_expr else {
|
||||
return UpdateKind::Complex;
|
||||
};
|
||||
|
||||
// Step 3: Verify LHS appears in multiplication
|
||||
let Variable { name: lhs_name } = mul_left else {
|
||||
return UpdateKind::Complex;
|
||||
};
|
||||
if lhs_name != lhs {
|
||||
return UpdateKind::Complex;
|
||||
}
|
||||
|
||||
// Step 4: Extract base constant
|
||||
let Literal { value: Integer(base) } = mul_right else {
|
||||
return UpdateKind::Complex; // base is not constant
|
||||
};
|
||||
|
||||
// Step 5: Classify addend
|
||||
match addend {
|
||||
// Variable or Const: NumberAccumulation
|
||||
Literal { value: Integer(_) } | Variable { .. } => {
|
||||
UpdateKind::NumberAccumulation { base: *base }
|
||||
}
|
||||
|
||||
// Method call or complex expression: Complex
|
||||
MethodCall { .. } | BinaryOp { .. } | Call { .. } => {
|
||||
UpdateKind::Complex
|
||||
}
|
||||
|
||||
_ => UpdateKind::Complex,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 LHS Occurrence Check
|
||||
|
||||
**Goal**: Ensure LHS appears exactly 1 time in RHS.
|
||||
|
||||
**Implementation**:
|
||||
```rust
|
||||
fn count_lhs_occurrences(lhs: &str, rhs: &ASTNode) -> usize {
|
||||
match rhs {
|
||||
Variable { name } if name == lhs => 1,
|
||||
BinaryOp { left, right, .. } => {
|
||||
count_lhs_occurrences(lhs, left) + count_lhs_occurrences(lhs, right)
|
||||
}
|
||||
MethodCall { receiver, args, .. } => {
|
||||
let mut count = count_lhs_occurrences(lhs, receiver);
|
||||
for arg in args {
|
||||
count += count_lhs_occurrences(lhs, arg);
|
||||
}
|
||||
count
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```rust
|
||||
fn classify_number_update(lhs: &str, rhs_ast: &ASTNode) -> UpdateKind {
|
||||
// FIRST: Check LHS occurrence count
|
||||
let lhs_count = count_lhs_occurrences(lhs, rhs_ast);
|
||||
if lhs_count != 1 {
|
||||
return UpdateKind::Complex;
|
||||
}
|
||||
|
||||
// THEN: Check structure
|
||||
// ... (rest of classify_number_update logic)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 5: Pattern2/4 can_lower Specification
|
||||
|
||||
### 5.1 Current Behavior
|
||||
|
||||
**Pattern2 can_lower** (Phase 176+):
|
||||
- Accepts: CounterLike, AccumulationLike, StringAppendChar, StringAppendLiteral
|
||||
- Rejects: Complex → `[joinir/freeze]`
|
||||
|
||||
**Pattern4 can_lower** (Phase 33-19+):
|
||||
- Similar criteria to Pattern2
|
||||
|
||||
### 5.2 Proposed Extension
|
||||
|
||||
**Pattern2 can_lower** (Phase 190+):
|
||||
```rust
|
||||
fn can_lower_carrier_updates(updates: &HashMap<String, UpdateExpr>) -> bool {
|
||||
for (_name, update_expr) in updates {
|
||||
let kind = classify_update_kind(update_expr);
|
||||
|
||||
match kind {
|
||||
// Whitelist: Simple patterns
|
||||
UpdateKind::CounterLike
|
||||
| UpdateKind::AccumulationLike
|
||||
| UpdateKind::StringAppendChar
|
||||
| UpdateKind::StringAppendLiteral
|
||||
| UpdateKind::NumberAccumulation { .. } // ← NEW!
|
||||
=> { /* OK */ }
|
||||
|
||||
// Fail-Fast: Complex patterns
|
||||
UpdateKind::Complex => {
|
||||
eprintln!("[joinir/freeze] Complex carrier update detected");
|
||||
return false;
|
||||
}
|
||||
|
||||
UpdateKind::Other => {
|
||||
eprintln!("[joinir/freeze] Unknown update pattern");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Type Constraint
|
||||
|
||||
**NumberAccumulation Type Check**:
|
||||
```rust
|
||||
fn verify_number_accumulation_type(carrier: &CarrierVar) -> bool {
|
||||
// Phase 190: NumberAccumulation requires Integer type
|
||||
// String types must use StringAppendChar/StringAppendLiteral
|
||||
match carrier.mir_type {
|
||||
MirType::Integer => true,
|
||||
_ => {
|
||||
eprintln!("[joinir/freeze] NumberAccumulation requires Integer type, got {:?}",
|
||||
carrier.mir_type);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 LoopBodyLocal Handling
|
||||
|
||||
**Phase 5 未サポート**:
|
||||
```rust
|
||||
fn check_loopbodylocal_constraint(addend: &UpdateRhs, loop_scope: &LoopScopeShape) -> bool {
|
||||
match addend {
|
||||
UpdateRhs::Variable(name) => {
|
||||
// Check if variable is LoopBodyLocal
|
||||
if loop_scope.body_locals.contains(name) {
|
||||
eprintln!("[joinir/freeze] LoopBodyLocal in NumberAccumulation not supported yet");
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.5 Fallback Strategy
|
||||
|
||||
**Fail-Fast Principle**:
|
||||
- NumberAccumulation detection failure → `Complex` → `[joinir/freeze]`
|
||||
- **NO** silent fallback to LoopBuilder (already deleted)
|
||||
- **NO** new fallback paths
|
||||
|
||||
**Error Message Template**:
|
||||
```
|
||||
[joinir/freeze] NumberAccumulation pattern not supported:
|
||||
carrier: v
|
||||
reason: addend contains method call
|
||||
suggestion: extract method call to loop-local variable
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 6: CarrierUpdateLowerer Pseudocode
|
||||
|
||||
### 6.1 Responsibility Assignment
|
||||
|
||||
**CarrierUpdateLowerer** (Phase 176+):
|
||||
- Input: `CarrierVar`, `UpdateExpr`, `JoinIRBuilder`
|
||||
- Output: Emits JoinIR instructions for carrier update
|
||||
|
||||
**Current Support**:
|
||||
- CounterLike: `dst = lhs + 1`
|
||||
- AccumulationLike: `dst = lhs + rhs`
|
||||
- StringAppendChar: (BoxCall to StringBox.append)
|
||||
|
||||
**Phase 190 Extension**:
|
||||
- NumberAccumulation: `tmp = lhs * base; dst = tmp + addend`
|
||||
|
||||
### 6.2 JoinIR Emission Design
|
||||
|
||||
**Option A: 2-Instruction Approach** (Recommended)
|
||||
```rust
|
||||
fn emit_number_accumulation(
|
||||
builder: &mut JoinIRBuilder,
|
||||
carrier: &CarrierVar,
|
||||
base: i64,
|
||||
addend: &UpdateRhs,
|
||||
) -> ValueId {
|
||||
// Step 1: Multiply by base
|
||||
// tmp = lhs * base
|
||||
let base_value = builder.alloc_value();
|
||||
builder.emit(JoinInst::Const {
|
||||
dst: base_value,
|
||||
value: ConstValue::Integer(base),
|
||||
});
|
||||
|
||||
let mul_result = builder.alloc_value();
|
||||
builder.emit(JoinInst::BinOp {
|
||||
dst: mul_result,
|
||||
op: BinOpKind::Mul,
|
||||
left: carrier.join_id.unwrap(), // Current value from LoopHeader PHI
|
||||
right: base_value,
|
||||
});
|
||||
|
||||
// Step 2: Add addend
|
||||
// dst = tmp + addend
|
||||
let addend_value = emit_addend_value(builder, addend);
|
||||
let result = builder.alloc_value();
|
||||
builder.emit(JoinInst::BinOp {
|
||||
dst: result,
|
||||
op: BinOpKind::Add,
|
||||
left: mul_result,
|
||||
right: addend_value,
|
||||
});
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn emit_addend_value(builder: &mut JoinIRBuilder, addend: &UpdateRhs) -> ValueId {
|
||||
match addend {
|
||||
UpdateRhs::Const(n) => {
|
||||
let val = builder.alloc_value();
|
||||
builder.emit(JoinInst::Const {
|
||||
dst: val,
|
||||
value: ConstValue::Integer(*n),
|
||||
});
|
||||
val
|
||||
}
|
||||
UpdateRhs::Variable(name) => {
|
||||
// Look up variable in boundary.join_inputs or condition_bindings
|
||||
builder.lookup_variable(name)
|
||||
}
|
||||
_ => unreachable!("Complex addend should be rejected in can_lower"),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Option B: Single Complex Expression** (Not Recommended)
|
||||
```rust
|
||||
// Would require JoinInst::ComplexExpr or similar
|
||||
// Violates "flat instruction" principle of JoinIR
|
||||
// NOT RECOMMENDED
|
||||
```
|
||||
|
||||
**Decision**: Use Option A (2-instruction approach).
|
||||
- Pros: Clean separation, easy to optimize later, follows JoinIR flat instruction principle
|
||||
- Cons: None
|
||||
|
||||
### 6.3 Type Constraint Enforcement
|
||||
|
||||
```rust
|
||||
impl CarrierUpdateLowerer {
|
||||
pub fn emit_update(
|
||||
&self,
|
||||
builder: &mut JoinIRBuilder,
|
||||
carrier: &CarrierVar,
|
||||
update_expr: &UpdateExpr,
|
||||
) -> Result<ValueId, String> {
|
||||
match classify_update_kind(update_expr) {
|
||||
UpdateKind::NumberAccumulation { base } => {
|
||||
// Type check
|
||||
if carrier.mir_type != MirType::Integer {
|
||||
return Err(format!(
|
||||
"NumberAccumulation requires Integer type, got {:?}",
|
||||
carrier.mir_type
|
||||
));
|
||||
}
|
||||
|
||||
// Emit instructions
|
||||
Ok(self.emit_number_accumulation(builder, carrier, base, &extract_addend(update_expr)))
|
||||
}
|
||||
// ... other cases
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 Name Dependency Prohibition
|
||||
|
||||
**Phase 170-C-1 Principle**: No name-based heuristics in lowering.
|
||||
|
||||
**Enforcement**:
|
||||
```rust
|
||||
// ✅ GOOD: Structural detection
|
||||
fn is_number_accumulation(update_expr: &UpdateExpr) -> bool {
|
||||
matches!(
|
||||
update_expr,
|
||||
UpdateExpr::BinOp {
|
||||
op: BinOpKind::Add,
|
||||
lhs: _,
|
||||
rhs: UpdateRhs::Variable(_) | UpdateRhs::Const(_),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// ❌ BAD: Name-based detection
|
||||
fn is_number_accumulation(carrier_name: &str) -> bool {
|
||||
carrier_name.contains("result") || carrier_name.contains("v")
|
||||
}
|
||||
```
|
||||
|
||||
**Rationale**:
|
||||
- Variable names are user-controlled and unreliable
|
||||
- Structural AST analysis is robust and refactoring-safe
|
||||
- Follows "箱理論" separation of concerns
|
||||
|
||||
### 6.5 Integration with LoopHeaderPhiBuilder
|
||||
|
||||
**Phase 33-22 Context**:
|
||||
- LoopHeader PHI creates SSOT for carrier current value
|
||||
- `carrier.join_id` points to LoopHeader PHI dst
|
||||
|
||||
**Usage in NumberAccumulation**:
|
||||
```rust
|
||||
fn emit_number_accumulation(
|
||||
builder: &mut JoinIRBuilder,
|
||||
carrier: &CarrierVar,
|
||||
base: i64,
|
||||
addend: &UpdateRhs,
|
||||
) -> ValueId {
|
||||
// Use carrier.join_id (LoopHeader PHI dst) as current value
|
||||
let current_value = carrier.join_id.expect("LoopHeader PHI should set join_id");
|
||||
|
||||
// tmp = current_value * base
|
||||
let base_const = builder.emit_const(ConstValue::Integer(base));
|
||||
let mul_result = builder.emit_binop(BinOpKind::Mul, current_value, base_const);
|
||||
|
||||
// dst = tmp + addend
|
||||
let addend_value = emit_addend_value(builder, addend);
|
||||
builder.emit_binop(BinOpKind::Add, mul_result, addend_value)
|
||||
}
|
||||
```
|
||||
|
||||
**Invariant**: Never directly access `carrier.host_id` in JoinIR emission.
|
||||
Only use `carrier.join_id` (JoinIR-local ValueId).
|
||||
|
||||
---
|
||||
|
||||
## Section 7: Implementation Phases
|
||||
|
||||
### Phase 190-impl-A: Core Detection (2-3 commits)
|
||||
|
||||
**Goal**: Extend LoopUpdateAnalyzer to detect NumberAccumulation patterns.
|
||||
|
||||
**Tasks**:
|
||||
1. Add `UpdateKind::NumberAccumulation { base: i64 }`
|
||||
2. Implement `classify_number_update()` in LoopUpdateAnalyzer
|
||||
3. Add `count_lhs_occurrences()` helper
|
||||
4. Unit tests (5+ cases covering Section 2.3 matrix)
|
||||
|
||||
**Success Criteria**:
|
||||
- `_atoi` loop detected as NumberAccumulation { base: 10 }
|
||||
- Complex patterns (method calls) detected as Complex
|
||||
- Type safety: Only Integer carriers allowed
|
||||
|
||||
### Phase 190-impl-B: CarrierUpdateLowerer Extension (2-3 commits)
|
||||
|
||||
**Goal**: Emit JoinIR for NumberAccumulation updates.
|
||||
|
||||
**Tasks**:
|
||||
1. Extend `CarrierUpdateLowerer::emit_update()` with NumberAccumulation branch
|
||||
2. Implement 2-instruction emission (mul + add)
|
||||
3. Type constraint enforcement
|
||||
4. Unit tests (3+ cases)
|
||||
|
||||
**Success Criteria**:
|
||||
- Correct JoinIR emission: `tmp = v * 10; result = tmp + digit`
|
||||
- Type error on non-Integer carriers
|
||||
- Integration with LoopHeader PHI (carrier.join_id)
|
||||
|
||||
### Phase 190-impl-C: Pattern2/4 Integration (1-2 commits)
|
||||
|
||||
**Goal**: Enable NumberAccumulation in Pattern2/4 can_lower.
|
||||
|
||||
**Tasks**:
|
||||
1. Update `can_lower_carrier_updates()` whitelist
|
||||
2. Add NumberAccumulation test case to Pattern2
|
||||
3. Verify Fail-Fast for Complex patterns
|
||||
|
||||
**Success Criteria**:
|
||||
- `phase189_atoi_mini.hako` passes with JoinIR
|
||||
- Complex patterns (with method calls) rejected at can_lower
|
||||
- `[joinir/freeze]` message for unsupported cases
|
||||
|
||||
### Phase 190-impl-D: E2E Validation (1 commit)
|
||||
|
||||
**Goal**: Real-world JsonParserBox._atoi working via JoinIR.
|
||||
|
||||
**Tasks**:
|
||||
1. Enable JoinIR for `_atoi` method
|
||||
2. Verify MIR output correctness
|
||||
3. Runtime test: parse "12345" → 12345
|
||||
|
||||
**Success Criteria**:
|
||||
- JsonParserBox._atoi compiles via JoinIR Pattern2
|
||||
- Correct MIR: multiply-add sequence
|
||||
- Runtime correctness: atoi tests pass
|
||||
|
||||
---
|
||||
|
||||
## Section 8: Future Extensions
|
||||
|
||||
### 8.1 Other Bases
|
||||
|
||||
**Current**: `base` is extracted from AST (any integer constant).
|
||||
|
||||
**Example**:
|
||||
- Binary: `v = v * 2 + bit`
|
||||
- Hex: `v = v * 16 + hex_digit`
|
||||
|
||||
**No change needed**: Design already supports arbitrary bases.
|
||||
|
||||
### 8.2 Subtraction Variant
|
||||
|
||||
**Pattern**: `v = v * 10 - offset`
|
||||
|
||||
**Extension**:
|
||||
```rust
|
||||
fn classify_number_update(lhs: &str, rhs_ast: &ASTNode) -> UpdateKind {
|
||||
match rhs_ast {
|
||||
BinaryOp { op: Add | Sub, left: mul_expr, right: addend } => {
|
||||
// ... (same logic for both Add and Sub)
|
||||
}
|
||||
_ => UpdateKind::Complex,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Decision**: Support both Add and Sub in Phase 190-impl-A.
|
||||
|
||||
### 8.3 Complex Addends
|
||||
|
||||
**Examples**:
|
||||
- `v = v * 10 + (ch - '0')`
|
||||
- `v = v * 10 + digits.indexOf(ch)`
|
||||
|
||||
**Strategy**:
|
||||
- Phase 190: Reject as Complex (Fail-Fast)
|
||||
- Phase 191+: Extend to handle BinaryOp/MethodCall in addend
|
||||
- Emit extra JoinIR instructions for addend computation
|
||||
- Store addend in temporary ValueId
|
||||
|
||||
**Not in scope for Phase 190**.
|
||||
|
||||
### 8.4 Multi-Base Patterns
|
||||
|
||||
**Example** (unlikely):
|
||||
```nyash
|
||||
v1 = v1 * 10 + d1
|
||||
v2 = v2 * 16 + d2
|
||||
```
|
||||
|
||||
**Current Design**: Each carrier gets its own `NumberAccumulation { base }`.
|
||||
Already supports different bases per carrier.
|
||||
|
||||
---
|
||||
|
||||
## Section 9: Testing Strategy
|
||||
|
||||
### 9.1 Unit Tests (LoopUpdateAnalyzer)
|
||||
|
||||
**Coverage Matrix**:
|
||||
|
||||
| Test Case | Pattern | Expected Kind |
|
||||
|-----------|---------|---------------|
|
||||
| `v = v * 10 + pos` | Basic NumberAccumulation | NumberAccumulation { base: 10 } |
|
||||
| `v = v * 10 + 5` | Const addend | NumberAccumulation { base: 10 } |
|
||||
| `v = v * 2 + bit` | Binary base | NumberAccumulation { base: 2 } |
|
||||
| `v = v * 10 - offset` | Subtraction | NumberAccumulation { base: 10 } |
|
||||
| `v = v * base + x` | Variable base | Complex |
|
||||
| `v = v * 10 + f(x)` | Method call | Complex |
|
||||
| `v = v * 10 + v` | LHS appears 2x | Complex |
|
||||
|
||||
**Implementation**:
|
||||
```rust
|
||||
#[test]
|
||||
fn test_number_accumulation_basic() {
|
||||
let ast = parse("v = v * 10 + pos");
|
||||
let kind = LoopUpdateAnalyzer::classify_update("v", &ast);
|
||||
assert!(matches!(kind, UpdateKind::NumberAccumulation { base: 10 }));
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 Integration Tests (Pattern2)
|
||||
|
||||
**Test Files**:
|
||||
1. `apps/tests/phase190_number_update_basic.hako`
|
||||
- Single carrier: `v = v * 10 + i`
|
||||
- Verify JoinIR emission
|
||||
|
||||
2. `apps/tests/phase190_number_update_multi.hako`
|
||||
- Two carriers: counter + accumulator
|
||||
- Verify multi-carrier lowering
|
||||
|
||||
3. `apps/tests/phase190_number_update_complex.hako`
|
||||
- Complex pattern (method call)
|
||||
- Verify Fail-Fast rejection
|
||||
|
||||
### 9.3 E2E Tests (JsonParserBox)
|
||||
|
||||
**Test Cases**:
|
||||
1. `test_jsonparser_atoi_simple.hako`
|
||||
- Input: "123"
|
||||
- Expected: 123
|
||||
|
||||
2. `test_jsonparser_atoi_negative.hako`
|
||||
- Input: "-456"
|
||||
- Expected: -456
|
||||
|
||||
3. `test_jsonparser_parse_number_min_v2.hako`
|
||||
- Full `_parse_number` method
|
||||
- Verify string + number accumulation
|
||||
|
||||
**Success Criteria**:
|
||||
- All tests pass with JoinIR enabled
|
||||
- Same behavior as LoopBuilder (deleted, but historical behavior)
|
||||
|
||||
---
|
||||
|
||||
## Section 10: Migration Notes
|
||||
|
||||
### 10.1 From LoopBuilder (Deleted)
|
||||
|
||||
**Legacy**: LoopBuilder handled all loop patterns (deleted in Phase 170).
|
||||
|
||||
**Current**: JoinIR Pattern1-4 handle specific patterns.
|
||||
|
||||
**NumberAccumulation Migration**:
|
||||
- Old: LoopBuilder would blindly emit MIR for any loop
|
||||
- New: Pattern2/4 detect NumberAccumulation, emit specialized JoinIR
|
||||
|
||||
**No fallback needed**: LoopBuilder is deleted.
|
||||
|
||||
### 10.2 From Pattern Detection
|
||||
|
||||
**Phase 170-C-1**: Name-based heuristics (e.g., `is_typical_index_name`).
|
||||
|
||||
**Phase 190**: Structural AST analysis (no name dependency).
|
||||
|
||||
**Principle**: Lowering must be name-agnostic, only detection can use names.
|
||||
|
||||
### 10.3 Backward Compatibility
|
||||
|
||||
**Existing Patterns**:
|
||||
- CounterLike: `i = i + 1` (unchanged)
|
||||
- AccumulationLike: `sum = sum + x` (unchanged)
|
||||
- StringAppendChar: `s = s + ch` (unchanged)
|
||||
|
||||
**New Pattern**:
|
||||
- NumberAccumulation: `v = v * 10 + digit` (additive)
|
||||
|
||||
**No Breaking Changes**: All existing patterns continue to work.
|
||||
|
||||
---
|
||||
|
||||
## Section 11: Open Questions
|
||||
|
||||
### Q1: Should we support division?
|
||||
|
||||
**Pattern**: `v = v / 10`
|
||||
|
||||
**Current**: Only +, -, *, / in BinOpKind.
|
||||
|
||||
**Decision**: Out of scope for Phase 190 (no real-world use case found).
|
||||
|
||||
### Q2: Should addend be restricted to loop params only?
|
||||
|
||||
**Current Design**: Allow any Variable (loop param, outer local, carrier).
|
||||
|
||||
**Alternative**: Only allow LoopParam variables in addend.
|
||||
|
||||
**Decision**: Current design is more flexible, restrict if problems arise.
|
||||
|
||||
### Q3: How to handle floating-point bases?
|
||||
|
||||
**Example**: `v = v * 1.5 + x`
|
||||
|
||||
**Current**: `base: i64` only supports integers.
|
||||
|
||||
**Decision**: Out of scope (no real-world use case in Nyash codebase).
|
||||
|
||||
---
|
||||
|
||||
## Section 12: Success Metrics
|
||||
|
||||
### 12.1 Functional Metrics
|
||||
|
||||
- ✅ JsonParserBox._atoi compiles via JoinIR
|
||||
- ✅ phase189_atoi_mini.hako passes
|
||||
- ✅ Complex patterns rejected with `[joinir/freeze]`
|
||||
- ✅ No silent fallbacks (Fail-Fast verified)
|
||||
|
||||
### 12.2 Code Quality Metrics
|
||||
|
||||
- ✅ No name-based heuristics in lowering
|
||||
- ✅ Type safety enforced (Integer only)
|
||||
- ✅ Unit test coverage > 80%
|
||||
- ✅ Documentation updated (this doc + overview)
|
||||
|
||||
### 12.3 Performance Metrics
|
||||
|
||||
**Not a goal for Phase 190**: Focus on correctness, not optimization.
|
||||
|
||||
**Future**: Phase 191+ can optimize mul+add to single instruction if needed.
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: AST Examples
|
||||
|
||||
### A.1 Basic NumberAccumulation
|
||||
|
||||
**Source**:
|
||||
```nyash
|
||||
v = v * 10 + pos
|
||||
```
|
||||
|
||||
**AST**:
|
||||
```
|
||||
Assignment {
|
||||
target: Variable { name: "v" },
|
||||
value: BinaryOp {
|
||||
operator: Add,
|
||||
left: BinaryOp {
|
||||
operator: Mul,
|
||||
left: Variable { name: "v" },
|
||||
right: Literal { value: Integer(10) },
|
||||
},
|
||||
right: Variable { name: "pos" },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### A.2 Complex Pattern (Rejected)
|
||||
|
||||
**Source**:
|
||||
```nyash
|
||||
v = v * 10 + digits.indexOf(ch)
|
||||
```
|
||||
|
||||
**AST**:
|
||||
```
|
||||
Assignment {
|
||||
target: Variable { name: "v" },
|
||||
value: BinaryOp {
|
||||
operator: Add,
|
||||
left: BinaryOp {
|
||||
operator: Mul,
|
||||
left: Variable { name: "v" },
|
||||
right: Literal { value: Integer(10) },
|
||||
},
|
||||
right: MethodCall {
|
||||
receiver: Variable { name: "digits" },
|
||||
method: "indexOf",
|
||||
args: [Variable { name: "ch" }],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Decision**: Rejected as Complex (MethodCall in addend).
|
||||
|
||||
---
|
||||
|
||||
## Appendix B: References
|
||||
|
||||
### B.1 Related Phases
|
||||
|
||||
- **Phase 170-C-1**: Carrier name heuristics
|
||||
- **Phase 170-C-2**: LoopUpdateSummary skeleton
|
||||
- **Phase 176**: Multi-carrier lowering
|
||||
- **Phase 178**: String update detection
|
||||
|
||||
### B.2 Related Files
|
||||
|
||||
**Core**:
|
||||
- `src/mir/join_ir/lowering/loop_update_summary.rs`
|
||||
- `src/mir/join_ir/lowering/loop_update_analyzer.rs`
|
||||
- `src/mir/join_ir/lowering/carrier_update_lowerer.rs`
|
||||
|
||||
**Patterns**:
|
||||
- `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`
|
||||
- `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
|
||||
|
||||
**Tests**:
|
||||
- `tools/hako_shared/json_parser.hako` (line 436: _atoi)
|
||||
- `apps/tests/phase189_atoi_mini.hako`
|
||||
|
||||
### B.3 Design Principles
|
||||
|
||||
1. **Fail-Fast**: Reject Complex patterns explicitly
|
||||
2. **Whitelist Control**: Only safe patterns allowed
|
||||
3. **Type Safety**: Integer-only for NumberAccumulation
|
||||
4. **Name Agnostic**: No name-based lowering
|
||||
5. **SSOT**: LoopHeader PHI as single source of truth
|
||||
|
||||
---
|
||||
|
||||
## Revision History
|
||||
|
||||
- **2025-12-09**: Initial design (Section 1-12)
|
||||
- **2025-12-09**: Phase 190-impl 完了
|
||||
- Phase 190-impl-A: LoopUpdateAnalyzer に NumberAccumulation 検出実装
|
||||
- Phase 190-impl-B: CarrierUpdateLowerer で 2-instruction emission 実装
|
||||
- Phase 190-impl-C: Pattern2 can_lower ホワイトリスト更新
|
||||
- Phase 190-impl-D: E2E 検証成功 + PHI 配線修正
|
||||
- **バグ発見**: body-local と carrier の ValueId 衝突問題
|
||||
- **修正**: `body_local_start_offset = env.len() + carrier_info.carriers.len()` で安全な ValueId 空間分割
|
||||
- **E2E 結果**: `phase190_atoi_impl.hako` → 12 ✅、`phase190_parse_number_impl.hako` → 123 ✅
|
||||
- **制約**: body-local 変数 assignment は JoinIR 未対応(Phase 186 残タスク)
|
||||
- ExitLine contract Verifier 追加(`#[cfg(debug_assertions)]`)
|
||||
@ -1,291 +0,0 @@
|
||||
# Phase 191: Body-Local Init & Update Lowering Integration
|
||||
|
||||
**Status**: Ready for Implementation
|
||||
**Date**: 2025-12-09
|
||||
**Prerequisite**: Phase 190-impl complete (NumberAccumulation + PHI wiring fixed)
|
||||
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
Phase 184/186 で作った `LoopBodyLocalEnv`, `UpdateEnv`, `LoopBodyLocalInitLowerer` を
|
||||
Pattern2/4 の中に本番統合して、`local digit = ...` などの body-local を JoinIR/MIR にきちんと落とす。
|
||||
|
||||
P2/P4 の既存ループ(`_atoi` など)を、body-local を含んだ形でも JoinIR で動かせるようにする。
|
||||
|
||||
---
|
||||
|
||||
## Task 191-1: 対象ケースの再確認(1本に絞る)
|
||||
|
||||
### 目標
|
||||
「いま body-local が原因で JoinIR を OFF にしている代表ループ」を 1 本だけ選ぶ。
|
||||
|
||||
### 候補
|
||||
```nyash
|
||||
// _atoi 簡略版
|
||||
local digit = s[i] - '0'
|
||||
result = result * 10 + digit
|
||||
```
|
||||
|
||||
または body-local を使う最小テスト:
|
||||
```nyash
|
||||
// phase191_body_local_atoi.hako
|
||||
static box Main {
|
||||
main() {
|
||||
local result = 0
|
||||
local i = 0
|
||||
loop(i < 3) {
|
||||
local digit = i + 1 // body-local
|
||||
result = result * 10 + digit
|
||||
i = i + 1
|
||||
}
|
||||
print(result) // Expected: 123
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 成果物
|
||||
- 選定した代表ループのファイルパス
|
||||
- なぜ現在 JoinIR で通らないかの説明(body-local init が emit されていない)
|
||||
|
||||
---
|
||||
|
||||
## Task 191-2: LoopBodyLocalInitLowerer の統合
|
||||
|
||||
### 対象ファイル
|
||||
- `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`
|
||||
- 必要なら `pattern4_with_continue.rs` も
|
||||
|
||||
### 実装内容
|
||||
|
||||
ループ lowering の流れの中で:
|
||||
|
||||
1. ループ本体 AST を `LoopBodyLocalInitLowerer` に渡して、
|
||||
body-local の `local` 定義 (`digit = ...`) を JoinIR に emit しつつ、
|
||||
`LoopBodyLocalEnv` に `name -> join_id` を登録させる。
|
||||
|
||||
2. ここで emit するのは「init 分」だけ(Update 分は既に `CarrierUpdateEmitter` + `UpdateEnv` に任せる)。
|
||||
|
||||
### init emission の位置
|
||||
|
||||
- ループ body の **先頭**(carrier update より前)に出るようにする。
|
||||
- JoinIR 側の ValueId 空間は、Phase 190-impl-D での衝突回避ルール (`body_local_start_offset`) に従う。
|
||||
|
||||
### コード変更例
|
||||
|
||||
```rust
|
||||
// pattern2_with_break.rs 内
|
||||
|
||||
// Phase 191: Emit body-local initializations
|
||||
let body_local_env = LoopBodyLocalInitLowerer::lower_init(
|
||||
&body_ast,
|
||||
&mut join_builder,
|
||||
&mut alloc_body_local_value,
|
||||
)?;
|
||||
|
||||
// Phase 191: Create UpdateEnv with both condition and body-local
|
||||
let update_env = UpdateEnv::new(condition_env.clone(), body_local_env.clone());
|
||||
|
||||
// Emit carrier updates using UpdateEnv
|
||||
for (carrier_name, update_expr) in &carrier_info.updates {
|
||||
CarrierUpdateEmitter::emit_carrier_update_with_env(
|
||||
&mut join_builder,
|
||||
carrier_name,
|
||||
update_expr,
|
||||
&update_env,
|
||||
)?;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 191-3: UpdateEnv 統合の確認
|
||||
|
||||
### 対象ファイル
|
||||
- `src/mir/join_ir/lowering/carrier_update_emitter.rs`(すでに UpdateEnv 対応済み)
|
||||
|
||||
### 確認内容
|
||||
|
||||
選んだ代表ループで:
|
||||
- `UpdateEnv` が:
|
||||
- `ConditionEnv`(LoopParam / OuterLocal)
|
||||
- `LoopBodyLocalEnv`(今回 lower した init の join_id)
|
||||
両方を見て `digit` や `temp` を正しく解決できているか確認。
|
||||
|
||||
### ユニットテスト追加
|
||||
|
||||
body-local `digit` を含む NumberAccumulation 更新を与えて、
|
||||
JoinIR 側で Mul/Add 生成がエラーなく通ること:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn test_number_accumulation_with_body_local() {
|
||||
// UpdateEnv with body-local "digit" → ValueId(10)
|
||||
let mut body_local_env = LoopBodyLocalEnv::new();
|
||||
body_local_env.register("digit", ValueId(10));
|
||||
|
||||
let condition_env = ConditionEnv::new();
|
||||
let update_env = UpdateEnv::new(condition_env, body_local_env);
|
||||
|
||||
// Resolve "digit" should return ValueId(10)
|
||||
assert_eq!(update_env.resolve("digit"), Some(ValueId(10)));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 191-4: E2E テスト
|
||||
|
||||
### テストケース
|
||||
|
||||
191-1 で選んだ Minimal `_atoi` ループを:
|
||||
- body-local 版(`local digit = ...`)に戻して、
|
||||
- `NYASH_JOINIR_CORE=1` で実行 → 期待値が返ることを確認。
|
||||
|
||||
```bash
|
||||
# E2E テスト実行
|
||||
./target/release/hakorune apps/tests/phase191_body_local_atoi.hako
|
||||
# Expected output: 123
|
||||
```
|
||||
|
||||
### 退行チェック
|
||||
|
||||
- 既存の int-only ループ(body-local なし)が引き続き動くこと
|
||||
- `phase190_atoi_impl.hako` → 12
|
||||
- `phase190_parse_number_impl.hako` → 123
|
||||
- Trim/JsonParser の P5/P2 ラインに影響がないこと
|
||||
|
||||
---
|
||||
|
||||
## Task 191-5: ドキュメント更新
|
||||
|
||||
### 更新対象
|
||||
|
||||
1. **phase184-body-local-mir-lowering.md** / **phase186-body-local-init-lowering.md**:
|
||||
- 「Phase 191 で Pattern2/4 に統合完了」と追記
|
||||
- 代表ループ名と、init → UpdateEnv → PHI → ExitLine までの流れを 1 ケースだけ図解
|
||||
|
||||
2. **CURRENT_TASK.md**:
|
||||
- Phase 191: 完了マーク
|
||||
- 残タスク(もし他のループは後回しにするなら)を更新
|
||||
|
||||
3. **joinir-architecture-overview.md**:
|
||||
- Section 7.2 の残タスク「body-local 変数の init + update lowering」を完了マークに更新
|
||||
|
||||
---
|
||||
|
||||
## 成功基準
|
||||
|
||||
- [x] 代表ループ(body-local 版 `_atoi`)が JoinIR only で期待値を返す
|
||||
- [x] 既存テスト(phase190_*.hako)が退行しない
|
||||
- [x] UpdateEnv が body-local 変数を正しく解決できる
|
||||
- [x] ドキュメントが更新されている
|
||||
|
||||
---
|
||||
|
||||
## 実装完了レポート (2025-12-09)
|
||||
|
||||
### 実装概要
|
||||
|
||||
Phase 191 が**完全成功**しました! `LoopBodyLocalInitLowerer` を Pattern2 に統合し、body-local 変数の初期化式を JoinIR に正しく降下できるようになりました。
|
||||
|
||||
### 主な変更
|
||||
|
||||
1. **`loop_with_break_minimal.rs`**:
|
||||
- 関数シグネチャに `body_ast: &[ASTNode]` を追加
|
||||
- `body_local_env: Option<&mut LoopBodyLocalEnv>` に変更(mutable)
|
||||
- ループ body の先頭で `LoopBodyLocalInitLowerer` を呼び出し、初期化式を JoinIR に emit
|
||||
- Carrier update の前に body-local init を配置(正しい依存順序)
|
||||
|
||||
2. **`pattern2_with_break.rs`**:
|
||||
- `collect_body_local_variables` 呼び出しを削除(ValueId の二重割り当てを回避)
|
||||
- 空の `LoopBodyLocalEnv` を作成し、`LoopBodyLocalInitLowerer` に委譲
|
||||
- `lower_loop_with_break_minimal` に `_body` AST を渡すよう修正
|
||||
|
||||
3. **テストファイル**:
|
||||
- `apps/tests/phase191_body_local_atoi.hako` 新規作成
|
||||
- `local digit = i + 1` パターンで body-local 変数を使用
|
||||
- `result = result * 10 + digit` で NumberAccumulation パターン検証
|
||||
|
||||
4. **テスト修正**:
|
||||
- `tests/json_program_loop.rs` の `program_loop_body_local_exit` を修正
|
||||
- スコープ外の body-local 変数参照を削除(正しい動作に修正)
|
||||
|
||||
### 実行結果
|
||||
|
||||
```bash
|
||||
$ ./target/release/hakorune apps/tests/phase191_body_local_atoi.hako
|
||||
123 # ✅ 期待値通り
|
||||
|
||||
$ ./target/release/hakorune apps/tests/phase190_atoi_impl.hako
|
||||
12 # ✅ 退行なし
|
||||
|
||||
$ ./target/release/hakorune apps/tests/phase190_parse_number_impl.hako
|
||||
123 # ✅ 退行なし
|
||||
|
||||
$ cargo test --release json_program_loop
|
||||
test json_loop_simple_verifies ... ok
|
||||
test json_loop_body_local_exit_verifies ... ok
|
||||
test json_loop_with_continue_verifies ... ok
|
||||
# ✅ 全テストパス
|
||||
```
|
||||
|
||||
### 技術的発見
|
||||
|
||||
1. **ValueId 二重割り当て問題**:
|
||||
- 旧実装: `collect_body_local_variables` が ValueId を事前割り当て → `LoopBodyLocalInitLowerer` がスキップ
|
||||
- 解決: 空の `LoopBodyLocalEnv` を渡し、`LoopBodyLocalInitLowerer` に完全委譲
|
||||
|
||||
2. **JoinIR ValueId 空間**:
|
||||
- JoinIR は独立した ValueId 空間を持つ(0 から開始)
|
||||
- Host ValueId へのマッピングは merge 段階で実施
|
||||
- `body_local_start_offset` は Host 空間の概念であり、JoinIR には不要
|
||||
|
||||
3. **UpdateEnv の優先順位**:
|
||||
- ConditionEnv(ループパラメータ・条件変数)が最優先
|
||||
- LoopBodyLocalEnv(body-local 変数)がフォールバック
|
||||
- この順序により、シャドウイングが正しく動作
|
||||
|
||||
### 制限事項
|
||||
|
||||
現在の実装では以下の初期化式のみサポート:
|
||||
- 整数リテラル: `local x = 42`
|
||||
- 変数参照: `local y = i`
|
||||
- 二項演算: `local z = i + 1`, `local w = pos - start`
|
||||
|
||||
未サポート(Fail-Fast):
|
||||
- メソッド呼び出し: `local s = str.substring(...)`
|
||||
- 文字列操作: `local t = s + "abc"`
|
||||
- 複雑な式: ネストした呼び出し、非算術演算
|
||||
|
||||
### 次のステップ
|
||||
|
||||
Phase 191 で以下が達成されました:
|
||||
- [x] Pattern 2 への body-local init lowering 統合
|
||||
- [x] UpdateEnv による body-local 変数解決
|
||||
- [x] NumberAccumulation パターンでの動作検証
|
||||
- [x] 既存テストの退行なし
|
||||
|
||||
今後の拡張:
|
||||
- Pattern 4 (continue) への同様の統合(必要に応じて)
|
||||
- 文字列初期化式のサポート(Phase 188 の延長)
|
||||
- メソッド呼び出し初期化式のサポート(Phase 192+)
|
||||
|
||||
---
|
||||
|
||||
## 関連ファイル
|
||||
|
||||
### インフラ(Phase 184 で実装済み)
|
||||
- `src/mir/join_ir/lowering/loop_body_local_env.rs`
|
||||
- `src/mir/join_ir/lowering/update_env.rs`
|
||||
- `src/mir/join_ir/lowering/carrier_update_emitter.rs`
|
||||
|
||||
### 統合対象
|
||||
- `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`
|
||||
- `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
|
||||
|
||||
### テストファイル
|
||||
- `apps/tests/phase191_body_local_atoi.hako`(新規作成)
|
||||
- `apps/tests/phase190_atoi_impl.hako`(退行確認)
|
||||
- `apps/tests/phase190_parse_number_impl.hako`(退行確認)
|
||||
@ -1,600 +0,0 @@
|
||||
# Phase 192: Complex Addend Design (Doc-Only)
|
||||
|
||||
**Status**: Design Phase
|
||||
**Date**: 2025-12-09
|
||||
**Prerequisite**: Phase 191 complete (body-local init integrated)
|
||||
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
`result = result * 10 + digits.indexOf(ch)` のような
|
||||
「加算側がメソッド呼び出し」のパターンを、
|
||||
既存の NumberAccumulation ラインの前処理として安全に扱えるようにする。
|
||||
|
||||
新パターンは増やさず、Analyzer/Lowerer の箱を拡張するだけに留める。
|
||||
|
||||
---
|
||||
|
||||
## Section 1: 対象 RHS パターン一覧
|
||||
|
||||
### 1.1 JsonParser._parse_number (lines 106-142)
|
||||
|
||||
**ループパターン**:
|
||||
```nyash
|
||||
local num_str = ""
|
||||
local digits = "0123456789"
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
local digit_pos = digits.indexOf(ch)
|
||||
|
||||
if digit_pos < 0 {
|
||||
break
|
||||
}
|
||||
|
||||
num_str = num_str + ch // ← StringAppendChar (already supported)
|
||||
p = p + 1
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: このループは string accumulation なので Phase 192 の対象外(既にサポート済み)。
|
||||
|
||||
### 1.2 JsonParser._atoi (lines 436-467)
|
||||
|
||||
**ループパターン**:
|
||||
```nyash
|
||||
local v = 0
|
||||
local digits = "0123456789"
|
||||
loop(i < n) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch < "0" || ch > "9" { break }
|
||||
local pos = digits.indexOf(ch)
|
||||
if pos < 0 { break }
|
||||
v = v * 10 + pos // ← NumberAccumulation with body-local (Phase 191 supported)
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
|
||||
**Current Status**: Phase 191 で body-local `pos` が使えるようになったので対応可能。
|
||||
|
||||
### 1.3 Complex Addend Pattern (Target for Phase 192)
|
||||
|
||||
**パターン**:
|
||||
```nyash
|
||||
local v = 0
|
||||
local digits = "0123456789"
|
||||
loop(i < n) {
|
||||
local ch = s.substring(i, i+1)
|
||||
v = v * 10 + digits.indexOf(ch) // ← Complex addend (method call)
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
|
||||
**AST Form**:
|
||||
```
|
||||
Assign(
|
||||
lhs = "v",
|
||||
rhs = BinaryOp(
|
||||
op = Add,
|
||||
left = BinaryOp(
|
||||
op = Mul,
|
||||
left = Variable("v"),
|
||||
right = Literal(Integer(10))
|
||||
),
|
||||
right = MethodCall(
|
||||
receiver = Variable("digits"),
|
||||
method = "indexOf",
|
||||
args = [Variable("ch")]
|
||||
)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
**Characteristics**:
|
||||
- LHS appears exactly once in RHS (in left-most multiplication)
|
||||
- Base: 10 (constant)
|
||||
- Addend: **MethodCall** (complex expression)
|
||||
- Current behavior: Rejected as `UpdateKind::Complex`
|
||||
|
||||
---
|
||||
|
||||
## Section 2: LHS 出現回数とMethod Call位置の整理
|
||||
|
||||
### 2.1 パターンマトリクス
|
||||
|
||||
| Pattern | LHS Count | Base | Addend | Current | Phase 192 |
|
||||
|---------|-----------|------|--------|---------|-----------|
|
||||
| `v = v * 10 + pos` | 1 | Const(10) | Variable | NumberAccumulation | No change |
|
||||
| `v = v * 10 + 5` | 1 | Const(10) | Const(5) | NumberAccumulation | No change |
|
||||
| `v = v * 10 + digits.indexOf(ch)` | 1 | Const(10) | MethodCall | **Complex** | **Normalize** |
|
||||
| `v = v * base + x` | 1 | Variable | Variable | Complex | Fail-Fast |
|
||||
| `v = v * 10 + (a + b)` | 1 | Const(10) | BinaryOp | Complex | Future |
|
||||
| `v = v * 10 + v` | 2 | Const(10) | Variable | Complex | Fail-Fast |
|
||||
|
||||
### 2.2 Method Call の位置分類
|
||||
|
||||
**Type A: Addend に Method Call(Phase 192 対象)**:
|
||||
```nyash
|
||||
v = v * 10 + digits.indexOf(ch)
|
||||
```
|
||||
→ Normalize: `temp = digits.indexOf(ch); v = v * 10 + temp`
|
||||
|
||||
**Type B: Base に Method Call(対象外)**:
|
||||
```nyash
|
||||
v = v * getBase() + x
|
||||
```
|
||||
→ Fail-Fast(base は constant のみ許可)
|
||||
|
||||
**Type C: LHS 側に Method Call(対象外)**:
|
||||
```nyash
|
||||
v = obj.getValue() * 10 + x
|
||||
```
|
||||
→ Fail-Fast(LHS は simple variable のみ)
|
||||
|
||||
### 2.3 Nested Method Call(将来拡張)
|
||||
|
||||
```nyash
|
||||
v = v * 10 + parser.parse(s.substring(i, i+1))
|
||||
```
|
||||
→ Phase 192 では Fail-Fast、Phase 193+ で対応検討
|
||||
|
||||
---
|
||||
|
||||
## Section 3: Temp 分解戦略
|
||||
|
||||
### 3.1 正規化アプローチ
|
||||
|
||||
**Before (Complex)**:
|
||||
```nyash
|
||||
result = result * 10 + digits.indexOf(ch)
|
||||
```
|
||||
|
||||
**After (Normalized)**:
|
||||
```nyash
|
||||
local temp_digit = digits.indexOf(ch)
|
||||
result = result * 10 + temp_digit
|
||||
```
|
||||
|
||||
### 3.2 正規化の責務
|
||||
|
||||
新しい箱: **ComplexAddendNormalizer**
|
||||
|
||||
**入力**:
|
||||
- `Assign(lhs, complex_rhs)` where `complex_rhs` has MethodCall in addend
|
||||
- ループ本体 AST(temp 変数を挿入する位置情報)
|
||||
|
||||
**出力**:
|
||||
- `temp` 定義: `local temp = methodCall(...)`
|
||||
- 正規化された Assign: `lhs = lhs * base + temp`
|
||||
|
||||
**配置**:
|
||||
- Pattern2 lowering の前処理ライン(`can_lower` の前)
|
||||
|
||||
---
|
||||
|
||||
## Section 4: ComplexAddendNormalizer 擬似コード
|
||||
|
||||
### 4.1 検出ロジック
|
||||
|
||||
```rust
|
||||
fn is_complex_addend_pattern(update_expr: &UpdateExpr) -> bool {
|
||||
// Check structure: lhs = lhs * base + addend
|
||||
let UpdateExpr::BinOp { op: BinOpKind::Add, left, right } = update_expr else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Left side: lhs * base (multiplication)
|
||||
let UpdateRhs::BinOp { op: BinOpKind::Mul, .. } = left else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Right side (addend): MethodCall or complex expression
|
||||
matches!(right, UpdateRhs::MethodCall(_) | UpdateRhs::BinaryOp(_))
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 正規化ロジック
|
||||
|
||||
```rust
|
||||
fn normalize_complex_addend(
|
||||
lhs: &str,
|
||||
update_expr: &UpdateExpr,
|
||||
body_ast: &mut Vec<ASTNode>,
|
||||
) -> Result<(String, UpdateExpr), String> {
|
||||
// Extract addend (method call or complex expression)
|
||||
let addend = extract_addend(update_expr)?;
|
||||
|
||||
// Generate temp variable name
|
||||
let temp_name = format!("temp_{}_addend", lhs);
|
||||
|
||||
// Insert temp assignment at current position
|
||||
// local temp = digits.indexOf(ch)
|
||||
let temp_init = ASTNode::LocalDeclaration {
|
||||
name: temp_name.clone(),
|
||||
init: Some(Box::new(addend.to_ast())),
|
||||
};
|
||||
body_ast.insert(0, temp_init); // Insert at loop body start
|
||||
|
||||
// Create normalized update expression
|
||||
// lhs = lhs * 10 + temp
|
||||
let normalized_expr = UpdateExpr::BinOp {
|
||||
op: BinOpKind::Add,
|
||||
left: Box::new(UpdateRhs::BinOp {
|
||||
op: BinOpKind::Mul,
|
||||
left: Box::new(UpdateRhs::Variable(lhs.to_string())),
|
||||
right: Box::new(UpdateRhs::Const(extract_base(update_expr)?)),
|
||||
}),
|
||||
right: Box::new(UpdateRhs::Variable(temp_name.clone())),
|
||||
};
|
||||
|
||||
Ok((temp_name, normalized_expr))
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 Phase 191 との統合
|
||||
|
||||
Phase 191 の `LoopBodyLocalInitLowerer` が `temp` の初期化を処理:
|
||||
- `local temp_digit = digits.indexOf(ch)` → JoinIR に emit
|
||||
- `LoopBodyLocalEnv` に `temp_digit -> join_id` を登録
|
||||
- `UpdateEnv` が `temp_digit` を解決して NumberAccumulation に渡す
|
||||
|
||||
---
|
||||
|
||||
## Section 5: LoopUpdateAnalyzer との責務分け
|
||||
|
||||
### 5.1 現在のフロー
|
||||
|
||||
```
|
||||
AST → LoopUpdateAnalyzer → UpdateKind::Complex (Fail-Fast)
|
||||
```
|
||||
|
||||
### 5.2 Phase 192 フロー
|
||||
|
||||
```
|
||||
AST → LoopUpdateAnalyzer → UpdateKind::Complex
|
||||
↓ (Pattern2 内 can_lower 前)
|
||||
ComplexAddendNormalizer → 前処理 (temp 生成)
|
||||
↓
|
||||
再度 LoopUpdateAnalyzer → UpdateKind::NumberAccumulation { base: 10 }
|
||||
↓
|
||||
Pattern2 lowering → JoinIR emission
|
||||
```
|
||||
|
||||
### 5.3 can_lower の変更点
|
||||
|
||||
**Before (Phase 191)**:
|
||||
```rust
|
||||
fn can_lower_carrier_updates(updates: &HashMap<String, UpdateExpr>) -> bool {
|
||||
for (name, update_expr) in updates {
|
||||
let kind = classify_update_kind(update_expr);
|
||||
match kind {
|
||||
UpdateKind::Complex => {
|
||||
eprintln!("[joinir/freeze] Complex carrier update");
|
||||
return false;
|
||||
}
|
||||
_ => { /* OK */ }
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
```
|
||||
|
||||
**After (Phase 192)**:
|
||||
```rust
|
||||
fn can_lower_carrier_updates_with_normalization(
|
||||
updates: &HashMap<String, UpdateExpr>,
|
||||
body_ast: &mut Vec<ASTNode>,
|
||||
) -> Result<HashMap<String, UpdateExpr>, String> {
|
||||
let mut normalized_updates = HashMap::new();
|
||||
|
||||
for (name, update_expr) in updates {
|
||||
let kind = classify_update_kind(update_expr);
|
||||
match kind {
|
||||
UpdateKind::Complex => {
|
||||
// Try normalization
|
||||
if is_complex_addend_pattern(update_expr) {
|
||||
let (temp_name, normalized_expr) =
|
||||
ComplexAddendNormalizer::normalize(name, update_expr, body_ast)?;
|
||||
|
||||
// Re-classify
|
||||
let normalized_kind = classify_update_kind(&normalized_expr);
|
||||
if matches!(normalized_kind, UpdateKind::NumberAccumulation { .. }) {
|
||||
normalized_updates.insert(name.clone(), normalized_expr);
|
||||
} else {
|
||||
return Err("[joinir/freeze] Normalization failed".to_string());
|
||||
}
|
||||
} else {
|
||||
return Err("[joinir/freeze] Complex pattern not supported".to_string());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
normalized_updates.insert(name.clone(), update_expr.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(normalized_updates)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 6: Emission 側への影響
|
||||
|
||||
### 6.1 JoinIR Emission(変更なし)
|
||||
|
||||
ComplexAddendNormalizer で前処理するため、既存の emission ラインは変更不要:
|
||||
|
||||
1. **LoopBodyLocalInitLowerer** (Phase 191):
|
||||
- `local temp_digit = digits.indexOf(ch)` を JoinIR に emit
|
||||
- MethodCall の lowering は既存の式 lowerer に委譲
|
||||
|
||||
2. **CarrierUpdateLowerer** (Phase 190):
|
||||
- `result = result * 10 + temp_digit` を NumberAccumulation として emission
|
||||
- `temp_digit` は UpdateEnv 経由で解決
|
||||
|
||||
3. **NumberAccumulation emission**:
|
||||
- Phase 190 の 2-instruction emission そのまま:
|
||||
```
|
||||
tmp = result * 10
|
||||
result = tmp + temp_digit
|
||||
```
|
||||
|
||||
### 6.2 設計原則
|
||||
|
||||
- **Separation of Concerns**: 正規化 (ComplexAddendNormalizer) と emission (CarrierUpdateLowerer) を分離
|
||||
- **Reusability**: 既存の body-local init / NumberAccumulation emission を再利用
|
||||
- **Fail-Fast**: 対応できないパターンは明示的エラー
|
||||
|
||||
---
|
||||
|
||||
## Section 7: Implementation Phases (TBD)
|
||||
|
||||
### Phase 192-impl-A: ComplexAddendNormalizer 実装
|
||||
|
||||
1. `is_complex_addend_pattern()` 検出ロジック
|
||||
2. `normalize_complex_addend()` 正規化ロジック
|
||||
3. temp 変数生成とAST挿入
|
||||
|
||||
### Phase 192-impl-B: Pattern2 統合
|
||||
|
||||
1. `can_lower` を `can_lower_with_normalization` に拡張
|
||||
2. 正規化後の UpdateExpr で再解析
|
||||
3. Unit tests (5+ cases)
|
||||
|
||||
### Phase 192-impl-C: E2E テスト
|
||||
|
||||
1. `phase192_complex_addend_atoi.hako` 作成
|
||||
2. `result = result * 10 + digits.indexOf(ch)` パターンで期待値確認
|
||||
3. 退行テスト(phase191_*.hako)
|
||||
|
||||
---
|
||||
|
||||
## Section 8: 制約と Non-Goals
|
||||
|
||||
### 8.1 対応パターン
|
||||
|
||||
**Phase 192 で対応**:
|
||||
- Addend に simple MethodCall: `v = v * 10 + digits.indexOf(ch)`
|
||||
- Addend が Variable の場合は Phase 191 で対応済み
|
||||
|
||||
**Phase 192 で非対応(将来拡張)**:
|
||||
- Nested method call: `v = v * 10 + parser.parse(s.substring(...))`
|
||||
- Complex binary expression: `v = v * 10 + (a + b * c)`
|
||||
- Multiple method calls in same update
|
||||
|
||||
### 8.2 Fail-Fast ケース
|
||||
|
||||
- Base が variable: `v = v * base + f(x)`
|
||||
- LHS が複数回出現: `v = v * 10 + v`
|
||||
- Method call が base 側: `v = v * getBase() + x`
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: AST Examples
|
||||
|
||||
### A.1 Before Normalization
|
||||
|
||||
**Source**:
|
||||
```nyash
|
||||
result = result * 10 + digits.indexOf(ch)
|
||||
```
|
||||
|
||||
**AST**:
|
||||
```
|
||||
Assignment {
|
||||
target: Variable { name: "result" },
|
||||
value: BinaryOp {
|
||||
operator: Add,
|
||||
left: BinaryOp {
|
||||
operator: Mul,
|
||||
left: Variable { name: "result" },
|
||||
right: Literal { value: Integer(10) },
|
||||
},
|
||||
right: MethodCall {
|
||||
receiver: Variable { name: "digits" },
|
||||
method: "indexOf",
|
||||
args: [Variable { name: "ch" }],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### A.2 After Normalization
|
||||
|
||||
**Source**:
|
||||
```nyash
|
||||
local temp_result_addend = digits.indexOf(ch)
|
||||
result = result * 10 + temp_result_addend
|
||||
```
|
||||
|
||||
**AST**:
|
||||
```
|
||||
[
|
||||
LocalDeclaration {
|
||||
name: "temp_result_addend",
|
||||
init: Some(MethodCall {
|
||||
receiver: Variable { name: "digits" },
|
||||
method: "indexOf",
|
||||
args: [Variable { name: "ch" }],
|
||||
}),
|
||||
},
|
||||
Assignment {
|
||||
target: Variable { name: "result" },
|
||||
value: BinaryOp {
|
||||
operator: Add,
|
||||
left: BinaryOp {
|
||||
operator: Mul,
|
||||
left: Variable { name: "result" },
|
||||
right: Literal { value: Integer(10) },
|
||||
},
|
||||
right: Variable { name: "temp_result_addend" },
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Section 9: Implementation Complete (2025-12-09)
|
||||
|
||||
### 9.1 Implementation Summary
|
||||
|
||||
**Status**: ✅ Phase 192-impl Complete
|
||||
|
||||
**Deliverables**:
|
||||
1. ✅ `ComplexAddendNormalizer` module implemented (`src/mir/join_ir/lowering/complex_addend_normalizer.rs`)
|
||||
2. ✅ 5 unit tests all passing (method call, simple variable, wrong LHS, no multiplication, subtraction)
|
||||
3. ✅ Pattern2 integration complete (preprocessing before carrier update analysis)
|
||||
4. ✅ Existing tests verified (phase190/191 tests still pass)
|
||||
5. ✅ Documentation updated
|
||||
|
||||
### 9.2 Actual Implementation
|
||||
|
||||
**File**: `src/mir/join_ir/lowering/complex_addend_normalizer.rs`
|
||||
|
||||
**API**:
|
||||
```rust
|
||||
pub enum NormalizationResult {
|
||||
Unchanged,
|
||||
Normalized { temp_def: ASTNode, new_assign: ASTNode, temp_name: String },
|
||||
}
|
||||
|
||||
impl ComplexAddendNormalizer {
|
||||
pub fn normalize_assign(assign: &ASTNode) -> NormalizationResult;
|
||||
}
|
||||
```
|
||||
|
||||
**Integration Point** (Pattern2 - line 243-279):
|
||||
```rust
|
||||
// Phase 192: Normalize complex addend patterns in loop body
|
||||
let mut normalized_body = Vec::new();
|
||||
let mut has_normalization = false;
|
||||
|
||||
for node in _body {
|
||||
match ComplexAddendNormalizer::normalize_assign(node) {
|
||||
NormalizationResult::Normalized { temp_def, new_assign, temp_name } => {
|
||||
normalized_body.push(temp_def); // local temp = <complex_expr>
|
||||
normalized_body.push(new_assign); // lhs = lhs * base + temp
|
||||
has_normalization = true;
|
||||
}
|
||||
NormalizationResult::Unchanged => {
|
||||
normalized_body.push(node.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let analysis_body = if has_normalization { &normalized_body } else { _body };
|
||||
// Pass analysis_body to LoopUpdateAnalyzer and lower_loop_with_break_minimal
|
||||
```
|
||||
|
||||
### 9.3 AST Transformation Example
|
||||
|
||||
**Before**:
|
||||
```nyash
|
||||
result = result * 10 + digits.indexOf(ch)
|
||||
```
|
||||
|
||||
**After** (Normalized AST):
|
||||
```nyash
|
||||
local temp_result_addend = digits.indexOf(ch)
|
||||
result = result * 10 + temp_result_addend
|
||||
```
|
||||
|
||||
**AST Structure**:
|
||||
```
|
||||
[
|
||||
Local { variables: ["temp_result_addend"], initial_values: [MethodCall(...)] },
|
||||
Assignment { target: "result", value: BinOp(Add, BinOp(Mul, "result", 10), "temp_result_addend") }
|
||||
]
|
||||
```
|
||||
|
||||
### 9.4 Test Results
|
||||
|
||||
**Unit Tests** (5/5 passing):
|
||||
- ✅ `test_normalize_complex_addend_method_call` - Core normalization pattern
|
||||
- ✅ `test_normalize_simple_variable_unchanged` - No-op for simple patterns
|
||||
- ✅ `test_normalize_wrong_lhs_unchanged` - Reject invalid patterns
|
||||
- ✅ `test_normalize_no_multiplication_unchanged` - Reject non-accumulation patterns
|
||||
- ✅ `test_normalize_subtraction_complex_addend` - Subtraction variant
|
||||
|
||||
**Integration Tests** (regression verified):
|
||||
- ✅ `phase190_atoi_impl.hako` → 12 (no regression)
|
||||
- ✅ `phase190_parse_number_impl.hako` → 123 (no regression)
|
||||
- ✅ `phase191_body_local_atoi.hako` → 123 (no regression)
|
||||
- ✅ `phase192_normalization_demo.hako` → 123 (new demo test)
|
||||
|
||||
### 9.5 Current Limitations (Phase 193+ Work)
|
||||
|
||||
**Limitation**: Full E2E flow with MethodCall in temp variables requires extending `LoopBodyLocalInitLowerer` (Phase 186).
|
||||
|
||||
**Current Behavior**:
|
||||
```nyash
|
||||
local temp = digits.indexOf(ch) // ❌ Phase 186 error: "Unsupported init expression: method call"
|
||||
```
|
||||
|
||||
**Phase 186 Scope**: Only supports int/arithmetic operations (`+`, `-`, `*`, `/`, constants, variables)
|
||||
|
||||
**Future Work** (Phase 193+):
|
||||
- Extend `LoopBodyLocalInitLowerer::lower_init_expr()` to handle:
|
||||
- `ASTNode::MethodCall` (e.g., `digits.indexOf(ch)`)
|
||||
- `ASTNode::Call` (e.g., `parseInt(s)`)
|
||||
- Add emission logic for method call results in JoinIR
|
||||
- Add UpdateEnv resolution for method call temps
|
||||
|
||||
**Workaround**: For now, complex addend normalization works at the AST level, but lowering requires manual temp extraction outside the loop.
|
||||
|
||||
### 9.6 Design Principles Validated
|
||||
|
||||
✅ **箱化 (Box-First)**:
|
||||
- ComplexAddendNormalizer is a pure AST transformer (single responsibility)
|
||||
- No emission logic mixed in (delegation to existing Phase 191/190 infrastructure)
|
||||
|
||||
✅ **Fail-Fast**:
|
||||
- Unsupported patterns return `NormalizationResult::Unchanged`
|
||||
- Phase 186 limitation explicitly documented
|
||||
|
||||
✅ **Reusability**:
|
||||
- Normalizer works with any BinaryOp pattern (Add/Subtract)
|
||||
- Integration point is clean (10 lines in Pattern2)
|
||||
|
||||
✅ **Minimal Changes**:
|
||||
- Zero changes to emission layers (CarrierUpdateEmitter, LoopBodyLocalInitLowerer)
|
||||
- Only preprocessing added to Pattern2 (before carrier analysis)
|
||||
|
||||
### 9.7 Trace Output
|
||||
|
||||
When running a test with complex addend pattern:
|
||||
```
|
||||
[pattern2/phase192] Normalized complex addend: temp='temp_result_addend' inserted before update
|
||||
[cf_loop/pattern2] Phase 176-3: Analyzed 1 carrier updates
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Revision History
|
||||
|
||||
- **2025-12-09**: Initial design document (Section 1-8)
|
||||
- **2025-12-09**: Implementation complete (Section 9)
|
||||
@ -1,108 +0,0 @@
|
||||
# Phase 192: Loop Pattern Structure-Based Detection
|
||||
|
||||
**Status**: Completed – 2025-12-06
|
||||
**Scope**: JoinIR loop patterns 1–4 (Simple While / Break / If-Else PHI / Continue)
|
||||
|
||||
---
|
||||
|
||||
## 1. Goal
|
||||
|
||||
Replace ad-hoc, name-based loop pattern detection with a single, structure-based pipeline so that:
|
||||
|
||||
- Pattern routing depends only on loop structure (if/break/continue, assignments), not function names or variable names.
|
||||
- New patterns can be added by extending a classifier table instead of scattering `if func_name == "main"` checks.
|
||||
|
||||
---
|
||||
|
||||
## 2. Architecture
|
||||
|
||||
The router now follows a three-step pipeline:
|
||||
|
||||
```text
|
||||
AST (condition, body)
|
||||
↓ extract_features_from_ast
|
||||
LoopFeatures { has_break, has_continue, has_if_else_phi, carrier_count, ... }
|
||||
↓ classify(&LoopFeatures)
|
||||
LoopPatternKind::{Pattern1SimpleWhile, Pattern2Break, Pattern3IfPhi, Pattern4Continue}
|
||||
↓ LOOP_PATTERNS table
|
||||
LoopPatternEntry::lower(builder, &LoopPatternContext)
|
||||
```
|
||||
|
||||
Key types:
|
||||
|
||||
- `LoopFeatures` – structural features of the loop body
|
||||
- `LoopPatternKind` – classifier output enum
|
||||
- `LoopPatternContext` – carries AST + features + pattern_kind to the lowerers
|
||||
|
||||
---
|
||||
|
||||
## 3. Implementation Notes
|
||||
|
||||
Files:
|
||||
|
||||
- `src/mir/loop_pattern_detection.rs`
|
||||
- Extended `LoopFeatures` with:
|
||||
- `has_break`, `has_continue`
|
||||
- `has_if`, `has_if_else_phi`
|
||||
- `carrier_count`, `break_count`, `continue_count`
|
||||
- `pub fn classify(features: &LoopFeatures) -> LoopPatternKind`
|
||||
- `Pattern1SimpleWhile`
|
||||
- `Pattern2Break`
|
||||
- `Pattern3IfPhi`
|
||||
- `Pattern4Continue`
|
||||
|
||||
- `src/mir/builder/control_flow/joinir/patterns/router.rs`
|
||||
- `LoopPatternContext::new(condition, body, func_name, debug)`:
|
||||
- Scans AST for `Continue` / `Break` (`detect_continue_in_ast`, `detect_break_in_ast`)
|
||||
- Calls `extract_features_from_ast` to build `LoopFeatures`
|
||||
- Calls `classify(&features)` to compute `pattern_kind`
|
||||
- `LOOP_PATTERNS` table:
|
||||
- Entries now rely on `ctx.pattern_kind`; `func_name` is used only for debug logging.
|
||||
|
||||
Detection rules (conceptual):
|
||||
|
||||
- Pattern 4 (Continue): `has_continue && !has_break`
|
||||
- Pattern 3 (If-Else PHI): `has_if_else_phi && !has_break && !has_continue`
|
||||
- Pattern 1 (Simple While): `!has_break && !has_continue && !has_if_else_phi`
|
||||
- Pattern 2 (Break): `has_break && !has_continue`
|
||||
|
||||
Each pattern exposes:
|
||||
|
||||
```rust
|
||||
pub fn can_lower(builder: &MirBuilder, ctx: &LoopPatternContext) -> bool;
|
||||
pub fn lower(builder: &mut MirBuilder, ctx: &LoopPatternContext) -> Result<Option<ValueId>, String>;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Behavioural Results
|
||||
|
||||
With structure-based detection in place, all four representative tests route and lower via JoinIR-only paths:
|
||||
|
||||
- Pattern 1 – Simple While
|
||||
- `apps/tests/loop_min_while.hako`
|
||||
- Output: `0, 1, 2`
|
||||
|
||||
- Pattern 2 – Loop with Break
|
||||
- `apps/tests/joinir_min_loop.hako`
|
||||
|
||||
- Pattern 3 – Loop with If-Else PHI
|
||||
- `apps/tests/loop_if_phi.hako`
|
||||
- Output: `sum = 9`
|
||||
|
||||
- Pattern 4 – Loop with Continue
|
||||
- `apps/tests/loop_continue_pattern4.hako`
|
||||
- Output: `25`
|
||||
|
||||
No pattern depends on function names (e.g. `"main"`) or specific variable names (e.g. `"sum"`) any more.
|
||||
|
||||
---
|
||||
|
||||
## 5. Future Work
|
||||
|
||||
- Extend `LoopFeatures` / `LoopPatternKind` for:
|
||||
- Nested loops
|
||||
- Multiple carrier variables
|
||||
- More complex continue/break combinations
|
||||
- Align LoopForm/LoopScopeShape-based detection with this AST-based pipeline so both views are consistent.
|
||||
|
||||
@ -1,667 +0,0 @@
|
||||
# Phase 193: MethodCall in Init Lowering Support
|
||||
|
||||
**Status**: Ready for Implementation
|
||||
**Date**: 2025-12-09
|
||||
**Prerequisite**: Phase 192-impl complete (ComplexAddendNormalizer + body-local init integration)
|
||||
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
`LoopBodyLocalInitLowerer` が **単純な method call を含む init 式**を JoinIR に正しく降下できるようにする。
|
||||
|
||||
Pattern は増やさず、既存の body-local / UpdateEnv / NumberAccumulation ラインの上に乗せる。
|
||||
|
||||
---
|
||||
|
||||
## Task 193-1: 対象 init 式のインベントリ(doc only)
|
||||
|
||||
### 目標
|
||||
現在サポート外の MethodCall を含む init 式を整理し、Phase 193 で対応する範囲を明確化する。
|
||||
|
||||
### 対象パターン
|
||||
|
||||
#### Pattern 1: 単一 MethodCall(最優先)
|
||||
```nyash
|
||||
local digit = digits.indexOf(ch)
|
||||
```
|
||||
- **説明**: body-local 変数の初期化式が単一の MethodCall
|
||||
- **要件**: `digits` は ConditionEnv で解決可能(ループパラメータまたは外部変数)
|
||||
- **JoinIR**: StringBox.indexOf → CallBoxMethod 命令を emit
|
||||
|
||||
#### Pattern 2: MethodCall を含む二項演算
|
||||
```nyash
|
||||
local index = digits.indexOf(ch) + offset
|
||||
local result = base - array.get(i)
|
||||
```
|
||||
- **説明**: MethodCall を含む算術演算
|
||||
- **要件**: MethodCall が整数を返す、演算子は +, -, *, /
|
||||
- **JoinIR**: MethodCall emit → 結果を二項演算に使用
|
||||
|
||||
### Phase 193 の範囲
|
||||
|
||||
**✅ 対応する**:
|
||||
- Pattern 1: 単一 MethodCall(最優先・必須)
|
||||
- Pattern 2: MethodCall を含む二項演算(時間があれば)
|
||||
|
||||
**❌ 対応しない(Phase 194+)**:
|
||||
- ネストした MethodCall: `s.substring(0, s.indexOf(ch))`
|
||||
- 複数 MethodCall: `s.indexOf("a") + s.indexOf("b")`
|
||||
- 非算術演算: `s.concat(t).length()`
|
||||
- 配列アクセス: `array[i].method()`
|
||||
|
||||
### 成果物
|
||||
- 対象パターンの明確化
|
||||
- Phase 193 完了後の「できること/できないこと」リスト
|
||||
|
||||
---
|
||||
|
||||
## Task 193-2: LoopBodyLocalInitLowerer の拡張設計
|
||||
|
||||
### 現在のサポート範囲(Phase 191)
|
||||
|
||||
```rust
|
||||
// src/mir/join_ir/lowering/loop_body_local_init_lowerer.rs
|
||||
match rhs {
|
||||
ASTNode::IntLiteral(_) => { /* Const 命令 */ }
|
||||
ASTNode::Identifier(_) => { /* Copy 命令 */ }
|
||||
ASTNode::BinaryOp { .. } => { /* BinOp 命令 */ }
|
||||
_ => return Err("Unsupported init expression")
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 193 拡張方針
|
||||
|
||||
#### 1. MethodCall 対応の追加
|
||||
|
||||
```rust
|
||||
match rhs {
|
||||
// 既存: IntLiteral, Identifier, BinaryOp
|
||||
|
||||
// NEW: Single MethodCall
|
||||
ASTNode::MethodCall { receiver, method, args } => {
|
||||
emit_method_call_init(receiver, method, args)?
|
||||
}
|
||||
|
||||
// NEW: BinaryOp with MethodCall (optional)
|
||||
ASTNode::BinaryOp { lhs, op, rhs } if contains_method_call(lhs, rhs) => {
|
||||
emit_binary_with_method_call(lhs, op, rhs)?
|
||||
}
|
||||
|
||||
_ => return Err("Unsupported init expression")
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. `emit_method_call_init` の実装
|
||||
|
||||
```rust
|
||||
fn emit_method_call_init(
|
||||
receiver: &ASTNode,
|
||||
method: &str,
|
||||
args: &[ASTNode],
|
||||
join_builder: &mut JoinIRBuilder,
|
||||
env: &UpdateEnv,
|
||||
alloc: &mut dyn FnMut() -> ValueId,
|
||||
) -> Result<ValueId, String> {
|
||||
// 1. receiver を解決(ConditionEnv から)
|
||||
let receiver_id = match receiver {
|
||||
ASTNode::Identifier(name) => {
|
||||
env.resolve(name)
|
||||
.ok_or_else(|| format!("Undefined variable in init: {}", name))?
|
||||
}
|
||||
_ => return Err("Complex receiver not supported in init".to_string())
|
||||
};
|
||||
|
||||
// 2. args を解決(再帰的に lower)
|
||||
let arg_ids: Vec<ValueId> = args.iter()
|
||||
.map(|arg| lower_init_arg(arg, env, alloc))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// 3. CallBoxMethod 命令を emit
|
||||
let result_id = alloc();
|
||||
join_builder.emit(Instruction::CallBoxMethod {
|
||||
dst: result_id,
|
||||
receiver: receiver_id,
|
||||
method: method.to_string(),
|
||||
args: arg_ids,
|
||||
});
|
||||
|
||||
Ok(result_id)
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. メソッドホワイトリスト(Fail-Fast)
|
||||
|
||||
**Phase 193 で許可するメソッド**:
|
||||
- `StringBox.indexOf(char)` → 整数
|
||||
- `ArrayBox.get(index)` → 要素(型は Context 依存)
|
||||
|
||||
**未サポートメソッドは明示的エラー**:
|
||||
```rust
|
||||
const SUPPORTED_INIT_METHODS: &[&str] = &[
|
||||
"indexOf",
|
||||
"get",
|
||||
];
|
||||
|
||||
if !SUPPORTED_INIT_METHODS.contains(&method) {
|
||||
return Err(format!(
|
||||
"Method '{}' not supported in body-local init (Phase 193 limitation)",
|
||||
method
|
||||
));
|
||||
}
|
||||
```
|
||||
|
||||
### 設計原則
|
||||
|
||||
1. **Fail-Fast**: 未サポートパターンは即座にエラー
|
||||
2. **ConditionEnv 優先**: receiver は ConditionEnv で解決(body-local は参照不可)
|
||||
3. **単純性**: ネストや複数呼び出しは Phase 194+ に延期
|
||||
4. **既存インフラ再利用**: CallBoxMethod 命令は既に JoinIR でサポート済み
|
||||
|
||||
---
|
||||
|
||||
## Task 193-3: 実装 – Init 式 MethodCall Lowering
|
||||
|
||||
### 対象ファイル
|
||||
- `src/mir/join_ir/lowering/loop_body_local_init_lowerer.rs`
|
||||
|
||||
### 実装手順
|
||||
|
||||
#### Step 1: `emit_method_call_init` 関数の追加
|
||||
|
||||
```rust
|
||||
impl LoopBodyLocalInitLowerer {
|
||||
fn emit_method_call_init(
|
||||
receiver: &ASTNode,
|
||||
method: &str,
|
||||
args: &[ASTNode],
|
||||
join_builder: &mut JoinIRBuilder,
|
||||
env: &UpdateEnv,
|
||||
alloc: &mut dyn FnMut() -> ValueId,
|
||||
) -> Result<ValueId, String> {
|
||||
// 実装は Task 193-2 の擬似コードに従う
|
||||
}
|
||||
|
||||
fn lower_init_arg(
|
||||
arg: &ASTNode,
|
||||
env: &UpdateEnv,
|
||||
alloc: &mut dyn FnMut() -> ValueId,
|
||||
) -> Result<ValueId, String> {
|
||||
match arg {
|
||||
ASTNode::IntLiteral(n) => {
|
||||
let id = alloc();
|
||||
// Const 命令 emit
|
||||
Ok(id)
|
||||
}
|
||||
ASTNode::Identifier(name) => {
|
||||
env.resolve(name)
|
||||
.ok_or_else(|| format!("Undefined arg: {}", name))
|
||||
}
|
||||
_ => Err("Complex args not supported".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 2: `lower_init` の MethodCall 分岐追加
|
||||
|
||||
```rust
|
||||
pub fn lower_init(
|
||||
body_ast: &[ASTNode],
|
||||
join_builder: &mut JoinIRBuilder,
|
||||
env: &UpdateEnv,
|
||||
alloc: &mut dyn FnMut() -> ValueId,
|
||||
) -> Result<LoopBodyLocalEnv, String> {
|
||||
let mut body_local_env = LoopBodyLocalEnv::new();
|
||||
|
||||
for node in body_ast {
|
||||
match node {
|
||||
ASTNode::LocalVar { name, init } => {
|
||||
let value_id = match init {
|
||||
// 既存パターン
|
||||
ASTNode::IntLiteral(_) => { /* ... */ }
|
||||
ASTNode::Identifier(_) => { /* ... */ }
|
||||
ASTNode::BinaryOp { .. } => { /* ... */ }
|
||||
|
||||
// NEW: MethodCall
|
||||
ASTNode::MethodCall { receiver, method, args } => {
|
||||
Self::emit_method_call_init(
|
||||
receiver, method, args,
|
||||
join_builder, env, alloc
|
||||
)?
|
||||
}
|
||||
|
||||
_ => return Err(format!(
|
||||
"Unsupported init expression for '{}' in body-local",
|
||||
name
|
||||
))
|
||||
};
|
||||
|
||||
body_local_env.register(name, value_id);
|
||||
}
|
||||
_ => {} // Skip non-local nodes
|
||||
}
|
||||
}
|
||||
|
||||
Ok(body_local_env)
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 3: ユニットテスト追加
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_method_call_init_index_of() {
|
||||
// UpdateEnv with "digits" → ValueId(5)
|
||||
let mut condition_env = ConditionEnv::new();
|
||||
condition_env.register("digits", ValueId(5));
|
||||
let update_env = UpdateEnv::new(condition_env, LoopBodyLocalEnv::new());
|
||||
|
||||
// AST: local digit = digits.indexOf("x")
|
||||
let init_ast = ASTNode::MethodCall {
|
||||
receiver: Box::new(ASTNode::Identifier("digits".to_string())),
|
||||
method: "indexOf".to_string(),
|
||||
args: vec![ASTNode::StringLiteral("x".to_string())],
|
||||
};
|
||||
|
||||
let mut builder = JoinIRBuilder::new();
|
||||
let mut value_id_counter = 10;
|
||||
let mut alloc = || { value_id_counter += 1; ValueId(value_id_counter) };
|
||||
|
||||
let result_id = LoopBodyLocalInitLowerer::emit_method_call_init(
|
||||
&init_ast.receiver(),
|
||||
&init_ast.method(),
|
||||
&init_ast.args(),
|
||||
&mut builder,
|
||||
&update_env,
|
||||
&mut alloc,
|
||||
).unwrap();
|
||||
|
||||
// Verify CallBoxMethod instruction was emitted
|
||||
assert_eq!(result_id, ValueId(11));
|
||||
// ... verify builder contains CallBoxMethod instruction
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unsupported_method_fails() {
|
||||
// AST: local x = obj.unsupportedMethod()
|
||||
let init_ast = ASTNode::MethodCall {
|
||||
receiver: Box::new(ASTNode::Identifier("obj".to_string())),
|
||||
method: "unsupportedMethod".to_string(),
|
||||
args: vec![],
|
||||
};
|
||||
|
||||
let result = LoopBodyLocalInitLowerer::emit_method_call_init(
|
||||
// ...
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("not supported in body-local init"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 193-4: E2E 検証
|
||||
|
||||
### テストケース 1: 単一 MethodCall init
|
||||
|
||||
#### ファイル: `apps/tests/phase193_init_method_call.hako`
|
||||
|
||||
```nyash
|
||||
static box Main {
|
||||
main() {
|
||||
local digits = "0123456789"
|
||||
local result = 0
|
||||
local i = 0
|
||||
|
||||
loop(i < 3) {
|
||||
local ch = "0" // 簡略化: 実際は配列から取得
|
||||
local digit = digits.indexOf(ch) // ← Phase 193 target
|
||||
result = result * 10 + digit
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
print(result) // Expected: 0 (indexOf("0") = 0)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 実行手順
|
||||
|
||||
```bash
|
||||
# 1. ビルド
|
||||
cargo build --release
|
||||
|
||||
# 2. E2E 実行
|
||||
./target/release/hakorune apps/tests/phase193_init_method_call.hako
|
||||
# Expected output: 0
|
||||
|
||||
# 3. デバッグ(必要に応じて)
|
||||
NYASH_TRACE_VARMAP=1 ./target/release/hakorune apps/tests/phase193_init_method_call.hako 2>&1 | grep digit
|
||||
```
|
||||
|
||||
### テストケース 2: MethodCall を含む二項演算(オプション)
|
||||
|
||||
#### ファイル: `apps/tests/phase193_init_method_binop.hako`
|
||||
|
||||
```nyash
|
||||
static box Main {
|
||||
main() {
|
||||
local digits = "0123456789"
|
||||
local result = 0
|
||||
local i = 0
|
||||
|
||||
loop(i < 2) {
|
||||
local ch = "1"
|
||||
local digit = digits.indexOf(ch) + 1 // ← indexOf + offset
|
||||
result = result * 10 + digit
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
print(result) // Expected: 22 (indexOf("1") = 1, +1 = 2)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 退行チェック
|
||||
|
||||
既存のテストが引き続き動作すること:
|
||||
```bash
|
||||
# Phase 191 body-local tests
|
||||
./target/release/hakorune apps/tests/phase191_body_local_atoi.hako
|
||||
# Expected: 123
|
||||
|
||||
# Phase 192 complex addend tests
|
||||
./target/release/hakorune apps/tests/phase192_normalization_demo.hako
|
||||
# Expected: 123
|
||||
|
||||
# Phase 190 NumberAccumulation tests
|
||||
./target/release/hakorune apps/tests/phase190_atoi_impl.hako
|
||||
# Expected: 12
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 193-5: ドキュメント更新
|
||||
|
||||
### 更新対象
|
||||
|
||||
#### 1. `phase193-init-methodcall-design.md`(本ファイル)
|
||||
|
||||
実装完了後、以下のセクションを追記:
|
||||
|
||||
```markdown
|
||||
## Implementation Status
|
||||
|
||||
**完了日**: 2025-12-XX
|
||||
|
||||
### 実装サマリ
|
||||
|
||||
- **対応パターン**:
|
||||
- [x] 単一 MethodCall (`local digit = digits.indexOf(ch)`)
|
||||
- [ ] MethodCall を含む二項演算(オプション、時間により延期可)
|
||||
|
||||
- **サポートメソッド**:
|
||||
- `StringBox.indexOf(char)` → 整数
|
||||
- `ArrayBox.get(index)` → 要素
|
||||
|
||||
### JoinIR Emission 例
|
||||
|
||||
入力 AST:
|
||||
```nyash
|
||||
local digit = digits.indexOf(ch)
|
||||
```
|
||||
|
||||
生成される JoinIR:
|
||||
```
|
||||
%10 = Copy { src: %5 } // digits (from ConditionEnv)
|
||||
%11 = Copy { src: %6 } // ch (from ConditionEnv)
|
||||
%12 = CallBoxMethod { receiver: %10, method: "indexOf", args: [%11] }
|
||||
// LoopBodyLocalEnv: digit → %12
|
||||
```
|
||||
|
||||
### 技術的発見
|
||||
|
||||
- **ConditionEnv 優先**: receiver は必ず ConditionEnv で解決(body-local は相互参照不可)
|
||||
- **Fail-Fast 効果**: 未サポートメソッドは明示的エラーで早期検出
|
||||
- **既存インフラ再利用**: CallBoxMethod は JoinIR で既存、MIR merge も問題なし
|
||||
|
||||
### 制限事項
|
||||
|
||||
Phase 193 では以下をサポートしない(Fail-Fast でエラー):
|
||||
- ネストした MethodCall: `s.substring(0, s.indexOf(ch))`
|
||||
- 複数 MethodCall: `a.get(i) + b.get(j)`
|
||||
- 配列アクセス: `array[i].method()`
|
||||
|
||||
これらは Phase 194+ で段階的に対応予定。
|
||||
```
|
||||
|
||||
#### 2. `joinir-architecture-overview.md`
|
||||
|
||||
Section 2.2 "Update Lowering Infrastructure" に追記:
|
||||
|
||||
```markdown
|
||||
- **Phase 193完了**: LoopBodyLocalInitLowerer が MethodCall を含む init 式に対応。
|
||||
- 対応メソッド: `StringBox.indexOf`, `ArrayBox.get` (ホワイトリスト方式)
|
||||
- receiver は ConditionEnv で解決(ループパラメータ・外部変数のみ)
|
||||
- Fail-Fast: 未サポートメソッドは明示的エラー
|
||||
- 制約: ネスト・複数呼び出しは Phase 194+ で対応
|
||||
```
|
||||
|
||||
Section 7.2 "残タスク" を更新:
|
||||
|
||||
```markdown
|
||||
- [x] **Phase 193**: MethodCall を含む body-local init 式の対応
|
||||
- `local digit = digits.indexOf(ch)` パターンが動作
|
||||
- 既存インフラ(CallBoxMethod)再利用で実装完了
|
||||
- [ ] **Phase 194**: 複雑な MethodCall パターン(ネスト・複数呼び出し)
|
||||
- [ ] **Phase 195**: Pattern 3 (if-in-loop) への body-local 統合
|
||||
```
|
||||
|
||||
#### 3. `CURRENT_TASK.md`
|
||||
|
||||
Phase 193 完了マークを追加:
|
||||
|
||||
```markdown
|
||||
## Phase 193-impl: MethodCall in Init Lowering (完了: 2025-12-XX)
|
||||
|
||||
**目標**: body-local 変数の初期化式で MethodCall をサポート
|
||||
|
||||
**実装内容**:
|
||||
- `loop_body_local_init_lowerer.rs`: `emit_method_call_init` 関数追加(~80行)
|
||||
- ホワイトリスト方式: `indexOf`, `get` のみ許可
|
||||
- ConditionEnv 優先解決: receiver は必ずループパラメータ/外部変数
|
||||
|
||||
**テスト結果**:
|
||||
- phase193_init_method_call.hako → 0 ✅
|
||||
- 既存テスト退行なし ✅
|
||||
|
||||
**技術的成果**:
|
||||
- CallBoxMethod 命令の再利用(新規 Pattern 不要)
|
||||
- Fail-Fast でサポート範囲を明確化
|
||||
- ConditionEnv vs LoopBodyLocalEnv の責務分離確認
|
||||
|
||||
**制限事項**:
|
||||
- ネスト・複数呼び出しは Phase 194+ で対応
|
||||
- receiver は単一変数のみ(複雑な式は未サポート)
|
||||
|
||||
**次のステップ**: Phase 194(複雑な MethodCall パターン)または Phase 195(Pattern 3 統合)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 成功基準
|
||||
|
||||
- [x] 代表テスト(`phase193_init_method_call.hako`)が JoinIR only で期待値を返す
|
||||
- [x] 既存テスト(phase191, phase192, phase190)が退行しない
|
||||
- [x] LoopBodyLocalInitLowerer が MethodCall 式を CallBoxMethod 命令に変換できる
|
||||
- [x] 未サポートメソッドは明示的エラーで Fail-Fast する
|
||||
- [x] ドキュメントが更新されている
|
||||
|
||||
---
|
||||
|
||||
## 関連ファイル
|
||||
|
||||
### インフラ(Phase 191 で実装済み)
|
||||
- `src/mir/join_ir/lowering/loop_body_local_init_lowerer.rs`(Phase 193 で拡張)
|
||||
- `src/mir/join_ir/lowering/loop_body_local_env.rs`
|
||||
- `src/mir/join_ir/lowering/update_env.rs`
|
||||
|
||||
### 統合対象
|
||||
- `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs`(変更不要、Phase 191 インフラを再利用)
|
||||
|
||||
### テストファイル
|
||||
- `apps/tests/phase193_init_method_call.hako`(新規作成)
|
||||
- `apps/tests/phase193_init_method_binop.hako`(新規作成・オプション)
|
||||
- `apps/tests/phase191_body_local_atoi.hako`(退行確認)
|
||||
- `apps/tests/phase192_normalization_demo.hako`(退行確認)
|
||||
|
||||
---
|
||||
|
||||
## 次の Phase への接続
|
||||
|
||||
### Phase 194: 複雑な MethodCall パターン(候補)
|
||||
- ネストした MethodCall: `s.substring(0, s.indexOf(ch))`
|
||||
- 複数 MethodCall: `a.get(i) + b.get(j)`
|
||||
- 配列アクセス: `array[i].method()`
|
||||
|
||||
### Phase 195: Pattern 3 への body-local 統合(候補)
|
||||
- if-in-loop での body-local 変数
|
||||
- 条件分岐を跨ぐ PHI 接続
|
||||
|
||||
Phase 193 完了後、ユーザーと相談して次の優先順位を決定する。
|
||||
|
||||
---
|
||||
|
||||
## Implementation Status
|
||||
|
||||
**完了日**: 2025-12-09
|
||||
|
||||
### 実装サマリ
|
||||
|
||||
- **対応パターン**:
|
||||
- [x] 単一 MethodCall (`local digit_str = i.toString()`)
|
||||
- [x] String literal support in init expressions (`local ch = "0"`)
|
||||
- [ ] MethodCall を含む二項演算(時間制約により Phase 194+ に延期)
|
||||
|
||||
- **サポートメソッド** (Whitelist):
|
||||
- `StringBox.indexOf(char)` → 整数
|
||||
- `ArrayBox.get(index)` → 要素
|
||||
- `IntegerBox.toString()` → 文字列(Phase 193 テスト用追加)
|
||||
|
||||
### JoinIR Emission 例
|
||||
|
||||
入力 AST:
|
||||
```nyash
|
||||
local digit_str = i.toString()
|
||||
```
|
||||
|
||||
生成される JoinIR:
|
||||
```
|
||||
// 1. Resolve receiver from ConditionEnv
|
||||
%receiver = ValueId(0) // i (loop parameter)
|
||||
|
||||
// 2. Emit BoxCall instruction
|
||||
%result = BoxCall {
|
||||
dst: Some(ValueId(13)),
|
||||
box_name: "IntegerBox",
|
||||
method: "toString",
|
||||
args: [%receiver]
|
||||
}
|
||||
|
||||
// 3. Register in LoopBodyLocalEnv
|
||||
// digit_str → ValueId(13)
|
||||
```
|
||||
|
||||
### 技術的発見
|
||||
|
||||
1. **ConditionEnv 制約の明確化**:
|
||||
- receiver は必ず ConditionEnv で解決(ループパラメータのみ)
|
||||
- 外部スコープ変数(`local digits = ...` 等)は ConditionEnv に含まれない
|
||||
- → Pattern 2/3 の制約により、`digits.indexOf(ch)` パターンは Phase 194+ に延期
|
||||
|
||||
2. **String literal サポート追加**:
|
||||
- Phase 186 は Integer のみサポート
|
||||
- Phase 193 で String literal も追加(`local ch = "0"` 対応)
|
||||
|
||||
3. **Fail-Fast 効果**:
|
||||
- 未サポートメソッドは明示的エラーで早期検出
|
||||
- ホワイトリスト方式で段階的拡張が容易
|
||||
|
||||
4. **既存インフラ再利用**:
|
||||
- BoxCall は JoinIR で既存、MIR merge も問題なし
|
||||
- 新規 Pattern 不要、既存 Pattern 2/3 で動作
|
||||
|
||||
### 制限事項(Phase 193)
|
||||
|
||||
以下をサポートしない(Fail-Fast でエラー):
|
||||
- ❌ 外部スコープ変数を receiver とする MethodCall: `digits.indexOf(ch)`
|
||||
- 理由: ConditionEnv に外部変数が含まれない(Pattern 2/3 の制約)
|
||||
- 対応: Phase 194+ で ConditionEnv 拡張または別アプローチ検討
|
||||
- ❌ ネストした MethodCall: `s.substring(0, s.indexOf(ch))`
|
||||
- ❌ 複数 MethodCall: `a.get(i) + b.get(j)`
|
||||
- ❌ 配列アクセス: `array[i].method()`
|
||||
|
||||
これらは Phase 194+ で段階的に対応予定。
|
||||
|
||||
### E2E テスト結果
|
||||
|
||||
**ファイル**: `apps/tests/phase193_init_method_call.hako`
|
||||
|
||||
```nyash
|
||||
local digit_str = i.toString() // ← Phase 193 MethodCall in init
|
||||
```
|
||||
|
||||
**実行結果**:
|
||||
- ✅ コンパイル成功(エラーなし)
|
||||
- ✅ JoinIR BoxCall 命令が正しく emit される
|
||||
- ✅ 退行なし(phase191, 192, 190 全て PASS)
|
||||
|
||||
**注意**: `toString()` の出力内容は BoxCall 実装依存(Phase 193 の責務外)
|
||||
|
||||
### 変更ファイル
|
||||
|
||||
**実装** (~220 lines):
|
||||
- `src/mir/join_ir/lowering/loop_body_local_init.rs`
|
||||
- `emit_method_call_init()` 関数追加(~80 lines)
|
||||
- `lower_init_arg()` 関数追加(~60 lines)
|
||||
- `lower_init_expr()` に MethodCall 分岐追加
|
||||
- String literal サポート追加
|
||||
|
||||
**テスト** (1 file):
|
||||
- `apps/tests/phase193_init_method_call.hako` (新規作成)
|
||||
|
||||
**ドキュメント** (1 file):
|
||||
- `docs/development/current/main/phase193-init-methodcall-design.md` (本ファイル)
|
||||
|
||||
### ConditionEnv 制約の設計判断(重要)
|
||||
|
||||
Phase 193 では **ConditionEnv を「ループパラメータ専用 view」として維持**する設計判断を行った。
|
||||
|
||||
**理由**:
|
||||
- Phase 170-200 で確立した **ConditionEnv / LoopBodyLocalEnv の 2-tier 境界設計**を保持
|
||||
- 外部ローカル変数(`digits` 等)を含めると、Pattern 判定・BoolExprLowerer との境界が揺れる
|
||||
- **安全性と保守性を優先**(箱理論の実践)
|
||||
|
||||
**Phase 193 対応範囲**:
|
||||
- ✅ **ループパラメータをレシーバーとする MethodCall**: `i.toString()`
|
||||
- ❌ **外部ローカルをレシーバーとする MethodCall**: `digits.indexOf(ch)` → **Fail-Fast**
|
||||
|
||||
**将来の対応案(Phase 200+)**:
|
||||
- **Option A'**: ConditionEnv 拡張を独立した箱として設計(既存境界を壊さない)
|
||||
- **Option B'**: .hako 側でのリライト(前処理で分解)
|
||||
- **Option C**: Pattern 3/4 の実戦投入を優先(digits パターンは保留)
|
||||
|
||||
**設計原則**:
|
||||
> 「LoopBodyLocal + Param ベースの安全な init」は JoinIR に乗せる。
|
||||
> 「テーブル+メソッド呼び出し」のような複合パターンは、次の箱(または .hako 側のリライト)案件にする。
|
||||
|
||||
→ **Phase 194+ は Option C(実戦投入優先)を推奨**
|
||||
@ -1,203 +0,0 @@
|
||||
# Phase 193-5: Multi-Carrier Testing & Integration
|
||||
|
||||
**Phase**: 193-5
|
||||
**Status**: Implementation Phase
|
||||
**Date**: 2025-12-06
|
||||
**Goal**: Validate ExitBindingBuilder with multi-carrier test cases and integrate into Pattern 3 & 4 lowerers
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Phase 193-5 completes the Phase 193 modularization cycle by:
|
||||
1. Running existing multi-carrier test case to validate design
|
||||
2. Integrating ExitBindingBuilder into Pattern 3 & 4 lowerers
|
||||
3. Removing hardcoded carrier handling from pattern implementations
|
||||
4. Documenting multi-carrier support completion
|
||||
|
||||
---
|
||||
|
||||
## Test Case: loop_continue_multi_carrier.hako
|
||||
|
||||
**File**: `apps/tests/loop_continue_multi_carrier.hako`
|
||||
|
||||
**Pattern**: Pattern 4 (Loop with Continue)
|
||||
|
||||
**Structure**:
|
||||
```
|
||||
loop(i < 10) {
|
||||
i = i + 1
|
||||
if (i % 2 == 0) {
|
||||
continue // Skip even numbers
|
||||
}
|
||||
sum = sum + i // Accumulate odd numbers
|
||||
count = count + 1 // Count odd numbers
|
||||
}
|
||||
```
|
||||
|
||||
**Carriers**:
|
||||
- `sum` - Accumulator for odd numbers
|
||||
- `count` - Counter for odd numbers
|
||||
|
||||
**Expected Output**:
|
||||
```
|
||||
25 // sum = 1 + 3 + 5 + 7 + 9
|
||||
5 // count = 5 (five odd numbers from 1 to 9)
|
||||
```
|
||||
|
||||
**Test Command**:
|
||||
```bash
|
||||
./target/release/hakorune apps/tests/loop_continue_multi_carrier.hako
|
||||
# Expected output:
|
||||
# 25
|
||||
# 5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration Plan
|
||||
|
||||
### Step 1: Pattern 4 Lowerer Integration
|
||||
|
||||
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
|
||||
|
||||
**Current Approach** (to be replaced):
|
||||
```rust
|
||||
// OLD: Hardcoded carrier handling
|
||||
boundary.host_outputs.push(sum_value_id);
|
||||
boundary.join_outputs.push(join_sum_exit);
|
||||
variable_map.insert("sum".to_string(), new_sum_id);
|
||||
```
|
||||
|
||||
**New Approach**:
|
||||
```rust
|
||||
// NEW: Via ExitBindingBuilder
|
||||
let mut builder = ExitBindingBuilder::new(&carrier_info, &exit_meta, variable_map)?;
|
||||
let _bindings = builder.build_loop_exit_bindings()?;
|
||||
builder.apply_to_boundary(&mut boundary)?;
|
||||
```
|
||||
|
||||
**Changes**:
|
||||
1. Import `ExitBindingBuilder` and `LoopExitBinding`
|
||||
2. After `JoinModule` creation, create `CarrierInfo` and `ExitMeta`
|
||||
3. Create `ExitBindingBuilder` and apply to boundary
|
||||
4. Remove manual `boundary.host_outputs`/`boundary.join_outputs` manipulation
|
||||
|
||||
### Step 2: Pattern 3 Lowerer Integration
|
||||
|
||||
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs`
|
||||
|
||||
**Similar approach** to Pattern 4:
|
||||
1. Collect carriers from variables assigned in if-else branches
|
||||
2. Create `ExitMeta` from JoinIR exit values
|
||||
3. Use `ExitBindingBuilder` to apply to boundary
|
||||
|
||||
---
|
||||
|
||||
## Validation Criteria
|
||||
|
||||
### Test Execution
|
||||
|
||||
```bash
|
||||
# Basic execution
|
||||
./target/release/hakorune apps/tests/loop_continue_multi_carrier.hako
|
||||
|
||||
# With detailed MIR output
|
||||
./target/release/hakorune --dump-mir apps/tests/loop_continue_multi_carrier.hako
|
||||
|
||||
# With JoinIR core only
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_continue_multi_carrier.hako
|
||||
```
|
||||
|
||||
### Expected Results
|
||||
|
||||
**Pass Criteria**:
|
||||
- ✅ Output is exactly "25\n5"
|
||||
- ✅ Both carriers (`sum` and `count`) have correct final values
|
||||
- ✅ MIR shows proper PHI merging for exit values
|
||||
- ✅ No variable map corruption (correct post-loop ValueIds)
|
||||
|
||||
**Fail Criteria**:
|
||||
- ❌ Output is incorrect (e.g., "0\n0")
|
||||
- ❌ Only one carrier is updated
|
||||
- ❌ Carrier values are stale (pre-loop values)
|
||||
- ❌ Variable map is corrupted
|
||||
|
||||
---
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
### Code Changes
|
||||
|
||||
- [ ] **Pattern 4 Integration**:
|
||||
- [ ] Import `ExitBindingBuilder` in pattern4_with_continue.rs
|
||||
- [ ] Create `ExitBindingBuilder` after `JoinModule` creation
|
||||
- [ ] Remove manual boundary manipulation
|
||||
- [ ] Test compilation succeeds
|
||||
|
||||
- [ ] **Pattern 3 Integration**:
|
||||
- [ ] Identify carriers from if-else assignments
|
||||
- [ ] Create `ExitBindingBuilder` after `JoinModule` creation
|
||||
- [ ] Apply to boundary
|
||||
- [ ] Test compilation succeeds
|
||||
|
||||
- [ ] **Cleanup**:
|
||||
- [ ] Remove any remaining hardcoded carrier names
|
||||
- [ ] Remove any `TODO` comments about carrier handling
|
||||
- [ ] Ensure no variable name assumptions remain
|
||||
|
||||
### Testing
|
||||
|
||||
- [ ] Run `loop_continue_multi_carrier.hako` test
|
||||
- [ ] Verify output matches expected (25, 5)
|
||||
- [ ] Run existing Pattern 3 & 4 tests
|
||||
- [ ] Check no regressions in other loop patterns
|
||||
|
||||
### Documentation
|
||||
|
||||
- [ ] Update CURRENT_TASK.md with Phase 193-5 completion
|
||||
- [ ] Add note to Phase 193 completion summary
|
||||
- [ ] Document multi-carrier support completion
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria for Phase 193
|
||||
|
||||
All sub-phases complete:
|
||||
|
||||
✅ **Phase 193-1**: AST Feature Extractor Box module created and delegated to by router
|
||||
✅ **Phase 193-2**: CarrierInfo with flexible builder methods (automatic/explicit/manual)
|
||||
✅ **Phase 193-3**: Pattern classification with diagnostic helpers
|
||||
✅ **Phase 193-4**: ExitBindingBuilder fully boxified exit binding generation
|
||||
⏳ **Phase 193-5**: Multi-carrier tests integrated and validated
|
||||
|
||||
### Phase 193 Completion Statement
|
||||
|
||||
Phase 193 achieves complete modularization and enhancement of JoinIR loop lowering:
|
||||
|
||||
1. **AST Feature Extraction**: Separated into pure function module for reusability
|
||||
2. **Carrier Metadata**: Flexible construction and query methods
|
||||
3. **Pattern Classification**: Diagnostic information and runtime queries
|
||||
4. **Exit Bindings**: Fully boxified, eliminates hardcoded carrier handling
|
||||
5. **Multi-Carrier Support**: Seamless support for loops with 2+ carriers
|
||||
|
||||
**Impact**: JoinIR loop lowering now has clean separation of concerns, higher reusability, and eliminates fragile hardcoded variable name assumptions.
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- **Phase 193-4 Design**: [phase193_exit_binding_builder.md](phase193_exit_binding_builder.md)
|
||||
- **Phase 193-3**: Pattern classification improvements
|
||||
- **Phase 193-2**: CarrierInfo builder enhancement
|
||||
- **Phase 193-1**: AST feature extractor modularization
|
||||
- **Phase 188**: JoinInlineBoundary initial design
|
||||
- **Phase 190**: Pattern routing architecture
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **ValueId Allocation**: Currently uses `max(variable_map) + 1` strategy. Future: delegate to builder's proper ValueId allocator.
|
||||
- **Debugging**: Environment variable `NYASH_TRACE_EXIT_BINDING=1` will be useful for debugging Phase 193-5 integration.
|
||||
- **Next Phase**: Phase 194 can now focus on advanced pattern detection without carrier handling complexity.
|
||||
@ -1,364 +0,0 @@
|
||||
# Phase 193-4: ExitBindingBuilder Design & Implementation
|
||||
|
||||
**Phase**: 193-4
|
||||
**Status**: Design Phase
|
||||
**Date**: 2025-12-06
|
||||
**Goal**: Fully boxify loop exit binding generation for Pattern 3 & 4, eliminating hardcoded variable names and ValueId assumptions
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The ExitBindingBuilder box connects JoinIR exit values (from loop lowering) back to the host function's variable_map. This eliminates:
|
||||
- Hardcoded variable names like `"sum"`, `"printed"`
|
||||
- Assumptions about single-carrier patterns
|
||||
- Complex ValueId plumbing scattered across Pattern 3/4 lowerers
|
||||
|
||||
### Architecture Diagram
|
||||
|
||||
```
|
||||
Pattern Lowerer
|
||||
↓
|
||||
├─ CarrierInfo (loop_var, carriers[], host_ids)
|
||||
├─ ExitMeta (join_exit_values[])
|
||||
└─ variable_map
|
||||
↓
|
||||
ExitBindingBuilder
|
||||
↓
|
||||
├─ LoopExitBinding[] (carrier → host mapping)
|
||||
└─ JoinInlineBoundary update (host_outputs, join_outputs)
|
||||
↓
|
||||
Host function variable_map (updated with new ValueIds)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Structures
|
||||
|
||||
### Input: CarrierInfo
|
||||
|
||||
```rust
|
||||
pub struct CarrierInfo {
|
||||
pub loop_var_name: String, // e.g., "i"
|
||||
pub loop_var_id: ValueId, // Host-side ValueId for loop var
|
||||
pub carriers: Vec<CarrierVar>, // [{ name: "sum", host_id: ValueId(10) }, ...]
|
||||
}
|
||||
|
||||
pub struct CarrierVar {
|
||||
pub name: String, // Variable name (e.g., "sum")
|
||||
pub host_id: ValueId, // Host-side ValueId (initial value)
|
||||
}
|
||||
```
|
||||
|
||||
### Input: ExitMeta
|
||||
|
||||
```rust
|
||||
pub struct ExitMeta {
|
||||
pub exit_values: Vec<(String, ValueId)>,
|
||||
// Example: [("sum", ValueId(15)), ("printed", ValueId(16))]
|
||||
// where ValueId(15/16) are in JoinIR-local space (parameters/results)
|
||||
}
|
||||
```
|
||||
|
||||
### Output: LoopExitBinding (New)
|
||||
|
||||
```rust
|
||||
pub struct LoopExitBinding {
|
||||
/// Carrier variable name (e.g., "sum", "printed")
|
||||
pub carrier_name: String,
|
||||
|
||||
/// Host-side ValueId for this carrier
|
||||
pub host_id: ValueId,
|
||||
|
||||
/// Join-side exit ValueId (from ExitMeta)
|
||||
pub join_exit_id: ValueId,
|
||||
}
|
||||
```
|
||||
|
||||
### JoinInlineBoundary Updates
|
||||
|
||||
```rust
|
||||
pub struct JoinInlineBoundary {
|
||||
// ... existing fields ...
|
||||
|
||||
/// Host-side output ValueIds (one per carrier + loop_var)
|
||||
pub host_outputs: Vec<ValueId>,
|
||||
|
||||
/// Join-side output ValueIds (one per carrier + loop_var, in JoinIR space)
|
||||
pub join_outputs: Vec<ValueId>,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Design
|
||||
|
||||
### ExitBindingBuilder
|
||||
|
||||
```rust
|
||||
pub struct ExitBindingBuilder<'a> {
|
||||
carrier_info: &'a CarrierInfo,
|
||||
exit_meta: &'a ExitMeta,
|
||||
variable_map: &'a mut HashMap<String, ValueId>,
|
||||
}
|
||||
|
||||
impl<'a> ExitBindingBuilder<'a> {
|
||||
/// Create a new builder from metadata
|
||||
pub fn new(
|
||||
carrier_info: &'a CarrierInfo,
|
||||
exit_meta: &'a ExitMeta,
|
||||
variable_map: &'a mut HashMap<String, ValueId>,
|
||||
) -> Result<Self, String>;
|
||||
|
||||
/// Generate loop exit bindings
|
||||
///
|
||||
/// Returns one LoopExitBinding per carrier, in sorted order.
|
||||
/// Updates variable_map with new post-loop ValueIds.
|
||||
pub fn build_loop_exit_bindings(&mut self) -> Result<Vec<LoopExitBinding>, String>;
|
||||
|
||||
/// Apply bindings to JoinInlineBoundary
|
||||
///
|
||||
/// Sets host_outputs and join_outputs based on loop_var + carriers.
|
||||
/// Must be called after build_loop_exit_bindings().
|
||||
pub fn apply_to_boundary(&self, boundary: &mut JoinInlineBoundary) -> Result<(), String>;
|
||||
|
||||
/// Get the updated loop_var exit binding (always first)
|
||||
pub fn loop_var_exit_binding(&self) -> LoopExitBinding;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### Single Carrier Case
|
||||
|
||||
**Input Example**:
|
||||
```
|
||||
CarrierInfo {
|
||||
loop_var_name: "i",
|
||||
loop_var_id: ValueId(5),
|
||||
carriers: [{ name: "sum", host_id: ValueId(10) }]
|
||||
}
|
||||
|
||||
ExitMeta {
|
||||
exit_values: [("sum", ValueId(15))]
|
||||
}
|
||||
|
||||
variable_map: {"i": ValueId(5), "sum": ValueId(10)}
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```
|
||||
LoopExitBinding {
|
||||
carrier_name: "sum",
|
||||
host_id: ValueId(10),
|
||||
join_exit_id: ValueId(15)
|
||||
}
|
||||
|
||||
variable_map (updated): {"i": ValueId(5), "sum": ValueId(???)} // NEW ValueId for post-loop sum
|
||||
```
|
||||
|
||||
### Multiple Carrier Case
|
||||
|
||||
**Input Example**:
|
||||
```
|
||||
CarrierInfo {
|
||||
loop_var_name: "i",
|
||||
loop_var_id: ValueId(5),
|
||||
carriers: [
|
||||
{ name: "printed", host_id: ValueId(11) },
|
||||
{ name: "sum", host_id: ValueId(10) }
|
||||
]
|
||||
}
|
||||
|
||||
ExitMeta {
|
||||
exit_values: [
|
||||
("printed", ValueId(14)),
|
||||
("sum", ValueId(15))
|
||||
]
|
||||
}
|
||||
|
||||
variable_map: {"i": ValueId(5), "sum": ValueId(10), "printed": ValueId(11)}
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```
|
||||
LoopExitBinding[
|
||||
{ carrier_name: "printed", host_id: ValueId(11), join_exit_id: ValueId(14) },
|
||||
{ carrier_name: "sum", host_id: ValueId(10), join_exit_id: ValueId(15) }
|
||||
]
|
||||
|
||||
variable_map (updated):
|
||||
{
|
||||
"i": ValueId(5),
|
||||
"sum": ValueId(???), // NEW post-loop ValueId
|
||||
"printed": ValueId(???) // NEW post-loop ValueId
|
||||
}
|
||||
```
|
||||
|
||||
### Error Cases
|
||||
|
||||
1. **Carrier name mismatch**: ExitMeta contains carrier name not in CarrierInfo
|
||||
- Error: `"Exit carrier 'foo' not found in CarrierInfo"`
|
||||
|
||||
2. **Missing carrier in ExitMeta**: CarrierInfo has carrier not in ExitMeta
|
||||
- Error: `"Carrier 'sum' missing in ExitMeta"`
|
||||
|
||||
3. **Loop variable in ExitMeta**: ExitMeta erroneously maps loop_var
|
||||
- Error: `"Loop variable 'i' should not be in exit_values"`
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### File Structure
|
||||
|
||||
**New file**: `src/mir/builder/control_flow/joinir/exit_binding.rs`
|
||||
|
||||
```rust
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::join_ir::JoinInlineBoundary;
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct LoopExitBinding { ... }
|
||||
|
||||
pub struct ExitBindingBuilder<'a> { ... }
|
||||
|
||||
impl<'a> ExitBindingBuilder<'a> {
|
||||
pub fn new(...) -> Result<Self, String> { ... }
|
||||
pub fn build_loop_exit_bindings(&mut self) -> Result<Vec<LoopExitBinding>, String> { ... }
|
||||
pub fn apply_to_boundary(&self, boundary: &mut JoinInlineBoundary) -> Result<(), String> { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Module Declaration
|
||||
|
||||
Update `src/mir/builder/control_flow/joinir/mod.rs`:
|
||||
|
||||
```rust
|
||||
pub mod exit_binding;
|
||||
```
|
||||
|
||||
### Integration Points
|
||||
|
||||
**Pattern 3 & 4 Lowerers**:
|
||||
|
||||
```rust
|
||||
// OLD: Direct boundary manipulation
|
||||
boundary.host_outputs.push(sum_value_id);
|
||||
boundary.join_outputs.push(join_sum_exit);
|
||||
variable_map.insert("sum".to_string(), new_sum_id);
|
||||
|
||||
// NEW: Via ExitBindingBuilder
|
||||
let mut builder = ExitBindingBuilder::new(&carrier_info, &exit_meta, variable_map)?;
|
||||
let _bindings = builder.build_loop_exit_bindings()?;
|
||||
builder.apply_to_boundary(&mut boundary)?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests (exit_binding.rs)
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_single_carrier_binding() { ... }
|
||||
|
||||
#[test]
|
||||
fn test_multi_carrier_binding() { ... }
|
||||
|
||||
#[test]
|
||||
fn test_carrier_name_mismatch_error() { ... }
|
||||
|
||||
#[test]
|
||||
fn test_variable_map_update() { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
**File**: `apps/tests/loop_continue_multi_carrier.hako`
|
||||
|
||||
```hako
|
||||
static box Main {
|
||||
main() {
|
||||
local sum = 0
|
||||
local printed = 0
|
||||
|
||||
loop(i = 0; i < 5; i = i + 1) {
|
||||
if (i > 2) {
|
||||
printed = printed + 1
|
||||
continue
|
||||
}
|
||||
sum = sum + i
|
||||
}
|
||||
|
||||
// Expected: sum = 0+1+2 = 3, printed = 2 (i=3,4)
|
||||
print(sum)
|
||||
print(printed)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Test command**:
|
||||
```bash
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_continue_multi_carrier.hako
|
||||
# Expected output:
|
||||
# 3
|
||||
# 2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tracking Variable Updates
|
||||
|
||||
### variable_map lifecycle
|
||||
|
||||
1. **Before loop lowering**: `{"i": ValueId(5), "sum": ValueId(10), "printed": ValueId(11)}`
|
||||
|
||||
2. **After JoinModule creation**: (unchanged)
|
||||
|
||||
3. **ExitBindingBuilder::build_loop_exit_bindings()**:
|
||||
- Allocates new ValueIds for post-loop carrier values
|
||||
- Updates variable_map: `{"i": ValueId(5), "sum": ValueId(??), "printed": ValueId(??)}`
|
||||
|
||||
4. **After loop lowering**: variable_map reflects post-loop state
|
||||
|
||||
### Debugging support
|
||||
|
||||
Optional environment variable: `NYASH_TRACE_EXIT_BINDING=1`
|
||||
|
||||
Output example:
|
||||
```
|
||||
[exit_binding] Carrier "sum": host_id=ValueId(10) → join_exit=ValueId(15) → post_loop=ValueId(23)
|
||||
[exit_binding] Carrier "printed": host_id=ValueId(11) → join_exit=ValueId(14) → post_loop=ValueId(24)
|
||||
[exit_binding] JoinInlineBoundary: host_outputs=[ValueId(5), ValueId(23), ValueId(24)]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Phases
|
||||
|
||||
- **Phase 188**: JoinInlineBoundary initial design
|
||||
- **Phase 190**: CarrierInfo (Phase 193-2 enhancement)
|
||||
- **Phase 193-3**: Pattern classification helpers
|
||||
- **Phase 193-4**: ExitBindingBuilder (THIS PHASE)
|
||||
- **Phase 193-5**: Multi-carrier testing and validation
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] ExitBindingBuilder compiles and passes unit tests
|
||||
- [ ] Pattern 3 & 4 lowerers refactored to use ExitBindingBuilder
|
||||
- [ ] No hardcoded variable names or ValueId assumptions remain in lowering
|
||||
- [ ] loop_continue_multi_carrier.hako test passes with correct output
|
||||
- [ ] Variable map correctly reflects post-loop carrier state
|
||||
- [ ] Debugging environment variable works as expected
|
||||
@ -1,574 +0,0 @@
|
||||
# Phase 194: JsonParser P1/P2/P5 実戦投入
|
||||
|
||||
**Status**: Ready for Implementation
|
||||
**Date**: 2025-12-09
|
||||
**Prerequisite**: Phase 193 complete (MethodCall in init with ConditionEnv constraints)
|
||||
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
Phase 170-193 で構築した JoinIR インフラを **実コード(JsonParser)で検証**する。
|
||||
|
||||
**スコープ**: 「今の設計で素直に通せるところだけ」に絞る(無理に新 Pattern を作らない)
|
||||
|
||||
---
|
||||
|
||||
## Task 194-1: 対象ループの選定(実戦版)
|
||||
|
||||
### 目標
|
||||
`tools/hako_shared/json_parser.hako` から、**JoinIR で通す対象**と**保留するループ**を明確に分ける。
|
||||
|
||||
### 対象ループ候補
|
||||
|
||||
#### ✅ JoinIR で通せるループ(digits 依存なし)
|
||||
|
||||
**Pattern 1/2 系**:
|
||||
```nyash
|
||||
// _skip_whitespace (既に PoC 済み)
|
||||
loop(i < len and (s[i] == ' ' or s[i] == '\n' or ...)) {
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
- **Pattern**: P1 or P2 (条件のみ、単純インクリメント)
|
||||
- **Carrier**: i (IntegerBox)
|
||||
- **Update**: i = i + 1
|
||||
|
||||
**Pattern 5 系(Trim)**:
|
||||
```nyash
|
||||
// _trim leading/trailing (TrimLoopLowerer)
|
||||
loop(start < len and s[start] == ' ') {
|
||||
start = start + 1
|
||||
}
|
||||
loop(end > 0 and s[end - 1] == ' ') {
|
||||
end = end - 1
|
||||
}
|
||||
```
|
||||
- **Pattern**: P5 (Trim specialized)
|
||||
- **Carrier**: start, end (IntegerBox)
|
||||
- **Update**: start+1 or end-1
|
||||
|
||||
**Pattern 2 系(簡略版 parse_string)**:
|
||||
```nyash
|
||||
// _parse_string の簡略形(escape なし・buffer 1 キャリア版)
|
||||
local buffer = ""
|
||||
local i = start
|
||||
loop(i < len and s[i] != '"') {
|
||||
buffer = buffer + s[i]
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
- **Pattern**: P2 (break 条件あり)
|
||||
- **Carrier**: buffer (StringBox), i (IntegerBox)
|
||||
- **Update**: buffer concat, i+1
|
||||
- **制約**: escape 処理なし(`\` を含まない想定)
|
||||
|
||||
#### ⚠️ 保留ループ(Phase 200+)
|
||||
|
||||
**digits テーブル依存**:
|
||||
```nyash
|
||||
// _parse_number
|
||||
local digit = digits.indexOf(s[i]) // ← ConditionEnv 制約で保留
|
||||
result = result * 10 + digit
|
||||
```
|
||||
- **理由**: `digits` は外部ローカル変数 → ConditionEnv に含まれない
|
||||
- **対応**: Phase 200+ で ConditionEnv 拡張または .hako リライト
|
||||
|
||||
**複雑キャリア + flatten**:
|
||||
```nyash
|
||||
// _unescape_string
|
||||
local escaped = false
|
||||
local result = ""
|
||||
loop(...) {
|
||||
if(escaped) { ... }
|
||||
else if(ch == '\\') { escaped = true }
|
||||
else { result = result + ch }
|
||||
}
|
||||
```
|
||||
- **理由**: escaped フラグ + 条件分岐が複雑
|
||||
- **対応**: Phase 195+ (Pattern 3 拡張)
|
||||
|
||||
**MethodCall 複数**:
|
||||
```nyash
|
||||
// _parse_array, _parse_object
|
||||
local value = _parse_value(...) // ← MethodCall が複数・ネスト
|
||||
array.push(value)
|
||||
```
|
||||
- **理由**: MethodCall が複雑、ネストあり
|
||||
- **対応**: Phase 195+ (MethodCall 拡張)
|
||||
|
||||
### 成果物
|
||||
- **対象ループリスト**(3-5 個程度)
|
||||
- **保留ループリスト**(理由付き)
|
||||
- ドキュメント: `phase194-loop-inventory.md` (簡易版)
|
||||
|
||||
---
|
||||
|
||||
## Task 194-2: routing 側の適用拡張
|
||||
|
||||
### 対象ファイル
|
||||
- `src/mir/builder/control_flow/joinir/routing.rs`
|
||||
|
||||
### 実装内容
|
||||
|
||||
#### 1. Whitelist 更新(関数名ベース)
|
||||
|
||||
現在の routing は whitelist で JoinIR 適用を制御している。以下を追加:
|
||||
|
||||
```rust
|
||||
// src/mir/builder/control_flow/joinir/routing.rs
|
||||
|
||||
const JOINIR_ENABLED_FUNCTIONS: &[&str] = &[
|
||||
// 既存(PoC 済み)
|
||||
"_skip_whitespace",
|
||||
"_trim",
|
||||
|
||||
// Phase 194 追加
|
||||
"_trim_leading",
|
||||
"_trim_trailing",
|
||||
"_parse_string_simple", // escape なし版
|
||||
|
||||
// 保留(明示的にコメント)
|
||||
// "_parse_number", // Phase 200+: digits.indexOf 依存
|
||||
// "_unescape_string", // Phase 195+: 複雑キャリア
|
||||
// "_parse_array", // Phase 195+: MethodCall 複数
|
||||
];
|
||||
|
||||
pub fn should_use_joinir(function_name: &str) -> bool {
|
||||
JOINIR_ENABLED_FUNCTIONS.contains(&function_name)
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. PatternPipelineContext の調整(必要に応じて)
|
||||
|
||||
既存の Pattern 1/2/5 で処理できる範囲なので、新規 Pattern は追加しない。
|
||||
|
||||
ただし、routing ロジックで以下を確認:
|
||||
- P1/P2 の選択ロジックが正しく動作しているか
|
||||
- P5 (Trim) の検出が JsonParser のループで発火するか
|
||||
|
||||
### 設計原則
|
||||
|
||||
- **新 Pattern なし**: 既存 P1/P2/P5 で処理
|
||||
- **Fail-Fast**: 対応できないループは明示的にスキップ(fallback to legacy)
|
||||
- **whitelist 方式**: 段階的に対象を広げる(一気に全部オンにしない)
|
||||
|
||||
---
|
||||
|
||||
## Task 194-3: 実戦 E2E 実行
|
||||
|
||||
### 目標
|
||||
JsonParser 全体を `NYASH_JOINIR_CORE=1` で実行し、JoinIR ルートが動作することを確認。
|
||||
|
||||
### テストケース選定
|
||||
|
||||
#### 1. 代表ケース(min/basic)
|
||||
|
||||
```bash
|
||||
# Minimal JSON
|
||||
echo '{"key": "value"}' > /tmp/test_min.json
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune tools/hako_shared/json_parser.hako /tmp/test_min.json
|
||||
|
||||
# 期待結果: RC が想定通り(parse 成功)
|
||||
```
|
||||
|
||||
#### 2. Trace で JoinIR ルート確認
|
||||
|
||||
```bash
|
||||
# JoinIR trace 有効化
|
||||
NYASH_JOINIR_CORE=1 NYASH_JOINIR_DEBUG=1 ./target/release/hakorune tools/hako_shared/json_parser.hako /tmp/test_min.json 2>&1 | grep "\[trace:joinir\]"
|
||||
|
||||
# 確認項目:
|
||||
# - [trace:joinir] _skip_whitespace: Pattern 1/2 適用
|
||||
# - [trace:joinir] _trim_leading: Pattern 5 適用
|
||||
# - [joinir/freeze] が出ていないこと(freeze = fallback to legacy)
|
||||
```
|
||||
|
||||
#### 3. 退行確認(既存テスト)
|
||||
|
||||
```bash
|
||||
# Phase 190-193 の E2E テストが引き続き動作
|
||||
./target/release/hakorune apps/tests/phase190_atoi_impl.hako
|
||||
# Expected: 12
|
||||
|
||||
./target/release/hakorune apps/tests/phase191_body_local_atoi.hako
|
||||
# Expected: 123
|
||||
|
||||
./target/release/hakorune apps/tests/phase193_init_method_call.hako
|
||||
# Expected: コンパイル成功
|
||||
```
|
||||
|
||||
### 落ちた場合の対応
|
||||
|
||||
**Fail-Fast 戦略**:
|
||||
- エラーが出たループは **inventory に追加するだけ**
|
||||
- 無理に直さない(Phase 195+ の課題として記録)
|
||||
- whitelist から外して legacy 経路にフォールバック
|
||||
|
||||
### 成果物
|
||||
- 実行ログ(成功 or 失敗箇所の記録)
|
||||
- JoinIR ルートを踏んだループのリスト
|
||||
- inventory: 「Phase 19x で対応するループ」リスト
|
||||
|
||||
---
|
||||
|
||||
## Task 194-4: hako_check / selfhost との軽い接続(オプション)
|
||||
|
||||
### 目標(余力があれば)
|
||||
|
||||
hako_check や selfhost Stage-3 で、Phase 194 で JoinIR 化したループが実際に踏まれているかをチェック。
|
||||
|
||||
### 実施内容
|
||||
|
||||
#### 1. hako_check の JsonParser 関連ルール
|
||||
|
||||
```bash
|
||||
# JsonParser の品質チェック
|
||||
./tools/hako_check.sh tools/hako_shared/json_parser.hako --dead-code
|
||||
|
||||
# 確認:
|
||||
# - 今回 JoinIR 化したループ(_skip_whitespace, _trim 等)が使われているか
|
||||
# - デッドコードとして検出されていないか
|
||||
```
|
||||
|
||||
#### 2. selfhost Stage-3 代表パス
|
||||
|
||||
```bash
|
||||
# セルフホストコンパイラで JsonParser 使用
|
||||
NYASH_USE_NY_COMPILER=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 \
|
||||
./target/release/hakorune apps/selfhost-runtime/example_with_json.hako
|
||||
|
||||
# 確認:
|
||||
# - JsonParser のループが JoinIR 経路で動作
|
||||
# - セルフホストコンパイラ全体が正常動作
|
||||
```
|
||||
|
||||
### 判断基準
|
||||
|
||||
**厳しければ Phase 195+ に回す**:
|
||||
- selfhost 全体の動作確認は時間がかかる
|
||||
- Phase 194 のゴールは「JsonParser の一部ループが動く」こと
|
||||
- 完全検証は次フェーズでも OK
|
||||
|
||||
---
|
||||
|
||||
## Task 194-5: ドキュメント更新
|
||||
|
||||
### 1. JsonParser ループサマリ作成
|
||||
|
||||
**ファイル**: `docs/development/current/main/phase194-loop-inventory.md` (新規)
|
||||
|
||||
```markdown
|
||||
# Phase 194: JsonParser ループ Inventory
|
||||
|
||||
## JoinIR で動作中のループ(Phase 194)
|
||||
|
||||
| ループ名 | Pattern | Carrier | Update | 状態 |
|
||||
|---------|---------|---------|--------|------|
|
||||
| _skip_whitespace | P1/P2 | i | i+1 | ✅ 動作 |
|
||||
| _trim_leading | P5 | start | start+1 | ✅ 動作 |
|
||||
| _trim_trailing | P5 | end | end-1 | ✅ 動作 |
|
||||
| _parse_string_simple | P2 | buffer, i | concat, i+1 | ✅ 動作 |
|
||||
|
||||
## 保留ループ(Phase 200+)
|
||||
|
||||
| ループ名 | 保留理由 | 対応予定 |
|
||||
|---------|---------|---------|
|
||||
| _parse_number | digits.indexOf 依存(ConditionEnv 制約) | Phase 200+ |
|
||||
| _unescape_string | 複雑キャリア + flatten | Phase 195+ |
|
||||
| _parse_array | MethodCall 複数・ネスト | Phase 195+ |
|
||||
| _parse_object | MethodCall 複数・ネスト | Phase 195+ |
|
||||
|
||||
## 統計
|
||||
|
||||
- **JoinIR 対応**: 4/8 ループ(50%)
|
||||
- **Pattern 分布**: P1/P2: 2, P5: 2
|
||||
- **保留**: 4 ループ(明確な理由付き)
|
||||
```
|
||||
|
||||
### 2. CURRENT_TASK.md 更新
|
||||
|
||||
```markdown
|
||||
## Phase 194: JsonParser P1/P2/P5 実戦投入(完了: 2025-12-XX)
|
||||
|
||||
**目標**: Phase 170-193 のインフラを実コードで検証
|
||||
|
||||
**実装内容**:
|
||||
- ✅ 対象ループ選定(4 ループを JoinIR 化、4 ループを保留)
|
||||
- ✅ routing whitelist 更新(_skip_whitespace, _trim_*, _parse_string_simple)
|
||||
- ✅ E2E 実行: JsonParser が JoinIR ルートで動作確認
|
||||
- ✅ Trace で JoinIR ルート確認([joinir/freeze] なし)
|
||||
|
||||
**成果**:
|
||||
- JsonParser の 4/8 ループが JoinIR 経路で動作(P1/P2/P5)
|
||||
- digits.indexOf 等は Phase 200+ に明示的に保留
|
||||
- 既存テスト(phase190-193)退行なし
|
||||
|
||||
**技術的発見**:
|
||||
- P1/P2/P5 の実戦適用で箱の品質確認完了
|
||||
- ConditionEnv 制約が明確化(digits テーブル依存ループは保留)
|
||||
- Fail-Fast 戦略により、無理のない段階的拡張を実現
|
||||
|
||||
**次のステップ**: Phase 195(Pattern 3 拡張)or Phase 200+(ConditionEnv 拡張)
|
||||
```
|
||||
|
||||
### 3. joinir-architecture-overview.md 更新
|
||||
|
||||
Section 7.2 "残タスク" に追記:
|
||||
|
||||
```markdown
|
||||
- [x] **Phase 194**: JsonParser P1/P2/P5 実戦投入
|
||||
- 4/8 ループが JoinIR 経路で動作確認
|
||||
- digits.indexOf 等は Phase 200+ に保留
|
||||
- 実戦検証により箱の品質確認完了
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 成功基準
|
||||
|
||||
- [x] 対象ループリスト作成(4 ループ選定、4 ループ保留)
|
||||
- [x] routing whitelist 更新完了
|
||||
- [x] JsonParser E2E テストが JoinIR ルートで動作
|
||||
- [x] Trace で JoinIR ルート確認(freeze なし)
|
||||
- [x] 既存テスト(phase190-193)退行なし
|
||||
- [x] ドキュメント更新(inventory + CURRENT_TASK.md)
|
||||
|
||||
---
|
||||
|
||||
## 設計原則(Phase 194)
|
||||
|
||||
1. **「今できること」に集中**:
|
||||
- P1/P2/P5 で通せるループのみ対象
|
||||
- 新 Pattern を作らない(既存インフラ再利用)
|
||||
|
||||
2. **Fail-Fast 戦略**:
|
||||
- digits テーブル依存は明示的に保留
|
||||
- 落ちたら inventory に追加(無理に直さない)
|
||||
|
||||
3. **段階的拡張**:
|
||||
- whitelist で対象を絞る(一気に全部オンにしない)
|
||||
- 実戦検証 → 課題発見 → 次 Phase で対応
|
||||
|
||||
4. **箱理論の実践**:
|
||||
- 既存の箱(ConditionEnv/UpdateEnv/NumberAccumulation)の品質検証
|
||||
- 無理のない範囲での実戦投入
|
||||
|
||||
---
|
||||
|
||||
## 関連ファイル
|
||||
|
||||
### 実装対象
|
||||
- `src/mir/builder/control_flow/joinir/routing.rs` (whitelist 更新)
|
||||
|
||||
### テスト対象
|
||||
- `tools/hako_shared/json_parser.hako` (JsonParser 本体)
|
||||
- `/tmp/test_min.json` (テストデータ)
|
||||
|
||||
### ドキュメント
|
||||
- `docs/development/current/main/phase194-loop-inventory.md` (新規作成)
|
||||
- `docs/development/current/main/joinir-architecture-overview.md` (更新)
|
||||
- `CURRENT_TASK.md` (Phase 194 完了マーク)
|
||||
|
||||
---
|
||||
|
||||
## 次の Phase への接続
|
||||
|
||||
### Phase 195 候補: Pattern 3 拡張(if-in-loop)
|
||||
- _unescape_string の escaped フラグ対応
|
||||
- 条件分岐を跨ぐ body-local 変数
|
||||
|
||||
### Phase 200+ 候補: ConditionEnv 拡張
|
||||
- 外部ローカル変数(digits テーブル)対応
|
||||
- _parse_number の digits.indexOf サポート
|
||||
|
||||
### 判断基準
|
||||
- Phase 194 の実戦投入で「どこが詰まったか」を見て優先順位決定
|
||||
- 無理に全部対応しない(Fail-Fast で課題を明確化)
|
||||
|
||||
---
|
||||
|
||||
## Implementation Status
|
||||
|
||||
**完了日**: 2025-12-09
|
||||
|
||||
### 実装サマリ
|
||||
|
||||
**JoinIR 対応ループ** (4/10):
|
||||
- ✅ _skip_whitespace (Pattern 2) - Already whitelisted
|
||||
- ✅ _trim (leading) (Pattern 5) - Already whitelisted
|
||||
- ✅ _trim (trailing) (Pattern 5) - Already whitelisted
|
||||
- ✅ _match_literal (Pattern 2) - Already whitelisted
|
||||
|
||||
**保留ループ** (6/10):
|
||||
- ❌ _parse_number - ConditionEnv constraint (`digits.indexOf()`)
|
||||
- ❌ _atoi - ConditionEnv constraint (`digits.indexOf()`)
|
||||
- ❌ _parse_string - Complex carriers (escaped flag + continue)
|
||||
- ❌ _unescape_string - Complex carriers (multiple flags)
|
||||
- ❌ _parse_array - Multiple MethodCalls
|
||||
- ❌ _parse_object - Multiple MethodCalls
|
||||
|
||||
### E2E テスト結果
|
||||
|
||||
**テスト環境**:
|
||||
```bash
|
||||
cargo build --release
|
||||
NYASH_DISABLE_PLUGINS=1 NYASH_JOINIR_CORE=1 ./target/release/hakorune tools/hako_shared/json_parser.hako
|
||||
```
|
||||
|
||||
**結果**: ❌ Compilation Error (Expected - Fail-Fast Strategy)
|
||||
|
||||
**エラー内容**:
|
||||
```
|
||||
[ERROR] ❌ MIR compilation error: [cf_loop/pattern2] Lowering failed: [joinir/pattern2] Unsupported condition: uses loop-body-local variables: ["digit_pos"]. Pattern 2 supports only loop parameters and outer-scope variables. Consider using Pattern 5+ for complex loop conditions.
|
||||
```
|
||||
|
||||
**分析**:
|
||||
- `_parse_number` の `digits.indexOf(ch)` が Phase 193 ConditionEnv 制約に引っかかった
|
||||
- `digits` は外部ローカル変数(function-scoped)だが、ConditionEnv には含まれない
|
||||
- **Fail-Fast 戦略通り**: 無理に直さず、Phase 200+ に保留
|
||||
|
||||
### 退行テスト結果 (✅ All Pass)
|
||||
|
||||
```bash
|
||||
# Phase 190: NumberAccumulation
|
||||
./target/release/hakorune apps/tests/phase190_atoi_impl.hako
|
||||
# Expected: 12
|
||||
# Result: ✅ 12 (RC: 0)
|
||||
|
||||
# Phase 191: Body-local init
|
||||
./target/release/hakorune apps/tests/phase191_body_local_atoi.hako
|
||||
# Expected: 123
|
||||
# Result: ✅ 123 (RC: 0)
|
||||
|
||||
# Phase 193: MethodCall in init
|
||||
./target/release/hakorune apps/tests/phase193_init_method_call.hako
|
||||
# Expected: Compilation success
|
||||
# Result: ✅ RC: 0
|
||||
```
|
||||
|
||||
**結論**: 既存インフラに退行なし ✅
|
||||
|
||||
### 技術的発見
|
||||
|
||||
#### 1. **ConditionEnv 制約の明確化** (Phase 200+ 課題)
|
||||
|
||||
**現状** (Phase 193):
|
||||
- ConditionEnv に含まれる変数:
|
||||
- ✅ Loop parameters (loop variable)
|
||||
- ✅ Condition-only bindings (外部変数のループ前評価)
|
||||
- ✅ Body-local variables (ループ内で定義)
|
||||
- ✅ Carrier variables (ループで更新される変数)
|
||||
|
||||
**含まれない変数**:
|
||||
- ❌ Function-scoped local variables (例: `digits`)
|
||||
|
||||
**影響を受けたループ**:
|
||||
- `_parse_number`: `local digits = "0123456789"` → `digit_pos = digits.indexOf(ch)`
|
||||
- `_atoi`: 同様に `digits.indexOf(ch)` 依存
|
||||
|
||||
**解決策案** (Phase 200+):
|
||||
1. **ConditionEnv 拡張**: Function-scoped variables も ConditionEnv に含める
|
||||
2. **.hako リライト**: `digits.indexOf(ch)` を `(ch >= "0" && ch <= "9")` に置換
|
||||
3. **専用パターン**: `indexOf` 専用の Pattern 実装
|
||||
|
||||
#### 2. **P5 Trim Pattern の実用性確認** (✅ 動作確認済み)
|
||||
|
||||
**発見**: Trim pattern が `_trim` で正常動作
|
||||
- ✅ Leading whitespace trim (Pattern 5)
|
||||
- ✅ Trailing whitespace trim (Pattern 5)
|
||||
- ✅ TrimLoopLowerer が `ch` を `is_ch_match` carrier に昇格
|
||||
- ✅ Whitespace check (`[" ", "\t", "\n", "\r"]`) を JoinIR で生成
|
||||
|
||||
**技術詳細** (trace log):
|
||||
```
|
||||
[TrimLoopLowerer] Trim pattern detected! var='ch', literals=["\r", "\n", "\t", " "]
|
||||
[TrimLoopLowerer] LoopBodyLocal 'ch' promoted to carrier 'is_ch_match'
|
||||
[TrimLoopLowerer] Added carrier 'is_ch_match' to ConditionEnv
|
||||
```
|
||||
|
||||
#### 3. **Structure-Only Routing の有効性** (Phase 196 default)
|
||||
|
||||
**現状** (routing.rs Line 45-51):
|
||||
```rust
|
||||
let structure_only = match std::env::var("NYASH_JOINIR_STRUCTURE_ONLY") {
|
||||
Some("0") | Some("off") => false,
|
||||
_ => true, // ← Default: ON
|
||||
};
|
||||
```
|
||||
|
||||
**利点**:
|
||||
- ✅ Whitelist 不要 (関数名ベースの制約なし)
|
||||
- ✅ Pattern-based routing のみで判定
|
||||
- ✅ 段階的拡張が容易
|
||||
|
||||
**懸念**:
|
||||
- ⚠️ サポート外ループで compilation error (Fail-Fast)
|
||||
- ⚠️ ユーザーには `NYASH_JOINIR_STRUCTURE_ONLY=0` で回避可能
|
||||
|
||||
**結論**: 現状の Structure-only routing で Phase 194 の目的は達成可能
|
||||
|
||||
### 次のステップ
|
||||
|
||||
#### 優先度 High: Phase 200+ ConditionEnv 拡張
|
||||
|
||||
**目標**: `digits.indexOf()` 対応
|
||||
|
||||
**設計課題**:
|
||||
1. Function-scoped variables をどこまで ConditionEnv に含めるか
|
||||
2. スコープ解析の複雑化リスク
|
||||
3. `.hako` リライトで回避可能か検証
|
||||
|
||||
**影響ループ**: 2/10 (20%) - `_parse_number`, `_atoi`
|
||||
|
||||
#### 優先度 Medium: Phase 195 Pattern 3 拡張
|
||||
|
||||
**目標**: Complex carriers (多段フラグ) 対応
|
||||
|
||||
**設計課題**:
|
||||
1. `is_escape`, `has_next`, `process_escape` のような多段フラグ
|
||||
2. If-in-loop + continue の組み合わせ
|
||||
|
||||
**影響ループ**: 2/10 (20%) - `_parse_string`, `_unescape_string`
|
||||
|
||||
#### 優先度 Low: Phase 195+ MethodCall 拡張
|
||||
|
||||
**目標**: Multiple MethodCalls in loop body
|
||||
|
||||
**設計課題**:
|
||||
1. Phase 193 は init のみ対応、body は未対応
|
||||
2. ネストした MethodCall の扱い
|
||||
|
||||
**影響ループ**: 2/10 (20%) - `_parse_array`, `_parse_object`
|
||||
|
||||
### 推奨ロードマップ
|
||||
|
||||
**Phase 194 完了判定**: ✅ 検証完了(Fail-Fast 戦略成功)
|
||||
|
||||
**Phase 195**: Pattern 3 extension (if-in-loop + multi-flag carriers)
|
||||
- Target: `_parse_string` (代表例)
|
||||
- Defer: `_unescape_string` (複雑すぎるため Pattern 3 安定後)
|
||||
|
||||
**Phase 200+**: ConditionEnv expansion (function-scoped locals)
|
||||
- Target: `_parse_number`, `_atoi`
|
||||
- Design: Function-scoped variable capture strategy
|
||||
|
||||
**Phase 201+**: MethodCall extension (multiple calls in body)
|
||||
- Target: `_parse_array`, `_parse_object`
|
||||
- Design: MethodCall orchestration in loop body
|
||||
|
||||
### まとめ
|
||||
|
||||
**Phase 194 の成果**:
|
||||
1. ✅ Loop inventory 完成 (4 target, 6 deferred)
|
||||
2. ✅ Routing infrastructure 確認 (structure-only mode 動作)
|
||||
3. ✅ 退行テスト全て pass (Phase 190-193)
|
||||
4. ✅ Fail-Fast 戦略実証 (`digits.indexOf` 制約明確化)
|
||||
5. ✅ P5 Trim pattern 実戦検証 (_trim で動作確認)
|
||||
|
||||
**Phase 194 の課題**:
|
||||
1. ConditionEnv 制約 (function-scoped variables 未対応)
|
||||
2. Complex carriers 未対応 (多段フラグ)
|
||||
3. Multiple MethodCalls 未対応
|
||||
|
||||
**全体評価**: Phase 194 は「検証フェーズ」として大成功。次の Phase への明確な道筋を示した。
|
||||
@ -1,321 +0,0 @@
|
||||
# Phase 194: JsonParser Loop Inventory
|
||||
|
||||
**Date**: 2025-12-09
|
||||
**File**: `tools/hako_shared/json_parser.hako`
|
||||
**Total Loops**: 10
|
||||
|
||||
---
|
||||
|
||||
## JoinIR Target Loops (Phase 194)
|
||||
|
||||
Loops that CAN be handled by existing P1/P2/P5 patterns.
|
||||
|
||||
| Loop Name | Pattern | Line | Carrier | Update | Status |
|
||||
|-----------|---------|------|---------|--------|--------|
|
||||
| `_skip_whitespace` | P2 | 312-319 | p | p+1 | ✅ Whitelisted |
|
||||
| `_trim` (leading) | P5 | 330-337 | start | start+1 | ✅ Whitelisted |
|
||||
| `_trim` (trailing) | P5 | 340-347 | end | end-1 | ✅ Whitelisted |
|
||||
| `_match_literal` | P2 | 357-362 | i | i+1 | ✅ Whitelisted |
|
||||
|
||||
### Pattern Details
|
||||
|
||||
#### `_skip_whitespace` (Line 312-319)
|
||||
```nyash
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
p = p + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
- **Pattern**: P2 (break condition)
|
||||
- **Carrier**: p (IntegerBox)
|
||||
- **Update**: p = p + 1
|
||||
- **Routing**: Already whitelisted as `JsonParserBox._skip_whitespace/2`
|
||||
|
||||
#### `_trim` Leading (Line 330-337)
|
||||
```nyash
|
||||
loop(start < end) {
|
||||
local ch = s.substring(start, start+1)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
start = start + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
- **Pattern**: P5 (Trim specialized)
|
||||
- **Carrier**: start (IntegerBox)
|
||||
- **Update**: start = start + 1
|
||||
- **Routing**: Already whitelisted as `JsonParserBox._trim/1`
|
||||
|
||||
#### `_trim` Trailing (Line 340-347)
|
||||
```nyash
|
||||
loop(end > start) {
|
||||
local ch = s.substring(end-1, end)
|
||||
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" {
|
||||
end = end - 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
- **Pattern**: P5 (Trim specialized)
|
||||
- **Carrier**: end (IntegerBox)
|
||||
- **Update**: end = end - 1
|
||||
- **Routing**: Already whitelisted as `JsonParserBox._trim/1`
|
||||
|
||||
#### `_match_literal` (Line 357-362)
|
||||
```nyash
|
||||
loop(i < len) {
|
||||
if s.substring(pos + i, pos + i + 1) != literal.substring(i, i + 1) {
|
||||
return 0
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
- **Pattern**: P2 (break via return)
|
||||
- **Carrier**: i (IntegerBox)
|
||||
- **Update**: i = i + 1
|
||||
- **Routing**: Already whitelisted as `JsonParserBox._match_literal/3`
|
||||
|
||||
---
|
||||
|
||||
## Deferred Loops (Phase 200+)
|
||||
|
||||
Loops that CANNOT be handled by current P1/P2/P5 patterns.
|
||||
|
||||
| Loop Name | Line | Deferral Reason | Target Phase |
|
||||
|-----------|------|-----------------|--------------|
|
||||
| `_parse_number` | 121-133 | `digits.indexOf()` - ConditionEnv constraint | Phase 200+ |
|
||||
| `_atoi` | 453-460 | `digits.indexOf()` - ConditionEnv constraint | Phase 200+ |
|
||||
| `_parse_string` | 150-178 | Complex carriers (escaped flag via continue) | Phase 195+ |
|
||||
| `_unescape_string` | 373-431 | Complex carriers (is_escape, has_next, process_escape) | Phase 195+ |
|
||||
| `_parse_array` | 203-231 | Multiple MethodCalls (`_parse_value`, nested) | Phase 195+ |
|
||||
| `_parse_object` | 256-304 | Multiple MethodCalls (`_parse_string`, `_parse_value`) | Phase 195+ |
|
||||
|
||||
### Deferral Details
|
||||
|
||||
#### `_parse_number` (Line 121-133) - **ConditionEnv Constraint**
|
||||
```nyash
|
||||
local digits = "0123456789" // ← External local variable
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
local digit_pos = digits.indexOf(ch) // ← Uses external 'digits'
|
||||
|
||||
if digit_pos < 0 {
|
||||
break
|
||||
}
|
||||
|
||||
num_str = num_str + ch
|
||||
p = p + 1
|
||||
}
|
||||
```
|
||||
- **Reason**: `digits` is an external local variable not in ConditionEnv
|
||||
- **Constraint**: Phase 193 ConditionEnv only includes: function params, loop carriers, body-local vars
|
||||
- **Workaround**: None (fundamental ConditionEnv limitation)
|
||||
- **Solution**: Phase 200+ ConditionEnv expansion OR .hako rewrite to inline digits
|
||||
|
||||
#### `_atoi` (Line 453-460) - **ConditionEnv Constraint**
|
||||
```nyash
|
||||
local digits = "0123456789" // ← External local variable
|
||||
loop(i < n) {
|
||||
local ch = s.substring(i, i+1)
|
||||
if ch < "0" || ch > "9" { break }
|
||||
local pos = digits.indexOf(ch) // ← Uses external 'digits'
|
||||
if pos < 0 { break }
|
||||
v = v * 10 + pos
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
- **Reason**: Same as `_parse_number` - `digits.indexOf()` dependency
|
||||
- **Solution**: Phase 200+ ConditionEnv expansion
|
||||
|
||||
#### `_parse_string` (Line 150-178) - **Complex Carriers**
|
||||
```nyash
|
||||
loop(p < s.length()) {
|
||||
local ch = s.substring(p, p+1)
|
||||
|
||||
if ch == '"' {
|
||||
// ... MapBox construction ...
|
||||
return result
|
||||
}
|
||||
|
||||
if ch == "\\" {
|
||||
local has_next = 0
|
||||
if p + 1 < s.length() { has_next = 1 }
|
||||
|
||||
if has_next == 0 { return null }
|
||||
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
str = str + s.substring(p, p+1)
|
||||
p = p + 1
|
||||
continue // ← Escape handling via continue
|
||||
}
|
||||
|
||||
str = str + ch
|
||||
p = p + 1
|
||||
}
|
||||
```
|
||||
- **Reason**: Escape handling logic uses `continue` + conditional flag (`has_next`)
|
||||
- **Pattern Fit**: P2 or P3 with complex control flow
|
||||
- **Solution**: Phase 195+ Pattern 3 extension for if-in-loop with continue
|
||||
|
||||
#### `_unescape_string` (Line 373-431) - **Complex Carriers**
|
||||
```nyash
|
||||
loop(i < s.length()) {
|
||||
local ch = s.substring(i, i+1)
|
||||
|
||||
local is_escape = 0
|
||||
local has_next = 0
|
||||
|
||||
if ch == "\\" { is_escape = 1 }
|
||||
if i + 1 < s.length() { has_next = 1 }
|
||||
|
||||
local process_escape = 0
|
||||
if is_escape == 1 {
|
||||
if has_next == 1 {
|
||||
process_escape = 1
|
||||
}
|
||||
}
|
||||
|
||||
if process_escape == 1 {
|
||||
// ... multiple escape type checks ...
|
||||
continue
|
||||
}
|
||||
|
||||
result = result + ch
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
- **Reason**: Multiple body-local flags (`is_escape`, `has_next`, `process_escape`) + nested conditions
|
||||
- **Pattern Fit**: P3 with complex flatten pattern
|
||||
- **Solution**: Phase 195+ Pattern 3 extension for multi-flag carriers
|
||||
|
||||
#### `_parse_array` / `_parse_object` - **Multiple MethodCalls**
|
||||
```nyash
|
||||
// _parse_array
|
||||
loop(p < s.length()) {
|
||||
local elem_result = me._parse_value(s, p) // ← MethodCall 1
|
||||
if elem_result == null { return null }
|
||||
|
||||
local elem = elem_result.get("value") // ← MethodCall 2
|
||||
arr.push(elem) // ← MethodCall 3
|
||||
|
||||
p = elem_result.get("pos") // ← MethodCall 4
|
||||
// ...
|
||||
}
|
||||
|
||||
// _parse_object
|
||||
loop(p < s.length()) {
|
||||
local key_result = me._parse_string(s, p) // ← MethodCall 1
|
||||
// ...
|
||||
local value_result = me._parse_value(s, p) // ← MethodCall 2
|
||||
// ...
|
||||
obj.set(key, value) // ← MethodCall 3
|
||||
// ...
|
||||
}
|
||||
```
|
||||
- **Reason**: Multiple MethodCalls per iteration (4+ calls)
|
||||
- **Constraint**: Phase 193 supports 1 MethodCall in init, not multiple in body
|
||||
- **Solution**: Phase 195+ MethodCall extension for loop body
|
||||
|
||||
---
|
||||
|
||||
## Statistics
|
||||
|
||||
### Coverage
|
||||
- **JoinIR Target**: 4/10 loops (40%)
|
||||
- **Deferred**: 6/10 loops (60%)
|
||||
|
||||
### Pattern Distribution (Target Loops)
|
||||
- **P2 (Break)**: 2 loops (`_skip_whitespace`, `_match_literal`)
|
||||
- **P5 (Trim)**: 2 loops (`_trim` leading, `_trim` trailing)
|
||||
|
||||
### Deferral Reasons
|
||||
- **ConditionEnv Constraint**: 2 loops (`_parse_number`, `_atoi`)
|
||||
- **Complex Carriers**: 2 loops (`_parse_string`, `_unescape_string`)
|
||||
- **Multiple MethodCalls**: 2 loops (`_parse_array`, `_parse_object`)
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy (Phase 194)
|
||||
|
||||
### ✅ Already Whitelisted
|
||||
All 4 target loops are ALREADY in the routing whitelist:
|
||||
- `JsonParserBox._skip_whitespace/2` (Line 88)
|
||||
- `JsonParserBox._trim/1` (Line 87)
|
||||
- `JsonParserBox._match_literal/3` (Line 89)
|
||||
|
||||
### 🎯 E2E Validation
|
||||
**Goal**: Verify these loops actually run on JoinIR route without fallback.
|
||||
|
||||
```bash
|
||||
# Test 1: Basic execution
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune tools/hako_shared/json_parser.hako
|
||||
|
||||
# Test 2: Trace verification
|
||||
NYASH_JOINIR_CORE=1 NYASH_JOINIR_DEBUG=1 ./target/release/hakorune tools/hako_shared/json_parser.hako 2>&1 | grep "\[trace:joinir\]"
|
||||
|
||||
# Expected: NO [joinir/freeze] messages (freeze = fallback to legacy)
|
||||
```
|
||||
|
||||
### ⚠️ No New Whitelist Additions Needed
|
||||
Phase 194 is about **validating existing infrastructure**, not adding new functions.
|
||||
|
||||
### 📊 Success Criteria
|
||||
- [ ] All 4 target loops run on JoinIR route (no freeze)
|
||||
- [ ] JsonParser parses basic JSON successfully
|
||||
- [ ] No regressions in Phase 190-193 tests
|
||||
- [ ] Clear documentation of deferred loops (this file)
|
||||
|
||||
---
|
||||
|
||||
## Next Phase Priorities (Based on Deferral Analysis)
|
||||
|
||||
### Phase 200+: ConditionEnv Expansion (High Value)
|
||||
**Impact**: 2 loops (`_parse_number`, `_atoi`)
|
||||
**Solution**: Expand ConditionEnv to include function-scoped locals OR .hako rewrite
|
||||
|
||||
### Phase 195+: Pattern 3 Extension (Medium Value)
|
||||
**Impact**: 2 loops (`_parse_string`, `_unescape_string`)
|
||||
**Solution**: Support complex carriers with multiple body-local flags
|
||||
|
||||
### Phase 195+: MethodCall Extension (Low Priority)
|
||||
**Impact**: 2 loops (`_parse_array`, `_parse_object`)
|
||||
**Solution**: Support multiple MethodCalls in loop body
|
||||
**Note**: These are complex parsers, may be better handled by future optimization passes
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
### Short-term (Phase 194)
|
||||
1. ✅ E2E test with existing 4 whitelisted loops
|
||||
2. ✅ Verify no fallback via trace
|
||||
3. ✅ Document findings (this file)
|
||||
|
||||
### Medium-term (Phase 195)
|
||||
1. Focus on Pattern 3 extension (if-in-loop + multiple carriers)
|
||||
2. Target `_parse_string` as representative case
|
||||
3. Defer `_unescape_string` complexity until Pattern 3 is stable
|
||||
|
||||
### Long-term (Phase 200+)
|
||||
1. ConditionEnv expansion design document
|
||||
2. Evaluate .hako rewrite vs. compiler extension
|
||||
3. Consider `digits.indexOf()` as representative case for all external local dependencies
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
**Phase 194 is validation-focused**: 4 target loops are ALREADY whitelisted and should work with existing P1/P2/P5 patterns. The 6 deferred loops have clear constraints that require future infrastructure:
|
||||
|
||||
- **ConditionEnv**: Needs Phase 200+ design
|
||||
- **Complex Carriers**: Needs Phase 195 Pattern 3 extension
|
||||
- **Multiple MethodCalls**: Needs Phase 195+ design
|
||||
|
||||
This inventory provides a clear roadmap for future JoinIR expansion based on real-world loop patterns.
|
||||
@ -1,89 +0,0 @@
|
||||
# Phase 194: JoinLoopTrace / Debug Integration
|
||||
|
||||
**Status**: In Progress(trace.rs は実装済み、呼び出し置き換えと docs 反映が残り)
|
||||
**Date**: 2025-12-06
|
||||
|
||||
## Goal
|
||||
|
||||
JoinIR / ループ周辺のデバッグ出力を `JoinLoopTrace` に集約し、環境変数ベースの制御と `logging_policy.md` の方針に沿った形に整理する。
|
||||
|
||||
---
|
||||
|
||||
## Implemented Infrastructure
|
||||
|
||||
### JoinLoopTrace モジュール
|
||||
|
||||
- File: `src/mir/builder/control_flow/joinir/trace.rs`
|
||||
- 役割:
|
||||
- JoinIR ループまわりの `eprintln!` を 1 箱に集約する。
|
||||
- 環境変数からフラグを読み取り、どのカテゴリのトレースを出すかを制御する。
|
||||
- 対応環境変数:
|
||||
- `NYASH_TRACE_VARMAP=1` – variable_map トレース(var → ValueId)
|
||||
- `NYASH_JOINIR_DEBUG=1` – JoinIR 全般のデバッグ(パターン routing、merge 統計など)
|
||||
- `NYASH_OPTION_C_DEBUG=1` – PHI 生成(Option C)周りのトレース
|
||||
- `NYASH_JOINIR_MAINLINE_DEBUG=1` – mainline routing(代表関数の routing)トレース
|
||||
- `NYASH_LOOPFORM_DEBUG=1` – LoopForm 関連トレース(レガシー互換)
|
||||
|
||||
### 主なメソッド
|
||||
|
||||
- `pattern(tag, pattern_name, matched)` – パターン検出・選択
|
||||
- `varmap(tag, &BTreeMap<String, ValueId>)` – variable_map の状態
|
||||
- `joinir_stats(tag, func_count, block_count)` – JoinIR モジュールの統計
|
||||
- `phi(tag, msg)` – PHI 関連の操作
|
||||
- `merge(tag, msg)` – JoinIR→MIR マージ進行
|
||||
- `exit_phi(tag, var_name, old_id, new_id)` – Exit PHI 接続
|
||||
- `debug(tag, msg)` / `routing(tag, func_name, msg)` / `blocks(tag, msg)` / `instructions(tag, msg)` – 汎用デバッグ
|
||||
|
||||
グローバルアクセサ:
|
||||
|
||||
```rust
|
||||
pub fn trace() -> &'static JoinLoopTrace
|
||||
```
|
||||
|
||||
でどこからでも呼び出せる。
|
||||
|
||||
---
|
||||
|
||||
## Remaining Work (Phase 194)
|
||||
|
||||
### Task 194-3: 生 `eprintln!` の JoinLoopTrace 置き換え
|
||||
|
||||
対象(例):
|
||||
|
||||
- `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
|
||||
- まだ 8 箇所程度 `eprintln!` ベースのログが残っている。
|
||||
- ここを順次:
|
||||
- varmap ログ → `trace().varmap(...)`
|
||||
- パターン / ルーティング系 → `trace().pattern(...)` / `trace().routing(...)`
|
||||
- それ以外の debug → `trace().debug(...)`
|
||||
に差し替える。
|
||||
|
||||
同様に、他の JoinIR / loop 関連ファイルに散在している `[joinir/...]` 系 `eprintln!` も、必要に応じて `trace.rs` 経由に寄せる。
|
||||
|
||||
### Task 194-4: logging_policy.md 反映
|
||||
|
||||
- File: `docs/development/current/main/logging_policy.md`
|
||||
- 追記内容:
|
||||
- JoinIR / ループ系のトレースカテゴリを 1 セクションにまとめる:
|
||||
- varmap(NYASH_TRACE_VARMAP)
|
||||
- joinir-debug(NYASH_JOINIR_DEBUG)
|
||||
- phi-debug(NYASH_OPTION_C_DEBUG)
|
||||
- mainline-debug(NYASH_JOINIR_MAINLINE_DEBUG)
|
||||
- 開発時のみ ON、本番パスでは OFF を前提とする運用メモ。
|
||||
- `trace.rs` による prefix(`[trace:pattern]`, `[trace:varmap]`, ...)を簡単に説明。
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- JoinIR / ループ周辺の `eprintln!` が、意味あるかたちで `JoinLoopTrace` 経由に置き換わっている。
|
||||
- `NYASH_TRACE_VARMAP=1` や `NYASH_JOINIR_DEBUG=1` の挙動が `logging_policy.md` に説明されている。
|
||||
- デフォルト(env 未設定)ではトレースは出ず、既存の代表テスト(Pattern 1〜4)はログ無しで PASS する。
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Phase 194 は **挙動を変えない** リファクタフェーズ(観測レイヤーの整形)として扱う。
|
||||
- Loop パターンや ExitBinding まわりは Phase 193/196/197 で安定しているので、それを壊さない形でログだけを寄せることが目的。
|
||||
|
||||
@ -1,627 +0,0 @@
|
||||
# Phase 195-impl: Pattern3 複数キャリア対応(実装フェーズ)
|
||||
|
||||
**Status**: Ready for Implementation
|
||||
**Date**: 2025-12-09
|
||||
**Prerequisite**: Phase 195 design complete (phase195-pattern3-extension-design.md)
|
||||
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
**Pattern 3(If-Else PHI)で複数キャリア + 条件付き更新**を実装する。
|
||||
|
||||
**スコープ**:
|
||||
- ✅ 複数キャリア(2-3個)の P3 処理
|
||||
- ✅ ExitLine 拡張で対応(PhiGroupBox は作らない)
|
||||
- ✅ if-sum パターン(sum + count)を通す
|
||||
- ⏸️ _parse_string 簡易版(escaped のみ)は余力で
|
||||
|
||||
---
|
||||
|
||||
## Task 195-impl-1: Pattern3 lowerer の multi-carrier 対応
|
||||
|
||||
### 目標
|
||||
Pattern3 lowerer を単一キャリア前提から**複数キャリア対応**に拡張する。
|
||||
|
||||
### 対象ファイル
|
||||
- `src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs`
|
||||
|
||||
### 現状の実装(Phase 170-189)
|
||||
|
||||
**単一キャリア処理**:
|
||||
```rust
|
||||
// 既存: 単一キャリアのみ
|
||||
fn lower_if_else_phi(
|
||||
if_node: &ASTNode,
|
||||
carrier_info: &CarrierInfo,
|
||||
// ...
|
||||
) -> Result<ExitMeta, String> {
|
||||
// 1. Then/Else で carrier 更新式を取得
|
||||
let carrier_name = &carrier_info.carriers[0]; // ← 単一キャリア前提
|
||||
let then_update = extract_update(&if_node.then_branch, carrier_name)?;
|
||||
let else_update = extract_update(&if_node.else_branch, carrier_name)?;
|
||||
|
||||
// 2. Then/Else ブロックで値を emit
|
||||
let then_value = emit_update(&then_update, ...)?;
|
||||
let else_value = emit_update(&else_update, ...)?;
|
||||
|
||||
// 3. Merge 点で PHI 生成
|
||||
let phi_result = emit_phi(merge_block, then_value, else_value)?;
|
||||
|
||||
// 4. ExitMeta に接続情報を載せる
|
||||
let exit_meta = ExitMeta {
|
||||
carrier_bindings: vec![(carrier_name.clone(), phi_result)],
|
||||
// ...
|
||||
};
|
||||
|
||||
Ok(exit_meta)
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 195-impl での拡張
|
||||
|
||||
**複数キャリア処理**:
|
||||
```rust
|
||||
// Phase 195: 複数キャリア対応
|
||||
fn lower_if_else_phi(
|
||||
if_node: &ASTNode,
|
||||
carrier_info: &CarrierInfo,
|
||||
// ...
|
||||
) -> Result<ExitMeta, String> {
|
||||
let mut carrier_bindings = Vec::new();
|
||||
|
||||
// 1. 全キャリアについてループ処理
|
||||
for carrier_name in &carrier_info.carriers {
|
||||
// 2. Then/Else で carrier 更新式を取得
|
||||
let then_update = extract_update_or_unchanged(
|
||||
&if_node.then_branch,
|
||||
carrier_name,
|
||||
&previous_values, // ← 更新なしの場合は前の値
|
||||
)?;
|
||||
let else_update = extract_update_or_unchanged(
|
||||
&if_node.else_branch,
|
||||
carrier_name,
|
||||
&previous_values,
|
||||
)?;
|
||||
|
||||
// 3. Then/Else ブロックで値を emit
|
||||
let then_value = emit_update(&then_update, ...)?;
|
||||
let else_value = emit_update(&else_update, ...)?;
|
||||
|
||||
// 4. Merge 点で PHI 生成(carrier ごとに1つ)
|
||||
let phi_result = emit_phi(merge_block, then_value, else_value)?;
|
||||
|
||||
// 5. ExitMeta 用に保存
|
||||
carrier_bindings.push((carrier_name.clone(), phi_result));
|
||||
}
|
||||
|
||||
// 6. ExitMeta に全キャリアの接続情報を載せる
|
||||
let exit_meta = ExitMeta {
|
||||
carrier_bindings,
|
||||
// ...
|
||||
};
|
||||
|
||||
Ok(exit_meta)
|
||||
}
|
||||
```
|
||||
|
||||
### `extract_update_or_unchanged` 関数の追加
|
||||
|
||||
**目的**: 片方のブランチで更新がない場合、「前の値」を使用する。
|
||||
|
||||
```rust
|
||||
fn extract_update_or_unchanged(
|
||||
branch: &[ASTNode],
|
||||
carrier_name: &str,
|
||||
previous_values: &HashMap<String, ValueId>,
|
||||
) -> Result<UpdateExpr, String> {
|
||||
// Then/Else ブランチで carrier の Assign を探す
|
||||
if let Some(assign) = find_assign(branch, carrier_name) {
|
||||
// 更新あり: Assign の RHS を返す
|
||||
Ok(UpdateExpr::FromAST(assign.rhs.clone()))
|
||||
} else {
|
||||
// 更新なし: 前の値(ループ header の PHI param)を使用
|
||||
let prev_value = previous_values.get(carrier_name)
|
||||
.ok_or_else(|| format!("Carrier '{}' not in previous values", carrier_name))?;
|
||||
Ok(UpdateExpr::Unchanged(*prev_value))
|
||||
}
|
||||
}
|
||||
|
||||
enum UpdateExpr {
|
||||
FromAST(Box<ASTNode>), // 更新式あり
|
||||
Unchanged(ValueId), // 更新なし(前の値使用)
|
||||
}
|
||||
```
|
||||
|
||||
### Fail-Fast 条件
|
||||
|
||||
**Phase 195-impl で弾くケース**:
|
||||
1. **片方のブランチのみで carrier 定義**(明示的な更新なしも不可):
|
||||
```nyash
|
||||
if(cond) {
|
||||
sum = sum + i
|
||||
// count は更新なし(NG: 明示的に count = count が必要)
|
||||
} else {
|
||||
// sum も count も更新なし(NG)
|
||||
}
|
||||
```
|
||||
→ エラー: "Carrier 'count' not updated in both branches"
|
||||
|
||||
2. **複雑なネスト**:
|
||||
```nyash
|
||||
if(cond1) {
|
||||
if(cond2) { sum = sum + i }
|
||||
}
|
||||
```
|
||||
→ エラー: "Nested if not supported in Phase 195"
|
||||
|
||||
### 実装手順
|
||||
|
||||
1. **`extract_update_or_unchanged` 関数追加** (~40 lines)
|
||||
2. **`lower_if_else_phi` をループ処理に変更** (~30 lines 修正)
|
||||
3. **`emit_update` で `UpdateExpr::Unchanged` 対応** (~10 lines)
|
||||
4. **Fail-Fast エラーメッセージ追加** (~5 lines)
|
||||
|
||||
### ユニットテスト追加
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_multi_carrier_if_else() {
|
||||
// CarrierInfo with 2 carriers: sum, count
|
||||
let carrier_info = CarrierInfo {
|
||||
carriers: vec!["sum".to_string(), "count".to_string()],
|
||||
updates: hashmap! {
|
||||
"sum" => ASTNode::BinOp { ... },
|
||||
"count" => ASTNode::BinOp { ... },
|
||||
},
|
||||
};
|
||||
|
||||
// If-Else AST with updates for both carriers
|
||||
let if_node = create_if_else_ast(/* ... */);
|
||||
|
||||
let result = lower_if_else_phi(&if_node, &carrier_info, ...);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let exit_meta = result.unwrap();
|
||||
assert_eq!(exit_meta.carrier_bindings.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unchanged_carrier_in_branch() {
|
||||
// If with update only in then branch
|
||||
let if_node = create_if_with_partial_update();
|
||||
|
||||
// Should handle unchanged carrier (use previous value)
|
||||
let result = lower_if_else_phi(&if_node, &carrier_info, ...);
|
||||
|
||||
assert!(result.is_ok());
|
||||
// Verify else branch uses previous value for unchanged carrier
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 195-impl-2: ExitLine 拡張で複数キャリア PHI を扱う
|
||||
|
||||
### 目標
|
||||
ExitLine 側で**複数 PHI を処理**する拡張を入れる。
|
||||
|
||||
### 対象ファイル
|
||||
- `src/mir/builder/control_flow/joinir/exit_line/meta_collector.rs`
|
||||
- `src/mir/builder/control_flow/joinir/exit_line/reconnector.rs`
|
||||
|
||||
### 現状の ExitLine(Phase 170-189)
|
||||
|
||||
**単一キャリア処理**:
|
||||
```rust
|
||||
// ExitMetaCollector
|
||||
pub fn collect_exit_meta(
|
||||
pattern_result: &PatternResult,
|
||||
) -> ExitMeta {
|
||||
ExitMeta {
|
||||
carrier_bindings: vec![
|
||||
(carrier_name, phi_dst) // ← 単一キャリア
|
||||
],
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
// ExitLineReconnector
|
||||
pub fn reconnect(
|
||||
exit_meta: &ExitMeta,
|
||||
variable_map: &mut HashMap<String, ValueId>,
|
||||
) {
|
||||
for (carrier_name, phi_dst) in &exit_meta.carrier_bindings {
|
||||
variable_map.insert(carrier_name.clone(), *phi_dst);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 195-impl での拡張
|
||||
|
||||
**既に multi-carrier インフラあり**(CarrierVar.join_id, carrier_order):
|
||||
```rust
|
||||
pub struct CarrierVar {
|
||||
pub name: String,
|
||||
pub join_id: ValueId, // ← JoinIR 空間での ValueId
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**実装内容**:
|
||||
1. **ExitMetaCollector**: 既に複数 carrier 対応(変更不要の可能性高い)
|
||||
2. **ExitLineReconnector**: ループで全 carrier を処理(既存コードを確認)
|
||||
|
||||
### 実装確認手順
|
||||
|
||||
1. **ExitMetaCollector を確認**:
|
||||
```rust
|
||||
// 既に複数 carrier_bindings を扱えているか確認
|
||||
pub fn collect_exit_meta(
|
||||
pattern_result: &PatternResult,
|
||||
) -> ExitMeta {
|
||||
let mut carrier_bindings = Vec::new();
|
||||
|
||||
// Pattern3 から渡ってくる全 carrier について
|
||||
for carrier_var in &pattern_result.carriers {
|
||||
carrier_bindings.push((
|
||||
carrier_var.name.clone(),
|
||||
carrier_var.join_id,
|
||||
));
|
||||
}
|
||||
|
||||
ExitMeta { carrier_bindings, ... }
|
||||
}
|
||||
```
|
||||
|
||||
2. **ExitLineReconnector を確認**:
|
||||
```rust
|
||||
// 既にループで全 carrier を処理しているか確認
|
||||
pub fn reconnect(
|
||||
exit_meta: &ExitMeta,
|
||||
variable_map: &mut HashMap<String, ValueId>,
|
||||
) {
|
||||
for (carrier_name, phi_dst) in &exit_meta.carrier_bindings {
|
||||
// variable_map で carrier.name -> phi_dst を更新
|
||||
variable_map.insert(carrier_name.clone(), *phi_dst);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**想定**: 既存コードが既に複数対応している可能性が高い(Phase 170-189 の設計が良好)
|
||||
|
||||
**修正が必要な場合**: ループ処理を追加するだけ(~5 lines)
|
||||
|
||||
### ユニットテスト追加
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_exit_line_multi_carrier() {
|
||||
let exit_meta = ExitMeta {
|
||||
carrier_bindings: vec![
|
||||
("sum".to_string(), ValueId(10)),
|
||||
("count".to_string(), ValueId(11)),
|
||||
],
|
||||
// ...
|
||||
};
|
||||
|
||||
let mut variable_map = HashMap::new();
|
||||
reconnect(&exit_meta, &mut variable_map);
|
||||
|
||||
assert_eq!(variable_map.get("sum"), Some(&ValueId(10)));
|
||||
assert_eq!(variable_map.get("count"), Some(&ValueId(11)));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 195-impl-3: if-sum テスト(sum + count)で確認
|
||||
|
||||
### 目標
|
||||
複数キャリア P3 が**実際に動作する**ことを E2E テストで確認。
|
||||
|
||||
### テストファイル
|
||||
|
||||
**ファイル**: `apps/tests/phase195_sum_count.hako`
|
||||
|
||||
```nyash
|
||||
static box Main {
|
||||
main() {
|
||||
local sum = 0
|
||||
local count = 0
|
||||
local i = 0
|
||||
local len = 5
|
||||
|
||||
loop(i < len) {
|
||||
if(i > 2) {
|
||||
sum = sum + i // i=3,4 で加算
|
||||
count = count + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// Expected: sum=7 (3+4), count=2
|
||||
local result = sum * 10 + count // 72
|
||||
print(result)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 実行手順
|
||||
|
||||
#### 1. ビルド
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
#### 2. E2E 実行
|
||||
```bash
|
||||
# JoinIR 経路で実行
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase195_sum_count.hako
|
||||
|
||||
# Expected output: 72
|
||||
```
|
||||
|
||||
#### 3. Trace 確認
|
||||
```bash
|
||||
# JoinIR debug trace 有効化
|
||||
NYASH_JOINIR_CORE=1 NYASH_JOINIR_DEBUG=1 ./target/release/hakorune apps/tests/phase195_sum_count.hako 2>&1 | grep "\[trace:joinir\]"
|
||||
|
||||
# Expected output (example):
|
||||
# [trace:joinir] Pattern 3 applied: if-else with 2 carriers
|
||||
# [trace:joinir] Carrier 'sum': PHI(%10, %11) -> %12
|
||||
# [trace:joinir] Carrier 'count': PHI(%20, %21) -> %22
|
||||
|
||||
# CRITICAL: [joinir/freeze] が出ないこと
|
||||
```
|
||||
|
||||
#### 4. 退行確認
|
||||
```bash
|
||||
# 既存の単一キャリア P3 テスト
|
||||
./target/release/hakorune apps/tests/loop_if_phi.hako
|
||||
# Expected: 既存の期待値
|
||||
|
||||
# Phase 190-194 テスト
|
||||
./target/release/hakorune apps/tests/phase190_atoi_impl.hako
|
||||
# Expected: 12
|
||||
|
||||
./target/release/hakorune apps/tests/phase191_body_local_atoi.hako
|
||||
# Expected: 123
|
||||
|
||||
./target/release/hakorune apps/tests/phase193_init_method_call.hako
|
||||
# Expected: RC:0
|
||||
```
|
||||
|
||||
### 成功基準
|
||||
|
||||
- [ ] phase195_sum_count.hako が 72 を出力
|
||||
- [ ] [joinir/freeze] が出ない(JoinIR 経路で動作)
|
||||
- [ ] 既存テストが退行しない
|
||||
|
||||
---
|
||||
|
||||
## Task 195-impl-4: _parse_string 簡易版(escaped のみ)余力で
|
||||
|
||||
### 目標(オプション)
|
||||
_parse_string の escaped フラグを P3 で扱う(buffer 連結は後回し)。
|
||||
|
||||
### テストファイル
|
||||
|
||||
**ファイル**: `apps/tests/phase195_flag_buffer.hako`
|
||||
|
||||
```nyash
|
||||
static box Main {
|
||||
main() {
|
||||
local escaped = false
|
||||
local i = 0
|
||||
local len = 3
|
||||
|
||||
loop(i < len) {
|
||||
if(i == 1) {
|
||||
escaped = true
|
||||
} else {
|
||||
escaped = false
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// Expected: escaped=false (最後に i=2 で false 代入)
|
||||
local result = 0
|
||||
if(escaped) {
|
||||
result = 1
|
||||
}
|
||||
print(result) // Expected: 0
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 実行手順
|
||||
|
||||
```bash
|
||||
# JoinIR 経路で実行
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase195_flag_buffer.hako
|
||||
|
||||
# Expected output: 0
|
||||
```
|
||||
|
||||
### 成功基準(余力で)
|
||||
|
||||
- [ ] phase195_flag_buffer.hako が 0 を出力
|
||||
- [ ] P3 で BoolFlag(escaped)が動作
|
||||
- [ ] buffer 連結は Phase 19x 後半で対応(今回は対象外)
|
||||
|
||||
---
|
||||
|
||||
## Task 195-impl-5: ドキュメント更新
|
||||
|
||||
### 1. phase195-pattern3-extension-design.md 更新
|
||||
|
||||
末尾に "Implementation Status" セクション追加:
|
||||
|
||||
```markdown
|
||||
## Implementation Status
|
||||
|
||||
**完了日**: 2025-12-XX
|
||||
|
||||
### 実装サマリ
|
||||
|
||||
**対応パターン**:
|
||||
- [x] 複数キャリア P3 処理(2-3個)
|
||||
- [x] if-sum パターン(sum + count) - phase195_sum_count.hako
|
||||
- [ ] _parse_string 簡易版(escaped のみ)- phase195_flag_buffer.hako(オプション)
|
||||
|
||||
### 実装内容
|
||||
|
||||
**ファイル変更**:
|
||||
1. `pattern3_with_if_phi.rs` (+60 lines)
|
||||
- `extract_update_or_unchanged` 関数追加
|
||||
- `lower_if_else_phi` を複数キャリア対応に拡張
|
||||
- Fail-Fast 条件追加
|
||||
|
||||
2. `exit_line/meta_collector.rs`, `exit_line/reconnector.rs` (確認のみ)
|
||||
- 既に複数 carrier_bindings 対応済み(変更不要)
|
||||
|
||||
### E2E テスト結果
|
||||
|
||||
**phase195_sum_count.hako**:
|
||||
```bash
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase195_sum_count.hako
|
||||
# Output: 72 ✅
|
||||
```
|
||||
|
||||
**退行テスト**:
|
||||
- loop_if_phi.hako: PASS ✅
|
||||
- phase190-194 tests: ALL PASS ✅
|
||||
|
||||
### 技術的発見
|
||||
|
||||
1. **ExitLine は既に multi-carrier 対応**:
|
||||
- CarrierVar.join_id インフラが Phase 170-189 で整備済み
|
||||
- 新規実装不要、Pattern3 側の拡張のみで動作
|
||||
|
||||
2. **Unchanged carrier の扱い**:
|
||||
- 片方のブランチで更新なし → 前の値(ループ header PHI param)を使用
|
||||
- `UpdateExpr::Unchanged(ValueId)` で表現
|
||||
|
||||
3. **Fail-Fast 効果**:
|
||||
- 両ブランチで carrier 定義必須(明示的エラー)
|
||||
- ネストした if は Phase 196+ に延期
|
||||
|
||||
### 制限事項(Phase 195-impl)
|
||||
|
||||
- ❌ ネストした if: `if(c1) { if(c2) { ... } }`
|
||||
- ❌ 3個以上の carrier(設計は対応済みだが、テスト未実施)
|
||||
- ❌ LoopBodyLocal + MethodCall 混在は Phase 195+ 延期
|
||||
|
||||
### 次のステップ
|
||||
|
||||
**Phase 196+**: 候補
|
||||
- Pattern 3 のネスト対応
|
||||
- 3個以上の carrier での動作検証
|
||||
- _parse_string 完全版(buffer 連結 + escaped flag 統合)
|
||||
|
||||
**Phase 200+**: ConditionEnv 拡張
|
||||
- function-scoped variables サポート
|
||||
- _parse_number, _atoi が動作可能に
|
||||
```
|
||||
|
||||
### 2. CURRENT_TASK.md 更新
|
||||
|
||||
```markdown
|
||||
## Phase 195-impl: Pattern3 複数キャリア対応(完了: 2025-12-XX)
|
||||
|
||||
**目的**: P3(If-Else PHI)で複数キャリア + 条件付き更新を実装
|
||||
|
||||
**実装内容**:
|
||||
- ✅ Pattern3 lowerer 拡張(複数キャリアループ処理)
|
||||
- ✅ ExitLine 確認(既に multi-carrier 対応済み)
|
||||
- ✅ if-sum テスト成功(sum + count) - phase195_sum_count.hako → 72 ✅
|
||||
- ⏸️ _parse_string 簡易版(escaped のみ)- 余力により実施
|
||||
|
||||
**成果**:
|
||||
- P3 が「単一キャリア専用」から「複数キャリア対応」に昇格
|
||||
- JsonParser/selfhost で if-in-loop パターンを拡張可能に
|
||||
- 既存テスト退行なし
|
||||
|
||||
**技術的発見**:
|
||||
- ExitLine は既に multi-carrier 対応(CarrierVar.join_id インフラ活用)
|
||||
- Unchanged carrier は前の値(ループ header PHI param)使用
|
||||
- Fail-Fast で複雑な分岐を明示的に弾く
|
||||
|
||||
**次のステップ**: Phase 196+(Pattern 3 ネスト対応)or Phase 200+(ConditionEnv 拡張)
|
||||
```
|
||||
|
||||
### 3. joinir-architecture-overview.md 更新
|
||||
|
||||
Section 7.2 の Phase 195 を完了マークに更新:
|
||||
|
||||
```markdown
|
||||
- [x] **Phase 195**: Pattern 3 拡張(複数キャリア対応)
|
||||
- P3 で 2-3 個の Carrier を同時処理可能に
|
||||
- ExitLine 拡張で複数 PHI 生成(既存インフラ活用)
|
||||
- if-sum パターン(sum+count)動作確認完了
|
||||
- JsonParser カバレッジ向上への基盤完成
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 成功基準
|
||||
|
||||
- [x] Pattern3 lowerer が複数キャリア対応(extract_update_or_unchanged 実装)
|
||||
- [x] ExitLine が複数 PHI を処理(既存確認 or 拡張)
|
||||
- [x] phase195_sum_count.hako が期待値(72)を出力
|
||||
- [x] [joinir/freeze] が出ない(JoinIR 経路で動作)
|
||||
- [x] 既存テスト(phase190-194, loop_if_phi)が退行しない
|
||||
- [x] ドキュメント更新(Implementation Status, CURRENT_TASK, overview)
|
||||
|
||||
---
|
||||
|
||||
## 関連ファイル
|
||||
|
||||
### 実装対象
|
||||
- `src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs`(主要実装)
|
||||
- `src/mir/builder/control_flow/joinir/exit_line/meta_collector.rs`(確認のみ)
|
||||
- `src/mir/builder/control_flow/joinir/exit_line/reconnector.rs`(確認のみ)
|
||||
|
||||
### テストファイル
|
||||
- `apps/tests/phase195_sum_count.hako`(新規作成・必須)
|
||||
- `apps/tests/phase195_flag_buffer.hako`(新規作成・オプション)
|
||||
- `apps/tests/loop_if_phi.hako`(退行確認)
|
||||
|
||||
### ドキュメント
|
||||
- `docs/development/current/main/phase195-pattern3-extension-design.md`(Implementation Status 追加)
|
||||
- `docs/development/current/main/joinir-architecture-overview.md`(Phase 195 完了マーク)
|
||||
- `CURRENT_TASK.md`(Phase 195-impl 完了記録)
|
||||
|
||||
---
|
||||
|
||||
## 設計原則(Phase 195-impl)
|
||||
|
||||
1. **既存インフラ活用**:
|
||||
- ExitLine の CarrierVar.join_id を再利用
|
||||
- 新規箱なし(PhiGroupBox は作らない)
|
||||
|
||||
2. **段階的実装**:
|
||||
- まず if-sum で基盤確認
|
||||
- 次に _parse_string 簡易版(余力で)
|
||||
|
||||
3. **Fail-Fast 継承**:
|
||||
- 両ブランチで carrier 定義必須
|
||||
- 複雑な分岐は明示的に弾く
|
||||
|
||||
4. **箱理論の実践**:
|
||||
- 設計書に基づく実装
|
||||
- 単一責任の原則維持
|
||||
- ドキュメント駆動開発
|
||||
@ -1,694 +0,0 @@
|
||||
# Phase 195: Pattern 3 拡張(if-in-loop + マルチキャリア)
|
||||
|
||||
**Status**: Design Phase
|
||||
**Date**: 2025-12-09
|
||||
**Prerequisite**: Phase 194 complete (JsonParser deployment & validation)
|
||||
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
**Pattern 3(If-Else PHI)の対応範囲を拡張**し、複数キャリアの条件付き更新を JoinIR で扱えるようにする。
|
||||
|
||||
**スコープ**:
|
||||
- ✅ if 内で完結する複数キャリア更新(2-3個程度)
|
||||
- ❌ ConditionEnv 拡張は行わない(外部ローカル・digits 系は Phase 200+ 保留)
|
||||
- ❌ LoopBodyLocal + MethodCall 混在は Phase 195+ に延期
|
||||
|
||||
---
|
||||
|
||||
## Task 195-1: 対象ループの絞り込み(doc-only)
|
||||
|
||||
### 目標
|
||||
JsonParser / selfhost から「P3 で攻めたいループ」を **1-2 本だけ**選定し、AST 構造を詳細に分析する。
|
||||
|
||||
### 候補ループ
|
||||
|
||||
#### 候補 1: JsonParser `_parse_string` の簡易版(優先度: 高)
|
||||
|
||||
**ループ構造**:
|
||||
```nyash
|
||||
// _parse_string (escape 処理の簡略版)
|
||||
static box JsonParser {
|
||||
_parse_string(s, start) {
|
||||
local buffer = ""
|
||||
local escaped = false
|
||||
local i = start
|
||||
|
||||
loop(i < s.length() and s[i] != '"') {
|
||||
local ch = s[i]
|
||||
|
||||
if(ch == '\\') {
|
||||
escaped = true // ← Carrier 1 update
|
||||
} else {
|
||||
buffer = buffer + ch // ← Carrier 2 update
|
||||
escaped = false // ← Carrier 1 update
|
||||
}
|
||||
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**AST 構造**:
|
||||
```
|
||||
Loop {
|
||||
condition: BinOp(i < len AND s[i] != '"')
|
||||
body: [
|
||||
LocalVar { name: "ch", init: ArrayAccess(s, i) }, // body-local
|
||||
If {
|
||||
condition: Compare(ch == '\\'),
|
||||
then: [
|
||||
Assign { lhs: "escaped", rhs: BoolLiteral(true) }
|
||||
],
|
||||
else: [
|
||||
Assign { lhs: "buffer", rhs: BinOp(buffer + ch) },
|
||||
Assign { lhs: "escaped", rhs: BoolLiteral(false) }
|
||||
]
|
||||
},
|
||||
Assign { lhs: "i", rhs: BinOp(i + 1) }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**キャリア分析**:
|
||||
| Carrier | Type | Update Pattern | Then Branch | Else Branch |
|
||||
|---------|------|----------------|-------------|-------------|
|
||||
| `escaped` | BoolBox | Conditional flag | `true` | `false` |
|
||||
| `buffer` | StringBox | StringAppend | (unchanged) | `buffer + ch` |
|
||||
| `i` | IntegerBox | CounterLike | `i + 1` | `i + 1` |
|
||||
|
||||
**P3 で扱う範囲**:
|
||||
- ✅ `escaped`: 条件フラグ(両分岐で定義)
|
||||
- ✅ `buffer`: StringAppend(else のみ更新、then は不変)
|
||||
- ✅ `i`: ループ外で統一更新(P3 の外で処理)
|
||||
|
||||
**制約**:
|
||||
- `ch` は body-local 変数(Phase 191 で対応済み)
|
||||
- `s[i]` の配列アクセスは ConditionEnv で解決(`s` は outer local → Phase 200+)
|
||||
- **この Phase では `ch = "x"` のような定数で代替してテスト**
|
||||
|
||||
#### 候補 2: selfhost の if-sum パターン(優先度: 中)
|
||||
|
||||
**ループ構造**:
|
||||
```nyash
|
||||
// selfhost: 条件付き集計
|
||||
static box Aggregator {
|
||||
sum_positive(array) {
|
||||
local sum = 0
|
||||
local count = 0
|
||||
local i = 0
|
||||
|
||||
loop(i < array.length()) {
|
||||
if(array[i] > 0) {
|
||||
sum = sum + array[i] // ← Carrier 1 update
|
||||
count = count + 1 // ← Carrier 2 update
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
return sum
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**AST 構造**:
|
||||
```
|
||||
Loop {
|
||||
condition: Compare(i < array.length())
|
||||
body: [
|
||||
If {
|
||||
condition: Compare(array[i] > 0),
|
||||
then: [
|
||||
Assign { lhs: "sum", rhs: BinOp(sum + array[i]) },
|
||||
Assign { lhs: "count", rhs: BinOp(count + 1) }
|
||||
],
|
||||
else: [] // 空(更新なし)
|
||||
},
|
||||
Assign { lhs: "i", rhs: BinOp(i + 1) }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**キャリア分析**:
|
||||
| Carrier | Type | Update Pattern | Then Branch | Else Branch |
|
||||
|---------|------|----------------|-------------|-------------|
|
||||
| `sum` | IntegerBox | NumberAccumulation | `sum + array[i]` | (unchanged) |
|
||||
| `count` | IntegerBox | CounterLike | `count + 1` | (unchanged) |
|
||||
| `i` | IntegerBox | CounterLike | `i + 1` | `i + 1` |
|
||||
|
||||
**P3 で扱う範囲**:
|
||||
- ✅ `sum`: NumberAccumulation(then のみ更新)
|
||||
- ✅ `count`: CounterLike(then のみ更新)
|
||||
- ✅ `i`: ループ外で統一更新
|
||||
|
||||
**制約**:
|
||||
- `array[i]` の配列アクセスは ConditionEnv で解決(`array` は outer local → Phase 200+)
|
||||
- **この Phase では `i` のような既存パラメータで代替してテスト**
|
||||
|
||||
### Phase 195 での選定
|
||||
|
||||
**優先順位 1**: 候補 1(_parse_string 簡易版)
|
||||
- 理由: JsonParser の実戦コード、flag + buffer の2キャリア
|
||||
- 簡略化: `ch = "x"` 定数で配列アクセス回避
|
||||
|
||||
**優先順位 2**: 候補 2(if-sum)
|
||||
- 理由: selfhost の典型パターン、sum + count の2キャリア
|
||||
- 簡略化: `i` のみで配列アクセス回避
|
||||
|
||||
**成果物**:
|
||||
- 選定したループの詳細 AST 構造記録(本セクション)
|
||||
- キャリア分析表(UpdateKind 分類済み)
|
||||
|
||||
---
|
||||
|
||||
## Task 195-2: LoopUpdateSummary / CarrierInfo の設計整理
|
||||
|
||||
### 現状の P3 サポート(Phase 170-189)
|
||||
|
||||
**既存の P3 は単一キャリアのみ対応**:
|
||||
```rust
|
||||
// 既存の P3 ケース
|
||||
if(cond) {
|
||||
sum = sum + i // ← 単一キャリア "sum"
|
||||
} else {
|
||||
sum = sum - i
|
||||
}
|
||||
```
|
||||
|
||||
**LoopUpdateSummary / CarrierInfo の構造**:
|
||||
```rust
|
||||
pub struct CarrierInfo {
|
||||
pub carriers: Vec<String>, // キャリア名リスト
|
||||
pub updates: HashMap<String, ASTNode>, // name → update式
|
||||
}
|
||||
|
||||
pub struct LoopUpdateSummary {
|
||||
pub kind: UpdateKind, // CounterLike | NumberAccumulation | ...
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 195 で扱う「複数キャリア + 条件付き更新」
|
||||
|
||||
**拡張要件**:
|
||||
```rust
|
||||
// Phase 195 の P3 ケース
|
||||
if(cond) {
|
||||
escaped = true // ← Carrier 1
|
||||
// buffer は更新なし(不変)
|
||||
} else {
|
||||
buffer = buffer + ch // ← Carrier 2
|
||||
escaped = false // ← Carrier 1
|
||||
}
|
||||
```
|
||||
|
||||
**設計原則**:
|
||||
1. **両分岐で同じ Carrier が必ず定義**されること(PHI 生成の前提)
|
||||
- 片方の分岐で更新なし(不変)の場合、明示的に `carrier = carrier` を挿入
|
||||
- OR: PHI 生成時に「更新なし = 前の値を使う」として扱う
|
||||
|
||||
2. **各 Carrier の update 式は既存 UpdateKind 範囲内**:
|
||||
- CounterLike: `count + 1`, `count - 1`
|
||||
- NumberAccumulation: `sum + i`, `sum * base + addend`
|
||||
- StringAppend: `buffer + ch`
|
||||
- BoolFlag: `true`, `false`(新規 UpdateKind 候補)
|
||||
|
||||
3. **CarrierInfo は複数 Carrier を同時に保持**:
|
||||
```rust
|
||||
CarrierInfo {
|
||||
carriers: vec!["escaped", "buffer"],
|
||||
updates: {
|
||||
"escaped": ..., // then/else で異なる式
|
||||
"buffer": ...,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 制約の明確化
|
||||
|
||||
**P3 で扱う Carrier の制約** (Phase 195):
|
||||
- ✅ if-else の**両分岐で同じ Carrier が定義**される(明示的 or 不変)
|
||||
- ✅ 各 update 式は**既存 UpdateKind に対応**する
|
||||
- ❌ MethodCall を含む update は Phase 193 の制約に従う(ループパラメータのみ)
|
||||
- ❌ 外部ローカル変数(`digits` 等)は Phase 200+ に保留
|
||||
|
||||
**例(OK)**:
|
||||
```nyash
|
||||
if(ch == '\\') {
|
||||
escaped = true // ✅ BoolFlag
|
||||
} else {
|
||||
buffer = buffer + ch // ✅ StringAppend
|
||||
escaped = false // ✅ BoolFlag
|
||||
}
|
||||
```
|
||||
|
||||
**例(NG - Phase 195 範囲外)**:
|
||||
```nyash
|
||||
if(cond) {
|
||||
digit = digits.indexOf(ch) // ❌ 外部ローカル (Phase 200+)
|
||||
sum = sum + digit
|
||||
}
|
||||
```
|
||||
|
||||
### 成果物
|
||||
- 複数キャリア対応の設計原則(本セクション)
|
||||
- UpdateKind 拡張候補(BoolFlag)の検討
|
||||
- CarrierInfo 構造の拡張仕様(擬似コード)
|
||||
|
||||
---
|
||||
|
||||
## Task 195-3: Pattern3 lowerer の設計更新
|
||||
|
||||
### 現状の P3 lowerer(Phase 170-189)
|
||||
|
||||
**単一キャリアの処理フロー**:
|
||||
```
|
||||
1. If-Else AST を検出
|
||||
2. Then/Else 各分岐で carrier 更新式を抽出
|
||||
3. JoinIR で Then/Else ブロック生成
|
||||
4. Merge 点で PHI 命令生成(1つの carrier のみ)
|
||||
5. ExitLine で PHI 結果を variable_map に接続
|
||||
```
|
||||
|
||||
### Phase 195 での拡張設計
|
||||
|
||||
#### 1. 複数 Carrier の同時処理
|
||||
|
||||
**設計案**: 複数 PHI を同じ Merge 点で生成
|
||||
|
||||
```rust
|
||||
// 擬似コード: Pattern3 lowerer 拡張
|
||||
|
||||
// Step 1: If-Else で更新される全 Carrier を収集
|
||||
let carriers_in_then = extract_carriers(&if_node.then_branch);
|
||||
let carriers_in_else = extract_carriers(&if_node.else_branch);
|
||||
let all_carriers = carriers_in_then.union(&carriers_in_else);
|
||||
|
||||
// Step 2: 各 Carrier について Then/Else の update 式を取得
|
||||
let mut carrier_updates = HashMap::new();
|
||||
for carrier in all_carriers {
|
||||
let then_update = get_update_or_unchanged(&if_node.then_branch, carrier);
|
||||
let else_update = get_update_or_unchanged(&if_node.else_branch, carrier);
|
||||
carrier_updates.insert(carrier, (then_update, else_update));
|
||||
}
|
||||
|
||||
// Step 3: JoinIR で Then/Else ブロック生成(複数 update を emit)
|
||||
let then_block = emit_then_branch(&carrier_updates);
|
||||
let else_block = emit_else_branch(&carrier_updates);
|
||||
|
||||
// Step 4: Merge 点で複数 PHI 生成
|
||||
let merge_block = create_merge_block();
|
||||
for (carrier, (then_val, else_val)) in carrier_updates {
|
||||
let phi_result = emit_phi(merge_block, then_val, else_val);
|
||||
// ExitLine で variable_map に接続
|
||||
exit_line.connect(carrier, phi_result);
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. PhiGroupBox vs ExitLine/CarrierInfo 拡張
|
||||
|
||||
**Option A: PhiGroupBox(新規箱)**
|
||||
```rust
|
||||
pub struct PhiGroupBox {
|
||||
pub phis: Vec<PhiInfo>, // 複数 PHI の束
|
||||
}
|
||||
|
||||
pub struct PhiInfo {
|
||||
pub carrier_name: String,
|
||||
pub then_value: ValueId,
|
||||
pub else_value: ValueId,
|
||||
pub result: ValueId,
|
||||
}
|
||||
```
|
||||
|
||||
**メリット**:
|
||||
- 複数 PHI の関係性を明示的に管理
|
||||
- 単一責任の原則(PHI グループ専用)
|
||||
|
||||
**デメリット**:
|
||||
- 新規箱の追加(複雑度増加)
|
||||
- 既存の ExitLine との統合が必要
|
||||
|
||||
**Option B: ExitLine/CarrierInfo 拡張(既存箱再利用)**
|
||||
```rust
|
||||
// 既存の ExitLine を拡張
|
||||
pub struct ExitLine {
|
||||
pub phi_connections: HashMap<String, ValueId>, // carrier → PHI result
|
||||
}
|
||||
|
||||
// CarrierInfo は既に複数 Carrier 対応
|
||||
pub struct CarrierInfo {
|
||||
pub carriers: Vec<String>,
|
||||
pub updates: HashMap<String, ASTNode>,
|
||||
}
|
||||
```
|
||||
|
||||
**メリット**:
|
||||
- 新規箱なし(既存インフラ再利用)
|
||||
- ExitLine が既に複数 Carrier 接続をサポート
|
||||
|
||||
**デメリット**:
|
||||
- ExitLine の責務が拡大
|
||||
|
||||
**Phase 195 での判断**:
|
||||
→ **Option B(既存箱拡張)を採用**
|
||||
|
||||
**理由**:
|
||||
- ExitLine は既に「variable_map への接続」を担当
|
||||
- 複数 Carrier → 複数 PHI は自然な拡張
|
||||
- 新規箱を作る必要性が低い(YAGNI 原則)
|
||||
|
||||
#### 3. 更新なし(不変)の扱い
|
||||
|
||||
**ケース**: 片方の分岐で Carrier が更新されない
|
||||
|
||||
```nyash
|
||||
if(ch == '\\') {
|
||||
escaped = true
|
||||
// buffer は更新なし(不変)
|
||||
} else {
|
||||
buffer = buffer + ch
|
||||
escaped = false
|
||||
}
|
||||
```
|
||||
|
||||
**設計案**: PHI 生成時に「前の値」を使用
|
||||
|
||||
```rust
|
||||
// Then 分岐で buffer 更新なし
|
||||
let then_buffer_value = previous_buffer_value; // ← ループ header の PHI param
|
||||
|
||||
// Else 分岐で buffer 更新あり
|
||||
let else_buffer_value = emit_string_append(...);
|
||||
|
||||
// Merge 点で PHI
|
||||
let buffer_phi = emit_phi(merge, then_buffer_value, else_buffer_value);
|
||||
```
|
||||
|
||||
**実装詳細**:
|
||||
- `get_update_or_unchanged()` 関数で検出
|
||||
- 更新なし → `ValueId` として「前の値」を返す
|
||||
- PHI 生成時に自動的に接続
|
||||
|
||||
### 成果物
|
||||
- Pattern3 lowerer の擬似コード(本セクション)
|
||||
- PhiGroupBox vs ExitLine 拡張の判断(Option B 採用)
|
||||
- 更新なし(不変)の扱い方設計
|
||||
|
||||
---
|
||||
|
||||
## Task 195-4: 実装スコープの決定(どこまでやるか)
|
||||
|
||||
### Phase 195-impl の範囲
|
||||
|
||||
**✅ Phase 195-impl で実装する**:
|
||||
1. **複数 Carrier の P3 処理**(2-3個程度)
|
||||
- `escaped` + `buffer` のような flag + accumulation
|
||||
- `sum` + `count` のような accumulation + counter
|
||||
|
||||
2. **既存 UpdateKind 範囲内の update**:
|
||||
- CounterLike: `count + 1`
|
||||
- NumberAccumulation: `sum + i`
|
||||
- StringAppend: `buffer + ch`
|
||||
- BoolFlag: `true`, `false`(新規 UpdateKind 追加候補)
|
||||
|
||||
3. **両分岐での定義確認**:
|
||||
- Then/Else で同じ Carrier が定義されるケース
|
||||
- 更新なし(不変)の場合は自動的に「前の値」を使用
|
||||
|
||||
4. **E2E テスト**:
|
||||
- 簡易版 _parse_string(`ch = "x"` 定数版)
|
||||
- 簡易版 if-sum(配列アクセスなし版)
|
||||
|
||||
**❌ Phase 195-impl で実装しない**:
|
||||
1. **LoopBodyLocal + MethodCall 混在**:
|
||||
- `local ch = s[i]; if(...) { buf += ch }`
|
||||
- → Phase 191(body-local init)と Phase 193(MethodCall)の組み合わせ
|
||||
- → Phase 195+ に延期(複雑度高い)
|
||||
|
||||
2. **外部ローカル変数(ConditionEnv 拡張)**:
|
||||
- `local digits = "012..."; digit = digits.indexOf(ch)`
|
||||
- → Phase 200+ に保留(設計判断済み)
|
||||
|
||||
3. **ネストした If**:
|
||||
- `if(...) { if(...) { ... } }`
|
||||
- → Phase 196+ に延期(P3 の P3)
|
||||
|
||||
### ゴールの明確化
|
||||
|
||||
**Phase 195-impl のゴール**:
|
||||
> 「Named carrier が 2-3 個あっても P3 lowerer が壊れない」こと。
|
||||
>
|
||||
> JsonParser/simple selfhost で「flag + count」くらいの例が通ること。
|
||||
|
||||
**成功基準**:
|
||||
- [ ] 簡易版 _parse_string が JoinIR で動作(escaped + buffer)
|
||||
- [ ] 簡易版 if-sum が JoinIR で動作(sum + count)
|
||||
- [ ] 既存テスト(phase190-194)が退行しない
|
||||
- [ ] ドキュメント更新(Implementation Status セクション追加)
|
||||
|
||||
### テストケース設計
|
||||
|
||||
#### テスト 1: flag + buffer (BoolFlag + StringAppend)
|
||||
|
||||
**ファイル**: `apps/tests/phase195_flag_buffer.hako`
|
||||
|
||||
```nyash
|
||||
static box Main {
|
||||
main() {
|
||||
local buffer = ""
|
||||
local escaped = false
|
||||
local i = 0
|
||||
|
||||
loop(i < 3) {
|
||||
local ch = "a" // ← 定数(配列アクセス回避)
|
||||
|
||||
if(i == 1) {
|
||||
escaped = true
|
||||
} else {
|
||||
buffer = buffer + ch
|
||||
escaped = false
|
||||
}
|
||||
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
print(buffer) // Expected: "aa" (i=0,2 で追加)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### テスト 2: sum + count (NumberAccumulation + CounterLike)
|
||||
|
||||
**ファイル**: `apps/tests/phase195_sum_count.hako`
|
||||
|
||||
```nyash
|
||||
static box Main {
|
||||
main() {
|
||||
local sum = 0
|
||||
local count = 0
|
||||
local i = 0
|
||||
|
||||
loop(i < 5) {
|
||||
if(i > 2) {
|
||||
sum = sum + i // i=3,4 で加算
|
||||
count = count + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
print(sum) // Expected: 7 (3+4)
|
||||
print(count) // Expected: 2
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 195-5: CURRENT_TASK / overview 更新
|
||||
|
||||
### CURRENT_TASK.md 更新内容
|
||||
|
||||
```markdown
|
||||
## Phase 195: Pattern 3 拡張(設計フェーズ)(完了予定: 2025-12-XX)
|
||||
|
||||
**目的**: P3(If-Else PHI)を複数キャリア対応に拡張する設計
|
||||
|
||||
**タスク**:
|
||||
- [ ] 195-1: 対象ループ絞り込み(_parse_string 簡易版、if-sum)
|
||||
- [ ] 195-2: LoopUpdateSummary/CarrierInfo 設計整理(複数キャリア対応)
|
||||
- [ ] 195-3: Pattern3 lowerer 設計更新(PhiGroup vs ExitLine 拡張判断)
|
||||
- [ ] 195-4: 実装スコープ決定(2-3キャリア、既存 UpdateKind 範囲内)
|
||||
- [ ] 195-5: ドキュメント更新(本項目 + overview 更新)
|
||||
|
||||
**設計判断**:
|
||||
- ✅ ExitLine 拡張で対応(PhiGroupBox は作らない)
|
||||
- ✅ 両分岐での Carrier 定義確認(更新なし = 前の値使用)
|
||||
- ❌ ConditionEnv 拡張なし(Phase 200+ 保留)
|
||||
- ❌ LoopBodyLocal + MethodCall 混在は Phase 195+ 延期
|
||||
|
||||
**期待成果**:
|
||||
- phase195-pattern3-extension-design.md(完全設計書)
|
||||
- Phase 195-impl の実装スコープ明確化
|
||||
- JsonParser カバレッジ 40% → 60% への道筋
|
||||
```
|
||||
|
||||
### joinir-architecture-overview.md 更新内容
|
||||
|
||||
Section 7.2 "残タスク" に追記:
|
||||
|
||||
```markdown
|
||||
- [ ] **Phase 195**: Pattern 3 拡張(複数キャリア対応)
|
||||
- 設計フェーズ: P3 で 2-3 個の Carrier を同時処理
|
||||
- ExitLine 拡張で複数 PHI 生成(PhiGroupBox は不要)
|
||||
- 対象: _parse_string(flag+buffer)、if-sum(sum+count)
|
||||
- Phase 195-impl で実装予定
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 成功基準(設計フェーズ)
|
||||
|
||||
- [x] 対象ループ選定完了(_parse_string 簡易版、if-sum)
|
||||
- [x] キャリア分析表作成(UpdateKind 分類済み)
|
||||
- [x] 複数キャリア対応の設計原則明確化
|
||||
- [x] Pattern3 lowerer の擬似コード作成
|
||||
- [x] PhiGroupBox vs ExitLine 拡張の判断(Option B 採用)
|
||||
- [x] 実装スコープ決定(Phase 195-impl 範囲明確化)
|
||||
- [x] テストケース設計(2ケース)
|
||||
- [x] ドキュメント更新計画作成
|
||||
|
||||
---
|
||||
|
||||
## 関連ファイル
|
||||
|
||||
### 設計対象
|
||||
- `src/mir/builder/control_flow/joinir/patterns/pattern3_with_if.rs`(Phase 195-impl で実装)
|
||||
- `src/mir/join_ir/lowering/loop_update_summary.rs`(BoolFlag UpdateKind 追加候補)
|
||||
- `src/mir/join_ir/lowering/carrier_info.rs`(複数 Carrier 対応確認)
|
||||
|
||||
### テストファイル(Phase 195-impl で作成)
|
||||
- `apps/tests/phase195_flag_buffer.hako`(BoolFlag + StringAppend)
|
||||
- `apps/tests/phase195_sum_count.hako`(NumberAccumulation + CounterLike)
|
||||
|
||||
### ドキュメント
|
||||
- `docs/development/current/main/phase195-pattern3-extension-design.md`(本ファイル)
|
||||
- `docs/development/current/main/joinir-architecture-overview.md`(更新予定)
|
||||
- `CURRENT_TASK.md`(Phase 195 設計タスク追加)
|
||||
|
||||
---
|
||||
|
||||
## 次のステップ
|
||||
|
||||
### Phase 195-impl: Pattern3 拡張実装
|
||||
|
||||
設計書(本ファイル)に基づいて実装:
|
||||
1. Pattern3 lowerer に複数 Carrier 処理追加
|
||||
2. ExitLine で複数 PHI 接続
|
||||
3. BoolFlag UpdateKind 追加(必要に応じて)
|
||||
4. E2E テスト(phase195_flag_buffer.hako, phase195_sum_count.hako)
|
||||
5. ドキュメント更新(Implementation Status セクション)
|
||||
|
||||
### Phase 196+: 候補
|
||||
|
||||
- Pattern 3 のネスト対応(if-in-if)
|
||||
- LoopBodyLocal + MethodCall + P3 の統合
|
||||
- JsonParser の _parse_string 完全版(配列アクセス対応 = Phase 200+)
|
||||
|
||||
---
|
||||
|
||||
## 設計原則(Phase 195)
|
||||
|
||||
1. **既存箱再利用**:
|
||||
- PhiGroupBox を作らず、ExitLine/CarrierInfo を拡張
|
||||
- YAGNI(You Aren't Gonna Need It)原則
|
||||
|
||||
2. **段階的拡張**:
|
||||
- 単一キャリア(Phase 170-189)→ 複数キャリア(Phase 195)
|
||||
- 2-3 個程度に限定(無理に全部対応しない)
|
||||
|
||||
3. **Fail-Fast 継承**:
|
||||
- ConditionEnv 拡張なし(Phase 200+ 保留)
|
||||
- 複雑なパターンは Phase 195+ に延期
|
||||
|
||||
4. **箱理論の実践**:
|
||||
- 設計フェーズで構造を固める
|
||||
- 実装フェーズは lowerer のみに集中
|
||||
- ドキュメント駆動開発
|
||||
|
||||
---
|
||||
|
||||
## Implementation Status
|
||||
|
||||
**完了日**: 2025-12-09
|
||||
**状態**: Lowerer/ExitLine 側は完了、JoinIR→MIR 変換バグにより E2E ブロック
|
||||
|
||||
### 実装サマリ
|
||||
|
||||
**Phase 195-impl で実装したこと**:
|
||||
|
||||
1. **`loop_with_if_phi_minimal.rs`** (+91行):
|
||||
- multi-carrier PHI 生成を追加(sum + count の 2 キャリア対応)
|
||||
- 各キャリアについて then/else 値を収集し、個別に PHI を生成
|
||||
- JoinIR 上で正しい PHI 構造を出力
|
||||
|
||||
2. **`pattern3_with_if_phi.rs`** (+68行):
|
||||
- 単一キャリア(後方互換)と複数キャリアを動的に扱うよう拡張
|
||||
- exit_bindings に複数キャリアを載せる処理を追加
|
||||
|
||||
3. **ExitLine / LoopExitBinding / CarrierVar.join_id**:
|
||||
- **変更不要!** 既存インフラをそのまま利用できた
|
||||
- Phase 170-189 で整備された CarrierVar.join_id が multi-carrier を想定していた
|
||||
- YAGNI 原則の正しさが証明された
|
||||
|
||||
### Blocker: Nested Select→Branch+Phi 変換バグ
|
||||
|
||||
**問題の概要**:
|
||||
- JoinIR 側では正しい PHI が生成されている
|
||||
- MIR 変換時に PHI inputs が undefined を参照するようになる
|
||||
|
||||
**具体例**:
|
||||
```
|
||||
JoinIR (正しい):
|
||||
ValueId(20) = phi [(BasicBlockId(3), ValueId(14)), (BasicBlockId(4), ValueId(18))]
|
||||
|
||||
MIR (壊れている):
|
||||
bb10:
|
||||
%27 = phi [%28, bb8], [%32, bb9] // ← %28, %32 は undefined
|
||||
```
|
||||
|
||||
**原因分析**:
|
||||
- `joinir_block.rs` の Select 命令処理で、Nested Select(if-else が複数キャリアを持つ場合)の変換が壊れている
|
||||
- Select → 3ブロック + 1 PHI の展開時に、block ID マッピングまたは ValueId マッピングが不整合
|
||||
- Pattern3 の multi-carrier 対応自体は問題なく、bridge 層の既存バグ
|
||||
|
||||
**対応方針**:
|
||||
- Phase 195 の scope からは外す(Lowerer/ExitLine の責務は完了)
|
||||
- Phase 196 として Select 展開バグの調査・修正を別タスク化
|
||||
|
||||
### テスト状況
|
||||
|
||||
**後方互換テスト**:
|
||||
- 単一キャリア P3 テスト(loop_if_phi.hako 等): 退行確認が必要
|
||||
- Pattern1/2/4 代表テスト: 影響なし(Select 変換に依存しないパターン)
|
||||
|
||||
**multi-carrier E2E テスト**:
|
||||
- `phase195_sum_count.hako`: JoinIR 生成は正しい、MIR 変換でブロック
|
||||
|
||||
### 次のステップ
|
||||
|
||||
**Phase 196: Select 展開/変換バグ調査&修正**(別タスク)
|
||||
- `select_expansion` / `instruction_rewriter` の責務を doc に整理
|
||||
- 「1 Select = 3 ブロック + 1 PHI」変換インタフェースを明文化
|
||||
- block reuse / block ID マッピングの切り分け
|
||||
|
||||
**Phase 195 完了時点での成果**:
|
||||
- P3 Lowerer は複数キャリア対応完了 ✅
|
||||
- ExitLine/CarrierVar は既に対応済み(変更不要)✅
|
||||
- JoinIR→MIR bridge の Select バグは別 Issue として分離 ✅
|
||||
@ -1,221 +0,0 @@
|
||||
# Phase 196: Select Expansion Bug Analysis
|
||||
|
||||
## Problem Summary
|
||||
|
||||
JoinIR→MIR の Select 展開処理で、PHI inputs に undefined ValueId が使用される問題が発生している。
|
||||
|
||||
## Reproduction Case
|
||||
|
||||
```hako
|
||||
// apps/tests/phase195_sum_count.hako
|
||||
local i = 1
|
||||
local sum = 0
|
||||
local count = 0
|
||||
|
||||
loop(i <= 5) {
|
||||
if(i % 2 == 1) {
|
||||
sum = sum + i
|
||||
count = count + 1
|
||||
} else {
|
||||
sum = sum + 0
|
||||
count = count + 0
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
|
||||
##症状 (Before)
|
||||
|
||||
### JoinIR 側(正しい)
|
||||
```
|
||||
[joinir_block/handle_select] Created merge_block BasicBlockId(5) with 1 instructions
|
||||
(first=Some(Phi { dst: ValueId(20), inputs: [(BasicBlockId(3), ValueId(14)), (BasicBlockId(4), ValueId(18))], type_hint: None }))
|
||||
```
|
||||
|
||||
### ValueId Remapping(正しい)
|
||||
```
|
||||
[DEBUG-177] JoinIR ValueId(14) → Host ValueId(21)
|
||||
[DEBUG-177] JoinIR ValueId(18) → Host ValueId(25)
|
||||
```
|
||||
|
||||
### Block Remapping(正しい)
|
||||
```
|
||||
[trace:blocks] allocator: Block remap: join_func_1:BasicBlockId(3) → BasicBlockId(8)
|
||||
[trace:blocks] allocator: Block remap: join_func_1:BasicBlockId(4) → BasicBlockId(9)
|
||||
[trace:blocks] allocator: Block remap: join_func_1:BasicBlockId(5) → BasicBlockId(10)
|
||||
```
|
||||
|
||||
### MIR 側(壊れている)
|
||||
```
|
||||
bb10:
|
||||
1: %27 = phi [%28, bb8], [%32, bb9]
|
||||
1: br %20, label bb11, label bb12
|
||||
```
|
||||
|
||||
**問題**: ValueId(28) と ValueId(32) は定義されていない!
|
||||
- 期待値: `phi [%21, bb8], [%25, bb9]`(remap後の値)
|
||||
- 実際: `phi [%28, bb8], [%32, bb9]`(未定義の値)
|
||||
|
||||
## 根本原因の仮説
|
||||
|
||||
### 仮説1: PHI inputs の ValueId が remap されていない
|
||||
- `handle_select()` で生成された PHI は JoinIR ValueId を使用
|
||||
- `instruction_rewriter.rs` の PHI remap ロジック(line 317-333)が何らかの理由で適用されていない
|
||||
|
||||
### 仮説2: 二重 remap による上書き
|
||||
- `remap_instruction()` で 1回 remap(line 304)
|
||||
- 手動 block remap で再度 remap(line 317-333)
|
||||
- 二重 remap が問題を引き起こしている可能性
|
||||
|
||||
### 仮説3: local_block_map のスコープ問題
|
||||
- Select で生成された then/else ブロック(bb3, bb4)が local_block_map に含まれていない
|
||||
- `.unwrap_or(*bb)` でフォールバックしている可能性
|
||||
|
||||
## 調査経路
|
||||
|
||||
1. **handle_select() 実装**:
|
||||
- `src/mir/join_ir_vm_bridge/joinir_block_converter.rs:407-484`
|
||||
- Select → Branch + then/else + merge(PHI) に展開
|
||||
- PHI inputs に生の JoinIR ValueId を使用(line 461)
|
||||
|
||||
2. **instruction_rewriter PHI remap**:
|
||||
- `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs:317-333`
|
||||
- PHI の block ID と ValueID を両方 remap
|
||||
- `remapper.remap_value(*val)` で ValueId を変換(line 328)
|
||||
|
||||
3. **remap_instruction() の PHI 処理**:
|
||||
- `src/mir/builder/joinir_id_remapper.rs:327-334`
|
||||
- こちらでも PHI inputs を remap(line 331)
|
||||
|
||||
## 根本原因(確定)
|
||||
|
||||
**仮説2が正解: 二重 remap による破損**
|
||||
|
||||
### 問題の詳細
|
||||
|
||||
`instruction_rewriter.rs` の PHI 処理で、ValueId が **二重に remap** されていた:
|
||||
|
||||
1. **Line 304**: `remapper.remap_instruction(inst)` で PHI inputs の ValueId を remap
|
||||
- JoinIR `ValueId(14)` → Host `ValueId(21)`
|
||||
- JoinIR `ValueId(18)` → Host `ValueId(25)`
|
||||
|
||||
2. **Line 328**: `remapper.remap_value(*val)` で **再度** remap を試行
|
||||
- しかし `*val` は既に Host ValueId(21, 25)になっている!
|
||||
- `remap_value(ValueId(21))` → `value_map` に存在しない → `unwrap_or(21)` → `ValueId(21)`
|
||||
- **問題なく見えるが、実際には壊れている**
|
||||
|
||||
### なぜ壊れたか?
|
||||
|
||||
`remap_instruction()` が返した `inputs` は **既に remap 済み** なのに、line 328 で **もう一度 remap** しようとした。
|
||||
|
||||
しかし、実際には `remap_value()` は idempotent ではない可能性がある(value_map に存在しない場合は元の値を返すが、これは**別の ValueId が偶然同じ番号**の可能性がある)。
|
||||
|
||||
### 修正内容
|
||||
|
||||
**File**: `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs`
|
||||
**Line**: 317-335
|
||||
|
||||
```rust
|
||||
// Before (Phase 172)
|
||||
MirInstruction::Phi {
|
||||
dst,
|
||||
inputs,
|
||||
type_hint: None,
|
||||
} => MirInstruction::Phi {
|
||||
dst,
|
||||
inputs: inputs
|
||||
.iter()
|
||||
.map(|(bb, val)| {
|
||||
let remapped_bb = local_block_map.get(bb).copied().unwrap_or(*bb);
|
||||
let remapped_val = remapper.remap_value(*val); // ❌ 二重 remap!
|
||||
(remapped_bb, remapped_val)
|
||||
})
|
||||
.collect(),
|
||||
type_hint: None,
|
||||
},
|
||||
|
||||
// After (Phase 196)
|
||||
MirInstruction::Phi {
|
||||
dst,
|
||||
inputs,
|
||||
type_hint: None,
|
||||
} => MirInstruction::Phi {
|
||||
dst,
|
||||
inputs: inputs
|
||||
.iter()
|
||||
.map(|(bb, val)| {
|
||||
let remapped_bb = local_block_map.get(bb).copied().unwrap_or(*bb);
|
||||
// Phase 196 FIX: Don't double-remap values!
|
||||
// remapper.remap_instruction() already remapped *val
|
||||
(remapped_bb, *val) // ✅ 値はそのまま使う(既に remap 済み)
|
||||
})
|
||||
.collect(),
|
||||
type_hint: None,
|
||||
},
|
||||
```
|
||||
|
||||
### 修正後の動作(After)
|
||||
|
||||
```
|
||||
bb10:
|
||||
1: %27 = phi [%21, bb8], [%25, bb9] // ✅ 正しい ValueId
|
||||
1: br %20, label bb11, label bb12
|
||||
```
|
||||
|
||||
## テスト結果
|
||||
|
||||
### ✅ phase195_sum_count.hako
|
||||
```bash
|
||||
$ NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase195_sum_count.hako
|
||||
93
|
||||
RC: 0
|
||||
```
|
||||
期待値: sum=9 (1+3+5), count=3 → result=93 ✅
|
||||
|
||||
### ✅ loop_if_phi.hako (Single-carrier P3)
|
||||
```bash
|
||||
$ NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_if_phi.hako
|
||||
[Console LOG] sum=9
|
||||
RC: 0
|
||||
```
|
||||
|
||||
### ✅ loop_min_while.hako (Pattern 1)
|
||||
```bash
|
||||
$ NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_min_while.hako
|
||||
0
|
||||
1
|
||||
2
|
||||
RC: 0
|
||||
```
|
||||
|
||||
### ✅ joinir_min_loop.hako (Pattern 2)
|
||||
```bash
|
||||
$ NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/joinir_min_loop.hako
|
||||
RC: 0
|
||||
```
|
||||
|
||||
## 退行チェック
|
||||
|
||||
- ✅ Pattern 1 (Simple while): PASS
|
||||
- ✅ Pattern 2 (Break): PASS
|
||||
- ✅ Pattern 3 (Single-carrier): PASS
|
||||
- ✅ Pattern 3 (Multi-carrier): PASS
|
||||
- ✅ No `[joinir/freeze]` warnings
|
||||
|
||||
## まとめ
|
||||
|
||||
### 問題
|
||||
- Select 展開で生成された PHI の inputs が undefined ValueId を参照
|
||||
|
||||
### 根本原因
|
||||
- `instruction_rewriter.rs` で ValueId を二重 remap(1回目: remap_instruction, 2回目: manual remap)
|
||||
|
||||
### 修正
|
||||
- PHI の block ID のみを remap、ValueId は既に remap 済みなのでそのまま使用
|
||||
|
||||
### 影響範囲
|
||||
- **1ファイル 1箇所のみ**: `instruction_rewriter.rs` line 331
|
||||
|
||||
### 成果
|
||||
- Phase 195 の Multi-carrier Pattern 3 が完全動作
|
||||
- 既存 Pattern 1/2/3 に退行なし
|
||||
@ -1,599 +0,0 @@
|
||||
# Phase 196: Select 展開/変換バグ 調査 & 修正
|
||||
|
||||
**Status**: Ready for Implementation
|
||||
**Date**: 2025-12-09
|
||||
**Prerequisite**: Phase 195 complete (Lowerer side, blocked by Select bug)
|
||||
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
**JoinIR→MIR の Select 展開処理で発生している既存バグ**を特定・修正する。
|
||||
|
||||
**問題**:
|
||||
- JoinIR 上では正しい PHI が生成されている
|
||||
- MIR 変換時に PHI inputs が undefined ValueId を参照する
|
||||
|
||||
**スコープ**:
|
||||
- ✅ bridge 側(join_ir_vm_bridge / merge ライン)のみ修正
|
||||
- ❌ Pattern3/multi-carrier など パターン側は触らない(Phase 195 で完了)
|
||||
|
||||
---
|
||||
|
||||
## Task 196-1: 最小再現ケースと期待形の固定(doc + デバッグ)
|
||||
|
||||
### 目標
|
||||
**最小再現ケースを phase195_sum_count.hako に固定**し、「正しい構造 vs 壊れた構造」を 1 枚絵レベルまで落とす。
|
||||
|
||||
### 対象ファイル
|
||||
- `apps/tests/phase195_sum_count.hako`(既存)
|
||||
|
||||
### 実行手順
|
||||
|
||||
#### 1. JoinIR 側の期待される構造を記録
|
||||
|
||||
```bash
|
||||
# JoinIR debug trace を取得
|
||||
NYASH_JOINIR_CORE=1 NYASH_JOINIR_DEBUG=1 ./target/release/hakorune apps/tests/phase195_sum_count.hako 2>&1 > /tmp/joinir_trace.log
|
||||
|
||||
# JoinIR 上の Select/PHI を確認
|
||||
grep -E "Select|handle_select|Phi" /tmp/joinir_trace.log
|
||||
```
|
||||
|
||||
**記録する情報**:
|
||||
- Select 命令の dst ValueId
|
||||
- PHI の inputs: `[(BasicBlockId, ValueId), (BasicBlockId, ValueId)]`
|
||||
- 各 BasicBlockId が何を表すか(then/else/merge)
|
||||
|
||||
**例**(Phase 195 での観測結果):
|
||||
```
|
||||
JoinIR (正しい):
|
||||
Select dst=ValueId(20)
|
||||
PHI: ValueId(20) = phi [(BasicBlockId(3), ValueId(14)), (BasicBlockId(4), ValueId(18))]
|
||||
|
||||
ValueId(14): sum + i (then branch)
|
||||
ValueId(18): sum + 0 (else branch)
|
||||
BasicBlockId(3): then block
|
||||
BasicBlockId(4): else block
|
||||
```
|
||||
|
||||
#### 2. MIR 側で生成されている壊れた構造を記録
|
||||
|
||||
```bash
|
||||
# MIR dump を取得
|
||||
./target/release/hakorune --dump-mir apps/tests/phase195_sum_count.hako 2>&1 > /tmp/mir_dump.log
|
||||
|
||||
# 壊れた PHI を確認
|
||||
grep -A5 "phi" /tmp/mir_dump.log
|
||||
```
|
||||
|
||||
**記録する情報**:
|
||||
- 壊れた PHI 命令の行番号
|
||||
- PHI inputs の ValueId(%28, %32 等)が undefined か確認
|
||||
- どの BasicBlock が関与しているか
|
||||
|
||||
**例**(Phase 195 での観測結果):
|
||||
```
|
||||
MIR (壊れている):
|
||||
bb10:
|
||||
%27 = phi [%28, bb8], [%32, bb9]
|
||||
|
||||
問題: %28, %32 は bb8, bb9 で定義されていない(undefined)
|
||||
bb8, bb9 は単に jump のみ(値を生成していない)
|
||||
```
|
||||
|
||||
#### 3. 原因 Select の特定
|
||||
|
||||
**どの Select が壊れているか**を 1 ヶ所に絞る:
|
||||
- JoinIR で ValueId(20) として生成された Select
|
||||
- MIR で %27 として出現する PHI
|
||||
|
||||
### 成果物
|
||||
|
||||
**ファイル**: `docs/development/current/main/phase196-select-bug-analysis.md`
|
||||
|
||||
```markdown
|
||||
# Phase 196: Select Bug Analysis
|
||||
|
||||
## Minimal Reproduction Case
|
||||
|
||||
**File**: `apps/tests/phase195_sum_count.hako`
|
||||
|
||||
## Expected Structure (JoinIR)
|
||||
|
||||
### Select Instruction
|
||||
- dst: ValueId(20)
|
||||
- condition: ValueId(19) // i > 2
|
||||
- then_value: ValueId(14) // sum + i
|
||||
- else_value: ValueId(18) // sum + 0
|
||||
|
||||
### PHI Instruction (Correct)
|
||||
```
|
||||
ValueId(20) = phi [
|
||||
(BasicBlockId(3), ValueId(14)), // then: sum + i
|
||||
(BasicBlockId(4), ValueId(18)) // else: sum + 0
|
||||
]
|
||||
```
|
||||
|
||||
## Actual Structure (MIR - Broken)
|
||||
|
||||
### PHI Instruction (bb10)
|
||||
```
|
||||
%27 = phi [%28, bb8], [%32, bb9]
|
||||
```
|
||||
|
||||
**Problem**:
|
||||
- %28 is NOT defined in bb8 (bb8 only contains jump)
|
||||
- %32 is NOT defined in bb9 (bb9 only contains jump)
|
||||
- Expected: %27 = phi [%14_mapped, bb8], [%18_mapped, bb9]
|
||||
|
||||
## Root Cause Hypothesis
|
||||
|
||||
1. PHI inputs use wrong ValueIds (not the then/else values)
|
||||
2. Block ID mapping corruption
|
||||
3. ValueId remapping issue (JoinIR → MIR)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 196-2: Select 展開経路の責務整理(コード読むだけ)
|
||||
|
||||
### 目標
|
||||
**Select 展開の責務位置を特定**し、どこで「Branch + then/else + Phi」に変換されているかを明確化する。
|
||||
|
||||
### 対象ファイル(読み取りのみ)
|
||||
|
||||
1. **`src/mir/join_ir_vm_bridge/convert.rs`**:
|
||||
- JoinIR → MIR 変換の entry point
|
||||
- Select 命令の処理箇所を探す
|
||||
|
||||
2. **`src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs`**:
|
||||
- ExitLine / PHI 周りの書き換え処理
|
||||
- Select 関連の rewrite logic を確認
|
||||
|
||||
3. **`src/mir/join_ir/joinir_block.rs`**(Phase 195 で言及された):
|
||||
- `handle_select` 関数の実装
|
||||
- Select → 3 blocks + 1 PHI の展開ロジック
|
||||
|
||||
### 調査内容
|
||||
|
||||
#### 1. Select 展開の entry point を特定
|
||||
|
||||
**質問**:
|
||||
- どの関数が Select 命令を受け取るか?
|
||||
- Select → Branch + then/else に変換する箇所はどこか?
|
||||
|
||||
**記録する情報**:
|
||||
```markdown
|
||||
## Select Expansion Entry Point
|
||||
|
||||
**File**: src/mir/join_ir/joinir_block.rs
|
||||
**Function**: `handle_select` (line XXX)
|
||||
|
||||
**Responsibilities**:
|
||||
1. Create 3 blocks: then, else, merge
|
||||
2. Generate PHI in merge block
|
||||
3. Map JoinIR ValueIds to MIR ValueIds
|
||||
```
|
||||
|
||||
#### 2. PHI inputs の生成箇所を特定
|
||||
|
||||
**質問**:
|
||||
- PHI の inputs (ValueId, BasicBlockId) はどこで決定されるか?
|
||||
- JoinIR の ValueId → MIR の ValueId への変換はどこで行われるか?
|
||||
|
||||
**記録する情報**:
|
||||
```markdown
|
||||
## PHI Input Generation
|
||||
|
||||
**Location**: handle_select function (line YYY)
|
||||
|
||||
**Current Logic**:
|
||||
```rust
|
||||
let phi_inputs = vec![
|
||||
(then_block_id, ???), // どの ValueId を使っているか
|
||||
(else_block_id, ???),
|
||||
];
|
||||
```
|
||||
|
||||
**Remapper Usage**:
|
||||
- [ ] Uses remapper.get_value() ← CORRECT
|
||||
- [ ] Uses raw JoinIR ValueId ← INCORRECT
|
||||
```
|
||||
|
||||
#### 3. Block 作成と再利用の確認
|
||||
|
||||
**質問**:
|
||||
- 3 blocks (then, else, merge) はどう作成されるか?
|
||||
- 既存 block との衝突はないか?(Phase 33-20 の教訓)
|
||||
|
||||
**記録する情報**:
|
||||
```markdown
|
||||
## Block Creation
|
||||
|
||||
**then_block**:
|
||||
- Creation: `builder.create_block()` or reuse?
|
||||
- Content: then_value computation
|
||||
|
||||
**else_block**:
|
||||
- Creation: `builder.create_block()` or reuse?
|
||||
- Content: else_value computation
|
||||
|
||||
**merge_block**:
|
||||
- Creation: `builder.create_block()`
|
||||
- Content: PHI instruction only
|
||||
- Potential issue: Overwrite existing block?
|
||||
```
|
||||
|
||||
### 成果物
|
||||
|
||||
**phase196-select-bug-analysis.md に追記**:
|
||||
```markdown
|
||||
## Select Expansion Code Path
|
||||
|
||||
### Responsibility Location
|
||||
|
||||
**Primary Function**: `src/mir/join_ir/joinir_block.rs::handle_select`
|
||||
|
||||
**Flow**:
|
||||
1. Receive Select instruction (dst, cond, then_val, else_val)
|
||||
2. Create 3 blocks (then, else, merge)
|
||||
3. Emit then_value in then_block
|
||||
4. Emit else_value in else_block
|
||||
5. Create PHI in merge_block with inputs: [(then_block, then_val), (else_block, else_val)]
|
||||
6. Return merge_block
|
||||
|
||||
### Current Implementation Issues (Hypothesis)
|
||||
|
||||
- [ ] PHI inputs use wrong ValueIds (need verification)
|
||||
- [ ] Block ID mapping corruption (need verification)
|
||||
- [ ] ValueId remapping not applied (need verification)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 196-3: 修正方針の決定(箱は増やさない)
|
||||
|
||||
### 目標
|
||||
**3つの修正候補から原因を特定**し、最小限の変更で修正する。
|
||||
|
||||
### 修正候補
|
||||
|
||||
#### 候補 1: PHI inputs の ValueId チェック
|
||||
|
||||
**仮説**: PHI inputs に間違った ValueId を使っている
|
||||
|
||||
**確認方法**:
|
||||
```rust
|
||||
// handle_select の中で PHI inputs を作る箇所を確認
|
||||
let phi_inputs = vec![
|
||||
(then_block_id, then_value_id), // ← これが正しい ValueId か?
|
||||
(else_block_id, else_value_id),
|
||||
];
|
||||
```
|
||||
|
||||
**期待される修正**:
|
||||
```rust
|
||||
// 正しい形: then/else の計算結果を使う
|
||||
let then_result = emit_then_value(...)?; // ← この ValueId
|
||||
let else_result = emit_else_value(...)?; // ← この ValueId
|
||||
|
||||
let phi_inputs = vec![
|
||||
(then_block_id, then_result), // ← emit した結果を使う
|
||||
(else_block_id, else_result),
|
||||
];
|
||||
```
|
||||
|
||||
#### 候補 2: ブロック再利用 vs 上書き問題
|
||||
|
||||
**仮説**: BasicBlockId の HashMap 上書きで PHI が消える(Phase 33-20 と同じ)
|
||||
|
||||
**確認方法**:
|
||||
```rust
|
||||
// blocks HashMap に直接 insert していないか確認
|
||||
mir_builder.blocks.insert(merge_block_id, merge_block); // ← 危険
|
||||
|
||||
// 既存ブロックがある場合は「取り出して更新」パターンか確認
|
||||
let mut block = mir_builder.blocks.remove(&block_id).unwrap();
|
||||
block.instructions.push(phi_inst);
|
||||
mir_builder.blocks.insert(block_id, block); // ← 安全
|
||||
```
|
||||
|
||||
**期待される修正**:
|
||||
- HashMap を上書きせず、既存ブロックに追記する形に修正
|
||||
|
||||
#### 候補 3: JoinInlineBoundary remapper の使い方
|
||||
|
||||
**仮説**: remap 前の ValueId を PHI inputs に直接使っている
|
||||
|
||||
**確認方法**:
|
||||
```rust
|
||||
// remapper を使っているか確認
|
||||
let phi_inputs = vec![
|
||||
(then_block_id, join_value_id), // ← JoinIR の ValueId そのまま(NG)
|
||||
(else_block_id, join_value_id),
|
||||
];
|
||||
```
|
||||
|
||||
**期待される修正**:
|
||||
```rust
|
||||
// remapper 経由で MIR ValueId に変換
|
||||
let then_mir_value = remapper.get_value(join_then_value)
|
||||
.ok_or_else(|| format!("ValueId not in remapper: {:?}", join_then_value))?;
|
||||
let else_mir_value = remapper.get_value(join_else_value)
|
||||
.ok_or_else(|| format!("ValueId not in remapper: {:?}", join_else_value))?;
|
||||
|
||||
let phi_inputs = vec![
|
||||
(then_block_id, then_mir_value), // ← remap 済み ValueId
|
||||
(else_block_id, else_mir_value),
|
||||
];
|
||||
```
|
||||
|
||||
### 実装手順
|
||||
|
||||
1. **Task 196-2 の調査結果を元に原因を絞る**
|
||||
2. **1つの候補に集中して修正**(複数同時にやらない)
|
||||
3. **E2E テストで確認**(phase195_sum_count.hako)
|
||||
4. **ダメなら次の候補へ**(Fail-Fast)
|
||||
|
||||
### 設計原則
|
||||
|
||||
- **箱は増やさない**: 既存 Select 展開ロジックの中だけを修正
|
||||
- **最小限の変更**: 1箇所だけ直す(複数箇所の同時変更は避ける)
|
||||
- **明示的エラー**: remapper.get_value() が None なら明示的に失敗
|
||||
|
||||
---
|
||||
|
||||
## Task 196-4: E2E 再検証 & 退行チェック
|
||||
|
||||
### 目標
|
||||
修正後、**phase195_sum_count.hako が動作**し、**既存テストが退行しない**ことを確認。
|
||||
|
||||
### テストケース
|
||||
|
||||
#### 1. Multi-carrier P3 (Phase 195)
|
||||
|
||||
```bash
|
||||
# Phase 195 の multi-carrier テスト
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase195_sum_count.hako
|
||||
|
||||
# Expected: 72 (sum=7, count=2)
|
||||
# Verify: No SSA-undef error, No [joinir/freeze]
|
||||
```
|
||||
|
||||
#### 2. Single-carrier P3 (既存)
|
||||
|
||||
```bash
|
||||
# 既存の単一キャリア P3 テスト
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_if_phi.hako
|
||||
|
||||
# Expected: sum=9 (または既存の期待値)
|
||||
# Verify: No regression
|
||||
```
|
||||
|
||||
#### 3. Pattern 1/2/4 代表テスト
|
||||
|
||||
```bash
|
||||
# Pattern 1: Simple while
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_min_while.hako
|
||||
|
||||
# Pattern 2: Break
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/joinir_min_loop.hako
|
||||
|
||||
# Pattern 4: Continue
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_continue_pattern4.hako
|
||||
|
||||
# Verify: All pass with expected values
|
||||
```
|
||||
|
||||
### 成功基準
|
||||
|
||||
- [ ] phase195_sum_count.hako → 72 ✅
|
||||
- [ ] loop_if_phi.hako → 期待値(退行なし)✅
|
||||
- [ ] P1/P2/P4 代表テスト → 退行なし ✅
|
||||
- [ ] SSA-undef / PHI エラーなし ✅
|
||||
- [ ] [joinir/freeze] なし ✅
|
||||
|
||||
---
|
||||
|
||||
## Task 196-5: ドキュメント更新
|
||||
|
||||
### 1. phase196-select-bug-analysis.md に Before/After 追記
|
||||
|
||||
```markdown
|
||||
## Fix Applied
|
||||
|
||||
### Root Cause
|
||||
|
||||
[Determined root cause from candidates 1-3]
|
||||
|
||||
### Code Change
|
||||
|
||||
**File**: src/mir/join_ir/joinir_block.rs (line XXX)
|
||||
|
||||
**Before**:
|
||||
```rust
|
||||
let phi_inputs = vec![
|
||||
(then_block_id, wrong_value_id),
|
||||
(else_block_id, wrong_value_id),
|
||||
];
|
||||
```
|
||||
|
||||
**After**:
|
||||
```rust
|
||||
let then_mir_value = remapper.get_value(join_then_value)?;
|
||||
let else_mir_value = remapper.get_value(join_else_value)?;
|
||||
let phi_inputs = vec![
|
||||
(then_block_id, then_mir_value),
|
||||
(else_block_id, else_mir_value),
|
||||
];
|
||||
```
|
||||
|
||||
### MIR PHI (After Fix)
|
||||
|
||||
**Before**:
|
||||
```
|
||||
bb10:
|
||||
%27 = phi [%28, bb8], [%32, bb9] // %28, %32 undefined
|
||||
```
|
||||
|
||||
**After**:
|
||||
```
|
||||
bb10:
|
||||
%27 = phi [%14, bb8], [%18, bb9] // %14, %18 correctly defined
|
||||
```
|
||||
|
||||
### Test Results
|
||||
|
||||
- phase195_sum_count.hako: 72 ✅
|
||||
- loop_if_phi.hako: sum=9 ✅
|
||||
- No regressions ✅
|
||||
```
|
||||
|
||||
### 2. CURRENT_TASK.md 更新
|
||||
|
||||
```markdown
|
||||
## Phase 196: Select 展開/変換バグ調査&修正(完了: 2025-12-XX)
|
||||
|
||||
**目的**: JoinIR→MIR の Select 展開処理バグを修正
|
||||
|
||||
**実装内容**:
|
||||
- 196-1: 最小再現ケース固定(phase195_sum_count.hako)
|
||||
- 196-2: Select 展開経路の責務特定(joinir_block.rs::handle_select)
|
||||
- 196-3: 修正実装(PHI inputs に remapper 適用)
|
||||
- 196-4: E2E 再検証 + 退行チェック(全テスト PASS)
|
||||
|
||||
**成果**:
|
||||
- P3 multi-carrier が E2E で動作確認 ✅
|
||||
- phase195_sum_count.hako → 72 ✅
|
||||
- 既存テスト(P1/P2/P3/P4)退行なし ✅
|
||||
|
||||
**技術的発見**:
|
||||
- [Root cause: remapper 適用忘れ / block 上書き / 等]
|
||||
- Select 展開は 1 箇所に集約(joinir_block.rs)
|
||||
- bridge 層の修正でパターン側に影響なし
|
||||
|
||||
**次のステップ**: Phase 197(JsonParser 残り適用)or Phase 200+(ConditionEnv 拡張)
|
||||
```
|
||||
|
||||
### 3. joinir-architecture-overview.md 更新
|
||||
|
||||
Section 7.2 に追記:
|
||||
|
||||
```markdown
|
||||
- [x] **Phase 196**: Select 展開/変換バグ修正
|
||||
- JoinIR→MIR の Select→Branch+Phi 変換を修正
|
||||
- PHI inputs に remapper を正しく適用
|
||||
- P3 multi-carrier が E2E で動作確認完了
|
||||
- Select 展開は joinir_block.rs::handle_select に集約
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 成功基準
|
||||
|
||||
- [x] 最小再現ケース固定(phase196-select-bug-analysis.md)
|
||||
- [x] Select 展開経路の責務特定(doc に記録)
|
||||
- [x] 修正実装(1箇所のみ、箱は増やさない)
|
||||
- [x] phase195_sum_count.hako → 72 ✅
|
||||
- [x] 既存テスト退行なし(P1/P2/P3/P4)
|
||||
- [x] ドキュメント更新(Before/After 記録)
|
||||
|
||||
---
|
||||
|
||||
## 関連ファイル
|
||||
|
||||
### 調査対象
|
||||
- `src/mir/join_ir_vm_bridge/convert.rs`(読み取り)
|
||||
- `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs`(読み取り)
|
||||
- `src/mir/join_ir/joinir_block.rs`(**修正対象**)
|
||||
|
||||
### テストファイル
|
||||
- `apps/tests/phase195_sum_count.hako`(最小再現)
|
||||
- `apps/tests/loop_if_phi.hako`(退行確認)
|
||||
- `apps/tests/loop_min_while.hako`(P1 退行確認)
|
||||
- `apps/tests/joinir_min_loop.hako`(P2 退行確認)
|
||||
- `apps/tests/loop_continue_pattern4.hako`(P4 退行確認)
|
||||
|
||||
### ドキュメント
|
||||
- `docs/development/current/main/phase196-select-bug-analysis.md`(新規作成)
|
||||
- `docs/development/current/main/joinir-architecture-overview.md`(更新)
|
||||
- `CURRENT_TASK.md`(Phase 196 完了マーク)
|
||||
|
||||
---
|
||||
|
||||
## 設計原則(Phase 196)
|
||||
|
||||
1. **最小限の変更**:
|
||||
- 既存 Select 展開ロジックの中だけを修正
|
||||
- 新規箱なし(bridge 層の修正のみ)
|
||||
|
||||
2. **1箇所に集中**:
|
||||
- 複数箇所の同時変更は避ける
|
||||
- 1つの候補に集中して修正 → テスト → 次へ
|
||||
|
||||
3. **明示的エラー**:
|
||||
- remapper.get_value() が None なら明示的に失敗
|
||||
- 「なんとなく動く」より「壊れたら明示的に失敗」
|
||||
|
||||
4. **ドキュメント駆動**:
|
||||
- Before/After を明確に記録
|
||||
- 将来同じバグが起きないように教訓を残す
|
||||
|
||||
---
|
||||
|
||||
## Implementation Results(完了: 2025-12-09)
|
||||
|
||||
**Status**: ✅ Complete
|
||||
|
||||
### 修正ポイント
|
||||
|
||||
**File**: `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs`
|
||||
**Line**: 317-335
|
||||
|
||||
**根本原因**: PHI inputs の ValueId が二重に remap されていた
|
||||
- Line 304: `remap_instruction()` で JoinIR ValueId → Host ValueId に remap 済み
|
||||
- Line 328: `remap_value(*val)` で再度 remap を試行 → undefined ValueId 参照
|
||||
|
||||
**修正内容**: Block ID のみ remap、ValueId は既に remap 済みなのでそのまま使用
|
||||
|
||||
```rust
|
||||
// Before (Phase 172 - 壊れていた)
|
||||
let remapped_val = remapper.remap_value(*val); // ❌ 二重 remap
|
||||
(remapped_bb, remapped_val)
|
||||
|
||||
// After (Phase 196 - 修正後)
|
||||
(remapped_bb, *val) // ✅ 値はそのまま使用(既に remap 済み)
|
||||
```
|
||||
|
||||
### 具体例
|
||||
|
||||
**Before**:
|
||||
```
|
||||
bb10:
|
||||
%27 = phi [%28, bb8], [%32, bb9] // %28, %32 undefined
|
||||
```
|
||||
|
||||
**After**:
|
||||
```
|
||||
bb10:
|
||||
%27 = phi [%21, bb8], [%25, bb9] // %21, %25 correctly defined
|
||||
```
|
||||
|
||||
### 検証結果
|
||||
|
||||
- ✅ **phase195_sum_count.hako**: 93(multi-carrier P3)
|
||||
- ✅ **loop_if_phi.hako**: sum=9(single-carrier P3)
|
||||
- ✅ **loop_min_while.hako**: 0,1,2(Pattern 1)
|
||||
- ✅ **joinir_min_loop.hako**: RC:0(Pattern 2)
|
||||
- ✅ **退行なし**: 全 Pattern (P1/P2/P3/P4) PASS
|
||||
- ✅ **`[joinir/freeze]` なし**
|
||||
|
||||
### 詳細分析
|
||||
|
||||
完全な Before/After 分析、根本原因調査、テスト結果は以下を参照:
|
||||
- **[phase196-select-bug-analysis.md](./phase196-select-bug-analysis.md)**
|
||||
|
||||
### コミット
|
||||
|
||||
- **[996925eb]** fix(joinir): Phase 196 Select double-remap bug in instruction_rewriter
|
||||
@ -1,215 +0,0 @@
|
||||
# Phase 196: Loop Continue Multi-Carrier Support
|
||||
|
||||
**Phase**: 196
|
||||
**Status**: Infrastructure Complete(multi-carrier実行は通るが、更新式は次フェーズで改善予定)
|
||||
**Date**: 2025-12-06
|
||||
**Prerequisite**: Phase 193 (CarrierInfo/ExitMeta/ExitBindingBuilder infrastructure)
|
||||
**Goal**: Enable Pattern 4 (loop with continue) to correctly handle multiple carrier variables
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Phase 196 fixes the multi-carrier support gap in Pattern 4 loop lowering. Currently, loops with a single carrier (like "sum") work correctly, but loops with multiple carriers (like "sum" + "count") crash due to hardcoded single-carrier assumptions in the JoinIR lowerer.
|
||||
|
||||
---
|
||||
|
||||
## Current Behavior
|
||||
|
||||
### Single Carrier: PASS
|
||||
|
||||
**Test**: `apps/tests/loop_continue_pattern4.hako`
|
||||
```hako
|
||||
loop(i < 10) {
|
||||
i = i + 1
|
||||
if (i % 2 == 0) { continue }
|
||||
sum = sum + i
|
||||
}
|
||||
// Result: sum = 25 (1+3+5+7+9)
|
||||
```
|
||||
|
||||
**Status**: Works correctly with ExitMeta::single("sum", ...)
|
||||
|
||||
---
|
||||
|
||||
### Multiple Carriers: FAIL
|
||||
|
||||
**Test**: `apps/tests/loop_continue_multi_carrier.hako`
|
||||
```hako
|
||||
loop(i < 10) {
|
||||
i = i + 1
|
||||
if (i % 2 == 0) { continue }
|
||||
sum = sum + i
|
||||
count = count + 1
|
||||
}
|
||||
// Expected (ideal): sum = 25, count = 5
|
||||
// Phase 196 完了時点: 実行はクラッシュせず、出力は 5, 5(インフラは動くが更新式は暫定)
|
||||
```
|
||||
|
||||
**Error(Phase 196 着手前)**:
|
||||
```
|
||||
assertion `left == right` failed: join_inputs and host_inputs must have same length
|
||||
left: 2
|
||||
right: 3
|
||||
```
|
||||
|
||||
Phase 196 完了後はこのアサートは発生せず、多キャリアでも実行自体は通るようになった(ただし sum の更新式はまだ暫定で、意味論は今後のフェーズで修正する予定)。
|
||||
|
||||
### Task 196-2: Extend JoinIR Lowerer ExitMeta
|
||||
|
||||
**Files to modify**:
|
||||
- `src/mir/join_ir/lowering/loop_with_continue_minimal.rs`
|
||||
|
||||
**Changes**:
|
||||
1. Accept carrier information (from CarrierInfo or extracted from scope)
|
||||
2. Allocate ValueIds for ALL carriers (not just "sum")
|
||||
3. Generate JoinIR Select logic for each carrier
|
||||
4. Return `ExitMeta::multiple(...)` with all carrier exit values
|
||||
|
||||
**Example change**:
|
||||
```rust
|
||||
// Before:
|
||||
let exit_meta = ExitMeta::single("sum".to_string(), sum_exit);
|
||||
|
||||
// After:
|
||||
let mut exit_values = Vec::new();
|
||||
for carrier in &carriers {
|
||||
let carrier_exit = ...; // compute exit ValueId for this carrier
|
||||
exit_values.push((carrier.name.clone(), carrier_exit));
|
||||
}
|
||||
let exit_meta = ExitMeta::multiple(exit_values);
|
||||
```
|
||||
|
||||
### Task 196-3: Verify ExitBindingBuilder Multi-Carrier
|
||||
|
||||
**File**: `src/mir/builder/control_flow/joinir/patterns/exit_binding.rs`
|
||||
|
||||
**Verification**:
|
||||
1. Confirm `build_loop_exit_bindings()` iterates ALL carriers
|
||||
2. Confirm variable_map is updated for ALL carriers
|
||||
3. Add unit test for 2-carrier case
|
||||
|
||||
**Test case**:
|
||||
```rust
|
||||
#[test]
|
||||
fn test_two_carrier_binding() {
|
||||
let carrier_info = CarrierInfo::with_carriers(
|
||||
"i".to_string(),
|
||||
ValueId(5),
|
||||
vec![
|
||||
CarrierVar { name: "count".to_string(), host_id: ValueId(10) },
|
||||
CarrierVar { name: "sum".to_string(), host_id: ValueId(11) },
|
||||
],
|
||||
);
|
||||
|
||||
let exit_meta = ExitMeta::multiple(vec![
|
||||
("count".to_string(), ValueId(14)),
|
||||
("sum".to_string(), ValueId(15)),
|
||||
]);
|
||||
|
||||
// ... verify 2 bindings created, variable_map updated for both
|
||||
}
|
||||
```
|
||||
|
||||
### Task 196-4: Update Pattern4 Caller Boundary
|
||||
|
||||
**File**: `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
|
||||
|
||||
**Changes**:
|
||||
1. Remove hardcoded `vec![ValueId(0), ValueId(1)]`
|
||||
2. Compute `join_inputs` dynamically from carrier count
|
||||
3. Ensure `join_inputs.len() == host_inputs.len()`
|
||||
|
||||
**Example change**:
|
||||
```rust
|
||||
// Before:
|
||||
vec![ValueId(0), ValueId(1)] // Hardcoded
|
||||
|
||||
// After:
|
||||
let mut join_inputs = vec![ValueId(0)]; // loop_var is always ValueId(0)
|
||||
for idx in 1..=carrier_info.carriers.len() {
|
||||
join_inputs.push(ValueId(idx as u32));
|
||||
}
|
||||
// join_inputs: [0, 1, 2] for 2 carriers
|
||||
```
|
||||
|
||||
### Task 196-5: Multi-Carrier Test & Documentation
|
||||
|
||||
**Test command**:
|
||||
```bash
|
||||
./target/release/hakorune apps/tests/loop_continue_multi_carrier.hako
|
||||
```
|
||||
|
||||
**Expected output**:
|
||||
```
|
||||
25
|
||||
5
|
||||
```
|
||||
|
||||
**Documentation updates**:
|
||||
- Update CURRENT_TASK.md with Phase 196 completion
|
||||
- Update phase193_5_multi_carrier_testing.md to note Phase 196 implementation
|
||||
- Mark this file as complete
|
||||
|
||||
---
|
||||
|
||||
## Files to Modify
|
||||
|
||||
| File | Task | Change |
|
||||
|------|------|--------|
|
||||
| `loop_with_continue_minimal.rs` | 196-2 | Generate ExitMeta for all carriers |
|
||||
| `pattern4_with_continue.rs` | 196-4 | Compute join_inputs dynamically |
|
||||
| `exit_binding.rs` | 196-3 | Add multi-carrier unit test |
|
||||
| `CURRENT_TASK.md` | 196-5 | Document completion |
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [x] `loop_continue_pattern4.hako` (single carrier) still passes ✅
|
||||
- [x] `loop_continue_multi_carrier.hako` (2 carriers) passes with output "25\n5" ✅ (Phase 197 Complete)
|
||||
- [x] No hardcoded carrier names remain in Pattern 4 lowering path ✅
|
||||
- [x] ExitBindingBuilder unit tests pass for 2-carrier case ✅ (Phase 193-4)
|
||||
- [x] join_inputs computed dynamically, not hardcoded ✅
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- **Phase 193 Completion**: [PHASE_193_COMPLETION.md](PHASE_193_COMPLETION.md)
|
||||
- **Phase 193-4 ExitBindingBuilder**: [phase193_exit_binding_builder.md](phase193_exit_binding_builder.md)
|
||||
- **Phase 193-5 Testing Plan**: [phase193_5_multi_carrier_testing.md](phase193_5_multi_carrier_testing.md)
|
||||
|
||||
---
|
||||
|
||||
## Progress Log
|
||||
|
||||
### 2025-12-06 (Session 3 - Phase 197 Complete)
|
||||
- **Phase 197-B COMPLETE**: Multi-carrier exit mechanism fully fixed
|
||||
- `reconnect_boundary()` now uses `remapper` to get per-carrier exit values
|
||||
- ExitMeta uses `carrier_param_ids` (Jump arguments) instead of `carrier_exit_ids` (k_exit parameters)
|
||||
- Root cause: k_exit parameters aren't defined when JoinIR functions merge into host
|
||||
- **Phase 197-C COMPLETE**: AST-based update expression
|
||||
- LoopUpdateAnalyzer extracts `sum = sum + i` / `count = count + 1` patterns
|
||||
- Pattern 4 lowerer uses UpdateExpr for semantically correct RHS
|
||||
- **Test result**: `loop_continue_multi_carrier.hako` outputs `25, 5` ✅
|
||||
|
||||
### 2025-12-06 (Session 2 - Implementation)
|
||||
- **Task 196-2 COMPLETE**: Extended JoinIR lowerer to accept CarrierInfo parameter
|
||||
- Modified `lower_loop_with_continue_minimal()` signature
|
||||
- Dynamic ValueId allocation for N carriers
|
||||
- ExitMeta::multiple() with all carriers
|
||||
- Select generation for each carrier
|
||||
- **Task 196-3 COMPLETE (inherent)**: ExitBindingBuilder already supports multi-carrier (Phase 193-4)
|
||||
- **Task 196-4 COMPLETE**: Fixed Pattern4 caller boundary construction
|
||||
- Dynamic join_inputs generation matching carrier count
|
||||
- Passes carrier_info to lowerer
|
||||
- **Task 196-5 PARTIAL → FIXED in Phase 197**: Semantic issue resolved
|
||||
- **BEFORE**: Assertion crash (join_inputs.len()=2, host_inputs.len()=3)
|
||||
- **AFTER Phase 196**: No crash, outputs 5/5 (infrastructure fixed)
|
||||
- **AFTER Phase 197**: Outputs 25/5 (semantics fixed)
|
||||
|
||||
### 2025-12-06 (Session 1 - Documentation)
|
||||
- Created Phase 196 documentation (Task 196-1)
|
||||
- Identified root cause: hardcoded ExitMeta::single() and join_inputs
|
||||
- Task investigation completed by Agent
|
||||
@ -1,445 +0,0 @@
|
||||
# Phase 197: JoinIR 実戦適用(軽量ループライン)
|
||||
|
||||
**Date**: 2025-12-09
|
||||
**Status**: Ready for Implementation
|
||||
**Prerequisite**: Phase 196 complete (Select bug fixed)
|
||||
|
||||
---
|
||||
|
||||
## 目的
|
||||
|
||||
Phase 196 までで安定した JoinIR 基盤(P1/P2/P3/P4 + P5 Trim)を、
|
||||
**実戦の小さいループ**に当てはめて、以下の一連動作を確認する:
|
||||
|
||||
1. **パターン検出**: LoopFeatures / router が正しく Pattern を選択
|
||||
2. **JoinIR lowering**: Pattern lowerer が JoinIR を生成
|
||||
3. **MIR 実行**: phase195_sum_count.hako 等が正しく動作
|
||||
|
||||
**スコープ**:
|
||||
- ✅ 既存インフラの検証(新機能追加なし)
|
||||
- ✅ 軽量ループのみ(3-5本)
|
||||
- ✅ ドキュメント駆動(実戦適用状況を可視化)
|
||||
|
||||
---
|
||||
|
||||
## Task 197-1: 対象ループの確定
|
||||
|
||||
### 選定基準
|
||||
|
||||
1. **既にwhitelisted OR 既存テストで動作実績あり**
|
||||
2. **依存関係が少ない**(ConditionEnv制約/複雑キャリアなし)
|
||||
3. **代表性がある**(P1/P2/P3を1本ずつ)
|
||||
|
||||
### 対象ループ一覧(3-5本)
|
||||
|
||||
#### JsonParser 側(2本)
|
||||
|
||||
1. **`_match_literal`** (Pattern 1)
|
||||
- **Location**: `tools/hako_shared/json_parser.hako:357-362`
|
||||
- **Pattern**: P2 (break via return) → **実際は P1 候補**(単純whileとして扱える)
|
||||
- **Carrier**: i (IntegerBox)
|
||||
- **Update**: i = i + 1
|
||||
- **Status**: ✅ Already whitelisted (`JsonParserBox._match_literal/3`)
|
||||
- **理由**: P1 の最もシンプルなケース(break なし、単純な継続条件)
|
||||
|
||||
2. **`_skip_whitespace`** (Pattern 2)
|
||||
- **Location**: `tools/hako_shared/json_parser.hako:312-319`
|
||||
- **Pattern**: P2 (break condition)
|
||||
- **Carrier**: p (IntegerBox)
|
||||
- **Update**: p = p + 1
|
||||
- **Status**: ✅ Already whitelisted (`JsonParserBox._skip_whitespace/2`)
|
||||
- **理由**: P2 の代表的な break パターン
|
||||
|
||||
#### selfhost/tests 側(2本)
|
||||
|
||||
3. **`phase195_sum_count.hako`** (Pattern 3)
|
||||
- **Location**: `apps/tests/phase195_sum_count.hako`
|
||||
- **Pattern**: P3 (If-Else PHI, multi-carrier)
|
||||
- **Carrier**: sum, count (2キャリア)
|
||||
- **Update**:
|
||||
- then: sum = sum + i, count = count + 1
|
||||
- else: sum = sum + 0, count = count + 0
|
||||
- **Status**: ✅ Phase 196 で動作確認済み(出力: 93)
|
||||
- **理由**: P3 multi-carrier の実証済みケース
|
||||
|
||||
4. **`loop_if_phi.hako`** (Pattern 3 single-carrier)
|
||||
- **Location**: `apps/tests/loop_if_phi.hako`
|
||||
- **Pattern**: P3 (If-Else PHI, single-carrier)
|
||||
- **Carrier**: sum (1キャリア)
|
||||
- **Update**: if (i > 2) sum = sum + i else sum = sum + 0
|
||||
- **Status**: ✅ Phase 196 で動作確認済み(出力: sum=9)
|
||||
- **理由**: P3 single-carrier の既存実績
|
||||
|
||||
#### オプション(5本目)
|
||||
|
||||
5. **`loop_min_while.hako`** (Pattern 1 representative)
|
||||
- **Location**: `apps/tests/loop_min_while.hako`
|
||||
- **Pattern**: P1 (simple while)
|
||||
- **Carrier**: i (IntegerBox)
|
||||
- **Update**: i = i + 1
|
||||
- **Status**: ✅ Phase 165 で動作確認済み(出力: 0,1,2)
|
||||
- **理由**: P1 の最小ケース(比較用)
|
||||
|
||||
### 対象外ループ(Phase 197 では扱わない)
|
||||
|
||||
- ❌ `_parse_number` / `_atoi`: ConditionEnv 制約(digits.indexOf 依存) → Phase 200+
|
||||
- ❌ `_parse_string` / `_unescape_string`: 複雑キャリア → Phase 195+ Pattern3 拡張後
|
||||
- ❌ `_parse_array` / `_parse_object`: 複数 MethodCall → Phase 195+ MethodCall 拡張後
|
||||
|
||||
### 成果物
|
||||
|
||||
**CURRENT_TASK.md の Phase 197 セクションに以下を追記**:
|
||||
|
||||
```markdown
|
||||
### Phase 197: JoinIR 実戦適用(軽量ループライン)
|
||||
|
||||
**対象ループ**:
|
||||
1. `_match_literal` (P1) - JsonParser 単純 while
|
||||
2. `_skip_whitespace` (P2) - JsonParser break パターン
|
||||
3. `phase195_sum_count.hako` (P3 multi-carrier) - 既存実績
|
||||
4. `loop_if_phi.hako` (P3 single-carrier) - 既存実績
|
||||
5. `loop_min_while.hako` (P1 minimal) - 比較用
|
||||
|
||||
**実施内容**: 構造トレース + E2E 実行 + ドキュメント更新
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 197-2: routing 更新(JoinIR 経路に載せる)
|
||||
|
||||
### 方針
|
||||
|
||||
1. **構造ベース判定優先**: PatternPipelineContext / LoopPatternKind が自動判定
|
||||
2. **名前ベース whitelist は補助**: 既に whitelisted のものはそのまま
|
||||
3. **新規トグル・条件なし**: 既存インフラのみで対応
|
||||
|
||||
### 作業内容
|
||||
|
||||
#### 1. routing.rs の確認
|
||||
|
||||
**File**: `src/mir/builder/control_flow/joinir/routing.rs`
|
||||
|
||||
**確認事項**:
|
||||
- [ ] `_match_literal` が whitelist に存在するか
|
||||
- [ ] `_skip_whitespace` が whitelist に存在するか
|
||||
- [ ] 構造ベース判定(LoopFeatures)が優先されているか
|
||||
|
||||
**Expected**: Phase 194 で既に whitelisted 済み(変更不要の可能性大)
|
||||
|
||||
#### 2. 必要な場合のみ whitelist 追加
|
||||
|
||||
```rust
|
||||
// routing.rs の whitelist テーブル(例)
|
||||
const JOINIR_WHITELIST: &[&str] = &[
|
||||
"JsonParserBox._skip_whitespace/2", // ✅ 既存
|
||||
"JsonParserBox._trim/1", // ✅ 既存
|
||||
"JsonParserBox._match_literal/3", // ✅ 既存
|
||||
// Phase 197: 追加不要(既に全部 whitelisted)
|
||||
];
|
||||
```
|
||||
|
||||
### 検証
|
||||
|
||||
```bash
|
||||
# routing.rs の whitelist を確認
|
||||
grep -n "JsonParserBox\._match_literal" src/mir/builder/control_flow/joinir/routing.rs
|
||||
grep -n "JsonParserBox\._skip_whitespace" src/mir/builder/control_flow/joinir/routing.rs
|
||||
```
|
||||
|
||||
**Expected**: 既に存在している → **変更不要**
|
||||
|
||||
---
|
||||
|
||||
## Task 197-3: 構造トレースと E2E 実行
|
||||
|
||||
### 手順
|
||||
|
||||
#### 1. 構造トレース(失敗しないことの確認)
|
||||
|
||||
各対象ループについて、Pattern 選択が正しく動作するか確認。
|
||||
|
||||
```bash
|
||||
# Test 1: _match_literal (P1)
|
||||
NYASH_JOINIR_CORE=1 NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune \
|
||||
tools/hako_shared/json_parser.hako 2>&1 | grep -E "match_literal|Pattern1"
|
||||
|
||||
# Test 2: _skip_whitespace (P2)
|
||||
NYASH_JOINIR_CORE=1 NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune \
|
||||
tools/hako_shared/json_parser.hako 2>&1 | grep -E "skip_whitespace|Pattern2"
|
||||
|
||||
# Test 3: phase195_sum_count.hako (P3 multi-carrier)
|
||||
NYASH_JOINIR_CORE=1 NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune \
|
||||
apps/tests/phase195_sum_count.hako 2>&1 | grep -E "Pattern3|sum_count"
|
||||
|
||||
# Test 4: loop_if_phi.hako (P3 single-carrier)
|
||||
NYASH_JOINIR_CORE=1 NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune \
|
||||
apps/tests/loop_if_phi.hako 2>&1 | grep -E "Pattern3|if_phi"
|
||||
|
||||
# Test 5: loop_min_while.hako (P1 minimal)
|
||||
NYASH_JOINIR_CORE=1 NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune \
|
||||
apps/tests/loop_min_while.hako 2>&1 | grep -E "Pattern1|min_while"
|
||||
```
|
||||
|
||||
**確認ポイント**:
|
||||
- ✅ router が正しい Pattern (P1/P2/P3) を選択
|
||||
- ✅ `[joinir/freeze]` が出ない(freeze = legacy fallback)
|
||||
- ✅ `UnsupportedPattern` エラーが出ない
|
||||
|
||||
#### 2. E2E 実行テスト
|
||||
|
||||
実際に実行して、期待値が出力されるか確認。
|
||||
|
||||
```bash
|
||||
# Test 1: _match_literal
|
||||
# (JsonParser 全体実行が必要 - Phase 197 では個別テストを作成)
|
||||
# TODO: phase197_match_literal.hako を作成
|
||||
|
||||
# Test 2: _skip_whitespace
|
||||
# TODO: phase197_skip_whitespace.hako を作成
|
||||
|
||||
# Test 3: phase195_sum_count.hako
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase195_sum_count.hako
|
||||
# Expected: 93
|
||||
|
||||
# Test 4: loop_if_phi.hako
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_if_phi.hako
|
||||
# Expected: [Console LOG] sum=9
|
||||
|
||||
# Test 5: loop_min_while.hako
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_min_while.hako
|
||||
# Expected: 0\n1\n2
|
||||
```
|
||||
|
||||
**検証項目**:
|
||||
- [ ] 出力が期待値と一致
|
||||
- [ ] RC (Return Code) が 0
|
||||
- [ ] エラーメッセージなし
|
||||
|
||||
#### 3. 必要に応じてトレース(デバッグ)
|
||||
|
||||
ValueId の流れや PHI 接続が怪しい場合のみ実施。
|
||||
|
||||
```bash
|
||||
# PHI トレース
|
||||
NYASH_JOINIR_CORE=1 NYASH_TRACE_PHI=1 ./target/release/hakorune \
|
||||
apps/tests/phase195_sum_count.hako 2>&1 | grep -E "phi|PHI"
|
||||
|
||||
# variable_map トレース
|
||||
NYASH_JOINIR_CORE=1 NYASH_TRACE_VARMAP=1 ./target/release/hakorune \
|
||||
apps/tests/phase195_sum_count.hako 2>&1 | grep -E "\[trace:varmap\]"
|
||||
```
|
||||
|
||||
**確認ポイント**:
|
||||
- PHI の dst と inputs が正しく対応
|
||||
- variable_map 更新が整合している
|
||||
|
||||
---
|
||||
|
||||
## Task 197-4: ドキュメント更新
|
||||
|
||||
### 1. joinir-architecture-overview.md
|
||||
|
||||
**Section**: 7.2 残タスク(Phase 192+ で対応予定)
|
||||
|
||||
**追加内容**:
|
||||
|
||||
```markdown
|
||||
7. **Phase 197: 実戦適用(軽量ループ検証)** → 完了 ✅
|
||||
- 目的: 既存インフラの実戦検証(新機能追加なし)
|
||||
- 対象ループ:
|
||||
- `_match_literal` (P1) - JsonParser 単純 while
|
||||
- `_skip_whitespace` (P2) - JsonParser break パターン
|
||||
- `phase195_sum_count.hako` (P3 multi-carrier) - 既存実績
|
||||
- `loop_if_phi.hako` (P3 single-carrier) - 既存実績
|
||||
- `loop_min_while.hako` (P1 minimal) - 比較用
|
||||
- 結果:
|
||||
- [x] 構造トレース: 全ループで正しい Pattern 選択 ✅
|
||||
- [x] E2E 実行: 全ループで期待値出力 ✅
|
||||
- [x] 退行なし: Phase 190-196 テスト全 PASS ✅
|
||||
- 詳細: phase197-lightweight-loops-deployment.md
|
||||
|
||||
### JsonParser/selfhost 実戦 JoinIR 適用状況
|
||||
|
||||
| Function | Pattern | Status | Note |
|
||||
|----------|---------|--------|------|
|
||||
| `_match_literal` | P1 | ✅ JoinIR OK | Phase 197 検証済み |
|
||||
| `_skip_whitespace` | P2 | ✅ JoinIR OK | Phase 197 検証済み |
|
||||
| `_trim` (leading) | P5 | ✅ JoinIR OK | Phase 173 実証済み |
|
||||
| `_trim` (trailing) | P5 | ✅ JoinIR OK | Phase 173 実証済み |
|
||||
| `phase195_sum_count` | P3 | ✅ JoinIR OK | Phase 196 検証済み(multi-carrier)|
|
||||
| `loop_if_phi` | P3 | ✅ JoinIR OK | Phase 196 検証済み(single-carrier)|
|
||||
| `loop_min_while` | P1 | ✅ JoinIR OK | Phase 165 基本検証済み |
|
||||
| `_parse_number` | P2 | ⚠️ Deferred | ConditionEnv 制約(Phase 200+)|
|
||||
| `_atoi` | P2 | ⚠️ Deferred | ConditionEnv 制約(Phase 200+)|
|
||||
| `_parse_string` | P3 | ⚠️ Deferred | 複雑キャリア(Phase 195+ 拡張後)|
|
||||
| `_unescape_string` | P3 | ⚠️ Deferred | 複雑キャリア(Phase 195+ 拡張後)|
|
||||
| `_parse_array` | - | ⚠️ Deferred | 複数 MethodCall(Phase 195+)|
|
||||
| `_parse_object` | - | ⚠️ Deferred | 複数 MethodCall(Phase 195+)|
|
||||
|
||||
**Coverage**: 7/13 ループ JoinIR 対応済み(54%)
|
||||
```
|
||||
|
||||
### 2. CURRENT_TASK.md
|
||||
|
||||
**Phase 197 セクション追加**:
|
||||
|
||||
```markdown
|
||||
- [x] **Phase 197: JoinIR 実戦適用(軽量ループ検証)** ✅ (完了: 2025-12-XX)
|
||||
- **目的**: Phase 196 までの安定基盤を実戦の小さいループで検証
|
||||
- **対象ループ(5本)**:
|
||||
1. `_match_literal` (P1) - JsonParser 単純 while ✅
|
||||
2. `_skip_whitespace` (P2) - JsonParser break パターン ✅
|
||||
3. `phase195_sum_count.hako` (P3 multi-carrier) ✅
|
||||
4. `loop_if_phi.hako` (P3 single-carrier) ✅
|
||||
5. `loop_min_while.hako` (P1 minimal) ✅
|
||||
- **実施内容**:
|
||||
- 197-1: 対象ループ確定(3-5本)✅
|
||||
- 197-2: routing 確認(whitelist 既存)✅
|
||||
- 197-3: 構造トレース + E2E 実行 ✅
|
||||
- 197-4: ドキュメント更新 ✅
|
||||
- **成果**:
|
||||
- 全ループで正しい Pattern 選択 ✅
|
||||
- 全ループで期待値出力 ✅
|
||||
- 退行なし(Phase 190-196 テスト全 PASS)✅
|
||||
- JsonParser/selfhost 実戦適用状況表作成 ✅
|
||||
- **次候補**:
|
||||
- Phase 200+: ConditionEnv 拡張(_parse_number, _atoi)
|
||||
- Phase 198+: JsonParser 残りループ個別対応
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 成功基準
|
||||
|
||||
- [x] 対象ループ 3-5本を CURRENT_TASK.md に固定
|
||||
- [x] routing.rs 確認(whitelist 既存確認)
|
||||
- [x] 構造トレース: 全ループで正しい Pattern 選択
|
||||
- [x] E2E 実行: 全ループで期待値出力
|
||||
- [x] 退行なし: Phase 190-196 テスト全 PASS
|
||||
- [x] ドキュメント更新(joinir-architecture-overview.md, CURRENT_TASK.md)
|
||||
|
||||
---
|
||||
|
||||
## 設計原則(Phase 197)
|
||||
|
||||
1. **検証フォーカス**: 新機能追加なし、既存インフラの実戦検証のみ
|
||||
2. **軽量ループのみ**: 複雑なループ(ConditionEnv制約/複数MethodCall)は Phase 200+ に保留
|
||||
3. **ドキュメント駆動**: 実戦適用状況を可視化(カバレッジ表作成)
|
||||
4. **退行防止**: 全 Phase 190-196 テストが PASS することを確認
|
||||
|
||||
---
|
||||
|
||||
## 次フェーズ候補
|
||||
|
||||
### Phase 200+: ConditionEnv 拡張
|
||||
- **目的**: function-scoped local variables を ConditionEnv に含める
|
||||
- **対象**: `_parse_number`, `_atoi`(digits.indexOf 依存)
|
||||
- **設計**: ConditionEnv 拡張 OR .hako リライト
|
||||
|
||||
### Phase 198+: JsonParser 残りループ個別対応
|
||||
- **目的**: `_parse_string`, `_unescape_string` 等の複雑ループ
|
||||
- **前提**: Pattern 3 拡張(multi-flag carriers)完了
|
||||
- **対象**: 2-3 ループずつ段階的に適用
|
||||
|
||||
---
|
||||
|
||||
## Implementation Results (2025-12-09)
|
||||
|
||||
### Task 197-2: routing 確認結果 ✅
|
||||
|
||||
**Verification**: Both JsonParser functions already whitelisted in `routing.rs`:
|
||||
|
||||
```rust
|
||||
// Line 88-89 in routing.rs
|
||||
"JsonParserBox._skip_whitespace/2" => true,
|
||||
"JsonParserBox._match_literal/3" => true, // Phase 182: Fixed arity (s, pos, literal)
|
||||
```
|
||||
|
||||
**Conclusion**: No routing changes needed - existing infrastructure supports both functions.
|
||||
|
||||
### Task 197-3: E2E 実行テスト結果 ✅
|
||||
|
||||
#### Test 1: phase195_sum_count.hako (P3 multi-carrier) ✅
|
||||
```bash
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase195_sum_count.hako
|
||||
```
|
||||
- **Output**: `93` (expected)
|
||||
- **RC**: 0
|
||||
- **Pattern**: P3 (If-Else PHI with multi-carrier: sum, count)
|
||||
- **JoinIR Functions**: main, loop_step, k_exit
|
||||
- **Carriers**: i (counter), sum (accumulator), count (counter)
|
||||
- **Status**: ✅ PASS - No `[joinir/freeze]`, no errors
|
||||
|
||||
#### Test 2: loop_if_phi.hako (P3 single-carrier) ✅
|
||||
```bash
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_if_phi.hako
|
||||
```
|
||||
- **Output**: `[Console LOG] sum=9` (expected)
|
||||
- **RC**: 0
|
||||
- **Pattern**: P3 (If-Else PHI with single-carrier: sum)
|
||||
- **JoinIR Functions**: main, loop_step, k_exit
|
||||
- **Carriers**: i (counter), sum (accumulator)
|
||||
- **Status**: ✅ PASS - No `[joinir/freeze]`, no errors
|
||||
|
||||
#### Test 3: loop_min_while.hako (P1 minimal) ✅
|
||||
```bash
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/loop_min_while.hako
|
||||
```
|
||||
- **Output**: `0\n1\n2` (expected)
|
||||
- **RC**: 0
|
||||
- **Pattern**: P1 (Simple While)
|
||||
- **JoinIR Functions**: main, loop_step, k_exit
|
||||
- **Carrier**: i (IntegerBox)
|
||||
- **Status**: ✅ PASS - No `[joinir/freeze]`, no errors
|
||||
|
||||
#### Test 4: phase182_p1_match_literal.hako (P1 with return) ✅
|
||||
```bash
|
||||
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase182_p1_match_literal.hako
|
||||
```
|
||||
- **Output**: `Result: MATCH` (expected)
|
||||
- **RC**: 0
|
||||
- **Pattern**: P1 (Simple While with early return)
|
||||
- **JoinIR Functions**: main, loop_step, k_exit
|
||||
- **Carrier**: i (IntegerBox)
|
||||
- **Status**: ✅ PASS - Successfully routes through JoinIR
|
||||
- **Note**: Simulates `JsonParserBox._match_literal/3` logic
|
||||
|
||||
#### Test 5: _skip_whitespace (P2) - Structural Verification Only
|
||||
- **File**: `apps/tests/stage1_skip_ws_repro.hako`
|
||||
- **Status**: ⚠️ Requires StringHelpers box (not available in test environment)
|
||||
- **Routing Verification**: ✅ Already whitelisted (`JsonParserBox._skip_whitespace/2`)
|
||||
- **Conclusion**: Pattern 2 routing infrastructure confirmed, full E2E deferred to JsonParser integration
|
||||
|
||||
### Summary: All Core Tests PASS ✅
|
||||
|
||||
| Test | Pattern | Expected Output | Actual | Status |
|
||||
|------|---------|----------------|--------|--------|
|
||||
| phase195_sum_count | P3 multi | 93 | 93 | ✅ PASS |
|
||||
| loop_if_phi | P3 single | sum=9 | sum=9 | ✅ PASS |
|
||||
| loop_min_while | P1 | 0,1,2 | 0,1,2 | ✅ PASS |
|
||||
| phase182_match_literal | P1 | MATCH | MATCH | ✅ PASS |
|
||||
| _skip_whitespace | P2 | (routing only) | N/A | ✅ Whitelisted |
|
||||
|
||||
**Coverage**: 4/5 loops fully tested, 1/5 routing verified
|
||||
**Regression**: None detected
|
||||
**JoinIR Infrastructure**: Stable and production-ready for P1/P3 patterns
|
||||
|
||||
---
|
||||
|
||||
## 関連ファイル
|
||||
|
||||
### 調査対象
|
||||
- `src/mir/builder/control_flow/joinir/routing.rs`(whitelist 確認)
|
||||
- `tools/hako_shared/json_parser.hako`(JsonParser ループ)
|
||||
|
||||
### テストファイル
|
||||
- `apps/tests/phase195_sum_count.hako`(P3 multi-carrier)
|
||||
- `apps/tests/loop_if_phi.hako`(P3 single-carrier)
|
||||
- `apps/tests/loop_min_while.hako`(P1 minimal)
|
||||
- `apps/tests/phase182_p1_match_literal.hako`(P1 with return)
|
||||
- `apps/tests/stage1_skip_ws_repro.hako`(P2 routing verification)
|
||||
|
||||
### ドキュメント
|
||||
- `docs/development/current/main/phase194-loop-inventory.md`(ループ一覧)
|
||||
- `docs/development/current/main/joinir-architecture-overview.md`(実戦適用状況表)
|
||||
- `CURRENT_TASK.md`(Phase 197 セクション)
|
||||
@ -1,143 +0,0 @@
|
||||
# Phase 197: Pattern 4 Update Semantics
|
||||
|
||||
**Status**: In Progress(197-B で ExitMeta/Boundary 経路の修正は完了)
|
||||
**Date**: 2025-12-06
|
||||
**Goal**: Fix loop_continue_multi_carrier.hako to output correct values (25, 5)
|
||||
|
||||
## Problem Statement
|
||||
|
||||
### Current Behavior
|
||||
- Output: `5, 5`
|
||||
- Expected: `25, 5`
|
||||
|
||||
### Root Cause
|
||||
The update expressions for carriers are hardcoded in `loop_with_continue_minimal.rs:310-317`:
|
||||
|
||||
```rust
|
||||
let rhs = if carrier_name == "count" {
|
||||
const_1 // count = count + 1
|
||||
} else {
|
||||
i_next // sum = sum + i_next (and other accumulators)
|
||||
};
|
||||
```
|
||||
|
||||
This works for the simple pattern where:
|
||||
- `count` carriers always use `+1`
|
||||
- Other carriers always use `+i_next`
|
||||
|
||||
However, this breaks semantic correctness because:
|
||||
- `sum = sum + i` in the source code should use the **current** `i` value
|
||||
- The current implementation uses `i_next` (the next iteration's value)
|
||||
|
||||
### Test Case Analysis
|
||||
|
||||
From `loop_continue_multi_carrier.hako`:
|
||||
|
||||
```nyash
|
||||
local i = 0
|
||||
local sum = 0
|
||||
local count = 0
|
||||
loop(i < 10) {
|
||||
i = i + 1 // i goes 1,2,3,4,5,6,7,8,9,10
|
||||
if (i % 2 == 0) {
|
||||
continue // Skip even: 2,4,6,8,10
|
||||
}
|
||||
sum = sum + i // sum = sum + i (current i)
|
||||
count = count + 1 // count = count + 1
|
||||
}
|
||||
// Expected: sum=25 (1+3+5+7+9), count=5
|
||||
```
|
||||
|
||||
The update expressions should be:
|
||||
- `i`: `i = i + 1` (constant increment)
|
||||
- `sum`: `sum = sum + i` (accumulate current i)
|
||||
- `count`: `count = count + 1` (constant increment)
|
||||
|
||||
### Current Implementation Problem
|
||||
|
||||
In `loop_with_continue_minimal.rs`, the Select statement for carriers:
|
||||
|
||||
```rust
|
||||
// carrier_next = carrier_param + rhs
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: carrier_next,
|
||||
op: BinOpKind::Add,
|
||||
lhs: carrier_param,
|
||||
rhs, // This is WRONG for sum!
|
||||
}));
|
||||
```
|
||||
|
||||
For `sum`:
|
||||
- `rhs = i_next` (next iteration value)
|
||||
- Should be: `rhs = i_current` (current iteration value)
|
||||
|
||||
### 197-B: ExitMeta / Boundary 経路の修正(完了)
|
||||
|
||||
さらに、Phase 197-B では「どの ValueId を出口として見るか」という経路も修正した:
|
||||
|
||||
- **問題**:
|
||||
- もともと k_exit 関数のパラメータ(`carrier_exit_ids` = 例えば ValueId(20), ValueId(21))を ExitMeta に入れていたが、
|
||||
- これらは JoinIR 関数インライン後の MIR では「定義されない」ValueId になってしまうケースがあった。
|
||||
- 一方、`loop_step` 内の Jump 引数(`carrier_param_ids`)は MIR マージ時に正しい ValueId に remap される。
|
||||
|
||||
- **修正内容**:
|
||||
1. `loop_with_continue_minimal.rs` 側で、ExitMeta には k_exit のパラメータではなく、Jump の引数(`carrier_param_ids`)を使うよう変更。
|
||||
2. `reconnect_boundary`(mod.rs)に `remapper` パラメータを追加し、Boundary に書かれた JoinIR 側 ValueId を remapper 経由で MIR の ValueId に変換してから variable_map に接続。
|
||||
|
||||
この結果:
|
||||
- carrier の出口 ValueId は「必ず MIR 上で定義がある値」になり、
|
||||
- ExitBindingBuilder 〜 JoinInlineBoundary 〜 merge_joinir_mir_blocks までの配管が、multi-carrier でも破綻しない状態になった。
|
||||
|
||||
## Solution Design
|
||||
|
||||
### Phase 197-2: LoopUpdateAnalyzer
|
||||
|
||||
Create `src/mir/join_ir/lowering/loop_update_analyzer.rs`:
|
||||
|
||||
```rust
|
||||
pub enum UpdateExpr {
|
||||
Const(i64), // count = count + 1
|
||||
BinOp { lhs: String, op: BinOpKind, rhs: String }, // sum = sum + i
|
||||
}
|
||||
|
||||
pub struct LoopUpdateAnalyzer;
|
||||
|
||||
impl LoopUpdateAnalyzer {
|
||||
pub fn analyze_carrier_updates(
|
||||
body: &[ASTNode],
|
||||
carriers: &[CarrierVar]
|
||||
) -> HashMap<String, UpdateExpr> {
|
||||
// Extract update expressions from assignment statements
|
||||
// in the loop body
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 197-3: Update Lowerer
|
||||
|
||||
Modify `loop_with_continue_minimal.rs`:
|
||||
|
||||
1. Call `LoopUpdateAnalyzer::analyze_carrier_updates()` at the start
|
||||
2. Remove hardcoded `if carrier_name == "count"` logic
|
||||
3. Use extracted `UpdateExpr` metadata to generate correct RHS:
|
||||
- For `Const(n)`: Use `const_n`
|
||||
- For `BinOp { rhs: "i", ... }`: Use `i_current` (not `i_next`)
|
||||
|
||||
### Expected Fix
|
||||
|
||||
After implementation:
|
||||
- `sum = sum + i` will use **current** `i` value
|
||||
- Output will be `25, 5` as expected
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. **Task 197-1**: Create this documentation
|
||||
2. **Task 197-2**: Implement `LoopUpdateAnalyzer`
|
||||
3. **Task 197-3**: Update `loop_with_continue_minimal.rs` to use metadata
|
||||
4. **Task 197-4**: Test and verify output
|
||||
|
||||
## References
|
||||
|
||||
- Test case: `apps/tests/loop_continue_multi_carrier.hako`
|
||||
- Lowerer: `src/mir/join_ir/lowering/loop_with_continue_minimal.rs`
|
||||
- Pattern detection: `src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs`
|
||||
@ -1,255 +0,0 @@
|
||||
# Phase 33-10: Pattern 4 PHI Loss Analysis & Fix Recommendation
|
||||
|
||||
**Created**: 2025-12-06
|
||||
**Context**: Comparison between Pattern 3 (loop_if_phi.hako) and Pattern 4 (loop_continue_pattern4.hako)
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
**Problem**: Pattern 4 loses its loop-body PHI instruction during JoinIR→MIR merge phase.
|
||||
**Root Cause**: Block overwrite in `instruction_rewriter.rs` line 92
|
||||
**Impact**: CRITICAL - incorrect sum calculation in continue scenarios
|
||||
|
||||
---
|
||||
|
||||
## 1. Evidence
|
||||
|
||||
### Pattern 3 ✅ (Preserves PHI)
|
||||
```mir
|
||||
bb10:
|
||||
1: %23 = phi [%20, bb8], [%22, bb9] ← PHI PRESENT
|
||||
1: %24 = const 1
|
||||
1: %25 = %11 Add %24
|
||||
```
|
||||
|
||||
### Pattern 4 ❌ (Loses PHI)
|
||||
```mir
|
||||
bb10:
|
||||
1: %8 = copy %14 ← NO PHI!
|
||||
1: br label bb5
|
||||
```
|
||||
|
||||
### JoinIR Creation Phase (Both Successful)
|
||||
```
|
||||
[joinir_block/handle_select] Created merge_block BasicBlockId(5) with 1 instructions
|
||||
[joinir_block/finalize_block] Preserving 1 PHI instructions in block BasicBlockId(5)
|
||||
[joinir/meta] Block BasicBlockId(5): X instructions (1 PHI)
|
||||
```
|
||||
- Pattern 3: 5 instructions (1 PHI)
|
||||
- Pattern 4: 3 instructions (1 PHI)
|
||||
|
||||
---
|
||||
|
||||
## 2. Root Cause Analysis
|
||||
|
||||
### Hypothesis: Block Overwrite in instruction_rewriter.rs
|
||||
|
||||
**Location**: `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs:92`
|
||||
|
||||
**Problem Code**:
|
||||
```rust
|
||||
for (old_block_id, old_block) in blocks_merge {
|
||||
let new_block_id = remapper.get_block(func_name, *old_block_id)...;
|
||||
let mut new_block = BasicBlock::new(new_block_id); // ← ALWAYS CREATES FRESH BLOCK!
|
||||
// ... copy instructions from old_block ...
|
||||
current_func.add_block(new_block); // line 365
|
||||
}
|
||||
```
|
||||
|
||||
**Why This Fails**:
|
||||
1. `handle_select()` creates bb10 (BasicBlockId → bb10) with PHI instruction
|
||||
2. Later, merge phase processes JoinIR's BasicBlockId(5) which also maps to bb10
|
||||
3. `BasicBlock::new(new_block_id)` creates FRESH block, discarding existing PHI
|
||||
4. `add_block()` overwrites the block in HashMap
|
||||
|
||||
## 3. Recommended Fix
|
||||
|
||||
### Step 1: Add Debug Logging (Verification)
|
||||
|
||||
Add to `instruction_rewriter.rs` around line 87:
|
||||
|
||||
```rust
|
||||
for (old_block_id, old_block) in blocks_merge {
|
||||
let new_block_id = remapper.get_block(func_name, *old_block_id)...;
|
||||
|
||||
// DEBUG: Check if block already exists
|
||||
if let Some(ref current_func) = builder.current_function {
|
||||
if let Some(existing) = current_func.blocks.get(&new_block_id) {
|
||||
let phi_count = existing.instructions.iter()
|
||||
.filter(|i| matches!(i, MirInstruction::Phi{..}))
|
||||
.count();
|
||||
eprintln!("[cf_loop/joinir] 🚨 Block {:?} ALREADY EXISTS: {} inst, {} PHI",
|
||||
new_block_id, existing.instructions.len(), phi_count);
|
||||
}
|
||||
}
|
||||
|
||||
let mut new_block = BasicBlock::new(new_block_id); // ← This OVERWRITES!
|
||||
```
|
||||
|
||||
### Step 2: Preserve Existing Blocks (RECOMMENDED FIX)
|
||||
|
||||
**Modify line 92** in `instruction_rewriter.rs`:
|
||||
|
||||
```rust
|
||||
// OLD: Always creates fresh block (loses existing PHI!)
|
||||
let mut new_block = BasicBlock::new(new_block_id);
|
||||
|
||||
// NEW: Preserve existing block if present
|
||||
let mut new_block = if let Some(ref mut current_func) = builder.current_function {
|
||||
// Remove and reuse existing block (preserves PHI!)
|
||||
current_func.blocks.remove(&new_block_id)
|
||||
.unwrap_or_else(|| BasicBlock::new(new_block_id))
|
||||
} else {
|
||||
BasicBlock::new(new_block_id)
|
||||
};
|
||||
```
|
||||
|
||||
**Why This Works**:
|
||||
- If bb10 was created by `handle_select()` with PHI, we **retrieve and reuse** it
|
||||
- New instructions from JoinIR merge are **appended** to existing instructions
|
||||
- PHI instruction at the beginning of bb10 is **preserved**
|
||||
|
||||
### Step 3: Alternative - Check add_block() Logic
|
||||
|
||||
If Step 2 doesn't work, check `MirFunction::add_block()` implementation:
|
||||
|
||||
```rust
|
||||
// If this uses .insert(), it will OVERWRITE existing blocks!
|
||||
pub fn add_block(&mut self, block: BasicBlock) {
|
||||
self.blocks.insert(block.id, block); // ← HashMap::insert overwrites!
|
||||
}
|
||||
|
||||
// Should be:
|
||||
pub fn add_block(&mut self, block: BasicBlock) {
|
||||
if let Some(existing) = self.blocks.get_mut(&block.id) {
|
||||
// Merge: prepend existing PHI, append new instructions
|
||||
let mut merged = existing.instructions.clone();
|
||||
merged.extend(block.instructions);
|
||||
existing.instructions = merged;
|
||||
existing.terminator = block.terminator;
|
||||
} else {
|
||||
self.blocks.insert(block.id, block);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Testing Strategy
|
||||
|
||||
### 4.1 Before Fix - Reproduce PHI Loss
|
||||
|
||||
```bash
|
||||
./target/release/hakorune --dump-mir apps/tests/loop_continue_pattern4.hako 2>&1 | \
|
||||
grep -A3 "^bb10:"
|
||||
```
|
||||
|
||||
**Current Output (BUG)**:
|
||||
```mir
|
||||
bb10:
|
||||
1: %8 = copy %14 ← NO PHI!
|
||||
1: br label bb5
|
||||
```
|
||||
|
||||
### 4.2 After Fix - Verify PHI Preservation
|
||||
|
||||
```bash
|
||||
# Same command after applying Step 2 fix
|
||||
./target/release/hakorune --dump-mir apps/tests/loop_continue_pattern4.hako 2>&1 | \
|
||||
grep -A3 "^bb10:"
|
||||
```
|
||||
|
||||
**Expected Output (FIXED)**:
|
||||
```mir
|
||||
bb10:
|
||||
1: %16 = phi [%4, bb8], [%14, bb9] ← PHI RESTORED!
|
||||
1: %8 = copy %14
|
||||
1: br label bb5
|
||||
```
|
||||
|
||||
### 4.3 Compare with Pattern 3 (Control)
|
||||
|
||||
Pattern 3 should continue to work correctly (no regression):
|
||||
|
||||
```bash
|
||||
./target/release/hakorune --dump-mir apps/tests/loop_if_phi.hako 2>&1 | \
|
||||
grep -A3 "^bb10:"
|
||||
```
|
||||
|
||||
**Expected Output (Control)**:
|
||||
```mir
|
||||
bb10:
|
||||
1: %23 = phi [%20, bb8], [%22, bb9] ← PHI PRESENT (as before)
|
||||
1: %24 = const 1
|
||||
1: %25 = %11 Add %24
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Code Locations
|
||||
|
||||
### Files to Modify
|
||||
|
||||
| File | Lines | Purpose |
|
||||
|------|-------|---------|
|
||||
| `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs` | 92 | Fix block overwrite |
|
||||
| `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs` | 87-90 | Add debug logging |
|
||||
|
||||
### Reference Files (For Understanding)
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/mir/join_ir_vm_bridge/joinir_block_converter.rs` | Select→PHI conversion (handle_select) |
|
||||
| `src/mir/join_ir/lowering/loop_with_continue_minimal.rs` | Pattern 4 lowerer (Select at line 304) |
|
||||
| `src/mir/join_ir/lowering/loop_with_if_phi_minimal.rs` | Pattern 3 lowerer (comparison) |
|
||||
| `apps/tests/loop_continue_pattern4.hako` | Test case showing PHI loss |
|
||||
| `apps/tests/loop_if_phi.hako` | Control test case (PHI preserved) |
|
||||
|
||||
---
|
||||
|
||||
## 6. Summary & Next Steps
|
||||
|
||||
### 6.1 Key Findings
|
||||
|
||||
1. **PHI Loss Mechanism**: Block overwrite in merge phase, not JoinIR conversion
|
||||
2. **Root Cause**: `BasicBlock::new()` always creates fresh block at line 92
|
||||
3. **Impact**: Pattern 4 produces incorrect results due to missing PHI
|
||||
4. **Solution**: Preserve existing blocks by removing before recreating
|
||||
|
||||
### 6.2 Implementation Checklist
|
||||
|
||||
- [ ] Add debug logging (Step 1) - 5 minutes
|
||||
- [ ] Apply block preservation fix (Step 2) - 10 minutes
|
||||
- [ ] Test Pattern 4 PHI restoration - 5 minutes
|
||||
- [ ] Verify Pattern 3 no regression - 2 minutes
|
||||
- [ ] Run full test suite - 10 minutes
|
||||
- [ ] Commit with detailed message - 5 minutes
|
||||
|
||||
**Total Time**: ~40 minutes
|
||||
|
||||
### 6.3 Expected Impact
|
||||
|
||||
**Correctness**:
|
||||
- ✅ Pattern 4 will produce correct sum values
|
||||
- ✅ PHI instructions preserved in all loop patterns
|
||||
- ✅ No regression for existing patterns
|
||||
|
||||
**Code Quality**:
|
||||
- ✅ Proper block lifecycle management
|
||||
- ✅ No silent instruction loss
|
||||
- ✅ Better debugging with added logging
|
||||
|
||||
---
|
||||
|
||||
## 7. Related Documentation
|
||||
|
||||
- **Analysis Document**: [phase33-10-joinir-merge-phi-loss-analysis.md](phase33-10-joinir-merge-phi-loss-analysis.md)
|
||||
- **Local Pattern Analysis**: [phase33-10-local-pattern-mir-analysis.md](phase33-10-local-pattern-mir-analysis.md)
|
||||
- **JoinIR Design**: [if_joinir_design.md](../../../private/roadmap2/phases/phase-33-joinir-if-phi-cleanup/if_joinir_design.md)
|
||||
|
||||
---
|
||||
|
||||
**Created**: 2025-12-06
|
||||
**Status**: Analysis Complete, Ready for Implementation
|
||||
**Priority**: HIGH (Correctness Issue)
|
||||
@ -1,166 +0,0 @@
|
||||
# JoinIR→MIR Merge Processing PHI Loss Analysis
|
||||
|
||||
## Executive Summary
|
||||
|
||||
**Problem**: Pattern 4 (loop_continue_pattern4.hako) loses PHI instructions during JoinIR→MIR merge, while Pattern 3 (loop_if_phi.hako) preserves them correctly.
|
||||
|
||||
**Critical Finding**: Both patterns successfully create PHI instructions during JoinIR→MIR conversion, but Pattern 4's PHI disappears during the merge phase.
|
||||
|
||||
## Test Cases
|
||||
|
||||
### Pattern 3: loop_if_phi.hako ✅ PHI Preserved
|
||||
```nyash
|
||||
loop(i <= 5) {
|
||||
if (i % 2 == 1) { sum = sum + i } else { sum = sum + 0 }
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
|
||||
**Lowerer**: `loop_with_if_phi_minimal.rs`
|
||||
**JoinIR**: Uses `Select` instruction for if-else PHI in loop body
|
||||
|
||||
### Pattern 4: loop_continue_pattern4.hako ❌ PHI Lost
|
||||
```nyash
|
||||
loop(i < 10) {
|
||||
i = i + 1
|
||||
if (i % 2 == 0) { continue }
|
||||
sum = sum + i
|
||||
}
|
||||
```
|
||||
|
||||
**Lowerer**: `loop_with_continue_minimal.rs`
|
||||
**JoinIR**: Uses `Select` instruction for continue vs. normal path (line 304)
|
||||
|
||||
## PHI Creation Phase (JoinIR→MIR Conversion)
|
||||
|
||||
### Pattern 3 - Select→PHI Conversion ✅
|
||||
```
|
||||
[joinir_block/handle_select] Created merge_block BasicBlockId(5) with 1 instructions
|
||||
[joinir_block/handle_select] After insert: merge_block BasicBlockId(5) has 1 instructions
|
||||
[joinir_block/finalize_block] Preserving 1 PHI instructions in block BasicBlockId(5)
|
||||
[joinir/meta] Block BasicBlockId(5): 5 instructions (1 PHI)
|
||||
```
|
||||
|
||||
**Result**: `%23 = phi [%20, bb8], [%22, bb9]` appears in final MIR bb10
|
||||
|
||||
### Pattern 4 - Select→PHI Conversion ✅
|
||||
```
|
||||
[joinir_block/handle_select] Created merge_block BasicBlockId(5) with 1 instructions
|
||||
[joinir_block/handle_select] After insert: merge_block BasicBlockId(5) has 1 instructions
|
||||
[joinir_block/finalize_block] Preserving 1 PHI instructions in block BasicBlockId(5)
|
||||
[joinir/meta] Block BasicBlockId(5): 3 instructions (1 PHI)
|
||||
```
|
||||
|
||||
**Result**: PHI is created but does **NOT** appear in final MIR bb10!
|
||||
|
||||
## Key Difference: Instruction Count
|
||||
|
||||
- **Pattern 3**: BasicBlockId(5) has **5 instructions** (1 PHI + 4 others)
|
||||
- **Pattern 4**: BasicBlockId(5) has **3 instructions** (1 PHI + 2 others)
|
||||
|
||||
## Final MIR Comparison
|
||||
|
||||
### Pattern 3 - bb10 (from BasicBlockId(5))
|
||||
```mir
|
||||
bb10:
|
||||
1: %23 = phi [%20, bb8], [%22, bb9] ← PHI PRESENT ✅
|
||||
1: %24 = const 1
|
||||
1: %25 = %11 Add %24
|
||||
1: %11 = copy %25
|
||||
1: %12 = copy %23
|
||||
1: br label bb5
|
||||
```
|
||||
|
||||
### Pattern 4 - bb10 (from BasicBlockId(5))
|
||||
```mir
|
||||
bb10:
|
||||
1: %8 = copy %14 ← NO PHI! ❌
|
||||
1: br label bb5
|
||||
```
|
||||
|
||||
## Hypothesis: Merge Processing Difference
|
||||
|
||||
The merge processing in `instruction_rewriter.rs` (lines 117-213) should handle PHI instructions correctly:
|
||||
|
||||
```rust
|
||||
for inst in &old_block.instructions {
|
||||
// ... skip conditions ...
|
||||
|
||||
let remapped = remapper.remap_instruction(inst);
|
||||
|
||||
let remapped_with_blocks = match remapped {
|
||||
MirInstruction::Phi { dst, inputs, type_hint: None } => {
|
||||
// PHI block remapping (lines 183-196)
|
||||
MirInstruction::Phi {
|
||||
dst,
|
||||
inputs: inputs.iter()
|
||||
.map(|(bb, val)| (local_block_map.get(bb).copied().unwrap_or(*bb), *val))
|
||||
.collect(),
|
||||
type_hint: None,
|
||||
}
|
||||
}
|
||||
other => other,
|
||||
};
|
||||
|
||||
new_block.instructions.push(remapped_with_blocks); // Line 212
|
||||
}
|
||||
```
|
||||
|
||||
## Possible Causes
|
||||
|
||||
### Theory 1: Block Replacement
|
||||
- Pattern 4's bb10 might be getting **completely replaced** instead of merged
|
||||
- Check if `finalize_block()` in `joinir_block_converter.rs` (lines 691-727) is being called differently
|
||||
|
||||
### Theory 2: Tail Call Handling
|
||||
- Pattern 4 has a tail call that might trigger different merge logic
|
||||
- The tail call detection (lines 146-167) might affect how blocks are merged
|
||||
- Pattern 3 may not have a tail call in the same position
|
||||
|
||||
### Theory 3: Return Conversion
|
||||
- BasicBlockId(5) has terminator `Return { value: Some(ValueId(99991)) }`
|
||||
- This gets converted to `Jump` to exit block (lines 302-320)
|
||||
- Something in this conversion might be dropping instructions
|
||||
|
||||
### Theory 4: Block Overwrite
|
||||
- Pattern 4's shorter instruction count (3 vs 5) suggests instructions are being lost
|
||||
- Check if the merge process overwrites the block instead of preserving PHI
|
||||
|
||||
## Investigation Path
|
||||
|
||||
1. **Enable debug output** for instruction_rewriter.rs merge processing
|
||||
2. **Check finalize_block calls** - are they different between patterns?
|
||||
3. **Trace BasicBlockId(5)** - what happens to it during merge?
|
||||
4. **Compare Return terminator handling** - does the conversion lose instructions?
|
||||
5. **Check local_block_map** - is BasicBlockId(5) being mapped correctly?
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Add detailed logging to `instruction_rewriter.rs` around line 122-213
|
||||
2. Trace the exact path Pattern 4's BasicBlockId(5) takes during merge
|
||||
3. Compare with Pattern 3's path to find the divergence point
|
||||
4. Check if the problem is in the initial block creation or later overwriting
|
||||
|
||||
## Code Locations
|
||||
|
||||
- JoinIR Block Converter: `src/mir/join_ir_vm_bridge/joinir_block_converter.rs`
|
||||
- `handle_select()`: lines 407-484 (creates PHI)
|
||||
- `finalize_block()`: lines 691-727 (preserves existing PHI)
|
||||
- Merge Processing: `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs`
|
||||
- `merge_and_rewrite()`: lines 18-405
|
||||
- PHI remapping: lines 183-196
|
||||
- Pattern Lowerers:
|
||||
- Pattern 3: `src/mir/join_ir/lowering/loop_with_if_phi_minimal.rs`
|
||||
- Pattern 4: `src/mir/join_ir/lowering/loop_with_continue_minimal.rs` (Select at line 304)
|
||||
|
||||
## Expected Outcome
|
||||
|
||||
Pattern 4's bb10 should contain:
|
||||
```mir
|
||||
bb10:
|
||||
1: %XX = phi [%YY, bb8], [%ZZ, bb9] ← Missing PHI!
|
||||
1: %8 = copy %14
|
||||
1: br label bb5
|
||||
```
|
||||
|
||||
The PHI instruction for `sum_merged = Select(continue_cond, sum_param, sum_next)` (line 304 in loop_with_continue_minimal.rs) is being lost during merge.
|
||||
@ -1,630 +0,0 @@
|
||||
# Phase 33-10: Local Pattern の実MIR構造分析レポート
|
||||
|
||||
**作成日**: 2025-11-27
|
||||
**分析者**: Claude Code
|
||||
**目的**: If lowering における「local pattern」の実MIR構造を分析し、Phase 33-10の実装方針を決定する
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
### 1.1 重要な結論
|
||||
|
||||
**PHI命令を持つMIRは、JoinIR変換の対象外とすべき(Option A推奨)**
|
||||
|
||||
理由:
|
||||
1. **PHI命令は既にSSAの標準的な値合流表現** - これ以上の変換は不要
|
||||
2. **JoinIR Select/IfMergeの役割は "PHI生成"** - 既存PHIを変換するのは逆行
|
||||
3. **try_match_local_pattern()の想定は誤り** - 実MIRとの構造差分が根本的
|
||||
|
||||
### 1.2 実装への影響
|
||||
|
||||
**Phase 33-10の方針**:
|
||||
- ❌ try_match_local_pattern()の修正は**不要**
|
||||
- ✅ local patternは**Phase 33-10の対象外**として扱う
|
||||
- ✅ JoinIR loweringは「PHI命令がない場合のみ」実施
|
||||
- ✅ 既存のif_phi.rsによるPHI生成は維持(これが正常動作)
|
||||
|
||||
---
|
||||
|
||||
## 2. 構造比較詳細
|
||||
|
||||
### 2.1 ユニットテスト想定(誤り)
|
||||
|
||||
**try_match_local_pattern()が期待していた構造**:
|
||||
|
||||
```mir
|
||||
bb0: br cond, bb1, bb2
|
||||
|
||||
bb1 (then):
|
||||
Copy { dst: x, src: 100 } ← 単一の代入命令
|
||||
Jump bb3
|
||||
|
||||
bb2 (else):
|
||||
Copy { dst: x, src: 200 } ← 単一の代入命令
|
||||
Jump bb3
|
||||
|
||||
bb3 (merge):
|
||||
[空 - 命令なし] ← 重要: 命令がない!
|
||||
Return x
|
||||
```
|
||||
|
||||
**検証コード**(if_select.rs:262-263):
|
||||
```rust
|
||||
if !merge_block.instructions.is_empty() {
|
||||
return None; // ← merge blockが空でなければ失敗
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 実MIR構造(正しい)
|
||||
|
||||
**実際のコンパイラ出力** (`/tmp/test_phi_local.hako`):
|
||||
|
||||
```mir
|
||||
define i64 @main() {
|
||||
bb0:
|
||||
1: %3: Integer = const 1
|
||||
1: %4: Integer = copy %3
|
||||
1: %5: Integer = copy %4
|
||||
1: br %5, label bb3, label bb4
|
||||
|
||||
bb3 (then):
|
||||
1: %8: Integer = const 100 ← Const命令(Copy不使用)
|
||||
1: br label bb5
|
||||
|
||||
bb4 (else):
|
||||
1: %11: Integer = const 200 ← Const命令(Copy不使用)
|
||||
1: br label bb5
|
||||
|
||||
bb5 (merge):
|
||||
1: %12 = phi [%8, bb3], [%11, bb4] ← PHI命令が存在!
|
||||
1: ret %12
|
||||
}
|
||||
```
|
||||
|
||||
**構造差分**:
|
||||
|
||||
| 要素 | ユニットテスト想定 | 実MIR | 差分の意味 |
|
||||
|------|------------------|-------|----------|
|
||||
| Then/Else命令 | `Copy { dst: x, src: 100 }` | `Const { dst: %8, value: 100 }` | Copyではなく直接Const |
|
||||
| Merge命令 | **空(なし)** | **`Phi { dst: %12, inputs: [...] }`** | PHI命令が存在 |
|
||||
| Return値 | `Return x`(then/elseで書き込んだ変数) | `Return %12`(PHI結果の新しいValueId) | 異なるValueId |
|
||||
|
||||
### 2.3 Phase 33-9.2の修正との比較
|
||||
|
||||
**Phase 33-9.2で修正したSimple pattern**:
|
||||
|
||||
```rust
|
||||
// 修正内容: Const/Copyを許容
|
||||
fn is_side_effect_free(&self, instructions: &[MirInstruction]) -> bool {
|
||||
instructions.iter().all(|inst| {
|
||||
matches!(inst, MirInstruction::Const { .. } | MirInstruction::Copy { .. })
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Simple patternの実MIR**:
|
||||
```mir
|
||||
bb3 (then):
|
||||
%8: Integer = const 100
|
||||
ret %8 ← Return命令(merge blockなし)
|
||||
|
||||
bb4 (else):
|
||||
%11: Integer = const 200
|
||||
ret %11 ← Return命令(merge blockなし)
|
||||
```
|
||||
|
||||
**重要な違い**:
|
||||
- **Simple**: merge blockなし → return直接
|
||||
- **Local**: merge blockあり → **PHI命令で合流**
|
||||
|
||||
---
|
||||
|
||||
## 3. PHI命令の妥当性検証
|
||||
|
||||
### 3.1 SSA形式の正しさ
|
||||
|
||||
**実MIRのPHI構造は完全に正しい**:
|
||||
|
||||
1. **定義(Definition)**:
|
||||
- SSA形式では、複数の前任ブロックから値が流入する場合、PHI命令で合流させる
|
||||
- これが**唯一の正規的な表現**
|
||||
|
||||
2. **実MIRの構造**:
|
||||
```mir
|
||||
bb5 (merge):
|
||||
%12 = phi [%8, bb3], [%11, bb4]
|
||||
```
|
||||
- bb3から%8が流入
|
||||
- bb4から%11が流入
|
||||
- bb5では%12として合流
|
||||
- **完璧なSSA形式**
|
||||
|
||||
3. **検証**:
|
||||
- ✅ 各ValueIdは一度だけ定義される(Single Assignment)
|
||||
- ✅ 前任ブロックごとの値が明確(Static Single)
|
||||
- ✅ PHI配置は支配フロンティアに従っている
|
||||
|
||||
### 3.2 PHI命令の配置
|
||||
|
||||
**標準的なPHI配置規則**:
|
||||
- PHI命令はmerge blockの**先頭**に配置される
|
||||
- Return/Jump等の終端命令の**前**に配置される
|
||||
|
||||
**実MIRの配置**:
|
||||
```mir
|
||||
bb5:
|
||||
1: %12 = phi [%8, bb3], [%11, bb4] ← 先頭に配置
|
||||
1: ret %12 ← 終端命令
|
||||
```
|
||||
|
||||
✅ **完全に標準的な配置**
|
||||
|
||||
---
|
||||
|
||||
## 4. JoinIR変換の必要性分析
|
||||
|
||||
### 4.1 JoinIR Select/IfMergeの役割
|
||||
|
||||
**設計文書より** (if_joinir_design.md):
|
||||
|
||||
```rust
|
||||
/// Phase 33.1: 単純な値選択
|
||||
Select {
|
||||
dst: VarId,
|
||||
cond: VarId,
|
||||
then_val: VarId,
|
||||
else_val: VarId,
|
||||
}
|
||||
```
|
||||
|
||||
**Select命令の意味論**:
|
||||
- 条件に応じて値を選択する
|
||||
- **MIR reverse loweringでPHI命令を生成する**(重要!)
|
||||
|
||||
### 4.2 PHI → Select変換の問題点
|
||||
|
||||
**仮にPHI → Selectを実装した場合**:
|
||||
|
||||
```
|
||||
MIR (入力):
|
||||
bb5: %12 = phi [%8, bb3], [%11, bb4]
|
||||
|
||||
↓ lowering (MIR → JoinIR)
|
||||
|
||||
JoinIR:
|
||||
Select { dst: %12, cond: ???, then_val: %8, else_val: %11 }
|
||||
|
||||
↓ reverse lowering (JoinIR → MIR)
|
||||
|
||||
MIR (出力):
|
||||
bb5: %12 = phi [%8, bb3], [%11, bb4] ← 元に戻る
|
||||
```
|
||||
|
||||
**問題**:
|
||||
1. **情報損失**: PHI → Select時に条件式が不明(%5はbb0で消費済み)
|
||||
2. **無意味な往復**: MIR → JoinIR → MIR で同じPHIに戻る
|
||||
3. **責務の逆転**: JoinIRの役割は「PHI生成」であり、「PHI変換」ではない
|
||||
|
||||
### 4.3 JoinIR変換が有意義なケース
|
||||
|
||||
**唯一有意義なのは「PHI命令がまだ生成されていない場合」**:
|
||||
|
||||
```
|
||||
高レベルAST:
|
||||
if cond { x = 100 } else { x = 200 }; return x
|
||||
|
||||
↓ (従来の方式: if_phi.rs経由)
|
||||
|
||||
MIR:
|
||||
bb5: %12 = phi [%8, bb3], [%11, bb4]
|
||||
ret %12
|
||||
|
||||
↓ (新しい方式: JoinIR経由)
|
||||
|
||||
JoinIR:
|
||||
Select { dst: %12, cond: %5, then_val: %8, else_val: %11 }
|
||||
|
||||
↓ reverse lowering
|
||||
|
||||
MIR:
|
||||
bb5: %12 = phi [%8, bb3], [%11, bb4]
|
||||
ret %12
|
||||
```
|
||||
|
||||
**結果**: 最終MIRは同じだが、生成経路が異なる
|
||||
|
||||
**価値**:
|
||||
- ✅ if_phi.rsの削除が可能(コード削減)
|
||||
- ✅ JoinIR側で型検証が可能
|
||||
- ❌ 既にPHIがある場合は意味なし
|
||||
|
||||
---
|
||||
|
||||
## 5. Phase 33の設計意図確認
|
||||
|
||||
### 5.1 設計文書の記述
|
||||
|
||||
**if_joinir_design.md:11-27より**:
|
||||
|
||||
```
|
||||
1. If/Else の「値としての if」(`if ... then ... else ...` の PHI)を JoinIR 側で表現できるようにする。
|
||||
- ループ PHI と同じく、「PHI を関数引数や join 関数の引数に押し出す」設計に寄せる。
|
||||
- MIR Builder 側の if_phi.rs / conservative.rs / phi_invariants.rs に依存しない JoinIR lowering を用意する。
|
||||
```
|
||||
|
||||
**重要な前提**:
|
||||
- **「MIR Builder側のif_phi.rsに依存しない」** = if_phi.rsを**使わずに**PHIを生成する
|
||||
- **PHI生成の新しい経路**としてJoinIRを使う
|
||||
|
||||
### 5.2 if_phi.rsの役割
|
||||
|
||||
**現状のif_phi.rs**:
|
||||
- ASTから直接PHI命令を生成(MIR Builder層)
|
||||
- if/else構造を解析してPHI配置を決定
|
||||
- 316行のロジック
|
||||
|
||||
**JoinIR経由の場合**:
|
||||
- ASTからJoinIR Select/IfMergeを生成
|
||||
- JoinIR → MIR reverse lowering時にPHI命令を生成
|
||||
- if_phi.rsは**削除可能**
|
||||
|
||||
### 5.3 Phase 33のターゲット
|
||||
|
||||
**Phase 33-10の実装対象**:
|
||||
|
||||
```
|
||||
入力: AST(まだPHI生成されていない)
|
||||
出力: MIR(PHI命令を含む)
|
||||
|
||||
経路選択:
|
||||
Route A(従来): AST → if_phi.rs → MIR(PHI)
|
||||
Route B(新規): AST → JoinIR lowering → JoinIR Select → reverse lowering → MIR(PHI)
|
||||
```
|
||||
|
||||
**Phase 33-10が**扱うべきでない**ケース**:
|
||||
```
|
||||
入力: MIR(既にPHI命令が存在)
|
||||
出力: ???(何に変換する意味がある?)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Phase 33-10実装方針
|
||||
|
||||
### 6.1 推奨方針: Option A(PHI命令を変換対象外とする)
|
||||
|
||||
**実装内容**:
|
||||
|
||||
```rust
|
||||
// src/mir/join_ir/lowering/if_select.rs
|
||||
fn try_match_local_pattern(
|
||||
&self,
|
||||
func: &MirFunction,
|
||||
branch: &IfBranch,
|
||||
then_block: &crate::mir::BasicBlock,
|
||||
else_block: &crate::mir::BasicBlock,
|
||||
) -> Option<IfPattern> {
|
||||
// Phase 33-10: PHI命令が既に存在する場合は対象外
|
||||
// (JoinIRの役割はPHI生成であり、既存PHI変換ではない)
|
||||
|
||||
// merge blockの取得
|
||||
let merge_block_id = match then_block.terminator.as_ref()? {
|
||||
MirInstruction::Jump { target } => *target,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let merge_block = func.blocks.get(&merge_block_id)?;
|
||||
|
||||
// ❌ PHI命令がある場合は変換対象外(重要!)
|
||||
for inst in &merge_block.instructions {
|
||||
if matches!(inst, MirInstruction::Phi { .. }) {
|
||||
return None; // 既にPHIがあるので、JoinIR変換不要
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ PHI命令がない場合のみ、local patternとして処理
|
||||
// (この場合は現在の実装で正しく動作する)
|
||||
|
||||
// ... 既存のロジック継続 ...
|
||||
}
|
||||
```
|
||||
|
||||
**効果**:
|
||||
- ✅ PHI命令がある = 既にif_phi.rsで処理済み → JoinIR変換不要
|
||||
- ✅ PHI命令がない = JoinIR経由で処理すべき → 現在の実装が機能
|
||||
- ✅ 責務の明確化: JoinIRは「PHI生成器」として機能
|
||||
|
||||
### 6.2 Option B(PHI → Select/IfMerge変換)の問題点
|
||||
|
||||
**実装した場合の問題**:
|
||||
|
||||
1. **条件式の復元困難**:
|
||||
```mir
|
||||
bb0: br %5, label bb3, label bb4 ← %5はここで消費
|
||||
...
|
||||
bb5: %12 = phi [%8, bb3], [%11, bb4] ← %5にアクセスできない
|
||||
```
|
||||
|
||||
2. **無意味な往復変換**:
|
||||
- MIR(PHI) → JoinIR(Select) → MIR(PHI)
|
||||
- 何も変わらない
|
||||
|
||||
3. **Phase 33の意図に反する**:
|
||||
- 目的: if_phi.rsを削除して、JoinIR経由でPHI生成
|
||||
- 実際: 既にあるPHIを変換(意味なし)
|
||||
|
||||
### 6.3 Option C(新しいパターン定義)の不要性
|
||||
|
||||
**「PHI-based pattern」を定義する必要はない**:
|
||||
|
||||
理由:
|
||||
1. PHIがある = 既にMIR Builder(if_phi.rs)で処理済み
|
||||
2. JoinIRの役割は「**まだPHIがない**場合にPHI生成する」こと
|
||||
3. 既存PHIを再処理するパターンは設計意図に反する
|
||||
|
||||
---
|
||||
|
||||
## 7. Simple PatternとLocal Patternの真の違い
|
||||
|
||||
### 7.1 Simple Pattern(正しく動作)
|
||||
|
||||
**構造**:
|
||||
```mir
|
||||
bb0: br cond, bb3, bb4
|
||||
|
||||
bb3 (then):
|
||||
%8 = const 100
|
||||
ret %8 ← 直接return(merge blockなし)
|
||||
|
||||
bb4 (else):
|
||||
%11 = const 200
|
||||
ret %11 ← 直接return(merge blockなし)
|
||||
```
|
||||
|
||||
**特徴**:
|
||||
- ❌ merge blockが存在しない
|
||||
- ❌ PHI命令が不要(各ブロックで直接return)
|
||||
- ✅ JoinIR Selectで表現可能
|
||||
|
||||
### 7.2 Local Pattern(既にPHI生成済み)
|
||||
|
||||
**構造**:
|
||||
```mir
|
||||
bb0: br cond, bb3, bb4
|
||||
|
||||
bb3 (then):
|
||||
%8 = const 100
|
||||
br bb5 ← mergeへジャンプ
|
||||
|
||||
bb4 (else):
|
||||
%11 = const 200
|
||||
br bb5 ← mergeへジャンプ
|
||||
|
||||
bb5 (merge):
|
||||
%12 = phi [%8, bb3], [%11, bb4] ← PHI命令(既に生成済み)
|
||||
ret %12
|
||||
```
|
||||
|
||||
**特徴**:
|
||||
- ✅ merge blockが存在
|
||||
- ✅ **PHI命令が既に存在**(if_phi.rsで生成済み)
|
||||
- ❌ JoinIR変換の必要なし(既にSSA形式完成)
|
||||
|
||||
### 7.3 真のLocal Pattern(JoinIR変換すべきケース)
|
||||
|
||||
**仮想的な構造**(実際には生成されない):
|
||||
```mir
|
||||
bb0: br cond, bb3, bb4
|
||||
|
||||
bb3 (then):
|
||||
Store { ptr: &x, value: 100 } ← PHIではなくStore
|
||||
br bb5
|
||||
|
||||
bb4 (else):
|
||||
Store { ptr: &x, value: 200 } ← PHIではなくStore
|
||||
br bb5
|
||||
|
||||
bb5 (merge):
|
||||
[空 - 命令なし] ← PHIなし
|
||||
%v = Load { ptr: &x } ← 値の読み込み
|
||||
ret %v
|
||||
```
|
||||
|
||||
**現実**:
|
||||
- このような構造は**MIR Builderが生成しない**
|
||||
- Nyashは常にSSA形式 → 必ずPHI命令を使用
|
||||
- したがって、try_match_local_pattern()の想定自体が**実装されないケース**を前提にしている
|
||||
|
||||
---
|
||||
|
||||
## 8. Phase 33全体への影響
|
||||
|
||||
### 8.1 Simple Pattern(Phase 33-9.2)
|
||||
|
||||
**現状**: ✅ 完全動作
|
||||
- Const/Copy許容で100%成功
|
||||
- PHI命令なし → JoinIR変換が有意義
|
||||
|
||||
### 8.2 Local Pattern(Phase 33-10)
|
||||
|
||||
**結論**: ⚠️ **Phase 33-10の対象外**
|
||||
- PHI命令あり → 既にif_phi.rsで処理完了
|
||||
- JoinIR変換は不要(無意味な往復)
|
||||
|
||||
**推奨**:
|
||||
- try_match_local_pattern()を**削除**するか、
|
||||
- PHI命令チェックを追加して早期return
|
||||
|
||||
### 8.3 IfMerge Pattern
|
||||
|
||||
**Phase 33-6の設計との整合性**:
|
||||
|
||||
IfMerge設計(if_joinir_design.md:748-823):
|
||||
```rust
|
||||
JoinInst::IfMerge {
|
||||
cond: ValueId,
|
||||
merges: Vec<MergePair>, // 複数のPHI相当
|
||||
}
|
||||
```
|
||||
|
||||
**重要**: IfMergeも「PHI生成」が目的
|
||||
- 入力: AST(複数変数代入)
|
||||
- 出力: MIR(複数PHI命令)
|
||||
- **既存PHI → IfMerge変換ではない**
|
||||
|
||||
---
|
||||
|
||||
## 9. 最終推奨事項
|
||||
|
||||
### 9.1 Phase 33-10の実装方針
|
||||
|
||||
**推奨: Option A(PHI命令を対象外とする)**
|
||||
|
||||
```rust
|
||||
// Phase 33-10: 実装修正
|
||||
fn try_match_local_pattern(...) -> Option<IfPattern> {
|
||||
// Step 1: merge blockの取得
|
||||
let merge_block_id = ...;
|
||||
let merge_block = func.blocks.get(&merge_block_id)?;
|
||||
|
||||
// Step 2: PHI命令チェック(追加)
|
||||
if merge_block.instructions.iter().any(|inst| {
|
||||
matches!(inst, MirInstruction::Phi { .. })
|
||||
}) {
|
||||
// PHI命令がある = if_phi.rsで処理済み → JoinIR変換不要
|
||||
return None;
|
||||
}
|
||||
|
||||
// Step 3: 既存のロジック継続
|
||||
// (PHI命令がない場合のみ到達)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 Phase 33-10の完了判定
|
||||
|
||||
**完了条件**:
|
||||
1. ✅ try_match_local_pattern()にPHIチェック追加
|
||||
2. ✅ 既存ユニットテスト維持(PHI命令なしケース)
|
||||
3. ✅ 実用MIR(PHI命令あり)は正しくフォールバック
|
||||
4. ✅ ドキュメント更新(本レポート)
|
||||
|
||||
**非目標**:
|
||||
- ❌ PHI → Select変換実装(不要)
|
||||
- ❌ local patternの実用MIR対応(既にif_phi.rsで処理済み)
|
||||
|
||||
### 9.3 Phase 33全体の戦略修正
|
||||
|
||||
**修正前の理解**:
|
||||
- Simple/Local/IfMerge の3パターンをJoinIRで実装 → if_phi.rs削除
|
||||
|
||||
**修正後の理解**:
|
||||
- **Simple**: PHI不要 → JoinIR変換有意義 ✅
|
||||
- **Local**: PHI既存 → JoinIR変換不要(if_phi.rs保持) ⚠️
|
||||
- **IfMerge**: 複数PHI → AST→JoinIR経路でのみ有意義 ✅
|
||||
|
||||
**結論**:
|
||||
- if_phi.rsの完全削除は**Phase 34以降に延期**
|
||||
- Phase 33は「PHI生成前の経路にJoinIR挿入」に集中
|
||||
- 既存PHIの再処理は無意味 → 実装しない
|
||||
|
||||
---
|
||||
|
||||
## 10. 参考資料
|
||||
|
||||
### 10.1 実MIR出力(完全版)
|
||||
|
||||
```mir
|
||||
; MIR Module: main
|
||||
; Source: /tmp/test_phi_local.hako
|
||||
|
||||
define i64 @main() {
|
||||
bb0:
|
||||
1: %3: Integer = const 1
|
||||
1: %4: Integer = copy %3
|
||||
1: %5: Integer = copy %4
|
||||
1: br %5, label bb3, label bb4
|
||||
|
||||
bb3:
|
||||
1: %8: Integer = const 100
|
||||
1: br label bb5
|
||||
|
||||
bb4:
|
||||
1: %11: Integer = const 200
|
||||
1: br label bb5
|
||||
|
||||
bb5:
|
||||
1: %12 = phi [%8, bb3], [%11, bb4]
|
||||
1: ret %12
|
||||
}
|
||||
```
|
||||
|
||||
### 10.2 Simple Pattern MIR(Phase 33-9.2)
|
||||
|
||||
```mir
|
||||
define i64 @IfSelectTest.test/1(i64 %0) {
|
||||
bb2:
|
||||
br %3, label bb3, label bb4
|
||||
|
||||
bb3:
|
||||
%4: Integer = const 10
|
||||
ret %4
|
||||
|
||||
bb4:
|
||||
%6: Integer = const 20
|
||||
ret %6
|
||||
}
|
||||
```
|
||||
|
||||
### 10.3 関連ファイル
|
||||
|
||||
- `src/mir/join_ir/lowering/if_select.rs:201-273` - try_match_local_pattern()実装
|
||||
- `docs/private/roadmap2/phases/phase-33-joinir-if-phi-cleanup/if_joinir_design.md` - 設計文書
|
||||
- `src/mir/builder/if_phi.rs` - 現行PHI生成ロジック(316行)
|
||||
|
||||
---
|
||||
|
||||
## 11. ChatGPT実装者への提言
|
||||
|
||||
### 11.1 実装不要の理由
|
||||
|
||||
**結論**: Phase 33-10で local pattern のMIR変換実装は**不要**
|
||||
|
||||
理由:
|
||||
1. 実MIRは既に正しいPHI命令を含む
|
||||
2. JoinIRの役割は「PHI生成」であり「PHI変換」ではない
|
||||
3. 既存のif_phi.rsが正しく動作している
|
||||
4. 無意味な往復変換を避けるべき
|
||||
|
||||
### 11.2 実装すべき内容
|
||||
|
||||
**唯一実装すべきこと**: PHI命令チェックの追加
|
||||
|
||||
```rust
|
||||
// Phase 33-10: 5行の追加のみ
|
||||
if merge_block.instructions.iter().any(|inst| {
|
||||
matches!(inst, MirInstruction::Phi { .. })
|
||||
}) {
|
||||
return None; // PHI命令がある場合は対象外
|
||||
}
|
||||
```
|
||||
|
||||
**効果**:
|
||||
- ✅ 責務の明確化
|
||||
- ✅ 無駄な処理の防止
|
||||
- ✅ 設計意図との整合性
|
||||
|
||||
### 11.3 Phase 33-10完了判定
|
||||
|
||||
**完了条件(簡略化)**:
|
||||
1. ✅ PHIチェック追加(5行)
|
||||
2. ✅ 既存テスト維持
|
||||
3. ✅ ドキュメント更新
|
||||
|
||||
**作業時間**: 30分以内
|
||||
|
||||
---
|
||||
|
||||
**Phase 33-10完了判定**: PHIチェック追加のみで完了とする
|
||||
**次のフェーズ**: Phase 33-11(IfMerge実装、またはPhase 34へ移行)
|
||||
@ -1,141 +0,0 @@
|
||||
# Phase 33-11: Exit Block PHI Node Missing Bug Analysis
|
||||
|
||||
## Problem Summary
|
||||
|
||||
The MIR generated from JoinIR lowering is missing PHI nodes in the exit block, causing "use of undefined value" errors.
|
||||
|
||||
## Test Case
|
||||
|
||||
File: `apps/tests/loop_min_while.hako`
|
||||
|
||||
```nyash
|
||||
static box Main {
|
||||
main() {
|
||||
local i = 0
|
||||
loop(i < 3) {
|
||||
print(i)
|
||||
i = i + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ERROR] use of undefined value ValueId(16)
|
||||
```
|
||||
|
||||
## MIR Dump Analysis
|
||||
|
||||
```mir
|
||||
bb8:
|
||||
1: ret %16 # %16 is never defined!
|
||||
```
|
||||
|
||||
bb8 is the exit block, but ValueId(16) is never defined anywhere.
|
||||
|
||||
## Root Cause
|
||||
|
||||
In `src/mir/builder/control_flow.rs::merge_joinir_mir_blocks()`:
|
||||
|
||||
### Step 1: Exit block is created empty (line 618-621)
|
||||
```rust
|
||||
let exit_block_id = self.block_gen.next();
|
||||
// ...
|
||||
let exit_block = BasicBlock::new(exit_block_id); // Empty!
|
||||
func.add_block(exit_block);
|
||||
```
|
||||
|
||||
### Step 2: Return instructions are converted to Jump (line 827-841)
|
||||
```rust
|
||||
MirInstruction::Return { value } => {
|
||||
if let Some(ret_val) = value {
|
||||
let remapped_val = value_map.get(ret_val).copied().unwrap_or(*ret_val);
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Return({:?}) → Jump to exit",
|
||||
remapped_val
|
||||
);
|
||||
}
|
||||
}
|
||||
MirInstruction::Jump {
|
||||
target: exit_block_id,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Problem**: The return value (`remapped_val`) is logged but NOT STORED anywhere!
|
||||
|
||||
### Step 3: Exit block stays empty
|
||||
The exit block is never populated with:
|
||||
- PHI nodes to collect return values
|
||||
- Return instruction to return the PHI'd value
|
||||
|
||||
## Expected Fix
|
||||
|
||||
The exit block should look like:
|
||||
|
||||
```mir
|
||||
bb8:
|
||||
1: %16 = phi [%9 from bb7], [%2 from bb5], ...
|
||||
1: ret %16
|
||||
```
|
||||
|
||||
Or simpler, if all functions return the same constant:
|
||||
|
||||
```mir
|
||||
bb8:
|
||||
1: %16 = const 0
|
||||
1: ret %16
|
||||
```
|
||||
|
||||
## JoinIR Functions Structure
|
||||
|
||||
The Pattern 1 lowerer generates 3 functions:
|
||||
|
||||
1. **main()**: Calls loop_step, returns 0
|
||||
2. **loop_step()**: Tail recursion OR Jump to k_exit
|
||||
3. **k_exit()**: Returns 0
|
||||
|
||||
All Return instructions are converted to Jump to bb8 (exit block).
|
||||
|
||||
## Solution Strategy
|
||||
|
||||
### Option A: Collect Return Values + Generate PHI
|
||||
1. While converting Return→Jump, collect all return values
|
||||
2. After merging, generate PHI node in exit block
|
||||
3. Add Return instruction that returns PHI result
|
||||
|
||||
### Option B: Direct Value Propagation
|
||||
1. Since Pattern 1 always returns 0, directly emit const 0 in exit block
|
||||
2. Simpler but less general
|
||||
|
||||
### Option C: Track Return Values in Hash Map
|
||||
1. Create `HashMap<BasicBlockId, ValueId>` to track returns
|
||||
2. Use as PHI incoming values
|
||||
3. Most robust, handles all patterns
|
||||
|
||||
## Recommendation
|
||||
|
||||
Start with **Option B** (simplest fix for Pattern 1), then generalize to **Option C** for future patterns.
|
||||
|
||||
## Implementation Location
|
||||
|
||||
File: `src/mir/builder/control_flow.rs`
|
||||
Function: `merge_joinir_mir_blocks()`
|
||||
Lines: ~827-950
|
||||
|
||||
## Test Validation
|
||||
|
||||
```bash
|
||||
NYASH_DISABLE_PLUGINS=1 ./target/release/hakorune apps/tests/loop_min_while.hako
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
0
|
||||
1
|
||||
2
|
||||
```
|
||||
@ -1,399 +0,0 @@
|
||||
# Phase 33-13: trim Pattern Observation
|
||||
|
||||
## 33-13-1: 代表ケースの再観測結果
|
||||
|
||||
### テストケース
|
||||
|
||||
```hako
|
||||
// local_tests/test_trim_simple_pattern.hako
|
||||
method trim_string_simple(s) {
|
||||
if s == null { return "" }
|
||||
|
||||
local start = 0
|
||||
local end = s.length()
|
||||
|
||||
// Loop 1: Trim leading spaces (Pattern2: loop with break)
|
||||
loop(start < end) {
|
||||
local ch = s.substring(start, start + 1)
|
||||
if ch == " " {
|
||||
start = start + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Loop 2: Trim trailing spaces (Pattern2: loop with break)
|
||||
loop(end > start) {
|
||||
local ch = s.substring(end - 1, end)
|
||||
if ch == " " {
|
||||
end = end - 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return s.substring(start, end)
|
||||
}
|
||||
```
|
||||
|
||||
### 観測結果
|
||||
|
||||
#### ルーティング
|
||||
- ✅ `Main.trim_string_simple/1` がホワイトリストに追加済み
|
||||
- ✅ Pattern2_WithBreak として正しく検出
|
||||
|
||||
#### Loop 1 (start キャリア)
|
||||
```
|
||||
[trace:varmap] pattern2_start: ... end→r22, ... start→r19
|
||||
[cf_loop/pattern2] Phase 171-fix: ConditionEnv contains 2 variables:
|
||||
Loop param 'start' → JoinIR ValueId(0)
|
||||
1 condition-only bindings:
|
||||
'end': HOST ValueId(22) → JoinIR ValueId(1)
|
||||
[joinir/pattern2] Phase 172-3: ExitMeta { start → ValueId(9) }
|
||||
```
|
||||
|
||||
- キャリア: `start`
|
||||
- ExitMeta: `start → ValueId(9)`
|
||||
- 条件バインディング: `end` (read-only)
|
||||
|
||||
#### Loop 2 (end キャリア)
|
||||
```
|
||||
[trace:varmap] pattern2_start: ... end→r22, ... start→r32
|
||||
[cf_loop/pattern2] Phase 171-fix: ConditionEnv contains 2 variables:
|
||||
Loop param 'end' → JoinIR ValueId(0)
|
||||
1 condition-only bindings:
|
||||
'start': HOST ValueId(32) → JoinIR ValueId(1)
|
||||
[joinir/pattern2] Phase 172-3: ExitMeta { end → ValueId(9) }
|
||||
```
|
||||
|
||||
- キャリア: `end`
|
||||
- ExitMeta: `end → ValueId(9)`
|
||||
- 条件バインディング: `start` (should use Loop 1's exit value!)
|
||||
|
||||
### 問題発見
|
||||
|
||||
**SSA-undef エラー**:
|
||||
```
|
||||
[ssa-undef-debug] fn=Main.trim_string_simple/1 bb=BasicBlockId(16)
|
||||
used=ValueId(32)
|
||||
inst=Copy { dst: ValueId(37), src: ValueId(32) }
|
||||
```
|
||||
|
||||
**エラーメッセージ**:
|
||||
```
|
||||
[ERROR] ❌ [rust-vm] VM error: Invalid value:
|
||||
use of undefined value ValueId(32)
|
||||
(fn=Main.trim_string_simple/1, last_block=Some(BasicBlockId(16)),
|
||||
last_inst=Some(Copy { dst: ValueId(37), src: ValueId(32) }))
|
||||
```
|
||||
|
||||
### 根本原因分析
|
||||
|
||||
**問題**: 連続ループの SSA 接続
|
||||
|
||||
1. **ループ1終了**: `start` の exit value が variable_map に更新される
|
||||
2. **ループ2開始**: `start` を condition_binding として読む
|
||||
- **BUG**: `HOST ValueId(32)` を参照(ループ1の古い start)
|
||||
- **期待**: `variable_map["start"]` の更新後の値を参照すべき
|
||||
|
||||
### 仮説
|
||||
|
||||
**ExitLineReconnector** が `variable_map` を更新しているはずだが、
|
||||
Pattern2 lowerer が `condition_bindings` を作成する時点で、
|
||||
その更新後の値を参照していない。
|
||||
|
||||
```rust
|
||||
// Pattern2 lowerer の問題箇所
|
||||
let host_id = self.variable_map.get(var_name)
|
||||
.copied()
|
||||
.ok_or_else(|| ...)?;
|
||||
```
|
||||
|
||||
この `variable_map.get()` 呼び出し時点で、
|
||||
前のループの ExitLineReconnector がまだ実行されていない可能性がある。
|
||||
|
||||
### 解決方向性
|
||||
|
||||
**Option A**: ExitLineReconnector の即時実行保証
|
||||
- merge_joinir_mir_blocks() 完了後すぐに variable_map が更新されていることを保証
|
||||
|
||||
**Option B**: condition_bindings の遅延解決
|
||||
- condition_bindings を作成する時点ではなく、
|
||||
JoinIR merge 時に variable_map から最新値を取得
|
||||
|
||||
**Option C**: PHI 接続の修正
|
||||
- ループ2 の PHI 入力が ループ1 の exit block から正しく接続されていることを確認
|
||||
|
||||
### 次のステップ (33-13-2)
|
||||
|
||||
1. ExitLineReconnector の呼び出しタイミングを確認
|
||||
2. variable_map の更新フローを追跡
|
||||
3. 連続ループの SSA 接続を設計
|
||||
|
||||
---
|
||||
|
||||
## 33-13-2: LoopExitContract 設計
|
||||
|
||||
### 現在の ExitMeta 構造
|
||||
|
||||
```rust
|
||||
pub struct ExitMeta {
|
||||
pub exit_values: BTreeMap<String, ValueId>,
|
||||
}
|
||||
```
|
||||
|
||||
### trim の理想的な ExitMeta
|
||||
|
||||
**ループ1**:
|
||||
```
|
||||
ExitMeta {
|
||||
exit_values: {
|
||||
"start": ValueId(X) // ループ出口での start の値
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**ループ2**:
|
||||
```
|
||||
ExitMeta {
|
||||
exit_values: {
|
||||
"end": ValueId(Y) // ループ出口での end の値
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 問題: 連続ループの variable_map 更新
|
||||
|
||||
```
|
||||
初期状態:
|
||||
variable_map["start"] = r19
|
||||
variable_map["end"] = r22
|
||||
|
||||
ループ1 JoinIR merge 後:
|
||||
variable_map["start"] = r35 (remapped exit value)
|
||||
variable_map["end"] = r22 (unchanged)
|
||||
|
||||
ループ2 condition_bindings 構築:
|
||||
start: HOST r??? → JoinIR ValueId(1) // r35 or r32?
|
||||
|
||||
ループ2 JoinIR merge 後:
|
||||
variable_map["end"] = r48 (remapped exit value)
|
||||
```
|
||||
|
||||
### 契約 (Contract)
|
||||
|
||||
**ExitLineReconnector の契約**:
|
||||
1. merge_joinir_mir_blocks() 完了時点で variable_map が更新されている
|
||||
2. 後続のコードは variable_map["carrier"] で最新の出口値を取得できる
|
||||
|
||||
**Pattern2 lowerer の契約**:
|
||||
1. condition_bindings は variable_map の **現在の値** を使用する
|
||||
2. ループ開始時点での variable_map が正しく更新されていることを前提とする
|
||||
|
||||
### 検証ポイント
|
||||
|
||||
1. `merge_joinir_mir_blocks()` の最後で ExitLineOrchestrator が呼ばれているか?
|
||||
2. ExitLineReconnector が variable_map を正しく更新しているか?
|
||||
3. Pattern2 lowerer が condition_bindings を構築するタイミングは正しいか?
|
||||
|
||||
---
|
||||
|
||||
## 🎯 33-13-2: 根本原因特定!
|
||||
|
||||
### 問題のフロー
|
||||
|
||||
```
|
||||
1. ExitMetaCollector: exit_bindings 作成
|
||||
- start → JoinIR ValueId(9)
|
||||
|
||||
2. merge_joinir_mir_blocks:
|
||||
- JoinIR ValueId(9) → HOST ValueId(32) (remap)
|
||||
|
||||
3. exit_phi_builder: PHI 作成
|
||||
- phi_dst = builder.value_gen.next() → ValueId(0) ← NEW VALUE!
|
||||
- PHI { dst: ValueId(0), inputs: [..., ValueId(32)] }
|
||||
|
||||
4. ExitLineReconnector: variable_map 更新
|
||||
- variable_map["start"] = remapper.get_value(JoinIR ValueId(9)) = ValueId(32)
|
||||
|
||||
5. 問題!
|
||||
- variable_map["start"] = ValueId(32)
|
||||
- BUT PHI が定義しているのは ValueId(0)
|
||||
- → ValueId(32) は未定義!
|
||||
```
|
||||
|
||||
### 根本原因
|
||||
|
||||
**exit_phi_builder と ExitLineReconnector の不整合**:
|
||||
- `exit_phi_builder` は新しい `phi_dst` を割り当て
|
||||
- `ExitLineReconnector` は `remapped_exit` を variable_map に設定
|
||||
- **PHI が定義している ValueId と variable_map が指す ValueId が異なる**
|
||||
|
||||
### 設計上の制限
|
||||
|
||||
**単一 PHI 問題**:
|
||||
- 現在の `exit_phi_builder` は **1つの PHI** しか作らない
|
||||
- しかし trim は **2つのキャリア**(start, end)を持つ
|
||||
- 複数キャリアには **複数の exit PHI** が必要
|
||||
|
||||
### 解決方向性
|
||||
|
||||
**Option A**: ExitLineReconnector を exit_phi_result を使うように変更
|
||||
- シンプルだが、複数キャリアには対応できない
|
||||
|
||||
**Option B**: exit_phi_builder を複数キャリア対応に拡張
|
||||
- 各 exit_binding ごとに PHI を作成
|
||||
- ExitLineReconnector はその PHI の dst を variable_map に設定
|
||||
|
||||
**Option C**: exit_bindings から直接 PHI を作成する新 Box
|
||||
- ExitLinePHIBuilder Box を新設
|
||||
- 責任分離: PHI 作成と variable_map 更新を統合
|
||||
|
||||
**推奨**: Option B + C のハイブリッド
|
||||
- exit_phi_builder を拡張して exit_bindings を受け取る
|
||||
- 各キャリアごとに PHI を作成し、その dst を返す
|
||||
- ExitLineReconnector はその結果を variable_map に設定
|
||||
|
||||
---
|
||||
|
||||
## 次のステップ
|
||||
|
||||
### Phase 33-13-3: exit_phi_builder の複数キャリア対応
|
||||
|
||||
1. exit_bindings を exit_phi_builder に渡す
|
||||
2. 各キャリアごとに PHI を作成
|
||||
3. 各 PHI の dst を carrier → phi_dst マップとして返す
|
||||
4. ExitLineReconnector がそのマップを使って variable_map を更新
|
||||
|
||||
### Phase 33-13-4: 統合テスト
|
||||
|
||||
1. 単一キャリア(Pattern 2 simple)が動作確認
|
||||
2. 複数キャリア(trim)が動作確認
|
||||
|
||||
---
|
||||
|
||||
## Phase 33-13-2: 「式結果 PHI」と「キャリア PHI」の責務分離設計
|
||||
|
||||
### アーキテクチャ分析
|
||||
|
||||
現在のフロー:
|
||||
```
|
||||
1. instruction_rewriter: Return の戻り値を exit_phi_inputs に収集
|
||||
- exit_phi_inputs.push((new_block_id, remapped_val))
|
||||
- これは「式としての戻り値」(例: loop_min_while() の結果)
|
||||
|
||||
2. exit_phi_builder: exit_phi_inputs から式結果 PHI を1つ作成
|
||||
- PHI { dst: NEW_ID, inputs: exit_phi_inputs }
|
||||
- 「式としての戻り値」用
|
||||
|
||||
3. ExitLineReconnector: exit_bindings の remapped_exit を variable_map に設定
|
||||
- variable_map[carrier] = remapper.get_value(join_exit_value)
|
||||
- これは「キャリア更新」用
|
||||
```
|
||||
|
||||
### 根本問題の再確認
|
||||
|
||||
**問題**: ExitLineReconnector が設定する `remapped_exit` は、
|
||||
**exit_block に到達する前のブロック**で定義されている。
|
||||
|
||||
しかし SSA では、exit_block 以降から参照するには、
|
||||
**exit_block 内で PHI で合流させる必要がある**!
|
||||
|
||||
```
|
||||
# 問題のあるコード # 正しいコード
|
||||
k_exit: k_exit:
|
||||
// 何もない %start_final = phi [(%bb_A, %32), (%bb_B, %35)]
|
||||
// exit_block 以降で %32 参照 // exit_block 以降で %start_final 参照
|
||||
// → %32 は未定義! // → OK!
|
||||
```
|
||||
|
||||
### 責務分離設計
|
||||
|
||||
#### 式結果 PHI (exit_phi_builder 担当)
|
||||
|
||||
**用途**: ループが「値を返す式」として使われる場合
|
||||
```hako
|
||||
local result = loop_min_while(...) // ← 式として使用
|
||||
```
|
||||
|
||||
**実装**:
|
||||
- `instruction_rewriter` が Return の戻り値を収集
|
||||
- `exit_phi_builder` がそれらを合流させる PHI を生成
|
||||
- 返り値: `Option<ValueId>` (PHI の dst、または None)
|
||||
|
||||
#### キャリア PHI (新設: ExitCarrierPHIBuilder 担当)
|
||||
|
||||
**用途**: ループが「状態を更新するだけ」の場合
|
||||
```hako
|
||||
// trim パターン: start, end を更新
|
||||
loop(start < end) { ... } // start キャリア
|
||||
loop(end > start) { ... } // end キャリア
|
||||
```
|
||||
|
||||
**実装案**:
|
||||
1. `exit_bindings` から各キャリアの exit value を取得
|
||||
2. 各キャリアごとに **PHI を生成**
|
||||
3. PHI の dst を返す `BTreeMap<String, ValueId>`
|
||||
4. ExitLineReconnector がその phi_dst を variable_map に設定
|
||||
|
||||
### 修正計画
|
||||
|
||||
#### Phase 33-13-3: exit_phi_builder をキャリア PHI 対応に拡張
|
||||
|
||||
**変更前**:
|
||||
```rust
|
||||
pub fn build_exit_phi(
|
||||
builder: &mut MirBuilder,
|
||||
exit_block_id: BasicBlockId,
|
||||
exit_phi_inputs: &[(BasicBlockId, ValueId)], // 式結果のみ
|
||||
debug: bool,
|
||||
) -> Result<Option<ValueId>, String>
|
||||
```
|
||||
|
||||
**変更後**:
|
||||
```rust
|
||||
pub struct ExitPhiResult {
|
||||
pub expr_result: Option<ValueId>, // 式結果 PHI (従来)
|
||||
pub carrier_phis: BTreeMap<String, ValueId>, // キャリア PHI (新設)
|
||||
}
|
||||
|
||||
pub fn build_exit_phi(
|
||||
builder: &mut MirBuilder,
|
||||
exit_block_id: BasicBlockId,
|
||||
exit_phi_inputs: &[(BasicBlockId, ValueId)],
|
||||
carrier_inputs: &BTreeMap<String, Vec<(BasicBlockId, ValueId)>>, // NEW
|
||||
debug: bool,
|
||||
) -> Result<ExitPhiResult, String>
|
||||
```
|
||||
|
||||
#### Phase 33-13-4: ExitLineReconnector を phi_dst を使うように修正
|
||||
|
||||
**変更前** (reconnector.rs 99-107行):
|
||||
```rust
|
||||
if let Some(remapped_value) = remapper.get_value(binding.join_exit_value) {
|
||||
if let Some(var_vid) = builder.variable_map.get_mut(&binding.carrier_name) {
|
||||
*var_vid = remapped_value; // ← remapped_exit を直接使用
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**変更後**:
|
||||
```rust
|
||||
if let Some(phi_dst) = carrier_phis.get(&binding.carrier_name) {
|
||||
if let Some(var_vid) = builder.variable_map.get_mut(&binding.carrier_name) {
|
||||
*var_vid = *phi_dst; // ← PHI の dst を使用
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 設計上の決定事項
|
||||
|
||||
1. **式結果 PHI は exit_phi_builder が担当** (変更なし)
|
||||
2. **キャリア PHI は exit_phi_builder が追加で担当** (拡張)
|
||||
3. **ExitLineReconnector は PHI の dst を variable_map に設定** (修正)
|
||||
4. **exit_bindings は SSOT として維持** (変更なし)
|
||||
|
||||
---
|
||||
|
||||
## Date: 2025-12-07
|
||||
## Status: In Progress - Design Phase Complete!
|
||||
@ -1,3 +1,6 @@
|
||||
Status: Active
|
||||
Scope: Phase 33-16(Loop Header PHI SSOT)に関する現役目次。歴史詳細は archive 側を参照。
|
||||
|
||||
# Phase 33-16: Loop Header PHI SSOT - Documentation Index
|
||||
|
||||
**Last Updated**: 2025-12-07
|
||||
|
||||
@ -1,504 +0,0 @@
|
||||
# Phase 33-16 Implementation Plan: Loop Header PHI SSOT
|
||||
|
||||
**Date**: 2025-12-07
|
||||
**Status**: Detailed design & concrete implementation steps ready
|
||||
**Scope**: 6 concrete code changes to establish loop header PHI as Single Source of Truth
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Phase 33-16 transforms loop exit value handling from a "skip and hope" approach to a principled architecture where **loop header PHIs** track carrier values through iterations and serve as the single source of truth for exit values.
|
||||
|
||||
**Key Architecture Change**:
|
||||
```
|
||||
Before (Phase 33-15):
|
||||
LoopVariable → BoundaryInjector Copy → (undefined in latch) → SSA-undef
|
||||
Carrier → ExitLine reconnector → (no proper header PHI) → wrong value
|
||||
|
||||
After (Phase 33-16):
|
||||
LoopVariable → BoundaryInjector Copy → Loop Header PHI → Latch Copy → Loop Header PHI → Exit value
|
||||
Carrier → (tracked by same PHI) → ExitLine reconnector → correct value
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Problem Analysis (Consolidated from Phase 33-15)
|
||||
|
||||
### Current State
|
||||
From `instruction_rewriter.rs` lines 354-431, we currently:
|
||||
- **Skip exit_phi_inputs** (line 395): "Parameter values undefined"
|
||||
- **Skip carrier_inputs** (line 429): "Parameter references undefined"
|
||||
|
||||
**Root Cause**: Loop parameters (i_param, i_exit) reference function parameters passed via Jump args, not SSA definitions in inlined MIR.
|
||||
|
||||
### Why Loop Header PHI Solves This
|
||||
|
||||
When we inline JoinIR into MIR, the loop structure becomes:
|
||||
```text
|
||||
entry_block:
|
||||
i_phi_dst = PHI [(entry_block, i_init), (latch_block, i_next)]
|
||||
// Loop condition using i_phi_dst (not i_param)
|
||||
|
||||
latch_block:
|
||||
i_next = i + 1
|
||||
JUMP entry_block
|
||||
|
||||
exit_block:
|
||||
return i_phi_dst // Uses PHI dst, not parameter!
|
||||
```
|
||||
|
||||
The **PHI dst is SSA-defined** at the header, so it can be referenced in exit values.
|
||||
|
||||
---
|
||||
|
||||
## 6 Concrete Implementation Steps
|
||||
|
||||
### Step 1: Integrate LoopHeaderPhiBuilder into merge pipeline
|
||||
|
||||
**Location**: `src/mir/builder/control_flow/joinir/merge/mod.rs` (lines 62-184)
|
||||
|
||||
**Change**: Between Phase 3 (remap_values) and Phase 4 (instruction_rewriter), insert Phase 3.5:
|
||||
|
||||
```rust
|
||||
// Line 107: After remap_values(...)
|
||||
remap_values(builder, &used_values, &mut remapper, debug)?;
|
||||
|
||||
// NEW Phase 3.5: Build loop header PHIs
|
||||
// This must happen BEFORE instruction_rewriter so we know the PHI dsts
|
||||
let mut loop_header_phi_info = if let Some(boundary) = boundary {
|
||||
if let Some(loop_var_name) = &boundary.loop_var_name {
|
||||
// We need header_block_id and entry_block_id from the JoinIR structure
|
||||
// Entry block is the first function's entry block
|
||||
let (entry_func_name, entry_func) = mir_module
|
||||
.functions
|
||||
.iter()
|
||||
.next()
|
||||
.ok_or("JoinIR module has no functions")?;
|
||||
let entry_block_remapped = remapper
|
||||
.get_block(entry_func_name, entry_func.entry_block)
|
||||
.ok_or_else(|| format!("Entry block not found"))?;
|
||||
|
||||
// Header block = entry block in the simplest case
|
||||
// For more complex patterns, may need pattern-specific logic
|
||||
let header_block_id = entry_block_remapped;
|
||||
|
||||
// Get loop variable's initial value (remapped)
|
||||
let loop_var_init = remapper
|
||||
.get_value(ValueId(0)) // JoinIR param slot
|
||||
.ok_or("Loop var init not remapped")?;
|
||||
|
||||
// For now, no other carriers (Phase 33-16 minimal)
|
||||
let carriers = vec![];
|
||||
let expr_result_is_loop_var = boundary.expr_result.is_some();
|
||||
|
||||
loop_header_phi_builder::LoopHeaderPhiBuilder::build(
|
||||
builder,
|
||||
header_block_id,
|
||||
entry_block_remapped,
|
||||
loop_var_name,
|
||||
loop_var_init,
|
||||
&carriers,
|
||||
expr_result_is_loop_var,
|
||||
debug,
|
||||
)?
|
||||
} else {
|
||||
loop_header_phi_builder::LoopHeaderPhiInfo::empty(BasicBlockId(0))
|
||||
}
|
||||
} else {
|
||||
loop_header_phi_builder::LoopHeaderPhiInfo::empty(BasicBlockId(0))
|
||||
};
|
||||
|
||||
// Phase 4: Merge blocks and rewrite instructions
|
||||
// PASS loop_header_phi_info to instruction_rewriter
|
||||
let merge_result = instruction_rewriter::merge_and_rewrite(
|
||||
builder,
|
||||
mir_module,
|
||||
&mut remapper,
|
||||
&value_to_func_name,
|
||||
&function_params,
|
||||
boundary,
|
||||
&mut loop_header_phi_info, // NEW: Pass mutable reference
|
||||
debug,
|
||||
)?;
|
||||
```
|
||||
|
||||
**Key Points**:
|
||||
- Phase 3.5 executes after remap_values so we have remapped ValueIds
|
||||
- Allocates PHI dsts but doesn't emit PHI instructions yet
|
||||
- Stores PHI dst in LoopHeaderPhiInfo for use in Phase 4
|
||||
|
||||
---
|
||||
|
||||
### Step 2: Modify instruction_rewriter signature and latch tracking
|
||||
|
||||
**Location**: `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs` (lines 29-37)
|
||||
|
||||
**Change**: Add loop_header_phi_info parameter:
|
||||
|
||||
```rust
|
||||
pub(super) fn merge_and_rewrite(
|
||||
builder: &mut crate::mir::builder::MirBuilder,
|
||||
mir_module: &MirModule,
|
||||
remapper: &mut JoinIrIdRemapper,
|
||||
value_to_func_name: &HashMap<ValueId, String>,
|
||||
function_params: &HashMap<String, Vec<ValueId>>,
|
||||
boundary: Option<&JoinInlineBoundary>,
|
||||
loop_header_phi_info: &mut loop_header_phi_builder::LoopHeaderPhiInfo, // NEW
|
||||
debug: bool,
|
||||
) -> Result<MergeResult, String> {
|
||||
// ... existing code ...
|
||||
}
|
||||
```
|
||||
|
||||
**Change in return logic** (lines 346-459, in the terminator rewriting section):
|
||||
|
||||
When we process `MirInstruction::Return { value }` in the latch block, track the latch incoming:
|
||||
|
||||
```rust
|
||||
// After determining tail_call_target for tail call to loop header
|
||||
if let Some((target_block, args)) = tail_call_target {
|
||||
if debug {
|
||||
eprintln!("[cf_loop/joinir] Inserting param bindings for tail call to {:?}", target_block);
|
||||
}
|
||||
|
||||
// ... existing param binding code (lines 276-319) ...
|
||||
|
||||
// NEW: Track latch incoming for loop header PHI
|
||||
// The tail_call_target is the loop header, and args are the updated values
|
||||
// For Pattern 2, args[0] is the updated loop variable
|
||||
if let Some(loop_var_name) = &boundary.map(|b| &b.loop_var_name).flatten() {
|
||||
if !args.is_empty() {
|
||||
let latch_value = args[0]; // Updated loop variable value
|
||||
loop_header_phi_info.set_latch_incoming(
|
||||
loop_var_name,
|
||||
target_block, // latch block ID
|
||||
latch_value,
|
||||
);
|
||||
|
||||
if debug {
|
||||
eprintln!("[cf_loop/joinir] Phase 33-16: Set latch incoming for '{}': {:?}",
|
||||
loop_var_name, latch_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ... existing terminator code ...
|
||||
}
|
||||
```
|
||||
|
||||
**Key Points**:
|
||||
- After instruction rewriter completes, loop_header_phi_info has both entry and latch incoming set
|
||||
- Latch incoming is extracted from tail call args (the actual updated values)
|
||||
|
||||
---
|
||||
|
||||
### Step 3: Replace skip logic with header PHI references
|
||||
|
||||
**Location**: `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs` (lines 354-431)
|
||||
|
||||
**Change**: Replace the skip logic with proper PHI references:
|
||||
|
||||
```rust
|
||||
// OLD CODE (lines 354-398): Skip exit_phi_inputs collection
|
||||
// REMOVE: All the comments about "parameter values undefined"
|
||||
|
||||
// NEW CODE:
|
||||
if let Some(ret_val) = value {
|
||||
let remapped_val = remapper.get_value(*ret_val).unwrap_or(*ret_val);
|
||||
|
||||
// Phase 33-16: Use header PHI dst if available
|
||||
if let Some(loop_var_name) = &boundary.and_then(|b| b.loop_var_name.as_ref()) {
|
||||
if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(loop_var_name) {
|
||||
if debug {
|
||||
eprintln!("[cf_loop/joinir] Phase 33-16: Using loop header PHI {:?} for exit value (not parameter {:?})",
|
||||
phi_dst, ret_val);
|
||||
}
|
||||
// Collect the PHI dst as exit value, not the parameter
|
||||
exit_phi_inputs.push((exit_block_id, phi_dst));
|
||||
} else {
|
||||
if debug {
|
||||
eprintln!("[cf_loop/joinir] Phase 33-16 WARNING: No header PHI for loop var '{}', using parameter {:?}",
|
||||
loop_var_name, ret_val);
|
||||
}
|
||||
// Fallback: use parameter (for compatibility)
|
||||
exit_phi_inputs.push((exit_block_id, remapped_val));
|
||||
}
|
||||
} else {
|
||||
if debug {
|
||||
eprintln!("[cf_loop/joinir] Phase 33-16: No loop_var_name in boundary, using parameter {:?}", ret_val);
|
||||
}
|
||||
// Fallback: use parameter (for non-loop patterns)
|
||||
exit_phi_inputs.push((exit_block_id, remapped_val));
|
||||
}
|
||||
}
|
||||
|
||||
// OLD CODE (lines 400-431): Skip carrier_inputs collection
|
||||
// REMOVE: All the comments about "parameter values undefined"
|
||||
|
||||
// NEW CODE:
|
||||
// Phase 33-13/16: Collect carrier exit values using header PHI dsts
|
||||
if let Some(boundary) = boundary {
|
||||
for binding in &boundary.exit_bindings {
|
||||
// Phase 33-16: Look up the header PHI dst for this carrier
|
||||
if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(&binding.carrier_name) {
|
||||
if debug {
|
||||
eprintln!("[cf_loop/joinir] Phase 33-16: Using header PHI {:?} for carrier '{}' exit",
|
||||
phi_dst, binding.carrier_name);
|
||||
}
|
||||
carrier_inputs.entry(binding.carrier_name.clone())
|
||||
.or_insert_with(Vec::new)
|
||||
.push((exit_block_id, phi_dst));
|
||||
} else {
|
||||
if debug {
|
||||
eprintln!("[cf_loop/joinir] Phase 33-16 WARNING: No header PHI for carrier '{}', skipping",
|
||||
binding.carrier_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Points**:
|
||||
- Instead of skipping, we use the loop header PHI dsts
|
||||
- Loop header PHI dsts are guaranteed to be SSA-defined
|
||||
- Fallback to parameter for backward compatibility
|
||||
|
||||
---
|
||||
|
||||
### Step 4: Finalize header PHIs in the exit block
|
||||
|
||||
**Location**: `src/mir/builder/control_flow/joinir/merge/mod.rs` (lines 120-136)
|
||||
|
||||
**Change**: After Phase 5 (exit_phi_builder), finalize the header PHIs:
|
||||
|
||||
```rust
|
||||
// Phase 5: Build exit PHI (expr result and carrier PHIs)
|
||||
let (exit_phi_result_id, carrier_phis) = exit_phi_builder::build_exit_phi(
|
||||
builder,
|
||||
merge_result.exit_block_id,
|
||||
&merge_result.exit_phi_inputs,
|
||||
&merge_result.carrier_inputs,
|
||||
debug,
|
||||
)?;
|
||||
|
||||
// Phase 33-16 NEW: Finalize loop header PHIs
|
||||
// This inserts the PHI instructions at the beginning of the header block
|
||||
// with both entry and latch incoming edges now set
|
||||
loop_header_phi_builder::LoopHeaderPhiBuilder::finalize(
|
||||
builder,
|
||||
&loop_header_phi_info,
|
||||
debug,
|
||||
)?;
|
||||
|
||||
// Phase 6: Reconnect boundary (if specified)
|
||||
// ...
|
||||
```
|
||||
|
||||
**Key Points**:
|
||||
- finalize() inserts PHI instructions at the beginning of header block
|
||||
- Must happen after instruction_rewriter sets latch incoming
|
||||
- Before ExitLineOrchestrator so carrier_phis includes header PHI dsts
|
||||
|
||||
---
|
||||
|
||||
### Step 5: Update pattern lowerers to set loop_var_name and extract carriers
|
||||
|
||||
**Location**: `src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs` (lines 193-200)
|
||||
|
||||
**Change**: Extract carrier information and pass to LoopHeaderPhiBuilder:
|
||||
|
||||
```rust
|
||||
// Line 200: Already sets loop_var_name ✓
|
||||
boundary.loop_var_name = Some(loop_var_name.clone());
|
||||
|
||||
// NEW: Extract other carriers from exit_bindings
|
||||
// For Pattern 2 with multiple carriers (Phase 33-16 extension)
|
||||
let carriers: Vec<(String, ValueId)> = boundary.exit_bindings.iter()
|
||||
.filter(|binding| binding.carrier_name != loop_var_name) // Skip loop var
|
||||
.map(|binding| {
|
||||
// Get the initial value for this carrier from join_inputs
|
||||
// This is a simplification; real patterns may need more sophisticated extraction
|
||||
let init_val = /* extract from join_inputs based on binding.carrier_name */;
|
||||
(binding.carrier_name.clone(), init_val)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// The carriers list is then passed to LoopHeaderPhiBuilder::build()
|
||||
// (in Step 1, where merge_and_rewrite is called)
|
||||
```
|
||||
|
||||
**Key Points**:
|
||||
- Minimal change: mostly just data extraction
|
||||
- Carriers are extracted from exit_bindings which are already computed
|
||||
|
||||
---
|
||||
|
||||
### Step 6: Update Module Documentation
|
||||
|
||||
**Location**: `src/mir/builder/control_flow/joinir/merge/mod.rs` (lines 1-60)
|
||||
|
||||
**Change**: Update phase documentation to include Phase 3.5:
|
||||
|
||||
```rust
|
||||
//! JoinIR MIR Block Merging Coordinator
|
||||
//!
|
||||
//! This module coordinates the merging of JoinIR-generated MIR functions
|
||||
//! into the host MIR builder. The process is broken into 7 phases:
|
||||
//!
|
||||
//! 1. Block ID allocation (block_allocator.rs)
|
||||
//! 2. Value collection (value_collector.rs)
|
||||
//! 3. ValueId remapping (uses JoinIrIdRemapper)
|
||||
//! 3.5. Loop header PHI generation (loop_header_phi_builder.rs) [NEW - Phase 33-16]
|
||||
//! 4. Instruction rewriting (instruction_rewriter.rs)
|
||||
//! 5. Exit PHI construction (exit_phi_builder.rs)
|
||||
//! 6. Boundary reconnection (inline in this file)
|
||||
//!
|
||||
//! Phase 33-16: Loop header PHI as SSOT
|
||||
//! ====================================
|
||||
//!
|
||||
//! Phase 33-16 establishes loop header PHIs as the Single Source of Truth
|
||||
//! for carrier values during loop execution:
|
||||
//!
|
||||
//! - Phase 3.5: Generate header PHIs with entry incoming edges
|
||||
//! - Phase 4: Instruction rewriter sets latch incoming edges + uses PHI dsts
|
||||
//! for exit values instead of undefined loop parameters
|
||||
//! - Phase 4.5: Finalize header PHIs (insert into blocks)
|
||||
//! - Phase 6: ExitLineOrchestrator uses header PHI dsts from carrier_phis
|
||||
//!
|
||||
//! This fixes the SSA-undef errors from Phase 33-15 by ensuring all exit
|
||||
//! values reference SSA-defined PHI destinations, not function parameters.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests (for LoopHeaderPhiBuilder)
|
||||
|
||||
Already exist in `loop_header_phi_builder.rs` (lines 284-318):
|
||||
- `test_loop_header_phi_info_creation()`: Empty info
|
||||
- `test_carrier_phi_entry()`: Carrier setup and latch incoming
|
||||
|
||||
### Integration Tests
|
||||
|
||||
**Test Case 1**: Pattern 2 with loop variable (existing `joinir_min_loop.hako`)
|
||||
- Verify header PHI is created
|
||||
- Verify latch incoming is set from tail call args
|
||||
- Verify exit PHI uses header PHI dst (not parameter)
|
||||
|
||||
**Test Case 2**: Pattern 3 with multiple carriers (if implemented)
|
||||
- Verify each carrier has a header PHI
|
||||
- Verify exit values reference header PHI dsts
|
||||
- Verify variable_map is updated with carrier PHI dsts
|
||||
|
||||
### Debug Output Verification
|
||||
|
||||
```bash
|
||||
# Run with debug enabled
|
||||
NYASH_JOINIR_DEBUG=1 ./target/release/nyash --dump-mir test_file.hako 2>&1 | grep "Phase 33-16"
|
||||
|
||||
# Expected output:
|
||||
# [cf_loop/joinir] Phase 33-16: Building header PHIs at BasicBlockId(N)
|
||||
# [cf_loop/joinir] Loop var 'i' init=ValueId(X), entry_block=BasicBlockId(Y)
|
||||
# [cf_loop/joinir] Loop var PHI: ValueId(Z) = phi [(from BasicBlockId(Y), ValueId(X)), (latch TBD)]
|
||||
# [cf_loop/joinir] Phase 33-16: Set latch incoming for 'i': ValueId(W)
|
||||
# [cf_loop/joinir] Phase 33-16: Using loop header PHI ValueId(Z) for exit value
|
||||
# [cf_loop/joinir] Phase 33-16: Finalizing header PHIs at BasicBlockId(N)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] **Step 1**: Add Phase 3.5 to merge/mod.rs (LoopHeaderPhiBuilder::build call)
|
||||
- [ ] **Step 2**: Update instruction_rewriter signature and latch tracking
|
||||
- [ ] **Step 3**: Replace skip logic with header PHI references
|
||||
- [ ] **Step 4**: Add finalize call in merge pipeline
|
||||
- [ ] **Step 5**: Update pattern2 lowerer (loop_var_name already set, extract carriers)
|
||||
- [ ] **Step 6**: Update module documentation
|
||||
- [ ] **Compile**: `cargo build --release`
|
||||
- [ ] **Test**: Run joinir_min_loop.hako and verify MIR
|
||||
- [ ] **Debug**: Enable NYASH_JOINIR_DEBUG=1 and verify output
|
||||
|
||||
---
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
### Why Phase 3.5?
|
||||
- Must happen after remap_values() (need remapped ValueIds)
|
||||
- Must happen before instruction_rewriter (need to know PHI dsts for exit values)
|
||||
- Clear responsibility separation
|
||||
|
||||
### Why LoopHeaderPhiInfo as mutable?
|
||||
- instruction_rewriter sets latch incoming via set_latch_incoming()
|
||||
- Finalize reads all latch incoming to validate completeness
|
||||
- Mutable reference is cleaner than returning modified info
|
||||
|
||||
### Why keep fallback to parameters?
|
||||
- Not all patterns may use loop header PHIs yet
|
||||
- Backward compatibility with Phase 33-15
|
||||
- Easier to debug regressions
|
||||
|
||||
### Why separate build() and finalize()?
|
||||
- build() allocates ValueIds (Phase 3.5)
|
||||
- finalize() emits instructions (Phase 4.5)
|
||||
- Clear two-phase commit pattern
|
||||
- Allows validation that all latch incoming are set
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & Imports
|
||||
|
||||
**No new external dependencies**. Uses existing modules:
|
||||
- `loop_header_phi_builder` (already created in loop_header_phi_builder.rs)
|
||||
- `instruction_rewriter` (existing)
|
||||
- `block_allocator` (existing)
|
||||
- JoinIR lowering modules (existing)
|
||||
|
||||
---
|
||||
|
||||
## Risk Analysis
|
||||
|
||||
### Low Risk
|
||||
✅ LoopHeaderPhiBuilder already implemented and tested
|
||||
✅ Using existing pattern lowerer infrastructure
|
||||
✅ Fallback mechanism preserves backward compatibility
|
||||
|
||||
### Medium Risk
|
||||
⚠️ Loop header block identification (Step 1): Currently assumes entry block = header block
|
||||
- **Mitigation**: Add debug logging to verify correct block
|
||||
- **Future**: May need pattern-specific logic for complex loops
|
||||
|
||||
⚠️ Carrier extraction (Step 5): Currently minimal
|
||||
- **Mitigation**: Phase 33-16 focuses on loop variable; carriers in Phase 33-16+
|
||||
- **Future**: Will be enhanced as more patterns are implemented
|
||||
|
||||
### Testing Requirements
|
||||
- [ ] Compile clean (no warnings)
|
||||
- [ ] joinir_min_loop.hako produces correct MIR
|
||||
- [ ] Variable values are correct at loop exit
|
||||
- [ ] No SSA-undef errors in MIR verification
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements (Phase 33-16+)
|
||||
|
||||
1. **Pattern 3 with multiple carriers**: Extract all carriers from exit_bindings
|
||||
2. **Dynamic carrier discovery**: Analyze exit_bindings at runtime to determine carriers
|
||||
3. **Pattern-specific header block**: Some patterns may have different header structure
|
||||
4. **Optimization**: Eliminate redundant PHI nodes (constant carriers)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Phase 33-16 transforms exit value handling from "skip and hope" to "SSA-correct by design" through:
|
||||
|
||||
1. Allocating loop header PHI dsts early (Phase 3.5)
|
||||
2. Tracking latch incoming through instruction rewriting (Phase 4)
|
||||
3. Using PHI dsts instead of parameters in exit values (Phase 4/5)
|
||||
4. Finalizing PHIs for SSA verification (Phase 4.5)
|
||||
5. Passing correct values to ExitLineOrchestrator (Phase 6)
|
||||
|
||||
This eliminates SSA-undef errors while maintaining clear separation of concerns and backward compatibility.
|
||||
@ -1,245 +0,0 @@
|
||||
# Phase 33‑16: Loop Header PHI as Exit SSOT — Design
|
||||
|
||||
日付: 2025‑12‑07
|
||||
状態: 設計フェーズ完了(実装前の設計メモ)
|
||||
|
||||
このフェーズでは、JoinIR → MIR マージ時の「ループ出口値の扱い」を、
|
||||
|
||||
- expr 結果ライン(`loop` を式として使うケース)
|
||||
- carrier ライン(`start/end/sum` など状態更新だけを行うケース)
|
||||
|
||||
で構造的に整理し直すよ。
|
||||
|
||||
目的は:
|
||||
|
||||
1. SSA‑undef を根本的に防ぐ(継続関数パラメータを PHI 入力に使わない)
|
||||
2. ループヘッダ PHI を「ループ変数の真の現在値」の SSOT にする
|
||||
3. ExitLine(ExitMeta/ExitBinding/ExitLineReconnector)と expr PHI をきれいに分離する
|
||||
|
||||
---
|
||||
|
||||
## 1. 現状の問題整理
|
||||
|
||||
### 1.1 何が壊れているか
|
||||
|
||||
- Pattern2(`joinir_min_loop.hako`)などで:
|
||||
- 以前は `Return { value: i_param }` をそのまま exit PHI の入力に使っていた
|
||||
- JoinIR のパラメータ `i_param` は、MIR に inline された時点では **SSA 定義を持たない**
|
||||
- そのため PHI 入力に remap された ValueId が未定義になり、SSA‑undef が発生していた
|
||||
|
||||
- Phase 33‑15 では:
|
||||
- `value_collector` から JoinIR パラメータを除外
|
||||
- `instruction_rewriter` の `exit_phi_inputs` / `carrier_inputs` 収集を一時停止
|
||||
- → SSA‑undef は解消したが、「ループ出口値」が初期値のままになっている
|
||||
|
||||
### 1.2 何が足りていないか
|
||||
|
||||
本来必要だったものは:
|
||||
|
||||
1. ループヘッダに置かれる PHI(`i_phi`, `sum_phi` など)を、
|
||||
「ループの現在値」として扱う仕組み(構造上の SSOT)。
|
||||
2. expr 結果ライン:
|
||||
- 「どの ValueId が `loop` 式の最終結果か」を Loop ヘッダ PHI を通じて知ること。
|
||||
3. carrier ライン:
|
||||
- ExitLine が、ヘッダ PHI の `dst` をキャリア出口として利用できるようにすること。
|
||||
|
||||
今まではこの部分が暗黙のまま進んでいたため、JoinIR パラメータに依存した不安定な出口線になっていた。
|
||||
|
||||
---
|
||||
|
||||
## 2. マージパイプラインと Phase 3.5 追加
|
||||
|
||||
現在の JoinIR → MIR マージパイプラインは概念的にこうなっている:
|
||||
|
||||
1. Phase 1: block_allocator
|
||||
JoinIR 関数群をホスト MIR 関数に inline するためのブロック ID の「型」を決める。
|
||||
|
||||
2. Phase 2: value_collector
|
||||
JoinIR 内で必要となる ValueId を収集し、remap のための準備をする。
|
||||
|
||||
3. Phase 3: remap_values
|
||||
JoinIR の ValueId → MIR 側の ValueId にマップする。
|
||||
|
||||
4. Phase 4: instruction_rewriter
|
||||
Return → Jump, Call → Jump など命令を書き換える。
|
||||
|
||||
5. Phase 5: exit_phi_builder
|
||||
expr 結果用の exit PHI を生成する。
|
||||
|
||||
6. Phase 6: exit_line
|
||||
ExitMetaCollector + ExitLineReconnector で carrier を variable_map に反映。
|
||||
|
||||
Phase 33‑16 ではここに **Phase 3.5: LoopHeaderPhiBuilder** を追加する:
|
||||
|
||||
```text
|
||||
Phase 1: block_allocator
|
||||
Phase 2: value_collector
|
||||
Phase 3: remap_values
|
||||
Phase 3.5: loop_header_phi_builder ← NEW
|
||||
Phase 4: instruction_rewriter
|
||||
Phase 5: exit_phi_builder
|
||||
Phase 6: exit_line
|
||||
```
|
||||
|
||||
LoopHeaderPhiBuilder の責務は:
|
||||
|
||||
- ループヘッダブロックを特定し
|
||||
- そのブロックに PHI を生成して
|
||||
- 各キャリアの「現在値」の `dst` を決める
|
||||
- expr 結果として使うべき値(あれば)を決める
|
||||
- それらを構造体で返す
|
||||
|
||||
---
|
||||
|
||||
## 3. LoopHeaderPhiInfo 箱
|
||||
|
||||
### 3.1 構造
|
||||
|
||||
```rust
|
||||
/// ループヘッダ PHI の SSOT
|
||||
pub struct LoopHeaderPhiInfo {
|
||||
/// ヘッダブロック(PHI が置かれるブロック)
|
||||
pub header_block: BasicBlockId,
|
||||
|
||||
/// 各キャリアごとに「ヘッダ PHI の dst」を持つ
|
||||
/// 例: [("i", v_i_phi), ("sum", v_sum_phi)]
|
||||
pub carrier_phis: Vec<CarrierPhiEntry>,
|
||||
|
||||
/// ループを式として使う場合の expr 結果 PHI
|
||||
/// ない場合は None(キャリアのみのループ)
|
||||
pub expr_result_phi: Option<ValueId>,
|
||||
}
|
||||
|
||||
pub struct CarrierPhiEntry {
|
||||
pub name: String, // carrier 名 ("i", "sum" 等)
|
||||
pub dst: ValueId, // その carrier のヘッダ PHI の dst
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 入口 / 出口
|
||||
|
||||
LoopHeaderPhiBuilder は次の入力を取るイメージだよ:
|
||||
|
||||
- `loop_header: BasicBlockId`
|
||||
Pattern lowerer / ルーティングで「ここがヘッダ」と決めたブロック。
|
||||
- `carrier_names: &[String]`
|
||||
`CarrierInfo` や `LoopFeatures` から得たキャリア名一覧。
|
||||
- `join_fragment_meta: &JoinFragmentMeta`
|
||||
expr 結果があるかどうかのフラグ(後述)。
|
||||
|
||||
そして `LoopHeaderPhiInfo` を返す:
|
||||
|
||||
```rust
|
||||
fn build_loop_header_phis(
|
||||
func: &mut MirFunction,
|
||||
loop_header: BasicBlockId,
|
||||
carrier_names: &[String],
|
||||
fragment_meta: &JoinFragmentMeta,
|
||||
) -> LoopHeaderPhiInfo;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. JoinFragmentMeta / ExitMeta / Boundary との接続
|
||||
|
||||
### 4.1 JoinFragmentMeta
|
||||
|
||||
すでに次のような形になっている:
|
||||
|
||||
```rust
|
||||
pub struct JoinFragmentMeta {
|
||||
pub expr_result: Option<ValueId>, // JoinIR ローカルの expr 結果
|
||||
pub exit_meta: ExitMeta, // carrier 用メタ
|
||||
}
|
||||
```
|
||||
|
||||
Phase 33‑16 では:
|
||||
|
||||
- lowerer 側(Pattern2 等)は「Loop header PHI を前提にした expr_result」を設定する方向に揃える。
|
||||
- もしくは LoopHeaderPhiBuilder の結果から `expr_result_phi` を JoinFragmentMeta に反映する。
|
||||
- 重要なのは、「expr_result に JoinIR パラメータ(`i_param` 等)を直接入れない」こと。
|
||||
|
||||
### 4.2 ExitMeta / ExitLine 側
|
||||
|
||||
ExitMeta / ExitLine は既に carrier 専用ラインになっているので、方針は:
|
||||
|
||||
- ExitMeta 内の `join_exit_value` は「ヘッダ PHI の dst」を指すようにする。
|
||||
- LoopHeaderPhiInfo の `carrier_phis` から情報を取る形にリファクタリングする。
|
||||
- これにより、ExitLineReconnector は単に
|
||||
|
||||
```rust
|
||||
variable_map[carrier_name] = remapper.remap(phi_dst);
|
||||
```
|
||||
|
||||
と書けるようになる。
|
||||
|
||||
### 4.3 JoinInlineBoundary
|
||||
|
||||
Boundary 構造体自体には新フィールドを足さず、
|
||||
|
||||
- join_inputs / host_inputs(ループパラメータ)
|
||||
- condition_bindings(条件専用変数)
|
||||
- exit_bindings(ExitMetaCollector が作るキャリア出口)
|
||||
|
||||
の契約はそのまま維持する。LoopHeaderPhiInfo は merge フェーズ内部のメタとして扱う。
|
||||
|
||||
---
|
||||
|
||||
## 5. 変更範囲(Module boundaries)
|
||||
|
||||
### 5.1 触る予定のファイル
|
||||
|
||||
| ファイル | 予定される変更 |
|
||||
|---------------------------------------------------|-----------------------------------------------|
|
||||
| `src/mir/builder/control_flow/joinir/merge/mod.rs` | merge パイプラインに Phase 3.5 呼び出しを追加 |
|
||||
| `merge/loop_header_phi_builder.rs` (NEW) | LoopHeaderPhiBuilder 実装 |
|
||||
| `merge/exit_phi_builder.rs` | LoopHeaderPhiInfo を受け取って expr PHI を構築 |
|
||||
| `merge/instruction_rewriter.rs` | 33‑15 の暫定 skip ロジックを削除し、LoopHeaderPhiInfo を前提に整理 |
|
||||
| `merge/exit_line/reconnector.rs` | carrier_phis を入口として使うように変更(ExitMeta と連携) |
|
||||
| `join_ir/lowering/loop_with_break_minimal.rs` 等 | LoopHeaderPhiBuilder に必要なメタの受け渡し |
|
||||
|
||||
### 5.2 触らない場所
|
||||
|
||||
- `CarrierInfo` / `LoopUpdateAnalyzer`
|
||||
- `ExitMetaCollector`
|
||||
- `JoinInlineBoundary` 構造体(フィールド追加なし)
|
||||
- BoolExprLowerer / condition_to_joinir
|
||||
|
||||
これらは既に箱化されており、Loop ヘッダ PHI だけを中継する小箱を増やせば整合性を保てる設計になっている。
|
||||
|
||||
---
|
||||
|
||||
## 6. テスト戦略(完了条件)
|
||||
|
||||
### 6.1 expr 結果ライン
|
||||
|
||||
- `apps/tests/joinir_min_loop.hako`
|
||||
- 期待: RC が「ループ終了時の i」の値になること(現在は 0)。
|
||||
- SSA トレース: `NYASH_SSA_UNDEF_DEBUG=1` で undefined が出ないこと。
|
||||
|
||||
### 6.2 carrier ライン
|
||||
|
||||
- `local_tests/test_trim_main_pattern.hako` など trim 系:
|
||||
- 期待: start/end が正しく更新され、既存の期待出力から変わらないこと。
|
||||
- ExitLineReconnector が LoopHeaderPhiInfo 経由でも正常に variable_map を更新すること。
|
||||
|
||||
### 6.3 回帰
|
||||
|
||||
- 既存 Pattern1–4 のループテスト:
|
||||
- 結果・RC・SSA すべて元と同じであること。
|
||||
|
||||
---
|
||||
|
||||
## 7. 次のフェーズ
|
||||
|
||||
この設計フェーズ(33‑16)はここまで。
|
||||
|
||||
次の 33‑16 実装フェーズでは:
|
||||
|
||||
1. `loop_header_phi_builder.rs` を実装し、LoopHeaderPhiInfo を実際に構築。
|
||||
2. `merge/mod.rs` に Phase 3.5 を組み込み。
|
||||
3. `exit_phi_builder.rs` / `exit_line/reconnector.rs` / `instruction_rewriter.rs` を LoopHeaderPhiInfo 前提で整理。
|
||||
4. 上記テスト戦略に沿って回帰テスト・SSA 検証を行う。
|
||||
|
||||
実装時に設計が変わった場合は、このファイルと `joinir-architecture-overview.md` を SSOT として必ず更新すること。
|
||||
|
||||
@ -1,301 +0,0 @@
|
||||
# Phase 33-16: Q&A - Implementation Flow Details
|
||||
|
||||
## Your Questions Answered
|
||||
|
||||
### Q1: Where exactly should LoopHeaderPhiBuilder::build() be called?
|
||||
|
||||
**Answer**: Between Phase 3 (remap_values) and Phase 4 (instruction_rewriter) in `merge/mod.rs`
|
||||
|
||||
**Location**: Line 107, after `remap_values()`
|
||||
|
||||
**Why here**:
|
||||
- ✅ **After** remap_values: We have remapped ValueIds (needed for phi_dst allocation)
|
||||
- ✅ **Before** instruction_rewriter: We need to know PHI dsts so instruction_rewriter can use them in exit values
|
||||
- ✅ Clear phase boundary: Phase 3.5
|
||||
|
||||
**Code location in file**:
|
||||
```rust
|
||||
// Line 107: After remap_values(...)
|
||||
remap_values(builder, &used_values, &mut remapper, debug)?;
|
||||
|
||||
// INSERT HERE: Phase 3.5 - Build loop header PHIs
|
||||
let mut loop_header_phi_info = if let Some(boundary) = boundary {
|
||||
// ... build logic ...
|
||||
} else {
|
||||
loop_header_phi_builder::LoopHeaderPhiInfo::empty(BasicBlockId(0))
|
||||
};
|
||||
|
||||
// Phase 4: Merge blocks and rewrite instructions
|
||||
// PASS loop_header_phi_info to instruction_rewriter
|
||||
let merge_result = instruction_rewriter::merge_and_rewrite(
|
||||
builder,
|
||||
mir_module,
|
||||
&mut remapper,
|
||||
&value_to_func_name,
|
||||
&function_params,
|
||||
boundary,
|
||||
&mut loop_header_phi_info, // NEW: Pass mutable reference
|
||||
debug,
|
||||
)?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Q2: How do I get the header_block_id (loop_step's entry block after remapping)?
|
||||
|
||||
**Answer**: It's the entry function's entry block, obtained via remapper
|
||||
|
||||
**Exact code**:
|
||||
```rust
|
||||
let (entry_func_name, entry_func) = mir_module
|
||||
.functions
|
||||
.iter()
|
||||
.next()
|
||||
.ok_or("JoinIR module has no functions")?;
|
||||
|
||||
let entry_block_remapped = remapper
|
||||
.get_block(entry_func_name, entry_func.entry_block)
|
||||
.ok_or_else(|| format!("Entry block not found"))?;
|
||||
|
||||
// For Pattern 2, entry block == header block
|
||||
let header_block_id = entry_block_remapped;
|
||||
```
|
||||
|
||||
**Why this works**:
|
||||
- JoinIR's first function is always the entry function
|
||||
- `entry_func.entry_block` is the BasicBlockId in JoinIR space
|
||||
- `remapper.get_block()` returns the remapped BasicBlockId in the host MIR
|
||||
|
||||
**For more complex patterns** (future):
|
||||
- Pattern 3/4 might have different header block logic
|
||||
- For now, assume entry_block == header_block (safe for Pattern 2)
|
||||
|
||||
---
|
||||
|
||||
### Q3: How do I get the loop variable's initial value (host-side)?
|
||||
|
||||
**Answer**: Get it from the remapper (it's the remapped join_inputs[0])
|
||||
|
||||
**Exact code**:
|
||||
```rust
|
||||
// Loop variable's initial value is from join_inputs[0]
|
||||
// It's been remapped by remap_values() in Phase 3
|
||||
let loop_var_init = remapper
|
||||
.get_value(ValueId(0)) // JoinIR param slot (always 0 for loop var)
|
||||
.ok_or("Loop var init not remapped")?;
|
||||
```
|
||||
|
||||
**Why ValueId(0)**:
|
||||
- In JoinIR, loop parameter is always allocated as ValueId(0)
|
||||
- Pattern 2 lowerer does this (pattern2_with_break.rs line 84):
|
||||
```rust
|
||||
env.insert(loop_var_name.clone(), crate::mir::ValueId(0));
|
||||
```
|
||||
- After remap_values(), this becomes a new ValueId in host space
|
||||
|
||||
**What you DON'T need**:
|
||||
- ❌ Don't look in boundary.host_inputs[0] directly
|
||||
- ❌ Don't use boundary.join_inputs[0] (it's the pre-remap value)
|
||||
- ✅ Use remapper.get_value(ValueId(0)) (it's the post-remap value)
|
||||
|
||||
---
|
||||
|
||||
### Q4: Where should instruction_rewriter record latch_incoming?
|
||||
|
||||
**Answer**: In the tail call handling section, after parameter bindings
|
||||
|
||||
**Location**: `instruction_rewriter.rs`, in the tail call branch (lines 276-335)
|
||||
|
||||
**Exact code**:
|
||||
```rust
|
||||
if let Some((target_block, args)) = tail_call_target {
|
||||
// ... existing parameter binding code (lines 276-319) ...
|
||||
|
||||
// NEW: Track latch incoming AFTER param bindings
|
||||
if let Some(loop_var_name) = &boundary.and_then(|b| b.loop_var_name.as_ref()) {
|
||||
if !args.is_empty() {
|
||||
let latch_value = args[0]; // Updated loop variable from tail call args
|
||||
loop_header_phi_info.set_latch_incoming(
|
||||
loop_var_name,
|
||||
target_block, // This is the loop header block (from tail call target)
|
||||
latch_value, // This is i_next (the updated value)
|
||||
);
|
||||
|
||||
if debug {
|
||||
eprintln!("[cf_loop/joinir] Phase 33-16: Set latch incoming for '{}': {:?}",
|
||||
loop_var_name, latch_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ... then set terminator to Jump (line 321-323) ...
|
||||
}
|
||||
```
|
||||
|
||||
**Why this location**:
|
||||
- Tail call args are the ACTUAL updated values (i_next, not i_param)
|
||||
- args[0] is guaranteed to be the loop variable (Pattern 2 guarantees)
|
||||
- target_block is the loop header (where we're jumping back to)
|
||||
- Called for EACH block that has a tail call (ensures all paths tracked)
|
||||
|
||||
**Key insight**: The latch block is NOT explicitly identified; we identify it by the Jump target!
|
||||
|
||||
---
|
||||
|
||||
### Q5: Should the Phase 33-15 skip logic be removed or modified to use header PHI dst?
|
||||
|
||||
**Answer**: Modify, NOT remove. Use header PHI dst when available, fallback to parameter.
|
||||
|
||||
**What to do**:
|
||||
|
||||
1. **Replace skip logic** (lines 354-398 in instruction_rewriter.rs):
|
||||
```rust
|
||||
// OLD: Skip exit_phi_inputs collection
|
||||
// if debug { eprintln!(...skip...); }
|
||||
|
||||
// NEW: Use header PHI dst if available
|
||||
if let Some(ret_val) = value {
|
||||
let remapped_val = remapper.get_value(*ret_val).unwrap_or(*ret_val);
|
||||
|
||||
// Phase 33-16: Prefer header PHI dst
|
||||
if let Some(loop_var_name) = &boundary.and_then(|b| b.loop_var_name.as_ref()) {
|
||||
if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(loop_var_name) {
|
||||
// Use PHI dst (SSA-correct!)
|
||||
exit_phi_inputs.push((exit_block_id, phi_dst));
|
||||
} else {
|
||||
// Fallback: Use parameter (for backward compatibility)
|
||||
exit_phi_inputs.push((exit_block_id, remapped_val));
|
||||
}
|
||||
} else {
|
||||
// No boundary or loop_var_name: use parameter
|
||||
exit_phi_inputs.push((exit_block_id, remapped_val));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Modify carrier_inputs logic** (lines 400-431):
|
||||
```rust
|
||||
// OLD: Skip carrier_inputs collection
|
||||
// if debug { eprintln!(...skip...); }
|
||||
|
||||
// NEW: Use header PHI dsts for carriers
|
||||
if let Some(boundary) = boundary {
|
||||
for binding in &boundary.exit_bindings {
|
||||
// Phase 33-16: Look up header PHI dst for this carrier
|
||||
if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(&binding.carrier_name) {
|
||||
carrier_inputs.entry(binding.carrier_name.clone())
|
||||
.or_insert_with(Vec::new)
|
||||
.push((exit_block_id, phi_dst));
|
||||
}
|
||||
// If no PHI dst, skip this carrier (not yet implemented)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Why this approach**:
|
||||
- ✅ Phase 33-16 adds header PHIs → use them (SSA-correct)
|
||||
- ✅ If no header PHIs → fallback to old behavior (backward compat)
|
||||
- ✅ Gradual migration: Patterns enable loop_var_name progressively
|
||||
- ✅ Easy to debug: Explicit "Using PHI" vs "Fallback" logs
|
||||
|
||||
**Don't do**:
|
||||
- ❌ Don't remove skip logic entirely (patterns without loop_var_name would break)
|
||||
- ❌ Don't add loop_header_phi_info to merge_and_rewrite() signature if you don't track latch
|
||||
- ✅ Do add both build() and finalize() to merge/mod.rs
|
||||
|
||||
---
|
||||
|
||||
### Q6: Flow Summary - How does it all fit together?
|
||||
|
||||
**Complete flow**:
|
||||
|
||||
```
|
||||
merge_joinir_mir_blocks() {
|
||||
// Phase 1: Allocate block IDs
|
||||
allocate_blocks()
|
||||
|
||||
// Phase 2: Collect values
|
||||
collect_values()
|
||||
|
||||
// Phase 3: Remap ValueIds
|
||||
remap_values(builder, &used_values, &mut remapper)
|
||||
|
||||
// ===== Phase 3.5 (NEW) =====
|
||||
// Build loop header PHIs with entry incoming edges
|
||||
let mut loop_header_phi_info = if let Some(boundary) = boundary {
|
||||
if let Some(loop_var_name) = &boundary.loop_var_name {
|
||||
// Get header_block_id (entry block after remap)
|
||||
let entry_block = remapper.get_block(entry_func, entry_func.entry_block)?;
|
||||
|
||||
// Get loop_var_init (remapped ValueId(0))
|
||||
let loop_var_init = remapper.get_value(ValueId(0))?;
|
||||
|
||||
// Build header PHIs (allocates PHI dsts, doesn't emit yet)
|
||||
LoopHeaderPhiBuilder::build(
|
||||
builder,
|
||||
entry_block, // header_block_id
|
||||
entry_block, // entry_block_id
|
||||
loop_var_name,
|
||||
loop_var_init,
|
||||
&[], // No other carriers yet
|
||||
boundary.expr_result.is_some(),
|
||||
debug,
|
||||
)?
|
||||
} else {
|
||||
LoopHeaderPhiInfo::empty(...)
|
||||
}
|
||||
} else {
|
||||
LoopHeaderPhiInfo::empty(...)
|
||||
};
|
||||
|
||||
// ===== Phase 4 (MODIFIED) =====
|
||||
// Instruction rewriter sets latch incoming and uses PHI dsts
|
||||
let merge_result = instruction_rewriter::merge_and_rewrite(
|
||||
builder,
|
||||
mir_module,
|
||||
&mut remapper,
|
||||
...,
|
||||
&mut loop_header_phi_info, // PASS MUTABLE REFERENCE
|
||||
debug,
|
||||
)?;
|
||||
// Inside merge_and_rewrite:
|
||||
// - When processing tail calls: record latch_incoming
|
||||
// - When processing Return: use header PHI dsts (not parameters)
|
||||
|
||||
// ===== Phase 5 =====
|
||||
// Build exit PHI from exit_phi_inputs and carrier_inputs
|
||||
let (exit_phi_result_id, carrier_phis) = exit_phi_builder::build_exit_phi(...)?;
|
||||
|
||||
// ===== Phase 4.5 (NEW) =====
|
||||
// Finalize loop header PHIs (insert into blocks)
|
||||
LoopHeaderPhiBuilder::finalize(builder, &loop_header_phi_info, debug)?;
|
||||
|
||||
// ===== Phase 6 =====
|
||||
// Reconnect exit values using carrier_phis from Phase 5
|
||||
if let Some(boundary) = boundary {
|
||||
ExitLineOrchestrator::execute(builder, boundary, &carrier_phis, debug)?;
|
||||
}
|
||||
|
||||
// ... continue with boundary jump and exit block switch ...
|
||||
}
|
||||
```
|
||||
|
||||
**Key transitions**:
|
||||
1. Phase 3 → Phase 3.5: remap_values() gives us remapped ValueIds
|
||||
2. Phase 3.5 → Phase 4: loop_header_phi_info allocated (PHI dsts ready)
|
||||
3. Phase 4 → Phase 4.5: instruction_rewriter sets latch_incoming
|
||||
4. Phase 4.5 → Phase 5: Finalize emits PHIs into blocks
|
||||
5. Phase 5 → Phase 6: exit_phi_builder returns carrier_phis (PHI dsts)
|
||||
6. Phase 6: ExitLineOrchestrator uses carrier_phis to update variable_map
|
||||
|
||||
---
|
||||
|
||||
## Summary: Exact Answer to Your Core Question
|
||||
|
||||
**Where to call build()**: Line 107 in merge/mod.rs, after remap_values()
|
||||
**How to get header_block_id**: `remapper.get_block(entry_func_name, entry_func.entry_block)?`
|
||||
**How to get loop_var_init**: `remapper.get_value(ValueId(0))?`
|
||||
**Where to record latch_incoming**: In tail call handling (line ~300), use args[0] as latch_value
|
||||
**Replace skip logic**: Yes, with fallback mechanism for backward compatibility
|
||||
|
||||
**The magic**: Loop header PHI dst (allocated in Phase 3.5, finalized in Phase 4.5) is SSA-defined and can be safely used in exit values instead of parameters!
|
||||
@ -1,139 +0,0 @@
|
||||
# Phase 33-16: Instruction Rewriter Refactoring Summary
|
||||
|
||||
**Date**: 2025-12-07
|
||||
**File**: `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs`
|
||||
|
||||
## Motivation
|
||||
|
||||
Phase 33-16 fixed a critical bug where tail calls from the entry function's entry block were incorrectly redirected to the header block, creating self-referential loops (bb4 → bb4). The fix worked, but the implicit condition was difficult to understand:
|
||||
|
||||
```rust
|
||||
// Before: Implicit semantics
|
||||
let should_redirect = boundary.is_some()
|
||||
&& !carrier_phis.is_empty()
|
||||
&& !is_entry_func_entry_block;
|
||||
```
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. TailCallKind Enum (Lines 14-39)
|
||||
|
||||
Created an explicit classification system for tail calls:
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum TailCallKind {
|
||||
LoopEntry, // First entry: main → loop_step (no redirect)
|
||||
BackEdge, // Continuation: loop_step → loop_step (redirect to header)
|
||||
ExitJump, // Termination: → k_exit (Return conversion)
|
||||
}
|
||||
```
|
||||
|
||||
**Why three categories?**
|
||||
- **LoopEntry**: Entry function's entry block IS the header. Redirecting creates self-loop.
|
||||
- **BackEdge**: Loop body blocks must redirect to header for PHI merging.
|
||||
- **ExitJump**: Handled separately via Return → Jump conversion.
|
||||
|
||||
### 2. classify_tail_call() Function (Lines 41-69)
|
||||
|
||||
Extracted the classification logic into a pure function with explicit semantics:
|
||||
|
||||
```rust
|
||||
fn classify_tail_call(
|
||||
is_entry_func_entry_block: bool,
|
||||
has_loop_header_phis: bool,
|
||||
has_boundary: bool,
|
||||
) -> TailCallKind
|
||||
```
|
||||
|
||||
**Decision logic**:
|
||||
1. If entry function's entry block → LoopEntry (no redirect)
|
||||
2. Else if boundary exists AND header PHIs exist → BackEdge (redirect)
|
||||
3. Otherwise → ExitJump (Return conversion handles it)
|
||||
|
||||
### 3. Variable Rename
|
||||
|
||||
```rust
|
||||
// Before: Implementation-focused name
|
||||
let is_entry_func_entry_block = ...;
|
||||
|
||||
// After: Semantic name explaining the "why"
|
||||
let is_loop_entry_point = ...;
|
||||
```
|
||||
|
||||
Added documentation explaining that this block IS the header, so redirection would create self-loop.
|
||||
|
||||
### 4. Usage Site Refactoring (Lines 416-453)
|
||||
|
||||
Replaced implicit boolean logic with explicit match on TailCallKind:
|
||||
|
||||
```rust
|
||||
let tail_call_kind = classify_tail_call(...);
|
||||
|
||||
let actual_target = match tail_call_kind {
|
||||
TailCallKind::BackEdge => {
|
||||
// Redirect to header for PHI merging
|
||||
loop_header_phi_info.header_block
|
||||
}
|
||||
TailCallKind::LoopEntry => {
|
||||
// No redirect - entry block IS the header
|
||||
target_block
|
||||
}
|
||||
TailCallKind::ExitJump => {
|
||||
// Return conversion handles this
|
||||
target_block
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- Each case has explicit reasoning in comments
|
||||
- Debug logging differentiates between LoopEntry and BackEdge
|
||||
- Future maintainers can see the "why" immediately
|
||||
|
||||
## Impact
|
||||
|
||||
### Code Readability
|
||||
- **Before**: Boolean algebra requires mental model of loop structure
|
||||
- **After**: Explicit categories with documented semantics
|
||||
|
||||
### Maintainability
|
||||
- Classification logic is isolated and testable
|
||||
- Easy to add new tail call types if needed
|
||||
- Self-documenting code reduces cognitive load
|
||||
|
||||
### Correctness
|
||||
- No behavioral changes (verified by `cargo build --release`)
|
||||
- Makes the Phase 33-16 fix's reasoning explicit
|
||||
- Prevents future bugs from misunderstanding the condition
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
# ✅ Finished `release` profile [optimized] target(s) in 23.38s
|
||||
```
|
||||
|
||||
All tests pass, no regressions.
|
||||
|
||||
## Future Improvements
|
||||
|
||||
### Possible Enhancements (Low Priority)
|
||||
1. **Extract to module**: If tail call handling grows, create `tail_call_classifier.rs`
|
||||
2. **Add unit tests**: Test `classify_tail_call()` with various scenarios
|
||||
3. **Trace logging**: Add `TailCallKind` to debug output for better diagnostics
|
||||
|
||||
### Not Recommended
|
||||
- Don't merge LoopEntry and ExitJump - they have different semantics
|
||||
- Don't inline `classify_tail_call()` - keeping it separate preserves clarity
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
**Implicit semantics are tech debt.**
|
||||
The original code worked but required deep knowledge to maintain. The refactoring makes the "why" explicit without changing behavior, improving long-term maintainability at zero runtime cost.
|
||||
|
||||
---
|
||||
|
||||
**Related Files**:
|
||||
- `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs` (refactored)
|
||||
- `docs/development/current/main/phase33-16-self-loop-fix.md` (original bug fix)
|
||||
@ -1,467 +0,0 @@
|
||||
# Phase 33-16: Visual Flow Diagram & Code Map
|
||||
|
||||
## Architecture Diagram: Loop Header PHI as SSOT
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ merge_joinir_mir_blocks() Pipeline │
|
||||
│ (7 phases after Phase 33-16) │
|
||||
└────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Phase 1: allocate_blocks()
|
||||
├── Input: JoinIR mir_module.functions
|
||||
└── Output: remapper with block ID mappings
|
||||
|
||||
Phase 2: collect_values()
|
||||
├── Input: JoinIR blocks
|
||||
└── Output: used_values set
|
||||
|
||||
Phase 3: remap_values()
|
||||
├── Input: used_values set, remapper
|
||||
├── Action: Allocate new ValueIds for all used values
|
||||
└── Output: remapper with BOTH block AND value mappings
|
||||
|
||||
⚡ VALUE ID BOUNDARY: JoinIR (local 0,1,2...) → Host (100,101,102...)
|
||||
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ Phase 3.5: LoopHeaderPhiBuilder::build() ⭐ NEW in Phase 33-16 │
|
||||
│ │
|
||||
│ Input: │
|
||||
│ - boundary.loop_var_name: "i" │
|
||||
│ - remapper.get_value(ValueId(0)): ValueId(100) ← Init value (host) │
|
||||
│ - remapper.get_block(entry_func, entry_block): BasicBlockId(10) │
|
||||
│ │
|
||||
│ Action: │
|
||||
│ - Allocate phi_dst = ValueId(101) │
|
||||
│ - Create CarrierPhiEntry: │
|
||||
│ { phi_dst: ValueId(101), │
|
||||
│ entry_incoming: (BasicBlockId(10), ValueId(100)), │
|
||||
│ latch_incoming: None } ← Set in Phase 4! │
|
||||
│ │
|
||||
│ Output: │
|
||||
│ - loop_header_phi_info with empty latch_incoming │
|
||||
│ - PASS TO: instruction_rewriter as mutable reference │
|
||||
│ │
|
||||
│ Key: PHI dst (ValueId(101)) is NOW ALLOCATED and KNOWN before we │
|
||||
│ process instructions. instruction_rewriter will use it! │
|
||||
└──────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Phase 4: merge_and_rewrite()
|
||||
├── Input: loop_header_phi_info (mutable!)
|
||||
├
|
||||
│ Subphase 4a: Process instructions in each block
|
||||
│ ├── Copy instructions: Use remapped ValueIds
|
||||
│ └── Other instructions: Standard remapping
|
||||
│
|
||||
│ Subphase 4b: Process terminators
|
||||
│ ├── Tail call (Jump to loop header):
|
||||
│ │ ├── args = [ValueId(102)] ← i_next (updated loop variable)
|
||||
│ │ ├── Call: loop_header_phi_info.set_latch_incoming(
|
||||
│ │ │ "i",
|
||||
│ │ │ target_block, ← Loop header
|
||||
│ │ │ ValueId(102)) ← Updated value
|
||||
│ │ └── Emit parameter bindings + Jump
|
||||
│ │
|
||||
│ └── Return { value }:
|
||||
│ ├── OLD (Phase 33-15): Skip exit_phi_inputs
|
||||
│ │
|
||||
│ └── NEW (Phase 33-16):
|
||||
│ ├── Get phi_dst = loop_header_phi_info.get_carrier_phi("i")
|
||||
│ │ = ValueId(101) ← PHI output, not parameter!
|
||||
│ └── Collect: exit_phi_inputs.push((exit_block, ValueId(101)))
|
||||
│
|
||||
└── Output: MergeResult with exit_phi_inputs using PHI dsts
|
||||
|
||||
⚡ KEY MOMENT: loop_header_phi_info.latch_incoming NOW SET!
|
||||
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ Phase 5: exit_phi_builder::build_exit_phi() │
|
||||
│ │
|
||||
│ Input: │
|
||||
│ - exit_phi_inputs: [(exit_block, ValueId(101))] │
|
||||
│ - carrier_inputs: {} (empty in Phase 33-16 minimal) │
|
||||
│ │
|
||||
│ Action: │
|
||||
│ - Create exit block │
|
||||
│ - If exit_phi_inputs not empty: │
|
||||
│ { Create PHI: exit_phi_dst = PHI [(exit_block, ValueId(101))] │
|
||||
│ - For each carrier: Create carrier PHI │
|
||||
│ │
|
||||
│ Output: │
|
||||
│ - exit_phi_result_id: Some(ValueId(103)) ← Exit block's PHI dst │
|
||||
│ - carrier_phis: { "i" → ValueId(101) } ← Header PHI dsts! │
|
||||
│ │
|
||||
│ Key: carrier_phis now contains header PHI dsts, NOT remapped parameters!│
|
||||
└──────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ Phase 4.5: LoopHeaderPhiBuilder::finalize() ⭐ NEW in Phase 33-16 │
|
||||
│ │
|
||||
│ Input: loop_header_phi_info with latch_incoming NOW SET │
|
||||
│ │
|
||||
│ Action: │
|
||||
│ - Validate all latch_incoming are set │
|
||||
│ - For each carrier: │
|
||||
│ { entry_incoming = (entry_block, ValueId(100)), │
|
||||
│ latch_incoming = (header_block, ValueId(102)) ← From Phase 4! │
|
||||
│ Emit PHI: ValueId(101) = PHI [(entry_block, ValueId(100)), │
|
||||
│ (header_block, ValueId(102))] │
|
||||
│ - Prepend PHI instructions to header block │
|
||||
│ │
|
||||
│ Output: │
|
||||
│ - Header block now contains: │
|
||||
│ [PHI instructions...], [original instructions...] │
|
||||
│ │
|
||||
│ Key: PHI is now EMITTED into the MIR! SSA definition complete! │
|
||||
└──────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Phase 6: ExitLineOrchestrator::execute()
|
||||
├── Input: carrier_phis = { "i" → ValueId(101) }
|
||||
├── Action: Call ExitLineReconnector
|
||||
│ └── For each exit_binding:
|
||||
│ ├── Look up carrier PHI: carrier_phis["i"] = ValueId(101)
|
||||
│ └── Update: variable_map["i"] = ValueId(101) ← PHI dst!
|
||||
└── Output: Updated variable_map with PHI dsts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Code Change Map
|
||||
|
||||
### Files to Modify (6 locations)
|
||||
|
||||
```
|
||||
src/mir/builder/control_flow/joinir/merge/
|
||||
├── mod.rs (MODIFY)
|
||||
│ └── Between line 107 (after remap_values)
|
||||
│ and line 110 (before instruction_rewriter)
|
||||
│ ✅ ADD: Phase 3.5 - Build loop header PHIs
|
||||
│ ✅ ADD: Phase 4.5 - Finalize loop header PHIs (after exit_phi_builder)
|
||||
│
|
||||
├── instruction_rewriter.rs (MODIFY 3 places)
|
||||
│ ├── Line 29-37: Update fn signature
|
||||
│ │ ✅ ADD: loop_header_phi_info parameter
|
||||
│ │
|
||||
│ ├── ~Line 300 (in tail call section): Track latch incoming
|
||||
│ │ ✅ ADD: loop_header_phi_info.set_latch_incoming(...)
|
||||
│ │
|
||||
│ └── Lines 354-431 (Return processing): Use PHI dsts
|
||||
│ ✅ MODIFY: Replace skip logic with PHI dst usage
|
||||
│ ✅ MODIFY: Replace carrier skip with PHI dst usage
|
||||
│
|
||||
└── loop_header_phi_builder.rs (ALREADY EXISTS)
|
||||
├── ::build() ✅ Ready to use
|
||||
└── ::finalize() ✅ Ready to use
|
||||
```
|
||||
|
||||
### Optional: Pattern Lowerer Update
|
||||
|
||||
```
|
||||
src/mir/builder/control_flow/joinir/patterns/
|
||||
└── pattern2_with_break.rs
|
||||
├── Line 200: ✅ Already sets loop_var_name
|
||||
└── Line 200+: OPTIONAL - Extract other carriers from exit_bindings
|
||||
(For Phase 33-16+, not required for minimal)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Concrete Code Changes (Copy-Paste Ready)
|
||||
|
||||
### Change 1: Add mod.rs imports
|
||||
|
||||
**Location**: Top of `src/mir/builder/control_flow/joinir/merge/mod.rs`
|
||||
|
||||
```rust
|
||||
// Already present:
|
||||
mod instruction_rewriter;
|
||||
mod exit_phi_builder;
|
||||
pub mod exit_line;
|
||||
pub mod loop_header_phi_builder; // ✅ Already declared!
|
||||
|
||||
// Import the types
|
||||
use loop_header_phi_builder::{LoopHeaderPhiBuilder, LoopHeaderPhiInfo};
|
||||
```
|
||||
|
||||
### Change 2: Add Phase 3.5 after remap_values
|
||||
|
||||
**Location**: `mod.rs`, line 107+ (after `remap_values(...)`)
|
||||
|
||||
```rust
|
||||
// Phase 3: Remap ValueIds
|
||||
remap_values(builder, &used_values, &mut remapper, debug)?;
|
||||
|
||||
// ===== Phase 3.5: Build loop header PHIs =====
|
||||
let mut loop_header_phi_info = if let Some(boundary) = boundary {
|
||||
if let Some(loop_var_name) = &boundary.loop_var_name {
|
||||
// Get entry function and entry block
|
||||
let (entry_func_name, entry_func) = mir_module
|
||||
.functions
|
||||
.iter()
|
||||
.next()
|
||||
.ok_or("JoinIR module has no functions")?;
|
||||
|
||||
let entry_block_id = remapper
|
||||
.get_block(entry_func_name, entry_func.entry_block)
|
||||
.ok_or_else(|| format!("Entry block not found"))?;
|
||||
|
||||
// Get loop variable's initial value (remapped)
|
||||
let loop_var_init = remapper
|
||||
.get_value(ValueId(0))
|
||||
.ok_or("Loop var init not remapped")?;
|
||||
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 3.5: Building header PHIs for loop_var '{}'",
|
||||
loop_var_name
|
||||
);
|
||||
}
|
||||
|
||||
// Build header PHIs (allocates PHI dsts, doesn't emit yet)
|
||||
LoopHeaderPhiBuilder::build(
|
||||
builder,
|
||||
entry_block_id, // header_block_id
|
||||
entry_block_id, // entry_block_id
|
||||
loop_var_name,
|
||||
loop_var_init,
|
||||
&[], // No other carriers yet
|
||||
boundary.expr_result.is_some(),
|
||||
debug,
|
||||
)?
|
||||
} else {
|
||||
LoopHeaderPhiInfo::empty(BasicBlockId(0))
|
||||
}
|
||||
} else {
|
||||
LoopHeaderPhiInfo::empty(BasicBlockId(0))
|
||||
};
|
||||
|
||||
// Phase 4: Merge blocks and rewrite instructions
|
||||
let merge_result = instruction_rewriter::merge_and_rewrite(
|
||||
builder,
|
||||
mir_module,
|
||||
&mut remapper,
|
||||
&value_to_func_name,
|
||||
&function_params,
|
||||
boundary,
|
||||
&mut loop_header_phi_info, // NEW: Pass mutable reference
|
||||
debug,
|
||||
)?;
|
||||
```
|
||||
|
||||
### Change 3: Update instruction_rewriter::merge_and_rewrite signature
|
||||
|
||||
**Location**: `instruction_rewriter.rs`, line 29-37
|
||||
|
||||
```rust
|
||||
pub(super) fn merge_and_rewrite(
|
||||
builder: &mut crate::mir::builder::MirBuilder,
|
||||
mir_module: &MirModule,
|
||||
remapper: &mut JoinIrIdRemapper,
|
||||
value_to_func_name: &HashMap<ValueId, String>,
|
||||
function_params: &HashMap<String, Vec<ValueId>>,
|
||||
boundary: Option<&JoinInlineBoundary>,
|
||||
loop_header_phi_info: &mut super::loop_header_phi_builder::LoopHeaderPhiInfo, // NEW
|
||||
debug: bool,
|
||||
) -> Result<MergeResult, String> {
|
||||
// ... rest of function unchanged ...
|
||||
}
|
||||
```
|
||||
|
||||
### Change 4: Add latch tracking in tail call section
|
||||
|
||||
**Location**: `instruction_rewriter.rs`, after line ~319 (after param bindings)
|
||||
|
||||
```rust
|
||||
// Second pass: Insert parameter bindings for tail calls
|
||||
// Phase 188-Impl-3: Use actual parameter ValueIds from target function
|
||||
if let Some((target_block, args)) = tail_call_target {
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Inserting param bindings for tail call to {:?}",
|
||||
target_block
|
||||
);
|
||||
}
|
||||
|
||||
// ... existing param binding code (unchanged) ...
|
||||
|
||||
// ===== NEW Phase 33-16: Track latch incoming =====
|
||||
if let Some(loop_var_name) = &boundary.and_then(|b| b.loop_var_name.as_ref()) {
|
||||
if !args.is_empty() {
|
||||
let latch_value = args[0]; // Updated loop variable
|
||||
loop_header_phi_info.set_latch_incoming(
|
||||
loop_var_name,
|
||||
target_block, // Loop header block
|
||||
latch_value, // i_next value
|
||||
);
|
||||
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 33-16: Set latch incoming for '{}': {:?}",
|
||||
loop_var_name, latch_value
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set terminator to Jump
|
||||
new_block.terminator = Some(MirInstruction::Jump {
|
||||
target: target_block,
|
||||
});
|
||||
```
|
||||
|
||||
### Change 5: Replace exit_phi_inputs skip logic
|
||||
|
||||
**Location**: `instruction_rewriter.rs`, lines 354-398 (replace entire block)
|
||||
|
||||
```rust
|
||||
MirInstruction::Return { value } => {
|
||||
// Phase 33-16: Use header PHI dst instead of undefined parameters
|
||||
if let Some(ret_val) = value {
|
||||
let remapped_val = remapper.get_value(*ret_val).unwrap_or(*ret_val);
|
||||
|
||||
// Try to use header PHI dst (SSA-correct)
|
||||
if let Some(loop_var_name) = &boundary.and_then(|b| b.loop_var_name.as_ref()) {
|
||||
if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(loop_var_name) {
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 33-16: Using loop header PHI {:?} for exit value",
|
||||
phi_dst
|
||||
);
|
||||
}
|
||||
exit_phi_inputs.push((exit_block_id, phi_dst));
|
||||
} else {
|
||||
// Fallback: use parameter (backward compat)
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 33-16: No header PHI, fallback to parameter {:?}",
|
||||
remapped_val
|
||||
);
|
||||
}
|
||||
exit_phi_inputs.push((exit_block_id, remapped_val));
|
||||
}
|
||||
} else {
|
||||
// No loop_var_name: use parameter
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 33-16: No loop_var_name, using parameter {:?}",
|
||||
remapped_val
|
||||
);
|
||||
}
|
||||
exit_phi_inputs.push((exit_block_id, remapped_val));
|
||||
}
|
||||
}
|
||||
|
||||
MirInstruction::Jump {
|
||||
target: exit_block_id,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Change 6: Replace carrier_inputs skip logic
|
||||
|
||||
**Location**: `instruction_rewriter.rs`, lines 400-431 (replace entire block)
|
||||
|
||||
```rust
|
||||
// Phase 33-13/16: Collect carrier exit values using header PHI dsts
|
||||
if let Some(boundary) = boundary {
|
||||
for binding in &boundary.exit_bindings {
|
||||
// Try to use header PHI dst
|
||||
if let Some(phi_dst) = loop_header_phi_info.get_carrier_phi(&binding.carrier_name) {
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 33-16: Carrier '{}' using header PHI {:?}",
|
||||
binding.carrier_name, phi_dst
|
||||
);
|
||||
}
|
||||
carrier_inputs.entry(binding.carrier_name.clone())
|
||||
.or_insert_with(Vec::new)
|
||||
.push((exit_block_id, phi_dst));
|
||||
} else if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 33-16: No header PHI for carrier '{}', skipping",
|
||||
binding.carrier_name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Change 7: Add Phase 4.5 finalize call
|
||||
|
||||
**Location**: `mod.rs`, after Phase 5 (exit_phi_builder call)
|
||||
|
||||
```rust
|
||||
// Phase 5: Build exit PHI (expr result and carrier PHIs)
|
||||
let (exit_phi_result_id, carrier_phis) = exit_phi_builder::build_exit_phi(
|
||||
builder,
|
||||
merge_result.exit_block_id,
|
||||
&merge_result.exit_phi_inputs,
|
||||
&merge_result.carrier_inputs,
|
||||
debug,
|
||||
)?;
|
||||
|
||||
// ===== Phase 4.5: Finalize loop header PHIs =====
|
||||
LoopHeaderPhiBuilder::finalize(builder, &loop_header_phi_info, debug)?;
|
||||
|
||||
// Phase 6: Reconnect boundary (if specified)
|
||||
// Phase 197-B: Pass remapper to enable per-carrier exit value lookup
|
||||
// Phase 33-10-Refactor-P3: Delegate to ExitLineOrchestrator
|
||||
// Phase 33-13: Pass carrier_phis for proper variable_map update
|
||||
if let Some(boundary) = boundary {
|
||||
exit_line::ExitLineOrchestrator::execute(builder, boundary, &carrier_phis, debug)?;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Flow Checklist
|
||||
|
||||
1. ✅ **Phase 3** (remap_values): ValueIds remapped
|
||||
2. ✅ **Phase 3.5** (build):
|
||||
- Header PHI dsts allocated
|
||||
- Entry incoming set
|
||||
- Passed to Phase 4
|
||||
3. ✅ **Phase 4** (instruction_rewriter):
|
||||
- Latch incoming set when processing tail calls
|
||||
- Exit values use header PHI dsts (not parameters)
|
||||
- Carrier exit values use header PHI dsts
|
||||
4. ✅ **Phase 4.5** (finalize):
|
||||
- PHI instructions emitted into header block
|
||||
- Validation that all latch incoming are set
|
||||
5. ✅ **Phase 5** (exit_phi_builder):
|
||||
- Exit PHI created from exit_phi_inputs
|
||||
- carrier_phis returned with PHI dsts
|
||||
6. ✅ **Phase 6** (ExitLineOrchestrator):
|
||||
- variable_map updated with header PHI dsts
|
||||
|
||||
---
|
||||
|
||||
## Testing Commands
|
||||
|
||||
```bash
|
||||
# Build
|
||||
cargo build --release 2>&1 | head -50
|
||||
|
||||
# Test with debug output
|
||||
NYASH_JOINIR_DEBUG=1 ./target/release/nyash --dump-mir \
|
||||
apps/tests/joinir_min_loop.hako 2>&1 | grep "Phase 33-16"
|
||||
|
||||
# Check MIR structure
|
||||
./target/release/nyash --emit-mir-json mir.json apps/tests/joinir_min_loop.hako
|
||||
jq '.functions[0].blocks[0].instructions[0:3]' mir.json # First 3 instructions (should be PHIs)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary Table
|
||||
|
||||
| Phase | Component | Input | Output | Size |
|
||||
|-------|-----------|-------|--------|------|
|
||||
| 3 | remap_values | ValueId(0-10) | ValueId(100-110) | existing |
|
||||
| 3.5 | build | ValueId(100) | phi_dst=101 | 50 lines new |
|
||||
| 4 | merge_and_rewrite | loop_header_phi_info | latch_incoming set | +20 lines |
|
||||
| 4.5 | finalize | latch_incoming set | PHI emitted | 30 lines new |
|
||||
| 5 | build_exit_phi | exit_phi_inputs | carrier_phis | existing |
|
||||
| 6 | ExitLineOrchestrator | carrier_phis | var_map updated | existing |
|
||||
|
||||
**Total changes**: ~6 locations, ~100 lines added/modified, 0 lines removed (backward compat)
|
||||
@ -1,209 +0,0 @@
|
||||
# Phase 33-17: JoinIR モジュール化実装完了
|
||||
|
||||
## 実施日: 2025-12-07
|
||||
|
||||
## 🎯 実装サマリー
|
||||
|
||||
### Phase 33-17-A: instruction_rewriter 分割(完了✅)
|
||||
|
||||
**実施内容**:
|
||||
1. ✅ `tail_call_classifier.rs` 作成(109行、テスト含む)
|
||||
- TailCallKind enum
|
||||
- classify_tail_call() 関数
|
||||
- 単体テスト4ケース追加
|
||||
|
||||
2. ✅ `merge_result.rs` 作成(46行)
|
||||
- MergeResult struct
|
||||
- ヘルパーメソッド(new, add_exit_phi_input, add_carrier_input)
|
||||
|
||||
3. ✅ `instruction_rewriter.rs` リファクタリング
|
||||
- 649行 → 589行(60行削減、9.2%減)
|
||||
- 重複コード削除
|
||||
- 新モジュールへの委譲
|
||||
|
||||
4. ✅ `merge/mod.rs` 更新
|
||||
- 新モジュール宣言追加
|
||||
- 公開API再エクスポート(MergeResult, TailCallKind, classify_tail_call)
|
||||
|
||||
5. ✅ ビルド確認
|
||||
- `cargo build --release` 成功
|
||||
- 警告のみ(既存の未使用変数警告)
|
||||
- エラー0件
|
||||
|
||||
---
|
||||
|
||||
## 📊 効果測定
|
||||
|
||||
### ファイルサイズ変化
|
||||
|
||||
| ファイル | Before | After | 削減率 |
|
||||
|---------|--------|-------|--------|
|
||||
| instruction_rewriter.rs | 649行 | 589行 | -9.2% |
|
||||
| tail_call_classifier.rs | - | 109行 | 新規 |
|
||||
| merge_result.rs | - | 46行 | 新規 |
|
||||
| **合計** | **649行** | **744行** | +14.6% |
|
||||
|
||||
**注**: 合計行数は増加しているが、これは以下の理由により正常:
|
||||
- テストコード追加(40行)
|
||||
- ドキュメントコメント追加(30行)
|
||||
- ヘルパーメソッド追加(25行)
|
||||
|
||||
**実質的な削減**:
|
||||
- 重複コード削除: 60行
|
||||
- 可読性向上: 各ファイル200行以下達成(instruction_rewriter除く)
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ アーキテクチャ改善
|
||||
|
||||
### Before (Phase 33-16)
|
||||
```
|
||||
instruction_rewriter.rs (649行)
|
||||
- TailCallKind enum定義
|
||||
- classify_tail_call()関数
|
||||
- MergeResult struct定義
|
||||
- merge_and_rewrite()巨大関数
|
||||
```
|
||||
|
||||
### After (Phase 33-17)
|
||||
```
|
||||
tail_call_classifier.rs (109行)
|
||||
- TailCallKind enum + classify_tail_call()
|
||||
- 単体テスト完備
|
||||
✅ 単一責任: 分類ロジックのみ
|
||||
|
||||
merge_result.rs (46行)
|
||||
- MergeResult struct + ヘルパー
|
||||
✅ 単一責任: データ構造管理のみ
|
||||
|
||||
instruction_rewriter.rs (589行)
|
||||
- merge_and_rewrite()実装
|
||||
- 上記2モジュールに委譲
|
||||
✅ 単一責任: 命令変換のみ
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 箱理論への準拠
|
||||
|
||||
### TailCallClassifier Box
|
||||
- **責務**: tail call の分類ロジック
|
||||
- **入力**: is_entry_func_entry_block, has_loop_header_phis, has_boundary
|
||||
- **出力**: TailCallKind (LoopEntry/BackEdge/ExitJump)
|
||||
- **独立性**: ✅ 完全に独立してテスト可能
|
||||
|
||||
### MergeResult Box
|
||||
- **責務**: マージ結果のデータ保持
|
||||
- **状態**: exit_block_id, exit_phi_inputs, carrier_inputs
|
||||
- **操作**: add_exit_phi_input(), add_carrier_input()
|
||||
- **独立性**: ✅ 他のBoxに依存しない
|
||||
|
||||
### InstructionRewriter Box
|
||||
- **責務**: JoinIR命令のMIRへの変換
|
||||
- **委譲**: TailCallClassifier, MergeResult
|
||||
- **独立性**: ⚠️ まだ589行(次Phase対象)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 次のステップ
|
||||
|
||||
### Phase 33-17-B: loop_header_phi_builder 分割(推奨)
|
||||
|
||||
**目標**:
|
||||
- loop_header_phi_builder.rs: 318行 → 170行
|
||||
- loop_header_phi_info.rs: 新規 150行
|
||||
|
||||
**理由**:
|
||||
- データ構造(LoopHeaderPhiInfo)とビルダーロジックを分離
|
||||
- LoopHeaderPhiInfo を他モジュールから独立利用可能に
|
||||
|
||||
**実装タスク**:
|
||||
1. loop_header_phi_info.rs 作成
|
||||
- LoopHeaderPhiInfo struct
|
||||
- CarrierPhiEntry struct
|
||||
- get/set メソッド
|
||||
|
||||
2. loop_header_phi_builder.rs リファクタリング
|
||||
- LoopHeaderPhiBuilder のみ残す
|
||||
- build(), finalize() 実装
|
||||
|
||||
3. merge/mod.rs 更新
|
||||
- loop_header_phi_info モジュール追加
|
||||
- 公開API再エクスポート
|
||||
|
||||
---
|
||||
|
||||
### Phase 33-17-C: instruction_rewriter さらなる分割(検討中)
|
||||
|
||||
**現状**:
|
||||
- instruction_rewriter.rs: まだ589行(目標200行の2.9倍)
|
||||
|
||||
**候補分割案**:
|
||||
1. **boundary_injector_wrapper.rs** (180行)
|
||||
- BoundaryInjector 呼び出しロジック
|
||||
- Copy命令生成
|
||||
|
||||
2. **instruction_mapper.rs** (350行)
|
||||
- merge_and_rewrite() コア処理
|
||||
- Call→Jump変換
|
||||
- 命令リマッピング
|
||||
|
||||
3. **parameter_binder.rs** (60行)
|
||||
- tail call パラメータバインディング
|
||||
- Copy命令生成
|
||||
|
||||
**判断基準**:
|
||||
- ✅ 実施: instruction_rewriter が400行を超える場合
|
||||
- ⚠️ 保留: 300-400行なら現状維持
|
||||
- ❌ 不要: 300行以下なら分割不要
|
||||
|
||||
---
|
||||
|
||||
## 📈 プロジェクト全体への影響
|
||||
|
||||
### コード品質
|
||||
- ✅ 単体テスト追加: TailCallClassifier(4ケース)
|
||||
- ✅ ドキュメント改善: 箱理論の役割明記
|
||||
- ✅ 保守性向上: 関心の分離完全実現
|
||||
|
||||
### ビルド時間
|
||||
- 影響なし(1分02秒 → 1分03秒、誤差範囲)
|
||||
|
||||
### テスト通過
|
||||
- 既存テスト: 全てパス(確認済み)
|
||||
- 新規テスト: 4ケース追加(全てパス)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 達成事項
|
||||
|
||||
1. ✅ instruction_rewriter.rs の責務分離完了
|
||||
2. ✅ TailCallClassifier Box の完全独立化
|
||||
3. ✅ MergeResult Box のデータ管理責任明確化
|
||||
4. ✅ 単体テスト整備(4ケース追加)
|
||||
5. ✅ ビルド成功・既存テストパス確認
|
||||
6. ✅ 箱理論への完全準拠
|
||||
|
||||
---
|
||||
|
||||
## 📝 レビューポイント
|
||||
|
||||
### 良かった点
|
||||
- 分割粒度が適切(109行、46行)
|
||||
- テストコードを同時に追加
|
||||
- 既存のAPIを破壊しない設計
|
||||
|
||||
### 改善点
|
||||
- instruction_rewriter.rs がまだ589行(さらなる分割検討余地)
|
||||
- ドキュメントコメントをより充実させる余地
|
||||
|
||||
### 次の改善機会
|
||||
- Phase 33-17-B: loop_header_phi_builder 分割
|
||||
- Phase 33-17-C: instruction_rewriter さらなる分割(必要に応じて)
|
||||
|
||||
---
|
||||
|
||||
**Status**: Phase 33-17-A 完了✅
|
||||
**Build**: Success(1m 03s)
|
||||
**Tests**: All Pass
|
||||
**Next**: Phase 33-17-B 実施検討
|
||||
@ -1,3 +1,6 @@
|
||||
Status: Active
|
||||
Scope: Phase 33-17 の JoinIR モジュール化分析の要約(現行参照用)。詳細メモは archive 側へ移行。
|
||||
|
||||
# Phase 33-17: JoinIR モジュール化分析
|
||||
|
||||
## 実行日: 2025-12-07
|
||||
|
||||
@ -1,209 +0,0 @@
|
||||
# Phase 33-18: continue+if/else ループパターン設計フェーズ
|
||||
|
||||
**Goal**: 「if (cond) { … } else { continue }」型のループを JoinIR で扱う方法を箱理論ベースで設計する
|
||||
|
||||
---
|
||||
|
||||
## Task 33-18-1: continue+if/else パターンのインベントリ
|
||||
|
||||
### 検出方法
|
||||
- `rg "continue" apps/tests/ tools/selfhost/ --glob "*.hako"`
|
||||
|
||||
### パターン一覧表
|
||||
|
||||
| ファイル | ループ条件 | continue位置 | if構造 | carrier数 | 更新式 |
|
||||
|---------|-----------|-------------|--------|----------|--------|
|
||||
| **Pattern A: if (cond) { continue } - then側continue** |||||||
|
||||
| `loop_continue_pattern4.hako` | `i < 10` | then | `if (i % 2 == 0) { continue }` | 2 (i, sum) | `i = i + 1`, `sum = sum + i` |
|
||||
| `test_pattern4_simple_continue.hako` | `i < n` | then | `if is_even == 1 { continue }` | 3 (i, sum, is_even) | `i = i + 1`, `sum = sum + i` |
|
||||
| `parser_box_minimal.hako:skip_ws` | `i < n` | then | `if ch == " " \|\| ... { continue }` | 1 (i) | `i = i + 1` |
|
||||
| `llvm_phi_mix.hako` | `i < 10` | then | `if (i == 2 \|\| i == 4) { continue }` | 2 (i, sum) | 条件付き更新 |
|
||||
| `llvm_stage3_break_continue.hako` | `i < 10` | then | `if (i < 5) { continue }` | 1 (i) | `i = i + 1` |
|
||||
| **Pattern B: if (cond) { ... } else { continue } - else側continue** |||||||
|
||||
| `loop_if_phi_continue.hako` | `i < 6` | else | `if (i % 2 == 0) { i++; printed++; continue } else { i+=2 }` | 2 (i, printed) | 両分岐で更新 |
|
||||
| `失敗テスト(mirbuilder...)` | `i < 5` | else | `if (i != M) { sum += i } else { continue }` | 3 (i, s, M) | then側のみ更新 |
|
||||
| **Pattern C: 複雑パターン(nested/mixed)** |||||||
|
||||
| `loopform_continue_break_scan.hako` | `true` | then | continue + break 混在 | 2 (i, sum) | 複数分岐 |
|
||||
| `try_finally_continue_inner_loop.hako` | `j < 3` | then | `if (j == 1) { mark = 1; continue }` | 2 (j, mark) | try/finally内 |
|
||||
| `nested_loop_inner_continue_isolated.hako` | `j < 3` | then | `if (j == 1) { continue }` | 1 (j) | 内側ループ |
|
||||
|
||||
### パターン分類
|
||||
|
||||
#### Pattern A: then側continue(単純)
|
||||
```nyash
|
||||
loop(cond) {
|
||||
if (skip_condition) {
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
// main processing
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
- **特徴**: continue が条件成立時に実行される「スキップ」パターン
|
||||
- **既存対応**: Pattern4 で処理可能な形式
|
||||
- **問題なし**: 現在動作している
|
||||
|
||||
#### Pattern B: else側continue(問題あり)
|
||||
```nyash
|
||||
loop(cond) {
|
||||
if (process_condition) {
|
||||
// main processing
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
```
|
||||
- **特徴**: continue が条件不成立時に実行される
|
||||
- **論理的同等**: `if (!process_condition) { continue } else { ... }` と等価
|
||||
- **問題**: 現在 JoinIR では対応できず失敗する
|
||||
- **失敗例**: `mirbuilder_loop_varvar_ne_else_continue_desc_core_exec_canary_vm`
|
||||
|
||||
---
|
||||
|
||||
## Task 33-18-2: LoopFeatures / PatternKind から見た分類
|
||||
|
||||
### 現在の classify() ロジック
|
||||
|
||||
```rust
|
||||
pub fn classify(features: &LoopFeatures) -> LoopPatternKind {
|
||||
// Pattern 4: Continue (highest priority)
|
||||
if features.has_continue {
|
||||
return LoopPatternKind::Pattern4Continue;
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**問題点**: `has_continue == true` だけで Pattern4 に分類するが、
|
||||
- Pattern B(else側continue)は if-else 構造を持つ
|
||||
- `has_if_else_phi == true` と `has_continue == true` が同時に成立する可能性
|
||||
- 現在のロジックでは continue 優先のため、Pattern4 に分類されるが lowering できない
|
||||
|
||||
### 設計案
|
||||
|
||||
#### 案 A: Pattern4 に統合(BoolExprLowerer で正規化)
|
||||
|
||||
**アイデア**:
|
||||
- `if (!cond) { ... } else { continue }` を `if (cond) { continue } else { ... }` に変換
|
||||
- BoolExprLowerer に「条件反転 + 分岐入れ替え」ロジックを追加
|
||||
- Pattern4 lowerer はそのまま使える
|
||||
|
||||
**メリット**:
|
||||
- 新しい Pattern を追加しなくて良い
|
||||
- 既存の Pattern4 lowerer を再利用
|
||||
- 箱の数が増えない
|
||||
|
||||
**デメリット**:
|
||||
- BoolExprLowerer の責務が増える
|
||||
- 反転ロジックが複雑になる可能性
|
||||
|
||||
#### 案 B: 新規 Pattern5 として独立
|
||||
|
||||
**アイデア**:
|
||||
- `Pattern5ContinueIfElse` を新設
|
||||
- `has_continue && has_if_else_phi` の組み合わせを検出
|
||||
- 専用 lowerer を実装
|
||||
|
||||
**メリット**:
|
||||
- 責務が明確に分離
|
||||
- Pattern4 と独立して実装・テスト可能
|
||||
|
||||
**デメリット**:
|
||||
- 新しい箱が増える
|
||||
- 重複コードが発生する可能性
|
||||
|
||||
### 選択基準
|
||||
|
||||
| 基準 | 案 A (統合) | 案 B (新設) |
|
||||
|-----|------------|------------|
|
||||
| 箱の数 | 増えない | +1 (Pattern5) |
|
||||
| 既存コード変更 | BoolExprLowerer | classify() のみ |
|
||||
| 実装難易度 | 中(反転ロジック) | 中(新規lowerer) |
|
||||
| テスト容易性 | 既存テスト再利用 | 新規テスト必要 |
|
||||
|
||||
**推奨**: **案 A(Pattern4 統合)**
|
||||
- 理由: `if (cond) { continue }` と `if (!cond) { ... } else { continue }` は論理的に同型
|
||||
- 「continue がどちらの分岐にあっても、最終的に同じ CFG 骨格になる」ことを活用
|
||||
|
||||
---
|
||||
|
||||
## Task 33-18-3: JoinIR 箱との責務マッピング
|
||||
|
||||
### 既存箱との関係
|
||||
|
||||
| 箱 | 現在の責務 | Pattern B での役割 |
|
||||
|---|-----------|------------------|
|
||||
| **LoopFeatures** | break/continue/if_else_phi 検出 | 変更なし(情報収集のみ) |
|
||||
| **classify()** | Pattern 1-4 振り分け | 案Aなら変更なし |
|
||||
| **BoolExprLowerer** | 条件式の SSA 化 | **拡張**: continue 分岐の正規化 |
|
||||
| **Pattern4 lowerer** | continue ブロック生成 | 変更なし |
|
||||
| **Header PHI** | ループヘッダの PHI 生成 | 変更なし |
|
||||
| **ExitLine** | carrier / expr 出口処理 | 変更なし |
|
||||
|
||||
### 変更が必要な箇所
|
||||
|
||||
1. **BoolExprLowerer** (or 新規 ContinueBranchNormalizer Box)
|
||||
- `if (cond) { ... } else { continue }` を検出
|
||||
- `if (!cond) { continue } else { ... }` に変換
|
||||
- 変換後の AST を Pattern4 lowerer に渡す
|
||||
|
||||
2. **router.rs** (optional)
|
||||
- else 側 continue の検出を追加
|
||||
- BoolExprLowerer への委譲を追加
|
||||
|
||||
### joinir-architecture-overview.md への追記案
|
||||
|
||||
```markdown
|
||||
### Continue パターンの分類ルール (Phase 33-18)
|
||||
|
||||
- Pattern4_WithContinue は以下の条件で適用:
|
||||
- `has_continue == true` AND `has_break == false`
|
||||
- continue の位置(then/else)は問わない(正規化で吸収)
|
||||
|
||||
- else 側 continue の処理:
|
||||
- BoolExprLowerer で条件反転 → then 側 continue 形式に正規化
|
||||
- 正規化後は通常の Pattern4 として処理
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 33-18-4: 完了条件と次フェーズへの橋渡し
|
||||
|
||||
### Phase 33-18 完了条件チェックリスト
|
||||
|
||||
- [x] continue+if/else パターンのインベントリが docs に揃っている
|
||||
- [x] Pattern4 に畳めるか/Pattern5 新設かの方針が決まっている(案 A: 統合)
|
||||
- [x] JoinIR の箱たち(Features / BoolExprLowerer / Header PHI / ExitLine)のどこを触るかが決まっている
|
||||
- [ ] 実装フェーズ(33-19)のタスクリストが 3〜5 個に落ちている
|
||||
|
||||
### 実装フェーズ (Phase 33-19) タスクリスト案
|
||||
|
||||
1. **Task 33-19-1**: ContinueBranchNormalizer Box 作成
|
||||
- else 側 continue を then 側に移動する AST 変換
|
||||
- 単体テスト付き
|
||||
|
||||
2. **Task 33-19-2**: router.rs への統合
|
||||
- Pattern B 検出時に正規化を呼び出す
|
||||
- 正規化後 Pattern4 lowerer に委譲
|
||||
|
||||
3. **Task 33-19-3**: 失敗テスト修正
|
||||
- `mirbuilder_loop_varvar_ne_else_continue_desc_core_exec_canary_vm` が PASS になることを確認
|
||||
|
||||
4. **Task 33-19-4**: 追加スモークテスト
|
||||
- Pattern B の各バリエーション(単一carrier、複数carrier)
|
||||
|
||||
5. **Task 33-19-5**: ドキュメント更新
|
||||
- joinir-architecture-overview.md に正式追記
|
||||
|
||||
---
|
||||
|
||||
## 備考
|
||||
|
||||
- 失敗テストの直接原因は「JoinIR does not support this pattern」エラー
|
||||
- LoopBuilder は既に削除されているため、JoinIR での対応が必須
|
||||
- CFG reachability の問題も別途あり(Rust CLI 経由では MIR 生成されるが reachable=false)
|
||||
|
||||
**作成日**: 2025-12-07
|
||||
**Phase**: 33-18 (Design Only)
|
||||
@ -1,143 +0,0 @@
|
||||
今後は continue 系と JsonParserBox のような実アプリ側ロジックを順番に乗せていく段階に入る。**
|
||||
# Phase 33‑20: Loop Exit Semantics Fix — Completion Summary
|
||||
|
||||
日付: 2025‑12‑07
|
||||
状態: ✅ 実装完了(Pattern1/2/3/4 正常、複雑 continue パターンのみ残課題)
|
||||
|
||||
---
|
||||
|
||||
## 1. ゴール
|
||||
|
||||
LoopBuilder 完全削除後の JoinIR ループラインにおいて、
|
||||
|
||||
- loop header PHI
|
||||
- ExitLine(ExitMeta/ExitBinding/ExitLineReconnector)
|
||||
- JoinInlineBoundary + BoundaryInjector
|
||||
|
||||
のあいだで「ループ出口値(expr + carrier)」の意味論を揃え、
|
||||
|
||||
- SSA‑undef を起こさない
|
||||
- Pattern1/2/3/4 代表ケースでループ終了時の値が正しく戻ってくる
|
||||
|
||||
状態にすること。
|
||||
|
||||
---
|
||||
|
||||
## 2. 変更内容
|
||||
|
||||
### 2.1 BoundaryInjector の修正(loop_var_name 対応)
|
||||
|
||||
ファイル: `src/mir/builder/joinir_inline_boundary_injector.rs`
|
||||
|
||||
問題:
|
||||
|
||||
- loop header PHI の `dst`(ループ変数の現在値)に対して、BoundaryInjector が entry block で Copy を挿し、
|
||||
header PHI の意味を上書きしてしまうケースがあった。
|
||||
|
||||
修正:
|
||||
|
||||
- `JoinInlineBoundary.loop_var_name` が設定されている場合は、
|
||||
**すべての `join_inputs` について entry block での Copy 挿入をスキップ**するように変更。
|
||||
- これにより、header PHI で決まった `dst` が entry の Copy で壊されることがなくなり、
|
||||
header PHI が「ループ変数の SSOT」として機能するようになった。
|
||||
|
||||
### 2.2 Pattern3(If‑Else PHI) の Boundary 設定
|
||||
|
||||
ファイル: `src/mir/join_ir/lowering/loop_with_if_phi_minimal.rs`
|
||||
|
||||
問題:
|
||||
|
||||
- Pattern3 lowerer が JoinInlineBoundary に `loop_var_name` を設定しておらず、
|
||||
BoundaryInjector/InstructionRewriter が「このループに expr/キャリア出口がある」ことを認識できていなかった。
|
||||
|
||||
修正:
|
||||
|
||||
- Pattern2 と同様に、Pattern3 でも `boundary.loop_var_name = Some(..)` を設定。
|
||||
- これにより、JoinIR merge 時に header PHI / exit PHI のラインが Pattern3 でも有効になる。
|
||||
|
||||
### 2.3 merge/mod.rs — LoopHeader PHI + carrier PHI の連携
|
||||
|
||||
ファイル: `src/mir/builder/control_flow/joinir/merge/mod.rs`
|
||||
|
||||
修正ポイント:
|
||||
|
||||
1. ExitLine 側から header PHI に必要な carrier 名一覧(`other_carriers`)を抽出し、
|
||||
LoopHeaderPhiBuilder に渡すように変更。
|
||||
2. LoopHeaderPhiBuilder が生成した header PHI の `dst`(carrier_phis)を、
|
||||
LoopExitBinding/ExitLineReconnector が利用するように接続。
|
||||
3. function_params のキーを `"join_func_0"`, `"join_func_1"` 等の実際の JoinIR 関数名に合わせるよう修正
|
||||
(誤って `"main"`, `"loop_step"` を参照していたため Pattern4 で PHI が正しく構築されていなかった)。
|
||||
|
||||
これにより、
|
||||
|
||||
- header PHI `dst` を起点に、carrier 用の出口値が ExitLine へ正しく流れるようになった。
|
||||
|
||||
### 2.4 instruction_rewriter.rs — header PHI への Copy スキップ+latch incoming 設定
|
||||
|
||||
ファイル: `src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs`
|
||||
|
||||
修正内容:
|
||||
|
||||
- LoopHeader PHI の `dst` に対して、余計な Copy が挿入されないようにするガードを追加。
|
||||
- 複数キャリアのケースで、latch block からの incoming を header PHI の入力として正しく構成するよう調整。
|
||||
|
||||
これにより、
|
||||
|
||||
- Pattern1/2/3/4 のループ変数が header PHI → exit PHI → variable_map の順で一貫して伝播するようになった。
|
||||
|
||||
---
|
||||
|
||||
## 3. テスト結果
|
||||
|
||||
### 3.1 Pattern1: Simple While
|
||||
|
||||
- テスト: `apps/tests/loop_min_while.hako`
|
||||
- 期待値: `0, 1, 2` を出力し、RC は 0。
|
||||
- 結果: ✅ 期待どおり。
|
||||
|
||||
### 3.2 Pattern2: Loop with Break(expr ループ)
|
||||
|
||||
- テスト: `apps/tests/joinir_min_loop.hako`
|
||||
- 期待値: ループ終了時の `i` の値(2)が expr result として返る。
|
||||
- 結果: ✅ RC: 2、SSA‑undef なし。
|
||||
|
||||
### 3.3 Pattern3: Loop with If‑Else PHI
|
||||
|
||||
- テスト: `apps/tests/loop_if_phi.hako`
|
||||
- 期待値: `sum = 9`(1+3+5)。
|
||||
- 結果: ✅ `sum = 9` を出力、RC も期待どおり。
|
||||
|
||||
### 3.4 Pattern4: Loop with Continue
|
||||
|
||||
- テスト: `apps/tests/loop_continue_pattern4.hako`
|
||||
- 期待値: 25(1+3+5+7+9)。
|
||||
- 結果: ✅ 出力は 25。
|
||||
- `[joinir/freeze]` / SSA‑undef は発生しない。
|
||||
- function_params キーの誤参照(`"main"/"loop_step"` → `"join_func_0"/"join_func_1"`) を修正したことで、
|
||||
header PHI / ExitLine との結線が正しくなり、Pattern4 の単純 continue ケースでも期待どおりの値になった。
|
||||
|
||||
---
|
||||
|
||||
## 4. まとめと今後
|
||||
|
||||
### 達成点
|
||||
|
||||
- header PHI を起点とした Loop exit の意味論が Pattern1〜4 の代表ケースで一貫するようになった。
|
||||
- ExitLine(carrier)と expr PHI ラインが、LoopBuilder なしの JoinIR パイプラインで安全に動く。
|
||||
- trim や JsonParser のような複雑なループに対しても、基盤として使える出口経路が整った。
|
||||
|
||||
### 残課題
|
||||
|
||||
- 「else 側 continue」を含む複雑な continue パターン(Phase 33‑18 の Pattern B)はまだ JoinIR 側で正規化されていない。
|
||||
- これらは BoolExprLowerer/ContinueBranchNormalizer で `if (!cond) { … }` 形に正規化し、
|
||||
Pattern4 lowerer に統合する計画(Phase 33‑19 以降)。
|
||||
|
||||
### 関連フェーズ
|
||||
|
||||
- Phase 33‑16: Loop header PHI SSOT 導入
|
||||
- Phase 33‑19: Continue + if/else パターンの正規化(設計済み)
|
||||
- Phase 170: JsonParserBox / trim の JoinIR 準備ライン
|
||||
|
||||
このフェーズで「LoopBuilder 無しの JoinIR ループ出口ラインの基礎」は固まったので、
|
||||
今後は continue 系と JsonParserBox のような実アプリ側ロジックを順番に乗せていく段階に入る。**
|
||||
|
||||
@ -1,386 +0,0 @@
|
||||
# Phase 33-22: Common Pattern Initialization & Conversion Pipeline
|
||||
|
||||
## Overview
|
||||
|
||||
This phase integrates CommonPatternInitializer and JoinIRConversionPipeline across all 4 loop patterns, eliminating code duplication and establishing unified initialization and conversion flows.
|
||||
|
||||
## Current State Analysis (Before Refactoring)
|
||||
|
||||
### Pattern 1 (pattern1_minimal.rs)
|
||||
|
||||
**Lines 66-76**: Loop var extraction
|
||||
```rust
|
||||
let loop_var_name = self.extract_loop_variable_from_condition(condition)?;
|
||||
let loop_var_id = self.variable_map.get(&loop_var_name)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[cf_loop/pattern1] Loop variable '{}' not found", loop_var_name))?;
|
||||
```
|
||||
|
||||
**Lines 147-153**: Boundary creation
|
||||
```rust
|
||||
let mut boundary = JoinInlineBoundary::new_inputs_only(
|
||||
vec![ValueId(0)],
|
||||
vec![loop_var_id],
|
||||
);
|
||||
boundary.loop_var_name = Some(loop_var_name.clone());
|
||||
```
|
||||
|
||||
**Lines 126-156**: JoinIR conversion and merge
|
||||
- Manual stats logging
|
||||
- convert_join_module_to_mir_with_meta call
|
||||
- merge_joinir_mir_blocks call
|
||||
|
||||
**Status**:
|
||||
- ✅ boundary.loop_var_name: Set (Phase 33-23 fix)
|
||||
- ExitMeta: None (simple loop)
|
||||
- exit_bindings: None
|
||||
|
||||
### Pattern 2 (pattern2_with_break.rs)
|
||||
|
||||
**Lines 58-68**: Loop var extraction (duplicated from Pattern 1)
|
||||
```rust
|
||||
let loop_var_name = self.extract_loop_variable_from_condition(condition)?;
|
||||
let loop_var_id = self.variable_map.get(&loop_var_name)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[cf_loop/pattern2] Loop variable '{}' not found", loop_var_name))?;
|
||||
```
|
||||
|
||||
**Lines 73-126**: Condition variable handling (Pattern 2 specific)
|
||||
- ConditionEnv building
|
||||
- ConditionBinding creation
|
||||
|
||||
**Lines 191-205**: Boundary creation with exit_bindings
|
||||
```rust
|
||||
let exit_bindings = ExitMetaCollector::collect(self, &exit_meta, debug);
|
||||
let mut boundary = JoinInlineBoundary::new_inputs_only(...);
|
||||
boundary.condition_bindings = condition_bindings;
|
||||
boundary.exit_bindings = exit_bindings.clone();
|
||||
boundary.loop_var_name = Some(loop_var_name.clone());
|
||||
```
|
||||
|
||||
**Lines 175-208**: JoinIR conversion and merge
|
||||
- Manual stats logging
|
||||
- convert_join_module_to_mir_with_meta call
|
||||
- merge_joinir_mir_blocks call
|
||||
|
||||
**Status**:
|
||||
- ✅ boundary.loop_var_name: Set
|
||||
- ExitMeta: Break-triggered vars
|
||||
- exit_bindings: From ExitMetaCollector
|
||||
- condition_bindings: Custom handling
|
||||
|
||||
### Pattern 3 (pattern3_with_if_phi.rs)
|
||||
|
||||
**Lines 60-82**: Loop var + carrier extraction
|
||||
```rust
|
||||
let loop_var_name = self.extract_loop_variable_from_condition(condition)?;
|
||||
let loop_var_id = self.variable_map.get(&loop_var_name).copied()?;
|
||||
let sum_var_id = self.variable_map.get("sum").copied()?;
|
||||
```
|
||||
|
||||
**Lines 139-154**: Boundary creation with exit_bindings
|
||||
```rust
|
||||
let mut boundary = JoinInlineBoundary::new_with_exit_bindings(
|
||||
vec![ValueId(0), ValueId(1)],
|
||||
vec![loop_var_id, sum_var_id],
|
||||
vec![
|
||||
LoopExitBinding {
|
||||
carrier_name: "sum".to_string(),
|
||||
join_exit_value: ValueId(18),
|
||||
host_slot: sum_var_id,
|
||||
}
|
||||
],
|
||||
);
|
||||
boundary.loop_var_name = Some(loop_var_name.clone());
|
||||
```
|
||||
|
||||
**Lines 125-156**: JoinIR conversion and merge
|
||||
- Manual stats logging
|
||||
- convert_join_module_to_mir_with_meta call
|
||||
- merge_joinir_mir_blocks call
|
||||
|
||||
**Status**:
|
||||
- ✅ boundary.loop_var_name: Set (Phase 33-23 fix)
|
||||
- ExitMeta: i + sum carriers
|
||||
- exit_bindings: Hardcoded for "sum"
|
||||
|
||||
### Pattern 4 (pattern4_with_continue.rs)
|
||||
|
||||
**Lines 144-152**: Loop var extraction (duplicated)
|
||||
```rust
|
||||
let loop_var_name = self.extract_loop_variable_from_condition(condition)?;
|
||||
let loop_var_id = self.variable_map.get(&loop_var_name)
|
||||
.copied()
|
||||
.ok_or_else(|| format!(...)?;
|
||||
```
|
||||
|
||||
**Lines 155-179**: CarrierInfo building (Pattern 4 specific)
|
||||
```rust
|
||||
let mut carriers = Vec::new();
|
||||
for (var_name, &var_id) in &self.variable_map {
|
||||
if var_name != &loop_var_name {
|
||||
carriers.push(CarrierVar {
|
||||
name: var_name.clone(),
|
||||
host_id: var_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Lines 137-142**: Continue normalization (Pattern 4 specific)
|
||||
```rust
|
||||
let normalized_body = ContinueBranchNormalizer::normalize_loop_body(_body);
|
||||
let body_to_analyze = &normalized_body;
|
||||
```
|
||||
|
||||
**Status**:
|
||||
- ✅ boundary.loop_var_name: Set
|
||||
- ExitMeta: Dynamic carrier analysis
|
||||
- exit_bindings: Comprehensive
|
||||
- Special: ContinueBranchNormalizer + LoopUpdateAnalyzer
|
||||
|
||||
## Commonalization Strategy
|
||||
|
||||
### Shared Initialization (ALL patterns)
|
||||
|
||||
The following steps are identical across all 4 patterns and can be unified:
|
||||
|
||||
1. **Extract loop variable from condition**
|
||||
- Call `extract_loop_variable_from_condition(condition)`
|
||||
- Look up ValueId in variable_map
|
||||
- Error handling with pattern-specific message
|
||||
|
||||
2. **Build CarrierInfo from variable_map**
|
||||
- Iterate through variable_map
|
||||
- Filter out loop variable
|
||||
- Create CarrierVar structs
|
||||
- Optional: exclude specific variables (Pattern 2)
|
||||
|
||||
3. **Set boundary.loop_var_name** ← Critical invariant
|
||||
- Required for header PHI generation
|
||||
- Must be set for all patterns
|
||||
|
||||
### Pattern-Specific (Handled after init)
|
||||
|
||||
Each pattern has specific needs that happen AFTER common initialization:
|
||||
|
||||
- **Pattern 1**: No special handling (simplest case)
|
||||
- **Pattern 2**:
|
||||
- ConditionEnv building
|
||||
- ConditionBinding creation
|
||||
- ExitMetaCollector usage
|
||||
- **Pattern 3**:
|
||||
- Hardcoded exit_bindings for "sum"
|
||||
- Multiple carriers (i + sum)
|
||||
- **Pattern 4**:
|
||||
- ContinueBranchNormalizer
|
||||
- LoopUpdateAnalyzer
|
||||
- Dynamic carrier filtering
|
||||
|
||||
### Conversion Pipeline (ALL patterns)
|
||||
|
||||
The JoinIR → MIR → Merge flow is identical:
|
||||
|
||||
1. Log JoinIR stats (functions, blocks)
|
||||
2. Convert JoinModule → MirModule
|
||||
3. Log MIR stats (functions, blocks)
|
||||
4. Call merge_joinir_mir_blocks
|
||||
5. Return result
|
||||
|
||||
## Code Duplication Identified
|
||||
|
||||
### Loop Variable Extraction (4 occurrences × ~10 lines = 40 lines)
|
||||
|
||||
```rust
|
||||
// Pattern 1, 2, 3, 4: All have this
|
||||
let loop_var_name = self.extract_loop_variable_from_condition(condition)?;
|
||||
let loop_var_id = self.variable_map.get(&loop_var_name)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("[cf_loop/patternX] Loop variable '{}' not found", loop_var_name))?;
|
||||
```
|
||||
|
||||
### Conversion + Merge (4 occurrences × ~30 lines = 120 lines)
|
||||
|
||||
```rust
|
||||
// Pattern 1, 2, 3, 4: All have this
|
||||
trace::trace().joinir_stats(...);
|
||||
let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta)?;
|
||||
trace::trace().joinir_stats(...);
|
||||
let exit_phi_result = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;
|
||||
```
|
||||
|
||||
### CarrierInfo Building (3 occurrences × ~15 lines = 45 lines)
|
||||
|
||||
```rust
|
||||
// Pattern 3, 4: Build carriers from variable_map
|
||||
let mut carriers = Vec::new();
|
||||
for (var_name, &var_id) in &self.variable_map {
|
||||
if var_name != &loop_var_name {
|
||||
carriers.push(CarrierVar { name: var_name.clone(), host_id: var_id });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Total Duplication**: ~205 lines
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: CommonPatternInitializer Integration
|
||||
|
||||
For each pattern:
|
||||
1. Replace loop var extraction with `CommonPatternInitializer::initialize_pattern`
|
||||
2. Use returned `carrier_info` instead of manual building
|
||||
3. Keep pattern-specific processing (condition_bindings, normalizer, etc.)
|
||||
|
||||
### Phase 2: JoinIRConversionPipeline Integration
|
||||
|
||||
For each pattern:
|
||||
1. Replace manual conversion + merge with `JoinIRConversionPipeline::execute`
|
||||
2. Remove duplicate stats logging
|
||||
3. Maintain error handling
|
||||
|
||||
## Expected Results
|
||||
|
||||
### Code Reduction
|
||||
- Pattern 1: -20 lines (initialization + conversion)
|
||||
- Pattern 2: -25 lines (initialization + conversion, keep condition handling)
|
||||
- Pattern 3: -20 lines (initialization + conversion)
|
||||
- Pattern 4: -25 lines (initialization + conversion, keep normalizer)
|
||||
- **Total**: -90 lines (conservative estimate)
|
||||
|
||||
### Maintainability Improvements
|
||||
- Single source of truth for initialization
|
||||
- Unified conversion pipeline
|
||||
- Easier to add new patterns
|
||||
- Reduced cognitive load
|
||||
|
||||
### Testing Requirements
|
||||
- All 4 patterns must pass existing tests
|
||||
- No behavioral changes
|
||||
- SSA-undef checks must pass
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### Before (Pattern 1)
|
||||
```rust
|
||||
let loop_var_name = self.extract_loop_variable_from_condition(condition)?;
|
||||
let loop_var_id = self.variable_map.get(&loop_var_name).copied()?;
|
||||
let mut boundary = JoinInlineBoundary::new_inputs_only(...);
|
||||
boundary.loop_var_name = Some(loop_var_name.clone());
|
||||
let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta)?;
|
||||
let exit_phi_result = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;
|
||||
```
|
||||
|
||||
### After (Pattern 1)
|
||||
```rust
|
||||
use super::common_init::CommonPatternInitializer;
|
||||
use super::conversion_pipeline::JoinIRConversionPipeline;
|
||||
|
||||
let (loop_var_name, loop_var_id, _carrier_info) =
|
||||
CommonPatternInitializer::initialize_pattern(self, condition, &self.variable_map, None)?;
|
||||
|
||||
let mut boundary = JoinInlineBoundary::new_inputs_only(
|
||||
vec![ValueId(0)],
|
||||
vec![loop_var_id],
|
||||
);
|
||||
boundary.loop_var_name = Some(loop_var_name.clone());
|
||||
|
||||
let exit_phi_result = JoinIRConversionPipeline::execute(
|
||||
self,
|
||||
join_module,
|
||||
Some(&boundary),
|
||||
"pattern1",
|
||||
debug,
|
||||
)?;
|
||||
```
|
||||
|
||||
## Success Criteria
|
||||
|
||||
✅ CommonPatternInitializer used in all 4 patterns
|
||||
✅ JoinIRConversionPipeline used in all 4 patterns
|
||||
✅ At least 90 lines of code removed
|
||||
✅ All existing tests pass
|
||||
✅ No new SSA-undef errors
|
||||
✅ No new compiler warnings
|
||||
✅ Documentation updated
|
||||
|
||||
## Implementation Results
|
||||
|
||||
### Code Reduction Achievement
|
||||
|
||||
**Pattern File Line Counts (After Refactoring)**:
|
||||
- Pattern 1: 151 lines
|
||||
- Pattern 2: 191 lines
|
||||
- Pattern 3: 148 lines
|
||||
- Pattern 4: 316 lines
|
||||
- **Total**: 806 lines
|
||||
|
||||
**Infrastructure**:
|
||||
- CommonPatternInitializer: 117 lines
|
||||
- JoinIRConversionPipeline: 127 lines
|
||||
- **Total Infrastructure**: 244 lines
|
||||
|
||||
**Net Result**: All 4 patterns + infrastructure = 1,050 lines
|
||||
|
||||
### Before/After Comparison
|
||||
|
||||
**Pattern-Specific Changes**:
|
||||
|
||||
#### Pattern 1
|
||||
- **Removed**: Loop var extraction (10 lines), JoinIR conversion + merge (30 lines)
|
||||
- **Added**: CommonPatternInitializer call (7 lines), JoinIRConversionPipeline call (8 lines)
|
||||
- **Net Reduction**: ~25 lines
|
||||
|
||||
#### Pattern 2
|
||||
- **Removed**: Loop var extraction (10 lines), JoinIR conversion + merge (30 lines)
|
||||
- **Added**: CommonPatternInitializer call (7 lines), JoinIRConversionPipeline call (8 lines)
|
||||
- **Net Reduction**: ~25 lines
|
||||
|
||||
#### Pattern 3
|
||||
- **Removed**: Loop var + carrier extraction (22 lines), JoinIR conversion + merge (30 lines)
|
||||
- **Added**: CommonPatternInitializer call (19 lines - includes carrier extraction), JoinIRConversionPipeline call (8 lines)
|
||||
- **Net Reduction**: ~25 lines
|
||||
|
||||
#### Pattern 4
|
||||
- **Removed**: Loop var extraction (10 lines), Carrier building (15 lines), JoinIR conversion + merge (30 lines)
|
||||
- **Added**: CommonPatternInitializer call (7 lines), JoinIRConversionPipeline call (8 lines)
|
||||
- **Net Reduction**: ~40 lines (Pattern 4 had most duplication)
|
||||
|
||||
**Total Estimated Reduction**: ~115 lines across all patterns
|
||||
|
||||
### Test Results
|
||||
|
||||
All 4 patterns tested successfully:
|
||||
|
||||
```bash
|
||||
=== Pattern 1: Simple While ===
|
||||
0, 1, 2, RC: 0 ✅
|
||||
|
||||
=== Pattern 2: JoinIR Min Loop (with break) ===
|
||||
RC: 0 ✅
|
||||
|
||||
=== Pattern 3: If-Else PHI ===
|
||||
sum=9, RC: 0 ✅
|
||||
|
||||
=== Pattern 4: Then-Continue ===
|
||||
25, RC: 0 ✅
|
||||
```
|
||||
|
||||
### Quality Improvements
|
||||
|
||||
1. **Single Source of Truth**: All initialization logic consolidated
|
||||
2. **Unified Conversion Flow**: Consistent JoinIR→MIR→Merge pipeline
|
||||
3. **Reduced Duplication**: Zero duplicate initialization or conversion code
|
||||
4. **Maintainability**: Future changes only need to happen in 2 places
|
||||
5. **Testability**: Infrastructure can be tested independently
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
**None**: This is a pure refactoring with no API changes.
|
||||
|
||||
## References
|
||||
|
||||
- CommonPatternInitializer: `src/mir/builder/control_flow/joinir/patterns/common_init.rs`
|
||||
- JoinIRConversionPipeline: `src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs`
|
||||
- Phase 33-23 fix: boundary.loop_var_name setting
|
||||
- Pattern 1-4: `src/mir/builder/control_flow/joinir/patterns/pattern*.rs`
|
||||
@ -1,224 +0,0 @@
|
||||
# Phase 33-23: JoinIR中期リファクタリング完了報告
|
||||
|
||||
**日付**: 2025-12-07
|
||||
**フェーズ**: Phase 33-23 (JoinIR Modularization)
|
||||
**実装者**: Claude Code (Sonnet 4.5)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 実装完了サマリー
|
||||
|
||||
### Priority 1: Pattern 4 二重実装の分析
|
||||
|
||||
**結果**: **統合不要**(責務分離が正しいと確認)
|
||||
|
||||
#### 詳細分析
|
||||
|
||||
**ファイルA**: `pattern4_with_continue.rs` (361行)
|
||||
- **役割**: ホスト統合レイヤー
|
||||
- **責務**: CarrierInfo構築、variable_map管理、MIRマージ
|
||||
|
||||
**ファイルB**: `loop_with_continue_minimal.rs` (506行)
|
||||
- **役割**: 純粋JoinIR生成レイヤー
|
||||
- **責務**: JoinModule生成、Select命令実装
|
||||
|
||||
#### 判定理由
|
||||
|
||||
1. **責務分離が明確**: A=ホスト統合、B=純粋変換
|
||||
2. **再利用性**: Bは他のパターンでも利用可能
|
||||
3. **テスト容易性**: Bは独立してテスト可能
|
||||
4. **可読性**: 統合すると461行のヘルパーがAに混入
|
||||
|
||||
**削減見込み**: **0行**(統合しない)
|
||||
|
||||
---
|
||||
|
||||
### Priority 2: LoopToJoin 構造の箱化
|
||||
|
||||
**結果**: **実装完了** ✅
|
||||
|
||||
#### 新規作成ファイル
|
||||
|
||||
1. **loop_pattern_validator.rs** (224行)
|
||||
- Exit構造検証
|
||||
- Header構造検証
|
||||
- Progress carrier検証
|
||||
|
||||
2. **loop_view_builder.rs** (251行)
|
||||
- Pattern 1検出
|
||||
- Shape検出
|
||||
- Lowerer選択・ディスパッチ
|
||||
|
||||
#### 修正ファイル
|
||||
|
||||
**loop_to_join.rs**: 590行 → **294行** (50%削減)
|
||||
- Validator/Builder委譲
|
||||
- コーディネーター責務のみ
|
||||
|
||||
#### 責務分離構造
|
||||
|
||||
```
|
||||
Before (590行):
|
||||
LoopToJoinLowerer
|
||||
├── lower() (89行)
|
||||
├── is_supported_case_a_loop_view() (180行)
|
||||
└── lower_with_scope() (343行)
|
||||
|
||||
After (769行 total, 但し重複削減後は実質650行):
|
||||
LoopToJoinLowerer (294行)
|
||||
├── validator: LoopPatternValidator (224行)
|
||||
│ └── is_supported_case_a()
|
||||
└── builder: LoopViewBuilder (251行)
|
||||
└── build()
|
||||
```
|
||||
|
||||
#### 削減効果
|
||||
|
||||
- **Before**: 590行(単一ファイル)
|
||||
- **After**: 294行(coordinator)+ 224行(validator)+ 251行(builder)= 769行
|
||||
- **実質削減**: 重複コード削減により **約140行削減**(24%削減)
|
||||
|
||||
---
|
||||
|
||||
### Priority 3: Generic Case-A 統一
|
||||
|
||||
**結果**: **未実装**(今後のタスク)
|
||||
|
||||
#### 推奨設計
|
||||
|
||||
**Trait定義**:
|
||||
```rust
|
||||
pub trait CaseASpecialization {
|
||||
fn get_name(&self) -> &'static str;
|
||||
fn validate_scope(&self, scope: &LoopScopeShape) -> Option<ScopeBinding>;
|
||||
fn build_body_instructions(...) -> Vec<JoinInst>;
|
||||
fn build_phi_updates(...) -> Vec<(ValueId, ValueId)>;
|
||||
}
|
||||
```
|
||||
|
||||
**期待効果**:
|
||||
- 共通ボイラープレート削減: 200-300行(30%共通化)
|
||||
- 新パターン追加容易性向上
|
||||
|
||||
---
|
||||
|
||||
## 📊 全体成果
|
||||
|
||||
### ファイル構成
|
||||
|
||||
| ファイル | Before | After | 削減率 |
|
||||
|---------|--------|-------|-------|
|
||||
| loop_to_join.rs | 590行 | 294行 | **50%** |
|
||||
| loop_pattern_validator.rs | - | 224行 | (新規) |
|
||||
| loop_view_builder.rs | - | 251行 | (新規) |
|
||||
| **合計** | 590行 | 769行 | - |
|
||||
|
||||
### 実質削減効果
|
||||
|
||||
- **重複コード削減**: 約140行
|
||||
- **保守性向上**: 責務分離により各モジュール単体テスト可能
|
||||
- **拡張性向上**: 新パターン追加が容易(Validator/Builder分離)
|
||||
|
||||
---
|
||||
|
||||
## ✅ 品質保証
|
||||
|
||||
### ビルドテスト
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
# Result: ✅ SUCCESS (warnings only)
|
||||
```
|
||||
|
||||
### 警告対処
|
||||
|
||||
- ✅ `ExitAnalysis` unused import 修正済み
|
||||
- ⚠️ その他warnings(既存の問題、本PR範囲外)
|
||||
|
||||
### テスト実行
|
||||
|
||||
```bash
|
||||
cargo test --release
|
||||
# Result: 既存テスト全てPASS(回帰なし)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 設計判断の記録
|
||||
|
||||
### 1. Pattern 4統合しない理由
|
||||
|
||||
- **責務分離**: A(ホスト統合)とB(純粋変換)は異なる責務
|
||||
- **再利用性**: B は他のパターンでも利用可能な設計
|
||||
- **テスト容易性**: B は独立してテスト可能
|
||||
- **コード品質**: 統合すると可読性が低下(461行ヘルパー混入)
|
||||
|
||||
### 2. LoopToJoin箱化の価値
|
||||
|
||||
- **単一責任の原則**: Validator(検証)、Builder(選択)、Lowerer(調整)
|
||||
- **保守性**: 各Boxが独立してテスト・修正可能
|
||||
- **拡張性**: 新パターン追加時にBuilder のみ修正すればよい
|
||||
|
||||
### 3. CaseA統一の先送り理由
|
||||
|
||||
- **リスク**: Trait設計の妥当性検証に時間が必要
|
||||
- **優先度**: Priority 2の箱化でより大きな効果を達成済み
|
||||
- **将来実装**: Trait設計案は完成しており、実装は容易
|
||||
|
||||
---
|
||||
|
||||
## 📋 次のアクション
|
||||
|
||||
### 短期タスク(Phase 33-24)
|
||||
|
||||
1. **Priority 3実装**: CaseA Trait統一化
|
||||
- Phase 3-A: Trait定義
|
||||
- Phase 3-B: unified_lowering実装
|
||||
- Phase 3-C: 各パターン移行
|
||||
|
||||
### 中期タスク(Phase 34+)
|
||||
|
||||
1. **テスト強化**: Validator/Builder単体テスト追加
|
||||
2. **ドキュメント整備**: 各Box責務の明確化
|
||||
3. **パフォーマンス測定**: 箱化によるオーバーヘッド確認
|
||||
|
||||
---
|
||||
|
||||
## 📚 関連ドキュメント
|
||||
|
||||
- **分析資料**: [joinir-refactoring-analysis.md](joinir-refactoring-analysis.md)
|
||||
- **アーキテクチャ**: [joinir-architecture-overview.md](joinir-architecture-overview.md)
|
||||
- **Phase 33 INDEX**: [phase33-16-INDEX.md](phase33-16-INDEX.md)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 コミットメッセージ案
|
||||
|
||||
```
|
||||
refactor(joinir): Phase 33-23 LoopToJoin responsibility separation
|
||||
|
||||
**Priority 1: Pattern 4 analysis complete**
|
||||
- Confirmed separation is correct design
|
||||
- No merge needed (A=host integration, B=pure JoinIR)
|
||||
|
||||
**Priority 2: LoopToJoin boxification complete** ✅
|
||||
- Created LoopPatternValidator (224 lines) - structure validation
|
||||
- Created LoopViewBuilder (251 lines) - lowering selection
|
||||
- Reduced loop_to_join.rs: 590 → 294 lines (50% reduction)
|
||||
- Improved maintainability via single responsibility principle
|
||||
|
||||
**Priority 3: CaseA unification**
|
||||
- Deferred (Trait design complete, implementation pending)
|
||||
|
||||
**Impact**:
|
||||
- Effective reduction: ~140 lines (24%)
|
||||
- Build: ✅ SUCCESS
|
||||
- Tests: ✅ ALL PASS (no regression)
|
||||
|
||||
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**作成者**: Claude Code (Sonnet 4.5)
|
||||
**承認**: 要ユーザーレビュー
|
||||
@ -1,285 +0,0 @@
|
||||
# Phase 33 コード重複マップ - 視覚化ガイド
|
||||
|
||||
## 🎯 目的
|
||||
|
||||
Pattern 1-4の共通コードを視覚的に理解し、箱化の対象を明確にする。
|
||||
|
||||
---
|
||||
|
||||
## 📊 重複コード分布図
|
||||
|
||||
```
|
||||
Pattern Lowerer Structure (Before Optimization)
|
||||
================================================
|
||||
|
||||
pattern1_minimal.rs (176行) pattern2_with_break.rs (219行)
|
||||
┌─────────────────────────────┐ ┌─────────────────────────────┐
|
||||
│ ✅ can_lower() │ │ ✅ can_lower() │
|
||||
│ ✅ lower() │ │ ✅ lower() │
|
||||
│ │ │ │
|
||||
│ ⚠️ DUPLICATE INIT (50行) │ │ ⚠️ DUPLICATE INIT (50行) │
|
||||
│ - extract_loop_var │ │ - extract_loop_var │
|
||||
│ - variable_map lookup │ │ - variable_map lookup │
|
||||
│ - trace::varmap() │ │ - trace::varmap() │
|
||||
│ │ │ │
|
||||
│ ⚠️ DUPLICATE CONVERT (30行)│ │ ⚠️ DUPLICATE CONVERT (30行)│
|
||||
│ - convert_join_to_mir │ │ - convert_join_to_mir │
|
||||
│ - trace::joinir_stats() │ │ - trace::joinir_stats() │
|
||||
│ - JoinInlineBoundary │ │ - JoinInlineBoundary │
|
||||
│ - merge_joinir_blocks │ │ - merge_joinir_blocks │
|
||||
│ │ │ │
|
||||
│ ✅ Pattern 1 specific (96行)│ │ ✅ Pattern 2 specific (139行)│
|
||||
└─────────────────────────────┘ └─────────────────────────────┘
|
||||
|
||||
pattern3_with_if_phi.rs (165行) pattern4_with_continue.rs (343行)
|
||||
┌─────────────────────────────┐ ┌─────────────────────────────┐
|
||||
│ ✅ can_lower() │ │ ✅ can_lower() │
|
||||
│ ✅ lower() │ │ ✅ lower() │
|
||||
│ │ │ │
|
||||
│ ⚠️ DUPLICATE INIT (50行) │ │ ⚠️ DUPLICATE INIT (50行) │
|
||||
│ - extract_loop_var │ │ - extract_loop_var │
|
||||
│ - variable_map lookup │ │ - variable_map lookup │
|
||||
│ - trace::varmap() │ │ - trace::varmap() │
|
||||
│ │ │ │
|
||||
│ ⚠️ DUPLICATE CONVERT (30行)│ │ ⚠️ DUPLICATE CONVERT (30行)│
|
||||
│ - convert_join_to_mir │ │ - convert_join_to_mir │
|
||||
│ - trace::joinir_stats() │ │ - trace::joinir_stats() │
|
||||
│ - JoinInlineBoundary │ │ - JoinInlineBoundary │
|
||||
│ - merge_joinir_blocks │ │ - merge_joinir_blocks │
|
||||
│ │ │ │
|
||||
│ ✅ Pattern 3 specific (85行) │ │ ✅ Pattern 4 specific (263行)│
|
||||
│ │ │ + LoopUpdateAnalyzer │
|
||||
│ │ │ + ContinueNormalizer │
|
||||
└─────────────────────────────┘ └─────────────────────────────┘
|
||||
|
||||
⚠️ = 重複コード(削減対象)
|
||||
✅ = パターン固有ロジック(維持)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔥 重複コードの詳細内訳
|
||||
|
||||
### 重複箇所1: 初期化ロジック(4箇所×50行 = 200行)
|
||||
|
||||
**Pattern 1の例**:
|
||||
```rust
|
||||
// src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs:64-79
|
||||
let loop_var_name = self.extract_loop_variable_from_condition(condition)?;
|
||||
let loop_var_id = self
|
||||
.variable_map
|
||||
.get(&loop_var_name)
|
||||
.copied()
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"[cf_loop/pattern1] Loop variable '{}' not found in variable_map",
|
||||
loop_var_name
|
||||
)
|
||||
})?;
|
||||
|
||||
trace::trace().varmap("pattern1_start", &self.variable_map);
|
||||
|
||||
// Pattern 2, 3, 4でも同一コード
|
||||
```
|
||||
|
||||
**重複パターン**:
|
||||
- Pattern 1: `pattern1_minimal.rs:64-79` (16行)
|
||||
- Pattern 2: `pattern2_with_break.rs:56-71` (16行)
|
||||
- Pattern 3: `pattern3_with_if_phi.rs:56-71` (16行)
|
||||
- Pattern 4: `pattern4_with_continue.rs:115-130` (16行)
|
||||
|
||||
**合計**: 4箇所 × 16行 = **64行重複**
|
||||
|
||||
---
|
||||
|
||||
### 重複箇所2: JoinIR変換パイプライン(4箇所×30行 = 120行)
|
||||
|
||||
**Pattern 1の例**:
|
||||
```rust
|
||||
// src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs:100-130
|
||||
let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta)
|
||||
.map_err(|e| format!("[cf_loop/joinir/pattern1] MIR conversion failed: {:?}", e))?;
|
||||
|
||||
trace::trace().joinir_stats(
|
||||
"pattern1",
|
||||
join_module.functions.len(),
|
||||
mir_module.blocks.len(),
|
||||
);
|
||||
|
||||
let boundary = JoinInlineBoundary::new_inputs_only(
|
||||
vec![ValueId(0)],
|
||||
vec![loop_var_id],
|
||||
);
|
||||
|
||||
let _ = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;
|
||||
|
||||
// Pattern 2, 3, 4でも同一フロー(細部のみ異なる)
|
||||
```
|
||||
|
||||
**重複パターン**:
|
||||
- Pattern 1: `pattern1_minimal.rs:100-130` (31行)
|
||||
- Pattern 2: `pattern2_with_break.rs:120-150` (31行)
|
||||
- Pattern 3: `pattern3_with_if_phi.rs:105-135` (31行)
|
||||
- Pattern 4: `pattern4_with_continue.rs:200-230` (31行)
|
||||
|
||||
**合計**: 4箇所 × 31行 = **124行重複**
|
||||
|
||||
---
|
||||
|
||||
## 🎯 箱化後の理想構造
|
||||
|
||||
```
|
||||
After Optimization (CommonPatternInitializer + JoinIRConversionPipeline)
|
||||
=========================================================================
|
||||
|
||||
pattern1_minimal.rs (126行 → 28%削減)
|
||||
┌─────────────────────────────┐
|
||||
│ ✅ can_lower() │
|
||||
│ ✅ lower() │
|
||||
│ │
|
||||
│ 📦 CommonPatternInitializer │ ← 新しい箱!
|
||||
│ .extract_loop_context() │ ← 1行で50行分の処理
|
||||
│ │
|
||||
│ 📦 JoinIRConversionPipeline │ ← 新しい箱!
|
||||
│ .convert_and_merge() │ ← 1行で30行分の処理
|
||||
│ │
|
||||
│ ✅ Pattern 1 specific (96行)│ ← 変更なし
|
||||
└─────────────────────────────┘
|
||||
|
||||
pattern2_with_break.rs (169行 → 23%削減)
|
||||
pattern3_with_if_phi.rs (115行 → 30%削減)
|
||||
pattern4_with_continue.rs (293行 → 15%削減)
|
||||
↑
|
||||
全パターン同様に削減!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 削減インパクト分析
|
||||
|
||||
### Before / After 比較表
|
||||
|
||||
| ファイル | Before | After | 削減行数 | 削減率 |
|
||||
|---------|-------|-------|---------|-------|
|
||||
| pattern1_minimal.rs | 176行 | 126行 | -50行 | 28% |
|
||||
| pattern2_with_break.rs | 219行 | 169行 | -50行 | 23% |
|
||||
| pattern3_with_if_phi.rs | 165行 | 115行 | -50行 | 30% |
|
||||
| pattern4_with_continue.rs | 343行 | 293行 | -50行 | 15% |
|
||||
| **patterns/ 合計** | **1,801行** | **1,601行** | **-200行** | **11%** |
|
||||
|
||||
### 新規追加ファイル
|
||||
|
||||
| ファイル | 行数 | 役割 |
|
||||
|---------|-----|-----|
|
||||
| common_init.rs | 60行 | CommonPatternInitializer実装 |
|
||||
| conversion_pipeline.rs | 50行 | JoinIRConversionPipeline実装 |
|
||||
|
||||
**実質削減**: 200行 - 110行 = **90行削減** + 保守性大幅向上
|
||||
|
||||
---
|
||||
|
||||
## 🔍 コード重複検出コマンド
|
||||
|
||||
```bash
|
||||
# 重複箇所1: Loop variable extraction
|
||||
grep -A 10 "extract_loop_variable_from_condition" \
|
||||
src/mir/builder/control_flow/joinir/patterns/pattern*.rs
|
||||
|
||||
# 重複箇所2: JoinIR conversion
|
||||
grep -A 15 "convert_join_module_to_mir_with_meta" \
|
||||
src/mir/builder/control_flow/joinir/patterns/pattern*.rs
|
||||
|
||||
# 重複箇所3: Merge call
|
||||
grep -A 5 "merge_joinir_mir_blocks" \
|
||||
src/mir/builder/control_flow/joinir/patterns/pattern*.rs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 実装順序(推奨)
|
||||
|
||||
### Phase 1: CommonPatternInitializer (1時間)
|
||||
|
||||
```bash
|
||||
# Step 1: 新規ファイル作成
|
||||
touch src/mir/builder/control_flow/joinir/patterns/common_init.rs
|
||||
|
||||
# Step 2: Pattern 1で動作確認
|
||||
cargo test --release loop_min_while
|
||||
|
||||
# Step 3: Pattern 2, 3, 4に適用
|
||||
# (各10分)
|
||||
|
||||
# Step 4: 全体テスト
|
||||
cargo test --release loop_min_while loop_with_break \
|
||||
loop_with_if_phi_sum loop_with_continue
|
||||
```
|
||||
|
||||
### Phase 2: JoinIRConversionPipeline (1時間)
|
||||
|
||||
```bash
|
||||
# Step 1: 新規ファイル作成
|
||||
touch src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs
|
||||
|
||||
# Step 2: Pattern 1で動作確認
|
||||
cargo test --release loop_min_while
|
||||
|
||||
# Step 3: Pattern 2, 3, 4に適用
|
||||
# (各10分)
|
||||
|
||||
# Step 4: 全体テスト
|
||||
cargo test --release
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 成功基準
|
||||
|
||||
1. **ビルド成功**: 0エラー・0警告
|
||||
2. **テスト全PASS**: Pattern 1-4の既存テスト全て通過
|
||||
3. **SSA-undefゼロ**: MIRレベルのエラーなし
|
||||
4. **削減達成**: patterns/モジュール全体で200行削減
|
||||
5. **保守性向上**: 重複コードゼロ、単一責任の原則適用
|
||||
|
||||
---
|
||||
|
||||
## 🚨 リスク管理
|
||||
|
||||
### 潜在的リスク
|
||||
|
||||
1. **テスト失敗**: 初期化ロジックの微妙な差異を見落とす可能性
|
||||
- **対策**: Pattern毎に個別テスト実行、段階的移行
|
||||
|
||||
2. **デバッグ困難化**: エラー時のスタックトレースが深くなる
|
||||
- **対策**: 適切なエラーメッセージ、pattern_name引数の維持
|
||||
|
||||
3. **将来の拡張性**: Pattern 5/6で異なる初期化が必要になる可能性
|
||||
- **対策**: CommonPatternInitializerを柔軟に設計(オプション引数)
|
||||
|
||||
### 緊急時のロールバック手順
|
||||
|
||||
```bash
|
||||
# Step 1: 変更前のコミットに戻る
|
||||
git revert HEAD
|
||||
|
||||
# Step 2: テスト確認
|
||||
cargo test --release
|
||||
|
||||
# Step 3: 原因分析
|
||||
# → phase33-post-analysis.md の「テスト計画」を参照
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 関連ドキュメント
|
||||
|
||||
- [Phase 33-19 実装完了レポート](phase33-17-implementation-complete.md)
|
||||
- [Phase 33-21 Parameter remapping fix](../../../private/) (未作成)
|
||||
- [JoinIRアーキテクチャ概要](joinir-architecture-overview.md)
|
||||
- [Pattern Router設計](phase33-16-INDEX.md)
|
||||
|
||||
---
|
||||
|
||||
## 📝 変更履歴
|
||||
|
||||
- 2025-12-07: 初版作成(Phase 33-21完了後の調査結果)
|
||||
@ -1,531 +0,0 @@
|
||||
# Phase 33 最適化実装ガイド - Step by Step
|
||||
|
||||
**所要時間**: 2.5時間(CommonPatternInitializer: 1h + JoinIRConversionPipeline: 1h + 検証: 0.5h)
|
||||
**削減見込み**: 351行(patterns/モジュール: 200行 + merge/mod.rs: 31行 + パイプライン: 120行)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Phase 1: CommonPatternInitializer箱化(1時間)
|
||||
|
||||
### Step 1.1: 新規ファイル作成(5分)
|
||||
|
||||
```bash
|
||||
cd /home/tomoaki/git/hakorune-selfhost
|
||||
|
||||
# 新規ファイル作成
|
||||
cat > src/mir/builder/control_flow/joinir/patterns/common_init.rs << 'EOF'
|
||||
//! Phase 33-22: Common Pattern Initializer Box
|
||||
//!
|
||||
//! Extracts duplicate initialization logic from Pattern 1-4 lowerers.
|
||||
//!
|
||||
//! # Purpose
|
||||
//!
|
||||
//! All 4 patterns (Pattern1Minimal, Pattern2WithBreak, Pattern3WithIfPhi, Pattern4WithContinue)
|
||||
//! share the same initialization steps:
|
||||
//! 1. Extract loop variable name from condition
|
||||
//! 2. Look up ValueId in variable_map
|
||||
//! 3. Trace variable map state
|
||||
//!
|
||||
//! This module provides a unified initializer to eliminate 200 lines of duplicate code.
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::ValueId;
|
||||
use super::super::trace;
|
||||
|
||||
/// Loop context extracted from condition and variable_map
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LoopContext {
|
||||
pub loop_var_name: String,
|
||||
pub loop_var_id: ValueId,
|
||||
}
|
||||
|
||||
/// Common Pattern Initializer Box
|
||||
pub struct CommonPatternInitializer;
|
||||
|
||||
impl CommonPatternInitializer {
|
||||
/// Extract loop context from condition
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `builder` - MirBuilder instance (for variable_map access)
|
||||
/// * `condition` - Loop condition AST node
|
||||
/// * `pattern_name` - Pattern identifier for error messages (e.g., "pattern1", "pattern2")
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// LoopContext containing loop_var_name and loop_var_id
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// let ctx = CommonPatternInitializer::extract_loop_context(
|
||||
/// builder,
|
||||
/// condition,
|
||||
/// "pattern1",
|
||||
/// )?;
|
||||
/// // ctx.loop_var_name = "i"
|
||||
/// // ctx.loop_var_id = ValueId(42)
|
||||
/// ```
|
||||
pub fn extract_loop_context(
|
||||
builder: &MirBuilder,
|
||||
condition: &ASTNode,
|
||||
pattern_name: &str,
|
||||
) -> Result<LoopContext, String> {
|
||||
// Step 1: Extract loop variable name from condition (e.g., "i" from "i < 3")
|
||||
let loop_var_name = builder.extract_loop_variable_from_condition(condition)?;
|
||||
|
||||
// Step 2: Look up ValueId in variable_map
|
||||
let loop_var_id = builder
|
||||
.variable_map
|
||||
.get(&loop_var_name)
|
||||
.copied()
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"[cf_loop/{}] Loop variable '{}' not found in variable_map",
|
||||
pattern_name, loop_var_name
|
||||
)
|
||||
})?;
|
||||
|
||||
// Step 3: Trace variable map state for debugging
|
||||
trace::trace().varmap(&format!("{}_start", pattern_name), &builder.variable_map);
|
||||
|
||||
Ok(LoopContext {
|
||||
loop_var_name,
|
||||
loop_var_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# mod.rsに追加
|
||||
echo "pub mod common_init;" >> src/mir/builder/control_flow/joinir/patterns/mod.rs
|
||||
```
|
||||
|
||||
### Step 1.2: Pattern 1に適用(15分)
|
||||
|
||||
**Before (pattern1_minimal.rs:64-79)**:
|
||||
```rust
|
||||
let loop_var_name = self.extract_loop_variable_from_condition(condition)?;
|
||||
let loop_var_id = self
|
||||
.variable_map
|
||||
.get(&loop_var_name)
|
||||
.copied()
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"[cf_loop/pattern1] Loop variable '{}' not found in variable_map",
|
||||
loop_var_name
|
||||
)
|
||||
})?;
|
||||
|
||||
trace::trace().varmap("pattern1_start", &self.variable_map);
|
||||
```
|
||||
|
||||
**After**:
|
||||
```rust
|
||||
use super::common_init::{CommonPatternInitializer, LoopContext};
|
||||
|
||||
// ...
|
||||
|
||||
let LoopContext { loop_var_name, loop_var_id } =
|
||||
CommonPatternInitializer::extract_loop_context(self, condition, "pattern1")?;
|
||||
```
|
||||
|
||||
**編集コマンド**:
|
||||
```bash
|
||||
# Pattern 1のファイルを開く
|
||||
vim src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs
|
||||
|
||||
# 3行目あたりにuse追加:
|
||||
# use super::common_init::{CommonPatternInitializer, LoopContext};
|
||||
|
||||
# 64-79行を削除して1行に置き換え:
|
||||
# let LoopContext { loop_var_name, loop_var_id } =
|
||||
# CommonPatternInitializer::extract_loop_context(self, condition, "pattern1")?;
|
||||
```
|
||||
|
||||
**テスト**:
|
||||
```bash
|
||||
cargo test --release loop_min_while -- --nocapture
|
||||
```
|
||||
|
||||
### Step 1.3: Pattern 2, 3, 4に適用(各10分 = 30分)
|
||||
|
||||
**Pattern 2**:
|
||||
```bash
|
||||
vim src/mir/builder/control_flow/joinir/patterns/pattern2_with_break.rs
|
||||
|
||||
# 56-71行を削除して1行に置き換え
|
||||
# let LoopContext { loop_var_name, loop_var_id } =
|
||||
# CommonPatternInitializer::extract_loop_context(self, condition, "pattern2")?;
|
||||
|
||||
cargo test --release loop_with_break -- --nocapture
|
||||
```
|
||||
|
||||
**Pattern 3**:
|
||||
```bash
|
||||
vim src/mir/builder/control_flow/joinir/patterns/pattern3_with_if_phi.rs
|
||||
|
||||
# 56-71行を削除して1行に置き換え
|
||||
# let LoopContext { loop_var_name, loop_var_id } =
|
||||
# CommonPatternInitializer::extract_loop_context(self, condition, "pattern3")?;
|
||||
|
||||
cargo test --release loop_with_if_phi_sum -- --nocapture
|
||||
```
|
||||
|
||||
**Pattern 4**:
|
||||
```bash
|
||||
vim src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs
|
||||
|
||||
# 115-130行を削除して1行に置き換え
|
||||
# let LoopContext { loop_var_name, loop_var_id } =
|
||||
# CommonPatternInitializer::extract_loop_context(self, condition, "pattern4")?;
|
||||
|
||||
cargo test --release loop_with_continue -- --nocapture
|
||||
```
|
||||
|
||||
### Step 1.4: 全体テスト(10分)
|
||||
|
||||
```bash
|
||||
# ビルド確認
|
||||
cargo build --release
|
||||
|
||||
# 全パターンテスト
|
||||
cargo test --release loop_min_while loop_with_break \
|
||||
loop_with_if_phi_sum loop_with_continue
|
||||
|
||||
# SSA-undefエラーチェック
|
||||
cargo test --release 2>&1 | grep -i "ssa-undef\|undefined"
|
||||
|
||||
# 結果確認
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Phase 1完了!200行削減達成"
|
||||
else
|
||||
echo "❌ Phase 1失敗、ロールバックが必要"
|
||||
fi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Phase 2: JoinIRConversionPipeline箱化(1時間)
|
||||
|
||||
### Step 2.1: 新規ファイル作成(5分)
|
||||
|
||||
```bash
|
||||
cat > src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs << 'EOF'
|
||||
//! Phase 33-22: JoinIR Conversion Pipeline Box
|
||||
//!
|
||||
//! Unified pipeline for JoinModule → MIR conversion + merge.
|
||||
//!
|
||||
//! # Purpose
|
||||
//!
|
||||
//! All 4 patterns share the same conversion flow:
|
||||
//! 1. convert_join_module_to_mir_with_meta()
|
||||
//! 2. trace::joinir_stats()
|
||||
//! 3. merge_joinir_mir_blocks()
|
||||
//!
|
||||
//! This module eliminates 120 lines of duplicate code.
|
||||
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::JoinModule;
|
||||
use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta;
|
||||
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
use super::super::trace;
|
||||
|
||||
/// JoinIR Conversion Pipeline Box
|
||||
pub struct JoinIRConversionPipeline;
|
||||
|
||||
impl JoinIRConversionPipeline {
|
||||
/// Convert JoinModule to MIR and merge into host function
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `builder` - MirBuilder instance (mutable for merge)
|
||||
/// * `join_module` - JoinModule generated by pattern lowerer
|
||||
/// * `boundary` - JoinInlineBoundary for input/output mapping
|
||||
/// * `pattern_name` - Pattern identifier for trace/error messages
|
||||
/// * `debug` - Debug flag for verbose output
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Option<ValueId> from merge operation (loop result value)
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// let boundary = JoinInlineBoundary::new_inputs_only(
|
||||
/// vec![ValueId(0)],
|
||||
/// vec![loop_var_id],
|
||||
/// );
|
||||
///
|
||||
/// let result = JoinIRConversionPipeline::convert_and_merge(
|
||||
/// builder,
|
||||
/// join_module,
|
||||
/// boundary,
|
||||
/// "pattern1",
|
||||
/// debug,
|
||||
/// )?;
|
||||
/// ```
|
||||
pub fn convert_and_merge(
|
||||
builder: &mut MirBuilder,
|
||||
join_module: JoinModule,
|
||||
boundary: JoinInlineBoundary,
|
||||
pattern_name: &str,
|
||||
debug: bool,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
// Step 1: Convert JoinModule to MIR
|
||||
let empty_meta = BTreeMap::new();
|
||||
let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta)
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"[cf_loop/joinir/{}] MIR conversion failed: {:?}",
|
||||
pattern_name, e
|
||||
)
|
||||
})?;
|
||||
|
||||
// Step 2: Trace JoinIR stats for debugging
|
||||
trace::trace().joinir_stats(
|
||||
pattern_name,
|
||||
join_module.functions.len(),
|
||||
mir_module.blocks.len(),
|
||||
);
|
||||
|
||||
// Step 3: Merge MIR blocks into host function
|
||||
builder.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# mod.rsに追加
|
||||
echo "pub mod conversion_pipeline;" >> src/mir/builder/control_flow/joinir/patterns/mod.rs
|
||||
```
|
||||
|
||||
### Step 2.2: Pattern 1-4に適用(各10分 = 40分)
|
||||
|
||||
**Pattern 1の例**:
|
||||
```bash
|
||||
vim src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs
|
||||
|
||||
# use追加:
|
||||
# use super::conversion_pipeline::JoinIRConversionPipeline;
|
||||
|
||||
# 100-130行を削除して以下に置き換え:
|
||||
# let boundary = JoinInlineBoundary::new_inputs_only(
|
||||
# vec![ValueId(0)],
|
||||
# vec![loop_var_id],
|
||||
# );
|
||||
#
|
||||
# let _ = JoinIRConversionPipeline::convert_and_merge(
|
||||
# self,
|
||||
# join_module,
|
||||
# boundary,
|
||||
# "pattern1",
|
||||
# debug,
|
||||
# )?;
|
||||
|
||||
cargo test --release loop_min_while
|
||||
```
|
||||
|
||||
**Pattern 2, 3, 4も同様に適用**(各10分)
|
||||
|
||||
### Step 2.3: 全体テスト(15分)
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
cargo test --release
|
||||
|
||||
# 削減確認
|
||||
wc -l src/mir/builder/control_flow/joinir/patterns/pattern*.rs
|
||||
# Pattern 1: 176 → 126行
|
||||
# Pattern 2: 219 → 169行
|
||||
# Pattern 3: 165 → 115行
|
||||
# Pattern 4: 343 → 293行
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Phase 3: Legacy Fallback削除検証(30分)
|
||||
|
||||
### Step 3.1: Fallbackコメントアウト(5分)
|
||||
|
||||
```bash
|
||||
vim src/mir/builder/control_flow/joinir/merge/mod.rs
|
||||
|
||||
# 277-307行をコメントアウト:
|
||||
# /*
|
||||
# if function_params.get(main_func_name).is_none() && ...
|
||||
# ...
|
||||
# }
|
||||
# */
|
||||
```
|
||||
|
||||
### Step 3.2: テスト実行(20分)
|
||||
|
||||
```bash
|
||||
cargo test --release loop_min_while loop_with_break \
|
||||
loop_with_if_phi_sum loop_with_continue 2>&1 | tee /tmp/fallback-test.log
|
||||
|
||||
# エラー確認
|
||||
grep -i "error\|failed" /tmp/fallback-test.log
|
||||
```
|
||||
|
||||
### Step 3.3: 判定(5分)
|
||||
|
||||
**テスト全てPASS → Fallback削除OK**:
|
||||
```bash
|
||||
# 277-307行を完全削除
|
||||
vim src/mir/builder/control_flow/joinir/merge/mod.rs
|
||||
|
||||
# コミット
|
||||
git add -A
|
||||
git commit -m "feat(joinir): Phase 33-22 Remove legacy fallback (31 lines)"
|
||||
```
|
||||
|
||||
**テスト失敗 → Fallback必要**:
|
||||
```bash
|
||||
# コメントアウトを戻す
|
||||
git checkout src/mir/builder/control_flow/joinir/merge/mod.rs
|
||||
|
||||
# コメント追加(なぜ必要か記録)
|
||||
vim src/mir/builder/control_flow/joinir/merge/mod.rs
|
||||
# 277行目あたりに:
|
||||
# // Phase 33-22検証済み: このFallbackは〇〇のケースで必要
|
||||
# // 削除するとtest_XXXが失敗する
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 完了チェックリスト
|
||||
|
||||
### Phase 1: CommonPatternInitializer
|
||||
|
||||
- [ ] common_init.rs作成済み(60行)
|
||||
- [ ] Pattern 1適用済み(176 → 126行)
|
||||
- [ ] Pattern 2適用済み(219 → 169行)
|
||||
- [ ] Pattern 3適用済み(165 → 115行)
|
||||
- [ ] Pattern 4適用済み(343 → 293行)
|
||||
- [ ] テスト全PASS
|
||||
- [ ] ビルド警告ゼロ
|
||||
|
||||
### Phase 2: JoinIRConversionPipeline
|
||||
|
||||
- [ ] conversion_pipeline.rs作成済み(50行)
|
||||
- [ ] Pattern 1適用済み(さらに30行削減)
|
||||
- [ ] Pattern 2適用済み(さらに30行削減)
|
||||
- [ ] Pattern 3適用済み(さらに30行削減)
|
||||
- [ ] Pattern 4適用済み(さらに30行削減)
|
||||
- [ ] テスト全PASS
|
||||
- [ ] ビルド警告ゼロ
|
||||
|
||||
### Phase 3: Legacy Fallback削除
|
||||
|
||||
- [ ] Fallbackコメントアウト済み
|
||||
- [ ] テスト実行済み
|
||||
- [ ] 判定完了(削除 or 保持)
|
||||
- [ ] ドキュメント更新済み
|
||||
|
||||
---
|
||||
|
||||
## 🚨 トラブルシューティング
|
||||
|
||||
### Q1: テストが失敗する
|
||||
|
||||
**症状**:
|
||||
```
|
||||
test loop_min_while ... FAILED
|
||||
SSA-undef: ValueId(42) not found
|
||||
```
|
||||
|
||||
**原因**: LoopContext.loop_var_idのマッピングミス
|
||||
|
||||
**対処**:
|
||||
```bash
|
||||
# デバッグ出力有効化
|
||||
NYASH_TRACE_VARMAP=1 cargo test --release loop_min_while -- --nocapture
|
||||
|
||||
# variable_mapの状態確認
|
||||
grep "varmap.*pattern1" の出力を確認
|
||||
```
|
||||
|
||||
### Q2: ビルドエラー
|
||||
|
||||
**症状**:
|
||||
```
|
||||
error[E0433]: failed to resolve: use of undeclared type `LoopContext`
|
||||
```
|
||||
|
||||
**原因**: use文の追加忘れ
|
||||
|
||||
**対処**:
|
||||
```bash
|
||||
# 各patternファイルに追加
|
||||
use super::common_init::{CommonPatternInitializer, LoopContext};
|
||||
use super::conversion_pipeline::JoinIRConversionPipeline;
|
||||
```
|
||||
|
||||
### Q3: Fallback削除でテスト失敗
|
||||
|
||||
**症状**:
|
||||
```
|
||||
test loop_XXX ... FAILED
|
||||
ValueId(0) not found in remapper
|
||||
```
|
||||
|
||||
**原因**: 一部パターンでFallbackが必要
|
||||
|
||||
**対処**:
|
||||
```bash
|
||||
# Fallbackを保持
|
||||
git checkout src/mir/builder/control_flow/joinir/merge/mod.rs
|
||||
|
||||
# コメント追加
|
||||
# このFallbackはPattern Xで必要(理由: ...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 参考ドキュメント
|
||||
|
||||
- [Phase 33-22 分析レポート](phase33-post-analysis.md)
|
||||
- [コード重複マップ](phase33-duplication-map.md)
|
||||
- [JoinIRアーキテクチャ](joinir-architecture-overview.md)
|
||||
|
||||
---
|
||||
|
||||
## 📝 完了後のコミット
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "feat(joinir): Phase 33-22 CommonPatternInitializer + JoinIRConversionPipeline
|
||||
|
||||
- CommonPatternInitializer: Pattern 1-4の初期化ロジック統一化(200行削減)
|
||||
- JoinIRConversionPipeline: JoinIR変換フロー統一化(120行削減)
|
||||
- Legacy Fallback削除: merge/mod.rs 277-307行削除(31行削減)
|
||||
|
||||
Total: 351行削減
|
||||
|
||||
Phase 33-22完了!"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**最終確認**:
|
||||
```bash
|
||||
# ビルド成功
|
||||
cargo build --release
|
||||
|
||||
# テスト全PASS
|
||||
cargo test --release
|
||||
|
||||
# 削減確認
|
||||
git diff --stat HEAD~1
|
||||
# patterns/ モジュール: -200行
|
||||
# merge/mod.rs: -31行
|
||||
# conversion_pipeline.rs: +50行
|
||||
# common_init.rs: +60行
|
||||
# 実質削減: -121行
|
||||
```
|
||||
|
||||
✅ Phase 33-22最適化完了!
|
||||
@ -1,372 +0,0 @@
|
||||
# Phase 33-19/33-21完了時点での箱化・モジュール化・レガシー削除・共通化の機会調査
|
||||
|
||||
**調査日**: 2025-12-07
|
||||
**調査範囲**: Phase 33-21 (Parameter remapping fix) 完了後
|
||||
**調査目的**: 箱化モジュール化、レガシー削除、共通化の改善機会を発見
|
||||
|
||||
---
|
||||
|
||||
## エグゼクティブサマリー
|
||||
|
||||
### 🎯 主要発見
|
||||
|
||||
1. **高優先度**: Pattern 1-4で共通する初期化フローの重複(4箇所×約50行 = **200行削減可能**)
|
||||
2. **中優先度**: Phase 33-16時代のFallbackロジック(merge/mod.rs:277-307)の必要性検証
|
||||
3. **低優先度**: condition_to_joinirとBoolExprLowererの役割分担は適切(削除不要)
|
||||
|
||||
### 📊 コード規模
|
||||
|
||||
| モジュール | ファイル数 | 総行数 | 備考 |
|
||||
|-----------|----------|-------|-----|
|
||||
| patterns/ | 8 | 1,801行 | Pattern 1-4 + 共通モジュール |
|
||||
| merge/ | 9 | 1,850行 | JoinIR→MIR変換 |
|
||||
| lowering/ | 36 | 10,620行 | JoinIR生成・解析 |
|
||||
|
||||
---
|
||||
|
||||
## 推奨改善案
|
||||
|
||||
### 🔥 高優先度(簡単+インパクト大)
|
||||
|
||||
#### 1. **CommonPatternInitializer箱の作成**
|
||||
|
||||
**問題**: 全パターン(Pattern 1-4)で同じ初期化コードが重複
|
||||
|
||||
**重複コード例**:
|
||||
```rust
|
||||
// Pattern 1, 2, 3, 4 全てで同じコード
|
||||
let loop_var_name = self.extract_loop_variable_from_condition(condition)?;
|
||||
let loop_var_id = self
|
||||
.variable_map
|
||||
.get(&loop_var_name)
|
||||
.copied()
|
||||
.ok_or_else(|| {
|
||||
format!("[cf_loop/patternN] Loop variable '{}' not found", loop_var_name)
|
||||
})?;
|
||||
|
||||
trace::trace().varmap("patternN_start", &self.variable_map);
|
||||
```
|
||||
|
||||
**提案実装**:
|
||||
```rust
|
||||
// src/mir/builder/control_flow/joinir/patterns/common_init.rs
|
||||
pub struct PatternInitializer;
|
||||
|
||||
impl PatternInitializer {
|
||||
/// 全パターン共通の初期化処理
|
||||
pub fn extract_loop_context(
|
||||
builder: &MirBuilder,
|
||||
condition: &ASTNode,
|
||||
pattern_name: &str,
|
||||
) -> Result<LoopContext, String> {
|
||||
let loop_var_name = builder.extract_loop_variable_from_condition(condition)?;
|
||||
let loop_var_id = builder.variable_map
|
||||
.get(&loop_var_name)
|
||||
.copied()
|
||||
.ok_or_else(|| {
|
||||
format!("[cf_loop/{}] Loop variable '{}' not found",
|
||||
pattern_name, loop_var_name)
|
||||
})?;
|
||||
|
||||
trace::trace().varmap(&format!("{}_start", pattern_name), &builder.variable_map);
|
||||
|
||||
Ok(LoopContext {
|
||||
loop_var_name,
|
||||
loop_var_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**削減見込み**:
|
||||
- Pattern 1-4各50行 × 4パターン = **200行削減**
|
||||
- pattern1_minimal.rs: 176 → 126行(28%削減)
|
||||
- pattern2_with_break.rs: 219 → 169行(23%削減)
|
||||
- pattern3_with_if_phi.rs: 165 → 115行(30%削減)
|
||||
- pattern4_with_continue.rs: 343 → 293行(15%削減)
|
||||
|
||||
**実装工数**: < 1時間
|
||||
|
||||
**テスト計画**:
|
||||
```bash
|
||||
# 全パターンテスト実行
|
||||
cargo test --release loop_min_while # Pattern 1
|
||||
cargo test --release loop_with_break # Pattern 2
|
||||
cargo test --release loop_with_if_phi_sum # Pattern 3
|
||||
cargo test --release loop_with_continue # Pattern 4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 2. **JoinIR変換パイプライン箱化**
|
||||
|
||||
**問題**: `convert_join_module_to_mir_with_meta` + `merge_joinir_mir_blocks` の組み合わせが4箇所で重複
|
||||
|
||||
**重複パターン**:
|
||||
```rust
|
||||
// Pattern 1, 2, 3, 4 全てで同じフロー
|
||||
let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta)?;
|
||||
trace::trace().joinir_stats("patternN", join_module.functions.len(), mir_module.blocks.len());
|
||||
let boundary = JoinInlineBoundary::new_inputs_only(...);
|
||||
let result = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;
|
||||
```
|
||||
|
||||
**提案実装**:
|
||||
```rust
|
||||
// src/mir/builder/control_flow/joinir/patterns/conversion_pipeline.rs
|
||||
pub struct JoinIRConversionPipeline;
|
||||
|
||||
impl JoinIRConversionPipeline {
|
||||
/// JoinModule → MIR変換 + マージの統一パイプライン
|
||||
pub fn convert_and_merge(
|
||||
builder: &mut MirBuilder,
|
||||
join_module: JoinModule,
|
||||
boundary: JoinInlineBoundary,
|
||||
pattern_name: &str,
|
||||
debug: bool,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
let empty_meta = BTreeMap::new();
|
||||
let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta)
|
||||
.map_err(|e| format!("[cf_loop/joinir/{}] MIR conversion failed: {:?}", pattern_name, e))?;
|
||||
|
||||
trace::trace().joinir_stats(
|
||||
pattern_name,
|
||||
join_module.functions.len(),
|
||||
mir_module.blocks.len(),
|
||||
);
|
||||
|
||||
builder.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**削減見込み**:
|
||||
- Pattern 1-4各30行 × 4パターン = **120行削減**
|
||||
|
||||
**実装工数**: < 1時間
|
||||
|
||||
---
|
||||
|
||||
### ⚠️ 中優先度(検証必要)
|
||||
|
||||
#### 3. **Legacy Fallback削除(merge/mod.rs:277-307)**
|
||||
|
||||
**場所**: `src/mir/builder/control_flow/joinir/merge/mod.rs:277-307`
|
||||
|
||||
**問題のコード**:
|
||||
```rust
|
||||
if function_params.get(main_func_name).is_none() && function_params.get(loop_step_func_name).is_none() {
|
||||
// Fallback: Use old behavior (ValueId(0), ValueId(1), ...)
|
||||
// This handles patterns that don't have loop_step function
|
||||
if let Some(phi_dst) = phi_info.get_carrier_phi(loop_var_name) {
|
||||
remapper.set_value(ValueId(0), phi_dst);
|
||||
if debug {
|
||||
eprintln!(
|
||||
"[cf_loop/joinir] Phase 33-16 fallback: Override remap ValueId(0) → {:?} (PHI dst)",
|
||||
phi_dst
|
||||
);
|
||||
}
|
||||
}
|
||||
for (idx, (carrier_name, entry)) in phi_info.carrier_phis.iter().enumerate() {
|
||||
if carrier_name == loop_var_name {
|
||||
continue;
|
||||
}
|
||||
let join_value_id = ValueId(idx as u32);
|
||||
remapper.set_value(join_value_id, entry.phi_dst);
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**検証方法**:
|
||||
```bash
|
||||
# Step 1: Fallbackコードをコメントアウト
|
||||
# Step 2: 全パターンテスト実行
|
||||
cargo test --release loop_min_while loop_with_break loop_with_if_phi_sum loop_with_continue
|
||||
|
||||
# Step 3: もしテスト全てPASSなら削除してOK
|
||||
```
|
||||
|
||||
**判定基準**:
|
||||
- ✅ テスト全てPASS → **31行削減** + コード複雑度低減
|
||||
- ❌ テスト失敗 → Fallback必要(理由をコメントに追記)
|
||||
|
||||
**実装工数**: < 30分(検証のみ)
|
||||
|
||||
---
|
||||
|
||||
### 📘 低優先度(現状維持推奨)
|
||||
|
||||
#### 4. **condition_to_joinirとBoolExprLowererの役割分担**
|
||||
|
||||
**調査結果**: **削除不要・重複なし**
|
||||
|
||||
**理由**:
|
||||
1. **明確な責務分離**:
|
||||
- `BoolExprLowerer`: AST → MIR(通常の制御フロー用)
|
||||
- `condition_to_joinir`: AST → JoinIR(ループパターン用)
|
||||
|
||||
2. **出力空間が異なる**:
|
||||
- BoolExprLowerer: MIR命令(builder経由で状態変更)
|
||||
- condition_to_joinir: JoinIR命令(純粋関数変換)
|
||||
|
||||
3. **使用箇所**:
|
||||
- condition_to_joinir: 21箇所(loop lowering専用)
|
||||
- BoolExprLowerer: 14箇所(if/while等の通常制御フロー)
|
||||
|
||||
**コメント改善推奨**:
|
||||
```rust
|
||||
// src/mir/join_ir/lowering/condition_to_joinir.rs:1
|
||||
//! Phase 169: JoinIR Condition Lowering Helper
|
||||
//!
|
||||
//! **Design Decision (Phase 33-21確認済み)**:
|
||||
//! このモジュールはBoolExprLowererと**意図的に別実装**です。
|
||||
//! - BoolExprLowerer: MIR空間(状態変更あり)
|
||||
//! - condition_to_joinir: JoinIR空間(純粋関数)
|
||||
//!
|
||||
//! 統合しないでください。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🔍 その他の発見
|
||||
|
||||
#### 5. **LoopUpdateAnalyzer + ContinueBranchNormalizer の統合可能性**
|
||||
|
||||
**現状**:
|
||||
- `LoopUpdateAnalyzer`: Pattern 4のみ使用(1箇所)
|
||||
- `ContinueBranchNormalizer`: Pattern 4のみ使用(1箇所)
|
||||
|
||||
**統合提案**(低優先度):
|
||||
```rust
|
||||
// src/mir/join_ir/lowering/pattern4_pipeline.rs
|
||||
pub struct Pattern4Pipeline;
|
||||
|
||||
impl Pattern4Pipeline {
|
||||
/// Pattern 4専用のAST正規化→解析パイプライン
|
||||
pub fn prepare_loop_body(
|
||||
body: &[ASTNode],
|
||||
carriers: &[CarrierVar],
|
||||
) -> (Vec<ASTNode>, HashMap<String, UpdateExpr>) {
|
||||
// Step 1: Continue branch正規化
|
||||
let normalized_body = ContinueBranchNormalizer::normalize_loop_body(body);
|
||||
|
||||
// Step 2: Carrier update解析
|
||||
let carrier_updates = LoopUpdateAnalyzer::analyze_carrier_updates(&normalized_body, carriers);
|
||||
|
||||
(normalized_body, carrier_updates)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**削減見込み**: コード削減なし、可読性向上のみ
|
||||
**実装工数**: < 30分
|
||||
**優先度**: 低(Pattern 5/6実装時に再検討)
|
||||
|
||||
---
|
||||
|
||||
#### 6. **未使用警告の整理**
|
||||
|
||||
**発見箇所**:
|
||||
```
|
||||
warning: methods `detect_from_features`, `detect_with_carrier_name` are never used
|
||||
--> src/mir/loop_pattern_detection.rs:106:12
|
||||
|
||||
warning: methods `exit_analysis` and `has_progress_carrier` are never used
|
||||
--> src/mir/join_ir/lowering/loop_scope_shape/structural.rs:84:12
|
||||
```
|
||||
|
||||
**対処方針**:
|
||||
- Phase 170以降で使用予定 → `#[allow(dead_code)]` 追加
|
||||
- 本当に不要 → 削除(Phase 195確認推奨)
|
||||
|
||||
---
|
||||
|
||||
## 実装優先順位
|
||||
|
||||
### 即座に実装すべき(< 2時間)
|
||||
|
||||
1. ✅ **CommonPatternInitializer箱化** (1時間)
|
||||
- 削減: 200行
|
||||
- 効果: Pattern 1-4の保守性向上
|
||||
|
||||
2. ✅ **JoinIRConversionPipeline箱化** (1時間)
|
||||
- 削減: 120行
|
||||
- 効果: 変換フロー統一化
|
||||
|
||||
### 検証後に判断(< 1時間)
|
||||
|
||||
3. ⚠️ **Legacy Fallback削除検証** (30分)
|
||||
- 削減: 31行(削除可能な場合)
|
||||
- 条件: テスト全てPASS
|
||||
|
||||
### Phase 195以降で再検討
|
||||
|
||||
4. 📘 **Pattern4Pipeline統合** (30分)
|
||||
- 削減: なし
|
||||
- 効果: 可読性向上
|
||||
|
||||
5. 📘 **未使用警告整理** (15分)
|
||||
- 削減: 不明
|
||||
- 効果: コンパイル警告削減
|
||||
|
||||
---
|
||||
|
||||
## テスト計画
|
||||
|
||||
### 退行検証(必須)
|
||||
|
||||
```bash
|
||||
# Pattern 1-4 全体テスト
|
||||
cargo test --release loop_min_while # Pattern 1
|
||||
cargo test --release loop_with_break # Pattern 2
|
||||
cargo test --release loop_with_if_phi_sum # Pattern 3
|
||||
cargo test --release loop_with_continue # Pattern 4
|
||||
|
||||
# SSA-undefエラーチェック
|
||||
cargo test --release 2>&1 | grep -i "ssa-undef\|undefined"
|
||||
|
||||
# WARNING ログチェック
|
||||
cargo build --release 2>&1 | grep -i "warning.*unused"
|
||||
```
|
||||
|
||||
### 新規エラー検出
|
||||
|
||||
```bash
|
||||
# Phase 33-21完了時点でのベースライン取得
|
||||
cargo test --release 2>&1 | tee /tmp/phase33-21-baseline.log
|
||||
|
||||
# 改善後の差分確認
|
||||
cargo test --release 2>&1 | diff /tmp/phase33-21-baseline.log -
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 期待される効果
|
||||
|
||||
### 削減見込み
|
||||
|
||||
| 改善項目 | 削減行数 | 保守性向上 | 実装工数 |
|
||||
|---------|---------|-----------|---------|
|
||||
| CommonPatternInitializer | 200行 | ★★★★★ | 1時間 |
|
||||
| JoinIRConversionPipeline | 120行 | ★★★★☆ | 1時間 |
|
||||
| Legacy Fallback削除 | 31行 | ★★★☆☆ | 30分 |
|
||||
| **合計** | **351行** | - | **2.5時間** |
|
||||
|
||||
### コード品質向上
|
||||
|
||||
1. **DRY原則適用**: Pattern 1-4の重複コード完全削除
|
||||
2. **単一責任**: 初期化ロジックが1箇所に集約
|
||||
3. **テスト容易性**: CommonPatternInitializerを独立してテスト可能
|
||||
4. **拡張性**: Pattern 5/6追加時も同じ箱を使用可能
|
||||
|
||||
---
|
||||
|
||||
## 結論
|
||||
|
||||
Phase 33-19/33-21完了時点で、**351行削減可能な改善機会**を発見。
|
||||
特に「CommonPatternInitializer箱化」は高効果・低コストで即座に実装推奨。
|
||||
|
||||
Legacy Fallback削除は**テスト検証必須**(削除して問題ないか確認)。
|
||||
|
||||
condition_to_joinirとBoolExprLowererの統合は**不要**(設計上正しい分離)。
|
||||
Reference in New Issue
Block a user