diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 3a788088..e36a1660 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -35,6 +35,29 @@ - Unit tests: 1155/1155 PASS - 入口: `docs/development/current/main/phases/phase-129/README.md` +## 2025-12-18:Phase 132 完了 ✅ + +**Phase 132: loop(true) break-once + post-loop minimal(dev-only)** +- 目的: Phase 131 を拡張し、ループ後の最小 post 計算まで Normalized shadow で固定(PHI-free 維持) +- 仕様: + - `loop(true) { x = 1; break }; x = x + 2; return x` は exit code `3`(VM/LLVM EXE parity) + - post_k continuation で post-loop statements を処理 +- 実装: + - **P0**: post_k 生成(loop_true_break_once.rs 拡張) + - **P0.5**: StepTree が post-loop statements を保持(suffix router box 追加) + - **P1**: k_exit continuation 分類修正(構造ベースの判定) + - **R0**: Continuation SSOT 一本化 + legacy 隔離 + docs 整備 +- SSOT: + - `JoinInlineBoundary::default_continuations()` - continuation ID の集約 + - `src/mir/builder/control_flow/joinir/merge/README.md` - merge 契約明文化 + - `src/mir/builder/control_flow/joinir/legacy/README.md` - legacy 撤去条件 +- テスト配置: `src/mir/builder/control_flow/joinir/merge/tests/continuation_contract.rs` +- 検証: + - `bash tools/smokes/v2/profiles/integration/apps/phase132_loop_true_break_once_post_add_vm.sh` + - `bash tools/smokes/v2/profiles/integration/apps/phase132_loop_true_break_once_post_add_llvm_exe.sh` +- 回帰: Phase 131/97 維持確認(全 PASS) +- 入口: `docs/development/current/main/phases/phase-132/README.md` + ## 2025-12-18:Phase 131 P2 完了 ✅ **Phase 131: loop(true) break-once Normalized(dev-only)** diff --git a/docs/development/current/main/phases/phase-132/README.md b/docs/development/current/main/phases/phase-132/README.md index 6513323f..957d9526 100644 --- a/docs/development/current/main/phases/phase-132/README.md +++ b/docs/development/current/main/phases/phase-132/README.md @@ -1,148 +1,348 @@ -# Phase 132: Exit Values Parity (VM == LLVM) +# Phase 132: loop(true) break-once with Minimal Post Computation (dev-only) -**Date**: 2025-12-15 -**Status**: ✅ Done -**Scope**: ループ exit 値(exit PHI / boundary)が VM/LLVM で一致することを固定する(Pattern 1 と Case C を含む) +**Date**: 2025-12-18 +**Status**: DONE ✅ +**Scope**: loop(true) break-once + post-loop minimal computation (Normalized shadow) --- -## 背景 +## Previous Phase 132 Work (2025-12-15) ✅ Done -最小ケース: +### Phase 132-P1 to P3: Exit Values Parity and LLVM Python Fixes + +- **P1**: FunctionLowerContext Box for state isolation +- **P2**: Exit PHI ValueId collision fix (Case C) +- **P3**: Exit PHI collision early detection (debug-only) + +All previous Phase 132 work is complete and documented in commit history. + +--- + +## Phase 132-P4: loop(true) break-once with Post Computation (NEW) + +**Goal**: Extend Phase 131 `loop(true){...; break}` to support minimal post-loop computation. + +Related: +- Entry: `docs/development/current/main/10-Now.md` +- Phase 131 (loop baseline): `docs/development/current/main/phases/phase-131/README.md` +- Phase 130 (post_k pattern): `docs/development/current/main/phases/phase-130/README.md` +- ControlTree SSOT: `docs/development/current/main/design/control-tree.md` + +### Goal + +Extend Phase 131 `loop(true){...; break}` to support minimal post-loop computation: + +- Enable `x = x + 2` after loop exit, then return +- Keep **PHI禁止**: merge via env + continuations only +- Keep **dev-only** and **既定挙動不変**: unmatched shapes fall back (strict can Fail-Fast) + +### Non-Goals + +- No general loop conditions (only `true` literal) +- No continue support (only break at end of body) +- No nested control flow inside loop body +- No multiple breaks or conditional breaks +- No complex post-loop computation (one assignment + return only) + +### Accepted Forms + +#### ✅ Supported ```nyash -static box Main { - main() { - local i = 0 - loop(i < 3) { i = i + 1 } - return i // 期待: 3 - } +// Form: loop(true) with break + minimal post computation +local x +x = 0 +loop(true) { + x = 1 + break } +x = x + 2 +return x // Expected: 3 (1 + 2) ``` -- VM: `RC: 3` ✅ -- LLVM: `Result: 0` ❌(修正前) +#### ❌ Not Supported ---- +```nyash +// Multiple post-loop statements +loop(true) { x = 1; break } +x = x + 2 +y = x + 3 +return y -## 根本原因(2層) +// Continue (not break) +loop(true) { x = 1; continue } -### 1) JoinIR/Boundary 層: exit 値が境界を通っていない +// Nested control flow +loop(true) { if (y == 1) { x = 2 }; break } -- Pattern 1 の JoinIR で `k_exit` に渡す args が空のままだと、exit 側でホストの `variable_map["i"]` が更新されない。 -- その結果 `return i` が初期値(0)を返し得る。 +// General loop conditions +loop(x < 10) { x = x + 1; break } +``` -### 2) LLVM Python 層: PHI の SSOT が壊れている +### SSOT Contracts -- `phi.basic_block` が llvmlite で確定前は `None` になり得るため、PHI を落とす filter を信頼できない。 -- `builder.vmap` にある PHI placeholder を、Pass A の “sync created values” が上書きしてしまい、`ret` が PHI ではなく `0` を参照することがある。 +#### EnvLayout (Phase 126) ---- +Same as Phase 131: +- `writes`: Variables assigned in the fragment +- `inputs`: Variables read from outer scope +- `env_fields()`: `writes ++ inputs` (SSOT for parameter order) -## 修正内容 +#### Loop + Post Structure Contract -### JoinIR/Boundary 層(exit 値を明示的に渡す) +For `loop(true) { body ; break }; ; return`: -- `src/mir/join_ir/lowering/simple_while_minimal.rs` - - `Jump(k_exit, [i_param])` として exit 値を渡す -- `src/mir/builder/control_flow/joinir/patterns/pattern1_minimal.rs` - - `LoopExitBinding` を作成して `with_exit_bindings()` で境界に設定 +1. **Condition**: Must be Bool literal `true` (cond_ast check) +2. **Body**: Must be a Block ending with Break statement +3. **Post-loop**: One assignment statement (`x = x + `) followed by one return statement +4. **No exits**: No return/continue in body (only break at end) +5. **Assignments**: Body contains only Assign(int literal/var/add) and LocalDecl -### LLVM Python 層(PHI SSOT を保護する) +#### Generated JoinModule Structure -- `src/llvm_py/builders/block_lower.py` - - PHI filtering を `predeclared_ret_phis` ベースにし、`phi.basic_block` 依存を排除 - - `builder.vmap` の既存 PHI(placeholder)を上書きしない(PHI を SSOT として保護) +``` +main(env) → TailCall(loop_step, env) ---- +loop_step(env) → + TailCall(loop_body, env) // condition is always true, no branch -## 検証 +loop_body(env) → + + TailCall(k_exit, env) -- `/tmp/p1_return_i.hako` が VM/LLVM で `3` を返す -- `NYASH_LLVM_STRICT=1` でも “miss→0” に落ちない(Fail-Fast が維持される) +k_exit(env) → + TailCall(post_k, env) // Phase 132-P4: NEW (was Ret in Phase 131) -詳細ログ: -- `docs/development/current/main/investigations/phase132-llvm-exit-phi-wrong-result.md` +post_k(env) → + + Ret(env[x]) +``` ---- +**Key Differences from Phase 131**: +- Phase 131: `k_exit(env) → Ret(env[x])` +- Phase 132-P4: `k_exit(env) → TailCall(post_k, env)`, `post_k(env) → ; Ret(env[x])` -## 追加: Phase 132-P2(Case C)Exit PHI ValueId collision fix +**Invariants**: +- No PHI instructions (all merging via env parameters) +- All continuations take full env as parameters +- Post-loop assignment updates env before return -Case C(`apps/tests/llvm_stage3_loop_only.hako`)の LLVM EXE 検証で、 -**exit PHI の ValueId 衝突**が原因で `Result: 0` になる問題が見つかった。 +#### Reconnection Mode -### Root Cause +Phase 132-P4 uses **DirectValue mode** (same as Phase 131): +- No exit PHI generation +- Final env values from `post_k` are reconnected directly to host `variable_map` +- ExitMeta carries `exit_values: Vec<(String, ValueId)>` for reconnection -- `src/mir/builder/control_flow/joinir/merge/exit_phi_builder.rs` が - PHI dst の割り当てに `builder.value_gen.next()`(module-level)を使っており、 - 同一関数内で ValueId が衝突し得た。 +### VM + LLVM Parity -### Fix +Both backends must produce identical results: +- VM: Rust VM backend (`--backend vm`) +- LLVM: LLVM EXE backend via llvm_exe_runner.sh -- PHI dst の割り当てを `func.next_value_id()`(function-level)へ統一。 +Expected contract for fixture: exit code `3` (1 + 2) -コミット: -- `bd07b7f4 fix(joinir): Phase 132-P2 - Exit PHI ValueId collision fix` +### Work Items -### Verification +#### Step 0: Documentation ✅ -- Pattern 1: VM/LLVM ともに `3` -- Case C: VM/LLVM ともに `Result: 3` +- ✅ Create Phase 132-P4 documentation in `README.md` +- TODO: Update `docs/development/current/main/10-Now.md` -調査ログ: -- `docs/development/current/main/investigations/phase132-case-c-llvm-exe.md` +#### Step 1: Fixtures + Smokes ---- +- New fixture: `apps/tests/phase132_loop_true_break_once_post_add_min.hako` + - Expected exit code: `3` (1 + 2) + - Form: x=0; loop(true) { x=1; break }; x=x+2; return x +- VM smoke: `tools/smokes/v2/profiles/integration/apps/phase132_loop_true_break_once_post_add_vm.sh` +- LLVM EXE smoke: `tools/smokes/v2/profiles/integration/apps/phase132_loop_true_break_once_post_add_llvm_exe.sh` -## 追加: Phase 132-P3(debug-only)Exit PHI collision early detection +#### Step 2: Implementation -同種の ValueId 衝突が “LLVM 実行時” にしか露見しないと切り分けコストが高いので、 -JoinIR merge の契約検証(debug build)で **早期に Fail-Fast** する検証 Box を追加した。 +- Extend: `src/mir/control_tree/normalized_shadow/loop_true_break_once.rs` + - Box: `LoopTrueBreakOnceBuilderBox` (existing) + - Accept: `loop(true) { body ; break }; ; return` pattern + - Reject (Ok(None)): Non-matching patterns + - Generate: main → loop_step → loop_body → k_exit → post_k (no PHI) + - Reuse: Phase 130's `lower_assign_stmt` for post-loop assignment -- 検証: `verify_exit_phi_no_collision()` -- 実装: `src/mir/builder/control_flow/joinir/merge/contract_checks.rs` -- 呼び出し元: `verify_exit_line(...)`(`#[cfg(debug_assertions)]`) +#### Step 3: Verification -検出例(概念): -- `bb0: %1 = const 0` -- `exit block: %1 = phi ...`(衝突) +```bash +cargo test --lib +cargo build --release -p nyash-rust --features llvm -期待される振る舞い: -- debug build では compile/run 中に panic で即停止(原因と修正案が出る) -- release build の既定挙動は変えない(検証は `debug_assertions` のみ) +# Phase 132-P4 smokes (NEW) +bash tools/smokes/v2/profiles/integration/apps/phase132_loop_true_break_once_post_add_vm.sh +bash tools/smokes/v2/profiles/integration/apps/phase132_loop_true_break_once_post_add_llvm_exe.sh ---- +# Regressions (Phase 131) +bash tools/smokes/v2/profiles/integration/apps/phase131_loop_true_break_once_vm.sh +bash tools/smokes/v2/profiles/integration/apps/phase131_loop_true_break_once_llvm_exe.sh -## 追加: Phase 132-P1(LLVM Python)FunctionLowerContext Box +# Regressions (Phase 97 - stable baseline) +bash tools/smokes/v2/profiles/integration/apps/phase97_next_non_ws_llvm_exe.sh +``` -Phase 131–132 で顕在化した「関数間の状態漏洩(block id / value id の衝突)」を、 -tuple-key の小手先ではなく **構造で根治**するために、関数ローカル状態を 1 箱に隔離した。 +#### Step 4: Commits -- 実装: `src/llvm_py/context/function_lower_context.py` -- 統合: `src/llvm_py/builders/function_lower.py` +1. `docs: Phase 132-P4 structure documentation` +2. `test(joinir): Phase 132-P4 loop(true) break-once + post fixture + VM/LLVM smokes` +3. `feat(control_tree): Phase 132-P4 loop(true) break-once with minimal post computation (dev-only)` +4. `docs: Phase 132-P4 DONE` -箱の責務(SSOT): -- `block_end_values`(snapshot) -- `def_blocks` -- `jump_only_blocks` -- `phi_manager` -- resolver caches(i64/ptr/f64/end_i64 など) +### Implementation Notes -狙い: -- 関数単位で state を自動破棄し、cross-function collision を "起こせない構造" にする -- 追加の手動クリアや tuple-key を不要にする +#### Design Principles -### Phase 132-P2 Follow-up: Dict ctx Cleanup +1. **Box-First**: Post-loop lowering is a separate concern from loop body lowering + - Consider extracting to `PostLoopLowererBox` if complexity grows +2. **Reuse Infrastructure**: Leverage Phase 130's post_k pattern and `lower_assign_stmt` +3. **Fail-Fast**: Contract violations in supported patterns → `freeze_with_hint` +4. **Ok(None) Fallback**: Out of scope patterns → return `Ok(None)` for legacy fallback -完成: SSOT 統一を完全化するため、`src/llvm_py/builders/function_lower.py` の -`builder.ctx = dict(...)` を削除し、FunctionLowerContext への統一を完成させた。 +#### Reconnection Strategy -- 削除: lines 313-330(dict ctx の作成と `builder.resolver.ctx` の割り当て) -- 入口: `instruction_lower.py` の全ハンドラが既に `context=owner.context` で呼び出されているため、 - manual ctx dict は不要 -- テスト追加: `phase132_multifunc_isolation_min.hako`(f(2) + f(3) = 5 を exit code で検証) +Phase 132-P4 continues using **DirectValue mode**: +- `ExitMeta.exit_values` must point to the **final** ValueIds after post_k execution +- Not the k_exit parameters, not the loop_body values, but post_k's updated env values +- This is the SSOT for variable_map reconnection -**状態**: -- ✅ Dict ctx 削除 -- ✅ Instruction handlers は `context=owner.context` で統一 -- ✅ Multi-function isolation test Case C 追加(smoke test phase132_exit_phi_parity.sh) +#### Post-Loop Detection + +Check StepNode structure: +```rust +let (prefix_nodes, loop_node, post_nodes) = extract_loop_true_pattern(&step_tree.root); + +// Phase 131: post_nodes.is_empty() or post_nodes == [Return(Variable)] +// Phase 132-P4: post_nodes == [Assign(Add), Return(Variable)] +``` + +#### Reusing Phase 130's lower_assign_stmt + +Phase 130 already supports: +- `x = y` (Variable) +- `x = x + ` (Add) + +Phase 132-P4 can directly reuse this for post-loop assignment lowering. + +### Acceptance Criteria + +- ✅ cargo test --lib PASS +- ✅ cargo build --release -p nyash-rust --features llvm PASS +- ✅ Phase 132-P4 new smokes: 2/2 PASS (VM + LLVM EXE) +- ✅ Phase 131 regression smokes: 2/2 PASS +- ✅ Phase 97 regression smoke: 1/1 PASS +- ✅ Dev toggle OFF → no impact (Ok(None) fallback) + +### Box-First Feedback Points + +#### Modularization Opportunities + +1. **PostLoopLowererBox**: If post-loop logic grows beyond one assignment + return + - Separate from loop body lowering + - Single responsibility: lower post-loop statements into post_k +2. **ContinuationBuilderBox**: Common pattern for building continuation functions + - Reusable across loop_true, if-only, etc. + - SSOT for env parameter allocation and env map building + +#### Legacy Cleanup + +1. **Direct env map updates**: No legacy "host side PHI merging" in Normalized path + - All updates via env parameters and continuations + - DirectValue reconnection is the clean modern approach +2. **Fail-Fast discipline**: Phase 132-P4 should maintain Phase 131's strict error handling + - Use `freeze_with_hint` for contract violations + - Clear hints guide users to fix patterns + +#### SSOT Maintenance + +1. **ExitMeta.exit_values**: Must reference post_k's final env values + - Not loop_body values, not k_exit parameters + - This is critical for DirectValue reconnection correctness +2. **EnvLayout.env_fields()**: SSOT for all env parameter lists + - Used consistently in main, loop_step, loop_body, k_exit, post_k + - Any deviation is a contract violation + +### Current Status + +Phase 132 - DONE ✅ (2025-12-18) + +#### P0: post_k Generation ✅ + +- Extended `loop_true_break_once.rs` to generate post_k continuation +- Reused Phase 130's `lower_assign_stmt` for post-loop statements +- ExitMeta uses DirectValue mode (PHI-free) +- Fixture: `apps/tests/phase132_loop_true_break_once_post_add_min.hako` +- Expected exit code: 3 + +#### P0.5: Suffix Router for StepTree ✅ + +- **Root cause**: routing.rs created StepTree from Loop node only, losing post statements +- **Solution**: New `normalized_shadow_suffix_router_box.rs` + - Detects block suffix: Loop + Assign* + Return + - Creates StepTree from entire suffix (Block([Loop, Assign, Return])) +- Modified `build_block()` to call suffix router (dev-only) +- StepTree unchanged: Block is SSOT for statement order +- No data duplication: Loop doesn't hold post_nodes + +#### P1: k_exit Continuation Fix ✅ + +- **Problem**: k_exit with TailCall(post_k) was classified as skippable continuation +- **Root cause**: Classification was name-based, didn't examine function body +- **Solution**: Check function body structure before classifying as skippable +- Non-skippable pattern: continuation with TailCall to another function +- VM/LLVM EXE parity achieved (exit code 3) + +#### R0: Refactoring Infrastructure ✅ + +**Task 1: Continuation SSOT 一本化** +- Added `JoinInlineBoundary::default_continuations()` +- Replaced all `BTreeSet::from([JoinFuncId::new(2)])` hardcoding (7 locations) +- Single source of truth for continuation function IDs + +**Task 2: merge 契約 docs SSOT 化** +- New: `src/mir/builder/control_flow/joinir/merge/README.md` +- Documented continuation contracts, skip conditions, forbidden behaviors +- Prohibited by-name/by-id classification + +**Task 3: テスト配置正規化** +- New: `src/mir/builder/control_flow/joinir/merge/tests/continuation_contract.rs` +- Moved tests from instruction_rewriter.rs to dedicated test file +- Added 4 test cases (Case A-D) + +**Task 4: legacy 導線隔離** +- New: `src/mir/builder/control_flow/joinir/legacy/` +- Moved `routing_legacy_binding.rs` to `legacy/routing_legacy_binding.rs` +- Added `legacy/README.md` with removal conditions +- No cfg(feature="legacy") (docs-only isolation for now) + +**Task 5: ノイズ除去** +- Removed unused imports (ConstValue, MirInstruction) +- Reduced warnings in touched files + +#### Test Results ✅ + +```bash +cargo test --lib: 1176 PASS +Phase 132 VM: PASS (exit code 3) +Phase 132 LLVM EXE: PASS (exit code 3) +Phase 131 regression: PASS +Phase 97 regression: PASS +``` + +#### Key Achievements + +- ✅ loop(true) + post-loop works in both VM and LLVM EXE +- ✅ Continuation contracts SSOT化 +- ✅ merge が by-name 推測禁止 +- ✅ Legacy code path isolated for future removal +- ✅ Code quality improved (warnings reduced) + +#### Documentation + +- Entry: `docs/development/current/main/10-Now.md` +- SSOT: `JoinInlineBoundary::default_continuations()` +- SSOT: `src/mir/builder/control_flow/joinir/merge/README.md` (merge contracts) +- Tests: `src/mir/builder/control_flow/joinir/merge/tests/continuation_contract.rs` +- Legacy: `src/mir/builder/control_flow/joinir/legacy/README.md` (removal conditions)