**箱理論に基づく根治的修正**: ## 🎯 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>
Hakorune VM Layout (Current → Target)
Current
lang/src/vm/hakorune-vm/— Hakorune VM (nyvm) implementationlang/src/vm/boxes/— Shared helpers (op_handlers, scanners, compare, etc.)- Mini‑VM 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 day‑to‑day execution and integration. Mini VM validates meanings; Hakorune VM executes applications.
Verify Pipeline (hakovm primary / Fail‑Fast)
- Emit MIR(JSON v0) as a single JSON string (noise trimmed in runner).
- Runner embeds JSON into a tiny Hako driver:
using selfhost.vm.entry as MiniVmEntryBox; return MiniVmEntryBox.run_min(j) - 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.namewith workspaces declared inhako_module.tomland aliases innyash.toml. - Implement transitive resolution (bounded depth, cycle detection, caching).
- Prelude は Runner 側で“テキスト統合(merge_prelude_text)”に一本化(AST マージは撤退)。
- Dev プロファイルでは引用付きファイルパス("lang/…")を bring‑up のみ限定許可。prod は alias のみ。
Target (post‑20.12b, gradual)
engines/hakorune/— mainline nyvm engineengines/mini/— Mini‑VM engine (educational/minimal)boxes/— shared helperscore/— 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 engine‑agnostic execution primitives and should not import engine‑specific modules. During migration, temporary adapters may exist.
Static Box Methods(Singleton / self)
- 規約: 静的Boxのメソッドは「self(Singleton)を先頭引数」に持つ。
- 例:
LLVMPhiInstructionBox.lower_phi(self, dst, incoming_list)
- 例:
- 互換:
HAKO_BRIDGE_INJECT_SINGLETON=1(alias:NYASH_BRIDGE_INJECT_SINGLETON)で旧スタイルPhiInst.lower_phi(dst, incoming)に Singleton を注入して実行。 - Fail‑Fast: 期待 arity と不一致は静かなフォールバックをせず、安定メッセージで失敗する。
詳細:
docs/development/architecture/llvm/static_box_singleton.md
Bridge‑B (Ny/Core 直行)
- Wrapper 経路では
include "lang/src/vm/core/dispatcher.hako"で Core Dispatcher を取り込み、NyVmDispatcher.run(json)を直接呼び出す。usingは名前解決のみで実体は登録されないため、 Core を呼ぶ目的ではincludeを用いること。 Gate‑C(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 - Gate‑C(Core, json→Core 直行) canaries:
tools/smokes/v2/profiles/quick/core/canary_gate_c_core_{file,pipe}_vm.sh(既定OFF) - 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
- Emit→nyvm(Core) scripts:
- 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(
SMOKES_INCLUDE_POLICY=skip|warn|error。段階的に ERROR へ移行)。 - using+alias を正道に固定(Runner 解決→Prelude テキスト統合)。
- 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 化)
- MIR(JSON) →
- 代表:
- 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で終了。
- VM backend(Rust Interpreter): 戻り値は標準出力に出す。プロセスの終了コードは戻り値と一致しない場合があるため、スモークは安定タグや標準出力の数値で検証する(rcは参考)。
- 推奨: CIやスクリプトでは Gate‑C(Core) を優先し rc を厳密化。開発時の対話検証は VM ルートで標準出力を検証。
Tag→RC(Core Direct)
- Core Direct(
HAKO_CORE_DIRECT=1)では、数値行が見つからない場合は rc≠0 を返す(Fail‑Fast)。 - 代表タグ(例)
[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)で、Gate‑C(Core) の JSON 実行を "Core Dispatcher 直行" 子経路に切り替える。- 形: 一時Hakoスクリプトに
include "lang/src/vm/core/dispatcher.hako"を埋め込み、NyVmDispatcher.run(json)を実行。 - rc: 子プロセスの終了コードを採用(数値戻り=rc、タグ/非数=rc≠0)。
- 判定: 直行は MIR(JSON v0)(
functionsとblocksを含む)に限定する。Stage‑B Program(JSON v0) は v1 bridge→VM 実行に迂回し、直行は行わない。 - 用途: Core の診断タグや rc を CI で直接検証したい時に使用。
- 形: 一時Hakoスクリプトに
- Runner Core toggle:
HAKO_NYVM_CORE=1(orNYASH_NYVM_CORE=1) selects the Core bridge for the nyvm wrapper path. - Gate‑C Core route: set
NYASH_GATE_C_CORE=1(orHAKO_GATE_C_CORE=1) to execute MIR(JSON v0) directly via Core (interpreter path; quiet; exit code mirrors return).- Env:
NYASH_CORE_MAX_ITERSorHAKO_CORE_MAX_ITERSoverrides the Core dispatcher loop cap (default 10000). - Plugins: when
HAKO_GATE_C_ENABLE_PLUGINS=1is 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 固定にはならない。
- Env:
- Errors: VM 実行/JSON読込エラー時は非0で終了(Fail‑Fast)。
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.rs(PHI 適用時に pred 不一致を Fail‑Fast)
Quick profile opt‑in 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— Stage‑B OOB observation (array/map)SMOKES_ENABLE_OOB_STRICT=1— Gate‑C(Core) strict OOB fail‑fast canary (gate_c_oob_strict_fail_vm.sh)SMOKES_ENABLE_LLVM_SELF_PARAM=1— LLVM instruction boxes self‑param builder tests (const/binop/compare/branch/jump/ret)SMOKES_ENABLE_STAGEB=1— Stage‑B positive canaries(emit→Gate‑C)。既定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 を処理する。
- これにより、配列に対して誤って文字列長を返す回帰を防止する(2025‑11 修正)。
Strictness & Tolerance (ENV policy)
- PHI strict(既定ON)
- 既定ON(未指定時はON)。無効化は
HAKO_VM_PHI_STRICT=0(互換:NYASH_VM_PHI_STRICT=0)。 - 目的: pred不一致などPHI入力の欠落をFail‑Fastで検出。
- 既定ON(未指定時はON)。無効化は
- 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でGate‑C(Core)の実行を非0終了にする(出力のパース不要)。
- Tolerance系(開発専用)
NYASH_VM_TOLERATE_VOID=1等の寛容フラグは開発/診断専用。既定ではOFFで、CI/quickでは使用しないこと。
Deprecations
NYASH_GATE_C_DIRECTは移行中の互換トグル(TTL)だよ。将来は Gate‑C(Core) 直行(HAKO_GATE_C_CORE=1)に統一予定。新しい導線では Core の実行仕様(数値=rc, 安定化した診断タグ)が適用されるよ。- 互換トグルを使うと起動時に警告が出るよ(
HAKO_GATE_C_DIRECT_SILENCE=1で抑止可)。 - Stage‑B fallback TTL: 既定OFF(撤退方針)。必要な場合はテスト内限定で
HAKO_STAGEB_ALLOW_FALLBACK=1を付与する。
Diagnostics (stable tags)
- 本フェーズでは、Gate‑C(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 array(VM ArrayBox.pop 空時。strict環境(HAKO_OOB_STRICT=1)で有効)
- Gate‑C Direct では、リーダー/検証レイヤの診断をそのまま用いる(例:
unsupported callee type (expected Extern): ModuleFunction)。
Strict OOB policy (Gate‑C)
- 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), 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
- 数値が 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(メタのサイズを返す)— 他はタグ付き Fail‑Fast - Global/Extern:
env.console.{log|warn|error}(数値引数のみ印字)
- Constructor:
- Others are Fail‑Fast(安定文言を出力)。Closure 生成は v1 bridge で受理(NewClosure 発行)するが、 実行時の呼出は未実装(VM 側は Fail‑Fast)。
See also: docs/development/architecture/collection_semantics.md(Array/Map のSSOT集約)
String helpers
- Core route(Gate‑C/Core)での最小サポート(Method):
String.size/0— 文字列長(バイト)を返すString.indexOf/1— 最初の一致位置、なければ -1String.lastIndexOf/1— 最後の一致位置、なければ -1String.substring/2— 半開区間 [start, end) を返す
- インデックス規約(bytes ベース):
- start/end は範囲 [0, size] にクランプされる(負の値は 0、size 超は size)。
- start > end の場合は空文字(size==0)。
- インデックスは UTF‑8 のバイト境界(コードポイント境界ではない)。
- ModuleFunction:
StringHelpers.to_i64/1— interpreter に inline 実装あり(数値文字列のみを許容)。 数値結果が 255 超の場合、rc は下位8ビットに丸められるため、標準出力の値で検証すること。
Core dispatcher canaries(直行ルート)
profiles/quick/core/canary_core_dispatcher_*は Gate‑C(Core) 直行へ移行済み。 一部(大きな値や plugin‑enabled 経路)では rc 正規化が未整備のため、数値は標準出力を優先して検証し、 rc はフォールバックとして扱う(TTL; 収束後に rc 検証に戻す)。
Aliases
- Keep existing logical module names in
hako.tomland 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.hakoand is imported viausing selfhost.vm.helpers.mini_map as MiniMap. - InstructionScanner fixes: ret/const misclassification fixed. If an object
has
opordst, it is not treated as implicit ret. Typed const detection tolerates spaces. - PHI handling (dev aids):
- Runtime trace: set
NYASH_VM_TRACE_PHI=1to log block predecessors and PHIinputs.predat application time. On strict mismatch, the trace logsdst,last_pred,inputs, and the blockpredsset to aid diagnosis. - Strictness:
HAKO_VM_PHI_STRICT=0orNYASH_VM_PHI_STRICT=0can relax the fail‑fast during bring‑up.NYASH_VM_PHI_TOLERATE_UNDEFINED=1substitutesVoidwhen a referenced input is undefined. - Builder (merge) policy: if/merge PHI inputs are now limited to reachable
predecessors only. Unreachable
else_blockis no longer synthesized as an input. EnableHAKO_PHI_VERIFY=1orNYASH_PHI_VERIFY=1to print missing or duplicate predecessor inputs at build time.
- Runtime trace: set
Trace/verify quickstart
- Preludes (using) parse context:
NYASH_STRIP_DEBUG=1prints surrounding lines for parser errors to accelerate pinpointing braces/ASI issues. - PHI trace:
NYASH_VM_TRACE_PHI=1alongside a Core run (--json-file) shows predecessor→incoming selection decisions.