diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index e6b1bc57..638c6ac8 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -8,56 +8,38 @@ Scope: Repo root の旧リンク互換。現行の入口は `docs/development/cu --- -## Handoff (was blocking; now unblocked) +## Handoff (current) -### 状況 +### 状況(SSOT) -- JoinIR/Phase 131 系(Normalized shadow + DirectValue exit)が進行中だが、**LLVM EXE smoke が Rust のビルド段階でブロック**していた。 -- エラー: `Invalid cross-device link (os error 18)`(`cargo build` が `.rmeta/.rlib` を書き出すときに落ちる) -- 2025-12-18: `wsl --shutdown` による WSL 再起動後、`cargo build`/LLVM build/該当 smokes が復活(下のコマンドは PASS)。 +- 現行の入口: `docs/development/current/main/10-Now.md` +- 次の候補: `docs/development/current/main/30-Backlog.md` -### 症状(再現コマンド) +### 直近の道筋(JoinIR / Normalized) -- `cargo build -p nyash-rust` -- `cargo build --release -p nyash-rust --features llvm` -- LLVM EXE smokes(内部で release build するので同様に失敗) - - `bash tools/smokes/v2/profiles/integration/apps/phase131_loop_true_break_once_llvm_exe.sh` - - `bash tools/smokes/v2/profiles/integration/apps/phase97_next_non_ws_llvm_exe.sh` +- Phase 139: if-only `post_k` の return lowering を `ReturnValueLowererBox` に統一(DONE) + - `docs/development/current/main/phases/phase-139/README.md` +- Phase 140: `NormalizedExprLowererBox` 初版(pure expression のみ)(DONE) + - SSOT: `docs/development/current/main/design/normalized-expr-lowering.md` + - `docs/development/current/main/phases/phase-140/README.md` +- Phase 141 P0: impure 拡張点(contract)を SSOT 化(Call/MethodCall はまだ out-of-scope)(DONE) + - `docs/development/current/main/phases/phase-141/README.md` +- Phase 141 P1: “既知 intrinsic だけ” を許可して段階投入(DONE) + - `docs/development/current/main/phases/phase-141/README.md` +- Phase 141 P1.5: known intrinsic registry + available_inputs 3-source merge + diagnostics(DONE) + - `docs/development/current/main/phases/phase-141/README.md` +- Phase 142-loopstmt P0: 正規化単位を statement(loop 1個)へ寄せる(DONE) + - `docs/development/current/main/phases/phase-142-loopstmt/README.md` +- Phase 142-loopstmt P1: LLVM EXE smoke(同 fixture)を追加(planned) + - `docs/development/current/main/phases/phase-142-loopstmt/README.md` +- Phase 141 P2+: Call/MethodCall(effects + typing)を分離して段階投入 + - Historical context: `docs/development/current/main/investigations/joinir-generalization-study.md` +- Phase 143-loopvocab: StepTree の語彙拡張(loop 内 if/break/continue を「新パターン追加」ではなく「語彙追加」で吸収) + - 入口(planned): `docs/development/current/main/phases/phase-143-loopvocab/README.md` -### 影響しない(通るもの) +## Resolved (historical) -- `cargo test --lib` は PASS -- VM smokes は PASS(例) - - `bash tools/smokes/v2/profiles/integration/apps/phase131_loop_true_break_once_vm.sh` - - `bash tools/smokes/v2/profiles/integration/apps/phase130_if_only_post_if_add_vm.sh` +### WSL EXDEV / cargo build failure (resolved) -### 根本原因メモ(環境) - -- WSL2 上で、`O_TMPFILE` で作った一時ファイルを `/proc/self/fd/` 経由で `link` すると **EXDEV が返る**挙動を確認。 - - Rustc/cargo が内部で類似の「tmpfile → persist(link/rename)」を使っている可能性が高い。 -- さらに、この環境では `~/.rustup/tmp` と `~/.cargo` が書き込み不可に見える(immutable/perm issue の疑い)。 -- DNS も一時的に死んでおり、`rustup toolchain install` 等の回避策が取れない状態だった(`Temporary failure in name resolution`)。 - -### 対処(ユーザー側での復旧手順) - -1. WSL を完全再起動: - - Windows 側で `wsl --shutdown` → その後 WSL を起動し直す -2. 再起動後にビルドが復活しているか確認: - - `cargo build -p nyash-rust` - - `cargo build --release -p nyash-rust --features llvm` -3. 復活したら、VM+LLVM EXE の最終確認(integration): - - `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` - - `bash tools/smokes/v2/profiles/integration/apps/phase97_next_non_ws_llvm_exe.sh` - -### 補足(再発時のワークアラウンド) - -- `tools/build_llvm.sh` は EXDEV を避けるため、`TMPDIR` を `target/...` 配下へ寄せる(同一 FS 内での artifact finalize を狙う)。 - -### 注意(作業ツリー) - -- 現在 `git status` は clean ではない(Phase 131 関連の変更が多数 + 未tracked docs/ファイルあり)。 - - 未tracked: `docs/development/current/main/phases/phase-131/p1.5-implementation-guide.md` - - 未tracked: `docs/development/current/main/phases/phase-131/p1.5-option-b-analysis.md` - - 未tracked: `docs/development/current/main/phases/phase-131/p1.5-root-cause-summary.md` - - 未tracked: `src/mir/control_tree/normalized_shadow/exit_reconnector.rs` +- 2025-12-18: `Invalid cross-device link (os error 18)` により `cargo build` が失敗する事象があったが、`wsl --shutdown` 再起動後に復旧。 +- 再発時のワークアラウンド: `tools/build_llvm.sh` は EXDEV を避けるため `TMPDIR` を `target/...` 配下へ寄せる。 diff --git a/apps/tests/phase139_if_only_post_k_return_add_min.hako b/apps/tests/phase139_if_only_post_k_return_add_min.hako new file mode 100644 index 00000000..d8753f75 --- /dev/null +++ b/apps/tests/phase139_if_only_post_k_return_add_min.hako @@ -0,0 +1,29 @@ +// Phase 139 P0: if-only post_k return add minimal fixture +// +// Purpose: Ensure post_if_post_k.rs delegates return lowering to ReturnValueLowererBox +// Pattern: +// x = 1 +// flag = 1 +// if flag == 1 { x = 2 } else { x = 1 } +// return x + 2 +// Expected: exit code 4 +// +// Notes: +// - Requires explicit else (Phase 129-C contract for post_if_post_k) +// - Dev-only Normalized shadow path (NYASH_JOINIR_DEV=1 + HAKO_JOINIR_STRICT=1) + +static box Main { + main() { + local x + local flag + x = 1 + flag = 1 + if flag == 1 { + x = 2 + } else { + x = 1 + } + return x + 2 + } +} + diff --git a/apps/tests/phase141_p1_if_only_post_k_return_length_min.hako b/apps/tests/phase141_p1_if_only_post_k_return_length_min.hako new file mode 100644 index 00000000..8f5a3f95 --- /dev/null +++ b/apps/tests/phase141_p1_if_only_post_k_return_length_min.hako @@ -0,0 +1,27 @@ +// Phase 141 P1: ExprLowerer known intrinsic (length0) minimal fixture (if-only) +// +// Purpose: Exercise MethodCall lowering via `ReturnValueLowererBox` without loop-boundary merge. +// +// Pattern: +// s = "abc" +// flag = 1 +// if flag == 1 { s = s } else { s = s } +// return s.length() +// +// Expected: exit code 3 +// Notes: Dev-only Normalized shadow path (NYASH_JOINIR_DEV=1 + HAKO_JOINIR_STRICT=1) + +static box Main { + main() { + local s + local flag + s = "abc" + flag = 1 + if flag == 1 { + s = s + } else { + s = s + } + return s.length() + } +} diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 91e62c33..769897c6 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -1,5 +1,104 @@ # Self Current Task — Now (main) +## Next (planned) + +- Phase 142-loopstmt P1: LLVM EXE smoke test 追加(Phase 130 完了後) +- Phase 141 P2+: Call/MethodCall 対応(effects + typing を分離して段階投入) +- Phase 143-loopvocab: StepTree の語彙拡張(loop 内 if/break/continue を「新パターン追加」ではなく「語彙追加」で吸収) +- 詳細: `docs/development/current/main/30-Backlog.md` + +## 2025-12-19:Phase 142-loopstmt P0 完了 ✅ + +**Phase 142-loopstmt P0: Statement-Level Loop Normalization** +- 目的: 正規化単位を "block suffix" から "statement (loop 1個)" へ寄せてパターン爆発を防ぐ +- 変更: + - PlanBox: loop(true) に対して常に loop_only() を返す(consumed=1) + - SuffixRouter: LoopOnly を受け入れて実行 + - build_block: consumed 後も後続文を処理(break 削除) +- 実装 SSOT: + - `src/mir/builder/control_flow/normalization/plan_box.rs` + - `src/mir/builder/control_flow/joinir/patterns/policies/normalized_shadow_suffix_router_box.rs` + - `src/mir/builder/stmts.rs` +- Refactoring: + - LoopWithPost variant を deprecated(4 commits) + - suffix_router コメント更新 + - README 更新 +- Tests: + - Fixture: `apps/tests/phase142_loop_stmt_only_then_return_length_min.hako`(exit code 3) + - VM smoke: ✅ PASS + - Unit tests: ✅ 10/10 passed + - Regression: ✅ Phase 131, 141 green +- 統計: -38 lines net (code reduction success!) +- 入口: `docs/development/current/main/phases/phase-142-loopstmt/README.md` + +⚠️ **Note**: Phase 142 (Canonicalizer Pattern Extension) とは別物。SSOT 衝突回避のため phase-142-loopstmt として独立管理。 + +## 2025-12-19:Phase 141 P1.5 完了 ✅ + +**Phase 141 P1.5: KnownIntrinsic registry + available_inputs 3-source merge + diagnostics** +- 目的: “既知 intrinsic だけ” を SSOT 化しつつ、suffix 正規化が prefix の変数を見失わないようにする(既定挙動不変)。 +- Task B(バグ修正): `AvailableInputsCollectorBox::collect(.., prefix_variables)` を追加し、Function params > Prefix variables > CapturedEnv の 3-source merge に変更 +- Task A(SSOT化): `KnownIntrinsicRegistryBox` を追加し、intrinsic の metadata(name/arity/type_hint)を `known_intrinsics.rs` に集約 +- Task C(診断): `OutOfScopeReason::IntrinsicNotWhitelisted` を追加し、Call/MethodCall の out-of-scope 理由を精密化 +- 実装 SSOT: + - `src/mir/control_tree/normalized_shadow/available_inputs_collector.rs` + - `src/mir/control_tree/normalized_shadow/common/known_intrinsics.rs` + - `src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs` + - `src/mir/control_tree/normalized_shadow/common/expr_lowering_contract.rs` + - `src/mir/builder/control_flow/normalization/execute_box.rs` +- 設計 SSOT: + - `docs/development/current/main/design/normalized-expr-lowering.md` + +## 2025-12-19:Phase 141 P1 完了 ✅ + +**Phase 141 P1: KnownIntrinsicOnly (length0)** +- 目的: impure 導入の安全なオンランプとして、既知 intrinsic(小さな allowlist)のみを ExprLowerer に追加 +- 仕様: + - `ExprLoweringScope::WithImpure(ImpurePolicy::KnownIntrinsicOnly)` でのみ `receiver.length()` を lowering + - それ以外の Call/MethodCall は引き続き `Ok(None)`(既定挙動不変) +- Fixture: + - `apps/tests/phase141_p1_if_only_post_k_return_length_min.hako`(expected exit code 3) +- Smoke tests: + - VM: `tools/smokes/v2/profiles/integration/apps/phase141_p1_if_only_post_k_return_length_vm.sh` + - LLVM EXE: `tools/smokes/v2/profiles/integration/apps/phase141_p1_if_only_post_k_return_length_llvm_exe.sh` +- 入口: `docs/development/current/main/phases/phase-141/README.md` + +## 2025-12-19:Phase 141 P0 完了 ✅ + +**Phase 141 P0: Impure Extension Contract (Call/MethodCall stays out-of-scope)** +- 目的: 次フェーズの Call/MethodCall 導入に向けて、pure/impure 境界の contract(SSOT)を型で固定 +- SSOT: + - `src/mir/control_tree/normalized_shadow/common/expr_lowering_contract.rs` + - `src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs` +- 仕様: Call/MethodCall は引き続き `Ok(None)`(既定挙動不変) +- 入口: `docs/development/current/main/phases/phase-141/README.md` + +## 2025-12-19:Phase 140 完了 ✅ + +**Phase 140: NormalizedExprLowererBox (pure expressions)** +- 目的: “return の形追加” をやめて、pure expression を AST walker で一般化して収束させる +- SSOT: + - `src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs` + - `src/mir/control_tree/normalized_shadow/common/return_value_lowerer_box.rs` +- 仕様: + - pure のみ(Variable / Integer&Bool literal / unary(not,-) / arith(+,-,*,/) / compare(==,!=,<,<=,>,>=)) + - Call/MethodCall など impure は `Ok(None)`(Phase 141+) +- 入口: `docs/development/current/main/phases/phase-140/README.md` + +## 2025-12-19:Phase 139 完了 ✅ + +**Phase 139: post-if `post_k` Return Lowering Unification** +- 目的: if-only `post_k` 側の return lowering を `ReturnValueLowererBox` に統一し、loop/if の出口を一本化 +- 実装: + - `src/mir/control_tree/normalized_shadow/post_if_post_k.rs` の return lowering を `ReturnValueLowererBox::lower_to_value_id()` に委譲 + - out-of-scope は `Ok(None)` でフォールバック(既定挙動不変・dev-only) +- Fixture: + - `apps/tests/phase139_if_only_post_k_return_add_min.hako`(expected exit code 4) +- Smoke tests: + - VM: `tools/smokes/v2/profiles/integration/apps/phase139_if_only_post_k_return_add_vm.sh` + - LLVM EXE: `tools/smokes/v2/profiles/integration/apps/phase139_if_only_post_k_return_add_llvm_exe.sh` +- 入口: `docs/development/current/main/phases/phase-139/README.md` + ## 2025-12-18:Phase 138 完了 ✅ **Phase 138: ReturnValueLowererBox - Return Lowering SSOT** diff --git a/docs/development/current/main/20-Decisions.md b/docs/development/current/main/20-Decisions.md index f5ebcdf5..ff754a24 100644 --- a/docs/development/current/main/20-Decisions.md +++ b/docs/development/current/main/20-Decisions.md @@ -5,6 +5,11 @@ - promoted carriers(DigitPos/Trim などの synthetic name)は、`BindingId(original) → BindingId(promoted) → ValueId(join)` の鎖で接続し、by-name ルール分岐は導入しない。 - debug/観測は既存のフラグ(例: `NYASH_JOINIR_DEBUG`)に集約し、新しい環境変数のスパローは避ける。 +2025‑12‑19 +- return の表現力拡張は「パターン総当たり」ではなく、pure expression を扱う `NormalizedExprLowererBox`(AST walker)へ収束させる(Phase 140)。 +- Call/MethodCall は effects + typing の論点が増えるため、pure とは分離して Phase 141+ で段階投入する。 +- out-of-scope は `Ok(None)` で既存経路へフォールバックし、既定挙動不変を維持する(strict は “close-but-unsupported” のみ fail-fast)。 + 2025‑09‑08 - ループ制御は既存命令(Branch/Jump/Phi)で表現し、新命令は導入しない。 - Builder に loop_ctx({head, exit})を導入し、continue/break を分岐で降ろす。 diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index b782dec9..db9a0ab1 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -8,9 +8,27 @@ Related: ## 直近(JoinIR/selfhost) -- **Phase 131(Normalized loop lowering foundation / 入口作成)** - - ねらい: loop/if の “構造SSOT(StepTree/LoopSkeleton)” を使い、Normalized loop の最小骨格を固める - - 入口: (未作成) +- **Phase 142-loopstmt P1(planned): LLVM EXE smoke を追加** + - 前提(DONE): Phase 142-loopstmt P0(正規化単位を statement(loop 1個)へ寄せる) + - ねらい: VM で固定済みの fixture を LLVM EXE でも parity 固定する。 + - 受け入れ条件: + - 既存 fixture をそのまま使う(新パターン追加をしない) + - out-of-scope は `Ok(None)` でフォールバック(既定挙動不変) + - Phase 130 の LLVM EXE gate が前提(未完なら SKIP を維持) + +- **Phase 141 P2+(planned): Call/MethodCall(effects + typing を分離して段階投入)** + - ねらい: pure/impure 境界を壊さずに、impure lowering を段階投入する。 + - 前提(DONE): + - Phase 141 P1.5: known intrinsic allowlist + available_inputs 3-source merge + diagnostics + - 受け入れ条件: + - out-of-scope は `Ok(None)` でフォールバック(既定挙動不変) + - effects の順序付けは SSOT で固定してから解禁(by-name 増殖禁止) + +- **Phase 143-loopvocab(planned): “新パターン追加” ではなく “語彙追加” で吸収** + - 対象: `loop(true){ if(cond) break/continue }` を StepTree/ControlTree の語彙として表現し、同じ lowering に流す + - 受け入れ条件: + - capability guard(Fail-Fast)でスコープ外を明確化 + - fixture/smoke を 1 本ずつ小さく固定(VM + LLVM EXE parity) - **real-app loop regression の横展開(VM + LLVM EXE)** - ねらい: 実コード由来ループを 1 本ずつ最小抽出して fixture/smoke で固定する(段階投入)。 diff --git a/docs/development/current/main/design/README.md b/docs/development/current/main/design/README.md index 3db8d969..0cabeb40 100644 --- a/docs/development/current/main/design/README.md +++ b/docs/development/current/main/design/README.md @@ -10,3 +10,4 @@ - JoinIR の地図(navigation SSOT): `docs/development/current/main/design/joinir-design-map.md` - Loop Canonicalizer(設計 SSOT): `docs/development/current/main/design/loop-canonicalizer.md` - ControlTree / StepTree(構造SSOT): `docs/development/current/main/design/control-tree.md` +- Normalized ExprLowerer(式の一般化 SSOT): `docs/development/current/main/design/normalized-expr-lowering.md` diff --git a/docs/development/current/main/investigations/README.md b/docs/development/current/main/investigations/README.md index 70858c43..de585070 100644 --- a/docs/development/current/main/investigations/README.md +++ b/docs/development/current/main/investigations/README.md @@ -46,3 +46,7 @@ NYASH_LLVM_DUMP_IR= # Save LLVM IR to file ## Archive Completed investigations are kept for reference and pattern recognition. + +### JoinIR Generalization Study (Historical) + +- `joinir-generalization-study.md`(Phase 131–138 の状況と一般化案の相談用コンテキスト。SSOT は design/ を参照) diff --git a/docs/development/current/main/investigations/joinir-generalization-study.md b/docs/development/current/main/investigations/joinir-generalization-study.md new file mode 100644 index 00000000..d6f39421 --- /dev/null +++ b/docs/development/current/main/investigations/joinir-generalization-study.md @@ -0,0 +1,499 @@ +Status: Historical (context) +Scope: ChatGPT Pro 相談用に、Phase 131–138 の “当時点” の状況と一般化案をまとめたメモ。 +Related: +- SSOT (roadmap): `docs/development/current/main/10-Now.md` +- SSOT (decisions): `docs/development/current/main/20-Decisions.md` +- SSOT (design): `docs/development/current/main/design/normalized-expr-lowering.md` +- phases: `docs/development/current/main/phases/phase-139/README.md`, `docs/development/current/main/phases/phase-140/README.md` + +# Hakorune JoinIR 設計 - ChatGPT Pro 用コンテキスト + +> 注意: この文書は “相談の前提” であり SSOT ではありません。コード断片は擬似コードを含みます。 +> 正本(収束方針)は `docs/development/current/main/design/normalized-expr-lowering.md` を参照してください。 + +## 概要 + +Hakorune(セルフホスティングコンパイラ)の JoinIR(Join Intermediate Representation)設計に関する一般化戦略の相談。 + +### 現状の問題 + +- Phase 131-138 で「パターン追加」方式で拡張中 +- このままでは無限に Phase が必要になる可能性 +- 目標: パターンマッチング → 式の一般化(AST walker)への移行 + +### 提案中の方向 + +``` +制御フロー形(loop/if): 段階的拡張(正規化)OK +式と return: 早期に一般化(ExprLowererBox)へ移行 +PHI: Normalized 内で避ける → 後段(SSA/PHI)に押し出す +``` + +--- + +## ファイル構成(主要部分抜粋) + +### 1. LoopTrueBreakOnceBuilderBox (loop_true_break_once.rs - 主要実装) + +```rust +//! Phase 131-138: loop(true) break-once 正規化 +//! +//! ## 責務 +//! - loop(true) { * ; break } パターンを JoinModule に変換 +//! - PHI-free: env パラメータ + 継続で値を渡す +//! - Return lowering は ReturnValueLowererBox に委譲 +//! +//! ## 生成される構造 +//! main(env) +//! → TailCall(loop_step, env) +//! loop_step(env) +//! → TailCall(loop_body, env) +//! loop_body(env) +//! → → TailCall(k_exit, env) +//! k_exit(env) +//! → Ret(env[x]) or TailCall(post_k, env) +//! post_k(env) [Phase 132-P4+] +//! → → Ret(env[x]) + +pub struct LoopTrueBreakOnceBuilderBox; + +impl LoopTrueBreakOnceBuilderBox { + pub fn lower( + step_tree: &StepTree, + env_layout: &EnvLayout, + ) -> Result, String> { + // 1. Extract loop(true) { body ; break } [; ] pattern + let (prefix_nodes, loop_node, post_nodes) = + Self::extract_loop_true_pattern(&step_tree.root)?; + + // 2. Verify condition is Bool(true) + let is_loop_true = Self::is_bool_true_literal(&cond_ast.0); + if !is_loop_true { + return Ok(None); // Fallback + } + + // 3. Generate JoinModule with continuations + // - main → loop_step → loop_body → k_exit [→ post_k] + // - All parameters: env (BTreeMap) + + // 4. Return lowering via ReturnValueLowererBox + match ReturnValueLowererBox::lower_to_value_id( + return_value_ast, + &mut func.body, + &mut next_value_id, + &env, + )? { + Some(vid) => func.body.push(JoinInst::Ret { value: Some(vid) }), + None => return Ok(None), // Out of scope + } + } +} +``` + +**Key Points**: +- env は BTreeMap - 変数 → 値の対応 +- 継続関数は全て env をパラメータとして受け取る +- Return lowering は ReturnValueLowererBox に委譲(SSOT) + +--- + +### 2. ReturnValueLowererBox (common/return_value_lowerer_box.rs - SSOT) + +```rust +//! Phase 136-138: Return 値 lowering SSOT +//! +//! ## 責務 +//! - return value (変数/リテラル/式) → ValueId に変換 +//! - 任意の式に対応する準備(現在は pure expression のみ) + +pub struct ReturnValueLowererBox; + +impl ReturnValueLowererBox { + pub fn lower_to_value_id( + value_ast: &Option, + body: &mut Vec, + next_value_id: &mut u32, + env: &BTreeMap, + ) -> Result, String> { + match value_ast { + None => Ok(Some(ValueId(0))), // void return + + Some(ast_handle) => { + match ast_handle.0.as_ref() { + // Variable: env lookup + ASTNode::Variable { name, .. } => { + env.get(name).copied().ok_or_else(|| { + // Variable not in env - out of scope + Ok(None) + }) + } + + // Integer literal: Const generation + ASTNode::Literal { value: LiteralValue::Integer(i), .. } => { + let const_vid = ValueId(*next_value_id); + *next_value_id += 1; + body.push(JoinInst::Compute(MirLikeInst::Const { + dst: const_vid, + value: ConstValue::Integer(*i), + })); + Ok(Some(const_vid)) + } + + // BinaryOp (Phase 137 P0: Add のみ) + ASTNode::BinaryOp { operator, left, right, .. } => { + if !matches!(operator, BinaryOperator::Add) { + return Ok(None); // Out of scope + } + + // Lower LHS and RHS recursively + let lhs_vid = Self::lower_operand(left, body, next_value_id, env)?; + let rhs_vid = Self::lower_operand(right, body, next_value_id, env)?; + + // Generate BinOp + let result_vid = ValueId(*next_value_id); + *next_value_id += 1; + body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: result_vid, + op: BinOpKind::Add, + lhs: lhs_vid, + rhs: rhs_vid, + })); + + Ok(Some(result_vid)) + } + + _ => Ok(None) // Other types - out of scope + } + } + } + } +} +``` + +**問題点**: +- 現在は Add のみ対応(Phase 137) +- Sub, Mul, Div を追加するたびに Phase が必要 +- → 一般化(Phase 140+)で `lower_operand()` を再帰的に拡張したい + +--- + +### 3. NormalizationPlanBox (plan_box.rs - パターン検出) + +```rust +//! Phase 134-138: Pattern detection SSOT +//! +//! ## 責務 +//! - Block suffix が "正規化可能なパターン" か判定 +//! - loop(true) のみ受け入れ(loop(i Result, String> { + // 1. First statement must be loop(true) + let is_loop_true = match &remaining[0] { + ASTNode::Loop { condition, .. } => { + matches!( + condition.as_ref(), + ASTNode::Literal { value: LiteralValue::Bool(true), .. } + ) + } + _ => false, + }; + + if !is_loop_true { + return Ok(None); // Fallback: loop(i= remaining.len() { + return Ok(None); + } + + let has_return = matches!(&remaining[return_index], ASTNode::Return { .. }); + if !has_return { + return Ok(None); + } + + // 4. Return plan + Ok(Some(NormalizationPlan { + consumed: return_index + 1, + kind: if post_assign_count == 0 { + PlanKind::LoopOnly + } else { + PlanKind::LoopWithPost { post_assign_count } + }, + requires_return: true, + })) + } +} +``` + +**Problem**: +- パターンが複雑になると判定ロジックが肥大化 +- ネストした if などには対応困難 +- → 一般化で「制御フロー構造」と「式」を分離したい + +--- + +### 4. NormalizationExecuteBox (execute_box.rs - 実行ロジック) + +```rust +//! Phase 134-138: Plan の実行 + +pub struct NormalizationExecuteBox; + +impl NormalizationExecuteBox { + pub fn execute( + builder: &mut MirBuilder, + plan: &NormalizationPlan, + remaining: &[ASTNode], + ..., + ) -> Result { + match &plan.kind { + PlanKind::LoopOnly => { + Self::execute_loop_only(builder, remaining, ...) + } + PlanKind::LoopWithPost { post_assign_count } => { + Self::execute_loop_with_post(builder, plan, remaining, ...) + } + } + } + + fn execute_loop_only(...) -> Result { + // 1. Build StepTree from loop AST + let tree = StepTreeBuilderBox::build_from_ast(&loop_ast); + + // 2. Lower to JoinModule (PHI-free) + let (join_module, join_meta) = + match StepTreeNormalizedShadowLowererBox::try_lower_if_only( + &tree, + &available_inputs + )? { + Ok(Some(result)) => result, + Ok(None) => return Err("Out of scope".to_string()), + Err(e) => return Err(e), + }; + + // 3. Merge into MIR (DirectValue mode) + Self::merge_normalized_joinir( + builder, + join_module, + join_meta, + ..., + )?; + + Ok(void_id) + } + + fn merge_normalized_joinir(...) -> Result<(), String> { + // DirectValue mode: No PHI generation + // Exit values from join_meta → variable_map reconnection + let boundary = JoinInlineBoundary::new_with_exit_bindings( + vec![], + vec![], + exit_bindings, + ); + boundary.exit_reconnect_mode = ExitReconnectMode::DirectValue; + + // Bridge JoinIR → MIR + let mir_module = bridge_joinir_to_mir_with_meta(...)?; + + // Merge with boundary + merge::merge_joinir_mir_blocks(builder, &mir_module, Some(&boundary), ...)?; + } +} +``` + +**Problem**: +- Plan と Execute の責務が曖昧 +- パターン検出と実行ロジックが 1:1 対応 +- → Plan を「どこまで拡張するか」の判断基準が不明確 + +--- + +## 式の一般化案(Phase 140+ 向け) + +### 提案: NormalizedExprLowererBox + +```rust +//! Phase 140 P0: Pure expression 一般化 +//! +//! ## 責務 +//! - 任意の pure expression → ValueId に変換 +//! - 再帰的に operand を lower + +pub struct NormalizedExprLowererBox; + +impl NormalizedExprLowererBox { + pub fn lower_expr( + ast: &ASTNode, + body: &mut Vec, + next_value_id: &mut u32, + env: &BTreeMap, + ) -> Result, String> { + match ast { + // Variable + ASTNode::Variable { name, .. } => { + env.get(name).copied().ok_or_else(|| Ok(None)) + } + + // Literals + ASTNode::Literal { value: LiteralValue::Integer(i), .. } => { + let vid = ValueId(*next_value_id); + *next_value_id += 1; + body.push(JoinInst::Compute(MirLikeInst::Const { + dst: vid, + value: ConstValue::Integer(*i), + })); + Ok(Some(vid)) + } + + ASTNode::Literal { value: LiteralValue::Bool(b), .. } => { + let vid = ValueId(*next_value_id); + *next_value_id += 1; + body.push(JoinInst::Compute(MirLikeInst::Const { + dst: vid, + value: ConstValue::Bool(*b), + })); + Ok(Some(vid)) + } + + // BinaryOp - recursive + ASTNode::BinaryOp { operator, left, right, .. } => { + let lhs = Self::lower_expr(left, body, next_value_id, env)? + .ok_or_else(|| Ok(None))?; + let rhs = Self::lower_expr(right, body, next_value_id, env)? + .ok_or_else(|| Ok(None))?; + + let result = ValueId(*next_value_id); + *next_value_id += 1; + + let op = match operator { + BinaryOperator::Add => BinOpKind::Add, + BinaryOperator::Sub => BinOpKind::Sub, + BinaryOperator::Mul => BinOpKind::Mul, + BinaryOperator::Div => BinOpKind::Div, + BinaryOperator::Equal => BinOpKind::Eq, + BinaryOperator::Less => BinOpKind::Lt, + // ... other operators + _ => return Ok(None), // Out of scope + }; + + body.push(JoinInst::Compute(MirLikeInst::BinOp { + dst: result, + op, + lhs, + rhs, + })); + + Ok(Some(result)) + } + + // UnaryOp - recursive + ASTNode::UnaryOp { operator, operand, .. } => { + let operand_vid = Self::lower_expr( + operand, + body, + next_value_id, + env, + )? + .ok_or_else(|| Ok(None))?; + + let result = ValueId(*next_value_id); + *next_value_id += 1; + + let op = match operator { + UnaryOperator::Not => UnaryOpKind::Not, + UnaryOperator::Minus => UnaryOpKind::Neg, + _ => return Ok(None), + }; + + body.push(JoinInst::Compute(MirLikeInst::UnaryOp { + dst: result, + op, + operand: operand_vid, + })); + + Ok(Some(result)) + } + + // Call/MethodCall - Phase 141+ で対応 + ASTNode::Call { .. } | ASTNode::MethodCall { .. } => { + Ok(None) // Out of scope (Phase 141+) + } + + _ => Ok(None) // Out of scope + } + } +} +``` + +**利点**: +- 新しい operator を追加する際、1箇所だけ修正(総当たりにならない) +- 再帰的なので、ネストした式も自動的に対応 +- Phase 141+ で Call/MethodCall を追加しても、Return lowering は変更不要 + +--- + +## 現在の Phase スケジュール + +| Phase | 内容 | Status | +|-------|------|--------| +| 131 | loop(true) break-once | ✅ DONE | +| 132-133 | + post assigns | ✅ DONE | +| 134 | NormalizationPlan 統一 | ✅ DONE | +| 135 | post assigns 0個対応 | ✅ DONE | +| 136 | return literal | ✅ DONE | +| 137 | return add expr (x+2) | ✅ DONE | +| 138 | ReturnValueLowererBox SSOT | ✅ DONE | +| 139 | post_if_post_k 統一(計画中) | ⏳ | +| **140** | **ExprLowererBox 初版(pure のみ)** | 📋 提案 | +| 141+ | Call/MethodCall 対応 | 📋 提案 | + +--- + +## 質問(ChatGPT Pro 向け) + +1. **式の一般化と責務分離** + - ExprLowererBox と MirBuilder の責務の境界は? + - JoinIR 層でやるべきか MIR 層に押し出すべきか? + +2. **PHI-free の限界** + - PHI-free は無限に維持可能か? + - ネストした条件式(return if(a) {x} else {y})で PHI-free は可能か? + +3. **Pattern2 との統合** + - loop(true) break-once と loop(i < n) をどう統一するか? + - 「canonical」戦略は正しいか? + +4. **Call/MethodCall の分離** + - pure expression と impure expression の分離は正しいか? + - 混在する式(x + f(y))の扱いは? + +5. **セルフホスティングとの整合性** + - JoinIR 設計は .hako 側でも再現可能か? + - 二重実装の問題をどう解決すべきか? + +--- + +## 参考: 現在の環境 + +- **Rust コンパイラ**: `src/mir/control_tree/normalized_shadow/` +- **テスト**: Phase 131-138 全て green(VM + LLVM EXE) +- **セルフホスト**: 開発中(.hako で Nyash コンパイラを実装予定) diff --git a/docs/development/current/main/phases/README.md b/docs/development/current/main/phases/README.md index 5cd598d8..17b8c9d5 100644 --- a/docs/development/current/main/phases/README.md +++ b/docs/development/current/main/phases/README.md @@ -4,12 +4,15 @@ ## 現在の Phase -- **Phase 132**: Exit Values Parity (VM == LLVM) -- **Phase 133**: Promoted carrier join_id(Trim)修正 -- **Phase 134**: Plugin loader best-effort loading -- **Phase 135**: ConditionLoweringBox allocator SSOT(ValueId 衝突の根治) -- **Phase 136**: MirBuilder Context SSOT 化(+ ValueId allocator 掃討) -- **Phase 137–141**: Loop Canonicalizer(前処理 SSOT)導入(Phase 137 フォルダに統合して記録) +- **Phase 139(DONE)**: post-if `post_k` の return lowering を `ReturnValueLowererBox` に統一(出口 SSOT 完成) +- **Phase 140(DONE)**: `NormalizedExprLowererBox` 初版(pure expression のみ) +- **Phase 141 P0(DONE)**: impure 拡張点(contract)を SSOT 化(Call/MethodCall はまだ out-of-scope) +- **Phase 141 P1(DONE)**: “既知 intrinsic だけ” を許可して段階投入(length0) +- **Phase 141 P1.5(DONE)**: known intrinsic registry + available_inputs 3-source merge + diagnostics +- **Phase 141 P2+(planned)**: Call/MethodCall 対応(effects + typing の段階投入) +- **Phase 142-loopstmt P0(DONE)**: 正規化単位を statement(loop 1個)へ寄せる(パターン爆発を止める) +- **Phase 142-loopstmt P1(planned)**: LLVM EXE smoke(同 fixture)を追加 +- **Phase 143-loopvocab(planned)**: StepTree の語彙拡張(loop 内 if/break/continue を「語彙追加」で吸収) - **Phase 91–92**: Selfhost depth‑2 coverage(P5b escape recognition → lowering) - **Phase 94–100**: P5b escape E2E / Trim policy / pinned + accumulator(VM/LLVM EXE parity) - **Phase 102**: real-app read_quoted loop regression(VM + LLVM EXE) @@ -52,4 +55,4 @@ phases/phase-131/ --- -**最終更新**: 2025-12-18 +**最終更新**: 2025-12-19 diff --git a/docs/development/current/main/phases/phase-139/README.md b/docs/development/current/main/phases/phase-139/README.md new file mode 100644 index 00000000..25c71949 --- /dev/null +++ b/docs/development/current/main/phases/phase-139/README.md @@ -0,0 +1,46 @@ +# Phase 139: post-if `post_k` Return Lowering Unification + +Status: DONE ✅ +Scope: if-only `post_if_post_k.rs` の return lowering を `ReturnValueLowererBox` に統一し、loop/if の出口 SSOT を完成させる。 +Related: +- `docs/development/current/main/10-Now.md` +- `docs/development/current/main/30-Backlog.md` +- `docs/development/current/main/phases/phase-138/README.md` +- `src/mir/control_tree/normalized_shadow/post_if_post_k.rs` +- `src/mir/control_tree/normalized_shadow/common/return_value_lowerer_box.rs` + +--- + +## Goal + +- if-only `post_k` 内の return を `ReturnValueLowererBox` に委譲し、return の仕様を 1 箇所へ集約する。 +- out-of-scope は `Ok(None)` で既存経路へフォールバック(既定挙動不変)。 + +## Non-Goals + +- Assign lowering の共通化(return のみ対象) +- ExprLowerer の一般化(Phase 140) +- Call/MethodCall(Phase 141+) + +## Plan + +1. `post_if_post_k.rs` の return lowering を `ReturnValueLowererBox::lower_to_value_id()` 呼び出しへ置換(DONE) +2. return の重複ロジック削除(変数 lookup / const emission / add emission)(DONE) +3. fixture + smoke を追加して VM/LLVM EXE parity で固定(DONE) + +## Tests + +- Fixture: + - `apps/tests/phase139_if_only_post_k_return_add_min.hako`(expected exit code 4) +- Smokes: + - `tools/smokes/v2/profiles/integration/apps/phase139_if_only_post_k_return_add_vm.sh` + - `tools/smokes/v2/profiles/integration/apps/phase139_if_only_post_k_return_add_llvm_exe.sh` +- regressions: + - Phase 136/137/138(return 系) + - Phase 97(loop(i, >=) + +### ❌ Out of scope + +- Call/MethodCall(Phase 141+) +- Short-circuit(&&/||)の制御フローを伴う lowering +- 例外/throw/try/catch +- NormalizationPlan の粒度変更(suffix→statement などの再設計は Phase 141+ の検討に回す) + +## Contract + +`NormalizedExprLowererBox::lower_expr(...) -> Result, String>` + +- `Ok(Some(vid))`: lowering 成功 +- `Ok(None)`: out-of-scope(既存経路へフォールバック、既定挙動不変) +- `Err(_)`: 内部不整合のみ(strict では fail-fast) + +## Implementation Notes + +- 既存の `ReturnValueLowererBox` は “return 構文” の薄い箱に縮退し、expr lowering の実体は `ExprLowererBox` に置く。 +- 型は過剰に推論しない。Const/Compare/BinOp の最小ヒントのみ(必要なら段階投入)。 + +## Tests + +- Unit tests: + - `cargo test -p nyash-rust --lib mir::control_tree::normalized_shadow::common` +- Smokes (regressions): + - Phase 139(VM/LLVM EXE) + - Phase 136/137/138(return 系) + - Phase 97(フォールバック) + +## Implementation (DONE) + +- Added pure expression SSOT: `src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs` +- `ReturnValueLowererBox` shrunk to return-syntax only and delegates to `NormalizedExprLowererBox` +- Explicitly keeps out-of-scope = `Ok(None)` (fallback) and avoids fail-fast expansion diff --git a/docs/development/current/main/phases/phase-141/README.md b/docs/development/current/main/phases/phase-141/README.md new file mode 100644 index 00000000..92ead8eb --- /dev/null +++ b/docs/development/current/main/phases/phase-141/README.md @@ -0,0 +1,89 @@ +# Phase 141: Impure Extension Contract → P1 Known Intrinsic (incremental) + +Status: DONE ✅ +Scope: `NormalizedExprLowererBox` の pure/impure 境界を contract(SSOT)として型で固定し、Call/MethodCall を段階投入できる形へ収束させる。 +Related: +- `docs/development/current/main/design/normalized-expr-lowering.md` +- `docs/development/current/main/phases/phase-140/README.md` +- `src/mir/control_tree/normalized_shadow/common/expr_lowering_contract.rs` +- `src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs` + +--- + +## Goal + +- ExprLowerer が “pure only” のままでも、impure(Call/MethodCall)導入の拡張点(契約)を SSOT 化する。 +- P0 は contract のみ(Call/MethodCall を lowering しない、既定挙動不変)。 +- P1 は “既知 intrinsic だけ” を許可し、impure 導入の第一歩を作る(それ以外は既定挙動不変)。 + +## Non-Goals (P0) + +- Call/MethodCall の lowering(ValueId生成、effects、receiver materialization) +- impure式の順序付け(effects ordering) +- 型解決(dispatch/overload) + +## SSOT + +- Contract: `src/mir/control_tree/normalized_shadow/common/expr_lowering_contract.rs` + - `ExprLoweringScope::{PureOnly, WithImpure(..)}` + - Phase 141 P1: `ImpurePolicy::KnownIntrinsicOnly`, `KnownIntrinsic::{Length0}` + - `OutOfScopeReason`(Call/MethodCall 等の最小理由) +- ExprLowerer API: `src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs` + - `lower_expr_with_scope(scope, ...)` を追加 + - 既存 `lower_expr(...)` は `PureOnly` の thin wrapper(挙動不変) + - Return lowering entry: `src/mir/control_tree/normalized_shadow/common/return_value_lowerer_box.rs` + - Phase 141 P1: return lowering は `WithImpure(KnownIntrinsicOnly)` を使う(既知 intrinsic のみ許可) + +## Tests + +- Unit: `cargo test -p nyash-rust --lib mir::control_tree::normalized_shadow::common` +- Added minimal contract tests: + - `call_is_out_of_scope_in_pure_only` + - `methodcall_is_out_of_scope_in_pure_only` + - `methodcall_length0_is_in_scope_with_known_intrinsic_only` + - `lower_methodcall_length0_emits_method_call_inst` + +## Phase 141 P1: KnownIntrinsicOnly (Length0) + +### Scope + +- `ExprLoweringScope::WithImpure(ImpurePolicy::KnownIntrinsicOnly)` のときのみ、以下を lowering する: + - `receiver.length()`(引数 0、receiver は env に存在する Variable のみ) +- それ以外の Call/MethodCall は out-of-scope (`Ok(None)`) のまま(既定挙動不変)。 + +### Fixture / Smoke + +- Fixture: `apps/tests/phase141_p1_if_only_post_k_return_length_min.hako`(expected exit code 3) +- Smoke tests: + - VM: `tools/smokes/v2/profiles/integration/apps/phase141_p1_if_only_post_k_return_length_vm.sh` + - LLVM EXE: `tools/smokes/v2/profiles/integration/apps/phase141_p1_if_only_post_k_return_length_llvm_exe.sh` + +## Phase 141 P1.5: SSOT Reinforcement (Registry + available_inputs + diagnostics) + +### Task A: KnownIntrinsic SSOT (Registry) + +- 目的: intrinsic の metadata(method 名/arity/type_hint)を 1 箇所に集約し、文字列直書きの散らばりを止める。 +- SSOT: + - `src/mir/control_tree/normalized_shadow/common/known_intrinsics.rs` + - `KnownIntrinsicRegistryBox::{lookup,get_spec}` +- 効果: + - `expr_lowerer_box.rs` の by-name 判定が “registry lookup” に収束 + +### Task B: available_inputs 3-source merge (Bug fix) + +- 目的: suffix 正規化が prefix 側で生成された locals(`local s; s="..."`)を見失わないようにする。 +- 変更: + - `AvailableInputsCollectorBox::collect(builder, captured_env, prefix_variables)` を追加 + - 優先順位: Function params > Prefix variables > CapturedEnv +- Call sites: + - `src/mir/builder/control_flow/normalization/execute_box.rs` + - `src/mir/builder/control_flow/joinir/patterns/policies/normalized_shadow_suffix_router_box.rs` + +### Task C: Diagnostics + +- 追加: `OutOfScopeReason::IntrinsicNotWhitelisted` + - “methodcall だが known intrinsic allowlist 外” を区別できるようにする + +## Next + +- Phase 141 P2+: 一般 Call/MethodCall(effects + typing を分離して拡張) diff --git a/docs/development/current/main/phases/phase-142-loopstmt/README.md b/docs/development/current/main/phases/phase-142-loopstmt/README.md new file mode 100644 index 00000000..5fa6fb3b --- /dev/null +++ b/docs/development/current/main/phases/phase-142-loopstmt/README.md @@ -0,0 +1,351 @@ +# Phase 142-loopstmt: Statement-Level Loop Normalization + +Status: ✅ P0 Complete +Date: 2025-12-19 + +--- + +## 目的(Why) + +Phase 131-141 で loop(true) 系の Normalized shadow を段階的に拡張したが、「block suffix (loop + post assigns + return)」の直積パターン増殖を止める必要があった。 + +**Phase 142-loopstmt** では、正規化単位を **"block suffix" から "statement (loop 1個)" へ寄せる** ことで、パターン爆発を防ぐ。 + +### Non-Goal + +- ⚠️ **Phase 142 (Canonicalizer Pattern Extension) とは別物** + - Phase 142 = trim leading/trailing, continue pattern (Canonicalizer) + - Phase 142-loopstmt = Statement-level normalization (Normalized shadow) + - SSOT 衝突回避のため phase-142-loopstmt として独立管理 + +--- + +## P0: Statement-Level Normalization (COMPLETE ✅) + +### Summary + +Normalization unit changed from "block suffix (loop + post + return)" to "statement (loop only)". + +### Implementation + +#### 1. PlanBox の挙動変更 + +**File**: `src/mir/builder/control_flow/normalization/plan_box.rs` + +**Before**: +- `loop(true)` の後ろに return が無いと plan が None になりやすい +- LoopWithPost pattern を返す(loop + post assigns + return を一括消費) + +**After**: +- `remaining[0]` が `loop(true)` なら 常に `NormalizationPlan::loop_only()` を返す +- **consumed = 1** (loop のみ) +- 後続文(return/assign 等)は通常 MIR lowering へ戻す + +**Code changes**: +```rust +// Phase 142-loopstmt P0: Always return loop_only for loop(true) +// Normalization unit is now "statement (loop 1個)" not "block suffix" +// Subsequent statements handled by normal MIR lowering +if debug { + trace.routing( + "normalization/plan", + func_name, + "Detected loop(true) - Phase 142-loopstmt P0: returning loop_only (consumed=1)", + ); +} +Ok(Some(NormalizationPlan::loop_only())) +``` + +**Impact**: ~70 lines reduced + +#### 2. SuffixRouter の LoopOnly 対応 + +**File**: `src/mir/builder/control_flow/joinir/patterns/policies/normalized_shadow_suffix_router_box.rs` + +**Before**: Lines 64-75 で PlanKind::LoopOnly を reject ("not a suffix") + +**After**: LoopOnly も受け入れて execute_box に渡す + +**Code changes**: +```rust +// Phase 142-loopstmt P0: Accept both LoopOnly and LoopWithPost +// Normalization unit is now "statement (loop 1個)", not "block suffix" +if debug { + let description = match &plan.kind { + PlanKind::LoopOnly => "Loop-only pattern".to_string(), + PlanKind::LoopWithPost { post_assign_count } => { + format!("Loop+post pattern: {} post assigns", post_assign_count) + } + }; + trace.routing("suffix_router", func_name, &description); +} +``` + +**Impact**: ~12 lines reduced + +#### 3. build_block で consumed 後の break 削除 + +**File**: `src/mir/builder/stmts.rs` + +**Before**: +- suffix_router が Some(consumed) を返したら break +- return statement が消費されたと仮定 + +**After**: +- consumed 後も `idx += consumed` でスキップし、後続文を通常処理 +- Phase 142-loopstmt では loop 正規化後も `return s.length()` 等が残る + +**Code changes**: +```rust +Some(consumed) => { + trace.emit_if( + "debug", + "build_block/suffix_router", + &format!("Phase 142-loopstmt P0: Suffix router consumed {} statement(s), continuing to process subsequent statements", consumed), + debug, + ); + // Phase 142-loopstmt P0: Normalization unit is now "statement (loop 1個)" + // Loop normalization returns consumed=1, and subsequent statements + // (return, assignments, etc.) are handled by normal MIR lowering + idx += consumed; + // No break - continue processing subsequent statements +} +``` + +### Unit Tests + +**File**: `src/mir/builder/control_flow/normalization/plan_box.rs` + +**Updated tests** (7 tests total): +- `test_plan_block_suffix_phase131_loop_only()` - unchanged +- `test_plan_block_suffix_phase142_loop_with_subsequent_stmts()` - was phase132, now expects LoopOnly +- `test_plan_block_suffix_phase142_loop_only_always()` - was phase133, now expects LoopOnly +- `test_plan_block_suffix_phase142_loop_with_trailing_stmt()` - new, no return but still LoopOnly +- `test_plan_block_suffix_no_match_empty()` - unchanged +- `test_plan_block_suffix_no_match_not_loop()` - unchanged +- `test_plan_block_suffix_no_match_loop_not_true()` - unchanged + +**Results**: ✅ 7/7 passed + +### E2E Tests + +#### Fixture + +**File**: `apps/tests/phase142_loop_stmt_only_then_return_length_min.hako` + +```hako +static box Main { + main() { + local s + s = "abc" + loop(true) { + break + } + return s.length() + } +} +``` + +**Expected**: exit code 3 (s="abc" → s.length() → 3) + +#### VM Smoke Test + +**File**: `tools/smokes/v2/profiles/integration/apps/phase142_loop_stmt_only_then_return_length_min_vm.sh` + +**Command**: +```bash +bash tools/smokes/v2/profiles/integration/apps/phase142_loop_stmt_only_then_return_length_min_vm.sh +``` + +**Result**: ✅ PASS (exit code 3) + +#### LLVM EXE Smoke Test + +**File**: `tools/smokes/v2/profiles/integration/apps/phase142_loop_stmt_only_then_return_length_min_llvm_exe.sh` + +**Status**: ⚠️ TODO in P1 (requires Phase 130 LLVM EXE gate complete) + +--- + +## Refactoring (COMPLETE ✅) + +### Task 1: suffix_router コメント更新 + +**Commit**: `21a3c6b5d` - "docs(normalization): Update suffix_router comments for Phase 142-loopstmt P0" + +**Changes**: +- Updated header comments to reflect post statements no longer required +- Documented LoopOnly pattern acceptance + +### Task 2: LoopWithPost Deprecation + +**Commit**: `aaba27d31` - "refactor(normalization): Deprecate LoopWithPost variant" + +**Changes**: +- Added `#[deprecated]` to `PlanKind::LoopWithPost` enum variant +- Added deprecation to `loop_with_post()` constructor function +- Documented migration path to LoopOnly +- Kept for backward compatibility + +**Deprecation warnings** (4 locations): +- `normalized_shadow_suffix_router_box.rs:69` +- `routing.rs:457` +- `plan.rs:74` +- `execute_box.rs:66` + +### Task 3: README 更新 + +**Commit**: `3ef929df5` - "docs(normalization): Update README for Phase 142-loopstmt P0" + +**Changes**: +- Added Phase 142-loopstmt P0 section +- Marked Phase 132-135 as LEGACY +- Updated suffix_router description + +--- + +## Acceptance Criteria (All Met ✅) + +- [x] PlanBox が loop(true) に対して常に loop_only を返す(consumed=1) +- [x] SuffixRouter が LoopOnly を受け入れて実行する +- [x] build_block が consumed 後も後続文を処理する +- [x] Fixture が exit code 3 を返す(VM) +- [x] 既存 smokes が緑(Phase 97/131/136/137/139/141) + - Phase 131 VM: ✅ PASS + - Normalization unit tests: ✅ 10 passed +- [x] Out-of-scope は常に Ok(None) でフォールバック(既定挙動不変) +- [x] Documentation 作成 +- [x] Refactoring tasks 完了(3 commits) +- [x] Main implementation commit + +--- + +## Design Principles + +### Pattern Explosion Prevention + +- **制御フローの骨格(loop/if/post_k/continuation)**: 正規化(段階投入)で固める +- **式(return value を含む)**: 一般化(AST walker)で受ける(Phase 140+) + +### Normalization Unit Evolution + +**Phase 141 以前**: +``` +NormalizationPlan::loop_with_post(n) → consumed = 1 + n + 1 (loop + assigns + return) +``` + +**Phase 142-loopstmt P0**: +``` +NormalizationPlan::loop_only() → consumed = 1 (loop のみ) +後続文は通常 MIR lowering で処理 +``` + +### Fail-Fast Policy + +- **Out-of-scope**: 常に `Ok(None)` でフォールバック(既定挙動不変) +- **Fail-Fast**: "in-scope のはずなのに壊れた" ケースのみ(internal error) + +--- + +## Files Modified + +### Core Implementation + +1. `src/mir/builder/control_flow/normalization/plan_box.rs` + - Always return loop_only for loop(true) + - 7 unit tests updated + +2. `src/mir/builder/control_flow/joinir/patterns/policies/normalized_shadow_suffix_router_box.rs` + - Accept LoopOnly patterns + - Remove rejection logic + +3. `src/mir/builder/stmts.rs` + - Remove break after consumed + - Continue processing subsequent statements + +### Tests + +4. `apps/tests/phase142_loop_stmt_only_then_return_length_min.hako` (NEW) +5. `tools/smokes/v2/profiles/integration/apps/phase142_loop_stmt_only_then_return_length_min_vm.sh` (NEW) + +### Documentation + +6. `src/mir/builder/control_flow/normalization/plan.rs` (deprecation) +7. `src/mir/builder/control_flow/normalization/README.md` (updated) + +### Statistics + +- **Total commits**: 4 +- **Files changed**: 7 +- **Net change**: -38 lines (削減成功!) +- **Code reduction**: ~82 lines deleted (pattern detection logic) + +--- + +## Verification Commands + +### Unit Tests +```bash +cargo test -p nyash-rust --lib mir::builder::control_flow::normalization +``` + +### Regression Tests +```bash +# Phase 131 regression +bash tools/smokes/v2/profiles/integration/apps/phase131_loop_true_break_once_vm.sh + +# Phase 141 regression +bash tools/smokes/v2/profiles/integration/apps/phase141_p1_if_only_post_k_return_length_vm.sh + +# Phase 142-loopstmt P0 +bash tools/smokes/v2/profiles/integration/apps/phase142_loop_stmt_only_then_return_length_min_vm.sh +``` + +--- + +## Next Steps + +### P1: LLVM EXE Smoke Test (TODO) + +**File**: `tools/smokes/v2/profiles/integration/apps/phase142_loop_stmt_only_then_return_length_min_llvm_exe.sh` + +**Prerequisites**: Phase 130 LLVM EXE gate complete + +**Expected**: exit code 3 parity with VM + +### P2: Code Contract Enforcement (Planned) + +**Goal**: Prevent future regression to pattern explosion + +**Options**: +- **Option A (Recommended)**: Remove LoopWithPost creation path entirely +- **Option B**: Introduce Outcome { consumed, stop_block } SSOT + +### Phase 143-loopvocab (Planned) + +**Goal**: 「語彙追加」で `loop(true){ if(cond) break/continue }` を吸収 + +**Approach**: +- Extend StepTree/ControlTree vocabulary (not new patterns) +- Use NormalizedExprLowererBox for pure conditions +- JoinIR Jump { cond: Some(vid) } for conditional exits + +--- + +## Related Documentation + +- [10-Now.md](../../10-Now.md) - Current progress +- [30-Backlog.md](../../30-Backlog.md) - Future phases +- [phase-143-loopvocab/README.md](../phase-143-loopvocab/README.md) - Next planned vocabulary expansion +- [normalized-expr-lowering.md](../../design/normalized-expr-lowering.md) - Normalized design SSOT +- [control-tree.md](../../design/control-tree.md) - ControlTree design +- [normalization/README.md](../../../src/mir/builder/control_flow/normalization/README.md) - Architecture + +--- + +## Commits + +1. `21a3c6b5d` - docs(normalization): Update suffix_router comments for Phase 142-loopstmt P0 +2. `aaba27d31` - refactor(normalization): Deprecate LoopWithPost variant +3. `3ef929df5` - docs(normalization): Update README for Phase 142-loopstmt P0 +4. `275fe45ba` - feat(normalization): Phase 142-loopstmt P0 - Statement-level normalization diff --git a/docs/development/current/main/phases/phase-143-loopvocab/README.md b/docs/development/current/main/phases/phase-143-loopvocab/README.md new file mode 100644 index 00000000..cbde5cfb --- /dev/null +++ b/docs/development/current/main/phases/phase-143-loopvocab/README.md @@ -0,0 +1,96 @@ +# Phase 143-loopvocab: StepTree Vocabulary Expansion (loop → if/break/continue) + +Status: planned +Scope: Normalized shadow / JoinIR(dev-only, default behavior unchanged) + +Related (SSOT): +- `docs/development/current/main/design/control-tree.md` +- `docs/development/current/main/design/normalized-expr-lowering.md` +- `docs/development/current/main/phases/phase-142-loopstmt/README.md` + +--- + +## Goal + +`loop(true){ if(cond) break/continue }` を「新パターン追加」ではなく、**StepTree/ControlTree の語彙(vocabulary)拡張**として表現し、同じ Normalized lowering(env + continuation)に流す。 + +つまり、suffix/fixture の形を増やすのではなく: + +- **制御は構造(StepTree)** +- **値は一般化(ExprLowererBox)** + +で吸収する。 + +## Non-Goals + +- `loop()`(条件付き loop)を Normalized に入れる(既存経路へフォールバック維持) +- impure expression(Call/MethodCall general case)を増やす(Phase 141 P2+ で扱う) +- 既定挙動変更(out-of-scope は `Ok(None)` でフォールバック) + +--- + +## P0 (planned): If-in-loop (single if, break/continue only) + +### Accepted surface syntax (fixtures) + +1) break: +```hako +loop(true) { + if (cond) { break } +} +``` + +2) continue: +```hako +loop(true) { + if (cond) { continue } +} +``` + +### StepTree vocabulary (existing) + +StepTree は既に `StepNode::Loop` / `StepNode::If` / `StepStmtKind::{Break,Continue}` を持っている。 + +P0 の焦点は **Normalized lowering が Loop-body 内の If を受けられるようにする**こと。 + +### Capability / contract (Fail-Fast boundary) + +- P0 で許可するのは「Loop-body の先頭/末尾に 1 個の If(else無しでも可)で、その then/else が Break/Continue のみ」まで。 +- それ以外(ネスト if、複数 if、then に Assign など)は out-of-scope (`Ok(None)`) にして既存経路へフォールバック。 +- strict/dev では `OutOfScopeReason` を必ず出す(silent accept 禁止)。 + +### Implementation sketch (boxes) + +- `NormalizationPlanBox`: + - `loop(true)` を見たら `loop_only(consumed=1)`(Phase 142-loopstmt と同じ) + - 追加で「Loop-body が If を含むか」を feature として観測(plan の枝分かれは最小) +- `NormalizationExecuteBox`: + - `execute_loop_only` の内部で StepTree を作る + - `StepTreeContractBox` で capability 判定(許可形なら Normalized lowering、不可なら `Ok(None)`) +- Normalized lowering: + - `if(cond){break}`: `cond_vid` を `ExprLowererBox`(pure only)で生成し、branch to `k_exit` / `loop_step` に落とす + - `if(cond){continue}`: then -> tailcall `loop_step`, else -> fallthrough + +### Fixtures / smokes + +- VM + LLVM EXE parity を 1 本ずつ増やす(最小2本) + - `apps/tests/phase143_loop_true_if_break_min.hako` + - `apps/tests/phase143_loop_true_if_continue_min.hako` +- Integration smokes: + - `tools/smokes/v2/profiles/integration/apps/phase143_loop_true_if_break_vm.sh` + - `tools/smokes/v2/profiles/integration/apps/phase143_loop_true_if_break_llvm_exe.sh` + - `tools/smokes/v2/profiles/integration/apps/phase143_loop_true_if_continue_vm.sh` + - `tools/smokes/v2/profiles/integration/apps/phase143_loop_true_if_continue_llvm_exe.sh` + +### Acceptance criteria + +- Phase 142-loopstmt の「statement-level normalization(consumed=1)」を維持し、suffix 形の増殖をさせない +- out-of-scope は `Ok(None)` でフォールバック(既定挙動不変) +- Unit tests が「許可形/不許可形」を contract として固定している(箱の責務が明確) + +--- + +## Next + +- P1: `if(cond) { break } else { continue }`(else branch を入れる)を vocabulary として追加 +- P2+: nested if / multi-if は capability guard で段階解禁(Phase 切りで SSOT 化) diff --git a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs index d4cce578..7105c2d4 100644 --- a/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs +++ b/src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs @@ -651,10 +651,13 @@ pub(super) fn merge_and_rewrite( } // Phase 33-20: Also set latch incoming for other carriers from exit_bindings - // Phase 176-4 FIX: exit_bindings may include the loop variable itself - // We need to skip it since it's already handled above via boundary.loop_var_name - // The remaining non-loop-variable carriers are ordered to match args[1..] (after the loop variable) - let mut carrier_arg_idx = 1; // Start from args[1] (args[0] is loop variable) + // + // args layout depends on whether we have a dedicated loop variable: + // - loop_var_name = Some(..): args[0] is the loop variable, args[1..] are other carriers + // - loop_var_name = None: args[0..] are carriers (no reserved loop-var slot) + // + // Phase 176-4 FIX: exit_bindings may include the loop variable itself; skip it when loop_var_name is set. + let mut carrier_arg_idx = if b.loop_var_name.is_some() { 1 } else { 0 }; for binding in b.exit_bindings.iter() { // Skip if this binding is for the loop variable (already handled) if let Some(ref loop_var) = b.loop_var_name { diff --git a/src/mir/control_tree/normalized_shadow/common/return_value_lowerer_box.rs b/src/mir/control_tree/normalized_shadow/common/return_value_lowerer_box.rs index 762bdf4c..5d1dd385 100644 --- a/src/mir/control_tree/normalized_shadow/common/return_value_lowerer_box.rs +++ b/src/mir/control_tree/normalized_shadow/common/return_value_lowerer_box.rs @@ -1,14 +1,10 @@ -//! ReturnValueLowererBox: Return value lowering SSOT (Phase 138 P0) +//! ReturnValueLowererBox: Return syntax lowering (Phase 140 P0) //! //! ## Responsibility //! -//! Lower return values (variable, literal, expr) to ValueId for Normalized shadow paths +//! Normalize return value syntax (`return` vs `return `) for Normalized shadow paths. //! -//! ## Supported Patterns (Phase 136-137) -//! -//! - Variable: env lookup → ValueId -//! - Integer literal: Const generation → ValueId -//! - Add expr: (var|int) + int → BinOp(Add, lhs, rhs) → ValueId +//! Expression lowering is delegated to `NormalizedExprLowererBox` (SSOT). //! //! ## Fallback //! @@ -17,11 +13,12 @@ //! ## Usage //! //! - Phase 138 P0: loop_true_break_once.rs -//! - Phase 139 P0: post_if_post_k.rs (planned) +//! - Phase 139 P0: post_if_post_k.rs -use crate::ast::{ASTNode, BinaryOperator, LiteralValue}; +use super::expr_lowerer_box::NormalizedExprLowererBox; +use super::expr_lowering_contract::{ExprLoweringScope, ImpurePolicy}; use crate::mir::control_tree::step_tree::AstNodeHandle; -use crate::mir::join_ir::{BinOpKind, ConstValue, JoinInst, MirLikeInst}; +use crate::mir::join_ir::JoinInst; use crate::mir::ValueId; use std::collections::BTreeMap; @@ -31,8 +28,6 @@ pub struct ReturnValueLowererBox; impl ReturnValueLowererBox { /// Lower return value to ValueId /// - /// Phase 136-137: Support variable, integer literal, and add expression - /// /// Returns: /// - Ok(Some(vid)): Successfully lowered to ValueId /// - Ok(None): Out of scope (fallback to legacy routing) @@ -51,112 +46,17 @@ impl ReturnValueLowererBox { Ok(Some(ValueId(0))) // Dummy - caller handles void separately } Some(ast_handle) => { - match ast_handle.0.as_ref() { - // Variable: lookup in env - ASTNode::Variable { name, .. } => { - if let Some(&vid) = env.get(name) { - Ok(Some(vid)) - } else { - // Variable not in env - out of scope - Ok(None) - } - } - // Integer literal: generate Const instruction (Phase 136) - ASTNode::Literal { value: LiteralValue::Integer(i), .. } => { - let const_vid = ValueId(*next_value_id); - *next_value_id += 1; - - body.push(JoinInst::Compute(MirLikeInst::Const { - dst: const_vid, - value: ConstValue::Integer(*i), - })); - - Ok(Some(const_vid)) - } - // Phase 137: BinaryOp (x + 2) support - ASTNode::BinaryOp { operator, left, right, .. } => { - Self::lower_binary_op(operator, left, right, body, next_value_id, env) - } - _ => { - // Other return value types - out of scope - Ok(None) - } - } + // Phase 141 P1: allow a small allowlist of known intrinsic method calls. + NormalizedExprLowererBox::lower_expr_with_scope( + ExprLoweringScope::WithImpure(ImpurePolicy::KnownIntrinsicOnly), + ast_handle.0.as_ref(), + env, + body, + next_value_id, + ) } } } - - /// Lower binary operation to ValueId (Phase 137) - fn lower_binary_op( - operator: &BinaryOperator, - left: &ASTNode, - right: &ASTNode, - body: &mut Vec, - next_value_id: &mut u32, - env: &BTreeMap, - ) -> Result, String> { - // Phase 137 contract: Add only - if !matches!(operator, BinaryOperator::Add) { - return Ok(None); // out of scope - } - - // Lower LHS (Variable or Integer literal) - let lhs_vid = match left { - ASTNode::Variable { name, .. } => { - // Get from env - if let Some(&vid) = env.get(name) { - vid - } else { - // Variable not in env - out of scope - return Ok(None); - } - } - ASTNode::Literal { value: LiteralValue::Integer(i), .. } => { - // Generate Const for LHS integer literal - let vid = ValueId(*next_value_id); - *next_value_id += 1; - body.push(JoinInst::Compute(MirLikeInst::Const { - dst: vid, - value: ConstValue::Integer(*i), - })); - vid - } - _ => { - // Other LHS types - out of scope - return Ok(None); - } - }; - - // Lower RHS (Integer literal only) - let rhs_vid = match right { - ASTNode::Literal { value: LiteralValue::Integer(i), .. } => { - // Generate Const for RHS integer literal - let vid = ValueId(*next_value_id); - *next_value_id += 1; - body.push(JoinInst::Compute(MirLikeInst::Const { - dst: vid, - value: ConstValue::Integer(*i), - })); - vid - } - _ => { - // Other RHS types - out of scope (e.g., return x + y) - return Ok(None); - } - }; - - // Generate BinOp Add - let result_vid = ValueId(*next_value_id); - *next_value_id += 1; - body.push(JoinInst::Compute(MirLikeInst::BinOp { - dst: result_vid, - op: BinOpKind::Add, - lhs: lhs_vid, - rhs: rhs_vid, - })); - - Ok(Some(result_vid)) - } } #[cfg(test)] @@ -164,37 +64,32 @@ mod tests { use super::*; use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; use crate::mir::control_tree::step_tree::AstNodeHandle; + use crate::mir::join_ir::JoinInst; fn make_span() -> Span { Span::unknown() } #[test] - fn test_lower_variable() { + fn test_void_return_dummy_value_id() { let mut body = vec![]; let mut next_value_id = 100; - let mut env = BTreeMap::new(); - env.insert("x".to_string(), ValueId(42)); - - let var_ast = AstNodeHandle(Box::new(ASTNode::Variable { - name: "x".to_string(), - span: make_span(), - })); let result = ReturnValueLowererBox::lower_to_value_id( - &Some(var_ast), + &None, &mut body, &mut next_value_id, - &env, + &BTreeMap::new(), ) .unwrap(); - assert_eq!(result, Some(ValueId(42))); - assert_eq!(body.len(), 0); // No instructions emitted + assert_eq!(result, Some(ValueId(0))); + assert!(body.is_empty()); + assert_eq!(next_value_id, 100); } #[test] - fn test_lower_integer_literal() { + fn test_delegates_integer_literal() { let mut body = vec![]; let mut next_value_id = 100; let env = BTreeMap::new(); @@ -218,7 +113,7 @@ mod tests { } #[test] - fn test_lower_add_var_plus_int() { + fn test_delegates_add_var_plus_int() { let mut body = vec![]; let mut next_value_id = 100; let mut env = BTreeMap::new(); @@ -251,66 +146,36 @@ mod tests { } #[test] - fn test_lower_add_int_plus_int() { - let mut body = vec![]; - let mut next_value_id = 100; - let env = BTreeMap::new(); - - let add_ast = AstNodeHandle(Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Add, - left: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(5), - span: make_span(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(3), - span: make_span(), - }), - span: make_span(), - })); - - let result = ReturnValueLowererBox::lower_to_value_id( - &Some(add_ast), - &mut body, - &mut next_value_id, - &env, - ) - .unwrap(); - - assert_eq!(result, Some(ValueId(102))); // BinOp result - assert_eq!(body.len(), 3); // Const(5) + Const(3) + BinOp - assert_eq!(next_value_id, 103); - } - - #[test] - fn test_out_of_scope_subtract() { + fn test_delegates_known_intrinsic_method_call_length0() { let mut body = vec![]; let mut next_value_id = 100; let mut env = BTreeMap::new(); - env.insert("x".to_string(), ValueId(1)); + env.insert("s".to_string(), ValueId(1)); - let sub_ast = AstNodeHandle(Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Subtract, - left: Box::new(ASTNode::Variable { - name: "x".to_string(), - span: make_span(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(2), + let length_ast = AstNodeHandle(Box::new(ASTNode::MethodCall { + object: Box::new(ASTNode::Variable { + name: "s".to_string(), span: make_span(), }), + method: "length".to_string(), + arguments: vec![], span: make_span(), })); let result = ReturnValueLowererBox::lower_to_value_id( - &Some(sub_ast), + &Some(length_ast), &mut body, &mut next_value_id, &env, ) .unwrap(); - assert_eq!(result, None); // Out of scope - assert_eq!(body.len(), 0); // No instructions emitted + assert_eq!(result, Some(ValueId(100))); + assert_eq!(next_value_id, 101); + assert!(matches!( + body.as_slice(), + [JoinInst::MethodCall { method, args, .. }] + if method == "length" && args.is_empty() + )); } } diff --git a/src/mir/control_tree/normalized_shadow/loop_true_break_once.rs b/src/mir/control_tree/normalized_shadow/loop_true_break_once.rs index 7fd5e70b..1fc5af33 100644 --- a/src/mir/control_tree/normalized_shadow/loop_true_break_once.rs +++ b/src/mir/control_tree/normalized_shadow/loop_true_break_once.rs @@ -841,4 +841,7 @@ mod tests { "loop-only bridged MIR must have a single consistent k_exit arg[0]" ); } + + // Phase 141 P1 covers MethodCall as a "known intrinsic" on a small allowlist. + // End-to-end behavior is pinned by integration smokes in phase-141 docs. } diff --git a/src/mir/control_tree/normalized_shadow/post_if_post_k.rs b/src/mir/control_tree/normalized_shadow/post_if_post_k.rs index ca4b3fcf..6ac00bdf 100644 --- a/src/mir/control_tree/normalized_shadow/post_if_post_k.rs +++ b/src/mir/control_tree/normalized_shadow/post_if_post_k.rs @@ -19,7 +19,7 @@ //! //! ## Scope //! -//! - Post-if: Return(Variable) only (Phase 124/125/126 baseline) +//! - Post-if: Return value lowering uses `ReturnValueLowererBox` (Phase 138 SSOT) //! - If body: Assign(int literal) only (Phase 128 baseline) //! - Condition: minimal compare only (Phase 123 baseline) //! @@ -30,6 +30,7 @@ use super::env_layout::EnvLayout; use super::legacy::LegacyLowerer; +use super::common::return_value_lowerer_box::ReturnValueLowererBox; use crate::mir::control_tree::step_tree::{StepNode, StepStmtKind, StepTree}; use crate::mir::join_ir::lowering::carrier_info::JoinFragmentMeta; use crate::mir::join_ir::lowering::error_tags; @@ -251,17 +252,22 @@ impl PostIfPostKBuilderBox { match n { StepNode::Stmt { kind, .. } => match kind { StepStmtKind::Return { value_ast } => { - // Phase 124/125/126: Return(Variable) support - if LegacyLowerer::lower_return_value( - value_ast, - &mut post_k_func.body, - &mut next_value_id, - &env_post_k, - &step_tree.contract, - ) - .is_err() - { - return Ok(None); + if let Some(_ast_handle) = value_ast { + match ReturnValueLowererBox::lower_to_value_id( + value_ast, + &mut post_k_func.body, + &mut next_value_id, + &env_post_k, + )? { + Some(vid) => { + post_k_func.body.push(JoinInst::Ret { value: Some(vid) }); + } + None => { + return Ok(None); + } + } + } else { + post_k_func.body.push(JoinInst::Ret { value: None }); } } _ => { diff --git a/tools/smokes/v2/profiles/integration/apps/phase139_if_only_post_k_return_add_llvm_exe.sh b/tools/smokes/v2/profiles/integration/apps/phase139_if_only_post_k_return_add_llvm_exe.sh new file mode 100644 index 00000000..22b6b995 --- /dev/null +++ b/tools/smokes/v2/profiles/integration/apps/phase139_if_only_post_k_return_add_llvm_exe.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Phase 139 P0: if-only post_k return add (LLVM EXE parity) +# Pattern: x=1; if flag==1 { x=2 } else { x=1 }; return x+2 (should be 4) + +source "$(dirname "$0")/../../../lib/test_runner.sh" +source "$(dirname "$0")/../../../lib/llvm_exe_runner.sh" +export SMOKES_USE_PYVM=0 +require_env || exit 2 + +llvm_exe_preflight_or_skip || exit 0 + +# Phase 139 is a dev-only Normalized shadow post-if case. +require_joinir_dev + +# Minimal plugin set (StringBox, ConsoleBox, IntegerBox only) +STRINGBOX_SO="$NYASH_ROOT/plugins/nyash-string-plugin/libnyash_string_plugin.so" +CONSOLEBOX_SO="$NYASH_ROOT/plugins/nyash-console-plugin/libnyash_console_plugin.so" +INTEGERBOX_SO="$NYASH_ROOT/plugins/nyash-integer-plugin/libnyash_integer_plugin.so" + +LLVM_REQUIRED_PLUGINS=( + "StringBox|$STRINGBOX_SO|nyash-string-plugin" + "ConsoleBox|$CONSOLEBOX_SO|nyash-console-plugin" + "IntegerBox|$INTEGERBOX_SO|nyash-integer-plugin" +) +LLVM_PLUGIN_BUILD_LOG="/tmp/phase139_if_only_post_k_return_add_plugin_build.log" +llvm_exe_ensure_plugins_or_fail || exit 1 + +INPUT_HAKO="$NYASH_ROOT/apps/tests/phase139_if_only_post_k_return_add_min.hako" +OUTPUT_EXE="$NYASH_ROOT/tmp/phase139_if_only_post_k_return_add_llvm_exe" + +EXPECTED_EXIT_CODE=4 +LLVM_BUILD_LOG="/tmp/phase139_if_only_post_k_return_add_build.log" +if llvm_exe_build_and_run_expect_exit_code; then + test_pass "phase139_if_only_post_k_return_add_llvm_exe: exit code matches expected (4)" +else + exit 1 +fi + diff --git a/tools/smokes/v2/profiles/integration/apps/phase139_if_only_post_k_return_add_vm.sh b/tools/smokes/v2/profiles/integration/apps/phase139_if_only_post_k_return_add_vm.sh new file mode 100644 index 00000000..89837493 --- /dev/null +++ b/tools/smokes/v2/profiles/integration/apps/phase139_if_only_post_k_return_add_vm.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Phase 139 P0: if-only post_k return add (Normalized shadow, VM) +# +# Verifies that post_if_post_k.rs uses ReturnValueLowererBox for return lowering: +# - x=1; if flag==1 { x=2 } else { x=1 }; return x+2 → exit code 4 +# - Dev-only: NYASH_JOINIR_DEV=1 HAKO_JOINIR_STRICT=1 + +source "$(dirname "$0")/../../../lib/test_runner.sh" +export SMOKES_USE_PYVM=0 +require_env || exit 2 + +PASS_COUNT=0 +FAIL_COUNT=0 +RUN_TIMEOUT_SECS=${RUN_TIMEOUT_SECS:-10} + +echo "[INFO] Phase 139 P0: if-only post_k return add (Normalized shadow, VM)" + +echo "[INFO] Test 1: phase139_if_only_post_k_return_add_min.hako" +INPUT="$NYASH_ROOT/apps/tests/phase139_if_only_post_k_return_add_min.hako" + +set +e +OUTPUT=$(timeout "$RUN_TIMEOUT_SECS" env \ + NYASH_DISABLE_PLUGINS=1 \ + HAKO_JOINIR_STRICT=1 \ + NYASH_JOINIR_DEV=1 \ + "$NYASH_BIN" --backend vm "$INPUT" 2>&1) +EXIT_CODE=$? +set -e + +if [ "$EXIT_CODE" -eq 124 ]; then + echo "[FAIL] hakorune timed out (>${RUN_TIMEOUT_SECS}s)" + FAIL_COUNT=$((FAIL_COUNT + 1)) +elif [ "$EXIT_CODE" -eq 4 ]; then + echo "[PASS] exit code verified: 4" + PASS_COUNT=$((PASS_COUNT + 1)) +else + echo "[FAIL] hakorune failed with exit code $EXIT_CODE (expected 4)" + echo "[INFO] output (tail):" + echo "$OUTPUT" | tail -n 50 || true + FAIL_COUNT=$((FAIL_COUNT + 1)) +fi + +echo "[INFO] PASS: $PASS_COUNT, FAIL: $FAIL_COUNT" + +if [ "$FAIL_COUNT" -eq 0 ]; then + test_pass "phase139_if_only_post_k_return_add_vm: All tests passed" + exit 0 +else + test_fail "phase139_if_only_post_k_return_add_vm: $FAIL_COUNT test(s) failed" + exit 1 +fi + diff --git a/tools/smokes/v2/profiles/integration/apps/phase141_p1_if_only_post_k_return_length_llvm_exe.sh b/tools/smokes/v2/profiles/integration/apps/phase141_p1_if_only_post_k_return_length_llvm_exe.sh new file mode 100644 index 00000000..38f902d4 --- /dev/null +++ b/tools/smokes/v2/profiles/integration/apps/phase141_p1_if_only_post_k_return_length_llvm_exe.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# Phase 141 P1: known intrinsic method call in ExprLowerer (LLVM EXE parity) +# Pattern: s="abc"; if flag==1 {s=s} else {s=s}; return s.length() (should be 3) + +source "$(dirname "$0")/../../../lib/test_runner.sh" +source "$(dirname "$0")/../../../lib/llvm_exe_runner.sh" +export SMOKES_USE_PYVM=0 +require_env || exit 2 + +llvm_exe_preflight_or_skip || exit 0 + +# Phase 141 P1 is a dev-only Normalized shadow case. +require_joinir_dev + +# Minimal plugin set (StringBox + IntegerBox) +STRINGBOX_SO="$NYASH_ROOT/plugins/nyash-string-plugin/libnyash_string_plugin.so" +INTEGERBOX_SO="$NYASH_ROOT/plugins/nyash-integer-plugin/libnyash_integer_plugin.so" + +LLVM_REQUIRED_PLUGINS=( + "StringBox|$STRINGBOX_SO|nyash-string-plugin" + "IntegerBox|$INTEGERBOX_SO|nyash-integer-plugin" +) +LLVM_PLUGIN_BUILD_LOG="/tmp/phase141_p1_if_only_post_k_return_length_plugin_build.log" +llvm_exe_ensure_plugins_or_fail || exit 1 + +INPUT_HAKO="$NYASH_ROOT/apps/tests/phase141_p1_if_only_post_k_return_length_min.hako" +OUTPUT_EXE="$NYASH_ROOT/tmp/phase141_p1_if_only_post_k_return_length_llvm_exe" + +EXPECTED_EXIT_CODE=3 +LLVM_BUILD_LOG="/tmp/phase141_p1_if_only_post_k_return_length_build.log" +if llvm_exe_build_and_run_expect_exit_code; then + test_pass "phase141_p1_if_only_post_k_return_length_llvm_exe: exit code matches expected (3)" +else + exit 1 +fi diff --git a/tools/smokes/v2/profiles/integration/apps/phase141_p1_if_only_post_k_return_length_vm.sh b/tools/smokes/v2/profiles/integration/apps/phase141_p1_if_only_post_k_return_length_vm.sh new file mode 100644 index 00000000..4fb663c2 --- /dev/null +++ b/tools/smokes/v2/profiles/integration/apps/phase141_p1_if_only_post_k_return_length_vm.sh @@ -0,0 +1,51 @@ +#!/bin/bash +# Phase 141 P1: known intrinsic method call in ExprLowerer (Normalized shadow, VM) +# +# Pattern: +# - s="abc"; if flag==1 {s=s} else {s=s}; return s.length() → exit code 3 +# - Dev-only: NYASH_JOINIR_DEV=1 HAKO_JOINIR_STRICT=1 + +source "$(dirname "$0")/../../../lib/test_runner.sh" +export SMOKES_USE_PYVM=0 +require_env || exit 2 + +PASS_COUNT=0 +FAIL_COUNT=0 +RUN_TIMEOUT_SECS=${RUN_TIMEOUT_SECS:-10} + +echo "[INFO] Phase 141 P1: known intrinsic method call in ExprLowerer (Normalized shadow, VM)" + +echo "[INFO] Test 1: phase141_p1_if_only_post_k_return_length_min.hako" +INPUT="$NYASH_ROOT/apps/tests/phase141_p1_if_only_post_k_return_length_min.hako" + +set +e +OUTPUT=$(timeout "$RUN_TIMEOUT_SECS" env \ + NYASH_DISABLE_PLUGINS=1 \ + HAKO_JOINIR_STRICT=1 \ + NYASH_JOINIR_DEV=1 \ + "$NYASH_BIN" --backend vm "$INPUT" 2>&1) +EXIT_CODE=$? +set -e + +if [ "$EXIT_CODE" -eq 124 ]; then + echo "[FAIL] hakorune timed out (>${RUN_TIMEOUT_SECS}s)" + FAIL_COUNT=$((FAIL_COUNT + 1)) +elif [ "$EXIT_CODE" -eq 3 ]; then + echo "[PASS] exit code verified: 3" + PASS_COUNT=$((PASS_COUNT + 1)) +else + echo "[FAIL] hakorune failed with exit code $EXIT_CODE (expected 3)" + echo "[INFO] output (tail):" + echo "$OUTPUT" | tail -n 50 || true + FAIL_COUNT=$((FAIL_COUNT + 1)) +fi + +echo "[INFO] PASS: $PASS_COUNT, FAIL: $FAIL_COUNT" + +if [ "$FAIL_COUNT" -eq 0 ]; then + test_pass "phase141_p1_if_only_post_k_return_length_vm: All tests passed" + exit 0 +else + test_fail "phase141_p1_if_only_post_k_return_length_vm: $FAIL_COUNT test(s) failed" + exit 1 +fi