Files
hakorune/docs/development/current/main/phase195-pattern3-extension-design.md
nyash-codex dc4eda9cdc docs: Phase 195 implementation status + Phase 196 (Select bug) planning
Phase 195 implementation complete (Lowerer side):
- loop_with_if_phi_minimal.rs: multi-carrier PHI generation (sum + count)
- pattern3_with_if_phi.rs: dynamic single/multi-carrier handling
- ExitLine/CarrierVar.join_id: NO CHANGES NEEDED (existing infra worked!)
- YAGNI principle validated - PhiGroupBox was not necessary

Blocker discovered: Nested Select→Branch+Phi conversion bug
- JoinIR correctly generates: ValueId(20) = phi [(bb3, 14), (bb4, 18)]
- MIR incorrectly produces: %27 = phi [%28, bb8], [%32, bb9] (undefined)
- Root cause: joinir_block.rs Select expansion (bridge layer)
- NOT a Phase 195 issue - pre-existing bug in Select conversion

Phase 196 planned (NEW):
- 196-1: Document select_expansion / instruction_rewriter responsibilities
- 196-2: Formalize "1 Select = 3 blocks + 1 PHI" interface
- 196-3: Debug block reuse / block ID mapping
- 196-4: Fix and E2E test (phase195_sum_count.hako → 72)

Documentation updates:
- phase195-pattern3-extension-design.md: Implementation Status section
- CURRENT_TASK.md: Phase 195 complete + Phase 196 TODO
- joinir-architecture-overview.md: P3 Lowerer complete, bridge blocker noted

Key insight: "Pattern side is done, problem is in bridge side"

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-09 13:58:31 +09:00

695 lines
21 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 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 として分離 ✅