Files
hakorune/docs/development/current/main/phase33-13-trim-pattern-observation.md
nyash-codex 4e32a803a7 feat(joinir): Phase 33-22 CommonPatternInitializer & JoinIRConversionPipeline integration
Unifies initialization and conversion logic across all 4 loop patterns,
eliminating code duplication and establishing single source of truth.

## Changes

### Infrastructure (New)
- CommonPatternInitializer (117 lines): Unified loop var extraction + CarrierInfo building
- JoinIRConversionPipeline (127 lines): Unified JoinIR→MIR→Merge flow

### Pattern Refactoring
- Pattern 1: Uses CommonPatternInitializer + JoinIRConversionPipeline (-25 lines)
- Pattern 2: Uses CommonPatternInitializer + JoinIRConversionPipeline (-25 lines)
- Pattern 3: Uses CommonPatternInitializer + JoinIRConversionPipeline (-25 lines)
- Pattern 4: Uses CommonPatternInitializer + JoinIRConversionPipeline (-40 lines)

### Code Reduction
- Total reduction: ~115 lines across all patterns
- Zero code duplication in initialization/conversion
- Pattern files: 806 lines total (down from ~920)

### Quality Improvements
- Single source of truth for initialization
- Consistent conversion flow across all patterns
- Guaranteed boundary.loop_var_name setting (prevents SSA-undef bugs)
- Improved maintainability and testability

### Testing
- All 4 patterns tested and passing:
  - Pattern 1 (Simple While): 
  - Pattern 2 (With Break): 
  - Pattern 3 (If-Else PHI): 
  - Pattern 4 (With Continue): 

### Documentation
- Phase 33-22 inventory and results document
- Updated joinir-architecture-overview.md with new infrastructure

## Breaking Changes
None - pure refactoring with no API changes

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

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2025-12-07 21:02:20 +09:00

400 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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