Compare commits
7 Commits
optimize_m
...
fix_aot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7e3c0803c | ||
|
|
071aa33153 | ||
|
|
d041c49972 | ||
|
|
eadad194be | ||
|
|
81c88f9439 | ||
|
|
20c2576fa7 | ||
|
|
65fa37cc03 |
47
CLAUDE.md
47
CLAUDE.md
@@ -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
302
benches/micro_core.cm
Normal 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)
|
||||
}
|
||||
}
|
||||
12
build.cm
12
build.cm
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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);
|
||||
)
|
||||
|
||||
|
||||
157
internal/shop.cm
157
internal/shop.cm
@@ -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
13
link.ce
@@ -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()
|
||||
|
||||
443
qbe_emit.cm
443
qbe_emit.cm
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user