feat(phase32): L-2.1 Stage-1 UsingResolver JoinIR integration + cleanup
Phase 32 L-2.1 complete implementation: 1. Stage-1 UsingResolver main line JoinIR connection - CFG-based LoopForm construction for resolve_for_source/5 - LoopToJoinLowerer integration with handwritten fallback - JSON snapshot tests 6/6 PASS 2. JoinIR/VM Bridge improvements - Simplified join_ir_vm_bridge.rs dispatch logic - Enhanced json.rs serialization - PHI core boxes cleanup (local_scope_inspector, loop_exit_liveness, loop_var_classifier) 3. Stage-1 CLI enhancements - Extended args.rs, groups.rs, mod.rs for new options - Improved stage1_bridge module (args, env, mod) - Updated stage1_cli.hako 4. MIR builder cleanup - Simplified if_form.rs control flow - Removed dead code from loop_builder.rs - Enhanced phi_merge.rs 5. Runner module updates - json_v0_bridge/lowering.rs improvements - dispatch.rs, selfhost.rs, modes/vm.rs cleanup 6. Documentation updates - CURRENT_TASK.md, AGENTS.md - Various docs/ updates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -146,6 +146,15 @@ PR テンプレ(追加項目)
|
|||||||
- [ ] 「スモークを通すためだけのハードコード」を入れていません(根治で解決)
|
- [ ] 「スモークを通すためだけのハードコード」を入れていません(根治で解決)
|
||||||
- [ ] 受け入れ基準に記載の検証を実施しました(dev OFF/ログ確認)
|
- [ ] 受け入れ基準に記載の検証を実施しました(dev OFF/ログ確認)
|
||||||
|
|
||||||
|
### 5.3 環境変数スパロー防止(今回の反省)
|
||||||
|
- 事件メモ: `NYASH_*` が大量に増殖し、実質未使用のトグルが多数発見された(2025-02-XX)。環境変数病を防ぐための運用ルールを追加する。
|
||||||
|
- 追加ルール:
|
||||||
|
- 目的が明確なときだけ新設する。デフォルトOFFかつ撤去計画(どのフェーズで消すか)を `docs/reference/environment-variables.md` に必ず書く。
|
||||||
|
- 実装は `src/config/env` に集約し、直読み禁止。棚卸し用に定義の所在を一元化する。
|
||||||
|
- 期間限定トグルは dev/diagnostic 専用タグを付け、フェーズ完了時に削除する(CURRENT_TASK.md に期限を書く)。
|
||||||
|
- 半期ごとに未使用(定義のみ)の変数を洗い出し、削除または非推奨化する。
|
||||||
|
- ドキュメントに載せない「隠しトグル」は原則禁止。載せるか削除するかの二択。
|
||||||
|
|
||||||
### 5.2 Rust Minimal Policy(Self‑Host First, but not Frozen)
|
### 5.2 Rust Minimal Policy(Self‑Host First, but not Frozen)
|
||||||
|
|
||||||
目的: 脱Rustを志向しつつも、Stage‑1 / Self‑Host ラインの整備やツールの使いやすさ向上のために、**Rust層で必要な構造的変更やブリッジ強化は積極的に許可する**。分析/ルール/可視化は引き続き .hako 側が主戦場。
|
目的: 脱Rustを志向しつつも、Stage‑1 / Self‑Host ラインの整備やツールの使いやすさ向上のために、**Rust層で必要な構造的変更やブリッジ強化は積極的に許可する**。分析/ルール/可視化は引き続き .hako 側が主戦場。
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
- 27.8-27.11/27.13/27.15 で JoinIR Runner を JoinValue↔VMValue 変換+`MirInterpreter::execute_box_call` ラッパ経由で Rust VM の BoxCall 意味論と統合し、skip_ws/trim の JoinIR Runner スタンドアロン実行が安定(GC/BoxRef も Arc ベースで統合済み)+ `joinir_runner_standalone_skip_ws` / `joinir_runner_standalone_trim` を NYASH_JOINIR_EXPERIMENT=1 で常時叩ける正常系テストとして昇格済み。
|
- 27.8-27.11/27.13/27.15 で JoinIR Runner を JoinValue↔VMValue 変換+`MirInterpreter::execute_box_call` ラッパ経由で Rust VM の BoxCall 意味論と統合し、skip_ws/trim の JoinIR Runner スタンドアロン実行が安定(GC/BoxRef も Arc ベースで統合済み)+ `joinir_runner_standalone_skip_ws` / `joinir_runner_standalone_trim` を NYASH_JOINIR_EXPERIMENT=1 で常時叩ける正常系テストとして昇格済み。
|
||||||
- Phase 27-shortterm の S‑1〜S‑5.4 は完了。Phase 28-midterm では per-loop lowering を増やさず、LoopForm+LoopVarClassBox+LoopExitLivenessBox を入力にした「汎用 Loop→JoinIR ロワー」(generic_case_a + LoopScopeShape)に畳み込む方針に切り替え済み(join-ir.md に JoinIR ロワーが“やらないこと”チェックリストを明記)。
|
- Phase 27-shortterm の S‑1〜S‑5.4 は完了。Phase 28-midterm では per-loop lowering を増やさず、LoopForm+LoopVarClassBox+LoopExitLivenessBox を入力にした「汎用 Loop→JoinIR ロワー」(generic_case_a + LoopScopeShape)に畳み込む方針に切り替え済み(join-ir.md に JoinIR ロワーが“やらないこと”チェックリストを明記)。
|
||||||
- Phase 29-longterm では LoopForm を「構造専任箱」とし、その隣に LoopScopeShape(LoopVarClassBox / ExitLiveness / LocalScopeInspector を統合したスコープ箱)を置く形に整理。LoopVarClassBox/LoopExitLivenessBox/LocalScopeInspector は LoopScopeShape::from_existing_boxes 経由で細く呼ばれるだけのレガシーに寄せておき、将来的には LoopScopeShape に吸収して 1箱化する計画を TASKS に記録。
|
- Phase 29-longterm では LoopForm を「構造専任箱」とし、その隣に LoopScopeShape(LoopVarClassBox / ExitLiveness / LocalScopeInspector を統合したスコープ箱)を置く形に整理。LoopVarClassBox/LoopExitLivenessBox/LocalScopeInspector は LoopScopeShape::from_existing_boxes 経由で細く呼ばれるだけのレガシーに寄せておき、将来的には LoopScopeShape に吸収して 1箱化する計画を TASKS に記録。
|
||||||
- **30.x(JoinIR World)**: JoinIR を「実行系の主IR」として採用し、PHI まわりのレガシーを段階的に縮退・削除していく最終フェーズ。
|
- **30.x(JoinIR World)**: JoinIR を「実行系の主IR」として採用し、PHI まわりのレガシーを段階的に縮退・削除していく足場整備フェーズ(早期削除候補と minimal ケースまで完了、残りは Phase 32 へ引き継ぎ)。
|
||||||
- L‑0.1〜0.2: jsonir v0(JoinModule → JSON)と v0_* スナップショットフィクスチャを整備済み。
|
- L‑0.1〜0.2: jsonir v0(JoinModule → JSON)と v0_* スナップショットフィクスチャを整備済み。
|
||||||
- L‑0.3〜0.4: JoinIR VM Bridge を実装し、`Main.skip/1`(minimal_ssa_skip_ws)と `FuncScannerBox.trim/1` で Route A (MIR→VM) の PHI バグを Route B (MIR→JoinIR→VM) で設計的に修正できることを実行レベルで実証済み。
|
- L‑0.3〜0.4: JoinIR VM Bridge を実装し、`Main.skip/1`(minimal_ssa_skip_ws)と `FuncScannerBox.trim/1` で Route A (MIR→VM) の PHI バグを Route B (MIR→JoinIR→VM) で設計的に修正できることを実行レベルで実証済み。
|
||||||
- L‑0.5: Stage1UsingResolverBox.resolve_for_source/5 に対しても JoinIR VM Bridge A/B テストを追加し、Stage‑1 minimal について n=0 で `"init"` / n=3 で `"ABC"` が JoinIR 経由で正しく返ることを確認 → 「Stage‑1 ループでも JoinIR が PHI 問題を根治できる」ことを実行レベルで実証。
|
- L‑0.5: Stage1UsingResolverBox.resolve_for_source/5 に対しても JoinIR VM Bridge A/B テストを追加し、Stage‑1 minimal について n=0 で `"init"` / n=3 で `"ABC"` が JoinIR 経由で正しく返ることを確認 → 「Stage‑1 ループでも JoinIR が PHI 問題を根治できる」ことを実行レベルで実証。
|
||||||
@ -102,7 +102,7 @@
|
|||||||
3. **最終削除** (F-2.3): LoopVarClassBox / LoopExitLivenessBox / LocalScopeInspectorBox
|
3. **最終削除** (F-2.3): LoopVarClassBox / LoopExitLivenessBox / LocalScopeInspectorBox
|
||||||
- LoopScopeShape 完全移行後に削除
|
- LoopScopeShape 完全移行後に削除
|
||||||
|
|
||||||
**次手**: F-2.1 以降で早期削除候補から順に削除開始
|
**次手**: 早期削除候補とテスト専用箱は削除済み。残りの中期〜最終候補は Phase 32(`phase-32-joinir-complete-migration`)で扱う。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ LoopScopeShape → CaseAContext::from_scope() → lower_case_a_X_core() → Join
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 1-00z. Phase 30 F-1/F-3/F-4.4 準備調査 — generic_case_a 本線化準備(**完了** 2025-11-25)
|
### 1-00z. Phase 30 F-1/F-3/F-4.4 準備調査 — generic_case_a 本線化準備(**完了** 2025-11-25, Phase 32 view 切り替え継続中)
|
||||||
|
|
||||||
**目的**
|
**目的**
|
||||||
- Task A: 各 lowerer ファイルの Case A 判定フックと generic_case_a 呼び出し状況を棚卸し
|
- Task A: 各 lowerer ファイルの Case A 判定フックと generic_case_a 呼び出し状況を棚卸し
|
||||||
@ -165,6 +165,10 @@ LoopScopeShape → CaseAContext::from_scope() → lower_case_a_X_core() → Join
|
|||||||
- F-4.4 方向: VM runner の関数名分岐(約90行)を generic_case_all 完成後に縮退
|
- F-4.4 方向: VM runner の関数名分岐(約90行)を generic_case_all 完成後に縮退
|
||||||
- 縮退計画詳細: `docs/private/roadmap2/phases/phase-30-final-joinir-world/TASKS.md` F-4.4 に記録済み
|
- 縮退計画詳細: `docs/private/roadmap2/phases/phase-30-final-joinir-world/TASKS.md` F-4.4 に記録済み
|
||||||
|
|
||||||
|
補足(Phase 32 での追記):
|
||||||
|
- Phase 32 Step 3-B で LoopScopeShape::from_existing_boxes_legacy が LoopShape の view (`LoopRegion` / `LoopControlShape` / `ExitEdge`) 経由に切り替わった。
|
||||||
|
- Phase 32 Step 3-C で LoopToJoinLowerer 側も minimal 4 本(skip_ws / trim / append_defs / Stage‑1 minimal)について LoopRegion/LoopControlShape view を使用するよう統一済み。以降の JoinIR 汎用化(L-1.x, L-2.x)はこの view ベース API を前提に進める。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 1-00a. Phase 30 F-1.4 — LoopScopeShape API 確定(**完了** 2025-11-25)
|
### 1-00a. Phase 30 F-1.4 — LoopScopeShape API 確定(**完了** 2025-11-25)
|
||||||
|
|||||||
@ -32,7 +32,7 @@ Extern vs BoxCall — 分離方針とスロット/アリティ一覧(Phase 12
|
|||||||
- BoxCall: vtable(TypeRegistry のスロット)→ PIC(poly→mono)→ 汎用メソッド呼び。
|
- BoxCall: vtable(TypeRegistry のスロット)→ PIC(poly→mono)→ 汎用メソッド呼び。
|
||||||
- STRICT: 未登録メソッドは型名・メソッド名・arity・known一覧を含めてエラー。
|
- STRICT: 未登録メソッドは型名・メソッド名・arity・known一覧を含めてエラー。
|
||||||
- ExternCall: `extern_registry` で iface/method/arity を登録、任意で slot 経由のハンドラに集約。
|
- ExternCall: `extern_registry` で iface/method/arity を登録、任意で slot 経由のハンドラに集約。
|
||||||
- `NYASH_EXTERN_ROUTE_SLOTS=1` で name→slot 専用ハンドラへ(VM/JITの挙動安定)。
|
- name→slot 専用ハンドラは検討のみ。旧 `NYASH_EXTERN_ROUTE_SLOTS` は未使用につき撤去。
|
||||||
|
|
||||||
TypeRegistryの代表スロット
|
TypeRegistryの代表スロット
|
||||||
- InstanceBox: 1(getField), 2(setField), 3(has), 4(size)
|
- InstanceBox: 1(getField), 2(setField), 3(has), 4(size)
|
||||||
@ -50,7 +50,6 @@ Extern スロット(抜粋)
|
|||||||
環境変数
|
環境変数
|
||||||
- `NYASH_ABI_VTABLE`: VMのvtable経路有効化
|
- `NYASH_ABI_VTABLE`: VMのvtable経路有効化
|
||||||
- `NYASH_ABI_STRICT`: STRICT診断を有効化
|
- `NYASH_ABI_STRICT`: STRICT診断を有効化
|
||||||
- `NYASH_EXTERN_ROUTE_SLOTS`: Externをslot経路に統一
|
- `NYASH_EXTERN_ROUTE_SLOTS`: (撤去済み)Externをslot経路に統一するトグル
|
||||||
- `NYASH_JIT_HOST_BRIDGE`: JITのhost-bridge(by-slot経路)を有効化
|
- `NYASH_JIT_HOST_BRIDGE`: JITのhost-bridge(by-slot経路)を有効化
|
||||||
- `NYASH_VM_PIC_THRESHOLD`: PICモノ化しきい値(既定=8)
|
- `NYASH_VM_PIC_THRESHOLD`: (撤去済み)PICモノ化しきい値トグル
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ High‑Value Candidates
|
|||||||
|
|
||||||
- VM dispatch integration
|
- VM dispatch integration
|
||||||
- Gradually route the big `match` in VM through `backend/dispatch.rs` (already scaffolded). Start with side‑effect‑free ops (Const/Compare/TypeOp) to de‑risk.
|
- Gradually route the big `match` in VM through `backend/dispatch.rs` (already scaffolded). Start with side‑effect‑free ops (Const/Compare/TypeOp) to de‑risk.
|
||||||
- Add opt‑in flag `NYASH_VM_USE_DISPATCH=1` to gate behavior during the transition.
|
- (撤去済み) `NYASH_VM_USE_DISPATCH=1` でのディスパッチ経路スイッチ案は廃止。
|
||||||
|
|
||||||
- Env access centralization
|
- Env access centralization
|
||||||
- Replace scattered `std::env::var("NYASH_*")` with `config::env` getters in hot paths (VM tracing, GC barriers, resolver toggles).
|
- Replace scattered `std::env::var("NYASH_*")` with `config::env` getters in hot paths (VM tracing, GC barriers, resolver toggles).
|
||||||
@ -42,4 +42,3 @@ Suggested Sequencing
|
|||||||
Notes
|
Notes
|
||||||
- Keep JIT/Cranelift untouched in this phase to avoid drift from the mainline policy.
|
- Keep JIT/Cranelift untouched in this phase to avoid drift from the mainline policy.
|
||||||
- Prefer file‑level docs on new modules to guide incremental migration.
|
- Prefer file‑level docs on new modules to guide incremental migration.
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
- **現在のタスク**: [../../CURRENT_TASK.md](../../CURRENT_TASK.md)
|
- **現在のタスク**: [../../CURRENT_TASK.md](../../CURRENT_TASK.md)
|
||||||
- **Phase 8.3**: Box操作WASM実装(Copilot担当)
|
- **Phase 8.3**: Box操作WASM実装(Copilot担当)
|
||||||
- **Phase 8.4**: ネイティブコンパイル実装計画(AI大会議策定済み)
|
- **Phase 8.4**: ネイティブコンパイル実装計画(AI大会議策定済み)
|
||||||
|
- **Stage‑1 / JSON v0**: Stage‑1 CLI minimal は Program(JSON v0) まで安定到達済み、Ny compiler 経路の JSON v0→MIR 統一は Phase 28.2 以降で追う
|
||||||
|
|
||||||
## 🚀 ネイティブコンパイル計画 (2025-08-14策定)
|
## 🚀 ネイティブコンパイル計画 (2025-08-14策定)
|
||||||
|
|
||||||
|
|||||||
@ -3,3 +3,5 @@
|
|||||||
- 方針: MIR 命令に AST Span を持たせ、VMError (StepBudgetExceeded) で fn/bb/inst に加えて .hako 行番号を出す。
|
- 方針: MIR 命令に AST Span を持たせ、VMError (StepBudgetExceeded) で fn/bb/inst に加えて .hako 行番号を出す。
|
||||||
- 実装: MirInstruction 生成時に current_span を保存し、VM 側で last_inst_idx から Span を引いてエラーに埋め込む。Span が無い場合は従来どおり fn/bb/inst のみ。
|
- 実装: MirInstruction 生成時に current_span を保存し、VM 側で last_inst_idx から Span を引いてエラーに埋め込む。Span が無い場合は従来どおり fn/bb/inst のみ。
|
||||||
- 状態: Stage‑1 CLI の MIR には Span 未付与なので行番号はまだ出ていないが、Span 付き MIR なら `... (file.hako:line:col)` まで表示できる。
|
- 状態: Stage‑1 CLI の MIR には Span 未付与なので行番号はまだ出ていないが、Span 付き MIR なら `... (file.hako:line:col)` まで表示できる。
|
||||||
|
- ダンプ: `RUST_MIR_DUMP_PATH=/tmp/foo.mir` を指定すると、VM 実行前の MirModule をファイルに出力できる(`--dump-mir` のファイル版)。Stage‑1/Stage‑B 経路でも共通で使う想定。
|
||||||
|
- JSON v0 経路: `json_v0_bridge::maybe_dump_mir` が Program(JSON v0)→MIR 直後に動くので、Span が付いていればそこで観測できる(AST 直通の MirPrinter dump では Span 表示なし)。
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
- `Stage‑1 CLI → BuildBox.emit_program_json_v0` 実行時に VM が step budget を食い尽くしている原因を、
|
- `Stage‑1 CLI → BuildBox.emit_program_json_v0` 実行時に VM が step budget を食い尽くしている原因を、
|
||||||
ループ構造と BoxCall 依存の観点から整理しておくためのメモだよ。
|
ループ構造と BoxCall 依存の観点から整理しておくためのメモだよ。
|
||||||
|
|
||||||
## 1. 観測された症状
|
## 1. 観測された症状(修正前スナップショット)
|
||||||
|
|
||||||
- コマンド例:
|
- コマンド例:
|
||||||
```bash
|
```bash
|
||||||
@ -13,12 +13,22 @@
|
|||||||
STAGE1_EMIT_PROGRAM_JSON=1 \
|
STAGE1_EMIT_PROGRAM_JSON=1 \
|
||||||
./target/release/hakorune apps/tests/minimal_ssa_skip_ws.hako
|
./target/release/hakorune apps/tests/minimal_ssa_skip_ws.hako
|
||||||
```
|
```
|
||||||
- 状態:
|
-- 状態(修正前):
|
||||||
- `[stage1-bridge/trace]` + `[stage1-cli/debug] emit_program_json ENTRY` が出ており、
|
- `[stage1-bridge/trace]` + `[stage1-cli/debug] emit_program_json ENTRY` が出ており、
|
||||||
Stage1CliMain.main/0 〜 stage1_cli.hako 実行までは到達している。
|
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)。
|
- その後 `BuildBox.emit_program_json_v0` 実行中に VM が `vm step budget exceeded`(max_steps=1_000_000→2_000_000 でも NG)。
|
||||||
- FileBox/ArrayBox などの plugin 未ロード警告も併発。
|
- FileBox/ArrayBox などの plugin 未ロード警告も併発。
|
||||||
- 現状は **ステップ上限+プラグイン依存** が阻害要因となって、program-json 自体は得られていない。
|
- 当時は **ステップ上限+プラグイン依存** が阻害要因となって、program-json 自体は得られていなかった。
|
||||||
|
|
||||||
|
## 1.1 現在の状態(2025-11 時点)
|
||||||
|
|
||||||
|
- ParserBox / StringHelpers / 各種 parser_* 箱のすべての
|
||||||
|
`loop(cont == 1) { if cond { ... } else { cont = 0 } }` 形式のループを、
|
||||||
|
`loop(true) { if cond { ...; continue } break }` 形式に統一した。
|
||||||
|
- 代表ループ(`parse_program2` 本体、ws_init、セミコロン消費、string/number/map/array/control/exception/stmt/expr 系ループ)を一括で MirBuilder-friendly な形に寄せた結果、
|
||||||
|
- `NYASH_USE_STAGE1_CLI=1 STAGE1_EMIT_PROGRAM_JSON=1 HAKO_VM_MAX_STEPS=2000000 NYASH_STAGE1_INPUT=apps/tests/minimal_ssa_skip_ws.hako ./target/release/hakorune`
|
||||||
|
で **step budget 超過無し**、Program(JSON v0) が `{"version":0,"kind":"Program","body":[]}` として出力されることを確認済み。
|
||||||
|
- VM の step budget エラーには fn/bb/last_inst + Span(あれば .hako:line)が付くようになっており、将来の類似ケースも追いやすい。
|
||||||
|
|
||||||
## 2. emit_program_json_v0 周辺のループ構造(概観)
|
## 2. emit_program_json_v0 周辺のループ構造(概観)
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,11 @@ Phase 25.1 A-3: `.hako` 側 Stage1Cli skeleton に env-only 実処理を実装
|
|||||||
- Stage1 は **自分で VM/LLVM を実装しない**。常に Ring0 のサービス(env.codegen/env.mirbuilder, NyRT/ny-llvmc 等)を経由して実行・AOT する。
|
- Stage1 は **自分で VM/LLVM を実装しない**。常に Ring0 のサービス(env.codegen/env.mirbuilder, NyRT/ny-llvmc 等)を経由して実行・AOT する。
|
||||||
- Stage1 CLI は「どのステージまで進めるか」と「どのバックエンドで実行/ビルドするか」を宣言的に指定するだけに留める。
|
- Stage1 CLI は「どのステージまで進めるか」と「どのバックエンドで実行/ビルドするか」を宣言的に指定するだけに留める。
|
||||||
- Stage1 バイナリ自体は Stage0 の CLI からは独立しており、Stage0 はあくまで「ブートストラップおよびランタイムサービス提供者」として扱う。
|
- Stage1 バイナリ自体は Stage0 の CLI からは独立しており、Stage0 はあくまで「ブートストラップおよびランタイムサービス提供者」として扱う。
|
||||||
|
- JSON v0 境界の扱い:
|
||||||
|
- Stage0 直通: Rust AST → MirCompiler で MIR を生成し、`--dump-mir` は MirPrinter の stdout 出力のみ(Program(JSON v0) は介さない)。
|
||||||
|
- Stage1/selfhost: BuildBox/ParserBox などが `Program(JSON v0)` を返し、Stage0 は `json_v0_bridge::parse_json_v0_to_module` → `maybe_dump_mir`(`RUST_MIR_DUMP_PATH`/`--dump-mir` 両対応) → VM/LLVM という共通導線で処理する。
|
||||||
|
- Stage‑1 専用モード: `STAGE1_EMIT_MIR_JSON=1` で Program(JSON v0) を生成して Rust 側が即座に MIR 化し dump/emit までを行う(実行はしない。`RUST_MIR_DUMP_PATH` / `--dump-mir` / `--emit-mir-json` が JSON v0→MIR 共通パスで効く)。
|
||||||
|
- CLI フラグ整理: `.hako` / Stage‑1 を経由する入口は `--hako-emit-program-json` / `--hako-emit-mir-json` / `--hako-run` を前置きで区別する(内部で Stage‑1 stub を呼び出し、JSON v0 境界から先は共通パスへ流す)。
|
||||||
|
|
||||||
## トップレベル構文
|
## トップレベル構文
|
||||||
|
|
||||||
@ -251,13 +256,15 @@ hakorune emit mir-json [-o <out>] [--quiet] <source.hako>
|
|||||||
- `mir_stage1_cli_emit_program_min_compiles_and_verifies`:
|
- `mir_stage1_cli_emit_program_min_compiles_and_verifies`:
|
||||||
- Stage1Cli + UsingResolver + BuildBox を含んだモジュールが MIR 生成・verify まで通ることを確認(SSA/PHI 崩壊なし)。
|
- Stage1Cli + UsingResolver + BuildBox を含んだモジュールが MIR 生成・verify まで通ることを確認(SSA/PHI 崩壊なし)。
|
||||||
- `mir_stage1_cli_emit_program_min_exec_hits_type_error`:
|
- `mir_stage1_cli_emit_program_min_exec_hits_type_error`:
|
||||||
- VM 実行まで進め、Stage‑1 CLI 経路の型エラーや未解決呼び出しが発生しないことを確認するための箱(現在は安定化済み)。
|
- VM 実行まで進め、Stage‑1 CLI 経路の型エラーや未解決呼び出しが発生しないことを確認するための箱(現在は LoopForm/ParserBox 修正により安定化済み)。
|
||||||
|
|
||||||
### Stage‑1 CLI 環境変数(env-only 仕様)
|
### Stage‑1 CLI 環境変数(env-only 仕様)
|
||||||
|
|
||||||
- Stage0 の `stage1_bridge.rs` から `.hako` 側 `stage1_cli.hako` を呼び出す際の最低限の ENV:
|
- Stage0 の `stage1_bridge.rs` から `.hako` 側 `stage1_cli.hako` を呼び出す際の最低限の ENV:
|
||||||
- `STAGE1_EMIT_PROGRAM_JSON` / `STAGE1_EMIT_MIR_JSON` / `NYASH_USE_STAGE1_CLI`:
|
- `STAGE1_EMIT_PROGRAM_JSON` / `STAGE1_EMIT_MIR_JSON` / `NYASH_USE_STAGE1_CLI`:
|
||||||
- モード選択(emit_program_json / emit_mir_json / run)。
|
- モード選択(emit_program_json / emit_mir_json / run)。
|
||||||
|
- `STAGE1_EMIT_PROGRAM_JSON=1`: Program(JSON v0) を stdout に出して終了(VM 実行なし)。
|
||||||
|
- `STAGE1_EMIT_MIR_JSON=1`: Program(JSON v0) を JSON v0 ブリッジで MIR(JSON) に変換し、`--dump-mir` / `RUST_MIR_DUMP_PATH` / `--emit-mir-json` を通す emit 専用モード(VM 実行なし)。
|
||||||
- `STAGE1_SOURCE`:
|
- `STAGE1_SOURCE`:
|
||||||
- .hako ソースパス(FileBox 経由で読み込むときに使用)。
|
- .hako ソースパス(FileBox 経由で読み込むときに使用)。
|
||||||
- `STAGE1_SOURCE_TEXT`:
|
- `STAGE1_SOURCE_TEXT`:
|
||||||
|
|||||||
@ -149,7 +149,7 @@ JoinIR は制御構造を関数呼び出し + 継続に正規化する IR 層(
|
|||||||
| `NYASH_JOINIR_HEADER_EXP=1` | OFF | Any | Header PHI bypass 有効化(dev-only) |
|
| `NYASH_JOINIR_HEADER_EXP=1` | OFF | Any | Header PHI bypass 有効化(dev-only) |
|
||||||
| `NYASH_JOINIR_EXIT_EXP=1` | OFF | Any | Exit PHI 実験(dev-only) |
|
| `NYASH_JOINIR_EXIT_EXP=1` | OFF | Any | Exit PHI 実験(dev-only) |
|
||||||
| `NYASH_JOINIR_LOWER_FROM_MIR=1` | OFF | Any | MIRベース lowering 有効化(dev-only) |
|
| `NYASH_JOINIR_LOWER_FROM_MIR=1` | OFF | Any | MIRベース lowering 有効化(dev-only) |
|
||||||
| `NYASH_JOINIR_LOWER_GENERIC=1` | OFF | Any | Generic lowering パス(dev-only) |
|
| `NYASH_JOINIR_LOWER_GENERIC=1` | OFF | Any | 構造ベースのみで Case-A 判定(関数名フィルタを外す, Phase 32 L-1.2) |
|
||||||
| `NYASH_JOINIR_VM_BRIDGE=1` | OFF | Any | VM bridge テスト(dev-only) |
|
| `NYASH_JOINIR_VM_BRIDGE=1` | OFF | Any | VM bridge テスト(dev-only) |
|
||||||
|
|
||||||
### JoinIR 使用例
|
### JoinIR 使用例
|
||||||
|
|||||||
@ -28,7 +28,6 @@ Selection & Precedence
|
|||||||
Instrumentation & Diagnostics
|
Instrumentation & Diagnostics
|
||||||
- `NYASH_GC_METRICS=1`: print brief metrics (allocs/bytes/cycles/pauses)
|
- `NYASH_GC_METRICS=1`: print brief metrics (allocs/bytes/cycles/pauses)
|
||||||
- `NYASH_GC_METRICS_JSON=1`: emit JSON metrics for CI/aggregation
|
- `NYASH_GC_METRICS_JSON=1`: emit JSON metrics for CI/aggregation
|
||||||
- `NYASH_GC_LEAK_DIAG=1`: on exit, dump suspected unreleased objects (Top‑K by type/site)
|
|
||||||
- `NYASH_GC_ALLOC_THRESHOLD=<N>`: warn or fail when allocations/bytes exceed threshold
|
- `NYASH_GC_ALLOC_THRESHOLD=<N>`: warn or fail when allocations/bytes exceed threshold
|
||||||
|
|
||||||
Operational Guidance
|
Operational Guidance
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
- 関連ENV
|
- 関連ENV
|
||||||
- `NYASH_GC_MODE`(CLIが優先)
|
- `NYASH_GC_MODE`(CLIが優先)
|
||||||
- `NYASH_GC_METRICS` / `NYASH_GC_METRICS_JSON`
|
- `NYASH_GC_METRICS` / `NYASH_GC_METRICS_JSON`
|
||||||
- `NYASH_GC_LEAK_DIAG` / `NYASH_GC_ALLOC_THRESHOLD`
|
- `NYASH_GC_ALLOC_THRESHOLD`
|
||||||
- 詳細: `docs/reference/runtime/gc.md`
|
- 詳細: `docs/reference/runtime/gc.md`
|
||||||
|
|
||||||
## WASM/AOT
|
## WASM/AOT
|
||||||
|
|||||||
@ -240,9 +240,8 @@ static box Stage1Cli {
|
|||||||
prog_json = me.emit_program_json(source)
|
prog_json = me.emit_program_json(source)
|
||||||
}
|
}
|
||||||
if prog_json == null { return 96 }
|
if prog_json == null { return 96 }
|
||||||
local mir = me.emit_mir_json(prog_json)
|
// Rust parent lowers Program(JSON v0) → MIR (json_v0_bridge) to keep dump flags共通。
|
||||||
if mir == null { return 96 }
|
print(prog_json)
|
||||||
print(mir)
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -316,6 +316,25 @@ impl MirInterpreter {
|
|||||||
Ok(ret.to_nyash_box())
|
Ok(ret.to_nyash_box())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Execute a specific function with explicit arguments (bypasses entry discovery).
|
||||||
|
pub fn execute_function_with_args(
|
||||||
|
&mut self,
|
||||||
|
module: &MirModule,
|
||||||
|
func_name: &str,
|
||||||
|
args: &[VMValue],
|
||||||
|
) -> Result<VMValue, VMError> {
|
||||||
|
// Snapshot functions for call resolution
|
||||||
|
self.functions = module.functions.clone();
|
||||||
|
|
||||||
|
let func = self
|
||||||
|
.functions
|
||||||
|
.get(func_name)
|
||||||
|
.ok_or_else(|| VMError::InvalidInstruction(format!("function not found: {}", func_name)))?
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
self.exec_function_inner(&func, Some(args))
|
||||||
|
}
|
||||||
|
|
||||||
fn execute_function(&mut self, func: &MirFunction) -> Result<VMValue, VMError> {
|
fn execute_function(&mut self, func: &MirFunction) -> Result<VMValue, VMError> {
|
||||||
self.exec_function_inner(func, None)
|
self.exec_function_inner(func, None)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,6 +49,11 @@ pub fn build_command() -> Command {
|
|||||||
.arg(Arg::new("json-file").long("json-file").value_name("FILE").help("Read Ny JSON IR v0 from a file and execute via MIR Interpreter"))
|
.arg(Arg::new("json-file").long("json-file").value_name("FILE").help("Read Ny JSON IR v0 from a file and execute via MIR Interpreter"))
|
||||||
.arg(Arg::new("mir-json-file").long("mir-json-file").value_name("FILE").help("[Diagnostic] Read MIR JSON v0 from a file and perform minimal validation/inspection (experimental)") )
|
.arg(Arg::new("mir-json-file").long("mir-json-file").value_name("FILE").help("[Diagnostic] Read MIR JSON v0 from a file and perform minimal validation/inspection (experimental)") )
|
||||||
.arg(Arg::new("emit-mir-json").long("emit-mir-json").value_name("FILE").help("Emit MIR JSON v0 to file and exit"))
|
.arg(Arg::new("emit-mir-json").long("emit-mir-json").value_name("FILE").help("Emit MIR JSON v0 to file and exit"))
|
||||||
|
.arg(Arg::new("emit-program-json").long("emit-program-json").value_name("FILE").help("Emit Program(JSON v0) to file and exit (AST direct; no Stage-1 stub)"))
|
||||||
|
.arg(Arg::new("hako-emit-program-json").long("hako-emit-program-json").value_name("FILE").help("Emit Program(JSON v0) via Stage-1 (.hako) stub and exit"))
|
||||||
|
.arg(Arg::new("hako-emit-mir-json").long("hako-emit-mir-json").value_name("FILE").help("Emit MIR(JSON) via Stage-1 (.hako) stub (json_v0_bridge path)"))
|
||||||
|
.arg(Arg::new("hako-run").long("hako-run").help("Run via Stage-1 (.hako) stub (equivalent to NYASH_USE_STAGE1_CLI=1)").action(clap::ArgAction::SetTrue))
|
||||||
|
.arg(Arg::new("emit-program-json").long("emit-program-json").value_name("FILE").help("Emit Program(JSON v0) to file and exit (AST direct)"))
|
||||||
.arg(Arg::new("program-json-to-mir").long("program-json-to-mir").value_name("FILE").help("Convert Program(JSON v0) to MIR(JSON) and exit (use with --json-file)"))
|
.arg(Arg::new("program-json-to-mir").long("program-json-to-mir").value_name("FILE").help("Convert Program(JSON v0) to MIR(JSON) and exit (use with --json-file)"))
|
||||||
.arg(Arg::new("emit-exe").long("emit-exe").value_name("FILE").help("Emit native executable via ny-llvmc and exit"))
|
.arg(Arg::new("emit-exe").long("emit-exe").value_name("FILE").help("Emit native executable via ny-llvmc and exit"))
|
||||||
.arg(Arg::new("emit-exe-nyrt").long("emit-exe-nyrt").value_name("DIR").help("Directory containing libnyash_kernel.a (used with --emit-exe)"))
|
.arg(Arg::new("emit-exe-nyrt").long("emit-exe-nyrt").value_name("DIR").help("Directory containing libnyash_kernel.a (used with --emit-exe)"))
|
||||||
@ -121,6 +126,8 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig {
|
|||||||
if let Some(a) = matches.get_one::<String>("ny-compiler-args") {
|
if let Some(a) = matches.get_one::<String>("ny-compiler-args") {
|
||||||
std::env::set_var("NYASH_NY_COMPILER_CHILD_ARGS", a);
|
std::env::set_var("NYASH_NY_COMPILER_CHILD_ARGS", a);
|
||||||
}
|
}
|
||||||
|
let hako_emit_program_path = matches.get_one::<String>("hako-emit-program-json").cloned();
|
||||||
|
let hako_emit_mir_path = matches.get_one::<String>("hako-emit-mir-json").cloned();
|
||||||
let cfg = CliConfig {
|
let cfg = CliConfig {
|
||||||
file: matches.get_one::<String>("file").cloned(),
|
file: matches.get_one::<String>("file").cloned(),
|
||||||
debug_fuel: parse_debug_fuel(matches.get_one::<String>("debug-fuel").unwrap()),
|
debug_fuel: parse_debug_fuel(matches.get_one::<String>("debug-fuel").unwrap()),
|
||||||
@ -182,7 +189,17 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig {
|
|||||||
.get_many::<String>("using")
|
.get_many::<String>("using")
|
||||||
.map(|v| v.cloned().collect())
|
.map(|v| v.cloned().collect())
|
||||||
.unwrap_or_else(|| Vec::new()),
|
.unwrap_or_else(|| Vec::new()),
|
||||||
emit_mir_json: matches.get_one::<String>("emit-mir-json").cloned(),
|
emit_mir_json: matches
|
||||||
|
.get_one::<String>("emit-mir-json")
|
||||||
|
.cloned()
|
||||||
|
.or(hako_emit_mir_path.clone()),
|
||||||
|
emit_program_json: matches
|
||||||
|
.get_one::<String>("emit-program-json")
|
||||||
|
.cloned()
|
||||||
|
.or(hako_emit_program_path.clone()),
|
||||||
|
hako_emit_program_json: hako_emit_program_path.is_some(),
|
||||||
|
hako_emit_mir_json: hako_emit_mir_path.is_some(),
|
||||||
|
hako_run: matches.get_flag("hako-run"),
|
||||||
program_json_to_mir: matches.get_one::<String>("program-json-to-mir").cloned(),
|
program_json_to_mir: matches.get_one::<String>("program-json-to-mir").cloned(),
|
||||||
emit_exe: matches.get_one::<String>("emit-exe").cloned(),
|
emit_exe: matches.get_one::<String>("emit-exe").cloned(),
|
||||||
emit_exe_nyrt: matches.get_one::<String>("emit-exe-nyrt").cloned(),
|
emit_exe_nyrt: matches.get_one::<String>("emit-exe-nyrt").cloned(),
|
||||||
@ -201,6 +218,35 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig {
|
|||||||
if cfg.vm_stats_json {
|
if cfg.vm_stats_json {
|
||||||
std::env::set_var("NYASH_VM_STATS_JSON", "1");
|
std::env::set_var("NYASH_VM_STATS_JSON", "1");
|
||||||
}
|
}
|
||||||
|
// hako-prefixed Stage-1 stub routes
|
||||||
|
if cfg.hako_emit_program_json {
|
||||||
|
std::env::set_var("NYASH_USE_STAGE1_CLI", "1");
|
||||||
|
std::env::set_var("HAKO_STAGE1_MODE", "emit-program");
|
||||||
|
std::env::set_var("HAKO_EMIT_PROGRAM_JSON", "1");
|
||||||
|
std::env::set_var("STAGE1_EMIT_PROGRAM_JSON", "1");
|
||||||
|
if let Some(f) = cfg.file.as_ref() {
|
||||||
|
std::env::set_var("HAKO_STAGE1_INPUT", f);
|
||||||
|
std::env::set_var("NYASH_STAGE1_INPUT", f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cfg.hako_emit_mir_json {
|
||||||
|
std::env::set_var("NYASH_USE_STAGE1_CLI", "1");
|
||||||
|
std::env::set_var("HAKO_STAGE1_MODE", "emit-mir");
|
||||||
|
std::env::set_var("HAKO_EMIT_MIR_JSON", "1");
|
||||||
|
std::env::set_var("STAGE1_EMIT_MIR_JSON", "1");
|
||||||
|
if let Some(f) = cfg.file.as_ref() {
|
||||||
|
std::env::set_var("HAKO_STAGE1_INPUT", f);
|
||||||
|
std::env::set_var("NYASH_STAGE1_INPUT", f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cfg.hako_run {
|
||||||
|
std::env::set_var("NYASH_USE_STAGE1_CLI", "1");
|
||||||
|
std::env::set_var("HAKO_STAGE1_MODE", "run");
|
||||||
|
if let Some(f) = cfg.file.as_ref() {
|
||||||
|
std::env::set_var("HAKO_STAGE1_INPUT", f);
|
||||||
|
std::env::set_var("NYASH_STAGE1_INPUT", f);
|
||||||
|
}
|
||||||
|
}
|
||||||
if cfg.jit_exec {
|
if cfg.jit_exec {
|
||||||
std::env::set_var("NYASH_JIT_EXEC", "1");
|
std::env::set_var("NYASH_JIT_EXEC", "1");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,6 +57,10 @@ pub struct BuildConfig {
|
|||||||
pub struct EmitConfig {
|
pub struct EmitConfig {
|
||||||
pub emit_cfg: Option<String>,
|
pub emit_cfg: Option<String>,
|
||||||
pub emit_mir_json: Option<String>,
|
pub emit_mir_json: Option<String>,
|
||||||
|
pub emit_program_json: Option<String>,
|
||||||
|
pub hako_emit_program_json: bool,
|
||||||
|
pub hako_emit_mir_json: bool,
|
||||||
|
pub hako_run: bool,
|
||||||
pub program_json_to_mir: Option<String>,
|
pub program_json_to_mir: Option<String>,
|
||||||
pub emit_exe: Option<String>,
|
pub emit_exe: Option<String>,
|
||||||
pub emit_exe_nyrt: Option<String>,
|
pub emit_exe_nyrt: Option<String>,
|
||||||
|
|||||||
@ -43,6 +43,7 @@ pub struct CliConfig {
|
|||||||
pub jit_only: bool,
|
pub jit_only: bool,
|
||||||
pub jit_direct: bool,
|
pub jit_direct: bool,
|
||||||
pub emit_cfg: Option<String>,
|
pub emit_cfg: Option<String>,
|
||||||
|
pub emit_program_json: Option<String>,
|
||||||
pub cli_verbose: bool,
|
pub cli_verbose: bool,
|
||||||
pub run_task: Option<String>,
|
pub run_task: Option<String>,
|
||||||
pub load_ny_plugins: bool,
|
pub load_ny_plugins: bool,
|
||||||
@ -59,6 +60,9 @@ pub struct CliConfig {
|
|||||||
pub build_target: Option<String>,
|
pub build_target: Option<String>,
|
||||||
pub cli_usings: Vec<String>,
|
pub cli_usings: Vec<String>,
|
||||||
pub emit_mir_json: Option<String>,
|
pub emit_mir_json: Option<String>,
|
||||||
|
pub hako_emit_program_json: bool,
|
||||||
|
pub hako_emit_mir_json: bool,
|
||||||
|
pub hako_run: bool,
|
||||||
pub program_json_to_mir: Option<String>,
|
pub program_json_to_mir: Option<String>,
|
||||||
pub emit_exe: Option<String>,
|
pub emit_exe: Option<String>,
|
||||||
pub emit_exe_nyrt: Option<String>,
|
pub emit_exe_nyrt: Option<String>,
|
||||||
@ -127,6 +131,10 @@ impl CliConfig {
|
|||||||
emit: EmitConfig {
|
emit: EmitConfig {
|
||||||
emit_cfg: self.emit_cfg.clone(),
|
emit_cfg: self.emit_cfg.clone(),
|
||||||
emit_mir_json: self.emit_mir_json.clone(),
|
emit_mir_json: self.emit_mir_json.clone(),
|
||||||
|
emit_program_json: self.emit_program_json.clone(),
|
||||||
|
hako_emit_program_json: self.hako_emit_program_json,
|
||||||
|
hako_emit_mir_json: self.hako_emit_mir_json,
|
||||||
|
hako_run: self.hako_run,
|
||||||
program_json_to_mir: self.program_json_to_mir.clone(),
|
program_json_to_mir: self.program_json_to_mir.clone(),
|
||||||
emit_exe: self.emit_exe.clone(),
|
emit_exe: self.emit_exe.clone(),
|
||||||
emit_exe_nyrt: self.emit_exe_nyrt.clone(),
|
emit_exe_nyrt: self.emit_exe_nyrt.clone(),
|
||||||
@ -184,6 +192,7 @@ impl Default for CliConfig {
|
|||||||
jit_native_f64: false,
|
jit_native_f64: false,
|
||||||
jit_native_bool: false,
|
jit_native_bool: false,
|
||||||
emit_cfg: None,
|
emit_cfg: None,
|
||||||
|
emit_program_json: None,
|
||||||
jit_only: false,
|
jit_only: false,
|
||||||
jit_direct: false,
|
jit_direct: false,
|
||||||
cli_verbose: false,
|
cli_verbose: false,
|
||||||
@ -202,6 +211,9 @@ impl Default for CliConfig {
|
|||||||
build_target: None,
|
build_target: None,
|
||||||
cli_usings: Vec::new(),
|
cli_usings: Vec::new(),
|
||||||
emit_mir_json: None,
|
emit_mir_json: None,
|
||||||
|
hako_emit_program_json: false,
|
||||||
|
hako_emit_mir_json: false,
|
||||||
|
hako_run: false,
|
||||||
program_json_to_mir: None,
|
program_json_to_mir: None,
|
||||||
emit_exe: None,
|
emit_exe: None,
|
||||||
emit_exe_nyrt: None,
|
emit_exe_nyrt: None,
|
||||||
|
|||||||
@ -36,15 +36,37 @@ pub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[
|
|||||||
];
|
];
|
||||||
pub fn lookup_keyword(word: &str) -> Option<&'static str> {
|
pub fn lookup_keyword(word: &str) -> Option<&'static str> {
|
||||||
for (k, t) in KEYWORDS {
|
for (k, t) in KEYWORDS {
|
||||||
if *k == word {
|
if *k == word { return Some(*t); }
|
||||||
return Some(*t);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[
|
pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[
|
||||||
"box", "global", "function", "static", "if", "loop", "break", "return", "print", "nowait",
|
"box",
|
||||||
"include", "local", "outbox", "try", "throw", "using", "from",
|
"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",
|
||||||
|
];
|
||||||
@ -1,5 +1,5 @@
|
|||||||
use super::{MirBuilder, ValueId};
|
use super::{MirBuilder, ValueId};
|
||||||
use crate::ast::{ASTNode, BinaryOperator};
|
use crate::ast::ASTNode;
|
||||||
use crate::mir::loop_api::LoopBuilderApi; // for current_block()
|
use crate::mir::loop_api::LoopBuilderApi; // for current_block()
|
||||||
|
|
||||||
impl MirBuilder {
|
impl MirBuilder {
|
||||||
@ -13,35 +13,7 @@ impl MirBuilder {
|
|||||||
) -> Result<ValueId, String> {
|
) -> Result<ValueId, String> {
|
||||||
// Reserve a deterministic join id for debug region labeling
|
// Reserve a deterministic join id for debug region labeling
|
||||||
let join_id = self.debug_next_join_id();
|
let join_id = self.debug_next_join_id();
|
||||||
// Heuristic pre-pin: if condition is a comparison, evaluate its operands now and pin them
|
// Pre-pin heuristic was deprecated; keep operands as-is for predictability.
|
||||||
// so that subsequent branches can safely reuse these values across blocks.
|
|
||||||
// This leverages existing variable_map merges (PHI) at the merge block.
|
|
||||||
if crate::config::env::mir_pre_pin_compare_operands() {
|
|
||||||
if let ASTNode::BinaryOp {
|
|
||||||
operator,
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
..
|
|
||||||
} = &condition
|
|
||||||
{
|
|
||||||
match operator {
|
|
||||||
BinaryOperator::Equal
|
|
||||||
| BinaryOperator::NotEqual
|
|
||||||
| BinaryOperator::Less
|
|
||||||
| BinaryOperator::LessEqual
|
|
||||||
| BinaryOperator::Greater
|
|
||||||
| BinaryOperator::GreaterEqual => {
|
|
||||||
if let Ok(lhs_v) = self.build_expression((**left).clone()) {
|
|
||||||
let _ = self.pin_to_slot(lhs_v, "@if_lhs");
|
|
||||||
}
|
|
||||||
if let Ok(rhs_v) = self.build_expression((**right).clone()) {
|
|
||||||
let _ = self.pin_to_slot(rhs_v, "@if_rhs");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let condition_val = self.build_expression(condition)?;
|
let condition_val = self.build_expression(condition)?;
|
||||||
let condition_val = self.local_cond(condition_val);
|
let condition_val = self.local_cond(condition_val);
|
||||||
|
|||||||
@ -110,6 +110,7 @@ impl<'a> PhiMergeHelper<'a> {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// Ok(()) on success, Err(String) on failure
|
/// Ok(()) on success, Err(String) on failure
|
||||||
|
#[allow(dead_code)] // Reserved: explicit dst PHI merge for future use
|
||||||
pub fn merge_with_dst(
|
pub fn merge_with_dst(
|
||||||
&mut self,
|
&mut self,
|
||||||
dst: ValueId,
|
dst: ValueId,
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
BinOpKind, CompareOp, ConstValue, JoinFunction, JoinInst, JoinModule, MirLikeInst, VarId,
|
BinOpKind, CompareOp, ConstValue, JoinFunction, JoinInst, JoinModule, MirLikeInst,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// JoinModule を JSON としてシリアライズする
|
/// JoinModule を JSON としてシリアライズする
|
||||||
|
|||||||
@ -83,20 +83,6 @@ fn join_func_name(id: JoinFuncId) -> String {
|
|||||||
format!("join_func_{}", id.0)
|
format!("join_func_{}", id.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// JoinValue → MirConstValue 変換
|
|
||||||
fn join_value_to_mir_const(value: &JoinValue) -> Result<MirConstValue, JoinIrVmBridgeError> {
|
|
||||||
match value {
|
|
||||||
JoinValue::Int(v) => Ok(MirConstValue::Integer(*v)),
|
|
||||||
JoinValue::Bool(b) => Ok(MirConstValue::Bool(*b)),
|
|
||||||
JoinValue::Str(s) => Ok(MirConstValue::String(s.clone())),
|
|
||||||
JoinValue::Unit => Ok(MirConstValue::Null),
|
|
||||||
_ => Err(JoinIrVmBridgeError::new(format!(
|
|
||||||
"Unsupported JoinValue type: {:?}",
|
|
||||||
value
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Phase 27-shortterm S-4.3: JoinIR → VM 実行のエントリーポイント
|
/// Phase 27-shortterm S-4.3: JoinIR → VM 実行のエントリーポイント
|
||||||
///
|
///
|
||||||
/// ## Arguments
|
/// ## Arguments
|
||||||
@ -126,11 +112,11 @@ pub fn run_joinir_via_vm(
|
|||||||
debug_log!("[joinir_vm_bridge] Phase 27-shortterm S-4.3");
|
debug_log!("[joinir_vm_bridge] Phase 27-shortterm S-4.3");
|
||||||
debug_log!("[joinir_vm_bridge] Converting JoinIR to MIR for VM execution");
|
debug_log!("[joinir_vm_bridge] Converting JoinIR to MIR for VM execution");
|
||||||
|
|
||||||
// Step 1: JoinIR → MIR 変換 (with argument wrapper)
|
// Step 1: JoinIR → MIR 変換
|
||||||
let mir_module = convert_joinir_to_mir_with_args(join_module, entry_func, args)?;
|
let mir_module = convert_joinir_to_mir(join_module)?;
|
||||||
|
|
||||||
debug_log!(
|
debug_log!(
|
||||||
"[joinir_vm_bridge] Converted {} JoinIR functions to MIR (+ entry wrapper)",
|
"[joinir_vm_bridge] Converted {} JoinIR functions to MIR",
|
||||||
join_module.functions.len()
|
join_module.functions.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -142,11 +128,14 @@ pub fn run_joinir_via_vm(
|
|||||||
args.len()
|
args.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
let result_box = vm.execute_module(&mir_module)?;
|
// Convert JoinValue → VMValue (BoxRef 含む)
|
||||||
|
let vm_args: Vec<VMValue> = args.iter().cloned().map(|v| v.into_vm_value()).collect();
|
||||||
|
|
||||||
|
let entry_name = join_func_name(entry_func);
|
||||||
|
let result = vm.execute_function_with_args(&mir_module, &entry_name, &vm_args)?;
|
||||||
|
|
||||||
// Step 3: VMValue → JoinValue 変換
|
// Step 3: VMValue → JoinValue 変換
|
||||||
let vm_value = VMValue::from_nyash_box(result_box);
|
let join_result = JoinValue::from_vm_value(&result)
|
||||||
let join_result = JoinValue::from_vm_value(&vm_value)
|
|
||||||
.map_err(|e| JoinIrVmBridgeError::new(format!("Result conversion error: {}", e.message)))?;
|
.map_err(|e| JoinIrVmBridgeError::new(format!("Result conversion error: {}", e.message)))?;
|
||||||
|
|
||||||
debug_log!("[joinir_vm_bridge] Execution succeeded: {:?}", join_result);
|
debug_log!("[joinir_vm_bridge] Execution succeeded: {:?}", join_result);
|
||||||
@ -154,17 +143,8 @@ pub fn run_joinir_via_vm(
|
|||||||
Ok(join_result)
|
Ok(join_result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Phase 30.x: JoinIR → MIR 変換器 (with entry arguments)
|
/// Phase 30.x: JoinIR → MIR 変換器
|
||||||
///
|
fn convert_joinir_to_mir(join_module: &JoinModule) -> Result<MirModule, JoinIrVmBridgeError> {
|
||||||
/// Creates a wrapper "main" function that:
|
|
||||||
/// 1. Creates constant values for the entry arguments
|
|
||||||
/// 2. Calls the actual entry function with those arguments
|
|
||||||
/// 3. Returns the result
|
|
||||||
fn convert_joinir_to_mir_with_args(
|
|
||||||
join_module: &JoinModule,
|
|
||||||
entry_func: JoinFuncId,
|
|
||||||
args: &[JoinValue],
|
|
||||||
) -> Result<MirModule, JoinIrVmBridgeError> {
|
|
||||||
let mut mir_module = MirModule::new("joinir_bridge".to_string());
|
let mut mir_module = MirModule::new("joinir_bridge".to_string());
|
||||||
|
|
||||||
// Convert all JoinIR functions to MIR (entry function becomes "skip" or similar)
|
// Convert all JoinIR functions to MIR (entry function becomes "skip" or similar)
|
||||||
@ -180,75 +160,9 @@ fn convert_joinir_to_mir_with_args(
|
|||||||
mir_module.functions.insert(join_func_name(*func_id), mir_func);
|
mir_module.functions.insert(join_func_name(*func_id), mir_func);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a wrapper "main" function that calls the entry function with args
|
|
||||||
let wrapper = create_entry_wrapper(entry_func, args)?;
|
|
||||||
mir_module.functions.insert("main".to_string(), wrapper);
|
|
||||||
|
|
||||||
Ok(mir_module)
|
Ok(mir_module)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a wrapper "main" function that calls the entry function with constant arguments
|
|
||||||
fn create_entry_wrapper(
|
|
||||||
entry_func: JoinFuncId,
|
|
||||||
args: &[JoinValue],
|
|
||||||
) -> Result<MirFunction, JoinIrVmBridgeError> {
|
|
||||||
let entry_block = BasicBlockId(0);
|
|
||||||
|
|
||||||
let signature = FunctionSignature {
|
|
||||||
name: "main".to_string(),
|
|
||||||
params: vec![], // No parameters - args are hardcoded
|
|
||||||
return_type: MirType::Unknown,
|
|
||||||
effects: EffectMask::PURE,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut mir_func = MirFunction::new(signature, entry_block);
|
|
||||||
|
|
||||||
// Generate instructions to create constant arguments and call entry function
|
|
||||||
let mut instructions = Vec::new();
|
|
||||||
|
|
||||||
// Create constant values for each argument
|
|
||||||
let mut arg_value_ids = Vec::new();
|
|
||||||
for (i, arg) in args.iter().enumerate() {
|
|
||||||
let dst = ValueId(50000 + i as u32); // Use high ValueIds to avoid conflicts
|
|
||||||
arg_value_ids.push(dst);
|
|
||||||
|
|
||||||
let const_value = join_value_to_mir_const(arg)?;
|
|
||||||
instructions.push(MirInstruction::Const { dst, value: const_value });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create function name constant
|
|
||||||
let func_name_id = ValueId(59990);
|
|
||||||
instructions.push(MirInstruction::Const {
|
|
||||||
dst: func_name_id,
|
|
||||||
value: MirConstValue::String(join_func_name(entry_func)),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create Call instruction
|
|
||||||
let result_id = ValueId(59991);
|
|
||||||
instructions.push(MirInstruction::Call {
|
|
||||||
dst: Some(result_id),
|
|
||||||
func: func_name_id,
|
|
||||||
callee: None,
|
|
||||||
args: arg_value_ids,
|
|
||||||
effects: EffectMask::PURE,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set up entry block
|
|
||||||
finalize_block(
|
|
||||||
&mut mir_func,
|
|
||||||
entry_block,
|
|
||||||
instructions,
|
|
||||||
MirInstruction::Return { value: Some(result_id) },
|
|
||||||
);
|
|
||||||
|
|
||||||
debug_log!(
|
|
||||||
"[joinir_vm_bridge] Created entry wrapper with {} args",
|
|
||||||
args.len()
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(mir_func)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// JoinFunction → MirFunction 変換
|
/// JoinFunction → MirFunction 変換
|
||||||
fn convert_join_function_to_mir(
|
fn convert_join_function_to_mir(
|
||||||
join_func: &crate::mir::join_ir::JoinFunction,
|
join_func: &crate::mir::join_ir::JoinFunction,
|
||||||
|
|||||||
@ -884,20 +884,6 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
fn add_predecessor(&mut self, block: BasicBlockId, pred: BasicBlockId) -> Result<(), String> {
|
|
||||||
if let Some(ref mut function) = self.parent_builder.current_function {
|
|
||||||
if let Some(block) = function.get_block_mut(block) {
|
|
||||||
block.add_predecessor(pred);
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(format!("Block {} not found", block))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err("No current function".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================
|
// =============================================================
|
||||||
// Variable Map Utilities — snapshots and rebinding
|
// Variable Map Utilities — snapshots and rebinding
|
||||||
// =============================================================
|
// =============================================================
|
||||||
|
|||||||
@ -30,6 +30,7 @@ pub mod join_ir; // Phase 26-H: 関数正規化IR(JoinIR)
|
|||||||
pub mod join_ir_ops; // Phase 27.8: JoinIR 命令意味箱(ops box)
|
pub mod join_ir_ops; // Phase 27.8: JoinIR 命令意味箱(ops box)
|
||||||
pub mod join_ir_runner; // Phase 27.2: JoinIR 実行器(実験用)
|
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 join_ir_vm_bridge; // Phase 27-shortterm S-4: JoinIR → Rust VM ブリッジ
|
||||||
|
pub mod join_ir_vm_bridge_dispatch; // Phase 30 F-4.4: JoinIR VM ブリッジ dispatch helper
|
||||||
pub mod loop_form; // ControlForm::LoopShape の薄いエイリアス
|
pub mod loop_form; // ControlForm::LoopShape の薄いエイリアス
|
||||||
pub mod optimizer_passes; // optimizer passes (normalize/diagnostics)
|
pub mod optimizer_passes; // optimizer passes (normalize/diagnostics)
|
||||||
pub mod optimizer_stats; // extracted stats struct
|
pub mod optimizer_stats; // extracted stats struct
|
||||||
|
|||||||
@ -168,7 +168,8 @@ impl LocalScopeInspectorBox {
|
|||||||
///
|
///
|
||||||
/// LoopScopeShape::classify() は既に exit_live 情報を含んでいるため、
|
/// LoopScopeShape::classify() は既に exit_live 情報を含んでいるため、
|
||||||
/// 直接 classify() → needs_exit_phi() を使う方が効率的。
|
/// 直接 classify() → needs_exit_phi() を使う方が効率的。
|
||||||
pub fn is_available_in_all_with_scope(
|
#[allow(dead_code)] // Phase 30 F-1.3: will be used when LocalScopeInspectorBox is absorbed
|
||||||
|
pub(crate) fn is_available_in_all_with_scope(
|
||||||
&self,
|
&self,
|
||||||
var_name: &str,
|
var_name: &str,
|
||||||
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
|
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
|
||||||
|
|||||||
@ -195,7 +195,8 @@ impl LoopExitLivenessBox {
|
|||||||
/// let scope = LoopScopeShape::from_existing_boxes(...)?;
|
/// let scope = LoopScopeShape::from_existing_boxes(...)?;
|
||||||
/// let live = liveness_box.get_exit_live_from_scope(&scope);
|
/// let live = liveness_box.get_exit_live_from_scope(&scope);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn get_exit_live_from_scope(
|
#[allow(dead_code)] // Phase 30 F-1.2: will be used when LoopExitLivenessBox is absorbed
|
||||||
|
pub(crate) fn get_exit_live_from_scope(
|
||||||
&self,
|
&self,
|
||||||
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
|
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
|
||||||
) -> BTreeSet<String> {
|
) -> BTreeSet<String> {
|
||||||
|
|||||||
@ -278,7 +278,8 @@ impl LoopVarClassBox {
|
|||||||
/// let scope = LoopScopeShape::from_existing_boxes(...)?;
|
/// let scope = LoopScopeShape::from_existing_boxes(...)?;
|
||||||
/// let class = classifier.classify_with_scope("ch", &scope);
|
/// let class = classifier.classify_with_scope("ch", &scope);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn classify_with_scope(
|
#[allow(dead_code)] // Phase 30 F-1.1: will be used when LoopVarClassBox is absorbed
|
||||||
|
pub(crate) fn classify_with_scope(
|
||||||
&self,
|
&self,
|
||||||
var_name: &str,
|
var_name: &str,
|
||||||
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
|
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
|
||||||
@ -287,7 +288,8 @@ impl LoopVarClassBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Phase 30: LoopScopeShape を使って複数変数を一括分類
|
/// Phase 30: LoopScopeShape を使って複数変数を一括分類
|
||||||
pub fn classify_all_with_scope(
|
#[allow(dead_code)] // Phase 30 F-1.1: will be used when LoopVarClassBox is absorbed
|
||||||
|
pub(crate) fn classify_all_with_scope(
|
||||||
&self,
|
&self,
|
||||||
var_names: &[String],
|
var_names: &[String],
|
||||||
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
|
scope: &crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
|
||||||
|
|||||||
@ -9,7 +9,9 @@ pub(crate) enum MemberKind {
|
|||||||
Method,
|
Method,
|
||||||
Constructor,
|
Constructor,
|
||||||
PropertyComputed,
|
PropertyComputed,
|
||||||
|
#[allow(dead_code)] // Future: once property modifier
|
||||||
PropertyOnce,
|
PropertyOnce,
|
||||||
|
#[allow(dead_code)] // Future: birth_once property modifier
|
||||||
PropertyBirthOnce,
|
PropertyBirthOnce,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -208,9 +208,8 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
|||||||
"vm" => {
|
"vm" => {
|
||||||
crate::cli_v!("🚀 Hakorune VM Backend - Executing file: {} 🚀", filename);
|
crate::cli_v!("🚀 Hakorune VM Backend - Executing file: {} 🚀", filename);
|
||||||
// Route to primary VM path by default. Fallback is a last resort and must be explicitly enabled.
|
// Route to primary VM path by default. Fallback is a last resort and must be explicitly enabled.
|
||||||
let force_fallback =
|
let force_fallback = crate::config::env::vm_use_fallback();
|
||||||
std::env::var("NYASH_VM_USE_FALLBACK").ok().as_deref() == Some("1");
|
let route_trace = crate::config::env::vm_route_trace();
|
||||||
let route_trace = std::env::var("NYASH_VM_ROUTE_TRACE").ok().as_deref() == Some("1");
|
|
||||||
if force_fallback {
|
if force_fallback {
|
||||||
if route_trace {
|
if route_trace {
|
||||||
eprintln!("[vm-route] choose=fallback reason=env:NYASH_VM_USE_FALLBACK=1");
|
eprintln!("[vm-route] choose=fallback reason=env:NYASH_VM_USE_FALLBACK=1");
|
||||||
|
|||||||
@ -579,8 +579,70 @@ pub(super) fn lower_program(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn maybe_dump_mir(module: &MirModule) {
|
pub(super) fn maybe_dump_mir(module: &MirModule) {
|
||||||
|
// New: file dump path for offline analysis (Stage‑1/Stage‑B selfhost, ParserBox 等)
|
||||||
|
// Use env RUST_MIR_DUMP_PATH to write the MIR printer output to a file.
|
||||||
|
if let Some(path) = crate::config::env::rust_mir_dump_path() {
|
||||||
|
if let Ok(mut f) = std::fs::File::create(&path) {
|
||||||
|
let p = MirPrinter::new();
|
||||||
|
let _ = std::io::Write::write_all(&mut f, p.print_module(module).as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Existing: verbose flag dumps to stdout
|
||||||
if crate::config::env::cli_verbose() {
|
if crate::config::env::cli_verbose() {
|
||||||
let p = MirPrinter::new();
|
let p = MirPrinter::new();
|
||||||
println!("{}", p.print_module(module));
|
println!("{}", p.print_module(module));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn temp_path(name: &str) -> PathBuf {
|
||||||
|
let mut p = std::env::temp_dir();
|
||||||
|
p.push(format!("{}_{}", name, std::process::id()));
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dummy_module() -> MirModule {
|
||||||
|
MirModule::new("test-module".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn writes_file_when_dump_path_is_set() {
|
||||||
|
let path = temp_path("mir_dump_path");
|
||||||
|
let _ = fs::remove_file(&path);
|
||||||
|
std::env::set_var("RUST_MIR_DUMP_PATH", path.to_string_lossy().to_string());
|
||||||
|
|
||||||
|
maybe_dump_mir(&dummy_module());
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
path.exists(),
|
||||||
|
"maybe_dump_mir should write when RUST_MIR_DUMP_PATH is set"
|
||||||
|
);
|
||||||
|
let contents = fs::read_to_string(&path).unwrap_or_default();
|
||||||
|
assert!(
|
||||||
|
contents.contains("test-module"),
|
||||||
|
"dump should contain module name"
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = fs::remove_file(&path);
|
||||||
|
std::env::remove_var("RUST_MIR_DUMP_PATH");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn does_not_write_when_dump_path_is_unset() {
|
||||||
|
std::env::remove_var("RUST_MIR_DUMP_PATH");
|
||||||
|
let path = temp_path("mir_dump_path_unset");
|
||||||
|
let _ = fs::remove_file(&path);
|
||||||
|
|
||||||
|
maybe_dump_mir(&dummy_module());
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!path.exists(),
|
||||||
|
"maybe_dump_mir should not write when RUST_MIR_DUMP_PATH is unset"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -92,8 +92,11 @@ impl NyashRunner {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let groups = self.config.as_groups();
|
let groups = self.config.as_groups();
|
||||||
if let Some(code) = self.maybe_run_stage1_cli_stub(&groups) {
|
let skip_stage1_stub = groups.emit.hako_emit_program_json || groups.emit.hako_emit_mir_json;
|
||||||
std::process::exit(code);
|
if !skip_stage1_stub {
|
||||||
|
if let Some(code) = self.maybe_run_stage1_cli_stub(&groups) {
|
||||||
|
std::process::exit(code);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Early: direct MIR JSON execution (no source file). Experimental diagnostics/exec.
|
// Early: direct MIR JSON execution (no source file). Experimental diagnostics/exec.
|
||||||
if let Some(path) = groups.parser.mir_json_file.as_ref() {
|
if let Some(path) = groups.parser.mir_json_file.as_ref() {
|
||||||
@ -139,6 +142,50 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Emit Program(JSON v0) and exit
|
||||||
|
if let Some(path) = groups.emit.emit_program_json.as_ref() {
|
||||||
|
// Prefer Stage-1/.hako route when requested via hako-* flags or env
|
||||||
|
let use_hako = groups.emit.hako_emit_program_json
|
||||||
|
|| crate::config::env::stage1::emit_program_json()
|
||||||
|
|| crate::config::env::stage1::enabled();
|
||||||
|
if use_hako {
|
||||||
|
if let Err(e) = self.emit_program_json_v0(&groups, path) {
|
||||||
|
eprintln!("❌ emit-program-json error: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
} else {
|
||||||
|
println!("Program JSON written: {}", path);
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
} else if let Some(file) = groups.input.file.as_ref() {
|
||||||
|
match std::fs::read_to_string(file) {
|
||||||
|
Ok(code) => match crate::parser::NyashParser::parse_from_string(&code) {
|
||||||
|
Ok(ast) => {
|
||||||
|
let prog = crate::r#macro::ast_json::ast_to_json(&ast);
|
||||||
|
let out_path = std::path::Path::new(path);
|
||||||
|
if let Err(e) = std::fs::write(out_path, prog.to_string()) {
|
||||||
|
eprintln!("❌ emit-program-json write error: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
println!("Program JSON written: {}", out_path.display());
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
crate::runner::modes::common_util::diag::print_parse_error_with_context(
|
||||||
|
file, &code, &e,
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("❌ Error reading file {}: {}", file, e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("❌ --emit-program-json requires an input file");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Preprocess usings and directives (includes dep-tree log)
|
// Preprocess usings and directives (includes dep-tree log)
|
||||||
self.preprocess_usings_and_directives(&groups);
|
self.preprocess_usings_and_directives(&groups);
|
||||||
// JSON v0 bridge
|
// JSON v0 bridge
|
||||||
|
|||||||
@ -2,13 +2,9 @@ use super::super::NyashRunner;
|
|||||||
use nyash_rust::{ast::ASTNode, mir::MirCompiler, parser::NyashParser};
|
use nyash_rust::{ast::ASTNode, mir::MirCompiler, parser::NyashParser};
|
||||||
use std::{fs, process};
|
use std::{fs, process};
|
||||||
|
|
||||||
// Phase 30.x: JoinIR VM Bridge integration (experimental)
|
// Phase 30 F-4.4: JoinIR VM Bridge dispatch (experimental)
|
||||||
// Used only when NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_VM_BRIDGE=1
|
// Used only when NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_VM_BRIDGE=1
|
||||||
use crate::config::env::{joinir_experiment_enabled, joinir_vm_bridge_enabled};
|
use crate::mir::join_ir_vm_bridge_dispatch::try_run_joinir_vm_bridge;
|
||||||
use crate::mir::join_ir::{lower_funcscanner_trim_to_joinir, lower_skip_ws_to_joinir, JoinFuncId};
|
|
||||||
use crate::mir::join_ir::lowering::stage1_using_resolver::lower_stage1_usingresolver_to_joinir;
|
|
||||||
use crate::mir::join_ir_ops::JoinValue;
|
|
||||||
use crate::mir::join_ir_vm_bridge::run_joinir_via_vm;
|
|
||||||
|
|
||||||
impl NyashRunner {
|
impl NyashRunner {
|
||||||
/// Execute VM mode with full plugin initialization and AST prelude merge
|
/// Execute VM mode with full plugin initialization and AST prelude merge
|
||||||
@ -503,127 +499,10 @@ impl NyashRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 30.x: JoinIR VM Bridge experimental path
|
// Phase 30 F-4.4: JoinIR VM Bridge experimental path (consolidated dispatch)
|
||||||
// Activated when NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_VM_BRIDGE=1
|
// Activated when NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_VM_BRIDGE=1
|
||||||
// Currently only supports minimal_ssa_skip_ws.hako (Main.skip function)
|
// Routing logic is centralized in join_ir_vm_bridge_dispatch module
|
||||||
let joinir_path_attempted = if joinir_experiment_enabled() && joinir_vm_bridge_enabled() {
|
try_run_joinir_vm_bridge(&module_vm, quiet_pipe);
|
||||||
// Check if this module contains Main.skip/1 (minimal_ssa_skip_ws target)
|
|
||||||
// Note: function names include arity suffix like "Main.skip/1"
|
|
||||||
let has_main_skip = module_vm.functions.contains_key("Main.skip/1");
|
|
||||||
let has_trim = module_vm.functions.contains_key("FuncScannerBox.trim/1");
|
|
||||||
// Phase 30.x: Stage-1 UsingResolver (deletable block - start)
|
|
||||||
let has_stage1_usingresolver = module_vm.functions.contains_key("Stage1UsingResolverBox.resolve_for_source/5");
|
|
||||||
// Phase 30.x: Stage-1 UsingResolver (deletable block - end)
|
|
||||||
|
|
||||||
if has_main_skip {
|
|
||||||
eprintln!("[joinir/vm_bridge] Attempting JoinIR path for Main.skip");
|
|
||||||
match lower_skip_ws_to_joinir(&module_vm) {
|
|
||||||
Some(join_module) => {
|
|
||||||
// Get input argument from NYASH_JOINIR_INPUT or use default
|
|
||||||
let input = std::env::var("NYASH_JOINIR_INPUT")
|
|
||||||
.unwrap_or_else(|_| " abc".to_string());
|
|
||||||
eprintln!("[joinir/vm_bridge] Input: {:?}", input);
|
|
||||||
|
|
||||||
match run_joinir_via_vm(
|
|
||||||
&join_module,
|
|
||||||
JoinFuncId::new(0),
|
|
||||||
&[JoinValue::Str(input)],
|
|
||||||
) {
|
|
||||||
Ok(result) => {
|
|
||||||
let exit_code = match &result {
|
|
||||||
JoinValue::Int(v) => *v as i32,
|
|
||||||
JoinValue::Bool(b) => if *b { 1 } else { 0 },
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
eprintln!("[joinir/vm_bridge] ✅ JoinIR result: {:?}", result);
|
|
||||||
if !quiet_pipe {
|
|
||||||
println!("RC: {}", exit_code);
|
|
||||||
}
|
|
||||||
process::exit(exit_code);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("[joinir/vm_bridge] ❌ JoinIR execution failed: {:?}", e);
|
|
||||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
|
||||||
false // Continue to normal VM execution
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
eprintln!("[joinir/vm_bridge] lower_skip_ws_to_joinir returned None");
|
|
||||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if has_trim {
|
|
||||||
// Phase 30.x: FuncScannerBox.trim/1 JoinIR path
|
|
||||||
eprintln!("[joinir/vm_bridge] Attempting JoinIR path for FuncScannerBox.trim");
|
|
||||||
match lower_funcscanner_trim_to_joinir(&module_vm) {
|
|
||||||
Some(join_module) => {
|
|
||||||
// Get input argument from NYASH_JOINIR_INPUT or use default
|
|
||||||
let input = std::env::var("NYASH_JOINIR_INPUT")
|
|
||||||
.unwrap_or_else(|_| " abc ".to_string());
|
|
||||||
eprintln!("[joinir/vm_bridge] Input: {:?}", input);
|
|
||||||
|
|
||||||
match run_joinir_via_vm(
|
|
||||||
&join_module,
|
|
||||||
JoinFuncId::new(0),
|
|
||||||
&[JoinValue::Str(input)],
|
|
||||||
) {
|
|
||||||
Ok(result) => {
|
|
||||||
// trim returns a string, print it and exit with 0
|
|
||||||
eprintln!("[joinir/vm_bridge] ✅ JoinIR trim result: {:?}", result);
|
|
||||||
if !quiet_pipe {
|
|
||||||
match &result {
|
|
||||||
JoinValue::Str(s) => println!("{}", s),
|
|
||||||
_ => println!("{:?}", result),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("[joinir/vm_bridge] ❌ JoinIR trim failed: {:?}", e);
|
|
||||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
eprintln!("[joinir/vm_bridge] lower_funcscanner_trim_to_joinir returned None");
|
|
||||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Phase 30.x: Stage-1 UsingResolver JoinIR path (deletable block - start)
|
|
||||||
} else if has_stage1_usingresolver {
|
|
||||||
eprintln!("[joinir/vm_bridge] Attempting JoinIR path for Stage1UsingResolverBox.resolve_for_source");
|
|
||||||
match lower_stage1_usingresolver_to_joinir(&module_vm) {
|
|
||||||
Some(join_module) => {
|
|
||||||
eprintln!("[joinir/vm_bridge] ✅ Stage-1 JoinIR module generated ({} functions)", join_module.functions.len());
|
|
||||||
// Stage-1 requires ArrayBox/MapBox arguments which JoinValue doesn't support yet
|
|
||||||
// For now, just verify lowering works and fall back to VM
|
|
||||||
eprintln!("[joinir/vm_bridge] Note: ArrayBox/MapBox args not yet supported in JoinValue");
|
|
||||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path for actual execution");
|
|
||||||
false // Fall back to VM for now
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
eprintln!("[joinir/vm_bridge] lower_stage1_usingresolver_to_joinir returned None");
|
|
||||||
eprintln!("[joinir/vm_bridge] Falling back to normal VM path");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Phase 30.x: Stage-1 UsingResolver JoinIR path (deletable block - end)
|
|
||||||
} else {
|
|
||||||
false // No supported JoinIR target function
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
// Normal VM execution path (fallback or default)
|
|
||||||
if joinir_path_attempted {
|
|
||||||
// This branch is never reached because successful JoinIR path calls process::exit()
|
|
||||||
unreachable!("JoinIR path should have exited");
|
|
||||||
}
|
|
||||||
|
|
||||||
match vm.execute_module(&module_vm) {
|
match vm.execute_module(&module_vm) {
|
||||||
Ok(ret) => {
|
Ok(ret) => {
|
||||||
|
|||||||
@ -227,15 +227,13 @@ impl NyashRunner {
|
|||||||
extra_owned.push("--".to_string());
|
extra_owned.push("--".to_string());
|
||||||
extra_owned.push("--min-json".to_string());
|
extra_owned.push("--min-json".to_string());
|
||||||
}
|
}
|
||||||
// Phase 28.2 fix: Use Stage-B compiler for file-based compilation.
|
// Phase 28.2b fix: Use Stage-A (not Stage-B) for compiler.hako.
|
||||||
// Stage-A (_compile_source_to_json_v0) expects source CONTENT, not path.
|
// Stage-A expects source CONTENT via `--source <code>`, not a file path.
|
||||||
// Stage-B (StageBMain._do_compile_stage_b) reads files via FileBox.
|
// Stage-B requires FileBox (plugins), but Rust VM can't execute user-defined
|
||||||
extra_owned.push("--".to_string());
|
// boxes like StageBMain inside compiler.hako. Stage-A works without plugins.
|
||||||
extra_owned.push("--stage-b".to_string());
|
|
||||||
// Pass source file path to compiler.hako
|
|
||||||
extra_owned.push("--".to_string());
|
extra_owned.push("--".to_string());
|
||||||
extra_owned.push("--source".to_string());
|
extra_owned.push("--source".to_string());
|
||||||
extra_owned.push("tmp/ny_parser_input.ny".to_string());
|
extra_owned.push(code_ref.to_string());
|
||||||
if crate::config::env::ny_compiler_stage3() {
|
if crate::config::env::ny_compiler_stage3() {
|
||||||
extra_owned.push("--".to_string());
|
extra_owned.push("--".to_string());
|
||||||
extra_owned.push("--stage3".to_string());
|
extra_owned.push("--stage3".to_string());
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
* Constructs stage1_args based on execution mode (emit_program / emit_mir / run).
|
* Constructs stage1_args based on execution mode (emit_program / emit_mir / run).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use crate::config::env::stage1;
|
||||||
use crate::cli::CliGroups;
|
use crate::cli::CliGroups;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::process;
|
use std::process;
|
||||||
@ -15,6 +16,7 @@ pub(super) struct Stage1Args {
|
|||||||
pub env_script_args: Option<String>,
|
pub env_script_args: Option<String>,
|
||||||
pub source_env: Option<String>,
|
pub source_env: Option<String>,
|
||||||
pub progjson_env: Option<String>,
|
pub progjson_env: Option<String>,
|
||||||
|
pub emit_mir: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build stage1_args based on execution mode
|
/// Build stage1_args based on execution mode
|
||||||
@ -25,23 +27,11 @@ pub(super) struct Stage1Args {
|
|||||||
/// - run: run --backend <backend> <source.hako>
|
/// - run: run --backend <backend> <source.hako>
|
||||||
pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
||||||
// Prefer new env (NYASH_STAGE1_*) and fall back to legacy names to keep compatibility.
|
// Prefer new env (NYASH_STAGE1_*) and fall back to legacy names to keep compatibility.
|
||||||
let source = std::env::var("NYASH_STAGE1_INPUT")
|
let source = stage1::input_path()
|
||||||
.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());
|
|
||||||
|
|
||||||
let mode_env = std::env::var("NYASH_STAGE1_MODE")
|
let emit_program = stage1::emit_program_json();
|
||||||
.ok()
|
let emit_mir = stage1::emit_mir_json();
|
||||||
.map(|m| m.to_ascii_lowercase().replace('_', "-"));
|
|
||||||
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");
|
|
||||||
|
|
||||||
let mut args: Vec<String> = Vec::new();
|
let mut args: Vec<String> = Vec::new();
|
||||||
let mut source_env: Option<String> = None;
|
let mut source_env: Option<String> = None;
|
||||||
@ -57,9 +47,7 @@ pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
|||||||
args.push(src);
|
args.push(src);
|
||||||
source_env = args.last().cloned();
|
source_env = args.last().cloned();
|
||||||
} else if emit_mir {
|
} else if emit_mir {
|
||||||
if let Ok(pjson) = std::env::var("NYASH_STAGE1_PROGRAM_JSON")
|
if let Some(pjson) = stage1::program_json_path() {
|
||||||
.or_else(|_| std::env::var("STAGE1_PROGRAM_JSON"))
|
|
||||||
{
|
|
||||||
args.push("emit".into());
|
args.push("emit".into());
|
||||||
args.push("mir-json".into());
|
args.push("mir-json".into());
|
||||||
args.push("--from-program-json".into());
|
args.push("--from-program-json".into());
|
||||||
@ -81,9 +69,7 @@ pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
|||||||
process::exit(97);
|
process::exit(97);
|
||||||
});
|
});
|
||||||
args.push("run".into());
|
args.push("run".into());
|
||||||
let backend = std::env::var("NYASH_STAGE1_BACKEND")
|
let backend = stage1::backend_hint()
|
||||||
.ok()
|
|
||||||
.or_else(|| std::env::var("STAGE1_BACKEND").ok())
|
|
||||||
.unwrap_or_else(|| groups.backend.backend.clone());
|
.unwrap_or_else(|| groups.backend.backend.clone());
|
||||||
args.push("--backend".into());
|
args.push("--backend".into());
|
||||||
args.push(backend);
|
args.push(backend);
|
||||||
@ -110,5 +96,6 @@ pub(super) fn build_stage1_args(groups: &CliGroups) -> Stage1Args {
|
|||||||
env_script_args,
|
env_script_args,
|
||||||
source_env,
|
source_env,
|
||||||
progjson_env,
|
progjson_env,
|
||||||
|
emit_mir,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,8 @@
|
|||||||
* Sets default environment variables for Stage-1 CLI child process.
|
* Sets default environment variables for Stage-1 CLI child process.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use crate::config::env;
|
||||||
|
use crate::config::env::stage1;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
/// Configure environment variables for Stage-1 CLI child process
|
/// Configure environment variables for Stage-1 CLI child process
|
||||||
@ -23,12 +25,8 @@ pub(super) fn configure_stage1_env(
|
|||||||
|
|
||||||
// Unified Stage-1 env (NYASH_STAGE1_*) — derive from legacy if unset to keep compatibility.
|
// 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("NYASH_STAGE1_MODE").is_err() {
|
||||||
if std::env::var("STAGE1_EMIT_PROGRAM_JSON").ok().as_deref() == Some("1") {
|
if let Some(m) = stage1::mode() {
|
||||||
cmd.env("NYASH_STAGE1_MODE", "emit-program");
|
cmd.env("NYASH_STAGE1_MODE", m);
|
||||||
} 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") {
|
|
||||||
cmd.env("NYASH_STAGE1_MODE", "run");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,17 +51,17 @@ pub(super) fn configure_stage1_env(
|
|||||||
|
|
||||||
// Stage-1 unified input/backend (fallback to legacy)
|
// Stage-1 unified input/backend (fallback to legacy)
|
||||||
if std::env::var("NYASH_STAGE1_INPUT").is_err() {
|
if std::env::var("NYASH_STAGE1_INPUT").is_err() {
|
||||||
if let Ok(src) = std::env::var("STAGE1_SOURCE") {
|
if let Some(src) = stage1::input_path() {
|
||||||
cmd.env("NYASH_STAGE1_INPUT", src);
|
cmd.env("NYASH_STAGE1_INPUT", src);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if std::env::var("NYASH_STAGE1_BACKEND").is_err() {
|
if std::env::var("NYASH_STAGE1_BACKEND").is_err() {
|
||||||
if let Ok(be) = std::env::var("STAGE1_BACKEND") {
|
if let Some(be) = stage1::backend_hint().or_else(stage1::backend_alias_warned) {
|
||||||
cmd.env("NYASH_STAGE1_BACKEND", be);
|
cmd.env("NYASH_STAGE1_BACKEND", be);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if std::env::var("NYASH_STAGE1_PROGRAM_JSON").is_err() {
|
if std::env::var("NYASH_STAGE1_PROGRAM_JSON").is_err() {
|
||||||
if let Ok(pjson) = std::env::var("STAGE1_PROGRAM_JSON") {
|
if let Some(pjson) = stage1::program_json_path() {
|
||||||
cmd.env("NYASH_STAGE1_PROGRAM_JSON", pjson);
|
cmd.env("NYASH_STAGE1_PROGRAM_JSON", pjson);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,16 +76,16 @@ pub(super) fn configure_stage1_env(
|
|||||||
|
|
||||||
// Parser toggles
|
// Parser toggles
|
||||||
if std::env::var("NYASH_ENABLE_USING").is_err() {
|
if std::env::var("NYASH_ENABLE_USING").is_err() {
|
||||||
cmd.env("NYASH_ENABLE_USING", "1");
|
cmd.env("NYASH_ENABLE_USING", if env::enable_using() { "1" } else { "0" });
|
||||||
}
|
}
|
||||||
if std::env::var("HAKO_ENABLE_USING").is_err() {
|
if std::env::var("HAKO_ENABLE_USING").is_err() {
|
||||||
cmd.env("HAKO_ENABLE_USING", "1");
|
cmd.env("HAKO_ENABLE_USING", if env::enable_using() { "1" } else { "0" });
|
||||||
}
|
}
|
||||||
if std::env::var("NYASH_PARSER_STAGE3").is_err() {
|
if std::env::var("NYASH_PARSER_STAGE3").is_err() {
|
||||||
cmd.env("NYASH_PARSER_STAGE3", "1");
|
cmd.env("NYASH_PARSER_STAGE3", if env::parser_stage3() { "1" } else { "0" });
|
||||||
}
|
}
|
||||||
if std::env::var("HAKO_PARSER_STAGE3").is_err() {
|
if std::env::var("HAKO_PARSER_STAGE3").is_err() {
|
||||||
cmd.env("HAKO_PARSER_STAGE3", "1");
|
cmd.env("HAKO_PARSER_STAGE3", if env::parser_stage3() { "1" } else { "0" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modules list
|
// Modules list
|
||||||
@ -104,11 +102,11 @@ pub(super) fn configure_stage1_env(
|
|||||||
|
|
||||||
// Backend hint
|
// Backend hint
|
||||||
if std::env::var("STAGE1_BACKEND").is_err() {
|
if std::env::var("STAGE1_BACKEND").is_err() {
|
||||||
if let Some(be) = stage1_args
|
let be_cli = stage1_args
|
||||||
.windows(2)
|
.windows(2)
|
||||||
.find(|w| w[0] == "--backend")
|
.find(|w| w[0] == "--backend")
|
||||||
.map(|w| w[1].clone())
|
.map(|w| w[1].clone());
|
||||||
{
|
if let Some(be) = stage1::backend_hint().or(be_cli) {
|
||||||
cmd.env("STAGE1_BACKEND", be);
|
cmd.env("STAGE1_BACKEND", be);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,44 +20,117 @@ mod env;
|
|||||||
mod modules;
|
mod modules;
|
||||||
|
|
||||||
use super::NyashRunner;
|
use super::NyashRunner;
|
||||||
|
use crate::runner::stage1_bridge::args::Stage1Args;
|
||||||
|
use crate::config;
|
||||||
|
use crate::config::env::stage1;
|
||||||
use crate::cli::CliGroups;
|
use crate::cli::CliGroups;
|
||||||
|
use crate::mir::MirPrinter;
|
||||||
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
impl NyashRunner {
|
impl NyashRunner {
|
||||||
|
/// Emit Program(JSON v0) using Stage-1 stub and write to a file.
|
||||||
|
pub(crate) fn emit_program_json_v0(&self, groups: &CliGroups, out_path: &str) -> Result<(), String> {
|
||||||
|
// Resolve source path from CLI groups or env
|
||||||
|
let source = stage1::input_path()
|
||||||
|
.or_else(|| groups.input.file.as_ref().cloned())
|
||||||
|
.ok_or_else(|| "emit-program-json requires an input file".to_string())?;
|
||||||
|
|
||||||
|
// Build minimal args to force emit program-json
|
||||||
|
let args_result = Stage1Args {
|
||||||
|
args: vec!["emit".into(), "program-json".into(), source.clone()],
|
||||||
|
env_script_args: None,
|
||||||
|
source_env: Some(source.clone()),
|
||||||
|
progjson_env: None,
|
||||||
|
emit_mir: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Collect modules list (same as bridge)
|
||||||
|
let modules_list = modules::collect_modules_list();
|
||||||
|
|
||||||
|
// Prepare command
|
||||||
|
let exe = std::env::current_exe().unwrap_or_else(|_| {
|
||||||
|
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 = stage1::entry_override()
|
||||||
|
.unwrap_or_else(|| "lang/src/runner/stage1_cli.hako".to_string());
|
||||||
|
cmd.arg(&entry).arg("--");
|
||||||
|
for a in &args_result.args {
|
||||||
|
cmd.arg(a);
|
||||||
|
}
|
||||||
|
// Set environment variables for args
|
||||||
|
if let Some(src) = args_result.source_env.as_ref() {
|
||||||
|
cmd.env("STAGE1_SOURCE", src);
|
||||||
|
cmd.env("NYASH_STAGE1_INPUT", src);
|
||||||
|
}
|
||||||
|
if let Ok(text) = std::fs::read_to_string(&source) {
|
||||||
|
cmd.env("STAGE1_SOURCE_TEXT", text);
|
||||||
|
}
|
||||||
|
cmd.env("NYASH_USE_STAGE1_CLI", "1");
|
||||||
|
// Configure environment (shared helper)
|
||||||
|
env::configure_stage1_env(&mut cmd, &entry_fn, &args_result.args, modules_list);
|
||||||
|
cmd.env("STAGE1_EMIT_PROGRAM_JSON", "1");
|
||||||
|
|
||||||
|
let output = cmd
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("stage1 emit program-json spawn failed: {}", e))?;
|
||||||
|
if !output.stderr.is_empty() && std::env::var("STAGE1_CLI_DEBUG").ok().as_deref() == Some("1") {
|
||||||
|
let _ = std::io::stderr().write_all(&output.stderr);
|
||||||
|
}
|
||||||
|
if !output.status.success() {
|
||||||
|
if !output.stdout.is_empty() {
|
||||||
|
let _ = std::io::stdout().write_all(&output.stdout);
|
||||||
|
}
|
||||||
|
if !output.stderr.is_empty() {
|
||||||
|
let _ = std::io::stderr().write_all(&output.stderr);
|
||||||
|
}
|
||||||
|
return Err(format!(
|
||||||
|
"stage1 emit program-json exited with code {:?}",
|
||||||
|
output.status.code()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
|
let line = crate::runner::modes::common_util::selfhost::json::first_json_v0_line(&stdout)
|
||||||
|
.ok_or_else(|| "stage1 emit program-json did not produce Program(JSON v0)".to_string())?;
|
||||||
|
std::fs::write(out_path, line)
|
||||||
|
.map_err(|e| format!("write {} failed: {}", out_path, e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// If enabled, run the Stage-1 CLI stub as a child process and return its exit code.
|
/// If enabled, run the Stage-1 CLI stub as a child process and return its exit code.
|
||||||
/// Returns None when the bridge is not engaged.
|
/// Returns None when the bridge is not engaged.
|
||||||
pub(crate) fn maybe_run_stage1_cli_stub(&self, groups: &CliGroups) -> Option<i32> {
|
pub(crate) fn maybe_run_stage1_cli_stub(&self, groups: &CliGroups) -> Option<i32> {
|
||||||
// Temporary trace: confirm the bridge is evaluated
|
// Temporary trace: confirm the bridge is evaluated
|
||||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("2") {
|
if config::env::cli_verbose_level() == 2 {
|
||||||
eprintln!("[stage1-bridge/trace] maybe_run_stage1_cli_stub invoked");
|
eprintln!("[stage1-bridge/trace] maybe_run_stage1_cli_stub invoked");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guard: skip if child invocation
|
// Guard: skip if child invocation
|
||||||
if std::env::var("NYASH_STAGE1_CLI_CHILD").ok().as_deref() == Some("1") {
|
if stage1::child_invocation() {
|
||||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("2") {
|
if config::env::cli_verbose_level() == 2 {
|
||||||
eprintln!("[stage1-bridge/trace] skip: NYASH_STAGE1_CLI_CHILD=1");
|
eprintln!("[stage1-bridge/trace] skip: NYASH_STAGE1_CLI_CHILD=1");
|
||||||
}
|
}
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guard: skip if not enabled
|
// Guard: skip if not enabled
|
||||||
if std::env::var("NYASH_USE_STAGE1_CLI").ok().as_deref() != Some("1") {
|
if !stage1::enabled() {
|
||||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("2") {
|
if config::env::cli_verbose_level() == 2 {
|
||||||
eprintln!("[stage1-bridge/trace] skip: NYASH_USE_STAGE1_CLI!=1");
|
eprintln!("[stage1-bridge/trace] skip: NYASH_USE_STAGE1_CLI!=1");
|
||||||
}
|
}
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1")
|
if config::env::cli_verbose() || config::env::cli_verbose_level() == 2 {
|
||||||
|| std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("2")
|
|
||||||
{
|
|
||||||
eprintln!("[stage1-bridge/debug] NYASH_USE_STAGE1_CLI=1 detected");
|
eprintln!("[stage1-bridge/debug] NYASH_USE_STAGE1_CLI=1 detected");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locate Stage-1 CLI entry
|
// Locate Stage-1 CLI entry
|
||||||
let entry = std::env::var("STAGE1_CLI_ENTRY")
|
let entry = stage1::entry_override()
|
||||||
.or_else(|_| std::env::var("HAKORUNE_STAGE1_ENTRY"))
|
.unwrap_or_else(|| "lang/src/runner/stage1_cli.hako".to_string());
|
||||||
.unwrap_or_else(|_| "lang/src/runner/stage1_cli.hako".to_string());
|
|
||||||
if !Path::new(&entry).exists() {
|
if !Path::new(&entry).exists() {
|
||||||
eprintln!("[stage1-cli] entry not found: {}", entry);
|
eprintln!("[stage1-cli] entry not found: {}", entry);
|
||||||
return Some(97);
|
return Some(97);
|
||||||
@ -109,6 +182,64 @@ impl NyashRunner {
|
|||||||
// Configure environment
|
// Configure environment
|
||||||
env::configure_stage1_env(&mut cmd, &entry_fn, &args_result.args, modules_list);
|
env::configure_stage1_env(&mut cmd, &entry_fn, &args_result.args, modules_list);
|
||||||
|
|
||||||
|
// Emit-mir mode: capture stdout for Program(JSON v0) and lower in Rust to engage maybe_dump_mir.
|
||||||
|
if args_result.emit_mir {
|
||||||
|
let output = match cmd.output() {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("[stage1-cli] failed to spawn stub: {}", e);
|
||||||
|
return Some(97);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let code = output.status.code().unwrap_or(1);
|
||||||
|
if code != 0 {
|
||||||
|
if !output.stderr.is_empty() {
|
||||||
|
let _ = std::io::stderr().write_all(&output.stderr);
|
||||||
|
}
|
||||||
|
return Some(code);
|
||||||
|
}
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
|
let line = match crate::runner::modes::common_util::selfhost::json::first_json_v0_line(&stdout) {
|
||||||
|
Some(l) => l,
|
||||||
|
None => {
|
||||||
|
eprintln!("[stage1-cli] emit-mir: no Program(JSON v0) found in stub output");
|
||||||
|
return Some(98);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let module = match super::json_v0_bridge::parse_json_v0_to_module(&line) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("[stage1-cli] emit-mir: Program(JSON v0) parse error: {}", e);
|
||||||
|
return Some(98);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
super::json_v0_bridge::maybe_dump_mir(&module);
|
||||||
|
|
||||||
|
let groups = self.config.as_groups();
|
||||||
|
if groups.debug.dump_mir {
|
||||||
|
let mut printer = if groups.debug.mir_verbose {
|
||||||
|
MirPrinter::verbose()
|
||||||
|
} else {
|
||||||
|
MirPrinter::new()
|
||||||
|
};
|
||||||
|
if groups.debug.mir_verbose_effects {
|
||||||
|
printer.set_show_effects_inline(true);
|
||||||
|
}
|
||||||
|
println!("{}", printer.print_module(&module));
|
||||||
|
}
|
||||||
|
if let Some(path) = groups.emit.emit_mir_json.as_ref() {
|
||||||
|
let p = std::path::Path::new(path);
|
||||||
|
if let Err(e) =
|
||||||
|
crate::runner::mir_json_emit::emit_mir_json_for_harness_bin(&module, p)
|
||||||
|
{
|
||||||
|
eprintln!("❌ MIR JSON emit error: {}", e);
|
||||||
|
return Some(98);
|
||||||
|
}
|
||||||
|
println!("MIR JSON written: {}", p.display());
|
||||||
|
}
|
||||||
|
return Some(0);
|
||||||
|
}
|
||||||
|
|
||||||
crate::cli_v!(
|
crate::cli_v!(
|
||||||
"[stage1-cli] delegating to stub: {} -- {}",
|
"[stage1-cli] delegating to stub: {} -- {}",
|
||||||
entry,
|
entry,
|
||||||
|
|||||||
@ -22,7 +22,7 @@ fn require_experiment_toggle() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
#[ignore] // PHI/LoopForm バグあり - Phase 30 の PHI canary として据え置き
|
||||||
fn joinir_runner_minimal_skip_ws_executes() {
|
fn joinir_runner_minimal_skip_ws_executes() {
|
||||||
if !require_experiment_toggle() {
|
if !require_experiment_toggle() {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user