Phase 21.7 normalization: optimization pre-work + bench harness expansion
- Add opt-in optimizations (defaults OFF) - Ret purity verifier: NYASH_VERIFY_RET_PURITY=1 - strlen FAST enhancement for const handles - FAST_INT gate for same-BB SSA optimization - length cache for string literals in llvmlite - Expand bench harness (tools/perf/microbench.sh) - Add branch/call/stringchain/arraymap/chip8/kilo cases - Auto-calculate ratio vs C reference - Document in benchmarks/README.md - Compiler health improvements - Unify PHI insertion to insert_phi_at_head() - Add NYASH_LLVM_SKIP_BUILD=1 for build reuse - Runtime & safety enhancements - Clarify Rust/Hako ownership boundaries - Strengthen receiver localization (LocalSSA/pin/after-PHIs) - Stop excessive PluginInvoke→BoxCall rewrites - Update CURRENT_TASK.md, docs, and canaries 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
209
CURRENT_TASK.md
209
CURRENT_TASK.md
@ -1,5 +1,173 @@
|
|||||||
# Current Task — Phase 21.7(Normalization & Unification: Methodize Static Boxes)
|
# Current Task — Phase 21.7(Normalization & Unification: Methodize Static Boxes)
|
||||||
|
|
||||||
|
Update (2025-11-12 — Optimization pre-work and bench harness)
|
||||||
|
- Implemented (opt‑in, defaults OFF)
|
||||||
|
- Ret block purity verifier: NYASH_VERIFY_RET_PURITY=1 → Return直前の副作用命令をFail‑Fast(Const/Copy/Phi/Nopのみ許可)。構造純化の安全弁として維持。
|
||||||
|
- strlen FAST 強化: newbox(StringBox,const) だけでなく const ハンドルからの length() も即値畳み込み(NYASH_LLVM_FAST=1時)。
|
||||||
|
- Kernel hint: nyrt_string_length に #[inline(always)] を付与(AOT呼び出しのオーバーヘッド抑制)。
|
||||||
|
- FAST_INT ゲート: NYASH_LLVM_FAST_INT=1 で BinOp/Compare が同一ブロックSSA(vmap)を優先利用し、resolver/PHIの局所化コストを回避(文字列連結は除外)。
|
||||||
|
- length キャッシュ(llvmlite 最低限): NYASH_LLVM_FAST=1 時、文字列リテラル由来ハンドルに対する length()/len の i64 即値を resolver.length_cache に格納し、ループ内で再利用。
|
||||||
|
- Added
|
||||||
|
- Bench harness 拡張(tools/perf/microbench.sh): branch/call/stringchain/arraymap/chip8/kilo を追加。各ケースは C 参照付きで ratio を自動算出。
|
||||||
|
- Bench doc: benchmarks/README.md に一覧・実行例を追加。
|
||||||
|
- Preliminary results(LLVM/EXE, ノイズ抑制: NYASH_SKIP_TOML_ENV=1 NYASH_DISABLE_PLUGINS=1)
|
||||||
|
- 良好: call / stringchain / kilo → ratio < 100%(Nyash側が軽い)
|
||||||
|
- 要改善: branch / arraymap / chip8 → ratio ≈ 200%(Cの約1/2速度)
|
||||||
|
|
||||||
|
Compiler health(E0308対応)
|
||||||
|
- PHI 挿入のコールサイトを監査し、`insert_phi_at_head(&mut MirFunction, ...)` へ統一(`current_function.as_mut()` 経由で渡す)。`cargo check` は通過済み。
|
||||||
|
- `tools/ny_mir_builder.sh --emit exe` 実行時にワークスペース再ビルドが走る環境では、必要に応じて `NYASH_LLVM_SKIP_BUILD=1` を指定し、既存の `ny-llvmc` / `nyash_kernel` ビルド成果物を用いる運用を併記。
|
||||||
|
- Next actions(AOT集中; 既定OFFのまま段階適用)
|
||||||
|
1) ループ不変の簡易ホイスティング(strlen/比較/分母mod など)
|
||||||
|
2) FAST_INT の適用範囲精緻化(不要cast/extendの抑制、CFG保存のまま)
|
||||||
|
3) array/map のホットパス検討(AOT経路のみ・診断OFF)
|
||||||
|
4) 再ベンチ(branch/arraymap/chip8 を再測)、目標: ratio ≤ 125%(Cの8割)
|
||||||
|
5) 既定OFFで挙動不変・Ret純化ガード緑維持・小差分リバーシブルを堅持
|
||||||
|
|
||||||
|
Toggles(再掲)
|
||||||
|
- NYASH_LLVM_FAST=1 … strlen FAST + literal length キャッシュ
|
||||||
|
- NYASH_LLVM_FAST_INT=1 … i64 ホットパス(同一BB SSA 優先)
|
||||||
|
- NYASH_VERIFY_RET_PURITY=1 … Ret 純化ガード(開発ガード)
|
||||||
|
- NYASH_LLVM_SKIP_BUILD=1 … ny_mir_builder.sh の再ビルド抑止(既存バイナリ再利用)
|
||||||
|
- NYASH_MIR_DEV_IDEMP=1 … MIR normalize/dev 用の再実行安定化(idempotence)メタ。既定OFF(診断専用)
|
||||||
|
- HAKO_MIR_NORMALIZE_PRINT=1 … AotPrep(.hako) 側で print→externcall(env.console.log) を正規化(CFG不変・既定OFF)
|
||||||
|
- HAKO_MIR_NORMALIZE_REF=1 … AotPrep(.hako) 側で ref_get/ref_set→boxcall(getField/setField) を正規化(CFG不変・既定OFF)
|
||||||
|
- HAKO_SILENT_TAGS=0|1 … v2ランナーのタグ静音トグル(1=静音:既定、0=生ログ)
|
||||||
|
|
||||||
|
|
||||||
|
Wrap‑up (this session)
|
||||||
|
- Rust/Hako ownership clarified(docs/development/normalization/ownership.md)。Hako=意味論正規化、Rust=構造/安全。
|
||||||
|
- Runtime autoload for using.dylib(guarded)を Runner 初期化に集約。箱名は nyash_box.toml から推論して登録。
|
||||||
|
- Builder/VM safety: 受信ローカライズを強化(LocalSSA/pin/after‑PHIs/tail)。未定義受信は同一BBの直近 NewBox<Box> で構造回復(dev 安全弁)に限定。
|
||||||
|
- PluginInvoke→BoxCall の過剰な書換えを停止(意味論は Hako 側の methodize に集約)。
|
||||||
|
- カナリア:
|
||||||
|
- phase217_methodize_canary.sh → PASS(rc=5)。
|
||||||
|
- phase217_methodize_json_canary.sh → v1 + mir_call present(Method が優先、Global は経過容認)。
|
||||||
|
- phase217_methodize_json_strict.sh を追加(selfhost‑only 仕上げ用ゲート、現状は Stage‑B child パース修正が必要)。
|
||||||
|
|
||||||
|
## Phase 21.5 — Optimization Checklist(進行管理)
|
||||||
|
|
||||||
|
- [x] パス分割(AotPrep: StrlenFold / LoopHoist / ConstDedup / CollectionsHot / BinopCSE)
|
||||||
|
- [x] CollectionsHot: Array/Map の boxcall→externcall(既定OFF・トグル)
|
||||||
|
- [x] Map key モード導入(`NYASH_AOT_MAP_KEY_MODE={h|i64|hh|auto}`)
|
||||||
|
- [x] LoopHoist v1: compare/mod/div 右辺 const の前出し(参照 const を先頭へ)
|
||||||
|
- [x] BinopCSE v1: `+/*` の同一式を再利用、`copy` で伝播
|
||||||
|
- [x] VERIFY ガード常時ON(`NYASH_VERIFY_RET_PURITY=1`)で測定
|
||||||
|
- [x] ベンチ新規(`linidx`/`maplin`)追加・実測を README に反映
|
||||||
|
- [x] LoopHoist v2: `+/*` 右項 const の連鎖前出し、fix‑point(≤3周)の安全適用(定数 binop 折畳み+hoist 実装)
|
||||||
|
- [x] BinopCSE v2: 線形 `i*n` の共通化強化(copy 連鎖の正規化・左右交換可の正規化)
|
||||||
|
- [x] CollectionsHot v2: Array/Map index/key の式キー共有(同一ブロック短窓で get/set の a0 を統一)
|
||||||
|
- [x] Map auto 精緻化: `_is_const_or_linear` の再帰判定強化(copy 追跡+div/rem の定数片を線形扱い)
|
||||||
|
- [x] Rust normalize.rs の借用修正(E0499/E0502解消)+ dev idempotence メタ追加(既定OFF)
|
||||||
|
- [x] 正規化の .hako 化(新箱でモジュール化):NormalizePrint / NormalizeRef を AotPrep から opt-in で呼び出し
|
||||||
|
- [ ] Idempotence: 置換済みマーク(devメタ)で再実行の結果不変を担保
|
||||||
|
- [ ] ベンチ更新: `arraymap`/`matmul`/`sieve`/`linidx`/`maplin` EXE 比率 ≤ 125%(arraymap/matmul優先)
|
||||||
|
|
||||||
|
### 21.5 引き継ぎメモ(ベンチ&観測)
|
||||||
|
- 新規トグル/修正
|
||||||
|
- `NYASH_LLVM_DUMP_MIR_IN=/tmp/xxx.json`(AOT 入力MIRダンプ)
|
||||||
|
- dump_mir.sh: provider/selfhost-first + min fallback で実MIRダンプを安定化
|
||||||
|
- microbench: `PERF_USE_PROVIDER=1` で jsonfrag 強制を解除(実MIRでの突合せ用)
|
||||||
|
- ベンチ現状(EXE, C=100%)
|
||||||
|
- arraymap: 150–300%(分散あり) / matmul: 300% / sieve: 200% / linidx: 100% / maplin: 200%
|
||||||
|
- 次のアクション
|
||||||
|
1) arraymap/matmul の実MIR(provider or `NYASH_LLVM_DUMP_MIR_IN`)で index SSA 共有の崩れを特定
|
||||||
|
2) CollectionsHot v2.1: 同一ブロック短窓 get→set で a0 の一致を強制(式キー統一の取りこぼし追加)
|
||||||
|
3) BinopCSE 小窓 fix‑point 拡張(隣接ブロックまでの `i*n+k` 共有; 制御不変・既定OFF)
|
||||||
|
4) 再ベンチ(arraymap/matmul/maplin)→ `benchmarks/README.md` に 日付/トグル/ratio ≤ 125% を追記
|
||||||
|
5) provider emit の失敗パターンを継続調査(emit ラッパの fallback/guard を追加調整)
|
||||||
|
|
||||||
|
Decisions
|
||||||
|
- 既定挙動は変更しない(dev では methodize トグルON、CI/ユーザ既定は従来維持)。
|
||||||
|
- Rust 層は正規化を持たず、構造のみ(SSA/PHI/LocalSSA/安全弁)。Hako が mir_call(Method) のSSOT。
|
||||||
|
|
||||||
|
Rust normalize.rs sunset plan(記録)
|
||||||
|
- 目的: normalize.rs の責務を .hako 側へ移し、Rust 側は既定OFF→撤去へ。
|
||||||
|
- 段階:
|
||||||
|
- 1) dev カナリア期間は `HAKO_MIR_NORMALIZE_*`(PRINT/REF/ARRAY)を opt-in で運用(既定OFF)。
|
||||||
|
- 2) AotPrep 経由の normalize が十分に安定後、dev/quick で既定ONへ昇格(CI は段階移行)。
|
||||||
|
- 3) Rust 側 normalize は env で明示 ON のみ許可(互換ガードを残す)。
|
||||||
|
- 4) 既定ON 状態で 2 週安定を確認後、Rust normalize のビルド配線を外し、最終撤去。
|
||||||
|
- 付随: 新規/更新トグルは env 一覧へ追記し、昇格時にデフォルト化→古いトグルは段階的に非推奨化とする。
|
||||||
|
|
||||||
|
Follow‑ups (carried to backlog)
|
||||||
|
- Stage‑B child の "Unexpected token FN" 修正(selfhost‑first/strict ゲート用)。
|
||||||
|
- Provider 出力の callee を Method へ寄せ、Global 経過容認を段階縮小。
|
||||||
|
- core_bridge の methodize ブリッジは bring‑up 用。Hako 既定化後に撤去。
|
||||||
|
|
||||||
|
|
||||||
|
# Next Active Task — Phase 21.16(Pre‑Optimization / 最適化前フェーズへ戻す)
|
||||||
|
|
||||||
|
Intent
|
||||||
|
- 正規化の骨格が揃った段階で、最適化前の安定化に戻す(意味論は不変、構造/観測の強化)。
|
||||||
|
|
||||||
|
Scope(当面の作業)
|
||||||
|
1) Optimizer 前段の安定化(構造のみ)
|
||||||
|
- normalize_legacy/ref_field_access は構造変換の範囲に留める(意味論を変えない)。
|
||||||
|
- NYASH_MIR_DISABLE_OPT/HAKO_MIR_DISABLE_OPT で最適化全休のゲートを尊重(既存)。
|
||||||
|
2) Baseline/観測
|
||||||
|
- quick/integration の rc/出力をゴールデン更新(mir_call 既定はまだOFF)。
|
||||||
|
- crate_exec タイムボックスを EXE テストへ段階横展開。
|
||||||
|
- プラグイン parity の軽量観測(autoload ガード有効時)。
|
||||||
|
3) テスト整備
|
||||||
|
- 受信未定義の負テスト追加(Builder で防止)。dev 安全弁を将来的にOFFにできる状態を作る。
|
||||||
|
- v1 + unified の canary は dev のみ strict を維持(CI は現状どおり)。
|
||||||
|
|
||||||
|
Exit criteria(21.16)
|
||||||
|
- Optimizer OFF でも quick/integration/plugins が安定緑。
|
||||||
|
- 受信ローカライズの穴が負テストで検知可能(dev 安全弁に依存しない)。
|
||||||
|
- v1/unified は dev プロファイルで常時確認可能(既存トグルで担保)。
|
||||||
|
|
||||||
|
Handoff Summary (ready for restart)
|
||||||
|
- Delegate v1 fixed (provider‑first): tools/hakorune_emit_mir.sh now prefers env.mirbuilder.emit to emit MIR(JSON v1). Legacy CLI converter kept as fallback with NYASH_JSON_SCHEMA_V1=1/NYASH_MIR_UNIFIED_CALL=1.
|
||||||
|
- Selfhost‑first stabilized for mini cases: HAKO_SELFHOST_TRY_MIN=1 in dev profile; min runner (BuilderRunnerMinBox.run) is used when builder fails.
|
||||||
|
- Methodize canaries:
|
||||||
|
- phase217_methodize_canary.sh → compile‑run rc=5 PASS(semantics)
|
||||||
|
- phase217_methodize_json_canary.sh → schema_version present + mir_call present(Method preferred; Global tolerated for now)
|
||||||
|
- Dev one‑gun toggles (enable_mirbuilder_dev_env.sh): HAKO_STAGEB_FUNC_SCAN=1, HAKO_MIR_BUILDER_FUNCS=1, HAKO_MIR_BUILDER_CALL_RESOLVE=1, NYASH_JSON_SCHEMA_V1=1, NYASH_MIR_UNIFIED_CALL=1, HAKO_SELFHOST_TRY_MIN=1.
|
||||||
|
|
||||||
|
Next Steps (post‑restart)
|
||||||
|
1) Selfhost‑first child parse fix(Stage‑B): resolve the known “Unexpected token FN” in compiler_stageb chain; target [builder/selfhost‑first:ok] without min fallback.
|
||||||
|
2) Provider output callee finishing: prefer Method callee so JSON canary asserts Method strictly (now Global allowed temporarily).
|
||||||
|
3) Consolidate remaining legacy converter usage to provider where possible; keep defaults unchanged.
|
||||||
|
4) Docs: 21.7 checklist updated; core_bridge methodize remains diagnostic‑only (OFF) with a removal plan once Hako methodize is default.
|
||||||
|
|
||||||
|
|
||||||
|
# New Active Task — Phase 21.6(Dual‑Emit Parity & C‑line Readiness)
|
||||||
|
|
||||||
|
Intent
|
||||||
|
- Provider(Rust)と Selfhost(Hako)で同一の MIR(JSON) を出力できることを確認し、生成時間を計測・比較する。AOT(ny‑llvmc/crate)も O0 既定で安定させる。
|
||||||
|
|
||||||
|
What’s done (this session)
|
||||||
|
- Perf ツール追加:
|
||||||
|
- MIR emit bench: `tools/perf/bench_hakorune_emit_mir.sh`
|
||||||
|
- AOT bench: `tools/perf/bench_ny_mir_builder.sh`
|
||||||
|
- 構造比較: `tools/perf/compare_mir_json.sh`
|
||||||
|
- Dual emit + 比較 + ベンチ: `tools/perf/dual_emit_compare.sh`
|
||||||
|
- Docs 整備:
|
||||||
|
- `docs/guides/perf/benchmarks.md`(手順/トグル)
|
||||||
|
- README/README.ja にパフォーマンス導線を追記
|
||||||
|
- `docs/ENV_VARS.md` にベンチ関連のトグルを追加
|
||||||
|
- 小最適化(意味論不変):
|
||||||
|
- Builder の不要 clone / finalize 二重呼び出し削減
|
||||||
|
- VM console 出力の to_string の枝刈り(String/StringBox/Null 先出し)
|
||||||
|
- VM 受信未定義の負テストを 3 本追加(Array/Map/String)。fail‑fast を監視
|
||||||
|
|
||||||
|
Known issue / Action
|
||||||
|
- Provider 経路(tools/hakorune_emit_mir.sh の provider‑first)が一部環境で `Program→MIR delegate failed` になることがある。
|
||||||
|
- 原因: Stage‑B 実行の stdout にログが混在し JSON 抽出が空になるケース。
|
||||||
|
- 対策(次実装): ラッパを tmp 経由の CLI 変換(`--program-json-to-mir`)へフォールバックさせる安全化。selfhost‑first も明示トグルで維持。
|
||||||
|
|
||||||
|
Plan (next after restart)
|
||||||
|
1) tools/hakorune_emit_mir.sh を安全化(Stage‑B→tmp→CLI 変換フォールバック)。stderr/JSON 分離を強化
|
||||||
|
2) 代表 4 ケース(json_pp / json_query_min / json_lint / json_query)で dual‑emit を 5 回実行し、21.6 表に p50/Parity を記入
|
||||||
|
3) Parity 不一致はカテゴリ化(callee 種別/順序/PHI/メタ)して修正元を特定(provider か selfhost)
|
||||||
|
4) AOT(ny‑llvmc)O0 の 3 回ベンチを取得、必要なら O1 スポットも取得
|
||||||
|
|
||||||
|
Tracking
|
||||||
|
- 記録台帳: `docs/development/roadmap/phases/phase-21.6/README.md` の表へ随時追記(チェックボックス方式)
|
||||||
|
|
||||||
|
|
||||||
Phase 21.6 wrap-up
|
Phase 21.6 wrap-up
|
||||||
- Parser(Stage‑B) ループJSONの保守フォールバックで壊れ形のみ復元(正常形は素通り)
|
- Parser(Stage‑B) ループJSONの保守フォールバックで壊れ形のみ復元(正常形は素通り)
|
||||||
- 代表E2E(return/binop/loop/call)PASS(call は関数化: "Main.add" → Global 関数呼び出し)
|
- 代表E2E(return/binop/loop/call)PASS(call は関数化: "Main.add" → Global 関数呼び出し)
|
||||||
@ -131,9 +299,44 @@ Notes(ライン確認)
|
|||||||
- Helper: `tools/hakorune_emit_mir.sh`(Stage‑B→MIR)、`tools/ny_mir_builder.sh --emit exe`(crate で obj/exe)
|
- Helper: `tools/hakorune_emit_mir.sh`(Stage‑B→MIR)、`tools/ny_mir_builder.sh --emit exe`(crate で obj/exe)
|
||||||
|
|
||||||
- Done (recent)
|
- Done (recent)
|
||||||
- EXE canaries hardened(enable_exe_dev_env 適用・検証ON・代表は FAIL 基準へ昇格)
|
- EXE canaries hardened(enable_exe_dev_env 適用・検証ON・代表は FAIL 基準へ昇格)
|
||||||
- VM runtime counters(`NYASH_VM_STATS=1`)
|
- VM runtime counters(`NYASH_VM_STATS=1`)
|
||||||
- microbench `--exe` 実装(ビルド1回→EXEをRUNS回実行)。現状、loop/strlen の一般形は crate 未対応で EXE 失敗 → 上記 1)/2) で解消予定。
|
- microbench `--exe` 実装(ビルド1回→EXEをRUNS回実行)。現状、loop/strlen の一般形は crate 未対応で EXE 失敗 → 上記 1)/2) で解消予定。
|
||||||
|
|
||||||
|
2025‑11‑12 Updates(PHI grouping fix, ret‑after guards, provider hooks)
|
||||||
|
- LLVM Python backend(llvmlite harness/crate 経由)
|
||||||
|
- Return 合成 PHI を常にブロック先頭に配置(PHI グルーピング違反の根治)。
|
||||||
|
- lowering 入口(boxcall/mir_call/safepoint)で terminator 後の誤挿入を防止(継続BBへ移動)。
|
||||||
|
- builder 側でブロック命令列は最初の terminator(ret/branch/jump)で打ち切る。
|
||||||
|
- JsonFrag 正規化・純化(dev)
|
||||||
|
- purify=1 で newbox/boxcall/externcall/mir_call を除去、ret 以降を打ち切り。
|
||||||
|
- provider-first フック(dev)
|
||||||
|
- HAKO_MIR_NORMALIZE_PROVIDER=1 で provider 出力 MIR にも正規化を適用可能。
|
||||||
|
- HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG=1 を provider-first にも適用(最小ループ MIR を直返し)。
|
||||||
|
|
||||||
|
Verified
|
||||||
|
- selfhost-first + JsonFrag 正規化+purify: llvmlite=OK / ny‑llvmc=OK(EXE生成)
|
||||||
|
- provider-first + FORCE_JSONFRAG: ny‑llvmc=OK(EXE生成)
|
||||||
|
|
||||||
|
Open
|
||||||
|
- provider-first の通常出力に対する純化強化(ret ブロックの副作用禁止を徹底)。
|
||||||
|
- strlen(FAST) の適用強化(resolver マーク補強 → IR に nyrt_string_length を反映)。
|
||||||
|
|
||||||
|
- Rust→.hako Normalize 移行計画(Self‑Host First / Freeze準拠)
|
||||||
|
- 方針
|
||||||
|
- 正規化/置換系は Rust 側から .hako(AotPrep/MirBuilder 直下の新規箱)へ段階移行する。
|
||||||
|
- Rust 層は最小シーム+安全ガード(互換維持・借用修正・診断)に限定。既定挙動は不変。
|
||||||
|
- 現状
|
||||||
|
- 実装済み(opt‑in、既定OFF): NormalizePrintBox(print→externcall)、NormalizeRefBox(ref_get/ref_set→boxcall)。
|
||||||
|
- Rust 側 normalize.rs は借用修正のみ。dev 用 idempotence トグル(NYASH_MIR_DEV_IDEMP=1)を追加(既定OFF)。
|
||||||
|
- これから(段階)
|
||||||
|
1) .hako 正規化のカナリア整備(出力形状トークン検証+VM/EXE RC パリティ)。
|
||||||
|
2) Rust 側の同等パスは「既定OFF」を明文化し、.hako 側が有効な場合は二重適用を避ける運用に統一(最終的に Rust 側の normalize はOFF運用)。
|
||||||
|
3) 代表ケースが緑になった段階で、.hako 側をデフォルト化(dev/bench ラッパでON)。
|
||||||
|
4) 安定期間後、Rust 側の当該パスを撤去(環境変数は deprecate → 警告 → 削除の順)。
|
||||||
|
- 環境変数の扱い(増えたトグルの将来)
|
||||||
|
- いまは opt‑in(既定OFF)。デフォルト化タイミングで .hako 側トグルはON に寄せ、Rust 側の旧トグルは非推奨化。
|
||||||
|
- 最終的に Rust 側トグルは削除(ドキュメントも更新)。.hako 側は必要最小のみ残す。
|
||||||
|
|
||||||
- Next (actionable)
|
- Next (actionable)
|
||||||
- Implement loop JSONFrag purification (no MapBox/newbox) and add canary
|
- Implement loop JSONFrag purification (no MapBox/newbox) and add canary
|
||||||
|
|||||||
18
README.ja.md
18
README.ja.md
@ -32,6 +32,12 @@ ExternCall(env.*)と println 正規化: `docs/reference/runtime/externcall.m
|
|||||||
- `NYASH_DEV_DISABLE_LEGACY_METHOD_REWRITE=1`
|
- `NYASH_DEV_DISABLE_LEGACY_METHOD_REWRITE=1`
|
||||||
- JSON出力は unified ON で v1、OFF で v0(従来)
|
- JSON出力は unified ON で v1、OFF で v0(従来)
|
||||||
|
|
||||||
|
呼び出し実行(VM側の既定)
|
||||||
|
- VM は `Callee` 種別でルーティング(Global/Method/Extern/…)。
|
||||||
|
- callee なしのレガシー by‑name 呼び出しは廃止。Builder は常に `Callee` を付与してね(付与がない場合は Fail‑Fast)。
|
||||||
|
- Extern の実体ディスパッチ(SSOT)は `src/backend/mir_interpreter/handlers/calls/externs.rs` に集約。`env.get` などの Global からもここに委譲する方針だよ。
|
||||||
|
- arity サフィックスの正規化: `env.get/1` のような表記も受理し、`env.get` に正規化してからディスパッチするよ。
|
||||||
|
|
||||||
開発計測(任意)
|
開発計測(任意)
|
||||||
- `resolve.choose` の Known 率をKPIとして出力
|
- `resolve.choose` の Known 率をKPIとして出力
|
||||||
- `NYASH_DEBUG_KPI_KNOWN=1`(有効化)
|
- `NYASH_DEBUG_KPI_KNOWN=1`(有効化)
|
||||||
@ -52,6 +58,18 @@ ExternCall(env.*)と println 正規化: `docs/reference/runtime/externcall.m
|
|||||||
- 例: `using: duplicate import of '<canon_path>' at file.hako:12 (previous alias 'X' first seen at line 5)`
|
- 例: `using: duplicate import of '<canon_path>' at file.hako:12 (previous alias 'X' first seen at line 5)`
|
||||||
- 重複を削除/統合して解消してください。
|
- 重複を削除/統合して解消してください。
|
||||||
|
|
||||||
|
### パフォーマンス(MIR & AOT)
|
||||||
|
- MIR 生成ベンチ(Stage‑B → MIR(JSON))
|
||||||
|
- `tools/perf/bench_hakorune_emit_mir.sh apps/examples/json_query/main.hako 5`
|
||||||
|
- 環境トグル: `HAKO_USING_RESOLVER_FIRST=1`, `HAKO_SELFHOST_BUILDER_FIRST=1`
|
||||||
|
- MIR(JSON) → obj/exe ベンチ(ny-llvmc/crate)
|
||||||
|
- `NYASH_LLVM_BACKEND=crate tools/perf/bench_ny_mir_builder.sh /tmp/program.mir.json 3`
|
||||||
|
- 任意: `HAKO_LLVM_OPT_LEVEL=1`(既定 O0)
|
||||||
|
- MIR(JSON) 構造比較
|
||||||
|
- `tools/perf/compare_mir_json.sh out_before.json out_after.json`
|
||||||
|
|
||||||
|
詳細: `docs/guides/perf/benchmarks.md`
|
||||||
|
|
||||||
Phase‑15(2025‑09)アップデート
|
Phase‑15(2025‑09)アップデート
|
||||||
- LLVM は ny‑llvmc(クレート backend)が主線。llvmlite は内部ハーネスとして ny‑llvmc から呼び出されます(利用者は ny‑llvmc/スクリプトを使えばOK)。
|
- LLVM は ny‑llvmc(クレート backend)が主線。llvmlite は内部ハーネスとして ny‑llvmc から呼び出されます(利用者は ny‑llvmc/スクリプトを使えばOK)。
|
||||||
- パーサの改行処理は TokenCursor に統一中(`NYASH_PARSER_TOKEN_CURSOR=1`)。
|
- パーサの改行処理は TokenCursor に統一中(`NYASH_PARSER_TOKEN_CURSOR=1`)。
|
||||||
|
|||||||
18
README.md
18
README.md
@ -16,6 +16,17 @@ Notes
|
|||||||
- The wrapper runs Stage‑B with `NYASH_JSON_ONLY=1` to keep the output clean (no `RC:` lines).
|
- The wrapper runs Stage‑B with `NYASH_JSON_ONLY=1` to keep the output clean (no `RC:` lines).
|
||||||
- When the Hako MirBuilder fails (e.g., under development), it automatically falls back to `--program-json-to-mir` (no behavior change by default).
|
- When the Hako MirBuilder fails (e.g., under development), it automatically falls back to `--program-json-to-mir` (no behavior change by default).
|
||||||
|
|
||||||
|
[Performance quickstart]
|
||||||
|
- MIR emit bench (Stage‑B → MIR(JSON))
|
||||||
|
- `tools/perf/bench_hakorune_emit_mir.sh apps/examples/json_query/main.hako 5`
|
||||||
|
- Env toggles: `HAKO_USING_RESOLVER_FIRST=1`, `HAKO_SELFHOST_BUILDER_FIRST=1`
|
||||||
|
- MIR(JSON) → obj/exe bench(ny-llvmc/crate)
|
||||||
|
- `NYASH_LLVM_BACKEND=crate tools/perf/bench_ny_mir_builder.sh /tmp/program.mir.json 3`
|
||||||
|
- Optional: `HAKO_LLVM_OPT_LEVEL=1`(default O0)
|
||||||
|
- MIR(JSON) structural compare
|
||||||
|
- `tools/perf/compare_mir_json.sh out_before.json out_after.json`
|
||||||
|
|
||||||
|
See also: docs/guides/perf/benchmarks.md
|
||||||
*[🇯🇵 日本語版はこちら / Japanese Version](README.ja.md)*
|
*[🇯🇵 日本語版はこちら / Japanese Version](README.ja.md)*
|
||||||
|
|
||||||
[](https://github.com/moe-charm/nyash/actions/workflows/selfhost-minimal.yml)
|
[](https://github.com/moe-charm/nyash/actions/workflows/selfhost-minimal.yml)
|
||||||
@ -30,6 +41,13 @@ Notes
|
|||||||
Architecture notes
|
Architecture notes
|
||||||
- Runtime rings (ring0/ring1/ring2) and provider policy: see `docs/architecture/RINGS.md`.
|
- Runtime rings (ring0/ring1/ring2) and provider policy: see `docs/architecture/RINGS.md`.
|
||||||
|
|
||||||
|
Call system (unified by default)
|
||||||
|
- Builder emits `Call { callee: Callee }` whenever possible; the VM routes by callee kind (Global/Method/Extern/... ).
|
||||||
|
- Legacy by‑name calls(callee なし)は廃止。必ず Builder が `Callee` を付与する(付与されない場合は Fail‑Fast)。
|
||||||
|
- Extern dispatch SSOT lives in `src/backend/mir_interpreter/handlers/calls/externs.rs`. Global extern‑like names should delegate there (e.g., `env.get`).
|
||||||
|
- Extern arity normalization: names with arity suffix (e.g., `env.get/1`) are accepted and normalized to their base form (`env.get`).
|
||||||
|
See environment knobs and policies in `docs/ENV_VARS.md`.
|
||||||
|
|
||||||
Execution Status (Feature Additions Pause)
|
Execution Status (Feature Additions Pause)
|
||||||
- Active
|
- Active
|
||||||
- `--backend llvm` (ny-llvmc crate backend; llvmlite harness is internal) — AOT object/EXE line
|
- `--backend llvm` (ny-llvmc crate backend; llvmlite harness is internal) — AOT object/EXE line
|
||||||
|
|||||||
@ -67,7 +67,7 @@ static box Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate a simple JSON path by slicing JSON text directly (no full parse)
|
// Evaluate a simple JSON path by slicing JSON text directly (no full parse)
|
||||||
// Returns a JSON substring for the value or null if not found
|
// Returns a JSON substring for the value or the string "null" if not found
|
||||||
eval_path_text(json_text, path) {
|
eval_path_text(json_text, path) {
|
||||||
local DEBUG = 0 // set to 1 for ad-hoc debug
|
local DEBUG = 0 // set to 1 for ad-hoc debug
|
||||||
local cur_text = json_text
|
local cur_text = json_text
|
||||||
@ -87,11 +87,11 @@ static box Main {
|
|||||||
}
|
}
|
||||||
local key = path.substring(start, i)
|
local key = path.substring(start, i)
|
||||||
if DEBUG == 1 { print("[dbg] key=" + key) }
|
if DEBUG == 1 { print("[dbg] key=" + key) }
|
||||||
if key.length() == 0 { return null }
|
if key.length() == 0 { return "null" }
|
||||||
// Get value text directly; then reset window to that text
|
// Get value text directly; then reset window to that text
|
||||||
local next_text = this.object_get_text(cur_text, 0, cur_text.length(), key)
|
local next_text = this.object_get_text(cur_text, 0, cur_text.length(), key)
|
||||||
if DEBUG == 1 { if next_text == null { print("[dbg] obj miss") } else { print("[dbg] obj hit len=" + next_text.length()) } }
|
if DEBUG == 1 { if next_text == null { print("[dbg] obj miss") } else { print("[dbg] obj hit len=" + next_text.length()) } }
|
||||||
if next_text == null { return null }
|
if next_text == null { return "null" }
|
||||||
cur_text = next_text
|
cur_text = next_text
|
||||||
} else {
|
} else {
|
||||||
if ch == "[" {
|
if ch == "[" {
|
||||||
@ -102,16 +102,16 @@ static box Main {
|
|||||||
loop(i < path.length() && this.is_digit(path.substring(i, i + 1))) { i = i + 1 }
|
loop(i < path.length() && this.is_digit(path.substring(i, i + 1))) { i = i + 1 }
|
||||||
local idx_str = path.substring(start, i)
|
local idx_str = path.substring(start, i)
|
||||||
if DEBUG == 1 { print("[dbg] idx_str=" + idx_str + ", next=" + path.substring(i, i + 1)) }
|
if DEBUG == 1 { print("[dbg] idx_str=" + idx_str + ", next=" + path.substring(i, i + 1)) }
|
||||||
if i >= path.length() || path.substring(i, i + 1) != "]" { return null }
|
if i >= path.length() || path.substring(i, i + 1) != "]" { return "null" }
|
||||||
i = i + 1 // skip ']'
|
i = i + 1 // skip ']'
|
||||||
local idx = this.parse_int(idx_str)
|
local idx = this.parse_int(idx_str)
|
||||||
if DEBUG == 1 { print("[dbg] idx=" + idx) }
|
if DEBUG == 1 { print("[dbg] idx=" + idx) }
|
||||||
local next_text = this.array_get_text(cur_text, 0, cur_text.length(), idx)
|
local next_text = this.array_get_text(cur_text, 0, cur_text.length(), idx)
|
||||||
if DEBUG == 1 { if next_text == null { print("[dbg] arr miss idx=" + idx_str) } else { print("[dbg] arr hit len=" + next_text.length()) } }
|
if DEBUG == 1 { if next_text == null { print("[dbg] arr miss idx=" + idx_str) } else { print("[dbg] arr hit len=" + next_text.length()) } }
|
||||||
if next_text == null { return null }
|
if next_text == null { return "null" }
|
||||||
cur_text = next_text
|
cur_text = next_text
|
||||||
} else {
|
} else {
|
||||||
return null
|
return "null"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
80
benchmarks/README.md
Normal file
80
benchmarks/README.md
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# Hakorune Benchmarks
|
||||||
|
|
||||||
|
This repository now bundles a light micro/synthetic benchmark suite to keep an eye on low-level optimizations. All cases are emitted on the fly by `tools/perf/microbench.sh` so they do not interfere with normal apps, but the generated Nyash code mirrors patterns we see in real programs.
|
||||||
|
|
||||||
|
## Included cases
|
||||||
|
|
||||||
|
| Case | Notes |
|
||||||
|
|-------------|-----------------------------------------------------------------------|
|
||||||
|
| `loop` | Plain integer accumulation |
|
||||||
|
| `strlen` | String length in tight loop (`nyrt_string_length` path) |
|
||||||
|
| `box` | StringBox allocation/free |
|
||||||
|
| `branch` | Dense conditional tree (modulo & arithmetic) |
|
||||||
|
| `call` | Helper function dispatch (mix/twist) |
|
||||||
|
| `stringchain`| Substring concatenation + length accumulation |
|
||||||
|
| `arraymap` | ArrayBox + MapBox churn |
|
||||||
|
| `chip8` | Simplified CHIP-8 style fetch/decode loop (derived from apps/chip8) |
|
||||||
|
| `kilo` | Text-buffer edits/search (inspired by enhanced_kilo_editor) |
|
||||||
|
| `sieve` | Integer-heavy Sieve of Eratosthenes (prime count) |
|
||||||
|
| `matmul` | NxN integer matrix multiply (3 nested loops) |
|
||||||
|
| `linidx` | Linear index array ops: idx=i*cols+j (CSE/hoist検証) |
|
||||||
|
| `maplin` | Integer-key Map get/set with linear keys (auto key heuristics) |
|
||||||
|
|
||||||
|
Each case has a matching C reference, so the script reports both absolute time and the Hakorune/C ratio. Scenario-style cases (`chip8`, `kilo`) still keep all logic deterministic and print-free to make timings stable.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `tools/perf/dump_mir.sh` can optionally write the MIR(JSON) for a given `.hako` and print a block/op histogram. It tries the normal provider path first and falls back to the minimal `jsonfrag` version (while-form) when needed, so you can inspect both the structural skeleton and the full lowering.
|
||||||
|
- Current baseline observations (LLVM/EXE, `NYASH_SKIP_TOML_ENV=1 NYASH_DISABLE_PLUGINS=1`): `call`, `stringchain`, and `kilo` already beat the C reference (ratio < 100%), while `branch`, `arraymap`, and `chip8` remain near ≈200%—they are targets for the upcoming hoisting/array-map hot-path work.
|
||||||
|
|
||||||
|
## Latest fast-path measurements
|
||||||
|
|
||||||
|
The following numbers were recorded on 2025-11-12 with the opt-in work enabled:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export NYASH_SKIP_TOML_ENV=1 NYASH_DISABLE_PLUGINS=1 \
|
||||||
|
NYASH_LLVM_SKIP_BUILD=1 NYASH_LLVM_FAST=1 NYASH_LLVM_FAST_INT=1 \
|
||||||
|
NYASH_MIR_LOOP_HOIST=1 NYASH_AOT_COLLECTIONS_HOT=1
|
||||||
|
tools/perf/microbench.sh --case <case> --backend llvm --exe --runs 3
|
||||||
|
```
|
||||||
|
|
||||||
|
Goal: bring the Hakorune EXE ratio to ≤125% of the reference C time (roughly C's 80%).
|
||||||
|
Current effort: keep baking new hoist/CSE patterns so `arraymap`, `matmul`, and `maplin` fall below that bar while `sieve` and the other cases stay stable.
|
||||||
|
|
||||||
|
| Case | EXE ratio (C=100%) | Notes |
|
||||||
|
|-----------|--------------------|-------|
|
||||||
|
| `branch` | 75.00% | 目標達成(≤125%)。 |
|
||||||
|
| `arraymap` | 150.00% | Array/Map hot-path + binop CSE をさらに磨いて ≤125% を目指す。 |
|
||||||
|
| `chip8` | 25.00% | 十分速い。FAST_INT/hoist が効いている。 |
|
||||||
|
| `kilo` | 0.21% (N=200,000) | EXE モード既定で N を 200k に自動調整。C 参照の方が重い構成のため比率は極小。 |
|
||||||
|
| `sieve` | 200.00% | `NYASH_VERIFY_RET_PURITY=1` ON での測定。auto キー判定がまだ保守的。 |
|
||||||
|
| `matmul` | 300.00% | まだ 3 重ループの Array/Map get/set が支配。自動 CSE と auto map key を詰める予定。 |
|
||||||
|
| `linidx` | 100.00% | Linear index case is at parity; hoist + CSE already helps share SSA. |
|
||||||
|
| `maplin` | 200.00% | auto Map キー判定(linear/const拡張)で _h を選択しやすくなった。 さらに詰める余地あり。 |
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- You can rerun any case with the command above; `NYASH_LLVM_SKIP_BUILD=1` keeps repeated ny-llvmc builds cheap once the binaries are ready.
|
||||||
|
- `kilo` は C 参照側が重く既定 N=5,000,000 だと長時間化するため、EXE モードかつ N 未指定では既定 N を 200,000 に自動調整するようにしました(`tools/perf/microbench.sh`)。必要なら `--n <value>` で上書きしてください。
|
||||||
|
- `lang/src/llvm_ir/boxes/aot_prep/README.md` に StrlenFold / LoopHoist / ConstDedup / CollectionsHot のパス一覧をまとめ、NYASH_AOT_MAP_KEY_MODE={h|i64|hh|auto} の切り替えも説明しています。今後は hoist/collections の強化で arraymap/matmul/maplin/sieve を 125% 以内に引き下げる施策を続けます。
|
||||||
|
- 代表測定では `NYASH_VERIFY_RET_PURITY=1` を有効化し、Return 直前の副作用を Fail-Fast で検出しながら回しています(ごく軽微なハンドル・boxcallの変化が 2~3× に跳ねることがある点をご留意ください)。
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
Build `hakorune` in release mode first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo build --release --bin hakorune
|
||||||
|
```
|
||||||
|
|
||||||
|
Then pick a case:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# LLVM EXE vs C (default n/runs are tuned per case)
|
||||||
|
tools/perf/microbench.sh --case chip8 --exe --n 200000 --runs 3
|
||||||
|
|
||||||
|
# VM vs C for a string-heavy micro
|
||||||
|
tools/perf/microbench.sh --case stringchain --backend vm --n 100000 --runs 3
|
||||||
|
```
|
||||||
|
|
||||||
|
The script takes care of generating temporary Nyash/C sources, building the C baseline, piping through the MIR builder (with FAST toggles enabled), and printing averages + ratios. Set `NYASH_SKIP_TOML_ENV=1` / `NYASH_DISABLE_PLUGINS=1` before calling the script if you want a fully clean execution environment.
|
||||||
864
check.err
Normal file
864
check.err
Normal file
@ -0,0 +1,864 @@
|
|||||||
|
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
|
||||||
|
package: /home/tomoaki/git/hakorune-selfhost/plugins/nyash-console-plugin/Cargo.toml
|
||||||
|
workspace: /home/tomoaki/git/hakorune-selfhost/Cargo.toml
|
||||||
|
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
|
||||||
|
package: /home/tomoaki/git/hakorune-selfhost/plugins/nyash-counter-plugin/Cargo.toml
|
||||||
|
workspace: /home/tomoaki/git/hakorune-selfhost/Cargo.toml
|
||||||
|
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
|
||||||
|
package: /home/tomoaki/git/hakorune-selfhost/plugins/nyash-filebox-plugin/Cargo.toml
|
||||||
|
workspace: /home/tomoaki/git/hakorune-selfhost/Cargo.toml
|
||||||
|
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
|
||||||
|
package: /home/tomoaki/git/hakorune-selfhost/plugins/nyash-json-plugin/Cargo.toml
|
||||||
|
workspace: /home/tomoaki/git/hakorune-selfhost/Cargo.toml
|
||||||
|
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
|
||||||
|
package: /home/tomoaki/git/hakorune-selfhost/plugins/nyash-math-plugin/Cargo.toml
|
||||||
|
workspace: /home/tomoaki/git/hakorune-selfhost/Cargo.toml
|
||||||
|
warning: /home/tomoaki/git/hakorune-selfhost/Cargo.toml: file `/home/tomoaki/git/hakorune-selfhost/src/main.rs` found to be present in multiple build targets:
|
||||||
|
* `bin` target `hakorune`
|
||||||
|
* `bin` target `nyash`
|
||||||
|
warning: unused doc comment
|
||||||
|
--> src/box_operators/static_ops.rs:14:1
|
||||||
|
|
|
||||||
|
14 | / /// Static numeric operations for IntegerBox
|
||||||
|
15 | | ///
|
||||||
|
16 | | /// Generates implementations for: Add, Sub, Mul, Div with zero-division error handling
|
||||||
|
| |_--------------------------------------------------------------------------------------^
|
||||||
|
| |
|
||||||
|
| rustdoc does not generate documentation for macro invocations
|
||||||
|
|
|
||||||
|
= help: to document an item produced by a macro, the macro must produce the documentation as part of its expansion
|
||||||
|
= note: `#[warn(unused_doc_comments)]` on by default
|
||||||
|
|
||||||
|
warning: unused doc comment
|
||||||
|
--> src/box_operators/static_ops.rs:19:1
|
||||||
|
|
|
||||||
|
19 | / /// Static numeric operations for FloatBox
|
||||||
|
20 | | ///
|
||||||
|
21 | | /// Generates implementations for: Add, Sub, Mul, Div with zero-division error handling
|
||||||
|
| |_--------------------------------------------------------------------------------------^
|
||||||
|
| |
|
||||||
|
| rustdoc does not generate documentation for macro invocations
|
||||||
|
|
|
||||||
|
= help: to document an item produced by a macro, the macro must produce the documentation as part of its expansion
|
||||||
|
|
||||||
|
warning: unused import: `std::any::Any`
|
||||||
|
--> src/boxes/arithmetic/modulo_box.rs:6:5
|
||||||
|
|
|
||||||
|
6 | use std::any::Any;
|
||||||
|
| ^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: `#[warn(unused_imports)]` on by default
|
||||||
|
|
||||||
|
warning: unused import: `super::library`
|
||||||
|
--> src/runtime/plugin_loader_v2/enabled/loader/config.rs:1:5
|
||||||
|
|
|
||||||
|
1 | use super::library;
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `super::specs`
|
||||||
|
--> src/runtime/plugin_loader_v2/enabled/loader/singletons.rs:1:5
|
||||||
|
|
|
||||||
|
1 | use super::specs;
|
||||||
|
| ^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `PluginBoxV2`
|
||||||
|
--> src/runtime/plugin_loader_v2/enabled/loader/mod.rs:9:55
|
||||||
|
|
|
||||||
|
9 | use super::types::{LoadedPluginV2, PluginBoxMetadata, PluginBoxV2, PluginHandleInner};
|
||||||
|
| ^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `BidError`
|
||||||
|
--> src/runtime/plugin_loader_v2/enabled/loader/mod.rs:10:18
|
||||||
|
|
|
||||||
|
10 | use crate::bid::{BidError, BidResult};
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `std::collections::HashMap`
|
||||||
|
--> src/runtime/plugin_loader_v2/enabled/method_resolver.rs:8:5
|
||||||
|
|
|
||||||
|
8 | use std::collections::HashMap;
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `FactoryPolicy`
|
||||||
|
--> src/runtime/unified_registry.rs:11:46
|
||||||
|
|
|
||||||
|
11 | use crate::box_factory::{UnifiedBoxRegistry, FactoryPolicy};
|
||||||
|
| ^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: unexpected `cfg` condition value: `cranelift-jit`
|
||||||
|
--> src/runner/dispatch.rs:159:15
|
||||||
|
|
|
||||||
|
159 | #[cfg(feature = "cranelift-jit")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: expected values for `feature` are: `all-examples`, `aot-plan-import`, `builtin-core`, `builtin-filebox`, `c-core`, `cli`, `default`, `dynamic-file`, `e2e`, `gui`, `gui-examples`, `interpreter-legacy`, `jit-direct-only`, `llvm`, `llvm-harness`, `llvm-inkwell-legacy`, `mir_refbarrier_unify_poc`, `mir_typeop_poc`, `phi-legacy`, `plugins`, `plugins-only`, `tlv-shim`, `vm-legacy`, and `wasm-backend`
|
||||||
|
= help: consider adding `cranelift-jit` as a feature in `Cargo.toml`
|
||||||
|
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration
|
||||||
|
= note: `#[warn(unexpected_cfgs)]` on by default
|
||||||
|
|
||||||
|
warning: unexpected `cfg` condition value: `cranelift-jit`
|
||||||
|
--> src/runner/dispatch.rs:164:19
|
||||||
|
|
|
||||||
|
164 | #[cfg(not(feature = "cranelift-jit"))]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: expected values for `feature` are: `all-examples`, `aot-plan-import`, `builtin-core`, `builtin-filebox`, `c-core`, `cli`, `default`, `dynamic-file`, `e2e`, `gui`, `gui-examples`, `interpreter-legacy`, `jit-direct-only`, `llvm`, `llvm-harness`, `llvm-inkwell-legacy`, `mir_refbarrier_unify_poc`, `mir_typeop_poc`, `phi-legacy`, `plugins`, `plugins-only`, `tlv-shim`, `vm-legacy`, and `wasm-backend`
|
||||||
|
= help: consider adding `cranelift-jit` as a feature in `Cargo.toml`
|
||||||
|
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration
|
||||||
|
|
||||||
|
warning: unexpected `cfg` condition value: `cranelift-jit`
|
||||||
|
--> src/runner/dispatch.rs:190:15
|
||||||
|
|
|
||||||
|
190 | #[cfg(feature = "cranelift-jit")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: expected values for `feature` are: `all-examples`, `aot-plan-import`, `builtin-core`, `builtin-filebox`, `c-core`, `cli`, `default`, `dynamic-file`, `e2e`, `gui`, `gui-examples`, `interpreter-legacy`, `jit-direct-only`, `llvm`, `llvm-harness`, `llvm-inkwell-legacy`, `mir_refbarrier_unify_poc`, `mir_typeop_poc`, `phi-legacy`, `plugins`, `plugins-only`, `tlv-shim`, `vm-legacy`, and `wasm-backend`
|
||||||
|
= help: consider adding `cranelift-jit` as a feature in `Cargo.toml`
|
||||||
|
= note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration
|
||||||
|
|
||||||
|
warning: unused import: `BinaryOp`
|
||||||
|
--> src/runner/json_v0_bridge/lowering.rs:5:35
|
||||||
|
|
|
||||||
|
5 | MirPrinter, MirType, ValueId, BinaryOp,
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `MirInstruction`
|
||||||
|
--> src/runner/json_v0_bridge/lowering/if_else.rs:2:45
|
||||||
|
|
|
||||||
|
2 | use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `BinaryOp`
|
||||||
|
--> src/runner/json_v0_bridge/lowering/expr.rs:6:19
|
||||||
|
|
|
||||||
|
6 | BasicBlockId, BinaryOp, ConstValue, EffectMask, MirFunction, MirInstruction, ValueId,
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `MirInstruction`
|
||||||
|
--> src/runner/json_v0_bridge/lowering/ternary.rs:8:45
|
||||||
|
|
|
||||||
|
8 | use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `MirInstruction`
|
||||||
|
--> src/runner/json_v0_bridge/lowering/throw_ctx.rs:1:45
|
||||||
|
|
|
||||||
|
1 | use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `NyashBox`
|
||||||
|
--> src/runner/modes/vm.rs:420:40
|
||||||
|
|
|
||||||
|
420 | use crate::box_trait::{NyashBox, IntegerBox, BoolBox};
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `NyashBox`
|
||||||
|
--> src/runner/modes/vm_fallback.rs:324:40
|
||||||
|
|
|
||||||
|
324 | use crate::box_trait::{NyashBox, IntegerBox, BoolBox};
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `crate::runner::child_env`
|
||||||
|
--> src/runner/pipe_io.rs:13:5
|
||||||
|
|
|
||||||
|
13 | use crate::runner::child_env;
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `std::io::Write`
|
||||||
|
--> src/host_providers/mir_builder.rs:4:5
|
||||||
|
|
|
||||||
|
4 | use std::io::Write;
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `crate::box_trait::NyashBox`
|
||||||
|
--> src/backend/mir_interpreter/handlers/boxes_object_fields.rs:2:5
|
||||||
|
|
|
||||||
|
2 | use crate::box_trait::NyashBox;
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `crate::box_trait::NyashBox`
|
||||||
|
--> src/backend/mir_interpreter/handlers/boxes_string.rs:2:5
|
||||||
|
|
|
||||||
|
2 | use crate::box_trait::NyashBox;
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: variable does not need to be mutable
|
||||||
|
--> src/parser/statements/mod.rs:134:13
|
||||||
|
|
|
||||||
|
134 | let mut looks_like_method_head = |this: &Self| -> bool {
|
||||||
|
| ----^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
| |
|
||||||
|
| help: remove this `mut`
|
||||||
|
|
|
||||||
|
= note: `#[warn(unused_mut)]` on by default
|
||||||
|
|
||||||
|
warning: unused variable: `program_ast`
|
||||||
|
--> src/mir/builder/decls.rs:43:25
|
||||||
|
|
|
||||||
|
43 | let program_ast = ASTNode::Program {
|
||||||
|
| ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_program_ast`
|
||||||
|
|
|
||||||
|
= note: `#[warn(unused_variables)]` on by default
|
||||||
|
|
||||||
|
warning: unused variable: `type_id`
|
||||||
|
--> src/mir/builder/decls.rs:118:13
|
||||||
|
|
|
||||||
|
118 | let type_id = crate::mir::builder::emission::constant::emit_string(self, format!("__box_type_{}", name));
|
||||||
|
| ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_type_id`
|
||||||
|
|
||||||
|
warning: unreachable pattern
|
||||||
|
--> src/mir/builder/exprs.rs:330:13
|
||||||
|
|
|
||||||
|
10 | ASTNode::Program { statements, .. } => {
|
||||||
|
| ----------------------------------- matches all the relevant values
|
||||||
|
...
|
||||||
|
330 | ASTNode::Program { statements, .. } => self.cf_block(statements.clone()),
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no value can reach this
|
||||||
|
|
|
||||||
|
= note: `#[warn(unreachable_patterns)]` on by default
|
||||||
|
|
||||||
|
warning: unreachable pattern
|
||||||
|
--> src/mir/builder/exprs.rs:333:13
|
||||||
|
|
|
||||||
|
14 | ASTNode::Print { expression, .. } => {
|
||||||
|
| --------------------------------- matches all the relevant values
|
||||||
|
...
|
||||||
|
333 | ASTNode::Print { expression, .. } => self.build_print_statement(*expression.clone()),
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no value can reach this
|
||||||
|
|
||||||
|
warning: unreachable pattern
|
||||||
|
--> src/mir/builder/exprs.rs:335:13
|
||||||
|
|
|
||||||
|
17 | / ASTNode::If {
|
||||||
|
18 | | condition,
|
||||||
|
19 | | then_body,
|
||||||
|
20 | | else_body,
|
||||||
|
21 | | ..
|
||||||
|
22 | | } => {
|
||||||
|
| |_____________- matches all the relevant values
|
||||||
|
...
|
||||||
|
335 | / ASTNode::If {
|
||||||
|
336 | | condition,
|
||||||
|
337 | | then_body,
|
||||||
|
338 | | else_body,
|
||||||
|
339 | | ..
|
||||||
|
340 | | } => {
|
||||||
|
| |_____________^ no value can reach this
|
||||||
|
|
||||||
|
warning: unreachable pattern
|
||||||
|
--> src/mir/builder/exprs.rs:359:13
|
||||||
|
|
|
||||||
|
34 | ASTNode::Loop { condition, body, .. } => {
|
||||||
|
| ------------------------------------- matches all the relevant values
|
||||||
|
...
|
||||||
|
359 | / ASTNode::Loop {
|
||||||
|
360 | | condition, body, ..
|
||||||
|
361 | | } => self.cf_loop(*condition.clone(), body.clone()),
|
||||||
|
| |_____________^ no value can reach this
|
||||||
|
|
||||||
|
warning: unreachable pattern
|
||||||
|
--> src/mir/builder/exprs.rs:363:13
|
||||||
|
|
|
||||||
|
41 | / ASTNode::TryCatch {
|
||||||
|
42 | | try_body,
|
||||||
|
43 | | catch_clauses,
|
||||||
|
44 | | finally_body,
|
||||||
|
45 | | ..
|
||||||
|
46 | | } => self.cf_try_catch(try_body, catch_clauses, finally_body),
|
||||||
|
| |_____________- matches all the relevant values
|
||||||
|
...
|
||||||
|
363 | / ASTNode::TryCatch {
|
||||||
|
364 | | try_body,
|
||||||
|
365 | | catch_clauses,
|
||||||
|
366 | | finally_body,
|
||||||
|
367 | | ..
|
||||||
|
368 | | } => self.cf_try_catch(
|
||||||
|
| |_____________^ no value can reach this
|
||||||
|
|
||||||
|
warning: unreachable pattern
|
||||||
|
--> src/mir/builder/exprs.rs:374:13
|
||||||
|
|
|
||||||
|
47 | ASTNode::Throw { expression, .. } => self.cf_throw(*expression),
|
||||||
|
| --------------------------------- matches all the relevant values
|
||||||
|
...
|
||||||
|
374 | ASTNode::Throw { expression, .. } => self.cf_throw(*expression.clone()),
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no value can reach this
|
||||||
|
|
||||||
|
warning: unused variable: `else_block`
|
||||||
|
--> src/mir/builder/phi.rs:15:9
|
||||||
|
|
|
||||||
|
15 | else_block: super::BasicBlockId,
|
||||||
|
| ^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_else_block`
|
||||||
|
|
||||||
|
warning: unused variable: `then_block`
|
||||||
|
--> src/mir/builder/phi.rs:107:9
|
||||||
|
|
|
||||||
|
107 | then_block: BasicBlockId,
|
||||||
|
| ^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_then_block`
|
||||||
|
|
||||||
|
warning: unused variable: `else_block`
|
||||||
|
--> src/mir/builder/phi.rs:108:9
|
||||||
|
|
|
||||||
|
108 | else_block: BasicBlockId,
|
||||||
|
| ^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_else_block`
|
||||||
|
|
||||||
|
warning: unused variable: `func`
|
||||||
|
--> src/mir/builder/lifecycle.rs:241:60
|
||||||
|
|
|
||||||
|
241 | ... MirInstruction::Call { func, .. } => {
|
||||||
|
| ^^^^-
|
||||||
|
| |
|
||||||
|
| help: try removing the field
|
||||||
|
|
||||||
|
warning: variable does not need to be mutable
|
||||||
|
--> src/mir/builder/lifecycle.rs:271:17
|
||||||
|
|
|
||||||
|
271 | let mut sig = FunctionSignature {
|
||||||
|
| ----^^^
|
||||||
|
| |
|
||||||
|
| help: remove this `mut`
|
||||||
|
|
||||||
|
warning: unused variable: `name_const`
|
||||||
|
--> src/mir/builder/rewrite/known.rs:186:9
|
||||||
|
|
|
||||||
|
186 | let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
|
||||||
|
| ^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_name_const`
|
||||||
|
|
||||||
|
warning: unused variable: `name_const`
|
||||||
|
--> src/mir/builder/rewrite/special.rs:154:17
|
||||||
|
|
|
||||||
|
154 | let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &chosen) {
|
||||||
|
| ^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_name_const`
|
||||||
|
|
||||||
|
warning: unused variable: `name_const`
|
||||||
|
--> src/mir/builder/rewrite/special.rs:185:13
|
||||||
|
|
|
||||||
|
185 | let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
|
||||||
|
| ^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_name_const`
|
||||||
|
|
||||||
|
warning: unused variable: `name_const`
|
||||||
|
--> src/mir/builder/rewrite/special.rs:212:17
|
||||||
|
|
|
||||||
|
212 | let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
|
||||||
|
| ^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_name_const`
|
||||||
|
|
||||||
|
warning: unused variable: `dbg_fn_name`
|
||||||
|
--> src/mir/builder.rs:424:13
|
||||||
|
|
|
||||||
|
424 | let dbg_fn_name = self
|
||||||
|
| ^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_dbg_fn_name`
|
||||||
|
|
||||||
|
warning: unused variable: `dbg_region_id`
|
||||||
|
--> src/mir/builder.rs:428:13
|
||||||
|
|
|
||||||
|
428 | let dbg_region_id = self.debug_current_region_id();
|
||||||
|
| ^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_dbg_region_id`
|
||||||
|
|
||||||
|
warning: unused variable: `else_block`
|
||||||
|
--> src/mir/phi_core/if_phi.rs:148:5
|
||||||
|
|
|
||||||
|
148 | else_block: crate::mir::BasicBlockId,
|
||||||
|
| ^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_else_block`
|
||||||
|
|
||||||
|
warning: variable does not need to be mutable
|
||||||
|
--> src/mir/phi_core/loop_phi.rs:185:13
|
||||||
|
|
|
||||||
|
185 | let mut inc = IncompletePhi {
|
||||||
|
| ----^^^
|
||||||
|
| |
|
||||||
|
| help: remove this `mut`
|
||||||
|
|
||||||
|
warning: unused variable: `static_box_name`
|
||||||
|
--> src/backend/mir_interpreter/exec.rs:23:13
|
||||||
|
|
|
||||||
|
23 | let static_box_name = self.is_static_box_method(&func.signature.name);
|
||||||
|
| ^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_static_box_name`
|
||||||
|
|
||||||
|
warning: variable does not need to be mutable
|
||||||
|
--> src/backend/mir_interpreter/handlers/boxes_instance.rs:105:17
|
||||||
|
|
|
||||||
|
105 | let mut cands: Vec<String> = this
|
||||||
|
| ----^^^^^
|
||||||
|
| |
|
||||||
|
| help: remove this `mut`
|
||||||
|
|
||||||
|
warning: unreachable pattern
|
||||||
|
--> src/backend/mir_interpreter/handlers/externals.rs:138:13
|
||||||
|
|
|
||||||
|
30 | ("env", "get") => {
|
||||||
|
| -------------- matches all the relevant values
|
||||||
|
...
|
||||||
|
138 | ("env", "get") => {
|
||||||
|
| ^^^^^^^^^^^^^^ no value can reach this
|
||||||
|
|
||||||
|
warning: unused variable: `source`
|
||||||
|
--> src/benchmarks.rs:48:23
|
||||||
|
|
|
||||||
|
48 | if let Ok(source) = fs::read_to_string(file_path) {
|
||||||
|
| ^^^^^^ help: if this is intentional, prefix it with an underscore: `_source`
|
||||||
|
|
||||||
|
warning: variable does not need to be mutable
|
||||||
|
--> src/benchmarks.rs:36:13
|
||||||
|
|
|
||||||
|
36 | let mut results = Vec::new();
|
||||||
|
| ----^^^^^^^
|
||||||
|
| |
|
||||||
|
| help: remove this `mut`
|
||||||
|
|
||||||
|
warning: unused variable: `lib`
|
||||||
|
--> src/runtime/plugin_loader_v2/enabled/method_resolver.rs:40:19
|
||||||
|
|
|
||||||
|
40 | for ((lib, bt), spec) in map.iter() {
|
||||||
|
| ^^^ help: if this is intentional, prefix it with an underscore: `_lib`
|
||||||
|
|
||||||
|
warning: unnecessary `unsafe` block
|
||||||
|
--> src/runtime/plugin_loader_v2/enabled/method_resolver.rs:50:39
|
||||||
|
|
|
||||||
|
50 | ... let mid = unsafe { res_fn(cstr.as_ptr()) };
|
||||||
|
| ^^^^^^ unnecessary `unsafe` block
|
||||||
|
|
|
||||||
|
= note: `#[warn(unused_unsafe)]` on by default
|
||||||
|
|
||||||
|
warning: variable does not need to be mutable
|
||||||
|
--> src/runner/json_v0_bridge/lowering/ternary.rs:38:9
|
||||||
|
|
|
||||||
|
38 | let mut inputs = vec![(tend, tval), (eend, eval)];
|
||||||
|
| ----^^^^^^
|
||||||
|
| |
|
||||||
|
| help: remove this `mut`
|
||||||
|
|
||||||
|
warning: unreachable pattern
|
||||||
|
--> src/runner/json_v1_bridge.rs:512:29
|
||||||
|
|
|
||||||
|
368 | ... "Constructor" => {
|
||||||
|
| ------------- matches all the relevant values
|
||||||
|
...
|
||||||
|
512 | ... "Constructor" => {
|
||||||
|
| ^^^^^^^^^^^^^ no value can reach this
|
||||||
|
|
||||||
|
warning: unused variable: `filename`
|
||||||
|
--> src/runner/modes/vm_fallback.rs:349:44
|
||||||
|
|
|
||||||
|
349 | fn execute_vm_fallback_from_ast(&self, filename: &str, ast: nyash_rust::ast::ASTNode) {
|
||||||
|
| ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_filename`
|
||||||
|
|
||||||
|
warning: unused variable: `ast_on`
|
||||||
|
--> src/runner/modes/common_util/resolve/strip.rs:444:9
|
||||||
|
|
|
||||||
|
444 | let ast_on = crate::config::env::env_bool("NYASH_USING_AST");
|
||||||
|
| ^^^^^^ help: if this is intentional, prefix it with an underscore: `_ast_on`
|
||||||
|
|
||||||
|
warning: unused variable: `using_resolver`
|
||||||
|
--> src/runner/modes/common_util/resolve/prelude_manager.rs:126:17
|
||||||
|
|
|
||||||
|
126 | let using_resolver = UsingResolutionBox::new(&self.runner, path)?;
|
||||||
|
| ^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_using_resolver`
|
||||||
|
|
||||||
|
warning: unused variable: `filename`
|
||||||
|
--> src/runner/modes/common_util/resolve/prelude_manager.rs:114:9
|
||||||
|
|
|
||||||
|
114 | filename: &str,
|
||||||
|
| ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_filename`
|
||||||
|
|
||||||
|
warning: unused variable: `filename`
|
||||||
|
--> src/runner/modes/common_util/resolve/selfhost_pipeline.rs:132:9
|
||||||
|
|
|
||||||
|
132 | filename: &str,
|
||||||
|
| ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_filename`
|
||||||
|
|
||||||
|
warning: unused variable: `code`
|
||||||
|
--> src/runner/modes/common_util/resolve/selfhost_pipeline.rs:182:9
|
||||||
|
|
|
||||||
|
182 | code: &str,
|
||||||
|
| ^^^^ help: if this is intentional, prefix it with an underscore: `_code`
|
||||||
|
|
||||||
|
warning: unused variable: `filename`
|
||||||
|
--> src/runner/modes/common_util/resolve/selfhost_pipeline.rs:183:9
|
||||||
|
|
|
||||||
|
183 | filename: &str,
|
||||||
|
| ^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_filename`
|
||||||
|
|
||||||
|
warning: variable does not need to be mutable
|
||||||
|
--> src/runner/modes/common_util/plugin_guard.rs:25:9
|
||||||
|
|
|
||||||
|
25 | let mut v = vec![
|
||||||
|
| ----^
|
||||||
|
| |
|
||||||
|
| help: remove this `mut`
|
||||||
|
|
||||||
|
warning: variable does not need to be mutable
|
||||||
|
--> src/runner/pipe_io.rs:23:13
|
||||||
|
|
|
||||||
|
23 | let mut json = if let Some(path) = &groups.parser.json_file {
|
||||||
|
| ----^^^^
|
||||||
|
| |
|
||||||
|
| help: remove this `mut`
|
||||||
|
|
||||||
|
warning: unused variable: `code`
|
||||||
|
--> src/runner/plugins.rs:61:40
|
||||||
|
|
|
||||||
|
61 | ... Ok(code) => {
|
||||||
|
| ^^^^ help: if this is intentional, prefix it with an underscore: `_code`
|
||||||
|
|
||||||
|
warning: value assigned to `out` is never read
|
||||||
|
--> src/runner/hv1_inline.rs:132:25
|
||||||
|
|
|
||||||
|
132 | let mut out = 0i64;
|
||||||
|
| ^^^
|
||||||
|
|
|
||||||
|
= help: maybe it is overwritten before being read?
|
||||||
|
= note: `#[warn(unused_assignments)]` on by default
|
||||||
|
|
||||||
|
warning: variable does not need to be mutable
|
||||||
|
--> src/host_providers/mir_builder.rs:49:38
|
||||||
|
|
|
||||||
|
49 | Ok(JsonValue::Object(mut m)) => {
|
||||||
|
| ----^
|
||||||
|
| |
|
||||||
|
| help: remove this `mut`
|
||||||
|
|
||||||
|
warning: methods `peek`, `peek_nth`, `get_mode`, and `set_mode` are never used
|
||||||
|
--> src/parser/cursor.rs:46:12
|
||||||
|
|
|
||||||
|
23 | impl<'a> TokenCursor<'a> {
|
||||||
|
| ------------------------ methods in this implementation
|
||||||
|
...
|
||||||
|
46 | pub fn peek(&self) -> &Token {
|
||||||
|
| ^^^^
|
||||||
|
...
|
||||||
|
55 | pub fn peek_nth(&self, n: usize) -> &Token {
|
||||||
|
| ^^^^^^^^
|
||||||
|
...
|
||||||
|
223 | pub fn get_mode(&self) -> NewlineMode {
|
||||||
|
| ^^^^^^^^
|
||||||
|
...
|
||||||
|
228 | pub fn set_mode(&mut self, mode: NewlineMode) {
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: `#[warn(dead_code)]` on by default
|
||||||
|
|
||||||
|
warning: methods `err_unexpected` and `expect_identifier` are never used
|
||||||
|
--> src/parser/statements/helpers.rs:62:19
|
||||||
|
|
|
||||||
|
18 | impl NyashParser {
|
||||||
|
| ---------------- methods in this implementation
|
||||||
|
...
|
||||||
|
62 | pub(super) fn err_unexpected<S: Into<String>>(&self, expected: S) -> ParseError {
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
...
|
||||||
|
71 | pub(super) fn expect_identifier(&mut self, what: &str) -> Result<String, ParseError> {
|
||||||
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `is_env_interface` is never used
|
||||||
|
--> src/mir/builder/calls/extern_calls.rs:152:8
|
||||||
|
|
|
||||||
|
152 | pub fn is_env_interface(name: &str) -> bool {
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `is_numeric_value` is never used
|
||||||
|
--> src/mir/builder/calls/special_handlers.rs:61:8
|
||||||
|
|
|
||||||
|
61 | pub fn is_numeric_value(node: &ASTNode) -> bool {
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `extract_numeric_type` is never used
|
||||||
|
--> src/mir/builder/calls/special_handlers.rs:73:8
|
||||||
|
|
|
||||||
|
73 | pub fn extract_numeric_type(node: &ASTNode) -> Option<MirType> {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `make_function_name_with_arity` is never used
|
||||||
|
--> src/mir/builder/calls/special_handlers.rs:120:8
|
||||||
|
|
|
||||||
|
120 | pub fn make_function_name_with_arity(base_name: &str, arity: usize) -> String {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `is_reserved_function` is never used
|
||||||
|
--> src/mir/builder/calls/special_handlers.rs:125:8
|
||||||
|
|
|
||||||
|
125 | pub fn is_reserved_function(name: &str) -> bool {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `suggest_alternative_for_reserved` is never used
|
||||||
|
--> src/mir/builder/calls/special_handlers.rs:133:8
|
||||||
|
|
|
||||||
|
133 | pub fn suggest_alternative_for_reserved(name: &str) -> String {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `generate_static_method_function_name` is never used
|
||||||
|
--> src/mir/builder/calls/function_lowering.rs:88:8
|
||||||
|
|
|
||||||
|
88 | pub fn generate_static_method_function_name(
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `needs_void_termination` is never used
|
||||||
|
--> src/mir/builder/calls/function_lowering.rs:97:8
|
||||||
|
|
|
||||||
|
97 | pub fn needs_void_termination(returns_value: bool, is_terminated: bool) -> bool {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `create_method_parameter_mapping` is never used
|
||||||
|
--> src/mir/builder/calls/function_lowering.rs:103:8
|
||||||
|
|
|
||||||
|
103 | pub fn create_method_parameter_mapping(
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `create_static_parameter_mapping` is never used
|
||||||
|
--> src/mir/builder/calls/function_lowering.rs:121:8
|
||||||
|
|
|
||||||
|
121 | pub fn create_static_parameter_mapping(
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `method_likely_returns_value` is never used
|
||||||
|
--> src/mir/builder/calls/function_lowering.rs:138:8
|
||||||
|
|
|
||||||
|
138 | pub fn method_likely_returns_value(method_name: &str) -> bool {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `is_commonly_shadowed_method` is never used
|
||||||
|
--> src/mir/builder/call_resolution.rs:49:8
|
||||||
|
|
|
||||||
|
49 | pub fn is_commonly_shadowed_method(method: &str) -> bool {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `generate_self_recursion_warning` is never used
|
||||||
|
--> src/mir/builder/call_resolution.rs:60:8
|
||||||
|
|
|
||||||
|
60 | pub fn generate_self_recursion_warning(box_name: &str, method: &str) -> String {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: associated function `is_typeop_method` is never used
|
||||||
|
--> src/mir/builder/method_call_handlers.rs:64:19
|
||||||
|
|
|
||||||
|
11 | impl MirBuilder {
|
||||||
|
| --------------- associated function in this implementation
|
||||||
|
...
|
||||||
|
64 | pub(super) fn is_typeop_method(method: &str, arguments: &[ASTNode]) -> Option<String> {
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: method `materialize_local` is never used
|
||||||
|
--> src/mir/builder/utils.rs:297:19
|
||||||
|
|
|
||||||
|
106 | impl super::MirBuilder {
|
||||||
|
| ---------------------- method in this implementation
|
||||||
|
...
|
||||||
|
297 | pub(crate) fn materialize_local(&mut self, v: super::ValueId) -> Result<super::ValueId, String> {
|
||||||
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `try_known_rewrite` is never used
|
||||||
|
--> src/mir/builder/rewrite/known.rs:22:15
|
||||||
|
|
|
||||||
|
22 | pub(crate) fn try_known_rewrite(
|
||||||
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `try_unique_suffix_rewrite` is never used
|
||||||
|
--> src/mir/builder/rewrite/known.rs:122:15
|
||||||
|
|
|
||||||
|
122 | pub(crate) fn try_unique_suffix_rewrite(
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `try_known_or_unique` is never used
|
||||||
|
--> src/mir/builder/rewrite/known.rs:215:15
|
||||||
|
|
|
||||||
|
215 | pub(crate) fn try_known_or_unique(
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `try_early_str_like` is never used
|
||||||
|
--> src/mir/builder/rewrite/special.rs:5:15
|
||||||
|
|
|
||||||
|
5 | pub(crate) fn try_early_str_like(
|
||||||
|
| ^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `try_special_equals` is never used
|
||||||
|
--> src/mir/builder/rewrite/special.rs:107:15
|
||||||
|
|
|
||||||
|
107 | pub(crate) fn try_special_equals(
|
||||||
|
| ^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `propagate_with_override` is never used
|
||||||
|
--> src/mir/builder/metadata/propagate.rs:21:8
|
||||||
|
|
|
||||||
|
21 | pub fn propagate_with_override(builder: &mut MirBuilder, dst: ValueId, ty: MirType) {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `emit_eq_to` is never used
|
||||||
|
--> src/mir/builder/emission/compare.rs:20:8
|
||||||
|
|
|
||||||
|
20 | pub fn emit_eq_to(b: &mut MirBuilder, dst: ValueId, lhs: ValueId, rhs: ValueId) -> Result<(), String> {
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `emit_ne_to` is never used
|
||||||
|
--> src/mir/builder/emission/compare.rs:25:8
|
||||||
|
|
|
||||||
|
25 | pub fn emit_ne_to(b: &mut MirBuilder, dst: ValueId, lhs: ValueId, rhs: ValueId) -> Result<(), String> {
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `set_type` is never used
|
||||||
|
--> src/mir/builder/types/annotation.rs:8:8
|
||||||
|
|
|
||||||
|
8 | pub fn set_type(builder: &mut MirBuilder, dst: ValueId, ty: MirType) {
|
||||||
|
| ^^^^^^^^
|
||||||
|
|
||||||
|
warning: methods `try_handle_map_box`, `try_handle_string_box`, and `try_handle_array_box` are never used
|
||||||
|
--> src/backend/mir_interpreter/handlers/boxes.rs:281:8
|
||||||
|
|
|
||||||
|
4 | impl MirInterpreter {
|
||||||
|
| ------------------- methods in this implementation
|
||||||
|
...
|
||||||
|
281 | fn try_handle_map_box(
|
||||||
|
| ^^^^^^^^^^^^^^^^^^
|
||||||
|
...
|
||||||
|
292 | fn try_handle_string_box(
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
...
|
||||||
|
303 | fn try_handle_array_box(
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: associated function `ensure_mir_json_version_field` is never used
|
||||||
|
--> src/backend/mir_interpreter/handlers/externals.rs:6:8
|
||||||
|
|
|
||||||
|
4 | impl MirInterpreter {
|
||||||
|
| ------------------- associated function in this implementation
|
||||||
|
5 | #[inline]
|
||||||
|
6 | fn ensure_mir_json_version_field(s: &str) -> String {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: method `write_box_result` is never used
|
||||||
|
--> src/backend/mir_interpreter/utils/destination_helpers.rs:16:19
|
||||||
|
|
|
||||||
|
9 | impl MirInterpreter {
|
||||||
|
| ------------------- method in this implementation
|
||||||
|
...
|
||||||
|
16 | pub(crate) fn write_box_result(
|
||||||
|
| ^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: methods `validate_args_range` and `validate_args_min` are never used
|
||||||
|
--> src/backend/mir_interpreter/utils/arg_validation.rs:47:19
|
||||||
|
|
|
||||||
|
8 | impl MirInterpreter {
|
||||||
|
| ------------------- methods in this implementation
|
||||||
|
...
|
||||||
|
47 | pub(crate) fn validate_args_range(
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^
|
||||||
|
...
|
||||||
|
74 | pub(crate) fn validate_args_min(
|
||||||
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: method `convert_to_box` is never used
|
||||||
|
--> src/backend/mir_interpreter/utils/receiver_helpers.rs:18:19
|
||||||
|
|
|
||||||
|
9 | impl MirInterpreter {
|
||||||
|
| ------------------- method in this implementation
|
||||||
|
...
|
||||||
|
18 | pub(crate) fn convert_to_box(
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: associated functions `type_mismatch`, `out_of_bounds`, `receiver_type_error`, `arg_count_min`, and `from_error` are never used
|
||||||
|
--> src/backend/mir_interpreter/utils/error_helpers.rs:43:12
|
||||||
|
|
|
||||||
|
16 | impl ErrorBuilder {
|
||||||
|
| ----------------- associated functions in this implementation
|
||||||
|
...
|
||||||
|
43 | pub fn type_mismatch(method: &str, expected: &str, actual: &str) -> VMError {
|
||||||
|
| ^^^^^^^^^^^^^
|
||||||
|
...
|
||||||
|
60 | pub fn out_of_bounds(method: &str, index: usize, len: usize) -> VMError {
|
||||||
|
| ^^^^^^^^^^^^^
|
||||||
|
...
|
||||||
|
96 | pub fn receiver_type_error(expected: &str) -> VMError {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^
|
||||||
|
...
|
||||||
|
126 | pub fn arg_count_min(method: &str, min: usize, actual: usize) -> VMError {
|
||||||
|
| ^^^^^^^^^^^^^
|
||||||
|
...
|
||||||
|
156 | pub fn from_error(operation: &str, error: &dyn std::error::Error) -> VMError {
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: methods `err_type_mismatch` and `err_out_of_bounds` are never used
|
||||||
|
--> src/backend/mir_interpreter/utils/error_helpers.rs:181:19
|
||||||
|
|
|
||||||
|
162 | impl super::super::MirInterpreter {
|
||||||
|
| --------------------------------- methods in this implementation
|
||||||
|
...
|
||||||
|
181 | pub(crate) fn err_type_mismatch(&self, method: &str, expected: &str, actual: &str) -> VMError {
|
||||||
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
...
|
||||||
|
192 | pub(crate) fn err_out_of_bounds(&self, method: &str, index: usize, len: usize) -> VMError {
|
||||||
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: methods `load_as_int`, `load_as_bool`, and `load_args_as_values` are never used
|
||||||
|
--> src/backend/mir_interpreter/utils/conversion_helpers.rs:45:19
|
||||||
|
|
|
||||||
|
9 | impl MirInterpreter {
|
||||||
|
| ------------------- methods in this implementation
|
||||||
|
...
|
||||||
|
45 | pub(crate) fn load_as_int(&mut self, vid: ValueId) -> Result<i64, VMError> {
|
||||||
|
| ^^^^^^^^^^^
|
||||||
|
...
|
||||||
|
74 | pub(crate) fn load_as_bool(&mut self, vid: ValueId) -> Result<bool, VMError> {
|
||||||
|
| ^^^^^^^^^^^^
|
||||||
|
...
|
||||||
|
115 | pub(crate) fn load_args_as_values(
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: field `returns_result` is never read
|
||||||
|
--> src/runtime/plugin_loader_v2/enabled/loader/specs.rs:21:16
|
||||||
|
|
|
||||||
|
19 | pub(crate) struct MethodSpec {
|
||||||
|
| ---------- field in this struct
|
||||||
|
20 | pub(crate) method_id: u32,
|
||||||
|
21 | pub(crate) returns_result: bool,
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: `MethodSpec` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
|
||||||
|
|
||||||
|
warning: function `is_special_method` is never used
|
||||||
|
--> src/runtime/plugin_loader_v2/enabled/method_resolver.rs:133:15
|
||||||
|
|
|
||||||
|
133 | pub(super) fn is_special_method(method_name: &str) -> bool {
|
||||||
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `get_special_method_id` is never used
|
||||||
|
--> src/runtime/plugin_loader_v2/enabled/method_resolver.rs:138:15
|
||||||
|
|
|
||||||
|
138 | pub(super) fn get_special_method_id(method_name: &str) -> Option<u32> {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `post_run_exit_if_oob_strict_triggered` is never used
|
||||||
|
--> src/runner/child_env.rs:11:8
|
||||||
|
|
|
||||||
|
11 | pub fn post_run_exit_if_oob_strict_triggered() -> ! {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `demo_interpreter_system` is never used
|
||||||
|
--> src/runner/demos.rs:93:15
|
||||||
|
|
|
||||||
|
93 | pub(super) fn demo_interpreter_system() {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: method `execute_vm_fallback_from_ast` is never used
|
||||||
|
--> src/runner/modes/vm_fallback.rs:349:8
|
||||||
|
|
|
||||||
|
347 | impl NyashRunner {
|
||||||
|
| ---------------- method in this implementation
|
||||||
|
348 | /// Small helper to continue fallback execution once AST is prepared
|
||||||
|
349 | fn execute_vm_fallback_from_ast(&self, filename: &str, ast: nyash_rust::ast::ASTNode) {
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: field `filename_canon` is never read
|
||||||
|
--> src/runner/modes/common_util/resolve/using_resolution.rs:17:5
|
||||||
|
|
|
||||||
|
13 | pub struct UsingResolutionBox<'a> {
|
||||||
|
| ------------------ field in this struct
|
||||||
|
...
|
||||||
|
17 | filename_canon: Option<PathBuf>,
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: function `suggest_in_base` is never used
|
||||||
|
--> src/runner/pipeline.rs:87:15
|
||||||
|
|
|
||||||
|
87 | pub(super) fn suggest_in_base(base: &str, leaf: &str, out: &mut Vec<String>) {
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: `nyash-rust` (lib) generated 108 warnings (run `cargo fix --lib -p nyash-rust` to apply 26 suggestions)
|
||||||
|
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s
|
||||||
@ -131,6 +131,12 @@ fn main() -> Result<()> {
|
|||||||
bail!("input JSON not found: {}", input_path.display());
|
bail!("input JSON not found: {}", input_path.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optional: dump incoming MIR JSON for diagnostics (AotPrep 後の入力を観測)
|
||||||
|
if let Ok(dump_path) = env::var("NYASH_LLVM_DUMP_MIR_IN") {
|
||||||
|
let _ = std::fs::copy(&input_path, &dump_path);
|
||||||
|
eprintln!("[ny-llvmc] dumped MIR input to {}", dump_path);
|
||||||
|
}
|
||||||
|
|
||||||
// Optional: preflight shape/hints (best-effort; no behavior change)
|
// Optional: preflight shape/hints (best-effort; no behavior change)
|
||||||
if let Ok(s) = std::fs::read_to_string(&input_path) {
|
if let Ok(s) = std::fs::read_to_string(&input_path) {
|
||||||
if let Ok(val) = serde_json::from_str::<JsonValue>(&s) {
|
if let Ok(val) = serde_json::from_str::<JsonValue>(&s) {
|
||||||
@ -379,9 +385,26 @@ fn link_executable(
|
|||||||
cmd.arg(tok);
|
cmd.arg(tok);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let status = cmd.status().context("failed to invoke system linker")?;
|
// Run linker and capture diagnostics for better error reporting
|
||||||
if !status.success() {
|
let output = cmd
|
||||||
bail!("linker exited with status: {:?}", status.code());
|
.output()
|
||||||
|
.with_context(|| format!("failed to invoke system linker: {}", linker))?;
|
||||||
|
if !output.status.success() {
|
||||||
|
eprintln!("[ny-llvmc/link] command: {}", linker);
|
||||||
|
// Show args (for debugging)
|
||||||
|
// Note: std::process::Command doesn't expose argv back; re-emit essential parts
|
||||||
|
eprintln!(
|
||||||
|
"[ny-llvmc/link] args: -o {} {} -Wl,--whole-archive {} -Wl,--no-whole-archive -ldl -lpthread -lm {}",
|
||||||
|
out_exe.display(),
|
||||||
|
obj.display(),
|
||||||
|
libnyrt.display(),
|
||||||
|
extra_libs.unwrap_or("")
|
||||||
|
);
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
eprintln!("[ny-llvmc/link:stdout]\n{}", stdout);
|
||||||
|
eprintln!("[ny-llvmc/link:stderr]\n{}", stderr);
|
||||||
|
bail!("linker exited with status: {:?}", output.status.code());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,6 +38,7 @@ pub extern "C" fn nyash_string_len_h(handle: i64) -> i64 {
|
|||||||
|
|
||||||
// FAST-path helper: compute string length from raw pointer (i8*) with mode (reserved)
|
// FAST-path helper: compute string length from raw pointer (i8*) with mode (reserved)
|
||||||
// Exported as both legacy name (nyash.string.length_si) and neutral name (nyrt_string_length)
|
// Exported as both legacy name (nyash.string.length_si) and neutral name (nyrt_string_length)
|
||||||
|
#[inline(always)]
|
||||||
#[export_name = "nyrt_string_length"]
|
#[export_name = "nyrt_string_length"]
|
||||||
pub extern "C" fn nyrt_string_length(ptr: *const i8, _mode: i64) -> i64 {
|
pub extern "C" fn nyrt_string_length(ptr: *const i8, _mode: i64) -> i64 {
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
|
|||||||
311
docs/ENV_VARS.md
311
docs/ENV_VARS.md
@ -1,231 +1,126 @@
|
|||||||
# Environment Variables — Quick Reference (Phase 22.1)
|
ENV Variables Index (Core, Builder, Smokes)
|
||||||
|
|
||||||
This document lists the environment flags introduced or used by the Phase 22.1 work. Defaults are OFF and behavior remains unchanged unless noted.
|
Purpose: quick reference for toggles used frequently during development and in smokes. Defaults aim to be safe and fail‑fast.
|
||||||
|
|
||||||
- NYASH_JSON_ONLY=0|1
|
Runtime/VM
|
||||||
- Quiet JSON pipelines: suppresses `RC:` and routine logs on stdout (diagnostics still go to stderr).
|
- NYASH_FAIL_FAST=0
|
||||||
- Used by Stage‑B → Program(JSON) emit to keep the output clean for downstream processing.
|
- Global relaxation switch; when set to 0/false, paired legacy guards may allow otherwise fail‑fast paths.
|
||||||
|
- Use only for diagnosis; keep OFF in CI.
|
||||||
|
|
||||||
- HAKO_USING_SSOT=0|1
|
- NYASH_LEGACY_FIELDS_ENABLE=1
|
||||||
- Enables the SSOT resolver gate in the runner pipeline.
|
- Materialize legacy InstanceBox shared fields map for compatibility with older code paths.
|
||||||
- When ON, resolution first consults the SSOT bridge (modules-only MVP). If not resolved, it falls back to the existing resolver.
|
- Default: OFF. Dev/compat only; safe to keep disabled in CI and normal runs.
|
||||||
- Trace: set `NYASH_RESOLVE_TRACE=1` to see `[using/ssot]` tags.
|
|
||||||
|
|
||||||
- HAKO_USING_SSOT_HAKO=0|1
|
- NYASH_JSON_ONLY=1
|
||||||
- Optional: within the SSOT bridge, call the Hako box `UsingResolveSSOTBox.resolve(name, ctx)` via the VM.
|
- When tools/wrappers set this, Stage‑B and related emitters print only JSON to stdout. Auxiliary logs should be routed to stderr.
|
||||||
- MVP passes `{ modules, using_paths, cwd }` in `ctx` (modules is consulted). IO is not performed in the box.
|
- Smokes expect single‑line JSON on stdout for Program(JSON) and MIR(JSON) producers.
|
||||||
- Requires `nyash` binary present; guard remains OFF by default.
|
|
||||||
|
|
||||||
Relative inference (SSOT)
|
- NYASH_ENABLE_USING=1, HAKO_ENABLE_USING=1
|
||||||
- Default OFF: `HAKO_USING_SSOT_RELATIVE=1` enables a minimal relative candidate synthesis (cwd → using_paths). When multiple candidates exist and `NYASH_USING_STRICT=1`, resolution delegates to legacy resolver (behavior unchanged).
|
- Enable using/alias resolution in dev/VM runs. Smokes set both for symmetry.
|
||||||
- Ambiguous list size: `HAKO_USING_SSOT_RELATIVE_AMBIG_FIRST_N=<N>` customizes how many candidates are shown in trace (default 3, bounded 1–10).
|
|
||||||
|
|
||||||
Notes on SSOT ctx (expansion plan)
|
Extern/Providers
|
||||||
- The bridge constructs a context with:
|
- Arity suffix normalization
|
||||||
- `modules` (Map<String,String>) — exact name → path mapping
|
- The VM accepts extern names with arity suffixes and normalizes them before dispatch:
|
||||||
- `using_paths` (Array<String>) — resolution bases (MVP: hint only)
|
- Examples: `env.get/1` → `env.get`, `hostbridge.extern_invoke/3` → `hostbridge.extern_invoke`.
|
||||||
- `cwd` (String) — caller’s directory (MVP: hint only)
|
- Implemented in `src/backend/mir_interpreter/handlers/calls/externs.rs` and `handlers/externals.rs`.
|
||||||
- Hako box will progressively leverage `using_paths`/`cwd` for relative inference (planned; defaults remain unchanged until enabled).
|
|
||||||
|
|
||||||
- HAKO_TLV_SHIM=0|1
|
Plugins / Autoload
|
||||||
- Enables an identity TLV round‑trip at the end of argument encoding for plugin calls.
|
- NYASH_USING_DYLIB_AUTOLOAD=1
|
||||||
- Requires building with `--features tlv-shim` to link the optional crate `nyash-tlv`.
|
- Enable autoload of `[using.<name>]` entries with `kind="dylib"` from `nyash.toml`.
|
||||||
- Default OFF; when OFF, the buffer is returned unchanged.
|
- The runner loads the specified shared libraries at startup and registers providers for the boxes declared in each plugin’s `nyash_box.toml`.
|
||||||
|
- Default: OFF. Guarded to avoid surprises; respects `NYASH_DISABLE_PLUGINS=1`.
|
||||||
|
|
||||||
- tlv-shim (Cargo feature)
|
Parser/Stage‑B
|
||||||
- `cargo build --features tlv-shim` links the optional `nyash-tlv` crate.
|
- HAKO_PARSER_STAGE3=1, NYASH_PARSER_STAGE3=1
|
||||||
- Without this feature, `HAKO_TLV_SHIM=1` has no effect and the original path is used.
|
- Accept Stage‑3 syntax (Break/Continue/Try/Catch, etc.). Enabled in smokes for Stage‑B runs.
|
||||||
|
|
||||||
TLV shim diagnostics
|
- HAKO_STAGEB_FUNC_SCAN=1
|
||||||
- HAKO_TLV_SHIM_TRACE=0|1
|
- Dev‑only: inject a `defs` array into Program(JSON) with scanned method definitions for `box Main`.
|
||||||
- When 1 (with `tlv-shim` feature), emit a concise trace tag `[tlv/shim:<Box>.<method>]` for shimmed calls.
|
|
||||||
- Default: minimal noise(`MapBox.set` のみ)。詳細な対象はフィルタで拡張。
|
|
||||||
- HAKO_TLV_SHIM_FILTER=<CommaSeparatedList>
|
|
||||||
- Filter which calls are traced(例: `MapBox.set,ArrayBox.push`)。`<Box>.<method>` または `method` のみで一致。
|
|
||||||
- 未設定時は最小(`MapBox.set` のみ)。
|
|
||||||
- HAKO_TLV_SHIM_TRACE_DETAIL=0|1
|
|
||||||
- When 1, emits `[tlv/shim:detail argc=N]`.
|
|
||||||
|
|
||||||
Examples (TLV trace)
|
Selfhost builders and wrappers
|
||||||
- `HAKO_TLV_SHIM=1 HAKO_TLV_SHIM_TRACE=1 HAKO_TLV_SHIM_FILTER=ArrayBox.push`
|
- HAKO_SELFHOST_BUILDER_FIRST=1
|
||||||
- `HAKO_TLV_SHIM=1 HAKO_TLV_SHIM_TRACE=1 HAKO_TLV_SHIM_FILTER=MapBox.get`
|
- Prefer the Hako MirBuilder path first; wrappers fall back to Rust CLI builder on failure to keep runs green.
|
||||||
- `HAKO_TLV_SHIM=1 HAKO_TLV_SHIM_TRACE=1 HAKO_TLV_SHIM_FILTER=ArrayBox.push,MapBox.get` (複数指定)
|
|
||||||
- `HAKO_SHOW_CALL_LOGS=1 HAKO_CALL_TRACE=1 HAKO_CALL_TRACE_FILTER=env.console.log`(テストランナーのフィルタ無効+extern だけ観測)
|
|
||||||
|
|
||||||
Core Thinning I (Phase 22.2) — Plugin C wrapper (design hook)
|
- NYASH_LLVM_USE_HARNESS=1
|
||||||
- HAKO_PLUGIN_LOADER_C_WRAP=0|1
|
- Enable LLVM harness mode (ny‑llvmc crate backend). Used by builder scripts for EXE/OBJ emission.
|
||||||
- When 1, emits a design-stage tag `[cwrap:invoke:<Box>.<method>]` at the plugin invocation site and then proceeds with the normal path.
|
|
||||||
- Default OFF; no behavior change.
|
|
||||||
- HAKO_PLUGIN_LOADER_C_WRAP_FILTER=<CommaSeparatedList>
|
|
||||||
- Filter for cwrap tags(例: `MapBox.set,ArrayBox.push`)。`<Box>.<method>` または `method` のみで一致。
|
|
||||||
|
|
||||||
Core Thinning I (Phase 22.2) — C-core probe (design hook)
|
Smokes
|
||||||
- HAKO_C_CORE_ENABLE=0|1
|
- SMOKES_DEFAULT_TIMEOUT
|
||||||
- When 1, emits a tag `[c-core:invoke:<Box>.<method>]` and (when built with feature `c-core`) calls a thin C probe (`nyash-c-core`) before proceeding with the normal path.
|
- Per‑test timeout (seconds) used by `tools/smokes/v2/run.sh --timeout` or auto profile defaults. Quick profile defaults to ~15s.
|
||||||
- Default OFF; behavior unchanged.
|
- Some tests wrap heavy steps (e.g., running a built EXE) with a shorter internal `timeout` to convert hangs into SKIP.
|
||||||
- HAKO_C_CORE_TARGETS=<CommaSeparatedList>
|
- HAKO_BUILD_TIMEOUT, HAKO_EXE_TIMEOUT
|
||||||
- Targets to probe(例: `MapBox.set,ArrayBox.push`)。未設定時は `MapBox.set` のみ。
|
- Internal timeouts (seconds) used by several phase2100 crate‑backend tests to bound ny‑llvmc build/link and EXE execution steps under quick.
|
||||||
- Build note: enable C-core with `cargo build --release -p nyash-rust --features c-core`.
|
- Defaults: `HAKO_BUILD_TIMEOUT=10`, `HAKO_EXE_TIMEOUT=5`.
|
||||||
- Examples:
|
|
||||||
- `HAKO_C_CORE_ENABLE=1 HAKO_C_CORE_TARGETS=ArrayBox.push`
|
|
||||||
- `HAKO_C_CORE_ENABLE=1 HAKO_C_CORE_TARGETS=ArrayBox.len,ArrayBox.length`
|
|
||||||
|
|
||||||
Related toggles used by smokes/tools (for parity with runner/test wrappers):
|
|
||||||
|
|
||||||
Call/route unified trace (optional)
|
|
||||||
- HAKO_CALL_TRACE=0|1
|
|
||||||
- When ON, emits `[call:<target>.<method>]` for both plugin calls and extern calls.
|
|
||||||
- Default OFF; logs go to stderr.
|
|
||||||
- HAKO_CALL_TRACE_FILTER=<CommaSeparatedList>
|
|
||||||
- Restrict `[call:]` logs to specific targets.
|
|
||||||
- Matches `<target>.<method>` or bare `method`.
|
|
||||||
- Examples:
|
|
||||||
- `HAKO_CALL_TRACE_FILTER=MapBox.set` (method-only)
|
|
||||||
- `HAKO_CALL_TRACE_FILTER=env.console.log,MapBox.set` (mix target+method)
|
|
||||||
- HAKO_SHOW_CALL_LOGS=0|1
|
|
||||||
- When 1, test runner disables its default log filter so `[call:]` traces appear in output.
|
|
||||||
- NYASH_PARSER_STAGE3=1, HAKO_PARSER_STAGE3=1, NYASH_PARSER_ALLOW_SEMICOLON=1
|
|
||||||
- NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1
|
|
||||||
- NYASH_DISABLE_NY_COMPILER=1, HAKO_DISABLE_NY_COMPILER=1
|
|
||||||
|
|
||||||
LLVM backend selector (builder wrapper)
|
|
||||||
- NYASH_LLVM_BACKEND=auto|crate|llvmlite|native
|
|
||||||
- Selects the backend used by `tools/ny_mir_builder.sh` for `--emit obj|exe`.
|
|
||||||
- Default: `auto`(優先順位: `crate`(ny-llvmc が存在すれば既定)→ `native`(llc がある場合)/`llvmlite` は明示指定時のみ)。
|
|
||||||
- `crate`: `./target/release/ny-llvmc` を使用(`cargo build -p nyash-llvm-compiler --release`)。推奨の本線(EXE/AOT)。
|
|
||||||
- `llvmlite`: Python ハーネス `tools/llvmlite_harness.py`(内部用途。外部から使う場合は明示指定)
|
|
||||||
- `native`: 将来の Hako‑native builder 用に予約。
|
|
||||||
- `--emit exe` のリンク追加ライブラリは `HAKO_AOT_LDFLAGS` で渡す(例: `-static`)。ny‑llvmc は `--libs` 経由で受理。
|
|
||||||
- 備考: crate 経路では `ny_main` の戻り値(i64)がプロセスの終了コードに反映(rc mapping)。
|
|
||||||
|
|
||||||
- NYASH_LLVM_NATIVE_TRACE=0|1
|
|
||||||
- When 1, dumps the native IR to stderr for debugging.
|
|
||||||
- Used with `native` backend to inspect generated LLVM IR before optimization.
|
|
||||||
- Default OFF; only active when NYASH_LLVM_BACKEND=native is set.
|
|
||||||
|
|
||||||
- HAKO_LLVM_CANARY_NORMALIZE=0|1
|
|
||||||
- 開発/カナリア専用の正規化スイッチ。`1` のとき、最小の JSON 形状差(`schema_version=1` → `"1.0"`、`blocks.inst` → `instructions`、`const` の `ty/value` 包装)を自動補正してからビルドします。
|
|
||||||
- 既定は `0`(無効)。既存ツールの挙動は変わりません。`NYASH_CLI_VERBOSE=1` のとき形状ヒントを `[ny-llvmc/hint]` で出力します。
|
|
||||||
|
|
||||||
Name mapping note (EXE link convenience)
|
|
||||||
- nyash.console.* は C リンク時にシンボル名 `nyash_console_*` に正規化される(ドット→アンダースコア)。
|
|
||||||
- 例: `externcall nyash.console.log(i8*)` → C シンボル `nyash_console_log`。
|
|
||||||
- 最小 C ランタイム(Phase 22.3)の `nyash-kernel-min-c` は `nyash_console_log(char*)` を提供(設計段階)。
|
|
||||||
|
|
||||||
Kernel Minimal C Runtime (Phase 22.3 — design)
|
|
||||||
- NYASH_KERNEL_C_MIN=0|1
|
|
||||||
- Reserved toggle for enabling the minimal C runtime shims(design‑stage; defaults OFF)
|
|
||||||
- Build: `cargo build --release -p nyash-kernel-min-c`(not linked by default)
|
|
||||||
|
|
||||||
ENV consolidation (aliases)
|
|
||||||
- NY compiler path
|
|
||||||
- Primary: `NYASH_USE_NY_COMPILER=0|1`
|
|
||||||
- Accepted aliases (deprecated; prints a one‑time warning):
|
|
||||||
- `NYASH_DISABLE_NY_COMPILER=1` → equivalent to `NYASH_USE_NY_COMPILER=0`
|
|
||||||
- `HAKO_DISABLE_NY_COMPILER=1` → equivalent to `NYASH_USE_NY_COMPILER=0`
|
|
||||||
- LLVM opt level
|
|
||||||
- Primary: `NYASH_LLVM_OPT_LEVEL`
|
|
||||||
- Accepted alias (deprecated; one‑time warning): `HAKO_LLVM_OPT_LEVEL`
|
|
||||||
- Gate‑C (Core direct route)
|
|
||||||
- Primary: `NYASH_GATE_C_CORE`
|
|
||||||
- Accepted alias (deprecated; one‑time warning): `HAKO_GATE_C_CORE`
|
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
- Primary keys are preferred and will be kept. Aliases remain accepted for a grace period and emit a concise deprecation line once per process.
|
- Keep default behavior unchanged for users. Use these toggles in development and CI wrappers only.
|
||||||
NYASH_FAIL_FAST
|
- Avoid enabling legacy paths except for targeted diagnosis. The unified call system is the default in both builder and VM.
|
||||||
- Type: 0|1 (default: 1)
|
|
||||||
- Purpose: Global Fail‑Fast policy for runtime fallbacks in Rust layer. When 1, prohibits silent or alternate‑route fallbacks and panics with a stable tag (e.g., [failfast/provider/filebox:*], [failfast/ssot/*]). Set to 0 temporarily during bring‑up or canaries that rely on legacy routes.
|
|
||||||
|
|
||||||
Hakorune Stage‑B (include policy)
|
Using/Resolver
|
||||||
- VM backend currently does not support `include` statements in Hako execution. Stage‑B focuses on producing Program(JSON v0) (one‑line) and avoids includes.
|
- HAKO_USING_RESOLVER_FIRST=1
|
||||||
- Program(JSON) → MIR(JSON) uses the Rust Gate‑C path by default. Hako MirBuilder is opt‑in and introduced gradually (registry/internal toggles).
|
- Try the SSOT using resolver (`using::resolver::resolve_using_target_common`) first in the runner pipeline.
|
||||||
|
- On failure, the pipeline falls back to the existing runner logic (aliases/builtins/plugins/relative search).
|
||||||
|
- Default: OFF. Use to validate resolver‑first migration without changing defaults.
|
||||||
|
|
||||||
MirBuilder toggles (opt‑in)
|
Builder/Emit (Selfhost)
|
||||||
- `HAKO_MIR_BUILDER_INTERNAL=0|1` (default: 1)
|
- HAKO_SELFHOST_BUILDER_FIRST=1
|
||||||
- Enable internal lowers (Return/If/Compare/BinOp/Method minimal). When 0, only delegate path is used (if enabled).
|
- Prefer Hako MirBuilder path first; delegates to provider/legacy on failure. Used by `tools/hakorune_emit_mir.sh` and bench scripts.
|
||||||
- `HAKO_MIR_BUILDER_REGISTRY=0|1` (default: 1)
|
- HAKO_MIR_BUILDER_BOX=hako.mir.builder|min
|
||||||
- Enable registry‑driven lowering (pattern names like `if.compare.intint`, `return.binop.intint`, `return.method.arraymap`).
|
- Choose selfhost builder box (full or minimal runner).
|
||||||
- `HAKO_MIR_BUILDER_DELEGATE=0|1` (default: 0)
|
- HAKO_SELFHOST_TRACE=1
|
||||||
- Delegate to Runner (`--program-json-to-mir`) instead of internal lowers. Useful for bring‑up; keep OFF for self‑host canaries.
|
- Print additional traces during MIR emit bench/wrappers.
|
||||||
- `HAKO_MIR_BUILDER_SKIP_LOOPS=0|1` (default: 0)
|
|
||||||
- Skip heavy loop lowers (`lower_loop_sum_bc`, `lower_loop_count_param`, `lower_loop_simple`) when 1. Diagnostic/bring‑up aid; no behavior change when unset.
|
|
||||||
- `HAKO_MIR_BUILDER_REGISTRY_ONLY=<name>` (optional)
|
|
||||||
- Restrict registry dispatch to a single pattern name (e.g., `return.method.arraymap`) for diagnostics.
|
|
||||||
|
|
||||||
MirBuilder (min) alias — bring‑up helper
|
- HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG=1
|
||||||
- Module alias: `hako.mir.builder.min` → `lang/src/mir/builder/MirBuilderMinBox.hako`
|
- Force the selfhost builder (and wrappers) to emit a minimal, pure control‑flow MIR(JSON) for loop cases (const/phi/compare/branch/binop/jump/ret)。
|
||||||
- Minimal entry that only loads lightweight lowers (e.g., `return.method.arraymap`, `return.int`).
|
- Dev専用。purify/normalize と併用すると ret ブロックに副作用命令を混入させない形で AOT/EXE 検証がしやすくなる。
|
||||||
- Tags: emits `[mirbuilder/min:<pattern>]` on success. Default behavior unchanged; use explicitly in tests/tools.
|
|
||||||
|
|
||||||
FileBox provider policy(dev overrides)
|
- HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE=1, HAKO_MIR_BUILDER_JSONFRAG_PURIFY=1
|
||||||
- Baseline: FileBox は ring‑1 常備(core‑ro)として登録されます。プラグイン未配置でも panic 経路にはならず、Fail‑Fast が ON の場合は明示タグで失敗します(例: `[failfast/provider/filebox:*]`)。
|
- JsonFrag の正規化と純化を有効化する。purify=1 のとき newbox/boxcall/externcall/mir_call を除去し、ret 以降の命令を打ち切る(構造純化)。
|
||||||
- `NYASH_FILEBOX_MODE=auto|core-ro|plugin-only` — provider selection mode(default auto; with plugins disabled, resolves to core‑ro)
|
|
||||||
- `NYASH_FILEBOX_ALLOW_FALLBACK=0|1` — When 1, allows core‑ro fallback even if Fail‑Fast is ON (use sparingly; defaults OFF).
|
|
||||||
- JSON pipelines: `NYASH_JSON_ONLY=1` also permits core‑ro fallback under Fail‑Fast (quiet structured I/O).
|
|
||||||
|
|
||||||
Examples (Fail‑Fast tags and safe overrides)
|
Provider path (delegate)
|
||||||
- Default (Fail‑Fast=1): core‑ro フォールバックを禁止(プロバイダ未設定時に失敗)
|
- HAKO_MIR_NORMALIZE_PROVIDER=1
|
||||||
- 例外ログ: `[failfast/provider/filebox:auto-fallback-blocked]`
|
- Provider(Rust)出力の MIR(JSON) に対して、Hako の JsonFrag 正規化パスを適用する(tools/hakorune_emit_mir.sh 内部)。
|
||||||
- 実行例:
|
- 互換維持のため既定はOFF。Box 系で ret ブロックに副作用命令が残るようなケースの暫定純化に利用できる。
|
||||||
```sh
|
|
||||||
# will fail fast without plugins
|
|
||||||
./target/release/hakorune --backend vm apps/tests/filebox_sample.hako
|
|
||||||
```
|
|
||||||
- Bring‑up(局所的に許可): `NYASH_FILEBOX_ALLOW_FALLBACK=1` で core‑ro を許可
|
|
||||||
- 実行例:
|
|
||||||
```sh
|
|
||||||
NYASH_FILEBOX_ALLOW_FALLBACK=1 ./target/release/hakorune --backend vm apps/tests/filebox_sample.hako
|
|
||||||
```
|
|
||||||
- JSON パイプ(静音): `NYASH_JSON_ONLY=1` を併用して整形済み JSON のみを stdout に出す
|
|
||||||
- 実行例:
|
|
||||||
```sh
|
|
||||||
NYASH_JSON_ONLY=1 NYASH_FILEBOX_ALLOW_FALLBACK=1 ./target/release/hakorune --backend vm apps/tests/emit_program_json.hako
|
|
||||||
```
|
|
||||||
|
|
||||||
Provider policy(共通)
|
- NYASH_LLVM_FAST_INT=1
|
||||||
- `HAKO_PROVIDER_POLICY=strict-plugin-first|safe-core-first|static-preferred`
|
- Opt-in toggle for integer hot paths in LLVM MIR/IR. When `1`, `binop` や `compare` は同一ブロックの `vmap` 定義を最優先し、resolver/PHI を経由しない i64 経路を選びます。ループや分岐で i64 が綺麗に残るときだけ ON にして、CI 等では unset のまま。
|
||||||
- Auto モード時の選択ポリシーを制御(既定: `strict-plugin-first`)。
|
|
||||||
- `safe-core-first`/`static-preferred` は ring‑1(静的/core‑ro)を優先、利用不可時のみプラグインにフォールバック。
|
|
||||||
- Box個別のモード指定(例: FileBox)
|
|
||||||
- `<BOX>_MODE=auto|ring1|plugin-only`(例: `NYASH_FILEBOX_MODE`)
|
|
||||||
- `<BOX>_ALLOW_FALLBACK=0|1`(Fail‑Fast 例外。局所許可)
|
|
||||||
- 既存の FileBox 変数はそのまま有効。共通パターンとして今後は他Boxにも拡張予定。
|
|
||||||
|
|
||||||
Selfhost‑first wrapper toggles (Stage‑B → MirBuilder)
|
- NYASH_MIR_DEV_IDEMP=1
|
||||||
- `HAKO_SELFHOST_BUILDER_FIRST=0|1` (default: 0)
|
- Dev-only: Enable idempotence markers inside MIR normalize passes. Each pass stamps `pass:function` in the module metadata after rewriting a function once, and subsequent optimizer runs skip reprocessing that function for the same pass. 仕様不変(既定OFF)。診断時に複数回最適化を呼んでも差分が出ないことを保証するための保険だよ。
|
||||||
- When 1, tools like `tools/hakorune_emit_mir.sh` try Stage‑B → MirBuilder(Hako) first, and only fall back to the Rust delegate when necessary.
|
- NYASH_LLVM_FAST=1
|
||||||
- `HAKO_SELFHOST_NO_DELEGATE=0|1` (default: 0)
|
- Enables NyRT-based FAST helpers (string length via `nyrt_string_length`, pointer re-use, etc.) and now caches literal-backed `length/len` results so loops reuse the same constant value instead of re-calling the helper. Default OFF.
|
||||||
- When 1, forbids delegate fallback in the wrapper. If the Hako MirBuilder fails, the wrapper fails fast (useful to validate the self‑host path).
|
|
||||||
VM/AOT fast-path toggles (bench/dev only)
|
|
||||||
- NYASH_VM_FAST=0|1
|
|
||||||
- Enables small VM micro-optimizations used in micro-benchmarks (no behavior changes in normal runs):
|
|
||||||
- new StringBox("const") fast path (registry bypass when safe)
|
|
||||||
- StringBox.length/size returning i64 directly (avoid boxing)
|
|
||||||
- Default OFF.
|
|
||||||
- NYASH_LLVM_FAST=0|1
|
|
||||||
- Enables AOT lowering tweaks in ny-llvmc (opt-in, benches only):
|
|
||||||
- StringBox.length/size lowered to extern helper returning i64 (no boxing)
|
|
||||||
- Default OFF. Guarded in ny-llvmc; legacy lowering remains default.
|
|
||||||
|
|
||||||
MirBuilder toggles (trace and JsonFrag)
|
- NYASH_MIR_LOOP_HOIST=1
|
||||||
- `HAKO_MIR_BUILDER_TRACE=0|1` (default: 0)
|
- AOT 前準備(AotPrepBox)での軽ホイスティングを有効化。固定文字列の `length/len` を即値に置換(JSON 書換え)する。制御フローは変更しない。既定 OFF。
|
||||||
- When 1, emits concise builder tags like `[mirbuilder/internal/*]` from Hako lowers. Errors remain visible regardless; this flag controls informational tags.
|
|
||||||
- `HAKO_MIR_BUILDER_LOOP_JSONFRAG=0|1` (default: 0)
|
|
||||||
- Enable JsonFrag minimal MIR assembly in loop adapters/lowers (structure observation only).
|
|
||||||
- `HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE=0|1` (default: 0)
|
|
||||||
- Normalize JsonFrag output for stable canaries (const grouping, phi placement, numeric canonicalization).
|
|
||||||
- `HAKO_MIR_BUILDER_LOOP_RETURN=string|map` (default: string)
|
|
||||||
- Controls the return type of the loop adapter JsonFrag path. `string` returns MIR(JSON) text; `map` returns a `MapBox` like `{ mir: <string> }`.
|
|
||||||
|
|
||||||
Provider diagnostics
|
- NYASH_AOT_COLLECTIONS_HOT=1
|
||||||
- `HAKO_PROVIDER_TRACE=0|1` (default: 0)
|
- AOT 前準備(AotPrepBox)で Array/Map の `boxcall` を `externcall`(`nyash.array.*` / `nyash.map.*`)のホットパスに張り替える。AOT 専用の最短経路で、診断を省いてオーバーヘッドを抑える。既定 OFF。
|
||||||
- When 1, forces provider selection tags like `[provider/select:FileBox ring=1 src=static]` to appear even under `NYASH_JSON_ONLY=1`.
|
|
||||||
EXE argv bridge (crate backend)
|
- HAKO_MIR_NORMALIZE_PRINT=1
|
||||||
- NYASH_EXE_ARGV=0|1
|
- AotPrep の正規化パス(.hako)を有効化して、`print` 命令を `externcall env.console.log(value)` に書き換える(CFG 不変)。既定 OFF。
|
||||||
- When 1, the ny-llvmc entry wrapper calls `nyash.env.argv_get()` to obtain an `ArrayBox` of process arguments and passes it to `Main.main(args)`.
|
|
||||||
- Default OFF(互換維持)。OFF の場合は空の `ArrayBox` が渡されます。
|
- HAKO_MIR_NORMALIZE_REF=1
|
||||||
- VM 実行は従来どおり環境変数経由(`NYASH_SCRIPT_ARGS_HEX_JSON` / `NYASH_SCRIPT_ARGS_JSON` / `NYASH_ARGV`)で受け取り、`Main.main(args)` に配列を渡します。
|
- AotPrep の正規化パス(.hako)を有効化して、`ref_get/ref_set` を `boxcall getField/setField` に書き換える(CFG 不変、best-effort)。既定 OFF。
|
||||||
- MIR optimizer dev gate
|
|
||||||
- NYASH_MIR_DISABLE_OPT=0|1 (alias: HAKO_MIR_DISABLE_OPT)
|
- HAKO_MIR_NORMALIZE_ARRAY=1
|
||||||
- When 1, disables all MIR optimizer passes (diagnostics only). Default OFF.
|
- AotPrep の正規化パス(.hako)を有効化して、`array_get/array_set`(および一部の `map_get/map_set`)を `boxcall get/set` に書き換える(CFG 不変、best-effort)。CollectionsHot の前処理として有効。既定 OFF。
|
||||||
- 用途: 最適化の誤変換切り分けや、退行時の一時回避(既定不変)。
|
|
||||||
|
- NYASH_AOT_INDEX_WINDOW=1
|
||||||
|
- AotPrep(CollectionsHot 内部) の index 共有をブロック境界をまたいだ短窓で行う実験的トグル(デフォルト OFF)。
|
||||||
|
- 窓はバイト数で固定(2048)。誤共有のリスクがあるため初期はベンチ限定で使用。
|
||||||
|
|
||||||
|
- NYASH_VERIFY_RET_PURITY=1
|
||||||
|
- Ret ブロック純化の Fail-Fast トグル。Return の直前に `Const`・`Copy`・`Phi`・`Nop` 以外があると MIR/VM 実行が失敗するようにする開発ガード。既定 OFF。
|
||||||
|
|
||||||
|
- tools/perf/dump_mir.sh
|
||||||
|
- MIR(JSON) を provider-first → jsonfrag フォールバックで吐く小さな wrapper。`--mode provider` で通常 builder 主導、`--mode jsonfrag` で最小化ループを強制。内部でブロックごとの op カウンターを出力するので、branch/map/kilo などのホットパスをすばやく可視化できるよ。
|
||||||
|
|
||||||
|
AOT/LLVM (ny-llvmc)
|
||||||
|
- HAKO_LLVM_OPT_LEVEL=0|1
|
||||||
|
- ny-llvmc optimization level (default 0/O0). Bench scripts keep O0 unless overridden.
|
||||||
|
- NYASH_LLVM_DUMP_MIR_IN=/path/to/out.json
|
||||||
|
- ny-llvmc が受け取る MIR(JSON) をそのまま保存する開発用トグル。AotPrep 適用後の実入力を観測するのに使う。既定 OFF。
|
||||||
|
|
||||||
|
Bench helpers
|
||||||
|
- PERF_USE_PROVIDER=1
|
||||||
|
- `tools/perf/microbench.sh` で provider/selfhost-first の MIR emit を優先(jsonfrag 強制を解除)。環境により provider 失敗時は自動で最小ループにフォールバック。
|
||||||
|
|||||||
45
docs/development/cleanup/legacy-byname-removal.md
Normal file
45
docs/development/cleanup/legacy-byname-removal.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
Legacy By‑Name Call Removal Plan
|
||||||
|
|
||||||
|
Context
|
||||||
|
- Unified call system is default. VM routes by `Callee` kind (Global/Method/Extern/…)
|
||||||
|
- Legacy by‑name resolution (callee=None → string name lookup) is gated by env and OFF by default.
|
||||||
|
- Goal: remove legacy code path once parity is proven at integration/full levels.
|
||||||
|
|
||||||
|
Guards / Current Behavior
|
||||||
|
- 旧 by‑name 経路は削除済み。`NYASH_VM_ALLOW_LEGACY` は廃止。
|
||||||
|
- 既定: `callee=None` 生成時は Fail‑Fast(Builder で Callee を必ず付与)。
|
||||||
|
|
||||||
|
Scope of Work
|
||||||
|
1) Identify callers that could still rely on legacy resolution
|
||||||
|
- Builder emissions that may omit `callee` (should be none after Phase 1 refactor)
|
||||||
|
- Any VM entry points or helpers constructing by‑name calls dynamically
|
||||||
|
- Older tests or tools expecting by‑name lookups
|
||||||
|
|
||||||
|
2) Ensure unified path covers them
|
||||||
|
- For Global user functions: attach `Callee::Global("Module.func")`
|
||||||
|
- For dynamic/indirect calls: mark `Callee::Value` and keep VM restrictions explicit
|
||||||
|
- For extern‑like names: delegate via handlers/calls/externs.rs (SSOT)
|
||||||
|
- Normalize arity (`foo/1` → `foo`) via `utils::normalize_arity_suffix`
|
||||||
|
|
||||||
|
3) Strengthen tests
|
||||||
|
- Smokes/quick: unified path(by‑name 不可)を既に強制
|
||||||
|
- Integration/full: ユーザー関数呼び出し/extern 名正規化のカナリアを追加
|
||||||
|
- 旧ENV依存がないことを確認(`NYASH_VM_ALLOW_LEGACY` は削除済)
|
||||||
|
|
||||||
|
4) Remove legacy code path
|
||||||
|
- Delete `src/backend/mir_interpreter/handlers/calls/legacy.rs` and its resolver module
|
||||||
|
- Drop env guard checks referring to legacy path (keep error message clarity)
|
||||||
|
- Update READMEs to remove temporary notes
|
||||||
|
|
||||||
|
Readiness Criteria (before removal)
|
||||||
|
- quick/integration/full all green with legacy OFF
|
||||||
|
- No `callee=None` emission in builder (verified by grepping MIR JSON / code review)
|
||||||
|
- Extern SSOT accepts arity‑suffixed names; Global extern‑like names delegate properly
|
||||||
|
|
||||||
|
Rollback Plan
|
||||||
|
- Keep a small revertable commit boundary for legacy deletion
|
||||||
|
- If issues appear, reintroduce legacy.rs with the same path under a dev‑guard until fixed
|
||||||
|
|
||||||
|
Cross‑References
|
||||||
|
- handlers/calls/README.md (SSOT and boundaries)
|
||||||
|
- docs/ENV_VARS.md (env guards and policies)
|
||||||
43
docs/development/normalization/ownership.md
Normal file
43
docs/development/normalization/ownership.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
Normalization Ownership — Rust vs Hakorune
|
||||||
|
|
||||||
|
Goal
|
||||||
|
- Prevent double-normalization and keep a clear Single Source of Truth (SSOT) for where each rewrite lives.
|
||||||
|
|
||||||
|
Ownership
|
||||||
|
- Hakorune layer (Hako scripts)
|
||||||
|
- Methodization: Global("Box.m/N") → mir_call(Method) への変換。
|
||||||
|
- Name/arity canonicalization(Box.method/N)。
|
||||||
|
- Function defs scan/inject(HAKO_STAGEB_FUNC_SCAN, HAKO_MIR_BUILDER_FUNCS)。
|
||||||
|
- Emit JSON v1 + unified mir_call(NYASH_JSON_SCHEMA_V1=1, NYASH_MIR_UNIFIED_CALL=1)。
|
||||||
|
- 可視化タグ/診断の出力(dev のみ)
|
||||||
|
|
||||||
|
- Rust layer
|
||||||
|
- Structural/correctness: SSA/PHI、受信側ローカライズ(LocalSSA/Copy/pin)。
|
||||||
|
- Legacy JSON v0 → minimal bridging(json_v0_bridge 内での Callee 補完など)。
|
||||||
|
- 互換/安全弁: 未定義受信の構造的回復(同一BB直近 NewBox)など、dev ガード付きの最小範囲。
|
||||||
|
- Optimizer は構造・副作用ベースの最適化に限定(意味論の再書換えはしない)。
|
||||||
|
|
||||||
|
Guards and Toggles
|
||||||
|
- Hako(dev 推奨セット)
|
||||||
|
- HAKO_STAGEB_FUNC_SCAN=1
|
||||||
|
- HAKO_MIR_BUILDER_FUNCS=1
|
||||||
|
- HAKO_MIR_BUILDER_CALL_RESOLVE=1
|
||||||
|
- HAKO_MIR_BUILDER_METHODIZE=1(methodize が v1+unified 出力へ寄与)
|
||||||
|
- NYASH_JSON_SCHEMA_V1=1, NYASH_MIR_UNIFIED_CALL=1
|
||||||
|
|
||||||
|
- Rust(bridge/診断)
|
||||||
|
- HAKO_BRIDGE_METHODIZE=1 は bring-up 用の補助。Hako 既定化後は OFF(撤去予定)。
|
||||||
|
- mir_plugin_invoke/plugin_only は A/B 評価・診断用途。既定 OFF。
|
||||||
|
|
||||||
|
Rules of Engagement
|
||||||
|
- v1 + unified を Hako で生成した場合、Rust 側での methodize/再書換えは行わない(構造のみ)。
|
||||||
|
- json_v0_bridge は v0 入力に対する互換のために限定運用。v1 既定化が進めば縮退する。
|
||||||
|
- dev の安全弁(未定義受信の構造回復など)は、テストが十分になり次第 OFF/撤去する。
|
||||||
|
|
||||||
|
Testing
|
||||||
|
- Canary
|
||||||
|
- tools/dev/phase217_methodize_canary.sh(rc=5)
|
||||||
|
- tools/dev/phase217_methodize_json_canary.sh(schema_version + mir_call present、Method優先)
|
||||||
|
- tools/dev/phase216_chain_canary_call.sh(rc=5)
|
||||||
|
- 失敗時は Hako 側(methodize)→ Rust 側(構造) の順で原因を特定する。
|
||||||
|
|
||||||
@ -1,31 +1,33 @@
|
|||||||
# Phase 21.5 — Optimization (ny-llvm crate line)
|
# Phase 21.5 — Optimization (AotPrep‑First)
|
||||||
|
|
||||||
Scope
|
目的
|
||||||
- Optimize hot paths for the crate (ny-llvmc) line using Hakorune scripts only.
|
- .hako 側(AotPrep)で前処理最適化(構造のみ)を行い、LLVM/AOT に渡すIRを軽量にする。
|
||||||
- Preserve default behavior; all risky changes behind dev toggles.
|
- 既定は挙動不変(opt‑in)。Return 純化ガードで安全性を担保。
|
||||||
- Measure EXE runtime (build once, run many) to avoid toolchain overhead noise.
|
|
||||||
|
|
||||||
Targets (initial)
|
チェックリスト
|
||||||
- loop: integer accumulations (no I/O)
|
- [x] パス分割(StrlenFold / LoopHoist / ConstDedup / CollectionsHot / BinopCSE)
|
||||||
- strlen: FAST=1 path (pointer → nyrt_string_length)
|
- [x] CollectionsHot(Array/Map)導入(既定OFF)
|
||||||
- box: construct/destroy minimal boxes (String/Integer)
|
- [x] Map key モード `NYASH_AOT_MAP_KEY_MODE={h|i64|hh|auto}`
|
||||||
|
- [x] LoopHoist v1 / BinopCSE v1(最小)
|
||||||
|
- [x] ベンチ `linidx`/`maplin` 追加
|
||||||
|
- [ ] LoopHoist v2(+/* 右項 const の連鎖前出し/fix‑point)
|
||||||
|
- [ ] BinopCSE v2(線形 `i*n` 共通化の強化)
|
||||||
|
- [ ] CollectionsHot v2(array index の共通SSA利用)
|
||||||
|
- [ ] Map auto 精緻化(_is_const_or_linear の再帰判定)
|
||||||
|
- [ ] Idempotence(置換済みタグで再実行時も不変)
|
||||||
|
- [ ] `arraymap`/`matmul` ≤ 125%(C基準)
|
||||||
|
|
||||||
Methodology
|
トグル
|
||||||
- Build once via ny-llvmc; time execution only (`--exe` mode).
|
- `NYASH_MIR_LOOP_HOIST=1` … StrlenFold/LoopHoist/ConstDedup/BinopCSE を有効化
|
||||||
- Runs: 3–5; report median and average (target ≥ 100ms per run).
|
- `NYASH_AOT_COLLECTIONS_HOT=1` … CollectionsHot(Array/Map)
|
||||||
- Observe NYASH_VM_STATS=1 (inst/compare/branch) where relevant to correlate structure and runtime.
|
- `NYASH_AOT_MAP_KEY_MODE` … `h|i64|hh|auto`(推奨: `auto`)
|
||||||
|
- `NYASH_VERIFY_RET_PURITY=1` … Return 純化ガード(開発時ON)
|
||||||
Commands (examples)
|
|
||||||
- tools/perf/phase215/bench_loop.sh --runs 5
|
|
||||||
- tools/perf/phase215/bench_strlen.sh --runs 5 --fast 1
|
|
||||||
- tools/perf/phase215/run_all.sh --runs 5 --timeout 120
|
|
||||||
|
|
||||||
Dev Toggles (keep OFF by default)
|
|
||||||
- NYASH_LLVM_FAST=1 (strlen FAST)
|
|
||||||
- HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE=1 (normalize)
|
|
||||||
- HAKO_MIR_BUILDER_NORMALIZE_TAG=1 (tag, test-only)
|
|
||||||
|
|
||||||
Exit Criteria
|
|
||||||
- Representative microbenches stable (≤ 5% variance) and ≥ 80% of C baselines.
|
|
||||||
- No regression in EXE canaries (loop/print/strlen FAST) and VM parity canaries.
|
|
||||||
|
|
||||||
|
ベンチ(例)
|
||||||
|
```bash
|
||||||
|
export NYASH_SKIP_TOML_ENV=1 NYASH_DISABLE_PLUGINS=1 \
|
||||||
|
NYASH_LLVM_SKIP_BUILD=1 NYASH_LLVM_FAST=1 NYASH_LLVM_FAST_INT=1 \
|
||||||
|
NYASH_MIR_LOOP_HOIST=1 NYASH_AOT_COLLECTIONS_HOT=1 NYASH_VERIFY_RET_PURITY=1
|
||||||
|
for c in arraymap matmul sieve linidx maplin; do \
|
||||||
|
tools/perf/microbench.sh --case $c --exe --runs 3; echo; done
|
||||||
|
```
|
||||||
|
|||||||
@ -36,7 +36,13 @@ Canaries
|
|||||||
- tools/dev/stageb_loop_json_canary.sh — Program(JSON) shape for loop(i<n){i=i+1}
|
- tools/dev/stageb_loop_json_canary.sh — Program(JSON) shape for loop(i<n){i=i+1}
|
||||||
- tools/dev/phase216_chain_canary.sh — end‑to‑end EXE rc=10 for minimal loop
|
- tools/dev/phase216_chain_canary.sh — end‑to‑end EXE rc=10 for minimal loop
|
||||||
|
|
||||||
|
Provider Path Notes (Dev)
|
||||||
|
- Optional normalization for provider output is available via `HAKO_MIR_NORMALIZE_PROVIDER=1`.
|
||||||
|
- This applies the same JsonFrag normalizer/purifier to MIR(JSON) emitted by the Rust Provider path.
|
||||||
|
- Keep defaults unchanged; use only during bring‑up to eliminate ret‑after effects.
|
||||||
|
- `HAKO_MIR_BUILDER_LOOP_FORCE_JSONFRAG=1` now short‑circuits both selfhost‑first and provider‑first wrappers to emit a minimal, pure control‑flow MIR suitable for EXE build sanity.
|
||||||
|
- Default OFF; intended for small canaries and performance harness bring‑up.
|
||||||
|
|
||||||
Removal Plan for temporary parser fallback
|
Removal Plan for temporary parser fallback
|
||||||
- Once VM/gpos interaction is fixed and parser emits correct loop JSON without guards,
|
- Once VM/gpos interaction is fixed and parser emits correct loop JSON without guards,
|
||||||
remove the conservative fallback in ParserControlBox.parse_loop.
|
remove the conservative fallback in ParserControlBox.parse_loop.
|
||||||
|
|
||||||
|
|||||||
51
docs/development/roadmap/phases/phase-21.6/README.md
Normal file
51
docs/development/roadmap/phases/phase-21.6/README.md
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# Phase 21.6 — Dual‑Emit Parity & C‑line Readiness
|
||||||
|
|
||||||
|
Goal: Produce identical MIR(JSON) from both provider (Rust) and selfhost (Hako) builder paths, measure generation cost, and keep AOT (ny‑llvmc) fast/green. All work is semantics‑preserving; defaults remain unchanged.
|
||||||
|
|
||||||
|
## Checklists
|
||||||
|
|
||||||
|
- [ ] Dual‑emit parity on representative apps (MIR(JSON) normalized SHA1 equal)
|
||||||
|
- [ ] Resolver‑first ON passes quick/integration
|
||||||
|
- [ ] Selfhost‑first fallback ok (provider/legacy on failure)
|
||||||
|
- [ ] AOT obj/exe via ny‑llvmc (crate backend) green
|
||||||
|
- [ ] Docs updated (bench guides, env vars, quick recipes)
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
- Dual emit + compare + bench: `tools/perf/dual_emit_compare.sh <input.hako> [rounds]`
|
||||||
|
- MIR emit bench: `tools/perf/bench_hakorune_emit_mir.sh <input.hako> [rounds]`
|
||||||
|
- AOT bench: `tools/perf/bench_ny_mir_builder.sh <mir.json> [rounds]`
|
||||||
|
- MIR diff: `tools/perf/compare_mir_json.sh <a.json> <b.json>`
|
||||||
|
|
||||||
|
## Env Knobs
|
||||||
|
|
||||||
|
- `HAKO_USING_RESOLVER_FIRST=1` (resolver‑first)
|
||||||
|
- `HAKO_SELFHOST_BUILDER_FIRST=1` (selfhost→provider→legacy)
|
||||||
|
- `HAKO_MIR_BUILDER_BOX=hako.mir.builder|min`
|
||||||
|
- `NYASH_LLVM_BACKEND=crate`(ny‑llvmc)
|
||||||
|
- `HAKO_LLVM_OPT_LEVEL=0|1`(AOT O0 既定)
|
||||||
|
|
||||||
|
## Benchmarks — Tracking
|
||||||
|
|
||||||
|
Record normalized parity and generation times here (edit in place).
|
||||||
|
|
||||||
|
Legend: SHA1 = normalized JSON digest; Parity=Yes when SHA1 equal; Times are medians unless noted.
|
||||||
|
|
||||||
|
| Benchmark (Hako) | Resolver | Parity | Provider p50 (ms) | Selfhost p50 (ms) | Notes |
|
||||||
|
|------------------------------------------|----------|--------|-------------------|-------------------|-------|
|
||||||
|
| apps/examples/json_query/main.hako | off/on | | | | |
|
||||||
|
| apps/examples/json_pp/main.hako | off/on | | | | |
|
||||||
|
| apps/examples/json_lint/main.hako | off/on | | | | |
|
||||||
|
| apps/examples/json_query_min/main.hako | off/on | | | | |
|
||||||
|
|
||||||
|
How to fill:
|
||||||
|
1) Run `tools/perf/dual_emit_compare.sh <file> 5`
|
||||||
|
2) Copy p50s from the summary lines and mark Parity based on `compare_mir_json.sh` output.
|
||||||
|
3) Note any diffs (callee kinds/order/phi/meta) in Notes.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [ ] If parity holds on the above set, extend to apps/tests subset
|
||||||
|
- [ ] If diffs remain, categorize and align either provider or selfhost output
|
||||||
|
- [ ] Keep AOT line green under `HAKO_LLVM_OPT_LEVEL=0` and optional `=1` spot checks
|
||||||
|
|
||||||
@ -9,12 +9,15 @@ Targets (must be green)
|
|||||||
|
|
||||||
Canaries
|
Canaries
|
||||||
- tools/dev/phase216_chain_canary_call.sh — remains PASS when OFF, PASS when ON
|
- tools/dev/phase216_chain_canary_call.sh — remains PASS when OFF, PASS when ON
|
||||||
- Add: tools/dev/phase217_methodize_canary.sh (dev) — asserts method callee usage in MIR or IR tags
|
- tools/dev/phase217_methodize_canary.sh (dev) — compile-run rc=5(セマンティクス)
|
||||||
|
- tools/dev/phase217_methodize_json_canary.sh (dev) — v1 root(schema_version)+ mir_call present(Methodが望ましい、Globalは経過容認)
|
||||||
|
|
||||||
Toggles
|
Toggles
|
||||||
- HAKO_MIR_BUILDER_METHODIZE=1 (new)
|
- HAKO_MIR_BUILDER_METHODIZE=1 (new)
|
||||||
- HAKO_STAGEB_FUNC_SCAN=1 / HAKO_MIR_BUILDER_FUNCS=1 / HAKO_MIR_BUILDER_CALL_RESOLVE=1 (existing)
|
- HAKO_STAGEB_FUNC_SCAN=1 / HAKO_MIR_BUILDER_FUNCS=1 / HAKO_MIR_BUILDER_CALL_RESOLVE=1 (existing)
|
||||||
|
- 一軍(devプロファイルで既定ON): 上記3つ + NYASH_JSON_SCHEMA_V1=1 + NYASH_MIR_UNIFIED_CALL=1
|
||||||
|
- 診断(既定OFF): HAKO_BRIDGE_METHODIZE=1(core_bridge側の補助。Hako既定化後に撤去)
|
||||||
|
|
||||||
Rollback
|
Rollback
|
||||||
- Disable HAKO_MIR_BUILDER_METHODIZE; remove methodization rewrite; keep Global path active.
|
- Disable HAKO_MIR_BUILDER_METHODIZE; remove methodization rewrite; keep Global path active.
|
||||||
|
- core_bridgeの methodize ブリッジは Hako側が既定化され次第、撤去(タグ: [bridge/methodize:*] を一時観測可能にして差分検知)
|
||||||
|
|||||||
67
docs/guides/perf/benchmarks.md
Normal file
67
docs/guides/perf/benchmarks.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Benchmarking MIR Generation & AOT (Quickstart)
|
||||||
|
|
||||||
|
This guide shows how to measure Hakorune's MIR emit (Stage‑B → MIR(JSON)) and AOT build (MIR(JSON) → obj/exe) without llvmlite. All commands are semantics‑preserving and keep defaults conservative (fail‑fast, O0).
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Build binaries once (release):
|
||||||
|
- `cargo build --release`
|
||||||
|
- `cargo build --release -p nyash-llvm-compiler` (ny-llvmc)
|
||||||
|
- `cargo build --release -p nyash_kernel` (NyRT static runtime)
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
### 1) MIR emit bench (Stage‑B → MIR(JSON))
|
||||||
|
|
||||||
|
- Script: `tools/perf/bench_hakorune_emit_mir.sh`
|
||||||
|
- Usage: `tools/perf/bench_hakorune_emit_mir.sh <input.hako> [rounds]`
|
||||||
|
- Output CSV: `round,ms,size,sha1` (sha1 is normalized JSON digest; identical = structure equal)
|
||||||
|
- Useful env toggles:
|
||||||
|
- `HAKO_USING_RESOLVER_FIRST=1` (resolver‑first)
|
||||||
|
- `HAKO_SELFHOST_BUILDER_FIRST=1` (selfhost builder → provider fallback)
|
||||||
|
- `HAKO_MIR_BUILDER_BOX=hako.mir.builder|min` (builder selector)
|
||||||
|
- `HAKO_SELFHOST_TRACE=1` (stderr trace)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
tools/perf/bench_hakorune_emit_mir.sh apps/examples/json_query/main.hako 5
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2) MIR(JSON) → obj/exe bench(ny-llvmc / crate backend)
|
||||||
|
|
||||||
|
- Script: `tools/perf/bench_ny_mir_builder.sh`
|
||||||
|
- Usage: `tools/perf/bench_ny_mir_builder.sh <mir.json> [rounds]`
|
||||||
|
- Output CSV: `kind,round,ms`(kind = obj|exe)
|
||||||
|
- Useful env toggles:
|
||||||
|
- `NYASH_LLVM_BACKEND=crate`(既定。ny-llvmc を使う)
|
||||||
|
- `HAKO_LLVM_OPT_LEVEL=0|1`(既定は 0/O0)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
tools/perf/bench_ny_mir_builder.sh /path/to/out.json 3
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3) MIR(JSON) 構造比較
|
||||||
|
|
||||||
|
- Script: `tools/perf/compare_mir_json.sh`
|
||||||
|
- Usage: `tools/perf/compare_mir_json.sh <a.json> <b.json>`
|
||||||
|
- 出力: サイズと正規化 SHA1、差分(jq -S 利用時は整形差分)。
|
||||||
|
|
||||||
|
## Typical Workflow
|
||||||
|
|
||||||
|
1) Emit MIR(JSON)
|
||||||
|
- `tools/hakorune_emit_mir.sh apps/APP/main.hako out.json`
|
||||||
|
2) Measure MIR emit time
|
||||||
|
- `HAKO_USING_RESOLVER_FIRST=1 tools/perf/bench_hakorune_emit_mir.sh apps/APP/main.hako 5`
|
||||||
|
3) Measure AOT(obj/exe)
|
||||||
|
- `NYASH_LLVM_BACKEND=crate tools/perf/bench_ny_mir_builder.sh out.json 3`
|
||||||
|
4) Compare MIR outputs across toggles/branches
|
||||||
|
- `tools/perf/compare_mir_json.sh out_before.json out_after.json`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- All benches are best‑effort micro‑measurements; run multiple rounds and compare medians.
|
||||||
|
- Keep defaults strict: resolver/selfhost togglesは明示時のみON。AOTは O0 既定(`HAKO_LLVM_OPT_LEVEL` で上げられます)。
|
||||||
|
|
||||||
12
examples/plugins/autoload_sample/README.md
Normal file
12
examples/plugins/autoload_sample/README.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Plugins Autoload Sample
|
||||||
|
|
||||||
|
How to run (from repo root):
|
||||||
|
- Build core: `cargo build --release`
|
||||||
|
- Enable autoload and run:
|
||||||
|
`NYASH_USING_DYLIB_AUTOLOAD=1 ./target/release/hakorune examples/plugins/autoload_sample/main.hako`
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- The plugin path in nyash.toml points to `plugins/nyash-counter-plugin/*`. Ensure the shared library exists.
|
||||||
|
- If missing, build plugins with `cargo build --release -p nyash-counter-plugin`.
|
||||||
|
- Autoload is guarded; respects `NYASH_DISABLE_PLUGINS=1`.
|
||||||
|
|
||||||
13
examples/plugins/autoload_sample/main.hako
Normal file
13
examples/plugins/autoload_sample/main.hako
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using counter
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
// Minimal plugin usage example (CounterBox from nyash-counter-plugin)
|
||||||
|
local c = new CounterBox()
|
||||||
|
c.inc()
|
||||||
|
c.inc()
|
||||||
|
print("Counter value: " + c.get())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
9
examples/plugins/autoload_sample/nyash.toml
Normal file
9
examples/plugins/autoload_sample/nyash.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[using.counter]
|
||||||
|
kind = "dylib"
|
||||||
|
# Adjust the path if running from another directory
|
||||||
|
path = "../../plugins/nyash-counter-plugin/libnyash_counter_plugin.so"
|
||||||
|
bid = "CounterBox"
|
||||||
|
|
||||||
|
[using]
|
||||||
|
paths = ["lib"]
|
||||||
|
|
||||||
@ -7,6 +7,25 @@
|
|||||||
|
|
||||||
using selfhost.shared.mir.io as MirIoBox
|
using selfhost.shared.mir.io as MirIoBox
|
||||||
using selfhost.shared.common.string_helpers as StringHelpers
|
using selfhost.shared.common.string_helpers as StringHelpers
|
||||||
|
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||||
|
// Modular normalizers (opt-in, default OFF)
|
||||||
|
using selfhost.llvm.ir.normalize.print as NormalizePrintBox
|
||||||
|
using selfhost.llvm.ir.normalize.ref as NormalizeRefBox
|
||||||
|
using selfhost.llvm.ir.normalize.array_legacy as NormalizeArrayLegacyBox
|
||||||
|
// Externalized core passes (Stage-1: wrappers; impl move is incremental)
|
||||||
|
using selfhost.llvm.ir.aot_prep.passes.strlen as AotPrepStrlenBox
|
||||||
|
using selfhost.llvm.ir.aot_prep.passes.loop_hoist as AotPrepLoopHoistBox
|
||||||
|
using selfhost.llvm.ir.aot_prep.passes.const_dedup as AotPrepConstDedupBox
|
||||||
|
using selfhost.llvm.ir.aot_prep.passes.binop_cse as AotPrepBinopCSEBox
|
||||||
|
using selfhost.llvm.ir.aot_prep.passes.collections_hot as AotPrepCollectionsHotBox
|
||||||
|
using selfhost.llvm.ir.aot_prep.passes.fold_const_ret as AotPrepFoldConstRetBox
|
||||||
|
|
||||||
|
// Pass modules (logical分割; 将来はファイル分割へ移行)
|
||||||
|
static box AotPrepPass_Strlen { run(json) { return AotPrepStrlenBox.run(json) } }
|
||||||
|
static box AotPrepPass_LoopHoist { run(json) { return AotPrepLoopHoistBox.run(json) } }
|
||||||
|
static box AotPrepPass_ConstDedup { run(json) { return AotPrepConstDedupBox.run(json) } }
|
||||||
|
static box AotPrepPass_CollectionsHot { run(json) { return AotPrepCollectionsHotBox.run(json) } }
|
||||||
|
static box AotPrepPass_BinopCSE { run(json) { return AotPrepBinopCSEBox.run(json) } }
|
||||||
|
|
||||||
static box AotPrepBox {
|
static box AotPrepBox {
|
||||||
// AotPrepBox.prep
|
// AotPrepBox.prep
|
||||||
@ -30,6 +49,40 @@ static box AotPrepBox {
|
|||||||
local out = AotPrepBox._try_fold_const_binop_ret(canon)
|
local out = AotPrepBox._try_fold_const_binop_ret(canon)
|
||||||
if !out { out = canon }
|
if !out { out = canon }
|
||||||
|
|
||||||
|
// Phase‑3 (opt-in): 軽いホイスティング/ホットパス
|
||||||
|
// - ループ内の固定文字列 length/len の即値化(実質的な前計算)
|
||||||
|
// - Array/Map の get/set/push/len を AOT 向け externcall に張替え(最短経路)
|
||||||
|
// 既定はOFF。開発時のみ明示フラグで有効化する。
|
||||||
|
local do_hoist = (env.get("NYASH_MIR_LOOP_HOIST") == "1") || (env.get("NYASH_LLVM_FAST") == "1")
|
||||||
|
if do_hoist {
|
||||||
|
local h = AotPrepPass_Strlen.run(out)
|
||||||
|
if h != null && h != "" { out = h }
|
||||||
|
local l = AotPrepPass_LoopHoist.run(out)
|
||||||
|
if l != null && l != "" { out = l }
|
||||||
|
local d = AotPrepPass_ConstDedup.run(out)
|
||||||
|
if d != null && d != "" { out = d }
|
||||||
|
local c = AotPrepPass_BinopCSE.run(out)
|
||||||
|
if c != null && c != "" { out = c }
|
||||||
|
}
|
||||||
|
if env.get("NYASH_AOT_COLLECTIONS_HOT") == "1" {
|
||||||
|
local r = AotPrepPass_CollectionsHot.run(out)
|
||||||
|
if r != null && r != "" { out = r }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase‑3.5 (opt-in): 正規化(Rust normalize.rs からの移行先/既定OFF)
|
||||||
|
if env.get("HAKO_MIR_NORMALIZE_PRINT") == "1" {
|
||||||
|
local np = NormalizePrintBox.run(out)
|
||||||
|
if np != null && np != "" { out = np }
|
||||||
|
}
|
||||||
|
if env.get("HAKO_MIR_NORMALIZE_REF") == "1" {
|
||||||
|
local nr = NormalizeRefBox.run(out)
|
||||||
|
if nr != null && nr != "" { out = nr }
|
||||||
|
}
|
||||||
|
if env.get("HAKO_MIR_NORMALIZE_ARRAY") == "1" {
|
||||||
|
local na = NormalizeArrayLegacyBox.run(out)
|
||||||
|
if na != null && na != "" { out = na }
|
||||||
|
}
|
||||||
|
|
||||||
// Decide output path
|
// Decide output path
|
||||||
local out_path = json_in_path + ".prep.json"
|
local out_path = json_in_path + ".prep.json"
|
||||||
fb.open(out_path, "w")
|
fb.open(out_path, "w")
|
||||||
@ -38,6 +91,225 @@ static box AotPrepBox {
|
|||||||
return out_path
|
return out_path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_is_const_or_linear(json, vid) {
|
||||||
|
if vid == "" { return false }
|
||||||
|
return AotPrepBox._linear_expr(json, vid, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
_is_const_vid(json, vid) {
|
||||||
|
if vid == "" { return false }
|
||||||
|
local needle = "\"dst\":" + vid
|
||||||
|
local pos = JsonFragBox.index_of_from(json, needle, 0)
|
||||||
|
while pos >= 0 {
|
||||||
|
local start = json.lastIndexOf("{", pos)
|
||||||
|
if start < 0 { break }
|
||||||
|
local end = AotPrepBox._seek_object_end(json, start)
|
||||||
|
if end < 0 { break }
|
||||||
|
local inst = json.substring(start, end+1)
|
||||||
|
if inst.indexOf("\"op\":\"const\"") >= 0 { return true }
|
||||||
|
if inst.indexOf("\"op\":\"copy\"") >= 0 {
|
||||||
|
// copy 連鎖を辿って const を認定
|
||||||
|
local ksrc = inst.indexOf("\"src\":")
|
||||||
|
if ksrc >= 0 {
|
||||||
|
local svid = StringHelpers.read_digits(inst, ksrc + 6)
|
||||||
|
if svid != "" { return AotPrepBox._is_const_vid(json, svid) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos = JsonFragBox.index_of_from(json, needle, pos + 1)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
_linear_expr(json, vid, depth) {
|
||||||
|
if vid == "" { return false }
|
||||||
|
if depth > 10 { return false }
|
||||||
|
if AotPrepBox._is_const_vid(json, vid) { return true }
|
||||||
|
local needle = "\"dst\":" + vid
|
||||||
|
local pos = JsonFragBox.index_of_from(json, needle, 0)
|
||||||
|
while pos >= 0 {
|
||||||
|
local start = json.lastIndexOf("{", pos)
|
||||||
|
if start < 0 { break }
|
||||||
|
local end = AotPrepBox._seek_object_end(json, start)
|
||||||
|
if end < 0 { break }
|
||||||
|
local inst = json.substring(start, end+1)
|
||||||
|
// copy は線形性をそのまま伝播
|
||||||
|
if inst.indexOf("\"op\":\"copy\"") >= 0 {
|
||||||
|
local ksrc = inst.indexOf("\"src\":")
|
||||||
|
if ksrc >= 0 {
|
||||||
|
local svid = StringHelpers.read_digits(inst, ksrc + 6)
|
||||||
|
if svid != "" { return AotPrepBox._linear_expr(json, svid, depth + 1) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if inst.indexOf("\"op\":\"binop\"") >= 0 {
|
||||||
|
local lhs_pos = inst.indexOf("\"lhs\":")
|
||||||
|
local rhs_pos = inst.indexOf("\"rhs\":")
|
||||||
|
local op_key = inst.indexOf("\"operation\":\"")
|
||||||
|
if lhs_pos >= 0 && rhs_pos >= 0 && op_key >= 0 {
|
||||||
|
local lhs = StringHelpers.read_digits(inst, lhs_pos + 6)
|
||||||
|
local rhs = StringHelpers.read_digits(inst, rhs_pos + 6)
|
||||||
|
local op = JsonFragBox.read_string_after(inst, op_key + 13)
|
||||||
|
if lhs != "" && rhs != "" && (op == "+" || op == "-" || op == "*" || op == "add" || op == "sub" || op == "mul") {
|
||||||
|
if AotPrepBox._is_const_vid(json, lhs) && AotPrepBox._linear_expr(json, rhs, depth + 1) { return true }
|
||||||
|
if AotPrepBox._is_const_vid(json, rhs) && AotPrepBox._linear_expr(json, lhs, depth + 1) { return true }
|
||||||
|
}
|
||||||
|
// allow div/rem with const operand as linear for heuristics
|
||||||
|
if lhs != "" && rhs != "" && (op == "/" || op == "div" || op == "sdiv" || op == "%" || op == "rem" || op == "srem") {
|
||||||
|
// only accept when one side is a const and the other side is linear
|
||||||
|
if (op == "/" || op == "div" || op == "sdiv") {
|
||||||
|
if AotPrepBox._linear_expr(json, lhs, depth + 1) && AotPrepBox._is_const_vid(json, rhs) { return true }
|
||||||
|
if AotPrepBox._is_const_vid(json, lhs) && AotPrepBox._linear_expr(json, rhs, depth + 1) { return true }
|
||||||
|
} else {
|
||||||
|
// rem: treat similarly (mod by const)
|
||||||
|
if AotPrepBox._linear_expr(json, lhs, depth + 1) && AotPrepBox._is_const_vid(json, rhs) { return true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos = JsonFragBox.index_of_from(json, needle, pos + 1)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ループ内 mod/div/compare の定数をブロック先頭へ hoist(JSON文字列ベース、構造は変えない)
|
||||||
|
local read_field = fun(text, key) {
|
||||||
|
local needle = "\"" + key + "\":\""
|
||||||
|
local idx = text.indexOf(needle)
|
||||||
|
if idx < 0 { return "" }
|
||||||
|
return JsonFragBox.read_string_after(text, idx + needle.length())
|
||||||
|
}
|
||||||
|
local read_digits_field = fun(text, key) {
|
||||||
|
local needle = "\"" + key + "\":"
|
||||||
|
local idx = text.indexOf(needle)
|
||||||
|
if idx < 0 { return "" }
|
||||||
|
return StringHelpers.read_digits(text, idx + needle.length())
|
||||||
|
}
|
||||||
|
local read_const_value = fun(text) {
|
||||||
|
local needle = "\"value\":{\"type\":\"i64\",\"value\":"
|
||||||
|
local idx = text.indexOf(needle)
|
||||||
|
if idx < 0 { return "" }
|
||||||
|
return StringHelpers.read_digits(text, idx + needle.length())
|
||||||
|
}
|
||||||
|
local ref_fields = ["lhs", "rhs", "cond", "target"]
|
||||||
|
loop(true) {
|
||||||
|
local key = "\"instructions\":["
|
||||||
|
local kinst = out.indexOf(key, pos)
|
||||||
|
if kinst < 0 { break }
|
||||||
|
local lb = out.indexOf("[", kinst)
|
||||||
|
if lb < 0 { break }
|
||||||
|
local rb = JsonFragBox._seek_array_end(out, lb)
|
||||||
|
if rb < 0 { break }
|
||||||
|
local body = out.substring(lb+1, rb)
|
||||||
|
local insts = build_items(body)
|
||||||
|
local const_defs = {}
|
||||||
|
local const_vals = {}
|
||||||
|
for inst in insts {
|
||||||
|
local op = read_field(inst, "op")
|
||||||
|
if op == "const" {
|
||||||
|
local dst = read_digits_field(inst, "dst")
|
||||||
|
local val = read_const_value(inst)
|
||||||
|
if dst != "" && val != "" {
|
||||||
|
const_defs[dst] = inst
|
||||||
|
const_vals[dst] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local folded = true
|
||||||
|
while folded {
|
||||||
|
folded = false
|
||||||
|
for inst in insts {
|
||||||
|
local op = read_field(inst, "op")
|
||||||
|
if op != "binop" { continue }
|
||||||
|
local dst = read_digits_field(inst, "dst")
|
||||||
|
if dst == "" || const_vals.contains(dst) { continue }
|
||||||
|
local lhs = read_digits_field(inst, "lhs")
|
||||||
|
local rhs = read_digits_field(inst, "rhs")
|
||||||
|
local operation = read_field(inst, "operation")
|
||||||
|
if lhs == "" || rhs == "" || operation == "" { continue }
|
||||||
|
local lhs_val = const_vals.contains(lhs) ? const_vals[lhs] : ""
|
||||||
|
local rhs_val = const_vals.contains(rhs) ? const_vals[rhs] : ""
|
||||||
|
if lhs_val == "" || rhs_val == "" { continue }
|
||||||
|
local computed = AotPrepBox._evaluate_binop_constant(operation, lhs_val, rhs_val)
|
||||||
|
if computed == "" { continue }
|
||||||
|
const_defs[dst] = inst
|
||||||
|
const_vals[dst] = computed
|
||||||
|
folded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local needed = {}
|
||||||
|
for inst in insts {
|
||||||
|
local op = read_field(inst, "op")
|
||||||
|
if op == "const" { continue }
|
||||||
|
for field in ref_fields {
|
||||||
|
local ref = read_digits_field(inst, field)
|
||||||
|
if ref != "" && const_defs.contains(ref) {
|
||||||
|
needed[ref] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if needed.size() == 0 {
|
||||||
|
pos = rb + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
local hoist_items = []
|
||||||
|
local keep_items = []
|
||||||
|
for inst in insts {
|
||||||
|
local dst = read_digits_field(inst, "dst")
|
||||||
|
if dst != "" && needed.contains(dst) && const_defs.contains(dst) {
|
||||||
|
hoist_items.push(inst)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
keep_items.push(inst)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hoist_items.size() == 0 {
|
||||||
|
pos = rb + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
local merged = ""
|
||||||
|
local first = 1
|
||||||
|
local append_item = fun(item) {
|
||||||
|
if first == 0 { merged = merged + "," }
|
||||||
|
merged = merged + item
|
||||||
|
first = 0
|
||||||
|
}
|
||||||
|
for item in hoist_items { append_item(item) }
|
||||||
|
for item in keep_items { append_item(item) }
|
||||||
|
|
||||||
|
out = out.substring(0, lb+1) + merged + out.substring(rb, out.length())
|
||||||
|
pos = lb + merged.length() + 1
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
_evaluate_binop_constant(operation, lhs_val, rhs_val) {
|
||||||
|
if operation == "" { return "" }
|
||||||
|
local li = StringHelpers.to_i64(lhs_val)
|
||||||
|
local ri = StringHelpers.to_i64(rhs_val)
|
||||||
|
if li == null || ri == null { return "" }
|
||||||
|
local res = null
|
||||||
|
if operation == "add" || operation == "+" {
|
||||||
|
res = li + ri
|
||||||
|
} else if operation == "sub" || operation == "-" {
|
||||||
|
res = li - ri
|
||||||
|
} else if operation == "mul" || operation == "*" {
|
||||||
|
res = li * ri
|
||||||
|
} else if operation == "sdiv" || operation == "div" || operation == "/" {
|
||||||
|
if ri == 0 { return "" }
|
||||||
|
res = li / ri
|
||||||
|
} else if operation == "srem" || operation == "rem" || operation == "%" {
|
||||||
|
if ri == 0 { return "" }
|
||||||
|
res = li % ri
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return StringHelpers.int_to_str(res)
|
||||||
|
}
|
||||||
|
|
||||||
// 内部: 最小の安全畳み込み(JSON文字列ベース)
|
// 内部: 最小の安全畳み込み(JSON文字列ベース)
|
||||||
_try_fold_const_binop_ret(json) {
|
_try_fold_const_binop_ret(json) {
|
||||||
if !json { return null }
|
if !json { return null }
|
||||||
@ -159,4 +431,67 @@ static box AotPrepBox {
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 軽ホイスティング1: 固定文字列の length/len を即値に置換(JSON文字列書換、構造変更なし)
|
||||||
|
// Array/Map hot path: boxcall を externcall に張替え(AOT専用・診断OFFの最短経路)
|
||||||
|
// JSON 内のオブジェクト { ... } の終了位置を探索(入れ子/文字列・エスケープ対応)
|
||||||
|
_seek_object_end(s, start) {
|
||||||
|
if s == null { return -1 }
|
||||||
|
if start < 0 || start >= s.length() { return -1 }
|
||||||
|
if s.substring(start, start+1) != "{" { return -1 }
|
||||||
|
local i = start
|
||||||
|
local depth = 0
|
||||||
|
local in_str = 0
|
||||||
|
local esc = 0
|
||||||
|
loop (i < s.length()) {
|
||||||
|
local ch = s.substring(i, i+1)
|
||||||
|
if in_str == 1 {
|
||||||
|
if esc == 1 { esc = 0 }
|
||||||
|
else if ch == "\\" { esc = 1 }
|
||||||
|
else if ch == "\"" { in_str = 0 }
|
||||||
|
} else {
|
||||||
|
if ch == "\"" { in_str = 1 }
|
||||||
|
else if ch == "{" { depth = depth + 1 }
|
||||||
|
else if ch == "}" {
|
||||||
|
depth = depth - 1
|
||||||
|
if depth == 0 { return i }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// AotPrepBox.run_json — E2E (no FileBox). Input: JSON text; Output: JSON text after opt-in passes.
|
||||||
|
run_json(src_json) {
|
||||||
|
if src_json == null { return null }
|
||||||
|
// Phase‑1: 文字列正規化(安定化)
|
||||||
|
local out = MirIoBox.normalize("" + src_json)
|
||||||
|
|
||||||
|
// Phase‑2: 安全な単一ブロック const/binop(+,-,*)/ret の畳み込み(最小実装)
|
||||||
|
local tmp = AotPrepBox._try_fold_const_binop_ret(out)
|
||||||
|
if tmp != null && tmp != "" { out = tmp }
|
||||||
|
|
||||||
|
// Phase‑3: 軽いホイスティング/ホットパス(opt‑in)
|
||||||
|
local do_hoist = (env.get("NYASH_MIR_LOOP_HOIST") == "1") || (env.get("NYASH_LLVM_FAST") == "1")
|
||||||
|
if do_hoist {
|
||||||
|
local h = AotPrepPass_Strlen.run(out); if h != null && h != "" { out = h }
|
||||||
|
local l = AotPrepPass_LoopHoist.run(out); if l != null && l != "" { out = l }
|
||||||
|
local d = AotPrepPass_ConstDedup.run(out); if d != null && d != "" { out = d }
|
||||||
|
local c = AotPrepPass_BinopCSE.run(out); if c != null && c != "" { out = c }
|
||||||
|
}
|
||||||
|
if env.get("NYASH_AOT_COLLECTIONS_HOT") == "1" {
|
||||||
|
local r = AotPrepPass_CollectionsHot.run(out); if r != null && r != "" { out = r }
|
||||||
|
}
|
||||||
|
if env.get("HAKO_MIR_NORMALIZE_PRINT") == "1" {
|
||||||
|
local np = NormalizePrintBox.run(out); if np != null && np != "" { out = np }
|
||||||
|
}
|
||||||
|
if env.get("HAKO_MIR_NORMALIZE_REF") == "1" {
|
||||||
|
local nr = NormalizeRefBox.run(out); if nr != null && nr != "" { out = nr }
|
||||||
|
}
|
||||||
|
if env.get("HAKO_MIR_NORMALIZE_ARRAY") == "1" {
|
||||||
|
local na = NormalizeArrayLegacyBox.run(out); if na != null && na != "" { out = na }
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
lang/src/llvm_ir/boxes/aot_prep/README.md
Normal file
40
lang/src/llvm_ir/boxes/aot_prep/README.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# AotPrep Passes (Pre‑AOT Normalization)
|
||||||
|
|
||||||
|
目的: MIR(JSON v0) を安全な範囲で前処理し、LLVM/AOT に渡す前の負荷を軽減します。
|
||||||
|
|
||||||
|
方針
|
||||||
|
- 既定は挙動不変・最小。すべて opt‑in の ENV トグルで有効化します。
|
||||||
|
- バックエンド非依存の“構造正規化”のみを行い、意味論を変えません。
|
||||||
|
- 将来的にファイル分割(passes/*)へ移行できるよう、パスの役割を明確化します。
|
||||||
|
|
||||||
|
主なパス(現在)
|
||||||
|
- StrlenFold(`NYASH_LLVM_FAST=1` or `NYASH_MIR_LOOP_HOIST=1`)
|
||||||
|
- 固定文字列由来の `length/len` を i64 即値に置換
|
||||||
|
- LoopHoist(`NYASH_MIR_LOOP_HOIST=1`)
|
||||||
|
- mod/div/compare の右辺にぶら下がる定数 `const` をブロック先頭へ移動
|
||||||
|
- 同一ブロック i64 の `const` をデデュープ
|
||||||
|
- CollectionsHot(`NYASH_AOT_COLLECTIONS_HOT=1`)
|
||||||
|
- Array/Map の単純 `boxcall` を `externcall`(`nyash.array.*` / `nyash.map.*`)へ張替え
|
||||||
|
- Map キー戦略は `NYASH_AOT_MAP_KEY_MODE={h|i64|hh|auto}`(既定: `h`/`i64`)
|
||||||
|
- BinopCSE(`NYASH_MIR_LOOP_HOIST=1`)
|
||||||
|
- 同一 binop(加算/乗算を含む)を1回だけ発行し、`copy` で再利用することで線形インデックス(例: `i*n+k`)の再計算を抑制
|
||||||
|
- `LoopHoist` と組み合わせることで `const` + `binop` の共通部分を前出しする汎用CSEになっている
|
||||||
|
|
||||||
|
ENV トグル一覧
|
||||||
|
- `NYASH_MIR_LOOP_HOIST=1` … StrlenFold + LoopHoist + ConstDedup を有効化
|
||||||
|
- `NYASH_AOT_COLLECTIONS_HOT=1` … Array/Map hot-path 置換を有効化
|
||||||
|
- `NYASH_AOT_MAP_KEY_MODE` … `h`/`i64`(既定), `hh`, `auto`(将来拡張)
|
||||||
|
- `auto` モードでは `_is_const_or_linear` で args を走査し、単純な i64 定数/線形表現と判定できる場合に `nyash.map.*_h` を選ぶ(それ以外は `*_hh`)。
|
||||||
|
- `NYASH_VERIFY_RET_PURITY=1` … Return 直前の副作用を Fail-Fast で検出。ベンチはこのトグルをオンにした状態で回しています。
|
||||||
|
|
||||||
|
使い方(例)
|
||||||
|
```
|
||||||
|
export NYASH_SKIP_TOML_ENV=1 NYASH_DISABLE_PLUGINS=1 \
|
||||||
|
NYASH_LLVM_SKIP_BUILD=1 NYASH_LLVM_FAST=1 NYASH_LLVM_FAST_INT=1 \
|
||||||
|
NYASH_MIR_LOOP_HOIST=1 NYASH_AOT_COLLECTIONS_HOT=1
|
||||||
|
tools/perf/microbench.sh --case arraymap --exe --runs 3
|
||||||
|
```
|
||||||
|
|
||||||
|
注意
|
||||||
|
- すべて opt‑in。CI/既定ユーザ挙動は従来どおりです。
|
||||||
|
- Return 純化ガード(`NYASH_VERIFY_RET_PURITY=1`)と併用してください。
|
||||||
114
lang/src/llvm_ir/boxes/aot_prep/helpers/common.hako
Normal file
114
lang/src/llvm_ir/boxes/aot_prep/helpers/common.hako
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// AotPrepHelpers — shared helpers for AotPrep passes (const/linear checks)
|
||||||
|
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||||
|
using selfhost.shared.common.string_helpers as StringHelpers
|
||||||
|
|
||||||
|
static box AotPrepHelpers {
|
||||||
|
// Public: Determine if `vid` is a constant or linear i64 expression in JSON(MIR v0)
|
||||||
|
is_const_or_linear(json, vid) {
|
||||||
|
if vid == "" { return false }
|
||||||
|
return me._linear_expr(json, vid, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public: Is `vid` defined by a const (following copy chains)
|
||||||
|
is_const_vid(json, vid) {
|
||||||
|
if vid == "" { return false }
|
||||||
|
local needle = "\"dst\":" + vid
|
||||||
|
local pos = JsonFragBox.index_of_from(json, needle, 0)
|
||||||
|
while pos >= 0 {
|
||||||
|
local start = json.lastIndexOf("{", pos)
|
||||||
|
if start < 0 { break }
|
||||||
|
local end = me._seek_object_end(json, start)
|
||||||
|
if end < 0 { break }
|
||||||
|
local inst = json.substring(start, end+1)
|
||||||
|
if inst.indexOf("\"op\":\"const\"") >= 0 { return true }
|
||||||
|
if inst.indexOf("\"op\":\"copy\"") >= 0 {
|
||||||
|
// Follow copy chains to the source
|
||||||
|
local ksrc = inst.indexOf("\"src\":")
|
||||||
|
if ksrc >= 0 {
|
||||||
|
local svid = StringHelpers.read_digits(inst, ksrc + 6)
|
||||||
|
if svid != "" { return me.is_const_vid(json, svid) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos = JsonFragBox.index_of_from(json, needle, pos + 1)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal: linear expression checker with small recursion bound
|
||||||
|
_linear_expr(json, vid, depth) {
|
||||||
|
if vid == "" { return false }
|
||||||
|
if depth > 10 { return false }
|
||||||
|
if me.is_const_vid(json, vid) { return true }
|
||||||
|
local needle = "\"dst\":" + vid
|
||||||
|
local pos = JsonFragBox.index_of_from(json, needle, 0)
|
||||||
|
while pos >= 0 {
|
||||||
|
local start = json.lastIndexOf("{", pos)
|
||||||
|
if start < 0 { break }
|
||||||
|
local end = me._seek_object_end(json, start)
|
||||||
|
if end < 0 { break }
|
||||||
|
local inst = json.substring(start, end+1)
|
||||||
|
// copy: propagate linearity
|
||||||
|
if inst.indexOf("\"op\":\"copy\"") >= 0 {
|
||||||
|
local ksrc = inst.indexOf("\"src\":")
|
||||||
|
if ksrc >= 0 {
|
||||||
|
local svid = StringHelpers.read_digits(inst, ksrc + 6)
|
||||||
|
if svid != "" { return me._linear_expr(json, svid, depth + 1) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if inst.indexOf("\"op\":\"binop\"") >= 0 {
|
||||||
|
local lhs_pos = inst.indexOf("\"lhs\":")
|
||||||
|
local rhs_pos = inst.indexOf("\"rhs\":")
|
||||||
|
local op_key = inst.indexOf("\"operation\":\"")
|
||||||
|
if lhs_pos >= 0 && rhs_pos >= 0 && op_key >= 0 {
|
||||||
|
local lhs = StringHelpers.read_digits(inst, lhs_pos + 6)
|
||||||
|
local rhs = StringHelpers.read_digits(inst, rhs_pos + 6)
|
||||||
|
local op = JsonFragBox.read_string_after(inst, op_key + 13)
|
||||||
|
// Treat +,-,* with one const and one linear as linear
|
||||||
|
if lhs != "" && rhs != "" && (op == "+" || op == "-" || op == "*" || op == "add" || op == "sub" || op == "mul") {
|
||||||
|
if me.is_const_vid(json, lhs) && me._linear_expr(json, rhs, depth + 1) { return true }
|
||||||
|
if me.is_const_vid(json, rhs) && me._linear_expr(json, lhs, depth + 1) { return true }
|
||||||
|
}
|
||||||
|
// Heuristic: allow div/rem with a const side as linear
|
||||||
|
if lhs != "" && rhs != "" && (op == "/" || op == "div" || op == "sdiv" || op == "%" || op == "rem" || op == "srem") {
|
||||||
|
// div: either linear/const or const/linear
|
||||||
|
if (op == "/" || op == "div" || op == "sdiv") {
|
||||||
|
if me._linear_expr(json, lhs, depth + 1) && me.is_const_vid(json, rhs) { return true }
|
||||||
|
if me.is_const_vid(json, lhs) && me._linear_expr(json, rhs, depth + 1) { return true }
|
||||||
|
} else {
|
||||||
|
// rem: only accept linear % const (mod by const)
|
||||||
|
if me._linear_expr(json, lhs, depth + 1) && me.is_const_vid(json, rhs) { return true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos = JsonFragBox.index_of_from(json, needle, pos + 1)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minimal object end seeker (duplicated here to avoid box coupling)
|
||||||
|
_seek_object_end(s, start) {
|
||||||
|
if s == null { return -1 }
|
||||||
|
if start < 0 || start >= s.length() { return -1 }
|
||||||
|
if s.substring(start, start+1) != "{" { return -1 }
|
||||||
|
local i = start
|
||||||
|
local depth = 0
|
||||||
|
local in_str = 0
|
||||||
|
local esc = 0
|
||||||
|
loop (i < s.length()) {
|
||||||
|
local ch = s.substring(i, i+1)
|
||||||
|
if in_str == 1 {
|
||||||
|
if esc == 1 { esc = 0 }
|
||||||
|
else if ch == "\\" { esc = 1 }
|
||||||
|
else if ch == "\"" { in_str = 0 }
|
||||||
|
} else {
|
||||||
|
if ch == "\"" { in_str = 1 }
|
||||||
|
else if ch == "{" { depth = depth + 1 }
|
||||||
|
else if ch == "}" { depth = depth - 1 if depth == 0 { return i } }
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
141
lang/src/llvm_ir/boxes/aot_prep/passes/binop_cse.hako
Normal file
141
lang/src/llvm_ir/boxes/aot_prep/passes/binop_cse.hako
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
// AotPrepBinopCSEBox — common subexpression elimination for binops (text-level)
|
||||||
|
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||||
|
using selfhost.shared.common.string_helpers as StringHelpers
|
||||||
|
|
||||||
|
static box AotPrepBinopCSEBox {
|
||||||
|
run(json) {
|
||||||
|
if json == null { return null }
|
||||||
|
local pos = 0
|
||||||
|
local out = json
|
||||||
|
local read_field = fun(text, key) {
|
||||||
|
local needle = "\"" + key + "\":\""
|
||||||
|
local idx = text.indexOf(needle)
|
||||||
|
if idx < 0 { return "" }
|
||||||
|
return JsonFragBox.read_string_after(text, idx + needle.length())
|
||||||
|
}
|
||||||
|
local read_digits_field = fun(text, key) {
|
||||||
|
local needle = "\"" + key + "\":"
|
||||||
|
local idx = text.indexOf(needle)
|
||||||
|
if idx < 0 { return "" }
|
||||||
|
return StringHelpers.read_digits(text, idx + needle.length())
|
||||||
|
}
|
||||||
|
loop(true) {
|
||||||
|
local key = "\"instructions\":["
|
||||||
|
local kinst = out.indexOf(key, pos)
|
||||||
|
if kinst < 0 { break }
|
||||||
|
local lb = out.indexOf("[", kinst)
|
||||||
|
if lb < 0 { break }
|
||||||
|
local rb = JsonFragBox._seek_array_end(out, lb)
|
||||||
|
if rb < 0 { break }
|
||||||
|
local body = out.substring(lb+1, rb)
|
||||||
|
local insts = []
|
||||||
|
local i = 0
|
||||||
|
loop(true) {
|
||||||
|
local os = body.indexOf("{", i)
|
||||||
|
if os < 0 { break }
|
||||||
|
local oe = me._seek_object_end(body, os)
|
||||||
|
if oe < 0 { break }
|
||||||
|
insts.push(body.substring(os, oe+1))
|
||||||
|
i = oe + 1
|
||||||
|
}
|
||||||
|
local copy_src = {}
|
||||||
|
local new_body = ""
|
||||||
|
local first = 1
|
||||||
|
local append_item = fun(item) {
|
||||||
|
if first == 0 { new_body = new_body + "," }
|
||||||
|
new_body = new_body + item
|
||||||
|
first = 0
|
||||||
|
}
|
||||||
|
for inst in insts {
|
||||||
|
local op = read_field(inst, "op")
|
||||||
|
if op == "copy" {
|
||||||
|
local dst = read_digits_field(inst, "dst")
|
||||||
|
local src = read_digits_field(inst, "src")
|
||||||
|
if dst != "" && src != "" {
|
||||||
|
copy_src[dst] = src
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
local resolve_copy = fun(vid) {
|
||||||
|
local current = vid
|
||||||
|
local depth = 0
|
||||||
|
loop(true) {
|
||||||
|
if current == "" { break }
|
||||||
|
if !copy_src.contains(current) { break }
|
||||||
|
current = copy_src[current]
|
||||||
|
depth = depth + 1
|
||||||
|
if depth >= 12 { break }
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
local canon_binop = fun(op, lhs, rhs) {
|
||||||
|
if lhs == "" || rhs == "" { return "" }
|
||||||
|
local key_lhs = resolve_copy(lhs)
|
||||||
|
local key_rhs = resolve_copy(rhs)
|
||||||
|
if key_lhs == "" || key_rhs == "" { return "" }
|
||||||
|
if op == "+" || op == "add" || op == "*" || op == "mul" {
|
||||||
|
local li = StringHelpers.to_i64(key_lhs)
|
||||||
|
local ri = StringHelpers.to_i64(key_rhs)
|
||||||
|
if li != null && ri != null && ri < li {
|
||||||
|
local tmp = key_lhs
|
||||||
|
key_lhs = key_rhs
|
||||||
|
key_rhs = tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return op + ":" + key_lhs + ":" + key_rhs
|
||||||
|
}
|
||||||
|
for inst in insts {
|
||||||
|
local op = read_field(inst, "op")
|
||||||
|
if op == "binop" {
|
||||||
|
local operation = read_field(inst, "operation")
|
||||||
|
local lhs = read_digits_field(inst, "lhs")
|
||||||
|
local rhs = read_digits_field(inst, "rhs")
|
||||||
|
local key = canon_binop(operation, lhs, rhs)
|
||||||
|
if key != "" && seen.contains(key) {
|
||||||
|
local dst = read_digits_field(inst, "dst")
|
||||||
|
if dst != "" {
|
||||||
|
append_item("{\"op\":\"copy\",\"dst\":" + dst + ",\"src\":" + seen[key] + "}")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if key != "" {
|
||||||
|
local dst = read_digits_field(inst, "dst")
|
||||||
|
if dst != "" {
|
||||||
|
seen[key] = dst
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
append_item(inst)
|
||||||
|
}
|
||||||
|
out = out.substring(0, lb+1) + new_body + out.substring(rb, out.length())
|
||||||
|
pos = lb + new_body.length() + 1
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
_seek_object_end(s, start) {
|
||||||
|
if s == null { return -1 }
|
||||||
|
if start < 0 || start >= s.length() { return -1 }
|
||||||
|
if s.substring(start, start+1) != "{" { return -1 }
|
||||||
|
local i = start
|
||||||
|
local depth = 0
|
||||||
|
local in_str = 0
|
||||||
|
local esc = 0
|
||||||
|
loop (i < s.length()) {
|
||||||
|
local ch = s.substring(i, i+1)
|
||||||
|
if in_str == 1 {
|
||||||
|
if esc == 1 { esc = 0 }
|
||||||
|
else if ch == "\\" { esc = 1 }
|
||||||
|
else if ch == "\"" { in_str = 0 }
|
||||||
|
} else {
|
||||||
|
if ch == "\"" { in_str = 1 }
|
||||||
|
else if ch == "{" { depth = depth + 1 }
|
||||||
|
else if ch == "}" {
|
||||||
|
depth = depth - 1
|
||||||
|
if depth == 0 { return i }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
226
lang/src/llvm_ir/boxes/aot_prep/passes/collections_hot.hako
Normal file
226
lang/src/llvm_ir/boxes/aot_prep/passes/collections_hot.hako
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
// AotPrepCollectionsHotBox — rewrite Array/Map boxcall to externcall hot paths (AOT-only)
|
||||||
|
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||||
|
using selfhost.shared.common.string_helpers as StringHelpers
|
||||||
|
using selfhost.llvm.ir.aot_prep.helpers.common as AotPrepHelpers // for is_const_or_linear
|
||||||
|
|
||||||
|
static box AotPrepCollectionsHotBox {
|
||||||
|
run(json) {
|
||||||
|
if json == null { return null }
|
||||||
|
local arr_recv = {}
|
||||||
|
local map_recv = {}
|
||||||
|
local copy_src = {}
|
||||||
|
{
|
||||||
|
local pos = 0
|
||||||
|
loop(true){
|
||||||
|
local k = JsonFragBox.index_of_from(json, "\"op\":\"newbox\"", pos)
|
||||||
|
if k < 0 { break }
|
||||||
|
local kdst = JsonFragBox.index_of_from(json, "\"dst\":", k)
|
||||||
|
local dsts = StringHelpers.read_digits(json, kdst+6)
|
||||||
|
if dsts != "" {
|
||||||
|
if JsonFragBox.index_of_from(json, "\"type\":\"ArrayBox\"", k) >= 0 { arr_recv[dsts] = 1 }
|
||||||
|
if JsonFragBox.index_of_from(json, "\"type\":\"MapBox\"", k) >= 0 { map_recv[dsts] = 1 }
|
||||||
|
}
|
||||||
|
pos = k + 1
|
||||||
|
}
|
||||||
|
pos = 0
|
||||||
|
loop(true){
|
||||||
|
local k = JsonFragBox.index_of_from(json, "\"op\":\"copy\"", pos)
|
||||||
|
if k < 0 { break }
|
||||||
|
local kdst = JsonFragBox.index_of_from(json, "\"dst\":", k)
|
||||||
|
local ksrc = JsonFragBox.index_of_from(json, "\"src\":", k)
|
||||||
|
if kdst >= 0 && ksrc >= 0 {
|
||||||
|
local dsts = StringHelpers.read_digits(json, kdst+6)
|
||||||
|
local srcs = StringHelpers.read_digits(json, ksrc+6)
|
||||||
|
if dsts != "" && srcs != "" { copy_src[dsts] = srcs }
|
||||||
|
}
|
||||||
|
pos = k + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if arr_recv.size() == 0 && map_recv.size() == 0 { return json }
|
||||||
|
local out = json
|
||||||
|
local key_mode = env.get("NYASH_AOT_MAP_KEY_MODE")
|
||||||
|
local find_block_span = fun(text, p) {
|
||||||
|
local key = "\"instructions\":["
|
||||||
|
local start = text.lastIndexOf(key, p)
|
||||||
|
if start < 0 { return [-1, -1] }
|
||||||
|
local lb = text.indexOf("[", start)
|
||||||
|
if lb < 0 { return [-1, -1] }
|
||||||
|
local rb = JsonFragBox._seek_array_end(text, lb)
|
||||||
|
if rb < 0 { return [-1, -1] }
|
||||||
|
return [lb, rb]
|
||||||
|
}
|
||||||
|
local resolve_copy = fun(vid) {
|
||||||
|
local cur = vid
|
||||||
|
local depth = 0
|
||||||
|
loop(true){ if cur == "" { break } if !copy_src.contains(cur) { break } cur = copy_src[cur]; depth = depth + 1; if depth >= 12 { break } }
|
||||||
|
return cur
|
||||||
|
}
|
||||||
|
local expr_key_in_block = fun(text, block_lb, k, vid) {
|
||||||
|
if vid == "" { return "" }
|
||||||
|
local needle = "\"dst\":" + vid
|
||||||
|
local pos = JsonFragBox.index_of_from(text, needle, block_lb)
|
||||||
|
local last = -1
|
||||||
|
while pos >= 0 && pos < k { last = pos; pos = JsonFragBox.index_of_from(text, needle, pos + 1) }
|
||||||
|
if last < 0 { return "" }
|
||||||
|
local os = text.lastIndexOf("{", last)
|
||||||
|
if os < 0 { return "" }
|
||||||
|
local oe = me._seek_object_end(text, os)
|
||||||
|
if oe < 0 || oe >= k { return "" }
|
||||||
|
local inst = text.substring(os, oe+1)
|
||||||
|
if inst.indexOf("\"op\":\"const\"") >= 0 {
|
||||||
|
local kval = inst.indexOf("\"value\":{\"type\":\"i64\",\"value\":")
|
||||||
|
local val = (kval>=0 ? StringHelpers.read_digits(inst, kval+30) : "")
|
||||||
|
if val != "" { return "const:" + val }
|
||||||
|
}
|
||||||
|
if inst.indexOf("\"op\":\"copy\"") >= 0 {
|
||||||
|
local ksrc = inst.indexOf("\"src\":")
|
||||||
|
local svid = (ksrc>=0 ? StringHelpers.read_digits(inst, ksrc+6) : "")
|
||||||
|
if svid != "" { return "copy:" + resolve_copy(svid) }
|
||||||
|
}
|
||||||
|
if inst.indexOf("\"op\":\"binop\"") >= 0 {
|
||||||
|
local lhs_pos = inst.indexOf("\"lhs\":")
|
||||||
|
local rhs_pos = inst.indexOf("\"rhs\":")
|
||||||
|
local op_key = inst.indexOf("\"operation\":\"")
|
||||||
|
if lhs_pos >= 0 && rhs_pos >= 0 && op_key >= 0 {
|
||||||
|
local lhs = StringHelpers.read_digits(inst, lhs_pos + 6)
|
||||||
|
local rhs = StringHelpers.read_digits(inst, rhs_pos + 6)
|
||||||
|
local op = JsonFragBox.read_string_after(inst, op_key + 13)
|
||||||
|
lhs = resolve_copy(lhs); rhs = resolve_copy(rhs)
|
||||||
|
if op == "+" || op == "add" || op == "*" || op == "mul" {
|
||||||
|
local li = StringHelpers.to_i64(lhs)
|
||||||
|
local ri = StringHelpers.to_i64(rhs)
|
||||||
|
if li != null && ri != null && ri < li { local tmp = lhs; lhs = rhs; rhs = tmp }
|
||||||
|
}
|
||||||
|
return "binop:" + op + ":" + lhs + ":" + rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// helper: find last set(box,a0,*) for same receiver inside block
|
||||||
|
local find_last_set_index_in_block = fun(text, block_lb, k, recv_vid) {
|
||||||
|
if recv_vid == "" { return "" }
|
||||||
|
// naive scan: look back in block slice for boxcall set with same box
|
||||||
|
local slice = text.substring(block_lb, k)
|
||||||
|
local p = slice.lastIndexOf("\"op\":\"boxcall\"")
|
||||||
|
while p >= 0 {
|
||||||
|
local abs = block_lb + p
|
||||||
|
local os = text.lastIndexOf("{", abs)
|
||||||
|
if os < 0 { break }
|
||||||
|
local oe = me._seek_object_end(text, os)
|
||||||
|
if oe < 0 || oe >= k { break }
|
||||||
|
local inst = text.substring(os, oe+1)
|
||||||
|
if inst.indexOf("\"method\":\"set\"") >= 0 {
|
||||||
|
local kbox = inst.indexOf("\"box\":")
|
||||||
|
local bid = (kbox>=0 ? StringHelpers.read_digits(inst, kbox+6) : "")
|
||||||
|
if bid == recv_vid {
|
||||||
|
local kargs = inst.indexOf("\"args\":[")
|
||||||
|
if kargs >= 0 {
|
||||||
|
local idx = StringHelpers.read_digits(inst, kargs+8)
|
||||||
|
if idx != "" { return idx }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// search previous occurrence
|
||||||
|
p = slice.lastIndexOf("\"op\":\"boxcall\"", p-1)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
local pos2 = 0
|
||||||
|
local seen_key_vid = {}
|
||||||
|
loop(true){
|
||||||
|
local k = JsonFragBox.index_of_from(out, "\"op\":\"boxcall\"", pos2)
|
||||||
|
if k < 0 { break }
|
||||||
|
local kdst = JsonFragBox.index_of_from(out, "\"dst\":", k)
|
||||||
|
local dvid = (kdst>=0 ? StringHelpers.read_digits(out, kdst+6) : "")
|
||||||
|
local kbox = JsonFragBox.index_of_from(out, "\"box\":", k)
|
||||||
|
if kbox < 0 { pos2 = k + 1; continue }
|
||||||
|
local bvid = StringHelpers.read_digits(out, kbox+6)
|
||||||
|
if bvid != "" { bvid = resolve_copy(bvid) }
|
||||||
|
local mend = JsonFragBox.index_of_from(out, "\"method\":\"", k)
|
||||||
|
if mend < 0 { pos2 = k + 1; continue }
|
||||||
|
local mname = JsonFragBox.read_string_after(out, mend+10)
|
||||||
|
local is_arr = arr_recv.contains(bvid)
|
||||||
|
local is_map = map_recv.contains(bvid)
|
||||||
|
if !(is_arr || is_map) { pos2 = k + 1; continue }
|
||||||
|
local kargs = JsonFragBox.index_of_from(out, "\"args\":[", k)
|
||||||
|
local a0 = ""; local a1 = ""
|
||||||
|
if kargs >= 0 {
|
||||||
|
a0 = StringHelpers.read_digits(out, kargs+8)
|
||||||
|
local sep = out.indexOf(",", kargs)
|
||||||
|
if sep > 0 { a1 = StringHelpers.read_digits(out, sep+1) }
|
||||||
|
}
|
||||||
|
if a0 != "" { a0 = resolve_copy(a0) }
|
||||||
|
do {
|
||||||
|
local span = find_block_span(out, k)
|
||||||
|
local lb = span[0]; local rb = span[1]
|
||||||
|
if lb >= 0 && rb >= 0 {
|
||||||
|
local key = expr_key_in_block(out, lb, k, a0)
|
||||||
|
if key != "" {
|
||||||
|
local sc_key = (is_map ? "map:" : "arr:") + bvid + "|" + key
|
||||||
|
if seen_key_vid.contains(sc_key) { a0 = seen_key_vid[sc_key] } else { seen_key_vid[sc_key] = a0 }
|
||||||
|
} else if is_arr && mname == "get" {
|
||||||
|
// Safe set->get index reuse inside same block
|
||||||
|
local prev_idx = find_last_set_index_in_block(out, lb, k, bvid)
|
||||||
|
if prev_idx != "" { a0 = prev_idx }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while(false)
|
||||||
|
local func = ""; local args = ""
|
||||||
|
if is_arr {
|
||||||
|
if mname == "get" { func = "nyash.array.get_h"; args = bvid + "," + a0 }
|
||||||
|
else if mname == "set" { func = "nyash.array.set_h"; args = bvid + "," + a0 + "," + a1 }
|
||||||
|
else if mname == "push" { func = "nyash.array.push_h"; args = bvid + "," + a0 }
|
||||||
|
else if (mname == "len" || mname == "length" || mname == "size") { func = "nyash.array.len_h"; args = bvid }
|
||||||
|
} else if is_map {
|
||||||
|
local mode = key_mode
|
||||||
|
local use_hh = (mode == "hh")
|
||||||
|
if mode == "auto" { use_hh = !AotPrepHelpers.is_const_or_linear(out, a0) }
|
||||||
|
if use_hh {
|
||||||
|
if mname == "get" { func = "nyash.map.get_hh"; args = bvid + "," + a0 }
|
||||||
|
else if mname == "set" { func = "nyash.map.set_hh"; args = bvid + "," + a0 + "," + a1 }
|
||||||
|
else if mname == "has" { func = "nyash.map.has_hh"; args = bvid + "," + a0 }
|
||||||
|
else if (mname == "len" || mname == "length" || mname == "size") { func = "nyash.map.size_h"; args = bvid }
|
||||||
|
} else {
|
||||||
|
if mname == "get" { func = "nyash.map.get_h"; args = bvid + "," + a0 }
|
||||||
|
else if mname == "set" { func = "nyash.map.set_h"; args = bvid + "," + a0 + "," + a1 }
|
||||||
|
else if mname == "has" { func = "nyash.map.has_h"; args = bvid + "," + a0 }
|
||||||
|
else if (mname == "len" || mname == "length" || mname == "size") { func = "nyash.map.size_h"; args = bvid }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if func == "" { pos2 = k + 1; continue }
|
||||||
|
local obj_start = out.lastIndexOf("{", k)
|
||||||
|
if obj_start < 0 { pos2 = k + 1; continue }
|
||||||
|
local obj_end = me._seek_object_end(out, obj_start)
|
||||||
|
if obj_end < 0 { pos2 = k + 1; continue }
|
||||||
|
local dst_part = (dvid != "" ? ("\"dst\":" + dvid + ",") : "")
|
||||||
|
local repl = "{" + dst_part + "\"op\":\"externcall\",\"func\":\"" + func + "\",\"args\":[" + args + "]}"
|
||||||
|
out = out.substring(0, obj_start) + repl + out.substring(obj_end+1, out.length())
|
||||||
|
pos2 = obj_start + repl.length()
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
_seek_object_end(s, start) {
|
||||||
|
if s == null { return -1 }
|
||||||
|
if start < 0 || start >= s.length() { return -1 }
|
||||||
|
if s.substring(start, start+1) != "{" { return -1 }
|
||||||
|
local i = start
|
||||||
|
local depth = 0
|
||||||
|
local in_str = 0
|
||||||
|
local esc = 0
|
||||||
|
loop (i < s.length()) {
|
||||||
|
local ch = s.substring(i, i+1)
|
||||||
|
if in_str == 1 {
|
||||||
|
if esc == 1 { esc = 0 }
|
||||||
|
else if ch == "\\" { esc = 1 }
|
||||||
|
else if ch == "\"" { in_str = 0 }
|
||||||
|
} else {
|
||||||
|
if ch == "\"" { in_str = 1 }
|
||||||
|
else if ch == "{" { depth = depth + 1 }
|
||||||
|
else if ch == "}" { depth = depth - 1 if depth == 0 { return i } }
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
87
lang/src/llvm_ir/boxes/aot_prep/passes/const_dedup.hako
Normal file
87
lang/src/llvm_ir/boxes/aot_prep/passes/const_dedup.hako
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||||
|
using selfhost.shared.common.string_helpers as StringHelpers
|
||||||
|
|
||||||
|
static box AotPrepConstDedupBox {
|
||||||
|
run(json) {
|
||||||
|
if json == null { return null }
|
||||||
|
local pos = 0
|
||||||
|
local out = json
|
||||||
|
loop(true) {
|
||||||
|
local kinst = JsonFragBox.index_of_from(out, "\"instructions\"", pos)
|
||||||
|
if kinst < 0 { break }
|
||||||
|
local lb = out.indexOf("[", kinst)
|
||||||
|
if lb < 0 { pos = kinst + 1; continue }
|
||||||
|
local rb = JsonFragBox._seek_array_end(out, lb)
|
||||||
|
if rb < 0 { pos = kinst + 1; continue }
|
||||||
|
local body = out.substring(lb+1, rb)
|
||||||
|
local i = 0
|
||||||
|
local new_body = ""
|
||||||
|
local first_vid_by_value = {}
|
||||||
|
loop(i < body.length()) {
|
||||||
|
local os = body.indexOf("{", i)
|
||||||
|
if os < 0 {
|
||||||
|
new_body = new_body + body.substring(i, body.length())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
new_body = new_body + body.substring(i, os)
|
||||||
|
local oe = me._seek_object_end(body, os)
|
||||||
|
if oe < 0 {
|
||||||
|
new_body = new_body + body.substring(os, body.length())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
local obj = body.substring(os, oe+1)
|
||||||
|
if obj.indexOf("\"op\":\"const\"") >= 0 && obj.indexOf("\"type\":\"i64\"") >= 0 {
|
||||||
|
local kdst = obj.indexOf("\"dst\":")
|
||||||
|
local dsts = (kdst>=0 ? StringHelpers.read_digits(obj, kdst+6) : "")
|
||||||
|
local kval = obj.indexOf("\"value\":{\"type\":\"i64\",\"value\":")
|
||||||
|
local vals = (kval>=0 ? StringHelpers.read_digits(obj, kval+30) : "")
|
||||||
|
if dsts != "" && vals != "" {
|
||||||
|
if first_vid_by_value.contains(vals) {
|
||||||
|
local src = first_vid_by_value[vals]
|
||||||
|
local repl = "{\"op\":\"copy\",\"dst\":" + dsts + ",\"src\":" + src + "}"
|
||||||
|
new_body = new_body + repl
|
||||||
|
} else {
|
||||||
|
first_vid_by_value[vals] = dsts
|
||||||
|
new_body = new_body + obj
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
new_body = new_body + obj
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
new_body = new_body + obj
|
||||||
|
}
|
||||||
|
i = oe + 1
|
||||||
|
}
|
||||||
|
out = out.substring(0, lb+1) + new_body + out.substring(rb, out.length())
|
||||||
|
pos = rb + 1
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
_seek_object_end(s, start) {
|
||||||
|
if s == null { return -1 }
|
||||||
|
if start < 0 || start >= s.length() { return -1 }
|
||||||
|
if s.substring(start, start+1) != "{" { return -1 }
|
||||||
|
local i = start
|
||||||
|
local depth = 0
|
||||||
|
local in_str = 0
|
||||||
|
local esc = 0
|
||||||
|
loop (i < s.length()) {
|
||||||
|
local ch = s.substring(i, i+1)
|
||||||
|
if in_str == 1 {
|
||||||
|
if esc == 1 { esc = 0 }
|
||||||
|
else if ch == "\\" { esc = 1 }
|
||||||
|
else if ch == "\"" { in_str = 0 }
|
||||||
|
} else {
|
||||||
|
if ch == "\"" { in_str = 1 }
|
||||||
|
else if ch == "{" { depth = depth + 1 }
|
||||||
|
else if ch == "}" {
|
||||||
|
depth = depth - 1
|
||||||
|
if depth == 0 { return i }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
// AotPrepFoldConstRetBox — Fold simple const/binop/ret single-block cases
|
||||||
|
using selfhost.llvm.ir.aot_prep as AotPrepBox
|
||||||
|
static box AotPrepFoldConstRetBox { run(json) { return AotPrepBox._try_fold_const_binop_ret(json) } }
|
||||||
|
|
||||||
137
lang/src/llvm_ir/boxes/aot_prep/passes/loop_hoist.hako
Normal file
137
lang/src/llvm_ir/boxes/aot_prep/passes/loop_hoist.hako
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// AotPrepLoopHoistBox — Hoist loop-local consts (binop/div/rem/compare) to block head
|
||||||
|
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||||
|
using selfhost.shared.common.string_helpers as StringHelpers
|
||||||
|
using selfhost.llvm.ir.aot_prep as AotPrepBox // for _evaluate_binop_constant
|
||||||
|
|
||||||
|
static box AotPrepLoopHoistBox {
|
||||||
|
run(json) {
|
||||||
|
if json == null { return null }
|
||||||
|
local out = json
|
||||||
|
local pos = 0
|
||||||
|
local build_items = fun(body) {
|
||||||
|
local items = []
|
||||||
|
local i = 0
|
||||||
|
loop(true) {
|
||||||
|
local os = body.indexOf("{", i)
|
||||||
|
if os < 0 { break }
|
||||||
|
local oe = me._seek_object_end(body, os)
|
||||||
|
if oe < 0 { break }
|
||||||
|
items.push(body.substring(os, oe+1))
|
||||||
|
i = oe + 1
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
local read_field = fun(text, key) {
|
||||||
|
local needle = "\"" + key + "\":\""
|
||||||
|
local idx = text.indexOf(needle)
|
||||||
|
if idx < 0 { return "" }
|
||||||
|
return JsonFragBox.read_string_after(text, idx + needle.length())
|
||||||
|
}
|
||||||
|
local read_digits_field = fun(text, key) {
|
||||||
|
local needle = "\"" + key + "\":"
|
||||||
|
local idx = text.indexOf(needle)
|
||||||
|
if idx < 0 { return "" }
|
||||||
|
return StringHelpers.read_digits(text, idx + needle.length())
|
||||||
|
}
|
||||||
|
local read_const_value = fun(text) {
|
||||||
|
local needle = "\"value\":{\"type\":\"i64\",\"value\":"
|
||||||
|
local idx = text.indexOf(needle)
|
||||||
|
if idx < 0 { return "" }
|
||||||
|
return StringHelpers.read_digits(text, idx + needle.length())
|
||||||
|
}
|
||||||
|
local ref_fields = ["lhs", "rhs", "cond", "target"]
|
||||||
|
loop(true) {
|
||||||
|
local key = "\"instructions\":["
|
||||||
|
local kinst = out.indexOf(key, pos)
|
||||||
|
if kinst < 0 { break }
|
||||||
|
local lb = out.indexOf("[", kinst)
|
||||||
|
if lb < 0 { break }
|
||||||
|
local rb = JsonFragBox._seek_array_end(out, lb)
|
||||||
|
if rb < 0 { break }
|
||||||
|
local body = out.substring(lb+1, rb)
|
||||||
|
local insts = build_items(body)
|
||||||
|
local const_defs = {}
|
||||||
|
local const_vals = {}
|
||||||
|
for inst in insts {
|
||||||
|
local op = read_field(inst, "op")
|
||||||
|
if op == "const" {
|
||||||
|
local dst = read_digits_field(inst, "dst")
|
||||||
|
local val = read_const_value(inst)
|
||||||
|
if dst != "" && val != "" { const_defs[dst] = inst; const_vals[dst] = val }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
local folded = true
|
||||||
|
while folded {
|
||||||
|
folded = false
|
||||||
|
for inst in insts {
|
||||||
|
local op = read_field(inst, "op")
|
||||||
|
if op != "binop" { continue }
|
||||||
|
local dst = read_digits_field(inst, "dst")
|
||||||
|
if dst == "" || const_vals.contains(dst) { continue }
|
||||||
|
local lhs = read_digits_field(inst, "lhs")
|
||||||
|
local rhs = read_digits_field(inst, "rhs")
|
||||||
|
local operation = read_field(inst, "operation")
|
||||||
|
if lhs == "" || rhs == "" || operation == "" { continue }
|
||||||
|
local lhs_val = const_vals.contains(lhs) ? const_vals[lhs] : ""
|
||||||
|
local rhs_val = const_vals.contains(rhs) ? const_vals[rhs] : ""
|
||||||
|
if lhs_val == "" || rhs_val == "" { continue }
|
||||||
|
local computed = AotPrepBox._evaluate_binop_constant(operation, lhs_val, rhs_val)
|
||||||
|
if computed == "" { continue }
|
||||||
|
const_defs[dst] = inst
|
||||||
|
const_vals[dst] = computed
|
||||||
|
folded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
local needed = {}
|
||||||
|
for inst in insts {
|
||||||
|
local op = read_field(inst, "op")
|
||||||
|
if op == "const" { continue }
|
||||||
|
for field in ref_fields {
|
||||||
|
local ref = read_digits_field(inst, field)
|
||||||
|
if ref != "" && const_defs.contains(ref) { needed[ref] = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if needed.size() == 0 { pos = rb + 1; continue }
|
||||||
|
local hoist_items = []
|
||||||
|
local keep_items = []
|
||||||
|
for inst in insts {
|
||||||
|
local dst = read_digits_field(inst, "dst")
|
||||||
|
if dst != "" && needed.contains(dst) && const_defs.contains(dst) { hoist_items.push(inst); continue }
|
||||||
|
keep_items.push(inst)
|
||||||
|
}
|
||||||
|
if hoist_items.size() == 0 { pos = rb + 1; continue }
|
||||||
|
local merged = ""
|
||||||
|
local first = 1
|
||||||
|
local append_item = fun(item) { if first == 0 { merged = merged + "," } merged = merged + item; first = 0 }
|
||||||
|
for item in hoist_items { append_item(item) }
|
||||||
|
for item in keep_items { append_item(item) }
|
||||||
|
out = out.substring(0, lb+1) + merged + out.substring(rb, out.length())
|
||||||
|
pos = lb + merged.length() + 1
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
_seek_object_end(s, start) {
|
||||||
|
if s == null { return -1 }
|
||||||
|
if start < 0 || start >= s.length() { return -1 }
|
||||||
|
if s.substring(start, start+1) != "{" { return -1 }
|
||||||
|
local i = start
|
||||||
|
local depth = 0
|
||||||
|
local in_str = 0
|
||||||
|
local esc = 0
|
||||||
|
loop (i < s.length()) {
|
||||||
|
local ch = s.substring(i, i+1)
|
||||||
|
if in_str == 1 {
|
||||||
|
if esc == 1 { esc = 0 }
|
||||||
|
else if ch == "\\" { esc = 1 }
|
||||||
|
else if ch == "\"" { in_str = 0 }
|
||||||
|
} else {
|
||||||
|
if ch == "\"" { in_str = 1 }
|
||||||
|
else if ch == "{" { depth = depth + 1 }
|
||||||
|
else if ch == "}" { depth = depth - 1 if depth == 0 { return i } }
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
114
lang/src/llvm_ir/boxes/aot_prep/passes/strlen.hako
Normal file
114
lang/src/llvm_ir/boxes/aot_prep/passes/strlen.hako
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// AotPrepStrlenBox — fold length/len for known StringBox receivers (JSON text)
|
||||||
|
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||||
|
using selfhost.shared.common.string_helpers as StringHelpers
|
||||||
|
|
||||||
|
static box AotPrepStrlenBox {
|
||||||
|
run(json) {
|
||||||
|
if json == null { return null }
|
||||||
|
// 1) const string: vid -> byte length
|
||||||
|
local map_len = {}
|
||||||
|
{
|
||||||
|
local pos = 0
|
||||||
|
loop(true){
|
||||||
|
local k = JsonFragBox.index_of_from(json, "\"op\":\"const\"", pos)
|
||||||
|
if k < 0 { break }
|
||||||
|
local kdst = JsonFragBox.index_of_from(json, "\"dst\":", k)
|
||||||
|
local kid = StringHelpers.read_digits(json, kdst+6)
|
||||||
|
if kid != "" {
|
||||||
|
local ktp = JsonFragBox.index_of_from(json, "\"type\":", k)
|
||||||
|
if ktp >= 0 {
|
||||||
|
local is_str = (JsonFragBox.index_of_from(json, "\"type\":\"string\"", ktp) >= 0)
|
||||||
|
local is_h_str = (JsonFragBox.index_of_from(json, "\"box_type\":\"StringBox\"", ktp) >= 0)
|
||||||
|
if is_str || is_h_str {
|
||||||
|
local kv = JsonFragBox.index_of_from(json, "\"value\":\"", ktp)
|
||||||
|
if kv >= 0 {
|
||||||
|
local lit = JsonFragBox.read_string_after(json, kv+8)
|
||||||
|
if lit != null {
|
||||||
|
local blen = (""+lit).length()
|
||||||
|
map_len[StringHelpers.int_to_str(StringHelpers.to_i64(kid))] = blen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos = k + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if map_len.size() == 0 { return json }
|
||||||
|
// 2) newbox(StringBox, <const_str_vid>): dst_vid -> byte length
|
||||||
|
local recv_len = {}
|
||||||
|
{
|
||||||
|
local pos2 = 0
|
||||||
|
loop(true){
|
||||||
|
local k = JsonFragBox.index_of_from(json, "\"op\":\"newbox\"", pos2)
|
||||||
|
if k < 0 { break }
|
||||||
|
local kty = JsonFragBox.index_of_from(json, "\"type\":\"StringBox\"", k)
|
||||||
|
if kty >= 0 {
|
||||||
|
local kdst = JsonFragBox.index_of_from(json, "\"dst\":", k)
|
||||||
|
local dsts = StringHelpers.read_digits(json, kdst+6)
|
||||||
|
local kargs = JsonFragBox.index_of_from(json, "\"args\":[", k)
|
||||||
|
if dsts != "" && kargs >= 0 {
|
||||||
|
local avid = StringHelpers.read_digits(json, kargs+8)
|
||||||
|
if avid != "" && map_len.contains(avid) {
|
||||||
|
recv_len[dsts] = map_len[avid]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos2 = k + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if recv_len.size() == 0 { return json }
|
||||||
|
// 3) boxcall length/len → const i64
|
||||||
|
local out = json
|
||||||
|
local pos3 = 0
|
||||||
|
loop(true){
|
||||||
|
local k = JsonFragBox.index_of_from(out, "\"op\":\"boxcall\"", pos3)
|
||||||
|
if k < 0 { break }
|
||||||
|
local mend = JsonFragBox.index_of_from(out, "\"method\":\"", k)
|
||||||
|
if mend < 0 { pos3 = k + 1; continue }
|
||||||
|
local mname = JsonFragBox.read_string_after(out, mend+10)
|
||||||
|
if !(mname == "length" || mname == "len") { pos3 = k + 1; continue }
|
||||||
|
local kbox = JsonFragBox.index_of_from(out, "\"box\":", k)
|
||||||
|
if kbox < 0 { pos3 = k + 1; continue }
|
||||||
|
local bvid = StringHelpers.read_digits(out, kbox+6)
|
||||||
|
if bvid == "" || !recv_len.contains(bvid) { pos3 = k + 1; continue }
|
||||||
|
local kdst = JsonFragBox.index_of_from(out, "\"dst\":", k)
|
||||||
|
if kdst < 0 { pos3 = k + 1; continue }
|
||||||
|
local dvid = StringHelpers.read_digits(out, kdst+6)
|
||||||
|
if dvid == "" { pos3 = k + 1; continue }
|
||||||
|
local obj_start = out.lastIndexOf("{", k)
|
||||||
|
if obj_start < 0 { pos3 = k + 1; continue }
|
||||||
|
local obj_end = me._seek_object_end(out, obj_start)
|
||||||
|
if obj_end < 0 { pos3 = k + 1; continue }
|
||||||
|
local blen = recv_len[bvid]
|
||||||
|
local repl = "{\"op\":\"const\",\"dst\":" + dvid + ",\"value\":{\"type\":\"i64\",\"value\":" + StringHelpers.int_to_str(blen) + "}}"
|
||||||
|
out = out.substring(0, obj_start) + repl + out.substring(obj_end+1, out.length())
|
||||||
|
pos3 = obj_start + repl.length()
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
_seek_object_end(s, start) {
|
||||||
|
if s == null { return -1 }
|
||||||
|
if start < 0 || start >= s.length() { return -1 }
|
||||||
|
if s.substring(start, start+1) != "{" { return -1 }
|
||||||
|
local i = start
|
||||||
|
local depth = 0
|
||||||
|
local in_str = 0
|
||||||
|
local esc = 0
|
||||||
|
loop (i < s.length()) {
|
||||||
|
local ch = s.substring(i, i+1)
|
||||||
|
if in_str == 1 {
|
||||||
|
if esc == 1 { esc = 0 }
|
||||||
|
else if ch == "\\" { esc = 1 }
|
||||||
|
else if ch == "\"" { in_str = 0 }
|
||||||
|
} else {
|
||||||
|
if ch == "\"" { in_str = 1 }
|
||||||
|
else if ch == "{" { depth = depth + 1 }
|
||||||
|
else if ch == "}" { depth = depth - 1 if depth == 0 { return i } }
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
17
lang/src/llvm_ir/boxes/normalize/README.md
Normal file
17
lang/src/llvm_ir/boxes/normalize/README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
Normalize Passes (AOT Prep)
|
||||||
|
|
||||||
|
Purpose
|
||||||
|
- Provide modular, opt-in JSON v0 normalizers to keep Rust normalize.rs minimal and move pre-ny-llvmc shaping into .hako.
|
||||||
|
|
||||||
|
Passes (initial)
|
||||||
|
- NormalizePrintBox: rewrite `op: print` to `externcall env.console.log(value)`.
|
||||||
|
- NormalizeRefBox: rewrite `ref_get/ref_set` to `boxcall getField/setField` with a string field operand (best-effort; no CFG change).
|
||||||
|
|
||||||
|
Toggles (default OFF)
|
||||||
|
- HAKO_MIR_NORMALIZE_PRINT=1
|
||||||
|
- HAKO_MIR_NORMALIZE_REF=1
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- These operate on MIR(JSON v0) text. They are simple, conservative string transforms built on JsonFrag utilities.
|
||||||
|
- Keep effects and CFG unchanged. For ref_set, inserting a barrier is not enforced here; AOT will later select the shortest extern hot-path.
|
||||||
|
|
||||||
129
lang/src/llvm_ir/boxes/normalize/normalize_array_legacy.hako
Normal file
129
lang/src/llvm_ir/boxes/normalize/normalize_array_legacy.hako
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// NormalizeArrayLegacyBox — rewrite array_get/array_set → boxcall get/set
|
||||||
|
|
||||||
|
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||||
|
using selfhost.shared.common.string_helpers as StringHelpers
|
||||||
|
|
||||||
|
static box NormalizeArrayLegacyBox {
|
||||||
|
_seek_object_end(s, start) {
|
||||||
|
if s == null { return -1 }
|
||||||
|
if start < 0 || start >= s.length() { return -1 }
|
||||||
|
if s.substring(start, start+1) != "{" { return -1 }
|
||||||
|
local i = start
|
||||||
|
local depth = 0
|
||||||
|
local in_str = 0
|
||||||
|
local esc = 0
|
||||||
|
loop (i < s.length()) {
|
||||||
|
local ch = s.substring(i, i+1)
|
||||||
|
if in_str == 1 {
|
||||||
|
if esc == 1 { esc = 0 }
|
||||||
|
else if ch == "\\" { esc = 1 }
|
||||||
|
else if ch == "\"" { in_str = 0 }
|
||||||
|
} else {
|
||||||
|
if ch == "\"" { in_str = 1 }
|
||||||
|
else if ch == "{" { depth = depth + 1 }
|
||||||
|
else if ch == "}" { depth = depth - 1 if depth == 0 { return i } }
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
run(json) {
|
||||||
|
if json == null { return null }
|
||||||
|
local out = json
|
||||||
|
|
||||||
|
// array_get → boxcall(get)
|
||||||
|
local pos = 0
|
||||||
|
loop(true) {
|
||||||
|
local k = JsonFragBox.index_of_from(out, "\"op\":\"array_get\"", pos)
|
||||||
|
if k < 0 { break }
|
||||||
|
local obj_start = out.lastIndexOf("{", k)
|
||||||
|
if obj_start < 0 { pos = k + 1; continue }
|
||||||
|
local obj_end = me._seek_object_end(out, obj_start)
|
||||||
|
if obj_end < 0 { pos = k + 1; continue }
|
||||||
|
local kdst = JsonFragBox.index_of_from(out, "\"dst\":", obj_start)
|
||||||
|
local dsts = (kdst>=0 && kdst<obj_end ? StringHelpers.read_digits(out, kdst+6) : "")
|
||||||
|
local ka = JsonFragBox.index_of_from(out, "\"array\":", obj_start)
|
||||||
|
local arr = (ka>=0 && ka<obj_end ? StringHelpers.read_digits(out, ka+8) : "")
|
||||||
|
local ki = JsonFragBox.index_of_from(out, "\"index\":", obj_start)
|
||||||
|
local idx = (ki>=0 && ki<obj_end ? StringHelpers.read_digits(out, ki+8) : "")
|
||||||
|
if dsts != "" && arr != "" && idx != "" {
|
||||||
|
local dst_part = "\"dst\":" + dsts + ","
|
||||||
|
local repl = "{" + dst_part + "\"op\":\"boxcall\",\"box\":" + arr + ",\"method\":\"get\",\"args\":[" + idx + "]}"
|
||||||
|
out = out.substring(0, obj_start) + repl + out.substring(obj_end+1, out.length())
|
||||||
|
pos = obj_start + repl.length()
|
||||||
|
} else {
|
||||||
|
pos = k + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// array_set → boxcall(set)
|
||||||
|
pos = 0
|
||||||
|
loop(true) {
|
||||||
|
local k = JsonFragBox.index_of_from(out, "\"op\":\"array_set\"", pos)
|
||||||
|
if k < 0 { break }
|
||||||
|
local obj_start = out.lastIndexOf("{", k)
|
||||||
|
if obj_start < 0 { pos = k + 1; continue }
|
||||||
|
local obj_end = me._seek_object_end(out, obj_start)
|
||||||
|
if obj_end < 0 { pos = k + 1; continue }
|
||||||
|
local ka = JsonFragBox.index_of_from(out, "\"array\":", obj_start)
|
||||||
|
local arr = (ka>=0 && ka<obj_end ? StringHelpers.read_digits(out, ka+8) : "")
|
||||||
|
local ki = JsonFragBox.index_of_from(out, "\"index\":", obj_start)
|
||||||
|
local idx = (ki>=0 && ki<obj_end ? StringHelpers.read_digits(out, ki+8) : "")
|
||||||
|
local kv = JsonFragBox.index_of_from(out, "\"value\":", obj_start)
|
||||||
|
local val = (kv>=0 && kv<obj_end ? StringHelpers.read_digits(out, kv+8) : "")
|
||||||
|
if arr != "" && idx != "" && val != "" {
|
||||||
|
local repl = "{\"op\":\"boxcall\",\"box\":" + arr + ",\"method\":\"set\",\"args\":[" + idx + "," + val + "]}"
|
||||||
|
out = out.substring(0, obj_start) + repl + out.substring(obj_end+1, out.length())
|
||||||
|
pos = obj_start + repl.length()
|
||||||
|
} else {
|
||||||
|
pos = k + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: map_get/map_set → boxcall(get/set) if present in legacy JSON
|
||||||
|
pos = 0
|
||||||
|
loop(true) {
|
||||||
|
local k = JsonFragBox.index_of_from(out, "\"op\":\"map_get\"", pos)
|
||||||
|
if k < 0 { break }
|
||||||
|
local obj_start = out.lastIndexOf("{", k)
|
||||||
|
if obj_start < 0 { pos = k + 1; continue }
|
||||||
|
local obj_end = me._seek_object_end(out, obj_start)
|
||||||
|
if obj_end < 0 { pos = k + 1; continue }
|
||||||
|
local km = JsonFragBox.index_of_from(out, "\"map\":", obj_start)
|
||||||
|
local mv = (km>=0 && km<obj_end ? StringHelpers.read_digits(out, km+6) : "")
|
||||||
|
local kk = JsonFragBox.index_of_from(out, "\"key\":", obj_start)
|
||||||
|
local kv = (kk>=0 && kk<obj_end ? StringHelpers.read_digits(out, kk+6) : "")
|
||||||
|
local kdst = JsonFragBox.index_of_from(out, "\"dst\":", obj_start)
|
||||||
|
local dsts = (kdst>=0 && kdst<obj_end ? StringHelpers.read_digits(out, kdst+6) : "")
|
||||||
|
if mv != "" && kv != "" && dsts != "" {
|
||||||
|
local dst_part = "\"dst\":" + dsts + ","
|
||||||
|
local repl = "{" + dst_part + "\"op\":\"boxcall\",\"box\":" + mv + ",\"method\":\"get\",\"args\":[" + kv + "]}"
|
||||||
|
out = out.substring(0, obj_start) + repl + out.substring(obj_end+1, out.length())
|
||||||
|
pos = obj_start + repl.length()
|
||||||
|
} else { pos = k + 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = 0
|
||||||
|
loop(true) {
|
||||||
|
local k = JsonFragBox.index_of_from(out, "\"op\":\"map_set\"", pos)
|
||||||
|
if k < 0 { break }
|
||||||
|
local obj_start = out.lastIndexOf("{", k)
|
||||||
|
if obj_start < 0 { pos = k + 1; continue }
|
||||||
|
local obj_end = me._seek_object_end(out, obj_start)
|
||||||
|
if obj_end < 0 { pos = k + 1; continue }
|
||||||
|
local km = JsonFragBox.index_of_from(out, "\"map\":", obj_start)
|
||||||
|
local mv = (km>=0 && km<obj_end ? StringHelpers.read_digits(out, km+6) : "")
|
||||||
|
local kk = JsonFragBox.index_of_from(out, "\"key\":", obj_start)
|
||||||
|
local kv = (kk>=0 && kk<obj_end ? StringHelpers.read_digits(out, kk+6) : "")
|
||||||
|
local kval = JsonFragBox.index_of_from(out, "\"value\":", obj_start)
|
||||||
|
local vv = (kval>=0 && kval<obj_end ? StringHelpers.read_digits(out, kval+8) : "")
|
||||||
|
if mv != "" && kv != "" && vv != "" {
|
||||||
|
local repl = "{\"op\":\"boxcall\",\"box\":" + mv + ",\"method\":\"set\",\"args\":[" + kv + "," + vv + "]}"
|
||||||
|
out = out.substring(0, obj_start) + repl + out.substring(obj_end+1, out.length())
|
||||||
|
pos = obj_start + repl.length()
|
||||||
|
} else { pos = k + 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
55
lang/src/llvm_ir/boxes/normalize/normalize_print.hako
Normal file
55
lang/src/llvm_ir/boxes/normalize/normalize_print.hako
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// NormalizePrintBox — rewrite print → externcall(env.console.log)
|
||||||
|
|
||||||
|
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||||
|
using selfhost.shared.common.string_helpers as StringHelpers
|
||||||
|
|
||||||
|
static box NormalizePrintBox {
|
||||||
|
_seek_object_end(s, start) {
|
||||||
|
if s == null { return -1 }
|
||||||
|
if start < 0 || start >= s.length() { return -1 }
|
||||||
|
if s.substring(start, start+1) != "{" { return -1 }
|
||||||
|
local i = start
|
||||||
|
local depth = 0
|
||||||
|
local in_str = 0
|
||||||
|
local esc = 0
|
||||||
|
loop (i < s.length()) {
|
||||||
|
local ch = s.substring(i, i+1)
|
||||||
|
if in_str == 1 {
|
||||||
|
if esc == 1 { esc = 0 }
|
||||||
|
else if ch == "\\" { esc = 1 }
|
||||||
|
else if ch == "\"" { in_str = 0 }
|
||||||
|
} else {
|
||||||
|
if ch == "\"" { in_str = 1 }
|
||||||
|
else if ch == "{" { depth = depth + 1 }
|
||||||
|
else if ch == "}" { depth = depth - 1 if depth == 0 { return i } }
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
run(json) {
|
||||||
|
if json == null { return null }
|
||||||
|
local out = json
|
||||||
|
local pos = 0
|
||||||
|
loop(true) {
|
||||||
|
local k = JsonFragBox.index_of_from(out, "\"op\":\"print\"", pos)
|
||||||
|
if k < 0 { break }
|
||||||
|
// Find enclosing object span
|
||||||
|
local obj_start = out.lastIndexOf("{", k)
|
||||||
|
if obj_start < 0 { pos = k + 1; continue }
|
||||||
|
local obj_end = me._seek_object_end(out, obj_start)
|
||||||
|
if obj_end < 0 { pos = k + 1; continue }
|
||||||
|
// Extract value register
|
||||||
|
local kval = JsonFragBox.index_of_from(out, "\"value\":", obj_start)
|
||||||
|
local vreg = (kval>=0 && kval<obj_end ? StringHelpers.read_digits(out, kval+8) : "")
|
||||||
|
// Build externcall
|
||||||
|
local dst_part = "" // print has no dst
|
||||||
|
local args = (vreg != "" ? vreg : "")
|
||||||
|
local repl = "{" + dst_part + "\"op\":\"externcall\",\"func\":\"env.console.log\",\"args\":[" + args + "]}"
|
||||||
|
out = out.substring(0, obj_start) + repl + out.substring(obj_end+1, out.length())
|
||||||
|
pos = obj_start + repl.length()
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
92
lang/src/llvm_ir/boxes/normalize/normalize_ref.hako
Normal file
92
lang/src/llvm_ir/boxes/normalize/normalize_ref.hako
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// NormalizeRefBox — rewrite ref_get/ref_set → boxcall getField/setField
|
||||||
|
|
||||||
|
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||||
|
using selfhost.shared.common.string_helpers as StringHelpers
|
||||||
|
|
||||||
|
static box NormalizeRefBox {
|
||||||
|
_seek_object_end(s, start) {
|
||||||
|
if s == null { return -1 }
|
||||||
|
if start < 0 || start >= s.length() { return -1 }
|
||||||
|
if s.substring(start, start+1) != "{" { return -1 }
|
||||||
|
local i = start
|
||||||
|
local depth = 0
|
||||||
|
local in_str = 0
|
||||||
|
local esc = 0
|
||||||
|
loop (i < s.length()) {
|
||||||
|
local ch = s.substring(i, i+1)
|
||||||
|
if in_str == 1 {
|
||||||
|
if esc == 1 { esc = 0 }
|
||||||
|
else if ch == "\\" { esc = 1 }
|
||||||
|
else if ch == "\"" { in_str = 0 }
|
||||||
|
} else {
|
||||||
|
if ch == "\"" { in_str = 1 }
|
||||||
|
else if ch == "{" { depth = depth + 1 }
|
||||||
|
else if ch == "}" { depth = depth - 1 if depth == 0 { return i } }
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
run(json) {
|
||||||
|
if json == null { return null }
|
||||||
|
local out = json
|
||||||
|
local pos = 0
|
||||||
|
loop(true) {
|
||||||
|
// ref_get
|
||||||
|
local k = JsonFragBox.index_of_from(out, "\"op\":\"ref_get\"", pos)
|
||||||
|
if k < 0 { break }
|
||||||
|
local obj_start = out.lastIndexOf("{", k)
|
||||||
|
if obj_start < 0 { pos = k + 1; continue }
|
||||||
|
local obj_end = me._seek_object_end(out, obj_start)
|
||||||
|
if obj_end < 0 { pos = k + 1; continue }
|
||||||
|
local kdst = JsonFragBox.index_of_from(out, "\"dst\":", obj_start)
|
||||||
|
local dsts = (kdst>=0 && kdst<obj_end ? StringHelpers.read_digits(out, kdst+6) : "")
|
||||||
|
local kref = JsonFragBox.index_of_from(out, "\"reference\":", obj_start)
|
||||||
|
local refv = (kref>=0 && kref<obj_end ? StringHelpers.read_digits(out, kref+12) : "")
|
||||||
|
// field is JSON string; read raw token between quotes after "field":"
|
||||||
|
local kf = JsonFragBox.index_of_from(out, "\"field\":\"", obj_start)
|
||||||
|
local field = (kf>=0 && kf<obj_end ? JsonFragBox.read_string_after(out, kf+9) : "")
|
||||||
|
if dsts != "" && refv != "" && field != "" {
|
||||||
|
// emit: const field + boxcall getField(field)
|
||||||
|
local c = "{\"op\":\"const\",\"dst\":__FID__,\"value\":{\"type\":\"string\",\"value\":\"__FLD__\"}}"
|
||||||
|
// allocate a temporary dst id for field: reuse dst+1000000 to avoid collision (text-level best effort)
|
||||||
|
local fid = dsts + "000000"
|
||||||
|
c = StringHelpers.replace_all(c, "__FID__", fid)
|
||||||
|
c = StringHelpers.replace_all(c, "__FLD__", field)
|
||||||
|
local repl = c + ",{" + (dsts!=""?"\"dst\":"+dsts+",":"") + "\"op\":\"boxcall\",\"box\":" + refv + ",\"method\":\"getField\",\"args\":[" + fid + "]}"
|
||||||
|
out = out.substring(0, obj_start) + repl + out.substring(obj_end+1, out.length())
|
||||||
|
pos = obj_start + repl.length()
|
||||||
|
} else {
|
||||||
|
pos = k + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ref_set
|
||||||
|
pos = 0
|
||||||
|
loop(true) {
|
||||||
|
local k = JsonFragBox.index_of_from(out, "\"op\":\"ref_set\"", pos)
|
||||||
|
if k < 0 { break }
|
||||||
|
local obj_start = out.lastIndexOf("{", k)
|
||||||
|
if obj_start < 0 { pos = k + 1; continue }
|
||||||
|
local obj_end = me._seek_object_end(out, obj_start)
|
||||||
|
if obj_end < 0 { pos = k + 1; continue }
|
||||||
|
local kref = JsonFragBox.index_of_from(out, "\"reference\":", obj_start)
|
||||||
|
local refv = (kref>=0 && kref<obj_end ? StringHelpers.read_digits(out, kref+12) : "")
|
||||||
|
local kval = JsonFragBox.index_of_from(out, "\"value\":", obj_start)
|
||||||
|
local valv = (kval>=0 && kval<obj_end ? StringHelpers.read_digits(out, kval+8) : "")
|
||||||
|
local kf = JsonFragBox.index_of_from(out, "\"field\":\"", obj_start)
|
||||||
|
local field = (kf>=0 && kf<obj_end ? JsonFragBox.read_string_after(out, kf+9) : "")
|
||||||
|
if refv != "" && valv != "" && field != "" {
|
||||||
|
local c = "{\"op\":\"const\",\"dst\":__FID__,\"value\":{\"type\":\"string\",\"value\":\"__FLD__\"}}"
|
||||||
|
local fid = refv + "000000"
|
||||||
|
c = StringHelpers.replace_all(c, "__FID__", fid)
|
||||||
|
c = StringHelpers.replace_all(c, "__FLD__", field)
|
||||||
|
local repl = c + ",{" + "\"op\":\"boxcall\",\"box\":" + refv + ",\"method\":\"setField\",\"args\":[" + fid + "," + valv + "]}"
|
||||||
|
out = out.substring(0, obj_start) + repl + out.substring(obj_end+1, out.length())
|
||||||
|
pos = obj_start + repl.length()
|
||||||
|
} else {
|
||||||
|
pos = k + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,3 +12,14 @@ boxes = [
|
|||||||
"LLVMV0BuilderBox",
|
"LLVMV0BuilderBox",
|
||||||
"LLVMAotFacadeBox",
|
"LLVMAotFacadeBox",
|
||||||
]
|
]
|
||||||
|
normalize.print = "boxes/normalize/normalize_print.hako"
|
||||||
|
normalize.ref = "boxes/normalize/normalize_ref.hako"
|
||||||
|
normalize.array_legacy = "boxes/normalize/normalize_array_legacy.hako"
|
||||||
|
aot_prep = "boxes/aot_prep.hako"
|
||||||
|
aot_prep.passes.strlen = "boxes/aot_prep/passes/strlen.hako"
|
||||||
|
aot_prep.passes.loop_hoist = "boxes/aot_prep/passes/loop_hoist.hako"
|
||||||
|
aot_prep.passes.const_dedup = "boxes/aot_prep/passes/const_dedup.hako"
|
||||||
|
aot_prep.passes.binop_cse = "boxes/aot_prep/passes/binop_cse.hako"
|
||||||
|
aot_prep.passes.collections_hot = "boxes/aot_prep/passes/collections_hot.hako"
|
||||||
|
aot_prep.passes.fold_const_ret = "boxes/aot_prep/passes/fold_const_ret.hako"
|
||||||
|
aot_prep.helpers.common = "boxes/aot_prep/helpers/common.hako"
|
||||||
|
|||||||
@ -64,6 +64,11 @@ static box MirBuilderBox {
|
|||||||
if m == null { return null }
|
if m == null { return null }
|
||||||
// Inject function definitions if available
|
// Inject function definitions if available
|
||||||
local result = FuncLoweringBox.inject_funcs(m, func_defs_mir)
|
local result = FuncLoweringBox.inject_funcs(m, func_defs_mir)
|
||||||
|
// Optional methodize (dev): rewrite legacy Call -> unified mir_call(Method)
|
||||||
|
if env.get("HAKO_MIR_BUILDER_METHODIZE") == "1" {
|
||||||
|
result = FuncLoweringBox.methodize_calls_in_mir(result)
|
||||||
|
}
|
||||||
|
// Optional normalize (dev)
|
||||||
local nv = env.get("HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE")
|
local nv = env.get("HAKO_MIR_BUILDER_JSONFRAG_NORMALIZE")
|
||||||
if nv != null && ("" + nv) == "1" { return NormBox.normalize_all(result) }
|
if nv != null && ("" + nv) == "1" { return NormBox.normalize_all(result) }
|
||||||
return result
|
return result
|
||||||
@ -413,8 +418,15 @@ static box MirBuilderBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Unsupported internal shape → Fail‑Fast and return null
|
// Unsupported internal shape → try defs fallback (dev: HAKO_MIR_BUILDER_FUNCS=1)
|
||||||
print("[mirbuilder/internal/unsupported] only Return(Int|Binary(Int,Int)) supported in internal mode")
|
print("[mirbuilder/internal/unsupported] only Return(Int|Binary(Int,Int)) supported in internal mode")
|
||||||
|
if func_defs_mir != null && func_defs_mir != "" {
|
||||||
|
// Assemble a minimal module from lowered defs
|
||||||
|
local defs = func_defs_mir
|
||||||
|
if defs.substring(0,1) == "," { defs = defs.substring(1, defs.length()) }
|
||||||
|
local mod = "{\\\"functions\\\":[" + defs + "]}"
|
||||||
|
return norm_if(mod)
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -220,6 +220,89 @@ static box FuncLoweringBox {
|
|||||||
return func_defs_mir
|
return func_defs_mir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dev-only: rewrite legacy Call into unified mir_call(Method) when possible
|
||||||
|
// Heuristic: find call {func:K,args:[..],dst:R} and match preceding const {dst:K,value:"Box.method/N"}
|
||||||
|
// Receiverは省略(VM 側で静的シングルトンを補完)。LLVM 経路はPoCの範囲外。
|
||||||
|
method methodize_calls_in_mir(mir_json) {
|
||||||
|
if env.get("HAKO_MIR_BUILDER_METHODIZE") != "1" { return mir_json }
|
||||||
|
if mir_json == null { return mir_json }
|
||||||
|
local s = "" + mir_json
|
||||||
|
local out = ""
|
||||||
|
local pos = 0
|
||||||
|
loop(pos < s.length()) {
|
||||||
|
local k_call = JsonFragBox.index_of_from(s, "\"op\":\"call\"", pos)
|
||||||
|
if k_call < 0 {
|
||||||
|
out.append(s.substring(pos, s.length()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// copy up to call
|
||||||
|
out = out + s.substring(pos, k_call)
|
||||||
|
// parse func register
|
||||||
|
local k_func = JsonFragBox.index_of_from(s, "\"func\":", k_call)
|
||||||
|
local k_dst_call = JsonFragBox.index_of_from(s, "\"dst\":", k_call)
|
||||||
|
if k_func < 0 || k_dst_call < 0 { out = out + "\"op\":\"call\"" pos = k_call + 14 continue }
|
||||||
|
local func_reg = JsonFragBox.read_int_after(s, k_func + 7)
|
||||||
|
local call_dst = JsonFragBox.read_int_after(s, k_dst_call + 6)
|
||||||
|
if func_reg == null || call_dst == null { out = out + "\"op\":\"call\"" pos = k_call + 14 continue }
|
||||||
|
// find args list for splice
|
||||||
|
local k_args = JsonFragBox.index_of_from(s, "\"args\":[", k_call)
|
||||||
|
if k_args < 0 { out = out + "\"op\":\"call\"" pos = k_call + 14 continue }
|
||||||
|
local args_start = k_args + 7
|
||||||
|
// find closing ] for args
|
||||||
|
local args_end = -1 { local j = args_start local depth = 0 loop(j < s.length()) { local ch = s.substring(j,j+1) if ch == "[" { depth = depth + 1 } if ch == "]" { if depth == 0 { args_end = j break } else { depth = depth - 1 } } j = j + 1 } }
|
||||||
|
if args_end < 0 { out = out + "\"op\":\"call\"" pos = k_call + 14 continue }
|
||||||
|
local args_text = s.substring(args_start + 1, args_end)
|
||||||
|
// scan from start to k_call for const dst==func_reg and a string value
|
||||||
|
local scan = 0
|
||||||
|
local last_val = null
|
||||||
|
loop(scan < k_call) {
|
||||||
|
local k_dst = JsonFragBox.index_of_from(s, "\"dst\":", scan)
|
||||||
|
if k_dst < 0 || k_dst >= k_call { break }
|
||||||
|
local dst_id = JsonFragBox.read_int_after(s, k_dst + 6)
|
||||||
|
if dst_id != null && JsonFragBox._str_to_int("" + dst_id) == JsonFragBox._str_to_int("" + func_reg) {
|
||||||
|
// try to read value string after this position — prefer '"value":"' literal (skip nested type.value)
|
||||||
|
local k_val = JsonFragBox.index_of_from(s, "\"value\\\":\\\"", k_dst)
|
||||||
|
if k_val > 0 && k_val < k_call {
|
||||||
|
// Read string directly after '"value":"'
|
||||||
|
local name = JsonFragBox.read_string_after(s, k_val + 9)
|
||||||
|
if name != null {
|
||||||
|
if name != null && name.contains(".") {
|
||||||
|
last_val = name
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// fallback: try generic '"value":' then read next quoted string
|
||||||
|
local k_val2 = JsonFragBox.index_of_from(s, "\"value\":", k_dst)
|
||||||
|
if k_val2 > 0 && k_val2 < k_call {
|
||||||
|
local k_q2 = JsonFragBox.index_of_from(s, "\"", k_val2 + 7)
|
||||||
|
if k_q2 > 0 && k_q2 < k_call {
|
||||||
|
local name2 = JsonFragBox.read_string_after(s, k_q2)
|
||||||
|
if name2 != null && name2.contains(".") { last_val = name2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scan = k_dst + 6
|
||||||
|
}
|
||||||
|
if last_val == null {
|
||||||
|
// no match; keep original token and advance
|
||||||
|
out = out + "\"op\":\"call\""
|
||||||
|
pos = k_call + 14
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// split Box.method[/N]
|
||||||
|
local dot = JsonFragBox.index_of_from(last_val, ".", 0)
|
||||||
|
local slash = JsonFragBox.index_of_from(last_val, "/", 0)
|
||||||
|
local bname = (dot >= 0 ? last_val.substring(0, dot) : last_val)
|
||||||
|
local mname = (dot >= 0 ? last_val.substring(dot + 1, (slash >= 0 ? slash : last_val.length())) : last_val)
|
||||||
|
// emit methodized mir_call head
|
||||||
|
out = out + ("\"op\":\"mir_call\",\"dst\":" + call_dst + ",\"mir_call\":{\"callee\":{\"type\":\"Method\",\"box_name\":\"" + bname + "\",\"method\":\"" + mname + "\"},\"args\":[" + args_text + "],\"effects\":[]}")
|
||||||
|
// advance pos to after this call instruction object key we handled (skip minimal)
|
||||||
|
pos = args_end
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// Lower function body to MIR (minimal support)
|
// Lower function body to MIR (minimal support)
|
||||||
// Supports: Return(Int), Return(Binary(+|-|*|/, Int|Var, Int|Var)), Return(Call)
|
// Supports: Return(Int), Return(Binary(+|-|*|/, Int|Var, Int|Var)), Return(Call)
|
||||||
method _lower_func_body(func_name, box_name, params_arr, body_json, func_map) {
|
method _lower_func_body(func_name, box_name, params_arr, body_json, func_map) {
|
||||||
@ -252,7 +335,8 @@ static box FuncLoweringBox {
|
|||||||
if val != null {
|
if val != null {
|
||||||
// Build params JSON array
|
// Build params JSON array
|
||||||
local params_json = me._build_params_json(params_arr)
|
local params_json = me._build_params_json(params_arr)
|
||||||
local mir = "{\\\"name\\\":\\\"" + box_name + "." + func_name + "\\\",\\\"params\\\":" + params_json + ",\\\"locals\\\":[],\\\"blocks\\\":[{\\\"id\\\":0,\\\"instructions\\\":[{\\\"op\\\":\\\"const\\\",\\\"dst\\\":1,\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":" + val + "}},{\\\"op\\\":\\\"ret\\\",\\\"value\\\":1}]}]}"
|
local arity = JsonFragBox._str_to_int("" + params_arr.length())
|
||||||
|
local mir = "{\\\"name\\\":\\\"" + box_name + "." + func_name + "/" + arity + "\\\",\\\"params\\\":" + params_json + ",\\\"locals\\\":[],\\\"blocks\\\":[{\\\"id\\\":0,\\\"instructions\\\":[{\\\"op\\\":\\\"const\\\",\\\"dst\\\":1,\\\"value\\\":{\\\"type\\\":\\\"i64\\\",\\\"value\\\":" + val + "}},{\\\"op\\\":\\\"ret\\\",\\\"value\\\":1}]}]}"
|
||||||
return mir
|
return mir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -361,7 +445,8 @@ static box FuncLoweringBox {
|
|||||||
|
|
||||||
// Build params JSON array
|
// Build params JSON array
|
||||||
local params_json = me._build_params_json(params_arr)
|
local params_json = me._build_params_json(params_arr)
|
||||||
local mir = "{\\\"name\\\":\\\"" + box_name + "." + func_name + "\\\",\\\"params\\\":" + params_json + ",\\\"locals\\\":[],\\\"blocks\\\":[{\\\"id\\\":0,\\\"instructions\\\":[" + insts + "]}]}"
|
local arity = JsonFragBox._str_to_int("" + params_arr.length())
|
||||||
|
local mir = "{\\\"name\\\":\\\"" + box_name + "." + func_name + "/" + arity + "\\\",\\\"params\\\":" + params_json + ",\\\"locals\\\":[],\\\"blocks\\\":[{\\\"id\\\":0,\\\"instructions\\\":[" + insts + "]}]}"
|
||||||
return mir
|
return mir
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,12 +516,7 @@ static box FuncLoweringBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load const for function name
|
// Build args (materialize immediates/params into registers)
|
||||||
insts = "{\\\"op\\\":\\\"const\\\",\\\"dst\\\":" + next_reg + ",\\\"value\\\":{\\\"type\\\":\\\"string\\\",\\\"value\\\":\\\"" + resolved_name + "\\\"}}"
|
|
||||||
local func_reg = next_reg
|
|
||||||
next_reg = next_reg + 1
|
|
||||||
|
|
||||||
// Load args
|
|
||||||
local arg_regs = new ArrayBox()
|
local arg_regs = new ArrayBox()
|
||||||
{
|
{
|
||||||
local ai = 0
|
local ai = 0
|
||||||
@ -472,17 +552,52 @@ static box FuncLoweringBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// call instruction (using func_reg for function name)
|
// Decide emission form: Global call (legacy) or unified Method (dev: methodize)
|
||||||
insts = insts + ",{\\\"op\\\":\\\"call\\\",\\\"func\\\":" + func_reg + ",\\\"args\\\":[" + args_list + "],\\\"dst\\\":" + next_reg + "}"
|
|
||||||
local result_reg = next_reg
|
local result_reg = next_reg
|
||||||
next_reg = next_reg + 1
|
{
|
||||||
|
local dm = env.get("HAKO_MIR_BUILDER_METHODIZE")
|
||||||
|
local do_methodize = 0
|
||||||
|
if dm != null && ("" + dm) == "1" { do_methodize = 1 }
|
||||||
|
if do_methodize == 1 && resolved_name.contains(".") {
|
||||||
|
// Methodize PoC: rewrite Global("Box.method") -> mir_call Method(box_name, method, receiver: omitted)
|
||||||
|
// Note: receiver is omitted here; VM will materialize a static singleton when callee.type==Method
|
||||||
|
// LLVM path may treat args[0] as receiver; PoC canary targets VM backend.
|
||||||
|
local dot_pos = JsonFragBox.index_of_from(resolved_name, ".", 0)
|
||||||
|
local slash_pos = JsonFragBox.index_of_from(resolved_name, "/", 0)
|
||||||
|
if dot_pos >= 0 {
|
||||||
|
local box_part = resolved_name.substring(0, dot_pos)
|
||||||
|
local meth_part = resolved_name.substring(dot_pos + 1, (slash_pos >= 0 ? slash_pos : resolved_name.length()))
|
||||||
|
insts = insts + "," + (
|
||||||
|
"{\\\"op\\\":\\\"mir_call\\\",\\\"dst\\\":" + result_reg + ",\\\"mir_call\\\":{" +
|
||||||
|
"\\\"callee\\\":{\\\"type\\\":\\\"Method\\\",\\\"box_name\\\":\\\"" + box_part + "\\\",\\\"method\\\":\\\"" + meth_part + "\\\"}," +
|
||||||
|
"\\\"args\\\":[" + args_list + "],\\\"effects\\\":[]}}"
|
||||||
|
)
|
||||||
|
next_reg = next_reg + 1
|
||||||
|
} else {
|
||||||
|
// Fallback to legacy call if split failed
|
||||||
|
do_methodize = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if do_methodize != 1 {
|
||||||
|
// Legacy: const func name with arity and call
|
||||||
|
local call_arity = JsonFragBox._str_to_int("" + args_arr.length())
|
||||||
|
local resolved_with_arity = resolved_name + "/" + call_arity
|
||||||
|
insts = insts + ",{\\\"op\\\":\\\"const\\\",\\\"dst\\\":" + result_reg + ",\\\"value\\\":{\\\"type\\\":\\\"string\\\",\\\"value\\\":\\\"" + resolved_with_arity + "\\\"}}"
|
||||||
|
local func_reg = result_reg
|
||||||
|
result_reg = result_reg + 1
|
||||||
|
next_reg = result_reg + 0
|
||||||
|
insts = insts + ",{\\\"op\\\":\\\"call\\\",\\\"func\\\":" + func_reg + ",\\\"args\\\":[" + args_list + "],\\\"dst\\\":" + result_reg + "}"
|
||||||
|
next_reg = next_reg + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ret
|
// ret
|
||||||
insts = insts + ",{\\\"op\\\":\\\"ret\\\",\\\"value\\\":" + result_reg + "}"
|
insts = insts + ",{\\\"op\\\":\\\"ret\\\",\\\"value\\\":" + result_reg + "}"
|
||||||
|
|
||||||
// Build params JSON array
|
// Build params JSON array
|
||||||
local params_json = me._build_params_json(params_arr)
|
local params_json = me._build_params_json(params_arr)
|
||||||
local mir = "{\\\"name\\\":\\\"" + box_name + "." + func_name + "\\\",\\\"params\\\":" + params_json + ",\\\"locals\\\":[],\\\"blocks\\\":[{\\\"id\\\":0,\\\"instructions\\\":[" + insts + "]}]}"
|
local arity = JsonFragBox._str_to_int("" + params_arr.length())
|
||||||
|
local mir = "{\\\"name\\\":\\\"" + box_name + "." + func_name + "/" + arity + "\\\",\\\"params\\\":" + params_json + ",\\\"locals\\\":[],\\\"blocks\\\":[{\\\"id\\\":0,\\\"instructions\\\":[" + insts + "]}]}"
|
||||||
|
|
||||||
// Debug log
|
// Debug log
|
||||||
if env.get("HAKO_MIR_BUILDER_DEBUG") == "1" {
|
if env.get("HAKO_MIR_BUILDER_DEBUG") == "1" {
|
||||||
|
|||||||
@ -41,6 +41,7 @@ static box JsonFragNormalizerBox {
|
|||||||
local seen = new MapBox() // signature -> 1
|
local seen = new MapBox() // signature -> 1
|
||||||
// Dev-only: purify flag to drop builder-side allocations (e.g., newbox) from JSONFrag output
|
// Dev-only: purify flag to drop builder-side allocations (e.g., newbox) from JSONFrag output
|
||||||
local purify = 0; { local pv = env.get("HAKO_MIR_BUILDER_JSONFRAG_PURIFY"); if pv != null { local pvs = "" + pv; if pvs == "1" { purify = 1 } } }
|
local purify = 0; { local pv = env.get("HAKO_MIR_BUILDER_JSONFRAG_PURIFY"); if pv != null { local pvs = "" + pv; if pvs == "1" { purify = 1 } } }
|
||||||
|
local seen_ret = 0
|
||||||
loop(i < n) {
|
loop(i < n) {
|
||||||
guard = guard + 1
|
guard = guard + 1
|
||||||
if guard > 4096 { break }
|
if guard > 4096 { break }
|
||||||
@ -55,7 +56,11 @@ static box JsonFragNormalizerBox {
|
|||||||
// classify by op
|
// classify by op
|
||||||
local op = JsonFragBox.get_str(obj, "op")
|
local op = JsonFragBox.get_str(obj, "op")
|
||||||
if purify == 1 {
|
if purify == 1 {
|
||||||
|
// Drop builder-side allocations and side-effect calls for JsonFrag purify
|
||||||
if op == "newbox" { continue }
|
if op == "newbox" { continue }
|
||||||
|
if op == "boxcall" { continue }
|
||||||
|
if op == "externcall" { continue }
|
||||||
|
if op == "mir_call" { continue }
|
||||||
}
|
}
|
||||||
if op == "phi" {
|
if op == "phi" {
|
||||||
phi_list.push(obj)
|
phi_list.push(obj)
|
||||||
@ -74,7 +79,9 @@ static box JsonFragNormalizerBox {
|
|||||||
}
|
}
|
||||||
if op == "ret" {
|
if op == "ret" {
|
||||||
others_list.push(me._ret_ensure_value(obj))
|
others_list.push(me._ret_ensure_value(obj))
|
||||||
continue
|
// cut: do not allow any instruction after ret in this block
|
||||||
|
seen_ret = 1
|
||||||
|
break
|
||||||
}
|
}
|
||||||
others_list.push(obj)
|
others_list.push(obj)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -166,6 +166,9 @@ path = "lang/src/shared/common/string_helpers.hako"
|
|||||||
"selfhost.shared.mir.builder" = "lang/src/shared/mir/block_builder_box.hako"
|
"selfhost.shared.mir.builder" = "lang/src/shared/mir/block_builder_box.hako"
|
||||||
"selfhost.shared.mir.io" = "lang/src/shared/mir/mir_io_box.hako"
|
"selfhost.shared.mir.io" = "lang/src/shared/mir/mir_io_box.hako"
|
||||||
"selfhost.shared.mir.json_emit" = "lang/src/shared/mir/json_emit_box.hako"
|
"selfhost.shared.mir.json_emit" = "lang/src/shared/mir/json_emit_box.hako"
|
||||||
|
"selfhost.llvm.ir.normalize.print" = "lang/src/llvm_ir/boxes/normalize/normalize_print.hako"
|
||||||
|
"selfhost.llvm.ir.normalize.ref" = "lang/src/llvm_ir/boxes/normalize/normalize_ref.hako"
|
||||||
|
"selfhost.llvm.ir.normalize.array_legacy" = "lang/src/llvm_ir/boxes/normalize/normalize_array_legacy.hako"
|
||||||
"selfhost.vm.entry" = "lang/src/vm/boxes/mini_vm_entry.hako"
|
"selfhost.vm.entry" = "lang/src/vm/boxes/mini_vm_entry.hako"
|
||||||
"selfhost.vm.mir_min" = "lang/src/vm/boxes/mir_vm_min.hako"
|
"selfhost.vm.mir_min" = "lang/src/vm/boxes/mir_vm_min.hako"
|
||||||
"selfhost.vm.core" = "lang/src/vm/boxes/mini_vm_core.hako"
|
"selfhost.vm.core" = "lang/src/vm/boxes/mini_vm_core.hako"
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use super::super::utils::*;
|
|
||||||
|
|
||||||
impl MirInterpreter {
|
impl MirInterpreter {
|
||||||
pub(super) fn handle_const(&mut self, dst: ValueId, value: &ConstValue) -> Result<(), VMError> {
|
pub(super) fn handle_const(&mut self, dst: ValueId, value: &ConstValue) -> Result<(), VMError> {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use super::super::utils::*;
|
|
||||||
use crate::box_trait::NyashBox;
|
use crate::box_trait::NyashBox;
|
||||||
|
|
||||||
impl MirInterpreter {
|
impl MirInterpreter {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use super::super::utils::*;
|
|
||||||
use crate::box_trait::NyashBox;
|
use crate::box_trait::NyashBox;
|
||||||
|
|
||||||
pub(super) fn try_handle_array_box(
|
pub(super) fn try_handle_array_box(
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use super::super::utils::*;
|
|
||||||
use crate::box_trait::NyashBox;
|
use crate::box_trait::NyashBox;
|
||||||
|
|
||||||
pub(super) fn try_handle_instance_box(
|
pub(super) fn try_handle_instance_box(
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use super::super::utils::*;
|
|
||||||
use crate::box_trait::NyashBox;
|
use crate::box_trait::NyashBox;
|
||||||
|
|
||||||
pub(super) fn try_handle_map_box(
|
pub(super) fn try_handle_map_box(
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use super::super::utils::*;
|
|
||||||
use crate::box_trait::NyashBox;
|
use crate::box_trait::NyashBox;
|
||||||
|
|
||||||
pub(super) fn try_handle_object_fields(
|
pub(super) fn try_handle_object_fields(
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use super::super::utils::*;
|
|
||||||
use crate::box_trait::NyashBox;
|
use crate::box_trait::NyashBox;
|
||||||
|
|
||||||
pub(super) fn invoke_plugin_box(
|
pub(super) fn invoke_plugin_box(
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use super::super::utils::*;
|
|
||||||
use crate::box_trait::NyashBox;
|
use crate::box_trait::NyashBox;
|
||||||
|
|
||||||
pub(super) fn try_handle_string_box(
|
pub(super) fn try_handle_string_box(
|
||||||
|
|||||||
@ -1,89 +0,0 @@
|
|||||||
// Unique-tail function name resolution
|
|
||||||
// Extracted from execute_legacy_call to keep handlers lean
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use super::MirFunction;
|
|
||||||
|
|
||||||
/// Resolve function name using unique-tail matching algorithm.
|
|
||||||
///
|
|
||||||
/// Resolution strategy:
|
|
||||||
/// 1. Fast path: exact match on raw name
|
|
||||||
/// 2. Normalize with arity: "base/N"
|
|
||||||
/// 3. Unique-tail matching: find candidates ending with ".method/N"
|
|
||||||
/// 4. Same-box preference: prioritize functions in the same box as current_function
|
|
||||||
///
|
|
||||||
/// Returns resolved function name, or None if resolution fails.
|
|
||||||
pub(super) fn resolve_function_name(
|
|
||||||
raw: &str,
|
|
||||||
arity: usize,
|
|
||||||
functions: &HashMap<String, MirFunction>,
|
|
||||||
current_function: Option<&str>,
|
|
||||||
) -> Option<String> {
|
|
||||||
// Fast path: exact match
|
|
||||||
if functions.contains_key(raw) {
|
|
||||||
return Some(raw.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Robust normalization for names like "Box.method/Arity" or just "method"
|
|
||||||
let (base, ar_from_raw) = if let Some((b, a)) = raw.rsplit_once('/') {
|
|
||||||
(b.to_string(), a.parse::<usize>().ok())
|
|
||||||
} else {
|
|
||||||
(raw.to_string(), None)
|
|
||||||
};
|
|
||||||
let want_arity = ar_from_raw.unwrap_or(arity);
|
|
||||||
|
|
||||||
// Try exact canonical form: "base/arity"
|
|
||||||
let exact = format!("{}/{}", base, want_arity);
|
|
||||||
if functions.contains_key(&exact) {
|
|
||||||
return Some(exact);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split base into optional box and method name
|
|
||||||
let (maybe_box, method) = if let Some((bx, m)) = base.split_once('.') {
|
|
||||||
(Some(bx.to_string()), m.to_string())
|
|
||||||
} else {
|
|
||||||
(None, base.clone())
|
|
||||||
};
|
|
||||||
|
|
||||||
// Collect candidates that end with ".method/arity"
|
|
||||||
let mut cands: Vec<String> = Vec::new();
|
|
||||||
let tail = format!(".{}{}", method, format!("/{}", want_arity));
|
|
||||||
for k in functions.keys() {
|
|
||||||
if k.ends_with(&tail) {
|
|
||||||
if let Some(ref bx) = maybe_box {
|
|
||||||
if k.starts_with(&format!("{}.", bx)) {
|
|
||||||
cands.push(k.clone());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cands.push(k.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cands.len() > 1 {
|
|
||||||
// Prefer same-box candidate based on current function's box
|
|
||||||
if let Some(cur) = current_function {
|
|
||||||
let cur_box = cur.split('.').next().unwrap_or("");
|
|
||||||
let scoped: Vec<String> = cands
|
|
||||||
.iter()
|
|
||||||
.filter(|k| k.starts_with(&format!("{}.", cur_box)))
|
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
if scoped.len() == 1 {
|
|
||||||
cands = scoped;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match cands.len() {
|
|
||||||
0 => None,
|
|
||||||
1 => Some(cands.into_iter().next().unwrap()),
|
|
||||||
_ => {
|
|
||||||
// Multiple candidates: sort and pick first (deterministic fallback)
|
|
||||||
let mut c = cands;
|
|
||||||
c.sort();
|
|
||||||
Some(c.into_iter().next().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,873 +0,0 @@
|
|||||||
use super::*;
|
|
||||||
use super::super::utils::*;
|
|
||||||
|
|
||||||
impl MirInterpreter {
|
|
||||||
pub(super) fn handle_call(
|
|
||||||
&mut self,
|
|
||||||
dst: Option<ValueId>,
|
|
||||||
func: ValueId,
|
|
||||||
callee: Option<&Callee>,
|
|
||||||
args: &[ValueId],
|
|
||||||
) -> Result<(), VMError> {
|
|
||||||
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") {
|
|
||||||
match callee {
|
|
||||||
Some(Callee::Global(n)) => eprintln!("[hb:path] call Callee::Global {} argc={}", n, args.len()),
|
|
||||||
Some(Callee::Method{ box_name, method, ..}) => eprintln!("[hb:path] call Callee::Method {}.{} argc={}", box_name, method, args.len()),
|
|
||||||
Some(Callee::Constructor{ box_type }) => eprintln!("[hb:path] call Callee::Constructor {} argc={}", box_type, args.len()),
|
|
||||||
Some(Callee::Closure{ .. }) => eprintln!("[hb:path] call Callee::Closure argc={}", args.len()),
|
|
||||||
Some(Callee::Value(_)) => eprintln!("[hb:path] call Callee::Value argc={}", args.len()),
|
|
||||||
Some(Callee::Extern(n)) => eprintln!("[hb:path] call Callee::Extern {} argc={}", n, args.len()),
|
|
||||||
None => eprintln!("[hb:path] call Legacy func_id={:?} argc={}", func, args.len()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// SSOT fast-path: route hostbridge.extern_invoke to extern dispatcher regardless of resolution form
|
|
||||||
if let Some(Callee::Global(func_name)) = callee {
|
|
||||||
if func_name == "hostbridge.extern_invoke" || func_name.starts_with("hostbridge.extern_invoke/") {
|
|
||||||
let v = self.execute_extern_function("hostbridge.extern_invoke", args)?;
|
|
||||||
self.write_result(dst, v);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let call_result = if let Some(callee_type) = callee {
|
|
||||||
self.execute_callee_call(callee_type, args)?
|
|
||||||
} else {
|
|
||||||
self.execute_legacy_call(func, args)?
|
|
||||||
};
|
|
||||||
self.write_result(dst, call_result);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn execute_callee_call(
|
|
||||||
&mut self,
|
|
||||||
callee: &Callee,
|
|
||||||
args: &[ValueId],
|
|
||||||
) -> Result<VMValue, VMError> {
|
|
||||||
match callee {
|
|
||||||
Callee::Global(func_name) => {
|
|
||||||
// Phase 21.2: Dev by-name bridge removed - all adapter functions now in .hako
|
|
||||||
self.execute_global_function(func_name, args)
|
|
||||||
}
|
|
||||||
Callee::Method { box_name: _, method, receiver, certainty: _, } => {
|
|
||||||
if let Some(recv_id) = receiver {
|
|
||||||
// Primary: load receiver by id. Dev fallback: if undefined and env allows,
|
|
||||||
// use args[0] as a surrogate receiver (builder localization gap workaround).
|
|
||||||
let recv_val = match self.reg_load(*recv_id) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => {
|
|
||||||
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref() == Some("1")
|
|
||||||
|| std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1");
|
|
||||||
if tolerate {
|
|
||||||
if let Some(a0) = args.get(0) {
|
|
||||||
self.reg_load(*a0)?
|
|
||||||
} else {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let dev_trace = std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1");
|
|
||||||
// Fast bridge for builtin boxes (Array) and common methods.
|
|
||||||
// Preserve legacy semantics when plugins are absent.
|
|
||||||
if let VMValue::BoxRef(bx) = &recv_val {
|
|
||||||
// ArrayBox bridge
|
|
||||||
if let Some(arr) = bx.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
||||||
match method.as_str() {
|
|
||||||
"birth" => { return Ok(VMValue::Void); }
|
|
||||||
"push" => {
|
|
||||||
if let Some(a0) = args.get(0) {
|
|
||||||
let v = self.load_as_box(*a0)?;
|
|
||||||
let _ = arr.push(v);
|
|
||||||
return Ok(VMValue::Void);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"len" | "length" | "size" => {
|
|
||||||
let ret = arr.length();
|
|
||||||
return Ok(VMValue::from_nyash_box(ret));
|
|
||||||
}
|
|
||||||
"get" => {
|
|
||||||
if let Some(a0) = args.get(0) {
|
|
||||||
let idx = self.load_as_box(*a0)?;
|
|
||||||
let ret = arr.get(idx);
|
|
||||||
return Ok(VMValue::from_nyash_box(ret));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"set" => {
|
|
||||||
if args.len() >= 2 {
|
|
||||||
let idx = self.load_as_box(args[0])?;
|
|
||||||
let val = self.load_as_box(args[1])?;
|
|
||||||
let _ = arr.set(idx, val);
|
|
||||||
return Ok(VMValue::Void);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Minimal bridge for birth(): delegate to BoxCall handler and return Void
|
|
||||||
if method == &"birth" {
|
|
||||||
let _ = self.handle_box_call(None, *recv_id, method, args)?;
|
|
||||||
return Ok(VMValue::Void);
|
|
||||||
}
|
|
||||||
let is_kw = method == &"keyword_to_token_type";
|
|
||||||
if dev_trace && is_kw {
|
|
||||||
let a0 = args.get(0).and_then(|id| self.reg_load(*id).ok());
|
|
||||||
eprintln!("[vm-trace] mcall {} argv0={:?}", method, a0);
|
|
||||||
}
|
|
||||||
let out = self.execute_method_call(&recv_val, method, args)?;
|
|
||||||
if dev_trace && is_kw {
|
|
||||||
eprintln!("[vm-trace] mret {} -> {:?}", method, out);
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
} else {
|
|
||||||
Err(self.err_with_context("Method call", &format!("missing receiver for {}", method)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Callee::Constructor { box_type } => Err(self.err_unsupported(&format!("Constructor calls for {}", box_type))),
|
|
||||||
Callee::Closure { .. } => Err(self.err_unsupported("Closure creation in VM")),
|
|
||||||
Callee::Value(func_val_id) => {
|
|
||||||
let _func_val = self.reg_load(*func_val_id)?;
|
|
||||||
Err(self.err_unsupported("First-class function calls in VM"))
|
|
||||||
}
|
|
||||||
Callee::Extern(extern_name) => self.execute_extern_function(extern_name, args),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn execute_legacy_call(
|
|
||||||
&mut self,
|
|
||||||
func_id: ValueId,
|
|
||||||
args: &[ValueId],
|
|
||||||
) -> Result<VMValue, VMError> {
|
|
||||||
let name_val = self.reg_load(func_id)?;
|
|
||||||
let raw = match name_val {
|
|
||||||
VMValue::String(ref s) => s.clone(),
|
|
||||||
other => other.to_string(),
|
|
||||||
};
|
|
||||||
if std::env::var("HAKO_DEBUG_LEGACY_CALL").ok().as_deref() == Some("1") {
|
|
||||||
eprintln!("[vm-debug] legacy-call raw='{}' argc={}", raw, args.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Minimal builtin bridge: support print-like globals in legacy form
|
|
||||||
// Accept: "print", "nyash.console.log", "env.console.log", "nyash.builtin.print"
|
|
||||||
match raw.as_str() {
|
|
||||||
"print" | "nyash.console.log" | "env.console.log" | "nyash.builtin.print" => {
|
|
||||||
if let Some(a0) = args.get(0) {
|
|
||||||
let v = self.reg_load(*a0)?;
|
|
||||||
println!("{}", v.to_string());
|
|
||||||
} else {
|
|
||||||
println!("");
|
|
||||||
}
|
|
||||||
return Ok(VMValue::Void);
|
|
||||||
}
|
|
||||||
name if name == "hostbridge.extern_invoke" || name.starts_with("hostbridge.extern_invoke/") =>
|
|
||||||
// SSOT: always delegate to extern dispatcher
|
|
||||||
{ return self.execute_extern_function("hostbridge.extern_invoke", args); }
|
|
||||||
name if name == "env.get" || name.starts_with("env.get/") || name.contains("env.get") => {
|
|
||||||
return self.execute_extern_function("env.get", args);
|
|
||||||
}
|
|
||||||
// Minimal bridge for Hako static provider: HakoruneExternProviderBox.get(name, arg)
|
|
||||||
// Purpose: allow -c/inline alias path to tolerate static box calls without unresolved errors.
|
|
||||||
// Behavior: when HAKO_V1_EXTERN_PROVIDER_C_ABI=1, emit stable tag; always return empty string.
|
|
||||||
name if name == "HakoruneExternProviderBox.get"
|
|
||||||
|| name.starts_with("HakoruneExternProviderBox.get/")
|
|
||||||
|| name.contains("HakoruneExternProviderBox.get") =>
|
|
||||||
{
|
|
||||||
// Read provider name from first arg if available
|
|
||||||
let mut prov: Option<String> = None;
|
|
||||||
if let Some(a0) = args.get(0) {
|
|
||||||
if let Ok(v) = self.reg_load(*a0) { prov = Some(v.to_string()); }
|
|
||||||
}
|
|
||||||
if std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() == Some("1") {
|
|
||||||
if let Some(p) = prov.as_deref() {
|
|
||||||
match p {
|
|
||||||
"env.mirbuilder.emit" => eprintln!("[extern/c-abi:mirbuilder.emit]"),
|
|
||||||
"env.codegen.emit_object" => eprintln!("[extern/c-abi:codegen.emit_object]"),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Ok(VMValue::String(String::new()));
|
|
||||||
}
|
|
||||||
// Phase 21.2: Dev bridge removed - all adapter functions now resolved via .hako implementation
|
|
||||||
// MirCallV1HandlerBox.handle, JsonFragBox._str_to_int, AbiAdapterRegistryBox.*
|
|
||||||
// are now implemented in lang/src/vm/ and compiled via text-merge
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve function name using unique-tail matching
|
|
||||||
let fname = call_resolution::resolve_function_name(
|
|
||||||
&raw,
|
|
||||||
args.len(),
|
|
||||||
&self.functions,
|
|
||||||
self.cur_fn.as_deref(),
|
|
||||||
)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
ErrorBuilder::with_context("call", &format!("unresolved: '{}' (arity={})", raw, args.len()))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if std::env::var("NYASH_VM_CALL_TRACE").ok().as_deref() == Some("1") {
|
|
||||||
eprintln!("[vm] legacy-call resolved '{}' -> '{}'", raw, fname);
|
|
||||||
}
|
|
||||||
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") {
|
|
||||||
eprintln!("[hb:resolved:calls] fname='{}'", fname);
|
|
||||||
}
|
|
||||||
|
|
||||||
let callee =
|
|
||||||
self.functions.get(&fname).cloned().ok_or_else(|| {
|
|
||||||
self.err_with_context("function call", &format!("function not found: {}", fname))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// SSOT: delegate hostbridge.extern_invoke to the extern dispatcher early,
|
|
||||||
// avoiding duplicated legacy handling below. This ensures C-API link path
|
|
||||||
// is consistently covered by extern_provider_dispatch.
|
|
||||||
if fname == "hostbridge.extern_invoke" || fname.starts_with("hostbridge.extern_invoke/") {
|
|
||||||
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") {
|
|
||||||
eprintln!("[hb:delegate:calls] hostbridge.extern_invoke -> execute_extern_function (early)");
|
|
||||||
}
|
|
||||||
return self.execute_extern_function("hostbridge.extern_invoke", args);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut argv: Vec<VMValue> = Vec::new();
|
|
||||||
for a in args {
|
|
||||||
argv.push(self.reg_load(*a)?);
|
|
||||||
}
|
|
||||||
let dev_trace = std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1");
|
|
||||||
let is_kw = fname.ends_with("JsonTokenizer.keyword_to_token_type/1");
|
|
||||||
let is_sc_ident = fname.ends_with("JsonScanner.read_identifier/0");
|
|
||||||
let is_sc_current = fname.ends_with("JsonScanner.current/0");
|
|
||||||
let is_tok_kw = fname.ends_with("JsonTokenizer.tokenize_keyword/0");
|
|
||||||
let is_tok_struct = fname.ends_with("JsonTokenizer.create_structural_token/2");
|
|
||||||
if dev_trace && (is_kw || is_sc_ident || is_sc_current || is_tok_kw || is_tok_struct) {
|
|
||||||
if let Some(a0) = argv.get(0) {
|
|
||||||
eprintln!("[vm-trace] call {} argv0={:?}", fname, a0);
|
|
||||||
} else {
|
|
||||||
eprintln!("[vm-trace] call {}", fname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Dev trace: emit a synthetic "call" event for global function calls
|
|
||||||
// so operator boxes (e.g., CompareOperator.apply/3) are observable with
|
|
||||||
// argument kinds. This produces a JSON line on stderr, filtered by
|
|
||||||
// NYASH_BOX_TRACE_FILTER like other box traces.
|
|
||||||
if Self::box_trace_enabled() {
|
|
||||||
// Render class/method from canonical fname like "Class.method/Arity"
|
|
||||||
let (class_name, method_name) = if let Some((cls, rest)) = fname.split_once('.') {
|
|
||||||
let method = rest.split('/').next().unwrap_or(rest);
|
|
||||||
(cls.to_string(), method.to_string())
|
|
||||||
} else {
|
|
||||||
("<global>".to_string(), fname.split('/').next().unwrap_or(&fname).to_string())
|
|
||||||
};
|
|
||||||
// Simple filter match (local copy to avoid private helper)
|
|
||||||
let filt_ok = match std::env::var("NYASH_BOX_TRACE_FILTER").ok() {
|
|
||||||
Some(filt) => {
|
|
||||||
let want = filt.trim();
|
|
||||||
if want.is_empty() { true } else {
|
|
||||||
want.split(|c: char| c == ',' || c.is_whitespace())
|
|
||||||
.map(|t| t.trim())
|
|
||||||
.filter(|t| !t.is_empty())
|
|
||||||
.any(|t| class_name.contains(t))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => true,
|
|
||||||
};
|
|
||||||
if filt_ok {
|
|
||||||
// Optionally include argument kinds for targeted debugging.
|
|
||||||
let with_args = std::env::var("NYASH_OP_TRACE_ARGS").ok().as_deref() == Some("1")
|
|
||||||
|| class_name == "CompareOperator";
|
|
||||||
if with_args {
|
|
||||||
// local JSON string escaper (subset)
|
|
||||||
let mut esc = |s: &str| {
|
|
||||||
let mut out = String::with_capacity(s.len() + 8);
|
|
||||||
for ch in s.chars() {
|
|
||||||
match ch {
|
|
||||||
'"' => out.push_str("\\\""),
|
|
||||||
'\\' => out.push_str("\\\\"),
|
|
||||||
'\n' => out.push_str("\\n"),
|
|
||||||
'\r' => out.push_str("\\r"),
|
|
||||||
'\t' => out.push_str("\\t"),
|
|
||||||
c if c.is_control() => out.push(' '),
|
|
||||||
c => out.push(c),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out
|
|
||||||
};
|
|
||||||
let mut kinds: Vec<String> = Vec::with_capacity(argv.len());
|
|
||||||
let mut nullish: Vec<String> = Vec::with_capacity(argv.len());
|
|
||||||
for v in &argv {
|
|
||||||
let k = match v {
|
|
||||||
VMValue::Integer(_) => "Integer".to_string(),
|
|
||||||
VMValue::Float(_) => "Float".to_string(),
|
|
||||||
VMValue::Bool(_) => "Bool".to_string(),
|
|
||||||
VMValue::String(_) => "String".to_string(),
|
|
||||||
VMValue::Void => "Void".to_string(),
|
|
||||||
VMValue::Future(_) => "Future".to_string(),
|
|
||||||
VMValue::BoxRef(b) => {
|
|
||||||
// Prefer InstanceBox.class_name when available
|
|
||||||
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
|
||||||
format!("BoxRef:{}", inst.class_name)
|
|
||||||
} else {
|
|
||||||
format!("BoxRef:{}", b.type_name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
kinds.push(k);
|
|
||||||
// nullish tag (env-gated): "null" | "missing" | "void" | ""
|
|
||||||
if crate::config::env::null_missing_box_enabled() {
|
|
||||||
let tag = match v {
|
|
||||||
VMValue::Void => "void",
|
|
||||||
VMValue::BoxRef(b) => {
|
|
||||||
if b.as_any().downcast_ref::<crate::boxes::null_box::NullBox>().is_some() { "null" }
|
|
||||||
else if b.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some() { "missing" }
|
|
||||||
else if b.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() { "void" }
|
|
||||||
else { "" }
|
|
||||||
}
|
|
||||||
_ => "",
|
|
||||||
};
|
|
||||||
nullish.push(tag.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let args_json = kinds
|
|
||||||
.into_iter()
|
|
||||||
.map(|s| format!("\"{}\"", esc(&s)))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(",");
|
|
||||||
let nullish_json = if crate::config::env::null_missing_box_enabled() {
|
|
||||||
let arr = nullish
|
|
||||||
.into_iter()
|
|
||||||
.map(|s| format!("\"{}\"", esc(&s)))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(",");
|
|
||||||
Some(arr)
|
|
||||||
} else { None };
|
|
||||||
// For CompareOperator, include op string value if present in argv[0]
|
|
||||||
let cur_fn = self
|
|
||||||
.cur_fn
|
|
||||||
.as_deref()
|
|
||||||
.map(|s| esc(s))
|
|
||||||
.unwrap_or_else(|| String::from("") );
|
|
||||||
if class_name == "CompareOperator" && !argv.is_empty() {
|
|
||||||
let op_str = match &argv[0] {
|
|
||||||
VMValue::String(s) => esc(s),
|
|
||||||
_ => String::from("")
|
|
||||||
};
|
|
||||||
if let Some(nj) = nullish_json {
|
|
||||||
eprintln!(
|
|
||||||
"{{\"ev\":\"call\",\"class\":\"{}\",\"method\":\"{}\",\"argc\":{},\"fn\":\"{}\",\"op\":\"{}\",\"argk\":[{}],\"nullish\":[{}]}}",
|
|
||||||
esc(&class_name), esc(&method_name), argv.len(), cur_fn, op_str, args_json, nj
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"{{\"ev\":\"call\",\"class\":\"{}\",\"method\":\"{}\",\"argc\":{},\"fn\":\"{}\",\"op\":\"{}\",\"argk\":[{}]}}",
|
|
||||||
esc(&class_name), esc(&method_name), argv.len(), cur_fn, op_str, args_json
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if let Some(nj) = nullish_json {
|
|
||||||
eprintln!(
|
|
||||||
"{{\"ev\":\"call\",\"class\":\"{}\",\"method\":\"{}\",\"argc\":{},\"fn\":\"{}\",\"argk\":[{}],\"nullish\":[{}]}}",
|
|
||||||
esc(&class_name), esc(&method_name), argv.len(), cur_fn, args_json, nj
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"{{\"ev\":\"call\",\"class\":\"{}\",\"method\":\"{}\",\"argc\":{},\"fn\":\"{}\",\"argk\":[{}]}}",
|
|
||||||
esc(&class_name), esc(&method_name), argv.len(), cur_fn, args_json
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.box_trace_emit_call(&class_name, &method_name, argv.len());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let out = self.exec_function_inner(&callee, Some(&argv))?;
|
|
||||||
if dev_trace && (is_kw || is_sc_ident || is_sc_current || is_tok_kw || is_tok_struct) {
|
|
||||||
eprintln!("[vm-trace] ret {} -> {:?}", fname, out);
|
|
||||||
}
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_global_function(
|
|
||||||
&mut self,
|
|
||||||
func_name: &str,
|
|
||||||
args: &[ValueId],
|
|
||||||
) -> Result<VMValue, VMError> {
|
|
||||||
match func_name {
|
|
||||||
name if name == "env.get" || name.starts_with("env.get/") => {
|
|
||||||
// Route env.get global to extern handler
|
|
||||||
return self.execute_extern_function("env.get", args);
|
|
||||||
}
|
|
||||||
name if name == "hostbridge.extern_invoke" || name.starts_with("hostbridge.extern_invoke/") => {
|
|
||||||
// SSOT: delegate to extern dispatcher (provider). Keep legacy block unreachable.
|
|
||||||
return self.execute_extern_function("hostbridge.extern_invoke", args);
|
|
||||||
// Treat as extern_invoke in legacy/global-resolved form
|
|
||||||
if args.len() < 3 {
|
|
||||||
return Err(self.err_arg_count("hostbridge.extern_invoke", 3, args.len()));
|
|
||||||
}
|
|
||||||
let name = self.reg_load(args[0])?.to_string();
|
|
||||||
let method = self.reg_load(args[1])?.to_string();
|
|
||||||
let v = self.reg_load(args[2])?;
|
|
||||||
let mut first_arg_str: Option<String> = None;
|
|
||||||
match v {
|
|
||||||
VMValue::BoxRef(b) => {
|
|
||||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
||||||
let idx: Box<dyn crate::box_trait::NyashBox> =
|
|
||||||
Box::new(crate::box_trait::IntegerBox::new(0));
|
|
||||||
let elem = ab.get(idx);
|
|
||||||
first_arg_str = Some(elem.to_string_box().value);
|
|
||||||
} else {
|
|
||||||
first_arg_str = Some(b.to_string_box().value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => first_arg_str = Some(v.to_string()),
|
|
||||||
}
|
|
||||||
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") {
|
|
||||||
eprintln!("[hb:dispatch:calls] {} {}", name, method);
|
|
||||||
}
|
|
||||||
match (name.as_str(), method.as_str()) {
|
|
||||||
("env.mirbuilder", "emit") => {
|
|
||||||
if let Some(s) = first_arg_str {
|
|
||||||
match crate::host_providers::mir_builder::program_json_to_mir_json(&s) {
|
|
||||||
Ok(out) => Ok(VMValue::String(out)),
|
|
||||||
Err(e) => Err(self.err_with_context("env.mirbuilder.emit", &e.to_string())),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(self.err_invalid("extern_invoke env.mirbuilder.emit expects 1 arg"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
("env.codegen", "emit_object") => {
|
|
||||||
if let Some(s) = first_arg_str {
|
|
||||||
let opts = crate::host_providers::llvm_codegen::Opts {
|
|
||||||
out: None,
|
|
||||||
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from),
|
|
||||||
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok().or(Some("0".to_string())),
|
|
||||||
timeout_ms: None,
|
|
||||||
};
|
|
||||||
match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts) {
|
|
||||||
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
|
|
||||||
Err(e) => Err(self.err_with_context("env.codegen.emit_object", &e.to_string())),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(self.err_invalid("extern_invoke env.codegen.emit_object expects 1 arg"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
("env.codegen", "link_object") => {
|
|
||||||
// C-API route only; here args[2] is already loaded into `v`
|
|
||||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
|
|
||||||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
|
|
||||||
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
|
|
||||||
}
|
|
||||||
let (obj_path, exe_out) = match v {
|
|
||||||
VMValue::BoxRef(b) => {
|
|
||||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
||||||
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
|
|
||||||
let elem0 = ab.get(idx0).to_string_box().value;
|
|
||||||
let mut exe: Option<String> = None;
|
|
||||||
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
|
|
||||||
let e1 = ab.get(idx1).to_string_box().value;
|
|
||||||
if !e1.is_empty() { exe = Some(e1); }
|
|
||||||
(elem0, exe)
|
|
||||||
} else { (b.to_string_box().value, None) }
|
|
||||||
}
|
|
||||||
_ => (v.to_string(), None),
|
|
||||||
};
|
|
||||||
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
|
|
||||||
let obj = std::path::PathBuf::from(obj_path);
|
|
||||||
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
|
||||||
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
|
|
||||||
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
|
|
||||||
Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
("env.codegen", "link_object") => {
|
|
||||||
// C-API route only; args[2] is expected to be an ArrayBox [obj_path, exe_out?]
|
|
||||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
|
|
||||||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
|
|
||||||
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
|
|
||||||
}
|
|
||||||
if args.len() < 3 { return Err(self.err_arg_count("env.codegen.link_object", 3, args.len())); }
|
|
||||||
let v = self.reg_load(args[2])?;
|
|
||||||
let (obj_path, exe_out) = match v {
|
|
||||||
VMValue::BoxRef(b) => {
|
|
||||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
||||||
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
|
|
||||||
let elem0 = ab.get(idx0).to_string_box().value;
|
|
||||||
let mut exe: Option<String> = None;
|
|
||||||
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
|
|
||||||
let e1 = ab.get(idx1).to_string_box().value;
|
|
||||||
if !e1.is_empty() { exe = Some(e1); }
|
|
||||||
(elem0, exe)
|
|
||||||
} else { (b.to_string_box().value, None) }
|
|
||||||
}
|
|
||||||
_ => (v.to_string(), None),
|
|
||||||
};
|
|
||||||
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
|
|
||||||
let obj = std::path::PathBuf::from(obj_path);
|
|
||||||
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
|
||||||
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
|
|
||||||
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
|
|
||||||
Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
("env.codegen", "link_object") => {
|
|
||||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
|
|
||||||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
|
|
||||||
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
|
|
||||||
}
|
|
||||||
// Extract from args[2] (ArrayBox): [obj_path, exe_out?]
|
|
||||||
let v = self.reg_load(args[2])?;
|
|
||||||
let (obj_path, exe_out) = match v {
|
|
||||||
VMValue::BoxRef(b) => {
|
|
||||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
||||||
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
|
|
||||||
let elem0 = ab.get(idx0).to_string_box().value;
|
|
||||||
let mut exe: Option<String> = None;
|
|
||||||
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
|
|
||||||
let e1 = ab.get(idx1).to_string_box().value;
|
|
||||||
if !e1.is_empty() { exe = Some(e1); }
|
|
||||||
(elem0, exe)
|
|
||||||
} else { (b.to_string_box().value, None) }
|
|
||||||
}
|
|
||||||
_ => (v.to_string(), None),
|
|
||||||
};
|
|
||||||
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
|
|
||||||
let obj = std::path::PathBuf::from(obj_path);
|
|
||||||
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
|
||||||
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
|
|
||||||
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
|
|
||||||
Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
("env.codegen", "link_object") => {
|
|
||||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
|
|
||||||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
|
|
||||||
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
|
|
||||||
}
|
|
||||||
// Here args[2] is already loaded into `v`; parse ArrayBox [obj, exe?]
|
|
||||||
let (obj_path, exe_out) = match v {
|
|
||||||
VMValue::BoxRef(b) => {
|
|
||||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
||||||
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
|
|
||||||
let elem0 = ab.get(idx0).to_string_box().value;
|
|
||||||
let mut exe: Option<String> = None;
|
|
||||||
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
|
|
||||||
let e1 = ab.get(idx1).to_string_box().value;
|
|
||||||
if !e1.is_empty() { exe = Some(e1); }
|
|
||||||
(elem0, exe)
|
|
||||||
} else { (b.to_string_box().value, None) }
|
|
||||||
}
|
|
||||||
_ => (v.to_string(), None),
|
|
||||||
};
|
|
||||||
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
|
|
||||||
let obj = std::path::PathBuf::from(obj_path);
|
|
||||||
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
|
||||||
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
|
|
||||||
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
|
|
||||||
Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Last-chance bridge: some older dispatch paths may fall through here
|
|
||||||
if name == "env.codegen" && method == "link_object" {
|
|
||||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
|
|
||||||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
|
|
||||||
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
|
|
||||||
}
|
|
||||||
// Expect args[2] as ArrayBox [obj, exe?]
|
|
||||||
if args.len() >= 3 {
|
|
||||||
let v = self.reg_load(args[2])?;
|
|
||||||
let (obj_path, exe_out) = match v {
|
|
||||||
VMValue::BoxRef(b) => {
|
|
||||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
|
||||||
let i0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
|
|
||||||
let s0 = ab.get(i0).to_string_box().value;
|
|
||||||
let mut e: Option<String> = None;
|
|
||||||
let i1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
|
|
||||||
let s1 = ab.get(i1).to_string_box().value;
|
|
||||||
if !s1.is_empty() { e = Some(s1); }
|
|
||||||
(s0, e)
|
|
||||||
} else { (b.to_string_box().value, None) }
|
|
||||||
}
|
|
||||||
_ => (v.to_string(), None),
|
|
||||||
};
|
|
||||||
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
|
|
||||||
let obj = std::path::PathBuf::from(obj_path);
|
|
||||||
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
|
||||||
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
|
|
||||||
Ok(()) => return Ok(VMValue::String(exe.to_string_lossy().into_owned())),
|
|
||||||
Err(e) => return Err(self.err_with_context("env.codegen.link_object", &e.to_string())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// As a final safety, try provider dispatcher again
|
|
||||||
if let Some(res) = self.extern_provider_dispatch("hostbridge.extern_invoke", args) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") {
|
|
||||||
eprintln!("[hb:unsupported:calls] {}.{}", name, method);
|
|
||||||
}
|
|
||||||
Err(self.err_invalid(format!(
|
|
||||||
"hostbridge.extern_invoke unsupported for {}.{} [calls]",
|
|
||||||
name, method
|
|
||||||
)))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"nyash.builtin.print" | "print" | "nyash.console.log" => {
|
|
||||||
if let Some(arg_id) = args.get(0) {
|
|
||||||
let val = self.reg_load(*arg_id)?;
|
|
||||||
// Dev-only: print trace (kind/class) before actual print
|
|
||||||
if Self::print_trace_enabled() { self.print_trace_emit(&val); }
|
|
||||||
// Dev observe: Null/Missing boxes quick normalization (no behavior change to prod)
|
|
||||||
if let VMValue::BoxRef(bx) = &val {
|
|
||||||
// NullBox → always print as null (stable)
|
|
||||||
if bx.as_any().downcast_ref::<crate::boxes::null_box::NullBox>().is_some() {
|
|
||||||
println!("null");
|
|
||||||
return Ok(VMValue::Void);
|
|
||||||
}
|
|
||||||
// MissingBox → default prints as null; when flag ON, show (missing)
|
|
||||||
if bx.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some() {
|
|
||||||
if crate::config::env::null_missing_box_enabled() {
|
|
||||||
println!("(missing)");
|
|
||||||
} else {
|
|
||||||
println!("null");
|
|
||||||
}
|
|
||||||
return Ok(VMValue::Void);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Dev: treat VM Void and BoxRef(VoidBox) as JSON null for print
|
|
||||||
match &val {
|
|
||||||
VMValue::Void => {
|
|
||||||
println!("null");
|
|
||||||
return Ok(VMValue::Void);
|
|
||||||
}
|
|
||||||
VMValue::BoxRef(bx) => {
|
|
||||||
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
|
||||||
println!("null");
|
|
||||||
return Ok(VMValue::Void);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
// Print raw strings directly (avoid double quoting via StringifyOperator)
|
|
||||||
match &val {
|
|
||||||
VMValue::String(s) => { println!("{}", s); return Ok(VMValue::Void); }
|
|
||||||
VMValue::BoxRef(bx) => {
|
|
||||||
if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
|
||||||
println!("{}", sb.value);
|
|
||||||
return Ok(VMValue::Void);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
// Operator Box (Stringify) – dev flag gated
|
|
||||||
if std::env::var("NYASH_OPERATOR_BOX_STRINGIFY").ok().as_deref() == Some("1") {
|
|
||||||
if let Some(op) = self.functions.get("StringifyOperator.apply/1").cloned() {
|
|
||||||
let out = self.exec_function_inner(&op, Some(&[val.clone()]))?;
|
|
||||||
println!("{}", out.to_string());
|
|
||||||
} else {
|
|
||||||
println!("{}", val.to_string());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!("{}", val.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(VMValue::Void)
|
|
||||||
}
|
|
||||||
"nyash.builtin.error" => {
|
|
||||||
if let Some(arg_id) = args.get(0) {
|
|
||||||
let val = self.reg_load(*arg_id)?;
|
|
||||||
eprintln!("Error: {}", val.to_string());
|
|
||||||
}
|
|
||||||
Ok(VMValue::Void)
|
|
||||||
}
|
|
||||||
_ => Err(self.err_with_context("global function", &format!("Unknown: {}", func_name))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_method_call(
|
|
||||||
&mut self,
|
|
||||||
receiver: &VMValue,
|
|
||||||
method: &str,
|
|
||||||
args: &[ValueId],
|
|
||||||
) -> Result<VMValue, VMError> {
|
|
||||||
match receiver {
|
|
||||||
VMValue::String(s) => match method {
|
|
||||||
"length" => Ok(VMValue::Integer(s.len() as i64)),
|
|
||||||
"concat" => {
|
|
||||||
if let Some(arg_id) = args.get(0) {
|
|
||||||
let arg_val = self.reg_load(*arg_id)?;
|
|
||||||
let new_str = format!("{}{}", s, arg_val.to_string());
|
|
||||||
Ok(VMValue::String(new_str))
|
|
||||||
} else {
|
|
||||||
Err(self.err_invalid("concat requires 1 argument"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"replace" => {
|
|
||||||
if args.len() == 2 {
|
|
||||||
let old = self.reg_load(args[0])?.to_string();
|
|
||||||
let new = self.reg_load(args[1])?.to_string();
|
|
||||||
Ok(VMValue::String(s.replace(&old, &new)))
|
|
||||||
} else {
|
|
||||||
Err(self.err_invalid("replace requires 2 arguments"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"indexOf" => {
|
|
||||||
if let Some(arg_id) = args.get(0) {
|
|
||||||
let needle = self.reg_load(*arg_id)?.to_string();
|
|
||||||
let idx = s.find(&needle).map(|i| i as i64).unwrap_or(-1);
|
|
||||||
Ok(VMValue::Integer(idx))
|
|
||||||
} else {
|
|
||||||
Err(self.err_invalid("indexOf requires 1 argument"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"lastIndexOf" => {
|
|
||||||
if let Some(arg_id) = args.get(0) {
|
|
||||||
let needle = self.reg_load(*arg_id)?.to_string();
|
|
||||||
let idx = s.rfind(&needle).map(|i| i as i64).unwrap_or(-1);
|
|
||||||
Ok(VMValue::Integer(idx))
|
|
||||||
} else {
|
|
||||||
Err(self.err_invalid("lastIndexOf requires 1 argument"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"substring" => {
|
|
||||||
let start = if let Some(a0) = args.get(0) {
|
|
||||||
self.reg_load(*a0)?.as_integer().unwrap_or(0)
|
|
||||||
} else { 0 };
|
|
||||||
let end = if let Some(a1) = args.get(1) {
|
|
||||||
self.reg_load(*a1)?.as_integer().unwrap_or(s.len() as i64)
|
|
||||||
} else { s.len() as i64 };
|
|
||||||
let len = s.len() as i64;
|
|
||||||
let i0 = start.max(0).min(len) as usize;
|
|
||||||
let i1 = end.max(0).min(len) as usize;
|
|
||||||
if i0 > i1 { return Ok(VMValue::String(String::new())); }
|
|
||||||
// Note: operating on bytes; Nyash strings are UTF‑8, but tests are ASCII only here
|
|
||||||
let bytes = s.as_bytes();
|
|
||||||
let sub = String::from_utf8(bytes[i0..i1].to_vec()).unwrap_or_default();
|
|
||||||
Ok(VMValue::String(sub))
|
|
||||||
}
|
|
||||||
_ => Err(self.err_method_not_found("String", method)),
|
|
||||||
},
|
|
||||||
VMValue::BoxRef(box_ref) => {
|
|
||||||
// Try builtin StringBox first
|
|
||||||
if let Some(string_box) = box_ref
|
|
||||||
.as_any()
|
|
||||||
.downcast_ref::<crate::boxes::string_box::StringBox>()
|
|
||||||
{
|
|
||||||
match method {
|
|
||||||
"lastIndexOf" => {
|
|
||||||
if let Some(arg_id) = args.get(0) {
|
|
||||||
let needle = self.reg_load(*arg_id)?.to_string();
|
|
||||||
let result_box = string_box.lastIndexOf(&needle);
|
|
||||||
Ok(VMValue::from_nyash_box(result_box))
|
|
||||||
} else {
|
|
||||||
Err(self.err_invalid("lastIndexOf requires 1 argument"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"indexOf" | "find" => {
|
|
||||||
if let Some(arg_id) = args.get(0) {
|
|
||||||
let needle = self.reg_load(*arg_id)?.to_string();
|
|
||||||
let result_box = string_box.find(&needle);
|
|
||||||
Ok(VMValue::from_nyash_box(result_box))
|
|
||||||
} else {
|
|
||||||
Err(self.err_invalid("indexOf/find requires 1 argument"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Err(self.err_method_not_found("StringBox", method)),
|
|
||||||
}
|
|
||||||
} else if let Some(p) = box_ref
|
|
||||||
.as_any()
|
|
||||||
.downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>()
|
|
||||||
{
|
|
||||||
let host = crate::runtime::plugin_loader_unified::get_global_plugin_host();
|
|
||||||
let host = host.read().unwrap();
|
|
||||||
let argv = self.load_args_as_boxes(args)?;
|
|
||||||
match host.invoke_instance_method(
|
|
||||||
&p.box_type,
|
|
||||||
method,
|
|
||||||
p.inner.instance_id,
|
|
||||||
&argv,
|
|
||||||
) {
|
|
||||||
Ok(Some(ret)) => Ok(VMValue::from_nyash_box(ret)),
|
|
||||||
Ok(None) => Ok(VMValue::Void),
|
|
||||||
Err(e) => Err(self.err_with_context(
|
|
||||||
&format!("Plugin method {}.{}", p.box_type, method),
|
|
||||||
&format!("{:?}", e)
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(self.err_method_not_found(&box_ref.type_name(), method))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Err(self.err_with_context("method call", &format!("{} not supported on {:?}", method, receiver))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: user-defined function dispatch for Global calls
|
|
||||||
// If none of the above extern/provider/global bridges matched,
|
|
||||||
// try to resolve and execute a user function present in this module.
|
|
||||||
{
|
|
||||||
// Use unique-tail resolver against snapshot of self.functions
|
|
||||||
let fname = call_resolution::resolve_function_name(
|
|
||||||
func_name,
|
|
||||||
args.len(),
|
|
||||||
&self.functions,
|
|
||||||
self.cur_fn.as_deref(),
|
|
||||||
);
|
|
||||||
if let Some(fname) = fname {
|
|
||||||
if let Some(func) = self.functions.get(&fname).cloned() {
|
|
||||||
// Load arguments and execute
|
|
||||||
let mut argv: Vec<VMValue> = Vec::new();
|
|
||||||
for a in args { argv.push(self.reg_load(*a)?); }
|
|
||||||
if std::env::var("NYASH_VM_CALL_TRACE").ok().as_deref() == Some("1") {
|
|
||||||
eprintln!("[vm] global-call resolved '{}' -> '{}'", func_name, fname);
|
|
||||||
}
|
|
||||||
return self.exec_function_inner(&func, Some(&argv));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_extern_function(
|
|
||||||
&mut self,
|
|
||||||
extern_name: &str,
|
|
||||||
args: &[ValueId],
|
|
||||||
) -> Result<VMValue, VMError> {
|
|
||||||
if let Some(res) = self.extern_provider_dispatch(extern_name, args) {
|
|
||||||
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") {
|
|
||||||
eprintln!("[hb:dispatch:calls] provider {}", extern_name);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
match extern_name {
|
|
||||||
// Minimal console externs
|
|
||||||
"nyash.console.log" | "env.console.log" | "print" | "nyash.builtin.print" => {
|
|
||||||
if let Some(arg_id) = args.get(0) {
|
|
||||||
let v = self.reg_load(*arg_id)?;
|
|
||||||
println!("{}", v.to_string());
|
|
||||||
} else {
|
|
||||||
println!("");
|
|
||||||
}
|
|
||||||
Ok(VMValue::Void)
|
|
||||||
}
|
|
||||||
// Direct provider calls (bypass hostbridge.extern_invoke)
|
|
||||||
// Above provider covers env.* family; keep legacy fallbacks below
|
|
||||||
"exit" => {
|
|
||||||
let code = if let Some(arg_id) = args.get(0) {
|
|
||||||
self.reg_load(*arg_id)?.as_integer().unwrap_or(0)
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
std::process::exit(code as i32);
|
|
||||||
}
|
|
||||||
"panic" => {
|
|
||||||
let msg = if let Some(arg_id) = args.get(0) {
|
|
||||||
self.reg_load(*arg_id)?.to_string()
|
|
||||||
} else {
|
|
||||||
"VM panic".to_string()
|
|
||||||
};
|
|
||||||
panic!("{}", msg);
|
|
||||||
}
|
|
||||||
"hostbridge.extern_invoke" => Err(self.err_invalid("hostbridge.extern_invoke should be routed via extern_provider_dispatch")),
|
|
||||||
_ => Err(self.err_with_context("extern function", &format!("Unknown: {}", extern_name))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
13
src/backend/mir_interpreter/handlers/calls/LAYER_GUARD.md
Normal file
13
src/backend/mir_interpreter/handlers/calls/LAYER_GUARD.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Layer Guard — handlers/calls
|
||||||
|
|
||||||
|
Scope
|
||||||
|
- Route MIR Call by callee kind (Global/Method/Extern/Legacy only).
|
||||||
|
- Keep legacy resolver isolated for phased removal.
|
||||||
|
|
||||||
|
Allowed
|
||||||
|
- Use `super::*` and `super::super::utils::*` helpers (e.g., `normalize_arity_suffix`).
|
||||||
|
|
||||||
|
Forbidden
|
||||||
|
- Direct provider/registry imports from runtime or plugins.
|
||||||
|
- Code generation or MIR building is out of scope.
|
||||||
|
|
||||||
27
src/backend/mir_interpreter/handlers/calls/README.md
Normal file
27
src/backend/mir_interpreter/handlers/calls/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# handlers/calls — Call Handling Layer
|
||||||
|
|
||||||
|
Purpose: isolate call handling by callee kind for the MIR interpreter.
|
||||||
|
|
||||||
|
- In scope: routing `handle_call`, per‑callee executors, legacy name resolution.
|
||||||
|
- Out of scope: arithmetic/box handlers, memory ops, extern provider registry.
|
||||||
|
|
||||||
|
Do not import sibling handler modules directly from here (keep boundaries tight).
|
||||||
|
Use `super::*` only and call through the interpreter methods.
|
||||||
|
|
||||||
|
Files
|
||||||
|
- `mod.rs`: entry point and callee routing
|
||||||
|
- `global.rs`: global function calls (Callee::Global)
|
||||||
|
- `method.rs`: instance/static method calls (Callee::Method)
|
||||||
|
- `externs.rs`: extern calls (Callee::Extern)
|
||||||
|
|
||||||
|
Removal status (Phase 2 complete)
|
||||||
|
- Unified callee path is default。by‑name 旧経路は削除済み(`legacy.rs`/`resolution.rs`)。
|
||||||
|
- `callee=None` の呼び出しは「厳密一致のモジュール関数名」以外は Fail‑Fast(ビルダーで Callee を付与してね)。
|
||||||
|
|
||||||
|
Extern SSOT
|
||||||
|
- `externs.rs` is the runtime SSOT for provider dispatch. Global calls that are extern-like should delegate here (e.g., `env.get`).
|
||||||
|
- Arity suffix normalization: names like `env.get/1` are accepted and normalized to `env.get` before dispatch (both in Global and ExternCall paths).
|
||||||
|
|
||||||
|
Layer Guard
|
||||||
|
- Scope: call routing only (Global/Method/Extern/Legacy isolation). Do not import provider registries or runtime plugins directly from here.
|
||||||
|
- Use helpers under `super::super::utils::*` for shared concerns (e.g., `normalize_arity_suffix`).
|
||||||
63
src/backend/mir_interpreter/handlers/calls/externs.rs
Normal file
63
src/backend/mir_interpreter/handlers/calls/externs.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl MirInterpreter {
|
||||||
|
pub(super) fn execute_extern_function(
|
||||||
|
&mut self,
|
||||||
|
extern_name: &str,
|
||||||
|
args: &[ValueId],
|
||||||
|
) -> Result<VMValue, VMError> {
|
||||||
|
// Normalize arity suffix (e.g., "env.get/1" -> "env.get")
|
||||||
|
let base = super::super::utils::normalize_arity_suffix(extern_name);
|
||||||
|
if let Some(res) = self.extern_provider_dispatch(base, args) {
|
||||||
|
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[hb:dispatch:calls] provider {}", base);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
match base {
|
||||||
|
// Minimal console externs
|
||||||
|
"nyash.console.log" | "env.console.log" | "print" | "nyash.builtin.print" => {
|
||||||
|
if let Some(arg_id) = args.get(0) {
|
||||||
|
let v = self.reg_load(*arg_id)?;
|
||||||
|
match &v {
|
||||||
|
VMValue::Void => println!("null"),
|
||||||
|
VMValue::BoxRef(bx) => {
|
||||||
|
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||||
|
println!("null");
|
||||||
|
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||||
|
println!("{}", sb.value);
|
||||||
|
} else {
|
||||||
|
println!("{}", v.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VMValue::String(s) => println!("{}", s),
|
||||||
|
_ => println!("{}", v.to_string()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
Ok(VMValue::Void)
|
||||||
|
}
|
||||||
|
// Direct provider calls (bypass hostbridge.extern_invoke)
|
||||||
|
// Above provider covers env.* family; keep legacy fallbacks below
|
||||||
|
"exit" => {
|
||||||
|
let code = if let Some(arg_id) = args.get(0) {
|
||||||
|
self.reg_load(*arg_id)?.as_integer().unwrap_or(0)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
std::process::exit(code as i32);
|
||||||
|
}
|
||||||
|
"panic" => {
|
||||||
|
let msg = if let Some(arg_id) = args.get(0) {
|
||||||
|
self.reg_load(*arg_id)?.to_string()
|
||||||
|
} else {
|
||||||
|
"VM panic".to_string()
|
||||||
|
};
|
||||||
|
panic!("{}", msg);
|
||||||
|
}
|
||||||
|
"hostbridge.extern_invoke" => Err(self.err_invalid("hostbridge.extern_invoke should be routed via extern_provider_dispatch")),
|
||||||
|
_ => Err(self.err_with_context("extern function", &format!("Unknown: {}", extern_name))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
115
src/backend/mir_interpreter/handlers/calls/global.rs
Normal file
115
src/backend/mir_interpreter/handlers/calls/global.rs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl MirInterpreter {
|
||||||
|
pub(super) fn execute_global_function(
|
||||||
|
&mut self,
|
||||||
|
func_name: &str,
|
||||||
|
args: &[ValueId],
|
||||||
|
) -> Result<VMValue, VMError> {
|
||||||
|
// Normalize arity suffix for extern-like dispatch, but keep original name
|
||||||
|
// for module-local function table lookup (functions may carry arity suffix).
|
||||||
|
let base = super::super::utils::normalize_arity_suffix(func_name);
|
||||||
|
// Module-local/global function: execute by function table if present (use original name)
|
||||||
|
if let Some(func) = self.functions.get(func_name).cloned() {
|
||||||
|
let mut argv: Vec<VMValue> = Vec::with_capacity(args.len());
|
||||||
|
for a in args { argv.push(self.reg_load(*a)?); }
|
||||||
|
return self.exec_function_inner(&func, Some(&argv));
|
||||||
|
}
|
||||||
|
|
||||||
|
match base {
|
||||||
|
// Console-like globals
|
||||||
|
"print" | "nyash.builtin.print" => {
|
||||||
|
// Reuse extern handler for consistency with other console names
|
||||||
|
return self.execute_extern_function("print", args);
|
||||||
|
}
|
||||||
|
"error" => {
|
||||||
|
if let Some(arg_id) = args.get(0) {
|
||||||
|
let val = self.reg_load(*arg_id)?;
|
||||||
|
eprintln!("Error: {}", val.to_string());
|
||||||
|
}
|
||||||
|
return Ok(VMValue::Void);
|
||||||
|
}
|
||||||
|
"panic" => {
|
||||||
|
return self.execute_extern_function("panic", args);
|
||||||
|
}
|
||||||
|
"exit" => {
|
||||||
|
return self.execute_extern_function("exit", args);
|
||||||
|
}
|
||||||
|
"env.get" => {
|
||||||
|
// Route env.get global to extern handler
|
||||||
|
return self.execute_extern_function("env.get", args);
|
||||||
|
}
|
||||||
|
"hostbridge.extern_invoke" => {
|
||||||
|
// SSOT: delegate to extern dispatcher (provider)
|
||||||
|
return self.execute_extern_function("hostbridge.extern_invoke", args);
|
||||||
|
}
|
||||||
|
// LLVM harness providers (direct)
|
||||||
|
"env.mirbuilder.emit" | "env.mirbuilder.emit/1" => {
|
||||||
|
if let Some(a0) = args.get(0) {
|
||||||
|
let s = self.reg_load(*a0)?.to_string();
|
||||||
|
match crate::host_providers::mir_builder::program_json_to_mir_json(&s) {
|
||||||
|
Ok(out) => Ok(VMValue::String(out)),
|
||||||
|
Err(e) => Err(self.err_with_context("env.mirbuilder.emit", &e.to_string())),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(self.err_invalid("env.mirbuilder.emit expects 1 arg"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"env.codegen.emit_object" | "env.codegen.emit_object/1" => {
|
||||||
|
if let Some(a0) = args.get(0) {
|
||||||
|
let s = self.reg_load(*a0)?.to_string();
|
||||||
|
let opts = crate::host_providers::llvm_codegen::Opts {
|
||||||
|
out: None,
|
||||||
|
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from),
|
||||||
|
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok().or(Some("0".to_string())),
|
||||||
|
timeout_ms: None,
|
||||||
|
};
|
||||||
|
match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts) {
|
||||||
|
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
|
||||||
|
Err(e) => Err(self.err_with_context("env.codegen.emit_object", &e.to_string())),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(self.err_invalid("env.codegen.emit_object expects 1 arg"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"env.codegen.link_object" | "env.codegen.link_object/3" => {
|
||||||
|
// C-API route only; args[2] is expected to be an ArrayBox [obj_path, exe_out?]
|
||||||
|
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
|
||||||
|
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
|
||||||
|
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
|
||||||
|
}
|
||||||
|
if args.len() < 3 { return Err(self.err_arg_count("env.codegen.link_object", 3, args.len())); }
|
||||||
|
let v = self.reg_load(args[2])?;
|
||||||
|
let (obj_path, exe_out) = match v {
|
||||||
|
VMValue::BoxRef(b) => {
|
||||||
|
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
|
||||||
|
let elem0 = ab.get(idx0).to_string_box().value;
|
||||||
|
let mut exe: Option<String> = None;
|
||||||
|
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
|
||||||
|
let e1 = ab.get(idx1).to_string_box().value;
|
||||||
|
if !e1.is_empty() { exe = Some(e1); }
|
||||||
|
(elem0, exe)
|
||||||
|
} else { (b.to_string_box().value, None) }
|
||||||
|
}
|
||||||
|
_ => (v.to_string(), None),
|
||||||
|
};
|
||||||
|
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
|
||||||
|
let obj = std::path::PathBuf::from(obj_path);
|
||||||
|
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
||||||
|
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
|
||||||
|
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
|
||||||
|
Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"nyash.builtin.error" => {
|
||||||
|
if let Some(arg_id) = args.get(0) {
|
||||||
|
let val = self.reg_load(*arg_id)?;
|
||||||
|
eprintln!("Error: {}", val.to_string());
|
||||||
|
}
|
||||||
|
Ok(VMValue::Void)
|
||||||
|
}
|
||||||
|
_ => Err(self.err_with_context("global function", &format!("Unknown: {}", func_name))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
234
src/backend/mir_interpreter/handlers/calls/method.rs
Normal file
234
src/backend/mir_interpreter/handlers/calls/method.rs
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl MirInterpreter {
|
||||||
|
pub(super) fn execute_method_callee(
|
||||||
|
&mut self,
|
||||||
|
box_name: &str,
|
||||||
|
method: &str,
|
||||||
|
receiver: &Option<ValueId>,
|
||||||
|
args: &[ValueId],
|
||||||
|
) -> Result<VMValue, VMError> {
|
||||||
|
if let Some(recv_id) = receiver {
|
||||||
|
// Primary: load receiver by id. If undefined due to builder localization gap,
|
||||||
|
// try to auto-locate the most recent `NewBox <box_name>` in the current block
|
||||||
|
// (same fn/last_block) and use its dst as the receiver. This is a structural
|
||||||
|
// recovery, not a by-name fallback, and keeps semantics stable for plugin boxes.
|
||||||
|
let recv_val = match self.reg_load(*recv_id) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
// Attempt structured autoscan for receiver in current block
|
||||||
|
if let (Some(cur_fn), Some(bb)) = (self.cur_fn.clone(), self.last_block) {
|
||||||
|
if let Some(func) = self.functions.get(&cur_fn) {
|
||||||
|
if let Some(block) = func.blocks.get(&bb) {
|
||||||
|
let mut last_recv: Option<ValueId> = None;
|
||||||
|
for inst in &block.instructions {
|
||||||
|
if let crate::mir::MirInstruction::NewBox { dst, box_type, .. } = inst {
|
||||||
|
if box_type == box_name { last_recv = Some(*dst); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(rid) = last_recv {
|
||||||
|
if let Ok(v) = self.reg_load(rid) { v } else { return Err(e); }
|
||||||
|
} else {
|
||||||
|
// Dev fallback (guarded): use args[0] as surrogate receiver if explicitly allowed
|
||||||
|
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref() == Some("1")
|
||||||
|
|| std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1");
|
||||||
|
if tolerate { if let Some(a0) = args.get(0) { self.reg_load(*a0)? } else { return Err(e); } }
|
||||||
|
else { return Err(e); }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Dev fallback (guarded): use args[0] as surrogate receiver if explicitly allowed
|
||||||
|
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref() == Some("1")
|
||||||
|
|| std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1");
|
||||||
|
if tolerate { if let Some(a0) = args.get(0) { self.reg_load(*a0)? } else { return Err(e); } }
|
||||||
|
else { return Err(e); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let dev_trace = std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1");
|
||||||
|
// Fast bridge for builtin boxes (Array) and common methods.
|
||||||
|
// Preserve legacy semantics when plugins are absent.
|
||||||
|
if let VMValue::BoxRef(bx) = &recv_val {
|
||||||
|
// ArrayBox bridge
|
||||||
|
if let Some(arr) = bx.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||||
|
match method {
|
||||||
|
"birth" => { return Ok(VMValue::Void); }
|
||||||
|
"push" => {
|
||||||
|
if let Some(a0) = args.get(0) {
|
||||||
|
let v = self.load_as_box(*a0)?;
|
||||||
|
let _ = arr.push(v);
|
||||||
|
return Ok(VMValue::Void);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"len" | "length" | "size" => {
|
||||||
|
let ret = arr.length();
|
||||||
|
return Ok(VMValue::from_nyash_box(ret));
|
||||||
|
}
|
||||||
|
"get" => {
|
||||||
|
if let Some(a0) = args.get(0) {
|
||||||
|
let idx = self.load_as_box(*a0)?;
|
||||||
|
let ret = arr.get(idx);
|
||||||
|
return Ok(VMValue::from_nyash_box(ret));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"set" => {
|
||||||
|
if args.len() >= 2 {
|
||||||
|
let idx = self.load_as_box(args[0])?;
|
||||||
|
let val = self.load_as_box(args[1])?;
|
||||||
|
let _ = arr.set(idx, val);
|
||||||
|
return Ok(VMValue::Void);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Minimal bridge for birth(): delegate to BoxCall handler and return Void
|
||||||
|
if method == "birth" {
|
||||||
|
let _ = self.handle_box_call(None, *recv_id, &method.to_string(), args)?;
|
||||||
|
return Ok(VMValue::Void);
|
||||||
|
}
|
||||||
|
let is_kw = method == "keyword_to_token_type";
|
||||||
|
if dev_trace && is_kw {
|
||||||
|
let a0 = args.get(0).and_then(|id| self.reg_load(*id).ok());
|
||||||
|
eprintln!("[vm-trace] mcall {} argv0={:?}", method, a0);
|
||||||
|
}
|
||||||
|
let out = self.execute_method_call(&recv_val, method, args)?;
|
||||||
|
if dev_trace && is_kw {
|
||||||
|
eprintln!("[vm-trace] mret {} -> {:?}", method, out);
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
} else {
|
||||||
|
// Receiver not provided: try static singleton instance for the box (methodize PoC fallback)
|
||||||
|
if self.static_box_decls.contains_key(box_name) {
|
||||||
|
let instance = self.ensure_static_box_instance(box_name)?;
|
||||||
|
let recv_val = VMValue::from_nyash_box(Box::new(instance.clone()));
|
||||||
|
return self.execute_method_call(&recv_val, method, args);
|
||||||
|
}
|
||||||
|
Err(self.err_with_context("Method call", &format!("missing receiver for {}", method)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_method_call(
|
||||||
|
&mut self,
|
||||||
|
receiver: &VMValue,
|
||||||
|
method: &str,
|
||||||
|
args: &[ValueId],
|
||||||
|
) -> Result<VMValue, VMError> {
|
||||||
|
match receiver {
|
||||||
|
VMValue::String(s) => match method {
|
||||||
|
"length" => Ok(VMValue::Integer(s.len() as i64)),
|
||||||
|
"concat" => {
|
||||||
|
if let Some(arg_id) = args.get(0) {
|
||||||
|
let arg_val = self.reg_load(*arg_id)?;
|
||||||
|
let new_str = format!("{}{}", s, arg_val.to_string());
|
||||||
|
Ok(VMValue::String(new_str))
|
||||||
|
} else {
|
||||||
|
Err(self.err_invalid("concat requires 1 argument"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"replace" => {
|
||||||
|
if args.len() == 2 {
|
||||||
|
let old = self.reg_load(args[0])?.to_string();
|
||||||
|
let new = self.reg_load(args[1])?.to_string();
|
||||||
|
Ok(VMValue::String(s.replace(&old, &new)))
|
||||||
|
} else {
|
||||||
|
Err(self.err_invalid("replace requires 2 arguments"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"indexOf" => {
|
||||||
|
if let Some(arg_id) = args.get(0) {
|
||||||
|
let needle = self.reg_load(*arg_id)?.to_string();
|
||||||
|
let idx = s.find(&needle).map(|i| i as i64).unwrap_or(-1);
|
||||||
|
Ok(VMValue::Integer(idx))
|
||||||
|
} else {
|
||||||
|
Err(self.err_invalid("indexOf requires 1 argument"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"lastIndexOf" => {
|
||||||
|
if let Some(arg_id) = args.get(0) {
|
||||||
|
let needle = self.reg_load(*arg_id)?.to_string();
|
||||||
|
let idx = s.rfind(&needle).map(|i| i as i64).unwrap_or(-1);
|
||||||
|
Ok(VMValue::Integer(idx))
|
||||||
|
} else {
|
||||||
|
Err(self.err_invalid("lastIndexOf requires 1 argument"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"substring" => {
|
||||||
|
let start = if let Some(a0) = args.get(0) {
|
||||||
|
self.reg_load(*a0)?.as_integer().unwrap_or(0)
|
||||||
|
} else { 0 };
|
||||||
|
let end = if let Some(a1) = args.get(1) {
|
||||||
|
self.reg_load(*a1)?.as_integer().unwrap_or(s.len() as i64)
|
||||||
|
} else { s.len() as i64 };
|
||||||
|
let len = s.len() as i64;
|
||||||
|
let i0 = start.max(0).min(len) as usize;
|
||||||
|
let i1 = end.max(0).min(len) as usize;
|
||||||
|
if i0 > i1 { return Ok(VMValue::String(String::new())); }
|
||||||
|
// Note: operating on bytes; Nyash strings are UTF‑8, but tests are ASCII only here
|
||||||
|
let bytes = s.as_bytes();
|
||||||
|
let sub = String::from_utf8(bytes[i0..i1].to_vec()).unwrap_or_default();
|
||||||
|
Ok(VMValue::String(sub))
|
||||||
|
}
|
||||||
|
_ => Err(self.err_method_not_found("String", method)),
|
||||||
|
},
|
||||||
|
VMValue::BoxRef(box_ref) => {
|
||||||
|
// Try builtin StringBox first
|
||||||
|
if let Some(string_box) = box_ref
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<crate::boxes::string_box::StringBox>()
|
||||||
|
{
|
||||||
|
match method {
|
||||||
|
"lastIndexOf" => {
|
||||||
|
if let Some(arg_id) = args.get(0) {
|
||||||
|
let needle = self.reg_load(*arg_id)?.to_string();
|
||||||
|
let result_box = string_box.lastIndexOf(&needle);
|
||||||
|
Ok(VMValue::from_nyash_box(result_box))
|
||||||
|
} else {
|
||||||
|
Err(self.err_invalid("lastIndexOf requires 1 argument"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"indexOf" | "find" => {
|
||||||
|
if let Some(arg_id) = args.get(0) {
|
||||||
|
let needle = self.reg_load(*arg_id)?.to_string();
|
||||||
|
let result_box = string_box.find(&needle);
|
||||||
|
Ok(VMValue::from_nyash_box(result_box))
|
||||||
|
} else {
|
||||||
|
Err(self.err_invalid("indexOf/find requires 1 argument"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(self.err_method_not_found("StringBox", method)),
|
||||||
|
}
|
||||||
|
} else if let Some(p) = box_ref
|
||||||
|
.as_any()
|
||||||
|
.downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>()
|
||||||
|
{
|
||||||
|
let host = crate::runtime::plugin_loader_unified::get_global_plugin_host();
|
||||||
|
let host = host.read().unwrap();
|
||||||
|
let argv = self.load_args_as_boxes(args)?;
|
||||||
|
match host.invoke_instance_method(
|
||||||
|
&p.box_type,
|
||||||
|
method,
|
||||||
|
p.inner.instance_id,
|
||||||
|
&argv,
|
||||||
|
) {
|
||||||
|
Ok(Some(ret)) => Ok(VMValue::from_nyash_box(ret)),
|
||||||
|
Ok(None) => Ok(VMValue::Void),
|
||||||
|
Err(e) => Err(self.err_with_context(
|
||||||
|
&format!("Plugin method {}.{}", p.box_type, method),
|
||||||
|
&format!("{:?}", e)
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(self.err_method_not_found(&box_ref.type_name(), method))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Err(self.err_with_context("method call", &format!("{} not supported on {:?}", method, receiver))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
83
src/backend/mir_interpreter/handlers/calls/mod.rs
Normal file
83
src/backend/mir_interpreter/handlers/calls/mod.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
//! Call handling (split from handlers/calls.rs)
|
||||||
|
//! - Route by Callee kind
|
||||||
|
//! - Keep legacy path isolated for phased removal
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
mod global;
|
||||||
|
mod method;
|
||||||
|
mod externs;
|
||||||
|
// legacy by-name resolver has been removed (Phase 2 complete)
|
||||||
|
|
||||||
|
impl MirInterpreter {
|
||||||
|
pub(super) fn handle_call(
|
||||||
|
&mut self,
|
||||||
|
dst: Option<ValueId>,
|
||||||
|
func: ValueId,
|
||||||
|
callee: Option<&Callee>,
|
||||||
|
args: &[ValueId],
|
||||||
|
) -> Result<(), VMError> {
|
||||||
|
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
match callee {
|
||||||
|
Some(Callee::Global(n)) => eprintln!("[hb:path] call Callee::Global {} argc={}", n, args.len()),
|
||||||
|
Some(Callee::Method{ box_name, method, ..}) => eprintln!("[hb:path] call Callee::Method {}.{} argc={}", box_name, method, args.len()),
|
||||||
|
Some(Callee::Constructor{ box_type }) => eprintln!("[hb:path] call Callee::Constructor {} argc={}", box_type, args.len()),
|
||||||
|
Some(Callee::Closure{ .. }) => eprintln!("[hb:path] call Callee::Closure argc={}", args.len()),
|
||||||
|
Some(Callee::Value(_)) => eprintln!("[hb:path] call Callee::Value argc={}", args.len()),
|
||||||
|
Some(Callee::Extern(n)) => eprintln!("[hb:path] call Callee::Extern {} argc={}", n, args.len()),
|
||||||
|
None => eprintln!("[hb:path] call Legacy func_id={:?} argc={}", func, args.len()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SSOT fast-path: route hostbridge.extern_invoke to extern dispatcher regardless of resolution form
|
||||||
|
if let Some(Callee::Global(func_name)) = callee {
|
||||||
|
if func_name == "hostbridge.extern_invoke" || func_name.starts_with("hostbridge.extern_invoke/") {
|
||||||
|
let v = self.execute_extern_function("hostbridge.extern_invoke", args)?;
|
||||||
|
self.write_result(dst, v);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let call_result = if let Some(callee_type) = callee {
|
||||||
|
self.execute_callee_call(callee_type, args)?
|
||||||
|
} else {
|
||||||
|
// Fast path: allow exact module function calls without legacy resolver.
|
||||||
|
let name_val = self.reg_load(func)?;
|
||||||
|
if let VMValue::String(ref s) = name_val {
|
||||||
|
if let Some(f) = self.functions.get(s).cloned() {
|
||||||
|
let mut argv: Vec<VMValue> = Vec::with_capacity(args.len());
|
||||||
|
for a in args { argv.push(self.reg_load(*a)?); }
|
||||||
|
self.exec_function_inner(&f, Some(&argv))?
|
||||||
|
} else {
|
||||||
|
return Err(self.err_with_context("call", &format!(
|
||||||
|
"unknown function '{}' (by-name calls unsupported). attach Callee in builder or define the function",
|
||||||
|
s
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(self.err_with_context("call", "by-name calls unsupported without Callee attachment"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.write_result(dst, call_result);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn execute_callee_call(
|
||||||
|
&mut self,
|
||||||
|
callee: &Callee,
|
||||||
|
args: &[ValueId],
|
||||||
|
) -> Result<VMValue, VMError> {
|
||||||
|
match callee {
|
||||||
|
Callee::Global(func_name) => self.execute_global_function(func_name, args),
|
||||||
|
Callee::Method { box_name, method, receiver, certainty: _ } => {
|
||||||
|
self.execute_method_callee(box_name, method, receiver, args)
|
||||||
|
}
|
||||||
|
Callee::Constructor { box_type } => Err(self.err_unsupported(&format!("Constructor calls for {}", box_type))),
|
||||||
|
Callee::Closure { .. } => Err(self.err_unsupported("Closure creation in VM")),
|
||||||
|
Callee::Value(func_val_id) => {
|
||||||
|
let _ = self.reg_load(*func_val_id)?;
|
||||||
|
Err(self.err_unsupported("First-class function calls in VM"))
|
||||||
|
}
|
||||||
|
Callee::Extern(extern_name) => self.execute_extern_function(extern_name, args),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use super::super::utils::*;
|
use crate::backend::mir_interpreter::utils::error_helpers::ErrorBuilder;
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
impl MirInterpreter {
|
impl MirInterpreter {
|
||||||
@ -56,18 +56,81 @@ impl MirInterpreter {
|
|||||||
match extern_name {
|
match extern_name {
|
||||||
// Console family (minimal)
|
// Console family (minimal)
|
||||||
"nyash.console.log" | "env.console.log" | "print" | "nyash.builtin.print" => {
|
"nyash.console.log" | "env.console.log" | "print" | "nyash.builtin.print" => {
|
||||||
let s = if let Some(a0) = args.get(0) { self.reg_load(*a0).ok() } else { None };
|
if let Some(a0) = args.get(0) {
|
||||||
if let Some(v) = s { println!("{}", v.to_string()); } else { println!(""); }
|
let v = self.reg_load(*a0).ok();
|
||||||
|
if let Some(v) = v {
|
||||||
|
match &v {
|
||||||
|
VMValue::Void => println!("null"),
|
||||||
|
VMValue::String(s) => println!("{}", s),
|
||||||
|
VMValue::BoxRef(bx) => {
|
||||||
|
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||||
|
println!("null");
|
||||||
|
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||||
|
println!("{}", sb.value);
|
||||||
|
} else {
|
||||||
|
println!("{}", v.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => println!("{}", v.to_string()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
Some(Ok(VMValue::Void))
|
Some(Ok(VMValue::Void))
|
||||||
}
|
}
|
||||||
"env.console.warn" | "nyash.console.warn" => {
|
"env.console.warn" | "nyash.console.warn" => {
|
||||||
let s = if let Some(a0) = args.get(0) { self.reg_load(*a0).ok() } else { None };
|
if let Some(a0) = args.get(0) {
|
||||||
if let Some(v) = s { eprintln!("[warn] {}", v.to_string()); } else { eprintln!("[warn]"); }
|
let v = self.reg_load(*a0).ok();
|
||||||
|
if let Some(v) = v {
|
||||||
|
match &v {
|
||||||
|
VMValue::Void => eprintln!("[warn] null"),
|
||||||
|
VMValue::String(s) => eprintln!("[warn] {}", s),
|
||||||
|
VMValue::BoxRef(bx) => {
|
||||||
|
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||||
|
eprintln!("[warn] null");
|
||||||
|
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||||
|
eprintln!("[warn] {}", sb.value);
|
||||||
|
} else {
|
||||||
|
eprintln!("[warn] {}", v.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => eprintln!("[warn] {}", v.to_string()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("[warn]");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("[warn]");
|
||||||
|
}
|
||||||
Some(Ok(VMValue::Void))
|
Some(Ok(VMValue::Void))
|
||||||
}
|
}
|
||||||
"env.console.error" | "nyash.console.error" => {
|
"env.console.error" | "nyash.console.error" => {
|
||||||
let s = if let Some(a0) = args.get(0) { self.reg_load(*a0).ok() } else { None };
|
if let Some(a0) = args.get(0) {
|
||||||
if let Some(v) = s { eprintln!("[error] {}", v.to_string()); } else { eprintln!("[error]"); }
|
let v = self.reg_load(*a0).ok();
|
||||||
|
if let Some(v) = v {
|
||||||
|
match &v {
|
||||||
|
VMValue::Void => eprintln!("[error] null"),
|
||||||
|
VMValue::String(s) => eprintln!("[error] {}", s),
|
||||||
|
VMValue::BoxRef(bx) => {
|
||||||
|
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||||
|
eprintln!("[error] null");
|
||||||
|
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||||
|
eprintln!("[error] {}", sb.value);
|
||||||
|
} else {
|
||||||
|
eprintln!("[error] {}", v.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => eprintln!("[error] {}", v.to_string()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("[error]");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eprintln!("[error]");
|
||||||
|
}
|
||||||
Some(Ok(VMValue::Void))
|
Some(Ok(VMValue::Void))
|
||||||
}
|
}
|
||||||
// Extern providers (env.mirbuilder / env.codegen)
|
// Extern providers (env.mirbuilder / env.codegen)
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use super::super::utils::*;
|
use serde_json::Value as JsonValue;
|
||||||
use serde_json::{Value as JsonValue, Map as JsonMap};
|
|
||||||
|
|
||||||
impl MirInterpreter {
|
impl MirInterpreter {
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -25,7 +24,9 @@ impl MirInterpreter {
|
|||||||
method: &str,
|
method: &str,
|
||||||
args: &[ValueId],
|
args: &[ValueId],
|
||||||
) -> Result<(), VMError> {
|
) -> Result<(), VMError> {
|
||||||
match (iface, method) {
|
// Normalize method arity suffix (e.g., get/1 -> get)
|
||||||
|
let mbase = super::super::utils::normalize_arity_suffix(method);
|
||||||
|
match (iface, mbase) {
|
||||||
("env", "get") => {
|
("env", "get") => {
|
||||||
if let Some(a0) = args.get(0) {
|
if let Some(a0) = args.get(0) {
|
||||||
let key = self.reg_load(*a0)?.to_string();
|
let key = self.reg_load(*a0)?.to_string();
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use super::super::utils::*;
|
|
||||||
|
|
||||||
impl MirInterpreter {
|
impl MirInterpreter {
|
||||||
pub(super) fn handle_ref_set(
|
pub(super) fn handle_ref_set(
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use super::super::utils::*;
|
|
||||||
|
|
||||||
impl MirInterpreter {
|
impl MirInterpreter {
|
||||||
pub(super) fn handle_debug(&mut self, message: &str, value: ValueId) -> Result<(), VMError> {
|
pub(super) fn handle_debug(&mut self, message: &str, value: ValueId) -> Result<(), VMError> {
|
||||||
|
|||||||
@ -20,7 +20,6 @@ mod boxes_object_fields;
|
|||||||
mod boxes_instance;
|
mod boxes_instance;
|
||||||
mod boxes_plugin;
|
mod boxes_plugin;
|
||||||
mod boxes_void_guards;
|
mod boxes_void_guards;
|
||||||
mod call_resolution;
|
|
||||||
mod calls;
|
mod calls;
|
||||||
mod externals;
|
mod externals;
|
||||||
mod extern_provider;
|
mod extern_provider;
|
||||||
|
|||||||
@ -5,11 +5,8 @@ pub mod arg_validation;
|
|||||||
pub mod receiver_helpers;
|
pub mod receiver_helpers;
|
||||||
pub mod error_helpers;
|
pub mod error_helpers;
|
||||||
pub mod conversion_helpers;
|
pub mod conversion_helpers;
|
||||||
|
pub mod naming;
|
||||||
// Phase 21.2: adapter_dev removed - all adapter functions now in .hako implementation
|
// Phase 21.2: adapter_dev removed - all adapter functions now in .hako implementation
|
||||||
|
|
||||||
// Re-export for convenience
|
// Selective re-export (only naming is widely used via utils::normalize_arity_suffix)
|
||||||
pub use destination_helpers::*;
|
pub use naming::*;
|
||||||
pub use arg_validation::*;
|
|
||||||
pub use receiver_helpers::*;
|
|
||||||
pub use error_helpers::*;
|
|
||||||
pub use conversion_helpers::*;
|
|
||||||
|
|||||||
15
src/backend/mir_interpreter/utils/naming.rs
Normal file
15
src/backend/mir_interpreter/utils/naming.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
//! Naming helpers shared in MIR Interpreter handlers.
|
||||||
|
|
||||||
|
/// Normalize an optional arity suffix from a function/method name.
|
||||||
|
/// Examples:
|
||||||
|
/// - "env.get/1" -> "env.get"
|
||||||
|
/// - "hostbridge.extern_invoke/3" -> "hostbridge.extern_invoke"
|
||||||
|
/// - "print" -> "print"
|
||||||
|
#[inline]
|
||||||
|
pub fn normalize_arity_suffix(name: &str) -> &str {
|
||||||
|
match name.split_once('/') {
|
||||||
|
Some((base, _)) => base,
|
||||||
|
None => name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -133,6 +133,10 @@ pub fn verify_allow_no_phi() -> bool {
|
|||||||
/// and the merge block itself must not introduce a self-copy to the merged destination.
|
/// and the merge block itself must not introduce a self-copy to the merged destination.
|
||||||
pub fn verify_edge_copy_strict() -> bool { env_bool("NYASH_VERIFY_EDGE_COPY_STRICT") }
|
pub fn verify_edge_copy_strict() -> bool { env_bool("NYASH_VERIFY_EDGE_COPY_STRICT") }
|
||||||
|
|
||||||
|
/// Enforce purity of return blocks: no side-effecting instructions allowed before Return
|
||||||
|
/// Default: OFF. Enable with NYASH_VERIFY_RET_PURITY=1 in dev/profiling sessions.
|
||||||
|
pub fn verify_ret_purity() -> bool { env_bool("NYASH_VERIFY_RET_PURITY") }
|
||||||
|
|
||||||
// ---- LLVM harness toggle (llvmlite) ----
|
// ---- LLVM harness toggle (llvmlite) ----
|
||||||
pub fn llvm_use_harness() -> bool {
|
pub fn llvm_use_harness() -> bool {
|
||||||
// Phase 15: デフォルトON(LLVMバックエンドはPythonハーネス使用)
|
// Phase 15: デフォルトON(LLVMバックエンドはPythonハーネス使用)
|
||||||
@ -170,6 +174,8 @@ pub fn env_bool_default(key: &str, default: bool) -> bool {
|
|||||||
/// Set NYASH_FAIL_FAST=0 to temporarily allow legacy fallbacks during bring-up.
|
/// Set NYASH_FAIL_FAST=0 to temporarily allow legacy fallbacks during bring-up.
|
||||||
pub fn fail_fast() -> bool { env_bool_default("NYASH_FAIL_FAST", true) }
|
pub fn fail_fast() -> bool { env_bool_default("NYASH_FAIL_FAST", true) }
|
||||||
|
|
||||||
|
// VM legacy by-name call fallback was removed (Phase 2 complete).
|
||||||
|
|
||||||
// ---- Phase 11.8 MIR cleanup toggles ----
|
// ---- Phase 11.8 MIR cleanup toggles ----
|
||||||
/// Core-13 minimal MIR mode toggle
|
/// Core-13 minimal MIR mode toggle
|
||||||
/// Default: ON (unless explicitly disabled with NYASH_MIR_CORE13=0)
|
/// Default: ON (unless explicitly disabled with NYASH_MIR_CORE13=0)
|
||||||
@ -226,6 +232,11 @@ pub fn opt_diag_fail() -> bool {
|
|||||||
std::env::var("NYASH_OPT_DIAG_FAIL").is_ok()
|
std::env::var("NYASH_OPT_DIAG_FAIL").is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Legacy compatibility (dev-only) ----
|
||||||
|
/// Enable legacy InstanceBox fields (SharedNyashBox map) for compatibility.
|
||||||
|
/// Default: OFF. Set NYASH_LEGACY_FIELDS_ENABLE=1 to materialize and use legacy fields.
|
||||||
|
pub fn legacy_fields_enable() -> bool { env_bool("NYASH_LEGACY_FIELDS_ENABLE") }
|
||||||
|
|
||||||
// ---- GC/Runtime tracing (execution-affecting visibility) ----
|
// ---- GC/Runtime tracing (execution-affecting visibility) ----
|
||||||
pub fn gc_trace() -> bool { env_bool("NYASH_GC_TRACE") }
|
pub fn gc_trace() -> bool { env_bool("NYASH_GC_TRACE") }
|
||||||
pub fn gc_barrier_trace() -> bool { env_bool("NYASH_GC_BARRIER_TRACE") }
|
pub fn gc_barrier_trace() -> bool { env_bool("NYASH_GC_BARRIER_TRACE") }
|
||||||
|
|||||||
@ -99,6 +99,7 @@ impl InstanceBox {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let legacy_enabled = crate::config::env::legacy_fields_enable();
|
||||||
Self {
|
Self {
|
||||||
class_name,
|
class_name,
|
||||||
fields_ng: Arc::new(Mutex::new(field_map)),
|
fields_ng: Arc::new(Mutex::new(field_map)),
|
||||||
@ -106,8 +107,8 @@ impl InstanceBox {
|
|||||||
inner_content: None, // ユーザー定義は内包Boxなし
|
inner_content: None, // ユーザー定義は内包Boxなし
|
||||||
base: BoxBase::new(),
|
base: BoxBase::new(),
|
||||||
finalized: Arc::new(Mutex::new(false)),
|
finalized: Arc::new(Mutex::new(false)),
|
||||||
// レガシー互換フィールド
|
// レガシー互換フィールド(既定OFF)
|
||||||
fields: Some(Arc::new(Mutex::new(legacy_field_map))),
|
fields: if legacy_enabled { Some(Arc::new(Mutex::new(legacy_field_map))) } else { None },
|
||||||
init_field_order: fields,
|
init_field_order: fields,
|
||||||
weak_fields_union: std::collections::HashSet::new(),
|
weak_fields_union: std::collections::HashSet::new(),
|
||||||
in_finalization: Arc::new(Mutex::new(false)),
|
in_finalization: Arc::new(Mutex::new(false)),
|
||||||
@ -233,7 +234,8 @@ impl InstanceBox {
|
|||||||
fields.lock().unwrap().insert(field_name, arc_box);
|
fields.lock().unwrap().insert(field_name, arc_box);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err("Legacy fields not initialized".to_string())
|
// 既定OFFのため何もしない(互換不要)。必要なら NYASH_LEGACY_FIELDS_ENABLE=1 を設定。
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +272,12 @@ impl InstanceBox {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err("Legacy fields not initialized".to_string())
|
// 既定OFF: fields_ng のみ更新(Null 占位)。整合を壊さないようにOK返す。
|
||||||
|
self.fields_ng
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(field_name.to_string(), NyashValue::Null);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -62,6 +62,14 @@ def lower_function(builder, func_data: Dict[str, Any]):
|
|||||||
builder.resolver.string_literals.clear()
|
builder.resolver.string_literals.clear()
|
||||||
if hasattr(builder.resolver, 'string_ptrs'):
|
if hasattr(builder.resolver, 'string_ptrs'):
|
||||||
builder.resolver.string_ptrs.clear()
|
builder.resolver.string_ptrs.clear()
|
||||||
|
if hasattr(builder.resolver, 'length_cache'):
|
||||||
|
builder.resolver.length_cache.clear()
|
||||||
|
# Also clear newbox→string-arg hints per function to avoid leakage
|
||||||
|
try:
|
||||||
|
if hasattr(builder.resolver, 'newbox_string_args') and isinstance(builder.resolver.newbox_string_args, dict):
|
||||||
|
builder.resolver.newbox_string_args.clear()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,30 @@ Handles +, -, *, /, %, &, |, ^, <<, >>
|
|||||||
import llvmlite.ir as ir
|
import llvmlite.ir as ir
|
||||||
from typing import Dict, Optional, Any
|
from typing import Dict, Optional, Any
|
||||||
from utils.values import resolve_i64_strict
|
from utils.values import resolve_i64_strict
|
||||||
|
import os
|
||||||
from .compare import lower_compare
|
from .compare import lower_compare
|
||||||
import llvmlite.ir as ir
|
|
||||||
|
|
||||||
|
def _canonicalize_i64(builder: ir.IRBuilder, value, vid, vmap: Dict[int, ir.Value], hint: str):
|
||||||
|
"""Normalize integers/pointers to i64 and cache per value id for FAST_INT paths."""
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
target = ir.IntType(64)
|
||||||
|
try:
|
||||||
|
vtype = value.type
|
||||||
|
except Exception:
|
||||||
|
vtype = None
|
||||||
|
if isinstance(vtype, ir.PointerType):
|
||||||
|
value = builder.ptrtoint(value, target, name=f"{hint}_p2i_{vid}")
|
||||||
|
elif isinstance(vtype, ir.IntType):
|
||||||
|
width = vtype.width
|
||||||
|
if width < 64:
|
||||||
|
value = builder.zext(value, target, name=f"{hint}_zext_{vid}")
|
||||||
|
elif width > 64:
|
||||||
|
value = builder.trunc(value, target, name=f"{hint}_trunc_{vid}")
|
||||||
|
if isinstance(vid, int):
|
||||||
|
vmap[vid] = value
|
||||||
|
return value
|
||||||
|
|
||||||
def lower_binop(
|
def lower_binop(
|
||||||
builder: ir.IRBuilder,
|
builder: ir.IRBuilder,
|
||||||
@ -37,10 +59,20 @@ def lower_binop(
|
|||||||
vmap: Value map
|
vmap: Value map
|
||||||
current_block: Current basic block
|
current_block: Current basic block
|
||||||
"""
|
"""
|
||||||
# Resolve operands as i64 (using resolver when available)
|
# Resolve operands as i64
|
||||||
# For now, simple vmap lookup
|
fast_int = os.environ.get('NYASH_LLVM_FAST_INT') == '1'
|
||||||
lhs_val = resolve_i64_strict(resolver, lhs, current_block, preds, block_end_values, vmap, bb_map)
|
lhs_val = None
|
||||||
rhs_val = resolve_i64_strict(resolver, rhs, current_block, preds, block_end_values, vmap, bb_map)
|
rhs_val = None
|
||||||
|
if fast_int:
|
||||||
|
# Prefer same-block SSA directly to avoid resolver/PHI materialization cost in hot loops
|
||||||
|
lhs_val = vmap.get(lhs)
|
||||||
|
rhs_val = vmap.get(rhs)
|
||||||
|
if lhs_val is None:
|
||||||
|
lhs_val = resolve_i64_strict(resolver, lhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||||
|
if rhs_val is None:
|
||||||
|
rhs_val = resolve_i64_strict(resolver, rhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||||
|
lhs_val = _canonicalize_i64(builder, lhs_val, lhs, vmap, "bin_lhs")
|
||||||
|
rhs_val = _canonicalize_i64(builder, rhs_val, rhs, vmap, "bin_rhs")
|
||||||
if lhs_val is None:
|
if lhs_val is None:
|
||||||
lhs_val = ir.Constant(ir.IntType(64), 0)
|
lhs_val = ir.Constant(ir.IntType(64), 0)
|
||||||
if rhs_val is None:
|
if rhs_val is None:
|
||||||
|
|||||||
@ -51,6 +51,14 @@ def lower_boxcall(
|
|||||||
bb_map=None,
|
bb_map=None,
|
||||||
ctx: Optional[Any] = None,
|
ctx: Optional[Any] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
# Guard against emitting after a terminator: create continuation block if needed.
|
||||||
|
try:
|
||||||
|
if builder.block is not None and getattr(builder.block, 'terminator', None) is not None:
|
||||||
|
func = builder.block.parent
|
||||||
|
cont = func.append_basic_block(name=f"cont_bb_{builder.block.name}")
|
||||||
|
builder.position_at_end(cont)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
"""
|
"""
|
||||||
Lower MIR BoxCall instruction
|
Lower MIR BoxCall instruction
|
||||||
|
|
||||||
@ -124,6 +132,56 @@ def lower_boxcall(
|
|||||||
fast_on = os.environ.get('NYASH_LLVM_FAST') == '1'
|
fast_on = os.environ.get('NYASH_LLVM_FAST') == '1'
|
||||||
except Exception:
|
except Exception:
|
||||||
fast_on = False
|
fast_on = False
|
||||||
|
def _cache_len(val):
|
||||||
|
if not fast_on or resolver is None or dst_vid is None or box_vid is None:
|
||||||
|
return
|
||||||
|
cache = getattr(resolver, 'length_cache', None)
|
||||||
|
if cache is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
cache[int(box_vid)] = val
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if fast_on and resolver is not None and dst_vid is not None and box_vid is not None:
|
||||||
|
cache = getattr(resolver, 'length_cache', None)
|
||||||
|
if cache is not None:
|
||||||
|
try:
|
||||||
|
cached = cache.get(int(box_vid))
|
||||||
|
except Exception:
|
||||||
|
cached = None
|
||||||
|
if cached is not None:
|
||||||
|
vmap[dst_vid] = cached
|
||||||
|
return
|
||||||
|
# Ultra-fast: literal length folding when receiver originates from a string literal.
|
||||||
|
# Check resolver.newbox_string_args[recv] -> arg_vid -> resolver.string_literals[arg_vid]
|
||||||
|
if fast_on and dst_vid is not None and resolver is not None:
|
||||||
|
try:
|
||||||
|
arg_vid = None
|
||||||
|
if hasattr(resolver, 'newbox_string_args'):
|
||||||
|
arg_vid = resolver.newbox_string_args.get(int(box_vid))
|
||||||
|
# Case A: newbox(StringBox, const)
|
||||||
|
if arg_vid is not None and hasattr(resolver, 'string_literals'):
|
||||||
|
lit = resolver.string_literals.get(int(arg_vid))
|
||||||
|
if isinstance(lit, str):
|
||||||
|
# Mode: bytes or code points
|
||||||
|
use_cp = os.environ.get('NYASH_STR_CP') == '1'
|
||||||
|
n = len(lit) if use_cp else len(lit.encode('utf-8'))
|
||||||
|
const_len = ir.Constant(ir.IntType(64), n)
|
||||||
|
vmap[dst_vid] = const_len
|
||||||
|
_cache_len(const_len)
|
||||||
|
return
|
||||||
|
# Case B: receiver itself is a literal-backed handle (const string)
|
||||||
|
if hasattr(resolver, 'string_literals'):
|
||||||
|
lit2 = resolver.string_literals.get(int(box_vid))
|
||||||
|
if isinstance(lit2, str):
|
||||||
|
use_cp = os.environ.get('NYASH_STR_CP') == '1'
|
||||||
|
n2 = len(lit2) if use_cp else len(lit2.encode('utf-8'))
|
||||||
|
const_len2 = ir.Constant(ir.IntType(64), n2)
|
||||||
|
vmap[dst_vid] = const_len2
|
||||||
|
_cache_len(const_len2)
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
if fast_on and resolver is not None and hasattr(resolver, 'string_ptrs'):
|
if fast_on and resolver is not None and hasattr(resolver, 'string_ptrs'):
|
||||||
try:
|
try:
|
||||||
ptr = resolver.string_ptrs.get(int(box_vid))
|
ptr = resolver.string_ptrs.get(int(box_vid))
|
||||||
|
|||||||
@ -6,9 +6,31 @@ Handles comparison operations (<, >, <=, >=, ==, !=)
|
|||||||
import llvmlite.ir as ir
|
import llvmlite.ir as ir
|
||||||
from typing import Dict, Optional, Any
|
from typing import Dict, Optional, Any
|
||||||
from utils.values import resolve_i64_strict
|
from utils.values import resolve_i64_strict
|
||||||
|
import os
|
||||||
from .externcall import lower_externcall
|
from .externcall import lower_externcall
|
||||||
from trace import values as trace_values
|
from trace import values as trace_values
|
||||||
|
|
||||||
|
|
||||||
|
def _canonicalize_i64(builder: ir.IRBuilder, value, vid, vmap: Dict[int, ir.Value], hint: str):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
target = ir.IntType(64)
|
||||||
|
try:
|
||||||
|
vtype = value.type
|
||||||
|
except Exception:
|
||||||
|
vtype = None
|
||||||
|
if isinstance(vtype, ir.PointerType):
|
||||||
|
value = builder.ptrtoint(value, target, name=f"{hint}_p2i_{vid}")
|
||||||
|
elif isinstance(vtype, ir.IntType):
|
||||||
|
width = vtype.width
|
||||||
|
if width < 64:
|
||||||
|
value = builder.zext(value, target, name=f"{hint}_zext_{vid}")
|
||||||
|
elif width > 64:
|
||||||
|
value = builder.trunc(value, target, name=f"{hint}_trunc_{vid}")
|
||||||
|
if isinstance(vid, int):
|
||||||
|
vmap[vid] = value
|
||||||
|
return value
|
||||||
|
|
||||||
def lower_compare(
|
def lower_compare(
|
||||||
builder: ir.IRBuilder,
|
builder: ir.IRBuilder,
|
||||||
op: str,
|
op: str,
|
||||||
@ -50,8 +72,18 @@ def lower_compare(
|
|||||||
pass
|
pass
|
||||||
# Get operands
|
# Get operands
|
||||||
# Prefer same-block SSA from vmap; fallback to resolver for cross-block dominance
|
# Prefer same-block SSA from vmap; fallback to resolver for cross-block dominance
|
||||||
lhs_val = resolve_i64_strict(resolver, lhs, current_block, preds, block_end_values, vmap, bb_map)
|
fast_int = os.environ.get('NYASH_LLVM_FAST_INT') == '1'
|
||||||
rhs_val = resolve_i64_strict(resolver, rhs, current_block, preds, block_end_values, vmap, bb_map)
|
lhs_val = None
|
||||||
|
rhs_val = None
|
||||||
|
if fast_int:
|
||||||
|
lhs_val = vmap.get(lhs)
|
||||||
|
rhs_val = vmap.get(rhs)
|
||||||
|
if lhs_val is None:
|
||||||
|
lhs_val = resolve_i64_strict(resolver, lhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||||
|
if rhs_val is None:
|
||||||
|
rhs_val = resolve_i64_strict(resolver, rhs, current_block, preds, block_end_values, vmap, bb_map)
|
||||||
|
lhs_val = _canonicalize_i64(builder, lhs_val, lhs, vmap, "cmp_lhs")
|
||||||
|
rhs_val = _canonicalize_i64(builder, rhs_val, rhs, vmap, "cmp_rhs")
|
||||||
|
|
||||||
i64 = ir.IntType(64)
|
i64 = ir.IntType(64)
|
||||||
i8p = ir.IntType(8).as_pointer()
|
i8p = ir.IntType(8).as_pointer()
|
||||||
|
|||||||
@ -21,6 +21,15 @@ def lower_mir_call(owner, builder: ir.IRBuilder, mir_call: Dict[str, Any], dst_v
|
|||||||
- resolver: Value resolver instance
|
- resolver: Value resolver instance
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Guard: avoid emitting after a terminator; if current block is closed, create continuation.
|
||||||
|
try:
|
||||||
|
if builder.block is not None and getattr(builder.block, 'terminator', None) is not None:
|
||||||
|
func = builder.block.parent
|
||||||
|
cont = func.append_basic_block(name=f"cont_bb_{builder.block.name}")
|
||||||
|
builder.position_at_end(cont)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Check if unified call is enabled
|
# Check if unified call is enabled
|
||||||
use_unified = os.getenv("NYASH_MIR_UNIFIED_CALL", "1").lower() not in ("0", "false", "off")
|
use_unified = os.getenv("NYASH_MIR_UNIFIED_CALL", "1").lower() not in ("0", "false", "off")
|
||||||
if not use_unified:
|
if not use_unified:
|
||||||
|
|||||||
@ -87,6 +87,12 @@ def lower_newbox(
|
|||||||
resolver.newbox_string_args = {}
|
resolver.newbox_string_args = {}
|
||||||
# Map the resulting box handle to the string argument
|
# Map the resulting box handle to the string argument
|
||||||
resolver.newbox_string_args[dst_vid] = args[0]
|
resolver.newbox_string_args[dst_vid] = args[0]
|
||||||
|
# Hint downstream passes that this dst is string-ish
|
||||||
|
if hasattr(resolver, 'mark_string'):
|
||||||
|
try:
|
||||||
|
resolver.mark_string(int(dst_vid))
|
||||||
|
except Exception:
|
||||||
|
resolver.mark_string(dst_vid)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # Silently ignore failures
|
pass # Silently ignore failures
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,11 @@ Handles void and value returns
|
|||||||
|
|
||||||
import llvmlite.ir as ir
|
import llvmlite.ir as ir
|
||||||
from typing import Dict, Optional, Any
|
from typing import Dict, Optional, Any
|
||||||
|
try:
|
||||||
|
# Create PHIs at block head to satisfy LLVM invariant
|
||||||
|
from ..phi_wiring.wiring import phi_at_block_head as _phi_at_block_head
|
||||||
|
except Exception:
|
||||||
|
_phi_at_block_head = None
|
||||||
|
|
||||||
def lower_return(
|
def lower_return(
|
||||||
builder: ir.IRBuilder,
|
builder: ir.IRBuilder,
|
||||||
@ -115,6 +120,7 @@ def lower_return(
|
|||||||
zero_like = (str(ret_val) == str(ir.Constant(return_type, 0.0)))
|
zero_like = (str(ret_val) == str(ir.Constant(return_type, 0.0)))
|
||||||
elif isinstance(return_type, ir.PointerType):
|
elif isinstance(return_type, ir.PointerType):
|
||||||
zero_like = (str(ret_val) == str(ir.Constant(return_type, None)))
|
zero_like = (str(ret_val) == str(ir.Constant(return_type, None)))
|
||||||
|
# Synthesize a PHI for return at the BLOCK HEAD (grouped), not inline.
|
||||||
if zero_like and preds is not None and block_end_values is not None and bb_map is not None and isinstance(value_id, int):
|
if zero_like and preds is not None and block_end_values is not None and bb_map is not None and isinstance(value_id, int):
|
||||||
# Derive current block id from name like 'bb3'
|
# Derive current block id from name like 'bb3'
|
||||||
cur_bid = None
|
cur_bid = None
|
||||||
@ -125,8 +131,8 @@ def lower_return(
|
|||||||
if cur_bid is not None:
|
if cur_bid is not None:
|
||||||
incoming = []
|
incoming = []
|
||||||
for p in preds.get(cur_bid, []):
|
for p in preds.get(cur_bid, []):
|
||||||
# Skip self-loop
|
if p == cur_bid:
|
||||||
if p == cur_bid: continue
|
continue
|
||||||
v = None
|
v = None
|
||||||
try:
|
try:
|
||||||
v = block_end_values.get(p, {}).get(value_id)
|
v = block_end_values.get(p, {}).get(value_id)
|
||||||
@ -138,7 +144,17 @@ def lower_return(
|
|||||||
if bblk is not None:
|
if bblk is not None:
|
||||||
incoming.append((v, bblk))
|
incoming.append((v, bblk))
|
||||||
if incoming:
|
if incoming:
|
||||||
phi = builder.phi(return_type, name=f"ret_phi_{value_id}")
|
if _phi_at_block_head is not None:
|
||||||
|
phi = _phi_at_block_head(builder.block, return_type, name=f"ret_phi_{value_id}")
|
||||||
|
else:
|
||||||
|
# Fallback: create PHI at block head using a temporary builder
|
||||||
|
try:
|
||||||
|
_b = ir.IRBuilder(builder.block)
|
||||||
|
_b.position_at_start(builder.block)
|
||||||
|
phi = _b.phi(return_type, name=f"ret_phi_{value_id}")
|
||||||
|
except Exception:
|
||||||
|
# As a last resort, create via current builder (may still succeed)
|
||||||
|
phi = builder.phi(return_type, name=f"ret_phi_{value_id}")
|
||||||
for (v, bblk) in incoming:
|
for (v, bblk) in incoming:
|
||||||
phi.add_incoming(v, bblk)
|
phi.add_incoming(v, bblk)
|
||||||
ret_val = phi
|
ret_val = phi
|
||||||
@ -162,4 +178,5 @@ def lower_return(
|
|||||||
# Zero extend
|
# Zero extend
|
||||||
ret_val = builder.zext(ret_val, return_type)
|
ret_val = builder.zext(ret_val, return_type)
|
||||||
|
|
||||||
|
# Emit return; no further instructions should be emitted in this block
|
||||||
builder.ret(ret_val)
|
builder.ret(ret_val)
|
||||||
|
|||||||
@ -121,5 +121,13 @@ def insert_automatic_safepoint(
|
|||||||
func_type = ir.FunctionType(void, [])
|
func_type = ir.FunctionType(void, [])
|
||||||
check_func = ir.Function(module, func_type, name="ny_check_safepoint")
|
check_func = ir.Function(module, func_type, name="ny_check_safepoint")
|
||||||
|
|
||||||
|
# Guard: do not insert into a terminated block; create continuation if needed
|
||||||
|
try:
|
||||||
|
if builder.block is not None and getattr(builder.block, 'terminator', None) is not None:
|
||||||
|
func = builder.block.parent
|
||||||
|
cont = func.append_basic_block(name=f"cont_bb_{builder.block.name}")
|
||||||
|
builder.position_at_end(cont)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
# Insert safepoint check
|
# Insert safepoint check
|
||||||
builder.call(check_func, [], name=f"safepoint_{location}")
|
builder.call(check_func, [], name=f"safepoint_{location}")
|
||||||
|
|||||||
@ -575,12 +575,27 @@ class NyashLLVMBuilder:
|
|||||||
# to avoid divergence between two implementations.
|
# to avoid divergence between two implementations.
|
||||||
|
|
||||||
def _lower_instruction_list(self, builder: ir.IRBuilder, insts: List[Dict[str, Any]], func: ir.Function):
|
def _lower_instruction_list(self, builder: ir.IRBuilder, insts: List[Dict[str, Any]], func: ir.Function):
|
||||||
"""Lower a flat list of instructions using current builder and function."""
|
"""Lower a flat list of instructions using current builder and function.
|
||||||
for sub in insts:
|
Structural guard: truncate at first terminator (ret/branch/jump) to keep IR valid.
|
||||||
# If current block already has a terminator, create a continuation block
|
"""
|
||||||
if builder.block.terminator is not None:
|
# Sanitize: stop at first terminator in the MIR list
|
||||||
cont = func.append_basic_block(name=f"cont_bb_{builder.block.name}")
|
effective: List[Dict[str, Any]] = []
|
||||||
builder.position_at_end(cont)
|
try:
|
||||||
|
for it in insts:
|
||||||
|
op = (it or {}).get('op')
|
||||||
|
effective.append(it)
|
||||||
|
if op in ('ret', 'branch', 'jump'):
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
effective = list(insts)
|
||||||
|
for sub in effective:
|
||||||
|
# If current block already has a terminator, stop lowering further instructions
|
||||||
|
# to keep LLVM IR structurally valid. Any residuals should be split upstream.
|
||||||
|
try:
|
||||||
|
if builder.block is not None and builder.block.terminator is not None:
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
self.lower_instruction(builder, sub, func)
|
self.lower_instruction(builder, sub, func)
|
||||||
|
|
||||||
def finalize_phis(self):
|
def finalize_phis(self):
|
||||||
|
|||||||
@ -47,6 +47,8 @@ class Resolver:
|
|||||||
# Track value-ids that are known to represent string handles (i64)
|
# Track value-ids that are known to represent string handles (i64)
|
||||||
# This is a best-effort tag used to decide '+' as string concat when both sides are i64.
|
# This is a best-effort tag used to decide '+' as string concat when both sides are i64.
|
||||||
self.string_ids: set[int] = set()
|
self.string_ids: set[int] = set()
|
||||||
|
# Cache for repeated string length queries when immutably known
|
||||||
|
self.length_cache: Dict[int, ir.Value] = {}
|
||||||
|
|
||||||
# Type shortcuts
|
# Type shortcuts
|
||||||
self.i64 = ir.IntType(64)
|
self.i64 = ir.IntType(64)
|
||||||
|
|||||||
49
src/llvm_py/tests/test_strlen_fast.py
Normal file
49
src/llvm_py/tests/test_strlen_fast.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
import llvmlite.ir as ir
|
||||||
|
|
||||||
|
from src.llvm_py.llvm_builder import NyashLLVMBuilder
|
||||||
|
|
||||||
|
|
||||||
|
class TestStrlenFast(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# Ensure FAST toggle is ON for this test
|
||||||
|
os.environ['NYASH_LLVM_FAST'] = '1'
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
os.environ.pop('NYASH_LLVM_FAST', None)
|
||||||
|
|
||||||
|
def test_newbox_string_length_fast_lowering(self):
|
||||||
|
# Minimal MIR JSON v0-like: const "hello" → newbox StringBox(arg) → boxcall length
|
||||||
|
mir = {
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"name": "main",
|
||||||
|
"params": [],
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"instructions": [
|
||||||
|
{"op": "const", "dst": 10, "value": {"type": "string", "value": "hello"}},
|
||||||
|
{"op": "newbox", "dst": 20, "box_type": "StringBox", "args": [10]},
|
||||||
|
{"op": "boxcall", "dst": 30, "box_val": 20, "method": "length", "args": []},
|
||||||
|
{"op": "ret", "value": 30},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
b = NyashLLVMBuilder()
|
||||||
|
ir_txt = b.build_from_mir(mir)
|
||||||
|
# Must reference the neutral kernel symbol for FAST strlen
|
||||||
|
self.assertIn('declare i64 @nyrt_string_length(i8*, i64)', ir_txt or '')
|
||||||
|
# And a call site should be present
|
||||||
|
self.assertIn('call i64 @nyrt_string_length', ir_txt)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
||||||
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use super::{EffectMask, MirInstruction, ValueId};
|
use super::{EffectMask, MirInstruction, ValueId};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashSet;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// Unique identifier for basic blocks within a function
|
/// Unique identifier for basic blocks within a function
|
||||||
|
|||||||
@ -442,6 +442,12 @@ impl MirBuilder {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(block) = function.get_block_mut(block_id) {
|
if let Some(block) = function.get_block_mut(block_id) {
|
||||||
|
// Invariant: Call must always carry a Callee (unified path).
|
||||||
|
if let MirInstruction::Call { callee, .. } = &instruction {
|
||||||
|
if callee.is_none() {
|
||||||
|
return Err("builder invariant violated: MirInstruction::Call.callee must be Some (unified call)".into());
|
||||||
|
}
|
||||||
|
}
|
||||||
if utils::builder_debug_enabled() {
|
if utils::builder_debug_enabled() {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"[BUILDER] emit @bb{} -> {}",
|
"[BUILDER] emit @bb{} -> {}",
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
// Extracted call-related builders from builder.rs to keep files lean
|
// Extracted call-related builders from builder.rs to keep files lean
|
||||||
use super::{Effect, EffectMask, FunctionSignature, MirInstruction, MirType, ValueId};
|
use super::{Effect, EffectMask, MirInstruction, MirType, ValueId};
|
||||||
use crate::ast::{ASTNode, LiteralValue, MethodCallExpr};
|
use crate::ast::{ASTNode, LiteralValue};
|
||||||
use crate::mir::definitions::call_unified::{Callee, CallFlags, MirCall};
|
use crate::mir::definitions::call_unified::Callee;
|
||||||
use crate::mir::TypeOpKind;
|
use crate::mir::TypeOpKind;
|
||||||
use super::call_resolution;
|
|
||||||
|
|
||||||
// Import from new modules
|
// Import from new modules
|
||||||
use super::calls::*;
|
use super::calls::*;
|
||||||
@ -116,7 +115,13 @@ impl super::MirBuilder {
|
|||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
};
|
};
|
||||||
self.emit_instruction(MirInstruction::Call { dst: Some(dstv), func: name_const, callee: None, args: args.clone(), effects: EffectMask::IO })?;
|
self.emit_instruction(MirInstruction::Call {
|
||||||
|
dst: Some(dstv),
|
||||||
|
func: name_const,
|
||||||
|
callee: Some(Callee::Global(name.clone())),
|
||||||
|
args: args.clone(),
|
||||||
|
effects: EffectMask::IO,
|
||||||
|
})?;
|
||||||
self.annotate_call_result_from_func_name(dstv, name);
|
self.annotate_call_result_from_func_name(dstv, name);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -140,7 +145,7 @@ impl super::MirBuilder {
|
|||||||
self.emit_instruction(MirInstruction::Call {
|
self.emit_instruction(MirInstruction::Call {
|
||||||
dst: Some(dstv),
|
dst: Some(dstv),
|
||||||
func: name_const,
|
func: name_const,
|
||||||
callee: None,
|
callee: Some(Callee::Global(func_name.clone())),
|
||||||
args: args.clone(),
|
args: args.clone(),
|
||||||
effects: EffectMask::IO,
|
effects: EffectMask::IO,
|
||||||
})?;
|
})?;
|
||||||
@ -231,20 +236,15 @@ impl super::MirBuilder {
|
|||||||
|
|
||||||
// Finalize operands in current block (EmitGuardBox wrapper)
|
// Finalize operands in current block (EmitGuardBox wrapper)
|
||||||
let mut callee = callee;
|
let mut callee = callee;
|
||||||
let mut args_local: Vec<ValueId> = args.clone();
|
let mut args_local: Vec<ValueId> = args;
|
||||||
crate::mir::builder::emit_guard::finalize_call_operands(self, &mut callee, &mut args_local);
|
crate::mir::builder::emit_guard::finalize_call_operands(self, &mut callee, &mut args_local);
|
||||||
|
|
||||||
// Create MirCall instruction using the new module (pure data composition)
|
// Create MirCall instruction using the new module (pure data composition)
|
||||||
let mir_call = call_unified::create_mir_call(dst, callee.clone(), args_local.clone());
|
let mir_call = call_unified::create_mir_call(dst, callee.clone(), args_local.clone());
|
||||||
|
|
||||||
// Final last-chance LocalSSA just before emission in case any block switch happened
|
|
||||||
let mut callee2 = callee.clone();
|
|
||||||
let mut args2 = args_local.clone();
|
|
||||||
crate::mir::builder::emit_guard::finalize_call_operands(self, &mut callee2, &mut args2);
|
|
||||||
|
|
||||||
// Dev trace: show final callee/recv right before emission (guarded)
|
// Dev trace: show final callee/recv right before emission (guarded)
|
||||||
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") || super::utils::builder_debug_enabled() {
|
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") || super::utils::builder_debug_enabled() {
|
||||||
if let Callee::Method { method, receiver, box_name, .. } = &callee2 {
|
if let Callee::Method { method, receiver, box_name, .. } = &callee {
|
||||||
if let Some(r) = receiver {
|
if let Some(r) = receiver {
|
||||||
eprintln!("[vm-call-final] bb={:?} method={} recv=%{} class={}",
|
eprintln!("[vm-call-final] bb={:?} method={} recv=%{} class={}",
|
||||||
self.current_block, method, r.0, box_name);
|
self.current_block, method, r.0, box_name);
|
||||||
@ -252,38 +252,13 @@ impl super::MirBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final forced in-block materialization for receiver just-before emission
|
|
||||||
// Rationale: ensure a Copy(def) immediately precedes the Call in the same block
|
|
||||||
let callee2 = match callee2 {
|
|
||||||
Callee::Method { box_name, method, receiver: Some(r), certainty } => {
|
|
||||||
if super::utils::builder_debug_enabled() || std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
|
|
||||||
eprintln!("[call-forced-copy] bb={:?} recv=%{} -> (copy)", self.current_block, r.0);
|
|
||||||
}
|
|
||||||
// Insert immediately after PHIs to guarantee position and dominance
|
|
||||||
let r_forced = crate::mir::builder::schedule::block::BlockScheduleBox::ensure_after_phis_copy(self, r)?;
|
|
||||||
if super::utils::builder_debug_enabled() || std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
|
|
||||||
eprintln!("[call-forced-copy] bb={:?} inserted Copy %{} := %{} (after PHIs)", self.current_block, r_forced.0, r.0);
|
|
||||||
}
|
|
||||||
Callee::Method { box_name, method, receiver: Some(r_forced), certainty }
|
|
||||||
}
|
|
||||||
other => other,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Optional last-chance: emit a tail copy right before we emit Call (keeps order stable)
|
|
||||||
let callee2 = match callee2 {
|
|
||||||
Callee::Method { box_name, method, receiver: Some(r), certainty } => {
|
|
||||||
let r_tail = crate::mir::builder::schedule::block::BlockScheduleBox::emit_before_call_copy(self, r)?;
|
|
||||||
Callee::Method { box_name, method, receiver: Some(r_tail), certainty }
|
|
||||||
}
|
|
||||||
other => other,
|
|
||||||
};
|
|
||||||
|
|
||||||
// For Phase 2: Convert to legacy Call instruction with new callee field (use finalized operands)
|
// For Phase 2: Convert to legacy Call instruction with new callee field (use finalized operands)
|
||||||
let legacy_call = MirInstruction::Call {
|
let legacy_call = MirInstruction::Call {
|
||||||
dst: mir_call.dst,
|
dst: mir_call.dst,
|
||||||
func: ValueId::new(0), // Dummy value for legacy compatibility
|
func: ValueId::new(0), // Dummy value for legacy compatibility
|
||||||
callee: Some(callee2),
|
callee: Some(callee),
|
||||||
args: args2,
|
args: args_local,
|
||||||
effects: mir_call.effects,
|
effects: mir_call.effects,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -345,7 +320,7 @@ impl super::MirBuilder {
|
|||||||
self.emit_instruction(MirInstruction::Call {
|
self.emit_instruction(MirInstruction::Call {
|
||||||
dst: Some(actual_dst),
|
dst: Some(actual_dst),
|
||||||
func: name_const,
|
func: name_const,
|
||||||
callee: None, // Legacy mode
|
callee: Some(Callee::Global(name.clone())),
|
||||||
args,
|
args,
|
||||||
effects: EffectMask::IO,
|
effects: EffectMask::IO,
|
||||||
})?;
|
})?;
|
||||||
@ -359,7 +334,7 @@ impl super::MirBuilder {
|
|||||||
self.emit_instruction(MirInstruction::Call {
|
self.emit_instruction(MirInstruction::Call {
|
||||||
dst,
|
dst,
|
||||||
func: func_val,
|
func: func_val,
|
||||||
callee: None, // Legacy mode
|
callee: Some(Callee::Value(func_val)),
|
||||||
args,
|
args,
|
||||||
effects: EffectMask::IO,
|
effects: EffectMask::IO,
|
||||||
})
|
})
|
||||||
@ -524,7 +499,7 @@ impl super::MirBuilder {
|
|||||||
if let Err(e) = self.emit_instruction(MirInstruction::Call {
|
if let Err(e) = self.emit_instruction(MirInstruction::Call {
|
||||||
dst: Some(result_id),
|
dst: Some(result_id),
|
||||||
func: fun_val,
|
func: fun_val,
|
||||||
callee: None, // Legacy math function - use old resolution
|
callee: Some(Callee::Global(fun_name.clone())),
|
||||||
args: arg_values,
|
args: arg_values,
|
||||||
effects: EffectMask::READ.add(Effect::ReadHeap)
|
effects: EffectMask::READ.add(Effect::ReadHeap)
|
||||||
}) { return Some(Err(e)); }
|
}) { return Some(Err(e)); }
|
||||||
@ -626,8 +601,8 @@ impl super::MirBuilder {
|
|||||||
let (bx, _arity) = matches.remove(0);
|
let (bx, _arity) = matches.remove(0);
|
||||||
let dst = self.value_gen.next();
|
let dst = self.value_gen.next();
|
||||||
let func_name = format!("{}.{}{}", bx, name, format!("/{}", arg_values.len()));
|
let func_name = format!("{}.{}{}", bx, name, format!("/{}", arg_values.len()));
|
||||||
// Emit legacy global call to the lowered static method function
|
// Emit unified global call to the lowered static method function
|
||||||
self.emit_legacy_call(Some(dst), CallTarget::Global(func_name), arg_values)?;
|
self.emit_unified_call(Some(dst), CallTarget::Global(func_name), arg_values)?;
|
||||||
return Ok(dst);
|
return Ok(dst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -739,18 +714,12 @@ impl super::MirBuilder {
|
|||||||
call_args.push(me_id);
|
call_args.push(me_id);
|
||||||
call_args.extend(arg_values.into_iter());
|
call_args.extend(arg_values.into_iter());
|
||||||
let dst = self.value_gen.next();
|
let dst = self.value_gen.next();
|
||||||
// Emit function name via NameConstBox
|
// Emit as unified global call to lowered function
|
||||||
let c = match crate::mir::builder::name_const::make_name_const_result(self, &fname) {
|
self.emit_unified_call(
|
||||||
Ok(v) => v,
|
Some(dst),
|
||||||
Err(e) => return Err(e),
|
CallTarget::Global(fname.clone()),
|
||||||
};
|
call_args,
|
||||||
self.emit_instruction(MirInstruction::Call {
|
)?;
|
||||||
dst: Some(dst),
|
|
||||||
func: c,
|
|
||||||
callee: None,
|
|
||||||
args: call_args,
|
|
||||||
effects: crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap),
|
|
||||||
})?;
|
|
||||||
self.annotate_call_result_from_func_name(dst, &fname);
|
self.annotate_call_result_from_func_name(dst, &fname);
|
||||||
return Ok(dst);
|
return Ok(dst);
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/mir/builder/calls/README.md
Normal file
17
src/mir/builder/calls/README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# MIR Builder — Calls SSOT
|
||||||
|
|
||||||
|
Scope
|
||||||
|
- This directory is the single source of truth for call shaping in the builder.
|
||||||
|
- Responsibilities: target resolution, extern mapping, method lookup, flags/effects, MIR emission.
|
||||||
|
|
||||||
|
Out of scope
|
||||||
|
- Runtime dispatch details (VM/LLVM) and legacy by-name resolution. The VM keeps a legacy resolver only behind a dev flag for bring-up.
|
||||||
|
|
||||||
|
Contract
|
||||||
|
- Builder must populate `MirInstruction::Call` with a concrete `Callee` whenever possible.
|
||||||
|
- Arity and canonical names are normalized here so runtimes can be simple routers.
|
||||||
|
|
||||||
|
Phase-3 alignment
|
||||||
|
- VM call resolver is treated as legacy-only. Default runtime disables by-name fallback.
|
||||||
|
- Extern interface normalization aligns with `handlers/calls/externs.rs` (runtime SSOT for extern dispatch).
|
||||||
|
|
||||||
@ -28,13 +28,14 @@ pub fn convert_target_to_callee(
|
|||||||
) -> Result<Callee, String> {
|
) -> Result<Callee, String> {
|
||||||
match target {
|
match target {
|
||||||
CallTarget::Global(name) => {
|
CallTarget::Global(name) => {
|
||||||
// Check if it's a built-in function
|
// Prefer explicit categories; otherwise treat as module-global function
|
||||||
if method_resolution::is_builtin_function(&name) {
|
if method_resolution::is_builtin_function(&name) {
|
||||||
Ok(Callee::Global(name))
|
Ok(Callee::Global(name))
|
||||||
} else if method_resolution::is_extern_function(&name) {
|
} else if method_resolution::is_extern_function(&name) {
|
||||||
Ok(Callee::Extern(name))
|
Ok(Callee::Extern(name))
|
||||||
} else {
|
} else {
|
||||||
Err(format!("Unknown global function: {}", name))
|
// Module-local or static lowered function (e.g., "Box.method/N")
|
||||||
|
Ok(Callee::Global(name))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
// Core types
|
// Core types
|
||||||
pub mod call_target;
|
pub mod call_target;
|
||||||
pub use call_target::CallTarget;
|
|
||||||
|
|
||||||
// Resolution system
|
// Resolution system
|
||||||
pub mod method_resolution;
|
pub mod method_resolution;
|
||||||
@ -27,43 +26,5 @@ pub mod call_unified;
|
|||||||
// Call result annotation
|
// Call result annotation
|
||||||
pub mod annotation;
|
pub mod annotation;
|
||||||
|
|
||||||
// Re-export commonly used items
|
// Re-exports were removed to reduce unused-import warnings.
|
||||||
pub use method_resolution::{
|
// Use module-qualified paths (e.g., special_handlers::parse_type_name_to_mir) instead.
|
||||||
resolve_call_target,
|
|
||||||
is_builtin_function,
|
|
||||||
is_extern_function,
|
|
||||||
has_method,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use extern_calls::{
|
|
||||||
get_env_method_spec,
|
|
||||||
parse_extern_name,
|
|
||||||
is_env_interface,
|
|
||||||
compute_extern_effects,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use special_handlers::{
|
|
||||||
is_math_function,
|
|
||||||
is_typeop_method,
|
|
||||||
extract_string_literal,
|
|
||||||
parse_type_name_to_mir,
|
|
||||||
contains_value_return,
|
|
||||||
make_function_name_with_arity,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use function_lowering::{
|
|
||||||
prepare_method_signature,
|
|
||||||
prepare_static_method_signature,
|
|
||||||
generate_method_function_name,
|
|
||||||
generate_static_method_function_name,
|
|
||||||
wrap_in_program,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use call_unified::{
|
|
||||||
is_unified_call_enabled,
|
|
||||||
convert_target_to_callee,
|
|
||||||
compute_call_effects,
|
|
||||||
create_call_flags,
|
|
||||||
create_mir_call,
|
|
||||||
validate_call_args,
|
|
||||||
};
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
//! Control-flow entrypoints (if/loop/try/throw) centralized here.
|
//! Control-flow entrypoints (if/loop/try/throw) centralized here.
|
||||||
use super::{ConstValue, Effect, EffectMask, MirInstruction, ValueId};
|
use super::{Effect, EffectMask, MirInstruction, ValueId};
|
||||||
use crate::ast::ASTNode;
|
use crate::ast::ASTNode;
|
||||||
|
|
||||||
impl super::MirBuilder {
|
impl super::MirBuilder {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Declarations lowering: static boxes and box declarations
|
// Declarations lowering: static boxes and box declarations
|
||||||
use super::{ConstValue, MirInstruction, ValueId};
|
use super::{MirInstruction, ValueId};
|
||||||
use crate::ast::ASTNode;
|
use crate::ast::ASTNode;
|
||||||
use crate::mir::slot_registry::{get_or_assign_type_id, reserve_method_slot};
|
use crate::mir::slot_registry::{get_or_assign_type_id, reserve_method_slot};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|||||||
@ -4,11 +4,24 @@ use crate::mir::ValueId;
|
|||||||
|
|
||||||
/// Finalize call operands (receiver/args) using LocalSSA; thin wrapper to centralize usage.
|
/// Finalize call operands (receiver/args) using LocalSSA; thin wrapper to centralize usage.
|
||||||
pub fn finalize_call_operands(builder: &mut MirBuilder, callee: &mut Callee, args: &mut Vec<ValueId>) {
|
pub fn finalize_call_operands(builder: &mut MirBuilder, callee: &mut Callee, args: &mut Vec<ValueId>) {
|
||||||
|
// Step 1: LocalSSA materialization for receiver/args
|
||||||
crate::mir::builder::ssa::local::finalize_callee_and_args(builder, callee, args);
|
crate::mir::builder::ssa::local::finalize_callee_and_args(builder, callee, args);
|
||||||
|
|
||||||
|
// Step 2: Stabilize receiver placement within the current block
|
||||||
|
// Ensure a Copy right after PHIs and a tail Copy just before Call emission, so that
|
||||||
|
// dominance/order invariants are satisfied without duplicating logic at call sites.
|
||||||
|
if let Callee::Method { box_name, method, receiver: Some(r0), certainty } = callee.clone() {
|
||||||
|
if let Ok(r_after) = crate::mir::builder::schedule::block::BlockScheduleBox::ensure_after_phis_copy(builder, r0) {
|
||||||
|
if let Ok(r_tail) = crate::mir::builder::schedule::block::BlockScheduleBox::emit_before_call_copy(builder, r_after) {
|
||||||
|
*callee = Callee::Method { box_name, method, receiver: Some(r_tail), certainty };
|
||||||
|
} else {
|
||||||
|
*callee = Callee::Method { box_name, method, receiver: Some(r_after), certainty };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify block schedule invariants after emitting a call (dev-only WARNs inside).
|
/// Verify block schedule invariants after emitting a call (dev-only WARNs inside).
|
||||||
pub fn verify_after_call(builder: &mut MirBuilder) {
|
pub fn verify_after_call(builder: &mut MirBuilder) {
|
||||||
crate::mir::builder::schedule::block::BlockScheduleBox::verify_order(builder);
|
crate::mir::builder::schedule::block::BlockScheduleBox::verify_order(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Expression lowering split from builder.rs to keep files lean
|
// Expression lowering split from builder.rs to keep files lean
|
||||||
use super::{ConstValue, MirInstruction, ValueId};
|
use super::{MirInstruction, ValueId};
|
||||||
use crate::ast::{ASTNode, AssignStmt, ReturnStmt, BinaryExpr, CallExpr, MethodCallExpr, FieldAccessExpr};
|
use crate::ast::{ASTNode, AssignStmt, ReturnStmt, BinaryExpr, CallExpr, MethodCallExpr, FieldAccessExpr};
|
||||||
|
|
||||||
impl super::MirBuilder {
|
impl super::MirBuilder {
|
||||||
|
|||||||
@ -27,12 +27,12 @@ impl super::MirBuilder {
|
|||||||
)?;
|
)?;
|
||||||
Ok(dst)
|
Ok(dst)
|
||||||
} else {
|
} else {
|
||||||
// Legacy path - keep for compatibility
|
// Unified-off path: still encode callee as Value to avoid by-name resolution
|
||||||
let dst = self.value_gen.next();
|
let dst = self.value_gen.next();
|
||||||
self.emit_instruction(super::MirInstruction::Call {
|
self.emit_instruction(super::MirInstruction::Call {
|
||||||
dst: Some(dst),
|
dst: Some(dst),
|
||||||
func: callee_id,
|
func: callee_id,
|
||||||
callee: None, // Legacy call expression - use old resolution
|
callee: Some(crate::mir::definitions::call_unified::Callee::Value(callee_id)),
|
||||||
args: arg_ids,
|
args: arg_ids,
|
||||||
effects: super::EffectMask::PURE,
|
effects: super::EffectMask::PURE,
|
||||||
})?;
|
})?;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// Field access and assignment lowering
|
// Field access and assignment lowering
|
||||||
use super::{ConstValue, EffectMask, MirInstruction, ValueId};
|
use super::{EffectMask, MirInstruction, ValueId};
|
||||||
use crate::ast::ASTNode;
|
use crate::ast::ASTNode;
|
||||||
use crate::mir::slot_registry;
|
use crate::mir::slot_registry;
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use super::{ConstValue, MirBuilder, MirInstruction, ValueId};
|
use super::{MirBuilder, ValueId};
|
||||||
use crate::mir::loop_api::LoopBuilderApi; // for current_block()
|
use crate::mir::loop_api::LoopBuilderApi; // for current_block()
|
||||||
use crate::ast::{ASTNode, BinaryOperator};
|
use crate::ast::{ASTNode, BinaryOperator};
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use super::{EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId, BasicBlockId, ConstValue};
|
use super::{EffectMask, FunctionSignature, MirFunction, MirInstruction, MirModule, MirType, ValueId, BasicBlockId};
|
||||||
use crate::ast::ASTNode;
|
use crate::ast::ASTNode;
|
||||||
|
|
||||||
// Lifecycle routines extracted from builder.rs
|
// Lifecycle routines extracted from builder.rs
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
use crate::ast::ASTNode;
|
use crate::ast::ASTNode;
|
||||||
use crate::mir::builder::{MirBuilder, ValueId};
|
use crate::mir::builder::{MirBuilder, ValueId};
|
||||||
use crate::mir::builder::builder_calls::CallTarget;
|
use crate::mir::builder::builder_calls::CallTarget;
|
||||||
use crate::mir::{MirInstruction, TypeOpKind, MirType};
|
use crate::mir::{MirInstruction, TypeOpKind};
|
||||||
|
|
||||||
impl MirBuilder {
|
impl MirBuilder {
|
||||||
/// Handle static method calls: BoxName.method(args)
|
/// Handle static method calls: BoxName.method(args)
|
||||||
@ -30,8 +30,8 @@ impl MirBuilder {
|
|||||||
eprintln!("[builder] static-call {}", func_name);
|
eprintln!("[builder] static-call {}", func_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use legacy global-call emission to avoid unified builtin/extern constraints
|
// Emit unified global call to the static-lowered function (module-local)
|
||||||
self.emit_legacy_call(Some(dst), CallTarget::Global(func_name), arg_values)?;
|
self.emit_unified_call(Some(dst), CallTarget::Global(func_name), arg_values)?;
|
||||||
Ok(dst)
|
Ok(dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use super::super::{MirBuilder, MirInstruction, MirType, ValueId, BasicBlockId};
|
use super::super::{MirBuilder, MirType, ValueId, BasicBlockId};
|
||||||
|
|
||||||
/// Lightweight propagation at PHI when all inputs agree(型/起源)。
|
/// Lightweight propagation at PHI when all inputs agree(型/起源)。
|
||||||
/// 仕様は不変: 一致時のみ dst にコピーする(不一致/未知は何もしない)。
|
/// 仕様は不変: 一致時のみ dst にコピーする(不一致/未知は何もしない)。
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use super::super::{ConstValue, Effect, EffectMask, MirBuilder, MirInstruction, ValueId};
|
use super::super::{MirBuilder, ValueId};
|
||||||
|
|
||||||
/// Gate: whether instance→function rewrite is enabled.
|
/// Gate: whether instance→function rewrite is enabled.
|
||||||
fn rewrite_enabled() -> bool {
|
fn rewrite_enabled() -> bool {
|
||||||
@ -48,19 +48,17 @@ pub(crate) fn try_known_rewrite(
|
|||||||
if !( (module_has || allow_userbox_rewrite) || (from_new_origin && allow_new_origin) ) {
|
if !( (module_has || allow_userbox_rewrite) || (from_new_origin && allow_new_origin) ) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// Materialize function call: pass 'me' first, then args
|
// Materialize function call: pass 'me' first, then args (unified call)
|
||||||
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => return Some(Err(e)),
|
|
||||||
};
|
|
||||||
let mut call_args = Vec::with_capacity(arity + 1);
|
let mut call_args = Vec::with_capacity(arity + 1);
|
||||||
call_args.push(object_value);
|
call_args.push(object_value);
|
||||||
call_args.append(&mut arg_values);
|
call_args.append(&mut arg_values);
|
||||||
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
||||||
let dst = builder.value_gen.next();
|
let dst = builder.value_gen.next();
|
||||||
if let Err(e) = builder.emit_instruction(MirInstruction::Call {
|
if let Err(e) = builder.emit_unified_call(
|
||||||
dst: Some(dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap),
|
Some(dst),
|
||||||
}) { return Some(Err(e)); }
|
crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()),
|
||||||
|
call_args,
|
||||||
|
) { return Some(Err(e)); }
|
||||||
// Annotate and emit choose
|
// Annotate and emit choose
|
||||||
let chosen = fname.clone();
|
let chosen = fname.clone();
|
||||||
builder.annotate_call_result_from_func_name(dst, &chosen);
|
builder.annotate_call_result_from_func_name(dst, &chosen);
|
||||||
@ -95,16 +93,17 @@ pub(crate) fn try_known_rewrite_to_dst(
|
|||||||
let fname = crate::mir::builder::calls::function_lowering::generate_method_function_name(cls, method, arity);
|
let fname = crate::mir::builder::calls::function_lowering::generate_method_function_name(cls, method, arity);
|
||||||
let module_has = if let Some(ref module) = builder.current_module { module.functions.contains_key(&fname) } else { false };
|
let module_has = if let Some(ref module) = builder.current_module { module.functions.contains_key(&fname) } else { false };
|
||||||
if !((module_has || allow_userbox_rewrite) || (from_new_origin && allow_new_origin)) { return None; }
|
if !((module_has || allow_userbox_rewrite) || (from_new_origin && allow_new_origin)) { return None; }
|
||||||
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
|
// unified global function call (module-local)
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => return Some(Err(e)),
|
|
||||||
};
|
|
||||||
let mut call_args = Vec::with_capacity(arity + 1);
|
let mut call_args = Vec::with_capacity(arity + 1);
|
||||||
call_args.push(object_value);
|
call_args.push(object_value);
|
||||||
call_args.append(&mut arg_values);
|
call_args.append(&mut arg_values);
|
||||||
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
||||||
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
|
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
|
||||||
if let Err(e) = builder.emit_instruction(MirInstruction::Call { dst: Some(actual_dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap) }) { return Some(Err(e)); }
|
if let Err(e) = builder.emit_unified_call(
|
||||||
|
Some(actual_dst),
|
||||||
|
crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()),
|
||||||
|
call_args,
|
||||||
|
) { return Some(Err(e)); }
|
||||||
builder.annotate_call_result_from_func_name(actual_dst, &fname);
|
builder.annotate_call_result_from_func_name(actual_dst, &fname);
|
||||||
let meta = serde_json::json!({
|
let meta = serde_json::json!({
|
||||||
"recv_cls": cls,
|
"recv_cls": cls,
|
||||||
@ -145,19 +144,18 @@ pub(crate) fn try_unique_suffix_rewrite(
|
|||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
|
// unified
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => return Some(Err(e)),
|
|
||||||
};
|
|
||||||
let mut call_args = Vec::with_capacity(arg_values.len() + 1);
|
let mut call_args = Vec::with_capacity(arg_values.len() + 1);
|
||||||
call_args.push(object_value); // 'me'
|
call_args.push(object_value); // 'me'
|
||||||
let arity_us = arg_values.len();
|
let arity_us = arg_values.len();
|
||||||
call_args.append(&mut arg_values);
|
call_args.append(&mut arg_values);
|
||||||
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
||||||
let dst = builder.value_gen.next();
|
let dst = builder.value_gen.next();
|
||||||
if let Err(e) = builder.emit_instruction(MirInstruction::Call {
|
if let Err(e) = builder.emit_unified_call(
|
||||||
dst: Some(dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap),
|
Some(dst),
|
||||||
}) { return Some(Err(e)); }
|
crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()),
|
||||||
|
call_args,
|
||||||
|
) { return Some(Err(e)); }
|
||||||
builder.annotate_call_result_from_func_name(dst, &fname);
|
builder.annotate_call_result_from_func_name(dst, &fname);
|
||||||
let meta = serde_json::json!({
|
let meta = serde_json::json!({
|
||||||
"recv_cls": builder.value_origin_newbox.get(&object_value).cloned().unwrap_or_default(),
|
"recv_cls": builder.value_origin_newbox.get(&object_value).cloned().unwrap_or_default(),
|
||||||
@ -195,7 +193,11 @@ pub(crate) fn try_unique_suffix_rewrite_to_dst(
|
|||||||
call_args.append(&mut arg_values);
|
call_args.append(&mut arg_values);
|
||||||
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
||||||
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
|
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
|
||||||
if let Err(e) = builder.emit_instruction(MirInstruction::Call { dst: Some(actual_dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap) }) { return Some(Err(e)); }
|
if let Err(e) = builder.emit_unified_call(
|
||||||
|
Some(actual_dst),
|
||||||
|
crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()),
|
||||||
|
call_args,
|
||||||
|
) { return Some(Err(e)); }
|
||||||
builder.annotate_call_result_from_func_name(actual_dst, &fname);
|
builder.annotate_call_result_from_func_name(actual_dst, &fname);
|
||||||
let meta = serde_json::json!({
|
let meta = serde_json::json!({
|
||||||
"recv_cls": builder.value_origin_newbox.get(&object_value).cloned().unwrap_or_default(),
|
"recv_cls": builder.value_origin_newbox.get(&object_value).cloned().unwrap_or_default(),
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use super::super::{ConstValue, Effect, EffectMask, MirBuilder, MirInstruction};
|
use super::super::MirBuilder;
|
||||||
|
|
||||||
/// Early special-case: toString/stringify → str(互換)を処理。
|
/// Early special-case: toString/stringify → str(互換)を処理。
|
||||||
/// 戻り値: Some(result_id) なら処理済み。None なら通常経路へ委譲。
|
/// 戻り値: Some(result_id) なら処理済み。None なら通常経路へ委譲。
|
||||||
@ -31,17 +31,16 @@ pub(crate) fn try_early_str_like(
|
|||||||
"certainty": "Known",
|
"certainty": "Known",
|
||||||
});
|
});
|
||||||
super::super::observe::resolve::emit_choose(builder, meta);
|
super::super::observe::resolve::emit_choose(builder, meta);
|
||||||
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &chosen) {
|
// unified
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => return Some(Err(e)),
|
|
||||||
};
|
|
||||||
let mut call_args = Vec::with_capacity(1);
|
let mut call_args = Vec::with_capacity(1);
|
||||||
call_args.push(object_value);
|
call_args.push(object_value);
|
||||||
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
||||||
let dst = builder.value_gen.next();
|
let dst = builder.value_gen.next();
|
||||||
if let Err(e) = builder.emit_instruction(MirInstruction::Call {
|
if let Err(e) = builder.emit_unified_call(
|
||||||
dst: Some(dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap),
|
Some(dst),
|
||||||
}) { return Some(Err(e)); }
|
crate::mir::builder::builder_calls::CallTarget::Global(chosen.clone()),
|
||||||
|
call_args,
|
||||||
|
) { return Some(Err(e)); }
|
||||||
builder.annotate_call_result_from_func_name(dst, &chosen);
|
builder.annotate_call_result_from_func_name(dst, &chosen);
|
||||||
return Some(Ok(dst));
|
return Some(Ok(dst));
|
||||||
}
|
}
|
||||||
@ -62,15 +61,16 @@ pub(crate) fn try_early_str_like(
|
|||||||
"certainty": "Heuristic",
|
"certainty": "Heuristic",
|
||||||
});
|
});
|
||||||
super::super::observe::resolve::emit_choose(builder, meta);
|
super::super::observe::resolve::emit_choose(builder, meta);
|
||||||
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
|
// unified
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => return Some(Err(e)),
|
|
||||||
};
|
|
||||||
let mut call_args = Vec::with_capacity(1);
|
let mut call_args = Vec::with_capacity(1);
|
||||||
call_args.push(object_value);
|
call_args.push(object_value);
|
||||||
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
||||||
let dst = builder.value_gen.next();
|
let dst = builder.value_gen.next();
|
||||||
if let Err(e) = builder.emit_instruction(MirInstruction::Call { dst: Some(dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap), }) { return Some(Err(e)); }
|
if let Err(e) = builder.emit_unified_call(
|
||||||
|
Some(dst),
|
||||||
|
crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()),
|
||||||
|
call_args,
|
||||||
|
) { return Some(Err(e)); }
|
||||||
builder.annotate_call_result_from_func_name(dst, &fname);
|
builder.annotate_call_result_from_func_name(dst, &fname);
|
||||||
return Some(Ok(dst));
|
return Some(Ok(dst));
|
||||||
} else if cands.len() > 1 {
|
} else if cands.len() > 1 {
|
||||||
@ -85,15 +85,16 @@ pub(crate) fn try_early_str_like(
|
|||||||
"certainty": "Heuristic",
|
"certainty": "Heuristic",
|
||||||
});
|
});
|
||||||
super::super::observe::resolve::emit_choose(builder, meta);
|
super::super::observe::resolve::emit_choose(builder, meta);
|
||||||
let name_const = match crate::mir::builder::name_const::make_name_const_result(builder, &fname) {
|
// unified
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => return Some(Err(e)),
|
|
||||||
};
|
|
||||||
let mut call_args = Vec::with_capacity(1);
|
let mut call_args = Vec::with_capacity(1);
|
||||||
call_args.push(object_value);
|
call_args.push(object_value);
|
||||||
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
||||||
let dst = builder.value_gen.next();
|
let dst = builder.value_gen.next();
|
||||||
if let Err(e) = builder.emit_instruction(MirInstruction::Call { dst: Some(dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap), }) { return Some(Err(e)); }
|
if let Err(e) = builder.emit_unified_call(
|
||||||
|
Some(dst),
|
||||||
|
crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()),
|
||||||
|
call_args,
|
||||||
|
) { return Some(Err(e)); }
|
||||||
builder.annotate_call_result_from_func_name(dst, &fname);
|
builder.annotate_call_result_from_func_name(dst, &fname);
|
||||||
return Some(Ok(dst));
|
return Some(Ok(dst));
|
||||||
}
|
}
|
||||||
@ -158,7 +159,11 @@ pub(crate) fn try_early_str_like_to_dst(
|
|||||||
call_args.push(object_value);
|
call_args.push(object_value);
|
||||||
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
||||||
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
|
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
|
||||||
if let Err(e) = builder.emit_instruction(MirInstruction::Call { dst: Some(actual_dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap), }) { return Some(Err(e)); }
|
if let Err(e) = builder.emit_unified_call(
|
||||||
|
Some(actual_dst),
|
||||||
|
crate::mir::builder::builder_calls::CallTarget::Global(chosen.clone()),
|
||||||
|
call_args,
|
||||||
|
) { return Some(Err(e)); }
|
||||||
builder.annotate_call_result_from_func_name(actual_dst, &chosen);
|
builder.annotate_call_result_from_func_name(actual_dst, &chosen);
|
||||||
return Some(Ok(actual_dst));
|
return Some(Ok(actual_dst));
|
||||||
}
|
}
|
||||||
@ -185,7 +190,11 @@ pub(crate) fn try_early_str_like_to_dst(
|
|||||||
call_args.push(object_value);
|
call_args.push(object_value);
|
||||||
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
||||||
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
|
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
|
||||||
if let Err(e) = builder.emit_instruction(MirInstruction::Call { dst: Some(actual_dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap), }) { return Some(Err(e)); }
|
if let Err(e) = builder.emit_unified_call(
|
||||||
|
Some(actual_dst),
|
||||||
|
crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()),
|
||||||
|
call_args,
|
||||||
|
) { return Some(Err(e)); }
|
||||||
builder.annotate_call_result_from_func_name(actual_dst, &fname);
|
builder.annotate_call_result_from_func_name(actual_dst, &fname);
|
||||||
return Some(Ok(actual_dst));
|
return Some(Ok(actual_dst));
|
||||||
} else if cands.len() > 1 {
|
} else if cands.len() > 1 {
|
||||||
@ -208,7 +217,11 @@ pub(crate) fn try_early_str_like_to_dst(
|
|||||||
call_args.push(object_value);
|
call_args.push(object_value);
|
||||||
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
crate::mir::builder::ssa::local::finalize_args(builder, &mut call_args);
|
||||||
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
|
let actual_dst = want_dst.unwrap_or_else(|| builder.value_gen.next());
|
||||||
if let Err(e) = builder.emit_instruction(MirInstruction::Call { dst: Some(actual_dst), func: name_const, callee: None, args: call_args, effects: EffectMask::READ.add(Effect::ReadHeap), }) { return Some(Err(e)); }
|
if let Err(e) = builder.emit_unified_call(
|
||||||
|
Some(actual_dst),
|
||||||
|
crate::mir::builder::builder_calls::CallTarget::Global(fname.clone()),
|
||||||
|
call_args,
|
||||||
|
) { return Some(Err(e)); }
|
||||||
builder.annotate_call_result_from_func_name(actual_dst, &fname);
|
builder.annotate_call_result_from_func_name(actual_dst, &fname);
|
||||||
return Some(Ok(actual_dst));
|
return Some(Ok(actual_dst));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
use crate::mir::builder::MirBuilder;
|
use crate::mir::builder::MirBuilder;
|
||||||
use crate::mir::{ValueId, Callee};
|
use crate::mir::{ValueId, Callee};
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
||||||
pub enum LocalKind {
|
pub enum LocalKind {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use super::{ConstValue, Effect, EffectMask, MirInstruction, ValueId};
|
use super::{Effect, EffectMask, MirInstruction, ValueId};
|
||||||
use crate::ast::{ASTNode, CallExpr};
|
use crate::ast::{ASTNode, CallExpr};
|
||||||
use crate::mir::TypeOpKind;
|
use crate::mir::TypeOpKind;
|
||||||
use crate::mir::utils::is_current_block_terminated;
|
use crate::mir::utils::is_current_block_terminated;
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use super::{BasicBlock, BasicBlockId, EffectMask, MirInstruction, MirType, ValueId};
|
use super::{BasicBlock, BasicBlockId, EffectMask, MirInstruction, MirType, ValueId};
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// Function signature for MIR functions
|
/// Function signature for MIR functions
|
||||||
@ -329,6 +329,10 @@ pub struct ModuleMetadata {
|
|||||||
|
|
||||||
/// Optimization level used
|
/// Optimization level used
|
||||||
pub optimization_level: u32,
|
pub optimization_level: u32,
|
||||||
|
|
||||||
|
/// Dev idempotence markers for passes (optional; default empty)
|
||||||
|
/// Key format suggestion: "pass_name:function_name"
|
||||||
|
pub dev_processed_markers: HashSet<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MirModule {
|
impl MirModule {
|
||||||
|
|||||||
@ -4,16 +4,13 @@
|
|||||||
* SSA-form instructions with effect tracking for optimization
|
* SSA-form instructions with effect tracking for optimization
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::{Effect, EffectMask, ValueId};
|
use super::{EffectMask, ValueId};
|
||||||
use crate::mir::definitions::Callee; // Import Callee from unified definitions
|
use crate::mir::definitions::Callee; // Import Callee from unified definitions
|
||||||
use crate::mir::types::{
|
use crate::mir::types::{
|
||||||
BarrierOp, BinaryOp, CompareOp, ConstValue, MirType, TypeOpKind, UnaryOp, WeakRefOp,
|
BarrierOp, BinaryOp, CompareOp, ConstValue, MirType, TypeOpKind, UnaryOp, WeakRefOp,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::fmt;
|
// (unused imports removed)
|
||||||
|
|
||||||
// Kind-specific metadata (non-functional refactor scaffolding)
|
|
||||||
use crate::mir::instruction_kinds as inst_meta;
|
|
||||||
|
|
||||||
/// MIR instruction types - limited to 20 core instructions
|
/// MIR instruction types - limited to 20 core instructions
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|||||||
@ -2,10 +2,33 @@ use crate::mir::optimizer::MirOptimizer;
|
|||||||
use crate::mir::optimizer_stats::OptimizationStats;
|
use crate::mir::optimizer_stats::OptimizationStats;
|
||||||
use crate::mir::{BarrierOp, MirModule, TypeOpKind, ValueId, WeakRefOp};
|
use crate::mir::{BarrierOp, MirModule, TypeOpKind, ValueId, WeakRefOp};
|
||||||
|
|
||||||
|
fn idemp_enabled() -> bool {
|
||||||
|
std::env::var("NYASH_MIR_DEV_IDEMP").ok().as_deref() == Some("1")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn idemp_key(pass: &str, func_name: &str) -> String {
|
||||||
|
format!("{}:{}", pass, func_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn idemp_already_done(module: &crate::mir::MirModule, key: &str) -> bool {
|
||||||
|
module.metadata.dev_processed_markers.contains(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn idemp_mark(module: &mut crate::mir::MirModule, key: String) {
|
||||||
|
module.metadata.dev_processed_markers.insert(key);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn force_plugin_invoke(_opt: &mut MirOptimizer, module: &mut MirModule) -> OptimizationStats {
|
pub fn force_plugin_invoke(_opt: &mut MirOptimizer, module: &mut MirModule) -> OptimizationStats {
|
||||||
use crate::mir::MirInstruction as I;
|
use crate::mir::MirInstruction as I;
|
||||||
let mut stats = OptimizationStats::new();
|
let mut stats = OptimizationStats::new();
|
||||||
for (_fname, function) in &mut module.functions {
|
let pass_name = "normalize.force_plugin_invoke";
|
||||||
|
let func_names: Vec<String> = module.functions.keys().cloned().collect();
|
||||||
|
for fname in func_names {
|
||||||
|
if idemp_enabled() {
|
||||||
|
let key = idemp_key(pass_name, &fname);
|
||||||
|
if idemp_already_done(module, &key) { continue; }
|
||||||
|
}
|
||||||
|
let function = match module.functions.get_mut(&fname) { Some(f) => f, None => continue };
|
||||||
for (_bb, block) in &mut function.blocks {
|
for (_bb, block) in &mut function.blocks {
|
||||||
for inst in &mut block.instructions {
|
for inst in &mut block.instructions {
|
||||||
if let I::BoxCall {
|
if let I::BoxCall {
|
||||||
@ -28,6 +51,7 @@ pub fn force_plugin_invoke(_opt: &mut MirOptimizer, module: &mut MirModule) -> O
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if idemp_enabled() { let key = idemp_key(pass_name, &fname); idemp_mark(module, key); }
|
||||||
}
|
}
|
||||||
stats
|
stats
|
||||||
}
|
}
|
||||||
@ -38,7 +62,14 @@ pub fn normalize_python_helper_calls(
|
|||||||
) -> OptimizationStats {
|
) -> OptimizationStats {
|
||||||
use crate::mir::MirInstruction as I;
|
use crate::mir::MirInstruction as I;
|
||||||
let mut stats = OptimizationStats::new();
|
let mut stats = OptimizationStats::new();
|
||||||
for (_fname, function) in &mut module.functions {
|
let pass_name = "normalize.python_helper_calls";
|
||||||
|
let func_names: Vec<String> = module.functions.keys().cloned().collect();
|
||||||
|
for fname in func_names {
|
||||||
|
if idemp_enabled() {
|
||||||
|
let key = idemp_key(pass_name, &fname);
|
||||||
|
if idemp_already_done(module, &key) { continue; }
|
||||||
|
}
|
||||||
|
let function = match module.functions.get_mut(&fname) { Some(f) => f, None => continue };
|
||||||
for (_bb, block) in &mut function.blocks {
|
for (_bb, block) in &mut function.blocks {
|
||||||
for inst in &mut block.instructions {
|
for inst in &mut block.instructions {
|
||||||
if let I::PluginInvoke {
|
if let I::PluginInvoke {
|
||||||
@ -62,6 +93,7 @@ pub fn normalize_python_helper_calls(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if idemp_enabled() { let key = idemp_key(pass_name, &fname); idemp_mark(module, key); }
|
||||||
}
|
}
|
||||||
stats
|
stats
|
||||||
}
|
}
|
||||||
@ -80,7 +112,14 @@ pub fn normalize_legacy_instructions(
|
|||||||
if core13 {
|
if core13 {
|
||||||
array_to_boxcall = true;
|
array_to_boxcall = true;
|
||||||
}
|
}
|
||||||
for (_fname, function) in &mut module.functions {
|
let pass_name = "normalize.legacy_instructions";
|
||||||
|
let func_names: Vec<String> = module.functions.keys().cloned().collect();
|
||||||
|
for fname in func_names {
|
||||||
|
if idemp_enabled() {
|
||||||
|
let key = idemp_key(pass_name, &fname);
|
||||||
|
if idemp_already_done(module, &key) { continue; }
|
||||||
|
}
|
||||||
|
let function = match module.functions.get_mut(&fname) { Some(f) => f, None => continue };
|
||||||
for (_bb, block) in &mut function.blocks {
|
for (_bb, block) in &mut function.blocks {
|
||||||
for inst in &mut block.instructions {
|
for inst in &mut block.instructions {
|
||||||
match inst {
|
match inst {
|
||||||
@ -345,6 +384,7 @@ pub fn normalize_legacy_instructions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if idemp_enabled() { let key = idemp_key(pass_name, &fname); idemp_mark(module, key); }
|
||||||
}
|
}
|
||||||
stats
|
stats
|
||||||
}
|
}
|
||||||
@ -355,7 +395,14 @@ pub fn normalize_ref_field_access(
|
|||||||
) -> OptimizationStats {
|
) -> OptimizationStats {
|
||||||
use crate::mir::MirInstruction as I;
|
use crate::mir::MirInstruction as I;
|
||||||
let mut stats = OptimizationStats::new();
|
let mut stats = OptimizationStats::new();
|
||||||
for (_fname, function) in &mut module.functions {
|
let pass_name = "normalize.ref_field_access";
|
||||||
|
let func_names: Vec<String> = module.functions.keys().cloned().collect();
|
||||||
|
for fname in func_names {
|
||||||
|
if idemp_enabled() {
|
||||||
|
let key = idemp_key(pass_name, &fname);
|
||||||
|
if idemp_already_done(module, &key) { continue; }
|
||||||
|
}
|
||||||
|
let function = match module.functions.get_mut(&fname) { Some(f) => f, None => continue };
|
||||||
for (_bb, block) in &mut function.blocks {
|
for (_bb, block) in &mut function.blocks {
|
||||||
let mut out: Vec<I> = Vec::with_capacity(block.instructions.len() + 2);
|
let mut out: Vec<I> = Vec::with_capacity(block.instructions.len() + 2);
|
||||||
let old = std::mem::take(&mut block.instructions);
|
let old = std::mem::take(&mut block.instructions);
|
||||||
@ -462,6 +509,7 @@ pub fn normalize_ref_field_access(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if idemp_enabled() { let key = idemp_key(pass_name, &fname); idemp_mark(module, key); }
|
||||||
}
|
}
|
||||||
stats
|
stats
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
//! counts eliminations without rewriting uses (SSA update is TODO). This keeps
|
//! counts eliminations without rewriting uses (SSA update is TODO). This keeps
|
||||||
//! behavior identical while modularizing the pass for future enhancement.
|
//! behavior identical while modularizing the pass for future enhancement.
|
||||||
|
|
||||||
use crate::mir::{MirFunction, MirInstruction, MirModule, ValueId};
|
use crate::mir::{MirFunction, MirInstruction, MirModule, MirType, ValueId};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Run CSE across the module. Returns the number of eliminated expressions.
|
/// Run CSE across the module. Returns the number of eliminated expressions.
|
||||||
@ -19,6 +19,15 @@ pub fn eliminate_common_subexpressions(module: &mut MirModule) -> usize {
|
|||||||
fn cse_in_function(function: &mut MirFunction) -> usize {
|
fn cse_in_function(function: &mut MirFunction) -> usize {
|
||||||
let mut expression_map: HashMap<String, ValueId> = HashMap::new();
|
let mut expression_map: HashMap<String, ValueId> = HashMap::new();
|
||||||
let mut eliminated = 0usize;
|
let mut eliminated = 0usize;
|
||||||
|
let fast_int = std::env::var("NYASH_LLVM_FAST_INT").ok().as_deref() == Some("1");
|
||||||
|
|
||||||
|
// Helper: check if both operands are numeric (Integer/Float) via value type hints
|
||||||
|
let is_numeric = |vid: ValueId| -> bool {
|
||||||
|
match function.metadata.value_types.get(&vid) {
|
||||||
|
Some(MirType::Integer) | Some(MirType::Float) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (_bid, block) in &mut function.blocks {
|
for (_bid, block) in &mut function.blocks {
|
||||||
for inst in &mut block.instructions {
|
for inst in &mut block.instructions {
|
||||||
@ -26,8 +35,25 @@ fn cse_in_function(function: &mut MirFunction) -> usize {
|
|||||||
let key = instruction_key(inst);
|
let key = instruction_key(inst);
|
||||||
if let Some(&existing) = expression_map.get(&key) {
|
if let Some(&existing) = expression_map.get(&key) {
|
||||||
if let Some(dst) = inst.dst_value() {
|
if let Some(dst) = inst.dst_value() {
|
||||||
// Count as eliminated; rewriting uses is a future improvement.
|
// Prefer existing SSA value in the same block when FAST_INT is enabled.
|
||||||
let _ = (existing, dst); // keep variables referenced
|
if fast_int {
|
||||||
|
match inst {
|
||||||
|
MirInstruction::BinOp { op, lhs, rhs, .. } => {
|
||||||
|
// Only rewrite Add when both operands are numeric (avoid String + String)
|
||||||
|
let allow = match op {
|
||||||
|
crate::mir::BinaryOp::Add => is_numeric(*lhs) && is_numeric(*rhs),
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
if allow { *inst = MirInstruction::Copy { dst, src: existing }; }
|
||||||
|
}
|
||||||
|
MirInstruction::Compare { .. }
|
||||||
|
| MirInstruction::UnaryOp { .. }
|
||||||
|
| MirInstruction::Cast { .. } => {
|
||||||
|
*inst = MirInstruction::Copy { dst, src: existing };
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
eliminated += 1;
|
eliminated += 1;
|
||||||
}
|
}
|
||||||
} else if let Some(dst) = inst.dst_value() {
|
} else if let Some(dst) = inst.dst_value() {
|
||||||
|
|||||||
@ -63,36 +63,10 @@ pub fn inject_method_ids(module: &mut MirModule) -> usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
I::PluginInvoke {
|
I::PluginInvoke { .. } => {
|
||||||
dst,
|
// Keep PluginInvoke as-is and let the interpreter handle it via plugin host.
|
||||||
box_val,
|
// This avoids premature lowering that can mask plugin-specific calling
|
||||||
method,
|
// conventions. Method ID resolution for plugins is handled at runtime.
|
||||||
args,
|
|
||||||
effects,
|
|
||||||
} => {
|
|
||||||
if let Some(bt) = origin.get(box_val).cloned() {
|
|
||||||
// Resolve id as above
|
|
||||||
let mid_u16 = if let Some(h) = host_guard.as_ref() {
|
|
||||||
match h.resolve_method(&bt, method) {
|
|
||||||
Ok(mh) => Some(mh.method_id as u16),
|
|
||||||
Err(_) => resolve_slot_by_type_name(&bt, method),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resolve_slot_by_type_name(&bt, method)
|
|
||||||
};
|
|
||||||
if let Some(mid) = mid_u16 {
|
|
||||||
// Rewrite to BoxCall with method_id
|
|
||||||
*inst = I::BoxCall {
|
|
||||||
dst: dst.take(),
|
|
||||||
box_val: *box_val,
|
|
||||||
method: method.clone(),
|
|
||||||
method_id: Some(mid),
|
|
||||||
args: args.clone(),
|
|
||||||
effects: *effects,
|
|
||||||
};
|
|
||||||
injected += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,4 +21,4 @@ pub use control_flow::{
|
|||||||
|
|
||||||
// PHI挿入ヘルパー(MirBuilderのextension methodsとして実装)
|
// PHI挿入ヘルパー(MirBuilderのextension methodsとして実装)
|
||||||
// 使用例: self.insert_phi(vec![(block1, val1), (block2, val2)])?
|
// 使用例: self.insert_phi(vec![(block1, val1), (block2, val2)])?
|
||||||
pub use phi_helpers::*;
|
// Re-exports removed to reduce unused-import warnings. Use builder methods directly.
|
||||||
|
|||||||
@ -98,6 +98,13 @@ impl MirVerifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 10. Ret-block purity (optional, dev-only)
|
||||||
|
if crate::config::env::verify_ret_purity() {
|
||||||
|
if let Err(mut rpe) = self.verify_ret_block_purity(function) {
|
||||||
|
local_errors.append(&mut rpe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if local_errors.is_empty() {
|
if local_errors.is_empty() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@ -255,6 +262,32 @@ impl MirVerifier {
|
|||||||
if errors.is_empty() { Ok(()) } else { Err(errors) }
|
if errors.is_empty() { Ok(()) } else { Err(errors) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verify that any block ending with Return contains no side-effecting instructions before it.
|
||||||
|
/// Allowed before Return: Const, Copy, Phi, Nop only. Others are considered side-effecting for this policy.
|
||||||
|
fn verify_ret_block_purity(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||||
|
use super::MirInstruction as I;
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
for (bid, bb) in &function.blocks {
|
||||||
|
if let Some(I::Return { .. }) = bb.terminator {
|
||||||
|
for (idx, inst) in bb.instructions.iter().enumerate() {
|
||||||
|
let allowed = matches!(
|
||||||
|
inst,
|
||||||
|
I::Const { .. } | I::Copy { .. } | I::Phi { .. } | I::Nop
|
||||||
|
);
|
||||||
|
if !allowed {
|
||||||
|
let name = format!("{:?}", inst);
|
||||||
|
errors.push(VerificationError::RetBlockSideEffect {
|
||||||
|
block: *bid,
|
||||||
|
instruction_index: idx,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errors.is_empty() { Ok(()) } else { Err(errors) }
|
||||||
|
}
|
||||||
|
|
||||||
/// Reject legacy instructions that should be rewritten to Core-15 equivalents
|
/// Reject legacy instructions that should be rewritten to Core-15 equivalents
|
||||||
/// Skips check when NYASH_VERIFY_ALLOW_LEGACY=1
|
/// Skips check when NYASH_VERIFY_ALLOW_LEGACY=1
|
||||||
fn verify_no_legacy_ops(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
fn verify_no_legacy_ops(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user