2 Commits

Author SHA1 Message Date
John Alanbrook
dcc9659e6b Merge branch 'runtime_rework' into fix_gc 2026-02-14 22:11:31 -06:00
John Alanbrook
2f7f2233b8 compiling 2026-02-14 22:08:55 -06:00
20 changed files with 277939 additions and 230224 deletions

183
bench_native.ce Normal file
View File

@@ -0,0 +1,183 @@
// bench_native.ce — compare VM vs native execution speed
//
// Usage:
// cell --dev bench_native.ce <module.cm> [iterations]
//
// Compiles (if needed) and benchmarks a module via both VM and native dylib.
// Reports median/mean timing per benchmark + speedup ratio.
var os = use('os')
var fd = use('fd')
if (length(args) < 1) {
print('usage: cell --dev bench_native.ce <module.cm> [iterations]')
return
}
var file = args[0]
var name = file
if (ends_with(name, '.cm')) {
name = text(name, 0, length(name) - 3)
}
var iterations = 11
if (length(args) > 1) {
iterations = number(args[1])
}
def WARMUP = 3
var safe = replace(replace(name, '/', '_'), '-', '_')
var symbol = 'js_' + safe + '_use'
var dylib_path = './' + file + '.dylib'
// --- Statistics ---
var stat_sort = function(arr) {
return sort(arr)
}
var stat_median = function(arr) {
if (length(arr) == 0) return 0
var sorted = stat_sort(arr)
var mid = floor(length(arr) / 2)
if (length(arr) % 2 == 0) {
return (sorted[mid - 1] + sorted[mid]) / 2
}
return sorted[mid]
}
var stat_mean = function(arr) {
if (length(arr) == 0) return 0
var sum = reduce(arr, function(a, b) { return a + b })
return sum / length(arr)
}
var format_ns = function(ns) {
if (ns < 1000) return text(round(ns)) + 'ns'
if (ns < 1000000) return text(round(ns / 1000 * 100) / 100) + 'us'
if (ns < 1000000000) return text(round(ns / 1000000 * 100) / 100) + 'ms'
return text(round(ns / 1000000000 * 100) / 100) + 's'
}
// --- Collect benchmarks from module ---
var collect_benches = function(mod) {
var benches = []
if (is_function(mod)) {
push(benches, {name: 'main', fn: mod})
} else if (is_object(mod)) {
var keys = array(mod)
var i = 0
while (i < length(keys)) {
var k = keys[i]
if (is_function(mod[k])) {
push(benches, {name: k, fn: mod[k]})
}
i = i + 1
}
}
return benches
}
// --- Run one benchmark function ---
var run_bench = function(fn, label) {
var samples = []
var i = 0
var t1 = 0
var t2 = 0
// warmup
i = 0
while (i < WARMUP) {
fn(1)
i = i + 1
}
// collect samples
i = 0
while (i < iterations) {
t1 = os.now()
fn(1)
t2 = os.now()
push(samples, t2 - t1)
i = i + 1
}
return {
label: label,
median: stat_median(samples),
mean: stat_mean(samples)
}
}
// --- Load VM module ---
print('loading VM module: ' + file)
var vm_mod = use(name)
var vm_benches = collect_benches(vm_mod)
if (length(vm_benches) == 0) {
print('no benchmarkable functions found in ' + file)
return
}
// --- Load native module ---
var native_mod = null
var native_benches = []
var has_native = fd.is_file(dylib_path)
if (has_native) {
print('loading native module: ' + dylib_path)
var lib = os.dylib_open(dylib_path)
native_mod = os.dylib_symbol(lib, symbol)
native_benches = collect_benches(native_mod)
} else {
print('no ' + dylib_path + ' found -- VM-only benchmarking')
print(' hint: cell --dev compile.ce ' + file)
}
// --- Run benchmarks ---
print('')
print('samples: ' + text(iterations) + ' (warmup: ' + text(WARMUP) + ')')
print('')
var pad = function(s, n) {
while (length(s) < n) s = s + ' '
return s
}
var i = 0
while (i < length(vm_benches)) {
var b = vm_benches[i]
var vm_result = run_bench(b.fn, 'vm')
print(pad(b.name, 20) + ' VM: ' + pad(format_ns(vm_result.median), 12) + ' (median) ' + format_ns(vm_result.mean) + ' (mean)')
// find matching native bench
var j = 0
var found = false
while (j < length(native_benches)) {
if (native_benches[j].name == b.name) {
var nat_result = run_bench(native_benches[j].fn, 'native')
print(pad('', 20) + ' NT: ' + pad(format_ns(nat_result.median), 12) + ' (median) ' + format_ns(nat_result.mean) + ' (mean)')
if (nat_result.median > 0) {
var speedup = vm_result.median / nat_result.median
print(pad('', 20) + ' speedup: ' + text(round(speedup * 100) / 100) + 'x')
}
found = true
}
j = j + 1
}
if (has_native && !found) {
print(pad('', 20) + ' NT: (no matching function)')
}
print('')
i = i + 1
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
// compile.ce — compile a .cm module to native .dylib via QBE // compile.ce — compile a .cm module to native .dylib via QBE
// //
// Usage: // Usage:
// cell --core . compile.ce <file.cm> // cell --dev compile.ce <file.cm>
// //
// Produces <file>.dylib in the current directory. // Produces <file>.dylib in the current directory.
@@ -9,7 +9,7 @@ var fd = use('fd')
var os = use('os') var os = use('os')
if (length(args) < 1) { if (length(args) < 1) {
print('usage: cell --core . compile.ce <file.cm>') print('usage: cell --dev compile.ce <file.cm>')
return return
} }
@@ -26,39 +26,22 @@ var ssa_path = tmp + '.ssa'
var s_path = tmp + '.s' var s_path = tmp + '.s'
var o_path = tmp + '.o' var o_path = tmp + '.o'
var rt_o_path = '/tmp/qbe_rt.o' var rt_o_path = '/tmp/qbe_rt.o'
var dylib_path = base + '.dylib' var dylib_path = file + '.dylib'
var cwd = fd.getcwd() var cwd = fd.getcwd()
var rc = 0 var rc = 0
// Step 1: emit QBE IL // Step 1: emit QBE IL
print('emit qbe...') 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) { if (rc != 0) {
print('failed to emit qbe il') print('failed to emit qbe il')
return return
} }
// Step 2: post-process — insert dead labels after ret/jmp, append wrapper // Step 2: append wrapper function — called as symbol(ctx) by os.dylib_symbol.
// 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.
// Delegates to cell_rt_module_entry which heap-allocates a frame // Delegates to cell_rt_module_entry which heap-allocates a frame
// (so closures survive) and calls cell_main. // (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) rc = os.system(wrapper_cmd)
if (rc != 0) { if (rc != 0) {
print('wrapper append failed') print('wrapper append failed')
@@ -67,7 +50,7 @@ if (rc != 0) {
// Step 3: compile QBE IL to assembly // Step 3: compile QBE IL to assembly
print('qbe compile...') 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) { if (rc != 0) {
print('qbe compilation failed') print('qbe compilation failed')
return return

98
compile_seed.ce Normal file
View File

@@ -0,0 +1,98 @@
// compile_seed.ce — compile a .cm module to native .dylib via QBE (seed mode)
// Usage: ./cell --dev --seed compile_seed <file.cm>
var fd = use("fd")
var os = use("os")
var tokenize = use("tokenize")
var parse = use("parse")
var fold = use("fold")
var mcode = use("mcode")
var streamline = use("streamline")
var qbe_macros = use("qbe")
var qbe_emit = use("qbe_emit")
if (length(args) < 1) {
print("usage: cell --dev --seed compile_seed <file.cm>")
disrupt
}
var file = args[0]
var base = file
if (ends_with(base, ".cm")) {
base = text(base, 0, length(base) - 3)
} else if (ends_with(base, ".ce")) {
base = text(base, 0, length(base) - 3)
}
var safe = replace(replace(replace(base, "/", "_"), "-", "_"), ".", "_")
var symbol = "js_" + safe + "_use"
var tmp = "/tmp/qbe_" + safe
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 = file + ".dylib"
var rc = 0
// Step 1: compile to QBE IL
print("compiling " + file + " to QBE IL...")
var src = text(fd.slurp(file))
var result = tokenize(src, file)
var ast = parse(result.tokens, src, file, tokenize)
var folded = fold(ast)
var compiled = mcode(folded)
var optimized = streamline(compiled)
var il = qbe_emit(optimized, qbe_macros)
// Step 2: append wrapper function
var wrapper = `
export function l $${symbol}(l %ctx) {
@entry
%result =l call $cell_rt_module_entry(l %ctx)
ret %result
}
`
il = il + wrapper
// Write IL to file — remove old file first to avoid leftover content
if (fd.is_file(ssa_path)) fd.unlink(ssa_path)
var out_fd = fd.open(ssa_path, 1537, 420)
fd.write(out_fd, il)
fd.close(out_fd)
print("wrote " + ssa_path + " (" + text(length(il)) + " bytes)")
// Step 3: compile QBE IL to assembly
print("qbe compile...")
rc = os.system("qbe -o " + s_path + " " + ssa_path)
if (rc != 0) {
print("qbe compilation failed")
disrupt
}
// Step 4: assemble
print("assemble...")
rc = os.system("cc -c " + s_path + " -o " + o_path)
if (rc != 0) {
print("assembly failed")
disrupt
}
// Step 5: compile runtime stubs
if (!fd.is_file(rt_o_path)) {
print("compile runtime stubs...")
rc = os.system("cc -c source/qbe_helpers.c -o " + rt_o_path + " -fPIC -Isource")
if (rc != 0) {
print("runtime stubs compilation failed")
disrupt
}
}
// Step 6: link dylib
print("link...")
rc = os.system("cc -shared -fPIC -undefined dynamic_lookup " + o_path + " " + rt_o_path + " -o " + dylib_path)
if (rc != 0) {
print("linking failed")
disrupt
}
print("built: " + dylib_path)

View File

@@ -280,19 +280,70 @@ var mcode = function(ast) {
return node.kind == "null" 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 // reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
var emit_add_decomposed = function() { 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)) { if (is_known_text(_bp_ln) && is_known_text(_bp_rn)) {
emit_3("concat", _bp_dest, _bp_left, _bp_right) emit_3("concat", _bp_dest, _bp_left, _bp_right)
return null return null
} }
if (is_known_number(_bp_ln) && is_known_number(_bp_rn)) {
emit_3("add", _bp_dest, _bp_left, _bp_right) emit_3("add", _bp_dest, _bp_left, _bp_right)
return null 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")
// emit_numeric_binop removed — generic ops emitted directly via passthrough // 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: 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) // emit_eq_decomposed: identical -> int -> float -> text -> null -> bool -> mismatch(false)
// reads _bp_dest, _bp_left, _bp_right from closure // reads _bp_dest, _bp_left, _bp_right from closure
@@ -518,11 +569,25 @@ var mcode = function(ast) {
return null 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) { var emit_neg_decomposed = function(dest, src, src_node) {
if (is_known_number(src_node)) {
emit_2("negate", dest, src) emit_2("negate", dest, src)
return null 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
}
// Central router: maps op string to decomposition helper // Central router: maps op string to decomposition helper
// Sets _bp_* closure vars then calls helper with reduced args // Sets _bp_* closure vars then calls helper with reduced args
@@ -547,9 +612,11 @@ var mcode = function(ast) {
rel = relational_ops[op_str] rel = relational_ops[op_str]
if (rel != null) { if (rel != null) {
emit_relational(rel[0], rel[1], rel[2]) emit_relational(rel[0], rel[1], rel[2])
} else if (op_str == "subtract" || op_str == "multiply" ||
op_str == "divide" || op_str == "modulo" || op_str == "pow") {
emit_numeric_binop(op_str)
} else { } else {
// Passthrough for subtract, multiply, divide, modulo, // Passthrough for bitwise, in, etc.
// bitwise, pow, in, etc.
emit_3(op_str, dest, left, right) emit_3(op_str, dest, left, right)
} }
} }

356
qbe.cm
View File

@@ -98,6 +98,7 @@ var is_text = function(p, v) {
jmp @${p}.done jmp @${p}.done
@${p}.no @${p}.no
%${p} =w copy 0 %${p} =w copy 0
jmp @${p}.done
@${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}.fs63, %${p}.fe52
%${p}.fbits =l or %${p}.fbits, %${p}.fmant %${p}.fbits =l or %${p}.fbits, %${p}.fmant
%${p} =d cast %${p}.fbits %${p} =d cast %${p}.fbits
jmp @${p}.done
@${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} // new_float64 — C call to __JS_NewFloat64(ctx, val). Result: %{p}
var new_float64 = function(p, ctx, d) { 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) // Arithmetic — add/sub/mul/div/mod(p, ctx, a, b)
// Int fast path inline, text concat and float as C calls. // Simple C call wrappers. Type dispatch is handled in mcode.cm.
// Jumps to @disrupt on type mismatch.
// ============================================================ // ============================================================
var add = function(p, ctx, a, b) { var add = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_add(l ${ctx}, l ${a}, l ${b})
%${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
` `
} }
var sub = function(p, ctx, a, b) { var sub = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_sub(l ${ctx}, l ${a}, l ${b})
%${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
` `
} }
var mul = function(p, ctx, a, b) { var mul = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_mul(l ${ctx}, l ${a}, l ${b})
%${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
` `
} }
var div = function(p, ctx, a, b) { var div = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_div(l ${ctx}, l ${a}, l ${b})
%${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
` `
} }
var mod = function(p, ctx, a, b) { var mod = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_mod(l ${ctx}, l ${a}, l ${b})
%${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
` `
} }
@@ -484,6 +322,7 @@ var cmp = function(p, ctx, a, b) {
jmp @${p}.done jmp @${p}.done
@${p}.mismatch @${p}.mismatch
%${p} =l copy ${mismatch_val} %${p} =l copy ${mismatch_val}
jmp @${p}.done
@${p}.done @${p}.done
` `
} }
@@ -518,90 +357,28 @@ var gt = function(p, ctx, a, b) {
var ge = 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} _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 // 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) { var neg = function(p, ctx, v) {
return `@${p}.start return ` %${p} =l call $qbe_float_neg(l ${ctx}, l ${v})
%${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
` `
} }
// 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) { var inc = function(p, ctx, v) {
return `@${p}.start return ` %${p} =l call $qbe_float_inc(l ${ctx}, l ${v})
%${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
` `
} }
// 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) { var dec = function(p, ctx, v) {
return `@${p}.start return ` %${p} =l call $qbe_float_dec(l ${ctx}, l ${v})
%${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
` `
} }
@@ -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) { var bnot = function(p, ctx, v) {
return `@${p}.start return ` %${p} =l call $qbe_bnot(l ${ctx}, l ${v})
%${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
` `
} }
@@ -639,92 +403,34 @@ var bnot = function(p, ctx, v) {
// Both operands must be numeric. Int fast path, float -> convert to int32. // Both operands must be numeric. Int fast path, float -> convert to int32.
// ============================================================ // ============================================================
// reads _qop from closure var band = function(p, ctx, a, b) {
var bitwise_op = function(p, ctx, a, b) { return ` %${p} =l call $qbe_bitwise_and(l ${ctx}, l ${a}, l ${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) {
_qop = "and"
return bitwise_op(p, ctx, a, b)
}
var bor = function(p, ctx, a, b) { var bor = function(p, ctx, a, b) {
_qop = "or" return ` %${p} =l call $qbe_bitwise_or(l ${ctx}, l ${a}, l ${b})
return bitwise_op(p, ctx, a, b) `
} }
var bxor = function(p, ctx, a, b) { var bxor = function(p, ctx, a, b) {
_qop = "xor" return ` %${p} =l call $qbe_bitwise_xor(l ${ctx}, l ${a}, l ${b})
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
` `
} }
var shl = function(p, ctx, a, b) { var shl = function(p, ctx, a, b) {
_qop = "shl" return ` %${p} =l call $qbe_shift_shl(l ${ctx}, l ${a}, l ${b})
return shift_op(p, ctx, a, b) `
} }
var shr = function(p, ctx, a, b) { var shr = function(p, ctx, a, b) {
_qop = "sar" return ` %${p} =l call $qbe_shift_sar(l ${ctx}, l ${a}, l ${b})
return shift_op(p, ctx, a, b) `
} }
var ushr = function(p, ctx, a, b) { var ushr = function(p, ctx, a, b) {
_qop = "shr" return ` %${p} =l call $qbe_shift_shr(l ${ctx}, l ${a}, l ${b})
return shift_op(p, ctx, a, b) `
} }
// ============================================================ // ============================================================

View File

@@ -76,6 +76,7 @@ var qbe_emit = function(ir, qbe) {
var instrs = fn.instructions var instrs = fn.instructions
var nr_slots = fn.nr_slots var nr_slots = fn.nr_slots
var nr_args = fn.nr_args var nr_args = fn.nr_args
var captured = build_captured(fn)
var name = is_main ? "cell_main" : "cell_fn_" + text(fn_idx) var name = is_main ? "cell_main" : "cell_fn_" + text(fn_idx)
name = sanitize(name) name = sanitize(name)
var i = 0 var i = 0
@@ -88,6 +89,7 @@ var qbe_emit = function(ir, qbe) {
var p = null var p = null
var pn = null var pn = null
var sl = null var sl = null
var lbl = null
var fop_id = 0 var fop_id = 0
var nr_elems = 0 var nr_elems = 0
var ei = 0 var ei = 0
@@ -113,22 +115,45 @@ var qbe_emit = function(ir, qbe) {
emit(` storel ${s(slot)}, %p${text(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 // Walk instructions
// Slot loads above are not terminators
var last_was_term = false
i = 0 i = 0
while (i < length(instrs)) { while (i < length(instrs)) {
instr = instrs[i] instr = instrs[i]
i = i + 1 i = i + 1
// Labels are plain strings // Labels are plain strings; skip _nop_ur_ pseudo-labels from streamline
if (is_text(instr)) { 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 continue
} }
// Skip dead code: non-label instructions after a terminator are unreachable
if (last_was_term) continue
op = instr[0] op = instr[0]
a1 = instr[1] a1 = instr[1]
a2 = instr[2] a2 = instr[2]
a3 = instr[3] a3 = instr[3]
last_was_term = false
// --- Constants --- // --- Constants ---
@@ -157,11 +182,11 @@ var qbe_emit = function(ir, qbe) {
if (is_integer(a2)) { if (is_integer(a2)) {
emit(` ${s(a1)} =l copy ${text(a2 * 2)}`) emit(` ${s(a1)} =l copy ${text(a2 * 2)}`)
} else { } 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)) { } else if (is_text(a2)) {
sl = intern_str(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)) { } else if (is_object(a2)) {
if (a2.make == "intrinsic") { if (a2.make == "intrinsic") {
sl = intern_str(a2.name) sl = intern_str(a2.name)
@@ -170,13 +195,13 @@ var qbe_emit = function(ir, qbe) {
if (a2.number != null && is_integer(a2.number)) { if (a2.number != null && is_integer(a2.number)) {
emit(` ${s(a1)} =l copy ${text(a2.number * 2)}`) emit(` ${s(a1)} =l copy ${text(a2.number * 2)}`)
} else if (a2.number != null) { } 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 { } else {
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`) emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
} }
} else if (a2.kind == "text") { } else if (a2.kind == "text") {
sl = intern_str(a2.value) 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") { } else if (a2.kind == "true") {
emit(` ${s(a1)} =l copy ${text(qbe.js_true)}`) emit(` ${s(a1)} =l copy ${text(qbe.js_true)}`)
} else if (a2.kind == "false") { } else if (a2.kind == "false") {
@@ -205,7 +230,7 @@ var qbe_emit = function(ir, qbe) {
if (op == "add") { if (op == "add") {
p = fresh() 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}`) emit(` ${s(a1)} =l copy %${p}`)
wb(a1) wb(a1)
continue continue
@@ -246,6 +271,12 @@ var qbe_emit = function(ir, qbe) {
continue 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 --- // --- String concat ---
if (op == "concat") { if (op == "concat") {
@@ -305,6 +336,46 @@ var qbe_emit = function(ir, qbe) {
wb(a1) wb(a1)
continue 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) --- // --- Comparisons (int path) ---
@@ -367,14 +438,30 @@ var qbe_emit = function(ir, qbe) {
wb(a1) wb(a1)
continue continue
} }
if (op == "lt_float" || op == "gt_float" || op == "le_float" || op == "ge_float") { if (op == "lt_float") {
p = fresh() p = fresh()
fop_id = 0 emit(qbe.lt_float(p, "%ctx", s(a2), s(a3)))
if (op == "lt_float") fop_id = 2 emit(` ${s(a1)} =l copy %${p}`)
else if (op == "le_float") fop_id = 3 wb(a1)
else if (op == "gt_float") fop_id = 4 continue
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)})`) 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}`) emit(` ${s(a1)} =l copy %${p}`)
wb(a1) wb(a1)
continue continue
@@ -494,7 +581,10 @@ var qbe_emit = function(ir, qbe) {
// --- Property access — runtime calls --- // --- Property access — runtime calls ---
if (op == "load_field") { 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) { if (pn != null) {
sl = intern_str(pn) sl = intern_str(pn)
emit(` ${s(a1)} =l call $cell_rt_load_field(l %ctx, l ${s(a2)}, l ${sl})`) 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 continue
} }
if (op == "load_dynamic") { 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)})`) emit(` ${s(a1)} =l call $cell_rt_load_dynamic(l %ctx, l ${s(a2)}, l ${s(a3)})`)
}
wb(a1) wb(a1)
continue continue
} }
if (op == "store_field") { if (op == "store_field") {
// IR: ["store_field", obj, val, prop] → C: (ctx, val, obj, name) // 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) { if (pn != null) {
sl = intern_str(pn) sl = intern_str(pn)
emit(` call $cell_rt_store_field(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${sl})`) 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") { if (op == "store_dynamic") {
// IR: ["store_dynamic", obj, val, key] → C: (ctx, val, obj, key) // 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)})`) emit(` call $cell_rt_store_dynamic(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`)
}
continue continue
} }
// --- Closure access --- // --- Closure access ---
if (op == "get") { 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) wb(a1)
continue continue
} }
if (op == "put") { 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 continue
} }
@@ -552,6 +668,7 @@ var qbe_emit = function(ir, qbe) {
if (op == "jump") { if (op == "jump") {
emit(` jmp @${sanitize(a1)}`) emit(` jmp @${sanitize(a1)}`)
last_was_term = true
continue continue
} }
if (op == "jump_true") { if (op == "jump_true") {
@@ -611,6 +728,13 @@ var qbe_emit = function(ir, qbe) {
if (op == "invoke") { if (op == "invoke") {
emit(` ${s(a2)} =l call $cell_rt_invoke(l %ctx, l ${s(a1)})`) emit(` ${s(a2)} =l call $cell_rt_invoke(l %ctx, l ${s(a1)})`)
wb(a2) 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 continue
} }
if (op == "goframe") { if (op == "goframe") {
@@ -621,6 +745,7 @@ var qbe_emit = function(ir, qbe) {
if (op == "goinvoke") { if (op == "goinvoke") {
emit(` %_goret =l call $cell_rt_goinvoke(l %ctx, l ${s(a1)})`) emit(` %_goret =l call $cell_rt_goinvoke(l %ctx, l ${s(a1)})`)
emit(` ret %_goret`) emit(` ret %_goret`)
last_was_term = true
continue continue
} }
@@ -664,19 +789,38 @@ var qbe_emit = function(ir, qbe) {
continue continue
} }
// --- Length ---
if (op == "length") {
emit(` ${s(a1)} =l call $JS_CellLength(l %ctx, l ${s(a2)})`)
wb(a1)
continue
}
// --- Misc --- // --- Misc ---
if (op == "return") { if (op == "return") {
emit(` ret ${s(a1)}`) emit(` ret ${s(a1)}`)
last_was_term = true
continue continue
} }
if (op == "disrupt") { if (op == "disrupt") {
emit(` call $cell_rt_disrupt(l %ctx)`) emit(` call $cell_rt_disrupt(l %ctx)`)
emit(` ret ${text(qbe.js_null)}`) emit(` ret ${text(qbe.js_null)}`)
last_was_term = true
continue continue
} }
if (op == "delete") { 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)})`) emit(` ${s(a1)} =l call $cell_rt_delete(l %ctx, l ${s(a2)}, l ${s(a3)})`)
}
wb(a1) wb(a1)
continue continue
} }
@@ -690,6 +834,14 @@ var qbe_emit = function(ir, qbe) {
emit(` # unknown: ${op}`) 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("}")
emit("") emit("")
} }
@@ -698,6 +850,70 @@ var qbe_emit = function(ir, qbe) {
// Main: compile all functions then main // 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 var fi = 0
while (fi < length(ir.functions)) { while (fi < length(ir.functions)) {
compile_fn(ir.functions[fi], fi, false) compile_fn(ir.functions[fi], fi, false)

View File

@@ -1,16 +1,16 @@
// run_native.ce — load a module both interpreted and native, compare speed // run_native.ce — load a module both interpreted and native, compare speed
// //
// Usage: // 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. // runs both and compares results and timing.
var os = use('os') var os = use('os')
if (length(args) < 1) { if (length(args) < 1) {
print('usage: cell --core . run_native.ce <module>') print('usage: cell --dev run_native.ce <module>')
print(' e.g. cell --core . run_native.ce num_torture') print(' e.g. cell --dev run_native.ce num_torture')
return return
} }
@@ -21,7 +21,7 @@ if (ends_with(name, '.cm')) {
var safe = replace(replace(name, '/', '_'), '-', '_') var safe = replace(replace(name, '/', '_'), '-', '_')
var symbol = 'js_' + safe + '_use' var symbol = 'js_' + safe + '_use'
var dylib_path = './' + name + '.dylib' var dylib_path = './' + name + '.cm.dylib'
var fd = use('fd') var fd = use('fd')
// --- Test argument for function-returning modules --- // --- Test argument for function-returning modules ---

78
run_native_seed.ce Normal file
View File

@@ -0,0 +1,78 @@
// run_native_seed.ce — load and run a native .dylib module (seed mode)
// Usage: ./cell --dev --seed run_native_seed benches/fibonacci
var fd = use("fd")
var os = use("os")
if (length(args) < 1) {
print("usage: cell --dev --seed run_native_seed <module>")
disrupt
}
var name = args[0]
if (ends_with(name, ".cm")) {
name = text(name, 0, length(name) - 3)
}
var safe = replace(replace(name, "/", "_"), "-", "_")
var symbol = "js_" + safe + "_use"
var dylib_path = "./" + name + ".cm.dylib"
var test_arg = 30
if (length(args) > 1) {
test_arg = number(args[1])
}
// --- Interpreted run ---
print("--- interpreted ---")
var t1 = os.now()
var mod_interp = use(name)
var t2 = os.now()
var result_interp = null
if (is_function(mod_interp)) {
print("module returns a function, calling with " + text(test_arg))
t1 = os.now()
result_interp = mod_interp(test_arg)
t2 = os.now()
}
result_interp = result_interp != null ? result_interp : mod_interp
var ms_interp = (t2 - t1) / 1000000
print("result: " + text(result_interp))
print("time: " + text(ms_interp) + " ms")
// --- Native run ---
if (!fd.is_file(dylib_path)) {
print("\nno " + dylib_path + " found")
disrupt
}
print("\n--- native ---")
var t3 = os.now()
var lib = os.dylib_open(dylib_path)
var t4 = os.now()
var mod_native = os.dylib_symbol(lib, symbol)
var t5 = os.now()
var result_native = null
if (is_function(mod_native)) {
print("module returns a function, calling with " + text(test_arg))
t4 = os.now()
result_native = mod_native(test_arg)
t5 = os.now()
}
result_native = result_native != null ? result_native : mod_native
var ms_native = (t5 - t3) / 1000000
var ms_exec = (t5 - t4) / 1000000
print("result: " + text(result_native))
print("load: " + text((t4 - t3) / 1000000) + " ms")
print("exec: " + text(ms_exec) + " ms")
print("total: " + text(ms_native) + " ms")
// --- Comparison ---
print("\n--- comparison ---")
print("match: " + text(result_interp == result_native))
if (ms_native > 0) {
print("speedup: " + text(ms_interp / ms_native) + "x (total)")
}
if (ms_exec > 0) {
print("speedup: " + text(ms_interp / ms_exec) + "x (exec only)")
}

View File

@@ -9,6 +9,15 @@
#include "quickjs-internal.h" #include "quickjs-internal.h"
#include <math.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) */ /* Comparison op IDs (must match qbe.cm float_cmp_op_id values) */
enum { enum {
QBE_CMP_EQ = 0, 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); 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) { JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b) {
return qbe_float_binop(ctx, a, b, op_sub); 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); 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 --- */ /* --- Disruption --- */
void cell_rt_disrupt(JSContext *ctx) { void cell_rt_disrupt(JSContext *ctx) {