Merge branch 'fix_aot'

This commit is contained in:
2026-02-19 00:47:41 -06:00
20 changed files with 3320 additions and 609 deletions

View File

@@ -417,17 +417,25 @@ function format_ops(ops) {
// Load a module for benchmarking in the given mode
// Returns the module value, or null on failure
function load_bench_module(f, package_name, mode) {
function resolve_bench_load(f, package_name) {
var mod_path = text(f, 0, -3)
var use_pkg = package_name ? package_name : fd.realpath('.')
var prefix = null
var src_path = null
var prefix = testlib.get_pkg_dir(package_name)
var src_path = prefix + '/' + f
return {mod_path, use_pkg, src_path}
}
function load_bench_module_native(f, package_name) {
var r = resolve_bench_load(f, package_name)
return shop.use_native(r.src_path, r.use_pkg)
}
function load_bench_module(f, package_name, mode) {
var r = resolve_bench_load(f, package_name)
if (mode == "native") {
prefix = testlib.get_pkg_dir(package_name)
src_path = prefix + '/' + f
return shop.use_native(src_path, use_pkg)
return load_bench_module_native(f, package_name)
}
return shop.use(mod_path, use_pkg)
return shop.use(r.mod_path, r.use_pkg)
}
// Collect benchmark functions from a loaded module

133
build.cm
View File

@@ -80,6 +80,14 @@ function content_hash(str) {
return text(crypto.blake2(bb, 32), 'h')
}
// Enable AOT ASan by creating .cell/asan_aot in the package root.
function native_sanitize_flags() {
if (fd.is_file('.cell/asan_aot')) {
return ' -fsanitize=address -fno-omit-frame-pointer'
}
return ''
}
// ============================================================================
// Cache key salts — canonical registry
// Every artifact type has a unique salt so hash collisions between types
@@ -102,6 +110,10 @@ function manifest_path(pkg) {
return get_build_dir() + '/' + content_hash(pkg + '\n' + 'manifest')
}
function native_cache_content(src, target, san_flags) {
return src + '\n' + target + '\nnative\n' + (san_flags || '')
}
function get_build_dir() {
return shop.get_build_dir()
}
@@ -365,17 +377,17 @@ function compute_link_content(objects, ldflags, target_ldflags, opts) {
// Build a string representing all link inputs
var parts = []
push(parts, 'target:' + opts.target)
push(parts, 'cc:' + opts.cc)
push(parts, 'target:' + text(opts.target))
push(parts, 'cc:' + text(opts.cc))
arrfor(sorted_objects, function(obj) {
// Object paths are content-addressed, so the path itself is the hash
push(parts, 'obj:' + obj)
push(parts, 'obj:' + text(obj))
})
arrfor(ldflags, function(flag) {
push(parts, 'ldflag:' + flag)
push(parts, 'ldflag:' + text(flag))
})
arrfor(target_ldflags, function(flag) {
push(parts, 'target_ldflag:' + flag)
push(parts, 'target_ldflag:' + text(flag))
})
return text(parts, '\n')
@@ -449,9 +461,9 @@ Build.build_module_dylib = function(pkg, file, target, opts) {
}
push(cmd_parts, '-L"' + local_dir + '"')
push(cmd_parts, '"' + obj + '"')
push(cmd_parts, '"' + text(obj) + '"')
arrfor(_extra, function(extra_obj) {
push(cmd_parts, '"' + extra_obj + '"')
if (extra_obj != null) push(cmd_parts, '"' + text(extra_obj) + '"')
})
cmd_parts = array(cmd_parts, resolved_ldflags)
cmd_parts = array(cmd_parts, target_ldflags)
@@ -491,10 +503,12 @@ Build.build_dynamic = function(pkg, target, buildtype, opts) {
// Compile support sources to cached objects
var sources = pkg_tools.get_sources(pkg)
var support_objects = []
arrfor(sources, function(src_file) {
var obj = Build.compile_file(pkg, src_file, _target, {buildtype: _buildtype, cflags: cached_cflags, verbose: _opts.verbose})
push(support_objects, obj)
})
if (pkg != 'core') {
arrfor(sources, function(src_file) {
var obj = Build.compile_file(pkg, src_file, _target, {buildtype: _buildtype, cflags: cached_cflags, verbose: _opts.verbose})
if (obj != null) push(support_objects, obj)
})
}
arrfor(c_files, function(file) {
var sym_name = shop.c_symbol_for_file(pkg, file)
@@ -610,62 +624,22 @@ Build.build_static = function(packages, target, output, buildtype) {
// il_parts: {data: text, functions: [text, ...]}
// cc: C compiler path
// tmp_prefix: prefix for temp files (e.g. /tmp/cell_native_<hash>)
function compile_native_batched(il_parts, cc, tmp_prefix) {
var nfuncs = length(il_parts.functions)
var nbatch = 8
var o_paths = []
var s_paths = []
var asm_cmds = []
var batch_fns = null
var batch_il = null
var asm_text = null
var s_path = null
var o_path = null
var end = 0
var bi = 0
var fi = 0
var ai = 0
var rc = null
var parallel_cmd = null
function compile_native_single(il_parts, cc, tmp_prefix, extra_flags) {
var _extra = extra_flags || ''
var helpers_il = (il_parts.helpers && length(il_parts.helpers) > 0)
? text(il_parts.helpers, "\n") : ""
var prefix = null
if (nfuncs < nbatch) nbatch = nfuncs
if (nbatch < 1) nbatch = 1
// Generate .s files: run QBE on each batch
while (bi < nbatch) {
batch_fns = []
end = nfuncs * (bi + 1) / nbatch
while (fi < end) {
batch_fns[] = il_parts.functions[fi]
fi = fi + 1
}
// Batch 0 includes helper functions; others reference them as external symbols
prefix = (bi == 0 && helpers_il != "") ? helpers_il + "\n\n" : ""
batch_il = il_parts.data + "\n\n" + prefix + text(batch_fns, "\n")
asm_text = os.qbe(batch_il)
s_path = tmp_prefix + '_b' + text(bi) + '.s'
o_path = tmp_prefix + '_b' + text(bi) + '.o'
fd.slurpwrite(s_path, stone(blob(asm_text)))
s_paths[] = s_path
o_paths[] = o_path
bi = bi + 1
}
// Assemble all batches in parallel
while (ai < length(s_paths)) {
asm_cmds[] = cc + ' -c ' + s_paths[ai] + ' -o ' + o_paths[ai]
ai = ai + 1
}
parallel_cmd = text(asm_cmds, ' & ') + ' & wait'
rc = os.system(parallel_cmd)
var all_fns = text(il_parts.functions, "\n")
var full_il = il_parts.data + "\n\n" + helpers_il + "\n\n" + all_fns
var asm_text = os.qbe(full_il)
var s_path = tmp_prefix + '.s'
var o_path = tmp_prefix + '.o'
var rc = null
fd.slurpwrite(s_path, stone(blob(asm_text)))
rc = os.system(cc + _extra + ' -c ' + s_path + ' -o ' + o_path)
if (rc != 0) {
print('Parallel assembly failed'); disrupt
print('Assembly failed'); disrupt
}
return o_paths
return [o_path]
}
// Post-process QBE IL: insert dead labels after ret/jmp (QBE requirement)
@@ -710,6 +684,8 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
var tc = toolchains[_target]
var cc = tc.c
var san_flags = native_sanitize_flags()
var san_suffix = length(san_flags) > 0 ? '_asan' : ''
// Step 1: Compile through pipeline
var optimized = shop.compile_file(src_path)
@@ -724,31 +700,33 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
var il_parts = qbe_emit(optimized, qbe_macros, sym_name)
// Content hash for cache key
var src = text(fd.slurp(src_path))
var native_key = native_cache_content(src, _target, san_flags)
var build_dir = get_build_dir()
ensure_dir(build_dir)
var dylib_path = cache_path(text(fd.slurp(src_path)) + '\n' + _target, SALT_NATIVE)
var dylib_path = cache_path(native_key, SALT_NATIVE)
if (fd.is_file(dylib_path))
return dylib_path
// Compile and assemble via batched parallel pipeline
var tmp = '/tmp/cell_native_' + content_hash(src_path)
var rt_o_path = '/tmp/cell_qbe_rt.o'
var tmp = '/tmp/cell_native_' + content_hash(native_key)
var rt_o_path = '/tmp/cell_qbe_rt' + san_suffix + '.o'
var o_paths = compile_native_batched(il_parts, cc, tmp)
var o_paths = compile_native_single(il_parts, cc, tmp, san_flags)
// Compile QBE runtime stubs if needed
var rc = null
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')
rc = os.system(cc + san_flags + ' -c ' + qbe_rt_path + ' -o ' + rt_o_path + ' -fPIC')
if (rc != 0) {
print('QBE runtime stubs compilation failed'); disrupt
}
}
// Link dylib
var link_cmd = cc + ' -shared -fPIC'
var link_cmd = cc + san_flags + ' -shared -fPIC'
if (tc.system == 'darwin') {
link_cmd = link_cmd + ' -undefined dynamic_lookup'
} else if (tc.system == 'linux') {
@@ -782,6 +760,8 @@ Build.compile_native_ir = function(optimized, src_path, opts) {
var tc = toolchains[_target]
var cc = tc.c
var san_flags = native_sanitize_flags()
var san_suffix = length(san_flags) > 0 ? '_asan' : ''
var qbe_macros = use('qbe')
var qbe_emit = use('qbe_emit')
@@ -793,31 +773,32 @@ Build.compile_native_ir = function(optimized, src_path, opts) {
var il_parts = qbe_emit(optimized, qbe_macros, sym_name)
var src = text(fd.slurp(src_path))
var native_key = native_cache_content(src, _target, san_flags)
var build_dir = get_build_dir()
ensure_dir(build_dir)
var dylib_path = cache_path(src + '\n' + _target, SALT_NATIVE)
var dylib_path = cache_path(native_key, SALT_NATIVE)
if (fd.is_file(dylib_path))
return dylib_path
// Compile and assemble via batched parallel pipeline
var tmp = '/tmp/cell_native_' + content_hash(src_path)
var rt_o_path = '/tmp/cell_qbe_rt.o'
var tmp = '/tmp/cell_native_' + content_hash(native_key)
var rt_o_path = '/tmp/cell_qbe_rt' + san_suffix + '.o'
var o_paths = compile_native_batched(il_parts, cc, tmp)
var o_paths = compile_native_single(il_parts, cc, tmp, san_flags)
// Compile QBE runtime stubs if needed
var rc = null
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')
rc = os.system(cc + san_flags + ' -c ' + qbe_rt_path + ' -o ' + rt_o_path + ' -fPIC')
if (rc != 0) {
print('QBE runtime stubs compilation failed'); disrupt
}
}
// Link dylib
var link_cmd = cc + ' -shared -fPIC'
var link_cmd = cc + san_flags + ' -shared -fPIC'
if (tc.system == 'darwin') {
link_cmd = link_cmd + ' -undefined dynamic_lookup'
} else if (tc.system == 'linux') {
@@ -968,5 +949,7 @@ Build.SALT_DEPS = SALT_DEPS
Build.SALT_FAIL = SALT_FAIL
Build.cache_path = cache_path
Build.manifest_path = manifest_path
Build.native_sanitize_flags = native_sanitize_flags
Build.native_cache_content = native_cache_content
return Build

View File

@@ -168,6 +168,9 @@ pit bench <suite> # run specific benchmark file
pit bench package <name> # benchmark a named package
pit bench package <name> <suite> # specific benchmark in a package
pit bench package all # benchmark all packages
pit bench --bytecode <suite> # force bytecode-only benchmark run
pit bench --native <suite> # force native-only benchmark run
pit bench --compare <suite> # run bytecode and native side-by-side
```
Output includes median, mean, standard deviation, and percentiles for each benchmark.

View File

@@ -73,7 +73,7 @@ use('gitea.pockle.world/john/renderer/sprite')
## Compilation and Caching
Every module goes through a content-addressed caching pipeline. The cache key is the BLAKE2 hash of the source content, so changing the source automatically invalidates the cache.
Every module goes through a content-addressed caching pipeline. Cache keys are based on the inputs that affect the output artifact, so changing any relevant input automatically invalidates the cache.
### Cache Hierarchy
@@ -124,6 +124,8 @@ Dylibs live at content-addressed paths (`~/.cell/build/<hash>`) that can only be
At runtime, when `use()` needs a C module from another package, the shop reads the manifest to find the dylib path. This means `cell build` must be run before C modules from packages can be loaded.
For native `.cm` dylibs, the cache content includes source, target, native mode marker, and sanitize flags, then uses the `native` salt. Changing any of those inputs produces a new cache path automatically.
### Core Module Caching
Core modules loaded via `use_core()` in engine.cm follow the same content-addressed pattern. On first use, a module is compiled from source and cached by the BLAKE2 hash of its source content. Subsequent loads with unchanged source hit the cache directly.

View File

@@ -118,6 +118,45 @@ When a mismatch is found:
MISMATCH: test_foo: result mismatch opt=42 noopt=43
```
## ASAN for Native AOT
When debugging native (`shop.use_native`) crashes, there are two useful sanitizer workflows.
### 1) AOT-only sanitizer (fastest loop)
Enable sanitizer flags for generated native modules by creating a marker file:
```bash
touch .cell/asan_aot
cell --dev bench --native fibonacci
```
This adds `-fsanitize=address -fno-omit-frame-pointer` to AOT module compilation.
Disable it with:
```bash
rm -f .cell/asan_aot
```
### 2) Full runtime sanitizer (CLI + runtime + AOT)
Build an ASAN-instrumented `cell` binary:
```bash
meson setup build-asan -Dbuildtype=debug -Db_sanitize=address
CCACHE_DISABLE=1 meson compile -C build-asan
ASAN_OPTIONS=abort_on_error=1:detect_leaks=0 ./build-asan/cell --dev bench --native fibonacci
```
This catches bugs crossing the boundary between generated dylibs and runtime helpers.
If stale native artifacts are suspected after compiler/runtime changes, clear build outputs first:
```bash
cell --dev clean shop --build
```
## Fuzz Testing
The fuzzer generates random self-checking programs, compiles them, and runs them through both optimized and unoptimized paths. Each generated program contains test functions that validate their own expected results, so failures catch both correctness bugs and optimizer mismatches.

View File

@@ -458,7 +458,7 @@ var fold = function(ast) {
else if (k == "-") result = lv - rv
else if (k == "*") result = lv * rv
else if (k == "/") result = lv / rv
else if (k == "%") result = lv % rv
else if (k == "%") result = lv - (trunc(lv / rv) * rv)
else if (k == "**") result = lv ** rv
if (result == null) return make_null(expr)
return make_number(result, expr)

View File

@@ -11,7 +11,9 @@ var json_mod = use_embed('json')
var crypto = use_embed('crypto')
function content_hash(content) {
return text(crypto.blake2(content), 'h')
var data = content
if (!is_blob(data)) data = stone(blob(text(data)))
return text(crypto.blake2(data), 'h')
}
function cache_path(hash) {

View File

@@ -35,7 +35,9 @@ var packages_path = shop_path ? shop_path + '/packages' : null
var use_cache = {}
function content_hash(content) {
return text(crypto.blake2(content), 'h')
var data = content
if (!is_blob(data)) data = stone(blob(text(data)))
return text(crypto.blake2(data), 'h')
}
function cache_path(hash) {

View File

@@ -449,7 +449,11 @@ function try_native_mod_dylib(pkg, stem) {
var host = detect_host_target()
if (!host) return null
var build_path = build_mod.cache_path(src + '\n' + host, build_mod.SALT_NATIVE)
var san_flags = build_mod.native_sanitize_flags ? build_mod.native_sanitize_flags() : ''
var native_key = build_mod.native_cache_content ?
build_mod.native_cache_content(src, host, san_flags) :
(src + '\n' + host)
var build_path = build_mod.cache_path(native_key, build_mod.SALT_NATIVE)
if (!fd.is_file(build_path)) return null
log.shop('native dylib cache hit: ' + stem)
@@ -862,7 +866,11 @@ function read_dylib_manifest(pkg) {
// Ensure all C modules for a package are built and loaded.
// Returns the array of {file, symbol, dylib} results, cached per package.
function ensure_package_dylibs(pkg) {
if (package_dylibs[pkg]) return package_dylibs[pkg]
if (package_dylibs[pkg] != null) return package_dylibs[pkg]
if (pkg == 'core') {
package_dylibs[pkg] = []
return []
}
var results = null
var build_mod = use_cache['core/build']
@@ -888,6 +896,7 @@ function ensure_package_dylibs(pkg) {
log.shop('loaded manifest for ' + pkg + ' (' + text(length(results)) + ' modules)')
}
if (results == null) results = []
package_dylibs[pkg] = results
// Preload all sibling dylibs with RTLD_LAZY|RTLD_GLOBAL
@@ -1910,4 +1919,4 @@ Shop.use_native = function(path, package_context) {
return os.native_module_load(handle, env)
}
return Shop
return Shop

103
mcode.cm
View File

@@ -4,7 +4,7 @@ var mcode = function(ast) {
// Translation tables
var binop_map = {
"+": "add", "-": "subtract", "*": "multiply", "/": "divide",
"%": "modulo", "&": "bitand", "|": "bitor", "^": "bitxor",
"%": "remainder", "&": "bitand", "|": "bitor", "^": "bitxor",
"<<": "shl", ">>": "shr", ">>>": "ushr",
"==": "eq", "===": "eq", "!=": "ne", "!==": "ne",
"<": "lt", "<=": "le", ">": "gt", ">=": "ge",
@@ -24,13 +24,13 @@ var mcode = function(ast) {
var binop_sym = {
add: "+", subtract: "-", multiply: "*", divide: "/",
modulo: "%", pow: "**",
remainder: "%", pow: "**",
lt: "<", le: "<=", gt: ">", ge: ">="
}
var compound_map = {
"+=": "add", "-=": "subtract", "*=": "multiply", "/=": "divide",
"%=": "modulo", "&=": "bitand", "|=": "bitor", "^=": "bitxor",
"%=": "remainder", "&=": "bitand", "|=": "bitor", "^=": "bitxor",
"<<=": "shl", ">>=": "shr", ">>>=": "ushr"
}
@@ -41,6 +41,28 @@ var mcode = function(ast) {
length: "length"
}
// Numeric intrinsic lowering maps (Tier 1 direct mcode).
var intrinsic_num_unary_ops = {
abs: "abs",
sign: "sign",
fraction: "fraction",
integer: "integer",
whole: "integer",
neg: "negate"
}
var intrinsic_num_binary_ops = {
modulo: "modulo",
remainder: "remainder",
max: "max",
min: "min"
}
var intrinsic_num_place_ops = {
floor: "floor",
ceiling: "ceiling",
round: "round",
trunc: "trunc"
}
// Compiler state
var s_instructions = null
var s_data = null
@@ -651,7 +673,8 @@ var mcode = function(ast) {
if (rel != null) {
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") {
op_str == "divide" || op_str == "modulo" || op_str == "remainder" ||
op_str == "pow") {
emit_numeric_binop(op_str)
} else {
// Passthrough for bitwise, in, etc.
@@ -877,6 +900,56 @@ var mcode = function(ast) {
}
}
// Intrinsic numeric helpers:
// preserve native intrinsic behavior for bad argument types by returning null.
var emit_intrinsic_num_unary = function(op, arg_slot) {
var dest = alloc_slot()
var t = alloc_slot()
var bad = gen_label(op + "_arg_bad")
var done = gen_label(op + "_arg_done")
emit_2("is_num", t, arg_slot)
emit_jump_cond("jump_false", t, bad)
emit_2(op, dest, arg_slot)
emit_jump(done)
emit_label(bad)
emit_1("null", dest)
emit_label(done)
return dest
}
var emit_intrinsic_num_binary = function(op, left_slot, right_slot) {
var dest = alloc_slot()
var t0 = alloc_slot()
var t1 = alloc_slot()
var bad = gen_label(op + "_arg_bad")
var done = gen_label(op + "_arg_done")
emit_2("is_num", t0, left_slot)
emit_jump_cond("jump_false", t0, bad)
emit_2("is_num", t1, right_slot)
emit_jump_cond("jump_false", t1, bad)
emit_3(op, dest, left_slot, right_slot)
emit_jump(done)
emit_label(bad)
emit_1("null", dest)
emit_label(done)
return dest
}
var emit_intrinsic_num_place = function(op, value_slot, place_slot) {
var dest = alloc_slot()
var t = alloc_slot()
var bad = gen_label(op + "_arg_bad")
var done = gen_label(op + "_arg_done")
emit_2("is_num", t, value_slot)
emit_jump_cond("jump_false", t, bad)
emit_3(op, dest, value_slot, place_slot)
emit_jump(done)
emit_label(bad)
emit_1("null", dest)
emit_label(done)
return dest
}
// Scan scope record for variable declarations
var scan_scope = function() {
var scope = find_scope_record(s_function_nr)
@@ -1796,6 +1869,28 @@ var mcode = function(ast) {
if (callee_kind == "name" && callee.intrinsic == true) {
fname = callee.name
nargs = args_list != null ? length(args_list) : 0
mop = intrinsic_num_unary_ops[fname]
if (mop != null && nargs == 1) {
a0 = gen_expr(args_list[0], -1)
return emit_intrinsic_num_unary(mop, a0)
}
mop = intrinsic_num_binary_ops[fname]
if (mop != null && nargs == 2) {
a0 = gen_expr(args_list[0], -1)
a1 = gen_expr(args_list[1], -1)
return emit_intrinsic_num_binary(mop, a0, a1)
}
mop = intrinsic_num_place_ops[fname]
if (mop != null && (nargs == 1 || nargs == 2)) {
a0 = gen_expr(args_list[0], -1)
if (nargs == 2) {
a1 = gen_expr(args_list[1], -1)
} else {
a1 = alloc_slot()
emit_1("null", a1)
}
return emit_intrinsic_num_place(mop, a0, a1)
}
// 1-arg type check intrinsics → direct opcode
if (nargs == 1 && sensory_ops[fname] != null) {
a0 = gen_expr(args_list[0], -1)

View File

@@ -38,8 +38,7 @@ if host_machine.system() == 'darwin'
foreach fkit : fworks
deps += dependency('appleframeworks', modules: fkit)
endforeach
# 32MB stack for deep native recursion (CPS patterns without TCO)
link += ['-Wl,-stack_size,0x2000000']
# Native code uses dispatch loop (no C stack recursion)
endif
if host_machine.system() == 'playdate'

6
qbe.cm
View File

@@ -11,7 +11,7 @@ def js_null = 7
def js_false = 3
def js_true = 35
def js_exception = 15
def js_empty_text = 27
def js_empty_text = 11
// Shared closure vars for functions with >4 params
var _qop = null
@@ -67,13 +67,13 @@ var is_ptr = function(p, v) {
var is_imm_text = function(p, v) {
return ` %${p}.t =l and ${v}, 31
%${p} =w ceql %${p}.t, 27
%${p} =w ceql %${p}.t, 11
`
}
var is_text = function(p, v) {
return ` %${p}.imm =l and ${v}, 31
%${p}.is_imm =w ceql %${p}.imm, 27
%${p}.is_imm =w ceql %${p}.imm, 11
jnz %${p}.is_imm, @${p}.yes, @${p}.chk_ptr
@${p}.chk_ptr
%${p}.ptag =l and ${v}, 7

File diff suppressed because it is too large Load Diff

View File

@@ -37,6 +37,7 @@ static char *compute_blake2_hex(const char *data, size_t size) {
uint8_t hash[32];
crypto_blake2b(hash, 32, (const uint8_t *)data, size);
char *hex = malloc(65);
if (!hex) return NULL;
for (int i = 0; i < 32; i++)
snprintf(hex + i * 2, 3, "%02x", hash[i]);
return hex;
@@ -64,6 +65,7 @@ static int write_cache_file(const char *path, const uint8_t *data, size_t size)
// Returns heap-allocated binary data and sets *out_size, or NULL on failure
static char *load_or_cache_bootstrap(const char *mcode_data, size_t mcode_size, size_t *out_size) {
char *hex = compute_blake2_hex(mcode_data, mcode_size);
if (!hex) return NULL;
char *cpath = build_cache_path(hex);
free(hex);
@@ -222,6 +224,7 @@ static char *try_engine_cache(size_t *out_size) {
char *hex = compute_blake2_hex(src, src_size);
free(src);
if (!hex) return NULL;
char *cpath = build_cache_path(hex);
if (!cpath) { free(hex); return NULL; }
free(hex);

View File

@@ -460,6 +460,33 @@ JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count) {
return frame;
}
static JSValue js_new_register_code(JSContext *ctx, JSCodeRegister *code) {
JSCode *jc;
if (!code) return JS_EXCEPTION;
jc = ct_alloc(ctx, sizeof(JSCode), 8);
if (!jc) return JS_EXCEPTION;
memset(jc, 0, sizeof(JSCode));
jc->header = objhdr_make(0, OBJ_CODE, 0, 0, 0, 0);
jc->kind = JS_CODE_KIND_REGISTER;
jc->arity = (int16_t)code->arity;
jc->u.reg.code = code;
return JS_MKPTR(jc);
}
static JSValue js_new_native_code(JSContext *ctx, void *fn_ptr, void *dl_handle,
uint16_t nr_slots, int arity) {
JSCode *jc = ct_alloc(ctx, sizeof(JSCode), 8);
if (!jc) return JS_EXCEPTION;
memset(jc, 0, sizeof(JSCode));
jc->header = objhdr_make(0, OBJ_CODE, 0, 0, 0, 0);
jc->kind = JS_CODE_KIND_NATIVE;
jc->arity = (int16_t)arity;
jc->u.native.fn_ptr = fn_ptr;
jc->u.native.dl_handle = dl_handle;
jc->u.native.nr_slots = nr_slots;
return JS_MKPTR(jc);
}
/* Create a register-based function from JSCodeRegister */
JSValue js_new_register_function(JSContext *ctx, JSCodeRegister *code, JSValue env, JSValue outer_frame) {
/* Protect env and outer_frame from GC — js_mallocz can trigger
@@ -470,24 +497,84 @@ JSValue js_new_register_function(JSContext *ctx, JSCodeRegister *code, JSValue e
JS_PushGCRef(ctx, &frame_ref);
frame_ref.val = outer_frame;
JSFunction *fn = js_mallocz(ctx, sizeof(JSFunction));
JSGCRef fn_ref;
JSFunction *fn;
JSValue code_obj;
JS_AddGCRef(ctx, &fn_ref);
fn_ref.val = JS_NULL;
fn = js_mallocz(ctx, sizeof(JSFunction));
if (!fn) {
JS_DeleteGCRef(ctx, &fn_ref);
JS_PopGCRef(ctx, &frame_ref);
JS_PopGCRef(ctx, &env_ref);
return JS_EXCEPTION;
}
fn_ref.val = JS_MKPTR(fn);
fn->header = objhdr_make(0, OBJ_FUNCTION, 0, 0, 0, 0);
fn->kind = JS_FUNC_KIND_REGISTER;
fn->length = code->arity;
fn->name = code->name;
fn->u.reg.code = code;
fn->u.reg.env_record = env_ref.val;
fn->u.reg.outer_frame = frame_ref.val;
code_obj = js_new_register_code(ctx, code);
if (JS_IsException(code_obj)) {
JS_DeleteGCRef(ctx, &fn_ref);
JS_PopGCRef(ctx, &frame_ref);
JS_PopGCRef(ctx, &env_ref);
return JS_EXCEPTION;
}
fn = JS_VALUE_GET_FUNCTION(fn_ref.val);
fn->u.cell.code = code_obj;
fn->u.cell.env_record = env_ref.val;
fn->u.cell.outer_frame = frame_ref.val;
JSValue out = fn_ref.val;
JS_DeleteGCRef(ctx, &fn_ref);
JS_PopGCRef(ctx, &frame_ref);
JS_PopGCRef(ctx, &env_ref);
return JS_MKPTR(fn);
return out;
}
JSValue js_new_native_function_with_code(JSContext *ctx, JSValue code_obj, int arity, JSValue outer_frame) {
JSGCRef frame_ref;
JSGCRef fn_ref;
JSFunction *fn;
JS_PushGCRef(ctx, &frame_ref);
frame_ref.val = outer_frame;
JS_AddGCRef(ctx, &fn_ref);
fn_ref.val = JS_NULL;
fn = js_mallocz(ctx, sizeof(JSFunction));
if (!fn) {
JS_DeleteGCRef(ctx, &fn_ref);
JS_PopGCRef(ctx, &frame_ref);
return JS_EXCEPTION;
}
fn_ref.val = JS_MKPTR(fn);
fn->header = objhdr_make(0, OBJ_FUNCTION, 0, 0, 0, 0);
fn->kind = JS_FUNC_KIND_NATIVE;
fn->length = arity;
fn->name = JS_NULL;
fn = JS_VALUE_GET_FUNCTION(fn_ref.val);
fn->u.cell.code = code_obj;
fn->u.cell.env_record = JS_NULL;
fn->u.cell.outer_frame = frame_ref.val;
JSValue out = fn_ref.val;
JS_DeleteGCRef(ctx, &fn_ref);
JS_PopGCRef(ctx, &frame_ref);
return out;
}
/* Create a native (QBE-compiled) function */
JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle,
uint16_t nr_slots, int arity, JSValue outer_frame) {
JSValue code_obj = js_new_native_code(ctx, fn_ptr, dl_handle, nr_slots, arity);
if (JS_IsException(code_obj))
return JS_EXCEPTION;
return js_new_native_function_with_code(ctx, code_obj, arity, outer_frame);
}
/* Binary operations helper */
@@ -521,7 +608,7 @@ static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
return JS_NewFloat64(ctx, (double)ia / (double)ib);
case MACH_MOD:
if (ib == 0) return JS_NULL;
return JS_NewInt32(ctx, ia % ib);
return JS_NewFloat64(ctx, (double)ia - ((double)ib * floor((double)ia / (double)ib)));
case MACH_EQ:
return JS_NewBool(ctx, ia == ib);
case MACH_NEQ:
@@ -647,8 +734,9 @@ static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
}
case MACH_MOD: {
if (db == 0.0) return JS_NULL;
double r = fmod(da, db);
if (!isfinite(r)) return JS_NULL;
if (isnan(da) || isnan(db)) return JS_NULL;
if (da == 0.0) return JS_NewFloat64(ctx, 0.0);
double r = da - (db * floor(da / db));
return JS_NewFloat64(ctx, r);
}
case MACH_POW: {
@@ -678,6 +766,34 @@ static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
return JS_RaiseDisrupt(ctx, "type mismatch in binary operation");
}
static inline int mach_get_number(JSValue v, double *out) {
uint32_t tag = JS_VALUE_GET_TAG(v);
if (tag == JS_TAG_INT) {
*out = (double)JS_VALUE_GET_INT(v);
return 0;
}
if (JS_TAG_IS_FLOAT64(tag)) {
*out = JS_VALUE_GET_FLOAT64(v);
return 0;
}
return -1;
}
static inline int mach_get_place(JSContext *ctx, JSValue v, int32_t *out) {
uint32_t tag = JS_VALUE_GET_NORM_TAG(v);
if (tag == JS_TAG_INT || tag == JS_TAG_BOOL || tag == JS_TAG_NULL || tag == JS_TAG_FLOAT64) {
return JS_ToInt32(ctx, out, v);
}
return -1;
}
static inline double mach_apply_place(double d, int32_t place, double (*f)(double)) {
if (place == 0)
return f(d);
double mult = pow(10.0, -(double)place);
return f(d * mult) / mult;
}
#ifdef HAVE_ASAN
void __asan_on_error(void) {
@@ -695,8 +811,8 @@ void __asan_on_error(void) {
const char *file = NULL;
uint16_t line = 0;
uint32_t pc = is_first ? cur_pc : 0;
if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) {
JSCodeRegister *code = fn->u.reg.code;
if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) {
JSCodeRegister *code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
file = code->filename_cstr;
func_name = code->name_cstr;
if (!is_first)
@@ -732,8 +848,8 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
ctx->suspended_frame_ref.val = JS_NULL;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
code = fn->u.reg.code;
env = fn->u.reg.env_record;
code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
env = fn->u.cell.env_record;
pc = ctx->suspended_pc;
result = JS_NULL;
#ifdef HAVE_ASAN
@@ -830,6 +946,12 @@ vm_dispatch:
DT(MACH_MUL), DT(MACH_DIV),
DT(MACH_MOD), DT(MACH_POW),
DT(MACH_NEG),
DT(MACH_REMAINDER), DT(MACH_MAX),
DT(MACH_MIN), DT(MACH_ABS),
DT(MACH_SIGN), DT(MACH_FRACTION),
DT(MACH_INTEGER), DT(MACH_FLOOR),
DT(MACH_CEILING), DT(MACH_ROUND),
DT(MACH_TRUNC),
DT(MACH_EQ), DT(MACH_NEQ),
DT(MACH_LT), DT(MACH_LE),
DT(MACH_GT), DT(MACH_GE),
@@ -1017,17 +1139,15 @@ vm_dispatch:
}
VM_CASE(MACH_MOD): {
JSValue left = frame->slots[b], right = frame->slots[c];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int32_t ib = JS_VALUE_GET_INT(right);
frame->slots[a] = (ib != 0) ? JS_NewInt32(ctx, JS_VALUE_GET_INT(left) % ib) : JS_NULL;
double da, db;
if (mach_get_number(left, &da) != 0 || mach_get_number(right, &db) != 0 || db == 0.0 ||
isnan(da) || isnan(db)) {
frame->slots[a] = JS_NULL;
} else {
double da, db, r;
JS_ToFloat64(ctx, &da, left);
JS_ToFloat64(ctx, &db, right);
if (db == 0.0) { frame->slots[a] = JS_NULL; }
else {
r = fmod(da, db);
frame->slots[a] = !isfinite(r) ? JS_NULL : JS_NewFloat64(ctx, r);
if (da == 0.0) {
frame->slots[a] = JS_NewFloat64(ctx, 0.0);
} else {
frame->slots[a] = JS_NewFloat64(ctx, da - (db * floor(da / db)));
}
}
VM_BREAK();
@@ -1051,6 +1171,116 @@ vm_dispatch:
VM_BREAK();
}
VM_CASE(MACH_REMAINDER): {
JSValue left = frame->slots[b], right = frame->slots[c];
double da, db;
if (mach_get_number(left, &da) != 0 || mach_get_number(right, &db) != 0 || db == 0.0) {
frame->slots[a] = JS_NULL;
} else {
frame->slots[a] = JS_NewFloat64(ctx, da - (trunc(da / db) * db));
}
VM_BREAK();
}
VM_CASE(MACH_MAX): {
JSValue left = frame->slots[b], right = frame->slots[c];
double da, db;
if (mach_get_number(left, &da) != 0 || mach_get_number(right, &db) != 0) {
frame->slots[a] = JS_NULL;
} else {
frame->slots[a] = JS_NewFloat64(ctx, da > db ? da : db);
}
VM_BREAK();
}
VM_CASE(MACH_MIN): {
JSValue left = frame->slots[b], right = frame->slots[c];
double da, db;
if (mach_get_number(left, &da) != 0 || mach_get_number(right, &db) != 0) {
frame->slots[a] = JS_NULL;
} else {
frame->slots[a] = JS_NewFloat64(ctx, da < db ? da : db);
}
VM_BREAK();
}
VM_CASE(MACH_ABS): {
JSValue v = frame->slots[b];
double d;
if (mach_get_number(v, &d) != 0) {
frame->slots[a] = JS_NULL;
} else {
frame->slots[a] = JS_NewFloat64(ctx, fabs(d));
}
VM_BREAK();
}
VM_CASE(MACH_SIGN): {
JSValue v = frame->slots[b];
double d;
if (mach_get_number(v, &d) != 0) {
frame->slots[a] = JS_NULL;
} else if (d < 0) {
frame->slots[a] = JS_NewInt32(ctx, -1);
} else if (d > 0) {
frame->slots[a] = JS_NewInt32(ctx, 1);
} else {
frame->slots[a] = JS_NewInt32(ctx, 0);
}
VM_BREAK();
}
VM_CASE(MACH_FRACTION): {
JSValue v = frame->slots[b];
double d;
if (mach_get_number(v, &d) != 0) {
frame->slots[a] = JS_NULL;
} else {
frame->slots[a] = JS_NewFloat64(ctx, d - trunc(d));
}
VM_BREAK();
}
VM_CASE(MACH_INTEGER): {
JSValue v = frame->slots[b];
double d;
if (mach_get_number(v, &d) != 0) {
frame->slots[a] = JS_NULL;
} else {
frame->slots[a] = JS_NewFloat64(ctx, trunc(d));
}
VM_BREAK();
}
VM_CASE(MACH_FLOOR):
VM_CASE(MACH_CEILING):
VM_CASE(MACH_ROUND):
VM_CASE(MACH_TRUNC): {
JSValue v = frame->slots[b];
JSValue pval = frame->slots[c];
double d, r;
int32_t place = 0;
if (mach_get_number(v, &d) != 0) {
frame->slots[a] = JS_NULL;
VM_BREAK();
}
if (!JS_IsNull(pval) && mach_get_place(ctx, pval, &place) != 0) {
frame->slots[a] = JS_NULL;
VM_BREAK();
}
if (op == MACH_FLOOR) {
r = mach_apply_place(d, place, floor);
} else if (op == MACH_CEILING) {
r = mach_apply_place(d, place, ceil);
} else if (op == MACH_ROUND) {
r = mach_apply_place(d, place, round);
} else {
r = mach_apply_place(d, place, trunc);
}
frame->slots[a] = JS_NewFloat64(ctx, r);
VM_BREAK();
}
/* Comparison — inline integer fast paths */
VM_CASE(MACH_EQ): {
JSValue left = frame->slots[b], right = frame->slots[c];
@@ -1330,7 +1560,7 @@ vm_dispatch:
/* Read env fresh from frame->function — C local env can go stale after GC */
int bx = MACH_GET_Bx(instr);
JSValue key = code->cpool[bx];
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.reg.env_record;
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.cell.env_record;
JSValue val = JS_GetProperty(ctx, cur_env, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[a] = val;
@@ -1342,7 +1572,7 @@ vm_dispatch:
int bx = MACH_GET_Bx(instr);
JSValue key = code->cpool[bx];
JSValue val = JS_NULL;
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.reg.env_record;
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.cell.env_record;
if (!JS_IsNull(cur_env)) {
val = JS_GetProperty(ctx, cur_env, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
@@ -1359,7 +1589,7 @@ vm_dispatch:
/* R(A) = outer_frame[B].slots[C] — walk lexical scope chain */
int depth = b;
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame);
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
if (!target) {
fprintf(stderr, "GETUP: NULL outer_frame at depth 0! pc=%d a=%d depth=%d slot=%d nr_slots=%d instr=0x%08x\n",
pc-1, a, depth, c, code->nr_slots, instr);
@@ -1368,7 +1598,7 @@ vm_dispatch:
}
for (int d = 1; d < depth; d++) {
fn = JS_VALUE_GET_FUNCTION(target->function);
JSFrameRegister *next = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame);
JSFrameRegister *next = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
if (!next) {
fprintf(stderr, "GETUP: NULL outer_frame at depth %d! pc=%d a=%d depth=%d slot=%d nr_slots=%d instr=0x%08x\n",
d, pc-1, a, depth, c, code->nr_slots, instr);
@@ -1385,10 +1615,10 @@ vm_dispatch:
/* outer_frame[B].slots[C] = R(A) — walk lexical scope chain */
int depth = b;
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame);
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
for (int d = 1; d < depth; d++) {
fn = JS_VALUE_GET_FUNCTION(target->function);
target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame);
target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
}
target->slots[c] = frame->slots[a];
VM_BREAK();
@@ -1482,9 +1712,9 @@ vm_dispatch:
const char *callee_file = "?";
{
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(frame->function);
if (callee_fn->kind == JS_FUNC_KIND_REGISTER && callee_fn->u.reg.code) {
if (callee_fn->u.reg.code->name_cstr) callee_name = callee_fn->u.reg.code->name_cstr;
if (callee_fn->u.reg.code->filename_cstr) callee_file = callee_fn->u.reg.code->filename_cstr;
if (callee_fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.reg.code) {
if (JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.reg.code->name_cstr) callee_name = JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.reg.code->name_cstr;
if (JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.reg.code->filename_cstr) callee_file = JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.reg.code->filename_cstr;
}
}
#endif
@@ -1494,8 +1724,8 @@ vm_dispatch:
frame_ref.val = JS_MKPTR(frame);
int ret_info = JS_VALUE_GET_INT(frame->address);
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
code = fn->u.reg.code;
env = fn->u.reg.env_record;
code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
env = fn->u.cell.env_record;
pc = ret_info >> 16;
int ret_slot = ret_info & 0xFFFF;
if (ret_slot != 0xFFFF) {
@@ -1527,8 +1757,8 @@ vm_dispatch:
frame_ref.val = JS_MKPTR(frame);
int ret_info = JS_VALUE_GET_INT(frame->address);
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
code = fn->u.reg.code;
env = fn->u.reg.env_record;
code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
env = fn->u.cell.env_record;
pc = ret_info >> 16;
int ret_slot = ret_info & 0xFFFF;
if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result;
@@ -1556,7 +1786,7 @@ vm_dispatch:
if ((uint32_t)bx < code->func_count) {
JSCodeRegister *fn_code = code->functions[bx];
/* Read env fresh from frame->function — C local can be stale */
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.reg.env_record;
JSValue cur_env = JS_VALUE_GET_FUNCTION(frame->function)->u.cell.env_record;
JSValue fn_val = js_new_register_function(ctx, fn_code, cur_env, frame_ref.val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[a] = fn_val;
@@ -1943,7 +2173,7 @@ vm_dispatch:
if (fn->kind == JS_FUNC_KIND_REGISTER) {
/* Register function: switch frames inline (fast path) */
JSCodeRegister *fn_code = fn->u.reg.code;
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots);
if (!new_frame) {
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
@@ -1953,7 +2183,7 @@ vm_dispatch:
fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
fn_val = fr->function;
fn = JS_VALUE_GET_FUNCTION(fn_val);
fn_code = fn->u.reg.code;
fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
new_frame->function = fn_val;
/* Copy this + args from call frame to new frame */
int copy_count = (c_argc < fn_code->arity) ? c_argc : fn_code->arity;
@@ -1966,16 +2196,18 @@ vm_dispatch:
frame = new_frame;
frame_ref.val = JS_MKPTR(frame);
code = fn_code;
env = fn->u.reg.env_record;
env = fn->u.cell.env_record;
pc = code->entry_point;
} else {
/* C or bytecode function: args already in fr->slots (GC-protected via frame chain) */
/* C, native, or bytecode function */
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
ctx->vm_call_depth++;
JSValue ret;
if (fn->kind == JS_FUNC_KIND_C)
ret = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
else if (fn->kind == JS_FUNC_KIND_NATIVE)
ret = cell_native_dispatch(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
else
ret = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1], 0);
ctx->vm_call_depth--;
@@ -2011,7 +2243,7 @@ vm_dispatch:
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
if (fn->kind == JS_FUNC_KIND_REGISTER) {
JSCodeRegister *fn_code = fn->u.reg.code;
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
int current_slots = (int)objhdr_cap56(frame->header);
if (fn_code->nr_slots <= current_slots) {
@@ -2026,7 +2258,7 @@ vm_dispatch:
frame->function = fn_val;
/* caller stays the same — we're reusing this frame */
code = fn_code;
env = fn->u.reg.env_record;
env = fn->u.cell.env_record;
pc = code->entry_point;
} else {
/* SLOW PATH: callee needs more slots, must allocate */
@@ -2039,7 +2271,7 @@ vm_dispatch:
fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->slots[a]);
fn_val = fr->function;
fn = JS_VALUE_GET_FUNCTION(fn_val);
fn_code = fn->u.reg.code;
fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
new_frame->function = fn_val;
int copy_count = (c_argc < fn_code->arity) ? c_argc : fn_code->arity;
new_frame->slots[0] = fr->slots[0]; /* this */
@@ -2050,17 +2282,19 @@ vm_dispatch:
frame = new_frame;
frame_ref.val = JS_MKPTR(frame);
code = fn_code;
env = fn->u.reg.env_record;
env = fn->u.cell.env_record;
pc = code->entry_point;
}
} else {
/* C/bytecode function: call it, then return result to our caller */
/* C, native, or bytecode function: call it, then return result to our caller */
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
ctx->vm_call_depth++;
JSValue ret;
if (fn->kind == JS_FUNC_KIND_C)
ret = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
else if (fn->kind == JS_FUNC_KIND_NATIVE)
ret = cell_native_dispatch(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
else
ret = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1], 0);
ctx->vm_call_depth--;
@@ -2076,8 +2310,8 @@ vm_dispatch:
frame_ref.val = JS_MKPTR(frame);
int ret_info = JS_VALUE_GET_INT(frame->address);
JSFunction *ret_fn = JS_VALUE_GET_FUNCTION(frame->function);
code = ret_fn->u.reg.code;
env = ret_fn->u.reg.env_record;
code = JS_VALUE_GET_CODE(ret_fn->u.cell.code)->u.reg.code;
env = ret_fn->u.cell.env_record;
pc = ret_info >> 16;
int ret_slot = ret_info & 0xFFFF;
if (ret_slot != 0xFFFF) frame->slots[ret_slot] = ret;
@@ -2129,10 +2363,10 @@ vm_dispatch:
uint32_t frame_pc = pc;
for (;;) {
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
code = fn->u.reg.code;
code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
/* Only enter handler if we're not already inside it */
if (code->disruption_pc > 0 && frame_pc < code->disruption_pc) {
env = fn->u.reg.env_record;
env = fn->u.cell.env_record;
pc = code->disruption_pc;
ctx->disruption_reported = FALSE;
frame_ref.val = JS_MKPTR(frame); /* root handler frame for GC */
@@ -2373,6 +2607,17 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
else if (strcmp(op, "modulo") == 0) { ABC3(MACH_MOD); }
else if (strcmp(op, "pow") == 0) { ABC3(MACH_POW); }
else if (strcmp(op, "negate") == 0) { AB2(MACH_NEG); }
else if (strcmp(op, "remainder") == 0) { ABC3(MACH_REMAINDER); }
else if (strcmp(op, "max") == 0) { ABC3(MACH_MAX); }
else if (strcmp(op, "min") == 0) { ABC3(MACH_MIN); }
else if (strcmp(op, "abs") == 0) { AB2(MACH_ABS); }
else if (strcmp(op, "sign") == 0) { AB2(MACH_SIGN); }
else if (strcmp(op, "fraction") == 0) { AB2(MACH_FRACTION); }
else if (strcmp(op, "integer") == 0) { AB2(MACH_INTEGER); }
else if (strcmp(op, "floor") == 0) { ABC3(MACH_FLOOR); }
else if (strcmp(op, "ceiling") == 0) { ABC3(MACH_CEILING); }
else if (strcmp(op, "round") == 0) { ABC3(MACH_ROUND); }
else if (strcmp(op, "trunc") == 0) { ABC3(MACH_TRUNC); }
/* Typed integer comparisons */
else if (strcmp(op, "eq_int") == 0) { ABC3(MACH_EQ_INT); }
else if (strcmp(op, "ne_int") == 0) { ABC3(MACH_NE_INT); }
@@ -3077,4 +3322,3 @@ void JS_DumpMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue en
dump_register_code(ctx, code, 0);
JS_PopGCRef(ctx, &env_ref);
}

File diff suppressed because it is too large Load Diff

View File

@@ -120,8 +120,8 @@ typedef struct JSBlob JSBlob;
typedef struct JSText JSText;
typedef struct JSRecord JSRecord;
typedef struct JSFunction JSFunction;
typedef struct JSFrame JSFrame;
typedef struct JSCode JSCode;
typedef struct JSFrame JSFrame;
#define OBJHDR_CAP_SHIFT 8u
#define OBJHDR_CAP_MASK (((objhdr_t)1ull << 56) - 1ull)
@@ -278,7 +278,6 @@ typedef void (*JSLogCallback)(JSContext *ctx, const char *channel, const char *m
/* Forward declaration for bytecode freeing */
#define JS_VALUE_GET_BLOB(v) ((JSBlob *)JS_VALUE_GET_PTR (v))
#define JS_VALUE_GET_CODE(v) (JS_VALUE_GET_PTR (v))
#ifdef HEAP_CHECK
void heap_check_fail(void *ptr, struct JSContext *ctx);
@@ -286,6 +285,7 @@ void heap_check_fail(void *ptr, struct JSContext *ctx);
#define JS_VALUE_GET_OBJ(v) ((JSRecord *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_TEXT(v) ((JSText *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_FUNCTION(v) ((JSFunction *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_CODE(v) ((JSCode *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_FRAME(v) ((JSFrame *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_STRING(v) ((JSText *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_RECORD(v) ((JSRecord *)heap_check_chase(ctx, v))
@@ -294,6 +294,7 @@ void heap_check_fail(void *ptr, struct JSContext *ctx);
#define JS_VALUE_GET_OBJ(v) ((JSRecord *)chase (v))
#define JS_VALUE_GET_TEXT(v) ((JSText *)chase (v))
#define JS_VALUE_GET_FUNCTION(v) ((JSFunction *)chase (v))
#define JS_VALUE_GET_CODE(v) ((JSCode *)chase (v))
#define JS_VALUE_GET_FRAME(v) ((JSFrame *)chase (v))
#define JS_VALUE_GET_STRING(v) ((JSText *)chase (v))
#define JS_VALUE_GET_RECORD(v) ((JSRecord *)chase (v))
@@ -485,6 +486,17 @@ 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_REMAINDER, /* R(A) = remainder(R(B), R(C)) */
MACH_MAX, /* R(A) = max(R(B), R(C)) */
MACH_MIN, /* R(A) = min(R(B), R(C)) */
MACH_ABS, /* R(A) = abs(R(B)) */
MACH_SIGN, /* R(A) = sign(R(B)) */
MACH_FRACTION, /* R(A) = fraction(R(B)) */
MACH_INTEGER, /* R(A) = integer(R(B)) */
MACH_FLOOR, /* R(A) = floor(R(B), R(C)) */
MACH_CEILING, /* R(A) = ceiling(R(B), R(C)) */
MACH_ROUND, /* R(A) = round(R(B), R(C)) */
MACH_TRUNC, /* R(A) = trunc(R(B), R(C)) */
MACH__DEAD_INC, /* reserved — was MACH_INC, never emitted */
MACH__DEAD_DEC, /* reserved — was MACH_DEC, never emitted */
@@ -658,6 +670,17 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
[MACH_MOD] = "mod",
[MACH_POW] = "pow",
[MACH_NEG] = "neg",
[MACH_REMAINDER] = "remainder",
[MACH_MAX] = "max",
[MACH_MIN] = "min",
[MACH_ABS] = "abs",
[MACH_SIGN] = "sign",
[MACH_FRACTION] = "fraction",
[MACH_INTEGER] = "integer",
[MACH_FLOOR] = "floor",
[MACH_CEILING] = "ceiling",
[MACH_ROUND] = "round",
[MACH_TRUNC] = "trunc",
[MACH__DEAD_INC] = "dead_inc",
[MACH__DEAD_DEC] = "dead_dec",
[MACH_EQ] = "eq",
@@ -1098,6 +1121,7 @@ struct JSContext {
JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */
JSLocalRef *top_local_ref; /* for JS_LOCAL macro - GC updates C locals through pointers */
CCallRoot *c_call_root; /* stack of auto-rooted C call argv arrays */
void *native_state; /* qbe_helpers.c per-actor native runtime state */
int class_count; /* size of class_array and class_proto */
JSClass *class_array;
@@ -1316,8 +1340,30 @@ typedef enum {
JS_FUNC_KIND_BYTECODE,
JS_FUNC_KIND_C_DATA,
JS_FUNC_KIND_REGISTER, /* register-based VM function */
JS_FUNC_KIND_NATIVE, /* QBE-compiled native function */
} JSFunctionKind;
typedef enum {
JS_CODE_KIND_REGISTER = 1,
JS_CODE_KIND_NATIVE = 2,
} JSCodeKind;
typedef struct JSCode {
objhdr_t header; /* OBJ_CODE */
uint8_t kind;
int16_t arity;
union {
struct {
JSCodeRegister *code;
} reg;
struct {
void *fn_ptr; /* compiled cell_fn_N pointer */
void *dl_handle; /* dylib handle for dlsym lookups */
uint16_t nr_slots; /* frame size for this function */
} native;
} u;
} JSCode;
typedef struct JSFunction {
objhdr_t header; /* must come first */
JSValue name; /* function name as JSValue text */
@@ -1330,10 +1376,10 @@ typedef struct JSFunction {
int16_t magic;
} cfunc;
struct {
JSCodeRegister *code; /* compiled register code (off-heap) */
JSValue code; /* JSCode object (OBJ_CODE) */
JSValue env_record; /* stone record, module environment */
JSValue outer_frame; /* JSFrame JSValue, for closures */
} reg;
} cell;
} u;
} JSFunction;
@@ -1356,6 +1402,7 @@ typedef struct JSFunction {
JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv);
JSValue JS_CallInternal (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags);
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame);
JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv);
int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop);
JSValue __attribute__ ((format (printf, 2, 3)))
JS_RaiseDisrupt (JSContext *ctx, const char *fmt, ...);
@@ -1394,8 +1441,6 @@ static JSValue js_cell_splat (JSContext *ctx, JSValue this_val, int argc, JSValu
static JSValue js_cell_meme (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_call (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_modulo (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_neg (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_not (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
JSValue js_cell_text_lower (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
JSValue js_cell_text_upper (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
@@ -1406,17 +1451,6 @@ static JSValue js_cell_text_search (JSContext *ctx, JSValue this_val, int argc,
static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
JSValue js_cell_character (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_abs (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_sign (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_floor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_ceiling (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_round (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_trunc (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_whole (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_fraction (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_min (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_max (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_remainder (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_print (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
@@ -1529,6 +1563,8 @@ static inline void set_value (JSContext *ctx, JSValue *pval, JSValue new_val) {
*pval = new_val;
}
int cell_rt_native_active(JSContext *ctx);
static inline __exception int js_poll_interrupts (JSContext *ctx) {
if (unlikely (atomic_load_explicit (&ctx->pause_flag, memory_order_relaxed) >= 2)) {
JS_RaiseDisrupt (ctx, "interrupted");
@@ -1623,7 +1659,10 @@ JSValue js_key_from_string (JSContext *ctx, JSValue val);
/* mach.c exports */
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame);
JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle, uint16_t nr_slots, int arity, JSValue outer_frame);
JSValue js_new_native_function_with_code(JSContext *ctx, JSValue code_obj, int arity, JSValue outer_frame);
JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count);
void cell_rt_free_native_state(JSContext *ctx);
#endif /* QUICKJS_INTERNAL_H */

View File

@@ -53,8 +53,8 @@ void heap_check_fail(void *ptr, JSContext *ctx) {
JSFunction *fn = (JSFunction *)JS_VALUE_GET_PTR(frame->function);
const char *name = NULL, *file = NULL;
uint16_t line = 0;
if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) {
JSCodeRegister *code = fn->u.reg.code;
if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) {
JSCodeRegister *code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
file = code->filename_cstr;
name = code->name_cstr;
if (!first)
@@ -1394,12 +1394,14 @@ void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *fro
JSFunction *fn = (JSFunction *)ptr;
/* Scan the function name */
fn->name = gc_copy_value (ctx, fn->name, from_base, from_end, to_base, to_free, to_end);
if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) {
if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) {
/* Scan code tree to arbitrary nesting depth */
gc_scan_code_tree (ctx, fn->u.reg.code, from_base, from_end, to_base, to_free, to_end);
gc_scan_code_tree (ctx, JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code, from_base, from_end, to_base, to_free, to_end);
/* Scan outer_frame and env_record */
fn->u.reg.outer_frame = gc_copy_value (ctx, fn->u.reg.outer_frame, from_base, from_end, to_base, to_free, to_end);
fn->u.reg.env_record = gc_copy_value (ctx, fn->u.reg.env_record, from_base, from_end, to_base, to_free, to_end);
fn->u.cell.outer_frame = gc_copy_value (ctx, fn->u.cell.outer_frame, from_base, from_end, to_base, to_free, to_end);
fn->u.cell.env_record = gc_copy_value (ctx, fn->u.cell.env_record, from_base, from_end, to_base, to_free, to_end);
} else if (fn->kind == JS_FUNC_KIND_NATIVE) {
fn->u.cell.outer_frame = gc_copy_value (ctx, fn->u.cell.outer_frame, from_base, from_end, to_base, to_free, to_end);
}
break;
}
@@ -1432,10 +1434,10 @@ void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *fro
objhdr_t fh = *(objhdr_t *)JS_VALUE_GET_PTR (frame->function);
if (objhdr_type (fh) == OBJ_FUNCTION) {
JSFunction *fn = (JSFunction *)JS_VALUE_GET_PTR (frame->function);
if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) {
if (fn->u.reg.code->name_cstr) fname = fn->u.reg.code->name_cstr;
if (fn->u.reg.code->filename_cstr) ffile = fn->u.reg.code->filename_cstr;
fnslots = fn->u.reg.code->nr_slots;
if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) {
if (JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code->name_cstr) fname = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code->name_cstr;
if (JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code->filename_cstr) ffile = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code->filename_cstr;
fnslots = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code->nr_slots;
}
}
}
@@ -1541,8 +1543,8 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
}
if (objhdr_type (fnh) == OBJ_FUNCTION) {
JSFunction *fnp = (JSFunction *)JS_VALUE_GET_PTR (fn_v);
if (fnp->kind == JS_FUNC_KIND_REGISTER && fnp->u.reg.code && fnp->u.reg.code->name_cstr)
fn_name = fnp->u.reg.code->name_cstr;
if (fnp->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(fnp->u.cell.code)->u.reg.code && JS_VALUE_GET_CODE(fnp->u.cell.code)->u.reg.code->name_cstr)
fn_name = JS_VALUE_GET_CODE(fnp->u.cell.code)->u.reg.code->name_cstr;
}
}
fprintf (stderr, "VALIDATE_GC: pre-gc frame %p slot[%llu] -> %p (chased %p) bad type %d (hdr=0x%llx) fn=%s\n",
@@ -2100,6 +2102,7 @@ void JS_FreeContext (JSContext *ctx) {
JSRuntime *rt = ctx->rt;
int i;
cell_rt_free_native_state(ctx);
JS_DeleteGCRef(ctx, &ctx->suspended_frame_ref);
for (i = 0; i < ctx->class_count; i++) {
@@ -4738,8 +4741,10 @@ JSValue JS_CallInternal (JSContext *ctx, JSValue func_obj, JSValue this_obj,
case JS_FUNC_KIND_C_DATA:
return js_call_c_function (ctx, func_obj, this_obj, argc, argv);
case JS_FUNC_KIND_REGISTER:
return JS_CallRegisterVM (ctx, f->u.reg.code, this_obj, argc, argv,
f->u.reg.env_record, f->u.reg.outer_frame);
return JS_CallRegisterVM (ctx, JS_VALUE_GET_CODE(f->u.cell.code)->u.reg.code, this_obj, argc, argv,
f->u.cell.env_record, f->u.cell.outer_frame);
case JS_FUNC_KIND_NATIVE:
return cell_native_dispatch (ctx, func_obj, this_obj, argc, argv);
default:
return JS_RaiseDisrupt (ctx, "not a function");
}
@@ -4759,8 +4764,10 @@ JSValue JS_Call (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, J
case JS_FUNC_KIND_C:
return js_call_c_function (ctx, func_obj, this_obj, argc, argv);
case JS_FUNC_KIND_REGISTER:
return JS_CallRegisterVM (ctx, f->u.reg.code, this_obj, argc, argv,
f->u.reg.env_record, f->u.reg.outer_frame);
return JS_CallRegisterVM (ctx, JS_VALUE_GET_CODE(f->u.cell.code)->u.reg.code, this_obj, argc, argv,
f->u.cell.env_record, f->u.cell.outer_frame);
case JS_FUNC_KIND_NATIVE:
return cell_native_dispatch (ctx, func_obj, this_obj, argc, argv);
default:
return JS_RaiseDisrupt (ctx, "not a function");
}
@@ -5326,6 +5333,10 @@ JSValue js_regexp_toString (JSContext *ctx, JSValue this_val, int argc, JSValue
int lre_check_timeout (void *opaque) {
JSContext *ctx = opaque;
if (cell_rt_native_active (ctx)) {
atomic_store_explicit (&ctx->pause_flag, 0, memory_order_relaxed);
return 0;
}
return atomic_load_explicit (&ctx->pause_flag, memory_order_relaxed) >= 2;
}
@@ -10488,15 +10499,46 @@ JSValue JS_CellCall (JSContext *ctx, JSValue fn, JSValue this_val, JSValue args)
return js_cell_call (ctx, JS_NULL, argc, argv);
}
static int js_cell_read_number_strict (JSValue val, double *out) {
uint32_t tag = JS_VALUE_GET_TAG (val);
if (tag == JS_TAG_INT) {
*out = (double)JS_VALUE_GET_INT (val);
return 0;
}
if (JS_TAG_IS_FLOAT64 (tag)) {
*out = JS_VALUE_GET_FLOAT64 (val);
return 0;
}
return -1;
}
static JSValue js_cell_number_from_double (JSContext *ctx, double d) {
if (d >= INT32_MIN && d <= INT32_MAX) {
int32_t i = (int32_t)d;
if ((double)i == d)
return JS_NewInt32 (ctx, i);
}
return JS_NewFloat64 (ctx, d);
}
/* C API: modulo(a, b) - modulo operation */
JSValue JS_CellModulo (JSContext *ctx, JSValue a, JSValue b) {
JSValue argv[2] = { a, b };
return js_cell_modulo (ctx, JS_NULL, 2, argv);
double dividend, divisor;
if (js_cell_read_number_strict (a, &dividend) < 0) return JS_NULL;
if (js_cell_read_number_strict (b, &divisor) < 0) return JS_NULL;
if (isnan (dividend) || isnan (divisor)) return JS_NULL;
if (divisor == 0.0) return JS_NULL;
if (dividend == 0.0) return JS_NewFloat64 (ctx, 0.0);
return js_cell_number_from_double (ctx,
dividend - (divisor * floor (dividend / divisor)));
}
/* C API: neg(val) - negate number */
JSValue JS_CellNeg (JSContext *ctx, JSValue val) {
return js_cell_neg (ctx, JS_NULL, 1, &val);
double d;
if (js_cell_read_number_strict (val, &d) < 0) return JS_NULL;
if (isnan (d)) return JS_NULL;
return js_cell_number_from_double (ctx, -d);
}
/* C API: not(val) - logical not */
@@ -10621,60 +10663,86 @@ JSValue JS_CellNumber (JSContext *ctx, JSValue val) {
/* C API: abs(num) - absolute value */
JSValue JS_CellAbs (JSContext *ctx, JSValue num) {
return js_cell_number_abs (ctx, JS_NULL, 1, &num);
double d;
if (js_cell_read_number_strict (num, &d) < 0) return JS_NULL;
return js_cell_number_from_double (ctx, fabs (d));
}
/* C API: sign(num) - sign of number (-1, 0, 1) */
JSValue JS_CellSign (JSContext *ctx, JSValue num) {
return js_cell_number_sign (ctx, JS_NULL, 1, &num);
double d;
if (js_cell_read_number_strict (num, &d) < 0) return JS_NULL;
if (d < 0) return JS_NewInt32 (ctx, -1);
if (d > 0) return JS_NewInt32 (ctx, 1);
return JS_NewInt32 (ctx, 0);
}
/* C API: floor(num) - floor */
JSValue JS_CellFloor (JSContext *ctx, JSValue num) {
return js_cell_number_floor (ctx, JS_NULL, 1, &num);
double d;
if (js_cell_read_number_strict (num, &d) < 0) return JS_NULL;
return js_cell_number_from_double (ctx, floor (d));
}
/* C API: ceiling(num) - ceiling */
JSValue JS_CellCeiling (JSContext *ctx, JSValue num) {
return js_cell_number_ceiling (ctx, JS_NULL, 1, &num);
double d;
if (js_cell_read_number_strict (num, &d) < 0) return JS_NULL;
return js_cell_number_from_double (ctx, ceil (d));
}
/* C API: round(num) - round to nearest integer */
JSValue JS_CellRound (JSContext *ctx, JSValue num) {
return js_cell_number_round (ctx, JS_NULL, 1, &num);
double d;
if (js_cell_read_number_strict (num, &d) < 0) return JS_NULL;
return js_cell_number_from_double (ctx, round (d));
}
/* C API: trunc(num) - truncate towards zero */
JSValue JS_CellTrunc (JSContext *ctx, JSValue num) {
return js_cell_number_trunc (ctx, JS_NULL, 1, &num);
double d;
if (js_cell_read_number_strict (num, &d) < 0) return JS_NULL;
return js_cell_number_from_double (ctx, trunc (d));
}
/* C API: whole(num) - integer part */
JSValue JS_CellWhole (JSContext *ctx, JSValue num) {
return js_cell_number_whole (ctx, JS_NULL, 1, &num);
double d;
if (js_cell_read_number_strict (num, &d) < 0) return JS_NULL;
return js_cell_number_from_double (ctx, trunc (d));
}
/* C API: fraction(num) - fractional part */
JSValue JS_CellFraction (JSContext *ctx, JSValue num) {
return js_cell_number_fraction (ctx, JS_NULL, 1, &num);
double d;
if (js_cell_read_number_strict (num, &d) < 0) return JS_NULL;
return js_cell_number_from_double (ctx, d - trunc (d));
}
/* C API: min(a, b) - minimum of two numbers */
JSValue JS_CellMin (JSContext *ctx, JSValue a, JSValue b) {
JSValue argv[2] = { a, b };
return js_cell_number_min (ctx, JS_NULL, 2, argv);
double da, db;
if (js_cell_read_number_strict (a, &da) < 0) return JS_NULL;
if (js_cell_read_number_strict (b, &db) < 0) return JS_NULL;
return js_cell_number_from_double (ctx, da < db ? da : db);
}
/* C API: max(a, b) - maximum of two numbers */
JSValue JS_CellMax (JSContext *ctx, JSValue a, JSValue b) {
JSValue argv[2] = { a, b };
return js_cell_number_max (ctx, JS_NULL, 2, argv);
double da, db;
if (js_cell_read_number_strict (a, &da) < 0) return JS_NULL;
if (js_cell_read_number_strict (b, &db) < 0) return JS_NULL;
return js_cell_number_from_double (ctx, da > db ? da : db);
}
/* C API: remainder(a, b) - remainder after division */
JSValue JS_CellRemainder (JSContext *ctx, JSValue a, JSValue b) {
JSValue argv[2] = { a, b };
return js_cell_number_remainder (ctx, JS_NULL, 2, argv);
double dividend, divisor;
if (js_cell_read_number_strict (a, &dividend) < 0) return JS_NULL;
if (js_cell_read_number_strict (b, &divisor) < 0) return JS_NULL;
if (divisor == 0.0) return JS_NULL;
return js_cell_number_from_double (ctx,
dividend - (trunc (dividend / divisor) * divisor));
}
/* Object functions */
@@ -11348,7 +11416,7 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) {
js_set_global_cfunc(ctx, "filter", js_cell_array_filter, 2);
js_set_global_cfunc(ctx, "sort", js_cell_array_sort, 2);
/* Number utility functions */
/* Number intrinsics: direct calls lower to mcode; globals remain for first-class use. */
js_set_global_cfunc(ctx, "whole", js_cell_number_whole, 1);
js_set_global_cfunc(ctx, "fraction", js_cell_number_fraction, 1);
js_set_global_cfunc(ctx, "floor", js_cell_number_floor, 2);
@@ -12684,8 +12752,8 @@ JSValue JS_GetStack(JSContext *ctx) {
if (!JS_IsFunction(frame->function)) break;
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) {
JSCodeRegister *code = fn->u.reg.code;
if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) {
JSCodeRegister *code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
uint32_t pc = is_first ? cur_pc : (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16);
frames[count].fn = code->name_cstr;
frames[count].file = code->filename_cstr;

View File

@@ -37,7 +37,8 @@ var streamline = function(ir, log) {
var numeric_ops = {
add: true, subtract: true, multiply: true,
divide: true, modulo: true, pow: true
divide: true, modulo: true, remainder: true,
max: true, min: true, pow: true
}
var bool_result_ops = {
eq_int: true, ne_int: true, lt_int: true, gt_int: true,
@@ -229,7 +230,12 @@ var streamline = function(ir, log) {
add: [2, T_NUM, 3, T_NUM],
subtract: [2, T_NUM, 3, T_NUM], multiply: [2, T_NUM, 3, T_NUM],
divide: [2, T_NUM, 3, T_NUM], modulo: [2, T_NUM, 3, T_NUM],
pow: [2, T_NUM, 3, T_NUM], negate: [2, T_NUM],
remainder: [2, T_NUM, 3, T_NUM], max: [2, T_NUM, 3, T_NUM],
min: [2, T_NUM, 3, T_NUM], pow: [2, T_NUM, 3, T_NUM],
negate: [2, T_NUM], abs: [2, T_NUM], sign: [2, T_NUM],
fraction: [2, T_NUM], integer: [2, T_NUM],
floor: [2, T_NUM], ceiling: [2, T_NUM],
round: [2, T_NUM], trunc: [2, T_NUM],
bitand: [2, T_INT, 3, T_INT], bitor: [2, T_INT, 3, T_INT],
bitxor: [2, T_INT, 3, T_INT], shl: [2, T_INT, 3, T_INT],
shr: [2, T_INT, 3, T_INT], ushr: [2, T_INT, 3, T_INT],
@@ -250,8 +256,13 @@ var streamline = function(ir, log) {
var param_types = null
var i = 0
var j = 0
var iter = 0
var instr = null
var bt = null
var src = 0
var dst = 0
var old_bt = null
var changed = false
var rule = null
if (instructions == null || nr_args == 0) {
@@ -275,6 +286,31 @@ var streamline = function(ir, log) {
i = i + 1
}
// Propagate typed constraints backward through move chains.
changed = true
iter = 0
while (changed && iter < num_instr + 4) {
changed = false
i = 0
while (i < num_instr) {
instr = instructions[i]
if (is_array(instr) && instr[0] == "move") {
dst = instr[1]
src = instr[2]
bt = backward_types[dst]
if (bt != null && bt != T_UNKNOWN) {
old_bt = backward_types[src]
merge_backward(backward_types, src, bt)
if (backward_types[src] != old_bt) {
changed = true
}
}
}
i = i + 1
}
iter = iter + 1
}
param_types = array(func.nr_slots)
j = 1
while (j <= nr_args) {
@@ -302,10 +338,14 @@ var streamline = function(ir, log) {
bitnot: [1, T_INT], bitand: [1, T_INT], bitor: [1, T_INT],
bitxor: [1, T_INT], shl: [1, T_INT], shr: [1, T_INT], ushr: [1, T_INT],
negate: [1, T_NUM], concat: [1, T_TEXT],
abs: [1, T_NUM], sign: [1, T_INT], fraction: [1, T_NUM],
integer: [1, T_NUM], floor: [1, T_NUM], ceiling: [1, T_NUM],
round: [1, T_NUM], trunc: [1, T_NUM],
eq: [1, T_BOOL], ne: [1, T_BOOL], lt: [1, T_BOOL],
le: [1, T_BOOL], gt: [1, T_BOOL], ge: [1, T_BOOL], in: [1, T_BOOL],
add: [1, T_NUM], subtract: [1, T_NUM], multiply: [1, T_NUM],
divide: [1, T_NUM], modulo: [1, T_NUM], pow: [1, T_NUM],
divide: [1, T_NUM], modulo: [1, T_NUM], remainder: [1, T_NUM],
max: [1, T_NUM], min: [1, T_NUM], pow: [1, T_NUM],
move: [1, T_UNKNOWN], load_field: [1, T_UNKNOWN],
load_index: [1, T_UNKNOWN], load_dynamic: [1, T_UNKNOWN],
pop: [1, T_UNKNOWN], get: [1, T_UNKNOWN],
@@ -325,16 +365,35 @@ var streamline = function(ir, log) {
is_record: [1, T_BOOL], is_stone: [1, T_BOOL]
}
var infer_slot_write_types = function(func) {
// Known intrinsic return types for invoke result inference.
var intrinsic_return_types = {
abs: T_NUM, floor: T_NUM, ceiling: T_NUM,
round: T_NUM, trunc: T_NUM, fraction: T_NUM,
integer: T_NUM, whole: T_NUM, sign: T_NUM,
max: T_NUM, min: T_NUM, remainder: T_NUM, modulo: T_NUM
}
var infer_slot_write_types = function(func, param_types) {
var instructions = func.instructions
var nr_args = func.nr_args != null ? func.nr_args : 0
var num_instr = 0
var write_types = null
var frame_callee = null
var intrinsic_slots = null
var move_dests = null
var move_srcs = null
var i = 0
var k = 0
var iter = 0
var instr = null
var op = null
var src = 0
var slot = 0
var old_typ = null
var src_typ = null
var typ = null
var callee_slot = null
var changed = false
var rule = null
var cw_keys = null
@@ -344,11 +403,62 @@ var streamline = function(ir, log) {
num_instr = length(instructions)
write_types = array(func.nr_slots)
frame_callee = array(func.nr_slots)
intrinsic_slots = array(func.nr_slots)
move_dests = []
move_srcs = []
i = 0
while (i < num_instr) {
instr = instructions[i]
if (is_array(instr)) {
rule = write_rules[instr[0]]
op = instr[0]
if (op == "access") {
slot = instr[1]
if (slot > 0 && slot > nr_args) {
merge_backward(write_types, slot, access_value_type(instr[2]))
}
if (is_object(instr[2]) && instr[2].make == "intrinsic") {
typ = intrinsic_return_types[instr[2].name]
if (typ != null && slot >= 0 && slot < length(intrinsic_slots)) {
intrinsic_slots[slot] = typ
}
}
i = i + 1
continue
}
if (op == "move") {
slot = instr[1]
if (slot > 0 && slot > nr_args) {
move_dests[] = slot
move_srcs[] = instr[2]
}
i = i + 1
continue
}
if (op == "frame" || op == "goframe") {
if (is_number(instr[1]) && instr[1] >= 0 && instr[1] < length(frame_callee)) {
frame_callee[instr[1]] = instr[2]
}
i = i + 1
continue
}
if (op == "invoke" || op == "tail_invoke") {
slot = instr[2]
typ = T_UNKNOWN
callee_slot = frame_callee[instr[1]]
if (is_number(callee_slot) && callee_slot >= 0 && callee_slot < length(intrinsic_slots)) {
if (intrinsic_slots[callee_slot] != null) {
typ = intrinsic_slots[callee_slot]
}
}
if (slot > 0 && slot > nr_args) {
merge_backward(write_types, slot, typ)
}
i = i + 1
continue
}
rule = write_rules[op]
if (rule != null) {
slot = instr[rule[0]]
typ = rule[1]
@@ -363,6 +473,54 @@ var streamline = function(ir, log) {
i = i + 1
}
// Resolve move writes from known source invariants (fixed-point).
changed = true
iter = 0
while (changed && iter < length(write_types) + 4) {
changed = false
k = 0
while (k < length(move_dests)) {
slot = move_dests[k]
src = move_srcs[k]
src_typ = null
if (is_number(src) && src >= 0) {
if (src < length(write_types) && write_types[src] != null) {
src_typ = write_types[src]
} else if (param_types != null && src < length(param_types) && param_types[src] != null) {
src_typ = param_types[src]
}
}
if (src_typ != null) {
old_typ = write_types[slot]
merge_backward(write_types, slot, src_typ)
if (write_types[slot] != old_typ) {
changed = true
}
}
k = k + 1
}
iter = iter + 1
}
// Any remaining unresolved move write can carry arbitrary type.
k = 0
while (k < length(move_dests)) {
slot = move_dests[k]
src = move_srcs[k]
src_typ = null
if (is_number(src) && src >= 0) {
if (src < length(write_types) && write_types[src] != null) {
src_typ = write_types[src]
} else if (param_types != null && src < length(param_types) && param_types[src] != null) {
src_typ = param_types[src]
}
}
if (src_typ == null && slot > 0 && slot > nr_args) {
merge_backward(write_types, slot, T_UNKNOWN)
}
k = k + 1
}
// Closure-written slots can have any type at runtime — mark unknown
if (func.closure_written != null) {
cw_keys = array(func.closure_written)
@@ -976,6 +1134,94 @@ var streamline = function(ir, log) {
return null
}
// =========================================================
// Pass: eliminate_unreachable_cfg — nop blocks not reachable
// from function entry under explicit jump control-flow.
// =========================================================
var eliminate_unreachable_cfg = function(func) {
var instructions = func.instructions
var num_instr = 0
var disruption_pc = -1
var label_map = null
var reachable = null
var stack = null
var sp = 0
var idx = 0
var tgt = null
var instr = null
var op = null
var nc = 0
if (instructions == null || length(instructions) == 0) {
return null
}
num_instr = length(instructions)
if (is_number(func.disruption_pc)) {
disruption_pc = func.disruption_pc
}
label_map = {}
idx = 0
while (idx < num_instr) {
instr = instructions[idx]
if (is_text(instr) && !starts_with(instr, "_nop_")) {
label_map[instr] = idx
}
idx = idx + 1
}
reachable = array(num_instr, false)
stack = [0]
if (disruption_pc > 0 && disruption_pc < num_instr) {
stack[] = disruption_pc
}
sp = 0
while (sp < length(stack)) {
idx = stack[sp]
sp = sp + 1
if (idx < 0 || idx >= num_instr || reachable[idx]) {
continue
}
reachable[idx] = true
instr = instructions[idx]
if (!is_array(instr)) {
stack[] = idx + 1
continue
}
op = instr[0]
if (op == "jump") {
tgt = label_map[instr[1]]
if (is_number(tgt)) stack[] = tgt
continue
}
if (op == "jump_true" || op == "jump_false" || op == "jump_not_null") {
tgt = label_map[instr[2]]
if (is_number(tgt)) stack[] = tgt
stack[] = idx + 1
continue
}
if (op == "return" || op == "disrupt") {
continue
}
stack[] = idx + 1
}
idx = 0
while (idx < num_instr) {
if (!reachable[idx] && is_array(instructions[idx])) {
nc = nc + 1
instructions[idx] = "_nop_ucfg_" + text(nc)
}
idx = idx + 1
}
return null
}
// =========================================================
// Pass: eliminate_dead_jumps — jump to next label → nop
// =========================================================
@@ -1590,51 +1836,75 @@ var streamline = function(ir, log) {
var param_types = null
var write_types = null
var slot_types = null
var run_cycle = function(suffix) {
var name = null
name = "infer_param_types" + suffix
run_pass(func, name, function() {
param_types = infer_param_types(func)
return param_types
})
if (verify_fn) verify_fn(func, "after " + name)
name = "infer_slot_write_types" + suffix
run_pass(func, name, function() {
write_types = infer_slot_write_types(func, param_types)
return write_types
})
if (verify_fn) verify_fn(func, "after " + name)
name = "eliminate_type_checks" + suffix
run_pass(func, name, function() {
slot_types = eliminate_type_checks(func, param_types, write_types, log)
return slot_types
})
if (verify_fn) verify_fn(func, "after " + name)
if (log != null && log.type_deltas != null && slot_types != null) {
log.type_deltas[] = {
fn: func.name,
cycle: suffix == "" ? 1 : 2,
param_types: param_types,
slot_types: slot_types
}
}
name = "simplify_algebra" + suffix
run_pass(func, name, function() {
return simplify_algebra(func, log)
})
if (verify_fn) verify_fn(func, "after " + name)
name = "simplify_booleans" + suffix
run_pass(func, name, function() {
return simplify_booleans(func, log)
})
if (verify_fn) verify_fn(func, "after " + name)
name = "eliminate_moves" + suffix
run_pass(func, name, function() {
return eliminate_moves(func, log)
})
if (verify_fn) verify_fn(func, "after " + name)
name = "eliminate_unreachable" + suffix
run_pass(func, name, function() {
return eliminate_unreachable(func)
})
if (verify_fn) verify_fn(func, "after " + name)
name = "eliminate_dead_jumps" + suffix
run_pass(func, name, function() {
return eliminate_dead_jumps(func, log)
})
if (verify_fn) verify_fn(func, "after " + name)
return null
}
if (func.instructions == null || length(func.instructions) == 0) {
return null
}
run_pass(func, "infer_param_types", function() {
param_types = infer_param_types(func)
return param_types
})
if (verify_fn) verify_fn(func, "after infer_param_types")
run_pass(func, "infer_slot_write_types", function() {
write_types = infer_slot_write_types(func)
return write_types
})
if (verify_fn) verify_fn(func, "after infer_slot_write_types")
run_pass(func, "eliminate_type_checks", function() {
slot_types = eliminate_type_checks(func, param_types, write_types, log)
return slot_types
})
if (verify_fn) verify_fn(func, "after eliminate_type_checks")
if (log != null && log.type_deltas != null && slot_types != null) {
log.type_deltas[] = {
fn: func.name,
param_types: param_types,
slot_types: slot_types
}
}
run_pass(func, "simplify_algebra", function() {
return simplify_algebra(func, log)
})
if (verify_fn) verify_fn(func, "after simplify_algebra")
run_pass(func, "simplify_booleans", function() {
return simplify_booleans(func, log)
})
if (verify_fn) verify_fn(func, "after simplify_booleans")
run_pass(func, "eliminate_moves", function() {
return eliminate_moves(func, log)
})
if (verify_fn) verify_fn(func, "after eliminate_moves")
run_pass(func, "eliminate_unreachable", function() {
return eliminate_unreachable(func)
})
if (verify_fn) verify_fn(func, "after eliminate_unreachable")
run_pass(func, "eliminate_dead_jumps", function() {
return eliminate_dead_jumps(func, log)
})
if (verify_fn) verify_fn(func, "after eliminate_dead_jumps")
run_cycle("")
return null
}

View File

@@ -827,6 +827,27 @@ run("disruption handler accesses object from outer scope", function() {
if (obj.y != 20) fail("handler mutation lost, y=" + text(obj.y))
})
run("disruption in callback with multiple calls after", function() {
// Regression: a function with a disruption handler that calls a
// callback which disrupts, followed by more successful calls.
// In native mode, cell_rt_disrupt must NOT use JS_ThrowTypeError
// (which prints to stderr) — it must silently set the exception.
var log = []
var run_inner = function(name, fn) {
fn()
log[] = "pass:" + name
} disruption {
log[] = "fail:" + name
}
run_inner("a", function() { var x = 1 })
run_inner("b", function() { disrupt })
run_inner("c", function() { var y = 2 })
if (length(log) != 3) fail("expected 3 log entries, got " + text(length(log)))
if (log[0] != "pass:a") fail("expected pass:a, got " + log[0])
if (log[1] != "fail:b") fail("expected fail:b, got " + log[1])
if (log[2] != "pass:c") fail("expected pass:c, got " + log[2])
})
// ============================================================================
// TYPE CHECKING WITH is_* FUNCTIONS
// ============================================================================