Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1b41d5ecf | ||
|
|
eb19b18594 | ||
|
|
e203700d37 | ||
|
|
c56444556d | ||
|
|
080e675d18 | ||
|
|
957b964d9d | ||
|
|
fa9d2609b1 | ||
|
|
e38c2f07bf | ||
|
|
ecc1777b24 | ||
|
|
1cfd5b8133 | ||
|
|
9c1141f408 | ||
|
|
696cca530b | ||
|
|
c92a4087a6 | ||
|
|
01637c49b0 | ||
|
|
f9e660ebaa | ||
|
|
4f8fada57d | ||
|
|
adcaa92bea | ||
|
|
fc36707b39 | ||
|
|
7cb8ce7945 | ||
|
|
bb7997a751 | ||
|
|
327b990442 | ||
|
|
51c0a0b306 | ||
|
|
8ac82016dd | ||
|
|
d0bf757d91 | ||
|
|
c77f1f8639 | ||
|
|
2b877e6b0c | ||
|
|
3d4c0ec3d3 | ||
|
|
33d9013409 | ||
|
|
c2f57d1dae | ||
|
|
7bd17c6476 | ||
|
|
5ac1620b48 | ||
|
|
124c9536b4 | ||
|
|
940807c37a | ||
|
|
70f560550f | ||
|
|
060a494f47 | ||
|
|
6812d3edbc | ||
|
|
4da15d2a3e | ||
|
|
a34566a0c1 | ||
|
|
9f7d861932 | ||
|
|
c5536697ff | ||
|
|
d066ab03cd | ||
|
|
9c1cb43c7d | ||
|
|
99fb575c9c | ||
|
|
193991c532 | ||
|
|
76552c6854 | ||
|
|
f26b6e853d | ||
|
|
94c28f0e17 | ||
|
|
a18584afd3 | ||
|
|
3f6cfad7ef | ||
|
|
b03edb0d90 | ||
|
|
62440d3ed6 | ||
|
|
4edc4b7cc5 | ||
|
|
012b507415 | ||
|
|
ee6398ada9 | ||
|
|
173438e8bc | ||
|
|
7ac5ac63d2 | ||
|
|
a05f180356 | ||
|
|
d88692cd30 | ||
|
|
b0ac5de7e2 | ||
|
|
1d4fc11772 | ||
|
|
7372b80e07 | ||
|
|
d27047dd82 | ||
|
|
8e96379377 | ||
|
|
8f415fea80 | ||
|
|
cec0b99207 | ||
|
|
2d4645da9c | ||
|
|
017b63ba80 | ||
|
|
99fa86a09c | ||
|
|
6d6b53009f | ||
|
|
cc7fc6b667 | ||
|
|
bbeb757e40 | ||
|
|
2ac446f7cf | ||
|
|
517bd64275 | ||
|
|
7d0c96f328 | ||
|
|
e7ed6bd8b2 | ||
|
|
700b640cf1 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,7 +1,12 @@
|
|||||||
.git/
|
.git/
|
||||||
.obj/
|
.obj/
|
||||||
website/public/
|
website/public/
|
||||||
|
website/site/
|
||||||
website/.hugo_build.lock
|
website/.hugo_build.lock
|
||||||
|
.cache
|
||||||
|
.cell
|
||||||
|
cell
|
||||||
|
libcell_runtime*
|
||||||
bin/
|
bin/
|
||||||
build/
|
build/
|
||||||
*.zip
|
*.zip
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ The creator functions are **polymorphic** — behavior depends on argument types
|
|||||||
- `record(record, another)` — merge
|
- `record(record, another)` — merge
|
||||||
- `record(array_of_keys)` — create record from keys
|
- `record(array_of_keys)` — create record from keys
|
||||||
|
|
||||||
Other key intrinsics: `length()`, `stone()`, `is_stone()`, `print()`, `filter()`, `find()`, `reduce()`, `sort()`, `reverse()`, `some()`, `every()`, `starts_with()`, `ends_with()`, `meme()`, `proto()`, `isa()`, `splat()`, `apply()`, `extract()`, `replace()`, `search()`, `format()`, `lower()`, `upper()`, `trim()`
|
Other key intrinsics: `object()`, `length()`, `stone()`, `is_stone()`, `filter()`, `find()`, `reduce()`, `sort()`, `reverse()`, `some()`, `every()`, `starts_with()`, `ends_with()`, `meme()`, `proto()`, `isa()`, `splat()`, `apply()`, `extract()`, `replace()`, `search()`, `format()`, `lower()`, `upper()`, `trim()`
|
||||||
|
|
||||||
Sensory functions: `is_array()`, `is_text()`, `is_number()`, `is_object()`, `is_function()`, `is_null()`, `is_logical()`, `is_integer()`, `is_stone()`, etc.
|
Sensory functions: `is_array()`, `is_text()`, `is_number()`, `is_object()`, `is_function()`, `is_null()`, `is_logical()`, `is_integer()`, `is_stone()`, etc.
|
||||||
|
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -24,7 +24,7 @@ $(BUILD_DBG)/build.ninja:
|
|||||||
install: all $(CELL_SHOP)
|
install: all $(CELL_SHOP)
|
||||||
cp cell $(INSTALL_BIN)/cell
|
cp cell $(INSTALL_BIN)/cell
|
||||||
cp libcell_runtime.dylib $(INSTALL_LIB)/
|
cp libcell_runtime.dylib $(INSTALL_LIB)/
|
||||||
cp source/cell.h source/quickjs.h source/wota.h $(INSTALL_INC)/
|
cp source/cell.h $(INSTALL_INC)/
|
||||||
rm -rf $(CELL_SHOP)/packages/core
|
rm -rf $(CELL_SHOP)/packages/core
|
||||||
ln -s $(CURDIR) $(CELL_SHOP)/packages/core
|
ln -s $(CURDIR) $(CELL_SHOP)/packages/core
|
||||||
@echo "Installed cell to $(INSTALL_BIN) and $(INSTALL_LIB)"
|
@echo "Installed cell to $(INSTALL_BIN) and $(INSTALL_LIB)"
|
||||||
@@ -32,7 +32,7 @@ install: all $(CELL_SHOP)
|
|||||||
install_debug: debug $(CELL_SHOP)
|
install_debug: debug $(CELL_SHOP)
|
||||||
cp cell $(INSTALL_BIN)/cell
|
cp cell $(INSTALL_BIN)/cell
|
||||||
cp libcell_runtime.dylib $(INSTALL_LIB)/
|
cp libcell_runtime.dylib $(INSTALL_LIB)/
|
||||||
cp source/cell.h source/quickjs.h source/wota.h $(INSTALL_INC)/
|
cp source/cell.h $(INSTALL_INC)/
|
||||||
rm -rf $(CELL_SHOP)/packages/core
|
rm -rf $(CELL_SHOP)/packages/core
|
||||||
ln -s $(CURDIR) $(CELL_SHOP)/packages/core
|
ln -s $(CURDIR) $(CELL_SHOP)/packages/core
|
||||||
@echo "Installed cell (debug+asan) to $(INSTALL_BIN) and $(INSTALL_LIB)"
|
@echo "Installed cell (debug+asan) to $(INSTALL_BIN) and $(INSTALL_LIB)"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "quickjs.h"
|
|
||||||
#include "miniz.h"
|
|
||||||
#include "cell.h"
|
#include "cell.h"
|
||||||
|
#include "miniz.h"
|
||||||
|
|
||||||
static JSClassID js_reader_class_id;
|
static JSClassID js_reader_class_id;
|
||||||
static JSClassID js_writer_class_id;
|
static JSClassID js_writer_class_id;
|
||||||
@@ -319,7 +318,6 @@ JSValue js_reader_list(JSContext *js, JSValue self, int argc, JSValue *argv)
|
|||||||
|
|
||||||
JSValue filename = JS_NewString(js, file_stat.m_filename);
|
JSValue filename = JS_NewString(js, file_stat.m_filename);
|
||||||
if (JS_IsException(filename)) {
|
if (JS_IsException(filename)) {
|
||||||
JS_FreeValue(js, arr);
|
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
JS_SetPropertyNumber(js, arr, arr_index++, filename);
|
JS_SetPropertyNumber(js, arr, arr_index++, filename);
|
||||||
|
|||||||
162
audit.ce
162
audit.ce
@@ -4,90 +4,146 @@
|
|||||||
// cell audit Audit all packages
|
// cell audit Audit all packages
|
||||||
// cell audit <locator> Audit specific package
|
// cell audit <locator> Audit specific package
|
||||||
// cell audit . Audit current directory package
|
// cell audit . Audit current directory package
|
||||||
|
// cell audit --function-hoist [<locator>] Report function hoisting usage
|
||||||
//
|
//
|
||||||
// Compiles every script in the package(s) to check for errors.
|
// Compiles every script in the package(s) to check for errors.
|
||||||
// Continues past failures and reports all issues at the end.
|
// Continues past failures and reports all issues at the end.
|
||||||
|
|
||||||
var shop = use('internal/shop')
|
var shop = use('internal/shop')
|
||||||
var pkg = use('package')
|
var pkg = use('package')
|
||||||
|
var fd = use('fd')
|
||||||
|
|
||||||
var target_package = null
|
var target_package = null
|
||||||
|
var function_hoist = false
|
||||||
var i = 0
|
var i = 0
|
||||||
|
|
||||||
var run = function() {
|
var run = function() {
|
||||||
|
var packages = null
|
||||||
|
var tokenize_mod = null
|
||||||
|
var parse_mod = null
|
||||||
|
var hoist_files = 0
|
||||||
|
var hoist_refs = 0
|
||||||
|
var total_ok = 0
|
||||||
|
var total_errors = 0
|
||||||
|
var total_scripts = 0
|
||||||
|
var all_failures = []
|
||||||
|
var all_unresolved = []
|
||||||
|
var summary = null
|
||||||
|
|
||||||
for (i = 0; i < length(args); i++) {
|
for (i = 0; i < length(args); i++) {
|
||||||
if (args[i] == '--help' || args[i] == '-h') {
|
if (args[i] == '--help' || args[i] == '-h') {
|
||||||
log.console("Usage: cell audit [<locator>]")
|
log.console("Usage: cell audit [--function-hoist] [<locator>]")
|
||||||
log.console("")
|
log.console("")
|
||||||
log.console("Test-compile all .ce and .cm scripts in package(s).")
|
log.console("Test-compile all .ce and .cm scripts in package(s).")
|
||||||
log.console("Reports all errors without stopping at the first failure.")
|
log.console("Reports all errors without stopping at the first failure.")
|
||||||
|
log.console("")
|
||||||
|
log.console("Flags:")
|
||||||
|
log.console(" --function-hoist Report files that rely on function hoisting")
|
||||||
return
|
return
|
||||||
|
} else if (args[i] == '--function-hoist') {
|
||||||
|
function_hoist = true
|
||||||
} else if (!starts_with(args[i], '-')) {
|
} else if (!starts_with(args[i], '-')) {
|
||||||
target_package = args[i]
|
target_package = args[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve local paths
|
// Resolve local paths
|
||||||
if (target_package) {
|
if (target_package) {
|
||||||
target_package = shop.resolve_locator(target_package)
|
target_package = shop.resolve_locator(target_package)
|
||||||
}
|
}
|
||||||
|
|
||||||
var packages = null
|
if (target_package) {
|
||||||
var total_ok = 0
|
packages = [target_package]
|
||||||
var total_errors = 0
|
} else {
|
||||||
var total_scripts = 0
|
packages = shop.list_packages()
|
||||||
var all_failures = []
|
}
|
||||||
var all_unresolved = []
|
|
||||||
|
|
||||||
if (target_package) {
|
if (function_hoist) {
|
||||||
packages = [target_package]
|
tokenize_mod = use('tokenize')
|
||||||
} else {
|
parse_mod = use('parse')
|
||||||
packages = shop.list_packages()
|
|
||||||
}
|
|
||||||
|
|
||||||
arrfor(packages, function(p) {
|
arrfor(packages, function(p) {
|
||||||
var scripts = shop.get_package_scripts(p)
|
var scripts = shop.get_package_scripts(p)
|
||||||
if (length(scripts) == 0) return
|
var pkg_dir = shop.get_package_dir(p)
|
||||||
|
if (length(scripts) == 0) return
|
||||||
|
|
||||||
log.console("Auditing " + p + " (" + text(length(scripts)) + " scripts)...")
|
arrfor(scripts, function(script) {
|
||||||
var result = shop.build_package_scripts(p)
|
var src_path = pkg_dir + '/' + script
|
||||||
total_ok = total_ok + result.ok
|
var src = null
|
||||||
total_errors = total_errors + length(result.errors)
|
var tok_result = null
|
||||||
total_scripts = total_scripts + result.total
|
var ast = null
|
||||||
|
var scan = function() {
|
||||||
|
if (!fd.is_file(src_path)) return
|
||||||
|
src = text(fd.slurp(src_path))
|
||||||
|
tok_result = tokenize_mod(src, script)
|
||||||
|
ast = parse_mod(tok_result.tokens, src, script, tokenize_mod)
|
||||||
|
if (ast._hoisted_fns != null && length(ast._hoisted_fns) > 0) {
|
||||||
|
log.console(p + '/' + script + ":")
|
||||||
|
hoist_files = hoist_files + 1
|
||||||
|
arrfor(ast._hoisted_fns, function(ref) {
|
||||||
|
var msg = " " + ref.name
|
||||||
|
if (ref.line != null) msg = msg + " (ref line " + text(ref.line)
|
||||||
|
if (ref.decl_line != null) msg = msg + ", declared line " + text(ref.decl_line)
|
||||||
|
if (ref.line != null) msg = msg + ")"
|
||||||
|
log.console(msg)
|
||||||
|
hoist_refs = hoist_refs + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} disruption {
|
||||||
|
// skip files that fail to parse
|
||||||
|
}
|
||||||
|
scan()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
arrfor(result.errors, function(e) {
|
log.console("")
|
||||||
push(all_failures, p + ": " + e)
|
log.console("Summary: " + text(hoist_files) + " files with function hoisting, " + text(hoist_refs) + " total forward references")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
arrfor(packages, function(p) {
|
||||||
|
var scripts = shop.get_package_scripts(p)
|
||||||
|
var result = null
|
||||||
|
var resolution = null
|
||||||
|
if (length(scripts) == 0) return
|
||||||
|
|
||||||
|
log.console("Auditing " + p + " (" + text(length(scripts)) + " scripts)...")
|
||||||
|
result = shop.build_package_scripts(p)
|
||||||
|
total_ok = total_ok + result.ok
|
||||||
|
total_errors = total_errors + length(result.errors)
|
||||||
|
total_scripts = total_scripts + result.total
|
||||||
|
|
||||||
|
arrfor(result.errors, function(e) {
|
||||||
|
all_failures[] = p + ": " + e
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check use() resolution
|
||||||
|
resolution = shop.audit_use_resolution(p)
|
||||||
|
arrfor(resolution.unresolved, function(u) {
|
||||||
|
all_unresolved[] = p + '/' + u.script + ": use('" + u.module + "') cannot be resolved"
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check use() resolution
|
|
||||||
var resolution = shop.audit_use_resolution(p)
|
|
||||||
arrfor(resolution.unresolved, function(u) {
|
|
||||||
push(all_unresolved, p + '/' + u.script + ": use('" + u.module + "') cannot be resolved")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
log.console("")
|
|
||||||
if (length(all_failures) > 0) {
|
|
||||||
log.console("Failed scripts:")
|
|
||||||
arrfor(all_failures, function(f) {
|
|
||||||
log.console(" " + f)
|
|
||||||
})
|
|
||||||
log.console("")
|
log.console("")
|
||||||
}
|
if (length(all_failures) > 0) {
|
||||||
|
log.console("Failed scripts:")
|
||||||
|
arrfor(all_failures, function(f) {
|
||||||
|
log.console(" " + f)
|
||||||
|
})
|
||||||
|
log.console("")
|
||||||
|
}
|
||||||
|
|
||||||
if (length(all_unresolved) > 0) {
|
if (length(all_unresolved) > 0) {
|
||||||
log.console("Unresolved modules:")
|
log.console("Unresolved modules:")
|
||||||
arrfor(all_unresolved, function(u) {
|
arrfor(all_unresolved, function(u) {
|
||||||
log.console(" " + u)
|
log.console(" " + u)
|
||||||
})
|
})
|
||||||
log.console("")
|
log.console("")
|
||||||
}
|
}
|
||||||
|
|
||||||
var summary = "Audit complete: " + text(total_ok) + "/" + text(total_scripts) + " scripts compiled"
|
summary = "Audit complete: " + text(total_ok) + "/" + text(total_scripts) + " scripts compiled"
|
||||||
if (total_errors > 0) summary = summary + ", " + text(total_errors) + " failed"
|
if (total_errors > 0) summary = summary + ", " + text(total_errors) + " failed"
|
||||||
if (length(all_unresolved) > 0) summary = summary + ", " + text(length(all_unresolved)) + " unresolved use() calls"
|
if (length(all_unresolved) > 0) summary = summary + ", " + text(length(all_unresolved)) + " unresolved use() calls"
|
||||||
log.console(summary)
|
log.console(summary)
|
||||||
}
|
}
|
||||||
run()
|
run()
|
||||||
|
|
||||||
$stop()
|
|
||||||
|
|||||||
30
bench.ce
30
bench.ce
@@ -28,7 +28,7 @@ function strip_mode_flags() {
|
|||||||
} else if (a == '--compare') {
|
} else if (a == '--compare') {
|
||||||
bench_mode = "compare"
|
bench_mode = "compare"
|
||||||
} else {
|
} else {
|
||||||
push(filtered, a)
|
filtered[] = a
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
_args = filtered
|
_args = filtered
|
||||||
@@ -197,7 +197,7 @@ function collect_benches(package_name, specific_bench) {
|
|||||||
match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
|
match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
|
||||||
if (bench_name != match_base) return
|
if (bench_name != match_base) return
|
||||||
}
|
}
|
||||||
push(bench_files, f)
|
bench_files[] = f
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return bench_files
|
return bench_files
|
||||||
@@ -355,7 +355,7 @@ function run_single_bench(bench_fn, bench_name) {
|
|||||||
if (teardown_fn) teardown_fn(state)
|
if (teardown_fn) teardown_fn(state)
|
||||||
|
|
||||||
ns_per_op = is_batch ? duration / batch_size : duration
|
ns_per_op = is_batch ? duration / batch_size : duration
|
||||||
push(timings_per_op, ns_per_op)
|
timings_per_op[] = ns_per_op
|
||||||
} else {
|
} else {
|
||||||
start = os.now()
|
start = os.now()
|
||||||
if (is_batch) {
|
if (is_batch) {
|
||||||
@@ -366,7 +366,7 @@ function run_single_bench(bench_fn, bench_name) {
|
|||||||
duration = os.now() - start
|
duration = os.now() - start
|
||||||
|
|
||||||
ns_per_op = is_batch ? duration / batch_size : duration
|
ns_per_op = is_batch ? duration / batch_size : duration
|
||||||
push(timings_per_op, ns_per_op)
|
timings_per_op[] = ns_per_op
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,11 +442,11 @@ function load_bench_module(f, package_name, mode) {
|
|||||||
function collect_bench_fns(bench_mod) {
|
function collect_bench_fns(bench_mod) {
|
||||||
var benches = []
|
var benches = []
|
||||||
if (is_function(bench_mod)) {
|
if (is_function(bench_mod)) {
|
||||||
push(benches, {name: 'main', fn: bench_mod})
|
benches[] = {name: 'main', fn: bench_mod}
|
||||||
} else if (is_object(bench_mod)) {
|
} else if (is_object(bench_mod)) {
|
||||||
arrfor(array(bench_mod), function(k) {
|
arrfor(array(bench_mod), function(k) {
|
||||||
if (is_function(bench_mod[k]))
|
if (is_function(bench_mod[k]))
|
||||||
push(benches, {name: k, fn: bench_mod[k]})
|
benches[] = {name: k, fn: bench_mod[k]}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return benches
|
return benches
|
||||||
@@ -524,7 +524,7 @@ function run_benchmarks(package_name, specific_bench) {
|
|||||||
result = run_single_bench(b.fn, b.name)
|
result = run_single_bench(b.fn, b.name)
|
||||||
result.package = pkg_result.package
|
result.package = pkg_result.package
|
||||||
result.mode = bench_mode == "compare" ? "bytecode" : bench_mode
|
result.mode = bench_mode == "compare" ? "bytecode" : bench_mode
|
||||||
push(file_result.benchmarks, result)
|
file_result.benchmarks[] = result
|
||||||
pkg_result.total++
|
pkg_result.total++
|
||||||
|
|
||||||
log.console(` ${result.name}`)
|
log.console(` ${result.name}`)
|
||||||
@@ -538,7 +538,7 @@ function run_benchmarks(package_name, specific_bench) {
|
|||||||
nat_result = run_single_bench(native_benches[nat_b].fn, b.name)
|
nat_result = run_single_bench(native_benches[nat_b].fn, b.name)
|
||||||
nat_result.package = pkg_result.package
|
nat_result.package = pkg_result.package
|
||||||
nat_result.mode = "native"
|
nat_result.mode = "native"
|
||||||
push(file_result.benchmarks, nat_result)
|
file_result.benchmarks[] = nat_result
|
||||||
pkg_result.total++
|
pkg_result.total++
|
||||||
print_bench_result(nat_result, "native ")
|
print_bench_result(nat_result, "native ")
|
||||||
|
|
||||||
@@ -570,7 +570,7 @@ function run_benchmarks(package_name, specific_bench) {
|
|||||||
name: b.name,
|
name: b.name,
|
||||||
error: "benchmark disrupted"
|
error: "benchmark disrupted"
|
||||||
}
|
}
|
||||||
push(file_result.benchmarks, error_result)
|
file_result.benchmarks[] = error_result
|
||||||
pkg_result.total++
|
pkg_result.total++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -586,12 +586,12 @@ function run_benchmarks(package_name, specific_bench) {
|
|||||||
name: "load_module",
|
name: "load_module",
|
||||||
error: "error loading module"
|
error: "error loading module"
|
||||||
}
|
}
|
||||||
push(file_result.benchmarks, error_result)
|
file_result.benchmarks[] = error_result
|
||||||
pkg_result.total++
|
pkg_result.total++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length(file_result.benchmarks) > 0) {
|
if (length(file_result.benchmarks) > 0) {
|
||||||
push(pkg_result.files, file_result)
|
pkg_result.files[] = file_result
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -604,15 +604,15 @@ var packages = null
|
|||||||
|
|
||||||
if (all_pkgs) {
|
if (all_pkgs) {
|
||||||
if (testlib.is_valid_package('.')) {
|
if (testlib.is_valid_package('.')) {
|
||||||
push(all_results, run_benchmarks(null, null))
|
all_results[] = run_benchmarks(null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
packages = shop.list_packages()
|
packages = shop.list_packages()
|
||||||
arrfor(packages, function(p) {
|
arrfor(packages, function(p) {
|
||||||
push(all_results, run_benchmarks(p, null))
|
all_results[] = run_benchmarks(p, null)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
push(all_results, run_benchmarks(target_pkg, target_bench))
|
all_results[] = run_benchmarks(target_pkg, target_bench)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate totals
|
// Calculate totals
|
||||||
@@ -688,7 +688,7 @@ Total benchmarks: ${total_benches}
|
|||||||
var pkg_benches = []
|
var pkg_benches = []
|
||||||
arrfor(pkg_res.files, function(f) {
|
arrfor(pkg_res.files, function(f) {
|
||||||
arrfor(f.benchmarks, function(benchmark) {
|
arrfor(f.benchmarks, function(benchmark) {
|
||||||
push(pkg_benches, benchmark)
|
pkg_benches[] = benchmark
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
194
bench_native.ce
194
bench_native.ce
@@ -1,194 +0,0 @@
|
|||||||
// bench_native.ce — compare VM vs native execution speed
|
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
// cell --dev bench_native.ce <module.cm> [iterations]
|
|
||||||
//
|
|
||||||
// Compiles (if needed) and benchmarks a module via both VM and native dylib.
|
|
||||||
// Reports median/mean timing per benchmark + speedup ratio.
|
|
||||||
|
|
||||||
var os = use('internal/os')
|
|
||||||
var fd = use('fd')
|
|
||||||
|
|
||||||
if (length(args) < 1) {
|
|
||||||
log.bench('usage: cell --dev bench_native.ce <module.cm> [iterations]')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var file = args[0]
|
|
||||||
var name = file
|
|
||||||
if (ends_with(name, '.cm')) {
|
|
||||||
name = text(name, 0, length(name) - 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
var iterations = 11
|
|
||||||
if (length(args) > 1) {
|
|
||||||
iterations = number(args[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
def WARMUP = 3
|
|
||||||
|
|
||||||
var safe = replace(replace(name, '/', '_'), '-', '_')
|
|
||||||
var symbol = 'js_' + safe + '_use'
|
|
||||||
var dylib_path = './' + file + '.dylib'
|
|
||||||
|
|
||||||
// --- Statistics ---
|
|
||||||
|
|
||||||
var stat_sort = function(arr) {
|
|
||||||
return sort(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
var stat_median = function(arr) {
|
|
||||||
if (length(arr) == 0) return 0
|
|
||||||
var sorted = stat_sort(arr)
|
|
||||||
var mid = floor(length(arr) / 2)
|
|
||||||
if (length(arr) % 2 == 0) {
|
|
||||||
return (sorted[mid - 1] + sorted[mid]) / 2
|
|
||||||
}
|
|
||||||
return sorted[mid]
|
|
||||||
}
|
|
||||||
|
|
||||||
var stat_mean = function(arr) {
|
|
||||||
if (length(arr) == 0) return 0
|
|
||||||
var sum = reduce(arr, function(a, b) { return a + b })
|
|
||||||
return sum / length(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
var format_ns = function(ns) {
|
|
||||||
if (ns < 1000) return text(round(ns)) + 'ns'
|
|
||||||
if (ns < 1000000) return text(round(ns / 1000 * 100) / 100) + 'us'
|
|
||||||
if (ns < 1000000000) return text(round(ns / 1000000 * 100) / 100) + 'ms'
|
|
||||||
return text(round(ns / 1000000000 * 100) / 100) + 's'
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Collect benchmarks from module ---
|
|
||||||
|
|
||||||
var collect_benches = function(mod) {
|
|
||||||
var benches = []
|
|
||||||
var keys = null
|
|
||||||
var i = 0
|
|
||||||
var k = null
|
|
||||||
if (is_function(mod)) {
|
|
||||||
push(benches, {name: 'main', fn: mod})
|
|
||||||
} else if (is_object(mod)) {
|
|
||||||
keys = array(mod)
|
|
||||||
i = 0
|
|
||||||
while (i < length(keys)) {
|
|
||||||
k = keys[i]
|
|
||||||
if (is_function(mod[k])) {
|
|
||||||
push(benches, {name: k, fn: mod[k]})
|
|
||||||
}
|
|
||||||
i = i + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return benches
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Run one benchmark function ---
|
|
||||||
|
|
||||||
var run_bench = function(fn, label) {
|
|
||||||
var samples = []
|
|
||||||
var i = 0
|
|
||||||
var t1 = 0
|
|
||||||
var t2 = 0
|
|
||||||
|
|
||||||
// warmup
|
|
||||||
i = 0
|
|
||||||
while (i < WARMUP) {
|
|
||||||
fn(1)
|
|
||||||
i = i + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect samples
|
|
||||||
i = 0
|
|
||||||
while (i < iterations) {
|
|
||||||
t1 = os.now()
|
|
||||||
fn(1)
|
|
||||||
t2 = os.now()
|
|
||||||
push(samples, t2 - t1)
|
|
||||||
i = i + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: label,
|
|
||||||
median: stat_median(samples),
|
|
||||||
mean: stat_mean(samples)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Load VM module ---
|
|
||||||
|
|
||||||
log.bench('loading VM module: ' + file)
|
|
||||||
var vm_mod = use(name)
|
|
||||||
var vm_benches = collect_benches(vm_mod)
|
|
||||||
|
|
||||||
if (length(vm_benches) == 0) {
|
|
||||||
log.bench('no benchmarkable functions found in ' + file)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Load native module ---
|
|
||||||
|
|
||||||
var native_mod = null
|
|
||||||
var native_benches = []
|
|
||||||
var has_native = fd.is_file(dylib_path)
|
|
||||||
var lib = null
|
|
||||||
|
|
||||||
if (has_native) {
|
|
||||||
log.bench('loading native module: ' + dylib_path)
|
|
||||||
lib = os.dylib_open(dylib_path)
|
|
||||||
native_mod = os.dylib_symbol(lib, symbol)
|
|
||||||
native_benches = collect_benches(native_mod)
|
|
||||||
} else {
|
|
||||||
log.bench('no ' + dylib_path + ' found -- VM-only benchmarking')
|
|
||||||
log.bench(' hint: cell --dev compile.ce ' + file)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Run benchmarks ---
|
|
||||||
|
|
||||||
log.bench('')
|
|
||||||
log.bench('samples: ' + text(iterations) + ' (warmup: ' + text(WARMUP) + ')')
|
|
||||||
log.bench('')
|
|
||||||
|
|
||||||
var pad = function(s, n) {
|
|
||||||
var result = s
|
|
||||||
while (length(result) < n) result = result + ' '
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
var i = 0
|
|
||||||
var b = null
|
|
||||||
var vm_result = null
|
|
||||||
var j = 0
|
|
||||||
var found = false
|
|
||||||
var nat_result = null
|
|
||||||
var speedup = 0
|
|
||||||
while (i < length(vm_benches)) {
|
|
||||||
b = vm_benches[i]
|
|
||||||
vm_result = run_bench(b.fn, 'vm')
|
|
||||||
|
|
||||||
log.bench(pad(b.name, 20) + ' VM: ' + pad(format_ns(vm_result.median), 12) + ' (median) ' + format_ns(vm_result.mean) + ' (mean)')
|
|
||||||
|
|
||||||
// find matching native bench
|
|
||||||
j = 0
|
|
||||||
found = false
|
|
||||||
while (j < length(native_benches)) {
|
|
||||||
if (native_benches[j].name == b.name) {
|
|
||||||
nat_result = run_bench(native_benches[j].fn, 'native')
|
|
||||||
log.bench(pad('', 20) + ' NT: ' + pad(format_ns(nat_result.median), 12) + ' (median) ' + format_ns(nat_result.mean) + ' (mean)')
|
|
||||||
|
|
||||||
if (nat_result.median > 0) {
|
|
||||||
speedup = vm_result.median / nat_result.median
|
|
||||||
log.bench(pad('', 20) + ' speedup: ' + text(round(speedup * 100) / 100) + 'x')
|
|
||||||
}
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
j = j + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (has_native && !found) {
|
|
||||||
log.bench(pad('', 20) + ' NT: (no matching function)')
|
|
||||||
}
|
|
||||||
|
|
||||||
log.bench('')
|
|
||||||
i = i + 1
|
|
||||||
}
|
|
||||||
@@ -27,13 +27,13 @@ function send(mailbox, msg) {
|
|||||||
function receive(mailbox) {
|
function receive(mailbox) {
|
||||||
if (length(mailbox.queue) == 0) return null
|
if (length(mailbox.queue) == 0) return null
|
||||||
mailbox.delivered++
|
mailbox.delivered++
|
||||||
return pop(mailbox.queue)
|
return mailbox.queue[]
|
||||||
}
|
}
|
||||||
|
|
||||||
function drain(mailbox) {
|
function drain(mailbox) {
|
||||||
var count = 0
|
var count = 0
|
||||||
while (length(mailbox.queue) > 0) {
|
while (length(mailbox.queue) > 0) {
|
||||||
pop(mailbox.queue)
|
mailbox.queue[]
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ function generate_records(n) {
|
|||||||
var dept_vals = ["eng", "sales", "ops", "hr", "marketing"]
|
var dept_vals = ["eng", "sales", "ops", "hr", "marketing"]
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
x = ((x * 1103515245 + 12345) & 0x7FFFFFFF) | 0
|
x = ((x * 1103515245 + 12345) & 0x7FFFFFFF) | 0
|
||||||
push(records, {
|
records[] = {
|
||||||
id: i + 1,
|
id: i + 1,
|
||||||
name: `user_${i}`,
|
name: `user_${i}`,
|
||||||
score: (x % 1000) / 10,
|
score: (x % 1000) / 10,
|
||||||
status: status_vals[i % 4],
|
status: status_vals[i % 4],
|
||||||
department: dept_vals[i % 5]
|
department: dept_vals[i % 5]
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
return records
|
return records
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ function filter_records(records, field, value) {
|
|||||||
var i = 0
|
var i = 0
|
||||||
for (i = 0; i < length(records); i++) {
|
for (i = 0; i < length(records); i++) {
|
||||||
if (records[i][field] == value) {
|
if (records[i][field] == value) {
|
||||||
push(result, records[i])
|
result[] = records[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@@ -45,7 +45,7 @@ function group_by(records, field) {
|
|||||||
key = records[i][field]
|
key = records[i][field]
|
||||||
if (!key) key = "unknown"
|
if (!key) key = "unknown"
|
||||||
if (!groups[key]) groups[key] = []
|
if (!groups[key]) groups[key] = []
|
||||||
push(groups[key], records[i])
|
groups[key][] = records[i]
|
||||||
}
|
}
|
||||||
return groups
|
return groups
|
||||||
}
|
}
|
||||||
@@ -70,13 +70,13 @@ function aggregate(groups) {
|
|||||||
if (grp[j].score < mn) mn = grp[j].score
|
if (grp[j].score < mn) mn = grp[j].score
|
||||||
if (grp[j].score > mx) mx = grp[j].score
|
if (grp[j].score > mx) mx = grp[j].score
|
||||||
}
|
}
|
||||||
push(result, {
|
result[] = {
|
||||||
group: keys[i],
|
group: keys[i],
|
||||||
count: length(grp),
|
count: length(grp),
|
||||||
average: total / length(grp),
|
average: total / length(grp),
|
||||||
low: mn,
|
low: mn,
|
||||||
high: mx
|
high: mx
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ function build_chain(n) {
|
|||||||
var constraints = []
|
var constraints = []
|
||||||
var i = 0
|
var i = 0
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
push(vars, make_variable(`v${i}`, 0))
|
vars[] = make_variable(`v${i}`, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set first variable
|
// Set first variable
|
||||||
@@ -69,8 +69,8 @@ function build_chain(n) {
|
|||||||
self.variables[1].value = self.variables[0].value + 1
|
self.variables[1].value = self.variables[0].value + 1
|
||||||
self.output = self.variables[1]
|
self.output = self.variables[1]
|
||||||
})
|
})
|
||||||
push(constraints, c)
|
constraints[] = c
|
||||||
push(vars[i].constraints, c)
|
vars[i].constraints[] = c
|
||||||
}
|
}
|
||||||
|
|
||||||
return {vars: vars, constraints: constraints}
|
return {vars: vars, constraints: constraints}
|
||||||
@@ -83,8 +83,8 @@ function build_projection(n) {
|
|||||||
var constraints = []
|
var constraints = []
|
||||||
var i = 0
|
var i = 0
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
push(src, make_variable(`src${i}`, i * 10))
|
src[] = make_variable(`src${i}`, i * 10)
|
||||||
push(dst, make_variable(`dst${i}`, 0))
|
dst[] = make_variable(`dst${i}`, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
var scale_c = null
|
var scale_c = null
|
||||||
@@ -93,8 +93,8 @@ function build_projection(n) {
|
|||||||
self.variables[1].value = self.variables[0].value * 2 + 1
|
self.variables[1].value = self.variables[0].value * 2 + 1
|
||||||
self.output = self.variables[1]
|
self.output = self.variables[1]
|
||||||
})
|
})
|
||||||
push(constraints, scale_c)
|
constraints[] = scale_c
|
||||||
push(dst[i].constraints, scale_c)
|
dst[i].constraints[] = scale_c
|
||||||
}
|
}
|
||||||
|
|
||||||
return {src: src, dst: dst, constraints: constraints}
|
return {src: src, dst: dst, constraints: constraints}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ function make_words(count) {
|
|||||||
var words = []
|
var words = []
|
||||||
var i = 0
|
var i = 0
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
push(words, base_words[i % length(base_words)])
|
words[] = base_words[i % length(base_words)]
|
||||||
}
|
}
|
||||||
return words
|
return words
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ function top_n(freq, n) {
|
|||||||
var pairs = []
|
var pairs = []
|
||||||
var i = 0
|
var i = 0
|
||||||
for (i = 0; i < length(keys); i++) {
|
for (i = 0; i < length(keys); i++) {
|
||||||
push(pairs, {word: keys[i], count: freq[keys[i]]})
|
pairs[] = {word: keys[i], count: freq[keys[i]]}
|
||||||
}
|
}
|
||||||
var sorted = sort(pairs, "count")
|
var sorted = sort(pairs, "count")
|
||||||
// Return last N (highest counts)
|
// Return last N (highest counts)
|
||||||
@@ -47,7 +47,7 @@ function top_n(freq, n) {
|
|||||||
var start = length(sorted) - n
|
var start = length(sorted) - n
|
||||||
if (start < 0) start = 0
|
if (start < 0) start = 0
|
||||||
for (i = start; i < length(sorted); i++) {
|
for (i = start; i < length(sorted); i++) {
|
||||||
push(result, sorted[i])
|
result[] = sorted[i]
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ function group_by_length(words) {
|
|||||||
w = words[i]
|
w = words[i]
|
||||||
k = text(length(w))
|
k = text(length(w))
|
||||||
if (!groups[k]) groups[k] = []
|
if (!groups[k]) groups[k] = []
|
||||||
push(groups[k], w)
|
groups[k][] = w
|
||||||
}
|
}
|
||||||
return groups
|
return groups
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ function make_array_data(size) {
|
|||||||
var arr = []
|
var arr = []
|
||||||
var i = 0
|
var i = 0
|
||||||
for (i = 0; i < size; i++) {
|
for (i = 0; i < size; i++) {
|
||||||
push(arr, {
|
arr[] = {
|
||||||
id: i,
|
id: i,
|
||||||
name: `item_${i}`,
|
name: `item_${i}`,
|
||||||
active: i % 2 == 0,
|
active: i % 2 == 0,
|
||||||
score: i * 1.5,
|
score: i * 1.5,
|
||||||
tags: [`tag_${i % 5}`, `tag_${(i + 1) % 5}`]
|
tags: [`tag_${i % 5}`, `tag_${(i + 1) % 5}`]
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
return arr
|
return arr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ function make_obj_yx(x, y) {
|
|||||||
function make_packed_array(n) {
|
function make_packed_array(n) {
|
||||||
var a = []
|
var a = []
|
||||||
var i = 0
|
var i = 0
|
||||||
for (i = 0; i < n; i++) push(a, i)
|
for (i = 0; i < n; i++) a[] = i
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ return {
|
|||||||
var a = null
|
var a = null
|
||||||
for (j = 0; j < n; j++) {
|
for (j = 0; j < n; j++) {
|
||||||
a = []
|
a = []
|
||||||
for (i = 0; i < 256; i++) push(a, i)
|
for (i = 0; i < 256; i++) a[] = i
|
||||||
x = (x + length(a)) | 0
|
x = (x + length(a)) | 0
|
||||||
}
|
}
|
||||||
return blackhole(sink, x)
|
return blackhole(sink, x)
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ return {
|
|||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
push(a, i)
|
push(a, i)
|
||||||
if (length(a) > 64) {
|
if (length(a) > 64) {
|
||||||
v = pop(a)
|
v = a[]
|
||||||
x = (x + v) | 0
|
x = (x + v) | 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,21 +16,21 @@ function tokenize(src) {
|
|||||||
ch = chars[i]
|
ch = chars[i]
|
||||||
if (ch == " " || ch == "\n" || ch == "\t") {
|
if (ch == " " || ch == "\n" || ch == "\t") {
|
||||||
if (length(buf) > 0) {
|
if (length(buf) > 0) {
|
||||||
push(tokens, buf)
|
tokens[] = buf
|
||||||
buf = ""
|
buf = ""
|
||||||
}
|
}
|
||||||
} else if (ch == "(" || ch == ")" || ch == "+" || ch == "-"
|
} else if (ch == "(" || ch == ")" || ch == "+" || ch == "-"
|
||||||
|| ch == "*" || ch == "=" || ch == ";" || ch == ",") {
|
|| ch == "*" || ch == "=" || ch == ";" || ch == ",") {
|
||||||
if (length(buf) > 0) {
|
if (length(buf) > 0) {
|
||||||
push(tokens, buf)
|
tokens[] = buf
|
||||||
buf = ""
|
buf = ""
|
||||||
}
|
}
|
||||||
push(tokens, ch)
|
tokens[] = ch
|
||||||
} else {
|
} else {
|
||||||
buf = buf + ch
|
buf = buf + ch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (length(buf) > 0) push(tokens, buf)
|
if (length(buf) > 0) tokens[] = buf
|
||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,21 +49,21 @@ function parse_tokens(tokens) {
|
|||||||
i++ // skip =
|
i++ // skip =
|
||||||
i++
|
i++
|
||||||
if (i < length(tokens)) node.value = tokens[i]
|
if (i < length(tokens)) node.value = tokens[i]
|
||||||
push(ast, node)
|
ast[] = node
|
||||||
} else if (tok == "return") {
|
} else if (tok == "return") {
|
||||||
node = {type: "return", value: null}
|
node = {type: "return", value: null}
|
||||||
i++
|
i++
|
||||||
if (i < length(tokens)) node.value = tokens[i]
|
if (i < length(tokens)) node.value = tokens[i]
|
||||||
push(ast, node)
|
ast[] = node
|
||||||
} else if (tok == "function") {
|
} else if (tok == "function") {
|
||||||
node = {type: "func", name: null, body: []}
|
node = {type: "func", name: null, body: []}
|
||||||
i++
|
i++
|
||||||
if (i < length(tokens)) node.name = tokens[i]
|
if (i < length(tokens)) node.name = tokens[i]
|
||||||
// Skip to matching )
|
// Skip to matching )
|
||||||
while (i < length(tokens) && tokens[i] != ")") i++
|
while (i < length(tokens) && tokens[i] != ")") i++
|
||||||
push(ast, node)
|
ast[] = node
|
||||||
} else {
|
} else {
|
||||||
push(ast, {type: "expr", value: tok})
|
ast[] = {type: "expr", value: tok}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ast
|
return ast
|
||||||
@@ -121,7 +121,7 @@ function simulate_build(n_modules, deps_per_module) {
|
|||||||
// Generate all module sources
|
// Generate all module sources
|
||||||
for (i = 0; i < n_modules; i++) {
|
for (i = 0; i < n_modules; i++) {
|
||||||
src = generate_module(i, deps_per_module)
|
src = generate_module(i, deps_per_module)
|
||||||
push(modules, src)
|
modules[] = src
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Load" each module: tokenize → parse → evaluate
|
// "Load" each module: tokenize → parse → evaluate
|
||||||
@@ -173,7 +173,7 @@ function topo_sort(n_modules, deps_per_module) {
|
|||||||
for (j = 0; j < deps_per_module; j++) {
|
for (j = 0; j < deps_per_module; j++) {
|
||||||
if (j < i) {
|
if (j < i) {
|
||||||
dep = "mod_" + text(j)
|
dep = "mod_" + text(j)
|
||||||
push(adj[dep], name)
|
adj[dep][] = name
|
||||||
in_degree[name] = in_degree[name] + 1
|
in_degree[name] = in_degree[name] + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,7 +183,7 @@ function topo_sort(n_modules, deps_per_module) {
|
|||||||
var queue = []
|
var queue = []
|
||||||
var keys = array(in_degree)
|
var keys = array(in_degree)
|
||||||
for (i = 0; i < length(keys); i++) {
|
for (i = 0; i < length(keys); i++) {
|
||||||
if (in_degree[keys[i]] == 0) push(queue, keys[i])
|
if (in_degree[keys[i]] == 0) queue[] = keys[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
var order = []
|
var order = []
|
||||||
@@ -193,12 +193,12 @@ function topo_sort(n_modules, deps_per_module) {
|
|||||||
while (qi < length(queue)) {
|
while (qi < length(queue)) {
|
||||||
current = queue[qi]
|
current = queue[qi]
|
||||||
qi++
|
qi++
|
||||||
push(order, current)
|
order[] = current
|
||||||
neighbors = adj[current]
|
neighbors = adj[current]
|
||||||
if (neighbors) {
|
if (neighbors) {
|
||||||
for (i = 0; i < length(neighbors); i++) {
|
for (i = 0; i < length(neighbors); i++) {
|
||||||
in_degree[neighbors[i]] = in_degree[neighbors[i]] - 1
|
in_degree[neighbors[i]] = in_degree[neighbors[i]] - 1
|
||||||
if (in_degree[neighbors[i]] == 0) push(queue, neighbors[i])
|
if (in_degree[neighbors[i]] == 0) queue[] = neighbors[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ function make_random_array(n, seed) {
|
|||||||
var i = 0
|
var i = 0
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
x = ((x * 1103515245 + 12345) & 0x7FFFFFFF) | 0
|
x = ((x * 1103515245 + 12345) & 0x7FFFFFFF) | 0
|
||||||
push(a, x % 10000)
|
a[] = x % 10000
|
||||||
}
|
}
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ function make_random_array(n, seed) {
|
|||||||
function make_descending(n) {
|
function make_descending(n) {
|
||||||
var a = []
|
var a = []
|
||||||
var i = 0
|
var i = 0
|
||||||
for (i = n - 1; i >= 0; i--) push(a, i)
|
for (i = n - 1; i >= 0; i--) a[] = i
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,19 +58,19 @@ function merge(a, b) {
|
|||||||
var j = 0
|
var j = 0
|
||||||
while (i < length(a) && j < length(b)) {
|
while (i < length(a) && j < length(b)) {
|
||||||
if (a[i] <= b[j]) {
|
if (a[i] <= b[j]) {
|
||||||
push(result, a[i])
|
result[] = a[i]
|
||||||
i++
|
i++
|
||||||
} else {
|
} else {
|
||||||
push(result, b[j])
|
result[] = b[j]
|
||||||
j++
|
j++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (i < length(a)) {
|
while (i < length(a)) {
|
||||||
push(result, a[i])
|
result[] = a[i]
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
while (j < length(b)) {
|
while (j < length(b)) {
|
||||||
push(result, b[j])
|
result[] = b[j]
|
||||||
j++
|
j++
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@@ -97,7 +97,7 @@ function sort_records(n) {
|
|||||||
var i = 0
|
var i = 0
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
x = ((x * 1103515245 + 12345) & 0x7FFFFFFF) | 0
|
x = ((x * 1103515245 + 12345) & 0x7FFFFFFF) | 0
|
||||||
push(records, {id: i, score: x % 10000, name: `item_${i}`})
|
records[] = {id: i, score: x % 10000, name: `item_${i}`}
|
||||||
}
|
}
|
||||||
return sort(records, "score")
|
return sort(records, "score")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ function build_index(txt) {
|
|||||||
if (!index[w]) {
|
if (!index[w]) {
|
||||||
index[w] = []
|
index[w] = []
|
||||||
}
|
}
|
||||||
push(index[w], i)
|
index[w][] = i
|
||||||
}
|
}
|
||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ function tree_map(node, fn) {
|
|||||||
function tree_flatten(node, result) {
|
function tree_flatten(node, result) {
|
||||||
if (!node) return null
|
if (!node) return null
|
||||||
tree_flatten(node.left, result)
|
tree_flatten(node.left, result)
|
||||||
push(result, node.val)
|
result[] = node.val
|
||||||
tree_flatten(node.right, result)
|
tree_flatten(node.right, result)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,7 @@ return {
|
|||||||
// Build a balanced BST of 1024 elements
|
// Build a balanced BST of 1024 elements
|
||||||
var data = []
|
var data = []
|
||||||
var i = 0
|
var i = 0
|
||||||
for (i = 0; i < 1024; i++) push(data, i)
|
for (i = 0; i < 1024; i++) data[] = i
|
||||||
var bst = build_balanced(data, 0, 1023)
|
var bst = build_balanced(data, 0, 1023)
|
||||||
var found = 0
|
var found = 0
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Run hyperfine with parameter lists
|
|
||||||
# This will create a cross-product of all libraries × all scenarios
|
|
||||||
hyperfine \
|
|
||||||
--warmup 3 \
|
|
||||||
--runs 20 \
|
|
||||||
-i \
|
|
||||||
--export-csv wota_vs_nota_vs_json.csv \
|
|
||||||
--export-json wota_vs_nota_vs_json.json \
|
|
||||||
--export-markdown wota_vs_nota_vs_json.md \
|
|
||||||
--parameter-list lib wota,nota,json \
|
|
||||||
--parameter-list scen empty,integers,floats,strings,objects,nested,large_array \
|
|
||||||
'cell benchmarks/wota_nota_json {lib} {scen}'
|
|
||||||
|
|
||||||
|
|
||||||
echo "Benchmark complete! Results saved to:"
|
|
||||||
echo " - wota_vs_nota_vs_json.csv"
|
|
||||||
echo " - wota_vs_nota_vs_json.json"
|
|
||||||
echo " - wota_vs_nota_vs_json.md"
|
|
||||||
@@ -95,12 +95,12 @@ function benchArrayOps() {
|
|||||||
var arr = [];
|
var arr = [];
|
||||||
var j = 0
|
var j = 0
|
||||||
for (j = 0; j < iterations.medium; j++) {
|
for (j = 0; j < iterations.medium; j++) {
|
||||||
push(arr, j);
|
arr[] = j;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var arr = [];
|
var arr = [];
|
||||||
for (i = 0; i < 10000; i++) push(arr, i);
|
for (i = 0; i < 10000; i++) arr[] = i;
|
||||||
|
|
||||||
var accessTime = measureTime(function() {
|
var accessTime = measureTime(function() {
|
||||||
var sum = 0;
|
var sum = 0;
|
||||||
@@ -188,7 +188,7 @@ function benchStringOps() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (i = 0; i < 1000; i++) {
|
for (i = 0; i < 1000; i++) {
|
||||||
push(strings, "string" + i);
|
strings[] = "string" + i;
|
||||||
}
|
}
|
||||||
|
|
||||||
var joinTime = measureTime(function() {
|
var joinTime = measureTime(function() {
|
||||||
@@ -261,13 +261,13 @@ function benchClosures() {
|
|||||||
var funcs = [];
|
var funcs = [];
|
||||||
var j = 0
|
var j = 0
|
||||||
for (j = 0; j < iterations.medium; j++) {
|
for (j = 0; j < iterations.medium; j++) {
|
||||||
push(funcs, makeAdder(j));
|
funcs[] = makeAdder(j);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var adders = [];
|
var adders = [];
|
||||||
for (i = 0; i < 1000; i++) {
|
for (i = 0; i < 1000; i++) {
|
||||||
push(adders, makeAdder(i));
|
adders[] = makeAdder(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
var closureCallTime = measureTime(function() {
|
var closureCallTime = measureTime(function() {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ var nll = null
|
|||||||
var oll = null
|
var oll = null
|
||||||
for (i = 0; i < 10000; i++) {
|
for (i = 0; i < 10000; i++) {
|
||||||
accstr += i;
|
accstr += i;
|
||||||
push(newarr, text(i))
|
newarr[] = text(i)
|
||||||
}
|
}
|
||||||
var jsonDecodeTimes = [];
|
var jsonDecodeTimes = [];
|
||||||
var jsonEncodeTimes = [];
|
var jsonEncodeTimes = [];
|
||||||
@@ -26,19 +26,19 @@ var notaSizes = [];
|
|||||||
for (i = 0; i < 100; i++) {
|
for (i = 0; i < 100; i++) {
|
||||||
start = os.now();
|
start = os.now();
|
||||||
jll = json.decode(ll);
|
jll = json.decode(ll);
|
||||||
push(jsonDecodeTimes, (os.now() - start) * 1000);
|
jsonDecodeTimes[] = (os.now() - start) * 1000;
|
||||||
|
|
||||||
start = os.now();
|
start = os.now();
|
||||||
jsonStr = JSON.stringify(jll);
|
jsonStr = JSON.stringify(jll);
|
||||||
push(jsonEncodeTimes, (os.now() - start) * 1000);
|
jsonEncodeTimes[] = (os.now() - start) * 1000;
|
||||||
|
|
||||||
start = os.now();
|
start = os.now();
|
||||||
nll = nota.encode(jll);
|
nll = nota.encode(jll);
|
||||||
push(notaEncodeTimes, (os.now() - start) * 1000);
|
notaEncodeTimes[] = (os.now() - start) * 1000;
|
||||||
|
|
||||||
start = os.now();
|
start = os.now();
|
||||||
oll = nota.decode(nll);
|
oll = nota.decode(nll);
|
||||||
push(notaDecodeTimes, (os.now() - start) * 1000);
|
notaDecodeTimes[] = (os.now() - start) * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStats(arr) {
|
function getStats(arr) {
|
||||||
|
|||||||
2132
benchmarks/nota.json
2132
benchmarks/nota.json
File diff suppressed because it is too large
Load Diff
@@ -99,7 +99,7 @@ function runBenchmarkForLibrary(lib, bench) {
|
|||||||
for (j = 0; j < length(bench.data); j++) {
|
for (j = 0; j < length(bench.data); j++) {
|
||||||
e = lib.encode(bench.data[j]);
|
e = lib.encode(bench.data[j]);
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
push(encodedList, e);
|
encodedList[] = e;
|
||||||
totalSize += lib.getSize(e);
|
totalSize += lib.getSize(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
192
boot.ce
Normal file
192
boot.ce
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
// cell boot [--native] <program> - Pre-compile all module dependencies in parallel
|
||||||
|
//
|
||||||
|
// Discovers all transitive module dependencies for a program,
|
||||||
|
// checks which are not yet cached, and compiles uncached ones
|
||||||
|
// in parallel using worker actors composed via parallel() requestors.
|
||||||
|
//
|
||||||
|
// Also used as a child actor by engine.cm for auto-boot.
|
||||||
|
|
||||||
|
var shop = use('internal/shop')
|
||||||
|
var fd = use('fd')
|
||||||
|
var pkg_tools = use('package')
|
||||||
|
var build = use('build')
|
||||||
|
|
||||||
|
var is_native = false
|
||||||
|
var target_prog = null
|
||||||
|
var target_pkg = null
|
||||||
|
var i = 0
|
||||||
|
|
||||||
|
// Child actor mode: receive message from engine.cm
|
||||||
|
var _child_mode = false
|
||||||
|
var run_boot = null
|
||||||
|
|
||||||
|
$receiver(function(msg) {
|
||||||
|
_child_mode = true
|
||||||
|
is_native = msg.native || false
|
||||||
|
target_prog = msg.program
|
||||||
|
target_pkg = msg.package
|
||||||
|
run_boot()
|
||||||
|
})
|
||||||
|
|
||||||
|
// CLI mode: parse arguments
|
||||||
|
if (args && length(args) > 0) {
|
||||||
|
for (i = 0; i < length(args); i = i + 1) {
|
||||||
|
if (args[i] == '--native') {
|
||||||
|
is_native = true
|
||||||
|
} else if (args[i] == '--help' || args[i] == '-h') {
|
||||||
|
log.console("Usage: cell boot [--native] <program>")
|
||||||
|
log.console("")
|
||||||
|
log.console("Pre-compile all module dependencies for a program.")
|
||||||
|
log.console("Uncached modules are compiled in parallel.")
|
||||||
|
$stop()
|
||||||
|
} else if (!starts_with(args[i], '-')) {
|
||||||
|
target_prog = args[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!target_prog) {
|
||||||
|
log.error("boot: no program specified")
|
||||||
|
$stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover all transitive module dependencies for a file
|
||||||
|
function discover_deps(file_path) {
|
||||||
|
return shop.trace_deps(file_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out already-cached modules
|
||||||
|
function filter_uncached(deps) {
|
||||||
|
var uncached = []
|
||||||
|
var j = 0
|
||||||
|
var s = null
|
||||||
|
|
||||||
|
j = 0
|
||||||
|
while (j < length(deps.scripts)) {
|
||||||
|
s = deps.scripts[j]
|
||||||
|
if (is_native) {
|
||||||
|
if (!shop.is_native_cached(s.path, s.package)) {
|
||||||
|
uncached[] = {type: 'native_script', path: s.path, package: s.package}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!shop.is_cached(s.path)) {
|
||||||
|
uncached[] = {type: 'script', path: s.path, package: s.package}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j = j + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand C packages into individual files for parallel compilation
|
||||||
|
var target = build.detect_host_target()
|
||||||
|
var pkg = null
|
||||||
|
var c_files = null
|
||||||
|
var k = 0
|
||||||
|
j = 0
|
||||||
|
while (j < length(deps.c_packages)) {
|
||||||
|
pkg = deps.c_packages[j]
|
||||||
|
if (pkg != 'core') {
|
||||||
|
c_files = pkg_tools.get_c_files(pkg, target, true)
|
||||||
|
k = 0
|
||||||
|
while (k < length(c_files)) {
|
||||||
|
uncached[] = {type: 'c_file', package: pkg, file: c_files[k]}
|
||||||
|
k = k + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j = j + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return uncached
|
||||||
|
}
|
||||||
|
|
||||||
|
function item_name(item) {
|
||||||
|
if (item.path) return item.path
|
||||||
|
if (item.file) return item.package + '/' + item.file
|
||||||
|
return item.package
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a requestor that spawns a compile_worker actor for one item
|
||||||
|
function make_compile_requestor(item) {
|
||||||
|
var worker = null
|
||||||
|
var name = item_name(item)
|
||||||
|
return function(callback, value) {
|
||||||
|
log.console('boot: spawning worker for ' + name)
|
||||||
|
$start(function(event) {
|
||||||
|
if (event.type == 'greet') {
|
||||||
|
worker = event.actor
|
||||||
|
send(event.actor, {
|
||||||
|
type: item.type,
|
||||||
|
path: item.path,
|
||||||
|
package: item.package,
|
||||||
|
file: item.file
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (event.type == 'stop') {
|
||||||
|
callback(name)
|
||||||
|
}
|
||||||
|
if (event.type == 'disrupt') {
|
||||||
|
log.error('boot: worker failed for ' + name)
|
||||||
|
callback(null, {message: 'compile failed: ' + name})
|
||||||
|
}
|
||||||
|
}, 'compile_worker')
|
||||||
|
return function cancel(reason) {
|
||||||
|
if (worker) $stop(worker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_boot = function() {
|
||||||
|
var prog_path = null
|
||||||
|
var prog_info = null
|
||||||
|
var deps = null
|
||||||
|
var uncached = null
|
||||||
|
var requestors = null
|
||||||
|
var p = null
|
||||||
|
|
||||||
|
// Resolve the program path
|
||||||
|
if (target_prog) {
|
||||||
|
p = target_prog
|
||||||
|
if (ends_with(p, '.ce')) p = text(p, 0, -3)
|
||||||
|
prog_info = shop.resolve_program ? shop.resolve_program(p, target_pkg) : null
|
||||||
|
if (prog_info) {
|
||||||
|
prog_path = prog_info.path
|
||||||
|
if (!target_pkg && prog_info.pkg) target_pkg = prog_info.pkg
|
||||||
|
} else {
|
||||||
|
prog_path = p + '.ce'
|
||||||
|
if (!fd.is_file(prog_path)) {
|
||||||
|
prog_path = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prog_path || !fd.is_file(prog_path)) {
|
||||||
|
log.error('boot: could not find program: ' + text(target_prog || ''))
|
||||||
|
$stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover all transitive deps
|
||||||
|
deps = discover_deps(prog_path)
|
||||||
|
uncached = filter_uncached(deps)
|
||||||
|
|
||||||
|
if (length(uncached) == 0) {
|
||||||
|
log.console('boot: all modules cached')
|
||||||
|
$stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile uncached modules in parallel using worker actors
|
||||||
|
log.console('boot: ' + text(length(uncached)) + ' modules to compile')
|
||||||
|
requestors = array(uncached, make_compile_requestor)
|
||||||
|
parallel(requestors)(function(results, reason) {
|
||||||
|
if (reason) {
|
||||||
|
log.error('boot: ' + (reason.message || text(reason)))
|
||||||
|
} else {
|
||||||
|
log.console('boot: compiled ' + text(length(results)) + ' modules')
|
||||||
|
}
|
||||||
|
$stop()
|
||||||
|
}, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLI mode: start immediately
|
||||||
|
if (!_child_mode && target_prog) {
|
||||||
|
run_boot()
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
8828
boot/fold.cm.mcode
8828
boot/fold.cm.mcode
File diff suppressed because one or more lines are too long
25416
boot/mcode.cm.mcode
25416
boot/mcode.cm.mcode
File diff suppressed because one or more lines are too long
13804
boot/parse.cm.mcode
13804
boot/parse.cm.mcode
File diff suppressed because one or more lines are too long
2764
boot/qbe.cm.mcode
Normal file
2764
boot/qbe.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
34991
boot/qbe_emit.cm.mcode
Normal file
34991
boot/qbe_emit.cm.mcode
Normal file
File diff suppressed because one or more lines are too long
27919
boot/streamline.cm.mcode
27919
boot/streamline.cm.mcode
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
207
build.cm
207
build.cm
@@ -65,7 +65,7 @@ function replace_sigils(str, pkg_dir) {
|
|||||||
function replace_sigils_array(flags, pkg_dir) {
|
function replace_sigils_array(flags, pkg_dir) {
|
||||||
var result = []
|
var result = []
|
||||||
arrfor(flags, function(flag) {
|
arrfor(flags, function(flag) {
|
||||||
push(result, replace_sigils(flag, pkg_dir))
|
result[] = replace_sigils(flag, pkg_dir)
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -179,7 +179,7 @@ function bmfst_save(cmd_str, src_path, deps, obj_path) {
|
|||||||
arrfor(deps, function(dep_path) {
|
arrfor(deps, function(dep_path) {
|
||||||
var st = memo_stat(dep_path)
|
var st = memo_stat(dep_path)
|
||||||
if (st)
|
if (st)
|
||||||
push(entries, {p: dep_path, m: st.m, s: st.s})
|
entries[] = {p: dep_path, m: st.m, s: st.s}
|
||||||
})
|
})
|
||||||
var mf = {o: obj_path, d: entries}
|
var mf = {o: obj_path, d: entries}
|
||||||
var mf_path = bmfst_path(cmd_str, src_path)
|
var mf_path = bmfst_path(cmd_str, src_path)
|
||||||
@@ -191,16 +191,16 @@ function bmfst_save(cmd_str, src_path, deps, obj_path) {
|
|||||||
|
|
||||||
function bmfst_dl_key(setup, link_info) {
|
function bmfst_dl_key(setup, link_info) {
|
||||||
var parts = [setup.cmd_str, setup.src_path]
|
var parts = [setup.cmd_str, setup.src_path]
|
||||||
push(parts, 'target:' + text(link_info.target))
|
parts[] = 'target:' + text(link_info.target)
|
||||||
push(parts, 'cc:' + text(link_info.cc))
|
parts[] = 'cc:' + text(link_info.cc)
|
||||||
arrfor(link_info.extra_objects, function(obj) {
|
arrfor(link_info.extra_objects, function(obj) {
|
||||||
if (obj != null) push(parts, 'extra:' + text(obj))
|
if (obj != null) parts[] = 'extra:' + text(obj)
|
||||||
})
|
})
|
||||||
arrfor(link_info.ldflags, function(flag) {
|
arrfor(link_info.ldflags, function(flag) {
|
||||||
push(parts, 'ldflag:' + text(flag))
|
parts[] = 'ldflag:' + text(flag)
|
||||||
})
|
})
|
||||||
arrfor(link_info.target_ldflags, function(flag) {
|
arrfor(link_info.target_ldflags, function(flag) {
|
||||||
push(parts, 'target_ldflag:' + text(flag))
|
parts[] = 'target_ldflag:' + text(flag)
|
||||||
})
|
})
|
||||||
return text(parts, '\n')
|
return text(parts, '\n')
|
||||||
}
|
}
|
||||||
@@ -227,7 +227,7 @@ function bmfst_dl_save(setup, link_info, deps, dylib_path) {
|
|||||||
arrfor(deps, function(dep_path) {
|
arrfor(deps, function(dep_path) {
|
||||||
var st = memo_stat(dep_path)
|
var st = memo_stat(dep_path)
|
||||||
if (st)
|
if (st)
|
||||||
push(entries, {p: dep_path, m: st.m, s: st.s})
|
entries[] = {p: dep_path, m: st.m, s: st.s}
|
||||||
})
|
})
|
||||||
var mf = {dylib: dylib_path, d: entries}
|
var mf = {dylib: dylib_path, d: entries}
|
||||||
var mf_path = cache_path(bmfst_dl_key(setup, link_info), SALT_BMFST_DL)
|
var mf_path = cache_path(bmfst_dl_key(setup, link_info), SALT_BMFST_DL)
|
||||||
@@ -259,7 +259,7 @@ function get_c_deps(cc, flags, src_path) {
|
|||||||
var dep_file = '/tmp/cell_deps_' + content_hash(src_path) + '.d'
|
var dep_file = '/tmp/cell_deps_' + content_hash(src_path) + '.d'
|
||||||
var dep_cmd = [cc, '-MM', '-MG', '-MF', '"' + dep_file + '"']
|
var dep_cmd = [cc, '-MM', '-MG', '-MF', '"' + dep_file + '"']
|
||||||
dep_cmd = array(dep_cmd, flags)
|
dep_cmd = array(dep_cmd, flags)
|
||||||
push(dep_cmd, '"' + src_path + '"')
|
dep_cmd[] = '"' + src_path + '"'
|
||||||
var ret = os.system(text(dep_cmd, ' ') + ' 2>/dev/null')
|
var ret = os.system(text(dep_cmd, ' ') + ' 2>/dev/null')
|
||||||
if (ret != 0) return [src_path]
|
if (ret != 0) return [src_path]
|
||||||
if (!fd.is_file(dep_file)) return [src_path]
|
if (!fd.is_file(dep_file)) return [src_path]
|
||||||
@@ -274,9 +274,9 @@ function hash_all_deps(cmd_str, deps) {
|
|||||||
arrfor(deps, function(dep_path) {
|
arrfor(deps, function(dep_path) {
|
||||||
var content = memo_read(dep_path)
|
var content = memo_read(dep_path)
|
||||||
if (content != null)
|
if (content != null)
|
||||||
push(parts, dep_path + '\n' + content)
|
parts[] = dep_path + '\n' + content
|
||||||
else
|
else
|
||||||
push(parts, dep_path + '\n<missing>')
|
parts[] = dep_path + '\n<missing>'
|
||||||
})
|
})
|
||||||
return text(parts, '\n')
|
return text(parts, '\n')
|
||||||
}
|
}
|
||||||
@@ -310,16 +310,16 @@ function compile_setup(pkg, file, target, opts) {
|
|||||||
common_flags = array(common_flags, ['-Os', '-DNDEBUG'])
|
common_flags = array(common_flags, ['-Os', '-DNDEBUG'])
|
||||||
}
|
}
|
||||||
|
|
||||||
push(common_flags, '-DCELL_USE_NAME=' + sym_name)
|
common_flags[] = '-DCELL_USE_NAME=' + sym_name
|
||||||
push(common_flags, '-I"' + pkg_dir + '"')
|
common_flags[] = '-I"' + pkg_dir + '"'
|
||||||
|
|
||||||
if (fd.is_dir(pkg_dir + '/include')) {
|
if (fd.is_dir(pkg_dir + '/include')) {
|
||||||
push(common_flags, '-I"' + pkg_dir + '/include"')
|
common_flags[] = '-I"' + pkg_dir + '/include"'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pkg != 'core') {
|
if (pkg != 'core') {
|
||||||
core_dir = shop.get_package_dir('core')
|
core_dir = shop.get_package_dir('core')
|
||||||
push(common_flags, '-I"' + core_dir + '/source"')
|
common_flags[] = '-I"' + core_dir + '/source"'
|
||||||
}
|
}
|
||||||
|
|
||||||
arrfor(cflags, function(flag) {
|
arrfor(cflags, function(flag) {
|
||||||
@@ -331,16 +331,16 @@ function compile_setup(pkg, file, target, opts) {
|
|||||||
f = '-I"' + pkg_dir + '/' + ipath + '"'
|
f = '-I"' + pkg_dir + '/' + ipath + '"'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
push(common_flags, f)
|
common_flags[] = f
|
||||||
})
|
})
|
||||||
|
|
||||||
arrfor(target_cflags, function(flag) {
|
arrfor(target_cflags, function(flag) {
|
||||||
push(common_flags, flag)
|
common_flags[] = flag
|
||||||
})
|
})
|
||||||
|
|
||||||
var cmd_parts = [cc, '-c', '-fPIC']
|
var cmd_parts = [cc, '-c', '-fPIC']
|
||||||
cmd_parts = array(cmd_parts, common_flags)
|
cmd_parts = array(cmd_parts, common_flags)
|
||||||
push(cmd_parts, '"' + src_path + '"')
|
cmd_parts[] = '"' + src_path + '"'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cmd_str: text(cmd_parts, ' '),
|
cmd_str: text(cmd_parts, ' '),
|
||||||
@@ -464,7 +464,7 @@ Build.compile_file = function(pkg, file, target, opts) {
|
|||||||
|
|
||||||
// Compile
|
// Compile
|
||||||
log.shop('compiling ' + file)
|
log.shop('compiling ' + file)
|
||||||
log.console('Compiling ' + file)
|
log.build('Compiling ' + file)
|
||||||
err_path = '/tmp/cell_build_err_' + content_hash(setup.src_path) + '.log'
|
err_path = '/tmp/cell_build_err_' + content_hash(setup.src_path) + '.log'
|
||||||
full_cmd = setup.cmd_str + ' -o "' + obj_path + '" 2>"' + err_path + '"'
|
full_cmd = setup.cmd_str + ' -o "' + obj_path + '" 2>"' + err_path + '"'
|
||||||
ret = os.system(full_cmd)
|
ret = os.system(full_cmd)
|
||||||
@@ -513,7 +513,7 @@ Build.build_package = function(pkg, target, exclude_main, buildtype) {
|
|||||||
|
|
||||||
arrfor(c_files, function(file) {
|
arrfor(c_files, function(file) {
|
||||||
var obj = Build.compile_file(pkg, file, _target, {buildtype: _buildtype, cflags: cached_cflags})
|
var obj = Build.compile_file(pkg, file, _target, {buildtype: _buildtype, cflags: cached_cflags})
|
||||||
push(objects, obj)
|
objects[] = obj
|
||||||
})
|
})
|
||||||
|
|
||||||
return objects
|
return objects
|
||||||
@@ -527,16 +527,16 @@ Build.build_package = function(pkg, target, exclude_main, buildtype) {
|
|||||||
// link_opts: {extra_objects, ldflags, target_ldflags, target, cc}
|
// link_opts: {extra_objects, ldflags, target_ldflags, target, cc}
|
||||||
function compute_dylib_content(full_content, link_opts) {
|
function compute_dylib_content(full_content, link_opts) {
|
||||||
var parts = [full_content]
|
var parts = [full_content]
|
||||||
push(parts, 'target:' + text(link_opts.target))
|
parts[] = 'target:' + text(link_opts.target)
|
||||||
push(parts, 'cc:' + text(link_opts.cc))
|
parts[] = 'cc:' + text(link_opts.cc)
|
||||||
arrfor(link_opts.extra_objects, function(obj) {
|
arrfor(link_opts.extra_objects, function(obj) {
|
||||||
if (obj != null) push(parts, 'extra:' + text(obj))
|
if (obj != null) parts[] = 'extra:' + text(obj)
|
||||||
})
|
})
|
||||||
arrfor(link_opts.ldflags, function(flag) {
|
arrfor(link_opts.ldflags, function(flag) {
|
||||||
push(parts, 'ldflag:' + text(flag))
|
parts[] = 'ldflag:' + text(flag)
|
||||||
})
|
})
|
||||||
arrfor(link_opts.target_ldflags, function(flag) {
|
arrfor(link_opts.target_ldflags, function(flag) {
|
||||||
push(parts, 'target_ldflag:' + text(flag))
|
parts[] = 'target_ldflag:' + text(flag)
|
||||||
})
|
})
|
||||||
return text(parts, '\n')
|
return text(parts, '\n')
|
||||||
}
|
}
|
||||||
@@ -570,7 +570,7 @@ Build.build_module_dylib = function(pkg, file, target, opts) {
|
|||||||
f = '-L"' + setup.pkg_dir + '/' + lpath + '"'
|
f = '-L"' + setup.pkg_dir + '/' + lpath + '"'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
push(resolved_ldflags, f)
|
resolved_ldflags[] = f
|
||||||
})
|
})
|
||||||
|
|
||||||
var build_dir = get_build_dir()
|
var build_dir = get_build_dir()
|
||||||
@@ -603,6 +603,8 @@ Build.build_module_dylib = function(pkg, file, target, opts) {
|
|||||||
var post_probe = null
|
var post_probe = null
|
||||||
var fallback_probe = null
|
var fallback_probe = null
|
||||||
var _fail_msg2 = null
|
var _fail_msg2 = null
|
||||||
|
var link_err_path = null
|
||||||
|
var link_err_text = null
|
||||||
|
|
||||||
if (probe && probe.fail) {
|
if (probe && probe.fail) {
|
||||||
_fail_msg2 = probe.fail_path ? text(fd.slurp(probe.fail_path)) : null
|
_fail_msg2 = probe.fail_path ? text(fd.slurp(probe.fail_path)) : null
|
||||||
@@ -681,26 +683,32 @@ Build.build_module_dylib = function(pkg, file, target, opts) {
|
|||||||
'-Wl,-rpath,' + local_dir
|
'-Wl,-rpath,' + local_dir
|
||||||
])
|
])
|
||||||
} else if (tc.system == 'windows') {
|
} else if (tc.system == 'windows') {
|
||||||
push(cmd_parts, '-Wl,--allow-shlib-undefined')
|
cmd_parts[] = '-Wl,--allow-shlib-undefined'
|
||||||
}
|
}
|
||||||
|
|
||||||
push(cmd_parts, '-L"' + local_dir + '"')
|
cmd_parts[] = '-L"' + local_dir + '"'
|
||||||
push(cmd_parts, '"' + text(obj) + '"')
|
cmd_parts[] = '"' + text(obj) + '"'
|
||||||
arrfor(_extra, function(extra_obj) {
|
arrfor(_extra, function(extra_obj) {
|
||||||
if (extra_obj != null) push(cmd_parts, '"' + text(extra_obj) + '"')
|
if (extra_obj != null) cmd_parts[] = '"' + text(extra_obj) + '"'
|
||||||
})
|
})
|
||||||
cmd_parts = array(cmd_parts, resolved_ldflags)
|
cmd_parts = array(cmd_parts, resolved_ldflags)
|
||||||
cmd_parts = array(cmd_parts, target_ldflags)
|
cmd_parts = array(cmd_parts, target_ldflags)
|
||||||
push(cmd_parts, '-o')
|
cmd_parts[] = '-o'
|
||||||
push(cmd_parts, '"' + dylib_path + '"')
|
cmd_parts[] = '"' + dylib_path + '"'
|
||||||
|
|
||||||
cmd_str = text(cmd_parts, ' ')
|
cmd_str = text(cmd_parts, ' ')
|
||||||
if (_opts.verbose) log.build('[verbose] link: ' + cmd_str)
|
if (_opts.verbose) log.build('[verbose] link: ' + cmd_str)
|
||||||
log.shop('linking ' + file)
|
log.shop('linking ' + file)
|
||||||
log.console('Linking module ' + file + ' -> ' + fd.basename(dylib_path))
|
log.build('Linking module ' + file + ' -> ' + fd.basename(dylib_path))
|
||||||
ret = os.system(cmd_str)
|
link_err_path = '/tmp/cell_link_err_' + content_hash(file) + '.log'
|
||||||
|
ret = os.system(cmd_str + ' 2>"' + link_err_path + '"')
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
log.error('Linking failed: ' + file)
|
if (fd.is_file(link_err_path))
|
||||||
|
link_err_text = text(fd.slurp(link_err_path))
|
||||||
|
if (link_err_text)
|
||||||
|
log.error('Linking failed: ' + file + '\n' + link_err_text)
|
||||||
|
else
|
||||||
|
log.error('Linking failed: ' + file)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,6 +722,28 @@ Build.build_module_dylib = function(pkg, file, target, opts) {
|
|||||||
return dylib_path
|
return dylib_path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compile a single C module file for a package (support objects + one dylib).
|
||||||
|
// Used by parallel boot workers. No manifest writing — the runtime handles that.
|
||||||
|
Build.compile_c_module = function(pkg, file, target, opts) {
|
||||||
|
var _target = target || Build.detect_host_target()
|
||||||
|
var _opts = opts || {}
|
||||||
|
var _buildtype = _opts.buildtype || 'release'
|
||||||
|
var pkg_dir = shop.get_package_dir(pkg)
|
||||||
|
var cached_cflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'CFLAGS', _target), pkg_dir)
|
||||||
|
|
||||||
|
// Compile support sources to cached objects (content-addressed, safe for concurrent workers)
|
||||||
|
var sources = pkg_tools.get_sources(pkg)
|
||||||
|
var support_objects = []
|
||||||
|
if (pkg != 'core') {
|
||||||
|
arrfor(sources, function(src_file) {
|
||||||
|
var obj = Build.compile_file(pkg, src_file, _target, {buildtype: _buildtype, cflags: cached_cflags})
|
||||||
|
if (obj != null) support_objects[] = obj
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return Build.build_module_dylib(pkg, file, _target, {buildtype: _buildtype, extra_objects: support_objects, cflags: cached_cflags})
|
||||||
|
}
|
||||||
|
|
||||||
// Build a dynamic library for a package (one dylib per C file)
|
// Build a dynamic library for a package (one dylib per C file)
|
||||||
// Returns array of {file, symbol, dylib} for each module
|
// Returns array of {file, symbol, dylib} for each module
|
||||||
// Also writes a manifest mapping symbols to dylib paths
|
// Also writes a manifest mapping symbols to dylib paths
|
||||||
@@ -723,6 +753,9 @@ Build.build_dynamic = function(pkg, target, buildtype, opts) {
|
|||||||
var _opts = opts || {}
|
var _opts = opts || {}
|
||||||
var c_files = pkg_tools.get_c_files(pkg, _target, true)
|
var c_files = pkg_tools.get_c_files(pkg, _target, true)
|
||||||
var results = []
|
var results = []
|
||||||
|
var total = length(c_files)
|
||||||
|
var done = 0
|
||||||
|
var failed = 0
|
||||||
|
|
||||||
// Pre-fetch cflags once to avoid repeated TOML reads
|
// Pre-fetch cflags once to avoid repeated TOML reads
|
||||||
var pkg_dir = shop.get_package_dir(pkg)
|
var pkg_dir = shop.get_package_dir(pkg)
|
||||||
@@ -734,7 +767,7 @@ Build.build_dynamic = function(pkg, target, buildtype, opts) {
|
|||||||
if (pkg != 'core') {
|
if (pkg != 'core') {
|
||||||
arrfor(sources, function(src_file) {
|
arrfor(sources, function(src_file) {
|
||||||
var obj = Build.compile_file(pkg, src_file, _target, {buildtype: _buildtype, cflags: cached_cflags, verbose: _opts.verbose, force: _opts.force})
|
var obj = Build.compile_file(pkg, src_file, _target, {buildtype: _buildtype, cflags: cached_cflags, verbose: _opts.verbose, force: _opts.force})
|
||||||
if (obj != null) push(support_objects, obj)
|
if (obj != null) support_objects[] = obj
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -742,10 +775,16 @@ Build.build_dynamic = function(pkg, target, buildtype, opts) {
|
|||||||
var sym_name = shop.c_symbol_for_file(pkg, file)
|
var sym_name = shop.c_symbol_for_file(pkg, file)
|
||||||
var dylib = Build.build_module_dylib(pkg, file, _target, {buildtype: _buildtype, extra_objects: support_objects, cflags: cached_cflags, verbose: _opts.verbose, force: _opts.force})
|
var dylib = Build.build_module_dylib(pkg, file, _target, {buildtype: _buildtype, extra_objects: support_objects, cflags: cached_cflags, verbose: _opts.verbose, force: _opts.force})
|
||||||
if (dylib) {
|
if (dylib) {
|
||||||
push(results, {file: file, symbol: sym_name, dylib: dylib})
|
results[] = {file: file, symbol: sym_name, dylib: dylib}
|
||||||
|
} else {
|
||||||
|
failed = failed + 1
|
||||||
}
|
}
|
||||||
|
done = done + 1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (total > 0)
|
||||||
|
log.build(` Building C modules (${text(done)} ok${failed > 0 ? `, ${text(failed)} failed` : ''})`)
|
||||||
|
|
||||||
// Write manifest so runtime can find dylibs without the build module
|
// Write manifest so runtime can find dylibs without the build module
|
||||||
var mpath = manifest_path(pkg)
|
var mpath = manifest_path(pkg)
|
||||||
fd.slurpwrite(mpath, stone(blob(json.encode(results))))
|
fd.slurpwrite(mpath, stone(blob(json.encode(results))))
|
||||||
@@ -775,7 +814,7 @@ Build.build_static = function(packages, target, output, buildtype) {
|
|||||||
var objects = Build.build_package(pkg, _target, !is_core, _buildtype)
|
var objects = Build.build_package(pkg, _target, !is_core, _buildtype)
|
||||||
|
|
||||||
arrfor(objects, function(obj) {
|
arrfor(objects, function(obj) {
|
||||||
push(all_objects, obj)
|
all_objects[] = obj
|
||||||
})
|
})
|
||||||
|
|
||||||
// Collect LDFLAGS (with sigil replacement)
|
// Collect LDFLAGS (with sigil replacement)
|
||||||
@@ -795,7 +834,7 @@ Build.build_static = function(packages, target, output, buildtype) {
|
|||||||
f = '-L"' + pkg_dir + '/' + lpath + '"'
|
f = '-L"' + pkg_dir + '/' + lpath + '"'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
push(all_ldflags, f)
|
all_ldflags[] = f
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -817,18 +856,18 @@ Build.build_static = function(packages, target, output, buildtype) {
|
|||||||
var cmd_parts = [cc]
|
var cmd_parts = [cc]
|
||||||
|
|
||||||
arrfor(all_objects, function(obj) {
|
arrfor(all_objects, function(obj) {
|
||||||
push(cmd_parts, '"' + obj + '"')
|
cmd_parts[] = '"' + obj + '"'
|
||||||
})
|
})
|
||||||
|
|
||||||
arrfor(all_ldflags, function(flag) {
|
arrfor(all_ldflags, function(flag) {
|
||||||
push(cmd_parts, flag)
|
cmd_parts[] = flag
|
||||||
})
|
})
|
||||||
|
|
||||||
arrfor(target_ldflags, function(flag) {
|
arrfor(target_ldflags, function(flag) {
|
||||||
push(cmd_parts, flag)
|
cmd_parts[] = flag
|
||||||
})
|
})
|
||||||
|
|
||||||
push(cmd_parts, '-o', '"' + out_path + '"')
|
cmd_parts[] = '-o', '"' + out_path + '"'
|
||||||
|
|
||||||
var cmd_str = text(cmd_parts, ' ')
|
var cmd_str = text(cmd_parts, ' ')
|
||||||
|
|
||||||
@@ -882,7 +921,7 @@ function qbe_insert_dead_labels(il_text) {
|
|||||||
line = lines[i]
|
line = lines[i]
|
||||||
trimmed = trim(line)
|
trimmed = trim(line)
|
||||||
if (need_label && !starts_with(trimmed, '@') && !starts_with(trimmed, '}') && length(trimmed) > 0) {
|
if (need_label && !starts_with(trimmed, '@') && !starts_with(trimmed, '}') && length(trimmed) > 0) {
|
||||||
push(result, "@_dead_" + text(dead_id))
|
result[] = "@_dead_" + text(dead_id)
|
||||||
dead_id = dead_id + 1
|
dead_id = dead_id + 1
|
||||||
need_label = false
|
need_label = false
|
||||||
}
|
}
|
||||||
@@ -892,7 +931,7 @@ function qbe_insert_dead_labels(il_text) {
|
|||||||
if (starts_with(trimmed, 'ret ') || starts_with(trimmed, 'jmp ')) {
|
if (starts_with(trimmed, 'ret ') || starts_with(trimmed, 'jmp ')) {
|
||||||
need_label = true
|
need_label = true
|
||||||
}
|
}
|
||||||
push(result, line)
|
result[] = line
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
return text(result, "\n")
|
return text(result, "\n")
|
||||||
@@ -920,9 +959,17 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
|
|||||||
var qbe_emit = use('qbe_emit')
|
var qbe_emit = use('qbe_emit')
|
||||||
|
|
||||||
// Step 2: Generate QBE IL
|
// Step 2: Generate QBE IL
|
||||||
|
// Derive package from the file itself (not the caller's context) to ensure correct symbol names
|
||||||
var sym_name = null
|
var sym_name = null
|
||||||
if (pkg) {
|
var _file_info = shop.file_info(src_path)
|
||||||
sym_name = shop.c_symbol_for_file(pkg, fd.basename(src_path))
|
var _actual_pkg = _file_info.package || pkg
|
||||||
|
var _sym_stem = null
|
||||||
|
if (_actual_pkg) {
|
||||||
|
if (_file_info.name)
|
||||||
|
_sym_stem = _file_info.name + (_file_info.is_actor ? '.ce' : '.cm')
|
||||||
|
else
|
||||||
|
_sym_stem = fd.basename(src_path)
|
||||||
|
sym_name = shop.c_symbol_for_file(_actual_pkg, _sym_stem)
|
||||||
}
|
}
|
||||||
var il_parts = qbe_emit(optimized, qbe_macros, sym_name)
|
var il_parts = qbe_emit(optimized, qbe_macros, sym_name)
|
||||||
|
|
||||||
@@ -971,7 +1018,7 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
|
|||||||
log.error('Linking native dylib failed for: ' + src_path); disrupt
|
log.error('Linking native dylib failed for: ' + src_path); disrupt
|
||||||
}
|
}
|
||||||
|
|
||||||
log.console('Built native: ' + fd.basename(dylib_path))
|
log.shop('compiled native: ' + src_path)
|
||||||
|
|
||||||
return dylib_path
|
return dylib_path
|
||||||
}
|
}
|
||||||
@@ -993,9 +1040,17 @@ Build.compile_native_ir = function(optimized, src_path, opts) {
|
|||||||
var qbe_macros = use('qbe')
|
var qbe_macros = use('qbe')
|
||||||
var qbe_emit = use('qbe_emit')
|
var qbe_emit = use('qbe_emit')
|
||||||
|
|
||||||
|
// Derive package from the file itself (not the caller's context)
|
||||||
var sym_name = null
|
var sym_name = null
|
||||||
if (pkg) {
|
var _file_info2 = shop.file_info(src_path)
|
||||||
sym_name = shop.c_symbol_for_file(pkg, fd.basename(src_path))
|
var _actual_pkg2 = _file_info2.package || pkg
|
||||||
|
var _sym_stem2 = null
|
||||||
|
if (_actual_pkg2) {
|
||||||
|
if (_file_info2.name)
|
||||||
|
_sym_stem2 = _file_info2.name + (_file_info2.is_actor ? '.ce' : '.cm')
|
||||||
|
else
|
||||||
|
_sym_stem2 = fd.basename(src_path)
|
||||||
|
sym_name = shop.c_symbol_for_file(_actual_pkg2, _sym_stem2)
|
||||||
}
|
}
|
||||||
var il_parts = qbe_emit(optimized, qbe_macros, sym_name)
|
var il_parts = qbe_emit(optimized, qbe_macros, sym_name)
|
||||||
|
|
||||||
@@ -1043,7 +1098,7 @@ Build.compile_native_ir = function(optimized, src_path, opts) {
|
|||||||
log.error('Linking native dylib failed for: ' + src_path); disrupt
|
log.error('Linking native dylib failed for: ' + src_path); disrupt
|
||||||
}
|
}
|
||||||
|
|
||||||
log.console('Built native: ' + fd.basename(dylib_path))
|
log.shop('compiled native: ' + src_path)
|
||||||
|
|
||||||
return dylib_path
|
return dylib_path
|
||||||
}
|
}
|
||||||
@@ -1067,16 +1122,16 @@ Build.compile_cm_to_mach = function(src_path) {
|
|||||||
// output: path to write the generated .c file
|
// output: path to write the generated .c file
|
||||||
Build.generate_module_table = function(modules, output) {
|
Build.generate_module_table = function(modules, output) {
|
||||||
var lines = []
|
var lines = []
|
||||||
push(lines, '/* Generated module table — do not edit */')
|
lines[] = '/* Generated module table — do not edit */'
|
||||||
push(lines, '#include <stddef.h>')
|
lines[] = '#include <stddef.h>'
|
||||||
push(lines, '#include <string.h>')
|
lines[] = '#include <string.h>'
|
||||||
push(lines, '')
|
lines[] = ''
|
||||||
push(lines, 'struct cell_embedded_entry {')
|
lines[] = 'struct cell_embedded_entry {'
|
||||||
push(lines, ' const char *name;')
|
lines[] = ' const char *name;'
|
||||||
push(lines, ' const unsigned char *data;')
|
lines[] = ' const unsigned char *data;'
|
||||||
push(lines, ' size_t size;')
|
lines[] = ' size_t size;'
|
||||||
push(lines, '};')
|
lines[] = '};'
|
||||||
push(lines, '')
|
lines[] = ''
|
||||||
|
|
||||||
var entries = []
|
var entries = []
|
||||||
arrfor(modules, function(mod) {
|
arrfor(modules, function(mod) {
|
||||||
@@ -1085,27 +1140,27 @@ Build.generate_module_table = function(modules, output) {
|
|||||||
var bytes = array(mach)
|
var bytes = array(mach)
|
||||||
var hex = []
|
var hex = []
|
||||||
arrfor(bytes, function(b) {
|
arrfor(bytes, function(b) {
|
||||||
push(hex, '0x' + text(b, 'h2'))
|
hex[] = '0x' + text(b, 'h2')
|
||||||
})
|
})
|
||||||
push(lines, 'static const unsigned char mod_' + safe + '_data[] = {')
|
lines[] = 'static const unsigned char mod_' + safe + '_data[] = {'
|
||||||
push(lines, ' ' + text(hex, ', '))
|
lines[] = ' ' + text(hex, ', ')
|
||||||
push(lines, '};')
|
lines[] = '};'
|
||||||
push(lines, '')
|
lines[] = ''
|
||||||
push(entries, safe)
|
entries[] = safe
|
||||||
log.console('Embedded: ' + mod.name + ' (' + text(length(bytes)) + ' bytes)')
|
log.console('Embedded: ' + mod.name + ' (' + text(length(bytes)) + ' bytes)')
|
||||||
})
|
})
|
||||||
|
|
||||||
// Lookup function
|
// Lookup function
|
||||||
push(lines, 'const struct cell_embedded_entry *cell_embedded_module_lookup(const char *name) {')
|
lines[] = 'const struct cell_embedded_entry *cell_embedded_module_lookup(const char *name) {'
|
||||||
arrfor(modules, function(mod, i) {
|
arrfor(modules, function(mod, i) {
|
||||||
var safe = entries[i]
|
var safe = entries[i]
|
||||||
push(lines, ' if (strcmp(name, "' + mod.name + '") == 0) {')
|
lines[] = ' if (strcmp(name, "' + mod.name + '") == 0) {'
|
||||||
push(lines, ' static const struct cell_embedded_entry e = {"' + mod.name + '", mod_' + safe + '_data, sizeof(mod_' + safe + '_data)};')
|
lines[] = ' static const struct cell_embedded_entry e = {"' + mod.name + '", mod_' + safe + '_data, sizeof(mod_' + safe + '_data)};'
|
||||||
push(lines, ' return &e;')
|
lines[] = ' return &e;'
|
||||||
push(lines, ' }')
|
lines[] = ' }'
|
||||||
})
|
})
|
||||||
push(lines, ' return (void *)0;')
|
lines[] = ' return (void *)0;'
|
||||||
push(lines, '}')
|
lines[] = '}'
|
||||||
|
|
||||||
var c_text = text(lines, '\n')
|
var c_text = text(lines, '\n')
|
||||||
fd.slurpwrite(output, stone(blob(c_text)))
|
fd.slurpwrite(output, stone(blob(c_text)))
|
||||||
@@ -1133,14 +1188,14 @@ Build.build_all_dynamic = function(target, buildtype, opts) {
|
|||||||
// Build core first
|
// Build core first
|
||||||
if (find(packages, function(p) { return p == 'core' }) != null) {
|
if (find(packages, function(p) { return p == 'core' }) != null) {
|
||||||
core_mods = Build.build_dynamic('core', _target, _buildtype, _opts)
|
core_mods = Build.build_dynamic('core', _target, _buildtype, _opts)
|
||||||
push(results, {package: 'core', modules: core_mods})
|
results[] = {package: 'core', modules: core_mods}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build other packages
|
// Build other packages
|
||||||
arrfor(packages, function(pkg) {
|
arrfor(packages, function(pkg) {
|
||||||
if (pkg == 'core') return
|
if (pkg == 'core') return
|
||||||
var pkg_mods = Build.build_dynamic(pkg, _target, _buildtype, _opts)
|
var pkg_mods = Build.build_dynamic(pkg, _target, _buildtype, _opts)
|
||||||
push(results, {package: pkg, modules: pkg_mods})
|
results[] = {package: pkg, modules: pkg_mods}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Print build report
|
// Print build report
|
||||||
|
|||||||
174
cellfs.cm
174
cellfs.cm
@@ -4,10 +4,11 @@ var fd = use('fd')
|
|||||||
var miniz = use('miniz')
|
var miniz = use('miniz')
|
||||||
var qop = use('internal/qop')
|
var qop = use('internal/qop')
|
||||||
var wildstar = use('internal/wildstar')
|
var wildstar = use('internal/wildstar')
|
||||||
|
var blib = use('blob')
|
||||||
|
|
||||||
var mounts = []
|
var mounts = []
|
||||||
|
|
||||||
var writepath = "."
|
var write_mount = null
|
||||||
|
|
||||||
function normalize_path(path) {
|
function normalize_path(path) {
|
||||||
if (!path) return ""
|
if (!path) return ""
|
||||||
@@ -30,7 +31,7 @@ function mount_exists(mount, path) {
|
|||||||
result = mount.handle.stat(path) != null
|
result = mount.handle.stat(path) != null
|
||||||
} disruption {}
|
} disruption {}
|
||||||
_check()
|
_check()
|
||||||
} else {
|
} else if (mount.type == 'fs') {
|
||||||
full_path = fd.join_paths(mount.source, path)
|
full_path = fd.join_paths(mount.source, path)
|
||||||
_check = function() {
|
_check = function() {
|
||||||
st = fd.stat(full_path)
|
st = fd.stat(full_path)
|
||||||
@@ -119,12 +120,12 @@ function resolve(path, must_exist) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mount(source, name) {
|
function mount(source, name) {
|
||||||
var st = fd.stat(source)
|
var st = null
|
||||||
var blob = null
|
var blob = null
|
||||||
var qop_archive = null
|
var qop_archive = null
|
||||||
var zip = null
|
var zip = null
|
||||||
|
|
||||||
var _try_qop = null
|
var _try_qop = null
|
||||||
|
var http = null
|
||||||
|
|
||||||
var mount_info = {
|
var mount_info = {
|
||||||
source: source,
|
source: source,
|
||||||
@@ -134,6 +135,29 @@ function mount(source, name) {
|
|||||||
zip_blob: null
|
zip_blob: null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (starts_with(source, 'http://') || starts_with(source, 'https://')) {
|
||||||
|
http = use('http')
|
||||||
|
mount_info.type = 'http'
|
||||||
|
mount_info.handle = {
|
||||||
|
base_url: source,
|
||||||
|
get: function(path, callback) {
|
||||||
|
var url = source + '/' + path
|
||||||
|
$clock(function(_t) {
|
||||||
|
var resp = http.request('GET', url, null, null)
|
||||||
|
if (resp && resp.status == 200) {
|
||||||
|
callback(resp.body)
|
||||||
|
} else {
|
||||||
|
callback(null, "HTTP " + text(resp ? resp.status : 0) + ": " + url)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mounts[] = mount_info
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
st = fd.stat(source)
|
||||||
|
|
||||||
if (st.isDirectory) {
|
if (st.isDirectory) {
|
||||||
mount_info.type = 'fs'
|
mount_info.type = 'fs'
|
||||||
} else if (st.isFile) {
|
} else if (st.isFile) {
|
||||||
@@ -146,24 +170,24 @@ function mount(source, name) {
|
|||||||
_try_qop()
|
_try_qop()
|
||||||
|
|
||||||
if (qop_archive) {
|
if (qop_archive) {
|
||||||
mount_info.type = 'qop'
|
mount_info.type = 'qop'
|
||||||
mount_info.handle = qop_archive
|
mount_info.handle = qop_archive
|
||||||
mount_info.zip_blob = blob
|
mount_info.zip_blob = blob
|
||||||
} else {
|
} else {
|
||||||
zip = miniz.read(blob)
|
zip = miniz.read(blob)
|
||||||
if (!is_object(zip) || !is_function(zip.count)) {
|
if (!is_object(zip) || !is_function(zip.count)) {
|
||||||
log.error("Invalid archive file (not zip or qop): " + source); disrupt
|
log.error("Invalid archive file (not zip or qop): " + source); disrupt
|
||||||
}
|
}
|
||||||
|
|
||||||
mount_info.type = 'zip'
|
mount_info.type = 'zip'
|
||||||
mount_info.handle = zip
|
mount_info.handle = zip
|
||||||
mount_info.zip_blob = blob
|
mount_info.zip_blob = blob
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.error("Unsupported mount source type: " + source); disrupt
|
log.error("Unsupported mount source type: " + source); disrupt
|
||||||
}
|
}
|
||||||
|
|
||||||
push(mounts, mount_info)
|
mounts[] = mount_info
|
||||||
}
|
}
|
||||||
|
|
||||||
function unmount(name_or_source) {
|
function unmount(name_or_source) {
|
||||||
@@ -191,11 +215,13 @@ function slurp(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function slurpwrite(path, data) {
|
function slurpwrite(path, data) {
|
||||||
var full_path = writepath + "/" + path
|
var full_path = null
|
||||||
|
if (write_mount) {
|
||||||
var f = fd.open(full_path, 'w')
|
full_path = fd.join_paths(write_mount.source, path)
|
||||||
fd.write(f, data)
|
} else {
|
||||||
fd.close(f)
|
full_path = fd.join_paths(".", path)
|
||||||
|
}
|
||||||
|
fd.slurpwrite(full_path, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
function exists(path) {
|
function exists(path) {
|
||||||
@@ -276,12 +302,25 @@ function rm(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mkdir(path) {
|
function mkdir(path) {
|
||||||
var full = fd.join_paths(writepath, path)
|
var full = null
|
||||||
|
if (write_mount) {
|
||||||
|
full = fd.join_paths(write_mount.source, path)
|
||||||
|
} else {
|
||||||
|
full = fd.join_paths(".", path)
|
||||||
|
}
|
||||||
fd.mkdir(full)
|
fd.mkdir(full)
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_writepath(path) {
|
function set_writepath(mount_name) {
|
||||||
writepath = path
|
var found = null
|
||||||
|
if (mount_name == null) { write_mount = null; return }
|
||||||
|
arrfor(mounts, function(m) {
|
||||||
|
if (m.name == mount_name) { found = m; return true }
|
||||||
|
}, false, true)
|
||||||
|
if (!found || found.type != 'fs') {
|
||||||
|
log.error("writepath: must be an fs mount"); disrupt
|
||||||
|
}
|
||||||
|
write_mount = found
|
||||||
}
|
}
|
||||||
|
|
||||||
function basedir() {
|
function basedir() {
|
||||||
@@ -317,7 +356,7 @@ function enumerate(_path, recurse) {
|
|||||||
arrfor(list, function(item) {
|
arrfor(list, function(item) {
|
||||||
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
|
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
|
||||||
var child_st = null
|
var child_st = null
|
||||||
push(results, item_rel)
|
results[] = item_rel
|
||||||
|
|
||||||
if (recurse) {
|
if (recurse) {
|
||||||
child_st = fd.stat(fd.join_paths(curr_full, item))
|
child_st = fd.stat(fd.join_paths(curr_full, item))
|
||||||
@@ -357,7 +396,7 @@ function enumerate(_path, recurse) {
|
|||||||
|
|
||||||
if (!seen[rel]) {
|
if (!seen[rel]) {
|
||||||
seen[rel] = true
|
seen[rel] = true
|
||||||
push(results, rel)
|
results[] = rel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -416,7 +455,7 @@ function globfs(globs, _dir) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!check_neg(item_rel) && check_pos(item_rel)) {
|
if (!check_neg(item_rel) && check_pos(item_rel)) {
|
||||||
push(results, item_rel)
|
results[] = item_rel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -440,7 +479,7 @@ function globfs(globs, _dir) {
|
|||||||
if (length(rel) == 0) return
|
if (length(rel) == 0) return
|
||||||
|
|
||||||
if (!check_neg(rel) && check_pos(rel)) {
|
if (!check_neg(rel) && check_pos(rel)) {
|
||||||
push(results, rel)
|
results[] = rel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -449,6 +488,82 @@ function globfs(globs, _dir) {
|
|||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Requestor factory: returns a requestor for reading a file at path
|
||||||
|
function get(path) {
|
||||||
|
return function get_requestor(callback, value) {
|
||||||
|
var res = resolve(path, false)
|
||||||
|
var full = null
|
||||||
|
var f = null
|
||||||
|
var acc = null
|
||||||
|
var cancelled = false
|
||||||
|
var data = null
|
||||||
|
var _close = null
|
||||||
|
|
||||||
|
if (!res) { callback(null, "not found: " + path); return }
|
||||||
|
|
||||||
|
if (res.mount.type == 'zip') {
|
||||||
|
callback(res.mount.handle.slurp(res.path))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (res.mount.type == 'qop') {
|
||||||
|
data = res.mount.handle.read(res.path)
|
||||||
|
if (data) {
|
||||||
|
callback(data)
|
||||||
|
} else {
|
||||||
|
callback(null, "not found in qop: " + path)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (res.mount.type == 'http') {
|
||||||
|
res.mount.handle.get(res.path, callback)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
full = fd.join_paths(res.mount.source, res.path)
|
||||||
|
f = fd.open(full, 'r')
|
||||||
|
acc = blob()
|
||||||
|
|
||||||
|
function next(_t) {
|
||||||
|
var chunk = null
|
||||||
|
if (cancelled) return
|
||||||
|
chunk = fd.read(f, 65536)
|
||||||
|
if (length(chunk) == 0) {
|
||||||
|
fd.close(f)
|
||||||
|
stone(acc)
|
||||||
|
callback(acc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
acc.write_blob(chunk)
|
||||||
|
$clock(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
|
return function cancel() {
|
||||||
|
cancelled = true
|
||||||
|
_close = function() { fd.close(f) } disruption {}
|
||||||
|
_close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Requestor factory: returns a requestor for writing data to path
|
||||||
|
function put(path, data) {
|
||||||
|
return function put_requestor(callback, value) {
|
||||||
|
var _data = data != null ? data : value
|
||||||
|
var full = null
|
||||||
|
var _do = null
|
||||||
|
if (!write_mount) { callback(null, "no write mount set"); return }
|
||||||
|
full = fd.join_paths(write_mount.source, path)
|
||||||
|
_do = function() {
|
||||||
|
fd.slurpwrite(full, _data)
|
||||||
|
callback(true)
|
||||||
|
} disruption {
|
||||||
|
callback(null, "write failed: " + path)
|
||||||
|
}
|
||||||
|
_do()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cellfs.mount = mount
|
cellfs.mount = mount
|
||||||
cellfs.mount_package = mount_package
|
cellfs.mount_package = mount_package
|
||||||
cellfs.unmount = unmount
|
cellfs.unmount = unmount
|
||||||
@@ -467,7 +582,8 @@ cellfs.writepath = set_writepath
|
|||||||
cellfs.basedir = basedir
|
cellfs.basedir = basedir
|
||||||
cellfs.prefdir = prefdir
|
cellfs.prefdir = prefdir
|
||||||
cellfs.realdir = realdir
|
cellfs.realdir = realdir
|
||||||
|
cellfs.get = get
|
||||||
cellfs.mount('.')
|
cellfs.put = put
|
||||||
|
cellfs.resolve = resolve
|
||||||
|
|
||||||
return cellfs
|
return cellfs
|
||||||
|
|||||||
18
cfg.ce
18
cfg.ce
@@ -168,7 +168,7 @@ var run = function() {
|
|||||||
if (is_array(instr)) {
|
if (is_array(instr)) {
|
||||||
if (block_start_pcs[text(pc)]) {
|
if (block_start_pcs[text(pc)]) {
|
||||||
if (current_block != null) {
|
if (current_block != null) {
|
||||||
push(blocks, current_block)
|
blocks[] = current_block
|
||||||
}
|
}
|
||||||
current_block = {
|
current_block = {
|
||||||
id: length(blocks),
|
id: length(blocks),
|
||||||
@@ -184,7 +184,7 @@ var run = function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (current_block != null) {
|
if (current_block != null) {
|
||||||
push(current_block.instrs, {pc: pc, instr: instr})
|
current_block.instrs[] = {pc: pc, instr: instr}
|
||||||
current_block.end_pc = pc
|
current_block.end_pc = pc
|
||||||
n = length(instr)
|
n = length(instr)
|
||||||
line_num = instr[n - 2]
|
line_num = instr[n - 2]
|
||||||
@@ -200,7 +200,7 @@ var run = function() {
|
|||||||
ii = ii + 1
|
ii = ii + 1
|
||||||
}
|
}
|
||||||
if (current_block != null) {
|
if (current_block != null) {
|
||||||
push(blocks, current_block)
|
blocks[] = current_block
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build block index
|
// Build block index
|
||||||
@@ -235,19 +235,19 @@ var run = function() {
|
|||||||
if (target_bi <= bi) {
|
if (target_bi <= bi) {
|
||||||
edge_type = "loop back-edge"
|
edge_type = "loop back-edge"
|
||||||
}
|
}
|
||||||
push(blk.edges, {target: target_bi, kind: edge_type})
|
blk.edges[] = {target: target_bi, kind: edge_type}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_conditional_jump(last_op)) {
|
if (is_conditional_jump(last_op)) {
|
||||||
if (bi + 1 < length(blocks)) {
|
if (bi + 1 < length(blocks)) {
|
||||||
push(blk.edges, {target: bi + 1, kind: "fallthrough"})
|
blk.edges[] = {target: bi + 1, kind: "fallthrough"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (is_terminator(last_op)) {
|
} else if (is_terminator(last_op)) {
|
||||||
push(blk.edges, {target: -1, kind: "EXIT (" + last_op + ")"})
|
blk.edges[] = {target: -1, kind: "EXIT (" + last_op + ")"}
|
||||||
} else {
|
} else {
|
||||||
if (bi + 1 < length(blocks)) {
|
if (bi + 1 < length(blocks)) {
|
||||||
push(blk.edges, {target: bi + 1, kind: "fallthrough"})
|
blk.edges[] = {target: bi + 1, kind: "fallthrough"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,7 +308,7 @@ var run = function() {
|
|||||||
parts = []
|
parts = []
|
||||||
j = 1
|
j = 1
|
||||||
while (j < n - 2) {
|
while (j < n - 2) {
|
||||||
push(parts, fmt_val(instr[j]))
|
parts[] = fmt_val(instr[j])
|
||||||
j = j + 1
|
j = j + 1
|
||||||
}
|
}
|
||||||
operands = text(parts, ", ")
|
operands = text(parts, ", ")
|
||||||
@@ -381,7 +381,7 @@ var run = function() {
|
|||||||
parts = []
|
parts = []
|
||||||
j = 1
|
j = 1
|
||||||
while (j < n - 2) {
|
while (j < n - 2) {
|
||||||
push(parts, fmt_val(instr[j]))
|
parts[] = fmt_val(instr[j])
|
||||||
j = j + 1
|
j = j + 1
|
||||||
}
|
}
|
||||||
operands = text(parts, ", ")
|
operands = text(parts, ", ")
|
||||||
|
|||||||
12
clean.ce
12
clean.ce
@@ -93,13 +93,13 @@ if (is_shop_scope) {
|
|||||||
packages_to_clean = shop.list_packages()
|
packages_to_clean = shop.list_packages()
|
||||||
} else {
|
} else {
|
||||||
// Single package
|
// Single package
|
||||||
push(packages_to_clean, scope)
|
packages_to_clean[] = scope
|
||||||
|
|
||||||
if (deep) {
|
if (deep) {
|
||||||
_gather = function() {
|
_gather = function() {
|
||||||
deps = pkg.gather_dependencies(scope)
|
deps = pkg.gather_dependencies(scope)
|
||||||
arrfor(deps, function(dep) {
|
arrfor(deps, function(dep) {
|
||||||
push(packages_to_clean, dep)
|
packages_to_clean[] = dep
|
||||||
})
|
})
|
||||||
} disruption {
|
} disruption {
|
||||||
// Skip if can't read dependencies
|
// Skip if can't read dependencies
|
||||||
@@ -116,11 +116,11 @@ var packages_dir = replace(shop.get_package_dir(''), /\/$/, '') // Get base pack
|
|||||||
if (clean_build) {
|
if (clean_build) {
|
||||||
// Nuke entire build cache (content-addressed, per-package clean impractical)
|
// Nuke entire build cache (content-addressed, per-package clean impractical)
|
||||||
if (fd.is_dir(build_dir)) {
|
if (fd.is_dir(build_dir)) {
|
||||||
push(dirs_to_delete, build_dir)
|
dirs_to_delete[] = build_dir
|
||||||
}
|
}
|
||||||
// Clean orphaned lib/ directory if it exists (legacy)
|
// Clean orphaned lib/ directory if it exists (legacy)
|
||||||
if (fd.is_dir(lib_dir)) {
|
if (fd.is_dir(lib_dir)) {
|
||||||
push(dirs_to_delete, lib_dir)
|
dirs_to_delete[] = lib_dir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ if (clean_fetch) {
|
|||||||
if (is_shop_scope) {
|
if (is_shop_scope) {
|
||||||
// Clean entire packages directory (dangerous!)
|
// Clean entire packages directory (dangerous!)
|
||||||
if (fd.is_dir(packages_dir)) {
|
if (fd.is_dir(packages_dir)) {
|
||||||
push(dirs_to_delete, packages_dir)
|
dirs_to_delete[] = packages_dir
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Clean specific package directories
|
// Clean specific package directories
|
||||||
@@ -137,7 +137,7 @@ if (clean_fetch) {
|
|||||||
|
|
||||||
var pkg_dir = shop.get_package_dir(p)
|
var pkg_dir = shop.get_package_dir(p)
|
||||||
if (fd.is_dir(pkg_dir) || fd.is_link(pkg_dir)) {
|
if (fd.is_dir(pkg_dir) || fd.is_link(pkg_dir)) {
|
||||||
push(dirs_to_delete, pkg_dir)
|
dirs_to_delete[] = pkg_dir
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
40
compile_worker.ce
Normal file
40
compile_worker.ce
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// compile_worker - Worker actor that compiles a single module and replies
|
||||||
|
//
|
||||||
|
// Receives a message with:
|
||||||
|
// {type: 'script', path, package} — bytecode compile
|
||||||
|
// {type: 'native_script', path, package} — native compile
|
||||||
|
// {type: 'c_package', package} — C package build
|
||||||
|
// {type: 'c_file', package, file} — single C module build
|
||||||
|
//
|
||||||
|
// Replies with {ok: true/false, path} and stops.
|
||||||
|
|
||||||
|
var shop = use('internal/shop')
|
||||||
|
var build = use('build')
|
||||||
|
|
||||||
|
$receiver(function(msg) {
|
||||||
|
var name = msg.path || (msg.file ? msg.package + '/' + msg.file : msg.package)
|
||||||
|
var _work = function() {
|
||||||
|
if (msg.type == 'script') {
|
||||||
|
log.console('compile_worker: compiling ' + name)
|
||||||
|
shop.precompile(msg.path, msg.package)
|
||||||
|
} else if (msg.type == 'native_script') {
|
||||||
|
log.console('compile_worker: native compiling ' + name)
|
||||||
|
build.compile_native(msg.path, null, null, msg.package)
|
||||||
|
} else if (msg.type == 'c_package') {
|
||||||
|
log.console('compile_worker: building package ' + name)
|
||||||
|
build.build_dynamic(msg.package, null, null, null)
|
||||||
|
} else if (msg.type == 'c_file') {
|
||||||
|
log.console('compile_worker: building ' + name)
|
||||||
|
build.compile_c_module(msg.package, msg.file)
|
||||||
|
}
|
||||||
|
log.console('compile_worker: done ' + name)
|
||||||
|
send(msg, {ok: true, path: name})
|
||||||
|
} disruption {
|
||||||
|
log.error('compile_worker: failed ' + name)
|
||||||
|
send(msg, {ok: false, error: 'compile failed'})
|
||||||
|
}
|
||||||
|
_work()
|
||||||
|
$stop()
|
||||||
|
})
|
||||||
|
|
||||||
|
var _t = $delay($stop, 120)
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "cell.h"
|
#include "cell.h"
|
||||||
|
#include "pit_internal.h"
|
||||||
|
|
||||||
JSC_CCALL(os_mem_limit, JS_SetMemoryLimit(JS_GetRuntime(js), js2number(js,argv[0])))
|
JSC_CCALL(os_mem_limit, JS_SetMemoryLimit(JS_GetRuntime(js), js2number(js,argv[0])))
|
||||||
JSC_CCALL(os_max_stacksize, JS_SetMaxStackSize(js, js2number(js,argv[0])))
|
JSC_CCALL(os_max_stacksize, JS_SetMaxStackSize(js, js2number(js,argv[0])))
|
||||||
|
|||||||
24
diff.ce
24
diff.ce
@@ -55,7 +55,7 @@ function collect_tests(specific_test) {
|
|||||||
match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
|
match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
|
||||||
if (test_name != match_base) continue
|
if (test_name != match_base) continue
|
||||||
}
|
}
|
||||||
push(test_files, f)
|
test_files[] = f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return test_files
|
return test_files
|
||||||
@@ -100,7 +100,7 @@ function diff_test_file(file_path) {
|
|||||||
src = text(fd.slurp(src_path))
|
src = text(fd.slurp(src_path))
|
||||||
ast = analyze(src, src_path)
|
ast = analyze(src, src_path)
|
||||||
} disruption {
|
} disruption {
|
||||||
push(results.errors, `failed to parse ${file_path}`)
|
results.errors[] = `failed to parse ${file_path}`
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
_read()
|
_read()
|
||||||
@@ -124,14 +124,14 @@ function diff_test_file(file_path) {
|
|||||||
|
|
||||||
// Compare module-level behavior
|
// Compare module-level behavior
|
||||||
if (opt_error != noopt_error) {
|
if (opt_error != noopt_error) {
|
||||||
push(results.errors, `module load mismatch: opt=${opt_error != null ? opt_error : "ok"} noopt=${noopt_error != null ? noopt_error : "ok"}`)
|
results.errors[] = `module load mismatch: opt=${opt_error != null ? opt_error : "ok"} noopt=${noopt_error != null ? noopt_error : "ok"}`
|
||||||
results.failed = results.failed + 1
|
results.failed = results.failed + 1
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
if (opt_error != null) {
|
if (opt_error != null) {
|
||||||
// Both disrupted during load — that's consistent
|
// Both disrupted during load — that's consistent
|
||||||
results.passed = results.passed + 1
|
results.passed = results.passed + 1
|
||||||
push(results.tests, {name: "<module>", status: "passed"})
|
results.tests[] = {name: "<module>", status: "passed"}
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,15 +161,15 @@ function diff_test_file(file_path) {
|
|||||||
_run_one_noopt()
|
_run_one_noopt()
|
||||||
|
|
||||||
if (opt_err != noopt_err) {
|
if (opt_err != noopt_err) {
|
||||||
push(results.tests, {name: k, status: "failed"})
|
results.tests[] = {name: k, status: "failed"}
|
||||||
push(results.errors, `${k}: disruption mismatch opt=${opt_err != null ? opt_err : "ok"} noopt=${noopt_err != null ? noopt_err : "ok"}`)
|
results.errors[] = `${k}: disruption mismatch opt=${opt_err != null ? opt_err : "ok"} noopt=${noopt_err != null ? noopt_err : "ok"}`
|
||||||
results.failed = results.failed + 1
|
results.failed = results.failed + 1
|
||||||
} else if (!values_equal(opt_result, noopt_result)) {
|
} else if (!values_equal(opt_result, noopt_result)) {
|
||||||
push(results.tests, {name: k, status: "failed"})
|
results.tests[] = {name: k, status: "failed"}
|
||||||
push(results.errors, `${k}: result mismatch opt=${describe(opt_result)} noopt=${describe(noopt_result)}`)
|
results.errors[] = `${k}: result mismatch opt=${describe(opt_result)} noopt=${describe(noopt_result)}`
|
||||||
results.failed = results.failed + 1
|
results.failed = results.failed + 1
|
||||||
} else {
|
} else {
|
||||||
push(results.tests, {name: k, status: "passed"})
|
results.tests[] = {name: k, status: "passed"}
|
||||||
results.passed = results.passed + 1
|
results.passed = results.passed + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,11 +178,11 @@ function diff_test_file(file_path) {
|
|||||||
} else {
|
} else {
|
||||||
// Compare direct return values
|
// Compare direct return values
|
||||||
if (!values_equal(mod_opt, mod_noopt)) {
|
if (!values_equal(mod_opt, mod_noopt)) {
|
||||||
push(results.tests, {name: "<return>", status: "failed"})
|
results.tests[] = {name: "<return>", status: "failed"}
|
||||||
push(results.errors, `return value mismatch: opt=${describe(mod_opt)} noopt=${describe(mod_noopt)}`)
|
results.errors[] = `return value mismatch: opt=${describe(mod_opt)} noopt=${describe(mod_noopt)}`
|
||||||
results.failed = results.failed + 1
|
results.failed = results.failed + 1
|
||||||
} else {
|
} else {
|
||||||
push(results.tests, {name: "<return>", status: "passed"})
|
results.tests[] = {name: "<return>", status: "passed"}
|
||||||
results.passed = results.passed + 1
|
results.passed = results.passed + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ var run = function() {
|
|||||||
var operands = null
|
var operands = null
|
||||||
var line_str = null
|
var line_str = null
|
||||||
while (j < n - 2) {
|
while (j < n - 2) {
|
||||||
push(parts, fmt_val(instr[j]))
|
parts[] = fmt_val(instr[j])
|
||||||
j = j + 1
|
j = j + 1
|
||||||
}
|
}
|
||||||
operands = text(parts, ", ")
|
operands = text(parts, ", ")
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ var run = function() {
|
|||||||
parts = []
|
parts = []
|
||||||
j = 1
|
j = 1
|
||||||
while (j < n - 2) {
|
while (j < n - 2) {
|
||||||
push(parts, fmt_val(instr[j]))
|
parts[] = fmt_val(instr[j])
|
||||||
j = j + 1
|
j = j + 1
|
||||||
}
|
}
|
||||||
operands = text(parts, ", ")
|
operands = text(parts, ", ")
|
||||||
|
|||||||
233
docs/library/probe.md
Normal file
233
docs/library/probe.md
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
---
|
||||||
|
title: "probe"
|
||||||
|
description: "Runtime observability for actors"
|
||||||
|
weight: 90
|
||||||
|
type: "docs"
|
||||||
|
---
|
||||||
|
|
||||||
|
Runtime observability for actors. Register named probe functions on any actor and query them over HTTP while the program runs.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var probe = use('probe')
|
||||||
|
```
|
||||||
|
|
||||||
|
The probe server starts automatically on the first `register()` call, listening on `127.0.0.1:9000`.
|
||||||
|
|
||||||
|
## Registering Probes
|
||||||
|
|
||||||
|
### probe.register(target, probes)
|
||||||
|
|
||||||
|
Register a group of probe functions under a target name. Each probe is a function that receives an `args` record and returns a value.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var probe = use('probe')
|
||||||
|
|
||||||
|
var world = {
|
||||||
|
entities: [
|
||||||
|
{id: 1, name: "player", x: 10, y: 20, hp: 100},
|
||||||
|
{id: 2, name: "goblin", x: 55, y: 30, hp: 40}
|
||||||
|
],
|
||||||
|
tick: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
probe.register("game", {
|
||||||
|
state: function(args) {
|
||||||
|
return world
|
||||||
|
},
|
||||||
|
entities: function(args) {
|
||||||
|
return world.entities
|
||||||
|
},
|
||||||
|
entity: function(args) {
|
||||||
|
return find(world.entities, function(e) {
|
||||||
|
return e.id == args.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
probe.register("render", {
|
||||||
|
info: function(args) {
|
||||||
|
return {fps: 60, draw_calls: 128, batches: 12}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
A target is just a namespace — group related probes under the same target name. Register as many targets as you like; the server starts once and serves them all.
|
||||||
|
|
||||||
|
## HTTP Endpoints
|
||||||
|
|
||||||
|
All responses are JSON with an `ok` field.
|
||||||
|
|
||||||
|
### GET /discover
|
||||||
|
|
||||||
|
Lists all registered targets and their probe names. Designed for tooling — an LLM or dashboard can call this first to learn what's available, then query specific probes.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl http://127.0.0.1:9000/discover
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"targets": {
|
||||||
|
"game": ["state", "entities", "entity"],
|
||||||
|
"render": ["info"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /probe
|
||||||
|
|
||||||
|
Call a single probe function by target and name. Optionally pass arguments.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -X POST -H "Content-Type: application/json" \
|
||||||
|
-d '{"target":"game","name":"state"}' \
|
||||||
|
http://127.0.0.1:9000/probe
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"result": {
|
||||||
|
"entities": [
|
||||||
|
{"id": 1, "name": "player", "x": 10, "y": 20, "hp": 100},
|
||||||
|
{"id": 2, "name": "goblin", "x": 55, "y": 30, "hp": 40}
|
||||||
|
],
|
||||||
|
"tick": 4821
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
With arguments:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -X POST -H "Content-Type: application/json" \
|
||||||
|
-d '{"target":"game","name":"entity","args":{"id":1}}' \
|
||||||
|
http://127.0.0.1:9000/probe
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"result": {"id": 1, "name": "player", "x": 10, "y": 20, "hp": 100}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /snapshot
|
||||||
|
|
||||||
|
Call multiple probes in one request. Returns all results keyed by `target/name`.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -X POST -H "Content-Type: application/json" \
|
||||||
|
-d '{"probes":[{"target":"game","name":"state"},{"target":"render","name":"info"}]}' \
|
||||||
|
http://127.0.0.1:9000/snapshot
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"results": {
|
||||||
|
"game/state": {
|
||||||
|
"entities": [
|
||||||
|
{"id": 1, "name": "player", "x": 10, "y": 20, "hp": 100},
|
||||||
|
{"id": 2, "name": "goblin", "x": 55, "y": 30, "hp": 40}
|
||||||
|
],
|
||||||
|
"tick": 4821
|
||||||
|
},
|
||||||
|
"render/info": {
|
||||||
|
"fps": 60,
|
||||||
|
"draw_calls": 128,
|
||||||
|
"batches": 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Errors
|
||||||
|
|
||||||
|
Unknown paths return 404:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"ok": false, "error": "not found"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Unknown targets or probe names:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"ok": false, "error": "unknown probe: game/nonexistent"}
|
||||||
|
```
|
||||||
|
|
||||||
|
If a probe function disrupts:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"ok": false, "error": "probe failed"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
A game actor with a simulation loop and probe observability:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// game.ce
|
||||||
|
var probe = use('probe')
|
||||||
|
|
||||||
|
var state = {
|
||||||
|
entities: [
|
||||||
|
{id: 1, name: "player", x: 0, y: 0, hp: 100},
|
||||||
|
{id: 2, name: "enemy", x: 50, y: 50, hp: 60}
|
||||||
|
],
|
||||||
|
frame: 0,
|
||||||
|
paused: false
|
||||||
|
}
|
||||||
|
|
||||||
|
probe.register("game", {
|
||||||
|
state: function(args) {
|
||||||
|
return state
|
||||||
|
},
|
||||||
|
entities: function(args) {
|
||||||
|
return state.entities
|
||||||
|
},
|
||||||
|
entity: function(args) {
|
||||||
|
return find(state.entities, function(e) {
|
||||||
|
return e.id == args.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// game loop
|
||||||
|
def tick = function(_) {
|
||||||
|
if (!state.paused) {
|
||||||
|
state.frame = state.frame + 1
|
||||||
|
// ... update entities, physics, AI ...
|
||||||
|
}
|
||||||
|
$delay(tick, 0.016)
|
||||||
|
}
|
||||||
|
$delay(tick, 0.016)
|
||||||
|
```
|
||||||
|
|
||||||
|
While the game runs, query it from a terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ curl -s http://127.0.0.1:9000/discover | jq .targets
|
||||||
|
{
|
||||||
|
"game": ["state", "entities", "entity"]
|
||||||
|
}
|
||||||
|
|
||||||
|
$ curl -s -X POST -d '{"target":"game","name":"state"}' \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
http://127.0.0.1:9000/probe | jq .result.frame
|
||||||
|
7834
|
||||||
|
|
||||||
|
$ curl -s -X POST -d '{"target":"game","name":"entity","args":{"id":1}}' \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
http://127.0.0.1:9000/probe | jq .result
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "player",
|
||||||
|
"x": 142,
|
||||||
|
"y": 87,
|
||||||
|
"hp": 100
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Probes run inside the actor's normal turn, so the values are always consistent — never a half-updated frame.
|
||||||
@@ -9,13 +9,16 @@ Logging in ƿit is channel-based. Any `log.X(value)` call writes to channel `"X"
|
|||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
|
|
||||||
Three channels are conventional:
|
These channels are conventional:
|
||||||
|
|
||||||
| Channel | Usage |
|
| Channel | Usage |
|
||||||
|---------|-------|
|
|---------|-------|
|
||||||
| `log.console(msg)` | General output |
|
| `log.console(msg)` | General output |
|
||||||
| `log.error(msg)` | Errors and warnings |
|
| `log.error(msg)` | Errors |
|
||||||
|
| `log.warn(msg)` | Compiler diagnostics and warnings |
|
||||||
| `log.system(msg)` | Internal system messages |
|
| `log.system(msg)` | Internal system messages |
|
||||||
|
| `log.build(msg)` | Per-file compile/link output |
|
||||||
|
| `log.shop(msg)` | Package management internals |
|
||||||
|
|
||||||
Any name works. `log.debug(msg)` creates channel `"debug"`, `log.perf(msg)` creates `"perf"`, and so on.
|
Any name works. `log.debug(msg)` creates channel `"debug"`, `log.perf(msg)` creates `"perf"`, and so on.
|
||||||
|
|
||||||
@@ -29,16 +32,18 @@ Non-text values are JSON-encoded automatically.
|
|||||||
|
|
||||||
## Default Behavior
|
## Default Behavior
|
||||||
|
|
||||||
With no configuration, a default sink routes `console`, `error`, and `system` to the terminal in pretty format. The `error` channel includes a stack trace by default:
|
With no configuration, a default sink routes `console` and `error` to the terminal in clean format. The `error` channel includes a stack trace by default:
|
||||||
|
|
||||||
```
|
```
|
||||||
[a3f12] [console] server started on port 8080
|
server started on port 8080
|
||||||
[a3f12] [error] connection refused
|
error: connection refused
|
||||||
at handle_request (server.ce:42:3)
|
at handle_request (server.ce:42:3)
|
||||||
at main (main.ce:5:1)
|
at main (main.ce:5:1)
|
||||||
```
|
```
|
||||||
|
|
||||||
The format is `[actor_id] [channel] message`. Error stack traces are always on unless you explicitly configure a sink without them.
|
Clean format prints messages without actor ID or channel prefix. Error messages are prefixed with `error:`. Other formats (`pretty`, `bare`) include actor IDs and are available for debugging. Stack traces are always on for errors unless you explicitly configure a sink without them.
|
||||||
|
|
||||||
|
Channels like `warn`, `build`, and `shop` are not routed to the terminal by default. Enable them with `pit log enable <channel>`.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@@ -67,7 +72,7 @@ exclude = ["console"]
|
|||||||
| Field | Values | Description |
|
| Field | Values | Description |
|
||||||
|-------|--------|-------------|
|
|-------|--------|-------------|
|
||||||
| `type` | `"console"`, `"file"` | Where output goes |
|
| `type` | `"console"`, `"file"` | Where output goes |
|
||||||
| `format` | `"pretty"`, `"bare"`, `"json"` | How output is formatted |
|
| `format` | `"clean"`, `"pretty"`, `"bare"`, `"json"` | How output is formatted |
|
||||||
| `channels` | array of names, or `["*"]` | Which channels this sink receives. Quote `'*'` on the CLI to prevent shell glob expansion. |
|
| `channels` | array of names, or `["*"]` | Which channels this sink receives. Quote `'*'` on the CLI to prevent shell glob expansion. |
|
||||||
| `exclude` | array of names | Channels to skip (useful with `"*"`) |
|
| `exclude` | array of names | Channels to skip (useful with `"*"`) |
|
||||||
| `stack` | array of channel names | Channels that capture a stack trace |
|
| `stack` | array of channel names | Channels that capture a stack trace |
|
||||||
@@ -75,6 +80,13 @@ exclude = ["console"]
|
|||||||
|
|
||||||
### Formats
|
### Formats
|
||||||
|
|
||||||
|
**clean** — CLI-friendly. No actor ID or channel prefix. Error channel messages are prefixed with `error:`. This is the default format.
|
||||||
|
|
||||||
|
```
|
||||||
|
server started on port 8080
|
||||||
|
error: connection refused
|
||||||
|
```
|
||||||
|
|
||||||
**pretty** — human-readable, one line per message. Includes actor ID, channel, source location, and message.
|
**pretty** — human-readable, one line per message. Includes actor ID, channel, source location, and message.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -158,7 +170,10 @@ The `pit log` command manages sinks and reads log files. See [CLI — pit log](/
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
pit log list # show sinks
|
pit log list # show sinks
|
||||||
pit log add terminal console --format=bare --channels=console
|
pit log channels # list channels with enabled/disabled status
|
||||||
|
pit log enable warn # enable a channel on the terminal sink
|
||||||
|
pit log disable warn # disable a channel
|
||||||
|
pit log add terminal console --format=clean --channels=console
|
||||||
pit log add dump file .cell/logs/dump.jsonl '--channels=*' --exclude=console
|
pit log add dump file .cell/logs/dump.jsonl '--channels=*' --exclude=console
|
||||||
pit log add debug console --channels=error,debug --stack=error,debug
|
pit log add debug console --channels=error,debug --stack=error,debug
|
||||||
pit log remove terminal
|
pit log remove terminal
|
||||||
@@ -166,6 +181,16 @@ pit log read dump --lines=20 --channel=error
|
|||||||
pit log tail dump
|
pit log tail dump
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Channel toggling
|
||||||
|
|
||||||
|
The `enable` and `disable` commands modify the terminal sink's channel list without touching other sink configuration. This is the easiest way to opt in to extra output:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pit log enable warn # see compiler warnings
|
||||||
|
pit log enable build # see per-file compile/link output
|
||||||
|
pit log disable warn # hide warnings again
|
||||||
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Development setup
|
### Development setup
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ source/
|
|||||||
|
|
||||||
**`cell_runtime.c`** is the single file that defines the native code contract. It should:
|
**`cell_runtime.c`** is the single file that defines the native code contract. It should:
|
||||||
|
|
||||||
1. Include `quickjs-internal.h` for access to value representation and heap types
|
1. Include `pit_internal.h` for access to value representation and heap types
|
||||||
2. Export all `cell_rt_*` functions with C linkage (no `static`)
|
2. Export all `cell_rt_*` functions with C linkage (no `static`)
|
||||||
3. Keep each function thin — delegate to existing `JS_*` functions where possible
|
3. Keep each function thin — delegate to existing `JS_*` functions where possible
|
||||||
4. Handle GC safety: after any allocation (frame, string, array), callers' frames may have moved
|
4. Handle GC safety: after any allocation (frame, string, array), callers' frames may have moved
|
||||||
|
|||||||
2
fd.cm
2
fd.cm
@@ -83,7 +83,7 @@ fd.globfs = function(globs, dir) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!check_neg(item_rel) && check_pos(item_rel)) {
|
if (!check_neg(item_rel) && check_pos(item_rel)) {
|
||||||
push(results, item_rel)
|
results[] = item_rel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
29
fold.cm
29
fold.cm
@@ -362,6 +362,7 @@ var fold = function(ast) {
|
|||||||
var fold_expr = null
|
var fold_expr = null
|
||||||
var fold_stmt = null
|
var fold_stmt = null
|
||||||
var fold_stmts = null
|
var fold_stmts = null
|
||||||
|
var fold_fn = null
|
||||||
|
|
||||||
fold_expr = function(expr, fn_nr) {
|
fold_expr = function(expr, fn_nr) {
|
||||||
if (expr == null) return null
|
if (expr == null) return null
|
||||||
@@ -592,8 +593,6 @@ var fold = function(ast) {
|
|||||||
return expr
|
return expr
|
||||||
}
|
}
|
||||||
|
|
||||||
var fold_fn = null
|
|
||||||
|
|
||||||
fold_stmt = function(stmt, fn_nr) {
|
fold_stmt = function(stmt, fn_nr) {
|
||||||
if (stmt == null) return null
|
if (stmt == null) return null
|
||||||
var k = stmt.kind
|
var k = stmt.kind
|
||||||
@@ -710,26 +709,26 @@ var fold = function(ast) {
|
|||||||
if (sv != null && sv.nr_uses == 0) {
|
if (sv != null && sv.nr_uses == 0) {
|
||||||
if (is_pure(stmt.right)) stmt.dead = true
|
if (is_pure(stmt.right)) stmt.dead = true
|
||||||
if (stmt.right != null && stmt.right.kind == "(" && stmt.right.expression != null && stmt.right.expression.name == "use") {
|
if (stmt.right != null && stmt.right.kind == "(" && stmt.right.expression != null && stmt.right.expression.name == "use") {
|
||||||
push(ast._diagnostics, {
|
ast._diagnostics[] = {
|
||||||
severity: "warning",
|
severity: "warning",
|
||||||
line: stmt.left.from_row + 1,
|
line: stmt.left.from_row + 1,
|
||||||
col: stmt.left.from_column + 1,
|
col: stmt.left.from_column + 1,
|
||||||
message: `unused import '${name}'`
|
message: `unused import '${name}'`
|
||||||
})
|
}
|
||||||
} else if (stmt.kind == "def") {
|
} else if (stmt.kind == "def") {
|
||||||
push(ast._diagnostics, {
|
ast._diagnostics[] = {
|
||||||
severity: "warning",
|
severity: "warning",
|
||||||
line: stmt.left.from_row + 1,
|
line: stmt.left.from_row + 1,
|
||||||
col: stmt.left.from_column + 1,
|
col: stmt.left.from_column + 1,
|
||||||
message: `unused constant '${name}'`
|
message: `unused constant '${name}'`
|
||||||
})
|
}
|
||||||
} else {
|
} else {
|
||||||
push(ast._diagnostics, {
|
ast._diagnostics[] = {
|
||||||
severity: "warning",
|
severity: "warning",
|
||||||
line: stmt.left.from_row + 1,
|
line: stmt.left.from_row + 1,
|
||||||
col: stmt.left.from_column + 1,
|
col: stmt.left.from_column + 1,
|
||||||
message: `unused variable '${name}'`
|
message: `unused variable '${name}'`
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -743,15 +742,15 @@ var fold = function(ast) {
|
|||||||
sv = scope_var(fn_nr, stmt.name)
|
sv = scope_var(fn_nr, stmt.name)
|
||||||
if (sv != null && sv.nr_uses == 0) {
|
if (sv != null && sv.nr_uses == 0) {
|
||||||
stmt.dead = true
|
stmt.dead = true
|
||||||
push(ast._diagnostics, {
|
ast._diagnostics[] = {
|
||||||
severity: "warning",
|
severity: "warning",
|
||||||
line: stmt.from_row + 1,
|
line: stmt.from_row + 1,
|
||||||
col: stmt.from_column + 1,
|
col: stmt.from_column + 1,
|
||||||
message: `unused function '${stmt.name}'`
|
message: `unused function '${stmt.name}'`
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (stmt.dead != true) push(out, stmt)
|
if (stmt.dead != true) out[] = stmt
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
@@ -1040,7 +1039,7 @@ var fold = function(ast) {
|
|||||||
i = 0
|
i = 0
|
||||||
while (i < length(ast.intrinsics)) {
|
while (i < length(ast.intrinsics)) {
|
||||||
if (used_intrinsics[ast.intrinsics[i]] == true) {
|
if (used_intrinsics[ast.intrinsics[i]] == true) {
|
||||||
push(new_intrinsics, ast.intrinsics[i])
|
new_intrinsics[] = ast.intrinsics[i]
|
||||||
}
|
}
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
@@ -1072,16 +1071,16 @@ var fold = function(ast) {
|
|||||||
fn_sv = scope_var(0, fn.name)
|
fn_sv = scope_var(0, fn.name)
|
||||||
if (fn_sv != null && fn_sv.nr_uses == 0) {
|
if (fn_sv != null && fn_sv.nr_uses == 0) {
|
||||||
fn.dead = true
|
fn.dead = true
|
||||||
push(ast._diagnostics, {
|
ast._diagnostics[] = {
|
||||||
severity: "warning",
|
severity: "warning",
|
||||||
line: fn.from_row + 1,
|
line: fn.from_row + 1,
|
||||||
col: fn.from_column + 1,
|
col: fn.from_column + 1,
|
||||||
message: `unused function '${fn.name}'`
|
message: `unused function '${fn.name}'`
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fn.dead != true) {
|
if (fn.dead != true) {
|
||||||
push(live_fns, fn)
|
live_fns[] = fn
|
||||||
}
|
}
|
||||||
fi = fi + 1
|
fi = fi + 1
|
||||||
}
|
}
|
||||||
|
|||||||
12
fuzz.ce
12
fuzz.ce
@@ -89,7 +89,7 @@ function run_fuzz(seed_val) {
|
|||||||
var _parse = function() {
|
var _parse = function() {
|
||||||
ast = analyze(src, name + ".cm")
|
ast = analyze(src, name + ".cm")
|
||||||
} disruption {
|
} disruption {
|
||||||
push(errors, "parse error")
|
errors[] = "parse error"
|
||||||
}
|
}
|
||||||
_parse()
|
_parse()
|
||||||
if (length(errors) > 0) return {seed: seed_val, errors: errors, src: src}
|
if (length(errors) > 0) return {seed: seed_val, errors: errors, src: src}
|
||||||
@@ -112,7 +112,7 @@ function run_fuzz(seed_val) {
|
|||||||
|
|
||||||
// Check module-level behavior
|
// Check module-level behavior
|
||||||
if (opt_err != noopt_err) {
|
if (opt_err != noopt_err) {
|
||||||
push(errors, `module load: opt=${opt_err != null ? opt_err : "ok"} noopt=${noopt_err != null ? noopt_err : "ok"}`)
|
errors[] = `module load: opt=${opt_err != null ? opt_err : "ok"} noopt=${noopt_err != null ? noopt_err : "ok"}`
|
||||||
return {seed: seed_val, errors: errors, src: src}
|
return {seed: seed_val, errors: errors, src: src}
|
||||||
}
|
}
|
||||||
if (opt_err != null) {
|
if (opt_err != null) {
|
||||||
@@ -137,10 +137,10 @@ function run_fuzz(seed_val) {
|
|||||||
_run()
|
_run()
|
||||||
|
|
||||||
if (is_text(ret)) {
|
if (is_text(ret)) {
|
||||||
push(errors, `self-check ${key}: ${ret}`)
|
errors[] = `self-check ${key}: ${ret}`
|
||||||
}
|
}
|
||||||
if (run_err != null) {
|
if (run_err != null) {
|
||||||
push(errors, `self-check ${key}: unexpected disruption`)
|
errors[] = `self-check ${key}: unexpected disruption`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
k = k + 1
|
k = k + 1
|
||||||
@@ -174,9 +174,9 @@ function run_fuzz(seed_val) {
|
|||||||
_run_noopt()
|
_run_noopt()
|
||||||
|
|
||||||
if (opt_fn_err != noopt_fn_err) {
|
if (opt_fn_err != noopt_fn_err) {
|
||||||
push(errors, `diff ${key2}: opt=${opt_fn_err != null ? opt_fn_err : "ok"} noopt=${noopt_fn_err != null ? noopt_fn_err : "ok"}`)
|
errors[] = `diff ${key2}: opt=${opt_fn_err != null ? opt_fn_err : "ok"} noopt=${noopt_fn_err != null ? noopt_fn_err : "ok"}`
|
||||||
} else if (!values_equal(opt_result, noopt_result)) {
|
} else if (!values_equal(opt_result, noopt_result)) {
|
||||||
push(errors, `diff ${key2}: opt=${describe(opt_result)} noopt=${describe(noopt_result)}`)
|
errors[] = `diff ${key2}: opt=${describe(opt_result)} noopt=${describe(noopt_result)}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
k2 = k2 + 1
|
k2 = k2 + 1
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ function gen_array_test() {
|
|||||||
var v = 0
|
var v = 0
|
||||||
while (i < n) {
|
while (i < n) {
|
||||||
v = rand_int(-100, 100)
|
v = rand_int(-100, 100)
|
||||||
push(vals, v)
|
vals[] = v
|
||||||
sum = sum + v
|
sum = sum + v
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
@@ -249,7 +249,7 @@ function gen_array_test() {
|
|||||||
var val_strs = []
|
var val_strs = []
|
||||||
i = 0
|
i = 0
|
||||||
while (i < n) {
|
while (i < n) {
|
||||||
push(val_strs, text(vals[i]))
|
val_strs[] = text(vals[i])
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
403
gc_plan.md
403
gc_plan.md
@@ -1,403 +0,0 @@
|
|||||||
# Plan: Complete Copying GC Implementation
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Remove reference counting (DupValue/FreeValue) entirely and complete the Cheney copying garbage collector. Each JSContext will use bump allocation from a heap block, and when out of memory, request a new heap from JSRuntime's buddy allocator and copy live objects to the new heap.
|
|
||||||
|
|
||||||
## Target Architecture (from docs/memory.md)
|
|
||||||
|
|
||||||
### Object Types (simplified from current):
|
|
||||||
|
|
||||||
**Type 0 - Array**: `{ header, length, elements[] }`
|
|
||||||
**Type 1 - Blob**: `{ header, length, bits[] }`
|
|
||||||
**Type 2 - Text**: `{ header, length_or_hash, packed_chars[] }`
|
|
||||||
**Type 3 - Record**: `{ header, prototype, length, key_value_pairs[] }`
|
|
||||||
**Type 4 - Function**: `{ header, code_ptr, outer_frame_ptr }` - 3 words only, always stone
|
|
||||||
**Type 5 - Frame**: `{ header, function_ptr, caller_ptr, ret_addr, args[], closure_vars[], local_vars[], temps[] }`
|
|
||||||
**Type 6 - Code**: Lives in immutable memory only, never copied
|
|
||||||
**Type 7 - Forward**: Object has moved; cap56 contains new address
|
|
||||||
|
|
||||||
### Key Design Points:
|
|
||||||
- **JSFunction** is just a pointer to code and a pointer to the frame that created it (3 words)
|
|
||||||
- **Closure variables live in frames** - when a function returns, its frame is "reduced" to just the closure variables
|
|
||||||
- **Code objects are immutable** - stored in stone memory, never copied during GC
|
|
||||||
- **Frame reduction**: When a function returns, `caller` is set to zero, signaling the frame can be shrunk
|
|
||||||
|
|
||||||
## Current State (needs refactoring)
|
|
||||||
|
|
||||||
1. **Partial Cheney GC exists** at `source/quickjs.c:1844-2030`: `ctx_gc`, `gc_copy_value`, `gc_scan_object`
|
|
||||||
2. **744 calls to JS_DupValue/JS_FreeValue** scattered throughout (currently undefined, causing compilation errors)
|
|
||||||
3. **Current JSFunction** is bloated (has kind, name, union of cfunc/bytecode/bound) - needs simplification
|
|
||||||
4. **Current JSVarRef** is a separate object - should be eliminated, closures live in frames
|
|
||||||
5. **Bump allocator** in `js_malloc` (line 1495) with `heap_base`/`heap_free`/`heap_end`
|
|
||||||
6. **Buddy allocator** for memory blocks (lines 1727-1837)
|
|
||||||
7. **Header offset inconsistency** - some structs have header at offset 0, some at offset 8
|
|
||||||
|
|
||||||
## Implementation Steps
|
|
||||||
|
|
||||||
### Phase 1: Define No-Op DupValue/FreeValue (To Enable Compilation)
|
|
||||||
|
|
||||||
Add these near line 100 in `source/quickjs.c`:
|
|
||||||
|
|
||||||
```c
|
|
||||||
/* Copying GC - no reference counting needed */
|
|
||||||
#define JS_DupValue(ctx, v) (v)
|
|
||||||
#define JS_FreeValue(ctx, v) ((void)0)
|
|
||||||
#define JS_DupValueRT(rt, v) (v)
|
|
||||||
#define JS_FreeValueRT(rt, v) ((void)0)
|
|
||||||
```
|
|
||||||
|
|
||||||
This makes the code compile while keeping existing call sites (they become no-ops).
|
|
||||||
|
|
||||||
### Phase 2: Standardize Object Headers (offset 0)
|
|
||||||
|
|
||||||
Remove `JSGCObjectHeader` (ref counting remnant) and put `objhdr_t` at offset 0:
|
|
||||||
|
|
||||||
```c
|
|
||||||
typedef struct JSArray {
|
|
||||||
objhdr_t hdr; // offset 0
|
|
||||||
word_t length;
|
|
||||||
JSValue values[];
|
|
||||||
} JSArray;
|
|
||||||
|
|
||||||
typedef struct JSRecord {
|
|
||||||
objhdr_t hdr; // offset 0
|
|
||||||
JSRecord *proto;
|
|
||||||
word_t length;
|
|
||||||
slot slots[];
|
|
||||||
} JSRecord;
|
|
||||||
|
|
||||||
typedef struct JSText {
|
|
||||||
objhdr_t hdr; // offset 0
|
|
||||||
word_t length; // pretext: length, text: hash
|
|
||||||
word_t packed[];
|
|
||||||
} JSText;
|
|
||||||
|
|
||||||
typedef struct JSBlob {
|
|
||||||
objhdr_t hdr; // offset 0
|
|
||||||
word_t length;
|
|
||||||
uint8_t bits[];
|
|
||||||
} JSBlob;
|
|
||||||
|
|
||||||
/* Simplified JSFunction per memory.md - 3 words */
|
|
||||||
typedef struct JSFunction {
|
|
||||||
objhdr_t hdr; // offset 0, always stone
|
|
||||||
JSCode *code; // pointer to immutable code object
|
|
||||||
struct JSFrame *outer; // frame that created this function
|
|
||||||
} JSFunction;
|
|
||||||
|
|
||||||
/* JSFrame per memory.md */
|
|
||||||
typedef struct JSFrame {
|
|
||||||
objhdr_t hdr; // offset 0
|
|
||||||
JSFunction *function; // function being executed
|
|
||||||
struct JSFrame *caller; // calling frame (NULL = reduced/returned)
|
|
||||||
word_t ret_addr; // return instruction address
|
|
||||||
JSValue slots[]; // args, closure vars, locals, temps
|
|
||||||
} JSFrame;
|
|
||||||
|
|
||||||
/* JSCode - always in immutable (stone) memory */
|
|
||||||
typedef struct JSCode {
|
|
||||||
objhdr_t hdr; // offset 0, always stone
|
|
||||||
word_t arity; // max number of inputs
|
|
||||||
word_t frame_size; // capacity of activation frame
|
|
||||||
word_t closure_size; // reduced capacity for returned frames
|
|
||||||
word_t entry_point; // address to begin execution
|
|
||||||
word_t disruption_point;// address of disruption clause
|
|
||||||
uint8_t bytecode[]; // actual bytecode
|
|
||||||
} JSCode;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 3: Complete gc_object_size for All Types
|
|
||||||
|
|
||||||
Update `gc_object_size` (line 1850) to read header at offset 0:
|
|
||||||
|
|
||||||
```c
|
|
||||||
static size_t gc_object_size(void *ptr) {
|
|
||||||
objhdr_t hdr = *(objhdr_t*)ptr; // Header at offset 0
|
|
||||||
uint8_t type = objhdr_type(hdr);
|
|
||||||
uint64_t cap = objhdr_cap56(hdr);
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case OBJ_ARRAY:
|
|
||||||
return sizeof(JSArray) + cap * sizeof(JSValue);
|
|
||||||
case OBJ_BLOB:
|
|
||||||
return sizeof(JSBlob) + (cap + 7) / 8; // cap is bits
|
|
||||||
case OBJ_TEXT:
|
|
||||||
return sizeof(JSText) + ((cap + 1) / 2) * sizeof(uint64_t);
|
|
||||||
case OBJ_RECORD:
|
|
||||||
return sizeof(JSRecord) + (cap + 1) * sizeof(slot); // cap is mask
|
|
||||||
case OBJ_FUNCTION:
|
|
||||||
return sizeof(JSFunction); // 3 words
|
|
||||||
case OBJ_FRAME:
|
|
||||||
return sizeof(JSFrame) + cap * sizeof(JSValue); // cap is slot count
|
|
||||||
case OBJ_CODE:
|
|
||||||
return 0; // Code is never copied (immutable)
|
|
||||||
default:
|
|
||||||
return 64; // Conservative fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 4: Complete gc_scan_object for All Types
|
|
||||||
|
|
||||||
Update `gc_scan_object` (line 1924):
|
|
||||||
|
|
||||||
```c
|
|
||||||
static void gc_scan_object(JSContext *ctx, void *ptr, uint8_t **to_free, uint8_t *to_end) {
|
|
||||||
objhdr_t hdr = *(objhdr_t*)ptr;
|
|
||||||
uint8_t type = objhdr_type(hdr);
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case OBJ_ARRAY: {
|
|
||||||
JSArray *arr = (JSArray*)ptr;
|
|
||||||
for (uint32_t i = 0; i < arr->length; i++) {
|
|
||||||
arr->values[i] = gc_copy_value(ctx, arr->values[i], to_free, to_end);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OBJ_RECORD: {
|
|
||||||
JSRecord *rec = (JSRecord*)ptr;
|
|
||||||
// Copy prototype
|
|
||||||
if (rec->proto) {
|
|
||||||
JSValue proto_val = JS_MKPTR(rec->proto);
|
|
||||||
proto_val = gc_copy_value(ctx, proto_val, to_free, to_end);
|
|
||||||
rec->proto = (JSRecord*)JS_VALUE_GET_PTR(proto_val);
|
|
||||||
}
|
|
||||||
// Copy table entries
|
|
||||||
uint32_t mask = objhdr_cap56(rec->hdr);
|
|
||||||
for (uint32_t i = 1; i <= mask; i++) { // Skip slot 0
|
|
||||||
JSValue k = rec->slots[i].key;
|
|
||||||
if (!rec_key_is_empty(k) && !rec_key_is_tomb(k)) {
|
|
||||||
rec->slots[i].key = gc_copy_value(ctx, k, to_free, to_end);
|
|
||||||
rec->slots[i].value = gc_copy_value(ctx, rec->slots[i].value, to_free, to_end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OBJ_FUNCTION: {
|
|
||||||
JSFunction *func = (JSFunction*)ptr;
|
|
||||||
// Code is immutable, don't copy - but outer frame needs copying
|
|
||||||
if (func->outer) {
|
|
||||||
JSValue outer_val = JS_MKPTR(func->outer);
|
|
||||||
outer_val = gc_copy_value(ctx, outer_val, to_free, to_end);
|
|
||||||
func->outer = (JSFrame*)JS_VALUE_GET_PTR(outer_val);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OBJ_FRAME: {
|
|
||||||
JSFrame *frame = (JSFrame*)ptr;
|
|
||||||
// Copy function pointer
|
|
||||||
if (frame->function) {
|
|
||||||
JSValue func_val = JS_MKPTR(frame->function);
|
|
||||||
func_val = gc_copy_value(ctx, func_val, to_free, to_end);
|
|
||||||
frame->function = (JSFunction*)JS_VALUE_GET_PTR(func_val);
|
|
||||||
}
|
|
||||||
// Copy caller (unless NULL = reduced frame)
|
|
||||||
if (frame->caller) {
|
|
||||||
JSValue caller_val = JS_MKPTR(frame->caller);
|
|
||||||
caller_val = gc_copy_value(ctx, caller_val, to_free, to_end);
|
|
||||||
frame->caller = (JSFrame*)JS_VALUE_GET_PTR(caller_val);
|
|
||||||
}
|
|
||||||
// Copy all slots (args, closure vars, locals, temps)
|
|
||||||
uint32_t slot_count = objhdr_cap56(frame->hdr);
|
|
||||||
for (uint32_t i = 0; i < slot_count; i++) {
|
|
||||||
frame->slots[i] = gc_copy_value(ctx, frame->slots[i], to_free, to_end);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OBJ_TEXT:
|
|
||||||
case OBJ_BLOB:
|
|
||||||
case OBJ_CODE:
|
|
||||||
// No internal references to scan
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 5: Fix gc_copy_value Forwarding
|
|
||||||
|
|
||||||
Update `gc_copy_value` (line 1883) for offset 0 headers:
|
|
||||||
|
|
||||||
```c
|
|
||||||
static JSValue gc_copy_value(JSContext *ctx, JSValue v, uint8_t **to_free, uint8_t *to_end) {
|
|
||||||
if (!JS_IsPtr(v)) return v; // Immediate value
|
|
||||||
|
|
||||||
void *ptr = JS_VALUE_GET_PTR(v);
|
|
||||||
|
|
||||||
// Stone memory - don't copy (includes Code objects)
|
|
||||||
objhdr_t hdr = *(objhdr_t*)ptr;
|
|
||||||
if (objhdr_s(hdr)) return v;
|
|
||||||
|
|
||||||
// Check if in current heap
|
|
||||||
if ((uint8_t*)ptr < ctx->heap_base || (uint8_t*)ptr >= ctx->heap_end)
|
|
||||||
return v; // External allocation
|
|
||||||
|
|
||||||
// Already forwarded?
|
|
||||||
if (objhdr_type(hdr) == OBJ_FORWARD) {
|
|
||||||
void *new_ptr = (void*)(uintptr_t)objhdr_cap56(hdr);
|
|
||||||
return JS_MKPTR(new_ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy object to new space
|
|
||||||
size_t size = gc_object_size(ptr);
|
|
||||||
void *new_ptr = *to_free;
|
|
||||||
*to_free += size;
|
|
||||||
memcpy(new_ptr, ptr, size);
|
|
||||||
|
|
||||||
// Leave forwarding pointer in old location
|
|
||||||
*(objhdr_t*)ptr = objhdr_make((uint64_t)(uintptr_t)new_ptr, OBJ_FORWARD, 0, 0, 0, 0);
|
|
||||||
|
|
||||||
return JS_MKPTR(new_ptr);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 6: Complete GC Root Tracing
|
|
||||||
|
|
||||||
Update `ctx_gc` (line 1966) to trace all roots including JSGCRef:
|
|
||||||
|
|
||||||
```c
|
|
||||||
static int ctx_gc(JSContext *ctx) {
|
|
||||||
// ... existing setup code ...
|
|
||||||
|
|
||||||
// Copy roots: global object, class prototypes, etc. (existing)
|
|
||||||
ctx->global_obj = gc_copy_value(ctx, ctx->global_obj, &to_free, to_end);
|
|
||||||
ctx->global_var_obj = gc_copy_value(ctx, ctx->global_var_obj, &to_free, to_end);
|
|
||||||
// ... other existing root copying ...
|
|
||||||
|
|
||||||
// Copy GC root stack (JS_PUSH_VALUE/JS_POP_VALUE)
|
|
||||||
for (JSGCRef *ref = ctx->top_gc_ref; ref; ref = ref->prev) {
|
|
||||||
ref->val = gc_copy_value(ctx, ref->val, &to_free, to_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy GC root list (JS_AddGCRef/JS_DeleteGCRef)
|
|
||||||
for (JSGCRef *ref = ctx->last_gc_ref; ref; ref = ref->prev) {
|
|
||||||
ref->val = gc_copy_value(ctx, ref->val, &to_free, to_end);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy current exception
|
|
||||||
ctx->current_exception = gc_copy_value(ctx, ctx->current_exception, &to_free, to_end);
|
|
||||||
|
|
||||||
// Cheney scan (existing)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 7: Trigger GC on Allocation Failure
|
|
||||||
|
|
||||||
Update `js_malloc` (line 1495):
|
|
||||||
|
|
||||||
```c
|
|
||||||
void *js_malloc(JSContext *ctx, size_t size) {
|
|
||||||
size = (size + 7) & ~7; // Align to 8 bytes
|
|
||||||
|
|
||||||
if ((uint8_t*)ctx->heap_free + size > (uint8_t*)ctx->heap_end) {
|
|
||||||
if (ctx_gc(ctx) < 0) {
|
|
||||||
JS_ThrowOutOfMemory(ctx);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
// Retry after GC
|
|
||||||
if ((uint8_t*)ctx->heap_free + size > (uint8_t*)ctx->heap_end) {
|
|
||||||
JS_ThrowOutOfMemory(ctx);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void *ptr = ctx->heap_free;
|
|
||||||
ctx->heap_free = (uint8_t*)ctx->heap_free + size;
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 8: Frame Reduction (for closures)
|
|
||||||
|
|
||||||
When a function returns, "reduce" its frame to just closure variables:
|
|
||||||
|
|
||||||
```c
|
|
||||||
static void reduce_frame(JSContext *ctx, JSFrame *frame) {
|
|
||||||
if (frame->caller == NULL) return; // Already reduced
|
|
||||||
|
|
||||||
JSCode *code = frame->function->code;
|
|
||||||
uint32_t closure_size = code->closure_size;
|
|
||||||
|
|
||||||
// Shrink capacity to just closure variables
|
|
||||||
frame->hdr = objhdr_make(closure_size, OBJ_FRAME, 0, 0, 0, 0);
|
|
||||||
frame->caller = NULL; // Signal: frame is reduced
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 9: Remove Unused Reference Counting Code
|
|
||||||
|
|
||||||
Delete:
|
|
||||||
- `gc_decref`, `gc_decref_child` functions
|
|
||||||
- `gc_scan_incref_child`, `gc_scan_incref_child2` functions
|
|
||||||
- `JS_GCPhaseEnum`, `gc_phase` fields
|
|
||||||
- `JSGCObjectHeader` struct (merge into objhdr_t)
|
|
||||||
- `ref_count` fields from any remaining structs
|
|
||||||
- `mark_function_children_decref` function
|
|
||||||
- All `free_*` functions that rely on ref counting
|
|
||||||
|
|
||||||
## Files to Modify
|
|
||||||
|
|
||||||
1. **source/quickjs.c** - Main implementation:
|
|
||||||
- Add DupValue/FreeValue no-op macros (~line 100)
|
|
||||||
- Restructure JSArray, JSBlob, JSText, JSRecord (lines 468-499)
|
|
||||||
- Simplify JSFunction to 3-word struct (line 1205)
|
|
||||||
- Add JSFrame as heap object (new)
|
|
||||||
- Restructure JSCode/JSFunctionBytecode (line 1293)
|
|
||||||
- Fix gc_object_size (line 1850)
|
|
||||||
- Fix gc_copy_value (line 1883)
|
|
||||||
- Complete gc_scan_object (line 1924)
|
|
||||||
- Update ctx_gc for all roots (line 1966)
|
|
||||||
- Update js_malloc to trigger GC (line 1495)
|
|
||||||
- Delete ref counting code throughout
|
|
||||||
|
|
||||||
2. **source/quickjs.h** - Public API:
|
|
||||||
- Remove JSGCObjectHeader
|
|
||||||
- Update JSValue type checks if needed
|
|
||||||
- Ensure JS_IsStone works with offset 0 headers
|
|
||||||
|
|
||||||
## Execution Order
|
|
||||||
|
|
||||||
1. **First**: Add DupValue/FreeValue macros (enables compilation)
|
|
||||||
2. **Second**: Standardize struct layouts (header at offset 0)
|
|
||||||
3. **Third**: Fix gc_object_size and gc_copy_value
|
|
||||||
4. **Fourth**: Complete gc_scan_object for all types
|
|
||||||
5. **Fifth**: Update ctx_gc with complete root tracing
|
|
||||||
6. **Sixth**: Wire js_malloc to trigger GC
|
|
||||||
7. **Seventh**: Add frame reduction for closures
|
|
||||||
8. **Finally**: Remove ref counting dead code
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
1. **Compile test**: `make` should succeed without errors
|
|
||||||
2. **Basic test**: Run simple scripts:
|
|
||||||
```js
|
|
||||||
var a = [1, 2, 3]
|
|
||||||
log.console(a[1])
|
|
||||||
```
|
|
||||||
3. **Stress test**: Allocate many objects to trigger GC:
|
|
||||||
```js
|
|
||||||
for (var i = 0; i < 100000; i++) {
|
|
||||||
var x = { value: i }
|
|
||||||
}
|
|
||||||
log.console("done")
|
|
||||||
```
|
|
||||||
4. **Closure test**: Test functions with closures survive GC:
|
|
||||||
```js
|
|
||||||
fn make_counter() {
|
|
||||||
var count = 0
|
|
||||||
fn inc() { count = count + 1; return count }
|
|
||||||
return inc
|
|
||||||
}
|
|
||||||
var c = make_counter()
|
|
||||||
log.console(c()) // 1
|
|
||||||
log.console(c()) // 2
|
|
||||||
```
|
|
||||||
5. **GC stress with closures**: Create many closures, trigger GC, verify they still work
|
|
||||||
|
|
||||||
## Key Design Decisions (Resolved)
|
|
||||||
|
|
||||||
1. **JSCode storage**: Lives in stone (immutable) memory, never copied during GC ✓
|
|
||||||
2. **Header offset**: Standardized to offset 0 for all heap objects ✓
|
|
||||||
3. **Closure variables**: Live in JSFrame objects; frames are "reduced" when functions return ✓
|
|
||||||
4. **JSVarRef**: Eliminated - closures reference their outer frame directly ✓
|
|
||||||
12
graph.ce
12
graph.ce
@@ -98,7 +98,7 @@ function gather_graph(locator, visited) {
|
|||||||
arrfor(array(deps), function(alias) {
|
arrfor(array(deps), function(alias) {
|
||||||
var dep_locator = deps[alias]
|
var dep_locator = deps[alias]
|
||||||
add_node(dep_locator)
|
add_node(dep_locator)
|
||||||
push(edges, { from: locator, to: dep_locator, alias: alias })
|
edges[] = { from: locator, to: dep_locator, alias: alias }
|
||||||
gather_graph(dep_locator, visited)
|
gather_graph(dep_locator, visited)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ if (show_world) {
|
|||||||
packages = shop.list_packages()
|
packages = shop.list_packages()
|
||||||
arrfor(packages, function(p) {
|
arrfor(packages, function(p) {
|
||||||
if (p != 'core') {
|
if (p != 'core') {
|
||||||
push(roots, p)
|
roots[] = p
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -128,7 +128,7 @@ if (show_world) {
|
|||||||
|
|
||||||
target_locator = shop.resolve_locator(target_locator)
|
target_locator = shop.resolve_locator(target_locator)
|
||||||
|
|
||||||
push(roots, target_locator)
|
roots[] = target_locator
|
||||||
}
|
}
|
||||||
|
|
||||||
arrfor(roots, function(root) {
|
arrfor(roots, function(root) {
|
||||||
@@ -164,7 +164,7 @@ if (format == 'tree') {
|
|||||||
children = []
|
children = []
|
||||||
arrfor(edges, function(e) {
|
arrfor(edges, function(e) {
|
||||||
if (e.from == locator) {
|
if (e.from == locator) {
|
||||||
push(children, e)
|
children[] = e
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ if (format == 'tree') {
|
|||||||
children = []
|
children = []
|
||||||
arrfor(edges, function(e) {
|
arrfor(edges, function(e) {
|
||||||
if (e.from == roots[i]) {
|
if (e.from == roots[i]) {
|
||||||
push(children, e)
|
children[] = e
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -230,7 +230,7 @@ if (format == 'tree') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
arrfor(array(nodes), function(id) {
|
arrfor(array(nodes), function(id) {
|
||||||
push(output.nodes, nodes[id])
|
output.nodes[] = nodes[id]
|
||||||
})
|
})
|
||||||
|
|
||||||
output.edges = edges
|
output.edges = edges
|
||||||
|
|||||||
783
http.cm
Normal file
783
http.cm
Normal file
@@ -0,0 +1,783 @@
|
|||||||
|
var socket = use('socket')
|
||||||
|
var tls = use('net/tls')
|
||||||
|
var Blob = use('blob')
|
||||||
|
|
||||||
|
def CRLF = "\r\n"
|
||||||
|
|
||||||
|
def status_texts = {
|
||||||
|
"200": "OK", "201": "Created", "204": "No Content",
|
||||||
|
"301": "Moved Permanently", "302": "Found", "307": "Temporary Redirect",
|
||||||
|
"400": "Bad Request", "401": "Unauthorized", "403": "Forbidden",
|
||||||
|
"404": "Not Found", "405": "Method Not Allowed", "500": "Internal Server Error"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Server (unchanged)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
function serve(port) {
|
||||||
|
var fd = socket.socket("AF_INET", "SOCK_STREAM")
|
||||||
|
socket.setsockopt(fd, "SOL_SOCKET", "SO_REUSEADDR", true)
|
||||||
|
socket.bind(fd, {address: "127.0.0.1", port: port})
|
||||||
|
socket.listen(fd, 16)
|
||||||
|
return fd
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_request(conn_fd) {
|
||||||
|
var data = socket.recv(conn_fd, 65536)
|
||||||
|
var raw = text(data)
|
||||||
|
|
||||||
|
var hdr_end = search(raw, CRLF + CRLF)
|
||||||
|
if (hdr_end == null) disrupt
|
||||||
|
var header_text = text(raw, 0, hdr_end)
|
||||||
|
var body_text = text(raw, hdr_end + 4)
|
||||||
|
|
||||||
|
var lines = array(header_text, CRLF)
|
||||||
|
var parts = array(lines[0], " ")
|
||||||
|
var method = parts[0]
|
||||||
|
var url = parts[1]
|
||||||
|
var qpos = search(url, "?")
|
||||||
|
var path = qpos != null ? text(url, 0, qpos) : url
|
||||||
|
|
||||||
|
var headers = {}
|
||||||
|
var i = 1
|
||||||
|
var colon = null
|
||||||
|
var key = null
|
||||||
|
var val = null
|
||||||
|
while (i < length(lines)) {
|
||||||
|
colon = search(lines[i], ": ")
|
||||||
|
if (colon != null) {
|
||||||
|
key = lower(text(lines[i], 0, colon))
|
||||||
|
val = text(lines[i], colon + 2)
|
||||||
|
headers[key] = val
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var cl = headers["content-length"]
|
||||||
|
var content_length = null
|
||||||
|
var remaining = null
|
||||||
|
var more = null
|
||||||
|
if (cl != null) content_length = number(cl)
|
||||||
|
if (content_length != null && length(body_text) < content_length) {
|
||||||
|
remaining = content_length - length(body_text)
|
||||||
|
more = socket.recv(conn_fd, remaining)
|
||||||
|
body_text = body_text + text(more)
|
||||||
|
}
|
||||||
|
if (content_length == null || content_length == 0) body_text = null
|
||||||
|
|
||||||
|
return {
|
||||||
|
method: method, path: path, url: url,
|
||||||
|
headers: headers, body: body_text, _conn: conn_fd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function accept(server_fd) {
|
||||||
|
var conn = socket.accept(server_fd)
|
||||||
|
return parse_request(conn.socket)
|
||||||
|
}
|
||||||
|
|
||||||
|
function on_request(server_fd, handler) {
|
||||||
|
var _accept = function() {
|
||||||
|
var conn = socket.accept(server_fd)
|
||||||
|
var req = null
|
||||||
|
var _parse = function() {
|
||||||
|
req = parse_request(conn.socket)
|
||||||
|
} disruption {
|
||||||
|
req = null
|
||||||
|
}
|
||||||
|
_parse()
|
||||||
|
if (req != null) handler(req)
|
||||||
|
socket.on_readable(server_fd, _accept)
|
||||||
|
}
|
||||||
|
socket.on_readable(server_fd, _accept)
|
||||||
|
}
|
||||||
|
|
||||||
|
function respond(conn, status, headers, body) {
|
||||||
|
var st = status_texts[text(status)]
|
||||||
|
if (st == null) st = "Unknown"
|
||||||
|
var out = "HTTP/1.1 " + text(status) + " " + st + CRLF
|
||||||
|
out = out + "Connection: close" + CRLF
|
||||||
|
|
||||||
|
var body_str = ""
|
||||||
|
var keys = null
|
||||||
|
var i = 0
|
||||||
|
if (body != null) {
|
||||||
|
if (is_text(body)) body_str = body
|
||||||
|
else body_str = text(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers != null) {
|
||||||
|
keys = array(headers)
|
||||||
|
i = 0
|
||||||
|
while (i < length(keys)) {
|
||||||
|
out = out + keys[i] + ": " + headers[keys[i]] + CRLF
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out = out + "Content-Length: " + text(length(body_str)) + CRLF
|
||||||
|
out = out + CRLF + body_str
|
||||||
|
|
||||||
|
socket.send(conn, out)
|
||||||
|
socket.close(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
function sse_open(conn, headers) {
|
||||||
|
var out = "HTTP/1.1 200 OK" + CRLF
|
||||||
|
out = out + "Content-Type: text/event-stream" + CRLF
|
||||||
|
out = out + "Cache-Control: no-cache" + CRLF
|
||||||
|
out = out + "Connection: keep-alive" + CRLF
|
||||||
|
var keys = null
|
||||||
|
var i = 0
|
||||||
|
if (headers != null) {
|
||||||
|
keys = array(headers)
|
||||||
|
i = 0
|
||||||
|
while (i < length(keys)) {
|
||||||
|
out = out + keys[i] + ": " + headers[keys[i]] + CRLF
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = out + CRLF
|
||||||
|
socket.send(conn, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
function sse_event(conn, event, data) {
|
||||||
|
var frame = "event: " + event + "\ndata: " + data + "\n\n"
|
||||||
|
var ok = true
|
||||||
|
var _send = function() {
|
||||||
|
socket.send(conn, frame)
|
||||||
|
} disruption {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
_send()
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
function sse_close(conn) {
|
||||||
|
socket.close(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Blocking client request (kept for compatibility)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
function request(method, url, headers, body) {
|
||||||
|
var parts = array(url, "/")
|
||||||
|
var host_port = parts[2]
|
||||||
|
var path = "/" + text(array(parts, 3, length(parts)), "/")
|
||||||
|
var hp = array(host_port, ":")
|
||||||
|
var host = hp[0]
|
||||||
|
var port = length(hp) > 1 ? number(hp[1]) : 80
|
||||||
|
|
||||||
|
var fd = socket.socket("AF_INET", "SOCK_STREAM")
|
||||||
|
var raw = null
|
||||||
|
var hdr_end = null
|
||||||
|
var _do = function() {
|
||||||
|
socket.connect(fd, {address: host, port: port})
|
||||||
|
var body_str = ""
|
||||||
|
if (body != null) {
|
||||||
|
if (is_text(body)) body_str = body
|
||||||
|
else body_str = text(body)
|
||||||
|
}
|
||||||
|
var keys = null
|
||||||
|
var i = 0
|
||||||
|
var req = method + " " + path + " HTTP/1.1" + CRLF
|
||||||
|
req = req + "Host: " + host_port + CRLF
|
||||||
|
req = req + "Connection: close" + CRLF
|
||||||
|
if (headers != null) {
|
||||||
|
keys = array(headers)
|
||||||
|
i = 0
|
||||||
|
while (i < length(keys)) {
|
||||||
|
req = req + keys[i] + ": " + headers[keys[i]] + CRLF
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (length(body_str) > 0) {
|
||||||
|
req = req + "Content-Length: " + text(length(body_str)) + CRLF
|
||||||
|
}
|
||||||
|
req = req + CRLF + body_str
|
||||||
|
socket.send(fd, req)
|
||||||
|
raw = text(socket.recv(fd, 65536))
|
||||||
|
} disruption {
|
||||||
|
raw = null
|
||||||
|
}
|
||||||
|
_do()
|
||||||
|
socket.close(fd)
|
||||||
|
if (raw == null) return null
|
||||||
|
hdr_end = search(raw, CRLF + CRLF)
|
||||||
|
if (hdr_end == null) return null
|
||||||
|
|
||||||
|
var header_text = text(raw, 0, hdr_end)
|
||||||
|
var lines = array(header_text, CRLF)
|
||||||
|
var status_parts = array(lines[0], " ")
|
||||||
|
var status = number(status_parts[1])
|
||||||
|
|
||||||
|
var resp_headers = {}
|
||||||
|
var hi = 1
|
||||||
|
var colon = null
|
||||||
|
while (hi < length(lines)) {
|
||||||
|
colon = search(lines[hi], ": ")
|
||||||
|
if (colon != null) {
|
||||||
|
resp_headers[lower(text(lines[hi], 0, colon))] = text(lines[hi], colon + 2)
|
||||||
|
}
|
||||||
|
hi = hi + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: status,
|
||||||
|
headers: resp_headers,
|
||||||
|
body: text(raw, hdr_end + 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Requestor-based async fetch
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// parse_url requestor — sync, extract {scheme, host, port, path} from URL
|
||||||
|
var parse_url = function(callback, value) {
|
||||||
|
var url = null
|
||||||
|
var method = "GET"
|
||||||
|
var req_headers = null
|
||||||
|
var req_body = null
|
||||||
|
log.console("value type=" + text(is_text(value)) + " val=" + text(value))
|
||||||
|
if (is_text(value)) {
|
||||||
|
url = value
|
||||||
|
log.console("url after assign=" + text(is_text(url)) + " url=" + text(url))
|
||||||
|
} else {
|
||||||
|
url = value.url
|
||||||
|
if (value.method != null) method = value.method
|
||||||
|
if (value.headers != null) req_headers = value.headers
|
||||||
|
if (value.body != null) req_body = value.body
|
||||||
|
}
|
||||||
|
|
||||||
|
// strip scheme
|
||||||
|
var scheme = "http"
|
||||||
|
var rest = url
|
||||||
|
var scheme_end = search(url, "://")
|
||||||
|
log.console("A: url_type=" + text(is_text(url)) + " scheme_end=" + text(scheme_end))
|
||||||
|
if (scheme_end != null) {
|
||||||
|
scheme = lower(text(url, 0, scheme_end))
|
||||||
|
rest = text(url, scheme_end + 3, length(url))
|
||||||
|
log.console("B: scheme=" + scheme + " rest=" + rest + " rest_type=" + text(is_text(rest)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// split host from path
|
||||||
|
var slash = search(rest, "/")
|
||||||
|
var host_port = rest
|
||||||
|
var path = "/"
|
||||||
|
log.console("C: slash=" + text(slash))
|
||||||
|
if (slash != null) {
|
||||||
|
host_port = text(rest, 0, slash)
|
||||||
|
path = text(rest, slash, length(rest))
|
||||||
|
}
|
||||||
|
// split host:port
|
||||||
|
var hp = array(host_port, ":")
|
||||||
|
var host = hp[0]
|
||||||
|
var port = null
|
||||||
|
if (length(hp) > 1) {
|
||||||
|
port = number(hp[1])
|
||||||
|
} else {
|
||||||
|
port = scheme == "https" ? 443 : 80
|
||||||
|
}
|
||||||
|
|
||||||
|
callback({
|
||||||
|
scheme: scheme, host: host, port: port, path: path,
|
||||||
|
host_port: host_port, method: method,
|
||||||
|
req_headers: req_headers, req_body: req_body
|
||||||
|
})
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve_dns requestor — blocking getaddrinfo, swappable later
|
||||||
|
var resolve_dns = function(callback, state) {
|
||||||
|
var ok = true
|
||||||
|
var addrs = null
|
||||||
|
var _resolve = function() {
|
||||||
|
addrs = socket.getaddrinfo(state.host, text(state.port))
|
||||||
|
} disruption {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
_resolve()
|
||||||
|
if (!ok || addrs == null || length(addrs) == 0) {
|
||||||
|
callback(null, "dns resolution failed for " + state.host)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
callback(record(state, {address: addrs[0].address}))
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// open_connection requestor — non-blocking connect + optional TLS
|
||||||
|
var open_connection = function(callback, state) {
|
||||||
|
var fd = socket.socket("AF_INET", "SOCK_STREAM")
|
||||||
|
var cancelled = false
|
||||||
|
|
||||||
|
var cancel = function() {
|
||||||
|
var _close = null
|
||||||
|
if (!cancelled) {
|
||||||
|
cancelled = true
|
||||||
|
_close = function() {
|
||||||
|
socket.unwatch(fd)
|
||||||
|
socket.close(fd)
|
||||||
|
} disruption {}
|
||||||
|
_close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.setnonblock(fd)
|
||||||
|
|
||||||
|
var finish_connect = function(the_fd) {
|
||||||
|
var ctx = null
|
||||||
|
if (state.scheme == "https") {
|
||||||
|
ctx = tls.wrap(the_fd, state.host)
|
||||||
|
}
|
||||||
|
callback(record(state, {fd: the_fd, tls: ctx}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// non-blocking connect — EINPROGRESS is expected
|
||||||
|
var connect_err = false
|
||||||
|
var _connect = function() {
|
||||||
|
socket.connect(fd, {address: state.address, port: state.port})
|
||||||
|
} disruption {
|
||||||
|
connect_err = true
|
||||||
|
}
|
||||||
|
_connect()
|
||||||
|
|
||||||
|
// if connect succeeded immediately (localhost, etc)
|
||||||
|
var _finish_immediate = null
|
||||||
|
if (!connect_err && !cancelled) {
|
||||||
|
_finish_immediate = function() {
|
||||||
|
finish_connect(fd)
|
||||||
|
} disruption {
|
||||||
|
cancel()
|
||||||
|
callback(null, "connection setup failed")
|
||||||
|
}
|
||||||
|
_finish_immediate()
|
||||||
|
return cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for connect to complete
|
||||||
|
socket.on_writable(fd, function() {
|
||||||
|
if (cancelled) return
|
||||||
|
var err = socket.getsockopt(fd, "SOL_SOCKET", "SO_ERROR")
|
||||||
|
if (err != 0) {
|
||||||
|
cancel()
|
||||||
|
callback(null, "connect failed (errno " + text(err) + ")")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _finish = function() {
|
||||||
|
finish_connect(fd)
|
||||||
|
} disruption {
|
||||||
|
cancel()
|
||||||
|
callback(null, "connection setup failed")
|
||||||
|
}
|
||||||
|
_finish()
|
||||||
|
})
|
||||||
|
|
||||||
|
return cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
// send_request requestor — format + send HTTP/1.1 request
|
||||||
|
var send_request = function(callback, state) {
|
||||||
|
var cancelled = false
|
||||||
|
var cancel = function() {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
var _send = function() {
|
||||||
|
var body_str = ""
|
||||||
|
var keys = null
|
||||||
|
var i = 0
|
||||||
|
if (state.req_body != null) {
|
||||||
|
if (is_text(state.req_body)) body_str = state.req_body
|
||||||
|
else body_str = text(state.req_body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var req = state.method + " " + state.path + " HTTP/1.1" + CRLF
|
||||||
|
req = req + "Host: " + state.host_port + CRLF
|
||||||
|
req = req + "Connection: close" + CRLF
|
||||||
|
req = req + "User-Agent: cell/1.0" + CRLF
|
||||||
|
req = req + "Accept: */*" + CRLF
|
||||||
|
|
||||||
|
if (state.req_headers != null) {
|
||||||
|
keys = array(state.req_headers)
|
||||||
|
i = 0
|
||||||
|
while (i < length(keys)) {
|
||||||
|
req = req + keys[i] + ": " + state.req_headers[keys[i]] + CRLF
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length(body_str) > 0) {
|
||||||
|
req = req + "Content-Length: " + text(length(body_str)) + CRLF
|
||||||
|
}
|
||||||
|
|
||||||
|
req = req + CRLF + body_str
|
||||||
|
|
||||||
|
if (state.tls != null) {
|
||||||
|
tls.send(state.tls, req)
|
||||||
|
} else {
|
||||||
|
socket.send(state.fd, req)
|
||||||
|
}
|
||||||
|
} disruption {
|
||||||
|
if (!cancelled) callback(null, "send request failed")
|
||||||
|
return cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
_send()
|
||||||
|
if (!cancelled) callback(state)
|
||||||
|
return cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse response headers from raw text
|
||||||
|
function parse_headers(raw) {
|
||||||
|
var hdr_end = search(raw, CRLF + CRLF)
|
||||||
|
if (hdr_end == null) return null
|
||||||
|
|
||||||
|
var header_text = text(raw, 0, hdr_end)
|
||||||
|
var lines = array(header_text, CRLF)
|
||||||
|
var status_parts = array(lines[0], " ")
|
||||||
|
var status_code = number(status_parts[1])
|
||||||
|
|
||||||
|
var headers = {}
|
||||||
|
var i = 1
|
||||||
|
var colon = null
|
||||||
|
while (i < length(lines)) {
|
||||||
|
colon = search(lines[i], ": ")
|
||||||
|
if (colon != null) {
|
||||||
|
headers[lower(text(lines[i], 0, colon))] = text(lines[i], colon + 2)
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: status_code, headers: headers,
|
||||||
|
body_start: hdr_end + 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode chunked transfer encoding (text version, for async responses)
|
||||||
|
function decode_chunked(body_text) {
|
||||||
|
var result = ""
|
||||||
|
var pos = 0
|
||||||
|
var chunk_end = null
|
||||||
|
var chunk_size = null
|
||||||
|
while (pos < length(body_text)) {
|
||||||
|
chunk_end = search(text(body_text, pos), CRLF)
|
||||||
|
if (chunk_end == null) return result
|
||||||
|
chunk_size = number(text(body_text, pos, pos + chunk_end), 16)
|
||||||
|
if (chunk_size == null || chunk_size == 0) return result
|
||||||
|
pos = pos + chunk_end + 2
|
||||||
|
result = result + text(body_text, pos, pos + chunk_size)
|
||||||
|
pos = pos + chunk_size + 2
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode chunked transfer encoding (blob version, preserves binary data)
|
||||||
|
function decode_chunked_blob(buf, body_start_bytes) {
|
||||||
|
var result = Blob()
|
||||||
|
var pos = body_start_bytes
|
||||||
|
var total_bytes = length(buf) / 8
|
||||||
|
var header_end = null
|
||||||
|
var header_blob = null
|
||||||
|
var header_text = null
|
||||||
|
var crlf_pos = null
|
||||||
|
var chunk_size = null
|
||||||
|
var chunk_data = null
|
||||||
|
while (pos < total_bytes) {
|
||||||
|
header_end = pos + 20
|
||||||
|
if (header_end > total_bytes) header_end = total_bytes
|
||||||
|
header_blob = buf.read_blob(pos * 8, header_end * 8)
|
||||||
|
stone(header_blob)
|
||||||
|
header_text = text(header_blob)
|
||||||
|
crlf_pos = search(header_text, CRLF)
|
||||||
|
if (crlf_pos == null) break
|
||||||
|
chunk_size = number(text(header_text, 0, crlf_pos), 16)
|
||||||
|
if (chunk_size == null || chunk_size == 0) break
|
||||||
|
pos = pos + crlf_pos + 2
|
||||||
|
chunk_data = buf.read_blob(pos * 8, (pos + chunk_size) * 8)
|
||||||
|
stone(chunk_data)
|
||||||
|
result.write_blob(chunk_data)
|
||||||
|
pos = pos + chunk_size + 2
|
||||||
|
}
|
||||||
|
stone(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// receive_response requestor — async incremental receive
|
||||||
|
var receive_response = function(callback, state) {
|
||||||
|
var cancelled = false
|
||||||
|
var buffer = ""
|
||||||
|
var parsed = null
|
||||||
|
var content_length = null
|
||||||
|
var is_chunked = false
|
||||||
|
var body_complete = false
|
||||||
|
|
||||||
|
var cancel = function() {
|
||||||
|
var _cleanup = null
|
||||||
|
if (!cancelled) {
|
||||||
|
cancelled = true
|
||||||
|
_cleanup = function() {
|
||||||
|
if (state.tls != null) {
|
||||||
|
tls.close(state.tls)
|
||||||
|
} else {
|
||||||
|
socket.unwatch(state.fd)
|
||||||
|
socket.close(state.fd)
|
||||||
|
}
|
||||||
|
} disruption {}
|
||||||
|
_cleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var finish = function() {
|
||||||
|
if (cancelled) return
|
||||||
|
var body_text = text(buffer, parsed.body_start)
|
||||||
|
|
||||||
|
if (is_chunked) {
|
||||||
|
body_text = decode_chunked(body_text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up connection
|
||||||
|
var _cleanup = function() {
|
||||||
|
if (state.tls != null) {
|
||||||
|
tls.close(state.tls)
|
||||||
|
} else {
|
||||||
|
socket.close(state.fd)
|
||||||
|
}
|
||||||
|
} disruption {}
|
||||||
|
_cleanup()
|
||||||
|
|
||||||
|
callback({
|
||||||
|
status: parsed.status,
|
||||||
|
headers: parsed.headers,
|
||||||
|
body: body_text
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var check_complete = function() {
|
||||||
|
var te = null
|
||||||
|
var cl = null
|
||||||
|
var body_text = null
|
||||||
|
// still waiting for headers
|
||||||
|
if (parsed == null) {
|
||||||
|
parsed = parse_headers(buffer)
|
||||||
|
if (parsed == null) return false
|
||||||
|
te = parsed.headers["transfer-encoding"]
|
||||||
|
if (te != null && search(lower(te), "chunked") != null) {
|
||||||
|
is_chunked = true
|
||||||
|
}
|
||||||
|
cl = parsed.headers["content-length"]
|
||||||
|
if (cl != null) content_length = number(cl)
|
||||||
|
}
|
||||||
|
|
||||||
|
body_text = text(buffer, parsed.body_start)
|
||||||
|
|
||||||
|
if (is_chunked) {
|
||||||
|
// chunked: look for the terminating 0-length chunk
|
||||||
|
if (search(body_text, CRLF + "0" + CRLF) != null) return true
|
||||||
|
if (starts_with(body_text, "0" + CRLF)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content_length != null) {
|
||||||
|
return length(body_text) >= content_length
|
||||||
|
}
|
||||||
|
|
||||||
|
// connection: close — we read until EOF (handled by recv returning 0 bytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var on_data = function() {
|
||||||
|
if (cancelled) return
|
||||||
|
var chunk = null
|
||||||
|
var got_data = false
|
||||||
|
var eof = false
|
||||||
|
|
||||||
|
var _recv = function() {
|
||||||
|
if (state.tls != null) {
|
||||||
|
chunk = tls.recv(state.tls, 16384)
|
||||||
|
} else {
|
||||||
|
chunk = socket.recv(state.fd, 16384)
|
||||||
|
}
|
||||||
|
} disruption {
|
||||||
|
// recv error — treat as EOF
|
||||||
|
eof = true
|
||||||
|
}
|
||||||
|
_recv()
|
||||||
|
|
||||||
|
var chunk_text = null
|
||||||
|
if (!eof && chunk != null) {
|
||||||
|
stone(chunk)
|
||||||
|
chunk_text = text(chunk)
|
||||||
|
if (length(chunk_text) > 0) {
|
||||||
|
buffer = buffer + chunk_text
|
||||||
|
got_data = true
|
||||||
|
} else {
|
||||||
|
eof = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (got_data && check_complete()) {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eof) {
|
||||||
|
// connection closed — if we have headers, deliver what we have
|
||||||
|
if (parsed != null || parse_headers(buffer) != null) {
|
||||||
|
if (parsed == null) parsed = parse_headers(buffer)
|
||||||
|
finish()
|
||||||
|
} else {
|
||||||
|
cancel()
|
||||||
|
callback(null, "connection closed before headers received")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-register for more data (one-shot watches)
|
||||||
|
if (!cancelled) {
|
||||||
|
if (state.tls != null) {
|
||||||
|
tls.on_readable(state.tls, on_data)
|
||||||
|
} else {
|
||||||
|
socket.on_readable(state.fd, on_data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start reading
|
||||||
|
if (state.tls != null) {
|
||||||
|
tls.on_readable(state.tls, on_data)
|
||||||
|
} else {
|
||||||
|
socket.on_readable(state.fd, on_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// fetch — synchronous HTTP(S) GET, returns response body (stoned blob)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
var fetch = function(url) {
|
||||||
|
var scheme = "http"
|
||||||
|
var rest = url
|
||||||
|
var scheme_end = search(url, "://")
|
||||||
|
var slash = null
|
||||||
|
var host_port = null
|
||||||
|
var path = "/"
|
||||||
|
var hp = null
|
||||||
|
var host = null
|
||||||
|
var port = null
|
||||||
|
var fd = null
|
||||||
|
var ctx = null
|
||||||
|
var buf = Blob()
|
||||||
|
var raw_text = null
|
||||||
|
var hdr_end = null
|
||||||
|
var header_text = null
|
||||||
|
var body_start_bits = null
|
||||||
|
var body = null
|
||||||
|
var addrs = null
|
||||||
|
var address = null
|
||||||
|
var ok = true
|
||||||
|
var status_line = null
|
||||||
|
var status_code = null
|
||||||
|
|
||||||
|
if (scheme_end != null) {
|
||||||
|
scheme = lower(text(url, 0, scheme_end))
|
||||||
|
rest = text(url, scheme_end + 3, length(url))
|
||||||
|
}
|
||||||
|
slash = search(rest, "/")
|
||||||
|
host_port = rest
|
||||||
|
if (slash != null) {
|
||||||
|
host_port = text(rest, 0, slash)
|
||||||
|
path = text(rest, slash, length(rest))
|
||||||
|
}
|
||||||
|
hp = array(host_port, ":")
|
||||||
|
host = hp[0]
|
||||||
|
port = length(hp) > 1 ? number(hp[1]) : (scheme == "https" ? 443 : 80)
|
||||||
|
|
||||||
|
addrs = socket.getaddrinfo(host, text(port))
|
||||||
|
if (addrs == null || length(addrs) == 0) return null
|
||||||
|
address = addrs[0].address
|
||||||
|
|
||||||
|
fd = socket.socket("AF_INET", "SOCK_STREAM")
|
||||||
|
var _do = function() {
|
||||||
|
var req = null
|
||||||
|
var chunk = null
|
||||||
|
socket.connect(fd, {address: address, port: port})
|
||||||
|
if (scheme == "https") ctx = tls.wrap(fd, host)
|
||||||
|
req = "GET " + path + " HTTP/1.1" + CRLF
|
||||||
|
req = req + "Host: " + host_port + CRLF
|
||||||
|
req = req + "Connection: close" + CRLF
|
||||||
|
req = req + "User-Agent: cell/1.0" + CRLF
|
||||||
|
req = req + "Accept: */*" + CRLF + CRLF
|
||||||
|
if (ctx != null) tls.send(ctx, req)
|
||||||
|
else socket.send(fd, req)
|
||||||
|
while (true) {
|
||||||
|
if (ctx != null) chunk = tls.recv(ctx, 16384)
|
||||||
|
else chunk = socket.recv(fd, 16384)
|
||||||
|
if (chunk == null) break
|
||||||
|
stone(chunk)
|
||||||
|
if (length(chunk) == 0) break
|
||||||
|
buf.write_blob(chunk)
|
||||||
|
}
|
||||||
|
} disruption {
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
_do()
|
||||||
|
var _cleanup = function() {
|
||||||
|
if (ctx != null) tls.close(ctx)
|
||||||
|
else socket.close(fd)
|
||||||
|
} disruption {}
|
||||||
|
_cleanup()
|
||||||
|
if (!ok) return null
|
||||||
|
stone(buf)
|
||||||
|
raw_text = text(buf)
|
||||||
|
hdr_end = search(raw_text, CRLF + CRLF)
|
||||||
|
if (hdr_end == null) return null
|
||||||
|
header_text = text(raw_text, 0, hdr_end)
|
||||||
|
status_line = text(header_text, 0, search(header_text, CRLF) || length(header_text))
|
||||||
|
status_code = number(text(status_line, 9, 12))
|
||||||
|
if (status_code == null || status_code < 200 || status_code >= 300) {
|
||||||
|
log.error("fetch: " + status_line)
|
||||||
|
disrupt
|
||||||
|
}
|
||||||
|
if (search(lower(header_text), "transfer-encoding: chunked") != null) {
|
||||||
|
body = decode_chunked_blob(buf, hdr_end + 4)
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
// Headers are ASCII so char offset = byte offset
|
||||||
|
body_start_bits = (hdr_end + 4) * 8
|
||||||
|
body = buf.read_blob(body_start_bits, length(buf))
|
||||||
|
stone(body)
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// fetch_requestor — async requestor pipeline for fetch
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
var fetch_requestor = sequence([
|
||||||
|
parse_url,
|
||||||
|
resolve_dns,
|
||||||
|
open_connection,
|
||||||
|
send_request,
|
||||||
|
receive_response
|
||||||
|
])
|
||||||
|
|
||||||
|
function close(fd) {
|
||||||
|
socket.close(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// server
|
||||||
|
serve: serve, accept: accept, on_request: on_request,
|
||||||
|
respond: respond, close: close,
|
||||||
|
sse_open: sse_open, sse_event: sse_event, sse_close: sse_close,
|
||||||
|
// client
|
||||||
|
fetch: fetch,
|
||||||
|
fetch_requestor: fetch_requestor,
|
||||||
|
request: request
|
||||||
|
}
|
||||||
@@ -109,7 +109,7 @@ function trace_imports(file_path, depth) {
|
|||||||
|
|
||||||
all_packages[imp_pkg] = true
|
all_packages[imp_pkg] = true
|
||||||
|
|
||||||
push(all_imports, {
|
all_imports[] = {
|
||||||
from: file_path,
|
from: file_path,
|
||||||
from_pkg: file_pkg,
|
from_pkg: file_pkg,
|
||||||
module_path: mod_path,
|
module_path: mod_path,
|
||||||
@@ -117,7 +117,7 @@ function trace_imports(file_path, depth) {
|
|||||||
package: imp_pkg,
|
package: imp_pkg,
|
||||||
type: imp_type,
|
type: imp_type,
|
||||||
depth: depth
|
depth: depth
|
||||||
})
|
}
|
||||||
|
|
||||||
// Recurse into resolved scripts
|
// Recurse into resolved scripts
|
||||||
if (resolved && (ends_with(resolved, '.cm') || ends_with(resolved, '.ce'))) {
|
if (resolved && (ends_with(resolved, '.cm') || ends_with(resolved, '.ce'))) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Minimal bootstrap — seeds the content-addressed cache
|
// Minimal bootstrap — seeds the content-addressed cache
|
||||||
// Only runs on cold start (C runtime couldn't find engine in cache)
|
// Only runs on cold start (C runtime couldn't find engine in cache)
|
||||||
// Hidden vars: os, core_path, shop_path
|
// Hidden vars: os, core_path, shop_path, native_mode (optional)
|
||||||
var load_internal = os.load_internal
|
var load_internal = os.load_internal
|
||||||
function use_embed(name) {
|
function use_embed(name) {
|
||||||
return load_internal("js_core_" + name + "_use")
|
return load_internal("js_core_" + name + "_use")
|
||||||
@@ -89,20 +89,190 @@ function compile_and_cache(name, source_path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seed the cache with everything engine needs
|
// --- Native compilation support ---
|
||||||
var seed_files = [
|
|
||||||
{name: "tokenize", path: "tokenize.cm"},
|
function detect_host_target() {
|
||||||
{name: "parse", path: "parse.cm"},
|
var platform = os.platform()
|
||||||
{name: "fold", path: "fold.cm"},
|
var arch = os.arch ? os.arch() : 'arm64'
|
||||||
{name: "mcode", path: "mcode.cm"},
|
if (platform == 'macOS' || platform == 'darwin')
|
||||||
{name: "streamline", path: "streamline.cm"},
|
return arch == 'x86_64' ? 'macos_x86_64' : 'macos_arm64'
|
||||||
{name: "engine", path: "internal/engine.cm"}
|
if (platform == 'Linux' || platform == 'linux')
|
||||||
]
|
return arch == 'x86_64' ? 'linux' : 'linux_arm64'
|
||||||
var _i = 0
|
if (platform == 'Windows' || platform == 'windows')
|
||||||
var entry = null
|
return 'windows'
|
||||||
while (_i < length(seed_files)) {
|
return null
|
||||||
entry = seed_files[_i]
|
}
|
||||||
compile_and_cache(entry.name, core_path + '/' + entry.path)
|
|
||||||
_i = _i + 1
|
function detect_cc() {
|
||||||
|
var platform = os.platform()
|
||||||
|
if (platform == 'macOS') return 'clang'
|
||||||
|
return 'cc'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute native dylib cache path matching build.cm's scheme:
|
||||||
|
// cache_path(native_cache_content(src, target, ''), SALT_NATIVE)
|
||||||
|
function native_dylib_cache_path(src, target) {
|
||||||
|
var native_key = src + '\n' + target + '\nnative\n'
|
||||||
|
var full_key = native_key + '\nnative'
|
||||||
|
return cache_path(content_hash(full_key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile a module to a native dylib and cache it
|
||||||
|
var _qbe_mod = null
|
||||||
|
var _qbe_emit_mod = null
|
||||||
|
var _host_target = null
|
||||||
|
var _cc = null
|
||||||
|
var _is_darwin = false
|
||||||
|
var _rt_compiled = false
|
||||||
|
|
||||||
|
function compile_native_cached(name, source_path) {
|
||||||
|
var source_blob = fd.slurp(source_path)
|
||||||
|
var src = text(source_blob)
|
||||||
|
var dylib_path = native_dylib_cache_path(src, _host_target)
|
||||||
|
var ast = null
|
||||||
|
var compiled = null
|
||||||
|
var il_parts = null
|
||||||
|
var helpers_il = null
|
||||||
|
var all_fns = null
|
||||||
|
var full_il = null
|
||||||
|
var asm_text = null
|
||||||
|
var tmp = null
|
||||||
|
var rc = null
|
||||||
|
var rt_o = null
|
||||||
|
var qbe_rt_path = null
|
||||||
|
var link_cmd = null
|
||||||
|
|
||||||
|
if (dylib_path && fd.is_file(dylib_path)) {
|
||||||
|
os.print("bootstrap: native cache hit: " + name + "\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var t0 = null
|
||||||
|
var t1 = null
|
||||||
|
|
||||||
|
os.print("bootstrap: compiling native: " + name + "\n")
|
||||||
|
t0 = os.now()
|
||||||
|
ast = analyze(src, source_path)
|
||||||
|
compiled = streamline_mod(mcode_mod(ast))
|
||||||
|
t1 = os.now()
|
||||||
|
os.print(" [" + name + "] pipeline (tok+parse+fold+mcode+streamline): " + text((t1 - t0) / 1000000) + "ms\n")
|
||||||
|
|
||||||
|
t0 = os.now()
|
||||||
|
il_parts = _qbe_emit_mod(compiled, _qbe_mod, null)
|
||||||
|
t1 = os.now()
|
||||||
|
os.print(" [" + name + "] qbe_emit: " + text((t1 - t0) / 1000000) + "ms\n")
|
||||||
|
|
||||||
|
helpers_il = (il_parts.helpers && length(il_parts.helpers) > 0)
|
||||||
|
? text(il_parts.helpers, "\n") : ""
|
||||||
|
all_fns = text(il_parts.functions, "\n")
|
||||||
|
full_il = il_parts.data + "\n\n" + helpers_il + "\n\n" + all_fns
|
||||||
|
|
||||||
|
t0 = os.now()
|
||||||
|
asm_text = os.qbe(full_il)
|
||||||
|
t1 = os.now()
|
||||||
|
os.print(" [" + name + "] os.qbe (QBE compile): " + text((t1 - t0) / 1000000) + "ms\n")
|
||||||
|
|
||||||
|
tmp = '/tmp/cell_boot_' + name
|
||||||
|
fd.slurpwrite(tmp + '.s', stone(blob(asm_text)))
|
||||||
|
|
||||||
|
t0 = os.now()
|
||||||
|
rc = os.system(_cc + ' -c ' + tmp + '.s -o ' + tmp + '.o')
|
||||||
|
t1 = os.now()
|
||||||
|
os.print(" [" + name + "] clang -c: " + text((t1 - t0) / 1000000) + "ms\n")
|
||||||
|
if (rc != 0) {
|
||||||
|
os.print("error: assembly failed for " + name + "\n")
|
||||||
|
disrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile QBE runtime stubs (once)
|
||||||
|
rt_o = '/tmp/cell_qbe_rt.o'
|
||||||
|
if (!_rt_compiled && !fd.is_file(rt_o)) {
|
||||||
|
qbe_rt_path = core_path + '/src/qbe_rt.c'
|
||||||
|
rc = os.system(_cc + ' -c ' + qbe_rt_path + ' -o ' + rt_o + ' -fPIC')
|
||||||
|
if (rc != 0) {
|
||||||
|
os.print("error: qbe_rt compilation failed\n")
|
||||||
|
disrupt
|
||||||
|
}
|
||||||
|
_rt_compiled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link dylib
|
||||||
|
ensure_build_dir()
|
||||||
|
link_cmd = _cc + ' -shared -fPIC'
|
||||||
|
if (_is_darwin)
|
||||||
|
link_cmd = link_cmd + ' -undefined dynamic_lookup'
|
||||||
|
link_cmd = link_cmd + ' ' + tmp + '.o ' + rt_o + ' -o ' + dylib_path
|
||||||
|
|
||||||
|
t0 = os.now()
|
||||||
|
rc = os.system(link_cmd)
|
||||||
|
t1 = os.now()
|
||||||
|
os.print(" [" + name + "] clang -shared (link): " + text((t1 - t0) / 1000000) + "ms\n")
|
||||||
|
if (rc != 0) {
|
||||||
|
os.print("error: linking failed for " + name + "\n")
|
||||||
|
disrupt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Main bootstrap logic ---
|
||||||
|
|
||||||
|
// Check if native_mode was passed from C runtime
|
||||||
|
var _native = false
|
||||||
|
var _check_nm = function() {
|
||||||
|
if (native_mode) _native = true
|
||||||
|
} disruption {}
|
||||||
|
_check_nm()
|
||||||
|
|
||||||
|
var _targets = null
|
||||||
|
var _ti = 0
|
||||||
|
var _te = null
|
||||||
|
|
||||||
|
if (_native) {
|
||||||
|
// Native path: compile everything to native dylibs
|
||||||
|
_qbe_mod = boot_load("qbe")
|
||||||
|
_qbe_emit_mod = boot_load("qbe_emit")
|
||||||
|
_host_target = detect_host_target()
|
||||||
|
_cc = detect_cc()
|
||||||
|
_is_darwin = os.platform() == 'macOS'
|
||||||
|
|
||||||
|
if (!_host_target) {
|
||||||
|
os.print("error: could not detect host target for native compilation\n")
|
||||||
|
disrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also seed bytecode cache for engine (so non-native path still works)
|
||||||
|
compile_and_cache("engine", core_path + '/internal/engine.cm')
|
||||||
|
|
||||||
|
// Compile pipeline modules + qbe/qbe_emit + engine to native dylibs
|
||||||
|
_targets = [
|
||||||
|
{name: "tokenize", path: "tokenize.cm"},
|
||||||
|
{name: "parse", path: "parse.cm"},
|
||||||
|
{name: "fold", path: "fold.cm"},
|
||||||
|
{name: "mcode", path: "mcode.cm"},
|
||||||
|
{name: "streamline", path: "streamline.cm"},
|
||||||
|
{name: "qbe", path: "qbe.cm"},
|
||||||
|
{name: "qbe_emit", path: "qbe_emit.cm"},
|
||||||
|
{name: "engine", path: "internal/engine.cm"}
|
||||||
|
]
|
||||||
|
_ti = 0
|
||||||
|
while (_ti < length(_targets)) {
|
||||||
|
_te = _targets[_ti]
|
||||||
|
compile_native_cached(_te.name, core_path + '/' + _te.path)
|
||||||
|
_ti = _ti + 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Bytecode path: seed cache with everything engine needs
|
||||||
|
_targets = [
|
||||||
|
{name: "tokenize", path: "tokenize.cm"},
|
||||||
|
{name: "parse", path: "parse.cm"},
|
||||||
|
{name: "fold", path: "fold.cm"},
|
||||||
|
{name: "mcode", path: "mcode.cm"},
|
||||||
|
{name: "streamline", path: "streamline.cm"},
|
||||||
|
{name: "engine", path: "internal/engine.cm"}
|
||||||
|
]
|
||||||
|
_ti = 0
|
||||||
|
while (_ti < length(_targets)) {
|
||||||
|
_te = _targets[_ti]
|
||||||
|
compile_and_cache(_te.name, core_path + '/' + _te.path)
|
||||||
|
_ti = _ti + 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
os.print("bootstrap: cache seeded\n")
|
|
||||||
|
|||||||
472
internal/enet.c
Normal file
472
internal/enet.c
Normal file
@@ -0,0 +1,472 @@
|
|||||||
|
#include "cell.h"
|
||||||
|
#define ENET_IMPLEMENTATION
|
||||||
|
#include "enet.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
static JSClassID enet_host_id;
|
||||||
|
static JSClassID enet_peer_class_id;
|
||||||
|
|
||||||
|
static void js_enet_host_finalizer(JSRuntime *rt, JSValue val)
|
||||||
|
{
|
||||||
|
ENetHost *host = JS_GetOpaque(val, enet_host_id);
|
||||||
|
if (host) enet_host_destroy(host);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSClassDef enet_host_def = {
|
||||||
|
"ENetHost",
|
||||||
|
.finalizer = js_enet_host_finalizer,
|
||||||
|
};
|
||||||
|
|
||||||
|
static JSClassDef enet_peer_def = {
|
||||||
|
"ENetPeer",
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Helper: create a JS peer wrapper for an ENetPeer pointer.
|
||||||
|
Fresh wrapper each time — no caching in peer->data. */
|
||||||
|
static JSValue peer_wrap(JSContext *ctx, ENetPeer *peer)
|
||||||
|
{
|
||||||
|
JSValue obj = JS_NewObjectClass(ctx, enet_peer_class_id);
|
||||||
|
if (JS_IsException(obj)) return obj;
|
||||||
|
JS_SetOpaque(obj, peer);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Host functions ─────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static JSValue js_enet_create_host(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
ENetHost *host;
|
||||||
|
ENetAddress address;
|
||||||
|
ENetAddress *send = &address;
|
||||||
|
size_t peer_count = 1000;
|
||||||
|
size_t channel_limit = 0;
|
||||||
|
enet_uint32 incoming_bandwidth = 0;
|
||||||
|
enet_uint32 outgoing_bandwidth = 0;
|
||||||
|
JSValue obj;
|
||||||
|
|
||||||
|
if (argc < 1 || !JS_IsRecord(argv[0])) {
|
||||||
|
host = enet_host_create(NULL, peer_count, channel_limit, incoming_bandwidth, outgoing_bandwidth);
|
||||||
|
if (!host) return JS_RaiseDisrupt(ctx, "Failed to create ENet client host");
|
||||||
|
goto wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue config_obj = argv[0];
|
||||||
|
JSValue addr_val = JS_GetPropertyStr(ctx, config_obj, "address");
|
||||||
|
const char *addr_str = JS_IsText(addr_val) ? JS_ToCString(ctx, addr_val) : NULL;
|
||||||
|
|
||||||
|
if (!addr_str)
|
||||||
|
send = NULL;
|
||||||
|
else {
|
||||||
|
JSValue port_val = JS_GetPropertyStr(ctx, config_obj, "port");
|
||||||
|
int32_t port32 = 0;
|
||||||
|
JS_ToInt32(ctx, &port32, port_val);
|
||||||
|
|
||||||
|
if (strcmp(addr_str, "any") == 0)
|
||||||
|
address.host = ENET_HOST_ANY;
|
||||||
|
else if (strcmp(addr_str, "broadcast") == 0)
|
||||||
|
enet_address_set_host_ip(&address, "255.255.255.255");
|
||||||
|
else {
|
||||||
|
int err = enet_address_set_host_ip(&address, addr_str);
|
||||||
|
if (err != 0) {
|
||||||
|
JS_FreeCString(ctx, addr_str);
|
||||||
|
return JS_RaiseDisrupt(ctx, "Failed to set host IP. Error: %d", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
address.port = (enet_uint16)port32;
|
||||||
|
JS_FreeCString(ctx, addr_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue chan_val = JS_GetPropertyStr(ctx, config_obj, "channels");
|
||||||
|
JS_ToUint32(ctx, &channel_limit, chan_val);
|
||||||
|
|
||||||
|
JSValue in_bw_val = JS_GetPropertyStr(ctx, config_obj, "incoming_bandwidth");
|
||||||
|
JS_ToUint32(ctx, &incoming_bandwidth, in_bw_val);
|
||||||
|
|
||||||
|
JSValue out_bw_val = JS_GetPropertyStr(ctx, config_obj, "outgoing_bandwidth");
|
||||||
|
JS_ToUint32(ctx, &outgoing_bandwidth, out_bw_val);
|
||||||
|
|
||||||
|
host = enet_host_create(send, peer_count, channel_limit, incoming_bandwidth, outgoing_bandwidth);
|
||||||
|
if (!host) return JS_RaiseDisrupt(ctx, "Failed to create ENet host");
|
||||||
|
|
||||||
|
wrap:
|
||||||
|
obj = JS_NewObjectClass(ctx, enet_host_id);
|
||||||
|
if (JS_IsException(obj)) {
|
||||||
|
enet_host_destroy(host);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
JS_SetOpaque(obj, host);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* service(host, callback [, timeout]) */
|
||||||
|
static JSValue js_enet_service(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 2) return JS_RaiseDisrupt(ctx, "service: expected (host, callback)");
|
||||||
|
|
||||||
|
ENetHost *host = JS_GetOpaque(argv[0], enet_host_id);
|
||||||
|
if (!host) return JS_RaiseDisrupt(ctx, "service: invalid host");
|
||||||
|
|
||||||
|
if (!JS_IsFunction(argv[1]))
|
||||||
|
return JS_RaiseDisrupt(ctx, "service: expected callback function");
|
||||||
|
|
||||||
|
enet_uint32 timeout_ms = 0;
|
||||||
|
if (argc >= 3 && !JS_IsNull(argv[2])) {
|
||||||
|
double secs = 0;
|
||||||
|
JS_ToFloat64(ctx, &secs, argv[2]);
|
||||||
|
if (secs > 0) timeout_ms = (enet_uint32)(secs * 1000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_FRAME(ctx);
|
||||||
|
JS_ROOT(event_obj, JS_NULL);
|
||||||
|
|
||||||
|
ENetEvent event;
|
||||||
|
while (enet_host_service(host, &event, timeout_ms) > 0) {
|
||||||
|
event_obj.val = JS_NewObject(ctx);
|
||||||
|
|
||||||
|
JSValue peer_val = peer_wrap(ctx, event.peer);
|
||||||
|
JS_SetPropertyStr(ctx, event_obj.val, "peer", peer_val);
|
||||||
|
|
||||||
|
switch (event.type) {
|
||||||
|
case ENET_EVENT_TYPE_CONNECT:
|
||||||
|
JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "connect"));
|
||||||
|
break;
|
||||||
|
case ENET_EVENT_TYPE_RECEIVE:
|
||||||
|
JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "receive"));
|
||||||
|
JS_SetPropertyStr(ctx, event_obj.val, "channelID", JS_NewInt32(ctx, event.channelID));
|
||||||
|
if (event.packet->dataLength > 0) {
|
||||||
|
JSValue data_val = js_new_blob_stoned_copy(ctx, event.packet->data, event.packet->dataLength);
|
||||||
|
JS_SetPropertyStr(ctx, event_obj.val, "data", data_val);
|
||||||
|
}
|
||||||
|
enet_packet_destroy(event.packet);
|
||||||
|
break;
|
||||||
|
case ENET_EVENT_TYPE_DISCONNECT:
|
||||||
|
JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "disconnect"));
|
||||||
|
break;
|
||||||
|
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
|
||||||
|
JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "disconnect_timeout"));
|
||||||
|
break;
|
||||||
|
case ENET_EVENT_TYPE_NONE:
|
||||||
|
JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "none"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_Call(ctx, argv[1], JS_NULL, 1, &event_obj.val);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_RETURN_NULL();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* connect(host, address, port) → peer */
|
||||||
|
static JSValue js_enet_connect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 3) return JS_RaiseDisrupt(ctx, "connect: expected (host, address, port)");
|
||||||
|
|
||||||
|
ENetHost *host = JS_GetOpaque(argv[0], enet_host_id);
|
||||||
|
if (!host) return JS_RaiseDisrupt(ctx, "connect: invalid host");
|
||||||
|
|
||||||
|
const char *hostname = JS_ToCString(ctx, argv[1]);
|
||||||
|
if (!hostname) return JS_EXCEPTION;
|
||||||
|
int port;
|
||||||
|
JS_ToInt32(ctx, &port, argv[2]);
|
||||||
|
|
||||||
|
ENetAddress address;
|
||||||
|
enet_address_set_host(&address, hostname);
|
||||||
|
JS_FreeCString(ctx, hostname);
|
||||||
|
address.port = port;
|
||||||
|
|
||||||
|
ENetPeer *peer = enet_host_connect(host, &address, 2, 0);
|
||||||
|
if (!peer) return JS_RaiseDisrupt(ctx, "No available peers for connection");
|
||||||
|
|
||||||
|
return peer_wrap(ctx, peer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* flush(host) */
|
||||||
|
static JSValue js_enet_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 1) return JS_RaiseDisrupt(ctx, "flush: expected (host)");
|
||||||
|
ENetHost *host = JS_GetOpaque(argv[0], enet_host_id);
|
||||||
|
if (!host) return JS_RaiseDisrupt(ctx, "flush: invalid host");
|
||||||
|
enet_host_flush(host);
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* broadcast(host, data) */
|
||||||
|
static JSValue js_enet_broadcast(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 2) return JS_RaiseDisrupt(ctx, "broadcast: expected (host, data)");
|
||||||
|
ENetHost *host = JS_GetOpaque(argv[0], enet_host_id);
|
||||||
|
if (!host) return JS_RaiseDisrupt(ctx, "broadcast: invalid host");
|
||||||
|
|
||||||
|
const char *data_str = NULL;
|
||||||
|
size_t data_len = 0;
|
||||||
|
uint8_t *buf = NULL;
|
||||||
|
|
||||||
|
if (JS_IsText(argv[1])) {
|
||||||
|
data_str = JS_ToCStringLen(ctx, &data_len, argv[1]);
|
||||||
|
if (!data_str) return JS_EXCEPTION;
|
||||||
|
} else if (js_is_blob(ctx, argv[1])) {
|
||||||
|
buf = js_get_blob_data(ctx, &data_len, argv[1]);
|
||||||
|
if (!buf) return JS_EXCEPTION;
|
||||||
|
} else {
|
||||||
|
return JS_RaiseDisrupt(ctx, "broadcast: data must be string or blob");
|
||||||
|
}
|
||||||
|
|
||||||
|
ENetPacket *packet = enet_packet_create(data_str ? (const void *)data_str : (const void *)buf, data_len, ENET_PACKET_FLAG_RELIABLE);
|
||||||
|
if (data_str) JS_FreeCString(ctx, data_str);
|
||||||
|
if (!packet) return JS_RaiseDisrupt(ctx, "Failed to create ENet packet");
|
||||||
|
|
||||||
|
enet_host_broadcast(host, 0, packet);
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* host_port(host) → number */
|
||||||
|
static JSValue js_enet_host_port(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 1) return JS_RaiseDisrupt(ctx, "host_port: expected (host)");
|
||||||
|
ENetHost *host = JS_GetOpaque(argv[0], enet_host_id);
|
||||||
|
if (!host) return JS_RaiseDisrupt(ctx, "host_port: invalid host");
|
||||||
|
return JS_NewInt32(ctx, host->address.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* host_address(host) → string */
|
||||||
|
static JSValue js_enet_host_address(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 1) return JS_RaiseDisrupt(ctx, "host_address: expected (host)");
|
||||||
|
ENetHost *host = JS_GetOpaque(argv[0], enet_host_id);
|
||||||
|
if (!host) return JS_RaiseDisrupt(ctx, "host_address: invalid host");
|
||||||
|
char ip_str[128];
|
||||||
|
if (enet_address_get_host_ip(&host->address, ip_str, sizeof(ip_str)) != 0)
|
||||||
|
return JS_NULL;
|
||||||
|
return JS_NewString(ctx, ip_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Peer functions ─────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/* send(peer, data) */
|
||||||
|
static JSValue js_enet_send(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 2) return JS_RaiseDisrupt(ctx, "send: expected (peer, data)");
|
||||||
|
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
|
||||||
|
if (!peer) return JS_RaiseDisrupt(ctx, "send: invalid peer");
|
||||||
|
|
||||||
|
const char *data_str = NULL;
|
||||||
|
size_t data_len = 0;
|
||||||
|
uint8_t *buf = NULL;
|
||||||
|
|
||||||
|
if (JS_IsText(argv[1])) {
|
||||||
|
data_str = JS_ToCStringLen(ctx, &data_len, argv[1]);
|
||||||
|
if (!data_str) return JS_EXCEPTION;
|
||||||
|
} else if (js_is_blob(ctx, argv[1])) {
|
||||||
|
buf = js_get_blob_data(ctx, &data_len, argv[1]);
|
||||||
|
if (!buf) return JS_EXCEPTION;
|
||||||
|
} else {
|
||||||
|
return JS_RaiseDisrupt(ctx, "send: data must be string or blob");
|
||||||
|
}
|
||||||
|
|
||||||
|
ENetPacket *packet = enet_packet_create(data_str ? (const void *)data_str : (const void *)buf, data_len, ENET_PACKET_FLAG_RELIABLE);
|
||||||
|
if (data_str) JS_FreeCString(ctx, data_str);
|
||||||
|
if (!packet) return JS_RaiseDisrupt(ctx, "Failed to create ENet packet");
|
||||||
|
|
||||||
|
if (enet_peer_send(peer, 0, packet) < 0) return JS_RaiseDisrupt(ctx, "Unable to send packet");
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* disconnect(peer) */
|
||||||
|
static JSValue js_enet_disconnect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 1) return JS_RaiseDisrupt(ctx, "disconnect: expected (peer)");
|
||||||
|
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
|
||||||
|
if (!peer) return JS_RaiseDisrupt(ctx, "disconnect: invalid peer");
|
||||||
|
enet_peer_disconnect(peer, 0);
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* disconnect_now(peer) */
|
||||||
|
static JSValue js_enet_disconnect_now(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 1) return JS_RaiseDisrupt(ctx, "disconnect_now: expected (peer)");
|
||||||
|
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
|
||||||
|
if (!peer) return JS_RaiseDisrupt(ctx, "disconnect_now: invalid peer");
|
||||||
|
enet_peer_disconnect_now(peer, 0);
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* disconnect_later(peer) */
|
||||||
|
static JSValue js_enet_disconnect_later(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 1) return JS_RaiseDisrupt(ctx, "disconnect_later: expected (peer)");
|
||||||
|
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
|
||||||
|
if (!peer) return JS_RaiseDisrupt(ctx, "disconnect_later: invalid peer");
|
||||||
|
enet_peer_disconnect_later(peer, 0);
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reset(peer) */
|
||||||
|
static JSValue js_enet_reset(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 1) return JS_RaiseDisrupt(ctx, "reset: expected (peer)");
|
||||||
|
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
|
||||||
|
if (!peer) return JS_RaiseDisrupt(ctx, "reset: invalid peer");
|
||||||
|
enet_peer_reset(peer);
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ping(peer) */
|
||||||
|
static JSValue js_enet_ping(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 1) return JS_RaiseDisrupt(ctx, "ping: expected (peer)");
|
||||||
|
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
|
||||||
|
if (!peer) return JS_RaiseDisrupt(ctx, "ping: invalid peer");
|
||||||
|
enet_peer_ping(peer);
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* throttle_configure(peer, interval, acceleration, deceleration) */
|
||||||
|
static JSValue js_enet_throttle_configure(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 4) return JS_RaiseDisrupt(ctx, "throttle_configure: expected (peer, interval, accel, decel)");
|
||||||
|
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
|
||||||
|
if (!peer) return JS_RaiseDisrupt(ctx, "throttle_configure: invalid peer");
|
||||||
|
int interval, acceleration, deceleration;
|
||||||
|
if (JS_ToInt32(ctx, &interval, argv[1]) || JS_ToInt32(ctx, &acceleration, argv[2]) || JS_ToInt32(ctx, &deceleration, argv[3]))
|
||||||
|
return JS_RaiseDisrupt(ctx, "throttle_configure: expected integer arguments");
|
||||||
|
enet_peer_throttle_configure(peer, interval, acceleration, deceleration);
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* peer_timeout(peer, limit, min, max) */
|
||||||
|
static JSValue js_enet_peer_timeout(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 4) return JS_RaiseDisrupt(ctx, "peer_timeout: expected (peer, limit, min, max)");
|
||||||
|
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
|
||||||
|
if (!peer) return JS_RaiseDisrupt(ctx, "peer_timeout: invalid peer");
|
||||||
|
int timeout_limit, timeout_min, timeout_max;
|
||||||
|
if (JS_ToInt32(ctx, &timeout_limit, argv[1]) || JS_ToInt32(ctx, &timeout_min, argv[2]) || JS_ToInt32(ctx, &timeout_max, argv[3]))
|
||||||
|
return JS_RaiseDisrupt(ctx, "peer_timeout: expected integer arguments");
|
||||||
|
enet_peer_timeout(peer, timeout_limit, timeout_min, timeout_max);
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Peer property getters ──────────────────────────────────── */
|
||||||
|
|
||||||
|
#define PEER_GETTER(name, field, convert) \
|
||||||
|
static JSValue js_enet_##name(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { \
|
||||||
|
if (argc < 1) return JS_RaiseDisrupt(ctx, #name ": expected (peer)"); \
|
||||||
|
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); \
|
||||||
|
if (!peer) return JS_RaiseDisrupt(ctx, #name ": invalid peer"); \
|
||||||
|
return convert(ctx, peer->field); \
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline JSValue _int32(JSContext *ctx, int v) { return JS_NewInt32(ctx, v); }
|
||||||
|
static inline JSValue _uint32(JSContext *ctx, unsigned int v) { return JS_NewUint32(ctx, v); }
|
||||||
|
|
||||||
|
PEER_GETTER(peer_rtt, roundTripTime, _int32)
|
||||||
|
PEER_GETTER(peer_rtt_variance, roundTripTimeVariance, _int32)
|
||||||
|
PEER_GETTER(peer_last_send_time, lastSendTime, _int32)
|
||||||
|
PEER_GETTER(peer_last_receive_time, lastReceiveTime, _int32)
|
||||||
|
PEER_GETTER(peer_mtu, mtu, _int32)
|
||||||
|
PEER_GETTER(peer_outgoing_data_total, outgoingDataTotal, _int32)
|
||||||
|
PEER_GETTER(peer_incoming_data_total, incomingDataTotal, _int32)
|
||||||
|
PEER_GETTER(peer_packet_loss, packetLoss, _int32)
|
||||||
|
PEER_GETTER(peer_state, state, _int32)
|
||||||
|
PEER_GETTER(peer_reliable_data_in_transit, reliableDataInTransit, _int32)
|
||||||
|
|
||||||
|
static JSValue js_enet_peer_incoming_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 1) return JS_RaiseDisrupt(ctx, "peer_incoming_bandwidth: expected (peer)");
|
||||||
|
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
|
||||||
|
if (!peer) return JS_RaiseDisrupt(ctx, "peer_incoming_bandwidth: invalid peer");
|
||||||
|
if (peer->incomingBandwidth == 0) return JS_NewFloat64(ctx, INFINITY);
|
||||||
|
return JS_NewInt32(ctx, peer->incomingBandwidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue js_enet_peer_outgoing_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 1) return JS_RaiseDisrupt(ctx, "peer_outgoing_bandwidth: expected (peer)");
|
||||||
|
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
|
||||||
|
if (!peer) return JS_RaiseDisrupt(ctx, "peer_outgoing_bandwidth: invalid peer");
|
||||||
|
if (peer->outgoingBandwidth == 0) return JS_NewFloat64(ctx, INFINITY);
|
||||||
|
return JS_NewInt32(ctx, peer->outgoingBandwidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue js_enet_peer_port(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 1) return JS_RaiseDisrupt(ctx, "peer_port: expected (peer)");
|
||||||
|
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
|
||||||
|
if (!peer) return JS_RaiseDisrupt(ctx, "peer_port: invalid peer");
|
||||||
|
return JS_NewUint32(ctx, peer->address.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue js_enet_peer_address(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
if (argc < 1) return JS_RaiseDisrupt(ctx, "peer_address: expected (peer)");
|
||||||
|
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
|
||||||
|
if (!peer) return JS_RaiseDisrupt(ctx, "peer_address: invalid peer");
|
||||||
|
char ip_str[128];
|
||||||
|
if (enet_address_get_host_ip(&peer->address, ip_str, sizeof(ip_str)) != 0)
|
||||||
|
return JS_NULL;
|
||||||
|
return JS_NewString(ctx, ip_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSValue js_enet_resolve_hostname(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||||
|
{
|
||||||
|
const char *hostname = JS_ToCString(ctx, argv[0]);
|
||||||
|
JS_FreeCString(ctx, hostname);
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Module export ──────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static const JSCFunctionListEntry js_enet_funcs[] = {
|
||||||
|
/* host */
|
||||||
|
JS_CFUNC_DEF("create_host", 1, js_enet_create_host),
|
||||||
|
JS_CFUNC_DEF("service", 2, js_enet_service),
|
||||||
|
JS_CFUNC_DEF("connect", 3, js_enet_connect),
|
||||||
|
JS_CFUNC_DEF("flush", 1, js_enet_flush),
|
||||||
|
JS_CFUNC_DEF("broadcast", 2, js_enet_broadcast),
|
||||||
|
JS_CFUNC_DEF("host_port", 1, js_enet_host_port),
|
||||||
|
JS_CFUNC_DEF("host_address", 1, js_enet_host_address),
|
||||||
|
/* peer */
|
||||||
|
JS_CFUNC_DEF("send", 2, js_enet_send),
|
||||||
|
JS_CFUNC_DEF("disconnect", 1, js_enet_disconnect),
|
||||||
|
JS_CFUNC_DEF("disconnect_now", 1, js_enet_disconnect_now),
|
||||||
|
JS_CFUNC_DEF("disconnect_later", 1, js_enet_disconnect_later),
|
||||||
|
JS_CFUNC_DEF("reset", 1, js_enet_reset),
|
||||||
|
JS_CFUNC_DEF("ping", 1, js_enet_ping),
|
||||||
|
JS_CFUNC_DEF("throttle_configure", 4, js_enet_throttle_configure),
|
||||||
|
JS_CFUNC_DEF("peer_timeout", 4, js_enet_peer_timeout),
|
||||||
|
JS_CFUNC_DEF("peer_address", 1, js_enet_peer_address),
|
||||||
|
JS_CFUNC_DEF("peer_port", 1, js_enet_peer_port),
|
||||||
|
JS_CFUNC_DEF("peer_rtt", 1, js_enet_peer_rtt),
|
||||||
|
JS_CFUNC_DEF("peer_rtt_variance", 1, js_enet_peer_rtt_variance),
|
||||||
|
JS_CFUNC_DEF("peer_incoming_bandwidth", 1, js_enet_peer_incoming_bandwidth),
|
||||||
|
JS_CFUNC_DEF("peer_outgoing_bandwidth", 1, js_enet_peer_outgoing_bandwidth),
|
||||||
|
JS_CFUNC_DEF("peer_last_send_time", 1, js_enet_peer_last_send_time),
|
||||||
|
JS_CFUNC_DEF("peer_last_receive_time", 1, js_enet_peer_last_receive_time),
|
||||||
|
JS_CFUNC_DEF("peer_mtu", 1, js_enet_peer_mtu),
|
||||||
|
JS_CFUNC_DEF("peer_outgoing_data_total", 1, js_enet_peer_outgoing_data_total),
|
||||||
|
JS_CFUNC_DEF("peer_incoming_data_total", 1, js_enet_peer_incoming_data_total),
|
||||||
|
JS_CFUNC_DEF("peer_packet_loss", 1, js_enet_peer_packet_loss),
|
||||||
|
JS_CFUNC_DEF("peer_state", 1, js_enet_peer_state),
|
||||||
|
JS_CFUNC_DEF("peer_reliable_data_in_transit", 1, js_enet_peer_reliable_data_in_transit),
|
||||||
|
JS_CFUNC_DEF("resolve_hostname", 1, js_enet_resolve_hostname),
|
||||||
|
};
|
||||||
|
|
||||||
|
JSValue js_core_internal_enet_use(JSContext *ctx)
|
||||||
|
{
|
||||||
|
enet_initialize();
|
||||||
|
|
||||||
|
JS_FRAME(ctx);
|
||||||
|
|
||||||
|
JS_NewClassID(&enet_host_id);
|
||||||
|
JS_NewClass(ctx, enet_host_id, &enet_host_def);
|
||||||
|
|
||||||
|
JS_NewClassID(&enet_peer_class_id);
|
||||||
|
JS_NewClass(ctx, enet_peer_class_id, &enet_peer_def);
|
||||||
|
|
||||||
|
JS_ROOT(export_obj, JS_NewObject(ctx));
|
||||||
|
JS_SetPropertyFunctionList(ctx, export_obj.val, js_enet_funcs, countof(js_enet_funcs));
|
||||||
|
JS_RETURN(export_obj.val);
|
||||||
|
}
|
||||||
1122
internal/engine.cm
1122
internal/engine.cm
File diff suppressed because it is too large
Load Diff
@@ -117,10 +117,10 @@ JSC_CCALL(fd_read,
|
|||||||
JSC_SCALL(fd_slurp,
|
JSC_SCALL(fd_slurp,
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (stat(str, &st) != 0)
|
if (stat(str, &st) != 0)
|
||||||
return JS_RaiseDisrupt(js, "stat failed: %s", strerror(errno));
|
return JS_RaiseDisrupt(js, "stat failed for %s: %s", str, strerror(errno));
|
||||||
|
|
||||||
if (!S_ISREG(st.st_mode))
|
if (!S_ISREG(st.st_mode))
|
||||||
return JS_RaiseDisrupt(js, "path is not a regular file");
|
return JS_RaiseDisrupt(js, "path %s is not a regular file", str);
|
||||||
|
|
||||||
size_t size = st.st_size;
|
size_t size = st.st_size;
|
||||||
if (size == 0)
|
if (size == 0)
|
||||||
@@ -223,12 +223,10 @@ JSC_SCALL(fd_rmdir,
|
|||||||
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||||
JSValue args[2] = { JS_NewString(js, full_path), JS_TRUE };
|
JSValue args[2] = { JS_NewString(js, full_path), JS_TRUE };
|
||||||
JSValue result = js_fd_rmdir(js, JS_NULL, 2, args);
|
JSValue result = js_fd_rmdir(js, JS_NULL, 2, args);
|
||||||
JS_FreeValue(js, args[0]);
|
|
||||||
if (JS_IsException(result)) {
|
if (JS_IsException(result)) {
|
||||||
FindClose(hFind);
|
FindClose(hFind);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
JS_FreeValue(js, result);
|
|
||||||
} else {
|
} else {
|
||||||
if (unlink(full_path) != 0) {
|
if (unlink(full_path) != 0) {
|
||||||
FindClose(hFind);
|
FindClose(hFind);
|
||||||
@@ -252,12 +250,10 @@ JSC_SCALL(fd_rmdir,
|
|||||||
if (lstat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
if (lstat(full_path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||||
JSValue args[2] = { JS_NewString(js, full_path), JS_TRUE };
|
JSValue args[2] = { JS_NewString(js, full_path), JS_TRUE };
|
||||||
JSValue result = js_fd_rmdir(js, JS_NULL, 2, args);
|
JSValue result = js_fd_rmdir(js, JS_NULL, 2, args);
|
||||||
JS_FreeValue(js, args[0]);
|
|
||||||
if (JS_IsException(result)) {
|
if (JS_IsException(result)) {
|
||||||
closedir(dir);
|
closedir(dir);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
JS_FreeValue(js, result);
|
|
||||||
} else {
|
} else {
|
||||||
if (unlink(full_path) != 0) {
|
if (unlink(full_path) != 0) {
|
||||||
closedir(dir);
|
closedir(dir);
|
||||||
@@ -640,7 +636,8 @@ static void visit_directory(JSContext *js, JSValue *results, int *result_count,
|
|||||||
} else {
|
} else {
|
||||||
strcpy(item_rel, ffd.cFileName);
|
strcpy(item_rel, ffd.cFileName);
|
||||||
}
|
}
|
||||||
JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel));
|
JSValue name_str = JS_NewString(js, item_rel);
|
||||||
|
JS_SetPropertyNumber(js, *results, (*result_count)++, name_str);
|
||||||
|
|
||||||
if (recurse) {
|
if (recurse) {
|
||||||
struct stat st;
|
struct stat st;
|
||||||
@@ -665,7 +662,8 @@ static void visit_directory(JSContext *js, JSValue *results, int *result_count,
|
|||||||
} else {
|
} else {
|
||||||
strcpy(item_rel, dir->d_name);
|
strcpy(item_rel, dir->d_name);
|
||||||
}
|
}
|
||||||
JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel));
|
JSValue name_str = JS_NewString(js, item_rel);
|
||||||
|
JS_SetPropertyNumber(js, *results, (*result_count)++, name_str);
|
||||||
|
|
||||||
if (recurse) {
|
if (recurse) {
|
||||||
struct stat st;
|
struct stat st;
|
||||||
@@ -765,6 +763,22 @@ JSC_CCALL(fd_readlink,
|
|||||||
#endif
|
#endif
|
||||||
)
|
)
|
||||||
|
|
||||||
|
JSC_CCALL(fd_on_readable,
|
||||||
|
int fd = js2fd(js, argv[0]);
|
||||||
|
if (fd < 0) return JS_EXCEPTION;
|
||||||
|
if (!JS_IsFunction(argv[1]))
|
||||||
|
return JS_RaiseDisrupt(js, "on_readable: callback must be a function");
|
||||||
|
actor_watch_readable(js, fd, argv[1]);
|
||||||
|
return JS_NULL;
|
||||||
|
)
|
||||||
|
|
||||||
|
JSC_CCALL(fd_unwatch,
|
||||||
|
int fd = js2fd(js, argv[0]);
|
||||||
|
if (fd < 0) return JS_EXCEPTION;
|
||||||
|
actor_unwatch(js, fd);
|
||||||
|
return JS_NULL;
|
||||||
|
)
|
||||||
|
|
||||||
static const JSCFunctionListEntry js_fd_funcs[] = {
|
static const JSCFunctionListEntry js_fd_funcs[] = {
|
||||||
MIST_FUNC_DEF(fd, open, 2),
|
MIST_FUNC_DEF(fd, open, 2),
|
||||||
MIST_FUNC_DEF(fd, write, 2),
|
MIST_FUNC_DEF(fd, write, 2),
|
||||||
@@ -791,6 +805,8 @@ static const JSCFunctionListEntry js_fd_funcs[] = {
|
|||||||
MIST_FUNC_DEF(fd, symlink, 2),
|
MIST_FUNC_DEF(fd, symlink, 2),
|
||||||
MIST_FUNC_DEF(fd, realpath, 1),
|
MIST_FUNC_DEF(fd, realpath, 1),
|
||||||
MIST_FUNC_DEF(fd, readlink, 1),
|
MIST_FUNC_DEF(fd, readlink, 1),
|
||||||
|
MIST_FUNC_DEF(fd, on_readable, 2),
|
||||||
|
MIST_FUNC_DEF(fd, unwatch, 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
JSValue js_core_internal_fd_use(JSContext *js) {
|
JSValue js_core_internal_fd_use(JSContext *js) {
|
||||||
|
|||||||
@@ -326,7 +326,6 @@ JSC_SCALL(fd_readdir,
|
|||||||
|
|
||||||
if (pd_file->listfiles(str, listfiles_cb, &ctx, 0) != 0) {
|
if (pd_file->listfiles(str, listfiles_cb, &ctx, 0) != 0) {
|
||||||
const char* err = pd_file->geterr();
|
const char* err = pd_file->geterr();
|
||||||
JS_FreeValue(js, ret_arr);
|
|
||||||
return JS_RaiseDisrupt(js, "listfiles failed: %s", err ? err : "unknown error");
|
return JS_RaiseDisrupt(js, "listfiles failed: %s", err ? err : "unknown error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#define NOTA_IMPLEMENTATION
|
#define NOTA_IMPLEMENTATION
|
||||||
#include "quickjs-internal.h"
|
#include "pit_internal.h"
|
||||||
#include "cell.h"
|
#include "cell.h"
|
||||||
|
|
||||||
static int nota_get_arr_len (JSContext *ctx, JSValue arr) {
|
static int nota_get_arr_len (JSContext *ctx, JSValue arr) {
|
||||||
@@ -24,7 +24,7 @@ typedef struct NotaEncodeContext {
|
|||||||
static void nota_stack_push (NotaEncodeContext *enc, JSValueConst val) {
|
static void nota_stack_push (NotaEncodeContext *enc, JSValueConst val) {
|
||||||
NotaVisitedNode *node = (NotaVisitedNode *)sys_malloc (sizeof (NotaVisitedNode));
|
NotaVisitedNode *node = (NotaVisitedNode *)sys_malloc (sizeof (NotaVisitedNode));
|
||||||
JS_PushGCRef (enc->ctx, &node->ref);
|
JS_PushGCRef (enc->ctx, &node->ref);
|
||||||
node->ref.val = JS_DupValue (enc->ctx, val);
|
node->ref.val = val;
|
||||||
node->next = enc->visited_list;
|
node->next = enc->visited_list;
|
||||||
enc->visited_list = node;
|
enc->visited_list = node;
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,6 @@ static void nota_stack_push (NotaEncodeContext *enc, JSValueConst val) {
|
|||||||
static void nota_stack_pop (NotaEncodeContext *enc) {
|
static void nota_stack_pop (NotaEncodeContext *enc) {
|
||||||
NotaVisitedNode *node = enc->visited_list;
|
NotaVisitedNode *node = enc->visited_list;
|
||||||
enc->visited_list = node->next;
|
enc->visited_list = node->next;
|
||||||
JS_FreeValue (enc->ctx, node->ref.val);
|
|
||||||
JS_PopGCRef (enc->ctx, &node->ref);
|
JS_PopGCRef (enc->ctx, &node->ref);
|
||||||
sys_free (node);
|
sys_free (node);
|
||||||
}
|
}
|
||||||
@@ -48,14 +47,12 @@ static int nota_stack_has (NotaEncodeContext *enc, JSValueConst val) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static JSValue nota_apply_replacer (NotaEncodeContext *enc, JSValueConst holder, JSValueConst key, JSValueConst val) {
|
static JSValue nota_apply_replacer (NotaEncodeContext *enc, JSValueConst holder, JSValueConst key, JSValueConst val) {
|
||||||
if (!enc->replacer_ref || JS_IsNull (enc->replacer_ref->val)) return JS_DupValue (enc->ctx, val);
|
if (!enc->replacer_ref || JS_IsNull (enc->replacer_ref->val)) return val;
|
||||||
|
|
||||||
JSValue args[2] = { JS_DupValue (enc->ctx, key), JS_DupValue (enc->ctx, val) };
|
JSValue args[2] = { key, val };
|
||||||
JSValue result = JS_Call (enc->ctx, enc->replacer_ref->val, holder, 2, args);
|
JSValue result = JS_Call (enc->ctx, enc->replacer_ref->val, holder, 2, args);
|
||||||
JS_FreeValue (enc->ctx, args[0]);
|
|
||||||
JS_FreeValue (enc->ctx, args[1]);
|
|
||||||
|
|
||||||
if (JS_IsException (result)) return JS_DupValue (enc->ctx, val);
|
if (JS_IsException (result)) return val;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,15 +137,11 @@ static char *js_do_nota_decode (JSContext *js, JSValue *tmp, char *nota, JSValue
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!JS_IsNull (reviver)) {
|
if (!JS_IsNull (reviver)) {
|
||||||
JSValue args[2] = { JS_DupValue (js, key), JS_DupValue (js, *tmp) };
|
JSValue args[2] = { key, *tmp };
|
||||||
JSValue revived = JS_Call (js, reviver, holder, 2, args);
|
JSValue revived = JS_Call (js, reviver, holder, 2, args);
|
||||||
JS_FreeValue (js, args[0]);
|
|
||||||
JS_FreeValue (js, args[1]);
|
|
||||||
if (!JS_IsException (revived)) {
|
if (!JS_IsException (revived)) {
|
||||||
JS_FreeValue (js, *tmp);
|
|
||||||
*tmp = revived;
|
*tmp = revived;
|
||||||
} else {
|
} else {
|
||||||
JS_FreeValue (js, revived);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,10 +222,8 @@ static void nota_encode_value (NotaEncodeContext *enc, JSValueConst val, JSValue
|
|||||||
if (!JS_IsNull (adata)) {
|
if (!JS_IsNull (adata)) {
|
||||||
nota_write_sym (&enc->nb, NOTA_PRIVATE);
|
nota_write_sym (&enc->nb, NOTA_PRIVATE);
|
||||||
nota_encode_value (enc, adata, replaced_ref.val, JS_NULL);
|
nota_encode_value (enc, adata, replaced_ref.val, JS_NULL);
|
||||||
JS_FreeValue (ctx, adata);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
JS_FreeValue (ctx, adata);
|
|
||||||
if (nota_stack_has (enc, replaced_ref.val)) {
|
if (nota_stack_has (enc, replaced_ref.val)) {
|
||||||
enc->cycle = 1;
|
enc->cycle = 1;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "cell.h"
|
#include "cell.h"
|
||||||
#include "cell_internal.h"
|
#include "pit_internal.h"
|
||||||
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
@@ -308,12 +308,9 @@ JSC_SCALL(os_system,
|
|||||||
setting pause_flag = 2. Bump turn_gen so stale timer events are
|
setting pause_flag = 2. Bump turn_gen so stale timer events are
|
||||||
ignored, and clear the pause flag so the VM doesn't raise
|
ignored, and clear the pause flag so the VM doesn't raise
|
||||||
"interrupted" on the next backward branch. */
|
"interrupted" on the next backward branch. */
|
||||||
cell_rt *crt = JS_GetContextOpaque(js);
|
atomic_fetch_add_explicit(&js->turn_gen, 1, memory_order_relaxed);
|
||||||
if (crt) {
|
JS_SetPauseFlag(js, 0);
|
||||||
atomic_fetch_add_explicit(&crt->turn_gen, 1, memory_order_relaxed);
|
js->turn_start_ns = cell_ns();
|
||||||
JS_SetPauseFlag(js, 0);
|
|
||||||
crt->turn_start_ns = cell_ns();
|
|
||||||
}
|
|
||||||
ret = number2js(js, err);
|
ret = number2js(js, err);
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -706,6 +703,27 @@ static JSValue js_os_stack(JSContext *js, JSValue self, int argc, JSValue *argv)
|
|||||||
JS_RETURN(arr.val);
|
JS_RETURN(arr.val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static JSValue js_os_unstone(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||||
|
if (argc < 1) return JS_NULL;
|
||||||
|
JSValue obj = argv[0];
|
||||||
|
if (mist_is_blob(obj)) {
|
||||||
|
JSBlob *bd = (JSBlob *)chase(obj);
|
||||||
|
bd->mist_hdr = objhdr_set_s(bd->mist_hdr, false);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
if (JS_IsArray(obj)) {
|
||||||
|
JSArray *arr = JS_VALUE_GET_ARRAY(obj);
|
||||||
|
arr->mist_hdr = objhdr_set_s(arr->mist_hdr, false);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
if (mist_is_gc_object(obj)) {
|
||||||
|
JSRecord *rec = JS_VALUE_GET_RECORD(obj);
|
||||||
|
rec->mist_hdr = objhdr_set_s(rec->mist_hdr, false);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static const JSCFunctionListEntry js_os_funcs[] = {
|
static const JSCFunctionListEntry js_os_funcs[] = {
|
||||||
MIST_FUNC_DEF(os, platform, 0),
|
MIST_FUNC_DEF(os, platform, 0),
|
||||||
MIST_FUNC_DEF(os, arch, 0),
|
MIST_FUNC_DEF(os, arch, 0),
|
||||||
@@ -734,6 +752,7 @@ static const JSCFunctionListEntry js_os_funcs[] = {
|
|||||||
MIST_FUNC_DEF(os, getenv, 1),
|
MIST_FUNC_DEF(os, getenv, 1),
|
||||||
MIST_FUNC_DEF(os, qbe, 1),
|
MIST_FUNC_DEF(os, qbe, 1),
|
||||||
MIST_FUNC_DEF(os, stack, 1),
|
MIST_FUNC_DEF(os, stack, 1),
|
||||||
|
MIST_FUNC_DEF(os, unstone, 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
JSValue js_core_internal_os_use(JSContext *js) {
|
JSValue js_core_internal_os_use(JSContext *js) {
|
||||||
|
|||||||
337
internal/shop.cm
337
internal/shop.cm
@@ -13,8 +13,8 @@ var os = use('internal/os')
|
|||||||
var link = use('link')
|
var link = use('link')
|
||||||
|
|
||||||
// These come from env (via core_extras in engine.cm):
|
// These come from env (via core_extras in engine.cm):
|
||||||
// analyze, run_ast_fn, core_json, use_cache, shop_path, actor_api, runtime_env,
|
// analyze, run_ast_fn, core_json, use_cache, core_path, shop_path, actor_api,
|
||||||
// content_hash, cache_path, ensure_build_dir
|
// runtime_env, content_hash, cache_path, ensure_build_dir
|
||||||
var shop_json = core_json
|
var shop_json = core_json
|
||||||
var global_shop_path = shop_path
|
var global_shop_path = shop_path
|
||||||
var my$_ = actor_api
|
var my$_ = actor_api
|
||||||
@@ -29,21 +29,18 @@ function safe_c_name(name) {
|
|||||||
|
|
||||||
function pull_from_cache(content)
|
function pull_from_cache(content)
|
||||||
{
|
{
|
||||||
var path = hash_path(content)
|
var path = cache_path(content)
|
||||||
if (fd.is_file(path))
|
if (fd.is_file(path)) {
|
||||||
|
log.system('shop: cache hit')
|
||||||
return fd.slurp(path)
|
return fd.slurp(path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function put_into_cache(content, obj)
|
function put_into_cache(content, obj)
|
||||||
{
|
{
|
||||||
var path = hash_path(content)
|
var path = cache_path(content)
|
||||||
fd.slurpwrite(path, obj)
|
fd.slurpwrite(path, obj)
|
||||||
}
|
log.system('shop: cached')
|
||||||
|
|
||||||
function hash_path(content, salt)
|
|
||||||
{
|
|
||||||
var s = salt || 'mach'
|
|
||||||
return global_shop_path + '/build/' + content_hash(stone(blob(text(content) + '\n' + s)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var Shop = {}
|
var Shop = {}
|
||||||
@@ -236,31 +233,34 @@ function safe_canonicalize(pkg) {
|
|||||||
// given a file, find the absolute path, package name, and import name
|
// given a file, find the absolute path, package name, and import name
|
||||||
Shop.file_info = function(file) {
|
Shop.file_info = function(file) {
|
||||||
var info = {
|
var info = {
|
||||||
path: file,
|
path: file,
|
||||||
is_module: false,
|
is_module: false,
|
||||||
is_actor: false,
|
is_actor: false,
|
||||||
package: null,
|
package: null,
|
||||||
name: null
|
name: null
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ends_with(file, MOD_EXT))
|
if (ends_with(file, MOD_EXT))
|
||||||
info.is_module = true
|
info.is_module = true
|
||||||
else if (ends_with(file, ACTOR_EXT))
|
else if (ends_with(file, ACTOR_EXT))
|
||||||
info.is_actor = true
|
info.is_actor = true
|
||||||
|
|
||||||
// Find package directory and determine package name
|
// Find package directory and determine package name
|
||||||
|
// find_package_dir resolves symlinks internally, so we must use the
|
||||||
|
// resolved file path for substring math to get the correct name.
|
||||||
var pkg_dir = pkg_tools.find_package_dir(file)
|
var pkg_dir = pkg_tools.find_package_dir(file)
|
||||||
|
var resolved_file = fd.realpath(file) || file
|
||||||
if (pkg_dir) {
|
if (pkg_dir) {
|
||||||
info.package = abs_path_to_package(pkg_dir)
|
info.package = abs_path_to_package(pkg_dir)
|
||||||
|
|
||||||
if (info.is_actor)
|
if (info.is_actor)
|
||||||
info.name = text(file, length(pkg_dir) + 1, length(file) - length(ACTOR_EXT))
|
info.name = text(resolved_file, length(pkg_dir) + 1, length(resolved_file) - length(ACTOR_EXT))
|
||||||
else if (info.is_module)
|
else if (info.is_module)
|
||||||
info.name = text(file, length(pkg_dir) + 1, length(file) - length(MOD_EXT))
|
info.name = text(resolved_file, length(pkg_dir) + 1, length(resolved_file) - length(MOD_EXT))
|
||||||
else
|
else
|
||||||
info.name = text(file, length(pkg_dir) + 1)
|
info.name = text(resolved_file, length(pkg_dir) + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,6 +455,7 @@ Shop.extract_commit_hash = function(pkg, response) {
|
|||||||
|
|
||||||
var open_dls = {}
|
var open_dls = {}
|
||||||
var package_dylibs = {} // pkg -> [{file, symbol, dylib}, ...]
|
var package_dylibs = {} // pkg -> [{file, symbol, dylib}, ...]
|
||||||
|
var reload_hashes = {} // cache_key -> content hash for reload change detection
|
||||||
|
|
||||||
function open_dylib_cached(path) {
|
function open_dylib_cached(path) {
|
||||||
var handle = open_dls[path]
|
var handle = open_dls[path]
|
||||||
@@ -504,6 +505,11 @@ function try_native_mod_dylib(pkg, stem) {
|
|||||||
var handle = open_dylib_cached(build_path)
|
var handle = open_dylib_cached(build_path)
|
||||||
if (!handle) return null
|
if (!handle) return null
|
||||||
var sym = Shop.c_symbol_for_file(pkg, stem)
|
var sym = Shop.c_symbol_for_file(pkg, stem)
|
||||||
|
// Verify the symbol actually exists in the dylib before returning native descriptor
|
||||||
|
if (sym && !os.dylib_has_symbol(handle, sym) && !os.dylib_has_symbol(handle, 'cell_main')) {
|
||||||
|
log.shop('native dylib for ' + stem + ' (dylib=' + build_path + ') missing symbol ' + sym + ' and cell_main, falling back to bytecode')
|
||||||
|
return null
|
||||||
|
}
|
||||||
return {_native: true, _handle: handle, _sym: sym}
|
return {_native: true, _handle: handle, _sym: sym}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,6 +750,8 @@ function resolve_mod_fn(path, pkg) {
|
|||||||
var dylib_path = null
|
var dylib_path = null
|
||||||
var handle = null
|
var handle = null
|
||||||
var sym = null
|
var sym = null
|
||||||
|
var _fi = null
|
||||||
|
var _fi_pkg = null
|
||||||
|
|
||||||
policy = get_policy()
|
policy = get_policy()
|
||||||
|
|
||||||
@@ -755,8 +763,8 @@ function resolve_mod_fn(path, pkg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for native .cm dylib at deterministic path first
|
// Check for native .cm dylib at deterministic path first (only in native mode)
|
||||||
if (policy.allow_dylib && pkg && _stem) {
|
if (policy.native && policy.allow_dylib && pkg && _stem) {
|
||||||
native_result = try_native_mod_dylib(pkg, _stem)
|
native_result = try_native_mod_dylib(pkg, _stem)
|
||||||
if (native_result != null) return native_result
|
if (native_result != null) return native_result
|
||||||
}
|
}
|
||||||
@@ -769,7 +777,10 @@ function resolve_mod_fn(path, pkg) {
|
|||||||
if (dylib_path) {
|
if (dylib_path) {
|
||||||
handle = os.dylib_open(dylib_path)
|
handle = os.dylib_open(dylib_path)
|
||||||
if (handle) {
|
if (handle) {
|
||||||
sym = pkg && _stem ? Shop.c_symbol_for_file(pkg, _stem) : null
|
// Derive symbol from file_info (authoritative package), not caller's pkg
|
||||||
|
_fi = Shop.file_info(path)
|
||||||
|
_fi_pkg = _fi.package || pkg
|
||||||
|
sym = _fi_pkg ? Shop.c_symbol_for_file(_fi_pkg, (_fi.name ? _fi.name + (_fi.is_actor ? '.ce' : '.cm') : fd.basename(path))) : null
|
||||||
return {_native: true, _handle: handle, _sym: sym}
|
return {_native: true, _handle: handle, _sym: sym}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -786,11 +797,11 @@ function resolve_mod_fn(path, pkg) {
|
|||||||
|
|
||||||
// Check for cached mcode in content-addressed store
|
// Check for cached mcode in content-addressed store
|
||||||
if (policy.allow_compile) {
|
if (policy.allow_compile) {
|
||||||
cached_mcode_path = hash_path(content_key, 'mcode')
|
cached_mcode_path = cache_path(content_key, 'mcode')
|
||||||
if (fd.is_file(cached_mcode_path)) {
|
if (fd.is_file(cached_mcode_path)) {
|
||||||
mcode_json = text(fd.slurp(cached_mcode_path))
|
mcode_json = text(fd.slurp(cached_mcode_path))
|
||||||
compiled = mach_compile_mcode_bin(path, mcode_json)
|
compiled = mach_compile_mcode_bin(path, mcode_json)
|
||||||
put_into_cache(content_key, compiled)
|
if (!policy.native) put_into_cache(content_key, compiled)
|
||||||
return compiled
|
return compiled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -811,12 +822,14 @@ function resolve_mod_fn(path, pkg) {
|
|||||||
mcode_json = shop_json.encode(optimized)
|
mcode_json = shop_json.encode(optimized)
|
||||||
|
|
||||||
// Cache mcode (architecture-independent) in content-addressed store
|
// Cache mcode (architecture-independent) in content-addressed store
|
||||||
fd.ensure_dir(global_shop_path + '/build')
|
if (!policy.native) {
|
||||||
fd.slurpwrite(cached_mcode_path, stone(blob(mcode_json)))
|
fd.ensure_dir(global_shop_path + '/build')
|
||||||
|
fd.slurpwrite(cached_mcode_path, stone(blob(mcode_json)))
|
||||||
|
}
|
||||||
|
|
||||||
// Cache mach blob
|
// Cache mach blob
|
||||||
compiled = mach_compile_mcode_bin(path, mcode_json)
|
compiled = mach_compile_mcode_bin(path, mcode_json)
|
||||||
put_into_cache(content_key, compiled)
|
if (!policy.native) put_into_cache(content_key, compiled)
|
||||||
|
|
||||||
return compiled
|
return compiled
|
||||||
}
|
}
|
||||||
@@ -825,6 +838,50 @@ function resolve_mod_fn(path, pkg) {
|
|||||||
disrupt
|
disrupt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve a module's bytecode only (skip native dylib check).
|
||||||
|
// Used as fallback when a cached native dylib fails to load.
|
||||||
|
function resolve_mod_fn_bytecode(path, pkg) {
|
||||||
|
if (!fd.is_file(path)) return null
|
||||||
|
|
||||||
|
var content = text(fd.slurp(path))
|
||||||
|
if (length(content) == 0) return null
|
||||||
|
var content_key = stone(blob(content))
|
||||||
|
var cached = null
|
||||||
|
var cached_mcode_path = null
|
||||||
|
var mcode_json = null
|
||||||
|
var compiled = null
|
||||||
|
|
||||||
|
// Check cache for pre-compiled .mach blob
|
||||||
|
cached = pull_from_cache(content_key)
|
||||||
|
if (cached) return cached
|
||||||
|
|
||||||
|
// Check for cached mcode
|
||||||
|
cached_mcode_path = cache_path(content_key, 'mcode')
|
||||||
|
if (fd.is_file(cached_mcode_path)) {
|
||||||
|
mcode_json = text(fd.slurp(cached_mcode_path))
|
||||||
|
compiled = mach_compile_mcode_bin(path, mcode_json)
|
||||||
|
put_into_cache(content_key, compiled)
|
||||||
|
return compiled
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile from source
|
||||||
|
if (!_mcode_mod) _mcode_mod = use_cache['core/mcode'] || use_cache['mcode']
|
||||||
|
if (!_streamline_mod) _streamline_mod = use_cache['core/streamline'] || use_cache['streamline']
|
||||||
|
if (!_mcode_mod || !_streamline_mod) return null
|
||||||
|
|
||||||
|
var ast = analyze(content, path)
|
||||||
|
var ir = _mcode_mod(ast)
|
||||||
|
var optimized = _streamline_mod(ir)
|
||||||
|
mcode_json = shop_json.encode(optimized)
|
||||||
|
|
||||||
|
fd.ensure_dir(global_shop_path + '/build')
|
||||||
|
fd.slurpwrite(cache_path(content_key, 'mcode'), stone(blob(mcode_json)))
|
||||||
|
|
||||||
|
compiled = mach_compile_mcode_bin(path, mcode_json)
|
||||||
|
put_into_cache(content_key, compiled)
|
||||||
|
return compiled
|
||||||
|
}
|
||||||
|
|
||||||
// given a path and a package context
|
// given a path and a package context
|
||||||
// return module info about where it was found
|
// return module info about where it was found
|
||||||
// Resolve a module path to {path, scope, pkg} without compiling.
|
// Resolve a module path to {path, scope, pkg} without compiling.
|
||||||
@@ -884,7 +941,7 @@ function resolve_path(path, ctx)
|
|||||||
if (alias) {
|
if (alias) {
|
||||||
alias_path = get_packages_dir() + '/' + fd.safe_package_path(alias.package) + '/' + alias.path
|
alias_path = get_packages_dir() + '/' + fd.safe_package_path(alias.package) + '/' + alias.path
|
||||||
if (fd.is_file(alias_path))
|
if (fd.is_file(alias_path))
|
||||||
return {path: alias_path, scope: SCOPE_PACKAGE, pkg: ctx}
|
return {path: alias_path, scope: SCOPE_PACKAGE, pkg: alias.package}
|
||||||
}
|
}
|
||||||
|
|
||||||
package_path = get_packages_dir() + '/' + fd.safe_package_path(path)
|
package_path = get_packages_dir() + '/' + fd.safe_package_path(path)
|
||||||
@@ -964,19 +1021,41 @@ function ensure_package_dylibs(pkg) {
|
|||||||
var build_mod = use_cache['core/build']
|
var build_mod = use_cache['core/build']
|
||||||
var target = null
|
var target = null
|
||||||
var c_files = null
|
var c_files = null
|
||||||
|
var _all_ok = true
|
||||||
|
var _ri = 0
|
||||||
|
|
||||||
if (build_mod) {
|
if (build_mod) {
|
||||||
target = detect_host_target()
|
// Fast path: if manifest exists and all dylibs are present, skip build_dynamic
|
||||||
if (!target) return null
|
results = read_dylib_manifest(_pkg)
|
||||||
|
if (results != null) {
|
||||||
c_files = pkg_tools.get_c_files(_pkg, target, true)
|
_all_ok = true
|
||||||
if (!c_files || length(c_files) == 0) {
|
_ri = 0
|
||||||
package_dylibs[_pkg] = []
|
while (_ri < length(results)) {
|
||||||
return []
|
if (results[_ri].dylib && !fd.is_file(results[_ri].dylib)) {
|
||||||
|
_all_ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_ri = _ri + 1
|
||||||
|
}
|
||||||
|
if (_all_ok) {
|
||||||
|
log.shop('manifest ok for ' + _pkg + ' (' + text(length(results)) + ' modules)')
|
||||||
|
} else {
|
||||||
|
results = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (results == null) {
|
||||||
|
target = detect_host_target()
|
||||||
|
if (!target) return null
|
||||||
|
|
||||||
log.shop('ensuring C modules for ' + _pkg)
|
c_files = pkg_tools.get_c_files(_pkg, target, true)
|
||||||
results = build_mod.build_dynamic(_pkg, target, 'release', {})
|
if (!c_files || length(c_files) == 0) {
|
||||||
|
package_dylibs[_pkg] = []
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
log.shop('ensuring C modules for ' + _pkg)
|
||||||
|
results = build_mod.build_dynamic(_pkg, target, 'release', {})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// No build module at runtime — read manifest from cell build
|
// No build module at runtime — read manifest from cell build
|
||||||
results = read_dylib_manifest(_pkg)
|
results = read_dylib_manifest(_pkg)
|
||||||
@@ -1292,6 +1371,8 @@ function execute_module(info)
|
|||||||
var inject = null
|
var inject = null
|
||||||
var env = null
|
var env = null
|
||||||
var pkg = null
|
var pkg = null
|
||||||
|
var _native_load = null
|
||||||
|
var _bc = null
|
||||||
|
|
||||||
if (mod_resolve.scope < 900) {
|
if (mod_resolve.scope < 900) {
|
||||||
// Check if native dylib was resolved (descriptor with _handle and _sym)
|
// Check if native dylib was resolved (descriptor with _handle and _sym)
|
||||||
@@ -1302,8 +1383,27 @@ function execute_module(info)
|
|||||||
pkg = file_info.package
|
pkg = file_info.package
|
||||||
env.use = make_use_fn(pkg, true)
|
env.use = make_use_fn(pkg, true)
|
||||||
env = stone(env)
|
env = stone(env)
|
||||||
used = os.native_module_load_named(
|
_native_load = function() {
|
||||||
mod_resolve.symbol._handle, mod_resolve.symbol._sym, env)
|
used = os.native_module_load_named(
|
||||||
|
mod_resolve.symbol._handle, mod_resolve.symbol._sym, env)
|
||||||
|
log.shop('loaded ' + info.cache_key + ' [native]')
|
||||||
|
} disruption {
|
||||||
|
// Native load failed — fall back to bytecode
|
||||||
|
log.shop('native load failed for ' + info.cache_key + ' (sym=' + text(mod_resolve.symbol._sym || '') + '), falling back to bytecode')
|
||||||
|
_bc = resolve_mod_fn_bytecode(mod_resolve.path, file_info.package)
|
||||||
|
if (_bc) {
|
||||||
|
// Build a fresh env for bytecode (env is stoned, can't modify)
|
||||||
|
env = inject_env(inject)
|
||||||
|
env.use = make_use_fn(pkg)
|
||||||
|
env = stone(env)
|
||||||
|
used = mach_load(_bc, env)
|
||||||
|
log.shop('loaded ' + info.cache_key + ' [bytecode fallback]')
|
||||||
|
} else {
|
||||||
|
log.error('native load failed and bytecode fallback also failed for ' + info.cache_key)
|
||||||
|
disrupt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_native_load()
|
||||||
} else {
|
} else {
|
||||||
// Build env with runtime fns, capabilities, and use function
|
// Build env with runtime fns, capabilities, and use function
|
||||||
file_info = Shop.file_info(mod_resolve.path)
|
file_info = Shop.file_info(mod_resolve.path)
|
||||||
@@ -1315,6 +1415,7 @@ function execute_module(info)
|
|||||||
|
|
||||||
// Load compiled bytecode with env
|
// Load compiled bytecode with env
|
||||||
used = mach_load(mod_resolve.symbol, env)
|
used = mach_load(mod_resolve.symbol, env)
|
||||||
|
log.shop('loaded ' + info.cache_key + ' [bytecode]')
|
||||||
}
|
}
|
||||||
} else if (c_resolve.scope < 900) {
|
} else if (c_resolve.scope < 900) {
|
||||||
// C only
|
// C only
|
||||||
@@ -1323,7 +1424,7 @@ function execute_module(info)
|
|||||||
log.shop(`Module could not be found (c_resolve scope=${info.c_resolve.scope}, mod_resolve scope=${info.mod_resolve.scope}, cache_key=${info.cache_key})`); disrupt
|
log.shop(`Module could not be found (c_resolve scope=${info.c_resolve.scope}, mod_resolve scope=${info.mod_resolve.scope}, cache_key=${info.cache_key})`); disrupt
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!used) { log.error(`Module ${info} returned null`); disrupt }
|
if (!used) { log.error('Module ' + text(info.cache_key || info) + ' returned null'); disrupt }
|
||||||
|
|
||||||
return used
|
return used
|
||||||
}
|
}
|
||||||
@@ -1398,18 +1499,18 @@ Shop.use = function use(path, _pkg_ctx) {
|
|||||||
if (use_cache[info.cache_key])
|
if (use_cache[info.cache_key])
|
||||||
return use_cache[info.cache_key]
|
return use_cache[info.cache_key]
|
||||||
|
|
||||||
push(use_stack, _use_entry)
|
use_stack[] = _use_entry
|
||||||
var _use_result = null
|
var _use_result = null
|
||||||
var _use_ok = false
|
var _use_ok = false
|
||||||
var _load = function() {
|
var _load = function() {
|
||||||
_use_result = execute_module(info)
|
_use_result = execute_module(info)
|
||||||
_use_ok = true
|
_use_ok = true
|
||||||
} disruption {
|
} disruption {
|
||||||
pop(use_stack)
|
use_stack[]
|
||||||
disrupt
|
disrupt
|
||||||
}
|
}
|
||||||
_load()
|
_load()
|
||||||
pop(use_stack)
|
use_stack[]
|
||||||
use_cache[info.cache_key] = _use_result
|
use_cache[info.cache_key] = _use_result
|
||||||
return _use_result
|
return _use_result
|
||||||
}
|
}
|
||||||
@@ -1543,12 +1644,16 @@ function download_zip(pkg, commit_hash) {
|
|||||||
return _download()
|
return _download()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get zip from cache, returns null if not cached
|
// Get zip from cache, returns null if not cached or empty
|
||||||
function get_cached_zip(pkg, commit_hash) {
|
function get_cached_zip(pkg, commit_hash) {
|
||||||
var cache_path = get_cache_path(pkg, commit_hash)
|
var cache_path = get_cache_path(pkg, commit_hash)
|
||||||
if (fd.is_file(cache_path))
|
var data = null
|
||||||
return fd.slurp(cache_path)
|
if (fd.is_file(cache_path)) {
|
||||||
|
data = fd.slurp(cache_path)
|
||||||
|
stone(data)
|
||||||
|
if (length(data) > 0) return data
|
||||||
|
fd.remove(cache_path)
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1804,6 +1909,7 @@ Shop.sync_with_deps = function(pkg, opts) {
|
|||||||
if (visited[current]) continue
|
if (visited[current]) continue
|
||||||
visited[current] = true
|
visited[current] = true
|
||||||
|
|
||||||
|
log.build(' Fetching ' + current + '...')
|
||||||
Shop.sync(current, opts)
|
Shop.sync(current, opts)
|
||||||
|
|
||||||
_read_deps = function() {
|
_read_deps = function() {
|
||||||
@@ -1817,7 +1923,7 @@ Shop.sync_with_deps = function(pkg, opts) {
|
|||||||
arrfor(array(deps), function(alias) {
|
arrfor(array(deps), function(alias) {
|
||||||
dep_locator = deps[alias]
|
dep_locator = deps[alias]
|
||||||
if (!visited[dep_locator])
|
if (!visited[dep_locator])
|
||||||
push(queue, dep_locator)
|
queue[] = dep_locator
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1903,19 +2009,27 @@ Shop.file_reload = function(file)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Shop.module_reload = function(path, package) {
|
Shop.module_reload = function(path, package) {
|
||||||
if (!Shop.is_loaded(path,package)) return
|
if (!Shop.is_loaded(path, package)) return false
|
||||||
|
|
||||||
// Clear the module info cache for this path
|
|
||||||
var lookup_key = package ? package + ':' + path : ':' + path
|
var lookup_key = package ? package + ':' + path : ':' + path
|
||||||
module_info_cache[lookup_key] = null
|
var info = resolve_module_info(path, package)
|
||||||
|
if (!info) return false
|
||||||
|
|
||||||
// Invalidate package dylib cache so next resolve triggers rebuild
|
// Check if source actually changed
|
||||||
if (package) {
|
var mod_path = null
|
||||||
package_dylibs[package] = null
|
var source = null
|
||||||
|
var new_hash = null
|
||||||
|
if (info.mod_resolve) mod_path = info.mod_resolve.path
|
||||||
|
if (mod_path && fd.is_file(mod_path)) {
|
||||||
|
source = fd.slurp(mod_path)
|
||||||
|
new_hash = content_hash(stone(blob(text(source))))
|
||||||
|
if (reload_hashes[info.cache_key] == new_hash) return false
|
||||||
|
reload_hashes[info.cache_key] = new_hash
|
||||||
}
|
}
|
||||||
|
|
||||||
var info = resolve_module_info(path, package)
|
// Clear caches
|
||||||
if (!info) return
|
module_info_cache[lookup_key] = null
|
||||||
|
if (package) package_dylibs[package] = null
|
||||||
|
|
||||||
var cache_key = info.cache_key
|
var cache_key = info.cache_key
|
||||||
var old = use_cache[cache_key]
|
var old = use_cache[cache_key]
|
||||||
@@ -1924,13 +2038,18 @@ Shop.module_reload = function(path, package) {
|
|||||||
var newmod = get_module(path, package)
|
var newmod = get_module(path, package)
|
||||||
use_cache[cache_key] = newmod
|
use_cache[cache_key] = newmod
|
||||||
|
|
||||||
|
// Smart update: unstone -> merge -> re-stone to preserve references
|
||||||
if (old && is_object(old) && is_object(newmod)) {
|
if (old && is_object(old) && is_object(newmod)) {
|
||||||
|
os.unstone(old)
|
||||||
arrfor(array(newmod), function(k) { old[k] = newmod[k] })
|
arrfor(array(newmod), function(k) { old[k] = newmod[k] })
|
||||||
arrfor(array(old), function(k) {
|
arrfor(array(old), function(k) {
|
||||||
if (!(k in newmod)) old[k] = null
|
if (!(k in newmod)) old[k] = null
|
||||||
})
|
})
|
||||||
|
stone(old)
|
||||||
use_cache[cache_key] = old
|
use_cache[cache_key] = old
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_package_scripts(package)
|
function get_package_scripts(package)
|
||||||
@@ -1943,7 +2062,7 @@ function get_package_scripts(package)
|
|||||||
for (i = 0; i < length(files); i++) {
|
for (i = 0; i < length(files); i++) {
|
||||||
file = files[i]
|
file = files[i]
|
||||||
if (ends_with(file, '.cm') || ends_with(file, '.ce')) {
|
if (ends_with(file, '.cm') || ends_with(file, '.ce')) {
|
||||||
push(scripts, file)
|
scripts[] = file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1965,7 +2084,7 @@ function extract_use_calls(source) {
|
|||||||
if (end == null) end = search(text(source, start), '"')
|
if (end == null) end = search(text(source, start), '"')
|
||||||
if (end != null) {
|
if (end != null) {
|
||||||
arg = text(source, start, start + end)
|
arg = text(source, start, start + end)
|
||||||
push(uses, arg)
|
uses[] = arg
|
||||||
}
|
}
|
||||||
idx = search(text(source, idx + 4), "use(")
|
idx = search(text(source, idx + 4), "use(")
|
||||||
if (idx != null) idx = idx + (source.length - (source.length - idx))
|
if (idx != null) idx = idx + (source.length - (source.length - idx))
|
||||||
@@ -1988,12 +2107,18 @@ Shop.build_package_scripts = function(package)
|
|||||||
resolve_mod_fn(pkg_dir + '/' + script, package)
|
resolve_mod_fn(pkg_dir + '/' + script, package)
|
||||||
ok = ok + 1
|
ok = ok + 1
|
||||||
} disruption {
|
} disruption {
|
||||||
push(errors, script)
|
errors[] = script
|
||||||
log.console(" compile error: " + package + '/' + script)
|
log.build(" compile error: " + package + '/' + script)
|
||||||
}
|
}
|
||||||
_try()
|
_try()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (length(errors) > 0) {
|
||||||
|
log.build(' Compiling scripts (' + text(ok) + ' ok, ' + text(length(errors)) + ' errors)')
|
||||||
|
} else if (ok > 0) {
|
||||||
|
log.build(' Compiling scripts (' + text(ok) + ' ok)')
|
||||||
|
}
|
||||||
|
|
||||||
return {ok: ok, errors: errors, total: length(scripts)}
|
return {ok: ok, errors: errors, total: length(scripts)}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2035,14 +2160,14 @@ Shop.audit_use_resolution = function(package) {
|
|||||||
end = search(rest, quote)
|
end = search(rest, quote)
|
||||||
if (end == null) continue
|
if (end == null) continue
|
||||||
arg = text(rest, 0, end)
|
arg = text(rest, 0, end)
|
||||||
if (length(arg) > 0) push(uses, arg)
|
if (length(arg) > 0) uses[] = arg
|
||||||
rest = text(rest, end + 1)
|
rest = text(rest, end + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
arrfor(uses, function(mod) {
|
arrfor(uses, function(mod) {
|
||||||
var _resolve = function() {
|
var _resolve = function() {
|
||||||
info = resolve_module_info(mod, package)
|
info = resolve_module_info(mod, package)
|
||||||
if (!info) push(unresolved, {script: script, module: mod})
|
if (!info) unresolved[] = {script: script, module: mod}
|
||||||
} disruption {}
|
} disruption {}
|
||||||
_resolve()
|
_resolve()
|
||||||
})
|
})
|
||||||
@@ -2070,6 +2195,7 @@ Shop.get_lib_dir = function() {
|
|||||||
Shop.ensure_dir = fd.ensure_dir
|
Shop.ensure_dir = fd.ensure_dir
|
||||||
Shop.install_zip = install_zip
|
Shop.install_zip = install_zip
|
||||||
Shop.ensure_package_dylibs = ensure_package_dylibs
|
Shop.ensure_package_dylibs = ensure_package_dylibs
|
||||||
|
Shop.resolve_path = resolve_path
|
||||||
|
|
||||||
Shop.get_local_dir = function() {
|
Shop.get_local_dir = function() {
|
||||||
return global_shop_path + "/local"
|
return global_shop_path + "/local"
|
||||||
@@ -2136,7 +2262,7 @@ Shop.load_as_mach = function(path, pkg) {
|
|||||||
|
|
||||||
// Try cached mcode -> compile to mach
|
// Try cached mcode -> compile to mach
|
||||||
if (!compiled) {
|
if (!compiled) {
|
||||||
cached_mcode_path = hash_path(content_key, 'mcode')
|
cached_mcode_path = cache_path(content_key, 'mcode')
|
||||||
if (fd.is_file(cached_mcode_path)) {
|
if (fd.is_file(cached_mcode_path)) {
|
||||||
mcode_json = text(fd.slurp(cached_mcode_path))
|
mcode_json = text(fd.slurp(cached_mcode_path))
|
||||||
compiled = mach_compile_mcode_bin(file_path, mcode_json)
|
compiled = mach_compile_mcode_bin(file_path, mcode_json)
|
||||||
@@ -2156,7 +2282,7 @@ Shop.load_as_mach = function(path, pkg) {
|
|||||||
ir = _mcode_mod(ast)
|
ir = _mcode_mod(ast)
|
||||||
optimized = _streamline_mod(ir)
|
optimized = _streamline_mod(ir)
|
||||||
mcode_json = shop_json.encode(optimized)
|
mcode_json = shop_json.encode(optimized)
|
||||||
cached_mcode_path = hash_path(content_key, 'mcode')
|
cached_mcode_path = cache_path(content_key, 'mcode')
|
||||||
fd.ensure_dir(global_shop_path + '/build')
|
fd.ensure_dir(global_shop_path + '/build')
|
||||||
fd.slurpwrite(cached_mcode_path, stone(blob(mcode_json)))
|
fd.slurpwrite(cached_mcode_path, stone(blob(mcode_json)))
|
||||||
compiled = mach_compile_mcode_bin(file_path, mcode_json)
|
compiled = mach_compile_mcode_bin(file_path, mcode_json)
|
||||||
@@ -2209,6 +2335,80 @@ Shop.load_as_dylib = function(path, pkg) {
|
|||||||
return os.native_module_load_named(result._handle, result._sym, env)
|
return os.native_module_load_named(result._handle, result._sym, env)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trace all transitive module dependencies for a file.
|
||||||
|
// Returns {scripts: [{path, package}], c_packages: [string]}
|
||||||
|
Shop.trace_deps = function(file_path) {
|
||||||
|
var visited = {}
|
||||||
|
var scripts = []
|
||||||
|
var c_packages = {}
|
||||||
|
|
||||||
|
function trace(fp) {
|
||||||
|
if (visited[fp]) return
|
||||||
|
visited[fp] = true
|
||||||
|
var fi = Shop.file_info(fp)
|
||||||
|
var file_pkg = fi.package
|
||||||
|
var idx = null
|
||||||
|
var j = 0
|
||||||
|
var imp = null
|
||||||
|
var rinfo = null
|
||||||
|
if (ends_with(fp, '.cm'))
|
||||||
|
scripts[] = {path: fp, package: file_pkg}
|
||||||
|
var _trace = function() {
|
||||||
|
idx = Shop.index_file(fp)
|
||||||
|
if (!idx || !idx.imports) return
|
||||||
|
j = 0
|
||||||
|
while (j < length(idx.imports)) {
|
||||||
|
imp = idx.imports[j]
|
||||||
|
rinfo = Shop.resolve_import_info(imp.module_path, file_pkg)
|
||||||
|
if (rinfo) {
|
||||||
|
if (rinfo.type == 'script' && rinfo.resolved_path)
|
||||||
|
trace(rinfo.resolved_path)
|
||||||
|
else if (rinfo.type == 'native' && rinfo.package)
|
||||||
|
c_packages[rinfo.package] = true
|
||||||
|
}
|
||||||
|
j = j + 1
|
||||||
|
}
|
||||||
|
} disruption {}
|
||||||
|
_trace()
|
||||||
|
}
|
||||||
|
|
||||||
|
trace(file_path)
|
||||||
|
return {scripts: scripts, c_packages: array(c_packages)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a C package has a build manifest (was previously built)
|
||||||
|
Shop.has_c_manifest = function(pkg) {
|
||||||
|
return fd.is_file(dylib_manifest_path(pkg))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a .cm file has a cached bytecode artifact (mach or mcode)
|
||||||
|
Shop.is_cached = function(path) {
|
||||||
|
if (!fd.is_file(path)) return false
|
||||||
|
var content_key = stone(blob(text(fd.slurp(path))))
|
||||||
|
if (fd.is_file(cache_path(content_key, 'mach'))) return true
|
||||||
|
if (fd.is_file(cache_path(content_key, 'mcode'))) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a .cm file has a cached native dylib artifact
|
||||||
|
Shop.is_native_cached = function(path, pkg) {
|
||||||
|
var build_mod = use_cache['core/build']
|
||||||
|
if (!build_mod || !fd.is_file(path)) return false
|
||||||
|
var src = text(fd.slurp(path))
|
||||||
|
var host = detect_host_target()
|
||||||
|
if (!host) return false
|
||||||
|
var san_flags = build_mod.native_sanitize_flags ? build_mod.native_sanitize_flags() : ''
|
||||||
|
var native_key = build_mod.native_cache_content ?
|
||||||
|
build_mod.native_cache_content(src, host, san_flags) :
|
||||||
|
(src + '\n' + host)
|
||||||
|
return fd.is_file(build_mod.cache_path(native_key, build_mod.SALT_NATIVE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile + cache a module without executing it
|
||||||
|
Shop.precompile = function(path, pkg) {
|
||||||
|
resolve_mod_fn(path, pkg)
|
||||||
|
}
|
||||||
|
|
||||||
Shop.audit_packages = function() {
|
Shop.audit_packages = function() {
|
||||||
var packages = Shop.list_packages()
|
var packages = Shop.list_packages()
|
||||||
|
|
||||||
@@ -2218,7 +2418,7 @@ Shop.audit_packages = function() {
|
|||||||
if (package == 'core') return
|
if (package == 'core') return
|
||||||
if (fd.is_dir(package)) return
|
if (fd.is_dir(package)) return
|
||||||
if (fetch_remote_hash(package)) return
|
if (fetch_remote_hash(package)) return
|
||||||
push(bad, package)
|
bad[] = package
|
||||||
})
|
})
|
||||||
|
|
||||||
return bad
|
return bad
|
||||||
@@ -2259,7 +2459,7 @@ Shop.use_native = function(path, package_context) {
|
|||||||
if (!starts_with(path, '/') && !fd.is_file(path)) {
|
if (!starts_with(path, '/') && !fd.is_file(path)) {
|
||||||
lookup = ends_with(path, '.cm') ? path : path + '.cm'
|
lookup = ends_with(path, '.cm') ? path : path + '.cm'
|
||||||
locator = resolve_locator(lookup, package_context)
|
locator = resolve_locator(lookup, package_context)
|
||||||
if (!locator) { print('Module not found: ' + path); disrupt }
|
if (!locator) { log.error('use_native: module not found: ' + path + ' (package: ' + text(package_context || '') + ')'); disrupt }
|
||||||
src_path = locator.path
|
src_path = locator.path
|
||||||
} else if (!starts_with(path, '/')) {
|
} else if (!starts_with(path, '/')) {
|
||||||
src_path = fd.realpath(path)
|
src_path = fd.realpath(path)
|
||||||
@@ -2268,17 +2468,12 @@ Shop.use_native = function(path, package_context) {
|
|||||||
|
|
||||||
var file_info = Shop.file_info(src_path)
|
var file_info = Shop.file_info(src_path)
|
||||||
var pkg = file_info.package || (locator ? locator.pkg : package_context)
|
var pkg = file_info.package || (locator ? locator.pkg : package_context)
|
||||||
var sym_stem = fd.basename(src_path)
|
var sym_stem = file_info.name ? file_info.name + (file_info.is_actor ? '.ce' : '.cm') : fd.basename(src_path)
|
||||||
var pkg_dir = null
|
|
||||||
cache_key = 'native:' + text(pkg || '') + ':' + src_path
|
cache_key = 'native:' + text(pkg || '') + ':' + src_path
|
||||||
if (use_cache[cache_key]) return use_cache[cache_key]
|
if (use_cache[cache_key]) return use_cache[cache_key]
|
||||||
|
|
||||||
var sym_name = null
|
var sym_name = null
|
||||||
if (pkg) {
|
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)
|
sym_name = Shop.c_symbol_for_file(pkg, sym_stem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#define WOTA_IMPLEMENTATION
|
#define WOTA_IMPLEMENTATION
|
||||||
#include "quickjs-internal.h"
|
#include "pit_internal.h"
|
||||||
#include "cell.h"
|
#include "cell.h"
|
||||||
|
|
||||||
typedef struct ObjectRef {
|
typedef struct ObjectRef {
|
||||||
@@ -41,13 +41,11 @@ static void wota_stack_free (WotaEncodeContext *enc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static JSValue wota_apply_replacer (WotaEncodeContext *enc, JSValueConst holder, JSValue key, JSValueConst val) {
|
static JSValue wota_apply_replacer (WotaEncodeContext *enc, JSValueConst holder, JSValue key, JSValueConst val) {
|
||||||
if (JS_IsNull (enc->replacer)) return JS_DupValue (enc->ctx, val);
|
if (JS_IsNull (enc->replacer)) return val;
|
||||||
JSValue key_val = JS_IsNull (key) ? JS_NULL : JS_DupValue (enc->ctx, key);
|
JSValue key_val = JS_IsNull (key) ? JS_NULL : key;
|
||||||
JSValue args[2] = { key_val, JS_DupValue (enc->ctx, val) };
|
JSValue args[2] = { key_val, val };
|
||||||
JSValue result = JS_Call (enc->ctx, enc->replacer, holder, 2, args);
|
JSValue result = JS_Call (enc->ctx, enc->replacer, holder, 2, args);
|
||||||
JS_FreeValue (enc->ctx, args[0]);
|
if (JS_IsException (result)) return val;
|
||||||
JS_FreeValue (enc->ctx, args[1]);
|
|
||||||
if (JS_IsException (result)) return JS_DupValue (enc->ctx, val);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,20 +58,17 @@ static void encode_object_properties (WotaEncodeContext *enc, JSValueConst val,
|
|||||||
JSGCRef val_ref, keys_ref;
|
JSGCRef val_ref, keys_ref;
|
||||||
JS_PushGCRef (ctx, &val_ref);
|
JS_PushGCRef (ctx, &val_ref);
|
||||||
JS_PushGCRef (ctx, &keys_ref);
|
JS_PushGCRef (ctx, &keys_ref);
|
||||||
val_ref.val = JS_DupValue (ctx, val);
|
val_ref.val = val;
|
||||||
|
|
||||||
keys_ref.val = JS_GetOwnPropertyNames (ctx, val_ref.val);
|
keys_ref.val = JS_GetOwnPropertyNames (ctx, val_ref.val);
|
||||||
if (JS_IsException (keys_ref.val)) {
|
if (JS_IsException (keys_ref.val)) {
|
||||||
wota_write_sym (&enc->wb, WOTA_NULL);
|
wota_write_sym (&enc->wb, WOTA_NULL);
|
||||||
JS_FreeValue (ctx, val_ref.val);
|
|
||||||
JS_PopGCRef (ctx, &keys_ref);
|
JS_PopGCRef (ctx, &keys_ref);
|
||||||
JS_PopGCRef (ctx, &val_ref);
|
JS_PopGCRef (ctx, &val_ref);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int64_t plen64;
|
int64_t plen64;
|
||||||
if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) {
|
if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) {
|
||||||
JS_FreeValue (ctx, keys_ref.val);
|
|
||||||
JS_FreeValue (ctx, val_ref.val);
|
|
||||||
wota_write_sym (&enc->wb, WOTA_NULL);
|
wota_write_sym (&enc->wb, WOTA_NULL);
|
||||||
JS_PopGCRef (ctx, &keys_ref);
|
JS_PopGCRef (ctx, &keys_ref);
|
||||||
JS_PopGCRef (ctx, &val_ref);
|
JS_PopGCRef (ctx, &val_ref);
|
||||||
@@ -105,12 +100,9 @@ static void encode_object_properties (WotaEncodeContext *enc, JSValueConst val,
|
|||||||
prop_refs[non_function_count].val = prop_val;
|
prop_refs[non_function_count].val = prop_val;
|
||||||
non_function_count++;
|
non_function_count++;
|
||||||
} else {
|
} else {
|
||||||
JS_FreeValue (ctx, prop_val);
|
|
||||||
JS_FreeValue (ctx, key_refs[i].val);
|
|
||||||
key_refs[i].val = JS_NULL;
|
key_refs[i].val = JS_NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JS_FreeValue (ctx, keys_ref.val);
|
|
||||||
wota_write_record (&enc->wb, non_function_count);
|
wota_write_record (&enc->wb, non_function_count);
|
||||||
for (uint32_t i = 0; i < non_function_count; i++) {
|
for (uint32_t i = 0; i < non_function_count; i++) {
|
||||||
size_t klen;
|
size_t klen;
|
||||||
@@ -118,8 +110,6 @@ static void encode_object_properties (WotaEncodeContext *enc, JSValueConst val,
|
|||||||
wota_write_text_len (&enc->wb, prop_name ? prop_name : "", prop_name ? klen : 0);
|
wota_write_text_len (&enc->wb, prop_name ? prop_name : "", prop_name ? klen : 0);
|
||||||
wota_encode_value (enc, prop_refs[i].val, val_ref.val, key_refs[i].val);
|
wota_encode_value (enc, prop_refs[i].val, val_ref.val, key_refs[i].val);
|
||||||
JS_FreeCString (ctx, prop_name);
|
JS_FreeCString (ctx, prop_name);
|
||||||
JS_FreeValue (ctx, prop_refs[i].val);
|
|
||||||
JS_FreeValue (ctx, key_refs[i].val);
|
|
||||||
}
|
}
|
||||||
/* Pop all GC refs in reverse order */
|
/* Pop all GC refs in reverse order */
|
||||||
for (int i = plen - 1; i >= 0; i--) {
|
for (int i = plen - 1; i >= 0; i--) {
|
||||||
@@ -128,7 +118,6 @@ static void encode_object_properties (WotaEncodeContext *enc, JSValueConst val,
|
|||||||
}
|
}
|
||||||
sys_free (prop_refs);
|
sys_free (prop_refs);
|
||||||
sys_free (key_refs);
|
sys_free (key_refs);
|
||||||
JS_FreeValue (ctx, val_ref.val);
|
|
||||||
JS_PopGCRef (ctx, &keys_ref);
|
JS_PopGCRef (ctx, &keys_ref);
|
||||||
JS_PopGCRef (ctx, &val_ref);
|
JS_PopGCRef (ctx, &val_ref);
|
||||||
}
|
}
|
||||||
@@ -139,7 +128,7 @@ static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValue
|
|||||||
if (!JS_IsNull (enc->replacer) && !JS_IsNull (key))
|
if (!JS_IsNull (enc->replacer) && !JS_IsNull (key))
|
||||||
replaced = wota_apply_replacer (enc, holder, key, val);
|
replaced = wota_apply_replacer (enc, holder, key, val);
|
||||||
else
|
else
|
||||||
replaced = JS_DupValue (enc->ctx, val);
|
replaced = val;
|
||||||
|
|
||||||
int tag = JS_VALUE_GET_TAG (replaced);
|
int tag = JS_VALUE_GET_TAG (replaced);
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
@@ -183,7 +172,6 @@ static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValue
|
|||||||
size_t buf_len;
|
size_t buf_len;
|
||||||
void *buf_data = js_get_blob_data (ctx, &buf_len, replaced);
|
void *buf_data = js_get_blob_data (ctx, &buf_len, replaced);
|
||||||
if (buf_data == (void *)-1) {
|
if (buf_data == (void *)-1) {
|
||||||
JS_FreeValue (ctx, replaced);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (buf_len == 0) {
|
if (buf_len == 0) {
|
||||||
@@ -205,7 +193,6 @@ static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValue
|
|||||||
for (int64_t i = 0; i < arr_len; i++) {
|
for (int64_t i = 0; i < arr_len; i++) {
|
||||||
JSValue elem_val = JS_GetPropertyNumber (ctx, replaced, i);
|
JSValue elem_val = JS_GetPropertyNumber (ctx, replaced, i);
|
||||||
wota_encode_value (enc, elem_val, replaced, JS_NewInt32 (ctx, (int32_t)i));
|
wota_encode_value (enc, elem_val, replaced, JS_NewInt32 (ctx, (int32_t)i));
|
||||||
JS_FreeValue (ctx, elem_val);
|
|
||||||
}
|
}
|
||||||
wota_stack_pop (enc);
|
wota_stack_pop (enc);
|
||||||
break;
|
break;
|
||||||
@@ -218,10 +205,8 @@ static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValue
|
|||||||
if (!JS_IsNull (adata)) {
|
if (!JS_IsNull (adata)) {
|
||||||
wota_write_sym (&enc->wb, WOTA_PRIVATE);
|
wota_write_sym (&enc->wb, WOTA_PRIVATE);
|
||||||
wota_encode_value (enc, adata, replaced, JS_NULL);
|
wota_encode_value (enc, adata, replaced, JS_NULL);
|
||||||
JS_FreeValue (ctx, adata);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
JS_FreeValue (ctx, adata);
|
|
||||||
if (wota_stack_has (enc, replaced)) {
|
if (wota_stack_has (enc, replaced)) {
|
||||||
enc->cycle = 1;
|
enc->cycle = 1;
|
||||||
break;
|
break;
|
||||||
@@ -230,16 +215,13 @@ static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValue
|
|||||||
JSValue to_json = JS_GetPropertyStr (ctx, replaced, "toJSON");
|
JSValue to_json = JS_GetPropertyStr (ctx, replaced, "toJSON");
|
||||||
if (JS_IsFunction (to_json)) {
|
if (JS_IsFunction (to_json)) {
|
||||||
JSValue result = JS_Call (ctx, to_json, replaced, 0, NULL);
|
JSValue result = JS_Call (ctx, to_json, replaced, 0, NULL);
|
||||||
JS_FreeValue (ctx, to_json);
|
|
||||||
if (!JS_IsException (result)) {
|
if (!JS_IsException (result)) {
|
||||||
wota_encode_value (enc, result, holder, key);
|
wota_encode_value (enc, result, holder, key);
|
||||||
JS_FreeValue (ctx, result);
|
|
||||||
} else
|
} else
|
||||||
wota_write_sym (&enc->wb, WOTA_NULL);
|
wota_write_sym (&enc->wb, WOTA_NULL);
|
||||||
wota_stack_pop (enc);
|
wota_stack_pop (enc);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
JS_FreeValue (ctx, to_json);
|
|
||||||
encode_object_properties (enc, replaced, holder);
|
encode_object_properties (enc, replaced, holder);
|
||||||
wota_stack_pop (enc);
|
wota_stack_pop (enc);
|
||||||
break;
|
break;
|
||||||
@@ -248,7 +230,6 @@ static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValue
|
|||||||
wota_write_sym (&enc->wb, WOTA_NULL);
|
wota_write_sym (&enc->wb, WOTA_NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
JS_FreeValue (ctx, replaced);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *decode_wota_value (JSContext *ctx, char *data_ptr, JSValue *out_val, JSValue holder, JSValue key, JSValue reviver) {
|
static char *decode_wota_value (JSContext *ctx, char *data_ptr, JSValue *out_val, JSValue holder, JSValue key, JSValue reviver) {
|
||||||
@@ -355,16 +336,12 @@ static char *decode_wota_value (JSContext *ctx, char *data_ptr, JSValue *out_val
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!JS_IsNull (reviver)) {
|
if (!JS_IsNull (reviver)) {
|
||||||
JSValue key_val = JS_IsNull (key) ? JS_NULL : JS_DupValue (ctx, key);
|
JSValue key_val = JS_IsNull (key) ? JS_NULL : key;
|
||||||
JSValue args[2] = { key_val, JS_DupValue (ctx, *out_val) };
|
JSValue args[2] = { key_val, *out_val };
|
||||||
JSValue revived = JS_Call (ctx, reviver, holder, 2, args);
|
JSValue revived = JS_Call (ctx, reviver, holder, 2, args);
|
||||||
JS_FreeValue (ctx, args[0]);
|
|
||||||
JS_FreeValue (ctx, args[1]);
|
|
||||||
if (!JS_IsException (revived)) {
|
if (!JS_IsException (revived)) {
|
||||||
JS_FreeValue (ctx, *out_val);
|
|
||||||
*out_val = revived;
|
*out_val = revived;
|
||||||
} else
|
}
|
||||||
JS_FreeValue (ctx, revived);
|
|
||||||
}
|
}
|
||||||
return data_ptr;
|
return data_ptr;
|
||||||
}
|
}
|
||||||
|
|||||||
6
link.cm
6
link.cm
@@ -208,11 +208,11 @@ Link.sync_all = function(shop) {
|
|||||||
// Validate target exists
|
// Validate target exists
|
||||||
var link_target = resolve_link_target(target)
|
var link_target = resolve_link_target(target)
|
||||||
if (!fd.is_dir(link_target)) {
|
if (!fd.is_dir(link_target)) {
|
||||||
push(errors, canonical + ': target ' + link_target + ' does not exist')
|
errors[] = canonical + ': target ' + link_target + ' does not exist'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!fd.is_file(link_target + '/cell.toml')) {
|
if (!fd.is_file(link_target + '/cell.toml')) {
|
||||||
push(errors, canonical + ': target ' + link_target + ' is not a valid package')
|
errors[] = canonical + ': target ' + link_target + ' is not a valid package'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +246,7 @@ Link.sync_all = function(shop) {
|
|||||||
|
|
||||||
count = count + 1
|
count = count + 1
|
||||||
} disruption {
|
} disruption {
|
||||||
push(errors, canonical + ': sync failed')
|
errors[] = canonical + ': sync failed'
|
||||||
}
|
}
|
||||||
_sync()
|
_sync()
|
||||||
})
|
})
|
||||||
|
|||||||
14
list.ce
14
list.ce
@@ -89,16 +89,16 @@ var run = function() {
|
|||||||
// Add status indicators
|
// Add status indicators
|
||||||
status = []
|
status = []
|
||||||
if (link_target) {
|
if (link_target) {
|
||||||
push(status, "linked -> " + link_target)
|
status[] = "linked -> " + link_target
|
||||||
}
|
}
|
||||||
if (lock_entry && lock_entry.commit) {
|
if (lock_entry && lock_entry.commit) {
|
||||||
push(status, "@" + text(lock_entry.commit, 0, 8))
|
status[] = "@" + text(lock_entry.commit, 0, 8)
|
||||||
}
|
}
|
||||||
if (lock_entry && lock_entry.type == 'local') {
|
if (lock_entry && lock_entry.type == 'local') {
|
||||||
push(status, "local")
|
status[] = "local"
|
||||||
}
|
}
|
||||||
if (!lock_entry) {
|
if (!lock_entry) {
|
||||||
push(status, "not installed")
|
status[] = "not installed"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length(status) > 0) {
|
if (length(status) > 0) {
|
||||||
@@ -136,11 +136,11 @@ if (mode == 'local') {
|
|||||||
var link_target = links[p]
|
var link_target = links[p]
|
||||||
|
|
||||||
if (link_target) {
|
if (link_target) {
|
||||||
push(linked_pkgs, p)
|
linked_pkgs[] = p
|
||||||
} else if (lock_entry && lock_entry.type == 'local') {
|
} else if (lock_entry && lock_entry.type == 'local') {
|
||||||
push(local_pkgs, p)
|
local_pkgs[] = p
|
||||||
} else {
|
} else {
|
||||||
push(remote_pkgs, p)
|
remote_pkgs[] = p
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
496
log.ce
496
log.ce
@@ -1,15 +1,17 @@
|
|||||||
// cell log - Manage and read log sinks
|
// cell log - Manage log sink configuration
|
||||||
//
|
//
|
||||||
// Usage:
|
// Usage:
|
||||||
// cell log list List configured sinks
|
// cell log list Show sinks and channel routing
|
||||||
|
// cell log channels List channels with status
|
||||||
|
// cell log enable <channel> Enable a channel on terminal
|
||||||
|
// cell log disable <channel> Disable a channel on terminal
|
||||||
// cell log add <name> console [opts] Add a console sink
|
// cell log add <name> console [opts] Add a console sink
|
||||||
// cell log add <name> file <path> [opts] Add a file sink
|
// cell log add <name> file <path> [opts] Add a file sink
|
||||||
// cell log remove <name> Remove a sink
|
// cell log remove <name> Remove a sink
|
||||||
// cell log read <sink> [opts] Read from a file sink
|
// cell log route <channel> <sink> Route a channel to a sink
|
||||||
// cell log tail <sink> [--lines=N] Follow a file sink
|
// cell log unroute <channel> <sink> Remove a channel from a sink
|
||||||
//
|
// cell log stack <channel> Enable stack traces on a channel
|
||||||
// The --stack option controls which channels capture a stack trace.
|
// cell log unstack <channel> Disable stack traces on a channel
|
||||||
// Default: --stack=error (errors always show a stack trace).
|
|
||||||
|
|
||||||
var toml = use('toml')
|
var toml = use('toml')
|
||||||
var fd = use('fd')
|
var fd = use('fd')
|
||||||
@@ -18,9 +20,8 @@ var json = use('json')
|
|||||||
var log_path = shop_path + '/log.toml'
|
var log_path = shop_path + '/log.toml'
|
||||||
|
|
||||||
function load_config() {
|
function load_config() {
|
||||||
if (fd.is_file(log_path)) {
|
if (fd.is_file(log_path))
|
||||||
return toml.decode(text(fd.slurp(log_path)))
|
return toml.decode(text(fd.slurp(log_path)))
|
||||||
}
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,23 +46,24 @@ function print_help() {
|
|||||||
log.console("Usage: cell log <command> [options]")
|
log.console("Usage: cell log <command> [options]")
|
||||||
log.console("")
|
log.console("")
|
||||||
log.console("Commands:")
|
log.console("Commands:")
|
||||||
log.console(" list List configured sinks")
|
log.console(" list Show sinks and channel routing")
|
||||||
|
log.console(" channels List channels with status")
|
||||||
|
log.console(" enable <channel> Enable a channel on terminal")
|
||||||
|
log.console(" disable <channel> Disable a channel on terminal")
|
||||||
log.console(" add <name> console [opts] Add a console sink")
|
log.console(" add <name> console [opts] Add a console sink")
|
||||||
log.console(" add <name> file <path> [opts] Add a file sink")
|
log.console(" add <name> file <path> [opts] Add a file sink")
|
||||||
log.console(" remove <name> Remove a sink")
|
log.console(" remove <name> Remove a sink")
|
||||||
log.console(" read <sink> [opts] Read from a file sink")
|
log.console(" route <channel> <sink> Route a channel to a sink")
|
||||||
log.console(" tail <sink> [--lines=N] Follow a file sink")
|
log.console(" unroute <channel> <sink> Remove a channel from a sink")
|
||||||
|
log.console(" stack <channel> Enable stack traces on a channel")
|
||||||
|
log.console(" unstack <channel> Disable stack traces on a channel")
|
||||||
log.console("")
|
log.console("")
|
||||||
log.console("Options for add:")
|
log.console("Options for add:")
|
||||||
log.console(" --format=pretty|bare|json Output format (default: pretty for console, json for file)")
|
log.console(" --format=pretty|bare|json Output format (default: pretty for console, json for file)")
|
||||||
log.console(" --channels=ch1,ch2 Channels to subscribe (default: console,error,system)")
|
log.console(" --channels=ch1,ch2 Channels to subscribe (default: *)")
|
||||||
log.console(" --exclude=ch1,ch2 Channels to exclude (for wildcard sinks)")
|
log.console(" --exclude=ch1,ch2 Channels to exclude")
|
||||||
log.console(" --stack=ch1,ch2 Channels that capture a stack trace (default: error)")
|
log.console(" --mode=append|overwrite File write mode (default: append)")
|
||||||
log.console("")
|
log.console(" --max_size=N Max file size in bytes before truncation")
|
||||||
log.console("Options for read:")
|
|
||||||
log.console(" --lines=N Show last N lines (default: all)")
|
|
||||||
log.console(" --channel=X Filter by channel")
|
|
||||||
log.console(" --since=timestamp Only show entries after timestamp")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse_opt(arg, prefix) {
|
function parse_opt(arg, prefix) {
|
||||||
@@ -71,36 +73,85 @@ function parse_opt(arg, prefix) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function format_entry(entry) {
|
// Collect all stack channels across all sinks
|
||||||
var aid = text(entry.actor_id, 0, 5)
|
function collect_stack_channels(config) {
|
||||||
var src = ""
|
var stack_chs = {}
|
||||||
var ev = null
|
var names = array(config.sink)
|
||||||
if (entry.source && entry.source.file)
|
arrfor(names, function(n) {
|
||||||
src = entry.source.file + ":" + text(entry.source.line)
|
var s = config.sink[n]
|
||||||
ev = is_text(entry.event) ? entry.event : json.encode(entry.event)
|
if (is_array(s.stack)) {
|
||||||
return "[" + aid + "] [" + entry.channel + "] " + src + " " + ev
|
arrfor(s.stack, function(ch) { stack_chs[ch] = true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return stack_chs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find which sinks a stack channel is declared on (for modification)
|
||||||
|
function find_stack_sink(config, channel) {
|
||||||
|
var names = array(config.sink)
|
||||||
|
var found = null
|
||||||
|
arrfor(names, function(n) {
|
||||||
|
if (found) return
|
||||||
|
var s = config.sink[n]
|
||||||
|
if (is_array(s.stack)) {
|
||||||
|
arrfor(s.stack, function(ch) {
|
||||||
|
if (ch == channel) found = n
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
function do_list() {
|
function do_list() {
|
||||||
var config = load_config()
|
var config = load_config()
|
||||||
var names = null
|
var names = null
|
||||||
|
var channel_routing = {}
|
||||||
|
var stack_chs = null
|
||||||
names = (config && config.sink) ? array(config.sink) : []
|
names = (config && config.sink) ? array(config.sink) : []
|
||||||
if (length(names) == 0) {
|
if (length(names) == 0) {
|
||||||
log.console("No log sinks configured.")
|
log.console("No log sinks configured.")
|
||||||
log.console("Default: console pretty for console/error/system (stack traces on error)")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show sinks
|
||||||
|
log.console("Sinks:")
|
||||||
arrfor(names, function(n) {
|
arrfor(names, function(n) {
|
||||||
var s = config.sink[n]
|
var s = config.sink[n]
|
||||||
var ch = is_array(s.channels) ? text(s.channels, ', ') : '(none)'
|
|
||||||
var ex = is_array(s.exclude) ? " exclude=" + text(s.exclude, ',') : ""
|
|
||||||
var stk = is_array(s.stack) ? " stack=" + text(s.stack, ',') : ""
|
|
||||||
var fmt = s.format || (s.type == 'file' ? 'json' : 'pretty')
|
var fmt = s.format || (s.type == 'file' ? 'json' : 'pretty')
|
||||||
|
var mode = s.mode ? " mode=" + s.mode : ""
|
||||||
|
var maxsz = s.max_size ? " max_size=" + text(s.max_size) : ""
|
||||||
|
var ex = is_array(s.exclude) ? " exclude=" + text(s.exclude, ',') : ""
|
||||||
if (s.type == 'file')
|
if (s.type == 'file')
|
||||||
log.console(" " + n + ": " + s.type + " -> " + s.path + " [" + ch + "] format=" + fmt + ex + stk)
|
log.console(" " + n + ": file -> " + s.path + " format=" + fmt + mode + maxsz)
|
||||||
else
|
else
|
||||||
log.console(" " + n + ": " + s.type + " [" + ch + "] format=" + fmt + ex + stk)
|
log.console(" " + n + ": console format=" + fmt + ex)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Build channel -> sinks map
|
||||||
|
arrfor(names, function(n) {
|
||||||
|
var s = config.sink[n]
|
||||||
|
var chs = is_array(s.channels) ? s.channels : []
|
||||||
|
arrfor(chs, function(ch) {
|
||||||
|
if (!channel_routing[ch]) channel_routing[ch] = []
|
||||||
|
channel_routing[ch][] = n
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Show routing
|
||||||
|
log.console("")
|
||||||
|
log.console("Routing:")
|
||||||
|
var channels = array(channel_routing)
|
||||||
|
arrfor(channels, function(ch) {
|
||||||
|
log.console(" " + ch + " -> " + text(channel_routing[ch], ', '))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Show stack traces
|
||||||
|
stack_chs = collect_stack_channels(config)
|
||||||
|
var stack_list = array(stack_chs)
|
||||||
|
if (length(stack_list) > 0) {
|
||||||
|
log.console("")
|
||||||
|
log.console("Stack traces on: " + text(stack_list, ', '))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function do_add() {
|
function do_add() {
|
||||||
@@ -108,14 +159,15 @@ function do_add() {
|
|||||||
var sink_type = null
|
var sink_type = null
|
||||||
var path = null
|
var path = null
|
||||||
var format = null
|
var format = null
|
||||||
var channels = ["console", "error", "system"]
|
var channels = ["*"]
|
||||||
var exclude = null
|
var exclude = null
|
||||||
var stack_chs = ["error"]
|
var mode = null
|
||||||
|
var max_size = null
|
||||||
var config = null
|
var config = null
|
||||||
var val = null
|
var val = null
|
||||||
var i = 0
|
var i = 0
|
||||||
if (length(args) < 3) {
|
if (length(args) < 3) {
|
||||||
log.error("Usage: cell log add <name> console|file [path] [options]")
|
log.console("Usage: cell log add <name> console|file [path] [options]")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
name = args[1]
|
name = args[1]
|
||||||
@@ -123,7 +175,7 @@ function do_add() {
|
|||||||
|
|
||||||
if (sink_type == 'file') {
|
if (sink_type == 'file') {
|
||||||
if (length(args) < 4) {
|
if (length(args) < 4) {
|
||||||
log.error("Usage: cell log add <name> file <path> [options]")
|
log.console("Usage: cell log add <name> file <path> [options]")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
path = args[3]
|
path = args[3]
|
||||||
@@ -133,7 +185,7 @@ function do_add() {
|
|||||||
format = "pretty"
|
format = "pretty"
|
||||||
i = 3
|
i = 3
|
||||||
} else {
|
} else {
|
||||||
log.error("Unknown sink type: " + sink_type + " (use 'console' or 'file')")
|
log.console("Unknown sink type: " + sink_type + " (use 'console' or 'file')")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,17 +196,21 @@ function do_add() {
|
|||||||
if (val) { channels = array(val, ','); continue }
|
if (val) { channels = array(val, ','); continue }
|
||||||
val = parse_opt(args[i], 'exclude')
|
val = parse_opt(args[i], 'exclude')
|
||||||
if (val) { exclude = array(val, ','); continue }
|
if (val) { exclude = array(val, ','); continue }
|
||||||
val = parse_opt(args[i], 'stack')
|
val = parse_opt(args[i], 'mode')
|
||||||
if (val) { stack_chs = array(val, ','); continue }
|
if (val) { mode = val; continue }
|
||||||
|
val = parse_opt(args[i], 'max_size')
|
||||||
|
if (val) { max_size = number(val); continue }
|
||||||
}
|
}
|
||||||
|
|
||||||
config = load_config()
|
config = load_config()
|
||||||
if (!config) config = {}
|
if (!config) config = {}
|
||||||
if (!config.sink) config.sink = {}
|
if (!config.sink) config.sink = {}
|
||||||
|
|
||||||
config.sink[name] = {type: sink_type, format: format, channels: channels, stack: stack_chs}
|
config.sink[name] = {type: sink_type, format: format, channels: channels}
|
||||||
if (path) config.sink[name].path = path
|
if (path) config.sink[name].path = path
|
||||||
if (exclude) config.sink[name].exclude = exclude
|
if (exclude) config.sink[name].exclude = exclude
|
||||||
|
if (mode) config.sink[name].mode = mode
|
||||||
|
if (max_size) config.sink[name].max_size = max_size
|
||||||
|
|
||||||
save_config(config)
|
save_config(config)
|
||||||
log.console("Added sink: " + name)
|
log.console("Added sink: " + name)
|
||||||
@@ -164,13 +220,13 @@ function do_remove() {
|
|||||||
var name = null
|
var name = null
|
||||||
var config = null
|
var config = null
|
||||||
if (length(args) < 2) {
|
if (length(args) < 2) {
|
||||||
log.error("Usage: cell log remove <name>")
|
log.console("Usage: cell log remove <name>")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
name = args[1]
|
name = args[1]
|
||||||
config = load_config()
|
config = load_config()
|
||||||
if (!config || !config.sink || !config.sink[name]) {
|
if (!config || !config.sink || !config.sink[name]) {
|
||||||
log.error("Sink not found: " + name)
|
log.console("Sink not found: " + name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete config.sink[name]
|
delete config.sink[name]
|
||||||
@@ -178,154 +234,244 @@ function do_remove() {
|
|||||||
log.console("Removed sink: " + name)
|
log.console("Removed sink: " + name)
|
||||||
}
|
}
|
||||||
|
|
||||||
function do_read() {
|
function do_route() {
|
||||||
var name = null
|
var channel = null
|
||||||
var max_lines = 0
|
var sink_name = null
|
||||||
var filter_channel = null
|
|
||||||
var since = 0
|
|
||||||
var config = null
|
var config = null
|
||||||
var sink = null
|
var sink = null
|
||||||
var content = null
|
var already = false
|
||||||
var lines = null
|
if (length(args) < 3) {
|
||||||
var entries = []
|
log.console("Usage: cell log route <channel> <sink>")
|
||||||
var entry = null
|
|
||||||
var val = null
|
|
||||||
var i = 0
|
|
||||||
|
|
||||||
if (length(args) < 2) {
|
|
||||||
log.error("Usage: cell log read <sink_name> [options]")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
name = args[1]
|
channel = args[1]
|
||||||
|
sink_name = args[2]
|
||||||
for (i = 2; i < length(args); i++) {
|
|
||||||
val = parse_opt(args[i], 'lines')
|
|
||||||
if (val) { max_lines = number(val); continue }
|
|
||||||
val = parse_opt(args[i], 'channel')
|
|
||||||
if (val) { filter_channel = val; continue }
|
|
||||||
val = parse_opt(args[i], 'since')
|
|
||||||
if (val) { since = number(val); continue }
|
|
||||||
}
|
|
||||||
|
|
||||||
config = load_config()
|
config = load_config()
|
||||||
if (!config || !config.sink || !config.sink[name]) {
|
if (!config || !config.sink || !config.sink[sink_name]) {
|
||||||
log.error("Sink not found: " + name)
|
log.console("Sink not found: " + sink_name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sink = config.sink[name]
|
sink = config.sink[sink_name]
|
||||||
if (sink.type != 'file') {
|
if (!is_array(sink.channels)) sink.channels = []
|
||||||
log.error("Can only read from file sinks")
|
arrfor(sink.channels, function(ch) {
|
||||||
return
|
if (ch == channel) already = true
|
||||||
}
|
|
||||||
if (!fd.is_file(sink.path)) {
|
|
||||||
log.console("Log file does not exist yet: " + sink.path)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
content = text(fd.slurp(sink.path))
|
|
||||||
lines = array(content, '\n')
|
|
||||||
|
|
||||||
arrfor(lines, function(line) {
|
|
||||||
var parse_fn = null
|
|
||||||
if (length(line) == 0) return
|
|
||||||
parse_fn = function() {
|
|
||||||
entry = json.decode(line)
|
|
||||||
} disruption {
|
|
||||||
entry = null
|
|
||||||
}
|
|
||||||
parse_fn()
|
|
||||||
if (!entry) return
|
|
||||||
if (filter_channel && entry.channel != filter_channel) return
|
|
||||||
if (since > 0 && entry.timestamp < since) return
|
|
||||||
entries[] = entry
|
|
||||||
})
|
|
||||||
|
|
||||||
if (max_lines > 0 && length(entries) > max_lines)
|
|
||||||
entries = array(entries, length(entries) - max_lines, length(entries))
|
|
||||||
|
|
||||||
arrfor(entries, function(e) {
|
|
||||||
log.console(format_entry(e))
|
|
||||||
})
|
})
|
||||||
|
if (already) {
|
||||||
|
log.console(channel + " already routed to " + sink_name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sink.channels[] = channel
|
||||||
|
save_config(config)
|
||||||
|
log.console(channel + " -> " + sink_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
function do_tail() {
|
function do_unroute() {
|
||||||
var name = null
|
var channel = null
|
||||||
var tail_lines = 10
|
var sink_name = null
|
||||||
var config = null
|
var config = null
|
||||||
var sink = null
|
var sink = null
|
||||||
var last_size = 0
|
var found = false
|
||||||
var val = null
|
if (length(args) < 3) {
|
||||||
var i = 0
|
log.console("Usage: cell log unroute <channel> <sink>")
|
||||||
|
|
||||||
if (length(args) < 2) {
|
|
||||||
log.error("Usage: cell log tail <sink_name> [--lines=N]")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
name = args[1]
|
channel = args[1]
|
||||||
|
sink_name = args[2]
|
||||||
for (i = 2; i < length(args); i++) {
|
|
||||||
val = parse_opt(args[i], 'lines')
|
|
||||||
if (val) { tail_lines = number(val); continue }
|
|
||||||
}
|
|
||||||
|
|
||||||
config = load_config()
|
config = load_config()
|
||||||
if (!config || !config.sink || !config.sink[name]) {
|
if (!config || !config.sink || !config.sink[sink_name]) {
|
||||||
log.error("Sink not found: " + name)
|
log.console("Sink not found: " + sink_name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sink = config.sink[name]
|
sink = config.sink[sink_name]
|
||||||
if (sink.type != 'file') {
|
if (!is_array(sink.channels)) sink.channels = []
|
||||||
log.error("Can only tail file sinks")
|
sink.channels = filter(sink.channels, function(ch) { return ch != channel })
|
||||||
|
save_config(config)
|
||||||
|
log.console(channel + " removed from " + sink_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_stack() {
|
||||||
|
var channel = null
|
||||||
|
var config = null
|
||||||
|
var names = null
|
||||||
|
var added = false
|
||||||
|
if (length(args) < 2) {
|
||||||
|
log.console("Usage: cell log stack <channel>")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!fd.is_file(sink.path))
|
channel = args[1]
|
||||||
log.console("Waiting for log file: " + sink.path)
|
config = load_config()
|
||||||
|
if (!config || !config.sink) {
|
||||||
function poll() {
|
log.console("No sinks configured")
|
||||||
var st = null
|
return
|
||||||
var poll_content = null
|
}
|
||||||
var poll_lines = null
|
// Add to first sink that already has a stack array, or first sink overall
|
||||||
var start = 0
|
names = array(config.sink)
|
||||||
var poll_entry = null
|
arrfor(names, function(n) {
|
||||||
var old_line_count = 0
|
var s = config.sink[n]
|
||||||
var idx = 0
|
var already = false
|
||||||
var parse_fn = null
|
if (added) return
|
||||||
if (!fd.is_file(sink.path)) {
|
if (is_array(s.stack)) {
|
||||||
$delay(poll, 1)
|
arrfor(s.stack, function(ch) { if (ch == channel) already = true })
|
||||||
return
|
if (!already) s.stack[] = channel
|
||||||
|
added = true
|
||||||
}
|
}
|
||||||
st = fd.stat(sink.path)
|
})
|
||||||
if (st.size == last_size) {
|
if (!added && length(names) > 0) {
|
||||||
$delay(poll, 1)
|
config.sink[names[0]].stack = [channel]
|
||||||
return
|
added = true
|
||||||
|
}
|
||||||
|
if (added) {
|
||||||
|
save_config(config)
|
||||||
|
log.console("Stack traces enabled on: " + channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_unstack() {
|
||||||
|
var channel = null
|
||||||
|
var config = null
|
||||||
|
var names = null
|
||||||
|
if (length(args) < 2) {
|
||||||
|
log.console("Usage: cell log unstack <channel>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
channel = args[1]
|
||||||
|
config = load_config()
|
||||||
|
if (!config || !config.sink) {
|
||||||
|
log.console("No sinks configured")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
names = array(config.sink)
|
||||||
|
arrfor(names, function(n) {
|
||||||
|
var s = config.sink[n]
|
||||||
|
if (is_array(s.stack))
|
||||||
|
s.stack = filter(s.stack, function(ch) { return ch != channel })
|
||||||
|
})
|
||||||
|
save_config(config)
|
||||||
|
log.console("Stack traces disabled on: " + channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
var known_channels = ["console", "error", "warn", "system", "build", "shop", "compile", "test"]
|
||||||
|
|
||||||
|
function find_terminal_sink(config) {
|
||||||
|
var names = null
|
||||||
|
var found = null
|
||||||
|
if (!config || !config.sink) return null
|
||||||
|
names = array(config.sink)
|
||||||
|
if (config.sink.terminal) return config.sink.terminal
|
||||||
|
arrfor(names, function(n) {
|
||||||
|
if (!found && config.sink[n].type == "console")
|
||||||
|
found = config.sink[n]
|
||||||
|
})
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_enable() {
|
||||||
|
var channel = null
|
||||||
|
var config = null
|
||||||
|
var sink = null
|
||||||
|
var i = 0
|
||||||
|
var already = false
|
||||||
|
var new_exclude = []
|
||||||
|
if (length(args) < 2) {
|
||||||
|
log.error("Usage: cell log enable <channel>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
channel = args[1]
|
||||||
|
config = load_config()
|
||||||
|
if (!config) config = {sink: {}}
|
||||||
|
if (!config.sink) config.sink = {}
|
||||||
|
sink = find_terminal_sink(config)
|
||||||
|
if (!sink) {
|
||||||
|
config.sink.terminal = {type: "console", format: "clean", channels: ["console", "error", channel], stack: ["error"]}
|
||||||
|
save_config(config)
|
||||||
|
log.console("Enabled channel: " + channel)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (is_array(sink.channels) && length(sink.channels) == 1 && sink.channels[0] == "*") {
|
||||||
|
if (is_array(sink.exclude)) {
|
||||||
|
new_exclude = []
|
||||||
|
arrfor(sink.exclude, function(ex) {
|
||||||
|
if (ex != channel) new_exclude[] = ex
|
||||||
|
})
|
||||||
|
sink.exclude = new_exclude
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (!is_array(sink.channels)) sink.channels = ["console", "error"]
|
||||||
|
arrfor(sink.channels, function(ch) {
|
||||||
|
if (ch == channel) already = true
|
||||||
|
})
|
||||||
|
if (!already) sink.channels[] = channel
|
||||||
|
}
|
||||||
|
save_config(config)
|
||||||
|
log.console("Enabled channel: " + channel)
|
||||||
|
}
|
||||||
|
|
||||||
poll_content = text(fd.slurp(sink.path))
|
function do_disable() {
|
||||||
poll_lines = array(poll_content, '\n')
|
var channel = null
|
||||||
|
var config = null
|
||||||
if (last_size == 0 && length(poll_lines) > tail_lines) {
|
var sink = null
|
||||||
start = length(poll_lines) - tail_lines
|
var i = 0
|
||||||
} else if (last_size > 0) {
|
var new_channels = []
|
||||||
old_line_count = length(array(text(poll_content, 0, last_size), '\n'))
|
var already_excluded = false
|
||||||
start = old_line_count
|
if (length(args) < 2) {
|
||||||
|
log.error("Usage: cell log disable <channel>")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
channel = args[1]
|
||||||
|
config = load_config()
|
||||||
|
if (!config || !config.sink) {
|
||||||
|
log.error("No log configuration found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sink = find_terminal_sink(config)
|
||||||
|
if (!sink) {
|
||||||
|
log.error("No terminal sink found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (is_array(sink.channels) && length(sink.channels) == 1 && sink.channels[0] == "*") {
|
||||||
|
if (!is_array(sink.exclude)) sink.exclude = []
|
||||||
|
already_excluded = false
|
||||||
|
arrfor(sink.exclude, function(ex) {
|
||||||
|
if (ex == channel) already_excluded = true
|
||||||
|
})
|
||||||
|
if (!already_excluded) sink.exclude[] = channel
|
||||||
|
} else {
|
||||||
|
if (is_array(sink.channels)) {
|
||||||
|
arrfor(sink.channels, function(ch) {
|
||||||
|
if (ch != channel) new_channels[] = ch
|
||||||
|
})
|
||||||
|
sink.channels = new_channels
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
save_config(config)
|
||||||
|
log.console("Disabled channel: " + channel)
|
||||||
|
}
|
||||||
|
|
||||||
last_size = st.size
|
function do_channels() {
|
||||||
for (idx = start; idx < length(poll_lines); idx++) {
|
var config = load_config()
|
||||||
if (length(poll_lines[idx]) == 0) continue
|
var sink = null
|
||||||
parse_fn = function() {
|
var is_wildcard = false
|
||||||
poll_entry = json.decode(poll_lines[idx])
|
var active = {}
|
||||||
} disruption {
|
if (config) sink = find_terminal_sink(config)
|
||||||
poll_entry = null
|
if (sink) {
|
||||||
|
if (is_array(sink.channels) && length(sink.channels) == 1 && sink.channels[0] == "*") {
|
||||||
|
is_wildcard = true
|
||||||
|
arrfor(known_channels, function(ch) { active[ch] = true })
|
||||||
|
if (is_array(sink.exclude)) {
|
||||||
|
arrfor(sink.exclude, function(ex) { active[ex] = false })
|
||||||
}
|
}
|
||||||
parse_fn()
|
} else if (is_array(sink.channels)) {
|
||||||
if (!poll_entry) continue
|
arrfor(sink.channels, function(ch) { active[ch] = true })
|
||||||
os.print(format_entry(poll_entry) + "\n")
|
|
||||||
}
|
}
|
||||||
$delay(poll, 1)
|
} else {
|
||||||
|
active.console = true
|
||||||
|
active.error = true
|
||||||
}
|
}
|
||||||
|
log.console("Channels:")
|
||||||
poll()
|
arrfor(known_channels, function(ch) {
|
||||||
|
var status = active[ch] ? "enabled" : "disabled"
|
||||||
|
log.console(" " + ch + ": " + status)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main dispatch
|
// Main dispatch
|
||||||
@@ -335,16 +481,26 @@ if (length(args) == 0) {
|
|||||||
print_help()
|
print_help()
|
||||||
} else if (args[0] == 'list') {
|
} else if (args[0] == 'list') {
|
||||||
do_list()
|
do_list()
|
||||||
|
} else if (args[0] == 'channels') {
|
||||||
|
do_channels()
|
||||||
|
} else if (args[0] == 'enable') {
|
||||||
|
do_enable()
|
||||||
|
} else if (args[0] == 'disable') {
|
||||||
|
do_disable()
|
||||||
} else if (args[0] == 'add') {
|
} else if (args[0] == 'add') {
|
||||||
do_add()
|
do_add()
|
||||||
} else if (args[0] == 'remove') {
|
} else if (args[0] == 'remove') {
|
||||||
do_remove()
|
do_remove()
|
||||||
} else if (args[0] == 'read') {
|
} else if (args[0] == 'route') {
|
||||||
do_read()
|
do_route()
|
||||||
} else if (args[0] == 'tail') {
|
} else if (args[0] == 'unroute') {
|
||||||
do_tail()
|
do_unroute()
|
||||||
|
} else if (args[0] == 'stack') {
|
||||||
|
do_stack()
|
||||||
|
} else if (args[0] == 'unstack') {
|
||||||
|
do_unstack()
|
||||||
} else {
|
} else {
|
||||||
log.error("Unknown command: " + args[0])
|
log.console("Unknown command: " + args[0])
|
||||||
print_help()
|
print_help()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
mcode.ce
2
mcode.ce
@@ -85,7 +85,7 @@ var dump_function = function(func, name) {
|
|||||||
parts = []
|
parts = []
|
||||||
j = 1
|
j = 1
|
||||||
while (j < n - 2) {
|
while (j < n - 2) {
|
||||||
push(parts, fmt_val(instr[j]))
|
parts[] = fmt_val(instr[j])
|
||||||
j = j + 1
|
j = j + 1
|
||||||
}
|
}
|
||||||
operands = text(parts, ", ")
|
operands = text(parts, ", ")
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ if host_machine.system() == 'darwin'
|
|||||||
fworks = [
|
fworks = [
|
||||||
'CoreFoundation',
|
'CoreFoundation',
|
||||||
'CFNetwork',
|
'CFNetwork',
|
||||||
|
'Security',
|
||||||
]
|
]
|
||||||
foreach fkit : fworks
|
foreach fkit : fworks
|
||||||
deps += dependency('appleframeworks', modules: fkit)
|
deps += dependency('appleframeworks', modules: fkit)
|
||||||
@@ -82,7 +83,9 @@ scripts = [
|
|||||||
'internal/os.c',
|
'internal/os.c',
|
||||||
'internal/fd.c',
|
'internal/fd.c',
|
||||||
'net/http.c',
|
'net/http.c',
|
||||||
'net/enet.c',
|
'net/tls.c',
|
||||||
|
'net/socket.c',
|
||||||
|
'internal/enet.c',
|
||||||
'archive/miniz.c',
|
'archive/miniz.c',
|
||||||
'source/cJSON.c'
|
'source/cJSON.c'
|
||||||
]
|
]
|
||||||
@@ -193,5 +196,3 @@ cell_exe = executable('cell',
|
|||||||
|
|
||||||
# Install headers for building dynamic libraries using Cell
|
# Install headers for building dynamic libraries using Cell
|
||||||
install_headers('source/cell.h')
|
install_headers('source/cell.h')
|
||||||
install_headers('source/quickjs.h')
|
|
||||||
install_headers('source/wota.h')
|
|
||||||
|
|||||||
588
net/enet.c
588
net/enet.c
@@ -1,588 +0,0 @@
|
|||||||
#include "cell.h"
|
|
||||||
#define ENET_IMPLEMENTATION
|
|
||||||
#include "enet.h"
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
static JSClassID enet_host_id;
|
|
||||||
static JSClassID enet_peer_class_id;
|
|
||||||
|
|
||||||
static void js_enet_host_finalizer(JSRuntime *rt, JSValue val)
|
|
||||||
{
|
|
||||||
ENetHost *host = JS_GetOpaque(val, enet_host_id);
|
|
||||||
if (host) enet_host_destroy(host);
|
|
||||||
}
|
|
||||||
|
|
||||||
//static void js_enet_peer_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
|
|
||||||
//{
|
|
||||||
// ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id);
|
|
||||||
// JS_MarkValue(rt, *(JSValue*)peer->data, mark_func);
|
|
||||||
//}
|
|
||||||
|
|
||||||
static void js_enet_peer_finalizer(JSRuntime *rt, JSValue val)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id);
|
|
||||||
JS_FreeValueRT(rt, *(JSValue*)peer->data);
|
|
||||||
free(peer->data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the ENet library. Must be called before using any ENet functionality.
|
|
||||||
static JSValue js_enet_initialize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
if (enet_initialize() != 0) return JS_RaiseDisrupt(ctx, "Error initializing ENet");
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deinitialize the ENet library, cleaning up all resources. Call this when you no longer
|
|
||||||
// need any ENet functionality.
|
|
||||||
static JSValue js_enet_deinitialize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
enet_deinitialize();
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an ENet host for either a client-like unbound host or a server bound to a specific
|
|
||||||
// address and port:
|
|
||||||
//
|
|
||||||
// - If no argument is provided, creates an unbound "client-like" host with default settings
|
|
||||||
// (maximum 32 peers, 2 channels, unlimited bandwidth).
|
|
||||||
// - If you pass an "ip:port" string (e.g. "127.0.0.1:7777"), it creates a server bound to
|
|
||||||
// that address. The server supports up to 32 peers, 2 channels, and unlimited bandwidth.
|
|
||||||
//
|
|
||||||
// Throws an error if host creation fails for any reason.
|
|
||||||
static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetHost *host;
|
|
||||||
ENetAddress address;
|
|
||||||
ENetAddress *send = &address;
|
|
||||||
size_t peer_count = 1000;
|
|
||||||
size_t channel_limit = 0;
|
|
||||||
enet_uint32 incoming_bandwidth = 0;
|
|
||||||
enet_uint32 outgoing_bandwidth = 0;
|
|
||||||
JSValue obj;
|
|
||||||
|
|
||||||
if (argc < 1 || !JS_IsRecord(argv[0])) {
|
|
||||||
host = enet_host_create(NULL, peer_count, channel_limit, incoming_bandwidth, outgoing_bandwidth);
|
|
||||||
if (!host) return JS_RaiseDisrupt(ctx, "Failed to create ENet client host");
|
|
||||||
goto wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue config_obj = argv[0];
|
|
||||||
JSValue addr_val = JS_GetPropertyStr(ctx, config_obj, "address");
|
|
||||||
const char *addr_str = JS_IsText(addr_val) ? JS_ToCString(ctx, addr_val) : NULL;
|
|
||||||
JS_FreeValue(ctx, addr_val);
|
|
||||||
|
|
||||||
if (!addr_str)
|
|
||||||
send = NULL;
|
|
||||||
else {
|
|
||||||
JSValue port_val = JS_GetPropertyStr(ctx, config_obj, "port");
|
|
||||||
int32_t port32 = 0;
|
|
||||||
JS_ToInt32(ctx, &port32, port_val);
|
|
||||||
JS_FreeValue(ctx, port_val);
|
|
||||||
|
|
||||||
if (strcmp(addr_str, "any") == 0)
|
|
||||||
address.host = ENET_HOST_ANY;
|
|
||||||
else if (strcmp(addr_str, "broadcast") == 0)
|
|
||||||
enet_address_set_host_ip(&address, "255.255.255.255");
|
|
||||||
else {
|
|
||||||
int err = enet_address_set_host_ip(&address, addr_str);
|
|
||||||
if (err != 0) {
|
|
||||||
JS_FreeCString(ctx, addr_str);
|
|
||||||
return JS_RaiseDisrupt(ctx, "Failed to set host IP from '%s'. Error: %d", addr_str, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
address.port = (enet_uint16)port32;
|
|
||||||
JS_FreeCString(ctx, addr_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue chan_val = JS_GetPropertyStr(ctx, config_obj, "channels");
|
|
||||||
JS_ToUint32(ctx, &channel_limit, chan_val);
|
|
||||||
JS_FreeValue(ctx, chan_val);
|
|
||||||
|
|
||||||
JSValue in_bw_val = JS_GetPropertyStr(ctx, config_obj, "incoming_bandwidth");
|
|
||||||
JS_ToUint32(ctx, &incoming_bandwidth, in_bw_val);
|
|
||||||
JS_FreeValue(ctx, in_bw_val);
|
|
||||||
|
|
||||||
JSValue out_bw_val = JS_GetPropertyStr(ctx, config_obj, "outgoing_bandwidth");
|
|
||||||
JS_ToUint32(ctx, &outgoing_bandwidth, out_bw_val);
|
|
||||||
JS_FreeValue(ctx, out_bw_val);
|
|
||||||
|
|
||||||
host = enet_host_create(send, peer_count, channel_limit, incoming_bandwidth, outgoing_bandwidth);
|
|
||||||
if (!host) return JS_RaiseDisrupt(ctx, "Failed to create ENet host");
|
|
||||||
|
|
||||||
wrap:
|
|
||||||
obj = JS_NewObjectClass(ctx, enet_host_id);
|
|
||||||
if (JS_IsException(obj)) {
|
|
||||||
enet_host_destroy(host);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
JS_SetOpaque(obj, host);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to get a JSValue for an ENetPeer.
|
|
||||||
static JSValue peer_get_value(JSContext *ctx, ENetPeer *peer)
|
|
||||||
{
|
|
||||||
if (!peer->data) {
|
|
||||||
peer->data = malloc(sizeof(JSValue));
|
|
||||||
*(JSValue*)peer->data = JS_NewObjectClass(ctx, enet_peer_class_id);
|
|
||||||
JS_SetOpaque(*(JSValue*)peer->data, peer);
|
|
||||||
}
|
|
||||||
return JS_DupValue(ctx, *(JSValue*)peer->data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Poll for and process any available network events (connect, receive, disconnect, or none)
|
|
||||||
// from this host, calling the provided callback for each event. This function loops until
|
|
||||||
// no more events are available in the current timeframe.
|
|
||||||
//
|
|
||||||
// :param callback: A function called once for each available event, receiving an event
|
|
||||||
// object as its single argument.
|
|
||||||
// :param timeout: (optional) Timeout in milliseconds. Defaults to 0 (non-blocking).
|
|
||||||
// :return: None
|
|
||||||
static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
|
|
||||||
if (!host) return JS_EXCEPTION;
|
|
||||||
|
|
||||||
if (argc < 1 || !JS_IsFunction(argv[0])) return JS_RaiseDisrupt(ctx, "Expected a callback function as first argument");
|
|
||||||
|
|
||||||
double secs;
|
|
||||||
JS_ToFloat64(ctx, &secs, argv[1]);
|
|
||||||
|
|
||||||
ENetEvent event;
|
|
||||||
while (enet_host_service(host, &event, secs*1000.0f) > 0) {
|
|
||||||
JSValue event_obj = JS_NewObject(ctx);
|
|
||||||
JS_SetPropertyStr(ctx, event_obj, "peer", peer_get_value(ctx, event.peer));
|
|
||||||
|
|
||||||
switch (event.type) {
|
|
||||||
case ENET_EVENT_TYPE_CONNECT:
|
|
||||||
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "connect"));
|
|
||||||
break;
|
|
||||||
case ENET_EVENT_TYPE_RECEIVE:
|
|
||||||
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "receive"));
|
|
||||||
JS_SetPropertyStr(ctx, event_obj, "channelID", JS_NewInt32(ctx, event.channelID));
|
|
||||||
|
|
||||||
// Pass raw data as string or ArrayBuffer
|
|
||||||
if (event.packet->dataLength > 0) {
|
|
||||||
JSValue data_val = js_new_blob_stoned_copy(ctx, event.packet->data, event.packet->dataLength);
|
|
||||||
JS_SetPropertyStr(ctx, event_obj, "data", data_val);
|
|
||||||
}
|
|
||||||
enet_packet_destroy(event.packet);
|
|
||||||
break;
|
|
||||||
case ENET_EVENT_TYPE_DISCONNECT:
|
|
||||||
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "disconnect"));
|
|
||||||
break;
|
|
||||||
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
|
|
||||||
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "disconnect_timeout"));
|
|
||||||
break;
|
|
||||||
case ENET_EVENT_TYPE_NONE:
|
|
||||||
JS_SetPropertyStr(ctx, event_obj, "type", JS_NewString(ctx, "none"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: raise exception?
|
|
||||||
JS_FreeValue(ctx, event_obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initiate a connection from this host to a remote server. Throws an error if the
|
|
||||||
// connection cannot be started.
|
|
||||||
//
|
|
||||||
// :param hostname: The hostname or IP address of the remote server (e.g. "example.com" or "127.0.0.1").
|
|
||||||
// :param port: The port number to connect to.
|
|
||||||
// :return: An ENetPeer object representing the connection.
|
|
||||||
static JSValue js_enet_host_connect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
|
|
||||||
if (!host) return JS_EXCEPTION;
|
|
||||||
|
|
||||||
if (argc < 2) return JS_RaiseDisrupt(ctx, "Expected 2 arguments: hostname, port");
|
|
||||||
|
|
||||||
const char *hostname = JS_ToCString(ctx, argv[0]);
|
|
||||||
if (!hostname) return JS_EXCEPTION;
|
|
||||||
int port;
|
|
||||||
JS_ToInt32(ctx, &port, argv[1]);
|
|
||||||
|
|
||||||
ENetAddress address;
|
|
||||||
enet_address_set_host(&address, hostname);
|
|
||||||
JS_FreeCString(ctx, hostname);
|
|
||||||
address.port = port;
|
|
||||||
|
|
||||||
ENetPeer *peer = enet_host_connect(host, &address, 2, 0);
|
|
||||||
if (!peer) return JS_RaiseDisrupt(ctx, "No available peers for initiating an ENet connection");
|
|
||||||
|
|
||||||
return peer_get_value(ctx, peer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush all pending outgoing packets for this host immediately.
|
|
||||||
//
|
|
||||||
// :return: None
|
|
||||||
static JSValue js_enet_host_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
|
|
||||||
if (!host) return JS_EXCEPTION;
|
|
||||||
enet_host_flush(host);
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Broadcast a string or ArrayBuffer to all connected peers on channel 0.
|
|
||||||
//
|
|
||||||
// :param data: A string or ArrayBuffer to broadcast to all peers.
|
|
||||||
// :return: None
|
|
||||||
static JSValue js_enet_host_broadcast(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
|
|
||||||
if (!host) return JS_EXCEPTION;
|
|
||||||
|
|
||||||
if (argc < 1) return JS_RaiseDisrupt(ctx, "Expected a string or ArrayBuffer to broadcast");
|
|
||||||
|
|
||||||
const char *data_str = NULL;
|
|
||||||
size_t data_len = 0;
|
|
||||||
uint8_t *buf = NULL;
|
|
||||||
|
|
||||||
if (JS_IsText(argv[0])) {
|
|
||||||
data_str = JS_ToCStringLen(ctx, &data_len, argv[0]);
|
|
||||||
if (!data_str) return JS_EXCEPTION;
|
|
||||||
} else if (js_is_blob(ctx,argv[0])) {
|
|
||||||
buf = js_get_blob_data(ctx, &data_len, argv[0]);
|
|
||||||
if (!buf) return JS_EXCEPTION;
|
|
||||||
} else {
|
|
||||||
return JS_RaiseDisrupt(ctx, "broadcast() only accepts a string or ArrayBuffer");
|
|
||||||
}
|
|
||||||
|
|
||||||
ENetPacket *packet = enet_packet_create(data_str ? (const void*)data_str : (const void*)buf, data_len, ENET_PACKET_FLAG_RELIABLE);
|
|
||||||
if (data_str) JS_FreeCString(ctx, data_str);
|
|
||||||
if (!packet) return JS_RaiseDisrupt(ctx, "Failed to create ENet packet");
|
|
||||||
|
|
||||||
enet_host_broadcast(host, 0, packet);
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_host_get_port(JSContext *js, JSValueConst self, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetHost *host = JS_GetOpaque(self, enet_host_id);
|
|
||||||
if (!host) return JS_EXCEPTION;
|
|
||||||
return JS_NewInt32(js, host->address.port);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_host_get_address(JSContext *js, JSValueConst self, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetHost *me = JS_GetOpaque(self, enet_host_id);
|
|
||||||
if (!me) return JS_EXCEPTION;
|
|
||||||
|
|
||||||
char ip_str[128];
|
|
||||||
if (enet_address_get_host_ip(&me->address, ip_str, sizeof(ip_str)) != 0)
|
|
||||||
return JS_NULL;
|
|
||||||
|
|
||||||
return JS_NewString(js, ip_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Peer-level operations
|
|
||||||
// Request a graceful disconnection from this peer. The connection will close after
|
|
||||||
// pending data is sent.
|
|
||||||
//
|
|
||||||
// :return: None
|
|
||||||
static JSValue js_enet_peer_disconnect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_EXCEPTION;
|
|
||||||
enet_peer_disconnect(peer, 0);
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a string or ArrayBuffer to this peer on channel 0.
|
|
||||||
//
|
|
||||||
// :param data: A string or ArrayBuffer to send.
|
|
||||||
// :return: None
|
|
||||||
static JSValue js_enet_peer_send(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_EXCEPTION;
|
|
||||||
|
|
||||||
if (argc < 1) return JS_RaiseDisrupt(ctx, "Expected a string or ArrayBuffer to send");
|
|
||||||
|
|
||||||
const char *data_str = NULL;
|
|
||||||
size_t data_len = 0;
|
|
||||||
uint8_t *buf = NULL;
|
|
||||||
|
|
||||||
if (JS_IsText(argv[0])) {
|
|
||||||
data_str = JS_ToCStringLen(ctx, &data_len, argv[0]);
|
|
||||||
if (!data_str) return JS_EXCEPTION;
|
|
||||||
} else if (js_is_blob(ctx,argv[0])) {
|
|
||||||
buf = js_get_blob_data(ctx, &data_len, argv[0]);
|
|
||||||
if (!buf) return JS_EXCEPTION;
|
|
||||||
} else {
|
|
||||||
return JS_RaiseDisrupt(ctx, "send() only accepts a string or ArrayBuffer");
|
|
||||||
}
|
|
||||||
|
|
||||||
ENetPacket *packet = enet_packet_create(data_str ? (const void*)data_str : (const void*)buf, data_len, ENET_PACKET_FLAG_RELIABLE);
|
|
||||||
if (data_str) JS_FreeCString(ctx, data_str);
|
|
||||||
if (!packet) return JS_RaiseDisrupt(ctx, "Failed to create ENet packet");
|
|
||||||
|
|
||||||
if (enet_peer_send(peer, 0, packet) < 0) return JS_RaiseDisrupt(ctx, "Unable to send packet");
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Immediately terminate the connection to this peer, discarding any pending data.
|
|
||||||
//
|
|
||||||
// :return: None
|
|
||||||
static JSValue js_enet_peer_disconnect_now(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_EXCEPTION;
|
|
||||||
enet_peer_disconnect_now(peer, 0);
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request a disconnection from this peer after all queued packets are sent.
|
|
||||||
//
|
|
||||||
// :return: None
|
|
||||||
static JSValue js_enet_peer_disconnect_later(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_EXCEPTION;
|
|
||||||
enet_peer_disconnect_later(peer, 0);
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset this peer's connection, immediately dropping it and clearing its internal state.
|
|
||||||
//
|
|
||||||
// :return: None
|
|
||||||
static JSValue js_enet_peer_reset(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_EXCEPTION;
|
|
||||||
enet_peer_reset(peer);
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a ping request to this peer to measure latency.
|
|
||||||
//
|
|
||||||
// :return: None
|
|
||||||
static JSValue js_enet_peer_ping(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_EXCEPTION;
|
|
||||||
enet_peer_ping(peer);
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the throttling behavior for this peer, controlling how ENet adjusts its sending
|
|
||||||
// rate based on packet loss or congestion.
|
|
||||||
//
|
|
||||||
// :param interval: The interval (ms) between throttle adjustments.
|
|
||||||
// :param acceleration: The factor to increase sending speed when conditions improve.
|
|
||||||
// :param deceleration: The factor to decrease sending speed when conditions worsen.
|
|
||||||
// :return: None
|
|
||||||
static JSValue js_enet_peer_throttle_configure(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_EXCEPTION;
|
|
||||||
|
|
||||||
int interval, acceleration, deceleration;
|
|
||||||
if (argc < 3 || JS_ToInt32(ctx, &interval, argv[0]) || JS_ToInt32(ctx, &acceleration, argv[1]) || JS_ToInt32(ctx, &deceleration, argv[2]))
|
|
||||||
return JS_RaiseDisrupt(ctx, "Expected 3 int arguments: interval, acceleration, deceleration");
|
|
||||||
|
|
||||||
enet_peer_throttle_configure(peer, interval, acceleration, deceleration);
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_timeout(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_EXCEPTION;
|
|
||||||
|
|
||||||
int timeout_limit, timeout_min, timeout_max;
|
|
||||||
if (argc < 3 || JS_ToInt32(ctx, &timeout_limit, argv[0]) || JS_ToInt32(ctx, &timeout_min, argv[1]) || JS_ToInt32(ctx, &timeout_max, argv[2]))
|
|
||||||
return JS_RaiseDisrupt(ctx, "Expected 3 integer arguments: timeout_limit, timeout_min, timeout_max");
|
|
||||||
|
|
||||||
enet_peer_timeout(peer, timeout_limit, timeout_min, timeout_max);
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Class definitions
|
|
||||||
static JSClassDef enet_host = {
|
|
||||||
"ENetHost",
|
|
||||||
.finalizer = js_enet_host_finalizer,
|
|
||||||
};
|
|
||||||
|
|
||||||
static JSClassDef enet_peer_class = {
|
|
||||||
"ENetPeer",
|
|
||||||
.finalizer = js_enet_peer_finalizer,
|
|
||||||
// .gc_mark = js_enet_peer_mark
|
|
||||||
};
|
|
||||||
|
|
||||||
JSValue js_enet_resolve_hostname(JSContext *js, JSValue self, int argc, JSValue *argv)
|
|
||||||
{
|
|
||||||
// TODO: implement
|
|
||||||
const char *hostname = JS_ToCString(js, argv[0]);
|
|
||||||
JS_FreeCString(js, hostname);
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const JSCFunctionListEntry js_enet_funcs[] = {
|
|
||||||
JS_CFUNC_DEF("initialize", 0, js_enet_initialize),
|
|
||||||
JS_CFUNC_DEF("deinitialize", 0, js_enet_deinitialize),
|
|
||||||
JS_CFUNC_DEF("create_host", 1, js_enet_host_create),
|
|
||||||
JS_CFUNC_DEF("resolve_hostname", 1, js_enet_resolve_hostname),
|
|
||||||
};
|
|
||||||
|
|
||||||
static const JSCFunctionListEntry js_enet_host_funcs[] = {
|
|
||||||
JS_CFUNC_DEF("service", 2, js_enet_host_service),
|
|
||||||
JS_CFUNC_DEF("connect", 2, js_enet_host_connect),
|
|
||||||
JS_CFUNC_DEF("flush", 0, js_enet_host_flush),
|
|
||||||
JS_CFUNC_DEF("broadcast", 1, js_enet_host_broadcast),
|
|
||||||
// JS_CGETSET_DEF("port", js_enet_host_get_port, NULL),
|
|
||||||
// JS_CGETSET_DEF("address", js_enet_host_get_address, NULL),
|
|
||||||
};
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_get_rtt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_EXCEPTION;
|
|
||||||
return JS_NewInt32(ctx, peer->roundTripTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_get_incoming_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_EXCEPTION;
|
|
||||||
if (peer->incomingBandwidth == 0) return JS_NewFloat64(ctx, INFINITY);
|
|
||||||
return JS_NewInt32(ctx, peer->incomingBandwidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_get_outgoing_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_EXCEPTION;
|
|
||||||
if (peer->outgoingBandwidth == 0) return JS_NewFloat64(ctx, INFINITY);
|
|
||||||
return JS_NewInt32(ctx, peer->outgoingBandwidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_get_last_send_time(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_EXCEPTION;
|
|
||||||
return JS_NewInt32(ctx, peer->lastSendTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_get_last_receive_time(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_EXCEPTION;
|
|
||||||
return JS_NewInt32(ctx, peer->lastReceiveTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_get_mtu(JSContext *ctx, JSValueConst this_val)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_NewFloat64(ctx, INFINITY);
|
|
||||||
return JS_NewInt32(ctx, peer->mtu);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_get_outgoing_data_total(JSContext *ctx, JSValueConst this_val)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_NewFloat64(ctx, INFINITY);
|
|
||||||
return JS_NewInt32(ctx, peer->outgoingDataTotal);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_get_incoming_data_total(JSContext *ctx, JSValueConst this_val)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_NewFloat64(ctx, INFINITY);
|
|
||||||
return JS_NewInt32(ctx, peer->incomingDataTotal);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_get_rtt_variance(JSContext *ctx, JSValueConst this_val)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_NewFloat64(ctx, INFINITY);
|
|
||||||
return JS_NewInt32(ctx, peer->roundTripTimeVariance);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_get_packet_loss(JSContext *ctx, JSValueConst this_val)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_NewFloat64(ctx, INFINITY);
|
|
||||||
return JS_NewInt32(ctx, peer->packetLoss);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_get_state(JSContext *ctx, JSValueConst this_val)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_NewInt32(ctx, -1);
|
|
||||||
return JS_NewInt32(ctx, peer->state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_get_reliable_data_in_transit(JSContext *ctx, JSValueConst this_val)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
|
|
||||||
if (!peer) return JS_NewFloat64(ctx, INFINITY);
|
|
||||||
return JS_NewInt32(ctx, peer->reliableDataInTransit);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_get_port(JSContext *js, JSValueConst self)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(self, enet_peer_class_id);
|
|
||||||
return JS_NewUint32(js, peer->address.port);
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_enet_peer_get_address(JSContext *js, JSValueConst self)
|
|
||||||
{
|
|
||||||
ENetPeer *peer = JS_GetOpaque(self, enet_peer_class_id);
|
|
||||||
char ip_str[128];
|
|
||||||
if (enet_address_get_host_ip(&peer->address, ip_str, sizeof(ip_str)) != 0)
|
|
||||||
return JS_NULL;
|
|
||||||
|
|
||||||
return JS_NewString(js, ip_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const JSCFunctionListEntry js_enet_peer_funcs[] = {
|
|
||||||
JS_CFUNC_DEF("send", 1, js_enet_peer_send),
|
|
||||||
JS_CFUNC_DEF("disconnect", 0, js_enet_peer_disconnect),
|
|
||||||
JS_CFUNC_DEF("disconnect_now", 0, js_enet_peer_disconnect_now),
|
|
||||||
JS_CFUNC_DEF("disconnect_later", 0, js_enet_peer_disconnect_later),
|
|
||||||
JS_CFUNC_DEF("reset", 0, js_enet_peer_reset),
|
|
||||||
JS_CFUNC_DEF("ping", 0, js_enet_peer_ping),
|
|
||||||
JS_CFUNC_DEF("throttle_configure", 3, js_enet_peer_throttle_configure),
|
|
||||||
JS_CFUNC_DEF("timeout", 3, js_enet_peer_timeout),
|
|
||||||
// JS_CGETSET_DEF("rtt", js_enet_peer_get_rtt, NULL),
|
|
||||||
// JS_CGETSET_DEF("incoming_bandwidth", js_enet_peer_get_incoming_bandwidth, NULL),
|
|
||||||
// JS_CGETSET_DEF("outgoing_bandwidth", js_enet_peer_get_outgoing_bandwidth, NULL),
|
|
||||||
// JS_CGETSET_DEF("last_send_time", js_enet_peer_get_last_send_time, NULL),
|
|
||||||
// JS_CGETSET_DEF("last_receive_time", js_enet_peer_get_last_receive_time, NULL),
|
|
||||||
// JS_CGETSET_DEF("mtu", js_enet_peer_get_mtu, NULL),
|
|
||||||
// JS_CGETSET_DEF("outgoing_data_total", js_enet_peer_get_outgoing_data_total, NULL),
|
|
||||||
// JS_CGETSET_DEF("incoming_data_total", js_enet_peer_get_incoming_data_total, NULL),
|
|
||||||
// JS_CGETSET_DEF("rtt_variance", js_enet_peer_get_rtt_variance, NULL),
|
|
||||||
// JS_CGETSET_DEF("packet_loss", js_enet_peer_get_packet_loss, NULL),
|
|
||||||
// JS_CGETSET_DEF("state", js_enet_peer_get_state, NULL),
|
|
||||||
// JS_CGETSET_DEF("reliable_data_in_transit", js_enet_peer_get_reliable_data_in_transit, NULL),
|
|
||||||
// JS_CGETSET_DEF("port", js_enet_peer_get_port, NULL),
|
|
||||||
// JS_CGETSET_DEF("address", js_enet_peer_get_address, NULL),
|
|
||||||
};
|
|
||||||
|
|
||||||
JSValue js_core_enet_use(JSContext *ctx)
|
|
||||||
{
|
|
||||||
JS_FRAME(ctx);
|
|
||||||
|
|
||||||
JS_NewClassID(&enet_host_id);
|
|
||||||
JS_NewClass(ctx, enet_host_id, &enet_host);
|
|
||||||
JS_ROOT(host_proto, JS_NewObject(ctx));
|
|
||||||
JS_SetPropertyFunctionList(ctx, host_proto.val, js_enet_host_funcs, countof(js_enet_host_funcs));
|
|
||||||
JS_SetClassProto(ctx, enet_host_id, host_proto.val);
|
|
||||||
|
|
||||||
JS_NewClassID(&enet_peer_class_id);
|
|
||||||
JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class);
|
|
||||||
JS_ROOT(peer_proto, JS_NewObject(ctx));
|
|
||||||
JS_SetPropertyFunctionList(ctx, peer_proto.val, js_enet_peer_funcs, countof(js_enet_peer_funcs));
|
|
||||||
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto.val);
|
|
||||||
|
|
||||||
JS_ROOT(export_obj, JS_NewObject(ctx));
|
|
||||||
JS_SetPropertyFunctionList(ctx, export_obj.val, js_enet_funcs, countof(js_enet_funcs));
|
|
||||||
JS_RETURN(export_obj.val);
|
|
||||||
}
|
|
||||||
@@ -321,7 +321,7 @@ static const JSCFunctionListEntry js_http_funcs[] = {
|
|||||||
JS_CFUNC_DEF("fetch", 2, js_fetch_picoparser),
|
JS_CFUNC_DEF("fetch", 2, js_fetch_picoparser),
|
||||||
};
|
};
|
||||||
|
|
||||||
JSValue js_core_http_use(JSContext *js) {
|
JSValue js_core_net_http_use(JSContext *js) {
|
||||||
JS_FRAME(js);
|
JS_FRAME(js);
|
||||||
par_easycurl_init(0); // Initialize platform HTTP backend
|
par_easycurl_init(0); // Initialize platform HTTP backend
|
||||||
JS_ROOT(mod, JS_NewObject(js));
|
JS_ROOT(mod, JS_NewObject(js));
|
||||||
|
|||||||
116
net/socket.c
116
net/socket.c
@@ -24,6 +24,9 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <fcntl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// Helper to convert JS value to file descriptor
|
// Helper to convert JS value to file descriptor
|
||||||
static int js2fd(JSContext *ctx, JSValueConst val)
|
static int js2fd(JSContext *ctx, JSValueConst val)
|
||||||
@@ -64,7 +67,6 @@ JSC_CCALL(socket_getaddrinfo,
|
|||||||
else if (strcmp(family, "AF_INET6") == 0) hints.ai_family = AF_INET6;
|
else if (strcmp(family, "AF_INET6") == 0) hints.ai_family = AF_INET6;
|
||||||
JS_FreeCString(js, family);
|
JS_FreeCString(js, family);
|
||||||
}
|
}
|
||||||
JS_FreeValue(js, val);
|
|
||||||
|
|
||||||
val = JS_GetPropertyStr(js, argv[2], "socktype");
|
val = JS_GetPropertyStr(js, argv[2], "socktype");
|
||||||
if (!JS_IsNull(val)) {
|
if (!JS_IsNull(val)) {
|
||||||
@@ -73,19 +75,16 @@ JSC_CCALL(socket_getaddrinfo,
|
|||||||
else if (strcmp(socktype, "SOCK_DGRAM") == 0) hints.ai_socktype = SOCK_DGRAM;
|
else if (strcmp(socktype, "SOCK_DGRAM") == 0) hints.ai_socktype = SOCK_DGRAM;
|
||||||
JS_FreeCString(js, socktype);
|
JS_FreeCString(js, socktype);
|
||||||
}
|
}
|
||||||
JS_FreeValue(js, val);
|
|
||||||
|
|
||||||
val = JS_GetPropertyStr(js, argv[2], "flags");
|
val = JS_GetPropertyStr(js, argv[2], "flags");
|
||||||
if (!JS_IsNull(val)) {
|
if (!JS_IsNull(val)) {
|
||||||
hints.ai_flags = js2number(js, val);
|
hints.ai_flags = js2number(js, val);
|
||||||
}
|
}
|
||||||
JS_FreeValue(js, val);
|
|
||||||
|
|
||||||
val = JS_GetPropertyStr(js, argv[2], "passive");
|
val = JS_GetPropertyStr(js, argv[2], "passive");
|
||||||
if (JS_ToBool(js, val)) {
|
if (JS_ToBool(js, val)) {
|
||||||
hints.ai_flags |= AI_PASSIVE;
|
hints.ai_flags |= AI_PASSIVE;
|
||||||
}
|
}
|
||||||
JS_FreeValue(js, val);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int status = getaddrinfo(node, service, &hints, &res);
|
int status = getaddrinfo(node, service, &hints, &res);
|
||||||
@@ -563,10 +562,107 @@ JSC_CCALL(socket_setsockopt,
|
|||||||
JSC_CCALL(socket_close,
|
JSC_CCALL(socket_close,
|
||||||
int sockfd = js2fd(js, argv[0]);
|
int sockfd = js2fd(js, argv[0]);
|
||||||
if (sockfd < 0) return JS_EXCEPTION;
|
if (sockfd < 0) return JS_EXCEPTION;
|
||||||
|
|
||||||
if (close(sockfd) != 0)
|
if (close(sockfd) != 0)
|
||||||
return JS_RaiseDisrupt(js, "close failed: %s", strerror(errno));
|
return JS_RaiseDisrupt(js, "close failed: %s", strerror(errno));
|
||||||
|
|
||||||
|
return JS_NULL;
|
||||||
|
)
|
||||||
|
|
||||||
|
JSC_CCALL(socket_on_readable,
|
||||||
|
int sockfd = js2fd(js, argv[0]);
|
||||||
|
if (sockfd < 0) return JS_EXCEPTION;
|
||||||
|
if (!JS_IsFunction(argv[1]))
|
||||||
|
return JS_RaiseDisrupt(js, "on_readable: callback must be a function");
|
||||||
|
actor_watch_readable(js, sockfd, argv[1]);
|
||||||
|
return JS_NULL;
|
||||||
|
)
|
||||||
|
|
||||||
|
JSC_CCALL(socket_unwatch,
|
||||||
|
int sockfd = js2fd(js, argv[0]);
|
||||||
|
if (sockfd < 0) return JS_EXCEPTION;
|
||||||
|
actor_unwatch(js, sockfd);
|
||||||
|
return JS_NULL;
|
||||||
|
)
|
||||||
|
|
||||||
|
JSC_CCALL(socket_on_writable,
|
||||||
|
int sockfd = js2fd(js, argv[0]);
|
||||||
|
if (sockfd < 0) return JS_EXCEPTION;
|
||||||
|
if (!JS_IsFunction(argv[1]))
|
||||||
|
return JS_RaiseDisrupt(js, "on_writable: callback must be a function");
|
||||||
|
actor_watch_writable(js, sockfd, argv[1]);
|
||||||
|
return JS_NULL;
|
||||||
|
)
|
||||||
|
|
||||||
|
JSC_CCALL(socket_setnonblock,
|
||||||
|
int sockfd = js2fd(js, argv[0]);
|
||||||
|
if (sockfd < 0) return JS_EXCEPTION;
|
||||||
|
#ifdef _WIN32
|
||||||
|
u_long mode = 1;
|
||||||
|
if (ioctlsocket(sockfd, FIONBIO, &mode) != 0)
|
||||||
|
return JS_RaiseDisrupt(js, "setnonblock failed");
|
||||||
|
#else
|
||||||
|
int flags = fcntl(sockfd, F_GETFL, 0);
|
||||||
|
if (flags < 0 || fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0)
|
||||||
|
return JS_RaiseDisrupt(js, "setnonblock failed: %s", strerror(errno));
|
||||||
|
#endif
|
||||||
|
return JS_NULL;
|
||||||
|
)
|
||||||
|
|
||||||
|
JSC_CCALL(socket_getsockopt,
|
||||||
|
int sockfd = js2fd(js, argv[0]);
|
||||||
|
if (sockfd < 0) return JS_EXCEPTION;
|
||||||
|
|
||||||
|
int level = SOL_SOCKET;
|
||||||
|
int optname = 0;
|
||||||
|
|
||||||
|
// Parse level
|
||||||
|
if (JS_IsText(argv[1])) {
|
||||||
|
const char *level_str = JS_ToCString(js, argv[1]);
|
||||||
|
if (strcmp(level_str, "SOL_SOCKET") == 0) level = SOL_SOCKET;
|
||||||
|
else if (strcmp(level_str, "IPPROTO_TCP") == 0) level = IPPROTO_TCP;
|
||||||
|
else if (strcmp(level_str, "IPPROTO_IP") == 0) level = IPPROTO_IP;
|
||||||
|
else if (strcmp(level_str, "IPPROTO_IPV6") == 0) level = IPPROTO_IPV6;
|
||||||
|
JS_FreeCString(js, level_str);
|
||||||
|
} else {
|
||||||
|
level = js2number(js, argv[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse option name
|
||||||
|
if (JS_IsText(argv[2])) {
|
||||||
|
const char *opt_str = JS_ToCString(js, argv[2]);
|
||||||
|
if (strcmp(opt_str, "SO_ERROR") == 0) optname = SO_ERROR;
|
||||||
|
else if (strcmp(opt_str, "SO_REUSEADDR") == 0) optname = SO_REUSEADDR;
|
||||||
|
else if (strcmp(opt_str, "SO_KEEPALIVE") == 0) optname = SO_KEEPALIVE;
|
||||||
|
else if (strcmp(opt_str, "SO_BROADCAST") == 0) optname = SO_BROADCAST;
|
||||||
|
JS_FreeCString(js, opt_str);
|
||||||
|
} else {
|
||||||
|
optname = js2number(js, argv[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int optval = 0;
|
||||||
|
socklen_t optlen = sizeof(optval);
|
||||||
|
if (getsockopt(sockfd, level, optname, &optval, &optlen) < 0)
|
||||||
|
return JS_RaiseDisrupt(js, "getsockopt failed: %s", strerror(errno));
|
||||||
|
|
||||||
|
return JS_NewInt32(js, optval);
|
||||||
|
)
|
||||||
|
|
||||||
|
JSC_CCALL(socket_send_self,
|
||||||
|
if (argc < 1 || !JS_IsText(argv[0]))
|
||||||
|
return JS_RaiseDisrupt(js, "send_self: expects a text argument");
|
||||||
|
const char *msg = JS_ToCString(js, argv[0]);
|
||||||
|
WotaBuffer wb;
|
||||||
|
wota_buffer_init(&wb, 16);
|
||||||
|
wota_write_record(&wb, 1);
|
||||||
|
wota_write_text(&wb, "text");
|
||||||
|
wota_write_text(&wb, msg);
|
||||||
|
JS_FreeCString(js, msg);
|
||||||
|
const char *err = JS_SendMessage(js, &wb);
|
||||||
|
if (err) {
|
||||||
|
wota_buffer_free(&wb);
|
||||||
|
return JS_RaiseDisrupt(js, "send_self failed: %s", err);
|
||||||
|
}
|
||||||
return JS_NULL;
|
return JS_NULL;
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -587,6 +683,12 @@ static const JSCFunctionListEntry js_socket_funcs[] = {
|
|||||||
MIST_FUNC_DEF(socket, gai_strerror, 1),
|
MIST_FUNC_DEF(socket, gai_strerror, 1),
|
||||||
MIST_FUNC_DEF(socket, setsockopt, 4),
|
MIST_FUNC_DEF(socket, setsockopt, 4),
|
||||||
MIST_FUNC_DEF(socket, close, 1),
|
MIST_FUNC_DEF(socket, close, 1),
|
||||||
|
MIST_FUNC_DEF(socket, on_readable, 2),
|
||||||
|
MIST_FUNC_DEF(socket, on_writable, 2),
|
||||||
|
MIST_FUNC_DEF(socket, unwatch, 1),
|
||||||
|
MIST_FUNC_DEF(socket, setnonblock, 1),
|
||||||
|
MIST_FUNC_DEF(socket, getsockopt, 3),
|
||||||
|
MIST_FUNC_DEF(socket, send_self, 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
JSValue js_core_socket_use(JSContext *js) {
|
JSValue js_core_socket_use(JSContext *js) {
|
||||||
@@ -611,6 +713,8 @@ JSValue js_core_socket_use(JSContext *js) {
|
|||||||
|
|
||||||
JS_SetPropertyStr(js, mod.val, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET));
|
JS_SetPropertyStr(js, mod.val, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET));
|
||||||
JS_SetPropertyStr(js, mod.val, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR));
|
JS_SetPropertyStr(js, mod.val, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR));
|
||||||
|
JS_SetPropertyStr(js, mod.val, "SO_ERROR", JS_NewInt32(js, SO_ERROR));
|
||||||
|
JS_SetPropertyStr(js, mod.val, "SO_KEEPALIVE", JS_NewInt32(js, SO_KEEPALIVE));
|
||||||
|
|
||||||
JS_RETURN(mod.val);
|
JS_RETURN(mod.val);
|
||||||
}
|
}
|
||||||
|
|||||||
238
net/tls.c
Normal file
238
net/tls.c
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
#include "cell.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
/* SecureTransport — deprecated but functional, no external deps */
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
#include <Security/Security.h>
|
||||||
|
#include <Security/SecureTransport.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
SSLContextRef ssl;
|
||||||
|
int fd;
|
||||||
|
} tls_ctx;
|
||||||
|
|
||||||
|
static void tls_ctx_free(JSRuntime *rt, tls_ctx *ctx) {
|
||||||
|
if (!ctx) return;
|
||||||
|
if (ctx->ssl) {
|
||||||
|
SSLClose(ctx->ssl);
|
||||||
|
CFRelease(ctx->ssl);
|
||||||
|
}
|
||||||
|
if (ctx->fd >= 0)
|
||||||
|
close(ctx->fd);
|
||||||
|
free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJSCLASS(tls_ctx,)
|
||||||
|
|
||||||
|
static OSStatus tls_read_cb(SSLConnectionRef conn, void *data, size_t *len) {
|
||||||
|
int fd = *(const int *)conn;
|
||||||
|
size_t requested = *len;
|
||||||
|
size_t total = 0;
|
||||||
|
while (total < requested) {
|
||||||
|
ssize_t n = read(fd, (char *)data + total, requested - total);
|
||||||
|
if (n > 0) {
|
||||||
|
total += n;
|
||||||
|
} else if (n == 0) {
|
||||||
|
*len = total;
|
||||||
|
return errSSLClosedGraceful;
|
||||||
|
} else {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
*len = total;
|
||||||
|
return (total > 0) ? noErr : errSSLWouldBlock;
|
||||||
|
}
|
||||||
|
*len = total;
|
||||||
|
return errSSLClosedAbort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*len = total;
|
||||||
|
return noErr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static OSStatus tls_write_cb(SSLConnectionRef conn, const void *data, size_t *len) {
|
||||||
|
int fd = *(const int *)conn;
|
||||||
|
size_t requested = *len;
|
||||||
|
size_t total = 0;
|
||||||
|
while (total < requested) {
|
||||||
|
ssize_t n = write(fd, (const char *)data + total, requested - total);
|
||||||
|
if (n > 0) {
|
||||||
|
total += n;
|
||||||
|
} else if (n == 0) {
|
||||||
|
*len = total;
|
||||||
|
return errSSLClosedGraceful;
|
||||||
|
} else {
|
||||||
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
*len = total;
|
||||||
|
return (total > 0) ? noErr : errSSLWouldBlock;
|
||||||
|
}
|
||||||
|
*len = total;
|
||||||
|
return errSSLClosedAbort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*len = total;
|
||||||
|
return noErr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tls.wrap(fd, hostname) -> ctx */
|
||||||
|
JSC_CCALL(tls_wrap,
|
||||||
|
int fd = -1;
|
||||||
|
if (JS_ToInt32(js, &fd, argv[0]) < 0)
|
||||||
|
return JS_RaiseDisrupt(js, "tls.wrap: fd must be a number");
|
||||||
|
const char *hostname = JS_ToCString(js, argv[1]);
|
||||||
|
if (!hostname)
|
||||||
|
return JS_RaiseDisrupt(js, "tls.wrap: hostname must be a string");
|
||||||
|
|
||||||
|
tls_ctx *ctx = calloc(1, sizeof(tls_ctx));
|
||||||
|
ctx->fd = fd;
|
||||||
|
ctx->ssl = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
|
||||||
|
if (!ctx->ssl) {
|
||||||
|
free(ctx);
|
||||||
|
JS_FreeCString(js, hostname);
|
||||||
|
return JS_RaiseDisrupt(js, "tls.wrap: SSLCreateContext failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
SSLSetIOFuncs(ctx->ssl, tls_read_cb, tls_write_cb);
|
||||||
|
SSLSetConnection(ctx->ssl, &ctx->fd);
|
||||||
|
SSLSetPeerDomainName(ctx->ssl, hostname, strlen(hostname));
|
||||||
|
JS_FreeCString(js, hostname);
|
||||||
|
|
||||||
|
/* Retry handshake on non-blocking sockets (errSSLWouldBlock) */
|
||||||
|
OSStatus status;
|
||||||
|
for (int attempts = 0; attempts < 200; attempts++) {
|
||||||
|
status = SSLHandshake(ctx->ssl);
|
||||||
|
if (status == noErr) break;
|
||||||
|
if (status != errSSLWouldBlock) break;
|
||||||
|
struct pollfd pfd = { .fd = ctx->fd, .events = POLLIN | POLLOUT };
|
||||||
|
poll(&pfd, 1, 50);
|
||||||
|
}
|
||||||
|
if (status != noErr) {
|
||||||
|
CFRelease(ctx->ssl);
|
||||||
|
ctx->ssl = NULL;
|
||||||
|
ctx->fd = -1; /* don't close caller's fd */
|
||||||
|
free(ctx);
|
||||||
|
return JS_RaiseDisrupt(js, "tls.wrap: handshake failed (status %d)", (int)status);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tls_ctx2js(js, ctx);
|
||||||
|
)
|
||||||
|
|
||||||
|
/* tls.send(ctx, data) -> bytes_sent */
|
||||||
|
JSC_CCALL(tls_send,
|
||||||
|
tls_ctx *ctx = js2tls_ctx(js, argv[0]);
|
||||||
|
if (!ctx || !ctx->ssl)
|
||||||
|
return JS_RaiseDisrupt(js, "tls.send: invalid context");
|
||||||
|
|
||||||
|
size_t len;
|
||||||
|
size_t written = 0;
|
||||||
|
OSStatus status;
|
||||||
|
|
||||||
|
if (JS_IsText(argv[1])) {
|
||||||
|
const char *data = JS_ToCStringLen(js, &len, argv[1]);
|
||||||
|
status = SSLWrite(ctx->ssl, data, len, &written);
|
||||||
|
JS_FreeCString(js, data);
|
||||||
|
} else {
|
||||||
|
unsigned char *data = js_get_blob_data(js, &len, argv[1]);
|
||||||
|
if (!data)
|
||||||
|
return JS_RaiseDisrupt(js, "tls.send: invalid data");
|
||||||
|
status = SSLWrite(ctx->ssl, data, len, &written);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status != noErr && status != errSSLWouldBlock)
|
||||||
|
return JS_RaiseDisrupt(js, "tls.send: write failed (status %d)", (int)status);
|
||||||
|
|
||||||
|
return JS_NewInt64(js, (int64_t)written);
|
||||||
|
)
|
||||||
|
|
||||||
|
/* tls.recv(ctx, len) -> blob */
|
||||||
|
JSC_CCALL(tls_recv,
|
||||||
|
tls_ctx *ctx = js2tls_ctx(js, argv[0]);
|
||||||
|
if (!ctx || !ctx->ssl)
|
||||||
|
return JS_RaiseDisrupt(js, "tls.recv: invalid context");
|
||||||
|
|
||||||
|
size_t len = 4096;
|
||||||
|
if (argc > 1) len = js2number(js, argv[1]);
|
||||||
|
|
||||||
|
void *out;
|
||||||
|
ret = js_new_blob_alloc(js, len, &out);
|
||||||
|
if (JS_IsException(ret)) return ret;
|
||||||
|
|
||||||
|
size_t received = 0;
|
||||||
|
OSStatus status = SSLRead(ctx->ssl, out, len, &received);
|
||||||
|
|
||||||
|
if (status != noErr && status != errSSLWouldBlock &&
|
||||||
|
status != errSSLClosedGraceful) {
|
||||||
|
return JS_RaiseDisrupt(js, "tls.recv: read failed (status %d)", (int)status);
|
||||||
|
}
|
||||||
|
|
||||||
|
js_blob_stone(ret, received);
|
||||||
|
return ret;
|
||||||
|
)
|
||||||
|
|
||||||
|
/* tls.close(ctx) -> null */
|
||||||
|
JSC_CCALL(tls_close,
|
||||||
|
tls_ctx *ctx = js2tls_ctx(js, argv[0]);
|
||||||
|
if (!ctx) return JS_NULL;
|
||||||
|
if (ctx->ssl) {
|
||||||
|
SSLClose(ctx->ssl);
|
||||||
|
CFRelease(ctx->ssl);
|
||||||
|
ctx->ssl = NULL;
|
||||||
|
}
|
||||||
|
if (ctx->fd >= 0) {
|
||||||
|
close(ctx->fd);
|
||||||
|
ctx->fd = -1;
|
||||||
|
}
|
||||||
|
return JS_NULL;
|
||||||
|
)
|
||||||
|
|
||||||
|
/* tls.fd(ctx) -> number — get underlying fd for on_readable */
|
||||||
|
JSC_CCALL(tls_fd,
|
||||||
|
tls_ctx *ctx = js2tls_ctx(js, argv[0]);
|
||||||
|
if (!ctx)
|
||||||
|
return JS_RaiseDisrupt(js, "tls.fd: invalid context");
|
||||||
|
return JS_NewInt32(js, ctx->fd);
|
||||||
|
)
|
||||||
|
|
||||||
|
/* tls.on_readable(ctx, callback) -> null */
|
||||||
|
JSC_CCALL(tls_on_readable,
|
||||||
|
tls_ctx *ctx = js2tls_ctx(js, argv[0]);
|
||||||
|
if (!ctx)
|
||||||
|
return JS_RaiseDisrupt(js, "tls.on_readable: invalid context");
|
||||||
|
if (!JS_IsFunction(argv[1]))
|
||||||
|
return JS_RaiseDisrupt(js, "tls.on_readable: callback must be a function");
|
||||||
|
actor_watch_readable(js, ctx->fd, argv[1]);
|
||||||
|
return JS_NULL;
|
||||||
|
)
|
||||||
|
|
||||||
|
static const JSCFunctionListEntry js_tls_funcs[] = {
|
||||||
|
MIST_FUNC_DEF(tls, wrap, 2),
|
||||||
|
MIST_FUNC_DEF(tls, send, 2),
|
||||||
|
MIST_FUNC_DEF(tls, recv, 2),
|
||||||
|
MIST_FUNC_DEF(tls, close, 1),
|
||||||
|
MIST_FUNC_DEF(tls, fd, 1),
|
||||||
|
MIST_FUNC_DEF(tls, on_readable, 2),
|
||||||
|
};
|
||||||
|
|
||||||
|
JSValue js_core_net_tls_use(JSContext *js) {
|
||||||
|
JS_FRAME(js);
|
||||||
|
QJSCLASSPREP_NO_FUNCS(tls_ctx);
|
||||||
|
JS_ROOT(mod, JS_NewObject(js));
|
||||||
|
JS_SetPropertyFunctionList(js, mod.val, js_tls_funcs, countof(js_tls_funcs));
|
||||||
|
JS_RETURN(mod.val);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
|
||||||
|
#else
|
||||||
|
/* Stub for non-Apple platforms — TLS not yet implemented */
|
||||||
|
JSValue js_core_net_tls_use(JSContext *js) {
|
||||||
|
return JS_RaiseDisrupt(js, "TLS not available on this platform");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
6
pack.ce
6
pack.ce
@@ -88,9 +88,9 @@ var packages = ['core']
|
|||||||
var deps = pkg_tools.gather_dependencies(target_package)
|
var deps = pkg_tools.gather_dependencies(target_package)
|
||||||
|
|
||||||
for (i = 0; i < length(deps); i++) {
|
for (i = 0; i < length(deps); i++) {
|
||||||
push(packages, deps[i])
|
packages[] = deps[i]
|
||||||
}
|
}
|
||||||
push(packages, target_package)
|
packages[] = target_package
|
||||||
|
|
||||||
// Remove duplicates
|
// Remove duplicates
|
||||||
var unique_packages = []
|
var unique_packages = []
|
||||||
@@ -98,7 +98,7 @@ var seen = {}
|
|||||||
for (i = 0; i < length(packages); i++) {
|
for (i = 0; i < length(packages); i++) {
|
||||||
if (!seen[packages[i]]) {
|
if (!seen[packages[i]]) {
|
||||||
seen[packages[i]] = true
|
seen[packages[i]] = true
|
||||||
push(unique_packages, packages[i])
|
unique_packages[] = packages[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
packages = unique_packages
|
packages = unique_packages
|
||||||
|
|||||||
12
package.cm
12
package.cm
@@ -198,7 +198,7 @@ package.find_packages = function(dir) {
|
|||||||
var list = fd.readdir(dir)
|
var list = fd.readdir(dir)
|
||||||
if (!list) return found
|
if (!list) return found
|
||||||
if (fd.is_file(dir + '/cell.toml'))
|
if (fd.is_file(dir + '/cell.toml'))
|
||||||
push(found, dir)
|
found[] = dir
|
||||||
arrfor(list, function(item) {
|
arrfor(list, function(item) {
|
||||||
if (item == '.' || item == '..' || item == '.cell' || item == '.git') return
|
if (item == '.' || item == '..' || item == '.cell' || item == '.git') return
|
||||||
var full = dir + '/' + item
|
var full = dir + '/' + item
|
||||||
@@ -207,7 +207,7 @@ package.find_packages = function(dir) {
|
|||||||
if (st && st.isDirectory) {
|
if (st && st.isDirectory) {
|
||||||
sub = package.find_packages(full)
|
sub = package.find_packages(full)
|
||||||
arrfor(sub, function(p) {
|
arrfor(sub, function(p) {
|
||||||
push(found, p)
|
found[] = p
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -227,14 +227,14 @@ package.list_modules = function(name) {
|
|||||||
var stem = null
|
var stem = null
|
||||||
for (i = 0; i < length(files); i++) {
|
for (i = 0; i < length(files); i++) {
|
||||||
if (ends_with(files[i], '.cm')) {
|
if (ends_with(files[i], '.cm')) {
|
||||||
push(modules, text(files[i], 0, -3))
|
modules[] = text(files[i], 0, -3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var c_files = package.get_c_files(name, null, true)
|
var c_files = package.get_c_files(name, null, true)
|
||||||
for (i = 0; i < length(c_files); i++) {
|
for (i = 0; i < length(c_files); i++) {
|
||||||
stem = ends_with(c_files[i], '.cpp') ? text(c_files[i], 0, -4) : text(c_files[i], 0, -2)
|
stem = ends_with(c_files[i], '.cpp') ? text(c_files[i], 0, -4) : text(c_files[i], 0, -2)
|
||||||
if (find(modules, function(m) { return m == stem }) == null)
|
if (find(modules, function(m) { return m == stem }) == null)
|
||||||
push(modules, stem)
|
modules[] = stem
|
||||||
}
|
}
|
||||||
return modules
|
return modules
|
||||||
}
|
}
|
||||||
@@ -245,7 +245,7 @@ package.list_programs = function(name) {
|
|||||||
var i = 0
|
var i = 0
|
||||||
for (i = 0; i < length(files); i++) {
|
for (i = 0; i < length(files); i++) {
|
||||||
if (ends_with(files[i], '.ce')) {
|
if (ends_with(files[i], '.ce')) {
|
||||||
push(programs, text(files[i], 0, -3))
|
programs[] = text(files[i], 0, -3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return programs
|
return programs
|
||||||
@@ -360,7 +360,7 @@ package.get_c_files = function(name, target, exclude_main) {
|
|||||||
basename = fd.basename(selected)
|
basename = fd.basename(selected)
|
||||||
if (basename == 'main.c' || starts_with(basename, 'main_')) return
|
if (basename == 'main.c' || starts_with(basename, 'main_')) return
|
||||||
}
|
}
|
||||||
push(result, selected)
|
result[] = selected
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
156
parse.cm
156
parse.cm
@@ -90,12 +90,12 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
var parse_error = function(token, msg) {
|
var parse_error = function(token, msg) {
|
||||||
if (error_count >= 5) return null
|
if (error_count >= 5) return null
|
||||||
error_count = error_count + 1
|
error_count = error_count + 1
|
||||||
push(errors, {
|
errors[] = {
|
||||||
message: msg,
|
message: msg,
|
||||||
line: token.from_row + 1,
|
line: token.from_row + 1,
|
||||||
column: token.from_column + 1,
|
column: token.from_column + 1,
|
||||||
offset: token.at
|
offset: token.at
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _keywords = {
|
var _keywords = {
|
||||||
@@ -230,8 +230,8 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
|
if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
|
||||||
esc_ch = tv[tvi + 1]
|
esc_ch = tv[tvi + 1]
|
||||||
esc_val = template_escape_map[esc_ch]
|
esc_val = template_escape_map[esc_ch]
|
||||||
if (esc_val != null) { push(fmt_parts, esc_val) }
|
if (esc_val != null) { fmt_parts[] = esc_val }
|
||||||
else { push(fmt_parts, esc_ch) }
|
else { fmt_parts[] = esc_ch }
|
||||||
tvi = tvi + 2
|
tvi = tvi + 2
|
||||||
} else if (tv[tvi] == "$" && tvi + 1 < tvlen && tv[tvi + 1] == "{") {
|
} else if (tv[tvi] == "$" && tvi + 1 < tvlen && tv[tvi + 1] == "{") {
|
||||||
tvi = tvi + 2
|
tvi = tvi + 2
|
||||||
@@ -239,27 +239,27 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
expr_parts = []
|
expr_parts = []
|
||||||
while (tvi < tvlen && depth > 0) {
|
while (tvi < tvlen && depth > 0) {
|
||||||
tc = tv[tvi]
|
tc = tv[tvi]
|
||||||
if (tc == "{") { depth = depth + 1; push(expr_parts, tc); tvi = tvi + 1 }
|
if (tc == "{") { depth = depth + 1; expr_parts[] = tc; tvi = tvi + 1 }
|
||||||
else if (tc == "}") {
|
else if (tc == "}") {
|
||||||
depth = depth - 1
|
depth = depth - 1
|
||||||
if (depth > 0) { push(expr_parts, tc) }
|
if (depth > 0) { expr_parts[] = tc }
|
||||||
tvi = tvi + 1
|
tvi = tvi + 1
|
||||||
}
|
}
|
||||||
else if (tc == "'" || tc == "\"" || tc == "`") {
|
else if (tc == "'" || tc == "\"" || tc == "`") {
|
||||||
tq = tc
|
tq = tc
|
||||||
push(expr_parts, tc)
|
expr_parts[] = tc
|
||||||
tvi = tvi + 1
|
tvi = tvi + 1
|
||||||
while (tvi < tvlen && tv[tvi] != tq) {
|
while (tvi < tvlen && tv[tvi] != tq) {
|
||||||
if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
|
if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
|
||||||
push(expr_parts, tv[tvi])
|
expr_parts[] = tv[tvi]
|
||||||
tvi = tvi + 1
|
tvi = tvi + 1
|
||||||
}
|
}
|
||||||
push(expr_parts, tv[tvi])
|
expr_parts[] = tv[tvi]
|
||||||
tvi = tvi + 1
|
tvi = tvi + 1
|
||||||
}
|
}
|
||||||
if (tvi < tvlen) { push(expr_parts, tv[tvi]); tvi = tvi + 1 }
|
if (tvi < tvlen) { expr_parts[] = tv[tvi]; tvi = tvi + 1 }
|
||||||
} else {
|
} else {
|
||||||
push(expr_parts, tc)
|
expr_parts[] = tc
|
||||||
tvi = tvi + 1
|
tvi = tvi + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,14 +274,14 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
} else {
|
} else {
|
||||||
sub_expr = sub_stmt
|
sub_expr = sub_stmt
|
||||||
}
|
}
|
||||||
push(tpl_list, sub_expr)
|
tpl_list[] = sub_expr
|
||||||
}
|
}
|
||||||
push(fmt_parts, "{")
|
fmt_parts[] = "{"
|
||||||
push(fmt_parts, text(idx))
|
fmt_parts[] = text(idx)
|
||||||
push(fmt_parts, "}")
|
fmt_parts[] = "}"
|
||||||
idx = idx + 1
|
idx = idx + 1
|
||||||
} else {
|
} else {
|
||||||
push(fmt_parts, tv[tvi])
|
fmt_parts[] = tv[tvi]
|
||||||
tvi = tvi + 1
|
tvi = tvi + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,7 +332,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
advance()
|
advance()
|
||||||
while (tok.kind != "]" && tok.kind != "eof") {
|
while (tok.kind != "]" && tok.kind != "eof") {
|
||||||
elem = parse_assign_expr()
|
elem = parse_assign_expr()
|
||||||
if (elem != null) push(list, elem)
|
if (elem != null) list[] = elem
|
||||||
if (tok.kind == ",") advance()
|
if (tok.kind == ",") advance()
|
||||||
else break
|
else break
|
||||||
}
|
}
|
||||||
@@ -395,7 +395,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
advance()
|
advance()
|
||||||
param.expression = parse_assign_expr()
|
param.expression = parse_assign_expr()
|
||||||
}
|
}
|
||||||
push(params, param)
|
params[] = param
|
||||||
} else {
|
} else {
|
||||||
parse_error(tok, "expected parameter name")
|
parse_error(tok, "expected parameter name")
|
||||||
break
|
break
|
||||||
@@ -436,7 +436,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
} else {
|
} else {
|
||||||
parse_error(tok, "expected ':' after property name")
|
parse_error(tok, "expected ':' after property name")
|
||||||
}
|
}
|
||||||
push(list, pair)
|
list[] = pair
|
||||||
if (tok.kind == ",") advance()
|
if (tok.kind == ",") advance()
|
||||||
else if (tok.kind == "{") {
|
else if (tok.kind == "{") {
|
||||||
if (right && right.kind == "(") {
|
if (right && right.kind == "(") {
|
||||||
@@ -473,17 +473,17 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
flags_parts = []
|
flags_parts = []
|
||||||
while (rpos < _src_len && src[rpos] != "/") {
|
while (rpos < _src_len && src[rpos] != "/") {
|
||||||
if (src[rpos] == "\\" && rpos + 1 < _src_len) {
|
if (src[rpos] == "\\" && rpos + 1 < _src_len) {
|
||||||
push(pattern_parts, src[rpos])
|
pattern_parts[] = src[rpos]
|
||||||
push(pattern_parts, src[rpos + 1])
|
pattern_parts[] = src[rpos + 1]
|
||||||
rpos = rpos + 2
|
rpos = rpos + 2
|
||||||
} else {
|
} else {
|
||||||
push(pattern_parts, src[rpos])
|
pattern_parts[] = src[rpos]
|
||||||
rpos = rpos + 1
|
rpos = rpos + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rpos < _src_len) rpos = rpos + 1
|
if (rpos < _src_len) rpos = rpos + 1
|
||||||
while (rpos < _src_len && is_letter(src[rpos])) {
|
while (rpos < _src_len && is_letter(src[rpos])) {
|
||||||
push(flags_parts, src[rpos])
|
flags_parts[] = src[rpos]
|
||||||
rpos = rpos + 1
|
rpos = rpos + 1
|
||||||
}
|
}
|
||||||
node.pattern = text(pattern_parts)
|
node.pattern = text(pattern_parts)
|
||||||
@@ -557,7 +557,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
new_node.list = args_list
|
new_node.list = args_list
|
||||||
while (tok.kind != ")" && tok.kind != "eof") {
|
while (tok.kind != ")" && tok.kind != "eof") {
|
||||||
arg = parse_assign_expr()
|
arg = parse_assign_expr()
|
||||||
if (arg != null) push(args_list, arg)
|
if (arg != null) args_list[] = arg
|
||||||
if (tok.kind == ",") advance()
|
if (tok.kind == ",") advance()
|
||||||
else break
|
else break
|
||||||
}
|
}
|
||||||
@@ -830,7 +830,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
before = cursor
|
before = cursor
|
||||||
stmt = parse_statement()
|
stmt = parse_statement()
|
||||||
if (stmt != null) {
|
if (stmt != null) {
|
||||||
push(stmts, stmt)
|
stmts[] = stmt
|
||||||
} else if (cursor == before) {
|
} else if (cursor == before) {
|
||||||
sync_to_statement()
|
sync_to_statement()
|
||||||
}
|
}
|
||||||
@@ -872,14 +872,14 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
param.name = tok.value
|
param.name = tok.value
|
||||||
pname = tok.value
|
pname = tok.value
|
||||||
if (find(prev_names, pname) != null) parse_error(tok, "duplicate parameter name '" + pname + "'")
|
if (find(prev_names, pname) != null) parse_error(tok, "duplicate parameter name '" + pname + "'")
|
||||||
push(prev_names, pname)
|
prev_names[] = pname
|
||||||
advance()
|
advance()
|
||||||
ast_node_end(param)
|
ast_node_end(param)
|
||||||
if (tok.kind == "=" || tok.kind == "|") {
|
if (tok.kind == "=" || tok.kind == "|") {
|
||||||
advance()
|
advance()
|
||||||
param.expression = parse_assign_expr()
|
param.expression = parse_assign_expr()
|
||||||
}
|
}
|
||||||
push(params, param)
|
params[] = param
|
||||||
} else {
|
} else {
|
||||||
parse_error(tok, "expected parameter name")
|
parse_error(tok, "expected parameter name")
|
||||||
break
|
break
|
||||||
@@ -959,7 +959,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
param.name = tok.value
|
param.name = tok.value
|
||||||
advance()
|
advance()
|
||||||
ast_node_end(param)
|
ast_node_end(param)
|
||||||
push(params, param)
|
params[] = param
|
||||||
} else if (tok.kind == "(") {
|
} else if (tok.kind == "(") {
|
||||||
advance()
|
advance()
|
||||||
prev_names = []
|
prev_names = []
|
||||||
@@ -969,14 +969,14 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
param.name = tok.value
|
param.name = tok.value
|
||||||
pname = tok.value
|
pname = tok.value
|
||||||
if (find(prev_names, pname) != null) parse_error(tok, "duplicate parameter name '" + pname + "'")
|
if (find(prev_names, pname) != null) parse_error(tok, "duplicate parameter name '" + pname + "'")
|
||||||
push(prev_names, pname)
|
prev_names[] = pname
|
||||||
advance()
|
advance()
|
||||||
ast_node_end(param)
|
ast_node_end(param)
|
||||||
if (tok.kind == "=" || tok.kind == "|") {
|
if (tok.kind == "=" || tok.kind == "|") {
|
||||||
advance()
|
advance()
|
||||||
param.expression = parse_assign_expr()
|
param.expression = parse_assign_expr()
|
||||||
}
|
}
|
||||||
push(params, param)
|
params[] = param
|
||||||
} else {
|
} else {
|
||||||
parse_error(tok, "expected parameter name")
|
parse_error(tok, "expected parameter name")
|
||||||
break
|
break
|
||||||
@@ -1010,7 +1010,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
expr = parse_assign_expr()
|
expr = parse_assign_expr()
|
||||||
ret.expression = expr
|
ret.expression = expr
|
||||||
ast_node_end(ret)
|
ast_node_end(ret)
|
||||||
push(stmts, ret)
|
stmts[] = ret
|
||||||
node.statements = stmts
|
node.statements = stmts
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1110,7 +1110,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
parse_error(start, "'var' declarations must be initialized; use 'var " + var_name + " = null' if no value is needed")
|
parse_error(start, "'var' declarations must be initialized; use 'var " + var_name + " = null' if no value is needed")
|
||||||
}
|
}
|
||||||
ast_node_end(node)
|
ast_node_end(node)
|
||||||
push(decls, node)
|
decls[] = node
|
||||||
decl_count = decl_count + 1
|
decl_count = decl_count + 1
|
||||||
if (tok.kind == ",") advance()
|
if (tok.kind == ",") advance()
|
||||||
else break
|
else break
|
||||||
@@ -1142,7 +1142,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
_control_depth = _control_depth + 1
|
_control_depth = _control_depth + 1
|
||||||
_expecting_body = true
|
_expecting_body = true
|
||||||
body = parse_statement()
|
body = parse_statement()
|
||||||
if (body != null) push(then_stmts, body)
|
if (body != null) then_stmts[] = body
|
||||||
else_ifs = []
|
else_ifs = []
|
||||||
node.list = else_ifs
|
node.list = else_ifs
|
||||||
if (tok.kind == "else") {
|
if (tok.kind == "else") {
|
||||||
@@ -1151,7 +1151,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
_control_depth = saved_cd
|
_control_depth = saved_cd
|
||||||
_control_type = saved_ct
|
_control_type = saved_ct
|
||||||
elif = parse_statement()
|
elif = parse_statement()
|
||||||
if (elif != null) push(else_ifs, elif)
|
if (elif != null) else_ifs[] = elif
|
||||||
ast_node_end(node)
|
ast_node_end(node)
|
||||||
return node
|
return node
|
||||||
} else {
|
} else {
|
||||||
@@ -1159,7 +1159,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
node.else = else_stmts
|
node.else = else_stmts
|
||||||
_expecting_body = true
|
_expecting_body = true
|
||||||
body = parse_statement()
|
body = parse_statement()
|
||||||
if (body != null) push(else_stmts, body)
|
if (body != null) else_stmts[] = body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_control_depth = saved_cd
|
_control_depth = saved_cd
|
||||||
@@ -1185,7 +1185,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
_control_depth = _control_depth + 1
|
_control_depth = _control_depth + 1
|
||||||
_expecting_body = true
|
_expecting_body = true
|
||||||
body = parse_statement()
|
body = parse_statement()
|
||||||
if (body != null) push(stmts, body)
|
if (body != null) stmts[] = body
|
||||||
_control_depth = saved_cd
|
_control_depth = saved_cd
|
||||||
_control_type = saved_ct
|
_control_type = saved_ct
|
||||||
ast_node_end(node)
|
ast_node_end(node)
|
||||||
@@ -1203,7 +1203,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
_control_depth = _control_depth + 1
|
_control_depth = _control_depth + 1
|
||||||
_expecting_body = true
|
_expecting_body = true
|
||||||
body = parse_statement()
|
body = parse_statement()
|
||||||
if (body != null) push(stmts, body)
|
if (body != null) stmts[] = body
|
||||||
_control_depth = saved_cd
|
_control_depth = saved_cd
|
||||||
_control_type = saved_ct
|
_control_type = saved_ct
|
||||||
if (tok.kind == "while") advance()
|
if (tok.kind == "while") advance()
|
||||||
@@ -1256,7 +1256,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
_control_depth = _control_depth + 1
|
_control_depth = _control_depth + 1
|
||||||
_expecting_body = true
|
_expecting_body = true
|
||||||
body = parse_statement()
|
body = parse_statement()
|
||||||
if (body != null) push(stmts, body)
|
if (body != null) stmts[] = body
|
||||||
_control_depth = saved_cd
|
_control_depth = saved_cd
|
||||||
_control_type = saved_ct
|
_control_type = saved_ct
|
||||||
ast_node_end(node)
|
ast_node_end(node)
|
||||||
@@ -1402,9 +1402,9 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
stmt = parse_statement()
|
stmt = parse_statement()
|
||||||
if (stmt != null) {
|
if (stmt != null) {
|
||||||
if (stmt.kind == "function") {
|
if (stmt.kind == "function") {
|
||||||
push(functions, stmt)
|
functions[] = stmt
|
||||||
} else {
|
} else {
|
||||||
push(statements, stmt)
|
statements[] = stmt
|
||||||
}
|
}
|
||||||
} else if (cursor == before) {
|
} else if (cursor == before) {
|
||||||
sync_to_statement()
|
sync_to_statement()
|
||||||
@@ -1420,12 +1420,13 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
var sem_errors = []
|
var sem_errors = []
|
||||||
var scopes_array = []
|
var scopes_array = []
|
||||||
var intrinsics = []
|
var intrinsics = []
|
||||||
|
var hoisted_fn_refs = []
|
||||||
|
|
||||||
var sem_error = function(node, msg) {
|
var sem_error = function(node, msg) {
|
||||||
var err = {message: msg}
|
var err = {message: msg}
|
||||||
if (node.from_row != null) err.line = node.from_row + 1
|
if (node.from_row != null) err.line = node.from_row + 1
|
||||||
if (node.from_column != null) err.column = node.from_column + 1
|
if (node.from_column != null) err.column = node.from_column + 1
|
||||||
push(sem_errors, err)
|
sem_errors[] = err
|
||||||
}
|
}
|
||||||
|
|
||||||
var make_scope = function(parent, fn_nr, opts) {
|
var make_scope = function(parent, fn_nr, opts) {
|
||||||
@@ -1441,14 +1442,17 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var sem_add_var = function(scope, name, make_opts) {
|
var sem_add_var = function(scope, name, make_opts) {
|
||||||
push(scope.vars, {
|
var entry = {
|
||||||
name: name,
|
name: name,
|
||||||
is_const: make_opts.is_const == true,
|
is_const: make_opts.is_const == true,
|
||||||
make: make_opts.make,
|
make: make_opts.make,
|
||||||
function_nr: make_opts.fn_nr,
|
function_nr: make_opts.fn_nr,
|
||||||
nr_uses: 0,
|
nr_uses: 0,
|
||||||
closure: 0
|
closure: 0
|
||||||
})
|
}
|
||||||
|
if (make_opts.reached == false) entry.reached = false
|
||||||
|
if (make_opts.decl_line != null) entry.decl_line = make_opts.decl_line
|
||||||
|
scope.vars[] = entry
|
||||||
}
|
}
|
||||||
|
|
||||||
var sem_lookup_var = function(scope, name) {
|
var sem_lookup_var = function(scope, name) {
|
||||||
@@ -1499,7 +1503,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var sem_add_intrinsic = function(name) {
|
var sem_add_intrinsic = function(name) {
|
||||||
if (find(intrinsics, name) == null) push(intrinsics, name)
|
if (find(intrinsics, name) == null) intrinsics[] = name
|
||||||
}
|
}
|
||||||
|
|
||||||
var functino_names = {
|
var functino_names = {
|
||||||
@@ -1567,39 +1571,17 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
var sem_check_expr = null
|
var sem_check_expr = null
|
||||||
var sem_check_stmt = null
|
var sem_check_stmt = null
|
||||||
|
|
||||||
var sem_predeclare_vars = function(scope, stmts) {
|
var sem_predeclare_fns = function(scope, stmts) {
|
||||||
var i = 0
|
var i = 0
|
||||||
var stmt = null
|
var stmt = null
|
||||||
var kind = null
|
|
||||||
var name = null
|
var name = null
|
||||||
var item = null
|
|
||||||
var ik = null
|
|
||||||
var j = 0
|
|
||||||
while (i < length(stmts)) {
|
while (i < length(stmts)) {
|
||||||
stmt = stmts[i]
|
stmt = stmts[i]
|
||||||
kind = stmt.kind
|
if (stmt.kind == "function") {
|
||||||
if (kind == "function") {
|
|
||||||
name = stmt.name
|
name = stmt.name
|
||||||
if (name != null && sem_find_var(scope, name) == null) {
|
if (name != null && sem_find_var(scope, name) == null) {
|
||||||
sem_add_var(scope, name, {make: "function", fn_nr: scope.function_nr})
|
sem_add_var(scope, name, {make: "function", fn_nr: scope.function_nr,
|
||||||
}
|
decl_line: stmt.from_row != null ? stmt.from_row + 1 : null, reached: false})
|
||||||
} else if (kind == "var") {
|
|
||||||
name = stmt.left.name
|
|
||||||
if (name != null && sem_find_var(scope, name) == null) {
|
|
||||||
sem_add_var(scope, name, {make: "var", fn_nr: scope.function_nr})
|
|
||||||
}
|
|
||||||
} else if (kind == "var_list") {
|
|
||||||
j = 0
|
|
||||||
while (j < length(stmt.list)) {
|
|
||||||
item = stmt.list[j]
|
|
||||||
ik = item.kind
|
|
||||||
if (ik == "var") {
|
|
||||||
name = item.left.name
|
|
||||||
if (name != null && sem_find_var(scope, name) == null) {
|
|
||||||
sem_add_var(scope, name, {make: "var", fn_nr: scope.function_nr})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
j = j + 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i = i + 1
|
i = i + 1
|
||||||
@@ -1831,7 +1813,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
if (expr.statements != null) {
|
if (expr.statements != null) {
|
||||||
sem_predeclare_vars(fn_scope, expr.statements)
|
sem_predeclare_fns(fn_scope, expr.statements)
|
||||||
i = 0
|
i = 0
|
||||||
while (i < length(expr.statements)) {
|
while (i < length(expr.statements)) {
|
||||||
sem_check_stmt(fn_scope, expr.statements[i])
|
sem_check_stmt(fn_scope, expr.statements[i])
|
||||||
@@ -1846,7 +1828,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sr = sem_build_scope_record(fn_scope)
|
sr = sem_build_scope_record(fn_scope)
|
||||||
push(scopes_array, sr.rec)
|
scopes_array[] = sr.rec
|
||||||
expr.nr_slots = sr.nr_slots
|
expr.nr_slots = sr.nr_slots
|
||||||
expr.nr_close_slots = sr.nr_close
|
expr.nr_close_slots = sr.nr_close
|
||||||
return null
|
return null
|
||||||
@@ -1875,6 +1857,11 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
expr.function_nr = r.def_function_nr
|
expr.function_nr = r.def_function_nr
|
||||||
r.v.nr_uses = r.v.nr_uses + 1
|
r.v.nr_uses = r.v.nr_uses + 1
|
||||||
if (r.level > 0) r.v.closure = 1
|
if (r.level > 0) r.v.closure = 1
|
||||||
|
if (r.v.reached == false && r.v.decl_line != null && expr.from_row != null && expr.from_row + 1 < r.v.decl_line) {
|
||||||
|
hoisted_fn_refs[] = {name: name, line: expr.from_row + 1,
|
||||||
|
col: expr.from_column != null ? expr.from_column + 1 : null,
|
||||||
|
decl_line: r.v.decl_line}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
expr.level = -1
|
expr.level = -1
|
||||||
expr.intrinsic = true
|
expr.intrinsic = true
|
||||||
@@ -2088,7 +2075,14 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
enclosing = sem_find_func_scope(scope)
|
enclosing = sem_find_func_scope(scope)
|
||||||
if (enclosing != null) enclosing.has_inner_func = true
|
if (enclosing != null) enclosing.has_inner_func = true
|
||||||
name = stmt.name
|
name = stmt.name
|
||||||
if (name != null && sem_find_var(scope, name) == null) sem_add_var(scope, name, {make: "function", fn_nr: scope.function_nr})
|
if (name != null) {
|
||||||
|
existing = sem_find_var(scope, name)
|
||||||
|
if (existing != null) {
|
||||||
|
existing.reached = true
|
||||||
|
} else {
|
||||||
|
sem_add_var(scope, name, {make: "function", fn_nr: scope.function_nr})
|
||||||
|
}
|
||||||
|
}
|
||||||
fn_nr_val = stmt.function_nr
|
fn_nr_val = stmt.function_nr
|
||||||
if (fn_nr_val == null) fn_nr_val = scope.function_nr
|
if (fn_nr_val == null) fn_nr_val = scope.function_nr
|
||||||
fn_scope = make_scope(scope, fn_nr_val, {is_func: true})
|
fn_scope = make_scope(scope, fn_nr_val, {is_func: true})
|
||||||
@@ -2102,7 +2096,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
if (def_val != null) sem_check_expr(fn_scope, def_val)
|
if (def_val != null) sem_check_expr(fn_scope, def_val)
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
sem_predeclare_vars(fn_scope, stmt.statements)
|
sem_predeclare_fns(fn_scope, stmt.statements)
|
||||||
i = 0
|
i = 0
|
||||||
while (i < length(stmt.statements)) {
|
while (i < length(stmt.statements)) {
|
||||||
sem_check_stmt(fn_scope, stmt.statements[i])
|
sem_check_stmt(fn_scope, stmt.statements[i])
|
||||||
@@ -2116,7 +2110,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sr = sem_build_scope_record(fn_scope)
|
sr = sem_build_scope_record(fn_scope)
|
||||||
push(scopes_array, sr.rec)
|
scopes_array[] = sr.rec
|
||||||
stmt.nr_slots = sr.nr_slots
|
stmt.nr_slots = sr.nr_slots
|
||||||
stmt.nr_close_slots = sr.nr_close
|
stmt.nr_close_slots = sr.nr_close
|
||||||
return null
|
return null
|
||||||
@@ -2124,6 +2118,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var semantic_check = function(ast) {
|
var semantic_check = function(ast) {
|
||||||
|
hoisted_fn_refs = []
|
||||||
var global_scope = make_scope(null, 0, {is_func: true})
|
var global_scope = make_scope(null, 0, {is_func: true})
|
||||||
var i = 0
|
var i = 0
|
||||||
var stmt = null
|
var stmt = null
|
||||||
@@ -2134,7 +2129,11 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
i = 0
|
i = 0
|
||||||
while (i < length(ast.functions)) {
|
while (i < length(ast.functions)) {
|
||||||
name = ast.functions[i].name
|
name = ast.functions[i].name
|
||||||
if (name != null) sem_add_var(global_scope, name, {make: "function", fn_nr: 0})
|
if (name != null) {
|
||||||
|
sem_add_var(global_scope, name, {make: "function", fn_nr: 0,
|
||||||
|
decl_line: ast.functions[i].from_row != null ? ast.functions[i].from_row + 1 : null,
|
||||||
|
reached: false})
|
||||||
|
}
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2154,13 +2153,14 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
new_scopes = [sr.rec]
|
new_scopes = [sr.rec]
|
||||||
i = 0
|
i = 0
|
||||||
while (i < length(scopes_array)) {
|
while (i < length(scopes_array)) {
|
||||||
push(new_scopes, scopes_array[i])
|
new_scopes[] = scopes_array[i]
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
scopes_array = new_scopes
|
scopes_array = new_scopes
|
||||||
|
|
||||||
ast.scopes = scopes_array
|
ast.scopes = scopes_array
|
||||||
ast.intrinsics = intrinsics
|
ast.intrinsics = intrinsics
|
||||||
|
if (length(hoisted_fn_refs) > 0) ast._hoisted_fns = hoisted_fn_refs
|
||||||
if (length(sem_errors) > 0) {
|
if (length(sem_errors) > 0) {
|
||||||
ast.errors = sem_errors
|
ast.errors = sem_errors
|
||||||
}
|
}
|
||||||
@@ -2183,7 +2183,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
|||||||
if (ast.errors != null) {
|
if (ast.errors != null) {
|
||||||
_mi = 0
|
_mi = 0
|
||||||
while (_mi < length(errors)) {
|
while (_mi < length(errors)) {
|
||||||
push(ast.errors, errors[_mi])
|
ast.errors[] = errors[_mi]
|
||||||
_mi = _mi + 1
|
_mi = _mi + 1
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
338
plan.md
338
plan.md
@@ -1,338 +0,0 @@
|
|||||||
# Cell/QuickJS Refactoring Plan: Remove Atoms, Shapes, and Dual-Encoding
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Refactor `source/quickjs.c` to match `docs/memory.md` specification:
|
|
||||||
- Remove JSAtom system (171 references → ~41 remaining)
|
|
||||||
- Remove JSShape system (94 references) ✓
|
|
||||||
- Remove IC caches (shape-based inline caches) ✓
|
|
||||||
- Remove `is_wide_char` dual-encoding (18 locations) ✓
|
|
||||||
- Use JSValue texts directly as property keys
|
|
||||||
- Reference: `mquickjs.c` shows the target pattern
|
|
||||||
|
|
||||||
## Completed Phases
|
|
||||||
|
|
||||||
### Phase 1: Remove is_wide_char Remnants ✓
|
|
||||||
### Phase 2: Remove IC Caches ✓
|
|
||||||
### Phase 3: Remove JSShape System ✓
|
|
||||||
### Phase 4: Complete Property Access with JSValue Keys ✓
|
|
||||||
|
|
||||||
Completed:
|
|
||||||
- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_get_field
|
|
||||||
- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_put_field
|
|
||||||
- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_define_field
|
|
||||||
- Created emit_key() function that adds JSValue to cpool and emits index
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 5: Convert JSAtom to JSValue Text (IN PROGRESS)
|
|
||||||
|
|
||||||
This is the core transformation. All identifier handling moves from atoms to JSValue.
|
|
||||||
|
|
||||||
### Completed Items
|
|
||||||
|
|
||||||
**Token and Parser Infrastructure:**
|
|
||||||
- [x] Change JSToken.u.ident.atom to JSToken.u.ident.str (JSValue)
|
|
||||||
- [x] Change parse_ident() to return JSValue
|
|
||||||
- [x] Create emit_key() function (cpool-based)
|
|
||||||
- [x] Create JS_KEY_* macros for common names (lines ~279-335 in quickjs.c)
|
|
||||||
- [x] Update all token.u.ident.atom references to .str
|
|
||||||
- [x] Create keyword lookup table (js_keywords[]) with string comparison
|
|
||||||
- [x] Rewrite update_token_ident() to use js_keyword_lookup()
|
|
||||||
- [x] Rewrite is_strict_future_keyword() to use JSValue
|
|
||||||
- [x] Update token_is_pseudo_keyword() to use JSValue and js_key_equal()
|
|
||||||
|
|
||||||
**Function Declaration Parsing:**
|
|
||||||
- [x] Update js_parse_function_decl() signature to use JSValue func_name
|
|
||||||
- [x] Update js_parse_function_decl2() to use JSValue func_name throughout
|
|
||||||
- [x] Update js_parse_function_check_names() to use JSValue
|
|
||||||
- [x] Convert JS_DupAtom/JS_FreeAtom to JS_DupValue/JS_FreeValue in function parsing
|
|
||||||
|
|
||||||
**Variable Definition and Lookup:**
|
|
||||||
- [x] Update find_global_var() to use JSValue and js_key_equal()
|
|
||||||
- [x] Update find_lexical_global_var() to use JSValue
|
|
||||||
- [x] Update find_lexical_decl() to use JSValue and js_key_equal()
|
|
||||||
- [x] Update js_define_var() to use JSValue
|
|
||||||
- [x] Update js_parse_check_duplicate_parameter() to use JSValue and js_key_equal()
|
|
||||||
- [x] Update js_parse_destructuring_var() to return JSValue
|
|
||||||
- [x] Update js_parse_var() to use JSValue for variable names
|
|
||||||
|
|
||||||
**Comparison Helpers:**
|
|
||||||
- [x] Create js_key_equal_str() for comparing JSValue with C string literals
|
|
||||||
- [x] Update is_var_in_arg_scope() to use js_key_equal/js_key_equal_str
|
|
||||||
- [x] Update has_with_scope() to use js_key_equal_str
|
|
||||||
- [x] Update closure variable comparisons (cv->var_name) to use js_key_equal_str
|
|
||||||
|
|
||||||
**Property Access:**
|
|
||||||
- [x] Fix JS_GetPropertyStr to create proper JSValue keys
|
|
||||||
- [x] Fix JS_SetPropertyInternal callers to use JS_KEY_* instead of JS_ATOM_*
|
|
||||||
|
|
||||||
### JS_KEY_* Macros Added
|
|
||||||
|
|
||||||
Compile-time immediate ASCII string constants (≤7 chars):
|
|
||||||
```c
|
|
||||||
JS_KEY_empty, JS_KEY_name, JS_KEY_message, JS_KEY_stack,
|
|
||||||
JS_KEY_errors, JS_KEY_Error, JS_KEY_cause, JS_KEY_length,
|
|
||||||
JS_KEY_value, JS_KEY_get, JS_KEY_set, JS_KEY_raw,
|
|
||||||
JS_KEY_flags, JS_KEY_source, JS_KEY_exec, JS_KEY_toJSON,
|
|
||||||
JS_KEY_eval, JS_KEY_this, JS_KEY_true, JS_KEY_false,
|
|
||||||
JS_KEY_null, JS_KEY_NaN, JS_KEY_default, JS_KEY_index,
|
|
||||||
JS_KEY_input, JS_KEY_groups, JS_KEY_indices, JS_KEY_let,
|
|
||||||
JS_KEY_var, JS_KEY_new, JS_KEY_of, JS_KEY_yield,
|
|
||||||
JS_KEY_async, JS_KEY_target, JS_KEY_from, JS_KEY_meta,
|
|
||||||
JS_KEY_as, JS_KEY_with
|
|
||||||
```
|
|
||||||
|
|
||||||
Runtime macro for strings >7 chars:
|
|
||||||
```c
|
|
||||||
#define JS_KEY_STR(ctx, str) JS_NewStringLen((ctx), (str), sizeof(str) - 1)
|
|
||||||
```
|
|
||||||
|
|
||||||
Helper function for comparing JSValue with C string literals:
|
|
||||||
```c
|
|
||||||
static JS_BOOL js_key_equal_str(JSValue a, const char *str);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Remaining Work
|
|
||||||
|
|
||||||
#### 5.3 Update js_parse_property_name() ✓
|
|
||||||
- [x] Change return type from JSAtom* to JSValue*
|
|
||||||
- [x] Update all callers (js_parse_object_literal, etc.)
|
|
||||||
- [x] Updated get_lvalue(), put_lvalue(), js_parse_destructuring_element()
|
|
||||||
|
|
||||||
#### 5.4 Replace remaining emit_atom() calls with emit_key() ✓
|
|
||||||
- [x] Removed emit_atom wrapper function
|
|
||||||
- [x] Changed last emit_atom(JS_ATOM_this) to emit_key(JS_KEY_this)
|
|
||||||
|
|
||||||
#### 5.5 Update Variable Opcode Format in quickjs-opcode.h
|
|
||||||
- [ ] Change `atom` format opcodes to `key` format
|
|
||||||
- [ ] Change `atom_u8` and `atom_u16` to `key_u8` and `key_u16`
|
|
||||||
|
|
||||||
#### 5.6 Update VM Opcode Handlers ✓
|
|
||||||
These now read cpool indices and look up JSValue:
|
|
||||||
- [x] OP_check_var, OP_get_var_undef, OP_get_var
|
|
||||||
- [x] OP_put_var, OP_put_var_init, OP_put_var_strict
|
|
||||||
- [x] OP_set_name, OP_make_var_ref, OP_delete_var
|
|
||||||
- [x] OP_define_var, OP_define_func, OP_throw_error
|
|
||||||
- [x] OP_make_loc_ref, OP_make_arg_ref
|
|
||||||
- [x] OP_define_method, OP_define_method_computed
|
|
||||||
|
|
||||||
#### 5.7 Update resolve_scope_var() ✓
|
|
||||||
- [x] Changed signature to use JSValue var_name
|
|
||||||
- [x] Updated all comparisons to use js_key_equal()/js_key_equal_str()
|
|
||||||
- [x] Updated var_object_test() to use JSValue
|
|
||||||
- [x] Updated optimize_scope_make_global_ref() to use JSValue
|
|
||||||
- [x] Updated resolve_variables() callers to read from cpool
|
|
||||||
|
|
||||||
#### 5.8 Convert Remaining JS_ATOM_* Usages
|
|
||||||
Categories remaining:
|
|
||||||
- Some debug/print functions still use JSAtom
|
|
||||||
- Some function signatures not yet converted
|
|
||||||
- Will be addressed in Phase 7 cleanup
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 6: Update Bytecode Serialization ✓
|
|
||||||
|
|
||||||
### 6.1 JS_WriteObjectTag Changes ✓
|
|
||||||
- [x] Changed JS_WriteObjectTag to use bc_put_key() directly for property keys
|
|
||||||
- [x] Removed JS_ValueToAtom/bc_put_atom path (was broken anyway)
|
|
||||||
- [x] cpool values serialized via JS_WriteObjectRec()
|
|
||||||
|
|
||||||
### 6.2 JS_ReadObject Changes ✓
|
|
||||||
- [x] Changed JS_ReadObjectTag to use bc_get_key() for property keys
|
|
||||||
- [x] Uses JS_SetPropertyInternal with JSValue keys
|
|
||||||
|
|
||||||
### 6.3 Opcode Format Updates ✓
|
|
||||||
- [x] Added OP_FMT_key_u8, OP_FMT_key_u16, OP_FMT_key_label_u16 formats
|
|
||||||
- [x] Updated variable opcodes to use key formats instead of atom formats
|
|
||||||
- [x] Updated bc_byte_swap() to handle new key formats
|
|
||||||
- [x] Updated JS_WriteFunctionBytecode() to skip key format opcodes
|
|
||||||
- [x] Updated JS_ReadFunctionBytecode() to skip key format opcodes
|
|
||||||
|
|
||||||
### 6.4 Version Bump ✓
|
|
||||||
- [x] Incremented BC_VERSION from 5 to 6
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 7: Final Cleanup ✓
|
|
||||||
|
|
||||||
### 7.1 Additional Parser/Compiler Fixes ✓
|
|
||||||
- [x] Fixed TOK_IDENT case to use JSValue name, JS_DupValue, emit_key
|
|
||||||
- [x] Fixed TOK_TRY catch clause to use JSValue name
|
|
||||||
- [x] Fixed js_parse_statement_or_decl label_name to use JSValue
|
|
||||||
- [x] Fixed OP_scope_get_var "eval" check to use js_key_equal_str
|
|
||||||
- [x] Fixed js_parse_delete to use JSValue for name comparison
|
|
||||||
- [x] Fixed JSON parsing to use js_key_from_string for property names
|
|
||||||
- [x] Added js_key_from_string() helper function
|
|
||||||
- [x] Added JS_KEY__ret_, JS_KEY__eval_, JS_KEY__var_ for internal names
|
|
||||||
- [x] Updated add_closure_var, get_closure_var2, get_closure_var to use JSValue var_name
|
|
||||||
- [x] Updated set_closure_from_var to use JS_DupValue
|
|
||||||
- [x] Updated add_closure_variables to use JS_DupValue
|
|
||||||
- [x] Updated instantiate_hoisted_definitions to use fd_cpool_add for keys
|
|
||||||
- [x] Updated resolve_variables to use js_key_equal and fd_cpool_add
|
|
||||||
|
|
||||||
### 7.1.1 Property Access and Runtime Fixes ✓
|
|
||||||
- [x] Fixed JS_GetPropertyValue to use js_key_from_string instead of JS_ValueToAtom
|
|
||||||
- [x] Fixed JS_GetPropertyKey to use js_key_from_string for string keys
|
|
||||||
- [x] Fixed JS_SetPropertyKey to use js_key_from_string for string keys
|
|
||||||
- [x] Fixed JS_HasPropertyKey to use js_key_from_string for string keys
|
|
||||||
- [x] Fixed JS_DeletePropertyKey to use js_key_from_string for string keys
|
|
||||||
- [x] Updated JS_HasProperty signature to take JSValue prop
|
|
||||||
- [x] Fixed OP_get_ref_value handler to use JSValue key
|
|
||||||
- [x] Fixed OP_put_ref_value handler to use JSValue key
|
|
||||||
- [x] Updated free_func_def to use JS_FreeValue for JSValue fields
|
|
||||||
|
|
||||||
### 7.2 Remove JSAtom Type and Functions ✓
|
|
||||||
- [x] Removed most JS_ATOM_* constants (kept JS_ATOM_NULL, JS_ATOM_END for BC compat)
|
|
||||||
- [x] JS_NewAtomString now returns JSValue using js_key_new
|
|
||||||
- [x] JS_FreeAtom, JS_DupAtom are stubs (no-op for backward compat)
|
|
||||||
- [x] JS_AtomToValue, JS_ValueToAtom are stubs (minimal BC compat)
|
|
||||||
- [x] Replaced JS_ATOM_* usages with JS_KEY_* or JS_GetPropertyStr
|
|
||||||
|
|
||||||
### 7.3 Additional Runtime Fixes ✓
|
|
||||||
- [x] Fixed free_function_bytecode to use JS_FreeValueRT for JSValue fields
|
|
||||||
- [x] Fixed JS_SetPropertyFunctionList to use JSValue keys via find_key()
|
|
||||||
- [x] Fixed JS_InstantiateFunctionListItem to use JSValue keys
|
|
||||||
- [x] Fixed internalize_json_property to use JSValue names
|
|
||||||
- [x] Fixed emit_break and push_break_entry to use JSValue label_name
|
|
||||||
- [x] Implemented JS_Invoke to use JSValue method key
|
|
||||||
|
|
||||||
### 7.4 Remaining Stubs (kept for bytecode backward compatibility)
|
|
||||||
- JSAtom typedef (uint32_t) - used in BC serialization
|
|
||||||
- JS_ATOM_NULL, JS_ATOM_END - bytecode format markers
|
|
||||||
- JS_FreeAtom, JS_DupAtom - no-op stubs
|
|
||||||
- JS_FreeAtomRT, JS_DupAtomRT - no-op stubs
|
|
||||||
- Legacy BC reader (idx_to_atom array) - for reading old bytecode
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Current Build Status
|
|
||||||
|
|
||||||
**Build: SUCCEEDS** with warnings (unused variables, labels)
|
|
||||||
|
|
||||||
**Statistics:**
|
|
||||||
- JS_ATOM_* usages: Minimal (only BC serialization compat)
|
|
||||||
- Property access uses JS_KEY_* macros or JS_GetPropertyStr
|
|
||||||
- BC_VERSION: 6 (updated for new key-based format)
|
|
||||||
|
|
||||||
**What Works:**
|
|
||||||
- All property access via JSValue keys
|
|
||||||
- Keyword detection via string comparison
|
|
||||||
- Function declaration parsing with JSValue names
|
|
||||||
- Variable definition with JSValue names
|
|
||||||
- Closure variable tracking with JSValue names
|
|
||||||
- VM opcode handlers read cpool indices and look up JSValue
|
|
||||||
- resolve_scope_var() uses JSValue throughout
|
|
||||||
- js_parse_property_name() returns JSValue
|
|
||||||
- Bytecode serialization uses bc_put_key/bc_get_key for property keys
|
|
||||||
- Variable opcodes use key format (cpool indices)
|
|
||||||
- JSON parsing uses JSValue keys
|
|
||||||
- Internal variable names use JS_KEY__ret_, JS_KEY__eval_, JS_KEY__var_
|
|
||||||
- JS_SetPropertyFunctionList uses JSValue keys
|
|
||||||
- JS_Invoke uses JSValue method keys
|
|
||||||
- break/continue labels use JSValue
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 8: Migrate to New Tagging System (IN PROGRESS)
|
|
||||||
|
|
||||||
**Problem**: `JS_VALUE_GET_TAG` returns `JS_TAG_PTR` for all pointers, but ~200 places check for obsolete tags like `JS_TAG_OBJECT`, `JS_TAG_STRING`, `JS_TAG_FUNCTION`, etc., which are never returned. This causes crashes.
|
|
||||||
|
|
||||||
**Target Design** (from memory.md):
|
|
||||||
- JSValue tags: Only `JS_TAG_INT`, `JS_TAG_PTR`, `JS_TAG_SHORT_FLOAT`, `JS_TAG_SPECIAL`
|
|
||||||
- Pointer types determined by `objhdr_t` header at offset 8 in heap objects
|
|
||||||
- mist_obj_type: `OBJ_ARRAY(0)`, `OBJ_BLOB(1)`, `OBJ_TEXT(2)`, `OBJ_RECORD(3)`, `OBJ_FUNCTION(4)`, etc.
|
|
||||||
|
|
||||||
### 8.1 Unified Heap Object Layout ✓
|
|
||||||
- [x] Updated mist_text structure to have objhdr_t at offset 8:
|
|
||||||
```c
|
|
||||||
typedef struct mist_text {
|
|
||||||
JSRefCountHeader _dummy_header; /* unused, for offset alignment */
|
|
||||||
uint32_t _pad; /* padding to align objhdr_t to offset 8 */
|
|
||||||
objhdr_t hdr; /* NOW at offset 8, like JSString */
|
|
||||||
word_t length;
|
|
||||||
word_t packed[];
|
|
||||||
} mist_text;
|
|
||||||
```
|
|
||||||
- [x] JSString already has objhdr_t at offset 8
|
|
||||||
|
|
||||||
### 8.2 Type-Checking Helper Functions ✓
|
|
||||||
Added lowercase internal helpers (to avoid conflict with quickjs.h declarations):
|
|
||||||
```c
|
|
||||||
static inline JS_BOOL js_is_gc_object(JSValue v) { return JS_IsPtr(v); }
|
|
||||||
static inline JSGCObjectTypeEnum js_get_gc_type(JSValue v) {
|
|
||||||
return ((JSGCObjectHeader *)JS_VALUE_GET_PTR(v))->gc_obj_type;
|
|
||||||
}
|
|
||||||
static inline JS_BOOL js_is_record(JSValue v) {
|
|
||||||
if (!JS_IsPtr(v)) return FALSE;
|
|
||||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_RECORD;
|
|
||||||
}
|
|
||||||
static inline JS_BOOL js_is_array(JSValue v) {
|
|
||||||
if (!JS_IsPtr(v)) return FALSE;
|
|
||||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_ARRAY;
|
|
||||||
}
|
|
||||||
static inline JS_BOOL js_is_function(JSValue v) {
|
|
||||||
if (!JS_IsPtr(v)) return FALSE;
|
|
||||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_FUNCTION;
|
|
||||||
}
|
|
||||||
static inline JS_BOOL js_is_object(JSValue v) {
|
|
||||||
if (!JS_IsPtr(v)) return FALSE;
|
|
||||||
JSGCObjectTypeEnum t = js_get_gc_type(v);
|
|
||||||
return t == JS_GC_OBJ_TYPE_RECORD || t == JS_GC_OBJ_TYPE_ARRAY;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8.3 Updated Core Functions ✓
|
|
||||||
- [x] Updated JS_IsString to read objhdr_t from offset 8
|
|
||||||
- [x] Updated js_key_hash to read objhdr_t from offset 8
|
|
||||||
- [x] Updated js_key_equal to read objhdr_t from offset 8
|
|
||||||
- [x] Updated __JS_FreeValueRT to use objhdr_type for type dispatch
|
|
||||||
- [x] Updated JS_MarkValue, JS_MarkValueEdgeEx for GC
|
|
||||||
- [x] Added JS_SetPropertyValue function
|
|
||||||
- [x] Changed quickjs.h JS_IsFunction/JS_IsObject from inline to extern declarations
|
|
||||||
|
|
||||||
### 8.4 Tag Check Migration (PARTIAL)
|
|
||||||
Updated some critical tag checks:
|
|
||||||
- [x] Some JS_TAG_OBJECT checks → js_is_object() or js_is_record()
|
|
||||||
- [ ] Many more JS_TAG_OBJECT checks remain (~200 total)
|
|
||||||
- [ ] JS_TAG_FUNCTION checks → js_is_function()
|
|
||||||
- [ ] JS_TAG_STRING checks (some already use JS_IsString)
|
|
||||||
|
|
||||||
### 8.5 Remaining Work
|
|
||||||
- [ ] Fix ASAN memory corruption error (attempting free on address not malloc'd)
|
|
||||||
- Crash occurs in js_def_realloc during js_realloc_array
|
|
||||||
- Address is 112 bytes inside a JSFunctionDef allocation
|
|
||||||
- [ ] Complete remaining ~200 tag check migrations
|
|
||||||
- [ ] Add mist_hdr to JSFunction (optional, gc_obj_type already works)
|
|
||||||
- [ ] Remove obsolete tag definitions from quickjs.h:
|
|
||||||
- JS_TAG_STRING = -8
|
|
||||||
- JS_TAG_ARRAY = -6
|
|
||||||
- JS_TAG_FUNCTION = -5
|
|
||||||
- JS_TAG_FUNCTION_BYTECODE = -2
|
|
||||||
- JS_TAG_OBJECT = -1
|
|
||||||
|
|
||||||
### Current Status
|
|
||||||
|
|
||||||
**Build: SUCCEEDS** with warnings
|
|
||||||
|
|
||||||
**Runtime: CRASHES** with ASAN error:
|
|
||||||
```
|
|
||||||
==16122==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed
|
|
||||||
```
|
|
||||||
The crash occurs during test execution in `js_def_realloc` called from `js_realloc_array`.
|
|
||||||
Root cause not yet identified - likely a pointer being passed to realloc that wasn't allocated with malloc.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- JSVarDef.var_name is JSValue
|
|
||||||
- JSClosureVar.var_name is JSValue
|
|
||||||
- JSGlobalVar.var_name is JSValue
|
|
||||||
- JSFunctionDef.func_name is JSValue
|
|
||||||
- BlockEnv.label_name is JSValue
|
|
||||||
- OP_get_field/put_field/define_field already use cpool index format
|
|
||||||
- JSRecord with open addressing is fully implemented
|
|
||||||
- js_key_hash and js_key_equal work with both immediate and heap text
|
|
||||||
- js_key_equal_str enables comparison with C string literals for internal names
|
|
||||||
@@ -135,7 +135,6 @@ JSC_SCALL(file_listfiles,
|
|||||||
JSValue arr = JS_NewArray(js);
|
JSValue arr = JS_NewArray(js);
|
||||||
struct listfiles_ctx ctx = { js, arr, 0 };
|
struct listfiles_ctx ctx = { js, arr, 0 };
|
||||||
if (pd_file->listfiles(str, listfiles_cb, &ctx, showhidden) != 0) {
|
if (pd_file->listfiles(str, listfiles_cb, &ctx, showhidden) != 0) {
|
||||||
JS_FreeValue(js, arr);
|
|
||||||
ret = JS_NULL;
|
ret = JS_NULL;
|
||||||
} else {
|
} else {
|
||||||
ret = arr;
|
ret = arr;
|
||||||
|
|||||||
@@ -112,9 +112,6 @@ static void encode_js_object(json_encoder *enc, JSContext *js, JSValue obj) {
|
|||||||
JSValue val = JS_GetProperty(js, obj, props[i].atom);
|
JSValue val = JS_GetProperty(js, obj, props[i].atom);
|
||||||
enc->addTableMember(enc, key, strlen(key));
|
enc->addTableMember(enc, key, strlen(key));
|
||||||
encode_js_value(enc, js, val);
|
encode_js_value(enc, js, val);
|
||||||
JS_FreeValue(js, val);
|
|
||||||
JS_FreeCString(js, key);
|
|
||||||
JS_FreeAtom(js, props[i].atom);
|
|
||||||
}
|
}
|
||||||
js_free_rt(props);
|
js_free_rt(props);
|
||||||
}
|
}
|
||||||
@@ -125,12 +122,10 @@ static void encode_js_array(json_encoder *enc, JSContext *js, JSValue arr) {
|
|||||||
enc->startArray(enc);
|
enc->startArray(enc);
|
||||||
JSValue lenVal = JS_GetPropertyStr(js, arr, "length");
|
JSValue lenVal = JS_GetPropertyStr(js, arr, "length");
|
||||||
int len = (int)js2number(js, lenVal);
|
int len = (int)js2number(js, lenVal);
|
||||||
JS_FreeValue(js, lenVal);
|
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
enc->addArrayMember(enc);
|
enc->addArrayMember(enc);
|
||||||
JSValue val = JS_GetPropertyNumber(js, arr, i);
|
JSValue val = JS_GetPropertyNumber(js, arr, i);
|
||||||
encode_js_value(enc, js, val);
|
encode_js_value(enc, js, val);
|
||||||
JS_FreeValue(js, val);
|
|
||||||
}
|
}
|
||||||
enc->endArray(enc);
|
enc->endArray(enc);
|
||||||
}
|
}
|
||||||
@@ -149,7 +144,6 @@ static void encode_js_value(json_encoder *enc, JSContext *js, JSValue val) {
|
|||||||
size_t len;
|
size_t len;
|
||||||
const char *str = JS_ToCStringLen(js, &len, val);
|
const char *str = JS_ToCStringLen(js, &len, val);
|
||||||
enc->writeString(enc, str, len);
|
enc->writeString(enc, str, len);
|
||||||
JS_FreeCString(js, str);
|
|
||||||
} else if (JS_IsArray(val)) {
|
} else if (JS_IsArray(val)) {
|
||||||
encode_js_array(enc, js, val);
|
encode_js_array(enc, js, val);
|
||||||
} else if (JS_IsObject(val)) {
|
} else if (JS_IsObject(val)) {
|
||||||
|
|||||||
@@ -30,9 +30,6 @@ static void add_score_cb(PDScore *score, const char *errorMessage) {
|
|||||||
args[0] = score_to_js(g_scoreboard_js, score);
|
args[0] = score_to_js(g_scoreboard_js, score);
|
||||||
args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
|
args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
|
||||||
JSValue ret = JS_Call(g_scoreboard_js, g_add_score_callback, JS_NULL, 2, args);
|
JSValue ret = JS_Call(g_scoreboard_js, g_add_score_callback, JS_NULL, 2, args);
|
||||||
JS_FreeValue(g_scoreboard_js, ret);
|
|
||||||
JS_FreeValue(g_scoreboard_js, args[0]);
|
|
||||||
JS_FreeValue(g_scoreboard_js, args[1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void personal_best_cb(PDScore *score, const char *errorMessage) {
|
static void personal_best_cb(PDScore *score, const char *errorMessage) {
|
||||||
@@ -41,9 +38,6 @@ static void personal_best_cb(PDScore *score, const char *errorMessage) {
|
|||||||
args[0] = score_to_js(g_scoreboard_js, score);
|
args[0] = score_to_js(g_scoreboard_js, score);
|
||||||
args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
|
args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
|
||||||
JSValue ret = JS_Call(g_scoreboard_js, g_personal_best_callback, JS_NULL, 2, args);
|
JSValue ret = JS_Call(g_scoreboard_js, g_personal_best_callback, JS_NULL, 2, args);
|
||||||
JS_FreeValue(g_scoreboard_js, ret);
|
|
||||||
JS_FreeValue(g_scoreboard_js, args[0]);
|
|
||||||
JS_FreeValue(g_scoreboard_js, args[1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void boards_list_cb(PDBoardsList *boards, const char *errorMessage) {
|
static void boards_list_cb(PDBoardsList *boards, const char *errorMessage) {
|
||||||
@@ -65,9 +59,6 @@ static void boards_list_cb(PDBoardsList *boards, const char *errorMessage) {
|
|||||||
}
|
}
|
||||||
args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
|
args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
|
||||||
JSValue ret = JS_Call(g_scoreboard_js, g_boards_list_callback, JS_NULL, 2, args);
|
JSValue ret = JS_Call(g_scoreboard_js, g_boards_list_callback, JS_NULL, 2, args);
|
||||||
JS_FreeValue(g_scoreboard_js, ret);
|
|
||||||
JS_FreeValue(g_scoreboard_js, args[0]);
|
|
||||||
JS_FreeValue(g_scoreboard_js, args[1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void scores_cb(PDScoresList *scores, const char *errorMessage) {
|
static void scores_cb(PDScoresList *scores, const char *errorMessage) {
|
||||||
@@ -92,9 +83,6 @@ static void scores_cb(PDScoresList *scores, const char *errorMessage) {
|
|||||||
}
|
}
|
||||||
args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
|
args[1] = errorMessage ? JS_NewString(g_scoreboard_js, errorMessage) : JS_NULL;
|
||||||
JSValue ret = JS_Call(g_scoreboard_js, g_scores_callback, JS_NULL, 2, args);
|
JSValue ret = JS_Call(g_scoreboard_js, g_scores_callback, JS_NULL, 2, args);
|
||||||
JS_FreeValue(g_scoreboard_js, ret);
|
|
||||||
JS_FreeValue(g_scoreboard_js, args[0]);
|
|
||||||
JS_FreeValue(g_scoreboard_js, args[1]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- API Functions ---
|
// --- API Functions ---
|
||||||
@@ -104,8 +92,7 @@ JSC_SCALL(scoreboards_addScore,
|
|||||||
uint32_t value = (uint32_t)js2number(js, argv[1]);
|
uint32_t value = (uint32_t)js2number(js, argv[1]);
|
||||||
if (argc > 2 && JS_IsFunction(argv[2])) {
|
if (argc > 2 && JS_IsFunction(argv[2])) {
|
||||||
g_scoreboard_js = js;
|
g_scoreboard_js = js;
|
||||||
JS_FreeValue(js, g_add_score_callback);
|
g_add_score_callback = argv[2];
|
||||||
g_add_score_callback = JS_DupValue(js, argv[2]);
|
|
||||||
}
|
}
|
||||||
ret = JS_NewBool(js, pd_scoreboards->addScore(str, value, add_score_cb));
|
ret = JS_NewBool(js, pd_scoreboards->addScore(str, value, add_score_cb));
|
||||||
)
|
)
|
||||||
@@ -114,8 +101,7 @@ JSC_SCALL(scoreboards_getPersonalBest,
|
|||||||
if (!pd_scoreboards) return JS_RaiseDisrupt(js, "scoreboards not initialized");
|
if (!pd_scoreboards) return JS_RaiseDisrupt(js, "scoreboards not initialized");
|
||||||
if (argc > 1 && JS_IsFunction(argv[1])) {
|
if (argc > 1 && JS_IsFunction(argv[1])) {
|
||||||
g_scoreboard_js = js;
|
g_scoreboard_js = js;
|
||||||
JS_FreeValue(js, g_personal_best_callback);
|
g_personal_best_callback = argv[1];
|
||||||
g_personal_best_callback = JS_DupValue(js, argv[1]);
|
|
||||||
}
|
}
|
||||||
ret = JS_NewBool(js, pd_scoreboards->getPersonalBest(str, personal_best_cb));
|
ret = JS_NewBool(js, pd_scoreboards->getPersonalBest(str, personal_best_cb));
|
||||||
)
|
)
|
||||||
@@ -131,8 +117,7 @@ JSC_CCALL(scoreboards_getScoreboards,
|
|||||||
if (!pd_scoreboards) return JS_RaiseDisrupt(js, "scoreboards not initialized");
|
if (!pd_scoreboards) return JS_RaiseDisrupt(js, "scoreboards not initialized");
|
||||||
if (argc > 0 && JS_IsFunction(argv[0])) {
|
if (argc > 0 && JS_IsFunction(argv[0])) {
|
||||||
g_scoreboard_js = js;
|
g_scoreboard_js = js;
|
||||||
JS_FreeValue(js, g_boards_list_callback);
|
g_boards_list_callback = argv[0];
|
||||||
g_boards_list_callback = JS_DupValue(js, argv[0]);
|
|
||||||
}
|
}
|
||||||
return JS_NewBool(js, pd_scoreboards->getScoreboards(boards_list_cb));
|
return JS_NewBool(js, pd_scoreboards->getScoreboards(boards_list_cb));
|
||||||
)
|
)
|
||||||
@@ -147,8 +132,7 @@ JSC_SCALL(scoreboards_getScores,
|
|||||||
if (!pd_scoreboards) return JS_RaiseDisrupt(js, "scoreboards not initialized");
|
if (!pd_scoreboards) return JS_RaiseDisrupt(js, "scoreboards not initialized");
|
||||||
if (argc > 1 && JS_IsFunction(argv[1])) {
|
if (argc > 1 && JS_IsFunction(argv[1])) {
|
||||||
g_scoreboard_js = js;
|
g_scoreboard_js = js;
|
||||||
JS_FreeValue(js, g_scores_callback);
|
g_scores_callback = argv[1];
|
||||||
g_scores_callback = JS_DupValue(js, argv[1]);
|
|
||||||
}
|
}
|
||||||
ret = JS_NewBool(js, pd_scoreboards->getScores(str, scores_cb));
|
ret = JS_NewBool(js, pd_scoreboards->getScores(str, scores_cb));
|
||||||
)
|
)
|
||||||
|
|||||||
124
probe.ce
Normal file
124
probe.ce
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
// cell probe - Query a running probe server
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// cell probe List all targets and probes
|
||||||
|
// cell probe <target> <name> Query a probe
|
||||||
|
// cell probe <target> <name> k=v ... Query with arguments
|
||||||
|
// cell probe --port=8080 game state Use a different port
|
||||||
|
|
||||||
|
var http = use('http')
|
||||||
|
var json = use('json')
|
||||||
|
|
||||||
|
var port = 9000
|
||||||
|
var base = null
|
||||||
|
|
||||||
|
function print_targets(targets) {
|
||||||
|
var keys = array(targets)
|
||||||
|
var j = 0
|
||||||
|
var p = 0
|
||||||
|
var probes = null
|
||||||
|
while (j < length(keys)) {
|
||||||
|
probes = targets[keys[j]]
|
||||||
|
log.console(keys[j])
|
||||||
|
p = 0
|
||||||
|
while (p < length(probes)) {
|
||||||
|
log.console(" " + probes[p])
|
||||||
|
p = p + 1
|
||||||
|
}
|
||||||
|
j = j + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
var target = null
|
||||||
|
var name = null
|
||||||
|
var probe_args = {}
|
||||||
|
var i = 0
|
||||||
|
var eq = null
|
||||||
|
var k = null
|
||||||
|
var v = null
|
||||||
|
var n = null
|
||||||
|
|
||||||
|
for (i = 0; i < length(args); i++) {
|
||||||
|
if (args[i] == '--help' || args[i] == '-h') {
|
||||||
|
log.console("Usage: cell probe [target] [name] [key=value ...]")
|
||||||
|
log.console("")
|
||||||
|
log.console(" cell probe List all targets and probes")
|
||||||
|
log.console(" cell probe game state Query game/state")
|
||||||
|
log.console(" cell probe game entity id=1 Query with arguments")
|
||||||
|
log.console("")
|
||||||
|
log.console("Options:")
|
||||||
|
log.console(" --port=N Connect to port N (default 9000)")
|
||||||
|
return
|
||||||
|
} else if (starts_with(args[i], '--port=')) {
|
||||||
|
port = number(text(args[i], 7))
|
||||||
|
} else if (target == null) {
|
||||||
|
target = args[i]
|
||||||
|
} else if (name == null) {
|
||||||
|
name = args[i]
|
||||||
|
} else {
|
||||||
|
eq = search(args[i], "=")
|
||||||
|
if (eq != null) {
|
||||||
|
k = text(args[i], 0, eq)
|
||||||
|
v = text(args[i], eq + 1)
|
||||||
|
n = number(v)
|
||||||
|
if (n != null) {
|
||||||
|
v = n
|
||||||
|
} else if (v == "true") {
|
||||||
|
v = true
|
||||||
|
} else if (v == "false") {
|
||||||
|
v = false
|
||||||
|
} else if (v == "null") {
|
||||||
|
v = null
|
||||||
|
}
|
||||||
|
probe_args[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base = "http://127.0.0.1:" + text(port)
|
||||||
|
|
||||||
|
var resp = null
|
||||||
|
var body = null
|
||||||
|
var data = null
|
||||||
|
|
||||||
|
if (target == null) {
|
||||||
|
resp = http.request("GET", base + "/discover", null, null)
|
||||||
|
} else {
|
||||||
|
body = {target: target, name: name}
|
||||||
|
if (length(array(probe_args)) > 0) body.args = probe_args
|
||||||
|
resp = http.request("POST", base + "/probe",
|
||||||
|
{"content-type": "application/json"}, json.encode(body, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp == null) {
|
||||||
|
log.error("could not connect to probe server on port " + text(port))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var _parse = function() {
|
||||||
|
data = json.decode(resp.body)
|
||||||
|
} disruption {
|
||||||
|
data = null
|
||||||
|
}
|
||||||
|
_parse()
|
||||||
|
|
||||||
|
if (data == null) {
|
||||||
|
log.error("invalid response from server")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.ok) {
|
||||||
|
log.error(data.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target == null) {
|
||||||
|
print_targets(data.targets)
|
||||||
|
} else {
|
||||||
|
log.console(json.encode(data.result, 2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run()
|
||||||
|
|
||||||
|
$stop()
|
||||||
124
probe.cm
Normal file
124
probe.cm
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
var http = use('http')
|
||||||
|
var json = use('json')
|
||||||
|
|
||||||
|
var registry = {}
|
||||||
|
var server_fd = null
|
||||||
|
var port = 9000
|
||||||
|
|
||||||
|
function handle_request(req) {
|
||||||
|
var result = null
|
||||||
|
var _try = null
|
||||||
|
|
||||||
|
if (req.method == "GET" && req.path == "/discover") {
|
||||||
|
result = discover()
|
||||||
|
http.respond(req._conn, 200, {"content-type": "application/json"},
|
||||||
|
json.encode(result))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method == "POST" && req.path == "/probe") {
|
||||||
|
_try = function() {
|
||||||
|
result = handle_probe(req)
|
||||||
|
} disruption {
|
||||||
|
result = {ok: false, error: "probe failed"}
|
||||||
|
}
|
||||||
|
_try()
|
||||||
|
http.respond(req._conn, 200, {"content-type": "application/json"},
|
||||||
|
json.encode(result))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method == "POST" && req.path == "/snapshot") {
|
||||||
|
_try = function() {
|
||||||
|
result = handle_snapshot(req)
|
||||||
|
} disruption {
|
||||||
|
result = {ok: false, error: "snapshot failed"}
|
||||||
|
}
|
||||||
|
_try()
|
||||||
|
http.respond(req._conn, 200, {"content-type": "application/json"},
|
||||||
|
json.encode(result))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.respond(req._conn, 404, {"content-type": "application/json"},
|
||||||
|
json.encode({ok: false, error: "not found"}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function discover() {
|
||||||
|
var targets = {}
|
||||||
|
var target_keys = array(registry)
|
||||||
|
var i = 0
|
||||||
|
while (i < length(target_keys)) {
|
||||||
|
targets[target_keys[i]] = array(registry[target_keys[i]])
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return {ok: true, targets: targets}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_probe(req) {
|
||||||
|
var body = json.decode(req.body)
|
||||||
|
var target = body.target
|
||||||
|
var name = body.name
|
||||||
|
var args = body.args
|
||||||
|
|
||||||
|
if (target == null || name == null) {
|
||||||
|
return {ok: false, error: "missing target or name"}
|
||||||
|
}
|
||||||
|
|
||||||
|
var target_probes = registry[target]
|
||||||
|
if (target_probes == null) {
|
||||||
|
return {ok: false, error: "unknown target: " + target}
|
||||||
|
}
|
||||||
|
|
||||||
|
var probe_fn = target_probes[name]
|
||||||
|
if (probe_fn == null) {
|
||||||
|
return {ok: false, error: "unknown probe: " + target + "/" + name}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args == null) args = {}
|
||||||
|
var result = probe_fn(args)
|
||||||
|
return {ok: true, result: result}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle_snapshot(req) {
|
||||||
|
var body = json.decode(req.body)
|
||||||
|
var probes = body.probes
|
||||||
|
if (probes == null) {
|
||||||
|
return {ok: false, error: "missing probes array"}
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = {}
|
||||||
|
var i = 0
|
||||||
|
var p = null
|
||||||
|
var target_probes = null
|
||||||
|
var probe_fn = null
|
||||||
|
var key = null
|
||||||
|
while (i < length(probes)) {
|
||||||
|
p = probes[i]
|
||||||
|
key = p.target + "/" + p.name
|
||||||
|
target_probes = registry[p.target]
|
||||||
|
if (target_probes != null) {
|
||||||
|
probe_fn = target_probes[p.name]
|
||||||
|
if (probe_fn != null) {
|
||||||
|
results[key] = probe_fn(p.args != null ? p.args : {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
return {ok: true, results: results}
|
||||||
|
}
|
||||||
|
|
||||||
|
function start_server() {
|
||||||
|
server_fd = http.serve(port)
|
||||||
|
http.on_request(server_fd, handle_request)
|
||||||
|
}
|
||||||
|
|
||||||
|
function register(target, probes) {
|
||||||
|
registry[target] = probes
|
||||||
|
if (server_fd == null) start_server()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
register: register,
|
||||||
|
port: port
|
||||||
|
}
|
||||||
3321
qbe_emit.cm
3321
qbe_emit.cm
File diff suppressed because it is too large
Load Diff
@@ -149,7 +149,7 @@ if (!is_array(args) || length(args) < 1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (; i < length(args) - 1; i++) {
|
for (; i < length(args) - 1; i++) {
|
||||||
push(sources, args[i])
|
sources[] = args[i]
|
||||||
}
|
}
|
||||||
archive = args[length(args) - 1]
|
archive = args[length(args) - 1]
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ var run = function() {
|
|||||||
arrfor(all_packages, function(p) {
|
arrfor(all_packages, function(p) {
|
||||||
if (p == 'core') return
|
if (p == 'core') return
|
||||||
if (!needed[p] && find(packages_to_remove, p) == null) {
|
if (!needed[p] && find(packages_to_remove, p) == null) {
|
||||||
push(packages_to_remove, p)
|
packages_to_remove[] = p
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
10
resolve.ce
10
resolve.ce
@@ -165,11 +165,11 @@ for (i = 0; i < length(sorted); i++) {
|
|||||||
|
|
||||||
// Format output
|
// Format output
|
||||||
status_parts = []
|
status_parts = []
|
||||||
if (is_linked) push(status_parts, "linked")
|
if (is_linked) status_parts[] = "linked"
|
||||||
if (is_local) push(status_parts, "local")
|
if (is_local) status_parts[] = "local"
|
||||||
if (!is_in_lock) push(status_parts, "not in lock")
|
if (!is_in_lock) status_parts[] = "not in lock"
|
||||||
if (!is_fetched) push(status_parts, "not fetched")
|
if (!is_fetched) status_parts[] = "not fetched"
|
||||||
if (has_c_files) push(status_parts, "has C modules")
|
if (has_c_files) status_parts[] = "has C modules"
|
||||||
|
|
||||||
commit_str = ""
|
commit_str = ""
|
||||||
if (lock_entry && lock_entry.commit) {
|
if (lock_entry && lock_entry.commit) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ var packages = shop.list_packages()
|
|||||||
arrfor(packages, function(package_name) {
|
arrfor(packages, function(package_name) {
|
||||||
// Check if package name matches
|
// Check if package name matches
|
||||||
if (search(package_name, query) != null) {
|
if (search(package_name, query) != null) {
|
||||||
push(found_packages, package_name)
|
found_packages[] = package_name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search modules and actors within the package
|
// Search modules and actors within the package
|
||||||
@@ -29,14 +29,14 @@ arrfor(packages, function(package_name) {
|
|||||||
var modules = pkg.list_modules(package_name)
|
var modules = pkg.list_modules(package_name)
|
||||||
arrfor(modules, function(mod) {
|
arrfor(modules, function(mod) {
|
||||||
if (search(mod, query) != null) {
|
if (search(mod, query) != null) {
|
||||||
push(found_modules, package_name + ':' + mod)
|
found_modules[] = package_name + ':' + mod
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var actors = pkg.list_programs(package_name)
|
var actors = pkg.list_programs(package_name)
|
||||||
arrfor(actors, function(actor) {
|
arrfor(actors, function(actor) {
|
||||||
if (search(actor, query) != null) {
|
if (search(actor, query) != null) {
|
||||||
push(found_actors, package_name + ':' + actor)
|
found_actors[] = package_name + ':' + actor
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} disruption {
|
} disruption {
|
||||||
|
|||||||
2
seed.ce
2
seed.ce
@@ -42,7 +42,7 @@ for (i = 0; i < length(args); i++) {
|
|||||||
|
|
||||||
var core_dir = shop.get_package_dir('core')
|
var core_dir = shop.get_package_dir('core')
|
||||||
var boot_dir = core_dir + '/boot'
|
var boot_dir = core_dir + '/boot'
|
||||||
var pipeline_modules = ['tokenize', 'parse', 'fold', 'mcode', 'streamline']
|
var pipeline_modules = ['tokenize', 'parse', 'fold', 'mcode', 'streamline', 'qbe', 'qbe_emit']
|
||||||
var generated = 0
|
var generated = 0
|
||||||
var name = null
|
var name = null
|
||||||
var src_path = null
|
var src_path = null
|
||||||
|
|||||||
6
slots.ce
6
slots.ce
@@ -179,7 +179,7 @@ var run = function() {
|
|||||||
first_def[slot_num] = pc
|
first_def[slot_num] = pc
|
||||||
first_def_op[slot_num] = op
|
first_def_op[slot_num] = op
|
||||||
}
|
}
|
||||||
push(events, {kind: "DEF", slot: operand_val, pc: pc, instr: instr})
|
events[] = {kind: "DEF", slot: operand_val, pc: pc, instr: instr}
|
||||||
}
|
}
|
||||||
di = di + 1
|
di = di + 1
|
||||||
}
|
}
|
||||||
@@ -191,7 +191,7 @@ var run = function() {
|
|||||||
slot_num = text(operand_val)
|
slot_num = text(operand_val)
|
||||||
if (!uses[slot_num]) uses[slot_num] = 0
|
if (!uses[slot_num]) uses[slot_num] = 0
|
||||||
uses[slot_num] = uses[slot_num] + 1
|
uses[slot_num] = uses[slot_num] + 1
|
||||||
push(events, {kind: "USE", slot: operand_val, pc: pc, instr: instr})
|
events[] = {kind: "USE", slot: operand_val, pc: pc, instr: instr}
|
||||||
}
|
}
|
||||||
ui = ui + 1
|
ui = ui + 1
|
||||||
}
|
}
|
||||||
@@ -219,7 +219,7 @@ var run = function() {
|
|||||||
parts = []
|
parts = []
|
||||||
j = 1
|
j = 1
|
||||||
while (j < n - 2) {
|
while (j < n - 2) {
|
||||||
push(parts, fmt_val(evt.instr[j]))
|
parts[] = fmt_val(evt.instr[j])
|
||||||
j = j + 1
|
j = j + 1
|
||||||
}
|
}
|
||||||
operands = text(parts, ", ")
|
operands = text(parts, ", ")
|
||||||
|
|||||||
281
source/cell.c
281
source/cell.c
@@ -8,8 +8,7 @@
|
|||||||
#include "stb_ds.h"
|
#include "stb_ds.h"
|
||||||
|
|
||||||
#include "cell.h"
|
#include "cell.h"
|
||||||
#include "cell_internal.h"
|
#include "pit_internal.h"
|
||||||
#include "cJSON.h"
|
|
||||||
|
|
||||||
#define BOOTSTRAP_MCODE "boot/bootstrap.cm.mcode"
|
#define BOOTSTRAP_MCODE "boot/bootstrap.cm.mcode"
|
||||||
#define ENGINE_SRC "internal/engine.cm"
|
#define ENGINE_SRC "internal/engine.cm"
|
||||||
@@ -20,19 +19,20 @@
|
|||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
#include "monocypher.h"
|
#include "monocypher.h"
|
||||||
|
|
||||||
/* Test suite declarations */
|
/* Test suite declarations */
|
||||||
int run_c_test_suite(JSContext *ctx);
|
int run_c_test_suite(JSContext *ctx);
|
||||||
static int run_test_suite(size_t heap_size);
|
static int run_test_suite(size_t heap_size);
|
||||||
|
|
||||||
cell_rt *root_cell = NULL;
|
static JSContext *root_ctx = NULL;
|
||||||
static char *shop_path = NULL;
|
static char *shop_path = NULL;
|
||||||
volatile JSContext *g_crash_ctx = NULL;
|
volatile JSContext *g_crash_ctx = NULL;
|
||||||
static char *core_path = NULL;
|
static char *core_path = NULL;
|
||||||
static int native_mode = 0;
|
static int native_mode = 0;
|
||||||
static int warn_mode = 1;
|
static int warn_mode = 1;
|
||||||
static JSRuntime *g_runtime = NULL;
|
JSRuntime *g_runtime = NULL;
|
||||||
|
|
||||||
// Compute blake2b hash of data and return hex string (caller must free)
|
// Compute blake2b hash of data and return hex string (caller must free)
|
||||||
static char *compute_blake2_hex(const char *data, size_t size) {
|
static char *compute_blake2_hex(const char *data, size_t size) {
|
||||||
@@ -249,50 +249,101 @@ static char *try_engine_cache(size_t *out_size) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *detect_host_target(void) {
|
||||||
|
#if defined(__APPLE__) && defined(__aarch64__)
|
||||||
|
return "macos_arm64";
|
||||||
|
#elif defined(__APPLE__) && defined(__x86_64__)
|
||||||
|
return "macos_x86_64";
|
||||||
|
#elif defined(__linux__) && defined(__x86_64__)
|
||||||
|
return "linux";
|
||||||
|
#elif defined(__linux__) && defined(__aarch64__)
|
||||||
|
return "linux_arm64";
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
return "windows";
|
||||||
|
#else
|
||||||
|
return NULL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *try_engine_native_cache(void) {
|
||||||
|
const char *target = detect_host_target();
|
||||||
|
if (!target) return NULL;
|
||||||
|
size_t src_size;
|
||||||
|
char *src = load_core_file(ENGINE_SRC, &src_size);
|
||||||
|
if (!src) return NULL;
|
||||||
|
size_t target_len = strlen(target);
|
||||||
|
size_t key_len = src_size + 1 + target_len + 8 + 7;
|
||||||
|
char *key = malloc(key_len + 1);
|
||||||
|
if (!key) { free(src); return NULL; }
|
||||||
|
memcpy(key, src, src_size);
|
||||||
|
size_t pos = src_size;
|
||||||
|
key[pos++] = '\n';
|
||||||
|
memcpy(key + pos, target, target_len);
|
||||||
|
pos += target_len;
|
||||||
|
memcpy(key + pos, "\nnative\n", 8);
|
||||||
|
pos += 8;
|
||||||
|
memcpy(key + pos, "\nnative", 7);
|
||||||
|
pos += 7;
|
||||||
|
key[pos] = '\0';
|
||||||
|
free(src);
|
||||||
|
char *hex = compute_blake2_hex(key, pos);
|
||||||
|
free(key);
|
||||||
|
if (!hex) return NULL;
|
||||||
|
char *cpath = build_cache_path(hex);
|
||||||
|
free(hex);
|
||||||
|
if (!cpath) return NULL;
|
||||||
|
struct stat st;
|
||||||
|
if (stat(cpath, &st) != 0) { free(cpath); return NULL; }
|
||||||
|
return cpath;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the core path for use by scripts
|
// Get the core path for use by scripts
|
||||||
const char* cell_get_core_path(void) {
|
const char* cell_get_core_path(void) {
|
||||||
return core_path;
|
return core_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
void actor_disrupt(cell_rt *crt)
|
void actor_disrupt(JSContext *ctx)
|
||||||
{
|
{
|
||||||
crt->disrupt = 1;
|
ctx->disrupt = 1;
|
||||||
JS_SetPauseFlag(crt->context, 2);
|
JS_SetPauseFlag(ctx, 2);
|
||||||
if (crt->state != ACTOR_RUNNING)
|
if (ctx->state != ACTOR_RUNNING)
|
||||||
actor_free(crt);
|
actor_free(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue js_core_internal_os_use(JSContext *js);
|
JSValue js_core_internal_os_use(JSContext *js);
|
||||||
JSValue js_core_json_use(JSContext *js);
|
JSValue js_core_json_use(JSContext *js);
|
||||||
|
|
||||||
void script_startup(cell_rt *prt)
|
/* Engine-env log proxy: routes log("channel", [msg]) through JS_Log.
|
||||||
|
Before set_log is called, JS_Log falls back to stderr.
|
||||||
|
After set_log, JS_Log forwards to the engine's JS log function.
|
||||||
|
This exists so mcode-generated type-check error paths (which always
|
||||||
|
access 'log' as an intrinsic) work inside engine.cm itself. */
|
||||||
|
static JSValue js_engine_log(JSContext *js, JSValue self,
|
||||||
|
int argc, JSValue *argv) {
|
||||||
|
if (argc < 2) return JS_NULL;
|
||||||
|
const char *channel = JS_ToCString(js, argv[0]);
|
||||||
|
if (!channel) return JS_NULL;
|
||||||
|
JSValue msg_val = JS_GetPropertyNumber(js, argv[1], 0);
|
||||||
|
const char *msg = JS_ToCString(js, msg_val);
|
||||||
|
if (msg) {
|
||||||
|
JS_Log(js, channel, "%s", msg);
|
||||||
|
JS_FreeCString(js, msg);
|
||||||
|
}
|
||||||
|
JS_FreeCString(js, channel);
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void script_startup(JSContext *js)
|
||||||
{
|
{
|
||||||
if (!g_runtime) {
|
if (!g_runtime) {
|
||||||
g_runtime = JS_NewRuntime();
|
g_runtime = JS_NewRuntime();
|
||||||
}
|
}
|
||||||
JSContext *js = JS_NewContext(g_runtime);
|
|
||||||
|
|
||||||
JS_SetContextOpaque(js, prt);
|
|
||||||
JS_SetGCScanExternal(js, actor_gc_scan);
|
JS_SetGCScanExternal(js, actor_gc_scan);
|
||||||
prt->context = js;
|
|
||||||
|
|
||||||
/* Set per-actor heap memory limit */
|
js->actor_label = js->name; /* may be NULL; updated when name is set */
|
||||||
JS_SetHeapMemoryLimit(js, ACTOR_MEMORY_LIMIT);
|
JS_SetHeapMemoryLimit(js, ACTOR_MEMORY_LIMIT);
|
||||||
|
|
||||||
/* Register all GCRef fields so the Cheney GC can relocate them. */
|
|
||||||
JS_AddGCRef(js, &prt->idx_buffer_ref);
|
|
||||||
JS_AddGCRef(js, &prt->on_exception_ref);
|
|
||||||
JS_AddGCRef(js, &prt->message_handle_ref);
|
|
||||||
JS_AddGCRef(js, &prt->unneeded_ref);
|
|
||||||
JS_AddGCRef(js, &prt->actor_sym_ref);
|
|
||||||
prt->idx_buffer_ref.val = JS_NULL;
|
|
||||||
prt->on_exception_ref.val = JS_NULL;
|
|
||||||
prt->message_handle_ref.val = JS_NULL;
|
|
||||||
prt->unneeded_ref.val = JS_NULL;
|
|
||||||
prt->actor_sym_ref.val = JS_NULL;
|
|
||||||
|
|
||||||
cell_rt *crt = JS_GetContextOpaque(js);
|
|
||||||
JS_FreeValue(js, js_core_blob_use(js));
|
|
||||||
|
|
||||||
// Try engine fast-path: load engine.cm from source-hash cache
|
// Try engine fast-path: load engine.cm from source-hash cache
|
||||||
size_t bin_size;
|
size_t bin_size;
|
||||||
@@ -327,14 +378,16 @@ void script_startup(cell_rt *prt)
|
|||||||
}
|
}
|
||||||
btmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
|
btmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
|
||||||
JS_SetPropertyStr(js, boot_env_ref.val, "shop_path", btmp);
|
JS_SetPropertyStr(js, boot_env_ref.val, "shop_path", btmp);
|
||||||
|
if (native_mode)
|
||||||
|
JS_SetPropertyStr(js, boot_env_ref.val, "native_mode", JS_NewBool(js, 1));
|
||||||
JSValue boot_env = JS_Stone(js, boot_env_ref.val);
|
JSValue boot_env = JS_Stone(js, boot_env_ref.val);
|
||||||
JS_DeleteGCRef(js, &boot_env_ref);
|
JS_DeleteGCRef(js, &boot_env_ref);
|
||||||
|
|
||||||
crt->state = ACTOR_RUNNING;
|
js->state = ACTOR_RUNNING;
|
||||||
JSValue bv = JS_RunMachBin(js, (const uint8_t *)boot_bin, boot_bin_size, boot_env);
|
JSValue bv = JS_RunMachBin(js, (const uint8_t *)boot_bin, boot_bin_size, boot_env);
|
||||||
free(boot_bin);
|
free(boot_bin);
|
||||||
uncaught_exception(js, bv);
|
uncaught_exception(js, bv);
|
||||||
crt->state = ACTOR_IDLE;
|
js->state = ACTOR_IDLE;
|
||||||
|
|
||||||
// Retry engine from cache
|
// Retry engine from cache
|
||||||
bin_data = try_engine_cache(&bin_size);
|
bin_data = try_engine_cache(&bin_size);
|
||||||
@@ -354,20 +407,20 @@ void script_startup(cell_rt *prt)
|
|||||||
tmp = js_core_json_use(js);
|
tmp = js_core_json_use(js);
|
||||||
JS_SetPropertyStr(js, env_ref.val, "json", tmp);
|
JS_SetPropertyStr(js, env_ref.val, "json", tmp);
|
||||||
|
|
||||||
crt->actor_sym_ref.val = JS_NewObject(js);
|
js->actor_sym_ref.val = JS_NewObject(js);
|
||||||
JS_CellStone(js, crt->actor_sym_ref.val);
|
JS_CellStone(js, js->actor_sym_ref.val);
|
||||||
JS_SetActorSym(js, JS_DupValue(js, crt->actor_sym_ref.val));
|
JS_SetActorSym(js, js->actor_sym_ref.val);
|
||||||
JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
|
JS_SetPropertyStr(js, env_ref.val, "actorsym", js->actor_sym_ref.val);
|
||||||
|
|
||||||
// Always set init (even if null)
|
// Always set init (even if null)
|
||||||
if (crt->init_wota) {
|
if (js->init_wota) {
|
||||||
JSGCRef init_ref;
|
JSGCRef init_ref;
|
||||||
JS_PushGCRef(js, &init_ref);
|
JS_PushGCRef(js, &init_ref);
|
||||||
init_ref.val = wota2value(js, crt->init_wota);
|
init_ref.val = wota2value(js, js->init_wota);
|
||||||
JS_SetPropertyStr(js, env_ref.val, "init", init_ref.val);
|
JS_SetPropertyStr(js, env_ref.val, "init", init_ref.val);
|
||||||
JS_PopGCRef(js, &init_ref);
|
JS_PopGCRef(js, &init_ref);
|
||||||
free(crt->init_wota);
|
free(js->init_wota);
|
||||||
crt->init_wota = NULL;
|
js->init_wota = NULL;
|
||||||
} else {
|
} else {
|
||||||
JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
|
JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
|
||||||
}
|
}
|
||||||
@@ -381,18 +434,20 @@ void script_startup(cell_rt *prt)
|
|||||||
}
|
}
|
||||||
tmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
|
tmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
|
||||||
JS_SetPropertyStr(js, env_ref.val, "shop_path", tmp);
|
JS_SetPropertyStr(js, env_ref.val, "shop_path", tmp);
|
||||||
|
tmp = JS_NewCFunction(js, js_engine_log, "log", 2);
|
||||||
|
JS_SetPropertyStr(js, env_ref.val, "log", tmp);
|
||||||
|
|
||||||
// Stone the environment
|
// Stone the environment
|
||||||
JSValue hidden_env = JS_Stone(js, env_ref.val);
|
JSValue hidden_env = JS_Stone(js, env_ref.val);
|
||||||
JS_DeleteGCRef(js, &env_ref);
|
JS_DeleteGCRef(js, &env_ref);
|
||||||
|
|
||||||
// Run engine from binary
|
// Run engine from binary
|
||||||
crt->state = ACTOR_RUNNING;
|
js->state = ACTOR_RUNNING;
|
||||||
JSValue v = JS_RunMachBin(js, (const uint8_t *)bin_data, bin_size, hidden_env);
|
JSValue v = JS_RunMachBin(js, (const uint8_t *)bin_data, bin_size, hidden_env);
|
||||||
free(bin_data);
|
free(bin_data);
|
||||||
uncaught_exception(js, v);
|
uncaught_exception(js, v);
|
||||||
crt->state = ACTOR_IDLE;
|
js->state = ACTOR_IDLE;
|
||||||
set_actor_state(crt);
|
set_actor_state(js);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void signal_handler(int sig)
|
static void signal_handler(int sig)
|
||||||
@@ -455,6 +510,7 @@ static void print_usage(const char *prog)
|
|||||||
printf(" --native Use AOT native code instead of bytecode\n");
|
printf(" --native Use AOT native code instead of bytecode\n");
|
||||||
printf(" --heap <size> Initial heap size (e.g. 256MB, 1GB)\n");
|
printf(" --heap <size> Initial heap size (e.g. 256MB, 1GB)\n");
|
||||||
printf(" --test [heap_size] Run C test suite\n");
|
printf(" --test [heap_size] Run C test suite\n");
|
||||||
|
printf(" -e <code> Evaluate code string as a program\n");
|
||||||
printf(" -h, --help Show this help message\n");
|
printf(" -h, --help Show this help message\n");
|
||||||
printf("\nEnvironment:\n");
|
printf("\nEnvironment:\n");
|
||||||
printf(" CELL_CORE Core path (default: <shop>/packages/core)\n");
|
printf(" CELL_CORE Core path (default: <shop>/packages/core)\n");
|
||||||
@@ -464,6 +520,8 @@ static void print_usage(const char *prog)
|
|||||||
printf("Run the 'help' script like 'cell help' to see available scripts\n");
|
printf("Run the 'help' script like 'cell help' to see available scripts\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const char *sym_name, JSValue env);
|
||||||
|
|
||||||
int cell_init(int argc, char **argv)
|
int cell_init(int argc, char **argv)
|
||||||
{
|
{
|
||||||
/* Check for --help flag */
|
/* Check for --help flag */
|
||||||
@@ -490,6 +548,7 @@ int cell_init(int argc, char **argv)
|
|||||||
size_t heap_size = 1024 * 1024; /* 1MB default */
|
size_t heap_size = 1024 * 1024; /* 1MB default */
|
||||||
const char *shop_override = NULL;
|
const char *shop_override = NULL;
|
||||||
const char *core_override = NULL;
|
const char *core_override = NULL;
|
||||||
|
const char *eval_script = NULL;
|
||||||
|
|
||||||
// Parse flags (order-independent)
|
// Parse flags (order-independent)
|
||||||
while (arg_start < argc && argv[arg_start][0] == '-') {
|
while (arg_start < argc && argv[arg_start][0] == '-') {
|
||||||
@@ -536,12 +595,19 @@ int cell_init(int argc, char **argv)
|
|||||||
} else if (strcmp(argv[arg_start], "--no-warn") == 0) {
|
} else if (strcmp(argv[arg_start], "--no-warn") == 0) {
|
||||||
warn_mode = 0;
|
warn_mode = 0;
|
||||||
arg_start++;
|
arg_start++;
|
||||||
|
} else if (strcmp(argv[arg_start], "-e") == 0) {
|
||||||
|
if (arg_start + 1 >= argc) {
|
||||||
|
printf("ERROR: -e requires a code string argument\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
eval_script = argv[arg_start + 1];
|
||||||
|
arg_start += 2;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg_start >= argc) {
|
if (arg_start >= argc && !eval_script) {
|
||||||
print_usage(argv[0]);
|
print_usage(argv[0]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -562,45 +628,41 @@ int cell_init(int argc, char **argv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create a cell_rt for the CLI context so JS-C bridge functions work */
|
/* Set up mutexes on the CLI context */
|
||||||
cell_rt *cli_rt = calloc(sizeof(*cli_rt), 1);
|
ctx->mutex = malloc(sizeof(pthread_mutex_t));
|
||||||
cli_rt->mutex = malloc(sizeof(pthread_mutex_t));
|
|
||||||
pthread_mutexattr_t mattr;
|
pthread_mutexattr_t mattr;
|
||||||
pthread_mutexattr_init(&mattr);
|
pthread_mutexattr_init(&mattr);
|
||||||
pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
|
pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
|
||||||
pthread_mutex_init(cli_rt->mutex, &mattr);
|
pthread_mutex_init(ctx->mutex, &mattr);
|
||||||
cli_rt->msg_mutex = malloc(sizeof(pthread_mutex_t));
|
ctx->msg_mutex = malloc(sizeof(pthread_mutex_t));
|
||||||
pthread_mutex_init(cli_rt->msg_mutex, &mattr);
|
pthread_mutex_init(ctx->msg_mutex, &mattr);
|
||||||
pthread_mutexattr_destroy(&mattr);
|
pthread_mutexattr_destroy(&mattr);
|
||||||
|
|
||||||
cli_rt->context = ctx;
|
|
||||||
JS_SetContextOpaque(ctx, cli_rt);
|
|
||||||
JS_SetGCScanExternal(ctx, actor_gc_scan);
|
JS_SetGCScanExternal(ctx, actor_gc_scan);
|
||||||
|
|
||||||
JS_AddGCRef(ctx, &cli_rt->idx_buffer_ref);
|
ctx->actor_sym_ref.val = JS_NewObject(ctx);
|
||||||
JS_AddGCRef(ctx, &cli_rt->on_exception_ref);
|
JS_CellStone(ctx, ctx->actor_sym_ref.val);
|
||||||
JS_AddGCRef(ctx, &cli_rt->message_handle_ref);
|
JS_SetActorSym(ctx, ctx->actor_sym_ref.val);
|
||||||
JS_AddGCRef(ctx, &cli_rt->unneeded_ref);
|
|
||||||
JS_AddGCRef(ctx, &cli_rt->actor_sym_ref);
|
|
||||||
cli_rt->idx_buffer_ref.val = JS_NULL;
|
|
||||||
cli_rt->on_exception_ref.val = JS_NULL;
|
|
||||||
cli_rt->message_handle_ref.val = JS_NULL;
|
|
||||||
cli_rt->unneeded_ref.val = JS_NULL;
|
|
||||||
cli_rt->actor_sym_ref.val = JS_NewObject(ctx);
|
|
||||||
JS_CellStone(ctx, cli_rt->actor_sym_ref.val);
|
|
||||||
JS_SetActorSym(ctx, JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
|
|
||||||
|
|
||||||
root_cell = cli_rt;
|
root_ctx = ctx;
|
||||||
|
|
||||||
JS_FreeValue(ctx, js_core_blob_use(ctx));
|
|
||||||
|
|
||||||
int exit_code = 0;
|
int exit_code = 0;
|
||||||
|
int use_native_engine = 0;
|
||||||
|
char *native_dylib_path = NULL;
|
||||||
|
void *native_handle = NULL;
|
||||||
|
|
||||||
|
// Native mode: try native engine cache first
|
||||||
|
if (native_mode)
|
||||||
|
native_dylib_path = try_engine_native_cache();
|
||||||
|
|
||||||
// Try engine fast-path: load engine.cm from source-hash cache
|
// Try engine fast-path: load engine.cm from source-hash cache
|
||||||
size_t bin_size;
|
size_t bin_size;
|
||||||
char *bin_data = try_engine_cache(&bin_size);
|
char *bin_data = NULL;
|
||||||
|
if (!native_dylib_path)
|
||||||
|
bin_data = try_engine_cache(&bin_size);
|
||||||
|
|
||||||
if (!bin_data) {
|
if (!native_dylib_path && !bin_data) {
|
||||||
// Cold path: run bootstrap to seed cache, then retry
|
// Cold path: run bootstrap to seed cache, then retry
|
||||||
size_t boot_size;
|
size_t boot_size;
|
||||||
char *boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size);
|
char *boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size);
|
||||||
@@ -628,9 +690,11 @@ int cell_init(int argc, char **argv)
|
|||||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "core_path", btmp);
|
JS_SetPropertyStr(ctx, boot_env_ref.val, "core_path", btmp);
|
||||||
btmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
|
btmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
|
||||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "shop_path", btmp);
|
JS_SetPropertyStr(ctx, boot_env_ref.val, "shop_path", btmp);
|
||||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
|
JS_SetPropertyStr(ctx, boot_env_ref.val, "actorsym", ctx->actor_sym_ref.val);
|
||||||
btmp = js_core_json_use(ctx);
|
btmp = js_core_json_use(ctx);
|
||||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "json", btmp);
|
JS_SetPropertyStr(ctx, boot_env_ref.val, "json", btmp);
|
||||||
|
if (native_mode)
|
||||||
|
JS_SetPropertyStr(ctx, boot_env_ref.val, "native_mode", JS_NewBool(ctx, 1));
|
||||||
JS_SetPropertyStr(ctx, boot_env_ref.val, "init", JS_NULL);
|
JS_SetPropertyStr(ctx, boot_env_ref.val, "init", JS_NULL);
|
||||||
JSGCRef boot_args_ref;
|
JSGCRef boot_args_ref;
|
||||||
JS_AddGCRef(ctx, &boot_args_ref);
|
JS_AddGCRef(ctx, &boot_args_ref);
|
||||||
@@ -652,14 +716,32 @@ int cell_init(int argc, char **argv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retry engine from cache (new-style bootstrap seeds it)
|
// After bootstrap, retry cache
|
||||||
bin_data = try_engine_cache(&bin_size);
|
if (native_mode)
|
||||||
if (!bin_data) {
|
native_dylib_path = try_engine_native_cache();
|
||||||
// Old-style bootstrap already ran the program — skip engine load
|
if (!native_dylib_path) {
|
||||||
goto check_actors;
|
bin_data = try_engine_cache(&bin_size);
|
||||||
|
if (!bin_data) {
|
||||||
|
// Old-style bootstrap already ran the program — skip engine load
|
||||||
|
goto check_actors;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open native dylib if we have a path
|
||||||
|
if (native_dylib_path) {
|
||||||
|
native_handle = dlopen(native_dylib_path, RTLD_NOW | RTLD_GLOBAL);
|
||||||
|
if (native_handle) {
|
||||||
|
use_native_engine = 1;
|
||||||
|
} else {
|
||||||
|
// Fall back to bytecode
|
||||||
|
if (!bin_data)
|
||||||
|
bin_data = try_engine_cache(&bin_size);
|
||||||
|
}
|
||||||
|
free(native_dylib_path);
|
||||||
|
native_dylib_path = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// Build engine environment
|
// Build engine environment
|
||||||
JSGCRef env_ref;
|
JSGCRef env_ref;
|
||||||
@@ -672,10 +754,10 @@ int cell_init(int argc, char **argv)
|
|||||||
JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp);
|
JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp);
|
||||||
tmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
|
tmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
|
||||||
JS_SetPropertyStr(ctx, env_ref.val, "shop_path", tmp);
|
JS_SetPropertyStr(ctx, env_ref.val, "shop_path", tmp);
|
||||||
JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
|
JS_SetPropertyStr(ctx, env_ref.val, "actorsym", ctx->actor_sym_ref.val);
|
||||||
tmp = js_core_json_use(ctx);
|
tmp = js_core_json_use(ctx);
|
||||||
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
|
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
|
||||||
if (native_mode || !warn_mode) {
|
if (native_mode || !warn_mode || eval_script) {
|
||||||
JSGCRef init_ref;
|
JSGCRef init_ref;
|
||||||
JS_AddGCRef(ctx, &init_ref);
|
JS_AddGCRef(ctx, &init_ref);
|
||||||
init_ref.val = JS_NewObject(ctx);
|
init_ref.val = JS_NewObject(ctx);
|
||||||
@@ -683,6 +765,10 @@ int cell_init(int argc, char **argv)
|
|||||||
JS_SetPropertyStr(ctx, init_ref.val, "native_mode", JS_NewBool(ctx, 1));
|
JS_SetPropertyStr(ctx, init_ref.val, "native_mode", JS_NewBool(ctx, 1));
|
||||||
if (!warn_mode)
|
if (!warn_mode)
|
||||||
JS_SetPropertyStr(ctx, init_ref.val, "no_warn", JS_NewBool(ctx, 1));
|
JS_SetPropertyStr(ctx, init_ref.val, "no_warn", JS_NewBool(ctx, 1));
|
||||||
|
if (eval_script) {
|
||||||
|
JSValue es = JS_NewString(ctx, eval_script);
|
||||||
|
JS_SetPropertyStr(ctx, init_ref.val, "eval_script", es);
|
||||||
|
}
|
||||||
JS_SetPropertyStr(ctx, env_ref.val, "init", init_ref.val);
|
JS_SetPropertyStr(ctx, env_ref.val, "init", init_ref.val);
|
||||||
JS_DeleteGCRef(ctx, &init_ref);
|
JS_DeleteGCRef(ctx, &init_ref);
|
||||||
} else {
|
} else {
|
||||||
@@ -697,13 +783,20 @@ int cell_init(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
JS_SetPropertyStr(ctx, env_ref.val, "args", args_ref.val);
|
JS_SetPropertyStr(ctx, env_ref.val, "args", args_ref.val);
|
||||||
JS_DeleteGCRef(ctx, &args_ref);
|
JS_DeleteGCRef(ctx, &args_ref);
|
||||||
|
tmp = JS_NewCFunction(ctx, js_engine_log, "log", 2);
|
||||||
|
JS_SetPropertyStr(ctx, env_ref.val, "log", tmp);
|
||||||
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
|
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
|
||||||
|
|
||||||
g_crash_ctx = ctx;
|
g_crash_ctx = ctx;
|
||||||
JSValue result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env);
|
JSValue result;
|
||||||
|
if (use_native_engine) {
|
||||||
|
result = cell_rt_native_module_load_named(ctx, native_handle, "cell_main", hidden_env);
|
||||||
|
} else {
|
||||||
|
result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env);
|
||||||
|
free(bin_data);
|
||||||
|
}
|
||||||
g_crash_ctx = NULL;
|
g_crash_ctx = NULL;
|
||||||
JS_DeleteGCRef(ctx, &env_ref);
|
JS_DeleteGCRef(ctx, &env_ref);
|
||||||
free(bin_data);
|
|
||||||
|
|
||||||
if (JS_IsException(result)) {
|
if (JS_IsException(result)) {
|
||||||
JS_GetException(ctx);
|
JS_GetException(ctx);
|
||||||
@@ -726,18 +819,11 @@ check_actors:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* No actors spawned — clean up CLI context */
|
/* No actors spawned — clean up CLI context */
|
||||||
JS_DeleteGCRef(ctx, &cli_rt->idx_buffer_ref);
|
pthread_mutex_destroy(ctx->mutex);
|
||||||
JS_DeleteGCRef(ctx, &cli_rt->on_exception_ref);
|
free(ctx->mutex);
|
||||||
JS_DeleteGCRef(ctx, &cli_rt->message_handle_ref);
|
pthread_mutex_destroy(ctx->msg_mutex);
|
||||||
JS_DeleteGCRef(ctx, &cli_rt->unneeded_ref);
|
free(ctx->msg_mutex);
|
||||||
JS_DeleteGCRef(ctx, &cli_rt->actor_sym_ref);
|
root_ctx = NULL;
|
||||||
|
|
||||||
pthread_mutex_destroy(cli_rt->mutex);
|
|
||||||
free(cli_rt->mutex);
|
|
||||||
pthread_mutex_destroy(cli_rt->msg_mutex);
|
|
||||||
free(cli_rt->msg_mutex);
|
|
||||||
free(cli_rt);
|
|
||||||
root_cell = NULL;
|
|
||||||
|
|
||||||
JS_FreeContext(ctx);
|
JS_FreeContext(ctx);
|
||||||
JS_FreeRuntime(g_runtime);
|
JS_FreeRuntime(g_runtime);
|
||||||
@@ -776,9 +862,21 @@ double cell_random() {
|
|||||||
return (double)buf / 9007199254740992.0;
|
return (double)buf / 9007199254740992.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static cell_hook g_cell_trace_hook = NULL;
|
||||||
|
|
||||||
void cell_trace_sethook(cell_hook hook)
|
void cell_trace_sethook(cell_hook hook)
|
||||||
{
|
{
|
||||||
(void)hook;
|
g_cell_trace_hook = hook;
|
||||||
|
}
|
||||||
|
|
||||||
|
cell_hook cell_trace_gethook(void)
|
||||||
|
{
|
||||||
|
return g_cell_trace_hook;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cell_rt_set_trace_hook(JSContext *ctx, cell_hook hook)
|
||||||
|
{
|
||||||
|
ctx->actor_trace_hook = hook;
|
||||||
}
|
}
|
||||||
|
|
||||||
int uncaught_exception(JSContext *js, JSValue v)
|
int uncaught_exception(JSContext *js, JSValue v)
|
||||||
@@ -791,13 +889,12 @@ int uncaught_exception(JSContext *js, JSValue v)
|
|||||||
by JS_ThrowError2 / print_backtrace. Just clear the flag. */
|
by JS_ThrowError2 / print_backtrace. Just clear the flag. */
|
||||||
if (has_exc)
|
if (has_exc)
|
||||||
JS_GetException(js);
|
JS_GetException(js);
|
||||||
cell_rt *crt = JS_GetContextOpaque(js);
|
if (!JS_IsNull(js->on_exception_ref.val)) {
|
||||||
if (crt && !JS_IsNull(crt->on_exception_ref.val)) {
|
|
||||||
/* Disable interruption so actor_die can send messages
|
/* Disable interruption so actor_die can send messages
|
||||||
without being re-interrupted. */
|
without being re-interrupted. */
|
||||||
JS_SetPauseFlag(js, 0);
|
JS_SetPauseFlag(js, 0);
|
||||||
JSValue err = JS_NewString(js, "interrupted");
|
JSValue err = JS_NewString(js, "interrupted");
|
||||||
JS_Call(js, crt->on_exception_ref.val, JS_NULL, 1, &err);
|
JS_Call(js, js->on_exception_ref.val, JS_NULL, 1, &err);
|
||||||
/* Clear any secondary exception from the callback. */
|
/* Clear any secondary exception from the callback. */
|
||||||
if (JS_HasException(js))
|
if (JS_HasException(js))
|
||||||
JS_GetException(js);
|
JS_GetException(js);
|
||||||
|
|||||||
974
source/cell.h
974
source/cell.h
File diff suppressed because it is too large
Load Diff
@@ -1,112 +0,0 @@
|
|||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <stdatomic.h>
|
|
||||||
|
|
||||||
/* Letter type for unified message queue */
|
|
||||||
typedef enum {
|
|
||||||
LETTER_BLOB, /* Blob message */
|
|
||||||
LETTER_CALLBACK /* JSValue callback function */
|
|
||||||
} letter_type;
|
|
||||||
|
|
||||||
typedef struct letter {
|
|
||||||
letter_type type;
|
|
||||||
union {
|
|
||||||
blob *blob_data; /* For LETTER_BLOB */
|
|
||||||
JSValue callback; /* For LETTER_CALLBACK */
|
|
||||||
};
|
|
||||||
} letter;
|
|
||||||
|
|
||||||
#define ACTOR_IDLE 0 // Actor not doing anything
|
|
||||||
#define ACTOR_READY 1 // Actor ready for a turn
|
|
||||||
#define ACTOR_RUNNING 2 // Actor taking a turn
|
|
||||||
#define ACTOR_EXHAUSTED 3 // Actor waiting for GC
|
|
||||||
#define ACTOR_RECLAIMING 4 // Actor running GC
|
|
||||||
#define ACTOR_SLOW 5 // Actor going slowly; deprioritize
|
|
||||||
#define ACTOR_REFRESHED 6 // GC finished, ready to resume
|
|
||||||
|
|
||||||
// #define ACTOR_TRACE
|
|
||||||
|
|
||||||
#define ACTOR_FAST_TIMER_NS (10ULL * 1000000) // 10ms per turn
|
|
||||||
#define ACTOR_SLOW_TIMER_NS (60000ULL * 1000000) // 60s for slow actors
|
|
||||||
#define ACTOR_SLOW_STRIKES_MAX 3 // consecutive slow turns -> kill
|
|
||||||
#define ACTOR_MEMORY_LIMIT (16ULL * 1024 * 1024) // 16MB heap cap
|
|
||||||
|
|
||||||
typedef struct cell_rt {
|
|
||||||
JSContext *context;
|
|
||||||
|
|
||||||
/* JSValues on the GC heap — each paired with a JSGCRef so the
|
|
||||||
Cheney GC can relocate them during compaction. */
|
|
||||||
JSGCRef idx_buffer_ref;
|
|
||||||
JSGCRef on_exception_ref;
|
|
||||||
JSGCRef message_handle_ref;
|
|
||||||
JSGCRef unneeded_ref;
|
|
||||||
JSGCRef actor_sym_ref;
|
|
||||||
|
|
||||||
void *init_wota;
|
|
||||||
|
|
||||||
/* Protects JSContext usage */
|
|
||||||
pthread_mutex_t *mutex; /* for everything else */
|
|
||||||
pthread_mutex_t *msg_mutex; /* For message queue and timers queue */
|
|
||||||
|
|
||||||
char *id;
|
|
||||||
|
|
||||||
int idx_count;
|
|
||||||
|
|
||||||
/* The "mailbox" for incoming messages + a dedicated lock for it: */
|
|
||||||
letter *letters;
|
|
||||||
|
|
||||||
/* CHANGED FOR EVENTS: a separate lock for the actor->events queue */
|
|
||||||
struct { uint32_t key; JSValue value; } *timers;
|
|
||||||
|
|
||||||
int state;
|
|
||||||
uint32_t ar; // timer for unneeded
|
|
||||||
double ar_secs; // time for unneeded
|
|
||||||
|
|
||||||
int disrupt;
|
|
||||||
int is_quiescent; // tracked by scheduler for quiescence detection
|
|
||||||
int main_thread_only;
|
|
||||||
int affinity;
|
|
||||||
|
|
||||||
uint64_t turn_start_ns; // cell_ns() when turn began
|
|
||||||
_Atomic uint32_t turn_gen; // incremented each turn start
|
|
||||||
int slow_strikes; // consecutive slow-completed turns
|
|
||||||
int vm_suspended; // 1 if VM is paused mid-turn
|
|
||||||
|
|
||||||
const char *name; // human friendly name
|
|
||||||
cell_hook trace_hook;
|
|
||||||
} cell_rt;
|
|
||||||
|
|
||||||
/* Set by actor_turn/CLI before entering the VM, cleared after.
|
|
||||||
Read by signal_handler to print JS stack on crash. */
|
|
||||||
extern volatile JSContext *g_crash_ctx;
|
|
||||||
|
|
||||||
cell_rt *create_actor(void *wota);
|
|
||||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
|
|
||||||
void actor_disrupt(cell_rt *actor);
|
|
||||||
|
|
||||||
const char *send_message(const char *id, void *msg);
|
|
||||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
|
|
||||||
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds);
|
|
||||||
void script_startup(cell_rt *rt);
|
|
||||||
int uncaught_exception(JSContext *js, JSValue v);
|
|
||||||
int actor_exists(const char *id);
|
|
||||||
|
|
||||||
void set_actor_state(cell_rt *actor);
|
|
||||||
void enqueue_actor_priority(cell_rt *actor);
|
|
||||||
void actor_clock(cell_rt *actor, JSValue fn);
|
|
||||||
uint32_t actor_delay(cell_rt *actor, JSValue fn, double seconds);
|
|
||||||
JSValue actor_remove_timer(cell_rt *actor, uint32_t timer_id);
|
|
||||||
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);
|
|
||||||
|
|
||||||
JSValue JS_ResumeRegisterVM(JSContext *ctx);
|
|
||||||
|
|
||||||
uint64_t cell_ns();
|
|
||||||
void cell_sleep(double seconds);
|
|
||||||
int randombytes(void *buf, size_t n);
|
|
||||||
452
source/mach.c
452
source/mach.c
@@ -1,29 +1,5 @@
|
|||||||
/*
|
#include "pit_internal.h"
|
||||||
* QuickJS Javascript Engine
|
#include <assert.h>
|
||||||
*
|
|
||||||
* Copyright (c) 2017-2025 Fabrice Bellard
|
|
||||||
* Copyright (c) 2017-2025 Charlie Gordon
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to
|
|
||||||
* deal in the Software without restriction, including without limitation the
|
|
||||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
* sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "quickjs-internal.h"
|
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
Mach VM instruction definitions (private to mach.c)
|
Mach VM instruction definitions (private to mach.c)
|
||||||
@@ -88,7 +64,7 @@
|
|||||||
[P] IS_IDENTICAL, IS_INT, IS_NUM, IS_TEXT, IS_BOOL, IS_NULL
|
[P] IS_IDENTICAL, IS_INT, IS_NUM, IS_TEXT, IS_BOOL, IS_NULL
|
||||||
[P] IS_ARRAY, IS_FUNC, IS_RECORD, IS_STONE, IS_PROXY
|
[P] IS_ARRAY, IS_FUNC, IS_RECORD, IS_STONE, IS_PROXY
|
||||||
[P] NOT, AND, OR, BITNOT, BITAND, BITOR, BITXOR
|
[P] NOT, AND, OR, BITNOT, BITAND, BITOR, BITXOR
|
||||||
[P] JMP, JMPTRUE, JMPFALSE, JMPNULL, JMPNOTNULL
|
[P] JMP, JMPTRUE, JMPFALSE, JMPNULL, JMPNOTNULL, WARYTRUE, WARYFALSE, JMPEMPTY
|
||||||
[P] RETURN, RETNIL, SETARG, GETUP, SETUP, DISRUPT, THROW
|
[P] RETURN, RETNIL, SETARG, GETUP, SETUP, DISRUPT, THROW
|
||||||
[P] LENGTH (array + imm-ASCII fast path only; text/blob fallback is [G])
|
[P] LENGTH (array + imm-ASCII fast path only; text/blob fallback is [G])
|
||||||
[N] EQ_TEXT..GE_TEXT (js_string_compare_value — no allocation)
|
[N] EQ_TEXT..GE_TEXT (js_string_compare_value — no allocation)
|
||||||
@@ -269,6 +245,22 @@ typedef enum MachOpcode {
|
|||||||
MACH_IS_STONE, /* R(A) = is_stone(R(B)) */
|
MACH_IS_STONE, /* R(A) = is_stone(R(B)) */
|
||||||
MACH_LENGTH, /* R(A) = length(R(B)) — array/text/blob length */
|
MACH_LENGTH, /* R(A) = length(R(B)) — array/text/blob length */
|
||||||
MACH_IS_PROXY, /* R(A) = is_function(R(B)) && R(B).length == 2 */
|
MACH_IS_PROXY, /* R(A) = is_function(R(B)) && R(B).length == 2 */
|
||||||
|
MACH_IS_BLOB, /* R(A) = is_blob(R(B)) */
|
||||||
|
MACH_IS_DATA, /* R(A) = is_data(R(B)) — not function or null */
|
||||||
|
MACH_IS_TRUE, /* R(A) = (R(B) === true) */
|
||||||
|
MACH_IS_FALSE, /* R(A) = (R(B) === false) */
|
||||||
|
MACH_IS_FIT, /* R(A) = is_fit(R(B)) — safe integer */
|
||||||
|
MACH_IS_CHAR, /* R(A) = is_character(R(B)) — single char text */
|
||||||
|
MACH_IS_DIGIT, /* R(A) = is_digit(R(B)) */
|
||||||
|
MACH_IS_LETTER, /* R(A) = is_letter(R(B)) */
|
||||||
|
MACH_IS_LOWER, /* R(A) = is_lower(R(B)) */
|
||||||
|
MACH_IS_UPPER, /* R(A) = is_upper(R(B)) */
|
||||||
|
MACH_IS_WS, /* R(A) = is_whitespace(R(B)) */
|
||||||
|
MACH_IS_ACTOR, /* R(A) = is_actor(R(B)) — has actor_sym property */
|
||||||
|
MACH_APPLY, /* R(A) = apply(R(B), R(C)) — call fn with args from array (ABC) */
|
||||||
|
MACH_WARYTRUE, /* if toBool(R(A)): pc += sBx — coercing (iAsBx) */
|
||||||
|
MACH_WARYFALSE, /* if !toBool(R(A)): pc += sBx — coercing (iAsBx) */
|
||||||
|
MACH_JMPEMPTY, /* if R(A)==empty_text: pc += sBx (iAsBx) */
|
||||||
|
|
||||||
MACH_OP_COUNT
|
MACH_OP_COUNT
|
||||||
} MachOpcode;
|
} MachOpcode;
|
||||||
@@ -381,6 +373,22 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
|
|||||||
[MACH_IS_STONE] = "is_stone",
|
[MACH_IS_STONE] = "is_stone",
|
||||||
[MACH_LENGTH] = "length",
|
[MACH_LENGTH] = "length",
|
||||||
[MACH_IS_PROXY] = "is_proxy",
|
[MACH_IS_PROXY] = "is_proxy",
|
||||||
|
[MACH_IS_BLOB] = "is_blob",
|
||||||
|
[MACH_IS_DATA] = "is_data",
|
||||||
|
[MACH_IS_TRUE] = "is_true",
|
||||||
|
[MACH_IS_FALSE] = "is_false",
|
||||||
|
[MACH_IS_FIT] = "is_fit",
|
||||||
|
[MACH_IS_CHAR] = "is_char",
|
||||||
|
[MACH_IS_DIGIT] = "is_digit",
|
||||||
|
[MACH_IS_LETTER] = "is_letter",
|
||||||
|
[MACH_IS_LOWER] = "is_lower",
|
||||||
|
[MACH_IS_UPPER] = "is_upper",
|
||||||
|
[MACH_IS_WS] = "is_ws",
|
||||||
|
[MACH_IS_ACTOR] = "is_actor",
|
||||||
|
[MACH_APPLY] = "apply",
|
||||||
|
[MACH_WARYTRUE] = "wary_true",
|
||||||
|
[MACH_WARYFALSE] = "wary_false",
|
||||||
|
[MACH_JMPEMPTY] = "jump_empty",
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ---- Compile-time constant pool entry ---- */
|
/* ---- Compile-time constant pool entry ---- */
|
||||||
@@ -932,7 +940,7 @@ JSValue js_new_register_function(JSContext *ctx, JSCodeRegister *code, JSValue e
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue js_new_native_function_with_code(JSContext *ctx, JSValue code_obj, int arity, JSValue outer_frame) {
|
JSValue js_new_native_function_with_code(JSContext *ctx, JSValue code_obj, int arity, JSValue outer_frame, JSValue env_record) {
|
||||||
JSGCRef frame_ref;
|
JSGCRef frame_ref;
|
||||||
JSGCRef fn_ref;
|
JSGCRef fn_ref;
|
||||||
JSFunction *fn;
|
JSFunction *fn;
|
||||||
@@ -955,7 +963,7 @@ JSValue js_new_native_function_with_code(JSContext *ctx, JSValue code_obj, int a
|
|||||||
fn->name = JS_NULL;
|
fn->name = JS_NULL;
|
||||||
fn = JS_VALUE_GET_FUNCTION(fn_ref.val);
|
fn = JS_VALUE_GET_FUNCTION(fn_ref.val);
|
||||||
fn->u.cell.code = code_obj;
|
fn->u.cell.code = code_obj;
|
||||||
fn->u.cell.env_record = JS_NULL;
|
fn->u.cell.env_record = env_record;
|
||||||
fn->u.cell.outer_frame = frame_ref.val;
|
fn->u.cell.outer_frame = frame_ref.val;
|
||||||
|
|
||||||
JSValue out = fn_ref.val;
|
JSValue out = fn_ref.val;
|
||||||
@@ -966,11 +974,11 @@ JSValue js_new_native_function_with_code(JSContext *ctx, JSValue code_obj, int a
|
|||||||
|
|
||||||
/* Create a native (QBE-compiled) function */
|
/* Create a native (QBE-compiled) function */
|
||||||
JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle,
|
JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle,
|
||||||
uint16_t nr_slots, int arity, JSValue outer_frame) {
|
uint16_t nr_slots, int arity, JSValue outer_frame, JSValue env) {
|
||||||
JSValue code_obj = js_new_native_code(ctx, fn_ptr, dl_handle, nr_slots, arity);
|
JSValue code_obj = js_new_native_code(ctx, fn_ptr, dl_handle, nr_slots, arity);
|
||||||
if (JS_IsException(code_obj))
|
if (JS_IsException(code_obj))
|
||||||
return JS_EXCEPTION;
|
return JS_EXCEPTION;
|
||||||
return js_new_native_function_with_code(ctx, code_obj, arity, outer_frame);
|
return js_new_native_function_with_code(ctx, code_obj, arity, outer_frame, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Binary operations helper */
|
/* Binary operations helper */
|
||||||
@@ -1312,6 +1320,20 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
|||||||
|
|
||||||
pc = code->entry_point;
|
pc = code->entry_point;
|
||||||
result = JS_NULL;
|
result = JS_NULL;
|
||||||
|
|
||||||
|
/* Fire trace hook for top-level register function entry */
|
||||||
|
if (unlikely(ctx->trace_hook) && (ctx->trace_type & JS_HOOK_CALL)) {
|
||||||
|
js_debug dbg = {0};
|
||||||
|
if (code->name_cstr)
|
||||||
|
snprintf(dbg.name, sizeof(dbg.name), "%s", code->name_cstr);
|
||||||
|
if (code->filename_cstr)
|
||||||
|
snprintf(dbg.filename, sizeof(dbg.filename), "%s", code->filename_cstr);
|
||||||
|
if (code->line_table)
|
||||||
|
dbg.line = code->line_table[code->entry_point].line;
|
||||||
|
dbg.param_n = code->arity;
|
||||||
|
dbg.unique = (int)(uintptr_t)code;
|
||||||
|
ctx->trace_hook(ctx, JS_HOOK_CALL, &dbg, ctx->trace_data);
|
||||||
|
}
|
||||||
} /* end normal path block */
|
} /* end normal path block */
|
||||||
|
|
||||||
vm_dispatch:
|
vm_dispatch:
|
||||||
@@ -1396,6 +1418,14 @@ vm_dispatch:
|
|||||||
DT(MACH_IS_ARRAY), DT(MACH_IS_FUNC),
|
DT(MACH_IS_ARRAY), DT(MACH_IS_FUNC),
|
||||||
DT(MACH_IS_RECORD), DT(MACH_IS_STONE),
|
DT(MACH_IS_RECORD), DT(MACH_IS_STONE),
|
||||||
DT(MACH_LENGTH), DT(MACH_IS_PROXY),
|
DT(MACH_LENGTH), DT(MACH_IS_PROXY),
|
||||||
|
DT(MACH_IS_BLOB), DT(MACH_IS_DATA),
|
||||||
|
DT(MACH_IS_TRUE), DT(MACH_IS_FALSE),
|
||||||
|
DT(MACH_IS_FIT), DT(MACH_IS_CHAR),
|
||||||
|
DT(MACH_IS_DIGIT), DT(MACH_IS_LETTER),
|
||||||
|
DT(MACH_IS_LOWER), DT(MACH_IS_UPPER),
|
||||||
|
DT(MACH_IS_WS), DT(MACH_IS_ACTOR),
|
||||||
|
DT(MACH_APPLY),
|
||||||
|
DT(MACH_WARYTRUE), DT(MACH_WARYFALSE), DT(MACH_JMPEMPTY),
|
||||||
};
|
};
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
#undef DT
|
#undef DT
|
||||||
@@ -1988,21 +2018,12 @@ vm_dispatch:
|
|||||||
int depth = b;
|
int depth = b;
|
||||||
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
|
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
|
||||||
if (!target) {
|
assert(depth > 0);
|
||||||
fprintf(stderr, "GETUP: NULL outer_frame at depth 0! pc=%d a=%d depth=%d slot=%d nr_slots=%d instr=0x%08x\n",
|
assert(target != NULL);
|
||||||
pc-1, a, depth, c, code->nr_slots, instr);
|
|
||||||
result = JS_RaiseDisrupt(ctx, "GETUP: NULL outer_frame");
|
|
||||||
goto disrupt;
|
|
||||||
}
|
|
||||||
for (int d = 1; d < depth; d++) {
|
for (int d = 1; d < depth; d++) {
|
||||||
fn = JS_VALUE_GET_FUNCTION(target->function);
|
fn = JS_VALUE_GET_FUNCTION(target->function);
|
||||||
JSFrameRegister *next = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
|
JSFrameRegister *next = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
|
||||||
if (!next) {
|
assert(next != NULL);
|
||||||
fprintf(stderr, "GETUP: NULL outer_frame at depth %d! pc=%d a=%d depth=%d slot=%d nr_slots=%d instr=0x%08x\n",
|
|
||||||
d, pc-1, a, depth, c, code->nr_slots, instr);
|
|
||||||
result = JS_RaiseDisrupt(ctx, "GETUP: NULL outer_frame at depth %d", d);
|
|
||||||
goto disrupt;
|
|
||||||
}
|
|
||||||
target = next;
|
target = next;
|
||||||
}
|
}
|
||||||
stone_mutable_text(target->slots[c]);
|
stone_mutable_text(target->slots[c]);
|
||||||
@@ -2015,22 +2036,14 @@ vm_dispatch:
|
|||||||
int depth = b;
|
int depth = b;
|
||||||
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
|
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
|
||||||
|
assert(depth > 0);
|
||||||
|
assert(target != NULL);
|
||||||
for (int d = 1; d < depth; d++) {
|
for (int d = 1; d < depth; d++) {
|
||||||
fn = JS_VALUE_GET_FUNCTION(target->function);
|
fn = JS_VALUE_GET_FUNCTION(target->function);
|
||||||
target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
|
target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
|
||||||
|
assert(target != NULL);
|
||||||
}
|
}
|
||||||
{
|
assert((unsigned)c < objhdr_cap56(target->header));
|
||||||
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];
|
target->slots[c] = frame->slots[a];
|
||||||
VM_BREAK();
|
VM_BREAK();
|
||||||
}
|
}
|
||||||
@@ -2055,12 +2068,7 @@ vm_dispatch:
|
|||||||
}
|
}
|
||||||
|
|
||||||
VM_CASE(MACH_JMPTRUE): {
|
VM_CASE(MACH_JMPTRUE): {
|
||||||
JSValue v = frame->slots[a];
|
if (frame->slots[a] == JS_TRUE) {
|
||||||
int cond;
|
|
||||||
if (v == JS_TRUE) cond = 1;
|
|
||||||
else if (v == JS_FALSE || v == JS_NULL) cond = 0;
|
|
||||||
else cond = JS_ToBool(ctx, v);
|
|
||||||
if (cond) {
|
|
||||||
int offset = MACH_GET_sBx(instr);
|
int offset = MACH_GET_sBx(instr);
|
||||||
pc = (uint32_t)((int32_t)pc + offset);
|
pc = (uint32_t)((int32_t)pc + offset);
|
||||||
if (offset < 0) {
|
if (offset < 0) {
|
||||||
@@ -2081,12 +2089,7 @@ vm_dispatch:
|
|||||||
}
|
}
|
||||||
|
|
||||||
VM_CASE(MACH_JMPFALSE): {
|
VM_CASE(MACH_JMPFALSE): {
|
||||||
JSValue v = frame->slots[a];
|
if (frame->slots[a] == JS_FALSE) {
|
||||||
int cond;
|
|
||||||
if (v == JS_TRUE) cond = 1;
|
|
||||||
else if (v == JS_FALSE || v == JS_NULL) cond = 0;
|
|
||||||
else cond = JS_ToBool(ctx, v);
|
|
||||||
if (!cond) {
|
|
||||||
int offset = MACH_GET_sBx(instr);
|
int offset = MACH_GET_sBx(instr);
|
||||||
pc = (uint32_t)((int32_t)pc + offset);
|
pc = (uint32_t)((int32_t)pc + offset);
|
||||||
if (offset < 0) {
|
if (offset < 0) {
|
||||||
@@ -2118,6 +2121,8 @@ vm_dispatch:
|
|||||||
stone_mutable_text(frame->slots[a]);
|
stone_mutable_text(frame->slots[a]);
|
||||||
result = frame->slots[a];
|
result = frame->slots[a];
|
||||||
if (!JS_IsPtr(frame->caller)) goto done;
|
if (!JS_IsPtr(frame->caller)) goto done;
|
||||||
|
if (unlikely(ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET))
|
||||||
|
ctx->trace_hook(ctx, JS_HOOK_RET, NULL, ctx->trace_data);
|
||||||
{
|
{
|
||||||
#ifdef VALIDATE_GC
|
#ifdef VALIDATE_GC
|
||||||
const char *callee_name = "?";
|
const char *callee_name = "?";
|
||||||
@@ -2163,6 +2168,8 @@ vm_dispatch:
|
|||||||
VM_CASE(MACH_RETNIL):
|
VM_CASE(MACH_RETNIL):
|
||||||
result = JS_NULL;
|
result = JS_NULL;
|
||||||
if (!JS_IsPtr(frame->caller)) goto done;
|
if (!JS_IsPtr(frame->caller)) goto done;
|
||||||
|
if (unlikely(ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET))
|
||||||
|
ctx->trace_hook(ctx, JS_HOOK_RET, NULL, ctx->trace_data);
|
||||||
{
|
{
|
||||||
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
||||||
frame->caller = JS_NULL;
|
frame->caller = JS_NULL;
|
||||||
@@ -2386,6 +2393,182 @@ vm_dispatch:
|
|||||||
frame->slots[a] = JS_NewBool(ctx, is_proxy);
|
frame->slots[a] = JS_NewBool(ctx, is_proxy);
|
||||||
VM_BREAK();
|
VM_BREAK();
|
||||||
}
|
}
|
||||||
|
VM_CASE(MACH_IS_BLOB):
|
||||||
|
frame->slots[a] = JS_NewBool(ctx, mist_is_blob(frame->slots[b]));
|
||||||
|
VM_BREAK();
|
||||||
|
VM_CASE(MACH_IS_DATA): {
|
||||||
|
JSValue v = frame->slots[b];
|
||||||
|
/* data is text, number, logical, array, blob, or record — not function, null */
|
||||||
|
int result = (v != JS_NULL && !mist_is_function(v));
|
||||||
|
frame->slots[a] = JS_NewBool(ctx, result);
|
||||||
|
VM_BREAK();
|
||||||
|
}
|
||||||
|
VM_CASE(MACH_IS_TRUE):
|
||||||
|
frame->slots[a] = JS_NewBool(ctx, frame->slots[b] == JS_TRUE);
|
||||||
|
VM_BREAK();
|
||||||
|
VM_CASE(MACH_IS_FALSE):
|
||||||
|
frame->slots[a] = JS_NewBool(ctx, frame->slots[b] == JS_FALSE);
|
||||||
|
VM_BREAK();
|
||||||
|
VM_CASE(MACH_IS_FIT): {
|
||||||
|
JSValue v = frame->slots[b];
|
||||||
|
int result = 0;
|
||||||
|
if (JS_IsInt(v)) {
|
||||||
|
result = 1;
|
||||||
|
} else if (JS_IsShortFloat(v)) {
|
||||||
|
double d = JS_VALUE_GET_FLOAT64(v);
|
||||||
|
result = (isfinite(d) && trunc(d) == d && fabs(d) <= 9007199254740992.0);
|
||||||
|
}
|
||||||
|
frame->slots[a] = JS_NewBool(ctx, result);
|
||||||
|
VM_BREAK();
|
||||||
|
}
|
||||||
|
VM_CASE(MACH_IS_CHAR): {
|
||||||
|
JSValue v = frame->slots[b];
|
||||||
|
int result = 0;
|
||||||
|
if (MIST_IsImmediateASCII(v))
|
||||||
|
result = (MIST_GetImmediateASCIILen(v) == 1);
|
||||||
|
else if (mist_is_text(v))
|
||||||
|
result = (js_string_value_len(v) == 1);
|
||||||
|
frame->slots[a] = JS_NewBool(ctx, result);
|
||||||
|
VM_BREAK();
|
||||||
|
}
|
||||||
|
VM_CASE(MACH_IS_DIGIT): {
|
||||||
|
JSValue v = frame->slots[b];
|
||||||
|
int result = 0;
|
||||||
|
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
|
||||||
|
int ch = MIST_GetImmediateASCIIChar(v, 0);
|
||||||
|
result = (ch >= '0' && ch <= '9');
|
||||||
|
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
|
||||||
|
uint32_t ch = js_string_value_get(v, 0);
|
||||||
|
result = (ch >= '0' && ch <= '9');
|
||||||
|
}
|
||||||
|
frame->slots[a] = JS_NewBool(ctx, result);
|
||||||
|
VM_BREAK();
|
||||||
|
}
|
||||||
|
VM_CASE(MACH_IS_LETTER): {
|
||||||
|
JSValue v = frame->slots[b];
|
||||||
|
int result = 0;
|
||||||
|
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
|
||||||
|
int ch = MIST_GetImmediateASCIIChar(v, 0);
|
||||||
|
result = ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'));
|
||||||
|
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
|
||||||
|
uint32_t ch = js_string_value_get(v, 0);
|
||||||
|
result = ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'));
|
||||||
|
}
|
||||||
|
frame->slots[a] = JS_NewBool(ctx, result);
|
||||||
|
VM_BREAK();
|
||||||
|
}
|
||||||
|
VM_CASE(MACH_IS_LOWER): {
|
||||||
|
JSValue v = frame->slots[b];
|
||||||
|
int result = 0;
|
||||||
|
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
|
||||||
|
int ch = MIST_GetImmediateASCIIChar(v, 0);
|
||||||
|
result = (ch >= 'a' && ch <= 'z');
|
||||||
|
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
|
||||||
|
uint32_t ch = js_string_value_get(v, 0);
|
||||||
|
result = (ch >= 'a' && ch <= 'z');
|
||||||
|
}
|
||||||
|
frame->slots[a] = JS_NewBool(ctx, result);
|
||||||
|
VM_BREAK();
|
||||||
|
}
|
||||||
|
VM_CASE(MACH_IS_UPPER): {
|
||||||
|
JSValue v = frame->slots[b];
|
||||||
|
int result = 0;
|
||||||
|
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
|
||||||
|
int ch = MIST_GetImmediateASCIIChar(v, 0);
|
||||||
|
result = (ch >= 'A' && ch <= 'Z');
|
||||||
|
} else if (mist_is_text(v) && js_string_value_len(v) == 1) {
|
||||||
|
uint32_t ch = js_string_value_get(v, 0);
|
||||||
|
result = (ch >= 'A' && ch <= 'Z');
|
||||||
|
}
|
||||||
|
frame->slots[a] = JS_NewBool(ctx, result);
|
||||||
|
VM_BREAK();
|
||||||
|
}
|
||||||
|
VM_CASE(MACH_IS_WS): {
|
||||||
|
JSValue v = frame->slots[b];
|
||||||
|
int result = 0;
|
||||||
|
if (MIST_IsImmediateASCII(v)) {
|
||||||
|
int len = MIST_GetImmediateASCIILen(v);
|
||||||
|
if (len > 0) {
|
||||||
|
result = 1;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
int ch = MIST_GetImmediateASCIIChar(v, i);
|
||||||
|
if (!(ch == ' ' || ch == '\t' || ch == '\n'
|
||||||
|
|| ch == '\r' || ch == '\f' || ch == '\v')) {
|
||||||
|
result = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (mist_is_text(v)) {
|
||||||
|
int len = js_string_value_len(v);
|
||||||
|
if (len > 0) {
|
||||||
|
result = 1;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
uint32_t ch = js_string_value_get(v, i);
|
||||||
|
if (!(ch == ' ' || ch == '\t' || ch == '\n'
|
||||||
|
|| ch == '\r' || ch == '\f' || ch == '\v')) {
|
||||||
|
result = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frame->slots[a] = JS_NewBool(ctx, result);
|
||||||
|
VM_BREAK();
|
||||||
|
}
|
||||||
|
VM_CASE(MACH_IS_ACTOR): {
|
||||||
|
JSValue v = frame->slots[b];
|
||||||
|
int result = 0;
|
||||||
|
if (mist_is_record(v) && !JS_IsNull(ctx->actor_sym)) {
|
||||||
|
result = JS_HasPropertyKey(ctx, v, ctx->actor_sym) > 0;
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
}
|
||||||
|
frame->slots[a] = JS_NewBool(ctx, result);
|
||||||
|
VM_BREAK();
|
||||||
|
}
|
||||||
|
VM_CASE(MACH_APPLY): {
|
||||||
|
/* A=dest, B=fn, C=arr_or_val */
|
||||||
|
JSValue fn_val = frame->slots[b];
|
||||||
|
JSValue arg_val = frame->slots[c];
|
||||||
|
if (!mist_is_function(fn_val)) {
|
||||||
|
frame->slots[a] = fn_val;
|
||||||
|
VM_BREAK();
|
||||||
|
}
|
||||||
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
|
||||||
|
JSValue ret;
|
||||||
|
ctx->reg_current_frame = frame_ref.val;
|
||||||
|
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
||||||
|
ctx->vm_call_depth++;
|
||||||
|
if (!mist_is_array(arg_val)) {
|
||||||
|
/* Non-array: use as single argument */
|
||||||
|
if (!mach_check_call_arity(ctx, fn, 1)) {
|
||||||
|
ctx->vm_call_depth--;
|
||||||
|
goto disrupt;
|
||||||
|
}
|
||||||
|
ret = JS_CallInternal(ctx, fn_val, JS_NULL, 1, &arg_val, 0);
|
||||||
|
} else {
|
||||||
|
JSArray *arr = JS_VALUE_GET_ARRAY(arg_val);
|
||||||
|
int len = arr->len;
|
||||||
|
if (!mach_check_call_arity(ctx, fn, len)) {
|
||||||
|
ctx->vm_call_depth--;
|
||||||
|
goto disrupt;
|
||||||
|
}
|
||||||
|
if (len == 0) {
|
||||||
|
ret = JS_CallInternal(ctx, fn_val, JS_NULL, 0, NULL, 0);
|
||||||
|
} else {
|
||||||
|
JSValue *args = alloca(sizeof(JSValue) * len);
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
args[i] = arr->values[i];
|
||||||
|
ret = JS_CallInternal(ctx, fn_val, JS_NULL, len, args, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx->vm_call_depth--;
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
ctx->reg_current_frame = JS_NULL;
|
||||||
|
if (JS_IsException(ret)) goto disrupt;
|
||||||
|
frame->slots[a] = ret;
|
||||||
|
VM_BREAK();
|
||||||
|
}
|
||||||
/* Logical */
|
/* Logical */
|
||||||
VM_CASE(MACH_NOT): {
|
VM_CASE(MACH_NOT): {
|
||||||
int bval = JS_ToBool(ctx, frame->slots[b]);
|
int bval = JS_ToBool(ctx, frame->slots[b]);
|
||||||
@@ -2571,6 +2754,19 @@ vm_dispatch:
|
|||||||
if (fn->kind == JS_FUNC_KIND_REGISTER) {
|
if (fn->kind == JS_FUNC_KIND_REGISTER) {
|
||||||
/* Register function: FRAME already allocated nr_slots — just switch */
|
/* Register function: FRAME already allocated nr_slots — just switch */
|
||||||
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
|
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
|
||||||
|
/* Fire trace hook for register-to-register call */
|
||||||
|
if (unlikely(ctx->trace_hook) && (ctx->trace_type & JS_HOOK_CALL)) {
|
||||||
|
js_debug dbg = {0};
|
||||||
|
if (fn_code->name_cstr)
|
||||||
|
snprintf(dbg.name, sizeof(dbg.name), "%s", fn_code->name_cstr);
|
||||||
|
if (fn_code->filename_cstr)
|
||||||
|
snprintf(dbg.filename, sizeof(dbg.filename), "%s", fn_code->filename_cstr);
|
||||||
|
if (fn_code->line_table)
|
||||||
|
dbg.line = fn_code->line_table[fn_code->entry_point].line;
|
||||||
|
dbg.param_n = fn_code->arity;
|
||||||
|
dbg.unique = (int)(uintptr_t)fn_code;
|
||||||
|
ctx->trace_hook(ctx, JS_HOOK_CALL, &dbg, ctx->trace_data);
|
||||||
|
}
|
||||||
/* Save return info */
|
/* Save return info */
|
||||||
frame->address = JS_NewInt32(ctx, (pc << 16) | b);
|
frame->address = JS_NewInt32(ctx, (pc << 16) | b);
|
||||||
fr->caller = JS_MKPTR(frame);
|
fr->caller = JS_MKPTR(frame);
|
||||||
@@ -2626,6 +2822,23 @@ vm_dispatch:
|
|||||||
|
|
||||||
if (fn->kind == JS_FUNC_KIND_REGISTER) {
|
if (fn->kind == JS_FUNC_KIND_REGISTER) {
|
||||||
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
|
JSCodeRegister *fn_code = JS_VALUE_GET_CODE(FN_READ_CODE(fn))->u.reg.code;
|
||||||
|
/* Tail call: fire RET for current, CALL for new */
|
||||||
|
if (unlikely(ctx->trace_hook)) {
|
||||||
|
if (ctx->trace_type & JS_HOOK_RET)
|
||||||
|
ctx->trace_hook(ctx, JS_HOOK_RET, NULL, ctx->trace_data);
|
||||||
|
if (ctx->trace_type & JS_HOOK_CALL) {
|
||||||
|
js_debug dbg = {0};
|
||||||
|
if (fn_code->name_cstr)
|
||||||
|
snprintf(dbg.name, sizeof(dbg.name), "%s", fn_code->name_cstr);
|
||||||
|
if (fn_code->filename_cstr)
|
||||||
|
snprintf(dbg.filename, sizeof(dbg.filename), "%s", fn_code->filename_cstr);
|
||||||
|
if (fn_code->line_table)
|
||||||
|
dbg.line = fn_code->line_table[fn_code->entry_point].line;
|
||||||
|
dbg.param_n = fn_code->arity;
|
||||||
|
dbg.unique = (int)(uintptr_t)fn_code;
|
||||||
|
ctx->trace_hook(ctx, JS_HOOK_CALL, &dbg, ctx->trace_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
int current_slots = (int)objhdr_cap56(frame->header);
|
int current_slots = (int)objhdr_cap56(frame->header);
|
||||||
|
|
||||||
if (fn_code->nr_slots <= current_slots) {
|
if (fn_code->nr_slots <= current_slots) {
|
||||||
@@ -2695,6 +2908,67 @@ vm_dispatch:
|
|||||||
VM_BREAK();
|
VM_BREAK();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Wary jumps — coerce via JS_ToBool (old JMPTRUE/JMPFALSE behavior) */
|
||||||
|
VM_CASE(MACH_WARYTRUE): {
|
||||||
|
JSValue v = frame->slots[a];
|
||||||
|
int cond;
|
||||||
|
if (v == JS_TRUE) cond = 1;
|
||||||
|
else if (v == JS_FALSE || v == JS_NULL) cond = 0;
|
||||||
|
else cond = JS_ToBool(ctx, v);
|
||||||
|
if (cond) {
|
||||||
|
int offset = MACH_GET_sBx(instr);
|
||||||
|
pc = (uint32_t)((int32_t)pc + offset);
|
||||||
|
if (offset < 0) {
|
||||||
|
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
|
||||||
|
if (pf == 2) {
|
||||||
|
result = JS_RaiseDisrupt(ctx, "interrupted");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (pf == 1) {
|
||||||
|
if (ctx->vm_call_depth > 0)
|
||||||
|
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
|
||||||
|
else
|
||||||
|
goto suspend;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VM_BREAK();
|
||||||
|
}
|
||||||
|
|
||||||
|
VM_CASE(MACH_WARYFALSE): {
|
||||||
|
JSValue v = frame->slots[a];
|
||||||
|
int cond;
|
||||||
|
if (v == JS_TRUE) cond = 1;
|
||||||
|
else if (v == JS_FALSE || v == JS_NULL) cond = 0;
|
||||||
|
else cond = JS_ToBool(ctx, v);
|
||||||
|
if (!cond) {
|
||||||
|
int offset = MACH_GET_sBx(instr);
|
||||||
|
pc = (uint32_t)((int32_t)pc + offset);
|
||||||
|
if (offset < 0) {
|
||||||
|
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
|
||||||
|
if (pf == 2) {
|
||||||
|
result = JS_RaiseDisrupt(ctx, "interrupted");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
if (pf == 1) {
|
||||||
|
if (ctx->vm_call_depth > 0)
|
||||||
|
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
|
||||||
|
else
|
||||||
|
goto suspend;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VM_BREAK();
|
||||||
|
}
|
||||||
|
|
||||||
|
VM_CASE(MACH_JMPEMPTY): {
|
||||||
|
if (frame->slots[a] == JS_EMPTY_TEXT) {
|
||||||
|
int offset = MACH_GET_sBx(instr);
|
||||||
|
pc = (uint32_t)((int32_t)pc + offset);
|
||||||
|
}
|
||||||
|
VM_BREAK();
|
||||||
|
}
|
||||||
|
|
||||||
/* Disrupt (mcode alias) */
|
/* Disrupt (mcode alias) */
|
||||||
VM_CASE(MACH_DISRUPT):
|
VM_CASE(MACH_DISRUPT):
|
||||||
goto disrupt;
|
goto disrupt;
|
||||||
@@ -2768,6 +3042,9 @@ suspend:
|
|||||||
return result;
|
return result;
|
||||||
|
|
||||||
done:
|
done:
|
||||||
|
/* Fire trace hook for top-level register function return */
|
||||||
|
if (unlikely(ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET))
|
||||||
|
ctx->trace_hook(ctx, JS_HOOK_RET, NULL, ctx->trace_data);
|
||||||
#ifdef HAVE_ASAN
|
#ifdef HAVE_ASAN
|
||||||
__asan_js_ctx = NULL;
|
__asan_js_ctx = NULL;
|
||||||
#endif
|
#endif
|
||||||
@@ -3031,6 +3308,19 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
|||||||
else if (strcmp(op, "is_stone") == 0) { AB2(MACH_IS_STONE); }
|
else if (strcmp(op, "is_stone") == 0) { AB2(MACH_IS_STONE); }
|
||||||
else if (strcmp(op, "length") == 0) { AB2(MACH_LENGTH); }
|
else if (strcmp(op, "length") == 0) { AB2(MACH_LENGTH); }
|
||||||
else if (strcmp(op, "is_proxy") == 0) { AB2(MACH_IS_PROXY); }
|
else if (strcmp(op, "is_proxy") == 0) { AB2(MACH_IS_PROXY); }
|
||||||
|
else if (strcmp(op, "is_blob") == 0) { AB2(MACH_IS_BLOB); }
|
||||||
|
else if (strcmp(op, "is_data") == 0) { AB2(MACH_IS_DATA); }
|
||||||
|
else if (strcmp(op, "is_true") == 0) { AB2(MACH_IS_TRUE); }
|
||||||
|
else if (strcmp(op, "is_false") == 0) { AB2(MACH_IS_FALSE); }
|
||||||
|
else if (strcmp(op, "is_fit") == 0) { AB2(MACH_IS_FIT); }
|
||||||
|
else if (strcmp(op, "is_char") == 0) { AB2(MACH_IS_CHAR); }
|
||||||
|
else if (strcmp(op, "is_digit") == 0) { AB2(MACH_IS_DIGIT); }
|
||||||
|
else if (strcmp(op, "is_letter") == 0) { AB2(MACH_IS_LETTER); }
|
||||||
|
else if (strcmp(op, "is_lower") == 0) { AB2(MACH_IS_LOWER); }
|
||||||
|
else if (strcmp(op, "is_upper") == 0) { AB2(MACH_IS_UPPER); }
|
||||||
|
else if (strcmp(op, "is_ws") == 0) { AB2(MACH_IS_WS); }
|
||||||
|
else if (strcmp(op, "is_actor") == 0) { AB2(MACH_IS_ACTOR); }
|
||||||
|
else if (strcmp(op, "apply") == 0) { ABC3(MACH_APPLY); }
|
||||||
/* Logical */
|
/* Logical */
|
||||||
else if (strcmp(op, "not") == 0) { AB2(MACH_NOT); }
|
else if (strcmp(op, "not") == 0) { AB2(MACH_NOT); }
|
||||||
else if (strcmp(op, "and") == 0) { ABC3(MACH_AND); }
|
else if (strcmp(op, "and") == 0) { ABC3(MACH_AND); }
|
||||||
@@ -3181,6 +3471,34 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
|||||||
EM(MACH_AsBx(MACH_JMPNOTNULL, reg, 0));
|
EM(MACH_AsBx(MACH_JMPNOTNULL, reg, 0));
|
||||||
ml_patch(&s, pc_now, lbl, 0, reg);
|
ml_patch(&s, pc_now, lbl, 0, reg);
|
||||||
}
|
}
|
||||||
|
else if (strcmp(op, "jump_null") == 0) {
|
||||||
|
int reg = A1;
|
||||||
|
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
|
||||||
|
int pc_now = s.code_count;
|
||||||
|
EM(MACH_AsBx(MACH_JMPNULL, reg, 0));
|
||||||
|
ml_patch(&s, pc_now, lbl, 0, reg);
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "wary_true") == 0) {
|
||||||
|
int reg = A1;
|
||||||
|
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
|
||||||
|
int pc_now = s.code_count;
|
||||||
|
EM(MACH_AsBx(MACH_WARYTRUE, reg, 0));
|
||||||
|
ml_patch(&s, pc_now, lbl, 0, reg);
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "wary_false") == 0) {
|
||||||
|
int reg = A1;
|
||||||
|
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
|
||||||
|
int pc_now = s.code_count;
|
||||||
|
EM(MACH_AsBx(MACH_WARYFALSE, reg, 0));
|
||||||
|
ml_patch(&s, pc_now, lbl, 0, reg);
|
||||||
|
}
|
||||||
|
else if (strcmp(op, "jump_empty") == 0) {
|
||||||
|
int reg = A1;
|
||||||
|
const char *lbl = cJSON_GetArrayItem(it, 2)->valuestring;
|
||||||
|
int pc_now = s.code_count;
|
||||||
|
EM(MACH_AsBx(MACH_JMPEMPTY, reg, 0));
|
||||||
|
ml_patch(&s, pc_now, lbl, 0, reg);
|
||||||
|
}
|
||||||
/* Return / error */
|
/* Return / error */
|
||||||
else if (strcmp(op, "return") == 0) {
|
else if (strcmp(op, "return") == 0) {
|
||||||
EM(MACH_ABC(MACH_RETURN, A1, 0, 0));
|
EM(MACH_ABC(MACH_RETURN, A1, 0, 0));
|
||||||
|
|||||||
@@ -1,30 +1,6 @@
|
|||||||
#ifndef QUICKJS_INTERNAL_H
|
#ifndef QUICKJS_INTERNAL_H
|
||||||
#define QUICKJS_INTERNAL_H
|
#define QUICKJS_INTERNAL_H
|
||||||
|
|
||||||
/*
|
|
||||||
* QuickJS Javascript Engine
|
|
||||||
*
|
|
||||||
* Copyright (c) 2017-2025 Fabrice Bellard
|
|
||||||
* Copyright (c) 2017-2025 Charlie Gordon
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to
|
|
||||||
* deal in the Software without restriction, including without limitation the
|
|
||||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
* sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
@@ -36,6 +12,9 @@
|
|||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#ifndef TARGET_PLAYDATE
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
#include <malloc/malloc.h>
|
#include <malloc/malloc.h>
|
||||||
#elif defined(__linux__) || defined(__GLIBC__)
|
#elif defined(__linux__) || defined(__GLIBC__)
|
||||||
@@ -45,16 +24,176 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "cutils.h"
|
#include "cutils.h"
|
||||||
|
|
||||||
#include "dtoa.h"
|
#include "dtoa.h"
|
||||||
#include "libregexp.h"
|
#include "libregexp.h"
|
||||||
#include "libunicode.h"
|
#include "libunicode.h"
|
||||||
#include "list.h"
|
#include "list.h"
|
||||||
#include "quickjs.h"
|
#include "cell.h"
|
||||||
#include "cJSON.h"
|
#include "cJSON.h"
|
||||||
#include "blob.h"
|
#include "blob.h"
|
||||||
#include "nota.h"
|
#include "nota.h"
|
||||||
#include "wota.h"
|
#include "wota.h"
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Internal API — not for C module authors
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
/* Object header types */
|
||||||
|
enum mist_obj_type {
|
||||||
|
OBJ_ARRAY = 0,
|
||||||
|
OBJ_BLOB = 1,
|
||||||
|
OBJ_TEXT = 2,
|
||||||
|
OBJ_RECORD = 3, // js style objects
|
||||||
|
OBJ_FUNCTION = 4,
|
||||||
|
OBJ_CODE = 5,
|
||||||
|
OBJ_FRAME = 6,
|
||||||
|
OBJ_FORWARD = 7
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Object header bits */
|
||||||
|
#define OBJHDR_S_BIT 3u
|
||||||
|
#define OBJHDR_P_BIT 4u
|
||||||
|
#define OBJHDR_A_BIT 5u
|
||||||
|
#define OBJHDR_R_BIT 7u
|
||||||
|
|
||||||
|
#define OBJHDR_FLAG(bit) ((objhdr_t)1ull << (bit))
|
||||||
|
#define OBJHDR_S_MASK OBJHDR_FLAG (OBJHDR_S_BIT)
|
||||||
|
#define OBJHDR_P_MASK OBJHDR_FLAG (OBJHDR_P_BIT)
|
||||||
|
#define OBJHDR_A_MASK OBJHDR_FLAG (OBJHDR_A_BIT)
|
||||||
|
#define OBJHDR_R_MASK OBJHDR_FLAG (OBJHDR_R_BIT)
|
||||||
|
|
||||||
|
typedef uint64_t word_t; // one actor-memory word
|
||||||
|
typedef uint64_t objhdr_t; // header word
|
||||||
|
typedef uint64_t objref_t; // 56-bit word address (0 = null)
|
||||||
|
|
||||||
|
static inline uint8_t objhdr_type (objhdr_t h) { return (uint8_t)(h & 7u); }
|
||||||
|
static inline int objhdr_s (objhdr_t h) { return (h & OBJHDR_S_MASK) != 0; }
|
||||||
|
|
||||||
|
/* Word size constant */
|
||||||
|
#define JSW 8
|
||||||
|
|
||||||
|
/* Runtime / Context lifecycle */
|
||||||
|
JSRuntime *JS_NewRuntime (void);
|
||||||
|
void JS_FreeRuntime (JSRuntime *rt);
|
||||||
|
void JS_SetMemoryLimit (JSRuntime *rt, size_t limit);
|
||||||
|
void JS_SetPoolSize (JSRuntime *rt, size_t initial, size_t cap);
|
||||||
|
|
||||||
|
JSContext *JS_NewContext (JSRuntime *rt);
|
||||||
|
JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size);
|
||||||
|
void JS_FreeContext (JSContext *s);
|
||||||
|
|
||||||
|
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);
|
||||||
|
void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size);
|
||||||
|
void JS_UpdateStackTop (JSContext *ctx);
|
||||||
|
int JS_GetVMCallDepth(JSContext *ctx);
|
||||||
|
void JS_SetHeapMemoryLimit(JSContext *ctx, size_t limit);
|
||||||
|
void JS_SetPauseFlag(JSContext *ctx, int value);
|
||||||
|
JS_BOOL JS_IsLiveObject (JSRuntime *rt, JSValue obj);
|
||||||
|
|
||||||
|
/* Suspended state */
|
||||||
|
#define JS_TAG_SUSPENDED 0x13 /* 10011 - distinct special tag */
|
||||||
|
#define JS_SUSPENDED ((JSValue)JS_TAG_SUSPENDED)
|
||||||
|
|
||||||
|
static inline JS_BOOL JS_IsSuspended(JSValue v) {
|
||||||
|
return JS_VALUE_GET_TAG(v) == JS_TAG_SUSPENDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef JS_DEFAULT_STACK_SIZE
|
||||||
|
#define JS_DEFAULT_STACK_SIZE (1024 * 1024)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Internal compile flags */
|
||||||
|
#define JS_EVAL_FLAG_COMPILE_ONLY (1 << 5)
|
||||||
|
|
||||||
|
/* Compilation and MachCode */
|
||||||
|
struct cJSON;
|
||||||
|
typedef struct MachCode MachCode;
|
||||||
|
|
||||||
|
void JS_FreeMachCode(MachCode *mc);
|
||||||
|
uint8_t *JS_SerializeMachCode(MachCode *mc, size_t *out_size);
|
||||||
|
MachCode *JS_DeserializeMachCode(const uint8_t *data, size_t size);
|
||||||
|
struct JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env);
|
||||||
|
JSValue JS_RunMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue env);
|
||||||
|
JSValue JS_RunMachMcode(JSContext *ctx, const char *json_str, size_t len, JSValue env);
|
||||||
|
void JS_DumpMachBin(JSContext *ctx, const uint8_t *data, size_t size, JSValue env);
|
||||||
|
MachCode *mach_compile_mcode(struct cJSON *mcode_json);
|
||||||
|
|
||||||
|
/* Debug / Dump utilities */
|
||||||
|
typedef struct JSMemoryUsage {
|
||||||
|
int64_t malloc_size, malloc_limit, memory_used_size;
|
||||||
|
int64_t malloc_count;
|
||||||
|
int64_t memory_used_count;
|
||||||
|
int64_t str_count, str_size;
|
||||||
|
int64_t obj_count, obj_size;
|
||||||
|
int64_t prop_count, prop_size;
|
||||||
|
int64_t shape_count, shape_size;
|
||||||
|
int64_t js_func_count, js_func_size, js_func_code_size;
|
||||||
|
int64_t js_func_pc2line_count, js_func_pc2line_size;
|
||||||
|
int64_t c_func_count, array_count;
|
||||||
|
int64_t fast_array_count, fast_array_elements;
|
||||||
|
int64_t binary_object_count, binary_object_size;
|
||||||
|
} JSMemoryUsage;
|
||||||
|
|
||||||
|
void JS_ComputeMemoryUsage (JSRuntime *rt, JSMemoryUsage *s);
|
||||||
|
void JS_DumpMemoryUsage (FILE *fp, const JSMemoryUsage *s, JSRuntime *rt);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
JS_BOOL show_hidden : 8;
|
||||||
|
JS_BOOL raw_dump : 8;
|
||||||
|
uint32_t max_depth;
|
||||||
|
uint32_t max_string_length;
|
||||||
|
uint32_t max_item_count;
|
||||||
|
} JSPrintValueOptions;
|
||||||
|
|
||||||
|
typedef void JSPrintValueWrite (void *opaque, const char *buf, size_t len);
|
||||||
|
|
||||||
|
void JS_PrintValueSetDefaultOptions (JSPrintValueOptions *options);
|
||||||
|
void JS_PrintValueRT (JSRuntime *rt, JSPrintValueWrite *write_func,
|
||||||
|
void *write_opaque, JSValue val,
|
||||||
|
const JSPrintValueOptions *options);
|
||||||
|
void JS_PrintValue (JSContext *ctx, JSPrintValueWrite *write_func,
|
||||||
|
void *write_opaque, JSValue val,
|
||||||
|
const JSPrintValueOptions *options);
|
||||||
|
|
||||||
|
void js_debug_info (JSContext *js, JSValue fn, js_debug *dbg);
|
||||||
|
|
||||||
|
uint32_t js_debugger_stack_depth (JSContext *ctx);
|
||||||
|
JSValue js_debugger_backtrace_fns (JSContext *ctx);
|
||||||
|
JSValue js_debugger_closure_variables (JSContext *ctx, JSValue fn);
|
||||||
|
JSValue js_debugger_local_variables (JSContext *ctx, int stack_index);
|
||||||
|
void js_debugger_set_closure_variable (JSContext *js, JSValue fn,
|
||||||
|
JSValue var_name, JSValue val);
|
||||||
|
JSValue js_debugger_build_backtrace (JSContext *ctx);
|
||||||
|
JSValue js_debugger_fn_info (JSContext *ctx, JSValue fn);
|
||||||
|
JSValue js_debugger_fn_bytecode (JSContext *js, JSValue fn);
|
||||||
|
void *js_debugger_val_address (JSContext *js, JSValue val);
|
||||||
|
|
||||||
|
/* Stack trace */
|
||||||
|
JSValue JS_GetStack (JSContext *ctx);
|
||||||
|
void JS_CrashPrintStack(JSContext *ctx);
|
||||||
|
|
||||||
|
/* Serialization (internal) */
|
||||||
|
JSValue wota2value(JSContext *js, void *v);
|
||||||
|
void *value2wota(JSContext *js, JSValue v, JSValue replacer, size_t *bytes);
|
||||||
|
JSValue nota2value(JSContext *js, void *nota);
|
||||||
|
void *value2nota(JSContext *js, JSValue v);
|
||||||
|
|
||||||
|
/* Internal module init (called during context init) */
|
||||||
|
JSValue js_core_blob_use(JSContext *js);
|
||||||
|
JSValue js_core_json_use(JSContext *js);
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
End internal API declarations
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
void *js_malloc (JSContext *ctx, size_t size);
|
void *js_malloc (JSContext *ctx, size_t size);
|
||||||
void *js_mallocz (JSContext *ctx, size_t size);
|
void *js_mallocz (JSContext *ctx, size_t size);
|
||||||
|
|
||||||
@@ -702,6 +841,46 @@ typedef struct CCallRoot {
|
|||||||
struct CCallRoot *prev; /* stack for nesting (C -> JS -> C -> ...) */
|
struct CCallRoot *prev; /* stack for nesting (C -> JS -> C -> ...) */
|
||||||
} CCallRoot;
|
} CCallRoot;
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Actor Types (merged from cell_internal.h)
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
/* Letter type for unified message queue */
|
||||||
|
typedef enum {
|
||||||
|
LETTER_BLOB,
|
||||||
|
LETTER_CALLBACK
|
||||||
|
} letter_type;
|
||||||
|
|
||||||
|
typedef struct letter {
|
||||||
|
letter_type type;
|
||||||
|
union {
|
||||||
|
blob *blob_data;
|
||||||
|
JSValue callback;
|
||||||
|
};
|
||||||
|
} letter;
|
||||||
|
|
||||||
|
/* I/O watch entry — one per watched file descriptor */
|
||||||
|
typedef struct {
|
||||||
|
int fd;
|
||||||
|
short events; /* POLLIN, POLLOUT */
|
||||||
|
JSContext *actor;
|
||||||
|
JSValue callback;
|
||||||
|
} io_watch;
|
||||||
|
|
||||||
|
/* Actor state machine constants */
|
||||||
|
#define ACTOR_IDLE 0
|
||||||
|
#define ACTOR_READY 1
|
||||||
|
#define ACTOR_RUNNING 2
|
||||||
|
#define ACTOR_EXHAUSTED 3
|
||||||
|
#define ACTOR_RECLAIMING 4
|
||||||
|
#define ACTOR_SLOW 5
|
||||||
|
#define ACTOR_REFRESHED 6
|
||||||
|
|
||||||
|
#define ACTOR_FAST_TIMER_NS (1000ULL * 1000000)
|
||||||
|
#define ACTOR_SLOW_TIMER_NS (60000ULL * 1000000)
|
||||||
|
#define ACTOR_SLOW_STRIKES_MAX 3
|
||||||
|
#define ACTOR_MEMORY_LIMIT (1024ULL * 1024 * 1024)
|
||||||
|
|
||||||
struct JSContext {
|
struct JSContext {
|
||||||
JSRuntime *rt;
|
JSRuntime *rt;
|
||||||
|
|
||||||
@@ -757,7 +936,6 @@ struct JSContext {
|
|||||||
|
|
||||||
/* if NULL, RegExp compilation is not supported */
|
/* if NULL, RegExp compilation is not supported */
|
||||||
JSValue (*compile_regexp) (JSContext *ctx, JSValue pattern, JSValue flags);
|
JSValue (*compile_regexp) (JSContext *ctx, JSValue pattern, JSValue flags);
|
||||||
void *user_opaque;
|
|
||||||
|
|
||||||
/* GC callback to scan external C-side roots (actor letters, timers) */
|
/* GC callback to scan external C-side roots (actor letters, timers) */
|
||||||
void (*gc_scan_external)(JSContext *ctx,
|
void (*gc_scan_external)(JSContext *ctx,
|
||||||
@@ -778,6 +956,7 @@ struct JSContext {
|
|||||||
uint32_t suspended_pc; /* saved PC for resume */
|
uint32_t suspended_pc; /* saved PC for resume */
|
||||||
int vm_call_depth; /* 0 = pure bytecode, >0 = C frames on stack */
|
int vm_call_depth; /* 0 = pure bytecode, >0 = C frames on stack */
|
||||||
size_t heap_memory_limit; /* 0 = no limit, else max heap bytes */
|
size_t heap_memory_limit; /* 0 = no limit, else max heap bytes */
|
||||||
|
const char *actor_label; /* human-readable label for OOM diagnostics */
|
||||||
|
|
||||||
JSValue current_exception;
|
JSValue current_exception;
|
||||||
|
|
||||||
@@ -794,6 +973,44 @@ struct JSContext {
|
|||||||
// todo: want this, but should be a simple increment/decrement counter while frames are pushed
|
// todo: want this, but should be a simple increment/decrement counter while frames are pushed
|
||||||
size_t stack_depth;
|
size_t stack_depth;
|
||||||
size_t stack_limit;
|
size_t stack_limit;
|
||||||
|
|
||||||
|
/* === Actor fields (merged from cell_rt) === */
|
||||||
|
|
||||||
|
JSGCRef idx_buffer_ref;
|
||||||
|
JSGCRef on_exception_ref;
|
||||||
|
JSGCRef message_handle_ref;
|
||||||
|
JSGCRef unneeded_ref;
|
||||||
|
JSGCRef actor_sym_ref;
|
||||||
|
|
||||||
|
void *init_wota;
|
||||||
|
|
||||||
|
#ifndef TARGET_PLAYDATE
|
||||||
|
pthread_mutex_t *mutex;
|
||||||
|
pthread_mutex_t *msg_mutex;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
char *id;
|
||||||
|
int idx_count;
|
||||||
|
|
||||||
|
letter *letters;
|
||||||
|
struct { uint32_t key; JSValue value; } *timers;
|
||||||
|
|
||||||
|
int state;
|
||||||
|
uint32_t ar;
|
||||||
|
double ar_secs;
|
||||||
|
|
||||||
|
int disrupt;
|
||||||
|
int is_quiescent;
|
||||||
|
int main_thread_only;
|
||||||
|
int affinity;
|
||||||
|
|
||||||
|
uint64_t turn_start_ns;
|
||||||
|
_Atomic uint32_t turn_gen;
|
||||||
|
int slow_strikes;
|
||||||
|
int vm_suspended;
|
||||||
|
|
||||||
|
const char *name;
|
||||||
|
cell_hook actor_trace_hook;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
@@ -817,6 +1034,48 @@ static inline const char *JS_KeyGetStr (JSContext *ctx, char *buf, size_t buf_si
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Actor / Scheduler Declarations (merged from cell_internal.h)
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
|
/* Set by actor_turn/CLI before entering the VM, cleared after.
|
||||||
|
Read by signal_handler to print JS stack on crash. */
|
||||||
|
extern volatile JSContext *g_crash_ctx;
|
||||||
|
|
||||||
|
JSContext *create_actor(void *wota);
|
||||||
|
const char *register_actor(const char *id, JSContext *actor, int mainthread, double ar);
|
||||||
|
void actor_disrupt(JSContext *actor);
|
||||||
|
|
||||||
|
const char *send_message(const char *id, void *msg);
|
||||||
|
void actor_unneeded(JSContext *actor, JSValue fn, double seconds);
|
||||||
|
void script_startup(JSContext *ctx);
|
||||||
|
int uncaught_exception(JSContext *js, JSValue v);
|
||||||
|
int actor_exists(const char *id);
|
||||||
|
|
||||||
|
void set_actor_state(JSContext *actor);
|
||||||
|
void enqueue_actor_priority(JSContext *actor);
|
||||||
|
void actor_clock(JSContext *actor, JSValue fn);
|
||||||
|
uint32_t actor_delay(JSContext *actor, JSValue fn, double seconds);
|
||||||
|
JSValue actor_remove_timer(JSContext *actor, uint32_t timer_id);
|
||||||
|
void actor_watch(JSContext *actor, int fd, short events, JSValue fn);
|
||||||
|
void actor_watch_readable(JSContext *actor, int fd, JSValue fn);
|
||||||
|
void actor_watch_writable(JSContext *actor, int fd, JSValue fn);
|
||||||
|
void actor_unwatch(JSContext *actor, int fd);
|
||||||
|
void exit_handler(void);
|
||||||
|
void actor_loop(void);
|
||||||
|
void actor_initialize(void);
|
||||||
|
void actor_free(JSContext *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);
|
||||||
|
|
||||||
|
JSValue JS_ResumeRegisterVM(JSContext *ctx);
|
||||||
|
|
||||||
|
uint64_t cell_ns(void);
|
||||||
|
void cell_sleep(double seconds);
|
||||||
|
int randombytes(void *buf, size_t n);
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
Constant Text Pool Functions
|
Constant Text Pool Functions
|
||||||
============================================================ */
|
============================================================ */
|
||||||
@@ -1287,8 +1546,8 @@ JSValue js_key_from_string (JSContext *ctx, JSValue val);
|
|||||||
|
|
||||||
/* mach.c exports */
|
/* mach.c exports */
|
||||||
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame);
|
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame);
|
||||||
JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle, uint16_t nr_slots, int arity, JSValue outer_frame);
|
JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle, uint16_t nr_slots, int arity, JSValue outer_frame, JSValue env);
|
||||||
JSValue js_new_native_function_with_code(JSContext *ctx, JSValue code_obj, int arity, JSValue outer_frame);
|
JSValue js_new_native_function_with_code(JSContext *ctx, JSValue code_obj, int arity, JSValue outer_frame, JSValue env_record);
|
||||||
JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count);
|
JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count);
|
||||||
void cell_rt_free_native_state(JSContext *ctx);
|
void cell_rt_free_native_state(JSContext *ctx);
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
/* QBE headers */
|
/* QBE headers */
|
||||||
#include "all.h"
|
#include "all.h"
|
||||||
@@ -154,6 +155,8 @@ JSValue js_os_qbe(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Run the QBE pipeline */
|
/* Run the QBE pipeline */
|
||||||
|
struct timespec t0, t1;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t0);
|
||||||
parse(inf, "<ir>", qbe_dbgfile, qbe_data, qbe_func);
|
parse(inf, "<ir>", qbe_dbgfile, qbe_data, qbe_func);
|
||||||
fclose(inf);
|
fclose(inf);
|
||||||
|
|
||||||
@@ -162,6 +165,11 @@ JSValue js_os_qbe(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
|||||||
fflush(qbe_outf);
|
fflush(qbe_outf);
|
||||||
fclose(qbe_outf);
|
fclose(qbe_outf);
|
||||||
qbe_outf = NULL;
|
qbe_outf = NULL;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &t1);
|
||||||
|
double ms = (t1.tv_sec - t0.tv_sec) * 1000.0 +
|
||||||
|
(t1.tv_nsec - t0.tv_nsec) / 1e6;
|
||||||
|
fprintf(stderr, "[qbe_backend] os.qbe: %.1fms (%zu bytes IL -> %zu bytes asm)\n",
|
||||||
|
ms, ir_len, out_len);
|
||||||
|
|
||||||
JS_FreeCString(js, ir);
|
JS_FreeCString(js, ir);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* string comparison, bitwise ops on floats, and boolean conversion.
|
* string comparison, bitwise ops on floats, and boolean conversion.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "quickjs-internal.h"
|
#include "pit_internal.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -17,10 +17,6 @@ JSValue qbe_new_float64(JSContext *ctx, double d) {
|
|||||||
return __JS_NewFloat64(ctx, d);
|
return __JS_NewFloat64(ctx, d);
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue qbe_new_string(JSContext *ctx, const char *str) {
|
|
||||||
return JS_NewString(ctx, str);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Comparison op IDs (must match qbe.cm float_cmp_op_id values) */
|
/* Comparison op IDs (must match qbe.cm float_cmp_op_id values) */
|
||||||
enum {
|
enum {
|
||||||
QBE_CMP_EQ = 0,
|
QBE_CMP_EQ = 0,
|
||||||
@@ -31,128 +27,89 @@ enum {
|
|||||||
QBE_CMP_GE = 5
|
QBE_CMP_GE = 5
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ============================================================
|
/* Generic comparison helper matching MACH eq/ne/lt/le/gt/ge semantics. */
|
||||||
Float binary arithmetic
|
JSValue cell_rt_cmp(JSContext *ctx, int op, JSValue a, JSValue b) {
|
||||||
============================================================ */
|
if (JS_VALUE_IS_BOTH_INT(a, b)) {
|
||||||
|
int32_t ia = JS_VALUE_GET_INT(a);
|
||||||
static inline JSValue qbe_float_binop(JSContext *ctx, JSValue a, JSValue b,
|
int32_t ib = JS_VALUE_GET_INT(b);
|
||||||
double (*op)(double, double)) {
|
switch (op) {
|
||||||
double da, db;
|
case QBE_CMP_EQ: return JS_NewBool(ctx, ia == ib);
|
||||||
JS_ToFloat64(ctx, &da, a);
|
case QBE_CMP_NE: return JS_NewBool(ctx, ia != ib);
|
||||||
JS_ToFloat64(ctx, &db, b);
|
case QBE_CMP_LT: return JS_NewBool(ctx, ia < ib);
|
||||||
double r = op(da, db);
|
case QBE_CMP_LE: return JS_NewBool(ctx, ia <= ib);
|
||||||
if (!isfinite(r))
|
case QBE_CMP_GT: return JS_NewBool(ctx, ia > ib);
|
||||||
return JS_NULL;
|
case QBE_CMP_GE: return JS_NewBool(ctx, ia >= ib);
|
||||||
return JS_NewFloat64(ctx, r);
|
default: return JS_NewBool(ctx, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static double op_add(double a, double b) { return a + b; }
|
|
||||||
static double op_sub(double a, double b) { return a - b; }
|
|
||||||
static double op_mul(double a, double b) { return a * b; }
|
|
||||||
|
|
||||||
JSValue qbe_float_add(JSContext *ctx, JSValue a, JSValue b) {
|
|
||||||
return qbe_float_binop(ctx, a, b, op_add);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Generic add: concat if both text, float add if both numeric, else type error */
|
|
||||||
JSValue cell_rt_add(JSContext *ctx, JSValue a, JSValue b) {
|
|
||||||
if (JS_IsText(a) && JS_IsText(b))
|
|
||||||
return JS_ConcatString(ctx, a, b);
|
|
||||||
if (JS_IsNumber(a) && JS_IsNumber(b))
|
|
||||||
return qbe_float_binop(ctx, a, b, op_add);
|
|
||||||
JS_RaiseDisrupt(ctx, "cannot add incompatible types");
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b) {
|
|
||||||
return qbe_float_binop(ctx, a, b, op_sub);
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue qbe_float_mul(JSContext *ctx, JSValue a, JSValue b) {
|
|
||||||
return qbe_float_binop(ctx, a, b, op_mul);
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue qbe_float_div(JSContext *ctx, JSValue a, JSValue b) {
|
|
||||||
double da, db;
|
|
||||||
JS_ToFloat64(ctx, &da, a);
|
|
||||||
JS_ToFloat64(ctx, &db, b);
|
|
||||||
if (db == 0.0)
|
|
||||||
return JS_NULL;
|
|
||||||
double r = da / db;
|
|
||||||
if (!isfinite(r))
|
|
||||||
return JS_NULL;
|
|
||||||
return JS_NewFloat64(ctx, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue qbe_float_mod(JSContext *ctx, JSValue a, JSValue b) {
|
|
||||||
double da, db;
|
|
||||||
JS_ToFloat64(ctx, &da, a);
|
|
||||||
JS_ToFloat64(ctx, &db, b);
|
|
||||||
if (db == 0.0)
|
|
||||||
return JS_NULL;
|
|
||||||
double r = fmod(da, db);
|
|
||||||
if (!isfinite(r))
|
|
||||||
return JS_NULL;
|
|
||||||
return JS_NewFloat64(ctx, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue qbe_float_pow(JSContext *ctx, JSValue a, JSValue b) {
|
|
||||||
double da, db;
|
|
||||||
JS_ToFloat64(ctx, &da, a);
|
|
||||||
JS_ToFloat64(ctx, &db, b);
|
|
||||||
double r = pow(da, db);
|
|
||||||
if (!isfinite(r) && isfinite(da) && isfinite(db))
|
|
||||||
return JS_NULL;
|
|
||||||
return JS_NewFloat64(ctx, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================
|
|
||||||
Float unary ops
|
|
||||||
============================================================ */
|
|
||||||
|
|
||||||
JSValue qbe_float_neg(JSContext *ctx, JSValue v) {
|
|
||||||
double d;
|
|
||||||
JS_ToFloat64(ctx, &d, v);
|
|
||||||
return JS_NewFloat64(ctx, -d);
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue qbe_float_inc(JSContext *ctx, JSValue v) {
|
|
||||||
double d;
|
|
||||||
JS_ToFloat64(ctx, &d, v);
|
|
||||||
return JS_NewFloat64(ctx, d + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue qbe_float_dec(JSContext *ctx, JSValue v) {
|
|
||||||
double d;
|
|
||||||
JS_ToFloat64(ctx, &d, v);
|
|
||||||
return JS_NewFloat64(ctx, d - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================
|
|
||||||
Float comparison — returns 0 or 1 for QBE branching
|
|
||||||
============================================================ */
|
|
||||||
|
|
||||||
int qbe_float_cmp(JSContext *ctx, int op, JSValue a, JSValue b) {
|
|
||||||
double da, db;
|
|
||||||
JS_ToFloat64(ctx, &da, a);
|
|
||||||
JS_ToFloat64(ctx, &db, b);
|
|
||||||
switch (op) {
|
|
||||||
case QBE_CMP_EQ: return da == db;
|
|
||||||
case QBE_CMP_NE: return da != db;
|
|
||||||
case QBE_CMP_LT: return da < db;
|
|
||||||
case QBE_CMP_LE: return da <= db;
|
|
||||||
case QBE_CMP_GT: return da > db;
|
|
||||||
case QBE_CMP_GE: return da >= db;
|
|
||||||
default: return 0;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================
|
/* Fast path: identity after chasing forward pointers (matches MACH). */
|
||||||
Boolean conversion wrapper
|
{
|
||||||
============================================================ */
|
JSValue ca = JS_IsPtr(a) ? JS_MKPTR(chase(a)) : a;
|
||||||
|
JSValue cb = JS_IsPtr(b) ? JS_MKPTR(chase(b)) : b;
|
||||||
|
if (ca == cb) {
|
||||||
|
if (op == QBE_CMP_EQ || op == QBE_CMP_LE || op == QBE_CMP_GE)
|
||||||
|
return JS_TRUE;
|
||||||
|
if (op == QBE_CMP_NE)
|
||||||
|
return JS_FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int qbe_to_bool(JSContext *ctx, JSValue v) {
|
if (JS_IsNumber(a) && JS_IsNumber(b)) {
|
||||||
return JS_ToBool(ctx, v);
|
double da, db;
|
||||||
|
JS_ToFloat64(ctx, &da, a);
|
||||||
|
JS_ToFloat64(ctx, &db, b);
|
||||||
|
switch (op) {
|
||||||
|
case QBE_CMP_EQ: return JS_NewBool(ctx, da == db);
|
||||||
|
case QBE_CMP_NE: return JS_NewBool(ctx, da != db);
|
||||||
|
case QBE_CMP_LT: return JS_NewBool(ctx, da < db);
|
||||||
|
case QBE_CMP_LE: return JS_NewBool(ctx, da <= db);
|
||||||
|
case QBE_CMP_GT: return JS_NewBool(ctx, da > db);
|
||||||
|
case QBE_CMP_GE: return JS_NewBool(ctx, da >= db);
|
||||||
|
default: return JS_NewBool(ctx, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mist_is_text(a) && mist_is_text(b)) {
|
||||||
|
int cmp = js_string_compare_value(ctx, a, b, FALSE);
|
||||||
|
switch (op) {
|
||||||
|
case QBE_CMP_EQ: return JS_NewBool(ctx, cmp == 0);
|
||||||
|
case QBE_CMP_NE: return JS_NewBool(ctx, cmp != 0);
|
||||||
|
case QBE_CMP_LT: return JS_NewBool(ctx, cmp < 0);
|
||||||
|
case QBE_CMP_LE: return JS_NewBool(ctx, cmp <= 0);
|
||||||
|
case QBE_CMP_GT: return JS_NewBool(ctx, cmp > 0);
|
||||||
|
case QBE_CMP_GE: return JS_NewBool(ctx, cmp >= 0);
|
||||||
|
default: return JS_NewBool(ctx, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JS_IsNull(a) && JS_IsNull(b)) {
|
||||||
|
if (op == QBE_CMP_EQ || op == QBE_CMP_LE || op == QBE_CMP_GE)
|
||||||
|
return JS_TRUE;
|
||||||
|
return JS_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JS_IsBool(a) && JS_IsBool(b)) {
|
||||||
|
int ba = JS_VALUE_GET_BOOL(a);
|
||||||
|
int bb = JS_VALUE_GET_BOOL(b);
|
||||||
|
switch (op) {
|
||||||
|
case QBE_CMP_EQ: return JS_NewBool(ctx, ba == bb);
|
||||||
|
case QBE_CMP_NE: return JS_NewBool(ctx, ba != bb);
|
||||||
|
case QBE_CMP_LT: return JS_NewBool(ctx, ba < bb);
|
||||||
|
case QBE_CMP_LE: return JS_NewBool(ctx, ba <= bb);
|
||||||
|
case QBE_CMP_GT: return JS_NewBool(ctx, ba > bb);
|
||||||
|
case QBE_CMP_GE: return JS_NewBool(ctx, ba >= bb);
|
||||||
|
default: return JS_NewBool(ctx, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op == QBE_CMP_EQ)
|
||||||
|
return JS_NewBool(ctx, 0);
|
||||||
|
if (op == QBE_CMP_NE)
|
||||||
|
return JS_NewBool(ctx, 1);
|
||||||
|
|
||||||
|
JS_RaiseDisrupt(ctx, "cannot compare: operands must be same type");
|
||||||
|
return JS_EXCEPTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
@@ -190,29 +147,33 @@ JSValue qbe_bitwise_xor(JSContext *ctx, JSValue a, JSValue b) {
|
|||||||
return JS_NewInt32(ctx, ia ^ ib);
|
return JS_NewInt32(ctx, ia ^ ib);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* Concat helper matching MACH_CONCAT semantics exactly. */
|
||||||
Shift ops on floats (convert to int32, shift, re-tag)
|
JSValue cell_rt_concat(JSContext *ctx, JSValue left, JSValue right, int self_assign) {
|
||||||
============================================================ */
|
if (self_assign) {
|
||||||
|
if (JS_IsPtr(left)) {
|
||||||
|
JSText *s = (JSText *)chase(left);
|
||||||
|
int slen = (int)s->length;
|
||||||
|
int rlen = js_string_value_len(right);
|
||||||
|
int cap = (int)objhdr_cap56(s->hdr);
|
||||||
|
if (objhdr_type(s->hdr) == OBJ_TEXT
|
||||||
|
&& !(s->hdr & OBJHDR_S_MASK)
|
||||||
|
&& slen + rlen <= cap) {
|
||||||
|
for (int i = 0; i < rlen; i++)
|
||||||
|
string_put(s, slen + i, js_string_value_get(right, i));
|
||||||
|
s->length = slen + rlen;
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JSValue res = JS_ConcatStringGrow(ctx, left, right);
|
||||||
|
if (JS_IsException(res))
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
JSValue qbe_shift_shl(JSContext *ctx, JSValue a, JSValue b) {
|
JSValue res = JS_ConcatString(ctx, left, right);
|
||||||
int32_t ia, ib;
|
if (JS_IsException(res))
|
||||||
JS_ToInt32(ctx, &ia, a);
|
return JS_EXCEPTION;
|
||||||
JS_ToInt32(ctx, &ib, b);
|
return res;
|
||||||
return JS_NewInt32(ctx, ia << (ib & 31));
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue qbe_shift_sar(JSContext *ctx, JSValue a, JSValue b) {
|
|
||||||
int32_t ia, ib;
|
|
||||||
JS_ToInt32(ctx, &ia, a);
|
|
||||||
JS_ToInt32(ctx, &ib, b);
|
|
||||||
return JS_NewInt32(ctx, ia >> (ib & 31));
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue qbe_shift_shr(JSContext *ctx, JSValue a, JSValue b) {
|
|
||||||
int32_t ia, ib;
|
|
||||||
JS_ToInt32(ctx, &ia, a);
|
|
||||||
JS_ToInt32(ctx, &ib, b);
|
|
||||||
return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
@@ -251,9 +212,6 @@ typedef struct {
|
|||||||
AOTCodeCacheEntry *code_cache;
|
AOTCodeCacheEntry *code_cache;
|
||||||
int code_cache_count;
|
int code_cache_count;
|
||||||
int code_cache_cap;
|
int code_cache_cap;
|
||||||
JSGCRef native_env_ref;
|
|
||||||
int has_native_env;
|
|
||||||
int native_env_ref_inited;
|
|
||||||
AOTGCRefChunk **gc_ref_chunks;
|
AOTGCRefChunk **gc_ref_chunks;
|
||||||
int gc_ref_chunk_count;
|
int gc_ref_chunk_count;
|
||||||
int aot_depth;
|
int aot_depth;
|
||||||
@@ -381,13 +339,6 @@ static JSValue cell_rt_load_field_key(JSContext *ctx, JSValue obj, JSValue key)
|
|||||||
return JS_GetProperty(ctx, obj, key);
|
return JS_GetProperty(ctx, obj, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue cell_rt_load_field(JSContext *ctx, JSValue obj, const char *name) {
|
|
||||||
JSValue key = aot_key_from_cstr(ctx, name);
|
|
||||||
if (JS_IsException(key))
|
|
||||||
return JS_EXCEPTION;
|
|
||||||
return cell_rt_load_field_key(ctx, obj, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue cell_rt_load_field_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) {
|
JSValue cell_rt_load_field_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) {
|
||||||
JSValue key = aot_lit_from_index(ctx, lit_idx);
|
JSValue key = aot_lit_from_index(ctx, lit_idx);
|
||||||
if (JS_IsException(key))
|
if (JS_IsException(key))
|
||||||
@@ -395,29 +346,12 @@ JSValue cell_rt_load_field_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) {
|
|||||||
return cell_rt_load_field_key(ctx, obj, key);
|
return cell_rt_load_field_key(ctx, obj, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Like cell_rt_load_field but without the function guard.
|
|
||||||
Used by load_dynamic when the key happens to be a static string. */
|
|
||||||
JSValue cell_rt_load_prop_str(JSContext *ctx, JSValue obj, const char *name) {
|
|
||||||
JSValue key = aot_key_from_cstr(ctx, name);
|
|
||||||
if (JS_IsException(key))
|
|
||||||
return JS_EXCEPTION;
|
|
||||||
return JS_GetProperty(ctx, obj, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cell_rt_store_field_key(JSContext *ctx, JSValue val, JSValue obj,
|
static int cell_rt_store_field_key(JSContext *ctx, JSValue val, JSValue obj,
|
||||||
JSValue key) {
|
JSValue key) {
|
||||||
int ret = JS_SetProperty(ctx, obj, key, val);
|
int ret = JS_SetProperty(ctx, obj, key, val);
|
||||||
return (ret < 0 || JS_HasException(ctx)) ? 0 : 1;
|
return (ret < 0 || JS_HasException(ctx)) ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cell_rt_store_field(JSContext *ctx, JSValue val, JSValue obj,
|
|
||||||
const char *name) {
|
|
||||||
JSValue key = aot_key_from_cstr(ctx, name);
|
|
||||||
if (JS_IsException(key))
|
|
||||||
return 0;
|
|
||||||
return cell_rt_store_field_key(ctx, val, obj, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
int cell_rt_store_field_lit(JSContext *ctx, JSValue val, JSValue obj,
|
int cell_rt_store_field_lit(JSContext *ctx, JSValue val, JSValue obj,
|
||||||
int64_t lit_idx) {
|
int64_t lit_idx) {
|
||||||
JSValue key = aot_lit_from_index(ctx, lit_idx);
|
JSValue key = aot_lit_from_index(ctx, lit_idx);
|
||||||
@@ -455,70 +389,22 @@ int cell_rt_store_dynamic(JSContext *ctx, JSValue val, JSValue obj,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue cell_rt_load_index(JSContext *ctx, JSValue arr, JSValue idx) {
|
/* --- Env-based variable lookup (env at frame slot 0) --- */
|
||||||
if (JS_IsInt(idx))
|
|
||||||
return JS_GetPropertyNumber(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx));
|
|
||||||
return JS_GetProperty(ctx, arr, idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
int cell_rt_store_index(JSContext *ctx, JSValue val, JSValue arr,
|
JSValue cell_rt_access_env(JSContext *ctx, JSValue env, int64_t lit_idx) {
|
||||||
JSValue idx) {
|
JSValue key = aot_lit_from_index(ctx, lit_idx);
|
||||||
int ret = 0;
|
if (JS_IsException(key))
|
||||||
JSValue nr = JS_NULL;
|
return JS_EXCEPTION;
|
||||||
if (JS_IsInt(idx))
|
/* Try env first (linear scan for stoned records) */
|
||||||
nr = JS_SetPropertyNumber(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx), val);
|
if (!JS_IsNull(env) && JS_IsRecord(env)) {
|
||||||
else
|
JSRecord *rec = (JSRecord *)chase(env);
|
||||||
ret = JS_SetProperty(ctx, arr, idx, val);
|
uint64_t mask = objhdr_cap56(rec->mist_hdr);
|
||||||
if (JS_IsInt(idx))
|
for (uint64_t i = 1; i <= mask; i++) {
|
||||||
return JS_IsException(nr) ? 0 : 1;
|
if (js_key_equal(rec->slots[i].key, key))
|
||||||
return (ret < 0 || JS_HasException(ctx)) ? 0 : 1;
|
return rec->slots[i].val;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Intrinsic/global lookup --- */
|
|
||||||
|
|
||||||
void cell_rt_set_native_env(JSContext *ctx, JSValue env) {
|
|
||||||
NativeRTState *st = native_state(ctx);
|
|
||||||
if (!st) return;
|
|
||||||
if (!JS_IsNull(env) && !JS_IsStone(env)) {
|
|
||||||
fprintf(stderr, "cell_rt_set_native_env: ERROR env not stone\n");
|
|
||||||
abort();
|
|
||||||
}
|
}
|
||||||
/* Drop module literal pool roots before switching native env/module. */
|
/* Fallback to global object */
|
||||||
aot_clear_lit_pool(ctx, st);
|
|
||||||
|
|
||||||
/* Native module boundary: clear per-actor key cache so stale keys
|
|
||||||
cannot survive across context/module lifetimes. */
|
|
||||||
free(st->key_cache);
|
|
||||||
st->key_cache = NULL;
|
|
||||||
st->key_cache_count = 0;
|
|
||||||
st->key_cache_cap = 0;
|
|
||||||
|
|
||||||
if (st->has_native_env && st->native_env_ref_inited) {
|
|
||||||
JS_DeleteGCRef(ctx, &st->native_env_ref);
|
|
||||||
st->native_env_ref_inited = 0;
|
|
||||||
}
|
|
||||||
if (!JS_IsNull(env)) {
|
|
||||||
JS_AddGCRef(ctx, &st->native_env_ref);
|
|
||||||
st->native_env_ref_inited = 1;
|
|
||||||
st->native_env_ref.val = env;
|
|
||||||
st->has_native_env = 1;
|
|
||||||
} else {
|
|
||||||
st->has_native_env = 0;
|
|
||||||
st->native_env_ref.val = JS_NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue cell_rt_get_intrinsic_key(JSContext *ctx, JSValue key) {
|
|
||||||
NativeRTState *st = native_state(ctx);
|
|
||||||
if (!st) return JS_EXCEPTION;
|
|
||||||
/* Check native env first (runtime-provided functions like log) */
|
|
||||||
if (st->has_native_env) {
|
|
||||||
JSValue v = JS_GetProperty(ctx, st->native_env_ref.val, key);
|
|
||||||
if (!JS_IsNull(v))
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
/* Linear scan of global object — avoids hash mismatch issues with
|
|
||||||
stoned records whose keys may be in cold storage */
|
|
||||||
JSValue gobj = ctx->global_obj;
|
JSValue gobj = ctx->global_obj;
|
||||||
if (JS_IsRecord(gobj)) {
|
if (JS_IsRecord(gobj)) {
|
||||||
JSRecord *rec = (JSRecord *)chase(gobj);
|
JSRecord *rec = (JSRecord *)chase(gobj);
|
||||||
@@ -528,70 +414,12 @@ static JSValue cell_rt_get_intrinsic_key(JSContext *ctx, JSValue key) {
|
|||||||
return rec->slots[i].val;
|
return rec->slots[i].val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JS_RaiseDisrupt(ctx, "name is not defined");
|
const char *kstr = JS_ToCString(ctx, key);
|
||||||
|
JS_RaiseDisrupt(ctx, "'%s' is not defined", kstr ? kstr : "?");
|
||||||
|
if (kstr) JS_FreeCString(ctx, kstr);
|
||||||
return JS_EXCEPTION;
|
return JS_EXCEPTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) {
|
|
||||||
JSValue key = aot_key_from_cstr(ctx, name);
|
|
||||||
if (JS_IsException(key))
|
|
||||||
return JS_EXCEPTION;
|
|
||||||
return cell_rt_get_intrinsic_key(ctx, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue cell_rt_get_intrinsic_lit(JSContext *ctx, int64_t lit_idx) {
|
|
||||||
JSValue key = aot_lit_from_index(ctx, lit_idx);
|
|
||||||
if (JS_IsException(key))
|
|
||||||
return JS_EXCEPTION;
|
|
||||||
return cell_rt_get_intrinsic_key(ctx, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Closure access ---
|
|
||||||
Walk the outer_frame chain on JSFunction (JS_FUNC_KIND_NATIVE).
|
|
||||||
The frame's function field links to the JSFunction, whose
|
|
||||||
u.native.outer_frame points to the enclosing frame.
|
|
||||||
GC traces outer_frame naturally — no registry needed. */
|
|
||||||
|
|
||||||
/* Get the outer frame's slots from a frame pointer.
|
|
||||||
The frame's function must be JS_FUNC_KIND_NATIVE. */
|
|
||||||
static JSValue *get_outer_frame_slots(JSValue *fp) {
|
|
||||||
/* fp points to frame->slots[0]; frame header is before it */
|
|
||||||
JSFrameRegister *frame = (JSFrameRegister *)((char *)fp - offsetof(JSFrameRegister, slots));
|
|
||||||
if (JS_IsNull(frame->function))
|
|
||||||
return NULL;
|
|
||||||
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
|
||||||
if (fn->kind != JS_FUNC_KIND_NATIVE)
|
|
||||||
return NULL;
|
|
||||||
JSValue outer = fn->u.cell.outer_frame;
|
|
||||||
if (JS_IsNull(outer))
|
|
||||||
return NULL;
|
|
||||||
JSFrameRegister *outer_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(outer);
|
|
||||||
return (JSValue *)outer_frame->slots;
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth,
|
|
||||||
int64_t slot) {
|
|
||||||
(void)ctx;
|
|
||||||
JSValue *frame = (JSValue *)fp;
|
|
||||||
for (int64_t d = 0; d < depth; d++) {
|
|
||||||
frame = get_outer_frame_slots(frame);
|
|
||||||
if (!frame)
|
|
||||||
return JS_NULL;
|
|
||||||
}
|
|
||||||
return frame[slot];
|
|
||||||
}
|
|
||||||
|
|
||||||
void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
|
|
||||||
int64_t slot) {
|
|
||||||
(void)ctx;
|
|
||||||
JSValue *frame = (JSValue *)fp;
|
|
||||||
for (int64_t d = 0; d < depth; d++) {
|
|
||||||
frame = get_outer_frame_slots(frame);
|
|
||||||
if (!frame) return;
|
|
||||||
}
|
|
||||||
frame[slot] = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- GC-managed AOT frame stack ---
|
/* --- GC-managed AOT frame stack ---
|
||||||
Each native dispatch loop pushes a GC ref so the GC can find and
|
Each native dispatch loop pushes a GC ref so the GC can find and
|
||||||
update the current frame pointer when it moves objects.
|
update the current frame pointer when it moves objects.
|
||||||
@@ -740,18 +568,6 @@ typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
|
|||||||
to call another function (instead of recursing via C stack).
|
to call another function (instead of recursing via C stack).
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
/* Poll pause state on taken backward jumps (AOT backedges).
|
|
||||||
MACH can suspend/resume a register VM frame at pc granularity; native AOT
|
|
||||||
does not currently have an equivalent resume point, so we acknowledge timer
|
|
||||||
pauses by clearing pause_flag and continuing the current turn. */
|
|
||||||
int cell_rt_check_backedge(JSContext *ctx) {
|
|
||||||
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
|
|
||||||
if (pf >= 1) {
|
|
||||||
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void cell_rt_signal_call(JSContext *ctx, void *fp, int64_t frame_slot) {
|
void cell_rt_signal_call(JSContext *ctx, void *fp, int64_t frame_slot) {
|
||||||
NativeRTState *st = native_state(ctx);
|
NativeRTState *st = native_state(ctx);
|
||||||
if (!st) return;
|
if (!st) return;
|
||||||
@@ -779,6 +595,30 @@ static int cell_check_call_arity(JSContext *ctx, JSFunction *fn, int argc) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JSValue cell_rt_apply(JSContext *ctx, JSValue fn_val, JSValue arg_val) {
|
||||||
|
if (!mist_is_function(fn_val))
|
||||||
|
return fn_val;
|
||||||
|
|
||||||
|
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
|
||||||
|
if (!mist_is_array(arg_val)) {
|
||||||
|
if (!cell_check_call_arity(ctx, fn, 1))
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
return JS_CallInternal(ctx, fn_val, JS_NULL, 1, &arg_val, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSArray *arr = JS_VALUE_GET_ARRAY(arg_val);
|
||||||
|
int len = arr->len;
|
||||||
|
if (!cell_check_call_arity(ctx, fn, len))
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
if (len == 0)
|
||||||
|
return JS_CallInternal(ctx, fn_val, JS_NULL, 0, NULL, 0);
|
||||||
|
|
||||||
|
JSValue args[len];
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
args[i] = arr->values[i];
|
||||||
|
return JS_CallInternal(ctx, fn_val, JS_NULL, len, args, 0);
|
||||||
|
}
|
||||||
|
|
||||||
static inline void cell_copy_args_0_4(JSValue *fp, JSValue *argv, int copy) {
|
static inline void cell_copy_args_0_4(JSValue *fp, JSValue *argv, int copy) {
|
||||||
/* fp[0] is `this`; copy args into fp[1..4] */
|
/* fp[0] is `this`; copy args into fp[1..4] */
|
||||||
switch (copy) {
|
switch (copy) {
|
||||||
@@ -895,6 +735,12 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
|
|||||||
|
|
||||||
if (!JS_IsFunction(callee_fn_val)) {
|
if (!JS_IsFunction(callee_fn_val)) {
|
||||||
JS_RaiseDisrupt(ctx, "not a function");
|
JS_RaiseDisrupt(ctx, "not a function");
|
||||||
|
{
|
||||||
|
int ret_info = JS_VALUE_GET_INT(frame->address);
|
||||||
|
int ret_slot = ret_info & 0xFFFF;
|
||||||
|
if (ret_slot != 0xFFFF)
|
||||||
|
fp[ret_slot] = JS_EXCEPTION;
|
||||||
|
}
|
||||||
/* Resume caller with exception pending */
|
/* Resume caller with exception pending */
|
||||||
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr;
|
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr;
|
||||||
@@ -905,6 +751,10 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
|
|||||||
|
|
||||||
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(callee_fn_val);
|
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(callee_fn_val);
|
||||||
if (!cell_check_call_arity(ctx, callee_fn, callee_argc)) {
|
if (!cell_check_call_arity(ctx, callee_fn, callee_argc)) {
|
||||||
|
int ret_info = JS_VALUE_GET_INT(frame->address);
|
||||||
|
int ret_slot = ret_info & 0xFFFF;
|
||||||
|
if (ret_slot != 0xFFFF)
|
||||||
|
fp[ret_slot] = JS_EXCEPTION;
|
||||||
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->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);
|
cell_sync_dl_from_native_fn(st, exc_fn);
|
||||||
@@ -1152,7 +1002,6 @@ static JSValue aot_get_or_create_native_code(JSContext *ctx, NativeRTState *st,
|
|||||||
Called from QBE-generated code during function creation. */
|
Called from QBE-generated code during function creation. */
|
||||||
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp,
|
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp,
|
||||||
int64_t nr_args, int64_t nr_slots) {
|
int64_t nr_args, int64_t nr_slots) {
|
||||||
(void)outer_fp;
|
|
||||||
NativeRTState *st = native_state(ctx);
|
NativeRTState *st = native_state(ctx);
|
||||||
if (!st) return JS_EXCEPTION;
|
if (!st) return JS_EXCEPTION;
|
||||||
if (!st->current_dl_handle)
|
if (!st->current_dl_handle)
|
||||||
@@ -1168,7 +1017,18 @@ JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp,
|
|||||||
if (st->aot_depth > 0)
|
if (st->aot_depth > 0)
|
||||||
outer_frame = aot_gc_ref_at(st, st->aot_depth - 1)->val;
|
outer_frame = aot_gc_ref_at(st, st->aot_depth - 1)->val;
|
||||||
|
|
||||||
return js_new_native_function_with_code(ctx, code_obj, (int)nr_args, outer_frame);
|
/* Read env from the parent frame's function object */
|
||||||
|
JSValue env = JS_NULL;
|
||||||
|
if (outer_fp) {
|
||||||
|
JSFrameRegister *parent = (JSFrameRegister *)((char *)outer_fp - offsetof(JSFrameRegister, slots));
|
||||||
|
if (JS_IsFunction(parent->function)) {
|
||||||
|
JSFunction *parent_fn = JS_VALUE_GET_FUNCTION(parent->function);
|
||||||
|
if (parent_fn->kind == JS_FUNC_KIND_NATIVE)
|
||||||
|
env = parent_fn->u.cell.env_record;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return js_new_native_function_with_code(ctx, code_obj, (int)nr_args, outer_frame, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Frame-based function calling ---
|
/* --- Frame-based function calling ---
|
||||||
@@ -1191,56 +1051,10 @@ JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) {
|
|||||||
return JS_MKPTR(new_frame);
|
return JS_MKPTR(new_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cell_rt_setarg(JSValue frame_val, int64_t idx, JSValue val) {
|
|
||||||
if (frame_val == JS_EXCEPTION || frame_val == JS_NULL) return;
|
|
||||||
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
|
||||||
fr->slots[idx] = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* cell_rt_invoke — still used for non-dispatch-loop paths (e.g. old code) */
|
|
||||||
JSValue cell_rt_invoke(JSContext *ctx, JSValue frame_val) {
|
|
||||||
if (frame_val == JS_EXCEPTION) return JS_EXCEPTION;
|
|
||||||
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
|
||||||
int c_argc = JS_VALUE_GET_INT(fr->address);
|
|
||||||
if (c_argc < 0) c_argc = 0;
|
|
||||||
JSValue fn_val = fr->function;
|
|
||||||
|
|
||||||
if (!JS_IsFunction(fn_val)) {
|
|
||||||
JS_RaiseDisrupt(ctx, "not a function");
|
|
||||||
return JS_EXCEPTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
|
|
||||||
JSValue result;
|
|
||||||
if (!cell_check_call_arity(ctx, fn, c_argc))
|
|
||||||
return JS_EXCEPTION;
|
|
||||||
|
|
||||||
if (fn->kind == JS_FUNC_KIND_C) {
|
|
||||||
result = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
|
||||||
} else if (fn->kind == JS_FUNC_KIND_NATIVE) {
|
|
||||||
result = cell_native_dispatch(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
|
||||||
} else {
|
|
||||||
JSValue args[c_argc > 0 ? c_argc : 1];
|
|
||||||
for (int i = 0; i < c_argc; i++)
|
|
||||||
args[i] = fr->slots[i + 1];
|
|
||||||
result = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, args, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JS_IsException(result))
|
|
||||||
return JS_EXCEPTION;
|
|
||||||
if (JS_HasException(ctx))
|
|
||||||
JS_GetException(ctx);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue cell_rt_goframe(JSContext *ctx, JSValue fn, int64_t nargs) {
|
JSValue cell_rt_goframe(JSContext *ctx, JSValue fn, int64_t nargs) {
|
||||||
return cell_rt_frame(ctx, fn, nargs);
|
return cell_rt_frame(ctx, fn, nargs);
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue cell_rt_goinvoke(JSContext *ctx, JSValue frame_val) {
|
|
||||||
return cell_rt_invoke(ctx, frame_val);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Array push/pop --- */
|
/* --- Array push/pop --- */
|
||||||
|
|
||||||
JSValue cell_rt_push(JSContext *ctx, JSValue arr, JSValue val) {
|
JSValue cell_rt_push(JSContext *ctx, JSValue arr, JSValue val) {
|
||||||
@@ -1268,13 +1082,6 @@ static JSValue cell_rt_delete_key(JSContext *ctx, JSValue obj, JSValue key) {
|
|||||||
return JS_NewBool(ctx, ret >= 0);
|
return JS_NewBool(ctx, ret >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue cell_rt_delete_str(JSContext *ctx, JSValue obj, const char *name) {
|
|
||||||
JSValue key = aot_key_from_cstr(ctx, name);
|
|
||||||
if (JS_IsException(key))
|
|
||||||
return JS_EXCEPTION;
|
|
||||||
return cell_rt_delete_key(ctx, obj, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue cell_rt_delete_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) {
|
JSValue cell_rt_delete_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) {
|
||||||
JSValue key = aot_lit_from_index(ctx, lit_idx);
|
JSValue key = aot_lit_from_index(ctx, lit_idx);
|
||||||
if (JS_IsException(key))
|
if (JS_IsException(key))
|
||||||
@@ -1282,49 +1089,6 @@ JSValue cell_rt_delete_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) {
|
|||||||
return cell_rt_delete_key(ctx, obj, key);
|
return cell_rt_delete_key(ctx, obj, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Typeof --- */
|
|
||||||
|
|
||||||
JSValue cell_rt_typeof(JSContext *ctx, JSValue val) {
|
|
||||||
if (JS_IsNull(val)) return JS_NewString(ctx, "null");
|
|
||||||
if (JS_IsInt(val) || JS_IsNumber(val)) return JS_NewString(ctx, "number");
|
|
||||||
if (JS_IsBool(val)) return JS_NewString(ctx, "logical");
|
|
||||||
if (JS_IsText(val)) return JS_NewString(ctx, "text");
|
|
||||||
if (JS_IsFunction(val)) return JS_NewString(ctx, "function");
|
|
||||||
if (JS_IsArray(val)) return JS_NewString(ctx, "array");
|
|
||||||
if (JS_IsRecord(val)) return JS_NewString(ctx, "object");
|
|
||||||
return JS_NewString(ctx, "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Text comparison stubs (called from QBE type-dispatch branches) --- */
|
|
||||||
|
|
||||||
JSValue cell_rt_lt_text(JSContext *ctx, JSValue a, JSValue b) {
|
|
||||||
const char *sa = JS_ToCString(ctx, a);
|
|
||||||
const char *sb = JS_ToCString(ctx, b);
|
|
||||||
int r = (sa && sb) ? strcmp(sa, sb) < 0 : 0;
|
|
||||||
return JS_NewBool(ctx, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue cell_rt_gt_text(JSContext *ctx, JSValue a, JSValue b) {
|
|
||||||
const char *sa = JS_ToCString(ctx, a);
|
|
||||||
const char *sb = JS_ToCString(ctx, b);
|
|
||||||
int r = (sa && sb) ? strcmp(sa, sb) > 0 : 0;
|
|
||||||
return JS_NewBool(ctx, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue cell_rt_le_text(JSContext *ctx, JSValue a, JSValue b) {
|
|
||||||
const char *sa = JS_ToCString(ctx, a);
|
|
||||||
const char *sb = JS_ToCString(ctx, b);
|
|
||||||
int r = (sa && sb) ? strcmp(sa, sb) <= 0 : 0;
|
|
||||||
return JS_NewBool(ctx, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue cell_rt_ge_text(JSContext *ctx, JSValue a, JSValue b) {
|
|
||||||
const char *sa = JS_ToCString(ctx, a);
|
|
||||||
const char *sb = JS_ToCString(ctx, b);
|
|
||||||
int r = (sa && sb) ? strcmp(sa, sb) >= 0 : 0;
|
|
||||||
return JS_NewBool(ctx, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cell_rt_tol_eq_inner(JSContext *ctx, JSValue a, JSValue b,
|
static int cell_rt_tol_eq_inner(JSContext *ctx, JSValue a, JSValue b,
|
||||||
JSValue tol) {
|
JSValue tol) {
|
||||||
if (JS_IsNumber(a) && JS_IsNumber(b) && JS_IsNumber(tol)) {
|
if (JS_IsNumber(a) && JS_IsNumber(b) && JS_IsNumber(tol)) {
|
||||||
@@ -1358,31 +1122,11 @@ JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b, JSValue tol) {
|
|||||||
return JS_NewBool(ctx, !cell_rt_tol_eq_inner(ctx, a, b, tol));
|
return JS_NewBool(ctx, !cell_rt_tol_eq_inner(ctx, a, b, tol));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Type check: is_proxy (function with arity 2) --- */
|
int cell_rt_is_actor(JSContext *ctx, JSValue v) {
|
||||||
|
int result = 0;
|
||||||
int cell_rt_is_proxy(JSContext *ctx, JSValue v) {
|
if (mist_is_record(v) && !JS_IsNull(ctx->actor_sym))
|
||||||
(void)ctx;
|
result = JS_HasPropertyKey(ctx, v, ctx->actor_sym) > 0;
|
||||||
if (!JS_IsFunction(v)) return 0;
|
return result;
|
||||||
JSFunction *fn = JS_VALUE_GET_FUNCTION(v);
|
|
||||||
return fn->length == 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Identity check (chases forwarding pointers) --- */
|
|
||||||
|
|
||||||
JSValue cell_rt_is_identical(JSContext *ctx, JSValue a, JSValue b) {
|
|
||||||
if (JS_IsPtr(a)) a = JS_MKPTR(chase(a));
|
|
||||||
if (JS_IsPtr(b)) b = JS_MKPTR(chase(b));
|
|
||||||
return JS_NewBool(ctx, a == b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Short-circuit and/or (non-allocating) --- */
|
|
||||||
|
|
||||||
JSValue cell_rt_and(JSContext *ctx, JSValue left, JSValue right) {
|
|
||||||
return JS_ToBool(ctx, left) ? right : left;
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue cell_rt_or(JSContext *ctx, JSValue left, JSValue right) {
|
|
||||||
return JS_ToBool(ctx, left) ? left : right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Exception checking ---
|
/* --- Exception checking ---
|
||||||
@@ -1431,15 +1175,16 @@ JSValue cell_rt_regexp(JSContext *ctx, const char *pattern, const char *flags) {
|
|||||||
Creates a temporary JS_FUNC_KIND_NATIVE function so that the full
|
Creates a temporary JS_FUNC_KIND_NATIVE function so that the full
|
||||||
dispatch loop (tail calls, closures, etc.) works for module-level code. */
|
dispatch loop (tail calls, closures, etc.) works for module-level code. */
|
||||||
static JSValue native_module_run(JSContext *ctx, void *dl_handle,
|
static JSValue native_module_run(JSContext *ctx, void *dl_handle,
|
||||||
cell_compiled_fn entry, int nr_slots) {
|
cell_compiled_fn entry, int nr_slots,
|
||||||
|
JSValue env) {
|
||||||
NativeRTState *st = native_state(ctx);
|
NativeRTState *st = native_state(ctx);
|
||||||
if (!st) return JS_EXCEPTION;
|
if (!st) return JS_EXCEPTION;
|
||||||
void *prev_handle = st->current_dl_handle;
|
void *prev_handle = st->current_dl_handle;
|
||||||
st->current_dl_handle = dl_handle;
|
st->current_dl_handle = dl_handle;
|
||||||
|
|
||||||
/* Create a native function object for the entry point */
|
/* Create a native function object for the entry point, with env on the function */
|
||||||
JSValue func_obj = js_new_native_function(ctx, (void *)entry, dl_handle,
|
JSValue func_obj = js_new_native_function(ctx, (void *)entry, dl_handle,
|
||||||
(uint16_t)nr_slots, 0, JS_NULL);
|
(uint16_t)nr_slots, 0, JS_NULL, env);
|
||||||
if (JS_IsException(func_obj)) {
|
if (JS_IsException(func_obj)) {
|
||||||
st->current_dl_handle = prev_handle;
|
st->current_dl_handle = prev_handle;
|
||||||
return JS_EXCEPTION;
|
return JS_EXCEPTION;
|
||||||
@@ -1459,14 +1204,11 @@ JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle, JSValue env)
|
|||||||
if (!fn)
|
if (!fn)
|
||||||
return JS_RaiseDisrupt(ctx, "cell_main not found in native module dylib");
|
return JS_RaiseDisrupt(ctx, "cell_main not found in native module dylib");
|
||||||
|
|
||||||
/* Make env available for cell_rt_get_intrinsic lookups */
|
|
||||||
cell_rt_set_native_env(ctx, env);
|
|
||||||
|
|
||||||
/* Try to read nr_slots from the module (exported by emitter) */
|
/* Try to read nr_slots from the module (exported by emitter) */
|
||||||
int *slots_ptr = (int *)dlsym(dl_handle, "cell_main_nr_slots");
|
int *slots_ptr = (int *)dlsym(dl_handle, "cell_main_nr_slots");
|
||||||
int nr_slots = slots_ptr ? *slots_ptr : 512;
|
int nr_slots = slots_ptr ? *slots_ptr : 512;
|
||||||
|
|
||||||
return native_module_run(ctx, dl_handle, fn, nr_slots);
|
return native_module_run(ctx, dl_handle, fn, nr_slots, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Load a native module from a dylib handle, trying a named symbol first.
|
/* Load a native module from a dylib handle, trying a named symbol first.
|
||||||
@@ -1482,11 +1224,11 @@ JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const
|
|||||||
fn = (cell_compiled_fn)dlsym(dl_handle, "cell_main");
|
fn = (cell_compiled_fn)dlsym(dl_handle, "cell_main");
|
||||||
used_name = "cell_main";
|
used_name = "cell_main";
|
||||||
}
|
}
|
||||||
if (!fn)
|
if (!fn) {
|
||||||
return JS_RaiseDisrupt(ctx, "symbol not found in native module dylib");
|
return JS_RaiseDisrupt(ctx,
|
||||||
|
"symbol '%s' (and fallback 'cell_main') not found in native module dylib",
|
||||||
/* Make env available for cell_rt_get_intrinsic lookups */
|
sym_name ? sym_name : "(null)");
|
||||||
cell_rt_set_native_env(ctx, env);
|
}
|
||||||
|
|
||||||
/* Try to read nr_slots from the module */
|
/* Try to read nr_slots from the module */
|
||||||
char slots_sym[128];
|
char slots_sym[128];
|
||||||
@@ -1494,7 +1236,7 @@ JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const
|
|||||||
int *slots_ptr = (int *)dlsym(dl_handle, slots_sym);
|
int *slots_ptr = (int *)dlsym(dl_handle, slots_sym);
|
||||||
int nr_slots = slots_ptr ? *slots_ptr : 512;
|
int nr_slots = slots_ptr ? *slots_ptr : 512;
|
||||||
|
|
||||||
return native_module_run(ctx, dl_handle, fn, nr_slots);
|
return native_module_run(ctx, dl_handle, fn, nr_slots, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cell_rt_free_native_state(JSContext *ctx) {
|
void cell_rt_free_native_state(JSContext *ctx) {
|
||||||
@@ -1503,12 +1245,6 @@ void cell_rt_free_native_state(JSContext *ctx) {
|
|||||||
|
|
||||||
aot_clear_lit_pool(ctx, st);
|
aot_clear_lit_pool(ctx, st);
|
||||||
|
|
||||||
if (st->has_native_env && st->native_env_ref_inited) {
|
|
||||||
JS_DeleteGCRef(ctx, &st->native_env_ref);
|
|
||||||
st->native_env_ref_inited = 0;
|
|
||||||
st->native_env_ref.val = JS_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int ci = 0; ci < st->gc_ref_chunk_count; ci++) {
|
for (int ci = 0; ci < st->gc_ref_chunk_count; ci++) {
|
||||||
AOTGCRefChunk *chunk = st->gc_ref_chunks[ci];
|
AOTGCRefChunk *chunk = st->gc_ref_chunks[ci];
|
||||||
if (!chunk) continue;
|
if (!chunk) continue;
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
#include "cell.h"
|
#include "cell.h"
|
||||||
#include "cell_internal.h"
|
#include "pit_internal.h"
|
||||||
#include "quickjs-internal.h"
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
JSC_CCALL(os_createactor,
|
JSC_CCALL(os_createactor,
|
||||||
cell_rt *rt = JS_GetContextOpaque(js);
|
if (js->disrupt)
|
||||||
if (rt->disrupt)
|
|
||||||
return JS_RaiseDisrupt(js, "Can't start a new actor while disrupting.");
|
return JS_RaiseDisrupt(js, "Can't start a new actor while disrupting.");
|
||||||
|
|
||||||
void *startup = value2wota(js, argv[0], JS_NULL, NULL);
|
void *startup = value2wota(js, argv[0], JS_NULL, NULL);
|
||||||
@@ -57,14 +55,12 @@ JSC_CCALL(os_mailbox_push,
|
|||||||
)
|
)
|
||||||
|
|
||||||
JSC_CCALL(os_register_actor,
|
JSC_CCALL(os_register_actor,
|
||||||
cell_rt *rt = JS_GetContextOpaque(js);
|
|
||||||
const char *id = JS_ToCString(js, argv[0]);
|
const char *id = JS_ToCString(js, argv[0]);
|
||||||
double ar;
|
double ar;
|
||||||
JS_ToFloat64(js, &ar, argv[3]);
|
JS_ToFloat64(js, &ar, argv[3]);
|
||||||
const char *err = register_actor(id, rt, JS_ToBool(js, argv[2]), ar);
|
const char *err = register_actor(id, js, JS_ToBool(js, argv[2]), ar);
|
||||||
if (err) return JS_RaiseDisrupt(js, "Could not register actor: %s", err);
|
if (err) return JS_RaiseDisrupt(js, "Could not register actor: %s", err);
|
||||||
rt->message_handle_ref.val = argv[1];
|
js->message_handle_ref.val = argv[1];
|
||||||
rt->context = js;
|
|
||||||
JS_FreeCString(js, id);
|
JS_FreeCString(js, id);
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -77,56 +73,50 @@ JSC_CCALL(os_mailbox_exist,
|
|||||||
)
|
)
|
||||||
|
|
||||||
JSC_CCALL(os_unneeded,
|
JSC_CCALL(os_unneeded,
|
||||||
cell_rt *actor = JS_GetContextOpaque(js);
|
|
||||||
double secs;
|
double secs;
|
||||||
JS_ToFloat64(js, &secs, argv[1]);
|
JS_ToFloat64(js, &secs, argv[1]);
|
||||||
actor_unneeded(actor, argv[0], secs);
|
actor_unneeded(js, argv[0], secs);
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_CCALL(actor_disrupt,
|
JSC_CCALL(actor_disrupt,
|
||||||
cell_rt *crt = JS_GetContextOpaque(js);
|
actor_disrupt(js);
|
||||||
actor_disrupt(crt);
|
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_SCALL(actor_setname,
|
JSC_SCALL(actor_setname,
|
||||||
cell_rt *rt = JS_GetContextOpaque(js);
|
js->name = strdup(str);
|
||||||
rt->name = strdup(str);
|
js->actor_label = js->name;
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_CCALL(actor_on_exception,
|
JSC_CCALL(actor_on_exception,
|
||||||
cell_rt *rt = JS_GetContextOpaque(js);
|
js->on_exception_ref.val = argv[0];
|
||||||
rt->on_exception_ref.val = argv[0];
|
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_CCALL(actor_clock,
|
JSC_CCALL(actor_clock,
|
||||||
if (!JS_IsFunction(argv[0]))
|
if (!JS_IsFunction(argv[0]))
|
||||||
return JS_RaiseDisrupt(js, "Argument must be a function.");
|
return JS_RaiseDisrupt(js, "Argument must be a function.");
|
||||||
|
|
||||||
cell_rt *actor = JS_GetContextOpaque(js);
|
actor_clock(js, argv[0]);
|
||||||
actor_clock(actor, argv[0]);
|
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_CCALL(actor_delay,
|
JSC_CCALL(actor_delay,
|
||||||
if (!JS_IsFunction(argv[0]))
|
if (!JS_IsFunction(argv[0]))
|
||||||
return JS_RaiseDisrupt(js, "Argument must be a function.");
|
return JS_RaiseDisrupt(js, "Argument must be a function.");
|
||||||
|
|
||||||
cell_rt *actor = JS_GetContextOpaque(js);
|
|
||||||
double seconds;
|
double seconds;
|
||||||
JS_ToFloat64(js, &seconds, argv[1]);
|
JS_ToFloat64(js, &seconds, argv[1]);
|
||||||
if (seconds <= 0) {
|
if (seconds <= 0) {
|
||||||
actor_clock(actor, argv[0]);
|
actor_clock(js, argv[0]);
|
||||||
return JS_NULL;
|
return JS_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t id = actor_delay(actor, argv[0], seconds);
|
uint32_t id = actor_delay(js, argv[0], seconds);
|
||||||
return JS_NewUint32(js, id);
|
return JS_NewUint32(js, id);
|
||||||
)
|
)
|
||||||
|
|
||||||
JSC_CCALL(actor_removetimer,
|
JSC_CCALL(actor_removetimer,
|
||||||
cell_rt *actor = JS_GetContextOpaque(js);
|
|
||||||
uint32_t timer_id;
|
uint32_t timer_id;
|
||||||
JS_ToUint32(js, &timer_id, argv[0]);
|
JS_ToUint32(js, &timer_id, argv[0]);
|
||||||
(void)actor_remove_timer(actor, timer_id);
|
(void)actor_remove_timer(js, timer_id);
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Log callback bridge: called from JS_Log, calls ƿit log(channel, [msg, stack])
|
/* Log callback bridge: called from JS_Log, calls ƿit log(channel, [msg, stack])
|
||||||
@@ -143,7 +133,8 @@ static void js_log_callback(JSContext *ctx, const char *channel, const char *msg
|
|||||||
JS_FRAME(ctx);
|
JS_FRAME(ctx);
|
||||||
JS_ROOT(stack, JS_GetStack(ctx));
|
JS_ROOT(stack, JS_GetStack(ctx));
|
||||||
JS_ROOT(args_array, JS_NewArray(ctx));
|
JS_ROOT(args_array, JS_NewArray(ctx));
|
||||||
JS_SetPropertyNumber(ctx, args_array.val, 0, JS_NewString(ctx, msg));
|
JSValue msg_str = JS_NewString(ctx, msg);
|
||||||
|
JS_SetPropertyNumber(ctx, args_array.val, 0, msg_str);
|
||||||
JS_SetPropertyNumber(ctx, args_array.val, 1, stack.val);
|
JS_SetPropertyNumber(ctx, args_array.val, 1, stack.val);
|
||||||
JSValue argv[2];
|
JSValue argv[2];
|
||||||
argv[0] = JS_NewString(ctx, channel);
|
argv[0] = JS_NewString(ctx, channel);
|
||||||
|
|||||||
@@ -1,265 +0,0 @@
|
|||||||
/*
|
|
||||||
* QuickJS atom definitions
|
|
||||||
*
|
|
||||||
* Copyright (c) 2017-2018 Fabrice Bellard
|
|
||||||
* Copyright (c) 2017-2018 Charlie Gordon
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
* THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef DEF
|
|
||||||
|
|
||||||
/* Note: first atoms are considered as keywords in the parser */
|
|
||||||
DEF(null, "null") /* must be first */
|
|
||||||
DEF(false, "false")
|
|
||||||
DEF(true, "true")
|
|
||||||
DEF(if, "if")
|
|
||||||
DEF(else, "else")
|
|
||||||
DEF(return, "return")
|
|
||||||
DEF(go, "go")
|
|
||||||
DEF(var, "var")
|
|
||||||
DEF(def, "def")
|
|
||||||
DEF(this, "this")
|
|
||||||
DEF(delete, "delete")
|
|
||||||
DEF(void, "void")
|
|
||||||
DEF(new, "new")
|
|
||||||
DEF(in, "in")
|
|
||||||
DEF(do, "do")
|
|
||||||
DEF(while, "while")
|
|
||||||
DEF(for, "for")
|
|
||||||
DEF(break, "break")
|
|
||||||
DEF(continue, "continue")
|
|
||||||
DEF(switch, "switch")
|
|
||||||
DEF(case, "case")
|
|
||||||
DEF(default, "default")
|
|
||||||
DEF(throw, "throw")
|
|
||||||
DEF(try, "try")
|
|
||||||
DEF(catch, "catch")
|
|
||||||
DEF(finally, "finally")
|
|
||||||
DEF(function, "function")
|
|
||||||
DEF(debugger, "debugger")
|
|
||||||
DEF(with, "with")
|
|
||||||
/* FutureReservedWord */
|
|
||||||
DEF(class, "class")
|
|
||||||
DEF(const, "const")
|
|
||||||
DEF(enum, "enum")
|
|
||||||
DEF(export, "export")
|
|
||||||
DEF(extends, "extends")
|
|
||||||
DEF(import, "import")
|
|
||||||
DEF(super, "super")
|
|
||||||
/* FutureReservedWords when parsing strict mode code */
|
|
||||||
DEF(implements, "implements")
|
|
||||||
DEF(interface, "interface")
|
|
||||||
DEF(let, "let")
|
|
||||||
DEF(private, "private")
|
|
||||||
DEF(protected, "protected")
|
|
||||||
DEF(public, "public")
|
|
||||||
DEF(static, "static")
|
|
||||||
DEF(yield, "yield")
|
|
||||||
DEF(await, "await")
|
|
||||||
|
|
||||||
/* empty string */
|
|
||||||
DEF(empty_string, "")
|
|
||||||
/* identifiers */
|
|
||||||
DEF(length, "length")
|
|
||||||
DEF(fileName, "fileName")
|
|
||||||
DEF(lineNumber, "lineNumber")
|
|
||||||
DEF(columnNumber, "columnNumber")
|
|
||||||
DEF(message, "message")
|
|
||||||
DEF(cause, "cause")
|
|
||||||
DEF(errors, "errors")
|
|
||||||
DEF(stack, "stack")
|
|
||||||
DEF(name, "name")
|
|
||||||
DEF(toString, "toString")
|
|
||||||
DEF(toLocaleString, "toLocaleString")
|
|
||||||
DEF(valueOf, "valueOf")
|
|
||||||
DEF(eval, "eval")
|
|
||||||
DEF(prototype, "prototype")
|
|
||||||
DEF(constructor, "constructor")
|
|
||||||
DEF(configurable, "configurable")
|
|
||||||
DEF(writable, "writable")
|
|
||||||
DEF(enumerable, "enumerable")
|
|
||||||
DEF(value, "value")
|
|
||||||
DEF(get, "get")
|
|
||||||
DEF(set, "set")
|
|
||||||
DEF(of, "of")
|
|
||||||
DEF(__proto__, "__proto__")
|
|
||||||
DEF(undefined, "undefined")
|
|
||||||
DEF(number, "number")
|
|
||||||
DEF(boolean, "boolean")
|
|
||||||
DEF(string, "string")
|
|
||||||
DEF(object, "object")
|
|
||||||
DEF(symbol, "symbol")
|
|
||||||
DEF(integer, "integer")
|
|
||||||
DEF(unknown, "unknown")
|
|
||||||
DEF(callee, "callee")
|
|
||||||
DEF(caller, "caller")
|
|
||||||
DEF(_eval_, "<eval>")
|
|
||||||
DEF(_ret_, "<ret>")
|
|
||||||
DEF(_var_, "<var>")
|
|
||||||
DEF(_arg_var_, "<arg_var>")
|
|
||||||
DEF(_with_, "<with>")
|
|
||||||
DEF(lastIndex, "lastIndex")
|
|
||||||
DEF(target, "target")
|
|
||||||
DEF(index, "index")
|
|
||||||
DEF(input, "input")
|
|
||||||
DEF(defineProperties, "defineProperties")
|
|
||||||
DEF(apply, "apply")
|
|
||||||
DEF(join, "join")
|
|
||||||
DEF(concat, "concat")
|
|
||||||
DEF(split, "split")
|
|
||||||
DEF(construct, "construct")
|
|
||||||
DEF(getPrototypeOf, "getPrototypeOf")
|
|
||||||
DEF(setPrototypeOf, "setPrototypeOf")
|
|
||||||
DEF(isExtensible, "isExtensible")
|
|
||||||
DEF(preventExtensions, "preventExtensions")
|
|
||||||
DEF(has, "has")
|
|
||||||
DEF(deleteProperty, "deleteProperty")
|
|
||||||
DEF(defineProperty, "defineProperty")
|
|
||||||
DEF(getOwnPropertyDescriptor, "getOwnPropertyDescriptor")
|
|
||||||
DEF(ownKeys, "ownKeys")
|
|
||||||
DEF(add, "add")
|
|
||||||
DEF(done, "done")
|
|
||||||
DEF(next, "next")
|
|
||||||
DEF(values, "values")
|
|
||||||
DEF(source, "source")
|
|
||||||
DEF(flags, "flags")
|
|
||||||
DEF(global, "global")
|
|
||||||
DEF(unicode, "unicode")
|
|
||||||
DEF(raw, "raw")
|
|
||||||
DEF(new_target, "new.target")
|
|
||||||
DEF(this_active_func, "this.active_func")
|
|
||||||
DEF(home_object, "<home_object>")
|
|
||||||
DEF(computed_field, "<computed_field>")
|
|
||||||
DEF(static_computed_field, "<static_computed_field>") /* must come after computed_fields */
|
|
||||||
DEF(class_fields_init, "<class_fields_init>")
|
|
||||||
DEF(brand, "<brand>")
|
|
||||||
DEF(hash_constructor, "#constructor")
|
|
||||||
DEF(as, "as")
|
|
||||||
DEF(from, "from")
|
|
||||||
DEF(meta, "meta")
|
|
||||||
DEF(_default_, "*default*")
|
|
||||||
DEF(_star_, "*")
|
|
||||||
DEF(Module, "Module")
|
|
||||||
DEF(then, "then")
|
|
||||||
DEF(resolve, "resolve")
|
|
||||||
DEF(reject, "reject")
|
|
||||||
DEF(promise, "promise")
|
|
||||||
DEF(proxy, "proxy")
|
|
||||||
DEF(revoke, "revoke")
|
|
||||||
DEF(async, "async")
|
|
||||||
DEF(exec, "exec")
|
|
||||||
DEF(groups, "groups")
|
|
||||||
DEF(indices, "indices")
|
|
||||||
DEF(status, "status")
|
|
||||||
DEF(reason, "reason")
|
|
||||||
DEF(globalThis, "globalThis")
|
|
||||||
DEF(bigint, "bigint")
|
|
||||||
DEF(minus_zero, "-0")
|
|
||||||
DEF(Infinity, "Infinity")
|
|
||||||
DEF(minus_Infinity, "-Infinity")
|
|
||||||
DEF(NaN, "NaN")
|
|
||||||
DEF(hasIndices, "hasIndices")
|
|
||||||
DEF(ignoreCase, "ignoreCase")
|
|
||||||
DEF(multiline, "multiline")
|
|
||||||
DEF(dotAll, "dotAll")
|
|
||||||
DEF(sticky, "sticky")
|
|
||||||
DEF(unicodeSets, "unicodeSets")
|
|
||||||
/* the following 3 atoms are only used with CONFIG_ATOMICS */
|
|
||||||
DEF(not_equal, "not-equal")
|
|
||||||
DEF(timed_out, "timed-out")
|
|
||||||
DEF(ok, "ok")
|
|
||||||
/* */
|
|
||||||
DEF(toJSON, "toJSON")
|
|
||||||
/* class names */
|
|
||||||
DEF(Object, "Object")
|
|
||||||
DEF(Array, "Array")
|
|
||||||
DEF(Error, "Error")
|
|
||||||
DEF(Number, "Number")
|
|
||||||
DEF(String, "String")
|
|
||||||
DEF(Boolean, "Boolean")
|
|
||||||
DEF(Symbol, "Symbol")
|
|
||||||
DEF(Math, "Math")
|
|
||||||
DEF(JSON, "JSON")
|
|
||||||
DEF(Date, "Date")
|
|
||||||
DEF(Function, "Function")
|
|
||||||
DEF(GeneratorFunction, "GeneratorFunction")
|
|
||||||
DEF(ForInIterator, "ForInIterator")
|
|
||||||
DEF(RegExp, "RegExp")
|
|
||||||
DEF(ArrayBuffer, "ArrayBuffer")
|
|
||||||
DEF(SharedArrayBuffer, "SharedArrayBuffer")
|
|
||||||
/* must keep same order as class IDs for typed arrays */
|
|
||||||
DEF(Uint8ClampedArray, "Uint8ClampedArray")
|
|
||||||
DEF(Int8Array, "Int8Array")
|
|
||||||
DEF(Uint8Array, "Uint8Array")
|
|
||||||
DEF(Int16Array, "Int16Array")
|
|
||||||
DEF(Uint16Array, "Uint16Array")
|
|
||||||
DEF(Int32Array, "Int32Array")
|
|
||||||
DEF(Uint32Array, "Uint32Array")
|
|
||||||
DEF(BigInt64Array, "BigInt64Array")
|
|
||||||
DEF(BigUint64Array, "BigUint64Array")
|
|
||||||
DEF(Float16Array, "Float16Array")
|
|
||||||
DEF(Float32Array, "Float32Array")
|
|
||||||
DEF(Float64Array, "Float64Array")
|
|
||||||
DEF(DataView, "DataView")
|
|
||||||
DEF(BigInt, "BigInt")
|
|
||||||
DEF(WeakRef, "WeakRef")
|
|
||||||
DEF(FinalizationRegistry, "FinalizationRegistry")
|
|
||||||
DEF(Map, "Map")
|
|
||||||
DEF(Set, "Set") /* Map + 1 */
|
|
||||||
DEF(WeakMap, "WeakMap") /* Map + 2 */
|
|
||||||
DEF(WeakSet, "WeakSet") /* Map + 3 */
|
|
||||||
DEF(Map_Iterator, "Map Iterator")
|
|
||||||
DEF(Set_Iterator, "Set Iterator")
|
|
||||||
DEF(Array_Iterator, "Array Iterator")
|
|
||||||
DEF(String_Iterator, "String Iterator")
|
|
||||||
DEF(RegExp_String_Iterator, "RegExp String Iterator")
|
|
||||||
DEF(Generator, "Generator")
|
|
||||||
DEF(Proxy, "Proxy")
|
|
||||||
DEF(Promise, "Promise")
|
|
||||||
DEF(PromiseResolveFunction, "PromiseResolveFunction")
|
|
||||||
DEF(PromiseRejectFunction, "PromiseRejectFunction")
|
|
||||||
DEF(AsyncFunction, "AsyncFunction")
|
|
||||||
DEF(AsyncFunctionResolve, "AsyncFunctionResolve")
|
|
||||||
DEF(AsyncFunctionReject, "AsyncFunctionReject")
|
|
||||||
DEF(AsyncGeneratorFunction, "AsyncGeneratorFunction")
|
|
||||||
DEF(AsyncGenerator, "AsyncGenerator")
|
|
||||||
DEF(EvalError, "EvalError")
|
|
||||||
DEF(RangeError, "RangeError")
|
|
||||||
DEF(ReferenceError, "ReferenceError")
|
|
||||||
DEF(SyntaxError, "SyntaxError")
|
|
||||||
DEF(TypeError, "TypeError")
|
|
||||||
DEF(URIError, "URIError")
|
|
||||||
DEF(InternalError, "InternalError")
|
|
||||||
/* symbols */
|
|
||||||
DEF(Symbol_toPrimitive, "Symbol.toPrimitive")
|
|
||||||
DEF(Symbol_iterator, "Symbol.iterator")
|
|
||||||
DEF(Symbol_match, "Symbol.match")
|
|
||||||
DEF(Symbol_matchAll, "Symbol.matchAll")
|
|
||||||
DEF(Symbol_replace, "Symbol.replace")
|
|
||||||
DEF(Symbol_search, "Symbol.search")
|
|
||||||
DEF(Symbol_split, "Symbol.split")
|
|
||||||
DEF(Symbol_toStringTag, "Symbol.toStringTag")
|
|
||||||
DEF(Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable")
|
|
||||||
DEF(Symbol_hasInstance, "Symbol.hasInstance")
|
|
||||||
DEF(Symbol_species, "Symbol.species")
|
|
||||||
DEF(Symbol_unscopables, "Symbol.unscopables")
|
|
||||||
DEF(Symbol_asyncIterator, "Symbol.asyncIterator")
|
|
||||||
|
|
||||||
#endif /* DEF */
|
|
||||||
1075
source/quickjs.h
1075
source/quickjs.h
File diff suppressed because it is too large
Load Diff
363
source/runtime.c
363
source/runtime.c
@@ -1,30 +1,5 @@
|
|||||||
/*
|
|
||||||
* QuickJS Javascript Engine
|
|
||||||
*
|
|
||||||
* Copyright (c) 2017-2025 Fabrice Bellard
|
|
||||||
* Copyright (c) 2017-2025 Charlie Gordon
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to
|
|
||||||
* deal in the Software without restriction, including without limitation the
|
|
||||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
||||||
* sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
||||||
* IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define BLOB_IMPLEMENTATION
|
#define BLOB_IMPLEMENTATION
|
||||||
#include "quickjs-internal.h"
|
#include "pit_internal.h"
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
// #define DUMP_BUDDY
|
// #define DUMP_BUDDY
|
||||||
@@ -87,7 +62,7 @@ static inline JS_BOOL JS_IsInteger (JSValue v) {
|
|||||||
JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT;
|
JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT;
|
||||||
|
|
||||||
/* === Public API wrappers (non-inline, for quickjs.h declarations) ===
|
/* === Public API wrappers (non-inline, for quickjs.h declarations) ===
|
||||||
These delegate to the static inline versions in quickjs-internal.h. */
|
These delegate to the static inline versions in pit_internal.h. */
|
||||||
|
|
||||||
JS_BOOL JS_IsStone(JSValue v) { return mist_is_stone(v); }
|
JS_BOOL JS_IsStone(JSValue v) { return mist_is_stone(v); }
|
||||||
JS_BOOL JS_IsArray(JSValue v) { return mist_is_array(v); }
|
JS_BOOL JS_IsArray(JSValue v) { return mist_is_array(v); }
|
||||||
@@ -1475,6 +1450,7 @@ void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *fro
|
|||||||
fn->u.cell.env_record = gc_copy_value (ctx, fn->u.cell.env_record, from_base, from_end, to_base, to_free, to_end);
|
fn->u.cell.env_record = gc_copy_value (ctx, fn->u.cell.env_record, from_base, from_end, to_base, to_free, to_end);
|
||||||
} else if (fn->kind == JS_FUNC_KIND_NATIVE) {
|
} else if (fn->kind == JS_FUNC_KIND_NATIVE) {
|
||||||
fn->u.cell.outer_frame = gc_copy_value (ctx, fn->u.cell.outer_frame, from_base, from_end, to_base, to_free, to_end);
|
fn->u.cell.outer_frame = gc_copy_value (ctx, fn->u.cell.outer_frame, from_base, from_end, to_base, to_free, to_end);
|
||||||
|
fn->u.cell.env_record = gc_copy_value (ctx, fn->u.cell.env_record, from_base, from_end, to_base, to_free, to_end);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1935,14 +1911,17 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Check memory limit — kill actor if heap exceeds cap */
|
/* Fire GC hook if registered */
|
||||||
|
if (ctx->trace_hook && (ctx->trace_type & JS_HOOK_GC))
|
||||||
|
ctx->trace_hook(ctx, JS_HOOK_GC, NULL, ctx->trace_data);
|
||||||
|
|
||||||
|
/* Check memory limit — kill actor if heap exceeds cap.
|
||||||
|
Use JS_Log "memory" channel which always fprintf's (safe during GC). */
|
||||||
if (ctx->heap_memory_limit > 0 && ctx->current_block_size > ctx->heap_memory_limit) {
|
if (ctx->heap_memory_limit > 0 && ctx->current_block_size > ctx->heap_memory_limit) {
|
||||||
#ifdef ACTOR_TRACE
|
JS_Log(ctx, "memory", "%s: heap %zuKB exceeds limit %zuMB, killing",
|
||||||
void *crt = ctx->user_opaque;
|
ctx->name ? ctx->name : ctx->id,
|
||||||
if (crt)
|
ctx->current_block_size / 1024,
|
||||||
fprintf(stderr, "[ACTOR_TRACE] heap %zu > limit %zu, OOM\n",
|
ctx->heap_memory_limit / (1024 * 1024));
|
||||||
ctx->current_block_size, ctx->heap_memory_limit);
|
|
||||||
#endif
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2066,6 +2045,7 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
|
|||||||
ctx->suspended_pc = 0;
|
ctx->suspended_pc = 0;
|
||||||
ctx->vm_call_depth = 0;
|
ctx->vm_call_depth = 0;
|
||||||
ctx->heap_memory_limit = 0;
|
ctx->heap_memory_limit = 0;
|
||||||
|
ctx->actor_label = NULL;
|
||||||
JS_AddGCRef(ctx, &ctx->suspended_frame_ref);
|
JS_AddGCRef(ctx, &ctx->suspended_frame_ref);
|
||||||
ctx->suspended_frame_ref.val = JS_NULL;
|
ctx->suspended_frame_ref.val = JS_NULL;
|
||||||
|
|
||||||
@@ -2075,6 +2055,18 @@ JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) {
|
|||||||
ctx->log_callback_js = JS_NULL;
|
ctx->log_callback_js = JS_NULL;
|
||||||
ctx->log_callback = NULL;
|
ctx->log_callback = NULL;
|
||||||
|
|
||||||
|
/* Register actor GCRef fields so the Cheney GC can relocate them. */
|
||||||
|
JS_AddGCRef(ctx, &ctx->idx_buffer_ref);
|
||||||
|
JS_AddGCRef(ctx, &ctx->on_exception_ref);
|
||||||
|
JS_AddGCRef(ctx, &ctx->message_handle_ref);
|
||||||
|
JS_AddGCRef(ctx, &ctx->unneeded_ref);
|
||||||
|
JS_AddGCRef(ctx, &ctx->actor_sym_ref);
|
||||||
|
ctx->idx_buffer_ref.val = JS_NULL;
|
||||||
|
ctx->on_exception_ref.val = JS_NULL;
|
||||||
|
ctx->message_handle_ref.val = JS_NULL;
|
||||||
|
ctx->unneeded_ref.val = JS_NULL;
|
||||||
|
ctx->actor_sym_ref.val = JS_NULL;
|
||||||
|
|
||||||
/* Initialize constant text pool (avoids overflow pages for common case) */
|
/* Initialize constant text pool (avoids overflow pages for common case) */
|
||||||
{
|
{
|
||||||
size_t ct_pool_size = 64 * 1024; /* 64KB initial CT pool */
|
size_t ct_pool_size = 64 * 1024; /* 64KB initial CT pool */
|
||||||
@@ -2151,11 +2143,7 @@ JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size) {
|
|||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *JS_GetContextOpaque (JSContext *ctx) { return ctx->user_opaque; }
|
|
||||||
|
|
||||||
void JS_SetContextOpaque (JSContext *ctx, void *opaque) {
|
|
||||||
ctx->user_opaque = opaque;
|
|
||||||
}
|
|
||||||
|
|
||||||
void JS_SetGCScanExternal(JSContext *ctx, JS_GCScanFn fn) {
|
void JS_SetGCScanExternal(JSContext *ctx, JS_GCScanFn fn) {
|
||||||
ctx->gc_scan_external = fn;
|
ctx->gc_scan_external = fn;
|
||||||
@@ -2185,6 +2173,11 @@ void JS_FreeContext (JSContext *ctx) {
|
|||||||
|
|
||||||
cell_rt_free_native_state(ctx);
|
cell_rt_free_native_state(ctx);
|
||||||
JS_DeleteGCRef(ctx, &ctx->suspended_frame_ref);
|
JS_DeleteGCRef(ctx, &ctx->suspended_frame_ref);
|
||||||
|
JS_DeleteGCRef(ctx, &ctx->idx_buffer_ref);
|
||||||
|
JS_DeleteGCRef(ctx, &ctx->on_exception_ref);
|
||||||
|
JS_DeleteGCRef(ctx, &ctx->message_handle_ref);
|
||||||
|
JS_DeleteGCRef(ctx, &ctx->unneeded_ref);
|
||||||
|
JS_DeleteGCRef(ctx, &ctx->actor_sym_ref);
|
||||||
|
|
||||||
for (i = 0; i < ctx->class_count; i++) {
|
for (i = 0; i < ctx->class_count; i++) {
|
||||||
}
|
}
|
||||||
@@ -3285,14 +3278,27 @@ JS_RaiseDisrupt (JSContext *ctx, const char *fmt, ...) {
|
|||||||
va_start (ap, fmt);
|
va_start (ap, fmt);
|
||||||
vsnprintf (buf, sizeof (buf), fmt, ap);
|
vsnprintf (buf, sizeof (buf), fmt, ap);
|
||||||
va_end (ap);
|
va_end (ap);
|
||||||
JS_Log (ctx, "error", "%s", buf);
|
if (ctx->log_callback)
|
||||||
|
JS_Log (ctx, "error", "%s", buf);
|
||||||
ctx->current_exception = JS_TRUE;
|
ctx->current_exception = JS_TRUE;
|
||||||
return JS_EXCEPTION;
|
return JS_EXCEPTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Log to "memory" channel + disrupt. Skips JS callback (can't allocate). */
|
/* Log to "memory" channel + disrupt. Skips JS callback (can't allocate).
|
||||||
|
Uses fprintf directly — safe even during OOM. */
|
||||||
JSValue JS_RaiseOOM (JSContext *ctx) {
|
JSValue JS_RaiseOOM (JSContext *ctx) {
|
||||||
fprintf (stderr, "out of memory\n");
|
size_t used = (size_t)((uint8_t *)ctx->heap_free - (uint8_t *)ctx->heap_base);
|
||||||
|
size_t block = ctx->current_block_size;
|
||||||
|
size_t limit = ctx->heap_memory_limit;
|
||||||
|
const char *name = ctx->name ? ctx->name : ctx->id;
|
||||||
|
const char *label = ctx->actor_label;
|
||||||
|
fprintf(stderr, "[memory] %s: out of memory — heap %zuKB / %zuKB block",
|
||||||
|
name ? name : "?", used / 1024, block / 1024);
|
||||||
|
if (limit > 0)
|
||||||
|
fprintf(stderr, ", limit %zuMB", limit / (1024 * 1024));
|
||||||
|
if (label)
|
||||||
|
fprintf(stderr, " [%s]", label);
|
||||||
|
fprintf(stderr, "\n");
|
||||||
ctx->current_exception = JS_TRUE;
|
ctx->current_exception = JS_TRUE;
|
||||||
return JS_EXCEPTION;
|
return JS_EXCEPTION;
|
||||||
}
|
}
|
||||||
@@ -8370,7 +8376,15 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
|
|||||||
/* Map - GC-safe: root result throughout, use rooted refs for func and array */
|
/* Map - GC-safe: root result throughout, use rooted refs for func and array */
|
||||||
int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (arg1_ref.val))->length;
|
int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (arg1_ref.val))->length;
|
||||||
|
|
||||||
int reverse = argc > 2 && JS_ToBool (ctx, argv[2]);
|
int reverse = 0;
|
||||||
|
if (argc > 2 && !JS_IsNull (argv[2])) {
|
||||||
|
if (!JS_IsBool (argv[2])) {
|
||||||
|
JS_PopGCRef (ctx, &arg1_ref);
|
||||||
|
JS_PopGCRef (ctx, &arg0_ref);
|
||||||
|
return JS_RaiseDisrupt (ctx, "array: reverse must be a logical");
|
||||||
|
}
|
||||||
|
reverse = JS_VALUE_GET_BOOL (argv[2]);
|
||||||
|
}
|
||||||
JSValue exit_val = argc > 3 ? argv[3] : JS_NULL;
|
JSValue exit_val = argc > 3 ? argv[3] : JS_NULL;
|
||||||
|
|
||||||
JSGCRef result_ref;
|
JSGCRef result_ref;
|
||||||
@@ -8500,7 +8514,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
|
|||||||
if (!JS_IsInteger (argv[1])) {
|
if (!JS_IsInteger (argv[1])) {
|
||||||
JS_PopGCRef (ctx, &arg1_ref);
|
JS_PopGCRef (ctx, &arg1_ref);
|
||||||
JS_PopGCRef (ctx, &arg0_ref);
|
JS_PopGCRef (ctx, &arg0_ref);
|
||||||
return JS_NULL;
|
return JS_RaiseDisrupt (ctx, "array slice: from must be an integer");
|
||||||
}
|
}
|
||||||
int from = JS_VALUE_GET_INT (argv[1]);
|
int from = JS_VALUE_GET_INT (argv[1]);
|
||||||
int to;
|
int to;
|
||||||
@@ -8508,7 +8522,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
|
|||||||
if (!JS_IsNumber (argv[2]) || !JS_IsInteger (argv[2])) {
|
if (!JS_IsNumber (argv[2]) || !JS_IsInteger (argv[2])) {
|
||||||
JS_PopGCRef (ctx, &arg1_ref);
|
JS_PopGCRef (ctx, &arg1_ref);
|
||||||
JS_PopGCRef (ctx, &arg0_ref);
|
JS_PopGCRef (ctx, &arg0_ref);
|
||||||
return JS_NULL;
|
return JS_RaiseDisrupt (ctx, "array slice: to must be an integer");
|
||||||
}
|
}
|
||||||
to = JS_VALUE_GET_INT (argv[2]);
|
to = JS_VALUE_GET_INT (argv[2]);
|
||||||
} else {
|
} else {
|
||||||
@@ -8545,11 +8559,13 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu
|
|||||||
|
|
||||||
JS_PopGCRef (ctx, &arg1_ref);
|
JS_PopGCRef (ctx, &arg1_ref);
|
||||||
JS_PopGCRef (ctx, &arg0_ref);
|
JS_PopGCRef (ctx, &arg0_ref);
|
||||||
return JS_NULL;
|
return JS_RaiseDisrupt (ctx, "array: invalid argument combination");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* array(object) - keys */
|
/* array(object) - keys */
|
||||||
if (JS_IsRecord (arg)) {
|
if (JS_IsRecord (arg)) {
|
||||||
|
if (argc > 1 && JS_IsFunction (argv[1]))
|
||||||
|
return JS_RaiseDisrupt (ctx, "array(record, fn) is not valid — use array(array(record), fn) to map over keys");
|
||||||
/* Return object keys */
|
/* Return object keys */
|
||||||
return JS_GetOwnPropertyNames (ctx, arg);
|
return JS_GetOwnPropertyNames (ctx, arg);
|
||||||
}
|
}
|
||||||
@@ -8815,7 +8831,12 @@ static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc,
|
|||||||
word_t len = arr->len;
|
word_t len = arr->len;
|
||||||
JSValue fn = argv[1];
|
JSValue fn = argv[1];
|
||||||
|
|
||||||
int reverse = argc > 3 && JS_ToBool (ctx, argv[3]);
|
int reverse = 0;
|
||||||
|
if (argc > 3 && !JS_IsNull (argv[3])) {
|
||||||
|
if (!JS_IsBool (argv[3]))
|
||||||
|
return JS_RaiseDisrupt (ctx, "reduce: reverse must be a logical");
|
||||||
|
reverse = JS_VALUE_GET_BOOL (argv[3]);
|
||||||
|
}
|
||||||
JSGCRef acc_ref;
|
JSGCRef acc_ref;
|
||||||
JSValue acc;
|
JSValue acc;
|
||||||
|
|
||||||
@@ -8891,7 +8912,12 @@ static JSValue js_cell_array_for (JSContext *ctx, JSValue this_val, int argc, JS
|
|||||||
word_t len = arr->len;
|
word_t len = arr->len;
|
||||||
if (len == 0) return JS_NULL;
|
if (len == 0) return JS_NULL;
|
||||||
|
|
||||||
int reverse = argc > 2 && JS_ToBool (ctx, argv[2]);
|
int reverse = 0;
|
||||||
|
if (argc > 2 && !JS_IsNull (argv[2])) {
|
||||||
|
if (!JS_IsBool (argv[2]))
|
||||||
|
return JS_RaiseDisrupt (ctx, "arrfor: reverse must be a logical");
|
||||||
|
reverse = JS_VALUE_GET_BOOL (argv[2]);
|
||||||
|
}
|
||||||
JSValue exit_val = argc > 3 ? argv[3] : JS_NULL;
|
JSValue exit_val = argc > 3 ? argv[3] : JS_NULL;
|
||||||
|
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
@@ -8931,7 +8957,12 @@ static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, J
|
|||||||
JSArray *arr = JS_VALUE_GET_ARRAY (argv[0]);
|
JSArray *arr = JS_VALUE_GET_ARRAY (argv[0]);
|
||||||
word_t len = arr->len;
|
word_t len = arr->len;
|
||||||
|
|
||||||
int reverse = argc > 2 && JS_ToBool (ctx, argv[2]);
|
int reverse = 0;
|
||||||
|
if (argc > 2 && !JS_IsNull (argv[2])) {
|
||||||
|
if (!JS_IsBool (argv[2]))
|
||||||
|
return JS_RaiseDisrupt (ctx, "find: reverse must be a logical");
|
||||||
|
reverse = JS_VALUE_GET_BOOL (argv[2]);
|
||||||
|
}
|
||||||
int32_t from;
|
int32_t from;
|
||||||
if (argc > 3 && !JS_IsNull (argv[3])) {
|
if (argc > 3 && !JS_IsNull (argv[3])) {
|
||||||
if (JS_ToInt32 (ctx, &from, argv[3])) return JS_NULL;
|
if (JS_ToInt32 (ctx, &from, argv[3])) return JS_NULL;
|
||||||
@@ -9409,7 +9440,7 @@ static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSVal
|
|||||||
/* Use text directly as key */
|
/* Use text directly as key */
|
||||||
JSValue prop_key = js_key_from_string (ctx, key);
|
JSValue prop_key = js_key_from_string (ctx, key);
|
||||||
JSValue val;
|
JSValue val;
|
||||||
if (argc < 2 || JS_IsNull (func_ref.val)) {
|
if (argc < 2) {
|
||||||
val = JS_TRUE;
|
val = JS_TRUE;
|
||||||
} else if (is_func) {
|
} else if (is_func) {
|
||||||
JSValue arg_key = key;
|
JSValue arg_key = key;
|
||||||
@@ -9446,15 +9477,23 @@ static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSV
|
|||||||
if (argc < 1) return JS_NULL;
|
if (argc < 1) return JS_NULL;
|
||||||
if (!JS_IsFunction (argv[0])) return argv[0];
|
if (!JS_IsFunction (argv[0])) return argv[0];
|
||||||
|
|
||||||
|
JSFunction *fn = JS_VALUE_GET_FUNCTION (argv[0]);
|
||||||
|
|
||||||
if (argc < 2)
|
if (argc < 2)
|
||||||
return JS_CallInternal (ctx, argv[0], JS_NULL, 0, NULL, 0);
|
return JS_CallInternal (ctx, argv[0], JS_NULL, 0, NULL, 0);
|
||||||
|
|
||||||
if (!JS_IsArray (argv[1]))
|
if (!JS_IsArray (argv[1])) {
|
||||||
|
if (fn->length >= 0 && 1 > fn->length)
|
||||||
|
return JS_RaiseDisrupt (ctx, "too many arguments for apply: expected %d, got 1", fn->length);
|
||||||
return JS_CallInternal (ctx, argv[0], JS_NULL, 1, &argv[1], 0);
|
return JS_CallInternal (ctx, argv[0], JS_NULL, 1, &argv[1], 0);
|
||||||
|
}
|
||||||
|
|
||||||
JSArray *arr = JS_VALUE_GET_ARRAY (argv[1]);
|
JSArray *arr = JS_VALUE_GET_ARRAY (argv[1]);
|
||||||
int len = arr->len;
|
int len = arr->len;
|
||||||
|
|
||||||
|
if (fn->length >= 0 && len > fn->length)
|
||||||
|
return JS_RaiseDisrupt (ctx, "too many arguments for apply: expected %d, got %d", fn->length, len);
|
||||||
|
|
||||||
if (len == 0)
|
if (len == 0)
|
||||||
return JS_CallInternal (ctx, argv[0], JS_NULL, 0, NULL, 0);
|
return JS_CallInternal (ctx, argv[0], JS_NULL, 0, NULL, 0);
|
||||||
|
|
||||||
@@ -11321,11 +11360,9 @@ static JSValue js_cell_is_blob (JSContext *ctx, JSValue this_val, int argc, JSVa
|
|||||||
static JSValue js_cell_is_data (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
static JSValue js_cell_is_data (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||||
if (argc < 1) return JS_FALSE;
|
if (argc < 1) return JS_FALSE;
|
||||||
JSValue val = argv[0];
|
JSValue val = argv[0];
|
||||||
if (!mist_is_gc_object (val)) return JS_FALSE;
|
/* data is text, number, logical, array, blob, or record — not function, null */
|
||||||
if (JS_IsArray (val)) return JS_FALSE;
|
if (JS_IsNull (val)) return JS_FALSE;
|
||||||
if (JS_IsFunction (val)) return JS_FALSE;
|
if (JS_IsFunction (val)) return JS_FALSE;
|
||||||
if (mist_is_blob (val)) return JS_FALSE;
|
|
||||||
/* Check if it's a plain object (prototype is Object.prototype or null) */
|
|
||||||
return JS_TRUE;
|
return JS_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11405,6 +11442,84 @@ static JSValue js_cell_is_letter (JSContext *ctx, JSValue this_val, int argc, JS
|
|||||||
return JS_NewBool (ctx, (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
|
return JS_NewBool (ctx, (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* is_true(val) - check if value is exactly true */
|
||||||
|
static JSValue js_cell_is_true (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||||
|
if (argc < 1) return JS_FALSE;
|
||||||
|
return JS_NewBool (ctx, argv[0] == JS_TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* is_false(val) - check if value is exactly false */
|
||||||
|
static JSValue js_cell_is_false (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||||
|
if (argc < 1) return JS_FALSE;
|
||||||
|
return JS_NewBool (ctx, argv[0] == JS_FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* is_fit(val) - check if value is a safe integer (int32 or float with integer value <= 2^53) */
|
||||||
|
static JSValue js_cell_is_fit (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||||
|
if (argc < 1) return JS_FALSE;
|
||||||
|
JSValue val = argv[0];
|
||||||
|
if (JS_IsInt (val)) return JS_TRUE;
|
||||||
|
if (JS_IsShortFloat (val)) {
|
||||||
|
double d = JS_VALUE_GET_FLOAT64 (val);
|
||||||
|
return JS_NewBool (ctx, isfinite (d) && trunc (d) == d && fabs (d) <= 9007199254740992.0);
|
||||||
|
}
|
||||||
|
return JS_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* is_character(val) - check if value is a single character text */
|
||||||
|
static JSValue js_cell_is_character (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||||
|
if (argc < 1) return JS_FALSE;
|
||||||
|
JSValue val = argv[0];
|
||||||
|
if (!JS_IsText (val)) return JS_FALSE;
|
||||||
|
return JS_NewBool (ctx, js_string_value_len (val) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* is_digit(val) - check if value is a single digit character */
|
||||||
|
static JSValue js_cell_is_digit (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||||
|
if (argc < 1) return JS_FALSE;
|
||||||
|
JSValue val = argv[0];
|
||||||
|
if (!JS_IsText (val)) return JS_FALSE;
|
||||||
|
if (js_string_value_len (val) != 1) return JS_FALSE;
|
||||||
|
uint32_t c = js_string_value_get (val, 0);
|
||||||
|
return JS_NewBool (ctx, c >= '0' && c <= '9');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* is_lower(val) - check if value is a single lowercase letter */
|
||||||
|
static JSValue js_cell_is_lower (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||||
|
if (argc < 1) return JS_FALSE;
|
||||||
|
JSValue val = argv[0];
|
||||||
|
if (!JS_IsText (val)) return JS_FALSE;
|
||||||
|
if (js_string_value_len (val) != 1) return JS_FALSE;
|
||||||
|
uint32_t c = js_string_value_get (val, 0);
|
||||||
|
return JS_NewBool (ctx, c >= 'a' && c <= 'z');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* is_upper(val) - check if value is a single uppercase letter */
|
||||||
|
static JSValue js_cell_is_upper (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||||
|
if (argc < 1) return JS_FALSE;
|
||||||
|
JSValue val = argv[0];
|
||||||
|
if (!JS_IsText (val)) return JS_FALSE;
|
||||||
|
if (js_string_value_len (val) != 1) return JS_FALSE;
|
||||||
|
uint32_t c = js_string_value_get (val, 0);
|
||||||
|
return JS_NewBool (ctx, c >= 'A' && c <= 'Z');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* is_whitespace(val) - check if all characters are whitespace (non-empty) */
|
||||||
|
static JSValue js_cell_is_whitespace (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||||
|
if (argc < 1) return JS_FALSE;
|
||||||
|
JSValue val = argv[0];
|
||||||
|
if (!JS_IsText (val)) return JS_FALSE;
|
||||||
|
int len = js_string_value_len (val);
|
||||||
|
if (len == 0) return JS_FALSE;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
uint32_t c = js_string_value_get (val, i);
|
||||||
|
if (!(c == ' ' || c == '\t' || c == '\n'
|
||||||
|
|| c == '\r' || c == '\f' || c == '\v'))
|
||||||
|
return JS_FALSE;
|
||||||
|
}
|
||||||
|
return JS_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
/* is_proto(val, master) - check if val has master in prototype chain */
|
/* is_proto(val, master) - check if val has master in prototype chain */
|
||||||
static JSValue js_cell_is_proto (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
static JSValue js_cell_is_proto (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||||
if (argc < 2) return JS_FALSE;
|
if (argc < 2) return JS_FALSE;
|
||||||
@@ -11462,6 +11577,47 @@ static JSValue js_cell_logical(JSContext *ctx, JSValue this_val, int argc, JSVal
|
|||||||
return JS_NULL;
|
return JS_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* realloc wrapper for unicode_normalize */
|
||||||
|
static void *normalize_realloc(void *opaque, void *ptr, size_t size) {
|
||||||
|
(void)opaque;
|
||||||
|
if (size == 0) {
|
||||||
|
pjs_free(ptr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return pjs_realloc(ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* normalize(text) — NFC normalization */
|
||||||
|
static JSValue js_cell_normalize(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||||
|
if (argc < 1) return JS_NULL;
|
||||||
|
JSValue val = argv[0];
|
||||||
|
if (!JS_IsText(val)) return JS_NULL;
|
||||||
|
int len = js_string_value_len(val);
|
||||||
|
if (len == 0) return JS_NewString(ctx, "");
|
||||||
|
uint32_t *src = pjs_malloc(len * sizeof(uint32_t));
|
||||||
|
if (!src) return JS_EXCEPTION;
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
src[i] = js_string_value_get(val, i);
|
||||||
|
uint32_t *dst = NULL;
|
||||||
|
int dst_len = unicode_normalize(&dst, src, len, UNICODE_NFC, NULL,
|
||||||
|
normalize_realloc);
|
||||||
|
pjs_free(src);
|
||||||
|
if (dst_len < 0) {
|
||||||
|
pjs_free(dst);
|
||||||
|
return JS_NULL;
|
||||||
|
}
|
||||||
|
JSText *str = js_alloc_string(ctx, dst_len);
|
||||||
|
if (!str) {
|
||||||
|
pjs_free(dst);
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < dst_len; i++)
|
||||||
|
string_put(str, i, dst[i]);
|
||||||
|
str->length = dst_len;
|
||||||
|
pjs_free(dst);
|
||||||
|
return pretext_end(ctx, str);
|
||||||
|
}
|
||||||
|
|
||||||
/* starts_with(str, prefix) — search(str, prefix) == 0 */
|
/* starts_with(str, prefix) — search(str, prefix) == 0 */
|
||||||
static JSValue js_cell_starts_with(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
static JSValue js_cell_starts_with(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
|
||||||
if (argc < 2) return JS_NULL;
|
if (argc < 2) return JS_NULL;
|
||||||
@@ -11507,6 +11663,85 @@ static JSValue js_cell_some(JSContext *ctx, JSValue this_val, int argc, JSValue
|
|||||||
return JS_FALSE;
|
return JS_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* C API: Type-check wrappers for sensory functions */
|
||||||
|
JS_BOOL JS_IsDigit (JSContext *ctx, JSValue val) {
|
||||||
|
JSValue r = js_cell_is_digit (ctx, JS_NULL, 1, &val);
|
||||||
|
return JS_VALUE_GET_BOOL (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_BOOL JS_IsLetter (JSContext *ctx, JSValue val) {
|
||||||
|
JSValue r = js_cell_is_letter (ctx, JS_NULL, 1, &val);
|
||||||
|
return JS_VALUE_GET_BOOL (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_BOOL JS_IsLower (JSContext *ctx, JSValue val) {
|
||||||
|
JSValue r = js_cell_is_lower (ctx, JS_NULL, 1, &val);
|
||||||
|
return JS_VALUE_GET_BOOL (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_BOOL JS_IsUpper (JSContext *ctx, JSValue val) {
|
||||||
|
JSValue r = js_cell_is_upper (ctx, JS_NULL, 1, &val);
|
||||||
|
return JS_VALUE_GET_BOOL (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_BOOL JS_IsWhitespace (JSContext *ctx, JSValue val) {
|
||||||
|
JSValue r = js_cell_is_whitespace (ctx, JS_NULL, 1, &val);
|
||||||
|
return JS_VALUE_GET_BOOL (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_BOOL JS_IsCharacter (JSContext *ctx, JSValue val) {
|
||||||
|
JSValue r = js_cell_is_character (ctx, JS_NULL, 1, &val);
|
||||||
|
return JS_VALUE_GET_BOOL (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_BOOL JS_IsFit (JSContext *ctx, JSValue val) {
|
||||||
|
JSValue r = js_cell_is_fit (ctx, JS_NULL, 1, &val);
|
||||||
|
return JS_VALUE_GET_BOOL (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_BOOL JS_IsData (JSValue val) {
|
||||||
|
return !JS_IsNull (val) && !JS_IsFunction (val);
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_BOOL JS_IsActor (JSContext *ctx, JSValue val) {
|
||||||
|
JSValue r = js_cell_is_actor (ctx, JS_NULL, 1, &val);
|
||||||
|
return JS_VALUE_GET_BOOL (r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* C API: logical(val) */
|
||||||
|
JSValue JS_CellLogical (JSContext *ctx, JSValue val) {
|
||||||
|
return js_cell_logical (ctx, JS_NULL, 1, &val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* C API: every(arr, pred) */
|
||||||
|
JSValue JS_CellEvery (JSContext *ctx, JSValue arr, JSValue pred) {
|
||||||
|
JSValue argv[2] = { arr, pred };
|
||||||
|
return js_cell_every (ctx, JS_NULL, 2, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* C API: some(arr, pred) */
|
||||||
|
JSValue JS_CellSome (JSContext *ctx, JSValue arr, JSValue pred) {
|
||||||
|
JSValue argv[2] = { arr, pred };
|
||||||
|
return js_cell_some (ctx, JS_NULL, 2, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* C API: starts_with(text, prefix) */
|
||||||
|
JSValue JS_CellStartsWith (JSContext *ctx, JSValue text, JSValue prefix) {
|
||||||
|
JSValue argv[2] = { text, prefix };
|
||||||
|
return js_cell_starts_with (ctx, JS_NULL, 2, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* C API: ends_with(text, suffix) */
|
||||||
|
JSValue JS_CellEndsWith (JSContext *ctx, JSValue text, JSValue suffix) {
|
||||||
|
JSValue argv[2] = { text, suffix };
|
||||||
|
return js_cell_ends_with (ctx, JS_NULL, 2, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* C API: normalize(text) */
|
||||||
|
JSValue JS_CellNormalize (JSContext *ctx, JSValue text) {
|
||||||
|
return js_cell_normalize (ctx, JS_NULL, 1, &text);
|
||||||
|
}
|
||||||
|
|
||||||
static void js_set_global_cfunc(JSContext *ctx, const char *name, JSCFunction *func, int length) {
|
static void js_set_global_cfunc(JSContext *ctx, const char *name, JSCFunction *func, int length) {
|
||||||
JSGCRef ref;
|
JSGCRef ref;
|
||||||
JS_PushGCRef(ctx, &ref);
|
JS_PushGCRef(ctx, &ref);
|
||||||
@@ -11572,6 +11807,14 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) {
|
|||||||
js_set_global_cfunc(ctx, "is_text", js_cell_is_text, 1);
|
js_set_global_cfunc(ctx, "is_text", js_cell_is_text, 1);
|
||||||
js_set_global_cfunc(ctx, "is_proto", js_cell_is_proto, 2);
|
js_set_global_cfunc(ctx, "is_proto", js_cell_is_proto, 2);
|
||||||
js_set_global_cfunc(ctx, "is_letter", js_cell_is_letter, 1);
|
js_set_global_cfunc(ctx, "is_letter", js_cell_is_letter, 1);
|
||||||
|
js_set_global_cfunc(ctx, "is_true", js_cell_is_true, 1);
|
||||||
|
js_set_global_cfunc(ctx, "is_false", js_cell_is_false, 1);
|
||||||
|
js_set_global_cfunc(ctx, "is_fit", js_cell_is_fit, 1);
|
||||||
|
js_set_global_cfunc(ctx, "is_character", js_cell_is_character, 1);
|
||||||
|
js_set_global_cfunc(ctx, "is_digit", js_cell_is_digit, 1);
|
||||||
|
js_set_global_cfunc(ctx, "is_lower", js_cell_is_lower, 1);
|
||||||
|
js_set_global_cfunc(ctx, "is_upper", js_cell_is_upper, 1);
|
||||||
|
js_set_global_cfunc(ctx, "is_whitespace", js_cell_is_whitespace, 1);
|
||||||
|
|
||||||
/* Utility functions */
|
/* Utility functions */
|
||||||
js_set_global_cfunc(ctx, "apply", js_cell_fn_apply, 2);
|
js_set_global_cfunc(ctx, "apply", js_cell_fn_apply, 2);
|
||||||
@@ -11623,6 +11866,7 @@ static void JS_AddIntrinsicBaseObjects (JSContext *ctx) {
|
|||||||
js_set_global_cfunc(ctx, "ends_with", js_cell_ends_with, 2);
|
js_set_global_cfunc(ctx, "ends_with", js_cell_ends_with, 2);
|
||||||
js_set_global_cfunc(ctx, "every", js_cell_every, 2);
|
js_set_global_cfunc(ctx, "every", js_cell_every, 2);
|
||||||
js_set_global_cfunc(ctx, "some", js_cell_some, 2);
|
js_set_global_cfunc(ctx, "some", js_cell_some, 2);
|
||||||
|
js_set_global_cfunc(ctx, "normalize", js_cell_normalize, 1);
|
||||||
|
|
||||||
/* fn record with apply property */
|
/* fn record with apply property */
|
||||||
{
|
{
|
||||||
@@ -11689,6 +11933,15 @@ void js_debug_sethook (JSContext *ctx, js_hook hook, int type, void *user) {
|
|||||||
ctx->trace_data = user;
|
ctx->trace_data = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void js_debug_gethook (JSContext *ctx, js_hook *hook, int *type, void **user) {
|
||||||
|
if (hook) *hook = ctx->trace_hook;
|
||||||
|
if (type) *type = ctx->trace_type;
|
||||||
|
if (user) *user = ctx->trace_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cell_ctx_heap_used (JSContext *ctx) {
|
||||||
|
return (size_t)(ctx->heap_free - ctx->heap_base);
|
||||||
|
}
|
||||||
|
|
||||||
/* Public API: get stack trace as JS array of {fn, file, line, col} objects.
|
/* Public API: get stack trace as JS array of {fn, file, line, col} objects.
|
||||||
Does NOT clear reg_current_frame — caller is responsible if needed.
|
Does NOT clear reg_current_frame — caller is responsible if needed.
|
||||||
@@ -11756,7 +12009,7 @@ void JS_CrashPrintStack(JSContext *ctx) {
|
|||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
if (JS_IsNull(ctx->reg_current_frame)) return;
|
if (JS_IsNull(ctx->reg_current_frame)) return;
|
||||||
|
|
||||||
static const char hdr[] = "\n--- JS Stack at crash ---\n";
|
static const char hdr[] = "\n--- JS Stack ---\n";
|
||||||
write(STDERR_FILENO, hdr, sizeof(hdr) - 1);
|
write(STDERR_FILENO, hdr, sizeof(hdr) - 1);
|
||||||
|
|
||||||
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame);
|
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame);
|
||||||
|
|||||||
@@ -6,18 +6,20 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
#include "stb_ds.h"
|
#include "stb_ds.h"
|
||||||
#include "cell.h"
|
#include "cell.h"
|
||||||
#include "quickjs-internal.h"
|
#include "pit_internal.h"
|
||||||
#include "cell_internal.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#include <winsock2.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef struct actor_node {
|
typedef struct actor_node {
|
||||||
cell_rt *actor;
|
JSContext *actor;
|
||||||
struct actor_node *next;
|
struct actor_node *next;
|
||||||
} actor_node;
|
} actor_node;
|
||||||
|
|
||||||
@@ -30,13 +32,13 @@ typedef enum {
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint64_t execute_at_ns;
|
uint64_t execute_at_ns;
|
||||||
cell_rt *actor;
|
JSContext *actor;
|
||||||
uint32_t timer_id;
|
uint32_t timer_id;
|
||||||
timer_type type;
|
timer_type type;
|
||||||
uint32_t turn_gen; /* generation at registration time */
|
uint32_t turn_gen; /* generation at registration time */
|
||||||
} timer_node;
|
} timer_node;
|
||||||
|
|
||||||
static timer_node *timer_heap = NULL;
|
static timer_node *timer_heap = NULL;
|
||||||
|
|
||||||
// Priority queue indices
|
// Priority queue indices
|
||||||
#define PQ_READY 0
|
#define PQ_READY 0
|
||||||
@@ -92,7 +94,96 @@ static int has_any_work(actor_node *heads[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static pthread_mutex_t *actors_mutex;
|
static pthread_mutex_t *actors_mutex;
|
||||||
static struct { char *key; cell_rt *value; } *actors = NULL;
|
static struct { char *key; JSContext *value; } *actors = NULL;
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
I/O Watch Thread — poll()-based fd monitoring
|
||||||
|
============================================================ */
|
||||||
|
static io_watch *g_io_watches = NULL; /* stb_ds dynamic array */
|
||||||
|
static pthread_mutex_t io_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static pthread_t io_thread;
|
||||||
|
static int io_pipe[2] = {-1, -1}; /* self-pipe to wake poll() */
|
||||||
|
|
||||||
|
static void io_wake(void) {
|
||||||
|
char c = 1;
|
||||||
|
(void)write(io_pipe[1], &c, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *io_thread_func(void *arg) {
|
||||||
|
(void)arg;
|
||||||
|
while (1) {
|
||||||
|
pthread_mutex_lock(&io_mutex);
|
||||||
|
if (engine.shutting_down) {
|
||||||
|
pthread_mutex_unlock(&io_mutex);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
int n = arrlen(g_io_watches);
|
||||||
|
/* +1 for the wakeup pipe */
|
||||||
|
struct pollfd *fds = malloc(sizeof(struct pollfd) * (n + 1));
|
||||||
|
fds[0].fd = io_pipe[0];
|
||||||
|
fds[0].events = POLLIN;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
fds[i + 1].fd = g_io_watches[i].fd;
|
||||||
|
fds[i + 1].events = g_io_watches[i].events;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&io_mutex);
|
||||||
|
|
||||||
|
int ready = poll(fds, n + 1, 500); /* 500ms timeout for shutdown check */
|
||||||
|
if (ready <= 0) {
|
||||||
|
free(fds);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drain wakeup pipe */
|
||||||
|
if (fds[0].revents & POLLIN) {
|
||||||
|
char buf[64];
|
||||||
|
(void)read(io_pipe[0], buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fire callbacks for ready fds */
|
||||||
|
pthread_mutex_lock(&io_mutex);
|
||||||
|
for (int i = n - 1; i >= 0; i--) {
|
||||||
|
if (i >= arrlen(g_io_watches)) continue;
|
||||||
|
if (fds[i + 1].revents & g_io_watches[i].events) {
|
||||||
|
io_watch w = g_io_watches[i];
|
||||||
|
arrdel(g_io_watches, i); /* one-shot: remove before firing */
|
||||||
|
pthread_mutex_unlock(&io_mutex);
|
||||||
|
actor_clock(w.actor, w.callback);
|
||||||
|
pthread_mutex_lock(&io_mutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&io_mutex);
|
||||||
|
free(fds);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void actor_watch(JSContext *actor, int fd, short events, JSValue fn) {
|
||||||
|
io_watch w = { .fd = fd, .events = events, .actor = actor, .callback = fn };
|
||||||
|
pthread_mutex_lock(&io_mutex);
|
||||||
|
arrput(g_io_watches, w);
|
||||||
|
pthread_mutex_unlock(&io_mutex);
|
||||||
|
io_wake();
|
||||||
|
}
|
||||||
|
|
||||||
|
void actor_watch_readable(JSContext *actor, int fd, JSValue fn) {
|
||||||
|
actor_watch(actor, fd, POLLIN, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void actor_watch_writable(JSContext *actor, int fd, JSValue fn) {
|
||||||
|
actor_watch(actor, fd, POLLOUT, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void actor_unwatch(JSContext *actor, int fd) {
|
||||||
|
pthread_mutex_lock(&io_mutex);
|
||||||
|
for (int i = arrlen(g_io_watches) - 1; i >= 0; i--) {
|
||||||
|
if (g_io_watches[i].actor == actor && g_io_watches[i].fd == fd) {
|
||||||
|
arrdel(g_io_watches, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&io_mutex);
|
||||||
|
io_wake();
|
||||||
|
}
|
||||||
|
|
||||||
#define lockless_shdel(NAME, KEY) pthread_mutex_lock(NAME##_mutex); shdel(NAME, KEY); pthread_mutex_unlock(NAME##_mutex);
|
#define lockless_shdel(NAME, KEY) pthread_mutex_lock(NAME##_mutex); shdel(NAME, KEY); pthread_mutex_unlock(NAME##_mutex);
|
||||||
#define lockless_shlen(NAME) ({ \
|
#define lockless_shlen(NAME) ({ \
|
||||||
@@ -109,7 +200,7 @@ static struct { char *key; cell_rt *value; } *actors = NULL;
|
|||||||
})
|
})
|
||||||
#define lockless_shget(NAME, KEY) ({ \
|
#define lockless_shget(NAME, KEY) ({ \
|
||||||
pthread_mutex_lock(NAME##_mutex); \
|
pthread_mutex_lock(NAME##_mutex); \
|
||||||
cell_rt *_actor = shget(NAME, KEY); \
|
JSContext *_actor = shget(NAME, KEY); \
|
||||||
pthread_mutex_unlock(NAME##_mutex); \
|
pthread_mutex_unlock(NAME##_mutex); \
|
||||||
_actor; \
|
_actor; \
|
||||||
})
|
})
|
||||||
@@ -122,21 +213,21 @@ static struct { char *key; cell_rt *value; } *actors = NULL;
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval);
|
uint32_t actor_remove_cb(JSContext *actor, uint32_t id, uint32_t interval);
|
||||||
void actor_turn(cell_rt *actor);
|
void actor_turn(JSContext *actor);
|
||||||
|
|
||||||
void heap_push(uint64_t when, cell_rt *actor, uint32_t timer_id, timer_type type) {
|
void heap_push(uint64_t when, JSContext *actor, uint32_t timer_id, timer_type type) {
|
||||||
timer_node node = { .execute_at_ns = when, .actor = actor, .timer_id = timer_id, .type = type, .turn_gen = 0 };
|
timer_node node = { .execute_at_ns = when, .actor = actor, .timer_id = timer_id, .type = type, .turn_gen = 0 };
|
||||||
if (type == TIMER_PAUSE || type == TIMER_KILL)
|
if (type == TIMER_PAUSE || type == TIMER_KILL)
|
||||||
node.turn_gen = atomic_load_explicit(&actor->turn_gen, memory_order_relaxed);
|
node.turn_gen = atomic_load_explicit(&actor->turn_gen, memory_order_relaxed);
|
||||||
arrput(timer_heap, node);
|
arrput(timer_heap, node);
|
||||||
|
|
||||||
// Bubble up
|
// Bubble up
|
||||||
int i = arrlen(timer_heap) - 1;
|
int i = arrlen(timer_heap) - 1;
|
||||||
while (i > 0) {
|
while (i > 0) {
|
||||||
int parent = (i - 1) / 2;
|
int parent = (i - 1) / 2;
|
||||||
if (timer_heap[i].execute_at_ns >= timer_heap[parent].execute_at_ns) break;
|
if (timer_heap[i].execute_at_ns >= timer_heap[parent].execute_at_ns) break;
|
||||||
|
|
||||||
timer_node tmp = timer_heap[i];
|
timer_node tmp = timer_heap[i];
|
||||||
timer_heap[i] = timer_heap[parent];
|
timer_heap[i] = timer_heap[parent];
|
||||||
timer_heap[parent] = tmp;
|
timer_heap[parent] = tmp;
|
||||||
@@ -147,10 +238,10 @@ void heap_push(uint64_t when, cell_rt *actor, uint32_t timer_id, timer_type type
|
|||||||
// Helper: Heap Pop
|
// Helper: Heap Pop
|
||||||
int heap_pop(timer_node *out) {
|
int heap_pop(timer_node *out) {
|
||||||
if (arrlen(timer_heap) == 0) return 0;
|
if (arrlen(timer_heap) == 0) return 0;
|
||||||
|
|
||||||
*out = timer_heap[0];
|
*out = timer_heap[0];
|
||||||
timer_node last = arrpop(timer_heap);
|
timer_node last = arrpop(timer_heap);
|
||||||
|
|
||||||
if (arrlen(timer_heap) > 0) {
|
if (arrlen(timer_heap) > 0) {
|
||||||
timer_heap[0] = last;
|
timer_heap[0] = last;
|
||||||
// Bubble down
|
// Bubble down
|
||||||
@@ -160,14 +251,14 @@ int heap_pop(timer_node *out) {
|
|||||||
int left = 2 * i + 1;
|
int left = 2 * i + 1;
|
||||||
int right = 2 * i + 2;
|
int right = 2 * i + 2;
|
||||||
int smallest = i;
|
int smallest = i;
|
||||||
|
|
||||||
if (left < n && timer_heap[left].execute_at_ns < timer_heap[smallest].execute_at_ns)
|
if (left < n && timer_heap[left].execute_at_ns < timer_heap[smallest].execute_at_ns)
|
||||||
smallest = left;
|
smallest = left;
|
||||||
if (right < n && timer_heap[right].execute_at_ns < timer_heap[smallest].execute_at_ns)
|
if (right < n && timer_heap[right].execute_at_ns < timer_heap[smallest].execute_at_ns)
|
||||||
smallest = right;
|
smallest = right;
|
||||||
|
|
||||||
if (smallest == i) break;
|
if (smallest == i) break;
|
||||||
|
|
||||||
timer_node tmp = timer_heap[i];
|
timer_node tmp = timer_heap[i];
|
||||||
timer_heap[i] = timer_heap[smallest];
|
timer_heap[i] = timer_heap[smallest];
|
||||||
timer_heap[smallest] = tmp;
|
timer_heap[smallest] = tmp;
|
||||||
@@ -180,7 +271,7 @@ int heap_pop(timer_node *out) {
|
|||||||
void *timer_thread_func(void *arg) {
|
void *timer_thread_func(void *arg) {
|
||||||
while (1) {
|
while (1) {
|
||||||
pthread_mutex_lock(&engine.lock);
|
pthread_mutex_lock(&engine.lock);
|
||||||
|
|
||||||
if (engine.shutting_down) {
|
if (engine.shutting_down) {
|
||||||
pthread_mutex_unlock(&engine.lock);
|
pthread_mutex_unlock(&engine.lock);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -202,11 +293,17 @@ void *timer_thread_func(void *arg) {
|
|||||||
/* Only fire if turn_gen still matches (stale timers are ignored) */
|
/* Only fire if turn_gen still matches (stale timers are ignored) */
|
||||||
uint32_t cur = atomic_load_explicit(&t.actor->turn_gen, memory_order_relaxed);
|
uint32_t cur = atomic_load_explicit(&t.actor->turn_gen, memory_order_relaxed);
|
||||||
if (cur == t.turn_gen) {
|
if (cur == t.turn_gen) {
|
||||||
|
/* Can't call JS_Log from timer thread — use fprintf */
|
||||||
|
const char *name = t.actor->name ? t.actor->name : t.actor->id;
|
||||||
if (t.type == TIMER_PAUSE) {
|
if (t.type == TIMER_PAUSE) {
|
||||||
JS_SetPauseFlag(t.actor->context, 1);
|
fprintf(stderr, "[slow] %s: pausing (turn exceeded %llums)\n",
|
||||||
|
name, (unsigned long long)(ACTOR_FAST_TIMER_NS / 1000000));
|
||||||
|
JS_SetPauseFlag(t.actor, 1);
|
||||||
} else {
|
} else {
|
||||||
|
fprintf(stderr, "[slow] %s: kill timer fired (turn exceeded %llus)\n",
|
||||||
|
name, (unsigned long long)(ACTOR_SLOW_TIMER_NS / 1000000000));
|
||||||
t.actor->disrupt = 1;
|
t.actor->disrupt = 1;
|
||||||
JS_SetPauseFlag(t.actor->context, 2);
|
JS_SetPauseFlag(t.actor, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -216,26 +313,25 @@ void *timer_thread_func(void *arg) {
|
|||||||
JSValue cb = t.actor->timers[idx].value;
|
JSValue cb = t.actor->timers[idx].value;
|
||||||
hmdel(t.actor->timers, t.timer_id);
|
hmdel(t.actor->timers, t.timer_id);
|
||||||
actor_clock(t.actor, cb);
|
actor_clock(t.actor, cb);
|
||||||
JS_FreeValue(t.actor->context, cb);
|
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(t.actor->msg_mutex);
|
pthread_mutex_unlock(t.actor->msg_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
// --- WAIT FOR DEADLINE ---
|
// --- WAIT FOR DEADLINE ---
|
||||||
struct timespec ts;
|
struct timespec ts;
|
||||||
uint64_t wait_ns = timer_heap[0].execute_at_ns - now;
|
uint64_t wait_ns = timer_heap[0].execute_at_ns - now;
|
||||||
|
|
||||||
// Convert relative wait time to absolute CLOCK_REALTIME
|
// Convert relative wait time to absolute CLOCK_REALTIME
|
||||||
struct timespec now_real;
|
struct timespec now_real;
|
||||||
clock_gettime(CLOCK_REALTIME, &now_real);
|
clock_gettime(CLOCK_REALTIME, &now_real);
|
||||||
|
|
||||||
uint64_t deadline_real_ns = (uint64_t)now_real.tv_sec * 1000000000ULL +
|
uint64_t deadline_real_ns = (uint64_t)now_real.tv_sec * 1000000000ULL +
|
||||||
(uint64_t)now_real.tv_nsec + wait_ns;
|
(uint64_t)now_real.tv_nsec + wait_ns;
|
||||||
ts.tv_sec = deadline_real_ns / 1000000000ULL;
|
ts.tv_sec = deadline_real_ns / 1000000000ULL;
|
||||||
ts.tv_nsec = deadline_real_ns % 1000000000ULL;
|
ts.tv_nsec = deadline_real_ns % 1000000000ULL;
|
||||||
|
|
||||||
pthread_cond_timedwait(&engine.timer_cond, &engine.lock, &ts);
|
pthread_cond_timedwait(&engine.timer_cond, &engine.lock, &ts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,7 +340,7 @@ void *timer_thread_func(void *arg) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void enqueue_actor_priority(cell_rt *actor) {
|
void enqueue_actor_priority(JSContext *actor) {
|
||||||
actor_node *n = malloc(sizeof(actor_node));
|
actor_node *n = malloc(sizeof(actor_node));
|
||||||
n->actor = actor;
|
n->actor = actor;
|
||||||
n->next = NULL;
|
n->next = NULL;
|
||||||
@@ -301,13 +397,20 @@ void actor_initialize(void) {
|
|||||||
engine.mq_head[i] = NULL;
|
engine.mq_head[i] = NULL;
|
||||||
engine.mq_tail[i] = NULL;
|
engine.mq_tail[i] = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
actors_mutex = malloc(sizeof(pthread_mutex_t));
|
actors_mutex = malloc(sizeof(pthread_mutex_t));
|
||||||
pthread_mutex_init(actors_mutex, NULL);
|
pthread_mutex_init(actors_mutex, NULL);
|
||||||
|
|
||||||
// Start Timer Thread
|
// Start Timer Thread
|
||||||
pthread_create(&engine.timer_thread, NULL, timer_thread_func, NULL);
|
pthread_create(&engine.timer_thread, NULL, timer_thread_func, NULL);
|
||||||
|
|
||||||
|
// Start I/O Watch Thread
|
||||||
|
if (pipe(io_pipe) == 0) {
|
||||||
|
fcntl(io_pipe[0], F_SETFL, O_NONBLOCK);
|
||||||
|
fcntl(io_pipe[1], F_SETFL, O_NONBLOCK);
|
||||||
|
pthread_create(&io_thread, NULL, io_thread_func, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
// Start Workers
|
// Start Workers
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
SYSTEM_INFO sysinfo;
|
SYSTEM_INFO sysinfo;
|
||||||
@@ -323,7 +426,7 @@ void actor_initialize(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void actor_free(cell_rt *actor)
|
void actor_free(JSContext *actor)
|
||||||
{
|
{
|
||||||
if (actor->is_quiescent) {
|
if (actor->is_quiescent) {
|
||||||
actor->is_quiescent = 0;
|
actor->is_quiescent = 0;
|
||||||
@@ -360,46 +463,46 @@ void actor_free(cell_rt *actor)
|
|||||||
pthread_mutex_unlock(&engine.lock);
|
pthread_mutex_unlock(&engine.lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Remove I/O watches for this actor */
|
||||||
|
pthread_mutex_lock(&io_mutex);
|
||||||
|
for (int i = arrlen(g_io_watches) - 1; i >= 0; i--) {
|
||||||
|
if (g_io_watches[i].actor == actor)
|
||||||
|
arrdel(g_io_watches, i);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&io_mutex);
|
||||||
|
|
||||||
// Do not go forward with actor destruction until the actor is completely free
|
// Do not go forward with actor destruction until the actor is completely free
|
||||||
pthread_mutex_lock(actor->msg_mutex);
|
pthread_mutex_lock(actor->msg_mutex);
|
||||||
pthread_mutex_lock(actor->mutex);
|
pthread_mutex_lock(actor->mutex);
|
||||||
|
|
||||||
JSContext *js = actor->context;
|
|
||||||
|
|
||||||
JS_DeleteGCRef(js, &actor->idx_buffer_ref);
|
|
||||||
JS_DeleteGCRef(js, &actor->on_exception_ref);
|
|
||||||
JS_DeleteGCRef(js, &actor->message_handle_ref);
|
|
||||||
JS_DeleteGCRef(js, &actor->unneeded_ref);
|
|
||||||
JS_DeleteGCRef(js, &actor->actor_sym_ref);
|
|
||||||
|
|
||||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||||
JS_FreeValue(js, actor->timers[i].value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hmfree(actor->timers);
|
hmfree(actor->timers);
|
||||||
|
|
||||||
/* Free all letters in the queue */
|
/* Free all letters in the queue */
|
||||||
for (int i = 0; i < arrlen(actor->letters); i++) {
|
for (int i = 0; i < arrlen(actor->letters); i++) {
|
||||||
if (actor->letters[i].type == LETTER_BLOB) {
|
if (actor->letters[i].type == LETTER_BLOB) {
|
||||||
blob_destroy(actor->letters[i].blob_data);
|
blob_destroy(actor->letters[i].blob_data);
|
||||||
} else if (actor->letters[i].type == LETTER_CALLBACK) {
|
} else if (actor->letters[i].type == LETTER_CALLBACK) {
|
||||||
JS_FreeValue(js, actor->letters[i].callback);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
arrfree(actor->letters);
|
arrfree(actor->letters);
|
||||||
|
|
||||||
JS_FreeContext(js);
|
|
||||||
free(actor->id);
|
free(actor->id);
|
||||||
|
|
||||||
pthread_mutex_unlock(actor->mutex);
|
pthread_mutex_t *m = actor->mutex;
|
||||||
pthread_mutex_destroy(actor->mutex);
|
pthread_mutex_t *mm = actor->msg_mutex;
|
||||||
free(actor->mutex);
|
|
||||||
pthread_mutex_unlock(actor->msg_mutex);
|
JS_FreeContext(actor);
|
||||||
pthread_mutex_destroy(actor->msg_mutex);
|
|
||||||
free(actor->msg_mutex);
|
pthread_mutex_unlock(m);
|
||||||
|
pthread_mutex_destroy(m);
|
||||||
free(actor);
|
free(m);
|
||||||
|
pthread_mutex_unlock(mm);
|
||||||
|
pthread_mutex_destroy(mm);
|
||||||
|
free(mm);
|
||||||
|
|
||||||
int actor_count = lockless_shlen(actors);
|
int actor_count = lockless_shlen(actors);
|
||||||
if (actor_count == 0) {
|
if (actor_count == 0) {
|
||||||
@@ -445,6 +548,16 @@ void exit_handler(void) {
|
|||||||
|
|
||||||
pthread_join(engine.timer_thread, NULL);
|
pthread_join(engine.timer_thread, NULL);
|
||||||
|
|
||||||
|
/* Shut down I/O thread */
|
||||||
|
if (io_pipe[1] >= 0) {
|
||||||
|
io_wake();
|
||||||
|
pthread_join(io_thread, NULL);
|
||||||
|
close(io_pipe[0]);
|
||||||
|
close(io_pipe[1]);
|
||||||
|
io_pipe[0] = io_pipe[1] = -1;
|
||||||
|
}
|
||||||
|
arrfree(g_io_watches);
|
||||||
|
|
||||||
for (int i=0; i < engine.num_workers; i++) {
|
for (int i=0; i < engine.num_workers; i++) {
|
||||||
pthread_join(engine.worker_threads[i], NULL);
|
pthread_join(engine.worker_threads[i], NULL);
|
||||||
}
|
}
|
||||||
@@ -467,20 +580,14 @@ int actor_exists(const char *id)
|
|||||||
return idx != -1;
|
return idx != -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_actor_state(cell_rt *actor)
|
void set_actor_state(JSContext *actor)
|
||||||
{
|
{
|
||||||
if (actor->disrupt) {
|
if (actor->disrupt) {
|
||||||
#ifdef SCHEDULER_DEBUG
|
|
||||||
fprintf(stderr, "set_actor_state: %s disrupted, freeing\n", actor->name ? actor->name : actor->id);
|
|
||||||
#endif
|
|
||||||
actor_free(actor);
|
actor_free(actor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pthread_mutex_lock(actor->msg_mutex);
|
pthread_mutex_lock(actor->msg_mutex);
|
||||||
|
|
||||||
#ifdef SCHEDULER_DEBUG
|
|
||||||
fprintf(stderr, "set_actor_state: %s state=%d letters=%ld\n", actor->name ? actor->name : actor->id, actor->state, (long)arrlen(actor->letters));
|
|
||||||
#endif
|
|
||||||
switch(actor->state) {
|
switch(actor->state) {
|
||||||
case ACTOR_RUNNING:
|
case ACTOR_RUNNING:
|
||||||
case ACTOR_READY:
|
case ACTOR_READY:
|
||||||
@@ -494,9 +601,6 @@ void set_actor_state(cell_rt *actor)
|
|||||||
actor->is_quiescent = 0;
|
actor->is_quiescent = 0;
|
||||||
atomic_fetch_sub(&engine.quiescent_count, 1);
|
atomic_fetch_sub(&engine.quiescent_count, 1);
|
||||||
}
|
}
|
||||||
#ifdef SCHEDULER_DEBUG
|
|
||||||
fprintf(stderr, "set_actor_state: %s IDLE->READY, enqueueing (main=%d)\n", actor->name ? actor->name : actor->id, actor->main_thread_only);
|
|
||||||
#endif
|
|
||||||
actor->state = ACTOR_READY;
|
actor->state = ACTOR_READY;
|
||||||
actor->ar = 0;
|
actor->ar = 0;
|
||||||
enqueue_actor_priority(actor);
|
enqueue_actor_priority(actor);
|
||||||
@@ -548,7 +652,7 @@ void set_actor_state(cell_rt *actor)
|
|||||||
pthread_mutex_unlock(actor->msg_mutex);
|
pthread_mutex_unlock(actor->msg_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
|
uint32_t actor_remove_cb(JSContext *actor, uint32_t id, uint32_t interval)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(actor->mutex);
|
pthread_mutex_lock(actor->mutex);
|
||||||
// Check if this timer is still valid (match actor->ar)
|
// Check if this timer is still valid (match actor->ar)
|
||||||
@@ -556,22 +660,22 @@ uint32_t actor_remove_cb(cell_rt *actor, uint32_t id, uint32_t interval)
|
|||||||
pthread_mutex_unlock(actor->mutex);
|
pthread_mutex_unlock(actor->mutex);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
actor->disrupt = 1;
|
actor->disrupt = 1;
|
||||||
|
|
||||||
if (!JS_IsNull(actor->unneeded_ref.val)) {
|
if (!JS_IsNull(actor->unneeded_ref.val)) {
|
||||||
JSValue ret = JS_Call(actor->context, actor->unneeded_ref.val, JS_NULL, 0, NULL);
|
JSValue ret = JS_Call(actor, actor->unneeded_ref.val, JS_NULL, 0, NULL);
|
||||||
uncaught_exception(actor->context, ret);
|
uncaught_exception(actor, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
int should_free = (actor->state == ACTOR_IDLE);
|
int should_free = (actor->state == ACTOR_IDLE);
|
||||||
pthread_mutex_unlock(actor->mutex);
|
pthread_mutex_unlock(actor->mutex);
|
||||||
|
|
||||||
if (should_free) actor_free(actor);
|
if (should_free) actor_free(actor);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
|
void actor_unneeded(JSContext *actor, JSValue fn, double seconds)
|
||||||
{
|
{
|
||||||
if (actor->disrupt) return;
|
if (actor->disrupt) return;
|
||||||
|
|
||||||
@@ -582,14 +686,14 @@ void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
|
|||||||
|
|
||||||
actor->unneeded_ref.val = fn;
|
actor->unneeded_ref.val = fn;
|
||||||
actor->ar_secs = seconds;
|
actor->ar_secs = seconds;
|
||||||
|
|
||||||
END:
|
END:
|
||||||
if (actor->ar)
|
if (actor->ar)
|
||||||
actor->ar = 0;
|
actor->ar = 0;
|
||||||
set_actor_state(actor);
|
set_actor_state(actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
cell_rt *get_actor(char *id)
|
JSContext *get_actor(char *id)
|
||||||
{
|
{
|
||||||
int idx = lockless_shgeti(actors, id);
|
int idx = lockless_shgeti(actors, id);
|
||||||
if (idx == -1) {
|
if (idx == -1) {
|
||||||
@@ -622,17 +726,14 @@ void actor_loop()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cell_rt *create_actor(void *wota)
|
extern JSRuntime *g_runtime;
|
||||||
|
|
||||||
|
JSContext *create_actor(void *wota)
|
||||||
{
|
{
|
||||||
cell_rt *actor = calloc(sizeof(*actor), 1);
|
JSContext *actor = JS_NewContext(g_runtime);
|
||||||
#ifdef HAVE_MIMALLOC
|
if (!actor) return NULL;
|
||||||
actor->heap = mi_heap_new();
|
|
||||||
#endif
|
|
||||||
actor->init_wota = wota;
|
actor->init_wota = wota;
|
||||||
/* GCRef fields are registered after JSContext creation in script_startup.
|
|
||||||
For now, zero-init from calloc is sufficient (val = 0 = JS_MKVAL(JS_TAG_INT,0),
|
|
||||||
which is not a pointer so GC-safe). The actual JS_NULL assignment and
|
|
||||||
JS_AddGCRef happen in script_startup. */
|
|
||||||
|
|
||||||
arrsetcap(actor->letters, 5);
|
arrsetcap(actor->letters, 5);
|
||||||
|
|
||||||
@@ -642,19 +743,22 @@ cell_rt *create_actor(void *wota)
|
|||||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||||
pthread_mutex_init(actor->mutex, &attr);
|
pthread_mutex_init(actor->mutex, &attr);
|
||||||
actor->msg_mutex = malloc(sizeof(pthread_mutex_t));
|
actor->msg_mutex = malloc(sizeof(pthread_mutex_t));
|
||||||
pthread_mutex_init(actor->msg_mutex, &attr); // msg_mutex can be recursive too to be safe
|
pthread_mutex_init(actor->msg_mutex, &attr);
|
||||||
pthread_mutexattr_destroy(&attr);
|
pthread_mutexattr_destroy(&attr);
|
||||||
|
|
||||||
|
JS_SetGCScanExternal(actor, actor_gc_scan);
|
||||||
|
JS_SetHeapMemoryLimit(actor, ACTOR_MEMORY_LIMIT);
|
||||||
|
|
||||||
/* Lock actor->mutex while initializing JS runtime. */
|
/* Lock actor->mutex while initializing JS runtime. */
|
||||||
pthread_mutex_lock(actor->mutex);
|
pthread_mutex_lock(actor->mutex);
|
||||||
script_startup(actor);
|
script_startup(actor);
|
||||||
set_actor_state(actor);
|
set_actor_state(actor);
|
||||||
pthread_mutex_unlock(actor->mutex);
|
pthread_mutex_unlock(actor->mutex);
|
||||||
|
|
||||||
return actor;
|
return actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar)
|
const char *register_actor(const char *id, JSContext *actor, int mainthread, double ar)
|
||||||
{
|
{
|
||||||
actor->main_thread_only = mainthread;
|
actor->main_thread_only = mainthread;
|
||||||
actor->id = strdup(id);
|
actor->id = strdup(id);
|
||||||
@@ -676,29 +780,29 @@ const char *register_actor(const char *id, cell_rt *actor, int mainthread, doubl
|
|||||||
|
|
||||||
const char *send_message(const char *id, void *msg)
|
const char *send_message(const char *id, void *msg)
|
||||||
{
|
{
|
||||||
cell_rt *target = get_actor(id);
|
JSContext *target = get_actor(id);
|
||||||
if (!target) {
|
if (!target) {
|
||||||
blob_destroy((blob *)msg);
|
blob_destroy((blob *)msg);
|
||||||
return "Could not get actor from id.";
|
return "Could not get actor from id.";
|
||||||
}
|
}
|
||||||
|
|
||||||
letter l;
|
letter l;
|
||||||
l.type = LETTER_BLOB;
|
l.type = LETTER_BLOB;
|
||||||
l.blob_data = (blob *)msg;
|
l.blob_data = (blob *)msg;
|
||||||
|
|
||||||
pthread_mutex_lock(target->msg_mutex);
|
pthread_mutex_lock(target->msg_mutex);
|
||||||
arrput(target->letters, l);
|
arrput(target->letters, l);
|
||||||
pthread_mutex_unlock(target->msg_mutex);
|
pthread_mutex_unlock(target->msg_mutex);
|
||||||
|
|
||||||
if (target->ar)
|
if (target->ar)
|
||||||
target->ar = 0;
|
target->ar = 0;
|
||||||
|
|
||||||
set_actor_state(target);
|
set_actor_state(target);
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void actor_turn(cell_rt *actor)
|
void actor_turn(JSContext *actor)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(actor->mutex);
|
pthread_mutex_lock(actor->mutex);
|
||||||
#ifdef ACTOR_TRACE
|
#ifdef ACTOR_TRACE
|
||||||
@@ -710,16 +814,21 @@ void actor_turn(cell_rt *actor)
|
|||||||
actor->name ? actor->name : actor->id, prev_state);
|
actor->name ? actor->name : actor->id, prev_state);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (actor->trace_hook)
|
if (!actor->actor_trace_hook) {
|
||||||
actor->trace_hook(actor->name, CELL_HOOK_ENTER);
|
cell_hook gh = cell_trace_gethook();
|
||||||
|
if (gh) actor->actor_trace_hook = gh;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actor->actor_trace_hook)
|
||||||
|
actor->actor_trace_hook(actor, CELL_HOOK_ENTER);
|
||||||
|
|
||||||
JSValue result;
|
JSValue result;
|
||||||
|
|
||||||
if (actor->vm_suspended) {
|
if (actor->vm_suspended) {
|
||||||
/* RESUME path: continue suspended turn under kill timer only */
|
/* RESUME path: continue suspended turn under kill timer only */
|
||||||
g_crash_ctx = actor->context;
|
g_crash_ctx = actor;
|
||||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||||
JS_SetPauseFlag(actor->context, 0);
|
JS_SetPauseFlag(actor, 0);
|
||||||
actor->turn_start_ns = cell_ns();
|
actor->turn_start_ns = cell_ns();
|
||||||
|
|
||||||
/* Register kill timer only for resume */
|
/* Register kill timer only for resume */
|
||||||
@@ -729,29 +838,27 @@ void actor_turn(cell_rt *actor)
|
|||||||
pthread_cond_signal(&engine.timer_cond);
|
pthread_cond_signal(&engine.timer_cond);
|
||||||
pthread_mutex_unlock(&engine.lock);
|
pthread_mutex_unlock(&engine.lock);
|
||||||
|
|
||||||
result = JS_ResumeRegisterVM(actor->context);
|
result = JS_ResumeRegisterVM(actor);
|
||||||
actor->vm_suspended = 0;
|
actor->vm_suspended = 0;
|
||||||
|
|
||||||
if (JS_IsSuspended(result)) {
|
if (JS_IsSuspended(result)) {
|
||||||
/* Still suspended after kill timer — shouldn't happen, kill it */
|
/* Still suspended after kill timer — shouldn't happen, kill it */
|
||||||
|
fprintf(stderr, "[slow] %s: still suspended after resume, killing\n",
|
||||||
|
actor->name ? actor->name : actor->id);
|
||||||
actor->disrupt = 1;
|
actor->disrupt = 1;
|
||||||
goto ENDTURN;
|
goto ENDTURN;
|
||||||
}
|
}
|
||||||
if (JS_IsException(result)) {
|
if (JS_IsException(result)) {
|
||||||
if (!uncaught_exception(actor->context, result))
|
if (!uncaught_exception(actor, result))
|
||||||
actor->disrupt = 1;
|
actor->disrupt = 1;
|
||||||
}
|
}
|
||||||
actor->slow_strikes++;
|
actor->slow_strikes++;
|
||||||
#ifdef ACTOR_TRACE
|
JS_Log(actor, "slow", "%s: slow strike %d/%d",
|
||||||
fprintf(stderr, "[ACTOR_TRACE] %s: slow strike %d/%d\n",
|
actor->name ? actor->name : actor->id,
|
||||||
actor->name ? actor->name : actor->id,
|
actor->slow_strikes, ACTOR_SLOW_STRIKES_MAX);
|
||||||
actor->slow_strikes, ACTOR_SLOW_STRIKES_MAX);
|
|
||||||
#endif
|
|
||||||
if (actor->slow_strikes >= ACTOR_SLOW_STRIKES_MAX) {
|
if (actor->slow_strikes >= ACTOR_SLOW_STRIKES_MAX) {
|
||||||
#ifdef ACTOR_TRACE
|
JS_Log(actor, "slow", "%s: killed after %d consecutive slow turns",
|
||||||
fprintf(stderr, "[ACTOR_TRACE] %s: %d slow strikes, killing\n",
|
actor->name ? actor->name : actor->id, actor->slow_strikes);
|
||||||
actor->name ? actor->name : actor->id, actor->slow_strikes);
|
|
||||||
#endif
|
|
||||||
actor->disrupt = 1;
|
actor->disrupt = 1;
|
||||||
}
|
}
|
||||||
goto ENDTURN;
|
goto ENDTURN;
|
||||||
@@ -764,16 +871,12 @@ void actor_turn(cell_rt *actor)
|
|||||||
pthread_mutex_unlock(actor->msg_mutex);
|
pthread_mutex_unlock(actor->msg_mutex);
|
||||||
goto ENDTURN;
|
goto ENDTURN;
|
||||||
}
|
}
|
||||||
#ifdef SCHEDULER_DEBUG
|
|
||||||
fprintf(stderr, "actor_turn: %s has %d letters, type=%d\n",
|
|
||||||
actor->name ? actor->name : actor->id, pending, actor->letters[0].type);
|
|
||||||
#endif
|
|
||||||
letter l = actor->letters[0];
|
letter l = actor->letters[0];
|
||||||
arrdel(actor->letters, 0);
|
arrdel(actor->letters, 0);
|
||||||
pthread_mutex_unlock(actor->msg_mutex);
|
pthread_mutex_unlock(actor->msg_mutex);
|
||||||
|
|
||||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||||
JS_SetPauseFlag(actor->context, 0);
|
JS_SetPauseFlag(actor, 0);
|
||||||
actor->turn_start_ns = cell_ns();
|
actor->turn_start_ns = cell_ns();
|
||||||
|
|
||||||
/* Register both pause and kill timers */
|
/* Register both pause and kill timers */
|
||||||
@@ -785,35 +888,31 @@ void actor_turn(cell_rt *actor)
|
|||||||
pthread_cond_signal(&engine.timer_cond);
|
pthread_cond_signal(&engine.timer_cond);
|
||||||
pthread_mutex_unlock(&engine.lock);
|
pthread_mutex_unlock(&engine.lock);
|
||||||
|
|
||||||
g_crash_ctx = actor->context;
|
g_crash_ctx = actor;
|
||||||
|
|
||||||
if (l.type == LETTER_BLOB) {
|
if (l.type == LETTER_BLOB) {
|
||||||
size_t size = blob_length(l.blob_data) / 8;
|
size_t size = blob_length(l.blob_data) / 8;
|
||||||
JSValue arg = js_new_blob_stoned_copy(actor->context,
|
JSValue arg = js_new_blob_stoned_copy(actor,
|
||||||
(void *)blob_data(l.blob_data), size);
|
(void *)blob_data(l.blob_data), size);
|
||||||
blob_destroy(l.blob_data);
|
blob_destroy(l.blob_data);
|
||||||
result = JS_Call(actor->context, actor->message_handle_ref.val,
|
result = JS_Call(actor, actor->message_handle_ref.val,
|
||||||
JS_NULL, 1, &arg);
|
JS_NULL, 1, &arg);
|
||||||
if (JS_IsSuspended(result)) {
|
if (JS_IsSuspended(result)) {
|
||||||
actor->vm_suspended = 1;
|
actor->vm_suspended = 1;
|
||||||
actor->state = ACTOR_SLOW;
|
actor->state = ACTOR_SLOW;
|
||||||
JS_FreeValue(actor->context, arg);
|
|
||||||
goto ENDTURN_SLOW;
|
goto ENDTURN_SLOW;
|
||||||
}
|
}
|
||||||
if (!uncaught_exception(actor->context, result))
|
if (!uncaught_exception(actor, result))
|
||||||
actor->disrupt = 1;
|
actor->disrupt = 1;
|
||||||
JS_FreeValue(actor->context, arg);
|
|
||||||
} else if (l.type == LETTER_CALLBACK) {
|
} else if (l.type == LETTER_CALLBACK) {
|
||||||
result = JS_Call(actor->context, l.callback, JS_NULL, 0, NULL);
|
result = JS_Call(actor, l.callback, JS_NULL, 0, NULL);
|
||||||
if (JS_IsSuspended(result)) {
|
if (JS_IsSuspended(result)) {
|
||||||
actor->vm_suspended = 1;
|
actor->vm_suspended = 1;
|
||||||
actor->state = ACTOR_SLOW;
|
actor->state = ACTOR_SLOW;
|
||||||
JS_FreeValue(actor->context, l.callback);
|
|
||||||
goto ENDTURN_SLOW;
|
goto ENDTURN_SLOW;
|
||||||
}
|
}
|
||||||
if (!uncaught_exception(actor->context, result))
|
if (!uncaught_exception(actor, result))
|
||||||
actor->disrupt = 1;
|
actor->disrupt = 1;
|
||||||
JS_FreeValue(actor->context, l.callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actor->disrupt) goto ENDTURN;
|
if (actor->disrupt) goto ENDTURN;
|
||||||
@@ -825,35 +924,28 @@ ENDTURN:
|
|||||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||||
actor->state = ACTOR_IDLE;
|
actor->state = ACTOR_IDLE;
|
||||||
|
|
||||||
if (actor->trace_hook)
|
if (actor->actor_trace_hook)
|
||||||
actor->trace_hook(actor->name, CELL_HOOK_EXIT);
|
actor->actor_trace_hook(actor, CELL_HOOK_EXIT);
|
||||||
|
|
||||||
if (actor->disrupt) {
|
if (actor->disrupt) {
|
||||||
#ifdef SCHEDULER_DEBUG
|
|
||||||
fprintf(stderr, "actor_turn ENDTURN: %s disrupted, freeing\n",
|
|
||||||
actor->name ? actor->name : actor->id);
|
|
||||||
#endif
|
|
||||||
pthread_mutex_unlock(actor->mutex);
|
pthread_mutex_unlock(actor->mutex);
|
||||||
actor_free(actor);
|
actor_free(actor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SCHEDULER_DEBUG
|
|
||||||
fprintf(stderr, "actor_turn ENDTURN: %s has %ld letters, calling set_actor_state\n",
|
|
||||||
actor->name ? actor->name : actor->id, (long)arrlen(actor->letters));
|
|
||||||
#endif
|
|
||||||
set_actor_state(actor);
|
set_actor_state(actor);
|
||||||
pthread_mutex_unlock(actor->mutex);
|
pthread_mutex_unlock(actor->mutex);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ENDTURN_SLOW:
|
ENDTURN_SLOW:
|
||||||
g_crash_ctx = NULL;
|
g_crash_ctx = NULL;
|
||||||
#ifdef ACTOR_TRACE
|
/* VM suspended mid-turn — can't call JS_Log, use fprintf.
|
||||||
fprintf(stderr, "[ACTOR_TRACE] %s: suspended mid-turn -> SLOW\n",
|
Print stack trace while frames are still intact. */
|
||||||
actor->name ? actor->name : actor->id);
|
fprintf(stderr, "[slow] %s: suspended mid-turn, entering slow queue (strike %d/%d)\n",
|
||||||
#endif
|
actor->name ? actor->name : actor->id,
|
||||||
if (actor->trace_hook)
|
actor->slow_strikes + 1, ACTOR_SLOW_STRIKES_MAX);
|
||||||
actor->trace_hook(actor->name, CELL_HOOK_EXIT);
|
if (actor->actor_trace_hook)
|
||||||
|
actor->actor_trace_hook(actor, CELL_HOOK_EXIT);
|
||||||
enqueue_actor_priority(actor);
|
enqueue_actor_priority(actor);
|
||||||
pthread_mutex_unlock(actor->mutex);
|
pthread_mutex_unlock(actor->mutex);
|
||||||
}
|
}
|
||||||
@@ -864,51 +956,61 @@ void actor_gc_scan(JSContext *ctx,
|
|||||||
uint8_t *from_base, uint8_t *from_end,
|
uint8_t *from_base, uint8_t *from_end,
|
||||||
uint8_t *to_base, uint8_t **to_free, uint8_t *to_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
|
/* Lock msg_mutex to synchronize with the timer thread, which reads
|
||||||
timers and writes letters under the same lock. */
|
timers and writes letters under the same lock. */
|
||||||
pthread_mutex_lock(actor->msg_mutex);
|
if (ctx->msg_mutex)
|
||||||
|
pthread_mutex_lock(ctx->msg_mutex);
|
||||||
|
|
||||||
for (int i = 0; i < arrlen(actor->letters); i++) {
|
for (int i = 0; i < arrlen(ctx->letters); i++) {
|
||||||
if (actor->letters[i].type == LETTER_CALLBACK) {
|
if (ctx->letters[i].type == LETTER_CALLBACK) {
|
||||||
actor->letters[i].callback = gc_copy_value(ctx,
|
ctx->letters[i].callback = gc_copy_value(ctx,
|
||||||
actor->letters[i].callback,
|
ctx->letters[i].callback,
|
||||||
from_base, from_end, to_base, to_free, to_end);
|
from_base, from_end, to_base, to_free, to_end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
for (int i = 0; i < hmlen(ctx->timers); i++) {
|
||||||
actor->timers[i].value = gc_copy_value(ctx,
|
ctx->timers[i].value = gc_copy_value(ctx,
|
||||||
actor->timers[i].value,
|
ctx->timers[i].value,
|
||||||
from_base, from_end, to_base, to_free, to_end);
|
from_base, from_end, to_base, to_free, to_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_unlock(actor->msg_mutex);
|
if (ctx->msg_mutex)
|
||||||
|
pthread_mutex_unlock(ctx->msg_mutex);
|
||||||
|
|
||||||
|
/* Scan I/O watch callbacks belonging to this actor */
|
||||||
|
pthread_mutex_lock(&io_mutex);
|
||||||
|
for (int i = 0; i < arrlen(g_io_watches); i++) {
|
||||||
|
if (g_io_watches[i].actor == ctx) {
|
||||||
|
g_io_watches[i].callback = gc_copy_value(ctx,
|
||||||
|
g_io_watches[i].callback,
|
||||||
|
from_base, from_end, to_base, to_free, to_end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&io_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void actor_clock(cell_rt *actor, JSValue fn)
|
void actor_clock(JSContext *actor, JSValue fn)
|
||||||
{
|
{
|
||||||
letter l;
|
letter l;
|
||||||
l.type = LETTER_CALLBACK;
|
l.type = LETTER_CALLBACK;
|
||||||
l.callback = JS_DupValue(actor->context, fn);
|
l.callback = fn;
|
||||||
pthread_mutex_lock(actor->msg_mutex);
|
pthread_mutex_lock(actor->msg_mutex);
|
||||||
arrput(actor->letters, l);
|
arrput(actor->letters, l);
|
||||||
pthread_mutex_unlock(actor->msg_mutex);
|
pthread_mutex_unlock(actor->msg_mutex);
|
||||||
set_actor_state(actor);
|
set_actor_state(actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t actor_delay(cell_rt *actor, JSValue fn, double seconds)
|
uint32_t actor_delay(JSContext *actor, JSValue fn, double seconds)
|
||||||
{
|
{
|
||||||
pthread_mutex_lock(actor->msg_mutex);
|
pthread_mutex_lock(actor->msg_mutex);
|
||||||
|
|
||||||
static uint32_t global_timer_id = 1;
|
static uint32_t global_timer_id = 1;
|
||||||
uint32_t id = global_timer_id++;
|
uint32_t id = global_timer_id++;
|
||||||
|
|
||||||
JSValue cb = JS_DupValue(actor->context, fn);
|
JSValue cb = fn;
|
||||||
hmput(actor->timers, id, cb);
|
hmput(actor->timers, id, cb);
|
||||||
|
|
||||||
uint64_t now = cell_ns();
|
uint64_t now = cell_ns();
|
||||||
uint64_t execute_at = now + (uint64_t)(seconds * 1e9);
|
uint64_t execute_at = now + (uint64_t)(seconds * 1e9);
|
||||||
|
|
||||||
@@ -923,7 +1025,7 @@ uint32_t actor_delay(cell_rt *actor, JSValue fn, double seconds)
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue actor_remove_timer(cell_rt *actor, uint32_t timer_id)
|
JSValue actor_remove_timer(JSContext *actor, uint32_t timer_id)
|
||||||
{
|
{
|
||||||
JSValue cb = JS_NULL;
|
JSValue cb = JS_NULL;
|
||||||
pthread_mutex_lock(actor->msg_mutex);
|
pthread_mutex_lock(actor->msg_mutex);
|
||||||
@@ -936,3 +1038,57 @@ JSValue actor_remove_timer(cell_rt *actor, uint32_t timer_id)
|
|||||||
// Note: We don't remove from heap, it will misfire safely
|
// Note: We don't remove from heap, it will misfire safely
|
||||||
return cb;
|
return cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int JS_ActorExists(const char *actor_id)
|
||||||
|
{
|
||||||
|
return actor_exists(actor_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *JS_SendMessage(JSContext *ctx, WotaBuffer *wb)
|
||||||
|
{
|
||||||
|
if (!wb || !wb->data || wb->size == 0)
|
||||||
|
return "Empty WOTA buffer";
|
||||||
|
|
||||||
|
/* Wrap the caller's payload in the engine protocol envelope:
|
||||||
|
{type: "user", data: <payload>}
|
||||||
|
The header takes ~6 words; pre-allocate enough for header + payload. */
|
||||||
|
WotaBuffer envelope;
|
||||||
|
wota_buffer_init(&envelope, wb->size + 8);
|
||||||
|
wota_write_record(&envelope, 2);
|
||||||
|
wota_write_text(&envelope, "type");
|
||||||
|
wota_write_text(&envelope, "user");
|
||||||
|
wota_write_text(&envelope, "data");
|
||||||
|
|
||||||
|
/* Append the caller's pre-encoded WOTA payload words directly. */
|
||||||
|
size_t need = envelope.size + wb->size;
|
||||||
|
if (need > envelope.capacity) {
|
||||||
|
size_t new_cap = envelope.capacity ? envelope.capacity * 2 : 8;
|
||||||
|
while (new_cap < need) new_cap *= 2;
|
||||||
|
envelope.data = realloc(envelope.data, new_cap * sizeof(uint64_t));
|
||||||
|
envelope.capacity = new_cap;
|
||||||
|
}
|
||||||
|
memcpy(envelope.data + envelope.size, wb->data,
|
||||||
|
wb->size * sizeof(uint64_t));
|
||||||
|
envelope.size += wb->size;
|
||||||
|
|
||||||
|
size_t byte_len = envelope.size * sizeof(uint64_t);
|
||||||
|
blob *msg = blob_new(byte_len * 8);
|
||||||
|
if (!msg) {
|
||||||
|
wota_buffer_free(&envelope);
|
||||||
|
return "Could not allocate blob";
|
||||||
|
}
|
||||||
|
|
||||||
|
blob_write_bytes(msg, envelope.data, byte_len);
|
||||||
|
blob_make_stone(msg);
|
||||||
|
wota_buffer_free(&envelope);
|
||||||
|
|
||||||
|
const char *err = send_message(ctx->id, msg);
|
||||||
|
if (!err) {
|
||||||
|
/* Success — send_message took ownership of the blob.
|
||||||
|
Free the WotaBuffer internals since we consumed them. */
|
||||||
|
wota_buffer_free(wb);
|
||||||
|
}
|
||||||
|
/* On failure, send_message already destroyed the blob.
|
||||||
|
Caller still owns wb and must free it. */
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user