- 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.
310 lines
11 KiB
Plaintext
310 lines
11 KiB
Plaintext
// 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)
|
||
}
|
||
|
||
}
|