944 lines
26 KiB
Plaintext
944 lines
26 KiB
Plaintext
// qbe_emit.cm — mcode IR → QBE IL compiler
|
|
// Takes mcode IR (from mcode.cm) and uses qbe.cm macros to produce
|
|
// a complete QBE IL program ready for the qbe compiler.
|
|
// qbe module is passed via env as 'qbe'
|
|
|
|
var qbe_emit = function(ir, qbe, export_name) {
|
|
var out = []
|
|
var data_out = []
|
|
var str_table = {}
|
|
var str_id = 0
|
|
var uid = 0
|
|
|
|
// ============================================================
|
|
// Output helpers
|
|
// ============================================================
|
|
|
|
var emit = function(s) {
|
|
push(out, s)
|
|
}
|
|
|
|
var fresh = function() {
|
|
uid = uid + 1
|
|
return "u" + text(uid)
|
|
}
|
|
|
|
var s = function(n) {
|
|
return "%s" + text(n)
|
|
}
|
|
|
|
var sanitize = function(lbl) {
|
|
var r = replace(lbl, ".", "_")
|
|
r = replace(r, "-", "_")
|
|
r = replace(r, " ", "_")
|
|
r = replace(r, "/", "_")
|
|
r = replace(r, "<", "")
|
|
r = replace(r, ">", "")
|
|
r = replace(r, "(", "")
|
|
r = replace(r, ")", "")
|
|
return r
|
|
}
|
|
|
|
// ============================================================
|
|
// String interning — emit data section entries
|
|
// ============================================================
|
|
|
|
var intern_str = function(val) {
|
|
if (str_table[val] != null) return str_table[val]
|
|
var label = "$d_str_" + text(str_id)
|
|
str_id = str_id + 1
|
|
var escaped = replace(val, "\\", "\\\\")
|
|
escaped = replace(escaped, "\"", "\\\"")
|
|
var line = "data " + label + ' = ' + '{ b "' + escaped + '", b 0 }'
|
|
push(data_out, line)
|
|
str_table[val] = label
|
|
return label
|
|
}
|
|
|
|
// ============================================================
|
|
// Extract property name from mcode operand
|
|
// ============================================================
|
|
|
|
var prop_name = function(a) {
|
|
if (is_text(a)) return a
|
|
if (is_object(a)) {
|
|
if (a.name != null) return a.name
|
|
if (a.value != null) return a.value
|
|
}
|
|
return null
|
|
}
|
|
|
|
// ============================================================
|
|
// Compile one function's instructions
|
|
// ============================================================
|
|
|
|
var compile_fn = function(fn, fn_idx, is_main) {
|
|
var instrs = fn.instructions
|
|
var nr_slots = fn.nr_slots
|
|
var nr_args = fn.nr_args
|
|
var captured = build_captured(fn)
|
|
var name = is_main ? (export_name ? export_name : "cell_main") : "cell_fn_" + text(fn_idx)
|
|
name = sanitize(name)
|
|
var i = 0
|
|
var instr = null
|
|
var op = null
|
|
var a1 = null
|
|
var a2 = null
|
|
var a3 = null
|
|
var a4 = null
|
|
var p = null
|
|
var pn = null
|
|
var sl = null
|
|
var lbl = null
|
|
var fop_id = 0
|
|
var nr_elems = 0
|
|
var ei = 0
|
|
var elem_slot = 0
|
|
|
|
// Function signature: (ctx, frame_ptr) → JSValue
|
|
emit(`export function l $${name}(l %ctx, l %fp) {`)
|
|
emit("@entry")
|
|
|
|
// Load all slots from frame into SSA variables
|
|
// Each slot is a JSValue (8 bytes) at fp + slot*8
|
|
var off = 0
|
|
i = 0
|
|
while (i < nr_slots) {
|
|
off = i * 8
|
|
emit(` %p${text(i)} =l add %fp, ${text(off)}`)
|
|
emit(` ${s(i)} =l loadl %p${text(i)}`)
|
|
i = i + 1
|
|
}
|
|
|
|
// Write-back: store SSA var to frame slot so closures see updates
|
|
var wb = function(slot) {
|
|
emit(` storel ${s(slot)}, %p${text(slot)}`)
|
|
}
|
|
|
|
// Reload captured slots from frame (after invoke, closures may have modified them)
|
|
var reload_captured = function() {
|
|
var ri = 0
|
|
while (ri < nr_slots) {
|
|
if (captured[text(ri)] == true) {
|
|
emit(` ${s(ri)} =l loadl %p${text(ri)}`)
|
|
}
|
|
ri = ri + 1
|
|
}
|
|
}
|
|
|
|
// Walk instructions
|
|
// Slot loads above are not terminators
|
|
var last_was_term = false
|
|
i = 0
|
|
while (i < length(instrs)) {
|
|
instr = instrs[i]
|
|
i = i + 1
|
|
|
|
// Labels are plain strings; skip _nop_ur_ pseudo-labels from streamline
|
|
if (is_text(instr)) {
|
|
if (starts_with(instr, "_nop_ur_")) continue
|
|
lbl = sanitize(instr)
|
|
if (!last_was_term) {
|
|
emit(` jmp @${lbl}`)
|
|
}
|
|
emit("@" + lbl)
|
|
last_was_term = false
|
|
continue
|
|
}
|
|
|
|
// Skip dead code: non-label instructions after a terminator are unreachable
|
|
if (last_was_term) continue
|
|
|
|
op = instr[0]
|
|
a1 = instr[1]
|
|
a2 = instr[2]
|
|
a3 = instr[3]
|
|
last_was_term = false
|
|
|
|
// --- Constants ---
|
|
|
|
if (op == "int") {
|
|
emit(` ${s(a1)} =l copy ${text(a2 * 2)}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "null") {
|
|
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "true") {
|
|
emit(` ${s(a1)} =l copy ${text(qbe.js_true)}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "false") {
|
|
emit(` ${s(a1)} =l copy ${text(qbe.js_false)}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "access") {
|
|
if (is_number(a2)) {
|
|
if (is_integer(a2)) {
|
|
emit(` ${s(a1)} =l copy ${text(a2 * 2)}`)
|
|
} else {
|
|
emit(` ${s(a1)} =l call $qbe_new_float64(l %ctx, d d_${text(a2)})`)
|
|
}
|
|
} else if (is_text(a2)) {
|
|
sl = intern_str(a2)
|
|
emit(` ${s(a1)} =l call $qbe_new_string(l %ctx, l ${sl})`)
|
|
} else if (is_object(a2)) {
|
|
if (a2.make == "intrinsic") {
|
|
sl = intern_str(a2.name)
|
|
emit(` ${s(a1)} =l call $cell_rt_get_intrinsic(l %ctx, l ${sl})`)
|
|
} else if (a2.kind == "number") {
|
|
if (a2.number != null && is_integer(a2.number)) {
|
|
emit(` ${s(a1)} =l copy ${text(a2.number * 2)}`)
|
|
} else if (a2.number != null) {
|
|
emit(` ${s(a1)} =l call $qbe_new_float64(l %ctx, d d_${text(a2.number)})`)
|
|
} else {
|
|
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
|
|
}
|
|
} else if (a2.kind == "text") {
|
|
sl = intern_str(a2.value)
|
|
emit(` ${s(a1)} =l call $qbe_new_string(l %ctx, l ${sl})`)
|
|
} else if (a2.kind == "true") {
|
|
emit(` ${s(a1)} =l copy ${text(qbe.js_true)}`)
|
|
} else if (a2.kind == "false") {
|
|
emit(` ${s(a1)} =l copy ${text(qbe.js_false)}`)
|
|
} else if (a2.kind == "null") {
|
|
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
|
|
} else {
|
|
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
|
|
}
|
|
} else {
|
|
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
|
|
}
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
// --- Movement ---
|
|
|
|
if (op == "move") {
|
|
emit(` ${s(a1)} =l copy ${s(a2)}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
// --- Generic arithmetic (VM dispatches int/float) ---
|
|
|
|
if (op == "add") {
|
|
p = fresh()
|
|
emit(` %${p} =l call $cell_rt_add(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "subtract") {
|
|
p = fresh()
|
|
emit(qbe.sub(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "multiply") {
|
|
p = fresh()
|
|
emit(qbe.mul(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "divide") {
|
|
p = fresh()
|
|
emit(qbe.div(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "modulo") {
|
|
p = fresh()
|
|
emit(qbe.mod(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "negate") {
|
|
p = fresh()
|
|
emit(qbe.neg(p, "%ctx", s(a2)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
if (op == "pow") {
|
|
emit(` ${s(a1)} =l call $qbe_float_pow(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
// --- String concat ---
|
|
|
|
if (op == "concat") {
|
|
p = fresh()
|
|
emit(qbe.concat(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
// --- Type checks — use qbe.cm macros ---
|
|
|
|
if (op == "is_int") {
|
|
p = fresh()
|
|
emit(qbe.is_int(p, s(a2)))
|
|
emit(qbe.new_bool(p + ".r", "%" + p))
|
|
emit(` ${s(a1)} =l copy %${p}.r`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "is_text") {
|
|
p = fresh()
|
|
emit(qbe.is_imm_text(p, s(a2)))
|
|
emit(qbe.new_bool(p + ".r", "%" + p))
|
|
emit(` ${s(a1)} =l copy %${p}.r`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "is_num") {
|
|
p = fresh()
|
|
emit(qbe.is_number(p, s(a2)))
|
|
emit(qbe.new_bool(p + ".r", "%" + p))
|
|
emit(` ${s(a1)} =l copy %${p}.r`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "is_bool") {
|
|
p = fresh()
|
|
emit(qbe.is_bool(p, s(a2)))
|
|
emit(qbe.new_bool(p + ".r", "%" + p))
|
|
emit(` ${s(a1)} =l copy %${p}.r`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "is_null") {
|
|
p = fresh()
|
|
emit(qbe.is_null(p, s(a2)))
|
|
emit(qbe.new_bool(p + ".r", "%" + p))
|
|
emit(` ${s(a1)} =l copy %${p}.r`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "is_identical") {
|
|
p = fresh()
|
|
emit(qbe.is_identical(p, s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "is_array") {
|
|
p = fresh()
|
|
emit(` %${p} =w call $JS_IsArray(l ${s(a2)})`)
|
|
emit(qbe.new_bool(p + ".r", "%" + p))
|
|
emit(` ${s(a1)} =l copy %${p}.r`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "is_func") {
|
|
p = fresh()
|
|
emit(` %${p} =w call $JS_IsFunction(l ${s(a2)})`)
|
|
emit(qbe.new_bool(p + ".r", "%" + p))
|
|
emit(` ${s(a1)} =l copy %${p}.r`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "is_record") {
|
|
p = fresh()
|
|
emit(` %${p} =w call $JS_IsRecord(l ${s(a2)})`)
|
|
emit(qbe.new_bool(p + ".r", "%" + p))
|
|
emit(` ${s(a1)} =l copy %${p}.r`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "is_stone") {
|
|
p = fresh()
|
|
emit(` %${p} =w call $JS_IsStone(l ${s(a2)})`)
|
|
emit(qbe.new_bool(p + ".r", "%" + p))
|
|
emit(` ${s(a1)} =l copy %${p}.r`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "is_proxy") {
|
|
p = fresh()
|
|
emit(` %${p} =w call $cell_rt_is_proxy(l %ctx, l ${s(a2)})`)
|
|
emit(qbe.new_bool(p + ".r", "%" + p))
|
|
emit(` ${s(a1)} =l copy %${p}.r`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
// --- Comparisons (int path) ---
|
|
|
|
if (op == "eq_int") {
|
|
p = fresh()
|
|
emit(qbe.eq_int(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "ne_int") {
|
|
p = fresh()
|
|
emit(qbe.ne_int(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "lt_int") {
|
|
p = fresh()
|
|
emit(qbe.lt_int(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "gt_int") {
|
|
p = fresh()
|
|
emit(qbe.gt_int(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "le_int") {
|
|
p = fresh()
|
|
emit(qbe.le_int(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "ge_int") {
|
|
p = fresh()
|
|
emit(qbe.ge_int(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
// --- Comparisons (float/text/bool) ---
|
|
|
|
if (op == "eq_float") {
|
|
p = fresh()
|
|
emit(qbe.eq_float(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "ne_float") {
|
|
p = fresh()
|
|
emit(qbe.ne_float(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "lt_float") {
|
|
p = fresh()
|
|
emit(qbe.lt_float(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "le_float") {
|
|
p = fresh()
|
|
emit(qbe.le_float(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "gt_float") {
|
|
p = fresh()
|
|
emit(qbe.gt_float(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "ge_float") {
|
|
p = fresh()
|
|
emit(qbe.ge_float(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "eq_text") {
|
|
p = fresh()
|
|
emit(qbe.eq_text(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "ne_text") {
|
|
p = fresh()
|
|
emit(qbe.ne_text(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "lt_text" || op == "gt_text" || op == "le_text" || op == "ge_text") {
|
|
p = fresh()
|
|
emit(` ${s(a1)} =l call $cell_rt_${op}(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "eq_bool") {
|
|
p = fresh()
|
|
emit(qbe.eq_bool(p, s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "ne_bool") {
|
|
p = fresh()
|
|
emit(qbe.ne_bool(p, s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "eq_tol" || op == "ne_tol") {
|
|
emit(` ${s(a1)} =l call $cell_rt_${op}(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
// --- Boolean ops ---
|
|
|
|
if (op == "not") {
|
|
p = fresh()
|
|
emit(qbe.lnot(p, "%ctx", s(a2)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "and") {
|
|
emit(` ${s(a1)} =l and ${s(a2)}, ${s(a3)}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "or") {
|
|
emit(` ${s(a1)} =l or ${s(a2)}, ${s(a3)}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
// --- Bitwise ops — use qbe.cm macros ---
|
|
|
|
if (op == "bitnot") {
|
|
p = fresh()
|
|
emit(qbe.bnot(p, "%ctx", s(a2)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "bitand") {
|
|
p = fresh()
|
|
emit(qbe.band(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "bitor") {
|
|
p = fresh()
|
|
emit(qbe.bor(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "bitxor") {
|
|
p = fresh()
|
|
emit(qbe.bxor(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "shl") {
|
|
p = fresh()
|
|
emit(qbe.shl(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "shr") {
|
|
p = fresh()
|
|
emit(qbe.shr(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "ushr") {
|
|
p = fresh()
|
|
emit(qbe.ushr(p, "%ctx", s(a2), s(a3)))
|
|
emit(` ${s(a1)} =l copy %${p}`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
// --- Property access — runtime calls ---
|
|
|
|
if (op == "load_field") {
|
|
pn = null
|
|
if (is_text(a3)) pn = a3
|
|
else if (is_object(a3) && a3.name != null) pn = a3.name
|
|
else if (is_object(a3) && a3.value != null) pn = a3.value
|
|
if (pn != null) {
|
|
sl = intern_str(pn)
|
|
emit(` ${s(a1)} =l call $cell_rt_load_field(l %ctx, l ${s(a2)}, l ${sl})`)
|
|
} else {
|
|
emit(` ${s(a1)} =l call $cell_rt_load_dynamic(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
|
}
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "load_index") {
|
|
emit(` ${s(a1)} =l call $cell_rt_load_index(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "load_dynamic") {
|
|
pn = null
|
|
if (is_text(a3)) pn = a3
|
|
else if (is_object(a3) && a3.name != null) pn = a3.name
|
|
else if (is_object(a3) && a3.value != null) pn = a3.value
|
|
if (pn != null) {
|
|
sl = intern_str(pn)
|
|
emit(` ${s(a1)} =l call $cell_rt_load_field(l %ctx, l ${s(a2)}, l ${sl})`)
|
|
} else {
|
|
emit(` ${s(a1)} =l call $cell_rt_load_dynamic(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
|
}
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "store_field") {
|
|
// IR: ["store_field", obj, val, prop] → C: (ctx, val, obj, name)
|
|
pn = null
|
|
if (is_text(a3)) {
|
|
pn = a3
|
|
} else if (is_object(a3)) {
|
|
if (a3.name != null) pn = a3.name
|
|
else if (a3.value != null) pn = a3.value
|
|
}
|
|
if (pn != null) {
|
|
sl = intern_str(pn)
|
|
emit(` call $cell_rt_store_field(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${sl})`)
|
|
} else {
|
|
emit(` call $cell_rt_store_dynamic(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`)
|
|
}
|
|
continue
|
|
}
|
|
if (op == "store_index") {
|
|
// IR: ["store_index", obj, val, idx] → C: (ctx, val, obj, idx)
|
|
emit(` call $cell_rt_store_index(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "store_dynamic") {
|
|
// IR: ["store_dynamic", obj, val, key] → C: (ctx, val, obj, key)
|
|
pn = null
|
|
if (is_text(a3)) pn = a3
|
|
else if (is_object(a3) && a3.name != null) pn = a3.name
|
|
else if (is_object(a3) && a3.value != null) pn = a3.value
|
|
if (pn != null) {
|
|
sl = intern_str(pn)
|
|
emit(` call $cell_rt_store_field(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${sl})`)
|
|
} else {
|
|
emit(` call $cell_rt_store_dynamic(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// --- Closure access ---
|
|
|
|
if (op == "get") {
|
|
// mcode: get(dest, slot, depth) — a2=slot, a3=depth
|
|
emit(` ${s(a1)} =l call $cell_rt_get_closure(l %ctx, l %fp, l ${text(a3)}, l ${text(a2)})`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "put") {
|
|
// mcode: put(val, slot, depth) — a2=slot, a3=depth
|
|
emit(` call $cell_rt_put_closure(l %ctx, l %fp, l ${s(a1)}, l ${text(a3)}, l ${text(a2)})`)
|
|
continue
|
|
}
|
|
|
|
// --- Control flow ---
|
|
|
|
if (op == "jump") {
|
|
emit(` jmp @${sanitize(a1)}`)
|
|
last_was_term = true
|
|
continue
|
|
}
|
|
if (op == "jump_true") {
|
|
p = fresh()
|
|
emit(` %${p} =w call $JS_ToBool(l %ctx, l ${s(a1)})`)
|
|
emit(` jnz %${p}, @${sanitize(a2)}, @${p}_f`)
|
|
emit(`@${p}_f`)
|
|
continue
|
|
}
|
|
if (op == "jump_false") {
|
|
p = fresh()
|
|
emit(` %${p} =w call $JS_ToBool(l %ctx, l ${s(a1)})`)
|
|
emit(` jnz %${p}, @${p}_t, @${sanitize(a2)}`)
|
|
emit(`@${p}_t`)
|
|
continue
|
|
}
|
|
if (op == "jump_null") {
|
|
p = fresh()
|
|
emit(` %${p} =w ceql ${s(a1)}, ${text(qbe.js_null)}`)
|
|
emit(` jnz %${p}, @${sanitize(a2)}, @${p}_nn`)
|
|
emit(`@${p}_nn`)
|
|
continue
|
|
}
|
|
if (op == "jump_not_null") {
|
|
p = fresh()
|
|
emit(` %${p} =w cnel ${s(a1)}, ${text(qbe.js_null)}`)
|
|
emit(` jnz %${p}, @${sanitize(a2)}, @${p}_n`)
|
|
emit(`@${p}_n`)
|
|
continue
|
|
}
|
|
if (op == "wary_true") {
|
|
p = fresh()
|
|
emit(` %${p} =w call $JS_ToBool(l %ctx, l ${s(a1)})`)
|
|
emit(` jnz %${p}, @${sanitize(a2)}, @${p}_f`)
|
|
emit(`@${p}_f`)
|
|
continue
|
|
}
|
|
if (op == "wary_false") {
|
|
p = fresh()
|
|
emit(` %${p} =w call $JS_ToBool(l %ctx, l ${s(a1)})`)
|
|
emit(` jnz %${p}, @${p}_t, @${sanitize(a2)}`)
|
|
emit(`@${p}_t`)
|
|
continue
|
|
}
|
|
|
|
// --- Function calls ---
|
|
|
|
if (op == "frame") {
|
|
emit(` ${s(a1)} =l call $cell_rt_frame(l %ctx, l ${s(a2)}, l ${text(a3)})`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "setarg") {
|
|
emit(` call $cell_rt_setarg(l ${s(a1)}, l ${text(a2)}, l ${s(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "invoke") {
|
|
emit(` ${s(a2)} =l call $cell_rt_invoke(l %ctx, l ${s(a1)})`)
|
|
wb(a2)
|
|
reload_captured()
|
|
continue
|
|
}
|
|
if (op == "tail_invoke") {
|
|
emit(` ${s(a2)} =l call $cell_rt_invoke(l %ctx, l ${s(a1)})`)
|
|
wb(a2)
|
|
reload_captured()
|
|
continue
|
|
}
|
|
if (op == "goframe") {
|
|
emit(` ${s(a1)} =l call $cell_rt_goframe(l %ctx, l ${s(a2)}, l ${text(a3)})`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "goinvoke") {
|
|
emit(` %_goret =l call $cell_rt_goinvoke(l %ctx, l ${s(a1)})`)
|
|
emit(` ret %_goret`)
|
|
last_was_term = true
|
|
continue
|
|
}
|
|
|
|
// --- Function object creation ---
|
|
|
|
if (op == "function") {
|
|
emit(` ${s(a1)} =l call $cell_rt_make_function(l %ctx, l ${text(a2)}, l %fp)`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
// --- Record/Array creation ---
|
|
|
|
if (op == "record") {
|
|
emit(` ${s(a1)} =l call $JS_NewObject(l %ctx)`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "array") {
|
|
nr_elems = a2 != null ? a2 : 0
|
|
emit(` ${s(a1)} =l call $JS_NewArray(l %ctx)`)
|
|
ei = 0
|
|
while (ei < nr_elems) {
|
|
elem_slot = instr[3 + ei]
|
|
emit(` call $JS_SetPropertyNumber(l %ctx, l ${s(a1)}, l ${text(ei)}, l ${s(elem_slot)})`)
|
|
ei = ei + 1
|
|
}
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
// --- Array push/pop ---
|
|
|
|
if (op == "push") {
|
|
emit(` call $cell_rt_push(l %ctx, l ${s(a1)}, l ${s(a2)})`)
|
|
continue
|
|
}
|
|
if (op == "pop") {
|
|
emit(` ${s(a1)} =l call $cell_rt_pop(l %ctx, l ${s(a2)})`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
// --- Length ---
|
|
|
|
if (op == "length") {
|
|
emit(` ${s(a1)} =l call $JS_CellLength(l %ctx, l ${s(a2)})`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
// --- Misc ---
|
|
|
|
if (op == "return") {
|
|
emit(` ret ${s(a1)}`)
|
|
last_was_term = true
|
|
continue
|
|
}
|
|
if (op == "disrupt") {
|
|
emit(` call $cell_rt_disrupt(l %ctx)`)
|
|
emit(` ret ${text(qbe.js_null)}`)
|
|
last_was_term = true
|
|
continue
|
|
}
|
|
if (op == "delete") {
|
|
pn = null
|
|
if (is_text(a3)) pn = a3
|
|
else if (is_object(a3) && a3.name != null) pn = a3.name
|
|
else if (is_object(a3) && a3.value != null) pn = a3.value
|
|
if (pn != null) {
|
|
sl = intern_str(pn)
|
|
emit(` ${s(a1)} =l call $cell_rt_delete(l %ctx, l ${s(a2)}, l ${sl})`)
|
|
} else {
|
|
emit(` ${s(a1)} =l call $cell_rt_delete(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
|
}
|
|
wb(a1)
|
|
continue
|
|
}
|
|
if (op == "typeof") {
|
|
emit(` ${s(a1)} =l call $cell_rt_typeof(l %ctx, l ${s(a2)})`)
|
|
wb(a1)
|
|
continue
|
|
}
|
|
|
|
// --- Unknown opcode ---
|
|
emit(` # unknown: ${op}`)
|
|
}
|
|
|
|
// Emit @disrupt landing pad for arithmetic type-error branches
|
|
if (!last_was_term) {
|
|
emit(" jmp @disrupt")
|
|
}
|
|
emit("@disrupt")
|
|
emit(` call $cell_rt_disrupt(l %ctx)`)
|
|
emit(` ret ${text(qbe.js_null)}`)
|
|
|
|
emit("}")
|
|
emit("")
|
|
}
|
|
|
|
// ============================================================
|
|
// Main: compile all functions then main
|
|
// ============================================================
|
|
|
|
// ============================================================
|
|
// Pre-scan: find which slots each function has that are modified
|
|
// by child closures (via "put" instructions at depth=1).
|
|
// Build a map: fn_idx → array of captured slot numbers.
|
|
// ============================================================
|
|
|
|
// For each function, find which fn_idxes it creates via "function" op
|
|
var find_children = function(fn_instrs) {
|
|
var children = []
|
|
var ci = 0
|
|
var cinstr = null
|
|
while (ci < length(fn_instrs)) {
|
|
cinstr = fn_instrs[ci]
|
|
ci = ci + 1
|
|
if (!is_array(cinstr)) continue
|
|
if (cinstr[0] == "function") {
|
|
push(children, cinstr[2])
|
|
}
|
|
}
|
|
return children
|
|
}
|
|
|
|
// For a child function, find which parent slots it writes to via put(val, slot, depth=1)
|
|
var find_put_slots = function(fn_instrs) {
|
|
var slots = []
|
|
var pi = 0
|
|
var pinstr = null
|
|
while (pi < length(fn_instrs)) {
|
|
pinstr = fn_instrs[pi]
|
|
pi = pi + 1
|
|
if (!is_array(pinstr)) continue
|
|
// put format: ["put", val, slot, depth]
|
|
if (pinstr[0] == "put" && pinstr[3] == 1) {
|
|
push(slots, pinstr[2])
|
|
}
|
|
}
|
|
return slots
|
|
}
|
|
|
|
// Build captured_slots for each function (and main)
|
|
var build_captured = function(fn) {
|
|
var children = find_children(fn.instructions)
|
|
var captured = {}
|
|
var bi = 0
|
|
var child_idx = 0
|
|
var child_fn = null
|
|
var pslots = null
|
|
var si = 0
|
|
while (bi < length(children)) {
|
|
child_idx = children[bi]
|
|
bi = bi + 1
|
|
if (child_idx >= 0 && child_idx < length(ir.functions)) {
|
|
child_fn = ir.functions[child_idx]
|
|
pslots = find_put_slots(child_fn.instructions)
|
|
si = 0
|
|
while (si < length(pslots)) {
|
|
captured[text(pslots[si])] = true
|
|
si = si + 1
|
|
}
|
|
}
|
|
}
|
|
return captured
|
|
}
|
|
|
|
var fi = 0
|
|
while (fi < length(ir.functions)) {
|
|
compile_fn(ir.functions[fi], fi, false)
|
|
fi = fi + 1
|
|
}
|
|
|
|
compile_fn(ir.main, -1, true)
|
|
|
|
// Assemble: data section first, then function bodies
|
|
var result = []
|
|
var di = 0
|
|
while (di < length(data_out)) {
|
|
push(result, data_out[di])
|
|
di = di + 1
|
|
}
|
|
if (length(data_out) > 0) push(result, "")
|
|
|
|
di = 0
|
|
while (di < length(out)) {
|
|
push(result, out[di])
|
|
di = di + 1
|
|
}
|
|
|
|
return text(result, "\n")
|
|
}
|
|
|
|
return qbe_emit
|