Span trace utilities and runner source hint
This commit is contained in:
@ -12,16 +12,17 @@
|
||||
- **25.x**: Stage0/Stage1/Stage‑B / Selfhost ラインのブートストラップと LoopForm v2 / LoopSSA v2 まわりの整備。
|
||||
- **25.1 系**: Stage‑B / Stage‑1 / selfhost 向けに、Rust MIR / LoopForm v2 / LoopSSA v2 を段階的に整える長期ライン。
|
||||
- **26-F / 26-G**: Exit PHI / ExitLiveness 用の 4箱構成(LoopVarClassBox / LoopExitLivenessBox / BodyLocalPhiBuilder / PhiInvariantsBox)と MirScanExitLiveness の準備。
|
||||
- **26-H / 27.x(New)**: JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim/Stage‑1 UsingResolver minimal/FuncScanner.append_defs minimal までを対象に、制御構造を関数呼び出しに正規化する IR とランナーを段階的に整備中(27.4 で Header φ を LoopHeaderShape 化、27.5 で Exit φ の意味を LoopExitShape として固定済み。27.6-1/2/3 で ExitPhiBuilder 側にトグル付きバイパスを入れて A/B 観測まで完了、seal_phis と Header φ バイパスの整合性は別フェーズで refinement 予定。27.8〜27.11 で skip_ws/trim を Shared Builder Pattern+MIR-based lowering に移行し、27.12/27.13 で Stage‑1 UsingResolver minimal loop も同じ型に乗せ、27.14 では FuncScanner.append_defs 用の lowering+minimal .hako+auto_lowering テストまで整備済み。短期フェーズ残りは JoinIR→Rust VM ブリッジの最小実装と、それを使った skip_ws/trim/Stage‑1 minimal あたりの A/B テスト整備)。
|
||||
- **26-H / 27.x(New)**: JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim/Stage‑1 UsingResolver minimal/FuncScanner.append_defs minimal/StageB minimal までを対象に、制御構造を関数呼び出しに正規化する IR とランナーを段階的に整備中(27.4 で Header φ を LoopHeaderShape 化、27.5 で Exit φ の意味を LoopExitShape として固定済み。27.6-1/2/3 で ExitPhiBuilder 側にトグル付きバイパスを入れて A/B 観測まで完了、seal_phis と Header φ バイパスの整合性は別フェーズで refinement 済み。27.8〜27.11/27.13/27.14 で skip_ws/trim/Stage‑1 UsingResolver minimal/FuncScanner.append_defs minimal/StageB minimal を Shared Builder Pattern+MIR-based lowering に移行し、ValueId 範囲管理も一元化。27.8-27.11/27.13/27.15 で JoinIR Runner を JoinValue↔VMValue 変換+`MirInterpreter::execute_box_call` ラッパ経由で Rust VM の BoxCall 意味論と統合し、skip_ws/trim の JoinIR Runner スタンドアロン実行が安定(GC/BoxRef も Arc ベースで統合済み)。Phase 27-shortterm の S‑1〜S‑5.4 は完了、Phase 28-midterm では per-loop lowering を増やさず、LoopForm+LoopVarClassBox+LoopExitLivenessBox を入力にした「汎用 Loop→JoinIR ロワー」に畳み込んでいく方針に切り替え済み(join-ir.md に JoinIR ロワーが“やらないこと”チェックリストを明記)。
|
||||
- Rust 側:
|
||||
- LoopForm v2 + ControlForm + Conservative PHI は、代表テスト(Stage‑1 UsingResolver / Stage‑B 最小ループ)ではほぼ安定。
|
||||
- 静的メソッド呼び出し規約と `continue` 絡みの PHI は 25.1m までで根治済み。
|
||||
- .hako 側:
|
||||
- Stage‑B コンパイラ本体 / LoopSSA v2 / BreakFinderBox / PhiInjectorBox はまだ部分実装。
|
||||
- JSON v0 / selfhost ルートは Rust 側の LoopForm v2 規約に追いつかせる必要がある。
|
||||
- JSON v0 / selfhost ルートは Rust 側の LoopForm v2 規約に追いつかせる必要がある。Stage‑B selfhost 経路は依然として Program JSON が取れていないが、直近の実行では `Unknown method 'main' on InstanceBox` は再現しておらず、別の箇所で落ちている可能性が高い。Stage‑1 CLI ブリッジ自体は Rust バイナリ経由で発火して `stage1_cli.hako` 実行までは到達しているが、その後 BuildBox.emit_program_json_v0 → `ParserBox.parse_program2` 実行中に VM 側の step budget 超過(max_steps 1_000_000→2_000_000 でも NG)とプラグイン未ロード警告で止まっており、Parser 側の本体ループが budget を食い尽くしている可能性が高い(詳細は stage1-cli-loop-analysis.md と private 側の parser-budget メモ参照)。Rust VM の budget エラーは fn/bb/last_inst + Span (あれば file:line:col) 付きで出るようにしたので、Span が入る MIR なら再発時の位置特定がしやすい(Stage‑1 CLI の現在の MIR には Span が無く行番号は未表示)。
|
||||
- Stage‑B / FuncScanner ライン:
|
||||
- Phase 25.3 をクローズし、`stageb_fib_program_defs_canary_vm.sh` が緑(`defs` に `TestBox.fib/Main.main`、fib.body.body[*] に `Loop`)。
|
||||
- Stage‑B は block パーサ優先 + defs を Block 包みで構造化。次手: Stage‑1 UsingResolver ループの Region+next_i 揃え / Stage‑1 CLI program-json selfhost 準備。
|
||||
- Stage‑1 CLI 実験: `NYASH_USE_STAGE1_CLI=1 STAGE1_EMIT_PROGRAM_JSON=1 ./target/release/hakorune apps/tests/minimal_ssa_skip_ws.hako` でブリッジ発火・stage1_cli.hako 実行は確認できたが、VM が max_steps 2000000 を超過して中断(プラグイン未ロード警告あり)。budget 超過メッセージには `fn=ParserBox.parse_program2/1 ... (lang/src/runner/stage1_cli.hako:1:1)` まで出るようになった(Span 配線は通ったが位置精度はまだ粗い)。selfhost ビルド経路(tools/selfhost/build_stage1.sh)は StageBDriverBox.main 呼び出しで Unknown method 'main' on InstanceBox → Program JSON 未生成。
|
||||
|
||||
---
|
||||
|
||||
|
||||
21
apps/tests/stageb_body_extract_minimal.hako
Normal file
21
apps/tests/stageb_body_extract_minimal.hako
Normal file
@ -0,0 +1,21 @@
|
||||
// stageb_body_extract_minimal.hako
|
||||
// Phase 28: StageBBodyExtractorBox.build_body_src minimal loop for JoinIR testing
|
||||
// - Case A: loop(i < n) with next_i and simple accumulator
|
||||
|
||||
static box StageBBodyExtractorBox {
|
||||
build_body_src(src, args) {
|
||||
local n = src.length()
|
||||
local i = 0
|
||||
local acc = 0
|
||||
loop(i < n) {
|
||||
local next_i = i + 1
|
||||
acc = acc + 1
|
||||
i = next_i
|
||||
}
|
||||
return acc
|
||||
}
|
||||
}
|
||||
|
||||
static box Main {
|
||||
main() { return 0 }
|
||||
}
|
||||
19
apps/tests/stageb_funcscanner_scan_boxes_minimal.hako
Normal file
19
apps/tests/stageb_funcscanner_scan_boxes_minimal.hako
Normal file
@ -0,0 +1,19 @@
|
||||
// stageb_funcscanner_scan_boxes_minimal.hako
|
||||
// Phase 28: StageBFuncScannerBox.scan_all_boxes minimal loop for JoinIR testing
|
||||
// - Case A: loop(i < n) linear scan, defs is a placeholder accumulator
|
||||
|
||||
static box StageBFuncScannerBox {
|
||||
scan_all_boxes(src) {
|
||||
local n = src.length()
|
||||
local i = 0
|
||||
local defs = 0
|
||||
loop(i < n) {
|
||||
i = i + 1
|
||||
}
|
||||
return defs
|
||||
}
|
||||
}
|
||||
|
||||
static box Main {
|
||||
main() { return 0 }
|
||||
}
|
||||
@ -14,6 +14,26 @@
|
||||
- JoinIR は「PHI/SSA の実装負担を肩代わりする層」として導入し、ヘッダ/exit φ や BodyLocal の扱いを **関数の引数と継続** に吸収していく。
|
||||
- PHI 専用の箱(HeaderPhiBuilder / ExitPhiBuilder / BodyLocalPhiBuilder など)は、最終的には JoinIR 降ろしの補助に縮退させることを目標にする。
|
||||
|
||||
### JoinIR ロワーが「やらないこと」チェックリスト(暴走防止用)
|
||||
|
||||
JoinIR への変換はあくまで「LoopForm で正規化された形」を前提にした **薄い汎用ロワー** に寄せる。
|
||||
このため、以下は **JoinIR ロワーでは絶対にやらない**こととして明示しておく。
|
||||
|
||||
- 条件式の **中身** を解析しない(`i < n` か `flag && x != 0` かを理解しない)
|
||||
- 見るのは「header ブロックの succ が 2 本あって、LoopForm が body/exit を教えてくれるか」だけ。
|
||||
- 多重ヘッダ・ネストループを自力で扱おうとしない
|
||||
- LoopForm 側で「単一 header / 単一 latch のループ」として正規化できないものは、JoinIR 対象外(フォールバック)。
|
||||
- LoopForm/VarClass で判定できない BodyLocal/ExitLiveness を、JoinIR 側で推測しない
|
||||
- pinned/carrier/exit 値は LoopVarClassBox / LoopExitLivenessBox からだけ受け取り、独自解析はしない。
|
||||
- 各ループ用に「特別な lowering 分岐」を増やさない
|
||||
- `skip_ws` / `trim` / `stageb_*` / `stage1_using_resolver` 向けの per-loop lowering は Phase 27.x の実験用足場であり、最終的には Case A/B/D 向けの汎用ロワーに吸収する。
|
||||
|
||||
> 要するに: JoinIR は「LoopForm+変数分類の結果」だけを入力にして、
|
||||
> ループ/if の **構造(続行 or exit の2択+持ち回り変数)** を関数呼び出しに写す箱だよ。
|
||||
> 構文パターンや条件式そのものを全網羅で理解しに行くのは、この層の責務から外す。
|
||||
|
||||
Phase 28 メモ: generic_case_a ロワーは LoopForm / LoopVarClassBox / LoopExitLivenessBox から実データを読み、minimal_skip_ws の JoinIR を組み立てるステップに進化中。Case A/B/D を汎用ロワーに畳み込む足場として扱う。
|
||||
|
||||
位置づけ
|
||||
- 変換パイプラインにおける位置:
|
||||
|
||||
@ -258,6 +278,115 @@ enum JoinInst {
|
||||
| 構文/概念 | JoinIR での表現 | φ/合流の扱い |
|
||||
|-----------|-----------------|--------------|
|
||||
| if/merge | join 関数呼び出し | join 関数の引数が φ 相当 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 汎用 LoopForm→JoinIR ロワー設計(Case A/B ベース案)
|
||||
|
||||
Phase 28 以降は、`skip_ws` や `trim` のような「ループごとの lowering」を増やすのではなく、
|
||||
LoopForm v2 と変数分類箱を入力にした **汎用ロワー** で Case A/B 型ループをまとめて JoinIR に落とすのがゴールになる。
|
||||
|
||||
ここでは、その v1 として **単一ヘッダの Case A/B ループ** を対象にした設計をまとめておく。
|
||||
|
||||
### 7-1. 入力と前提条件
|
||||
|
||||
汎用ロワーが見るのは、次の 4つだけに限定する。
|
||||
|
||||
- `LoopForm` / `ControlForm`(構造)
|
||||
- `preheader`, `header`, `body`, `latch`, `exit`, `continue_merge` の各ブロック ID
|
||||
- `header` ブロックの succ がちょうど 2 本であること
|
||||
- LoopForm が「どちらが body 側 / exit 側か」を与えてくれていること
|
||||
- `LoopVarClassBox`(変数分類)
|
||||
- pinned: ループ中で不変な変数集合
|
||||
- carriers: ループごとに更新される LoopCarried 変数集合
|
||||
- body-local: ループ内部だけで完結する変数集合(基本は JoinIR では引数にしない)
|
||||
- `LoopExitLivenessBox`(Exit 後で必要な変数)
|
||||
- exit ブロック以降で実際に参照される変数集合 `E`
|
||||
- 条件式の形そのもの(`i < n` か `1 == 1` か等)は **見ない**
|
||||
- 汎用ロワーが使うのは「header の succ が body/exit に分かれている」という事実だけ。
|
||||
|
||||
前提条件として、v1 では次のようなループだけを対象にする。
|
||||
|
||||
- LoopForm が「単一 header / 単一 latch の loop」として構築できていること。
|
||||
- header の succ が 2 本で、ControlForm が `(cond → {body, exit})` を一意に教えてくれること。
|
||||
- break/continue が LoopForm の設計どおり `exit` / `latch` に正規化されていること。
|
||||
|
||||
これを満たさないループ(多重ヘッダ・ネスト・例外的な jump 等)は **JoinIR 対象外(フォールバック)** とする。
|
||||
|
||||
### 7-2. 出力の形(ループごとの JoinFunction セット)
|
||||
|
||||
Case A/B 型の単一ヘッダ loop について、汎用ロワーは原則として次の 2 関数を生成する。
|
||||
|
||||
- `loop_step(pinned..., carriers..., k_exit)`
|
||||
- 引数:
|
||||
- pinned: LoopVarClassBox の pinned 変数(ループ外で初期化され、ループ中不変)
|
||||
- carriers: LoopVarClassBox の carriers(ループをまたいで値を持ち回る)
|
||||
- `k_exit`: ループを抜けたあとの処理を表す継続
|
||||
- 本体:
|
||||
- header 条件の判定(`header` ブロック)
|
||||
- body/latch の処理(`body`/`latch` ブロックから拾った Compute/BoxCall 等)
|
||||
- break/continue の分岐を、それぞれ `k_exit(exit_args...)` / `loop_step(next_carriers..., k_exit)` に変換したもの
|
||||
- `k_exit` 相当の関数(もしくは呼び出し先関数の entry)
|
||||
- ExitLiveness が教えてくれる `E`(exit 後で必要な変数)を引数とし、
|
||||
exit ブロック以降の処理をそのまま MIR→JoinIR で表現したもの。
|
||||
|
||||
これにより:
|
||||
|
||||
- header φ: `LoopHeaderShape` / carriers 引数として `loop_step` に吸収される。
|
||||
- exit φ: `LoopExitShape` / exit 引数として `k_exit` に吸収される。
|
||||
- LoopCarried 変数は常に `loop_step` の引数経由で再帰されるので、PHI ノードは JoinIR 側では不要になる。
|
||||
|
||||
### 7-3. 汎用ロワー v1 のアルゴリズム(Case A/B)
|
||||
|
||||
Case A/B を対象にした最初の汎用ロワーは、次のような流れになる。
|
||||
|
||||
1. **対象ループかどうかのチェック**
|
||||
- LoopForm が単一 header / 単一 latch を持つことを確認。
|
||||
- header の succ が 2 本で、ControlForm が body/exit を特定していることを確認。
|
||||
- 条件: LoopForm の invariants を満たすループだけを対象にし、それ以外は `None` でフォールバック。
|
||||
|
||||
2. **変数セットの決定**
|
||||
- pinned 集合 `P` と carriers 集合 `C` を LoopVarClassBox から取得。
|
||||
- ExitLiveness から exit 後で必要な変数集合 `E` を取得。
|
||||
- `LoopHeaderShape` と `LoopExitShape` を構築しておき、`loop_step` / `k_exit` の引数順を固定。
|
||||
|
||||
3. **`loop_step` 関数の生成**
|
||||
- JoinFunction を新規に作成し、`params = P ∪ C`(+必要なら `k_exit`)とする。
|
||||
- header ブロックの Compare/BinOp から「続行 or exit」の判定命令を MirLikeInst として移植。
|
||||
- body/latch ブロックの Compute / BoxCall を順に MirLikeInst へ写し、carrier 更新を `C_next` として集約。
|
||||
- break:
|
||||
- LoopForm/ControlForm が break 経路としてマークしたブロックからは、`Jump { cont: k_exit, args: exit_values }` を生成。
|
||||
- continue:
|
||||
- latch への backedge 経路からは、`Call { func: loop_step, args: pinned..., carriers_next..., k_next: None/dst=None }` を生成。
|
||||
|
||||
4. **エントリ関数からの呼び出し**
|
||||
- 元の MIR 関数の entry から、LoopForm が示す preheader までの処理を MirLikeInst として保持。
|
||||
- preheader の最後で `loop_step(pinned_init..., carriers_init..., k_exit)` 呼び出しを挿入。
|
||||
|
||||
5. **Exit 継続の構築**
|
||||
- exit ブロック以降の MIR 命令を、`k_exit` 相当の JoinFunction か、呼び出し先関数の entry に写す。
|
||||
- `E` の各変数を引数として受け取り、そのまま下流の処理に流す。
|
||||
|
||||
### 7-4. 既存 per-loop lowering との関係
|
||||
|
||||
Phase 27.x で実装した以下の lowering は、この汎用ロワーの「見本」として扱う。
|
||||
|
||||
- `skip_ws` 系: minimal_ssa_skip_ws(Case B: `loop(1 == 1)` + break)
|
||||
- `FuncScanner.trim_minimal`: Case D の簡易版(`loop(e > b)` + continue+break)
|
||||
- `FuncScanner.append_defs_minimal`: Case A(配列走査)
|
||||
- `Stage1UsingResolver minimal`: Case A/B の混合(Region+next_i 形)
|
||||
- StageB minimal ループ: Case A の defs/body 抽出
|
||||
|
||||
Phase 28 では:
|
||||
|
||||
- まず minimal_ssa_skip_ws だけを対象に、`generic_case_a` のような汎用ロワー v1 を実装し、
|
||||
既存の手書き JoinIR と同じ構造が得られることを確認する(実装済み: `lower_case_a_loop_to_joinir_for_minimal_skip_ws`)。
|
||||
- そのあとで `trim_minimal` / `append_defs_minimal` / Stage‑1 minimal / StageB minimal に順に適用し、
|
||||
per-loop lowering を「汎用ロワーを呼ぶ薄いラッパー」に置き換えていく。
|
||||
- 最終的には per-loop lowering ファイルは削減され、LoopForm+変数分類箱から JoinIR へ落とす汎用ロワーが SSOT になることを目指す。
|
||||
|
||||
このセクションは「汎用ロワー設計のターゲット像」として置いておき、
|
||||
実装は Phase 28-midterm のタスク(generic lowering v1)で少しずつ進めていく。
|
||||
| loop | step 再帰 + k_exit 継続 | LoopCarried/Exit の値を引数で渡す |
|
||||
| break | k_exit 呼び出し | φ 不要(引数で値を渡す) |
|
||||
| continue | step 呼び出し | φ 不要(引数で値を渡す) |
|
||||
|
||||
@ -2,6 +2,14 @@
|
||||
|
||||
Status: design+partial implementation(Stage1 ビルド導線の初期版まで)
|
||||
|
||||
## Stage‑1 CLI 実験メモ(2025-XX)
|
||||
|
||||
- 2025-XX: `NYASH_USE_STAGE1_CLI=1 STAGE1_EMIT_PROGRAM_JSON=1 ./target/release/hakorune apps/tests/minimal_ssa_skip_ws.hako` を実行。
|
||||
- ブリッジ自体は発火し、`stage1-cli/debug` ログと `Stage1CliMain.main/0` が生成されることを確認。
|
||||
- しかし VM 側で `vm step budget exceeded`(max_steps=2000000)で終了。プラグイン未ロード警告あり(FileBox/ArrayBox など)。
|
||||
- 対応メモ: ① NYASH_DISABLE_PLUGINS=1 + core-ro で FileBox は代替読込、② `HAKO_VM_MAX_STEPS` をさらに引き上げる or パーサ前処理を削る検討、③ Stage‑B 自前ビルドは `Unknown method 'main' on InstanceBox` で失敗中(StageBDriverBox 呼び出しが壊れている)。
|
||||
- Rust VM 側で `vm step budget exceeded` に **fn / bb / last_inst 情報** + Span(MIR に付いていれば .hako 行番号)を付与。今回の Stage‑1 CLI 実行では `fn=ParserBox.parse_program2/1 ... (lang/src/runner/stage1_cli.hako:1:1)` まで出力された(Span は通ったが行位置はまだ粗い)。
|
||||
|
||||
## 25.1 サブフェーズの整理(a〜e 概要)
|
||||
|
||||
- **25.1a — Stage1 Build Hotfix(配線)**
|
||||
|
||||
5
docs/development/roadmap/phases/phase-25.1/span-trace.md
Normal file
5
docs/development/roadmap/phases/phase-25.1/span-trace.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Phase 25.1 — Span Trace Mini Note
|
||||
|
||||
- 方針: MIR 命令に AST Span を持たせ、VMError (StepBudgetExceeded) で fn/bb/inst に加えて .hako 行番号を出す。
|
||||
- 実装: MirInstruction 生成時に current_span を保存し、VM 側で last_inst_idx から Span を引いてエラーに埋め込む。Span が無い場合は従来どおり fn/bb/inst のみ。
|
||||
- 状態: Stage‑1 CLI の MIR には Span 未付与なので行番号はまだ出ていないが、Span 付き MIR なら `... (file.hako:line:col)` まで表示できる。
|
||||
@ -0,0 +1,97 @@
|
||||
# Phase 25.1 — Stage‑1 CLI / BuildBox ループ & Box 依存分析メモ
|
||||
|
||||
目的
|
||||
- `Stage‑1 CLI → BuildBox.emit_program_json_v0` 実行時に VM が step budget を食い尽くしている原因を、
|
||||
ループ構造と BoxCall 依存の観点から整理しておくためのメモだよ。
|
||||
|
||||
## 1. 観測された症状
|
||||
|
||||
- コマンド例:
|
||||
```bash
|
||||
NYASH_CLI_VERBOSE=2 \
|
||||
NYASH_USE_STAGE1_CLI=1 \
|
||||
STAGE1_EMIT_PROGRAM_JSON=1 \
|
||||
./target/release/hakorune apps/tests/minimal_ssa_skip_ws.hako
|
||||
```
|
||||
- 状態:
|
||||
- `[stage1-bridge/trace]` + `[stage1-cli/debug] emit_program_json ENTRY` が出ており、
|
||||
Stage1CliMain.main/0 〜 stage1_cli.hako 実行までは到達している。
|
||||
- その後 `BuildBox.emit_program_json_v0` 実行中に VM が `vm step budget exceeded`(max_steps=1_000_000→2_000_000 でも NG)。
|
||||
- FileBox/ArrayBox などの plugin 未ロード警告も併発。
|
||||
- 現状は **ステップ上限+プラグイン依存** が阻害要因となって、program-json 自体は得られていない。
|
||||
|
||||
## 2. emit_program_json_v0 周辺のループ構造(概観)
|
||||
|
||||
調査時点でのループ構造のざっくり分類(ファイルは概略)だよ。
|
||||
|
||||
| ループ | ファイル | 複雑度 | 危険度 |
|
||||
|------------------------------|--------------------------|-------------|--------|
|
||||
| `ParserBox.parse_program2` | stage1_cli.hako:84 あたり | exponential | 🔴 最有力 |
|
||||
| alias_table パース (opts) | build_box.hako:60-73 | O(n²) | 🟡 中 |
|
||||
| alias_table パース (env) | build_box.hako:94-107 | O(n²) | 🟡 中 |
|
||||
| `FileBox._read_file` | stage1_cli.hako:67 | I/O bound | ⚠️ 注視 |
|
||||
| bundle_names 重複チェック | build_box.hako:41-51 | O(n²) | 🟢 許容 |
|
||||
|
||||
直感:
|
||||
- alias_table や bundle_names の O(n²) 系ループは、入力サイズが小さい間は budget を食い尽くすレベルではない。
|
||||
- `ParserBox.parse_program2` は Stage‑3 parser の statement/expression を全部受け持つため、
|
||||
実装によっては **指数的** にブロック数・ステップ数が増え得る。
|
||||
- FileBox は I/O bound なので、budget ではなくタイムアウト側のリスクが大きい。
|
||||
|
||||
## 3. Box 依存度マトリックス
|
||||
|
||||
Stage‑1 CLI / BuildBox ラインで使われている Box の洗い出しと、
|
||||
「core だけで足りるか/外部プラグインが要るか」のざっくり分類だよ。
|
||||
|
||||
| Box | 用途 | プラグイン依存 | 影響度 |
|
||||
|-----------|-------------------------------|---------|-------------|
|
||||
| FileBox | `_read_file` で source 読み込み | YES | 🔴 CRITICAL |
|
||||
| ParserBox | `parse_program2` AST parse | NO | 🟡 MEDIUM |
|
||||
| ArrayBox | bundles 配列格納 | NO | 🟢 LOW |
|
||||
| StringBox | alias_table / bundle_names parse | NO | 🟢 LOW |
|
||||
|
||||
メモ:
|
||||
- FileBox は core-ro だけでは厳しく、実際には plugin / env 設定に依存するケースが多い。
|
||||
- ParserBox は Stage‑3 parser の仕様そのものなので、LoopForm/JoinIR と独立に「parser の loop 形」問題として見る必要がある。
|
||||
|
||||
## 4. 今後の観測テンプレ(提案)
|
||||
|
||||
ステップ budget 溶けの原因をもう少し切り分けるために、次のようなコマンドで実験できるよ。
|
||||
|
||||
```bash
|
||||
# Test 1: FileBox なしで inline source を直書き
|
||||
NYASH_STAGE1_MODE=emit-program \
|
||||
STAGE1_SOURCE_TEXT='static box Main { main(args) { return 0 } }' \
|
||||
./target/release/nyash lang/src/runner/stage1_cli.hako
|
||||
|
||||
# Test 2: プラグイン OFF で挙動を見る
|
||||
NYASH_DISABLE_PLUGINS=1 \
|
||||
NYASH_STAGE1_MODE=emit-program \
|
||||
STAGE1_SOURCE_TEXT='static box Main { main(args) { return 0 } }' \
|
||||
./target/release/nyash lang/src/runner/stage1_cli.hako
|
||||
|
||||
# Test 3: ステップトレースを有効化
|
||||
NYASH_VM_STATS=1 \
|
||||
NYASH_STAGE1_MODE=emit-program \
|
||||
STAGE1_SOURCE_TEXT='static box Main { main(args) { return 0 } }' \
|
||||
./target/release/nyash lang/src/runner/stage1_cli.hako 2>&1 | grep -i step
|
||||
```
|
||||
|
||||
目的:
|
||||
- FileBox を完全にバイパスできる inline source モードで試し、
|
||||
それでも budget が溶けるかを見る(ParserBox 起因かどうかの切り分け)。
|
||||
- プラグイン OFF/ON で挙動がどれだけ変わるか確認する。
|
||||
|
||||
## 5. 方針メモ
|
||||
|
||||
- Stage‑1 CLI の「ブリッジまでは動いているが、そのあとで止まる」という現象は、
|
||||
- Stage‑B/BuildBox 経由の Program JSON emit
|
||||
- ParserBox / FileBox のループ
|
||||
のどこに問題があるかを 1 ループずつ切り出していけば、局所化できる見込み。
|
||||
- joinIR 側とは独立に、このファイルでは「Stage‑1 CLI 実行ルートのうち、どのループと Box が危ないか」の観測結果を積み上げていくよ。
|
||||
|
||||
## 6. VM エラー出力改善メモ(2025-XX)
|
||||
|
||||
- Rust MIR Interpreter の step budget エラーに **fn / bb / last_inst / steps** + Span を付与したよ。
|
||||
- 例(Span 付き MIR の場合): `vm step budget exceeded (max_steps=2000000, steps=2000001) at bb=bb49 fn=ParserBox.parse_program2/1 last_inst_idx=12 last_inst_bb=bb48 last_inst=... (file.hako:312:7)`
|
||||
- Stage‑1 CLI の現行 MIR には Span が載っていないため、いまは fn/bb/inst だけが出る(`apps/tests/minimal_ssa_skip_ws.hako` 実行で確認)。Span 付与を通せば .hako 行まで出る想定。
|
||||
@ -0,0 +1,75 @@
|
||||
# Phase 25.1 — Stage‑B Selfhost main 解決メモ(InstanceBox / Static Box 呼び出し)
|
||||
|
||||
目的
|
||||
- `tools/selfhost/build_stage1.sh` 実行時に発生している
|
||||
`Unknown method 'main' on InstanceBox` の根本原因を整理し、
|
||||
どの Box / メソッド名のズレかを明確にするための観測メモだよ。
|
||||
|
||||
## 1. 現状の観測結果
|
||||
|
||||
- エラー発生箇所:
|
||||
- `src/backend/mir_interpreter/handlers/boxes.rs:105` 付近(InstanceBox 用のメソッドディスパッチ)。
|
||||
- 症状:
|
||||
- VM が **InstanceBox** という generic 型に対して `"main"` を探している。
|
||||
- 期待されるのは `StageBDriverBox.main(...)` のような「ユーザー定義 static box 上の main」。
|
||||
- Rust の `type_name()` macro がユーザー定義 Box を generic 型名(InstanceBox)として返しているため、
|
||||
ログ上は常に `box_type_name=InstanceBox` となり、「どの box の main を探しているか」が見えづらい。
|
||||
|
||||
## 2. StageBDriverBox 側の定義
|
||||
|
||||
- 定義ファイル:
|
||||
- `lang/src/compiler/entry/compiler_stageb.hako:1141-1367`
|
||||
- 構造:
|
||||
- `static box StageBDriverBox { main(args) { ... } }` という形で main が定義されている。
|
||||
- エントリポイント:
|
||||
- `Main.main()` から `StageBDriverBox.main()` を呼ぶ導線が 1373-1377 行あたりに存在。
|
||||
- 直感的には:
|
||||
- **`.hako` 側は正しく main を定義している**が、
|
||||
- VM 側の `InstanceBox` 呼び出し時に **メソッド名の解決 or Box 名の正規化** でズレている可能性が高い。
|
||||
|
||||
## 3. ログ追加の案(型名+メソッド名の観測)
|
||||
|
||||
次のようなログを handlers/boxes.rs の Unknown method 分岐直前に入れておくと、
|
||||
どの Box / 関数名の組み合わせが原因かが見やすくなるよ。
|
||||
|
||||
```rust
|
||||
eprintln!(
|
||||
"[vm/method-dispatch] Unknown method on Box: box_type_name={}, method_name={}",
|
||||
recv_box.type_name(), // InstanceBox 側の type_name
|
||||
method, // 探しにいったメソッド名(例: \"main\" / \"StageBDriverBox.main/0\")
|
||||
);
|
||||
```
|
||||
|
||||
※ 実際には `type_name()` は常に `InstanceBox` を返すので、
|
||||
実 box 名は `BoxDeclaration` / `InstanceBox` 内のフィールドから取り出す必要があるかもしれない。
|
||||
|
||||
## 4. 次に見ると良い箇所
|
||||
|
||||
### 4-1. MIR 側の BoxCall 受け側
|
||||
|
||||
- 目的:
|
||||
- Stage‑B selfhost の MIR で、「どの global / boxcall 名で StageBDriverBox.main を呼んでいるか」を確認する。
|
||||
- 手順の目安:
|
||||
- Stage‑B ランチャの MIR を `--dump-mir` 等でダンプ。
|
||||
- `call_global "StageBDriverBox.main/..."` のような形になっているかを見る。
|
||||
|
||||
### 4-2. MirBuilder の receiver / callee 解決
|
||||
|
||||
- ファイル候補:
|
||||
- `src/mir/builder/calls/unified_emitter.rs`
|
||||
- `src/backend/mir_interpreter/handlers/global.rs`
|
||||
- 見たいこと:
|
||||
- StaticMethodId / NamingBox を通った後に、
|
||||
`"StageBDriverBox.main/arity"` がどのように解決されているか。
|
||||
- InstanceBox に対するメソッド呼び出しのときに、
|
||||
メソッド名から Box 名が正しく分離されているか。
|
||||
|
||||
## 5. 方針メモ
|
||||
|
||||
- joinIR ラインとは独立して、この問題は「Stage‑B driver / InstanceBox / naming」の層で解決する。
|
||||
- まずは:
|
||||
- どの InstanceBox/関数名の組み合わせで Unknown が出ているかをログで観測。
|
||||
- `.hako` 側の StageBDriverBox 定義と、Rust 側のメソッド名解決ロジックの差分を見る。
|
||||
- 修正自体(命名・正規化の調整)は Phase 25.1 の別サブフェーズで扱う前提として、
|
||||
このファイルは「現状の観測結果と差分の候補」を残しておくためのメモだよ。
|
||||
|
||||
@ -351,6 +351,19 @@ box ParserBox {
|
||||
}
|
||||
|
||||
loop(cont_prog == 1) {
|
||||
// ✅ TRACE: main_prog loop progress (Task先生推奨トレースポイント)
|
||||
local _loop_info = ""
|
||||
if trace == 1 && (guard_prog < 5 || (guard_prog % 10) == 0) {
|
||||
_loop_info = "[parse_program2:main] count=" + ("" + guard_prog) +
|
||||
" i=" + ("" + i) + " n=" + ("" + n)
|
||||
if i < n {
|
||||
local _end = i + 40
|
||||
if _end > n { _end = n }
|
||||
_loop_info = _loop_info + " ctx=\"" + src.substring(i, _end) + "\""
|
||||
}
|
||||
print(_loop_info)
|
||||
}
|
||||
|
||||
if max_prog > 0 {
|
||||
guard_prog = guard_prog + 1
|
||||
if guard_prog > max_prog {
|
||||
|
||||
@ -71,10 +71,23 @@ impl MirInterpreter {
|
||||
steps += 1;
|
||||
// max_steps == 0 は「上限なし」を意味する(開発/診断専用)。
|
||||
if max_steps > 0 && steps > max_steps {
|
||||
return Err(VMError::InvalidInstruction(format!(
|
||||
"vm step budget exceeded (max_steps={})",
|
||||
max_steps
|
||||
)));
|
||||
let last_inst_str = self.last_inst.as_ref().map(|inst| format!("{:?}", inst));
|
||||
let target_bb = self.last_block.unwrap_or(cur);
|
||||
let span = func
|
||||
.blocks
|
||||
.get(&target_bb)
|
||||
.and_then(|block| self.lookup_span_for_inst(block, self.last_inst_index));
|
||||
return Err(VMError::StepBudgetExceeded {
|
||||
max_steps,
|
||||
steps,
|
||||
function: self.cur_fn.clone(),
|
||||
current_block: cur,
|
||||
last_block: self.last_block,
|
||||
last_inst: last_inst_str,
|
||||
last_inst_index: self.last_inst_index,
|
||||
span,
|
||||
source_file: func.metadata.source_file.clone(),
|
||||
});
|
||||
}
|
||||
let block = func
|
||||
.blocks
|
||||
@ -128,6 +141,21 @@ impl MirInterpreter {
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup_span_for_inst(
|
||||
&self,
|
||||
block: &BasicBlock,
|
||||
inst_index: Option<usize>,
|
||||
) -> Option<crate::ast::Span> {
|
||||
let idx = inst_index?;
|
||||
if idx < block.instructions.len() {
|
||||
block.instruction_span(idx)
|
||||
} else if idx == block.instructions.len() {
|
||||
block.terminator_span()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_phi_nodes(
|
||||
&mut self,
|
||||
block: &BasicBlock,
|
||||
@ -140,7 +168,10 @@ impl MirInterpreter {
|
||||
block.id, last_pred, block.predecessors
|
||||
);
|
||||
}
|
||||
for inst in block.phi_instructions() {
|
||||
for (idx, inst) in block.phi_instructions().enumerate() {
|
||||
self.last_block = Some(block.id);
|
||||
self.last_inst_index = Some(idx);
|
||||
self.last_inst = Some(inst.clone());
|
||||
if let MirInstruction::Phi { dst, inputs } = inst {
|
||||
let dst_id = *dst;
|
||||
if trace_phi {
|
||||
@ -302,8 +333,10 @@ impl MirInterpreter {
|
||||
}
|
||||
|
||||
fn execute_block_instructions(&mut self, block: &BasicBlock) -> Result<(), VMError> {
|
||||
for inst in block.non_phi_instructions() {
|
||||
let phi_count = block.phi_instructions().count();
|
||||
for (idx, inst) in block.non_phi_instructions().enumerate() {
|
||||
self.last_block = Some(block.id);
|
||||
self.last_inst_index = Some(phi_count + idx);
|
||||
self.last_inst = Some(inst.clone());
|
||||
if Self::trace_enabled() {
|
||||
eprintln!("[vm-trace] inst bb={:?} {:?}", block.id, inst);
|
||||
@ -319,6 +352,11 @@ impl MirInterpreter {
|
||||
}
|
||||
|
||||
fn handle_terminator(&mut self, block: &BasicBlock) -> Result<BlockOutcome, VMError> {
|
||||
if let Some(term) = &block.terminator {
|
||||
self.last_block = Some(block.id);
|
||||
self.last_inst_index = Some(block.instructions.len());
|
||||
self.last_inst = Some(term.clone());
|
||||
}
|
||||
match &block.terminator {
|
||||
Some(MirInstruction::Return { value }) => {
|
||||
let result = if let Some(v) = value {
|
||||
|
||||
@ -102,6 +102,12 @@ impl MirInterpreter {
|
||||
self.write_string(dst, recv_box.to_string_box().value);
|
||||
Ok(())
|
||||
} else {
|
||||
// Phase 25.1 diagnostic: Log unknown method details before error
|
||||
eprintln!(
|
||||
"[vm/method-dispatch/plugin-invoke] Unknown method on Box: box_type_name={}, method_name={}",
|
||||
recv_box.type_name(),
|
||||
method
|
||||
);
|
||||
Err(self.err_method_not_found(&recv_box.type_name(), method))
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,8 +36,10 @@ impl MirInterpreter {
|
||||
|
||||
// Phase 2.2: Show parsed StaticMethodId info
|
||||
if let Some(id) = StaticMethodId::parse(func_name) {
|
||||
eprintln!("[DEBUG/vm] Parsed: box='{}', method='{}', arity={:?}",
|
||||
id.box_name, id.method, id.arity);
|
||||
eprintln!(
|
||||
"[DEBUG/vm] Parsed: box='{}', method='{}', arity={:?}",
|
||||
id.box_name, id.method, id.arity
|
||||
);
|
||||
} else {
|
||||
eprintln!("[DEBUG/vm] Not a static method (builtin?)");
|
||||
}
|
||||
@ -53,7 +55,9 @@ impl MirInterpreter {
|
||||
} else {
|
||||
&canonical
|
||||
};
|
||||
let matching: Vec<_> = self.functions.keys()
|
||||
let matching: Vec<_> = self
|
||||
.functions
|
||||
.keys()
|
||||
.filter(|k| k.starts_with(prefix))
|
||||
.collect();
|
||||
if !matching.is_empty() {
|
||||
@ -204,7 +208,9 @@ impl MirInterpreter {
|
||||
&canonical
|
||||
};
|
||||
|
||||
let similar: Vec<_> = self.functions.keys()
|
||||
let similar: Vec<_> = self
|
||||
.functions
|
||||
.keys()
|
||||
.filter(|k| k.starts_with(prefix))
|
||||
.take(5)
|
||||
.collect();
|
||||
@ -218,7 +224,8 @@ impl MirInterpreter {
|
||||
}
|
||||
}
|
||||
|
||||
err_msg.push_str("\n\n🔍 Debug: NYASH_DEBUG_FUNCTION_LOOKUP=1 for full lookup trace");
|
||||
err_msg
|
||||
.push_str("\n\n🔍 Debug: NYASH_DEBUG_FUNCTION_LOOKUP=1 for full lookup trace");
|
||||
|
||||
// NamingBox SSOT: ここで canonical に失敗したら素直に Unknown とする。
|
||||
// レガシーフォールバック(functions.get(func_name) 再探索)は Phase 25.x で廃止済み。
|
||||
|
||||
@ -33,6 +33,7 @@ pub struct MirInterpreter {
|
||||
// Trace context (dev-only; enabled with NYASH_VM_TRACE=1)
|
||||
pub(super) last_block: Option<BasicBlockId>,
|
||||
pub(super) last_inst: Option<MirInstruction>,
|
||||
pub(super) last_inst_index: Option<usize>,
|
||||
// Static box singleton instances (persistent across method calls)
|
||||
pub(super) static_boxes: HashMap<String, crate::instance_v2::InstanceBox>,
|
||||
// Static box declarations (metadata for creating instances)
|
||||
@ -56,6 +57,7 @@ impl MirInterpreter {
|
||||
cur_fn: None,
|
||||
last_block: None,
|
||||
last_inst: None,
|
||||
last_inst_index: None,
|
||||
static_boxes: HashMap::new(),
|
||||
static_box_decls: HashMap::new(),
|
||||
inst_count: 0,
|
||||
|
||||
@ -5,8 +5,9 @@
|
||||
* Kept separate to thin vm.rs and allow reuse across helpers.
|
||||
*/
|
||||
|
||||
use crate::ast::Span;
|
||||
use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox, VoidBox};
|
||||
use crate::mir::ConstValue;
|
||||
use crate::mir::{BasicBlockId, ConstValue};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// VM execution error
|
||||
@ -18,6 +19,17 @@ pub enum VMError {
|
||||
DivisionByZero,
|
||||
StackUnderflow,
|
||||
TypeError(String),
|
||||
StepBudgetExceeded {
|
||||
max_steps: u64,
|
||||
steps: u64,
|
||||
function: Option<String>,
|
||||
current_block: BasicBlockId,
|
||||
last_block: Option<BasicBlockId>,
|
||||
last_inst: Option<String>,
|
||||
last_inst_index: Option<usize>,
|
||||
span: Option<Span>,
|
||||
source_file: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Display for VMError {
|
||||
@ -29,6 +41,48 @@ impl std::fmt::Display for VMError {
|
||||
VMError::DivisionByZero => write!(f, "Division by zero"),
|
||||
VMError::StackUnderflow => write!(f, "Stack underflow"),
|
||||
VMError::TypeError(msg) => write!(f, "Type error: {}", msg),
|
||||
VMError::StepBudgetExceeded {
|
||||
max_steps,
|
||||
steps,
|
||||
function,
|
||||
current_block,
|
||||
last_block,
|
||||
last_inst,
|
||||
last_inst_index,
|
||||
span,
|
||||
source_file,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"vm step budget exceeded (max_steps={}, steps={}) at bb={}",
|
||||
max_steps, steps, current_block
|
||||
)?;
|
||||
if let Some(fn_name) = function {
|
||||
write!(f, " fn={}", fn_name)?;
|
||||
}
|
||||
if let Some(idx) = last_inst_index {
|
||||
write!(f, " last_inst_idx={}", idx)?;
|
||||
}
|
||||
if let Some(bb) = last_block {
|
||||
write!(f, " last_inst_bb={}", bb)?;
|
||||
}
|
||||
if let Some(inst) = last_inst {
|
||||
write!(f, " last_inst={}", inst)?;
|
||||
}
|
||||
match (span, source_file) {
|
||||
(Some(span), Some(file)) => {
|
||||
write!(f, " ({}:{}:{})", file, span.line, span.column)?;
|
||||
}
|
||||
(Some(span), None) => {
|
||||
write!(f, " (line {}, col {})", span.line, span.column)?;
|
||||
}
|
||||
(None, Some(file)) => {
|
||||
write!(f, " ({})", file)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,37 +36,15 @@ pub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[
|
||||
];
|
||||
pub fn lookup_keyword(word: &str) -> Option<&'static str> {
|
||||
for (k, t) in KEYWORDS {
|
||||
if *k == word { return Some(*t); }
|
||||
if *k == word {
|
||||
return Some(*t);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[
|
||||
"box",
|
||||
"global",
|
||||
"function",
|
||||
"static",
|
||||
"if",
|
||||
"loop",
|
||||
"break",
|
||||
"return",
|
||||
"print",
|
||||
"nowait",
|
||||
"include",
|
||||
"local",
|
||||
"outbox",
|
||||
"try",
|
||||
"throw",
|
||||
"using",
|
||||
"from",
|
||||
];
|
||||
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[
|
||||
"add",
|
||||
"sub",
|
||||
"mul",
|
||||
"div",
|
||||
"and",
|
||||
"or",
|
||||
"eq",
|
||||
"ne",
|
||||
"box", "global", "function", "static", "if", "loop", "break", "return", "print", "nowait",
|
||||
"include", "local", "outbox", "try", "throw", "using", "from",
|
||||
];
|
||||
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &["add", "sub", "mul", "div", "and", "or", "eq", "ne"];
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
* SSA-form basic blocks with phi functions and terminator instructions
|
||||
*/
|
||||
|
||||
use super::{EffectMask, MirInstruction, ValueId};
|
||||
use super::{EffectMask, MirInstruction, SpannedInstruction, ValueId};
|
||||
use crate::ast::Span;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
|
||||
@ -49,9 +50,15 @@ pub struct BasicBlock {
|
||||
/// Instructions in this block (excluding terminator)
|
||||
pub instructions: Vec<MirInstruction>,
|
||||
|
||||
/// Span per instruction (aligned with `instructions`)
|
||||
pub instruction_spans: Vec<Span>,
|
||||
|
||||
/// Terminator instruction (branch, jump, or return)
|
||||
pub terminator: Option<MirInstruction>,
|
||||
|
||||
/// Span for the terminator instruction
|
||||
pub terminator_span: Option<Span>,
|
||||
|
||||
/// Predecessors in the control flow graph
|
||||
pub predecessors: HashSet<BasicBlockId>,
|
||||
|
||||
@ -74,7 +81,9 @@ impl BasicBlock {
|
||||
Self {
|
||||
id,
|
||||
instructions: Vec::new(),
|
||||
instruction_spans: Vec::new(),
|
||||
terminator: None,
|
||||
terminator_span: None,
|
||||
predecessors: HashSet::new(),
|
||||
successors: HashSet::new(),
|
||||
effects: EffectMask::PURE,
|
||||
@ -83,8 +92,18 @@ impl BasicBlock {
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a spanned instruction to this block
|
||||
pub fn add_spanned_instruction(&mut self, sp: super::SpannedInstruction) {
|
||||
self.add_instruction_with_span(sp.inst, sp.span);
|
||||
}
|
||||
|
||||
/// Add an instruction to this block
|
||||
pub fn add_instruction(&mut self, instruction: MirInstruction) {
|
||||
self.add_instruction_with_span(instruction, Span::unknown());
|
||||
}
|
||||
|
||||
/// Add an instruction with explicit span to this block
|
||||
pub fn add_instruction_with_span(&mut self, instruction: MirInstruction, span: Span) {
|
||||
// Update effect mask
|
||||
self.effects = self.effects | instruction.effects();
|
||||
|
||||
@ -94,11 +113,13 @@ impl BasicBlock {
|
||||
panic!("Basic block {} already has a terminator", self.id);
|
||||
}
|
||||
self.terminator = Some(instruction);
|
||||
self.terminator_span = Some(span);
|
||||
|
||||
// Update successors based on terminator
|
||||
self.update_successors_from_terminator();
|
||||
} else {
|
||||
self.instructions.push(instruction);
|
||||
self.instruction_spans.push(span);
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,6 +132,7 @@ impl BasicBlock {
|
||||
// Non-terminator instructions always go into instructions vec
|
||||
if !self.is_terminator(&instruction) {
|
||||
self.instructions.push(instruction);
|
||||
self.instruction_spans.push(Span::unknown());
|
||||
} else {
|
||||
panic!("Cannot add terminator via add_instruction_before_terminator");
|
||||
}
|
||||
@ -212,6 +234,16 @@ impl BasicBlock {
|
||||
.skip_while(|inst| matches!(inst, MirInstruction::Phi { .. }))
|
||||
}
|
||||
|
||||
/// Get span for instruction index (phi/non-phi)
|
||||
pub fn instruction_span(&self, idx: usize) -> Option<Span> {
|
||||
self.instruction_spans.get(idx).copied()
|
||||
}
|
||||
|
||||
/// Get span for terminator instruction
|
||||
pub fn terminator_span(&self) -> Option<Span> {
|
||||
self.terminator_span
|
||||
}
|
||||
|
||||
/// Insert instruction at the beginning (after phi instructions)
|
||||
pub fn insert_instruction_after_phis(&mut self, instruction: MirInstruction) {
|
||||
let phi_count = self.phi_instructions().count();
|
||||
@ -223,6 +255,22 @@ impl BasicBlock {
|
||||
}
|
||||
self.effects = self.effects | instruction.effects();
|
||||
self.instructions.insert(phi_count, instruction);
|
||||
self.instruction_spans.insert(phi_count, Span::unknown());
|
||||
}
|
||||
|
||||
/// Insert spanned instruction at the beginning (after phi instructions)
|
||||
pub fn insert_spanned_after_phis(&mut self, sp: super::SpannedInstruction) {
|
||||
let phi_count = self.phi_instructions().count();
|
||||
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||
if let MirInstruction::Copy { dst, src } = &sp.inst {
|
||||
eprintln!(
|
||||
"[insert-after-phis] bb={:?} phi_count={} inserting Copy dst=%{} src=%{} total_inst={}",
|
||||
self.id, phi_count, dst.0, src.0, self.instructions.len());
|
||||
}
|
||||
}
|
||||
self.effects = self.effects | sp.inst.effects();
|
||||
self.instructions.insert(phi_count, sp.inst);
|
||||
self.instruction_spans.insert(phi_count, sp.span);
|
||||
}
|
||||
|
||||
/// Update PHI instruction input by destination ValueId
|
||||
@ -254,9 +302,32 @@ impl BasicBlock {
|
||||
|
||||
self.effects = self.effects | terminator.effects();
|
||||
self.terminator = Some(terminator);
|
||||
self.terminator_span = Some(Span::unknown());
|
||||
self.update_successors_from_terminator();
|
||||
}
|
||||
|
||||
/// Replace terminator with explicit span
|
||||
pub fn set_terminator_with_span(&mut self, terminator: MirInstruction, span: Span) {
|
||||
if !self.is_terminator(&terminator) {
|
||||
panic!("Instruction is not a valid terminator: {:?}", terminator);
|
||||
}
|
||||
self.effects = self.effects | terminator.effects();
|
||||
self.terminator = Some(terminator);
|
||||
self.terminator_span = Some(span);
|
||||
self.update_successors_from_terminator();
|
||||
}
|
||||
|
||||
/// Drain instructions into spanned form (keeps block empty and aligned).
|
||||
pub fn drain_spanned_instructions(&mut self) -> Vec<SpannedInstruction> {
|
||||
let insts = std::mem::take(&mut self.instructions);
|
||||
let spans = std::mem::take(&mut self.instruction_spans);
|
||||
insts
|
||||
.into_iter()
|
||||
.zip(spans.into_iter())
|
||||
.map(|(inst, span)| SpannedInstruction { inst, span })
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Mark this block as reachable
|
||||
pub fn mark_reachable(&mut self) {
|
||||
self.reachable = true;
|
||||
|
||||
@ -9,12 +9,12 @@ use super::{
|
||||
BasicBlock, BasicBlockId, BasicBlockIdGenerator, CompareOp, ConstValue, Effect, EffectMask,
|
||||
FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId, ValueIdGenerator,
|
||||
};
|
||||
use crate::ast::{ASTNode, LiteralValue};
|
||||
use crate::ast::{ASTNode, LiteralValue, Span};
|
||||
use crate::mir::builder::builder_calls::CallTarget;
|
||||
use crate::mir::region::function_slot_registry::FunctionSlotRegistry;
|
||||
use crate::mir::region::RegionId;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
mod builder_calls;
|
||||
mod call_resolution; // ChatGPT5 Pro: Type-safe call resolution utilities
|
||||
mod calls; // Call system modules (refactored from builder_calls)
|
||||
@ -219,6 +219,12 @@ pub struct MirBuilder {
|
||||
/// Tracks the depth of build_expression calls to detect infinite loops
|
||||
pub(super) recursion_depth: usize,
|
||||
|
||||
/// Current AST span being lowered (used to annotate MIR instructions)
|
||||
pub(super) current_span: Span,
|
||||
|
||||
/// Optional source file hint for metadata/spans
|
||||
pub(super) source_file: Option<String>,
|
||||
|
||||
/// Root lowering mode: how to treat top-level Program
|
||||
/// - None: not decided yet (lower_root not called)
|
||||
/// - Some(true): App mode (static box Main.main is entry)
|
||||
@ -290,6 +296,8 @@ impl MirBuilder {
|
||||
|
||||
in_unified_boxcall_fallback: false,
|
||||
recursion_depth: 0,
|
||||
current_span: Span::unknown(),
|
||||
source_file: None,
|
||||
root_is_app_mode: None,
|
||||
static_box_singletons: HashMap::new(), // Phase 21.7: methodization support
|
||||
}
|
||||
@ -347,6 +355,34 @@ impl MirBuilder {
|
||||
self.debug_scope_stack.last().cloned()
|
||||
}
|
||||
|
||||
/// Hint for downstream metadata: set the logical source file name/path for the next build.
|
||||
pub fn set_source_file_hint<S: Into<String>>(&mut self, source: S) {
|
||||
self.source_file = Some(source.into());
|
||||
}
|
||||
|
||||
/// Clear the source file hint (used when reusing the builder across modules).
|
||||
pub fn clear_source_file_hint(&mut self) {
|
||||
self.source_file = None;
|
||||
}
|
||||
|
||||
/// Resolve current source file hint (builder field or env fallback).
|
||||
fn current_source_file(&self) -> Option<String> {
|
||||
self.source_file
|
||||
.clone()
|
||||
.or_else(|| std::env::var("NYASH_SOURCE_FILE_HINT").ok())
|
||||
}
|
||||
|
||||
/// Create a new MirFunction with source metadata applied.
|
||||
fn new_function_with_metadata(
|
||||
&self,
|
||||
signature: FunctionSignature,
|
||||
entry_block: BasicBlockId,
|
||||
) -> MirFunction {
|
||||
let mut f = MirFunction::new(signature, entry_block);
|
||||
f.metadata.source_file = self.current_source_file();
|
||||
f
|
||||
}
|
||||
|
||||
// ----------------------
|
||||
// Compile trace helpers (dev only; env-gated)
|
||||
// ----------------------
|
||||
@ -739,7 +775,7 @@ impl MirBuilder {
|
||||
}
|
||||
);
|
||||
}
|
||||
block.add_instruction(instruction);
|
||||
block.add_instruction_with_span(instruction, self.current_span);
|
||||
// Drop the mutable borrow of `block` before updating other blocks
|
||||
}
|
||||
// Update predecessor sets for branch/jump immediately so that
|
||||
|
||||
@ -105,8 +105,15 @@ impl<'a> CalleeGuardBox<'a> {
|
||||
// length, substring, charAt, indexOf, etc.
|
||||
let is_string_method = matches!(
|
||||
method.as_str(),
|
||||
"length" | "substring" | "charAt" | "indexOf" | "lastIndexOf"
|
||||
| "toUpperCase" | "toLowerCase" | "trim" | "split"
|
||||
"length"
|
||||
| "substring"
|
||||
| "charAt"
|
||||
| "indexOf"
|
||||
| "lastIndexOf"
|
||||
| "toUpperCase"
|
||||
| "toLowerCase"
|
||||
| "trim"
|
||||
| "split"
|
||||
);
|
||||
|
||||
if is_string_method {
|
||||
@ -130,7 +137,9 @@ impl<'a> CalleeGuardBox<'a> {
|
||||
} else {
|
||||
// Non-string methods: fallback to Global call
|
||||
if trace_enabled {
|
||||
eprintln!("[static-runtime-guard] StaticCompiler receiver has NO Box type:");
|
||||
eprintln!(
|
||||
"[static-runtime-guard] StaticCompiler receiver has NO Box type:"
|
||||
);
|
||||
eprintln!(
|
||||
" {}.{} receiver %{} has no type info",
|
||||
box_name, method, recv.0
|
||||
|
||||
@ -77,7 +77,7 @@ impl MirBuilder {
|
||||
let signature =
|
||||
function_lowering::prepare_static_method_signature(func_name.clone(), params, body);
|
||||
let entry = self.block_gen.next();
|
||||
let function = super::super::MirFunction::new(signature, entry);
|
||||
let function = self.new_function_with_metadata(signature, entry);
|
||||
|
||||
// 現在の関数・ブロックを保存
|
||||
ctx.saved_function = self.current_function.take();
|
||||
@ -259,7 +259,7 @@ impl MirBuilder {
|
||||
let signature =
|
||||
function_lowering::prepare_method_signature(func_name, box_name, params, body);
|
||||
let entry = self.block_gen.next();
|
||||
let function = super::super::MirFunction::new(signature, entry);
|
||||
let function = self.new_function_with_metadata(signature, entry);
|
||||
|
||||
// 現在の関数・ブロックを保存
|
||||
ctx.saved_function = self.current_function.take();
|
||||
|
||||
@ -175,10 +175,7 @@ impl UnifiedCallEmitterBox {
|
||||
|
||||
// 🎯 Phase 21.7: Methodization (HAKO_MIR_BUILDER_METHODIZE=1)
|
||||
// Convert Global("BoxName.method/arity") → Method{receiver=static singleton}
|
||||
let methodize_on = match std::env::var("HAKO_MIR_BUILDER_METHODIZE")
|
||||
.ok()
|
||||
.as_deref()
|
||||
{
|
||||
let methodize_on = match std::env::var("HAKO_MIR_BUILDER_METHODIZE").ok().as_deref() {
|
||||
// 明示的に "0" が指定されたときだけ無効化。
|
||||
Some("0") => false,
|
||||
_ => true,
|
||||
@ -194,7 +191,8 @@ impl UnifiedCallEmitterBox {
|
||||
let box_name = &id.box_name;
|
||||
let method = &id.method;
|
||||
// Get or create static box singleton instance
|
||||
let singleton = if let Some(&existing) = builder.static_box_singletons.get(box_name) {
|
||||
let singleton =
|
||||
if let Some(&existing) = builder.static_box_singletons.get(box_name) {
|
||||
existing
|
||||
} else {
|
||||
// Create new singleton instance
|
||||
@ -209,9 +207,13 @@ impl UnifiedCallEmitterBox {
|
||||
singleton_id,
|
||||
crate::mir::MirType::Box(box_name.to_string()),
|
||||
);
|
||||
builder.value_origin_newbox.insert(singleton_id, box_name.to_string());
|
||||
builder
|
||||
.value_origin_newbox
|
||||
.insert(singleton_id, box_name.to_string());
|
||||
// Cache for future use
|
||||
builder.static_box_singletons.insert(box_name.to_string(), singleton_id);
|
||||
builder
|
||||
.static_box_singletons
|
||||
.insert(box_name.to_string(), singleton_id);
|
||||
singleton_id
|
||||
};
|
||||
|
||||
@ -221,7 +223,8 @@ impl UnifiedCallEmitterBox {
|
||||
method: method.to_string(),
|
||||
receiver: Some(singleton),
|
||||
certainty: crate::mir::definitions::call_unified::TypeCertainty::Known,
|
||||
box_kind: crate::mir::definitions::call_unified::CalleeBoxKind::StaticCompiler,
|
||||
box_kind:
|
||||
crate::mir::definitions::call_unified::CalleeBoxKind::StaticCompiler,
|
||||
};
|
||||
|
||||
if std::env::var("NYASH_METHODIZE_TRACE").ok().as_deref() == Some("1") {
|
||||
|
||||
@ -20,11 +20,8 @@ impl super::MirBuilder {
|
||||
}
|
||||
if let ASTNode::FunctionDeclaration { params, body, .. } = mast {
|
||||
// NamingBox 経由で static メソッド名を一元管理する
|
||||
let func_name = crate::mir::naming::encode_static_method(
|
||||
&box_name,
|
||||
mname,
|
||||
params.len(),
|
||||
);
|
||||
let func_name =
|
||||
crate::mir::naming::encode_static_method(&box_name, mname, params.len());
|
||||
self.lower_static_method_as_function(func_name, params.clone(), body.clone())?;
|
||||
}
|
||||
}
|
||||
@ -43,7 +40,8 @@ impl super::MirBuilder {
|
||||
== Some("1")
|
||||
{
|
||||
// NamingBox SSOT: Use encode_static_method for main/arity entry
|
||||
let func_name = crate::mir::naming::encode_static_method(&box_name, "main", params.len());
|
||||
let func_name =
|
||||
crate::mir::naming::encode_static_method(&box_name, "main", params.len());
|
||||
eprintln!(
|
||||
"[DEBUG] build_static_main_box: Before lower_static_method_as_function"
|
||||
);
|
||||
|
||||
@ -45,7 +45,8 @@ pub fn emit_string<S: Into<String>>(b: &mut MirBuilder, s: S) -> ValueId {
|
||||
});
|
||||
// 🎯 Phase 3-A: String constant type annotation
|
||||
// Ensures string constants have proper Box type for method resolution
|
||||
b.value_types.insert(dst, crate::mir::MirType::Box("StringBox".to_string()));
|
||||
b.value_types
|
||||
.insert(dst, crate::mir::MirType::Box("StringBox".to_string()));
|
||||
b.value_origin_newbox.insert(dst, "StringBox".to_string());
|
||||
dst
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ use crate::mir::builder::observe::types as type_trace;
|
||||
impl super::MirBuilder {
|
||||
// Main expression dispatcher
|
||||
pub(super) fn build_expression_impl(&mut self, ast: ASTNode) -> Result<ValueId, String> {
|
||||
// Track current source span for downstream instruction emission
|
||||
self.current_span = ast.span();
|
||||
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
||||
if matches!(ast, ASTNode::Loop { .. }) {
|
||||
eprintln!("[build_expression_impl] === ENTRY === processing Loop node");
|
||||
@ -296,7 +298,8 @@ impl super::MirBuilder {
|
||||
args: vec![],
|
||||
effects: super::EffectMask::MUT,
|
||||
})?;
|
||||
self.value_origin_newbox.insert(arr_id, "ArrayBox".to_string());
|
||||
self.value_origin_newbox
|
||||
.insert(arr_id, "ArrayBox".to_string());
|
||||
self.value_types
|
||||
.insert(arr_id, super::MirType::Box("ArrayBox".to_string()));
|
||||
// TypeRegistry + trace for deterministic debug
|
||||
@ -339,7 +342,8 @@ impl super::MirBuilder {
|
||||
args: vec![],
|
||||
effects: super::EffectMask::MUT,
|
||||
})?;
|
||||
self.value_origin_newbox.insert(map_id, "MapBox".to_string());
|
||||
self.value_origin_newbox
|
||||
.insert(map_id, "MapBox".to_string());
|
||||
self.value_types
|
||||
.insert(map_id, super::MirType::Box("MapBox".to_string()));
|
||||
self.type_registry
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
use super::{
|
||||
BasicBlockId, EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType,
|
||||
ValueId,
|
||||
BasicBlockId, EffectMask, FunctionSignature, MirInstruction, MirModule, MirType, ValueId,
|
||||
};
|
||||
use crate::ast::ASTNode;
|
||||
|
||||
@ -63,7 +62,8 @@ impl super::MirBuilder {
|
||||
}
|
||||
}
|
||||
pub(super) fn prepare_module(&mut self) -> Result<(), String> {
|
||||
let module = MirModule::new("main".to_string());
|
||||
let mut module = MirModule::new("main".to_string());
|
||||
module.metadata.source_file = self.current_source_file();
|
||||
let main_signature = FunctionSignature {
|
||||
name: "main".to_string(),
|
||||
params: vec![],
|
||||
@ -72,7 +72,7 @@ impl super::MirBuilder {
|
||||
};
|
||||
|
||||
let entry_block = self.block_gen.next();
|
||||
let mut main_function = MirFunction::new(main_signature, entry_block);
|
||||
let mut main_function = self.new_function_with_metadata(main_signature, entry_block);
|
||||
main_function.metadata.is_entry_point = true;
|
||||
|
||||
self.current_module = Some(module);
|
||||
@ -391,7 +391,7 @@ impl super::MirBuilder {
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
let entry = BasicBlockId::new(0);
|
||||
let mut f = MirFunction::new(sig, entry);
|
||||
let mut f = self.new_function_with_metadata(sig, entry);
|
||||
// body: const 1; return it(FunctionEmissionBox を使用)
|
||||
let one = crate::mir::function_emission::emit_const_integer(&mut f, entry, 1);
|
||||
crate::mir::function_emission::emit_return_value(&mut f, entry, one);
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
//! 🎯 箱理論: TypeRegistryBox 統合対応
|
||||
//! NYASH_USE_TYPE_REGISTRY=1 で TypeRegistry 経由に切り替え(段階的移行)
|
||||
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::builder::observe::types as type_trace;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::{MirType, ValueId};
|
||||
|
||||
/// src から dst へ builder 内メタデータ(value_types / value_origin_newbox)を伝播する。
|
||||
|
||||
@ -41,8 +41,8 @@ impl MeCallPolicyBox {
|
||||
// Instance methods: params[0] is Box(box_name)
|
||||
// Static methods: params[0] is non-Box or params.is_empty()
|
||||
let params = &func.signature.params;
|
||||
let is_instance_method = !params.is_empty()
|
||||
&& matches!(params[0], MirType::Box(_));
|
||||
let is_instance_method =
|
||||
!params.is_empty() && matches!(params[0], MirType::Box(_));
|
||||
|
||||
// Expected argument count from signature (including receiver for instance)
|
||||
let expected_params = params.len();
|
||||
@ -53,18 +53,14 @@ impl MeCallPolicyBox {
|
||||
let call_args: Vec<ValueId> = if is_instance_method {
|
||||
// Instance method: prepend 'me' receiver
|
||||
if expected_params != provided_instance {
|
||||
if std::env::var("NYASH_ME_CALL_ARITY_STRICT")
|
||||
.ok()
|
||||
.as_deref()
|
||||
if std::env::var("NYASH_ME_CALL_ARITY_STRICT").ok().as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
return Err(format!(
|
||||
"[me-call] arity mismatch (instance): {}: declared {} params, got {} args(+me)",
|
||||
fname, expected_params, provided_instance
|
||||
));
|
||||
} else if std::env::var("NYASH_STATIC_CALL_TRACE")
|
||||
.ok()
|
||||
.as_deref()
|
||||
} else if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
eprintln!(
|
||||
@ -81,18 +77,14 @@ impl MeCallPolicyBox {
|
||||
} else {
|
||||
// Static method: no receiver
|
||||
if expected_params != provided_static {
|
||||
if std::env::var("NYASH_ME_CALL_ARITY_STRICT")
|
||||
.ok()
|
||||
.as_deref()
|
||||
if std::env::var("NYASH_ME_CALL_ARITY_STRICT").ok().as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
return Err(format!(
|
||||
"[me-call] arity mismatch (static): {}: declared {} params, got {} args",
|
||||
fname, expected_params, provided_static
|
||||
));
|
||||
} else if std::env::var("NYASH_STATIC_CALL_TRACE")
|
||||
.ok()
|
||||
.as_deref()
|
||||
} else if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
eprintln!(
|
||||
|
||||
@ -29,9 +29,6 @@ pub fn ty(event: &str, vid: ValueId, ty: &MirType) {
|
||||
/// Trace propagation between ValueIds.
|
||||
pub fn propagate(event: &str, src: ValueId, dst: ValueId) {
|
||||
if enabled() {
|
||||
eprintln!(
|
||||
"[type-trace] propagate:{} %{} → %{}",
|
||||
event, src.0, dst.0
|
||||
);
|
||||
eprintln!("[type-trace] propagate:{} %{} → %{}", event, src.0, dst.0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,16 +71,26 @@ impl super::MirBuilder {
|
||||
let lhs_is_str = match self.value_types.get(&lhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => self.value_origin_newbox.get(&lhs).map(|s| s == "StringBox").unwrap_or(false),
|
||||
_ => self
|
||||
.value_origin_newbox
|
||||
.get(&lhs)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
let rhs_is_str = match self.value_types.get(&rhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => self.value_origin_newbox.get(&rhs).map(|s| s == "StringBox").unwrap_or(false),
|
||||
_ => self
|
||||
.value_origin_newbox
|
||||
.get(&rhs)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
if lhs_is_str || rhs_is_str {
|
||||
self.value_types.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox.insert(dst, "StringBox".to_string());
|
||||
self.value_types
|
||||
.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox
|
||||
.insert(dst, "StringBox".to_string());
|
||||
} else {
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
}
|
||||
@ -147,16 +157,26 @@ impl super::MirBuilder {
|
||||
let lhs_is_str = match self.value_types.get(&lhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => self.value_origin_newbox.get(&lhs).map(|s| s == "StringBox").unwrap_or(false),
|
||||
_ => self
|
||||
.value_origin_newbox
|
||||
.get(&lhs)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
let rhs_is_str = match self.value_types.get(&rhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => self.value_origin_newbox.get(&rhs).map(|s| s == "StringBox").unwrap_or(false),
|
||||
_ => self
|
||||
.value_origin_newbox
|
||||
.get(&rhs)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
if lhs_is_str || rhs_is_str {
|
||||
self.value_types.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox.insert(dst, "StringBox".to_string());
|
||||
self.value_types
|
||||
.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox
|
||||
.insert(dst, "StringBox".to_string());
|
||||
} else {
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
}
|
||||
@ -180,16 +200,26 @@ impl super::MirBuilder {
|
||||
let lhs_is_str = match self.value_types.get(&lhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => self.value_origin_newbox.get(&lhs).map(|s| s == "StringBox").unwrap_or(false),
|
||||
_ => self
|
||||
.value_origin_newbox
|
||||
.get(&lhs)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
let rhs_is_str = match self.value_types.get(&rhs) {
|
||||
Some(MirType::String) => true,
|
||||
Some(MirType::Box(bt)) if bt == "StringBox" => true,
|
||||
_ => self.value_origin_newbox.get(&rhs).map(|s| s == "StringBox").unwrap_or(false),
|
||||
_ => self
|
||||
.value_origin_newbox
|
||||
.get(&rhs)
|
||||
.map(|s| s == "StringBox")
|
||||
.unwrap_or(false),
|
||||
};
|
||||
if lhs_is_str || rhs_is_str {
|
||||
self.value_types.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox.insert(dst, "StringBox".to_string());
|
||||
self.value_types
|
||||
.insert(dst, MirType::Box("StringBox".to_string()));
|
||||
self.value_origin_newbox
|
||||
.insert(dst, "StringBox".to_string());
|
||||
} else {
|
||||
self.value_types.insert(dst, MirType::Integer);
|
||||
}
|
||||
|
||||
@ -121,7 +121,9 @@ pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId
|
||||
// CRITICAL FIX: For receiver kind, if type is missing but origin exists,
|
||||
// infer MirType::Box from origin
|
||||
if kind == LocalKind::Recv && builder.value_types.get(&loc).is_none() {
|
||||
builder.value_types.insert(loc, crate::mir::MirType::Box(cls));
|
||||
builder
|
||||
.value_types
|
||||
.insert(loc, crate::mir::MirType::Box(cls));
|
||||
}
|
||||
}
|
||||
builder.local_ssa_map.insert(key, loc);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use super::{BasicBlock, BasicBlockId};
|
||||
use crate::mir::{BarrierOp, TypeOpKind, WeakRefOp};
|
||||
use crate::mir::{BarrierOp, SpannedInstruction, TypeOpKind, WeakRefOp};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
// include path resolver removed (using handles modules)
|
||||
|
||||
@ -405,7 +405,10 @@ impl super::MirBuilder {
|
||||
bb, dst.0, src.0, block.phi_instructions().count());
|
||||
}
|
||||
// Propagate effects on the block
|
||||
block.insert_instruction_after_phis(super::MirInstruction::Copy { dst, src });
|
||||
block.insert_spanned_after_phis(SpannedInstruction {
|
||||
inst: super::MirInstruction::Copy { dst, src },
|
||||
span: self.current_span,
|
||||
});
|
||||
// Lightweight metadata propagation (unified)
|
||||
crate::mir::builder::metadata::propagate::propagate(self, src, dst);
|
||||
return Ok(());
|
||||
|
||||
@ -2,8 +2,10 @@
|
||||
//!
|
||||
//! CFG sanity checks and dispatcher helpers for MIR-based lowering.
|
||||
|
||||
use crate::mir::{BasicBlockId, BinaryOp, ConstValue, MirInstruction};
|
||||
pub mod case_a;
|
||||
|
||||
use crate::mir::query::{MirQuery, MirQueryBox};
|
||||
use crate::mir::{BasicBlockId, BinaryOp, ConstValue, MirInstruction};
|
||||
|
||||
/// Check if entry block has at least one successor
|
||||
///
|
||||
@ -191,7 +193,10 @@ where
|
||||
use crate::mir::join_ir::env_flag_is_1;
|
||||
|
||||
if env_flag_is_1("NYASH_JOINIR_LOWER_FROM_MIR") {
|
||||
eprintln!("[joinir/{}] Using MIR-based lowering (NYASH_JOINIR_LOWER_FROM_MIR=1)", tag);
|
||||
eprintln!(
|
||||
"[joinir/{}] Using MIR-based lowering (NYASH_JOINIR_LOWER_FROM_MIR=1)",
|
||||
tag
|
||||
);
|
||||
mir_based(module)
|
||||
} else {
|
||||
eprintln!("[joinir/{}] Using handwritten lowering (default)", tag);
|
||||
|
||||
20
src/mir/join_ir/lowering/common/case_a.rs
Normal file
20
src/mir/join_ir/lowering/common/case_a.rs
Normal file
@ -0,0 +1,20 @@
|
||||
//! Common helpers for simple Case A loop detection (single header/body/exit with minimal branches).
|
||||
//!
|
||||
//! 目的:
|
||||
//! - minimal 系ロワーで個別に持っていた「Case A っぽいか?」チェックを一箇所に集約する。
|
||||
//! - generic_case_a を噛ませる候補判定を共通化し、責務を明示する。
|
||||
|
||||
use crate::mir::loop_form::LoopForm;
|
||||
|
||||
/// Detects a minimal single-header Case A loop shape.
|
||||
///
|
||||
/// 条件:
|
||||
/// - header と exit が異なる
|
||||
/// - latch が body か header のどちらか
|
||||
/// - continue/break の出発点がそれぞれ高々1
|
||||
pub fn is_simple_case_a_loop(loop_form: &LoopForm) -> bool {
|
||||
loop_form.header != loop_form.exit
|
||||
&& (loop_form.latch == loop_form.body || loop_form.latch == loop_form.header)
|
||||
&& loop_form.continue_targets.len() <= 1
|
||||
&& loop_form.break_targets.len() <= 1
|
||||
}
|
||||
35
src/mir/join_ir/lowering/exit_args_resolver.rs
Normal file
35
src/mir/join_ir/lowering/exit_args_resolver.rs
Normal file
@ -0,0 +1,35 @@
|
||||
//! Exit argument resolver for JoinIR loop lowering.
|
||||
//!
|
||||
//! 役割:
|
||||
//! - ExitLiveness から得た live 集合と、名前→ValueId マップを突き合わせて exit_args を決定する。
|
||||
//! - live 集合に未解決名があれば None で返し、フォールバックを促す。
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// live 集合と名前マップから exit_args を決定する。
|
||||
pub(crate) fn resolve_exit_args(
|
||||
live_names: &BTreeSet<String>,
|
||||
name_to_id: &std::collections::BTreeMap<String, ValueId>,
|
||||
fallback_carriers: &[String],
|
||||
) -> Option<Vec<ValueId>> {
|
||||
let mut args = Vec::new();
|
||||
for name in live_names {
|
||||
if let Some(v) = name_to_id.get(name) {
|
||||
args.push(*v);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if args.is_empty() {
|
||||
for name in fallback_carriers {
|
||||
if let Some(v) = name_to_id.get(name) {
|
||||
args.push(*v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(args)
|
||||
}
|
||||
@ -43,11 +43,12 @@
|
||||
//! ```
|
||||
|
||||
use crate::mir::join_ir::lowering::common::{
|
||||
dispatch_lowering, ensure_entry_has_succs, has_const_int,
|
||||
has_array_method, has_loop_increment, log_fallback
|
||||
dispatch_lowering, ensure_entry_has_succs, has_array_method, has_const_int, has_loop_increment,
|
||||
log_fallback,
|
||||
};
|
||||
use crate::mir::join_ir::lowering::value_id_ranges::funcscanner_append_defs as vid;
|
||||
use crate::mir::join_ir::{JoinModule};
|
||||
use crate::mir::join_ir::JoinModule;
|
||||
use crate::mir::loop_form::LoopForm;
|
||||
use crate::mir::query::{MirQuery, MirQueryBox};
|
||||
|
||||
/// Phase 27.14: FuncScannerBox._append_defs の JoinIR lowering(public dispatcher)
|
||||
@ -61,7 +62,9 @@ use crate::mir::query::{MirQuery, MirQueryBox};
|
||||
///
|
||||
/// ## Shared Builder Pattern
|
||||
/// 両方の実装が `build_funcscanner_append_defs_joinir()` を呼び出す共通パターン。
|
||||
pub fn lower_funcscanner_append_defs_to_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
pub fn lower_funcscanner_append_defs_to_joinir(
|
||||
module: &crate::mir::MirModule,
|
||||
) -> Option<JoinModule> {
|
||||
dispatch_lowering(
|
||||
"funcscanner_append_defs",
|
||||
module,
|
||||
@ -164,7 +167,9 @@ fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Optio
|
||||
let const_1 = vid::loop_step(13); // 10013
|
||||
|
||||
// cmp_result = (i >= n)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp_result,
|
||||
op: CompareOp::Ge,
|
||||
lhs: i_loop,
|
||||
@ -183,7 +188,9 @@ fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Optio
|
||||
});
|
||||
|
||||
// item = defs_box.get(i)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(item_value),
|
||||
box_name: "ArrayBox".to_string(),
|
||||
method: "get".to_string(),
|
||||
@ -191,7 +198,9 @@ fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Optio
|
||||
}));
|
||||
|
||||
// dst.push(item) - 破壊的変更(戻り値なし)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: None, // push は戻り値なし
|
||||
box_name: "ArrayBox".to_string(),
|
||||
method: "push".to_string(),
|
||||
@ -199,13 +208,17 @@ fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Optio
|
||||
}));
|
||||
|
||||
// const_1 = 1
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_1,
|
||||
value: ConstValue::Integer(1),
|
||||
}));
|
||||
|
||||
// next_i = i + 1
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: next_i,
|
||||
op: BinOpKind::Add,
|
||||
lhs: i_loop,
|
||||
@ -223,7 +236,10 @@ fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Optio
|
||||
join_module.add_function(loop_step_func);
|
||||
|
||||
eprintln!("[joinir/funcscanner_append_defs/build] ✅ JoinIR construction completed");
|
||||
eprintln!("[joinir/funcscanner_append_defs/build] Functions: {}", join_module.functions.len());
|
||||
eprintln!(
|
||||
"[joinir/funcscanner_append_defs/build] Functions: {}",
|
||||
join_module.functions.len()
|
||||
);
|
||||
|
||||
Some(join_module)
|
||||
}
|
||||
@ -251,7 +267,10 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
let target_func = module.functions.get("FuncScannerBox._append_defs/2")?;
|
||||
|
||||
eprintln!("[joinir/funcscanner_append_defs/mir] Found FuncScannerBox._append_defs/2");
|
||||
eprintln!("[joinir/funcscanner_append_defs/mir] MIR blocks: {}", target_func.blocks.len());
|
||||
eprintln!(
|
||||
"[joinir/funcscanner_append_defs/mir] MIR blocks: {}",
|
||||
target_func.blocks.len()
|
||||
);
|
||||
|
||||
// Step 2: MirQueryBox を作成
|
||||
let query = MirQueryBox::new(target_func);
|
||||
@ -266,17 +285,26 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
// CFG Check 2: Entry block contains expected patterns
|
||||
// Pattern 1: i = 0 (初期化)
|
||||
if !has_const_int(&query, entry, 0) {
|
||||
log_fallback("funcscanner_append_defs", "Const(0) not found in entry block");
|
||||
log_fallback(
|
||||
"funcscanner_append_defs",
|
||||
"Const(0) not found in entry block",
|
||||
);
|
||||
return lower_handwritten(module);
|
||||
}
|
||||
|
||||
// Pattern 2: defs_box.length() の検出
|
||||
// Check entry block and its immediate successors for length() call
|
||||
let has_length_call = has_array_method(&query, entry, "length")
|
||||
|| query.succs(entry).iter().any(|&succ| has_array_method(&query, succ, "length"));
|
||||
|| query
|
||||
.succs(entry)
|
||||
.iter()
|
||||
.any(|&succ| has_array_method(&query, succ, "length"));
|
||||
|
||||
if !has_length_call {
|
||||
log_fallback("funcscanner_append_defs", "ArrayBox.length() not found in entry or successors");
|
||||
log_fallback(
|
||||
"funcscanner_append_defs",
|
||||
"ArrayBox.length() not found in entry or successors",
|
||||
);
|
||||
return lower_handwritten(module);
|
||||
}
|
||||
|
||||
@ -284,27 +312,78 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
// Check all blocks for array operations (get/push) and loop increment
|
||||
let all_blocks: Vec<_> = target_func.blocks.keys().copied().collect();
|
||||
|
||||
let has_get_call = all_blocks.iter().any(|&bb| has_array_method(&query, bb, "get"));
|
||||
let has_get_call = all_blocks
|
||||
.iter()
|
||||
.any(|&bb| has_array_method(&query, bb, "get"));
|
||||
if !has_get_call {
|
||||
log_fallback("funcscanner_append_defs", "ArrayBox.get() not found in function body");
|
||||
log_fallback(
|
||||
"funcscanner_append_defs",
|
||||
"ArrayBox.get() not found in function body",
|
||||
);
|
||||
return lower_handwritten(module);
|
||||
}
|
||||
|
||||
let has_push_call = all_blocks.iter().any(|&bb| has_array_method(&query, bb, "push"));
|
||||
let has_push_call = all_blocks
|
||||
.iter()
|
||||
.any(|&bb| has_array_method(&query, bb, "push"));
|
||||
if !has_push_call {
|
||||
log_fallback("funcscanner_append_defs", "ArrayBox.push() not found in function body");
|
||||
log_fallback(
|
||||
"funcscanner_append_defs",
|
||||
"ArrayBox.push() not found in function body",
|
||||
);
|
||||
return lower_handwritten(module);
|
||||
}
|
||||
|
||||
let has_increment = all_blocks.iter().any(|&bb| has_loop_increment(&query, bb));
|
||||
if !has_increment {
|
||||
log_fallback("funcscanner_append_defs", "loop increment (i + 1) not found in function body");
|
||||
log_fallback(
|
||||
"funcscanner_append_defs",
|
||||
"loop increment (i + 1) not found in function body",
|
||||
);
|
||||
return lower_handwritten(module);
|
||||
}
|
||||
|
||||
eprintln!("[joinir/funcscanner_append_defs/mir] CFG sanity checks passed ✅");
|
||||
eprintln!("[joinir/funcscanner_append_defs/mir] Found: length(), get(), push(), i+1");
|
||||
|
||||
if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") {
|
||||
let header = query.succs(entry).get(0).copied().unwrap_or(entry);
|
||||
let succs_header = query.succs(header);
|
||||
let body = succs_header.get(0).copied().unwrap_or(header);
|
||||
let exit = succs_header.get(1).copied().unwrap_or(header);
|
||||
let loop_form = LoopForm {
|
||||
preheader: entry,
|
||||
header,
|
||||
body,
|
||||
latch: body,
|
||||
exit,
|
||||
continue_targets: vec![body],
|
||||
break_targets: vec![exit],
|
||||
};
|
||||
if crate::mir::join_ir::lowering::common::case_a::is_simple_case_a_loop(&loop_form) {
|
||||
eprintln!(
|
||||
"[joinir/funcscanner_append_defs/generic-hook] detected simple Case A loop (generic_case_a hookup point)"
|
||||
);
|
||||
let var_classes = crate::mir::phi_core::loop_var_classifier::LoopVarClassBox::new();
|
||||
let exit_live = crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox::new();
|
||||
if let Some(jm) = crate::mir::join_ir::lowering::generic_case_a::lower_case_a_loop_to_joinir_for_append_defs_minimal(
|
||||
&loop_form,
|
||||
&var_classes,
|
||||
&exit_live,
|
||||
&query,
|
||||
target_func,
|
||||
) {
|
||||
eprintln!(
|
||||
"[joinir/funcscanner_append_defs/generic-hook] generic_case_a produced JoinIR, returning early"
|
||||
);
|
||||
return Some(jm);
|
||||
}
|
||||
eprintln!(
|
||||
"[joinir/funcscanner_append_defs/generic-hook] generic_case_a returned None, falling back to handwritten/MIR path"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 27.14: Generate JoinIR using shared builder
|
||||
// CFG checks passed, so we can use build_funcscanner_append_defs_joinir() directly
|
||||
eprintln!("[joinir/funcscanner_append_defs/mir] Calling build_funcscanner_append_defs_joinir() after CFG validation");
|
||||
|
||||
@ -44,22 +44,19 @@
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::join_ir::{
|
||||
BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction, JoinInst, JoinModule,
|
||||
LoopExitShape, LoopHeaderShape, MirLikeInst,
|
||||
};
|
||||
use crate::mir::loop_form::LoopForm;
|
||||
use crate::mir::query::MirQuery;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Phase 27.9: Toggle dispatcher for trim lowering
|
||||
/// - Default: handwritten lowering
|
||||
/// - NYASH_JOINIR_LOWER_FROM_MIR=1: MIR-based lowering
|
||||
pub fn lower_funcscanner_trim_to_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
super::common::dispatch_lowering(
|
||||
"trim",
|
||||
module,
|
||||
lower_trim_from_mir,
|
||||
lower_trim_handwritten,
|
||||
)
|
||||
super::common::dispatch_lowering("trim", module, lower_trim_from_mir, lower_trim_handwritten)
|
||||
}
|
||||
|
||||
/// Phase 27.11: Common JoinIR builder for FuncScannerBox.trim/1
|
||||
@ -72,14 +69,18 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
let target_func = module.functions.get("FuncScannerBox.trim/1")?;
|
||||
|
||||
eprintln!("[joinir/trim/build] Found FuncScannerBox.trim/1");
|
||||
eprintln!("[joinir/trim/build] MIR blocks: {}", target_func.blocks.len());
|
||||
eprintln!(
|
||||
"[joinir/trim/build] MIR blocks: {}",
|
||||
target_func.blocks.len()
|
||||
);
|
||||
|
||||
let mut join_module = JoinModule::new();
|
||||
|
||||
// trim_main 関数: 前処理 + 先頭/末尾の空白を除去
|
||||
let trim_main_id = JoinFuncId::new(0);
|
||||
let s_param = ValueId(5000);
|
||||
let mut trim_main_func = JoinFunction::new(trim_main_id, "trim_main".to_string(), vec![s_param]);
|
||||
let mut trim_main_func =
|
||||
JoinFunction::new(trim_main_id, "trim_main".to_string(), vec![s_param]);
|
||||
|
||||
let str_val = ValueId(5001);
|
||||
let n_val = ValueId(5002);
|
||||
@ -89,11 +90,15 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
let const_zero = ValueId(5006);
|
||||
|
||||
// str = "" + s_param (文字列化)
|
||||
trim_main_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
trim_main_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_empty,
|
||||
value: ConstValue::String("".to_string()),
|
||||
}));
|
||||
trim_main_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
trim_main_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: str_val,
|
||||
lhs: const_empty,
|
||||
rhs: s_param,
|
||||
@ -101,7 +106,9 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
}));
|
||||
|
||||
// n = str.length()
|
||||
trim_main_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
trim_main_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(n_val),
|
||||
box_name: "StringBox".to_string(),
|
||||
method: "length".to_string(),
|
||||
@ -109,7 +116,9 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
}));
|
||||
|
||||
// const 0
|
||||
trim_main_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
trim_main_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_zero,
|
||||
value: ConstValue::Integer(0),
|
||||
}));
|
||||
@ -124,7 +133,9 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
});
|
||||
|
||||
// e_init = n (コピー)
|
||||
trim_main_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
trim_main_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: e_init,
|
||||
op: BinOpKind::Add,
|
||||
lhs: n_val,
|
||||
@ -167,7 +178,9 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
|
||||
// cond = (e > b)
|
||||
let cond = ValueId(6003);
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cond,
|
||||
lhs: e_loop,
|
||||
rhs: b_loop,
|
||||
@ -176,14 +189,18 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
|
||||
// bool false (共通)
|
||||
let bool_false = ValueId(6019);
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: bool_false,
|
||||
value: ConstValue::Bool(false),
|
||||
}));
|
||||
|
||||
// trimmed_base = str.substring(b, e)
|
||||
let trimmed_base = ValueId(6004);
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(trimmed_base),
|
||||
box_name: "StringBox".to_string(),
|
||||
method: "substring".to_string(),
|
||||
@ -192,7 +209,9 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
|
||||
// cond_is_false = (cond == false)
|
||||
let cond_is_false = ValueId(6020);
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cond_is_false,
|
||||
lhs: cond,
|
||||
rhs: bool_false,
|
||||
@ -213,14 +232,18 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
|
||||
// const 1
|
||||
let const_1 = ValueId(6005);
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_1,
|
||||
value: ConstValue::Integer(1),
|
||||
}));
|
||||
|
||||
// e_minus_1 = e - 1
|
||||
let e_minus_1 = ValueId(6006);
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: e_minus_1,
|
||||
lhs: e_loop,
|
||||
rhs: const_1,
|
||||
@ -228,7 +251,9 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
}));
|
||||
|
||||
let ch = ValueId(6007);
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(ch),
|
||||
box_name: "StringBox".to_string(),
|
||||
method: "substring".to_string(),
|
||||
@ -246,44 +271,60 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
let const_newline = ValueId(6014);
|
||||
let const_cr = ValueId(6015);
|
||||
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_space,
|
||||
value: ConstValue::String(" ".to_string()),
|
||||
}));
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp_space,
|
||||
lhs: ch,
|
||||
rhs: const_space,
|
||||
op: CompareOp::Eq,
|
||||
}));
|
||||
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_tab,
|
||||
value: ConstValue::String("\\t".to_string()),
|
||||
}));
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp_tab,
|
||||
lhs: ch,
|
||||
rhs: const_tab,
|
||||
op: CompareOp::Eq,
|
||||
}));
|
||||
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_newline,
|
||||
value: ConstValue::String("\\n".to_string()),
|
||||
}));
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp_newline,
|
||||
lhs: ch,
|
||||
rhs: const_newline,
|
||||
op: CompareOp::Eq,
|
||||
}));
|
||||
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_cr,
|
||||
value: ConstValue::String("\\r".to_string()),
|
||||
}));
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp_cr,
|
||||
lhs: ch,
|
||||
rhs: const_cr,
|
||||
@ -295,21 +336,27 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
let or2 = ValueId(6017);
|
||||
let is_space = ValueId(6018);
|
||||
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: or1,
|
||||
lhs: cmp_space,
|
||||
rhs: cmp_tab,
|
||||
op: BinOpKind::Or,
|
||||
}));
|
||||
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: or2,
|
||||
lhs: or1,
|
||||
rhs: cmp_newline,
|
||||
op: BinOpKind::Or,
|
||||
}));
|
||||
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: is_space,
|
||||
lhs: or2,
|
||||
rhs: cmp_cr,
|
||||
@ -318,7 +365,9 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
|
||||
// is_space_false = (is_space == false)
|
||||
let is_space_false = ValueId(6021);
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: is_space_false,
|
||||
lhs: is_space,
|
||||
rhs: bool_false,
|
||||
@ -335,7 +384,9 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
|
||||
// continue path: e_next = e - 1; loop_step(str, b, e_next)
|
||||
let e_next = ValueId(6022);
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: e_next,
|
||||
lhs: e_loop,
|
||||
rhs: const_1,
|
||||
@ -508,7 +559,10 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
|
||||
});
|
||||
|
||||
join_module.add_function(skip_func);
|
||||
eprintln!("[joinir/trim] Generated {} JoinIR functions", join_module.functions.len());
|
||||
eprintln!(
|
||||
"[joinir/trim] Generated {} JoinIR functions",
|
||||
join_module.functions.len()
|
||||
);
|
||||
|
||||
Some(join_module)
|
||||
}
|
||||
@ -526,9 +580,11 @@ fn lower_trim_handwritten(module: &crate::mir::MirModule) -> Option<JoinModule>
|
||||
/// - Lightweight CFG sanity checks
|
||||
/// - Fallback to handwritten if MIR structure is unexpected
|
||||
fn lower_trim_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
use super::common::{
|
||||
ensure_entry_has_succs, has_binop, has_const_string, has_string_method, log_fallback,
|
||||
};
|
||||
use crate::mir::query::MirQueryBox;
|
||||
use crate::mir::BinaryOp;
|
||||
use super::common::{ensure_entry_has_succs, has_const_string, has_string_method, has_binop, log_fallback};
|
||||
|
||||
// Step 1: "FuncScannerBox.trim/1" を探す
|
||||
let target_func = module.functions.get("FuncScannerBox.trim/1")?;
|
||||
@ -550,16 +606,49 @@ fn lower_trim_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
// - Const("") for string coercion
|
||||
// - BoxCall(String.length)
|
||||
// - BinOp(Add) for "" + s
|
||||
if !has_const_string(&query, entry_id, "") ||
|
||||
!has_string_method(&query, entry_id, "length") ||
|
||||
!has_binop(&query, entry_id, BinaryOp::Add)
|
||||
if !has_const_string(&query, entry_id, "")
|
||||
|| !has_string_method(&query, entry_id, "length")
|
||||
|| !has_binop(&query, entry_id, BinaryOp::Add)
|
||||
{
|
||||
log_fallback("trim", "entry block missing expected patterns (Const(\"\"), String.length, or BinOp(Add))");
|
||||
log_fallback(
|
||||
"trim",
|
||||
"entry block missing expected patterns (Const(\"\"), String.length, or BinOp(Add))",
|
||||
);
|
||||
return lower_trim_handwritten(module);
|
||||
}
|
||||
|
||||
eprintln!("[joinir/trim/mir] CFG sanity checks passed ✅");
|
||||
|
||||
if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") {
|
||||
let header = query.succs(entry_id).get(0).copied().unwrap_or(entry_id);
|
||||
let succs_header = query.succs(header);
|
||||
let body = succs_header.get(0).copied().unwrap_or(header);
|
||||
let exit = succs_header.get(1).copied().unwrap_or(header);
|
||||
let loop_form = LoopForm {
|
||||
preheader: entry_id,
|
||||
header,
|
||||
body,
|
||||
latch: body,
|
||||
exit,
|
||||
continue_targets: vec![body],
|
||||
break_targets: vec![exit],
|
||||
};
|
||||
if crate::mir::join_ir::lowering::common::case_a::is_simple_case_a_loop(&loop_form) {
|
||||
eprintln!(
|
||||
"[joinir/trim/generic-hook] simple Case A loop detected (generic_case_a hook planned)"
|
||||
);
|
||||
let var_classes = crate::mir::phi_core::loop_var_classifier::LoopVarClassBox::new();
|
||||
let exit_live = crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox::new();
|
||||
if let Some(jm) = crate::mir::join_ir::lowering::generic_case_a::lower_case_a_loop_to_joinir_for_trim_minimal(&loop_form, &var_classes, &exit_live, &query, target_func) {
|
||||
eprintln!("[joinir/trim/generic-hook] generic_case_a produced JoinIR, returning early");
|
||||
return Some(jm);
|
||||
}
|
||||
eprintln!(
|
||||
"[joinir/trim/generic-hook] generic_case_a placeholder returned None, falling back to handwritten"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 27.11: Generate JoinIR using shared builder
|
||||
// CFG checks passed, so we can use build_funcscanner_trim_joinir() directly
|
||||
eprintln!("[joinir/trim/mir] Calling build_funcscanner_trim_joinir() after CFG validation");
|
||||
|
||||
1196
src/mir/join_ir/lowering/generic_case_a.rs
Normal file
1196
src/mir/join_ir/lowering/generic_case_a.rs
Normal file
File diff suppressed because it is too large
Load Diff
212
src/mir/join_ir/lowering/loop_form_intake.rs
Normal file
212
src/mir/join_ir/lowering/loop_form_intake.rs
Normal file
@ -0,0 +1,212 @@
|
||||
//! LoopForm intake helpers for generic JoinIR lowering.
|
||||
//!
|
||||
//! 役割:
|
||||
//! - LoopForm + MirQuery + MirFunction から、JoinIR 降下に必要な情報を抽出する。
|
||||
//! - pinned/carrier 推定と ValueId マッピング、header/exit スナップショット収集を一元化。
|
||||
//! - generic_case_a/B など複数ロワーで再利用するための「入口箱」だよ。
|
||||
|
||||
use crate::mir::phi_core::local_scope_inspector::LocalScopeInspectorBox;
|
||||
use crate::mir::phi_core::loop_var_classifier::{LoopVarClass, LoopVarClassBox};
|
||||
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, MirQuery, ValueId};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
/// 抽出結果
|
||||
pub(crate) struct LoopFormIntake {
|
||||
pub pinned_ordered: Vec<String>,
|
||||
pub carrier_ordered: Vec<String>,
|
||||
pub name_to_header_id: BTreeMap<String, ValueId>,
|
||||
pub header_snapshot: BTreeMap<String, ValueId>,
|
||||
pub exit_snapshots: Vec<(BasicBlockId, BTreeMap<String, ValueId>)>,
|
||||
pub exit_preds: Vec<BasicBlockId>,
|
||||
}
|
||||
|
||||
/// LoopForm + MIR から pinned/carrier とスナップショットを抽出する。
|
||||
///
|
||||
/// 失敗時は None(フォールバック用)。
|
||||
pub(crate) fn intake_loop_form(
|
||||
loop_form: &crate::mir::loop_form::LoopForm,
|
||||
var_classes: &LoopVarClassBox,
|
||||
query: &impl MirQuery,
|
||||
mir_func: &MirFunction,
|
||||
) -> Option<LoopFormIntake> {
|
||||
// preheader のパラメータを名前付け
|
||||
let mut value_to_name: BTreeMap<ValueId, String> = BTreeMap::new();
|
||||
let mut preheader_names: BTreeMap<String, ValueId> = BTreeMap::new();
|
||||
for (idx, val) in mir_func.params.iter().copied().enumerate() {
|
||||
let name = if idx == 0 {
|
||||
"s".to_string()
|
||||
} else {
|
||||
format!("param{}", idx)
|
||||
};
|
||||
value_to_name.insert(val, name.clone());
|
||||
preheader_names.insert(name, val);
|
||||
}
|
||||
|
||||
let mut string_name: Option<String> = value_to_name.get(mir_func.params.get(0)?).cloned();
|
||||
let mut len_name: Option<String> = None;
|
||||
let mut index_name: Option<String> = None;
|
||||
|
||||
for inst in query.insts_in_block(loop_form.preheader) {
|
||||
match inst {
|
||||
MirInstruction::BoxCall {
|
||||
dst: Some(dst),
|
||||
method,
|
||||
args,
|
||||
..
|
||||
} if method == "length" => {
|
||||
if let Some(arg) = args.get(0) {
|
||||
let s_name = value_to_name
|
||||
.get(arg)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "s".to_string());
|
||||
value_to_name.entry(*arg).or_insert(s_name.clone());
|
||||
string_name.get_or_insert(s_name.clone());
|
||||
}
|
||||
value_to_name.entry(*dst).or_insert_with(|| "n".to_string());
|
||||
len_name.get_or_insert_with(|| value_to_name[dst].clone());
|
||||
preheader_names
|
||||
.entry(value_to_name[dst].clone())
|
||||
.or_insert(*dst);
|
||||
}
|
||||
MirInstruction::Const {
|
||||
dst,
|
||||
value: crate::mir::types::ConstValue::Integer(0),
|
||||
} if index_name.is_none() => {
|
||||
value_to_name.entry(*dst).or_insert_with(|| "i".to_string());
|
||||
index_name = Some(value_to_name[dst].clone());
|
||||
preheader_names
|
||||
.entry(value_to_name[dst].clone())
|
||||
.or_insert(*dst);
|
||||
}
|
||||
MirInstruction::Copy { dst, src } => {
|
||||
if let Some(existing) = value_to_name.get(src).cloned() {
|
||||
value_to_name.entry(*dst).or_insert(existing.clone());
|
||||
preheader_names.entry(existing).or_insert(*dst);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// header φ を読む
|
||||
let mut header_vals_mir: BTreeMap<String, ValueId> = BTreeMap::new();
|
||||
let mut snapshot_maps: BTreeMap<BasicBlockId, BTreeMap<String, ValueId>> = BTreeMap::new();
|
||||
let mut pinned_hint: Vec<String> = Vec::new();
|
||||
let mut carrier_hint: Vec<String> = Vec::new();
|
||||
|
||||
for inst in query.insts_in_block(loop_form.header) {
|
||||
if let MirInstruction::Phi { dst, inputs } = inst {
|
||||
let name = inputs
|
||||
.iter()
|
||||
.find_map(|(_, v)| value_to_name.get(v).cloned())
|
||||
.unwrap_or_else(|| format!("v{}", dst.0));
|
||||
value_to_name.entry(*dst).or_insert(name.clone());
|
||||
header_vals_mir.insert(name.clone(), *dst);
|
||||
|
||||
let pre_val = inputs
|
||||
.iter()
|
||||
.find(|(bb, _)| *bb == loop_form.preheader)
|
||||
.map(|(_, v)| *v);
|
||||
let latch_val = inputs
|
||||
.iter()
|
||||
.find(|(bb, _)| *bb == loop_form.latch)
|
||||
.map(|(_, v)| *v);
|
||||
let is_carrier = match (pre_val, latch_val) {
|
||||
(Some(p), Some(l)) => p != l,
|
||||
(None, Some(_)) => true,
|
||||
_ => false,
|
||||
};
|
||||
if is_carrier {
|
||||
carrier_hint.push(name.clone());
|
||||
} else {
|
||||
pinned_hint.push(name.clone());
|
||||
}
|
||||
|
||||
for (bb, val) in inputs {
|
||||
let entry = snapshot_maps.entry(*bb).or_default();
|
||||
entry.insert(name.clone(), *val);
|
||||
value_to_name.entry(*val).or_insert(name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// header で読まれている値も拾う
|
||||
for inst in query.insts_in_block(loop_form.header) {
|
||||
for used in query.reads_of(inst) {
|
||||
if let Some(name) = value_to_name.get(&used).cloned() {
|
||||
header_vals_mir.entry(name).or_insert(used);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// preheader の既知を補完(s/n/i)
|
||||
for (name, val) in &preheader_names {
|
||||
header_vals_mir.entry(name.clone()).or_insert(*val);
|
||||
}
|
||||
|
||||
// exit preds を CFG から集める
|
||||
let mut exit_preds: Vec<BasicBlockId> = mir_func
|
||||
.blocks
|
||||
.iter()
|
||||
.filter(|(_, bb)| bb.successors.contains(&loop_form.exit))
|
||||
.map(|(id, _)| *id)
|
||||
.collect();
|
||||
exit_preds.sort_by_key(|b| b.0);
|
||||
|
||||
if exit_preds.contains(&loop_form.header) {
|
||||
snapshot_maps
|
||||
.entry(loop_form.header)
|
||||
.or_insert_with(|| header_vals_mir.clone());
|
||||
}
|
||||
|
||||
let mut exit_snapshots: Vec<(BasicBlockId, BTreeMap<String, ValueId>)> = Vec::new();
|
||||
for pred in &exit_preds {
|
||||
if let Some(snap) = snapshot_maps.get(pred) {
|
||||
exit_snapshots.push((*pred, snap.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// LocalScopeInspector に snapshot を登録して VarClass 判定の土台を整える
|
||||
let mut inspector = LocalScopeInspectorBox::new();
|
||||
inspector.record_snapshot(loop_form.header, &header_vals_mir);
|
||||
for (bb, snap) in &exit_snapshots {
|
||||
inspector.record_snapshot(*bb, snap);
|
||||
}
|
||||
|
||||
let all_names: Vec<String> = header_vals_mir.keys().cloned().collect();
|
||||
let classified = var_classes.classify_all(
|
||||
&all_names,
|
||||
&pinned_hint,
|
||||
&carrier_hint,
|
||||
&inspector,
|
||||
&exit_preds,
|
||||
);
|
||||
|
||||
let ordered_pinned: Vec<String> = classified
|
||||
.iter()
|
||||
.filter(|(_, c)| matches!(c, LoopVarClass::Pinned))
|
||||
.map(|(n, _)| n.clone())
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect();
|
||||
let ordered_carriers: Vec<String> = classified
|
||||
.iter()
|
||||
.filter(|(_, c)| matches!(c, LoopVarClass::Carrier))
|
||||
.map(|(n, _)| n.clone())
|
||||
.collect::<BTreeSet<_>>()
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
if ordered_pinned.is_empty() || ordered_carriers.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(LoopFormIntake {
|
||||
pinned_ordered: ordered_pinned,
|
||||
carrier_ordered: ordered_carriers,
|
||||
name_to_header_id: header_vals_mir.clone(),
|
||||
header_snapshot: header_vals_mir,
|
||||
exit_snapshots,
|
||||
exit_preds,
|
||||
})
|
||||
}
|
||||
@ -31,10 +31,10 @@
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::join_ir::{
|
||||
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule, MirLikeInst,
|
||||
};
|
||||
use crate::mir::ValueId;
|
||||
|
||||
pub fn lower_min_loop_to_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
// Step 1: "JoinIrMin.main/0" を探す
|
||||
@ -87,13 +87,17 @@ pub fn lower_min_loop_to_joinir(module: &crate::mir::MirModule) -> Option<JoinMo
|
||||
let i_plus_1 = ValueId(2002);
|
||||
|
||||
// const 2 (for comparison)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_2,
|
||||
value: ConstValue::Integer(2),
|
||||
}));
|
||||
|
||||
// cmp_result = (i >= 2)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp_result,
|
||||
op: CompareOp::Ge,
|
||||
lhs: i_param,
|
||||
@ -109,13 +113,17 @@ pub fn lower_min_loop_to_joinir(module: &crate::mir::MirModule) -> Option<JoinMo
|
||||
});
|
||||
|
||||
// const 1 (for increment)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_1,
|
||||
value: ConstValue::Integer(1),
|
||||
}));
|
||||
|
||||
// i_plus_1 = i + 1
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: i_plus_1,
|
||||
op: BinOpKind::Add,
|
||||
lhs: i_param,
|
||||
@ -132,7 +140,10 @@ pub fn lower_min_loop_to_joinir(module: &crate::mir::MirModule) -> Option<JoinMo
|
||||
|
||||
join_module.add_function(loop_step_func);
|
||||
|
||||
eprintln!("[joinir/lower] Generated {} JoinIR functions", join_module.functions.len());
|
||||
eprintln!(
|
||||
"[joinir/lower] Generated {} JoinIR functions",
|
||||
join_module.functions.len()
|
||||
);
|
||||
|
||||
Some(join_module)
|
||||
}
|
||||
|
||||
@ -14,16 +14,24 @@
|
||||
//! - `funcscanner_append_defs.rs`: FuncScannerBox._append_defs/2 の配列結合 lowering(Phase 27.14)
|
||||
|
||||
pub mod common;
|
||||
pub mod value_id_ranges;
|
||||
pub mod exit_args_resolver;
|
||||
pub mod funcscanner_append_defs;
|
||||
pub mod funcscanner_trim;
|
||||
pub mod generic_case_a;
|
||||
pub mod loop_form_intake;
|
||||
pub mod min_loop;
|
||||
pub mod skip_ws;
|
||||
pub mod stage1_using_resolver;
|
||||
pub mod stageb_body;
|
||||
pub mod stageb_funcscanner;
|
||||
pub mod value_id_ranges;
|
||||
|
||||
// Re-export public lowering functions
|
||||
pub use funcscanner_append_defs::lower_funcscanner_append_defs_to_joinir;
|
||||
pub use funcscanner_trim::lower_funcscanner_trim_to_joinir;
|
||||
pub use generic_case_a::lower_case_a_loop_to_joinir_for_minimal_skip_ws;
|
||||
pub use min_loop::lower_min_loop_to_joinir;
|
||||
pub use skip_ws::lower_skip_ws_to_joinir;
|
||||
pub use stage1_using_resolver::lower_stage1_usingresolver_to_joinir;
|
||||
pub use stageb_body::lower_stageb_body_to_joinir;
|
||||
pub use stageb_funcscanner::lower_stageb_funcscanner_to_joinir;
|
||||
|
||||
@ -39,11 +39,12 @@
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::join_ir::{
|
||||
BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction,
|
||||
JoinInst, JoinModule, LoopExitShape, LoopHeaderShape, MirLikeInst,
|
||||
BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction, JoinInst, JoinModule,
|
||||
LoopExitShape, LoopHeaderShape, MirLikeInst,
|
||||
};
|
||||
use crate::mir::MirQuery;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Phase 27.11.1: Common JoinIR builder for Main.skip/1
|
||||
///
|
||||
@ -55,7 +56,10 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
let target_func = module.functions.get("Main.skip/1")?;
|
||||
|
||||
eprintln!("[joinir/skip_ws/build] Found Main.skip/1");
|
||||
eprintln!("[joinir/skip_ws/build] MIR blocks: {}", target_func.blocks.len());
|
||||
eprintln!(
|
||||
"[joinir/skip_ws/build] MIR blocks: {}",
|
||||
target_func.blocks.len()
|
||||
);
|
||||
|
||||
// Step 2: JoinModule を構築
|
||||
let mut join_module = JoinModule::new();
|
||||
@ -128,7 +132,9 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
let cmp2_is_false = ValueId(4012);
|
||||
|
||||
// cmp1_result = (i >= n)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp1_result,
|
||||
op: CompareOp::Ge,
|
||||
lhs: i_loop,
|
||||
@ -147,13 +153,17 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
});
|
||||
|
||||
// const 1
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_1,
|
||||
value: ConstValue::Integer(1),
|
||||
}));
|
||||
|
||||
// i_plus_1 = i + 1 (再利用: substring end / continue path)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: i_plus_1,
|
||||
op: BinOpKind::Add,
|
||||
lhs: i_loop,
|
||||
@ -161,7 +171,9 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
}));
|
||||
|
||||
// ch = s.substring(i, i + 1)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(ch),
|
||||
box_name: "StringBox".to_string(),
|
||||
method: "substring".to_string(),
|
||||
@ -169,13 +181,17 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
}));
|
||||
|
||||
// const " " (space)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_space,
|
||||
value: ConstValue::String(" ".to_string()),
|
||||
}));
|
||||
|
||||
// cmp2_result = (ch == " ")
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp2_result,
|
||||
op: CompareOp::Eq,
|
||||
lhs: ch,
|
||||
@ -183,13 +199,17 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
}));
|
||||
|
||||
// bool false (for negation)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: bool_false,
|
||||
value: ConstValue::Bool(false),
|
||||
}));
|
||||
|
||||
// cmp2_is_false = (cmp2_result == false)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp2_is_false,
|
||||
op: CompareOp::Eq,
|
||||
lhs: cmp2_result,
|
||||
@ -214,7 +234,10 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
|
||||
join_module.add_function(loop_step_func);
|
||||
|
||||
eprintln!("[joinir/skip_ws] Generated {} JoinIR functions", join_module.functions.len());
|
||||
eprintln!(
|
||||
"[joinir/skip_ws] Generated {} JoinIR functions",
|
||||
join_module.functions.len()
|
||||
);
|
||||
|
||||
Some(join_module)
|
||||
}
|
||||
@ -230,14 +253,17 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
/// ## 実装状況:
|
||||
/// - Phase 27.8: 基本実装(MirQuery を使用した MIR 解析)
|
||||
fn lower_skip_ws_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
use crate::mir::query::MirQueryBox;
|
||||
use super::common::{ensure_entry_has_succs, has_const_int, has_string_method, log_fallback};
|
||||
use crate::mir::query::MirQueryBox;
|
||||
|
||||
// Step 1: "Main.skip/1" を探す
|
||||
let target_func = module.functions.get("Main.skip/1")?;
|
||||
|
||||
eprintln!("[joinir/skip_ws/mir] Found Main.skip/1 (MIR-based lowering)");
|
||||
eprintln!("[joinir/skip_ws/mir] MIR blocks: {}", target_func.blocks.len());
|
||||
eprintln!(
|
||||
"[joinir/skip_ws/mir] MIR blocks: {}",
|
||||
target_func.blocks.len()
|
||||
);
|
||||
|
||||
// NOTE:
|
||||
// このフェーズでは minimal_ssa_skip_ws.hako 固定のパターンを前提に、
|
||||
@ -246,7 +272,10 @@ fn lower_skip_ws_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule>
|
||||
|
||||
// 簡易チェック: ブロック数が最低限あるか確認
|
||||
if target_func.blocks.len() < 3 {
|
||||
log_fallback("skip_ws", &format!("insufficient blocks ({})", target_func.blocks.len()));
|
||||
log_fallback(
|
||||
"skip_ws",
|
||||
&format!("insufficient blocks ({})", target_func.blocks.len()),
|
||||
);
|
||||
return lower_skip_ws_handwritten(module);
|
||||
}
|
||||
|
||||
@ -301,6 +330,19 @@ fn lower_skip_ws_handwritten(module: &crate::mir::MirModule) -> Option<JoinModul
|
||||
/// NYASH_JOINIR_LOWER_FROM_MIR=1 ./target/release/hakorune program.hako
|
||||
/// ```
|
||||
pub fn lower_skip_ws_to_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
// Phase 28: Generic Case A トグル(minimal_ssa_skip_ws 限定)
|
||||
if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") {
|
||||
if let Some(jm) = try_lower_skip_ws_generic_case_a(module) {
|
||||
return Some(jm);
|
||||
}
|
||||
eprintln!("[joinir/skip_ws] generic_case_a fallback → existing dispatcher");
|
||||
}
|
||||
|
||||
lower_skip_ws_handwritten_or_mir(module)
|
||||
}
|
||||
|
||||
/// 既存の hand-written / MIR-based dispatcher をラップしただけの関数
|
||||
fn lower_skip_ws_handwritten_or_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
super::common::dispatch_lowering(
|
||||
"skip_ws",
|
||||
module,
|
||||
@ -308,3 +350,44 @@ pub fn lower_skip_ws_to_joinir(module: &crate::mir::MirModule) -> Option<JoinMod
|
||||
lower_skip_ws_handwritten,
|
||||
)
|
||||
}
|
||||
|
||||
/// トグル ON 時にだけ試す generic Case A ロワー(minimal_ssa_skip_ws 限定)
|
||||
fn try_lower_skip_ws_generic_case_a(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
use crate::mir::join_ir::lowering::generic_case_a::lower_case_a_loop_to_joinir_for_minimal_skip_ws;
|
||||
use crate::mir::loop_form::LoopForm;
|
||||
use crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox;
|
||||
use crate::mir::phi_core::loop_var_classifier::LoopVarClassBox;
|
||||
use crate::mir::query::MirQueryBox;
|
||||
|
||||
let target_func = module.functions.get("Main.skip/1")?;
|
||||
let query = MirQueryBox::new(target_func);
|
||||
|
||||
// 最小限の LoopForm 形状推定(Case A/constant-true ループ想定)
|
||||
let preheader = target_func.entry_block;
|
||||
let header = query.succs(preheader).get(0).copied().unwrap_or(preheader);
|
||||
let succs_header = query.succs(header);
|
||||
let body = succs_header.get(0).copied().unwrap_or(header);
|
||||
let exit = succs_header.get(1).copied().unwrap_or(header);
|
||||
let latch = body;
|
||||
|
||||
let loop_form = LoopForm {
|
||||
preheader,
|
||||
header,
|
||||
body,
|
||||
latch,
|
||||
exit,
|
||||
continue_targets: vec![body],
|
||||
break_targets: vec![exit],
|
||||
};
|
||||
|
||||
let var_classes = LoopVarClassBox::new();
|
||||
let exit_live = LoopExitLivenessBox::new();
|
||||
|
||||
lower_case_a_loop_to_joinir_for_minimal_skip_ws(
|
||||
&loop_form,
|
||||
&var_classes,
|
||||
&exit_live,
|
||||
&query,
|
||||
target_func,
|
||||
)
|
||||
}
|
||||
|
||||
@ -42,10 +42,14 @@
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use crate::mir::join_ir::lowering::common::{dispatch_lowering, ensure_entry_has_succs, has_const_int, log_fallback};
|
||||
use crate::mir::join_ir::lowering::common::{
|
||||
dispatch_lowering, ensure_entry_has_succs, has_const_int, log_fallback,
|
||||
};
|
||||
use crate::mir::join_ir::lowering::value_id_ranges::stage1_using_resolver as vid;
|
||||
use crate::mir::join_ir::{JoinModule};
|
||||
use crate::mir::query::MirQueryBox;
|
||||
use crate::mir::join_ir::JoinModule;
|
||||
use crate::mir::loop_form::LoopForm;
|
||||
use crate::mir::query::{MirQuery, MirQueryBox};
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Phase 27.12: Stage1UsingResolverBox.resolve_for_source の JoinIR lowering(public dispatcher)
|
||||
///
|
||||
@ -83,11 +87,15 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option<
|
||||
use crate::mir::join_ir::*;
|
||||
|
||||
// Phase 27.13: ターゲット関数が存在するかチェック
|
||||
let _target_func = module.functions.get("Stage1UsingResolverBox.resolve_for_source/5")?;
|
||||
let _target_func = module
|
||||
.functions
|
||||
.get("Stage1UsingResolverBox.resolve_for_source/5")?;
|
||||
|
||||
eprintln!("[joinir/stage1_using_resolver/build] Phase 27.13 implementation");
|
||||
eprintln!("[joinir/stage1_using_resolver/build] Generating JoinIR for entries loop");
|
||||
eprintln!("[joinir/stage1_using_resolver/build] Using ValueId range: 7000-8999 (via value_id_ranges)");
|
||||
eprintln!(
|
||||
"[joinir/stage1_using_resolver/build] Using ValueId range: 7000-8999 (via value_id_ranges)"
|
||||
);
|
||||
|
||||
// Step 1: JoinModule を構築
|
||||
let mut join_module = JoinModule::new();
|
||||
@ -107,13 +115,21 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option<
|
||||
let mut resolve_func = JoinFunction::new(
|
||||
resolve_id,
|
||||
"resolve_entries".to_string(),
|
||||
vec![entries_param, n_param, modules_param, seen_param, prefix_init_param],
|
||||
vec![
|
||||
entries_param,
|
||||
n_param,
|
||||
modules_param,
|
||||
seen_param,
|
||||
prefix_init_param,
|
||||
],
|
||||
);
|
||||
|
||||
let i_init = vid::entry(10); // 7010
|
||||
|
||||
// i_init = 0
|
||||
resolve_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
resolve_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: i_init,
|
||||
value: ConstValue::Integer(0),
|
||||
}));
|
||||
@ -122,7 +138,14 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option<
|
||||
let loop_step_id = JoinFuncId::new(1);
|
||||
resolve_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![entries_param, n_param, modules_param, seen_param, prefix_init_param, i_init],
|
||||
args: vec![
|
||||
entries_param,
|
||||
n_param,
|
||||
modules_param,
|
||||
seen_param,
|
||||
prefix_init_param,
|
||||
i_init,
|
||||
],
|
||||
k_next: None,
|
||||
dst: None,
|
||||
});
|
||||
@ -159,7 +182,14 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option<
|
||||
let mut loop_step_func = JoinFunction::new(
|
||||
loop_step_id,
|
||||
"loop_step".to_string(),
|
||||
vec![entries_loop, n_loop, modules_loop, seen_loop, prefix_loop, i_loop],
|
||||
vec![
|
||||
entries_loop,
|
||||
n_loop,
|
||||
modules_loop,
|
||||
seen_loop,
|
||||
prefix_loop,
|
||||
i_loop,
|
||||
],
|
||||
);
|
||||
|
||||
let cmp_result = vid::loop_step(10); // 8010
|
||||
@ -169,7 +199,9 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option<
|
||||
let new_prefix = vid::loop_step(14); // 8014
|
||||
|
||||
// cmp_result = (i >= n)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp_result,
|
||||
op: CompareOp::Ge,
|
||||
lhs: i_loop,
|
||||
@ -188,7 +220,9 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option<
|
||||
});
|
||||
|
||||
// entry = entries.get(i)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(entry_value),
|
||||
box_name: "ArrayBox".to_string(),
|
||||
method: "get".to_string(),
|
||||
@ -196,13 +230,17 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option<
|
||||
}));
|
||||
|
||||
// const_1 = 1
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_1,
|
||||
value: ConstValue::Integer(1),
|
||||
}));
|
||||
|
||||
// next_i = i + 1
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: next_i,
|
||||
op: BinOpKind::Add,
|
||||
lhs: i_loop,
|
||||
@ -211,7 +249,9 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option<
|
||||
|
||||
// 簡略化: 文字列連結のみ(実際の should_emit, path 解決等は省略)
|
||||
// new_prefix = prefix + entry (実際は "\n" + code + "\n" だが、ここでは簡略化)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: new_prefix,
|
||||
op: BinOpKind::Or, // String concatenation uses Or in JoinIR
|
||||
lhs: prefix_loop,
|
||||
@ -221,7 +261,14 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option<
|
||||
// loop_step(entries, n, modules, seen, new_prefix, next_i) - tail recursion
|
||||
loop_step_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![entries_loop, n_loop, modules_loop, seen_loop, new_prefix, next_i],
|
||||
args: vec![
|
||||
entries_loop,
|
||||
n_loop,
|
||||
modules_loop,
|
||||
seen_loop,
|
||||
new_prefix,
|
||||
next_i,
|
||||
],
|
||||
k_next: None,
|
||||
dst: None,
|
||||
});
|
||||
@ -229,7 +276,10 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option<
|
||||
join_module.add_function(loop_step_func);
|
||||
|
||||
eprintln!("[joinir/stage1_using_resolver/build] ✅ JoinIR construction completed");
|
||||
eprintln!("[joinir/stage1_using_resolver/build] Functions: {}", join_module.functions.len());
|
||||
eprintln!(
|
||||
"[joinir/stage1_using_resolver/build] Functions: {}",
|
||||
join_module.functions.len()
|
||||
);
|
||||
|
||||
Some(join_module)
|
||||
}
|
||||
@ -253,10 +303,17 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
eprintln!("[joinir/stage1_using_resolver/mir] Starting MIR-based lowering");
|
||||
|
||||
// Step 1: Stage1UsingResolverBox.resolve_for_source/5 を探す
|
||||
let target_func = module.functions.get("Stage1UsingResolverBox.resolve_for_source/5")?;
|
||||
let target_func = module
|
||||
.functions
|
||||
.get("Stage1UsingResolverBox.resolve_for_source/5")?;
|
||||
|
||||
eprintln!("[joinir/stage1_using_resolver/mir] Found Stage1UsingResolverBox.resolve_for_source/5");
|
||||
eprintln!("[joinir/stage1_using_resolver/mir] MIR blocks: {}", target_func.blocks.len());
|
||||
eprintln!(
|
||||
"[joinir/stage1_using_resolver/mir] Found Stage1UsingResolverBox.resolve_for_source/5"
|
||||
);
|
||||
eprintln!(
|
||||
"[joinir/stage1_using_resolver/mir] MIR blocks: {}",
|
||||
target_func.blocks.len()
|
||||
);
|
||||
|
||||
// Step 2: MirQueryBox を作成
|
||||
let query = MirQueryBox::new(target_func);
|
||||
@ -287,6 +344,47 @@ fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
|
||||
eprintln!("[joinir/stage1_using_resolver/mir] CFG sanity checks passed ✅");
|
||||
|
||||
if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") {
|
||||
let header = query.succs(entry).get(0).copied().unwrap_or(entry);
|
||||
let succs_header = query.succs(header);
|
||||
let body = succs_header.get(0).copied().unwrap_or(header);
|
||||
let exit = succs_header.get(1).copied().unwrap_or(header);
|
||||
let loop_form = LoopForm {
|
||||
preheader: entry,
|
||||
header,
|
||||
body,
|
||||
latch: body,
|
||||
exit,
|
||||
continue_targets: vec![body],
|
||||
break_targets: vec![exit],
|
||||
};
|
||||
if crate::mir::join_ir::lowering::common::case_a::is_simple_case_a_loop(&loop_form) {
|
||||
eprintln!(
|
||||
"[joinir/stage1_using_resolver/generic-hook] simple Case A loop detected (minimal target, generic_case_a hook placeholder)"
|
||||
);
|
||||
let var_classes = crate::mir::phi_core::loop_var_classifier::LoopVarClassBox::new();
|
||||
let exit_live = crate::mir::phi_core::loop_exit_liveness::LoopExitLivenessBox::new();
|
||||
let params_len = target_func.params.len();
|
||||
if params_len == 5 {
|
||||
if let Some(jm) = crate::mir::join_ir::lowering::generic_case_a::lower_case_a_loop_to_joinir_for_stage1_usingresolver_minimal(
|
||||
&loop_form,
|
||||
&var_classes,
|
||||
&exit_live,
|
||||
&query,
|
||||
target_func,
|
||||
) {
|
||||
eprintln!(
|
||||
"[joinir/stage1_using_resolver/generic-hook] generic_case_a produced JoinIR, returning early"
|
||||
);
|
||||
return Some(jm);
|
||||
}
|
||||
}
|
||||
eprintln!(
|
||||
"[joinir/stage1_using_resolver/generic-hook] generic_case_a returned None or params mismatch, falling back to handwritten/MIR path"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 27.12: Generate JoinIR using shared builder
|
||||
// CFG checks passed, so we can use build_stage1_using_resolver_joinir() directly
|
||||
eprintln!("[joinir/stage1_using_resolver/mir] Calling build_stage1_using_resolver_joinir() after CFG validation");
|
||||
|
||||
202
src/mir/join_ir/lowering/stageb_body.rs
Normal file
202
src/mir/join_ir/lowering/stageb_body.rs
Normal file
@ -0,0 +1,202 @@
|
||||
//! Phase 28: StageBBodyExtractorBox.build_body_src ループの JoinIR lowering
|
||||
//!
|
||||
//! 対象: `lang/src/compiler/entry/compiler_stageb.hako` の
|
||||
//! `StageBBodyExtractorBox.build_body_src/2` の一次元スキャンループ(Case A)。
|
||||
//! - Pinned: src, args, n
|
||||
//! - Carrier: acc (body_len 的なカウンタ), i
|
||||
//! - Exit: acc
|
||||
|
||||
use crate::mir::join_ir::lowering::common::{
|
||||
dispatch_lowering, ensure_entry_has_succs, log_fallback,
|
||||
};
|
||||
use crate::mir::join_ir::lowering::value_id_ranges::stageb_body_extract as vid;
|
||||
use crate::mir::join_ir::JoinModule;
|
||||
use crate::mir::loop_form::LoopForm;
|
||||
use crate::mir::query::{MirQuery, MirQueryBox};
|
||||
|
||||
/// Public dispatcher (MIR-based vs handwritten)
|
||||
pub fn lower_stageb_body_to_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
dispatch_lowering("stageb_body", module, lower_from_mir, lower_handwritten)
|
||||
}
|
||||
|
||||
/// 共通の JoinIR 構築(MIR/handwritten 共通)
|
||||
fn build_stageb_body_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
use crate::mir::join_ir::*;
|
||||
|
||||
// ターゲット関数が無ければ None でフォールバック
|
||||
let _target = module
|
||||
.functions
|
||||
.get("StageBBodyExtractorBox.build_body_src/2")?;
|
||||
|
||||
eprintln!("[joinir/stageb_body/build] Generating JoinIR for build_body_src");
|
||||
eprintln!("[joinir/stageb_body/build] Using ValueId range: 11000-12999");
|
||||
|
||||
let mut join_module = JoinModule::new();
|
||||
|
||||
// Entry: build_body_src(src, args)
|
||||
let entry_id = JoinFuncId::new(0);
|
||||
let src_param = vid::entry(0); // 11000
|
||||
let args_param = vid::entry(1); // 11001
|
||||
let n_val = vid::entry(2); // 11002
|
||||
let i_init = vid::entry(3); // 11003
|
||||
let acc_init = vid::entry(4); // 11004
|
||||
let loop_step_id = JoinFuncId::new(1);
|
||||
|
||||
let mut entry_func = JoinFunction::new(
|
||||
entry_id,
|
||||
"build_body_src".to_string(),
|
||||
vec![src_param, args_param],
|
||||
);
|
||||
|
||||
// n = src.length()
|
||||
entry_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(n_val),
|
||||
box_name: "StringBox".to_string(),
|
||||
method: "length".to_string(),
|
||||
args: vec![src_param],
|
||||
}));
|
||||
|
||||
// i_init = 0
|
||||
entry_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: i_init,
|
||||
value: ConstValue::Integer(0),
|
||||
}));
|
||||
|
||||
// acc_init = 0
|
||||
entry_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: acc_init,
|
||||
value: ConstValue::Integer(0),
|
||||
}));
|
||||
|
||||
// loop_step(src, args, n, acc_init, i_init)
|
||||
entry_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![src_param, args_param, n_val, acc_init, i_init],
|
||||
k_next: None,
|
||||
dst: None,
|
||||
});
|
||||
|
||||
join_module.entry = Some(entry_id);
|
||||
join_module.add_function(entry_func);
|
||||
|
||||
// Loop step: loop_step(src, args, n, acc, i)
|
||||
let src_loop = vid::loop_step(0); // 12000 (Pinned)
|
||||
let args_loop = vid::loop_step(1); // 12001 (Pinned)
|
||||
let n_loop = vid::loop_step(2); // 12002 (Pinned)
|
||||
let acc_loop = vid::loop_step(3); // 12003 (Carrier)
|
||||
let i_loop = vid::loop_step(4); // 12004 (Carrier)
|
||||
|
||||
let _header_shape = LoopHeaderShape::new_manual(
|
||||
vec![src_loop, args_loop, n_loop], // Pinned
|
||||
vec![acc_loop, i_loop], // Carrier
|
||||
);
|
||||
|
||||
let mut loop_func = JoinFunction::new(
|
||||
loop_step_id,
|
||||
"loop_step".to_string(),
|
||||
vec![src_loop, args_loop, n_loop, acc_loop, i_loop],
|
||||
);
|
||||
|
||||
let cmp_exit = vid::loop_step(10); // 12010
|
||||
let const_one = vid::loop_step(11); // 12011
|
||||
let next_i = vid::loop_step(12); // 12012
|
||||
let next_acc = vid::loop_step(13); // 12013
|
||||
|
||||
// cmp_exit = (i >= n)
|
||||
loop_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp_exit,
|
||||
op: CompareOp::Ge,
|
||||
lhs: i_loop,
|
||||
rhs: n_loop,
|
||||
}));
|
||||
|
||||
// Exit shape: acc をそのまま返す
|
||||
let _exit_shape = LoopExitShape::new_manual(vec![acc_loop]);
|
||||
|
||||
// if i >= n { return acc }
|
||||
loop_func.body.push(JoinInst::Jump {
|
||||
cont: JoinContId::new(0),
|
||||
args: vec![acc_loop],
|
||||
cond: Some(cmp_exit),
|
||||
});
|
||||
|
||||
// const_one = 1
|
||||
loop_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_one,
|
||||
value: ConstValue::Integer(1),
|
||||
}));
|
||||
|
||||
// next_i = i + 1
|
||||
loop_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: next_i,
|
||||
op: BinOpKind::Add,
|
||||
lhs: i_loop,
|
||||
rhs: const_one,
|
||||
}));
|
||||
|
||||
// next_acc = acc + 1 (body 長カウントの簡略表現)
|
||||
loop_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: next_acc,
|
||||
op: BinOpKind::Add,
|
||||
lhs: acc_loop,
|
||||
rhs: const_one,
|
||||
}));
|
||||
|
||||
// loop_step(..., next_acc, next_i)
|
||||
loop_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![src_loop, args_loop, n_loop, next_acc, next_i],
|
||||
k_next: None,
|
||||
dst: None,
|
||||
});
|
||||
|
||||
join_module.add_function(loop_func);
|
||||
|
||||
eprintln!("[joinir/stageb_body/build] ✅ JoinIR construction completed");
|
||||
Some(join_module)
|
||||
}
|
||||
|
||||
/// MIR ベースの軽量パターンチェック(最低限)
|
||||
fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
eprintln!("[joinir/stageb_body/mir] Starting MIR-based lowering");
|
||||
|
||||
let target_func = module
|
||||
.functions
|
||||
.get("StageBBodyExtractorBox.build_body_src/2")?;
|
||||
|
||||
let query = MirQueryBox::new(target_func);
|
||||
let entry = target_func.entry_block;
|
||||
if !ensure_entry_has_succs(&query, entry) {
|
||||
log_fallback("stageb_body", "entry has no successors");
|
||||
return lower_handwritten(module);
|
||||
}
|
||||
|
||||
if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") {
|
||||
let header = query.succs(entry).get(0).copied().unwrap_or(entry);
|
||||
let succs_header = query.succs(header);
|
||||
let body = succs_header.get(0).copied().unwrap_or(header);
|
||||
let exit = succs_header.get(1).copied().unwrap_or(header);
|
||||
let loop_form = LoopForm {
|
||||
preheader: entry,
|
||||
header,
|
||||
body,
|
||||
latch: body,
|
||||
exit,
|
||||
continue_targets: vec![body],
|
||||
break_targets: vec![exit],
|
||||
};
|
||||
if crate::mir::join_ir::lowering::common::case_a::is_simple_case_a_loop(&loop_form) {
|
||||
eprintln!("[joinir/stageb_body/generic-hook] simple Case A loop detected (generic_case_a hook placeholder)");
|
||||
}
|
||||
}
|
||||
|
||||
build_stageb_body_joinir(module)
|
||||
}
|
||||
|
||||
/// 手書き版(MIR 形状に依存しない)
|
||||
fn lower_handwritten(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
eprintln!("[joinir/stageb_body/hand] Using handwritten lowering");
|
||||
build_stageb_body_joinir(module)
|
||||
}
|
||||
192
src/mir/join_ir/lowering/stageb_funcscanner.rs
Normal file
192
src/mir/join_ir/lowering/stageb_funcscanner.rs
Normal file
@ -0,0 +1,192 @@
|
||||
//! Phase 28: StageBFuncScannerBox.scan_all_boxes ループの JoinIR lowering
|
||||
//!
|
||||
//! 対象: `lang/src/compiler/entry/compiler_stageb.hako` の
|
||||
//! `StageBFuncScannerBox.scan_all_boxes/1`(Case A の一次元スキャン)。
|
||||
//! - Pinned: src, n
|
||||
//! - Carrier: defs(累積カウント的なプレースホルダ), i
|
||||
//! - Exit: defs
|
||||
|
||||
use crate::mir::join_ir::lowering::common::{
|
||||
dispatch_lowering, ensure_entry_has_succs, log_fallback,
|
||||
};
|
||||
use crate::mir::join_ir::lowering::value_id_ranges::stageb_funcscanner as vid;
|
||||
use crate::mir::join_ir::JoinModule;
|
||||
use crate::mir::loop_form::LoopForm;
|
||||
use crate::mir::query::{MirQuery, MirQueryBox};
|
||||
|
||||
/// Public dispatcher (MIR-based vs handwritten)
|
||||
pub fn lower_stageb_funcscanner_to_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
dispatch_lowering(
|
||||
"stageb_funcscanner",
|
||||
module,
|
||||
lower_from_mir,
|
||||
lower_handwritten,
|
||||
)
|
||||
}
|
||||
|
||||
/// 共通の JoinIR 構築(MIR/handwritten 共通)
|
||||
fn build_stageb_funcscanner_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
use crate::mir::join_ir::*;
|
||||
|
||||
// ターゲット関数が無ければ None でフォールバック
|
||||
let _target = module
|
||||
.functions
|
||||
.get("StageBFuncScannerBox.scan_all_boxes/1")?;
|
||||
|
||||
eprintln!("[joinir/stageb_funcscanner/build] Generating JoinIR for scan_all_boxes");
|
||||
eprintln!("[joinir/stageb_funcscanner/build] Using ValueId range: 13000-14999");
|
||||
|
||||
let mut join_module = JoinModule::new();
|
||||
|
||||
// Entry: scan_all_boxes(src)
|
||||
let entry_id = JoinFuncId::new(0);
|
||||
let src_param = vid::entry(0); // 13000
|
||||
let n_val = vid::entry(1); // 13001
|
||||
let defs_init = vid::entry(2); // 13002
|
||||
let i_init = vid::entry(3); // 13003
|
||||
let loop_step_id = JoinFuncId::new(1);
|
||||
|
||||
let mut entry_func = JoinFunction::new(entry_id, "scan_all_boxes".to_string(), vec![src_param]);
|
||||
|
||||
// n = src.length()
|
||||
entry_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(n_val),
|
||||
box_name: "StringBox".to_string(),
|
||||
method: "length".to_string(),
|
||||
args: vec![src_param],
|
||||
}));
|
||||
|
||||
// defs_init = 0 (プレースホルダ: defs 配列長)
|
||||
entry_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: defs_init,
|
||||
value: ConstValue::Integer(0),
|
||||
}));
|
||||
|
||||
// i_init = 0
|
||||
entry_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: i_init,
|
||||
value: ConstValue::Integer(0),
|
||||
}));
|
||||
|
||||
// loop_step(src, n, defs_init, i_init)
|
||||
entry_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![src_param, n_val, defs_init, i_init],
|
||||
k_next: None,
|
||||
dst: None,
|
||||
});
|
||||
|
||||
join_module.entry = Some(entry_id);
|
||||
join_module.add_function(entry_func);
|
||||
|
||||
// Loop step: loop_step(src, n, defs, i)
|
||||
let src_loop = vid::loop_step(0); // 14000 (Pinned)
|
||||
let n_loop = vid::loop_step(1); // 14001 (Pinned)
|
||||
let defs_loop = vid::loop_step(2); // 14002 (Carrier)
|
||||
let i_loop = vid::loop_step(3); // 14003 (Carrier)
|
||||
|
||||
let _header_shape = LoopHeaderShape::new_manual(
|
||||
vec![src_loop, n_loop], // Pinned
|
||||
vec![defs_loop, i_loop], // Carrier
|
||||
);
|
||||
|
||||
let mut loop_func = JoinFunction::new(
|
||||
loop_step_id,
|
||||
"loop_step".to_string(),
|
||||
vec![src_loop, n_loop, defs_loop, i_loop],
|
||||
);
|
||||
|
||||
let cmp_exit = vid::loop_step(10); // 14010
|
||||
let const_one = vid::loop_step(11); // 14011
|
||||
let next_i = vid::loop_step(12); // 14012
|
||||
|
||||
// cmp_exit = (i >= n)
|
||||
loop_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp_exit,
|
||||
op: CompareOp::Ge,
|
||||
lhs: i_loop,
|
||||
rhs: n_loop,
|
||||
}));
|
||||
|
||||
// Exit shape: defs を返す
|
||||
let _exit_shape = LoopExitShape::new_manual(vec![defs_loop]);
|
||||
|
||||
// if i >= n { return defs }
|
||||
loop_func.body.push(JoinInst::Jump {
|
||||
cont: JoinContId::new(0),
|
||||
args: vec![defs_loop],
|
||||
cond: Some(cmp_exit),
|
||||
});
|
||||
|
||||
// const_one = 1
|
||||
loop_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: const_one,
|
||||
value: ConstValue::Integer(1),
|
||||
}));
|
||||
|
||||
// next_i = i + 1
|
||||
loop_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: next_i,
|
||||
op: BinOpKind::Add,
|
||||
lhs: i_loop,
|
||||
rhs: const_one,
|
||||
}));
|
||||
|
||||
// loop_step(..., defs_loop, next_i)
|
||||
loop_func.body.push(JoinInst::Call {
|
||||
func: loop_step_id,
|
||||
args: vec![src_loop, n_loop, defs_loop, next_i],
|
||||
k_next: None,
|
||||
dst: None,
|
||||
});
|
||||
|
||||
join_module.add_function(loop_func);
|
||||
|
||||
eprintln!("[joinir/stageb_funcscanner/build] ✅ JoinIR construction completed");
|
||||
Some(join_module)
|
||||
}
|
||||
|
||||
/// MIR ベースの軽量パターンチェック(最低限)
|
||||
fn lower_from_mir(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
eprintln!("[joinir/stageb_funcscanner/mir] Starting MIR-based lowering");
|
||||
|
||||
let target_func = module
|
||||
.functions
|
||||
.get("StageBFuncScannerBox.scan_all_boxes/1")?;
|
||||
|
||||
let query = MirQueryBox::new(target_func);
|
||||
let entry = target_func.entry_block;
|
||||
if !ensure_entry_has_succs(&query, entry) {
|
||||
log_fallback("stageb_funcscanner", "entry has no successors");
|
||||
return lower_handwritten(module);
|
||||
}
|
||||
|
||||
if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_LOWER_GENERIC") {
|
||||
let header = query.succs(entry).get(0).copied().unwrap_or(entry);
|
||||
let succs_header = query.succs(header);
|
||||
let body = succs_header.get(0).copied().unwrap_or(header);
|
||||
let exit = succs_header.get(1).copied().unwrap_or(header);
|
||||
let loop_form = LoopForm {
|
||||
preheader: entry,
|
||||
header,
|
||||
body,
|
||||
latch: body,
|
||||
exit,
|
||||
continue_targets: vec![body],
|
||||
break_targets: vec![exit],
|
||||
};
|
||||
if crate::mir::join_ir::lowering::common::case_a::is_simple_case_a_loop(&loop_form) {
|
||||
eprintln!("[joinir/stageb_funcscanner/generic-hook] simple Case A loop detected (generic_case_a hook placeholder)");
|
||||
}
|
||||
}
|
||||
|
||||
build_stageb_funcscanner_joinir(module)
|
||||
}
|
||||
|
||||
/// 手書き版(MIR 形状に依存しない)
|
||||
fn lower_handwritten(module: &crate::mir::MirModule) -> Option<JoinModule> {
|
||||
eprintln!("[joinir/stageb_funcscanner/hand] Using handwritten lowering");
|
||||
build_stageb_funcscanner_joinir(module)
|
||||
}
|
||||
@ -12,6 +12,8 @@
|
||||
//! | funcscanner_trim | 5000-6999 | 5000+ | 6000+ | Trim whitespace |
|
||||
//! | stage1_using_resolver | 7000-8999 | 7000+ | 8000+ | Stage-1 using resolver |
|
||||
//! | funcscanner_append_defs | 9000-10999 | 9000+ | 10000+ | FuncScanner append defs |
|
||||
//! | stageb_body_extract | 11000-12999| 11000+ | 12000+ | Stage-B body extractor |
|
||||
//! | stageb_funcscanner | 13000-14999| 13000+ | 14000+ | Stage-B FuncScanner (scan_all_boxes) |
|
||||
//!
|
||||
//! ## Usage Example
|
||||
//!
|
||||
@ -50,6 +52,12 @@ pub mod base {
|
||||
|
||||
/// funcscanner_append_defs: FuncScanner append defs loop (9000-10999)
|
||||
pub const FUNCSCANNER_APPEND_DEFS: u32 = 9000;
|
||||
|
||||
/// stageb_body_extract: Stage-B body extractor loop (11000-12999)
|
||||
pub const STAGEB_BODY_EXTRACT: u32 = 11000;
|
||||
|
||||
/// stageb_funcscanner: Stage-B FuncScanner scan_all_boxes loop (13000-14999)
|
||||
pub const STAGEB_FUNCSCANNER: u32 = 13000;
|
||||
}
|
||||
|
||||
/// Helper function to create ValueId from base + offset
|
||||
@ -150,6 +158,42 @@ pub mod funcscanner_append_defs {
|
||||
}
|
||||
}
|
||||
|
||||
/// ValueId helpers for Stage-B body extractor lowering module
|
||||
pub mod stageb_body_extract {
|
||||
use super::{base, id};
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Entry function ValueIds (11000-11999)
|
||||
#[inline]
|
||||
pub const fn entry(offset: u32) -> ValueId {
|
||||
id(base::STAGEB_BODY_EXTRACT, offset)
|
||||
}
|
||||
|
||||
/// Loop function ValueIds (12000-12999)
|
||||
#[inline]
|
||||
pub const fn loop_step(offset: u32) -> ValueId {
|
||||
id(base::STAGEB_BODY_EXTRACT, 1000 + offset)
|
||||
}
|
||||
}
|
||||
|
||||
/// ValueId helpers for Stage-B FuncScanner lowering module
|
||||
pub mod stageb_funcscanner {
|
||||
use super::{base, id};
|
||||
use crate::mir::ValueId;
|
||||
|
||||
/// Entry function ValueIds (13000-13999)
|
||||
#[inline]
|
||||
pub const fn entry(offset: u32) -> ValueId {
|
||||
id(base::STAGEB_FUNCSCANNER, offset)
|
||||
}
|
||||
|
||||
/// Loop function ValueIds (14000-14999)
|
||||
#[inline]
|
||||
pub const fn loop_step(offset: u32) -> ValueId {
|
||||
id(base::STAGEB_FUNCSCANNER, 1000 + offset)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -160,10 +204,20 @@ mod tests {
|
||||
/// based on the module's allocated range.
|
||||
macro_rules! test_value_id_range {
|
||||
($module:ident, $entry_base:expr, $loop_base:expr) => {
|
||||
assert_eq!($module::entry(0).0, $entry_base,
|
||||
"{} entry(0) should be {}", stringify!($module), $entry_base);
|
||||
assert_eq!($module::loop_step(999).0, $loop_base + 999,
|
||||
"{} loop_step(999) should be {}", stringify!($module), $loop_base + 999);
|
||||
assert_eq!(
|
||||
$module::entry(0).0,
|
||||
$entry_base,
|
||||
"{} entry(0) should be {}",
|
||||
stringify!($module),
|
||||
$entry_base
|
||||
);
|
||||
assert_eq!(
|
||||
$module::loop_step(999).0,
|
||||
$loop_base + 999,
|
||||
"{} loop_step(999) should be {}",
|
||||
stringify!($module),
|
||||
$loop_base + 999
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@ -175,6 +229,8 @@ mod tests {
|
||||
test_value_id_range!(funcscanner_trim, 5000, 6000);
|
||||
test_value_id_range!(stage1_using_resolver, 7000, 8000);
|
||||
test_value_id_range!(funcscanner_append_defs, 9000, 10000);
|
||||
test_value_id_range!(stageb_body_extract, 11000, 12000);
|
||||
test_value_id_range!(stageb_funcscanner, 13000, 14000);
|
||||
|
||||
// Automated overlap detection
|
||||
// Each range is 2000 units: (base, base+1999)
|
||||
@ -184,15 +240,22 @@ mod tests {
|
||||
(5000, 6999), // funcscanner_trim
|
||||
(7000, 8999), // stage1_using_resolver
|
||||
(9000, 10999), // funcscanner_append_defs
|
||||
(11000, 12999), // stageb_body_extract
|
||||
(13000, 14999), // stageb_funcscanner
|
||||
];
|
||||
|
||||
// Verify no overlaps between consecutive ranges
|
||||
for i in 0..ranges.len() - 1 {
|
||||
let (_, end_i) = ranges[i];
|
||||
let (start_next, _) = ranges[i + 1];
|
||||
assert!(end_i < start_next,
|
||||
assert!(
|
||||
end_i < start_next,
|
||||
"Overlap detected: range {} ends at {} but range {} starts at {}",
|
||||
i, end_i, i + 1, start_next);
|
||||
i,
|
||||
end_i,
|
||||
i + 1,
|
||||
start_next
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,9 +174,7 @@ pub enum JoinInst {
|
||||
},
|
||||
|
||||
/// ルート関数 or 上位への戻り
|
||||
Ret {
|
||||
value: Option<VarId>,
|
||||
},
|
||||
Ret { value: Option<VarId> },
|
||||
|
||||
/// それ以外の演算は、現行 MIR の算術/比較/boxcall を再利用
|
||||
Compute(MirLikeInst),
|
||||
@ -186,10 +184,7 @@ pub enum JoinInst {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MirLikeInst {
|
||||
/// 定数代入
|
||||
Const {
|
||||
dst: VarId,
|
||||
value: ConstValue,
|
||||
},
|
||||
Const { dst: VarId, value: ConstValue },
|
||||
|
||||
/// 二項演算
|
||||
BinOp {
|
||||
@ -283,7 +278,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_join_function_creation() {
|
||||
let func_id = JoinFuncId::new(0);
|
||||
let func = JoinFunction::new(func_id, "test_func".to_string(), vec![ValueId(1), ValueId(2)]);
|
||||
let func = JoinFunction::new(
|
||||
func_id,
|
||||
"test_func".to_string(),
|
||||
vec![ValueId(1), ValueId(2)],
|
||||
);
|
||||
|
||||
assert_eq!(func.id, func_id);
|
||||
assert_eq!(func.name, "test_func");
|
||||
|
||||
@ -74,9 +74,7 @@ pub fn eval_binop(
|
||||
match op {
|
||||
BinOpKind::Add => match (lhs, rhs) {
|
||||
(JoinValue::Int(a), JoinValue::Int(b)) => Ok(JoinValue::Int(a + b)),
|
||||
(JoinValue::Str(a), JoinValue::Str(b)) => {
|
||||
Ok(JoinValue::Str(format!("{}{}", a, b)))
|
||||
}
|
||||
(JoinValue::Str(a), JoinValue::Str(b)) => Ok(JoinValue::Str(format!("{}{}", a, b))),
|
||||
_ => Err(JoinIrOpError::new(
|
||||
"Add supported only for Int+Int or Str+Str",
|
||||
)),
|
||||
@ -90,9 +88,7 @@ pub fn eval_binop(
|
||||
_ => Err(JoinIrOpError::new("Mul supported only for Int*Int")),
|
||||
},
|
||||
BinOpKind::Div => match (lhs, rhs) {
|
||||
(JoinValue::Int(_), JoinValue::Int(0)) => {
|
||||
Err(JoinIrOpError::new("Division by zero"))
|
||||
}
|
||||
(JoinValue::Int(_), JoinValue::Int(0)) => Err(JoinIrOpError::new("Division by zero")),
|
||||
(JoinValue::Int(a), JoinValue::Int(b)) => Ok(JoinValue::Int(a / b)),
|
||||
_ => Err(JoinIrOpError::new("Div supported only for Int/Int")),
|
||||
},
|
||||
@ -137,16 +133,12 @@ pub fn eval_compare(
|
||||
(JoinValue::Bool(a), JoinValue::Bool(b)) => match op {
|
||||
CompareOp::Eq => Ok(JoinValue::Bool(a == b)),
|
||||
CompareOp::Ne => Ok(JoinValue::Bool(a != b)),
|
||||
_ => Err(JoinIrOpError::new(
|
||||
"Bool comparison only supports Eq/Ne",
|
||||
)),
|
||||
_ => Err(JoinIrOpError::new("Bool comparison only supports Eq/Ne")),
|
||||
},
|
||||
(JoinValue::Str(a), JoinValue::Str(b)) => match op {
|
||||
CompareOp::Eq => Ok(JoinValue::Bool(a == b)),
|
||||
CompareOp::Ne => Ok(JoinValue::Bool(a != b)),
|
||||
_ => Err(JoinIrOpError::new(
|
||||
"String comparison only supports Eq/Ne",
|
||||
)),
|
||||
_ => Err(JoinIrOpError::new("String comparison only supports Eq/Ne")),
|
||||
},
|
||||
_ => Err(JoinIrOpError::new(
|
||||
"Type mismatch in Compare (expected homogeneous operands)",
|
||||
@ -160,11 +152,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_eval_binop_add_int() {
|
||||
let result = eval_binop(
|
||||
BinOpKind::Add,
|
||||
&JoinValue::Int(3),
|
||||
&JoinValue::Int(5),
|
||||
);
|
||||
let result = eval_binop(BinOpKind::Add, &JoinValue::Int(3), &JoinValue::Int(5));
|
||||
assert_eq!(result.unwrap(), JoinValue::Int(8));
|
||||
}
|
||||
|
||||
@ -180,21 +168,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_eval_binop_sub() {
|
||||
let result = eval_binop(
|
||||
BinOpKind::Sub,
|
||||
&JoinValue::Int(10),
|
||||
&JoinValue::Int(3),
|
||||
);
|
||||
let result = eval_binop(BinOpKind::Sub, &JoinValue::Int(10), &JoinValue::Int(3));
|
||||
assert_eq!(result.unwrap(), JoinValue::Int(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_binop_div_by_zero() {
|
||||
let result = eval_binop(
|
||||
BinOpKind::Div,
|
||||
&JoinValue::Int(10),
|
||||
&JoinValue::Int(0),
|
||||
);
|
||||
let result = eval_binop(BinOpKind::Div, &JoinValue::Int(10), &JoinValue::Int(0));
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().message, "Division by zero");
|
||||
}
|
||||
@ -221,21 +201,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_eval_compare_int_gt() {
|
||||
let result = eval_compare(
|
||||
CompareOp::Gt,
|
||||
&JoinValue::Int(10),
|
||||
&JoinValue::Int(5),
|
||||
);
|
||||
let result = eval_compare(CompareOp::Gt, &JoinValue::Int(10), &JoinValue::Int(5));
|
||||
assert_eq!(result.unwrap(), JoinValue::Bool(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_compare_int_eq() {
|
||||
let result = eval_compare(
|
||||
CompareOp::Eq,
|
||||
&JoinValue::Int(5),
|
||||
&JoinValue::Int(5),
|
||||
);
|
||||
let result = eval_compare(CompareOp::Eq, &JoinValue::Int(5), &JoinValue::Int(5));
|
||||
assert_eq!(result.unwrap(), JoinValue::Bool(true));
|
||||
}
|
||||
|
||||
@ -277,16 +249,15 @@ mod tests {
|
||||
&JoinValue::Bool(false),
|
||||
);
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err().message, "Bool comparison only supports Eq/Ne");
|
||||
assert_eq!(
|
||||
result.unwrap_err().message,
|
||||
"Bool comparison only supports Eq/Ne"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_compare_type_mismatch() {
|
||||
let result = eval_compare(
|
||||
CompareOp::Eq,
|
||||
&JoinValue::Int(5),
|
||||
&JoinValue::Bool(true),
|
||||
);
|
||||
let result = eval_compare(CompareOp::Eq, &JoinValue::Int(5), &JoinValue::Bool(true));
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().message.contains("Type mismatch"));
|
||||
}
|
||||
|
||||
@ -11,9 +11,7 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::mir::join_ir::{
|
||||
ConstValue, JoinFuncId, JoinInst, JoinModule, MirLikeInst, VarId,
|
||||
};
|
||||
use crate::mir::join_ir::{ConstValue, JoinFuncId, JoinInst, JoinModule, MirLikeInst, VarId};
|
||||
|
||||
// Phase 27.8: ops box からの再エクスポート
|
||||
pub use crate::mir::join_ir_ops::{JoinIrOpError, JoinValue};
|
||||
@ -37,10 +35,9 @@ fn execute_function(
|
||||
mut current_args: Vec<JoinValue>,
|
||||
) -> Result<JoinValue, JoinRuntimeError> {
|
||||
'exec: loop {
|
||||
let func = module
|
||||
.functions
|
||||
.get(¤t_func)
|
||||
.ok_or_else(|| JoinRuntimeError::new(format!("Function {:?} not found", current_func)))?;
|
||||
let func = module.functions.get(¤t_func).ok_or_else(|| {
|
||||
JoinRuntimeError::new(format!("Function {:?} not found", current_func))
|
||||
})?;
|
||||
|
||||
if func.params.len() != current_args.len() {
|
||||
return Err(JoinRuntimeError::new(format!(
|
||||
@ -85,7 +82,11 @@ fn execute_function(
|
||||
continue 'exec;
|
||||
}
|
||||
}
|
||||
JoinInst::Jump { cont: _, args, cond } => {
|
||||
JoinInst::Jump {
|
||||
cont: _,
|
||||
args,
|
||||
cond,
|
||||
} => {
|
||||
let should_jump = match cond {
|
||||
Some(var) => as_bool(&read_var(&locals, *var)?)?,
|
||||
None => true,
|
||||
@ -177,7 +178,8 @@ fn eval_compute(
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// Invoke VM's execute_box_call for complete semantics
|
||||
let result_vm = vm.execute_box_call(receiver_vm, method, method_args_vm)
|
||||
let result_vm = vm
|
||||
.execute_box_call(receiver_vm, method, method_args_vm)
|
||||
.map_err(|e| JoinRuntimeError::new(format!("BoxCall failed: {}", e)))?;
|
||||
|
||||
// Convert result back to JoinValue
|
||||
@ -217,4 +219,3 @@ fn as_bool(value: &JoinValue) -> Result<bool, JoinRuntimeError> {
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -28,8 +28,8 @@ use crate::mir::join_ir::{
|
||||
};
|
||||
use crate::mir::join_ir_ops::JoinValue;
|
||||
use crate::mir::{
|
||||
BasicBlockId, BinaryOp, CompareOp as MirCompareOp, ConstValue as MirConstValue,
|
||||
EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId,
|
||||
BasicBlockId, BinaryOp, CompareOp as MirCompareOp, ConstValue as MirConstValue, EffectMask,
|
||||
FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@ -80,9 +80,7 @@ pub fn run_joinir_via_vm(
|
||||
args: &[JoinValue],
|
||||
) -> Result<JoinValue, JoinIrVmBridgeError> {
|
||||
eprintln!("[joinir_vm_bridge] Phase 27-shortterm S-4.3");
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge] Converting JoinIR to MIR for VM execution"
|
||||
);
|
||||
eprintln!("[joinir_vm_bridge] Converting JoinIR to MIR for VM execution");
|
||||
|
||||
// Step 1: JoinIR → MIR 変換
|
||||
let mir_module = convert_joinir_to_mir(join_module, entry_func)?;
|
||||
@ -158,7 +156,9 @@ fn convert_join_function_to_mir(
|
||||
|
||||
// Create minimal FunctionSignature for JoinIR function
|
||||
// Phase 27-shortterm: すべて MirType::Unknown として扱う(型情報は JoinIR に無いため)
|
||||
let param_types = join_func.params.iter()
|
||||
let param_types = join_func
|
||||
.params
|
||||
.iter()
|
||||
.map(|_| MirType::Unknown)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@ -186,7 +186,12 @@ fn convert_join_function_to_mir(
|
||||
let mir_inst = convert_mir_like_inst(mir_like)?;
|
||||
current_instructions.push(mir_inst);
|
||||
}
|
||||
JoinInst::Call { func, args, dst, k_next } => {
|
||||
JoinInst::Call {
|
||||
func,
|
||||
args,
|
||||
dst,
|
||||
k_next,
|
||||
} => {
|
||||
// Phase 27-shortterm S-4.4-A: skip_ws pattern only (tail calls)
|
||||
// Validate: dst=None, k_next=None (tail call)
|
||||
if dst.is_some() || k_next.is_some() {
|
||||
@ -204,7 +209,11 @@ fn convert_join_function_to_mir(
|
||||
format!("join_func_{}", func.0)
|
||||
};
|
||||
|
||||
eprintln!("[joinir_vm_bridge] Converting Call to function '{}' with {} args", func_name, args.len());
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge] Converting Call to function '{}' with {} args",
|
||||
func_name,
|
||||
args.len()
|
||||
);
|
||||
|
||||
// Create a temporary ValueId to hold the function name (string constant)
|
||||
// This is a workaround for MIR's string-based Call resolution
|
||||
@ -231,7 +240,10 @@ fn convert_join_function_to_mir(
|
||||
// Phase 27-shortterm S-4.4-A: Jump with condition → Branch + Return
|
||||
// Jump represents an exit continuation (k_exit) in skip_ws pattern
|
||||
|
||||
eprintln!("[joinir_vm_bridge] Converting Jump to cont={:?}, args={:?}, cond={:?}", cont, args, cond);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge] Converting Jump to cont={:?}, args={:?}, cond={:?}",
|
||||
cont, args, cond
|
||||
);
|
||||
|
||||
match cond {
|
||||
Some(cond_var) => {
|
||||
@ -256,9 +268,9 @@ fn convert_join_function_to_mir(
|
||||
// Create exit block with Return
|
||||
let exit_value = args.first().copied();
|
||||
let mut exit_block = crate::mir::BasicBlock::new(exit_block_id);
|
||||
exit_block.instructions.push(MirInstruction::Return {
|
||||
value: exit_value,
|
||||
});
|
||||
exit_block
|
||||
.instructions
|
||||
.push(MirInstruction::Return { value: exit_value });
|
||||
mir_func.blocks.insert(exit_block_id, exit_block);
|
||||
|
||||
// Create continue block (will be populated by subsequent instructions)
|
||||
@ -272,9 +284,7 @@ fn convert_join_function_to_mir(
|
||||
None => {
|
||||
// Unconditional jump: direct Return
|
||||
let exit_value = args.first().copied();
|
||||
current_instructions.push(MirInstruction::Return {
|
||||
value: exit_value,
|
||||
});
|
||||
current_instructions.push(MirInstruction::Return { value: exit_value });
|
||||
|
||||
// Finalize current block
|
||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
||||
@ -287,9 +297,7 @@ fn convert_join_function_to_mir(
|
||||
}
|
||||
}
|
||||
JoinInst::Ret { value } => {
|
||||
current_instructions.push(MirInstruction::Return {
|
||||
value: *value,
|
||||
});
|
||||
current_instructions.push(MirInstruction::Return { value: *value });
|
||||
|
||||
// Finalize current block
|
||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
||||
@ -312,9 +320,7 @@ fn convert_join_function_to_mir(
|
||||
}
|
||||
|
||||
/// MirLikeInst → MirInstruction 変換
|
||||
fn convert_mir_like_inst(
|
||||
mir_like: &MirLikeInst,
|
||||
) -> Result<MirInstruction, JoinIrVmBridgeError> {
|
||||
fn convert_mir_like_inst(mir_like: &MirLikeInst) -> Result<MirInstruction, JoinIrVmBridgeError> {
|
||||
match mir_like {
|
||||
MirLikeInst::Const { dst, value } => {
|
||||
let mir_const = match value {
|
||||
|
||||
@ -139,6 +139,8 @@ impl LoopBuilderApi for super::builder::MirBuilder {
|
||||
// Update effect mask and insert at the very start
|
||||
bb.effects = bb.effects | inst.effects();
|
||||
bb.instructions.insert(0, inst);
|
||||
bb.instruction_spans
|
||||
.insert(0, self.current_span);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Block {} not found", block.as_u32()))
|
||||
|
||||
@ -811,6 +811,7 @@ impl<'a> LoopBuilder<'a> {
|
||||
// Phi命令は必ずブロックの先頭に配置。ただし同一dstの既存PHIがある場合は差し替える。
|
||||
let mut replaced = false;
|
||||
let mut idx = 0;
|
||||
let span = self.parent_builder.current_span;
|
||||
while idx < block.instructions.len() {
|
||||
match &mut block.instructions[idx] {
|
||||
MirInstruction::Phi {
|
||||
@ -818,6 +819,13 @@ impl<'a> LoopBuilder<'a> {
|
||||
inputs: ins,
|
||||
} if *d == dst => {
|
||||
*ins = inputs.clone();
|
||||
if block.instruction_spans.len() <= idx {
|
||||
// Backfill missing spans to preserve alignment after legacy inserts.
|
||||
while block.instruction_spans.len() <= idx {
|
||||
block.instruction_spans.push(crate::ast::Span::unknown());
|
||||
}
|
||||
}
|
||||
block.instruction_spans[idx] = span;
|
||||
replaced = true;
|
||||
break;
|
||||
}
|
||||
@ -833,6 +841,7 @@ impl<'a> LoopBuilder<'a> {
|
||||
inputs: inputs.clone(),
|
||||
};
|
||||
block.instructions.insert(0, phi_inst);
|
||||
block.instruction_spans.insert(0, span);
|
||||
}
|
||||
if dbg {
|
||||
eprintln!("[DEBUG] ✅ PHI instruction inserted at position 0");
|
||||
@ -895,7 +904,8 @@ impl<'a> LoopBuilder<'a> {
|
||||
// =============================================================
|
||||
// Variable Map Utilities — snapshots and rebinding
|
||||
// =============================================================
|
||||
fn get_current_variable_map(&self) -> BTreeMap<String, ValueId> { // Phase 25.1: BTreeMap化
|
||||
fn get_current_variable_map(&self) -> BTreeMap<String, ValueId> {
|
||||
// Phase 25.1: BTreeMap化
|
||||
self.parent_builder.variable_map.clone()
|
||||
}
|
||||
|
||||
@ -921,6 +931,8 @@ impl<'a> LoopBuilder<'a> {
|
||||
}
|
||||
|
||||
fn build_statement(&mut self, stmt: ASTNode) -> Result<ValueId, String> {
|
||||
// Preserve the originating span for loop-local control instructions (break/continue/phi).
|
||||
self.parent_builder.current_span = stmt.span();
|
||||
match stmt {
|
||||
// Ensure nested bare blocks inside loops are lowered with loop-aware semantics
|
||||
ASTNode::Program { statements, .. } => {
|
||||
@ -1210,15 +1222,15 @@ impl<'a> LoopBuilder<'a> {
|
||||
// Phase 26-F-3: ループ内if-mergeコンテキスト設定(ChatGPT設計)
|
||||
// pre_if_var_mapにある全変数をcarrier候補として扱う(保守的だが安全)
|
||||
// 理由: ループ内変数は全てキャリア候補の可能性があるため
|
||||
let carrier_names: std::collections::BTreeSet<String> =
|
||||
pre_if_var_map.keys()
|
||||
let carrier_names: std::collections::BTreeSet<String> = pre_if_var_map
|
||||
.keys()
|
||||
.filter(|name| !name.starts_with("__pin$")) // 一時変数除外
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
phi_builder.set_if_context(
|
||||
true, // in_loop_body = true
|
||||
carrier_names
|
||||
carrier_names,
|
||||
);
|
||||
|
||||
// Phase 26-F-2: IfBodyLocalMergeBox は phi_builder_box.rs 内で直接使用
|
||||
|
||||
6
src/mir/loop_form.rs
Normal file
6
src/mir/loop_form.rs
Normal file
@ -0,0 +1,6 @@
|
||||
//! LoopForm helper alias
|
||||
//!
|
||||
//! Alias to ControlForm::LoopShape so generic JoinIR lowering can depend on a
|
||||
//! stable loop shape representation without pulling in builder details.
|
||||
|
||||
pub type LoopForm = crate::mir::control_form::LoopShape;
|
||||
@ -12,12 +12,12 @@ pub mod builder;
|
||||
pub mod definitions; // Unified MIR definitions (MirCall, Callee, etc.)
|
||||
pub mod effect;
|
||||
pub mod function;
|
||||
pub mod naming; // Static box / entry naming rules(NamingBox)
|
||||
pub mod instruction;
|
||||
pub mod instruction_introspection; // Introspection helpers for tests (instruction names)
|
||||
pub mod instruction_kinds; // small kind-specific metadata (Const/BinOp)
|
||||
pub mod loop_api; // Minimal LoopBuilder facade (adapter-ready)
|
||||
pub mod loop_builder; // SSA loop construction with phi nodes
|
||||
pub mod naming; // Static box / entry naming rules(NamingBox)
|
||||
pub mod optimizer;
|
||||
pub mod ssot; // Shared helpers (SSOT) for instruction lowering
|
||||
pub mod types; // core MIR enums (ConstValue, Ops, MirType)
|
||||
@ -26,21 +26,23 @@ pub mod utils; // Phase 15 control flow utilities for root treatment
|
||||
pub mod control_form;
|
||||
pub mod function_emission; // FunctionEmissionBox(MirFunction直編集の発行ヘルパ)
|
||||
pub mod hints; // scaffold: zero-cost guidance (no-op)
|
||||
pub mod join_ir; // Phase 26-H: 関数正規化IR(JoinIR)
|
||||
pub mod join_ir_ops; // Phase 27.8: JoinIR 命令意味箱(ops box)
|
||||
pub mod join_ir_runner; // Phase 27.2: JoinIR 実行器(実験用)
|
||||
pub mod join_ir_vm_bridge; // Phase 27-shortterm S-4: JoinIR → Rust VM ブリッジ
|
||||
pub mod loop_form; // ControlForm::LoopShape の薄いエイリアス
|
||||
pub mod optimizer_passes; // optimizer passes (normalize/diagnostics)
|
||||
pub mod optimizer_stats; // extracted stats struct
|
||||
mod spanned_instruction;
|
||||
pub mod passes;
|
||||
pub mod phi_core; // Phase 1 scaffold: unified PHI entry (re-exports only)
|
||||
pub mod printer;
|
||||
mod printer_helpers; // internal helpers extracted from printer.rs
|
||||
pub mod query; // Phase 26-G: MIR read/write/CFGビュー (MirQuery)
|
||||
pub mod region; // Phase 25.1l: Region/GC観測レイヤ(LoopForm v2 × RefKind)
|
||||
pub mod slot_registry; // Phase 9.79b.1: method slot resolution (IDs)
|
||||
pub mod value_id;
|
||||
pub mod value_kind; // Phase 26-A: ValueId型安全化
|
||||
pub mod query; // Phase 26-G: MIR read/write/CFGビュー (MirQuery)
|
||||
pub mod join_ir; // Phase 26-H: 関数正規化IR(JoinIR)
|
||||
pub mod join_ir_runner; // Phase 27.2: JoinIR 実行器(実験用)
|
||||
pub mod join_ir_ops; // Phase 27.8: JoinIR 命令意味箱(ops box)
|
||||
pub mod join_ir_vm_bridge; // Phase 27-shortterm S-4: JoinIR → Rust VM ブリッジ
|
||||
pub mod verification;
|
||||
pub mod verification_types; // extracted error types // Optimization subpasses (e.g., type_hints) // Phase 25.1f: Loop/If 共通ビュー(ControlForm)
|
||||
|
||||
@ -51,9 +53,11 @@ pub use definitions::{CallFlags, Callee, MirCall}; // Unified call definitions
|
||||
pub use effect::{Effect, EffectMask};
|
||||
pub use function::{FunctionSignature, MirFunction, MirModule};
|
||||
pub use instruction::MirInstruction;
|
||||
pub use join_ir_runner::{run_joinir_function, JoinRuntimeError, JoinValue};
|
||||
pub use optimizer::MirOptimizer;
|
||||
pub use query::{MirQuery, MirQueryBox};
|
||||
pub use printer::MirPrinter;
|
||||
pub use spanned_instruction::SpannedInstruction;
|
||||
pub use query::{MirQuery, MirQueryBox};
|
||||
pub use slot_registry::{BoxTypeId, MethodSlot};
|
||||
pub use types::{
|
||||
BarrierOp, BinaryOp, CompareOp, ConstValue, MirType, TypeOpKind, UnaryOp, WeakRefOp,
|
||||
@ -62,7 +66,6 @@ pub use value_id::{LocalId, ValueId, ValueIdGenerator};
|
||||
pub use value_kind::{MirValueKind, TypedValueId}; // Phase 26-A: ValueId型安全化
|
||||
pub use verification::MirVerifier;
|
||||
pub use verification_types::VerificationError;
|
||||
pub use join_ir_runner::{run_joinir_function, JoinRuntimeError, JoinValue};
|
||||
// Phase 15 control flow utilities (段階的根治戦略)
|
||||
pub use utils::{
|
||||
capture_actual_predecessor_and_jump, collect_phi_incoming_if_reachable,
|
||||
@ -102,7 +105,16 @@ impl MirCompiler {
|
||||
}
|
||||
|
||||
/// Compile AST to MIR module with verification
|
||||
pub fn compile(&mut self, ast: crate::ast::ASTNode) -> Result<MirCompileResult, String> {
|
||||
pub fn compile_with_source(
|
||||
&mut self,
|
||||
ast: crate::ast::ASTNode,
|
||||
source_file: Option<&str>,
|
||||
) -> Result<MirCompileResult, String> {
|
||||
if let Some(src) = source_file {
|
||||
self.builder.set_source_file_hint(src.to_string());
|
||||
} else {
|
||||
self.builder.clear_source_file_hint();
|
||||
}
|
||||
// Convert AST to MIR using builder
|
||||
let mut module = self.builder.build_module(ast)?;
|
||||
|
||||
@ -128,6 +140,11 @@ impl MirCompiler {
|
||||
})
|
||||
}
|
||||
|
||||
/// Compile AST to MIR module with verification (no source hint).
|
||||
pub fn compile(&mut self, ast: crate::ast::ASTNode) -> Result<MirCompileResult, String> {
|
||||
self.compile_with_source(ast, None)
|
||||
}
|
||||
|
||||
/// Dump MIR to string for debugging
|
||||
pub fn dump_mir(&self, module: &MirModule) -> String {
|
||||
MirPrinter::new().print_module(module)
|
||||
|
||||
@ -11,7 +11,12 @@
|
||||
|
||||
/// Encode a static box method into a MIR function name: `BoxName.method/arity`.
|
||||
pub fn encode_static_method(box_name: &str, method: &str, arity: usize) -> String {
|
||||
format!("{}.{}{}", canonical_box_name(box_name), method, format!("/{}", arity))
|
||||
format!(
|
||||
"{}.{}{}",
|
||||
canonical_box_name(box_name),
|
||||
method,
|
||||
format!("/{}", arity)
|
||||
)
|
||||
}
|
||||
|
||||
/// Canonicalize a static box name for MIR-level usage.
|
||||
@ -245,4 +250,3 @@ pub fn parse_global_name(name: &str) -> Option<StaticMethodId> {
|
||||
pub fn format_global_name(id: &StaticMethodId) -> String {
|
||||
id.format()
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use crate::ast::Span;
|
||||
use crate::mir::optimizer::MirOptimizer;
|
||||
use crate::mir::optimizer_stats::OptimizationStats;
|
||||
use crate::mir::{BarrierOp, MirModule, TypeOpKind, ValueId, WeakRefOp};
|
||||
use crate::mir::{BarrierOp, MirModule, SpannedInstruction, TypeOpKind, ValueId, WeakRefOp};
|
||||
|
||||
fn idemp_enabled() -> bool {
|
||||
std::env::var("NYASH_MIR_DEV_IDEMP").ok().as_deref() == Some("1")
|
||||
@ -433,9 +434,10 @@ pub fn normalize_ref_field_access(
|
||||
None => continue,
|
||||
};
|
||||
for (_bb, block) in &mut function.blocks {
|
||||
let mut out: Vec<I> = Vec::with_capacity(block.instructions.len() + 2);
|
||||
let old = std::mem::take(&mut block.instructions);
|
||||
for inst in old.into_iter() {
|
||||
let old_spanned: Vec<SpannedInstruction> = block.drain_spanned_instructions();
|
||||
let mut out: Vec<I> = Vec::with_capacity(old_spanned.len() + 2);
|
||||
let mut out_spans: Vec<Span> = Vec::with_capacity(old_spanned.len() + 2);
|
||||
for SpannedInstruction { inst, span } in old_spanned.into_iter() {
|
||||
match inst {
|
||||
I::RefGet {
|
||||
dst,
|
||||
@ -448,6 +450,7 @@ pub fn normalize_ref_field_access(
|
||||
dst: new_id,
|
||||
value: crate::mir::ConstValue::String(field),
|
||||
});
|
||||
out_spans.push(span);
|
||||
out.push(I::BoxCall {
|
||||
dst: Some(dst),
|
||||
box_val: reference,
|
||||
@ -456,6 +459,7 @@ pub fn normalize_ref_field_access(
|
||||
args: vec![new_id],
|
||||
effects: crate::mir::EffectMask::READ,
|
||||
});
|
||||
out_spans.push(span);
|
||||
stats.intrinsic_optimizations += 1;
|
||||
}
|
||||
I::RefSet {
|
||||
@ -469,10 +473,12 @@ pub fn normalize_ref_field_access(
|
||||
dst: new_id,
|
||||
value: crate::mir::ConstValue::String(field),
|
||||
});
|
||||
out_spans.push(span);
|
||||
out.push(I::Barrier {
|
||||
op: BarrierOp::Write,
|
||||
ptr: reference,
|
||||
});
|
||||
out_spans.push(span);
|
||||
out.push(I::BoxCall {
|
||||
dst: None,
|
||||
box_val: reference,
|
||||
@ -481,14 +487,20 @@ pub fn normalize_ref_field_access(
|
||||
args: vec![new_id, value],
|
||||
effects: crate::mir::EffectMask::WRITE,
|
||||
});
|
||||
out_spans.push(span);
|
||||
stats.intrinsic_optimizations += 1;
|
||||
}
|
||||
other => out.push(other),
|
||||
other => {
|
||||
out.push(other);
|
||||
out_spans.push(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
block.instructions = out;
|
||||
block.instruction_spans = out_spans;
|
||||
|
||||
if let Some(term) = block.terminator.take() {
|
||||
let term_span = block.terminator_span.take().unwrap_or_else(Span::unknown);
|
||||
block.terminator = Some(match term {
|
||||
I::RefGet {
|
||||
dst,
|
||||
@ -501,6 +513,7 @@ pub fn normalize_ref_field_access(
|
||||
dst: new_id,
|
||||
value: crate::mir::ConstValue::String(field),
|
||||
});
|
||||
block.instruction_spans.push(term_span);
|
||||
I::BoxCall {
|
||||
dst: Some(dst),
|
||||
box_val: reference,
|
||||
@ -521,10 +534,12 @@ pub fn normalize_ref_field_access(
|
||||
dst: new_id,
|
||||
value: crate::mir::ConstValue::String(field),
|
||||
});
|
||||
block.instruction_spans.push(term_span);
|
||||
block.instructions.push(I::Barrier {
|
||||
op: BarrierOp::Write,
|
||||
ptr: reference,
|
||||
});
|
||||
block.instruction_spans.push(term_span);
|
||||
I::BoxCall {
|
||||
dst: None,
|
||||
box_val: reference,
|
||||
@ -536,6 +551,9 @@ pub fn normalize_ref_field_access(
|
||||
}
|
||||
other => other,
|
||||
});
|
||||
if block.terminator_span.is_none() {
|
||||
block.terminator_span = Some(term_span);
|
||||
}
|
||||
}
|
||||
}
|
||||
if idemp_enabled() {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use crate::ast::Span;
|
||||
use crate::mir::optimizer::MirOptimizer;
|
||||
use crate::mir::optimizer_stats::OptimizationStats;
|
||||
use crate::mir::{BinaryOp, CompareOp, EffectMask, MirInstruction as I, MirModule, ValueId};
|
||||
@ -16,8 +17,11 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) ->
|
||||
for (_fname, function) in &mut module.functions {
|
||||
for (_bb, block) in &mut function.blocks {
|
||||
let mut out: Vec<I> = Vec::with_capacity(block.instructions.len() + 8);
|
||||
let mut out_spans: Vec<Span> = Vec::with_capacity(block.instructions.len() + 8);
|
||||
let old = std::mem::take(&mut block.instructions);
|
||||
let mut old_spans_iter = std::mem::take(&mut block.instruction_spans).into_iter();
|
||||
for inst in old.into_iter() {
|
||||
let span = old_spans_iter.next().unwrap_or_else(Span::unknown);
|
||||
match inst {
|
||||
I::Load { dst, ptr } => {
|
||||
out.push(I::ExternCall {
|
||||
@ -27,6 +31,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) ->
|
||||
args: vec![ptr],
|
||||
effects: EffectMask::READ,
|
||||
});
|
||||
out_spans.push(span);
|
||||
stats.intrinsic_optimizations += 1;
|
||||
}
|
||||
I::Store { value, ptr } => {
|
||||
@ -37,6 +42,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) ->
|
||||
args: vec![ptr, value],
|
||||
effects: EffectMask::WRITE,
|
||||
});
|
||||
out_spans.push(span);
|
||||
stats.intrinsic_optimizations += 1;
|
||||
}
|
||||
I::NewBox {
|
||||
@ -51,6 +57,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) ->
|
||||
dst: ty_id,
|
||||
value: crate::mir::ConstValue::String(box_type),
|
||||
});
|
||||
out_spans.push(span);
|
||||
let mut call_args = Vec::with_capacity(1 + args.len());
|
||||
call_args.push(ty_id);
|
||||
call_args.append(&mut args);
|
||||
@ -61,6 +68,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) ->
|
||||
args: call_args,
|
||||
effects: EffectMask::PURE, // constructor is logically alloc; conservatively PURE here
|
||||
});
|
||||
out_spans.push(span);
|
||||
stats.intrinsic_optimizations += 1;
|
||||
}
|
||||
I::UnaryOp { dst, op, operand } => {
|
||||
@ -72,12 +80,14 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) ->
|
||||
dst: zero,
|
||||
value: crate::mir::ConstValue::Integer(0),
|
||||
});
|
||||
out_spans.push(span);
|
||||
out.push(I::BinOp {
|
||||
dst,
|
||||
op: BinaryOp::Sub,
|
||||
lhs: zero,
|
||||
rhs: operand,
|
||||
});
|
||||
out_spans.push(span);
|
||||
}
|
||||
crate::mir::UnaryOp::Not => {
|
||||
let f = ValueId::new(function.next_value_id);
|
||||
@ -86,12 +96,14 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) ->
|
||||
dst: f,
|
||||
value: crate::mir::ConstValue::Bool(false),
|
||||
});
|
||||
out_spans.push(span);
|
||||
out.push(I::Compare {
|
||||
dst,
|
||||
op: CompareOp::Eq,
|
||||
lhs: operand,
|
||||
rhs: f,
|
||||
});
|
||||
out_spans.push(span);
|
||||
}
|
||||
crate::mir::UnaryOp::BitNot => {
|
||||
let all1 = ValueId::new(function.next_value_id);
|
||||
@ -100,22 +112,29 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) ->
|
||||
dst: all1,
|
||||
value: crate::mir::ConstValue::Integer(-1),
|
||||
});
|
||||
out_spans.push(span);
|
||||
out.push(I::BinOp {
|
||||
dst,
|
||||
op: BinaryOp::BitXor,
|
||||
lhs: operand,
|
||||
rhs: all1,
|
||||
});
|
||||
out_spans.push(span);
|
||||
}
|
||||
}
|
||||
stats.intrinsic_optimizations += 1;
|
||||
}
|
||||
other => out.push(other),
|
||||
other => {
|
||||
out.push(other);
|
||||
out_spans.push(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
block.instructions = out;
|
||||
block.instruction_spans = out_spans;
|
||||
|
||||
if let Some(term) = block.terminator.take() {
|
||||
let term_span = block.terminator_span.take().unwrap_or_else(Span::unknown);
|
||||
block.terminator = Some(match term {
|
||||
I::Load { dst, ptr } => I::ExternCall {
|
||||
dst: Some(dst),
|
||||
@ -142,6 +161,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) ->
|
||||
dst: ty_id,
|
||||
value: ConstValue::String(box_type),
|
||||
});
|
||||
block.instruction_spans.push(term_span);
|
||||
let mut call_args = Vec::with_capacity(1 + args.len());
|
||||
call_args.push(ty_id);
|
||||
call_args.append(&mut args);
|
||||
@ -161,6 +181,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) ->
|
||||
dst: zero,
|
||||
value: ConstValue::Integer(0),
|
||||
});
|
||||
block.instruction_spans.push(term_span);
|
||||
I::BinOp {
|
||||
dst,
|
||||
op: BinaryOp::Sub,
|
||||
@ -175,6 +196,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) ->
|
||||
dst: f,
|
||||
value: ConstValue::Bool(false),
|
||||
});
|
||||
block.instruction_spans.push(term_span);
|
||||
I::Compare {
|
||||
dst,
|
||||
op: CompareOp::Eq,
|
||||
@ -189,6 +211,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) ->
|
||||
dst: all1,
|
||||
value: ConstValue::Integer(-1),
|
||||
});
|
||||
block.instruction_spans.push(term_span);
|
||||
I::BinOp {
|
||||
dst,
|
||||
op: BinaryOp::BitXor,
|
||||
@ -199,6 +222,7 @@ pub fn normalize_pure_core13(_opt: &mut MirOptimizer, module: &mut MirModule) ->
|
||||
},
|
||||
other => other,
|
||||
});
|
||||
block.terminator_span = Some(term_span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,11 +180,11 @@ impl BodyLocalPhiBuilder {
|
||||
}
|
||||
|
||||
if enable_live_rescue {
|
||||
if matches!(class, super::loop_var_classifier::LoopVarClass::BodyLocalInternal)
|
||||
&& live_at_exit.contains(*var_name)
|
||||
&& self
|
||||
.inspector
|
||||
.is_available_in_all(var_name, exit_preds)
|
||||
if matches!(
|
||||
class,
|
||||
super::loop_var_classifier::LoopVarClass::BodyLocalInternal
|
||||
) && live_at_exit.contains(*var_name)
|
||||
&& self.inspector.is_available_in_all(var_name, exit_preds)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -406,8 +406,13 @@ mod tests {
|
||||
// Phase 26-F-4: empty live_at_exit(live情報なし)
|
||||
let live_at_exit = std::collections::BTreeSet::new();
|
||||
|
||||
let phi_vars =
|
||||
builder.filter_exit_phi_candidates(&all_vars, &pinned, &carrier, &exit_preds, &live_at_exit);
|
||||
let phi_vars = builder.filter_exit_phi_candidates(
|
||||
&all_vars,
|
||||
&pinned,
|
||||
&carrier,
|
||||
&exit_preds,
|
||||
&live_at_exit,
|
||||
);
|
||||
|
||||
// Expected: s, idx (ch filtered out!)
|
||||
assert_eq!(phi_vars.len(), 2);
|
||||
@ -474,8 +479,13 @@ mod tests {
|
||||
// Phase 26-F-4: empty live_at_exit(live情報なし)
|
||||
let live_at_exit = std::collections::BTreeSet::new();
|
||||
|
||||
let phi_vars =
|
||||
builder.filter_exit_phi_candidates(&all_vars, &pinned, &[], &[BasicBlockId(5)], &live_at_exit);
|
||||
let phi_vars = builder.filter_exit_phi_candidates(
|
||||
&all_vars,
|
||||
&pinned,
|
||||
&[],
|
||||
&[BasicBlockId(5)],
|
||||
&live_at_exit,
|
||||
);
|
||||
|
||||
// Only s should remain
|
||||
assert_eq!(phi_vars.len(), 1);
|
||||
|
||||
@ -18,8 +18,8 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
use super::body_local_phi_builder::BodyLocalPhiBuilder;
|
||||
use super::loop_exit_liveness::{ExitLivenessProvider, LoopExitLivenessBox}; // Phase 26-F-4
|
||||
use super::loop_snapshot_merge::LoopSnapshotMergeBox;
|
||||
use super::phi_invariants::PhiInvariantsBox;
|
||||
use super::phi_input_collector::PhiInputCollector;
|
||||
use super::phi_invariants::PhiInvariantsBox;
|
||||
|
||||
/// Exit PHI生成専門Box
|
||||
///
|
||||
@ -76,8 +76,7 @@ impl ExitPhiBuilder {
|
||||
/// ```
|
||||
pub fn new(body_local_builder: BodyLocalPhiBuilder) -> Self {
|
||||
// 環境変数で簡易 MirScan 版を opt-in できるようにする
|
||||
let use_scan =
|
||||
std::env::var("NYASH_EXIT_LIVE_ENABLE").ok().as_deref() == Some("1");
|
||||
let use_scan = std::env::var("NYASH_EXIT_LIVE_ENABLE").ok().as_deref() == Some("1");
|
||||
if use_scan {
|
||||
Self::with_liveness(
|
||||
body_local_builder,
|
||||
@ -185,9 +184,12 @@ impl ExitPhiBuilder {
|
||||
|
||||
// Phase 26-F/G: ExitLivenessProvider で live_at_exit を計算(MirQuery 経由)
|
||||
let query = crate::mir::MirQueryBox::new(ops.mir_function());
|
||||
let live_at_exit = self
|
||||
.liveness_provider
|
||||
.compute_live_at_exit(&query, exit_id, header_vals, exit_snapshots);
|
||||
let live_at_exit = self.liveness_provider.compute_live_at_exit(
|
||||
&query,
|
||||
exit_id,
|
||||
header_vals,
|
||||
exit_snapshots,
|
||||
);
|
||||
|
||||
let phi_vars = self.body_local_builder.filter_exit_phi_candidates(
|
||||
&required_vars.iter().cloned().collect::<Vec<_>>(),
|
||||
@ -361,10 +363,7 @@ impl<T: crate::mir::phi_core::loopform_builder::LoopFormOps> LoopFormOps for T {
|
||||
}
|
||||
|
||||
fn get_block_predecessors(&self, block_id: BasicBlockId) -> BTreeSet<BasicBlockId> {
|
||||
crate::mir::phi_core::loopform_builder::LoopFormOps::get_block_predecessors(
|
||||
self,
|
||||
block_id,
|
||||
)
|
||||
crate::mir::phi_core::loopform_builder::LoopFormOps::get_block_predecessors(self, block_id)
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
@ -181,7 +181,9 @@ impl HeaderPhiBuilder {
|
||||
pub fn new() -> Self {
|
||||
// Phase 27.4-B/27.4C Cleanup: JoinIR 実験フラグのチェック(ログ出力のみ、挙動変更なし)
|
||||
if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_HEADER_EXP") {
|
||||
eprintln!("[HeaderPhiBuilder] JoinIR experiment flag is ON (NYASH_JOINIR_HEADER_EXP=1)");
|
||||
eprintln!(
|
||||
"[HeaderPhiBuilder] JoinIR experiment flag is ON (NYASH_JOINIR_HEADER_EXP=1)"
|
||||
);
|
||||
}
|
||||
Self::default()
|
||||
}
|
||||
|
||||
@ -24,8 +24,8 @@
|
||||
//! - **橋渡し**: IfPhiContext経由で最小限の情報伝達(箱離婚)
|
||||
//! - **Fail-Fast**: 不正入力は即座にエラー
|
||||
|
||||
use crate::mir::{BasicBlockId, ValueId};
|
||||
use crate::mir::phi_core::phi_builder_box::IfPhiContext;
|
||||
use crate::mir::{BasicBlockId, ValueId};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// If Body-Local Merge Box
|
||||
@ -117,7 +117,8 @@ impl IfBodyLocalMergeBox {
|
||||
// 1. 両腕に存在する変数名を収集(決定的順序)
|
||||
let then_names: BTreeSet<&String> = then_end.keys().collect();
|
||||
let else_names: BTreeSet<&String> = else_end.keys().collect();
|
||||
let common_names: BTreeSet<&String> = then_names.intersection(&else_names).copied().collect();
|
||||
let common_names: BTreeSet<&String> =
|
||||
then_names.intersection(&else_names).copied().collect();
|
||||
|
||||
// Phase 26-F-3: ループ内モードで片腕のみのcarrier変数も追加
|
||||
let mut candidate_names_set = common_names.clone();
|
||||
|
||||
@ -201,10 +201,7 @@ impl ExitLivenessProvider for MirScanExitLiveness {
|
||||
header_vals: &BTreeMap<String, ValueId>,
|
||||
exit_snapshots: &[(BasicBlockId, BTreeMap<String, ValueId>)],
|
||||
) -> BTreeSet<String> {
|
||||
let trace = std::env::var("NYASH_EXIT_LIVENESS_TRACE")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1");
|
||||
let trace = std::env::var("NYASH_EXIT_LIVENESS_TRACE").ok().as_deref() == Some("1");
|
||||
|
||||
// 対象ブロック集合(exit と break preds)
|
||||
let mut targets: BTreeSet<BasicBlockId> = BTreeSet::new();
|
||||
@ -325,13 +322,14 @@ mod tests {
|
||||
snap2.insert("i".to_string(), ValueId(50));
|
||||
snap2.insert("pname".to_string(), ValueId(60));
|
||||
|
||||
let exit_snapshots = vec![
|
||||
(BasicBlockId(100), snap1),
|
||||
(BasicBlockId(200), snap2),
|
||||
];
|
||||
let exit_snapshots = vec![(BasicBlockId(100), snap1), (BasicBlockId(200), snap2)];
|
||||
|
||||
let live_at_exit =
|
||||
liveness_box.compute_live_at_exit(&query, BasicBlockId(0), &header_vals, &exit_snapshots);
|
||||
let live_at_exit = liveness_box.compute_live_at_exit(
|
||||
&query,
|
||||
BasicBlockId(0),
|
||||
&header_vals,
|
||||
&exit_snapshots,
|
||||
);
|
||||
|
||||
// Phase 1: 空の live_at_exit(MIRスキャン実装待ち)
|
||||
assert_eq!(live_at_exit.len(), 0);
|
||||
@ -345,8 +343,12 @@ mod tests {
|
||||
let header_vals = BTreeMap::new();
|
||||
let exit_snapshots = vec![];
|
||||
|
||||
let live_at_exit =
|
||||
liveness_box.compute_live_at_exit(&query, BasicBlockId(0), &header_vals, &exit_snapshots);
|
||||
let live_at_exit = liveness_box.compute_live_at_exit(
|
||||
&query,
|
||||
BasicBlockId(0),
|
||||
&header_vals,
|
||||
&exit_snapshots,
|
||||
);
|
||||
|
||||
assert_eq!(live_at_exit.len(), 0);
|
||||
}
|
||||
@ -365,13 +367,14 @@ mod tests {
|
||||
let mut snap2 = BTreeMap::new();
|
||||
snap2.insert("i".to_string(), ValueId(30)); // 重複
|
||||
|
||||
let exit_snapshots = vec![
|
||||
(BasicBlockId(100), snap1),
|
||||
(BasicBlockId(200), snap2),
|
||||
];
|
||||
let exit_snapshots = vec![(BasicBlockId(100), snap1), (BasicBlockId(200), snap2)];
|
||||
|
||||
let live_at_exit =
|
||||
liveness_box.compute_live_at_exit(&query, BasicBlockId(0), &header_vals, &exit_snapshots);
|
||||
let live_at_exit = liveness_box.compute_live_at_exit(
|
||||
&query,
|
||||
BasicBlockId(0),
|
||||
&header_vals,
|
||||
&exit_snapshots,
|
||||
);
|
||||
|
||||
// Phase 1: 空の live_at_exit(MIRスキャン実装待ち)
|
||||
assert_eq!(live_at_exit.len(), 0);
|
||||
|
||||
@ -104,7 +104,8 @@ impl LoopSnapshotManager {
|
||||
// Phase 25.1: BTreeMap → BTreeMap(決定性確保・内部変換)
|
||||
pub fn add_exit_snapshot(&mut self, block: BasicBlockId, vars: BTreeMap<String, ValueId>) {
|
||||
// Convert BTreeMap to BTreeMap for deterministic iteration
|
||||
self.exit_snapshots.push((block, vars.into_iter().collect()));
|
||||
self.exit_snapshots
|
||||
.push((block, vars.into_iter().collect()));
|
||||
}
|
||||
|
||||
/// Add continue snapshot
|
||||
@ -120,7 +121,8 @@ impl LoopSnapshotManager {
|
||||
// Phase 25.1: BTreeMap → BTreeMap(決定性確保・内部変換)
|
||||
pub fn add_continue_snapshot(&mut self, block: BasicBlockId, vars: BTreeMap<String, ValueId>) {
|
||||
// Convert BTreeMap to BTreeMap for deterministic iteration
|
||||
self.continue_snapshots.push((block, vars.into_iter().collect()));
|
||||
self.continue_snapshots
|
||||
.push((block, vars.into_iter().collect()));
|
||||
}
|
||||
|
||||
/// Get preheader snapshot
|
||||
|
||||
@ -442,8 +442,9 @@ impl LoopFormBuilder {
|
||||
continue;
|
||||
}
|
||||
|
||||
carrier.latch_value =
|
||||
ops.get_variable_at_block(&carrier.name, latch_id).ok_or_else(|| {
|
||||
carrier.latch_value = ops
|
||||
.get_variable_at_block(&carrier.name, latch_id)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"carrier '{}' not found at latch {:?}",
|
||||
carrier.name, latch_id
|
||||
|
||||
@ -212,12 +212,8 @@ impl PhiBuilderBox {
|
||||
}
|
||||
|
||||
// Compute modified variables (決定的順序: BTreeSet使用)
|
||||
let modified_vars = self.compute_modified_names_if(
|
||||
pre_snapshot,
|
||||
then_end,
|
||||
&else_end_opt,
|
||||
if_shape,
|
||||
);
|
||||
let modified_vars =
|
||||
self.compute_modified_names_if(pre_snapshot, then_end, &else_end_opt, if_shape);
|
||||
|
||||
for var_name in modified_vars {
|
||||
// Conservative strategy: get values with void fallback
|
||||
@ -302,7 +298,11 @@ impl PhiBuilderBox {
|
||||
let else_end_owned = else_end_opt.map(|m| m.clone());
|
||||
|
||||
// Phase 26-F-3: IfPhiContextを取得(デフォルト: ループ外)
|
||||
let if_context = self.if_context.as_ref().cloned().unwrap_or_else(|| IfPhiContext {
|
||||
let if_context = self
|
||||
.if_context
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| IfPhiContext {
|
||||
in_loop_body: false,
|
||||
loop_carrier_names: std::collections::BTreeSet::new(),
|
||||
});
|
||||
|
||||
@ -68,7 +68,12 @@ impl<'m> MirQuery for MirQueryBox<'m> {
|
||||
Load { ptr, .. } => vec![*ptr],
|
||||
Store { ptr, value } => vec![*ptr, *value],
|
||||
ArrayGet { array, index, .. } => vec![*array, *index],
|
||||
ArraySet { array, index, value, .. } => vec![*array, *index, *value],
|
||||
ArraySet {
|
||||
array,
|
||||
index,
|
||||
value,
|
||||
..
|
||||
} => vec![*array, *index, *value],
|
||||
Call { args, .. }
|
||||
| BoxCall { args, .. }
|
||||
| PluginInvoke { args, .. }
|
||||
@ -91,7 +96,9 @@ impl<'m> MirQuery for MirQueryBox<'m> {
|
||||
}
|
||||
RefNew { box_val, .. } => vec![*box_val],
|
||||
RefGet { reference, .. } => vec![*reference],
|
||||
RefSet { reference, value, .. } => vec![*reference, *value],
|
||||
RefSet {
|
||||
reference, value, ..
|
||||
} => vec![*reference, *value],
|
||||
WeakNew { box_val, .. } => vec![*box_val],
|
||||
WeakLoad { weak_ref, .. } => vec![*weak_ref],
|
||||
WeakRef { value, .. } => vec![*value],
|
||||
|
||||
9
src/mir/spanned_instruction.rs
Normal file
9
src/mir/spanned_instruction.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use crate::ast::Span;
|
||||
use super::MirInstruction;
|
||||
|
||||
/// MIR instruction bundled with its source span.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpannedInstruction {
|
||||
pub inst: MirInstruction,
|
||||
pub span: Span,
|
||||
}
|
||||
@ -56,6 +56,9 @@ pub fn insert_phi_at_head(
|
||||
) {
|
||||
inputs.sort_by_key(|(bb, _)| bb.0);
|
||||
if let Some(bb) = f.get_block_mut(bb_id) {
|
||||
bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs });
|
||||
bb.insert_spanned_after_phis(crate::mir::SpannedInstruction {
|
||||
inst: MirInstruction::Phi { dst, inputs },
|
||||
span: crate::ast::Span::unknown(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,9 +115,7 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => {
|
||||
crate::runner::modes::common_util::diag::print_parse_error_with_context(
|
||||
filename,
|
||||
&code,
|
||||
&e,
|
||||
filename, &code, &e,
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
@ -146,9 +144,7 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => {
|
||||
crate::runner::modes::common_util::diag::print_parse_error_with_context(
|
||||
filename,
|
||||
&code,
|
||||
&e,
|
||||
filename, &code, &e,
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
@ -159,7 +159,13 @@ impl NyashRunner {
|
||||
if let Ok(ast0) = NyashParser::parse_from_string(code) {
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast0, false);
|
||||
let mut mc = MirCompiler::new();
|
||||
if let Ok(cr) = mc.compile(ast) {
|
||||
if let Ok(cr) =
|
||||
crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
&mut mc,
|
||||
ast,
|
||||
None,
|
||||
)
|
||||
{
|
||||
let mut vm = VM::new();
|
||||
let _ = vm.execute_module(&cr.module);
|
||||
}
|
||||
@ -191,7 +197,13 @@ impl NyashRunner {
|
||||
if let Ok(ast0) = NyashParser::parse_from_string(code) {
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast0, false);
|
||||
let mut mc = MirCompiler::new();
|
||||
if let Ok(cr) = mc.compile(ast) {
|
||||
if let Ok(cr) =
|
||||
crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
&mut mc,
|
||||
ast,
|
||||
None,
|
||||
)
|
||||
{
|
||||
let mut vm = VM::new();
|
||||
let _ = vm.execute_module(&cr.module);
|
||||
}
|
||||
@ -214,7 +226,12 @@ impl NyashRunner {
|
||||
let ast0 = NyashParser::parse_from_string(code).map_err(|e| format!("vm parse: {}", e))?;
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast0, false);
|
||||
let mut mc = MirCompiler::new();
|
||||
let cr = mc.compile(ast).map_err(|e| format!("vm compile: {}", e))?;
|
||||
let cr = crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
&mut mc,
|
||||
ast,
|
||||
None,
|
||||
)
|
||||
.map_err(|e| format!("vm compile: {}", e))?;
|
||||
let mut vm = VM::new();
|
||||
let out = vm
|
||||
.execute_module(&cr.module)
|
||||
|
||||
@ -71,9 +71,7 @@ fn extract_line_col(err: &ParseError) -> (Option<usize>, Option<usize>) {
|
||||
ParseError::UnsupportedNamespace { line, .. } => (Some(*line), None),
|
||||
ParseError::ExpectedIdentifier { line } => (Some(*line), None),
|
||||
ParseError::TokenizeError(te) => match te {
|
||||
TokenizeError::UnexpectedCharacter { line, column, .. } => {
|
||||
(Some(*line), Some(*column))
|
||||
}
|
||||
TokenizeError::UnexpectedCharacter { line, column, .. } => (Some(*line), Some(*column)),
|
||||
TokenizeError::UnterminatedString { line }
|
||||
| TokenizeError::InvalidNumber { line }
|
||||
| TokenizeError::UnterminatedComment { line } => (Some(*line), None),
|
||||
|
||||
@ -13,6 +13,7 @@ pub mod io;
|
||||
pub mod plugin_guard;
|
||||
pub mod provider_registry;
|
||||
pub mod pyvm;
|
||||
pub mod source_hint;
|
||||
pub mod resolve;
|
||||
pub mod selfhost;
|
||||
pub mod selfhost_exe;
|
||||
|
||||
@ -353,10 +353,12 @@ pub fn collect_using_and_strip(
|
||||
}
|
||||
} else {
|
||||
// ⚠️ Phase 0.3: User-friendly "Did you mean?" suggestions
|
||||
let similar: Vec<_> = using_ctx.aliases.keys()
|
||||
let similar: Vec<_> = using_ctx
|
||||
.aliases
|
||||
.keys()
|
||||
.filter(|k| {
|
||||
k.to_lowercase().contains(&target_unquoted.to_lowercase()) ||
|
||||
target_unquoted.to_lowercase().contains(&k.to_lowercase())
|
||||
k.to_lowercase().contains(&target_unquoted.to_lowercase())
|
||||
|| target_unquoted.to_lowercase().contains(&k.to_lowercase())
|
||||
})
|
||||
.take(3)
|
||||
.collect();
|
||||
@ -374,9 +376,13 @@ pub fn collect_using_and_strip(
|
||||
}
|
||||
|
||||
if using_ctx.aliases.is_empty() {
|
||||
err_msg.push_str("\n\n⚠️ No aliases loaded (check TOML parse errors above)");
|
||||
err_msg
|
||||
.push_str("\n\n⚠️ No aliases loaded (check TOML parse errors above)");
|
||||
} else {
|
||||
err_msg.push_str(&format!("\n\nAvailable modules: {} aliases", using_ctx.aliases.len()));
|
||||
err_msg.push_str(&format!(
|
||||
"\n\nAvailable modules: {} aliases",
|
||||
using_ctx.aliases.len()
|
||||
));
|
||||
}
|
||||
|
||||
err_msg.push_str("\n\n📝 Suggestions:");
|
||||
|
||||
17
src/runner/modes/common_util/source_hint.rs
Normal file
17
src/runner/modes/common_util/source_hint.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use crate::mir::{MirCompileResult, MirCompiler};
|
||||
use crate::ast::ASTNode;
|
||||
|
||||
/// Compile AST with a source filename hint, reducing call-site duplication.
|
||||
/// Falls back to regular compile when filename is None or empty.
|
||||
pub fn compile_with_source_hint(
|
||||
compiler: &mut MirCompiler,
|
||||
ast: ASTNode,
|
||||
filename: Option<&str>,
|
||||
) -> Result<MirCompileResult, String> {
|
||||
if let Some(f) = filename {
|
||||
if !f.is_empty() {
|
||||
return compiler.compile_with_source(ast, Some(f));
|
||||
}
|
||||
}
|
||||
compiler.compile_with_source(ast, None)
|
||||
}
|
||||
@ -72,9 +72,7 @@ impl NyashRunner {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => {
|
||||
crate::runner::modes::common_util::diag::print_parse_error_with_context(
|
||||
filename,
|
||||
code_ref,
|
||||
&e,
|
||||
filename, code_ref, &e,
|
||||
);
|
||||
// Enhanced context: list merged prelude files if any (from text-merge path)
|
||||
let preludes =
|
||||
@ -107,7 +105,11 @@ impl NyashRunner {
|
||||
|
||||
// Compile to MIR
|
||||
let mut mir_compiler = MirCompiler::new();
|
||||
let compile_result = match mir_compiler.compile(ast) {
|
||||
let compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
&mut mir_compiler,
|
||||
ast,
|
||||
Some(filename),
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
eprintln!("❌ MIR compilation error: {}", e);
|
||||
@ -290,7 +292,8 @@ impl NyashRunner {
|
||||
println!("🔧 Mock LLVM Backend Execution:");
|
||||
println!(" Build with --features llvm-inkwell-legacy for Rust/inkwell backend, or set NYASH_LLVM_OBJ_OUT and NYASH_LLVM_USE_HARNESS=1 for harness.");
|
||||
// NamingBox SSOT: Select entry (arity-aware, Main.main → main fallback)
|
||||
let entry = crate::runner::modes::common_util::entry_selection::select_entry_function(&module);
|
||||
let entry =
|
||||
crate::runner::modes::common_util::entry_selection::select_entry_function(&module);
|
||||
if let Some(main_func) = module.functions.get(&entry) {
|
||||
for (_bid, block) in &main_func.blocks {
|
||||
for inst in &block.instructions {
|
||||
|
||||
@ -22,9 +22,7 @@ impl NyashRunner {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => {
|
||||
crate::runner::modes::common_util::diag::print_parse_error_with_context(
|
||||
filename,
|
||||
&code,
|
||||
&e,
|
||||
filename, &code, &e,
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
@ -34,7 +32,11 @@ impl NyashRunner {
|
||||
|
||||
// Compile to MIR (opt passes configurable)
|
||||
let mut mir_compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||||
let compile_result = match mir_compiler.compile(ast) {
|
||||
let compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
&mut mir_compiler,
|
||||
ast,
|
||||
Some(filename),
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
eprintln!("❌ MIR compilation error: {}", e);
|
||||
|
||||
@ -44,7 +44,11 @@ impl NyashRunner {
|
||||
|
||||
// Compile to MIR (opt passes configurable)
|
||||
let mut mir_compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||||
let compile_result = match mir_compiler.compile(ast) {
|
||||
let compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
&mut mir_compiler,
|
||||
ast,
|
||||
Some(filename),
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(e) => { eprintln!("❌ MIR compilation error: {}", e); process::exit(1); }
|
||||
};
|
||||
|
||||
@ -137,9 +137,7 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => {
|
||||
crate::runner::modes::common_util::diag::print_parse_error_with_context(
|
||||
filename,
|
||||
&code,
|
||||
&e,
|
||||
filename, &code, &e,
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
@ -149,7 +147,11 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) {
|
||||
|
||||
// Compile to MIR (respect default optimizer setting)
|
||||
let mut mir_compiler = MirCompiler::with_options(true);
|
||||
let mut compile_result = match mir_compiler.compile(ast) {
|
||||
let mut compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
&mut mir_compiler,
|
||||
ast,
|
||||
Some(filename),
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
eprintln!("❌ MIR compilation error: {}", e);
|
||||
|
||||
@ -194,7 +194,9 @@ impl NyashRunner {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => {
|
||||
crate::runner::modes::common_util::diag::print_parse_error_with_context(
|
||||
filename, &code_final, &e,
|
||||
filename,
|
||||
&code_final,
|
||||
&e,
|
||||
);
|
||||
// Enhanced context: list merged prelude files if any
|
||||
let preludes =
|
||||
@ -407,7 +409,11 @@ impl NyashRunner {
|
||||
|
||||
// Compile to MIR
|
||||
let mut compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||||
let compile = match compiler.compile(ast) {
|
||||
let compile = match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
&mut compiler,
|
||||
ast,
|
||||
Some(filename),
|
||||
) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("❌ MIR compilation error: {}", e);
|
||||
|
||||
@ -116,9 +116,7 @@ impl NyashRunner {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => {
|
||||
crate::runner::modes::common_util::diag::print_parse_error_with_context(
|
||||
filename,
|
||||
&code2,
|
||||
&e,
|
||||
filename, &code2, &e,
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
@ -271,7 +269,11 @@ impl NyashRunner {
|
||||
}
|
||||
}
|
||||
let mut compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||||
let compile = match compiler.compile(ast) {
|
||||
let compile = match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
&mut compiler,
|
||||
ast,
|
||||
Some(filename),
|
||||
) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("❌ MIR compilation error: {}", e);
|
||||
@ -357,7 +359,7 @@ impl NyashRunner {
|
||||
impl NyashRunner {
|
||||
/// Small helper to continue fallback execution once AST is prepared
|
||||
#[allow(dead_code)]
|
||||
fn execute_vm_fallback_from_ast(&self, _filename: &str, ast: nyash_rust::ast::ASTNode) {
|
||||
fn execute_vm_fallback_from_ast(&self, filename: &str, ast: nyash_rust::ast::ASTNode) {
|
||||
use crate::{
|
||||
backend::MirInterpreter,
|
||||
box_factory::{BoxFactory, RuntimeError},
|
||||
@ -471,7 +473,11 @@ impl NyashRunner {
|
||||
}
|
||||
// Compile to MIR and execute via interpreter
|
||||
let mut compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||||
let module = match compiler.compile(ast) {
|
||||
let module = match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
&mut compiler,
|
||||
ast,
|
||||
Some(filename),
|
||||
) {
|
||||
Ok(r) => r.module,
|
||||
Err(e) => {
|
||||
eprintln!("❌ MIR compilation error: {}", e);
|
||||
|
||||
@ -30,7 +30,11 @@ impl NyashRunner {
|
||||
|
||||
// Compile to MIR
|
||||
let mut mir_compiler = MirCompiler::new();
|
||||
let compile_result = match mir_compiler.compile(ast) {
|
||||
let compile_result = match crate::runner::modes::common_util::source_hint::compile_with_source_hint(
|
||||
&mut mir_compiler,
|
||||
ast,
|
||||
Some(filename),
|
||||
) {
|
||||
Ok(result) => result,
|
||||
Err(e) => { eprintln!("❌ MIR compilation error: {}", e); process::exit(1); }
|
||||
};
|
||||
|
||||
@ -27,13 +27,7 @@ pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
||||
// Prefer new env (NYASH_STAGE1_*) and fall back to legacy names to keep compatibility.
|
||||
let source = std::env::var("NYASH_STAGE1_INPUT")
|
||||
.ok()
|
||||
.or_else(|| {
|
||||
groups
|
||||
.input
|
||||
.file
|
||||
.as_ref()
|
||||
.cloned()
|
||||
})
|
||||
.or_else(|| groups.input.file.as_ref().cloned())
|
||||
.or_else(|| std::env::var("STAGE1_SOURCE").ok())
|
||||
.or_else(|| std::env::var("STAGE1_INPUT").ok());
|
||||
|
||||
@ -43,12 +37,11 @@ pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
||||
let emit_program = matches!(
|
||||
mode_env.as_deref(),
|
||||
Some("emit-program") | Some("emit-program-json")
|
||||
) || std::env::var("STAGE1_EMIT_PROGRAM_JSON")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1");
|
||||
let emit_mir = matches!(mode_env.as_deref(), Some("emit-mir") | Some("emit-mir-json"))
|
||||
|| std::env::var("STAGE1_EMIT_MIR_JSON").ok().as_deref() == Some("1");
|
||||
) || std::env::var("STAGE1_EMIT_PROGRAM_JSON").ok().as_deref() == Some("1");
|
||||
let emit_mir = matches!(
|
||||
mode_env.as_deref(),
|
||||
Some("emit-mir") | Some("emit-mir-json")
|
||||
) || std::env::var("STAGE1_EMIT_MIR_JSON").ok().as_deref() == Some("1");
|
||||
|
||||
let mut args: Vec<String> = Vec::new();
|
||||
let mut source_env: Option<String> = None;
|
||||
|
||||
@ -23,23 +23,11 @@ pub(super) fn configure_stage1_env(
|
||||
|
||||
// Unified Stage-1 env (NYASH_STAGE1_*) — derive from legacy if unset to keep compatibility.
|
||||
if std::env::var("NYASH_STAGE1_MODE").is_err() {
|
||||
if std::env::var("STAGE1_EMIT_PROGRAM_JSON")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
if std::env::var("STAGE1_EMIT_PROGRAM_JSON").ok().as_deref() == Some("1") {
|
||||
cmd.env("NYASH_STAGE1_MODE", "emit-program");
|
||||
} else if std::env::var("STAGE1_EMIT_MIR_JSON")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
} else if std::env::var("STAGE1_EMIT_MIR_JSON").ok().as_deref() == Some("1") {
|
||||
cmd.env("NYASH_STAGE1_MODE", "emit-mir");
|
||||
} else if std::env::var("NYASH_USE_STAGE1_CLI")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
} else if std::env::var("NYASH_USE_STAGE1_CLI").ok().as_deref() == Some("1") {
|
||||
cmd.env("NYASH_STAGE1_MODE", "run");
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,11 +33,7 @@ impl NyashRunner {
|
||||
}
|
||||
|
||||
// Guard: skip if child invocation
|
||||
if std::env::var("NYASH_STAGE1_CLI_CHILD")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
if std::env::var("NYASH_STAGE1_CLI_CHILD").ok().as_deref() == Some("1") {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("2") {
|
||||
eprintln!("[stage1-bridge/trace] skip: NYASH_STAGE1_CLI_CHILD=1");
|
||||
}
|
||||
@ -45,11 +41,7 @@ impl NyashRunner {
|
||||
}
|
||||
|
||||
// Guard: skip if not enabled
|
||||
if std::env::var("NYASH_USE_STAGE1_CLI")
|
||||
.ok()
|
||||
.as_deref()
|
||||
!= Some("1")
|
||||
{
|
||||
if std::env::var("NYASH_USE_STAGE1_CLI").ok().as_deref() != Some("1") {
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("2") {
|
||||
eprintln!("[stage1-bridge/trace] skip: NYASH_USE_STAGE1_CLI!=1");
|
||||
}
|
||||
@ -83,8 +75,8 @@ impl NyashRunner {
|
||||
std::path::PathBuf::from("target/release/nyash")
|
||||
});
|
||||
let mut cmd = std::process::Command::new(exe);
|
||||
let entry_fn = std::env::var("NYASH_ENTRY")
|
||||
.unwrap_or_else(|_| "Stage1CliMain.main/0".to_string());
|
||||
let entry_fn =
|
||||
std::env::var("NYASH_ENTRY").unwrap_or_else(|_| "Stage1CliMain.main/0".to_string());
|
||||
cmd.arg(&entry).arg("--");
|
||||
for a in &args_result.args {
|
||||
cmd.arg(a);
|
||||
|
||||
@ -12,11 +12,7 @@ use crate::mir::MirCompiler;
|
||||
use crate::parser::NyashParser;
|
||||
|
||||
fn require_experiment_toggle() -> bool {
|
||||
if std::env::var("NYASH_JOINIR_EXPERIMENT")
|
||||
.ok()
|
||||
.as_deref()
|
||||
!= Some("1")
|
||||
{
|
||||
if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") {
|
||||
eprintln!(
|
||||
"[joinir/runner] NYASH_JOINIR_EXPERIMENT=1 not set, skipping experimental runner test"
|
||||
);
|
||||
@ -50,8 +46,7 @@ static box Runner {
|
||||
"#;
|
||||
let full_src = format!("{src}\n{runner}");
|
||||
|
||||
let ast: ASTNode =
|
||||
NyashParser::parse_from_string(&full_src).expect("skip_ws: parse failed");
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&full_src).expect("skip_ws: parse failed");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("skip_ws: MIR compile failed");
|
||||
|
||||
@ -113,8 +108,7 @@ static box Runner {
|
||||
"#;
|
||||
let full_src = format!("{func_scanner_src}\n{test_src}\n{runner}");
|
||||
|
||||
let ast: ASTNode =
|
||||
NyashParser::parse_from_string(&full_src).expect("trim_min: parse failed");
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&full_src).expect("trim_min: parse failed");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("trim_min: MIR compile failed");
|
||||
|
||||
|
||||
@ -14,11 +14,7 @@ use crate::mir::join_ir::*;
|
||||
use crate::mir::join_ir_runner::{run_joinir_function, JoinValue};
|
||||
|
||||
fn require_experiment_toggle() -> bool {
|
||||
if std::env::var("NYASH_JOINIR_EXPERIMENT")
|
||||
.ok()
|
||||
.as_deref()
|
||||
!= Some("1")
|
||||
{
|
||||
if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") {
|
||||
eprintln!(
|
||||
"[joinir/runner/standalone] NYASH_JOINIR_EXPERIMENT=1 not set, skipping standalone test"
|
||||
);
|
||||
@ -161,7 +157,10 @@ fn joinir_runner_standalone_trim() {
|
||||
)
|
||||
.expect("trim runner failed");
|
||||
match result {
|
||||
JoinValue::Str(s) => assert_eq!(s, "abc ", "simplified trim should remove only leading spaces"),
|
||||
JoinValue::Str(s) => assert_eq!(
|
||||
s, "abc ",
|
||||
"simplified trim should remove only leading spaces"
|
||||
),
|
||||
other => panic!("trim returned non-string: {:?}", other),
|
||||
}
|
||||
|
||||
@ -209,7 +208,9 @@ fn build_skip_ws_joinir() -> JoinModule {
|
||||
let mut entry_func = JoinFunction::new(entry_id, "skip_ws_entry".to_string(), vec![s_param]);
|
||||
|
||||
// n = s.length()
|
||||
entry_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
entry_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(n_var),
|
||||
box_name: "StringBox".to_string(),
|
||||
method: "length".to_string(),
|
||||
@ -360,7 +361,9 @@ fn build_trim_joinir() -> JoinModule {
|
||||
let mut entry_func = JoinFunction::new(entry_id, "trim_entry".to_string(), vec![s_param]);
|
||||
|
||||
// n = s.length()
|
||||
entry_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
entry_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(n_var),
|
||||
box_name: "StringBox".to_string(),
|
||||
method: "length".to_string(),
|
||||
@ -383,7 +386,9 @@ fn build_trim_joinir() -> JoinModule {
|
||||
});
|
||||
|
||||
// result = s.substring(start, n)
|
||||
entry_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
entry_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(result_var),
|
||||
box_name: "StringBox".to_string(),
|
||||
method: "substring".to_string(),
|
||||
|
||||
@ -21,14 +21,8 @@ use crate::mir::MirCompiler;
|
||||
use crate::parser::NyashParser;
|
||||
|
||||
fn require_experiment_toggle() -> bool {
|
||||
if std::env::var("NYASH_JOINIR_VM_BRIDGE")
|
||||
.ok()
|
||||
.as_deref()
|
||||
!= Some("1")
|
||||
{
|
||||
eprintln!(
|
||||
"[joinir/vm_bridge] NYASH_JOINIR_VM_BRIDGE=1 not set, skipping VM bridge test"
|
||||
);
|
||||
if std::env::var("NYASH_JOINIR_VM_BRIDGE").ok().as_deref() != Some("1") {
|
||||
eprintln!("[joinir/vm_bridge] NYASH_JOINIR_VM_BRIDGE=1 not set, skipping VM bridge test");
|
||||
return false;
|
||||
}
|
||||
true
|
||||
@ -57,8 +51,7 @@ static box Runner {
|
||||
"#;
|
||||
let full_src = format!("{src}\n{runner}");
|
||||
|
||||
let ast: ASTNode =
|
||||
NyashParser::parse_from_string(&full_src).expect("skip_ws: parse failed");
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&full_src).expect("skip_ws: parse failed");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("skip_ws: MIR compile failed");
|
||||
|
||||
@ -86,10 +79,16 @@ static box Runner {
|
||||
)
|
||||
.expect("JoinIR VM bridge failed for skip_ws");
|
||||
|
||||
eprintln!("[joinir_vm_bridge_test] Route C result: {:?}", bridge_result);
|
||||
eprintln!(
|
||||
"[joinir_vm_bridge_test] Route C result: {:?}",
|
||||
bridge_result
|
||||
);
|
||||
|
||||
// Assertions: Both routes should produce the same result
|
||||
assert_eq!(vm_result, "3", "Route A (VM) expected to skip 3 leading spaces");
|
||||
assert_eq!(
|
||||
vm_result, "3",
|
||||
"Route A (VM) expected to skip 3 leading spaces"
|
||||
);
|
||||
match bridge_result {
|
||||
JoinValue::Int(v) => {
|
||||
assert_eq!(v, 3, "Route C (JoinIR→VM bridge) skip_ws result mismatch");
|
||||
|
||||
@ -106,4 +106,3 @@ static box Main {
|
||||
std::env::remove_var("NYASH_DISABLE_PLUGINS");
|
||||
std::env::remove_var("HAKO_MIR_BUILDER_METHODIZE");
|
||||
}
|
||||
|
||||
|
||||
@ -30,10 +30,8 @@ fn mir_funcscanner_parse_params_trim_min_verify_and_vm() {
|
||||
// std::env::set_var("NYASH_IF_HOLE_TRACE", "1");
|
||||
|
||||
// Bundle FuncScanner 本体と最小テスト。
|
||||
let func_scanner_src =
|
||||
include_str!("../../lang/src/compiler/entry/func_scanner.hako");
|
||||
let test_src =
|
||||
std::fs::read_to_string(test_file).expect("Failed to read minimal test .hako");
|
||||
let func_scanner_src = include_str!("../../lang/src/compiler/entry/func_scanner.hako");
|
||||
let test_src = std::fs::read_to_string(test_file).expect("Failed to read minimal test .hako");
|
||||
let src = format!("{func_scanner_src}\n\n{test_src}");
|
||||
|
||||
let ast: ASTNode =
|
||||
@ -86,4 +84,3 @@ fn mir_funcscanner_parse_params_trim_min_verify_and_vm() {
|
||||
std::env::remove_var("NYASH_VM_VERIFY_MIR");
|
||||
std::env::remove_var("NYASH_IF_HOLE_TRACE");
|
||||
}
|
||||
|
||||
|
||||
@ -29,14 +29,11 @@ fn mir_funcscanner_trim_min_verify_and_vm() {
|
||||
// std::env::set_var("NYASH_IF_HOLE_TRACE", "1");
|
||||
|
||||
// FuncScanner 本体と最小 _trim テストを 1 ソースにまとめる。
|
||||
let func_scanner_src =
|
||||
include_str!("../../lang/src/compiler/entry/func_scanner.hako");
|
||||
let test_src =
|
||||
std::fs::read_to_string(test_file).expect("Failed to read trim_min .hako");
|
||||
let func_scanner_src = include_str!("../../lang/src/compiler/entry/func_scanner.hako");
|
||||
let test_src = std::fs::read_to_string(test_file).expect("Failed to read trim_min .hako");
|
||||
let src = format!("{func_scanner_src}\n\n{test_src}");
|
||||
|
||||
let ast: ASTNode =
|
||||
NyashParser::parse_from_string(&src).expect("trim_min: parse failed");
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&src).expect("trim_min: parse failed");
|
||||
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("trim_min: MIR compile failed");
|
||||
|
||||
@ -25,6 +25,7 @@ use crate::mir::join_ir::lowering::funcscanner_append_defs::lower_funcscanner_ap
|
||||
use crate::mir::join_ir::*;
|
||||
use crate::mir::{MirCompiler, ValueId};
|
||||
use crate::parser::NyashParser;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
#[ignore] // 手動実行用(Phase 27.14 実験段階)
|
||||
@ -47,11 +48,13 @@ fn mir_joinir_funcscanner_append_defs_auto_lowering() {
|
||||
let src = std::fs::read_to_string(test_file)
|
||||
.unwrap_or_else(|_| panic!("Failed to read {}", test_file));
|
||||
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&src)
|
||||
.expect("funcscanner_append_defs: parse failed");
|
||||
let ast: ASTNode =
|
||||
NyashParser::parse_from_string(&src).expect("funcscanner_append_defs: parse failed");
|
||||
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("funcscanner_append_defs: MIR compile failed");
|
||||
let compiled = mc
|
||||
.compile(ast)
|
||||
.expect("funcscanner_append_defs: MIR compile failed");
|
||||
|
||||
eprintln!(
|
||||
"[joinir/funcscanner_append_defs] MIR module compiled, {} functions",
|
||||
@ -66,32 +69,69 @@ fn mir_joinir_funcscanner_append_defs_auto_lowering() {
|
||||
eprintln!("{:#?}", join_module);
|
||||
|
||||
// Step 3: 妥当性検証(Phase 27.14)
|
||||
assert_eq!(join_module.functions.len(), 2, "Expected 2 functions (append_defs_entry + loop_step)");
|
||||
assert_eq!(
|
||||
join_module.functions.len(),
|
||||
2,
|
||||
"Expected 2 functions (append_defs_entry + loop_step)"
|
||||
);
|
||||
|
||||
let entry_id = JoinFuncId::new(0);
|
||||
let loop_step_id = JoinFuncId::new(1);
|
||||
|
||||
// append_defs_entry 関数の検証
|
||||
let entry_func = join_module.functions.get(&entry_id)
|
||||
let entry_func = join_module
|
||||
.functions
|
||||
.get(&entry_id)
|
||||
.expect("append_defs_entry function not found");
|
||||
assert_eq!(entry_func.name, "append_defs_entry");
|
||||
assert_eq!(entry_func.params.len(), 3, "append_defs_entry has 3 parameters (dst, defs_box, n)");
|
||||
assert_eq!(
|
||||
entry_func.params.len(),
|
||||
3,
|
||||
"append_defs_entry has 3 parameters (dst, defs_box, n)"
|
||||
);
|
||||
|
||||
// loop_step 関数の検証
|
||||
let loop_step_func = join_module.functions.get(&loop_step_id)
|
||||
let loop_step_func = join_module
|
||||
.functions
|
||||
.get(&loop_step_id)
|
||||
.expect("loop_step function not found");
|
||||
assert_eq!(loop_step_func.name, "loop_step");
|
||||
assert_eq!(loop_step_func.params.len(), 4, "loop_step has 4 parameters (dst, defs_box, n, i)");
|
||||
assert_eq!(
|
||||
loop_step_func.params.len(),
|
||||
4,
|
||||
"loop_step has 4 parameters (dst, defs_box, n, i)"
|
||||
);
|
||||
|
||||
// ValueId range 検証 (9000-10999)
|
||||
assert_eq!(entry_func.params[0].0, 9000, "dst parameter should be ValueId(9000)");
|
||||
assert_eq!(entry_func.params[1].0, 9001, "defs_box parameter should be ValueId(9001)");
|
||||
assert_eq!(entry_func.params[2].0, 9002, "n parameter should be ValueId(9002)");
|
||||
assert_eq!(
|
||||
entry_func.params[0].0, 9000,
|
||||
"dst parameter should be ValueId(9000)"
|
||||
);
|
||||
assert_eq!(
|
||||
entry_func.params[1].0, 9001,
|
||||
"defs_box parameter should be ValueId(9001)"
|
||||
);
|
||||
assert_eq!(
|
||||
entry_func.params[2].0, 9002,
|
||||
"n parameter should be ValueId(9002)"
|
||||
);
|
||||
|
||||
assert_eq!(loop_step_func.params[0].0, 10000, "dst_loop parameter should be ValueId(10000)");
|
||||
assert_eq!(loop_step_func.params[1].0, 10001, "defs_box_loop parameter should be ValueId(10001)");
|
||||
assert_eq!(loop_step_func.params[2].0, 10002, "n_loop parameter should be ValueId(10002)");
|
||||
assert_eq!(loop_step_func.params[3].0, 10003, "i_loop parameter should be ValueId(10003)");
|
||||
assert_eq!(
|
||||
loop_step_func.params[0].0, 10000,
|
||||
"dst_loop parameter should be ValueId(10000)"
|
||||
);
|
||||
assert_eq!(
|
||||
loop_step_func.params[1].0, 10001,
|
||||
"defs_box_loop parameter should be ValueId(10001)"
|
||||
);
|
||||
assert_eq!(
|
||||
loop_step_func.params[2].0, 10002,
|
||||
"n_loop parameter should be ValueId(10002)"
|
||||
);
|
||||
assert_eq!(
|
||||
loop_step_func.params[3].0, 10003,
|
||||
"i_loop parameter should be ValueId(10003)"
|
||||
);
|
||||
|
||||
eprintln!("[joinir/funcscanner_append_defs] ✅ 自動変換成功(Phase 27.14)");
|
||||
}
|
||||
@ -127,5 +167,78 @@ fn mir_joinir_funcscanner_append_defs_empty_module_returns_none() {
|
||||
let result = lower_funcscanner_append_defs_to_joinir(&test_module);
|
||||
|
||||
eprintln!("[joinir/funcscanner_append_defs] empty_module test: result is None (expected)");
|
||||
assert!(result.is_none(), "Empty MirModule should return None (target function not found)");
|
||||
assert!(
|
||||
result.is_none(),
|
||||
"Empty MirModule should return None (target function not found)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // 手動実行: generic_case_a トグル検証(append_defs minimal)
|
||||
fn mir_joinir_funcscanner_append_defs_generic_matches_handwritten() {
|
||||
if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") {
|
||||
eprintln!("[joinir/funcscanner_append_defs] NYASH_JOINIR_EXPERIMENT=1 not set, skipping generic test");
|
||||
return;
|
||||
}
|
||||
|
||||
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||
|
||||
let test_file = "apps/tests/funcscanner_append_defs_minimal.hako";
|
||||
let src = std::fs::read_to_string(test_file)
|
||||
.unwrap_or_else(|_| panic!("Failed to read {}", test_file));
|
||||
|
||||
let ast: ASTNode =
|
||||
NyashParser::parse_from_string(&src).expect("append_defs generic: parse failed");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc
|
||||
.compile(ast)
|
||||
.expect("append_defs generic: MIR compile failed");
|
||||
|
||||
fn params_by_name(jm: &JoinModule) -> BTreeMap<String, usize> {
|
||||
jm.functions
|
||||
.values()
|
||||
.map(|f| (f.name.clone(), f.params.len()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Baseline (generic OFF)
|
||||
std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "0");
|
||||
let baseline = lower_funcscanner_append_defs_to_joinir(&compiled.module)
|
||||
.expect("baseline append_defs lowering failed");
|
||||
|
||||
// Generic ON
|
||||
std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "1");
|
||||
let generic = lower_funcscanner_append_defs_to_joinir(&compiled.module)
|
||||
.expect("generic append_defs lowering failed");
|
||||
|
||||
let baseline_params = params_by_name(&baseline);
|
||||
let generic_params = params_by_name(&generic);
|
||||
|
||||
assert_eq!(baseline_params.len(), 2, "baseline should have 2 functions");
|
||||
assert_eq!(generic_params.len(), 2, "generic should have 2 functions");
|
||||
assert_eq!(
|
||||
baseline_params.len(),
|
||||
generic_params.len(),
|
||||
"function count mismatch"
|
||||
);
|
||||
|
||||
let expected_funcs = ["append_defs_entry", "loop_step"];
|
||||
for name in expected_funcs {
|
||||
let b_params = baseline_params
|
||||
.get(name)
|
||||
.copied()
|
||||
.unwrap_or_else(|| panic!("baseline missing function {}", name));
|
||||
let g_params = generic_params
|
||||
.get(name)
|
||||
.copied()
|
||||
.unwrap_or_else(|| panic!("generic missing function {}", name));
|
||||
assert_eq!(
|
||||
b_params, g_params,
|
||||
"param count differs for function {}",
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
eprintln!("[joinir/funcscanner_append_defs] ✅ generic_case_a JoinIR matches baseline");
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::*;
|
||||
use crate::mir::{MirCompiler, ValueId};
|
||||
use crate::parser::NyashParser;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
#[ignore] // 手動実行用(Phase 27.1 実験段階)
|
||||
@ -51,8 +52,7 @@ fn mir_joinir_funcscanner_trim_auto_lowering() {
|
||||
.unwrap_or_else(|_| panic!("Failed to read {}", test_file));
|
||||
let src = format!("{func_scanner_src}\n\n{test_src}");
|
||||
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&src)
|
||||
.expect("trim: parse failed");
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&src).expect("trim: parse failed");
|
||||
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("trim: MIR compile failed");
|
||||
@ -70,24 +70,46 @@ fn mir_joinir_funcscanner_trim_auto_lowering() {
|
||||
eprintln!("{:#?}", join_module);
|
||||
|
||||
// Step 3: 妥当性検証
|
||||
assert_eq!(join_module.functions.len(), 3, "Expected 3 functions (trim_main + loop_step + skip_leading)");
|
||||
assert_eq!(
|
||||
join_module.functions.len(),
|
||||
3,
|
||||
"Expected 3 functions (trim_main + loop_step + skip_leading)"
|
||||
);
|
||||
|
||||
let trim_main_id = JoinFuncId::new(0);
|
||||
let loop_step_id = JoinFuncId::new(1);
|
||||
|
||||
// trim_main 関数の検証
|
||||
let trim_main_func = join_module.functions.get(&trim_main_id)
|
||||
let trim_main_func = join_module
|
||||
.functions
|
||||
.get(&trim_main_id)
|
||||
.expect("trim_main function not found");
|
||||
assert_eq!(trim_main_func.name, "trim_main");
|
||||
assert_eq!(trim_main_func.params.len(), 1, "trim_main has 1 parameter (s)");
|
||||
assert!(trim_main_func.body.len() >= 5, "trim_main should have at least 5 instructions");
|
||||
assert_eq!(
|
||||
trim_main_func.params.len(),
|
||||
1,
|
||||
"trim_main has 1 parameter (s)"
|
||||
);
|
||||
assert!(
|
||||
trim_main_func.body.len() >= 5,
|
||||
"trim_main should have at least 5 instructions"
|
||||
);
|
||||
|
||||
// loop_step 関数の検証
|
||||
let loop_step_func = join_module.functions.get(&loop_step_id)
|
||||
let loop_step_func = join_module
|
||||
.functions
|
||||
.get(&loop_step_id)
|
||||
.expect("loop_step function not found");
|
||||
assert_eq!(loop_step_func.name, "loop_step");
|
||||
assert_eq!(loop_step_func.params.len(), 3, "loop_step has 3 parameters (str, b, e)");
|
||||
assert!(loop_step_func.body.len() >= 10, "loop_step should have multiple instructions");
|
||||
assert_eq!(
|
||||
loop_step_func.params.len(),
|
||||
3,
|
||||
"loop_step has 3 parameters (str, b, e)"
|
||||
);
|
||||
assert!(
|
||||
loop_step_func.body.len() >= 10,
|
||||
"loop_step should have multiple instructions"
|
||||
);
|
||||
|
||||
eprintln!("[joinir/trim] ✅ 自動変換成功(Phase 27.1)");
|
||||
}
|
||||
@ -98,14 +120,82 @@ fn mir_joinir_funcscanner_trim_type_sanity() {
|
||||
// trim 用の JoinFunction が作成できることを確認
|
||||
|
||||
let trim_main_id = JoinFuncId::new(10);
|
||||
let trim_main_func = JoinFunction::new(
|
||||
trim_main_id,
|
||||
"trim_main_test".to_string(),
|
||||
vec![ValueId(1)],
|
||||
);
|
||||
let trim_main_func =
|
||||
JoinFunction::new(trim_main_id, "trim_main_test".to_string(), vec![ValueId(1)]);
|
||||
|
||||
assert_eq!(trim_main_func.id, trim_main_id);
|
||||
assert_eq!(trim_main_func.name, "trim_main_test");
|
||||
assert_eq!(trim_main_func.params.len(), 1);
|
||||
assert_eq!(trim_main_func.body.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // 手動実行: generic_case_a トグル検証(trim minimal)
|
||||
fn mir_joinir_funcscanner_trim_generic_matches_handwritten() {
|
||||
if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") {
|
||||
eprintln!("[joinir/trim] NYASH_JOINIR_EXPERIMENT=1 not set, skipping generic test");
|
||||
return;
|
||||
}
|
||||
|
||||
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||
std::env::set_var("NYASH_ENABLE_USING", "1");
|
||||
std::env::set_var("HAKO_ENABLE_USING", "1");
|
||||
|
||||
let func_scanner_src = include_str!("../../lang/src/compiler/entry/func_scanner.hako");
|
||||
let test_file = "lang/src/compiler/tests/funcscanner_trim_min.hako";
|
||||
let test_src = std::fs::read_to_string(test_file)
|
||||
.unwrap_or_else(|_| panic!("Failed to read {}", test_file));
|
||||
let src = format!("{func_scanner_src}\n\n{test_src}");
|
||||
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&src).expect("trim generic: parse failed");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("trim generic: MIR compile failed");
|
||||
|
||||
fn params_by_name(jm: &JoinModule) -> BTreeMap<String, usize> {
|
||||
jm.functions
|
||||
.values()
|
||||
.map(|f| (f.name.clone(), f.params.len()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Baseline (generic OFF)
|
||||
std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "0");
|
||||
let baseline =
|
||||
lower_funcscanner_trim_to_joinir(&compiled.module).expect("baseline trim lowering failed");
|
||||
|
||||
// Generic ON
|
||||
std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "1");
|
||||
let generic =
|
||||
lower_funcscanner_trim_to_joinir(&compiled.module).expect("generic trim lowering failed");
|
||||
|
||||
let baseline_params = params_by_name(&baseline);
|
||||
let generic_params = params_by_name(&generic);
|
||||
|
||||
assert_eq!(baseline_params.len(), 3, "baseline should have 3 functions");
|
||||
assert_eq!(generic_params.len(), 3, "generic should have 3 functions");
|
||||
assert_eq!(
|
||||
baseline_params.len(),
|
||||
generic_params.len(),
|
||||
"function count mismatch"
|
||||
);
|
||||
|
||||
let expected_funcs = ["trim_main", "loop_step", "skip_leading"];
|
||||
for name in expected_funcs {
|
||||
let b_params = baseline_params
|
||||
.get(name)
|
||||
.copied()
|
||||
.unwrap_or_else(|| panic!("baseline missing function {}", name));
|
||||
let g_params = generic_params
|
||||
.get(name)
|
||||
.copied()
|
||||
.unwrap_or_else(|| panic!("generic missing function {}", name));
|
||||
assert_eq!(
|
||||
b_params, g_params,
|
||||
"param count differs for function {}",
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
eprintln!("[joinir/trim] ✅ generic_case_a JoinIR matches baseline");
|
||||
}
|
||||
|
||||
@ -23,7 +23,9 @@ fn mir_joinir_min_manual_construction() {
|
||||
|
||||
// 環境変数トグルチェック
|
||||
if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") {
|
||||
eprintln!("[joinir/min] NYASH_JOINIR_EXPERIMENT=1 not set, skipping manual construction test");
|
||||
eprintln!(
|
||||
"[joinir/min] NYASH_JOINIR_EXPERIMENT=1 not set, skipping manual construction test"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -36,8 +38,7 @@ fn mir_joinir_min_manual_construction() {
|
||||
let src = std::fs::read_to_string(test_file)
|
||||
.unwrap_or_else(|_| panic!("Failed to read {}", test_file));
|
||||
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&src)
|
||||
.expect("joinir_min: parse failed");
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&src).expect("joinir_min: parse failed");
|
||||
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("joinir_min: MIR compile failed");
|
||||
@ -85,7 +86,9 @@ fn mir_joinir_min_manual_construction() {
|
||||
let i_plus_1 = ValueId(202);
|
||||
|
||||
// cmp_result = (i >= 2)
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Compare {
|
||||
dst: cmp_result,
|
||||
op: CompareOp::Ge,
|
||||
lhs: i_param,
|
||||
@ -93,7 +96,9 @@ fn mir_joinir_min_manual_construction() {
|
||||
}));
|
||||
|
||||
// const 2
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: ValueId(203),
|
||||
value: ConstValue::Integer(2),
|
||||
}));
|
||||
@ -107,7 +112,9 @@ fn mir_joinir_min_manual_construction() {
|
||||
});
|
||||
|
||||
// i_plus_1 = i + 1
|
||||
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
loop_step_func
|
||||
.body
|
||||
.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||
dst: i_plus_1,
|
||||
op: BinOpKind::Add,
|
||||
lhs: i_param,
|
||||
@ -148,8 +155,7 @@ fn mir_joinir_min_auto_lowering() {
|
||||
let src = std::fs::read_to_string(test_file)
|
||||
.unwrap_or_else(|_| panic!("Failed to read {}", test_file));
|
||||
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&src)
|
||||
.expect("joinir_min: parse failed");
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&src).expect("joinir_min: parse failed");
|
||||
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("joinir_min: MIR compile failed");
|
||||
@ -160,31 +166,49 @@ fn mir_joinir_min_auto_lowering() {
|
||||
);
|
||||
|
||||
// Step 2: MIR → JoinIR 自動変換
|
||||
let join_module = lower_min_loop_to_joinir(&compiled.module)
|
||||
.expect("lower_min_loop_to_joinir failed");
|
||||
let join_module =
|
||||
lower_min_loop_to_joinir(&compiled.module).expect("lower_min_loop_to_joinir failed");
|
||||
|
||||
eprintln!("[joinir/auto] JoinIR module generated:");
|
||||
eprintln!("{:#?}", join_module);
|
||||
|
||||
// Step 3: 妥当性検証
|
||||
assert_eq!(join_module.functions.len(), 2, "Expected 2 functions (main + loop_step)");
|
||||
assert_eq!(
|
||||
join_module.functions.len(),
|
||||
2,
|
||||
"Expected 2 functions (main + loop_step)"
|
||||
);
|
||||
|
||||
let main_id = JoinFuncId::new(0);
|
||||
let loop_step_id = JoinFuncId::new(1);
|
||||
|
||||
// main 関数の検証
|
||||
let main_func = join_module.functions.get(&main_id)
|
||||
let main_func = join_module
|
||||
.functions
|
||||
.get(&main_id)
|
||||
.expect("main function not found");
|
||||
assert_eq!(main_func.name, "main");
|
||||
assert_eq!(main_func.params.len(), 0, "main has no parameters");
|
||||
assert!(main_func.body.len() >= 2, "main should have at least 2 instructions (const + call)");
|
||||
assert!(
|
||||
main_func.body.len() >= 2,
|
||||
"main should have at least 2 instructions (const + call)"
|
||||
);
|
||||
|
||||
// loop_step 関数の検証
|
||||
let loop_step_func = join_module.functions.get(&loop_step_id)
|
||||
let loop_step_func = join_module
|
||||
.functions
|
||||
.get(&loop_step_id)
|
||||
.expect("loop_step function not found");
|
||||
assert_eq!(loop_step_func.name, "loop_step");
|
||||
assert_eq!(loop_step_func.params.len(), 1, "loop_step has 1 parameter (i)");
|
||||
assert!(loop_step_func.body.len() >= 4, "loop_step should have multiple instructions");
|
||||
assert_eq!(
|
||||
loop_step_func.params.len(),
|
||||
1,
|
||||
"loop_step has 1 parameter (i)"
|
||||
);
|
||||
assert!(
|
||||
loop_step_func.body.len() >= 4,
|
||||
"loop_step should have multiple instructions"
|
||||
);
|
||||
|
||||
eprintln!("[joinir/auto] ✅ 自動変換成功(Phase 26-H Step 2)");
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::*;
|
||||
use crate::mir::{MirCompiler, ValueId};
|
||||
use crate::parser::NyashParser;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
#[ignore] // 手動実行用(Phase 27.1 実験段階)
|
||||
@ -32,7 +33,9 @@ fn mir_joinir_skip_ws_auto_lowering() {
|
||||
|
||||
// 環境変数トグルチェック
|
||||
if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") {
|
||||
eprintln!("[joinir/skip_ws] NYASH_JOINIR_EXPERIMENT=1 not set, skipping auto-lowering test");
|
||||
eprintln!(
|
||||
"[joinir/skip_ws] NYASH_JOINIR_EXPERIMENT=1 not set, skipping auto-lowering test"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -45,8 +48,7 @@ fn mir_joinir_skip_ws_auto_lowering() {
|
||||
let src = std::fs::read_to_string(test_file)
|
||||
.unwrap_or_else(|_| panic!("Failed to read {}", test_file));
|
||||
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&src)
|
||||
.expect("skip_ws: parse failed");
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&src).expect("skip_ws: parse failed");
|
||||
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("skip_ws: MIR compile failed");
|
||||
@ -57,30 +59,45 @@ fn mir_joinir_skip_ws_auto_lowering() {
|
||||
);
|
||||
|
||||
// Step 2: MIR → JoinIR 自動変換
|
||||
let join_module = lower_skip_ws_to_joinir(&compiled.module)
|
||||
.expect("lower_skip_ws_to_joinir failed");
|
||||
let join_module =
|
||||
lower_skip_ws_to_joinir(&compiled.module).expect("lower_skip_ws_to_joinir failed");
|
||||
|
||||
eprintln!("[joinir/skip_ws] JoinIR module generated:");
|
||||
eprintln!("{:#?}", join_module);
|
||||
|
||||
// Step 3: 妥当性検証
|
||||
assert_eq!(join_module.functions.len(), 2, "Expected 2 functions (skip + loop_step)");
|
||||
assert_eq!(
|
||||
join_module.functions.len(),
|
||||
2,
|
||||
"Expected 2 functions (skip + loop_step)"
|
||||
);
|
||||
|
||||
let skip_id = JoinFuncId::new(0);
|
||||
let loop_step_id = JoinFuncId::new(1);
|
||||
|
||||
// skip 関数の検証
|
||||
let skip_func = join_module.functions.get(&skip_id)
|
||||
let skip_func = join_module
|
||||
.functions
|
||||
.get(&skip_id)
|
||||
.expect("skip function not found");
|
||||
assert_eq!(skip_func.name, "skip");
|
||||
assert_eq!(skip_func.params.len(), 1, "skip has 1 parameter (s)");
|
||||
assert!(skip_func.body.len() >= 3, "skip should have at least 3 instructions (const 0, length call, loop_step call)");
|
||||
assert!(
|
||||
skip_func.body.len() >= 3,
|
||||
"skip should have at least 3 instructions (const 0, length call, loop_step call)"
|
||||
);
|
||||
|
||||
// loop_step 関数の検証
|
||||
let loop_step_func = join_module.functions.get(&loop_step_id)
|
||||
let loop_step_func = join_module
|
||||
.functions
|
||||
.get(&loop_step_id)
|
||||
.expect("loop_step function not found");
|
||||
assert_eq!(loop_step_func.name, "loop_step");
|
||||
assert_eq!(loop_step_func.params.len(), 3, "loop_step has 3 parameters (s, i, n)");
|
||||
assert_eq!(
|
||||
loop_step_func.params.len(),
|
||||
3,
|
||||
"loop_step has 3 parameters (s, i, n)"
|
||||
);
|
||||
assert!(loop_step_func.body.len() >= 8, "loop_step should have multiple instructions (comparisons, substring, recursive call, etc.)");
|
||||
|
||||
eprintln!("[joinir/skip_ws] ✅ 自動変換成功(Phase 27.1)");
|
||||
@ -103,3 +120,97 @@ fn mir_joinir_skip_ws_type_sanity() {
|
||||
assert_eq!(skip_func.params.len(), 3);
|
||||
assert_eq!(skip_func.body.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mir_joinir_skip_ws_generic_matches_handwritten() {
|
||||
if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") {
|
||||
eprintln!("[joinir/skip_ws] NYASH_JOINIR_EXPERIMENT=1 not set, skipping generic test");
|
||||
return;
|
||||
}
|
||||
|
||||
// Stage-3 parserを有効化
|
||||
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||
|
||||
let test_file = "apps/tests/minimal_ssa_skip_ws.hako";
|
||||
let src = std::fs::read_to_string(test_file)
|
||||
.unwrap_or_else(|_| panic!("Failed to read {}", test_file));
|
||||
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&src).expect("skip_ws generic: parse failed");
|
||||
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc
|
||||
.compile(ast)
|
||||
.expect("skip_ws generic: MIR compile failed");
|
||||
|
||||
fn params_by_name(jm: &JoinModule) -> BTreeMap<String, usize> {
|
||||
jm.functions
|
||||
.values()
|
||||
.map(|f| (f.name.clone(), f.params.len()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn has_jump_to_cont(jm: &JoinModule, func_name: &str, cont: JoinContId) -> bool {
|
||||
jm.functions
|
||||
.values()
|
||||
.find(|f| f.name == func_name)
|
||||
.map(|f| {
|
||||
f.body.iter().any(|inst| match inst {
|
||||
JoinInst::Jump { cont: c, .. } => *c == cont,
|
||||
_ => false,
|
||||
})
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
// Baseline (generic OFF)
|
||||
std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "0");
|
||||
let baseline =
|
||||
lower_skip_ws_to_joinir(&compiled.module).expect("baseline skip_ws lowering failed");
|
||||
|
||||
// Generic ON
|
||||
std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "1");
|
||||
let generic =
|
||||
lower_skip_ws_to_joinir(&compiled.module).expect("generic skip_ws lowering failed");
|
||||
|
||||
// Compare shape (function count + params + loop_step/k_exit 相当の存在確認)
|
||||
let baseline_params = params_by_name(&baseline);
|
||||
let generic_params = params_by_name(&generic);
|
||||
|
||||
assert_eq!(baseline_params.len(), 2, "baseline should have 2 functions");
|
||||
assert_eq!(generic_params.len(), 2, "generic should have 2 functions");
|
||||
assert_eq!(
|
||||
baseline_params.len(),
|
||||
generic_params.len(),
|
||||
"function count mismatch"
|
||||
);
|
||||
|
||||
let expected_funcs = ["skip", "loop_step"];
|
||||
for name in expected_funcs {
|
||||
let b_params = baseline_params
|
||||
.get(name)
|
||||
.copied()
|
||||
.unwrap_or_else(|| panic!("baseline missing function {}", name));
|
||||
let g_params = generic_params
|
||||
.get(name)
|
||||
.copied()
|
||||
.unwrap_or_else(|| panic!("generic missing function {}", name));
|
||||
assert_eq!(
|
||||
b_params, g_params,
|
||||
"param count differs for function {}",
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
let exit_cont = JoinContId::new(0);
|
||||
assert!(
|
||||
has_jump_to_cont(&baseline, "loop_step", exit_cont),
|
||||
"baseline loop_step should jump to k_exit(cont0)"
|
||||
);
|
||||
assert!(
|
||||
has_jump_to_cont(&generic, "loop_step", exit_cont),
|
||||
"generic loop_step should jump to k_exit(cont0)"
|
||||
);
|
||||
|
||||
eprintln!("[joinir/skip_ws] ✅ generic_case_a JoinIR matches baseline");
|
||||
}
|
||||
|
||||
@ -25,9 +25,10 @@ use crate::mir::join_ir::lowering::stage1_using_resolver::lower_stage1_usingreso
|
||||
use crate::mir::join_ir::*;
|
||||
use crate::mir::{MirCompiler, ValueId};
|
||||
use crate::parser::NyashParser;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
#[ignore] // 手動実行用(Phase 27.12 実験段階)
|
||||
#[ignore] // 手動実行用(Stage-1 minimal)
|
||||
fn mir_joinir_stage1_using_resolver_auto_lowering() {
|
||||
// Phase 27.12: Stage1UsingResolverBox.resolve_for_source の MIR → JoinIR 自動変換
|
||||
|
||||
@ -47,11 +48,13 @@ fn mir_joinir_stage1_using_resolver_auto_lowering() {
|
||||
let src = std::fs::read_to_string(test_file)
|
||||
.unwrap_or_else(|_| panic!("Failed to read {}", test_file));
|
||||
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&src)
|
||||
.expect("stage1_using_resolver: parse failed");
|
||||
let ast: ASTNode =
|
||||
NyashParser::parse_from_string(&src).expect("stage1_using_resolver: parse failed");
|
||||
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("stage1_using_resolver: MIR compile failed");
|
||||
let compiled = mc
|
||||
.compile(ast)
|
||||
.expect("stage1_using_resolver: MIR compile failed");
|
||||
|
||||
eprintln!(
|
||||
"[joinir/stage1_using_resolver] MIR module compiled, {} functions",
|
||||
@ -66,22 +69,38 @@ fn mir_joinir_stage1_using_resolver_auto_lowering() {
|
||||
eprintln!("{:#?}", join_module);
|
||||
|
||||
// Step 3: 妥当性検証(Phase 27.13 以降で実装)
|
||||
assert_eq!(join_module.functions.len(), 2, "Expected 2 functions (resolve_entries + loop_step)");
|
||||
assert_eq!(
|
||||
join_module.functions.len(),
|
||||
2,
|
||||
"Expected 2 functions (resolve_entries + loop_step)"
|
||||
);
|
||||
|
||||
let resolve_id = JoinFuncId::new(0);
|
||||
let loop_step_id = JoinFuncId::new(1);
|
||||
|
||||
// resolve_entries 関数の検証
|
||||
let resolve_func = join_module.functions.get(&resolve_id)
|
||||
let resolve_func = join_module
|
||||
.functions
|
||||
.get(&resolve_id)
|
||||
.expect("resolve_entries function not found");
|
||||
assert_eq!(resolve_func.name, "resolve_entries");
|
||||
assert_eq!(resolve_func.params.len(), 5, "resolve_entries has 5 parameters (entries, n, modules, seen, prefix_init)");
|
||||
assert_eq!(
|
||||
resolve_func.params.len(),
|
||||
5,
|
||||
"resolve_entries has 5 parameters (entries, n, modules, seen, prefix_init)"
|
||||
);
|
||||
|
||||
// loop_step 関数の検証
|
||||
let loop_step_func = join_module.functions.get(&loop_step_id)
|
||||
let loop_step_func = join_module
|
||||
.functions
|
||||
.get(&loop_step_id)
|
||||
.expect("loop_step function not found");
|
||||
assert_eq!(loop_step_func.name, "loop_step");
|
||||
assert_eq!(loop_step_func.params.len(), 6, "loop_step has 6 parameters (entries, n, modules, seen, prefix, i)");
|
||||
assert_eq!(
|
||||
loop_step_func.params.len(),
|
||||
6,
|
||||
"loop_step has 6 parameters (entries, n, modules, seen, prefix, i)"
|
||||
);
|
||||
|
||||
eprintln!("[joinir/stage1_using_resolver] ✅ 自動変換成功(Phase 27.12/27.13)");
|
||||
}
|
||||
@ -117,5 +136,80 @@ fn mir_joinir_stage1_using_resolver_empty_module_returns_none() {
|
||||
let result = lower_stage1_usingresolver_to_joinir(&test_module);
|
||||
|
||||
eprintln!("[joinir/stage1_using_resolver] empty_module test: result is None (expected)");
|
||||
assert!(result.is_none(), "Empty MirModule should return None (target function not found)");
|
||||
assert!(
|
||||
result.is_none(),
|
||||
"Empty MirModule should return None (target function not found)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // 手動実行: generic_case_a トグル検証(stage1_using_resolver minimal)
|
||||
fn mir_joinir_stage1_using_resolver_generic_matches_handwritten() {
|
||||
if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") {
|
||||
eprintln!(
|
||||
"[joinir/stage1_using_resolver] NYASH_JOINIR_EXPERIMENT=1 not set, skipping generic test"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||
|
||||
let test_file = "apps/tests/stage1_usingresolver_minimal.hako";
|
||||
let src = std::fs::read_to_string(test_file)
|
||||
.unwrap_or_else(|_| panic!("Failed to read {}", test_file));
|
||||
|
||||
let ast: ASTNode =
|
||||
NyashParser::parse_from_string(&src).expect("stage1_using_resolver generic: parse failed");
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc
|
||||
.compile(ast)
|
||||
.expect("stage1_using_resolver generic: MIR compile failed");
|
||||
|
||||
fn params_by_name(jm: &JoinModule) -> BTreeMap<String, usize> {
|
||||
jm.functions
|
||||
.values()
|
||||
.map(|f| (f.name.clone(), f.params.len()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Baseline (generic OFF)
|
||||
std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "0");
|
||||
let baseline = lower_stage1_usingresolver_to_joinir(&compiled.module)
|
||||
.expect("baseline stage1_using_resolver lowering failed");
|
||||
|
||||
// Generic ON
|
||||
std::env::set_var("NYASH_JOINIR_LOWER_GENERIC", "1");
|
||||
let generic = lower_stage1_usingresolver_to_joinir(&compiled.module)
|
||||
.expect("generic stage1_using_resolver lowering failed");
|
||||
|
||||
let baseline_params = params_by_name(&baseline);
|
||||
let generic_params = params_by_name(&generic);
|
||||
|
||||
assert_eq!(baseline_params.len(), 2, "baseline should have 2 functions");
|
||||
assert_eq!(generic_params.len(), 2, "generic should have 2 functions");
|
||||
assert_eq!(
|
||||
baseline_params.len(),
|
||||
generic_params.len(),
|
||||
"function count mismatch"
|
||||
);
|
||||
|
||||
let expected_funcs = ["resolve_entries", "loop_step"];
|
||||
for name in expected_funcs {
|
||||
let b_params = baseline_params
|
||||
.get(name)
|
||||
.copied()
|
||||
.unwrap_or_else(|| panic!("baseline missing function {}", name));
|
||||
let g_params = generic_params
|
||||
.get(name)
|
||||
.copied()
|
||||
.unwrap_or_else(|| panic!("generic missing function {}", name));
|
||||
assert_eq!(
|
||||
b_params, g_params,
|
||||
"param count differs for function {}",
|
||||
name
|
||||
);
|
||||
}
|
||||
|
||||
eprintln!("[joinir/stage1_using_resolver] ✅ generic_case_a JoinIR matches baseline");
|
||||
}
|
||||
|
||||
101
src/tests/mir_joinir_stageb_body.rs
Normal file
101
src/tests/mir_joinir_stageb_body.rs
Normal file
@ -0,0 +1,101 @@
|
||||
// mir_joinir_stageb_body.rs
|
||||
// Phase 28: StageBBodyExtractorBox.build_body_src JoinIR 変換テスト
|
||||
//
|
||||
// 目的:
|
||||
// - StageBBodyExtractorBox.build_body_src/2 の Case A ループを JoinIR に落とせることを確認
|
||||
// - Pinned/Carrier/Exit の構造を固定化(Pinned: src/args/n, Carrier: acc/i, Exit: acc)
|
||||
//
|
||||
// 実行条件:
|
||||
// - #[ignore] で手動実行。環境変数 NYASH_JOINIR_EXPERIMENT=1 が必要。
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::lowering::stageb_body::lower_stageb_body_to_joinir;
|
||||
use crate::mir::join_ir::*;
|
||||
use crate::mir::{MirCompiler, ValueId};
|
||||
use crate::parser::NyashParser;
|
||||
|
||||
#[test]
|
||||
#[ignore] // 手動実行用(実験モードのみ)
|
||||
fn mir_joinir_stageb_body_auto_lowering() {
|
||||
if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") {
|
||||
eprintln!(
|
||||
"[joinir/stageb_body] NYASH_JOINIR_EXPERIMENT=1 not set, skipping auto-lowering test"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stage-3 パーサを有効化(local/loop を安全に扱うため)
|
||||
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||
|
||||
let test_file = "apps/tests/stageb_body_extract_minimal.hako";
|
||||
let src = std::fs::read_to_string(test_file)
|
||||
.unwrap_or_else(|_| panic!("Failed to read {}", test_file));
|
||||
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&src).expect("stageb_body: parse failed");
|
||||
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).expect("stageb_body: MIR compile failed");
|
||||
|
||||
eprintln!(
|
||||
"[joinir/stageb_body] MIR module compiled, {} functions",
|
||||
compiled.module.functions.len()
|
||||
);
|
||||
|
||||
let join_module = lower_stageb_body_to_joinir(&compiled.module)
|
||||
.expect("StageBBodyExtractorBox.build_body_src should lower to JoinIR");
|
||||
|
||||
eprintln!("[joinir/stageb_body] JoinIR module generated:");
|
||||
eprintln!("{:#?}", join_module);
|
||||
|
||||
assert_eq!(join_module.functions.len(), 2, "Expected entry + loop_step");
|
||||
|
||||
let entry_id = JoinFuncId::new(0);
|
||||
let loop_id = JoinFuncId::new(1);
|
||||
|
||||
let entry_func = join_module
|
||||
.functions
|
||||
.get(&entry_id)
|
||||
.expect("build_body_src function not found");
|
||||
assert_eq!(entry_func.name, "build_body_src");
|
||||
assert_eq!(
|
||||
entry_func.params.len(),
|
||||
2,
|
||||
"build_body_src should take (src, args)"
|
||||
);
|
||||
|
||||
let loop_func = join_module
|
||||
.functions
|
||||
.get(&loop_id)
|
||||
.expect("loop_step function not found");
|
||||
assert_eq!(loop_func.name, "loop_step");
|
||||
assert_eq!(
|
||||
loop_func.params.len(),
|
||||
5,
|
||||
"loop_step should take (src, args, n, acc, i)"
|
||||
);
|
||||
|
||||
eprintln!("[joinir/stageb_body] ✅ 自動変換成功(Phase 28)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mir_joinir_stageb_body_type_sanity() {
|
||||
let entry_id = JoinFuncId::new(42);
|
||||
let f = JoinFunction::new(
|
||||
entry_id,
|
||||
"stageb_body_test".to_string(),
|
||||
vec![ValueId(1), ValueId(2)],
|
||||
);
|
||||
assert_eq!(f.id, entry_id);
|
||||
assert_eq!(f.name, "stageb_body_test");
|
||||
assert_eq!(f.params.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mir_joinir_stageb_body_empty_module_returns_none() {
|
||||
use crate::mir::MirModule;
|
||||
let test_module = MirModule::new("empty".to_string());
|
||||
let result = lower_stageb_body_to_joinir(&test_module);
|
||||
eprintln!("[joinir/stageb_body] empty_module test => {:?}", result);
|
||||
assert!(result.is_none());
|
||||
}
|
||||
105
src/tests/mir_joinir_stageb_funcscanner.rs
Normal file
105
src/tests/mir_joinir_stageb_funcscanner.rs
Normal file
@ -0,0 +1,105 @@
|
||||
// mir_joinir_stageb_funcscanner.rs
|
||||
// Phase 28: StageBFuncScannerBox.scan_all_boxes JoinIR 変換テスト
|
||||
//
|
||||
// 目的:
|
||||
// - StageBFuncScannerBox.scan_all_boxes/1 の Case A ループを JoinIR に落とせることを確認
|
||||
// - Pinned/Carrier/Exit の構造を固定化(Pinned: src/n, Carrier: defs/i, Exit: defs)
|
||||
//
|
||||
// 実行条件:
|
||||
// - #[ignore] で手動実行。環境変数 NYASH_JOINIR_EXPERIMENT=1 が必要。
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::lowering::stageb_funcscanner::lower_stageb_funcscanner_to_joinir;
|
||||
use crate::mir::join_ir::*;
|
||||
use crate::mir::{MirCompiler, ValueId};
|
||||
use crate::parser::NyashParser;
|
||||
|
||||
#[test]
|
||||
#[ignore] // 手動実行用(実験モードのみ)
|
||||
fn mir_joinir_stageb_funcscanner_auto_lowering() {
|
||||
if std::env::var("NYASH_JOINIR_EXPERIMENT").ok().as_deref() != Some("1") {
|
||||
eprintln!("[joinir/stageb_funcscanner] NYASH_JOINIR_EXPERIMENT=1 not set, skipping auto-lowering test");
|
||||
return;
|
||||
}
|
||||
|
||||
// Stage-3 パーサを有効化
|
||||
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||
|
||||
let test_file = "apps/tests/stageb_funcscanner_scan_boxes_minimal.hako";
|
||||
let src = std::fs::read_to_string(test_file)
|
||||
.unwrap_or_else(|_| panic!("Failed to read {}", test_file));
|
||||
|
||||
let ast: ASTNode =
|
||||
NyashParser::parse_from_string(&src).expect("stageb_funcscanner: parse failed");
|
||||
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc
|
||||
.compile(ast)
|
||||
.expect("stageb_funcscanner: MIR compile failed");
|
||||
|
||||
eprintln!(
|
||||
"[joinir/stageb_funcscanner] MIR module compiled, {} functions",
|
||||
compiled.module.functions.len()
|
||||
);
|
||||
|
||||
let join_module = lower_stageb_funcscanner_to_joinir(&compiled.module)
|
||||
.expect("StageBFuncScannerBox.scan_all_boxes should lower to JoinIR");
|
||||
|
||||
eprintln!("[joinir/stageb_funcscanner] JoinIR module generated:");
|
||||
eprintln!("{:#?}", join_module);
|
||||
|
||||
assert_eq!(join_module.functions.len(), 2, "Expected entry + loop_step");
|
||||
|
||||
let entry_id = JoinFuncId::new(0);
|
||||
let loop_id = JoinFuncId::new(1);
|
||||
|
||||
let entry_func = join_module
|
||||
.functions
|
||||
.get(&entry_id)
|
||||
.expect("scan_all_boxes function not found");
|
||||
assert_eq!(entry_func.name, "scan_all_boxes");
|
||||
assert_eq!(
|
||||
entry_func.params.len(),
|
||||
1,
|
||||
"scan_all_boxes should take (src)"
|
||||
);
|
||||
|
||||
let loop_func = join_module
|
||||
.functions
|
||||
.get(&loop_id)
|
||||
.expect("loop_step function not found");
|
||||
assert_eq!(loop_func.name, "loop_step");
|
||||
assert_eq!(
|
||||
loop_func.params.len(),
|
||||
4,
|
||||
"loop_step should take (src, n, defs, i)"
|
||||
);
|
||||
|
||||
eprintln!("[joinir/stageb_funcscanner] ✅ 自動変換成功(Phase 28)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mir_joinir_stageb_funcscanner_type_sanity() {
|
||||
let entry_id = JoinFuncId::new(24);
|
||||
let f = JoinFunction::new(
|
||||
entry_id,
|
||||
"stageb_funcscanner_test".to_string(),
|
||||
vec![ValueId(1)],
|
||||
);
|
||||
assert_eq!(f.id, entry_id);
|
||||
assert_eq!(f.name, "stageb_funcscanner_test");
|
||||
assert_eq!(f.params.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mir_joinir_stageb_funcscanner_empty_module_returns_none() {
|
||||
use crate::mir::MirModule;
|
||||
let test_module = MirModule::new("empty".to_string());
|
||||
let result = lower_stageb_funcscanner_to_joinir(&test_module);
|
||||
eprintln!(
|
||||
"[joinir/stageb_funcscanner] empty_module test => {:?}",
|
||||
result
|
||||
);
|
||||
assert!(result.is_none());
|
||||
}
|
||||
@ -82,4 +82,3 @@ fn mir_loopform_nested_region_verify() {
|
||||
}
|
||||
teardown_stage3_env();
|
||||
}
|
||||
|
||||
|
||||
@ -104,11 +104,7 @@ fn mir_stage1_cli_emit_program_min_exec_hits_type_error() {
|
||||
let cr = mc.compile(ast).expect("compile");
|
||||
|
||||
// Optional: scan for Compare::Ge instructions to locate suspicious comparisons
|
||||
if std::env::var("NYASH_STAGE1_SCAN_GE")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
if std::env::var("NYASH_STAGE1_SCAN_GE").ok().as_deref() == Some("1") {
|
||||
for (fname, func) in cr.module.functions.iter() {
|
||||
for (bb_id, bb) in func.blocks.iter() {
|
||||
for inst in bb.instructions.iter() {
|
||||
|
||||
@ -40,8 +40,7 @@ fn ensure_stage3_env() {
|
||||
/// Stage‑1 CLI 全体を読み込まずに、StringHelpers 内部の `.length()` 正規化だけを確認する。
|
||||
fn stage1_staticcompiler_fixture_src() -> String {
|
||||
// StringHelpers 本体を直接バンドルして using 依存を排除。
|
||||
let string_helpers =
|
||||
include_str!("../../lang/src/shared/common/string_helpers.hako");
|
||||
let string_helpers = include_str!("../../lang/src/shared/common/string_helpers.hako");
|
||||
let test_main_src = r#"
|
||||
using lang.src.shared.common.string_helpers as StringHelpers
|
||||
|
||||
|
||||
@ -743,8 +743,6 @@ static box Stage1UsingResolverModulesMapContinueBreakLookup {
|
||||
for e in &errors {
|
||||
eprintln!("[rust-mir-verify] {}", e);
|
||||
}
|
||||
panic!(
|
||||
"MIR verification failed for Stage1UsingResolverModulesMapContinueBreakLookup"
|
||||
);
|
||||
panic!("MIR verification failed for Stage1UsingResolverModulesMapContinueBreakLookup");
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,11 +76,7 @@ fn mir_static_main_box_executes_void_path_with_guard() {
|
||||
|
||||
// Just ensure the symbol exists; methodization may optimize away the actual call.
|
||||
assert!(
|
||||
compiled
|
||||
.module
|
||||
.functions
|
||||
.keys()
|
||||
.any(|k| k == "Main._nop/0"),
|
||||
compiled.module.functions.keys().any(|k| k == "Main._nop/0"),
|
||||
"Main._nop/0 should remain defined"
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user