compiling
This commit is contained in:
3200
boot/qbe.cm.mcode
3200
boot/qbe.cm.mcode
File diff suppressed because it is too large
Load Diff
11792
boot/qbe_emit.cm.mcode
11792
boot/qbe_emit.cm.mcode
File diff suppressed because it is too large
Load Diff
31
compile.ce
31
compile.ce
@@ -1,7 +1,7 @@
|
||||
// compile.ce — compile a .cm module to native .dylib via QBE
|
||||
//
|
||||
// Usage:
|
||||
// cell --core . compile.ce <file.cm>
|
||||
// cell --dev compile.ce <file.cm>
|
||||
//
|
||||
// Produces <file>.dylib in the current directory.
|
||||
|
||||
@@ -9,7 +9,7 @@ var fd = use('fd')
|
||||
var os = use('os')
|
||||
|
||||
if (length(args) < 1) {
|
||||
print('usage: cell --core . compile.ce <file.cm>')
|
||||
print('usage: cell --dev compile.ce <file.cm>')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,39 +26,22 @@ var ssa_path = tmp + '.ssa'
|
||||
var s_path = tmp + '.s'
|
||||
var o_path = tmp + '.o'
|
||||
var rt_o_path = '/tmp/qbe_rt.o'
|
||||
var dylib_path = base + '.dylib'
|
||||
var dylib_path = file + '.dylib'
|
||||
var cwd = fd.getcwd()
|
||||
var rc = 0
|
||||
|
||||
// Step 1: emit QBE IL
|
||||
print('emit qbe...')
|
||||
rc = os.system('cd ' + cwd + ' && ./cell --core . --emit-qbe ' + file + ' > ' + ssa_path)
|
||||
rc = os.system('cd ' + cwd + ' && ./cell --dev qbe.ce ' + file + ' > ' + ssa_path)
|
||||
if (rc != 0) {
|
||||
print('failed to emit qbe il')
|
||||
return
|
||||
}
|
||||
|
||||
// Step 2: post-process — insert dead labels after ret/jmp, append wrapper
|
||||
// Use awk via shell to avoid blob/slurpwrite issues with long strings
|
||||
print('post-process...')
|
||||
var awk_cmd = `awk '
|
||||
need_label && /^[[:space:]]*[^@}]/ && NF > 0 {
|
||||
print "@_dead_" dead_id; dead_id++; need_label=0
|
||||
}
|
||||
/^@/ || /^}/ || NF==0 { need_label=0 }
|
||||
/^[[:space:]]*ret / || /^[[:space:]]*jmp / { need_label=1; print; next }
|
||||
{ print }
|
||||
' ` + ssa_path + ` > ` + tmp + `_fixed.ssa`
|
||||
rc = os.system(awk_cmd)
|
||||
if (rc != 0) {
|
||||
print('post-process failed')
|
||||
return
|
||||
}
|
||||
|
||||
// Append wrapper function — called as symbol(ctx) by os.dylib_symbol.
|
||||
// Step 2: append wrapper function — called as symbol(ctx) by os.dylib_symbol.
|
||||
// Delegates to cell_rt_module_entry which heap-allocates a frame
|
||||
// (so closures survive) and calls cell_main.
|
||||
var wrapper_cmd = `printf '\nexport function l $` + symbol + `(l %%ctx) {\n@entry\n %%result =l call $cell_rt_module_entry(l %%ctx)\n ret %%result\n}\n' >> ` + tmp + `_fixed.ssa`
|
||||
var wrapper_cmd = `printf '\nexport function l $` + symbol + `(l %%ctx) {\n@entry\n %%result =l call $cell_rt_module_entry(l %%ctx)\n ret %%result\n}\n' >> ` + ssa_path
|
||||
rc = os.system(wrapper_cmd)
|
||||
if (rc != 0) {
|
||||
print('wrapper append failed')
|
||||
@@ -67,7 +50,7 @@ if (rc != 0) {
|
||||
|
||||
// Step 3: compile QBE IL to assembly
|
||||
print('qbe compile...')
|
||||
rc = os.system('~/.local/bin/qbe -o ' + s_path + ' ' + tmp + '_fixed.ssa')
|
||||
rc = os.system('qbe -o ' + s_path + ' ' + ssa_path)
|
||||
if (rc != 0) {
|
||||
print('qbe compilation failed')
|
||||
return
|
||||
|
||||
79
mcode.cm
79
mcode.cm
@@ -273,19 +273,70 @@ var mcode = function(ast) {
|
||||
return node.kind == "null"
|
||||
}
|
||||
|
||||
// emit_add_decomposed: emit generic add (VM dispatches int/float/text)
|
||||
// emit_add_decomposed: emit type-dispatched add (text → concat, num → add)
|
||||
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
|
||||
var emit_add_decomposed = function() {
|
||||
// Known text+text → concat directly (skip numeric check in VM)
|
||||
if (is_known_text(_bp_ln) && is_known_text(_bp_rn)) {
|
||||
emit_3("concat", _bp_dest, _bp_left, _bp_right)
|
||||
return null
|
||||
}
|
||||
if (is_known_number(_bp_ln) && is_known_number(_bp_rn)) {
|
||||
emit_3("add", _bp_dest, _bp_left, _bp_right)
|
||||
return null
|
||||
}
|
||||
// Unknown types: emit full dispatch
|
||||
var t0 = alloc_slot()
|
||||
var t1 = alloc_slot()
|
||||
var done = gen_label("add_done")
|
||||
var check_num = gen_label("add_cn")
|
||||
|
||||
// Check text path first (since add doubles as concat)
|
||||
emit_2("is_text", t0, _bp_left)
|
||||
emit_jump_cond("jump_false", t0, check_num)
|
||||
emit_2("is_text", t1, _bp_right)
|
||||
emit_jump_cond("jump_false", t1, check_num)
|
||||
emit_3("concat", _bp_dest, _bp_left, _bp_right)
|
||||
emit_jump(done)
|
||||
|
||||
// Numeric path
|
||||
var err = gen_label("add_err")
|
||||
emit_label(check_num)
|
||||
emit_2("is_num", t0, _bp_left)
|
||||
emit_jump_cond("jump_false", t0, err)
|
||||
emit_2("is_num", t1, _bp_right)
|
||||
emit_jump_cond("jump_false", t1, err)
|
||||
emit_3("add", _bp_dest, _bp_left, _bp_right)
|
||||
emit_jump(done)
|
||||
|
||||
emit_label(err)
|
||||
emit_0("disrupt")
|
||||
emit_label(done)
|
||||
return null
|
||||
}
|
||||
|
||||
// emit_numeric_binop removed — generic ops emitted directly via passthrough
|
||||
// emit_numeric_binop: emit type-guarded numeric binary op
|
||||
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
|
||||
var emit_numeric_binop = function(op_str) {
|
||||
if (is_known_number(_bp_ln) && is_known_number(_bp_rn)) {
|
||||
emit_3(op_str, _bp_dest, _bp_left, _bp_right)
|
||||
return null
|
||||
}
|
||||
var t0 = alloc_slot()
|
||||
var t1 = alloc_slot()
|
||||
var err = gen_label("num_err")
|
||||
var done = gen_label("num_done")
|
||||
emit_2("is_num", t0, _bp_left)
|
||||
emit_jump_cond("jump_false", t0, err)
|
||||
emit_2("is_num", t1, _bp_right)
|
||||
emit_jump_cond("jump_false", t1, err)
|
||||
emit_3(op_str, _bp_dest, _bp_left, _bp_right)
|
||||
emit_jump(done)
|
||||
|
||||
emit_label(err)
|
||||
emit_0("disrupt")
|
||||
emit_label(done)
|
||||
return null
|
||||
}
|
||||
|
||||
// emit_eq_decomposed: identical -> int -> float -> text -> null -> bool -> mismatch(false)
|
||||
// reads _bp_dest, _bp_left, _bp_right from closure
|
||||
@@ -511,9 +562,23 @@ var mcode = function(ast) {
|
||||
return null
|
||||
}
|
||||
|
||||
// emit_neg_decomposed: emit generic negate (VM dispatches int/float)
|
||||
// emit_neg_decomposed: emit type-guarded negate
|
||||
var emit_neg_decomposed = function(dest, src, src_node) {
|
||||
if (is_known_number(src_node)) {
|
||||
emit_2("negate", dest, src)
|
||||
return null
|
||||
}
|
||||
var t0 = alloc_slot()
|
||||
var err = gen_label("neg_err")
|
||||
var done = gen_label("neg_done")
|
||||
emit_2("is_num", t0, src)
|
||||
emit_jump_cond("jump_false", t0, err)
|
||||
emit_2("negate", dest, src)
|
||||
emit_jump(done)
|
||||
|
||||
emit_label(err)
|
||||
emit_0("disrupt")
|
||||
emit_label(done)
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -537,9 +602,11 @@ var mcode = function(ast) {
|
||||
emit_relational("gt_int", "gt_float", "gt_text")
|
||||
} else if (op_str == "ge") {
|
||||
emit_relational("ge_int", "ge_float", "ge_text")
|
||||
} else if (op_str == "subtract" || op_str == "multiply" ||
|
||||
op_str == "divide" || op_str == "modulo" || op_str == "pow") {
|
||||
emit_numeric_binop(op_str)
|
||||
} else {
|
||||
// Passthrough for subtract, multiply, divide, modulo,
|
||||
// bitwise, pow, in, etc.
|
||||
// Passthrough for bitwise, in, etc.
|
||||
emit_3(op_str, dest, left, right)
|
||||
}
|
||||
return null
|
||||
|
||||
356
qbe.cm
356
qbe.cm
@@ -98,6 +98,7 @@ var is_text = function(p, v) {
|
||||
jmp @${p}.done
|
||||
@${p}.no
|
||||
%${p} =w copy 0
|
||||
jmp @${p}.done
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
@@ -174,6 +175,7 @@ var to_float64 = function(p, v) {
|
||||
%${p}.fbits =l or %${p}.fs63, %${p}.fe52
|
||||
%${p}.fbits =l or %${p}.fbits, %${p}.fmant
|
||||
%${p} =d cast %${p}.fbits
|
||||
jmp @${p}.done
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
@@ -199,201 +201,37 @@ var new_bool = function(p, b) {
|
||||
|
||||
// new_float64 — C call to __JS_NewFloat64(ctx, val). Result: %{p}
|
||||
var new_float64 = function(p, ctx, d) {
|
||||
return ` %${p} =l call $__JS_NewFloat64(l ${ctx}, d ${d})
|
||||
return ` %${p} =l call $qbe_new_float64(l ${ctx}, d ${d})
|
||||
`
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Arithmetic — add(p, ctx, a, b)
|
||||
// Int fast path inline, text concat and float as C calls.
|
||||
// Jumps to @disrupt on type mismatch.
|
||||
// Arithmetic — add/sub/mul/div/mod(p, ctx, a, b)
|
||||
// Simple C call wrappers. Type dispatch is handled in mcode.cm.
|
||||
// ============================================================
|
||||
|
||||
var add = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.sum =l add %${p}.ia, %${p}.ib
|
||||
%${p}.lo =w csltl %${p}.sum, ${int32_min}
|
||||
%${p}.hi =w csgtl %${p}.sum, ${int32_max}
|
||||
%${p}.ov =w or %${p}.lo, %${p}.hi
|
||||
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
|
||||
@${p}.int_ok
|
||||
%${p}.rw =w copy %${p}.sum
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.int_overflow
|
||||
%${p}.fd =d sltof %${p}.sum
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_text =w call $JS_IsText(l ${a})
|
||||
%${p}.b_is_text =w call $JS_IsText(l ${b})
|
||||
%${p}.both_text =w and %${p}.a_is_text, %${p}.b_is_text
|
||||
jnz %${p}.both_text, @${p}.text_path, @${p}.chk_num
|
||||
@${p}.text_path
|
||||
%${p} =l call $JS_ConcatString(l ${ctx}, l ${a}, l ${b})
|
||||
jmp @${p}.done
|
||||
@${p}.chk_num
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_add(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_add(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var sub = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.diff =l sub %${p}.ia, %${p}.ib
|
||||
%${p}.lo =w csltl %${p}.diff, ${int32_min}
|
||||
%${p}.hi =w csgtl %${p}.diff, ${int32_max}
|
||||
%${p}.ov =w or %${p}.lo, %${p}.hi
|
||||
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
|
||||
@${p}.int_ok
|
||||
%${p}.rw =w copy %${p}.diff
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.int_overflow
|
||||
%${p}.fd =d sltof %${p}.diff
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_sub(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_sub(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var mul = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.prod =l mul %${p}.ia, %${p}.ib
|
||||
%${p}.lo =w csltl %${p}.prod, ${int32_min}
|
||||
%${p}.hi =w csgtl %${p}.prod, ${int32_max}
|
||||
%${p}.ov =w or %${p}.lo, %${p}.hi
|
||||
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
|
||||
@${p}.int_ok
|
||||
%${p}.rw =w copy %${p}.prod
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.int_overflow
|
||||
%${p}.fd =d sltof %${p}.prod
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_mul(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_mul(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var div = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =w copy 0
|
||||
%${p}.tmp =l sar ${a}, 1
|
||||
%${p}.ia =w copy %${p}.tmp
|
||||
%${p}.ib =w copy 0
|
||||
%${p}.tmp2 =l sar ${b}, 1
|
||||
%${p}.ib =w copy %${p}.tmp2
|
||||
%${p}.div0 =w ceqw %${p}.ib, 0
|
||||
jnz %${p}.div0, @${p}.ret_null, @${p}.chk_exact
|
||||
@${p}.ret_null
|
||||
%${p} =l copy ${js_null}
|
||||
jmp @${p}.done
|
||||
@${p}.chk_exact
|
||||
%${p}.rem =w rem %${p}.ia, %${p}.ib
|
||||
%${p}.exact =w ceqw %${p}.rem, 0
|
||||
jnz %${p}.exact, @${p}.int_div, @${p}.int_to_float
|
||||
@${p}.int_div
|
||||
%${p}.q =w div %${p}.ia, %${p}.ib
|
||||
%${p}.qext =l extuw %${p}.q
|
||||
%${p} =l shl %${p}.qext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.int_to_float
|
||||
%${p}.da =d swtof %${p}.ia
|
||||
%${p}.db =d swtof %${p}.ib
|
||||
%${p}.dr =d div %${p}.da, %${p}.db
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.dr)
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_div(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_div(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var mod = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =w copy 0
|
||||
%${p}.tmp =l sar ${a}, 1
|
||||
%${p}.ia =w copy %${p}.tmp
|
||||
%${p}.ib =w copy 0
|
||||
%${p}.tmp2 =l sar ${b}, 1
|
||||
%${p}.ib =w copy %${p}.tmp2
|
||||
%${p}.div0 =w ceqw %${p}.ib, 0
|
||||
jnz %${p}.div0, @${p}.ret_null, @${p}.do_mod
|
||||
@${p}.ret_null
|
||||
%${p} =l copy ${js_null}
|
||||
jmp @${p}.done
|
||||
@${p}.do_mod
|
||||
%${p}.r =w rem %${p}.ia, %${p}.ib
|
||||
%${p}.rext =l extuw %${p}.r
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_mod(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_mod(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
@@ -484,6 +322,7 @@ var cmp = function(p, ctx, a, b) {
|
||||
jmp @${p}.done
|
||||
@${p}.mismatch
|
||||
%${p} =l copy ${mismatch_val}
|
||||
jmp @${p}.done
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
@@ -518,90 +357,28 @@ var gt = function(p, ctx, a, b) {
|
||||
|
||||
var ge = function(p, ctx, a, b) {
|
||||
_qflags = {int_cmp_op: "csgew", float_id: 5, is_eq: false, is_ne: false, null_true: true}
|
||||
return cmp(p, ctx, a, b)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Unary Ops
|
||||
// ============================================================
|
||||
|
||||
// neg(p, ctx, v) — negate. Int fast path (INT32_MIN edge case), else C call.
|
||||
// neg(p, ctx, v) — negate via C call (type guards in mcode)
|
||||
var neg = function(p, ctx, v) {
|
||||
return `@${p}.start
|
||||
%${p}.tag =l and ${v}, 1
|
||||
%${p}.is_int =w ceql %${p}.tag, 0
|
||||
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
|
||||
@${p}.int_path
|
||||
%${p}.sl =l sar ${v}, 1
|
||||
%${p}.iw =w copy %${p}.sl
|
||||
%${p}.is_min =w ceqw %${p}.iw, ${int32_min}
|
||||
jnz %${p}.is_min, @${p}.min_overflow, @${p}.int_ok
|
||||
@${p}.min_overflow
|
||||
%${p}.fd =d swtof %${p}.iw
|
||||
%${p}.fdn =d neg %${p}.fd
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fdn)
|
||||
jmp @${p}.done
|
||||
@${p}.int_ok
|
||||
%${p}.ni =w sub 0, %${p}.iw
|
||||
%${p}.niext =l extuw %${p}.ni
|
||||
%${p} =l shl %${p}.niext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_neg(l ${ctx}, l ${v})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_neg(l ${ctx}, l ${v})
|
||||
`
|
||||
}
|
||||
|
||||
// inc(p, ctx, v) — increment. Int fast path (INT32_MAX edge case), else C call.
|
||||
// inc(p, ctx, v) — increment via C call (type guards in mcode)
|
||||
var inc = function(p, ctx, v) {
|
||||
return `@${p}.start
|
||||
%${p}.tag =l and ${v}, 1
|
||||
%${p}.is_int =w ceql %${p}.tag, 0
|
||||
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
|
||||
@${p}.int_path
|
||||
%${p}.sl =l sar ${v}, 1
|
||||
%${p}.iw =w copy %${p}.sl
|
||||
%${p}.is_max =w ceqw %${p}.iw, ${int32_max}
|
||||
jnz %${p}.is_max, @${p}.max_overflow, @${p}.int_ok
|
||||
@${p}.max_overflow
|
||||
%${p}.fd =d swtof %${p}.iw
|
||||
%${p}.fd1 =d add %${p}.fd, d_1.0
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd1)
|
||||
jmp @${p}.done
|
||||
@${p}.int_ok
|
||||
%${p}.ni =w add %${p}.iw, 1
|
||||
%${p}.niext =l extuw %${p}.ni
|
||||
%${p} =l shl %${p}.niext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_inc(l ${ctx}, l ${v})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_inc(l ${ctx}, l ${v})
|
||||
`
|
||||
}
|
||||
|
||||
// dec(p, ctx, v) — decrement. Int fast path (INT32_MIN edge case), else C call.
|
||||
// dec(p, ctx, v) — decrement via C call (type guards in mcode)
|
||||
var dec = function(p, ctx, v) {
|
||||
return `@${p}.start
|
||||
%${p}.tag =l and ${v}, 1
|
||||
%${p}.is_int =w ceql %${p}.tag, 0
|
||||
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
|
||||
@${p}.int_path
|
||||
%${p}.sl =l sar ${v}, 1
|
||||
%${p}.iw =w copy %${p}.sl
|
||||
%${p}.is_min =w ceqw %${p}.iw, ${int32_min}
|
||||
jnz %${p}.is_min, @${p}.min_overflow, @${p}.int_ok
|
||||
@${p}.min_overflow
|
||||
%${p}.fd =d swtof %${p}.iw
|
||||
%${p}.fd1 =d sub %${p}.fd, d_1.0
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd1)
|
||||
jmp @${p}.done
|
||||
@${p}.int_ok
|
||||
%${p}.ni =w sub %${p}.iw, 1
|
||||
%${p}.niext =l extuw %${p}.ni
|
||||
%${p} =l shl %${p}.niext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_dec(l ${ctx}, l ${v})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_dec(l ${ctx}, l ${v})
|
||||
`
|
||||
}
|
||||
|
||||
@@ -615,22 +392,9 @@ var lnot = function(p, ctx, v) {
|
||||
`
|
||||
}
|
||||
|
||||
// bnot(p, ctx, v) — bitwise not. Convert to int32, ~, re-tag.
|
||||
// bnot(p, ctx, v) — bitwise not via C call
|
||||
var bnot = function(p, ctx, v) {
|
||||
return `@${p}.start
|
||||
%${p}.tag =l and ${v}, 1
|
||||
%${p}.is_int =w ceql %${p}.tag, 0
|
||||
jnz %${p}.is_int, @${p}.int_path, @${p}.slow_path
|
||||
@${p}.int_path
|
||||
%${p}.sl =l sar ${v}, 1
|
||||
%${p}.iw =w copy %${p}.sl
|
||||
%${p}.nw =w xor %${p}.iw, -1
|
||||
%${p}.nex =l extuw %${p}.nw
|
||||
%${p} =l shl %${p}.nex, 1
|
||||
jmp @${p}.done
|
||||
@${p}.slow_path
|
||||
%${p} =l call $qbe_bnot(l ${ctx}, l ${v})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_bnot(l ${ctx}, l ${v})
|
||||
`
|
||||
}
|
||||
|
||||
@@ -639,92 +403,34 @@ var bnot = function(p, ctx, v) {
|
||||
// Both operands must be numeric. Int fast path, float -> convert to int32.
|
||||
// ============================================================
|
||||
|
||||
// reads _qop from closure
|
||||
var bitwise_op = function(p, ctx, a, b) {
|
||||
var qbe_op = _qop
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.slow_path, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.iaw =w copy %${p}.ia
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.ibw =w copy %${p}.ib
|
||||
%${p}.rw =w ${qbe_op} %${p}.iaw, %${p}.ibw
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.slow_path
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_to_int, @disrupt
|
||||
@${p}.float_to_int
|
||||
%${p} =l call $qbe_bitwise_${qbe_op}(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
var band = function(p, ctx, a, b) {
|
||||
return ` %${p} =l call $qbe_bitwise_and(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var band = function(p, ctx, a, b) {
|
||||
_qop = "and"
|
||||
return bitwise_op(p, ctx, a, b)
|
||||
}
|
||||
|
||||
var bor = function(p, ctx, a, b) {
|
||||
_qop = "or"
|
||||
return bitwise_op(p, ctx, a, b)
|
||||
return ` %${p} =l call $qbe_bitwise_or(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var bxor = function(p, ctx, a, b) {
|
||||
_qop = "xor"
|
||||
return bitwise_op(p, ctx, a, b)
|
||||
}
|
||||
|
||||
// Shift ops: mask shift amount to 5 bits (& 31)
|
||||
// reads _qop from closure
|
||||
var shift_op = function(p, ctx, a, b) {
|
||||
var qbe_op = _qop
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.slow_path, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.iaw =w copy %${p}.ia
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.ibw =w copy %${p}.ib
|
||||
%${p}.sh =w and %${p}.ibw, 31
|
||||
%${p}.rw =w ${qbe_op} %${p}.iaw, %${p}.sh
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.slow_path
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_to_int, @disrupt
|
||||
@${p}.float_to_int
|
||||
%${p} =l call $qbe_shift_${qbe_op}(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_bitwise_xor(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var shl = function(p, ctx, a, b) {
|
||||
_qop = "shl"
|
||||
return shift_op(p, ctx, a, b)
|
||||
return ` %${p} =l call $qbe_shift_shl(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var shr = function(p, ctx, a, b) {
|
||||
_qop = "sar"
|
||||
return shift_op(p, ctx, a, b)
|
||||
return ` %${p} =l call $qbe_shift_sar(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var ushr = function(p, ctx, a, b) {
|
||||
_qop = "shr"
|
||||
return shift_op(p, ctx, a, b)
|
||||
return ` %${p} =l call $qbe_shift_shr(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
258
qbe_emit.cm
258
qbe_emit.cm
@@ -76,6 +76,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
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 ? "cell_main" : "cell_fn_" + text(fn_idx)
|
||||
name = sanitize(name)
|
||||
var i = 0
|
||||
@@ -88,6 +89,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
var p = null
|
||||
var pn = null
|
||||
var sl = null
|
||||
var lbl = null
|
||||
var fop_id = 0
|
||||
var nr_elems = 0
|
||||
var ei = 0
|
||||
@@ -113,22 +115,45 @@ var qbe_emit = function(ir, qbe) {
|
||||
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
|
||||
// Labels are plain strings; skip _nop_ur_ pseudo-labels from streamline
|
||||
if (is_text(instr)) {
|
||||
emit("@" + sanitize(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 ---
|
||||
|
||||
@@ -157,11 +182,11 @@ var qbe_emit = function(ir, qbe) {
|
||||
if (is_integer(a2)) {
|
||||
emit(` ${s(a1)} =l copy ${text(a2 * 2)}`)
|
||||
} else {
|
||||
emit(` ${s(a1)} =l call $__JS_NewFloat64(l %ctx, d d_${text(a2)})`)
|
||||
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 $JS_NewString(l %ctx, l ${sl})`)
|
||||
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)
|
||||
@@ -170,13 +195,13 @@ var qbe_emit = function(ir, qbe) {
|
||||
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 $__JS_NewFloat64(l %ctx, d d_${text(a2.number)})`)
|
||||
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 $JS_NewString(l %ctx, l ${sl})`)
|
||||
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") {
|
||||
@@ -205,7 +230,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
|
||||
if (op == "add") {
|
||||
p = fresh()
|
||||
emit(qbe.add(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` %${p} =l call $cell_rt_add(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
@@ -246,6 +271,12 @@ var qbe_emit = function(ir, qbe) {
|
||||
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") {
|
||||
@@ -305,6 +336,46 @@ var qbe_emit = function(ir, qbe) {
|
||||
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) ---
|
||||
|
||||
@@ -367,14 +438,30 @@ var qbe_emit = function(ir, qbe) {
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "lt_float" || op == "gt_float" || op == "le_float" || op == "ge_float") {
|
||||
if (op == "lt_float") {
|
||||
p = fresh()
|
||||
fop_id = 0
|
||||
if (op == "lt_float") fop_id = 2
|
||||
else if (op == "le_float") fop_id = 3
|
||||
else if (op == "gt_float") fop_id = 4
|
||||
else if (op == "ge_float") fop_id = 5
|
||||
emit(qbe.cmp_float != null ? qbe.cmp_float(p, "%ctx", s(a2), s(a3), fop_id) : ` %${p} =l call $qbe_float_cmp(l %ctx, w ${text(fop_id)}, l ${s(a2)}, l ${s(a3)})`)
|
||||
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
|
||||
@@ -494,7 +581,10 @@ var qbe_emit = function(ir, qbe) {
|
||||
// --- Property access — runtime calls ---
|
||||
|
||||
if (op == "load_field") {
|
||||
pn = prop_name(a3)
|
||||
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})`)
|
||||
@@ -510,13 +600,28 @@ var qbe_emit = function(ir, qbe) {
|
||||
continue
|
||||
}
|
||||
if (op == "load_dynamic") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_load_dynamic(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
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 = prop_name(a3)
|
||||
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})`)
|
||||
@@ -532,19 +637,30 @@ var qbe_emit = function(ir, qbe) {
|
||||
}
|
||||
if (op == "store_dynamic") {
|
||||
// IR: ["store_dynamic", obj, val, key] → C: (ctx, val, obj, key)
|
||||
emit(` call $cell_rt_store_dynamic(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`)
|
||||
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") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_get_closure(l %ctx, l %fp, l ${text(a2)}, l ${text(a3)})`)
|
||||
// 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") {
|
||||
emit(` call $cell_rt_put_closure(l %ctx, l %fp, l ${s(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -552,6 +668,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
|
||||
if (op == "jump") {
|
||||
emit(` jmp @${sanitize(a1)}`)
|
||||
last_was_term = true
|
||||
continue
|
||||
}
|
||||
if (op == "jump_true") {
|
||||
@@ -611,6 +728,13 @@ var qbe_emit = function(ir, qbe) {
|
||||
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") {
|
||||
@@ -621,6 +745,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
if (op == "goinvoke") {
|
||||
emit(` %_goret =l call $cell_rt_goinvoke(l %ctx, l ${s(a1)})`)
|
||||
emit(` ret %_goret`)
|
||||
last_was_term = true
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -664,19 +789,38 @@ var qbe_emit = function(ir, qbe) {
|
||||
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") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_delete(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
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
|
||||
}
|
||||
@@ -690,6 +834,14 @@ var qbe_emit = function(ir, qbe) {
|
||||
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("")
|
||||
}
|
||||
@@ -698,6 +850,70 @@ var qbe_emit = function(ir, qbe) {
|
||||
// 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)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// run_native.ce — load a module both interpreted and native, compare speed
|
||||
//
|
||||
// Usage:
|
||||
// cell --core . run_native.ce <module>
|
||||
// cell --dev run_native.ce <module>
|
||||
//
|
||||
// Loads <module>.cm via use() (interpreted) and <module>.dylib (native),
|
||||
// Loads <module>.cm via use() (interpreted) and <module>.cm.dylib (native),
|
||||
// runs both and compares results and timing.
|
||||
|
||||
var os = use('os')
|
||||
|
||||
if (length(args) < 1) {
|
||||
print('usage: cell --core . run_native.ce <module>')
|
||||
print(' e.g. cell --core . run_native.ce num_torture')
|
||||
print('usage: cell --dev run_native.ce <module>')
|
||||
print(' e.g. cell --dev run_native.ce num_torture')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ if (ends_with(name, '.cm')) {
|
||||
|
||||
var safe = replace(replace(name, '/', '_'), '-', '_')
|
||||
var symbol = 'js_' + safe + '_use'
|
||||
var dylib_path = './' + name + '.dylib'
|
||||
var dylib_path = './' + name + '.cm.dylib'
|
||||
var fd = use('fd')
|
||||
|
||||
// --- Test argument for function-returning modules ---
|
||||
|
||||
@@ -9,6 +9,15 @@
|
||||
#include "quickjs-internal.h"
|
||||
#include <math.h>
|
||||
|
||||
/* Non-inline wrappers for static inline functions in quickjs.h */
|
||||
JSValue qbe_new_float64(JSContext *ctx, double d) {
|
||||
return __JS_NewFloat64(ctx, d);
|
||||
}
|
||||
|
||||
JSValue qbe_new_string(JSContext *ctx, const char *str) {
|
||||
return JS_NewString(ctx, str);
|
||||
}
|
||||
|
||||
/* Comparison op IDs (must match qbe.cm float_cmp_op_id values) */
|
||||
enum {
|
||||
QBE_CMP_EQ = 0,
|
||||
@@ -42,6 +51,16 @@ JSValue qbe_float_add(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return qbe_float_binop(ctx, a, b, op_add);
|
||||
}
|
||||
|
||||
/* Generic add: concat if both text, float add if both numeric, else type error */
|
||||
JSValue cell_rt_add(JSContext *ctx, JSValue a, JSValue b) {
|
||||
if (JS_IsText(a) && JS_IsText(b))
|
||||
return JS_ConcatString(ctx, a, b);
|
||||
if (JS_IsNumber(a) && JS_IsNumber(b))
|
||||
return qbe_float_binop(ctx, a, b, op_add);
|
||||
JS_ThrowTypeError(ctx, "cannot add incompatible types");
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return qbe_float_binop(ctx, a, b, op_sub);
|
||||
}
|
||||
@@ -446,6 +465,15 @@ JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_NewBool(ctx, a != b);
|
||||
}
|
||||
|
||||
/* --- Type check: is_proxy (function with arity 2) --- */
|
||||
|
||||
int cell_rt_is_proxy(JSContext *ctx, JSValue v) {
|
||||
(void)ctx;
|
||||
if (!JS_IsFunction(v)) return 0;
|
||||
JSFunction *fn = JS_VALUE_GET_FUNCTION(v);
|
||||
return fn->length == 2;
|
||||
}
|
||||
|
||||
/* --- Disruption --- */
|
||||
|
||||
void cell_rt_disrupt(JSContext *ctx) {
|
||||
|
||||
6
time.cm
6
time.cm
@@ -5,9 +5,9 @@ var now = time.now
|
||||
var computer_zone = time.computer_zone
|
||||
var computer_dst = time.computer_dst
|
||||
|
||||
delete time.now
|
||||
delete time.computer_zone
|
||||
delete time.computer_dst
|
||||
//delete time.now
|
||||
//delete time.computer_zone
|
||||
//delete time.computer_dst
|
||||
|
||||
time.second = 1
|
||||
time.minute = 60
|
||||
|
||||
Reference in New Issue
Block a user