Span trace utilities and runner source hint

This commit is contained in:
nyash-codex
2025-11-24 14:17:02 +09:00
parent 3154903121
commit 466e636af6
106 changed files with 4597 additions and 958 deletions

View File

@ -12,16 +12,17 @@
- **25.x**: Stage0/Stage1/StageB / Selfhost ラインのブートストラップと LoopForm v2 / LoopSSA v2 まわりの整備。
- **25.1 系**: StageB / Stage1 / selfhost 向けに、Rust MIR / LoopForm v2 / LoopSSA v2 を段階的に整える長期ライン。
- **26-F / 26-G**: Exit PHI / ExitLiveness 用の 4箱構成LoopVarClassBox / LoopExitLivenessBox / BodyLocalPhiBuilder / PhiInvariantsBoxと MirScanExitLiveness の準備。
- **26-H / 27.xNew**: JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim/Stage1 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 PatternMIR-based lowering に移行し、27.12/27.13Stage1 UsingResolver minimal loop も同じ型に乗せ、27.14 では FuncScanner.append_defs 用の loweringminimal .hakoauto_lowering テストまで整備済み。短期フェーズ残りは JoinIR→Rust VM ブリッジの最小実装と、それを使った skip_ws/trim/Stage1 minimal あたりの A/B テスト整備)。
- **26-H / 27.xNew**: JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim/Stage1 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/Stage1 UsingResolver minimal/FuncScanner.append_defs minimal/StageB minimal を Shared Builder PatternMIR-based lowering に移行し、ValueId 範囲管理も一元化。27.8-27.11/27.13/27.15JoinIR Runner を JoinValue↔VMValue 変換+`MirInterpreter::execute_box_call` ラッパ経由で Rust VM の BoxCall 意味論と統合し、skip_ws/trim の JoinIR Runner スタンドアロン実行が安定GC/BoxRef も Arc ベースで統合済み。Phase 27-shortterm の S1〜S5.4 は完了、Phase 28-midterm では per-loop lowering を増やさず、LoopFormLoopVarClassBoxLoopExitLivenessBox を入力にした「汎用 Loop→JoinIR ロワー」に畳み込んでいく方針に切り替え済みjoin-ir.md に JoinIR ロワーが“やらないこと”チェックリストを明記)。
- Rust 側:
- LoopForm v2 + ControlForm + Conservative PHI は、代表テストStage1 UsingResolver / StageB 最小ループ)ではほぼ安定。
- 静的メソッド呼び出し規約と `continue` 絡みの PHI は 25.1m までで根治済み。
- .hako 側:
- StageB コンパイラ本体 / LoopSSA v2 / BreakFinderBox / PhiInjectorBox はまだ部分実装。
- JSON v0 / selfhost ルートは Rust 側の LoopForm v2 規約に追いつかせる必要がある。
- JSON v0 / selfhost ルートは Rust 側の LoopForm v2 規約に追いつかせる必要がある。StageB selfhost 経路は依然として Program JSON が取れていないが、直近の実行では `Unknown method 'main' on InstanceBox` は再現しておらず、別の箇所で落ちている可能性が高い。Stage1 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 なら再発時の位置特定がしやすいStage1 CLI の現在の MIR には Span が無く行番号は未表示)。
- StageB / FuncScanner ライン:
- Phase 25.3 をクローズし、`stageb_fib_program_defs_canary_vm.sh` が緑(`defs``TestBox.fib/Main.main`、fib.body.body[*] に `Loop`)。
- StageB は block パーサ優先 + defs を Block 包みで構造化。次手: Stage1 UsingResolver ループの Region+next_i 揃え / Stage1 CLI program-json selfhost 準備。
- Stage1 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 未生成。
---

View 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 }
}

View 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 }
}

View File

@ -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_wsCase 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` / Stage1 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 呼び出し | φ 不要(引数で値を渡す) |

View File

@ -2,6 +2,14 @@
Status: design+partial implementationStage1 ビルド導線の初期版まで)
## Stage1 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 パーサ前処理を削る検討、③ StageB 自前ビルドは `Unknown method 'main' on InstanceBox` で失敗中StageBDriverBox 呼び出しが壊れている)。
- Rust VM 側で `vm step budget exceeded`**fn / bb / last_inst 情報** SpanMIR に付いていれば .hako 行番号)を付与。今回の Stage1 CLI 実行では `fn=ParserBox.parse_program2/1 ... (lang/src/runner/stage1_cli.hako:1:1)` まで出力されたSpan は通ったが行位置はまだ粗い)。
## 25.1 サブフェーズの整理a〜e 概要)
- **25.1a — Stage1 Build Hotfix配線**

View 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 のみ。
- 状態: Stage1 CLI の MIR には Span 未付与なので行番号はまだ出ていないが、Span 付き MIR なら `... (file.hako:line:col)` まで表示できる。

View File

@ -0,0 +1,97 @@
# Phase 25.1 — Stage1 CLI / BuildBox ループ & Box 依存分析メモ
目的
- `Stage1 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` は Stage3 parser の statement/expression を全部受け持つため、
実装によっては **指数的** にブロック数・ステップ数が増え得る。
- FileBox は I/O bound なので、budget ではなくタイムアウト側のリスクが大きい。
## 3. Box 依存度マトリックス
Stage1 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 は Stage3 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. 方針メモ
- Stage1 CLI の「ブリッジまでは動いているが、そのあとで止まる」という現象は、
- StageB/BuildBox 経由の Program JSON emit
- ParserBox / FileBox のループ
のどこに問題があるかを 1 ループずつ切り出していけば、局所化できる見込み。
- joinIR 側とは独立に、このファイルでは「Stage1 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)`
- Stage1 CLI の現行 MIR には Span が載っていないため、いまは fn/bb/inst だけが出る(`apps/tests/minimal_ssa_skip_ws.hako` 実行で確認。Span 付与を通せば .hako 行まで出る想定。

View File

@ -0,0 +1,75 @@
# Phase 25.1 — StageB 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 受け側
- 目的:
- StageB selfhost の MIR で、「どの global / boxcall 名で StageBDriverBox.main を呼んでいるか」を確認する。
- 手順の目安:
- StageB ランチャの 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 ラインとは独立して、この問題は「StageB driver / InstanceBox / naming」の層で解決する。
- まずは:
- どの InstanceBox関数名の組み合わせで Unknown が出ているかをログで観測。
- `.hako` 側の StageBDriverBox 定義と、Rust 側のメソッド名解決ロジックの差分を見る。
- 修正自体(命名・正規化の調整)は Phase 25.1 の別サブフェーズで扱う前提として、
このファイルは「現状の観測結果と差分の候補」を残しておくためのメモだよ。

View File

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

View File

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

View File

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

View File

@ -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 で廃止済み。

View File

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

View File

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

View File

@ -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",
"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",
];
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &["add", "sub", "mul", "div", "and", "or", "eq", "ne"];

View File

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

View File

@ -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)
@ -243,7 +249,7 @@ impl MirBuilder {
current_block: None,
value_gen: ValueIdGenerator::new(),
block_gen: BasicBlockIdGenerator::new(),
compilation_context: None, // 箱理論: デフォルトは従来モード
compilation_context: None, // 箱理論: デフォルトは従来モード
variable_map: BTreeMap::new(), // Phase 25.1: 決定性確保
pending_phis: Vec::new(),
value_origin_newbox: BTreeMap::new(), // Phase 25.1: 決定性確保
@ -253,7 +259,7 @@ impl MirBuilder {
field_origin_class: HashMap::new(),
field_origin_by_box: HashMap::new(),
value_types: BTreeMap::new(), // Phase 25.1: 決定性確保
value_kinds: HashMap::new(), // Phase 26-A: ValueId型安全化
value_kinds: HashMap::new(), // Phase 26-A: ValueId型安全化
current_slot_registry: None,
type_registry: type_registry::TypeRegistry::new(),
plugin_method_sigs,
@ -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

View File

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

View File

@ -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();

View File

@ -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,
@ -186,7 +183,7 @@ impl UnifiedCallEmitterBox {
if methodize_on {
if let Callee::Global(ref name) = callee {
let name_clone = name.clone(); // Clone to avoid borrow checker issues
// 🎯 Phase 21.7++ Phase 3: StaticMethodId SSOT 実装
// 🎯 Phase 21.7++ Phase 3: StaticMethodId SSOT 実装
if let Some(id) = crate::mir::naming::StaticMethodId::parse(&name_clone) {
// Check if arity matches provided args (arity may be None if not specified)
let arity_matches = id.arity.map_or(true, |a| a == args.len());
@ -194,26 +191,31 @@ 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) {
existing
} else {
// Create new singleton instance
let singleton_id = builder.next_value_id();
builder.emit_instruction(MirInstruction::NewBox {
dst: singleton_id,
box_type: box_name.to_string(),
args: Vec::new(), // Static box singleton, no constructor args
})?;
// Register type information
builder.value_types.insert(
singleton_id,
crate::mir::MirType::Box(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);
singleton_id
};
let singleton =
if let Some(&existing) = builder.static_box_singletons.get(box_name) {
existing
} else {
// Create new singleton instance
let singleton_id = builder.next_value_id();
builder.emit_instruction(MirInstruction::NewBox {
dst: singleton_id,
box_type: box_name.to_string(),
args: Vec::new(), // Static box singleton, no constructor args
})?;
// Register type information
builder.value_types.insert(
singleton_id,
crate::mir::MirType::Box(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);
singleton_id
};
// Convert to Method call
callee = Callee::Method {
@ -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") {

View File

@ -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"
);

View File

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

View File

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

View File

@ -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 itFunctionEmissionBox を使用)
let one = crate::mir::function_emission::emit_const_integer(&mut f, entry, 1);
crate::mir::function_emission::emit_return_value(&mut f, entry, one);

View File

@ -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を伝播する。

View File

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

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -16,7 +16,7 @@ impl MirBuilder {
then_exit_block_opt: Option<super::BasicBlockId>,
else_exit_block_opt: Option<super::BasicBlockId>,
pre_if_snapshot: &BTreeMap<String, super::ValueId>, // Phase 25.1: BTreeMap化
then_map_end: &BTreeMap<String, super::ValueId>, // Phase 25.1: BTreeMap化
then_map_end: &BTreeMap<String, super::ValueId>, // Phase 25.1: BTreeMap化
else_map_end_opt: &Option<BTreeMap<String, super::ValueId>>, // Phase 25.1: BTreeMap化
skip_var: Option<&str>,
) -> Result<(), String> {

View File

@ -159,7 +159,7 @@ impl<'a> PhiMergeHelper<'a> {
pub fn merge_all_vars(
&mut self,
pre_if_snapshot: &BTreeMap<String, ValueId>, // Phase 25.1: BTreeMap化
then_map_end: &BTreeMap<String, ValueId>, // Phase 25.1: BTreeMap化
then_map_end: &BTreeMap<String, ValueId>, // Phase 25.1: BTreeMap化
else_map_end_opt: &Option<BTreeMap<String, ValueId>>, // Phase 25.1: BTreeMap化
skip_var: Option<&str>,
) -> Result<(), String> {

View File

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

View File

@ -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(());

View File

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

View 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
}

View 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)
}

View File

@ -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 loweringpublic 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,
@ -99,9 +102,9 @@ fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Optio
// loop_step(dst, defs_box, n, i_init)
// }
let entry_id = JoinFuncId::new(0);
let dst_param = vid::entry(0); // 9000
let defs_box_param = vid::entry(1); // 9001
let n_param = vid::entry(2); // 9002
let dst_param = vid::entry(0); // 9000
let defs_box_param = vid::entry(1); // 9001
let n_param = vid::entry(2); // 9002
let mut entry_func = JoinFunction::new(
entry_id,
@ -109,7 +112,7 @@ fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Optio
vec![dst_param, defs_box_param, n_param],
);
let i_init = vid::entry(10); // 9010
let i_init = vid::entry(10); // 9010
// i_init = 0
entry_func.body.push(JoinInst::Compute(MirLikeInst::Const {
@ -134,14 +137,14 @@ fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Optio
// - Pinned: dst (ArrayBox), defs_box (ArrayBox), n (Integer)
// - Carrier: i (Integer)
// - Exit: none (void return)
let dst_loop = vid::loop_step(0); // 10000 - Pinned
let dst_loop = vid::loop_step(0); // 10000 - Pinned
let defs_box_loop = vid::loop_step(1); // 10001 - Pinned
let n_loop = vid::loop_step(2); // 10002 - Pinned
let i_loop = vid::loop_step(3); // 10003 - Carrier
let n_loop = vid::loop_step(2); // 10002 - Pinned
let i_loop = vid::loop_step(3); // 10003 - Carrier
let _header_shape = LoopHeaderShape::new_manual(
vec![dst_loop, defs_box_loop, n_loop], // Pinned
vec![i_loop], // Carrier
vec![dst_loop, defs_box_loop, n_loop], // Pinned
vec![i_loop], // Carrier
);
// loop_step 関数:
@ -158,59 +161,69 @@ fn build_funcscanner_append_defs_joinir(module: &crate::mir::MirModule) -> Optio
vec![dst_loop, defs_box_loop, n_loop, i_loop],
);
let cmp_result = vid::loop_step(10); // 10010
let item_value = vid::loop_step(11); // 10011
let next_i = vid::loop_step(12); // 10012
let const_1 = vid::loop_step(13); // 10013
let cmp_result = vid::loop_step(10); // 10010
let item_value = vid::loop_step(11); // 10011
let next_i = vid::loop_step(12); // 10012
let const_1 = vid::loop_step(13); // 10013
// cmp_result = (i >= n)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_result,
op: CompareOp::Ge,
lhs: i_loop,
rhs: n_loop,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_result,
op: CompareOp::Ge,
lhs: i_loop,
rhs: n_loop,
}));
// Phase 27.14: Exit φ の意味を LoopExitShape で明示
// FuncScanner _append_defs ループ脱出時は void 返却dst は破壊的変更済み)
let _exit_shape = LoopExitShape::new_manual(vec![]); // exit_args = [] (void)
let _exit_shape = LoopExitShape::new_manual(vec![]); // exit_args = [] (void)
// if i >= n { return } (void)
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(0),
args: vec![], // ← LoopExitShape.exit_args に対応 (void)
args: vec![], // ← LoopExitShape.exit_args に対応 (void)
cond: Some(cmp_result),
});
// item = defs_box.get(i)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(item_value),
box_name: "ArrayBox".to_string(),
method: "get".to_string(),
args: vec![defs_box_loop, i_loop],
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(item_value),
box_name: "ArrayBox".to_string(),
method: "get".to_string(),
args: vec![defs_box_loop, i_loop],
}));
// dst.push(item) - 破壊的変更(戻り値なし)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: None, // push は戻り値なし
box_name: "ArrayBox".to_string(),
method: "push".to_string(),
args: vec![dst_loop, item_value],
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: None, // push は戻り値なし
box_name: "ArrayBox".to_string(),
method: "push".to_string(),
args: vec![dst_loop, item_value],
}));
// const_1 = 1
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1,
value: ConstValue::Integer(1),
}));
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 {
dst: next_i,
op: BinOpKind::Add,
lhs: i_loop,
rhs: const_1,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: next_i,
op: BinOpKind::Add,
lhs: i_loop,
rhs: const_1,
}));
// loop_step(dst, defs_box, n, next_i) - tail recursion
loop_step_func.body.push(JoinInst::Call {
@ -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");

View File

@ -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,30 +90,38 @@ 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 {
dst: const_empty,
value: ConstValue::String("".to_string()),
}));
trim_main_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: str_val,
lhs: const_empty,
rhs: s_param,
op: BinOpKind::Add,
}));
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 {
dst: str_val,
lhs: const_empty,
rhs: s_param,
op: BinOpKind::Add,
}));
// n = str.length()
trim_main_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(n_val),
box_name: "StringBox".to_string(),
method: "length".to_string(),
args: vec![str_val],
}));
trim_main_func
.body
.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(n_val),
box_name: "StringBox".to_string(),
method: "length".to_string(),
args: vec![str_val],
}));
// const 0
trim_main_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_zero,
value: ConstValue::Integer(0),
}));
trim_main_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_zero,
value: ConstValue::Integer(0),
}));
// b = skip_leading_whitespace(str, 0, n)
let skip_leading_id = JoinFuncId::new(2);
@ -124,12 +133,14 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
});
// e_init = n (コピー)
trim_main_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: e_init,
op: BinOpKind::Add,
lhs: n_val,
rhs: const_zero,
}));
trim_main_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: e_init,
op: BinOpKind::Add,
lhs: n_val,
rhs: const_zero,
}));
// loop_step(str, b, e_init) -> 戻り値をそのまま返す
let loop_step_id = JoinFuncId::new(1);
@ -147,13 +158,13 @@ fn build_funcscanner_trim_joinir(module: &crate::mir::MirModule) -> Option<JoinM
// trim ループの場合:
// - Pinned: str (文字列), b (開始位置) - ループ中で不変
// - Carrier: e (終了位置) - ループで後ろから前へ更新される
let str_loop = ValueId(6000); // Pinned
let b_loop = ValueId(6001); // Pinned
let e_loop = ValueId(6002); // Carrier
let str_loop = ValueId(6000); // Pinned
let b_loop = ValueId(6001); // Pinned
let e_loop = ValueId(6002); // Carrier
let _header_shape = LoopHeaderShape::new_manual(
vec![str_loop, b_loop], // Pinned: str, b
vec![e_loop], // Carrier: e
vec![str_loop, b_loop], // Pinned: str, b
vec![e_loop], // Carrier: e
);
// 将来: to_loop_step_params() で [str, b, e] (pinned..., carriers...) を生成する設計。
// 現在は既存 JoinIR テストとの互換性のため、手動で [str, b, e] の順を維持している。
@ -167,73 +178,87 @@ 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 {
dst: cond,
lhs: e_loop,
rhs: b_loop,
op: CompareOp::Gt,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cond,
lhs: e_loop,
rhs: b_loop,
op: CompareOp::Gt,
}));
// bool false (共通)
let bool_false = ValueId(6019);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: bool_false,
value: ConstValue::Bool(false),
}));
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 {
dst: Some(trimmed_base),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![str_loop, b_loop, e_loop],
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(trimmed_base),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![str_loop, b_loop, e_loop],
}));
// cond_is_false = (cond == false)
let cond_is_false = ValueId(6020);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cond_is_false,
lhs: cond,
rhs: bool_false,
op: CompareOp::Eq,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cond_is_false,
lhs: cond,
rhs: bool_false,
op: CompareOp::Eq,
}));
// Phase 27.5: Exit φ の意味を LoopExitShape で明示Option A
// trim のループ脱出時は e の値で substring(b, e) を計算済み
let _exit_shape_trim = LoopExitShape::new_manual(vec![e_loop]); // exit_args = [e] (Option A)
// 実装上は既に trimmed_base = substring(b, e) を計算済みで、その結果を返している
let _exit_shape_trim = LoopExitShape::new_manual(vec![e_loop]); // exit_args = [e] (Option A)
// 実装上は既に trimmed_base = substring(b, e) を計算済みで、その結果を返している
// if !(e > b) { return substring(b, e) }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(0),
args: vec![trimmed_base], // ← substring(b, e) の結果
args: vec![trimmed_base], // ← substring(b, e) の結果
cond: Some(cond_is_false),
});
// const 1
let const_1 = ValueId(6005);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1,
value: ConstValue::Integer(1),
}));
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 {
dst: e_minus_1,
lhs: e_loop,
rhs: const_1,
op: BinOpKind::Sub,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: e_minus_1,
lhs: e_loop,
rhs: const_1,
op: BinOpKind::Sub,
}));
let ch = ValueId(6007);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(ch),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![str_loop, e_minus_1, e_loop],
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(ch),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![str_loop, e_minus_1, e_loop],
}));
// is_space = (ch == " " || ch == "\\t" || ch == "\\n" || ch == "\\r")
let cmp_space = ValueId(6008);
@ -246,101 +271,127 @@ 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 {
dst: const_space,
value: ConstValue::String(" ".to_string()),
}));
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 {
dst: const_space,
value: ConstValue::String(" ".to_string()),
}));
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 {
dst: const_tab,
value: ConstValue::String("\\t".to_string()),
}));
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 {
dst: const_tab,
value: ConstValue::String("\\t".to_string()),
}));
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 {
dst: const_newline,
value: ConstValue::String("\\n".to_string()),
}));
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 {
dst: const_newline,
value: ConstValue::String("\\n".to_string()),
}));
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 {
dst: const_cr,
value: ConstValue::String("\\r".to_string()),
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_cr,
lhs: ch,
rhs: const_cr,
op: CompareOp::Eq,
}));
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 {
dst: cmp_cr,
lhs: ch,
rhs: const_cr,
op: CompareOp::Eq,
}));
// OR chain: (cmp_space || cmp_tab) || cmp_newline || cmp_cr
let or1 = ValueId(6016);
let or2 = ValueId(6017);
let is_space = ValueId(6018);
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 {
dst: or1,
lhs: cmp_space,
rhs: cmp_tab,
op: BinOpKind::Or,
}));
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 {
dst: or2,
lhs: or1,
rhs: cmp_newline,
op: BinOpKind::Or,
}));
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: is_space,
lhs: or2,
rhs: cmp_cr,
op: BinOpKind::Or,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: is_space,
lhs: or2,
rhs: cmp_cr,
op: BinOpKind::Or,
}));
// is_space_false = (is_space == false)
let is_space_false = ValueId(6021);
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: is_space_false,
lhs: is_space,
rhs: bool_false,
op: CompareOp::Eq,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: is_space_false,
lhs: is_space,
rhs: bool_false,
op: CompareOp::Eq,
}));
// Phase 27.5: 2箇所目の exit パス(同じく exit_args = [e], Option A
// if !is_space { return substring(b, e) }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(1),
args: vec![trimmed_base], // ← substring(b, e) の結果1箇所目と同じ
args: vec![trimmed_base], // ← substring(b, e) の結果1箇所目と同じ
cond: Some(is_space_false),
});
// 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 {
dst: e_next,
lhs: e_loop,
rhs: const_1,
op: BinOpKind::Sub,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: e_next,
lhs: e_loop,
rhs: const_1,
op: BinOpKind::Sub,
}));
loop_step_func.body.push(JoinInst::Call {
func: loop_step_id, // 再帰呼び出し
@ -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");

File diff suppressed because it is too large Load Diff

View 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,
})
}

View File

@ -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,18 +87,22 @@ 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 {
dst: const_2,
value: ConstValue::Integer(2),
}));
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 {
dst: cmp_result,
op: CompareOp::Ge,
lhs: i_param,
rhs: const_2,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_result,
op: CompareOp::Ge,
lhs: i_param,
rhs: const_2,
}));
// if cmp_result { ret i } else { loop_step(i+1) }
// Phase 26-H 簡略化: 分岐はせず両方の経路を示す
@ -109,18 +113,22 @@ 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 {
dst: const_1,
value: ConstValue::Integer(1),
}));
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 {
dst: i_plus_1,
op: BinOpKind::Add,
lhs: i_param,
rhs: const_1,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: i_plus_1,
op: BinOpKind::Add,
lhs: i_param,
rhs: const_1,
}));
// loop_step(i + 1) (continue path)
loop_step_func.body.push(JoinInst::Call {
@ -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)
}

View File

@ -14,16 +14,24 @@
//! - `funcscanner_append_defs.rs`: FuncScannerBox._append_defs/2 の配列結合 loweringPhase 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;

View File

@ -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();
@ -100,13 +104,13 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
// skip_ws ループの場合:
// - Pinned: s (文字列), n (長さ) - ループ中で不変
// - Carrier: i (現在位置) - ループで更新される
let s_loop = ValueId(4000); // Pinned
let i_loop = ValueId(4001); // Carrier
let n_loop = ValueId(4002); // Pinned
let s_loop = ValueId(4000); // Pinned
let i_loop = ValueId(4001); // Carrier
let n_loop = ValueId(4002); // Pinned
let _header_shape = LoopHeaderShape::new_manual(
vec![s_loop, n_loop], // Pinned: s, n
vec![i_loop], // Carrier: i
vec![s_loop, n_loop], // Pinned: s, n
vec![i_loop], // Carrier: i
);
// 将来: LoopHeaderShape.to_loop_step_params() は [pinned..., carriers...] の順を返す。
// 現在は既存 JoinIR テストとの互換性のため、手動で [s, i, n] の順を維持している。
@ -115,7 +119,7 @@ fn build_skip_ws_joinir(module: &crate::mir::MirModule) -> Option<JoinModule> {
let mut loop_step_func = JoinFunction::new(
loop_step_id,
"loop_step".to_string(),
vec![s_loop, i_loop, n_loop], // [pinned, carrier, pinned] の順(現行実装)
vec![s_loop, i_loop, n_loop], // [pinned, carrier, pinned] の順(現行実装)
);
let cmp1_result = ValueId(4003);
@ -128,79 +132,95 @@ 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 {
dst: cmp1_result,
op: CompareOp::Ge,
lhs: i_loop,
rhs: n_loop,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp1_result,
op: CompareOp::Ge,
lhs: i_loop,
rhs: n_loop,
}));
// Phase 27.5: Exit φ の意味を LoopExitShape で明示
// skip_ws のループ脱出時は i の値だけを返す(先頭空白の文字数)
let _exit_shape = LoopExitShape::new_manual(vec![i_loop]); // exit_args = [i]
let _exit_shape = LoopExitShape::new_manual(vec![i_loop]); // exit_args = [i]
// if i >= n { return i }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(0),
args: vec![i_loop], // ← LoopExitShape.exit_args に対応
args: vec![i_loop], // ← LoopExitShape.exit_args に対応
cond: Some(cmp1_result),
});
// const 1
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1,
value: ConstValue::Integer(1),
}));
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 {
dst: i_plus_1,
op: BinOpKind::Add,
lhs: i_loop,
rhs: const_1,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: i_plus_1,
op: BinOpKind::Add,
lhs: i_loop,
rhs: const_1,
}));
// ch = s.substring(i, i + 1)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(ch),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![s_loop, i_loop, i_plus_1],
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(ch),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![s_loop, i_loop, i_plus_1],
}));
// const " " (space)
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::Const {
dst: const_space,
value: ConstValue::String(" ".to_string()),
}));
// cmp2_result = (ch == " ")
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp2_result,
op: CompareOp::Eq,
lhs: ch,
rhs: const_space,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp2_result,
op: CompareOp::Eq,
lhs: ch,
rhs: const_space,
}));
// bool false (for negation)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: bool_false,
value: ConstValue::Bool(false),
}));
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 {
dst: cmp2_is_false,
op: CompareOp::Eq,
lhs: cmp2_result,
rhs: bool_false,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp2_is_false,
op: CompareOp::Eq,
lhs: cmp2_result,
rhs: bool_false,
}));
// Phase 27.5: 2箇所目の exit パス(同じく exit_args = [i]
// if ch != " " { return i }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(1),
args: vec![i_loop], // ← LoopExitShape.exit_args に対応1箇所目と同じ
args: vec![i_loop], // ← LoopExitShape.exit_args に対応1箇所目と同じ
cond: Some(cmp2_is_false),
});
@ -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,
)
}

View File

@ -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 loweringpublic 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();
@ -98,31 +106,46 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option<
// loop_step(entries, n, modules, seen, prefix_init, i_init)
// }
let resolve_id = JoinFuncId::new(0);
let entries_param = vid::entry(0); // 7000
let n_param = vid::entry(1); // 7001
let modules_param = vid::entry(2); // 7002
let seen_param = vid::entry(3); // 7003
let prefix_init_param = vid::entry(4); // 7004
let entries_param = vid::entry(0); // 7000
let n_param = vid::entry(1); // 7001
let modules_param = vid::entry(2); // 7002
let seen_param = vid::entry(3); // 7003
let prefix_init_param = vid::entry(4); // 7004
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
let i_init = vid::entry(10); // 7010
// i_init = 0
resolve_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: i_init,
value: ConstValue::Integer(0),
}));
resolve_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: i_init,
value: ConstValue::Integer(0),
}));
// loop_step(entries, n, modules, seen, prefix_init, i_init)
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,
});
@ -135,16 +158,16 @@ fn build_stage1_using_resolver_joinir(module: &crate::mir::MirModule) -> Option<
// - Pinned: entries (ArrayBox), n (Integer), modules (MapBox), seen (MapBox)
// - Carrier: prefix (String), i (Integer)
// - Exit: prefix (String)
let entries_loop = vid::loop_step(0); // 8000 - Pinned
let n_loop = vid::loop_step(1); // 8001 - Pinned
let modules_loop = vid::loop_step(2); // 8002 - Pinned
let seen_loop = vid::loop_step(3); // 8003 - Pinned
let prefix_loop = vid::loop_step(4); // 8004 - Carrier
let i_loop = vid::loop_step(5); // 8005 - Carrier
let entries_loop = vid::loop_step(0); // 8000 - Pinned
let n_loop = vid::loop_step(1); // 8001 - Pinned
let modules_loop = vid::loop_step(2); // 8002 - Pinned
let seen_loop = vid::loop_step(3); // 8003 - Pinned
let prefix_loop = vid::loop_step(4); // 8004 - Carrier
let i_loop = vid::loop_step(5); // 8005 - Carrier
let _header_shape = LoopHeaderShape::new_manual(
vec![entries_loop, n_loop, modules_loop, seen_loop], // Pinned
vec![prefix_loop, i_loop], // Carrier
vec![entries_loop, n_loop, modules_loop, seen_loop], // Pinned
vec![prefix_loop, i_loop], // Carrier
);
// loop_step 関数:
@ -159,69 +182,93 @@ 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
let entry_value = vid::loop_step(11); // 8011
let next_i = vid::loop_step(12); // 8012
let const_1 = vid::loop_step(13); // 8013
let new_prefix = vid::loop_step(14); // 8014
let cmp_result = vid::loop_step(10); // 8010
let entry_value = vid::loop_step(11); // 8011
let next_i = vid::loop_step(12); // 8012
let const_1 = vid::loop_step(13); // 8013
let new_prefix = vid::loop_step(14); // 8014
// cmp_result = (i >= n)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_result,
op: CompareOp::Ge,
lhs: i_loop,
rhs: n_loop,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_result,
op: CompareOp::Ge,
lhs: i_loop,
rhs: n_loop,
}));
// Phase 27.13: Exit φ の意味を LoopExitShape で明示
// UsingResolver entries ループ脱出時は prefix の値を返す(最終的な連結文字列)
let _exit_shape = LoopExitShape::new_manual(vec![prefix_loop]); // exit_args = [prefix]
let _exit_shape = LoopExitShape::new_manual(vec![prefix_loop]); // exit_args = [prefix]
// if i >= n { return prefix }
loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(0),
args: vec![prefix_loop], // ← LoopExitShape.exit_args に対応
args: vec![prefix_loop], // ← LoopExitShape.exit_args に対応
cond: Some(cmp_result),
});
// entry = entries.get(i)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(entry_value),
box_name: "ArrayBox".to_string(),
method: "get".to_string(),
args: vec![entries_loop, i_loop],
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(entry_value),
box_name: "ArrayBox".to_string(),
method: "get".to_string(),
args: vec![entries_loop, i_loop],
}));
// const_1 = 1
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1,
value: ConstValue::Integer(1),
}));
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 {
dst: next_i,
op: BinOpKind::Add,
lhs: i_loop,
rhs: const_1,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: next_i,
op: BinOpKind::Add,
lhs: i_loop,
rhs: const_1,
}));
// 簡略化: 文字列連結のみ(実際の should_emit, path 解決等は省略)
// new_prefix = prefix + entry (実際は "\n" + code + "\n" だが、ここでは簡略化)
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: new_prefix,
op: BinOpKind::Or, // String concatenation uses Or in JoinIR
lhs: prefix_loop,
rhs: entry_value,
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: new_prefix,
op: BinOpKind::Or, // String concatenation uses Or in JoinIR
lhs: prefix_loop,
rhs: entry_value,
}));
// 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");

View 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)
}

View 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)
}

View File

@ -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,
"Overlap detected: range {} ends at {} but range {} starts at {}",
i, end_i, i + 1, start_next);
assert!(
end_i < start_next,
"Overlap detected: range {} ends at {} but range {} starts at {}",
i,
end_i,
i + 1,
start_next
);
}
}
}

View File

@ -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");

View File

@ -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"));
}

View File

@ -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(&current_func)
.ok_or_else(|| JoinRuntimeError::new(format!("Function {:?} not found", current_func)))?;
let func = module.functions.get(&current_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,
@ -155,7 +156,7 @@ fn eval_compute(
// * Method re-routing (toString→str)
MirLikeInst::BoxCall {
dst,
box_name: _, // box_name は VM が内部で判定するため不要
box_name: _, // box_name は VM が内部で判定するため不要
method,
args,
} => {
@ -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> {
))),
}
}

View File

@ -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<_>>();
@ -178,7 +178,7 @@ fn convert_join_function_to_mir(
// - On Call: emit Call in current block
let mut current_block_id = entry_block;
let mut current_instructions = Vec::new();
let mut next_block_id = 1u32; // for creating new blocks
let mut next_block_id = 1u32; // for creating new blocks
for join_inst in &join_func.body {
match join_inst {
@ -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
@ -220,9 +229,9 @@ fn convert_join_function_to_mir(
// Create Call instruction (legacy string-based)
// Phase 27-shortterm: No Callee yet, use func field with string ValueId
current_instructions.push(MirInstruction::Call {
dst: None, // tail call, no return value captured
dst: None, // tail call, no return value captured
func: func_name_id,
callee: None, // Phase 27-shortterm: no Callee resolution
callee: None, // Phase 27-shortterm: no Callee resolution
args: args.clone(),
effects: EffectMask::PURE,
});
@ -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(&current_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(&current_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 {

View File

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

View File

@ -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()
.filter(|name| !name.starts_with("__pin$")) // 一時変数除外
.cloned()
.collect();
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
true, // in_loop_body = true
carrier_names,
);
// Phase 26-F-2: IfBodyLocalMergeBox は phi_builder_box.rs 内で直接使用

6
src/mir/loop_form.rs Normal file
View 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;

View File

@ -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 rulesNamingBox
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 rulesNamingBox
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; // FunctionEmissionBoxMirFunction直編集の発行ヘルパ
pub mod hints; // scaffold: zero-cost guidance (no-op)
pub mod join_ir; // Phase 26-H: 関数正規化IRJoinIR
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: 関数正規化IRJoinIR
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)

View File

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

View File

@ -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() {

View File

@ -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);
}
}
}

View File

@ -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;
}
@ -370,7 +370,7 @@ mod tests {
&pinned,
&carrier,
&[BasicBlockId(2), BasicBlockId(5)],
&live_at_exit, // Phase 26-F-4
&live_at_exit, // Phase 26-F-4
);
// Expected: s, idx, n (ch is BodyLocalInternal → filtered out)
@ -406,8 +406,13 @@ mod tests {
// Phase 26-F-4: empty live_at_exitlive情報なし
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_exitlive情報なし
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);

View File

@ -27,7 +27,7 @@ impl ConservativeMerge {
/// * `then_end` - then-branch終了時の変数マップ
/// * `else_end_opt` - else-branch終了時の変数マップNoneの場合はempty else
pub fn analyze(
pre_if: &BTreeMap<String, ValueId>, // Phase 25.1: BTreeMap化
pre_if: &BTreeMap<String, ValueId>, // Phase 25.1: BTreeMap化
then_end: &BTreeMap<String, ValueId>, // Phase 25.1: BTreeMap化
else_end_opt: &Option<BTreeMap<String, ValueId>>, // Phase 25.1: BTreeMap化
) -> Self {

View File

@ -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,16 +184,19 @@ 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<_>>(),
pinned_vars,
carrier_vars,
&exit_preds,
&live_at_exit, // Phase 26-F-4: live_at_exit 追加
&live_at_exit, // Phase 26-F-4: live_at_exit 追加
);
// Fail-Fast invariant共通箱経由:
@ -361,12 +363,9 @@ 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,
)
.into_iter()
.collect()
crate::mir::phi_core::loopform_builder::LoopFormOps::get_block_predecessors(self, block_id)
.into_iter()
.collect()
}
fn block_exists(&self, block_id: BasicBlockId) -> bool {

View File

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

View File

@ -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
@ -105,7 +105,7 @@ impl IfBodyLocalMergeBox {
then_end: &BTreeMap<String, ValueId>,
else_end_opt: &Option<BTreeMap<String, ValueId>>,
_reachable_preds: &[BasicBlockId],
if_context: &IfPhiContext, // Phase 26-F-3: ループ内コンテキスト
if_context: &IfPhiContext, // Phase 26-F-3: ループ内コンテキスト
) -> Vec<String> {
use std::collections::BTreeSet;
@ -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();
@ -207,7 +208,7 @@ mod tests {
&then_end,
&Some(else_end),
&[BasicBlockId(1), BasicBlockId(2)],
&default_if_context(), // Phase 26-F-3: コンテキスト追加
&default_if_context(), // Phase 26-F-3: コンテキスト追加
);
assert_eq!(candidates, vec!["x".to_string()]);
@ -228,7 +229,7 @@ mod tests {
&then_end,
&Some(else_end),
&[BasicBlockId(1), BasicBlockId(2)],
&default_if_context(), // Phase 26-F-3: コンテキスト追加
&default_if_context(), // Phase 26-F-3: コンテキスト追加
);
// 片腕のみ → BodyLocalInternal相当 → 候補なし
@ -252,7 +253,7 @@ mod tests {
&then_end,
&Some(else_end),
&[BasicBlockId(1), BasicBlockId(2)],
&default_if_context(), // Phase 26-F-3: コンテキスト追加
&default_if_context(), // Phase 26-F-3: コンテキスト追加
);
// 値が変わってない → φ不要
@ -279,7 +280,7 @@ mod tests {
&then_end,
&Some(else_end),
&[BasicBlockId(1), BasicBlockId(2)],
&loop_context(vec!["i"]), // i はキャリア変数
&loop_context(vec!["i"]), // i はキャリア変数
);
// ループ内モード: carrier変数iは片腕のみでもPHI候補に
@ -296,18 +297,18 @@ mod tests {
pre_if.insert("i".to_string(), ValueId(10));
let mut then_end = BTreeMap::new();
then_end.insert("ch".to_string(), ValueId(5)); // ch は then のみ
then_end.insert("ch".to_string(), ValueId(5)); // ch は then のみ
then_end.insert("i".to_string(), ValueId(20));
let mut else_end = BTreeMap::new();
else_end.insert("i".to_string(), ValueId(30)); // i は else にも
else_end.insert("i".to_string(), ValueId(30)); // i は else にも
let candidates = IfBodyLocalMergeBox::compute_if_merge_phi_candidates(
&pre_if,
&then_end,
&Some(else_end),
&[BasicBlockId(1), BasicBlockId(2)],
&loop_context(vec!["i"]), // i のみキャリア
&loop_context(vec!["i"]), // i のみキャリア
);
// i はキャリア → 候補に含まれる
@ -329,7 +330,7 @@ mod tests {
&then_end,
&None,
&[BasicBlockId(1)],
&default_if_context(), // Phase 26-F-3: コンテキスト追加
&default_if_context(), // Phase 26-F-3: コンテキスト追加
);
// empty else → 何も絞らない

View File

@ -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_exitMIRスキャン実装待ち
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_exitMIRスキャン実装待ち
assert_eq!(live_at_exit.len(), 0);

View File

@ -9,7 +9,7 @@
use crate::mir::{BasicBlockId, ValueId};
use std::collections::BTreeMap; // Phase 25.1: 決定性確保
// Phase 25.1: BTreeMap → BTreeMap決定性確保・内部使用のみ
// Phase 25.1: BTreeMap → BTreeMap決定性確保・内部使用のみ
/// ループSnapshotの一元管理Box
///
@ -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

View File

@ -17,7 +17,7 @@
use crate::mir::{BasicBlockId, ValueId};
use std::collections::{BTreeMap, BTreeSet}; // Phase 25.1: 決定性確保
// Phase 25.1: BTreeMap → BTreeMap決定性確保・内部使用のみ
// Phase 25.1: BTreeMap → BTreeMap決定性確保・内部使用のみ
// Option C PHI bug fix: Use box-based classification
use super::local_scope_inspector::LocalScopeInspectorBox;

View File

@ -332,7 +332,7 @@ impl LoopFormBuilder {
latch_id: BasicBlockId,
continue_snapshots: &[(BasicBlockId, BTreeMap<String, ValueId>)],
_writes: &std::collections::HashSet<String>, // Step 5-1/5-2: Reserved for future optimization
header_bypass: bool, // Phase 27.4C: Header φ バイパスフラグ
header_bypass: bool, // Phase 27.4C: Header φ バイパスフラグ
) -> Result<(), String> {
let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok();
@ -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

View File

@ -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,10 +298,14 @@ 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 {
in_loop_body: false,
loop_carrier_names: std::collections::BTreeSet::new(),
});
let if_context = self
.if_context
.as_ref()
.cloned()
.unwrap_or_else(|| IfPhiContext {
in_loop_body: false,
loop_carrier_names: std::collections::BTreeSet::new(),
});
// Phase 26-F-2/F-3: IfBodyLocalMergeBoxでif-merge専用のφ候補決定
let candidates = IfBodyLocalMergeBox::compute_if_merge_phi_candidates(
@ -313,7 +313,7 @@ impl PhiBuilderBox {
then_end,
&else_end_owned,
&reachable_preds,
&if_context, // Phase 26-F-3: コンテキスト渡し
&if_context, // Phase 26-F-3: コンテキスト渡し
);
// Debug trace

View File

@ -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],
@ -131,7 +138,7 @@ impl<'m> MirQuery for MirQueryBox<'m> {
| FutureNew { dst, .. }
| NewClosure { dst, .. }
| Await { dst, .. }
| Copy { dst, .. } => vec![*dst], // Copy writes to dst
| Copy { dst, .. } => vec![*dst], // Copy writes to dst
// No writes
Nop
| Store { .. }

View 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,
}

View File

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

View File

@ -113,14 +113,12 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
};
let ast = match NyashParser::parse_from_string(&code) {
Ok(ast) => ast,
Err(e) => {
crate::runner::modes::common_util::diag::print_parse_error_with_context(
filename,
&code,
&e,
);
process::exit(1);
}
Err(e) => {
crate::runner::modes::common_util::diag::print_parse_error_with_context(
filename, &code, &e,
);
process::exit(1);
}
};
// Optional macro expansion dump (no-op expansion for now)
let ast2 = if crate::r#macro::enabled() {
@ -144,14 +142,12 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
};
let ast = match NyashParser::parse_from_string(&code) {
Ok(ast) => ast,
Err(e) => {
crate::runner::modes::common_util::diag::print_parse_error_with_context(
filename,
&code,
&e,
);
process::exit(1);
}
Err(e) => {
crate::runner::modes::common_util::diag::print_parse_error_with_context(
filename, &code, &e,
);
process::exit(1);
}
};
let expanded = if crate::r#macro::enabled() {
let a = crate::r#macro::maybe_expand_and_dump(&ast, false);

View File

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

View File

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

View File

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

View File

@ -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:");

View 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)
}

View File

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

View File

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

View File

@ -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); }
};

View File

@ -133,23 +133,25 @@ pub fn execute_pyvm_only(runner: &NyashRunner, filename: &str) {
if crate::config::env::env_bool("NYASH_PYVM_DUMP_CODE") {
eprintln!("[pyvm-code]\n{}", code);
}
let ast = match NyashParser::parse_from_string(&code) {
Ok(ast) => ast,
Err(e) => {
crate::runner::modes::common_util::diag::print_parse_error_with_context(
filename,
&code,
&e,
);
process::exit(1);
}
};
let ast = match NyashParser::parse_from_string(&code) {
Ok(ast) => ast,
Err(e) => {
crate::runner::modes::common_util::diag::print_parse_error_with_context(
filename, &code, &e,
);
process::exit(1);
}
};
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
let ast = crate::runner::modes::macro_child::normalize_core_pass(&ast);
// 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);

View File

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

View File

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

View File

@ -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); }
};

View File

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

View File

@ -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");
}
}

View File

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

View File

@ -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");

View File

@ -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,12 +208,14 @@ 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 {
dst: Some(n_var),
box_name: "StringBox".to_string(),
method: "length".to_string(),
args: vec![s_param],
}));
entry_func
.body
.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(n_var),
box_name: "StringBox".to_string(),
method: "length".to_string(),
args: vec![s_param],
}));
// i_init = 0
entry_func.body.push(JoinInst::Compute(MirLikeInst::Const {
@ -360,12 +361,14 @@ 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 {
dst: Some(n_var),
box_name: "StringBox".to_string(),
method: "length".to_string(),
args: vec![s_param],
}));
entry_func
.body
.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(n_var),
box_name: "StringBox".to_string(),
method: "length".to_string(),
args: vec![s_param],
}));
// const_0 = 0
entry_func.body.push(JoinInst::Compute(MirLikeInst::Const {
@ -383,12 +386,14 @@ fn build_trim_joinir() -> JoinModule {
});
// result = s.substring(start, n)
entry_func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(result_var),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![s_param, start_var, n_var],
}));
entry_func
.body
.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(result_var),
box_name: "StringBox".to_string(),
method: "substring".to_string(),
args: vec![s_param, start_var, n_var],
}));
// return result
entry_func.body.push(JoinInst::Ret {

View File

@ -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");

View File

@ -106,4 +106,3 @@ static box Main {
std::env::remove_var("NYASH_DISABLE_PLUGINS");
std::env::remove_var("HAKO_MIR_BUILDER_METHODIZE");
}

View File

@ -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");
}

View File

@ -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");

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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,18 +86,22 @@ 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 {
dst: cmp_result,
op: CompareOp::Ge,
lhs: i_param,
rhs: ValueId(203), // const 2
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: cmp_result,
op: CompareOp::Ge,
lhs: i_param,
rhs: ValueId(203), // const 2
}));
// const 2
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: ValueId(203),
value: ConstValue::Integer(2),
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: ValueId(203),
value: ConstValue::Integer(2),
}));
// if cmp_result { k_exit(i) } else { loop_step(i+1, k_exit) }
// ここでは簡略化して Jump 命令だけ書く(実際は分岐制御が必要だが Phase 26-H では型チェックのみ)
@ -107,12 +112,14 @@ fn mir_joinir_min_manual_construction() {
});
// i_plus_1 = i + 1
loop_step_func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: i_plus_1,
op: BinOpKind::Add,
lhs: i_param,
rhs: ValueId(204), // const 1
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: i_plus_1,
op: BinOpKind::Add,
lhs: i_param,
rhs: ValueId(204), // const 1
}));
join_module.add_function(loop_step_func);
@ -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");
}

View File

@ -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");
}

View File

@ -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");
}

View 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());
}

View 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());
}

View File

@ -82,4 +82,3 @@ fn mir_loopform_nested_region_verify() {
}
teardown_stage3_env();
}

Some files were not shown because too many files have changed in this diff Show More