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:
@ -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 ""
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
// 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 {
|
||||
|
||||
|
||||
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.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 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
|
||||
@ -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). Fail‑Fast for others.
|
||||
local name = me._parse_callee_name(seg)
|
||||
local arg0id = me._parse_first_arg(seg)
|
||||
// Support minimal externs/methods (i64 variants). Constructor is no‑op.
|
||||
// v1: treat Constructor as no‑op 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)
|
||||
}
|
||||
// Mini‑VM 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user