feat(normalization): Phase 142 P0 - Loop statement-level normalization
Phase 142-loopstmt P0: Statement-level normalization 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -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 のビルド段階でブロック**していた。
|
- 現行の入口: `docs/development/current/main/10-Now.md`
|
||||||
- エラー: `Invalid cross-device link (os error 18)`(`cargo build` が `.rmeta/.rlib` を書き出すときに落ちる)
|
- 次の候補: `docs/development/current/main/30-Backlog.md`
|
||||||
- 2025-12-18: `wsl --shutdown` による WSL 再起動後、`cargo build`/LLVM build/該当 smokes が復活(下のコマンドは PASS)。
|
|
||||||
|
|
||||||
### 症状(再現コマンド)
|
### 直近の道筋(JoinIR / Normalized)
|
||||||
|
|
||||||
- `cargo build -p nyash-rust`
|
- Phase 139: if-only `post_k` の return lowering を `ReturnValueLowererBox` に統一(DONE)
|
||||||
- `cargo build --release -p nyash-rust --features llvm`
|
- `docs/development/current/main/phases/phase-139/README.md`
|
||||||
- LLVM EXE smokes(内部で release build するので同様に失敗)
|
- Phase 140: `NormalizedExprLowererBox` 初版(pure expression のみ)(DONE)
|
||||||
- `bash tools/smokes/v2/profiles/integration/apps/phase131_loop_true_break_once_llvm_exe.sh`
|
- SSOT: `docs/development/current/main/design/normalized-expr-lowering.md`
|
||||||
- `bash tools/smokes/v2/profiles/integration/apps/phase97_next_non_ws_llvm_exe.sh`
|
- `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
|
### WSL EXDEV / cargo build failure (resolved)
|
||||||
- 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`
|
|
||||||
|
|
||||||
### 根本原因メモ(環境)
|
- 2025-12-18: `Invalid cross-device link (os error 18)` により `cargo build` が失敗する事象があったが、`wsl --shutdown` 再起動後に復旧。
|
||||||
|
- 再発時のワークアラウンド: `tools/build_llvm.sh` は EXDEV を避けるため `TMPDIR` を `target/...` 配下へ寄せる。
|
||||||
- WSL2 上で、`O_TMPFILE` で作った一時ファイルを `/proc/self/fd/<n>` 経由で `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`
|
|
||||||
|
|||||||
29
apps/tests/phase139_if_only_post_k_return_add_min.hako
Normal file
29
apps/tests/phase139_if_only_post_k_return_add_min.hako
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
27
apps/tests/phase141_p1_if_only_post_k_return_length_min.hako
Normal file
27
apps/tests/phase141_p1_if_only_post_k_return_length_min.hako
Normal file
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,104 @@
|
|||||||
# Self Current Task — Now (main)
|
# 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 完了 ✅
|
## 2025-12-18:Phase 138 完了 ✅
|
||||||
|
|
||||||
**Phase 138: ReturnValueLowererBox - Return Lowering SSOT**
|
**Phase 138: ReturnValueLowererBox - Return Lowering SSOT**
|
||||||
|
|||||||
@ -5,6 +5,11 @@
|
|||||||
- promoted carriers(DigitPos/Trim などの synthetic name)は、`BindingId(original) → BindingId(promoted) → ValueId(join)` の鎖で接続し、by-name ルール分岐は導入しない。
|
- promoted carriers(DigitPos/Trim などの synthetic name)は、`BindingId(original) → BindingId(promoted) → ValueId(join)` の鎖で接続し、by-name ルール分岐は導入しない。
|
||||||
- debug/観測は既存のフラグ(例: `NYASH_JOINIR_DEBUG`)に集約し、新しい環境変数のスパローは避ける。
|
- 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
|
2025‑09‑08
|
||||||
- ループ制御は既存命令(Branch/Jump/Phi)で表現し、新命令は導入しない。
|
- ループ制御は既存命令(Branch/Jump/Phi)で表現し、新命令は導入しない。
|
||||||
- Builder に loop_ctx({head, exit})を導入し、continue/break を分岐で降ろす。
|
- Builder に loop_ctx({head, exit})を導入し、continue/break を分岐で降ろす。
|
||||||
|
|||||||
@ -8,9 +8,27 @@ Related:
|
|||||||
|
|
||||||
## 直近(JoinIR/selfhost)
|
## 直近(JoinIR/selfhost)
|
||||||
|
|
||||||
- **Phase 131(Normalized loop lowering foundation / 入口作成)**
|
- **Phase 142-loopstmt P1(planned): LLVM EXE smoke を追加**
|
||||||
- ねらい: loop/if の “構造SSOT(StepTree/LoopSkeleton)” を使い、Normalized loop の最小骨格を固める
|
- 前提(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)**
|
- **real-app loop regression の横展開(VM + LLVM EXE)**
|
||||||
- ねらい: 実コード由来ループを 1 本ずつ最小抽出して fixture/smoke で固定する(段階投入)。
|
- ねらい: 実コード由来ループを 1 本ずつ最小抽出して fixture/smoke で固定する(段階投入)。
|
||||||
|
|||||||
@ -10,3 +10,4 @@
|
|||||||
- JoinIR の地図(navigation SSOT): `docs/development/current/main/design/joinir-design-map.md`
|
- JoinIR の地図(navigation SSOT): `docs/development/current/main/design/joinir-design-map.md`
|
||||||
- Loop Canonicalizer(設計 SSOT): `docs/development/current/main/design/loop-canonicalizer.md`
|
- Loop Canonicalizer(設計 SSOT): `docs/development/current/main/design/loop-canonicalizer.md`
|
||||||
- ControlTree / StepTree(構造SSOT): `docs/development/current/main/design/control-tree.md`
|
- ControlTree / StepTree(構造SSOT): `docs/development/current/main/design/control-tree.md`
|
||||||
|
- Normalized ExprLowerer(式の一般化 SSOT): `docs/development/current/main/design/normalized-expr-lowering.md`
|
||||||
|
|||||||
@ -46,3 +46,7 @@ NYASH_LLVM_DUMP_IR=<path> # Save LLVM IR to file
|
|||||||
## Archive
|
## Archive
|
||||||
|
|
||||||
Completed investigations are kept for reference and pattern recognition.
|
Completed investigations are kept for reference and pattern recognition.
|
||||||
|
|
||||||
|
### JoinIR Generalization Study (Historical)
|
||||||
|
|
||||||
|
- `joinir-generalization-study.md`(Phase 131–138 の状況と一般化案の相談用コンテキスト。SSOT は design/ を参照)
|
||||||
|
|||||||
@ -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) { <assign>* ; break } パターンを JoinModule に変換
|
||||||
|
//! - PHI-free: env パラメータ + 継続で値を渡す
|
||||||
|
//! - Return lowering は ReturnValueLowererBox に委譲
|
||||||
|
//!
|
||||||
|
//! ## 生成される構造
|
||||||
|
//! main(env)
|
||||||
|
//! → TailCall(loop_step, env)
|
||||||
|
//! loop_step(env)
|
||||||
|
//! → TailCall(loop_body, env)
|
||||||
|
//! loop_body(env)
|
||||||
|
//! → <assign statements> → TailCall(k_exit, env)
|
||||||
|
//! k_exit(env)
|
||||||
|
//! → Ret(env[x]) or TailCall(post_k, env)
|
||||||
|
//! post_k(env) [Phase 132-P4+]
|
||||||
|
//! → <post assign> → Ret(env[x])
|
||||||
|
|
||||||
|
pub struct LoopTrueBreakOnceBuilderBox;
|
||||||
|
|
||||||
|
impl LoopTrueBreakOnceBuilderBox {
|
||||||
|
pub fn lower(
|
||||||
|
step_tree: &StepTree,
|
||||||
|
env_layout: &EnvLayout,
|
||||||
|
) -> Result<Option<(JoinModule, JoinFragmentMeta)>, String> {
|
||||||
|
// 1. Extract loop(true) { body ; break } [; <post>] 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<String, ValueId>)
|
||||||
|
|
||||||
|
// 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<String, ValueId> - 変数 → 値の対応
|
||||||
|
- 継続関数は全て 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<AstNodeHandle>,
|
||||||
|
body: &mut Vec<JoinInst>,
|
||||||
|
next_value_id: &mut u32,
|
||||||
|
env: &BTreeMap<String, ValueId>,
|
||||||
|
) -> Result<Option<ValueId>, 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<n) は Ok(None))
|
||||||
|
//! - post-loop statements の形を検出
|
||||||
|
|
||||||
|
pub struct NormalizationPlanBox;
|
||||||
|
|
||||||
|
impl NormalizationPlanBox {
|
||||||
|
pub fn plan_block_suffix(
|
||||||
|
remaining: &[ASTNode],
|
||||||
|
...,
|
||||||
|
) -> Result<Option<NormalizationPlan>, 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<n) など
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Count post-loop assignments
|
||||||
|
let mut post_assign_count = 0;
|
||||||
|
for i in 1..remaining.len() {
|
||||||
|
if matches!(&remaining[i], ASTNode::Assignment { .. }) {
|
||||||
|
post_assign_count += 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. After assignments, must have return
|
||||||
|
let return_index = 1 + post_assign_count;
|
||||||
|
if return_index >= 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<ValueId, String> {
|
||||||
|
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<ValueId, String> {
|
||||||
|
// 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<JoinInst>,
|
||||||
|
next_value_id: &mut u32,
|
||||||
|
env: &BTreeMap<String, ValueId>,
|
||||||
|
) -> Result<Option<ValueId>, 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 コンパイラを実装予定)
|
||||||
@ -4,12 +4,15 @@
|
|||||||
|
|
||||||
## 現在の Phase
|
## 現在の Phase
|
||||||
|
|
||||||
- **Phase 132**: Exit Values Parity (VM == LLVM)
|
- **Phase 139(DONE)**: post-if `post_k` の return lowering を `ReturnValueLowererBox` に統一(出口 SSOT 完成)
|
||||||
- **Phase 133**: Promoted carrier join_id(Trim)修正
|
- **Phase 140(DONE)**: `NormalizedExprLowererBox` 初版(pure expression のみ)
|
||||||
- **Phase 134**: Plugin loader best-effort loading
|
- **Phase 141 P0(DONE)**: impure 拡張点(contract)を SSOT 化(Call/MethodCall はまだ out-of-scope)
|
||||||
- **Phase 135**: ConditionLoweringBox allocator SSOT(ValueId 衝突の根治)
|
- **Phase 141 P1(DONE)**: “既知 intrinsic だけ” を許可して段階投入(length0)
|
||||||
- **Phase 136**: MirBuilder Context SSOT 化(+ ValueId allocator 掃討)
|
- **Phase 141 P1.5(DONE)**: known intrinsic registry + available_inputs 3-source merge + diagnostics
|
||||||
- **Phase 137–141**: Loop Canonicalizer(前処理 SSOT)導入(Phase 137 フォルダに統合して記録)
|
- **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 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 94–100**: P5b escape E2E / Trim policy / pinned + accumulator(VM/LLVM EXE parity)
|
||||||
- **Phase 102**: real-app read_quoted loop regression(VM + LLVM EXE)
|
- **Phase 102**: real-app read_quoted loop regression(VM + LLVM EXE)
|
||||||
@ -52,4 +55,4 @@ phases/phase-131/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**最終更新**: 2025-12-18
|
**最終更新**: 2025-12-19
|
||||||
|
|||||||
46
docs/development/current/main/phases/phase-139/README.md
Normal file
46
docs/development/current/main/phases/phase-139/README.md
Normal file
@ -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<n) フォールバック確認)
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
|
||||||
|
- `post_if_post_k.rs` の return lowering が `ReturnValueLowererBox` に一本化されている
|
||||||
|
- 既存の挙動を壊さない(dev-only、既定挙動不変)
|
||||||
|
- VM/LLVM EXE parity を fixture/smoke で固定
|
||||||
62
docs/development/current/main/phases/phase-140/README.md
Normal file
62
docs/development/current/main/phases/phase-140/README.md
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# Phase 140: NormalizedExprLowererBox (pure expressions)
|
||||||
|
|
||||||
|
Status: DONE ✅
|
||||||
|
Scope: Normalized shadow の expression lowering を AST walker で一般化し、パターン爆発を避ける(pure のみ)。
|
||||||
|
Related:
|
||||||
|
- `docs/development/current/main/design/normalized-expr-lowering.md`
|
||||||
|
- `docs/development/current/main/30-Backlog.md`
|
||||||
|
- `docs/development/current/main/phases/phase-139/README.md`
|
||||||
|
- `src/mir/control_tree/normalized_shadow/common/expr_lowerer_box.rs`
|
||||||
|
- `src/mir/control_tree/normalized_shadow/common/return_value_lowerer_box.rs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
- `NormalizedExprLowererBox` を導入し、pure expression を JoinIR(Normalized dialect)へ lowering できるようにする。
|
||||||
|
- return lowering は `ReturnValueLowererBox` → `ExprLowererBox` 委譲へ寄せ、return の形追加を “総当たり” にしない。
|
||||||
|
|
||||||
|
## Scope (Phase 140 P0)
|
||||||
|
|
||||||
|
### ✅ In scope: pure expression
|
||||||
|
|
||||||
|
- `Variable`
|
||||||
|
- `Literal`(Integer/Bool から開始)
|
||||||
|
- `UnaryOp`(not, -)
|
||||||
|
- `BinaryOp`(+ - * /)
|
||||||
|
- `Compare`(==, <, <=, >, >=)
|
||||||
|
|
||||||
|
### ❌ Out of scope
|
||||||
|
|
||||||
|
- Call/MethodCall(Phase 141+)
|
||||||
|
- Short-circuit(&&/||)の制御フローを伴う lowering
|
||||||
|
- 例外/throw/try/catch
|
||||||
|
- NormalizationPlan の粒度変更(suffix→statement などの再設計は Phase 141+ の検討に回す)
|
||||||
|
|
||||||
|
## Contract
|
||||||
|
|
||||||
|
`NormalizedExprLowererBox::lower_expr(...) -> Result<Option<ValueId>, 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
|
||||||
89
docs/development/current/main/phases/phase-141/README.md
Normal file
89
docs/development/current/main/phases/phase-141/README.md
Normal file
@ -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 を分離して拡張)
|
||||||
@ -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
|
||||||
@ -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(<cond>)`(条件付き 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 化)
|
||||||
@ -651,10 +651,13 @@ pub(super) fn merge_and_rewrite(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Phase 33-20: Also set latch incoming for other carriers from exit_bindings
|
// 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
|
// args layout depends on whether we have a dedicated loop variable:
|
||||||
// The remaining non-loop-variable carriers are ordered to match args[1..] (after the loop variable)
|
// - loop_var_name = Some(..): args[0] is the loop variable, args[1..] are other carriers
|
||||||
let mut carrier_arg_idx = 1; // Start from args[1] (args[0] is loop variable)
|
// - 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() {
|
for binding in b.exit_bindings.iter() {
|
||||||
// Skip if this binding is for the loop variable (already handled)
|
// Skip if this binding is for the loop variable (already handled)
|
||||||
if let Some(ref loop_var) = b.loop_var_name {
|
if let Some(ref loop_var) = b.loop_var_name {
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
//! ReturnValueLowererBox: Return value lowering SSOT (Phase 138 P0)
|
//! ReturnValueLowererBox: Return syntax lowering (Phase 140 P0)
|
||||||
//!
|
//!
|
||||||
//! ## Responsibility
|
//! ## Responsibility
|
||||||
//!
|
//!
|
||||||
//! Lower return values (variable, literal, expr) to ValueId for Normalized shadow paths
|
//! Normalize return value syntax (`return` vs `return <expr>`) for Normalized shadow paths.
|
||||||
//!
|
//!
|
||||||
//! ## Supported Patterns (Phase 136-137)
|
//! Expression lowering is delegated to `NormalizedExprLowererBox` (SSOT).
|
||||||
//!
|
|
||||||
//! - Variable: env lookup → ValueId
|
|
||||||
//! - Integer literal: Const generation → ValueId
|
|
||||||
//! - Add expr: (var|int) + int → BinOp(Add, lhs, rhs) → ValueId
|
|
||||||
//!
|
//!
|
||||||
//! ## Fallback
|
//! ## Fallback
|
||||||
//!
|
//!
|
||||||
@ -17,11 +13,12 @@
|
|||||||
//! ## Usage
|
//! ## Usage
|
||||||
//!
|
//!
|
||||||
//! - Phase 138 P0: loop_true_break_once.rs
|
//! - 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::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 crate::mir::ValueId;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
@ -31,8 +28,6 @@ pub struct ReturnValueLowererBox;
|
|||||||
impl ReturnValueLowererBox {
|
impl ReturnValueLowererBox {
|
||||||
/// Lower return value to ValueId
|
/// Lower return value to ValueId
|
||||||
///
|
///
|
||||||
/// Phase 136-137: Support variable, integer literal, and add expression
|
|
||||||
///
|
|
||||||
/// Returns:
|
/// Returns:
|
||||||
/// - Ok(Some(vid)): Successfully lowered to ValueId
|
/// - Ok(Some(vid)): Successfully lowered to ValueId
|
||||||
/// - Ok(None): Out of scope (fallback to legacy routing)
|
/// - Ok(None): Out of scope (fallback to legacy routing)
|
||||||
@ -51,111 +46,16 @@ impl ReturnValueLowererBox {
|
|||||||
Ok(Some(ValueId(0))) // Dummy - caller handles void separately
|
Ok(Some(ValueId(0))) // Dummy - caller handles void separately
|
||||||
}
|
}
|
||||||
Some(ast_handle) => {
|
Some(ast_handle) => {
|
||||||
match ast_handle.0.as_ref() {
|
// Phase 141 P1: allow a small allowlist of known intrinsic method calls.
|
||||||
// Variable: lookup in env
|
NormalizedExprLowererBox::lower_expr_with_scope(
|
||||||
ASTNode::Variable { name, .. } => {
|
ExprLoweringScope::WithImpure(ImpurePolicy::KnownIntrinsicOnly),
|
||||||
if let Some(&vid) = env.get(name) {
|
ast_handle.0.as_ref(),
|
||||||
Ok(Some(vid))
|
env,
|
||||||
} else {
|
body,
|
||||||
// Variable not in env - out of scope
|
next_value_id,
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lower binary operation to ValueId (Phase 137)
|
|
||||||
fn lower_binary_op(
|
|
||||||
operator: &BinaryOperator,
|
|
||||||
left: &ASTNode,
|
|
||||||
right: &ASTNode,
|
|
||||||
body: &mut Vec<JoinInst>,
|
|
||||||
next_value_id: &mut u32,
|
|
||||||
env: &BTreeMap<String, ValueId>,
|
|
||||||
) -> Result<Option<ValueId>, 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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,37 +64,32 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
|
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
|
||||||
use crate::mir::control_tree::step_tree::AstNodeHandle;
|
use crate::mir::control_tree::step_tree::AstNodeHandle;
|
||||||
|
use crate::mir::join_ir::JoinInst;
|
||||||
|
|
||||||
fn make_span() -> Span {
|
fn make_span() -> Span {
|
||||||
Span::unknown()
|
Span::unknown()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lower_variable() {
|
fn test_void_return_dummy_value_id() {
|
||||||
let mut body = vec![];
|
let mut body = vec![];
|
||||||
let mut next_value_id = 100;
|
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(
|
let result = ReturnValueLowererBox::lower_to_value_id(
|
||||||
&Some(var_ast),
|
&None,
|
||||||
&mut body,
|
&mut body,
|
||||||
&mut next_value_id,
|
&mut next_value_id,
|
||||||
&env,
|
&BTreeMap::new(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(result, Some(ValueId(42)));
|
assert_eq!(result, Some(ValueId(0)));
|
||||||
assert_eq!(body.len(), 0); // No instructions emitted
|
assert!(body.is_empty());
|
||||||
|
assert_eq!(next_value_id, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lower_integer_literal() {
|
fn test_delegates_integer_literal() {
|
||||||
let mut body = vec![];
|
let mut body = vec![];
|
||||||
let mut next_value_id = 100;
|
let mut next_value_id = 100;
|
||||||
let env = BTreeMap::new();
|
let env = BTreeMap::new();
|
||||||
@ -218,7 +113,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lower_add_var_plus_int() {
|
fn test_delegates_add_var_plus_int() {
|
||||||
let mut body = vec![];
|
let mut body = vec![];
|
||||||
let mut next_value_id = 100;
|
let mut next_value_id = 100;
|
||||||
let mut env = BTreeMap::new();
|
let mut env = BTreeMap::new();
|
||||||
@ -251,66 +146,36 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lower_add_int_plus_int() {
|
fn test_delegates_known_intrinsic_method_call_length0() {
|
||||||
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() {
|
|
||||||
let mut body = vec![];
|
let mut body = vec![];
|
||||||
let mut next_value_id = 100;
|
let mut next_value_id = 100;
|
||||||
let mut env = BTreeMap::new();
|
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 {
|
let length_ast = AstNodeHandle(Box::new(ASTNode::MethodCall {
|
||||||
operator: BinaryOperator::Subtract,
|
object: Box::new(ASTNode::Variable {
|
||||||
left: Box::new(ASTNode::Variable {
|
name: "s".to_string(),
|
||||||
name: "x".to_string(),
|
|
||||||
span: make_span(),
|
|
||||||
}),
|
|
||||||
right: Box::new(ASTNode::Literal {
|
|
||||||
value: LiteralValue::Integer(2),
|
|
||||||
span: make_span(),
|
span: make_span(),
|
||||||
}),
|
}),
|
||||||
|
method: "length".to_string(),
|
||||||
|
arguments: vec![],
|
||||||
span: make_span(),
|
span: make_span(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let result = ReturnValueLowererBox::lower_to_value_id(
|
let result = ReturnValueLowererBox::lower_to_value_id(
|
||||||
&Some(sub_ast),
|
&Some(length_ast),
|
||||||
&mut body,
|
&mut body,
|
||||||
&mut next_value_id,
|
&mut next_value_id,
|
||||||
&env,
|
&env,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(result, None); // Out of scope
|
assert_eq!(result, Some(ValueId(100)));
|
||||||
assert_eq!(body.len(), 0); // No instructions emitted
|
assert_eq!(next_value_id, 101);
|
||||||
|
assert!(matches!(
|
||||||
|
body.as_slice(),
|
||||||
|
[JoinInst::MethodCall { method, args, .. }]
|
||||||
|
if method == "length" && args.is_empty()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -841,4 +841,7 @@ mod tests {
|
|||||||
"loop-only bridged MIR must have a single consistent k_exit arg[0]"
|
"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.
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
//!
|
//!
|
||||||
//! ## Scope
|
//! ## 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)
|
//! - If body: Assign(int literal) only (Phase 128 baseline)
|
||||||
//! - Condition: minimal compare only (Phase 123 baseline)
|
//! - Condition: minimal compare only (Phase 123 baseline)
|
||||||
//!
|
//!
|
||||||
@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
use super::env_layout::EnvLayout;
|
use super::env_layout::EnvLayout;
|
||||||
use super::legacy::LegacyLowerer;
|
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::control_tree::step_tree::{StepNode, StepStmtKind, StepTree};
|
||||||
use crate::mir::join_ir::lowering::carrier_info::JoinFragmentMeta;
|
use crate::mir::join_ir::lowering::carrier_info::JoinFragmentMeta;
|
||||||
use crate::mir::join_ir::lowering::error_tags;
|
use crate::mir::join_ir::lowering::error_tags;
|
||||||
@ -251,19 +252,24 @@ impl PostIfPostKBuilderBox {
|
|||||||
match n {
|
match n {
|
||||||
StepNode::Stmt { kind, .. } => match kind {
|
StepNode::Stmt { kind, .. } => match kind {
|
||||||
StepStmtKind::Return { value_ast } => {
|
StepStmtKind::Return { value_ast } => {
|
||||||
// Phase 124/125/126: Return(Variable) support
|
if let Some(_ast_handle) = value_ast {
|
||||||
if LegacyLowerer::lower_return_value(
|
match ReturnValueLowererBox::lower_to_value_id(
|
||||||
value_ast,
|
value_ast,
|
||||||
&mut post_k_func.body,
|
&mut post_k_func.body,
|
||||||
&mut next_value_id,
|
&mut next_value_id,
|
||||||
&env_post_k,
|
&env_post_k,
|
||||||
&step_tree.contract,
|
)? {
|
||||||
)
|
Some(vid) => {
|
||||||
.is_err()
|
post_k_func.body.push(JoinInst::Ret { value: Some(vid) });
|
||||||
{
|
}
|
||||||
|
None => {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
post_k_func.body.push(JoinInst::Ret { value: None });
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Ok(None); // Unsupported post-if statement
|
return Ok(None); // Unsupported post-if statement
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
@ -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
|
||||||
Reference in New Issue
Block a user