feat(joinir): Phase 188.3-P3.1,3.2 - AST extraction & validation helpers

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 <noreply@anthropic.com>
This commit is contained in:
2025-12-27 06:22:59 +09:00
parent a2c5fd90fe
commit c6ffc06660
4 changed files with 280 additions and 9 deletions

View File

@ -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

View File

@ -0,0 +1,137 @@
# Phase 188.3 P1: Pattern6NestedLoopMinimallowering を “実装済み” にする
**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: JoinIRnested 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: boundaryexit 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` の paramPattern1 と同じく `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」に強化する

View File

@ -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 固定

View File

@ -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<Option<ValueId>, 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())
}