Files
hakorune/lang/src/vm
nyash-codex 484bea946d VM: PHI strict default-ON + loop-header PHI fix; add VM step budget; StringBox.lastIndexOf; docs + strict smoke
- PHI strict: default ON, disable via HAKO_VM_PHI_STRICT=0 (alias NYASH_VM_PHI_STRICT=0)
- LoopForm: insert initial header PHI (preheader input) and rebind vars before condition; seal updates PHI inputs; avoid duplicate PHIs by replace
- MIR interpreter: add HAKO_VM_MAX_STEPS (alias NYASH_VM_MAX_STEPS) fail-fast budget to prevent infinite loops
- StringBox.lastIndexOf implemented (rfind, returns -1 when not found) in VM handlers
- Smokes: add strict/core/vm_phi_strict_smoke.sh (opt-in); quick remains green 120/120
- Docs: lang/src/vm/README.md and CURRENT_TASK.md updated with PHI strict policy and step budget
2025-11-02 11:01:03 +09:00
..

VM Layout (Current → Target)

Current

  • lang/src/vm/hakorune-vm/ — Hakorune VM (nyvm) implementation
  • lang/src/vm/boxes/ — Shared helpers (op_handlers, scanners, compare, etc.)
  • MiniVM minimal executor lives as boxes (e.g., boxes/mir_vm_min.hako)

Target (post20.12b, gradual)

  • engines/hakorune/ — mainline nyvm engine
  • engines/mini/ — MiniVM engine (educational/minimal)
  • boxes/ — shared helpers
  • core/ — centralized execution core (value/state/reader/dispatcher + ops)

Policy

  • Engines orchestrate execution and may depend on boxes and shared/*.
  • Boxes are pure helpers (no engine loop, no I/O, no plugin/ABI).
  • Parser/Resolver/Emitter must not be imported from engines/boxes.
  • Core provides engineagnostic execution primitives and should not import enginespecific modules. During migration, temporary adapters may exist.

Static Box MethodsSingleton / self

  • 規約: 静的Boxのメソッドは「selfSingletonを先頭引数」に持つ。
    • 例: LLVMPhiInstructionBox.lower_phi(self, dst, incoming_list)
  • 互換: HAKO_BRIDGE_INJECT_SINGLETON=1alias: NYASH_BRIDGE_INJECT_SINGLETON)で旧スタイル PhiInst.lower_phi(dst, incoming) に Singleton を注入して実行。
  • FailFast: 期待 arity と不一致は静かなフォールバックをせず、安定メッセージで失敗する。 詳細: docs/development/architecture/llvm/static_box_singleton.md

BridgeB (Ny/Core 直行)

  • Wrapper 経路では include "lang/src/vm/core/dispatcher.hako" で Core Dispatcher を取り込み、 NyVmDispatcher.run(json) を直接呼び出す。using は名前解決のみで実体は登録されないため、 Core を呼ぶ目的では include を用いること。 GateC(Core) 直行(NYASH_GATE_C_CORE=1)は JSON→Core Interpreter 実行なのでこの問題の影響を受けない。

Toggles and Canaries

  • Core canaries (quick profile): enable with SMOKES_ENABLE_CORE_CANARY=1.
    • Emit→nyvm(Core) scripts: tools/smokes/v2/profiles/quick/core/canary_emit_nyvm_core_{return,binop,if}_vm.sh
    • GateC(Core, json→Core 直行) canaries: tools/smokes/v2/profiles/quick/core/canary_gate_c_core_{file,pipe}_vm.sh既定OFF
    • GateC(Core) array sequence: tools/smokes/v2/profiles/quick/core/canary_gate_c_core_array_mixed_vm.shpush→set→get をログで検証)
    • GateC(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
    • GateC Direct sanity: tools/smokes/v2/profiles/quick/core/canary_gate_c_core_direct_string_vm.sh
  • Runner Core toggle: HAKO_NYVM_CORE=1 (or NYASH_NYVM_CORE=1) selects the Core bridge for the nyvm wrapper path.
  • GateC Core route: set NYASH_GATE_C_CORE=1 (or HAKO_GATE_C_CORE=1) to execute MIR(JSON v0) directly via Core (interpreter path; quiet; exit code mirrors return).
    • Env: NYASH_CORE_MAX_ITERS or HAKO_CORE_MAX_ITERS overrides the Core dispatcher loop cap (default 10000).
    • Plugins: when HAKO_GATE_C_ENABLE_PLUGINS=1 is set, the runner normalizes Array/Map core methods through HostHandleRouter (HAKO_ARRAY_FORCE_HOST=1, HAKO_MAP_FORCE_HOST=1) to keep value/return semantics stable. Plugins が OFF のときは ビルトインの ArrayBox / MapBox にフォールバックする。Map の len()/size() は extern adapter が ビルトイン MapBox の内部データ長を返すフォールバックを持つためplugins=OFF でも0 固定にはならない。
  • Errors: VM 実行/JSON読込エラー時は非0で終了FailFast

PHI Strict既定ON

  • 目的: ループ/分岐における PHI 入力の不整合pred 欠落や自己参照)を早期に検出し、クラッシュや未定義使用を防止する。
  • 既定: ON何も設定しない場合は厳格
  • 無効化(開発・暫定用途のみ):
    • HAKO_VM_PHI_STRICT=0 または NYASH_VM_PHI_STRICT=0
    • ON 指定: 1|true|on|yes 等、OFF 指定: 0|false|off|no 等を受理
  • 実装: src/backend/mir_interpreter/exec.rsPHI 適用時に pred 不一致を FailFast

Quick profile optin switches (smokes)

  • SMOKES_ENABLE_LOOP_COMPARE=1 — Direct↔Bridge parity for loops (sum/break/continue/nested/mixed)
  • SMOKES_ENABLE_LOOP_BRIDGE=1 — Bridge(JSON v0) loop canaries (quiet; last numeric extraction)
  • SMOKES_ENABLE_STAGEB_OOB=1 — StageB OOB observation (array/map)
  • SMOKES_ENABLE_OOB_STRICT=1 — GateC(Core) strict OOB failfast canary (gate_c_oob_strict_fail_vm.sh)
  • SMOKES_ENABLE_LLVM_SELF_PARAM=1 — LLVM instruction boxes selfparam builder tests (const/binop/compare/branch/jump/ret)
  • SMOKES_ENABLE_STAGEB=1 — StageB positive canariesemit→GateC。既定OFF安定化後に昇格予定。 必要に応じて各テスト内で HAKO_STAGEB_ALLOW_FALLBACK=1 を付与TTL; 既定OFF

Default quick canaries (regression)

  • apps/json_lint_vm.sh — JSON Lint expected outputs (OK×10 / ERROR×6)
  • core/array/array_length_vm.sh — ArrayBox.length returns 0→1→2→3 for push sequence

Dispatch policy: length()

  • String 受けに対してのみ StringBox ハンドラが length() を処理する。
  • Array/Map などは各 Box 専用ハンドラで length/len/size を処理する。
  • これにより、配列に対して誤って文字列長を返す回帰を防止する202511 修正)。

Deprecations

  • NYASH_GATE_C_DIRECT は移行中の互換トグルTTLだよ。将来は GateC(Core) 直行(HAKO_GATE_C_CORE=1)に統一予定。新しい導線では Core の実行仕様(数値=rc, 安定化した診断タグ)が適用されるよ。
  • 互換トグルを使うと起動時に警告が出るよ(HAKO_GATE_C_DIRECT_SILENCE=1 で抑止可)。
  • StageB fallback TTL: 既定OFF撤退方針。必要な場合はテスト内限定で HAKO_STAGEB_ALLOW_FALLBACK=1 を付与する。

Diagnostics (stable tags)

  • 本フェーズでは、GateC(Core) の境界で安定タグを整形して出力する:
    • [core/binop] div by zero
    • [core/mir_call] array get out of bounds
    • [core/mir_call] array set out of bounds
    • [core/mir_call] modulefn unsupported: …
    • [core/mir_call] map iterator unsupported
    • [core/mir_call] map len missing arg
    • [core/mir_call] map set missing key|bad key|missing value|bad value
    • [core/mir_call] map get missing key|bad key
    • [core/mir_call] unsupported callee type: Closure
  • GateC Direct では、リーダー/検証レイヤの診断をそのまま用いる(例: unsupported callee type (expected Extern): ModuleFunction)。

Strict OOB policy (GateC)

  • Enable HAKO_OOB_STRICT=1 (alias: NYASH_OOB_STRICT) to tag Array OOB as stable strings ([oob/array/get]…, [oob/array/set]…).
  • With HAKO_OOB_STRICT_FAIL=1 (alias: NYASH_OOB_STRICT_FAIL), GateC(Core) exits nonzero if any OOB was observed during execution (no need to parse stdout in tests).

Exit code differences

  • Core: 数値=rcOS仕様により 0255 に丸められる。例: 777 → rc=9、エラーは非0
  • Direct: 数値出力のみrc=0、エラーは非0
    • 数値が 255 を超えるケースは標準出力の値で検証することrc は下位8ビットへ丸められるため

注: これらの整形は移行中の暫定仕様で、最終的には Core 側に移管される予定CURRENT_TASK に TTL を記載)。

Minimal mir_call semantics (Core)

  • Implemented (scoped):
    • Constructor: ArrayBox(サイズメタ初期化) / MapBox(エントリ数メタ初期化)
    • Methods (Array): size()/push()/pop()/get()/set()(メタデータでサイズ検証)
    • Methods (Map): size()/len()/iterator()/set()/get()(エントリ数メタを返す/更新する/メタから取得する)
    • ModuleFunction: ArrayBox.len/0 / MapBox.len/0(メタのサイズを返す)— 他はタグ付き FailFast
    • Global/Extern: env.console.{log|warn|error}(数値引数のみ印字)
  • Others are FailFast安定文言を出力。Closure 生成は v1 bridge で受理NewClosure 発行)するが、 実行時の呼出は未実装VM 側は FailFast

See also: docs/development/architecture/collection_semantics.mdArray/Map のSSOT集約

String helpers

  • Core routeGateC/Coreでの最小サポートMethod:
    • String.size/0 — 文字列長(バイト)を返す
    • String.indexOf/1 — 最初の一致位置、なければ -1
    • String.lastIndexOf/1 — 最後の一致位置、なければ -1
    • String.substring/2 — 半開区間 [start, end) を返す
  • インデックス規約bytes ベース):
    • start/end は範囲 [0, size] にクランプされる(負の値は 0、size 超は size
    • start > end の場合は空文字size==0
    • インデックスは UTF8 のバイト境界(コードポイント境界ではない)。
  • ModuleFunction:
    • StringHelpers.to_i64/1 — interpreter に inline 実装あり(数値文字列のみを許容)。 数値結果が 255 超の場合、rc は下位8ビットに丸められるため、標準出力の値で検証すること。

Core dispatcher canaries直行ルート

  • profiles/quick/core/canary_core_dispatcher_* は GateC(Core) 直行へ移行済み。 一部(大きな値や pluginenabled 経路)では rc 正規化が未整備のため、数値は標準出力を優先して検証し、 rc はフォールバックとして扱うTTL; 収束後に rc 検証に戻す)。

Aliases

  • Keep existing logical module names in hako.toml and introduce aliases to new paths when transitioning.