diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index ff9ef237..94f3ba20 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -245,13 +245,18 @@ - **E2E 結果**: `phase190_atoi_impl.hako` → 12 ✅、`phase190_parse_number_impl.hako` → 123 ✅ - ExitLine contract Verifier 追加(`#[cfg(debug_assertions)]`) - **残課題**: body-local 変数 assignment は JoinIR 未対応(Phase 191 で対応) - - [ ] **Phase 191: Body-Local Init & Update Lowering Integration** - - **目的**: LoopBodyLocalEnv / UpdateEnv / LoopBodyLocalInitLowerer を Pattern2/4 に本番統合 - - 191-1: 対象ケース選定(body-local が原因で JoinIR OFF な代表ループ 1 本) - - 191-2: LoopBodyLocalInitLowerer の Pattern2 統合(init emission 位置は body 先頭) - - 191-3: UpdateEnv 統合確認(ConditionEnv + LoopBodyLocalEnv 両方から解決) - - 191-4: E2E テスト(body-local 版 _atoi で期待値確認) - - 191-5: ドキュメント更新(phase184/186.md + CURRENT_TASK) + - [x] **Phase 191: Body-Local Init & Update Lowering Integration** ✅ (2025-12-09) + - **目的**: LoopBodyLocalEnv / UpdateEnv / LoopBodyLocalInitLowerer を Pattern2 に本番統合完了 + - 191-1: 対象ケース選定 → `phase191_body_local_atoi.hako` 作成 ✅ + - 191-2: LoopBodyLocalInitLowerer の Pattern2 統合(init emission 位置は body 先頭)✅ + - 191-3: UpdateEnv 統合確認(ConditionEnv + LoopBodyLocalEnv 両方から解決)✅ + - 191-4: E2E テスト → 期待値 123 出力確認 ✅ + - 191-5: ドキュメント更新 ✅ + - **成果**: + - 対応済み init 式: 整数リテラル、変数参照、二項演算(`local digit = i + 1`) + - UpdateEnv 優先順位: ConditionEnv(高)→ LoopBodyLocalEnv(フォールバック) + - ValueId 二重割当問題を修正(空の LoopBodyLocalEnv を InitLowerer に委譲) + - **テスト**: phase191_body_local_atoi.hako → 123 ✅、退行なし - [ ] Phase 192: Complex addend 対応 - `v = v * 10 + f(x)` のような method call を含むパターン - addend を事前計算して temp に格納 diff --git a/docs/development/current/main/joinir-architecture-overview.md b/docs/development/current/main/joinir-architecture-overview.md index 9b3181b9..252bcb69 100644 --- a/docs/development/current/main/joinir-architecture-overview.md +++ b/docs/development/current/main/joinir-architecture-overview.md @@ -169,11 +169,12 @@ JoinIR ラインで守るべきルールを先に書いておくよ: - else-continue を then-continue へ正規化し、Select ベースの continue を簡潔にする。 - ループ本体で実際に更新されるキャリアだけを抽出(Pattern 4 で不要キャリアを排除)。 -- **LoopBodyLocalEnv / UpdateEnv / CarrierUpdateEmitter(Phase 184)** +- **LoopBodyLocalEnv / UpdateEnv / CarrierUpdateEmitter(Phase 184, 191統合完了)** - ファイル: - `src/mir/join_ir/lowering/loop_body_local_env.rs` - `src/mir/join_ir/lowering/update_env.rs` - `src/mir/join_ir/lowering/carrier_update_emitter.rs` + - `src/mir/join_ir/lowering/loop_with_break_minimal.rs`(Phase 191統合) - 責務: - **LoopBodyLocalEnv**: ループ本体で宣言された body-local 変数の名前→ValueId マッピングを管理(箱化設計)。 - **UpdateEnv**: 条件変数(ConditionEnv)と body-local 変数(LoopBodyLocalEnv)を統合した変数解決層。 @@ -181,10 +182,13 @@ JoinIR ラインで守るべきルールを先に書いておくよ: - **CarrierUpdateEmitter**: UpdateExpr を JoinIR 命令に変換する際、UpdateEnv を使用して body-local 変数をサポート。 - `emit_carrier_update_with_env()`: UpdateEnv 版(Phase 184 新規) - `emit_carrier_update()`: ConditionEnv 版(後方互換) + - **LoopBodyLocalInitLowerer**: Phase 191 で Pattern2 に統合完了。 + - 対応済み init 式: 整数リテラル、変数参照、二項演算(`local digit = i + 1`) + - UpdateEnv の優先順位により ConditionEnv → LoopBodyLocalEnv の順で変数解決 - 設計原則: - **箱理論**: 各 Box が単一責任を持ち、境界明確。 - **決定性**: BTreeMap 使用で一貫した順序保証(PHI 生成の決定性)。 - - **保守的**: Pattern5 (Trim) は対象外、Phase 185 で統合予定。 + - **Phase 192予定**: Complex addend(`v = v*10 + f(x)`)は Normalizer で temp に落としてから NumberAccumulation に載せる。 ### 2.3 キャリア / Exit / Boundary ライン @@ -489,18 +493,19 @@ JoinIR は Rust 側だけでなく、将来的に .hako selfhost コンパイラ - ✅ JoinIRVerifier: デバッグビルドで契約検証 - ✅ ExitLine Contract Verifier: PHI 配線検証(Phase 190-impl-D) -### 7.2 残タスク(Phase 191+ で対応予定) +### 7.2 残タスク(Phase 192+ で対応予定) -1. **body-local 変数の init + update lowering(Phase 186/191 ライン)** - - `local digit = s[i] - '0'` のような body-local 変数の JoinIR/MIR 生成 - - LoopBodyLocalEnv / UpdateEnv インフラは Phase 184 で実装済み - - Pattern2/4 への統合が残タスク +1. **✅ body-local 変数の init + update lowering** → Phase 191 完了 + - `local digit = i + 1` のような body-local 変数の JoinIR/MIR 生成完了 + - 対応済み: 整数リテラル、変数参照、二項演算 + - テスト: `phase191_body_local_atoi.hako` → 期待値 123 ✅ -2. **Complex addend 対応(Phase 191+)** +2. **Complex addend 対応(Phase 192)** - `v = v * 10 + digits.indexOf(ch)` のような method call を含む NumberAccumulation - - 現状は Fail-Fast で拒否、将来的に addend を事前計算して temp に格納 + - 方針: ComplexAddendNormalizer で `temp = f(x)` に分解してから NumberAccumulation に載せる + - 現状は Fail-Fast で拒否 -3. **JsonParser 残り複雑ループ・selfhost ループへの適用** +3. **JsonParser 残り複雑ループ・selfhost ループへの適用(Phase 193+)** - `_parse_array` / `_parse_object`(P4 Continue + 複数 MethodCall) - `_unescape_string`(複雑なキャリア + flatten) - selfhost `.hako` コンパイラの全ループを JoinIR で処理 diff --git a/docs/development/current/main/phase192-complex-addend-design.md b/docs/development/current/main/phase192-complex-addend-design.md new file mode 100644 index 00000000..3d3f4ae4 --- /dev/null +++ b/docs/development/current/main/phase192-complex-addend-design.md @@ -0,0 +1,461 @@ +# Phase 192: Complex Addend Design (Doc-Only) + +**Status**: Design Phase +**Date**: 2025-12-09 +**Prerequisite**: Phase 191 complete (body-local init integrated) + +--- + +## 目的 + +`result = result * 10 + digits.indexOf(ch)` のような +「加算側がメソッド呼び出し」のパターンを、 +既存の NumberAccumulation ラインの前処理として安全に扱えるようにする。 + +新パターンは増やさず、Analyzer/Lowerer の箱を拡張するだけに留める。 + +--- + +## Section 1: 対象 RHS パターン一覧 + +### 1.1 JsonParser._parse_number (lines 106-142) + +**ループパターン**: +```nyash +local num_str = "" +local digits = "0123456789" +loop(p < s.length()) { + local ch = s.substring(p, p+1) + local digit_pos = digits.indexOf(ch) + + if digit_pos < 0 { + break + } + + num_str = num_str + ch // ← StringAppendChar (already supported) + p = p + 1 +} +``` + +**Note**: このループは string accumulation なので Phase 192 の対象外(既にサポート済み)。 + +### 1.2 JsonParser._atoi (lines 436-467) + +**ループパターン**: +```nyash +local v = 0 +local digits = "0123456789" +loop(i < n) { + local ch = s.substring(i, i+1) + if ch < "0" || ch > "9" { break } + local pos = digits.indexOf(ch) + if pos < 0 { break } + v = v * 10 + pos // ← NumberAccumulation with body-local (Phase 191 supported) + i = i + 1 +} +``` + +**Current Status**: Phase 191 で body-local `pos` が使えるようになったので対応可能。 + +### 1.3 Complex Addend Pattern (Target for Phase 192) + +**パターン**: +```nyash +local v = 0 +local digits = "0123456789" +loop(i < n) { + local ch = s.substring(i, i+1) + v = v * 10 + digits.indexOf(ch) // ← Complex addend (method call) + i = i + 1 +} +``` + +**AST Form**: +``` +Assign( + lhs = "v", + rhs = BinaryOp( + op = Add, + left = BinaryOp( + op = Mul, + left = Variable("v"), + right = Literal(Integer(10)) + ), + right = MethodCall( + receiver = Variable("digits"), + method = "indexOf", + args = [Variable("ch")] + ) + ) +) +``` + +**Characteristics**: +- LHS appears exactly once in RHS (in left-most multiplication) +- Base: 10 (constant) +- Addend: **MethodCall** (complex expression) +- Current behavior: Rejected as `UpdateKind::Complex` + +--- + +## Section 2: LHS 出現回数とMethod Call位置の整理 + +### 2.1 パターンマトリクス + +| Pattern | LHS Count | Base | Addend | Current | Phase 192 | +|---------|-----------|------|--------|---------|-----------| +| `v = v * 10 + pos` | 1 | Const(10) | Variable | NumberAccumulation | No change | +| `v = v * 10 + 5` | 1 | Const(10) | Const(5) | NumberAccumulation | No change | +| `v = v * 10 + digits.indexOf(ch)` | 1 | Const(10) | MethodCall | **Complex** | **Normalize** | +| `v = v * base + x` | 1 | Variable | Variable | Complex | Fail-Fast | +| `v = v * 10 + (a + b)` | 1 | Const(10) | BinaryOp | Complex | Future | +| `v = v * 10 + v` | 2 | Const(10) | Variable | Complex | Fail-Fast | + +### 2.2 Method Call の位置分類 + +**Type A: Addend に Method Call(Phase 192 対象)**: +```nyash +v = v * 10 + digits.indexOf(ch) +``` +→ Normalize: `temp = digits.indexOf(ch); v = v * 10 + temp` + +**Type B: Base に Method Call(対象外)**: +```nyash +v = v * getBase() + x +``` +→ Fail-Fast(base は constant のみ許可) + +**Type C: LHS 側に Method Call(対象外)**: +```nyash +v = obj.getValue() * 10 + x +``` +→ Fail-Fast(LHS は simple variable のみ) + +### 2.3 Nested Method Call(将来拡張) + +```nyash +v = v * 10 + parser.parse(s.substring(i, i+1)) +``` +→ Phase 192 では Fail-Fast、Phase 193+ で対応検討 + +--- + +## Section 3: Temp 分解戦略 + +### 3.1 正規化アプローチ + +**Before (Complex)**: +```nyash +result = result * 10 + digits.indexOf(ch) +``` + +**After (Normalized)**: +```nyash +local temp_digit = digits.indexOf(ch) +result = result * 10 + temp_digit +``` + +### 3.2 正規化の責務 + +新しい箱: **ComplexAddendNormalizer** + +**入力**: +- `Assign(lhs, complex_rhs)` where `complex_rhs` has MethodCall in addend +- ループ本体 AST(temp 変数を挿入する位置情報) + +**出力**: +- `temp` 定義: `local temp = methodCall(...)` +- 正規化された Assign: `lhs = lhs * base + temp` + +**配置**: +- Pattern2 lowering の前処理ライン(`can_lower` の前) + +--- + +## Section 4: ComplexAddendNormalizer 擬似コード + +### 4.1 検出ロジック + +```rust +fn is_complex_addend_pattern(update_expr: &UpdateExpr) -> bool { + // Check structure: lhs = lhs * base + addend + let UpdateExpr::BinOp { op: BinOpKind::Add, left, right } = update_expr else { + return false; + }; + + // Left side: lhs * base (multiplication) + let UpdateRhs::BinOp { op: BinOpKind::Mul, .. } = left else { + return false; + }; + + // Right side (addend): MethodCall or complex expression + matches!(right, UpdateRhs::MethodCall(_) | UpdateRhs::BinaryOp(_)) +} +``` + +### 4.2 正規化ロジック + +```rust +fn normalize_complex_addend( + lhs: &str, + update_expr: &UpdateExpr, + body_ast: &mut Vec, +) -> Result<(String, UpdateExpr), String> { + // Extract addend (method call or complex expression) + let addend = extract_addend(update_expr)?; + + // Generate temp variable name + let temp_name = format!("temp_{}_addend", lhs); + + // Insert temp assignment at current position + // local temp = digits.indexOf(ch) + let temp_init = ASTNode::LocalDeclaration { + name: temp_name.clone(), + init: Some(Box::new(addend.to_ast())), + }; + body_ast.insert(0, temp_init); // Insert at loop body start + + // Create normalized update expression + // lhs = lhs * 10 + temp + let normalized_expr = UpdateExpr::BinOp { + op: BinOpKind::Add, + left: Box::new(UpdateRhs::BinOp { + op: BinOpKind::Mul, + left: Box::new(UpdateRhs::Variable(lhs.to_string())), + right: Box::new(UpdateRhs::Const(extract_base(update_expr)?)), + }), + right: Box::new(UpdateRhs::Variable(temp_name.clone())), + }; + + Ok((temp_name, normalized_expr)) +} +``` + +### 4.3 Phase 191 との統合 + +Phase 191 の `LoopBodyLocalInitLowerer` が `temp` の初期化を処理: +- `local temp_digit = digits.indexOf(ch)` → JoinIR に emit +- `LoopBodyLocalEnv` に `temp_digit -> join_id` を登録 +- `UpdateEnv` が `temp_digit` を解決して NumberAccumulation に渡す + +--- + +## Section 5: LoopUpdateAnalyzer との責務分け + +### 5.1 現在のフロー + +``` +AST → LoopUpdateAnalyzer → UpdateKind::Complex (Fail-Fast) +``` + +### 5.2 Phase 192 フロー + +``` +AST → LoopUpdateAnalyzer → UpdateKind::Complex + ↓ (Pattern2 内 can_lower 前) +ComplexAddendNormalizer → 前処理 (temp 生成) + ↓ +再度 LoopUpdateAnalyzer → UpdateKind::NumberAccumulation { base: 10 } + ↓ +Pattern2 lowering → JoinIR emission +``` + +### 5.3 can_lower の変更点 + +**Before (Phase 191)**: +```rust +fn can_lower_carrier_updates(updates: &HashMap) -> bool { + for (name, update_expr) in updates { + let kind = classify_update_kind(update_expr); + match kind { + UpdateKind::Complex => { + eprintln!("[joinir/freeze] Complex carrier update"); + return false; + } + _ => { /* OK */ } + } + } + true +} +``` + +**After (Phase 192)**: +```rust +fn can_lower_carrier_updates_with_normalization( + updates: &HashMap, + body_ast: &mut Vec, +) -> Result, String> { + let mut normalized_updates = HashMap::new(); + + for (name, update_expr) in updates { + let kind = classify_update_kind(update_expr); + match kind { + UpdateKind::Complex => { + // Try normalization + if is_complex_addend_pattern(update_expr) { + let (temp_name, normalized_expr) = + ComplexAddendNormalizer::normalize(name, update_expr, body_ast)?; + + // Re-classify + let normalized_kind = classify_update_kind(&normalized_expr); + if matches!(normalized_kind, UpdateKind::NumberAccumulation { .. }) { + normalized_updates.insert(name.clone(), normalized_expr); + } else { + return Err("[joinir/freeze] Normalization failed".to_string()); + } + } else { + return Err("[joinir/freeze] Complex pattern not supported".to_string()); + } + } + _ => { + normalized_updates.insert(name.clone(), update_expr.clone()); + } + } + } + + Ok(normalized_updates) +} +``` + +--- + +## Section 6: Emission 側への影響 + +### 6.1 JoinIR Emission(変更なし) + +ComplexAddendNormalizer で前処理するため、既存の emission ラインは変更不要: + +1. **LoopBodyLocalInitLowerer** (Phase 191): + - `local temp_digit = digits.indexOf(ch)` を JoinIR に emit + - MethodCall の lowering は既存の式 lowerer に委譲 + +2. **CarrierUpdateLowerer** (Phase 190): + - `result = result * 10 + temp_digit` を NumberAccumulation として emission + - `temp_digit` は UpdateEnv 経由で解決 + +3. **NumberAccumulation emission**: + - Phase 190 の 2-instruction emission そのまま: + ``` + tmp = result * 10 + result = tmp + temp_digit + ``` + +### 6.2 設計原則 + +- **Separation of Concerns**: 正規化 (ComplexAddendNormalizer) と emission (CarrierUpdateLowerer) を分離 +- **Reusability**: 既存の body-local init / NumberAccumulation emission を再利用 +- **Fail-Fast**: 対応できないパターンは明示的エラー + +--- + +## Section 7: Implementation Phases (TBD) + +### Phase 192-impl-A: ComplexAddendNormalizer 実装 + +1. `is_complex_addend_pattern()` 検出ロジック +2. `normalize_complex_addend()` 正規化ロジック +3. temp 変数生成とAST挿入 + +### Phase 192-impl-B: Pattern2 統合 + +1. `can_lower` を `can_lower_with_normalization` に拡張 +2. 正規化後の UpdateExpr で再解析 +3. Unit tests (5+ cases) + +### Phase 192-impl-C: E2E テスト + +1. `phase192_complex_addend_atoi.hako` 作成 +2. `result = result * 10 + digits.indexOf(ch)` パターンで期待値確認 +3. 退行テスト(phase191_*.hako) + +--- + +## Section 8: 制約と Non-Goals + +### 8.1 対応パターン + +**Phase 192 で対応**: +- Addend に simple MethodCall: `v = v * 10 + digits.indexOf(ch)` +- Addend が Variable の場合は Phase 191 で対応済み + +**Phase 192 で非対応(将来拡張)**: +- Nested method call: `v = v * 10 + parser.parse(s.substring(...))` +- Complex binary expression: `v = v * 10 + (a + b * c)` +- Multiple method calls in same update + +### 8.2 Fail-Fast ケース + +- Base が variable: `v = v * base + f(x)` +- LHS が複数回出現: `v = v * 10 + v` +- Method call が base 側: `v = v * getBase() + x` + +--- + +## Appendix A: AST Examples + +### A.1 Before Normalization + +**Source**: +```nyash +result = result * 10 + digits.indexOf(ch) +``` + +**AST**: +``` +Assignment { + target: Variable { name: "result" }, + value: BinaryOp { + operator: Add, + left: BinaryOp { + operator: Mul, + left: Variable { name: "result" }, + right: Literal { value: Integer(10) }, + }, + right: MethodCall { + receiver: Variable { name: "digits" }, + method: "indexOf", + args: [Variable { name: "ch" }], + }, + }, +} +``` + +### A.2 After Normalization + +**Source**: +```nyash +local temp_result_addend = digits.indexOf(ch) +result = result * 10 + temp_result_addend +``` + +**AST**: +``` +[ + LocalDeclaration { + name: "temp_result_addend", + init: Some(MethodCall { + receiver: Variable { name: "digits" }, + method: "indexOf", + args: [Variable { name: "ch" }], + }), + }, + Assignment { + target: Variable { name: "result" }, + value: BinaryOp { + operator: Add, + left: BinaryOp { + operator: Mul, + left: Variable { name: "result" }, + right: Literal { value: Integer(10) }, + }, + right: Variable { name: "temp_result_addend" }, + }, + }, +] +``` + +--- + +## Revision History + +- **2025-12-09**: Initial design document (Section 1-8)