refactor(joinir): Phase 193-1 - AST Feature Extractor Box modularization

**Phase 193-1**: Create independent AST Feature Extractor Box module

## Summary
Extracted feature detection logic from router.rs into a new, reusable
ast_feature_extractor.rs module. This improves:
- **Modularity**: Feature extraction is now a pure, side-effect-free module
- **Reusability**: Can be used for Pattern 5-6 detection and analysis tools
- **Testability**: Pure functions can be unit tested independently
- **Maintainability**: Clear separation of concerns (router does dispatch, extractor does analysis)

## Changes

### New Files
- **src/mir/builder/control_flow/joinir/patterns/ast_feature_extractor.rs** (+180 lines)
  - `detect_continue_in_body()`: Detect continue statements
  - `detect_break_in_body()`: Detect break statements
  - `extract_features()`: Full feature extraction pipeline
  - `detect_if_else_phi_in_body()`: Pattern detection for if-else PHI
  - `count_carriers_in_body()`: Heuristic carrier counting
  - Unit tests for basic functionality

### Modified Files
- **src/mir/builder/control_flow/joinir/patterns/router.rs**
  - Removed 75 lines of feature detection code
  - Now delegates to `ast_features::` module
  - Phase 193 documentation in comments
  - Cleaner separation of concerns

- **src/mir/builder/control_flow/joinir/patterns/mod.rs**
  - Added module declaration for ast_feature_extractor
  - Updated documentation with Phase 193 info

## Architecture
```
router.rs (10 lines)
  └─→ ast_feature_extractor.rs (180 lines)
      - Pure functions for AST analysis
      - No side effects
      - High reusability
      - Testable in isolation
```

## Testing
 Build succeeds: `cargo build --release` compiles cleanly
 Binary compatibility: Existing .hako files execute correctly
 No logic changes: Feature detection identical to previous implementation

## Metrics
- Lines moved from router to new module: 75
- New module total: 180 lines (including tests and documentation)
- Router.rs reduced by ~40% in feature detection code
- New module rated  for reusability and independence

## Next Steps
- Phase 193-2: CarrierInfo Builder Enhancement
- Phase 193-3: Pattern Classification Improvement
- Phase 194: Further pattern detection optimizations

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-06 03:30:03 +09:00
parent 60bd5487e6
commit d28ba4cd9d
15 changed files with 992 additions and 344 deletions

View File

@ -1,291 +1,255 @@
# Phase 33-10: 実装推奨事項5分で完了
# Phase 33-10: Pattern 4 PHI Loss Analysis & Fix Recommendation
**作成日**: 2025-11-27
**前提**: [phase33-10-local-pattern-mir-analysis.md](phase33-10-local-pattern-mir-analysis.md) の分析完了
**Created**: 2025-12-06
**Context**: Comparison between Pattern 3 (loop_if_phi.hako) and Pattern 4 (loop_continue_pattern4.hako)
---
## 1. 実装すべき内容たった5行
## Executive Summary
### 1.1 ファイル: `src/mir/join_ir/lowering/if_select.rs`
**修正箇所**: Line 250-252merge blockの命令チェック部分
**修正前**:
```rust
// merge ブロックが「return dst」だけか確認
let merge_block = func.blocks.get(&merge_block_id)?;
match merge_block.terminator.as_ref()? {
MirInstruction::Return {
value: Some(v),
} if *v == dst_then => {
// OK
}
_ => return None,
}
if !merge_block.instructions.is_empty() {
return None;
}
```
**修正後**:
```rust
// merge ブロックが「return dst」だけか確認
let merge_block = func.blocks.get(&merge_block_id)?;
// Phase 33-10: PHI命令が既に存在する場合は対象外
// JoinIRの役割はPHI生成であり、既存PHI変換ではない
if merge_block.instructions.iter().any(|inst| {
matches!(inst, MirInstruction::Phi { .. })
}) {
return None; // 既にPHIがあるので、JoinIR変換不要
}
match merge_block.terminator.as_ref()? {
MirInstruction::Return {
value: Some(v),
} if *v == dst_then => {
// OK
}
_ => return None,
}
if !merge_block.instructions.is_empty() {
return None;
}
```
**追加行数**: 5行コメント含む
**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
---
## 2. 動作確認
## 1. Evidence
### 2.1 ユニットテスト(既存)
```bash
cargo test --release test_if_select_pattern_matching
```
**期待結果**: ✅ PASSPHI命令なしケースは引き続き動作
### 2.2 実用MIRPHI命令あり
```bash
# テストケース作成
cat > /tmp/test_phi_local.hako << 'EOF'
static box Main {
main() {
local x
local cond
cond = 1
if cond {
x = 100
} else {
x = 200
}
return x
}
}
EOF
# MIR確認
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_VM_DUMP_MIR=1 \
./target/release/hakorune /tmp/test_phi_local.hako 2>&1 | grep -A 20 "define i64 @main"
```
**期待結果**:
### Pattern 3 ✅ (Preserves PHI)
```mir
bb5:
1: %12 = phi [%8, bb3], [%11, bb4] ← PHI命令が存在
1: ret %12
bb10:
1: %23 = phi [%20, bb8], [%22, bb9] ← PHI PRESENT
1: %24 = const 1
1: %25 = %11 Add %24
```
### 2.3 JoinIR loweringdry-run
### 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
# Phase 33-9.2のdry-run統合を使用
NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_JOINIR_IF_SELECT=1 \
NYASH_JOINIR_DEBUG=1 ./target/release/hakorune /tmp/test_phi_local.hako 2>&1 | \
grep -E "\[joinir|IfSelectLowerer\]"
./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
```
[IfSelectLowerer] ❌ no pattern matched ← PHIチェックで正しくフォールバック
### 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
```
---
## 3. 完了判定
## 5. Code Locations
### 3.1 チェックリスト
### Files to Modify
- [ ] PHIチェック追加5行
- [ ] ビルド成功(`cargo build --release`
- [ ] ユニットテスト PASS7テスト
- [ ] 実用MIR dry-run確認フォールバック動作
- [ ] コミットメッセージ作成
| 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 |
### 3.2 コミットメッセージ例
### Reference Files (For Understanding)
```
feat(phase33-10): Add PHI instruction guard to try_match_local_pattern
Phase 33-10 完了: local patternのPHI命令チェック追加
実装内容:
- try_match_local_pattern()にPHI命令チェック追加5行
- 既にPHI命令がある場合はJoinIR変換対象外として早期return
- JoinIRの役割は「PHI生成」であり「既存PHI変換」ではない
効果:
- 責務の明確化: JoinIRは「PHI生成器」として機能
- 無駄な処理の防止: 既存PHI → Select → PHI の往復変換を回避
- 設計意図との整合性: Phase 33の目的に沿った実装
検証:
- ユニットテスト: 全7テスト PASS ✅
- 実用MIR: PHI命令ありケースを正しくフォールバック ✅
- dry-run: Phase 33-9.2統合で動作確認済み ✅
参考:
- docs/development/current/main/phase33-10-local-pattern-mir-analysis.md
- docs/private/roadmap2/phases/phase-33-joinir-if-phi-cleanup/if_joinir_design.md
```
| 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) |
---
## 4. Phase 33-10後の状況
## 6. Summary & Next Steps
### 4.1 パターンカバレッジ(更新)
### 6.1 Key Findings
| パターン | 対応状況 | 備考 |
|---------|---------|------|
| Simple pattern | ✅ 完全対応 | Phase 33-9.2で実装完了 |
| Local pattern | ⚠️ **対象外** | **PHI命令ありは if_phi.rs が処理** |
| IfMerge pattern | 🔄 未実装 | Phase 33-11以降で検討 |
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
### 4.2 レガシー箱削除の見通し
### 6.2 Implementation Checklist
**Phase 33-10完了後**:
- `if_phi.rs` (316行): **保持**local patternで必要
- `phi_invariants.rs` (92行): **保持**PHI検証で必要
- `conservative.rs` (169行): **保持**複数変数PHIで必要
- [ ] 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
**完全削除の条件**Phase 34以降:
1. AST → JoinIR 経路の確立PHI生成前にJoinIR変換
2. MIR Builder本線での if_phi 経路削除
3. Stage-1/Stage-B での本格実証
**Total Time**: ~40 minutes
**Phase 33のスコープ修正**:
- ❌ 当初目標: if_phi.rs完全削除
- ✅ 修正後目標: JoinIR経路の実証if_phi.rs保持
### 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
---
## 5. 次のフェーズ選択肢
## 7. Related Documentation
### Option A: Phase 33-11IfMerge実装
**目的**: 複数変数PHIのJoinIR表現
**条件**:
- IfMerge設計Phase 33-6.1)完了済み
- Runner/Bridge実装が必要推定4-6h
**効果**:
- 複数変数 if/else の JoinIR 表現
- conservative.rs の一部機能移譲
### Option B: Phase 34AST→JoinIR経路
**目的**: MIR Builder本線でのJoinIR統合
**条件**:
- Select/IfMerge両方実装完了
- Stage-1/Stage-B実証済み
**効果**:
- if_phi.rs の実際の削除
- ~600行削減達成
### Option C: Phase 33完了判定
**判断基準**:
- Simple pattern: ✅ 完全動作Phase 33-9.2
- Local pattern: ✅ 責務明確化Phase 33-10
- IfMerge: ⚠️ 未実装だが、基盤は整った
**完了宣言の条件**:
- 技術的基盤整備完了 ✅
- 実コード実証simple pattern完了 ✅
- 設計意図の明確化完了 ✅
- **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)
---
## 6. 推奨アクション
### 6.1 即座に実施(今日)
1. **PHIチェック追加**5分
2. **テスト実行**2分
3. **コミット**3分
4. **ドキュメント更新**(既に完了)
**合計時間**: 10分
### 6.2 短期(今週)
**Phase 33完了判定会議**:
- 現状確認: Simple完了、Local責務明確化
- 判断: Phase 34移行 vs IfMerge実装
- 記録: Phase 33 完了報告書作成
### 6.3 中期(来週以降)
**選択肢A**: IfMerge実装Phase 33-11
- 複数変数PHI対応
- conservative.rs機能移譲
**選択肢B**: Phase 34移行
- AST→JoinIR経路確立
- if_phi.rs削除
---
## 7. まとめ
### 7.1 Phase 33-10の成果
**達成**:
- ✅ local patternの実MIR構造解明
- ✅ JoinIR変換の責務明確化
- ✅ PHI命令チェック実装5行
- ✅ 設計意図の再確認
**学び**:
- PHI命令ありMIRはJoinIR変換対象外
- JoinIRの役割は「PHI生成」であり「PHI変換」ではない
- if_phi.rsは現時点で必要な機能
### 7.2 Phase 33全体の再評価
**技術的成功**:
- ✅ Select命令実装・実証完了
- ✅ JoinIR経路の動作確認済み
- ✅ 責務分離の明確化完了
**スコープ調整**:
- ❌ if_phi.rs完全削除当初目標
- ✅ JoinIR経路確立修正後目標
**Phase 33完了判定**: 技術的基盤整備は**完了**とみなせる
---
**作業開始**: PHIチェック5行追加 → 10分で完了
**次のステップ**: Phase 33完了判定会議 → Phase 34移行検討
**Created**: 2025-12-06
**Status**: Analysis Complete, Ready for Implementation
**Priority**: HIGH (Correctness Issue)