Compare commits
28 Commits
mcode_stre
...
pitweb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7de20b39da | ||
|
|
ee646db394 | ||
|
|
ff80e0d30d | ||
|
|
d9f41db891 | ||
|
|
860632e0fa | ||
|
|
dcc9659e6b | ||
|
|
2f7f2233b8 | ||
|
|
eee06009b9 | ||
|
|
a765872017 | ||
|
|
a93218e1ff | ||
|
|
f2c4fa2f2b | ||
|
|
5fe05c60d3 | ||
|
|
e75596ce30 | ||
|
|
86609c27f8 | ||
|
|
356c51bde3 | ||
|
|
89421e11a4 | ||
|
|
e5fc04fecd | ||
|
|
8ec56e85fa | ||
|
|
f49ca530bb | ||
|
|
83263379bd | ||
|
|
e80e615634 | ||
|
|
c1430fd59b | ||
|
|
db73eb4eeb | ||
|
|
f2556c5622 | ||
|
|
291304f75d | ||
|
|
d26a96bc62 | ||
|
|
1ba060668e | ||
|
|
77fa058135 |
@@ -103,6 +103,11 @@ var v = a[] // pop: v is 3, a is [1, 2]
|
||||
- Most files don't have headers; files in a package are not shared between packages
|
||||
- No undefined in C API: use `JS_IsNull` and `JS_NULL` only
|
||||
- A C file with correct macros (`CELL_USE_FUNCS` etc) is loaded as a module by its name (e.g., `png.c` in a package → `use('<package>/png')`)
|
||||
- C symbol naming: `js_<pkg>_<file>_use` (e.g., `js_core_math_radians_use` for `core/math/radians`)
|
||||
- Core is the `core` package — its symbols follow the same `js_core_<name>_use` pattern as all other packages
|
||||
- Package directories should contain only source files (no `.mach`/`.mcode` alongside source)
|
||||
- Build cache files in `build/` are bare hashes (no extensions)
|
||||
- Use `JS_FRAME`/`JS_ROOT`/`JS_RETURN` macros for any C function that allocates multiple heap objects. Any `JS_New*`/`JS_SetProperty*` call can trigger GC.
|
||||
|
||||
## Project Layout
|
||||
|
||||
|
||||
2
Makefile
2
Makefile
@@ -11,7 +11,7 @@
|
||||
CELL_SHOP = $(HOME)/.cell
|
||||
CELL_CORE_PACKAGE = $(CELL_SHOP)/packages/core
|
||||
|
||||
# .cm sources that compile to .mach bytecode
|
||||
# .cm sources that compile to .mcode bytecode
|
||||
MACH_SOURCES = tokenize.cm parse.cm fold.cm mcode.cm \
|
||||
internal/bootstrap.cm internal/engine.cm
|
||||
|
||||
|
||||
33
add.ce
33
add.ce
@@ -13,6 +13,10 @@ var fd = use('fd')
|
||||
|
||||
var locator = null
|
||||
var alias = null
|
||||
var resolved = null
|
||||
var parts = null
|
||||
var cwd = null
|
||||
var build_target = null
|
||||
|
||||
array(args, function(arg) {
|
||||
if (arg == '--help' || arg == '-h') {
|
||||
@@ -41,7 +45,7 @@ if (!locator) {
|
||||
|
||||
// Resolve relative paths to absolute paths
|
||||
if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) {
|
||||
var resolved = fd.realpath(locator)
|
||||
resolved = fd.realpath(locator)
|
||||
if (resolved) {
|
||||
locator = resolved
|
||||
}
|
||||
@@ -50,7 +54,7 @@ if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../')
|
||||
// Generate default alias from locator
|
||||
if (!alias) {
|
||||
// Use the last component of the locator as alias
|
||||
var parts = array(locator, '/')
|
||||
parts = array(locator, '/')
|
||||
alias = parts[length(parts) - 1]
|
||||
// Remove any version suffix
|
||||
if (search(alias, '@') != null) {
|
||||
@@ -59,7 +63,7 @@ if (!alias) {
|
||||
}
|
||||
|
||||
// Check we're in a package directory
|
||||
var cwd = fd.realpath('.')
|
||||
cwd = fd.realpath('.')
|
||||
if (!fd.is_file(cwd + '/cell.toml')) {
|
||||
log.error("Not in a package directory (no cell.toml found)")
|
||||
$stop()
|
||||
@@ -68,16 +72,17 @@ if (!fd.is_file(cwd + '/cell.toml')) {
|
||||
log.console("Adding " + locator + " as '" + alias + "'...")
|
||||
|
||||
// Add to local project's cell.toml
|
||||
try {
|
||||
var _add_dep = function() {
|
||||
pkg.add_dependency(null, locator, alias)
|
||||
log.console(" Added to cell.toml")
|
||||
} catch (e) {
|
||||
log.error("Failed to update cell.toml: " + e)
|
||||
} disruption {
|
||||
log.error("Failed to update cell.toml")
|
||||
$stop()
|
||||
}
|
||||
_add_dep()
|
||||
|
||||
// Install to shop
|
||||
try {
|
||||
var _install = function() {
|
||||
shop.get(locator)
|
||||
shop.extract(locator)
|
||||
|
||||
@@ -85,18 +90,20 @@ try {
|
||||
shop.build_package_scripts(locator)
|
||||
|
||||
// Build C code if any
|
||||
try {
|
||||
var target = build.detect_host_target()
|
||||
build.build_dynamic(locator, target, 'release')
|
||||
} catch (e) {
|
||||
var _build_c = function() {
|
||||
build_target = build.detect_host_target()
|
||||
build.build_dynamic(locator, build_target, 'release')
|
||||
} disruption {
|
||||
// Not all packages have C code
|
||||
}
|
||||
_build_c()
|
||||
|
||||
log.console(" Installed to shop")
|
||||
} catch (e) {
|
||||
log.error("Failed to install: " + e)
|
||||
} disruption {
|
||||
log.error("Failed to install")
|
||||
$stop()
|
||||
}
|
||||
_install()
|
||||
|
||||
log.console("Added " + alias + " (" + locator + ")")
|
||||
|
||||
|
||||
@@ -379,21 +379,23 @@ static const JSCFunctionListEntry js_reader_funcs[] = {
|
||||
JS_CFUNC_DEF("count", 0, js_reader_count),
|
||||
};
|
||||
|
||||
JSValue js_miniz_use(JSContext *js)
|
||||
JSValue js_core_miniz_use(JSContext *js)
|
||||
{
|
||||
JS_FRAME(js);
|
||||
|
||||
JS_NewClassID(&js_reader_class_id);
|
||||
JS_NewClass(js, js_reader_class_id, &js_reader_class);
|
||||
JSValue reader_proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, reader_proto, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry));
|
||||
JS_SetClassProto(js, js_reader_class_id, reader_proto);
|
||||
JS_ROOT(reader_proto, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, reader_proto.val, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry));
|
||||
JS_SetClassProto(js, js_reader_class_id, reader_proto.val);
|
||||
|
||||
JS_NewClassID(&js_writer_class_id);
|
||||
JS_NewClass(js, js_writer_class_id, &js_writer_class);
|
||||
JSValue writer_proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, writer_proto, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry));
|
||||
JS_SetClassProto(js, js_writer_class_id, writer_proto);
|
||||
|
||||
JSValue export = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, export, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry));
|
||||
return export;
|
||||
JS_ROOT(writer_proto, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, writer_proto.val, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry));
|
||||
JS_SetClassProto(js, js_writer_class_id, writer_proto.val);
|
||||
|
||||
JS_ROOT(export, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, export.val, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry));
|
||||
JS_RETURN(export.val);
|
||||
}
|
||||
|
||||
183
bench_native.ce
Normal file
183
bench_native.ce
Normal file
@@ -0,0 +1,183 @@
|
||||
// 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('os')
|
||||
var fd = use('fd')
|
||||
|
||||
if (length(args) < 1) {
|
||||
print('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 = []
|
||||
if (is_function(mod)) {
|
||||
push(benches, {name: 'main', fn: mod})
|
||||
} else if (is_object(mod)) {
|
||||
var keys = array(mod)
|
||||
var i = 0
|
||||
while (i < length(keys)) {
|
||||
var 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 ---
|
||||
|
||||
print('loading VM module: ' + file)
|
||||
var vm_mod = use(name)
|
||||
var vm_benches = collect_benches(vm_mod)
|
||||
|
||||
if (length(vm_benches) == 0) {
|
||||
print('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)
|
||||
|
||||
if (has_native) {
|
||||
print('loading native module: ' + dylib_path)
|
||||
var lib = os.dylib_open(dylib_path)
|
||||
native_mod = os.dylib_symbol(lib, symbol)
|
||||
native_benches = collect_benches(native_mod)
|
||||
} else {
|
||||
print('no ' + dylib_path + ' found -- VM-only benchmarking')
|
||||
print(' hint: cell --dev compile.ce ' + file)
|
||||
}
|
||||
|
||||
// --- Run benchmarks ---
|
||||
|
||||
print('')
|
||||
print('samples: ' + text(iterations) + ' (warmup: ' + text(WARMUP) + ')')
|
||||
print('')
|
||||
|
||||
var pad = function(s, n) {
|
||||
while (length(s) < n) s = s + ' '
|
||||
return s
|
||||
}
|
||||
|
||||
var i = 0
|
||||
while (i < length(vm_benches)) {
|
||||
var b = vm_benches[i]
|
||||
var vm_result = run_bench(b.fn, 'vm')
|
||||
|
||||
print(pad(b.name, 20) + ' VM: ' + pad(format_ns(vm_result.median), 12) + ' (median) ' + format_ns(vm_result.mean) + ' (mean)')
|
||||
|
||||
// find matching native bench
|
||||
var j = 0
|
||||
var found = false
|
||||
while (j < length(native_benches)) {
|
||||
if (native_benches[j].name == b.name) {
|
||||
var nat_result = run_bench(native_benches[j].fn, 'native')
|
||||
print(pad('', 20) + ' NT: ' + pad(format_ns(nat_result.median), 12) + ' (median) ' + format_ns(nat_result.mean) + ' (mean)')
|
||||
|
||||
if (nat_result.median > 0) {
|
||||
var speedup = vm_result.median / nat_result.median
|
||||
print(pad('', 20) + ' speedup: ' + text(round(speedup * 100) / 100) + 'x')
|
||||
}
|
||||
found = true
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
|
||||
if (has_native && !found) {
|
||||
print(pad('', 20) + ' NT: (no matching function)')
|
||||
}
|
||||
|
||||
print('')
|
||||
i = i + 1
|
||||
}
|
||||
11396
boot/bootstrap.cm.mcode
Normal file
11396
boot/bootstrap.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
39881
boot/engine.cm.mcode
Normal file
39881
boot/engine.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
2244
boot/fd.cm.mcode
Normal file
2244
boot/fd.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
60251
boot/internal_shop.cm.mcode
Normal file
60251
boot/internal_shop.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
14083
boot/link.cm.mcode
Normal file
14083
boot/link.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
14693
boot/package.cm.mcode
Normal file
14693
boot/package.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
8189
boot/pronto.cm.mcode
Normal file
8189
boot/pronto.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
11500
boot/qbe.cm.mcode
Normal file
11500
boot/qbe.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
77345
boot/qbe_emit.cm.mcode
Normal file
77345
boot/qbe_emit.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
153
boot/seed_bootstrap.cm
Normal file
153
boot/seed_bootstrap.cm
Normal file
@@ -0,0 +1,153 @@
|
||||
// seed_bootstrap.cm — minimal bootstrap for regenerating boot files
|
||||
// Loads only the compiler pipeline, runs a script directly (no engine/actors)
|
||||
// Usage: ./cell --dev --seed regen
|
||||
//
|
||||
// Hidden env: os, core_path, shop_path, args, json
|
||||
|
||||
var load_internal = os.load_internal
|
||||
var fd = load_internal("js_core_internal_fd_use")
|
||||
|
||||
var use_cache = {}
|
||||
use_cache['fd'] = fd
|
||||
use_cache['os'] = os
|
||||
use_cache['json'] = json
|
||||
|
||||
function use_basic(path) {
|
||||
if (use_cache[path])
|
||||
return use_cache[path]
|
||||
var result = load_internal("js_core_" + replace(path, '/', '_') + "_use")
|
||||
if (result) {
|
||||
use_cache[path] = result
|
||||
return result
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Load a module from boot .mcode — no caching, just eval
|
||||
function boot_load(name) {
|
||||
var mcode_path = core_path + '/boot/' + name + ".cm.mcode"
|
||||
var mcode_json = null
|
||||
if (!fd.is_file(mcode_path)) {
|
||||
print("seed: missing boot mcode: " + mcode_path + "\n")
|
||||
disrupt
|
||||
}
|
||||
mcode_json = text(fd.slurp(mcode_path))
|
||||
return mach_eval_mcode(name, mcode_json, {use: use_basic})
|
||||
}
|
||||
|
||||
var tokenize_mod = boot_load("tokenize")
|
||||
var parse_mod = boot_load("parse")
|
||||
var fold_mod = boot_load("fold")
|
||||
var mcode_mod = boot_load("mcode")
|
||||
var streamline_mod = boot_load("streamline")
|
||||
|
||||
use_cache['tokenize'] = tokenize_mod
|
||||
use_cache['parse'] = parse_mod
|
||||
use_cache['fold'] = fold_mod
|
||||
use_cache['mcode'] = mcode_mod
|
||||
use_cache['streamline'] = streamline_mod
|
||||
|
||||
function analyze(src, filename) {
|
||||
var tok_result = tokenize_mod(src, filename)
|
||||
var ast = parse_mod(tok_result.tokens, src, filename, tokenize_mod)
|
||||
var _i = 0
|
||||
var e = null
|
||||
var has_errors = ast.errors != null && length(ast.errors) > 0
|
||||
if (has_errors) {
|
||||
while (_i < length(ast.errors)) {
|
||||
e = ast.errors[_i]
|
||||
if (e.line != null) {
|
||||
print(`${filename}:${text(e.line)}:${text(e.column)}: error: ${e.message}`)
|
||||
} else {
|
||||
print(`${filename}: error: ${e.message}`)
|
||||
}
|
||||
_i = _i + 1
|
||||
}
|
||||
disrupt
|
||||
}
|
||||
return fold_mod(ast)
|
||||
}
|
||||
|
||||
function run_ast(name, ast, env) {
|
||||
var compiled = mcode_mod(ast)
|
||||
var optimized = streamline_mod(compiled)
|
||||
var mcode_json = json.encode(optimized)
|
||||
return mach_eval_mcode(name, mcode_json, env)
|
||||
}
|
||||
|
||||
function use_fn(path) {
|
||||
var result = null
|
||||
var file_path = null
|
||||
var script = null
|
||||
var ast = null
|
||||
var mcode_path = null
|
||||
var mcode_json = null
|
||||
if (use_cache[path])
|
||||
return use_cache[path]
|
||||
|
||||
// Try C embed
|
||||
result = load_internal("js_core_" + replace(path, '/', '_') + "_use")
|
||||
if (result) {
|
||||
use_cache[path] = result
|
||||
return result
|
||||
}
|
||||
|
||||
// Try boot mcode
|
||||
mcode_path = core_path + '/boot/' + path + '.cm.mcode'
|
||||
if (fd.is_file(mcode_path)) {
|
||||
mcode_json = text(fd.slurp(mcode_path))
|
||||
result = mach_eval_mcode(path, mcode_json, {use: use_fn})
|
||||
use_cache[path] = result
|
||||
return result
|
||||
}
|
||||
|
||||
// Try .cm source (CWD then core)
|
||||
file_path = path + '.cm'
|
||||
if (!fd.is_file(file_path))
|
||||
file_path = core_path + '/' + path + '.cm'
|
||||
if (fd.is_file(file_path)) {
|
||||
script = text(fd.slurp(file_path))
|
||||
ast = analyze(script, file_path)
|
||||
result = run_ast(path, ast, {use: use_fn})
|
||||
use_cache[path] = result
|
||||
return result
|
||||
}
|
||||
|
||||
print("seed: module not found: " + path + "\n")
|
||||
disrupt
|
||||
}
|
||||
|
||||
// Run the program from args
|
||||
var program = args[0]
|
||||
var user_args = []
|
||||
var _j = 1
|
||||
var prog_path = null
|
||||
var script = null
|
||||
var ast = null
|
||||
|
||||
if (!program) {
|
||||
print("seed: no program specified\n")
|
||||
disrupt
|
||||
}
|
||||
|
||||
while (_j < length(args)) {
|
||||
push(user_args, args[_j])
|
||||
_j = _j + 1
|
||||
}
|
||||
|
||||
prog_path = program + '.ce'
|
||||
if (!fd.is_file(prog_path))
|
||||
prog_path = core_path + '/' + program + '.ce'
|
||||
if (!fd.is_file(prog_path)) {
|
||||
prog_path = program + '.cm'
|
||||
if (!fd.is_file(prog_path))
|
||||
prog_path = core_path + '/' + program + '.cm'
|
||||
}
|
||||
if (!fd.is_file(prog_path)) {
|
||||
print("seed: program not found: " + program + "\n")
|
||||
disrupt
|
||||
}
|
||||
|
||||
script = text(fd.slurp(prog_path))
|
||||
ast = analyze(script, prog_path)
|
||||
run_ast(program, ast, {use: use_fn, args: user_args})
|
||||
6295
boot/seed_bootstrap.cm.mcode
Normal file
6295
boot/seed_bootstrap.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
14564
boot/time.cm.mcode
Normal file
14564
boot/time.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
12640
boot/toml.cm.mcode
Normal file
12640
boot/toml.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
4422
boot/toolchains.cm.mcode
Normal file
4422
boot/toolchains.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
24524
boot/verify_ir.cm.mcode
Normal file
24524
boot/verify_ir.cm.mcode
Normal file
File diff suppressed because it is too large
Load Diff
42
bootstrap.ce
42
bootstrap.ce
@@ -1,42 +0,0 @@
|
||||
// bootstrap.ce — regenerate .mach bytecode files consumed by the mach engine
|
||||
// usage: cell bootstrap.ce
|
||||
|
||||
var fd = use("fd")
|
||||
var json = use("json")
|
||||
var tokenize = use("tokenize")
|
||||
var parse = use("parse")
|
||||
var fold = use("fold")
|
||||
|
||||
var files = [
|
||||
{src: "tokenize.cm", name: "tokenize", out: "tokenize.mach"},
|
||||
{src: "parse.cm", name: "parse", out: "parse.mach"},
|
||||
{src: "fold.cm", name: "fold", out: "fold.mach"},
|
||||
{src: "mcode.cm", name: "mcode", out: "mcode.mach"},
|
||||
{src: "internal/bootstrap.cm", name: "bootstrap", out: "internal/bootstrap.mach"},
|
||||
{src: "internal/engine.cm", name: "engine", out: "internal/engine.mach"}
|
||||
]
|
||||
|
||||
var i = 0
|
||||
var entry = null
|
||||
var src = null
|
||||
var tok_result = null
|
||||
var ast = null
|
||||
var folded = null
|
||||
var ast_json = null
|
||||
var bytecode = null
|
||||
var f = null
|
||||
|
||||
while (i < length(files)) {
|
||||
entry = files[i]
|
||||
src = text(fd.slurp(entry.src))
|
||||
tok_result = tokenize(src, entry.src)
|
||||
ast = parse(tok_result.tokens, src, entry.src, tokenize)
|
||||
folded = fold(ast)
|
||||
ast_json = json.encode(folded)
|
||||
bytecode = mach_compile_ast(entry.name, ast_json)
|
||||
f = fd.open(entry.out, "w")
|
||||
fd.write(f, bytecode)
|
||||
fd.close(f)
|
||||
print(`wrote ${entry.out}`)
|
||||
i = i + 1
|
||||
}
|
||||
38
build.ce
38
build.ce
@@ -17,8 +17,16 @@ var target_package = null
|
||||
var buildtype = 'release'
|
||||
var force_rebuild = false
|
||||
var dry_run = false
|
||||
var i = 0
|
||||
var targets = null
|
||||
var t = 0
|
||||
var resolved = null
|
||||
var lib = null
|
||||
var results = null
|
||||
var success = 0
|
||||
var failed = 0
|
||||
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
for (i = 0; i < length(args); i++) {
|
||||
if (args[i] == '-t' || args[i] == '--target') {
|
||||
if (i + 1 < length(args)) {
|
||||
target = args[++i]
|
||||
@@ -51,8 +59,8 @@ for (var i = 0; i < length(args); i++) {
|
||||
dry_run = true
|
||||
} else if (args[i] == '--list-targets') {
|
||||
log.console('Available targets:')
|
||||
var targets = build.list_targets()
|
||||
for (var t = 0; t < length(targets); t++) {
|
||||
targets = build.list_targets()
|
||||
for (t = 0; t < length(targets); t++) {
|
||||
log.console(' ' + targets[t])
|
||||
}
|
||||
$stop()
|
||||
@@ -65,7 +73,7 @@ for (var i = 0; i < length(args); i++) {
|
||||
// Resolve local paths to absolute paths
|
||||
if (target_package) {
|
||||
if (target_package == '.' || starts_with(target_package, './') || starts_with(target_package, '../') || fd.is_dir(target_package)) {
|
||||
var resolved = fd.realpath(target_package)
|
||||
resolved = fd.realpath(target_package)
|
||||
if (resolved) {
|
||||
target_package = resolved
|
||||
}
|
||||
@@ -91,33 +99,35 @@ arrfor(packages, function(package) {
|
||||
shop.extract(package)
|
||||
})
|
||||
|
||||
var _build = null
|
||||
if (target_package) {
|
||||
// Build single package
|
||||
log.console('Building ' + target_package + '...')
|
||||
try {
|
||||
var lib = build.build_dynamic(target_package, target, buildtype)
|
||||
_build = function() {
|
||||
lib = build.build_dynamic(target_package, target, buildtype)
|
||||
if (lib) {
|
||||
log.console('Built: ' + lib)
|
||||
}
|
||||
} catch (e) {
|
||||
log.error('Build failed: ' + e)
|
||||
} disruption {
|
||||
log.error('Build failed')
|
||||
$stop()
|
||||
}
|
||||
_build()
|
||||
} else {
|
||||
// Build all packages
|
||||
log.console('Building all packages...')
|
||||
var results = build.build_all_dynamic(target, buildtype)
|
||||
|
||||
var success = 0
|
||||
var failed = 0
|
||||
for (var i = 0; i < length(results); i++) {
|
||||
results = build.build_all_dynamic(target, buildtype)
|
||||
|
||||
success = 0
|
||||
failed = 0
|
||||
for (i = 0; i < length(results); i++) {
|
||||
if (results[i].library) {
|
||||
success++
|
||||
} else if (results[i].error) {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
log.console(`Build complete: ${success} libraries built${failed > 0 ? `, ${failed} failed` : ''}`)
|
||||
}
|
||||
|
||||
|
||||
480
build.cm
480
build.cm
@@ -85,7 +85,8 @@ function ensure_dir(path) {
|
||||
if (fd.stat(path).isDirectory) return
|
||||
var parts = array(path, '/')
|
||||
var current = starts_with(path, '/') ? '/' : ''
|
||||
for (var i = 0; i < length(parts); i++) {
|
||||
var i = 0
|
||||
for (i = 0; i < length(parts); i++) {
|
||||
if (parts[i] == '') continue
|
||||
current += parts[i] + '/'
|
||||
if (!fd.stat(current).isDirectory) fd.mkdir(current)
|
||||
@@ -100,12 +101,13 @@ Build.ensure_dir = ensure_dir
|
||||
|
||||
// Compile a single C file for a package
|
||||
// Returns the object file path (content-addressed in .cell/build)
|
||||
Build.compile_file = function(pkg, file, target, buildtype = 'release') {
|
||||
Build.compile_file = function(pkg, file, target, buildtype) {
|
||||
var _buildtype = buildtype || 'release'
|
||||
var pkg_dir = shop.get_package_dir(pkg)
|
||||
var src_path = pkg_dir + '/' + file
|
||||
|
||||
|
||||
if (!fd.is_file(src_path)) {
|
||||
throw Error('Source file not found: ' + src_path)
|
||||
print('Source file not found: ' + src_path); disrupt
|
||||
}
|
||||
|
||||
// Get flags (with sigil replacement)
|
||||
@@ -120,11 +122,11 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') {
|
||||
var cmd_parts = [cc, '-c', '-fPIC']
|
||||
|
||||
// Add buildtype-specific flags
|
||||
if (buildtype == 'release') {
|
||||
if (_buildtype == 'release') {
|
||||
cmd_parts = array(cmd_parts, ['-O3', '-DNDEBUG'])
|
||||
} else if (buildtype == 'debug') {
|
||||
} else if (_buildtype == 'debug') {
|
||||
cmd_parts = array(cmd_parts, ['-O2', '-g'])
|
||||
} else if (buildtype == 'minsize') {
|
||||
} else if (_buildtype == 'minsize') {
|
||||
cmd_parts = array(cmd_parts, ['-Os', '-DNDEBUG'])
|
||||
}
|
||||
|
||||
@@ -133,10 +135,11 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') {
|
||||
|
||||
// Add package CFLAGS (resolve relative -I paths)
|
||||
arrfor(cflags, function(flag) {
|
||||
if (starts_with(flag, '-I') && !starts_with(flag, '-I/')) {
|
||||
flag = '-I"' + pkg_dir + '/' + text(flag, 2) + '"'
|
||||
var f = flag
|
||||
if (starts_with(f, '-I') && !starts_with(f, '-I/')) {
|
||||
f = '-I"' + pkg_dir + '/' + text(f, 2) + '"'
|
||||
}
|
||||
push(cmd_parts, flag)
|
||||
push(cmd_parts, f)
|
||||
})
|
||||
|
||||
// Add target CFLAGS
|
||||
@@ -167,7 +170,7 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') {
|
||||
log.console('Compiling ' + file)
|
||||
var ret = os.system(full_cmd)
|
||||
if (ret != 0) {
|
||||
throw Error('Compilation failed: ' + file)
|
||||
print('Compilation failed: ' + file); disrupt
|
||||
}
|
||||
|
||||
return obj_path
|
||||
@@ -175,12 +178,14 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') {
|
||||
|
||||
// Build all C files for a package
|
||||
// Returns array of object file paths
|
||||
Build.build_package = function(pkg, target = Build.detect_host_target(), exclude_main, buildtype = 'release') {
|
||||
var c_files = pkg_tools.get_c_files(pkg, target, exclude_main)
|
||||
Build.build_package = function(pkg, target, exclude_main, buildtype) {
|
||||
var _target = target || Build.detect_host_target()
|
||||
var _buildtype = buildtype || 'release'
|
||||
var c_files = pkg_tools.get_c_files(pkg, _target, exclude_main)
|
||||
var objects = []
|
||||
|
||||
|
||||
arrfor(c_files, function(file) {
|
||||
var obj = Build.compile_file(pkg, file, target, buildtype)
|
||||
var obj = Build.compile_file(pkg, file, _target, _buildtype)
|
||||
push(objects, obj)
|
||||
})
|
||||
|
||||
@@ -192,14 +197,14 @@ Build.build_package = function(pkg, target = Build.detect_host_target(), exclude
|
||||
// ============================================================================
|
||||
|
||||
// Compute link key from all inputs that affect the dylib output
|
||||
function compute_link_key(objects, ldflags, target_ldflags, target, cc) {
|
||||
function compute_link_key(objects, ldflags, target_ldflags, opts) {
|
||||
// Sort objects for deterministic hash
|
||||
var sorted_objects = sort(objects)
|
||||
|
||||
// Build a string representing all link inputs
|
||||
var parts = []
|
||||
push(parts, 'target:' + target)
|
||||
push(parts, 'cc:' + cc)
|
||||
push(parts, 'target:' + opts.target)
|
||||
push(parts, 'cc:' + opts.cc)
|
||||
arrfor(sorted_objects, function(obj) {
|
||||
// Object paths are content-addressed, so the path itself is the hash
|
||||
push(parts, 'obj:' + obj)
|
||||
@@ -214,74 +219,46 @@ function compute_link_key(objects, ldflags, target_ldflags, target, cc) {
|
||||
return content_hash(text(parts, '\n'))
|
||||
}
|
||||
|
||||
// Build a dynamic library for a package
|
||||
// Output goes to .cell/lib/<package_name>.<ext>
|
||||
// Dynamic libraries do NOT link against core; undefined symbols are resolved at dlopen time
|
||||
// Uses content-addressed store + symlink for caching
|
||||
Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildtype = 'release') {
|
||||
var objects = Build.build_package(pkg, target, true, buildtype) // exclude main.c
|
||||
// Build a per-module dynamic library for a single C file
|
||||
// Returns the content-addressed dylib path in .cell/build/<hash>.<target>.dylib
|
||||
Build.build_module_dylib = function(pkg, file, target, buildtype) {
|
||||
var _target = target || Build.detect_host_target()
|
||||
var _buildtype = buildtype || 'release'
|
||||
var obj = Build.compile_file(pkg, file, _target, _buildtype)
|
||||
|
||||
if (length(objects) == 0) {
|
||||
log.console('No C files in ' + pkg)
|
||||
return null
|
||||
}
|
||||
|
||||
var lib_dir = shop.get_lib_dir()
|
||||
var store_dir = lib_dir + '/store'
|
||||
ensure_dir(lib_dir)
|
||||
ensure_dir(store_dir)
|
||||
|
||||
var lib_name = shop.lib_name_for_package(pkg)
|
||||
var dylib_ext = toolchains[target].system == 'windows' ? '.dll' : (toolchains[target].system == 'darwin' ? '.dylib' : '.so')
|
||||
var stable_path = lib_dir + '/' + lib_name + dylib_ext
|
||||
|
||||
// Get link flags (with sigil replacement)
|
||||
var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', target))
|
||||
var target_ldflags = toolchains[target].c_link_args || []
|
||||
var cc = toolchains[target].cpp || toolchains[target].c
|
||||
var pkg_dir = shop.get_package_dir(pkg)
|
||||
var tc = toolchains[_target]
|
||||
var dylib_ext = tc.system == 'windows' ? '.dll' : (tc.system == 'darwin' ? '.dylib' : '.so')
|
||||
var cc = tc.cpp || tc.c
|
||||
var local_dir = get_local_dir()
|
||||
var tc = toolchains[target]
|
||||
var pkg_dir = shop.get_package_dir(pkg)
|
||||
|
||||
// Resolve relative -L paths in ldflags for hash computation
|
||||
// Get link flags
|
||||
var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', _target))
|
||||
var target_ldflags = tc.c_link_args || []
|
||||
var resolved_ldflags = []
|
||||
arrfor(ldflags, function(flag) {
|
||||
if (starts_with(flag, '-L') && !starts_with(flag, '-L/')) {
|
||||
flag = '-L"' + pkg_dir + '/' + text(flag, 2) + '"'
|
||||
var f = flag
|
||||
if (starts_with(f, '-L') && !starts_with(f, '-L/')) {
|
||||
f = '-L"' + pkg_dir + '/' + text(f, 2) + '"'
|
||||
}
|
||||
push(resolved_ldflags, flag)
|
||||
push(resolved_ldflags, f)
|
||||
})
|
||||
|
||||
// Compute link key
|
||||
var link_key = compute_link_key(objects, resolved_ldflags, target_ldflags, target, cc)
|
||||
var store_path = store_dir + '/' + lib_name + '-' + link_key + dylib_ext
|
||||
// Content-addressed output: hash of (object + link flags + target)
|
||||
var link_key = compute_link_key([obj], resolved_ldflags, target_ldflags, {target: _target, cc: cc})
|
||||
var build_dir = get_build_dir()
|
||||
ensure_dir(build_dir)
|
||||
var dylib_path = build_dir + '/' + link_key + '.' + _target + dylib_ext
|
||||
|
||||
// Check if already linked in store
|
||||
if (fd.is_file(store_path)) {
|
||||
// Ensure symlink points to the store file
|
||||
if (fd.is_link(stable_path)) {
|
||||
var current_target = fd.readlink(stable_path)
|
||||
if (current_target == store_path) {
|
||||
// Already up to date
|
||||
return stable_path
|
||||
}
|
||||
fd.unlink(stable_path)
|
||||
} else if (fd.is_file(stable_path)) {
|
||||
fd.unlink(stable_path)
|
||||
}
|
||||
fd.symlink(store_path, stable_path)
|
||||
return stable_path
|
||||
}
|
||||
if (fd.is_file(dylib_path))
|
||||
return dylib_path
|
||||
|
||||
// Build link command
|
||||
var cmd_parts = [cc, '-shared', '-fPIC']
|
||||
|
||||
// Platform-specific flags for undefined symbols (resolved at dlopen) and size optimization
|
||||
if (tc.system == 'darwin') {
|
||||
cmd_parts = array(cmd_parts, [
|
||||
'-undefined', 'dynamic_lookup',
|
||||
'-Wl,-dead_strip',
|
||||
'-Wl,-install_name,' + stable_path,
|
||||
'-Wl,-rpath,@loader_path/../local',
|
||||
'-Wl,-rpath,' + local_dir
|
||||
])
|
||||
@@ -293,41 +270,53 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty
|
||||
'-Wl,-rpath,' + local_dir
|
||||
])
|
||||
} else if (tc.system == 'windows') {
|
||||
// Windows DLLs: use --allow-shlib-undefined for mingw
|
||||
push(cmd_parts, '-Wl,--allow-shlib-undefined')
|
||||
}
|
||||
|
||||
// Add .cell/local to library search path
|
||||
push(cmd_parts, '-L"' + local_dir + '"')
|
||||
|
||||
arrfor(objects, function(obj) {
|
||||
push(cmd_parts, '"' + obj + '"')
|
||||
})
|
||||
|
||||
// Do NOT link against core library - symbols resolved at dlopen time
|
||||
push(cmd_parts, '"' + obj + '"')
|
||||
cmd_parts = array(cmd_parts, resolved_ldflags)
|
||||
cmd_parts = array(cmd_parts, target_ldflags)
|
||||
|
||||
push(cmd_parts, '-o')
|
||||
push(cmd_parts, '"' + store_path + '"')
|
||||
push(cmd_parts, '"' + dylib_path + '"')
|
||||
|
||||
var cmd_str = text(cmd_parts, ' ')
|
||||
|
||||
log.console('Linking ' + lib_name + dylib_ext)
|
||||
log.console('Linking module ' + file + ' -> ' + fd.basename(dylib_path))
|
||||
var ret = os.system(cmd_str)
|
||||
if (ret != 0) {
|
||||
throw Error('Linking failed: ' + pkg)
|
||||
print('Linking failed: ' + file); disrupt
|
||||
}
|
||||
|
||||
// Update symlink to point to the new store file
|
||||
if (fd.is_link(stable_path)) {
|
||||
fd.unlink(stable_path)
|
||||
} else if (fd.is_file(stable_path)) {
|
||||
fd.unlink(stable_path)
|
||||
// Install to deterministic lib/<pkg>/<stem>.dylib
|
||||
var file_stem = fd.stem(file)
|
||||
var install_dir = shop.get_lib_dir() + '/' + shop.lib_name_for_package(pkg)
|
||||
var stem_dir = fd.dirname(file_stem)
|
||||
if (stem_dir && stem_dir != '.') {
|
||||
install_dir = install_dir + '/' + stem_dir
|
||||
}
|
||||
fd.symlink(store_path, stable_path)
|
||||
ensure_dir(install_dir)
|
||||
var install_path = shop.get_lib_dir() + '/' + shop.lib_name_for_package(pkg) + '/' + file_stem + dylib_ext
|
||||
fd.slurpwrite(install_path, fd.slurp(dylib_path))
|
||||
|
||||
return stable_path
|
||||
return dylib_path
|
||||
}
|
||||
|
||||
// Build a dynamic library for a package (one dylib per C file)
|
||||
// Returns array of {file, symbol, dylib} for each module
|
||||
// Also writes a manifest mapping symbols to dylib paths
|
||||
Build.build_dynamic = function(pkg, target, buildtype) {
|
||||
var _target = target || Build.detect_host_target()
|
||||
var _buildtype = buildtype || 'release'
|
||||
var c_files = pkg_tools.get_c_files(pkg, _target, true)
|
||||
var results = []
|
||||
|
||||
arrfor(c_files, function(file) {
|
||||
var sym_name = shop.c_symbol_for_file(pkg, file)
|
||||
var dylib = Build.build_module_dylib(pkg, file, _target, _buildtype)
|
||||
push(results, {file: file, symbol: sym_name, dylib: dylib})
|
||||
})
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -337,7 +326,9 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty
|
||||
// Build a static binary from multiple packages
|
||||
// packages: array of package names
|
||||
// output: output binary path
|
||||
Build.build_static = function(packages, target = Build.detect_host_target(), output, buildtype = 'release') {
|
||||
Build.build_static = function(packages, target, output, buildtype) {
|
||||
var _target = target || Build.detect_host_target()
|
||||
var _buildtype = buildtype || 'release'
|
||||
var all_objects = []
|
||||
var all_ldflags = []
|
||||
var seen_flags = {}
|
||||
@@ -347,14 +338,14 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out
|
||||
var is_core = (pkg == 'core')
|
||||
|
||||
// For core, include main.c; for others, exclude it
|
||||
var objects = Build.build_package(pkg, target, !is_core, buildtype)
|
||||
|
||||
var objects = Build.build_package(pkg, _target, !is_core, _buildtype)
|
||||
|
||||
arrfor(objects, function(obj) {
|
||||
push(all_objects, obj)
|
||||
})
|
||||
|
||||
|
||||
// Collect LDFLAGS (with sigil replacement)
|
||||
var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', target))
|
||||
var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', _target))
|
||||
var pkg_dir = shop.get_package_dir(pkg)
|
||||
|
||||
// Deduplicate based on the entire LDFLAGS string for this package
|
||||
@@ -362,28 +353,29 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out
|
||||
if (!seen_flags[ldflags_key]) {
|
||||
seen_flags[ldflags_key] = true
|
||||
arrfor(ldflags, function(flag) {
|
||||
// Resolve relative -L paths
|
||||
if (starts_with(flag, '-L') && !starts_with(flag, '-L/')) {
|
||||
flag = '-L"' + pkg_dir + '/' + text(flag, 2) + '"'
|
||||
var f = flag
|
||||
if (starts_with(f, '-L') && !starts_with(f, '-L/')) {
|
||||
f = '-L"' + pkg_dir + '/' + text(f, 2) + '"'
|
||||
}
|
||||
push(all_ldflags, flag)
|
||||
push(all_ldflags, f)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if (length(all_objects) == 0) {
|
||||
throw Error('No object files to link')
|
||||
print('No object files to link'); disrupt
|
||||
}
|
||||
|
||||
|
||||
// Link
|
||||
var cc = toolchains[target].c
|
||||
var target_ldflags = toolchains[target].c_link_args || []
|
||||
var exe_ext = toolchains[target].system == 'windows' ? '.exe' : ''
|
||||
var cc = toolchains[_target].c
|
||||
var target_ldflags = toolchains[_target].c_link_args || []
|
||||
var exe_ext = toolchains[_target].system == 'windows' ? '.exe' : ''
|
||||
|
||||
if (!ends_with(output, exe_ext) && exe_ext) {
|
||||
output = output + exe_ext
|
||||
var out_path = output
|
||||
if (!ends_with(out_path, exe_ext) && exe_ext) {
|
||||
out_path = out_path + exe_ext
|
||||
}
|
||||
|
||||
|
||||
var cmd_parts = [cc]
|
||||
|
||||
arrfor(all_objects, function(obj) {
|
||||
@@ -398,17 +390,240 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out
|
||||
push(cmd_parts, flag)
|
||||
})
|
||||
|
||||
push(cmd_parts, '-o', '"' + output + '"')
|
||||
|
||||
push(cmd_parts, '-o', '"' + out_path + '"')
|
||||
|
||||
var cmd_str = text(cmd_parts, ' ')
|
||||
|
||||
log.console('Linking ' + output)
|
||||
|
||||
log.console('Linking ' + out_path)
|
||||
var ret = os.system(cmd_str)
|
||||
if (ret != 0) {
|
||||
throw Error('Linking failed with command: ' + cmd_str)
|
||||
print('Linking failed: ' + cmd_str); disrupt
|
||||
}
|
||||
|
||||
log.console('Built ' + output)
|
||||
|
||||
log.console('Built ' + out_path)
|
||||
return out_path
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Native .cm compilation (source → mcode → QBE IL → .o → .dylib)
|
||||
// ============================================================================
|
||||
|
||||
// Post-process QBE IL: insert dead labels after ret/jmp (QBE requirement)
|
||||
function qbe_insert_dead_labels(il_text) {
|
||||
var lines = array(il_text, "\n")
|
||||
var result = []
|
||||
var dead_id = 0
|
||||
var need_label = false
|
||||
var i = 0
|
||||
var line = null
|
||||
var trimmed = null
|
||||
while (i < length(lines)) {
|
||||
line = lines[i]
|
||||
trimmed = trim(line)
|
||||
if (need_label && !starts_with(trimmed, '@') && !starts_with(trimmed, '}') && length(trimmed) > 0) {
|
||||
push(result, "@_dead_" + text(dead_id))
|
||||
dead_id = dead_id + 1
|
||||
need_label = false
|
||||
}
|
||||
if (starts_with(trimmed, '@') || starts_with(trimmed, '}') || length(trimmed) == 0) {
|
||||
need_label = false
|
||||
}
|
||||
if (starts_with(trimmed, 'ret ') || starts_with(trimmed, 'jmp ')) {
|
||||
need_label = true
|
||||
}
|
||||
push(result, line)
|
||||
i = i + 1
|
||||
}
|
||||
return text(result, "\n")
|
||||
}
|
||||
|
||||
// Compile a .cm source file to a native .dylib via QBE
|
||||
// Returns the content-addressed dylib path
|
||||
Build.compile_native = function(src_path, target, buildtype, pkg) {
|
||||
var _target = target || Build.detect_host_target()
|
||||
var _buildtype = buildtype || 'release'
|
||||
var qbe_rt_path = null
|
||||
var native_stem = null
|
||||
var native_install_dir = null
|
||||
var native_install_path = null
|
||||
|
||||
if (!fd.is_file(src_path)) {
|
||||
print('Source file not found: ' + src_path); disrupt
|
||||
}
|
||||
|
||||
var tc = toolchains[_target]
|
||||
var dylib_ext = tc.system == 'windows' ? '.dll' : (tc.system == 'darwin' ? '.dylib' : '.so')
|
||||
var cc = tc.c
|
||||
|
||||
// Step 1: Read source and compile through pipeline
|
||||
var content = fd.slurp(src_path)
|
||||
var src = text(content)
|
||||
var tokenize = use('tokenize')
|
||||
var parse = use('parse')
|
||||
var fold = use('fold')
|
||||
var mcode_mod = use('mcode')
|
||||
var streamline_mod = use('streamline')
|
||||
var qbe_macros = use('qbe')
|
||||
var qbe_emit = use('qbe_emit')
|
||||
|
||||
var tok_result = tokenize(src, src_path)
|
||||
var ast = parse(tok_result.tokens, src, src_path, tokenize)
|
||||
var folded = fold(ast)
|
||||
var compiled = mcode_mod(folded)
|
||||
var optimized = streamline_mod(compiled)
|
||||
|
||||
// Step 2: Generate QBE IL
|
||||
var sym_name = null
|
||||
if (pkg) {
|
||||
sym_name = shop.c_symbol_for_file(pkg, fd.basename(src_path))
|
||||
}
|
||||
var il = qbe_emit(optimized, qbe_macros, sym_name)
|
||||
|
||||
// Step 3: Post-process (insert dead labels)
|
||||
il = qbe_insert_dead_labels(il)
|
||||
|
||||
// Content hash for cache key
|
||||
var hash = content_hash(src + '\n' + _target + '\nnative')
|
||||
var build_dir = get_build_dir()
|
||||
ensure_dir(build_dir)
|
||||
|
||||
var dylib_path = build_dir + '/' + hash + '.' + _target + dylib_ext
|
||||
if (fd.is_file(dylib_path))
|
||||
return dylib_path
|
||||
|
||||
// Step 4: Write QBE IL to temp file
|
||||
var tmp = '/tmp/cell_native_' + hash
|
||||
var ssa_path = tmp + '.ssa'
|
||||
var s_path = tmp + '.s'
|
||||
var o_path = tmp + '.o'
|
||||
var rt_o_path = '/tmp/cell_qbe_rt.o'
|
||||
|
||||
fd.slurpwrite(ssa_path, stone(blob(il)))
|
||||
|
||||
// Step 5: QBE compile to assembly
|
||||
var rc = os.system('qbe -o ' + s_path + ' ' + ssa_path)
|
||||
if (rc != 0) {
|
||||
print('QBE compilation failed for: ' + src_path); disrupt
|
||||
}
|
||||
|
||||
// Step 6: Assemble
|
||||
rc = os.system(cc + ' -c ' + s_path + ' -o ' + o_path)
|
||||
if (rc != 0) {
|
||||
print('Assembly failed for: ' + src_path); disrupt
|
||||
}
|
||||
|
||||
// Step 7: Compile QBE runtime stubs if needed
|
||||
if (!fd.is_file(rt_o_path)) {
|
||||
qbe_rt_path = shop.get_package_dir('core') + '/qbe_rt.c'
|
||||
rc = os.system(cc + ' -c ' + qbe_rt_path + ' -o ' + rt_o_path + ' -fPIC')
|
||||
if (rc != 0) {
|
||||
print('QBE runtime stubs compilation failed'); disrupt
|
||||
}
|
||||
}
|
||||
|
||||
// Step 8: Link dylib
|
||||
var link_cmd = cc + ' -shared -fPIC'
|
||||
if (tc.system == 'darwin') {
|
||||
link_cmd = link_cmd + ' -undefined dynamic_lookup'
|
||||
} else if (tc.system == 'linux') {
|
||||
link_cmd = link_cmd + ' -Wl,--allow-shlib-undefined'
|
||||
}
|
||||
link_cmd = link_cmd + ' ' + o_path + ' ' + rt_o_path + ' -o ' + dylib_path
|
||||
|
||||
rc = os.system(link_cmd)
|
||||
if (rc != 0) {
|
||||
print('Linking native dylib failed for: ' + src_path); disrupt
|
||||
}
|
||||
|
||||
log.console('Built native: ' + fd.basename(dylib_path))
|
||||
|
||||
// Install to deterministic lib/<pkg>/<stem>.dylib
|
||||
if (pkg) {
|
||||
native_stem = fd.stem(fd.basename(src_path))
|
||||
native_install_dir = shop.get_lib_dir() + '/' + shop.lib_name_for_package(pkg)
|
||||
ensure_dir(native_install_dir)
|
||||
native_install_path = native_install_dir + '/' + native_stem + dylib_ext
|
||||
fd.slurpwrite(native_install_path, fd.slurp(dylib_path))
|
||||
}
|
||||
|
||||
return dylib_path
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Module table generation (for static builds)
|
||||
// ============================================================================
|
||||
|
||||
// Compile a .cm module to mach bytecode blob
|
||||
// Returns the raw mach bytes as a blob
|
||||
Build.compile_cm_to_mach = function(src_path) {
|
||||
if (!fd.is_file(src_path)) {
|
||||
print('Source file not found: ' + src_path); disrupt
|
||||
}
|
||||
var src = text(fd.slurp(src_path))
|
||||
var tokenize = use('tokenize')
|
||||
var parse = use('parse')
|
||||
var fold = use('fold')
|
||||
var mcode_mod = use('mcode')
|
||||
var streamline_mod = use('streamline')
|
||||
var json = use('json')
|
||||
|
||||
var tok_result = tokenize(src, src_path)
|
||||
var ast = parse(tok_result.tokens, src, src_path, tokenize)
|
||||
var folded = fold(ast)
|
||||
var compiled = mcode_mod(folded)
|
||||
var optimized = streamline_mod(compiled)
|
||||
return mach_compile_mcode_bin(src_path, json.encode(optimized))
|
||||
}
|
||||
|
||||
// Generate a module_table.c file that embeds mach bytecode for .cm modules
|
||||
// modules: array of {name, src_path} — name is the module name, src_path is the .cm file
|
||||
// output: path to write the generated .c file
|
||||
Build.generate_module_table = function(modules, output) {
|
||||
var lines = []
|
||||
var json = use('json')
|
||||
push(lines, '/* Generated module table — do not edit */')
|
||||
push(lines, '#include <stddef.h>')
|
||||
push(lines, '#include <string.h>')
|
||||
push(lines, '')
|
||||
push(lines, 'struct cell_embedded_entry {')
|
||||
push(lines, ' const char *name;')
|
||||
push(lines, ' const unsigned char *data;')
|
||||
push(lines, ' size_t size;')
|
||||
push(lines, '};')
|
||||
push(lines, '')
|
||||
|
||||
var entries = []
|
||||
arrfor(modules, function(mod) {
|
||||
var safe = replace(replace(replace(mod.name, '/', '_'), '.', '_'), '-', '_')
|
||||
var mach = Build.compile_cm_to_mach(mod.src_path)
|
||||
var bytes = array(mach)
|
||||
var hex = []
|
||||
arrfor(bytes, function(b) {
|
||||
push(hex, '0x' + text(b, 'h2'))
|
||||
})
|
||||
push(lines, 'static const unsigned char mod_' + safe + '_data[] = {')
|
||||
push(lines, ' ' + text(hex, ', '))
|
||||
push(lines, '};')
|
||||
push(lines, '')
|
||||
push(entries, safe)
|
||||
log.console('Embedded: ' + mod.name + ' (' + text(length(bytes)) + ' bytes)')
|
||||
})
|
||||
|
||||
// Lookup function
|
||||
push(lines, 'const struct cell_embedded_entry *cell_embedded_module_lookup(const char *name) {')
|
||||
arrfor(modules, function(mod, i) {
|
||||
var safe = entries[i]
|
||||
push(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)};')
|
||||
push(lines, ' return &e;')
|
||||
push(lines, ' }')
|
||||
})
|
||||
push(lines, ' return (void *)0;')
|
||||
push(lines, '}')
|
||||
|
||||
var c_text = text(lines, '\n')
|
||||
fd.slurpwrite(output, stone(blob(c_text)))
|
||||
log.console('Generated ' + output)
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -417,38 +632,27 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out
|
||||
// ============================================================================
|
||||
|
||||
// Build dynamic libraries for all installed packages
|
||||
Build.build_all_dynamic = function(target, buildtype = 'release') {
|
||||
target = target || Build.detect_host_target()
|
||||
|
||||
Build.build_all_dynamic = function(target, buildtype) {
|
||||
var _target = target || Build.detect_host_target()
|
||||
var _buildtype = buildtype || 'release'
|
||||
|
||||
var packages = shop.list_packages()
|
||||
var results = []
|
||||
|
||||
var core_mods = null
|
||||
|
||||
// Build core first
|
||||
if (find(packages, 'core') != null) {
|
||||
try {
|
||||
var lib = Build.build_dynamic('core', target, buildtype)
|
||||
push(results, { package: 'core', library: lib })
|
||||
} catch (e) {
|
||||
log.error('Failed to build core: ' + text(e))
|
||||
push(results, { package: 'core', error: e })
|
||||
}
|
||||
if (find(packages, function(p) { return p == 'core' }) != null) {
|
||||
core_mods = Build.build_dynamic('core', _target, _buildtype)
|
||||
push(results, {package: 'core', modules: core_mods})
|
||||
}
|
||||
|
||||
|
||||
// Build other packages
|
||||
arrfor(packages, function(pkg) {
|
||||
if (pkg == 'core') return
|
||||
|
||||
try {
|
||||
var lib = Build.build_dynamic(pkg, target, buildtype)
|
||||
push(results, { package: pkg, library: lib })
|
||||
} catch (e) {
|
||||
log.error('Failed to build ' + pkg + ': ')
|
||||
log.console(e.message)
|
||||
log.console(e.stack)
|
||||
push(results, { package: pkg, error: e })
|
||||
}
|
||||
var pkg_mods = Build.build_dynamic(pkg, _target, _buildtype)
|
||||
push(results, {package: pkg, modules: pkg_mods})
|
||||
})
|
||||
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
97
cellfs.cm
97
cellfs.cm
@@ -22,55 +22,55 @@ function normalize_path(path) {
|
||||
|
||||
// Check if a file exists in a specific mount
|
||||
function mount_exists(mount, path) {
|
||||
var result = false
|
||||
var _check = null
|
||||
if (mount.type == 'zip') {
|
||||
try {
|
||||
_check = function() {
|
||||
mount.handle.mod(path)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
result = true
|
||||
} disruption {}
|
||||
_check()
|
||||
} else if (mount.type == 'qop') {
|
||||
try {
|
||||
return mount.handle.stat(path) != null
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
} else { // fs
|
||||
_check = function() {
|
||||
result = mount.handle.stat(path) != null
|
||||
} disruption {}
|
||||
_check()
|
||||
} else {
|
||||
var full_path = fd.join_paths(mount.source, path)
|
||||
try {
|
||||
_check = function() {
|
||||
var st = fd.stat(full_path)
|
||||
return st.isFile || st.isDirectory
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
result = st.isFile || st.isDirectory
|
||||
} disruption {}
|
||||
_check()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Check if a path refers to a directory in a specific mount
|
||||
function is_directory(path) {
|
||||
var res = resolve(path)
|
||||
var mount = res.mount
|
||||
var result = false
|
||||
var _check = null
|
||||
if (mount.type == 'zip') {
|
||||
try {
|
||||
return mount.handle.is_directory(path);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
_check = function() {
|
||||
result = mount.handle.is_directory(path)
|
||||
} disruption {}
|
||||
_check()
|
||||
} else if (mount.type == 'qop') {
|
||||
try {
|
||||
return mount.handle.is_directory(path);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
} else { // fs
|
||||
_check = function() {
|
||||
result = mount.handle.is_directory(path)
|
||||
} disruption {}
|
||||
_check()
|
||||
} else {
|
||||
var full_path = fd.join_paths(mount.source, path)
|
||||
try {
|
||||
_check = function() {
|
||||
var st = fd.stat(full_path)
|
||||
return st.isDirectory
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
result = st.isDirectory
|
||||
} disruption {}
|
||||
_check()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Resolve a path to a specific mount and relative path
|
||||
@@ -102,7 +102,7 @@ function resolve(path, must_exist) {
|
||||
}, false, true)
|
||||
|
||||
if (!mount) {
|
||||
throw Error("Unknown mount point: @" + mount_name)
|
||||
print("Unknown mount point: @" + mount_name); disrupt
|
||||
}
|
||||
|
||||
return { mount: mount, path: rel_path }
|
||||
@@ -122,7 +122,7 @@ function resolve(path, must_exist) {
|
||||
}
|
||||
|
||||
if (must_exist) {
|
||||
throw Error("File not found in any mount: " + path)
|
||||
print("File not found in any mount: " + path); disrupt
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,12 +144,11 @@ function mount(source, name) {
|
||||
} else if (st.isFile) {
|
||||
var blob = fd.slurp(source)
|
||||
|
||||
// Try QOP first (it's likely faster to fail?) or Zip?
|
||||
// QOP open checks magic.
|
||||
var qop_archive = null
|
||||
try {
|
||||
qop_archive = qop.open(blob)
|
||||
} catch(e) {}
|
||||
var _try_qop = function() {
|
||||
qop_archive = qop.open(blob)
|
||||
} disruption {}
|
||||
_try_qop()
|
||||
|
||||
if (qop_archive) {
|
||||
mount_info.type = 'qop'
|
||||
@@ -158,7 +157,7 @@ function mount(source, name) {
|
||||
} else {
|
||||
var zip = miniz.read(blob)
|
||||
if (!is_object(zip) || !is_function(zip.count)) {
|
||||
throw Error("Invalid archive file (not zip or qop): " + source)
|
||||
print("Invalid archive file (not zip or qop): " + source); disrupt
|
||||
}
|
||||
|
||||
mount_info.type = 'zip'
|
||||
@@ -166,7 +165,7 @@ function mount(source, name) {
|
||||
mount_info.zip_blob = blob // keep blob alive
|
||||
}
|
||||
} else {
|
||||
throw Error("Unsupported mount source type: " + source)
|
||||
print("Unsupported mount source type: " + source); disrupt
|
||||
}
|
||||
|
||||
push(mounts, mount_info)
|
||||
@@ -182,13 +181,13 @@ function unmount(name_or_source) {
|
||||
// Read file
|
||||
function slurp(path) {
|
||||
var res = resolve(path, true)
|
||||
if (!res) throw Error("File not found: " + path)
|
||||
|
||||
if (!res) { print("File not found: " + path); disrupt }
|
||||
|
||||
if (res.mount.type == 'zip') {
|
||||
return res.mount.handle.slurp(res.path)
|
||||
} else if (res.mount.type == 'qop') {
|
||||
var data = res.mount.handle.read(res.path)
|
||||
if (!data) throw Error("File not found in qop: " + path)
|
||||
if (!data) { print("File not found in qop: " + path); disrupt }
|
||||
return data
|
||||
} else {
|
||||
var full_path = fd.join_paths(res.mount.source, res.path)
|
||||
@@ -217,8 +216,8 @@ function exists(path) {
|
||||
// Stat
|
||||
function stat(path) {
|
||||
var res = resolve(path, true)
|
||||
if (!res) throw Error("File not found: " + path)
|
||||
|
||||
if (!res) { print("File not found: " + path); disrupt }
|
||||
|
||||
if (res.mount.type == 'zip') {
|
||||
var mod = res.mount.handle.mod(res.path)
|
||||
return {
|
||||
@@ -228,7 +227,7 @@ function stat(path) {
|
||||
}
|
||||
} else if (res.mount.type == 'qop') {
|
||||
var s = res.mount.handle.stat(res.path)
|
||||
if (!s) throw Error("File not found in qop: " + path)
|
||||
if (!s) { print("File not found in qop: " + path); disrupt }
|
||||
return {
|
||||
filesize: s.size,
|
||||
modtime: s.modtime,
|
||||
@@ -261,7 +260,7 @@ function mount_package(name) {
|
||||
var dir = shop.get_package_dir(name)
|
||||
|
||||
if (!dir) {
|
||||
throw Error("Package not found: " + name)
|
||||
print("Package not found: " + name); disrupt
|
||||
}
|
||||
|
||||
mount(dir, name)
|
||||
@@ -275,7 +274,7 @@ function match(str, pattern) {
|
||||
|
||||
function rm(path) {
|
||||
var res = resolve(path, true)
|
||||
if (res.mount.type != 'fs') throw Error("Cannot delete from non-fs mount")
|
||||
if (res.mount.type != 'fs') { print("Cannot delete from non-fs mount"); disrupt }
|
||||
|
||||
var full_path = fd.join_paths(res.mount.source, res.path)
|
||||
var st = fd.stat(full_path)
|
||||
|
||||
32
clean.ce
32
clean.ce
@@ -23,8 +23,11 @@ var clean_build = false
|
||||
var clean_fetch = false
|
||||
var deep = false
|
||||
var dry_run = false
|
||||
var i = 0
|
||||
var resolved = null
|
||||
var deps = null
|
||||
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
for (i = 0; i < length(args); i++) {
|
||||
if (args[i] == '--build') {
|
||||
clean_build = true
|
||||
} else if (args[i] == '--fetch') {
|
||||
@@ -74,7 +77,7 @@ var is_world_scope = (scope == 'world')
|
||||
|
||||
if (!is_shop_scope && !is_world_scope) {
|
||||
if (scope == '.' || starts_with(scope, './') || starts_with(scope, '../') || fd.is_dir(scope)) {
|
||||
var resolved = fd.realpath(scope)
|
||||
resolved = fd.realpath(scope)
|
||||
if (resolved) {
|
||||
scope = resolved
|
||||
}
|
||||
@@ -96,15 +99,17 @@ if (is_shop_scope) {
|
||||
// Single package
|
||||
push(packages_to_clean, scope)
|
||||
|
||||
var _gather = null
|
||||
if (deep) {
|
||||
try {
|
||||
var deps = pkg.gather_dependencies(scope)
|
||||
_gather = function() {
|
||||
deps = pkg.gather_dependencies(scope)
|
||||
arrfor(deps, function(dep) {
|
||||
push(packages_to_clean, dep)
|
||||
})
|
||||
} catch (e) {
|
||||
} disruption {
|
||||
// Skip if can't read dependencies
|
||||
}
|
||||
_gather()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +173,7 @@ if (clean_fetch) {
|
||||
}
|
||||
|
||||
// Execute or report
|
||||
var deleted_count = 0
|
||||
if (dry_run) {
|
||||
log.console("Would delete:")
|
||||
if (length(files_to_delete) == 0 && length(dirs_to_delete) == 0) {
|
||||
@@ -181,20 +187,19 @@ if (dry_run) {
|
||||
})
|
||||
}
|
||||
} else {
|
||||
var deleted_count = 0
|
||||
|
||||
arrfor(files_to_delete, function(f) {
|
||||
try {
|
||||
var _del = function() {
|
||||
fd.unlink(f)
|
||||
log.console("Deleted: " + f)
|
||||
deleted_count++
|
||||
} catch (e) {
|
||||
log.error("Failed to delete " + f + ": " + e)
|
||||
} disruption {
|
||||
log.error("Failed to delete " + f)
|
||||
}
|
||||
_del()
|
||||
})
|
||||
|
||||
arrfor(dirs_to_delete, function(d) {
|
||||
try {
|
||||
var _del = function() {
|
||||
if (fd.is_link(d)) {
|
||||
fd.unlink(d)
|
||||
} else {
|
||||
@@ -202,9 +207,10 @@ if (dry_run) {
|
||||
}
|
||||
log.console("Deleted: " + d)
|
||||
deleted_count++
|
||||
} catch (e) {
|
||||
log.error("Failed to delete " + d + ": " + e)
|
||||
} disruption {
|
||||
log.error("Failed to delete " + d)
|
||||
}
|
||||
_del()
|
||||
})
|
||||
|
||||
if (deleted_count == 0) {
|
||||
|
||||
65
clone.ce
65
clone.ce
@@ -7,11 +7,14 @@ var fd = use('fd')
|
||||
var http = use('http')
|
||||
var miniz = use('miniz')
|
||||
|
||||
var resolved = null
|
||||
var cwd = null
|
||||
var parent = null
|
||||
|
||||
if (length(args) < 2) {
|
||||
log.console("Usage: cell clone <origin> <path>")
|
||||
log.console("Clones a cell package to a local path and links it.")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
var origin = args[0]
|
||||
@@ -19,19 +22,19 @@ var target_path = args[1]
|
||||
|
||||
// Resolve target path to absolute
|
||||
if (target_path == '.' || starts_with(target_path, './') || starts_with(target_path, '../')) {
|
||||
var resolved = fd.realpath(target_path)
|
||||
resolved = fd.realpath(target_path)
|
||||
if (resolved) {
|
||||
target_path = resolved
|
||||
} else {
|
||||
// Path doesn't exist yet, resolve relative to cwd
|
||||
var cwd = fd.realpath('.')
|
||||
cwd = fd.realpath('.')
|
||||
if (target_path == '.') {
|
||||
target_path = cwd
|
||||
} else if (starts_with(target_path, './')) {
|
||||
target_path = cwd + text(target_path, 1)
|
||||
} else if (starts_with(target_path, '../')) {
|
||||
// Go up one directory from cwd
|
||||
var parent = fd.dirname(cwd)
|
||||
parent = fd.dirname(cwd)
|
||||
target_path = parent + text(target_path, 2)
|
||||
}
|
||||
}
|
||||
@@ -41,7 +44,6 @@ if (target_path == '.' || starts_with(target_path, './') || starts_with(target_p
|
||||
if (fd.is_dir(target_path)) {
|
||||
log.console("Error: " + target_path + " already exists")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
log.console("Cloning " + origin + " to " + target_path + "...")
|
||||
@@ -51,7 +53,6 @@ var info = shop.resolve_package_info(origin)
|
||||
if (!info || info == 'local') {
|
||||
log.console("Error: " + origin + " is not a remote package")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
// Update to get the commit hash
|
||||
@@ -59,7 +60,6 @@ var update_result = shop.update(origin)
|
||||
if (!update_result) {
|
||||
log.console("Error: Could not fetch " + origin)
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch and extract to the target path
|
||||
@@ -68,54 +68,61 @@ var entry = lock[origin]
|
||||
if (!entry || !entry.commit) {
|
||||
log.console("Error: No commit found for " + origin)
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
var download_url = shop.get_download_url(origin, entry.commit)
|
||||
log.console("Downloading from " + download_url)
|
||||
|
||||
try {
|
||||
var zip_blob = http.fetch(download_url)
|
||||
|
||||
var zip_blob = null
|
||||
var zip = null
|
||||
var count = 0
|
||||
var i = 0
|
||||
var filename = null
|
||||
var first_slash = null
|
||||
var rel_path = null
|
||||
var full_path = null
|
||||
var dir_path = null
|
||||
|
||||
var _clone = function() {
|
||||
zip_blob = http.fetch(download_url)
|
||||
|
||||
// Extract zip to target path
|
||||
var zip = miniz.read(zip_blob)
|
||||
zip = miniz.read(zip_blob)
|
||||
if (!zip) {
|
||||
log.console("Error: Failed to read zip archive")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Create target directory
|
||||
fd.mkdir(target_path)
|
||||
|
||||
var count = zip.count()
|
||||
for (var i = 0; i < count; i++) {
|
||||
|
||||
count = zip.count()
|
||||
for (i = 0; i < count; i++) {
|
||||
if (zip.is_directory(i)) continue
|
||||
var filename = zip.get_filename(i)
|
||||
var first_slash = search(filename, '/')
|
||||
filename = zip.get_filename(i)
|
||||
first_slash = search(filename, '/')
|
||||
if (first_slash == null) continue
|
||||
if (first_slash + 1 >= length(filename)) continue
|
||||
|
||||
var rel_path = text(filename, first_slash + 1)
|
||||
var full_path = target_path + '/' + rel_path
|
||||
var dir_path = fd.dirname(full_path)
|
||||
|
||||
rel_path = text(filename, first_slash + 1)
|
||||
full_path = target_path + '/' + rel_path
|
||||
dir_path = fd.dirname(full_path)
|
||||
|
||||
// Ensure directory exists
|
||||
if (!fd.is_dir(dir_path)) {
|
||||
fd.mkdir(dir_path)
|
||||
}
|
||||
fd.slurpwrite(full_path, zip.slurp(filename))
|
||||
}
|
||||
|
||||
|
||||
log.console("Extracted to " + target_path)
|
||||
|
||||
|
||||
// Link the origin to the cloned path
|
||||
link.add(origin, target_path, shop)
|
||||
log.console("Linked " + origin + " -> " + target_path)
|
||||
|
||||
} catch (e) {
|
||||
log.console("Error: " + e.message)
|
||||
if (e.stack) log.console(e.stack)
|
||||
} disruption {
|
||||
log.console("Error during clone")
|
||||
}
|
||||
_clone()
|
||||
|
||||
$stop()
|
||||
|
||||
31
compile.ce
31
compile.ce
@@ -1,7 +1,7 @@
|
||||
// compile.ce — compile a .cm module to native .dylib via QBE
|
||||
//
|
||||
// Usage:
|
||||
// cell --core . compile.ce <file.cm>
|
||||
// cell --dev compile.ce <file.cm>
|
||||
//
|
||||
// Produces <file>.dylib in the current directory.
|
||||
|
||||
@@ -9,7 +9,7 @@ var fd = use('fd')
|
||||
var os = use('os')
|
||||
|
||||
if (length(args) < 1) {
|
||||
print('usage: cell --core . compile.ce <file.cm>')
|
||||
print('usage: cell --dev compile.ce <file.cm>')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,39 +26,22 @@ var ssa_path = tmp + '.ssa'
|
||||
var s_path = tmp + '.s'
|
||||
var o_path = tmp + '.o'
|
||||
var rt_o_path = '/tmp/qbe_rt.o'
|
||||
var dylib_path = base + '.dylib'
|
||||
var dylib_path = file + '.dylib'
|
||||
var cwd = fd.getcwd()
|
||||
var rc = 0
|
||||
|
||||
// Step 1: emit QBE IL
|
||||
print('emit qbe...')
|
||||
rc = os.system('cd ' + cwd + ' && ./cell --core . --emit-qbe ' + file + ' > ' + ssa_path)
|
||||
rc = os.system('cd ' + cwd + ' && ./cell --dev qbe.ce ' + file + ' > ' + ssa_path)
|
||||
if (rc != 0) {
|
||||
print('failed to emit qbe il')
|
||||
return
|
||||
}
|
||||
|
||||
// Step 2: post-process — insert dead labels after ret/jmp, append wrapper
|
||||
// Use awk via shell to avoid blob/slurpwrite issues with long strings
|
||||
print('post-process...')
|
||||
var awk_cmd = `awk '
|
||||
need_label && /^[[:space:]]*[^@}]/ && NF > 0 {
|
||||
print "@_dead_" dead_id; dead_id++; need_label=0
|
||||
}
|
||||
/^@/ || /^}/ || NF==0 { need_label=0 }
|
||||
/^[[:space:]]*ret / || /^[[:space:]]*jmp / { need_label=1; print; next }
|
||||
{ print }
|
||||
' ` + ssa_path + ` > ` + tmp + `_fixed.ssa`
|
||||
rc = os.system(awk_cmd)
|
||||
if (rc != 0) {
|
||||
print('post-process failed')
|
||||
return
|
||||
}
|
||||
|
||||
// Append wrapper function — called as symbol(ctx) by os.dylib_symbol.
|
||||
// Step 2: append wrapper function — called as symbol(ctx) by os.dylib_symbol.
|
||||
// Delegates to cell_rt_module_entry which heap-allocates a frame
|
||||
// (so closures survive) and calls cell_main.
|
||||
var wrapper_cmd = `printf '\nexport function l $` + symbol + `(l %%ctx) {\n@entry\n %%result =l call $cell_rt_module_entry(l %%ctx)\n ret %%result\n}\n' >> ` + tmp + `_fixed.ssa`
|
||||
var wrapper_cmd = `printf '\nexport function l $` + symbol + `(l %%ctx) {\n@entry\n %%result =l call $cell_rt_module_entry(l %%ctx)\n ret %%result\n}\n' >> ` + ssa_path
|
||||
rc = os.system(wrapper_cmd)
|
||||
if (rc != 0) {
|
||||
print('wrapper append failed')
|
||||
@@ -67,7 +50,7 @@ if (rc != 0) {
|
||||
|
||||
// Step 3: compile QBE IL to assembly
|
||||
print('qbe compile...')
|
||||
rc = os.system('~/.local/bin/qbe -o ' + s_path + ' ' + tmp + '_fixed.ssa')
|
||||
rc = os.system('qbe -o ' + s_path + ' ' + ssa_path)
|
||||
if (rc != 0) {
|
||||
print('qbe compilation failed')
|
||||
return
|
||||
|
||||
98
compile_seed.ce
Normal file
98
compile_seed.ce
Normal file
@@ -0,0 +1,98 @@
|
||||
// compile_seed.ce — compile a .cm module to native .dylib via QBE (seed mode)
|
||||
// Usage: ./cell --dev --seed compile_seed <file.cm>
|
||||
|
||||
var fd = use("fd")
|
||||
var os = use("os")
|
||||
var tokenize = use("tokenize")
|
||||
var parse = use("parse")
|
||||
var fold = use("fold")
|
||||
var mcode = use("mcode")
|
||||
var streamline = use("streamline")
|
||||
var qbe_macros = use("qbe")
|
||||
var qbe_emit = use("qbe_emit")
|
||||
|
||||
if (length(args) < 1) {
|
||||
print("usage: cell --dev --seed compile_seed <file.cm>")
|
||||
disrupt
|
||||
}
|
||||
|
||||
var file = args[0]
|
||||
var base = file
|
||||
if (ends_with(base, ".cm")) {
|
||||
base = text(base, 0, length(base) - 3)
|
||||
} else if (ends_with(base, ".ce")) {
|
||||
base = text(base, 0, length(base) - 3)
|
||||
}
|
||||
|
||||
var safe = replace(replace(replace(base, "/", "_"), "-", "_"), ".", "_")
|
||||
var symbol = "js_" + safe + "_use"
|
||||
var tmp = "/tmp/qbe_" + safe
|
||||
var ssa_path = tmp + ".ssa"
|
||||
var s_path = tmp + ".s"
|
||||
var o_path = tmp + ".o"
|
||||
var rt_o_path = "/tmp/qbe_rt.o"
|
||||
var dylib_path = file + ".dylib"
|
||||
var rc = 0
|
||||
|
||||
// Step 1: compile to QBE IL
|
||||
print("compiling " + file + " to QBE IL...")
|
||||
var src = text(fd.slurp(file))
|
||||
var result = tokenize(src, file)
|
||||
var ast = parse(result.tokens, src, file, tokenize)
|
||||
var folded = fold(ast)
|
||||
var compiled = mcode(folded)
|
||||
var optimized = streamline(compiled)
|
||||
var il = qbe_emit(optimized, qbe_macros)
|
||||
|
||||
// Step 2: append wrapper function
|
||||
var wrapper = `
|
||||
export function l $${symbol}(l %ctx) {
|
||||
@entry
|
||||
%result =l call $cell_rt_module_entry(l %ctx)
|
||||
ret %result
|
||||
}
|
||||
`
|
||||
il = il + wrapper
|
||||
|
||||
// Write IL to file — remove old file first to avoid leftover content
|
||||
if (fd.is_file(ssa_path)) fd.unlink(ssa_path)
|
||||
var out_fd = fd.open(ssa_path, 1537, 420)
|
||||
fd.write(out_fd, il)
|
||||
fd.close(out_fd)
|
||||
print("wrote " + ssa_path + " (" + text(length(il)) + " bytes)")
|
||||
|
||||
// Step 3: compile QBE IL to assembly
|
||||
print("qbe compile...")
|
||||
rc = os.system("qbe -o " + s_path + " " + ssa_path)
|
||||
if (rc != 0) {
|
||||
print("qbe compilation failed")
|
||||
disrupt
|
||||
}
|
||||
|
||||
// Step 4: assemble
|
||||
print("assemble...")
|
||||
rc = os.system("cc -c " + s_path + " -o " + o_path)
|
||||
if (rc != 0) {
|
||||
print("assembly failed")
|
||||
disrupt
|
||||
}
|
||||
|
||||
// Step 5: compile runtime stubs
|
||||
if (!fd.is_file(rt_o_path)) {
|
||||
print("compile runtime stubs...")
|
||||
rc = os.system("cc -c source/qbe_helpers.c -o " + rt_o_path + " -fPIC -Isource")
|
||||
if (rc != 0) {
|
||||
print("runtime stubs compilation failed")
|
||||
disrupt
|
||||
}
|
||||
}
|
||||
|
||||
// Step 6: link dylib
|
||||
print("link...")
|
||||
rc = os.system("cc -shared -fPIC -undefined dynamic_lookup " + o_path + " " + rt_o_path + " -o " + dylib_path)
|
||||
if (rc != 0) {
|
||||
print("linking failed")
|
||||
disrupt
|
||||
}
|
||||
|
||||
print("built: " + dylib_path)
|
||||
261
config.ce
261
config.ce
@@ -47,8 +47,10 @@ function get_nested(obj, path) {
|
||||
// Set a value in nested object using path
|
||||
function set_nested(obj, path, value) {
|
||||
var current = obj
|
||||
for (var i = 0; i < length(path) - 1; i++) {
|
||||
var segment = path[i]
|
||||
var i = 0
|
||||
var segment = null
|
||||
for (i = 0; i < length(path) - 1; i++) {
|
||||
segment = path[i]
|
||||
if (is_null(current[segment]) || !is_object(current[segment])) {
|
||||
current[segment] = {}
|
||||
}
|
||||
@@ -59,15 +61,17 @@ function set_nested(obj, path, value) {
|
||||
|
||||
// Parse value string into appropriate type
|
||||
function parse_value(str) {
|
||||
var num_str = null
|
||||
var n = null
|
||||
// Boolean
|
||||
if (str == 'true') return true
|
||||
if (str == 'false') return false
|
||||
|
||||
// Number (including underscores)
|
||||
var num_str = replace(str, /_/g, '')
|
||||
if (/^-?\d+$/.test(num_str)) return parseInt(num_str)
|
||||
if (/^-?\d*\.\d+$/.test(num_str)) return parseFloat(num_str)
|
||||
|
||||
|
||||
// Number
|
||||
num_str = replace(str, /_/g, '')
|
||||
n = number(num_str)
|
||||
if (n != null) return n
|
||||
|
||||
// String
|
||||
return str
|
||||
}
|
||||
@@ -75,22 +79,19 @@ function parse_value(str) {
|
||||
// Format value for display
|
||||
function format_value(val) {
|
||||
if (is_text(val)) return '"' + val + '"'
|
||||
if (is_number(val) && val >= 1000) {
|
||||
// Add underscores to large numbers
|
||||
return replace(val.toString(), /\B(?=(\d{3})+(?!\d))/g, '_')
|
||||
}
|
||||
return text(val)
|
||||
}
|
||||
|
||||
// Print configuration tree recursively
|
||||
function print_config(obj, prefix = '') {
|
||||
function print_config(obj, pfx) {
|
||||
var p = pfx || ''
|
||||
arrfor(array(obj), function(key) {
|
||||
var val = obj[key]
|
||||
var full_key = prefix ? prefix + '.' + key : key
|
||||
|
||||
var full_key = p ? p + '.' + key : key
|
||||
|
||||
if (is_object(val))
|
||||
print_config(val, full_key)
|
||||
else
|
||||
else if (!is_null(val))
|
||||
log.console(full_key + ' = ' + format_value(val))
|
||||
})
|
||||
}
|
||||
@@ -99,151 +100,123 @@ function print_config(obj, prefix = '') {
|
||||
if (length(args) == 0) {
|
||||
print_help()
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
var config = pkg.load_config()
|
||||
if (!config) {
|
||||
log.error("Failed to load cell.toml")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
var command = args[0]
|
||||
var key
|
||||
var path
|
||||
var value
|
||||
var key = null
|
||||
var path = null
|
||||
var value = null
|
||||
var value_str = null
|
||||
var valid_system_keys = null
|
||||
var actor_name = null
|
||||
var actor_cmd = null
|
||||
|
||||
switch (command) {
|
||||
case 'help':
|
||||
case '-h':
|
||||
case '--help':
|
||||
print_help()
|
||||
break
|
||||
|
||||
case 'list':
|
||||
log.console("# Cell Configuration")
|
||||
log.console("")
|
||||
print_config(config)
|
||||
break
|
||||
|
||||
case 'get':
|
||||
if (length(args) < 2) {
|
||||
log.error("Usage: cell config get <key>")
|
||||
if (command == 'help' || command == '-h' || command == '--help') {
|
||||
print_help()
|
||||
} else if (command == 'list') {
|
||||
log.console("# Cell Configuration")
|
||||
log.console("")
|
||||
print_config(config)
|
||||
} else if (command == 'get') {
|
||||
if (length(args) < 2) {
|
||||
log.error("Usage: cell config get <key>")
|
||||
$stop()
|
||||
}
|
||||
key = args[1]
|
||||
path = parse_key(key)
|
||||
value = get_nested(config, path)
|
||||
|
||||
if (value == null) {
|
||||
log.error("Key not found: " + key)
|
||||
} else if (is_object(value)) {
|
||||
print_config(value, key)
|
||||
} else {
|
||||
log.console(key + ' = ' + format_value(value))
|
||||
}
|
||||
} else if (command == 'set') {
|
||||
if (length(args) < 3) {
|
||||
log.error("Usage: cell config set <key> <value>")
|
||||
$stop()
|
||||
}
|
||||
key = args[1]
|
||||
value_str = args[2]
|
||||
path = parse_key(key)
|
||||
value = parse_value(value_str)
|
||||
|
||||
if (path[0] == 'system') {
|
||||
valid_system_keys = [
|
||||
'ar_timer', 'actor_memory', 'net_service',
|
||||
'reply_timeout', 'actor_max', 'stack_max'
|
||||
]
|
||||
if (find(valid_system_keys, path[1]) == null) {
|
||||
log.error("Invalid system key. Valid keys: " + text(valid_system_keys, ', '))
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
key = args[1]
|
||||
path = parse_key(key)
|
||||
value = get_nested(config, path)
|
||||
|
||||
if (value == null) {
|
||||
log.error("Key not found: " + key)
|
||||
} else if (isa(value, object)) {
|
||||
// Print all nested values
|
||||
print_config(value, key)
|
||||
}
|
||||
|
||||
set_nested(config, path, value)
|
||||
pkg.save_config(config)
|
||||
log.console("Set " + key + " = " + format_value(value))
|
||||
} else if (command == 'actor') {
|
||||
if (length(args) < 3) {
|
||||
log.error("Usage: cell config actor <name> <command> [options]")
|
||||
$stop()
|
||||
}
|
||||
|
||||
actor_name = args[1]
|
||||
actor_cmd = args[2]
|
||||
|
||||
config.actors = config.actors || {}
|
||||
config.actors[actor_name] = config.actors[actor_name] || {}
|
||||
|
||||
if (actor_cmd == 'list') {
|
||||
if (length(array(config.actors[actor_name])) == 0) {
|
||||
log.console("No configuration for actor: " + actor_name)
|
||||
} else {
|
||||
log.console(key + ' = ' + format_value(value))
|
||||
log.console("# Configuration for actor: " + actor_name)
|
||||
log.console("")
|
||||
print_config(config.actors[actor_name], 'actors.' + actor_name)
|
||||
}
|
||||
break
|
||||
|
||||
case 'set':
|
||||
if (length(args) < 3) {
|
||||
log.error("Usage: cell config set <key> <value>")
|
||||
} else if (actor_cmd == 'get') {
|
||||
if (length(args) < 4) {
|
||||
log.error("Usage: cell config actor <name> get <key>")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
var key = args[1]
|
||||
var value_str = args[2]
|
||||
var path = parse_key(key)
|
||||
var value = parse_value(value_str)
|
||||
|
||||
// Validate system keys
|
||||
if (path[0] == 'system') {
|
||||
var valid_system_keys = [
|
||||
'ar_timer', 'actor_memory', 'net_service',
|
||||
'reply_timeout', 'actor_max', 'stack_max'
|
||||
]
|
||||
if (find(valid_system_keys, path[1]) == null) {
|
||||
log.error("Invalid system key. Valid keys: " + text(valid_system_keys, ', '))
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
key = args[3]
|
||||
path = parse_key(key)
|
||||
value = get_nested(config.actors[actor_name], path)
|
||||
|
||||
if (value == null) {
|
||||
log.error("Key not found for actor " + actor_name + ": " + key)
|
||||
} else {
|
||||
log.console('actors.' + actor_name + '.' + key + ' = ' + format_value(value))
|
||||
}
|
||||
|
||||
set_nested(config, path, value)
|
||||
} else if (actor_cmd == 'set') {
|
||||
if (length(args) < 5) {
|
||||
log.error("Usage: cell config actor <name> set <key> <value>")
|
||||
$stop()
|
||||
}
|
||||
key = args[3]
|
||||
value_str = args[4]
|
||||
path = parse_key(key)
|
||||
value = parse_value(value_str)
|
||||
|
||||
set_nested(config.actors[actor_name], path, value)
|
||||
pkg.save_config(config)
|
||||
log.console("Set " + key + " = " + format_value(value))
|
||||
break
|
||||
|
||||
case 'actor':
|
||||
// Handle actor-specific configuration
|
||||
if (length(args) < 3) {
|
||||
log.error("Usage: cell config actor <name> <command> [options]")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
var actor_name = args[1]
|
||||
var actor_cmd = args[2]
|
||||
|
||||
// Initialize actors section if needed
|
||||
config.actors = config.actors || {}
|
||||
config.actors[actor_name] = config.actors[actor_name] || {}
|
||||
|
||||
switch (actor_cmd) {
|
||||
case 'list':
|
||||
if (length(array(config.actors[actor_name])) == 0) {
|
||||
log.console("No configuration for actor: " + actor_name)
|
||||
} else {
|
||||
log.console("# Configuration for actor: " + actor_name)
|
||||
log.console("")
|
||||
print_config(config.actors[actor_name], 'actors.' + actor_name)
|
||||
}
|
||||
break
|
||||
|
||||
case 'get':
|
||||
if (length(args) < 4) {
|
||||
log.error("Usage: cell config actor <name> get <key>")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
key = args[3]
|
||||
path = parse_key(key)
|
||||
value = get_nested(config.actors[actor_name], path)
|
||||
|
||||
if (value == null) {
|
||||
log.error("Key not found for actor " + actor_name + ": " + key)
|
||||
} else {
|
||||
log.console('actors.' + actor_name + '.' + key + ' = ' + format_value(value))
|
||||
}
|
||||
break
|
||||
|
||||
case 'set':
|
||||
if (length(args) < 5) {
|
||||
log.error("Usage: cell config actor <name> set <key> <value>")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
key = args[3]
|
||||
var value_str = args[4]
|
||||
path = parse_key(key)
|
||||
value = parse_value(value_str)
|
||||
|
||||
set_nested(config.actors[actor_name], path, value)
|
||||
pkg.save_config(config)
|
||||
log.console("Set actors." + actor_name + "." + key + " = " + format_value(value))
|
||||
break
|
||||
|
||||
default:
|
||||
log.error("Unknown actor command: " + actor_cmd)
|
||||
log.console("Valid commands: list, get, set")
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
log.error("Unknown command: " + command)
|
||||
print_help()
|
||||
log.console("Set actors." + actor_name + "." + key + " = " + format_value(value))
|
||||
} else {
|
||||
log.error("Unknown actor command: " + actor_cmd)
|
||||
log.console("Valid commands: list, get, set")
|
||||
}
|
||||
} else {
|
||||
log.error("Unknown command: " + command)
|
||||
print_help()
|
||||
}
|
||||
|
||||
$stop()
|
||||
$stop()
|
||||
|
||||
9
crypto.c
9
crypto.c
@@ -238,9 +238,10 @@ static const JSCFunctionListEntry js_crypto_funcs[] = {
|
||||
JS_CFUNC_DEF("unlock", 3, js_crypto_unlock),
|
||||
};
|
||||
|
||||
JSValue js_crypto_use(JSContext *js)
|
||||
JSValue js_core_crypto_use(JSContext *js)
|
||||
{
|
||||
JSValue obj = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, obj, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0]));
|
||||
return obj;
|
||||
JS_FRAME(js);
|
||||
JS_ROOT(mod, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, mod.val, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0]));
|
||||
JS_RETURN(mod.val);
|
||||
}
|
||||
|
||||
@@ -21,8 +21,9 @@ static const JSCFunctionListEntry js_debug_funcs[] = {
|
||||
MIST_FUNC_DEF(debug, backtrace_fns,0),
|
||||
};
|
||||
|
||||
JSValue js_debug_use(JSContext *js) {
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js,mod,js_debug_funcs,countof(js_debug_funcs));
|
||||
return mod;
|
||||
JSValue js_core_debug_use(JSContext *js) {
|
||||
JS_FRAME(js);
|
||||
JS_ROOT(mod, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, mod.val, js_debug_funcs, countof(js_debug_funcs));
|
||||
JS_RETURN(mod.val);
|
||||
}
|
||||
|
||||
@@ -20,8 +20,9 @@ static const JSCFunctionListEntry js_js_funcs[] = {
|
||||
MIST_FUNC_DEF(js, fn_info, 1),
|
||||
};
|
||||
|
||||
JSValue js_js_use(JSContext *js) {
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js,mod,js_js_funcs,countof(js_js_funcs));
|
||||
return mod;
|
||||
JSValue js_core_js_use(JSContext *js) {
|
||||
JS_FRAME(js);
|
||||
JS_ROOT(mod, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, mod.val, js_js_funcs, countof(js_js_funcs));
|
||||
JS_RETURN(mod.val);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ pit hello
|
||||
- [**Actors and Modules**](/docs/actors/) — the execution model
|
||||
- [**Requestors**](/docs/requestors/) — asynchronous composition
|
||||
- [**Packages**](/docs/packages/) — code organization and sharing
|
||||
- [**Shop Architecture**](/docs/shop/) — module resolution, compilation, and caching
|
||||
|
||||
## Reference
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ use('json') // core json module
|
||||
use('otherlib/foo') // dependency 'otherlib', file foo.cm
|
||||
```
|
||||
|
||||
Files starting with underscore (`_helper.cm`) are private to the package.
|
||||
Files in the `internal/` directory are private to the package.
|
||||
|
||||
## Example: Simple Actor System
|
||||
|
||||
|
||||
@@ -52,6 +52,11 @@ Where:
|
||||
Examples:
|
||||
- `mypackage/math.c` -> `js_mypackage_math_use`
|
||||
- `gitea.pockle.world/john/lib/render.c` -> `js_gitea_pockle_world_john_lib_render_use`
|
||||
- `mypackage/game.ce` (AOT actor) -> `js_mypackage_game_program`
|
||||
|
||||
Actor files (`.ce`) use the `_program` suffix instead of `_use`.
|
||||
|
||||
**Note:** Having both a `.cm` and `.c` file with the same stem at the same scope is a build error.
|
||||
|
||||
## Required Headers
|
||||
|
||||
@@ -216,34 +221,6 @@ var n = vector.normalize(3, 4) // {x: 0.6, y: 0.8}
|
||||
var d = vector.dot(1, 0, 0, 1) // 0
|
||||
```
|
||||
|
||||
## Combining C and ƿit
|
||||
|
||||
A common pattern is to have a C file provide low-level functions and a `.cm` file provide a higher-level API:
|
||||
|
||||
```c
|
||||
// _vector_native.c
|
||||
// ... raw C functions ...
|
||||
```
|
||||
|
||||
```javascript
|
||||
// vector.cm
|
||||
var native = this // C module passed as 'this'
|
||||
|
||||
var Vector = function(x, y) {
|
||||
return {x: x, y: y}
|
||||
}
|
||||
|
||||
Vector.length = function(v) {
|
||||
return native.length(v.x, v.y)
|
||||
}
|
||||
|
||||
Vector.normalize = function(v) {
|
||||
return native.normalize(v.x, v.y)
|
||||
}
|
||||
|
||||
return Vector
|
||||
```
|
||||
|
||||
## Build Process
|
||||
|
||||
C files are automatically compiled when you run:
|
||||
@@ -253,7 +230,7 @@ pit build
|
||||
pit update
|
||||
```
|
||||
|
||||
The resulting dynamic library is placed in `~/.pit/lib/`.
|
||||
Each C file is compiled into a per-file dynamic library at `~/.pit/lib/<pkg>/<stem>.dylib`.
|
||||
|
||||
## Platform-Specific Code
|
||||
|
||||
|
||||
120
docs/cli.md
120
docs/cli.md
@@ -70,10 +70,11 @@ pit ls <package> # list files in specified package
|
||||
|
||||
### pit build
|
||||
|
||||
Build the current package.
|
||||
Build the current package. Compiles C files into per-file dynamic libraries and installs them to `~/.pit/lib/<pkg>/<stem>.dylib`.
|
||||
|
||||
```bash
|
||||
pit build
|
||||
pit build # build current package
|
||||
pit build <package> # build specific package
|
||||
```
|
||||
|
||||
### pit test
|
||||
@@ -122,6 +123,103 @@ Clean build artifacts.
|
||||
pit clean
|
||||
```
|
||||
|
||||
### pit add
|
||||
|
||||
Add a dependency to the current package. Updates `cell.toml` and installs the package to the shop.
|
||||
|
||||
```bash
|
||||
pit add gitea.pockle.world/john/prosperon # default alias
|
||||
pit add gitea.pockle.world/john/prosperon myalias # custom alias
|
||||
```
|
||||
|
||||
### pit clone
|
||||
|
||||
Clone a package to a local path and link it for development.
|
||||
|
||||
```bash
|
||||
pit clone gitea.pockle.world/john/prosperon ./prosperon
|
||||
```
|
||||
|
||||
### pit unlink
|
||||
|
||||
Remove a link created by `pit link` or `pit clone` and restore the original package.
|
||||
|
||||
```bash
|
||||
pit unlink gitea.pockle.world/john/prosperon
|
||||
```
|
||||
|
||||
### pit search
|
||||
|
||||
Search for packages, actors, or modules matching a query.
|
||||
|
||||
```bash
|
||||
pit search math
|
||||
```
|
||||
|
||||
### pit why
|
||||
|
||||
Show which installed packages depend on a given package (reverse dependency lookup).
|
||||
|
||||
```bash
|
||||
pit why gitea.pockle.world/john/prosperon
|
||||
```
|
||||
|
||||
### pit resolve
|
||||
|
||||
Print the fully resolved dependency closure for a package.
|
||||
|
||||
```bash
|
||||
pit resolve # resolve current package
|
||||
pit resolve <package> # resolve specific package
|
||||
pit resolve --locked # show lock state without links
|
||||
```
|
||||
|
||||
### pit graph
|
||||
|
||||
Emit a dependency graph.
|
||||
|
||||
```bash
|
||||
pit graph # tree of current package
|
||||
pit graph --format dot # graphviz dot output
|
||||
pit graph --format json # json output
|
||||
pit graph --world # graph all installed packages
|
||||
pit graph --locked # show lock view without links
|
||||
```
|
||||
|
||||
### pit verify
|
||||
|
||||
Verify integrity and consistency of packages, links, and builds.
|
||||
|
||||
```bash
|
||||
pit verify # verify current package
|
||||
pit verify shop # verify entire shop
|
||||
pit verify --deep # traverse full dependency closure
|
||||
pit verify --target <triple>
|
||||
```
|
||||
|
||||
### pit pack
|
||||
|
||||
Build a statically linked binary from a package and all its dependencies.
|
||||
|
||||
```bash
|
||||
pit pack <package> # build static binary (output: app)
|
||||
pit pack <package> -o myapp # specify output name
|
||||
pit pack <package> -t <triple> # cross-compile for target
|
||||
```
|
||||
|
||||
### pit config
|
||||
|
||||
Manage system and actor configuration values in `cell.toml`.
|
||||
|
||||
```bash
|
||||
pit config list # list all config
|
||||
pit config get system.ar_timer # get a value
|
||||
pit config set system.ar_timer 5.0 # set a value
|
||||
pit config actor <name> list # list actor config
|
||||
pit config actor <name> get <key> # get actor config
|
||||
pit config actor <name> set <key> <val> # set actor config
|
||||
```
|
||||
|
||||
### pit help
|
||||
|
||||
Display help information.
|
||||
@@ -131,16 +229,6 @@ pit help
|
||||
pit help <command>
|
||||
```
|
||||
|
||||
## Running Scripts
|
||||
|
||||
Any `.ce` file in the ƿit core can be run as a command:
|
||||
|
||||
```bash
|
||||
pit version # runs version.ce
|
||||
pit build # runs build.ce
|
||||
pit test # runs test.ce
|
||||
```
|
||||
|
||||
## Package Locators
|
||||
|
||||
Packages are identified by locators:
|
||||
@@ -159,9 +247,11 @@ pit install /Users/john/work/mylib
|
||||
|
||||
```
|
||||
~/.pit/
|
||||
├── packages/ # installed packages
|
||||
├── lib/ # compiled dynamic libraries
|
||||
├── build/ # build cache
|
||||
├── packages/ # installed package sources
|
||||
├── lib/ # installed per-file dylibs and mach (persistent)
|
||||
│ ├── core/ # core package: .dylib and .mach files
|
||||
│ └── <pkg>/ # per-package subdirectories
|
||||
├── build/ # ephemeral build cache (safe to delete)
|
||||
├── cache/ # downloaded archives
|
||||
├── lock.toml # installed package versions
|
||||
└── link.toml # local development links
|
||||
|
||||
@@ -19,7 +19,8 @@ mypackage/
|
||||
├── helper/
|
||||
│ └── math.cm # nested module
|
||||
├── render.c # C extension
|
||||
└── _internal.cm # private module (underscore prefix)
|
||||
└── internal/
|
||||
└── helpers.cm # private module (internal/ only)
|
||||
```
|
||||
|
||||
## pit.toml
|
||||
@@ -60,12 +61,12 @@ use('json') // core module
|
||||
|
||||
### Private Modules
|
||||
|
||||
Files starting with underscore are private:
|
||||
Files in the `internal/` directory are private to their package:
|
||||
|
||||
```javascript
|
||||
// _internal.cm is only accessible within the same package
|
||||
use('internal') // OK from same package
|
||||
use('myapp/internal') // Error from other packages
|
||||
// internal/helpers.cm is only accessible within the same package
|
||||
use('internal/helpers') // OK from same package
|
||||
use('myapp/internal/helpers') // Error from other packages
|
||||
```
|
||||
|
||||
## Package Locators
|
||||
@@ -105,8 +106,11 @@ Local packages are symlinked into the shop, making development seamless.
|
||||
│ └── work/
|
||||
│ └── mylib -> /Users/john/work/mylib
|
||||
├── lib/
|
||||
│ ├── local.dylib
|
||||
│ └── gitea_pockle_world_john_prosperon.dylib
|
||||
│ ├── core/
|
||||
│ │ ├── fd.dylib
|
||||
│ │ └── time.mach
|
||||
│ └── gitea_pockle_world_john_prosperon/
|
||||
│ └── sprite.dylib
|
||||
├── build/
|
||||
│ └── <content-addressed cache>
|
||||
├── cache/
|
||||
@@ -171,16 +175,16 @@ pit link delete gitea.pockle.world/john/prosperon
|
||||
|
||||
## C Extensions
|
||||
|
||||
C files in a package are compiled into a dynamic library:
|
||||
C files in a package are compiled into per-file dynamic libraries:
|
||||
|
||||
```
|
||||
mypackage/
|
||||
├── pit.toml
|
||||
├── render.c # compiled to mypackage.dylib
|
||||
└── render.cm # optional ƿit wrapper
|
||||
├── render.c # compiled to lib/mypackage/render.dylib
|
||||
└── physics.c # compiled to lib/mypackage/physics.dylib
|
||||
```
|
||||
|
||||
The library is named after the package and placed in `~/.pit/lib/`.
|
||||
Each `.c` file gets its own `.dylib` in `~/.pit/lib/<pkg>/`. A `.c` file and `.cm` file with the same stem at the same scope is a build error — use distinct names.
|
||||
|
||||
See [Writing C Modules](/docs/c-modules/) for details.
|
||||
|
||||
|
||||
160
docs/shop.md
Normal file
160
docs/shop.md
Normal file
@@ -0,0 +1,160 @@
|
||||
---
|
||||
title: "Shop Architecture"
|
||||
description: "How the shop resolves, compiles, caches, and loads modules"
|
||||
weight: 35
|
||||
type: "docs"
|
||||
---
|
||||
|
||||
The shop is the module resolution and loading engine behind `use()`. It handles finding modules, compiling them, caching the results, and loading C extensions. The shop lives in `internal/shop.cm`.
|
||||
|
||||
## Startup Pipeline
|
||||
|
||||
When `pit` runs a program, three layers bootstrap in sequence:
|
||||
|
||||
```
|
||||
bootstrap.cm → engine.cm → shop.cm → user program
|
||||
```
|
||||
|
||||
**bootstrap.cm** loads the compiler toolchain (tokenize, parse, fold, mcode, streamline) from pre-compiled bytecode. It defines `analyze()` (source to AST) and `compile_to_blob()` (AST to binary blob). It then loads engine.cm.
|
||||
|
||||
**engine.cm** creates the actor runtime (`$_`), defines `use_core()` for loading core modules, and populates the environment that shop receives. It then loads shop.cm via `use_core('internal/shop')`.
|
||||
|
||||
**shop.cm** receives its dependencies through the module environment — `analyze`, `run_ast_fn`, `use_cache`, `shop_path`, `runtime_env`, `content_hash`, `cache_path`, and others. It defines `Shop.use()`, which is the function behind every `use()` call in user code.
|
||||
|
||||
## Module Resolution
|
||||
|
||||
When `use('path')` is called from a package context, the shop resolves the module through a multi-layer search. Both the `.cm` script file and C symbol are resolved independently, and the one with the narrowest scope wins.
|
||||
|
||||
### Resolution Order
|
||||
|
||||
For a call like `use('sprite')` from package `myapp`:
|
||||
|
||||
1. **Own package** — `~/.pit/packages/myapp/sprite.cm` and C symbol `js_myapp_sprite_use`
|
||||
2. **Aliased dependencies** — if `myapp/pit.toml` has `renderer = "gitea.pockle.world/john/renderer"`, checks `renderer/sprite.cm` and its C symbols
|
||||
3. **Core** — built-in core modules and internal C symbols
|
||||
|
||||
For calls without a package context (from core modules), only core is searched.
|
||||
|
||||
### Private Modules
|
||||
|
||||
Paths starting with `internal/` are private to their package:
|
||||
|
||||
```javascript
|
||||
use('internal/helpers') // OK from within the same package
|
||||
// Cannot be accessed from other packages
|
||||
```
|
||||
|
||||
### Explicit Package Imports
|
||||
|
||||
Paths containing a dot in the first component are treated as explicit package references:
|
||||
|
||||
```javascript
|
||||
use('gitea.pockle.world/john/renderer/sprite')
|
||||
// Resolves directly to the renderer package's sprite.cm
|
||||
```
|
||||
|
||||
## Compilation and Caching
|
||||
|
||||
Every module goes through a content-addressed caching pipeline. The cache key is the BLAKE2 hash of the source content, so changing the source automatically invalidates the cache.
|
||||
|
||||
### Cache Hierarchy
|
||||
|
||||
When loading a module, the shop checks (in order):
|
||||
|
||||
1. **In-memory cache** — `use_cache[key]`, checked first on every `use()` call
|
||||
2. **Installed dylib** — per-file `.dylib` in `~/.pit/lib/<pkg>/<stem>.dylib`
|
||||
3. **Internal symbols** — statically linked into the `pit` binary (fat builds)
|
||||
4. **Installed mach** — pre-compiled bytecode in `~/.pit/lib/<pkg>/<stem>.mach`
|
||||
5. **Cached bytecode** — content-addressed in `~/.pit/build/<hash>` (no extension)
|
||||
6. **Cached .mcode IR** — JSON IR in `~/.pit/build/<hash>.mcode`
|
||||
7. **Adjacent .mach/.mcode** — files alongside the source (e.g., `sprite.mach`)
|
||||
8. **Source compilation** — full pipeline: analyze, mcode, streamline, serialize
|
||||
|
||||
When both a `.dylib` and `.mach` exist for the same module in `lib/`, the dylib is selected. Dylib resolution also wins over internal symbols, so a dylib in `lib/` can hot-patch a fat binary. Delete the dylib to fall back to mach or static.
|
||||
|
||||
Results from steps 6-8 are cached back to the content-addressed store for future loads.
|
||||
|
||||
### Content-Addressed Store
|
||||
|
||||
The build cache at `~/.pit/build/` stores ephemeral artifacts named by the BLAKE2 hash of their inputs:
|
||||
|
||||
```
|
||||
~/.pit/build/
|
||||
├── a1b2c3d4... # cached bytecode blob (no extension)
|
||||
├── c9d0e1f2...mcode # cached JSON IR
|
||||
└── f3a4b5c6... # compiled dylib (checked before copying to lib/)
|
||||
```
|
||||
|
||||
This scheme provides automatic cache invalidation: when source changes, its hash changes, and the old cache entry is simply never looked up again. When building a dylib, the build cache is checked first — if a matching hash exists, it is copied to `lib/` without recompiling.
|
||||
|
||||
### Core Module Caching
|
||||
|
||||
Core modules loaded via `use_core()` in engine.cm follow the same pattern. On first startup after a fresh install, core modules are compiled from `.cm.mcode` JSON IR and cached as `.mach` blobs. Subsequent startups load from cache, skipping the JSON parse and compile steps entirely.
|
||||
|
||||
User scripts (`.ce` files) are also cached. The first run compiles and caches; subsequent runs with unchanged source load from cache.
|
||||
|
||||
## C Extension Resolution
|
||||
|
||||
C extensions are resolved alongside script modules. A C module is identified by a symbol name derived from the package and file name:
|
||||
|
||||
```
|
||||
package: gitea.pockle.world/john/prosperon
|
||||
file: sprite.c
|
||||
symbol: js_gitea_pockle_world_john_prosperon_sprite_use
|
||||
```
|
||||
|
||||
### C Resolution Sources
|
||||
|
||||
1. **Installed dylibs** — per-file dylibs in `~/.pit/lib/<pkg>/<stem>.dylib` (deterministic paths, no manifests)
|
||||
2. **Internal symbols** — statically linked into the `pit` binary (fat builds)
|
||||
|
||||
Dylibs are checked first at each resolution scope, so an installed dylib always wins over a statically linked symbol. This enables hot-patching fat binaries by placing a dylib in `lib/`.
|
||||
|
||||
### Name Collisions
|
||||
|
||||
Having both a `.cm` script and a `.c` file with the same stem at the same scope is a **build error**. For example, `render.cm` and `render.c` in the same directory will fail. Use distinct names — e.g., `render.c` for the C implementation and `render_utils.cm` for the script wrapper.
|
||||
|
||||
## Environment Injection
|
||||
|
||||
When a module is loaded, the shop builds an `env` object that becomes the module's set of free variables. This includes:
|
||||
|
||||
- **Runtime functions** — `logical`, `some`, `every`, `starts_with`, `ends_with`, `is_actor`, `log`, `send`, `fallback`, `parallel`, `race`, `sequence`
|
||||
- **Capability injections** — actor intrinsics like `$self`, `$delay`, `$start`, `$receiver`, `$fd`, etc.
|
||||
- **`use` function** — scoped to the module's package context
|
||||
|
||||
The set of injected capabilities is controlled by `script_inject_for()`, which can be tuned per package or file.
|
||||
|
||||
## Shop Directory Layout
|
||||
|
||||
```
|
||||
~/.pit/
|
||||
├── packages/ # installed packages (directories and symlinks)
|
||||
│ └── core -> ... # symlink to the ƿit core
|
||||
├── lib/ # INSTALLED per-file artifacts (persistent, human-readable)
|
||||
│ ├── core/
|
||||
│ │ ├── fd.dylib
|
||||
│ │ ├── time.mach
|
||||
│ │ ├── time.dylib
|
||||
│ │ └── internal/
|
||||
│ │ └── os.dylib
|
||||
│ └── gitea_pockle_world_john_prosperon/
|
||||
│ ├── sprite.dylib
|
||||
│ └── render.dylib
|
||||
├── build/ # EPHEMERAL cache (safe to delete anytime)
|
||||
│ ├── <hash> # cached bytecode or dylib blobs (no extension)
|
||||
│ └── <hash>.mcode # cached JSON IR
|
||||
├── cache/ # downloaded package zip archives
|
||||
├── lock.toml # installed package versions and commit hashes
|
||||
└── link.toml # local development link overrides
|
||||
```
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Role |
|
||||
|------|------|
|
||||
| `internal/bootstrap.cm` | Loads compiler, defines `analyze()` and `compile_to_blob()` |
|
||||
| `internal/engine.cm` | Actor runtime, `use_core()`, environment setup |
|
||||
| `internal/shop.cm` | Module resolution, compilation, caching, C extension loading |
|
||||
| `internal/os.c` | OS intrinsics: dylib ops, internal symbol lookup, embedded modules |
|
||||
| `package.cm` | Package directory detection, alias resolution, file listing |
|
||||
| `link.cm` | Development link management (link.toml read/write) |
|
||||
@@ -13,6 +13,34 @@ Source → Tokenize → Parse → Fold → Mcode → Streamline → Machine
|
||||
|
||||
Mcode is produced by `mcode.cm`, optimized by `streamline.cm`, then either serialized to 32-bit bytecode for the Mach VM (`mach.c`), or lowered to QBE/LLVM IL for native compilation (`qbe_emit.cm`). See [Compilation Pipeline](pipeline.md) for the full overview.
|
||||
|
||||
## Module Structure
|
||||
|
||||
An `.mcode` file is a JSON object representing a compiled module:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | string | Module name (typically the source filename) |
|
||||
| `filename` | string | Source filename |
|
||||
| `data` | object | Constant pool — string and number literals used by instructions |
|
||||
| `main` | function | The top-level function (module body) |
|
||||
| `functions` | array | Nested function definitions (referenced by `function dest, id`) |
|
||||
|
||||
### Function Record
|
||||
|
||||
Each function (both `main` and entries in `functions`) has:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `name` | string | Function name (`"<anonymous>"` for lambdas) |
|
||||
| `filename` | string | Source filename |
|
||||
| `nr_args` | integer | Number of parameters |
|
||||
| `nr_slots` | integer | Total register slots needed (args + locals + temporaries) |
|
||||
| `nr_close_slots` | integer | Number of closure slots captured from parent scope |
|
||||
| `disruption_pc` | integer | Instruction index of the disruption handler (0 if none) |
|
||||
| `instructions` | array | Instruction arrays and label strings |
|
||||
|
||||
Slot 0 is reserved. Slots 1 through `nr_args` hold parameters. Remaining slots up to `nr_slots - 1` are locals and temporaries.
|
||||
|
||||
## Instruction Format
|
||||
|
||||
Each instruction is a JSON array. The first element is the instruction name (string), followed by operands. The last two elements are line and column numbers for source mapping:
|
||||
|
||||
4
fd.cm
4
fd.cm
@@ -1,4 +1,4 @@
|
||||
var fd = native
|
||||
var fd = use('internal/fd')
|
||||
var wildstar = use('wildstar')
|
||||
|
||||
function last_pos(str, sep) {
|
||||
@@ -97,4 +97,4 @@ fd.globfs = function(globs, dir) {
|
||||
return results
|
||||
}
|
||||
|
||||
return fd
|
||||
return fd
|
||||
|
||||
3
fetch.ce
3
fetch.ce
@@ -12,8 +12,9 @@ var shop = use('internal/shop')
|
||||
|
||||
// Parse arguments
|
||||
var target_pkg = null
|
||||
var i = 0
|
||||
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
for (i = 0; i < length(args); i++) {
|
||||
if (args[i] == '--help' || args[i] == '-h') {
|
||||
log.console("Usage: cell fetch [package]")
|
||||
log.console("Fetch package zips from remote sources.")
|
||||
|
||||
9
fit.c
9
fit.c
@@ -248,9 +248,10 @@ static const JSCFunctionListEntry js_fit_funcs[] = {
|
||||
MIST_FUNC_DEF(fit, zeros, 1),
|
||||
};
|
||||
|
||||
JSValue js_fit_use(JSContext *js)
|
||||
JSValue js_core_fit_use(JSContext *js)
|
||||
{
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_fit_funcs, countof(js_fit_funcs));
|
||||
return mod;
|
||||
JS_FRAME(js);
|
||||
JS_ROOT(mod, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, mod.val, js_fit_funcs, countof(js_fit_funcs));
|
||||
JS_RETURN(mod.val);
|
||||
}
|
||||
112
fix_pipeline.md
Normal file
112
fix_pipeline.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Fix Compilation Pipeline Bootstrap
|
||||
|
||||
## Problem
|
||||
|
||||
After merging `fix_gc` into `pitweb`, the compilation pipeline `.cm` source files
|
||||
(tokenize.cm, parse.cm, fold.cm, mcode.cm, streamline.cm) cannot bootstrap themselves.
|
||||
|
||||
The old pitweb pipeline mcode compiles the merged `.cm` source without errors, but the
|
||||
resulting new pipeline mcode is **semantically broken** — it can't even compile
|
||||
`var x = 42; print(x)`.
|
||||
|
||||
Both branches worked independently. The merge introduced no syntax errors, but the old
|
||||
pitweb compiler produces incorrect bytecode from the merged pipeline source. This is a
|
||||
classic bootstrapping problem: the new pipeline needs a compatible compiler to build
|
||||
itself, but the only available compiler (old pitweb) miscompiles it.
|
||||
|
||||
## Current State
|
||||
|
||||
- `boot/tokenize.cm.mcode` through `boot/streamline.cm.mcode` contain the **old pitweb**
|
||||
pipeline mcode (pre-merge). These pass 641/641 vm_suite tests.
|
||||
- All other boot mcode files (engine, bootstrap, seed_bootstrap, plus core modules like
|
||||
fd, time, toml, etc.) are compiled from the merged source and work correctly.
|
||||
- The merged pipeline `.cm` source has changes from fix_gc that are **not active** — the
|
||||
runtime uses the old pitweb pipeline mcode.
|
||||
|
||||
**The old pitweb pipeline is NOT fully working.** While it passes the test suite, it
|
||||
miscompiles nested function declarations. This breaks:
|
||||
|
||||
- `toml.encode()` — the encoder uses nested `function` declarations inside `encode_toml`
|
||||
- `Shop.save_lock()` — calls `toml.encode()`, so any lock.toml mutation fails
|
||||
- Any other `.cm` module that uses nested named function declarations
|
||||
|
||||
This means the **ID-based package symbol naming** (Phase 2 in the plan) is blocked: it
|
||||
needs `save_lock()` to persist package IDs to lock.toml.
|
||||
|
||||
The shop.cm changes for ID-based naming are already written and correct — they just need
|
||||
a working pipeline underneath. Once the pipeline is fixed, the ID system will work.
|
||||
|
||||
## What Changed in the Pipeline
|
||||
|
||||
The fix_gc merge brought these changes to the pipeline `.cm` files:
|
||||
|
||||
- **mcode.cm**: Type-guarded arithmetic (`emit_add_decomposed` now generates `is_text`/`is_num`
|
||||
checks instead of letting the VM dispatch), `emit_numeric_binop` for subtract/multiply/etc.,
|
||||
`sensory_ops` lookup table, array/record literal count args (`["array", dest, count]`
|
||||
instead of `["array", dest, 0]`)
|
||||
- **fold.cm**: Lookup tables (`binary_ops`, `unary_ops`, `assign_ops`, etc.) replacing
|
||||
if-chains, combined `"array"` and `"text literal"` handling
|
||||
- **tokenize.cm**: ~500 lines of changes
|
||||
- **streamline.cm**: ~700 lines of changes
|
||||
- **parse.cm**: ~40 lines of changes (minor)
|
||||
|
||||
## Regen Flags
|
||||
|
||||
`regen.ce` now has two modes:
|
||||
|
||||
```
|
||||
./cell --dev --seed regen # default: skip pipeline files
|
||||
./cell --dev --seed regen --all # include pipeline files (tokenize/parse/fold/mcode/streamline)
|
||||
```
|
||||
|
||||
The default mode is safe — it regenerates everything except the 5 pipeline files,
|
||||
preserving the working old pitweb pipeline mcode.
|
||||
|
||||
## How to Fix
|
||||
|
||||
The goal is to get the merged pipeline `.cm` source to produce working mcode when
|
||||
compiled by the current (old pitweb) pipeline. The process:
|
||||
|
||||
1. Start from the current repo state (old pitweb pipeline mcode in boot/)
|
||||
2. Edit one or more pipeline `.cm` files to fix the issue
|
||||
3. Regen with `--all` to recompile everything including pipeline:
|
||||
```
|
||||
./cell --dev --seed regen --all
|
||||
```
|
||||
4. Test the new pipeline with a simple sanity check:
|
||||
```
|
||||
rm -rf .cell/build/*
|
||||
echo 'var x = 42; print(x)' > /tmp/test.ce
|
||||
./cell --dev --seed /tmp/test
|
||||
```
|
||||
5. If that works, run the full test suite:
|
||||
```
|
||||
rm -rf .cell/build/*
|
||||
./cell --dev vm_suite
|
||||
```
|
||||
6. If tests pass, regen again (the new pipeline compiles itself):
|
||||
```
|
||||
./cell --dev --seed regen --all
|
||||
```
|
||||
7. Repeat steps 4-6 until **idempotent** — two consecutive `regen --all` runs produce
|
||||
identical boot mcode and all tests pass.
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
- The old pitweb pipeline mcode is always available via:
|
||||
```
|
||||
git checkout HEAD^1 -- boot/tokenize.cm.mcode boot/parse.cm.mcode \
|
||||
boot/fold.cm.mcode boot/mcode.cm.mcode boot/streamline.cm.mcode
|
||||
```
|
||||
- Use `--seed` mode for testing compilation — it bypasses the engine entirely and
|
||||
loads the pipeline directly from boot mcode.
|
||||
- The failure mode is silent: the old compiler compiles the new source without errors
|
||||
but produces wrong bytecode.
|
||||
- Known broken patterns with the old pitweb pipeline:
|
||||
- `var x = 42; print(x)` fails when compiled by the regenned pipeline mcode
|
||||
- Nested named function declarations (`function foo() {}` inside another function)
|
||||
produce "not a function" errors — this breaks `toml.encode()`
|
||||
- Test with: `echo 'var toml = use("toml"); print(toml.encode({a: 1}))' > /tmp/t.ce && ./cell --dev /tmp/t.ce`
|
||||
- The most likely culprits are the mcode.cm changes (type-guarded arithmetic, array/record
|
||||
count args) since these change the bytecode format. The fold.cm changes (lookup tables)
|
||||
are more likely safe refactors.
|
||||
59
fold.cm
59
fold.cm
@@ -5,6 +5,34 @@ var fold = function(ast) {
|
||||
var scopes = ast.scopes
|
||||
var nr_scopes = length(scopes)
|
||||
|
||||
var type_tag_map = {
|
||||
array: "array", record: "record", text: "text",
|
||||
number: "number", blob: "blob"
|
||||
}
|
||||
|
||||
var binary_ops = {
|
||||
"+": true, "-": true, "*": true, "/": true, "%": true,
|
||||
"**": true, "==": true, "!=": true, "<": true, ">": true,
|
||||
"<=": true, ">=": true, "&": true, "|": true, "^": true,
|
||||
"<<": true, ">>": true, ">>>": true, "&&": true, "||": true,
|
||||
",": true, in: true
|
||||
}
|
||||
var unary_ops = {
|
||||
"!": true, "~": true, "-unary": true, "+unary": true, delete: true
|
||||
}
|
||||
var assign_ops = {
|
||||
assign: true, "+=": true, "-=": true, "*=": true,
|
||||
"/=": true, "%=": true, "<<=": true, ">>=": true,
|
||||
">>>=": true, "&=": true, "^=": true, "|=": true,
|
||||
"**=": true, "&&=": true, "||=": true
|
||||
}
|
||||
var arith_ops = {
|
||||
"+": true, "-": true, "*": true, "/": true, "%": true, "**": true
|
||||
}
|
||||
var comparison_ops = {
|
||||
"==": true, "!=": true, "<": true, ">": true, "<=": true, ">=": true
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helpers
|
||||
// ============================================================
|
||||
@@ -194,11 +222,7 @@ var fold = function(ast) {
|
||||
if (rhs_target != null && rhs_target.intrinsic == true) {
|
||||
sv = scope_var(fn_nr, name)
|
||||
if (sv != null && sv.type_tag == null) {
|
||||
if (rhs_target.name == "array") sv.type_tag = "array"
|
||||
else if (rhs_target.name == "record") sv.type_tag = "record"
|
||||
else if (rhs_target.name == "text") sv.type_tag = "text"
|
||||
else if (rhs_target.name == "number") sv.type_tag = "number"
|
||||
else if (rhs_target.name == "blob") sv.type_tag = "blob"
|
||||
if (type_tag_map[rhs_target.name] != null) sv.type_tag = type_tag_map[rhs_target.name]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,17 +381,13 @@ var fold = function(ast) {
|
||||
var arg = null
|
||||
|
||||
// Recurse into children first (bottom-up)
|
||||
if (k == "+" || k == "-" || k == "*" || k == "/" || k == "%" ||
|
||||
k == "**" || k == "==" || k == "!=" || k == "<" || k == ">" ||
|
||||
k == "<=" || k == ">=" || k == "&" || k == "|" || k == "^" ||
|
||||
k == "<<" || k == ">>" || k == ">>>" || k == "&&" || k == "||" ||
|
||||
k == "," || k == "in") {
|
||||
if (binary_ops[k] == true) {
|
||||
expr.left = fold_expr(expr.left, fn_nr)
|
||||
expr.right = fold_expr(expr.right, fn_nr)
|
||||
} else if (k == "." || k == "[") {
|
||||
expr.left = fold_expr(expr.left, fn_nr)
|
||||
if (k == "[" && expr.right != null) expr.right = fold_expr(expr.right, fn_nr)
|
||||
} else if (k == "!" || k == "~" || k == "-unary" || k == "+unary" || k == "delete") {
|
||||
} else if (unary_ops[k] == true) {
|
||||
expr.expression = fold_expr(expr.expression, fn_nr)
|
||||
} else if (k == "++" || k == "--") {
|
||||
return expr
|
||||
@@ -382,7 +402,7 @@ var fold = function(ast) {
|
||||
expr.list[i] = fold_expr(expr.list[i], fn_nr)
|
||||
i = i + 1
|
||||
}
|
||||
} else if (k == "array") {
|
||||
} else if (k == "array" || k == "text literal") {
|
||||
i = 0
|
||||
while (i < length(expr.list)) {
|
||||
expr.list[i] = fold_expr(expr.list[i], fn_nr)
|
||||
@@ -394,19 +414,10 @@ var fold = function(ast) {
|
||||
expr.list[i].right = fold_expr(expr.list[i].right, fn_nr)
|
||||
i = i + 1
|
||||
}
|
||||
} else if (k == "text literal") {
|
||||
i = 0
|
||||
while (i < length(expr.list)) {
|
||||
expr.list[i] = fold_expr(expr.list[i], fn_nr)
|
||||
i = i + 1
|
||||
}
|
||||
} else if (k == "function") {
|
||||
fold_fn(expr)
|
||||
return expr
|
||||
} else if (k == "assign" || k == "+=" || k == "-=" || k == "*=" ||
|
||||
k == "/=" || k == "%=" || k == "<<=" || k == ">>=" ||
|
||||
k == ">>>=" || k == "&=" || k == "^=" || k == "|=" ||
|
||||
k == "**=" || k == "&&=" || k == "||=") {
|
||||
} else if (assign_ops[k] == true) {
|
||||
expr.right = fold_expr(expr.right, fn_nr)
|
||||
return expr
|
||||
}
|
||||
@@ -428,7 +439,7 @@ var fold = function(ast) {
|
||||
}
|
||||
|
||||
// Binary constant folding
|
||||
if (k == "+" || k == "-" || k == "*" || k == "/" || k == "%" || k == "**") {
|
||||
if (arith_ops[k] == true) {
|
||||
left = expr.left
|
||||
right = expr.right
|
||||
if (left != null && right != null && left.kind == "number" && right.kind == "number") {
|
||||
@@ -460,7 +471,7 @@ var fold = function(ast) {
|
||||
}
|
||||
|
||||
// Comparison folding
|
||||
if (k == "==" || k == "!=" || k == "<" || k == ">" || k == "<=" || k == ">=") {
|
||||
if (comparison_ops[k] == true) {
|
||||
left = expr.left
|
||||
right = expr.right
|
||||
if (left != null && right != null) {
|
||||
|
||||
39
graph.ce
39
graph.ce
@@ -22,8 +22,10 @@ var target_locator = null
|
||||
var format = 'tree'
|
||||
var show_locked = false
|
||||
var show_world = false
|
||||
var i = 0
|
||||
var resolved = null
|
||||
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
for (i = 0; i < length(args); i++) {
|
||||
if (args[i] == '--format' || args[i] == '-f') {
|
||||
if (i + 1 < length(args)) {
|
||||
format = args[++i]
|
||||
@@ -91,7 +93,7 @@ function gather_graph(locator, visited) {
|
||||
|
||||
add_node(locator)
|
||||
|
||||
try {
|
||||
var _gather = function() {
|
||||
var deps = pkg.dependencies(locator)
|
||||
if (deps) {
|
||||
arrfor(array(deps), function(alias) {
|
||||
@@ -101,17 +103,19 @@ function gather_graph(locator, visited) {
|
||||
gather_graph(dep_locator, visited)
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
} disruption {
|
||||
// Package might not have dependencies
|
||||
}
|
||||
_gather()
|
||||
}
|
||||
|
||||
// Gather graph from roots
|
||||
var roots = []
|
||||
|
||||
var packages = null
|
||||
if (show_world) {
|
||||
// Use all packages in shop as roots
|
||||
var packages = shop.list_packages()
|
||||
packages = shop.list_packages()
|
||||
arrfor(packages, function(p) {
|
||||
if (p != 'core') {
|
||||
push(roots, p)
|
||||
@@ -125,7 +129,7 @@ if (show_world) {
|
||||
|
||||
// Resolve local paths
|
||||
if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) {
|
||||
var resolved = fd.realpath(target_locator)
|
||||
resolved = fd.realpath(target_locator)
|
||||
if (resolved) {
|
||||
target_locator = resolved
|
||||
}
|
||||
@@ -141,14 +145,19 @@ arrfor(roots, function(root) {
|
||||
// Output based on format
|
||||
if (format == 'tree') {
|
||||
function print_tree(locator, prefix, is_last, visited) {
|
||||
var node = null
|
||||
var suffix = null
|
||||
var children = null
|
||||
var j = 0
|
||||
var child_prefix = null
|
||||
if (visited[locator]) {
|
||||
log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + " (circular)")
|
||||
return
|
||||
}
|
||||
visited[locator] = true
|
||||
|
||||
var node = nodes[locator]
|
||||
var suffix = ""
|
||||
node = nodes[locator]
|
||||
suffix = ""
|
||||
if (node.linked) suffix += " -> " + node.effective
|
||||
if (node.commit) suffix += " @" + node.commit
|
||||
if (node.local) suffix += " (local)"
|
||||
@@ -156,30 +165,32 @@ if (format == 'tree') {
|
||||
log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + suffix)
|
||||
|
||||
// Get children
|
||||
var children = []
|
||||
children = []
|
||||
arrfor(edges, function(e) {
|
||||
if (e.from == locator) {
|
||||
push(children, e)
|
||||
}
|
||||
})
|
||||
|
||||
for (var i = 0; i < length(children); i++) {
|
||||
var child_prefix = prefix + (is_last ? " " : "| ")
|
||||
print_tree(children[i].to, child_prefix, i == length(children) - 1, visited)
|
||||
for (j = 0; j < length(children); j++) {
|
||||
child_prefix = prefix + (is_last ? " " : "| ")
|
||||
print_tree(children[j].to, child_prefix, j == length(children) - 1, visited)
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < length(roots); i++) {
|
||||
var children = null
|
||||
var j = 0
|
||||
for (i = 0; i < length(roots); i++) {
|
||||
log.console(roots[i])
|
||||
|
||||
var children = []
|
||||
children = []
|
||||
arrfor(edges, function(e) {
|
||||
if (e.from == roots[i]) {
|
||||
push(children, e)
|
||||
}
|
||||
})
|
||||
|
||||
for (var j = 0; j < length(children); j++) {
|
||||
for (j = 0; j < length(children); j++) {
|
||||
print_tree(children[j].to, "", j == length(children) - 1, {})
|
||||
}
|
||||
|
||||
|
||||
16
help.ce
16
help.ce
@@ -3,27 +3,29 @@
|
||||
var fd = use('fd')
|
||||
|
||||
var command = length(args) > 0 ? args[0] : null
|
||||
var man_file = null
|
||||
var stat = null
|
||||
var content = null
|
||||
|
||||
// Display specific command help
|
||||
if (command) {
|
||||
var man_file = 'scripts/man/' + command + '.man'
|
||||
var stat = fd.stat(man_file);
|
||||
man_file = 'scripts/man/' + command + '.man'
|
||||
stat = fd.stat(man_file)
|
||||
if (stat && stat.isFile) {
|
||||
var content = text(fd.slurp(man_file))
|
||||
content = text(fd.slurp(man_file))
|
||||
log.console(content)
|
||||
} else {
|
||||
log.error("No help available for command: " + command)
|
||||
log.console("Run 'cell help' to see available commands.")
|
||||
}
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
// Display general help
|
||||
var cell_man = 'scripts/man/cell.man'
|
||||
var stat = fd.stat(cell_man);
|
||||
man_file = 'scripts/man/cell.man'
|
||||
stat = fd.stat(man_file)
|
||||
if (stat && stat.isFile) {
|
||||
var content = text(fd.slurp(cell_man))
|
||||
content = text(fd.slurp(man_file))
|
||||
log.console(content)
|
||||
} else {
|
||||
// Fallback if man file doesn't exist
|
||||
|
||||
27
install.ce
27
install.ce
@@ -28,8 +28,10 @@ var locator = null
|
||||
var target_triple = null
|
||||
var refresh = false
|
||||
var dry_run = false
|
||||
var i = 0
|
||||
var resolved = null
|
||||
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
for (i = 0; i < length(args); i++) {
|
||||
if (args[i] == '--target' || args[i] == '-t') {
|
||||
if (i + 1 < length(args)) {
|
||||
target_triple = args[++i]
|
||||
@@ -64,7 +66,7 @@ if (!locator) {
|
||||
// Resolve relative paths to absolute paths
|
||||
// Local paths like '.' or '../foo' need to be converted to absolute paths
|
||||
if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) {
|
||||
var resolved = fd.realpath(locator)
|
||||
resolved = fd.realpath(locator)
|
||||
if (resolved) {
|
||||
locator = resolved
|
||||
}
|
||||
@@ -83,6 +85,9 @@ var skipped_packages = []
|
||||
var visited = {}
|
||||
|
||||
function gather_packages(pkg_locator) {
|
||||
var lock = null
|
||||
var update_result = null
|
||||
var deps = null
|
||||
if (visited[pkg_locator]) return
|
||||
visited[pkg_locator] = true
|
||||
|
||||
@@ -96,12 +101,12 @@ function gather_packages(pkg_locator) {
|
||||
push(packages_to_install, pkg_locator)
|
||||
|
||||
// Try to read dependencies
|
||||
try {
|
||||
var _gather = function() {
|
||||
// For packages not yet extracted, we need to update and extract first to read deps
|
||||
var lock = shop.load_lock()
|
||||
lock = shop.load_lock()
|
||||
if (!lock[pkg_locator]) {
|
||||
if (!dry_run) {
|
||||
var update_result = shop.update(pkg_locator)
|
||||
update_result = shop.update(pkg_locator)
|
||||
if (update_result) {
|
||||
shop.extract(pkg_locator)
|
||||
} else {
|
||||
@@ -117,19 +122,20 @@ function gather_packages(pkg_locator) {
|
||||
}
|
||||
}
|
||||
|
||||
var deps = pkg.dependencies(pkg_locator)
|
||||
deps = pkg.dependencies(pkg_locator)
|
||||
if (deps) {
|
||||
arrfor(array(deps), function(alias) {
|
||||
var dep_locator = deps[alias]
|
||||
gather_packages(dep_locator)
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
} disruption {
|
||||
// Package might not have dependencies or cell.toml issue
|
||||
if (!dry_run) {
|
||||
log.console(`Warning: Could not read dependencies for ${pkg_locator}: ${e.message}`)
|
||||
log.console(`Warning: Could not read dependencies for ${pkg_locator}`)
|
||||
}
|
||||
}
|
||||
_gather()
|
||||
}
|
||||
|
||||
// Gather all packages
|
||||
@@ -164,11 +170,12 @@ function install_package(pkg_locator) {
|
||||
shop.build_package_scripts(pkg_locator)
|
||||
|
||||
// Build C code
|
||||
try {
|
||||
var _build_c = function() {
|
||||
build.build_dynamic(pkg_locator, target_triple, 'release')
|
||||
} catch (e) {
|
||||
} disruption {
|
||||
// Not all packages have C code
|
||||
}
|
||||
_build_c()
|
||||
}
|
||||
|
||||
arrfor(packages_to_install, function(p) {
|
||||
|
||||
@@ -1,19 +1,39 @@
|
||||
// Hidden vars come from env:
|
||||
// CLI mode (cell_init): os, args, core_path, shop_path
|
||||
// Actor spawn (script_startup): os, json, nota, wota, actorsym, init, core_path, shop_path
|
||||
// Actor spawn (script_startup): os, json, actorsym, init, core_path, shop_path
|
||||
// args[0] = script name, args[1..] = user args
|
||||
var load_internal = os.load_internal
|
||||
function use_embed(name) {
|
||||
return load_internal("js_" + name + "_use")
|
||||
return load_internal("js_core_" + name + "_use")
|
||||
}
|
||||
|
||||
var fd = use_embed('fd')
|
||||
var fd = use_embed('internal_fd')
|
||||
var json = use_embed('json')
|
||||
var crypto = use_embed('crypto')
|
||||
|
||||
var use_cache = {}
|
||||
use_cache['fd'] = fd
|
||||
use_cache['os'] = os
|
||||
use_cache['json'] = json
|
||||
use_cache['crypto'] = crypto
|
||||
|
||||
function content_hash(content) {
|
||||
return text(crypto.blake2(content), 'h')
|
||||
}
|
||||
|
||||
function cache_path(hash) {
|
||||
if (!shop_path) return null
|
||||
return shop_path + '/build/' + hash
|
||||
}
|
||||
|
||||
function ensure_build_dir() {
|
||||
if (!shop_path) return null
|
||||
var dir = shop_path + '/build'
|
||||
if (!fd.is_dir(dir)) {
|
||||
fd.mkdir(dir)
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// Bootstrap: load tokenize.cm, parse.cm, fold.cm from pre-compiled mach bytecode
|
||||
function use_basic(path) {
|
||||
@@ -24,19 +44,28 @@ function use_basic(path) {
|
||||
return result
|
||||
}
|
||||
|
||||
// Load a module from .mach/.mcode bytecode (bootstrap modules have no source fallback)
|
||||
// Load a module from cached .mach or .mcode bytecode
|
||||
function boot_load(name, env) {
|
||||
var mach_path = core_path + '/' + name + ".cm.mach"
|
||||
var mcode_path = core_path + '/' + name + ".cm.mcode"
|
||||
var data = null
|
||||
var mcode_path = core_path + '/boot/' + name + ".cm.mcode"
|
||||
var mcode_blob = null
|
||||
var hash = null
|
||||
var cached = null
|
||||
var mcode_json = null
|
||||
if (fd.is_file(mach_path)) {
|
||||
data = fd.slurp(mach_path)
|
||||
return mach_load(data, env)
|
||||
}
|
||||
var mach_blob = null
|
||||
if (fd.is_file(mcode_path)) {
|
||||
mcode_json = text(fd.slurp(mcode_path))
|
||||
return mach_eval_mcode(name, mcode_json, env)
|
||||
mcode_blob = fd.slurp(mcode_path)
|
||||
hash = content_hash(mcode_blob)
|
||||
cached = cache_path(hash)
|
||||
if (cached && fd.is_file(cached)) {
|
||||
return mach_load(fd.slurp(cached), env)
|
||||
}
|
||||
mcode_json = text(mcode_blob)
|
||||
mach_blob = mach_compile_mcode_bin(name, mcode_json)
|
||||
if (cached) {
|
||||
ensure_build_dir()
|
||||
fd.slurpwrite(cached, mach_blob)
|
||||
}
|
||||
return mach_load(mach_blob, env)
|
||||
}
|
||||
print("error: missing bootstrap bytecode: " + name + "\n")
|
||||
disrupt
|
||||
@@ -58,42 +87,33 @@ var streamline_mod = null
|
||||
// Warn if any .cm source is newer than its compiled bytecode
|
||||
function check_mach_stale() {
|
||||
var sources = [
|
||||
"tokenize.cm",
|
||||
"parse.cm",
|
||||
"fold.cm",
|
||||
"mcode.cm",
|
||||
"streamline.cm",
|
||||
"qbe.cm",
|
||||
"qbe_emit.cm",
|
||||
"internal/bootstrap.cm",
|
||||
"internal/engine.cm"
|
||||
{src: "tokenize.cm", mcode: "boot/tokenize.cm.mcode"},
|
||||
{src: "parse.cm", mcode: "boot/parse.cm.mcode"},
|
||||
{src: "fold.cm", mcode: "boot/fold.cm.mcode"},
|
||||
{src: "mcode.cm", mcode: "boot/mcode.cm.mcode"},
|
||||
{src: "streamline.cm", mcode: "boot/streamline.cm.mcode"},
|
||||
{src: "qbe.cm", mcode: "boot/qbe.cm.mcode"},
|
||||
{src: "qbe_emit.cm", mcode: "boot/qbe_emit.cm.mcode"},
|
||||
{src: "verify_ir.cm", mcode: "boot/verify_ir.cm.mcode"},
|
||||
{src: "internal/bootstrap.cm", mcode: "boot/bootstrap.cm.mcode"},
|
||||
{src: "internal/engine.cm", mcode: "boot/engine.cm.mcode"}
|
||||
]
|
||||
var stale = []
|
||||
var _i = 0
|
||||
var cm_path = null
|
||||
var mach_path = null
|
||||
var mcode_path = null
|
||||
var cm_stat = null
|
||||
var compiled_stat = null
|
||||
var best_mtime = null
|
||||
var entry = null
|
||||
while (_i < length(sources)) {
|
||||
cm_path = core_path + '/' + sources[_i]
|
||||
mach_path = cm_path + '.mach'
|
||||
mcode_path = cm_path + '.mcode'
|
||||
best_mtime = null
|
||||
if (fd.is_file(mach_path)) {
|
||||
best_mtime = fd.stat(mach_path).mtime
|
||||
}
|
||||
if (fd.is_file(mcode_path)) {
|
||||
entry = sources[_i]
|
||||
cm_path = core_path + '/' + entry.src
|
||||
mcode_path = core_path + '/' + entry.mcode
|
||||
if (fd.is_file(mcode_path) && fd.is_file(cm_path)) {
|
||||
compiled_stat = fd.stat(mcode_path)
|
||||
if (best_mtime == null || compiled_stat.mtime > best_mtime) {
|
||||
best_mtime = compiled_stat.mtime
|
||||
}
|
||||
}
|
||||
if (best_mtime != null && fd.is_file(cm_path)) {
|
||||
cm_stat = fd.stat(cm_path)
|
||||
if (cm_stat.mtime > best_mtime) {
|
||||
push(stale, sources[_i])
|
||||
if (cm_stat.mtime > compiled_stat.mtime) {
|
||||
push(stale, entry.src)
|
||||
}
|
||||
}
|
||||
_i = _i + 1
|
||||
@@ -140,38 +160,11 @@ function analyze(src, filename) {
|
||||
return ast
|
||||
}
|
||||
|
||||
// Load a module from .mach/.mcode bytecode, falling back to source compilation
|
||||
function load_module(name, env) {
|
||||
var mach_path = core_path + '/' + name + ".cm.mach"
|
||||
var mcode_path = core_path + '/' + name + ".cm.mcode"
|
||||
var data = null
|
||||
var mcode_json = null
|
||||
var src_path = null
|
||||
var src = null
|
||||
var ast = null
|
||||
var compiled = null
|
||||
var optimized = null
|
||||
if (fd.is_file(mach_path)) {
|
||||
data = fd.slurp(mach_path)
|
||||
return mach_load(data, env)
|
||||
}
|
||||
if (fd.is_file(mcode_path)) {
|
||||
mcode_json = text(fd.slurp(mcode_path))
|
||||
return mach_eval_mcode(name, mcode_json, env)
|
||||
}
|
||||
src_path = core_path + '/' + name + ".cm"
|
||||
src = text(fd.slurp(src_path))
|
||||
ast = analyze(src, src_path)
|
||||
compiled = mcode_mod(ast)
|
||||
optimized = streamline_mod(compiled)
|
||||
return mach_eval_mcode(name, json.encode(optimized), env)
|
||||
}
|
||||
|
||||
// Load optimization pipeline modules (needs analyze to be defined)
|
||||
streamline_mod = load_module("streamline", boot_env)
|
||||
streamline_mod = boot_load("streamline", boot_env)
|
||||
use_cache['streamline'] = streamline_mod
|
||||
|
||||
// Lazy-loaded verify_ir module (loaded on first use via use_fn)
|
||||
// Lazy-loaded verify_ir module (loaded on first use)
|
||||
var _verify_ir_mod = null
|
||||
|
||||
// Run AST through mcode pipeline → register VM
|
||||
@@ -179,7 +172,7 @@ function run_ast(name, ast, env) {
|
||||
var compiled = mcode_mod(ast)
|
||||
if (os._verify_ir) {
|
||||
if (_verify_ir_mod == null) {
|
||||
_verify_ir_mod = use_fn('verify_ir')
|
||||
_verify_ir_mod = boot_load('verify_ir', boot_env)
|
||||
}
|
||||
compiled._verify = true
|
||||
compiled._verify_mod = _verify_ir_mod
|
||||
@@ -190,88 +183,55 @@ function run_ast(name, ast, env) {
|
||||
delete optimized._verify
|
||||
delete optimized._verify_mod
|
||||
}
|
||||
return mach_eval_mcode(name, json.encode(optimized), env)
|
||||
var mcode_json = json.encode(optimized)
|
||||
var mach_blob = mach_compile_mcode_bin(name, mcode_json)
|
||||
return mach_load(mach_blob, env)
|
||||
}
|
||||
|
||||
// Run AST through mcode pipeline WITHOUT optimization → register VM
|
||||
function run_ast_noopt(name, ast, env) {
|
||||
var compiled = mcode_mod(ast)
|
||||
return mach_eval_mcode(name, json.encode(compiled), env)
|
||||
var mcode_json = json.encode(compiled)
|
||||
var mach_blob = mach_compile_mcode_bin(name, mcode_json)
|
||||
return mach_load(mach_blob, env)
|
||||
}
|
||||
|
||||
// use() with ƿit pipeline for .cm modules
|
||||
function use_fn(path) {
|
||||
var file_path = null
|
||||
var mach_path = null
|
||||
var mcode_path = null
|
||||
var mcode_json = null
|
||||
var data = null
|
||||
var script = null
|
||||
var ast = null
|
||||
var result = null
|
||||
if (use_cache[path])
|
||||
return use_cache[path]
|
||||
|
||||
// Try .cm.mach bytecode first (CWD then core_path)
|
||||
mach_path = path + '.cm.mach'
|
||||
if (!fd.is_file(mach_path))
|
||||
mach_path = core_path + '/' + path + '.cm.mach'
|
||||
if (fd.is_file(mach_path)) {
|
||||
data = fd.slurp(mach_path)
|
||||
result = mach_load(data, {use: use_fn})
|
||||
use_cache[path] = result
|
||||
return result
|
||||
}
|
||||
|
||||
// Try .cm.mcode JSON IR (CWD then core_path)
|
||||
mcode_path = path + '.cm.mcode'
|
||||
if (!fd.is_file(mcode_path))
|
||||
mcode_path = core_path + '/' + path + '.cm.mcode'
|
||||
if (fd.is_file(mcode_path)) {
|
||||
mcode_json = text(fd.slurp(mcode_path))
|
||||
result = mach_eval_mcode(path, mcode_json, {use: use_fn})
|
||||
use_cache[path] = result
|
||||
return result
|
||||
}
|
||||
|
||||
// Try .cm source (CWD then core_path)
|
||||
file_path = path + '.cm'
|
||||
if (!fd.is_file(file_path))
|
||||
file_path = core_path + '/' + path + '.cm'
|
||||
|
||||
if (fd.is_file(file_path)) {
|
||||
script = text(fd.slurp(file_path))
|
||||
ast = analyze(script, file_path)
|
||||
result = run_ast(path, ast, {use: use_fn})
|
||||
use_cache[path] = result
|
||||
return result
|
||||
}
|
||||
|
||||
// Fallback to embedded C module
|
||||
result = use_embed(replace(path, '/', '_'))
|
||||
use_cache[path] = result
|
||||
return result
|
||||
// Compile AST to blob without loading (for caching)
|
||||
function compile_to_blob(name, ast) {
|
||||
var compiled = mcode_mod(ast)
|
||||
var optimized = streamline_mod(compiled)
|
||||
return mach_compile_mcode_bin(name, json.encode(optimized))
|
||||
}
|
||||
|
||||
// Helper to load engine.cm and run it with given env
|
||||
function load_engine(env) {
|
||||
var engine_path = core_path + '/internal/engine.cm.mach'
|
||||
var mcode_path = core_path + '/internal/engine.cm.mcode'
|
||||
var data = null
|
||||
var mcode_path = core_path + '/boot/engine.cm.mcode'
|
||||
var mcode_blob = null
|
||||
var hash = null
|
||||
var cached = null
|
||||
var mcode_json = null
|
||||
var mach_blob = null
|
||||
var engine_src = null
|
||||
var engine_ast = null
|
||||
if (fd.is_file(engine_path)) {
|
||||
data = fd.slurp(engine_path)
|
||||
return mach_load(data, env)
|
||||
}
|
||||
if (fd.is_file(mcode_path)) {
|
||||
mcode_json = text(fd.slurp(mcode_path))
|
||||
return mach_eval_mcode('engine', mcode_json, env)
|
||||
mcode_blob = fd.slurp(mcode_path)
|
||||
hash = content_hash(mcode_blob)
|
||||
cached = cache_path(hash)
|
||||
if (cached && fd.is_file(cached)) {
|
||||
return mach_load(fd.slurp(cached), env)
|
||||
}
|
||||
mcode_json = text(mcode_blob)
|
||||
mach_blob = mach_compile_mcode_bin('engine', mcode_json)
|
||||
if (cached) {
|
||||
ensure_build_dir()
|
||||
fd.slurpwrite(cached, mach_blob)
|
||||
}
|
||||
return mach_load(mach_blob, env)
|
||||
}
|
||||
engine_path = core_path + '/internal/engine.cm'
|
||||
engine_src = text(fd.slurp(engine_path))
|
||||
engine_ast = analyze(engine_src, engine_path)
|
||||
// Fallback: compile from source
|
||||
var engine_cm = core_path + '/internal/engine.cm'
|
||||
engine_src = text(fd.slurp(engine_cm))
|
||||
engine_ast = analyze(engine_src, engine_cm)
|
||||
return run_ast('engine', engine_ast, env)
|
||||
}
|
||||
|
||||
@@ -298,13 +258,19 @@ if (args != null) {
|
||||
os: os, actorsym: actorsym,
|
||||
init: {program: program, arg: user_args},
|
||||
core_path: core_path, shop_path: shop_path, json: json,
|
||||
analyze: analyze, run_ast_fn: run_ast, run_ast_noopt_fn: run_ast_noopt
|
||||
analyze: analyze, run_ast_fn: run_ast, run_ast_noopt_fn: run_ast_noopt,
|
||||
use_cache: use_cache,
|
||||
content_hash: content_hash, cache_path: cache_path,
|
||||
ensure_build_dir: ensure_build_dir, compile_to_blob_fn: compile_to_blob
|
||||
})
|
||||
} else {
|
||||
// Actor spawn mode — load engine.cm with full actor env
|
||||
load_engine({
|
||||
os: os, actorsym: actorsym, init: init,
|
||||
core_path: core_path, shop_path: shop_path, json: json, nota: nota, wota: wota,
|
||||
analyze: analyze, run_ast_fn: run_ast, run_ast_noopt_fn: run_ast_noopt
|
||||
core_path: core_path, shop_path: shop_path, json: json,
|
||||
analyze: analyze, run_ast_fn: run_ast, run_ast_noopt_fn: run_ast_noopt,
|
||||
use_cache: use_cache,
|
||||
content_hash: content_hash, cache_path: cache_path,
|
||||
ensure_build_dir: ensure_build_dir, compile_to_blob_fn: compile_to_blob
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,4 @@
|
||||
// Hidden vars (os, actorsym, init, core_path, shop_path, analyze, run_ast_fn, run_ast_noopt_fn, json) come from env
|
||||
// In actor spawn mode, also: nota, wota
|
||||
// Hidden vars (os, actorsym, init, core_path, shop_path, analyze, run_ast_fn, run_ast_noopt_fn, json, use_cache, content_hash, cache_path, ensure_build_dir, compile_to_blob_fn) come from env
|
||||
var ACTORDATA = actorsym
|
||||
var SYSYM = '__SYSTEM__'
|
||||
|
||||
@@ -19,7 +18,7 @@ var ACTOR_EXT = '.ce'
|
||||
|
||||
var load_internal = os.load_internal
|
||||
function use_embed(name) {
|
||||
return load_internal("js_" + name + "_use")
|
||||
return load_internal("js_core_" + name + "_use")
|
||||
}
|
||||
|
||||
function logical(val1) {
|
||||
@@ -46,14 +45,13 @@ function ends_with(str, suffix) {
|
||||
return search(str, suffix, -length(suffix)) != null
|
||||
}
|
||||
|
||||
var fd = use_embed('fd')
|
||||
var fd = use_embed('internal_fd')
|
||||
var js = use_embed('js')
|
||||
|
||||
// core_path and shop_path come from env (bootstrap.cm passes them through)
|
||||
// shop_path may be null if --core was used without --shop
|
||||
var packages_path = shop_path ? shop_path + '/packages' : null
|
||||
|
||||
var use_cache = {}
|
||||
use_cache['core/os'] = os
|
||||
|
||||
// Extra env properties added as engine initializes (log, runtime fns, etc.)
|
||||
@@ -70,34 +68,56 @@ function use_core(path) {
|
||||
var result = null
|
||||
var script = null
|
||||
var ast = null
|
||||
var mcode_path = null
|
||||
var mcode_blob = null
|
||||
|
||||
// Build env: merge core_extras, include C embed as 'native' if available
|
||||
// Build env: merge core_extras
|
||||
env = {use: use_core}
|
||||
arrfor(array(core_extras), function(k) { env[k] = core_extras[k] })
|
||||
if (sym) env.native = sym
|
||||
|
||||
// Check for pre-compiled .cm.mach file first
|
||||
var mach_path = core_path + '/' + path + '.cm.mach'
|
||||
if (fd.is_file(mach_path)) {
|
||||
result = mach_load(fd.slurp(mach_path), env)
|
||||
use_cache[cache_key] = result
|
||||
return result
|
||||
}
|
||||
var hash = null
|
||||
var cached_path = null
|
||||
var mach_blob = null
|
||||
var source_blob = null
|
||||
|
||||
// Check for .cm.mcode JSON IR
|
||||
var mcode_path = core_path + '/' + path + '.cm.mcode'
|
||||
// Check for pre-compiled .cm.mcode JSON IR (generated by regen)
|
||||
mcode_path = core_path + '/boot/' + replace(path, '/', '_') + '.cm.mcode'
|
||||
if (fd.is_file(mcode_path)) {
|
||||
result = mach_eval_mcode('core:' + path, text(fd.slurp(mcode_path)), env)
|
||||
mcode_blob = fd.slurp(mcode_path)
|
||||
hash = content_hash(mcode_blob)
|
||||
cached_path = cache_path(hash)
|
||||
if (cached_path && fd.is_file(cached_path)) {
|
||||
result = mach_load(fd.slurp(cached_path), env)
|
||||
} else {
|
||||
mach_blob = mach_compile_mcode_bin('core:' + path, text(mcode_blob))
|
||||
if (cached_path) {
|
||||
ensure_build_dir()
|
||||
fd.slurpwrite(cached_path, mach_blob)
|
||||
}
|
||||
result = mach_load(mach_blob, env)
|
||||
}
|
||||
use_cache[cache_key] = result
|
||||
return result
|
||||
}
|
||||
|
||||
// Fall back to source .cm file — compile at runtime
|
||||
// Compile from source .cm file
|
||||
var file_path = core_path + '/' + path + MOD_EXT
|
||||
if (fd.is_file(file_path)) {
|
||||
script = text(fd.slurp(file_path))
|
||||
ast = analyze(script, file_path)
|
||||
result = run_ast_fn('core:' + path, ast, env)
|
||||
source_blob = fd.slurp(file_path)
|
||||
hash = content_hash(source_blob)
|
||||
cached_path = cache_path(hash)
|
||||
if (cached_path && fd.is_file(cached_path)) {
|
||||
result = mach_load(fd.slurp(cached_path), env)
|
||||
} else {
|
||||
script = text(source_blob)
|
||||
ast = analyze(script, file_path)
|
||||
mach_blob = compile_to_blob_fn('core:' + path, ast)
|
||||
if (cached_path) {
|
||||
ensure_build_dir()
|
||||
fd.slurpwrite(cached_path, mach_blob)
|
||||
}
|
||||
result = mach_load(mach_blob, env)
|
||||
}
|
||||
use_cache[cache_key] = result
|
||||
return result
|
||||
}
|
||||
@@ -209,15 +229,27 @@ function create_actor(desc) {
|
||||
var $_ = {}
|
||||
$_.self = create_actor()
|
||||
|
||||
os.use_cache = use_cache
|
||||
os.global_shop_path = shop_path
|
||||
os.$_ = $_
|
||||
os.analyze = analyze
|
||||
os.run_ast_fn = run_ast_fn
|
||||
os.run_ast_noopt_fn = run_ast_noopt_fn
|
||||
os.json = json
|
||||
use_cache['core/json'] = json
|
||||
|
||||
// Create runtime_env early (empty) — filled after pronto loads.
|
||||
// Shop accesses it lazily (in inject_env, called at module-use time, not load time)
|
||||
// so it sees the filled version.
|
||||
var runtime_env = {}
|
||||
|
||||
// Populate core_extras with everything shop (and other core modules) need
|
||||
core_extras.use_cache = use_cache
|
||||
core_extras.shop_path = shop_path
|
||||
core_extras.analyze = analyze
|
||||
core_extras.run_ast_fn = run_ast_fn
|
||||
core_extras.run_ast_noopt_fn = run_ast_noopt_fn
|
||||
core_extras.core_json = json
|
||||
core_extras.actor_api = $_
|
||||
core_extras.runtime_env = runtime_env
|
||||
core_extras.content_hash = content_hash
|
||||
core_extras.cache_path = cache_path
|
||||
core_extras.ensure_build_dir = ensure_build_dir
|
||||
|
||||
// NOW load shop — it receives all of the above via env
|
||||
var shop = use_core('internal/shop')
|
||||
var time = use_core('time')
|
||||
|
||||
@@ -227,29 +259,24 @@ var parallel = pronto.parallel
|
||||
var race = pronto.race
|
||||
var sequence = pronto.sequence
|
||||
|
||||
// Create runtime environment for modules
|
||||
var runtime_env = {
|
||||
logical: logical,
|
||||
some: some,
|
||||
every: every,
|
||||
starts_with: starts_with,
|
||||
ends_with: ends_with,
|
||||
actor: actor,
|
||||
is_actor: is_actor,
|
||||
log: log,
|
||||
send: send,
|
||||
fallback: fallback,
|
||||
parallel: parallel,
|
||||
race: race,
|
||||
sequence: sequence
|
||||
}
|
||||
// Fill runtime_env (same object reference shop holds)
|
||||
runtime_env.logical = logical
|
||||
runtime_env.some = some
|
||||
runtime_env.every = every
|
||||
runtime_env.starts_with = starts_with
|
||||
runtime_env.ends_with = ends_with
|
||||
runtime_env.actor = actor
|
||||
runtime_env.is_actor = is_actor
|
||||
runtime_env.log = log
|
||||
runtime_env.send = send
|
||||
runtime_env.fallback = fallback
|
||||
runtime_env.parallel = parallel
|
||||
runtime_env.race = race
|
||||
runtime_env.sequence = sequence
|
||||
|
||||
// Make runtime functions available to modules loaded via use_core
|
||||
arrfor(array(runtime_env), function(k) { core_extras[k] = runtime_env[k] })
|
||||
|
||||
// Pass to os for shop to access
|
||||
os.runtime_env = runtime_env
|
||||
|
||||
$_.time_limit = function(requestor, seconds)
|
||||
{
|
||||
if (!pronto.is_requestor(requestor)) {
|
||||
@@ -884,9 +911,25 @@ $_.clock(_ => {
|
||||
env.args = _cell.args.arg
|
||||
env.log = log
|
||||
|
||||
var script = text(fd.slurp(prog_path))
|
||||
var ast = analyze(script, prog_path)
|
||||
var val = run_ast_fn(prog, ast, env)
|
||||
var source_blob = fd.slurp(prog_path)
|
||||
var hash = content_hash(source_blob)
|
||||
var cached_path = cache_path(hash)
|
||||
var val = null
|
||||
var script = null
|
||||
var ast = null
|
||||
var mach_blob = null
|
||||
if (cached_path && fd.is_file(cached_path)) {
|
||||
val = mach_load(fd.slurp(cached_path), env)
|
||||
} else {
|
||||
script = text(source_blob)
|
||||
ast = analyze(script, prog_path)
|
||||
mach_blob = compile_to_blob_fn(prog, ast)
|
||||
if (cached_path) {
|
||||
ensure_build_dir()
|
||||
fd.slurpwrite(cached_path, mach_blob)
|
||||
}
|
||||
val = mach_load(mach_blob, env)
|
||||
}
|
||||
if (val) {
|
||||
log.error('Program must not return anything')
|
||||
disrupt
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -412,117 +412,117 @@ JSC_CCALL(fd_close,
|
||||
JSC_CCALL(fd_fstat,
|
||||
int fd = js2fd(js, argv[0]);
|
||||
if (fd < 0) return JS_EXCEPTION;
|
||||
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) != 0)
|
||||
return JS_ThrowInternalError(js, "fstat failed: %s", strerror(errno));
|
||||
|
||||
JSValue obj = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size));
|
||||
JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode));
|
||||
JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid));
|
||||
JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid));
|
||||
JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime));
|
||||
JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime));
|
||||
JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime));
|
||||
JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink));
|
||||
JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino));
|
||||
JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev));
|
||||
JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev));
|
||||
JS_FRAME(js);
|
||||
JS_ROOT(obj, JS_NewObject(js));
|
||||
JS_SetPropertyStr(js, obj.val, "size", JS_NewInt64(js, st.st_size));
|
||||
JS_SetPropertyStr(js, obj.val, "mode", JS_NewInt32(js, st.st_mode));
|
||||
JS_SetPropertyStr(js, obj.val, "uid", JS_NewInt32(js, st.st_uid));
|
||||
JS_SetPropertyStr(js, obj.val, "gid", JS_NewInt32(js, st.st_gid));
|
||||
JS_SetPropertyStr(js, obj.val, "atime", JS_NewInt64(js, st.st_atime));
|
||||
JS_SetPropertyStr(js, obj.val, "mtime", JS_NewInt64(js, st.st_mtime));
|
||||
JS_SetPropertyStr(js, obj.val, "ctime", JS_NewInt64(js, st.st_ctime));
|
||||
JS_SetPropertyStr(js, obj.val, "nlink", JS_NewInt32(js, st.st_nlink));
|
||||
JS_SetPropertyStr(js, obj.val, "ino", JS_NewInt64(js, st.st_ino));
|
||||
JS_SetPropertyStr(js, obj.val, "dev", JS_NewInt32(js, st.st_dev));
|
||||
JS_SetPropertyStr(js, obj.val, "rdev", JS_NewInt32(js, st.st_rdev));
|
||||
#ifndef _WIN32
|
||||
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize));
|
||||
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks));
|
||||
JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, st.st_blksize));
|
||||
JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_blocks));
|
||||
#else
|
||||
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096));
|
||||
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512));
|
||||
JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, 4096));
|
||||
JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_size / 512));
|
||||
#endif
|
||||
|
||||
// Add boolean properties for file type
|
||||
JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
|
||||
|
||||
return obj;
|
||||
JS_SetPropertyStr(js, obj.val, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj.val, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj.val, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj.val, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj.val, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj.val, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj.val, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
|
||||
JS_RETURN(obj.val);
|
||||
)
|
||||
|
||||
JSC_CCALL(fd_stat,
|
||||
const char *path = JS_ToCString(js, argv[0]);
|
||||
if (!path) return JS_EXCEPTION;
|
||||
|
||||
|
||||
struct stat st;
|
||||
if (stat(path, &st) != 0) {
|
||||
JS_FreeCString(js, path);
|
||||
return JS_NewObject(js);
|
||||
}
|
||||
|
||||
JSValue obj = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size));
|
||||
JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode));
|
||||
JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid));
|
||||
JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid));
|
||||
JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime));
|
||||
JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime));
|
||||
JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime));
|
||||
JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink));
|
||||
JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino));
|
||||
JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev));
|
||||
JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev));
|
||||
JS_FRAME(js);
|
||||
JS_ROOT(obj, JS_NewObject(js));
|
||||
JS_SetPropertyStr(js, obj.val, "size", JS_NewInt64(js, st.st_size));
|
||||
JS_SetPropertyStr(js, obj.val, "mode", JS_NewInt32(js, st.st_mode));
|
||||
JS_SetPropertyStr(js, obj.val, "uid", JS_NewInt32(js, st.st_uid));
|
||||
JS_SetPropertyStr(js, obj.val, "gid", JS_NewInt32(js, st.st_gid));
|
||||
JS_SetPropertyStr(js, obj.val, "atime", JS_NewInt64(js, st.st_atime));
|
||||
JS_SetPropertyStr(js, obj.val, "mtime", JS_NewInt64(js, st.st_mtime));
|
||||
JS_SetPropertyStr(js, obj.val, "ctime", JS_NewInt64(js, st.st_ctime));
|
||||
JS_SetPropertyStr(js, obj.val, "nlink", JS_NewInt32(js, st.st_nlink));
|
||||
JS_SetPropertyStr(js, obj.val, "ino", JS_NewInt64(js, st.st_ino));
|
||||
JS_SetPropertyStr(js, obj.val, "dev", JS_NewInt32(js, st.st_dev));
|
||||
JS_SetPropertyStr(js, obj.val, "rdev", JS_NewInt32(js, st.st_rdev));
|
||||
#ifndef _WIN32
|
||||
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize));
|
||||
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks));
|
||||
JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, st.st_blksize));
|
||||
JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_blocks));
|
||||
#else
|
||||
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096));
|
||||
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512));
|
||||
JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, 4096));
|
||||
JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_size / 512));
|
||||
#endif
|
||||
|
||||
// Add boolean properties for file type
|
||||
JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
|
||||
|
||||
JS_SetPropertyStr(js, obj.val, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj.val, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj.val, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj.val, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj.val, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj.val, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
|
||||
JS_SetPropertyStr(js, obj.val, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
|
||||
JS_FreeCString(js, path);
|
||||
return obj;
|
||||
JS_RETURN(obj.val);
|
||||
)
|
||||
|
||||
JSC_SCALL(fd_readdir,
|
||||
JS_FRAME(js);
|
||||
#ifdef _WIN32
|
||||
WIN32_FIND_DATA ffd;
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, sizeof(path), "%s\\*", str);
|
||||
HANDLE hFind = FindFirstFile(path, &ffd);
|
||||
if (hFind == INVALID_HANDLE_VALUE) {
|
||||
ret = JS_ThrowInternalError(js, "FindFirstFile failed for %s", path);
|
||||
ret = JS_ThrowInternalError(js, "FindFirstFile failed for %s", path);
|
||||
} else {
|
||||
ret = JS_NewArray(js);
|
||||
do {
|
||||
if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue;
|
||||
JS_ArrayPush(js, &ret, JS_NewString(js, ffd.cFileName));
|
||||
} while (FindNextFile(hFind, &ffd) != 0);
|
||||
FindClose(hFind);
|
||||
JS_ROOT(arr, JS_NewArray(js));
|
||||
do {
|
||||
if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue;
|
||||
JS_ArrayPush(js, &arr.val, JS_NewString(js, ffd.cFileName));
|
||||
} while (FindNextFile(hFind, &ffd) != 0);
|
||||
FindClose(hFind);
|
||||
ret = arr.val;
|
||||
}
|
||||
#else
|
||||
DIR *d;
|
||||
struct dirent *dir;
|
||||
d = opendir(str);
|
||||
if (d) {
|
||||
ret = JS_NewArray(js);
|
||||
JS_ROOT(arr, JS_NewArray(js));
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue;
|
||||
JS_ArrayPush(js, &ret, JS_NewString(js, dir->d_name));
|
||||
JS_ArrayPush(js, &arr.val, JS_NewString(js, dir->d_name));
|
||||
}
|
||||
closedir(d);
|
||||
ret = arr.val;
|
||||
} else {
|
||||
ret = JS_ThrowInternalError(js, "opendir failed for %s: %s", str, strerror(errno));
|
||||
}
|
||||
#endif
|
||||
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame);
|
||||
)
|
||||
|
||||
JSC_CCALL(fd_is_file,
|
||||
@@ -585,9 +585,9 @@ JSC_CCALL(fd_slurpwrite,
|
||||
)
|
||||
|
||||
// Helper function for recursive enumeration
|
||||
static void visit_directory(JSContext *js, JSValue results, int *result_count, const char *curr_path, const char *rel_prefix, int recurse) {
|
||||
static void visit_directory(JSContext *js, JSValue *results, int *result_count, const char *curr_path, const char *rel_prefix, int recurse) {
|
||||
if (!curr_path) return;
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
WIN32_FIND_DATA ffd;
|
||||
char search_path[PATH_MAX];
|
||||
@@ -602,7 +602,7 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c
|
||||
} else {
|
||||
strcpy(item_rel, ffd.cFileName);
|
||||
}
|
||||
JS_SetPropertyNumber(js, results, (*result_count)++, JS_NewString(js, item_rel));
|
||||
JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel));
|
||||
|
||||
if (recurse) {
|
||||
struct stat st;
|
||||
@@ -627,7 +627,7 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c
|
||||
} else {
|
||||
strcpy(item_rel, dir->d_name);
|
||||
}
|
||||
JS_SetPropertyNumber(js, results, (*result_count)++, JS_NewString(js, item_rel));
|
||||
JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel));
|
||||
|
||||
if (recurse) {
|
||||
struct stat st;
|
||||
@@ -651,14 +651,16 @@ JSC_SCALL(fd_enumerate,
|
||||
if (argc > 1)
|
||||
recurse = JS_ToBool(js, argv[1]);
|
||||
|
||||
JSValue results = JS_NewArray(js);
|
||||
JS_FRAME(js);
|
||||
JS_ROOT(arr, JS_NewArray(js));
|
||||
int result_count = 0;
|
||||
|
||||
struct stat st;
|
||||
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
|
||||
visit_directory(js, results, &result_count, path, "", recurse);
|
||||
visit_directory(js, &arr.val, &result_count, path, "", recurse);
|
||||
|
||||
ret = results;
|
||||
ret = arr.val;
|
||||
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame);
|
||||
)
|
||||
|
||||
JSC_CCALL(fd_realpath,
|
||||
@@ -752,8 +754,9 @@ static const JSCFunctionListEntry js_fd_funcs[] = {
|
||||
MIST_FUNC_DEF(fd, readlink, 1),
|
||||
};
|
||||
|
||||
JSValue js_fd_use(JSContext *js) {
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_fd_funcs, countof(js_fd_funcs));
|
||||
return mod;
|
||||
}
|
||||
JSValue js_core_internal_fd_use(JSContext *js) {
|
||||
JS_FRAME(js);
|
||||
JS_ROOT(mod, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, mod.val, js_fd_funcs, countof(js_fd_funcs));
|
||||
JS_RETURN(mod.val);
|
||||
}
|
||||
@@ -73,9 +73,10 @@ static const JSCFunctionListEntry js_kim_funcs[] = {
|
||||
MIST_FUNC_DEF(kim, decode, 1),
|
||||
};
|
||||
|
||||
JSValue js_kim_use(JSContext *js)
|
||||
JSValue js_core_kim_use(JSContext *js)
|
||||
{
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_kim_funcs, countof(js_kim_funcs));
|
||||
return mod;
|
||||
JS_FRAME(js);
|
||||
JS_ROOT(mod, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, mod.val, js_kim_funcs, countof(js_kim_funcs));
|
||||
JS_RETURN(mod.val);
|
||||
}
|
||||
111
internal/os.c
111
internal/os.c
@@ -277,29 +277,31 @@ JSC_CCALL(os_mallinfo,
|
||||
)
|
||||
|
||||
static JSValue js_os_rusage(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||
JSValue ret = JS_NULL;
|
||||
ret = JS_NewObject(js);
|
||||
JS_FRAME(js);
|
||||
JS_ROOT(ret, JS_NewObject(js));
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
struct rusage jsmem;
|
||||
getrusage(RUSAGE_SELF, &jsmem);
|
||||
JSJMEMRET(ru_maxrss);
|
||||
JSJMEMRET(ru_ixrss);
|
||||
JSJMEMRET(ru_idrss);
|
||||
JSJMEMRET(ru_isrss);
|
||||
JSJMEMRET(ru_minflt);
|
||||
JSJMEMRET(ru_majflt);
|
||||
JSJMEMRET(ru_nswap);
|
||||
JSJMEMRET(ru_inblock);
|
||||
JSJMEMRET(ru_oublock);
|
||||
JSJMEMRET(ru_msgsnd);
|
||||
JSJMEMRET(ru_msgrcv);
|
||||
JSJMEMRET(ru_nsignals);
|
||||
JSJMEMRET(ru_nvcsw);
|
||||
JSJMEMRET(ru_nivcsw);
|
||||
#define JSJMEMRET_GC(FIELD) JS_SetPropertyStr(js, ret.val, #FIELD, number2js(js, jsmem.FIELD));
|
||||
JSJMEMRET_GC(ru_maxrss);
|
||||
JSJMEMRET_GC(ru_ixrss);
|
||||
JSJMEMRET_GC(ru_idrss);
|
||||
JSJMEMRET_GC(ru_isrss);
|
||||
JSJMEMRET_GC(ru_minflt);
|
||||
JSJMEMRET_GC(ru_majflt);
|
||||
JSJMEMRET_GC(ru_nswap);
|
||||
JSJMEMRET_GC(ru_inblock);
|
||||
JSJMEMRET_GC(ru_oublock);
|
||||
JSJMEMRET_GC(ru_msgsnd);
|
||||
JSJMEMRET_GC(ru_msgrcv);
|
||||
JSJMEMRET_GC(ru_nsignals);
|
||||
JSJMEMRET_GC(ru_nvcsw);
|
||||
JSJMEMRET_GC(ru_nivcsw);
|
||||
#undef JSJMEMRET_GC
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
JS_RETURN(ret.val);
|
||||
}
|
||||
|
||||
JSC_SCALL(os_system,
|
||||
@@ -425,6 +427,38 @@ static JSValue js_os_dylib_has_symbol(JSContext *js, JSValue self, int argc, JSV
|
||||
return JS_NewBool(js, symbol != NULL);
|
||||
}
|
||||
|
||||
static JSValue js_os_dylib_close(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(js, "dylib_close requires a dylib object");
|
||||
void *handle = JS_GetOpaque(argv[0], js_dylib_class_id);
|
||||
if (handle) {
|
||||
#ifdef _WIN32
|
||||
FreeLibrary((HMODULE)handle);
|
||||
#else
|
||||
dlclose(handle);
|
||||
#endif
|
||||
JS_SetOpaque(argv[0], NULL);
|
||||
}
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
/* Load a native .cm module from a dylib handle.
|
||||
Uses cell_rt_native_module_load from qbe_helpers.c */
|
||||
extern JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle);
|
||||
|
||||
static JSValue js_os_native_module_load(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(js, "native_module_load requires a dylib object");
|
||||
|
||||
void *handle = JS_GetOpaque(argv[0], js_dylib_class_id);
|
||||
if (!handle)
|
||||
return JS_ThrowTypeError(js, "First argument must be a dylib object");
|
||||
|
||||
return cell_rt_native_module_load(js, handle);
|
||||
}
|
||||
|
||||
JSC_CCALL(os_print,
|
||||
size_t len;
|
||||
const char *str = JS_ToCStringLen(js, &len, argv[0]);
|
||||
@@ -552,6 +586,37 @@ JSC_CCALL(os_getenv,
|
||||
ret = JS_NULL;
|
||||
)
|
||||
|
||||
/* --- Embedded module table (generated for static builds) ---
|
||||
Uses dlsym to check if cell_embedded_module_lookup exists at runtime.
|
||||
When linking a static build with a generated module_table.c, the symbol
|
||||
will be found; in dynamic builds it returns NULL gracefully. */
|
||||
|
||||
struct cell_embedded_entry {
|
||||
const char *name;
|
||||
const unsigned char *data;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
typedef const struct cell_embedded_entry *(*cell_embed_lookup_fn)(const char *);
|
||||
|
||||
static JSValue js_os_embedded_module(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
{
|
||||
cell_embed_lookup_fn lookup = (cell_embed_lookup_fn)dlsym(RTLD_DEFAULT, "cell_embedded_module_lookup");
|
||||
if (!lookup)
|
||||
return JS_NULL;
|
||||
|
||||
const char *name = JS_ToCString(js, argv[0]);
|
||||
if (!name) return JS_NULL;
|
||||
|
||||
const struct cell_embedded_entry *entry = lookup(name);
|
||||
JS_FreeCString(js, name);
|
||||
|
||||
if (!entry) return JS_NULL;
|
||||
|
||||
/* Return the mach blob as a stoned blob */
|
||||
return js_new_blob_stoned_copy(js, (void *)entry->data, entry->size);
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_os_funcs[] = {
|
||||
MIST_FUNC_DEF(os, platform, 0),
|
||||
MIST_FUNC_DEF(os, arch, 0),
|
||||
@@ -566,8 +631,11 @@ static const JSCFunctionListEntry js_os_funcs[] = {
|
||||
MIST_FUNC_DEF(os, exit, 0),
|
||||
MIST_FUNC_DEF(os, sleep, 1),
|
||||
MIST_FUNC_DEF(os, dylib_open, 1),
|
||||
MIST_FUNC_DEF(os, dylib_close, 1),
|
||||
MIST_FUNC_DEF(os, dylib_symbol, 2),
|
||||
MIST_FUNC_DEF(os, dylib_has_symbol, 2),
|
||||
MIST_FUNC_DEF(os, native_module_load, 1),
|
||||
MIST_FUNC_DEF(os, embedded_module, 1),
|
||||
MIST_FUNC_DEF(os, load_internal, 1),
|
||||
MIST_FUNC_DEF(os, internal_exists, 1),
|
||||
MIST_FUNC_DEF(os, print, 1),
|
||||
@@ -575,11 +643,12 @@ static const JSCFunctionListEntry js_os_funcs[] = {
|
||||
MIST_FUNC_DEF(os, getenv, 1),
|
||||
};
|
||||
|
||||
JSValue js_os_use(JSContext *js) {
|
||||
JSValue js_core_os_use(JSContext *js) {
|
||||
JS_NewClassID(&js_dylib_class_id);
|
||||
JS_NewClass(js, js_dylib_class_id, &js_dylib_class);
|
||||
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js,mod,js_os_funcs,countof(js_os_funcs));
|
||||
return mod;
|
||||
JS_FRAME(js);
|
||||
JS_ROOT(mod, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, mod.val, js_os_funcs, countof(js_os_funcs));
|
||||
JS_RETURN(mod.val);
|
||||
}
|
||||
|
||||
420
internal/shop.cm
420
internal/shop.cm
@@ -12,12 +12,63 @@ var pkg_tools = use('package')
|
||||
var os = use('os')
|
||||
var link = use('link')
|
||||
|
||||
var analyze = os.analyze
|
||||
var run_ast_fn = os.run_ast_fn
|
||||
var shop_json = os.json
|
||||
// These come from env (via core_extras in engine.cm):
|
||||
// analyze, run_ast_fn, core_json, use_cache, shop_path, actor_api, runtime_env,
|
||||
// content_hash, cache_path, ensure_build_dir
|
||||
var shop_json = core_json
|
||||
var global_shop_path = shop_path
|
||||
var my$_ = actor_api
|
||||
|
||||
var core = "core"
|
||||
|
||||
// Generate a random 5-letter uppercase identifier for package symbol naming.
|
||||
// Avoids collisions with existing IDs and "CORE".
|
||||
function generate_package_id() {
|
||||
var lock = Shop.load_lock()
|
||||
var existing = {}
|
||||
var keys = array(lock)
|
||||
var _i = 0
|
||||
while (_i < length(keys)) {
|
||||
if (lock[keys[_i]] && lock[keys[_i]].id)
|
||||
existing[lock[keys[_i]].id] = true
|
||||
_i = _i + 1
|
||||
}
|
||||
existing["CORE"] = true
|
||||
|
||||
var id = null
|
||||
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
var _j = 0
|
||||
while (true) {
|
||||
id = ""
|
||||
_j = 0
|
||||
while (_j < 5) {
|
||||
id = id + chars[abs(os.random()) % 26]
|
||||
_j = _j + 1
|
||||
}
|
||||
if (!existing[id]) return id
|
||||
}
|
||||
}
|
||||
|
||||
// Get the assigned ID for a package, generating one if needed.
|
||||
// Core always returns "core". Other packages get a random 5-letter ID
|
||||
// assigned lazily on first use and persisted in lock.toml.
|
||||
function get_package_id(pkg) {
|
||||
if (pkg == core) return core
|
||||
|
||||
var lock = Shop.load_lock()
|
||||
var entry = lock[pkg]
|
||||
if (entry && entry.id) return entry.id
|
||||
|
||||
var id = generate_package_id()
|
||||
if (!entry) {
|
||||
entry = {}
|
||||
lock[pkg] = entry
|
||||
}
|
||||
entry.id = id
|
||||
Shop.save_lock(lock)
|
||||
return id
|
||||
}
|
||||
|
||||
function pull_from_cache(content)
|
||||
{
|
||||
var path = hash_path(content)
|
||||
@@ -45,11 +96,6 @@ function ensure_dir(path) {
|
||||
}
|
||||
}
|
||||
|
||||
function content_hash(content)
|
||||
{
|
||||
return text(crypto.blake2(content), 'h')
|
||||
}
|
||||
|
||||
function hash_path(content)
|
||||
{
|
||||
return global_shop_path + '/build' + '/' + content_hash(content)
|
||||
@@ -66,9 +112,6 @@ var ACTOR_EXT = '.ce'
|
||||
|
||||
var dylib_ext = '.dylib' // Default extension
|
||||
|
||||
var use_cache = os.use_cache
|
||||
var global_shop_path = os.global_shop_path
|
||||
var my$_ = os.$_
|
||||
|
||||
Shop.get_package_dir = function(name) {
|
||||
return global_shop_path + '/packages/' + name
|
||||
@@ -270,11 +313,6 @@ function package_cache_path(pkg)
|
||||
return global_shop_path + '/cache/' + replace(replace(pkg, '/', '_'), '@', '_')
|
||||
}
|
||||
|
||||
function get_shared_lib_path()
|
||||
{
|
||||
return get_global_build_dir() + '/' + 'lib'
|
||||
}
|
||||
|
||||
// Load lock.toml configuration (from global shop)
|
||||
var _lock = null
|
||||
Shop.load_lock = function() {
|
||||
@@ -377,9 +415,33 @@ Shop.extract_commit_hash = function(pkg, response) {
|
||||
return null
|
||||
}
|
||||
|
||||
var dylib_visited = {}
|
||||
var open_dls = {}
|
||||
|
||||
// Host target detection for native dylib resolution
|
||||
function detect_host_target() {
|
||||
var platform = os.platform()
|
||||
var arch = os.arch ? os.arch() : 'arm64'
|
||||
if (platform == 'macOS' || platform == 'darwin')
|
||||
return arch == 'x86_64' ? 'macos_x86_64' : 'macos_arm64'
|
||||
if (platform == 'Linux' || platform == 'linux')
|
||||
return arch == 'x86_64' ? 'linux' : 'linux_arm64'
|
||||
if (platform == 'Windows' || platform == 'windows')
|
||||
return 'windows'
|
||||
return null
|
||||
}
|
||||
|
||||
var host_target = detect_host_target()
|
||||
|
||||
// Check for a native .cm dylib at the deterministic lib path
|
||||
// Returns the loaded module value, or null if no native dylib exists
|
||||
function try_native_mod_dylib(pkg, stem) {
|
||||
var dylib_path = get_dylib_path(pkg, stem)
|
||||
if (!fd.is_file(dylib_path)) return null
|
||||
var handle = os.dylib_open(dylib_path)
|
||||
if (!handle) return null
|
||||
return os.native_module_load(handle)
|
||||
}
|
||||
|
||||
// Default capabilities injected into scripts
|
||||
// These map to $_ properties in engine.cm
|
||||
var SHOP_DEFAULT_INJECT = ['$self', '$overling', '$clock', '$delay', '$start', '$receiver', '$contact', '$portal', '$time_limit', '$couple', '$stop', '$unneeded', '$connection', '$fd']
|
||||
@@ -404,9 +466,8 @@ Shop.get_script_capabilities = function(path) {
|
||||
// Matches engine.cm's approach: env properties become free variables in the module.
|
||||
function inject_env(inject) {
|
||||
var env = {}
|
||||
var rt = my$_.os ? my$_.os.runtime_env : null
|
||||
if (rt) {
|
||||
arrfor(array(rt), function(k) { env[k] = rt[k] })
|
||||
if (runtime_env) {
|
||||
arrfor(array(runtime_env), function(k) { env[k] = runtime_env[k] })
|
||||
}
|
||||
|
||||
// Add capability injections with $ prefix
|
||||
@@ -433,7 +494,9 @@ function resolve_mod_fn(path, pkg) {
|
||||
if (!fd.is_file(path)) { print(`path ${path} is not a file`); disrupt }
|
||||
|
||||
var content = text(fd.slurp(path))
|
||||
var cached = pull_from_cache(stone(blob(content)))
|
||||
var content_key = stone(blob(content))
|
||||
var native_result = null
|
||||
var cached = null
|
||||
var ast = null
|
||||
var compiled = null
|
||||
var mach_path = null
|
||||
@@ -441,26 +504,36 @@ function resolve_mod_fn(path, pkg) {
|
||||
var mcode_path = null
|
||||
var ir = null
|
||||
var optimized = null
|
||||
var mcode_json = null
|
||||
var cached_mcode_path = null
|
||||
var _pkg_dir = null
|
||||
var _stem = null
|
||||
|
||||
// Check for native .cm dylib at deterministic path first
|
||||
if (pkg) {
|
||||
_pkg_dir = get_packages_dir() + '/' + safe_package_path(pkg)
|
||||
if (starts_with(path, _pkg_dir + '/')) {
|
||||
_stem = fd.stem(text(path, length(_pkg_dir) + 1))
|
||||
native_result = try_native_mod_dylib(pkg, _stem)
|
||||
if (native_result != null) {
|
||||
return {_native: true, value: native_result}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check cache for pre-compiled .mach blob
|
||||
cached = pull_from_cache(content_key)
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
|
||||
// Check for pre-compiled .mach or .mcode file alongside .cm source
|
||||
if (ends_with(path, '.cm')) {
|
||||
mach_path = text(path, 0, length(path) - 3) + '.mach'
|
||||
if (fd.is_file(mach_path)) {
|
||||
mach_blob = fd.slurp(mach_path)
|
||||
put_into_cache(stone(blob(content)), mach_blob)
|
||||
return mach_blob
|
||||
}
|
||||
mcode_path = path + '.mcode'
|
||||
if (fd.is_file(mcode_path)) {
|
||||
compiled = mach_compile_mcode_bin(path, text(fd.slurp(mcode_path)))
|
||||
put_into_cache(stone(blob(content)), compiled)
|
||||
return compiled
|
||||
}
|
||||
// Check for cached mcode in content-addressed store (salted hash to distinguish from mach)
|
||||
cached_mcode_path = global_shop_path + '/build/' + content_hash(stone(blob(text(content_key) + "\nmcode")))
|
||||
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 via full pipeline: analyze → mcode → streamline → serialize
|
||||
@@ -469,8 +542,15 @@ function resolve_mod_fn(path, pkg) {
|
||||
ast = analyze(content, path)
|
||||
ir = _mcode_mod(ast)
|
||||
optimized = _streamline_mod(ir)
|
||||
compiled = mach_compile_mcode_bin(path, shop_json.encode(optimized))
|
||||
put_into_cache(stone(blob(content)), compiled)
|
||||
mcode_json = shop_json.encode(optimized)
|
||||
|
||||
// Cache mcode (architecture-independent) in content-addressed store
|
||||
ensure_dir(global_shop_path + '/build')
|
||||
fd.slurpwrite(cached_mcode_path, stone(blob(mcode_json)))
|
||||
|
||||
// Cache mach blob
|
||||
compiled = mach_compile_mcode_bin(path, mcode_json)
|
||||
put_into_cache(content_key, compiled)
|
||||
|
||||
return compiled
|
||||
}
|
||||
@@ -561,71 +641,47 @@ function resolve_locator(path, ctx)
|
||||
}
|
||||
|
||||
// Generate symbol name for a C module file
|
||||
// Uses the same format as Shop.c_symbol_for_file
|
||||
// Symbol names are based on canonical package names, not link targets
|
||||
// Uses the package's assigned ID (5-letter random or "core") instead of the full name
|
||||
function make_c_symbol(pkg, file) {
|
||||
var pkg_safe = replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
|
||||
var pkg_id = get_package_id(pkg)
|
||||
var file_safe = replace(replace(replace(file, '/', '_'), '.', '_'), '-', '_')
|
||||
return 'js_' + pkg_safe + '_' + file_safe + '_use'
|
||||
return 'js_' + pkg_id + '_' + file_safe + '_use'
|
||||
}
|
||||
|
||||
// Get the library path for a package in .cell/lib
|
||||
// Library names are based on canonical package names, not link targets
|
||||
function get_lib_path(pkg) {
|
||||
var lib_name = replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
|
||||
return global_shop_path + '/lib/' + lib_name + dylib_ext
|
||||
// Get the deterministic dylib path for a module in lib/<pkg>/<stem>.dylib
|
||||
function get_dylib_path(pkg, stem) {
|
||||
return global_shop_path + '/lib/' + safe_package_path(pkg) + '/' + stem + dylib_ext
|
||||
}
|
||||
|
||||
// Open a package's dynamic library and all its dependencies
|
||||
Shop.open_package_dylib = function(pkg) {
|
||||
if (pkg == 'core' || !pkg) return
|
||||
if (dylib_visited[pkg]) return
|
||||
dylib_visited[pkg] = true
|
||||
// Open a per-module dylib and return the dlopen handle
|
||||
function open_module_dylib(dylib_path) {
|
||||
if (open_dls[dylib_path]) return open_dls[dylib_path]
|
||||
if (!fd.is_file(dylib_path)) return null
|
||||
open_dls[dylib_path] = os.dylib_open(dylib_path)
|
||||
return open_dls[dylib_path]
|
||||
}
|
||||
|
||||
var link_target = link.get_target(pkg)
|
||||
var resolved_pkg = link_target ? link_target : pkg
|
||||
|
||||
var pkg_dir = null
|
||||
if (starts_with(resolved_pkg, '/')) {
|
||||
pkg_dir = resolved_pkg
|
||||
} else {
|
||||
pkg_dir = get_packages_dir() + '/' + safe_package_path(resolved_pkg)
|
||||
}
|
||||
|
||||
var toml_path = pkg_dir + '/cell.toml'
|
||||
var content = null
|
||||
var cfg = null
|
||||
if (fd.is_file(toml_path)) {
|
||||
content = text(fd.slurp(toml_path))
|
||||
cfg = toml.decode(content)
|
||||
if (cfg.dependencies) {
|
||||
arrfor(array(cfg.dependencies), function(alias, i) {
|
||||
var dep_pkg = cfg.dependencies[alias]
|
||||
Shop.open_package_dylib(dep_pkg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var dl_path = get_lib_path(pkg)
|
||||
if (fd.is_file(dl_path)) {
|
||||
if (!open_dls[dl_path]) {
|
||||
open_dls[dl_path] = os.dylib_open(dl_path)
|
||||
}
|
||||
}
|
||||
// Try to resolve a C symbol from the deterministic dylib path
|
||||
// Returns a loader function or null
|
||||
function try_dylib_symbol(sym, pkg, file_stem) {
|
||||
var dylib_path = get_dylib_path(pkg, file_stem)
|
||||
var handle = open_module_dylib(dylib_path)
|
||||
if (!handle) return null
|
||||
if (!os.dylib_has_symbol(handle, sym)) return null
|
||||
return function() { return os.dylib_symbol(handle, sym) }
|
||||
}
|
||||
|
||||
// Resolve a C symbol by searching:
|
||||
// 1. If package_context is null, only check core internal symbols
|
||||
// 2. Otherwise: own package (internal then dylib) -> other packages (internal then dylib) -> core (internal only)
|
||||
// Core is never loaded as a dynamic library via dlopen
|
||||
// At each scope: check lib/ dylib first, then internal (static)
|
||||
function resolve_c_symbol(path, package_context) {
|
||||
var explicit = split_explicit_package_import(path)
|
||||
var sym = null
|
||||
var dl_path = null
|
||||
var loader = null
|
||||
var _path = null
|
||||
var core_sym = null
|
||||
var canon_pkg = null
|
||||
var mod_name = null
|
||||
var file_stem = null
|
||||
|
||||
if (explicit) {
|
||||
if (is_internal_path(explicit.path) && package_context && explicit.package != package_context)
|
||||
@@ -633,6 +689,20 @@ function resolve_c_symbol(path, package_context) {
|
||||
}
|
||||
if (explicit) {
|
||||
sym = make_c_symbol(explicit.package, explicit.path)
|
||||
file_stem = replace(explicit.path, '.c', '')
|
||||
|
||||
// Check lib/ dylib first
|
||||
loader = try_dylib_symbol(sym, explicit.package, file_stem)
|
||||
if (loader) {
|
||||
return {
|
||||
symbol: loader,
|
||||
scope: SCOPE_PACKAGE,
|
||||
package: explicit.package,
|
||||
path: sym
|
||||
}
|
||||
}
|
||||
|
||||
// Then check internal/static
|
||||
if (os.internal_exists(sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(sym) },
|
||||
@@ -641,23 +711,22 @@ function resolve_c_symbol(path, package_context) {
|
||||
path: sym
|
||||
}
|
||||
}
|
||||
|
||||
Shop.open_package_dylib(explicit.package)
|
||||
dl_path = get_lib_path(explicit.package)
|
||||
if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) {
|
||||
return {
|
||||
symbol: function() { return os.dylib_symbol(open_dls[dl_path], sym) },
|
||||
scope: SCOPE_PACKAGE,
|
||||
package: explicit.package,
|
||||
path: sym
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no package context, only check core internal symbols
|
||||
// If no package context, only check core
|
||||
if (!package_context || package_context == 'core') {
|
||||
_path = replace(path, '/', '_')
|
||||
core_sym = `js_${_path}_use`
|
||||
core_sym = make_c_symbol('core', path)
|
||||
|
||||
// Check lib/ dylib first for core
|
||||
loader = try_dylib_symbol(core_sym, 'core', path)
|
||||
if (loader) {
|
||||
return {
|
||||
symbol: loader,
|
||||
scope: SCOPE_CORE,
|
||||
path: core_sym
|
||||
}
|
||||
}
|
||||
|
||||
if (os.internal_exists(core_sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(core_sym) },
|
||||
@@ -668,22 +737,21 @@ function resolve_c_symbol(path, package_context) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 1. Check own package first (internal, then dylib)
|
||||
// 1. Check own package (dylib first, then internal)
|
||||
sym = make_c_symbol(package_context, path)
|
||||
if (os.internal_exists(sym)) {
|
||||
|
||||
loader = try_dylib_symbol(sym, package_context, path)
|
||||
if (loader) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(sym) },
|
||||
symbol: loader,
|
||||
scope: SCOPE_LOCAL,
|
||||
path: sym
|
||||
}
|
||||
}
|
||||
|
||||
Shop.open_package_dylib(package_context)
|
||||
dl_path = get_lib_path(package_context)
|
||||
|
||||
if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) {
|
||||
if (os.internal_exists(sym)) {
|
||||
return {
|
||||
symbol: function() { return os.dylib_symbol(open_dls[dl_path], sym) },
|
||||
symbol: function() { return os.load_internal(sym) },
|
||||
scope: SCOPE_LOCAL,
|
||||
path: sym
|
||||
}
|
||||
@@ -700,7 +768,16 @@ function resolve_c_symbol(path, package_context) {
|
||||
mod_name = get_import_name(path)
|
||||
sym = make_c_symbol(canon_pkg, mod_name)
|
||||
|
||||
// Check internal first
|
||||
loader = try_dylib_symbol(sym, canon_pkg, mod_name)
|
||||
if (loader) {
|
||||
return {
|
||||
symbol: loader,
|
||||
scope: SCOPE_PACKAGE,
|
||||
package: canon_pkg,
|
||||
path: sym
|
||||
}
|
||||
}
|
||||
|
||||
if (os.internal_exists(sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(sym) },
|
||||
@@ -709,23 +786,21 @@ function resolve_c_symbol(path, package_context) {
|
||||
path: sym
|
||||
}
|
||||
}
|
||||
|
||||
// Then check dylib
|
||||
Shop.open_package_dylib(canon_pkg)
|
||||
dl_path = get_lib_path(canon_pkg)
|
||||
if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) {
|
||||
return {
|
||||
symbol: function() { return os.dylib_symbol(open_dls[dl_path], sym) },
|
||||
scope: SCOPE_PACKAGE,
|
||||
package: canon_pkg,
|
||||
path: sym
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Check core internal symbols (core is never a dynamic library)
|
||||
core_sym = `js_${replace(path, '/', '_')}_use`
|
||||
// 3. Check core (dylib first, then internal)
|
||||
core_sym = make_c_symbol('core', path)
|
||||
|
||||
loader = try_dylib_symbol(core_sym, 'core', path)
|
||||
if (loader) {
|
||||
return {
|
||||
symbol: loader,
|
||||
scope: SCOPE_CORE,
|
||||
path: core_sym
|
||||
}
|
||||
}
|
||||
|
||||
if (os.internal_exists(core_sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(core_sym) },
|
||||
@@ -841,20 +916,20 @@ function execute_module(info)
|
||||
var pkg = null
|
||||
|
||||
if (mod_resolve.scope < 900) {
|
||||
// Build env with runtime fns, capabilities, and use function
|
||||
file_info = Shop.file_info(mod_resolve.path)
|
||||
inject = Shop.script_inject_for(file_info)
|
||||
env = inject_env(inject)
|
||||
pkg = file_info.package
|
||||
env.use = make_use_fn(pkg)
|
||||
// Check if native dylib was resolved
|
||||
if (is_object(mod_resolve.symbol) && mod_resolve.symbol._native) {
|
||||
used = mod_resolve.symbol.value
|
||||
} else {
|
||||
// Build env with runtime fns, capabilities, and use function
|
||||
file_info = Shop.file_info(mod_resolve.path)
|
||||
inject = Shop.script_inject_for(file_info)
|
||||
env = inject_env(inject)
|
||||
pkg = file_info.package
|
||||
env.use = make_use_fn(pkg)
|
||||
|
||||
// Add C module as native context if available
|
||||
if (c_resolve.scope < 900) {
|
||||
env.native = call_c_module(c_resolve)
|
||||
// Load compiled bytecode with env
|
||||
used = mach_load(mod_resolve.symbol, env)
|
||||
}
|
||||
|
||||
// Load compiled bytecode with env
|
||||
used = mach_load(mod_resolve.symbol, env)
|
||||
} else if (c_resolve.scope < 900) {
|
||||
// C only
|
||||
used = call_c_module(c_resolve)
|
||||
@@ -876,6 +951,21 @@ function get_module(path, package_context) {
|
||||
}
|
||||
|
||||
Shop.use = function use(path, package_context) {
|
||||
// Check for embedded module (static builds)
|
||||
var embed_key = 'embedded:' + path
|
||||
var embedded = null
|
||||
var embed_env = null
|
||||
if (use_cache[embed_key]) return use_cache[embed_key]
|
||||
if (os.embedded_module) {
|
||||
embedded = os.embedded_module(path)
|
||||
if (embedded) {
|
||||
embed_env = inject_env(SHOP_DEFAULT_INJECT)
|
||||
embed_env.use = make_use_fn(package_context)
|
||||
use_cache[embed_key] = mach_load(embedded, embed_env)
|
||||
return use_cache[embed_key]
|
||||
}
|
||||
}
|
||||
|
||||
var info = resolve_module_info(path, package_context)
|
||||
if (!info) { print(`Module ${path} could not be found in ${package_context}`); disrupt }
|
||||
|
||||
@@ -1229,26 +1319,38 @@ Shop.file_reload = function(file)
|
||||
|
||||
Shop.module_reload = function(path, package) {
|
||||
if (!Shop.is_loaded(path,package)) return
|
||||
|
||||
|
||||
// Clear the module info cache for this path
|
||||
var lookup_key = package ? package + ':' + path : ':' + path
|
||||
module_info_cache[lookup_key] = null
|
||||
|
||||
|
||||
// Close old dylib handle if any
|
||||
var old_dylib_path = null
|
||||
if (package) {
|
||||
old_dylib_path = get_dylib_path(package, path)
|
||||
if (open_dls[old_dylib_path]) {
|
||||
os.dylib_close(open_dls[old_dylib_path])
|
||||
open_dls[old_dylib_path] = null
|
||||
}
|
||||
}
|
||||
|
||||
var info = resolve_module_info(path, package)
|
||||
if (!info) return
|
||||
|
||||
var cache_key = info.cache_key
|
||||
var old = use_cache[cache_key]
|
||||
use_cache[cache_key] = null
|
||||
|
||||
var newmod = get_module(path, package)
|
||||
use_cache[cache_key] = newmod
|
||||
|
||||
arrfor(array(newmod), function(i, idx) {
|
||||
old[i] = newmod[i]
|
||||
})
|
||||
|
||||
arrfor(array(old), function(i, idx) {
|
||||
if (!(i in newmod))
|
||||
old[i] = null
|
||||
})
|
||||
if (old && is_object(old) && is_object(newmod)) {
|
||||
arrfor(array(newmod), function(k) { old[k] = newmod[k] })
|
||||
arrfor(array(old), function(k) {
|
||||
if (!(k in newmod)) old[k] = null
|
||||
})
|
||||
use_cache[cache_key] = old
|
||||
}
|
||||
}
|
||||
|
||||
function get_package_scripts(package)
|
||||
@@ -1305,28 +1407,38 @@ Shop.get_package_dir = function(pkg) {
|
||||
}
|
||||
|
||||
// Generate C symbol name for a file within a package
|
||||
// e.g., c_symbol_for_file('gitea.pockle.world/john/prosperon', 'sprite.c')
|
||||
// -> 'js_gitea_pockle_world_john_prosperon_sprite_use'
|
||||
// Uses the package's assigned ID (e.g., "WAWOF") instead of the full name
|
||||
// e.g., c_symbol_for_file('gitea.pockle.world/john/prosperon', 'sprite.c')
|
||||
// -> 'js_WAWOF_sprite_use'
|
||||
Shop.c_symbol_for_file = function(pkg, file) {
|
||||
var pkg_safe = replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
|
||||
var pkg_id = get_package_id(pkg)
|
||||
var file_safe = replace(replace(fd.stem(file), '/', '_'), '.', '_')
|
||||
return 'js_' + pkg_safe + '_' + file_safe + '_use'
|
||||
var suffix = ends_with(file, '.ce') ? '_program' : '_use'
|
||||
return 'js_' + pkg_id + '_' + file_safe + suffix
|
||||
}
|
||||
|
||||
// Generate C symbol prefix for a package
|
||||
// e.g., c_symbol_prefix('gitea.pockle.world/john/prosperon') -> 'js_gitea_pockle_world_john_prosperon_'
|
||||
// e.g., c_symbol_prefix('gitea.pockle.world/john/prosperon') -> 'js_WAWOF_'
|
||||
Shop.c_symbol_prefix = function(pkg) {
|
||||
var pkg_safe = replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
|
||||
return 'js_' + pkg_safe + '_'
|
||||
var pkg_id = get_package_id(pkg)
|
||||
return 'js_' + pkg_id + '_'
|
||||
}
|
||||
|
||||
// Get the library name for a package (without extension)
|
||||
// e.g., 'gitea.pockle.world/john/prosperon' -> 'gitea_pockle_world_john_prosperon'
|
||||
// Get the library name for a package
|
||||
// e.g., 'gitea.pockle.world/john/prosperon' -> 'WAWOF'
|
||||
Shop.lib_name_for_package = function(pkg) {
|
||||
return replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
|
||||
return get_package_id(pkg)
|
||||
}
|
||||
|
||||
// Get the assigned package ID (public API)
|
||||
Shop.get_package_id = get_package_id
|
||||
|
||||
// Returns { ok: bool, results: [{pkg, ok, error}] }
|
||||
// Get the deterministic dylib path for a module (public API)
|
||||
Shop.get_dylib_path = function(pkg, stem) {
|
||||
return get_dylib_path(pkg, stem)
|
||||
}
|
||||
|
||||
Shop.audit_packages = function() {
|
||||
var packages = Shop.list_packages()
|
||||
|
||||
|
||||
@@ -69,18 +69,13 @@ static const JSCFunctionListEntry js_time_funcs[] = {
|
||||
};
|
||||
|
||||
JSValue
|
||||
js_internal_time_use(JSContext *ctx)
|
||||
js_core_internal_time_use(JSContext *ctx)
|
||||
{
|
||||
JSValue obj = JS_NewObject(ctx);
|
||||
JS_SetPropertyFunctionList(ctx, obj,
|
||||
JS_FRAME(ctx);
|
||||
JS_ROOT(mod, JS_NewObject(ctx));
|
||||
JS_SetPropertyFunctionList(ctx, mod.val,
|
||||
js_time_funcs,
|
||||
sizeof(js_time_funcs) /
|
||||
sizeof(js_time_funcs[0]));
|
||||
return obj;
|
||||
}
|
||||
|
||||
JSValue
|
||||
js_time_use(JSContext *ctx)
|
||||
{
|
||||
return js_internal_time_use(ctx);
|
||||
JS_RETURN(mod.val);
|
||||
}
|
||||
113
link.ce
113
link.ce
@@ -17,6 +17,24 @@ var shop = use('internal/shop')
|
||||
var fd = use('fd')
|
||||
var toml = use('toml')
|
||||
|
||||
var links = null
|
||||
var count = 0
|
||||
var result = null
|
||||
var i = 0
|
||||
var pkg = null
|
||||
var cmd = null
|
||||
var pkg_name = null
|
||||
var target = null
|
||||
var start_idx = 0
|
||||
var arg1 = null
|
||||
var arg2 = null
|
||||
var cwd = null
|
||||
var toml_path = null
|
||||
var content = null
|
||||
var _restore = null
|
||||
var _read_toml = null
|
||||
var _add_link = null
|
||||
|
||||
if (length(args) < 1) {
|
||||
log.console("Usage: link <command> [args] or link [package] <target>")
|
||||
log.console("Commands:")
|
||||
@@ -27,154 +45,149 @@ if (length(args) < 1) {
|
||||
log.console(" <path> Link the package in <path> to that path")
|
||||
log.console(" <package> <target> Link <package> to <target> (path or package)")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
var cmd = args[0]
|
||||
cmd = args[0]
|
||||
|
||||
if (cmd == 'list') {
|
||||
var links = link.load()
|
||||
var count = 0
|
||||
links = link.load()
|
||||
count = 0
|
||||
arrfor(array(links), function(k) {
|
||||
log.console(k + " -> " + links[k])
|
||||
count++
|
||||
})
|
||||
if (count == 0) log.console("No links.")
|
||||
|
||||
|
||||
} else if (cmd == 'sync') {
|
||||
log.console("Syncing links...")
|
||||
var result = link.sync_all(shop)
|
||||
result = link.sync_all(shop)
|
||||
log.console("Synced " + result.synced + " link(s)")
|
||||
if (length(result.errors) > 0) {
|
||||
log.console("Errors:")
|
||||
for (var i = 0; i < length(result.errors); i++) {
|
||||
for (i = 0; i < length(result.errors); i++) {
|
||||
log.console(" " + result.errors[i])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else if (cmd == 'delete' || cmd == 'rm') {
|
||||
if (length(args) < 2) {
|
||||
log.console("Usage: link delete <package>")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
var pkg = args[1]
|
||||
|
||||
|
||||
pkg = args[1]
|
||||
|
||||
var _restore = null
|
||||
if (link.remove(pkg)) {
|
||||
// Try to restore the original package
|
||||
log.console("Restoring " + pkg + "...")
|
||||
try {
|
||||
_restore = function() {
|
||||
shop.fetch(pkg)
|
||||
shop.extract(pkg)
|
||||
log.console("Restored " + pkg)
|
||||
} catch (e) {
|
||||
log.console("Could not restore: " + e.message)
|
||||
} disruption {
|
||||
log.console("Could not restore")
|
||||
log.console("Run 'cell update " + pkg + "' to restore")
|
||||
}
|
||||
_restore()
|
||||
} else {
|
||||
log.console("No link found for " + pkg)
|
||||
}
|
||||
|
||||
|
||||
} else if (cmd == 'clear') {
|
||||
link.clear()
|
||||
log.console("Links cleared. Run 'cell update' to restore packages.")
|
||||
|
||||
|
||||
} else {
|
||||
// Linking logic
|
||||
var pkg_name = null
|
||||
var target = null
|
||||
|
||||
pkg_name = null
|
||||
target = null
|
||||
|
||||
// Check for 'add' compatibility
|
||||
var start_idx = 0
|
||||
start_idx = 0
|
||||
if (cmd == 'add') {
|
||||
start_idx = 1
|
||||
}
|
||||
|
||||
var arg1 = args[start_idx]
|
||||
var arg2 = (length(args) > start_idx + 1) ? args[start_idx + 1] : null
|
||||
|
||||
|
||||
arg1 = args[start_idx]
|
||||
arg2 = (length(args) > start_idx + 1) ? args[start_idx + 1] : null
|
||||
|
||||
if (!arg1) {
|
||||
log.console("Error: target or package required")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
if (arg2) {
|
||||
// Two arguments: explicit package name and target
|
||||
pkg_name = arg1
|
||||
target = arg2
|
||||
|
||||
|
||||
// Resolve target if it's a local path
|
||||
if (target == '.' || fd.is_dir(target)) {
|
||||
target = fd.realpath(target)
|
||||
} else if (starts_with(target, './') || starts_with(target, '../')) {
|
||||
// Relative path that doesn't exist yet - try to resolve anyway
|
||||
var cwd = fd.realpath('.')
|
||||
cwd = fd.realpath('.')
|
||||
if (starts_with(target, './')) {
|
||||
target = cwd + text(target, 1)
|
||||
} else {
|
||||
// For ../ paths, var fd.realpath handle it if possible
|
||||
// For ../ paths, let fd.realpath handle it if possible
|
||||
target = fd.realpath(target) || target
|
||||
}
|
||||
}
|
||||
// Otherwise target is a package name (e.g., github.com/prosperon)
|
||||
|
||||
|
||||
} else {
|
||||
// One argument: assume it's a local path, infer package name from cell.toml
|
||||
target = arg1
|
||||
|
||||
|
||||
// Resolve path
|
||||
if (target == '.' || fd.is_dir(target)) {
|
||||
target = fd.realpath(target)
|
||||
} else if (starts_with(target, './') || starts_with(target, '../')) {
|
||||
target = fd.realpath(target) || target
|
||||
}
|
||||
|
||||
|
||||
// Must be a local path with cell.toml
|
||||
var toml_path = target + '/cell.toml'
|
||||
toml_path = target + '/cell.toml'
|
||||
if (!fd.is_file(toml_path)) {
|
||||
log.console("Error: No cell.toml found at " + target)
|
||||
log.console("For linking to another package, use: link <package> <target>")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Read package name from cell.toml
|
||||
try {
|
||||
var content = toml.decode(text(fd.slurp(toml_path)))
|
||||
_read_toml = function() {
|
||||
content = toml.decode(text(fd.slurp(toml_path)))
|
||||
if (content.package) {
|
||||
pkg_name = content.package
|
||||
} else {
|
||||
log.console("Error: cell.toml at " + target + " does not define 'package'")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
log.console("Error reading cell.toml: " + e)
|
||||
} disruption {
|
||||
log.console("Error reading cell.toml")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
_read_toml()
|
||||
}
|
||||
|
||||
|
||||
// Validate: if target is a local path, it must have cell.toml
|
||||
if (starts_with(target, '/')) {
|
||||
if (!fd.is_file(target + '/cell.toml')) {
|
||||
log.console("Error: " + target + " is not a valid package (no cell.toml)")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add the link (this also creates the symlink)
|
||||
try {
|
||||
_add_link = function() {
|
||||
link.add(pkg_name, target, shop)
|
||||
} catch (e) {
|
||||
log.console("Error: " + e.message)
|
||||
log.error(e)
|
||||
} disruption {
|
||||
log.console("Error adding link")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
_add_link()
|
||||
}
|
||||
|
||||
$stop()
|
||||
$stop()
|
||||
|
||||
48
list.ce
48
list.ce
@@ -12,6 +12,13 @@ var fd = use('fd')
|
||||
|
||||
var mode = 'local'
|
||||
var target_pkg = null
|
||||
var resolved = null
|
||||
var i = 0
|
||||
var deps = null
|
||||
var packages = null
|
||||
var local_pkgs = null
|
||||
var linked_pkgs = null
|
||||
var remote_pkgs = null
|
||||
|
||||
if (args && length(args) > 0) {
|
||||
if (args[0] == 'shop') {
|
||||
@@ -32,7 +39,7 @@ if (args && length(args) > 0) {
|
||||
|
||||
// Resolve local paths
|
||||
if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) {
|
||||
var resolved = fd.realpath(target_pkg)
|
||||
resolved = fd.realpath(target_pkg)
|
||||
if (resolved) {
|
||||
target_pkg = resolved
|
||||
}
|
||||
@@ -44,21 +51,23 @@ var links = link.load()
|
||||
var lock = shop.load_lock()
|
||||
|
||||
function print_deps(ctx, indent) {
|
||||
var aliases = null
|
||||
indent = indent || ""
|
||||
var deps
|
||||
try {
|
||||
deps = null
|
||||
var _read = function() {
|
||||
deps = pkg.dependencies(ctx)
|
||||
} catch (e) {
|
||||
} disruption {
|
||||
log.console(indent + " (could not read dependencies)")
|
||||
return
|
||||
}
|
||||
_read()
|
||||
|
||||
if (!deps) {
|
||||
log.console(indent + " (none)")
|
||||
return
|
||||
}
|
||||
|
||||
var aliases = array(deps)
|
||||
aliases = array(deps)
|
||||
aliases = sort(aliases)
|
||||
|
||||
if (length(aliases) == 0) {
|
||||
@@ -66,19 +75,26 @@ function print_deps(ctx, indent) {
|
||||
return
|
||||
}
|
||||
|
||||
for (var i = 0; i < length(aliases); i++) {
|
||||
var alias = aliases[i]
|
||||
var locator = deps[alias]
|
||||
var link_target = links[locator]
|
||||
var lock_entry = lock[locator]
|
||||
var j = 0
|
||||
var alias = null
|
||||
var locator = null
|
||||
var link_target = null
|
||||
var lock_entry = null
|
||||
var line = null
|
||||
var status = null
|
||||
for (j = 0; j < length(aliases); j++) {
|
||||
alias = aliases[j]
|
||||
locator = deps[alias]
|
||||
link_target = links[locator]
|
||||
lock_entry = lock[locator]
|
||||
|
||||
var line = indent + " " + alias
|
||||
line = indent + " " + alias
|
||||
if (alias != locator) {
|
||||
line += " -> " + locator
|
||||
}
|
||||
|
||||
// Add status indicators
|
||||
var status = []
|
||||
status = []
|
||||
if (link_target) {
|
||||
push(status, "linked -> " + link_target)
|
||||
}
|
||||
@@ -110,16 +126,16 @@ if (mode == 'local') {
|
||||
log.console("Shop packages:")
|
||||
log.console("")
|
||||
|
||||
var packages = shop.list_packages()
|
||||
packages = shop.list_packages()
|
||||
if (length(packages) == 0) {
|
||||
log.console(" (none)")
|
||||
} else {
|
||||
packages = sort(packages)
|
||||
|
||||
// Group by type
|
||||
var local_pkgs = []
|
||||
var linked_pkgs = []
|
||||
var remote_pkgs = []
|
||||
local_pkgs = []
|
||||
linked_pkgs = []
|
||||
remote_pkgs = []
|
||||
|
||||
arrfor(packages, function(p) {
|
||||
if (p == 'core') return
|
||||
|
||||
5
ls.ce
5
ls.ce
@@ -9,13 +9,14 @@ var ctx = null
|
||||
var pkg = args[0] || package.find_package_dir('.')
|
||||
var modules = package.list_modules(pkg)
|
||||
var programs = package.list_programs(pkg)
|
||||
var i = 0
|
||||
|
||||
log.console("Modules in " + pkg + ":")
|
||||
modules = sort(modules)
|
||||
if (length(modules) == 0) {
|
||||
log.console(" (none)")
|
||||
} else {
|
||||
for (var i = 0; i < length(modules); i++) {
|
||||
for (i = 0; i < length(modules); i++) {
|
||||
log.console(" " + modules[i])
|
||||
}
|
||||
}
|
||||
@@ -26,7 +27,7 @@ programs = sort(programs)
|
||||
if (length(programs) == 0) {
|
||||
log.console(" (none)")
|
||||
} else {
|
||||
for (var i = 0; i < length(programs); i++) {
|
||||
for (i = 0; i < length(programs); i++) {
|
||||
log.console(" " + programs[i])
|
||||
}
|
||||
}
|
||||
|
||||
142
mcode.cm
142
mcode.cm
@@ -28,6 +28,13 @@ var mcode = function(ast) {
|
||||
"<<=": "shl", ">>=": "shr", ">>>=": "ushr"
|
||||
}
|
||||
|
||||
var sensory_ops = {
|
||||
is_array: "is_array", is_function: "is_func", is_object: "is_record",
|
||||
is_stone: "is_stone", is_integer: "is_int", is_text: "is_text",
|
||||
is_number: "is_num", is_logical: "is_bool", is_null: "is_null",
|
||||
length: "length"
|
||||
}
|
||||
|
||||
// Compiler state
|
||||
var s_instructions = null
|
||||
var s_data = null
|
||||
@@ -273,19 +280,70 @@ var mcode = function(ast) {
|
||||
return node.kind == "null"
|
||||
}
|
||||
|
||||
// emit_add_decomposed: emit generic add (VM dispatches int/float/text)
|
||||
// emit_add_decomposed: emit type-dispatched add (text → concat, num → add)
|
||||
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
|
||||
var emit_add_decomposed = function() {
|
||||
// Known text+text → concat directly (skip numeric check in VM)
|
||||
if (is_known_text(_bp_ln) && is_known_text(_bp_rn)) {
|
||||
emit_3("concat", _bp_dest, _bp_left, _bp_right)
|
||||
return null
|
||||
}
|
||||
if (is_known_number(_bp_ln) && is_known_number(_bp_rn)) {
|
||||
emit_3("add", _bp_dest, _bp_left, _bp_right)
|
||||
return null
|
||||
}
|
||||
// Unknown types: emit full dispatch
|
||||
var t0 = alloc_slot()
|
||||
var t1 = alloc_slot()
|
||||
var done = gen_label("add_done")
|
||||
var check_num = gen_label("add_cn")
|
||||
|
||||
// Check text path first (since add doubles as concat)
|
||||
emit_2("is_text", t0, _bp_left)
|
||||
emit_jump_cond("jump_false", t0, check_num)
|
||||
emit_2("is_text", t1, _bp_right)
|
||||
emit_jump_cond("jump_false", t1, check_num)
|
||||
emit_3("concat", _bp_dest, _bp_left, _bp_right)
|
||||
emit_jump(done)
|
||||
|
||||
// Numeric path
|
||||
var err = gen_label("add_err")
|
||||
emit_label(check_num)
|
||||
emit_2("is_num", t0, _bp_left)
|
||||
emit_jump_cond("jump_false", t0, err)
|
||||
emit_2("is_num", t1, _bp_right)
|
||||
emit_jump_cond("jump_false", t1, err)
|
||||
emit_3("add", _bp_dest, _bp_left, _bp_right)
|
||||
emit_jump(done)
|
||||
|
||||
emit_label(err)
|
||||
emit_0("disrupt")
|
||||
emit_label(done)
|
||||
return null
|
||||
}
|
||||
|
||||
// emit_numeric_binop removed — generic ops emitted directly via passthrough
|
||||
// emit_numeric_binop: emit type-guarded numeric binary op
|
||||
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
|
||||
var emit_numeric_binop = function(op_str) {
|
||||
if (is_known_number(_bp_ln) && is_known_number(_bp_rn)) {
|
||||
emit_3(op_str, _bp_dest, _bp_left, _bp_right)
|
||||
return null
|
||||
}
|
||||
var t0 = alloc_slot()
|
||||
var t1 = alloc_slot()
|
||||
var err = gen_label("num_err")
|
||||
var done = gen_label("num_done")
|
||||
emit_2("is_num", t0, _bp_left)
|
||||
emit_jump_cond("jump_false", t0, err)
|
||||
emit_2("is_num", t1, _bp_right)
|
||||
emit_jump_cond("jump_false", t1, err)
|
||||
emit_3(op_str, _bp_dest, _bp_left, _bp_right)
|
||||
emit_jump(done)
|
||||
|
||||
emit_label(err)
|
||||
emit_0("disrupt")
|
||||
emit_label(done)
|
||||
return null
|
||||
}
|
||||
|
||||
// emit_eq_decomposed: identical -> int -> float -> text -> null -> bool -> mismatch(false)
|
||||
// reads _bp_dest, _bp_left, _bp_right from closure
|
||||
@@ -511,15 +569,36 @@ var mcode = function(ast) {
|
||||
return null
|
||||
}
|
||||
|
||||
// emit_neg_decomposed: emit generic negate (VM dispatches int/float)
|
||||
// emit_neg_decomposed: emit type-guarded negate
|
||||
var emit_neg_decomposed = function(dest, src, src_node) {
|
||||
if (is_known_number(src_node)) {
|
||||
emit_2("negate", dest, src)
|
||||
return null
|
||||
}
|
||||
var t0 = alloc_slot()
|
||||
var err = gen_label("neg_err")
|
||||
var done = gen_label("neg_done")
|
||||
emit_2("is_num", t0, src)
|
||||
emit_jump_cond("jump_false", t0, err)
|
||||
emit_2("negate", dest, src)
|
||||
emit_jump(done)
|
||||
|
||||
emit_label(err)
|
||||
emit_0("disrupt")
|
||||
emit_label(done)
|
||||
return null
|
||||
}
|
||||
|
||||
// Central router: maps op string to decomposition helper
|
||||
// Sets _bp_* closure vars then calls helper with reduced args
|
||||
var relational_ops = {
|
||||
lt: ["lt_int", "lt_float", "lt_text"],
|
||||
le: ["le_int", "le_float", "le_text"],
|
||||
gt: ["gt_int", "gt_float", "gt_text"],
|
||||
ge: ["ge_int", "ge_float", "ge_text"]
|
||||
}
|
||||
var emit_binop = function(op_str, dest, left, right) {
|
||||
var rel = null
|
||||
_bp_dest = dest
|
||||
_bp_left = left
|
||||
_bp_right = right
|
||||
@@ -529,18 +608,17 @@ var mcode = function(ast) {
|
||||
emit_eq_decomposed()
|
||||
} else if (op_str == "ne") {
|
||||
emit_ne_decomposed()
|
||||
} else if (op_str == "lt") {
|
||||
emit_relational("lt_int", "lt_float", "lt_text")
|
||||
} else if (op_str == "le") {
|
||||
emit_relational("le_int", "le_float", "le_text")
|
||||
} else if (op_str == "gt") {
|
||||
emit_relational("gt_int", "gt_float", "gt_text")
|
||||
} else if (op_str == "ge") {
|
||||
emit_relational("ge_int", "ge_float", "ge_text")
|
||||
} else {
|
||||
// Passthrough for subtract, multiply, divide, modulo,
|
||||
// bitwise, pow, in, etc.
|
||||
emit_3(op_str, dest, left, right)
|
||||
rel = relational_ops[op_str]
|
||||
if (rel != null) {
|
||||
emit_relational(rel[0], rel[1], rel[2])
|
||||
} else if (op_str == "subtract" || op_str == "multiply" ||
|
||||
op_str == "divide" || op_str == "modulo" || op_str == "pow") {
|
||||
emit_numeric_binop(op_str)
|
||||
} else {
|
||||
// Passthrough for bitwise, in, etc.
|
||||
emit_3(op_str, dest, left, right)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -1670,37 +1748,11 @@ var mcode = function(ast) {
|
||||
fname = callee.name
|
||||
nargs = args_list != null ? length(args_list) : 0
|
||||
// 1-arg type check intrinsics → direct opcode
|
||||
if (nargs == 1) {
|
||||
if (fname == "is_array" || fname == "is_function" ||
|
||||
fname == "is_object" || fname == "is_stone" ||
|
||||
fname == "is_integer" || fname == "is_text" ||
|
||||
fname == "is_number" || fname == "is_logical" ||
|
||||
fname == "is_null" || fname == "length") {
|
||||
if (nargs == 1 && sensory_ops[fname] != null) {
|
||||
a0 = gen_expr(args_list[0], -1)
|
||||
d = alloc_slot()
|
||||
if (fname == "is_array") {
|
||||
emit_2("is_array", d, a0)
|
||||
} else if (fname == "is_function") {
|
||||
emit_2("is_func", d, a0)
|
||||
} else if (fname == "is_object") {
|
||||
emit_2("is_record", d, a0)
|
||||
} else if (fname == "is_stone") {
|
||||
emit_2("is_stone", d, a0)
|
||||
} else if (fname == "is_integer") {
|
||||
emit_2("is_int", d, a0)
|
||||
} else if (fname == "is_text") {
|
||||
emit_2("is_text", d, a0)
|
||||
} else if (fname == "is_number") {
|
||||
emit_2("is_num", d, a0)
|
||||
} else if (fname == "is_logical") {
|
||||
emit_2("is_bool", d, a0)
|
||||
} else if (fname == "is_null") {
|
||||
emit_2("is_null", d, a0)
|
||||
} else if (fname == "length") {
|
||||
emit_2("length", d, a0)
|
||||
}
|
||||
emit_2(sensory_ops[fname], d, a0)
|
||||
return d
|
||||
}
|
||||
}
|
||||
// 2-arg push: push(arr, val) → guarded direct opcode
|
||||
if (nargs == 2 && fname == "push") {
|
||||
@@ -1930,7 +1982,7 @@ var mcode = function(ast) {
|
||||
_i = _i + 1
|
||||
}
|
||||
dest = alloc_slot()
|
||||
add_instr(["array", dest, 0])
|
||||
add_instr(["array", dest, count])
|
||||
_i = 0
|
||||
while (_i < count) {
|
||||
emit_2("push", dest, elem_slots[_i])
|
||||
@@ -1943,7 +1995,7 @@ var mcode = function(ast) {
|
||||
if (kind == "record") {
|
||||
list = expr.list
|
||||
dest = alloc_slot()
|
||||
push(s_instructions, ["record", dest, 0])
|
||||
push(s_instructions, ["record", dest, length(list)])
|
||||
_i = 0
|
||||
while (_i < length(list)) {
|
||||
pair = list[_i]
|
||||
|
||||
12
meson.build
12
meson.build
@@ -18,6 +18,14 @@ add_project_arguments(
|
||||
)
|
||||
add_project_arguments('-Wno-narrowing', language: 'cpp')
|
||||
|
||||
if get_option('validate_gc')
|
||||
add_project_arguments('-DVALIDATE_GC', language: 'c')
|
||||
endif
|
||||
|
||||
if get_option('force_gc')
|
||||
add_project_arguments('-DFORCE_GC_AT_MALLOC', language: 'c')
|
||||
endif
|
||||
|
||||
deps = []
|
||||
|
||||
if host_machine.system() == 'darwin'
|
||||
@@ -59,10 +67,10 @@ scripts = [
|
||||
'fit.c',
|
||||
'crypto.c',
|
||||
'internal/kim.c',
|
||||
'time.c',
|
||||
'internal/time.c',
|
||||
'debug/debug.c',
|
||||
'internal/os.c',
|
||||
'fd.c',
|
||||
'internal/fd.c',
|
||||
'net/http.c',
|
||||
'net/enet.c',
|
||||
'wildstar.c',
|
||||
|
||||
4
meson.options
Normal file
4
meson.options
Normal file
@@ -0,0 +1,4 @@
|
||||
option('validate_gc', type: 'boolean', value: false,
|
||||
description: 'Enable GC validation checks (stale pointer detection, pre-GC frame validation)')
|
||||
option('force_gc', type: 'boolean', value: false,
|
||||
description: 'Force GC on every allocation (makes stale pointer bugs deterministic)')
|
||||
22
net/enet.c
22
net/enet.c
@@ -568,21 +568,23 @@ static const JSCFunctionListEntry js_enet_peer_funcs[] = {
|
||||
// JS_CGETSET_DEF("address", js_enet_peer_get_address, NULL),
|
||||
};
|
||||
|
||||
JSValue js_enet_use(JSContext *ctx)
|
||||
JSValue js_core_enet_use(JSContext *ctx)
|
||||
{
|
||||
JS_FRAME(ctx);
|
||||
|
||||
JS_NewClassID(&enet_host_id);
|
||||
JS_NewClass(ctx, enet_host_id, &enet_host);
|
||||
JSValue host_proto = JS_NewObject(ctx);
|
||||
JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs));
|
||||
JS_SetClassProto(ctx, enet_host_id, host_proto);
|
||||
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);
|
||||
JSValue peer_proto = JS_NewObject(ctx);
|
||||
JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs));
|
||||
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto);
|
||||
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);
|
||||
|
||||
JSValue export_obj = JS_NewObject(ctx);
|
||||
JS_SetPropertyFunctionList(ctx, export_obj, js_enet_funcs, countof(js_enet_funcs));
|
||||
return export_obj;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -318,10 +318,11 @@ static const JSCFunctionListEntry js_http_funcs[] = {
|
||||
JS_CFUNC_DEF("fetch", 2, js_fetch_picoparser),
|
||||
};
|
||||
|
||||
JSValue js_http_use(JSContext *js) {
|
||||
JSValue js_core_http_use(JSContext *js) {
|
||||
JS_FRAME(js);
|
||||
par_easycurl_init(0); // Initialize platform HTTP backend
|
||||
JSValue obj = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, obj, js_http_funcs,
|
||||
JS_ROOT(mod, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, mod.val, js_http_funcs,
|
||||
sizeof(js_http_funcs)/sizeof(js_http_funcs[0]));
|
||||
return obj;
|
||||
JS_RETURN(mod.val);
|
||||
}
|
||||
|
||||
45
net/socket.c
45
net/socket.c
@@ -594,27 +594,28 @@ static const JSCFunctionListEntry js_socket_funcs[] = {
|
||||
MIST_FUNC_DEF(socket, close, 1),
|
||||
};
|
||||
|
||||
JSValue js_socket_use(JSContext *js) {
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_socket_funcs, countof(js_socket_funcs));
|
||||
|
||||
JSValue js_core_socket_use(JSContext *js) {
|
||||
JS_FRAME(js);
|
||||
JS_ROOT(mod, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, mod.val, js_socket_funcs, countof(js_socket_funcs));
|
||||
|
||||
// Add constants
|
||||
JS_SetPropertyStr(js, mod, "AF_UNSPEC", JS_NewInt32(js, AF_UNSPEC));
|
||||
JS_SetPropertyStr(js, mod, "AF_INET", JS_NewInt32(js, AF_INET));
|
||||
JS_SetPropertyStr(js, mod, "AF_INET6", JS_NewInt32(js, AF_INET6));
|
||||
JS_SetPropertyStr(js, mod, "AF_UNIX", JS_NewInt32(js, AF_UNIX));
|
||||
|
||||
JS_SetPropertyStr(js, mod, "SOCK_STREAM", JS_NewInt32(js, SOCK_STREAM));
|
||||
JS_SetPropertyStr(js, mod, "SOCK_DGRAM", JS_NewInt32(js, SOCK_DGRAM));
|
||||
|
||||
JS_SetPropertyStr(js, mod, "AI_PASSIVE", JS_NewInt32(js, AI_PASSIVE));
|
||||
|
||||
JS_SetPropertyStr(js, mod, "SHUT_RD", JS_NewInt32(js, SHUT_RD));
|
||||
JS_SetPropertyStr(js, mod, "SHUT_WR", JS_NewInt32(js, SHUT_WR));
|
||||
JS_SetPropertyStr(js, mod, "SHUT_RDWR", JS_NewInt32(js, SHUT_RDWR));
|
||||
|
||||
JS_SetPropertyStr(js, mod, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET));
|
||||
JS_SetPropertyStr(js, mod, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR));
|
||||
|
||||
return mod;
|
||||
JS_SetPropertyStr(js, mod.val, "AF_UNSPEC", JS_NewInt32(js, AF_UNSPEC));
|
||||
JS_SetPropertyStr(js, mod.val, "AF_INET", JS_NewInt32(js, AF_INET));
|
||||
JS_SetPropertyStr(js, mod.val, "AF_INET6", JS_NewInt32(js, AF_INET6));
|
||||
JS_SetPropertyStr(js, mod.val, "AF_UNIX", JS_NewInt32(js, AF_UNIX));
|
||||
|
||||
JS_SetPropertyStr(js, mod.val, "SOCK_STREAM", JS_NewInt32(js, SOCK_STREAM));
|
||||
JS_SetPropertyStr(js, mod.val, "SOCK_DGRAM", JS_NewInt32(js, SOCK_DGRAM));
|
||||
|
||||
JS_SetPropertyStr(js, mod.val, "AI_PASSIVE", JS_NewInt32(js, AI_PASSIVE));
|
||||
|
||||
JS_SetPropertyStr(js, mod.val, "SHUT_RD", JS_NewInt32(js, SHUT_RD));
|
||||
JS_SetPropertyStr(js, mod.val, "SHUT_WR", JS_NewInt32(js, SHUT_WR));
|
||||
JS_SetPropertyStr(js, mod.val, "SHUT_RDWR", JS_NewInt32(js, SHUT_RDWR));
|
||||
|
||||
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_RETURN(mod.val);
|
||||
}
|
||||
|
||||
16
pack.ce
16
pack.ce
@@ -13,6 +13,7 @@ var target = null
|
||||
var output_name = 'app'
|
||||
var target_package = null
|
||||
var buildtype = 'debug'
|
||||
var i = 0
|
||||
|
||||
if (length(args) < 1) {
|
||||
log.error('Usage: cell pack <package> [options]')
|
||||
@@ -24,12 +25,11 @@ if (length(args) < 1) {
|
||||
log.error('')
|
||||
log.error('Available targets: ' + text(build.list_targets(), ', '))
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
target_package = args[0]
|
||||
|
||||
for (var i = 1; i < length(args); i++) {
|
||||
for (i = 1; i < length(args); i++) {
|
||||
if (args[i] == '-t' || args[i] == '--target') {
|
||||
if (i + 1 < length(args)) {
|
||||
target = args[++i]
|
||||
@@ -87,7 +87,7 @@ if (target && !build.has_target(target)) {
|
||||
var packages = ['core']
|
||||
var deps = pkg_tools.gather_dependencies(target_package)
|
||||
|
||||
for (var i = 0; i < length(deps); i++) {
|
||||
for (i = 0; i < length(deps); i++) {
|
||||
push(packages, deps[i])
|
||||
}
|
||||
push(packages, target_package)
|
||||
@@ -95,7 +95,7 @@ push(packages, target_package)
|
||||
// Remove duplicates
|
||||
var unique_packages = []
|
||||
var seen = {}
|
||||
for (var i = 0; i < length(packages); i++) {
|
||||
for (i = 0; i < length(packages); i++) {
|
||||
if (!seen[packages[i]]) {
|
||||
seen[packages[i]] = true
|
||||
push(unique_packages, packages[i])
|
||||
@@ -111,13 +111,13 @@ arrfor(packages, function(package) {
|
||||
|
||||
log.console('Building static binary from ' + text(length(packages)) + ' packages: ' + text(packages, ', '))
|
||||
|
||||
try {
|
||||
var _build = function() {
|
||||
var result = build.build_static(packages, target, output_name, buildtype)
|
||||
log.console('Build complete: ' + result)
|
||||
} catch (e) {
|
||||
log.error('Build failed: ')
|
||||
log.error(e)
|
||||
} disruption {
|
||||
log.error('Build failed')
|
||||
$stop()
|
||||
}
|
||||
_build()
|
||||
|
||||
$stop()
|
||||
|
||||
15
parse.cm
15
parse.cm
@@ -1,6 +1,11 @@
|
||||
var parse = function(tokens, src, filename, tokenizer) {
|
||||
var _src_len = length(src)
|
||||
|
||||
var template_escape_map = {
|
||||
n: "\n", t: "\t", r: "\r", "\\": "\\",
|
||||
"`": "`", "$": "$", "0": character(0)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Parser Cursor
|
||||
// ============================================================
|
||||
@@ -175,6 +180,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
var tc = null
|
||||
var tq = null
|
||||
var esc_ch = null
|
||||
var esc_val = null
|
||||
var expr_tokens = null
|
||||
var sub_ast = null
|
||||
var sub_stmt = null
|
||||
@@ -223,13 +229,8 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
while (tvi < tvlen) {
|
||||
if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
|
||||
esc_ch = tv[tvi + 1]
|
||||
if (esc_ch == "n") { push(fmt_parts, "\n") }
|
||||
else if (esc_ch == "t") { push(fmt_parts, "\t") }
|
||||
else if (esc_ch == "r") { push(fmt_parts, "\r") }
|
||||
else if (esc_ch == "\\") { push(fmt_parts, "\\") }
|
||||
else if (esc_ch == "`") { push(fmt_parts, "`") }
|
||||
else if (esc_ch == "$") { push(fmt_parts, "$") }
|
||||
else if (esc_ch == "0") { push(fmt_parts, character(0)) }
|
||||
esc_val = template_escape_map[esc_ch]
|
||||
if (esc_val != null) { push(fmt_parts, esc_val) }
|
||||
else { push(fmt_parts, esc_ch) }
|
||||
tvi = tvi + 2
|
||||
} else if (tv[tvi] == "$" && tvi + 1 < tvlen && tv[tvi + 1] == "{") {
|
||||
|
||||
116
prettify_mcode.ce
Normal file
116
prettify_mcode.ce
Normal file
@@ -0,0 +1,116 @@
|
||||
// prettify_mcode.ce — reformat .mcode files to be human-readable
|
||||
// Usage: ./cell --dev prettify_mcode boot/tokenize.cm.mcode
|
||||
// ./cell --dev prettify_mcode boot/*.mcode
|
||||
|
||||
var fd = use("fd")
|
||||
var json = use("json")
|
||||
|
||||
if (length(args) == 0) {
|
||||
print("usage: cell prettify_mcode <file.mcode> [...]")
|
||||
disrupt
|
||||
}
|
||||
|
||||
// Collapse leaf arrays (instruction arrays) onto single lines
|
||||
var compact_arrays = function(json_text) {
|
||||
var lines = array(json_text, "\n")
|
||||
var result = []
|
||||
var i = 0
|
||||
var line = null
|
||||
var trimmed = null
|
||||
var collecting = false
|
||||
var collected = null
|
||||
var indent = null
|
||||
var is_leaf = null
|
||||
var j = 0
|
||||
var inner = null
|
||||
var parts = null
|
||||
var trailing = null
|
||||
var chars = null
|
||||
var k = 0
|
||||
|
||||
while (i < length(lines)) {
|
||||
line = lines[i]
|
||||
trimmed = trim(line)
|
||||
if (collecting == false && trimmed == "[") {
|
||||
collecting = true
|
||||
chars = array(line)
|
||||
k = 0
|
||||
while (k < length(chars) && chars[k] == " ") {
|
||||
k = k + 1
|
||||
}
|
||||
indent = text(line, 0, k)
|
||||
collected = []
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if (collecting) {
|
||||
if (trimmed == "]" || trimmed == "],") {
|
||||
is_leaf = true
|
||||
j = 0
|
||||
while (j < length(collected)) {
|
||||
inner = trim(collected[j])
|
||||
if (starts_with(inner, "[") || starts_with(inner, "{")) {
|
||||
is_leaf = false
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
if (is_leaf && length(collected) > 0) {
|
||||
parts = []
|
||||
j = 0
|
||||
while (j < length(collected)) {
|
||||
inner = trim(collected[j])
|
||||
if (ends_with(inner, ",")) {
|
||||
inner = text(inner, 0, length(inner) - 1)
|
||||
}
|
||||
parts[] = inner
|
||||
j = j + 1
|
||||
}
|
||||
trailing = ""
|
||||
if (ends_with(trimmed, ",")) {
|
||||
trailing = ","
|
||||
}
|
||||
result[] = `${indent}[${text(parts, ", ")}]${trailing}`
|
||||
} else {
|
||||
result[] = `${indent}[`
|
||||
j = 0
|
||||
while (j < length(collected)) {
|
||||
result[] = collected[j]
|
||||
j = j + 1
|
||||
}
|
||||
result[] = line
|
||||
}
|
||||
collecting = false
|
||||
} else {
|
||||
collected[] = line
|
||||
}
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
result[] = line
|
||||
i = i + 1
|
||||
}
|
||||
return text(result, "\n")
|
||||
}
|
||||
|
||||
var i = 0
|
||||
var path = null
|
||||
var raw = null
|
||||
var obj = null
|
||||
var pretty = null
|
||||
var f = null
|
||||
while (i < length(args)) {
|
||||
path = args[i]
|
||||
if (!fd.is_file(path)) {
|
||||
print(`skip ${path} (not found)`)
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
raw = text(fd.slurp(path))
|
||||
obj = json.decode(raw)
|
||||
pretty = compact_arrays(json.encode(obj, null, 2))
|
||||
f = fd.open(path, "w")
|
||||
fd.write(f, pretty)
|
||||
fd.close(f)
|
||||
print(`prettified ${path}`)
|
||||
i = i + 1
|
||||
}
|
||||
356
qbe.cm
356
qbe.cm
@@ -98,6 +98,7 @@ var is_text = function(p, v) {
|
||||
jmp @${p}.done
|
||||
@${p}.no
|
||||
%${p} =w copy 0
|
||||
jmp @${p}.done
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
@@ -174,6 +175,7 @@ var to_float64 = function(p, v) {
|
||||
%${p}.fbits =l or %${p}.fs63, %${p}.fe52
|
||||
%${p}.fbits =l or %${p}.fbits, %${p}.fmant
|
||||
%${p} =d cast %${p}.fbits
|
||||
jmp @${p}.done
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
@@ -199,201 +201,37 @@ var new_bool = function(p, b) {
|
||||
|
||||
// new_float64 — C call to __JS_NewFloat64(ctx, val). Result: %{p}
|
||||
var new_float64 = function(p, ctx, d) {
|
||||
return ` %${p} =l call $__JS_NewFloat64(l ${ctx}, d ${d})
|
||||
return ` %${p} =l call $qbe_new_float64(l ${ctx}, d ${d})
|
||||
`
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Arithmetic — add(p, ctx, a, b)
|
||||
// Int fast path inline, text concat and float as C calls.
|
||||
// Jumps to @disrupt on type mismatch.
|
||||
// Arithmetic — add/sub/mul/div/mod(p, ctx, a, b)
|
||||
// Simple C call wrappers. Type dispatch is handled in mcode.cm.
|
||||
// ============================================================
|
||||
|
||||
var add = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.sum =l add %${p}.ia, %${p}.ib
|
||||
%${p}.lo =w csltl %${p}.sum, ${int32_min}
|
||||
%${p}.hi =w csgtl %${p}.sum, ${int32_max}
|
||||
%${p}.ov =w or %${p}.lo, %${p}.hi
|
||||
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
|
||||
@${p}.int_ok
|
||||
%${p}.rw =w copy %${p}.sum
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.int_overflow
|
||||
%${p}.fd =d sltof %${p}.sum
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_text =w call $JS_IsText(l ${a})
|
||||
%${p}.b_is_text =w call $JS_IsText(l ${b})
|
||||
%${p}.both_text =w and %${p}.a_is_text, %${p}.b_is_text
|
||||
jnz %${p}.both_text, @${p}.text_path, @${p}.chk_num
|
||||
@${p}.text_path
|
||||
%${p} =l call $JS_ConcatString(l ${ctx}, l ${a}, l ${b})
|
||||
jmp @${p}.done
|
||||
@${p}.chk_num
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_add(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_add(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var sub = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.diff =l sub %${p}.ia, %${p}.ib
|
||||
%${p}.lo =w csltl %${p}.diff, ${int32_min}
|
||||
%${p}.hi =w csgtl %${p}.diff, ${int32_max}
|
||||
%${p}.ov =w or %${p}.lo, %${p}.hi
|
||||
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
|
||||
@${p}.int_ok
|
||||
%${p}.rw =w copy %${p}.diff
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.int_overflow
|
||||
%${p}.fd =d sltof %${p}.diff
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_sub(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_sub(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var mul = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.prod =l mul %${p}.ia, %${p}.ib
|
||||
%${p}.lo =w csltl %${p}.prod, ${int32_min}
|
||||
%${p}.hi =w csgtl %${p}.prod, ${int32_max}
|
||||
%${p}.ov =w or %${p}.lo, %${p}.hi
|
||||
jnz %${p}.ov, @${p}.int_overflow, @${p}.int_ok
|
||||
@${p}.int_ok
|
||||
%${p}.rw =w copy %${p}.prod
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.int_overflow
|
||||
%${p}.fd =d sltof %${p}.prod
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_mul(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_mul(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var div = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =w copy 0
|
||||
%${p}.tmp =l sar ${a}, 1
|
||||
%${p}.ia =w copy %${p}.tmp
|
||||
%${p}.ib =w copy 0
|
||||
%${p}.tmp2 =l sar ${b}, 1
|
||||
%${p}.ib =w copy %${p}.tmp2
|
||||
%${p}.div0 =w ceqw %${p}.ib, 0
|
||||
jnz %${p}.div0, @${p}.ret_null, @${p}.chk_exact
|
||||
@${p}.ret_null
|
||||
%${p} =l copy ${js_null}
|
||||
jmp @${p}.done
|
||||
@${p}.chk_exact
|
||||
%${p}.rem =w rem %${p}.ia, %${p}.ib
|
||||
%${p}.exact =w ceqw %${p}.rem, 0
|
||||
jnz %${p}.exact, @${p}.int_div, @${p}.int_to_float
|
||||
@${p}.int_div
|
||||
%${p}.q =w div %${p}.ia, %${p}.ib
|
||||
%${p}.qext =l extuw %${p}.q
|
||||
%${p} =l shl %${p}.qext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.int_to_float
|
||||
%${p}.da =d swtof %${p}.ia
|
||||
%${p}.db =d swtof %${p}.ib
|
||||
%${p}.dr =d div %${p}.da, %${p}.db
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.dr)
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_div(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_div(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var mod = function(p, ctx, a, b) {
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.not_both_int, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =w copy 0
|
||||
%${p}.tmp =l sar ${a}, 1
|
||||
%${p}.ia =w copy %${p}.tmp
|
||||
%${p}.ib =w copy 0
|
||||
%${p}.tmp2 =l sar ${b}, 1
|
||||
%${p}.ib =w copy %${p}.tmp2
|
||||
%${p}.div0 =w ceqw %${p}.ib, 0
|
||||
jnz %${p}.div0, @${p}.ret_null, @${p}.do_mod
|
||||
@${p}.ret_null
|
||||
%${p} =l copy ${js_null}
|
||||
jmp @${p}.done
|
||||
@${p}.do_mod
|
||||
%${p}.r =w rem %${p}.ia, %${p}.ib
|
||||
%${p}.rext =l extuw %${p}.r
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.not_both_int
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_path, @disrupt
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_mod(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_mod(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
@@ -484,6 +322,7 @@ var cmp = function(p, ctx, a, b) {
|
||||
jmp @${p}.done
|
||||
@${p}.mismatch
|
||||
%${p} =l copy ${mismatch_val}
|
||||
jmp @${p}.done
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
@@ -518,90 +357,28 @@ var gt = function(p, ctx, a, b) {
|
||||
|
||||
var ge = function(p, ctx, a, b) {
|
||||
_qflags = {int_cmp_op: "csgew", float_id: 5, is_eq: false, is_ne: false, null_true: true}
|
||||
return cmp(p, ctx, a, b)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Unary Ops
|
||||
// ============================================================
|
||||
|
||||
// neg(p, ctx, v) — negate. Int fast path (INT32_MIN edge case), else C call.
|
||||
// neg(p, ctx, v) — negate via C call (type guards in mcode)
|
||||
var neg = function(p, ctx, v) {
|
||||
return `@${p}.start
|
||||
%${p}.tag =l and ${v}, 1
|
||||
%${p}.is_int =w ceql %${p}.tag, 0
|
||||
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
|
||||
@${p}.int_path
|
||||
%${p}.sl =l sar ${v}, 1
|
||||
%${p}.iw =w copy %${p}.sl
|
||||
%${p}.is_min =w ceqw %${p}.iw, ${int32_min}
|
||||
jnz %${p}.is_min, @${p}.min_overflow, @${p}.int_ok
|
||||
@${p}.min_overflow
|
||||
%${p}.fd =d swtof %${p}.iw
|
||||
%${p}.fdn =d neg %${p}.fd
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fdn)
|
||||
jmp @${p}.done
|
||||
@${p}.int_ok
|
||||
%${p}.ni =w sub 0, %${p}.iw
|
||||
%${p}.niext =l extuw %${p}.ni
|
||||
%${p} =l shl %${p}.niext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_neg(l ${ctx}, l ${v})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_neg(l ${ctx}, l ${v})
|
||||
`
|
||||
}
|
||||
|
||||
// inc(p, ctx, v) — increment. Int fast path (INT32_MAX edge case), else C call.
|
||||
// inc(p, ctx, v) — increment via C call (type guards in mcode)
|
||||
var inc = function(p, ctx, v) {
|
||||
return `@${p}.start
|
||||
%${p}.tag =l and ${v}, 1
|
||||
%${p}.is_int =w ceql %${p}.tag, 0
|
||||
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
|
||||
@${p}.int_path
|
||||
%${p}.sl =l sar ${v}, 1
|
||||
%${p}.iw =w copy %${p}.sl
|
||||
%${p}.is_max =w ceqw %${p}.iw, ${int32_max}
|
||||
jnz %${p}.is_max, @${p}.max_overflow, @${p}.int_ok
|
||||
@${p}.max_overflow
|
||||
%${p}.fd =d swtof %${p}.iw
|
||||
%${p}.fd1 =d add %${p}.fd, d_1.0
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd1)
|
||||
jmp @${p}.done
|
||||
@${p}.int_ok
|
||||
%${p}.ni =w add %${p}.iw, 1
|
||||
%${p}.niext =l extuw %${p}.ni
|
||||
%${p} =l shl %${p}.niext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_inc(l ${ctx}, l ${v})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_inc(l ${ctx}, l ${v})
|
||||
`
|
||||
}
|
||||
|
||||
// dec(p, ctx, v) — decrement. Int fast path (INT32_MIN edge case), else C call.
|
||||
// dec(p, ctx, v) — decrement via C call (type guards in mcode)
|
||||
var dec = function(p, ctx, v) {
|
||||
return `@${p}.start
|
||||
%${p}.tag =l and ${v}, 1
|
||||
%${p}.is_int =w ceql %${p}.tag, 0
|
||||
jnz %${p}.is_int, @${p}.int_path, @${p}.float_path
|
||||
@${p}.int_path
|
||||
%${p}.sl =l sar ${v}, 1
|
||||
%${p}.iw =w copy %${p}.sl
|
||||
%${p}.is_min =w ceqw %${p}.iw, ${int32_min}
|
||||
jnz %${p}.is_min, @${p}.min_overflow, @${p}.int_ok
|
||||
@${p}.min_overflow
|
||||
%${p}.fd =d swtof %${p}.iw
|
||||
%${p}.fd1 =d sub %${p}.fd, d_1.0
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd1)
|
||||
jmp @${p}.done
|
||||
@${p}.int_ok
|
||||
%${p}.ni =w sub %${p}.iw, 1
|
||||
%${p}.niext =l extuw %${p}.ni
|
||||
%${p} =l shl %${p}.niext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.float_path
|
||||
%${p} =l call $qbe_float_dec(l ${ctx}, l ${v})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_float_dec(l ${ctx}, l ${v})
|
||||
`
|
||||
}
|
||||
|
||||
@@ -615,22 +392,9 @@ var lnot = function(p, ctx, v) {
|
||||
`
|
||||
}
|
||||
|
||||
// bnot(p, ctx, v) — bitwise not. Convert to int32, ~, re-tag.
|
||||
// bnot(p, ctx, v) — bitwise not via C call
|
||||
var bnot = function(p, ctx, v) {
|
||||
return `@${p}.start
|
||||
%${p}.tag =l and ${v}, 1
|
||||
%${p}.is_int =w ceql %${p}.tag, 0
|
||||
jnz %${p}.is_int, @${p}.int_path, @${p}.slow_path
|
||||
@${p}.int_path
|
||||
%${p}.sl =l sar ${v}, 1
|
||||
%${p}.iw =w copy %${p}.sl
|
||||
%${p}.nw =w xor %${p}.iw, -1
|
||||
%${p}.nex =l extuw %${p}.nw
|
||||
%${p} =l shl %${p}.nex, 1
|
||||
jmp @${p}.done
|
||||
@${p}.slow_path
|
||||
%${p} =l call $qbe_bnot(l ${ctx}, l ${v})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_bnot(l ${ctx}, l ${v})
|
||||
`
|
||||
}
|
||||
|
||||
@@ -639,92 +403,34 @@ var bnot = function(p, ctx, v) {
|
||||
// Both operands must be numeric. Int fast path, float -> convert to int32.
|
||||
// ============================================================
|
||||
|
||||
// reads _qop from closure
|
||||
var bitwise_op = function(p, ctx, a, b) {
|
||||
var qbe_op = _qop
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.slow_path, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.iaw =w copy %${p}.ia
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.ibw =w copy %${p}.ib
|
||||
%${p}.rw =w ${qbe_op} %${p}.iaw, %${p}.ibw
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.slow_path
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_to_int, @disrupt
|
||||
@${p}.float_to_int
|
||||
%${p} =l call $qbe_bitwise_${qbe_op}(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
var band = function(p, ctx, a, b) {
|
||||
return ` %${p} =l call $qbe_bitwise_and(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var band = function(p, ctx, a, b) {
|
||||
_qop = "and"
|
||||
return bitwise_op(p, ctx, a, b)
|
||||
}
|
||||
|
||||
var bor = function(p, ctx, a, b) {
|
||||
_qop = "or"
|
||||
return bitwise_op(p, ctx, a, b)
|
||||
return ` %${p} =l call $qbe_bitwise_or(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var bxor = function(p, ctx, a, b) {
|
||||
_qop = "xor"
|
||||
return bitwise_op(p, ctx, a, b)
|
||||
}
|
||||
|
||||
// Shift ops: mask shift amount to 5 bits (& 31)
|
||||
// reads _qop from closure
|
||||
var shift_op = function(p, ctx, a, b) {
|
||||
var qbe_op = _qop
|
||||
return `@${p}.start
|
||||
%${p}.at =l and ${a}, 1
|
||||
%${p}.bt =l and ${b}, 1
|
||||
%${p}.not_int =l or %${p}.at, %${p}.bt
|
||||
jnz %${p}.not_int, @${p}.slow_path, @${p}.int_path
|
||||
@${p}.int_path
|
||||
%${p}.ia =l sar ${a}, 1
|
||||
%${p}.iaw =w copy %${p}.ia
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.ibw =w copy %${p}.ib
|
||||
%${p}.sh =w and %${p}.ibw, 31
|
||||
%${p}.rw =w ${qbe_op} %${p}.iaw, %${p}.sh
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.slow_path
|
||||
%${p}.a_is_num =w call $JS_IsNumber(l ${a})
|
||||
%${p}.b_is_num =w call $JS_IsNumber(l ${b})
|
||||
%${p}.both_num =w and %${p}.a_is_num, %${p}.b_is_num
|
||||
jnz %${p}.both_num, @${p}.float_to_int, @disrupt
|
||||
@${p}.float_to_int
|
||||
%${p} =l call $qbe_shift_${qbe_op}(l ${ctx}, l ${a}, l ${b})
|
||||
@${p}.done
|
||||
return ` %${p} =l call $qbe_bitwise_xor(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var shl = function(p, ctx, a, b) {
|
||||
_qop = "shl"
|
||||
return shift_op(p, ctx, a, b)
|
||||
return ` %${p} =l call $qbe_shift_shl(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var shr = function(p, ctx, a, b) {
|
||||
_qop = "sar"
|
||||
return shift_op(p, ctx, a, b)
|
||||
return ` %${p} =l call $qbe_shift_sar(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var ushr = function(p, ctx, a, b) {
|
||||
_qop = "shr"
|
||||
return shift_op(p, ctx, a, b)
|
||||
return ` %${p} =l call $qbe_shift_shr(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
3201
qbe.cm.mcode
3201
qbe.cm.mcode
File diff suppressed because it is too large
Load Diff
262
qbe_emit.cm
262
qbe_emit.cm
@@ -3,7 +3,7 @@
|
||||
// a complete QBE IL program ready for the qbe compiler.
|
||||
// qbe module is passed via env as 'qbe'
|
||||
|
||||
var qbe_emit = function(ir, qbe) {
|
||||
var qbe_emit = function(ir, qbe, export_name) {
|
||||
var out = []
|
||||
var data_out = []
|
||||
var str_table = {}
|
||||
@@ -76,7 +76,8 @@ var qbe_emit = function(ir, qbe) {
|
||||
var instrs = fn.instructions
|
||||
var nr_slots = fn.nr_slots
|
||||
var nr_args = fn.nr_args
|
||||
var name = is_main ? "cell_main" : "cell_fn_" + text(fn_idx)
|
||||
var captured = build_captured(fn)
|
||||
var name = is_main ? (export_name ? export_name : "cell_main") : "cell_fn_" + text(fn_idx)
|
||||
name = sanitize(name)
|
||||
var i = 0
|
||||
var instr = null
|
||||
@@ -88,6 +89,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
var p = null
|
||||
var pn = null
|
||||
var sl = null
|
||||
var lbl = null
|
||||
var fop_id = 0
|
||||
var nr_elems = 0
|
||||
var ei = 0
|
||||
@@ -113,22 +115,45 @@ var qbe_emit = function(ir, qbe) {
|
||||
emit(` storel ${s(slot)}, %p${text(slot)}`)
|
||||
}
|
||||
|
||||
// Reload captured slots from frame (after invoke, closures may have modified them)
|
||||
var reload_captured = function() {
|
||||
var ri = 0
|
||||
while (ri < nr_slots) {
|
||||
if (captured[text(ri)] == true) {
|
||||
emit(` ${s(ri)} =l loadl %p${text(ri)}`)
|
||||
}
|
||||
ri = ri + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Walk instructions
|
||||
// Slot loads above are not terminators
|
||||
var last_was_term = false
|
||||
i = 0
|
||||
while (i < length(instrs)) {
|
||||
instr = instrs[i]
|
||||
i = i + 1
|
||||
|
||||
// Labels are plain strings
|
||||
// Labels are plain strings; skip _nop_ur_ pseudo-labels from streamline
|
||||
if (is_text(instr)) {
|
||||
emit("@" + sanitize(instr))
|
||||
if (starts_with(instr, "_nop_ur_")) continue
|
||||
lbl = sanitize(instr)
|
||||
if (!last_was_term) {
|
||||
emit(` jmp @${lbl}`)
|
||||
}
|
||||
emit("@" + lbl)
|
||||
last_was_term = false
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip dead code: non-label instructions after a terminator are unreachable
|
||||
if (last_was_term) continue
|
||||
|
||||
op = instr[0]
|
||||
a1 = instr[1]
|
||||
a2 = instr[2]
|
||||
a3 = instr[3]
|
||||
last_was_term = false
|
||||
|
||||
// --- Constants ---
|
||||
|
||||
@@ -157,11 +182,11 @@ var qbe_emit = function(ir, qbe) {
|
||||
if (is_integer(a2)) {
|
||||
emit(` ${s(a1)} =l copy ${text(a2 * 2)}`)
|
||||
} else {
|
||||
emit(` ${s(a1)} =l call $__JS_NewFloat64(l %ctx, d d_${text(a2)})`)
|
||||
emit(` ${s(a1)} =l call $qbe_new_float64(l %ctx, d d_${text(a2)})`)
|
||||
}
|
||||
} else if (is_text(a2)) {
|
||||
sl = intern_str(a2)
|
||||
emit(` ${s(a1)} =l call $JS_NewString(l %ctx, l ${sl})`)
|
||||
emit(` ${s(a1)} =l call $qbe_new_string(l %ctx, l ${sl})`)
|
||||
} else if (is_object(a2)) {
|
||||
if (a2.make == "intrinsic") {
|
||||
sl = intern_str(a2.name)
|
||||
@@ -170,13 +195,13 @@ var qbe_emit = function(ir, qbe) {
|
||||
if (a2.number != null && is_integer(a2.number)) {
|
||||
emit(` ${s(a1)} =l copy ${text(a2.number * 2)}`)
|
||||
} else if (a2.number != null) {
|
||||
emit(` ${s(a1)} =l call $__JS_NewFloat64(l %ctx, d d_${text(a2.number)})`)
|
||||
emit(` ${s(a1)} =l call $qbe_new_float64(l %ctx, d d_${text(a2.number)})`)
|
||||
} else {
|
||||
emit(` ${s(a1)} =l copy ${text(qbe.js_null)}`)
|
||||
}
|
||||
} else if (a2.kind == "text") {
|
||||
sl = intern_str(a2.value)
|
||||
emit(` ${s(a1)} =l call $JS_NewString(l %ctx, l ${sl})`)
|
||||
emit(` ${s(a1)} =l call $qbe_new_string(l %ctx, l ${sl})`)
|
||||
} else if (a2.kind == "true") {
|
||||
emit(` ${s(a1)} =l copy ${text(qbe.js_true)}`)
|
||||
} else if (a2.kind == "false") {
|
||||
@@ -205,7 +230,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
|
||||
if (op == "add") {
|
||||
p = fresh()
|
||||
emit(qbe.add(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` %${p} =l call $cell_rt_add(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
@@ -246,6 +271,12 @@ var qbe_emit = function(ir, qbe) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (op == "pow") {
|
||||
emit(` ${s(a1)} =l call $qbe_float_pow(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
// --- String concat ---
|
||||
|
||||
if (op == "concat") {
|
||||
@@ -305,6 +336,46 @@ var qbe_emit = function(ir, qbe) {
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "is_array") {
|
||||
p = fresh()
|
||||
emit(` %${p} =w call $JS_IsArray(l ${s(a2)})`)
|
||||
emit(qbe.new_bool(p + ".r", "%" + p))
|
||||
emit(` ${s(a1)} =l copy %${p}.r`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "is_func") {
|
||||
p = fresh()
|
||||
emit(` %${p} =w call $JS_IsFunction(l ${s(a2)})`)
|
||||
emit(qbe.new_bool(p + ".r", "%" + p))
|
||||
emit(` ${s(a1)} =l copy %${p}.r`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "is_record") {
|
||||
p = fresh()
|
||||
emit(` %${p} =w call $JS_IsRecord(l ${s(a2)})`)
|
||||
emit(qbe.new_bool(p + ".r", "%" + p))
|
||||
emit(` ${s(a1)} =l copy %${p}.r`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "is_stone") {
|
||||
p = fresh()
|
||||
emit(` %${p} =w call $JS_IsStone(l ${s(a2)})`)
|
||||
emit(qbe.new_bool(p + ".r", "%" + p))
|
||||
emit(` ${s(a1)} =l copy %${p}.r`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "is_proxy") {
|
||||
p = fresh()
|
||||
emit(` %${p} =w call $cell_rt_is_proxy(l %ctx, l ${s(a2)})`)
|
||||
emit(qbe.new_bool(p + ".r", "%" + p))
|
||||
emit(` ${s(a1)} =l copy %${p}.r`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
// --- Comparisons (int path) ---
|
||||
|
||||
@@ -367,14 +438,30 @@ var qbe_emit = function(ir, qbe) {
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "lt_float" || op == "gt_float" || op == "le_float" || op == "ge_float") {
|
||||
if (op == "lt_float") {
|
||||
p = fresh()
|
||||
fop_id = 0
|
||||
if (op == "lt_float") fop_id = 2
|
||||
else if (op == "le_float") fop_id = 3
|
||||
else if (op == "gt_float") fop_id = 4
|
||||
else if (op == "ge_float") fop_id = 5
|
||||
emit(qbe.cmp_float != null ? qbe.cmp_float(p, "%ctx", s(a2), s(a3), fop_id) : ` %${p} =l call $qbe_float_cmp(l %ctx, w ${text(fop_id)}, l ${s(a2)}, l ${s(a3)})`)
|
||||
emit(qbe.lt_float(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "le_float") {
|
||||
p = fresh()
|
||||
emit(qbe.le_float(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "gt_float") {
|
||||
p = fresh()
|
||||
emit(qbe.gt_float(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "ge_float") {
|
||||
p = fresh()
|
||||
emit(qbe.ge_float(p, "%ctx", s(a2), s(a3)))
|
||||
emit(` ${s(a1)} =l copy %${p}`)
|
||||
wb(a1)
|
||||
continue
|
||||
@@ -494,7 +581,10 @@ var qbe_emit = function(ir, qbe) {
|
||||
// --- Property access — runtime calls ---
|
||||
|
||||
if (op == "load_field") {
|
||||
pn = prop_name(a3)
|
||||
pn = null
|
||||
if (is_text(a3)) pn = a3
|
||||
else if (is_object(a3) && a3.name != null) pn = a3.name
|
||||
else if (is_object(a3) && a3.value != null) pn = a3.value
|
||||
if (pn != null) {
|
||||
sl = intern_str(pn)
|
||||
emit(` ${s(a1)} =l call $cell_rt_load_field(l %ctx, l ${s(a2)}, l ${sl})`)
|
||||
@@ -510,13 +600,28 @@ var qbe_emit = function(ir, qbe) {
|
||||
continue
|
||||
}
|
||||
if (op == "load_dynamic") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_load_dynamic(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
pn = null
|
||||
if (is_text(a3)) pn = a3
|
||||
else if (is_object(a3) && a3.name != null) pn = a3.name
|
||||
else if (is_object(a3) && a3.value != null) pn = a3.value
|
||||
if (pn != null) {
|
||||
sl = intern_str(pn)
|
||||
emit(` ${s(a1)} =l call $cell_rt_load_field(l %ctx, l ${s(a2)}, l ${sl})`)
|
||||
} else {
|
||||
emit(` ${s(a1)} =l call $cell_rt_load_dynamic(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
}
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "store_field") {
|
||||
// IR: ["store_field", obj, val, prop] → C: (ctx, val, obj, name)
|
||||
pn = prop_name(a3)
|
||||
pn = null
|
||||
if (is_text(a3)) {
|
||||
pn = a3
|
||||
} else if (is_object(a3)) {
|
||||
if (a3.name != null) pn = a3.name
|
||||
else if (a3.value != null) pn = a3.value
|
||||
}
|
||||
if (pn != null) {
|
||||
sl = intern_str(pn)
|
||||
emit(` call $cell_rt_store_field(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${sl})`)
|
||||
@@ -532,19 +637,30 @@ var qbe_emit = function(ir, qbe) {
|
||||
}
|
||||
if (op == "store_dynamic") {
|
||||
// IR: ["store_dynamic", obj, val, key] → C: (ctx, val, obj, key)
|
||||
emit(` call $cell_rt_store_dynamic(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`)
|
||||
pn = null
|
||||
if (is_text(a3)) pn = a3
|
||||
else if (is_object(a3) && a3.name != null) pn = a3.name
|
||||
else if (is_object(a3) && a3.value != null) pn = a3.value
|
||||
if (pn != null) {
|
||||
sl = intern_str(pn)
|
||||
emit(` call $cell_rt_store_field(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${sl})`)
|
||||
} else {
|
||||
emit(` call $cell_rt_store_dynamic(l %ctx, l ${s(a2)}, l ${s(a1)}, l ${s(a3)})`)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// --- Closure access ---
|
||||
|
||||
if (op == "get") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_get_closure(l %ctx, l %fp, l ${text(a2)}, l ${text(a3)})`)
|
||||
// mcode: get(dest, slot, depth) — a2=slot, a3=depth
|
||||
emit(` ${s(a1)} =l call $cell_rt_get_closure(l %ctx, l %fp, l ${text(a3)}, l ${text(a2)})`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
if (op == "put") {
|
||||
emit(` call $cell_rt_put_closure(l %ctx, l %fp, l ${s(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
||||
// mcode: put(val, slot, depth) — a2=slot, a3=depth
|
||||
emit(` call $cell_rt_put_closure(l %ctx, l %fp, l ${s(a1)}, l ${text(a3)}, l ${text(a2)})`)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -552,6 +668,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
|
||||
if (op == "jump") {
|
||||
emit(` jmp @${sanitize(a1)}`)
|
||||
last_was_term = true
|
||||
continue
|
||||
}
|
||||
if (op == "jump_true") {
|
||||
@@ -611,6 +728,13 @@ var qbe_emit = function(ir, qbe) {
|
||||
if (op == "invoke") {
|
||||
emit(` ${s(a2)} =l call $cell_rt_invoke(l %ctx, l ${s(a1)})`)
|
||||
wb(a2)
|
||||
reload_captured()
|
||||
continue
|
||||
}
|
||||
if (op == "tail_invoke") {
|
||||
emit(` ${s(a2)} =l call $cell_rt_invoke(l %ctx, l ${s(a1)})`)
|
||||
wb(a2)
|
||||
reload_captured()
|
||||
continue
|
||||
}
|
||||
if (op == "goframe") {
|
||||
@@ -621,6 +745,7 @@ var qbe_emit = function(ir, qbe) {
|
||||
if (op == "goinvoke") {
|
||||
emit(` %_goret =l call $cell_rt_goinvoke(l %ctx, l ${s(a1)})`)
|
||||
emit(` ret %_goret`)
|
||||
last_was_term = true
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -664,19 +789,38 @@ var qbe_emit = function(ir, qbe) {
|
||||
continue
|
||||
}
|
||||
|
||||
// --- Length ---
|
||||
|
||||
if (op == "length") {
|
||||
emit(` ${s(a1)} =l call $JS_CellLength(l %ctx, l ${s(a2)})`)
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
|
||||
// --- Misc ---
|
||||
|
||||
if (op == "return") {
|
||||
emit(` ret ${s(a1)}`)
|
||||
last_was_term = true
|
||||
continue
|
||||
}
|
||||
if (op == "disrupt") {
|
||||
emit(` call $cell_rt_disrupt(l %ctx)`)
|
||||
emit(` ret ${text(qbe.js_null)}`)
|
||||
last_was_term = true
|
||||
continue
|
||||
}
|
||||
if (op == "delete") {
|
||||
emit(` ${s(a1)} =l call $cell_rt_delete(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
pn = null
|
||||
if (is_text(a3)) pn = a3
|
||||
else if (is_object(a3) && a3.name != null) pn = a3.name
|
||||
else if (is_object(a3) && a3.value != null) pn = a3.value
|
||||
if (pn != null) {
|
||||
sl = intern_str(pn)
|
||||
emit(` ${s(a1)} =l call $cell_rt_delete(l %ctx, l ${s(a2)}, l ${sl})`)
|
||||
} else {
|
||||
emit(` ${s(a1)} =l call $cell_rt_delete(l %ctx, l ${s(a2)}, l ${s(a3)})`)
|
||||
}
|
||||
wb(a1)
|
||||
continue
|
||||
}
|
||||
@@ -690,6 +834,14 @@ var qbe_emit = function(ir, qbe) {
|
||||
emit(` # unknown: ${op}`)
|
||||
}
|
||||
|
||||
// Emit @disrupt landing pad for arithmetic type-error branches
|
||||
if (!last_was_term) {
|
||||
emit(" jmp @disrupt")
|
||||
}
|
||||
emit("@disrupt")
|
||||
emit(` call $cell_rt_disrupt(l %ctx)`)
|
||||
emit(` ret ${text(qbe.js_null)}`)
|
||||
|
||||
emit("}")
|
||||
emit("")
|
||||
}
|
||||
@@ -698,6 +850,70 @@ var qbe_emit = function(ir, qbe) {
|
||||
// Main: compile all functions then main
|
||||
// ============================================================
|
||||
|
||||
// ============================================================
|
||||
// Pre-scan: find which slots each function has that are modified
|
||||
// by child closures (via "put" instructions at depth=1).
|
||||
// Build a map: fn_idx → array of captured slot numbers.
|
||||
// ============================================================
|
||||
|
||||
// For each function, find which fn_idxes it creates via "function" op
|
||||
var find_children = function(fn_instrs) {
|
||||
var children = []
|
||||
var ci = 0
|
||||
var cinstr = null
|
||||
while (ci < length(fn_instrs)) {
|
||||
cinstr = fn_instrs[ci]
|
||||
ci = ci + 1
|
||||
if (!is_array(cinstr)) continue
|
||||
if (cinstr[0] == "function") {
|
||||
push(children, cinstr[2])
|
||||
}
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
// For a child function, find which parent slots it writes to via put(val, slot, depth=1)
|
||||
var find_put_slots = function(fn_instrs) {
|
||||
var slots = []
|
||||
var pi = 0
|
||||
var pinstr = null
|
||||
while (pi < length(fn_instrs)) {
|
||||
pinstr = fn_instrs[pi]
|
||||
pi = pi + 1
|
||||
if (!is_array(pinstr)) continue
|
||||
// put format: ["put", val, slot, depth]
|
||||
if (pinstr[0] == "put" && pinstr[3] == 1) {
|
||||
push(slots, pinstr[2])
|
||||
}
|
||||
}
|
||||
return slots
|
||||
}
|
||||
|
||||
// Build captured_slots for each function (and main)
|
||||
var build_captured = function(fn) {
|
||||
var children = find_children(fn.instructions)
|
||||
var captured = {}
|
||||
var bi = 0
|
||||
var child_idx = 0
|
||||
var child_fn = null
|
||||
var pslots = null
|
||||
var si = 0
|
||||
while (bi < length(children)) {
|
||||
child_idx = children[bi]
|
||||
bi = bi + 1
|
||||
if (child_idx >= 0 && child_idx < length(ir.functions)) {
|
||||
child_fn = ir.functions[child_idx]
|
||||
pslots = find_put_slots(child_fn.instructions)
|
||||
si = 0
|
||||
while (si < length(pslots)) {
|
||||
captured[text(pslots[si])] = true
|
||||
si = si + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return captured
|
||||
}
|
||||
|
||||
var fi = 0
|
||||
while (fi < length(ir.functions)) {
|
||||
compile_fn(ir.functions[fi], fi, false)
|
||||
|
||||
11792
qbe_emit.cm.mcode
11792
qbe_emit.cm.mcode
File diff suppressed because it is too large
Load Diff
23
qop.c
23
qop.c
@@ -456,20 +456,21 @@ static const JSCFunctionListEntry js_qop_funcs[] = {
|
||||
JS_PROP_INT32_DEF("FLAG_ENCRYPTED", QOP_FLAG_ENCRYPTED, 0),
|
||||
};
|
||||
|
||||
JSValue js_qop_use(JSContext *js) {
|
||||
JSValue js_core_qop_use(JSContext *js) {
|
||||
JS_FRAME(js);
|
||||
JS_NewClassID(&js_qop_archive_class_id);
|
||||
JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class);
|
||||
JSValue archive_proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs));
|
||||
JS_SetClassProto(js, js_qop_archive_class_id, archive_proto);
|
||||
JS_ROOT(archive_proto, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, archive_proto.val, js_qop_archive_funcs, countof(js_qop_archive_funcs));
|
||||
JS_SetClassProto(js, js_qop_archive_class_id, archive_proto.val);
|
||||
|
||||
JS_NewClassID(&js_qop_writer_class_id);
|
||||
JS_NewClass(js, js_qop_writer_class_id, &js_qop_writer_class);
|
||||
JSValue writer_proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, writer_proto, js_qop_writer_funcs, countof(js_qop_writer_funcs));
|
||||
JS_SetClassProto(js, js_qop_writer_class_id, writer_proto);
|
||||
JS_ROOT(writer_proto, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, writer_proto.val, js_qop_writer_funcs, countof(js_qop_writer_funcs));
|
||||
JS_SetClassProto(js, js_qop_writer_class_id, writer_proto.val);
|
||||
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_qop_funcs, countof(js_qop_funcs));
|
||||
return mod;
|
||||
}
|
||||
JS_ROOT(mod, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, mod.val, js_qop_funcs, countof(js_qop_funcs));
|
||||
JS_RETURN(mod.val);
|
||||
}
|
||||
|
||||
89
qopconv.ce
89
qopconv.ce
@@ -9,19 +9,20 @@ function print_usage() {
|
||||
log.console(" <sources...> <archive> .. create archive from sources")
|
||||
}
|
||||
|
||||
function list(archive_path) {
|
||||
function list_archive(archive_path) {
|
||||
var blob = fd.slurp(archive_path)
|
||||
var archive = null
|
||||
if (!blob) {
|
||||
log.console("Could not open archive " + archive_path)
|
||||
return
|
||||
}
|
||||
var archive = null
|
||||
try {
|
||||
var _open = function() {
|
||||
archive = qop.open(blob)
|
||||
} catch(e) {
|
||||
log.console("Could not open archive " + archive_path + ": " + e.message)
|
||||
} disruption {
|
||||
log.console("Could not open archive " + archive_path)
|
||||
return
|
||||
}
|
||||
_open()
|
||||
|
||||
var files = archive.list()
|
||||
arrfor(files, function(f) {
|
||||
@@ -35,34 +36,41 @@ function list(archive_path) {
|
||||
|
||||
function unpack(archive_path) {
|
||||
var blob = fd.slurp(archive_path)
|
||||
var archive = null
|
||||
if (!blob) {
|
||||
log.console("Could not open archive " + archive_path)
|
||||
return
|
||||
}
|
||||
var archive = null
|
||||
try {
|
||||
var _open = function() {
|
||||
archive = qop.open(blob)
|
||||
} catch(e) {
|
||||
log.console("Could not open archive " + archive_path + ": " + e.message)
|
||||
} disruption {
|
||||
log.console("Could not open archive " + archive_path)
|
||||
return
|
||||
}
|
||||
_open()
|
||||
|
||||
var files = archive.list()
|
||||
arrfor(files, function(f) {
|
||||
var data = archive.read(f)
|
||||
var dir = null
|
||||
var parts = null
|
||||
var curr = null
|
||||
var fh = null
|
||||
var _mk = null
|
||||
if (data) {
|
||||
// Ensure directory exists
|
||||
var dir = fd.dirname(f)
|
||||
dir = fd.dirname(f)
|
||||
if (dir) {
|
||||
// recursive mkdir
|
||||
var parts = array(dir, '/')
|
||||
var curr = "."
|
||||
parts = array(dir, '/')
|
||||
curr = "."
|
||||
arrfor(parts, function(p) {
|
||||
curr += "/" + p
|
||||
try { fd.mkdir(curr) } catch(e) {}
|
||||
_mk = function() { fd.mkdir(curr) } disruption {}
|
||||
_mk()
|
||||
})
|
||||
}
|
||||
var fh = fd.open(f, "w")
|
||||
fh = fd.open(f, "w")
|
||||
fd.write(fh, data)
|
||||
fd.close(fh)
|
||||
log.console("Extracted " + f)
|
||||
@@ -73,9 +81,9 @@ function unpack(archive_path) {
|
||||
|
||||
function pack(sources, archive_path, read_dir) {
|
||||
var writer = qop.write(archive_path)
|
||||
|
||||
|
||||
var base_dir = read_dir || "."
|
||||
|
||||
|
||||
function add_recursive(path) {
|
||||
var full_path = base_dir + "/" + path
|
||||
if (path == ".") full_path = base_dir
|
||||
@@ -86,7 +94,7 @@ function pack(sources, archive_path, read_dir) {
|
||||
log.console("Could not stat " + full_path)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (st.isDirectory) {
|
||||
var list = fd.readdir(full_path)
|
||||
arrfor(list, function(item) {
|
||||
@@ -102,39 +110,44 @@ function pack(sources, archive_path, read_dir) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
arrfor(sources, function(s) {
|
||||
add_recursive(s)
|
||||
})
|
||||
|
||||
|
||||
writer.finalize()
|
||||
log.console("Created " + archive_path)
|
||||
}
|
||||
|
||||
if (!is_array(arg) || length(arg) < 1) {
|
||||
var sources = null
|
||||
var archive = null
|
||||
var read_dir = null
|
||||
var i = 0
|
||||
|
||||
if (!is_array(args) || length(args) < 1) {
|
||||
print_usage()
|
||||
} else {
|
||||
if (arg[0] == "-l") {
|
||||
if (length(arg) < 2) print_usage()
|
||||
else list(arg[1])
|
||||
} else if (arg[0] == "-u") {
|
||||
if (length(arg) < 2) print_usage()
|
||||
else unpack(arg[1])
|
||||
if (args[0] == "-l") {
|
||||
if (length(args) < 2) print_usage()
|
||||
else list_archive(args[1])
|
||||
} else if (args[0] == "-u") {
|
||||
if (length(args) < 2) print_usage()
|
||||
else unpack(args[1])
|
||||
} else {
|
||||
var sources = []
|
||||
var archive = null
|
||||
var read_dir = null
|
||||
var i = 0
|
||||
if (arg[0] == "-d") {
|
||||
read_dir = arg[1]
|
||||
sources = []
|
||||
archive = null
|
||||
read_dir = null
|
||||
i = 0
|
||||
if (args[0] == "-d") {
|
||||
read_dir = args[1]
|
||||
i = 2
|
||||
}
|
||||
|
||||
for (; i < length(arg) - 1; i++) {
|
||||
push(sources, arg[i])
|
||||
|
||||
for (; i < length(args) - 1; i++) {
|
||||
push(sources, args[i])
|
||||
}
|
||||
archive = arg[length(arg) - 1]
|
||||
|
||||
archive = args[length(args) - 1]
|
||||
|
||||
if (length(sources) == 0) {
|
||||
print_usage()
|
||||
} else {
|
||||
@@ -143,4 +156,4 @@ if (!is_array(arg) || length(arg) < 1) {
|
||||
}
|
||||
}
|
||||
|
||||
$stop()
|
||||
$stop()
|
||||
|
||||
160
regen.ce
160
regen.ce
@@ -1,32 +1,74 @@
|
||||
// regen.ce — regenerate .mach bytecode files
|
||||
// Run with: ./cell --core . regen
|
||||
// regen.ce — regenerate .mcode bytecode files and pre-warm .mach cache
|
||||
|
||||
var fd = use("fd")
|
||||
var json = use("json")
|
||||
var crypto = use("crypto")
|
||||
var tokenize = use("tokenize")
|
||||
var parse = use("parse")
|
||||
var fold = use("fold")
|
||||
var mcode = use("mcode")
|
||||
var streamline = use("streamline")
|
||||
|
||||
var files = [
|
||||
{src: "tokenize.cm", name: "tokenize", out: "tokenize.cm.mcode"},
|
||||
{src: "parse.cm", name: "parse", out: "parse.cm.mcode"},
|
||||
{src: "fold.cm", name: "fold", out: "fold.cm.mcode"},
|
||||
{src: "mcode.cm", name: "mcode", out: "mcode.cm.mcode"},
|
||||
{src: "streamline.cm", name: "streamline", out: "streamline.cm.mcode"},
|
||||
{src: "qbe.cm", name: "qbe", out: "qbe.cm.mcode"},
|
||||
{src: "qbe_emit.cm", name: "qbe_emit", out: "qbe_emit.cm.mcode"},
|
||||
{src: "internal/bootstrap.cm", name: "bootstrap", out: "internal/bootstrap.cm.mcode"},
|
||||
{src: "internal/engine.cm", name: "engine", out: "internal/engine.cm.mcode"}
|
||||
// Pipeline files (tokenize/parse/fold/mcode/streamline) are only regenerated
|
||||
// with --all flag since they require a self-consistent compiler to bootstrap.
|
||||
var pipeline_files = [
|
||||
{src: "tokenize.cm", name: "tokenize", out: "boot/tokenize.cm.mcode"},
|
||||
{src: "parse.cm", name: "parse", out: "boot/parse.cm.mcode"},
|
||||
{src: "fold.cm", name: "fold", out: "boot/fold.cm.mcode"},
|
||||
{src: "mcode.cm", name: "mcode", out: "boot/mcode.cm.mcode"},
|
||||
{src: "streamline.cm", name: "streamline", out: "boot/streamline.cm.mcode"}
|
||||
]
|
||||
|
||||
var files = [
|
||||
{src: "qbe.cm", name: "qbe", out: "boot/qbe.cm.mcode"},
|
||||
{src: "qbe_emit.cm", name: "qbe_emit", out: "boot/qbe_emit.cm.mcode"},
|
||||
{src: "verify_ir.cm", name: "verify_ir", out: "boot/verify_ir.cm.mcode"},
|
||||
{src: "internal/bootstrap.cm", name: "bootstrap", out: "boot/bootstrap.cm.mcode"},
|
||||
{src: "internal/engine.cm", name: "engine", out: "boot/engine.cm.mcode"},
|
||||
{src: "boot/seed_bootstrap.cm", name: "seed_bootstrap", out: "boot/seed_bootstrap.cm.mcode"},
|
||||
{src: "fd.cm", name: "fd", out: "boot/fd.cm.mcode"},
|
||||
{src: "time.cm", name: "time", out: "boot/time.cm.mcode"},
|
||||
{src: "pronto.cm", name: "pronto", out: "boot/pronto.cm.mcode"},
|
||||
{src: "toml.cm", name: "toml", out: "boot/toml.cm.mcode"},
|
||||
{src: "link.cm", name: "link", out: "boot/link.cm.mcode"},
|
||||
{src: "toolchains.cm", name: "toolchains", out: "boot/toolchains.cm.mcode"},
|
||||
{src: "package.cm", name: "package", out: "boot/package.cm.mcode"},
|
||||
{src: "internal/shop.cm", name: "internal_shop", out: "boot/internal_shop.cm.mcode"}
|
||||
]
|
||||
|
||||
// Include pipeline files with --all flag
|
||||
var os = use('os')
|
||||
var regen_all = args != null && length(args) > 0 && args[0] == "--all"
|
||||
if (regen_all) {
|
||||
files = array(pipeline_files, files)
|
||||
}
|
||||
|
||||
// Resolve shop_path for cache writes
|
||||
var shop = os.getenv('CELL_SHOP')
|
||||
var home = null
|
||||
var cache_dir = null
|
||||
if (!shop) {
|
||||
home = os.getenv('HOME')
|
||||
if (home) {
|
||||
shop = home + '/.cell'
|
||||
}
|
||||
}
|
||||
if (shop) {
|
||||
cache_dir = shop + '/build'
|
||||
if (!fd.is_dir(cache_dir)) {
|
||||
fd.mkdir(cache_dir)
|
||||
}
|
||||
}
|
||||
|
||||
var i = 0
|
||||
var entry = null
|
||||
var src = null
|
||||
var tok_result = null
|
||||
var ast = null
|
||||
var folded = null
|
||||
var mcode_blob = null
|
||||
var hash = null
|
||||
var mach_blob = null
|
||||
var compiled = null
|
||||
var optimized = null
|
||||
var mcode_text = null
|
||||
@@ -35,88 +77,7 @@ var errs = null
|
||||
var ei = 0
|
||||
var e = null
|
||||
var had_errors = false
|
||||
|
||||
// Collapse leaf arrays (instruction arrays) onto single lines
|
||||
var compact_arrays = function(json_text) {
|
||||
var lines = array(json_text, "\n")
|
||||
var result = []
|
||||
var i = 0
|
||||
var line = null
|
||||
var trimmed = null
|
||||
var collecting = false
|
||||
var collected = null
|
||||
var indent = null
|
||||
var is_leaf = null
|
||||
var j = 0
|
||||
var inner = null
|
||||
var parts = null
|
||||
var trailing = null
|
||||
var chars = null
|
||||
var k = 0
|
||||
|
||||
while (i < length(lines)) {
|
||||
line = lines[i]
|
||||
trimmed = trim(line)
|
||||
if (collecting == false && trimmed == "[") {
|
||||
collecting = true
|
||||
chars = array(line)
|
||||
k = 0
|
||||
while (k < length(chars) && chars[k] == " ") {
|
||||
k = k + 1
|
||||
}
|
||||
indent = text(line, 0, k)
|
||||
collected = []
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if (collecting) {
|
||||
if (trimmed == "]" || trimmed == "],") {
|
||||
is_leaf = true
|
||||
j = 0
|
||||
while (j < length(collected)) {
|
||||
inner = trim(collected[j])
|
||||
if (starts_with(inner, "[") || starts_with(inner, "{")) {
|
||||
is_leaf = false
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
if (is_leaf && length(collected) > 0) {
|
||||
parts = []
|
||||
j = 0
|
||||
while (j < length(collected)) {
|
||||
inner = trim(collected[j])
|
||||
if (ends_with(inner, ",")) {
|
||||
inner = text(inner, 0, length(inner) - 1)
|
||||
}
|
||||
parts[] = inner
|
||||
j = j + 1
|
||||
}
|
||||
trailing = ""
|
||||
if (ends_with(trimmed, ",")) {
|
||||
trailing = ","
|
||||
}
|
||||
result[] = `${indent}[${text(parts, ", ")}]${trailing}`
|
||||
} else {
|
||||
result[] = `${indent}[`
|
||||
j = 0
|
||||
while (j < length(collected)) {
|
||||
result[] = collected[j]
|
||||
j = j + 1
|
||||
}
|
||||
result[] = line
|
||||
}
|
||||
collecting = false
|
||||
} else {
|
||||
collected[] = line
|
||||
}
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
result[] = line
|
||||
i = i + 1
|
||||
}
|
||||
return text(result, "\n")
|
||||
}
|
||||
var compact_mcode = null
|
||||
|
||||
while (i < length(files)) {
|
||||
entry = files[i]
|
||||
@@ -143,11 +104,20 @@ while (i < length(files)) {
|
||||
folded = fold(ast)
|
||||
compiled = mcode(folded)
|
||||
optimized = streamline(compiled)
|
||||
mcode_text = compact_arrays(json.encode(optimized, null, 2))
|
||||
mcode_text = json.encode(optimized)
|
||||
f = fd.open(entry.out, "w")
|
||||
fd.write(f, mcode_text)
|
||||
fd.close(f)
|
||||
print(`wrote ${entry.out}`)
|
||||
// Pre-warm .mach cache
|
||||
if (cache_dir) {
|
||||
mcode_blob = stone(blob(mcode_text))
|
||||
hash = text(crypto.blake2(mcode_blob), 'h')
|
||||
compact_mcode = json.encode(optimized)
|
||||
mach_blob = mach_compile_mcode_bin(entry.name, compact_mcode)
|
||||
fd.slurpwrite(cache_dir + '/' + hash, mach_blob)
|
||||
print(` cached ${hash}`)
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
if (had_errors) {
|
||||
|
||||
20
remove.ce
20
remove.ce
@@ -16,8 +16,10 @@ var fd = use('fd')
|
||||
var target_pkg = null
|
||||
var prune = false
|
||||
var dry_run = false
|
||||
var i = 0
|
||||
var resolved = null
|
||||
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
for (i = 0; i < length(args); i++) {
|
||||
if (args[i] == '--prune') {
|
||||
prune = true
|
||||
} else if (args[i] == '--dry-run') {
|
||||
@@ -43,7 +45,7 @@ if (!target_pkg) {
|
||||
|
||||
// Resolve relative paths to absolute paths
|
||||
if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) {
|
||||
var resolved = fd.realpath(target_pkg)
|
||||
resolved = fd.realpath(target_pkg)
|
||||
if (resolved) {
|
||||
target_pkg = resolved
|
||||
}
|
||||
@@ -51,27 +53,31 @@ if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg
|
||||
|
||||
var packages_to_remove = [target_pkg]
|
||||
|
||||
var lock = null
|
||||
var all_packages = null
|
||||
var needed = null
|
||||
if (prune) {
|
||||
// Find packages no longer needed
|
||||
// Get all dependencies of remaining packages
|
||||
var lock = shop.load_lock()
|
||||
var all_packages = shop.list_packages()
|
||||
lock = shop.load_lock()
|
||||
all_packages = shop.list_packages()
|
||||
|
||||
// Build set of all needed packages (excluding target)
|
||||
var needed = {}
|
||||
needed = {}
|
||||
arrfor(all_packages, function(p) {
|
||||
if (p == target_pkg || p == 'core') return
|
||||
|
||||
// Mark this package and its deps as needed
|
||||
needed[p] = true
|
||||
try {
|
||||
var _gather = function() {
|
||||
var deps = pkg.gather_dependencies(p)
|
||||
arrfor(deps, function(dep) {
|
||||
needed[dep] = true
|
||||
})
|
||||
} catch (e) {
|
||||
} disruption {
|
||||
// Skip if can't read deps
|
||||
}
|
||||
_gather()
|
||||
})
|
||||
|
||||
// Find packages that are NOT needed
|
||||
|
||||
90
resolve.ce
90
resolve.ce
@@ -20,8 +20,10 @@ var target_locator = null
|
||||
var target_triple = null
|
||||
var show_locked = false
|
||||
var refresh_first = false
|
||||
var i = 0
|
||||
var resolved = null
|
||||
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
for (i = 0; i < length(args); i++) {
|
||||
if (args[i] == '--target' || args[i] == '-t') {
|
||||
if (i + 1 < length(args)) {
|
||||
target_triple = args[++i]
|
||||
@@ -55,16 +57,17 @@ if (!target_locator) {
|
||||
|
||||
// Resolve local paths
|
||||
if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) {
|
||||
var resolved = fd.realpath(target_locator)
|
||||
resolved = fd.realpath(target_locator)
|
||||
if (resolved) {
|
||||
target_locator = resolved
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a valid package
|
||||
var pkg_dir = null
|
||||
if (!fd.is_file(target_locator + '/cell.toml')) {
|
||||
// Try to find it in the shop
|
||||
var pkg_dir = shop.get_package_dir(target_locator)
|
||||
pkg_dir = shop.get_package_dir(target_locator)
|
||||
if (!fd.is_file(pkg_dir + '/cell.toml')) {
|
||||
log.error("Not a valid package: " + target_locator)
|
||||
$stop()
|
||||
@@ -89,7 +92,7 @@ function gather_deps(locator, depth) {
|
||||
|
||||
all_deps[locator] = { depth: depth }
|
||||
|
||||
try {
|
||||
var _gather = function() {
|
||||
var deps = pkg.dependencies(locator)
|
||||
if (deps) {
|
||||
arrfor(array(deps), function(alias) {
|
||||
@@ -97,9 +100,10 @@ function gather_deps(locator, depth) {
|
||||
gather_deps(dep_locator, depth + 1)
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
} disruption {
|
||||
// Package might not have dependencies
|
||||
}
|
||||
_gather()
|
||||
}
|
||||
|
||||
gather_deps(target_locator, 0)
|
||||
@@ -114,51 +118,74 @@ var sorted = array(array(all_deps), function(locator) { return { locator: locato
|
||||
sorted = sort(sorted, "locator")
|
||||
sorted = sort(sorted, "depth")
|
||||
|
||||
for (var i = 0; i < length(sorted); i++) {
|
||||
var locator = sorted[i].locator
|
||||
var depth = sorted[i].depth
|
||||
var j = 0
|
||||
var locator = null
|
||||
var depth = 0
|
||||
var indent = null
|
||||
var info = null
|
||||
var lock_entry = null
|
||||
var link_target = null
|
||||
var effective_locator = null
|
||||
var is_linked = false
|
||||
var is_in_lock = false
|
||||
var is_local = false
|
||||
var is_fetched = false
|
||||
var lib_dir = null
|
||||
var lib_name = null
|
||||
var dylib_ext = null
|
||||
var lib_path = null
|
||||
var is_built = false
|
||||
var status_parts = null
|
||||
var commit_str = null
|
||||
var line = null
|
||||
var cflags = null
|
||||
var ldflags = null
|
||||
|
||||
var indent = ""
|
||||
for (var j = 0; j < depth; j++) indent += " "
|
||||
for (i = 0; i < length(sorted); i++) {
|
||||
locator = sorted[i].locator
|
||||
depth = sorted[i].depth
|
||||
|
||||
indent = ""
|
||||
for (j = 0; j < depth; j++) indent += " "
|
||||
|
||||
// Get info about this package
|
||||
var info = shop.resolve_package_info(locator)
|
||||
var lock_entry = lock[locator]
|
||||
var link_target = show_locked ? null : links[locator]
|
||||
var effective_locator = link_target || locator
|
||||
info = shop.resolve_package_info(locator)
|
||||
lock_entry = lock[locator]
|
||||
link_target = show_locked ? null : links[locator]
|
||||
effective_locator = link_target || locator
|
||||
|
||||
// Check status
|
||||
var is_linked = link_target != null
|
||||
var is_in_lock = lock_entry != null
|
||||
var is_local = info == 'local'
|
||||
is_linked = link_target != null
|
||||
is_in_lock = lock_entry != null
|
||||
is_local = info == 'local'
|
||||
|
||||
// Check if fetched (package directory exists)
|
||||
var pkg_dir = shop.get_package_dir(locator)
|
||||
var is_fetched = fd.is_dir(pkg_dir) || fd.is_link(pkg_dir)
|
||||
pkg_dir = shop.get_package_dir(locator)
|
||||
is_fetched = fd.is_dir(pkg_dir) || fd.is_link(pkg_dir)
|
||||
|
||||
// Check if built (library exists)
|
||||
var lib_dir = shop.get_lib_dir()
|
||||
var lib_name = shop.lib_name_for_package(locator)
|
||||
var dylib_ext = '.dylib' // TODO: detect from target
|
||||
var lib_path = lib_dir + '/' + lib_name + dylib_ext
|
||||
var is_built = fd.is_file(lib_path)
|
||||
lib_dir = shop.get_lib_dir()
|
||||
lib_name = shop.lib_name_for_package(locator)
|
||||
dylib_ext = '.dylib' // TODO: detect from target
|
||||
lib_path = lib_dir + '/' + lib_name + dylib_ext
|
||||
is_built = fd.is_file(lib_path)
|
||||
|
||||
// Format output
|
||||
var status_parts = []
|
||||
status_parts = []
|
||||
if (is_linked) push(status_parts, "linked")
|
||||
if (is_local) push(status_parts, "local")
|
||||
if (!is_in_lock) push(status_parts, "not in lock")
|
||||
if (!is_fetched) push(status_parts, "not fetched")
|
||||
if (is_built) push(status_parts, "built")
|
||||
|
||||
var commit_str = ""
|
||||
commit_str = ""
|
||||
if (lock_entry && lock_entry.commit) {
|
||||
commit_str = " @" + text(lock_entry.commit, 0, 8)
|
||||
} else if (lock_entry && lock_entry.type == 'local') {
|
||||
commit_str = " (local)"
|
||||
}
|
||||
|
||||
var line = indent + locator + commit_str
|
||||
line = indent + locator + commit_str
|
||||
|
||||
if (is_linked && !show_locked) {
|
||||
line += " -> " + link_target
|
||||
@@ -172,9 +199,9 @@ for (var i = 0; i < length(sorted); i++) {
|
||||
|
||||
// Show compilation inputs if requested (verbose)
|
||||
if (depth == 0) {
|
||||
try {
|
||||
var cflags = pkg.get_flags(locator, 'CFLAGS', target_triple)
|
||||
var ldflags = pkg.get_flags(locator, 'LDFLAGS', target_triple)
|
||||
var _show_flags = function() {
|
||||
cflags = pkg.get_flags(locator, 'CFLAGS', target_triple)
|
||||
ldflags = pkg.get_flags(locator, 'LDFLAGS', target_triple)
|
||||
if (length(cflags) > 0 || length(ldflags) > 0) {
|
||||
log.console(indent + " Compilation inputs:")
|
||||
if (length(cflags) > 0) {
|
||||
@@ -184,9 +211,10 @@ for (var i = 0; i < length(sorted); i++) {
|
||||
log.console(indent + " LDFLAGS: " + text(ldflags, ' '))
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
} disruption {
|
||||
// Skip if can't read config
|
||||
}
|
||||
_show_flags()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// run_native.ce — load a module both interpreted and native, compare speed
|
||||
//
|
||||
// Usage:
|
||||
// cell --core . run_native.ce <module>
|
||||
// cell --dev run_native.ce <module>
|
||||
//
|
||||
// Loads <module>.cm via use() (interpreted) and <module>.dylib (native),
|
||||
// Loads <module>.cm via use() (interpreted) and <module>.cm.dylib (native),
|
||||
// runs both and compares results and timing.
|
||||
|
||||
var os = use('os')
|
||||
|
||||
if (length(args) < 1) {
|
||||
print('usage: cell --core . run_native.ce <module>')
|
||||
print(' e.g. cell --core . run_native.ce num_torture')
|
||||
print('usage: cell --dev run_native.ce <module>')
|
||||
print(' e.g. cell --dev run_native.ce num_torture')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ if (ends_with(name, '.cm')) {
|
||||
|
||||
var safe = replace(replace(name, '/', '_'), '-', '_')
|
||||
var symbol = 'js_' + safe + '_use'
|
||||
var dylib_path = './' + name + '.dylib'
|
||||
var dylib_path = './' + name + '.cm.dylib'
|
||||
var fd = use('fd')
|
||||
|
||||
// --- Test argument for function-returning modules ---
|
||||
|
||||
78
run_native_seed.ce
Normal file
78
run_native_seed.ce
Normal file
@@ -0,0 +1,78 @@
|
||||
// run_native_seed.ce — load and run a native .dylib module (seed mode)
|
||||
// Usage: ./cell --dev --seed run_native_seed benches/fibonacci
|
||||
|
||||
var fd = use("fd")
|
||||
var os = use("os")
|
||||
|
||||
if (length(args) < 1) {
|
||||
print("usage: cell --dev --seed run_native_seed <module>")
|
||||
disrupt
|
||||
}
|
||||
|
||||
var name = args[0]
|
||||
if (ends_with(name, ".cm")) {
|
||||
name = text(name, 0, length(name) - 3)
|
||||
}
|
||||
|
||||
var safe = replace(replace(name, "/", "_"), "-", "_")
|
||||
var symbol = "js_" + safe + "_use"
|
||||
var dylib_path = "./" + name + ".cm.dylib"
|
||||
|
||||
var test_arg = 30
|
||||
if (length(args) > 1) {
|
||||
test_arg = number(args[1])
|
||||
}
|
||||
|
||||
// --- Interpreted run ---
|
||||
print("--- interpreted ---")
|
||||
var t1 = os.now()
|
||||
var mod_interp = use(name)
|
||||
var t2 = os.now()
|
||||
var result_interp = null
|
||||
if (is_function(mod_interp)) {
|
||||
print("module returns a function, calling with " + text(test_arg))
|
||||
t1 = os.now()
|
||||
result_interp = mod_interp(test_arg)
|
||||
t2 = os.now()
|
||||
}
|
||||
result_interp = result_interp != null ? result_interp : mod_interp
|
||||
var ms_interp = (t2 - t1) / 1000000
|
||||
print("result: " + text(result_interp))
|
||||
print("time: " + text(ms_interp) + " ms")
|
||||
|
||||
// --- Native run ---
|
||||
if (!fd.is_file(dylib_path)) {
|
||||
print("\nno " + dylib_path + " found")
|
||||
disrupt
|
||||
}
|
||||
|
||||
print("\n--- native ---")
|
||||
var t3 = os.now()
|
||||
var lib = os.dylib_open(dylib_path)
|
||||
var t4 = os.now()
|
||||
var mod_native = os.dylib_symbol(lib, symbol)
|
||||
var t5 = os.now()
|
||||
var result_native = null
|
||||
if (is_function(mod_native)) {
|
||||
print("module returns a function, calling with " + text(test_arg))
|
||||
t4 = os.now()
|
||||
result_native = mod_native(test_arg)
|
||||
t5 = os.now()
|
||||
}
|
||||
result_native = result_native != null ? result_native : mod_native
|
||||
var ms_native = (t5 - t3) / 1000000
|
||||
var ms_exec = (t5 - t4) / 1000000
|
||||
print("result: " + text(result_native))
|
||||
print("load: " + text((t4 - t3) / 1000000) + " ms")
|
||||
print("exec: " + text(ms_exec) + " ms")
|
||||
print("total: " + text(ms_native) + " ms")
|
||||
|
||||
// --- Comparison ---
|
||||
print("\n--- comparison ---")
|
||||
print("match: " + text(result_interp == result_native))
|
||||
if (ms_native > 0) {
|
||||
print("speedup: " + text(ms_interp / ms_native) + "x (total)")
|
||||
}
|
||||
if (ms_exec > 0) {
|
||||
print("speedup: " + text(ms_interp / ms_exec) + "x (exec only)")
|
||||
}
|
||||
5
runtime.cm
Normal file
5
runtime.cm
Normal file
@@ -0,0 +1,5 @@
|
||||
// Runtime configuration — available to all modules via use('runtime')
|
||||
return stone({
|
||||
shop_path: shop_path,
|
||||
core_path: core_path
|
||||
})
|
||||
16
search.ce
16
search.ce
@@ -8,7 +8,6 @@ if (length(args) < 1) {
|
||||
log.console("Usage: cell search <query>")
|
||||
log.console("Searches for packages, actors, or modules matching the query.")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
var query = args[0]
|
||||
@@ -24,25 +23,26 @@ arrfor(packages, function(package_name) {
|
||||
if (search(package_name, query) != null) {
|
||||
push(found_packages, package_name)
|
||||
}
|
||||
|
||||
|
||||
// Search modules and actors within the package
|
||||
try {
|
||||
var _search = function() {
|
||||
var modules = pkg.list_modules(package_name)
|
||||
arrfor(modules, function(mod) {
|
||||
if (search(mod, query) != null) {
|
||||
push(found_modules, package_name + ':' + mod)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
var actors = pkg.list_programs(package_name)
|
||||
arrfor(actors, function(actor) {
|
||||
if (search(actor, query) != null) {
|
||||
push(found_actors, package_name + ':' + actor)
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
} disruption {
|
||||
// Skip packages that can't be read
|
||||
}
|
||||
_search()
|
||||
})
|
||||
|
||||
// Print results
|
||||
@@ -53,7 +53,7 @@ if (total == 0) {
|
||||
} else {
|
||||
log.console("Found " + text(total) + " result(s) for '" + query + "':")
|
||||
log.console("")
|
||||
|
||||
|
||||
if (length(found_packages) > 0) {
|
||||
log.console("Packages:")
|
||||
arrfor(found_packages, function(p) {
|
||||
@@ -61,7 +61,7 @@ if (total == 0) {
|
||||
})
|
||||
log.console("")
|
||||
}
|
||||
|
||||
|
||||
if (length(found_modules) > 0) {
|
||||
log.console("Modules:")
|
||||
arrfor(found_modules, function(m) {
|
||||
@@ -69,7 +69,7 @@ if (total == 0) {
|
||||
})
|
||||
log.console("")
|
||||
}
|
||||
|
||||
|
||||
if (length(found_actors) > 0) {
|
||||
log.console("Actors:")
|
||||
arrfor(found_actors, function(a) {
|
||||
|
||||
66
source/buddy_debug.c
Normal file
66
source/buddy_debug.c
Normal file
@@ -0,0 +1,66 @@
|
||||
/* buddy_debug.c — ASCII visualization for buddy allocator
|
||||
Included from runtime.c only when DUMP_BUDDY is defined. */
|
||||
|
||||
static void buddy_dump(BuddyPool *pool, const char *op,
|
||||
uint8_t *block, uint8_t order) {
|
||||
if (!pool || !pool->base) return;
|
||||
|
||||
int levels = pool->max_order - BUDDY_MIN_ORDER + 1;
|
||||
|
||||
/* Bitmap: one byte per min-block slot */
|
||||
size_t num_slots = pool->total_size >> BUDDY_MIN_ORDER;
|
||||
/* Dynamic VLA — pool sizes vary now */
|
||||
uint8_t *bitmap = alloca(num_slots);
|
||||
memset(bitmap, 0, num_slots); /* 0 = allocated */
|
||||
|
||||
/* Walk all free lists and mark free slots */
|
||||
for (int i = 0; i < levels; i++) {
|
||||
for (BuddyBlock *p = pool->free_lists[i]; p; p = p->next) {
|
||||
size_t off = (uint8_t *)p - pool->base;
|
||||
size_t slot = off >> BUDDY_MIN_ORDER;
|
||||
size_t count = 1ULL << i; /* number of min-block slots in this block */
|
||||
for (size_t s = 0; s < count && (slot + s) < num_slots; s++)
|
||||
bitmap[slot + s] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Render 64-char ASCII bar */
|
||||
size_t slots_per_char = num_slots / 64;
|
||||
if (slots_per_char == 0) slots_per_char = 1;
|
||||
char bar[65];
|
||||
size_t total_free_slots = 0;
|
||||
for (int c = 0; c < 64; c++) {
|
||||
size_t base_slot = c * slots_per_char;
|
||||
size_t free_count = 0;
|
||||
for (size_t s = 0; s < slots_per_char && (base_slot + s) < num_slots; s++) {
|
||||
if (bitmap[base_slot + s]) free_count++;
|
||||
}
|
||||
total_free_slots += free_count;
|
||||
/* Majority vote: if more than half are free, show free */
|
||||
bar[c] = (free_count > slots_per_char / 2) ? '.' : '#';
|
||||
}
|
||||
bar[64] = '\0';
|
||||
|
||||
size_t blk_offset = block - pool->base;
|
||||
size_t blk_size = 1ULL << order;
|
||||
size_t total_free = total_free_slots << BUDDY_MIN_ORDER;
|
||||
size_t total_alloc = pool->total_size - total_free;
|
||||
|
||||
fprintf(stderr, "buddy %s: pool %zuKB order %u (%zuKB) @ +%zuKB allocs=%u\n",
|
||||
op, pool->total_size / 1024, order, blk_size / 1024,
|
||||
blk_offset / 1024, pool->alloc_count);
|
||||
fprintf(stderr, " [%s]\n", bar);
|
||||
fprintf(stderr, " alloc: %zuKB free: %zuKB total: %zuKB\n",
|
||||
total_alloc / 1024, total_free / 1024, pool->total_size / 1024);
|
||||
|
||||
/* Print free list population */
|
||||
fprintf(stderr, " free lists:");
|
||||
for (int i = 0; i < levels; i++) {
|
||||
int count = 0;
|
||||
for (BuddyBlock *p = pool->free_lists[i]; p; p = p->next)
|
||||
count++;
|
||||
if (count > 0)
|
||||
fprintf(stderr, " o%d:%d", i + BUDDY_MIN_ORDER, count);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
244
source/cell.c
244
source/cell.c
@@ -11,9 +11,9 @@
|
||||
#include "cell_internal.h"
|
||||
#include "cJSON.h"
|
||||
|
||||
#define BOOTSTRAP_MACH "internal/bootstrap.cm.mach"
|
||||
#define BOOTSTRAP_MCODE "internal/bootstrap.cm.mcode"
|
||||
#define BOOTSTRAP_SRC "internal/bootstrap.cm"
|
||||
#define BOOTSTRAP_MCODE "boot/bootstrap.cm.mcode"
|
||||
#define SEED_BOOTSTRAP_MCODE "boot/seed_bootstrap.cm.mcode"
|
||||
#define BOOTSTRAP_SRC "internal/bootstrap.cm"
|
||||
#define CELL_SHOP_DIR ".cell"
|
||||
#define CELL_CORE_DIR "packages/core"
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include "monocypher.h"
|
||||
|
||||
/* Test suite declarations */
|
||||
int run_c_test_suite(JSContext *ctx);
|
||||
@@ -31,6 +32,83 @@ static char *shop_path = NULL;
|
||||
static char *core_path = NULL;
|
||||
static JSRuntime *g_runtime = NULL;
|
||||
|
||||
// Compute blake2b hash of data and return hex string (caller must free)
|
||||
static char *compute_blake2_hex(const char *data, size_t size) {
|
||||
uint8_t hash[32];
|
||||
crypto_blake2b(hash, 32, (const uint8_t *)data, size);
|
||||
char *hex = malloc(65);
|
||||
for (int i = 0; i < 32; i++)
|
||||
snprintf(hex + i * 2, 3, "%02x", hash[i]);
|
||||
return hex;
|
||||
}
|
||||
|
||||
// Build cache path: shop_path/build/<hex> (caller must free)
|
||||
static char *build_cache_path(const char *hex) {
|
||||
if (!shop_path) return NULL;
|
||||
size_t len = strlen(shop_path) + strlen("/build/") + 64 + 1;
|
||||
char *path = malloc(len);
|
||||
snprintf(path, len, "%s/build/%s", shop_path, hex);
|
||||
return path;
|
||||
}
|
||||
|
||||
// Write binary data to file
|
||||
static int write_cache_file(const char *path, const uint8_t *data, size_t size) {
|
||||
FILE *fh = fopen(path, "wb");
|
||||
if (!fh) return 0;
|
||||
size_t written = fwrite(data, 1, size, fh);
|
||||
fclose(fh);
|
||||
return written == size;
|
||||
}
|
||||
|
||||
// Load cached .mach or compile from .mcode and cache result
|
||||
// Returns heap-allocated binary data and sets *out_size, or NULL on failure
|
||||
static char *load_or_cache_bootstrap(const char *mcode_data, size_t mcode_size, size_t *out_size) {
|
||||
char *hex = compute_blake2_hex(mcode_data, mcode_size);
|
||||
char *cpath = build_cache_path(hex);
|
||||
free(hex);
|
||||
|
||||
if (cpath) {
|
||||
// Try loading from cache
|
||||
FILE *fh = fopen(cpath, "rb");
|
||||
if (fh) {
|
||||
fseek(fh, 0, SEEK_END);
|
||||
long file_size = ftell(fh);
|
||||
fseek(fh, 0, SEEK_SET);
|
||||
char *data = malloc(file_size);
|
||||
if (data && fread(data, 1, file_size, fh) == (size_t)file_size) {
|
||||
fclose(fh);
|
||||
free(cpath);
|
||||
*out_size = file_size;
|
||||
return data;
|
||||
}
|
||||
free(data);
|
||||
fclose(fh);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss: compile mcode to binary
|
||||
cJSON *mcode = cJSON_Parse(mcode_data);
|
||||
if (!mcode) { free(cpath); return NULL; }
|
||||
|
||||
MachCode *mc = mach_compile_mcode(mcode);
|
||||
cJSON_Delete(mcode);
|
||||
if (!mc) { free(cpath); return NULL; }
|
||||
|
||||
size_t bin_size;
|
||||
uint8_t *bin = JS_SerializeMachCode(mc, &bin_size);
|
||||
JS_FreeMachCode(mc);
|
||||
if (!bin) { free(cpath); return NULL; }
|
||||
|
||||
// Write to cache
|
||||
if (cpath) {
|
||||
write_cache_file(cpath, bin, bin_size);
|
||||
free(cpath);
|
||||
}
|
||||
|
||||
*out_size = bin_size;
|
||||
return (char *)bin;
|
||||
}
|
||||
|
||||
// Get the home directory
|
||||
static const char* get_home_dir(void) {
|
||||
const char *home = getenv("HOME");
|
||||
@@ -147,11 +225,8 @@ void actor_disrupt(cell_rt *crt)
|
||||
actor_free(crt);
|
||||
}
|
||||
|
||||
JSValue js_os_use(JSContext *js);
|
||||
JSValue js_math_use(JSContext *js);
|
||||
JSValue js_json_use(JSContext *js);
|
||||
JSValue js_nota_use(JSContext *js);
|
||||
JSValue js_wota_use(JSContext *js);
|
||||
JSValue js_core_os_use(JSContext *js);
|
||||
JSValue js_core_json_use(JSContext *js);
|
||||
|
||||
void script_startup(cell_rt *prt)
|
||||
{
|
||||
@@ -177,57 +252,68 @@ void script_startup(cell_rt *prt)
|
||||
prt->actor_sym_ref.val = JS_NULL;
|
||||
|
||||
cell_rt *crt = JS_GetContextOpaque(js);
|
||||
JS_FreeValue(js, js_blob_use(js));
|
||||
JS_FreeValue(js, js_core_blob_use(js));
|
||||
|
||||
// Load pre-compiled bootstrap (.cm.mach or .cm.mcode)
|
||||
// Load pre-compiled bootstrap .mcode
|
||||
size_t boot_size;
|
||||
char *boot_data = load_core_file(BOOTSTRAP_MACH, &boot_size);
|
||||
int boot_is_mcode = 0;
|
||||
if (!boot_data) {
|
||||
boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size);
|
||||
boot_is_mcode = 1;
|
||||
}
|
||||
char *boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size);
|
||||
if (!boot_data) {
|
||||
printf("ERROR: Could not load bootstrap from %s!\n", core_path);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try cache or compile mcode → binary
|
||||
size_t bin_size;
|
||||
char *bin_data = load_or_cache_bootstrap(boot_data, boot_size, &bin_size);
|
||||
free(boot_data);
|
||||
if (!bin_data) {
|
||||
printf("ERROR: Failed to compile bootstrap mcode!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create hidden environment
|
||||
JSValue hidden_env = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, hidden_env, "os", js_os_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "json", js_json_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js));
|
||||
JS_SetPropertyStr(js, hidden_env, "wota", js_wota_use(js));
|
||||
// Note: evaluate allocating calls into temporaries before passing to
|
||||
// JS_SetPropertyStr, so env_ref.val is read AFTER GC may have moved it.
|
||||
JSGCRef env_ref;
|
||||
JS_AddGCRef(js, &env_ref);
|
||||
env_ref.val = JS_NewObject(js);
|
||||
JSValue tmp;
|
||||
tmp = js_core_os_use(js);
|
||||
JS_SetPropertyStr(js, env_ref.val, "os", tmp);
|
||||
tmp = js_core_json_use(js);
|
||||
JS_SetPropertyStr(js, env_ref.val, "json", tmp);
|
||||
|
||||
crt->actor_sym_ref.val = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
|
||||
JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
|
||||
|
||||
// Always set init (even if null)
|
||||
if (crt->init_wota) {
|
||||
JS_SetPropertyStr(js, hidden_env, "init", wota2value(js, crt->init_wota));
|
||||
tmp = wota2value(js, crt->init_wota);
|
||||
JS_SetPropertyStr(js, env_ref.val, "init", tmp);
|
||||
free(crt->init_wota);
|
||||
crt->init_wota = NULL;
|
||||
} else {
|
||||
JS_SetPropertyStr(js, hidden_env, "init", JS_NULL);
|
||||
JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
|
||||
}
|
||||
|
||||
// Set args to null for actor spawn (not CLI mode)
|
||||
JS_SetPropertyStr(js, hidden_env, "args", JS_NULL);
|
||||
JS_SetPropertyStr(js, env_ref.val, "args", JS_NULL);
|
||||
|
||||
if (core_path)
|
||||
JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path));
|
||||
JS_SetPropertyStr(js, hidden_env, "shop_path",
|
||||
shop_path ? JS_NewString(js, shop_path) : JS_NULL);
|
||||
if (core_path) {
|
||||
tmp = JS_NewString(js, core_path);
|
||||
JS_SetPropertyStr(js, env_ref.val, "core_path", tmp);
|
||||
}
|
||||
tmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
|
||||
JS_SetPropertyStr(js, env_ref.val, "shop_path", tmp);
|
||||
|
||||
// Stone the environment
|
||||
hidden_env = JS_Stone(js, hidden_env);
|
||||
JSValue hidden_env = JS_Stone(js, env_ref.val);
|
||||
JS_DeleteGCRef(js, &env_ref);
|
||||
|
||||
// Run through MACH VM
|
||||
// Run from binary
|
||||
crt->state = ACTOR_RUNNING;
|
||||
JSValue v = boot_is_mcode
|
||||
? JS_RunMachMcode(js, boot_data, boot_size, hidden_env)
|
||||
: JS_RunMachBin(js, (const uint8_t *)boot_data, boot_size, hidden_env);
|
||||
free(boot_data);
|
||||
JSValue v = JS_RunMachBin(js, (const uint8_t *)bin_data, bin_size, hidden_env);
|
||||
free(bin_data);
|
||||
uncaught_exception(js, v);
|
||||
crt->state = ACTOR_IDLE;
|
||||
set_actor_state(crt);
|
||||
@@ -283,6 +369,8 @@ static void print_usage(const char *prog)
|
||||
printf(" --core <path> Set core path directly (overrides CELL_CORE)\n");
|
||||
printf(" --shop <path> Set shop path (overrides CELL_SHOP)\n");
|
||||
printf(" --dev Dev mode (shop=.cell, core=.)\n");
|
||||
printf(" --heap <size> Initial heap size (e.g. 256MB, 1GB)\n");
|
||||
printf(" --seed Use seed bootstrap (minimal, for regen)\n");
|
||||
printf(" --test [heap_size] Run C test suite\n");
|
||||
printf(" -h, --help Show this help message\n");
|
||||
printf("\nEnvironment:\n");
|
||||
@@ -315,6 +403,8 @@ int cell_init(int argc, char **argv)
|
||||
|
||||
/* Default: run script through bootstrap pipeline */
|
||||
int arg_start = 1;
|
||||
int seed_mode = 0;
|
||||
size_t heap_size = 1024 * 1024; /* 1MB default */
|
||||
const char *shop_override = NULL;
|
||||
const char *core_override = NULL;
|
||||
|
||||
@@ -334,6 +424,20 @@ int cell_init(int argc, char **argv)
|
||||
}
|
||||
core_override = argv[arg_start + 1];
|
||||
arg_start += 2;
|
||||
} else if (strcmp(argv[arg_start], "--seed") == 0) {
|
||||
seed_mode = 1;
|
||||
arg_start++;
|
||||
} else if (strcmp(argv[arg_start], "--heap") == 0) {
|
||||
if (arg_start + 1 >= argc) {
|
||||
printf("ERROR: --heap requires a size argument (e.g. 1GB, 256MB, 65536)\n");
|
||||
return 1;
|
||||
}
|
||||
char *end = NULL;
|
||||
heap_size = strtoull(argv[arg_start + 1], &end, 0);
|
||||
if (end && (*end == 'G' || *end == 'g')) heap_size *= 1024ULL * 1024 * 1024;
|
||||
else if (end && (*end == 'M' || *end == 'm')) heap_size *= 1024ULL * 1024;
|
||||
else if (end && (*end == 'K' || *end == 'k')) heap_size *= 1024ULL;
|
||||
arg_start += 2;
|
||||
} else if (strcmp(argv[arg_start], "--dev") == 0) {
|
||||
shop_override = ".cell";
|
||||
core_override = ".";
|
||||
@@ -359,28 +463,33 @@ int cell_init(int argc, char **argv)
|
||||
|
||||
actor_initialize();
|
||||
|
||||
const char *boot_mcode = seed_mode ? SEED_BOOTSTRAP_MCODE : BOOTSTRAP_MCODE;
|
||||
size_t boot_size;
|
||||
char *boot_data = load_core_file(BOOTSTRAP_MACH, &boot_size);
|
||||
int boot_is_mcode = 0;
|
||||
if (!boot_data) {
|
||||
boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size);
|
||||
boot_is_mcode = 1;
|
||||
}
|
||||
char *boot_data = load_core_file(boot_mcode, &boot_size);
|
||||
if (!boot_data) {
|
||||
printf("ERROR: Could not load bootstrap from %s\n", core_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Try cache or compile mcode → binary
|
||||
size_t bin_size;
|
||||
char *bin_data = load_or_cache_bootstrap(boot_data, boot_size, &bin_size);
|
||||
free(boot_data);
|
||||
if (!bin_data) {
|
||||
printf("ERROR: Failed to compile bootstrap mcode\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
g_runtime = JS_NewRuntime();
|
||||
if (!g_runtime) {
|
||||
printf("Failed to create JS runtime\n");
|
||||
free(boot_data);
|
||||
free(bin_data);
|
||||
return 1;
|
||||
}
|
||||
JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, 1024 * 1024);
|
||||
JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, heap_size);
|
||||
if (!ctx) {
|
||||
printf("Failed to create JS context\n");
|
||||
free(boot_data); JS_FreeRuntime(g_runtime);
|
||||
free(bin_data); JS_FreeRuntime(g_runtime);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -412,33 +521,36 @@ int cell_init(int argc, char **argv)
|
||||
|
||||
root_cell = cli_rt;
|
||||
|
||||
JS_FreeValue(ctx, js_blob_use(ctx));
|
||||
JS_FreeValue(ctx, js_core_blob_use(ctx));
|
||||
|
||||
JSValue hidden_env = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, hidden_env, "os", js_os_use(ctx));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "shop_path",
|
||||
shop_path ? JS_NewString(ctx, shop_path) : JS_NULL);
|
||||
/* TODO: remove after next 'make regen' — old bootstrap.mach reads these */
|
||||
JS_SetPropertyStr(ctx, hidden_env, "emit_qbe", JS_FALSE);
|
||||
JS_SetPropertyStr(ctx, hidden_env, "dump_mach", JS_FALSE);
|
||||
JS_SetPropertyStr(ctx, hidden_env, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "json", js_json_use(ctx));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "nota", js_nota_use(ctx));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "wota", js_wota_use(ctx));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "init", JS_NULL);
|
||||
JSValue args_arr = JS_NewArray(ctx);
|
||||
JSGCRef env_ref;
|
||||
JS_AddGCRef(ctx, &env_ref);
|
||||
env_ref.val = JS_NewObject(ctx);
|
||||
JSValue tmp;
|
||||
tmp = js_core_os_use(ctx);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "os", tmp);
|
||||
tmp = JS_NewString(ctx, core_path);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp);
|
||||
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, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
|
||||
tmp = js_core_json_use(ctx);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "init", JS_NULL);
|
||||
JSGCRef args_ref;
|
||||
JS_AddGCRef(ctx, &args_ref);
|
||||
args_ref.val = JS_NewArray(ctx);
|
||||
for (int i = arg_start; i < argc; i++) {
|
||||
JSValue str = JS_NewString(ctx, argv[i]);
|
||||
JS_ArrayPush(ctx, &args_arr, str);
|
||||
JS_ArrayPush(ctx, &args_ref.val, str);
|
||||
}
|
||||
JS_SetPropertyStr(ctx, hidden_env, "args", args_arr);
|
||||
hidden_env = JS_Stone(ctx, hidden_env);
|
||||
JS_SetPropertyStr(ctx, env_ref.val, "args", args_ref.val);
|
||||
JS_DeleteGCRef(ctx, &args_ref);
|
||||
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
|
||||
JS_DeleteGCRef(ctx, &env_ref);
|
||||
|
||||
JSValue result = boot_is_mcode
|
||||
? JS_RunMachMcode(ctx, boot_data, boot_size, hidden_env)
|
||||
: JS_RunMachBin(ctx, (const uint8_t *)boot_data, boot_size, hidden_env);
|
||||
free(boot_data);
|
||||
JSValue result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env);
|
||||
free(bin_data);
|
||||
|
||||
int exit_code = 0;
|
||||
if (JS_IsException(result)) {
|
||||
|
||||
@@ -9,7 +9,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
// blob fns
|
||||
JSValue js_blob_use(JSContext *js);
|
||||
JSValue js_core_blob_use(JSContext *js);
|
||||
JSValue js_new_blob_stoned_copy(JSContext *js, void *data, size_t bytes);
|
||||
void *js_get_blob_data(JSContext *js, size_t *size, JSValue v); // bytes
|
||||
void *js_get_blob_data_bits(JSContext *js, size_t *bits, JSValue v); // bits
|
||||
@@ -31,9 +31,7 @@ void *value2wota(JSContext *js, JSValue v, JSValue replacer, size_t *bytes);
|
||||
JSValue nota2value(JSContext *js, void *nota);
|
||||
void *value2nota(JSContext *js, JSValue v);
|
||||
|
||||
JSValue js_json_use(JSContext *js);
|
||||
JSValue js_nota_use(JSContext *js);
|
||||
JSValue js_wota_use(JSContext *js);
|
||||
JSValue js_core_json_use(JSContext *js);
|
||||
|
||||
#define CELL_HOOK_ENTER 1
|
||||
#define CELL_HOOK_EXIT 2
|
||||
@@ -156,6 +154,43 @@ JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \
|
||||
|
||||
#define countof(x) (sizeof(x)/sizeof((x)[0]))
|
||||
|
||||
/* GC safety macros for C functions that allocate multiple heap objects.
|
||||
Any allocation call (JS_NewObject, JS_SetPropertyStr, etc.) can trigger GC.
|
||||
JS_ROOT style: explicit, use .val to access the rooted value.
|
||||
JS_LOCAL style: transparent, GC updates the C local through a pointer. */
|
||||
|
||||
#define JS_FRAME(ctx) \
|
||||
JSContext *_js_ctx = (ctx); \
|
||||
JSGCRef *_js_gc_frame = JS_GetGCFrame(_js_ctx); \
|
||||
JSLocalRef *_js_local_frame = JS_GetLocalFrame(_js_ctx)
|
||||
|
||||
#define JS_ROOT(name, init) \
|
||||
JSGCRef name; \
|
||||
JS_PushGCRef(_js_ctx, &name); \
|
||||
name.val = (init)
|
||||
|
||||
#define JS_LOCAL(name, init) \
|
||||
JSValue name = (init); \
|
||||
JSLocalRef name##__lr; \
|
||||
name##__lr.ptr = &name; \
|
||||
JS_PushLocalRef(_js_ctx, &name##__lr)
|
||||
|
||||
#define JS_RETURN(val) do { \
|
||||
JSValue _js_ret = (val); \
|
||||
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
|
||||
return _js_ret; \
|
||||
} while (0)
|
||||
|
||||
#define JS_RETURN_NULL() do { \
|
||||
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
|
||||
return JS_NULL; \
|
||||
} while (0)
|
||||
|
||||
#define JS_RETURN_EX() do { \
|
||||
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
|
||||
return JS_EXCEPTION; \
|
||||
} while (0)
|
||||
|
||||
// Common macros for property access
|
||||
#define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\
|
||||
JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \
|
||||
@@ -200,6 +235,9 @@ JSValue CELL_USE_NAME(JSContext *js) { \
|
||||
JS_SetPropertyFunctionList(js, mod, FUNCS, countof(FUNCS)); \
|
||||
return mod; }
|
||||
|
||||
#define CELL_PROGRAM_INIT(c) \
|
||||
JSValue CELL_USE_NAME(JSContext *js) { do { c ; } while(0); }
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
172
source/mach.c
172
source/mach.c
@@ -815,12 +815,10 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
ctx->reg_current_frame = frame_ref.val;
|
||||
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
||||
int op = MACH_GET_OP(instr);
|
||||
/* trace disabled */
|
||||
int a = MACH_GET_A(instr);
|
||||
int b = MACH_GET_B(instr);
|
||||
int c = MACH_GET_C(instr);
|
||||
|
||||
|
||||
switch (op) {
|
||||
case MACH_NOP:
|
||||
break;
|
||||
@@ -983,6 +981,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
int ret = JS_SetProperty(ctx, obj, key, val);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
if (ret < 0) goto disrupt;
|
||||
mach_resolve_forward(&frame->slots[a]);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1003,6 +1002,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[b]), val);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
if (JS_IsException(r)) goto disrupt;
|
||||
mach_resolve_forward(&frame->slots[a]);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1129,6 +1129,17 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
result = frame->slots[a];
|
||||
if (JS_IsNull(frame->caller)) goto done;
|
||||
{
|
||||
#ifdef VALIDATE_GC
|
||||
const char *callee_name = "?";
|
||||
const char *callee_file = "?";
|
||||
{
|
||||
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||
if (callee_fn->kind == JS_FUNC_KIND_REGISTER && callee_fn->u.reg.code) {
|
||||
if (callee_fn->u.reg.code->name_cstr) callee_name = callee_fn->u.reg.code->name_cstr;
|
||||
if (callee_fn->u.reg.code->filename_cstr) callee_file = callee_fn->u.reg.code->filename_cstr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
||||
frame->caller = JS_NULL;
|
||||
frame = caller;
|
||||
@@ -1139,7 +1150,22 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
env = fn->u.reg.env_record;
|
||||
pc = ret_info >> 16;
|
||||
int ret_slot = ret_info & 0xFFFF;
|
||||
if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result;
|
||||
if (ret_slot != 0xFFFF) {
|
||||
#ifdef VALIDATE_GC
|
||||
if (JS_IsPtr(result)) {
|
||||
void *rp = JS_VALUE_GET_PTR(result);
|
||||
if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) {
|
||||
if (!is_ct_ptr(ctx, rp))
|
||||
fprintf(stderr, "VALIDATE_GC: stale RETURN into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u callee=%s (%s) caller=%s (%s)\n",
|
||||
ret_slot, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc,
|
||||
callee_name, callee_file,
|
||||
code->name_cstr ? code->name_cstr : "?",
|
||||
code->filename_cstr ? code->filename_cstr : "?");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
frame->slots[ret_slot] = result;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1170,7 +1196,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
}
|
||||
|
||||
case MACH_NEWARRAY: {
|
||||
JSValue arr = JS_NewArray(ctx);
|
||||
JSValue arr = JS_NewArrayCap(ctx, b);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
if (JS_IsException(arr)) { goto disrupt; }
|
||||
frame->slots[a] = arr;
|
||||
@@ -1464,6 +1490,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
int ret = JS_SetProperty(ctx, obj, key, val);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
if (ret < 0) goto disrupt;
|
||||
mach_resolve_forward(&frame->slots[a]);
|
||||
break;
|
||||
}
|
||||
case MACH_LOAD_INDEX: {
|
||||
@@ -1482,6 +1509,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[b]), val);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
if (JS_IsException(r)) goto disrupt;
|
||||
mach_resolve_forward(&frame->slots[a]);
|
||||
break;
|
||||
}
|
||||
case MACH_LOAD_DYNAMIC: {
|
||||
@@ -1516,12 +1544,13 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
}
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
if (ret < 0) goto disrupt;
|
||||
mach_resolve_forward(&frame->slots[a]);
|
||||
break;
|
||||
}
|
||||
|
||||
/* New record */
|
||||
case MACH_NEWRECORD: {
|
||||
JSValue obj = JS_NewObject(ctx);
|
||||
JSValue obj = b > 0 ? JS_NewObjectCap(ctx, b) : JS_NewObject(ctx);
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
if (JS_IsException(obj)) goto disrupt;
|
||||
frame->slots[a] = obj;
|
||||
@@ -1603,6 +1632,22 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||
ctx->reg_current_frame = JS_NULL;
|
||||
if (JS_IsException(ret)) goto disrupt;
|
||||
#ifdef VALIDATE_GC
|
||||
if (JS_IsPtr(ret)) {
|
||||
void *rp = JS_VALUE_GET_PTR(ret);
|
||||
if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) {
|
||||
if (!is_ct_ptr(ctx, rp)) {
|
||||
int magic = (fn->kind == JS_FUNC_KIND_C) ? fn->u.cfunc.magic : -1;
|
||||
void *cfp = (fn->kind == JS_FUNC_KIND_C) ? (void *)fn->u.cfunc.c_function.generic : NULL;
|
||||
fprintf(stderr, "VALIDATE_GC: stale INVOKE result into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u kind=%d magic=%d cfunc=%p caller=%s (%s)\n",
|
||||
b, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc - 1, fn->kind,
|
||||
magic, cfp,
|
||||
code->name_cstr ? code->name_cstr : "?",
|
||||
code->filename_cstr ? code->filename_cstr : "?");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
frame->slots[b] = ret;
|
||||
}
|
||||
break;
|
||||
@@ -1963,11 +2008,9 @@ static int mcode_reg_items(cJSON *it, cJSON **out) {
|
||||
/* record: [1]=dest, [2]=0(const) — no line/col suffix */
|
||||
if (!strcmp(op, "record")) { ADD(1); return c; }
|
||||
|
||||
/* array: [1]=dest, [2]=count(const), [3..]=elements (no line/col suffix) */
|
||||
/* array: [1]=dest, [2]=count(const) — elements added via separate push instrs */
|
||||
if (!strcmp(op, "array")) {
|
||||
ADD(1);
|
||||
int cnt = (int)cJSON_GetArrayItem(it, 2)->valuedouble;
|
||||
for (int j = 0; j < cnt; j++) ADD(3 + j);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -2023,8 +2066,8 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
|
||||
int pinned = 1 + nr_args;
|
||||
for (int i = 0; i < pinned; i++) { first_ref[i] = 0; last_ref[i] = n; }
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON *it = cJSON_GetArrayItem(instrs, i);
|
||||
{ cJSON *it = instrs ? instrs->child : NULL;
|
||||
for (int i = 0; it; i++, it = it->next) {
|
||||
if (!cJSON_IsArray(it)) continue;
|
||||
cJSON *regs[MAX_REG_ITEMS];
|
||||
int rc = mcode_reg_items(it, regs);
|
||||
@@ -2034,7 +2077,7 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
|
||||
if (first_ref[s] < 0) first_ref[s] = i;
|
||||
last_ref[s] = i;
|
||||
}
|
||||
}
|
||||
} }
|
||||
|
||||
/* Step 1a: extend live ranges for closure-captured slots.
|
||||
If a child function captures a parent slot via get/put, that slot must
|
||||
@@ -2056,8 +2099,8 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
|
||||
typedef struct { const char *name; int pos; } LabelPos;
|
||||
int lbl_cap = 32, lbl_n = 0;
|
||||
LabelPos *lbls = sys_malloc(lbl_cap * sizeof(LabelPos));
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON *it = cJSON_GetArrayItem(instrs, i);
|
||||
{ cJSON *it = instrs ? instrs->child : NULL;
|
||||
for (int i = 0; it; i++, it = it->next) {
|
||||
if (cJSON_IsString(it)) {
|
||||
if (lbl_n >= lbl_cap) {
|
||||
lbl_cap *= 2;
|
||||
@@ -2065,23 +2108,23 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
|
||||
}
|
||||
lbls[lbl_n++] = (LabelPos){it->valuestring, i};
|
||||
}
|
||||
}
|
||||
} }
|
||||
/* Find backward jumps and extend live ranges */
|
||||
int changed = 1;
|
||||
while (changed) {
|
||||
changed = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON *it = cJSON_GetArrayItem(instrs, i);
|
||||
cJSON *it = instrs ? instrs->child : NULL;
|
||||
for (int i = 0; it; i++, it = it->next) {
|
||||
if (!cJSON_IsArray(it)) continue;
|
||||
int sz = cJSON_GetArraySize(it);
|
||||
if (sz < 3) continue;
|
||||
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
|
||||
const char *op = it->child->valuestring;
|
||||
const char *target = NULL;
|
||||
if (!strcmp(op, "jump")) {
|
||||
target = cJSON_GetArrayItem(it, 1)->valuestring;
|
||||
target = it->child->next->valuestring;
|
||||
} else if (!strcmp(op, "jump_true") || !strcmp(op, "jump_false") ||
|
||||
!strcmp(op, "jump_not_null")) {
|
||||
target = cJSON_GetArrayItem(it, 2)->valuestring;
|
||||
target = it->child->next->next->valuestring;
|
||||
}
|
||||
if (!target) continue;
|
||||
/* Find label position */
|
||||
@@ -2187,8 +2230,8 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
|
||||
}
|
||||
|
||||
/* Step 3: apply remap to instructions */
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON *it = cJSON_GetArrayItem(instrs, i);
|
||||
{ cJSON *it = instrs ? instrs->child : NULL;
|
||||
for (int i = 0; it; i++, it = it->next) {
|
||||
if (!cJSON_IsArray(it)) continue;
|
||||
cJSON *regs[MAX_REG_ITEMS];
|
||||
int rc = mcode_reg_items(it, regs);
|
||||
@@ -2198,7 +2241,7 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
|
||||
cJSON_SetNumberValue(regs[j], remap[old]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} }
|
||||
|
||||
/* Update nr_slots in the JSON */
|
||||
cJSON_SetNumberValue(nr_slots_j, new_max);
|
||||
@@ -2230,8 +2273,8 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
||||
s.flat_to_pc = sys_malloc((n + 1) * sizeof(int));
|
||||
s.flat_count = n;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
cJSON *it = cJSON_GetArrayItem(instrs, i);
|
||||
{ cJSON *it = instrs ? instrs->child : NULL;
|
||||
for (int i = 0; it; i++, it = it->next) {
|
||||
s.flat_to_pc[i] = s.code_count;
|
||||
if (cJSON_IsString(it)) {
|
||||
ml_label(&s, it->valuestring);
|
||||
@@ -2430,15 +2473,10 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
||||
}
|
||||
/* Array/Object creation */
|
||||
else if (strcmp(op, "array") == 0) {
|
||||
int dest = A1, count = A2;
|
||||
EM(MACH_ABC(MACH_NEWARRAY, dest, 0, 0));
|
||||
for (int j = 0; j < count; j++) {
|
||||
int elem = ml_int(it, 3 + j);
|
||||
EM(MACH_ABC(MACH_PUSH, dest, elem, 0));
|
||||
}
|
||||
EM(MACH_ABC(MACH_NEWARRAY, A1, A2, 0));
|
||||
}
|
||||
else if (strcmp(op, "record") == 0) {
|
||||
EM(MACH_ABC(MACH_NEWRECORD, A1, 0, 0));
|
||||
EM(MACH_ABC(MACH_NEWRECORD, A1, A2, 0));
|
||||
}
|
||||
/* Push/Pop */
|
||||
else if (strcmp(op, "push") == 0) {
|
||||
@@ -2531,7 +2569,7 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
|
||||
/* Unknown opcode — emit NOP */
|
||||
EM(MACH_ABC(MACH_NOP, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
} }
|
||||
/* Sentinel for flat_to_pc */
|
||||
s.flat_to_pc[n] = s.code_count;
|
||||
|
||||
@@ -2670,34 +2708,32 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
|
||||
/* Scan main's instructions */
|
||||
{
|
||||
cJSON *main_instrs = cJSON_GetObjectItemCaseSensitive(main_obj, "instructions");
|
||||
int mn = main_instrs ? cJSON_GetArraySize(main_instrs) : 0;
|
||||
for (int i = 0; i < mn; i++) {
|
||||
cJSON *it = cJSON_GetArrayItem(main_instrs, i);
|
||||
cJSON *it = main_instrs ? main_instrs->child : NULL;
|
||||
for (; it; it = it->next) {
|
||||
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 3) continue;
|
||||
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
|
||||
const char *op = it->child->valuestring;
|
||||
if (!strcmp(op, "function")) {
|
||||
int child_idx = (int)cJSON_GetArrayItem(it, 2)->valuedouble;
|
||||
int child_idx = (int)it->child->next->next->valuedouble;
|
||||
if (child_idx >= 0 && child_idx < func_count)
|
||||
parent_of[child_idx] = func_count; /* main */
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Scan each function's instructions */
|
||||
for (int fi = 0; fi < func_count; fi++) {
|
||||
cJSON *fobj = cJSON_GetArrayItem(funcs_arr, fi);
|
||||
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
|
||||
for (int fi = 0; fobj; fi++, fobj = fobj->next) {
|
||||
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
|
||||
int fn = finstrs ? cJSON_GetArraySize(finstrs) : 0;
|
||||
for (int i = 0; i < fn; i++) {
|
||||
cJSON *it = cJSON_GetArrayItem(finstrs, i);
|
||||
cJSON *it = finstrs ? finstrs->child : NULL;
|
||||
for (; it; it = it->next) {
|
||||
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 3) continue;
|
||||
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
|
||||
const char *op = it->child->valuestring;
|
||||
if (!strcmp(op, "function")) {
|
||||
int child_idx = (int)cJSON_GetArrayItem(it, 2)->valuedouble;
|
||||
int child_idx = (int)it->child->next->next->valuedouble;
|
||||
if (child_idx >= 0 && child_idx < func_count)
|
||||
parent_of[child_idx] = fi;
|
||||
}
|
||||
}
|
||||
}
|
||||
} }
|
||||
|
||||
/* Build per-function capture sets: for each function F, which of its slots
|
||||
are captured by descendant functions via get/put. Captured slots must
|
||||
@@ -2707,17 +2743,16 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
|
||||
memset(cap_slots, 0, (func_count + 1) * sizeof(int *));
|
||||
memset(cap_counts, 0, (func_count + 1) * sizeof(int));
|
||||
|
||||
for (int fi = 0; fi < func_count; fi++) {
|
||||
cJSON *fobj = cJSON_GetArrayItem(funcs_arr, fi);
|
||||
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
|
||||
for (int fi = 0; fobj; fi++, fobj = fobj->next) {
|
||||
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
|
||||
int fn = finstrs ? cJSON_GetArraySize(finstrs) : 0;
|
||||
for (int i = 0; i < fn; i++) {
|
||||
cJSON *it = cJSON_GetArrayItem(finstrs, i);
|
||||
cJSON *it = finstrs ? finstrs->child : NULL;
|
||||
for (; it; it = it->next) {
|
||||
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 4) continue;
|
||||
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
|
||||
const char *op = it->child->valuestring;
|
||||
if (strcmp(op, "get") && strcmp(op, "put")) continue;
|
||||
int slot = (int)cJSON_GetArrayItem(it, 2)->valuedouble;
|
||||
int level = (int)cJSON_GetArrayItem(it, 3)->valuedouble;
|
||||
int slot = (int)it->child->next->next->valuedouble;
|
||||
int level = (int)it->child->next->next->next->valuedouble;
|
||||
/* Walk up parent chain to find the ancestor whose slot is referenced */
|
||||
int ancestor = fi;
|
||||
for (int l = 0; l < level && ancestor >= 0; l++)
|
||||
@@ -2733,7 +2768,7 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
|
||||
cap_slots[ancestor][cap_counts[ancestor]++] = slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
} }
|
||||
|
||||
/* Compress registers for functions that exceed 8-bit slot limits.
|
||||
Save remap tables so we can fix get/put parent_slot references. */
|
||||
@@ -2741,9 +2776,11 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
|
||||
int *remap_sizes = sys_malloc((func_count + 1) * sizeof(int));
|
||||
memset(remaps, 0, (func_count + 1) * sizeof(int *));
|
||||
|
||||
for (int i = 0; i < func_count; i++)
|
||||
remaps[i] = mcode_compress_regs(cJSON_GetArrayItem(funcs_arr, i),
|
||||
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
|
||||
for (int i = 0; fobj; i++, fobj = fobj->next)
|
||||
remaps[i] = mcode_compress_regs(fobj,
|
||||
&remap_sizes[i], cap_slots[i], cap_counts[i]);
|
||||
}
|
||||
/* main is stored at index func_count in our arrays */
|
||||
remaps[func_count] = mcode_compress_regs(main_obj,
|
||||
&remap_sizes[func_count], cap_slots[func_count], cap_counts[func_count]);
|
||||
@@ -2755,16 +2792,15 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
|
||||
sys_free(cap_counts);
|
||||
|
||||
/* Fix up get/put parent_slot references using ancestor remap tables */
|
||||
for (int fi = 0; fi < func_count; fi++) {
|
||||
cJSON *fobj = cJSON_GetArrayItem(funcs_arr, fi);
|
||||
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
|
||||
for (int fi = 0; fobj; fi++, fobj = fobj->next) {
|
||||
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
|
||||
int fn = finstrs ? cJSON_GetArraySize(finstrs) : 0;
|
||||
for (int i = 0; i < fn; i++) {
|
||||
cJSON *it = cJSON_GetArrayItem(finstrs, i);
|
||||
cJSON *it = finstrs ? finstrs->child : NULL;
|
||||
for (; it; it = it->next) {
|
||||
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 4) continue;
|
||||
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
|
||||
const char *op = it->child->valuestring;
|
||||
if (strcmp(op, "get") && strcmp(op, "put")) continue;
|
||||
int level = (int)cJSON_GetArrayItem(it, 3)->valuedouble;
|
||||
int level = (int)it->child->next->next->next->valuedouble;
|
||||
/* Walk up parent chain 'level' times to find ancestor */
|
||||
int ancestor = fi;
|
||||
for (int l = 0; l < level && ancestor >= 0; l++) {
|
||||
@@ -2773,14 +2809,14 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
|
||||
if (ancestor < 0) continue; /* unknown parent — leave as is */
|
||||
int *anc_remap = remaps[ancestor];
|
||||
if (!anc_remap) continue; /* ancestor wasn't compressed */
|
||||
cJSON *slot_item = cJSON_GetArrayItem(it, 2);
|
||||
cJSON *slot_item = it->child->next->next;
|
||||
int old_slot = (int)slot_item->valuedouble;
|
||||
if (old_slot >= 0 && old_slot < remap_sizes[ancestor]) {
|
||||
int new_slot = anc_remap[old_slot];
|
||||
cJSON_SetNumberValue(slot_item, new_slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
} }
|
||||
|
||||
/* Free remap tables */
|
||||
for (int i = 0; i <= func_count; i++)
|
||||
@@ -2794,8 +2830,10 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
|
||||
if (func_count > 0) {
|
||||
compiled = sys_malloc(func_count * sizeof(MachCode *));
|
||||
memset(compiled, 0, func_count * sizeof(MachCode *));
|
||||
for (int i = 0; i < func_count; i++)
|
||||
compiled[i] = mcode_lower_func(cJSON_GetArrayItem(funcs_arr, i), filename);
|
||||
{ cJSON *fobj = funcs_arr->child;
|
||||
for (int i = 0; fobj; i++, fobj = fobj->next)
|
||||
compiled[i] = mcode_lower_func(fobj, filename);
|
||||
}
|
||||
}
|
||||
|
||||
/* Compile main */
|
||||
|
||||
@@ -9,6 +9,15 @@
|
||||
#include "quickjs-internal.h"
|
||||
#include <math.h>
|
||||
|
||||
/* Non-inline wrappers for static inline functions in quickjs.h */
|
||||
JSValue qbe_new_float64(JSContext *ctx, double 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) */
|
||||
enum {
|
||||
QBE_CMP_EQ = 0,
|
||||
@@ -42,6 +51,16 @@ 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_ThrowTypeError(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);
|
||||
}
|
||||
@@ -277,19 +296,37 @@ void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
|
||||
|
||||
typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
|
||||
|
||||
/* Table mapping fn_idx → outer_fp at creation time.
|
||||
Valid for single-threaded, non-recursive closure patterns. */
|
||||
#define MAX_QBE_FUNCTIONS 256
|
||||
static void *g_outer_fp[MAX_QBE_FUNCTIONS];
|
||||
/* Per-module function registry.
|
||||
Each native .cm module gets its own dylib. When a module creates closures
|
||||
via cell_rt_make_function, we record the dylib handle so the trampoline
|
||||
can look up the correct cell_fn_N in the right dylib. */
|
||||
#define MAX_NATIVE_FN 4096
|
||||
|
||||
static struct {
|
||||
void *dl_handle;
|
||||
int fn_idx;
|
||||
void *outer_fp;
|
||||
} g_native_fn_registry[MAX_NATIVE_FN];
|
||||
|
||||
static int g_native_fn_count = 0;
|
||||
|
||||
/* Set before executing a native module's cell_main */
|
||||
static void *g_current_dl_handle = NULL;
|
||||
|
||||
static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val,
|
||||
int argc, JSValue *argv, int magic) {
|
||||
char name[64];
|
||||
snprintf(name, sizeof(name), "cell_fn_%d", magic);
|
||||
if (magic < 0 || magic >= g_native_fn_count)
|
||||
return JS_ThrowTypeError(ctx, "invalid native function id %d", magic);
|
||||
|
||||
cell_compiled_fn fn = (cell_compiled_fn)dlsym(RTLD_DEFAULT, name);
|
||||
void *handle = g_native_fn_registry[magic].dl_handle;
|
||||
int fn_idx = g_native_fn_registry[magic].fn_idx;
|
||||
|
||||
char name[64];
|
||||
snprintf(name, sizeof(name), "cell_fn_%d", fn_idx);
|
||||
|
||||
cell_compiled_fn fn = (cell_compiled_fn)dlsym(handle, name);
|
||||
if (!fn)
|
||||
return JS_ThrowTypeError(ctx, "native function %s not found", name);
|
||||
return JS_ThrowTypeError(ctx, "native function %s not found in dylib", name);
|
||||
|
||||
/* Allocate frame: slot 0 = this, slots 1..argc = args */
|
||||
JSValue frame[512];
|
||||
@@ -299,17 +336,22 @@ static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val,
|
||||
frame[1 + i] = argv[i];
|
||||
|
||||
/* Link to outer frame for closure access */
|
||||
if (magic >= 0 && magic < MAX_QBE_FUNCTIONS)
|
||||
frame[QBE_FRAME_OUTER_SLOT] = (JSValue)(uintptr_t)g_outer_fp[magic];
|
||||
frame[QBE_FRAME_OUTER_SLOT] = (JSValue)(uintptr_t)g_native_fn_registry[magic].outer_fp;
|
||||
|
||||
return fn(ctx, frame);
|
||||
}
|
||||
|
||||
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp) {
|
||||
if (fn_idx >= 0 && fn_idx < MAX_QBE_FUNCTIONS)
|
||||
g_outer_fp[fn_idx] = outer_fp;
|
||||
if (g_native_fn_count >= MAX_NATIVE_FN)
|
||||
return JS_ThrowTypeError(ctx, "too many native functions (max %d)", MAX_NATIVE_FN);
|
||||
|
||||
int global_id = g_native_fn_count++;
|
||||
g_native_fn_registry[global_id].dl_handle = g_current_dl_handle;
|
||||
g_native_fn_registry[global_id].fn_idx = (int)fn_idx;
|
||||
g_native_fn_registry[global_id].outer_fp = outer_fp;
|
||||
|
||||
return JS_NewCFunction2(ctx, (JSCFunction *)cell_fn_trampoline, "native_fn",
|
||||
255, JS_CFUNC_generic_magic, (int)fn_idx);
|
||||
255, JS_CFUNC_generic_magic, global_id);
|
||||
}
|
||||
|
||||
/* --- Frame-based function calling --- */
|
||||
@@ -423,6 +465,15 @@ JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b) {
|
||||
return JS_NewBool(ctx, a != b);
|
||||
}
|
||||
|
||||
/* --- Type check: is_proxy (function with arity 2) --- */
|
||||
|
||||
int cell_rt_is_proxy(JSContext *ctx, JSValue v) {
|
||||
(void)ctx;
|
||||
if (!JS_IsFunction(v)) return 0;
|
||||
JSFunction *fn = JS_VALUE_GET_FUNCTION(v);
|
||||
return fn->length == 2;
|
||||
}
|
||||
|
||||
/* --- Disruption --- */
|
||||
|
||||
void cell_rt_disrupt(JSContext *ctx) {
|
||||
@@ -430,20 +481,60 @@ void cell_rt_disrupt(JSContext *ctx) {
|
||||
}
|
||||
|
||||
/* --- Module entry point ---
|
||||
Called as symbol(ctx) by os.dylib_symbol. Looks up cell_main
|
||||
in the loaded dylib, builds a heap-allocated frame (so closures
|
||||
can reference it after the module returns), and runs the module body. */
|
||||
Loads a native .cm module from a dylib handle.
|
||||
Looks up cell_main, builds a heap-allocated frame, sets
|
||||
g_current_dl_handle so closures register in the right module. */
|
||||
|
||||
JSValue cell_rt_module_entry(JSContext *ctx) {
|
||||
cell_compiled_fn fn = (cell_compiled_fn)dlsym(RTLD_DEFAULT, "cell_main");
|
||||
JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle) {
|
||||
cell_compiled_fn fn = (cell_compiled_fn)dlsym(dl_handle, "cell_main");
|
||||
if (!fn)
|
||||
return JS_ThrowTypeError(ctx, "cell_main not found in loaded dylib");
|
||||
return JS_ThrowTypeError(ctx, "cell_main not found in native module dylib");
|
||||
|
||||
/* Set current handle so cell_rt_make_function registers closures
|
||||
against this module's dylib */
|
||||
void *prev_handle = g_current_dl_handle;
|
||||
g_current_dl_handle = dl_handle;
|
||||
|
||||
/* Heap-allocate so closures created in cell_main can reference
|
||||
this frame after the module entry returns. */
|
||||
JSValue *frame = calloc(512, sizeof(JSValue));
|
||||
if (!frame)
|
||||
if (!frame) {
|
||||
g_current_dl_handle = prev_handle;
|
||||
return JS_ThrowTypeError(ctx, "frame allocation failed");
|
||||
}
|
||||
|
||||
return fn(ctx, frame);
|
||||
JSValue result = fn(ctx, frame);
|
||||
g_current_dl_handle = prev_handle;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Load a native module from a dylib handle, trying a named symbol first.
|
||||
Falls back to cell_main if the named symbol is not found. */
|
||||
JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const char *sym_name) {
|
||||
cell_compiled_fn fn = NULL;
|
||||
if (sym_name)
|
||||
fn = (cell_compiled_fn)dlsym(dl_handle, sym_name);
|
||||
if (!fn)
|
||||
fn = (cell_compiled_fn)dlsym(dl_handle, "cell_main");
|
||||
if (!fn)
|
||||
return JS_ThrowTypeError(ctx, "symbol not found in native module dylib");
|
||||
|
||||
void *prev_handle = g_current_dl_handle;
|
||||
g_current_dl_handle = dl_handle;
|
||||
|
||||
JSValue *frame = calloc(512, sizeof(JSValue));
|
||||
if (!frame) {
|
||||
g_current_dl_handle = prev_handle;
|
||||
return JS_ThrowTypeError(ctx, "frame allocation failed");
|
||||
}
|
||||
|
||||
JSValue result = fn(ctx, frame);
|
||||
g_current_dl_handle = prev_handle;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Backward-compat: uses RTLD_DEFAULT (works when dylib opened with RTLD_GLOBAL) */
|
||||
JSValue cell_rt_module_entry(JSContext *ctx) {
|
||||
void *handle = dlopen(NULL, RTLD_LAZY);
|
||||
return cell_rt_native_module_load(ctx, handle);
|
||||
}
|
||||
|
||||
@@ -155,8 +155,9 @@ static const JSCFunctionListEntry js_actor_funcs[] = {
|
||||
MIST_FUNC_DEF(actor, clock, 1),
|
||||
};
|
||||
|
||||
JSValue js_actor_use(JSContext *js) {
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js,mod,js_actor_funcs,countof(js_actor_funcs));
|
||||
return mod;
|
||||
}
|
||||
JSValue js_core_actor_use(JSContext *js) {
|
||||
JS_FRAME(js);
|
||||
JS_ROOT(mod, JS_NewObject(js));
|
||||
JS_SetPropertyFunctionList(js, mod.val, js_actor_funcs, countof(js_actor_funcs));
|
||||
JS_RETURN(mod.val);
|
||||
}
|
||||
|
||||
@@ -95,9 +95,12 @@
|
||||
/* test the GC by forcing it before each object allocation */
|
||||
// #define FORCE_GC_AT_MALLOC
|
||||
|
||||
#define POISON_HEAP
|
||||
/* POISON_HEAP: Use ASan's memory poisoning to detect stale pointer access */
|
||||
#ifdef POISON_HEAP
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* HEAP_CHECK: validate heap pointers at JS_VALUE_GET_* macros */
|
||||
// #define HEAP_CHECK
|
||||
|
||||
#if defined(__has_feature)
|
||||
#if __has_feature(address_sanitizer)
|
||||
#define HAVE_ASAN 1
|
||||
@@ -106,25 +109,6 @@
|
||||
#define HAVE_ASAN 1
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_ASAN
|
||||
#include <sanitizer/asan_interface.h>
|
||||
#define gc_poison_region(addr, size) __asan_poison_memory_region((addr), (size))
|
||||
#define gc_unpoison_region(addr, size) __asan_unpoison_memory_region((addr), (size))
|
||||
#else
|
||||
/* Fallback: no-op when not building with ASan */
|
||||
#define gc_poison_region(addr, size) ((void)0)
|
||||
#define gc_unpoison_region(addr, size) ((void)0)
|
||||
#endif
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static inline size_t poison_page_align(size_t size) {
|
||||
size_t ps = (size_t)sysconf(_SC_PAGESIZE);
|
||||
return (size + ps - 1) & ~(ps - 1);
|
||||
}
|
||||
#endif /* POISON_HEAP */
|
||||
|
||||
#ifdef HAVE_ASAN
|
||||
static struct JSContext *__asan_js_ctx;
|
||||
#endif
|
||||
@@ -303,14 +287,27 @@ typedef enum JSErrorEnum {
|
||||
|
||||
/* Forward declaration for bytecode freeing */
|
||||
|
||||
#define JS_VALUE_GET_BLOB(v) ((JSBlob *)JS_VALUE_GET_PTR (v))
|
||||
#define JS_VALUE_GET_CODE(v) (JS_VALUE_GET_PTR (v))
|
||||
|
||||
#ifdef HEAP_CHECK
|
||||
void heap_check_fail(void *ptr, struct JSContext *ctx);
|
||||
#define JS_VALUE_GET_ARRAY(v) ((JSArray *)heap_check_chase(ctx, v))
|
||||
#define JS_VALUE_GET_OBJ(v) ((JSRecord *)heap_check_chase(ctx, v))
|
||||
#define JS_VALUE_GET_TEXT(v) ((JSText *)heap_check_chase(ctx, v))
|
||||
#define JS_VALUE_GET_FUNCTION(v) ((JSFunction *)heap_check_chase(ctx, v))
|
||||
#define JS_VALUE_GET_FRAME(v) ((JSFrame *)heap_check_chase(ctx, v))
|
||||
#define JS_VALUE_GET_STRING(v) ((JSText *)heap_check_chase(ctx, v))
|
||||
#define JS_VALUE_GET_RECORD(v) ((JSRecord *)heap_check_chase(ctx, v))
|
||||
#else
|
||||
#define JS_VALUE_GET_ARRAY(v) ((JSArray *)chase (v))
|
||||
#define JS_VALUE_GET_OBJ(v) ((JSRecord *)chase (v))
|
||||
#define JS_VALUE_GET_TEXT(v) ((JSText *)chase (v))
|
||||
#define JS_VALUE_GET_BLOB(v) ((JSBlob *)JS_VALUE_GET_PTR (v))
|
||||
#define JS_VALUE_GET_FUNCTION(v) ((JSFunction *)chase (v))
|
||||
#define JS_VALUE_GET_FRAME(v) ((JSFrame *)chase (v))
|
||||
#define JS_VALUE_GET_CODE(v) (JS_VALUE_GET_PTR (v))
|
||||
#define JS_VALUE_GET_STRING(v) ((JSText *)chase (v))
|
||||
#define JS_VALUE_GET_RECORD(v) ((JSRecord *)chase (v))
|
||||
#endif
|
||||
|
||||
/* Compatibility: JS_TAG_STRING is an alias for text type checks */
|
||||
#define JS_TAG_STRING JS_TAG_STRING_IMM
|
||||
@@ -333,9 +330,8 @@ static inline objhdr_t objhdr_set_cap56 (objhdr_t h, uint64_t cap) {
|
||||
#else
|
||||
#define BUDDY_MIN_ORDER 9 /* 512B minimum on 32-bit */
|
||||
#endif
|
||||
#define BUDDY_MAX_ORDER 28 /* 256MB maximum */
|
||||
#define BUDDY_LEVELS (BUDDY_MAX_ORDER - BUDDY_MIN_ORDER + 1)
|
||||
#define BUDDY_POOL_SIZE (1ULL << BUDDY_MAX_ORDER)
|
||||
#define BUDDY_MAX_LEVELS 40 /* supports pools up to 2^(BUDDY_MIN_ORDER+39) */
|
||||
#define BUDDY_DEFAULT_POOL (1ULL << 24) /* 16MB initial pool */
|
||||
|
||||
typedef struct BuddyBlock {
|
||||
struct BuddyBlock *next;
|
||||
@@ -344,15 +340,26 @@ typedef struct BuddyBlock {
|
||||
uint8_t is_free;
|
||||
} BuddyBlock;
|
||||
|
||||
typedef struct BuddyPool {
|
||||
struct BuddyPool *next;
|
||||
uint8_t *base;
|
||||
size_t total_size;
|
||||
uint8_t max_order; /* log2(total_size) */
|
||||
uint32_t alloc_count; /* outstanding allocations */
|
||||
BuddyBlock *free_lists[BUDDY_MAX_LEVELS];
|
||||
} BuddyPool;
|
||||
|
||||
typedef struct BuddyAllocator {
|
||||
uint8_t *base; /* 256MB base address */
|
||||
size_t total_size; /* 256MB */
|
||||
BuddyBlock *free_lists[BUDDY_LEVELS];
|
||||
uint8_t initialized;
|
||||
BuddyPool *pools; /* linked list, newest first */
|
||||
size_t next_pool_size; /* next pool doubles from this */
|
||||
size_t initial_size; /* starting pool size */
|
||||
size_t cap; /* 0 = no cap */
|
||||
size_t total_mapped; /* sum of all pool sizes */
|
||||
} BuddyAllocator;
|
||||
|
||||
/* Forward declarations for buddy allocator functions */
|
||||
static void buddy_destroy (BuddyAllocator *b);
|
||||
static size_t buddy_max_block (BuddyAllocator *b);
|
||||
|
||||
/* controls a host of contexts, handing out memory and scheduling */
|
||||
struct JSRuntime {
|
||||
@@ -838,6 +845,18 @@ static inline objhdr_t *chase(JSValue v) {
|
||||
return oh;
|
||||
}
|
||||
|
||||
/* Resolve a forward pointer in-place. After rec_resize the old record
|
||||
gets a forward header; any JSValue slot still pointing at it must be
|
||||
updated to follow the chain to the live copy. */
|
||||
static inline void mach_resolve_forward(JSValue *slot) {
|
||||
if (JS_IsPtr(*slot)) {
|
||||
objhdr_t h = *(objhdr_t *)JS_VALUE_GET_PTR(*slot);
|
||||
if (objhdr_type(h) == OBJ_FORWARD) {
|
||||
*slot = JS_MKPTR(objhdr_fwd_ptr(h));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Inline type checks — use these in the VM dispatch loop to avoid
|
||||
function call overhead. The public API (JS_IsArray etc. in quickjs.h)
|
||||
remains non-inline for external callers; those wrappers live in runtime.c. */
|
||||
@@ -1063,6 +1082,11 @@ struct JSContext {
|
||||
uint8_t *heap_end; /* end of block */
|
||||
size_t current_block_size; /* current block size (64KB initially) */
|
||||
size_t next_block_size; /* doubles if <10% recovered after GC */
|
||||
int gc_poor_streak; /* consecutive poor-recovery GC cycles */
|
||||
|
||||
/* GC stats (lightweight, always on) */
|
||||
uint64_t gc_count; /* number of GC cycles */
|
||||
uint64_t gc_bytes_copied; /* total bytes copied across all GCs */
|
||||
|
||||
/* Constant text pool — compilation constants */
|
||||
uint8_t *ct_base; /* pool base */
|
||||
@@ -1085,6 +1109,7 @@ struct JSContext {
|
||||
|
||||
JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */
|
||||
JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */
|
||||
JSLocalRef *top_local_ref; /* for JS_LOCAL macro - GC updates C locals through pointers */
|
||||
CCallRoot *c_call_root; /* stack of auto-rooted C call argv arrays */
|
||||
|
||||
int class_count; /* size of class_array and class_proto */
|
||||
@@ -1178,9 +1203,28 @@ static int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size);
|
||||
|
||||
/* Helper to check if a pointer is in constant text pool memory */
|
||||
static inline int is_ct_ptr (JSContext *ctx, void *ptr) {
|
||||
return (uint8_t *)ptr >= ctx->ct_base && (uint8_t *)ptr < ctx->ct_end;
|
||||
uint8_t *p = (uint8_t *)ptr;
|
||||
if (p >= ctx->ct_base && p < ctx->ct_end) return 1;
|
||||
/* Also check overflow pages */
|
||||
CTPage *page = (CTPage *)ctx->ct_pages;
|
||||
while (page) {
|
||||
if (p >= page->data && p < page->data + page->size) return 1;
|
||||
page = page->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef HEAP_CHECK
|
||||
static inline objhdr_t *heap_check_chase(JSContext *ctx, JSValue v) {
|
||||
objhdr_t *oh = chase(v);
|
||||
uint8_t *p = (uint8_t *)oh;
|
||||
if (!((p >= ctx->heap_base && p < ctx->heap_free) ||
|
||||
(p >= ctx->ct_base && p < ctx->ct_end)))
|
||||
heap_check_fail(oh, ctx);
|
||||
return oh;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Intern a UTF-32 string as a stone text, returning a JSValue string */
|
||||
|
||||
/* Create a stoned, interned key from a UTF-8 C string.
|
||||
@@ -1214,8 +1258,6 @@ typedef struct JSRegExp {
|
||||
#define obj_is_stone(rec) objhdr_s ((rec)->mist_hdr)
|
||||
#define obj_set_stone(rec) ((rec)->mist_hdr = objhdr_set_s ((rec)->mist_hdr, true))
|
||||
|
||||
#define JS_VALUE_GET_RECORD(v) ((JSRecord *)chase (v))
|
||||
|
||||
/* Get prototype from object (works for both JSRecord and JSRecord since they
|
||||
* share layout) */
|
||||
#define JS_OBJ_GET_PROTO(p) (JS_IsNull(((JSRecord *)(p))->proto) ? NULL : (JSRecord *)JS_VALUE_GET_PTR(((JSRecord *)(p))->proto))
|
||||
|
||||
@@ -146,10 +146,22 @@ typedef struct JSGCRef {
|
||||
struct JSGCRef *prev;
|
||||
} JSGCRef;
|
||||
|
||||
/* JSLocalRef - GC updates C locals through pointers (OCaml-style) */
|
||||
typedef struct JSLocalRef {
|
||||
JSValue *ptr;
|
||||
struct JSLocalRef *prev;
|
||||
} JSLocalRef;
|
||||
|
||||
/* stack of JSGCRef */
|
||||
JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref);
|
||||
JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref);
|
||||
|
||||
/* JS_FRAME/JS_ROOT/JS_LOCAL helpers (for use from cell.h macros) */
|
||||
JSGCRef *JS_GetGCFrame(JSContext *ctx);
|
||||
JSLocalRef *JS_GetLocalFrame(JSContext *ctx);
|
||||
void JS_PushLocalRef(JSContext *ctx, JSLocalRef *ref);
|
||||
void JS_RestoreFrame(JSContext *ctx, JSGCRef *gc_frame, JSLocalRef *local_frame);
|
||||
|
||||
#define JS_PUSH_VALUE(ctx, v) do { JS_PushGCRef(ctx, &v ## _ref); v ## _ref.val = v; } while (0)
|
||||
#define JS_POP_VALUE(ctx, v) v = JS_PopGCRef(ctx, &v ## _ref)
|
||||
|
||||
@@ -305,6 +317,7 @@ typedef JSValue JSCFunctionData (JSContext *ctx, JSValue this_val,
|
||||
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);
|
||||
@@ -501,9 +514,11 @@ JSValue JS_NewObjectProtoClass (JSContext *ctx, JSValue proto, JSClassID class_i
|
||||
JSValue JS_NewObjectClass (JSContext *ctx, int class_id);
|
||||
JSValue JS_NewObjectProto (JSContext *ctx, JSValue proto);
|
||||
JSValue JS_NewObject (JSContext *ctx);
|
||||
JSValue JS_NewObjectCap (JSContext *ctx, uint32_t n);
|
||||
|
||||
JSValue JS_NewArray (JSContext *ctx);
|
||||
JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len);
|
||||
JSValue JS_NewArrayCap (JSContext *ctx, uint32_t cap);
|
||||
JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values);
|
||||
int JS_ArrayPush (JSContext *ctx, JSValue *arr_ptr, JSValue val);
|
||||
JSValue JS_ArrayPop (JSContext *ctx, JSValue obj);
|
||||
|
||||
758
source/runtime.c
758
source/runtime.c
File diff suppressed because it is too large
Load Diff
432
streamline.cm
432
streamline.cm
@@ -60,6 +60,26 @@ var streamline = function(ir, log) {
|
||||
is_record: T_RECORD
|
||||
}
|
||||
|
||||
// simplify_algebra dispatch tables
|
||||
var self_true_ops = {
|
||||
eq_int: true, eq_float: true, eq_text: true, eq_bool: true,
|
||||
is_identical: true,
|
||||
le_int: true, le_float: true, le_text: true,
|
||||
ge_int: true, ge_float: true, ge_text: true
|
||||
}
|
||||
var self_false_ops = {
|
||||
ne_int: true, ne_float: true, ne_text: true, ne_bool: true,
|
||||
lt_int: true, lt_float: true, lt_text: true,
|
||||
gt_int: true, gt_float: true, gt_text: true
|
||||
}
|
||||
var no_clear_ops = {
|
||||
int: true, access: true, true: true, false: true, move: true, null: true,
|
||||
jump: true, jump_true: true, jump_false: true, jump_not_null: true,
|
||||
return: true, disrupt: true,
|
||||
store_field: true, store_index: true, store_dynamic: true,
|
||||
push: true, setarg: true, invoke: true, tail_invoke: true
|
||||
}
|
||||
|
||||
// --- Logging support ---
|
||||
|
||||
var ir_stats = null
|
||||
@@ -119,49 +139,30 @@ var streamline = function(ir, log) {
|
||||
return T_UNKNOWN
|
||||
}
|
||||
|
||||
// track_types reuses write_rules table; move handled specially
|
||||
var track_types = function(slot_types, instr) {
|
||||
var op = instr[0]
|
||||
var rule = null
|
||||
var src_type = null
|
||||
if (op == "access") {
|
||||
slot_types[text(instr[1])] = access_value_type(instr[2])
|
||||
} else if (op == "int") {
|
||||
slot_types[text(instr[1])] = T_INT
|
||||
} else if (op == "true" || op == "false") {
|
||||
slot_types[text(instr[1])] = T_BOOL
|
||||
} else if (op == "null") {
|
||||
slot_types[text(instr[1])] = T_NULL
|
||||
} else if (op == "move") {
|
||||
src_type = slot_types[text(instr[2])]
|
||||
slot_types[text(instr[1])] = src_type != null ? src_type : T_UNKNOWN
|
||||
} else if (op == "concat") {
|
||||
slot_types[text(instr[1])] = T_TEXT
|
||||
} else if (bool_result_ops[op] == true) {
|
||||
slot_types[text(instr[1])] = T_BOOL
|
||||
} else if (op == "load_field" || op == "load_index" || op == "load_dynamic") {
|
||||
slot_types[text(instr[1])] = T_UNKNOWN
|
||||
} else if (op == "invoke" || op == "tail_invoke") {
|
||||
slot_types[text(instr[2])] = T_UNKNOWN
|
||||
} else if (op == "pop" || op == "get") {
|
||||
slot_types[text(instr[1])] = T_UNKNOWN
|
||||
} else if (op == "array") {
|
||||
slot_types[text(instr[1])] = T_ARRAY
|
||||
} else if (op == "record") {
|
||||
slot_types[text(instr[1])] = T_RECORD
|
||||
} else if (op == "function") {
|
||||
slot_types[text(instr[1])] = T_FUNCTION
|
||||
} else if (op == "length") {
|
||||
slot_types[text(instr[1])] = T_INT
|
||||
} else if (op == "negate" || numeric_ops[op] == true) {
|
||||
slot_types[text(instr[1])] = T_UNKNOWN
|
||||
} else if (op == "bitnot" || op == "bitand" || op == "bitor" ||
|
||||
op == "bitxor" || op == "shl" || op == "shr" || op == "ushr") {
|
||||
slot_types[text(instr[1])] = T_INT
|
||||
var typ = null
|
||||
if (op == "move") {
|
||||
src_type = slot_types[instr[2]]
|
||||
slot_types[instr[1]] = src_type != null ? src_type : T_UNKNOWN
|
||||
return null
|
||||
}
|
||||
rule = write_rules[op]
|
||||
if (rule != null) {
|
||||
typ = rule[1]
|
||||
if (typ == null) {
|
||||
typ = access_value_type(instr[2])
|
||||
}
|
||||
slot_types[instr[rule[0]]] = typ
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
var slot_is = function(slot_types, slot, typ) {
|
||||
var known = slot_types[text(slot)]
|
||||
var known = slot_types[slot]
|
||||
if (known == null) {
|
||||
return false
|
||||
}
|
||||
@@ -175,24 +176,22 @@ var streamline = function(ir, log) {
|
||||
}
|
||||
|
||||
var merge_backward = function(backward_types, slot, typ) {
|
||||
var sk = null
|
||||
var existing = null
|
||||
if (!is_number(slot)) {
|
||||
return null
|
||||
}
|
||||
sk = text(slot)
|
||||
existing = backward_types[sk]
|
||||
existing = backward_types[slot]
|
||||
if (existing == null) {
|
||||
backward_types[sk] = typ
|
||||
backward_types[slot] = typ
|
||||
} else if (existing != typ && existing != T_UNKNOWN) {
|
||||
if ((existing == T_INT || existing == T_FLOAT) && typ == T_NUM) {
|
||||
// Keep more specific
|
||||
} else if (existing == T_NUM && (typ == T_INT || typ == T_FLOAT)) {
|
||||
backward_types[sk] = typ
|
||||
backward_types[slot] = typ
|
||||
} else if ((existing == T_INT && typ == T_FLOAT) || (existing == T_FLOAT && typ == T_INT)) {
|
||||
backward_types[sk] = T_NUM
|
||||
backward_types[slot] = T_NUM
|
||||
} else {
|
||||
backward_types[sk] = T_UNKNOWN
|
||||
backward_types[slot] = T_UNKNOWN
|
||||
}
|
||||
}
|
||||
return null
|
||||
@@ -201,8 +200,8 @@ var streamline = function(ir, log) {
|
||||
var seed_params = function(slot_types, param_types, nr_args) {
|
||||
var j = 1
|
||||
while (j <= nr_args) {
|
||||
if (param_types[text(j)] != null) {
|
||||
slot_types[text(j)] = param_types[text(j)]
|
||||
if (param_types[j] != null) {
|
||||
slot_types[j] = param_types[j]
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
@@ -210,10 +209,11 @@ var streamline = function(ir, log) {
|
||||
}
|
||||
|
||||
var seed_writes = function(slot_types, write_types) {
|
||||
var keys = array(write_types)
|
||||
var k = 0
|
||||
while (k < length(keys)) {
|
||||
slot_types[keys[k]] = write_types[keys[k]]
|
||||
while (k < length(write_types)) {
|
||||
if (write_types[k] != null) {
|
||||
slot_types[k] = write_types[k]
|
||||
}
|
||||
k = k + 1
|
||||
}
|
||||
return null
|
||||
@@ -222,7 +222,35 @@ var streamline = function(ir, log) {
|
||||
// =========================================================
|
||||
// Pass: infer_param_types — backward type inference
|
||||
// Scans typed operators to infer immutable parameter types.
|
||||
// Uses data-driven dispatch: each rule is [pos1, type1] or
|
||||
// [pos1, type1, pos2, type2] for operand positions to merge.
|
||||
// =========================================================
|
||||
var param_rules = {
|
||||
subtract: [2, T_NUM, 3, T_NUM], multiply: [2, T_NUM, 3, T_NUM],
|
||||
divide: [2, T_NUM, 3, T_NUM], modulo: [2, T_NUM, 3, T_NUM],
|
||||
pow: [2, T_NUM, 3, T_NUM], negate: [2, T_NUM],
|
||||
eq_int: [2, T_INT, 3, T_INT], ne_int: [2, T_INT, 3, T_INT],
|
||||
lt_int: [2, T_INT, 3, T_INT], gt_int: [2, T_INT, 3, T_INT],
|
||||
le_int: [2, T_INT, 3, T_INT], ge_int: [2, T_INT, 3, T_INT],
|
||||
bitand: [2, T_INT, 3, T_INT], bitor: [2, T_INT, 3, T_INT],
|
||||
bitxor: [2, T_INT, 3, T_INT], shl: [2, T_INT, 3, T_INT],
|
||||
shr: [2, T_INT, 3, T_INT], ushr: [2, T_INT, 3, T_INT],
|
||||
bitnot: [2, T_INT],
|
||||
eq_float: [2, T_FLOAT, 3, T_FLOAT], ne_float: [2, T_FLOAT, 3, T_FLOAT],
|
||||
lt_float: [2, T_FLOAT, 3, T_FLOAT], gt_float: [2, T_FLOAT, 3, T_FLOAT],
|
||||
le_float: [2, T_FLOAT, 3, T_FLOAT], ge_float: [2, T_FLOAT, 3, T_FLOAT],
|
||||
concat: [2, T_TEXT, 3, T_TEXT],
|
||||
eq_text: [2, T_TEXT, 3, T_TEXT], ne_text: [2, T_TEXT, 3, T_TEXT],
|
||||
lt_text: [2, T_TEXT, 3, T_TEXT], gt_text: [2, T_TEXT, 3, T_TEXT],
|
||||
le_text: [2, T_TEXT, 3, T_TEXT], ge_text: [2, T_TEXT, 3, T_TEXT],
|
||||
eq_bool: [2, T_BOOL, 3, T_BOOL], ne_bool: [2, T_BOOL, 3, T_BOOL],
|
||||
not: [2, T_BOOL], and: [2, T_BOOL, 3, T_BOOL], or: [2, T_BOOL, 3, T_BOOL],
|
||||
store_index: [1, T_ARRAY, 2, T_INT], store_field: [1, T_RECORD],
|
||||
push: [1, T_ARRAY],
|
||||
load_index: [2, T_ARRAY, 3, T_INT], load_field: [2, T_RECORD],
|
||||
pop: [2, T_ARRAY]
|
||||
}
|
||||
|
||||
var infer_param_types = function(func) {
|
||||
var instructions = func.instructions
|
||||
var nr_args = func.nr_args != null ? func.nr_args : 0
|
||||
@@ -232,76 +260,36 @@ var streamline = function(ir, log) {
|
||||
var i = 0
|
||||
var j = 0
|
||||
var instr = null
|
||||
var op = null
|
||||
var bt = null
|
||||
var rule = null
|
||||
|
||||
if (instructions == null || nr_args == 0) {
|
||||
return {}
|
||||
return array(func.nr_slots)
|
||||
}
|
||||
|
||||
num_instr = length(instructions)
|
||||
backward_types = {}
|
||||
backward_types = array(func.nr_slots)
|
||||
i = 0
|
||||
while (i < num_instr) {
|
||||
instr = instructions[i]
|
||||
if (is_array(instr)) {
|
||||
op = instr[0]
|
||||
if (op == "subtract" || op == "multiply" ||
|
||||
op == "divide" || op == "modulo" || op == "pow") {
|
||||
merge_backward(backward_types, instr[2], T_NUM)
|
||||
merge_backward(backward_types, instr[3], T_NUM)
|
||||
} else if (op == "negate") {
|
||||
merge_backward(backward_types, instr[2], T_NUM)
|
||||
} else if (op == "eq_int" || op == "ne_int" || op == "lt_int" ||
|
||||
op == "gt_int" || op == "le_int" || op == "ge_int" ||
|
||||
op == "bitand" || op == "bitor" || op == "bitxor" ||
|
||||
op == "shl" || op == "shr" || op == "ushr") {
|
||||
merge_backward(backward_types, instr[2], T_INT)
|
||||
merge_backward(backward_types, instr[3], T_INT)
|
||||
} else if (op == "bitnot") {
|
||||
merge_backward(backward_types, instr[2], T_INT)
|
||||
} else if (op == "eq_float" || op == "ne_float" || op == "lt_float" ||
|
||||
op == "gt_float" || op == "le_float" || op == "ge_float") {
|
||||
merge_backward(backward_types, instr[2], T_FLOAT)
|
||||
merge_backward(backward_types, instr[3], T_FLOAT)
|
||||
} else if (op == "concat" ||
|
||||
op == "eq_text" || op == "ne_text" || op == "lt_text" ||
|
||||
op == "gt_text" || op == "le_text" || op == "ge_text") {
|
||||
merge_backward(backward_types, instr[2], T_TEXT)
|
||||
merge_backward(backward_types, instr[3], T_TEXT)
|
||||
} else if (op == "eq_bool" || op == "ne_bool") {
|
||||
merge_backward(backward_types, instr[2], T_BOOL)
|
||||
merge_backward(backward_types, instr[3], T_BOOL)
|
||||
} else if (op == "not") {
|
||||
merge_backward(backward_types, instr[2], T_BOOL)
|
||||
} else if (op == "and" || op == "or") {
|
||||
merge_backward(backward_types, instr[2], T_BOOL)
|
||||
merge_backward(backward_types, instr[3], T_BOOL)
|
||||
} else if (op == "store_index") {
|
||||
merge_backward(backward_types, instr[1], T_ARRAY)
|
||||
merge_backward(backward_types, instr[2], T_INT)
|
||||
} else if (op == "store_field") {
|
||||
merge_backward(backward_types, instr[1], T_RECORD)
|
||||
} else if (op == "push") {
|
||||
merge_backward(backward_types, instr[1], T_ARRAY)
|
||||
} else if (op == "load_index") {
|
||||
merge_backward(backward_types, instr[2], T_ARRAY)
|
||||
merge_backward(backward_types, instr[3], T_INT)
|
||||
} else if (op == "load_field") {
|
||||
merge_backward(backward_types, instr[2], T_RECORD)
|
||||
} else if (op == "pop") {
|
||||
merge_backward(backward_types, instr[2], T_ARRAY)
|
||||
rule = param_rules[instr[0]]
|
||||
if (rule != null) {
|
||||
merge_backward(backward_types, instr[rule[0]], rule[1])
|
||||
if (length(rule) > 2) {
|
||||
merge_backward(backward_types, instr[rule[2]], rule[3])
|
||||
}
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
param_types = {}
|
||||
param_types = array(func.nr_slots)
|
||||
j = 1
|
||||
while (j <= nr_args) {
|
||||
bt = backward_types[text(j)]
|
||||
bt = backward_types[j]
|
||||
if (bt != null && bt != T_UNKNOWN) {
|
||||
param_types[text(j)] = bt
|
||||
param_types[j] = bt
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
@@ -313,114 +301,85 @@ var streamline = function(ir, log) {
|
||||
// Scans all instructions to find non-parameter slots where
|
||||
// every write produces the same type. These types persist
|
||||
// across label join points.
|
||||
// Uses data-driven dispatch: each rule is [dest_pos, type].
|
||||
// =========================================================
|
||||
var write_rules = {
|
||||
int: [1, T_INT], true: [1, T_BOOL], false: [1, T_BOOL],
|
||||
null: [1, T_NULL], access: [1, null],
|
||||
array: [1, T_ARRAY], record: [1, T_RECORD],
|
||||
function: [1, T_FUNCTION], length: [1, T_INT],
|
||||
bitnot: [1, T_INT], bitand: [1, T_INT], bitor: [1, T_INT],
|
||||
bitxor: [1, T_INT], shl: [1, T_INT], shr: [1, T_INT], ushr: [1, T_INT],
|
||||
negate: [1, T_UNKNOWN], concat: [1, T_TEXT],
|
||||
eq: [1, T_BOOL], ne: [1, T_BOOL], lt: [1, T_BOOL],
|
||||
le: [1, T_BOOL], gt: [1, T_BOOL], ge: [1, T_BOOL], in: [1, T_BOOL],
|
||||
add: [1, T_UNKNOWN], subtract: [1, T_UNKNOWN], multiply: [1, T_UNKNOWN],
|
||||
divide: [1, T_UNKNOWN], modulo: [1, T_UNKNOWN], pow: [1, T_UNKNOWN],
|
||||
move: [1, T_UNKNOWN], load_field: [1, T_UNKNOWN],
|
||||
load_index: [1, T_UNKNOWN], load_dynamic: [1, T_UNKNOWN],
|
||||
pop: [1, T_UNKNOWN], get: [1, T_UNKNOWN],
|
||||
invoke: [2, T_UNKNOWN], tail_invoke: [2, T_UNKNOWN],
|
||||
eq_int: [1, T_BOOL], ne_int: [1, T_BOOL], lt_int: [1, T_BOOL],
|
||||
gt_int: [1, T_BOOL], le_int: [1, T_BOOL], ge_int: [1, T_BOOL],
|
||||
eq_float: [1, T_BOOL], ne_float: [1, T_BOOL], lt_float: [1, T_BOOL],
|
||||
gt_float: [1, T_BOOL], le_float: [1, T_BOOL], ge_float: [1, T_BOOL],
|
||||
eq_text: [1, T_BOOL], ne_text: [1, T_BOOL], lt_text: [1, T_BOOL],
|
||||
gt_text: [1, T_BOOL], le_text: [1, T_BOOL], ge_text: [1, T_BOOL],
|
||||
eq_bool: [1, T_BOOL], ne_bool: [1, T_BOOL],
|
||||
eq_tol: [1, T_BOOL], ne_tol: [1, T_BOOL],
|
||||
not: [1, T_BOOL], and: [1, T_BOOL], or: [1, T_BOOL],
|
||||
is_int: [1, T_BOOL], is_text: [1, T_BOOL], is_num: [1, T_BOOL],
|
||||
is_bool: [1, T_BOOL], is_null: [1, T_BOOL], is_identical: [1, T_BOOL],
|
||||
is_array: [1, T_BOOL], is_func: [1, T_BOOL],
|
||||
is_record: [1, T_BOOL], is_stone: [1, T_BOOL]
|
||||
}
|
||||
|
||||
var infer_slot_write_types = function(func) {
|
||||
var instructions = func.instructions
|
||||
var nr_args = func.nr_args != null ? func.nr_args : 0
|
||||
var num_instr = 0
|
||||
var write_types = null
|
||||
var result = null
|
||||
var keys = null
|
||||
var i = 0
|
||||
var k = 0
|
||||
var instr = null
|
||||
var op = null
|
||||
var slot = 0
|
||||
var typ = null
|
||||
var wt = null
|
||||
var rule = null
|
||||
|
||||
if (instructions == null) {
|
||||
return {}
|
||||
return array(func.nr_slots)
|
||||
}
|
||||
|
||||
num_instr = length(instructions)
|
||||
write_types = {}
|
||||
write_types = array(func.nr_slots)
|
||||
i = 0
|
||||
while (i < num_instr) {
|
||||
instr = instructions[i]
|
||||
if (!is_array(instr)) {
|
||||
i = i + 1
|
||||
continue
|
||||
if (is_array(instr)) {
|
||||
rule = write_rules[instr[0]]
|
||||
if (rule != null) {
|
||||
slot = instr[rule[0]]
|
||||
typ = rule[1]
|
||||
if (typ == null) {
|
||||
typ = access_value_type(instr[2])
|
||||
}
|
||||
if (slot > 0 && slot > nr_args) {
|
||||
merge_backward(write_types, slot, typ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
op = instr[0]
|
||||
slot = -1
|
||||
typ = null
|
||||
|
||||
if (op == "int") {
|
||||
slot = instr[1]
|
||||
typ = T_INT
|
||||
} else if (op == "true" || op == "false") {
|
||||
slot = instr[1]
|
||||
typ = T_BOOL
|
||||
} else if (op == "null") {
|
||||
slot = instr[1]
|
||||
typ = T_NULL
|
||||
} else if (op == "access") {
|
||||
slot = instr[1]
|
||||
typ = access_value_type(instr[2])
|
||||
} else if (op == "array") {
|
||||
slot = instr[1]
|
||||
typ = T_ARRAY
|
||||
} else if (op == "record") {
|
||||
slot = instr[1]
|
||||
typ = T_RECORD
|
||||
} else if (op == "function") {
|
||||
slot = instr[1]
|
||||
typ = T_FUNCTION
|
||||
} else if (op == "length") {
|
||||
slot = instr[1]
|
||||
typ = T_INT
|
||||
} else if (op == "bitnot" || op == "bitand" ||
|
||||
op == "bitor" || op == "bitxor" || op == "shl" ||
|
||||
op == "shr" || op == "ushr") {
|
||||
slot = instr[1]
|
||||
typ = T_INT
|
||||
} else if (op == "negate") {
|
||||
slot = instr[1]
|
||||
typ = T_UNKNOWN
|
||||
} else if (op == "concat") {
|
||||
slot = instr[1]
|
||||
typ = T_TEXT
|
||||
} else if (bool_result_ops[op] == true) {
|
||||
slot = instr[1]
|
||||
typ = T_BOOL
|
||||
} else if (op == "eq" || op == "ne" || op == "lt" ||
|
||||
op == "le" || op == "gt" || op == "ge" || op == "in") {
|
||||
slot = instr[1]
|
||||
typ = T_BOOL
|
||||
} else if (op == "add" || op == "subtract" || op == "multiply" ||
|
||||
op == "divide" || op == "modulo" || op == "pow") {
|
||||
slot = instr[1]
|
||||
typ = T_UNKNOWN
|
||||
} else if (op == "move" || op == "load_field" || op == "load_index" ||
|
||||
op == "load_dynamic" || op == "pop" || op == "get") {
|
||||
slot = instr[1]
|
||||
typ = T_UNKNOWN
|
||||
} else if (op == "invoke" || op == "tail_invoke") {
|
||||
slot = instr[2]
|
||||
typ = T_UNKNOWN
|
||||
}
|
||||
|
||||
if (slot > 0 && slot > nr_args) {
|
||||
merge_backward(write_types, slot, typ != null ? typ : T_UNKNOWN)
|
||||
}
|
||||
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// Filter to only slots with known (non-unknown) types
|
||||
result = {}
|
||||
keys = array(write_types)
|
||||
k = 0
|
||||
while (k < length(keys)) {
|
||||
wt = write_types[keys[k]]
|
||||
if (wt != null && wt != T_UNKNOWN) {
|
||||
result[keys[k]] = wt
|
||||
while (k < length(write_types)) {
|
||||
if (write_types[k] == T_UNKNOWN) {
|
||||
write_types[k] = null
|
||||
}
|
||||
k = k + 1
|
||||
}
|
||||
return result
|
||||
return write_types
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
@@ -431,9 +390,8 @@ var streamline = function(ir, log) {
|
||||
var eliminate_type_checks = function(func, param_types, write_types, log) {
|
||||
var instructions = func.instructions
|
||||
var nr_args = func.nr_args != null ? func.nr_args : 0
|
||||
var has_params = false
|
||||
var has_writes = false
|
||||
var num_instr = 0
|
||||
var base_types = null
|
||||
var slot_types = null
|
||||
var nc = 0
|
||||
var i = 0
|
||||
@@ -460,35 +418,32 @@ var streamline = function(ir, log) {
|
||||
}
|
||||
|
||||
num_instr = length(instructions)
|
||||
|
||||
// Pre-compute base types: params + write-invariant types
|
||||
base_types = array(func.nr_slots)
|
||||
j = 1
|
||||
while (j <= nr_args) {
|
||||
if (param_types[text(j)] != null) {
|
||||
has_params = true
|
||||
if (param_types[j] != null) {
|
||||
base_types[j] = param_types[j]
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
j = 0
|
||||
while (j < length(write_types)) {
|
||||
if (write_types[j] != null) {
|
||||
base_types[j] = write_types[j]
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
has_writes = length(array(write_types)) > 0
|
||||
|
||||
slot_types = {}
|
||||
if (has_params) {
|
||||
seed_params(slot_types, param_types, nr_args)
|
||||
}
|
||||
if (has_writes) {
|
||||
seed_writes(slot_types, write_types)
|
||||
}
|
||||
slot_types = array(base_types)
|
||||
|
||||
i = 0
|
||||
while (i < num_instr) {
|
||||
instr = instructions[i]
|
||||
|
||||
if (is_text(instr)) {
|
||||
slot_types = {}
|
||||
if (has_params) {
|
||||
seed_params(slot_types, param_types, nr_args)
|
||||
}
|
||||
if (has_writes) {
|
||||
seed_writes(slot_types, write_types)
|
||||
}
|
||||
slot_types = array(base_types)
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
@@ -525,14 +480,14 @@ var streamline = function(ir, log) {
|
||||
at: i,
|
||||
before: [instr, next],
|
||||
after: [instructions[i], instructions[i + 1]],
|
||||
why: {slot: src, known_type: slot_types[text(src)], checked_type: checked_type}
|
||||
why: {slot: src, known_type: slot_types[src], checked_type: checked_type}
|
||||
}
|
||||
}
|
||||
slot_types[text(dest)] = T_BOOL
|
||||
slot_types[dest] = T_BOOL
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
src_known = slot_types[text(src)]
|
||||
src_known = slot_types[src]
|
||||
if (src_known != null && src_known != T_UNKNOWN && src_known != checked_type) {
|
||||
if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) {
|
||||
nc = nc + 1
|
||||
@@ -550,7 +505,7 @@ var streamline = function(ir, log) {
|
||||
why: {slot: src, known_type: src_known, checked_type: checked_type}
|
||||
}
|
||||
}
|
||||
slot_types[text(dest)] = T_BOOL
|
||||
slot_types[dest] = T_BOOL
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
@@ -569,12 +524,12 @@ var streamline = function(ir, log) {
|
||||
why: {slot: src, known_type: src_known, checked_type: checked_type}
|
||||
}
|
||||
}
|
||||
slot_types[text(dest)] = T_UNKNOWN
|
||||
slot_types[dest] = T_UNKNOWN
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
slot_types[text(dest)] = T_BOOL
|
||||
slot_types[text(src)] = checked_type
|
||||
slot_types[dest] = T_BOOL
|
||||
slot_types[src] = checked_type
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
@@ -594,14 +549,14 @@ var streamline = function(ir, log) {
|
||||
at: i,
|
||||
before: [instr, next],
|
||||
after: [instructions[i], instructions[i + 1]],
|
||||
why: {slot: src, known_type: slot_types[text(src)], checked_type: checked_type}
|
||||
why: {slot: src, known_type: slot_types[src], checked_type: checked_type}
|
||||
}
|
||||
}
|
||||
slot_types[text(dest)] = T_BOOL
|
||||
slot_types[dest] = T_BOOL
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
src_known = slot_types[text(src)]
|
||||
src_known = slot_types[src]
|
||||
if (src_known != null && src_known != T_UNKNOWN && src_known != checked_type) {
|
||||
if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) {
|
||||
nc = nc + 1
|
||||
@@ -619,7 +574,7 @@ var streamline = function(ir, log) {
|
||||
why: {slot: src, known_type: src_known, checked_type: checked_type}
|
||||
}
|
||||
}
|
||||
slot_types[text(dest)] = T_BOOL
|
||||
slot_types[dest] = T_BOOL
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
@@ -638,17 +593,17 @@ var streamline = function(ir, log) {
|
||||
why: {slot: src, known_type: src_known, checked_type: checked_type}
|
||||
}
|
||||
}
|
||||
slot_types[text(dest)] = T_BOOL
|
||||
slot_types[dest] = T_BOOL
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
slot_types[text(dest)] = T_BOOL
|
||||
slot_types[dest] = T_BOOL
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
slot_types[text(dest)] = T_BOOL
|
||||
slot_types[dest] = T_BOOL
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
@@ -664,7 +619,7 @@ var streamline = function(ir, log) {
|
||||
pass: "eliminate_type_checks",
|
||||
rule: "dynamic_to_field",
|
||||
at: i, before: old_op, after: instr[0],
|
||||
why: {slot: instr[3], known_type: slot_types[text(instr[3])]}
|
||||
why: {slot: instr[3], known_type: slot_types[instr[3]]}
|
||||
}
|
||||
}
|
||||
} else if (slot_is(slot_types, instr[3], T_INT)) {
|
||||
@@ -675,11 +630,11 @@ var streamline = function(ir, log) {
|
||||
pass: "eliminate_type_checks",
|
||||
rule: "dynamic_to_index",
|
||||
at: i, before: old_op, after: instr[0],
|
||||
why: {slot: instr[3], known_type: slot_types[text(instr[3])]}
|
||||
why: {slot: instr[3], known_type: slot_types[instr[3]]}
|
||||
}
|
||||
}
|
||||
}
|
||||
slot_types[text(instr[1])] = T_UNKNOWN
|
||||
slot_types[instr[1]] = T_UNKNOWN
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
@@ -693,7 +648,7 @@ var streamline = function(ir, log) {
|
||||
pass: "eliminate_type_checks",
|
||||
rule: "dynamic_to_field",
|
||||
at: i, before: old_op, after: instr[0],
|
||||
why: {slot: instr[3], known_type: slot_types[text(instr[3])]}
|
||||
why: {slot: instr[3], known_type: slot_types[instr[3]]}
|
||||
}
|
||||
}
|
||||
} else if (slot_is(slot_types, instr[3], T_INT)) {
|
||||
@@ -704,7 +659,7 @@ var streamline = function(ir, log) {
|
||||
pass: "eliminate_type_checks",
|
||||
rule: "dynamic_to_index",
|
||||
at: i, before: old_op, after: instr[0],
|
||||
why: {slot: instr[3], known_type: slot_types[text(instr[3])]}
|
||||
why: {slot: instr[3], known_type: slot_types[instr[3]]}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -745,14 +700,14 @@ var streamline = function(ir, log) {
|
||||
}
|
||||
|
||||
num_instr = length(instructions)
|
||||
slot_values = {}
|
||||
slot_values = array(func.nr_slots)
|
||||
|
||||
i = 0
|
||||
while (i < num_instr) {
|
||||
instr = instructions[i]
|
||||
|
||||
if (is_text(instr)) {
|
||||
slot_values = {}
|
||||
slot_values = array(func.nr_slots)
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
@@ -766,28 +721,25 @@ var streamline = function(ir, log) {
|
||||
|
||||
// Track known constant values
|
||||
if (op == "int") {
|
||||
slot_values[text(instr[1])] = instr[2]
|
||||
slot_values[instr[1]] = instr[2]
|
||||
} else if (op == "access" && is_number(instr[2])) {
|
||||
slot_values[text(instr[1])] = instr[2]
|
||||
slot_values[instr[1]] = instr[2]
|
||||
} else if (op == "true") {
|
||||
slot_values[text(instr[1])] = true
|
||||
slot_values[instr[1]] = true
|
||||
} else if (op == "false") {
|
||||
slot_values[text(instr[1])] = false
|
||||
slot_values[instr[1]] = false
|
||||
} else if (op == "move") {
|
||||
sv = slot_values[text(instr[2])]
|
||||
sv = slot_values[instr[2]]
|
||||
if (sv != null) {
|
||||
slot_values[text(instr[1])] = sv
|
||||
slot_values[instr[1]] = sv
|
||||
} else {
|
||||
slot_values[text(instr[1])] = null
|
||||
slot_values[instr[1]] = null
|
||||
}
|
||||
}
|
||||
|
||||
// Same-slot comparisons
|
||||
if (is_number(instr[2]) && instr[2] == instr[3]) {
|
||||
if (op == "eq_int" || op == "eq_float" || op == "eq_text" ||
|
||||
op == "eq_bool" || op == "is_identical" ||
|
||||
op == "le_int" || op == "le_float" || op == "le_text" ||
|
||||
op == "ge_int" || op == "ge_float" || op == "ge_text") {
|
||||
if (self_true_ops[op] == true) {
|
||||
instructions[i] = ["true", instr[1], instr[ilen - 2], instr[ilen - 1]]
|
||||
if (events != null) {
|
||||
events[] = {
|
||||
@@ -797,14 +749,11 @@ var streamline = function(ir, log) {
|
||||
why: {op: op, slot: instr[2]}
|
||||
}
|
||||
}
|
||||
slot_values[text(instr[1])] = true
|
||||
slot_values[instr[1]] = true
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if (op == "ne_int" || op == "ne_float" || op == "ne_text" ||
|
||||
op == "ne_bool" ||
|
||||
op == "lt_int" || op == "lt_float" || op == "lt_text" ||
|
||||
op == "gt_int" || op == "gt_float" || op == "gt_text") {
|
||||
if (self_false_ops[op] == true) {
|
||||
instructions[i] = ["false", instr[1], instr[ilen - 2], instr[ilen - 1]]
|
||||
if (events != null) {
|
||||
events[] = {
|
||||
@@ -814,7 +763,7 @@ var streamline = function(ir, log) {
|
||||
why: {op: op, slot: instr[2]}
|
||||
}
|
||||
}
|
||||
slot_values[text(instr[1])] = false
|
||||
slot_values[instr[1]] = false
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
@@ -822,15 +771,10 @@ var streamline = function(ir, log) {
|
||||
|
||||
// Clear value tracking for dest-producing ops (not reads-only)
|
||||
if (op == "invoke" || op == "tail_invoke") {
|
||||
slot_values[text(instr[2])] = null
|
||||
} else if (op != "int" && op != "access" && op != "true" &&
|
||||
op != "false" && op != "move" && op != "null" &&
|
||||
op != "jump" && op != "jump_true" && op != "jump_false" &&
|
||||
op != "jump_not_null" && op != "return" && op != "disrupt" &&
|
||||
op != "store_field" && op != "store_index" &&
|
||||
op != "store_dynamic" && op != "push" && op != "setarg") {
|
||||
slot_values[instr[2]] = null
|
||||
} else if (no_clear_ops[op] != true) {
|
||||
if (is_number(instr[1])) {
|
||||
slot_values[text(instr[1])] = null
|
||||
slot_values[instr[1]] = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
76
tests/build_audit.cm
Normal file
76
tests/build_audit.cm
Normal file
@@ -0,0 +1,76 @@
|
||||
// Build system audit tests
|
||||
// Tests deterministic dylib paths, symbol naming, and lib layout
|
||||
|
||||
var shop = use('internal/shop')
|
||||
var runtime = use('runtime')
|
||||
|
||||
return {
|
||||
// ========================================================================
|
||||
// DETERMINISTIC DYLIB PATHS
|
||||
// ========================================================================
|
||||
|
||||
test_dylib_path_deterministic: function() {
|
||||
var path = shop.get_dylib_path('core', 'time')
|
||||
if (!ends_with(path, '/lib/core/time.dylib')) return "dylib path should end with /lib/core/time.dylib, got: " + path
|
||||
},
|
||||
|
||||
test_dylib_path_internal: function() {
|
||||
var path = shop.get_dylib_path('core', 'internal/os')
|
||||
if (!ends_with(path, '/lib/core/internal/os.dylib')) return "dylib path should end with /lib/core/internal/os.dylib, got: " + path
|
||||
},
|
||||
|
||||
test_dylib_path_external_package: function() {
|
||||
var path = shop.get_dylib_path('gitea.pockle.world/john/prosperon', 'sprite')
|
||||
if (!ends_with(path, '/lib/gitea.pockle.world/john/prosperon/sprite.dylib'))
|
||||
return "dylib path should mirror package layout, got: " + path
|
||||
},
|
||||
|
||||
// ========================================================================
|
||||
// SYMBOL NAMING
|
||||
// ========================================================================
|
||||
|
||||
test_c_symbol_module: function() {
|
||||
var sym = shop.c_symbol_for_file('pkg', 'sprite.c')
|
||||
if (sym != 'js_pkg_sprite_use') return "expected js_pkg_sprite_use, got: " + sym
|
||||
},
|
||||
|
||||
test_c_symbol_program: function() {
|
||||
var sym = shop.c_symbol_for_file('pkg', 'game.ce')
|
||||
if (sym != 'js_pkg_game_program') return "expected js_pkg_game_program, got: " + sym
|
||||
},
|
||||
|
||||
test_c_symbol_internal: function() {
|
||||
var sym = shop.c_symbol_for_file('pkg', 'internal/os.c')
|
||||
if (sym != 'js_pkg_internal_os_use') return "expected js_pkg_internal_os_use, got: " + sym
|
||||
},
|
||||
|
||||
test_c_symbol_dotted_package: function() {
|
||||
var sym = shop.c_symbol_for_file('gitea.pockle.world/john/prosperon', 'sprite.c')
|
||||
if (sym != 'js_gitea_pockle_world_john_prosperon_sprite_use')
|
||||
return "expected js_gitea_pockle_world_john_prosperon_sprite_use, got: " + sym
|
||||
},
|
||||
|
||||
// ========================================================================
|
||||
// LIB NAME
|
||||
// ========================================================================
|
||||
|
||||
test_lib_name: function() {
|
||||
var name = shop.lib_name_for_package('gitea.pockle.world/john/prosperon')
|
||||
if (name != 'gitea_pockle_world_john_prosperon')
|
||||
return "expected gitea_pockle_world_john_prosperon, got: " + name
|
||||
},
|
||||
|
||||
test_lib_name_simple: function() {
|
||||
var name = shop.lib_name_for_package('core')
|
||||
if (name != 'core') return "expected core, got: " + name
|
||||
},
|
||||
|
||||
// ========================================================================
|
||||
// SYMBOL PREFIX
|
||||
// ========================================================================
|
||||
|
||||
test_c_symbol_prefix: function() {
|
||||
var prefix = shop.c_symbol_prefix('mypackage')
|
||||
if (prefix != 'js_mypackage_') return "expected js_mypackage_, got: " + prefix
|
||||
}
|
||||
}
|
||||
85
tests/shop_audit.cm
Normal file
85
tests/shop_audit.cm
Normal file
@@ -0,0 +1,85 @@
|
||||
// Shop system audit tests
|
||||
// Tests module resolution, caching, and runtime paths
|
||||
// accessible from user code via use()
|
||||
|
||||
var json = use('json')
|
||||
var runtime = use('runtime')
|
||||
var time_mod = use('time')
|
||||
|
||||
return {
|
||||
// ========================================================================
|
||||
// MODULE RESOLUTION
|
||||
// ========================================================================
|
||||
|
||||
test_use_core_module: function() {
|
||||
if (!is_object(json)) return "use('json') should return an object"
|
||||
if (!is_function(json.encode)) return "json should have encode function"
|
||||
if (!is_function(json.decode)) return "json should have decode function"
|
||||
},
|
||||
|
||||
test_use_returns_cached: function() {
|
||||
var json2 = use('json')
|
||||
if (json != json2) return "two use('json') calls should return same reference"
|
||||
},
|
||||
|
||||
test_use_time_module: function() {
|
||||
if (!is_object(time_mod)) return "use('time') should return an object"
|
||||
},
|
||||
|
||||
test_use_nonexistent_disrupts: function() {
|
||||
var caught = false
|
||||
var _fn = function() {
|
||||
var x = use('nonexistent_xyz_99')
|
||||
} disruption {
|
||||
caught = true
|
||||
}
|
||||
_fn()
|
||||
if (!caught) return "use('nonexistent_xyz_99') should disrupt"
|
||||
},
|
||||
|
||||
// ========================================================================
|
||||
// RUNTIME PATHS
|
||||
// ========================================================================
|
||||
|
||||
test_runtime_shop_path: function() {
|
||||
if (is_null(runtime.shop_path)) return "runtime.shop_path should not be null"
|
||||
if (!is_text(runtime.shop_path)) return "runtime.shop_path should be text"
|
||||
},
|
||||
|
||||
test_runtime_core_path: function() {
|
||||
if (is_null(runtime.core_path)) return "runtime.core_path should not be null"
|
||||
if (!is_text(runtime.core_path)) return "runtime.core_path should be text"
|
||||
},
|
||||
|
||||
test_runtime_is_frozen: function() {
|
||||
if (!is_stone(runtime)) return "runtime should be frozen (stoned)"
|
||||
},
|
||||
|
||||
// ========================================================================
|
||||
// MODULE CACHING BEHAVIOR
|
||||
// ========================================================================
|
||||
|
||||
test_json_encode_decode_roundtrip: function() {
|
||||
var obj = {a: 1, b: "hello", c: [1, 2, 3]}
|
||||
var encoded = json.encode(obj)
|
||||
var decoded = json.decode(encoded)
|
||||
if (decoded.a != 1) return "roundtrip failed for number"
|
||||
if (decoded.b != "hello") return "roundtrip failed for text"
|
||||
if (length(decoded.c) != 3) return "roundtrip failed for array"
|
||||
},
|
||||
|
||||
test_use_blob_module: function() {
|
||||
var b = use('blob')
|
||||
if (!is_object(b)) return "use('blob') should return an object"
|
||||
},
|
||||
|
||||
test_use_math_module: function() {
|
||||
var m = use('math')
|
||||
if (!is_object(m)) return "use('math') should return an object"
|
||||
},
|
||||
|
||||
test_use_random_module: function() {
|
||||
var r = use('random')
|
||||
if (!is_object(r)) return "use('random') should return an object"
|
||||
}
|
||||
}
|
||||
8
time.cm
8
time.cm
@@ -1,13 +1,13 @@
|
||||
// epoch = 0000-01-01 00:00:00 +0000
|
||||
var time = native
|
||||
var time = use('internal/time')
|
||||
|
||||
var now = time.now
|
||||
var computer_zone = time.computer_zone
|
||||
var computer_dst = time.computer_dst
|
||||
|
||||
delete time.now
|
||||
delete time.computer_zone
|
||||
delete time.computer_dst
|
||||
//delete time.now
|
||||
//delete time.computer_zone
|
||||
//delete time.computer_dst
|
||||
|
||||
time.second = 1
|
||||
time.minute = 60
|
||||
|
||||
318
tokenize.cm
318
tokenize.cm
@@ -1,72 +1,11 @@
|
||||
var tokenize = function(src, filename) {
|
||||
var len = length(src)
|
||||
var cp = array(array(src), codepoint)
|
||||
|
||||
var pos = 0
|
||||
var row = 0
|
||||
var col = 0
|
||||
var tokens = []
|
||||
|
||||
// Codepoint constants
|
||||
def CP_LF = 10
|
||||
def CP_CR = 13
|
||||
def CP_TAB = 9
|
||||
def CP_SPACE = 32
|
||||
def CP_BANG = 33
|
||||
def CP_DQUOTE = 34
|
||||
def CP_HASH = 35
|
||||
def CP_DOLLAR = 36
|
||||
def CP_PERCENT = 37
|
||||
def CP_AMP = 38
|
||||
def CP_SQUOTE = 39
|
||||
def CP_LPAREN = 40
|
||||
def CP_RPAREN = 41
|
||||
def CP_STAR = 42
|
||||
def CP_PLUS = 43
|
||||
def CP_COMMA = 44
|
||||
def CP_MINUS = 45
|
||||
def CP_DOT = 46
|
||||
def CP_SLASH = 47
|
||||
def CP_0 = 48
|
||||
def CP_1 = 49
|
||||
def CP_7 = 55
|
||||
def CP_9 = 57
|
||||
def CP_COLON = 58
|
||||
def CP_SEMI = 59
|
||||
def CP_LT = 60
|
||||
def CP_EQ = 61
|
||||
def CP_GT = 62
|
||||
def CP_QMARK = 63
|
||||
def CP_AT = 64
|
||||
def CP_A = 65
|
||||
def CP_B = 66
|
||||
def CP_E = 69
|
||||
def CP_F = 70
|
||||
def CP_O = 79
|
||||
def CP_X = 88
|
||||
def CP_Z = 90
|
||||
def CP_LBRACKET = 91
|
||||
def CP_BSLASH = 92
|
||||
def CP_RBRACKET = 93
|
||||
def CP_CARET = 94
|
||||
def CP_UNDERSCORE = 95
|
||||
def CP_BACKTICK = 96
|
||||
def CP_a = 97
|
||||
def CP_b = 98
|
||||
def CP_e = 101
|
||||
def CP_f = 102
|
||||
def CP_n = 110
|
||||
def CP_o = 111
|
||||
def CP_r = 114
|
||||
def CP_t = 116
|
||||
def CP_u = 117
|
||||
def CP_x = 120
|
||||
def CP_z = 122
|
||||
def CP_LBRACE = 123
|
||||
def CP_PIPE = 124
|
||||
def CP_RBRACE = 125
|
||||
def CP_TILDE = 126
|
||||
|
||||
// Keywords lookup
|
||||
var keywords = {
|
||||
if: "if", in: "in", do: "do", go: "go",
|
||||
@@ -78,21 +17,27 @@ var tokenize = function(src, filename) {
|
||||
disruption: "disruption"
|
||||
}
|
||||
|
||||
var escape_map = {
|
||||
n: "\n", t: "\t", r: "\r", "\\": "\\",
|
||||
"'": "'", "\"": "\"", "`": "`",
|
||||
"0": character(0)
|
||||
}
|
||||
|
||||
var pk = function() {
|
||||
if (pos >= len) return -1
|
||||
return cp[pos]
|
||||
if (pos >= len) return null
|
||||
return src[pos]
|
||||
}
|
||||
|
||||
var pk_at = function(n) {
|
||||
var idx = pos + n
|
||||
if (idx >= len) return -1
|
||||
return cp[idx]
|
||||
if (idx >= len) return null
|
||||
return src[idx]
|
||||
}
|
||||
|
||||
var adv = function() {
|
||||
var c = cp[pos]
|
||||
var c = src[pos]
|
||||
pos = pos + 1
|
||||
if (c == CP_LF) {
|
||||
if (c == "\n") {
|
||||
row = row + 1
|
||||
col = 0
|
||||
} else {
|
||||
@@ -102,17 +47,17 @@ var tokenize = function(src, filename) {
|
||||
}
|
||||
|
||||
var is_digit = function(c) {
|
||||
return c >= CP_0 && c <= CP_9
|
||||
return c >= "0" && c <= "9"
|
||||
}
|
||||
|
||||
var is_hex = function(c) {
|
||||
return (c >= CP_0 && c <= CP_9) || (c >= CP_a && c <= CP_f) || (c >= CP_A && c <= CP_F)
|
||||
return (c >= "0" && c <= "9") || (c >= "a" && c <= "f") || (c >= "A" && c <= "F")
|
||||
}
|
||||
|
||||
var hex_val = function(c) {
|
||||
if (c >= CP_0 && c <= CP_9) return c - CP_0
|
||||
if (c >= CP_a && c <= CP_f) return c - CP_a + 10
|
||||
if (c >= CP_A && c <= CP_F) return c - CP_A + 10
|
||||
if (c >= "0" && c <= "9") return codepoint(c) - codepoint("0")
|
||||
if (c >= "a" && c <= "f") return codepoint(c) - codepoint("a") + 10
|
||||
if (c >= "A" && c <= "F") return codepoint(c) - codepoint("A") + 10
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -127,7 +72,7 @@ var tokenize = function(src, filename) {
|
||||
}
|
||||
|
||||
var is_alpha = function(c) {
|
||||
return (c >= CP_a && c <= CP_z) || (c >= CP_A && c <= CP_Z)
|
||||
return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z")
|
||||
}
|
||||
|
||||
var is_alnum = function(c) {
|
||||
@@ -135,41 +80,36 @@ var tokenize = function(src, filename) {
|
||||
}
|
||||
|
||||
var is_ident_start = function(c) {
|
||||
return is_alpha(c) || c == CP_UNDERSCORE || c == CP_DOLLAR
|
||||
return is_alpha(c) || c == "_" || c == "$"
|
||||
}
|
||||
|
||||
var is_ident_char = function(c) {
|
||||
return is_alnum(c) || c == CP_UNDERSCORE || c == CP_DOLLAR || c == CP_QMARK || c == CP_BANG
|
||||
return is_alnum(c) || c == "_" || c == "$" || c == "?" || c == "!"
|
||||
}
|
||||
|
||||
var substr = function(start, end) {
|
||||
return text(src, start, end)
|
||||
}
|
||||
|
||||
var read_string = function(quote_cp) {
|
||||
var read_string = function(quote) {
|
||||
var start = pos
|
||||
var start_row = row
|
||||
var start_col = col
|
||||
var parts = []
|
||||
var run_start = 0
|
||||
var esc = 0
|
||||
var esc = null
|
||||
var esc_val = null
|
||||
adv() // skip opening quote
|
||||
run_start = pos
|
||||
while (pos < len && pk() != quote_cp) {
|
||||
if (pk() == CP_BSLASH) {
|
||||
while (pos < len && pk() != quote) {
|
||||
if (pk() == "\\") {
|
||||
if (pos > run_start) push(parts, text(src, run_start, pos))
|
||||
adv()
|
||||
esc = adv()
|
||||
if (esc == CP_n) { push(parts, "\n") }
|
||||
else if (esc == CP_t) { push(parts, "\t") }
|
||||
else if (esc == CP_r) { push(parts, "\r") }
|
||||
else if (esc == CP_BSLASH) { push(parts, "\\") }
|
||||
else if (esc == CP_SQUOTE) { push(parts, "'") }
|
||||
else if (esc == CP_DQUOTE) { push(parts, "\"") }
|
||||
else if (esc == CP_0) { push(parts, character(0)) }
|
||||
else if (esc == CP_BACKTICK) { push(parts, "`") }
|
||||
else if (esc == CP_u) { push(parts, read_unicode_escape()) }
|
||||
else { push(parts, character(esc)) }
|
||||
esc_val = escape_map[esc]
|
||||
if (esc_val != null) { push(parts, esc_val) }
|
||||
else if (esc == "u") { push(parts, read_unicode_escape()) }
|
||||
else { push(parts, esc) }
|
||||
run_start = pos
|
||||
} else {
|
||||
adv()
|
||||
@@ -192,33 +132,33 @@ var tokenize = function(src, filename) {
|
||||
var parts = []
|
||||
var run_start = 0
|
||||
var depth = 0
|
||||
var tc = 0
|
||||
var q = 0
|
||||
var tc = null
|
||||
var q = null
|
||||
var interp_start = 0
|
||||
adv() // skip opening backtick
|
||||
run_start = pos
|
||||
while (pos < len && pk() != CP_BACKTICK) {
|
||||
if (pk() == CP_BSLASH && pos + 1 < len) {
|
||||
while (pos < len && pk() != "`") {
|
||||
if (pk() == "\\" && pos + 1 < len) {
|
||||
if (pos > run_start) push(parts, text(src, run_start, pos))
|
||||
push(parts, text(src, pos, pos + 2))
|
||||
adv(); adv()
|
||||
run_start = pos
|
||||
} else if (pk() == CP_DOLLAR && pos + 1 < len && pk_at(1) == CP_LBRACE) {
|
||||
} else if (pk() == "$" && pos + 1 < len && pk_at(1) == "{") {
|
||||
if (pos > run_start) push(parts, text(src, run_start, pos))
|
||||
interp_start = pos
|
||||
adv(); adv() // $ {
|
||||
depth = 1
|
||||
while (pos < len && depth > 0) {
|
||||
tc = pk()
|
||||
if (tc == CP_LBRACE) { depth = depth + 1; adv() }
|
||||
else if (tc == CP_RBRACE) {
|
||||
if (tc == "{") { depth = depth + 1; adv() }
|
||||
else if (tc == "}") {
|
||||
depth = depth - 1
|
||||
adv()
|
||||
}
|
||||
else if (tc == CP_SQUOTE || tc == CP_DQUOTE || tc == CP_BACKTICK) {
|
||||
else if (tc == "'" || tc == "\"" || tc == "`") {
|
||||
q = adv()
|
||||
while (pos < len && pk() != q) {
|
||||
if (pk() == CP_BSLASH && pos + 1 < len) adv()
|
||||
if (pk() == "\\" && pos + 1 < len) adv()
|
||||
adv()
|
||||
}
|
||||
if (pos < len) adv()
|
||||
@@ -245,24 +185,24 @@ var tokenize = function(src, filename) {
|
||||
var start_row = row
|
||||
var start_col = col
|
||||
var raw = ""
|
||||
if (pk() == CP_0 && (pk_at(1) == CP_x || pk_at(1) == CP_X)) {
|
||||
if (pk() == "0" && (pk_at(1) == "x" || pk_at(1) == "X")) {
|
||||
adv(); adv()
|
||||
while (pos < len && (is_hex(pk()) || pk() == CP_UNDERSCORE)) adv()
|
||||
} else if (pk() == CP_0 && (pk_at(1) == CP_b || pk_at(1) == CP_B)) {
|
||||
while (pos < len && (is_hex(pk()) || pk() == "_")) adv()
|
||||
} else if (pk() == "0" && (pk_at(1) == "b" || pk_at(1) == "B")) {
|
||||
adv(); adv()
|
||||
while (pos < len && (pk() == CP_0 || pk() == CP_1 || pk() == CP_UNDERSCORE)) adv()
|
||||
} else if (pk() == CP_0 && (pk_at(1) == CP_o || pk_at(1) == CP_O)) {
|
||||
while (pos < len && (pk() == "0" || pk() == "1" || pk() == "_")) adv()
|
||||
} else if (pk() == "0" && (pk_at(1) == "o" || pk_at(1) == "O")) {
|
||||
adv(); adv()
|
||||
while (pos < len && pk() >= CP_0 && pk() <= CP_7) adv()
|
||||
while (pos < len && pk() >= "0" && pk() <= "7") adv()
|
||||
} else {
|
||||
while (pos < len && (is_digit(pk()) || pk() == CP_UNDERSCORE)) adv()
|
||||
if (pos < len && pk() == CP_DOT) {
|
||||
while (pos < len && (is_digit(pk()) || pk() == "_")) adv()
|
||||
if (pos < len && pk() == ".") {
|
||||
adv()
|
||||
while (pos < len && (is_digit(pk()) || pk() == CP_UNDERSCORE)) adv()
|
||||
while (pos < len && (is_digit(pk()) || pk() == "_")) adv()
|
||||
}
|
||||
if (pos < len && (pk() == CP_e || pk() == CP_E)) {
|
||||
if (pos < len && (pk() == "e" || pk() == "E")) {
|
||||
adv()
|
||||
if (pos < len && (pk() == CP_PLUS || pk() == CP_MINUS)) adv()
|
||||
if (pos < len && (pk() == "+" || pk() == "-")) adv()
|
||||
while (pos < len && is_digit(pk())) adv()
|
||||
}
|
||||
}
|
||||
@@ -305,12 +245,12 @@ var tokenize = function(src, filename) {
|
||||
var start_row = row
|
||||
var start_col = col
|
||||
var raw = ""
|
||||
if (pk_at(1) == CP_SLASH) {
|
||||
while (pos < len && pk() != CP_LF && pk() != CP_CR) adv()
|
||||
if (pk_at(1) == "/") {
|
||||
while (pos < len && pk() != "\n" && pk() != "\r") adv()
|
||||
} else {
|
||||
adv(); adv() // skip /*
|
||||
while (pos < len) {
|
||||
if (pk() == CP_STAR && pk_at(1) == CP_SLASH) {
|
||||
if (pk() == "*" && pk_at(1) == "/") {
|
||||
adv(); adv()
|
||||
break
|
||||
}
|
||||
@@ -359,144 +299,144 @@ var tokenize = function(src, filename) {
|
||||
var start_row = 0
|
||||
var start_col = 0
|
||||
var raw = ""
|
||||
if (c == -1) return false
|
||||
if (c == null) return false
|
||||
|
||||
if (c == CP_LF) {
|
||||
if (c == "\n") {
|
||||
start = pos; start_row = row; start_col = col
|
||||
adv()
|
||||
push(tokens, { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" })
|
||||
return true
|
||||
}
|
||||
if (c == CP_CR) {
|
||||
if (c == "\r") {
|
||||
start = pos; start_row = row; start_col = col
|
||||
adv()
|
||||
if (pos < len && pk() == CP_LF) adv()
|
||||
if (pos < len && pk() == "\n") adv()
|
||||
push(tokens, { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" })
|
||||
return true
|
||||
}
|
||||
if (c == CP_SPACE || c == CP_TAB) {
|
||||
if (c == " " || c == "\t") {
|
||||
start = pos; start_row = row; start_col = col
|
||||
while (pos < len && (pk() == CP_SPACE || pk() == CP_TAB)) adv()
|
||||
while (pos < len && (pk() == " " || pk() == "\t")) adv()
|
||||
raw = substr(start, pos)
|
||||
push(tokens, { kind: "space", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: raw })
|
||||
return true
|
||||
}
|
||||
if (c == CP_SQUOTE || c == CP_DQUOTE) { read_string(c); return true }
|
||||
if (c == CP_BACKTICK) { read_template(); return true }
|
||||
if (c == "'" || c == "\"") { read_string(c); return true }
|
||||
if (c == "`") { read_template(); return true }
|
||||
if (is_digit(c)) { read_number(); return true }
|
||||
if (c == CP_DOT && is_digit(pk_at(1))) { read_number(); return true }
|
||||
if (c == "." && is_digit(pk_at(1))) { read_number(); return true }
|
||||
if (is_ident_start(c)) { read_name(); return true }
|
||||
if (c == CP_SLASH) {
|
||||
if (pk_at(1) == CP_SLASH || pk_at(1) == CP_STAR) { read_comment(); return true }
|
||||
if (pk_at(1) == CP_EQ) { emit_op("/=", 2); return true }
|
||||
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
|
||||
if (c == "/") {
|
||||
if (pk_at(1) == "/" || pk_at(1) == "*") { read_comment(); return true }
|
||||
if (pk_at(1) == "=") { emit_op("/=", 2); return true }
|
||||
if (pk_at(1) == "!") { emit_ident(2); return true }
|
||||
emit_op("/", 1); return true
|
||||
}
|
||||
if (c == CP_STAR) {
|
||||
if (pk_at(1) == CP_STAR) {
|
||||
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
|
||||
if (pk_at(2) == CP_EQ) { emit_op("**=", 3); return true }
|
||||
if (c == "*") {
|
||||
if (pk_at(1) == "*") {
|
||||
if (pk_at(2) == "!") { emit_ident(3); return true }
|
||||
if (pk_at(2) == "=") { emit_op("**=", 3); return true }
|
||||
emit_op("**", 2); return true
|
||||
}
|
||||
if (pk_at(1) == CP_EQ) { emit_op("*=", 2); return true }
|
||||
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
|
||||
if (pk_at(1) == "=") { emit_op("*=", 2); return true }
|
||||
if (pk_at(1) == "!") { emit_ident(2); return true }
|
||||
emit_op("*", 1); return true
|
||||
}
|
||||
if (c == CP_PERCENT) {
|
||||
if (pk_at(1) == CP_EQ) { emit_op("%=", 2); return true }
|
||||
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
|
||||
if (c == "%") {
|
||||
if (pk_at(1) == "=") { emit_op("%=", 2); return true }
|
||||
if (pk_at(1) == "!") { emit_ident(2); return true }
|
||||
emit_op("%", 1); return true
|
||||
}
|
||||
if (c == CP_PLUS) {
|
||||
if (pk_at(1) == CP_EQ) { emit_op("+=", 2); return true }
|
||||
if (pk_at(1) == CP_PLUS) { emit_op("++", 2); return true }
|
||||
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
|
||||
if (c == "+") {
|
||||
if (pk_at(1) == "=") { emit_op("+=", 2); return true }
|
||||
if (pk_at(1) == "+") { emit_op("++", 2); return true }
|
||||
if (pk_at(1) == "!") { emit_ident(2); return true }
|
||||
emit_op("+", 1); return true
|
||||
}
|
||||
if (c == CP_MINUS) {
|
||||
if (pk_at(1) == CP_EQ) { emit_op("-=", 2); return true }
|
||||
if (pk_at(1) == CP_MINUS) { emit_op("--", 2); return true }
|
||||
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
|
||||
if (c == "-") {
|
||||
if (pk_at(1) == "=") { emit_op("-=", 2); return true }
|
||||
if (pk_at(1) == "-") { emit_op("--", 2); return true }
|
||||
if (pk_at(1) == "!") { emit_ident(2); return true }
|
||||
emit_op("-", 1); return true
|
||||
}
|
||||
if (c == CP_LT) {
|
||||
if (pk_at(1) == CP_EQ && pk_at(2) == CP_BANG) { emit_ident(3); return true }
|
||||
if (pk_at(1) == CP_EQ) { emit_op("<=", 2); return true }
|
||||
if (pk_at(1) == CP_LT) {
|
||||
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
|
||||
if (pk_at(2) == CP_EQ) { emit_op("<<=", 3); return true }
|
||||
if (c == "<") {
|
||||
if (pk_at(1) == "=" && pk_at(2) == "!") { emit_ident(3); return true }
|
||||
if (pk_at(1) == "=") { emit_op("<=", 2); return true }
|
||||
if (pk_at(1) == "<") {
|
||||
if (pk_at(2) == "!") { emit_ident(3); return true }
|
||||
if (pk_at(2) == "=") { emit_op("<<=", 3); return true }
|
||||
emit_op("<<", 2); return true
|
||||
}
|
||||
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
|
||||
if (pk_at(1) == "!") { emit_ident(2); return true }
|
||||
emit_op("<", 1); return true
|
||||
}
|
||||
if (c == CP_GT) {
|
||||
if (pk_at(1) == CP_EQ && pk_at(2) == CP_BANG) { emit_ident(3); return true }
|
||||
if (pk_at(1) == CP_EQ) { emit_op(">=", 2); return true }
|
||||
if (pk_at(1) == CP_GT) {
|
||||
if (pk_at(2) == CP_GT) {
|
||||
if (pk_at(3) == CP_BANG) { emit_ident(4); return true }
|
||||
if (pk_at(3) == CP_EQ) { emit_op(">>>=", 4); return true }
|
||||
if (c == ">") {
|
||||
if (pk_at(1) == "=" && pk_at(2) == "!") { emit_ident(3); return true }
|
||||
if (pk_at(1) == "=") { emit_op(">=", 2); return true }
|
||||
if (pk_at(1) == ">") {
|
||||
if (pk_at(2) == ">") {
|
||||
if (pk_at(3) == "!") { emit_ident(4); return true }
|
||||
if (pk_at(3) == "=") { emit_op(">>>=", 4); return true }
|
||||
emit_op(">>>", 3); return true
|
||||
}
|
||||
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
|
||||
if (pk_at(2) == CP_EQ) { emit_op(">>=", 3); return true }
|
||||
if (pk_at(2) == "!") { emit_ident(3); return true }
|
||||
if (pk_at(2) == "=") { emit_op(">>=", 3); return true }
|
||||
emit_op(">>", 2); return true
|
||||
}
|
||||
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
|
||||
if (pk_at(1) == "!") { emit_ident(2); return true }
|
||||
emit_op(">", 1); return true
|
||||
}
|
||||
if (c == CP_EQ) {
|
||||
if (pk_at(1) == CP_EQ) {
|
||||
if (pk_at(2) == CP_EQ) { emit_op("===", 3); return true }
|
||||
if (c == "=") {
|
||||
if (pk_at(1) == "=") {
|
||||
if (pk_at(2) == "=") { emit_op("===", 3); return true }
|
||||
emit_op("==", 2); return true
|
||||
}
|
||||
if (pk_at(1) == CP_GT) { emit_op("=>", 2); return true }
|
||||
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
|
||||
if (pk_at(1) == ">") { emit_op("=>", 2); return true }
|
||||
if (pk_at(1) == "!") { emit_ident(2); return true }
|
||||
emit_op("=", 1); return true
|
||||
}
|
||||
if (c == CP_BANG) {
|
||||
if (pk_at(1) == CP_EQ) {
|
||||
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
|
||||
if (pk_at(2) == CP_EQ) { emit_op("!==", 3); return true }
|
||||
if (c == "!") {
|
||||
if (pk_at(1) == "=") {
|
||||
if (pk_at(2) == "!") { emit_ident(3); return true }
|
||||
if (pk_at(2) == "=") { emit_op("!==", 3); return true }
|
||||
emit_op("!=", 2); return true
|
||||
}
|
||||
emit_op("!", 1); return true
|
||||
}
|
||||
if (c == CP_AMP) {
|
||||
if (pk_at(1) == CP_AMP) {
|
||||
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
|
||||
if (pk_at(2) == CP_EQ) { emit_op("&&=", 3); return true }
|
||||
if (c == "&") {
|
||||
if (pk_at(1) == "&") {
|
||||
if (pk_at(2) == "!") { emit_ident(3); return true }
|
||||
if (pk_at(2) == "=") { emit_op("&&=", 3); return true }
|
||||
emit_op("&&", 2); return true
|
||||
}
|
||||
if (pk_at(1) == CP_EQ) { emit_op("&=", 2); return true }
|
||||
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
|
||||
if (pk_at(1) == "=") { emit_op("&=", 2); return true }
|
||||
if (pk_at(1) == "!") { emit_ident(2); return true }
|
||||
emit_op("&", 1); return true
|
||||
}
|
||||
if (c == CP_PIPE) {
|
||||
if (pk_at(1) == CP_PIPE) {
|
||||
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
|
||||
if (pk_at(2) == CP_EQ) { emit_op("||=", 3); return true }
|
||||
if (c == "|") {
|
||||
if (pk_at(1) == "|") {
|
||||
if (pk_at(2) == "!") { emit_ident(3); return true }
|
||||
if (pk_at(2) == "=") { emit_op("||=", 3); return true }
|
||||
emit_op("||", 2); return true
|
||||
}
|
||||
if (pk_at(1) == CP_EQ) { emit_op("|=", 2); return true }
|
||||
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
|
||||
if (pk_at(1) == "=") { emit_op("|=", 2); return true }
|
||||
if (pk_at(1) == "!") { emit_ident(2); return true }
|
||||
emit_op("|", 1); return true
|
||||
}
|
||||
if (c == CP_CARET) {
|
||||
if (pk_at(1) == CP_EQ) { emit_op("^=", 2); return true }
|
||||
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
|
||||
if (c == "^") {
|
||||
if (pk_at(1) == "=") { emit_op("^=", 2); return true }
|
||||
if (pk_at(1) == "!") { emit_ident(2); return true }
|
||||
emit_op("^", 1); return true
|
||||
}
|
||||
if (c == CP_LBRACKET) {
|
||||
if (pk_at(1) == CP_RBRACKET && pk_at(2) == CP_BANG) { emit_ident(3); return true }
|
||||
if (c == "[") {
|
||||
if (pk_at(1) == "]" && pk_at(2) == "!") { emit_ident(3); return true }
|
||||
emit_op("[", 1); return true
|
||||
}
|
||||
if (c == CP_TILDE) {
|
||||
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
|
||||
if (c == "~") {
|
||||
if (pk_at(1) == "!") { emit_ident(2); return true }
|
||||
emit_op("~", 1); return true
|
||||
}
|
||||
emit_op(character(c), 1)
|
||||
emit_op(c, 1)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -508,7 +448,7 @@ var tokenize = function(src, filename) {
|
||||
// EOF token
|
||||
push(tokens, { kind: "eof", at: pos, from_row: row, from_column: col, to_row: row, to_column: col })
|
||||
|
||||
return {filename: filename, tokens: tokens, cp: cp}
|
||||
return {filename: filename, tokens: tokens}
|
||||
}
|
||||
|
||||
return tokenize
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user