Phase 195 design document for P3 (If-Else PHI) multi-carrier expansion: - Goal: Handle 2-3 carriers in if-else branches simultaneously - Scope: if-complete multi-carrier updates (flag+buffer, sum+count patterns) Task breakdown: - 195-1: Loop inventory (_parse_string simple, if-sum patterns) - 195-2: LoopUpdateSummary/CarrierInfo design (multi-carrier support) - 195-3: Pattern3 lowerer design (PhiGroup vs ExitLine extension) - 195-4: Implementation scope (2-3 carriers, existing UpdateKind range) - 195-5: Documentation updates (CURRENT_TASK.md + overview) Design decisions: - ✅ ExitLine extension (no PhiGroupBox - YAGNI principle) - ✅ Both-branch carrier definition check (unchanged = use previous value) - ❌ No ConditionEnv expansion (Phase 200+ deferred) - ❌ LoopBodyLocal + MethodCall mix deferred to Phase 195+ Target loops: 1. JsonParser _parse_string (escaped flag + buffer) - Carrier 1: escaped (BoolFlag) - conditional flag - Carrier 2: buffer (StringAppend) - else-only update 2. selfhost if-sum (sum + count) - Carrier 1: sum (NumberAccumulation) - then-only update - Carrier 2: count (CounterLike) - then-only update Test cases designed: - phase195_flag_buffer.hako (BoolFlag + StringAppend) - phase195_sum_count.hako (NumberAccumulation + CounterLike) Expected outcome: - phase195-pattern3-extension-design.md (complete design spec) - Clear implementation scope for Phase 195-impl - Path to 40% → 60% JsonParser coverage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
18 KiB
Phase 195: Pattern 3 拡張(if-in-loop + マルチキャリア)
Status: Design Phase Date: 2025-12-09 Prerequisite: Phase 194 complete (JsonParser deployment & validation)
目的
Pattern 3(If-Else PHI)の対応範囲を拡張し、複数キャリアの条件付き更新を JoinIR で扱えるようにする。
スコープ:
- ✅ if 内で完結する複数キャリア更新(2-3個程度)
- ❌ ConditionEnv 拡張は行わない(外部ローカル・digits 系は Phase 200+ 保留)
- ❌ LoopBodyLocal + MethodCall 混在は Phase 195+ に延期
Task 195-1: 対象ループの絞り込み(doc-only)
目標
JsonParser / selfhost から「P3 で攻めたいループ」を 1-2 本だけ選定し、AST 構造を詳細に分析する。
候補ループ
候補 1: JsonParser _parse_string の簡易版(優先度: 高)
ループ構造:
// _parse_string (escape 処理の簡略版)
static box JsonParser {
_parse_string(s, start) {
local buffer = ""
local escaped = false
local i = start
loop(i < s.length() and s[i] != '"') {
local ch = s[i]
if(ch == '\\') {
escaped = true // ← Carrier 1 update
} else {
buffer = buffer + ch // ← Carrier 2 update
escaped = false // ← Carrier 1 update
}
i = i + 1
}
return buffer
}
}
AST 構造:
Loop {
condition: BinOp(i < len AND s[i] != '"')
body: [
LocalVar { name: "ch", init: ArrayAccess(s, i) }, // body-local
If {
condition: Compare(ch == '\\'),
then: [
Assign { lhs: "escaped", rhs: BoolLiteral(true) }
],
else: [
Assign { lhs: "buffer", rhs: BinOp(buffer + ch) },
Assign { lhs: "escaped", rhs: BoolLiteral(false) }
]
},
Assign { lhs: "i", rhs: BinOp(i + 1) }
]
}
キャリア分析:
| Carrier | Type | Update Pattern | Then Branch | Else Branch |
|---|---|---|---|---|
escaped |
BoolBox | Conditional flag | true |
false |
buffer |
StringBox | StringAppend | (unchanged) | buffer + ch |
i |
IntegerBox | CounterLike | i + 1 |
i + 1 |
P3 で扱う範囲:
- ✅
escaped: 条件フラグ(両分岐で定義) - ✅
buffer: StringAppend(else のみ更新、then は不変) - ✅
i: ループ外で統一更新(P3 の外で処理)
制約:
chは body-local 変数(Phase 191 で対応済み)s[i]の配列アクセスは ConditionEnv で解決(sは outer local → Phase 200+)- この Phase では
ch = "x"のような定数で代替してテスト
候補 2: selfhost の if-sum パターン(優先度: 中)
ループ構造:
// selfhost: 条件付き集計
static box Aggregator {
sum_positive(array) {
local sum = 0
local count = 0
local i = 0
loop(i < array.length()) {
if(array[i] > 0) {
sum = sum + array[i] // ← Carrier 1 update
count = count + 1 // ← Carrier 2 update
}
i = i + 1
}
return sum
}
}
AST 構造:
Loop {
condition: Compare(i < array.length())
body: [
If {
condition: Compare(array[i] > 0),
then: [
Assign { lhs: "sum", rhs: BinOp(sum + array[i]) },
Assign { lhs: "count", rhs: BinOp(count + 1) }
],
else: [] // 空(更新なし)
},
Assign { lhs: "i", rhs: BinOp(i + 1) }
]
}
キャリア分析:
| Carrier | Type | Update Pattern | Then Branch | Else Branch |
|---|---|---|---|---|
sum |
IntegerBox | NumberAccumulation | sum + array[i] |
(unchanged) |
count |
IntegerBox | CounterLike | count + 1 |
(unchanged) |
i |
IntegerBox | CounterLike | i + 1 |
i + 1 |
P3 で扱う範囲:
- ✅
sum: NumberAccumulation(then のみ更新) - ✅
count: CounterLike(then のみ更新) - ✅
i: ループ外で統一更新
制約:
array[i]の配列アクセスは ConditionEnv で解決(arrayは outer local → Phase 200+)- この Phase では
iのような既存パラメータで代替してテスト
Phase 195 での選定
優先順位 1: 候補 1(_parse_string 簡易版)
- 理由: JsonParser の実戦コード、flag + buffer の2キャリア
- 簡略化:
ch = "x"定数で配列アクセス回避
優先順位 2: 候補 2(if-sum)
- 理由: selfhost の典型パターン、sum + count の2キャリア
- 簡略化:
iのみで配列アクセス回避
成果物:
- 選定したループの詳細 AST 構造記録(本セクション)
- キャリア分析表(UpdateKind 分類済み)
Task 195-2: LoopUpdateSummary / CarrierInfo の設計整理
現状の P3 サポート(Phase 170-189)
既存の P3 は単一キャリアのみ対応:
// 既存の P3 ケース
if(cond) {
sum = sum + i // ← 単一キャリア "sum"
} else {
sum = sum - i
}
LoopUpdateSummary / CarrierInfo の構造:
pub struct CarrierInfo {
pub carriers: Vec<String>, // キャリア名リスト
pub updates: HashMap<String, ASTNode>, // name → update式
}
pub struct LoopUpdateSummary {
pub kind: UpdateKind, // CounterLike | NumberAccumulation | ...
// ...
}
Phase 195 で扱う「複数キャリア + 条件付き更新」
拡張要件:
// Phase 195 の P3 ケース
if(cond) {
escaped = true // ← Carrier 1
// buffer は更新なし(不変)
} else {
buffer = buffer + ch // ← Carrier 2
escaped = false // ← Carrier 1
}
設計原則:
-
両分岐で同じ Carrier が必ず定義されること(PHI 生成の前提)
- 片方の分岐で更新なし(不変)の場合、明示的に
carrier = carrierを挿入 - OR: PHI 生成時に「更新なし = 前の値を使う」として扱う
- 片方の分岐で更新なし(不変)の場合、明示的に
-
各 Carrier の update 式は既存 UpdateKind 範囲内:
- CounterLike:
count + 1,count - 1 - NumberAccumulation:
sum + i,sum * base + addend - StringAppend:
buffer + ch - BoolFlag:
true,false(新規 UpdateKind 候補)
- CounterLike:
-
CarrierInfo は複数 Carrier を同時に保持:
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):
if(ch == '\\') {
escaped = true // ✅ BoolFlag
} else {
buffer = buffer + ch // ✅ StringAppend
escaped = false // ✅ BoolFlag
}
例(NG - Phase 195 範囲外):
if(cond) {
digit = digits.indexOf(ch) // ❌ 外部ローカル (Phase 200+)
sum = sum + digit
}
成果物
- 複数キャリア対応の設計原則(本セクション)
- UpdateKind 拡張候補(BoolFlag)の検討
- CarrierInfo 構造の拡張仕様(擬似コード)
Task 195-3: Pattern3 lowerer の設計更新
現状の P3 lowerer(Phase 170-189)
単一キャリアの処理フロー:
1. If-Else AST を検出
2. Then/Else 各分岐で carrier 更新式を抽出
3. JoinIR で Then/Else ブロック生成
4. Merge 点で PHI 命令生成(1つの carrier のみ)
5. ExitLine で PHI 結果を variable_map に接続
Phase 195 での拡張設計
1. 複数 Carrier の同時処理
設計案: 複数 PHI を同じ Merge 点で生成
// 擬似コード: 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(新規箱)
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 拡張(既存箱再利用)
// 既存の 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 が更新されない
if(ch == '\\') {
escaped = true
// buffer は更新なし(不変)
} else {
buffer = buffer + ch
escaped = false
}
設計案: PHI 生成時に「前の値」を使用
// 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 で実装する:
-
複数 Carrier の P3 処理(2-3個程度)
escaped+bufferのような flag + accumulationsum+countのような accumulation + counter
-
既存 UpdateKind 範囲内の update:
- CounterLike:
count + 1 - NumberAccumulation:
sum + i - StringAppend:
buffer + ch - BoolFlag:
true,false(新規 UpdateKind 追加候補)
- CounterLike:
-
両分岐での定義確認:
- Then/Else で同じ Carrier が定義されるケース
- 更新なし(不変)の場合は自動的に「前の値」を使用
-
E2E テスト:
- 簡易版 _parse_string(
ch = "x"定数版) - 簡易版 if-sum(配列アクセスなし版)
- 簡易版 _parse_string(
❌ Phase 195-impl で実装しない:
-
LoopBodyLocal + MethodCall 混在:
local ch = s[i]; if(...) { buf += ch }- → Phase 191(body-local init)と Phase 193(MethodCall)の組み合わせ
- → Phase 195+ に延期(複雑度高い)
-
外部ローカル変数(ConditionEnv 拡張):
local digits = "012..."; digit = digits.indexOf(ch)- → Phase 200+ に保留(設計判断済み)
-
ネストした 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
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
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 更新内容
## Phase 195: Pattern 3 拡張(設計フェーズ)(完了予定: 2025-12-XX)
**目的**: P3(If-Else PHI)を複数キャリア対応に拡張する設計
**タスク**:
- [ ] 195-1: 対象ループ絞り込み(_parse_string 簡易版、if-sum)
- [ ] 195-2: LoopUpdateSummary/CarrierInfo 設計整理(複数キャリア対応)
- [ ] 195-3: Pattern3 lowerer 設計更新(PhiGroup vs ExitLine 拡張判断)
- [ ] 195-4: 実装スコープ決定(2-3キャリア、既存 UpdateKind 範囲内)
- [ ] 195-5: ドキュメント更新(本項目 + overview 更新)
**設計判断**:
- ✅ ExitLine 拡張で対応(PhiGroupBox は作らない)
- ✅ 両分岐での Carrier 定義確認(更新なし = 前の値使用)
- ❌ ConditionEnv 拡張なし(Phase 200+ 保留)
- ❌ LoopBodyLocal + MethodCall 混在は Phase 195+ 延期
**期待成果**:
- phase195-pattern3-extension-design.md(完全設計書)
- Phase 195-impl の実装スコープ明確化
- JsonParser カバレッジ 40% → 60% への道筋
joinir-architecture-overview.md 更新内容
Section 7.2 "残タスク" に追記:
- [ ] **Phase 195**: Pattern 3 拡張(複数キャリア対応)
- 設計フェーズ: P3 で 2-3 個の Carrier を同時処理
- ExitLine 拡張で複数 PHI 生成(PhiGroupBox は不要)
- 対象: _parse_string(flag+buffer)、if-sum(sum+count)
- Phase 195-impl で実装予定
成功基準(設計フェーズ)
- 対象ループ選定完了(_parse_string 簡易版、if-sum)
- キャリア分析表作成(UpdateKind 分類済み)
- 複数キャリア対応の設計原則明確化
- Pattern3 lowerer の擬似コード作成
- PhiGroupBox vs ExitLine 拡張の判断(Option B 採用)
- 実装スコープ決定(Phase 195-impl 範囲明確化)
- テストケース設計(2ケース)
- ドキュメント更新計画作成
関連ファイル
設計対象
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 拡張実装
設計書(本ファイル)に基づいて実装:
- Pattern3 lowerer に複数 Carrier 処理追加
- ExitLine で複数 PHI 接続
- BoolFlag UpdateKind 追加(必要に応じて)
- E2E テスト(phase195_flag_buffer.hako, phase195_sum_count.hako)
- ドキュメント更新(Implementation Status セクション)
Phase 196+: 候補
- Pattern 3 のネスト対応(if-in-if)
- LoopBodyLocal + MethodCall + P3 の統合
- JsonParser の _parse_string 完全版(配列アクセス対応 = Phase 200+)
設計原則(Phase 195)
-
既存箱再利用:
- PhiGroupBox を作らず、ExitLine/CarrierInfo を拡張
- YAGNI(You Aren't Gonna Need It)原則
-
段階的拡張:
- 単一キャリア(Phase 170-189)→ 複数キャリア(Phase 195)
- 2-3 個程度に限定(無理に全部対応しない)
-
Fail-Fast 継承:
- ConditionEnv 拡張なし(Phase 200+ 保留)
- 複雑なパターンは Phase 195+ に延期
-
箱理論の実践:
- 設計フェーズで構造を固める
- 実装フェーズは lowerer のみに集中
- ドキュメント駆動開発