7 Commits

Author SHA1 Message Date
John Alanbrook
f7e3c0803c Merge branch 'master' into fix_aot 2026-02-21 13:31:33 -06:00
John Alanbrook
071aa33153 Merge branch 'optimize_mcode' 2026-02-21 03:38:38 -06:00
John Alanbrook
d041c49972 Merge branch 'optimize_mcode' 2026-02-21 03:38:34 -06:00
John Alanbrook
eadad194be doc gc bugs 2026-02-21 03:01:26 -06:00
John Alanbrook
81c88f9439 gx fices 2026-02-21 02:37:30 -06:00
John Alanbrook
20c2576fa7 working link 2026-02-21 02:18:42 -06:00
John Alanbrook
65fa37cc03 fix 2026-02-19 03:12:58 -06:00
17 changed files with 1154 additions and 139 deletions

View File

@@ -124,16 +124,19 @@ This project uses a **copying garbage collector**. ANY JS allocation (`JS_NewObj
JS_FRAME(js);
JS_ROOT(obj, JS_NewObject(js));
JS_SetPropertyStr(js, obj.val, "x", JS_NewInt32(js, 42));
JS_SetPropertyStr(js, obj.val, "name", JS_NewString(js, "hello"));
JSValue name = JS_NewString(js, "hello");
JS_SetPropertyStr(js, obj.val, "name", name);
JS_RETURN(obj.val);
```
**Pattern — array with loop:**
**Pattern — array with loop (declare root BEFORE the loop):**
```c
JS_FRAME(js);
JS_ROOT(arr, JS_NewArray(js));
JSGCRef item = { .val = JS_NULL, .prev = NULL };
JS_PushGCRef(js, &item);
for (int i = 0; i < count; i++) {
JS_ROOT(item, JS_NewObject(js));
item.val = JS_NewObject(js);
JS_SetPropertyStr(js, item.val, "v", JS_NewInt32(js, i));
JS_SetPropertyNumber(js, arr.val, i, item.val);
}
@@ -142,18 +145,28 @@ JS_RETURN(arr.val);
**Rules:**
- Access rooted values via `.val` (e.g., `obj.val`, not `obj`)
- NEVER put `JS_ROOT` inside a loop — it pushes the same stack address twice, corrupting the GC chain
- Error returns before `JS_FRAME` use plain `return`
- Error returns after `JS_FRAME` must use `JS_RETURN_EX()` or `JS_RETURN_NULL()`
- When calling a helper that itself returns a JSValue, that return value is safe to pass directly into `JS_SetPropertyStr` — no need to root temporaries that aren't stored in a local
**Common mistake — UNSAFE (will crash under GC pressure):**
**CRITICAL — C argument evaluation order bug:**
Allocating functions (`JS_NewString`, `JS_NewFloat64`, `js_new_blob_stoned_copy`, etc.) used as arguments to `JS_SetPropertyStr` can crash because C evaluates arguments in unspecified order. The compiler may read `obj.val` BEFORE the allocating call, then GC moves the object, leaving a stale pointer.
```c
JSValue obj = JS_NewObject(js); // NOT rooted
JS_SetPropertyStr(js, obj, "pixels", js_new_blob_stoned_copy(js, data, len));
// ^^^ blob allocation can GC, invalidating obj
return obj; // obj may be a dangling pointer
// UNSAFE — intermittent crash:
JS_SetPropertyStr(js, obj.val, "format", JS_NewString(js, "rgba32"));
JS_SetPropertyStr(js, obj.val, "pixels", js_new_blob_stoned_copy(js, data, len));
// SAFE — separate the allocation:
JSValue fmt = JS_NewString(js, "rgba32");
JS_SetPropertyStr(js, obj.val, "format", fmt);
JSValue pixels = js_new_blob_stoned_copy(js, data, len);
JS_SetPropertyStr(js, obj.val, "pixels", pixels);
```
`JS_NewInt32`, `JS_NewUint32`, and `JS_NewBool` do NOT allocate and are safe inline.
See `docs/c-modules.md` for the full GC safety reference.
## Project Layout
@@ -167,17 +180,19 @@ See `docs/c-modules.md` for the full GC safety reference.
## Package Management (Shop CLI)
When running locally with `./cell --dev`, these commands manage packages:
**Two shops:** `cell <cmd>` uses the global shop at `~/.cell/packages/`. `cell --dev <cmd>` uses the local shop at `.cell/packages/`. Linked packages (via `cell link`) are symlinked into the shop — edit the source directory directly.
```
./cell --dev add <path> # add a package (local path or remote)
./cell --dev remove <path> # remove a package (cleans lock, symlink, dylibs)
./cell --dev build <path> # build C modules for a package
./cell --dev test package <path> # run tests for a package
./cell --dev list # list installed packages
cell add <path> # add a package (local path or remote)
cell remove <path> # remove a package (cleans lock, symlink, dylibs)
cell build <path> # build C modules for a package
cell build <path> --force # force rebuild (ignore stat cache)
cell test package <path> # run tests for a package
cell list # list installed packages
cell link # list linked packages
```
Local paths are symlinked into `.cell/packages/`. The build step compiles C files to content-addressed dylibs in `~/.cell/build/<hash>` and writes a per-package manifest so the runtime can find them. C files in `src/` are support files linked into module dylibs, not standalone modules.
The build step compiles C files to content-addressed dylibs in `~/.cell/build/<hash>` and writes a per-package manifest so the runtime can find them. C files in `src/` are support files linked into module dylibs, not standalone modules.
## Debugging Compiler Issues

302
benches/micro_core.cm Normal file
View File

@@ -0,0 +1,302 @@
// micro_core.cm — direct microbenchmarks for core ops
function blackhole(sink, x) {
return (sink + (x | 0)) | 0
}
function make_obj_xy(x, y) {
return {x: x, y: y}
}
function make_obj_yx(x, y) {
// Different insertion order to force a different shape
return {y: y, x: x}
}
function make_packed_array(n) {
var a = []
var i = 0
for (i = 0; i < n; i++) push(a, i)
return a
}
function make_holey_array(n) {
var a = []
var i = 0
for (i = 0; i < n; i += 2) a[i] = i
return a
}
return {
loop_empty: function(n) {
var sink = 0
var i = 0
for (i = 0; i < n; i++) {}
return blackhole(sink, n)
},
i32_add: function(n) {
var sink = 0
var x = 1
var i = 0
for (i = 0; i < n; i++) x = (x + 3) | 0
return blackhole(sink, x)
},
f64_add: function(n) {
var sink = 0
var x = 1.0
var i = 0
for (i = 0; i < n; i++) x = x + 3.14159
return blackhole(sink, x | 0)
},
mixed_add: function(n) {
var sink = 0
var x = 1
var i = 0
for (i = 0; i < n; i++) x = x + 0.25
return blackhole(sink, x | 0)
},
bit_ops: function(n) {
var sink = 0
var x = 0x12345678
var i = 0
for (i = 0; i < n; i++) x = ((x << 5) ^ (x >>> 3)) | 0
return blackhole(sink, x)
},
overflow_path: function(n) {
var sink = 0
var x = 0x70000000
var i = 0
for (i = 0; i < n; i++) x = (x + 0x10000000) | 0
return blackhole(sink, x)
},
call_direct: function(n) {
var sink = 0
var f = function(a) { return (a + 1) | 0 }
var x = 0
var i = 0
for (i = 0; i < n; i++) x = f(x)
return blackhole(sink, x)
},
call_indirect: function(n) {
var sink = 0
var f = function(a) { return (a + 1) | 0 }
var g = f
var x = 0
var i = 0
for (i = 0; i < n; i++) x = g(x)
return blackhole(sink, x)
},
call_closure: function(n) {
var sink = 0
var make_adder = function(k) {
return function(a) { return (a + k) | 0 }
}
var add3 = make_adder(3)
var x = 0
var i = 0
for (i = 0; i < n; i++) x = add3(x)
return blackhole(sink, x)
},
array_read_packed: function(n) {
var sink = 0
var a = make_packed_array(1024)
var x = 0
var i = 0
for (i = 0; i < n; i++) x = (x + a[i & 1023]) | 0
return blackhole(sink, x)
},
array_write_packed: function(n) {
var sink = 0
var a = make_packed_array(1024)
var i = 0
for (i = 0; i < n; i++) a[i & 1023] = i
return blackhole(sink, a[17] | 0)
},
array_read_holey: function(n) {
var sink = 0
var a = make_holey_array(2048)
var x = 0
var i = 0
var v = null
for (i = 0; i < n; i++) {
v = a[(i & 2047)]
if (v) x = (x + v) | 0
}
return blackhole(sink, x)
},
array_push_steady: function(n) {
var sink = 0
var x = 0
var j = 0
var i = 0
var a = null
for (j = 0; j < n; j++) {
a = []
for (i = 0; i < 256; i++) push(a, i)
x = (x + length(a)) | 0
}
return blackhole(sink, x)
},
array_indexed_sum: function(n) {
var sink = 0
var a = make_packed_array(1024)
var x = 0
var j = 0
var i = 0
for (j = 0; j < n; j++) {
x = 0
for (i = 0; i < 1024; i++) {
x = (x + a[i]) | 0
}
}
return blackhole(sink, x)
},
prop_read_mono: function(n) {
var sink = 0
var o = make_obj_xy(1, 2)
var x = 0
var i = 0
for (i = 0; i < n; i++) x = (x + o.x) | 0
return blackhole(sink, x)
},
prop_read_poly_2: function(n) {
var sink = 0
var a = make_obj_xy(1, 2)
var b = make_obj_yx(1, 2)
var x = 0
var i = 0
var o = null
for (i = 0; i < n; i++) {
o = (i & 1) == 0 ? a : b
x = (x + o.x) | 0
}
return blackhole(sink, x)
},
prop_read_poly_4: function(n) {
var sink = 0
var shapes = [
{x: 1, y: 2},
{y: 2, x: 1},
{x: 1, z: 3, y: 2},
{w: 0, x: 1, y: 2}
]
var x = 0
var i = 0
for (i = 0; i < n; i++) {
x = (x + shapes[i & 3].x) | 0
}
return blackhole(sink, x)
},
string_concat_small: function(n) {
var sink = 0
var x = 0
var j = 0
var i = 0
var s = null
for (j = 0; j < n; j++) {
s = ""
for (i = 0; i < 16; i++) s = s + "x"
x = (x + length(s)) | 0
}
return blackhole(sink, x)
},
string_concat_medium: function(n) {
var sink = 0
var x = 0
var j = 0
var i = 0
var s = null
for (j = 0; j < n; j++) {
s = ""
for (i = 0; i < 100; i++) s = s + "abcdefghij"
x = (x + length(s)) | 0
}
return blackhole(sink, x)
},
string_slice: function(n) {
var sink = 0
var base = "the quick brown fox jumps over the lazy dog"
var x = 0
var i = 0
var s = null
for (i = 0; i < n; i++) {
s = text(base, i % 10, i % 10 + 10)
x = (x + length(s)) | 0
}
return blackhole(sink, x)
},
guard_hot_number: function(n) {
var sink = 0
var x = 1
var i = 0
for (i = 0; i < n; i++) x = x + 1
return blackhole(sink, x | 0)
},
guard_mixed_types: function(n) {
var sink = 0
var vals = [1, "a", 2, "b", 3, "c", 4, "d"]
var x = 0
var i = 0
for (i = 0; i < n; i++) {
if (is_number(vals[i & 7])) x = (x + vals[i & 7]) | 0
}
return blackhole(sink, x)
},
reduce_sum: function(n) {
var sink = 0
var a = make_packed_array(256)
var x = 0
var i = 0
for (i = 0; i < n; i++) {
x = (x + reduce(a, function(acc, v) { return acc + v }, 0)) | 0
}
return blackhole(sink, x)
},
filter_evens: function(n) {
var sink = 0
var a = make_packed_array(256)
var x = 0
var i = 0
for (i = 0; i < n; i++) {
x = (x + length(filter(a, function(v) { return v % 2 == 0 }))) | 0
}
return blackhole(sink, x)
},
arrfor_sum: function(n) {
var sink = 0
var a = make_packed_array(256)
var x = 0
var i = 0
var sum = 0
for (i = 0; i < n; i++) {
sum = 0
arrfor(a, function(v) { sum += v })
x = (x + sum) | 0
}
return blackhole(sink, x)
}
}

View File

@@ -390,11 +390,13 @@ Build.compile_file = function(pkg, file, target, opts) {
// Layer 2: stat-based manifest probe (zero file reads on warm cache)
var mf_obj = null
var _linked = fd.is_link(setup.pkg_dir)
var _tag = _linked ? ' [linked]' : ''
if (!_opts.force) {
mf_obj = bmfst_probe(setup.cmd_str, setup.src_path)
if (mf_obj) {
if (_opts.verbose) log.build('[verbose] manifest hit: ' + file)
log.shop('manifest hit ' + file)
if (_opts.verbose) log.build(`[verbose] manifest hit: ${pkg}/${file}${_tag}`)
log.shop(`manifest hit ${pkg}/${file}${_tag}`)
return mf_obj
}
}
@@ -578,11 +580,13 @@ Build.build_module_dylib = function(pkg, file, target, opts) {
// Stat-based dylib manifest — zero file reads on warm cache
var mf_dylib = null
var _linked = fd.is_link(setup.pkg_dir)
var _tag = _linked ? ' [linked]' : ''
if (!_opts.force) {
mf_dylib = bmfst_dl_probe(setup, link_info)
if (mf_dylib) {
if (_opts.verbose) log.build('[verbose] manifest hit: ' + file)
log.shop('manifest hit ' + file)
if (_opts.verbose) log.build(`[verbose] manifest hit: ${pkg}/${file}${_tag}`)
log.shop(`manifest hit ${pkg}/${file}${_tag}`)
return mf_dylib
}
}

View File

@@ -453,6 +453,53 @@ JSC_CCALL(mymod_make,
)
```
### C Argument Evaluation Order (critical)
In C, the **order of evaluation of function arguments is unspecified**. This interacts with the copying GC to create intermittent crashes that are extremely difficult to diagnose.
```c
// UNSAFE — crashes intermittently:
JS_FRAME(js);
JS_ROOT(obj, JS_NewObject(js));
JS_SetPropertyStr(js, obj.val, "format", JS_NewString(js, "rgba32"));
// ^^^^^^^ may be evaluated BEFORE JS_NewString runs
// If JS_NewString triggers GC, the already-read obj.val is a dangling pointer.
```
The compiler is free to evaluate `obj.val` into a register, *then* call `JS_NewString`. If `JS_NewString` triggers GC, the object moves to a new address. The rooted `obj` is updated by GC, but the **register copy** is not — it still holds the old address. `JS_SetPropertyStr` then writes to freed memory.
**Fix:** always separate the allocating call into a local variable:
```c
// SAFE:
JS_FRAME(js);
JS_ROOT(obj, JS_NewObject(js));
JSValue fmt = JS_NewString(js, "rgba32");
JS_SetPropertyStr(js, obj.val, "format", fmt);
// obj.val is read AFTER JS_NewString completes — guaranteed correct.
```
This applies to **any** allocating function used as an argument when another argument references a rooted `.val`:
```c
// ALL of these are UNSAFE:
JS_SetPropertyStr(js, obj.val, "pixels", js_new_blob_stoned_copy(js, data, len));
JS_SetPropertyStr(js, obj.val, "x", JS_NewFloat64(js, 3.14));
JS_SetPropertyStr(js, obj.val, "name", JS_NewString(js, name));
// SAFE versions — separate the allocation:
JSValue pixels = js_new_blob_stoned_copy(js, data, len);
JS_SetPropertyStr(js, obj.val, "pixels", pixels);
JSValue x = JS_NewFloat64(js, 3.14);
JS_SetPropertyStr(js, obj.val, "x", x);
JSValue s = JS_NewString(js, name);
JS_SetPropertyStr(js, obj.val, "name", s);
```
**Functions that allocate** (must be separated): `JS_NewString`, `JS_NewFloat64`, `JS_NewInt64`, `JS_NewObject`, `JS_NewArray`, `JS_NewCFunction`, `js_new_blob_stoned_copy`
**Functions that do NOT allocate** (safe inline): `JS_NewInt32`, `JS_NewUint32`, `JS_NewBool`, `JS_NULL`, `JS_TRUE`, `JS_FALSE`
### Macros
| Macro | Purpose |

View File

@@ -319,7 +319,8 @@ JSC_SCALL(os_system,
JSC_CCALL(os_exit,
int code = 0;
if (argc > 0) JS_ToInt32(js, &code, argv[0]);
if (argc > 0 && !JS_IsNull(argv[0]))
JS_ToInt32(js, &code, argv[0]);
exit(code);
)

View File

@@ -139,6 +139,42 @@ function package_in_shop(package) {
return package in lock
}
// Derive canonical lock name from a directory's git origin remote.
// Reads .git/config, extracts the origin url, strips https:// and .git suffix,
// then checks if that name exists in the lock file.
function git_origin_to_lock_name(dir) {
var git_cfg = dir + '/.git/config'
var raw = null
var lines = null
var in_origin = false
var url = null
var candidate = null
if (!fd.is_file(git_cfg)) return null
raw = text(fd.slurp(git_cfg))
if (!raw) return null
lines = array(raw, '\n')
arrfor(lines, function(line) {
var trimmed = trim(line)
if (trimmed == '[remote "origin"]') {
in_origin = true
} else if (starts_with(trimmed, '[')) {
in_origin = false
} else if (in_origin && starts_with(trimmed, 'url = ')) {
url = trim(text(trimmed, 6))
}
})
if (!url) return null
candidate = url
if (starts_with(candidate, 'https://'))
candidate = text(candidate, 8)
else if (starts_with(candidate, 'http://'))
candidate = text(candidate, 7)
if (ends_with(candidate, '.git'))
candidate = text(candidate, 0, length(candidate) - 4)
if (package_in_shop(candidate)) return candidate
return null
}
function abs_path_to_package(package_dir)
{
if (!fd.is_file(package_dir + '/cell.toml')) {
@@ -182,20 +218,21 @@ function abs_path_to_package(package_dir)
if (package_in_shop(package_dir))
return package_dir
// For local directories (e.g., linked targets), read the package name from cell.toml
var _toml_path = package_dir + '/cell.toml'
var content = null
var cfg = null
if (fd.is_file(_toml_path)) {
content = text(fd.slurp(_toml_path))
cfg = toml.decode(content)
if (cfg.package)
return cfg.package
}
// For local directories, try git remote origin to derive canonical name
var _git_name = git_origin_to_lock_name(package_dir)
if (_git_name) return _git_name
return package_dir
}
function safe_canonicalize(pkg) {
if (!pkg || !starts_with(pkg, '/')) return pkg
var _canon = null
var _try = function() { _canon = abs_path_to_package(pkg) } disruption {}
_try()
return (_canon && _canon != pkg) ? _canon : pkg
}
// given a file, find the absolute path, package name, and import name
Shop.file_info = function(file) {
var info = {
@@ -419,6 +456,15 @@ Shop.extract_commit_hash = function(pkg, response) {
var open_dls = {}
var package_dylibs = {} // pkg -> [{file, symbol, dylib}, ...]
function open_dylib_cached(path) {
var handle = open_dls[path]
if (handle) return handle
handle = os.dylib_open(path)
if (!handle) return null
open_dls[path] = handle
return handle
}
// Host target detection for native dylib resolution
function detect_host_target() {
var platform = os.platform()
@@ -455,7 +501,7 @@ function try_native_mod_dylib(pkg, stem) {
if (!fd.is_file(build_path)) return null
log.shop('native dylib cache hit: ' + stem)
var handle = os.dylib_open(build_path)
var handle = open_dylib_cached(build_path)
if (!handle) return null
var sym = Shop.c_symbol_for_file(pkg, stem)
return {_native: true, _handle: handle, _sym: sym}
@@ -828,7 +874,7 @@ function resolve_path(path, ctx)
if (fd.is_file(ctx_path)) {
is_core = (ctx == 'core') || is_core_dir(ctx_dir)
scope = is_core ? SCOPE_CORE : SCOPE_LOCAL
return {path: ctx_path, scope: scope, pkg: is_core ? 'core' : ctx}
return {path: ctx_path, scope: scope, pkg: is_core ? 'core' : safe_canonicalize(ctx)}
}
if (is_internal_path(path))
@@ -907,9 +953,10 @@ 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] != null) return package_dylibs[pkg]
if (pkg == 'core') {
package_dylibs[pkg] = []
var _pkg = safe_canonicalize(pkg)
if (package_dylibs[_pkg] != null) return package_dylibs[_pkg]
if (_pkg == 'core') {
package_dylibs[_pkg] = []
return []
}
@@ -922,23 +969,23 @@ function ensure_package_dylibs(pkg) {
target = detect_host_target()
if (!target) return null
c_files = pkg_tools.get_c_files(pkg, target, true)
c_files = pkg_tools.get_c_files(_pkg, target, true)
if (!c_files || length(c_files) == 0) {
package_dylibs[pkg] = []
package_dylibs[_pkg] = []
return []
}
log.shop('ensuring C modules for ' + pkg)
results = build_mod.build_dynamic(pkg, target, 'release', {})
log.shop('ensuring C modules for ' + _pkg)
results = build_mod.build_dynamic(_pkg, target, 'release', {})
} else {
// No build module at runtime — read manifest from cell build
results = read_dylib_manifest(pkg)
results = read_dylib_manifest(_pkg)
if (!results) return null
log.shop('loaded manifest for ' + pkg + ' (' + text(length(results)) + ' modules)')
log.shop('loaded manifest for ' + _pkg + ' (' + text(length(results)) + ' modules)')
}
if (results == null) results = []
package_dylibs[pkg] = results
package_dylibs[_pkg] = results
// Preload all sibling dylibs with RTLD_LAZY|RTLD_GLOBAL
arrfor(results, function(r) {
@@ -982,7 +1029,7 @@ function try_dylib_symbol(sym, pkg, file_stem) {
// Resolve a C symbol by searching:
// At each scope: check build-cache dylib first, then internal (static)
function resolve_c_symbol(path, _pkg_ctx) {
var package_context = is_core_dir(_pkg_ctx) ? 'core' : _pkg_ctx
var package_context = is_core_dir(_pkg_ctx) ? 'core' : safe_canonicalize(_pkg_ctx)
var explicit = split_explicit_package_import(path)
var sym = null
var loader = null
@@ -1212,8 +1259,17 @@ Shop.is_loaded = function is_loaded(path, package_context) {
}
// Create a use function bound to a specific package context
function make_use_fn(pkg) {
function make_use_fn(pkg, force_native) {
return function(path) {
var _native = null
if (force_native && !native_mode) {
_native = function() {
return Shop.use_native(path, pkg)
} disruption {
return Shop.use(path, pkg)
}
return _native()
}
return Shop.use(path, pkg)
}
}
@@ -1244,7 +1300,7 @@ function execute_module(info)
inject = Shop.script_inject_for(file_info)
env = inject_env(inject)
pkg = file_info.package
env.use = make_use_fn(pkg)
env.use = make_use_fn(pkg, true)
env = stone(env)
used = os.native_module_load_named(
mod_resolve.symbol._handle, mod_resolve.symbol._sym, env)
@@ -1298,7 +1354,7 @@ Shop.use = function use(path, _pkg_ctx) {
log.error("use() expects a text module path, but received a non-text value")
disrupt
}
var package_context = is_core_dir(_pkg_ctx) ? 'core' : _pkg_ctx
var package_context = is_core_dir(_pkg_ctx) ? 'core' : safe_canonicalize(_pkg_ctx)
// Check for embedded module (static builds)
var embed_key = 'embedded:' + path
var embedded = null
@@ -2148,7 +2204,7 @@ Shop.load_as_dylib = function(path, pkg) {
if (!file_info) file_info = Shop.file_info(file_path)
inject = Shop.script_inject_for(file_info)
env = inject_env(inject)
env.use = make_use_fn(real_pkg)
env.use = make_use_fn(real_pkg, true)
env = stone(env)
return os.native_module_load_named(result._handle, result._sym, env)
}
@@ -2195,18 +2251,45 @@ Shop.parse_package = function(locator) {
Shop.use_native = function(path, package_context) {
var src_path = path
if (!starts_with(path, '/'))
var locator = null
var lookup = null
var cache_key = null
var cfg = null
var old_native = null
if (!starts_with(path, '/') && !fd.is_file(path)) {
lookup = ends_with(path, '.cm') ? path : path + '.cm'
locator = resolve_locator(lookup, package_context)
if (!locator) { print('Module not found: ' + path); disrupt }
src_path = locator.path
} else if (!starts_with(path, '/')) {
src_path = fd.realpath(path)
}
if (!fd.is_file(src_path)) { log.error('File not found: ' + path); disrupt }
var file_info = Shop.file_info(src_path)
var pkg = file_info.package || package_context
var pkg = file_info.package || (locator ? locator.pkg : package_context)
var sym_stem = fd.basename(src_path)
var pkg_dir = null
cache_key = 'native:' + text(pkg || '') + ':' + src_path
if (use_cache[cache_key]) return use_cache[cache_key]
var sym_name = null
if (pkg)
sym_name = Shop.c_symbol_for_file(pkg, fd.basename(src_path))
if (pkg) {
pkg_dir = get_packages_dir() + '/' + safe_package_path(pkg)
if (starts_with(src_path, pkg_dir + '/')) {
sym_stem = text(src_path, length(pkg_dir) + 1)
}
sym_name = Shop.c_symbol_for_file(pkg, sym_stem)
}
var build = Shop.use('build', 'core')
var build = use_cache['core/build'] || use_cache['build']
if (!build) {
cfg = Shop.load_config()
old_native = cfg.policy.native
cfg.policy.native = false
build = Shop.use('build', 'core')
cfg.policy.native = old_native
}
var dylib_path = build.compile_native(src_path, null, null, pkg)
var handle = os.dylib_open(dylib_path)
@@ -2215,12 +2298,16 @@ Shop.use_native = function(path, package_context) {
// 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.use = make_use_fn(pkg, true)
env = stone(env)
var loaded = null
if (sym_name)
return os.native_module_load_named(handle, sym_name, env)
return os.native_module_load(handle, env)
loaded = os.native_module_load_named(handle, sym_name, env)
else
loaded = os.native_module_load(handle, env)
use_cache[cache_key] = loaded
return loaded
}
return Shop

13
link.ce
View File

@@ -140,17 +140,18 @@ if (cmd == 'list') {
return
}
// Read package name from cell.toml
// Derive canonical package name from the target directory
_read_toml = function() {
content = toml.decode(text(fd.slurp(toml_path)))
if (content.package) {
pkg_name = content.package
var info = shop.file_info(target + '/cell.toml')
if (info && info.package) {
pkg_name = info.package
} else {
log.console("Error: cell.toml at " + target + " does not define 'package'")
log.console("Error: could not determine package name for " + target)
log.console("Ensure it is installed or has a git remote matching a lock entry")
$stop()
}
} disruption {
log.console("Error reading cell.toml")
log.console("Error determining package name for " + target)
$stop()
}
_read_toml()

View File

@@ -790,6 +790,50 @@ ${sw("w", "%fp", "%dest", "%r")}
@entry
${sr("a", "%obj_slot")}
${sr("b", "%key_slot")}
%ptag =l and %a, 7
%is_ptr =w ceql %ptag, 1
jnz %is_ptr, @arr_ptr, @fallback
@arr_ptr
%arr_ptr =l and %a, -8
%arr_hdr =l loadl %arr_ptr
@arr_chase
%arr_ty =l and %arr_hdr, 7
%arr_is_fwd =w ceql %arr_ty, 7
jnz %arr_is_fwd, @arr_follow, @arr_chk
@arr_follow
%arr_ptr =l shr %arr_hdr, 3
%arr_hdr =l loadl %arr_ptr
jmp @arr_chase
@arr_chk
%arr_is_array =w ceql %arr_ty, 0
jnz %arr_is_array, @arr_index, @fallback
@arr_index
%idx_tag =l and %b, 1
%idx_is_int =w ceql %idx_tag, 0
jnz %idx_is_int, @idx_ok, @ret_null
@idx_ok
%idx_l =l sar %b, 1
%idx_w =w copy %idx_l
%idx_neg =w csltw %idx_w, 0
jnz %idx_neg, @ret_null, @arr_len
@arr_len
%len_p =l add %arr_ptr, 8
%len_l =l loadl %len_p
%len_w =w copy %len_l
%in =w csltw %idx_w, %len_w
jnz %in, @load, @ret_null
@load
%idx_off_l =l extsw %idx_w
%idx_off_l =l shl %idx_off_l, 3
%vals_p =l add %arr_ptr, 16
%elem_p =l add %vals_p, %idx_off_l
%r =l loadl %elem_p
${sw("w", "%fp", "%dest", "%r")}
ret %fp
@ret_null
${sw("w", "%fp", "%dest", text(qbe.js_null))}
ret %fp
@fallback
%r =l call $cell_rt_load_dynamic(l %ctx, l %a, l %b)
%is_exc =w ceql %r, 15
jnz %is_exc, @exc, @ok
@@ -805,14 +849,49 @@ ${sw("w", "%fp", "%dest", "%r")}
@entry
${sr("a", "%arr_slot")}
${sr("b", "%idx_slot")}
%r =l call $cell_rt_load_index(l %ctx, l %a, l %b)
%is_exc =w ceql %r, 15
jnz %is_exc, @exc, @ok
@ok
%idx_tag =l and %b, 1
%idx_is_int =w ceql %idx_tag, 0
jnz %idx_is_int, @idx_ok, @ret_null
@idx_ok
%idx_l =l sar %b, 1
%idx_w =w copy %idx_l
%idx_neg =w csltw %idx_w, 0
jnz %idx_neg, @ret_null, @arr_init
@arr_init
%ptag =l and %a, 7
%is_ptr =w ceql %ptag, 1
jnz %is_ptr, @arr_ptr_ok, @ret_null
@arr_ptr_ok
%arr_ptr =l and %a, -8
%arr_hdr =l loadl %arr_ptr
@arr_chase
%arr_ty =l and %arr_hdr, 7
%arr_is_fwd =w ceql %arr_ty, 7
jnz %arr_is_fwd, @arr_follow, @arr_chk
@arr_follow
%arr_ptr =l shr %arr_hdr, 3
%arr_hdr =l loadl %arr_ptr
jmp @arr_chase
@arr_chk
%arr_is_array =w ceql %arr_ty, 0
jnz %arr_is_array, @arr_len, @ret_null
@arr_len
%len_p =l add %arr_ptr, 8
%len_l =l loadl %len_p
%len_w =w copy %len_l
%in =w csltw %idx_w, %len_w
jnz %in, @load, @ret_null
@load
%idx_off_l =l extsw %idx_w
%idx_off_l =l shl %idx_off_l, 3
%vals_p =l add %arr_ptr, 16
%elem_p =l add %vals_p, %idx_off_l
%r =l loadl %elem_p
${sw("w", "%fp", "%dest", "%r")}
ret %fp
@exc
ret 0
@ret_null
${sw("w", "%fp", "%dest", text(qbe.js_null))}
ret %fp
}`
// store_field(ctx, fp, obj_slot, val_slot, lit_idx) — no dest write
@@ -834,10 +913,37 @@ ${sr("b", "%val_slot")}
${sr("a", "%obj_slot")}
${sr("b", "%val_slot")}
${sr("c", "%key_slot")}
%ptag =l and %a, 7
%is_ptr =w ceql %ptag, 1
jnz %is_ptr, @arr_ptr, @fallback
@arr_ptr
%arr_ptr =l and %a, -8
%arr_hdr =l loadl %arr_ptr
@arr_chase
%arr_ty =l and %arr_hdr, 7
%arr_is_fwd =w ceql %arr_ty, 7
jnz %arr_is_fwd, @arr_follow, @arr_chk
@arr_follow
%arr_ptr =l shr %arr_hdr, 3
%arr_hdr =l loadl %arr_ptr
jmp @arr_chase
@arr_chk
%arr_is_array =w ceql %arr_ty, 0
jnz %arr_is_array, @arr_key_chk, @fallback
@arr_key_chk
%idx_tag =l and %c, 1
%idx_is_int =w ceql %idx_tag, 0
jnz %idx_is_int, @arr_store, @bad
@arr_store
%fp2 =l call $__store_index_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %key_slot)
ret %fp2
@fallback
%ok =w call $cell_rt_store_dynamic(l %ctx, l %b, l %a, l %c)
jnz %ok, @ok, @exc
@ok
ret %fp
@bad
call $cell_rt_disrupt(l %ctx)
@exc
ret 0
}`
@@ -848,10 +954,151 @@ ${sr("c", "%key_slot")}
${sr("a", "%obj_slot")}
${sr("b", "%val_slot")}
${sr("c", "%idx_slot")}
%ok =w call $cell_rt_store_index(l %ctx, l %b, l %a, l %c)
jnz %ok, @ok, @exc
@ok
%idx_tag =l and %c, 1
%idx_is_int =w ceql %idx_tag, 0
jnz %idx_is_int, @idx_ok, @bad
@idx_ok
%idx_l =l sar %c, 1
%idx_w =w copy %idx_l
%idx_neg =w csltw %idx_w, 0
jnz %idx_neg, @bad, @arr_init
@arr_init
%ptag =l and %a, 7
%is_ptr =w ceql %ptag, 1
jnz %is_ptr, @arr_ptr_ok, @bad
@arr_ptr_ok
%arr_val =l copy %a
%arr_ptr =l and %arr_val, -8
%arr_hdr =l loadl %arr_ptr
@arr_chase
%arr_ty =l and %arr_hdr, 7
%arr_is_fwd =w ceql %arr_ty, 7
jnz %arr_is_fwd, @arr_follow, @arr_chk
@arr_follow
%arr_ptr =l shr %arr_hdr, 3
%arr_hdr =l loadl %arr_ptr
jmp @arr_chase
@arr_chk
%arr_is_array =w ceql %arr_ty, 0
jnz %arr_is_array, @stone_chk, @bad
@stone_chk
%arr_stone =l and %arr_hdr, 8
%arr_is_stone =w cnel %arr_stone, 0
jnz %arr_is_stone, @bad, @lens
@lens
%len_p =l add %arr_ptr, 8
%len_l =l loadl %len_p
%len_w =w copy %len_l
%cap_l =l shr %arr_hdr, 8
%cap_w =w copy %cap_l
%need_grow =w csgew %idx_w, %cap_w
jnz %need_grow, @grow_init, @set_item
@grow_init
%new_cap_w =w copy %cap_w
%cap_zero =w ceqw %new_cap_w, 0
jnz %cap_zero, @grow_cap0, @grow_check
@grow_cap0
%new_cap_w =w copy 2
jmp @grow_check
@grow_loop
%new_cap_w =w shl %new_cap_w, 1
%new_cap_neg =w csltw %new_cap_w, 0
jnz %new_cap_neg, @bad, @grow_check
@grow_check
%need_more =w cslew %new_cap_w, %idx_w
jnz %need_more, @grow_loop, @grow_alloc
@grow_alloc
%new_arr =l call $JS_NewArrayCap(l %ctx, w %new_cap_w)
%new_exc =w ceql %new_arr, 15
jnz %new_exc, @exc, @grow_refresh
@grow_refresh
%fp2 =l call $cell_rt_refresh_fp_checked(l %ctx)
jnz %fp2, @grow_reload, @exc
@grow_reload
%fp =l copy %fp2
${sr("ga", "%obj_slot")}
${sr("gb", "%val_slot")}
${sr("gc", "%idx_slot")}
%a =l copy %ga
%b =l copy %gb
%c =l copy %gc
%arr_val =l copy %a
%arr_ptr =l and %arr_val, -8
%arr_hdr =l loadl %arr_ptr
@grow_arr_chase
%arr_ty =l and %arr_hdr, 7
%arr_is_fwd =w ceql %arr_ty, 7
jnz %arr_is_fwd, @grow_arr_follow, @grow_arr_ok
@grow_arr_follow
%arr_ptr =l shr %arr_hdr, 3
%arr_hdr =l loadl %arr_ptr
jmp @grow_arr_chase
@grow_arr_ok
%grow_arr_is_array =w ceql %arr_ty, 0
jnz %grow_arr_is_array, @grow_arr_type_ok, @bad
@grow_arr_type_ok
%old_cap_l =l shr %arr_hdr, 8
%old_len_p =l add %arr_ptr, 8
%old_len_l =l loadl %old_len_p
%old_len_w =w copy %old_len_l
%new_ptr =l and %new_arr, -8
%old_vals =l add %arr_ptr, 16
%new_vals =l add %new_ptr, 16
%i_w =w copy 0
@copy_cond
%copy_more =w csltw %i_w, %old_len_w
jnz %copy_more, @copy_body, @copy_done
@copy_body
%i_l =l extsw %i_w
%i_off =l shl %i_l, 3
%old_ep =l add %old_vals, %i_off
%new_ep =l add %new_vals, %i_off
%ev =l loadl %old_ep
%ev_is_self =w ceql %ev, %arr_val
jnz %ev_is_self, @copy_self, @copy_store
@copy_self
storel %new_arr, %new_ep
jmp @copy_next
@copy_store
storel %ev, %new_ep
@copy_next
%i_w =w add %i_w, 1
jmp @copy_cond
@copy_done
storel %old_len_l, %old_len_p
%old_size =l shl %old_cap_l, 3
%old_size =l add %old_size, 16
%fwd =l shl %new_ptr, 3
%fwd =l or %fwd, 7
storel %fwd, %arr_ptr
%arr_size_p =l add %arr_ptr, 8
storel %old_size, %arr_size_p
%obj_slot_o =l shl %obj_slot, 3
%obj_slot_p =l add %fp2, %obj_slot_o
storel %new_arr, %obj_slot_p
%arr_val =l copy %new_arr
%arr_ptr =l copy %new_ptr
%arr_hdr =l loadl %arr_ptr
%len_p =l add %arr_ptr, 8
storel %old_len_l, %len_p
%len_l =l copy %old_len_l
%len_w =w copy %old_len_w
@set_item
%need_len =w csgew %idx_w, %len_w
jnz %need_len, @bump_len, @store_item
@bump_len
%next_len_w =w add %idx_w, 1
%next_len_l =l extsw %next_len_w
storel %next_len_l, %len_p
@store_item
%idx2_l =l extsw %idx_w
%idx2_off =l shl %idx2_l, 3
%vals_p =l add %arr_ptr, 16
%item_p =l add %vals_p, %idx2_off
storel %b, %item_p
ret %fp
@bad
call $cell_rt_disrupt(l %ctx)
@exc
ret 0
}`
@@ -937,12 +1184,133 @@ ${alloc_tail("%r")}
@entry
${sr("a", "%arr_slot")}
${sr("b", "%val_slot")}
%r =l call $cell_rt_push(l %ctx, l %a, l %b)
%ptag =l and %a, 7
%is_ptr =w ceql %ptag, 1
jnz %is_ptr, @arr_init, @bad
@arr_init
%arr_val =l copy %a
%arr_ptr =l and %arr_val, -8
%arr_hdr =l loadl %arr_ptr
@arr_chase
%arr_ty =l and %arr_hdr, 7
%arr_is_fwd =w ceql %arr_ty, 7
jnz %arr_is_fwd, @arr_follow, @arr_ok
@arr_follow
%arr_ptr =l shr %arr_hdr, 3
%arr_hdr =l loadl %arr_ptr
jmp @arr_chase
@arr_ok
%arr_is_array =w ceql %arr_ty, 0
jnz %arr_is_array, @arr_type_ok, @bad
@arr_type_ok
%arr_stone =l and %arr_hdr, 8
%arr_is_stone =w cnel %arr_stone, 0
jnz %arr_is_stone, @bad, @lens
@lens
%len_p =l add %arr_ptr, 8
%len_l =l loadl %len_p
%len_w =w copy %len_l
%cap_l =l shr %arr_hdr, 8
%cap_w =w copy %cap_l
%need_grow =w csgew %len_w, %cap_w
jnz %need_grow, @grow, @store_push
@grow
%new_cap_w =w copy %cap_w
%cap_zero =w ceqw %new_cap_w, 0
jnz %cap_zero, @grow_cap0, @grow_dbl
@grow_cap0
%new_cap_w =w copy 2
jmp @grow_alloc
@grow_dbl
%new_cap_w =w shl %new_cap_w, 1
%new_cap_neg =w csltw %new_cap_w, 0
jnz %new_cap_neg, @bad, @grow_alloc
@grow_alloc
%new_arr =l call $JS_NewArrayCap(l %ctx, w %new_cap_w)
%new_exc =w ceql %new_arr, 15
jnz %new_exc, @exc, @grow_refresh
@grow_refresh
%fp2 =l call $cell_rt_refresh_fp_checked(l %ctx)
jnz %fp2, @ok, @exc
@ok
${sw("w", "%fp2", "%arr_slot", "%r")}
ret %fp2
jnz %fp2, @grow_reload, @exc
@grow_reload
%fp =l copy %fp2
${sr("ga", "%arr_slot")}
${sr("gb", "%val_slot")}
%a =l copy %ga
%b =l copy %gb
%arr_val =l copy %a
%arr_ptr =l and %arr_val, -8
%arr_hdr =l loadl %arr_ptr
@grow_arr_chase
%arr_ty =l and %arr_hdr, 7
%arr_is_fwd =w ceql %arr_ty, 7
jnz %arr_is_fwd, @grow_arr_follow, @grow_arr_ok
@grow_arr_follow
%arr_ptr =l shr %arr_hdr, 3
%arr_hdr =l loadl %arr_ptr
jmp @grow_arr_chase
@grow_arr_ok
%grow_arr_is_array =w ceql %arr_ty, 0
jnz %grow_arr_is_array, @grow_arr_type_ok, @bad
@grow_arr_type_ok
%old_cap_l =l shr %arr_hdr, 8
%old_len_p =l add %arr_ptr, 8
%old_len_l =l loadl %old_len_p
%old_len_w =w copy %old_len_l
%new_ptr =l and %new_arr, -8
%old_vals =l add %arr_ptr, 16
%new_vals =l add %new_ptr, 16
%i_w =w copy 0
@copy_cond
%copy_more =w csltw %i_w, %old_len_w
jnz %copy_more, @copy_body, @copy_done
@copy_body
%i_l =l extsw %i_w
%i_off =l shl %i_l, 3
%old_ep =l add %old_vals, %i_off
%new_ep =l add %new_vals, %i_off
%ev =l loadl %old_ep
%ev_is_self =w ceql %ev, %arr_val
jnz %ev_is_self, @copy_self, @copy_store
@copy_self
storel %new_arr, %new_ep
jmp @copy_next
@copy_store
storel %ev, %new_ep
@copy_next
%i_w =w add %i_w, 1
jmp @copy_cond
@copy_done
storel %old_len_l, %old_len_p
%old_size =l shl %old_cap_l, 3
%old_size =l add %old_size, 16
%fwd =l shl %new_ptr, 3
%fwd =l or %fwd, 7
storel %fwd, %arr_ptr
%arr_size_p =l add %arr_ptr, 8
storel %old_size, %arr_size_p
%arr_slot_o =l shl %arr_slot, 3
%arr_slot_p =l add %fp2, %arr_slot_o
storel %new_arr, %arr_slot_p
%arr_val =l copy %new_arr
%arr_ptr =l copy %new_ptr
%arr_hdr =l loadl %arr_ptr
%len_p =l add %arr_ptr, 8
storel %old_len_l, %len_p
%len_l =l copy %old_len_l
%len_w =w copy %old_len_w
@store_push
%idx_l =l extsw %len_w
%idx_off =l shl %idx_l, 3
%vals_p =l add %arr_ptr, 16
%item_p =l add %vals_p, %idx_off
storel %b, %item_p
%next_len_w =w add %len_w, 1
%next_len_l =l extsw %next_len_w
storel %next_len_l, %len_p
ret %fp
@bad
call $cell_rt_disrupt(l %ctx)
@exc
ret 0
}`
@@ -951,8 +1319,51 @@ ${sw("w", "%fp2", "%arr_slot", "%r")}
h[] = `export function l $__pop_ss(l %ctx, l %fp, l %dest, l %arr_slot) {
@entry
${sr("a", "%arr_slot")}
%r =l call $cell_rt_pop(l %ctx, l %a)
${alloc_tail("%r")}
%ptag =l and %a, 7
%is_ptr =w ceql %ptag, 1
jnz %is_ptr, @arr_init, @bad
@arr_init
%arr_ptr =l and %a, -8
%arr_hdr =l loadl %arr_ptr
@arr_chase
%arr_ty =l and %arr_hdr, 7
%arr_is_fwd =w ceql %arr_ty, 7
jnz %arr_is_fwd, @arr_follow, @arr_ok
@arr_follow
%arr_ptr =l shr %arr_hdr, 3
%arr_hdr =l loadl %arr_ptr
jmp @arr_chase
@arr_ok
%arr_is_array =w ceql %arr_ty, 0
jnz %arr_is_array, @arr_type_ok, @bad
@arr_type_ok
%arr_stone =l and %arr_hdr, 8
%arr_is_stone =w cnel %arr_stone, 0
jnz %arr_is_stone, @bad, @len_chk
@len_chk
%len_p =l add %arr_ptr, 8
%len_l =l loadl %len_p
%len_w =w copy %len_l
%empty =w ceqw %len_w, 0
jnz %empty, @ret_null, @do_pop
@do_pop
%last_w =w sub %len_w, 1
%last_l =l extsw %last_w
%last_off =l shl %last_l, 3
%vals_p =l add %arr_ptr, 16
%item_p =l add %vals_p, %last_off
%r =l loadl %item_p
storel ${text(qbe.js_null)}, %item_p
%new_len_l =l extsw %last_w
storel %new_len_l, %len_p
${sw("w", "%fp", "%dest", "%r")}
ret %fp
@ret_null
${sw("w", "%fp", "%dest", text(qbe.js_null))}
ret %fp
@bad
call $cell_rt_disrupt(l %ctx)
ret 0
}`
// length(ctx, fp, dest, src)

View File

@@ -273,6 +273,7 @@ void script_startup(cell_rt *prt)
JSContext *js = JS_NewContext(g_runtime);
JS_SetContextOpaque(js, prt);
JS_SetGCScanExternal(js, actor_gc_scan);
prt->context = js;
/* Set per-actor heap memory limit */
@@ -574,6 +575,7 @@ int cell_init(int argc, char **argv)
cli_rt->context = ctx;
JS_SetContextOpaque(ctx, cli_rt);
JS_SetGCScanExternal(ctx, actor_gc_scan);
JS_AddGCRef(ctx, &cli_rt->idx_buffer_ref);
JS_AddGCRef(ctx, &cli_rt->on_exception_ref);

View File

@@ -100,6 +100,8 @@ void exit_handler(void);
void actor_loop();
void actor_initialize(void);
void actor_free(cell_rt *actor);
void actor_gc_scan(JSContext *ctx, uint8_t *fb, uint8_t *fe,
uint8_t *tb, uint8_t **tf, uint8_t *te);
int scheduler_actor_count(void);
void scheduler_enable_quiescence(void);

View File

@@ -925,7 +925,6 @@ JSValue js_new_register_function(JSContext *ctx, JSCodeRegister *code, JSValue e
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);
@@ -1207,8 +1206,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 && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) {
JSCodeRegister *code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code) {
JSCodeRegister *code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
file = code->filename_cstr;
func_name = code->name_cstr;
if (!is_first)
@@ -1244,7 +1243,7 @@ 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 = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
env = fn->u.cell.env_record;
pc = ctx->suspended_pc;
result = JS_NULL;
@@ -1440,28 +1439,34 @@ vm_dispatch:
VM_CASE(MACH_LOADK): {
int bx = MACH_GET_Bx(instr);
if (bx < (int)code->cpool_count)
frame->slots[a] = code->cpool[bx];
VM_BREAK();
}
VM_CASE(MACH_LOADI):
frame->slots[a] = JS_NewInt32(ctx, MACH_GET_sBx(instr));
VM_BREAK();
VM_CASE(MACH_LOADNULL):
frame->slots[a] = JS_NULL;
VM_BREAK();
VM_CASE(MACH_LOADTRUE):
frame->slots[a] = JS_TRUE;
VM_BREAK();
VM_CASE(MACH_LOADFALSE):
frame->slots[a] = JS_FALSE;
VM_BREAK();
VM_CASE(MACH_MOVE):
frame->slots[a] = frame->slots[b];
VM_BREAK();
@@ -2014,6 +2019,18 @@ vm_dispatch:
fn = JS_VALUE_GET_FUNCTION(target->function);
target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
}
{
uint64_t tcap = objhdr_cap56(target->header);
if ((unsigned)c >= tcap) {
fprintf(stderr, "MACH_SETUP OOB: slot=%d >= target_cap=%llu depth=%d "
"cur_fn=%s (%s) pc=%u\n",
c, (unsigned long long)tcap, depth,
code->name_cstr ? code->name_cstr : "?",
code->filename_cstr ? code->filename_cstr : "?", pc - 1);
fflush(stderr);
VM_BREAK();
}
}
target->slots[c] = frame->slots[a];
VM_BREAK();
}
@@ -2107,9 +2124,9 @@ vm_dispatch:
const char *callee_file = "?";
{
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(frame->function);
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;
if (callee_fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code) {
if (JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code->name_cstr) callee_name = JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code->name_cstr;
if (JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code->filename_cstr) callee_file = JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.reg.code->filename_cstr;
}
}
#endif
@@ -2119,11 +2136,12 @@ 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 = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
env = fn->u.cell.env_record;
pc = ret_info >> 16;
int ret_slot = ret_info & 0xFFFF;
if (ret_slot != 0xFFFF) {
#ifdef VALIDATE_GC
if (JS_IsPtr(result)) {
void *rp = JS_VALUE_GET_PTR(result);
@@ -2152,11 +2170,14 @@ 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 = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->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;
if (ret_slot != 0xFFFF) {
frame->slots[ret_slot] = result;
}
}
VM_BREAK();
@@ -2178,6 +2199,7 @@ vm_dispatch:
VM_CASE(MACH_CLOSURE): {
int bx = MACH_GET_Bx(instr);
if ((uint32_t)bx < code->func_count) {
JSCodeRegister *fn_code = code->functions[bx];
/* Read env fresh from frame->function — C local can be stale */
@@ -2603,7 +2625,7 @@ vm_dispatch:
goto disrupt;
if (fn->kind == JS_FUNC_KIND_REGISTER) {
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
int current_slots = (int)objhdr_cap56(frame->header);
if (fn_code->nr_slots <= current_slots) {
@@ -2655,7 +2677,7 @@ 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 = JS_VALUE_GET_CODE(ret_fn->u.cell.code)->u.reg.code;
code = JS_VALUE_GET_CODE(FN_READ_CODE(ret_fn))->u.reg.code;
env = ret_fn->u.cell.env_record;
pc = ret_info >> 16;
int ret_slot = ret_info & 0xFFFF;
@@ -2708,7 +2730,7 @@ vm_dispatch:
uint32_t frame_pc = pc;
for (;;) {
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->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.cell.env_record;

View File

@@ -779,6 +779,22 @@ static int cell_check_call_arity(JSContext *ctx, JSFunction *fn, int argc) {
return 1;
}
static inline void cell_copy_args_0_4(JSValue *fp, JSValue *argv, int copy) {
/* fp[0] is `this`; copy args into fp[1..4] */
switch (copy) {
case 4: fp[4] = argv[3];
case 3: fp[3] = argv[2];
case 2: fp[2] = argv[1];
case 1: fp[1] = argv[0];
case 0: break;
default: break;
}
}
static inline void cell_sync_dl_from_native_fn(NativeRTState *st, JSFunction *fn) {
st->current_dl_handle = JS_VALUE_GET_CODE(fn->u.cell.code)->u.native.dl_handle;
}
/* Entry point called from JS_CallInternal / JS_Call / MACH_INVOKE
for JS_FUNC_KIND_NATIVE functions. */
JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
@@ -786,11 +802,12 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
NativeRTState *st = native_state(ctx);
if (!st) return JS_EXCEPTION;
JSFunction *f = JS_VALUE_GET_FUNCTION(func_obj);
cell_compiled_fn fn = (cell_compiled_fn)JS_VALUE_GET_CODE(f->u.cell.code)->u.native.fn_ptr;
int nr_slots = JS_VALUE_GET_CODE(f->u.cell.code)->u.native.nr_slots;
JSCode *f_code = JS_VALUE_GET_CODE(FN_READ_CODE(f));
cell_compiled_fn fn = (cell_compiled_fn)f_code->u.native.fn_ptr;
int nr_slots = f_code->u.native.nr_slots;
int arity = f->length;
void *prev_dl_handle = st->current_dl_handle;
st->current_dl_handle = JS_VALUE_GET_CODE(f->u.cell.code)->u.native.dl_handle;
st->current_dl_handle = f_code->u.native.dl_handle;
#define RETURN_DISPATCH(v) \
do { \
@@ -822,8 +839,14 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
fp[0] = this_obj;
int copy = (argc < arity) ? argc : arity;
if (copy < 0) copy = argc; /* variadic: copy all */
for (int i = 0; i < copy && i < nr_slots - 1; i++)
fp[1 + i] = argv[i];
if (copy > nr_slots - 1)
copy = nr_slots - 1;
if (unlikely(copy > 4)) {
JS_RaiseDisrupt(ctx, "native calls support at most 4 arguments");
RETURN_DISPATCH(JS_EXCEPTION);
}
if (copy > 0 && argv)
cell_copy_args_0_4(fp, argv, copy);
/* Link function to frame for closure access */
JSFrameRegister *frame = (JSFrameRegister *)((char *)fp - offsetof(JSFrameRegister, slots));
@@ -841,7 +864,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
if (JS_IsFunction(frame->function)) {
JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function);
if (cur_fn->kind == JS_FUNC_KIND_NATIVE)
st->current_dl_handle = JS_VALUE_GET_CODE(cur_fn->u.cell.code)->u.native.dl_handle;
st->current_dl_handle = JS_VALUE_GET_CODE(FN_READ_CODE(cur_fn))->u.native.dl_handle;
}
JSValue result = fn(ctx, fp);
@@ -874,7 +897,8 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
JS_RaiseDisrupt(ctx, "not a function");
/* Resume caller with exception pending */
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr;
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, exc_fn);
JS_PopGCRef(ctx, &callee_ref);
continue;
}
@@ -882,14 +906,15 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(callee_fn_val);
if (!cell_check_call_arity(ctx, callee_fn, callee_argc)) {
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr;
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, exc_fn);
JS_PopGCRef(ctx, &callee_ref);
continue;
}
if (callee_fn->kind == JS_FUNC_KIND_NATIVE) {
/* Native-to-native call — no C stack growth */
cell_compiled_fn callee_ptr = (cell_compiled_fn)JS_VALUE_GET_CODE(callee_fn->u.cell.code)->u.native.fn_ptr;
cell_compiled_fn callee_ptr = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(callee_fn))->u.native.fn_ptr;
if (pending_is_tail) {
/* Tail call: replace current frame with the prepared callee frame. */
@@ -910,6 +935,7 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(aot_gc_ref_at(st, st->aot_depth - 1)->val);
fp = (JSValue *)frame->slots;
fn = callee_ptr;
cell_sync_dl_from_native_fn(st, callee_fn);
} else {
/* Regular call: link caller and push prepared callee frame. */
int ret_info = JS_VALUE_GET_INT(frame->address);
@@ -930,13 +956,15 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
fp = (JSValue *)frame->slots;
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr;
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, exc_fn);
JS_PopGCRef(ctx, &callee_ref);
continue;
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(aot_gc_ref_at(st, st->aot_depth - 1)->val);
fp = (JSValue *)frame->slots;
fn = callee_ptr;
cell_sync_dl_from_native_fn(st, callee_fn);
}
} else {
/* Non-native callee (C function, register VM, etc.) —
@@ -967,7 +995,8 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
/* fn and fp still point to the calling native function's frame.
Just resume it — it will detect JS_EXCEPTION in the return slot. */
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_fn->u.cell.code)->u.native.fn_ptr;
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, exc_fn);
JS_PopGCRef(ctx, &callee_ref);
continue;
}
@@ -998,7 +1027,8 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
fp[ret_slot] = ret;
/* Resume caller */
JSFunction *caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(caller_fn->u.cell.code)->u.native.fn_ptr;
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(caller_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, caller_fn);
} else {
/* Regular call: store result and resume current function */
int ret_info = JS_VALUE_GET_INT(frame->address);
@@ -1007,7 +1037,8 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
fp[ret_slot] = ret;
/* fn stays the same — we resume the same function at next segment */
JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(cur_fn->u.cell.code)->u.native.fn_ptr;
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(cur_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, cur_fn);
}
}
JS_PopGCRef(ctx, &callee_ref);
@@ -1040,7 +1071,8 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
fp[ret_slot] = JS_EXCEPTION;
JSFunction *exc_caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(exc_caller_fn->u.cell.code)->u.native.fn_ptr;
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_caller_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, exc_caller_fn);
continue;
}
@@ -1064,7 +1096,8 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
fp[ret_slot] = result;
JSFunction *caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(caller_fn->u.cell.code)->u.native.fn_ptr;
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(caller_fn))->u.native.fn_ptr;
cell_sync_dl_from_native_fn(st, caller_fn);
continue;
}
@@ -1149,8 +1182,8 @@ JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) {
}
int nr_slots = (int)nargs + 2;
JSFunction *f = JS_VALUE_GET_FUNCTION(fn);
if (f->kind == JS_FUNC_KIND_NATIVE && JS_VALUE_GET_CODE(f->u.cell.code)->u.native.nr_slots > nr_slots)
nr_slots = JS_VALUE_GET_CODE(f->u.cell.code)->u.native.nr_slots;
if (f->kind == JS_FUNC_KIND_NATIVE && JS_VALUE_GET_CODE(FN_READ_CODE(f))->u.native.nr_slots > nr_slots)
nr_slots = JS_VALUE_GET_CODE(FN_READ_CODE(f))->u.native.nr_slots;
JSFrameRegister *new_frame = alloc_frame_register(ctx, nr_slots);
if (!new_frame) return JS_EXCEPTION;
new_frame->function = fn;

View File

@@ -118,6 +118,7 @@ void *js_mallocz (JSContext *ctx, size_t size);
#endif
#ifdef HAVE_ASAN
#include <sanitizer/asan_interface.h>
static struct JSContext *__asan_js_ctx;
#endif
@@ -758,6 +759,11 @@ struct JSContext {
JSValue (*compile_regexp) (JSContext *ctx, JSValue pattern, JSValue flags);
void *user_opaque;
/* GC callback to scan external C-side roots (actor letters, timers) */
void (*gc_scan_external)(JSContext *ctx,
uint8_t *from_base, uint8_t *from_end,
uint8_t *to_base, uint8_t **to_free, uint8_t *to_end);
js_hook trace_hook;
int trace_type;
void *trace_data;
@@ -1001,6 +1007,8 @@ typedef struct JSFunction {
} u;
} JSFunction;
#define FN_READ_CODE(fn) ((fn)->u.cell.code)
/* ============================================================
Context-Neutral Module Format (Phase 2+)
Struct definitions are in quickjs.h

View File

@@ -330,6 +330,12 @@ JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size);
void JS_FreeContext (JSContext *s);
void *JS_GetContextOpaque (JSContext *ctx);
void JS_SetContextOpaque (JSContext *ctx, void *opaque);
typedef void (*JS_GCScanFn)(JSContext *ctx,
uint8_t *from_base, uint8_t *from_end,
uint8_t *to_base, uint8_t **to_free, uint8_t *to_end);
void JS_SetGCScanExternal(JSContext *ctx, JS_GCScanFn fn);
void JS_SetActorSym (JSContext *ctx, JSValue sym);
JSValue JS_GetActorSym (JSContext *ctx);
JSRuntime *JS_GetRuntime (JSContext *ctx);

View File

@@ -52,8 +52,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 && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) {
JSCodeRegister *code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code) {
JSCodeRegister *code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
file = code->filename_cstr;
name = code->name_cstr;
if (!first)
@@ -149,7 +149,10 @@ int JS_IsPretext (JSValue v) {
}
JSValue *JS_PushGCRef (JSContext *ctx, JSGCRef *ref) {
assert(ref != ctx->top_gc_ref && "JS_ROOT used in a loop — same address pushed twice");
if (ref == ctx->top_gc_ref) {
fprintf(stderr, "[warn] JS_PushGCRef duplicate top ref (non-fatal)\n");
return &ref->val;
}
ref->prev = ctx->top_gc_ref;
ctx->top_gc_ref = ref;
ref->val = JS_NULL;
@@ -201,7 +204,10 @@ JSValue JS_PopGCRef (JSContext *ctx, JSGCRef *ref) {
}
JSValue *JS_AddGCRef (JSContext *ctx, JSGCRef *ref) {
assert(ref != ctx->last_gc_ref && "JS_AddGCRef: same address added twice — cycle in GC ref list");
if (ref == ctx->last_gc_ref) {
fprintf(stderr, "[warn] JS_AddGCRef duplicate tail ref (non-fatal)\n");
return &ref->val;
}
ref->prev = ctx->last_gc_ref;
ctx->last_gc_ref = ref;
ref->val = JS_NULL;
@@ -1370,14 +1376,14 @@ JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *f
/* Frame shortening: returned frames (caller == JS_NULL) only need
[this][args][closure_locals] — shrink during copy. */
if (type == OBJ_FRAME) {
if (0 && type == OBJ_FRAME) {
JSFrame *f = (JSFrame *)hdr_ptr;
if (JS_IsNull (f->caller) && JS_IsPtr (f->function)) {
/* fn may be forwarded, but kind (offset 18) and u.cell.code (offset 24)
are past the 16 bytes overwritten by fwd+size. */
JSFunction *fn = (JSFunction *)JS_VALUE_GET_PTR (f->function);
if (fn->kind == JS_FUNC_KIND_REGISTER) {
JSCode *jc = (JSCode *)JS_VALUE_GET_PTR (fn->u.cell.code);
JSCode *jc = (JSCode *)JS_VALUE_GET_PTR (FN_READ_CODE(fn));
if (jc && jc->kind == JS_CODE_KIND_REGISTER && jc->u.reg.code
&& jc->u.reg.code->nr_close_slots > 0) {
uint16_t cs = 1 + jc->u.reg.code->arity + jc->u.reg.code->nr_close_slots;
@@ -1501,10 +1507,11 @@ 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 && 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;
if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code) {
JSCodeRegister *_vc = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
if (_vc->name_cstr) fname = _vc->name_cstr;
if (_vc->filename_cstr) ffile = _vc->filename_cstr;
fnslots = _vc->nr_slots;
}
}
}
@@ -1610,8 +1617,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 && 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;
if (fnp->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(fnp))->u.reg.code && JS_VALUE_GET_CODE(FN_READ_CODE(fnp))->u.reg.code->name_cstr)
fn_name = JS_VALUE_GET_CODE(FN_READ_CODE(fnp))->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",
@@ -1708,6 +1715,10 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
}
}
/* Scan external C-side roots (actor letters, timers) */
if (ctx->gc_scan_external)
ctx->gc_scan_external(ctx, from_base, from_end, to_base, &to_free, to_end);
/* Cheney scan: scan copied objects to find more references */
uint8_t *scan = to_base;
#ifdef DUMP_GC_DETAIL
@@ -1821,7 +1832,6 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
}
}
/* Return old block (in poison mode, just poison it and leak) */
heap_block_free (rt, from_base, old_heap_size);
/* Update context with new block */
@@ -2147,6 +2157,10 @@ void JS_SetContextOpaque (JSContext *ctx, void *opaque) {
ctx->user_opaque = opaque;
}
void JS_SetGCScanExternal(JSContext *ctx, JS_GCScanFn fn) {
ctx->gc_scan_external = fn;
}
void JS_SetActorSym (JSContext *ctx, JSValue sym) {
ctx->actor_sym = sym;
}
@@ -4891,7 +4905,7 @@ 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, JS_VALUE_GET_CODE(f->u.cell.code)->u.reg.code, this_obj, argc, argv,
return JS_CallRegisterVM (ctx, JS_VALUE_GET_CODE(FN_READ_CODE(f))->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);
@@ -4924,7 +4938,7 @@ 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, JS_VALUE_GET_CODE(f->u.cell.code)->u.reg.code, this_obj, argc, argv,
return JS_CallRegisterVM (ctx, JS_VALUE_GET_CODE(FN_READ_CODE(f))->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);
@@ -10522,6 +10536,8 @@ static JSValue js_cell_pop (JSContext *ctx, JSValue this_val, int argc, JSValue
if (!JS_IsArray (obj)) return JS_NULL;
JSArray *arr = JS_VALUE_GET_ARRAY (obj);
if (objhdr_s (arr->mist_hdr))
return JS_RaiseDisrupt (ctx, "cannot pop from a stoned array");
if (arr->len == 0) return JS_NULL;
@@ -11692,8 +11708,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 && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) {
JSCodeRegister *code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code) {
JSCodeRegister *code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->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;
@@ -11752,8 +11768,8 @@ void JS_CrashPrintStack(JSContext *ctx) {
if (!JS_IsFunction(frame->function)) break;
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
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;
if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code) {
JSCodeRegister *code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
uint32_t pc = is_first ? cur_pc : (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16);
const char *name = code->name_cstr ? code->name_cstr : "<anonymous>";

View File

@@ -9,6 +9,7 @@
#include "stb_ds.h"
#include "cell.h"
#include "quickjs-internal.h"
#include "cell_internal.h"
#ifdef _WIN32
@@ -857,6 +858,36 @@ ENDTURN_SLOW:
pthread_mutex_unlock(actor->mutex);
}
/* GC callback: scan actor's letters and timers so the copying GC
can relocate JSValues stored in C-side data structures. */
void actor_gc_scan(JSContext *ctx,
uint8_t *from_base, uint8_t *from_end,
uint8_t *to_base, uint8_t **to_free, uint8_t *to_end)
{
cell_rt *actor = JS_GetContextOpaque(ctx);
if (!actor) return;
/* Lock msg_mutex to synchronize with the timer thread, which reads
timers and writes letters under the same lock. */
pthread_mutex_lock(actor->msg_mutex);
for (int i = 0; i < arrlen(actor->letters); i++) {
if (actor->letters[i].type == LETTER_CALLBACK) {
actor->letters[i].callback = gc_copy_value(ctx,
actor->letters[i].callback,
from_base, from_end, to_base, to_free, to_end);
}
}
for (int i = 0; i < hmlen(actor->timers); i++) {
actor->timers[i].value = gc_copy_value(ctx,
actor->timers[i].value,
from_base, from_end, to_base, to_free, to_end);
}
pthread_mutex_unlock(actor->msg_mutex);
}
void actor_clock(cell_rt *actor, JSValue fn)
{
letter l;

View File

@@ -148,6 +148,21 @@ var streamline = function(ir, log) {
slot_types[instr[1]] = src_type != null ? src_type : T_UNKNOWN
return null
}
if (op == "load_index") {
slot_types[instr[2]] = T_ARRAY
slot_types[instr[3]] = T_INT
} else if (op == "store_index") {
slot_types[instr[1]] = T_ARRAY
slot_types[instr[3]] = T_INT
} else if (op == "load_field") {
slot_types[instr[2]] = T_RECORD
} else if (op == "store_field") {
slot_types[instr[1]] = T_RECORD
} else if (op == "push") {
slot_types[instr[1]] = T_ARRAY
} else if (op == "pop") {
slot_types[instr[2]] = T_ARRAY
}
rule = write_rules[op]
if (rule != null) {
typ = rule[1]
@@ -826,26 +841,32 @@ var streamline = function(ir, log) {
// Dynamic access reduction
if (op == "load_dynamic") {
old_op = op
if (slot_is(slot_types, instr[3], T_TEXT)) {
if (slot_is(slot_types, instr[2], T_RECORD) && slot_is(slot_types, instr[3], T_TEXT)) {
instr[0] = "load_field"
if (events != null) {
events[] = {
event: "rewrite",
pass: "eliminate_type_checks",
rule: "dynamic_to_field",
rule: "dynamic_record_to_field",
at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[instr[3]]}
why: {
object_slot: instr[2], object_type: slot_types[instr[2]],
key_slot: instr[3], key_type: slot_types[instr[3]]
}
}
}
} else if (slot_is(slot_types, instr[3], T_INT)) {
} else if (slot_is(slot_types, instr[2], T_ARRAY) && slot_is(slot_types, instr[3], T_INT)) {
instr[0] = "load_index"
if (events != null) {
events[] = {
event: "rewrite",
pass: "eliminate_type_checks",
rule: "dynamic_to_index",
rule: "dynamic_array_to_index",
at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[instr[3]]}
why: {
object_slot: instr[2], object_type: slot_types[instr[2]],
key_slot: instr[3], key_type: slot_types[instr[3]]
}
}
}
}
@@ -855,26 +876,32 @@ var streamline = function(ir, log) {
}
if (op == "store_dynamic") {
old_op = op
if (slot_is(slot_types, instr[3], T_TEXT)) {
if (slot_is(slot_types, instr[1], T_RECORD) && slot_is(slot_types, instr[3], T_TEXT)) {
instr[0] = "store_field"
if (events != null) {
events[] = {
event: "rewrite",
pass: "eliminate_type_checks",
rule: "dynamic_to_field",
rule: "dynamic_record_to_field",
at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[instr[3]]}
why: {
object_slot: instr[1], object_type: slot_types[instr[1]],
key_slot: instr[3], key_type: slot_types[instr[3]]
}
}
}
} else if (slot_is(slot_types, instr[3], T_INT)) {
} else if (slot_is(slot_types, instr[1], T_ARRAY) && slot_is(slot_types, instr[3], T_INT)) {
instr[0] = "store_index"
if (events != null) {
events[] = {
event: "rewrite",
pass: "eliminate_type_checks",
rule: "dynamic_to_index",
rule: "dynamic_array_to_index",
at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[instr[3]]}
why: {
object_slot: instr[1], object_type: slot_types[instr[1]],
key_slot: instr[3], key_type: slot_types[instr[3]]
}
}
}
}