From c6ffc06660b41b7afb3025e0a4db5d07cfaae40e Mon Sep 17 00:00:00 2001 From: tomoaki Date: Sat, 27 Dec 2025 06:22:59 +0900 Subject: [PATCH] feat(joinir): Phase 188.3-P3.1,3.2 - AST extraction & validation helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 3-1 完了: extract_inner_loop_ast() 実装 - 外側 loop body から内側 loop を抽出 - 正確に 1 つの inner loop を検証 - 0 個 or 2+ 個の場合は明示エラー (Fail-Fast) Phase 3-2 完了: validate_strict_mode() プレースホルダー - 現在は大半の検証を is_pattern6_lowerable() で実施 - 将来の strict mode チェック用の足場 ドキュメント追加: - P1-INSTRUCTIONS.md: Phase 3-3 実装指示書 (4関数モデル詳細) - README.md: max_loop_depth 定義の明確化 - 10-Now.md: Phase 188.3 方針の整合性維持 次タスク: Phase 3-3 (Continuation 生成) - P1-INSTRUCTIONS.md 参照 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- docs/development/current/main/10-Now.md | 2 +- .../phases/phase-188.3/P1-INSTRUCTIONS.md | 137 ++++++++++++++++++ .../current/main/phases/phase-188.3/README.md | 75 +++++++++- .../patterns/pattern6_nested_minimal.rs | 75 +++++++++- 4 files changed, 280 insertions(+), 9 deletions(-) create mode 100644 docs/development/current/main/phases/phase-188.3/P1-INSTRUCTIONS.md diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index b50e75af..99c738c1 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -6,7 +6,7 @@ - StepTreeの `max_loop_depth` を SSOT に採用(Option A) - strict mode で depth > 2 を明示エラー化(Fail-Fast) - quick 154/154 PASS、integration selfhost FAIL=0 維持 -- 次: `docs/development/current/main/phases/phase-188.3/README.md`(depth=2 を JoinIR lowering で通す / 変数はread-only capture→write-backは次フェーズ) +- 次: `docs/development/current/main/phases/phase-188.3/README.md`(depth=2 を JoinIR lowering で通す / Phase 188.3は“最小のwrite-back”をcarrierとして明示、一般化は次フェーズ) **2025-12-27: Phase S0.1 完了** ✅ - integration selfhost を「落ちない状態」に収束(FAIL=0) diff --git a/docs/development/current/main/phases/phase-188.3/P1-INSTRUCTIONS.md b/docs/development/current/main/phases/phase-188.3/P1-INSTRUCTIONS.md new file mode 100644 index 00000000..899d9082 --- /dev/null +++ b/docs/development/current/main/phases/phase-188.3/P1-INSTRUCTIONS.md @@ -0,0 +1,137 @@ +# Phase 188.3 P1: Pattern6(NestedLoopMinimal)lowering を “実装済み” にする + +**Date**: 2025-12-27 +**Scope**: Pattern6 の lowering 実装(fixture を PASS) +**Non-goals**: 汎用 nested loop / break/continue / 多段ネスト / 多重 inner loop + +--- + +## ✅ Success Criteria + +- `apps/tests/phase1883_nested_minimal.hako` が `--backend vm` で **exit=9** +- `./tools/smokes/v2/run.sh --profile quick` が **154/154 PASS** +- integration selfhost が **FAIL=0 維持** +- Pattern6 を選んだ後に **silent fallback しない**(Fail-Fast) + +--- + +## SSOT / Constraints + +- ネスト深さ SSOT: `StepTreeFeatures.max_loop_depth` +- Pattern6 選択 SSOT: `src/mir/builder/control_flow/joinir/routing.rs::choose_pattern_kind()` +- Phase 188.2: strict では depth > 2 を明示エラー +- 本タスクで触るのは “lowering stub を実装” のみ + +--- + +## Fixture(目標) + +`apps/tests/phase1883_nested_minimal.hako` を SSOT とする(Add/Compare のみ)。 + +--- + +## 実装方針(重要) + +### 1) `sum` は carrier として渡す(グローバル禁止) + +inner loop が outer の `sum` を更新しているので、**JoinIR では `sum` を引数で運び、`k_inner_exit` で outer に戻す**。 + +- `sum` を “グローバル変数” 扱いにしてはいけない(箱理論と Fail-Fast 的にも事故る) + +### 2) merge の「loop_step 選定」を壊さない + +JoinIR merge は「main でも continuation でもない関数」を 1つ選んで “loop header” とみなす。 +nested loop では `inner_step` が混ざるので、**`inner_step` / `k_inner_exit` を boundary の `continuation_func_ids` に入れて除外**する。 + +これをしないと、merge が誤って `inner_step` を loop header として選び、PHI/exit binding が壊れる。 + +--- + +## 実装タスク(順番) + +### Task A: lowering を実装する + +対象: `src/mir/builder/control_flow/joinir/patterns/pattern6_nested_minimal.rs` + +やること: +- `lower()` で `Err` している stub を置き換えて、JoinIR pipeline を呼ぶ +- 最小形だけ対応し、外れた形は **Pattern6 選択前に落とす**(`is_pattern6_lowerable()` 側を強化)か、ここで明示エラー + +推奨構成(Pattern1 と同じ流儀): +- JoinIR 生成は `src/mir/join_ir/lowering/nested_loop_minimal.rs`(新規)に切り出す +- builder 側は「context 作成 → JoinModule → boundary → conversion pipeline」のみ + +(ただし PoC なので builder 側に直書きでも可。差分は最小に。) + +### Task B: JoinIR(nested minimal)を生成する + +参考: `src/mir/join_ir/lowering/simple_while_minimal.rs` + +最小形の JoinIR 関数構成(推奨): + +- `main(i0, sum0)`: + - `Call(loop_step, [i0, sum0])` + - `Ret 0`(statement-position) +- `loop_step(i, sum)`(outer): + - `exit_cond = !(i < N_outer)` + - `Jump(k_exit, [sum], cond=exit_cond)` + - `Call(inner_step, [j0, i, sum])` +- `inner_step(j, i_outer, sum)`: + - `exit_cond = !(j < N_inner)` + - `Jump(k_inner_exit, [i_outer, sum], cond=exit_cond)` + - `sum_next = sum + 1` + - `j_next = j + 1` + - `Call(inner_step, [j_next, i_outer, sum_next])` +- `k_inner_exit(i, sum)`: + - `i_next = i + 1` + - `Call(loop_step, [i_next, sum])` +- `k_exit(sum)`: + - `Ret sum` + +命名: +- `k_exit` は canonical name: `src/mir/join_ir/lowering/canonical_names.rs::K_EXIT` +- `loop_step` は canonical name: `...::LOOP_STEP` +- `inner_step` / `k_inner_exit` は Phase 188.3 で追加(文字列でよいが、可能なら canonical_names に追加) + +### Task C: boundary(exit binding + continuation funcs)を正しく構築する + +参考: `src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs` + +必須: +- join_inputs/host_inputs は **JoinModule.entry.params と同順・同数** +- `exit_bindings` は `sum` を host slot に reconnect + - join_exit_value は `k_exit` の param(Pattern1 と同じく `join_module.require_function("k_exit").params[0]` から取得) +- `continuation_func_ids` に以下を含める: + - `k_exit` + - `k_inner_exit` + - `inner_step` + +### Task D: integration smoke を追加する(exit code SSOT) + +新規: +- `tools/smokes/v2/profiles/integration/joinir/phase1883_nested_minimal_vm.sh` + - `apps/tests/phase1883_nested_minimal.hako` を実行し、exit code == 9 で PASS + - stdout 比較はしない + +注意: +- `tools/smokes/v2` は `manifest.txt` 方式ではない(`find` ベース) +- 既存の helper を使う: `source tools/smokes/v2/lib/test_runner.sh` + +--- + +## 検証手順 + +1. `cargo build --release` +2. `./target/release/hakorune --backend vm apps/tests/phase1883_nested_minimal.hako`(exit=9) +3. `./tools/smokes/v2/run.sh --profile integration --filter "phase1883_nested_minimal"` +4. `./tools/smokes/v2/run.sh --profile quick`(154/154 PASS) +5. `./tools/smokes/v2/run.sh --profile integration --filter "selfhost_"`(FAIL=0) + +--- + +## 追加の注意(Fail-Fast) + +- Pattern6 が選ばれたあとに `Ok(None)` で他パターンに流すのは禁止(silent fallback) +- “選ぶ前に落とす” が最も安全: + - `is_pattern6_lowerable()` を「lowering が確実に通る形だけ true」に強化する + diff --git a/docs/development/current/main/phases/phase-188.3/README.md b/docs/development/current/main/phases/phase-188.3/README.md index 80eabb9d..92e07f11 100644 --- a/docs/development/current/main/phases/phase-188.3/README.md +++ b/docs/development/current/main/phases/phase-188.3/README.md @@ -1,7 +1,7 @@ # Phase 188.3: Nested loop lowering (1-level) — make Pattern 6 real -**Date**: TBD -**Status**: Planning (docs-first) +**Date**: 2025-12-27 +**Status**: In progress (Phase 2 done: selection / Phase 3: lowering) **Prereq**: Phase 188.2 Option A is complete (StepTree depth SSOT + strict Fail-Fast) --- @@ -15,6 +15,20 @@ --- +## Definition: `max_loop_depth` (SSOT) + +`StepTreeFeatures.max_loop_depth` は「その関数(or 評価対象ブロック)の中で、最も深い `loop{}` ネストの深さ」を表す。 + +- loop が無い: `0`(※StepTree実装に依存。現状の運用では “loopがある前提”で扱う) +- loop が 1 段: `1` +- loop の中に loop が 1 つ: `2` +- `loop{ loop{ loop{ ... } } }`: `3` + +Phase 188.2 の strict ガードは「depth > 2 を明示エラー」にしている。 +これは言語仕様の制限ではなく、「JoinIR lowering の実装範囲(compiler capability)」の制限。 + +--- + ## Scope (minimal) 対応するのは “NestedLoop Minimal” の 1形だけに限定する。 @@ -28,6 +42,20 @@ --- +## Current Code Status (reality) + +Phase 188.3 は “選択ロジック(Pattern6選定)” まで実装済みで、lowering が未実装(stub)な状態。 + +- Selection SSOT: `src/mir/builder/control_flow/joinir/routing.rs` の `choose_pattern_kind()` + - cheap check → StepTree → AST validation + - `max_loop_depth == 2` かつ “Pattern6 lowerable” のときだけ `Pattern6NestedLoopMinimal` を返す +- Lowering stub: `src/mir/builder/control_flow/joinir/patterns/pattern6_nested_minimal.rs` + - 現在は `Err("[Pattern6] ... not yet implemented")` で Fail-Fast + +この README のゴールは、stub を “実装済み” にして fixture を通すこと。 + +--- + ## SSOT (what to rely on) - nesting depth SSOT: `StepTreeFeatures.max_loop_depth` @@ -48,10 +76,12 @@ Nyash の変数スコープ方針に合わせて、nested loop lowering でも Phase 188.3 では段階的に進める: -- **P188.3 (minimal)**: outer 変数の **read-only capture** を許す(inner から outer を読む) -- **P188.4+ (generalize)**: outer 変数の **write-back** を対応する(inner で outer に代入した値を `k_inner_exit(...)` で戻す) +- **P188.3 (minimal)**: “最小の write-back” を **明示的な carrier** として許す + - inner loop が outer 変数を更新する場合、その変数は **carrier として引数で受けて、`k_inner_exit(...)` で返す** + - 対応範囲は最小(fixtureが要求する 1 carrier 程度)に限定する +- **P188.4+ (generalize)**: write-back を一般化(複数 carrier / 代入形 / break/continue を含む形) -この分離により、PHI/exit binding の複雑さを Phase 188.3 に持ち込まずに済む。 +この分離により、write-back の複雑さ(複数 carrier / break / continue / PHI の再接続)を Phase 188.3 に持ち込まずに済む。 --- @@ -65,6 +95,35 @@ Nested loop は JoinIR の「tail recursion + continuation」を再帰的に合 この `k_inner_exit` がスコープ境界として働くので、将来の write-back もここに集約できる。 +### Minimal function graph (recommended) + +Phase 188.3 の最小形は「outer の loop_step の中で inner の loop_step を呼び、inner が終わったら k_inner_exit で outer に戻る」。 + +推奨の JoinIR 関数群(概念): + +- `main(i0, sum0)`: + - `Call(loop_step, [i0, sum0])` + - `Ret 0`(statement-position loop の場合) +- `loop_step(i, sum)`(outer の step。canonical name は `loop_step` に寄せる): + - `exit_cond = !(i < N_outer)` + - `Jump(k_exit, [sum], cond=exit_cond)` + - `Call(inner_step, [j0, i, sum])` +- `inner_step(j, i_outer, sum)`: + - `exit_cond = !(j < N_inner)` + - `Jump(k_inner_exit, [i_outer, sum], cond=exit_cond)` + - `sum_next = sum + 1`(fixture の最小形) + - `j_next = j + 1` + - `Call(inner_step, [j_next, i_outer, sum_next])` +- `k_inner_exit(i, sum)`: + - `i_next = i + 1` + - `Call(loop_step, [i_next, sum])` +- `k_exit(sum)`(canonical name `k_exit`): + - `Ret sum` + +重要: +- **carrier はグローバル扱いしない**。`sum` は引数で運んで戻す。 +- merge 側の “loop_step 関数の選定” を壊さないため、`inner_step` と `k_inner_exit` は boundary の `continuation_func_ids` に含めて除外する(詳細は指示書)。 + --- ## Deliverables @@ -91,6 +150,12 @@ Nested loop は JoinIR の「tail recursion + continuation」を再帰的に合 --- +## Instructions (handoff) + +実装者(Claude Code)向けの手順書は `docs/development/current/main/phases/phase-188.3/P1-INSTRUCTIONS.md` を参照。 + +--- + ## Next (schedule) - **Phase 188.3**: depth=2 の最小形を “確実に通す” + PoC fixture を smoke 固定 diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern6_nested_minimal.rs b/src/mir/builder/control_flow/joinir/patterns/pattern6_nested_minimal.rs index 8c91eac6..e64296d2 100644 --- a/src/mir/builder/control_flow/joinir/patterns/pattern6_nested_minimal.rs +++ b/src/mir/builder/control_flow/joinir/patterns/pattern6_nested_minimal.rs @@ -22,11 +22,74 @@ //! - Generate k_inner_exit (bridges to outer continuation) //! - Wire continuations +use crate::ast::ASTNode; use crate::mir::builder::control_flow::joinir::patterns::LoopPatternContext; use crate::mir::builder::MirBuilder; use crate::mir::loop_pattern_detection::LoopPatternKind; use crate::mir::ValueId; +/// Phase 188.3: Validate strict mode constraints +/// +/// Requirements (most already checked in is_pattern6_lowerable): +/// - Inner loop has no break (checked in routing.rs) +/// - Inner loop has no continue (checked in routing.rs) +/// - Outer loop has no break (checked in routing.rs) +/// - Outer loop has no continue (checked in routing.rs) +/// +/// Additional checks (Phase 188.4+): +/// - Outer variable write-back detection (TODO: add to is_pattern6_lowerable) +/// +/// This function serves as final safety check before lowering. +/// If validation fails → Fail-Fast (not silent fallback). +fn validate_strict_mode(_inner_ast: &ASTNode, _ctx: &LoopPatternContext) -> Result<(), String> { + // Phase 188.3: Most validation already done in is_pattern6_lowerable() + // This is a placeholder for future strict mode checks + + // Future Phase 188.4+ checks: + // - Detect outer variable write-back in inner loop body + // - Validate carrier compatibility + // - Check for unsupported AST patterns + + Ok(()) +} + +/// Phase 188.3: Extract inner loop AST from outer loop body +/// +/// Requirements: +/// - Outer body must contain exactly 1 Loop node +/// - 0 loops → Error (shouldn't happen after Pattern6 selection) +/// - 2+ loops → Error (multiple inner loops not supported) +/// +/// Returns reference to inner loop ASTNode +fn extract_inner_loop_ast<'a>(ctx: &'a LoopPatternContext) -> Result<&'a ASTNode, String> { + // Find all Loop nodes in outer body + let mut inner_loop: Option<&ASTNode> = None; + let mut loop_count = 0; + + for stmt in ctx.body.iter() { + if matches!(stmt, ASTNode::Loop { .. }) { + loop_count += 1; + if inner_loop.is_none() { + inner_loop = Some(stmt); + } + } + } + + // Validate exactly 1 inner loop + match loop_count { + 0 => Err( + "[Pattern6/extract] No inner loop found (should not happen after Pattern6 selection)" + .to_string(), + ), + 1 => Ok(inner_loop.unwrap()), // Safe: loop_count == 1 guarantees Some + _ => Err(format!( + "[Pattern6/extract] Multiple inner loops ({}) not supported. \ + Phase 188.3 supports exactly 1 inner loop only.", + loop_count + )), + } +} + /// Detect if this context can be lowered as Pattern6 (NestedLoopMinimal) /// /// Pattern selection happens in choose_pattern_kind() (SSOT). @@ -40,9 +103,15 @@ pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &LoopPatternContext) -> bool /// Phase 188.3: Full implementation with continuation generation pub(crate) fn lower( _builder: &mut MirBuilder, - _ctx: &LoopPatternContext, + ctx: &LoopPatternContext, ) -> Result, String> { - // Phase 188.3 stub - full implementation in Phase 3-3 + // Phase 3-1: Extract inner loop AST (validate exactly 1 inner loop) + let inner_ast = extract_inner_loop_ast(ctx)?; + + // Phase 3-2: Validate strict mode constraints (Fail-Fast) + validate_strict_mode(inner_ast, ctx)?; + + // Phase 3-3 stub - full implementation next // TODO: Implement continuation generation (outer_step, inner_step, k_inner_exit) - Err("[Pattern6] Nested loop lowering not yet implemented (Phase 188.3 stub)".to_string()) + Err("[Pattern6] Nested loop lowering not yet implemented (Phase 3-3 pending)".to_string()) }