vm(hako): add v1 reader/dispatcher (flagged), commonize mir_call handler, share block scan; smokes: add v1 hakovm canary; docs: 20.37/20.38 plans, OOB policy; runner: v1 hakovm toggle; include SKIP summary
This commit is contained in:
1105
CURRENT_TASK.md
1105
CURRENT_TASK.md
File diff suppressed because it is too large
Load Diff
33
include/nyrt.h
Normal file
33
include/nyrt.h
Normal file
@ -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
|
||||||
|
|
||||||
@ -92,8 +92,9 @@ static box JsonFragBox {
|
|||||||
local pat1 = "\"" + key + "\":"
|
local pat1 = "\"" + key + "\":"
|
||||||
local p = me.index_of_from(seg, pat1, 0)
|
local p = me.index_of_from(seg, pat1, 0)
|
||||||
if p >= 0 {
|
if p >= 0 {
|
||||||
local v = me.read_digits(seg, p + pat1.length())
|
// tolerant: skip whitespace and optional sign
|
||||||
if v != "" { return me._str_to_int(v) }
|
local v = me.read_int_after(seg, p + pat1.length())
|
||||||
|
if v != null { return me._str_to_int(v) }
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) 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`
|
- 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→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
|
Exit Code Policy
|
||||||
- Gate‑C(Core): numeric return is mapped to process exit code。タグ付きの失敗時は安定メッセージを出し、可能な限り非0で終了。
|
- 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
|
- 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).
|
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
|
Exit code differences
|
||||||
- Core: 数値=rc(OS仕様により 0–255 に丸められる。例: 777 → rc=9)、エラーは非0
|
- Core: 数値=rc(OS仕様により 0–255 に丸められる。例: 777 → rc=9)、エラーは非0
|
||||||
- Direct: 数値出力のみ(rc=0)、エラーは非0
|
- Direct: 数値出力のみ(rc=0)、エラーは非0
|
||||||
@ -203,3 +241,27 @@ Core dispatcher canaries(直行ルート)
|
|||||||
Aliases
|
Aliases
|
||||||
- Keep existing logical module names in `hako.toml` and introduce aliases to
|
- Keep existing logical module names in `hako.toml` and introduce aliases to
|
||||||
new paths when transitioning.
|
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.
|
||||||
|
|||||||
@ -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, "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, "cond") == 1 { return "branch" }
|
||||||
if me._has_key(obj, "target") == 1 { return "jump" }
|
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
|
// Detect explicit ret first
|
||||||
if obj.indexOf("\"op\":\"ret\"") >= 0 { return "ret" }
|
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, "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"
|
return "ret"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Const fallback (typed value object)
|
|
||||||
if obj.indexOf("\"value\":{\"type\":\"i64\"") >= 0 { return "const" }
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
52
lang/src/vm/boxes/mini_map_box.hako
Normal file
52
lang/src/vm/boxes/mini_map_box.hako
Normal file
@ -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 } }
|
||||||
|
|
||||||
64
lang/src/vm/boxes/mini_mir_v1_scan.hako
Normal file
64
lang/src/vm/boxes/mini_mir_v1_scan.hako
Normal file
@ -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 } }
|
||||||
@ -1,6 +1,6 @@
|
|||||||
// mini_vm_entry.hako — MiniVmEntryBox
|
// mini_vm_entry.hako — MiniVmEntryBox
|
||||||
// Thin entry wrapper to stabilize static call names for smokes and tools.
|
// 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 {
|
static box MiniVmEntryBox {
|
||||||
|
|
||||||
|
|||||||
76
lang/src/vm/boxes/mir_call_v1_handler.hako
Normal file
76
lang/src/vm/boxes/mir_call_v1_handler.hako
Normal file
@ -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 } }
|
||||||
@ -5,8 +5,12 @@ using selfhost.shared.common.string_helpers as StringHelpers
|
|||||||
using selfhost.shared.common.string_ops as StringOps
|
using selfhost.shared.common.string_ops as StringOps
|
||||||
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||||
using selfhost.shared.json.core.json_cursor as JsonCursorBox
|
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.operator as OperatorBox
|
||||||
|
using selfhost.vm.helpers.mini_mir_v1_scan as MiniMirV1Scan
|
||||||
using selfhost.vm.helpers.compare_ops as CompareOpsBox
|
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.compare_scan as CompareScanBox
|
||||||
using selfhost.vm.helpers.phi_apply as PhiApplyBox
|
using selfhost.vm.helpers.phi_apply as PhiApplyBox
|
||||||
using selfhost.vm.helpers.guard as GuardBox
|
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.ret_resolve as RetResolveSimpleBox
|
||||||
using selfhost.vm.helpers.instruction_scanner as InstructionScannerBox
|
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 {
|
static box MirVmMin {
|
||||||
_tprint(msg) {
|
_tprint(msg) {
|
||||||
// Only emit hard errors by default; avoid env dependencies in Mini‑VM
|
// 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) }
|
if msg.indexOf("[ERROR]") >= 0 { print(msg) }
|
||||||
}
|
}
|
||||||
_d(msg, trace) { if trace == 1 { print(msg) } }
|
_d(msg, trace) { if trace == 1 { print(msg) } }
|
||||||
_parse_callee_name(seg) {
|
|
||||||
// naive scan: '"callee":{"name":"<sym>"'
|
|
||||||
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":"<sym>"'
|
|
||||||
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":[ <int>'
|
|
||||||
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) {
|
_handle_mir_call(seg, regs) {
|
||||||
// Support minimal externs only (i64 variants). Fail‑Fast for others.
|
// Support minimal externs/methods (i64 variants). Constructor is no‑op.
|
||||||
local name = me._parse_callee_name(seg)
|
// v1: treat Constructor as no‑op and write dst=0 to keep SSA continuity
|
||||||
local arg0id = me._parse_first_arg(seg)
|
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 == "" {
|
if name == "" {
|
||||||
// Try Method callee
|
// Try Method callee
|
||||||
local mname = me._parse_method_name(seg)
|
local mname = MiniMirV1Scan.method_name(seg)
|
||||||
if mname != "" {
|
if mname != "" {
|
||||||
// Optional: minimal stateful bridge for size/len/length/push
|
// 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.
|
// 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" {
|
if ("" + size_state) != "1" {
|
||||||
local stub = env.get("HAKO_VM_MIRCALL_STUB"); if stub == null { stub = "1" }
|
local stub = env.get("HAKO_VM_MIRCALL_STUB"); if stub == null { stub = "1" }
|
||||||
if ("" + stub) == "1" { print("[vm/method/stub:" + mname + "]") }
|
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
|
return
|
||||||
}
|
}
|
||||||
// Stateful branch
|
// Stateful branch
|
||||||
// Keep a simple per-run length counter in regs["__vm_len"]. Default 0.
|
// Length counter: global or per-receiver depending on flag
|
||||||
local cur_len_raw = regs.getField("__vm_len"); if cur_len_raw == null { cur_len_raw = "0" }
|
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)
|
local cur_len = JsonFragBox._str_to_int(cur_len_raw)
|
||||||
if mname == "push" {
|
if mname == "push" {
|
||||||
cur_len = cur_len + 1
|
cur_len = cur_len + 1
|
||||||
regs.set("__vm_len", "" + cur_len)
|
regs.setField(key, "" + cur_len)
|
||||||
// push returns void/0 in this minimal path
|
// 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
|
return
|
||||||
}
|
}
|
||||||
if mname == "len" || mname == "length" || mname == "size" {
|
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
|
return
|
||||||
}
|
}
|
||||||
// Others: no-op but keep stub tag for observability
|
// Others: no-op but keep stub tag for observability
|
||||||
print("[vm/method/stub:" + mname + "]")
|
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
|
return
|
||||||
}
|
}
|
||||||
me._tprint("[ERROR] mir_call: missing callee")
|
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 }
|
_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 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
|
// 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) }
|
_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) {
|
_run_min(mjson) {
|
||||||
// Normalize input as string to guarantee String methods availability
|
// Normalize input as string to guarantee String methods availability
|
||||||
mjson = "" + mjson
|
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
|
// Use user InstanceBox (MiniMap) with getField/setField to avoid provider dependencies
|
||||||
local regs = new MiniMap()
|
local regs = new MiniMap()
|
||||||
@ -272,13 +365,14 @@ static box MirVmMin {
|
|||||||
loop(true){
|
loop(true){
|
||||||
steps = steps + 1
|
steps = steps + 1
|
||||||
if steps > max_steps { return 0 }
|
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)
|
me._d("[DEBUG] start="+me._int_to_str(start), trace)
|
||||||
if start < 0 { return 0 }
|
if start < 0 { return 0 }
|
||||||
local endp = JsonCursorBox.seek_array_end(mjson, start)
|
local endp = JsonCursorBox.seek_array_end(mjson, start)
|
||||||
me._d("[DEBUG] endp="+me._int_to_str(endp), trace)
|
me._d("[DEBUG] endp="+me._int_to_str(endp), trace)
|
||||||
if endp <= start { return 0 }
|
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)
|
me._d("[DEBUG] seglen="+me._int_to_str(inst_seg.length()), trace)
|
||||||
// scan objects in this block
|
// scan objects in this block
|
||||||
local scan_pos = 0
|
local scan_pos = 0
|
||||||
@ -319,41 +413,24 @@ static box MirVmMin {
|
|||||||
}
|
}
|
||||||
else if op == "compare" {
|
else if op == "compare" {
|
||||||
me._d("[DEBUG] compare seg=" + seg, trace)
|
me._d("[DEBUG] compare seg=" + seg, trace)
|
||||||
// Safe fast-path: if this block later contains a ret of this dst,
|
// Extract fields directly to avoid MapBox dependency
|
||||||
// evaluate compare directly from current regs and return immediately.
|
local kdst_fast = JsonFragBox.get_int(seg, "dst")
|
||||||
// This avoids deeper handler chains that could recurse in edge cases.
|
local klhs_fast = JsonFragBox.get_int(seg, "lhs")
|
||||||
local rec = CompareScanBox.parse(seg)
|
local krhs_fast = JsonFragBox.get_int(seg, "rhs")
|
||||||
local kdst_fast = rec.get("dst")
|
local kcmp_fast = JsonFragBox.get_str(seg, "cmp")
|
||||||
local klhs_fast = rec.get("lhs")
|
if kcmp_fast == "" {
|
||||||
local krhs_fast = rec.get("rhs")
|
local sym = JsonFragBox.get_str(seg, "operation")
|
||||||
local kcmp_fast = rec.get("kind")
|
if sym != "" { kcmp_fast = CompareOpsBox.map_symbol(sym) } else { kcmp_fast = "Eq" }
|
||||||
// Determine if a ret exists after this compare in the same block
|
}
|
||||||
local tail = inst_seg.substring(obj_end, inst_seg.length())
|
// Compute compare result and store
|
||||||
local ridt = JsonFragBox.get_int(tail, "value")
|
if kdst_fast != null && klhs_fast != null && krhs_fast != null {
|
||||||
if kdst_fast != null && klhs_fast != null && krhs_fast != null && ridt != null && ridt == kdst_fast {
|
|
||||||
local a = me._load_reg(regs, klhs_fast)
|
local a = me._load_reg(regs, klhs_fast)
|
||||||
local b = me._load_reg(regs, krhs_fast)
|
local b = me._load_reg(regs, krhs_fast)
|
||||||
local cv_fast = CompareOpsBox.eval(kcmp_fast, a, b)
|
local cv = CompareOpsBox.eval(kcmp_fast, a, b)
|
||||||
// Store result to keep regs consistent for subsequent ops if any
|
regs.setField("" + kdst_fast, "" + cv)
|
||||||
regs.set("" + kdst_fast, "" + cv_fast)
|
|
||||||
last_cmp_dst = kdst_fast
|
last_cmp_dst = kdst_fast
|
||||||
last_cmp_val = cv_fast
|
last_cmp_val = cv
|
||||||
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" {
|
else if op == "mir_call" {
|
||||||
me._handle_mir_call(seg, regs)
|
me._handle_mir_call(seg, regs)
|
||||||
@ -388,117 +465,23 @@ static box MirVmMin {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
else if op == "phi" {
|
else if op == "phi" {
|
||||||
local res = PhiDecodeBox.decode_result(seg, prev_bb)
|
// Mini‑VM v0: skip PHI (blocks we exercise should not rely on PHI semantics)
|
||||||
if res.is_Ok() == 1 {
|
|
||||||
local pair = res.as_Ok()
|
|
||||||
PhiApplyBox.apply(pair.get(0), pair.get(1), regs)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if op == "throw" {
|
if op == "throw" {
|
||||||
me._tprint("[ERROR] Throw terminator encountered")
|
me._tprint("[ERROR] Throw terminator encountered")
|
||||||
return -2
|
return -2
|
||||||
}
|
}
|
||||||
else if op == "ret" {
|
if op == "ret" {
|
||||||
local v = JsonFragBox.get_int(seg, "value")
|
me._d("[DEBUG] ret seg=" + seg, trace)
|
||||||
if v == null { me._tprint("[ERROR] Undefined ret value field") return -1 }
|
local r = me._handle_ret_op(seg, regs, last_cmp_dst, last_cmp_val, gc_trace)
|
||||||
if v == last_cmp_dst {
|
return r
|
||||||
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
|
// advance
|
||||||
inst_count = inst_count + 1
|
inst_count = inst_count + 1
|
||||||
scan_pos = obj_end
|
scan_pos = obj_end
|
||||||
}
|
}
|
||||||
if moved == 1 { continue }
|
if moved == 1 { continue }
|
||||||
// Prefer recent compare result when a ret exists targeting it (no recompute)
|
return me._resolve_block_ret(inst_seg, regs, last_cmp_dst, last_cmp_val, gc_trace)
|
||||||
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 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
456
lang/src/vm/boxes/mir_vm_min.hako.backup
Normal file
456
lang/src/vm/boxes/mir_vm_min.hako.backup
Normal file
@ -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":"<sym>"'
|
||||||
|
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":"<sym>"'
|
||||||
|
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":[ <int>'
|
||||||
|
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<n){ local ch=s.substring(i,i+1) if ch<"0"||ch>"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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -17,7 +17,7 @@ static box NyVmJsonV0Reader {
|
|||||||
|
|
||||||
// Return substring for the first function object
|
// Return substring for the first function object
|
||||||
first_function(json) {
|
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 "" }
|
if p < 0 { return "" }
|
||||||
local lb = json.indexOf("[", p)
|
local lb = json.indexOf("[", p)
|
||||||
if lb < 0 { return "" }
|
if lb < 0 { return "" }
|
||||||
@ -30,7 +30,7 @@ static box NyVmJsonV0Reader {
|
|||||||
|
|
||||||
// Return substring for the first block object
|
// Return substring for the first block object
|
||||||
first_block(func_json) {
|
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 "" }
|
if p < 0 { return "" }
|
||||||
local lb = func_json.indexOf("[", p)
|
local lb = func_json.indexOf("[", p)
|
||||||
if lb < 0 { return "" }
|
if lb < 0 { return "" }
|
||||||
@ -43,7 +43,7 @@ static box NyVmJsonV0Reader {
|
|||||||
|
|
||||||
// Return substring for the instructions array content (without the surrounding brackets)
|
// Return substring for the instructions array content (without the surrounding brackets)
|
||||||
block_instructions_json(block_json) {
|
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 "" }
|
if p < 0 { return "" }
|
||||||
local lb = block_json.indexOf("[", p)
|
local lb = block_json.indexOf("[", p)
|
||||||
if lb < 0 { return "" }
|
if lb < 0 { return "" }
|
||||||
@ -54,7 +54,7 @@ static box NyVmJsonV0Reader {
|
|||||||
|
|
||||||
// Read function entry id if present; returns -1 when missing
|
// Read function entry id if present; returns -1 when missing
|
||||||
read_entry_id(func_json) {
|
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 }
|
if p < 0 { return -1 }
|
||||||
p = func_json.indexOf(":", p)
|
p = func_json.indexOf(":", p)
|
||||||
if p < 0 { return -1 }
|
if p < 0 { return -1 }
|
||||||
@ -68,7 +68,7 @@ static box NyVmJsonV0Reader {
|
|||||||
|
|
||||||
// Parse block id from a block JSON object; returns -1 on failure
|
// Parse block id from a block JSON object; returns -1 on failure
|
||||||
read_block_id(block_json) {
|
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 }
|
if p < 0 { return -1 }
|
||||||
p = block_json.indexOf(":", p)
|
p = block_json.indexOf(":", p)
|
||||||
if p < 0 { return -1 }
|
if p < 0 { return -1 }
|
||||||
@ -84,7 +84,7 @@ static box NyVmJsonV0Reader {
|
|||||||
build_block_map(func_json) {
|
build_block_map(func_json) {
|
||||||
local out = new MapBox()
|
local out = new MapBox()
|
||||||
// locate blocks array
|
// 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 }
|
if p < 0 { return out }
|
||||||
local lb = func_json.indexOf("[", p)
|
local lb = func_json.indexOf("[", p)
|
||||||
if lb < 0 { return out }
|
if lb < 0 { return out }
|
||||||
|
|||||||
22
lang/src/vm/hakorune-vm/dispatcher_v1.hako
Normal file
22
lang/src/vm/hakorune-vm/dispatcher_v1.hako
Normal file
@ -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 } }
|
||||||
25
lang/src/vm/hakorune-vm/extern_provider.hako
Normal file
25
lang/src/vm/hakorune-vm/extern_provider.hako
Normal file
@ -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 } }
|
||||||
45
lang/src/vm/hakorune-vm/json_v1_reader.hako
Normal file
45
lang/src/vm/hakorune-vm/json_v1_reader.hako
Normal file
@ -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 } }
|
||||||
@ -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.core" = "lang/src/vm/boxes/mini_vm_core.hako"
|
||||||
"selfhost.vm.helpers.op_handlers" = "lang/src/vm/boxes/op_handlers.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.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_ops" = "lang/src/vm/boxes/compare_ops.hako"
|
||||||
"selfhost.vm.helpers.compare_scan" = "lang/src/vm/boxes/compare_scan_box.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"
|
"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.gc_hooks" = "lang/src/vm/gc/gc_hooks.hako"
|
||||||
"selfhost.vm.helpers.arithmetic" = "lang/src/vm/boxes/arithmetic.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.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.entry" = "lang/src/vm/boxes/mini_vm_entry.hako"
|
||||||
"hakorune.vm.mir_min" = "lang/src/vm/boxes/mir_vm_min.hako"
|
"hakorune.vm.mir_min" = "lang/src/vm/boxes/mir_vm_min.hako"
|
||||||
"hakorune.vm.core" = "lang/src/vm/boxes/mini_vm_core.hako"
|
"hakorune.vm.core" = "lang/src/vm/boxes/mini_vm_core.hako"
|
||||||
|
|||||||
44
src/abi/nyrt_shim.rs
Normal file
44
src/abi/nyrt_shim.rs
Normal file
@ -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() };
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -100,9 +100,25 @@ impl MirInterpreter {
|
|||||||
block: &BasicBlock,
|
block: &BasicBlock,
|
||||||
last_pred: Option<BasicBlockId>,
|
last_pred: Option<BasicBlockId>,
|
||||||
) -> Result<(), VMError> {
|
) -> 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() {
|
for inst in block.phi_instructions() {
|
||||||
if let MirInstruction::Phi { dst, inputs } = inst {
|
if let MirInstruction::Phi { dst, inputs } = inst {
|
||||||
let dst_id = *dst;
|
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(pred) = last_pred {
|
||||||
if let Some((_, val)) = inputs.iter().find(|(bb, _)| *bb == pred) {
|
if let Some((_, val)) = inputs.iter().find(|(bb, _)| *bb == pred) {
|
||||||
let v = match self.reg_load(*val) {
|
let v = match self.reg_load(*val) {
|
||||||
@ -150,6 +166,15 @@ impl MirInterpreter {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if strict {
|
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!(
|
return Err(VMError::InvalidInstruction(format!(
|
||||||
"phi pred mismatch at {:?}: no input for predecessor {:?}",
|
"phi pred mismatch at {:?}: no input for predecessor {:?}",
|
||||||
dst_id, pred
|
dst_id, pred
|
||||||
|
|||||||
@ -645,6 +645,7 @@ impl MirInterpreter {
|
|||||||
extern_name: &str,
|
extern_name: &str,
|
||||||
args: &[ValueId],
|
args: &[ValueId],
|
||||||
) -> Result<VMValue, VMError> {
|
) -> Result<VMValue, VMError> {
|
||||||
|
if let Some(res) = self.extern_provider_dispatch(extern_name, args) { return res; }
|
||||||
match extern_name {
|
match extern_name {
|
||||||
// Minimal console externs
|
// Minimal console externs
|
||||||
"nyash.console.log" | "env.console.log" | "print" | "nyash.builtin.print" => {
|
"nyash.console.log" | "env.console.log" | "print" | "nyash.builtin.print" => {
|
||||||
@ -656,6 +657,8 @@ impl MirInterpreter {
|
|||||||
}
|
}
|
||||||
Ok(VMValue::Void)
|
Ok(VMValue::Void)
|
||||||
}
|
}
|
||||||
|
// Direct provider calls (bypass hostbridge.extern_invoke)
|
||||||
|
// Above provider covers env.* family; keep legacy fallbacks below
|
||||||
"exit" => {
|
"exit" => {
|
||||||
let code = if let Some(arg_id) = args.get(0) {
|
let code = if let Some(arg_id) = args.get(0) {
|
||||||
self.reg_load(*arg_id)?.as_integer().unwrap_or(0)
|
self.reg_load(*arg_id)?.as_integer().unwrap_or(0)
|
||||||
|
|||||||
142
src/backend/mir_interpreter/handlers/extern_provider.rs
Normal file
142
src/backend/mir_interpreter/handlers/extern_provider.rs
Normal file
@ -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::<JsonValue>(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<Result<VMValue, VMError>> {
|
||||||
|
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<String> = 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::<crate::boxes::array::ArrayBox>() {
|
||||||
|
let idx: Box<dyn crate::box_trait::NyashBox> =
|
||||||
|
Box::new(crate::box_trait::IntegerBox::new(0));
|
||||||
|
let elem = ab.get(idx);
|
||||||
|
first_arg_str = Some(elem.to_string_box().value);
|
||||||
|
} else {
|
||||||
|
first_arg_str = Some(b.to_string_box().value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => first_arg_str = Some(v.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,22 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use serde_json::{Value as JsonValue, Map as JsonMap};
|
||||||
|
|
||||||
impl MirInterpreter {
|
impl MirInterpreter {
|
||||||
|
#[inline]
|
||||||
|
fn ensure_mir_json_version_field(s: &str) -> String {
|
||||||
|
match serde_json::from_str::<JsonValue>(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(
|
pub(super) fn handle_extern_call(
|
||||||
&mut self,
|
&mut self,
|
||||||
dst: Option<ValueId>,
|
dst: Option<ValueId>,
|
||||||
@ -131,50 +147,20 @@ impl MirInterpreter {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
("env", "get") => {
|
("env", "get") => {
|
||||||
// env.get(key) - get environment variable
|
// Delegate to provider
|
||||||
if let Some(a0) = args.get(0) {
|
let ret = self.extern_provider_dispatch("env.get", args).unwrap_or(Ok(VMValue::Void))?;
|
||||||
let k = self.reg_load(*a0)?.to_string();
|
if let Some(d) = dst { self.regs.insert(d, ret); }
|
||||||
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())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
("env.mirbuilder", "emit") => {
|
("env.mirbuilder", "emit") => {
|
||||||
// program_json -> mir_json (delegate provider)
|
let ret = self.extern_provider_dispatch("env.mirbuilder.emit", args).unwrap_or(Ok(VMValue::Void))?;
|
||||||
if let Some(a0) = args.get(0) {
|
if let Some(d) = dst { self.regs.insert(d, ret); }
|
||||||
let program_json = self.reg_load(*a0)?.to_string();
|
Ok(())
|
||||||
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()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
("env.codegen", "emit_object") => {
|
("env.codegen", "emit_object") => {
|
||||||
// mir_json -> object path (ny-llvmc or harness)
|
let ret = self.extern_provider_dispatch("env.codegen.emit_object", args).unwrap_or(Ok(VMValue::Void))?;
|
||||||
if let Some(a0) = args.get(0) {
|
if let Some(d) = dst { self.regs.insert(d, ret); }
|
||||||
let mir_json = self.reg_load(*a0)?.to_string();
|
Ok(())
|
||||||
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()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
("hostbridge", "extern_invoke") => {
|
("hostbridge", "extern_invoke") => {
|
||||||
// hostbridge.extern_invoke(name, method, args?)
|
// hostbridge.extern_invoke(name, method, args?)
|
||||||
@ -215,9 +201,8 @@ impl MirInterpreter {
|
|||||||
if let Some(s) = first_arg_str {
|
if let Some(s) = first_arg_str {
|
||||||
match crate::host_providers::mir_builder::program_json_to_mir_json(&s) {
|
match crate::host_providers::mir_builder::program_json_to_mir_json(&s) {
|
||||||
Ok(out) => {
|
Ok(out) => {
|
||||||
if let Some(d) = dst {
|
let patched = Self::ensure_mir_json_version_field(&out);
|
||||||
self.regs.insert(d, VMValue::String(out));
|
if let Some(d) = dst { self.regs.insert(d, VMValue::String(patched)); }
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(e) => Err(VMError::InvalidInstruction(format!(
|
Err(e) => Err(VMError::InvalidInstruction(format!(
|
||||||
|
|||||||
@ -23,6 +23,7 @@ mod boxes_void_guards;
|
|||||||
mod call_resolution;
|
mod call_resolution;
|
||||||
mod calls;
|
mod calls;
|
||||||
mod externals;
|
mod externals;
|
||||||
|
mod extern_provider;
|
||||||
mod memory;
|
mod memory;
|
||||||
mod misc;
|
mod misc;
|
||||||
|
|
||||||
|
|||||||
@ -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("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("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("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("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("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"))
|
.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::<String>("parser").map(|s| s == "ny").unwrap_or(false),
|
parser_ny: matches.get_one::<String>("parser").map(|s| s == "ny").unwrap_or(false),
|
||||||
ny_parser_pipe: matches.get_flag("ny-parser-pipe"),
|
ny_parser_pipe: matches.get_flag("ny-parser-pipe"),
|
||||||
json_file: matches.get_one::<String>("json-file").cloned(),
|
json_file: matches.get_one::<String>("json-file").cloned(),
|
||||||
|
mir_json_file: matches.get_one::<String>("mir-json-file").cloned(),
|
||||||
build_path: matches.get_one::<String>("build").cloned(),
|
build_path: matches.get_one::<String>("build").cloned(),
|
||||||
build_app: matches.get_one::<String>("build-app").cloned(),
|
build_app: matches.get_one::<String>("build-app").cloned(),
|
||||||
build_out: matches.get_one::<String>("build-out").cloned(),
|
build_out: matches.get_one::<String>("build-out").cloned(),
|
||||||
|
|||||||
@ -68,6 +68,7 @@ pub struct ParserPipeConfig {
|
|||||||
pub parser_ny: bool,
|
pub parser_ny: bool,
|
||||||
pub ny_parser_pipe: bool,
|
pub ny_parser_pipe: bool,
|
||||||
pub json_file: Option<String>,
|
pub json_file: Option<String>,
|
||||||
|
pub mir_json_file: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|||||||
@ -50,6 +50,7 @@ pub struct CliConfig {
|
|||||||
pub parser_ny: bool,
|
pub parser_ny: bool,
|
||||||
pub ny_parser_pipe: bool,
|
pub ny_parser_pipe: bool,
|
||||||
pub json_file: Option<String>,
|
pub json_file: Option<String>,
|
||||||
|
pub mir_json_file: Option<String>,
|
||||||
pub gc_mode: Option<String>,
|
pub gc_mode: Option<String>,
|
||||||
pub build_path: Option<String>,
|
pub build_path: Option<String>,
|
||||||
pub build_app: Option<String>,
|
pub build_app: Option<String>,
|
||||||
@ -128,6 +129,7 @@ impl CliConfig {
|
|||||||
parser_ny: self.parser_ny,
|
parser_ny: self.parser_ny,
|
||||||
ny_parser_pipe: self.ny_parser_pipe,
|
ny_parser_pipe: self.ny_parser_pipe,
|
||||||
json_file: self.json_file.clone(),
|
json_file: self.json_file.clone(),
|
||||||
|
mir_json_file: self.mir_json_file.clone(),
|
||||||
},
|
},
|
||||||
gc_mode: self.gc_mode.clone(),
|
gc_mode: self.gc_mode.clone(),
|
||||||
compile_wasm: self.compile_wasm,
|
compile_wasm: self.compile_wasm,
|
||||||
@ -184,6 +186,7 @@ impl Default for CliConfig {
|
|||||||
parser_ny: false,
|
parser_ny: false,
|
||||||
ny_parser_pipe: false,
|
ny_parser_pipe: false,
|
||||||
json_file: None,
|
json_file: None,
|
||||||
|
mir_json_file: None,
|
||||||
build_path: None,
|
build_path: None,
|
||||||
build_app: None,
|
build_app: None,
|
||||||
build_out: None,
|
build_out: None,
|
||||||
|
|||||||
@ -72,6 +72,9 @@ pub mod using; // using resolver scaffolding (Phase 15)
|
|||||||
// Host providers (extern bridge for Hako boxes)
|
// Host providers (extern bridge for Hako boxes)
|
||||||
pub mod host_providers;
|
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/`.
|
// Expose the macro engine module under a raw identifier; the source lives under `src/macro/`.
|
||||||
#[path = "macro/mod.rs"]
|
#[path = "macro/mod.rs"]
|
||||||
pub mod r#macro;
|
pub mod r#macro;
|
||||||
|
|||||||
@ -44,7 +44,7 @@ impl MirBuilder {
|
|||||||
// incoming の predecessor は "実際に merge に遷移してくる出口ブロック" を使用する
|
// incoming の predecessor は "実際に merge に遷移してくる出口ブロック" を使用する
|
||||||
let mut inputs: Vec<(super::BasicBlockId, super::ValueId)> = Vec::new();
|
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(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() {
|
match inputs.len() {
|
||||||
0 => {}
|
0 => {}
|
||||||
1 => {
|
1 => {
|
||||||
@ -77,7 +77,7 @@ impl MirBuilder {
|
|||||||
.unwrap_or(*pre_val);
|
.unwrap_or(*pre_val);
|
||||||
let mut inputs: Vec<(super::BasicBlockId, super::ValueId)> = Vec::new();
|
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(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() {
|
match inputs.len() {
|
||||||
0 => {}
|
0 => {}
|
||||||
1 => {
|
1 => {
|
||||||
@ -146,7 +146,7 @@ impl MirBuilder {
|
|||||||
// Build inputs from reachable predecessors only
|
// Build inputs from reachable predecessors only
|
||||||
let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new();
|
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(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() {
|
match inputs.len() {
|
||||||
0 => {}
|
0 => {}
|
||||||
1 => {
|
1 => {
|
||||||
@ -169,7 +169,7 @@ impl MirBuilder {
|
|||||||
// No variable assignment pattern detected – just emit Phi for expression result
|
// No variable assignment pattern detected – just emit Phi for expression result
|
||||||
let mut inputs: Vec<(BasicBlockId, ValueId)> = Vec::new();
|
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(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() {
|
match inputs.len() {
|
||||||
0 => { /* leave result_val as fresh, but unused; synthesize void */
|
0 => { /* leave result_val as fresh, but unused; synthesize void */
|
||||||
let v = crate::mir::builder::emission::constant::emit_void(self);
|
let v = crate::mir::builder::emission::constant::emit_void(self);
|
||||||
|
|||||||
@ -178,8 +178,9 @@ pub fn merge_modified_at_merge_with<O: PhiMergeOps>(
|
|||||||
|
|
||||||
// Build incoming pairs from reachable predecessors only
|
// Build incoming pairs from reachable predecessors only
|
||||||
let mut inputs: Vec<(crate::mir::BasicBlockId, ValueId)> = Vec::new();
|
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(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() {
|
match inputs.len() {
|
||||||
0 => {}
|
0 => {}
|
||||||
|
|||||||
@ -42,6 +42,47 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
|||||||
|
|
||||||
// Direct v0 bridge when requested via CLI/env
|
// Direct v0 bridge when requested via CLI/env
|
||||||
let groups = runner.config.as_groups();
|
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
|
let use_ny_parser = groups.parser.parser_ny
|
||||||
|| std::env::var("NYASH_USE_NY_PARSER").ok().as_deref() == Some("1");
|
|| std::env::var("NYASH_USE_NY_PARSER").ok().as_deref() == Some("1");
|
||||||
if use_ny_parser {
|
if use_ny_parser {
|
||||||
@ -236,10 +277,13 @@ impl NyashRunner {
|
|||||||
if let Some(_sb) = result.as_any().downcast_ref::<StringBox>() {
|
if let Some(_sb) = result.as_any().downcast_ref::<StringBox>() {
|
||||||
return 0; // strings do not define rc semantics yet
|
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::<IntegerBox>() { return to_rc(ib.value); }
|
||||||
|
if let Some(bb) = result.as_any().downcast_ref::<BoolBox>() { return if bb.value { 1 } else { 0 }; }
|
||||||
|
if let Some(fb) = result.as_any().downcast_ref::<FloatBox>() { return to_rc(fb.value as i64); }
|
||||||
|
if let Some(_sb) = result.as_any().downcast_ref::<StringBox>() { return 0; }
|
||||||
|
0
|
||||||
}
|
}
|
||||||
Err(_) => 1,
|
Err(_) => 1,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,14 @@ pub(super) fn lower_loop_stmt(
|
|||||||
loop_stack: &mut Vec<LoopContext>,
|
loop_stack: &mut Vec<LoopContext>,
|
||||||
env: &BridgeEnv,
|
env: &BridgeEnv,
|
||||||
) -> Result<BasicBlockId, String> {
|
) -> Result<BasicBlockId, String> {
|
||||||
|
// 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 cond_bb = new_block(f);
|
||||||
let body_bb = new_block(f);
|
let body_bb = new_block(f);
|
||||||
let exit_bb = new_block(f);
|
let exit_bb = new_block(f);
|
||||||
|
|||||||
@ -3,6 +3,26 @@ use crate::mir::{
|
|||||||
BasicBlock, BasicBlockId, ConstValue, EffectMask, MirInstruction, MirType, ValueId,
|
BasicBlock, BasicBlockId, ConstValue, EffectMask, MirInstruction, MirType, ValueId,
|
||||||
};
|
};
|
||||||
use serde_json::Value;
|
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.
|
/// Try to parse MIR JSON v1 schema into a MIR module.
|
||||||
/// Returns Ok(None) when the input is not v1 (schema_version missing).
|
/// 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<Option<MirModule>, String> {
|
|||||||
func_name
|
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 {
|
block_ref.add_instruction(MirInstruction::Const {
|
||||||
dst: ValueId::new(dst),
|
dst: ValueId::new(dst),
|
||||||
value: const_val,
|
value: const_val,
|
||||||
@ -255,11 +275,19 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
|||||||
}
|
}
|
||||||
"mir_call" => {
|
"mir_call" => {
|
||||||
// Minimal v1 mir_call support (Global/Method/Constructor/Extern/Value + Closure creation)
|
// 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));
|
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<ValueId> = Vec::new();
|
let mut argv: Vec<ValueId> = 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 {
|
for a in arr {
|
||||||
let id = a.as_u64().ok_or_else(|| format!(
|
let id = a.as_u64().ok_or_else(|| format!(
|
||||||
"mir_call arg must be integer value id in function '{}'",
|
"mir_call arg must be integer value id in function '{}'",
|
||||||
@ -268,8 +296,11 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
|||||||
argv.push(ValueId::new(id));
|
argv.push(ValueId::new(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// callee: only Global(name) supported here
|
// callee: support Global/Method/Extern/Value/Closure/Constructor (minimal)
|
||||||
let callee_obj = inst.get("callee").ok_or_else(|| {
|
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)
|
format!("mir_call missing callee in function '{}'", func_name)
|
||||||
})?;
|
})?;
|
||||||
let ctype = callee_obj
|
let ctype = callee_obj
|
||||||
@ -306,10 +337,31 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
|||||||
func: ValueId::new(0),
|
func: ValueId::new(0),
|
||||||
callee: Some(crate::mir::definitions::Callee::Global(mapped)),
|
callee: Some(crate::mir::definitions::Callee::Global(mapped)),
|
||||||
args: argv,
|
args: argv,
|
||||||
effects: EffectMask::PURE,
|
effects,
|
||||||
});
|
});
|
||||||
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
|
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" => {
|
"Method" => {
|
||||||
// receiver: required u64, method: string, box_name: optional
|
// receiver: required u64, method: string, box_name: optional
|
||||||
let method = callee_obj
|
let method = callee_obj
|
||||||
@ -342,63 +394,96 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
|||||||
certainty: crate::mir::definitions::call_unified::TypeCertainty::Known,
|
certainty: crate::mir::definitions::call_unified::TypeCertainty::Known,
|
||||||
}),
|
}),
|
||||||
args: argv,
|
args: argv,
|
||||||
effects: EffectMask::PURE,
|
effects,
|
||||||
});
|
});
|
||||||
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
|
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
|
||||||
}
|
}
|
||||||
"Closure" => {
|
"Closure" => {
|
||||||
// Closure creation (NewClosure equivalent)
|
// Two shapes are seen in the wild:
|
||||||
// Requires dst; accepts optional params[], captures[[name, id]...], me_capture
|
// 1) NewClosure-style descriptor (params/captures/me_capture present) → NewClosure
|
||||||
let dst = dst_opt.ok_or_else(|| format!(
|
// 2) Value-style descriptor (func present, optionally captures array) → Call(Callee::Value)
|
||||||
"mir_call Closure requires dst in function '{}'",
|
let has_new_fields = callee_obj.get("params").is_some()
|
||||||
func_name
|
|| callee_obj.get("captures").is_some()
|
||||||
))?;
|
|| callee_obj.get("me_capture").is_some();
|
||||||
// params: array of strings (optional)
|
if has_new_fields {
|
||||||
let mut params: Vec<String> = Vec::new();
|
// Closure creation (NewClosure equivalent)
|
||||||
if let Some(arr) = callee_obj.get("params").and_then(Value::as_array) {
|
let dst = dst_opt.ok_or_else(|| format!(
|
||||||
for p in arr {
|
"mir_call Closure requires dst in function '{}'",
|
||||||
let s = p.as_str().ok_or_else(|| format!(
|
func_name
|
||||||
"mir_call Closure params must be strings in function '{}'",
|
))?;
|
||||||
func_name
|
// params: array of strings (optional)
|
||||||
))?;
|
let mut params: Vec<String> = Vec::new();
|
||||||
params.push(s.to_string());
|
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!(
|
||||||
// captures: array of [name, id]
|
"mir_call Closure params must be strings in function '{}'",
|
||||||
let mut captures: Vec<(String, ValueId)> = Vec::new();
|
func_name
|
||||||
if let Some(arr) = callee_obj.get("captures").and_then(Value::as_array) {
|
))?;
|
||||||
for e in arr {
|
params.push(s.to_string());
|
||||||
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)));
|
|
||||||
}
|
}
|
||||||
|
// 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" => {
|
"Constructor" => {
|
||||||
// box_type: string, dst: required
|
// box_type: string, dst: required
|
||||||
@ -453,41 +538,11 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
|||||||
func: ValueId::new(0),
|
func: ValueId::new(0),
|
||||||
callee: Some(crate::mir::definitions::Callee::Value(ValueId::new(fid))),
|
callee: Some(crate::mir::definitions::Callee::Value(ValueId::new(fid))),
|
||||||
args: argv,
|
args: argv,
|
||||||
effects: EffectMask::PURE,
|
effects,
|
||||||
});
|
|
||||||
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: <u64>, 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,
|
|
||||||
});
|
});
|
||||||
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
|
if let Some(d) = dst_opt { max_value_id = max_value_id.max(d.as_u32() + 1); }
|
||||||
}
|
}
|
||||||
|
// (no duplicate Closure arm; handled above)
|
||||||
other => {
|
other => {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"unsupported callee type '{}' in mir_call (Gate-C v1 bridge)",
|
"unsupported callee type '{}' in mir_call (Gate-C v1 bridge)",
|
||||||
@ -515,44 +570,101 @@ pub fn try_parse_v1_to_module(json: &str) -> Result<Option<MirModule>, String> {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn parse_const_value(value_obj: &Value) -> Result<ConstValue, String> {
|
fn parse_const_value(value_obj: &Value) -> Result<ConstValue, String> {
|
||||||
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
|
value_obj
|
||||||
.get("value")
|
.get("value")
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or_else(|| "const value missing numeric value".to_string())?,
|
.ok_or_else(|| "const value missing 'value' field".to_string())?,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(Value::String("i64".to_string()), value_obj.clone())
|
(None, value_obj.clone())
|
||||||
};
|
};
|
||||||
|
|
||||||
match type_str {
|
// String type descriptor
|
||||||
Value::String(s) => match s.as_str() {
|
if let Some(Value::String(s)) = type_desc.as_ref() {
|
||||||
|
match s.as_str() {
|
||||||
|
// Integer
|
||||||
"i64" | "int" => {
|
"i64" | "int" => {
|
||||||
let val = raw_val
|
let val = raw_val
|
||||||
.as_i64()
|
.as_i64()
|
||||||
.ok_or_else(|| "const value expected integer".to_string())?;
|
.ok_or_else(|| "const value expected integer".to_string())?;
|
||||||
Ok(ConstValue::Integer(val))
|
return Ok(ConstValue::Integer(val));
|
||||||
}
|
}
|
||||||
other => Err(format!(
|
// Float
|
||||||
"unsupported const type '{}' in Gate-C v1 bridge",
|
"f64" | "float" => {
|
||||||
other
|
let val = raw_val
|
||||||
)),
|
.as_f64()
|
||||||
},
|
.ok_or_else(|| "const value expected float".to_string())?;
|
||||||
Value::Object(obj) => {
|
return Ok(ConstValue::Float(val));
|
||||||
if let Some(Value::String(kind)) = obj.get("kind") {
|
}
|
||||||
if kind == "handle" {
|
// Bool (allow explicit bool schema even if current emitter uses i64)
|
||||||
if let Some(Value::String(box_type)) = obj.get("box_type") {
|
"i1" | "bool" => {
|
||||||
return Err(format!(
|
let b = match raw_val {
|
||||||
"unsupported const handle type '{}' in Gate-C v1 bridge",
|
Value::Bool(v) => v,
|
||||||
box_type
|
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()),
|
_ => Err("const value has unsupported type descriptor".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
63
src/runner/mir_json/common.rs
Normal file
63
src/runner/mir_json/common.rs
Normal file
@ -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<ConstValue, String> {
|
||||||
|
// 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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
162
src/runner/mir_json_v0.rs
Normal file
162
src/runner/mir_json_v0.rs
Normal file
@ -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<MirModule, String> {
|
||||||
|
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<u64, String> {
|
||||||
|
node.get(key).and_then(Value::as_u64).ok_or_else(|| format!("{} missing field '{}'", context, key))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_const_value(value_obj: &Value) -> Result<ConstValue, String> {
|
||||||
|
// 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<crate::mir::types::CompareOp, String> {
|
||||||
|
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)),
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -23,6 +23,8 @@ mod demos;
|
|||||||
mod dispatch;
|
mod dispatch;
|
||||||
pub mod json_v0_bridge;
|
pub mod json_v0_bridge;
|
||||||
mod json_v1_bridge;
|
mod json_v1_bridge;
|
||||||
|
pub mod mir_json { pub mod common; }
|
||||||
|
mod mir_json_v0;
|
||||||
pub mod mir_json_emit;
|
pub mod mir_json_emit;
|
||||||
pub mod modes;
|
pub mod modes;
|
||||||
mod pipe_io;
|
mod pipe_io;
|
||||||
@ -87,6 +89,29 @@ impl NyashRunner {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let groups = self.config.as_groups();
|
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
|
// Early: build
|
||||||
if let Some(cfg_path) = groups.build.path.clone() {
|
if let Some(cfg_path) = groups.build.path.clone() {
|
||||||
if let Err(e) = self.run_build_mvp(&cfg_path) {
|
if let Err(e) = self.run_build_mvp(&cfg_path) {
|
||||||
|
|||||||
@ -496,11 +496,38 @@ pub fn parse_preludes_to_asts(
|
|||||||
eprintln!("[strip-debug] Parse FAILED for: {} (debug={})", prelude_path, debug);
|
eprintln!("[strip-debug] Parse FAILED for: {} (debug={})", prelude_path, debug);
|
||||||
if debug {
|
if debug {
|
||||||
eprintln!("[strip-debug] Error: {}", e);
|
eprintln!("[strip-debug] Error: {}", e);
|
||||||
|
let es = format!("{}", e);
|
||||||
let lines: Vec<&str> = clean_src.lines().collect();
|
let lines: Vec<&str> = clean_src.lines().collect();
|
||||||
eprintln!("[strip-debug] Total lines: {}", lines.len());
|
eprintln!("[strip-debug] Total lines: {}", lines.len());
|
||||||
eprintln!("[strip-debug] Lines 15-25:");
|
// Try to extract error line number (e.g., "at line 451") and show local context
|
||||||
for (idx, line) in lines.iter().enumerate().skip(14).take(11) {
|
let mut printed = false;
|
||||||
eprintln!(" {:3}: {}", idx + 1, line);
|
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);
|
eprintln!("[strip-debug] Full clean_src:\n{}\n---", clean_src);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -180,13 +180,29 @@ pub(super) fn resolve_using_target(
|
|||||||
}
|
}
|
||||||
return Ok(hit);
|
return Ok(hit);
|
||||||
}
|
}
|
||||||
// Resolve aliases early (provided map) — and then recursively resolve the target
|
// Resolve aliases early(推移的に)
|
||||||
if let Some(v) = aliases.get(tgt) {
|
// - ループ/循環を検出して早期エラー
|
||||||
if trace {
|
// - 10段まで(防衛的)
|
||||||
crate::runner::trace::log(format!("[using/resolve] alias '{}' -> '{}'", tgt, v));
|
if let Some(_) = aliases.get(tgt) {
|
||||||
|
use std::collections::HashSet;
|
||||||
|
let mut seen: HashSet<String> = 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
|
// Recurse once into final target to materialize path/token
|
||||||
let rec = resolve_using_target(v, false, modules, using_paths, aliases, packages, context_dir, strict, verbose)?;
|
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());
|
crate::runner::box_index::cache_put(&key, rec.clone());
|
||||||
return Ok(rec);
|
return Ok(rec);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,8 @@ export SMOKES_START_TIME=$(date +%s.%N)
|
|||||||
export SMOKES_TEST_COUNT=0
|
export SMOKES_TEST_COUNT=0
|
||||||
export SMOKES_PASS_COUNT=0
|
export SMOKES_PASS_COUNT=0
|
||||||
export SMOKES_FAIL_COUNT=0
|
export SMOKES_FAIL_COUNT=0
|
||||||
|
export SMOKES_INCLUDE_SKIP_COUNT=0
|
||||||
|
declare -a SMOKES_INCLUDE_SKIP_LIST=()
|
||||||
|
|
||||||
# 色定義(重複回避)
|
# 色定義(重複回避)
|
||||||
if [ -z "${RED:-}" ]; then
|
if [ -z "${RED:-}" ]; then
|
||||||
@ -216,7 +218,29 @@ run_nyash_vm() {
|
|||||||
fi
|
fi
|
||||||
# Optional hint for include lines when preinclude is OFF
|
# 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
|
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
|
fi
|
||||||
NYASH_VM_USE_PY="$USE_PYVM" NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 \
|
NYASH_VM_USE_PY="$USE_PYVM" NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN=1 \
|
||||||
NYASH_DISABLE_NY_COMPILER=1 HAKO_DISABLE_NY_COMPILER=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 JSON rc using selected primary (Core or Hakorune VM)
|
||||||
verify_mir_rc() {
|
verify_mir_rc() {
|
||||||
local json_path="$1"
|
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 [ "$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
|
# Build a tiny driver to call MiniVmEntryBox.run_min with JSON literal embedded
|
||||||
if [ ! -f "$json_path" ]; then
|
if [ ! -f "$json_path" ]; then
|
||||||
echo "[FAIL] verify_mir_rc: json not found: $json_path" >&2
|
echo "[FAIL] verify_mir_rc: json not found: $json_path" >&2
|
||||||
@ -256,7 +308,7 @@ static box Main { method main(args) {
|
|||||||
HCODE
|
HCODE
|
||||||
)
|
)
|
||||||
code="${code/__MIR_JSON__/$json_literal}"
|
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() {
|
build_and_run_driver_include() {
|
||||||
local inc_path="$1"
|
local inc_path="$1"
|
||||||
@ -271,7 +323,7 @@ static box Main { method main(args) {
|
|||||||
HCODE
|
HCODE
|
||||||
)
|
)
|
||||||
code="${code/__MIR_JSON__/$json_literal}"
|
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
|
# Try alias header first; fallback to dev-file header; final fallback: include+preinclude
|
||||||
local out
|
local out
|
||||||
@ -289,11 +341,30 @@ HCODE
|
|||||||
return $n
|
return $n
|
||||||
fi
|
fi
|
||||||
# Fallback: core primary when MiniVM resolution is unavailable
|
# 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
|
if grep -q '"functions"' "$json_path" 2>/dev/null && grep -q '"blocks"' "$json_path" 2>/dev/null; then
|
||||||
return $?
|
local json_literal3; json_literal3="$(jq -Rs . < "$json_path")"
|
||||||
|
local code=$(cat <<HCODE
|
||||||
|
include "lang/src/vm/core/dispatcher.hako"
|
||||||
|
static box Main { method main(args) {
|
||||||
|
local j = __MIR_JSON__
|
||||||
|
local r = NyVmDispatcher.run(j)
|
||||||
|
print("" + r)
|
||||||
|
return r
|
||||||
|
} }
|
||||||
|
HCODE
|
||||||
|
)
|
||||||
|
code="${code/__MIR_JSON__/$json_literal3}"
|
||||||
|
local tmpwrap="/tmp/hako_core_wrap_$$.nyash"
|
||||||
|
echo "$code" > "$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
|
else
|
||||||
NYASH_GATE_C_CORE=1 HAKO_GATE_C_CORE=1 "$NYASH_BIN" --json-file "$json_path" >/dev/null 2>&1
|
# Core primary: detect MIR(JSON) vs Program(JSON v0)
|
||||||
return $?
|
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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,6 +465,14 @@ print_summary() {
|
|||||||
echo "Duration: ${total_duration}s"
|
echo "Duration: ${total_duration}s"
|
||||||
echo ""
|
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
|
if [ $SMOKES_FAIL_COUNT -eq 0 ]; then
|
||||||
log_success "All tests passed! ✨"
|
log_success "All tests passed! ✨"
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@ -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
|
||||||
@ -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
|
||||||
@ -32,7 +32,7 @@ fi
|
|||||||
|
|
||||||
# 2) Core‑Direct exec and rc check (expect rc=10)
|
# 2) Core‑Direct exec and rc check (expect rc=10)
|
||||||
set +e
|
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=$?
|
rc=$?
|
||||||
set -e
|
set -e
|
||||||
rm -f "$tmp_hako" "$tmp_json" || true
|
rm -f "$tmp_hako" "$tmp_json" || true
|
||||||
|
|||||||
@ -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
|
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
|
source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2
|
||||||
|
|
||||||
tmp_hako="/tmp/mirbuilder_emit_loop_$$.hako"
|
tmp_json="/tmp/program_loop_$$.json"
|
||||||
tmp_json="/tmp/mirbuilder_emit_loop_$$.json"
|
cat > "$tmp_json" <<'JSON'
|
||||||
|
{
|
||||||
cat > "$tmp_hako" <<'HAKO'
|
"version": 0,
|
||||||
static box Main { method main(args) {
|
"kind": "Program",
|
||||||
// Canonical loop Program(JSON): i=0; s=0; loop (i<3) { s=s+1; i=i+1 }; return s;
|
"body": [
|
||||||
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\"}}]}";
|
{ "type":"Local", "name":"i", "expr": {"type":"Int","value":0} },
|
||||||
local arr = new ArrayBox(); arr.push(j)
|
{ "type":"Local", "name":"s", "expr": {"type":"Int","value":0} },
|
||||||
local out = hostbridge.extern_invoke("env.mirbuilder", "emit", arr)
|
{ "type":"Loop",
|
||||||
if out == null { return 1 }
|
"cond": {"type":"Compare","op":"<","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":3}},
|
||||||
print("" + out)
|
"body": [
|
||||||
return 0
|
{ "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}} }
|
||||||
HAKO
|
]
|
||||||
|
},
|
||||||
|
{ "type":"Return", "expr": {"type":"Var","name":"s"} }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
JSON
|
||||||
|
|
||||||
set +e
|
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=$?
|
HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1
|
||||||
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
|
|
||||||
rc=$?
|
rc=$?
|
||||||
set -e
|
set -e
|
||||||
rm -f "$tmp_hako" "$tmp_json" || true
|
rm -f "$tmp_json" || true
|
||||||
|
|
||||||
if [ "$rc" -eq 3 ]; then
|
if [ "$rc" -eq 3 ]; then
|
||||||
echo "[PASS] mirbuilder_internal_loop_core_exec_canary_vm"
|
echo "[PASS] mirbuilder_internal_loop_core_exec_canary_vm"
|
||||||
|
|||||||
@ -1,44 +1,31 @@
|
|||||||
#!/bin/bash
|
#!/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
|
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
|
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
|
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/program_loop_count_param_$$.json"
|
||||||
tmp_json="/tmp/mirbuilder_emit_loop_count_param_$$.json"
|
cat > "$tmp_json" <<'JSON'
|
||||||
|
{
|
||||||
cat > "$tmp_hako" <<'HAKO'
|
"version": 0,
|
||||||
static box Main { method main(args) {
|
"kind": "Program",
|
||||||
local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" +
|
"body": [
|
||||||
"{\"type\":\"Local\",\"name\":\"i\",\"expr\":{\"type\":\"Int\",\"value\":2}}," +
|
{ "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":"Loop",
|
||||||
"{\"type\":\"Local\",\"name\":\"i\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":2}}}" +
|
"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\"}}" +
|
},
|
||||||
"]}";
|
{ "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 }
|
JSON
|
||||||
print("" + out)
|
|
||||||
return 0
|
|
||||||
} }
|
|
||||||
HAKO
|
|
||||||
|
|
||||||
set +e
|
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=$?
|
HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1
|
||||||
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
|
|
||||||
rc=$?
|
rc=$?
|
||||||
set -e
|
set -e
|
||||||
rm -f "$tmp_hako" "$tmp_json" || true
|
rm -f "$tmp_json" || true
|
||||||
|
|
||||||
if [ "$rc" -eq 6 ]; then
|
if [ "$rc" -eq 6 ]; then
|
||||||
echo "[PASS] mirbuilder_internal_loop_count_param_core_exec_canary_vm"
|
echo "[PASS] mirbuilder_internal_loop_count_param_core_exec_canary_vm"
|
||||||
|
|||||||
@ -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
|
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
|
source "$ROOT/tools/smokes/v2/lib/test_runner.sh"; require_env || exit 2
|
||||||
|
|
||||||
tmp_hako="/tmp/mirbuilder_emit_loop_bc_$$.hako"
|
tmp_json="/tmp/program_loop_sum_bc_$$.json"
|
||||||
tmp_json="/tmp/mirbuilder_emit_loop_bc_$$.json"
|
cat > "$tmp_json" <<'JSON'
|
||||||
|
{
|
||||||
cat > "$tmp_hako" <<'HAKO'
|
"version": 0,
|
||||||
static box Main { method main(args) {
|
"kind": "Program",
|
||||||
// Program: i=0; s=0; loop(true){ s=s+1; if(i==4) break; if(i==2) continue; i=i+1 } return s
|
"body": [
|
||||||
local j = "{\"version\":0,\"kind\":\"Program\",\"body\":[" +
|
{ "type":"Local", "name":"i", "expr": {"type":"Int","value":0} },
|
||||||
"{\"type\":\"Local\",\"name\":\"i\",\"expr\":{\"type\":\"Int\",\"value\":0}}," +
|
{ "type":"Local", "name":"s", "expr": {"type":"Int","value":0} },
|
||||||
"{\"type\":\"Local\",\"name\":\"s\",\"expr\":{\"type\":\"Int\",\"value\":0}}," +
|
{ "type":"Loop",
|
||||||
"{\"type\":\"Loop\",\"cond\":{\"type\":\"Compare\",\"op\":\"<\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":5}},\"body\":[" +
|
"cond": {"type":"Compare","op":"<","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":5}},
|
||||||
"{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"==\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":4}},\"then\":[{\"type\":\"Break\"}]}," +
|
"body": [
|
||||||
"{\"type\":\"If\",\"cond\":{\"type\":\"Compare\",\"op\":\"==\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":2}},\"then\":[{\"type\":\"Continue\"}]}," +
|
{ "type":"If", "cond": {"type":"Compare","op":"==","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":2}},
|
||||||
"{\"type\":\"Expr\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Var\",\"name\":\"s\"},\"rhs\":{\"type\":\"Var\",\"name\":\"i\"}}}," +
|
"then": [ ],
|
||||||
"{\"type\":\"Expr\",\"expr\":{\"type\":\"Binary\",\"op\":\"+\",\"lhs\":{\"type\":\"Var\",\"name\":\"i\"},\"rhs\":{\"type\":\"Int\",\"value\":1}}}" +
|
"else": [ { "type":"Local", "name":"s", "expr": {"type":"Binary","op":"+","lhs":{"type":"Var","name":"s"},"rhs":{"type":"Var","name":"i"}} } ]
|
||||||
"]}," +
|
},
|
||||||
"{\"type\":\"Return\",\"expr\":{\"type\":\"Var\",\"name\":\"s\"}}" +
|
{ "type":"Local", "name":"i", "expr": {"type":"Binary","op":"+","lhs":{"type":"Var","name":"i"},"rhs":{"type":"Int","value":1}} }
|
||||||
"]}";
|
]
|
||||||
local arr = new ArrayBox(); arr.push(j)
|
},
|
||||||
local out = hostbridge.extern_invoke("env.mirbuilder", "emit", arr)
|
{ "type":"Return", "expr": {"type":"Var","name":"s"} }
|
||||||
if out == null { return 1 }
|
]
|
||||||
print("" + out)
|
}
|
||||||
return 0
|
JSON
|
||||||
} }
|
|
||||||
HAKO
|
|
||||||
|
|
||||||
set +e
|
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=$?
|
HAKO_VERIFY_PRIMARY=core verify_mir_rc "$tmp_json" >/dev/null 2>&1
|
||||||
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
|
|
||||||
rc=$?
|
rc=$?
|
||||||
set -e
|
set -e
|
||||||
rm -f "$tmp_hako" "$tmp_json" || true
|
rm -f "$tmp_json" || true
|
||||||
|
|
||||||
if [ "$rc" -eq 8 ]; then
|
if [ "$rc" -eq 8 ]; then
|
||||||
echo "[PASS] mirbuilder_internal_loop_sum_bc_core_exec_canary_vm"
|
echo "[PASS] mirbuilder_internal_loop_sum_bc_core_exec_canary_vm"
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
@ -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" <<JSON
|
||||||
|
{
|
||||||
|
"schema_version": "1.0",
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"name": "main",
|
||||||
|
"blocks": [
|
||||||
|
{ "id": 0, "instructions": [
|
||||||
|
{"op":"const","dst":0, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "env.mirbuilder"}},
|
||||||
|
{"op":"const","dst":1, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": "emit"}},
|
||||||
|
{"op":"mir_call","dst":2, "callee": {"type":"Constructor","box_type":"ArrayBox"}, "args": [], "effects": [] },
|
||||||
|
{"op":"const","dst":3, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": ${prog_v0_quoted}}},
|
||||||
|
{"op":"mir_call", "callee": {"type":"Method","box_name":"ArrayBox","method":"push","receiver":2}, "args": [3], "effects": [] },
|
||||||
|
{"op":"mir_call","dst":4, "callee": {"type":"Extern","name":"hostbridge.extern_invoke"}, "args": [0,1,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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
@ -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" <<JSON
|
||||||
|
{
|
||||||
|
"schema_version": "1.0",
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"name": "main",
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"instructions": [
|
||||||
|
{"op":"const","dst":0, "value": {"type": {"kind":"handle","box_type":"StringBox"}, "value": ${prog_v0_quoted}}},
|
||||||
|
{"op":"mir_call","dst":1, "callee": {"type":"Extern","name":"env.mirbuilder.emit"}, "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
|
||||||
|
|
||||||
|
# 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
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
@ -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
|
||||||
|
|
||||||
@ -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
|
||||||
@ -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
|
||||||
Reference in New Issue
Block a user