diff --git a/docs/development/current/main/phase195-impl-pattern3-multicarrier.md b/docs/development/current/main/phase195-impl-pattern3-multicarrier.md new file mode 100644 index 00000000..f6c56253 --- /dev/null +++ b/docs/development/current/main/phase195-impl-pattern3-multicarrier.md @@ -0,0 +1,627 @@ +# Phase 195-impl: Pattern3 複数キャリア対応(実装フェーズ) + +**Status**: Ready for Implementation +**Date**: 2025-12-09 +**Prerequisite**: Phase 195 design complete (phase195-pattern3-extension-design.md) + +--- + +## 目的 + +**Pattern 3(If-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 { + // 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 { + 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, +) -> Result { + // 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), // 更新式あり + 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` + +### 現状の ExitLine(Phase 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, +) { + 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, + ) { + 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 で BoolFlag(escaped)が動作 +- [ ] 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) + +**目的**: P3(If-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. **箱理論の実践**: + - 設計書に基づく実装 + - 単一責任の原則維持 + - ドキュメント駆動開発