Files
hakorune/lang/src/vm
nyash-codex f74b7d2b04 📦 Hotfix 1 & 2: Parameter ValueId Reservation + Exit PHI Validation (Box-First Theory)
**箱理論に基づく根治的修正**:

## 🎯 Hotfix 1: Parameter ValueId Reservation (パラメータ ValueId 予約)

### 根本原因
- MirFunction counter が params.len() を考慮していなかった
- local variables が parameter ValueIds を上書き

### 箱理論的解決
1. **LoopFormContext Box**
   - パラメータ予約を明示的に管理
   - 境界をはっきりさせる

2. **MirFunction::new() 改善**
   - `initial_counter = param_count.max(1)` でパラメータ予約
   - Parameters are %0, %1, ..., %N-1

3. **ensure_counter_after() 強化**
   - パラメータ数 + 既存 ValueIds 両方を考慮
   - `min_counter = param_count.max(max_id + 1)`

4. **reserve_parameter_value_ids() 追加**
   - 明示的な予約メソッド(Box-First)

## 🎯 Hotfix 2: Exit PHI Predecessor Validation (Exit PHI 検証)

### 根本原因
- LoopForm builder が存在しないブロックを PHI predecessor に追加
- 「幽霊ブロック」問題

### 箱理論的解決
1. **LoopFormOps.block_exists() 追加**
   - CFG 存在確認メソッド
   - 境界を明確化

2. **build_exit_phis() 検証**
   - 非存在ブロックをスキップ
   - デバッグログ付き

### 実装ファイル
- `src/mir/function.rs`: Parameter reservation
- `src/mir/phi_core/loopform_builder.rs`: Context + validation
- `src/mir/loop_builder.rs`: LoopFormOps impl
- `src/mir/builder/stmts.rs`: Local variable allocation

### 業界標準準拠
-  LLVM IR: Parameters are %0, %1, ...
-  SSA Form: PHI predecessors must exist in CFG
-  Cytron et al. (1991): Parameter reservation principle

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 06:39:45 +09:00
..

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

Mini VM vs Hakorune VM (Roles)

  • Mini VM (Hako): reference semantic executor for MIR(JSON v0). Scope is the minimal instruction set (const/compare/branch/jump/ret/phi). Its job in the system is verification: given MIR(JSON v0), compute the return value and map it to an exit code (Int → value, Bool → 1/0). It must not depend on env/get or include; inputs are passed as inline JSON strings.
  • Hakorune VM (Rust): production runtime with Boxes/Plugins/Externs and wider semantics. Used for daytoday execution and integration. Mini VM validates meanings; Hakorune VM executes applications.

Verify Pipeline (hakovm primary / FailFast)

  1. Emit MIR(JSON v0) as a single JSON string (noise trimmed in runner).
  2. Runner embeds JSON into a tiny Hako driver: using selfhost.vm.entry as MiniVmEntryBox; return MiniVmEntryBox.run_min(j)
  3. The driver prints the numeric return; the runner converts it into a process exit code for canaries. No env.get or file I/O is required.

Resolver Policy (Modules)

  • Prefer using alias.name with workspaces declared in hako_module.toml and aliases in nyash.toml.
  • Implement transitive resolution (bounded depth, cycle detection, caching).
  • Prelude は Runner 側で“テキスト統合merge_prelude_text”に一本化AST マージは撤退)。
  • Dev プロファイルでは引用付きファイルパス("lang/…")を bringup のみ限定許可。prod は alias のみ。

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

MiniVM 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=2push×2→size
    • phase2036/v1_minivm_size_state_per_recv_on_canary_vm.sh → rc=2A/B 各1push→size(A)+size(B)=2

Scanners (Box化)

  • MiniMirV1Scanlang/src/vm/boxes/mini_mir_v1_scan.hako
    • callee_name/method_name/first_arg_register/receiver_id を提供。
    • MirVmMin は本スキャナを呼ぶだけにして重複スキャンを排除(保守性の向上)。

Include Policy (quick)

  • include は非推奨。quick プロファイルでは SKIPSMOKES_INCLUDE_POLICY=skip|warn|error。段階的に ERROR へ移行)。
  • using+alias を正道に固定Runner 解決→Prelude テキスト統合)。
  • nyash.toml の [modules] / aliasへ移行すること。

Core Route (Phase 20.34 final)

  • Canaryの一部Loop 系)は暫定的に Core 実行へ切替MiniVM の緑化は 20.36 で段階的に実施)。
  • verify_mir_rc は primary=core の場合、
    • MIR(JSON) → --mir-json-filev1→v0 順でローダが MirModule 復元)
    • Program(JSON v0) → --json-filejson_v0_bridge 経由で MirModule 化)
  • 代表:
    • mirbuilder_internal_core_exec_canary_vmrc=10
    • mirbuilder_internal_loop_core_exec_canary_vmrc=3
    • mirbuilder_internal_loop_count_param_core_exec_canary_vmrc=6
    • mirbuilder_internal_loop_sum_bc_core_exec_canary_vmrc=8

Loop/PHI Unification

  • Runner v0 の Loop/PHI は phi_core に統一SSOT
  • トグル: NYASH_MIR_UNIFY_LOOPFORM=1|0既定ON。OFF 指定時は警告を出しつつ統一経路を継続利用(レガシー経路は削除済み)。
  • 目的: 既定ONの明示化と将来のABテスト窓口の確保挙動は不変

Exit Code Policy

  • GateC(Core): numeric return is mapped to process exit code。タグ付きの失敗時は安定メッセージを出し、可能な限り非0で終了。
  • VM backendRust Interpreter: 戻り値は標準出力に出す。プロセスの終了コードは戻り値と一致しない場合があるため、スモークは安定タグや標準出力の数値で検証するrcは参考
  • 推奨: CIやスクリプトでは GateC(Core) を優先し rc を厳密化。開発時の対話検証は VM ルートで標準出力を検証。

Tag→RCCore Direct

  • Core DirectHAKO_CORE_DIRECT=1)では、数値行が見つからない場合は rc≠0 を返すFailFast
  • 代表タグ(例)
    • [core/string/bounds] → rc=1
    • [core/array/oob_set] → rc=1
    • [core/mir_call/method_unsupported] → rc=1

Core Direct Toggle

  • HAKO_CORE_DIRECT=1(互換: NYASH_CORE_DIRECTで、GateC(Core) の JSON 実行を "Core Dispatcher 直行" 子経路に切り替える。
    • 形: 一時Hakoスクリプトに include "lang/src/vm/core/dispatcher.hako" を埋め込み、NyVmDispatcher.run(json) を実行。
    • rc: 子プロセスの終了コードを採用(数値戻り=rc、タグ/非数=rc≠0
    • 判定: 直行は MIR(JSON v0)functionsblocks を含むに限定する。StageB Program(JSON v0) は v1 bridge→VM 実行に迂回し、直行は行わない。
    • 用途: Core の診断タグや rc を CI で直接検証したい時に使用。
  • 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 修正)。

Strictness & Tolerance (ENV policy)

  • PHI strict既定ON
    • 既定ON未指定時はON。無効化は HAKO_VM_PHI_STRICT=0(互換: NYASH_VM_PHI_STRICT=0)。
    • 目的: pred不一致などPHI入力の欠落をFailFastで検出。
  • VMステップ上限無限ループ対策
    • HAKO_VM_MAX_STEPS(互換: NYASH_VM_MAX_STEPSで1関数内の実行ステップに上限既定: 1,000,000
    • 0 を指定するとステップ上限なし(開発/診断専用; 無限ループに注意)。
  • OOB strict配列/範囲外の観測)
    • HAKO_OOB_STRICT=1(互換: NYASH_OOB_STRICT=1でOOBを観測・タグ出力。
    • HAKO_OOB_STRICT_FAIL=1 でGateC(Core)の実行を非0終了にする出力のパース不要
  • Tolerance系開発専用
    • NYASH_VM_TOLERATE_VOID=1 等の寛容フラグは開発/診断専用。既定ではOFFで、CI/quickでは使用しないこと。

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
    • [map/missing] Key not found: <key>VM MapBox.get の既定タグ。従来文言を含むため後方互換)
    • [array/empty/pop] empty arrayVM ArrayBox.pop 空時。strict環境HAKO_OOB_STRICT=1で有効
  • 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).

Default OOB behavior (GateC/Core)

  • 既定では OOB はエラー化せず、rc=0Void/0 同等)として扱う。
  • 検証を厳密化したい場合は Strict OOB policy を有効化して安定タグまたは非0終了に切替える。

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. MiniVM bringup (Phase 20.34 — notes)
  • MiniMap box: minimal stringbacked 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 failfast during bringup. 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.