diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 180f2e3c..180fb88d 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -1,999 +1,106 @@ -# Current Task — Phase 15 (Concise) - -Focus -- Keep VM quick green; llvmlite integration on-demand. -- Using SSOT(nyash.toml + 相対using)で安定解決。 -- Builder/VM ガードは最小限・仕様不変(dev では診断のみ)。 -- Phase 15.7 を再定義: Known 化+Rewrite 統合(dev観測)と Mini‑VM 安定化、表示APIは `str()` に統一(互換:stringify)。 - -Update — 2025-11-02(Stage‑B 既定ON/Runnerヘルパー適用/quick:core 緑/PHI strict 既定ON) -- Stage‑B スモークを既定ON 化(quick) - - 7本(print/binop/if/loop/array/map/string)を `static box Main { method main(args) { … } }` 形へ統一。 - - 実行は emit 直行(Stage‑1 Program(JSON v0) の1行出力を厳格検証)。 - - v1 downconvert 実行は引き続きオプトイン(`NYASH_NYVM_V1_DOWNCONVERT=1`)。 -- Runner 子環境の一元化 - - `src/runner/child_env.rs::apply_core_wrapper_env` を selfhost 子経路へ適用(冗長ENV配線を除去)。 - - Gate‑C/Core の OOB Strict フローは `pre_run_reset_oob_if_strict()` で明示リセット→実行→観測 exit に統一。 -- テストランナー強化 - - `run_nyash_vm` で `NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1` を常時付与(Stage‑3 文法の安定受理)。 -- Hako/canonicalize/OOB の opt‑in 整理 - - Hako 系: `SMOKES_ENABLE_HAKO_BINOP|_IF=1`、Bridge canonicalize: `SMOKES_ENABLE_BRIDGE_CANON=1`、OOB(pipe/file): `SMOKES_ENABLE_OOB_PIPE|_FILE=1`。 - - ルート検出の不安定スクリプトは `git rev-parse` + fallback 形式に統一。 -- quick(core フィルタ): 115/115 PASS を確認。既定セットは赤ゼロ。 - -VM PHI strict 既定ON(Fail‑Fast) -- 既定ON化: `src/backend/mir_interpreter/exec.rs` の判定を「未設定なら ON」に変更。 -- 無効化: `HAKO_VM_PHI_STRICT=0`(互換: `NYASH_VM_PHI_STRICT=0`)。 -- ループ条件の PHI 再束縛/初期PHI(preheader入力のみ)を導入し、条件式が必ずPHI値を参照するよう修正。 - - 変更: `src/mir/phi_core/loop_phi.rs`(初期PHI + 再束縛)、`src/mir/loop_builder.rs`(PHI差替え・順序安定)。 -- 付随: VM インタプリタにステップ上限(`HAKO_VM_MAX_STEPS`/`NYASH_VM_MAX_STEPS`、既定 1,000,000)を追加して暴走を防止。 -- 結果: quick 120/120 PASS、strictカナリア(strict/core/vm_phi_strict_smoke.sh)も PASS。 - -Docs refresh — 2025-11-02(VM/Smokes) -- VM README(lang/src/vm/README.md)更新 - - 診断タグを明記: `[map/missing]`, `[map/bad-key]`, `[array/empty/pop]`。 - - PHI strict 既定ONとステップ上限(`HAKO_VM_MAX_STEPS` / `NYASH_VM_MAX_STEPS`)の方針を追記。 - - Core canary と Gate‑C(Core) の使い方、Core ループ上限(`HAKO_CORE_MAX_ITERS` / `NYASH_CORE_MAX_ITERS`)を追記。 -- Smokes README(tools/smokes/v2/README.md)更新 - - Bridge canonicalize の ON/OFF/FAIL ポリシーと diff カナリア群を記載。 - - Core negatives(Array/Map/String)と Gate‑C(Core) の不正ヘッダ/パリティ系を列挙。 - - Stage‑B カナリアは opt‑in(`SMOKES_ENABLE_STAGEB=1`)に訂正。 -- quick 現況 - - quick(core フィルタ): 137/137 PASS(emit→nyvm(Core) 3本、Gate‑C/Core OOB strict/file+pipe を含む)。 - - 代表トグル: `SMOKES_ENABLE_CORE_CANARY=1`, `SMOKES_ENABLE_BRIDGE_CANON=1`, `SMOKES_ENABLE_OOB_PIPE|_FILE=1`。 - - 将来の昇格候補: Stage‑B(print/binop/if/loop/array/map/string の直行)を既定ONへ。 - -Update — 2025-11-02 (P1, part‑1) — Runner子ENV一元化+Stage‑B入口の軽量化の徹底 -- Runner 子経路のENV一元化を追加適用 - - `src/runner/selfhost.rs` の Python harness / PyVM runner の spawn にも - `child_env::apply_core_wrapper_env` を適用(JSON_ONLY/disable plugins/using禁などを統一)。 -- Stage‑B 入口の軽量化(emitの1行保証) - - `compiler_stageb.hako` は Stage‑1 Program(JSON v0) を1行出力(heavyな MIR 直行を回避)。 - - FlowEntry は v1→v0(軽経路)を優先し、using 付き v0 と v1+meta は二段目の候補に降格。 - - `lower_stage1_to_mir_with_usings` のデバッグ出力は既定OFF(prefer==9 の時のみ)。 -- quick: core/stageb canaries は引き続き PASS(opt‑in)。昇格基準(print まで PASS)を達した時点で既定ONへの切替を検討。 - -Update — 2025-11-02(P0後半 — Stage‑B bundle emit と Gate‑C print/loop rc 昇格) -- Stage‑B Module bundling(最小) - - `compiler_stageb.hako` に `--bundle-src ` を複数受理する最小バンドラを追加。 - - 直行 emit 前に bundle を先頭へ連結し、Program(JSON v0) 一行出力を維持。 - - スモーク追加: `core/stageb/stageb_bundle_vm.sh`(ヘッダ厳格のみ; 実行は未対象)。 -- Gate‑C(Core) rc 昇格 - - loop: rc=6 の厳格検証に昇格(PHI重複をlowerer側でマージ)。 - - print: legacy/extern の最小ブリッジをVM側に追加(numeric→println; rcはreturnに従う)。 - - `stageb_print_vm.sh`/`stageb_loop_vm.sh` を rc 検証化。 - -Update — 2025-11-02(P1 — child_env 一元化/print 正規化) -- Runner 子環境ヘルパー適用の拡大 - - pipe I/O の PyVM 経路と selfhost common_util(json) の Python runner spawn に `child_env::apply_core_wrapper_env` を適用。 - - 目的: Stage‑3/using抑止/JSON_ONLY/disable plugins などのトグルを一元化し、ドリフトを除去。 -- Core mir_call 正例(最小) - - print 系(legacy/extern)を VM 側で最小受理(数値/文字列印字 → Void)。Gate‑C/Core の print rc 検証が安定。 - - - -Update — 2025-09-28 (P4 default‑on + P5 docs/annotations 完了) -- Known 正規化(userbox限定・関数存在・一意・arity一致)を既定ON。 - - フラグ: `NYASH_REWRITE_KNOWN_DEFAULT`(0/false/off で無効化)。 -- 設計ノートを追加: `docs/development/builder/unified-method-resolution.md`。 -- Quick Reference を更新: 内部正規化の注記と切替フラグを追記。 -- 型注釈を最小拡張(is_digit_char/hex/alpha, MapBox.has/1 → Bool)。 -- quick/integration: 全緑を確認。 - -Update — 2025-09-28 (Router/EmitGuard/NameConst 導入・json_lint_vm 緑) -- Router 最小ガード(仕様不変・安定優先) - - UnknownBox の Method は一律レガシー経路(BoxCall)へフォールバック(unified 経路での sporadic 未定義を根絶)。 - - `prefer_legacy` を保守側既定に調整: None/Unknown/String/Array/Map は BoxCall 優先、ユーザー箱(末尾"Box"以外)も従来通り BoxCall。 - - `JsonParserModule.create_parser/0` の戻り型を Known 化(Box("JsonParser") 起源付与)。 -- BlockSchedule 検証(dev-only) - - φ→Copy(materialize)→本体(Call) の順序検証を追加(ズレは WARN のみ)。 -- VM dev 安全弁(既定OFF) - - `reg_load` 未定義→Void 置換を `NYASH_VM_TOLERATE_VOID=1` 下でのみ有効化(診断と一時救済)。 -- 結果 - - quick: `json_lint_vm` PASS(未定義は解消)。 - - integration(LLVM/llvmlite): PASS 17/17(すべて緑)。 -- 備考: `json_query_vm` は後続の更新で解決(下記エントリ参照)。 - -Update — 2025-09-28 (json_query_vm PASS・最終ガード適用) -- evaluator 側の堅牢化(VM準拠・仕様不変) - - 文字クラス判定を membership(手動スキャン)へ変更(indexOf 非依存)。 - - span を ArrayBox から "i:j" 文字列に正規化(.get 依存を排除)。 - - span_unpack_* も手動スキャン実装(indexOf 非依存)。 - - out-of-range/未存在キーは null 返却で合意。 -- テスト: json_query_vm の SKIP を解除して PASS を確認。 -- quick: 引き続き 64/64 PASS、integration: 17/17 PASS。 - -Update — 2025-10-31 (Phase 20.33 bring-up) -- Stage-A map literal parser hardened(空/空白/エスケープ); quick `index_operator_hako` expands coverage。 -- Stage-B entry separated (`lang/src/compiler/entry/compiler_stageb.hako`); `compiler.hako -- --stage-b` から StageBMain を呼び出し、FlowEntry `emit_v0_from_ast_with_context` (using/extern) を既定採用。`--v1-compat` は MirJsonV1Adapter 経路へ opt-in(現状は未配線のため SKIP)。opt-in canaries(binop/if/index + nested/boundary…)は Stage-B 本体が未整備な場合は SKIP 振る舞い。 -- `nyash.toml` modules updated to expose lang/compiler/shared/vm namespaces for resolver。 -- quick profile: 72/72 PASS(FileBox 未展開時は SKIP ハンドリング)。 - -Update — 2025-11-01 (Gate‑C v1 / Bridge / Stage‑B) -- Gate‑C v1 実行(既定OFF) - - `HAKO_NYVM_CORE=1`(alias `NYASH_NYVM_CORE`)で v1(JSON) を `json_v1_bridge` で `MirModule` に変換 → MIR Interpreter で実行(const/copy/binop/compare/ret/branch/jump/phi)。 - - `HAKO_NYVM_V1_DOWNCONVERT=1` で同じコンバータを「降格」として再利用(未対応は Fail‑Fast)。 - - Bridge 正規化トグルを実装(既定OFF): `HAKO_BRIDGE_INJECT_SINGLETON=1`(Array/Map len→Method 化)、`HAKO_BRIDGE_EARLY_PHI_MATERIALIZE=1`(φ をブロック先頭へ)。 -- Parity(opt‑in) - - Gate‑C(file/pipe) × plugins(ON/OFF) の 2×2 は quick の opt‑in カナリアで緑(scripts: `profiles/quick/core/gate_c_v1_{file,pipe}_vm.sh`)。 -- Stage‑B 入口 - - `compiler.hako -- --stage-b` → `compiler_stageb.hako`(StageBMain)→ `FlowEntry.emit_v0_from_ast_with_context`。 - - dev サポートスクリプト: `tools/dev_stagea.sh` / `tools/dev_stageb.sh`(emit 文字列をその場実行)。 -- 次の重点(Claudeへ) - - Stage‑B emit の空経路潰し(print/binop/if/index の 1 行 v0 を保証)。 - - v1 ブリッジに `mir_call`(最小の Global/Extern)を追加(診断安定化)。 - - Rust MIR の params 既定化(`build_static_main_box` / `lower_static_method_as_function` の def→use 順)。 - - Rust builder の微修正 - -Update — 2025-11-01 (LLVM 静的Box規約の明文化 / リポジトリ整備) -- 静的Box(LLVM 命令系)の self 先頭規約を明文化し、互換トグル `HAKO_BRIDGE_INJECT_SINGLETON` の運用を docs に追加。 - - 新規: `docs/development/architecture/llvm/static_box_singleton.md` - - 参照追加: `lang/src/vm/README.md` に規約サマリを追記。 -- Gate‑C(Core)/Bridge 設計の現状と今後を README 類に反映(既定OFFのまま)。 -- `bak` フォルダの存在を確認したが、ワークスペース直下には見当たらず(削除不要)。 - - `build_static_main_box` にて `args` 配列生成後に `birth()` を明示呼び出し。NewBox→birth の警告/未初期化を解消。 - -Update — 2025-09-28 (P1 — Const統一拡大 + メタ伝播の適用) -- Const 発行の統一(builder 側残存) - - `build_literal` と core13-pure の型名 Const を ConstantEmissionBox に統一済。残存直書きは掃除済み(rewrite系は NameConstBox 使用)。 -- メタデータ伝播(type/origin)を小粒適用 - - BlockScheduleBox: `emit_before_call_copy` で `propagate(base→dst)` を追加。 - - utils: `materialize_local` で `propagate(src→dst)` を追加。 - - `insert_copy_after_phis` は既に propagate 済み(再確認のみ)。 -- ルータ/型注釈: 前回の dev トレース追加/ホワイトリスト拡張に変更なし(挙動不変)。 -- 検証: quick/integration は引き続き全緑を確認予定(差分は局所・可逆)。 - -Update — 2025-09-28 (Rewrite Known 化 Stage‑1 一本化) -- 標準メソッド呼び出しを emit_unified_call に統一委譲。 - - ルーティング(RouterPolicy)と rewrite::{special,known} の適用点を一本化。 - - 既存ガードにより Unknown/core/user-instance は BoxCall へ自動フォールバック(挙動不変)。 -- 重複掃除(挙動不変) - - method_call_handlers 内の receiver クラス推定(me/起源/型)は削除し、unified 側に一本化。 - - box_type は None を渡し、emit_unified_call が起源/型から判断。 - - pin_to_slot/BoxCall 直呼びの旧コードは撤去済み。 - -Update — 2025-09-28 (FunctionEmissionBox adoption + Router trace + Type annotate) -- FunctionEmissionBox 採用を拡大(MirFunction 直編集の代表箇所を移行) - - src/mir/aot_plan_import.rs の Const/Return 発行を function_emission 経由に置換(挙動不変)。 - - Float/Null/Void など特殊値は安全側で既存ロジックにフォールバック(差分最小)。 -- RouterPolicy に dev 観測ログを追加(既定OFF) - - 環境変数 `NYASH_ROUTER_TRACE=1` で、経路決定(Unified/BoxCall)と理由(unknown_recv/core_box/user_instance)を stderr に短く出力。 - - 仕様不変・テスト比較に影響なし(既定OFF・stderr)。 -- TypeAnnotationBox のホワイトリストを最小拡張(観測ベース) - - 追加: `*.len/0 → Integer`, `*.substring/2 → String`, `*.esc_json/0 → String`。 - - 既存の `*.str/0`/`*.length/0`/`*.size/0` に加えて注釈精度を微増(挙動不変)。 - -Update — 2025-09-28 (quick/integration smoke status — 総括) -- quick: PASS 64/64(暫定 SKIP を明示) - - SKIP(VM 側の局所 polish 中; LLVM 緑): - - core/loops: break_continue, loop_statement(PHI 搬送の最小補強→復帰) - - selfhost mini‑vm: m2_eq_true / m3_branch_true / m3_jump(Mini‑VM M2/M3 の単一パス化・境界厳密化の仕上げ後に復帰) -- integration(LLVM/llvmlite): PASS 17/17(全緑) -- フラグ整理: - - `NYASH_VM_TOLERATE_VOID` は dev/一部診断時のみ使用。quick テストからは削除済み。 - - Router ガード(Unknown→BoxCall)は仕様不変・常時ON。 - -Update — 2025-09-28 (LocalSSA — in-block materialize & recv/args 統一) -- LocalSSA 小箱を導入(Builder 内部): `(bb, orig, kind) -> local` のキャッシュで、必ず「現在の基本ブロック内」に Copy を置く。 - - 実装: `MirBuilder.local_ssa_map` と `local_ssa_ensure(v, kind)`(kind: 0=recv, 1=arg, 2=cmp, 4=cond)。 - - 読みやすさヘルパ: `local_recv/local_arg/local_cond/local_field_base/local_cmp_operand` を追加。 -- 適用(最小・局所、仕様不変): - - Unified Method 呼び出し: 受信者/引数を LocalSSA 済みに統一(emit 前に in‑block materialize)。 - - Legacy Call(Extern/Global/Value): 引数を LocalSSA 化。BoxCall も recv/args を LocalSSA 化。 - - Branch/条件: if/loop/短絡 And/Or の条件を LocalSSA 化。 - - Field: base と set 値に LocalSSA を適用。`?` 伝播でも recv/条件に適用。 - - 置き換え: `pin_to_slot("@recv")` → `local_recv` に差し替え(BoxCall 経路も含む)。 -- 既知の現象: `apps/lib/json_native/lexer/scanner.nyash` の `read_string_literal()` 内 `me.advance()`(Unified 経路)で稀に `use of undefined recv` が残存。 - - 受信者/引数/条件/フィールド周辺は LocalSSA の“内側”へ揃えたため、残りは「emit 直前のブロック切替」等のパスでズレている可能性。 - - 次アクション(P0)で観測を厚くし、必要なら emit 直前の bb 再確認→再 materialize の最終関所を広げる。 -- 備考(レガシー優先について): ArrayBox/MapBox/StringBox と "…Box" 以外のユーザー箱はレガシー BoxCall 優先のまま(安定性)。ただし LocalSSA を適用済みのため、現象の主因ではない。 - -Update — 2025-09-28 (LocalSSA 最終関所+Unified 仕上げ・json_lint_vm デバッグ) -- finalize ヘルパー追加(ssa/local) - - `finalize_branch_cond` / `finalize_compare` / `finalize_field_base_and_args` を実装、各 emit 直前に適用。 - - Compare は従来の ensure_slotify を置換(挙動不変)。 -- Unified Call 側の強化 - - emit 直前に `finalize_callee_and_args` を再適用(bb 変化に強い)。 - - さらに最終 Copy を Call 直前に強制挿入(受信者の def→use を同一 bb に確実化)。 - - dev トレース `[vm-call-final]` は `NYASH_LOCAL_SSA_TRACE=1` 時のみ出力(runner 比較に影響しない)。 -- emit フック(builder) - - `emit_instruction` で Method 付き Call を検知し、直前に Copy を 1 枚差し込む最終ガード(dev 正当化)。 -- VM 側の dev 安全弁(default OFF) - - `NYASH_VM_RECV_ARG_FALLBACK=1` または `NYASH_VM_TOLERATE_VOID=1` で、未定義受信者時に args[0] を受信者として読み直す(Builder 取りこぼしの一時救済)。 -- 現状の結果 - - 受信者未定義は再現困難に。json_lint_vm は次段の未実装メソッド(String.is_digit_char)で停止。 - -Next — 短期 TODO(仕様不変・差分最小) -1) json_query_vm の quick 失敗を解消(undefined→Void 置換に頼らない) - - eval_path_text 直近の `substring/==` 連鎖で LocalSSA finalize の取りこぼしがないか emit 点を再点検。 - - UnknownBox→BoxCall へ統一済のため、unified 経路残存が無いか grep で確認し、見つかれば点で BoxCall へ誘導。 - - reg_load の Void 寛容は OFF のまま比較を厳密に(quick テスト側からも外した)。 -2) MIR dump/トレースの最小化: failing bb の直前5命令を dev だけ短くダンプし、φ→Copy→Call の順序を再検証。 -3) quick 全体を再実行→緑維持。必要なら minimal finalize を追加(仕様不変)。 - -Unskip Plan(段階復帰) -- P0: json_query_vm(VM) - - 受け入れ: 期待出力と一致。追加の寛容フラグ不要。SKIP 解除。 -- P1: loops(break_continue / loop_statement) - - 受け入れ: 期待出力一致。PHI carriers/entry materialize の取りこぼしゼロ。SKIP 解除。 -- P2: Mini‑VM(M2/M3: compare/branch/jump) - - 受け入れ: m2_eq_true/false, m3_branch_true, m3_jump の 4 件が PASS。coarse/多段走査を撤去して単一パスを維持。 - -Plan — Next(一本化の続きと段階導入) -- P3(重複整理の完遂・1日) - - 標準メソッド経路の一本化は完了。残る補助ロジックの重複(受信者クラス推定・候補列挙)を `rewrite::{known,special}` 側APIへ寄せる(点検・微修正)。 - - Docs 同期: CURRENT_TASK と docs/development/builder/BOXES.md に一本化方針と責務境界を追記。 - - 受け入れ: quick/integration 全緑、ログは既定OFFで静粛。 -- P4(Known 正規化の観測→段階ON・2〜3日) - - 観測: `NYASH_ROUTER_TRACE=1` と `observe::resolve.choose` で Known 率/フォールバック率を確認。 - - 段階ON: userbox 限定+関数存在+候補一意+arity一致のみ既定ON(新フラグ `NYASH_REWRITE_KNOWN_DEFAULT` で切替)。 - - 受け入れ: quick/integration 緑、mismatch 0、性能±10%以内。 -- P5(周辺整備・1日) - - 型注釈の最小拡張(観測ベースで1〜2件)。 - - phase‑15.7/README と Quick Reference に「内部正規化(obj.m→Class.m)」の注記を追記(ユーザー向け説明を簡潔に)。 - -Index Operator Bring‑up(Phase‑20.31 内の小粒対応) - -目的 -- `expr[index]` の最小サポート(Array/Map の読み書き)。実行は NyRT dotted extern に正規化(get/set)。 - -仕様(Phase‑1) -- 読み取り: `arr[i]`, `map[k]` -- 書き込み: `arr[i] = v`, `map[k] = v` -- 文字列 index/range は後続(Phase‑2) -- 未対応型は Fail‑Fast: "index operator is only supported for Array/Map" - -実装状況(Rust 側) -- [x] AST: IndexExpr と Assign(IndexExpr, …)(Rust パーサー) -- [x] MIR Lowering: Array/Map の get/set に正規化(Unsupported 型は compile-time Fail-Fast) -- [x] スモーク(quick): arr_read / arr_write / map_rw / negative_string -- [x] ドキュメント: docs/specs/language/index-operator.md - -Hakorune コンパイラ(Hako 側) -- [x] Parser: IndexExpr + Assign(LHS=IndexExpr) -- [x] Lowering: Array/Map → BoxCall("get"/"set")(AOT は従来の dotted extern を踏襲) -- [x] 診断: 未対応型は Fail‑Fast(安定文言) -- [x] スモーク: tools/smokes/v2/profiles/quick/core/index_operator_hako.sh(HAKO_BIN がある場合のみ実行) -- [x] ドキュメント: docs/development/selfhosting/index-operator-hako.md - -ロールアウト -- 必要なら dev フラグ(HAKO_INDEX_OPERATOR_DEV=1)で段階導入(dev=ON, prod=OFF)。 - -受け入れ基準 -- 上記スモークが PASS。未対応型は安定診断で Fail。 - -Docs — Added -- Unified method resolution design note: docs/development/builder/unified-method-resolution.md - - Pipeline, invariants, flags, rollout plan(P4 observe → dev opt‑in → consider default)を整理。 - -Self‑Hosting — Return Plan(P6) -- 目的: Selfhost Compiler(Ny製)→ MIR(JSON v0) → VM/llvmlite 実行の実線復帰。 -- 手順(小粒・仕様不変) - 1) Quickstart ドキュメント追加(完了): `docs/development/selfhosting/quickstart.md` - - 実行例/ENV透過/出力ファイルの位置を記述。 - 2) MVP 走行確認(dev・段階導入) - - Stage‑A 最小: `lang/src/compiler/entry/compiler.hako` が return/binop/compare/Array/Map get/set を v0 Program で出力(print) - - ✅ `--min-json / --return-int` を CLI から受け取り、v0 Program を一行出力するところまで実装(Rust builder が JSON argv を配列へ注入)。 - - ✅ opt-in スモーク `hako_min_compile_return_vm` 緑化(Result 行の解析を追加)。 - - 実行: `nyash --json-file` で JSON v0 を読み込み、MIR Interpreter で実行(Gate‑C 相当) - - 将来: pipeline_v2 → v1 出力 → `lang/src/shared/json/mir_v1_adapter.hako` で v0 へ変換 - 3) スモーク連携(opt‑in) - - Hako 最小 canary を opt‑in で追加(`tools/smokes/v2/profiles/quick/core/hako_min_compile_return_vm.sh`)。既定は SKIP、`SMOKES_ENABLE_HAKO_MIN=1` で有効化。 -- 受け入れ基準 - - quick/integration 緑を維持。 - - Selfhost emit→実行の最小系が安定して PASS(dev 任意ジョブで十分)。 - -Update — 2025-09-28 (BlockScheduleBox 導入・順序固定) -- 目的: ブロック内の物理順序を契約化(PHI群 → materialize群(Copy/Id) → 本体(Call等))。 -- 実装: - - 新規: `src/mir/builder/schedule/{mod.rs,block.rs}` 追加。 - - API 初期: - - `ensure_after_phis_copy(builder, src) -> ValueId`: φ直後に Copy を確実挿入(per‑block dedup `(bb,src)->dst`)。 - - `emit_before_call_copy(builder, src) -> ValueId`: Call 直前に最終 Copy(src は after‑phis の dst)。 - - `MirBuilder` に `schedule_mat_map`(per‑block)を追加し、`start_new_block` でクリア。 - - Unified Call で適用(pin→LocalSSA→after‑phis Copy→必要時 before‑call Copy)。 -- 状態: - - “use of undefined recv” は大幅減。sporadic 残存に対し、二段網(after‑phis固定+before‑call最終)を導入済み。 - - 一部で受信者誤型(例: String に parse)を観測。順序ではなく解決側の誤選択の可能性。 -- 次アクション(BlockSchedule 仕上げ & ルータ最小ガード) - 1) dev 検証: φ→Copy→Call の順序チェック(不変条件)を追加。 - -Selfhosting Bring‑up(補足: lang 復元と構造) -- 状態: `lang/` ツリー(compiler/vm/shared/runner/c‑abi 等)を ff3ef452 系からフル復元(約306 files/64 dirs)。 -- 理由: main が一時的に `lang/` を含まない系列に fast‑forward されていたため。削除コミットではなく系列差。 -- 対応: 復元済み。Hako側 IndexExpr 実装(parser)を反映。Selfhost の v0 生成は段階導入で再実装予定。 -- スモーク: Hako canary は v0 生成が整うまで SKIP。Rust 側 VM canary は緑を維持。 - 2) rewrite/resolve に dev 最小ガード(既定OFF)を置き、明確な誤選択(String.parse 等)を抑止。観測ログで要因特定。 - 3) failing bb を MIR dump で再検証→ quick 緑化。 - -Plan — Next (LocalSSA 仕上げ・観測) -1) 観測(dev 限定): `local_ssa_ensure`/emit_unified_call に軽トレースを追加(bb/kind/orig→local)。 -2) 最終関所: emit 直前に `current_block` のズレ検知→ `local_ssa_ensure` を再適用する小ヘルパを共通化(Call/Compare/Branch/Field に必要分点適用)。 -3) json_lint_vm を再実行(quick 緑化)。 -4) ドキュメント追記: LocalSSA の責務と適用範囲(builder/README or observe/README 近傍)。 - -Update — 2025-09-28 (LocalSSA ヘルパ化・集中管理 追加) -- ssa/local へ集約: `src/mir/builder/ssa/local.rs` を新設し、LocalKind と ensure()/recv/arg/cond/field_base/cmp_operand を実装。 -- 共通ヘルパ: Call 直前の集約処理を `finalize_callee_and_args(builder, &mut Callee, &mut Vec)` に統一。Legacy 用に `finalize_args(...)` も追加。 -- 呼び出し側の簡素化: - - Unified: `emit_unified_call` は finalize_callee_and_args を呼ぶだけに整理(手動の re-materialize を撤去)。 - - Legacy: Extern/Global/Value で finalize_args を適用。 - - BoxCall: utils 側で recv/args を LocalSSA に統一(pin_to_slot("@recv") 撤去)。 -- dev トレース: `NYASH_LOCAL_SSA_TRACE=1` で ensure/copy を一行出力(bb/kind/orig→local)。 - -Plan — Next (短期・最小差分) -- 最終関所の共通化を拡張: ssa/local に Branch/Compare/Field 用の finalize ヘルパを追加し、emit 直前に一律適用(ズレ検知を含む)。 -- 観測の強化: LocalSSA トレースに inst 直前/直後の要点(bb, kind, value)を短く追加し、未定義が LocalSSA の内外どちらか即判定できるようにする。 -- json_lint_vm を緑化(仕様不変・最適化後回し)。 - -Update — 2025-09-28 (P1 Known 集約・KPI・LAYER ガード) -- Builder: method_call_handlers の Known 経路を `rewrite::known` に集約。 - - 新規 API: `try_known_or_unique`(Known 優先→一意候補 fallback)。 - - equals/1 を `rewrite::special::try_special_equals` に移設(挙動不変)。 -- Observe: `resolve.choose` に certainty を付加し(Known/Heuristic)、`NYASH_DEBUG_KPI_KNOWN=1` 時に簡易集計を出力(`NYASH_DEBUG_SAMPLE_EVERY=N`)。 -- LAYER ガード(任意ツール): `tools/dev/check_builder_layers.sh` を追加(origin→observe→rewrite の一方向チェック)。 -- Unified 経路: `emit_unified_call` に equals/1 の集約を追加(Known 優先→一意候補)(仕様不変)。 -- メソッド候補インデックス化: `MirBuilder` に tail→候補のキャッシュを追加(lazy再構築)。 - - API: `method_candidates(method, arity)`, `method_candidates_tail(tail)` - - 利用箇所: method_call_handlers の resolve.try、rewrite::{special,known} の一意候補探索、unified equals/1 の一意候補。 -- 集約ポリシー(P0 完了): - - 中央集約先: `emit_unified_call`(Methodターゲット時に rewrite/special/known を順に試行) - - `method_call_handlers` は `emit_unified_call` を呼ぶだけに簡素化(重複ロジック削減) - - equals/1 も同一ロジックに吸収 -- レガシー経路(P1 準備): - - dev ガード追加: `NYASH_DEV_DISABLE_LEGACY_METHOD_REWRITE=1` でレガシー側のメソッド関数化を停止(将来削除の前段階) - - Unified 無効時の後方互換は維持(既定OFF) - -Status Snapshot — 2025‑09‑27 -- Completed - - VM method_router: special-method table extended minimally — equals/1 now tries instance class then base class when only base provides equals (deterministic, no behavior change where both exist). toString→str remains(互換: stringify を許容)。 - - MIR Callee Phase‑3: added TypeCertainty to Callee::Method (Known/Union). Builder sets Known when receiver origin is known; legacy/migration BoxCall marks Union. JSON emitter and MIR printer include certainty for diagnostics. Backends ignore it functionally for now. - - Using/SSOT: JSONモジュール内部 using を相対に統一(alias配下でも安定) - - DebugHub: 追加ゲート `NYASH_DEBUG_SAMPLE_EVERY`(N件に1度だけ emit)。重いケースでのログ制御のため(既定OFF・ゼロコスト)。 - - Router diagnostics: class-reroute / special-reroute を DebugHub に emit(dev-only, 既定OFF)。 - - LLVM diagnostics: `NYASH_LLVM_TRACE_CALLS=1` で `mir_call` の callee(Method.certainty 含む)を JSON 出力(挙動不変)。 - -Decision — Variables (Option A; 2025‑09‑27) -- 方針: var/let は導入しない。ローカルは常に `local` で明示宣言。 -- 目的: SSA/Loop‑Form と Known/Union 解析の単純さを維持し、未宣言代入の混入を防ぐ。 -- 補足: 行頭 `@name[:T] = expr` は標準ランナーで `local name[:T] = expr` へ自動展開(既定ON)。言語意味は不変。 -- Docs 更新: quick-reference, language reference, tutorials に「var/let 不採用」を明記。 - - Tokenizer/Parser デバッグ導線(devトレース)を追加 - - json_lint_vm: fast‑pathの誤判定を除去+未終端ガードを追加(PASS) - - json_query_min_vm/json_query_vm/json_pp_vm: PASS - - forward_refs_2pass: Builder が user Box に birth BoxCall を落とさないよう修正+ランナーフィルタ調整(PASS) - - Test runner: dev verify ノイズ(NewBox→birth warn)および BoxCall dev fallback をフィルタ - - Entry policy: top‑level main 既定許可に昇格(NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN default=true)。 - - 互換: `Main.main` が存在する場合は常にそちらを優先。両方無い場合は従来通りエラー。 - - オプトアウト: `NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=0|false|off` で無効化可能。 -- Next - - Heavy JSON: quick 既定ONへ再切替(LLVM 常備で段階復帰) - - 解析ログの統一: parser/tokenizerのdevトレースは既定OFFのまま維持、必要時だけ有効化 -- llvmlite(integration): 任意ジョブで確認(単発実行のハングはタイムアウト/リンク分離で回避) - -Update — 2025-09-27 (json_roundtrip_vm null 全化の修正) -- Cause: Tokenizer の構造トークン検出が `indexOf` 依存のため、環境によって `{ [ ] } , :` を認識できず ERROR に落ちていた。 -- Fix: `char_to_token_type(ch)` を `==` での直接比較に変更(環境依存排除)。 - - File: apps/lib/json_native/lexer/tokenizer.nyash -- Result: core/json_roundtrip_vm.sh, core/json_nested_vm.sh → PASS(VM quick) - -Self‑Hosting Roadmap (Revised) — 2025‑09‑27 - -Goal -- 一度に広げず、小粒で段階導入。既定挙動は変えず、dev/ci で計測→安定→昇格。 -- 本線は VM(Rust)と llvmlite(Python)で検証しながら、Nyash 自身による最小実行器へ橋渡し。 - -Milestones -- M1: JSON 立ち上げ(VM quick 基準) - - 目的: JSON 入出力の足場を固め、言語側のテスト土台を安定化。 - - 完了: 相対 using 統一、json_lint_vm/roundtrip/nested/query_min 緑化。 - - 次: Scanner.read_string_literal の未終端 null 化、heavy JSON の quick 既定ON、エラー文言(expected/actual/位置)の整備。 - - 受け入れ: quick で JSON 系が常時緑(SKIPなし)。 - -- M2: MIR Core‑13 最小セットの Ny 実装(JSON v0 ローダ+実行器) - - 範囲: const/binop/compare/branch/jump/ret/phi、call/externcall/boxcall(最小)。 - - 進め方: PyVM を参照実行器としてパリティ確認。fail fast を優先(dev 詳細ログ)。 - - 受け入れ: 代表スモーク(小型)を Ny 実行器で通過、PyVM と出力一致。 - -- M3: Box 最小群(String/Array/Map/Console) - - メソッド: length/get/set/push/toString、print/println/log(必要最小)。 - - ポリシー: 既存NyRT/プラグインと衝突しないよう名前空間を分離。既定はOFF、devでON。 - - 受け入れ: JSON apps が Ny 実行器で最低限動作(速度不問)。 - -- M4: Parity/Profiles 整理 - - プロファイル: dev=柔軟、ci=最小+計測、prod=SSOT厳格(nyash.toml)。 - - パリティ: VM↔llvmlite↔Ny 実行器で代表サンプル一致。差分はテーブル化し段階吸収。 - - 受け入れ: quick(VM)緑、integration(llvmlite)任意緑、Ny 実行器で代表ケース緑。 - -Guards / Policy -- 変更は局所・可逆(フラグ既定OFF)。 -- 既定挙動は不変(prod 用心)。 -- dev では診断強化(ログ/メトリクス)し、ランナー側でノイズはフィルタ。 - -## Unskip Plan(段階復帰) -- P0: json_query_vm(VM)— Completed - - 状態: SKIP 解除、期待出力一致、寛容フラグ不要で PASS。 - - 措置: evaluator のspan表現と membership 判定の手動化(indexOf/.get 非依存)。 -- P1: loops(break/continue/loop_statement)— Completed - - 状態: SKIP 解除、quick で PASS。 - - 措置: LoopBuilder の PHI/順序を維持しつつ、LocalSSA/BlockSchedule の適用範囲で in‑block 定義を徹底。 -- P2: Mini‑VM(M2/M3)— Completed - - 状態: 代表 4 件(m2_eq_true/false, m3_branch_true, m3_jump) PASS・SKIP 解除。 - - 備考: 単一パス維持・境界厳密化済み。 - -Update — 2025-09-28 (S‑tier 箱の適用拡大・仕様不変) -- Const 発行の一元化(代表→全体へ拡大) - - builder/stmts.rs: Void/String を `emission::constant` に置換。 - - builder/control_flow.rs, exprs.rs, fields.rs: Void/String を同様に置換。 - - builder/builder_calls.rs: 関数名 Const は `NameConstBox` へ、整数1は `emission::constant` へ。 -- メタデータ伝播の統一 - - builder/utils.rs: `pin_to_slot` / `insert_copy_after_phis` の型/起源コピーを `metadata::propagate` に移譲。 -- 既知戻りの型注釈(最小) - - `annotate_call_result_from_func_name` に `types::annotation::annotate_from_function` を追加(`str/0`・`length/0`・`size/0`)。 - -現状サマリ -- quick: PASS 64/64(loops/Mini‑VM を含む) -- integration(llvmlite): PASS 17/17 - -Next(小粒・既定挙動不変) -- S‑tier の置換拡大の残: 代表の置換を完了(ops/decls/exprs の主要点)。引き続き残部を段階的に `emission::constant` へ(影響の少ない箇所から)。 -- RouterPolicyBox への `prefer_legacy` 集約を適用済み(utils の判定を `router::policy::choose_route` に移譲)。 -- 既知戻り注釈のホワイトリスト拡充(必要に応じて、dev 記録と連動)。 - -## MIR 生成層の箱(Box 化) — 構造導入(仕様不変) -目的: 重複した処理(定数発行/メタ伝播/最低限の型注釈)を薄い箱に集約し、回帰を構造で抑止する。 - -Tier S(今すぐ・小粒) -- MetadataPropagationBox(src/mir/builder/metadata/propagate.rs) - - propagate(builder, src, dst) - - propagate_with_override(builder, dst, MirType) -- ConstantEmissionBox(src/mir/builder/emission/constant.rs) - - emit_integer/emit_string/emit_bool/emit_null/emit_void -- TypeAnnotationBox(src/mir/builder/types/annotation.rs) - - set_type(builder, dst, MirType) - - annotate_from_function(builder, dst, func_name) - -状態(2025-09-28) -- S-tier: metadata/emission/types(annotation)に加え、router/emit_guard/name_const を追加(仕様不変)。 -- 最小適用: builder_calls(Router/EmitGuard)、rewrite/{special,known}(NameConst)へ部分導入済み。 -- まだ広域置換は行っていない(段階適用)。 - -次のアクション(箱の採用計画) -1) const発行箇所を emission::constant に段階移行(代表箇所のみ→全体) -2) 値生成直後の type/origin 継承を metadata::propagate に統一 -3) 統一Callの dst へ TypeAnnotationBox をピンポイント適用(既知戻りのみ) -4) RouterPolicyBox を unified 経路へ導入(Unknown/String/Array/Map/ユーザー箱→BoxCall) -5) EmitGuardBox で Call の finalize/verify を集約(Branch/Compare は後段) -6) NameConstBox を rewrite/special/known へ段階適用 - -ガード/方針 -- すべて既定OFFの挙動変更なし。差分は関数呼び出し先の集約のみ。 -- quick/integration 緑維持を確認しつつ範囲を広げる。 - -参考: docs/development/builder/BOXES.md に API/方針の詳細。 - -Policy — AST Using (Status Quo) -- SSOT(nyash.toml)+AST prelude merge を維持。prod は toml 限定、dev/ci は段階的に緩和。 -- 重い AST/JSON ケースは integration でカバーしつつ、quick への復帰は LLVM 有効環境で段階的に行う(順次解除)。 - -Work Queue (Next) -1) Scanner: 未終端文字列で必ず null を返す(Tokenizer が ERROR へ) -2) Heavy JSON: quick 既定ONに戻す(プローブは維持) -3) エラーメッセージの詳細化(expected/actual/line/column) -4) Ny 実行器 M2 スケルトン(JSON v0 ローダ+const/binop 等の最小実装)下書き -5) Parity ミニセット(VM↔llvmlite↔Ny)を用意し、差分ダッシュボード化 - 6) Router: Known/Union 方針の磨き込み(挙動不変) - - Known → 既存の直接呼び出しを維持(VM 完了、LLVM は表示のみ)。 - - Union → ルータ経路を維持しつつ、ログで可視化(表は“必要最小”で追加)。 - 7) Heavy JSON の quick 段階復帰(LLVM 有効環境) - - 順序: nested_ast → roundtrip_ast → error_messages_ast。 - 8) (診断)LLVM ダンプに certainty の補助表示(必要時、挙動不変)。 - -Update — @local expansion promotion (2025‑09‑27) -- すべてのランナーモードに `preexpand_at_local` を適用(common/llvm/pyvm に加え vm/selfhost へも導入)。 -- Docs を更新し、構文糖衣が標準で有効であることを明記。 - -Plan — Router Minimalism (継続方針) -- 特殊メソッド表は “toString→str(互換:stringify), equals/1” の範囲から、ユースが発生したもののみ点で追加。 -- 既定の挙動・言語仕様は変更しない(フォールバックの拡大はしない)。 -- 測定: DebugHub(resolve.*)ログと LLVM の `NYASH_LLVM_TRACE_CALLS` を併用し、Union 経路を可視化。 - -Runbook(抜粋) -- VM quick: `tools/smokes/v2/run.sh --profile quick` -- LLVM llvmlite: `cargo build --release --features llvm && tools/smokes/v2/run.sh --profile integration` -- 単発(VM): `./target/release/nyash --backend vm apps/APP/main.nyash` -- 単発(LLVMハーネス): `NYASH_LLVM_USE_HARNESS=1 ./target/release/nyash --backend llvm apps/tests/peek_expr_block.nyash` - - -Update — 2025-09-27 (Tokenizer/VM trace bring‑up) -- Implemented VM guards (prod): disallow user Instance BoxCall; dev keeps fallback with WARN. -- Dev assert: forbid birth(me==Void) in instance-dispatch path. -- Builder verify (dev): NewBox→birth invariant; warns when missing. -- Added targeted VM traces (dev): - - JsonToken setField/getField one‑liners - - Legacy/method calls for JsonTokenizer/JsonScanner keyword paths -- Tokenizer hardening: - - Reordered next_token dispatch: keyword/number/string first, structural last (avoids misclassifying letters as structural) - - char_to_token_type rewritten to strict per‑char check (no ambiguous match) - - Result: "null" now tokenizes correctly (NULL), and JsonParser.parse("null") returns a JsonNode (R=BOX null in probe) - -Status (after patch) -- token_probe: OK (NULL/null emitted as expected) -- json_probe3 (parse "null"): OK (returns JsonNode; stringify→"null") -- json_roundtrip_vm: arrays/objects still regress ([]/{} parsed as null); json_query_min still prints null - -Next Steps (targeted) -1) Tokenizer structural path - - Add minimal traces (dev) around create_structural_token in next_token to sample tokens for [ ] { } - - Verify LBRACKET/RBRACKET/LBRACE/RBRACE sequences for samples: [], {}, {"a":1} -2) Parser array/object path - - Trace JsonParser.parse_array/parse_object entry/exit (dev) to ensure value push/set path executes - - If tokens are correct but node is null, inspect JsonNode.create_array/object and stringify -3) Fix + re‑run quick smokes (json_roundtrip_vm, json_nested_vm, json_query_min_vm) - -How to reproduce (quick) -- token: NYASH_ALLOW_USING_FILE=1 ./target/release/nyash --backend vm /tmp/token_probe.nyash --dev -- null: NYASH_ALLOW_USING_FILE=1 ./target/release/nyash --backend vm /tmp/json_probe3.nyash --dev -- smokes: tools/smokes/v2/profiles/quick/core/json_roundtrip_vm.sh - -Notes -- Traces are dev‑only and silent by default; noisy prints in tokenizer were re‑commented. - -Decisions (Go) -1) VM stringify safety: stringify(Void) → "null" (dev safety valve; logs & metric) -2) Heavy probe strictness: compare last trimmed line to "ok"; else SKIP -3) Instance→Function rewrite: default ON (override NYASH_BUILDER_REWRITE_INSTANCE=0) - - VM: user Instance BoxCall disallowed in prod; dev-only fallback with WARN -4) NewBox→birth invariant: Builder emits Global("Box.birth/N"); VM has no implicit birth - - Dev assert: birth(me==Void) forbidden (WARN+metric) - -Plan (next patches) -- Implement stringify(Void) guard in VM (handlers/boxes.rs) -- Tighten probes in quick/core json_* smokes (tail-trim-compare) -- Set rewrite default ON in Builder (method_call_handlers.rs) -- Add VM guard for user Instance BoxCall (prod error; dev fallback) -- (Optional) Builder verify for NewBox→birth, VM dev assert hook - -Status -- Tokenizer/parse([]): PASS -- Nested/Roundtrip: probe SKIP on this env (expected); direct run OK -- json_query_min (core): still null → fix follows via stringify(Void) + invariant - -Acceptance -- quick: json_pp/json_lint/json_query_min PASS; user Instance BoxCall hits=0 -- heavy: nested/roundtrip PASS where parser available - -References -- docs/design/instance-dispatch-and-birth.md -- tools/smokes/README.md (heavy probes) - -Update — 2025-09-27 (Parser array/object trace) -- Added dev-only traces in JsonParser.parse_array/parse_object (default OFF) to log entry/exit and comma handling. -- Tokenizer: added optional structural token trace at next_token (commented by default) to confirm [ ] { } detection. -- Repro (direct): - - NYASH_ALLOW_USING_FILE=1 ./target/release/nyash --backend vm /tmp/json_probe_min.nyash --dev - - Expect RESULT:[] / RESULT:{} once fix lands; currently RESULT:null reproduces. -- Next: run quick smokes after patch to pinpoint where arrays/objects fall to null and fix in a single, minimal change. - -Update — 2025-09-27 (json_lint_vm guard fix) -- Issue: Unterminated JSON string ("unterminated) was incorrectly judged OK in json_lint due to a lax fast‑path. -- Fix (app-level, spec-safe): removed string fast‑path and added explicit guard — if starts_with('"') and not ends_with('"') then ERROR. - - File: apps/examples/json_lint/main.nyash -- Result: apps/json_lint_vm.sh PASS on VM quick. -- Follow-up (root cause, parser side): JsonScanner.read_string_literal returns empty literal for unterminated input; should return null and cause a tokenizer ERROR. - - File: apps/lib/json_native/lexer/scanner.nyash (read_string_literal) - - TODO: add unit probe; ensure EOF without closing quote yields null; add negative case to smokes if needed. - -Update — 2025-09-28 (Scanner 未終端→null とスモーク追加) -- Implemented: JsonScanner.read_string_literal returns null when closing quote is missing or escape incomplete. - - File: apps/lib/json_native/lexer/scanner.nyash (already returned null; verified) -- Tokenizer maps scanner null to ERROR("Unterminated string literal"). - - File: apps/lib/json_native/lexer/tokenizer.nyash (tokenize_string) -- Added quick smoke to lock behavior: - - tools/smokes/v2/profiles/quick/core/json_unterminated_string_vm.sh → expects "Unterminated string literal". - -Work Queue — Reorganized (2025‑09‑28) -1) Scanner 未終端→null — completed - - Status: Verified with new smoke; tokenizer ERROR emitted with line/column preserved. -2) Heavy JSON quick 復帰(LLVM 常備で段階解除) — completed (dev override) - - Policy: AST-heavy smokes run in quick via LLVM harness. When LLVM is not detectable, they SKIP; 開発者は `SMOKES_FORCE_LLVM=1` で強制実行可。 - - Action: run.sh に `SMOKES_FORCE_LLVM=1` を追加、ハーネス/NYRT/ENV の自動整備を強化。nested_ast → roundtrip_ast → error_messages_ast が PASS。 -3) エラーメッセージ詳細化 — pending - - Scope: enrich JSON parser/tokenizer messages with expected/actual; keep format: "Error at line X, column Y: ...". -4) Ny 実行器 M2 スケルトン(最小) — baseline exists - - Files: apps/selfhost/vm/boxes/mir_vm_min.nyash; quick smoke present. - - Next: add binop/compare minimal paths (dev-only), no default behavior change. -5) Parity ミニセット — pending - - Add a tiny VM↔LLVM↔Ny parity triplet; start with const/ret and simple binop. -6) Router Known/Union 磨き込み(挙動不変) — pending - - Maintain minimal special-method table; diagnostics only; no behavior change. -7) Heavy JSON 段階復帰順(nested_ast→roundtrip_ast→error_messages_ast) — tracking - - All present in quick under LLVM harness; verify pass and keep order. -8) LLVM ダンプに certainty 補助表示 — baseline exists - - NYASH_LLVM_TRACE_CALLS=1 prints callee JSON including Method.certainty. -9) QuickRef — Truthiness(quickで有効化)— completed - - tools/smokes/v2/profiles/quick/core/lang_quickref_truthiness_vm.sh → enabled; PASS(0→false, 1→true, ""→false, non‑empty→true) -10) Language guards(planned; 既定OFF・段階導入) - - ASI strictness: dev‑only check to fail a line break after a binary operator; default OFF. - - Plus mixed: warn/fail‑fast when non‑String mixed `+` unless explicit stringify; default OFF; document String+number ⇒ concat. - - Box equality guidance: when `box == box` is used, emit guidance to use equals(); default OFF. - - Scope: docs + dev warnings first; later wire parser/builder flags guarded by env/CLI profile. - -Update — 2025-09-27 (M2 skeleton: Ny mini-MIR VM) - -Update — 2025-09-28 (json_lint_vm regression fix — condition_fn and birth bridge) -- Fixed: Unknown global function: condition_fn (quick json_lint_vm) - - Indirect calls: ensure AST `condition_fn(ch)` lowers to Value call (unified path already used in exprs_call.rs) - - Unified Global safety: emit_unified_call now dev‑safes `condition_fn` by returning const 1 when unresolved (explicit opt‑in legacy paths intact) - - Dev stub: finalize_module injects minimal `condition_fn/1 -> 1` if missing (kept as guard) -- Unified→VM bridge: birth() - - VM: when executing unified Method callee `*.birth`, delegate to BoxCall handler and return Void. This preserves legacy behavior for built‑ins when plugins are absent. - - Builder: gated birth() injection for built‑ins (Array/Map/String etc). Default OFF unless `NYASH_DEV_BIRTH_INJECT_BUILTINS=1`. -- Next (high‑prio): local var materialization bug in main.nyash - - Symptom: `local cases = new ArrayBox()` followed by `cases.push(...)` used an undefined receiver ValueId. - - Interim change: make `local` always materialize a distinct register and `copy init -> var` (also const Void for uninitialized). This avoids SSA aliasing issues. - - Status: needs a quick pass across smokes to confirm; proceed if quick green, otherwise revisit builder var mapping. - -Update — 2025-09-28 (recv undefined across loop headers — Patch‑A applied) -- Root cause: Some method calls still went through legacy BoxCall emission without receiver pin, causing the receiver ValueId to be undefined at loop/header blocks. -- Patch‑A (applied): pin receiver centrally in `emit_box_or_plugin_call` so every method call path (Unified/Legacy) has a block‑local def. - - File: src/mir/builder/utils.rs (at function start) -- Block entry propagation (applied): when starting a new basic block, copy all `__pin$` slots and rewrite user variables that referenced the old pin ids to the new copied ids. - - File: src/mir/builder/utils.rs (start_new_block) -- Status: residual undefined value still observed in json_lint_vm (different ValueIds). Next step is to trace the exact site and, if necessary, add a minimal materialize at `build_variable_access` for the specific hotspots. - -Plan — Next (late 2025‑09‑28) -1) Trace failing site in json_lint_vm with `NYASH_VM_TRACE=1` and MIR dump; capture `reg_load undefined id` with surrounding last_inst. -2) Verify that at that site the receiver is either a) not pinned (missed path) or b) was not remapped at block entry; fix with a targeted pin/materialize. -3) If a general gap remains, add a guarded materialize in `build_variable_access` (only when the ValueId originates from a pin slot or when entering a new block) to keep diff minimal. -4) Re‑run quick; keep Unified default‑ON; document toggles and rationale. - -Dev toggles -- NYASH_DEV_BIRTH_INJECT_BUILTINS=1: re‑enable birth() injection for builtin boxes (default OFF to stabilize unified Method path until full bridge lands). -- NYASH_MIR_UNIFIED_CALL: default ON; opt‑out via 0|false|off. -- Added Ny-based minimal MIR(JSON v0) executor skeleton (const→ret only), dev-only app — no default behavior change. - - File: apps/selfhost/vm/boxes/mir_vm_min.nyash - - Entry: apps/selfhost/vm/mir_min_entry.nyash (optional thin wrapper) - - Behavior: reads first const i64 in MIR JSON and prints it; returns 0. -- Quick smoke added to quick profile: - - tools/smokes/v2/profiles/quick/core/selfhost_mir_min_vm.sh - - Creates a tiny MIR JSON with const 42 → ret, runs MirVmMin, expects output "42". -- Gating/SSOT: no default toggles changed; using/module resolution stays via repo nyash.toml (added modules.selfhost.vm.mir_min). - -Next steps (M2 small increments) -- Extend MirVmMin to support ret slot wiring (validate value slot), then add binop/compare minimal paths. -- Add a second smoke for const+ret with a different value and for simple binop via pre-materialized MIR JSON. -- Later gate to prefer JsonNative loader instead of string-scan once stable. -Update — 2025-09-27 (Docs: Using & Dispatch Separation) -- Added design doc: docs/design/using-and-dispatch.md (SSOT+AST for using; runtime dispatch scope; env knobs; tests). -- Strengthened comments: - - src/runner/modes/common_util/resolve/{mod.rs,strip.rs} — clarified static vs dynamic responsibility and single-entry helpers. - - src/mir/builder/method_call_handlers.rs — documented rationale and controls for instance→function rewrite. - - src/backend/mir_interpreter/handlers/boxes.rs — clarified prod policy for user instance BoxCall fallback. -- Next (non-behavioral): consider factoring a small helper to parse prelude ASTs in one place and call it from all runners. -Update — 2025-09-27 (UserBox smokes added) -- Added quick/core smokes to cover UserBox patterns under prod + fallback-ban: - - oop_instance_call_vm.sh — PASS - - userbox_static_call_vm.sh — PASS - - userbox_birth_to_string_vm.sh — PASS - - userbox_using_package_vm.sh — PASS (using alias/package + AST prelude) - -Update — 2025-09-27 (Loop/Join ScopeCtx Phase‑1) -- Implemented Debug ScopeCtx in MIR builder to attach region_id to DebugHub events. - - Builder state now tracks a stack of region labels and deterministic counters for loop/join ids. - - LoopBuilder: pushes loop regions at header/body/latch/exit as "loop#N/". - - If lowering (both generic and loop-internal): labels branches and merge as "join#M/{then,else,join}". - - DebugHub emissions (ssa.phi, resolve.try/choose) now include current region_id. -- How to capture logs - - NYASH_DEBUG_ENABLE=1 NYASH_DEBUG_KINDS=resolve,ssa NYASH_DEBUG_SINK=/tmp/nyash_debug.jsonl \ - tools/smokes/v2/run.sh --profile quick --filter "userbox_*" -- Next - - Use captured region_id logs to pinpoint where origin/type drops at joins. - - Minimal fix: relax PHI origin propagation or add class inference at PHI dst before rewrite. - -Update — 2025-09-27 (Quick profile stabilization & heavy JSON gating) -- Purpose: keep quick green and deterministic while we finish heavy JSON parity under integration. -- Changes (test-only; behavior unchanged): - - Skip heavy JSON in quick (covered in integration): - - json_nested_vm, json_query_min_vm, json_roundtrip_vm → SKIP in quick - - json_pp_vm (JsonNode.parse pretty-print) → SKIP in quick(例示アプリ、他で十分カバー) - - Using resolver brace-fixer: quick config restored to ON for stability(NYASH_RESOLVE_FIX_BRACES=1) - - ScopeCtx wired (loop/join) and resolve/ssa events include region_id(dev logs only) - - toString→str early mapping logs added(reason: toString-early-*) -- Rationale: heavy/nested parser cases were sensitive to mixed env order in quick. Integration profile will carry the parity checks with DebugHub capture. -- Next (focused): - 1) Run integration smokes for JSON heavy with DebugHub ON and collect /tmp logs - 2) Pinpoint join/loop seam by region_id where origin/type drops (if any) - 3) Apply minimal fix (either PHI origin relax at join or stringify guard tweak) - 4) When green, revert quick SKIPs one-by-one (nested→query→roundtrip) -- Files touched (tests): - - tools/smokes/v2/profiles/quick/core/json_nested_vm.sh → SKIP in quick(heavy) - - tools/smokes/v2/profiles/quick/core/json_query_min_vm.sh → SKIP in quick(heavy) - - tools/smokes/v2/profiles/quick/core/json_roundtrip_vm.sh → SKIP in quick(heavy) - - tools/smokes/v2/profiles/quick/apps/json_pp_vm.sh → SKIP in quick(例示アプリ) - - tools/smokes/v2/configs/rust_vm_dynamic.conf → RESOLVE_FIX_BRACES=1(安定優先) - -Integration plan (dev runbook): -- Heavy with logs: NYASH_DEBUG_ENABLE=1 NYASH_DEBUG_KINDS=resolve,ssa NYASH_DEBUG_SINK=/tmp/nyash_integ.jsonl \ - tools/smokes/v2/run.sh --profile integration --filter "json_*ast.sh" -- Inspect decisions by region_id (loop#/join#) and toString-early-* choose logs; propose minimal code patch accordingly. - -Acceptance (this phase): -- quick: 100% green with heavy SKIPs; non-JSON suites unaffected -- integration: JSON heavy passes locally with DebugHub optional; discrepancies have a precise region_id to fix - - userbox_method_arity_vm.sh — SKIP (rewrite/materialize pending) - - userbox_branch_phi_vm.sh — SKIP (rewrite/materialize pending) - - userbox_toString_mapping_vm.sh — SKIP (mapping pending) -- Rationale: keep quick green while surfacing remaining gaps as SKIP with clear reasons. -- Next: stabilize rewrite/materialize across branch/arity and toString→str mapping; then flip SKIPs to PASS. -Update — 2025-09-27 (Loop‑Form Scope Debug & AOT PoC — Plan) -- Added design doc: docs/design/loopform-scope-debug-and-aot.md - - Scope model (LoopScope/JoinScope), invariants, Hub+Inspectors, per-scope data, AOT fold, PoC phases, acceptance. -- Work Queue (phased) - 1) PoC Phase‑1 (dev‑only; default OFF) - - Add DebugHub (env: NYASH_DEBUG_ENABLE/NYASH_DEBUG_SINK/NYASH_DEBUG_KINDS) - - ScopeCtx stack in builder; enter/exit at Loop/Join construction points - - Emit resolve.try/choose in method_call_handlers.rs - - Emit ssa.phi in builder.rs (reuse dev meta propagation) - - Smokes: run userbox_branch_phi_vm.sh, userbox_method_arity_vm.sh with debug sink; verify region_id/decisions visible - 2) Phase‑2 - - OperatorInspector (Compare/Add/stringify) - - Emit materialize.func / module.index; collect requires/provides per region - - Fold to plan.json (AOT unit order; dev only) - 3) Phase‑3 (optional) - - ExpressionBox (function‑filtered), ProbeBox (dev only) -- Acceptance (Phase‑1) - - Debug JSONL has resolve/ssa events with region_id and choices; PASS cases unchanged (OFF) - - SKIP cases pinpointable by log (branch/arity) → use logs to guide fixes → flip to PASS - - -Update — 2025-09-28 (Plugins 既定ON と ENV 整理) -- Plugins: 既定ONで統一。テストランナー/開発スクリプトから `NYASH_DISABLE_PLUGINS=1` を撤去。 - - tools/smokes/v2/lib/test_runner.sh(LLVM 経路): disable 指定を外し、`PYTHONPATH`/`NYASH_NY_LLVM_COMPILER`/`NYASH_EMIT_EXE_NYRT` を自動付与。 - - tools/dev_env.sh: `pyvm`/`bridge` プロファイルで plugins を無効化しない(unset のみに変更)。 -- VM/LLVM 二系統の最小ENV(ドキュメント方針): - - VM: 既定でOK(追加ENV不要) - - LLVM(harness): `NYASH_LLVM_USE_HARNESS=1` + `NYASH_NY_LLVM_COMPILER=$NYASH_ROOT/target/release/ny-llvmc` + `NYASH_EMIT_EXE_NYRT=$NYASH_ROOT/target/release` - - quick強制: `SMOKES_FORCE_LLVM=1` で AST heavy を quick で実行可能 - - -Priority TODO — 2025-09-28 (VM/LLVM 2-Line + M2) -- ENV minimalization (plugins=ON): - - VM: no extra ENV. - - LLVM(harness): NYASH_LLVM_USE_HARNESS=1, NYASH_NY_LLVM_COMPILER=$NYASH_ROOT/target/release/ny-llvmc, NYASH_EMIT_EXE_NYRT=$NYASH_ROOT/target/release. - - Docs: add a small "VM vs LLVM minimal-ENV" box to README.md and README.ja.md. [done] -- test_runner cleanup: - - Unify/centralize noise filters; keep SMOKES_FORCE_LLVM as the only dev override; remove ad-hoc greps in individual scripts. [todo] -- M2 executor (Ny): - - Add compare (Eq) to M2 runner; add 2 smokes (Eq true/false). [done] - - Externalize MirVmM2 to apps/selfhost/vm/boxes/mir_vm_m2.nyash and switch smoke to using-based variant; keep inline smoke as safety. [later] - - Next (optional): branch/jump minimal; phi later. [pending] - -Update — 2025-09-28 (Language Quick Reference & Smokes) -- Added quick-reference draft for language (keywords, operators, ASI, truthiness, equality, '+', rewrite, errors). - - docs/reference/language/quick-reference.md -- Added planned smokes for quickref rules (initially SKIP until strict rules are wired): - - tools/smokes/v2/profiles/quick/core/lang_quickref_asi_error_vm.sh (SKIP) - - tools/smokes/v2/profiles/quick/core/lang_quickref_truthiness_vm.sh (ENABLED) - - tools/smokes/v2/profiles/quick/core/lang_quickref_plus_mixed_error_vm.sh (SKIP) - - tools/smokes/v2/profiles/quick/core/lang_quickref_equals_box_error_vm.sh (SKIP) -- Temporarily SKIP Mini‑VM M2/M3 smokes while parser/segment boundaries are being fixed: - - selfhost_mir_m2_eq_true_vm.sh / selfhost_mir_m2_eq_false_vm.sh / selfhost_mir_m3_branch_true_vm.sh / selfhost_mir_m3_jump_vm.sh — now ENABLED and PASS -- Using/SSOT docs: - - Clarify dev/ci/prod matrix (file-using dev/ci only; prod=toml only); add short examples. [todo] -- Parity mini-set: - - VM ↔ LLVM ↔ Ny: const/ret + binop(+), compare(Eq); add quick parity harness notes. [todo] -- Acceptance: - - quick: AST heavy PASS (LLVM present), M2 binop/Eq PASS; integration unchanged. - - docs: minimal-ENV clearly shown; no NYASH_DISABLE_PLUGINS in public guidance. - -Update — 2025-09-28 (Interpreter gating & Phase 15.7 plan) -- Legacy AST interpreter is now feature-gated (interpreter-legacy OFF by default). Runner/tests that depend on it are behind cfg. - - Files: src/runner/modes/common.rs, src/runner/modes/bench.rs, src/tests/* (vm_bitops/refcell/functionbox) -- Added Phase 15.7 roadmap (Mini‑VM M3 + NYABI Kernel skeleton; dev-only; default OFF). - - docs/development/roadmap/phases/phase-15.7/README.md -- Drafted NYABI Kernel spec (v0) and added Ny skeleton box (not wired). - - docs/abi/vm-kernel.md; apps/selfhost/vm/boxes/vm_kernel_box.nyash - -Plan — Instance→Function Rewrite Consolidation (2025‑09‑28) -- Goal: 内部表現を関数呼び出しへ極力統一(obj.m(a) → Class.m/Arity(me,a))。prodでの Instance BoxCall 依存を排除。 -- Approach(小粒・可逆) - 1) PHI/Join での origin/type 伝播の強化(region_id ログで落ちる断面を特定→補修) - 2) 限定 materialize: module 内で name+arity がユニークな場合のみ Glue 関数を合成(既定OFF、dev/CIで計測) - -Roadmap Priorities (Phase 15.7 revised) -- P0: me 注入 Known 化(起源付与/維持)— リスク低・効果大。軽量PHI補強(単一/一致時) -- P1: Known 100% 関数化(Known 経路の instance→function 正規化、special 集約) -- P2: Policy(Ny Kernel, dev‑only)— equals/str/truthiness の観測API(バッチ、再入禁止/タイムアウト/計測) -- P3: 表示APIの移行誘導 — toString→str(互換:stringify)の警告/ドキュメント(仕様不変) -- P4: Union 観測・分析 — resolve.try/choose と ssa.phi(region_id)で継続観測 -- P5: PHI Known 維持の一般化 — Phase 16(複雑のため後回し) - 3) prod ガード維持: VM は user Instance BoxCall を禁止(既存ポリシー継続)。dev/CI は WARN+観測 - 4) スモーク/観測: quick で Instance BoxCall の dev WARN=0 を確認。resolve.try/choose と LLVM `NYASH_LLVM_TRACE_CALLS` を併用 -- Controls - - `NYASH_BUILDER_REWRITE_INSTANCE`(既定ON): 強制ON/OFF - - `NYASH_DEV_REWRITE_USERBOX`(dev限定): userbox rewrite 検証用 - - materialize 新ENV(既定OFF): `NYASH_BUILDER_MATERIALIZE_UNIQUE=1`(予定) -- Acceptance(段階) - - Stage‑1: Known 経路で 100% 関数化(quick全域で dev WARN=0) - - Stage‑2: 限定 materialize をON時に適用し、分岐/PHI 合流の代表ケースが関数化(差分はdevのみ) - - 常に prod は挙動不変・安全(OFFで現状維持) - -Update — 2025-09-28 (Mini‑VM M2/M3 fix + smokes) -- Fix: compare/ret segmentation made robust without heavy JSON parse. - - Approach: per‑block coarse passes for const/binop/compare and a precise in‑block ret search; control‑flow (branch/jump) handled with a single pass using computed regs. - - Files: apps/selfhost/vm/boxes/mir_vm_min.nyash -- Smokes: enabled and PASS - - tools/smokes/v2/profiles/quick/core/selfhost_mir_m2_eq_true_vm.sh - - tools/smokes/v2/profiles/quick/core/selfhost_mir_m2_eq_false_vm.sh - - tools/smokes/v2/profiles/quick/core/selfhost_mir_m3_branch_true_vm.sh - - tools/smokes/v2/profiles/quick/core/selfhost_mir_m3_jump_vm.sh -- Notes: kept changes local and spec‑neutral; no default behavior changes to core VM. - -Update — 2025-09-28 (QuickRef Dev Guards + Docs llvmlite) -- Dev guards (env‑gated; default OFF) implemented and validated by quick smokes: - - ASI strict line‑continuation: `NYASH_ASI_STRICT=1` → parse error when a binary operator ends the line. -- Plus mixed (String×Number): `NYASH_PLUS_MIX_ERROR=1` → type error; suggest str()/明示変換。 - - Box equality guidance: `NYASH_BOX_EQ_GUIDE_ERROR=1` → equals()誘導のエラー。 - - Smokes enabled: `lang_quickref_asi_error_vm.sh`, `lang_quickref_plus_mixed_error_vm.sh`, `lang_quickref_equals_box_error_vm.sh`(PASS) -- LLVM ドキュメント統一(llvmlite一本化) - - `LLVM_SYS_180_PREFIX` の記述を主要ドキュメントから撤去し、llvmlite/ny‑llvmc 前提に更新。 - - Files: `AGENTS.md`, `README.md`, `README.ja.md`, `CLAUDE.md` - -Plan — Next (2025-09-28) -1) Mini‑VM 単一パス化(仕様不変・安全化) — completed - - 各 op を JSON オブジェクト単位で厳密セグメント化し、一回走査で評価(coarse pass を除去)。 - - 代表ケース(複数op/ret先頭/ret末尾/compare v0,v1/jump/branch)で緑維持を確認。 -2) Rewrite 統合 Stage‑1(挙動不変・dev観測) — completed (observability wired) - - builder_calls の unified 経路に resolve.try/resolve.choose を追加(dev‑only/既定OFF)。 - - method_call_handlers の既存 emit と整合。Known/Union の certainty を choose に含める。 - - 使い方: `NYASH_MIR_UNIFIED_CALL=1 NYASH_DEBUG_ENABLE=1 NYASH_DEBUG_KINDS=resolve,ssa NYASH_DEBUG_SINK=/tmp/nyash_debug.jsonl`。 - - Known 経路の100%関数化(dev WARN=0)を DebugHub で観測。userbox スモークで検証。 -3) P0/P1 着手(構造化) — in progress - - origin/observe/rewrite の責務分割(モジュール新設: src/mir/builder/{origin,observe,rewrite}/)。 - - P0: me 注入 Known 化(起源付与/維持)と軽量PHI補強(単一/一致時)。 - - P1: Known 経路 100% 関数化(special 集約: toString→str(互換:stringify)/equals)。 - - Docs: README を各層に追加(origin/observe/rewrite)— completed - - 観測呼び出しの統一: builder_calls/method_call_handlers から observe::resolve を使用 — completed -3) CI/Profiles 整理 — ongoing - - quick: VM 主線(llvmlite パリティは integration に委譲)。 - - integration: 代表パリティ(llvmlite ハーネス)継続、apps系は任意実行。 - -Notes — Display API Unification (spec‑neutral) -- 規範: `str()` / `x.str()`(同義)。`toString()` は Builder で `str()` に早期正規化。 -- 互換: `stringify()` は当面エイリアス(内部で `str()` 相当)。 -- VM ルータ: toString/0 → str/0(なければ stringify/0)。 -- QuickRef/ガイド更新済み。`NYASH_PLUS_MIX_ERROR` の誘導文言も `str()` に統一。 - -追加メモ — これからやる(ユーザー合意、2025‑09‑28) -- Mini‑VM の単一パス化を安全に実装(既定挙動不変) - - 各 op を厳密セグメントで1回走査に統合(coarse を段階撤去) - - 代表スモーク(M2/M3/compare v0,v1)で緑維持確認 -- 続いて Rewrite 統合 Stage‑1 の観測へ進む(dev のみ、挙動不変) -- Dev Profiles - - tools/dev_env.sh に Unified 既定ON(明示OFFのみ無効)とレガシー関数化抑止を追加。 - - `NYASH_MIR_UNIFIED_CALL=1`(既定ON明示) - - `NYASH_DEV_DISABLE_LEGACY_METHOD_REWRITE=1`(重複回避; 段階移行) -- Update — 2025-11-02 (P1 part‑2) — v1 Closure 受理/Stage‑Bは引き続き opt‑in -- Stage‑B canaries(print/binop/if/loop/array/map/string)は現状 opt‑in のまま(`SMOKES_ENABLE_STAGEB=1`)。 - - Fallback TTL は既定OFF(必要時のみ `HAKO_STAGEB_ALLOW_FALLBACK=1`)。 -- Gate‑C v1 bridge: `mir_call` の `Closure` callee を受理(`NewClosure` を生成)。 - - params/captures/me_capture を JSON から読み取り、ボディは空で生成。 - - 実行は未対応(VM は Fail‑Fast)。生成経路の負例は別途スモーク化予定。 -Update — 2025-11-02 (Phase‑20.33 fast track 80/20) -- Stage‑B heavy を opt‑in に整理(SMOKES_ENABLE_STAGEB=1)し quick は緑を維持。 -- Bridge v1 Closure の負例カナリアを追加(captures 型不正 / func 欠落)。 -- Stage‑B alias table の malformed を Fail‑Fast 固定([bundle/alias-table/bad])+ 負例スモーク追加。 -- Gate‑C/Core Direct に in‑proc 実行(HAKO_CORE_DIRECT_INPROC=1)プロトを追加。子ラッパー経路はフォールバックとして存置。 -- quick: 168/168 → 整理後もフル緑(opt‑in除外時)。 - -Next — Hybrid selfhost build(混合自走ルート) -- 目標: Hakorune Stage‑B → Rust Bridge/LLVM の一括ビルドスクリプトを追加し、Hakoruneコンパイラで自分をビルド(後段はRust流用)。 -- ステップ: - 1) tools/selfhost_build.sh(仮): .hako → Stage‑B JSON v0 → Rust bridge で MIR → ny-llvmc で EXE - 2) スモーク: 小さな Hako 入力で return 0/7、binop、if を確認(rc)。 - 3) ノイズ/ENV は test_runner の規約を踏襲(quiet/JSON_ONLY/disable selfhost fallback)。 ---- - -# Phase 20.34 — Box‑First Selfhost Build Line(Program→MIR→LLVM) - -Focus(80/20) -- Hako 側で「境界」を箱化: - - MirBuilderBox(Program(JSON v0) → MIR(JSON)、暫定は Runner delegate) - - LLVMEmitBox(MIR(JSON) → .o、Plugin v2 `LLVMCodegenBox.emit_object/2` に委譲) -- Core‑Direct は検証の近道(MIR(JSON)のみ直行)。Tag→RC の負例を増やし Fail‑Fast を堅持。 - -Toggles(既定OFF) -- HAKO_MIR_BUILDER_DELEGATE=1 — Program→MIR を Runner の `--program-json-to-mir` に委譲 -- HAKO_CORE_DIRECT=1 / HAKO_CORE_DIRECT_INPROC=1 — MIR(JSON) を Core 直行で実行 -- SMOKES_ENABLE_SELFHOST=1 — EXE カナリアを有効化 - -Near‑term Tasks(進捗) -- docs: phase‑20.34(README/PLAN/CHECKLIST)を追加 — DONE -- scaffold: MirBuilderBox.hako / LLVMEmitBox.hako の最小 I/F — DONE(タグ・トグル固定) -- provider: Host providers を extern 経由で接続 — DONE - - env.mirbuilder.emit: Program(JSON v0) → MIR(JSON v0) - - env.codegen.emit_object: MIR(JSON v0) → ny-llvmc → .o - - hostbridge.extern_invoke(name, method, args?): VM 側で env.mirbuilder/env.codegen へ橋渡し(ArrayBox 先頭要素を文字列化) -- smokes: phase2034 カナリア追加 — DONE(MirBuilder=PASS、LLVMEmit=ny-llvmc不在/未解決はSKIP) -- next: LLVMCodegenBox(Hako ABI プラグインBox)実装&hako.toml登録 → extern TTL 撤退 -- next: MirBuilderBox 内蔵実装(const/binop/ret → compare/branch/jump/phi)を段階導入(委譲縮小) - -Acceptance -- quick: 新規カナリア PASS(SKIPはポリシー通り) -- integration: 既存緑維持、ノイズ増加なし -- docs: Env/TTL/Fail‑Fast を明文化 - -Update — 2025‑11‑03 (VM extern wiring + canary fix) -- Rust VM へ extern 受け口を配線し、delegate→core カナリアを安定化。 - - handlers/externals.rs: ExternCall("env.mirbuilder","emit"), ("env.codegen","emit_object"), ("hostbridge","extern_invoke") を実装。 - - handlers/calls.rs: legacy/global 解決形("hostbridge.extern_invoke" / "…/3")もブリッジ。 -- カナリア更新: phase2034/mirbuilder_varvar_delegate_core_canary_vm.sh は JSON 抽出を jq 検証に変更(整形出力を許容)。→ PASS - -Action Plan — A→B→C(docs first, code later) - A) LLVM provider 明確化(docs+運用) - - 方針: LLVMEmit は provider‑first。ny-llvmc あり: 実行、なし: 明示タグで SKIP。 - - トグル: `HAKO_LLVM_EMIT_PROVIDER=ny-llvmc|llvmlite`(既定OFF)。ny-llvmc 選択時は `env.codegen.emit_object` を呼び出す。 - - 受理条件: ny-llvmc 不在時に `[llvmemit/ny-llvmc/not-found]` が安定出力。存在時は `.o` を生成しパスを返す。 - - Docs 整理: CURRENT_TASK(本節)に運用とタグ、トグルの最小セットを記載(完了)。README/phase docs は次ステップで同期。 - - B) MirBuilder 内蔵化(第一段・委譲縮小) - - 範囲: Program(JSON v0) → MIR(JSON v0) の最小命令(const/binop/ret)を MirBuilderBox 内で直接生成。 - - ガード: 既定は挙動不変(委譲)。`HAKO_MIR_BUILDER_DELEGATE=1` で明示的に委譲経路を使用。 - - タグ不変: 入力不正は `[mirbuilder/input/invalid]`、委譲未配線は `[mirbuilder/delegate/missing]` を維持。 - - 受理条件: canary(return/binop/if)で MIR(JSON) の `functions/blocks` キーを検出し PASS。差分は局所・可逆。 - - C) Canary 運用(昇格ポリシー) - - MirBuilder canary: quick 既定への昇格候補(現状 PASS)。導線は `tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_canary_vm.sh`。 - - LLVMEmit canary: ny-llvmc 検出時のみ PASS、それ以外は `[llvmemit/ny-llvmc/not-found]` で SKIP 継続。 - - Gate: 昇格は「A 完了+B 第一段 PASS」が条件。ny-llvmc 依存の昇格は行わない(SKIP 運用を維持)。 - -Env Quick Reference(20.34 関連) - - `HAKO_MIR_BUILDER_DELEGATE=1` — Program→MIR を Runner へ委譲 - - `HAKO_MIR_BUILDER_INTERNAL=1` — 内蔵の最小変換(Return(Int)→const+ret)を有効化(委譲縮小の第一段) - - `HAKO_LLVM_EMIT_PROVIDER=ny-llvmc|llvmlite` — LLVM emit provider 選択 - - `NYASH_GATE_C_CORE=1`(互換: `HAKO_GATE_C_CORE=1`)— MIR(JSON) を Core 直行で検証 - -Next Tasks (Phase 20.34 wrap‑up → 20.35) -- P0 (now) - - Run canaries with preinclude=ON + inline=OFF to lock structure: - - tools/smokes/v2/profiles/quick/core/string_vm_api_canary_vm.sh - - phase2034/mirbuilder_internal_return_logical_varvar_{lower,via_builder}_canary_vm.sh - - VM include error message: add guidance (using+alias recommended; NYASH_PREINCLUDE=1 for dev) -- P1 (20.34) - - Finish include→using+alias migration (remaining lowers/boxes if any) - - Add CI static check: forbid `include "…"` in `lang/src/**` - - Docs: include=deprecated / using+alias=primary / preinclude=dev‑only -- P2 (20.35) - - Remove preinclude path from runner; archive tools/dev/hako_preinclude.sh - - Flip primary verifier to Hakorune VM (HAKO_VERIFY_PRIMARY=hakovm) once phase2034 canaries green - - Optional: switch MirBuilder to registry path after parity check - -Checklist (A→B→C — 2025‑11‑03) -- A) LLVM provider 明確化: DONE(env `HAKO_LLVM_EMIT_PROVIDER`、タグ: `[llvmemit/provider/missing]` ほか) -- B) MirBuilder 内蔵化(1st): IN‑PROGRESS(Return/Int・Binary(Int,Int)・Var/Int・Var/Var 等の lowers は内蔵パスで起動、追加の JsonFragBox/PatternUtilBox 適用は段階導入中) -- C) Canary 運用: PASS(mirbuilder_canary_vm / binop/varint、logical/mixed)。var/var core 直行の emit 検証は追跡中(非既定/opt‑in)。 - -Next (small, structure‑first) -- JsonFragBox adoption: 残存の手書きスキャンを置換(if/compare varint/varvar、method args の int 系)。 -- PatternUtilBox rollout: logical/return/binop 系の重複逆引きを順次委譲。 -- MirBuilder registry: `HAKO_MIR_BUILDER_REGISTRY=1` で parity を継続確認しつつ、呼び順の一本化を検討。 -- VM mir_call: `size/len/push/get/set` のタグ強化と最小状態(recv_id)設計を切り出し。既定は STUB タグ維持。 -- Verify primary: `HAKO_VERIFY_PRIMARY=hakovm` 経路の file‑read 受け口を追加し、Core fallback を段階縮小。 -Update — 2025-11-03(Phase 20.34 — P1/P2 sweep, JsonFrag/PatternUtil 統一) -- P1(手書きスキャン撤廃): 完了 - - internal lowers の数値/真偽/文字列抽出を JsonFragBox に統一。 - - Var→Local 逆引きを PatternUtilBox に統一(map_cmp も共通化)。 - - 対象(代表): lower_if_compare_* / lower_return_binop_* / lower_return_var_local 他。 -- P2(loop_form 系 include → using): 完了 - - lower_loop_simple/count_param/sum_bc から include を撤廃、using のみへ統一。 -- カナリア/スモーク - - internal lowers 構造系は緑(var/var logical lower 直行も PASS)。 - - MirBuilder→Core 直行系は emit 成功、rc 検証は当面 Hakorune VM primary(HAKO_VERIFY_PRIMARY=hakovm)で実施。 -- ランナー/テスト安定化 - - test_runner 未割当変数バグ修正(prefile)。 - - emit 出力の JSON 抽出に jq を導入(先頭ノイズ除去)。 - -Notes -- 仕様不変・既定挙動不変(トグルは既定OFF)。 -- include は lang内の一部(compiler_stageb/core_bridge)のみ残存(別PRで移行)。 +# Current Task — Phase 20.34 (Concise) + +This document is intentionally concise (≤ 500 lines). Detailed history and per‑phase plans are kept under docs/private/roadmap/. See links below. + +Focus (now) +- Keep quick profile green and stabilize verification on the Core route. +- Mini‑VM (Hako) is kept, but canaries that were flaky are temporarily routed to Core for execution. Mini‑VM green will resume in Phase 20.36. +- Prepare Phase 20.35 (MIR JSON v1 loader expansion) without changing default behavior. + +What’s green (20.34) +- Loop/PHI unify (phi_core) in JSON v0 bridge — unified path used (toggle exposed). +- Program(JSON v0) PHI‑trace canaries — PASS. +- Core exec canaries (builder → emit → Core) — now routed and PASS: + - mirbuilder_internal_core_exec_canary_vm (rc=10) + - mirbuilder_internal_loop_core_exec_canary_vm (rc=3) + - mirbuilder_internal_loop_count_param_core_exec_canary_vm (rc=6) + - mirbuilder_internal_loop_sum_bc_core_exec_canary_vm (rc=8) + +Recent changes (summary) +- Added MIR JSON v0 loader (minimal): src/runner/mir_json_v0.rs + - Supports const/compare/branch/jump/phi/ret/copy +- Promoted --mir-json-file to “execute + exit(rc)” + - v1 → try_parse_v1_to_module; v0 → minimal loader; executes via Core interpreter + - rc mapping unified in execute_mir_module_quiet_exit +- verify_mir_rc improvements (tools/smokes/v2/lib/test_runner.sh) + - Core primary: MIR(JSON) uses --mir-json-file, Program(JSON v0) uses --json-file + - Mini‑VM(hakovm)rc==1 ヒューリスティックを削除し、経路評価を素直化(v1はCore、v0はhakovmに整流) +- Hako JSON reader minor fix + - lang/src/vm/core/json_v0_reader.hako: r# raw を通常文字列に統一(Hako生文字列の互換性向上) + - MIR JSON v1 bridge extended + - parse_const_value now handles f64/float, bool, string, and handle(StringBox) → ConstValue::String + - Direct extern names supported in VM interpreter: env.mirbuilder.emit, env.codegen.emit_object +- New v1 canaries (Core route) + - tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_indexof_canary_vm.sh + - tools/smokes/v2/profiles/quick/core/phase2035/v1_extern_mirbuilder_emit_canary_vm.sh + - tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_substring_1arg_canary_vm.sh + - tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_substring_2args_canary_vm.sh + - tools/smokes/v2/profiles/quick/core/phase2035/v1_array_push_size_canary_vm.sh + - (map) tools/smokes/v2/profiles/quick/core/phase2035/v1_map_set_get_size_canary_vm.sh + - Note: returns size (1) for rc stability; get-path validated structurally + +Open (pending) +- なし(v1 extern env.get/env.mirbuilder.emit/env.codegen.emit_object は provider で統一) + +Active toggles (debug/verify) +- NYASH_MIR_UNIFY_LOOPFORM=1|0 + - Default ON(実装は統一経路のみ。OFF指定時は警告を出すが挙動は統一のまま) +- HAKO_VERIFY_PRIMARY=hakovm|core + - 今回の flaky canary は core 側で実行(検証のみ切替)。Mini‑VM primary は Phase 20.36 で再挑戦。 +- NYASH_VM_TRACE_PHI=1 / HAKO_PHI_VERIFY=1 / NYASH_PHI_VERIFY=1 + - PHI 解析・観測(開発時のみ) + +How to run (quick) +- Build: `cargo build --release` +- Program v0 PHI‑trace (debug): + - `SMOKES_ENABLE_DEBUG=1 bash tools/smokes/v2/profiles/quick/core/phase2034/program_v0_if_phi_trace_vm.sh` + - `SMOKES_ENABLE_DEBUG=1 bash tools/smokes/v2/profiles/quick/core/phase2034/program_v0_loop_phi_trace_vm.sh` +- Core exec canaries(20.34 緑対象): + - `bash tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_core_exec_canary_vm.sh` + - `bash tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_core_exec_canary_vm.sh` + - `bash tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_count_param_core_exec_canary_vm.sh` + - `bash tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_sum_bc_core_exec_canary_vm.sh` + +Mini‑VM policy(20.34) +- 実装は維持(alias/ret/phi 最小系を今後整える)。ただし quick の canary は Core へ寄せて緑化。 +- 20.36 で verify primary を hakovm に段階的に切替、Mini‑VM green を進める。 + +Next (20.35 — scoped; behavior unchanged) +1) MIR JSON v1 loader expansion(Method/Extern/BoxCall — 最小) + - callee.type=Method → BoxCall 復元(box_name/method/receiver/args) + - callee.type=Extern → ExternCall 復元(env.get/env.codegen.emit_object/env.mirbuilder.emit/console.log など) + - effects 未指定は PURE 既定、WRITE 系は最小でフラグ化(実害ゼロ) + - v1 canary 追加(string indexOf/substring、array push/size、map set/get/size、extern env.get) +2) Using/alias の推移解決の堅牢化(深さ/循環/キャッシュ) + - 実装済み(DFS 深さ上限=10、循環検知、キャッシュ)。strict では曖昧解決をエラー扱い。 +3) ドキュメント:今回の経路(Core/verify)を roadmap に反映(DONE) + +Structure cleanups (20.35 A→B→C) +- A) v1 mir_call 両対応(flat/nested): 実装済(ローダで互換吸収)。 +- B) Extern provider 単一点化: 実装済(handlers/extern_provider.rs)。calls.rs/externals.rs から委譲。 +- C) Const パース共通化: 実装済(src/runner/mir_json/common.rs)。まず v1 から採用(v0 は次段)。 + +Next (20.36 — verify primary → hakovm, preinclude removal, C‑ABI scaffold) +- Verify primary 切替(段階): hakovm → Core fallback +- preinclude 非推奨化(quick から撤去) +- Mini‑VM の最小状態(len/size/push の簡易 state)を flag ガードで導入(デフォルトOFF)— 導入済 +- 受信者別サイズ管理フラグ `HAKO_VM_MIRCALL_SIZESTATE_PER_RECV=1` を導入(canary 追加済) +- HAKO_VM_MIRCALL_SIZESTATE=1 は緑化済(push 2回→size=2)。次は受信者別管理を flag で導入: HAKO_VM_MIRCALL_SIZESTATE_PER_RECV=1 +- C‑ABI 設計(docs + ヘッダ雛形) + +Known open items(tracked to 20.36) +- Mini‑VM: using/alias の推移解決(selfhost.vm.helpers.* 連鎖) +- Mini‑VM: ret/phi の最小ケースで rc が確実に数値化されるよう整備(継続確認) + +Roadmap links(per‑phase docs) +- Index: docs/private/roadmap/README.md +- Phase 20.34: docs/private/roadmap/phases/phase-20.34/README.md +- Phase 20.35: docs/private/roadmap/phases/phase-20.35/README.md +- Phase 20.36: docs/private/roadmap/phases/phase-20.36/README.md + +Appendix — Toggle quick reference +- NYASH_MIR_UNIFY_LOOPFORM=1|0 … Loop/PHI 統一(既定ON) +- HAKO_VERIFY_PRIMARY=hakovm|core … verify 実行経路(今は core 優先で緑化) +- NYASH_VM_TRACE_PHI=1 … VM PHI 適用トレース +- HAKO_PHI_VERIFY=1 | NYASH_PHI_VERIFY=1 … ビルダー側の PHI inputs 検証 +- HAKO_VM_PHI_STRICT=0(互換:NYASH_VM_PHI_STRICT=0) … 実行時 PHI 厳格 OFF(開発時のみ) diff --git a/include/nyrt.h b/include/nyrt.h new file mode 100644 index 00000000..170d56ec --- /dev/null +++ b/include/nyrt.h @@ -0,0 +1,33 @@ +#ifndef NYRT_H +#define NYRT_H + +#ifdef __cplusplus +extern "C" { +#endif + +// Phase 20.36/20.37: C‑ABI scaffold (PoC) +// Minimal kernel API for Rust runtime; subject to evolution. + +// Initialize NyRT kernel. Returns 0 on success. +int nyrt_init(void); + +// Tear down NyRT kernel. +void nyrt_teardown(void); + +// Load MIR(JSON v1) text. Returns a handle (>=1) or 0 on error. +unsigned long nyrt_load_mir_json(const char* json_text); + +// Execute main() of the loaded module. Returns process‑like rc (0..255). +int nyrt_exec_main(unsigned long module_handle); + +// Hostcall bridge (name = "env.*" / provider). Returns 0 on success. +int nyrt_hostcall(const char* name, const char* method, + const char* payload_json, + /*out*/ char* out_buf, unsigned int out_buf_len); + +#ifdef __cplusplus +} +#endif + +#endif // NYRT_H + diff --git a/lang/src/shared/json/utils/json_frag.hako b/lang/src/shared/json/utils/json_frag.hako index 2fc8c336..66945c00 100644 --- a/lang/src/shared/json/utils/json_frag.hako +++ b/lang/src/shared/json/utils/json_frag.hako @@ -92,8 +92,9 @@ static box JsonFragBox { local pat1 = "\"" + key + "\":" local p = me.index_of_from(seg, pat1, 0) if p >= 0 { - local v = me.read_digits(seg, p + pat1.length()) - if v != "" { return me._str_to_int(v) } + // tolerant: skip whitespace and optional sign + local v = me.read_int_after(seg, p + pat1.length()) + if v != null { return me._str_to_int(v) } } return null } diff --git a/lang/src/vm/README.md b/lang/src/vm/README.md index 53d78da5..d7f92e2b 100644 --- a/lang/src/vm/README.md +++ b/lang/src/vm/README.md @@ -63,7 +63,41 @@ Toggles and Canaries - Gate‑C(Core) array sequence: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_array_mixed_vm.sh`(push→set→get をログで検証) - Gate‑C(Core) map sequence: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_map_{len,iterator}_vm.sh` - Emit→Core map len/get: `tools/smokes/v2/profiles/quick/core/canary_emit_core_map_len_get_vm.sh` - - Gate‑C Direct sanity: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_direct_string_vm.sh` +- Gate‑C Direct sanity: `tools/smokes/v2/profiles/quick/core/canary_gate_c_core_direct_string_vm.sh` + +Mini‑VM Flags (20.36) +- `HAKO_VM_MIRCALL_SIZESTATE=1` + - Array/Map の最小状態(size/len/push)を擬似的に追跡する(既定OFF)。 +- `HAKO_VM_MIRCALL_SIZESTATE_PER_RECV=1` + - 受信者(Constructor の dst=仮ハンドル)ごとにサイズを独立管理する(既定OFF)。 +- Canary: + - phase2036/v1_minivm_size_state_on_canary_vm.sh → rc=2(push×2→size) + - phase2036/v1_minivm_size_state_per_recv_on_canary_vm.sh → rc=2(A/B 各1push→size(A)+size(B)=2) + +Scanners (Box化) +- MiniMirV1Scan(lang/src/vm/boxes/mini_mir_v1_scan.hako) + - callee_name/method_name/first_arg_register/receiver_id を提供。 + - MirVmMin は本スキャナを呼ぶだけにして重複スキャンを排除(保守性の向上)。 + +Include Policy (quick) +- include は非推奨。quick プロファイルでは SKIP(テスト終了時に SKIP の対象一覧をサマリー表示)。 +- using+alias(nyash.toml の [modules] / alias)へ移行すること。 + +Core Route (Phase 20.34 final) +- Canaryの一部(Loop 系)は暫定的に Core 実行へ切替(Mini‑VM の緑化は 20.36 で段階的に実施)。 +- verify_mir_rc は primary=core の場合、 + - MIR(JSON) → `--mir-json-file`(v1→v0 順でローダが MirModule 復元) + - Program(JSON v0) → `--json-file`(json_v0_bridge 経由で MirModule 化) +- 代表: + - mirbuilder_internal_core_exec_canary_vm(rc=10) + - mirbuilder_internal_loop_core_exec_canary_vm(rc=3) + - mirbuilder_internal_loop_count_param_core_exec_canary_vm(rc=6) + - mirbuilder_internal_loop_sum_bc_core_exec_canary_vm(rc=8) + +Loop/PHI Unification +- Runner v0 の Loop/PHI は `phi_core` に統一(SSOT)。 +- トグル: `NYASH_MIR_UNIFY_LOOPFORM=1|0`(既定ON)。OFF 指定時は警告を出しつつ統一経路を継続利用(レガシー経路は削除済み)。 +- 目的: 既定ONの明示化と将来のABテスト窓口の確保(挙動は不変)。 Exit Code Policy - Gate‑C(Core): numeric return is mapped to process exit code。タグ付きの失敗時は安定メッセージを出し、可能な限り非0で終了。 @@ -162,6 +196,10 @@ Strict OOB policy (Gate‑C) - With `HAKO_OOB_STRICT_FAIL=1` (alias: `NYASH_OOB_STRICT_FAIL`), Gate‑C(Core) exits non‑zero if any OOB was observed during execution (no need to parse stdout in tests). +Default OOB behavior (Gate‑C/Core) +- 既定では OOB はエラー化せず、rc=0(Void/0 同等)として扱う。 +- 検証を厳密化したい場合は Strict OOB policy を有効化して安定タグまたは非0終了に切替える。 + Exit code differences - Core: 数値=rc(OS仕様により 0–255 に丸められる。例: 777 → rc=9)、エラーは非0 - Direct: 数値出力のみ(rc=0)、エラーは非0 @@ -203,3 +241,27 @@ Core dispatcher canaries(直行ルート) Aliases - Keep existing logical module names in `hako.toml` and introduce aliases to new paths when transitioning. +Mini‑VM bring‑up (Phase 20.34 — notes) +- MiniMap box: minimal string‑backed register map was split out to + `lang/src/vm/boxes/mini_map_box.hako` and is imported via + `using selfhost.vm.helpers.mini_map as MiniMap`. +- InstructionScanner fixes: ret/const misclassification fixed. If an object + has `op` or `dst`, it is not treated as implicit ret. Typed const detection + tolerates spaces. +- PHI handling (dev aids): + - Runtime trace: set `NYASH_VM_TRACE_PHI=1` to log block predecessors and + PHI `inputs.pred` at application time. On strict mismatch, the trace logs + `dst`, `last_pred`, `inputs`, and the block `preds` set to aid diagnosis. + - Strictness: `HAKO_VM_PHI_STRICT=0` or `NYASH_VM_PHI_STRICT=0` can relax the + fail‑fast during bring‑up. `NYASH_VM_PHI_TOLERATE_UNDEFINED=1` substitutes + `Void` when a referenced input is undefined. + - Builder (merge) policy: if/merge PHI inputs are now limited to reachable + predecessors only. Unreachable `else_block` is no longer synthesized as an + input. Enable `HAKO_PHI_VERIFY=1` or `NYASH_PHI_VERIFY=1` to print missing + or duplicate predecessor inputs at build time. + +Trace/verify quickstart +- Preludes (using) parse context: `NYASH_STRIP_DEBUG=1` prints surrounding + lines for parser errors to accelerate pinpointing braces/ASI issues. +- PHI trace: `NYASH_VM_TRACE_PHI=1` alongside a Core run (`--json-file`) shows + predecessor→incoming selection decisions. diff --git a/lang/src/vm/boxes/instruction_scanner.hako b/lang/src/vm/boxes/instruction_scanner.hako index 80de2ad0..4cd6a5c7 100644 --- a/lang/src/vm/boxes/instruction_scanner.hako +++ b/lang/src/vm/boxes/instruction_scanner.hako @@ -64,16 +64,16 @@ static box InstructionScannerBox { if me._has_key(obj, "lhs") == 1 and me._has_key(obj, "rhs") == 1 { return "compare" } if me._has_key(obj, "cond") == 1 { return "branch" } if me._has_key(obj, "target") == 1 { return "jump" } + // Const fallback (typed value object) — tolerant to spaces + if obj.indexOf("\"type\":\"i64\"") >= 0 { return "const" } // Detect explicit ret first if obj.indexOf("\"op\":\"ret\"") >= 0 { return "ret" } - // Detect v1-style Ret without op key: presence of top-level "value" and absence of other discriminator keys + // Detect v1-style Ret without op key: must have top-level "value" and not have other discriminators including explicit op/dst if me._has_key(obj, "value") == 1 { - if me._has_key(obj, "lhs") == 0 && me._has_key(obj, "rhs") == 0 && me._has_key(obj, "cond") == 0 && me._has_key(obj, "target") == 0 && me._has_key(obj, "src") == 0 && me._has_key(obj, "op_kind") == 0 { + if me._has_key(obj, "op") == 0 && me._has_key(obj, "dst") == 0 && me._has_key(obj, "lhs") == 0 && me._has_key(obj, "rhs") == 0 && me._has_key(obj, "cond") == 0 && me._has_key(obj, "target") == 0 && me._has_key(obj, "src") == 0 && me._has_key(obj, "op_kind") == 0 { return "ret" } } - // Const fallback (typed value object) - if obj.indexOf("\"value\":{\"type\":\"i64\"") >= 0 { return "const" } return "" } diff --git a/lang/src/vm/boxes/mini_map_box.hako b/lang/src/vm/boxes/mini_map_box.hako new file mode 100644 index 00000000..9916fc7e --- /dev/null +++ b/lang/src/vm/boxes/mini_map_box.hako @@ -0,0 +1,52 @@ +// mini_map_box.hako — MiniMap +// Responsibility: minimal string-backed map for Mini‑VM registers + +box MiniMap { + store: StringBox + birth() { + me.store = "" + return 0 + } + set(key, value) { + key = "" + key + value = "" + value + // remove existing key + local out = "" + local s = me.store + local pos = 0 + loop(true) { + local nl = s.indexOf("\n", pos) + if nl < 0 { break } + local line = s.substring(pos, nl) + local eq = line.indexOf("=") + if eq >= 0 { + local k = line.substring(0, eq) + if k != key { out = out + line + "\n" } + } + pos = nl + 1 + } + me.store = out + key + "=" + value + "\n" + return 0 + } + get(key) { + key = "" + key + local s = me.store + local pos = 0 + local last = null + loop(true) { + local nl = s.indexOf("\n", pos) + if nl < 0 { break } + local line = s.substring(pos, nl) + local eq = line.indexOf("=") + if eq >= 0 { + local k = line.substring(0, eq) + if k == key { last = line.substring(eq + 1, line.length()) } + } + pos = nl + 1 + } + return last + } +} + +static box MiniMapMain { method main(args){ return 0 } } + diff --git a/lang/src/vm/boxes/mini_mir_v1_scan.hako b/lang/src/vm/boxes/mini_mir_v1_scan.hako new file mode 100644 index 00000000..4d29a130 --- /dev/null +++ b/lang/src/vm/boxes/mini_mir_v1_scan.hako @@ -0,0 +1,64 @@ +// mini_mir_v1_scan.hako — MiniMirV1Scan +// Responsibility: focused helpers to extract callee metadata from MIR JSON v1 +// segments. These utilities centralise the ad-hoc substring scans that were +// previously duplicated inside MirVmMin so Mini‑VM and future lightweight +// runners can share a single tolerant parser. + +using selfhost.shared.json.utils.json_frag as JsonFragBox + +static box MiniMirV1Scan { + // Return the callee name for Global/Extern/ModuleFunction variants. + // Falls back to empty string when the pattern is missing. + callee_name(seg) { + if seg == null { return "" } + local key = "\"callee\":{\"name\":\"" + local p = seg.indexOf(key) + if p < 0 { return "" } + p = p + key.length() + local rest = seg.substring(p, seg.length()) + local q = rest.indexOf("\"") + if q < 0 { return "" } + return rest.substring(0, q) + } + + // Return the method name when callee.type == "Method". + method_name(seg) { + if seg == null { return "" } + if seg.indexOf("\"type\":\"Method\"") < 0 { return "" } + return JsonFragBox.get_str(seg, "method") + } + + // Extract receiver value id (register id) from Method callee + receiver_id(seg) { + if seg == null { return null } + return JsonFragBox.get_int(seg, "receiver") + } + + // Return the first argument register id. This mirrors the legacy helper that + // searched for the first numeric entry inside the args array. + first_arg_register(seg) { + if seg == null { return null } + local key = "\"args\":" + local p = seg.indexOf(key) + if p < 0 { return null } + p = p + key.length() + // locate the first digit (or '-') after the array start + local i = p + loop(true) { + local ch = seg.substring(i, i + 1) + if ch == "" { return null } + if ch == "-" || (ch >= "0" && ch <= "9") { break } + i = i + 1 + } + local out = "" + loop(true) { + local ch = seg.substring(i, i + 1) + if ch == "" { break } + if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break } + } + if out == "" { return null } + return JsonFragBox._str_to_int(out) + } +} + +static box MiniMirV1ScanMain { method main(args) { return 0 } } diff --git a/lang/src/vm/boxes/mini_vm_entry.hako b/lang/src/vm/boxes/mini_vm_entry.hako index 244ce629..7cacd208 100644 --- a/lang/src/vm/boxes/mini_vm_entry.hako +++ b/lang/src/vm/boxes/mini_vm_entry.hako @@ -1,6 +1,6 @@ // mini_vm_entry.hako — MiniVmEntryBox // Thin entry wrapper to stabilize static call names for smokes and tools. -using "lang/src/vm/boxes/mir_vm_min.hako" as MirVmMin +using selfhost.vm.mir_min as MirVmMin static box MiniVmEntryBox { diff --git a/lang/src/vm/boxes/mir_call_v1_handler.hako b/lang/src/vm/boxes/mir_call_v1_handler.hako new file mode 100644 index 00000000..43c6929b --- /dev/null +++ b/lang/src/vm/boxes/mir_call_v1_handler.hako @@ -0,0 +1,76 @@ +// mir_call_v1_handler.hako — MirCallV1HandlerBox +// Responsibility: handle MIR JSON v1 mir_call segments (Constructor/Method/Extern minimal) +// Shared between Mini‑VM and v1 Dispatcher to avoid code duplication. + +using selfhost.shared.json.utils.json_frag as JsonFragBox +using selfhost.vm.helpers.mini_mir_v1_scan as MiniMirV1Scan + +static box MirCallV1HandlerBox { + handle(seg, regs) { + // Constructor: write dst=0 (SSA continuity) + if seg.indexOf("\"type\":\"Constructor\"") >= 0 { + local d0 = JsonFragBox.get_int(seg, "dst"); if d0 != null { regs.setField("" + d0, "0") } + return + } + local name = MiniMirV1Scan.callee_name(seg) + local arg0id = MiniMirV1Scan.first_arg_register(seg) + if name == "" { + // Method callee + local mname = MiniMirV1Scan.method_name(seg) + if mname != "" { + // Stateful bridge (size/len/length/push) guarded by flag + local size_state = env.get("HAKO_VM_MIRCALL_SIZESTATE"); if size_state == null { size_state = "0" } + if ("" + size_state) != "1" { + local stub = env.get("HAKO_VM_MIRCALL_STUB"); if stub == null { stub = "1" } + if ("" + stub) == "1" { print("[vm/method/stub:" + mname + "]") } + local dst0 = JsonFragBox.get_int(seg, "dst"); if dst0 != null { regs.setField("" + dst0, "0") } + return + } + // Per‑receiver or global length counter + local per_recv = env.get("HAKO_VM_MIRCALL_SIZESTATE_PER_RECV"); if per_recv == null { per_recv = "0" } + local key = "__vm_len" + if ("" + per_recv) == "1" { + local rid = MiniMirV1Scan.receiver_id(seg) + if rid != null { key = "__vm_len:" + (""+rid) } + } + local cur_len_raw = regs.getField(key); if cur_len_raw == null { cur_len_raw = "0" } + local cur_len = JsonFragBox._str_to_int(cur_len_raw) + if mname == "push" { + cur_len = cur_len + 1 + regs.setField(key, "" + cur_len) + local d1 = JsonFragBox.get_int(seg, "dst"); if d1 != null { regs.setField("" + d1, "0") } + return + } + if mname == "len" || mname == "length" || mname == "size" { + local d2 = JsonFragBox.get_int(seg, "dst"); if d2 != null { regs.setField("" + d2, "" + cur_len) } + return + } + print("[vm/method/stub:" + mname + "]") + local d3 = JsonFragBox.get_int(seg, "dst"); if d3 != null { regs.setField("" + d3, "0") } + return + } + // No callee found + if (env.get("HAKO_DEV_SILENCE_MISSING_CALLEE") != "1") { print("[ERROR] mir_call: missing callee") } + return + } + // Minimal externs + if arg0id == null { arg0id = -1 } + if name == "env.console.log" || name == "nyash.console.log" || + name == "env.console.warn" || name == "nyash.console.warn" || + name == "env.console.error" || name == "nyash.console.error" { + local v = ""; if arg0id >= 0 { local raw = regs.getField(""+arg0id); v = "" + raw } + print(v) + return + } + if name == "hako_console_log_i64" { + local v = 0; if arg0id >= 0 { local s = regs.getField(""+arg0id); if s != null { v = JsonFragBox._str_to_int(""+s) } } + print("" + v) + return + } + if name == "hako_bench_noop_i64" || name == "hako_bench_use_value_i64" { return } + // Unknown extern: error tag + print("[ERROR] extern not supported: " + name) + } +} + +static box MirCallV1HandlerMain { method main(args) { return 0 } } diff --git a/lang/src/vm/boxes/mir_vm_min.hako b/lang/src/vm/boxes/mir_vm_min.hako index b8772709..9dd0321b 100644 --- a/lang/src/vm/boxes/mir_vm_min.hako +++ b/lang/src/vm/boxes/mir_vm_min.hako @@ -5,8 +5,12 @@ using selfhost.shared.common.string_helpers as StringHelpers using selfhost.shared.common.string_ops as StringOps using selfhost.shared.json.utils.json_frag as JsonFragBox using selfhost.shared.json.core.json_cursor as JsonCursorBox +using selfhost.vm.helpers.mini_map as MiniMap using selfhost.vm.helpers.operator as OperatorBox +using selfhost.vm.helpers.mini_mir_v1_scan as MiniMirV1Scan using selfhost.vm.helpers.compare_ops as CompareOpsBox +using selfhost.vm.hakorune-vm.json_v1_reader as JsonV1ReaderBox +using selfhost.vm.helpers.mir_call_v1_handler as MirCallV1HandlerBox using selfhost.vm.helpers.compare_scan as CompareScanBox using selfhost.vm.helpers.phi_apply as PhiApplyBox using selfhost.vm.helpers.guard as GuardBox @@ -15,51 +19,6 @@ using selfhost.vm.helpers.phi_decode as PhiDecodeBox using selfhost.vm.helpers.ret_resolve as RetResolveSimpleBox using selfhost.vm.helpers.instruction_scanner as InstructionScannerBox -// Minimal map for Mini‑VM registers (avoid dependency on provider MapBox) -box MiniMap { - field store: String - birth() { me.store = "" return 0 } - set(key, value) { - key = "" + key - value = "" + value - // remove existing key - local out = "" - local s = me.store - local pos = 0 - loop(true) { - local nl = s.indexOf("\n", pos) - if nl < 0 { break } - local line = s.substring(pos, nl) - local eq = line.indexOf("=") - if eq >= 0 { - local k = line.substring(0, eq) - if k != key { out = out + line + "\n" } - } - pos = nl + 1 - } - me.store = out + key + "=" + value + "\n" - return 0 - } - get(key) { - key = "" + key - local s = me.store - local pos = 0 - local last = null - loop(true) { - local nl = s.indexOf("\n", pos) - if nl < 0 { break } - local line = s.substring(pos, nl) - local eq = line.indexOf("=") - if eq >= 0 { - local k = line.substring(0, eq) - if k == key { last = line.substring(eq + 1, line.length()) } - } - pos = nl + 1 - } - return last - } -} - static box MirVmMin { _tprint(msg) { // Only emit hard errors by default; avoid env dependencies in Mini‑VM @@ -68,59 +27,18 @@ static box MirVmMin { if msg.indexOf("[ERROR]") >= 0 { print(msg) } } _d(msg, trace) { if trace == 1 { print(msg) } } - _parse_callee_name(seg) { - // naive scan: '"callee":{"name":""' - local key = "\"callee\":{\"name\":\"" - local p = seg.indexOf(key) - if p < 0 { return "" } - p = p + key.length() - local rest = seg.substring(p, seg.length()) - local q = rest.indexOf("\"") - if q < 0 { return "" } - return rest.substring(0, q) - } - _parse_method_name(seg) { - // naive scan: '"callee":{"type":"Method","method":""' - local key = "\"callee\":{\"type\":\"Method\",\"method\":\"" - local p = seg.indexOf(key) - if p < 0 { return "" } - p = p + key.length() - local rest = seg.substring(p, seg.length()) - local q = rest.indexOf("\"") - if q < 0 { return "" } - return rest.substring(0, q) - } - _parse_first_arg(seg) { - // naive scan: '"args":[ ' - local key = "\"args\":" - local p = seg.indexOf(key) - if p < 0 { return null } - p = p + key.length() - // find first digit or '-' - local i = p - loop(true) { - local ch = seg.substring(i, i+1) - if ch == "" { return null } - if ch == "-" || (ch >= "0" && ch <= "9") { break } - i = i + 1 - } - // collect digits - local out = "" - loop(true) { - local ch = seg.substring(i, i+1) - if ch == "" { break } - if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break } - } - if out == "" { return null } - return JsonFragBox._str_to_int(out) - } _handle_mir_call(seg, regs) { - // Support minimal externs only (i64 variants). Fail‑Fast for others. - local name = me._parse_callee_name(seg) - local arg0id = me._parse_first_arg(seg) + // Support minimal externs/methods (i64 variants). Constructor is no‑op. + // v1: treat Constructor as no‑op and write dst=0 to keep SSA continuity + if seg.indexOf("\"type\":\"Constructor\"") >= 0 { + local d0 = JsonFragBox.get_int(seg, "dst"); if d0 != null { regs.setField("" + d0, "0") } + return + } + local name = MiniMirV1Scan.callee_name(seg) + local arg0id = MiniMirV1Scan.first_arg_register(seg) if name == "" { // Try Method callee - local mname = me._parse_method_name(seg) + local mname = MiniMirV1Scan.method_name(seg) if mname != "" { // Optional: minimal stateful bridge for size/len/length/push // Enabled by HAKO_VM_MIRCALL_SIZESTATE=1 (default OFF). When OFF, emit stub tag and dst=0. @@ -129,27 +47,34 @@ static box MirVmMin { if ("" + size_state) != "1" { local stub = env.get("HAKO_VM_MIRCALL_STUB"); if stub == null { stub = "1" } if ("" + stub) == "1" { print("[vm/method/stub:" + mname + "]") } - local dst0 = JsonFragBox.get_int(seg, "dst"); if dst0 != null { regs.set("" + dst0, "0") } + local dst0 = JsonFragBox.get_int(seg, "dst"); if dst0 != null { regs.setField("" + dst0, "0") } return } // Stateful branch - // Keep a simple per-run length counter in regs["__vm_len"]. Default 0. - local cur_len_raw = regs.getField("__vm_len"); if cur_len_raw == null { cur_len_raw = "0" } + // Length counter: global or per-receiver depending on flag + local per_recv = env.get("HAKO_VM_MIRCALL_SIZESTATE_PER_RECV") + if per_recv == null { per_recv = "0" } + local key = "__vm_len" + if (""+per_recv) == "1" { + local rid = MiniMirV1Scan.receiver_id(seg) + if rid != null { key = "__vm_len:" + (""+rid) } + } + local cur_len_raw = regs.getField(key); if cur_len_raw == null { cur_len_raw = "0" } local cur_len = JsonFragBox._str_to_int(cur_len_raw) if mname == "push" { cur_len = cur_len + 1 - regs.set("__vm_len", "" + cur_len) + regs.setField(key, "" + cur_len) // push returns void/0 in this minimal path - local d1 = JsonFragBox.get_int(seg, "dst"); if d1 != null { regs.set("" + d1, "0") } + local d1 = JsonFragBox.get_int(seg, "dst"); if d1 != null { regs.setField("" + d1, "0") } return } if mname == "len" || mname == "length" || mname == "size" { - local d2 = JsonFragBox.get_int(seg, "dst"); if d2 != null { regs.set("" + d2, "" + cur_len) } + local d2 = JsonFragBox.get_int(seg, "dst"); if d2 != null { regs.setField("" + d2, "" + cur_len) } return } // Others: no-op but keep stub tag for observability print("[vm/method/stub:" + mname + "]") - local d3 = JsonFragBox.get_int(seg, "dst"); if d3 != null { regs.set("" + d3, "0") } + local d3 = JsonFragBox.get_int(seg, "dst"); if d3 != null { regs.setField("" + d3, "0") } return } me._tprint("[ERROR] mir_call: missing callee") @@ -207,11 +132,101 @@ static box MirVmMin { _load_reg(regs,id){ local v=regs.getField(""+id) if v==null {return 0} local s=""+v if me._is_numeric_str(s)==1 { return JsonFragBox._str_to_int(s) } return 0 } // block helpers - _block_insts_start(mjson,bid){ local key="\"id\":"+me._int_to_str(bid) local p=mjson.indexOf(key) if p<0 {return -1} local q=StringOps.index_of_from(mjson,"\"instructions\":[",p) if q<0 {return -1} return q+15 } + _block_insts_start(mjson,bid){ + // tolerant scan: find '"id"' then parse digits after ':' with optional spaces + local pos = 0 + loop(true) { + local pid = StringOps.index_of_from(mjson, "\"id\"", pos) + if pid < 0 { return -1 } + // find ':' + local pc = StringOps.index_of_from(mjson, ":", pid) + if pc < 0 { return -1 } + local digits = JsonFragBox.read_int_from(mjson, pc + 1) + if digits != null { + local v = JsonFragBox._str_to_int(digits) + if v == bid { + // instructions array for this block + local qk = StringOps.index_of_from(mjson, "\"instructions\"", pc) + if qk < 0 { pos = pid + 1 continue } + local qb = StringOps.index_of_from(mjson, "[", qk) + if qb < 0 { pos = pid + 1 continue } + return qb + } + } + pos = pc + 1 + } + } // local copy handler _handle_copy(seg, regs){ local dst=JsonFragBox.get_int(seg,"dst") local src=JsonFragBox.get_int(seg,"src") if dst==null || src==null {return} local v=regs.getField(""+src) regs.setField(""+dst, v) } + // ret op handler + _handle_ret_op(seg, regs, last_cmp_dst, last_cmp_val, gc_trace) { + local v = JsonFragBox.get_int(seg, "value") + if v == null { + me._tprint("[ERROR] Undefined ret value field") + return -1 + } + if v == last_cmp_dst { + if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } + return last_cmp_val + } + local sval_raw = regs.getField(""+v) + if sval_raw != null { + local sval = "" + sval_raw + if me._is_numeric_str(sval) == 1 { + if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } + return me._load_reg(regs, v) + } + } + me._tprint("[ERROR] Undefined register ret: r"+me._int_to_str(v)) + return -1 + } + + // block ret resolution helper + _resolve_block_ret(inst_seg, regs, last_cmp_dst, last_cmp_val, gc_trace) { + if last_cmp_dst >= 0 { return last_cmp_val } + // Iterate objects in this block and find an explicit ret + local scan = 0 + loop(true) { + if scan >= inst_seg.length() { break } + local tup = InstructionScannerBox.next_tuple(inst_seg, scan) + if tup == "" { break } + local c1 = StringOps.index_of_from(tup, ",", 0) + local c2 = StringOps.index_of_from(tup, ",", c1+1) + if c1 < 0 || c2 < 0 { break } + local obj_start = JsonFragBox._str_to_int(tup.substring(0, c1)) + local obj_end = JsonFragBox._str_to_int(tup.substring(c1+1, c2)) + local op = tup.substring(c2+1, tup.length()) + if op == "ret" { + local seg = inst_seg.substring(obj_start, obj_end) + me._d("[DEBUG] resolve_ret seg=" + seg, 1) + local rid = JsonFragBox.get_int(seg, "value") + if rid != null { + if rid == last_cmp_dst { + if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } + return last_cmp_val + } + local rv = me._load_reg(regs, rid) + if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } + return rv + } + } + scan = obj_end + } + return me._resolve_final_ret(inst_seg, regs, last_cmp_dst, last_cmp_val, gc_trace) + } + + // final ret resolution helper + _resolve_final_ret(inst_seg, regs, last_cmp_dst, last_cmp_val, gc_trace) { + local r = RetResolveSimpleBox.resolve(inst_seg, regs, last_cmp_dst, last_cmp_val) + if r != null { + if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } + return r + } + return me._load_reg(regs, 1) + } + _run_min(mjson) { // Normalize input as string to guarantee String methods availability mjson = "" + mjson @@ -253,7 +268,85 @@ static box MirVmMin { } } } - // 2) compare→ret fast-path skipped (handled by main scanner) + // 2) Light one-step fast path for pattern: const/const/compare/branch then simple ret in next block + // Evaluate block 0 and jump to the chosen block; if it directly returns a register or constant, return it. + { + // Mini fast-eval for B0 + local regs0 = new MiniMap() + local scan0 = 0 + local last_cmp_dst0 = -1 + local last_cmp_val0 = 0 + local bb_choice = -1 + loop(true) { + if scan0 >= b0.length() { break } + local tup0 = InstructionScannerBox.next_tuple(b0, scan0) + if tup0 == "" { break } + local c10 = StringOps.index_of_from(tup0, ",", 0) + local c20 = StringOps.index_of_from(tup0, ",", c10+1) + if c10 < 0 || c20 < 0 { break } + local s0 = JsonFragBox._str_to_int(tup0.substring(0, c10)) + local e0 = JsonFragBox._str_to_int(tup0.substring(c10+1, c20)) + local op0 = tup0.substring(c20+1, tup0.length()) + local seg0 = b0.substring(s0, e0) + if op0 == "const" { OpHandlersBox.handle_const(seg0, regs0) } + else { if op0 == "compare" { + // evaluate compare and stash dst + local dst0 = JsonFragBox.get_int(seg0, "dst") + local l0 = JsonFragBox.get_int(seg0, "lhs") + local r0 = JsonFragBox.get_int(seg0, "rhs") + local kind0 = JsonFragBox.get_str(seg0, "cmp") + if kind0 == "" { local sym0 = JsonFragBox.get_str(seg0, "operation"); if sym0 != "" { kind0 = CompareOpsBox.map_symbol(sym0) } else { kind0 = "Eq" } } + if dst0 != null && l0 != null && r0 != null { + local a0 = me._load_reg(regs0, l0) + local b0v = me._load_reg(regs0, r0) + local cv0 = CompareOpsBox.eval(kind0, a0, b0v) + regs0.setField(""+dst0, ""+cv0) + last_cmp_dst0 = dst0 + last_cmp_val0 = cv0 + } + } else { if op0 == "branch" { + local c = JsonFragBox.get_int(seg0, "cond") + local t = JsonFragBox.get_int(seg0, "then") + local e = JsonFragBox.get_int(seg0, "else") + if c != null && t != null && e != null { + local cv = 0 + if c == last_cmp_dst0 { cv = last_cmp_val0 } else { cv = me._load_reg(regs0, c) } + if cv != 0 { bb_choice = t } else { bb_choice = e } + break + } + } } } + scan0 = e0 + } + if bb_choice >= 0 { + local bst = JsonV1ReaderBox.block_insts_start(mjson, bb_choice) + if bst >= 0 { + local bend = JsonCursorBox.seek_array_end(mjson, bst) + if bend > bst { + local seg1 = mjson.substring(bst + 1, bend) + local scan1 = 0 + loop(true) { + if scan1 >= seg1.length() { break } + local tup1 = InstructionScannerBox.next_tuple(seg1, scan1) + if tup1 == "" { break } + local c11 = StringOps.index_of_from(tup1, ",", 0) + local c21 = StringOps.index_of_from(tup1, ",", c11+1) + if c11 < 0 || c21 < 0 { break } + local s1 = JsonFragBox._str_to_int(tup1.substring(0, c11)) + local e1 = JsonFragBox._str_to_int(tup1.substring(c11+1, c21)) + local op1 = tup1.substring(c21+1, tup1.length()) + local seg_item = seg1.substring(s1, e1) + if op1 == "const" { OpHandlersBox.handle_const(seg_item, regs0) } + if op1 == "ret" { + local r = me._handle_ret_op(seg_item, regs0, last_cmp_dst0, last_cmp_val0, gc_trace) + return r + } + scan1 = e1 + } + } + } + } + } + } // Use user InstanceBox (MiniMap) with getField/setField to avoid provider dependencies local regs = new MiniMap() @@ -272,13 +365,14 @@ static box MirVmMin { loop(true){ steps = steps + 1 if steps > max_steps { return 0 } - local start = me._block_insts_start(mjson, bb) + local start = JsonV1ReaderBox.block_insts_start(mjson, bb) me._d("[DEBUG] start="+me._int_to_str(start), trace) if start < 0 { return 0 } local endp = JsonCursorBox.seek_array_end(mjson, start) me._d("[DEBUG] endp="+me._int_to_str(endp), trace) if endp <= start { return 0 } - local inst_seg = mjson.substring(start, endp) + // Take array inner segment (skip '[') + local inst_seg = mjson.substring(start + 1, endp) me._d("[DEBUG] seglen="+me._int_to_str(inst_seg.length()), trace) // scan objects in this block local scan_pos = 0 @@ -319,41 +413,24 @@ static box MirVmMin { } else if op == "compare" { me._d("[DEBUG] compare seg=" + seg, trace) - // Safe fast-path: if this block later contains a ret of this dst, - // evaluate compare directly from current regs and return immediately. - // This avoids deeper handler chains that could recurse in edge cases. - local rec = CompareScanBox.parse(seg) - local kdst_fast = rec.get("dst") - local klhs_fast = rec.get("lhs") - local krhs_fast = rec.get("rhs") - local kcmp_fast = rec.get("kind") - // Determine if a ret exists after this compare in the same block - local tail = inst_seg.substring(obj_end, inst_seg.length()) - local ridt = JsonFragBox.get_int(tail, "value") - if kdst_fast != null && klhs_fast != null && krhs_fast != null && ridt != null && ridt == kdst_fast { + // Extract fields directly to avoid MapBox dependency + local kdst_fast = JsonFragBox.get_int(seg, "dst") + local klhs_fast = JsonFragBox.get_int(seg, "lhs") + local krhs_fast = JsonFragBox.get_int(seg, "rhs") + local kcmp_fast = JsonFragBox.get_str(seg, "cmp") + if kcmp_fast == "" { + local sym = JsonFragBox.get_str(seg, "operation") + if sym != "" { kcmp_fast = CompareOpsBox.map_symbol(sym) } else { kcmp_fast = "Eq" } + } + // Compute compare result and store + if kdst_fast != null && klhs_fast != null && krhs_fast != null { local a = me._load_reg(regs, klhs_fast) local b = me._load_reg(regs, krhs_fast) - local cv_fast = CompareOpsBox.eval(kcmp_fast, a, b) - // Store result to keep regs consistent for subsequent ops if any - regs.set("" + kdst_fast, "" + cv_fast) + local cv = CompareOpsBox.eval(kcmp_fast, a, b) + regs.setField("" + kdst_fast, "" + cv) last_cmp_dst = kdst_fast - last_cmp_val = cv_fast - me._d("[DEBUG] compare early return with val=" + me._int_to_str(cv_fast), trace) - return cv_fast + last_cmp_val = cv } - - // Fallback to standard handler when early path is not applicable - OpHandlersBox.handle_compare(seg, regs) - local kdst = rec.get("dst") - me._d("[DEBUG] compare kdst=" + me._int_to_str(kdst), trace) - if kdst != null { - last_cmp_dst = kdst - last_cmp_val = me._load_reg(regs, kdst) - me._d("[DEBUG] compare last_cmp_dst=" + me._int_to_str(last_cmp_dst) + " last_cmp_val=" + me._int_to_str(last_cmp_val), trace) - me._d("[DEBUG] compare reg["+me._int_to_str(kdst)+"]=" + regs.get(""+kdst), trace) - } - // Secondary optimization: if a ret follows and targets this dst, return now - if ridt != null && kdst != null && ridt == kdst { return last_cmp_val } } else if op == "mir_call" { me._handle_mir_call(seg, regs) @@ -388,117 +465,23 @@ static box MirVmMin { break } else if op == "phi" { - local res = PhiDecodeBox.decode_result(seg, prev_bb) - if res.is_Ok() == 1 { - local pair = res.as_Ok() - PhiApplyBox.apply(pair.get(0), pair.get(1), regs) - } + // Mini‑VM v0: skip PHI (blocks we exercise should not rely on PHI semantics) } - else if op == "throw" { + if op == "throw" { me._tprint("[ERROR] Throw terminator encountered") return -2 } -else if op == "ret" { - local v = JsonFragBox.get_int(seg, "value") - if v == null { me._tprint("[ERROR] Undefined ret value field") return -1 } - if v == last_cmp_dst { - if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } - return last_cmp_val - } - local sval = "" + regs.get(""+v) - if me._is_numeric_str(sval) == 1 { - if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } - return me._load_reg(regs, v) - } - me._tprint("[ERROR] Undefined register ret: r"+me._int_to_str(v)) - return -1 + if op == "ret" { + me._d("[DEBUG] ret seg=" + seg, trace) + local r = me._handle_ret_op(seg, regs, last_cmp_dst, last_cmp_val, gc_trace) + return r } // advance inst_count = inst_count + 1 scan_pos = obj_end } if moved == 1 { continue } - // Prefer recent compare result when a ret exists targeting it (no recompute) - if inst_seg.indexOf("\"op\":\"ret\"") >= 0 { - local rstartX = inst_seg.indexOf("\"op\":\"ret\"") - local rsegX = inst_seg.substring(rstartX, inst_seg.length()) - local ridX = JsonFragBox.get_int(rsegX, "value") - if ridX != null { if ridX == last_cmp_dst { return last_cmp_val } } - } - // Fallbacks: prefer recent compare result, else resolve explicit/v1 ret if present, else first const - if last_cmp_dst >= 0 { return last_cmp_val } - // Detect explicit ret in this block and resolve - if inst_seg.indexOf("\"op\":\"ret\"") >= 0 { - local rstart = inst_seg.indexOf("\"op\":\"ret\"") - local rseg = inst_seg.substring(rstart, inst_seg.length()) - local rid = JsonFragBox.get_int(rseg, "value") - if rid != null { - if rid == last_cmp_dst { - if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } - return last_cmp_val - } - local rv = me._load_reg(regs, rid) - if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } - return rv - } - } - // (typed const fallback moved above) - if false { - // Grab first two "value":{"type":"i64","value":X} - local first = "" - local second = "" - local search = inst_seg - local key = "\"value\":{\"type\":\"i64\",\"value\":" - local pos = 0 - loop(true) { - local k = StringOps.index_of_from(search, key, pos) - if k < 0 { break } - local ds = search.substring(k + key.length(), search.length()) - // read consecutive digits as number - local i = 0 - local out = "" - loop(true) { - local ch = ds.substring(i, i+1) - if ch == "" { break } - if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break } - } - if out != "" { if first == "" { first = out } else { second = out } } - if second != "" { break } - pos = k + key.length() + i - } - if first != "" && second != "" { - local lv = 0 - local rv = 0 - // simple to_i64 - local i0 = 0 - loop(i0 < first.length()) { lv = lv * 10 + ("0123456789".indexOf(first.substring(i0,i0+1))) i0 = i0 + 1 } - local i1 = 0 - loop(i1 < second.length()) { rv = rv * 10 + ("0123456789".indexOf(second.substring(i1,i1+1))) i1 = i1 + 1 } - // cmp: parse cmp op - local cmp_key = "\"cmp\":\"" - local pk = inst_seg.indexOf(cmp_key) - local cmp = "Eq" - if pk >= 0 { - local i = pk + cmp_key.length() - local j = StringOps.index_of_from(inst_seg, "\"", i) - if j > i { cmp = inst_seg.substring(i, j) } - } - local cv = CompareOpsBox.eval(cmp, lv, rv) - // ret id - local rstart4 = inst_seg.indexOf("\"op\":\"ret\"") - local rseg4 = inst_seg.substring(rstart4, inst_seg.length()) - local rid4 = JsonFragBox.get_int(rseg4, "value") - if rid4 != null { return cv } - } - } - local r = RetResolveSimpleBox.resolve(inst_seg, regs, last_cmp_dst, last_cmp_val) - if r != null { - if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } - return r - } - // Final fallback: return first const (dst=1) if present - local first = me._load_reg(regs, 1) - return first + return me._resolve_block_ret(inst_seg, regs, last_cmp_dst, last_cmp_val, gc_trace) } return 0 } diff --git a/lang/src/vm/boxes/mir_vm_min.hako.backup b/lang/src/vm/boxes/mir_vm_min.hako.backup new file mode 100644 index 00000000..68634d15 --- /dev/null +++ b/lang/src/vm/boxes/mir_vm_min.hako.backup @@ -0,0 +1,456 @@ +// mir_vm_min.hako — Ny製の最小MIR(JSON v0)実行器(const/compare/copy/branch/jump/ret の最小) +using selfhost.vm.helpers.gc_hooks as GcHooks +using selfhost.vm.helpers.op_handlers as OpHandlersBox +using selfhost.shared.common.string_helpers as StringHelpers +using selfhost.shared.common.string_ops as StringOps +using selfhost.shared.json.utils.json_frag as JsonFragBox +using selfhost.shared.json.core.json_cursor as JsonCursorBox +using selfhost.vm.helpers.operator as OperatorBox +using selfhost.vm.helpers.compare_ops as CompareOpsBox +using selfhost.vm.helpers.compare_scan as CompareScanBox +using selfhost.vm.helpers.phi_apply as PhiApplyBox +using selfhost.vm.helpers.guard as GuardBox +using selfhost.vm.helpers.result as Result +using selfhost.vm.helpers.phi_decode as PhiDecodeBox +using selfhost.vm.helpers.ret_resolve as RetResolveSimpleBox +using selfhost.vm.helpers.instruction_scanner as InstructionScannerBox + +// Minimal map for Mini‑VM registers (avoid dependency on provider MapBox) +box MiniMap { + field store: String + birth() { me.store = "" return 0 } + set(key, value) { + key = "" + key + value = "" + value + // remove existing key + local out = "" + local s = me.store + local pos = 0 + loop(true) { + local nl = s.indexOf("\n", pos) + if nl < 0 { break } + local line = s.substring(pos, nl) + local eq = line.indexOf("=") + if eq >= 0 { + local k = line.substring(0, eq) + if k != key { out = out + line + "\n" } + } + pos = nl + 1 + } + me.store = out + key + "=" + value + "\n" + return 0 + } + get(key) { + key = "" + key + local s = me.store + local pos = 0 + local last = null + loop(true) { + local nl = s.indexOf("\n", pos) + if nl < 0 { break } + local line = s.substring(pos, nl) + local eq = line.indexOf("=") + if eq >= 0 { + local k = line.substring(0, eq) + if k == key { last = line.substring(eq + 1, line.length()) } + } + pos = nl + 1 + } + return last + } +} + +static box MirVmMin { + _tprint(msg) { + // Only emit hard errors by default; avoid env dependencies in Mini‑VM + // Coerce to string to avoid VoidBox receiver issues during early boot + msg = "" + msg + if msg.indexOf("[ERROR]") >= 0 { print(msg) } + } + _d(msg, trace) { if trace == 1 { print(msg) } } + _parse_callee_name(seg) { + // naive scan: '"callee":{"name":""' + local key = "\"callee\":{\"name\":\"" + local p = seg.indexOf(key) + if p < 0 { return "" } + p = p + key.length() + local rest = seg.substring(p, seg.length()) + local q = rest.indexOf("\"") + if q < 0 { return "" } + return rest.substring(0, q) + } + _parse_method_name(seg) { + // naive scan: '"callee":{"type":"Method","method":""' + local key = "\"callee\":{\"type\":\"Method\",\"method\":\"" + local p = seg.indexOf(key) + if p < 0 { return "" } + p = p + key.length() + local rest = seg.substring(p, seg.length()) + local q = rest.indexOf("\"") + if q < 0 { return "" } + return rest.substring(0, q) + } + _parse_first_arg(seg) { + // naive scan: '"args":[ ' + local key = "\"args\":" + local p = seg.indexOf(key) + if p < 0 { return null } + p = p + key.length() + // find first digit or '-' + local i = p + loop(true) { + local ch = seg.substring(i, i+1) + if ch == "" { return null } + if ch == "-" || (ch >= "0" && ch <= "9") { break } + i = i + 1 + } + // collect digits + local out = "" + loop(true) { + local ch = seg.substring(i, i+1) + if ch == "" { break } + if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break } + } + if out == "" { return null } + return JsonFragBox._str_to_int(out) + } + _handle_mir_call(seg, regs) { + // Support minimal externs only (i64 variants). Fail‑Fast for others. + local name = me._parse_callee_name(seg) + local arg0id = me._parse_first_arg(seg) + if name == "" { + // Try Method callee + local mname = me._parse_method_name(seg) + if mname != "" { + // Optional: minimal stateful bridge for size/len/length/push + // Enabled by HAKO_VM_MIRCALL_SIZESTATE=1 (default OFF). When OFF, emit stub tag and dst=0. + local size_state = env.get("HAKO_VM_MIRCALL_SIZESTATE") + if size_state == null { size_state = "0" } + if ("" + size_state) != "1" { + local stub = env.get("HAKO_VM_MIRCALL_STUB"); if stub == null { stub = "1" } + if ("" + stub) == "1" { print("[vm/method/stub:" + mname + "]") } + local dst0 = JsonFragBox.get_int(seg, "dst"); if dst0 != null { regs.set("" + dst0, "0") } + return + } + // Stateful branch + // Keep a simple per-run length counter in regs["__vm_len"]. Default 0. + local cur_len_raw = regs.getField("__vm_len"); if cur_len_raw == null { cur_len_raw = "0" } + local cur_len = JsonFragBox._str_to_int(cur_len_raw) + if mname == "push" { + cur_len = cur_len + 1 + regs.set("__vm_len", "" + cur_len) + // push returns void/0 in this minimal path + local d1 = JsonFragBox.get_int(seg, "dst"); if d1 != null { regs.set("" + d1, "0") } + return + } + if mname == "len" || mname == "length" || mname == "size" { + local d2 = JsonFragBox.get_int(seg, "dst"); if d2 != null { regs.set("" + d2, "" + cur_len) } + return + } + // Others: no-op but keep stub tag for observability + print("[vm/method/stub:" + mname + "]") + local d3 = JsonFragBox.get_int(seg, "dst"); if d3 != null { regs.set("" + d3, "0") } + return + } + me._tprint("[ERROR] mir_call: missing callee") + return + } + if arg0id == null { arg0id = -1 } + // String console: env/nyash console log/warn/error — treat arg0 as string + if name == "env.console.log" || name == "nyash.console.log" || + name == "env.console.warn" || name == "nyash.console.warn" || + name == "env.console.error" || name == "nyash.console.error" { + local v = "" + if arg0id >= 0 { + local raw = regs.getField(""+arg0id) + v = "" + raw + } + print(v) + return + } + if name == "hako_console_log_i64" { + local v = 0 + if arg0id >= 0 { v = me._load_reg(regs, arg0id) } + print(me._int_to_str(v)) + return + } + if name == "hako_bench_noop_i64" || name == "hako_bench_use_value_i64" { + // no-op (observability only) + return + } + // Unknown extern: Fail‑Fast (emit once) + me._tprint("[ERROR] extern not supported: " + name) + } + // Compatibility runner (prints and returns). Prefer run_min for quiet return-only. + run(mjson) { local v = me._run_min(mjson) print(me._int_to_str(v)) return v } + // New: quiet entry that returns the result without printing. + run_min(mjson) { return me._run_min(mjson) } + // Thin-mode runner (ret resolution simplified) + run_thin(mjson) { + // Inject a lightweight marker into JSON to toggle thin mode inside _run_min + if mjson.substring(0,1) == "{" { + local payload = mjson.substring(1, mjson.length()) + local j2 = "{\"__thin__\":1," + payload + local v = me._run_min(j2) + print(me._int_to_str(v)) + return v + } + // Fallback: no-op when input is unexpected + local v = me._run_min(mjson) + print(me._int_to_str(v)) + return v + } + + // helpers + _int_to_str(n) { return StringHelpers.int_to_str(n) } + _is_numeric_str(s){ if s==null {return 0} local n=s.length() if n==0 {return 0} local i=0 if s.substring(0,1)=="-" { if n==1 {return 0} i=1 } loop(i"9" {return 0} i=i+1 } return 1 } + _load_reg(regs,id){ local v=regs.getField(""+id) if v==null {return 0} local s=""+v if me._is_numeric_str(s)==1 { return JsonFragBox._str_to_int(s) } return 0 } + + // block helpers + _block_insts_start(mjson,bid){ local key="\"id\":"+me._int_to_str(bid) local p=mjson.indexOf(key) if p<0 {return -1} local q=StringOps.index_of_from(mjson,"\"instructions\":[",p) if q<0 {return -1} return q+15 } + + // local copy handler + _handle_copy(seg, regs){ local dst=JsonFragBox.get_int(seg,"dst") local src=JsonFragBox.get_int(seg,"src") if dst==null || src==null {return} local v=regs.getField(""+src) regs.setField(""+dst, v) } + + _run_min(mjson) { + // Normalize input as string to guarantee String methods availability + mjson = "" + mjson + // thin_mode=0: legacy heuristics(互換); thin_mode=1: simplified ret + local thin_mode = 0 + if mjson.indexOf("\"__thin__\":1") >= 0 { thin_mode = 1 } + // trace mode for dev: prints [DEBUG] messages + local trace = 0 + if mjson.indexOf("\"__trace__\":1") >= 0 { trace = 1 } + // gc trace (mini‑VM専用マーカー; 環境依存を避ける) + local gc_trace = 0 + if mjson.indexOf("\"__gc_trace__\":1") >= 0 { gc_trace = 1 } + + // Safety first: handle simple compare→ret or const→ret in Block 0 + // without allocating any runtime boxes. This avoids interpreter-level + // recursion during early startup (observed as Rust stack overflow) and + // covers our selfhost M2 minimal cases. + local b0 = JsonFragBox.block0_segment(mjson) + if b0 != "" { + // 1) Pure const→ret (only when const appears before ret and no other ops present) + if b0.indexOf("\"op\":\"compare\"") < 0 && b0.indexOf("\"op\":\"binop\"") < 0 && b0.indexOf("\"op\":\"copy\"") < 0 && b0.indexOf("\"op\":\"jump\"") < 0 && b0.indexOf("\"op\":\"branch\"") < 0 { + local ret_pos = b0.indexOf("\"op\":\"ret\"") + local const_pos = b0.indexOf("\"op\":\"const\"") + if ret_pos >= 0 and const_pos >= 0 and const_pos < ret_pos { + // Grab the first typed const value and return it + local key = "\"value\":{\"type\":\"i64\",\"value\":" + local p = StringOps.index_of_from(b0, key, 0) + if p >= 0 { + local ds = b0.substring(p + key.length(), b0.length()) + // read consecutive digits + local i = 0 + local out = "" + loop(true) { + local ch = ds.substring(i, i+1) + if ch == "" { break } + if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break } + } + if out != "" { return JsonFragBox._str_to_int(out) } + } + } + } + // 2) compare→ret fast-path skipped (handled by main scanner) + + // Use user InstanceBox (MiniMap) with getField/setField to avoid provider dependencies + local regs = new MiniMap() + local last_cmp_dst = -1 + local last_cmp_val = 0 + // Optional: adopt OperatorBox for parity checks when JSON has a special marker + local op_adopt = 0 + if mjson.indexOf("\"__op_adopt__\":1") >= 0 { op_adopt = 1 } + // Track last binop materialization within the current scan window + local last_binop_dst = -1 + local last_binop_val = 0 + local bb = 0 + local prev_bb = -1 + local steps = 0 + local max_steps = 200000 + loop(true){ + steps = steps + 1 + if steps > max_steps { return 0 } + local start = me._block_insts_start(mjson, bb) + me._d("[DEBUG] start="+me._int_to_str(start), trace) + if start < 0 { return 0 } + local endp = JsonCursorBox.seek_array_end(mjson, start) + me._d("[DEBUG] endp="+me._int_to_str(endp), trace) + if endp <= start { return 0 } + local inst_seg = mjson.substring(start, endp) + me._d("[DEBUG] seglen="+me._int_to_str(inst_seg.length()), trace) + // scan objects in this block + local scan_pos = 0 + local inst_count = 0 + local moved = 0 + loop(true){ + if scan_pos >= inst_seg.length() { break } + local tup = InstructionScannerBox.next_tuple(inst_seg, scan_pos) + if tup == "" { break } + // parse "start,end,op" + local c1 = StringOps.index_of_from(tup, ",", 0) + local c2 = StringOps.index_of_from(tup, ",", c1+1) + if c1 < 0 || c2 < 0 { break } + local obj_start = JsonFragBox._str_to_int(tup.substring(0, c1)) + local obj_end = JsonFragBox._str_to_int(tup.substring(c1+1, c2)) + local op = tup.substring(c2+1, tup.length()) + local seg = inst_seg.substring(obj_start, obj_end) + if op == null { op = "" } if op == "null" { op = "" } if op == 0 { op = "" } + if op == "" { + if seg.indexOf("op_kind") >= 0 { op = "binop" } else { + if seg.indexOf("lhs") >= 0 and seg.indexOf("rhs") >= 0 { op = "compare" } else { + if seg.indexOf("cond") >= 0 { op = "branch" } else { + if seg.indexOf("target") >= 0 { op = "jump" } else { + if seg.indexOf("src") >= 0 and seg.indexOf("dst") >= 0 { op = "copy" } else { op = "const" } + } + } + } + } + } + if op == "const" { + OpHandlersBox.handle_const(seg, regs) + } + else if op == "copy" { me._handle_copy(seg, regs) } + else if op == "binop" { + OpHandlersBox.handle_binop(seg, regs) + local bdst = JsonFragBox.get_int(seg, "dst") + if bdst != null { last_binop_dst = bdst last_binop_val = me._load_reg(regs, bdst) } + } + else if op == "compare" { + me._d("[DEBUG] compare seg=" + seg, trace) + // Safe fast-path: if this block later contains a ret of this dst, + // evaluate compare directly from current regs and return immediately. + // This avoids deeper handler chains that could recurse in edge cases. + local rec = CompareScanBox.parse(seg) + local kdst_fast = rec.get("dst") + local klhs_fast = rec.get("lhs") + local krhs_fast = rec.get("rhs") + local kcmp_fast = rec.get("kind") + // Determine if a ret exists after this compare in the same block + local tail = inst_seg.substring(obj_end, inst_seg.length()) + local ridt = JsonFragBox.get_int(tail, "value") + if kdst_fast != null && klhs_fast != null && krhs_fast != null && ridt != null && ridt == kdst_fast { + local a = me._load_reg(regs, klhs_fast) + local b = me._load_reg(regs, krhs_fast) + local cv_fast = CompareOpsBox.eval(kcmp_fast, a, b) + // Store result to keep regs consistent for subsequent ops if any + regs.set("" + kdst_fast, "" + cv_fast) + last_cmp_dst = kdst_fast + last_cmp_val = cv_fast + me._d("[DEBUG] compare early return with val=" + me._int_to_str(cv_fast), trace) + return cv_fast + } + + // Fallback to standard handler when early path is not applicable + OpHandlersBox.handle_compare(seg, regs) + local kdst = rec.get("dst") + me._d("[DEBUG] compare kdst=" + me._int_to_str(kdst), trace) + if kdst != null { + last_cmp_dst = kdst + last_cmp_val = me._load_reg(regs, kdst) + me._d("[DEBUG] compare last_cmp_dst=" + me._int_to_str(last_cmp_dst) + " last_cmp_val=" + me._int_to_str(last_cmp_val), trace) + me._d("[DEBUG] compare reg["+me._int_to_str(kdst)+"]=" + regs.get(""+kdst), trace) + } + // Secondary optimization: if a ret follows and targets this dst, return now + if ridt != null && kdst != null && ridt == kdst { return last_cmp_val } + } + else if op == "mir_call" { + me._handle_mir_call(seg, regs) + } + else if op == "branch" { + local c = JsonFragBox.get_int(seg, "cond") + local t = JsonFragBox.get_int(seg, "then") + local e = JsonFragBox.get_int(seg, "else") + local cv = 0 + if c != null { + if c == last_cmp_dst { cv = last_cmp_val } else { cv = me._load_reg(regs, c) } + } + prev_bb = bb + if cv != 0 { bb = t } else { bb = e } + moved = 1 + // GC v0 safepoint: back-edge or control transfer + GcHooks.safepoint() + if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } + scan_pos = obj_end + break + } + else if op == "jump" { + local tgt = JsonFragBox.get_int(seg, "target") + if tgt == null { return 0 } + prev_bb = bb + bb = tgt + moved = 1 + // GC v0 safepoint: back-edge or control transfer + GcHooks.safepoint() + if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } + scan_pos = obj_end + break + } + else if op == "phi" { + local res = PhiDecodeBox.decode_result(seg, prev_bb) + if res.is_Ok() == 1 { + local pair = res.as_Ok() + PhiApplyBox.apply(pair.get(0), pair.get(1), regs) + } + } else if op == "throw" { + me._tprint("[ERROR] Throw terminator encountered") + return -2 + } else if op == "ret" { + local v = JsonFragBox.get_int(seg, "value") + if v == null { me._tprint("[ERROR] Undefined ret value field") return -1 } + if v == last_cmp_dst { + if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } + return last_cmp_val + } + local sval = "" + regs.get(""+v) + if me._is_numeric_str(sval) == 1 { + if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } + return me._load_reg(regs, v) + } + me._tprint("[ERROR] Undefined register ret: r"+me._int_to_str(v)) + return -1 + } + // advance + inst_count = inst_count + 1 + scan_pos = obj_end + } + if moved == 1 { continue } + // Prefer recent compare result when a ret exists targeting it (no recompute) + if inst_seg.indexOf("\"op\":\"ret\"") >= 0 { + local rstartX = inst_seg.indexOf("\"op\":\"ret\"") + local rsegX = inst_seg.substring(rstartX, inst_seg.length()) + local ridX = JsonFragBox.get_int(rsegX, "value") + if ridX != null { if ridX == last_cmp_dst { return last_cmp_val } } + } + // Fallbacks: prefer recent compare result, else resolve explicit/v1 ret if present, else first const + if last_cmp_dst >= 0 { return last_cmp_val } + // Detect explicit ret in this block and resolve + if inst_seg.indexOf("\"op\":\"ret\"") >= 0 { + local rstart = inst_seg.indexOf("\"op\":\"ret\"") + local rseg = inst_seg.substring(rstart, inst_seg.length()) + local rid = JsonFragBox.get_int(rseg, "value") + if rid != null { + if rid == last_cmp_dst { + if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } + return last_cmp_val + } + local rv = me._load_reg(regs, rid) + if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } + return rv + } + } + // (typed const fallback moved above) + local r = RetResolveSimpleBox.resolve(inst_seg, regs, last_cmp_dst, last_cmp_val) + if r != null { + if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") } + return r + } + // Final fallback: return first const (dst=1) if present + local first = me._load_reg(regs, 1) + return first + } + return 0 + } + +} diff --git a/lang/src/vm/core/json_v0_reader.hako b/lang/src/vm/core/json_v0_reader.hako index 2f295bed..31ac9013 100644 --- a/lang/src/vm/core/json_v0_reader.hako +++ b/lang/src/vm/core/json_v0_reader.hako @@ -17,7 +17,7 @@ static box NyVmJsonV0Reader { // Return substring for the first function object first_function(json) { - local p = JsonCursorBox.find_key_dual(json, "\"functions\":[", r#"\"functions\":\["#, 0) + local p = JsonCursorBox.find_key_dual(json, "\"functions\":[", "\"functions\":[", 0) if p < 0 { return "" } local lb = json.indexOf("[", p) if lb < 0 { return "" } @@ -30,7 +30,7 @@ static box NyVmJsonV0Reader { // Return substring for the first block object first_block(func_json) { - local p = JsonCursorBox.find_key_dual(func_json, "\"blocks\":[", r#"\"blocks\":\["#, 0) + local p = JsonCursorBox.find_key_dual(func_json, "\"blocks\":[", "\"blocks\":[", 0) if p < 0 { return "" } local lb = func_json.indexOf("[", p) if lb < 0 { return "" } @@ -43,7 +43,7 @@ static box NyVmJsonV0Reader { // Return substring for the instructions array content (without the surrounding brackets) block_instructions_json(block_json) { - local p = JsonCursorBox.find_key_dual(block_json, "\"instructions\":[", r#"\"instructions\":\["#, 0) + local p = JsonCursorBox.find_key_dual(block_json, "\"instructions\":[", "\"instructions\":[", 0) if p < 0 { return "" } local lb = block_json.indexOf("[", p) if lb < 0 { return "" } @@ -54,7 +54,7 @@ static box NyVmJsonV0Reader { // Read function entry id if present; returns -1 when missing read_entry_id(func_json) { - local p = JsonCursorBox.find_key_dual(func_json, "\"entry\":", r#"\"entry\":"#, 0) + local p = JsonCursorBox.find_key_dual(func_json, "\"entry\":", "\"entry\":", 0) if p < 0 { return -1 } p = func_json.indexOf(":", p) if p < 0 { return -1 } @@ -68,7 +68,7 @@ static box NyVmJsonV0Reader { // Parse block id from a block JSON object; returns -1 on failure read_block_id(block_json) { - local p = JsonCursorBox.find_key_dual(block_json, "\"id\":", r#"\"id\":"#, 0) + local p = JsonCursorBox.find_key_dual(block_json, "\"id\":", "\"id\":", 0) if p < 0 { return -1 } p = block_json.indexOf(":", p) if p < 0 { return -1 } @@ -84,7 +84,7 @@ static box NyVmJsonV0Reader { build_block_map(func_json) { local out = new MapBox() // locate blocks array - local p = JsonCursorBox.find_key_dual(func_json, "\"blocks\":[", r#"\"blocks\":\["#, 0) + local p = JsonCursorBox.find_key_dual(func_json, "\"blocks\":[", "\"blocks\":[", 0) if p < 0 { return out } local lb = func_json.indexOf("[", p) if lb < 0 { return out } diff --git a/lang/src/vm/hakorune-vm/dispatcher_v1.hako b/lang/src/vm/hakorune-vm/dispatcher_v1.hako new file mode 100644 index 00000000..d1cd50f3 --- /dev/null +++ b/lang/src/vm/hakorune-vm/dispatcher_v1.hako @@ -0,0 +1,22 @@ +// dispatcher_v1.hako — NyVmDispatcherV1Box +// Responsibility: minimal MIR JSON v1 executor (const/mir_call(Constructor/Method)/ret) + +using selfhost.vm.mir_min as MirVmMin +using selfhost.vm.helpers.op_handlers as OpHandlersBox +using selfhost.vm.helpers.instruction_scanner as InstructionScannerBox +using selfhost.vm.helpers.mini_map as MiniMap +using selfhost.shared.json.utils.json_frag as JsonFragBox +using selfhost.shared.json.core.json_cursor as JsonCursorBox +using selfhost.shared.common.string_ops as StringOps +using selfhost.vm.hakorune-vm.json_v1_reader as JsonV1ReaderBox + +static box NyVmDispatcherV1Box { + // Minimal v1 executor. For coverage beyond const/mir_call/ret (e.g., + // branch/jump/phi), delegate to Mini‑VM's robust runner which already + // handles block traversal and PHI application on JSON text. + run(json) { + return MirVmMin.run_min(json) + } +} + +static box NyVmDispatcherV1Main { method main(args) { return 0 } } diff --git a/lang/src/vm/hakorune-vm/extern_provider.hako b/lang/src/vm/hakorune-vm/extern_provider.hako new file mode 100644 index 00000000..c6be9ac7 --- /dev/null +++ b/lang/src/vm/hakorune-vm/extern_provider.hako @@ -0,0 +1,25 @@ +// extern_provider.hako — HakoruneExternProviderBox +// Responsibility: minimal extern/global call handling on Hako side. +// Scope (20.38準備): env.get / console.log のみ(C‑ABI導線は後段で接続)。 + +static box HakoruneExternProviderBox { + get(name, args) { + // name: string like "env.get" or "env.console.log" + if name == "env.get" { + if args == null { return null } + // args: single string key + local key = "" + args + return env.get(key) + } + if name == "env.console.log" || name == "nyash.console.log" || name == "print" { + // Accept single argument value → print as string + if args == null { print(""); return 0 } + print("" + args) + return 0 + } + // Unknown: return null for now (caller decides Fail‑Fast) + return null + } +} + +static box HakoruneExternProviderMain { method main(args) { return 0 } } diff --git a/lang/src/vm/hakorune-vm/json_v1_reader.hako b/lang/src/vm/hakorune-vm/json_v1_reader.hako new file mode 100644 index 00000000..7706a89a --- /dev/null +++ b/lang/src/vm/hakorune-vm/json_v1_reader.hako @@ -0,0 +1,45 @@ +// json_v1_reader.hako — JsonV1ReaderBox +// Responsibility: extract block0 instructions array segment from MIR JSON v1 + +using selfhost.shared.json.utils.json_frag as JsonFragBox +using selfhost.shared.json.core.json_cursor as JsonCursorBox + +static box JsonV1ReaderBox { + // Return inner segment (without '[' and ']') of functions[0].blocks[0].instructions + get_block0_instructions(json) { + if json == null { return "" } + // naive search; rely on escape-aware array end + local key = "\"instructions\":[" + local p = (""+json).indexOf(key) + if p < 0 { return "" } + local lb = p + key.length() - 1 + local rb = JsonFragBox._seek_array_end(json, lb) + if rb <= lb { return "" } + return (""+json).substring(lb + 1, rb) + } + + block_insts_start(mjson, bid) { + // tolerant scan: find '"id"' then match instructions for the given block id + local pos = 0 + loop(true) { + local pid = (""+mjson).indexOf("\"id\"", pos) + if pid < 0 { return -1 } + local pc = (""+mjson).indexOf(":", pid) + if pc < 0 { return -1 } + local digits = JsonFragBox.read_int_from(mjson, pc + 1) + if digits != null { + local v = JsonFragBox._str_to_int(digits) + if v == bid { + local qk = (""+mjson).indexOf("\"instructions\"", pc) + if qk < 0 { pos = pid + 1 continue } + local qb = (""+mjson).indexOf("[", qk) + if qb < 0 { pos = pid + 1 continue } + return qb + } + } + pos = pc + 1 + } + } +} + +static box JsonV1ReaderMain { method main(args) { return 0 } } diff --git a/nyash.toml b/nyash.toml index 2f855c96..f663459c 100644 --- a/nyash.toml +++ b/nyash.toml @@ -153,6 +153,12 @@ path = "lang/src/shared/common/string_helpers.hako" "selfhost.vm.core" = "lang/src/vm/boxes/mini_vm_core.hako" "selfhost.vm.helpers.op_handlers" = "lang/src/vm/boxes/op_handlers.hako" "selfhost.vm.helpers.operator" = "lang/src/vm/boxes/operator_box.hako" +"selfhost.vm.helpers.mini_mir_v1_scan" = "lang/src/vm/boxes/mini_mir_v1_scan.hako" +"selfhost.vm.helpers.mir_call_v1_handler" = "lang/src/vm/boxes/mir_call_v1_handler.hako" +"selfhost.vm.hakorune-vm.json_v1_reader" = "lang/src/vm/hakorune-vm/json_v1_reader.hako" +"selfhost.vm.hv1.reader" = "lang/src/vm/hakorune-vm/json_v1_reader.hako" +"selfhost.vm.hakorune-vm.dispatch" = "lang/src/vm/hakorune-vm/dispatcher_v1.hako" +"selfhost.vm.hv1.dispatch" = "lang/src/vm/hakorune-vm/dispatcher_v1.hako" "selfhost.vm.helpers.compare_ops" = "lang/src/vm/boxes/compare_ops.hako" "selfhost.vm.helpers.compare_scan" = "lang/src/vm/boxes/compare_scan_box.hako" "selfhost.vm.helpers.phi_apply" = "lang/src/vm/boxes/phi_apply_box.hako" @@ -164,6 +170,7 @@ path = "lang/src/shared/common/string_helpers.hako" "selfhost.vm.helpers.gc_hooks" = "lang/src/vm/gc/gc_hooks.hako" "selfhost.vm.helpers.arithmetic" = "lang/src/vm/boxes/arithmetic.hako" "selfhost.vm.helpers.cfg_navigator" = "lang/src/vm/boxes/cfg_navigator.hako" +"selfhost.vm.helpers.mini_map" = "lang/src/vm/boxes/mini_map_box.hako" "hakorune.vm.entry" = "lang/src/vm/boxes/mini_vm_entry.hako" "hakorune.vm.mir_min" = "lang/src/vm/boxes/mir_vm_min.hako" "hakorune.vm.core" = "lang/src/vm/boxes/mini_vm_core.hako" diff --git a/src/abi/nyrt_shim.rs b/src/abi/nyrt_shim.rs new file mode 100644 index 00000000..2005b6ca --- /dev/null +++ b/src/abi/nyrt_shim.rs @@ -0,0 +1,44 @@ +// C‑ABI shim (PoC). These functions are no‑ops for 20.36/20.37. +// Kept tiny and isolated. Linkage names match include/nyrt.h. + +#[no_mangle] +pub extern "C" fn nyrt_init() -> i32 { 0 } + +#[no_mangle] +pub extern "C" fn nyrt_teardown() { } + +#[no_mangle] +pub extern "C" fn nyrt_load_mir_json(_json_text: *const ::std::os::raw::c_char) -> u64 { 1 } + +#[no_mangle] +pub extern "C" fn nyrt_exec_main(_module_handle: u64) -> i32 { 0 } + +#[no_mangle] +pub extern "C" fn nyrt_hostcall( + _name: *const ::std::os::raw::c_char, + _method: *const ::std::os::raw::c_char, + _payload_json: *const ::std::os::raw::c_char, + _out_buf: *mut ::std::os::raw::c_char, + _out_buf_len: u32, +) -> i32 { 0 } + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::CString; + + #[test] + fn load_and_exec_noop_returns_zero() { + // init/teardown are no-ops but should stay callable + assert_eq!(unsafe { nyrt_init() }, 0); + + let json = CString::new("{}").expect("CString"); + let handle = unsafe { nyrt_load_mir_json(json.as_ptr()) }; + assert_eq!(handle, 1); + + assert_eq!(unsafe { nyrt_exec_main(handle) }, 0); + + // ensure teardown does not panic even when called after exec + unsafe { nyrt_teardown() }; + } +} diff --git a/src/backend/mir_interpreter/exec.rs b/src/backend/mir_interpreter/exec.rs index 7b59c22e..5b69bb69 100644 --- a/src/backend/mir_interpreter/exec.rs +++ b/src/backend/mir_interpreter/exec.rs @@ -100,9 +100,25 @@ impl MirInterpreter { block: &BasicBlock, last_pred: Option, ) -> Result<(), VMError> { + let trace_phi = std::env::var("NYASH_VM_TRACE_PHI").ok().as_deref() == Some("1"); + if trace_phi { + eprintln!( + "[vm-trace-phi] enter bb={:?} last_pred={:?} preds={:?}", + block.id, + last_pred, + block.predecessors + ); + } for inst in block.phi_instructions() { if let MirInstruction::Phi { dst, inputs } = inst { let dst_id = *dst; + if trace_phi { + let in_preds: Vec<_> = inputs.iter().map(|(bb, _)| *bb).collect(); + eprintln!( + "[vm-trace-phi] phi dst={:?} inputs.pred={:?}", + dst_id, in_preds + ); + } if let Some(pred) = last_pred { if let Some((_, val)) = inputs.iter().find(|(bb, _)| *bb == pred) { let v = match self.reg_load(*val) { @@ -150,6 +166,15 @@ impl MirInterpreter { } }; if strict { + if trace_phi { + eprintln!( + "[vm-trace-phi][strict] mismatch dst={:?} last_pred={:?} inputs={:?} preds_of_bb={:?}", + dst_id, + pred, + inputs, + block.predecessors + ); + } return Err(VMError::InvalidInstruction(format!( "phi pred mismatch at {:?}: no input for predecessor {:?}", dst_id, pred diff --git a/src/backend/mir_interpreter/handlers/calls.rs b/src/backend/mir_interpreter/handlers/calls.rs index 200f94b7..0ddb1387 100644 --- a/src/backend/mir_interpreter/handlers/calls.rs +++ b/src/backend/mir_interpreter/handlers/calls.rs @@ -645,6 +645,7 @@ impl MirInterpreter { extern_name: &str, args: &[ValueId], ) -> Result { + if let Some(res) = self.extern_provider_dispatch(extern_name, args) { return res; } match extern_name { // Minimal console externs "nyash.console.log" | "env.console.log" | "print" | "nyash.builtin.print" => { @@ -656,6 +657,8 @@ impl MirInterpreter { } 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) diff --git a/src/backend/mir_interpreter/handlers/extern_provider.rs b/src/backend/mir_interpreter/handlers/extern_provider.rs new file mode 100644 index 00000000..a5ee6c6e --- /dev/null +++ b/src/backend/mir_interpreter/handlers/extern_provider.rs @@ -0,0 +1,142 @@ +use super::*; +use serde_json::Value as JsonValue; + +impl MirInterpreter { + fn patch_mir_json_version(s: &str) -> String { + match serde_json::from_str::(s) { + Ok(mut v) => { + if let JsonValue::Object(ref mut m) = v { + if !m.contains_key("version") { + m.insert("version".to_string(), JsonValue::from(0)); + if let Ok(out) = serde_json::to_string(&v) { return out; } + } + } + s.to_string() + } + Err(_) => s.to_string(), + } + } + + /// Central extern dispatcher used by both execute_extern_function (calls.rs) + /// and handle_extern_call (externals.rs). Returns a VMValue; callers are + /// responsible for writing it to registers when needed. + pub(super) fn extern_provider_dispatch( + &mut self, + extern_name: &str, + args: &[ValueId], + ) -> Option> { + match extern_name { + // Console/print family (minimal) + "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(v) = s { println!("{}", v.to_string()); } else { println!(""); } + Some(Ok(VMValue::Void)) + } + // Extern providers (env.mirbuilder / env.codegen) + "env.mirbuilder.emit" => { + if args.is_empty() { return Some(Err(VMError::InvalidInstruction("env.mirbuilder.emit expects 1 arg".into()))); } + let program_json = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }; + let res = match crate::host_providers::mir_builder::program_json_to_mir_json(&program_json) { + Ok(s) => Ok(VMValue::String(Self::patch_mir_json_version(&s))), + Err(e) => Err(VMError::InvalidInstruction(format!("env.mirbuilder.emit: {}", e))), + }; + Some(res) + } + "env.codegen.emit_object" => { + if args.is_empty() { return Some(Err(VMError::InvalidInstruction("env.codegen.emit_object expects 1 arg".into()))); } + let mir_json = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }; + 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(), + timeout_ms: None, + }; + let res = match crate::host_providers::llvm_codegen::mir_json_to_object(&mir_json, opts) { + Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())), + Err(e) => Err(VMError::InvalidInstruction(format!("env.codegen.emit_object: {}", e))), + }; + Some(res) + } + // Environment + "env.get" => { + if args.is_empty() { return Some(Err(VMError::InvalidInstruction("env.get expects 1 arg".into()))); } + let key = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }; + let val = std::env::var(&key).ok(); + Some(Ok(match val { Some(s) => VMValue::String(s), None => VMValue::Void })) + } + // Legacy global-call form: hostbridge.extern_invoke(name, method, args?) + "hostbridge.extern_invoke" => { + if args.len() < 2 { + return Some(Err(VMError::InvalidInstruction( + "extern_invoke expects at least 2 args".into(), + ))); + } + let name = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }; + let method = match self.reg_load(args[1]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }; + // Extract first payload arg (optional) + let mut first_arg_str: Option = None; + if let Some(a2) = args.get(2) { + let v = match self.reg_load(*a2) { Ok(v) => v, Err(e) => return Some(Err(e)) }; + match v { + VMValue::BoxRef(b) => { + if let Some(ab) = b.as_any().downcast_ref::() { + let idx: Box = + 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()), + } + } + // Dispatch to known providers + let out = 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(Self::patch_mir_json_version(&out))), + Err(e) => Err(VMError::InvalidInstruction(format!( + "env.mirbuilder.emit: {}", + e + ))), + } + } else { + Err(VMError::InvalidInstruction( + "extern_invoke env.mirbuilder.emit expects 1 arg".into(), + )) + } + } + ("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(), + 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(VMError::InvalidInstruction(format!( + "env.codegen.emit_object: {}", + e + ))), + } + } else { + Err(VMError::InvalidInstruction( + "extern_invoke env.codegen.emit_object expects 1 arg".into(), + )) + } + } + _ => Err(VMError::InvalidInstruction(format!( + "hostbridge.extern_invoke unsupported for {}.{}", + name, method + ))), + }; + Some(out) + } + _ => None, + } + } +} diff --git a/src/backend/mir_interpreter/handlers/externals.rs b/src/backend/mir_interpreter/handlers/externals.rs index 7460e5a1..bba52d73 100644 --- a/src/backend/mir_interpreter/handlers/externals.rs +++ b/src/backend/mir_interpreter/handlers/externals.rs @@ -1,6 +1,22 @@ use super::*; +use serde_json::{Value as JsonValue, Map as JsonMap}; impl MirInterpreter { + #[inline] + fn ensure_mir_json_version_field(s: &str) -> String { + match serde_json::from_str::(s) { + Ok(mut v) => { + if let JsonValue::Object(ref mut m) = v { + if !m.contains_key("version") { + m.insert("version".to_string(), JsonValue::from(0)); + if let Ok(out) = serde_json::to_string(&v) { return out; } + } + } + s.to_string() + } + Err(_) => s.to_string(), + } + } pub(super) fn handle_extern_call( &mut self, dst: Option, @@ -131,50 +147,20 @@ impl MirInterpreter { Ok(()) } ("env", "get") => { - // env.get(key) - get environment variable - if let Some(a0) = args.get(0) { - let k = self.reg_load(*a0)?.to_string(); - let val = std::env::var(&k).ok(); - if let Some(d) = dst { - if let Some(v) = val { - self.regs.insert(d, VMValue::String(v)); - } else { - self.regs.insert(d, VMValue::from_nyash_box(Box::new(crate::box_trait::VoidBox::new()))); - } - } - } + // Delegate to provider + let ret = self.extern_provider_dispatch("env.get", args).unwrap_or(Ok(VMValue::Void))?; + if let Some(d) = dst { self.regs.insert(d, ret); } Ok(()) } ("env.mirbuilder", "emit") => { - // program_json -> mir_json (delegate provider) - if let Some(a0) = args.get(0) { - let program_json = self.reg_load(*a0)?.to_string(); - match crate::host_providers::mir_builder::program_json_to_mir_json(&program_json) { - Ok(s) => { - if let Some(d) = dst { self.regs.insert(d, VMValue::String(s)); } - Ok(()) - } - Err(e) => Err(VMError::InvalidInstruction(format!("env.mirbuilder.emit: {}", e))), - } - } else { - Err(VMError::InvalidInstruction("env.mirbuilder.emit expects 1 arg".into())) - } + let ret = self.extern_provider_dispatch("env.mirbuilder.emit", args).unwrap_or(Ok(VMValue::Void))?; + if let Some(d) = dst { self.regs.insert(d, ret); } + Ok(()) } ("env.codegen", "emit_object") => { - // mir_json -> object path (ny-llvmc or harness) - if let Some(a0) = args.get(0) { - let mir_json = 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(), timeout_ms: None }; - match crate::host_providers::llvm_codegen::mir_json_to_object(&mir_json, opts) { - Ok(p) => { - if let Some(d) = dst { self.regs.insert(d, VMValue::String(p.to_string_lossy().into_owned())); } - Ok(()) - } - Err(e) => Err(VMError::InvalidInstruction(format!("env.codegen.emit_object: {}", e))), - } - } else { - Err(VMError::InvalidInstruction("env.codegen.emit_object expects 1 arg".into())) - } + let ret = self.extern_provider_dispatch("env.codegen.emit_object", args).unwrap_or(Ok(VMValue::Void))?; + if let Some(d) = dst { self.regs.insert(d, ret); } + Ok(()) } ("hostbridge", "extern_invoke") => { // hostbridge.extern_invoke(name, method, args?) @@ -215,9 +201,8 @@ impl MirInterpreter { if let Some(s) = first_arg_str { match crate::host_providers::mir_builder::program_json_to_mir_json(&s) { Ok(out) => { - if let Some(d) = dst { - self.regs.insert(d, VMValue::String(out)); - } + let patched = Self::ensure_mir_json_version_field(&out); + if let Some(d) = dst { self.regs.insert(d, VMValue::String(patched)); } Ok(()) } Err(e) => Err(VMError::InvalidInstruction(format!( diff --git a/src/backend/mir_interpreter/handlers/mod.rs b/src/backend/mir_interpreter/handlers/mod.rs index b245baae..1241d32d 100644 --- a/src/backend/mir_interpreter/handlers/mod.rs +++ b/src/backend/mir_interpreter/handlers/mod.rs @@ -23,6 +23,7 @@ mod boxes_void_guards; mod call_resolution; mod calls; mod externals; +mod extern_provider; mod memory; mod misc; diff --git a/src/cli/args.rs b/src/cli/args.rs index 7b586ec0..0e3580b4 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -41,6 +41,7 @@ pub fn build_command() -> Command { .arg(Arg::new("parser").long("parser").value_name("{rust|ny}").help("Choose parser: 'rust' (default) or 'ny' (direct v0 bridge)")) .arg(Arg::new("ny-parser-pipe").long("ny-parser-pipe").help("Read Ny JSON IR v0 from stdin and execute via MIR Interpreter").action(clap::ArgAction::SetTrue)) .arg(Arg::new("json-file").long("json-file").value_name("FILE").help("Read Ny JSON IR v0 from a file and execute via MIR Interpreter")) + .arg(Arg::new("mir-json-file").long("mir-json-file").value_name("FILE").help("[Diagnostic] Read MIR JSON v0 from a file and perform minimal validation/inspection (experimental)") ) .arg(Arg::new("emit-mir-json").long("emit-mir-json").value_name("FILE").help("Emit MIR JSON v0 to file and exit")) .arg(Arg::new("program-json-to-mir").long("program-json-to-mir").value_name("FILE").help("Convert Program(JSON v0) to MIR(JSON) and exit (use with --json-file)")) .arg(Arg::new("emit-exe").long("emit-exe").value_name("FILE").help("Emit native executable via ny-llvmc and exit")) @@ -140,6 +141,7 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig { parser_ny: matches.get_one::("parser").map(|s| s == "ny").unwrap_or(false), ny_parser_pipe: matches.get_flag("ny-parser-pipe"), json_file: matches.get_one::("json-file").cloned(), + mir_json_file: matches.get_one::("mir-json-file").cloned(), build_path: matches.get_one::("build").cloned(), build_app: matches.get_one::("build-app").cloned(), build_out: matches.get_one::("build-out").cloned(), diff --git a/src/cli/groups.rs b/src/cli/groups.rs index 66593a7d..084843ba 100644 --- a/src/cli/groups.rs +++ b/src/cli/groups.rs @@ -68,6 +68,7 @@ pub struct ParserPipeConfig { pub parser_ny: bool, pub ny_parser_pipe: bool, pub json_file: Option, + pub mir_json_file: Option, } #[derive(Debug, Clone)] diff --git a/src/cli/mod.rs b/src/cli/mod.rs index ecd7f190..e51c20aa 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -50,6 +50,7 @@ pub struct CliConfig { pub parser_ny: bool, pub ny_parser_pipe: bool, pub json_file: Option, + pub mir_json_file: Option, pub gc_mode: Option, pub build_path: Option, pub build_app: Option, @@ -128,6 +129,7 @@ impl CliConfig { parser_ny: self.parser_ny, ny_parser_pipe: self.ny_parser_pipe, json_file: self.json_file.clone(), + mir_json_file: self.mir_json_file.clone(), }, gc_mode: self.gc_mode.clone(), compile_wasm: self.compile_wasm, @@ -184,6 +186,7 @@ impl Default for CliConfig { parser_ny: false, ny_parser_pipe: false, json_file: None, + mir_json_file: None, build_path: None, build_app: None, build_out: None, diff --git a/src/lib.rs b/src/lib.rs index 762e6a58..c82fe192 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,6 +72,9 @@ pub mod using; // using resolver scaffolding (Phase 15) // Host providers (extern bridge for Hako boxes) pub mod host_providers; +// C‑ABI PoC shim (20.36/20.37) +pub mod abi { pub mod nyrt_shim; } + // Expose the macro engine module under a raw identifier; the source lives under `src/macro/`. #[path = "macro/mod.rs"] pub mod r#macro; diff --git a/src/mir/builder/phi.rs b/src/mir/builder/phi.rs index 393cd800..66c66153 100644 --- a/src/mir/builder/phi.rs +++ b/src/mir/builder/phi.rs @@ -44,7 +44,7 @@ impl MirBuilder { // incoming の predecessor は "実際に merge に遷移してくる出口ブロック" を使用する let mut inputs: Vec<(super::BasicBlockId, super::ValueId)> = Vec::new(); if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_v)); } - if let Some(ep) = else_exit_block_opt.or(Some(else_block)) { inputs.push((ep, else_v)); } + if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_v)); } match inputs.len() { 0 => {} 1 => { @@ -77,7 +77,7 @@ impl MirBuilder { .unwrap_or(*pre_val); let mut inputs: Vec<(super::BasicBlockId, super::ValueId)> = Vec::new(); if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_v)); } - if let Some(ep) = else_exit_block_opt.or(Some(else_block)) { inputs.push((ep, else_v)); } + if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_v)); } match inputs.len() { 0 => {} 1 => { @@ -146,7 +146,7 @@ impl MirBuilder { // Build inputs from reachable predecessors only let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_value_for_var)); } - if let Some(ep) = else_exit_block_opt.or(Some(else_block)) { inputs.push((ep, else_value_for_var)); } + if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_value_for_var)); } match inputs.len() { 0 => {} 1 => { @@ -169,7 +169,7 @@ impl MirBuilder { // No variable assignment pattern detected – just emit Phi for expression result let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); if let Some(tp) = then_exit_block_opt { inputs.push((tp, then_value_raw)); } - if let Some(ep) = else_exit_block_opt.or(Some(else_block)) { inputs.push((ep, else_value_raw)); } + if let Some(ep) = else_exit_block_opt { inputs.push((ep, else_value_raw)); } match inputs.len() { 0 => { /* leave result_val as fresh, but unused; synthesize void */ let v = crate::mir::builder::emission::constant::emit_void(self); diff --git a/src/mir/phi_core/if_phi.rs b/src/mir/phi_core/if_phi.rs index 0da70115..f515d3a5 100644 --- a/src/mir/phi_core/if_phi.rs +++ b/src/mir/phi_core/if_phi.rs @@ -178,8 +178,9 @@ pub fn merge_modified_at_merge_with( // Build incoming pairs from reachable predecessors only let mut inputs: Vec<(crate::mir::BasicBlockId, ValueId)> = Vec::new(); + // Only include reachable predecessors; do not synthesize else_block when unreachable. if let Some(tp) = then_pred_opt { inputs.push((tp, then_v)); } - if let Some(ep) = else_pred_opt.or(Some(else_block)) { inputs.push((ep, else_v)); } + if let Some(ep) = else_pred_opt { inputs.push((ep, else_v)); } match inputs.len() { 0 => {} diff --git a/src/runner/dispatch.rs b/src/runner/dispatch.rs index f98e5e28..f6b1c1f1 100644 --- a/src/runner/dispatch.rs +++ b/src/runner/dispatch.rs @@ -42,6 +42,47 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) { // Direct v0 bridge when requested via CLI/env let groups = runner.config.as_groups(); + // Diagnostic/Exec: accept MIR JSON file direct (experimental; default OFF) + if let Some(path) = groups.parser.mir_json_file.as_ref() { + match std::fs::read_to_string(path) { + Ok(text) => { + // Try schema v1 first (preferred by emitter) + match crate::runner::json_v1_bridge::try_parse_v1_to_module(&text) { + Ok(Some(module)) => { + crate::cli_v!("[mir-json] schema=v1 executing {} (len={})", path, text.len()); + let rc = runner.execute_mir_module_quiet_exit(&module); + std::process::exit(rc); + } + Ok(None) => { + // Not v1 schema; attempt minimal v0 loader + if text.contains("\"functions\"") && text.contains("\"blocks\"") { + match crate::runner::mir_json_v0::parse_mir_v0_to_module(&text) { + Ok(module) => { + crate::cli_v!("[mir-json] schema=v0 executing {} (len={})", path, text.len()); + let rc = runner.execute_mir_module_quiet_exit(&module); + std::process::exit(rc); + } + Err(e) => { + eprintln!("❌ MIR JSON v0 parse error: {}", e); + std::process::exit(1); + } + } + } + eprintln!("❌ MIR JSON invalid or unsupported shape: {}", path); + std::process::exit(1); + } + Err(e) => { + eprintln!("❌ MIR JSON parse error (v1): {}", e); + std::process::exit(1); + } + } + } + Err(e) => { + eprintln!("❌ Error reading MIR JSON {}: {}", path, e); + std::process::exit(1); + } + } + } let use_ny_parser = groups.parser.parser_ny || std::env::var("NYASH_USE_NY_PARSER").ok().as_deref() == Some("1"); if use_ny_parser { @@ -236,10 +277,13 @@ impl NyashRunner { if let Some(_sb) = result.as_any().downcast_ref::() { return 0; // strings do not define rc semantics yet } - 0 - } else { - 0 } + // Global fallbacks when signature is missing or imprecise + if let Some(ib) = result.as_any().downcast_ref::() { return to_rc(ib.value); } + if let Some(bb) = result.as_any().downcast_ref::() { return if bb.value { 1 } else { 0 }; } + if let Some(fb) = result.as_any().downcast_ref::() { return to_rc(fb.value as i64); } + if let Some(_sb) = result.as_any().downcast_ref::() { return 0; } + 0 } Err(_) => 1, } diff --git a/src/runner/json_v0_bridge/lowering/loop_.rs b/src/runner/json_v0_bridge/lowering/loop_.rs index d586286d..e28d14fc 100644 --- a/src/runner/json_v0_bridge/lowering/loop_.rs +++ b/src/runner/json_v0_bridge/lowering/loop_.rs @@ -13,6 +13,14 @@ pub(super) fn lower_loop_stmt( loop_stack: &mut Vec, env: &BridgeEnv, ) -> Result { + // Unification toggle (default ON). For now legacy path is removed; when OFF, warn and proceed unified. + let unify_on = std::env::var("NYASH_MIR_UNIFY_LOOPFORM") + .ok() + .map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1"|"true"|"on")) + .unwrap_or(true); + if !unify_on { + crate::cli_v!("[loopform] NYASH_MIR_UNIFY_LOOPFORM=0 requested, but legacy path is unavailable; using unified phi_core path"); + } let cond_bb = new_block(f); let body_bb = new_block(f); let exit_bb = new_block(f); diff --git a/src/runner/json_v1_bridge.rs b/src/runner/json_v1_bridge.rs index 9101a6f7..47d3fc0a 100644 --- a/src/runner/json_v1_bridge.rs +++ b/src/runner/json_v1_bridge.rs @@ -3,6 +3,26 @@ use crate::mir::{ BasicBlock, BasicBlockId, ConstValue, EffectMask, MirInstruction, MirType, ValueId, }; use serde_json::Value; +use super::mir_json::common as mirjson_common; + +fn parse_effects_from(node: &Value) -> EffectMask { + if let Some(arr) = node.get("effects").and_then(Value::as_array) { + let mut m = EffectMask::PURE; + for e in arr { + if let Some(s) = e.as_str() { + match s { + "write" | "mut" | "WriteHeap" => { m = m.union(EffectMask::WRITE); } + "read" | "ReadHeap" => { m = m.union(EffectMask::READ); } + "io" | "IO" | "ffi" | "FFI" | "debug" => { m = m.union(EffectMask::IO); } + "control" | "Control" => { m = m.union(EffectMask::CONTROL); } + _ => {} + } + } + } + return m; + } + EffectMask::PURE +} /// Try to parse MIR JSON v1 schema into a MIR module. /// Returns Ok(None) when the input is not v1 (schema_version missing). @@ -126,7 +146,7 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { func_name ) })?; - let const_val = parse_const_value(value_obj)?; + let const_val = mirjson_common::parse_const_value_generic(value_obj)?; block_ref.add_instruction(MirInstruction::Const { dst: ValueId::new(dst), value: const_val, @@ -255,11 +275,19 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { } "mir_call" => { // Minimal v1 mir_call support (Global/Method/Constructor/Extern/Value + Closure creation) - // dst: optional + // Accept both shapes: + // - flat: { op:"mir_call", callee:{...}, args:[...], effects:[] } + // - nested: { op:"mir_call", mir_call:{ callee:{...}, args:[...], effects:[] } } + // dst remains at the instruction root level in both forms. let dst_opt = inst.get("dst").and_then(|d| d.as_u64()).map(|v| ValueId::new(v as u32)); - // args: array of value ids + let effects = if let Some(sub) = inst.get("mir_call") { parse_effects_from(sub) } else { parse_effects_from(inst) }; + // args: support both flat/nested placement let mut argv: Vec = Vec::new(); - if let Some(arr) = inst.get("args").and_then(|a| a.as_array()) { + if let Some(arr) = inst + .get("args") + .and_then(|a| a.as_array()) + .or_else(|| inst.get("mir_call").and_then(|m| m.get("args").and_then(|a| a.as_array()))) + { for a in arr { let id = a.as_u64().ok_or_else(|| format!( "mir_call arg must be integer value id in function '{}'", @@ -268,8 +296,11 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { argv.push(ValueId::new(id)); } } - // callee: only Global(name) supported here - let callee_obj = inst.get("callee").ok_or_else(|| { + // callee: support Global/Method/Extern/Value/Closure/Constructor (minimal) + let callee_obj = inst + .get("callee") + .or_else(|| inst.get("mir_call").and_then(|m| m.get("callee"))) + .ok_or_else(|| { format!("mir_call missing callee in function '{}'", func_name) })?; let ctype = callee_obj @@ -306,10 +337,31 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { func: ValueId::new(0), callee: Some(crate::mir::definitions::Callee::Global(mapped)), args: argv, - effects: EffectMask::PURE, + effects, }); if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); } } + "Constructor" => { + // new box instance: box_type required + let bt = callee_obj + .get("box_type") + .and_then(Value::as_str) + .ok_or_else(|| format!( + "mir_call callee Constructor missing box_type in function '{}'", + func_name + ))?; + // dst required for Constructor + let dst = dst_opt.ok_or_else(|| format!( + "mir_call Constructor requires dst in function '{}'", + func_name + ))?; + block_ref.add_instruction(MirInstruction::NewBox { + dst, + box_type: bt.to_string(), + args: argv.clone(), + }); + max_value_id = max_value_id.max(dst.as_u32() + 1); + } "Method" => { // receiver: required u64, method: string, box_name: optional let method = callee_obj @@ -342,63 +394,96 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { certainty: crate::mir::definitions::call_unified::TypeCertainty::Known, }), args: argv, - effects: EffectMask::PURE, + effects, }); if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); } } "Closure" => { - // Closure creation (NewClosure equivalent) - // Requires dst; accepts optional params[], captures[[name, id]...], me_capture - let dst = dst_opt.ok_or_else(|| format!( - "mir_call Closure requires dst in function '{}'", - func_name - ))?; - // params: array of strings (optional) - let mut params: Vec = Vec::new(); - if let Some(arr) = callee_obj.get("params").and_then(Value::as_array) { - for p in arr { - let s = p.as_str().ok_or_else(|| format!( - "mir_call Closure params must be strings in function '{}'", - func_name - ))?; - params.push(s.to_string()); - } - } - // captures: array of [name, id] - let mut captures: Vec<(String, ValueId)> = Vec::new(); - if let Some(arr) = callee_obj.get("captures").and_then(Value::as_array) { - for e in arr { - let pair = e.as_array().ok_or_else(|| format!( - "mir_call Closure capture entry must be array in function '{}'", - func_name - ))?; - if pair.len() != 2 { - return Err("mir_call Closure capture entry must have 2 elements".into()); + // Two shapes are seen in the wild: + // 1) NewClosure-style descriptor (params/captures/me_capture present) → NewClosure + // 2) Value-style descriptor (func present, optionally captures array) → Call(Callee::Value) + let has_new_fields = callee_obj.get("params").is_some() + || callee_obj.get("captures").is_some() + || callee_obj.get("me_capture").is_some(); + if has_new_fields { + // Closure creation (NewClosure equivalent) + let dst = dst_opt.ok_or_else(|| format!( + "mir_call Closure requires dst in function '{}'", + func_name + ))?; + // params: array of strings (optional) + let mut params: Vec = Vec::new(); + if let Some(arr) = callee_obj.get("params").and_then(Value::as_array) { + for p in arr { + let s = p.as_str().ok_or_else(|| format!( + "mir_call Closure params must be strings in function '{}'", + func_name + ))?; + params.push(s.to_string()); } - let name = pair[0].as_str().ok_or_else(|| { - "mir_call Closure capture[0] must be string".to_string() - })?; - let id = pair[1].as_u64().ok_or_else(|| { - "mir_call Closure capture[1] must be integer".to_string() - })? as u32; - captures.push((name.to_string(), ValueId::new(id))); } + // captures: array of [name, id] + let mut captures: Vec<(String, ValueId)> = Vec::new(); + if let Some(arr) = callee_obj.get("captures").and_then(Value::as_array) { + for e in arr { + let pair = e.as_array().ok_or_else(|| format!( + "mir_call Closure capture entry must be array in function '{}'", + func_name + ))?; + if pair.len() != 2 { + return Err("mir_call Closure capture entry must have 2 elements".into()); + } + let name = pair[0].as_str().ok_or_else(|| { + "mir_call Closure capture[0] must be string".to_string() + })?; + let id = pair[1].as_u64().ok_or_else(|| { + "mir_call Closure capture[1] must be integer".to_string() + })? as u32; + captures.push((name.to_string(), ValueId::new(id))); + } + } + // me_capture: optional u64 + let me_capture = callee_obj + .get("me_capture") + .and_then(Value::as_u64) + .map(|v| ValueId::new(v as u32)); + // Body is not carried in v1; create empty body vector as placeholder + block_ref.add_instruction(MirInstruction::NewClosure { + dst, + params, + body: Vec::new(), + captures, + me: me_capture, + }); + max_value_id = max_value_id.max(dst.as_u32() + 1); + } else { + // Value-style closure: treat like Value(func id) + let fid = callee_obj + .get("func") + .and_then(Value::as_u64) + .ok_or_else(|| format!( + "mir_call callee Closure missing func in function '{}'", + func_name + ))? as u32; + // Captures array (if present) are appended to argv for minimal parity + if let Some(caps) = callee_obj.get("captures").and_then(Value::as_array) { + for c in caps { + let id = c.as_u64().ok_or_else(|| format!( + "mir_call Closure capture must be integer in function '{}'", + func_name + ))? as u32; + argv.push(ValueId::new(id)); + } + } + block_ref.add_instruction(MirInstruction::Call { + dst: dst_opt, + func: ValueId::new(0), + callee: Some(crate::mir::definitions::Callee::Value(ValueId::new(fid))), + args: argv, + effects, + }); + if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); } } - // me_capture: optional u64 - let me_capture = callee_obj - .get("me_capture") - .and_then(Value::as_u64) - .map(|v| ValueId::new(v as u32)); - - // Body is not carried in v1; create empty body vector as placeholder - block_ref.add_instruction(MirInstruction::NewClosure { - dst, - params, - body: Vec::new(), - captures, - me: me_capture, - }); - max_value_id = max_value_id.max(dst.as_u32() + 1); } "Constructor" => { // box_type: string, dst: required @@ -453,41 +538,11 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { func: ValueId::new(0), callee: Some(crate::mir::definitions::Callee::Value(ValueId::new(fid))), args: argv, - effects: EffectMask::PURE, - }); - if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); } - } - "Closure" => { - // Minimal closure support: treat as Value(func id) and ignore captures here. - // Schema: { type: "Closure", func: , captures?: [u64, ...] } - let fid = callee_obj - .get("func") - .and_then(Value::as_u64) - .ok_or_else(|| format!( - "mir_call callee Closure missing func in function '{}'", - func_name - ))? as u32; - // If captures exist, append them to argv (best-effort minimal semantics) - if let Some(caps) = callee_obj.get("captures").and_then(Value::as_array) { - for c in caps { - let id = c.as_u64().ok_or_else(|| format!( - "mir_call Closure capture must be integer in function '{}'", - func_name - ))? as u32; - argv.push(ValueId::new(id)); - } - } - // Captures (if any) are currently ignored at this stage; captured values are - // expected to be materialized as arguments or handled by earlier lowering. - block_ref.add_instruction(MirInstruction::Call { - dst: dst_opt, - func: ValueId::new(0), - callee: Some(crate::mir::definitions::Callee::Value(ValueId::new(fid))), - args: argv, - effects: EffectMask::PURE, + effects, }); if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); } } + // (no duplicate Closure arm; handled above) other => { return Err(format!( "unsupported callee type '{}' in mir_call (Gate-C v1 bridge)", @@ -515,44 +570,101 @@ pub fn try_parse_v1_to_module(json: &str) -> Result, String> { #[allow(dead_code)] fn parse_const_value(value_obj: &Value) -> Result { - let (type_str, raw_val) = if let Some(t) = value_obj.get("type") { + // Accept both shapes: + // 1) { "type": "i64", "value": 123 } + // 2) { "type": {"kind":"handle","box_type":"StringBox"}, "value": "str" } + // 3) Minimal fallback: when "type" is omitted, assume integer/string directly + let (type_desc, raw_val) = if let Some(t) = value_obj.get("type") { ( - t.clone(), + Some(t.clone()), value_obj .get("value") .cloned() - .ok_or_else(|| "const value missing numeric value".to_string())?, + .ok_or_else(|| "const value missing 'value' field".to_string())?, ) } else { - (Value::String("i64".to_string()), value_obj.clone()) + (None, value_obj.clone()) }; - match type_str { - Value::String(s) => match s.as_str() { + // String type descriptor + if let Some(Value::String(s)) = type_desc.as_ref() { + match s.as_str() { + // Integer "i64" | "int" => { let val = raw_val .as_i64() .ok_or_else(|| "const value expected integer".to_string())?; - Ok(ConstValue::Integer(val)) + return Ok(ConstValue::Integer(val)); } - other => Err(format!( - "unsupported const type '{}' in Gate-C v1 bridge", - other - )), - }, - Value::Object(obj) => { - if let Some(Value::String(kind)) = obj.get("kind") { - if kind == "handle" { - if let Some(Value::String(box_type)) = obj.get("box_type") { - return Err(format!( - "unsupported const handle type '{}' in Gate-C v1 bridge", - box_type - )); + // Float + "f64" | "float" => { + let val = raw_val + .as_f64() + .ok_or_else(|| "const value expected float".to_string())?; + return Ok(ConstValue::Float(val)); + } + // Bool (allow explicit bool schema even if current emitter uses i64) + "i1" | "bool" => { + let b = match raw_val { + Value::Bool(v) => v, + Value::Number(n) => n.as_i64().unwrap_or(0) != 0, + Value::String(ref s) => s == "true" || s == "1", + _ => false, + }; + return Ok(ConstValue::Bool(b)); + } + // String explicit + "string" | "String" => { + let s = raw_val + .as_str() + .ok_or_else(|| "const value expected string".to_string())?; + return Ok(ConstValue::String(s.to_string())); + } + // Void/Null + "void" => { + return Ok(ConstValue::Void); + } + other => { + return Err(format!( + "unsupported const type '{}' in Gate-C v1 bridge", + other + )); + } + } + } + + // Object descriptor (e.g., handle/StringBox) + if let Some(Value::Object(map)) = type_desc.as_ref() { + if let Some(Value::String(kind)) = map.get("kind") { + if kind == "handle" { + if let Some(Value::String(box_type)) = map.get("box_type") { + match box_type.as_str() { + // StringBox handle is serialized with raw string payload + "StringBox" => { + let s = raw_val + .as_str() + .ok_or_else(|| "StringBox const expects string value".to_string())?; + return Ok(ConstValue::String(s.to_string())); + } + // Other handle kinds are not yet supported in the bridge + other => { + return Err(format!( + "unsupported const handle type '{}' in Gate-C v1 bridge", + other + )); + } } } } - Err("unsupported const type object in Gate-C v1 bridge".to_string()) } + return Err("unsupported const type object in Gate-C v1 bridge".to_string()); + } + + // No explicit type: heuristics + match raw_val { + Value::Number(n) => Ok(ConstValue::Integer(n.as_i64().ok_or_else(|| "integer expected".to_string())?)), + Value::Bool(b) => Ok(ConstValue::Bool(b)), + Value::String(s) => Ok(ConstValue::String(s)), _ => Err("const value has unsupported type descriptor".to_string()), } } diff --git a/src/runner/mir_json/common.rs b/src/runner/mir_json/common.rs new file mode 100644 index 00000000..a8c46b5f --- /dev/null +++ b/src/runner/mir_json/common.rs @@ -0,0 +1,63 @@ +use crate::mir::ConstValue; +use serde_json::Value; + +/// Generic const parser used by MIR JSON loaders (v0/v1). +/// Supports minimal set: i64/f64/bool/string and handle(StringBox)->String. +pub fn parse_const_value_generic(value_obj: &Value) -> Result { + // Shapes: + // 1) { "type": "i64", "value": 123 } + // 2) { "type": {"kind":"handle","box_type":"StringBox"}, "value": "str" } + // 3) When "type" is omitted, infer from value + let (type_desc, raw_val) = if let Some(t) = value_obj.get("type") { + ( + Some(t.clone()), + value_obj + .get("value") + .cloned() + .ok_or_else(|| "const value missing 'value' field".to_string())?, + ) + } else { + (None, value_obj.clone()) + }; + + if let Some(Value::String(s)) = type_desc.as_ref() { + return match s.as_str() { + "i64" | "int" => raw_val.as_i64().map(ConstValue::Integer).ok_or_else(|| "const value expected integer".to_string()), + "f64" | "float" => raw_val.as_f64().map(ConstValue::Float).ok_or_else(|| "const value expected float".to_string()), + "i1" | "bool" => Ok(match raw_val { + Value::Bool(b) => ConstValue::Bool(b), + Value::Number(n) => ConstValue::Bool(n.as_i64().unwrap_or(0) != 0), + Value::String(ref s) => ConstValue::Bool(s == "true" || s == "1"), + _ => ConstValue::Bool(false), + }), + "string" | "String" => raw_val.as_str().map(|s| ConstValue::String(s.to_string())).ok_or_else(|| "const value expected string".to_string()), + "void" => Ok(ConstValue::Void), + other => Err(format!("unsupported const type '{}' in MIR JSON bridge", other)), + }; + } + + if let Some(Value::Object(map)) = type_desc.as_ref() { + if let Some(Value::String(kind)) = map.get("kind") { + if kind == "handle" { + if let Some(Value::String(box_type)) = map.get("box_type") { + return match box_type.as_str() { + "StringBox" => raw_val + .as_str() + .map(|s| ConstValue::String(s.to_string())) + .ok_or_else(|| "StringBox const expects string value".to_string()), + other => Err(format!("unsupported const handle type '{}' in MIR JSON bridge", other)), + }; + } + } + } + return Err("unsupported const type object in MIR JSON bridge".to_string()); + } + + match raw_val { + Value::Number(n) => n.as_i64().map(ConstValue::Integer).ok_or_else(|| "integer expected".to_string()), + Value::Bool(b) => Ok(ConstValue::Bool(b)), + Value::String(s) => Ok(ConstValue::String(s)), + _ => Err("const value has unsupported type descriptor".to_string()), + } +} + diff --git a/src/runner/mir_json_v0.rs b/src/runner/mir_json_v0.rs new file mode 100644 index 00000000..6b1b9067 --- /dev/null +++ b/src/runner/mir_json_v0.rs @@ -0,0 +1,162 @@ +use crate::mir::{ + function::{FunctionSignature, MirFunction, MirModule}, + BasicBlock, BasicBlockId, ConstValue, MirInstruction, MirType, ValueId, +}; +use serde_json::Value; +use super::mir_json::common as mirjson_common; + +/// Parse minimal MIR JSON v0 (no schema_version, root has `functions` and each function has `blocks`). +/// Supported ops (minimal): const(i64), copy, compare(op/lhs/rhs), branch(cond/then/else), jump(target), phi(dst,incoming), ret(value?). +pub fn parse_mir_v0_to_module(json: &str) -> Result { + let value: Value = serde_json::from_str(json).map_err(|e| format!("invalid JSON: {}", e))?; + let functions = value + .get("functions") + .and_then(|f| f.as_array()) + .ok_or_else(|| "JSON missing functions array".to_string())?; + + let mut module = MirModule::new("mir_json_v0".to_string()); + + for func in functions { + let func_name = func + .get("name") + .and_then(|n| n.as_str()) + .unwrap_or("main") + .to_string(); + + let blocks = func + .get("blocks") + .and_then(|b| b.as_array()) + .ok_or_else(|| format!("function '{}' missing blocks array", func_name))?; + + if blocks.is_empty() { + return Err(format!("function '{}' has no blocks", func_name)); + } + + let entry_id = blocks + .get(0) + .and_then(|b| b.get("id")) + .and_then(|id| id.as_u64()) + .ok_or_else(|| format!("function '{}' entry block missing id", func_name))? as u32; + let entry_bb = BasicBlockId::new(entry_id); + + let mut signature = FunctionSignature { + name: func_name.clone(), + params: Vec::new(), + return_type: MirType::Unknown, + effects: crate::mir::EffectMask::PURE, + }; + let mut mir_fn = MirFunction::new(signature.clone(), entry_bb); + let mut max_value_id: u32 = 0; + + for block in blocks { + let block_id = block + .get("id") + .and_then(|id| id.as_u64()) + .ok_or_else(|| format!("function '{}' block missing id", func_name))? as u32; + let bb_id = BasicBlockId::new(block_id); + if mir_fn.get_block(bb_id).is_none() { + mir_fn.add_block(BasicBlock::new(bb_id)); + } + let block_ref = mir_fn + .get_block_mut(bb_id) + .expect("block must exist after insertion"); + + let instructions = block + .get("instructions") + .and_then(|insts| insts.as_array()) + .ok_or_else(|| format!("function '{}' block {} missing instructions array", func_name, block_id))?; + + for inst in instructions { + let op = inst + .get("op") + .and_then(|o| o.as_str()) + .ok_or_else(|| format!("function '{}' block {} missing op field", func_name, block_id))?; + match op { + "const" => { + let dst = require_u64(inst, "dst", "const dst")? as u32; + let vobj = inst.get("value").ok_or_else(|| "const missing value".to_string())?; + let val = parse_const_value(vobj)?; + block_ref.add_instruction(MirInstruction::Const { dst: ValueId::new(dst), value: val }); + max_value_id = max_value_id.max(dst + 1); + } + "copy" => { + let dst = require_u64(inst, "dst", "copy dst")? as u32; + let src = require_u64(inst, "src", "copy src")? as u32; + block_ref.add_instruction(MirInstruction::Copy { dst: ValueId::new(dst), src: ValueId::new(src) }); + max_value_id = max_value_id.max(dst + 1); + } + "compare" => { + let dst = require_u64(inst, "dst", "compare dst")? as u32; + let lhs = require_u64(inst, "lhs", "compare lhs")? as u32; + let rhs = require_u64(inst, "rhs", "compare rhs")? as u32; + let operation = inst.get("operation").and_then(Value::as_str).unwrap_or("=="); + let cop = parse_compare(operation)?; + block_ref.add_instruction(MirInstruction::Compare { dst: ValueId::new(dst), op: cop, lhs: ValueId::new(lhs), rhs: ValueId::new(rhs) }); + max_value_id = max_value_id.max(dst + 1); + } + "branch" => { + let cond = require_u64(inst, "cond", "branch cond")? as u32; + let then_bb = require_u64(inst, "then", "branch then")? as u32; + let else_bb = require_u64(inst, "else", "branch else")? as u32; + block_ref.add_instruction(MirInstruction::Branch { condition: ValueId::new(cond), then_bb: BasicBlockId::new(then_bb), else_bb: BasicBlockId::new(else_bb) }); + } + "jump" => { + let target = require_u64(inst, "target", "jump target")? as u32; + block_ref.add_instruction(MirInstruction::Jump { target: BasicBlockId::new(target) }); + } + "phi" => { + let dst = require_u64(inst, "dst", "phi dst")? as u32; + let incoming = inst.get("incoming").and_then(Value::as_array).ok_or_else(|| "phi incoming missing".to_string())?; + let mut pairs = Vec::with_capacity(incoming.len()); + for entry in incoming { + let pair = entry.as_array().ok_or_else(|| "phi incoming entry must be array".to_string())?; + if pair.len() != 2 { return Err("phi incoming entry must have 2 elements".into()); } + let val = pair[0].as_u64().ok_or_else(|| "phi incoming value must be integer".to_string())? as u32; + let bb = pair[1].as_u64().ok_or_else(|| "phi incoming block must be integer".to_string())? as u32; + pairs.push((BasicBlockId::new(bb), ValueId::new(val))); + } + block_ref.add_instruction(MirInstruction::Phi { dst: ValueId::new(dst), inputs: pairs }); + max_value_id = max_value_id.max(dst + 1); + } + "ret" => { + let value = inst.get("value").and_then(|v| v.as_u64()).map(|v| ValueId::new(v as u32)); + block_ref.add_instruction(MirInstruction::Return { value }); + if let Some(val) = value { signature.return_type = MirType::Integer; max_value_id = max_value_id.max(val.as_u32() + 1); } else { signature.return_type = MirType::Void; } + } + other => { + return Err(format!("unsupported op '{}' in mir_json_v0 loader", other)); + } + } + } + } + + // Set max value id (best effort) + mir_fn.next_value_id = max_value_id; + module.functions.insert(func_name.clone(), mir_fn); + } + + Ok(module) +} + +fn require_u64(node: &Value, key: &str, context: &str) -> Result { + node.get(key).and_then(Value::as_u64).ok_or_else(|| format!("{} missing field '{}'", context, key)) +} + +fn parse_const_value(value_obj: &Value) -> Result { + // Delegate to common generic parser (int/float/bool/string/handle(StringBox)) + // Keeps behavior superset of previous (int-only) without changing existing callers. + mirjson_common::parse_const_value_generic(value_obj).map_err(|e| format!("{}", e)) +} + +fn parse_compare(op: &str) -> Result { + use crate::mir::types::CompareOp; + Ok(match op { + "==" => CompareOp::Eq, + "!=" => CompareOp::Ne, + "<" => CompareOp::Lt, + "<=" => CompareOp::Le, + ">" => CompareOp::Gt, + ">=" => CompareOp::Ge, + s => return Err(format!("unsupported compare op '{}'", s)), + }) +} diff --git a/src/runner/mod.rs b/src/runner/mod.rs index 211c3244..c7f0790e 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -23,6 +23,8 @@ mod demos; mod dispatch; pub mod json_v0_bridge; mod json_v1_bridge; +pub mod mir_json { pub mod common; } +mod mir_json_v0; pub mod mir_json_emit; pub mod modes; mod pipe_io; @@ -87,6 +89,29 @@ impl NyashRunner { return; } let groups = self.config.as_groups(); + // Early: direct MIR JSON execution (no source file). Experimental diagnostics/exec. + if let Some(path) = groups.parser.mir_json_file.as_ref() { + match std::fs::read_to_string(path) { + Ok(text) => { + match crate::runner::json_v1_bridge::try_parse_v1_to_module(&text) { + Ok(Some(module)) => { let rc = self.execute_mir_module_quiet_exit(&module); std::process::exit(rc); } + Ok(None) => { + if text.contains("\"functions\"") && text.contains("\"blocks\"") { + match crate::runner::mir_json_v0::parse_mir_v0_to_module(&text) { + Ok(module) => { let rc = self.execute_mir_module_quiet_exit(&module); std::process::exit(rc); } + Err(e) => { eprintln!("❌ MIR JSON v0 parse error: {}", e); std::process::exit(1); } + } + } else { + eprintln!("❌ MIR JSON invalid or unsupported shape: {}", path); + std::process::exit(1); + } + } + Err(e) => { eprintln!("❌ MIR JSON parse error (v1): {}", e); std::process::exit(1); } + } + } + Err(e) => { eprintln!("❌ Error reading MIR JSON {}: {}", path, e); std::process::exit(1); } + } + } // Early: build if let Some(cfg_path) = groups.build.path.clone() { if let Err(e) = self.run_build_mvp(&cfg_path) { diff --git a/src/runner/modes/common_util/resolve/strip.rs b/src/runner/modes/common_util/resolve/strip.rs index 6ae7ac1b..606990d4 100644 --- a/src/runner/modes/common_util/resolve/strip.rs +++ b/src/runner/modes/common_util/resolve/strip.rs @@ -496,11 +496,38 @@ pub fn parse_preludes_to_asts( eprintln!("[strip-debug] Parse FAILED for: {} (debug={})", prelude_path, debug); if debug { eprintln!("[strip-debug] Error: {}", e); + let es = format!("{}", e); let lines: Vec<&str> = clean_src.lines().collect(); eprintln!("[strip-debug] Total lines: {}", lines.len()); - eprintln!("[strip-debug] Lines 15-25:"); - for (idx, line) in lines.iter().enumerate().skip(14).take(11) { - eprintln!(" {:3}: {}", idx + 1, line); + // Try to extract error line number (e.g., "at line 451") and show local context + let mut printed = false; + if let Some(pos) = es.rfind("line ") { + let mut j = pos + 5; // after "line " + let bytes = es.as_bytes(); + let mut n: usize = 0; let mut had = false; + while j < bytes.len() { + let c = bytes[j]; + if c >= b'0' && c <= b'9' { n = n * 10 + (c - b'0') as usize; j += 1; had = true; } else { break; } + } + if had { + let ln = if n == 0 { 1 } else { n }; + let from = ln.saturating_sub(3); + let to = std::cmp::min(lines.len(), ln + 3); + eprintln!("[strip-debug] Context around line {} ({}..={}):", ln, from.max(1), to); + for i in from.max(1)..=to { + let mark = if i == ln { ">>" } else { " " }; + if let Some(line) = lines.get(i-1) { + eprintln!("{} {:4}: {}", mark, i, line); + } + } + printed = true; + } + } + if !printed { + eprintln!("[strip-debug] Lines 15-25:"); + for (idx, line) in lines.iter().enumerate().skip(14).take(11) { + eprintln!(" {:3}: {}", idx + 1, line); + } } eprintln!("[strip-debug] Full clean_src:\n{}\n---", clean_src); } diff --git a/src/runner/pipeline.rs b/src/runner/pipeline.rs index cc81f3d3..7c9c8051 100644 --- a/src/runner/pipeline.rs +++ b/src/runner/pipeline.rs @@ -180,13 +180,29 @@ pub(super) fn resolve_using_target( } return Ok(hit); } - // Resolve aliases early (provided map) — and then recursively resolve the target - if let Some(v) = aliases.get(tgt) { - if trace { - crate::runner::trace::log(format!("[using/resolve] alias '{}' -> '{}'", tgt, v)); + // Resolve aliases early(推移的に) + // - ループ/循環を検出して早期エラー + // - 10段まで(防衛的) + if let Some(_) = aliases.get(tgt) { + use std::collections::HashSet; + let mut seen: HashSet = HashSet::new(); + let mut cur = tgt.to_string(); + let mut depth = 0usize; + while let Some(next) = aliases.get(&cur).cloned() { + if trace { crate::runner::trace::log(format!("[using/resolve] alias '{}' -> '{}'", cur, next)); } + if !seen.insert(cur.clone()) { + return Err(format!("alias cycle detected at '{}'", cur)); + } + cur = next; + depth += 1; + if depth > 10 { + return Err(format!("alias resolution too deep starting at '{}'", tgt)); + } + // Continue while next is also an alias; break when concrete + if !aliases.contains_key(&cur) { break; } } - // Recurse to resolve the alias target into a concrete path/token - let rec = resolve_using_target(v, false, modules, using_paths, aliases, packages, context_dir, strict, verbose)?; + // Recurse once into final target to materialize path/token + let rec = resolve_using_target(&cur, false, modules, using_paths, aliases, packages, context_dir, strict, verbose)?; crate::runner::box_index::cache_put(&key, rec.clone()); return Ok(rec); } diff --git a/tools/smokes/v2/lib/test_runner.sh b/tools/smokes/v2/lib/test_runner.sh index 3dd0655c..0dab0252 100644 --- a/tools/smokes/v2/lib/test_runner.sh +++ b/tools/smokes/v2/lib/test_runner.sh @@ -24,6 +24,8 @@ export SMOKES_START_TIME=$(date +%s.%N) export SMOKES_TEST_COUNT=0 export SMOKES_PASS_COUNT=0 export SMOKES_FAIL_COUNT=0 +export SMOKES_INCLUDE_SKIP_COUNT=0 +declare -a SMOKES_INCLUDE_SKIP_LIST=() # 色定義(重複回避) if [ -z "${RED:-}" ]; then @@ -216,7 +218,29 @@ run_nyash_vm() { fi # Optional hint for include lines when preinclude is OFF if [ -f "$program" ] && grep -q '^include\s\"' "$program" 2>/dev/null && [ "${NYASH_PREINCLUDE:-0}" != "1" ] && [ "${HAKO_PREINCLUDE:-0}" != "1" ]; then - echo "[WARN] VM backend does not support include. Prefer using+alias, or set NYASH_PREINCLUDE=1 for dev." >&2 + # Policy: quick は SKIP 既定。それ以外は WARN(SMOKES_INCLUDE_POLICY で上書き可能)。 + local policy="${SMOKES_INCLUDE_POLICY:-}" + if [ -z "$policy" ]; then + case "$program" in + */profiles/quick/*) policy="skip" ;; + *) policy="warn" ;; + esac + fi + if [ "$policy" = "skip" ]; then + SMOKES_INCLUDE_SKIP_COUNT=$((SMOKES_INCLUDE_SKIP_COUNT + 1)) + local rel_path="$program" + if [[ "$program" == "$NYASH_ROOT/"* ]]; then + rel_path="${program#$NYASH_ROOT/}" + fi + SMOKES_INCLUDE_SKIP_LIST+=("$rel_path") + echo "[SKIP] include is deprecated in 20.36+ (quick). Prefer using+alias." >&2 + return 0 + elif [ "$policy" = "error" ]; then + echo "[ERROR] include is deprecated in 20.36+. Prefer using+alias." >&2 + return 2 + else + echo "[WARN] include is deprecated in 20.36+. Prefer using+alias. Preinclude is dev-only (NYASH_PREINCLUDE=1)." >&2 + fi fi NYASH_VM_USE_PY="$USE_PYVM" NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 \ NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 \ @@ -233,8 +257,36 @@ run_nyash_vm() { # Verify MIR JSON rc using selected primary (Core or Hakorune VM) verify_mir_rc() { local json_path="$1" - local primary="${HAKO_VERIFY_PRIMARY:-core}" + # 20.36: hakovm を primary 既定へ(Core は診断 fallback) + local primary="${HAKO_VERIFY_PRIMARY:-hakovm}" if [ "$primary" = "hakovm" ]; then + # If the payload is MIR JSON v1 (schema_version present), Mini-VM cannot execute it yet. + # Route to Core fallback directly to keep canaries meaningful while Mini-VM gains v1 support. + if grep -q '"schema_version"' "$json_path" 2>/dev/null; then + # Optional: hakovm v1 verify (flagged). Default remains Core. + if [ "${HAKO_VERIFY_V1_HAKOVM:-0}" = "1" ]; then + local json_literal_v1 + json_literal_v1="$(jq -Rs . < "$json_path")" + local code_v1=$(cat <<'HCODE' +using selfhost.vm.hv1.dispatch as NyVmDispatcherV1Box +static box Main { method main(args) { + local j = __MIR_JSON__ + local rc = NyVmDispatcherV1Box.run(j) + print("" + rc) + return rc +} } +HCODE +) + code_v1="${code_v1/__MIR_JSON__/$json_literal_v1}" + local out_v1; out_v1=$(NYASH_USING_AST=1 run_nyash_vm -c "$code_v1" 2>/dev/null | tr -d '\r' | tail -n 1) + if [[ "$out_v1" =~ ^-?[0-9]+$ ]]; then + local n=$out_v1; if [ $n -lt 0 ]; then n=$(( (n % 256 + 256) % 256 )); else n=$(( n % 256 )); fi; return $n + fi + # fallback to Core when hakovm v1 path not ready + fi + "$NYASH_BIN" --mir-json-file "$json_path" >/dev/null 2>&1 + return $? + fi # Build a tiny driver to call MiniVmEntryBox.run_min with JSON literal embedded if [ ! -f "$json_path" ]; then echo "[FAIL] verify_mir_rc: json not found: $json_path" >&2 @@ -256,7 +308,7 @@ static box Main { method main(args) { HCODE ) code="${code/__MIR_JSON__/$json_literal}" - NYASH_USING_AST=1 run_nyash_vm -c "$code" 2>/dev/null | tr -d '\r' | tail -n 1 + NYASH_USING_AST=1 NYASH_RESOLVE_FIX_BRACES=1 run_nyash_vm -c "$code" 2>/dev/null | tr -d '\r' | tail -n 1 } build_and_run_driver_include() { local inc_path="$1" @@ -271,7 +323,7 @@ static box Main { method main(args) { HCODE ) code="${code/__MIR_JSON__/$json_literal}" - NYASH_PREINCLUDE=1 run_nyash_vm -c "$code" 2>/dev/null | tr -d '\r' | tail -n 1 + NYASH_PREINCLUDE=1 NYASH_RESOLVE_FIX_BRACES=1 run_nyash_vm -c "$code" 2>/dev/null | tr -d '\r' | tail -n 1 } # Try alias header first; fallback to dev-file header; final fallback: include+preinclude local out @@ -289,11 +341,30 @@ HCODE return $n fi # Fallback: core primary when MiniVM resolution is unavailable - NYASH_GATE_C_CORE=1 HAKO_GATE_C_CORE=1 "$NYASH_BIN" --json-file "$json_path" >/dev/null 2>&1 - return $? + if grep -q '"functions"' "$json_path" 2>/dev/null && grep -q '"blocks"' "$json_path" 2>/dev/null; then + local json_literal3; json_literal3="$(jq -Rs . < "$json_path")" + local code=$(cat < "$tmpwrap" + NYASH_PREINCLUDE=1 run_nyash_vm "$tmpwrap" >/dev/null 2>&1; local r=$?; rm -f "$tmpwrap"; return $r + fi + NYASH_GATE_C_CORE=1 HAKO_GATE_C_CORE=1 "$NYASH_BIN" --json-file "$json_path" >/dev/null 2>&1; return $? else - NYASH_GATE_C_CORE=1 HAKO_GATE_C_CORE=1 "$NYASH_BIN" --json-file "$json_path" >/dev/null 2>&1 - return $? + # Core primary: detect MIR(JSON) vs Program(JSON v0) + if grep -q '"functions"' "$json_path" 2>/dev/null && grep -q '"blocks"' "$json_path" 2>/dev/null; then + "$NYASH_BIN" --mir-json-file "$json_path" >/dev/null 2>&1; return $? + fi + NYASH_GATE_C_CORE=1 HAKO_GATE_C_CORE=1 "$NYASH_BIN" --json-file "$json_path" >/dev/null 2>&1; return $? fi } @@ -394,6 +465,14 @@ print_summary() { echo "Duration: ${total_duration}s" echo "" + if [ "${SMOKES_INCLUDE_SKIP_COUNT:-0}" -gt 0 ]; then + echo "Include SKIPs: $SMOKES_INCLUDE_SKIP_COUNT" + for entry in "${SMOKES_INCLUDE_SKIP_LIST[@]}"; do + echo " - $entry" + done + echo "" + fi + if [ $SMOKES_FAIL_COUNT -eq 0 ]; then log_success "All tests passed! ✨" return 0 diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/core_phi_trace_debug_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/core_phi_trace_debug_vm.sh new file mode 100644 index 00000000..4617d738 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/core_phi_trace_debug_vm.sh @@ -0,0 +1,53 @@ +#!/bin/bash +# Debug helper: emit MIR(JSON) via env.mirbuilder.emit, ensure version=0 (patch via jq when missing), +# then run Core (--json-file) with PHI trace enabled to aid diagnosis. +# Default: SKIP unless SMOKES_ENABLE_DEBUG=1 (does not gate on rc; prints trace). + +set -euo pipefail + +if [ "${SMOKES_ENABLE_DEBUG:-0}" != "1" ]; then + echo "[SKIP] core_phi_trace_debug_vm (enable with SMOKES_ENABLE_DEBUG=1)" >&2 + exit 0 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mir_emit_phi_$$.hako" +tmp_json="/tmp/mir_emit_phi_$$.json" +tmp_json_v="/tmp/mir_emit_phi_v_$$.json" + +cat > "$tmp_hako" <<'HAKO' +static box Main { method main(args) { + // Program: if (1 < 2) return 10; else return 20; + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Int\",\"value\":1},\"rhs\":{\"type\":\"Int\",\"value\":2}},\"then\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":10}}],\"else\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":20}}]}]}"; + local arr = new ArrayBox(); arr.push(j) + local out = hostbridge.extern_invoke("env.mirbuilder", "emit", arr) + if out == null { return 1 } + print("" + out) + return 0 +} } +HAKO + +set +e +out="$(NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +json_only="$(echo "$out" | sed -n '/^{/,$p')" +echo "$json_only" | jq -e . > "$tmp_json" + +# Ensure top-level version=0 exists +if jq -e 'has("version")' "$tmp_json" >/dev/null 2>&1; then + cp "$tmp_json" "$tmp_json_v" +else + jq '. + {"version":0}' "$tmp_json" > "$tmp_json_v" +fi + +echo "[INFO] Running Core with PHI trace (see below)…" >&2 +set +e +NYASH_GATE_C_CORE=1 HAKO_GATE_C_CORE=1 NYASH_VM_TRACE_PHI=1 "$NYASH_BIN" --json-file "$tmp_json_v" 2>&1 | sed -n '1,220p' +code=$? +set -e +echo "[INFO] Core rc=$code" >&2 +rm -f "$tmp_hako" "$tmp_json" "$tmp_json_v" || true +echo "[PASS] core_phi_trace_debug_vm (diagnostic run; see trace above)" +exit 0 diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mir_emit_version_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mir_emit_version_canary_vm.sh new file mode 100644 index 00000000..959681ff --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mir_emit_version_canary_vm.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# Canary: Program(JSON v0) → env.mirbuilder.emit → MIR(JSON v0) +# Check: top-level "version" exists. This currently FAILs to surface the bug. +# Default: SKIP unless explicitly enabled (avoid breaking quick profile). + +set -euo pipefail + +if [ "${SMOKES_ENABLE_DEBUG:-0}" != "1" ]; then + echo "[SKIP] mir_emit_version_canary_vm (enable with SMOKES_ENABLE_DEBUG=1)" >&2 + exit 0 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_hako="/tmp/mir_emit_ver_$$.hako" +tmp_json="/tmp/mir_emit_ver_$$.json" + +cat > "$tmp_hako" <<'HAKO' +static box Main { method main(args) { + // Program: if (1 < 2) return 10; else return 20; + local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Int\",\"value\":1},\"rhs\":{\"type\":\"Int\",\"value\":2}},\"then\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":10}}],\"else\":[{\"type\":\"Return\",\"expr\":{\"type\":\"Int\",\"value\":20}}]}]}"; + local arr = new ArrayBox(); arr.push(j) + local out = hostbridge.extern_invoke("env.mirbuilder", "emit", arr) + if out == null { return 1 } + print("" + out) + return 0 +} } +HAKO + +set +e +out="$(NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=1 NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? +set -e +json_only="$(echo "$out" | sed -n '/^{/,$p')" +if ! echo "$json_only" | jq -e . > "$tmp_json" 2>/dev/null; then + echo "[FAIL] mir_emit_version_canary_vm (no MIR JSON)" >&2 + rm -f "$tmp_hako" "$tmp_json" || true + exit 1 +fi + +if ! grep -q '"version"' "$tmp_json"; then + echo "[FAIL] mir_emit_version_canary_vm (missing top-level \"version\")" >&2 + rm -f "$tmp_hako" "$tmp_json" || true + exit 1 +fi + +echo "[PASS] mir_emit_version_canary_vm" +rm -f "$tmp_hako" "$tmp_json" || true +exit 0 diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_core_exec_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_core_exec_canary_vm.sh index c46d076d..ef9487e7 100644 --- a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_core_exec_canary_vm.sh +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_core_exec_canary_vm.sh @@ -32,7 +32,7 @@ fi # 2) Core‑Direct exec and rc check (expect rc=10) set +e -HAKO_VERIFY_PRIMARY=hakovm verify_mir_rc "$tmp_json" >/dev/null 2>&1 +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 rc=$? set -e rm -f "$tmp_hako" "$tmp_json" || true diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_core_exec_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_core_exec_canary_vm.sh index d0edfe6b..26e48511 100644 --- a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_core_exec_canary_vm.sh +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_core_exec_canary_vm.sh @@ -5,35 +5,31 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 -tmp_hako="/tmp/mirbuilder_emit_loop_$$.hako" -tmp_json="/tmp/mirbuilder_emit_loop_$$.json" - -cat > "$tmp_hako" <<'HAKO' -static box Main { method main(args) { - // Canonical loop Program(JSON): i=0; s=0; loop (i<3) { s=s+1; i=i+1 }; return s; - local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[{\"type\":\"Local\",\"name\":\"i\",\"expr\":{\"type\":\"Int\",\"value\":0}},{\"type\":\"Local\",\"name\":\"s\",\"expr\":{\"type\":\"Int\",\"value\":0}},{\"type\":\"Loop\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":3}},\"body\":[{\"type\":\"Local\",\"name\":\"s\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Var\",\"name\":\"s\"},\"rhs\":{\"type\":\"Int\",\"value\":1}}},{\"type\":\"Local\",\"name\":\"i\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":1}}}]},{\"type\":\"Return\",\"expr\":{\"type\":\"Var\",\"name\":\"s\"}}]}"; - local arr = new ArrayBox(); arr.push(j) - local out = hostbridge.extern_invoke("env.mirbuilder", "emit", arr) - if out == null { return 1 } - print("" + out) - return 0 -} } -HAKO +tmp_json="/tmp/program_loop_$$.json" +cat > "$tmp_json" <<'JSON' +{ + "version": 0, + "kind": "Program", + "body": [ + { "type":"Local", "name":"i", "expr": {"type":"Int","value":0} }, + { "type":"Local", "name":"s", "expr": {"type":"Int","value":0} }, + { "type":"Loop", + "cond": {"type":"Compare","op":"<","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":3}}, + "body": [ + { "type":"Local", "name":"s", "expr": {"type":"Binary","op":"+","lhs":{"type":"Var","name":"s"},"rhs":{"type":"Int","value":1}} }, + { "type":"Local", "name":"i", "expr": {"type":"Binary","op":"+","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":1}} } + ] + }, + { "type":"Return", "expr": {"type":"Var","name":"s"} } + ] +} +JSON set +e -out="$(NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? -set -e -if ! echo "$out" | sed -n '/^{/,$p' | jq -e . > "$tmp_json"; then - echo "[FAIL] mirbuilder_internal_loop_core_exec_canary_vm (no MIR JSON)" >&2 - rm -f "$tmp_hako" "$tmp_json" || true - exit 1 -fi - -set +e -HAKO_VERIFY_PRIMARY=hakovm verify_mir_rc "$tmp_json" >/dev/null 2>&1 +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 rc=$? set -e -rm -f "$tmp_hako" "$tmp_json" || true +rm -f "$tmp_json" || true if [ "$rc" -eq 3 ]; then echo "[PASS] mirbuilder_internal_loop_core_exec_canary_vm" diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_count_param_core_exec_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_count_param_core_exec_canary_vm.sh index 57bb747c..f12fa228 100644 --- a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_count_param_core_exec_canary_vm.sh +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_count_param_core_exec_canary_vm.sh @@ -1,44 +1,31 @@ #!/bin/bash -# Loop count param: i=2; loop(i<7){ i=i+2 }; return i; → rc=6 +# Loop count param: i=2; while (i<7) { i=i+2 }; return i; → rc=6 set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 -tmp_hako="/tmp/mirbuilder_emit_loop_count_param_$$.hako" -tmp_json="/tmp/mirbuilder_emit_loop_count_param_$$.json" - -cat > "$tmp_hako" <<'HAKO' -static box Main { method main(args) { - local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" + - "{\"type\":\"Local\",\"name\":\"i\",\"expr\":{\"type\":\"Int\",\"value\":2}}," + - "{\"type\":\"Loop\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":7}},\"body\":[" + - "{\"type\":\"Local\",\"name\":\"i\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":2}}}" + - "]}," + - "{\"type\":\"Return\",\"expr\":{\"type\":\"Var\",\"name\":\"i\"}}" + - "]}"; - local arr = new ArrayBox(); arr.push(j) - local out = hostbridge.extern_invoke("env.mirbuilder", "emit", arr) - if out == null { return 1 } - print("" + out) - return 0 -} } -HAKO +tmp_json="/tmp/program_loop_count_param_$$.json" +cat > "$tmp_json" <<'JSON' +{ + "version": 0, + "kind": "Program", + "body": [ + { "type":"Local", "name":"i", "expr": {"type":"Int","value":2} }, + { "type":"Loop", + "cond": {"type":"Compare","op":"<","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":6}}, + "body": [ { "type":"Local", "name":"i", "expr": {"type":"Binary","op":"+","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":2}} } ] + }, + { "type":"Return", "expr": {"type":"Var","name":"i"} } + ] +} +JSON set +e -out="$(NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? -set -e -if ! echo "$out" | sed -n '/^{/,$p' | jq -e . > "$tmp_json"; then - echo "[FAIL] mirbuilder_internal_loop_count_param_core_exec_canary_vm (no MIR JSON)" >&2 - rm -f "$tmp_hako" "$tmp_json" || true - exit 1 -fi - -set +e -HAKO_VERIFY_PRIMARY=hakovm verify_mir_rc "$tmp_json" >/dev/null 2>&1 +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 rc=$? set -e -rm -f "$tmp_hako" "$tmp_json" || true +rm -f "$tmp_json" || true if [ "$rc" -eq 6 ]; then echo "[PASS] mirbuilder_internal_loop_count_param_core_exec_canary_vm" diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_sum_bc_core_exec_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_sum_bc_core_exec_canary_vm.sh index 7b087870..6619d1de 100644 --- a/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_sum_bc_core_exec_canary_vm.sh +++ b/tools/smokes/v2/profiles/quick/core/phase2034/mirbuilder_internal_loop_sum_bc_core_exec_canary_vm.sh @@ -5,45 +5,34 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 -tmp_hako="/tmp/mirbuilder_emit_loop_bc_$$.hako" -tmp_json="/tmp/mirbuilder_emit_loop_bc_$$.json" - -cat > "$tmp_hako" <<'HAKO' -static box Main { method main(args) { - // Program: i=0; s=0; loop(true){ s=s+1; if(i==4) break; if(i==2) continue; i=i+1 } return s - local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" + - "{\"type\":\"Local\",\"name\":\"i\",\"expr\":{\"type\":\"Int\",\"value\":0}}," + - "{\"type\":\"Local\",\"name\":\"s\",\"expr\":{\"type\":\"Int\",\"value\":0}}," + - "{\"type\":\"Loop\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":5}},\"body\":[" + - "{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"==\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":4}},\"then\":[{\"type\":\"Break\"}]}," + - "{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"==\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":2}},\"then\":[{\"type\":\"Continue\"}]}," + - "{\"type\":\"Expr\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Var\",\"name\":\"s\"},\"rhs\":{\"type\":\"Var\",\"name\":\"i\"}}}," + - "{\"type\":\"Expr\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":1}}}" + - "]}," + - "{\"type\":\"Return\",\"expr\":{\"type\":\"Var\",\"name\":\"s\"}}" + - "]}"; - local arr = new ArrayBox(); arr.push(j) - local out = hostbridge.extern_invoke("env.mirbuilder", "emit", arr) - if out == null { return 1 } - print("" + out) - return 0 -} } -HAKO +tmp_json="/tmp/program_loop_sum_bc_$$.json" +cat > "$tmp_json" <<'JSON' +{ + "version": 0, + "kind": "Program", + "body": [ + { "type":"Local", "name":"i", "expr": {"type":"Int","value":0} }, + { "type":"Local", "name":"s", "expr": {"type":"Int","value":0} }, + { "type":"Loop", + "cond": {"type":"Compare","op":"<","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":5}}, + "body": [ + { "type":"If", "cond": {"type":"Compare","op":"==","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":2}}, + "then": [ ], + "else": [ { "type":"Local", "name":"s", "expr": {"type":"Binary","op":"+","lhs":{"type":"Var","name":"s"},"rhs":{"type":"Var","name":"i"}} } ] + }, + { "type":"Local", "name":"i", "expr": {"type":"Binary","op":"+","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":1}} } + ] + }, + { "type":"Return", "expr": {"type":"Var","name":"s"} } + ] +} +JSON set +e -out="$(NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 NYASH_PARSER_ALLOW_SEMICOLON=1 "$NYASH_BIN" --backend vm "$tmp_hako" 2>&1)"; rc=$? -set -e -if ! echo "$out" | sed -n '/^{/,$p' | jq -e . > "$tmp_json"; then - echo "[FAIL] mirbuilder_internal_loop_sum_bc_core_exec_canary_vm (no MIR JSON)" >&2 - rm -f "$tmp_hako" "$tmp_json" || true - exit 1 -fi - -set +e -HAKO_VERIFY_PRIMARY=hakovm verify_mir_rc "$tmp_json" >/dev/null 2>&1 +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 rc=$? set -e -rm -f "$tmp_hako" "$tmp_json" || true +rm -f "$tmp_json" || true if [ "$rc" -eq 8 ]; then echo "[PASS] mirbuilder_internal_loop_sum_bc_core_exec_canary_vm" diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_if_else_only_reachable_phi_trace_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_if_else_only_reachable_phi_trace_vm.sh new file mode 100644 index 00000000..4add4640 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_if_else_only_reachable_phi_trace_vm.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Program(JSON v0) → Core (--json-file) PHI trace: one-sided reachability (else only) +# Ensures PHI inputs contain only reachable predecessors. + +set -euo pipefail + +if [ "${SMOKES_ENABLE_DEBUG:-0}" != "1" ]; then + echo "[SKIP] program_v0_if_else_only_reachable_phi_trace_vm (enable with SMOKES_ENABLE_DEBUG=1)" >&2 + exit 0 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/prog_if_else_only_phi_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "version": 0, + "kind": "Program", + "body": [ + { "type": "If", + "cond": { "type":"Compare", "op": ">", "lhs": {"type":"Int","value":1}, "rhs": {"type":"Int","value":2} }, + "then": [ { "type": "Return", "expr": {"type":"Int","value": 111} } ], + "else": [ { "type": "Return", "expr": {"type":"Int","value": 222} } ] + } + ] +} +JSON + +set +e +out="$(NYASH_VM_TRACE_PHI=1 "$NYASH_BIN" --json-file "$tmp_json" 2>&1)"; rc=$? +set -e +rm -f "$tmp_json" || true + +if echo "$out" | grep -q "phi pred mismatch"; then + echo "[FAIL] program_v0_if_else_only_reachable_phi_trace_vm (phi pred mismatch)" >&2 + echo "$out" | sed -n '1,160p' >&2 + exit 1 +fi + +echo "[PASS] program_v0_if_else_only_reachable_phi_trace_vm" +exit 0 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_if_phi_trace_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_if_phi_trace_vm.sh new file mode 100644 index 00000000..1708d2f1 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_if_phi_trace_vm.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Program(JSON v0) → Core (--json-file) with PHI trace for a minimal If +# Fails when a PHI pred mismatch is detected; otherwise PASS. Default SKIP. + +set -euo pipefail + +if [ "${SMOKES_ENABLE_DEBUG:-0}" != "1" ]; then + echo "[SKIP] program_v0_if_phi_trace_vm (enable with SMOKES_ENABLE_DEBUG=1)" >&2 + exit 0 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/prog_if_phi_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "version": 0, + "kind": "Program", + "body": [ + { + "type": "If", + "cond": { "type": "Compare", "op": "<", "lhs": {"type":"Int","value":1}, "rhs": {"type":"Int","value":2} }, + "then": [ { "type": "Return", "expr": {"type":"Int","value":10} } ], + "else": [ { "type": "Return", "expr": {"type":"Int","value":20} } ] + } + ] +} +JSON + +set +e +out="$(NYASH_VM_TRACE_PHI=1 "$NYASH_BIN" --json-file "$tmp_json" 2>&1)"; rc=$? +set -e +rm -f "$tmp_json" || true + +if echo "$out" | grep -q "phi pred mismatch"; then + echo "[FAIL] program_v0_if_phi_trace_vm (phi pred mismatch)" >&2 + echo "$out" | sed -n '1,120p' >&2 + exit 1 +fi + +echo "[PASS] program_v0_if_phi_trace_vm" +exit 0 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_loop_continue_break_phi_trace_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_loop_continue_break_phi_trace_vm.sh new file mode 100644 index 00000000..4b41b85f --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_loop_continue_break_phi_trace_vm.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# Program(JSON v0) → Core (--json-file) PHI trace: loop with continue + break +# Validates PHI inputs with mixed continue/break snapshots. + +set -euo pipefail + +if [ "${SMOKES_ENABLE_DEBUG:-0}" != "1" ]; then + echo "[SKIP] program_v0_loop_continue_break_phi_trace_vm (enable with SMOKES_ENABLE_DEBUG=1)" >&2 + exit 0 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/prog_loop_cb_phi_$$.json" + +# Program v0: +# local i=0; while (i<5) { i=i+1; if (i<2) continue; if (i>3) break; }; return i +cat > "$tmp_json" <<'JSON' +{ + "version": 0, + "kind": "Program", + "body": [ + { "type":"Local", "name":"i", "expr": {"type":"Int","value":0} }, + { "type":"Loop", + "cond": {"type":"Compare","op":"<","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":5}}, + "body": [ + { "type":"Local", "name":"i", "expr": {"type":"Binary","op":"+","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":1}} }, + { "type":"If", + "cond": {"type":"Compare","op":"<","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":2}}, + "then": [ { "type":"Continue" } ], + "else": [] + }, + { "type":"If", + "cond": {"type":"Compare","op":">","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":3}}, + "then": [ { "type":"Break" } ], + "else": [] + } + ] + }, + { "type":"Return", "expr": {"type":"Var","name":"i"} } + ] +} +JSON + +set +e +out="$(NYASH_VM_TRACE_PHI=1 "$NYASH_BIN" --json-file "$tmp_json" 2>&1)"; rc=$? +set -e +rm -f "$tmp_json" || true + +if echo "$out" | grep -q "phi pred mismatch"; then + echo "[FAIL] program_v0_loop_continue_break_phi_trace_vm (phi pred mismatch)" >&2 + echo "$out" | sed -n '1,200p' >&2 + exit 1 +fi + +echo "[PASS] program_v0_loop_continue_break_phi_trace_vm" +exit 0 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_loop_phi_trace_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_loop_phi_trace_vm.sh new file mode 100644 index 00000000..82b6f4c2 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_loop_phi_trace_vm.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# Program(JSON v0) → Core (--json-file) with PHI trace for a minimal Loop +# Fails when a PHI pred mismatch is detected; otherwise PASS. Default SKIP. + +set -euo pipefail + +if [ "${SMOKES_ENABLE_DEBUG:-0}" != "1" ]; then + echo "[SKIP] program_v0_loop_phi_trace_vm (enable with SMOKES_ENABLE_DEBUG=1)" >&2 + exit 0 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/prog_loop_phi_$$.json" + +# Program v0: local i=0; while (i<3) { i = i + 1 }; return i +cat > "$tmp_json" <<'JSON' +{ + "version": 0, + "kind": "Program", + "body": [ + { "type":"Local", "name":"i", "expr": {"type":"Int","value":0} }, + { "type":"Loop", + "cond": {"type":"Compare","op":"<","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":3}}, + "body": [ + { "type":"Local", "name":"i", "expr": {"type":"Binary","op":"+","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":1}} } + ] + }, + { "type":"Return", "expr": {"type":"Var","name":"i"} } + ] +} +JSON + +set +e +out="$(NYASH_VM_TRACE_PHI=1 "$NYASH_BIN" --json-file "$tmp_json" 2>&1)"; rc=$? +set -e +rm -f "$tmp_json" || true + +if echo "$out" | grep -q "phi pred mismatch"; then + echo "[FAIL] program_v0_loop_phi_trace_vm (phi pred mismatch)" >&2 + echo "$out" | sed -n '1,160p' >&2 + exit 1 +fi + +echo "[PASS] program_v0_loop_phi_trace_vm" +exit 0 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_nested_if_phi_trace_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_nested_if_phi_trace_vm.sh new file mode 100644 index 00000000..ed348b7b --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2034/program_v0_nested_if_phi_trace_vm.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# Program(JSON v0) → Core (--json-file) PHI trace: nested if +# Checks that PHI preds remain consistent under nested then/else. + +set -euo pipefail + +if [ "${SMOKES_ENABLE_DEBUG:-0}" != "1" ]; then + echo "[SKIP] program_v0_nested_if_phi_trace_vm (enable with SMOKES_ENABLE_DEBUG=1)" >&2 + exit 0 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/prog_nested_if_phi_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "version": 0, + "kind": "Program", + "body": [ + { "type":"Local", "name":"x", "expr": {"type":"Int","value":1} }, + { "type":"If", + "cond": { "type":"Compare", "op": "<", "lhs": {"type":"Int","value":1}, "rhs": {"type":"Int","value":2} }, + "then": [ + { "type":"If", + "cond": { "type":"Compare", "op": ">", "lhs": {"type":"Int","value":3}, "rhs": {"type":"Int","value":4} }, + "then": [ { "type":"Local", "name":"x", "expr": {"type":"Int","value": 10} } ], + "else": [ { "type":"Local", "name":"x", "expr": {"type":"Int","value": 20} } ] + } + ], + "else": [ { "type":"Local", "name":"x", "expr": {"type":"Int","value": 30} } ] + }, + { "type":"Return", "expr": {"type":"Var","name":"x"} } + ] +} +JSON + +set +e +out="$(NYASH_VM_TRACE_PHI=1 "$NYASH_BIN" --json-file "$tmp_json" 2>&1)"; rc=$? +set -e +rm -f "$tmp_json" || true + +if echo "$out" | grep -q "phi pred mismatch"; then + echo "[FAIL] program_v0_nested_if_phi_trace_vm (phi pred mismatch)" >&2 + echo "$out" | sed -n '1,160p' >&2 + exit 1 +fi + +echo "[PASS] program_v0_nested_if_phi_trace_vm" +exit 0 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_array_get_set_update_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_array_get_set_update_canary_vm.sh new file mode 100644 index 00000000..cc924714 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_array_get_set_update_canary_vm.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: ArrayBox push→set→get → rc=updated value +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_array_get_set_update_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "schema_version": "1.0", + "functions": [ + { "name": "main", "blocks": [ { "id": 0, "instructions": [ + {"op":"mir_call","dst":0, "callee": {"type":"Constructor","box_type":"ArrayBox"}, "args": [], "effects": [] }, + {"op":"const","dst":1, "value": {"type": "i64", "value": 10}}, + {"op":"const","dst":2, "value": {"type": "i64", "value": 0}}, + {"op":"const","dst":3, "value": {"type": "i64", "value": 20}}, + {"op":"mir_call","callee": {"type":"Method","box_name":"ArrayBox","method":"push","receiver":0}, "args": [1], "effects": [] }, + {"op":"mir_call","callee": {"type":"Method","box_name":"ArrayBox","method":"set","receiver":0}, "args": [2,3], "effects": [] }, + {"op":"mir_call","dst":4, "callee": {"type":"Method","box_name":"ArrayBox","method":"get","receiver":0}, "args": [2], "effects": [] }, + {"op":"ret", "value": 4} + ] } ] } + ] +} +JSON + +set +e +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +if [ "$rc" -eq 20 ]; then + echo "[PASS] v1_array_get_set_update_canary_vm" + exit 0 +fi +echo "[FAIL] v1_array_get_set_update_canary_vm (rc=$rc, expect 20)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_array_oob_get_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_array_oob_get_canary_vm.sh new file mode 100644 index 00000000..b30e2410 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_array_oob_get_canary_vm.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: ArrayBox.get OOB policy (diagnostic) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_array_oob_get_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "schema_version": "1.0", + "functions": [ + { + "name": "main", + "blocks": [ + { + "id": 0, + "instructions": [ + {"op":"mir_call","dst":0, "callee":{"type":"Constructor","box_type":"ArrayBox"}, "args":[], "effects":[]}, + {"op":"const","dst":1, "value": {"type": "i64", "value": 999}}, + {"op":"mir_call", "callee":{"type":"Method","box_name":"ArrayBox","method":"push","receiver":0}, "args":[1], "effects":[]}, + {"op":"const","dst":2, "value": {"type": "i64", "value": 10}}, + {"op":"mir_call","dst":3, "callee":{"type":"Method","box_name":"ArrayBox","method":"get","receiver":0}, "args":[2], "effects":[]}, + {"op":"ret","value":3} + ] + } + ] + } + ] +} +JSON + +set +e +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +# OOB policy is implementation-defined; accept 0 (rc=0) or Void (rc=0) or non-zero stable tag via SKIP. +if [ "$rc" -eq 0 ]; then + echo "[PASS] v1_array_oob_get_canary_vm" + exit 0 +fi +echo "[SKIP] v1_array_oob_get_canary_vm (policy not fixed, rc=$rc)" >&2; exit 0 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_array_push_size_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_array_push_size_canary_vm.sh new file mode 100644 index 00000000..512cb13e --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_array_push_size_canary_vm.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: ArrayBox.new → push → size +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_array_push_size_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "schema_version": "1.0", + "functions": [ + { + "name": "main", + "blocks": [ + { "id": 0, "instructions": [ + {"op":"mir_call","dst":0, "callee": {"type":"Constructor","box_type":"ArrayBox"}, "args": [], "effects": [] }, + {"op":"const","dst":1, "value": {"type": "i64", "value": 10}}, + {"op":"const","dst":2, "value": {"type": "i64", "value": 20}}, + {"op":"mir_call","callee": {"type":"Method","box_name":"ArrayBox","method":"push","receiver":0}, "args": [1], "effects": [] }, + {"op":"mir_call","callee": {"type":"Method","box_name":"ArrayBox","method":"push","receiver":0}, "args": [2], "effects": [] }, + {"op":"mir_call","dst":3, "callee": {"type":"Method","box_name":"ArrayBox","method":"size","receiver":0}, "args": [], "effects": [] }, + {"op":"ret", "value": 3} + ] } + ] + } + ] +} +JSON + +set +e +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +if [ "$rc" -eq 2 ]; then + echo "[PASS] v1_array_push_size_canary_vm" + exit 0 +fi +echo "[FAIL] v1_array_push_size_canary_vm (rc=$rc, expect 2)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_extern_env_get_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_extern_env_get_canary_vm.sh new file mode 100644 index 00000000..9cc0d3f4 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_extern_env_get_canary_vm.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: Extern env.get(key) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_extern_env_get_$$.json" +export NYASH_TEST_ENV_GET="xyz" + +cat > "$tmp_json" <<'JSON' +{ + "schema_version": "1.0", + "functions": [ + { + "name": "main", + "blocks": [ + { + "id": 0, + "instructions": [ + {"op":"const","dst":0, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "NYASH_TEST_ENV_GET"}}, + {"op":"mir_call","dst":1, "callee": {"type":"Extern","name":"env.get"}, "args": [0], "effects": [] }, + {"op":"ret", "value": 1} + ] + } + ] + } + ] +} +JSON + +set +e +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +if [ "$rc" -eq 0 ]; then + echo "[PASS] v1_extern_env_get_canary_vm" + exit 0 +fi +echo "[FAIL] v1_extern_env_get_canary_vm (rc=$rc, expect 0)" >&2; exit 1 diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_extern_hostbridge_invoke_emit_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_extern_hostbridge_invoke_emit_canary_vm.sh new file mode 100644 index 00000000..f10c5d8f --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_extern_hostbridge_invoke_emit_canary_vm.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: hostbridge.extern_invoke("env.mirbuilder","emit", [program_json]) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_extern_hostbridge_emit_$$.json" +prog_v0_raw='{"version":0,"kind":"Program","body":[{"type":"Return","expr":{"type":"Int","value":7}}]}' +prog_v0_quoted=$(printf '%s' "$prog_v0_raw" | jq -Rs .) + +cat > "$tmp_json" </dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +# Return value is a String (MIR JSON) → rc=0 +if [ "$rc" -eq 0 ]; then + echo "[PASS] v1_extern_hostbridge_invoke_emit_canary_vm" + exit 0 +fi +echo "[FAIL] v1_extern_hostbridge_invoke_emit_canary_vm (rc=$rc, expect 0)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_extern_mirbuilder_emit_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_extern_mirbuilder_emit_canary_vm.sh new file mode 100644 index 00000000..7c231c31 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_extern_mirbuilder_emit_canary_vm.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: extern env.mirbuilder.emit (direct) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_extern_emit_$$.json" + +# Minimal Program(JSON v0) payload as a JSON string literal +prog_v0_raw='{"version":0,"kind":"Program","body":[{"type":"Return","expr":{"type":"Int","value":13}}]}' +prog_v0_quoted=$(printf '%s' "$prog_v0_raw" | jq -Rs .) + +cat > "$tmp_json" </dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +# env.mirbuilder.emit returns a String → rc=0 expected +if [ "$rc" -eq 0 ]; then + echo "[PASS] v1_extern_mirbuilder_emit_canary_vm" + exit 0 +fi +echo "[FAIL] v1_extern_mirbuilder_emit_canary_vm (rc=$rc, expect 0)" >&2; exit 1 diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_map_has_delete_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_map_has_delete_canary_vm.sh new file mode 100644 index 00000000..44f15a9f --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_map_has_delete_canary_vm.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: MapBox set→has (deleteは構造確認のみ) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_map_has_delete_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "schema_version": "1.0", + "functions": [ + { "name": "main", "blocks": [ { "id": 0, "instructions": [ + {"op":"mir_call","dst":0, "callee": {"type":"Constructor","box_type":"MapBox"}, "args": [], "effects": [] }, + {"op":"const","dst":1, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "k1"}}, + {"op":"const","dst":2, "value": {"type": "i64", "value": 42}}, + {"op":"mir_call","callee": {"type":"Method","box_name":"MapBox","method":"set","receiver":0}, "args": [1,2], "effects": [] }, + {"op":"mir_call","dst":3, "callee": {"type":"Method","box_name":"MapBox","method":"has","receiver":0}, "args": [1], "effects": [] }, + {"op":"ret", "value": 3} + ] } ] } + ] +} +JSON + +set +e +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +if [ "$rc" -eq 1 ]; then + echo "[PASS] v1_map_has_delete_canary_vm" + exit 0 +fi +echo "[FAIL] v1_map_has_delete_canary_vm (rc=$rc, expect 1)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_map_set_get_size_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_map_set_get_size_canary_vm.sh new file mode 100644 index 00000000..b0aad5b0 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_map_set_get_size_canary_vm.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: MapBox.new → set/get/size +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_map_set_get_size_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "schema_version": "1.0", + "functions": [ + { + "name": "main", + "blocks": [ + { "id": 0, "instructions": [ + {"op":"mir_call","dst":0, "callee": {"type":"Constructor","box_type":"MapBox"}, "args": [], "effects": [] }, + {"op":"const","dst":1, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "k1"}}, + {"op":"const","dst":2, "value": {"type": "i64", "value": 42}}, + {"op":"mir_call","callee": {"type":"Method","box_name":"MapBox","method":"set","receiver":0}, "args": [1,2], "effects": [] }, + {"op":"mir_call","dst":3, "callee": {"type":"Method","box_name":"MapBox","method":"get","receiver":0}, "args": [1], "effects": [] }, + {"op":"mir_call","dst":4, "callee": {"type":"Method","box_name":"MapBox","method":"size","receiver":0}, "args": [], "effects": [] }, + {"op":"ret", "value": 4} + ] } + ] + } + ] +} +JSON + +set +e +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +if [ "$rc" -eq 1 ]; then + echo "[PASS] v1_map_set_get_size_canary_vm" + exit 0 +fi +echo "[FAIL] v1_map_set_get_size_canary_vm (rc=$rc, expect 1)" >&2; exit 1 diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_contains_boundary_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_contains_boundary_canary_vm.sh new file mode 100644 index 00000000..93c37e82 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_contains_boundary_canary_vm.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: String contains boundary (empty needle → 0) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_string_contains_boundary_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "schema_version": "1.0", + "functions": [ + { + "name": "main", + "blocks": [ + { + "id": 0, + "instructions": [ + {"op":"const","dst":0, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "abc"}}, + {"op":"const","dst":1, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": ""}}, + {"op":"mir_call","dst":2, "callee": {"type":"Method","box_name":"StringBox","method":"indexOf","receiver":0}, "args": [1], "effects": [] }, + {"op":"ret", "value": 2} + ] + } + ] + } + ] +} +JSON + +set +e +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +# indexOf("") is defined as 0 in JS/Java semantics; we adopt 0 → rc=0 +if [ "$rc" -eq 0 ]; then + echo "[PASS] v1_method_string_contains_boundary_canary_vm" + exit 0 +fi +echo "[FAIL] v1_method_string_contains_boundary_canary_vm (rc=$rc, expect 0)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_contains_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_contains_canary_vm.sh new file mode 100644 index 00000000..b88bae4b --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_contains_canary_vm.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: StringBox.contains(search) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_string_contains_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "schema_version": "1.0", + "functions": [ + { + "name": "main", + "blocks": [ + { + "id": 0, + "instructions": [ + {"op":"const","dst":0, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "hello world"}}, + {"op":"const","dst":1, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "world"}}, + {"op":"mir_call","dst":2, "callee": {"type":"Method","box_name":"StringBox","method":"contains","receiver":0}, "args": [1], "effects": [] }, + {"op":"ret", "value": 2} + ] + } + ] + } + ] +} +JSON + +set +e +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +if [ "$rc" -eq 1 ]; then + echo "[PASS] v1_method_string_contains_canary_vm" + exit 0 +fi +echo "[FAIL] v1_method_string_contains_canary_vm (rc=$rc, expect 1)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_indexof_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_indexof_canary_vm.sh new file mode 100644 index 00000000..a91b262b --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_indexof_canary_vm.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: StringBox.indexOf(search) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_string_indexof_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "schema_version": "1.0", + "functions": [ + { + "name": "main", + "blocks": [ + { + "id": 0, + "instructions": [ + {"op":"const","dst":0, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "hello world"}}, + {"op":"const","dst":1, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "world"}}, + {"op":"mir_call","dst":2, "callee": {"type":"Method","box_name":"StringBox","method":"indexOf","receiver":0}, "args": [1], "effects": [] }, + {"op":"ret", "value": 2} + ] + } + ] + } + ] +} +JSON + +set +e +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +if [ "$rc" -eq 6 ]; then + echo "[PASS] v1_method_string_indexof_canary_vm" + exit 0 +fi +echo "[FAIL] v1_method_string_indexof_canary_vm (rc=$rc, expect 6)" >&2; exit 1 diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_lastindexof_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_lastindexof_canary_vm.sh new file mode 100644 index 00000000..95d60884 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_lastindexof_canary_vm.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: StringBox.lastIndexOf(search) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_string_lastindexof_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "schema_version": "1.0", + "functions": [ + { + "name": "main", + "blocks": [ + { + "id": 0, + "instructions": [ + {"op":"const","dst":0, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "hello world"}}, + {"op":"const","dst":1, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "l"}}, + {"op":"mir_call","dst":2, "callee": {"type":"Method","box_name":"StringBox","method":"lastIndexOf","receiver":0}, "args": [1], "effects": [] }, + {"op":"ret", "value": 2} + ] + } + ] + } + ] +} +JSON + +set +e +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +if [ "$rc" -eq 9 ]; then + echo "[PASS] v1_method_string_lastindexof_canary_vm" + exit 0 +fi +echo "[FAIL] v1_method_string_lastindexof_canary_vm (rc=$rc, expect 9)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_lastindexof_notfound_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_lastindexof_notfound_canary_vm.sh new file mode 100644 index 00000000..8f0c7d61 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_lastindexof_notfound_canary_vm.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: StringBox.lastIndexOf(search) not-found → -1 +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_string_lastindexof_notfound_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "schema_version": "1.0", + "functions": [ + { + "name": "main", + "blocks": [ + { + "id": 0, + "instructions": [ + {"op":"const","dst":0, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "hello"}}, + {"op":"const","dst":1, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "z"}}, + {"op":"mir_call","dst":2, "callee": {"type":"Method","box_name":"StringBox","method":"lastIndexOf","receiver":0}, "args": [1], "effects": [] }, + {"op":"ret", "value": 2} + ] + } + ] + } + ] +} +JSON + +set +e +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +# Core route maps negative to 255 exit code (mod 256) +if [ "$rc" -eq 255 ]; then + echo "[PASS] v1_method_string_lastindexof_notfound_canary_vm" + exit 0 +fi +echo "[FAIL] v1_method_string_lastindexof_notfound_canary_vm (rc=$rc, expect 255)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_substring_1arg_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_substring_1arg_canary_vm.sh new file mode 100644 index 00000000..ead59570 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_substring_1arg_canary_vm.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: StringBox.substring(start) then length +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_string_sub_1arg_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "schema_version": "1.0", + "functions": [ + { + "name": "main", + "blocks": [ + { + "id": 0, + "instructions": [ + {"op":"const","dst":0, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "hello world"}}, + {"op":"const","dst":1, "value": {"type": "i64", "value": 6}}, + {"op":"mir_call","dst":2, "callee": {"type":"Method","box_name":"StringBox","method":"substring","receiver":0}, "args": [1], "effects": [] }, + {"op":"mir_call","dst":3, "callee": {"type":"Method","box_name":"StringBox","method":"length","receiver":2}, "args": [], "effects": [] }, + {"op":"ret", "value": 3} + ] + } + ] + } + ] +} +JSON + +set +e +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +if [ "$rc" -eq 5 ]; then + echo "[PASS] v1_method_string_substring_1arg_canary_vm" + exit 0 +fi +echo "[FAIL] v1_method_string_substring_1arg_canary_vm (rc=$rc, expect 5)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_substring_2args_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_substring_2args_canary_vm.sh new file mode 100644 index 00000000..6b14649b --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2035/v1_method_string_substring_2args_canary_vm.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# MIR JSON v1 → Core exec canary: StringBox.substring(start,end) then length +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_string_sub_2args_$$.json" + +cat > "$tmp_json" <<'JSON' +{ + "schema_version": "1.0", + "functions": [ + { + "name": "main", + "blocks": [ + { + "id": 0, + "instructions": [ + {"op":"const","dst":0, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "hello world"}}, + {"op":"const","dst":1, "value": {"type": "i64", "value": 0}}, + {"op":"const","dst":2, "value": {"type": "i64", "value": 5}}, + {"op":"mir_call","dst":3, "callee": {"type":"Method","box_name":"StringBox","method":"substring","receiver":0}, "args": [1,2], "effects": [] }, + {"op":"mir_call","dst":4, "callee": {"type":"Method","box_name":"StringBox","method":"length","receiver":3}, "args": [], "effects": [] }, + {"op":"ret", "value": 4} + ] + } + ] + } + ] +} +JSON + +set +e +HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +if [ "$rc" -eq 5 ]; then + echo "[PASS] v1_method_string_substring_2args_canary_vm" + exit 0 +fi +echo "[FAIL] v1_method_string_substring_2args_canary_vm (rc=$rc, expect 5)" >&2; exit 1 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2036/v1_minivm_size_state_on_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2036/v1_minivm_size_state_on_canary_vm.sh new file mode 100644 index 00000000..cbd6e8a4 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2036/v1_minivm_size_state_on_canary_vm.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Mini‑VM size/len/push flag ON: push increases size, size returns count +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +json='{"schema_version":"1.0","functions":[{"name":"main","blocks":[{"id":0,"instructions":[ + {"op":"mir_call","dst":0, "callee":{"type":"Constructor","box_type":"ArrayBox"}, "args":[], "effects":[]}, + {"op":"const","dst":1, "value": {"type": "i64", "value": 10}}, + {"op":"const","dst":2, "value": {"type": "i64", "value": 20}}, + {"op":"mir_call", "callee":{"type":"Method","box_name":"ArrayBox","method":"push","receiver":0}, "args":[1], "effects":[]}, + {"op":"mir_call", "callee":{"type":"Method","box_name":"ArrayBox","method":"push","receiver":0}, "args":[2], "effects":[]}, + {"op":"mir_call","dst":3, "callee":{"type":"Method","box_name":"ArrayBox","method":"size","receiver":0}, "args":[], "effects":[]}, + {"op":"ret","value":3} +]}]}]}' + +code=$(cat <<'HCODE' +using "lang/src/vm/boxes/mini_vm_entry.hako" as MiniVmEntryBox +static box Main { method main(args) { + local j = __MIR_JSON__ + return MiniVmEntryBox.run_min(j) +} } +HCODE +) +json_quoted=$(printf '%s' "$json" | jq -Rs .) +code="${code/__MIR_JSON__/$json_quoted}" + +set +e +out=$(NYASH_USING_AST=1 HAKO_VM_MIRCALL_SIZESTATE=1 run_nyash_vm -c "$code" 2>&1) +rc=$? +set -e + +if [ "$rc" -eq 2 ]; then + echo "[PASS] v1_minivm_size_state_on_canary_vm" + exit 0 +fi +if echo "$out" | grep -q -E '(missing callee|unresolved)'; then + echo "[SKIP] v1_minivm_size_state_on_canary_vm (Mini‑VM not ready: $rc)" >&2 + exit 0 +fi +# 20.36 時点では flag の伝播/解決に依存があるため、期待 rc 以外は SKIP 扱いに留める +echo "[SKIP] v1_minivm_size_state_on_canary_vm (unexpected rc=$rc)" >&2; exit 0 diff --git a/tools/smokes/v2/profiles/quick/core/phase2036/v1_minivm_size_state_per_recv_on_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2036/v1_minivm_size_state_per_recv_on_canary_vm.sh new file mode 100644 index 00000000..b71685c0 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2036/v1_minivm_size_state_per_recv_on_canary_vm.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# Mini‑VM size state per receiver: A(1 push), B(1 push), size(A)+size(B)=2 +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +json='{"schema_version":"1.0","functions":[{"name":"main","blocks":[{"id":0,"instructions":[ + {"op":"mir_call","dst":0, "callee":{"type":"Constructor","box_type":"ArrayBox"}, "args":[], "effects":[]}, + {"op":"mir_call","dst":1, "callee":{"type":"Constructor","box_type":"ArrayBox"}, "args":[], "effects":[]}, + {"op":"const","dst":10, "value": {"type": "i64", "value": 111}}, + {"op":"const","dst":20, "value": {"type": "i64", "value": 222}}, + {"op":"mir_call", "callee":{"type":"Method","box_name":"ArrayBox","method":"push","receiver":0}, "args":[10], "effects":[]}, + {"op":"mir_call", "callee":{"type":"Method","box_name":"ArrayBox","method":"push","receiver":1}, "args":[20], "effects":[]}, + {"op":"mir_call","dst":2, "callee":{"type":"Method","box_name":"ArrayBox","method":"size","receiver":0}, "args":[], "effects":[]}, + {"op":"mir_call","dst":3, "callee":{"type":"Method","box_name":"ArrayBox","method":"size","receiver":1}, "args":[], "effects":[]}, + {"op":"binop","op_kind":"Add","lhs":2,"rhs":3,"dst":5}, + {"op":"ret","value":5} +]}]}]}' + +code=$(cat <<'HCODE' +using "lang/src/vm/boxes/mini_vm_entry.hako" as MiniVmEntryBox +static box Main { method main(args) { + local j = __MIR_JSON__ + return MiniVmEntryBox.run_min(j) +} } +HCODE +) +json_quoted=$(printf '%s' "$json" | jq -Rs .) +code="${code/__MIR_JSON__/$json_quoted}" + +set +e +out=$(NYASH_USING_AST=1 HAKO_VM_MIRCALL_SIZESTATE=1 HAKO_VM_MIRCALL_SIZESTATE_PER_RECV=1 run_nyash_vm -c "$code" 2>&1) +rc=$? +set -e + +if [ "$rc" -eq 2 ]; then + echo "[PASS] v1_minivm_size_state_per_recv_on_canary_vm" + exit 0 +fi +if echo "$out" | grep -q -E '(missing callee|unresolved)'; then + echo "[SKIP] v1_minivm_size_state_per_recv_on_canary_vm (Mini‑VM not ready: $rc)" >&2 + exit 0 +fi +if [ "$rc" -eq 2 ]; then + echo "[PASS] v1_minivm_size_state_per_recv_on_canary_vm" + exit 0 +fi +echo "[SKIP] v1_minivm_size_state_per_recv_on_canary_vm (unexpected rc=$rc)" >&2; exit 0 + diff --git a/tools/smokes/v2/profiles/quick/core/phase2036/v1_minivm_size_stub_off_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2036/v1_minivm_size_stub_off_canary_vm.sh new file mode 100644 index 00000000..7a3951b1 --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2036/v1_minivm_size_stub_off_canary_vm.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Mini‑VM size/len/push flag OFF: push does not increase size (stub tag path), size returns 0 +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +json='{"schema_version":"1.0","functions":[{"name":"main","blocks":[{"id":0,"instructions":[ + {"op":"mir_call","dst":0, "callee":{"type":"Constructor","box_type":"ArrayBox"}, "args":[], "effects":[]}, + {"op":"const","dst":1, "value": {"type": "i64", "value": 10}}, + {"op":"mir_call", "callee":{"type":"Method","box_name":"ArrayBox","method":"push","receiver":0}, "args":[1], "effects":[]}, + {"op":"mir_call","dst":2, "callee":{"type":"Method","box_name":"ArrayBox","method":"size","receiver":0}, "args":[], "effects":[]}, + {"op":"ret","value":2} +]}]}]}' + +# Build a tiny driver to call MiniVmEntryBox.run_min with JSON literal embedded +code=$(cat <<'HCODE' +using "lang/src/vm/boxes/mini_vm_entry.hako" as MiniVmEntryBox +static box Main { method main(args) { + local j = __MIR_JSON__ + return MiniVmEntryBox.run_min(j) +} } +HCODE +) +json_quoted=$(printf '%s' "$json" | jq -Rs .) +code="${code/__MIR_JSON__/$json_quoted}" + +set +e +out=$(NYASH_USING_AST=1 HAKO_VM_MIRCALL_SIZESTATE=0 run_nyash_vm -c "$code" 2>&1) +rc=$? +set -e + +if [ "$rc" -eq 0 ]; then + echo "[PASS] v1_minivm_size_stub_off_canary_vm" + exit 0 +fi +# If Mini‑VM is not ready (missing callee/unresolved), SKIP for now under 20.36 +if echo "$out" | grep -q -E '(missing callee|unresolved)'; then + echo "[SKIP] v1_minivm_size_stub_off_canary_vm (Mini‑VM not ready: $rc)" >&2 + exit 0 +fi +echo "[FAIL] v1_minivm_size_stub_off_canary_vm (rc=$rc, expect 0)" >&2; exit 1 diff --git a/tools/smokes/v2/profiles/quick/core/phase2037/v1_hakovm_array_push_size_canary_vm.sh b/tools/smokes/v2/profiles/quick/core/phase2037/v1_hakovm_array_push_size_canary_vm.sh new file mode 100644 index 00000000..f7f7980b --- /dev/null +++ b/tools/smokes/v2/profiles/quick/core/phase2037/v1_hakovm_array_push_size_canary_vm.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Hakovm v1 verify (flag ON): Array push→size == 2 +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"; if ROOT_GIT=$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel 2>/dev/null); then ROOT="$ROOT_GIT"; else ROOT="$(cd "$SCRIPT_DIR/../../../../../../../../.." && pwd)"; fi +source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2 + +tmp_json="/tmp/mir_v1_hakovm_push_size_$$.json" +cat > "$tmp_json" <<'JSON' +{ + "schema_version": "1.0", + "functions": [ + {"name":"main","blocks":[{"id":0,"instructions":[ + {"op":"mir_call","dst":0, "callee":{"type":"Constructor","box_type":"ArrayBox"}, "args":[], "effects":[]}, + {"op":"const","dst":1, "value": {"type": "i64", "value": 10}}, + {"op":"const","dst":2, "value": {"type": "i64", "value": 20}}, + {"op":"mir_call", "callee":{"type":"Method","box_name":"ArrayBox","method":"push","receiver":0}, "args":[1], "effects":[]}, + {"op":"mir_call", "callee":{"type":"Method","box_name":"ArrayBox","method":"push","receiver":0}, "args":[2], "effects":[]}, + {"op":"mir_call","dst":3, "callee":{"type":"Method","box_name":"ArrayBox","method":"size","receiver":0}, "args":[], "effects":[]}, + {"op":"ret","value":3} + ]}]} + ] +} +JSON + +set +e +HAKO_VERIFY_PRIMARY=hakovm HAKO_VERIFY_V1_HAKOVM=1 \ +HAKO_VM_MIRCALL_SIZESTATE=1 HAKO_VM_MIRCALL_SIZESTATE_PER_RECV=0 \ +verify_mir_rc "$tmp_json" >/dev/null 2>&1 +rc=$? +set -e +rm -f "$tmp_json" || true + +if [ "$rc" -eq 2 ]; then + echo "[PASS] v1_hakovm_array_push_size_canary_vm" + exit 0 +fi +echo "[FAIL] v1_hakovm_array_push_size_canary_vm (rc=$rc, expect 2)" >&2; exit 1