Merge branch 'gen_dylib'
This commit is contained in:
File diff suppressed because it is too large
Load Diff
37804
boot/fold.cm.mcode
37804
boot/fold.cm.mcode
File diff suppressed because it is too large
Load Diff
52242
boot/mcode.cm.mcode
52242
boot/mcode.cm.mcode
File diff suppressed because it is too large
Load Diff
62291
boot/parse.cm.mcode
62291
boot/parse.cm.mcode
File diff suppressed because it is too large
Load Diff
21132
boot/tokenize.cm.mcode
21132
boot/tokenize.cm.mcode
File diff suppressed because it is too large
Load Diff
87
build.cm
87
build.cm
@@ -582,6 +582,93 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
|
||||
return dylib_path
|
||||
}
|
||||
|
||||
// Compile pre-compiled mcode IR to a native .dylib via QBE.
|
||||
// Use this when the caller already has the optimized IR (avoids calling mcode
|
||||
// twice and hitting module-level state pollution).
|
||||
Build.compile_native_ir = function(optimized, src_path, opts) {
|
||||
var _target = (opts && opts.target) || Build.detect_host_target()
|
||||
var _buildtype = (opts && opts.buildtype) || 'release'
|
||||
var pkg = opts && opts.pkg
|
||||
var qbe_rt_path = null
|
||||
var native_stem = null
|
||||
var native_install_dir = null
|
||||
var native_install_path = null
|
||||
|
||||
var tc = toolchains[_target]
|
||||
var dylib_ext = tc.system == 'windows' ? '.dll' : (tc.system == 'darwin' ? '.dylib' : '.so')
|
||||
var cc = tc.c
|
||||
|
||||
var qbe_macros = use('qbe')
|
||||
var qbe_emit = use('qbe_emit')
|
||||
|
||||
var sym_name = null
|
||||
if (pkg) {
|
||||
sym_name = shop.c_symbol_for_file(pkg, fd.basename(src_path))
|
||||
}
|
||||
var il = qbe_emit(optimized, qbe_macros, sym_name)
|
||||
il = qbe_insert_dead_labels(il)
|
||||
|
||||
var src = text(fd.slurp(src_path))
|
||||
var hash = content_hash(src + '\n' + _target + '\nnative')
|
||||
var build_dir = get_build_dir()
|
||||
ensure_dir(build_dir)
|
||||
|
||||
var dylib_path = build_dir + '/' + hash + '.' + _target + dylib_ext
|
||||
if (fd.is_file(dylib_path))
|
||||
return dylib_path
|
||||
|
||||
var tmp = '/tmp/cell_native_' + hash
|
||||
var ssa_path = tmp + '.ssa'
|
||||
var s_path = tmp + '.s'
|
||||
var o_path = tmp + '.o'
|
||||
var rt_o_path = '/tmp/cell_qbe_rt.o'
|
||||
|
||||
fd.slurpwrite(ssa_path, stone(blob(il)))
|
||||
|
||||
var rc = os.system('qbe -o ' + s_path + ' ' + ssa_path)
|
||||
if (rc != 0) {
|
||||
print('QBE compilation failed for: ' + src_path); disrupt
|
||||
}
|
||||
|
||||
rc = os.system(cc + ' -c ' + s_path + ' -o ' + o_path)
|
||||
if (rc != 0) {
|
||||
print('Assembly failed for: ' + src_path); disrupt
|
||||
}
|
||||
|
||||
if (!fd.is_file(rt_o_path)) {
|
||||
qbe_rt_path = shop.get_package_dir('core') + '/qbe_rt.c'
|
||||
rc = os.system(cc + ' -c ' + qbe_rt_path + ' -o ' + rt_o_path + ' -fPIC')
|
||||
if (rc != 0) {
|
||||
print('QBE runtime stubs compilation failed'); disrupt
|
||||
}
|
||||
}
|
||||
|
||||
var link_cmd = cc + ' -shared -fPIC'
|
||||
if (tc.system == 'darwin') {
|
||||
link_cmd = link_cmd + ' -undefined dynamic_lookup'
|
||||
} else if (tc.system == 'linux') {
|
||||
link_cmd = link_cmd + ' -Wl,--allow-shlib-undefined'
|
||||
}
|
||||
link_cmd = link_cmd + ' ' + o_path + ' ' + rt_o_path + ' -o ' + dylib_path
|
||||
|
||||
rc = os.system(link_cmd)
|
||||
if (rc != 0) {
|
||||
print('Linking native dylib failed for: ' + src_path); disrupt
|
||||
}
|
||||
|
||||
log.console('Built native: ' + fd.basename(dylib_path))
|
||||
|
||||
if (pkg) {
|
||||
native_stem = fd.basename(src_path)
|
||||
native_install_dir = shop.get_lib_dir() + '/' + shop.lib_name_for_package(pkg)
|
||||
ensure_dir(native_install_dir)
|
||||
native_install_path = native_install_dir + '/' + native_stem + dylib_ext
|
||||
fd.slurpwrite(native_install_path, fd.slurp(dylib_path))
|
||||
}
|
||||
|
||||
return dylib_path
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Module table generation (for static builds)
|
||||
// ============================================================================
|
||||
|
||||
113
compare_aot.ce
Normal file
113
compare_aot.ce
Normal file
@@ -0,0 +1,113 @@
|
||||
// compare_aot.ce — compile a .ce/.cm file via both paths and compare results
|
||||
//
|
||||
// Usage:
|
||||
// cell --dev compare_aot.ce <file.ce>
|
||||
|
||||
var build = use('build')
|
||||
var fd_mod = use('fd')
|
||||
var os = use('os')
|
||||
var json = use('json')
|
||||
|
||||
var show = function(v) {
|
||||
if (v == null) return "null"
|
||||
return json.encode(v)
|
||||
}
|
||||
|
||||
if (length(args) < 1) {
|
||||
print('usage: cell --dev compare_aot.ce <file>')
|
||||
return
|
||||
}
|
||||
|
||||
var file = args[0]
|
||||
if (!fd_mod.is_file(file)) {
|
||||
if (!ends_with(file, '.ce') && fd_mod.is_file(file + '.ce'))
|
||||
file = file + '.ce'
|
||||
else if (!ends_with(file, '.cm') && fd_mod.is_file(file + '.cm'))
|
||||
file = file + '.cm'
|
||||
else {
|
||||
print('file not found: ' + file)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var abs = fd_mod.realpath(file)
|
||||
|
||||
// Shared compilation front-end
|
||||
var tokenize = use('tokenize')
|
||||
var parse_mod = use('parse')
|
||||
var fold = use('fold')
|
||||
var mcode_mod = use('mcode')
|
||||
var streamline_mod = use('streamline')
|
||||
|
||||
var src = text(fd_mod.slurp(abs))
|
||||
var tok = tokenize(src, abs)
|
||||
var ast = parse_mod(tok.tokens, src, abs, tokenize)
|
||||
var folded = fold(ast)
|
||||
var compiled = mcode_mod(folded)
|
||||
var optimized = streamline_mod(compiled)
|
||||
|
||||
// Shared env for both paths — only non-intrinsic runtime functions.
|
||||
// Intrinsics (starts_with, ends_with, logical, some, every, etc.) live on
|
||||
// the stoned global and are found via GETINTRINSIC/cell_rt_get_intrinsic.
|
||||
var env = stone({
|
||||
log: log,
|
||||
fallback: fallback,
|
||||
parallel: parallel,
|
||||
race: race,
|
||||
sequence: sequence,
|
||||
use
|
||||
})
|
||||
|
||||
// --- Interpreted (mach VM) ---
|
||||
var result_interp = null
|
||||
var interp_ok = false
|
||||
var run_interp = function() {
|
||||
print('--- interpreted ---')
|
||||
var mcode_json = json.encode(optimized)
|
||||
var mach_blob = mach_compile_mcode_bin(abs, mcode_json)
|
||||
result_interp = mach_load(mach_blob, env)
|
||||
interp_ok = true
|
||||
print('result: ' + show(result_interp))
|
||||
} disruption {
|
||||
interp_ok = true
|
||||
print('(disruption escaped from interpreted run)')
|
||||
}
|
||||
run_interp()
|
||||
|
||||
// --- Native (AOT via QBE) ---
|
||||
var result_native = null
|
||||
var native_ok = false
|
||||
var run_native = function() {
|
||||
print('\n--- native ---')
|
||||
var dylib_path = build.compile_native_ir(optimized, abs, null)
|
||||
print('dylib: ' + dylib_path)
|
||||
var handle = os.dylib_open(dylib_path)
|
||||
if (!handle) {
|
||||
print('failed to open dylib')
|
||||
return
|
||||
}
|
||||
result_native = os.native_module_load(handle, env)
|
||||
native_ok = true
|
||||
print('result: ' + show(result_native))
|
||||
} disruption {
|
||||
native_ok = true
|
||||
print('(disruption escaped from native run)')
|
||||
}
|
||||
run_native()
|
||||
|
||||
// --- Comparison ---
|
||||
print('\n--- comparison ---')
|
||||
var s_interp = show(result_interp)
|
||||
var s_native = show(result_native)
|
||||
if (interp_ok && native_ok) {
|
||||
if (s_interp == s_native) {
|
||||
print('MATCH')
|
||||
} else {
|
||||
print('MISMATCH')
|
||||
print(' interp: ' + s_interp)
|
||||
print(' native: ' + s_native)
|
||||
}
|
||||
} else {
|
||||
if (!interp_ok) print('interpreted run failed')
|
||||
if (!native_ok) print('native run failed')
|
||||
}
|
||||
4
diff.ce
4
diff.ce
@@ -129,11 +129,11 @@ function diff_test_file(file_path) {
|
||||
|
||||
// Build env for module loading
|
||||
var make_env = function() {
|
||||
return {
|
||||
return stone({
|
||||
use: function(path) {
|
||||
return shop.use(path, use_pkg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Read and parse
|
||||
|
||||
@@ -229,7 +229,6 @@ Function calls are decomposed into three instructions:
|
||||
| Instruction | Operands | Description |
|
||||
|-------------|----------|-------------|
|
||||
| `access` | `dest, name` | Load variable (intrinsic or module environment) |
|
||||
| `set_var` | `name, src` | Set top-level variable by name |
|
||||
| `get` | `dest, level, slot` | Get closure variable from parent scope |
|
||||
| `put` | `level, slot, src` | Set closure variable in parent scope |
|
||||
|
||||
|
||||
22
dump_ir.ce
Normal file
22
dump_ir.ce
Normal file
@@ -0,0 +1,22 @@
|
||||
var tokenize = use('tokenize')
|
||||
var parse_mod = use('parse')
|
||||
var fold = use('fold')
|
||||
var mcode_mod = use('mcode')
|
||||
var streamline_mod = use('streamline')
|
||||
var json = use('json')
|
||||
var fd = use('fd')
|
||||
|
||||
var file = args[0]
|
||||
var src = text(fd.slurp(file))
|
||||
var tok = tokenize(src, file)
|
||||
var ast = parse_mod(tok.tokens, src, file, tokenize)
|
||||
var folded = fold(ast)
|
||||
var compiled = mcode_mod(folded)
|
||||
var optimized = streamline_mod(compiled)
|
||||
|
||||
var instrs = optimized.main.instructions
|
||||
var i = 0
|
||||
while (i < length(instrs)) {
|
||||
print(text(i) + ': ' + json.encode(instrs[i]))
|
||||
i = i + 1
|
||||
}
|
||||
4
fuzz.ce
4
fuzz.ce
@@ -134,7 +134,7 @@ function run_fuzz(seed_val) {
|
||||
|
||||
// Run optimized
|
||||
var _opt = function() {
|
||||
mod_opt = run_ast_fn(name, ast, {use: function(p) { return use(p) }})
|
||||
mod_opt = run_ast_fn(name, ast, stone({use: function(p) { return use(p) }}))
|
||||
} disruption {
|
||||
opt_err = "disrupted"
|
||||
}
|
||||
@@ -142,7 +142,7 @@ function run_fuzz(seed_val) {
|
||||
|
||||
// Run unoptimized
|
||||
var _noopt = function() {
|
||||
mod_noopt = run_ast_noopt_fn(name + "_noopt", ast, {use: function(p) { return use(p) }})
|
||||
mod_noopt = run_ast_noopt_fn(name + "_noopt", ast, stone({use: function(p) { return use(p) }}))
|
||||
} disruption {
|
||||
noopt_err = "disrupted"
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ function boot_load(name) {
|
||||
}
|
||||
mcode_blob = fd.slurp(mcode_path)
|
||||
mach_blob = mach_compile_mcode_bin(name, text(mcode_blob))
|
||||
return mach_load(mach_blob, {use: use_embed})
|
||||
return mach_load(mach_blob, stone({use: use_embed}))
|
||||
}
|
||||
|
||||
var tokenize_mod = boot_load("tokenize")
|
||||
|
||||
@@ -22,30 +22,6 @@ function use_embed(name) {
|
||||
return load_internal("js_core_" + name + "_use")
|
||||
}
|
||||
|
||||
function logical(val1) {
|
||||
if (val1 == 0 || val1 == false || val1 == "false" || val1 == null)
|
||||
return false;
|
||||
if (val1 == 1 || val1 == true || val1 == "true")
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
|
||||
function some(arr, pred) {
|
||||
return find(arr, pred) != null
|
||||
}
|
||||
|
||||
function every(arr, pred) {
|
||||
return find(arr, x => not(pred(x))) == null
|
||||
}
|
||||
|
||||
function starts_with(str, prefix) {
|
||||
return search(str, prefix) == 0
|
||||
}
|
||||
|
||||
function ends_with(str, suffix) {
|
||||
return search(str, suffix, -length(suffix)) != null
|
||||
}
|
||||
|
||||
var fd = use_embed('internal_fd')
|
||||
var js = use_embed('js')
|
||||
var crypto = use_embed('crypto')
|
||||
@@ -84,7 +60,7 @@ function boot_load(name) {
|
||||
}
|
||||
mcode_blob = fd.slurp(mcode_path)
|
||||
mach_blob = mach_compile_mcode_bin(name, text(mcode_blob))
|
||||
return mach_load(mach_blob, {use: use_embed})
|
||||
return mach_load(mach_blob, stone({use: use_embed}))
|
||||
}
|
||||
|
||||
// Load a pipeline module from cache; on miss compile from source via boot chain
|
||||
@@ -149,7 +125,7 @@ function load_pipeline_module(name, env) {
|
||||
}
|
||||
|
||||
// Load compilation pipeline
|
||||
var pipeline_env = {use: use_embed}
|
||||
var pipeline_env = stone({use: use_embed})
|
||||
var tokenize_mod = load_pipeline_module('tokenize', pipeline_env)
|
||||
var parse_mod = load_pipeline_module('parse', pipeline_env)
|
||||
var fold_mod = load_pipeline_module('fold', pipeline_env)
|
||||
@@ -270,6 +246,7 @@ function use_core(path) {
|
||||
// Build env: merge core_extras
|
||||
env = {use: use_core}
|
||||
arrfor(array(core_extras), function(k) { env[k] = core_extras[k] })
|
||||
env = stone(env)
|
||||
|
||||
var hash = null
|
||||
var cached_path = null
|
||||
@@ -447,12 +424,6 @@ var parallel = pronto.parallel
|
||||
var race = pronto.race
|
||||
var sequence = pronto.sequence
|
||||
|
||||
// Fill runtime_env (same object reference shop holds)
|
||||
runtime_env.logical = logical
|
||||
runtime_env.some = some
|
||||
runtime_env.every = every
|
||||
runtime_env.starts_with = starts_with
|
||||
runtime_env.ends_with = ends_with
|
||||
runtime_env.actor = actor
|
||||
runtime_env.is_actor = is_actor
|
||||
runtime_env.log = log
|
||||
@@ -1109,6 +1080,7 @@ $_.clock(_ => {
|
||||
}
|
||||
env.args = _cell.args.arg
|
||||
env.log = log
|
||||
env = stone(env)
|
||||
|
||||
var source_blob = fd.slurp(prog_path)
|
||||
var hash = content_hash(source_blob)
|
||||
|
||||
@@ -445,8 +445,8 @@ static JSValue js_os_dylib_close(JSContext *js, JSValue self, int argc, JSValue
|
||||
|
||||
/* Load a native .cm module from a dylib handle.
|
||||
Uses cell_rt_native_module_load from qbe_helpers.c */
|
||||
extern JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle);
|
||||
extern JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const char *sym_name);
|
||||
extern JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle, JSValue env);
|
||||
extern JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const char *sym_name, JSValue env);
|
||||
|
||||
static JSValue js_os_native_module_load(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
@@ -457,7 +457,8 @@ static JSValue js_os_native_module_load(JSContext *js, JSValue self, int argc, J
|
||||
if (!handle)
|
||||
return JS_ThrowTypeError(js, "First argument must be a dylib object");
|
||||
|
||||
return cell_rt_native_module_load(js, handle);
|
||||
JSValue env = (argc >= 2) ? argv[1] : JS_NULL;
|
||||
return cell_rt_native_module_load(js, handle, env);
|
||||
}
|
||||
|
||||
static JSValue js_os_native_module_load_named(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
@@ -473,7 +474,8 @@ static JSValue js_os_native_module_load_named(JSContext *js, JSValue self, int a
|
||||
if (!sym_name)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
JSValue result = cell_rt_native_module_load_named(js, handle, sym_name);
|
||||
JSValue env = (argc >= 3) ? argv[2] : JS_NULL;
|
||||
JSValue result = cell_rt_native_module_load_named(js, handle, sym_name, env);
|
||||
JS_FreeCString(js, sym_name);
|
||||
return result;
|
||||
}
|
||||
@@ -653,8 +655,8 @@ static const JSCFunctionListEntry js_os_funcs[] = {
|
||||
MIST_FUNC_DEF(os, dylib_close, 1),
|
||||
MIST_FUNC_DEF(os, dylib_symbol, 2),
|
||||
MIST_FUNC_DEF(os, dylib_has_symbol, 2),
|
||||
MIST_FUNC_DEF(os, native_module_load, 1),
|
||||
MIST_FUNC_DEF(os, native_module_load_named, 2),
|
||||
MIST_FUNC_DEF(os, native_module_load, 2),
|
||||
MIST_FUNC_DEF(os, native_module_load_named, 3),
|
||||
MIST_FUNC_DEF(os, embedded_module, 1),
|
||||
MIST_FUNC_DEF(os, load_internal, 1),
|
||||
MIST_FUNC_DEF(os, internal_exists, 1),
|
||||
|
||||
@@ -959,6 +959,7 @@ function execute_module(info)
|
||||
env = inject_env(inject)
|
||||
pkg = file_info.package
|
||||
env.use = make_use_fn(pkg)
|
||||
env = stone(env)
|
||||
|
||||
// Load compiled bytecode with env
|
||||
used = mach_load(mod_resolve.symbol, env)
|
||||
@@ -994,6 +995,7 @@ Shop.use = function use(path, package_context) {
|
||||
if (embedded) {
|
||||
embed_env = inject_env(SHOP_DEFAULT_INJECT)
|
||||
embed_env.use = make_use_fn(package_context)
|
||||
embed_env = stone(embed_env)
|
||||
use_cache[embed_key] = mach_load(embedded, embed_env)
|
||||
return use_cache[embed_key]
|
||||
}
|
||||
@@ -1587,6 +1589,7 @@ Shop.load_as_mach = function(path, pkg) {
|
||||
inject = Shop.script_inject_for(file_info)
|
||||
env = inject_env(inject)
|
||||
env.use = make_use_fn(file_info.package)
|
||||
env = stone(env)
|
||||
return mach_load(compiled, env)
|
||||
}
|
||||
|
||||
@@ -1676,9 +1679,15 @@ Shop.use_native = function(path, package_context) {
|
||||
var handle = os.dylib_open(dylib_path)
|
||||
if (!handle) { print('Failed to open native dylib: ' + dylib_path); disrupt }
|
||||
|
||||
// Build env with runtime functions and capabilities
|
||||
var inject = Shop.script_inject_for(file_info)
|
||||
var env = inject_env(inject)
|
||||
env.use = make_use_fn(pkg)
|
||||
env = stone(env)
|
||||
|
||||
if (sym_name)
|
||||
return os.native_module_load_named(handle, sym_name)
|
||||
return os.native_module_load(handle)
|
||||
return os.native_module_load_named(handle, sym_name, env)
|
||||
return os.native_module_load(handle, env)
|
||||
}
|
||||
|
||||
return Shop
|
||||
5
mcode.cm
5
mcode.cm
@@ -1384,8 +1384,6 @@ var mcode = function(ast) {
|
||||
pstate = parent_states[length(parent_states) - 1 - _lv]
|
||||
pslot = find_var_in_saved(pstate, name)
|
||||
emit_3("put", dest, pslot, level)
|
||||
} else {
|
||||
add_instr(["set_var", name, dest])
|
||||
}
|
||||
return dest
|
||||
} else if (left_kind == ".") {
|
||||
@@ -1486,9 +1484,6 @@ var mcode = function(ast) {
|
||||
return val_slot
|
||||
}
|
||||
val_slot = gen_expr(right, -1)
|
||||
if (level == -1) {
|
||||
add_instr(["set_var", name, val_slot])
|
||||
}
|
||||
} else {
|
||||
val_slot = gen_expr(right, -1)
|
||||
if (level > 0) {
|
||||
|
||||
794
qbe_emit.cm
794
qbe_emit.cm
File diff suppressed because it is too large
Load Diff
@@ -85,6 +85,12 @@ static JSValue *mach_materialize_cpool(JSContext *ctx, MachCPoolEntry *entries,
|
||||
/* ---- Link pass: resolve GETNAME to GETINTRINSIC or GETENV ---- */
|
||||
|
||||
static void mach_link_code(JSContext *ctx, JSCodeRegister *code, JSValue env) {
|
||||
if (!JS_IsNull(env) && !JS_IsStone(env)) {
|
||||
fprintf(stderr, "mach_link_code: ERROR env not stone (code=%s file=%s)\n",
|
||||
code->name_cstr ? code->name_cstr : "<unknown>",
|
||||
code->filename_cstr ? code->filename_cstr : "<unknown>");
|
||||
abort();
|
||||
}
|
||||
JSGCRef env_ref;
|
||||
JS_PushGCRef(ctx, &env_ref);
|
||||
env_ref.val = env;
|
||||
@@ -809,7 +815,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
DT(MACH_ADD), DT(MACH_SUB),
|
||||
DT(MACH_MUL), DT(MACH_DIV),
|
||||
DT(MACH_MOD), DT(MACH_POW),
|
||||
DT(MACH_NEG), DT(MACH_INC), DT(MACH_DEC),
|
||||
DT(MACH_NEG),
|
||||
DT(MACH_EQ), DT(MACH_NEQ),
|
||||
DT(MACH_LT), DT(MACH_LE),
|
||||
DT(MACH_GT), DT(MACH_GE),
|
||||
@@ -859,7 +865,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
DT(MACH_GOINVOKE),
|
||||
DT(MACH_JMPNOTNULL),
|
||||
DT(MACH_DISRUPT),
|
||||
DT(MACH_SET_VAR), DT(MACH_IN),
|
||||
DT(MACH_IN),
|
||||
DT(MACH_IS_ARRAY), DT(MACH_IS_FUNC),
|
||||
DT(MACH_IS_RECORD), DT(MACH_IS_STONE),
|
||||
DT(MACH_LENGTH), DT(MACH_IS_PROXY),
|
||||
@@ -1218,38 +1224,6 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
VM_BREAK();
|
||||
}
|
||||
|
||||
VM_CASE(MACH_INC): {
|
||||
JSValue v = frame->slots[b];
|
||||
if (JS_IsInt(v)) {
|
||||
int32_t i = JS_VALUE_GET_INT(v);
|
||||
if (i == INT32_MAX)
|
||||
frame->slots[a] = JS_NewFloat64(ctx, (double)i + 1);
|
||||
else
|
||||
frame->slots[a] = JS_NewInt32(ctx, i + 1);
|
||||
} else {
|
||||
double d;
|
||||
JS_ToFloat64(ctx, &d, v);
|
||||
frame->slots[a] = JS_NewFloat64(ctx, d + 1);
|
||||
}
|
||||
VM_BREAK();
|
||||
}
|
||||
|
||||
VM_CASE(MACH_DEC): {
|
||||
JSValue v = frame->slots[b];
|
||||
if (JS_IsInt(v)) {
|
||||
int32_t i = JS_VALUE_GET_INT(v);
|
||||
if (i == INT32_MIN)
|
||||
frame->slots[a] = JS_NewFloat64(ctx, (double)i - 1);
|
||||
else
|
||||
frame->slots[a] = JS_NewInt32(ctx, i - 1);
|
||||
} else {
|
||||
double d;
|
||||
JS_ToFloat64(ctx, &d, v);
|
||||
frame->slots[a] = JS_NewFloat64(ctx, d - 1);
|
||||
}
|
||||
VM_BREAK();
|
||||
}
|
||||
|
||||
VM_CASE(MACH_LNOT): {
|
||||
int bval = JS_ToBool(ctx, frame->slots[b]);
|
||||
frame->slots[a] = JS_NewBool(ctx, !bval);
|
||||
@@ -2075,21 +2049,6 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
VM_CASE(MACH_DISRUPT):
|
||||
goto disrupt;
|
||||
|
||||
/* Variable storage: env/global[K(Bx)] = R(A) */
|
||||
VM_CASE(MACH_SET_VAR): {
|
||||
int bx = MACH_GET_Bx(instr);
|
||||
JSValue key = code->cpool[bx];
|
||||
JSValue val = frame->slots[a];
|
||||
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.reg.env_record;
|
||||
if (!JS_IsNull(cur_env)) {
|
||||
JS_SetProperty(ctx, cur_env, key, val);
|
||||
} else {
|
||||
JS_SetProperty(ctx, ctx->global_obj, key, val);
|
||||
}
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
VM_BREAK();
|
||||
}
|
||||
|
||||
/* Has-property check (mcode name) */
|
||||
VM_CASE(MACH_IN): {
|
||||
JSValue key = frame->slots[b];
|
||||
@@ -2128,6 +2087,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
pc = code->disruption_pc;
|
||||
ctx->disruption_reported = FALSE;
|
||||
frame_ref.val = JS_MKPTR(frame); /* root handler frame for GC */
|
||||
ctx->current_exception = JS_NULL;
|
||||
break;
|
||||
}
|
||||
if (JS_IsNull(frame->caller)) {
|
||||
@@ -2334,9 +2294,6 @@ static int mcode_reg_items(cJSON *it, cJSON **out) {
|
||||
/* goinvoke: [1]=frame only (no result) */
|
||||
if (!strcmp(op, "goinvoke")) { ADD(1); return c; }
|
||||
|
||||
/* set_var: [1]=name(string), [2]=val */
|
||||
if (!strcmp(op, "set_var")) { ADD(2); return c; }
|
||||
|
||||
/* setarg: [1]=call, [2]=arg_idx(const), [3]=val */
|
||||
if (!strcmp(op, "setarg")) { ADD(1); ADD(3); return c; }
|
||||
|
||||
@@ -2906,12 +2863,6 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
||||
else if (strcmp(op, "disrupt") == 0) {
|
||||
EM(MACH_ABC(MACH_DISRUPT, 0, 0, 0));
|
||||
}
|
||||
/* Variable storage */
|
||||
else if (strcmp(op, "set_var") == 0) {
|
||||
const char *vname = cJSON_GetArrayItem(it, 1)->valuestring;
|
||||
int val_reg = A2;
|
||||
EM(MACH_ABx(MACH_SET_VAR, val_reg, ml_cpool_str(&s, vname)));
|
||||
}
|
||||
/* Misc */
|
||||
else if (strcmp(op, "in") == 0) {
|
||||
EM(MACH_ABC(MACH_IN, A1, A2, A3));
|
||||
@@ -3341,8 +3292,6 @@ static void dump_register_code(JSContext *ctx, JSCodeRegister *code, int indent)
|
||||
/* A, B: move, unary ops */
|
||||
case MACH_MOVE:
|
||||
case MACH_NEG:
|
||||
case MACH_INC:
|
||||
case MACH_DEC:
|
||||
case MACH_LNOT:
|
||||
case MACH_BNOT:
|
||||
printf("r%d, r%d", a, b);
|
||||
|
||||
@@ -260,23 +260,70 @@ void cell_rt_store_index(JSContext *ctx, JSValue val, JSValue arr,
|
||||
|
||||
/* --- Intrinsic/global lookup --- */
|
||||
|
||||
/* Native module environment — set before executing a native module's cell_main.
|
||||
Contains runtime functions (starts_with, ends_with, etc.) and use(). */
|
||||
static JSGCRef g_native_env_ref;
|
||||
static int g_has_native_env = 0;
|
||||
|
||||
void cell_rt_set_native_env(JSContext *ctx, JSValue env) {
|
||||
if (!JS_IsNull(env) && !JS_IsStone(env)) {
|
||||
fprintf(stderr, "cell_rt_set_native_env: ERROR env not stone\n");
|
||||
abort();
|
||||
}
|
||||
if (g_has_native_env)
|
||||
JS_DeleteGCRef(ctx, &g_native_env_ref);
|
||||
if (!JS_IsNull(env)) {
|
||||
JS_AddGCRef(ctx, &g_native_env_ref);
|
||||
g_native_env_ref.val = env;
|
||||
g_has_native_env = 1;
|
||||
} else {
|
||||
g_has_native_env = 0;
|
||||
}
|
||||
}
|
||||
|
||||
JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) {
|
||||
return JS_GetPropertyStr(ctx, ctx->global_obj, name);
|
||||
/* Check native env first (runtime-provided functions like log) */
|
||||
if (g_has_native_env) {
|
||||
JSValue v = JS_GetPropertyStr(ctx, g_native_env_ref.val, name);
|
||||
if (!JS_IsNull(v))
|
||||
return v;
|
||||
}
|
||||
/* Linear scan of global object — avoids hash mismatch issues with
|
||||
stoned records whose keys may be in cold storage */
|
||||
JSValue gobj = ctx->global_obj;
|
||||
if (JS_IsRecord(gobj)) {
|
||||
JSRecord *rec = (JSRecord *)chase(gobj);
|
||||
uint64_t mask = objhdr_cap56(rec->mist_hdr);
|
||||
for (uint64_t i = 1; i <= mask; i++) {
|
||||
if (js_key_equal_str(rec->slots[i].key, name))
|
||||
return rec->slots[i].val;
|
||||
}
|
||||
}
|
||||
JS_ThrowReferenceError(ctx, "'%s' is not defined", name);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
|
||||
/* --- Closure access ---
|
||||
Slot 511 in each frame stores a pointer to the enclosing frame.
|
||||
Walking depth levels up the chain gives the target frame. */
|
||||
Slot 511 in each frame stores the magic ID (registry index) of the
|
||||
function that owns this frame. cell_rt_get/put_closure re-derive
|
||||
the enclosing frame from the function's GC ref at call time, so
|
||||
pointers stay valid even if GC moves frames. */
|
||||
|
||||
#define QBE_FRAME_OUTER_SLOT 511
|
||||
|
||||
static JSValue *derive_outer_fp(int magic);
|
||||
|
||||
JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth,
|
||||
int64_t slot) {
|
||||
JSValue *frame = (JSValue *)fp;
|
||||
for (int64_t d = 0; d < depth; d++) {
|
||||
void *outer = (void *)(uintptr_t)frame[QBE_FRAME_OUTER_SLOT];
|
||||
if (!outer) return JS_NULL;
|
||||
frame = (JSValue *)outer;
|
||||
/* fp[511] stores the magic ID (registry index) of the function
|
||||
that owns this frame. derive_outer_fp re-derives the enclosing
|
||||
frame from the function's GC ref, so it's always current even
|
||||
if GC moved the frame. */
|
||||
int magic = (int)(int64_t)frame[QBE_FRAME_OUTER_SLOT];
|
||||
frame = derive_outer_fp(magic);
|
||||
if (!frame) return JS_NULL;
|
||||
}
|
||||
return frame[slot];
|
||||
}
|
||||
@@ -285,13 +332,74 @@ void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
|
||||
int64_t slot) {
|
||||
JSValue *frame = (JSValue *)fp;
|
||||
for (int64_t d = 0; d < depth; d++) {
|
||||
void *outer = (void *)(uintptr_t)frame[QBE_FRAME_OUTER_SLOT];
|
||||
if (!outer) return;
|
||||
frame = (JSValue *)outer;
|
||||
int magic = (int)(int64_t)frame[QBE_FRAME_OUTER_SLOT];
|
||||
frame = derive_outer_fp(magic);
|
||||
if (!frame) return;
|
||||
}
|
||||
frame[slot] = val;
|
||||
}
|
||||
|
||||
/* --- GC-managed AOT frame stack ---
|
||||
Each AOT function call pushes a GC ref so the GC can find and
|
||||
update frame pointers when it moves objects. cell_rt_refresh_fp
|
||||
re-derives the slot pointer after any GC-triggering call. */
|
||||
|
||||
#define MAX_AOT_DEPTH 256
|
||||
static JSGCRef g_aot_gc_refs[MAX_AOT_DEPTH];
|
||||
static int g_aot_depth = 0;
|
||||
|
||||
JSValue *cell_rt_enter_frame(JSContext *ctx, int64_t nr_slots) {
|
||||
if (g_aot_depth >= MAX_AOT_DEPTH)
|
||||
return NULL;
|
||||
JSFrameRegister *frame = alloc_frame_register(ctx, (int)nr_slots);
|
||||
if (!frame) return NULL;
|
||||
JSGCRef *ref = &g_aot_gc_refs[g_aot_depth];
|
||||
JS_AddGCRef(ctx, ref);
|
||||
ref->val = JS_MKPTR(frame);
|
||||
g_aot_depth++;
|
||||
return (JSValue *)frame->slots;
|
||||
}
|
||||
|
||||
JSValue *cell_rt_refresh_fp(JSContext *ctx) {
|
||||
(void)ctx;
|
||||
if (g_aot_depth <= 0) {
|
||||
fprintf(stderr, "[BUG] cell_rt_refresh_fp: g_aot_depth=%d\n", g_aot_depth);
|
||||
abort();
|
||||
}
|
||||
JSValue val = g_aot_gc_refs[g_aot_depth - 1].val;
|
||||
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(val);
|
||||
if (!frame) {
|
||||
fprintf(stderr, "[BUG] cell_rt_refresh_fp: frame is NULL at depth=%d val=%lld\n",
|
||||
g_aot_depth, (long long)val);
|
||||
abort();
|
||||
}
|
||||
return (JSValue *)frame->slots;
|
||||
}
|
||||
|
||||
/* Combined refresh + exception check in a single call.
|
||||
Returns the refreshed fp, or NULL if there is a pending exception.
|
||||
This avoids QBE register-allocation issues from two consecutive calls. */
|
||||
JSValue *cell_rt_refresh_fp_checked(JSContext *ctx) {
|
||||
if (JS_HasException(ctx))
|
||||
return NULL;
|
||||
if (g_aot_depth <= 0) {
|
||||
fprintf(stderr, "[BUG] cell_rt_refresh_fp_checked: g_aot_depth=%d\n", g_aot_depth);
|
||||
abort();
|
||||
}
|
||||
JSValue val = g_aot_gc_refs[g_aot_depth - 1].val;
|
||||
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(val);
|
||||
if (!frame) {
|
||||
fprintf(stderr, "[BUG] cell_rt_refresh_fp_checked: frame is NULL\n");
|
||||
abort();
|
||||
}
|
||||
return (JSValue *)frame->slots;
|
||||
}
|
||||
|
||||
void cell_rt_leave_frame(JSContext *ctx) {
|
||||
g_aot_depth--;
|
||||
JS_DeleteGCRef(ctx, &g_aot_gc_refs[g_aot_depth]);
|
||||
}
|
||||
|
||||
/* --- Function creation and calling --- */
|
||||
|
||||
typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
|
||||
@@ -305,7 +413,8 @@ typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
|
||||
static struct {
|
||||
void *dl_handle;
|
||||
int fn_idx;
|
||||
void *outer_fp;
|
||||
JSGCRef frame_ref; /* independent GC ref for enclosing frame */
|
||||
int has_frame_ref;
|
||||
} g_native_fn_registry[MAX_NATIVE_FN];
|
||||
|
||||
static int g_native_fn_count = 0;
|
||||
@@ -313,6 +422,16 @@ static int g_native_fn_count = 0;
|
||||
/* Set before executing a native module's cell_main */
|
||||
static void *g_current_dl_handle = NULL;
|
||||
|
||||
/* Derive the outer frame's slots pointer from the closure's own GC ref.
|
||||
Each closure keeps an independent GC ref so the enclosing frame
|
||||
survives even after cell_rt_leave_frame pops the stack ref. */
|
||||
static JSValue *derive_outer_fp(int magic) {
|
||||
if (!g_native_fn_registry[magic].has_frame_ref) return NULL;
|
||||
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(
|
||||
g_native_fn_registry[magic].frame_ref.val);
|
||||
return (JSValue *)frame->slots;
|
||||
}
|
||||
|
||||
static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val,
|
||||
int argc, JSValue *argv, int magic) {
|
||||
if (magic < 0 || magic >= g_native_fn_count)
|
||||
@@ -328,27 +447,44 @@ static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val,
|
||||
if (!fn)
|
||||
return JS_ThrowTypeError(ctx, "native function %s not found in dylib", name);
|
||||
|
||||
/* Allocate frame: slot 0 = this, slots 1..argc = args */
|
||||
JSValue frame[512];
|
||||
memset(frame, 0, sizeof(frame));
|
||||
frame[0] = this_val;
|
||||
/* Allocate GC-managed frame: slot 0 = this, slots 1..argc = args */
|
||||
JSValue *fp = cell_rt_enter_frame(ctx, 512);
|
||||
if (!fp) return JS_EXCEPTION;
|
||||
fp[0] = this_val;
|
||||
for (int i = 0; i < argc && i < 510; i++)
|
||||
frame[1 + i] = argv[i];
|
||||
fp[1 + i] = argv[i];
|
||||
|
||||
/* Link to outer frame for closure access */
|
||||
frame[QBE_FRAME_OUTER_SLOT] = (JSValue)(uintptr_t)g_native_fn_registry[magic].outer_fp;
|
||||
/* Store the magic ID (registry index) so cell_rt_get/put_closure
|
||||
can re-derive the enclosing frame from the GC ref at call time,
|
||||
surviving GC moves */
|
||||
fp[QBE_FRAME_OUTER_SLOT] = (JSValue)(int64_t)magic;
|
||||
|
||||
return fn(ctx, frame);
|
||||
JSValue result = fn(ctx, fp);
|
||||
cell_rt_leave_frame(ctx);
|
||||
if (result == JS_EXCEPTION)
|
||||
return JS_EXCEPTION;
|
||||
return result;
|
||||
}
|
||||
|
||||
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp) {
|
||||
(void)outer_fp;
|
||||
if (g_native_fn_count >= MAX_NATIVE_FN)
|
||||
return JS_ThrowTypeError(ctx, "too many native functions (max %d)", MAX_NATIVE_FN);
|
||||
|
||||
int global_id = g_native_fn_count++;
|
||||
g_native_fn_registry[global_id].dl_handle = g_current_dl_handle;
|
||||
g_native_fn_registry[global_id].fn_idx = (int)fn_idx;
|
||||
g_native_fn_registry[global_id].outer_fp = outer_fp;
|
||||
|
||||
/* Create independent GC ref so the enclosing frame survives
|
||||
even after cell_rt_leave_frame pops the stack ref */
|
||||
if (g_aot_depth > 0) {
|
||||
JSGCRef *ref = &g_native_fn_registry[global_id].frame_ref;
|
||||
JS_AddGCRef(ctx, ref);
|
||||
ref->val = g_aot_gc_refs[g_aot_depth - 1].val;
|
||||
g_native_fn_registry[global_id].has_frame_ref = 1;
|
||||
} else {
|
||||
g_native_fn_registry[global_id].has_frame_ref = 0;
|
||||
}
|
||||
|
||||
return JS_NewCFunction2(ctx, (JSCFunction *)cell_fn_trampoline, "native_fn",
|
||||
255, JS_CFUNC_generic_magic, global_id);
|
||||
@@ -369,11 +505,13 @@ JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) {
|
||||
}
|
||||
|
||||
void cell_rt_setarg(JSValue frame_val, int64_t idx, JSValue val) {
|
||||
if (frame_val == JS_EXCEPTION || frame_val == JS_NULL) return;
|
||||
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
||||
fr->slots[idx] = val;
|
||||
}
|
||||
|
||||
JSValue cell_rt_invoke(JSContext *ctx, JSValue frame_val) {
|
||||
if (frame_val == JS_EXCEPTION) return JS_EXCEPTION;
|
||||
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
||||
int nr_slots = (int)objhdr_cap56(fr->header);
|
||||
int c_argc = (nr_slots >= 2) ? nr_slots - 2 : 0;
|
||||
@@ -474,18 +612,56 @@ int cell_rt_is_proxy(JSContext *ctx, JSValue v) {
|
||||
return fn->length == 2;
|
||||
}
|
||||
|
||||
/* --- Short-circuit and/or (non-allocating) --- */
|
||||
|
||||
JSValue cell_rt_and(JSContext *ctx, JSValue left, JSValue right) {
|
||||
return JS_ToBool(ctx, left) ? right : left;
|
||||
}
|
||||
|
||||
JSValue cell_rt_or(JSContext *ctx, JSValue left, JSValue right) {
|
||||
return JS_ToBool(ctx, left) ? left : right;
|
||||
}
|
||||
|
||||
/* --- Exception checking ---
|
||||
After potentially-throwing runtime calls, QBE-generated code needs to
|
||||
check for pending exceptions and branch to the disruption handler. */
|
||||
|
||||
void cell_rt_clear_exception(JSContext *ctx) {
|
||||
if (JS_HasException(ctx))
|
||||
JS_GetException(ctx);
|
||||
}
|
||||
|
||||
/* --- Disruption --- */
|
||||
|
||||
void cell_rt_disrupt(JSContext *ctx) {
|
||||
JS_ThrowTypeError(ctx, "type error in native code");
|
||||
}
|
||||
|
||||
/* --- in: key in obj --- */
|
||||
|
||||
JSValue cell_rt_in(JSContext *ctx, JSValue key, JSValue obj) {
|
||||
int has = JS_HasProperty(ctx, obj, key);
|
||||
return JS_NewBool(ctx, has > 0);
|
||||
}
|
||||
|
||||
/* --- regexp: create regex from pattern and flags --- */
|
||||
|
||||
JSValue cell_rt_regexp(JSContext *ctx, const char *pattern, const char *flags) {
|
||||
JSValue argv[2];
|
||||
argv[0] = JS_NewString(ctx, pattern);
|
||||
argv[1] = JS_NewString(ctx, flags);
|
||||
JSValue re = js_regexp_constructor(ctx, JS_NULL, 2, argv);
|
||||
if (JS_IsException(re))
|
||||
return JS_EXCEPTION;
|
||||
return re;
|
||||
}
|
||||
|
||||
/* --- Module entry point ---
|
||||
Loads a native .cm module from a dylib handle.
|
||||
Looks up cell_main, builds a heap-allocated frame, sets
|
||||
g_current_dl_handle so closures register in the right module. */
|
||||
|
||||
JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle) {
|
||||
JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle, JSValue env) {
|
||||
cell_compiled_fn fn = (cell_compiled_fn)dlsym(dl_handle, "cell_main");
|
||||
if (!fn)
|
||||
return JS_ThrowTypeError(ctx, "cell_main not found in native module dylib");
|
||||
@@ -495,22 +671,31 @@ JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle) {
|
||||
void *prev_handle = g_current_dl_handle;
|
||||
g_current_dl_handle = dl_handle;
|
||||
|
||||
/* Heap-allocate so closures created in cell_main can reference
|
||||
this frame after the module entry returns. */
|
||||
JSValue *frame = calloc(512, sizeof(JSValue));
|
||||
if (!frame) {
|
||||
/* Make env available for cell_rt_get_intrinsic lookups */
|
||||
cell_rt_set_native_env(ctx, env);
|
||||
|
||||
/* GC-managed frame for module execution */
|
||||
JSValue *fp = cell_rt_enter_frame(ctx, 512);
|
||||
if (!fp) {
|
||||
g_current_dl_handle = prev_handle;
|
||||
return JS_ThrowTypeError(ctx, "frame allocation failed");
|
||||
}
|
||||
|
||||
JSValue result = fn(ctx, frame);
|
||||
/* Clear any stale exception left by a previous interpreted run */
|
||||
if (JS_HasException(ctx))
|
||||
JS_GetException(ctx);
|
||||
|
||||
JSValue result = fn(ctx, fp);
|
||||
cell_rt_leave_frame(ctx); /* safe — closures have independent GC refs */
|
||||
g_current_dl_handle = prev_handle;
|
||||
if (result == JS_EXCEPTION)
|
||||
return JS_EXCEPTION;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Load a native module from a dylib handle, trying a named symbol first.
|
||||
Falls back to cell_main if the named symbol is not found. */
|
||||
JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const char *sym_name) {
|
||||
JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const char *sym_name, JSValue env) {
|
||||
cell_compiled_fn fn = NULL;
|
||||
if (sym_name)
|
||||
fn = (cell_compiled_fn)dlsym(dl_handle, sym_name);
|
||||
@@ -522,19 +707,25 @@ JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const
|
||||
void *prev_handle = g_current_dl_handle;
|
||||
g_current_dl_handle = dl_handle;
|
||||
|
||||
JSValue *frame = calloc(512, sizeof(JSValue));
|
||||
if (!frame) {
|
||||
/* Make env available for cell_rt_get_intrinsic lookups */
|
||||
cell_rt_set_native_env(ctx, env);
|
||||
|
||||
JSValue *fp = cell_rt_enter_frame(ctx, 512);
|
||||
if (!fp) {
|
||||
g_current_dl_handle = prev_handle;
|
||||
return JS_ThrowTypeError(ctx, "frame allocation failed");
|
||||
}
|
||||
|
||||
JSValue result = fn(ctx, frame);
|
||||
JSValue result = fn(ctx, fp);
|
||||
cell_rt_leave_frame(ctx); /* safe — closures have independent GC refs */
|
||||
g_current_dl_handle = prev_handle;
|
||||
if (result == JS_EXCEPTION)
|
||||
return JS_EXCEPTION;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Backward-compat: uses RTLD_DEFAULT (works when dylib opened with RTLD_GLOBAL) */
|
||||
JSValue cell_rt_module_entry(JSContext *ctx) {
|
||||
void *handle = dlopen(NULL, RTLD_LAZY);
|
||||
return cell_rt_native_module_load(ctx, handle);
|
||||
return cell_rt_native_module_load(ctx, handle, JS_NULL);
|
||||
}
|
||||
|
||||
@@ -495,8 +495,8 @@ typedef enum MachOpcode {
|
||||
MACH_MOD, /* R(A) = R(B) % R(C) */
|
||||
MACH_POW, /* R(A) = R(B) ** R(C) */
|
||||
MACH_NEG, /* R(A) = -R(B) */
|
||||
MACH_INC, /* R(A) = R(B) + 1 */
|
||||
MACH_DEC, /* R(A) = R(B) - 1 */
|
||||
MACH__DEAD_INC, /* reserved — was MACH_INC, never emitted */
|
||||
MACH__DEAD_DEC, /* reserved — was MACH_DEC, never emitted */
|
||||
|
||||
/* Generic comparison (ABC) — used by legacy .mach */
|
||||
MACH_EQ, /* R(A) = (R(B) == R(C)) */
|
||||
@@ -639,9 +639,6 @@ typedef enum MachOpcode {
|
||||
/* Error handling */
|
||||
MACH_DISRUPT, /* trigger disruption (A only) */
|
||||
|
||||
/* Variable storage */
|
||||
MACH_SET_VAR, /* env/global[K(Bx)] = R(A) — store to var (ABx) */
|
||||
|
||||
/* Misc */
|
||||
MACH_IN, /* R(A) = (R(B) in R(C)) — has property (ABC) */
|
||||
|
||||
@@ -671,8 +668,8 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
|
||||
[MACH_MOD] = "mod",
|
||||
[MACH_POW] = "pow",
|
||||
[MACH_NEG] = "neg",
|
||||
[MACH_INC] = "inc",
|
||||
[MACH_DEC] = "dec",
|
||||
[MACH__DEAD_INC] = "dead_inc",
|
||||
[MACH__DEAD_DEC] = "dead_dec",
|
||||
[MACH_EQ] = "eq",
|
||||
[MACH_NEQ] = "neq",
|
||||
[MACH_LT] = "lt",
|
||||
@@ -764,7 +761,6 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
|
||||
[MACH_GOINVOKE] = "goinvoke",
|
||||
[MACH_JMPNOTNULL] = "jmpnotnull",
|
||||
[MACH_DISRUPT] = "disrupt",
|
||||
[MACH_SET_VAR] = "set_var",
|
||||
[MACH_IN] = "in",
|
||||
/* Extended type checks */
|
||||
[MACH_IS_ARRAY] = "is_array",
|
||||
|
||||
@@ -1688,6 +1688,34 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
|
||||
scan += obj_size;
|
||||
}
|
||||
|
||||
#ifdef VALIDATE_GC
|
||||
{
|
||||
JSRecord *grec = JS_VALUE_GET_RECORD(ctx->global_obj);
|
||||
uint32_t mask = (uint32_t)objhdr_cap56(grec->mist_hdr);
|
||||
for (uint32_t i = 1; i <= mask; i++) {
|
||||
JSValue k = grec->slots[i].key;
|
||||
if (!rec_key_is_empty(k) && !rec_key_is_tomb(k)) {
|
||||
if (!JS_IsPtr(k)) {
|
||||
fprintf(stderr, "VALIDATE_GC: global slot[%u] key is not a pointer (tag=0x%llx)\n",
|
||||
i, (unsigned long long)k);
|
||||
} else {
|
||||
void *kp = JS_VALUE_GET_PTR(k);
|
||||
if (!ptr_in_range(kp, to_base, to_free) && !is_ct_ptr(ctx, kp)) {
|
||||
fprintf(stderr, "VALIDATE_GC: global slot[%u] key=%p outside valid ranges\n", i, kp);
|
||||
}
|
||||
}
|
||||
JSValue v = grec->slots[i].val;
|
||||
if (JS_IsPtr(v)) {
|
||||
void *vp = JS_VALUE_GET_PTR(v);
|
||||
if (!ptr_in_range(vp, to_base, to_free) && !is_ct_ptr(ctx, vp)) {
|
||||
fprintf(stderr, "VALIDATE_GC: global slot[%u] val=%p outside valid ranges\n", i, vp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Return old block (in poison mode, just poison it and leak) */
|
||||
heap_block_free (rt, from_base, old_heap_size);
|
||||
|
||||
@@ -1960,6 +1988,7 @@ JSContext *JS_NewContext (JSRuntime *rt) {
|
||||
if (!ctx) return NULL;
|
||||
JS_AddIntrinsicBaseObjects (ctx);
|
||||
JS_AddIntrinsicRegExp (ctx);
|
||||
obj_set_stone (JS_VALUE_GET_RECORD (ctx->global_obj));
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@@ -1967,7 +1996,8 @@ JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size) {
|
||||
JSContext *ctx = JS_NewContextRawWithHeapSize (rt, heap_size);
|
||||
if (!ctx) return NULL;
|
||||
JS_AddIntrinsicBaseObjects (ctx);
|
||||
JS_AddIntrinsicRegExp (ctx);
|
||||
JS_AddIntrinsicRegExp (ctx);
|
||||
obj_set_stone (JS_VALUE_GET_RECORD (ctx->global_obj));
|
||||
return ctx;
|
||||
}
|
||||
|
||||
@@ -3046,7 +3076,7 @@ JSValue JS_ThrowError2 (JSContext *ctx, JSErrorEnum error_num, const char *fmt,
|
||||
if (add_backtrace) {
|
||||
print_backtrace (ctx, NULL, 0, 0);
|
||||
}
|
||||
return JS_Throw (ctx, JS_NULL);
|
||||
return JS_Throw (ctx, JS_TRUE);
|
||||
}
|
||||
|
||||
static JSValue JS_ThrowError (JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap) {
|
||||
|
||||
458
streamline.cm
458
streamline.cm
@@ -1020,6 +1020,461 @@ var streamline = function(ir, log) {
|
||||
return null
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Pass: compress_slots — linear-scan register allocation
|
||||
// Reuses slots with non-overlapping live ranges to reduce
|
||||
// nr_slots. Mirrors mcode_compress_regs from mach.c.
|
||||
// Works across all functions for captured-slot tracking.
|
||||
// =========================================================
|
||||
|
||||
// Which instruction positions hold slot references (special cases)
|
||||
var slot_idx_special = {
|
||||
get: [1], put: [1],
|
||||
access: [1], int: [1], function: [1], regexp: [1],
|
||||
true: [1], false: [1], null: [1],
|
||||
record: [1], array: [1],
|
||||
invoke: [1, 2], tail_invoke: [1, 2],
|
||||
goinvoke: [1],
|
||||
setarg: [1, 3],
|
||||
frame: [1, 2], goframe: [1, 2],
|
||||
jump: [], disrupt: [],
|
||||
jump_true: [1], jump_false: [1], jump_not_null: [1],
|
||||
return: [1]
|
||||
}
|
||||
|
||||
var get_slot_refs = function(instr) {
|
||||
var special = slot_idx_special[instr[0]]
|
||||
var result = null
|
||||
var j = 0
|
||||
var limit = 0
|
||||
if (special != null) return special
|
||||
result = []
|
||||
limit = length(instr) - 2
|
||||
j = 1
|
||||
while (j < limit) {
|
||||
if (is_number(instr[j])) result[] = j
|
||||
j = j + 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var compress_one_fn = function(func, captured_slots) {
|
||||
var instructions = func.instructions
|
||||
var nr_slots = func.nr_slots
|
||||
var nr_args = func.nr_args != null ? func.nr_args : 0
|
||||
var n = 0
|
||||
var pinned = 0
|
||||
var first_ref = null
|
||||
var last_ref = null
|
||||
var i = 0
|
||||
var j = 0
|
||||
var k = 0
|
||||
var s = 0
|
||||
var instr = null
|
||||
var refs = null
|
||||
var op = null
|
||||
var target = null
|
||||
var tpos = 0
|
||||
var changed = false
|
||||
var label_map = null
|
||||
var live_slots = null
|
||||
var live_first = null
|
||||
var live_last = null
|
||||
var cnt = 0
|
||||
var key_s = 0
|
||||
var key_f = 0
|
||||
var key_l = 0
|
||||
var remap = null
|
||||
var pool = null
|
||||
var next_phys = 0
|
||||
var active_phys = null
|
||||
var active_last = null
|
||||
var phys = 0
|
||||
var mi = 0
|
||||
var new_max = 0
|
||||
var old_val = 0
|
||||
var new_active_phys = null
|
||||
var new_active_last = null
|
||||
var new_pool = null
|
||||
|
||||
if (instructions == null || !is_number(nr_slots) || nr_slots <= 1) return null
|
||||
n = length(instructions)
|
||||
pinned = 1 + nr_args
|
||||
|
||||
// Step 1: build live ranges
|
||||
first_ref = array(nr_slots, -1)
|
||||
last_ref = array(nr_slots, -1)
|
||||
|
||||
// Pin this + args
|
||||
k = 0
|
||||
while (k < pinned) {
|
||||
first_ref[k] = 0
|
||||
last_ref[k] = n
|
||||
k = k + 1
|
||||
}
|
||||
|
||||
// Scan instructions for slot references
|
||||
i = 0
|
||||
while (i < n) {
|
||||
instr = instructions[i]
|
||||
if (is_array(instr)) {
|
||||
refs = get_slot_refs(instr)
|
||||
j = 0
|
||||
while (j < length(refs)) {
|
||||
s = instr[refs[j]]
|
||||
if (is_number(s) && s >= 0 && s < nr_slots) {
|
||||
if (first_ref[s] < 0) first_ref[s] = i
|
||||
last_ref[s] = i
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// Pin captured slots (AFTER scan so last_ref isn't overwritten)
|
||||
if (captured_slots != null) {
|
||||
k = 0
|
||||
while (k < length(captured_slots)) {
|
||||
s = captured_slots[k]
|
||||
if (s >= 0 && s < nr_slots) {
|
||||
if (first_ref[s] < 0) first_ref[s] = 0
|
||||
last_ref[s] = n
|
||||
}
|
||||
k = k + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Step 1b: extend for backward jumps (loops)
|
||||
label_map = {}
|
||||
i = 0
|
||||
while (i < n) {
|
||||
instr = instructions[i]
|
||||
if (is_text(instr) && !starts_with(instr, "_nop_")) {
|
||||
label_map[instr] = i
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
changed = true
|
||||
while (changed) {
|
||||
changed = false
|
||||
i = 0
|
||||
while (i < n) {
|
||||
instr = instructions[i]
|
||||
if (!is_array(instr)) {
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
op = instr[0]
|
||||
target = null
|
||||
if (op == "jump") {
|
||||
target = instr[1]
|
||||
} else if (op == "jump_true" || op == "jump_false" || op == "jump_not_null") {
|
||||
target = instr[2]
|
||||
}
|
||||
if (target == null || !is_text(target)) {
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
tpos = label_map[target]
|
||||
if (tpos == null || tpos >= i) {
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
// Backward jump: extend slots live into loop
|
||||
s = pinned
|
||||
while (s < nr_slots) {
|
||||
if (first_ref[s] >= 0 && first_ref[s] < tpos && last_ref[s] >= tpos && last_ref[s] < i) {
|
||||
last_ref[s] = i
|
||||
changed = true
|
||||
}
|
||||
s = s + 1
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: sort live intervals by first_ref
|
||||
live_slots = []
|
||||
live_first = []
|
||||
live_last = []
|
||||
s = pinned
|
||||
while (s < nr_slots) {
|
||||
if (first_ref[s] >= 0) {
|
||||
live_slots[] = s
|
||||
live_first[] = first_ref[s]
|
||||
live_last[] = last_ref[s]
|
||||
}
|
||||
s = s + 1
|
||||
}
|
||||
|
||||
cnt = length(live_slots)
|
||||
i = 1
|
||||
while (i < cnt) {
|
||||
key_s = live_slots[i]
|
||||
key_f = live_first[i]
|
||||
key_l = live_last[i]
|
||||
j = i - 1
|
||||
while (j >= 0 && (live_first[j] > key_f || (live_first[j] == key_f && live_slots[j] > key_s))) {
|
||||
live_slots[j + 1] = live_slots[j]
|
||||
live_first[j + 1] = live_first[j]
|
||||
live_last[j + 1] = live_last[j]
|
||||
j = j - 1
|
||||
}
|
||||
live_slots[j + 1] = key_s
|
||||
live_first[j + 1] = key_f
|
||||
live_last[j + 1] = key_l
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// Linear-scan allocation
|
||||
remap = array(nr_slots)
|
||||
s = 0
|
||||
while (s < nr_slots) {
|
||||
remap[s] = s
|
||||
s = s + 1
|
||||
}
|
||||
|
||||
pool = []
|
||||
next_phys = pinned
|
||||
active_phys = []
|
||||
active_last = []
|
||||
|
||||
i = 0
|
||||
while (i < cnt) {
|
||||
// Expire intervals whose last < live_first[i]
|
||||
new_active_phys = []
|
||||
new_active_last = []
|
||||
j = 0
|
||||
while (j < length(active_phys)) {
|
||||
if (active_last[j] < live_first[i]) {
|
||||
pool[] = active_phys[j]
|
||||
} else {
|
||||
new_active_phys[] = active_phys[j]
|
||||
new_active_last[] = active_last[j]
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
active_phys = new_active_phys
|
||||
active_last = new_active_last
|
||||
|
||||
// Pick lowest available physical register
|
||||
if (length(pool) > 0) {
|
||||
mi = 0
|
||||
j = 1
|
||||
while (j < length(pool)) {
|
||||
if (pool[j] < pool[mi]) mi = j
|
||||
j = j + 1
|
||||
}
|
||||
phys = pool[mi]
|
||||
new_pool = []
|
||||
j = 0
|
||||
while (j < length(pool)) {
|
||||
if (j != mi) new_pool[] = pool[j]
|
||||
j = j + 1
|
||||
}
|
||||
pool = new_pool
|
||||
} else {
|
||||
phys = next_phys
|
||||
next_phys = next_phys + 1
|
||||
}
|
||||
|
||||
remap[live_slots[i]] = phys
|
||||
active_phys[] = phys
|
||||
active_last[] = live_last[i]
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// Compute new nr_slots
|
||||
new_max = pinned
|
||||
s = 0
|
||||
while (s < nr_slots) {
|
||||
if (first_ref[s] >= 0 && remap[s] >= new_max) {
|
||||
new_max = remap[s] + 1
|
||||
}
|
||||
s = s + 1
|
||||
}
|
||||
|
||||
if (new_max >= nr_slots) return null
|
||||
|
||||
// Step 3: apply remap to instructions
|
||||
i = 0
|
||||
while (i < n) {
|
||||
instr = instructions[i]
|
||||
if (is_array(instr)) {
|
||||
refs = get_slot_refs(instr)
|
||||
j = 0
|
||||
while (j < length(refs)) {
|
||||
old_val = instr[refs[j]]
|
||||
if (is_number(old_val) && old_val >= 0 && old_val < nr_slots) {
|
||||
instr[refs[j]] = remap[old_val]
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
func.nr_slots = new_max
|
||||
return remap
|
||||
}
|
||||
|
||||
var compress_slots = function(ir) {
|
||||
if (ir == null || ir.main == null) return null
|
||||
var functions = ir.functions != null ? ir.functions : []
|
||||
var func_count = length(functions)
|
||||
var parent_of = null
|
||||
var captured = null
|
||||
var remaps = null
|
||||
var remap_sizes = null
|
||||
var instrs = null
|
||||
var instr = null
|
||||
var child_idx = 0
|
||||
var parent_slot = 0
|
||||
var level = 0
|
||||
var ancestor = 0
|
||||
var caps = null
|
||||
var found = false
|
||||
var anc_remap = null
|
||||
var old_slot = 0
|
||||
var fi = 0
|
||||
var i = 0
|
||||
var j = 0
|
||||
var k = 0
|
||||
|
||||
// Build parent_of: parent_of[i] = parent index, func_count = main
|
||||
parent_of = array(func_count, -1)
|
||||
|
||||
// Scan main for function instructions
|
||||
if (ir.main != null && ir.main.instructions != null) {
|
||||
instrs = ir.main.instructions
|
||||
i = 0
|
||||
while (i < length(instrs)) {
|
||||
instr = instrs[i]
|
||||
if (is_array(instr) && instr[0] == "function") {
|
||||
child_idx = instr[2]
|
||||
if (child_idx >= 0 && child_idx < func_count) {
|
||||
parent_of[child_idx] = func_count
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Scan each function for function instructions
|
||||
fi = 0
|
||||
while (fi < func_count) {
|
||||
instrs = functions[fi].instructions
|
||||
if (instrs != null) {
|
||||
i = 0
|
||||
while (i < length(instrs)) {
|
||||
instr = instrs[i]
|
||||
if (is_array(instr) && instr[0] == "function") {
|
||||
child_idx = instr[2]
|
||||
if (child_idx >= 0 && child_idx < func_count) {
|
||||
parent_of[child_idx] = fi
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
fi = fi + 1
|
||||
}
|
||||
|
||||
// Build captured slots per function
|
||||
captured = array(func_count + 1)
|
||||
i = 0
|
||||
while (i < func_count + 1) {
|
||||
captured[i] = []
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
fi = 0
|
||||
while (fi < func_count) {
|
||||
instrs = functions[fi].instructions
|
||||
if (instrs != null) {
|
||||
i = 0
|
||||
while (i < length(instrs)) {
|
||||
instr = instrs[i]
|
||||
if (is_array(instr) && (instr[0] == "get" || instr[0] == "put")) {
|
||||
parent_slot = instr[2]
|
||||
level = instr[3]
|
||||
ancestor = fi
|
||||
j = 0
|
||||
while (j < level && ancestor >= 0) {
|
||||
ancestor = parent_of[ancestor]
|
||||
j = j + 1
|
||||
}
|
||||
if (ancestor >= 0) {
|
||||
caps = captured[ancestor]
|
||||
found = false
|
||||
k = 0
|
||||
while (k < length(caps)) {
|
||||
if (caps[k] == parent_slot) {
|
||||
found = true
|
||||
k = length(caps)
|
||||
}
|
||||
k = k + 1
|
||||
}
|
||||
if (!found) caps[] = parent_slot
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
fi = fi + 1
|
||||
}
|
||||
|
||||
// Compress each function and save remap tables
|
||||
remaps = array(func_count + 1)
|
||||
remap_sizes = array(func_count + 1, 0)
|
||||
|
||||
fi = 0
|
||||
while (fi < func_count) {
|
||||
remap_sizes[fi] = functions[fi].nr_slots
|
||||
remaps[fi] = compress_one_fn(functions[fi], captured[fi])
|
||||
fi = fi + 1
|
||||
}
|
||||
|
||||
if (ir.main != null) {
|
||||
remap_sizes[func_count] = ir.main.nr_slots
|
||||
remaps[func_count] = compress_one_fn(ir.main, captured[func_count])
|
||||
}
|
||||
|
||||
// Fix get/put parent_slot references using ancestor remap tables
|
||||
fi = 0
|
||||
while (fi < func_count) {
|
||||
instrs = functions[fi].instructions
|
||||
if (instrs != null) {
|
||||
i = 0
|
||||
while (i < length(instrs)) {
|
||||
instr = instrs[i]
|
||||
if (is_array(instr) && (instr[0] == "get" || instr[0] == "put")) {
|
||||
level = instr[3]
|
||||
ancestor = fi
|
||||
j = 0
|
||||
while (j < level && ancestor >= 0) {
|
||||
ancestor = parent_of[ancestor]
|
||||
j = j + 1
|
||||
}
|
||||
if (ancestor >= 0 && remaps[ancestor] != null) {
|
||||
anc_remap = remaps[ancestor]
|
||||
old_slot = instr[2]
|
||||
if (old_slot >= 0 && old_slot < remap_sizes[ancestor]) {
|
||||
instr[2] = anc_remap[old_slot]
|
||||
}
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
fi = fi + 1
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Compose all passes
|
||||
// =========================================================
|
||||
@@ -1090,6 +1545,9 @@ var streamline = function(ir, log) {
|
||||
}
|
||||
}
|
||||
|
||||
// Compress slots across all functions (must run after per-function passes)
|
||||
compress_slots(ir)
|
||||
|
||||
return ir
|
||||
}
|
||||
|
||||
|
||||
4
test.ce
4
test.ce
@@ -443,9 +443,9 @@ function run_tests(package_name, specific_test) {
|
||||
var src_path = prefix + '/' + f
|
||||
var src = text(fd.slurp(src_path))
|
||||
var ast = analyze(src, src_path)
|
||||
test_mod_noopt = run_ast_noopt_fn(mod_path + '_noopt', ast, {
|
||||
test_mod_noopt = run_ast_noopt_fn(mod_path + '_noopt', ast, stone({
|
||||
use: function(path) { return shop.use(path, use_pkg) }
|
||||
})
|
||||
}))
|
||||
} disruption {
|
||||
log.console(` DIFF: failed to load noopt module for ${f}`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user