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:
nyash-codex
2025-11-03 23:21:48 +09:00
parent a4f30ae827
commit 06a729ff40
67 changed files with 3340 additions and 1520 deletions

View File

@ -64,16 +64,16 @@ static box InstructionScannerBox {
if me._has_key(obj, "lhs") == 1 and me._has_key(obj, "rhs") == 1 { return "compare" }
if me._has_key(obj, "cond") == 1 { return "branch" }
if me._has_key(obj, "target") == 1 { return "jump" }
// Const fallback (typed value object) — tolerant to spaces
if obj.indexOf("\"type\":\"i64\"") >= 0 { return "const" }
// Detect explicit ret first
if obj.indexOf("\"op\":\"ret\"") >= 0 { return "ret" }
// Detect v1-style Ret without op key: presence of top-level "value" and absence of other discriminator keys
// Detect v1-style Ret without op key: must have top-level "value" and not have other discriminators including explicit op/dst
if me._has_key(obj, "value") == 1 {
if me._has_key(obj, "lhs") == 0 && me._has_key(obj, "rhs") == 0 && me._has_key(obj, "cond") == 0 && me._has_key(obj, "target") == 0 && me._has_key(obj, "src") == 0 && me._has_key(obj, "op_kind") == 0 {
if me._has_key(obj, "op") == 0 && me._has_key(obj, "dst") == 0 && me._has_key(obj, "lhs") == 0 && me._has_key(obj, "rhs") == 0 && me._has_key(obj, "cond") == 0 && me._has_key(obj, "target") == 0 && me._has_key(obj, "src") == 0 && me._has_key(obj, "op_kind") == 0 {
return "ret"
}
}
// Const fallback (typed value object)
if obj.indexOf("\"value\":{\"type\":\"i64\"") >= 0 { return "const" }
return ""
}

View File

@ -0,0 +1,52 @@
// mini_map_box.hako — MiniMap
// Responsibility: minimal string-backed map for MiniVM 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 } }

View 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 MiniVM 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 } }

View File

@ -1,6 +1,6 @@
// mini_vm_entry.hako — MiniVmEntryBox
// Thin entry wrapper to stabilize static call names for smokes and tools.
using "lang/src/vm/boxes/mir_vm_min.hako" as MirVmMin
using selfhost.vm.mir_min as MirVmMin
static box MiniVmEntryBox {

View 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 MiniVM 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
}
// Perreceiver 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 } }

View File

@ -5,8 +5,12 @@ using selfhost.shared.common.string_helpers as StringHelpers
using selfhost.shared.common.string_ops as StringOps
using selfhost.shared.json.utils.json_frag as JsonFragBox
using selfhost.shared.json.core.json_cursor as JsonCursorBox
using selfhost.vm.helpers.mini_map as MiniMap
using selfhost.vm.helpers.operator as OperatorBox
using selfhost.vm.helpers.mini_mir_v1_scan as MiniMirV1Scan
using selfhost.vm.helpers.compare_ops as CompareOpsBox
using selfhost.vm.hakorune-vm.json_v1_reader as JsonV1ReaderBox
using selfhost.vm.helpers.mir_call_v1_handler as MirCallV1HandlerBox
using selfhost.vm.helpers.compare_scan as CompareScanBox
using selfhost.vm.helpers.phi_apply as PhiApplyBox
using selfhost.vm.helpers.guard as GuardBox
@ -15,51 +19,6 @@ using selfhost.vm.helpers.phi_decode as PhiDecodeBox
using selfhost.vm.helpers.ret_resolve as RetResolveSimpleBox
using selfhost.vm.helpers.instruction_scanner as InstructionScannerBox
// Minimal map for MiniVM 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 MiniVM
@ -68,59 +27,18 @@ static box MirVmMin {
if msg.indexOf("[ERROR]") >= 0 { print(msg) }
}
_d(msg, trace) { if trace == 1 { print(msg) } }
_parse_callee_name(seg) {
// naive scan: '"callee":{"name":"<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). FailFast for others.
local name = me._parse_callee_name(seg)
local arg0id = me._parse_first_arg(seg)
// Support minimal externs/methods (i64 variants). Constructor is noop.
// v1: treat Constructor as noop and write dst=0 to keep SSA continuity
if seg.indexOf("\"type\":\"Constructor\"") >= 0 {
local d0 = JsonFragBox.get_int(seg, "dst"); if d0 != null { regs.setField("" + d0, "0") }
return
}
local name = MiniMirV1Scan.callee_name(seg)
local arg0id = MiniMirV1Scan.first_arg_register(seg)
if name == "" {
// Try Method callee
local mname = me._parse_method_name(seg)
local mname = MiniMirV1Scan.method_name(seg)
if mname != "" {
// Optional: minimal stateful bridge for size/len/length/push
// Enabled by HAKO_VM_MIRCALL_SIZESTATE=1 (default OFF). When OFF, emit stub tag and dst=0.
@ -129,27 +47,34 @@ static box MirVmMin {
if ("" + size_state) != "1" {
local stub = env.get("HAKO_VM_MIRCALL_STUB"); if stub == null { stub = "1" }
if ("" + stub) == "1" { print("[vm/method/stub:" + mname + "]") }
local dst0 = JsonFragBox.get_int(seg, "dst"); if dst0 != null { regs.set("" + dst0, "0") }
local dst0 = JsonFragBox.get_int(seg, "dst"); if dst0 != null { regs.setField("" + dst0, "0") }
return
}
// Stateful branch
// Keep a simple per-run length counter in regs["__vm_len"]. Default 0.
local cur_len_raw = regs.getField("__vm_len"); if cur_len_raw == null { cur_len_raw = "0" }
// Length counter: global or per-receiver depending on flag
local per_recv = env.get("HAKO_VM_MIRCALL_SIZESTATE_PER_RECV")
if per_recv == null { per_recv = "0" }
local key = "__vm_len"
if (""+per_recv) == "1" {
local rid = MiniMirV1Scan.receiver_id(seg)
if rid != null { key = "__vm_len:" + (""+rid) }
}
local cur_len_raw = regs.getField(key); if cur_len_raw == null { cur_len_raw = "0" }
local cur_len = JsonFragBox._str_to_int(cur_len_raw)
if mname == "push" {
cur_len = cur_len + 1
regs.set("__vm_len", "" + cur_len)
regs.setField(key, "" + cur_len)
// push returns void/0 in this minimal path
local d1 = JsonFragBox.get_int(seg, "dst"); if d1 != null { regs.set("" + d1, "0") }
local d1 = JsonFragBox.get_int(seg, "dst"); if d1 != null { regs.setField("" + d1, "0") }
return
}
if mname == "len" || mname == "length" || mname == "size" {
local d2 = JsonFragBox.get_int(seg, "dst"); if d2 != null { regs.set("" + d2, "" + cur_len) }
local d2 = JsonFragBox.get_int(seg, "dst"); if d2 != null { regs.setField("" + d2, "" + cur_len) }
return
}
// Others: no-op but keep stub tag for observability
print("[vm/method/stub:" + mname + "]")
local d3 = JsonFragBox.get_int(seg, "dst"); if d3 != null { regs.set("" + d3, "0") }
local d3 = JsonFragBox.get_int(seg, "dst"); if d3 != null { regs.setField("" + d3, "0") }
return
}
me._tprint("[ERROR] mir_call: missing callee")
@ -207,11 +132,101 @@ static box MirVmMin {
_load_reg(regs,id){ local v=regs.getField(""+id) if v==null {return 0} local s=""+v if me._is_numeric_str(s)==1 { return JsonFragBox._str_to_int(s) } return 0 }
// block helpers
_block_insts_start(mjson,bid){ local key="\"id\":"+me._int_to_str(bid) local p=mjson.indexOf(key) if p<0 {return -1} local q=StringOps.index_of_from(mjson,"\"instructions\":[",p) if q<0 {return -1} return q+15 }
_block_insts_start(mjson,bid){
// tolerant scan: find '"id"' then parse digits after ':' with optional spaces
local pos = 0
loop(true) {
local pid = StringOps.index_of_from(mjson, "\"id\"", pos)
if pid < 0 { return -1 }
// find ':'
local pc = StringOps.index_of_from(mjson, ":", pid)
if pc < 0 { return -1 }
local digits = JsonFragBox.read_int_from(mjson, pc + 1)
if digits != null {
local v = JsonFragBox._str_to_int(digits)
if v == bid {
// instructions array for this block
local qk = StringOps.index_of_from(mjson, "\"instructions\"", pc)
if qk < 0 { pos = pid + 1 continue }
local qb = StringOps.index_of_from(mjson, "[", qk)
if qb < 0 { pos = pid + 1 continue }
return qb
}
}
pos = pc + 1
}
}
// local copy handler
_handle_copy(seg, regs){ local dst=JsonFragBox.get_int(seg,"dst") local src=JsonFragBox.get_int(seg,"src") if dst==null || src==null {return} local v=regs.getField(""+src) regs.setField(""+dst, v) }
// ret op handler
_handle_ret_op(seg, regs, last_cmp_dst, last_cmp_val, gc_trace) {
local v = JsonFragBox.get_int(seg, "value")
if v == null {
me._tprint("[ERROR] Undefined ret value field")
return -1
}
if v == last_cmp_dst {
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
return last_cmp_val
}
local sval_raw = regs.getField(""+v)
if sval_raw != null {
local sval = "" + sval_raw
if me._is_numeric_str(sval) == 1 {
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
return me._load_reg(regs, v)
}
}
me._tprint("[ERROR] Undefined register ret: r"+me._int_to_str(v))
return -1
}
// block ret resolution helper
_resolve_block_ret(inst_seg, regs, last_cmp_dst, last_cmp_val, gc_trace) {
if last_cmp_dst >= 0 { return last_cmp_val }
// Iterate objects in this block and find an explicit ret
local scan = 0
loop(true) {
if scan >= inst_seg.length() { break }
local tup = InstructionScannerBox.next_tuple(inst_seg, scan)
if tup == "" { break }
local c1 = StringOps.index_of_from(tup, ",", 0)
local c2 = StringOps.index_of_from(tup, ",", c1+1)
if c1 < 0 || c2 < 0 { break }
local obj_start = JsonFragBox._str_to_int(tup.substring(0, c1))
local obj_end = JsonFragBox._str_to_int(tup.substring(c1+1, c2))
local op = tup.substring(c2+1, tup.length())
if op == "ret" {
local seg = inst_seg.substring(obj_start, obj_end)
me._d("[DEBUG] resolve_ret seg=" + seg, 1)
local rid = JsonFragBox.get_int(seg, "value")
if rid != null {
if rid == last_cmp_dst {
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
return last_cmp_val
}
local rv = me._load_reg(regs, rid)
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
return rv
}
}
scan = obj_end
}
return me._resolve_final_ret(inst_seg, regs, last_cmp_dst, last_cmp_val, gc_trace)
}
// final ret resolution helper
_resolve_final_ret(inst_seg, regs, last_cmp_dst, last_cmp_val, gc_trace) {
local r = RetResolveSimpleBox.resolve(inst_seg, regs, last_cmp_dst, last_cmp_val)
if r != null {
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
return r
}
return me._load_reg(regs, 1)
}
_run_min(mjson) {
// Normalize input as string to guarantee String methods availability
mjson = "" + mjson
@ -253,7 +268,85 @@ static box MirVmMin {
}
}
}
// 2) compare→ret fast-path skipped (handled by main scanner)
// 2) Light one-step fast path for pattern: const/const/compare/branch then simple ret in next block
// Evaluate block 0 and jump to the chosen block; if it directly returns a register or constant, return it.
{
// Mini fast-eval for B0
local regs0 = new MiniMap()
local scan0 = 0
local last_cmp_dst0 = -1
local last_cmp_val0 = 0
local bb_choice = -1
loop(true) {
if scan0 >= b0.length() { break }
local tup0 = InstructionScannerBox.next_tuple(b0, scan0)
if tup0 == "" { break }
local c10 = StringOps.index_of_from(tup0, ",", 0)
local c20 = StringOps.index_of_from(tup0, ",", c10+1)
if c10 < 0 || c20 < 0 { break }
local s0 = JsonFragBox._str_to_int(tup0.substring(0, c10))
local e0 = JsonFragBox._str_to_int(tup0.substring(c10+1, c20))
local op0 = tup0.substring(c20+1, tup0.length())
local seg0 = b0.substring(s0, e0)
if op0 == "const" { OpHandlersBox.handle_const(seg0, regs0) }
else { if op0 == "compare" {
// evaluate compare and stash dst
local dst0 = JsonFragBox.get_int(seg0, "dst")
local l0 = JsonFragBox.get_int(seg0, "lhs")
local r0 = JsonFragBox.get_int(seg0, "rhs")
local kind0 = JsonFragBox.get_str(seg0, "cmp")
if kind0 == "" { local sym0 = JsonFragBox.get_str(seg0, "operation"); if sym0 != "" { kind0 = CompareOpsBox.map_symbol(sym0) } else { kind0 = "Eq" } }
if dst0 != null && l0 != null && r0 != null {
local a0 = me._load_reg(regs0, l0)
local b0v = me._load_reg(regs0, r0)
local cv0 = CompareOpsBox.eval(kind0, a0, b0v)
regs0.setField(""+dst0, ""+cv0)
last_cmp_dst0 = dst0
last_cmp_val0 = cv0
}
} else { if op0 == "branch" {
local c = JsonFragBox.get_int(seg0, "cond")
local t = JsonFragBox.get_int(seg0, "then")
local e = JsonFragBox.get_int(seg0, "else")
if c != null && t != null && e != null {
local cv = 0
if c == last_cmp_dst0 { cv = last_cmp_val0 } else { cv = me._load_reg(regs0, c) }
if cv != 0 { bb_choice = t } else { bb_choice = e }
break
}
} } }
scan0 = e0
}
if bb_choice >= 0 {
local bst = JsonV1ReaderBox.block_insts_start(mjson, bb_choice)
if bst >= 0 {
local bend = JsonCursorBox.seek_array_end(mjson, bst)
if bend > bst {
local seg1 = mjson.substring(bst + 1, bend)
local scan1 = 0
loop(true) {
if scan1 >= seg1.length() { break }
local tup1 = InstructionScannerBox.next_tuple(seg1, scan1)
if tup1 == "" { break }
local c11 = StringOps.index_of_from(tup1, ",", 0)
local c21 = StringOps.index_of_from(tup1, ",", c11+1)
if c11 < 0 || c21 < 0 { break }
local s1 = JsonFragBox._str_to_int(tup1.substring(0, c11))
local e1 = JsonFragBox._str_to_int(tup1.substring(c11+1, c21))
local op1 = tup1.substring(c21+1, tup1.length())
local seg_item = seg1.substring(s1, e1)
if op1 == "const" { OpHandlersBox.handle_const(seg_item, regs0) }
if op1 == "ret" {
local r = me._handle_ret_op(seg_item, regs0, last_cmp_dst0, last_cmp_val0, gc_trace)
return r
}
scan1 = e1
}
}
}
}
}
}
// Use user InstanceBox (MiniMap) with getField/setField to avoid provider dependencies
local regs = new MiniMap()
@ -272,13 +365,14 @@ static box MirVmMin {
loop(true){
steps = steps + 1
if steps > max_steps { return 0 }
local start = me._block_insts_start(mjson, bb)
local start = JsonV1ReaderBox.block_insts_start(mjson, bb)
me._d("[DEBUG] start="+me._int_to_str(start), trace)
if start < 0 { return 0 }
local endp = JsonCursorBox.seek_array_end(mjson, start)
me._d("[DEBUG] endp="+me._int_to_str(endp), trace)
if endp <= start { return 0 }
local inst_seg = mjson.substring(start, endp)
// Take array inner segment (skip '[')
local inst_seg = mjson.substring(start + 1, endp)
me._d("[DEBUG] seglen="+me._int_to_str(inst_seg.length()), trace)
// scan objects in this block
local scan_pos = 0
@ -319,41 +413,24 @@ static box MirVmMin {
}
else if op == "compare" {
me._d("[DEBUG] compare seg=" + seg, trace)
// Safe fast-path: if this block later contains a ret of this dst,
// evaluate compare directly from current regs and return immediately.
// This avoids deeper handler chains that could recurse in edge cases.
local rec = CompareScanBox.parse(seg)
local kdst_fast = rec.get("dst")
local klhs_fast = rec.get("lhs")
local krhs_fast = rec.get("rhs")
local kcmp_fast = rec.get("kind")
// Determine if a ret exists after this compare in the same block
local tail = inst_seg.substring(obj_end, inst_seg.length())
local ridt = JsonFragBox.get_int(tail, "value")
if kdst_fast != null && klhs_fast != null && krhs_fast != null && ridt != null && ridt == kdst_fast {
// Extract fields directly to avoid MapBox dependency
local kdst_fast = JsonFragBox.get_int(seg, "dst")
local klhs_fast = JsonFragBox.get_int(seg, "lhs")
local krhs_fast = JsonFragBox.get_int(seg, "rhs")
local kcmp_fast = JsonFragBox.get_str(seg, "cmp")
if kcmp_fast == "" {
local sym = JsonFragBox.get_str(seg, "operation")
if sym != "" { kcmp_fast = CompareOpsBox.map_symbol(sym) } else { kcmp_fast = "Eq" }
}
// Compute compare result and store
if kdst_fast != null && klhs_fast != null && krhs_fast != null {
local a = me._load_reg(regs, klhs_fast)
local b = me._load_reg(regs, krhs_fast)
local cv_fast = CompareOpsBox.eval(kcmp_fast, a, b)
// Store result to keep regs consistent for subsequent ops if any
regs.set("" + kdst_fast, "" + cv_fast)
local cv = CompareOpsBox.eval(kcmp_fast, a, b)
regs.setField("" + kdst_fast, "" + cv)
last_cmp_dst = kdst_fast
last_cmp_val = cv_fast
me._d("[DEBUG] compare early return with val=" + me._int_to_str(cv_fast), trace)
return cv_fast
last_cmp_val = cv
}
// Fallback to standard handler when early path is not applicable
OpHandlersBox.handle_compare(seg, regs)
local kdst = rec.get("dst")
me._d("[DEBUG] compare kdst=" + me._int_to_str(kdst), trace)
if kdst != null {
last_cmp_dst = kdst
last_cmp_val = me._load_reg(regs, kdst)
me._d("[DEBUG] compare last_cmp_dst=" + me._int_to_str(last_cmp_dst) + " last_cmp_val=" + me._int_to_str(last_cmp_val), trace)
me._d("[DEBUG] compare reg["+me._int_to_str(kdst)+"]=" + regs.get(""+kdst), trace)
}
// Secondary optimization: if a ret follows and targets this dst, return now
if ridt != null && kdst != null && ridt == kdst { return last_cmp_val }
}
else if op == "mir_call" {
me._handle_mir_call(seg, regs)
@ -388,117 +465,23 @@ static box MirVmMin {
break
}
else if op == "phi" {
local res = PhiDecodeBox.decode_result(seg, prev_bb)
if res.is_Ok() == 1 {
local pair = res.as_Ok()
PhiApplyBox.apply(pair.get(0), pair.get(1), regs)
}
// MiniVM v0: skip PHI (blocks we exercise should not rely on PHI semantics)
}
else if op == "throw" {
if op == "throw" {
me._tprint("[ERROR] Throw terminator encountered")
return -2
}
else if op == "ret" {
local v = JsonFragBox.get_int(seg, "value")
if v == null { me._tprint("[ERROR] Undefined ret value field") return -1 }
if v == last_cmp_dst {
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
return last_cmp_val
}
local sval = "" + regs.get(""+v)
if me._is_numeric_str(sval) == 1 {
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
return me._load_reg(regs, v)
}
me._tprint("[ERROR] Undefined register ret: r"+me._int_to_str(v))
return -1
if op == "ret" {
me._d("[DEBUG] ret seg=" + seg, trace)
local r = me._handle_ret_op(seg, regs, last_cmp_dst, last_cmp_val, gc_trace)
return r
}
// advance
inst_count = inst_count + 1
scan_pos = obj_end
}
if moved == 1 { continue }
// Prefer recent compare result when a ret exists targeting it (no recompute)
if inst_seg.indexOf("\"op\":\"ret\"") >= 0 {
local rstartX = inst_seg.indexOf("\"op\":\"ret\"")
local rsegX = inst_seg.substring(rstartX, inst_seg.length())
local ridX = JsonFragBox.get_int(rsegX, "value")
if ridX != null { if ridX == last_cmp_dst { return last_cmp_val } }
}
// Fallbacks: prefer recent compare result, else resolve explicit/v1 ret if present, else first const
if last_cmp_dst >= 0 { return last_cmp_val }
// Detect explicit ret in this block and resolve
if inst_seg.indexOf("\"op\":\"ret\"") >= 0 {
local rstart = inst_seg.indexOf("\"op\":\"ret\"")
local rseg = inst_seg.substring(rstart, inst_seg.length())
local rid = JsonFragBox.get_int(rseg, "value")
if rid != null {
if rid == last_cmp_dst {
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
return last_cmp_val
}
local rv = me._load_reg(regs, rid)
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
return rv
}
}
// (typed const fallback moved above)
if false {
// Grab first two "value":{"type":"i64","value":X}
local first = ""
local second = ""
local search = inst_seg
local key = "\"value\":{\"type\":\"i64\",\"value\":"
local pos = 0
loop(true) {
local k = StringOps.index_of_from(search, key, pos)
if k < 0 { break }
local ds = search.substring(k + key.length(), search.length())
// read consecutive digits as number
local i = 0
local out = ""
loop(true) {
local ch = ds.substring(i, i+1)
if ch == "" { break }
if ch >= "0" && ch <= "9" { out = out + ch i = i + 1 } else { break }
}
if out != "" { if first == "" { first = out } else { second = out } }
if second != "" { break }
pos = k + key.length() + i
}
if first != "" && second != "" {
local lv = 0
local rv = 0
// simple to_i64
local i0 = 0
loop(i0 < first.length()) { lv = lv * 10 + ("0123456789".indexOf(first.substring(i0,i0+1))) i0 = i0 + 1 }
local i1 = 0
loop(i1 < second.length()) { rv = rv * 10 + ("0123456789".indexOf(second.substring(i1,i1+1))) i1 = i1 + 1 }
// cmp: parse cmp op
local cmp_key = "\"cmp\":\""
local pk = inst_seg.indexOf(cmp_key)
local cmp = "Eq"
if pk >= 0 {
local i = pk + cmp_key.length()
local j = StringOps.index_of_from(inst_seg, "\"", i)
if j > i { cmp = inst_seg.substring(i, j) }
}
local cv = CompareOpsBox.eval(cmp, lv, rv)
// ret id
local rstart4 = inst_seg.indexOf("\"op\":\"ret\"")
local rseg4 = inst_seg.substring(rstart4, inst_seg.length())
local rid4 = JsonFragBox.get_int(rseg4, "value")
if rid4 != null { return cv }
}
}
local r = RetResolveSimpleBox.resolve(inst_seg, regs, last_cmp_dst, last_cmp_val)
if r != null {
if gc_trace == 1 { print("[GC] mark=0 sweep=0 survivors=0") }
return r
}
// Final fallback: return first const (dst=1) if present
local first = me._load_reg(regs, 1)
return first
return me._resolve_block_ret(inst_seg, regs, last_cmp_dst, last_cmp_val, gc_trace)
}
return 0
}

View 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 MiniVM 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 MiniVM
// 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). FailFast 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: FailFast (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 (miniVM専用マーカー; 環境依存を避ける)
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
}
}