Files
hakorune/apps/selfhost/vm/boxes/mir_vm_min.nyash
nyash-codex 510f4cf523 builder/vm: stabilize json_lint_vm under unified calls
- Fix condition_fn resolution: Value call path + dev safety + stub injection
- VM bridge: handle Method::birth via BoxCall; ArrayBox push/get/length/set direct bridge
- Receiver safety: pin receiver in method_call_handlers to avoid undefined use across blocks
- Local vars: materialize on declaration (use init ValueId; void for uninit)
- Prefer legacy BoxCall for Array/Map/String/user boxes in emit_box_or_plugin_call (stability-first)
- Test runner: update LLVM hint to llvmlite harness (remove LLVM_SYS_180_PREFIX guidance)
- Docs/roadmap: update CURRENT_TASK with unified default-ON + guards

Note: NYASH_DEV_BIRTH_INJECT_BUILTINS=1 can re-enable builtin birth() injection during migration.
2025-09-28 12:19:49 +09:00

310 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// mir_vm_min.nyash — Ny製の最小MIR(JSON v0)実行器const→retのみ
// 目的: M2スケルトン。仕様は既定OFFに影響しない新規アプリのみ。
// 入力: MIR(JSON v0) 文字列。形式例:
// {
// "functions":[{"name":"main","params":[],"blocks":[{"id":0,"instructions":[
// {"op":"const","dst":1,"value":{"type":"i64","value":42}},
// {"op":"ret","value":1}
// ]}]}]
// }
// 振る舞い:
// - M1: 最初の const i64 の値を読み取り print
// - M2: const/binop/compare/ret を最小実装(簡易スキャンで安全に解釈)
static box MirVmMin {
// Public entry used by parity tests (calls into minimal runner)
run(mjson) {
local v = me._run_min(mjson)
print(me._int_to_str(v))
return v
}
// 最小限のスキャン関数(依存ゼロ版)
index_of_from(hay, needle, pos) {
if pos < 0 { pos = 0 }
local n = hay.length()
if pos >= n { return -1 }
local m = needle.length()
if m <= 0 { return pos }
local i = pos
local limit = n - m
loop (i <= limit) {
local seg = hay.substring(i, i + m)
if seg == needle { return i }
i = i + 1
}
return -1
}
read_digits(text, pos) {
local out = ""
local i = pos
loop (true) {
local s = text.substring(i, i+1)
if s == "" { break }
if s == "0" || s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" || s == "8" || s == "9" {
out = out + s
i = i + 1
} else { break }
}
return out
}
_str_to_int(s) {
local i = 0
local n = s.length()
local acc = 0
loop (i < n) {
local ch = s.substring(i, i+1)
if ch == "0" { acc = acc * 10 + 0 i = i + 1 continue }
if ch == "1" { acc = acc * 10 + 1 i = i + 1 continue }
if ch == "2" { acc = acc * 10 + 2 i = i + 1 continue }
if ch == "3" { acc = acc * 10 + 3 i = i + 1 continue }
if ch == "4" { acc = acc * 10 + 4 i = i + 1 continue }
if ch == "5" { acc = acc * 10 + 5 i = i + 1 continue }
if ch == "6" { acc = acc * 10 + 6 i = i + 1 continue }
if ch == "7" { acc = acc * 10 + 7 i = i + 1 continue }
if ch == "8" { acc = acc * 10 + 8 i = i + 1 continue }
if ch == "9" { acc = acc * 10 + 9 i = i + 1 continue }
break
}
return acc
}
_int_to_str(n) {
if n == 0 { return "0" }
local v = n
local out = ""
local digits = "0123456789"
loop (v > 0) {
local d = v % 10
local ch = digits.substring(d, d+1)
out = ch + out
v = v / 10
}
return out
}
// MVP: 最初の const i64 の値を抽出
_extract_first_const_i64(text) {
if text == null { return 0 }
// "op":"const" を探す
local p = text.indexOf("\"op\":\"const\"")
if p < 0 { return 0 }
// そこから "\"value\":{\"type\":\"i64\",\"value\":" を探す
local key = "\"value\":{\"type\":\"i64\",\"value\":"
local q = me.index_of_from(text, key, p)
if q < 0 { return 0 }
q = q + key.length()
// 連続する数字を読む
local digits = me.read_digits(text, q)
if digits == "" { return 0 }
return me._str_to_int(digits)
}
// --- M2 追加: 最小 MIR 実行const/binop/compare/ret ---
_get_map(regs, key) { if regs.has(key) { return regs.get(key) } return 0 }
_set_map(regs, key, val) { regs.set(key, val) }
_find_int_in(seg, keypat) {
local p = seg.indexOf(keypat)
if p < 0 { return null }
p = p + keypat.length()
local i = p
local out = ""
loop(true) {
local ch = seg.substring(i, i+1)
if ch == "" { break }
if ch == "0" || ch == "1" || ch == "2" || ch == "3" || ch == "4" || ch == "5" || ch == "6" || ch == "7" || ch == "8" || ch == "9" { out = out + ch i = i + 1 } else { break }
}
if out == "" { return null }
return me._str_to_int(out)
}
_find_str_in(seg, keypat) {
local p = seg.indexOf(keypat)
if p < 0 { return "" }
p = p + keypat.length()
local q = me.index_of_from(seg, "\"", p)
if q < 0 { return "" }
return seg.substring(p, q)
}
// --- JSON segment helpers (brace/bracket aware, minimal) ---
_seek_obj_start(text, from_pos) {
// scan backward to the nearest '{'
local i = from_pos
loop(true) {
i = i - 1
if i < 0 { return 0 }
local ch = text.substring(i, i+1)
if ch == "{" { return i }
}
return 0
}
_seek_obj_end(text, obj_start) {
// starting at '{', find matching '}' using depth counter
local i = obj_start
local depth = 0
loop(true) {
local ch = text.substring(i, i+1)
if ch == "" { break }
if ch == "{" { depth = depth + 1 }
else { if ch == "}" { depth = depth - 1 } }
if depth == 0 { return i + 1 }
i = i + 1
}
return i
}
_seek_array_end(text, array_after_bracket_pos) {
// given pos right after '[', find the matching ']'
local i = array_after_bracket_pos
local depth = 1
loop(true) {
local ch = text.substring(i, i+1)
if ch == "" { break }
if ch == "[" { depth = depth + 1 }
else { if ch == "]" { depth = depth - 1 } }
if depth == 0 { return i }
i = i + 1
}
return i
}
_map_binop_symbol(sym) {
if sym == "+" { return "Add" }
if sym == "-" { return "Sub" }
if sym == "*" { return "Mul" }
if sym == "/" { return "Div" }
if sym == "%" { return "Mod" }
return "" }
_map_cmp_symbol(sym) {
if sym == "==" { return "Eq" }
if sym == "!=" { return "Ne" }
if sym == "<" { return "Lt" }
if sym == "<=" { return "Le" }
if sym == ">" { return "Gt" }
if sym == ">=" { return "Ge" }
return "" }
_eval_binop(kind, a, b) {
if kind == "Add" { return a + b }
if kind == "Sub" { return a - b }
if kind == "Mul" { return a * b }
if kind == "Div" { if b == 0 { return 0 } else { return a / b } }
if kind == "Mod" { if b == 0 { return 0 } else { return a % b } }
return 0 }
_eval_cmp(kind, a, b) {
if kind == "Eq" { if a == b { return 1 } else { return 0 } }
if kind == "Ne" { if a != b { return 1 } else { return 0 } }
if kind == "Lt" { if a < b { return 1 } else { return 0 } }
if kind == "Gt" { if a > b { return 1 } else { return 0 } }
if kind == "Le" { if a <= b { return 1 } else { return 0 } }
if kind == "Ge" { if a >= b { return 1 } else { return 0 } }
return 0
}
// Locate start of instructions array for given block id
_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 = me.index_of_from(mjson, "\"instructions\":[", p)
if q < 0 { return -1 }
// "\"instructions\":[" is 16 chars → return pos right after '['
return q + 16
}
_block_insts_end(mjson, insts_start) {
// Bound to the end bracket of this block's instructions array
return me._seek_array_end(mjson, insts_start)
}
_run_min(mjson) {
local regs = new MapBox()
// Control flow: start at block 0, process until ret
local bb = 0
loop(true) {
local pos = me._block_insts_start(mjson, bb)
if pos < 0 { return me._extract_first_const_i64(mjson) }
local block_end = me._block_insts_end(mjson, pos)
// Single-pass over instructions: segment each op object precisely and evaluate
local scan = pos
local moved = 0
loop(true) {
// find next op field within this block
local opos = me.index_of_from(mjson, "\"op\":\"", scan)
if opos < 0 || opos >= block_end { break }
// find exact JSON object bounds for this instruction
// Determine object start as the last '{' between pos..opos
local i = pos
local obj_start = opos
loop(i <= opos) {
local ch0 = mjson.substring(i, i+1)
if ch0 == "{" { obj_start = i }
i = i + 1
}
local obj_end = me._seek_obj_end(mjson, obj_start)
if obj_end > block_end { obj_end = block_end }
local seg = mjson.substring(obj_start, obj_end)
// dispatch by op name (v0/v1 tolerant)
local opname = me._find_str_in(seg, "\"op\":\"")
if opname == "const" {
local cdst = me._find_int_in(seg, "\"dst\":")
local cval = me._find_int_in(seg, "\"value\":{\"type\":\"i64\",\"value\":")
if cdst != null and cval != null { me._set_map(regs, "" + cdst, cval) }
} else {
if opname == "binop" {
local bdst = me._find_int_in(seg, "\"dst\":")
local bkind = me._find_str_in(seg, "\"op_kind\":\"")
if bkind == "" { bkind = me._map_binop_symbol(me._find_str_in(seg, "\"operation\":\"")) }
local blhs = me._find_int_in(seg, "\"lhs\":")
local brhs = me._find_int_in(seg, "\"rhs\":")
if bdst != null and blhs != null and brhs != null {
local a = me._get_map(regs, "" + blhs)
local b = me._get_map(regs, "" + brhs)
local r = me._eval_binop(bkind, a, b)
me._set_map(regs, "" + bdst, r)
}
} else {
if opname == "compare" {
local kdst = me._find_int_in(seg, "\"dst\":")
local kkind = me._find_str_in(seg, "\"cmp\":\"")
if kkind == "" { kkind = me._map_cmp_symbol(me._find_str_in(seg, "\"operation\":\"")) }
local klhs = me._find_int_in(seg, "\"lhs\":")
local krhs = me._find_int_in(seg, "\"rhs\":")
if kdst != null and klhs != null and krhs != null {
local a = me._get_map(regs, "" + klhs)
local b = me._get_map(regs, "" + krhs)
local r = me._eval_cmp(kkind, a, b)
me._set_map(regs, "" + kdst, r)
}
} else {
if opname == "jump" {
local tgt = me._find_int_in(seg, "\"target\":")
if tgt != null { bb = tgt scan = block_end moved = 1 break }
} else {
if opname == "branch" {
local cond = me._find_int_in(seg, "\"cond\":")
local then_id = me._find_int_in(seg, "\"then\":")
local else_id = me._find_int_in(seg, "\"else\":")
local cval = 0
if cond != null { cval = me._get_map(regs, "" + cond) }
if cval != 0 { bb = then_id } else { bb = else_id }
scan = block_end
moved = 1
break
} else {
if opname == "ret" {
local rv = me._find_int_in(seg, "\"value\":")
if rv == null { rv = 0 }
return me._get_map(regs, "" + rv)
}
}
}
}
}
}
// advance to the end of this instruction object
scan = obj_end
}
// No ret encountered in this block; if control moved, continue with new bb
if moved == 1 { continue }
// Fallback when ret not found at all in processed blocks
return me._extract_first_const_i64(mjson)
}
return me._extract_first_const_i64(mjson)
}
}