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:
nyash-codex
2025-12-11 00:33:04 +09:00
parent 448bf3d8c5
commit a7dbc15878
116 changed files with 401 additions and 18 deletions

View File

@ -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 なので影響最小

View File

@ -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**

View File

@ -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.

View File

@ -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 NotePhase 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

View File

@ -1,110 +0,0 @@
# Phase 170D: LoopConditionScopeBox 設計メモ
日付: 20251207
状態: 設計完了(実装は今後の Phase で)
## 背景
Pattern2/Pattern4Loop 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 条件 ASTPattern2/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 170Dimpl など)で行う。

View File

@ -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

View File

@ -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 時間
**難易度**: 低(調査 + ドキュメント化)

View File

@ -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`

View File

@ -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`

View File

@ -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 を構築している部分」を特定
- どの JSONMIR/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-2hako_check JSON パーサ完全置き換え)
**予定工数**: 2-3 時間
**実工数**: 2 時間実装完了、using 制限発見)
**難易度**: 中(統合 + テスト確認)
**状態**: 実装完了using 制限により Phase 173 で動作確認予定)

View File

@ -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`

View File

@ -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

View File

@ -1,493 +0,0 @@
# Phase 171: JsonParserBox 実装 & hako_check への導入
## 0. ゴール
**Phase 170 で決めた API 草案どおりに .hako 純正 JSON パーサ BoxJsonParserBoxを実装する。**
目的:
- 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()`
- **型定義**: JsonValueBoxunion 相当), 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. **内部型の定義**
- JsonValueBoxkind で型判定)
- JsonObjectBoxkey-value map
- JsonArrayBoxvalue 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() / serializationPhase 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**: 171JsonParserBox 実装 & hako_check 導入)
**予定工数**: 3-4 時間
**難易度**: 中JSON パーサ実装 + .hako での Box 設計)
**期待削減**: hako_check 行数 60%、コード共通化 100%

View File

@ -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`

View File

@ -1,294 +0,0 @@
# Phase 172: JsonParserBox 再利用拡大 - 実装結果
**実装日**: 2025-12-04
**Phase**: 172JsonParserBox 再利用拡大)
---
## 実装サマリー
### ✅ 完了した内容
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+ 提案あり

View File

@ -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() / serializationPhase 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**: 172JsonParserBox 再利用拡大)
**予定工数**: 2-3 時間
**難易度**: 低-中(既存実装の適用 + 薄いラッパー追加)
**期待効果**: JSON 処理 SSOT 確立、selfhost depth-2 基盤完成

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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. 変更を小さくする
**重要な制約**:
- 既存の usingnamespace/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-2using resolver + MIR lowering
**予定工数**: 4-6 時間
**難易度**: 高名前解決・MIR lowering の統合)
**前提**: Phase 173 前半Task 1-3完了

View File

@ -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 パイプラインをそのまま活用

View File

@ -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 成果記録

View File

@ -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 問題の解決

View File

@ -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 の usingPhase 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/HC020PASS
### 🔄 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 3JsonParserBox バグ修正)または Task 4using 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**: 173using + 静的 Box メソッド解決)
**進捗**: Task 1-2 完了25%/ Task 3-8 残り75%

View File

@ -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 の usingPhase 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 3JsonParserBox バグ修正)先行
**理由**: 動作確認を可能にするための前提条件
**作業**:
1. `tools/hako_shared/json_parser.hako``_parse_number()` 確認
2. 無限ループの原因特定
3. 修正実装
4. 簡単な JSON (`{"x":1}`) で動作確認
#### Option B: Task 4using 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**: 173using + 静的 Box メソッド解決)
**進捗**: Task 1-2 完了25%
**次タスク**: Task 3JsonParserBox バグ修正)または Task 4using resolver 修正)

View File

@ -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 3JsonParserBox バグ修正)または Task 2仕様固定

View File

@ -1,387 +0,0 @@
# Phase 173: using + 静的 Box メソッド解決の整備
## 0. ゴール
**using した静的 Box をライブラリとして正しく呼び出せるようにする。**
目的:
- `.hako``using` した静的 BoxJsonParserBox 等)を 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**: 173using + 静的 Box メソッド解決)
**予定工数**: 4-6 時間
**難易度**: 高名前解決・MIR lowering の修正)
**Rust VM 変更**: なし(.hako/using 側のみ)

View File

@ -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!" ✨

View File

@ -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 分類**: Pattern2break 付き、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複雑ループ

View File

@ -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**(将来):
- Pattern4continue 付き)対応
- **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`: LoopBodyLocalsubstring でループ内定義)
- `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 等)の基盤が確立される

View File

@ -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)

View File

@ -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

View File

@ -1,165 +0,0 @@
# Phase 177: Carrier Evolution - min から Production へ
## 視覚的比較: ループ構造の進化
### Phase 174: min1-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: min22-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 Case2-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 Handling2-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 Production2-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 数を固定して制御フローを段階的に複雑化」が正解**

View File

@ -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 mergePhase 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」の動作確認を優先する。

View File

@ -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.

View File

@ -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`

View File

@ -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

View File

@ -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)
- アクション: continuewhitespace 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**: 182CharComparison 汎用化の準備・設計)
**ステータス**: ドキュメント・計画のみ(コード変更なし)
**次フェーズ**: Phase 183実装・リネーム

View File

@ -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`

View File

@ -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

View File

@ -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

View File

@ -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 / CarrierUpdateEmitterPhase 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"

View File

@ -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

View File

@ -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).

View File

@ -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

View File

@ -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

View File

@ -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)]`

View File

@ -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ループパラメータ・条件変数が最優先
- LoopBodyLocalEnvbody-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`(退行確認)

View File

@ -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 CallPhase 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-Fastbase は constant のみ許可)
**Type C: LHS 側に Method Call対象外**:
```nyash
v = obj.getValue() * 10 + x
```
→ Fail-FastLHS は 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
- ループ本体 ASTtemp 変数を挿入する位置情報)
**出力**:
- `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)

View File

@ -1,108 +0,0 @@
# Phase 192: Loop Pattern Structure-Based Detection
**Status**: Completed 2025-12-06
**Scope**: JoinIR loop patterns 14 (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.

View File

@ -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 195Pattern 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実戦投入優先を推奨**

View File

@ -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.

View File

@ -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

View File

@ -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 195Pattern 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 への明確な道筋を示した。

View File

@ -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.

View File

@ -1,89 +0,0 @@
# Phase 194: JoinLoopTrace / Debug Integration
**Status**: In Progresstrace.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 セクションにまとめる:
- varmapNYASH_TRACE_VARMAP
- joinir-debugNYASH_JOINIR_DEBUG
- phi-debugNYASH_OPTION_C_DEBUG
- mainline-debugNYASH_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 で安定しているので、それを壊さない形でログだけを寄せることが目的。

View File

@ -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 3If-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`
### 現状の ExitLinePhase 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 で BoolFlagescapedが動作
- [ ] 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
**目的**: P3If-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. **箱理論の実践**:
- 設計書に基づく実装
- 単一責任の原則維持
- ドキュメント駆動開発

View File

@ -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 3If-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`: StringAppendelse のみ更新、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`: NumberAccumulationthen のみ更新)
-`count`: CounterLikethen のみ更新)
-`i`: ループ外で統一更新
**制約**:
- `array[i]` の配列アクセスは ConditionEnv で解決(`array` は outer local → Phase 200+
- **この Phase では `i` のような既存パラメータで代替してテスト**
### Phase 195 での選定
**優先順位 1**: 候補 1_parse_string 簡易版)
- 理由: JsonParser の実戦コード、flag + buffer の2キャリア
- 簡略化: `ch = "x"` 定数で配列アクセス回避
**優先順位 2**: 候補 2if-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 lowererPhase 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 191body-local initと Phase 193MethodCallの組み合わせ
- → 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)
**目的**: P3If-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_stringflag+buffer、if-sumsum+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 を拡張
- YAGNIYou 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 Selectif-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 として分離 ✅

View File

@ -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回 remapline 304
- 手動 block remap で再度 remapline 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 を remapline 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 ValueId21, 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 を二重 remap1回目: 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 に退行なし

View File

@ -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 197JsonParser 残り適用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**: 93multi-carrier P3
-**loop_if_phi.hako**: sum=9single-carrier P3
-**loop_min_while.hako**: 0,1,2Pattern 1
-**joinir_min_loop.hako**: RC:0Pattern 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

View File

@ -1,215 +0,0 @@
# Phase 196: Loop Continue Multi-Carrier Support
**Phase**: 196
**Status**: Infrastructure Completemulti-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インフラは動くが更新式は暫定
```
**ErrorPhase 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

View File

@ -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 | 複数 MethodCallPhase 195+|
| `_parse_object` | - | ⚠️ Deferred | 複数 MethodCallPhase 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 セクション)

View File

@ -1,143 +0,0 @@
# Phase 197: Pattern 4 Update Semantics
**Status**: In Progress197-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`

View File

@ -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)

View File

@ -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.

View File

@ -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生成されていない
出力: MIRPHI命令を含む
経路選択:
Route A従来: AST → if_phi.rs → MIRPHI
Route B新規: AST → JoinIR lowering → JoinIR Select → reverse lowering → MIRPHI
```
**Phase 33-10が**扱うべきでない**ケース**:
```
入力: MIR既にPHI命令が存在
出力: ???(何に変換する意味がある?)
```
---
## 6. Phase 33-10実装方針
### 6.1 推奨方針: Option APHI命令を変換対象外とする
**実装内容**:
```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 BPHI → Select/IfMerge変換の問題点
**実装した場合の問題**:
1. **条件式の復元困難**:
```mir
bb0: br %5, label bb3, label bb4 ← %5はここで消費
...
bb5: %12 = phi [%8, bb3], [%11, bb4] ← %5にアクセスできない
```
2. **無意味な往復変換**:
- MIRPHI → JoinIRSelect → MIRPHI
- 何も変わらない
3. **Phase 33の意図に反する**:
- 目的: if_phi.rsを削除して、JoinIR経由でPHI生成
- 実際: 既にあるPHIを変換意味なし
### 6.3 Option C新しいパターン定義の不要性
**「PHI-based pattern」を定義する必要はない**:
理由:
1. PHIがある = 既にMIR Builderif_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 ← 直接returnmerge blockなし
bb4 (else):
%11 = const 200
ret %11 ← 直接returnmerge 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 PatternJoinIR変換すべきケース
**仮想的な構造**(実際には生成されない):
```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 PatternPhase 33-9.2
**現状**: ✅ 完全動作
- Const/Copy許容で100%成功
- PHI命令なし → JoinIR変換が有意義
### 8.2 Local PatternPhase 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 APHI命令を対象外とする**
```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. ✅ 実用MIRPHI命令ありは正しくフォールバック
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 MIRPhase 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-11IfMerge実装、またはPhase 34へ移行

View File

@ -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
```

View File

@ -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!

View File

@ -1,3 +1,6 @@
Status: Active
Scope: Phase 33-16Loop Header PHI SSOTに関する現役目次。歴史詳細は archive 側を参照。
# Phase 33-16: Loop Header PHI SSOT - Documentation Index
**Last Updated**: 2025-12-07

View File

@ -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.

View File

@ -1,245 +0,0 @@
# Phase 3316: Loop Header PHI as Exit SSOT — Design
日付: 20251207
状態: 設計フェーズ完了(実装前の設計メモ)
このフェーズでは、JoinIR → MIR マージ時の「ループ出口値の扱い」を、
- expr 結果ライン(`loop` を式として使うケース)
- carrier ライン(`start/end/sum` など状態更新だけを行うケース)
で構造的に整理し直すよ。
目的は:
1. SSAundef を根本的に防ぐ(継続関数パラメータを PHI 入力に使わない)
2. ループヘッダ PHI を「ループ変数の真の現在値」の SSOT にする
3. ExitLineExitMeta/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 が未定義になり、SSAundef が発生していた
- Phase 3315 では:
- `value_collector` から JoinIR パラメータを除外
- `instruction_rewriter``exit_phi_inputs` / `carrier_inputs` 収集を一時停止
- → SSAundef は解消したが、「ループ出口値」が初期値のままになっている
### 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 3316 ではここに **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 3316 では:
- 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_bindingsExitMetaCollector が作るキャリア出口)
の契約はそのまま維持する。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` | 3315 の暫定 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 回帰
- 既存 Pattern14 のループテスト:
- 結果・RC・SSA すべて元と同じであること。
---
## 7. 次のフェーズ
この設計フェーズ3316はここまで。
次の 3316 実装フェーズでは:
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 として必ず更新すること。

View File

@ -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!

View File

@ -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)

View File

@ -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)

View File

@ -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行以下なら分割不要
---
## 📈 プロジェクト全体への影響
### コード品質
- ✅ 単体テスト追加: TailCallClassifier4ケース
- ✅ ドキュメント改善: 箱理論の役割明記
- ✅ 保守性向上: 関心の分離完全実現
### ビルド時間
- 影響なし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**: Success1m 03s
**Tests**: All Pass
**Next**: Phase 33-17-B 実施検討

View File

@ -1,3 +1,6 @@
Status: Active
Scope: Phase 33-17 の JoinIR モジュール化分析の要約(現行参照用)。詳細メモは archive 側へ移行。
# Phase 33-17: JoinIR モジュール化分析
## 実行日: 2025-12-07

View File

@ -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 Belse側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 |
| テスト容易性 | 既存テスト再利用 | 新規テスト必要 |
**推奨**: **案 APattern4 統合)**
- 理由: `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)

View File

@ -1,143 +0,0 @@
今後は continue 系と JsonParserBox のような実アプリ側ロジックを順番に乗せていく段階に入る。**
# Phase 3320: Loop Exit Semantics Fix — Completion Summary
日付: 20251207
状態: ✅ 実装完了Pattern1/2/3/4 正常、複雑 continue パターンのみ残課題)
---
## 1. ゴール
LoopBuilder 完全削除後の JoinIR ループラインにおいて、
- loop header PHI
- ExitLineExitMeta/ExitBinding/ExitLineReconnector
- JoinInlineBoundary + BoundaryInjector
のあいだで「ループ出口値expr + carrier」の意味論を揃え、
- SSAundef を起こさない
- 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(IfElse 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 Breakexpr ループ)
- テスト: `apps/tests/joinir_min_loop.hako`
- 期待値: ループ終了時の `i` の値2が expr result として返る。
- 結果: ✅ RC: 2、SSAundef なし。
### 3.3 Pattern3: Loop with IfElse 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`
- 期待値: 251+3+5+7+9
- 結果: ✅ 出力は 25。
- `[joinir/freeze]` / SSAundef は発生しない。
- function_params キーの誤参照(`"main"/"loop_step"``"join_func_0"/"join_func_1"`) を修正したことで、
header PHI / ExitLine との結線が正しくなり、Pattern4 の単純 continue ケースでも期待どおりの値になった。
---
## 4. まとめと今後
### 達成点
- header PHI を起点とした Loop exit の意味論が Pattern1〜4 の代表ケースで一貫するようになった。
- ExitLinecarrierと expr PHI ラインが、LoopBuilder なしの JoinIR パイプラインで安全に動く。
- trim や JsonParser のような複雑なループに対しても、基盤として使える出口経路が整った。
### 残課題
- 「else 側 continue」を含む複雑な continue パターンPhase 3318 の Pattern Bはまだ JoinIR 側で正規化されていない。
- これらは BoolExprLowerer/ContinueBranchNormalizer で `if (!cond) { … }` 形に正規化し、
Pattern4 lowerer に統合する計画Phase 3319 以降)。
### 関連フェーズ
- Phase 3316: Loop header PHI SSOT 導入
- Phase 3319: Continue + if/else パターンの正規化(設計済み)
- Phase 170: JsonParserBox / trim の JoinIR 準備ライン
このフェーズで「LoopBuilder 無しの JoinIR ループ出口ラインの基礎」は固まったので、
今後は continue 系と JsonParserBox のような実アプリ側ロジックを順番に乗せていく段階に入る。**

View File

@ -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`

View File

@ -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)
**承認**: 要ユーザーレビュー

View File

@ -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完了後の調査結果

View File

@ -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最適化完了

View File

@ -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の統合は**不要**設計上正しい分離)。