46 Commits

Author SHA1 Message Date
John Alanbrook
4b7cde9400 progress on aot 2026-02-16 21:58:45 -06:00
John Alanbrook
a1ee7dd458 better json pretty print 2026-02-16 17:00:06 -06:00
John Alanbrook
9dbe699033 better make 2026-02-16 01:45:00 -06:00
John Alanbrook
f809cb05f0 Merge branch 'fix_core_scripts' into quicken_mcode 2026-02-16 01:43:08 -06:00
John Alanbrook
788ea98651 bootstrap init 2026-02-16 01:36:36 -06:00
John Alanbrook
433ce8a86e update actors 2026-02-16 01:35:07 -06:00
John Alanbrook
cd6e357b6e Merge branch 'quicken_mcode' into gen_dylib 2026-02-16 00:35:40 -06:00
John Alanbrook
f4f56ed470 run dylibs 2026-02-16 00:35:23 -06:00
John Alanbrook
ff61ab1f50 better streamline 2026-02-16 00:34:49 -06:00
John Alanbrook
46c345d34e cache invalidation 2026-02-16 00:04:30 -06:00
John Alanbrook
dc440587ff pretty json 2026-02-15 22:55:11 -06:00
John Alanbrook
8f92870141 correct syntax errors in core scripts 2026-02-15 22:23:04 -06:00
John Alanbrook
7fc4a205f6 go reuses frames 2026-02-15 19:45:17 -06:00
John Alanbrook
23b201bdd7 dynamic dispatch 2026-02-15 17:51:07 -06:00
John Alanbrook
913ec9afb1 Merge branch 'audit_gc' into fix_slots 2026-02-15 15:44:28 -06:00
John Alanbrook
56de0ce803 fix infinite loop in shop 2026-02-15 15:41:09 -06:00
John Alanbrook
96bbb9e4c8 idompent 2026-02-15 14:58:46 -06:00
John Alanbrook
ebd624b772 fixing gc bugs; nearly idempotent 2026-02-15 13:14:26 -06:00
John Alanbrook
7de20b39da more detail on broken pipeline and vm suit tests 2026-02-15 11:51:23 -06:00
John Alanbrook
ee646db394 failsafe boot mode 2026-02-15 11:44:33 -06:00
John Alanbrook
ff80e0d30d Merge branch 'fix_gc' into pitweb 2026-02-15 10:04:54 -06:00
John Alanbrook
d9f41db891 fix syntax errors in build 2026-02-15 09:29:07 -06:00
John Alanbrook
860632e0fa update cli docs and fix cli scripts with new syntax 2026-02-14 22:24:32 -06:00
John Alanbrook
dcc9659e6b Merge branch 'runtime_rework' into fix_gc 2026-02-14 22:11:31 -06:00
John Alanbrook
2f7f2233b8 compiling 2026-02-14 22:08:55 -06:00
John Alanbrook
eee06009b9 no more special case for core C 2026-02-14 22:00:12 -06:00
John Alanbrook
a765872017 remove if/else dispatch from compile chain 2026-02-14 17:57:48 -06:00
John Alanbrook
a93218e1ff faster streamline 2026-02-14 17:14:43 -06:00
John Alanbrook
f2c4fa2f2b remove redundant check 2026-02-14 16:49:16 -06:00
John Alanbrook
5fe05c60d3 faster gc 2026-02-14 16:46:11 -06:00
John Alanbrook
e75596ce30 respsect array and object length requests 2026-02-14 15:42:19 -06:00
John Alanbrook
86609c27f8 correct sections 2026-02-14 15:13:18 -06:00
John Alanbrook
356c51bde3 better array allocation 2026-02-14 14:44:00 -06:00
John Alanbrook
89421e11a4 pull out prettify mcode 2026-02-14 14:14:34 -06:00
John Alanbrook
e5fc04fecd faster mach compile 2026-02-14 14:02:15 -06:00
John Alanbrook
8ec56e85fa shop audit 2026-02-14 14:00:27 -06:00
John Alanbrook
f49ca530bb fix delete gc bug 2026-02-13 21:52:37 -06:00
John Alanbrook
83263379bd ocaml style rooting macros 2026-02-13 20:46:31 -06:00
John Alanbrook
e80e615634 fix array gc bug; new gc error chasing 2026-02-13 16:58:42 -06:00
John Alanbrook
c1430fd59b Merge branch 'fix_gc' into runtime_rework 2026-02-13 15:42:37 -06:00
John Alanbrook
db73eb4eeb Merge branch 'mcode_streamline' into runtime_rework 2026-02-13 15:42:20 -06:00
John Alanbrook
f2556c5622 proper shop caching 2026-02-13 09:04:25 -06:00
John Alanbrook
291304f75d new way to track actor bad memory access 2026-02-13 09:03:33 -06:00
John Alanbrook
d26a96bc62 cached bootstrap 2026-02-13 08:11:35 -06:00
John Alanbrook
1ba060668e growable buddy memory runtime 2026-02-13 07:59:52 -06:00
John Alanbrook
77fa058135 mach loading 2026-02-13 07:26:49 -06:00
121 changed files with 99420 additions and 132145 deletions

View File

@@ -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 - 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 - 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')`) - 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 ## Project Layout

View File

@@ -5,15 +5,11 @@
# or manually build with meson once. # or manually build with meson once.
# #
# The cell shop is at ~/.cell and core scripts are installed to ~/.cell/core # The cell shop is at ~/.cell and core scripts are installed to ~/.cell/core
#
# See BUILDING.md for details on the bootstrap process and .mach files.
CELL_SHOP = $(HOME)/.cell CELL_SHOP = $(HOME)/.cell
CELL_CORE_PACKAGE = $(CELL_SHOP)/packages/core CELL_CORE_PACKAGE = $(CELL_SHOP)/packages/core
# .cm sources that compile to .mach bytecode doit: bootstrap
MACH_SOURCES = tokenize.cm parse.cm fold.cm mcode.cm \
internal/bootstrap.cm internal/engine.cm
maker: install maker: install
@@ -22,7 +18,7 @@ makecell:
cp cell /opt/homebrew/bin/ cp cell /opt/homebrew/bin/
# Install core: symlink this directory to ~/.cell/core # Install core: symlink this directory to ~/.cell/core
install: bootstrap .mach.stamp $(CELL_SHOP) install: cell $(CELL_SHOP)
@echo "Linking cell core to $(CELL_CORE_PACKAGE)" @echo "Linking cell core to $(CELL_CORE_PACKAGE)"
rm -rf $(CELL_CORE_PACKAGE) rm -rf $(CELL_CORE_PACKAGE)
ln -s $(PWD) $(CELL_CORE_PACKAGE) ln -s $(PWD) $(CELL_CORE_PACKAGE)
@@ -46,16 +42,6 @@ libcell_runtime.dylib: $(CELL_SHOP)/build/dynamic
cell_main: source/main.c libcell_runtime.dylib cell_main: source/main.c libcell_runtime.dylib
cc -o cell_main source/main.c -L. -lcell_runtime -Wl,-rpath,@loader_path -Wl,-rpath,/opt/homebrew/lib cc -o cell_main source/main.c -L. -lcell_runtime -Wl,-rpath,@loader_path -Wl,-rpath,/opt/homebrew/lib
# Regenerate .mach bytecode when any .cm source changes
.mach.stamp: $(MACH_SOURCES)
./cell --dev regen
@touch $@
# Force-regenerate all .mach bytecode files
regen:
./cell --core . regen
@touch .mach.stamp
# Create the cell shop directories # Create the cell shop directories
$(CELL_SHOP): $(CELL_SHOP):
mkdir -p $(CELL_SHOP) mkdir -p $(CELL_SHOP)
@@ -78,13 +64,12 @@ bootstrap:
meson compile -C build_bootstrap meson compile -C build_bootstrap
cp build_bootstrap/cell . cp build_bootstrap/cell .
cp build_bootstrap/libcell_runtime.dylib . cp build_bootstrap/libcell_runtime.dylib .
@echo "Bootstrap complete. Cell shop initialized at $(CELL_SHOP)" @echo "Bootstrap complete. Run cell like ./cell --dev to use a local shop at .cell."
@echo "Now run 'make' to rebuild with cell itself."
# Clean build artifacts # Clean build artifacts
clean: clean:
rm -rf $(CELL_SHOP)/build build_bootstrap rm -rf $(CELL_SHOP)/build build_bootstrap
rm -f cell cell_main libcell_runtime.dylib .mach.stamp rm -f cell cell_main libcell_runtime.dylib
# Ensure dynamic build directory exists # Ensure dynamic build directory exists
$(CELL_SHOP)/build/dynamic: $(CELL_SHOP) $(CELL_SHOP)/build/dynamic: $(CELL_SHOP)
@@ -95,4 +80,4 @@ meson:
meson setup build_dbg -Dbuildtype=debugoptimized meson setup build_dbg -Dbuildtype=debugoptimized
meson install -C build_dbg meson install -C build_dbg
.PHONY: cell static bootstrap clean meson install regen .PHONY: cell static bootstrap clean meson install

33
add.ce
View File

@@ -13,6 +13,10 @@ var fd = use('fd')
var locator = null var locator = null
var alias = null var alias = null
var resolved = null
var parts = null
var cwd = null
var build_target = null
array(args, function(arg) { array(args, function(arg) {
if (arg == '--help' || arg == '-h') { if (arg == '--help' || arg == '-h') {
@@ -41,7 +45,7 @@ if (!locator) {
// Resolve relative paths to absolute paths // Resolve relative paths to absolute paths
if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) { if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) {
var resolved = fd.realpath(locator) resolved = fd.realpath(locator)
if (resolved) { if (resolved) {
locator = resolved locator = resolved
} }
@@ -50,7 +54,7 @@ if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../')
// Generate default alias from locator // Generate default alias from locator
if (!alias) { if (!alias) {
// Use the last component of the locator as alias // Use the last component of the locator as alias
var parts = array(locator, '/') parts = array(locator, '/')
alias = parts[length(parts) - 1] alias = parts[length(parts) - 1]
// Remove any version suffix // Remove any version suffix
if (search(alias, '@') != null) { if (search(alias, '@') != null) {
@@ -59,7 +63,7 @@ if (!alias) {
} }
// Check we're in a package directory // Check we're in a package directory
var cwd = fd.realpath('.') cwd = fd.realpath('.')
if (!fd.is_file(cwd + '/cell.toml')) { if (!fd.is_file(cwd + '/cell.toml')) {
log.error("Not in a package directory (no cell.toml found)") log.error("Not in a package directory (no cell.toml found)")
$stop() $stop()
@@ -68,16 +72,17 @@ if (!fd.is_file(cwd + '/cell.toml')) {
log.console("Adding " + locator + " as '" + alias + "'...") log.console("Adding " + locator + " as '" + alias + "'...")
// Add to local project's cell.toml // Add to local project's cell.toml
try { var _add_dep = function() {
pkg.add_dependency(null, locator, alias) pkg.add_dependency(null, locator, alias)
log.console(" Added to cell.toml") log.console(" Added to cell.toml")
} catch (e) { } disruption {
log.error("Failed to update cell.toml: " + e) log.error("Failed to update cell.toml")
$stop() $stop()
} }
_add_dep()
// Install to shop // Install to shop
try { var _install = function() {
shop.get(locator) shop.get(locator)
shop.extract(locator) shop.extract(locator)
@@ -85,18 +90,20 @@ try {
shop.build_package_scripts(locator) shop.build_package_scripts(locator)
// Build C code if any // Build C code if any
try { var _build_c = function() {
var target = build.detect_host_target() build_target = build.detect_host_target()
build.build_dynamic(locator, target, 'release') build.build_dynamic(locator, build_target, 'release')
} catch (e) { } disruption {
// Not all packages have C code // Not all packages have C code
} }
_build_c()
log.console(" Installed to shop") log.console(" Installed to shop")
} catch (e) { } disruption {
log.error("Failed to install: " + e) log.error("Failed to install")
$stop() $stop()
} }
_install()
log.console("Added " + alias + " (" + locator + ")") log.console("Added " + alias + " (" + locator + ")")

View File

@@ -379,21 +379,23 @@ static const JSCFunctionListEntry js_reader_funcs[] = {
JS_CFUNC_DEF("count", 0, js_reader_count), 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_NewClassID(&js_reader_class_id);
JS_NewClass(js, js_reader_class_id, &js_reader_class); JS_NewClass(js, js_reader_class_id, &js_reader_class);
JSValue reader_proto = JS_NewObject(js); JS_ROOT(reader_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, reader_proto, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry)); JS_SetPropertyFunctionList(js, reader_proto.val, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_reader_class_id, reader_proto); JS_SetClassProto(js, js_reader_class_id, reader_proto.val);
JS_NewClassID(&js_writer_class_id); JS_NewClassID(&js_writer_class_id);
JS_NewClass(js, js_writer_class_id, &js_writer_class); JS_NewClass(js, js_writer_class_id, &js_writer_class);
JSValue writer_proto = JS_NewObject(js); JS_ROOT(writer_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, writer_proto, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry)); JS_SetPropertyFunctionList(js, writer_proto.val, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_writer_class_id, writer_proto); JS_SetClassProto(js, js_writer_class_id, writer_proto.val);
JSValue export = JS_NewObject(js); JS_ROOT(export, JS_NewObject(js));
JS_SetPropertyFunctionList(js, export, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry)); JS_SetPropertyFunctionList(js, export.val, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry));
return export; JS_RETURN(export.val);
} }

194
bench_native.ce Normal file
View File

@@ -0,0 +1,194 @@
// 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 = []
var keys = null
var i = 0
var k = null
if (is_function(mod)) {
push(benches, {name: 'main', fn: mod})
} else if (is_object(mod)) {
keys = array(mod)
i = 0
while (i < length(keys)) {
k = keys[i]
if (is_function(mod[k])) {
push(benches, {name: k, fn: mod[k]})
}
i = i + 1
}
}
return benches
}
// --- Run one benchmark function ---
var run_bench = function(fn, label) {
var samples = []
var i = 0
var t1 = 0
var t2 = 0
// warmup
i = 0
while (i < WARMUP) {
fn(1)
i = i + 1
}
// collect samples
i = 0
while (i < iterations) {
t1 = os.now()
fn(1)
t2 = os.now()
push(samples, t2 - t1)
i = i + 1
}
return {
label: label,
median: stat_median(samples),
mean: stat_mean(samples)
}
}
// --- Load VM module ---
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)
var lib = null
if (has_native) {
print('loading native module: ' + dylib_path)
lib = os.dylib_open(dylib_path)
native_mod = os.dylib_symbol(lib, symbol)
native_benches = collect_benches(native_mod)
} else {
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) {
var result = s
while (length(result) < n) result = result + ' '
return result
}
var i = 0
var b = null
var vm_result = null
var j = 0
var found = false
var nat_result = null
var speedup = 0
while (i < length(vm_benches)) {
b = vm_benches[i]
vm_result = run_bench(b.fn, 'vm')
print(pad(b.name, 20) + ' VM: ' + pad(format_ns(vm_result.median), 12) + ' (median) ' + format_ns(vm_result.mean) + ' (mean)')
// find matching native bench
j = 0
found = false
while (j < length(native_benches)) {
if (native_benches[j].name == b.name) {
nat_result = run_bench(native_benches[j].fn, 'native')
print(pad('', 20) + ' NT: ' + pad(format_ns(nat_result.median), 12) + ' (median) ' + format_ns(nat_result.mean) + ' (mean)')
if (nat_result.median > 0) {
speedup = vm_result.median / nat_result.median
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
}

6200
boot/bootstrap.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

21056
boot/fold.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

24810
boot/mcode.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

29874
boot/parse.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

10436
boot/tokenize.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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
}

View File

@@ -17,8 +17,16 @@ var target_package = null
var buildtype = 'release' var buildtype = 'release'
var force_rebuild = false var force_rebuild = false
var dry_run = 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 (args[i] == '-t' || args[i] == '--target') {
if (i + 1 < length(args)) { if (i + 1 < length(args)) {
target = args[++i] target = args[++i]
@@ -51,8 +59,8 @@ for (var i = 0; i < length(args); i++) {
dry_run = true dry_run = true
} else if (args[i] == '--list-targets') { } else if (args[i] == '--list-targets') {
log.console('Available targets:') log.console('Available targets:')
var targets = build.list_targets() targets = build.list_targets()
for (var t = 0; t < length(targets); t++) { for (t = 0; t < length(targets); t++) {
log.console(' ' + targets[t]) log.console(' ' + targets[t])
} }
$stop() $stop()
@@ -65,7 +73,7 @@ for (var i = 0; i < length(args); i++) {
// Resolve local paths to absolute paths // Resolve local paths to absolute paths
if (target_package) { if (target_package) {
if (target_package == '.' || starts_with(target_package, './') || starts_with(target_package, '../') || fd.is_dir(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) { if (resolved) {
target_package = resolved target_package = resolved
} }
@@ -91,26 +99,28 @@ arrfor(packages, function(package) {
shop.extract(package) shop.extract(package)
}) })
var _build = null
if (target_package) { if (target_package) {
// Build single package // Build single package
log.console('Building ' + target_package + '...') log.console('Building ' + target_package + '...')
try { _build = function() {
var lib = build.build_dynamic(target_package, target, buildtype) lib = build.build_dynamic(target_package, target, buildtype)
if (lib) { if (lib) {
log.console('Built: ' + lib) log.console('Built: ' + lib)
} }
} catch (e) { } disruption {
log.error('Build failed: ' + e) log.error('Build failed')
$stop() $stop()
} }
_build()
} else { } else {
// Build all packages // Build all packages
log.console('Building all packages...') log.console('Building all packages...')
var results = build.build_all_dynamic(target, buildtype) results = build.build_all_dynamic(target, buildtype)
var success = 0 success = 0
var failed = 0 failed = 0
for (var i = 0; i < length(results); i++) { for (i = 0; i < length(results); i++) {
if (results[i].library) { if (results[i].library) {
success++ success++
} else if (results[i].error) { } else if (results[i].error) {

454
build.cm
View File

@@ -85,7 +85,8 @@ function ensure_dir(path) {
if (fd.stat(path).isDirectory) return if (fd.stat(path).isDirectory) return
var parts = array(path, '/') var parts = array(path, '/')
var current = starts_with(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 if (parts[i] == '') continue
current += parts[i] + '/' current += parts[i] + '/'
if (!fd.stat(current).isDirectory) fd.mkdir(current) 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 // Compile a single C file for a package
// Returns the object file path (content-addressed in .cell/build) // 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 pkg_dir = shop.get_package_dir(pkg)
var src_path = pkg_dir + '/' + file var src_path = pkg_dir + '/' + file
if (!fd.is_file(src_path)) { 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) // Get flags (with sigil replacement)
@@ -120,11 +122,11 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') {
var cmd_parts = [cc, '-c', '-fPIC'] var cmd_parts = [cc, '-c', '-fPIC']
// Add buildtype-specific flags // Add buildtype-specific flags
if (buildtype == 'release') { if (_buildtype == 'release') {
cmd_parts = array(cmd_parts, ['-O3', '-DNDEBUG']) cmd_parts = array(cmd_parts, ['-O3', '-DNDEBUG'])
} else if (buildtype == 'debug') { } else if (_buildtype == 'debug') {
cmd_parts = array(cmd_parts, ['-O2', '-g']) cmd_parts = array(cmd_parts, ['-O2', '-g'])
} else if (buildtype == 'minsize') { } else if (_buildtype == 'minsize') {
cmd_parts = array(cmd_parts, ['-Os', '-DNDEBUG']) 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) // Add package CFLAGS (resolve relative -I paths)
arrfor(cflags, function(flag) { arrfor(cflags, function(flag) {
if (starts_with(flag, '-I') && !starts_with(flag, '-I/')) { var f = flag
flag = '-I"' + pkg_dir + '/' + text(flag, 2) + '"' 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 // Add target CFLAGS
@@ -167,7 +170,7 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') {
log.console('Compiling ' + file) log.console('Compiling ' + file)
var ret = os.system(full_cmd) var ret = os.system(full_cmd)
if (ret != 0) { if (ret != 0) {
throw Error('Compilation failed: ' + file) print('Compilation failed: ' + file); disrupt
} }
return obj_path return obj_path
@@ -175,12 +178,14 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') {
// Build all C files for a package // Build all C files for a package
// Returns array of object file paths // Returns array of object file paths
Build.build_package = function(pkg, target = Build.detect_host_target(), exclude_main, buildtype = 'release') { Build.build_package = function(pkg, target, exclude_main, buildtype) {
var c_files = pkg_tools.get_c_files(pkg, target, exclude_main) 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 = [] var objects = []
arrfor(c_files, function(file) { 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) 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 // 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 // Sort objects for deterministic hash
var sorted_objects = sort(objects) var sorted_objects = sort(objects)
// Build a string representing all link inputs // Build a string representing all link inputs
var parts = [] var parts = []
push(parts, 'target:' + target) push(parts, 'target:' + opts.target)
push(parts, 'cc:' + cc) push(parts, 'cc:' + opts.cc)
arrfor(sorted_objects, function(obj) { arrfor(sorted_objects, function(obj) {
// Object paths are content-addressed, so the path itself is the hash // Object paths are content-addressed, so the path itself is the hash
push(parts, 'obj:' + obj) push(parts, 'obj:' + obj)
@@ -214,74 +219,46 @@ function compute_link_key(objects, ldflags, target_ldflags, target, cc) {
return content_hash(text(parts, '\n')) return content_hash(text(parts, '\n'))
} }
// Build a dynamic library for a package // Build a per-module dynamic library for a single C file
// Output goes to .cell/lib/<package_name>.<ext> // Returns the content-addressed dylib path in .cell/build/<hash>.<target>.dylib
// Dynamic libraries do NOT link against core; undefined symbols are resolved at dlopen time Build.build_module_dylib = function(pkg, file, target, buildtype) {
// Uses content-addressed store + symlink for caching var _target = target || Build.detect_host_target()
Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildtype = 'release') { var _buildtype = buildtype || 'release'
var objects = Build.build_package(pkg, target, true, buildtype) // exclude main.c var obj = Build.compile_file(pkg, file, _target, _buildtype)
if (length(objects) == 0) { var tc = toolchains[_target]
log.console('No C files in ' + pkg) var dylib_ext = tc.system == 'windows' ? '.dll' : (tc.system == 'darwin' ? '.dylib' : '.so')
return null var cc = tc.cpp || tc.c
}
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 local_dir = get_local_dir() 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 = [] var resolved_ldflags = []
arrfor(ldflags, function(flag) { arrfor(ldflags, function(flag) {
if (starts_with(flag, '-L') && !starts_with(flag, '-L/')) { var f = flag
flag = '-L"' + pkg_dir + '/' + text(flag, 2) + '"' 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 // Content-addressed output: hash of (object + link flags + target)
var link_key = compute_link_key(objects, resolved_ldflags, target_ldflags, target, cc) var link_key = compute_link_key([obj], resolved_ldflags, target_ldflags, {target: _target, cc: cc})
var store_path = store_dir + '/' + lib_name + '-' + link_key + dylib_ext 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(dylib_path))
if (fd.is_file(store_path)) { return dylib_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
}
// Build link command
var cmd_parts = [cc, '-shared', '-fPIC'] var cmd_parts = [cc, '-shared', '-fPIC']
// Platform-specific flags for undefined symbols (resolved at dlopen) and size optimization
if (tc.system == 'darwin') { if (tc.system == 'darwin') {
cmd_parts = array(cmd_parts, [ cmd_parts = array(cmd_parts, [
'-undefined', 'dynamic_lookup', '-undefined', 'dynamic_lookup',
'-Wl,-dead_strip', '-Wl,-dead_strip',
'-Wl,-install_name,' + stable_path,
'-Wl,-rpath,@loader_path/../local', '-Wl,-rpath,@loader_path/../local',
'-Wl,-rpath,' + local_dir '-Wl,-rpath,' + local_dir
]) ])
@@ -293,41 +270,53 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty
'-Wl,-rpath,' + local_dir '-Wl,-rpath,' + local_dir
]) ])
} else if (tc.system == 'windows') { } else if (tc.system == 'windows') {
// Windows DLLs: use --allow-shlib-undefined for mingw
push(cmd_parts, '-Wl,--allow-shlib-undefined') push(cmd_parts, '-Wl,--allow-shlib-undefined')
} }
// Add .cell/local to library search path
push(cmd_parts, '-L"' + local_dir + '"') push(cmd_parts, '-L"' + local_dir + '"')
push(cmd_parts, '"' + obj + '"')
arrfor(objects, function(obj) {
push(cmd_parts, '"' + obj + '"')
})
// Do NOT link against core library - symbols resolved at dlopen time
cmd_parts = array(cmd_parts, resolved_ldflags) cmd_parts = array(cmd_parts, resolved_ldflags)
cmd_parts = array(cmd_parts, target_ldflags) cmd_parts = array(cmd_parts, target_ldflags)
push(cmd_parts, '-o') push(cmd_parts, '-o')
push(cmd_parts, '"' + store_path + '"') push(cmd_parts, '"' + dylib_path + '"')
var cmd_str = text(cmd_parts, ' ') var cmd_str = text(cmd_parts, ' ')
log.console('Linking module ' + file + ' -> ' + fd.basename(dylib_path))
log.console('Linking ' + lib_name + dylib_ext)
var ret = os.system(cmd_str) var ret = os.system(cmd_str)
if (ret != 0) { if (ret != 0) {
throw Error('Linking failed: ' + pkg) print('Linking failed: ' + file); disrupt
} }
// Update symlink to point to the new store file // Install to deterministic lib/<pkg>/<stem>.dylib
if (fd.is_link(stable_path)) { var file_stem = file
fd.unlink(stable_path) var install_dir = shop.get_lib_dir() + '/' + shop.lib_name_for_package(pkg)
} else if (fd.is_file(stable_path)) { var stem_dir = fd.dirname(file_stem)
fd.unlink(stable_path) 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 // Build a static binary from multiple packages
// packages: array of package names // packages: array of package names
// output: output binary path // 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_objects = []
var all_ldflags = [] var all_ldflags = []
var seen_flags = {} var seen_flags = {}
@@ -347,14 +338,14 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out
var is_core = (pkg == 'core') var is_core = (pkg == 'core')
// For core, include main.c; for others, exclude it // 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) { arrfor(objects, function(obj) {
push(all_objects, obj) push(all_objects, obj)
}) })
// Collect LDFLAGS (with sigil replacement) // 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) var pkg_dir = shop.get_package_dir(pkg)
// Deduplicate based on the entire LDFLAGS string for this package // Deduplicate based on the entire LDFLAGS string for this package
@@ -362,26 +353,27 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out
if (!seen_flags[ldflags_key]) { if (!seen_flags[ldflags_key]) {
seen_flags[ldflags_key] = true seen_flags[ldflags_key] = true
arrfor(ldflags, function(flag) { arrfor(ldflags, function(flag) {
// Resolve relative -L paths var f = flag
if (starts_with(flag, '-L') && !starts_with(flag, '-L/')) { if (starts_with(f, '-L') && !starts_with(f, '-L/')) {
flag = '-L"' + pkg_dir + '/' + text(flag, 2) + '"' f = '-L"' + pkg_dir + '/' + text(f, 2) + '"'
} }
push(all_ldflags, flag) push(all_ldflags, f)
}) })
} }
}) })
if (length(all_objects) == 0) { if (length(all_objects) == 0) {
throw Error('No object files to link') print('No object files to link'); disrupt
} }
// Link // Link
var cc = toolchains[target].c var cc = toolchains[_target].c
var target_ldflags = toolchains[target].c_link_args || [] var target_ldflags = toolchains[_target].c_link_args || []
var exe_ext = toolchains[target].system == 'windows' ? '.exe' : '' var exe_ext = toolchains[_target].system == 'windows' ? '.exe' : ''
if (!ends_with(output, exe_ext) && exe_ext) { var out_path = output
output = output + exe_ext if (!ends_with(out_path, exe_ext) && exe_ext) {
out_path = out_path + exe_ext
} }
var cmd_parts = [cc] var cmd_parts = [cc]
@@ -398,17 +390,240 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out
push(cmd_parts, flag) push(cmd_parts, flag)
}) })
push(cmd_parts, '-o', '"' + output + '"') push(cmd_parts, '-o', '"' + out_path + '"')
var cmd_str = text(cmd_parts, ' ') var cmd_str = text(cmd_parts, ' ')
log.console('Linking ' + output) log.console('Linking ' + out_path)
var ret = os.system(cmd_str) var ret = os.system(cmd_str)
if (ret != 0) { 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.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 return output
} }
@@ -417,36 +632,25 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out
// ============================================================================ // ============================================================================
// Build dynamic libraries for all installed packages // Build dynamic libraries for all installed packages
Build.build_all_dynamic = function(target, buildtype = 'release') { Build.build_all_dynamic = function(target, buildtype) {
target = target || Build.detect_host_target() var _target = target || Build.detect_host_target()
var _buildtype = buildtype || 'release'
var packages = shop.list_packages() var packages = shop.list_packages()
var results = [] var results = []
var core_mods = null
// Build core first // Build core first
if (find(packages, 'core') != null) { if (find(packages, function(p) { return p == 'core' }) != null) {
try { core_mods = Build.build_dynamic('core', _target, _buildtype)
var lib = Build.build_dynamic('core', target, buildtype) push(results, {package: 'core', modules: core_mods})
push(results, { package: 'core', library: lib })
} catch (e) {
log.error('Failed to build core: ' + text(e))
push(results, { package: 'core', error: e })
}
} }
// Build other packages // Build other packages
arrfor(packages, function(pkg) { arrfor(packages, function(pkg) {
if (pkg == 'core') return if (pkg == 'core') return
var pkg_mods = Build.build_dynamic(pkg, _target, _buildtype)
try { push(results, {package: pkg, modules: pkg_mods})
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 })
}
}) })
return results return results

View File

@@ -22,55 +22,55 @@ function normalize_path(path) {
// Check if a file exists in a specific mount // Check if a file exists in a specific mount
function mount_exists(mount, path) { function mount_exists(mount, path) {
var result = false
var _check = null
if (mount.type == 'zip') { if (mount.type == 'zip') {
try { _check = function() {
mount.handle.mod(path) mount.handle.mod(path)
return true result = true
} catch (e) { } disruption {}
return false _check()
}
} else if (mount.type == 'qop') { } else if (mount.type == 'qop') {
try { _check = function() {
return mount.handle.stat(path) != null result = mount.handle.stat(path) != null
} catch (e) { } disruption {}
return false _check()
} } else {
} else { // fs
var full_path = fd.join_paths(mount.source, path) var full_path = fd.join_paths(mount.source, path)
try { _check = function() {
var st = fd.stat(full_path) var st = fd.stat(full_path)
return st.isFile || st.isDirectory result = st.isFile || st.isDirectory
} catch (e) { } disruption {}
return false _check()
}
} }
return result
} }
// Check if a path refers to a directory in a specific mount // Check if a path refers to a directory in a specific mount
function is_directory(path) { function is_directory(path) {
var res = resolve(path) var res = resolve(path)
var mount = res.mount var mount = res.mount
var result = false
var _check = null
if (mount.type == 'zip') { if (mount.type == 'zip') {
try { _check = function() {
return mount.handle.is_directory(path); result = mount.handle.is_directory(path)
} catch (e) { } disruption {}
return false; _check()
}
} else if (mount.type == 'qop') { } else if (mount.type == 'qop') {
try { _check = function() {
return mount.handle.is_directory(path); result = mount.handle.is_directory(path)
} catch (e) { } disruption {}
return false; _check()
} } else {
} else { // fs
var full_path = fd.join_paths(mount.source, path) var full_path = fd.join_paths(mount.source, path)
try { _check = function() {
var st = fd.stat(full_path) var st = fd.stat(full_path)
return st.isDirectory result = st.isDirectory
} catch (e) { } disruption {}
return false _check()
}
} }
return result
} }
// Resolve a path to a specific mount and relative path // Resolve a path to a specific mount and relative path
@@ -102,7 +102,7 @@ function resolve(path, must_exist) {
}, false, true) }, false, true)
if (!mount) { if (!mount) {
throw Error("Unknown mount point: @" + mount_name) print("Unknown mount point: @" + mount_name); disrupt
} }
return { mount: mount, path: rel_path } return { mount: mount, path: rel_path }
@@ -122,7 +122,7 @@ function resolve(path, must_exist) {
} }
if (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) { } else if (st.isFile) {
var blob = fd.slurp(source) var blob = fd.slurp(source)
// Try QOP first (it's likely faster to fail?) or Zip?
// QOP open checks magic.
var qop_archive = null var qop_archive = null
try { var _try_qop = function() {
qop_archive = qop.open(blob) qop_archive = qop.open(blob)
} catch(e) {} } disruption {}
_try_qop()
if (qop_archive) { if (qop_archive) {
mount_info.type = 'qop' mount_info.type = 'qop'
@@ -158,7 +157,7 @@ function mount(source, name) {
} else { } else {
var zip = miniz.read(blob) var zip = miniz.read(blob)
if (!is_object(zip) || !is_function(zip.count)) { 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' mount_info.type = 'zip'
@@ -166,7 +165,7 @@ function mount(source, name) {
mount_info.zip_blob = blob // keep blob alive mount_info.zip_blob = blob // keep blob alive
} }
} else { } else {
throw Error("Unsupported mount source type: " + source) print("Unsupported mount source type: " + source); disrupt
} }
push(mounts, mount_info) push(mounts, mount_info)
@@ -182,13 +181,13 @@ function unmount(name_or_source) {
// Read file // Read file
function slurp(path) { function slurp(path) {
var res = resolve(path, true) 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') { if (res.mount.type == 'zip') {
return res.mount.handle.slurp(res.path) return res.mount.handle.slurp(res.path)
} else if (res.mount.type == 'qop') { } else if (res.mount.type == 'qop') {
var data = res.mount.handle.read(res.path) 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 return data
} else { } else {
var full_path = fd.join_paths(res.mount.source, res.path) var full_path = fd.join_paths(res.mount.source, res.path)
@@ -217,7 +216,7 @@ function exists(path) {
// Stat // Stat
function stat(path) { function stat(path) {
var res = resolve(path, true) 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') { if (res.mount.type == 'zip') {
var mod = res.mount.handle.mod(res.path) var mod = res.mount.handle.mod(res.path)
@@ -228,7 +227,7 @@ function stat(path) {
} }
} else if (res.mount.type == 'qop') { } else if (res.mount.type == 'qop') {
var s = res.mount.handle.stat(res.path) 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 { return {
filesize: s.size, filesize: s.size,
modtime: s.modtime, modtime: s.modtime,
@@ -261,7 +260,7 @@ function mount_package(name) {
var dir = shop.get_package_dir(name) var dir = shop.get_package_dir(name)
if (!dir) { if (!dir) {
throw Error("Package not found: " + name) print("Package not found: " + name); disrupt
} }
mount(dir, name) mount(dir, name)
@@ -275,7 +274,7 @@ function match(str, pattern) {
function rm(path) { function rm(path) {
var res = resolve(path, true) 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 full_path = fd.join_paths(res.mount.source, res.path)
var st = fd.stat(full_path) var st = fd.stat(full_path)

View File

@@ -23,8 +23,11 @@ var clean_build = false
var clean_fetch = false var clean_fetch = false
var deep = false var deep = false
var dry_run = 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') { if (args[i] == '--build') {
clean_build = true clean_build = true
} else if (args[i] == '--fetch') { } else if (args[i] == '--fetch') {
@@ -74,7 +77,7 @@ var is_world_scope = (scope == 'world')
if (!is_shop_scope && !is_world_scope) { if (!is_shop_scope && !is_world_scope) {
if (scope == '.' || starts_with(scope, './') || starts_with(scope, '../') || fd.is_dir(scope)) { if (scope == '.' || starts_with(scope, './') || starts_with(scope, '../') || fd.is_dir(scope)) {
var resolved = fd.realpath(scope) resolved = fd.realpath(scope)
if (resolved) { if (resolved) {
scope = resolved scope = resolved
} }
@@ -86,6 +89,7 @@ var dirs_to_delete = []
// Gather packages to clean // Gather packages to clean
var packages_to_clean = [] var packages_to_clean = []
var _gather = null
if (is_shop_scope) { if (is_shop_scope) {
packages_to_clean = shop.list_packages() packages_to_clean = shop.list_packages()
@@ -97,14 +101,15 @@ if (is_shop_scope) {
push(packages_to_clean, scope) push(packages_to_clean, scope)
if (deep) { if (deep) {
try { _gather = function() {
var deps = pkg.gather_dependencies(scope) deps = pkg.gather_dependencies(scope)
arrfor(deps, function(dep) { arrfor(deps, function(dep) {
push(packages_to_clean, dep) push(packages_to_clean, dep)
}) })
} catch (e) { } disruption {
// Skip if can't read dependencies // Skip if can't read dependencies
} }
_gather()
} }
} }
@@ -168,6 +173,7 @@ if (clean_fetch) {
} }
// Execute or report // Execute or report
var deleted_count = 0
if (dry_run) { if (dry_run) {
log.console("Would delete:") log.console("Would delete:")
if (length(files_to_delete) == 0 && length(dirs_to_delete) == 0) { if (length(files_to_delete) == 0 && length(dirs_to_delete) == 0) {
@@ -181,20 +187,19 @@ if (dry_run) {
}) })
} }
} else { } else {
var deleted_count = 0
arrfor(files_to_delete, function(f) { arrfor(files_to_delete, function(f) {
try { var _del = function() {
fd.unlink(f) fd.unlink(f)
log.console("Deleted: " + f) log.console("Deleted: " + f)
deleted_count++ deleted_count++
} catch (e) { } disruption {
log.error("Failed to delete " + f + ": " + e) log.error("Failed to delete " + f)
} }
_del()
}) })
arrfor(dirs_to_delete, function(d) { arrfor(dirs_to_delete, function(d) {
try { var _del = function() {
if (fd.is_link(d)) { if (fd.is_link(d)) {
fd.unlink(d) fd.unlink(d)
} else { } else {
@@ -202,9 +207,10 @@ if (dry_run) {
} }
log.console("Deleted: " + d) log.console("Deleted: " + d)
deleted_count++ deleted_count++
} catch (e) { } disruption {
log.error("Failed to delete " + d + ": " + e) log.error("Failed to delete " + d)
} }
_del()
}) })
if (deleted_count == 0) { if (deleted_count == 0) {

View File

@@ -7,11 +7,14 @@ var fd = use('fd')
var http = use('http') var http = use('http')
var miniz = use('miniz') var miniz = use('miniz')
var resolved = null
var cwd = null
var parent = null
if (length(args) < 2) { if (length(args) < 2) {
log.console("Usage: cell clone <origin> <path>") log.console("Usage: cell clone <origin> <path>")
log.console("Clones a cell package to a local path and links it.") log.console("Clones a cell package to a local path and links it.")
$stop() $stop()
return
} }
var origin = args[0] var origin = args[0]
@@ -19,19 +22,19 @@ var target_path = args[1]
// Resolve target path to absolute // Resolve target path to absolute
if (target_path == '.' || starts_with(target_path, './') || starts_with(target_path, '../')) { if (target_path == '.' || starts_with(target_path, './') || starts_with(target_path, '../')) {
var resolved = fd.realpath(target_path) resolved = fd.realpath(target_path)
if (resolved) { if (resolved) {
target_path = resolved target_path = resolved
} else { } else {
// Path doesn't exist yet, resolve relative to cwd // Path doesn't exist yet, resolve relative to cwd
var cwd = fd.realpath('.') cwd = fd.realpath('.')
if (target_path == '.') { if (target_path == '.') {
target_path = cwd target_path = cwd
} else if (starts_with(target_path, './')) { } else if (starts_with(target_path, './')) {
target_path = cwd + text(target_path, 1) target_path = cwd + text(target_path, 1)
} else if (starts_with(target_path, '../')) { } else if (starts_with(target_path, '../')) {
// Go up one directory from cwd // Go up one directory from cwd
var parent = fd.dirname(cwd) parent = fd.dirname(cwd)
target_path = parent + text(target_path, 2) 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)) { if (fd.is_dir(target_path)) {
log.console("Error: " + target_path + " already exists") log.console("Error: " + target_path + " already exists")
$stop() $stop()
return
} }
log.console("Cloning " + origin + " to " + target_path + "...") log.console("Cloning " + origin + " to " + target_path + "...")
@@ -51,7 +53,6 @@ var info = shop.resolve_package_info(origin)
if (!info || info == 'local') { if (!info || info == 'local') {
log.console("Error: " + origin + " is not a remote package") log.console("Error: " + origin + " is not a remote package")
$stop() $stop()
return
} }
// Update to get the commit hash // Update to get the commit hash
@@ -59,7 +60,6 @@ var update_result = shop.update(origin)
if (!update_result) { if (!update_result) {
log.console("Error: Could not fetch " + origin) log.console("Error: Could not fetch " + origin)
$stop() $stop()
return
} }
// Fetch and extract to the target path // Fetch and extract to the target path
@@ -68,37 +68,45 @@ var entry = lock[origin]
if (!entry || !entry.commit) { if (!entry || !entry.commit) {
log.console("Error: No commit found for " + origin) log.console("Error: No commit found for " + origin)
$stop() $stop()
return
} }
var download_url = shop.get_download_url(origin, entry.commit) var download_url = shop.get_download_url(origin, entry.commit)
log.console("Downloading from " + download_url) log.console("Downloading from " + download_url)
try { var zip_blob = null
var zip_blob = http.fetch(download_url) 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 // Extract zip to target path
var zip = miniz.read(zip_blob) zip = miniz.read(zip_blob)
if (!zip) { if (!zip) {
log.console("Error: Failed to read zip archive") log.console("Error: Failed to read zip archive")
$stop() $stop()
return
} }
// Create target directory // Create target directory
fd.mkdir(target_path) fd.mkdir(target_path)
var count = zip.count() count = zip.count()
for (var i = 0; i < count; i++) { for (i = 0; i < count; i++) {
if (zip.is_directory(i)) continue if (zip.is_directory(i)) continue
var filename = zip.get_filename(i) filename = zip.get_filename(i)
var first_slash = search(filename, '/') first_slash = search(filename, '/')
if (first_slash == null) continue if (first_slash == null) continue
if (first_slash + 1 >= length(filename)) continue if (first_slash + 1 >= length(filename)) continue
var rel_path = text(filename, first_slash + 1) rel_path = text(filename, first_slash + 1)
var full_path = target_path + '/' + rel_path full_path = target_path + '/' + rel_path
var dir_path = fd.dirname(full_path) dir_path = fd.dirname(full_path)
// Ensure directory exists // Ensure directory exists
if (!fd.is_dir(dir_path)) { if (!fd.is_dir(dir_path)) {
@@ -112,10 +120,9 @@ try {
// Link the origin to the cloned path // Link the origin to the cloned path
link.add(origin, target_path, shop) link.add(origin, target_path, shop)
log.console("Linked " + origin + " -> " + target_path) log.console("Linked " + origin + " -> " + target_path)
} disruption {
} catch (e) { log.console("Error during clone")
log.console("Error: " + e.message)
if (e.stack) log.console(e.stack)
} }
_clone()
$stop() $stop()

92
compare_aot.ce Normal file
View File

@@ -0,0 +1,92 @@
// compare_aot.ce — compile a .cm module via both paths and compare results
//
// Usage:
// cell --dev compare_aot.ce <module.cm>
var build = use('build')
var fd_mod = use('fd')
var os = use('os')
var json = use('json')
var show = function(v) {
return json.encode(v)
}
if (length(args) < 1) {
print('usage: cell --dev compare_aot.ce <module.cm>')
return
}
var file = args[0]
if (!fd_mod.is_file(file)) {
if (!ends_with(file, '.cm') && fd_mod.is_file(file + '.cm'))
file = file + '.cm'
else {
print('file not found: ' + file)
return
}
}
var abs = fd_mod.realpath(file)
// Shared compilation front-end
var tokenize = use('tokenize')
var parse_mod = use('parse')
var fold = use('fold')
var mcode_mod = use('mcode')
var streamline_mod = use('streamline')
var src = text(fd_mod.slurp(abs))
var tok = tokenize(src, abs)
var ast = parse_mod(tok.tokens, src, abs, tokenize)
var folded = fold(ast)
var compiled = mcode_mod(folded)
var optimized = streamline_mod(compiled)
// --- Interpreted (mach VM) ---
print('--- interpreted ---')
var mcode_json = json.encode(optimized)
var mach_blob = mach_compile_mcode_bin(abs, mcode_json)
var result_interp = mach_load(mach_blob, stone({}))
print('result: ' + show(result_interp))
// --- Native (AOT via QBE) ---
print('\n--- native ---')
var dylib_path = build.compile_native(abs, null, null, null)
print('dylib: ' + dylib_path)
var handle = os.dylib_open(dylib_path)
if (!handle) {
print('failed to open dylib')
return
}
// Build env with runtime functions. Must include starts_with etc. because
// the GC can lose global object properties after compaction.
var env = stone({
logical: logical,
some: some,
every: every,
starts_with: starts_with,
ends_with: ends_with,
log: log,
fallback: fallback,
parallel: parallel,
race: race,
sequence: sequence
})
var result_native = os.native_module_load(handle, env)
print('result: ' + show(result_native))
// --- Comparison ---
print('\n--- comparison ---')
var s_interp = show(result_interp)
var s_native = show(result_native)
if (s_interp == s_native) {
print('MATCH')
} else {
print('MISMATCH')
print(' interp: ' + s_interp)
print(' native: ' + s_native)
}

View File

@@ -1,102 +1,27 @@
// compile.ce — compile a .cm module to native .dylib via QBE // compile.ce — compile a .cm or .ce file to native .dylib via QBE
// //
// Usage: // Usage:
// cell --core . compile.ce <file.cm> // cell compile <file.cm|file.ce>
// //
// Produces <file>.dylib in the current directory. // Installs the dylib to .cell/lib/<pkg>/<stem>.dylib
var shop = use('internal/shop')
var build = use('build')
var fd = use('fd') var fd = use('fd')
var os = use('os')
if (length(args) < 1) { if (length(args) < 1) {
print('usage: cell --core . compile.ce <file.cm>') print('usage: cell compile <file.cm|file.ce>')
return return
} }
var file = args[0] var file = args[0]
var base = file if (!fd.is_file(file)) {
if (ends_with(base, '.cm')) { print('file not found: ' + file)
base = text(base, 0, length(base) - 3)
}
var safe = 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 = base + '.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)
if (rc != 0) {
print('failed to emit qbe il')
return return
} }
// Step 2: post-process — insert dead labels after ret/jmp, append wrapper var abs = fd.realpath(file)
// Use awk via shell to avoid blob/slurpwrite issues with long strings var file_info = shop.file_info(abs)
print('post-process...') var pkg = file_info.package
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. build.compile_native(abs, null, null, pkg)
// 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`
rc = os.system(wrapper_cmd)
if (rc != 0) {
print('wrapper append failed')
return
}
// Step 3: compile QBE IL to assembly
print('qbe compile...')
rc = os.system('~/.local/bin/qbe -o ' + s_path + ' ' + tmp + '_fixed.ssa')
if (rc != 0) {
print('qbe compilation failed')
return
}
// Step 4: assemble
print('assemble...')
rc = os.system('cc -c ' + s_path + ' -o ' + o_path)
if (rc != 0) {
print('assembly failed')
return
}
// Step 5: compile runtime stubs (cached — skip if already built)
if (!fd.is_file(rt_o_path)) {
print('compile runtime stubs...')
rc = os.system('cc -c ' + cwd + '/qbe_rt.c -o ' + rt_o_path + ' -fPIC')
if (rc != 0) {
print('runtime stubs compilation failed')
return
}
}
// Step 6: link dylib
print('link...')
rc = os.system('cc -shared -fPIC -undefined dynamic_lookup ' + o_path + ' ' + rt_o_path + ' -o ' + cwd + '/' + dylib_path)
if (rc != 0) {
print('linking failed')
return
}
print('built: ' + dylib_path)

98
compile_seed.ce Normal file
View 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)

243
config.ce
View File

@@ -47,8 +47,10 @@ function get_nested(obj, path) {
// Set a value in nested object using path // Set a value in nested object using path
function set_nested(obj, path, value) { function set_nested(obj, path, value) {
var current = obj var current = obj
for (var i = 0; i < length(path) - 1; i++) { var i = 0
var segment = path[i] var segment = null
for (i = 0; i < length(path) - 1; i++) {
segment = path[i]
if (is_null(current[segment]) || !is_object(current[segment])) { if (is_null(current[segment]) || !is_object(current[segment])) {
current[segment] = {} current[segment] = {}
} }
@@ -59,14 +61,16 @@ function set_nested(obj, path, value) {
// Parse value string into appropriate type // Parse value string into appropriate type
function parse_value(str) { function parse_value(str) {
var num_str = null
var n = null
// Boolean // Boolean
if (str == 'true') return true if (str == 'true') return true
if (str == 'false') return false if (str == 'false') return false
// Number (including underscores) // Number
var num_str = replace(str, /_/g, '') num_str = replace(str, /_/g, '')
if (/^-?\d+$/.test(num_str)) return parseInt(num_str) n = number(num_str)
if (/^-?\d*\.\d+$/.test(num_str)) return parseFloat(num_str) if (n != null) return n
// String // String
return str return str
@@ -75,22 +79,19 @@ function parse_value(str) {
// Format value for display // Format value for display
function format_value(val) { function format_value(val) {
if (is_text(val)) return '"' + 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) return text(val)
} }
// Print configuration tree recursively // Print configuration tree recursively
function print_config(obj, prefix = '') { function print_config(obj, pfx) {
var p = pfx || ''
arrfor(array(obj), function(key) { arrfor(array(obj), function(key) {
var val = obj[key] var val = obj[key]
var full_key = prefix ? prefix + '.' + key : key var full_key = p ? p + '.' + key : key
if (is_object(val)) if (is_object(val))
print_config(val, full_key) print_config(val, full_key)
else else if (!is_null(val))
log.console(full_key + ' = ' + format_value(val)) log.console(full_key + ' = ' + format_value(val))
}) })
} }
@@ -99,151 +100,123 @@ function print_config(obj, prefix = '') {
if (length(args) == 0) { if (length(args) == 0) {
print_help() print_help()
$stop() $stop()
return
} }
var config = pkg.load_config() var config = pkg.load_config()
if (!config) { if (!config) {
log.error("Failed to load cell.toml") log.error("Failed to load cell.toml")
$stop() $stop()
return
} }
var command = args[0] var command = args[0]
var key var key = null
var path var path = null
var value var value = null
var value_str = null
var valid_system_keys = null
var actor_name = null
var actor_cmd = null
switch (command) { if (command == 'help' || command == '-h' || command == '--help') {
case 'help': print_help()
case '-h': } else if (command == 'list') {
case '--help': log.console("# Cell Configuration")
print_help() log.console("")
break 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)
case 'list': if (value == null) {
log.console("# Cell Configuration") log.error("Key not found: " + key)
log.console("") } else if (is_object(value)) {
print_config(config) print_config(value, key)
break } 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)
case 'get': if (path[0] == 'system') {
if (length(args) < 2) { valid_system_keys = [
log.error("Usage: cell config get <key>") '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() $stop()
return
} }
key = args[1] }
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("# Configuration for actor: " + actor_name)
log.console("")
print_config(config.actors[actor_name], 'actors.' + actor_name)
}
} else if (actor_cmd == 'get') {
if (length(args) < 4) {
log.error("Usage: cell config actor <name> get <key>")
$stop()
}
key = args[3]
path = parse_key(key) path = parse_key(key)
value = get_nested(config, path) value = get_nested(config.actors[actor_name], path)
if (value == null) { if (value == null) {
log.error("Key not found: " + key) log.error("Key not found for actor " + actor_name + ": " + key)
} else if (isa(value, object)) {
// Print all nested values
print_config(value, key)
} else { } else {
log.console(key + ' = ' + format_value(value)) log.console('actors.' + actor_name + '.' + key + ' = ' + format_value(value))
} }
break } else if (actor_cmd == 'set') {
if (length(args) < 5) {
case 'set': log.error("Usage: cell config actor <name> set <key> <value>")
if (length(args) < 3) {
log.error("Usage: cell config set <key> <value>")
$stop() $stop()
return
} }
var key = args[1] key = args[3]
var value_str = args[2] value_str = args[4]
var path = parse_key(key) path = parse_key(key)
var value = parse_value(value_str) value = parse_value(value_str)
// Validate system keys set_nested(config.actors[actor_name], path, value)
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
}
}
set_nested(config, path, value)
pkg.save_config(config) pkg.save_config(config)
log.console("Set " + key + " = " + format_value(value)) log.console("Set actors." + actor_name + "." + key + " = " + format_value(value))
break } else {
log.error("Unknown actor command: " + actor_cmd)
case 'actor': log.console("Valid commands: list, get, set")
// Handle actor-specific configuration }
if (length(args) < 3) { } else {
log.error("Usage: cell config actor <name> <command> [options]") log.error("Unknown command: " + command)
$stop() print_help()
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()
} }
$stop() $stop()

View File

@@ -238,9 +238,10 @@ static const JSCFunctionListEntry js_crypto_funcs[] = {
JS_CFUNC_DEF("unlock", 3, js_crypto_unlock), 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_FRAME(js);
JS_SetPropertyFunctionList(js, obj, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0])); JS_ROOT(mod, JS_NewObject(js));
return obj; JS_SetPropertyFunctionList(js, mod.val, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0]));
JS_RETURN(mod.val);
} }

View File

@@ -21,8 +21,9 @@ static const JSCFunctionListEntry js_debug_funcs[] = {
MIST_FUNC_DEF(debug, backtrace_fns,0), MIST_FUNC_DEF(debug, backtrace_fns,0),
}; };
JSValue js_debug_use(JSContext *js) { JSValue js_core_debug_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js,mod,js_debug_funcs,countof(js_debug_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_debug_funcs, countof(js_debug_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -20,8 +20,9 @@ static const JSCFunctionListEntry js_js_funcs[] = {
MIST_FUNC_DEF(js, fn_info, 1), MIST_FUNC_DEF(js, fn_info, 1),
}; };
JSValue js_js_use(JSContext *js) { JSValue js_core_js_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js,mod,js_js_funcs,countof(js_js_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_js_funcs, countof(js_js_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -129,11 +129,11 @@ function diff_test_file(file_path) {
// Build env for module loading // Build env for module loading
var make_env = function() { var make_env = function() {
return { return stone({
use: function(path) { use: function(path) {
return shop.use(path, use_pkg) return shop.use(path, use_pkg)
} }
} })
} }
// Read and parse // Read and parse

View File

@@ -34,6 +34,7 @@ pit hello
- [**Actors and Modules**](/docs/actors/) — the execution model - [**Actors and Modules**](/docs/actors/) — the execution model
- [**Requestors**](/docs/requestors/) — asynchronous composition - [**Requestors**](/docs/requestors/) — asynchronous composition
- [**Packages**](/docs/packages/) — code organization and sharing - [**Packages**](/docs/packages/) — code organization and sharing
- [**Shop Architecture**](/docs/shop/) — module resolution, compilation, and caching
## Reference ## Reference

View File

@@ -224,7 +224,7 @@ use('json') // core json module
use('otherlib/foo') // dependency 'otherlib', file foo.cm 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 ## Example: Simple Actor System

View File

@@ -52,6 +52,11 @@ Where:
Examples: Examples:
- `mypackage/math.c` -> `js_mypackage_math_use` - `mypackage/math.c` -> `js_mypackage_math_use`
- `gitea.pockle.world/john/lib/render.c` -> `js_gitea_pockle_world_john_lib_render_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 ## 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 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 ## Build Process
C files are automatically compiled when you run: C files are automatically compiled when you run:
@@ -253,7 +230,7 @@ pit build
pit update 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 ## Platform-Specific Code

View File

@@ -70,10 +70,11 @@ pit ls <package> # list files in specified package
### pit build ### 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 ```bash
pit build pit build # build current package
pit build <package> # build specific package
``` ```
### pit test ### pit test
@@ -122,6 +123,103 @@ Clean build artifacts.
pit clean 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 ### pit help
Display help information. Display help information.
@@ -131,16 +229,6 @@ pit help
pit help <command> 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 ## Package Locators
Packages are identified by locators: Packages are identified by locators:
@@ -159,9 +247,11 @@ pit install /Users/john/work/mylib
``` ```
~/.pit/ ~/.pit/
├── packages/ # installed packages ├── packages/ # installed package sources
├── lib/ # compiled dynamic libraries ├── lib/ # installed per-file dylibs and mach (persistent)
├── build/ # build cache │ ├── core/ # core package: .dylib and .mach files
│ └── <pkg>/ # per-package subdirectories
├── build/ # ephemeral build cache (safe to delete)
├── cache/ # downloaded archives ├── cache/ # downloaded archives
├── lock.toml # installed package versions ├── lock.toml # installed package versions
└── link.toml # local development links └── link.toml # local development links

View File

@@ -19,7 +19,8 @@ mypackage/
├── helper/ ├── helper/
│ └── math.cm # nested module │ └── math.cm # nested module
├── render.c # C extension ├── render.c # C extension
└── _internal.cm # private module (underscore prefix) └── internal/
└── helpers.cm # private module (internal/ only)
``` ```
## pit.toml ## pit.toml
@@ -60,12 +61,12 @@ use('json') // core module
### Private Modules ### Private Modules
Files starting with underscore are private: Files in the `internal/` directory are private to their package:
```javascript ```javascript
// _internal.cm is only accessible within the same package // internal/helpers.cm is only accessible within the same package
use('internal') // OK from same package use('internal/helpers') // OK from same package
use('myapp/internal') // Error from other packages use('myapp/internal/helpers') // Error from other packages
``` ```
## Package Locators ## Package Locators
@@ -105,8 +106,11 @@ Local packages are symlinked into the shop, making development seamless.
│ └── work/ │ └── work/
│ └── mylib -> /Users/john/work/mylib │ └── mylib -> /Users/john/work/mylib
├── lib/ ├── lib/
│ ├── local.dylib │ ├── core/
└── gitea_pockle_world_john_prosperon.dylib │ ├── fd.dylib
│ │ └── time.mach
│ └── gitea_pockle_world_john_prosperon/
│ └── sprite.dylib
├── build/ ├── build/
│ └── <content-addressed cache> │ └── <content-addressed cache>
├── cache/ ├── cache/
@@ -171,16 +175,16 @@ pit link delete gitea.pockle.world/john/prosperon
## C Extensions ## 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/ mypackage/
├── pit.toml ├── pit.toml
├── render.c # compiled to mypackage.dylib ├── render.c # compiled to lib/mypackage/render.dylib
└── render.cm # optional ƿit wrapper └── 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. See [Writing C Modules](/docs/c-modules/) for details.

223
docs/shop.md Normal file
View File

@@ -0,0 +1,223 @@
---
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, startup takes one of two paths:
### Fast path (warm cache)
```
C runtime → engine.cm (from cache) → shop.cm → user program
```
The C runtime hashes the source of `internal/engine.cm` with BLAKE2 and looks up the hash in the content-addressed cache (`~/.pit/build/<hash>`). On a cache hit, engine.cm loads directly — no bootstrap involved.
### Cold path (first run or cache cleared)
```
C runtime → bootstrap.cm → (seeds cache) → engine.cm (from cache) → shop.cm → user program
```
On a cache miss, the C runtime loads `boot/bootstrap.cm.mcode` (a pre-compiled seed). Bootstrap compiles engine.cm and the pipeline modules (tokenize, parse, fold, mcode, streamline) from source and caches the results. The C runtime then retries the engine cache lookup, which now succeeds.
### Engine
**engine.cm** is self-sufficient. It loads its own compilation pipeline from the content-addressed cache, with fallback to the pre-compiled seeds in `boot/`. It defines `analyze()` (source to AST), `compile_to_blob()` (AST to binary blob), and `use_core()` for loading core modules. It creates the actor runtime and loads shop.cm via `use_core('internal/shop')`.
### 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.
### Cache invalidation
All caching is content-addressed by BLAKE2 hash of the source. When any source file changes, its hash changes and the old cache entry is simply never looked up again. No manual invalidation is needed. To force a full rebuild, delete `~/.pit/build/`.
## 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. **Installed mach** — pre-compiled bytecode in `~/.pit/lib/<pkg>/<stem>.mach`
4. **Cached bytecode** — content-addressed in `~/.pit/build/<hash>` (no extension)
5. **Cached .mcode IR** — JSON IR in `~/.pit/build/<hash>.mcode`
6. **Internal symbols** — statically linked into the `pit` binary (fat builds)
7. **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 5-7 are cached back to the content-addressed store for future loads.
Each loading method (except the in-memory cache) can be individually enabled or disabled via `shop.toml` policy flags — see [Shop Configuration](#shop-configuration) below.
### 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 content-addressed pattern. On first use, a module is compiled from source and cached by the BLAKE2 hash of its source content. Subsequent loads with unchanged source hit the cache directly.
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 Configuration
The shop reads an optional `shop.toml` file from the shop root (`~/.pit/shop.toml`). This file controls which loading methods are permitted through policy flags.
### Policy Flags
All flags default to `true`. Set a flag to `false` to disable that loading method.
```toml
[policy]
allow_dylib = true # per-file .dylib loading (requires dlopen)
allow_static = true # statically linked C symbols (fat builds)
allow_mach = true # pre-compiled .mach bytecode (lib/ and build cache)
allow_compile = true # on-the-fly source compilation
```
### Example Configurations
**Production lockdown** — only use pre-compiled artifacts, never compile from source:
```toml
[policy]
allow_compile = false
```
**Pure-script mode** — bytecode only, no native code:
```toml
[policy]
allow_dylib = false
allow_static = false
```
**No dlopen platforms** — static linking and bytecode only:
```toml
[policy]
allow_dylib = false
```
If `shop.toml` is missing or has no `[policy]` section, all methods are enabled (default behavior).
## 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
└── shop.toml # optional shop configuration and policy flags
```
## Key Files
| File | Role |
|------|------|
| `internal/bootstrap.cm` | Minimal cache seeder (cold start only) |
| `internal/engine.cm` | Self-sufficient entry point: compilation pipeline, actor runtime, `use_core()` |
| `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) |
| `boot/*.cm.mcode` | Pre-compiled pipeline seeds (tokenize, parse, fold, mcode, bootstrap) |

View File

@@ -82,3 +82,14 @@ Named property instructions (`LOAD_FIELD`, `STORE_FIELD`, `DELETE`) use the iABC
2. `LOAD_DYNAMIC` / `STORE_DYNAMIC` / `DELETEINDEX` — use the register-based variant 2. `LOAD_DYNAMIC` / `STORE_DYNAMIC` / `DELETEINDEX` — use the register-based variant
This is transparent to the mcode compiler and streamline optimizer. This is transparent to the mcode compiler and streamline optimizer.
## Arithmetic Dispatch
Arithmetic ops (ADD, SUB, MUL, DIV, MOD, POW) are executed inline without calling the polymorphic `reg_vm_binop()` helper. Since mcode's type guard dispatch guarantees both operands are numbers:
1. **Int-int fast path**: `JS_VALUE_IS_BOTH_INT` → native integer arithmetic with int32 overflow check. Overflow promotes to float64.
2. **Float fallback**: `JS_ToFloat64` → native floating-point operation. Non-finite results produce null.
DIV and MOD check for zero divisor (→ null). POW uses `pow()` with non-finite handling for finite inputs.
Comparison ops (EQ through GE) and bitwise ops still use `reg_vm_binop()` for their slow paths, as they handle a wider range of type combinations (string comparisons, null equality, etc.).

View File

@@ -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. 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 ## 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: 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:

View File

@@ -26,7 +26,7 @@ Every heap-allocated object begins with a 64-bit header word (`objhdr_t`):
### Flags (bits 3-7) ### Flags (bits 3-7)
- **Bit 3 (S)** — Stone flag. If set, the object is immutable and excluded from GC. - **Bit 3 (S)** — Stone flag. If set, the object is immutable. Stone text in the constant table (ct) is not copied by GC since it lives outside the heap; stone objects on the GC heap are copied normally.
- **Bit 4 (P)** — Properties flag. - **Bit 4 (P)** — Properties flag.
- **Bit 5 (A)** — Array flag. - **Bit 5 (A)** — Array flag.
- **Bit 7 (R)** — Reserved. - **Bit 7 (R)** — Reserved.
@@ -69,7 +69,9 @@ struct JSText {
}; };
``` ```
Text is stored as UTF-32, with two 32-bit codepoints packed per 64-bit word. When a text object is stoned, the length field is repurposed to cache the hash value (computed via `fash64`), since stoned text is immutable and the hash never changes. Text is stored as UTF-32, with two 32-bit codepoints packed per 64-bit word.
A mutable text (pretext) uses capacity for the allocated slot count and length for the current codepoint count. When a pretext is stoned, the capacity field is set to the actual length (codepoint count), and the length field is zeroed for use as a lazy hash cache (computed via `fash64` on first use as a key). Since stoned text is immutable, the hash never changes. Stoning is done in-place — no new allocation is needed.
## Record ## Record
@@ -111,7 +113,7 @@ struct JSFrame {
objhdr_t header; // type=6, capacity=slot count objhdr_t header; // type=6, capacity=slot count
JSValue function; // owning function JSValue function; // owning function
JSValue caller; // parent frame JSValue caller; // parent frame
uint32_t return_pc; // return address JSValue address; // return address
JSValue slots[]; // [this][args][captured][locals][temps] JSValue slots[]; // [this][args][captured][locals][temps]
}; };
``` ```
@@ -138,4 +140,4 @@ All objects are aligned to 8 bytes. The total size in bytes for each type:
| Record | `8 + 8 + 8 + (capacity + 1) * 16` | | Record | `8 + 8 + 8 + (capacity + 1) * 16` |
| Function | `sizeof(JSFunction)` (fixed) | | Function | `sizeof(JSFunction)` (fixed) |
| Code | `sizeof(JSFunctionBytecode)` (fixed) | | Code | `sizeof(JSFunctionBytecode)` (fixed) |
| Frame | `8 + 8 + 8 + 4 + capacity * 8` | | Frame | `8 + 8 + 8 + 8 + capacity * 8` |

View File

@@ -104,7 +104,8 @@ pit --emit-qbe script.ce > output.ssa
| `streamline.cm` | Mcode IR optimizer | | `streamline.cm` | Mcode IR optimizer |
| `qbe_emit.cm` | Mcode IR → QBE IL emitter | | `qbe_emit.cm` | Mcode IR → QBE IL emitter |
| `qbe.cm` | QBE IL operation templates | | `qbe.cm` | QBE IL operation templates |
| `internal/bootstrap.cm` | Pipeline orchestrator | | `internal/bootstrap.cm` | Cache seeder (cold start only) |
| `internal/engine.cm` | Self-sufficient pipeline loader and orchestrator |
## Debug Tools ## Debug Tools

View File

@@ -45,11 +45,10 @@ Backward inference rules:
| Operator class | Operand type inferred | | Operator class | Operand type inferred |
|---|---| |---|---|
| `subtract`, `multiply`, `divide`, `modulo`, `pow`, `negate` | T_NUM | | `add`, `subtract`, `multiply`, `divide`, `modulo`, `pow`, `negate` | T_NUM |
| `eq_int`, `ne_int`, `lt_int`, `gt_int`, `le_int`, `ge_int`, bitwise ops | T_INT | | bitwise ops (`bitand`, `bitor`, `bitxor`, `shl`, `shr`, `ushr`, `bitnot`) | T_INT |
| `eq_float`, `ne_float`, `lt_float`, `gt_float`, `le_float`, `ge_float` | T_FLOAT | | `concat` | T_TEXT |
| `concat`, text comparisons | T_TEXT | | `not`, `and`, `or` | T_BOOL |
| `eq_bool`, `ne_bool`, `not`, `and`, `or` | T_BOOL |
| `store_index` (object operand) | T_ARRAY | | `store_index` (object operand) | T_ARRAY |
| `store_index` (index operand) | T_INT | | `store_index` (index operand) | T_INT |
| `store_field` (object operand) | T_RECORD | | `store_field` (object operand) | T_RECORD |
@@ -59,9 +58,11 @@ Backward inference rules:
| `load_field` (object operand) | T_RECORD | | `load_field` (object operand) | T_RECORD |
| `pop` (array operand) | T_ARRAY | | `pop` (array operand) | T_ARRAY |
Note: `add` is excluded from backward inference because it is polymorphic — it handles both numeric addition and text concatenation. Only operators that are unambiguously numeric can infer T_NUM. Typed comparison operators (`eq_int`, `lt_float`, `lt_text`, etc.) and typed boolean comparisons (`eq_bool`, `ne_bool`) are excluded from backward inference. These ops always appear inside guard dispatch patterns (`is_type` + `jump_false` + typed_op), where mutually exclusive branches use the same slot with different types. Including them would merge conflicting types (e.g., T_INT from `lt_int` + T_FLOAT from `lt_float` + T_TEXT from `lt_text`) into T_UNKNOWN, losing all type information. Only unconditionally executed ops contribute to backward inference.
When a slot appears with conflicting type inferences, the result is `unknown`. INT + FLOAT conflicts produce `num`. Note: `add` infers T_NUM even though it is polymorphic (numeric addition or text concatenation). When `add` appears in the IR, both operands have already passed a `is_num` guard, so they are guaranteed to be numeric. The text concatenation path uses `concat` instead.
When a slot appears with conflicting type inferences, the merge widens: INT + FLOAT → NUM, INT + NUM → NUM, FLOAT + NUM → NUM. Incompatible types (e.g., NUM + TEXT) produce `unknown`.
**Nop prefix:** none (analysis only, does not modify instructions) **Nop prefix:** none (analysis only, does not modify instructions)
@@ -88,8 +89,9 @@ Write type mapping:
| `length` | T_INT | | `length` | T_INT |
| bitwise ops | T_INT | | bitwise ops | T_INT |
| `concat` | T_TEXT | | `concat` | T_TEXT |
| `negate` | T_NUM |
| `add`, `subtract`, `multiply`, `divide`, `modulo`, `pow` | T_NUM |
| bool ops, comparisons, `in` | T_BOOL | | bool ops, comparisons, `in` | T_BOOL |
| generic arithmetic (`add`, `subtract`, `negate`, etc.) | T_UNKNOWN |
| `move`, `load_field`, `load_index`, `load_dynamic`, `pop`, `get` | T_UNKNOWN | | `move`, `load_field`, `load_index`, `load_dynamic`, `pop`, `get` | T_UNKNOWN |
| `invoke`, `tail_invoke` | T_UNKNOWN | | `invoke`, `tail_invoke` | T_UNKNOWN |
@@ -100,8 +102,9 @@ Common patterns this enables:
- **Length variables** (`var len = length(arr)`): written by `length` (T_INT) only → invariant T_INT - **Length variables** (`var len = length(arr)`): written by `length` (T_INT) only → invariant T_INT
- **Boolean flags** (`var found = false; ... found = true`): written by `false` and `true` → invariant T_BOOL - **Boolean flags** (`var found = false; ... found = true`): written by `false` and `true` → invariant T_BOOL
- **Locally-created containers** (`var arr = []`): written by `array` only → invariant T_ARRAY - **Locally-created containers** (`var arr = []`): written by `array` only → invariant T_ARRAY
- **Numeric accumulators** (`var sum = 0; sum = sum - x`): written by `access 0` (T_INT) and `subtract` (T_NUM) → merges to T_NUM
Note: Loop counters (`var i = 0; i = i + 1`) are NOT invariant because `add` produces T_UNKNOWN. However, if `i` is a function parameter used in arithmetic, backward inference from `subtract`/`multiply`/etc. will infer T_NUM for it, which persists across labels. Note: Loop counters using `+` (`var i = 0; i = i + 1`) may not achieve write-type invariance because the `+` operator emits a guard dispatch with both `concat` (T_TEXT) and `add` (T_NUM) paths writing to the same temp slot, producing T_UNKNOWN. However, when one operand is a known number literal, `mcode.cm` emits a numeric-only path (see "Known-Number Add Shortcut" below), avoiding the text dispatch. Other arithmetic ops (`-`, `*`, `/`, `%`, `**`) always emit a single numeric write path and work cleanly with write-type analysis.
**Nop prefix:** none (analysis only, does not modify instructions) **Nop prefix:** none (analysis only, does not modify instructions)
@@ -109,9 +112,11 @@ Note: Loop counters (`var i = 0; i = i + 1`) are NOT invariant because `add` pro
Forward pass that tracks the known type of each slot. When a type check (`is_int`, `is_text`, `is_num`, etc.) is followed by a conditional jump, and the slot's type is already known, the check and jump can be eliminated or converted to an unconditional jump. Forward pass that tracks the known type of each slot. When a type check (`is_int`, `is_text`, `is_num`, etc.) is followed by a conditional jump, and the slot's type is already known, the check and jump can be eliminated or converted to an unconditional jump.
Three cases: Five cases:
- **Known match** (e.g., `is_int` on a slot known to be `int`): both the check and the conditional jump are eliminated (nop'd). - **Known match** (e.g., `is_int` on a slot known to be `int`): both the check and the conditional jump are eliminated (nop'd).
- **Subsumption match** (e.g., `is_num` on a slot known to be `int` or `float`): since `int` and `float` are subtypes of `num`, both the check and jump are eliminated.
- **Subsumption partial** (e.g., `is_int` on a slot known to be `num`): the `num` type could be `int` or `float`, so the check must remain. On fallthrough, the slot narrows to the checked subtype (`int`). This is NOT a mismatch — `num` values can pass an `is_int` check.
- **Known mismatch** (e.g., `is_text` on a slot known to be `int`): the check is nop'd and the conditional jump is rewritten to an unconditional `jump`. - **Known mismatch** (e.g., `is_text` on a slot known to be `int`): the check is nop'd and the conditional jump is rewritten to an unconditional `jump`.
- **Unknown**: the check remains, but on fallthrough, the slot's type is narrowed to the checked type (enabling downstream eliminations). - **Unknown**: the check remains, but on fallthrough, the slot's type is narrowed to the checked type (enabling downstream eliminations).
@@ -212,12 +217,44 @@ These inlined opcodes have corresponding Mach VM implementations in `mach.c`.
Arithmetic operations use generic opcodes: `add`, `subtract`, `multiply`, `divide`, `modulo`, `pow`, `negate`. There are no type-dispatched variants (e.g., no `add_int`/`add_float`). Arithmetic operations use generic opcodes: `add`, `subtract`, `multiply`, `divide`, `modulo`, `pow`, `negate`. There are no type-dispatched variants (e.g., no `add_int`/`add_float`).
The Mach VM dispatches at runtime with an int-first fast path via `reg_vm_binop()`: it checks `JS_VALUE_IS_BOTH_INT` first for fast integer arithmetic, then falls back to float conversion, text concatenation (for `add` only), or type error. The Mach VM handles arithmetic inline with a two-tier fast path. Since mcode's type guard dispatch guarantees both operands are numbers by the time arithmetic executes, the VM does not need polymorphic dispatch:
1. **Int-int fast path**: `JS_VALUE_IS_BOTH_INT` → native integer arithmetic with overflow check. If the result fits int32, returns int32; otherwise promotes to float64.
2. **Float fallback**: `JS_ToFloat64` both operands → native floating-point arithmetic. Non-finite results (infinity, NaN) produce null.
Division and modulo additionally check for zero divisor (→ null). Power uses `pow()` with non-finite handling.
The legacy `reg_vm_binop()` function remains available for comparison operators and any non-mcode bytecode paths, but arithmetic ops no longer call it.
Bitwise operations (`shl`, `shr`, `ushr`, `bitand`, `bitor`, `bitxor`, `bitnot`) remain integer-only and disrupt if operands are not integers. Bitwise operations (`shl`, `shr`, `ushr`, `bitand`, `bitor`, `bitxor`, `bitnot`) remain integer-only and disrupt if operands are not integers.
The QBE/native backend maps generic arithmetic to helper calls (`qbe.add`, `qbe.sub`, etc.). The vision for the native path is that with sufficient type inference, the backend can unbox proven-numeric values to raw registers, operate directly, and only rebox at boundaries (returns, calls, stores). The QBE/native backend maps generic arithmetic to helper calls (`qbe.add`, `qbe.sub`, etc.). The vision for the native path is that with sufficient type inference, the backend can unbox proven-numeric values to raw registers, operate directly, and only rebox at boundaries (returns, calls, stores).
## Known-Number Add Shortcut
The `+` operator is the only arithmetic op that is polymorphic at the mcode level — `emit_add_decomposed` in `mcode.cm` emits a guard dispatch that checks for text (→ `concat`) before numeric (→ `add`). This dual dispatch means the temp slot is written by both `concat` (T_TEXT) and `add` (T_NUM), producing T_UNKNOWN in write-type analysis.
When either operand is a known number literal (e.g., `i + 1`, `x + 0.5`), `emit_add_decomposed` skips the text dispatch entirely and emits `emit_numeric_binop("add")` — a single `is_num` guard + `add` with no `concat` path. This is safe because text concatenation requires both operands to be text; a known number can never participate in concat.
This optimization eliminates 6-8 instructions from the add block (two `is_text` checks, two conditional jumps, `concat`, `jump`) and produces a clean single-type write path that works with write-type analysis.
Other arithmetic ops (`subtract`, `multiply`, etc.) always use `emit_numeric_binop` and never have this problem.
## Target Slot Propagation
For simple local variable assignments (`i = expr`), the mcode compiler passes the variable's register slot as a `target` to the expression compiler. Binary operations that use `emit_numeric_binop` (subtract, multiply, divide, modulo, pow) can write directly to the target slot instead of allocating a temp and emitting a `move`:
```
// Before: i = i - 1
subtract 7, 2, 6 // temp = i - 1
move 2, 7 // i = temp
// After: i = i - 1
subtract 2, 2, 6 // i = i - 1 (direct)
```
The `+` operator is excluded from target slot propagation when it would use the full text+num dispatch (i.e., when neither operand is a known number), because writing both `concat` and `add` to the variable's slot would pollute its write type. When the known-number shortcut applies, `+` uses `emit_numeric_binop` and would be safe for target propagation, but this is not currently implemented — the exclusion is by operator kind, not by dispatch path.
## Debugging Tools ## Debugging Tools
Three dump tools inspect the IR at different stages: Three dump tools inspect the IR at different stages:
@@ -295,6 +332,18 @@ The current purity set is conservative (only `is_*`). It could be expanded by:
- **User function purity**: Analyze user-defined function bodies during pre_scan. A function is pure if its body contains only pure expressions and calls to known-pure functions. This requires fixpoint iteration for mutual recursion. - **User function purity**: Analyze user-defined function bodies during pre_scan. A function is pure if its body contains only pure expressions and calls to known-pure functions. This requires fixpoint iteration for mutual recursion.
- **Callback-aware purity**: Intrinsics like `filter`, `find`, `reduce`, `some`, `every` are pure if their callback argument is pure. - **Callback-aware purity**: Intrinsics like `filter`, `find`, `reduce`, `some`, `every` are pure if their callback argument is pure.
### Move Type Resolution in Write-Type Analysis
Currently, `move` instructions produce T_UNKNOWN in write-type analysis. This prevents type propagation through moves — e.g., a slot written by `access 0` (T_INT) and `move` from an `add` result (T_NUM) merges to T_UNKNOWN instead of T_NUM.
A two-pass approach would fix this: first compute write types for all non-move instructions, then resolve moves by looking up the source slot's computed type. If the source has a known type, merge it into the destination; if unknown, skip the move (don't poison the destination with T_UNKNOWN).
This was implemented and tested but causes a bootstrap failure during self-hosting convergence. The root cause is not yet understood — the optimizer modifies its own bytecode, and the move resolution changes the type landscape enough to produce different code on each pass, preventing convergence. Further investigation is needed; the fix is correct in isolation but interacts badly with the self-hosting fixed-point iteration.
### Target Slot Propagation for Add with Known Numbers
When the known-number add shortcut applies (one operand is a literal number), the generated code uses `emit_numeric_binop` which has a single write path. Target slot propagation should be safe in this case, but is currently blocked by the blanket `kind != "+"` exclusion. Refining the exclusion to check whether the shortcut will apply (by testing `is_known_number` on either operand) would enable direct writes for patterns like `i = i + 1`.
### Forward Type Narrowing from Typed Operations ### Forward Type Narrowing from Typed Operations
With unified arithmetic (generic `add`/`subtract`/`multiply`/`divide`/`modulo`/`negate` instead of typed variants), this approach is no longer applicable. Typed comparisons (`eq_int`, `lt_float`, etc.) still exist and their operands have known types, but these are already handled by backward inference. With unified arithmetic (generic `add`/`subtract`/`multiply`/`divide`/`modulo`/`negate` instead of typed variants), this approach is no longer applicable. Typed comparisons (`eq_int`, `lt_float`, etc.) still exist and their operands have known types, but these are already handled by backward inference.

22
dump_ir.ce Normal file
View File

@@ -0,0 +1,22 @@
var tokenize = use('tokenize')
var parse_mod = use('parse')
var fold = use('fold')
var mcode_mod = use('mcode')
var streamline_mod = use('streamline')
var json = use('json')
var fd = use('fd')
var file = args[0]
var src = text(fd.slurp(file))
var tok = tokenize(src, file)
var ast = parse_mod(tok.tokens, src, file, tokenize)
var folded = fold(ast)
var compiled = mcode_mod(folded)
var optimized = streamline_mod(compiled)
var instrs = optimized.main.instructions
var i = 0
while (i < length(instrs)) {
print(text(i) + ': ' + json.encode(instrs[i]))
i = i + 1
}

2
fd.cm
View File

@@ -1,4 +1,4 @@
var fd = native var fd = use('internal/fd')
var wildstar = use('wildstar') var wildstar = use('wildstar')
function last_pos(str, sep) { function last_pos(str, sep) {

View File

@@ -12,8 +12,9 @@ var shop = use('internal/shop')
// Parse arguments // Parse arguments
var target_pkg = null 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') { if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell fetch [package]") log.console("Usage: cell fetch [package]")
log.console("Fetch package zips from remote sources.") log.console("Fetch package zips from remote sources.")

9
fit.c
View File

@@ -248,9 +248,10 @@ static const JSCFunctionListEntry js_fit_funcs[] = {
MIST_FUNC_DEF(fit, zeros, 1), 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_FRAME(js);
JS_SetPropertyFunctionList(js, mod, js_fit_funcs, countof(js_fit_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_fit_funcs, countof(js_fit_funcs));
JS_RETURN(mod.val);
} }

59
fold.cm
View File

@@ -5,6 +5,34 @@ var fold = function(ast) {
var scopes = ast.scopes var scopes = ast.scopes
var nr_scopes = length(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 // Helpers
// ============================================================ // ============================================================
@@ -194,11 +222,7 @@ var fold = function(ast) {
if (rhs_target != null && rhs_target.intrinsic == true) { if (rhs_target != null && rhs_target.intrinsic == true) {
sv = scope_var(fn_nr, name) sv = scope_var(fn_nr, name)
if (sv != null && sv.type_tag == null) { if (sv != null && sv.type_tag == null) {
if (rhs_target.name == "array") sv.type_tag = "array" if (type_tag_map[rhs_target.name] != null) sv.type_tag = type_tag_map[rhs_target.name]
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"
} }
} }
} }
@@ -357,17 +381,13 @@ var fold = function(ast) {
var arg = null var arg = null
// Recurse into children first (bottom-up) // Recurse into children first (bottom-up)
if (k == "+" || k == "-" || k == "*" || k == "/" || k == "%" || if (binary_ops[k] == true) {
k == "**" || k == "==" || k == "!=" || k == "<" || k == ">" ||
k == "<=" || k == ">=" || k == "&" || k == "|" || k == "^" ||
k == "<<" || k == ">>" || k == ">>>" || k == "&&" || k == "||" ||
k == "," || k == "in") {
expr.left = fold_expr(expr.left, fn_nr) expr.left = fold_expr(expr.left, fn_nr)
expr.right = fold_expr(expr.right, fn_nr) expr.right = fold_expr(expr.right, fn_nr)
} else if (k == "." || k == "[") { } else if (k == "." || k == "[") {
expr.left = fold_expr(expr.left, fn_nr) expr.left = fold_expr(expr.left, fn_nr)
if (k == "[" && expr.right != null) expr.right = fold_expr(expr.right, 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) expr.expression = fold_expr(expr.expression, fn_nr)
} else if (k == "++" || k == "--") { } else if (k == "++" || k == "--") {
return expr return expr
@@ -382,7 +402,7 @@ var fold = function(ast) {
expr.list[i] = fold_expr(expr.list[i], fn_nr) expr.list[i] = fold_expr(expr.list[i], fn_nr)
i = i + 1 i = i + 1
} }
} else if (k == "array") { } else if (k == "array" || k == "text literal") {
i = 0 i = 0
while (i < length(expr.list)) { while (i < length(expr.list)) {
expr.list[i] = fold_expr(expr.list[i], fn_nr) 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) expr.list[i].right = fold_expr(expr.list[i].right, fn_nr)
i = i + 1 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") { } else if (k == "function") {
fold_fn(expr) fold_fn(expr)
return expr return expr
} else if (k == "assign" || k == "+=" || k == "-=" || k == "*=" || } else if (assign_ops[k] == true) {
k == "/=" || k == "%=" || k == "<<=" || k == ">>=" ||
k == ">>>=" || k == "&=" || k == "^=" || k == "|=" ||
k == "**=" || k == "&&=" || k == "||=") {
expr.right = fold_expr(expr.right, fn_nr) expr.right = fold_expr(expr.right, fn_nr)
return expr return expr
} }
@@ -428,7 +439,7 @@ var fold = function(ast) {
} }
// Binary constant folding // Binary constant folding
if (k == "+" || k == "-" || k == "*" || k == "/" || k == "%" || k == "**") { if (arith_ops[k] == true) {
left = expr.left left = expr.left
right = expr.right right = expr.right
if (left != null && right != null && left.kind == "number" && right.kind == "number") { if (left != null && right != null && left.kind == "number" && right.kind == "number") {
@@ -460,7 +471,7 @@ var fold = function(ast) {
} }
// Comparison folding // Comparison folding
if (k == "==" || k == "!=" || k == "<" || k == ">" || k == "<=" || k == ">=") { if (comparison_ops[k] == true) {
left = expr.left left = expr.left
right = expr.right right = expr.right
if (left != null && right != null) { if (left != null && right != null) {

File diff suppressed because it is too large Load Diff

View File

@@ -134,7 +134,7 @@ function run_fuzz(seed_val) {
// Run optimized // Run optimized
var _opt = function() { var _opt = function() {
mod_opt = run_ast_fn(name, ast, {use: function(p) { return use(p) }}) mod_opt = run_ast_fn(name, ast, stone({use: function(p) { return use(p) }}))
} disruption { } disruption {
opt_err = "disrupted" opt_err = "disrupted"
} }
@@ -142,7 +142,7 @@ function run_fuzz(seed_val) {
// Run unoptimized // Run unoptimized
var _noopt = function() { var _noopt = function() {
mod_noopt = run_ast_noopt_fn(name + "_noopt", ast, {use: function(p) { return use(p) }}) mod_noopt = run_ast_noopt_fn(name + "_noopt", ast, stone({use: function(p) { return use(p) }}))
} disruption { } disruption {
noopt_err = "disrupted" noopt_err = "disrupted"
} }

View File

@@ -22,8 +22,10 @@ var target_locator = null
var format = 'tree' var format = 'tree'
var show_locked = false var show_locked = false
var show_world = 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 (args[i] == '--format' || args[i] == '-f') {
if (i + 1 < length(args)) { if (i + 1 < length(args)) {
format = args[++i] format = args[++i]
@@ -91,7 +93,7 @@ function gather_graph(locator, visited) {
add_node(locator) add_node(locator)
try { var _gather = function() {
var deps = pkg.dependencies(locator) var deps = pkg.dependencies(locator)
if (deps) { if (deps) {
arrfor(array(deps), function(alias) { arrfor(array(deps), function(alias) {
@@ -101,17 +103,19 @@ function gather_graph(locator, visited) {
gather_graph(dep_locator, visited) gather_graph(dep_locator, visited)
}) })
} }
} catch (e) { } disruption {
// Package might not have dependencies // Package might not have dependencies
} }
_gather()
} }
// Gather graph from roots // Gather graph from roots
var roots = [] var roots = []
var packages = null
if (show_world) { if (show_world) {
// Use all packages in shop as roots // Use all packages in shop as roots
var packages = shop.list_packages() packages = shop.list_packages()
arrfor(packages, function(p) { arrfor(packages, function(p) {
if (p != 'core') { if (p != 'core') {
push(roots, p) push(roots, p)
@@ -125,7 +129,7 @@ if (show_world) {
// Resolve local paths // Resolve local paths
if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) { 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) { if (resolved) {
target_locator = resolved target_locator = resolved
} }
@@ -139,16 +143,24 @@ arrfor(roots, function(root) {
}) })
// Output based on format // Output based on format
var children = null
var j = 0
var output = null
if (format == 'tree') { if (format == 'tree') {
function print_tree(locator, prefix, is_last, visited) { 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]) { if (visited[locator]) {
log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + " (circular)") log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + " (circular)")
return return
} }
visited[locator] = true visited[locator] = true
var node = nodes[locator] node = nodes[locator]
var suffix = "" suffix = ""
if (node.linked) suffix += " -> " + node.effective if (node.linked) suffix += " -> " + node.effective
if (node.commit) suffix += " @" + node.commit if (node.commit) suffix += " @" + node.commit
if (node.local) suffix += " (local)" if (node.local) suffix += " (local)"
@@ -156,30 +168,30 @@ if (format == 'tree') {
log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + suffix) log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + suffix)
// Get children // Get children
var children = [] children = []
arrfor(edges, function(e) { arrfor(edges, function(e) {
if (e.from == locator) { if (e.from == locator) {
push(children, e) push(children, e)
} }
}) })
for (var i = 0; i < length(children); i++) { for (j = 0; j < length(children); j++) {
var child_prefix = prefix + (is_last ? " " : "| ") child_prefix = prefix + (is_last ? " " : "| ")
print_tree(children[i].to, child_prefix, i == length(children) - 1, visited) print_tree(children[j].to, child_prefix, j == length(children) - 1, visited)
} }
} }
for (var i = 0; i < length(roots); i++) { for (i = 0; i < length(roots); i++) {
log.console(roots[i]) log.console(roots[i])
var children = [] children = []
arrfor(edges, function(e) { arrfor(edges, function(e) {
if (e.from == roots[i]) { if (e.from == roots[i]) {
push(children, e) 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, {}) print_tree(children[j].to, "", j == length(children) - 1, {})
} }
@@ -219,7 +231,7 @@ if (format == 'tree') {
log.console("}") log.console("}")
} else if (format == 'json') { } else if (format == 'json') {
var output = { output = {
nodes: [], nodes: [],
edges: [] edges: []
} }

16
help.ce
View File

@@ -3,27 +3,29 @@
var fd = use('fd') var fd = use('fd')
var command = length(args) > 0 ? args[0] : null var command = length(args) > 0 ? args[0] : null
var man_file = null
var stat = null
var content = null
// Display specific command help // Display specific command help
if (command) { if (command) {
var man_file = 'scripts/man/' + command + '.man' man_file = 'scripts/man/' + command + '.man'
var stat = fd.stat(man_file); stat = fd.stat(man_file)
if (stat && stat.isFile) { if (stat && stat.isFile) {
var content = text(fd.slurp(man_file)) content = text(fd.slurp(man_file))
log.console(content) log.console(content)
} else { } else {
log.error("No help available for command: " + command) log.error("No help available for command: " + command)
log.console("Run 'cell help' to see available commands.") log.console("Run 'cell help' to see available commands.")
} }
$stop() $stop()
return
} }
// Display general help // Display general help
var cell_man = 'scripts/man/cell.man' man_file = 'scripts/man/cell.man'
var stat = fd.stat(cell_man); stat = fd.stat(man_file)
if (stat && stat.isFile) { if (stat && stat.isFile) {
var content = text(fd.slurp(cell_man)) content = text(fd.slurp(man_file))
log.console(content) log.console(content)
} else { } else {
// Fallback if man file doesn't exist // Fallback if man file doesn't exist

View File

@@ -28,8 +28,10 @@ var locator = null
var target_triple = null var target_triple = null
var refresh = false var refresh = false
var dry_run = 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 (args[i] == '--target' || args[i] == '-t') {
if (i + 1 < length(args)) { if (i + 1 < length(args)) {
target_triple = args[++i] target_triple = args[++i]
@@ -64,7 +66,7 @@ if (!locator) {
// Resolve relative paths to absolute paths // Resolve relative paths to absolute paths
// Local paths like '.' or '../foo' need to be converted 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)) { if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) {
var resolved = fd.realpath(locator) resolved = fd.realpath(locator)
if (resolved) { if (resolved) {
locator = resolved locator = resolved
} }
@@ -83,6 +85,9 @@ var skipped_packages = []
var visited = {} var visited = {}
function gather_packages(pkg_locator) { function gather_packages(pkg_locator) {
var lock = null
var update_result = null
var deps = null
if (visited[pkg_locator]) return if (visited[pkg_locator]) return
visited[pkg_locator] = true visited[pkg_locator] = true
@@ -96,12 +101,12 @@ function gather_packages(pkg_locator) {
push(packages_to_install, pkg_locator) push(packages_to_install, pkg_locator)
// Try to read dependencies // Try to read dependencies
try { var _gather = function() {
// For packages not yet extracted, we need to update and extract first to read deps // 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 (!lock[pkg_locator]) {
if (!dry_run) { if (!dry_run) {
var update_result = shop.update(pkg_locator) update_result = shop.update(pkg_locator)
if (update_result) { if (update_result) {
shop.extract(pkg_locator) shop.extract(pkg_locator)
} else { } else {
@@ -117,19 +122,20 @@ function gather_packages(pkg_locator) {
} }
} }
var deps = pkg.dependencies(pkg_locator) deps = pkg.dependencies(pkg_locator)
if (deps) { if (deps) {
arrfor(array(deps), function(alias) { arrfor(array(deps), function(alias) {
var dep_locator = deps[alias] var dep_locator = deps[alias]
gather_packages(dep_locator) gather_packages(dep_locator)
}) })
} }
} catch (e) { } disruption {
// Package might not have dependencies or cell.toml issue // Package might not have dependencies or cell.toml issue
if (!dry_run) { 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 // Gather all packages
@@ -164,11 +170,12 @@ function install_package(pkg_locator) {
shop.build_package_scripts(pkg_locator) shop.build_package_scripts(pkg_locator)
// Build C code // Build C code
try { var _build_c = function() {
build.build_dynamic(pkg_locator, target_triple, 'release') build.build_dynamic(pkg_locator, target_triple, 'release')
} catch (e) { } disruption {
// Not all packages have C code // Not all packages have C code
} }
_build_c()
} }
arrfor(packages_to_install, function(p) { arrfor(packages_to_install, function(p) {

View File

@@ -1,310 +1,105 @@
// Hidden vars come from env: // Minimal bootstrap — seeds the content-addressed cache
// CLI mode (cell_init): os, args, core_path, shop_path // Only runs on cold start (C runtime couldn't find engine in cache)
// Actor spawn (script_startup): os, json, nota, wota, actorsym, init, core_path, shop_path // Hidden vars: os, core_path, shop_path
// args[0] = script name, args[1..] = user args
var load_internal = os.load_internal var load_internal = os.load_internal
function use_embed(name) { 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 json_mod = use_embed('json')
var crypto = use_embed('crypto')
var use_cache = {} function content_hash(content) {
use_cache['fd'] = fd return text(crypto.blake2(content), 'h')
use_cache['os'] = os
use_cache['json'] = json
// Bootstrap: load tokenize.cm, parse.cm, fold.cm from pre-compiled mach bytecode
function use_basic(path) {
if (use_cache[path])
return use_cache[path]
var result = use_embed(replace(path, '/', '_'))
use_cache[path] = result
return result
} }
// Load a module from .mach/.mcode bytecode (bootstrap modules have no source fallback) function cache_path(hash) {
function boot_load(name, env) { if (!shop_path) return null
var mach_path = core_path + '/' + name + ".cm.mach" return shop_path + '/build/' + hash
var mcode_path = core_path + '/' + name + ".cm.mcode"
var data = null
var mcode_json = 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)
}
print("error: missing bootstrap bytecode: " + name + "\n")
disrupt
} }
var boot_env = {use: use_basic} function ensure_build_dir() {
var tokenize_mod = boot_load("tokenize", boot_env) if (!shop_path) return null
var parse_mod = boot_load("parse", boot_env) var dir = shop_path + '/build'
var fold_mod = boot_load("fold", boot_env) if (!fd.is_dir(dir)) fd.mkdir(dir)
use_cache['tokenize'] = tokenize_mod return dir
use_cache['parse'] = parse_mod
use_cache['fold'] = fold_mod
// Always load mcode compiler module
var mcode_mod = boot_load("mcode", boot_env)
use_cache['mcode'] = mcode_mod
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"
]
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
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)) {
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])
}
}
_i = _i + 1
}
if (length(stale) > 0) {
print("warning: bytecode is stale for: " + text(stale, ", ") + "\n")
print("run 'make regen' to update\n")
}
} }
check_mach_stale()
// analyze: tokenize + parse, check for errors // Load seed pipeline from boot/ (tokenize, parse, mcode only)
function boot_load(name) {
var mcode_path = core_path + '/boot/' + name + '.cm.mcode'
var mcode_blob = null
var mach_blob = null
if (!fd.is_file(mcode_path)) {
print("error: missing seed: " + name + "\n")
disrupt
}
mcode_blob = fd.slurp(mcode_path)
mach_blob = mach_compile_mcode_bin(name, text(mcode_blob))
return mach_load(mach_blob, stone({use: use_embed}))
}
var tokenize_mod = boot_load("tokenize")
var parse_mod = boot_load("parse")
var fold_mod = boot_load("fold")
var mcode_mod = boot_load("mcode")
function analyze(src, filename) { function analyze(src, filename) {
var tok_result = tokenize_mod(src, filename) var tok_result = tokenize_mod(src, filename)
var ast = parse_mod(tok_result.tokens, src, filename, tokenize_mod) var ast = parse_mod(tok_result.tokens, src, filename, tokenize_mod)
var _i = 0 var _i = 0
var prev_line = -1
var prev_msg = null
var e = null var e = null
var msg = null var msg = null
var line = null
var col = null
var has_errors = ast.errors != null && length(ast.errors) > 0 var has_errors = ast.errors != null && length(ast.errors) > 0
if (has_errors) { if (has_errors) {
while (_i < length(ast.errors)) { while (_i < length(ast.errors)) {
e = ast.errors[_i] e = ast.errors[_i]
msg = e.message msg = e.message
line = e.line if (e.line != null && e.column != null)
col = e.column print(`${filename}:${text(e.line)}:${text(e.column)}: error: ${msg}`)
if (msg != prev_msg || line != prev_line) { else
if (line != null && col != null) { print(`${filename}: error: ${msg}`)
print(`${filename}:${text(line)}:${text(col)}: error: ${msg}`)
} else {
print(`${filename}: error: ${msg}`)
}
}
prev_line = line
prev_msg = msg
_i = _i + 1 _i = _i + 1
} }
disrupt disrupt
} }
ast = fold_mod(ast) return fold_mod(ast)
return ast
} }
// Load a module from .mach/.mcode bytecode, falling back to source compilation function compile_and_cache(name, source_path) {
function load_module(name, env) { var source_blob = fd.slurp(source_path)
var mach_path = core_path + '/' + name + ".cm.mach" var hash = content_hash(source_blob)
var mcode_path = core_path + '/' + name + ".cm.mcode" var cached = cache_path(hash)
var data = null
var mcode_json = null
var src_path = null
var src = null
var ast = null var ast = null
var compiled = null var compiled = null
var optimized = null var mcode_json = null
if (fd.is_file(mach_path)) { var mach_blob = null
data = fd.slurp(mach_path) if (cached && fd.is_file(cached)) return
return mach_load(data, env) ast = analyze(text(source_blob), source_path)
}
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) compiled = mcode_mod(ast)
optimized = streamline_mod(compiled) mcode_json = json_mod.encode(compiled)
return mach_eval_mcode(name, json.encode(optimized), env) mach_blob = mach_compile_mcode_bin(name, mcode_json)
if (cached) {
ensure_build_dir()
fd.slurpwrite(cached, mach_blob)
}
} }
// Load optimization pipeline modules (needs analyze to be defined) // Seed the cache with everything engine needs
streamline_mod = load_module("streamline", boot_env) var seed_files = [
use_cache['streamline'] = streamline_mod {name: "tokenize", path: "tokenize.cm"},
{name: "parse", path: "parse.cm"},
// Lazy-loaded verify_ir module (loaded on first use via use_fn) {name: "fold", path: "fold.cm"},
var _verify_ir_mod = null {name: "mcode", path: "mcode.cm"},
{name: "streamline", path: "streamline.cm"},
// Run AST through mcode pipeline → register VM {name: "engine", path: "internal/engine.cm"}
function run_ast(name, ast, env) { ]
var compiled = mcode_mod(ast) var _i = 0
if (os._verify_ir) { var entry = null
if (_verify_ir_mod == null) { while (_i < length(seed_files)) {
_verify_ir_mod = use_fn('verify_ir') entry = seed_files[_i]
} compile_and_cache(entry.name, core_path + '/' + entry.path)
compiled._verify = true _i = _i + 1
compiled._verify_mod = _verify_ir_mod
}
var optimized = streamline_mod(compiled)
// Clean up verify properties before JSON encoding
if (optimized._verify) {
delete optimized._verify
delete optimized._verify_mod
}
return mach_eval_mcode(name, json.encode(optimized), 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)
}
// 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
}
// 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_json = 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)
}
engine_path = core_path + '/internal/engine.cm'
engine_src = text(fd.slurp(engine_path))
engine_ast = analyze(engine_src, engine_path)
return run_ast('engine', engine_ast, env)
}
// Detect mode and route
// CLI mode has 'args'; actor spawn mode has 'init'
var program = null
var user_args = []
var _j = 0
if (args != null) {
// CLI mode — always run as actor program (.ce)
program = args[0]
if (!program) {
print("error: no program specified\n")
disrupt
}
_j = 1
while (_j < length(args)) {
push(user_args, args[_j])
_j = _j + 1
}
load_engine({
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
})
} 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
})
} }
print("bootstrap: cache seeded\n")

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
// Hidden vars (os, actorsym, init, core_path, shop_path, analyze, run_ast_fn, run_ast_noopt_fn, json) come from env // Hidden vars (os, actorsym, init, core_path, shop_path, json, args) come from env
// In actor spawn mode, also: nota, wota // Engine is self-sufficient: defines its own compilation pipeline
var ACTORDATA = actorsym var ACTORDATA = actorsym
var SYSYM = '__SYSTEM__' var SYSYM = '__SYSTEM__'
@@ -19,9 +19,12 @@ var ACTOR_EXT = '.ce'
var load_internal = os.load_internal var load_internal = os.load_internal
function use_embed(name) { function use_embed(name) {
return load_internal("js_" + name + "_use") return load_internal("js_core_" + name + "_use")
} }
// These duplicate C builtins from runtime.c, but are needed because the GC can
// lose properties on the global object after compaction. The runtime_env provides
// a stable way for modules to access them via env_record linking.
function logical(val1) { function logical(val1) {
if (val1 == 0 || val1 == false || val1 == "false" || val1 == null) if (val1 == 0 || val1 == false || val1 == "false" || val1 == null)
return false; return false;
@@ -46,14 +49,161 @@ function ends_with(str, suffix) {
return search(str, suffix, -length(suffix)) != null return search(str, suffix, -length(suffix)) != null
} }
var fd = use_embed('fd') var fd = use_embed('internal_fd')
var js = use_embed('js') var js = use_embed('js')
var crypto = use_embed('crypto')
// core_path and shop_path come from env (bootstrap.cm passes them through) // core_path and shop_path come from env (C runtime passes them through)
// shop_path may be null if --core was used without --shop // shop_path may be null if --core was used without --shop
var packages_path = shop_path ? shop_path + '/packages' : null var packages_path = shop_path ? shop_path + '/packages' : null
// Self-sufficient initialization: content-addressed cache
var use_cache = {} var use_cache = {}
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
}
// Load a pipeline module from cache, with boot/ seed fallback
function load_pipeline_module(name, env) {
var source_path = core_path + '/' + name + '.cm'
var source_blob = null
var hash = null
var cached = null
var mcode_path = null
var mcode_blob = null
var mach_blob = null
if (fd.is_file(source_path)) {
source_blob = fd.slurp(source_path)
hash = content_hash(source_blob)
cached = cache_path(hash)
if (cached && fd.is_file(cached))
return mach_load(fd.slurp(cached), env)
}
// Boot seed fallback
mcode_path = core_path + '/boot/' + name + '.cm.mcode'
if (fd.is_file(mcode_path)) {
mcode_blob = fd.slurp(mcode_path)
mach_blob = mach_compile_mcode_bin(name, text(mcode_blob))
return mach_load(mach_blob, env)
}
print("error: cannot load pipeline module: " + name + "\n")
disrupt
}
// Load compilation pipeline
var pipeline_env = stone({use: use_embed})
var tokenize_mod = load_pipeline_module('tokenize', pipeline_env)
var parse_mod = load_pipeline_module('parse', pipeline_env)
var fold_mod = load_pipeline_module('fold', pipeline_env)
var mcode_mod = load_pipeline_module('mcode', pipeline_env)
var streamline_mod = load_pipeline_module('streamline', pipeline_env)
use_cache['tokenize'] = tokenize_mod
use_cache['parse'] = parse_mod
use_cache['fold'] = fold_mod
use_cache['mcode'] = mcode_mod
use_cache['core/mcode'] = mcode_mod
use_cache['streamline'] = streamline_mod
use_cache['core/streamline'] = streamline_mod
// analyze: tokenize + parse + fold, check for errors
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 prev_line = -1
var prev_msg = null
var e = null
var msg = null
var line = null
var col = null
var has_errors = _ast.errors != null && length(_ast.errors) > 0
if (has_errors) {
while (_i < length(_ast.errors)) {
e = _ast.errors[_i]
msg = e.message
line = e.line
col = e.column
if (msg != prev_msg || line != prev_line) {
if (line != null && col != null)
print(`${filename}:${text(line)}:${text(col)}: error: ${msg}`)
else
print(`${filename}: error: ${msg}`)
}
prev_line = line
prev_msg = msg
_i = _i + 1
}
disrupt
}
return fold_mod(_ast)
}
// Lazy-loaded verify_ir module (loaded on first use)
var _verify_ir_mod = null
// Run AST through mcode pipeline -> register VM
function run_ast_fn(name, ast, env) {
var compiled = mcode_mod(ast)
if (os._verify_ir) {
if (_verify_ir_mod == null) {
_verify_ir_mod = load_pipeline_module('verify_ir', pipeline_env)
}
compiled._verify = true
compiled._verify_mod = _verify_ir_mod
}
var optimized = streamline_mod(compiled)
if (optimized._verify) {
delete optimized._verify
delete optimized._verify_mod
}
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_fn(name, ast, env) {
var compiled = mcode_mod(ast)
var mcode_json = json.encode(compiled)
var mach_blob = mach_compile_mcode_bin(name, mcode_json)
return mach_load(mach_blob, env)
}
// 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))
}
// If loaded directly by C runtime (not via bootstrap), convert args -> init
var _program = null
var _user_args = []
var _j = 1
var _init = init
if (args != null && _init == null) {
_program = args[0]
while (_j < length(args)) {
push(_user_args, args[_j])
_j = _j + 1
}
_init = {program: _program, arg: _user_args}
}
use_cache['core/os'] = os use_cache['core/os'] = os
// Extra env properties added as engine initializes (log, runtime fns, etc.) // Extra env properties added as engine initializes (log, runtime fns, etc.)
@@ -70,34 +220,43 @@ function use_core(path) {
var result = null var result = null
var script = null var script = null
var ast = null var ast = null
var _load_mod = null
// Build env: merge core_extras, include C embed as 'native' if available // Build env: merge core_extras
env = {use: use_core} env = {use: use_core}
arrfor(array(core_extras), function(k) { env[k] = core_extras[k] }) arrfor(array(core_extras), function(k) { env[k] = core_extras[k] })
if (sym) env.native = sym env = stone(env)
// Check for pre-compiled .cm.mach file first var hash = null
var mach_path = core_path + '/' + path + '.cm.mach' var cached_path = null
if (fd.is_file(mach_path)) { var mach_blob = null
result = mach_load(fd.slurp(mach_path), env) var source_blob = null
use_cache[cache_key] = result var file_path = null
return result
}
// Check for .cm.mcode JSON IR // Compile from source .cm file
var mcode_path = core_path + '/' + path + '.cm.mcode' file_path = core_path + '/' + path + MOD_EXT
if (fd.is_file(mcode_path)) {
result = mach_eval_mcode('core:' + path, text(fd.slurp(mcode_path)), env)
use_cache[cache_key] = result
return result
}
// Fall back to source .cm file — compile at runtime
var file_path = core_path + '/' + path + MOD_EXT
if (fd.is_file(file_path)) { if (fd.is_file(file_path)) {
script = text(fd.slurp(file_path)) _load_mod = function() {
ast = analyze(script, file_path) source_blob = fd.slurp(file_path)
result = run_ast_fn('core:' + path, ast, env) 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('core:' + path, ast)
if (cached_path) {
ensure_build_dir()
fd.slurpwrite(cached_path, mach_blob)
}
result = mach_load(mach_blob, env)
}
} disruption {
print("use('" + path + "'): failed to compile or load " + file_path + "\n")
disrupt
}
_load_mod()
use_cache[cache_key] = result use_cache[cache_key] = result
return result return result
} }
@@ -196,28 +355,44 @@ function actor_die(err)
//actor_mod.on_exception(actor_die) //actor_mod.on_exception(actor_die)
_cell.args = init != null ? init : {} _cell.args = _init != null ? _init : {}
_cell.id = "newguy"
function create_actor(desc) { function create_actor(desc) {
var _desc = desc == null ? {id:guid()} : desc var _desc = desc == null ? {id:guid()} : desc
var actor = {} var actor = {}
actor[ACTORDATA] = _desc actor[ACTORDATA] = _desc
stone(actor)
return actor return actor
} }
var $_ = {} var $_ = {}
$_.self = create_actor()
os.use_cache = use_cache use_cache['core/json'] = json
os.global_shop_path = shop_path
os.$_ = $_ // 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.core_path = core_path
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
os.analyze = analyze os.analyze = analyze
os.run_ast_fn = run_ast_fn os.run_ast_fn = run_ast_fn
os.run_ast_noopt_fn = run_ast_noopt_fn os.run_ast_noopt_fn = run_ast_noopt_fn
os.json = json core_extras.core_json = json
use_cache['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
core_extras.compile_to_blob = compile_to_blob
// NOW load shop -- it receives all of the above via env
var shop = use_core('internal/shop') var shop = use_core('internal/shop')
var time = use_core('time') var time = use_core('time')
@@ -227,29 +402,26 @@ var parallel = pronto.parallel
var race = pronto.race var race = pronto.race
var sequence = pronto.sequence var sequence = pronto.sequence
// Create runtime environment for modules // Fill runtime_env — includes duplicates of C builtins because the GC can
var runtime_env = { // lose global object properties after compaction. Modules resolve these via
logical: logical, // env_record, not the global.
some: some, runtime_env.logical = logical
every: every, runtime_env.some = some
starts_with: starts_with, runtime_env.every = every
ends_with: ends_with, runtime_env.starts_with = starts_with
actor: actor, runtime_env.ends_with = ends_with
is_actor: is_actor, runtime_env.actor = actor
log: log, runtime_env.is_actor = is_actor
send: send, runtime_env.log = log
fallback: fallback, runtime_env.send = send
parallel: parallel, runtime_env.fallback = fallback
race: race, runtime_env.parallel = parallel
sequence: sequence runtime_env.race = race
} runtime_env.sequence = sequence
// Make runtime functions available to modules loaded via use_core // Make runtime functions available to modules loaded via use_core
arrfor(array(runtime_env), function(k) { core_extras[k] = runtime_env[k] }) 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) $_.time_limit = function(requestor, seconds)
{ {
if (!pronto.is_requestor(requestor)) { if (!pronto.is_requestor(requestor)) {
@@ -689,7 +861,7 @@ stone(send)
if (!_cell.args.id) _cell.id = guid() if (!_cell.args.id) _cell.id = guid()
else _cell.id = _cell.args.id else _cell.id = _cell.args.id
$_.self[ACTORDATA].id = _cell.id $_.self = create_actor({id: _cell.id})
// Actor's timeslice for processing a single message // Actor's timeslice for processing a single message
function turn(msg) function turn(msg)
@@ -794,12 +966,8 @@ function handle_message(msg) {
if (msg.type == "user") { if (msg.type == "user") {
letter = msg.data // what the sender really sent letter = msg.data // what the sender really sent
_ObjectDefineProperty(letter, HEADER, { letter[HEADER] = msg
value: msg, enumerable: false letter[ACTORDATA] = { reply: msg.reply }
})
_ObjectDefineProperty(letter, ACTORDATA, { // this is so is_actor == true
value: { reply: msg.reply }, enumerable: false
})
if (msg.return) { if (msg.return) {
fn = replies[msg.return] fn = replies[msg.return]
@@ -883,10 +1051,27 @@ $_.clock(_ => {
} }
env.args = _cell.args.arg env.args = _cell.args.arg
env.log = log env.log = log
env = stone(env)
var script = text(fd.slurp(prog_path)) var source_blob = fd.slurp(prog_path)
var ast = analyze(script, prog_path) var hash = content_hash(source_blob)
var val = run_ast_fn(prog, ast, env) 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(prog, ast)
if (cached_path) {
ensure_build_dir()
fd.slurpwrite(cached_path, mach_blob)
}
val = mach_load(mach_blob, env)
}
if (val) { if (val) {
log.error('Program must not return anything') log.error('Program must not return anything')
disrupt disrupt

File diff suppressed because it is too large Load Diff

View File

@@ -417,36 +417,34 @@ JSC_CCALL(fd_fstat,
if (fstat(fd, &st) != 0) if (fstat(fd, &st) != 0)
return JS_ThrowInternalError(js, "fstat failed: %s", strerror(errno)); return JS_ThrowInternalError(js, "fstat failed: %s", strerror(errno));
JSValue obj = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size)); JS_ROOT(obj, JS_NewObject(js));
JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode)); JS_SetPropertyStr(js, obj.val, "size", JS_NewInt64(js, st.st_size));
JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid)); JS_SetPropertyStr(js, obj.val, "mode", JS_NewInt32(js, st.st_mode));
JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid)); JS_SetPropertyStr(js, obj.val, "uid", JS_NewInt32(js, st.st_uid));
JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime)); JS_SetPropertyStr(js, obj.val, "gid", JS_NewInt32(js, st.st_gid));
JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime)); JS_SetPropertyStr(js, obj.val, "atime", JS_NewInt64(js, st.st_atime));
JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime)); JS_SetPropertyStr(js, obj.val, "mtime", JS_NewInt64(js, st.st_mtime));
JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink)); JS_SetPropertyStr(js, obj.val, "ctime", JS_NewInt64(js, st.st_ctime));
JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino)); JS_SetPropertyStr(js, obj.val, "nlink", JS_NewInt32(js, st.st_nlink));
JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev)); JS_SetPropertyStr(js, obj.val, "ino", JS_NewInt64(js, st.st_ino));
JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev)); 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 #ifndef _WIN32
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize)); JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, st.st_blksize));
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks)); JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_blocks));
#else #else
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096)); JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, 4096));
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512)); JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_size / 512));
#endif #endif
JS_SetPropertyStr(js, obj.val, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
// Add boolean properties for file type JS_SetPropertyStr(js, obj.val, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode))); JS_RETURN(obj.val);
JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
return obj;
) )
JSC_CCALL(fd_stat, JSC_CCALL(fd_stat,
@@ -459,70 +457,72 @@ JSC_CCALL(fd_stat,
return JS_NewObject(js); return JS_NewObject(js);
} }
JSValue obj = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size)); JS_ROOT(obj, JS_NewObject(js));
JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode)); JS_SetPropertyStr(js, obj.val, "size", JS_NewInt64(js, st.st_size));
JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid)); JS_SetPropertyStr(js, obj.val, "mode", JS_NewInt32(js, st.st_mode));
JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid)); JS_SetPropertyStr(js, obj.val, "uid", JS_NewInt32(js, st.st_uid));
JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime)); JS_SetPropertyStr(js, obj.val, "gid", JS_NewInt32(js, st.st_gid));
JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime)); JS_SetPropertyStr(js, obj.val, "atime", JS_NewInt64(js, st.st_atime));
JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime)); JS_SetPropertyStr(js, obj.val, "mtime", JS_NewInt64(js, st.st_mtime));
JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink)); JS_SetPropertyStr(js, obj.val, "ctime", JS_NewInt64(js, st.st_ctime));
JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino)); JS_SetPropertyStr(js, obj.val, "nlink", JS_NewInt32(js, st.st_nlink));
JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev)); JS_SetPropertyStr(js, obj.val, "ino", JS_NewInt64(js, st.st_ino));
JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev)); 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 #ifndef _WIN32
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize)); JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, st.st_blksize));
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks)); JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_blocks));
#else #else
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096)); JS_SetPropertyStr(js, obj.val, "blksize", JS_NewInt32(js, 4096));
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512)); JS_SetPropertyStr(js, obj.val, "blocks", JS_NewInt64(js, st.st_size / 512));
#endif #endif
JS_SetPropertyStr(js, obj.val, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
// Add boolean properties for file type JS_SetPropertyStr(js, obj.val, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode))); JS_SetPropertyStr(js, obj.val, "isBlockDevice", JS_NewBool(js, S_ISBLK(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_FreeCString(js, path); JS_FreeCString(js, path);
return obj; JS_RETURN(obj.val);
) )
JSC_SCALL(fd_readdir, JSC_SCALL(fd_readdir,
JS_FRAME(js);
#ifdef _WIN32 #ifdef _WIN32
WIN32_FIND_DATA ffd; WIN32_FIND_DATA ffd;
char path[PATH_MAX]; char path[PATH_MAX];
snprintf(path, sizeof(path), "%s\\*", str); snprintf(path, sizeof(path), "%s\\*", str);
HANDLE hFind = FindFirstFile(path, &ffd); HANDLE hFind = FindFirstFile(path, &ffd);
if (hFind == INVALID_HANDLE_VALUE) { if (hFind == INVALID_HANDLE_VALUE) {
ret = JS_ThrowInternalError(js, "FindFirstFile failed for %s", path); ret = JS_ThrowInternalError(js, "FindFirstFile failed for %s", path);
} else { } else {
ret = JS_NewArray(js); JS_ROOT(arr, JS_NewArray(js));
do { do {
if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue; if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue;
JS_ArrayPush(js, &ret, JS_NewString(js, ffd.cFileName)); JS_ArrayPush(js, &arr.val, JS_NewString(js, ffd.cFileName));
} while (FindNextFile(hFind, &ffd) != 0); } while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind); FindClose(hFind);
ret = arr.val;
} }
#else #else
DIR *d; DIR *d;
struct dirent *dir; struct dirent *dir;
d = opendir(str); d = opendir(str);
if (d) { if (d) {
ret = JS_NewArray(js); JS_ROOT(arr, JS_NewArray(js));
while ((dir = readdir(d)) != NULL) { while ((dir = readdir(d)) != NULL) {
if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue; 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); closedir(d);
ret = arr.val;
} else { } else {
ret = JS_ThrowInternalError(js, "opendir failed for %s: %s", str, strerror(errno)); ret = JS_ThrowInternalError(js, "opendir failed for %s: %s", str, strerror(errno));
} }
#endif #endif
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame);
) )
JSC_CCALL(fd_is_file, JSC_CCALL(fd_is_file,
@@ -585,7 +585,7 @@ JSC_CCALL(fd_slurpwrite,
) )
// Helper function for recursive enumeration // 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; if (!curr_path) return;
#ifdef _WIN32 #ifdef _WIN32
@@ -602,7 +602,7 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c
} else { } else {
strcpy(item_rel, ffd.cFileName); 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) { if (recurse) {
struct stat st; struct stat st;
@@ -627,7 +627,7 @@ static void visit_directory(JSContext *js, JSValue results, int *result_count, c
} else { } else {
strcpy(item_rel, dir->d_name); strcpy(item_rel, dir->d_name);
} }
JS_SetPropertyNumber(js, results, (*result_count)++, JS_NewString(js, item_rel)); JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel));
if (recurse) { if (recurse) {
struct stat st; struct stat st;
@@ -651,14 +651,16 @@ JSC_SCALL(fd_enumerate,
if (argc > 1) if (argc > 1)
recurse = JS_ToBool(js, argv[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; int result_count = 0;
struct stat st; struct stat st;
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) 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, JSC_CCALL(fd_realpath,
@@ -752,8 +754,9 @@ static const JSCFunctionListEntry js_fd_funcs[] = {
MIST_FUNC_DEF(fd, readlink, 1), MIST_FUNC_DEF(fd, readlink, 1),
}; };
JSValue js_fd_use(JSContext *js) { JSValue js_core_internal_fd_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js, mod, js_fd_funcs, countof(js_fd_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_fd_funcs, countof(js_fd_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -73,9 +73,10 @@ static const JSCFunctionListEntry js_kim_funcs[] = {
MIST_FUNC_DEF(kim, decode, 1), 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_FRAME(js);
JS_SetPropertyFunctionList(js, mod, js_kim_funcs, countof(js_kim_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_kim_funcs, countof(js_kim_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -277,29 +277,31 @@ JSC_CCALL(os_mallinfo,
) )
static JSValue js_os_rusage(JSContext *js, JSValue self, int argc, JSValue *argv) { static JSValue js_os_rusage(JSContext *js, JSValue self, int argc, JSValue *argv) {
JSValue ret = JS_NULL; JS_FRAME(js);
ret = JS_NewObject(js); JS_ROOT(ret, JS_NewObject(js));
#if defined(__linux__) || defined(__APPLE__) #if defined(__linux__) || defined(__APPLE__)
struct rusage jsmem; struct rusage jsmem;
getrusage(RUSAGE_SELF, &jsmem); getrusage(RUSAGE_SELF, &jsmem);
JSJMEMRET(ru_maxrss); #define JSJMEMRET_GC(FIELD) JS_SetPropertyStr(js, ret.val, #FIELD, number2js(js, jsmem.FIELD));
JSJMEMRET(ru_ixrss); JSJMEMRET_GC(ru_maxrss);
JSJMEMRET(ru_idrss); JSJMEMRET_GC(ru_ixrss);
JSJMEMRET(ru_isrss); JSJMEMRET_GC(ru_idrss);
JSJMEMRET(ru_minflt); JSJMEMRET_GC(ru_isrss);
JSJMEMRET(ru_majflt); JSJMEMRET_GC(ru_minflt);
JSJMEMRET(ru_nswap); JSJMEMRET_GC(ru_majflt);
JSJMEMRET(ru_inblock); JSJMEMRET_GC(ru_nswap);
JSJMEMRET(ru_oublock); JSJMEMRET_GC(ru_inblock);
JSJMEMRET(ru_msgsnd); JSJMEMRET_GC(ru_oublock);
JSJMEMRET(ru_msgrcv); JSJMEMRET_GC(ru_msgsnd);
JSJMEMRET(ru_nsignals); JSJMEMRET_GC(ru_msgrcv);
JSJMEMRET(ru_nvcsw); JSJMEMRET_GC(ru_nsignals);
JSJMEMRET(ru_nivcsw); JSJMEMRET_GC(ru_nvcsw);
JSJMEMRET_GC(ru_nivcsw);
#undef JSJMEMRET_GC
#endif #endif
return ret; JS_RETURN(ret.val);
} }
JSC_SCALL(os_system, JSC_SCALL(os_system,
@@ -425,6 +427,59 @@ static JSValue js_os_dylib_has_symbol(JSContext *js, JSValue self, int argc, JSV
return JS_NewBool(js, symbol != NULL); 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, JSValue env);
extern JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const char *sym_name, JSValue env);
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");
JSValue env = (argc >= 2) ? argv[1] : JS_NULL;
return cell_rt_native_module_load(js, handle, env);
}
static JSValue js_os_native_module_load_named(JSContext *js, JSValue self, int argc, JSValue *argv)
{
if (argc < 2)
return JS_ThrowTypeError(js, "native_module_load_named requires (dylib, sym_name)");
void *handle = JS_GetOpaque(argv[0], js_dylib_class_id);
if (!handle)
return JS_ThrowTypeError(js, "First argument must be a dylib object");
const char *sym_name = JS_ToCString(js, argv[1]);
if (!sym_name)
return JS_EXCEPTION;
JSValue env = (argc >= 3) ? argv[2] : JS_NULL;
JSValue result = cell_rt_native_module_load_named(js, handle, sym_name, env);
JS_FreeCString(js, sym_name);
return result;
}
JSC_CCALL(os_print, JSC_CCALL(os_print,
size_t len; size_t len;
const char *str = JS_ToCStringLen(js, &len, argv[0]); const char *str = JS_ToCStringLen(js, &len, argv[0]);
@@ -552,6 +607,37 @@ JSC_CCALL(os_getenv,
ret = JS_NULL; 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[] = { static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, platform, 0), MIST_FUNC_DEF(os, platform, 0),
MIST_FUNC_DEF(os, arch, 0), MIST_FUNC_DEF(os, arch, 0),
@@ -566,8 +652,12 @@ static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, exit, 0), MIST_FUNC_DEF(os, exit, 0),
MIST_FUNC_DEF(os, sleep, 1), MIST_FUNC_DEF(os, sleep, 1),
MIST_FUNC_DEF(os, dylib_open, 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_symbol, 2),
MIST_FUNC_DEF(os, dylib_has_symbol, 2), MIST_FUNC_DEF(os, dylib_has_symbol, 2),
MIST_FUNC_DEF(os, native_module_load, 2),
MIST_FUNC_DEF(os, native_module_load_named, 3),
MIST_FUNC_DEF(os, embedded_module, 1),
MIST_FUNC_DEF(os, load_internal, 1), MIST_FUNC_DEF(os, load_internal, 1),
MIST_FUNC_DEF(os, internal_exists, 1), MIST_FUNC_DEF(os, internal_exists, 1),
MIST_FUNC_DEF(os, print, 1), MIST_FUNC_DEF(os, print, 1),
@@ -575,11 +665,12 @@ static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, getenv, 1), 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_NewClassID(&js_dylib_class_id);
JS_NewClass(js, js_dylib_class_id, &js_dylib_class); JS_NewClass(js, js_dylib_class_id, &js_dylib_class);
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js,mod,js_os_funcs,countof(js_os_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_os_funcs, countof(js_os_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -12,12 +12,63 @@ var pkg_tools = use('package')
var os = use('os') var os = use('os')
var link = use('link') var link = use('link')
var analyze = os.analyze // These come from env (via core_extras in engine.cm):
var run_ast_fn = os.run_ast_fn // analyze, run_ast_fn, core_json, use_cache, shop_path, actor_api, runtime_env,
var shop_json = os.json // 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" 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) function pull_from_cache(content)
{ {
var path = hash_path(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) function hash_path(content)
{ {
return global_shop_path + '/build' + '/' + content_hash(content) return global_shop_path + '/build' + '/' + content_hash(content)
@@ -66,9 +112,6 @@ var ACTOR_EXT = '.ce'
var dylib_ext = '.dylib' // Default extension 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) { Shop.get_package_dir = function(name) {
return global_shop_path + '/packages/' + name return global_shop_path + '/packages/' + name
@@ -270,11 +313,6 @@ function package_cache_path(pkg)
return global_shop_path + '/cache/' + replace(replace(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) // Load lock.toml configuration (from global shop)
var _lock = null var _lock = null
Shop.load_lock = function() { Shop.load_lock = function() {
@@ -301,6 +339,48 @@ Shop.save_lock = function(lock) {
} }
// Shop configuration (shop.toml) with policy flags
var _shop_config = null
var _default_policy = {
allow_dylib: true,
allow_static: true,
allow_mach: true,
allow_compile: true
}
Shop.load_config = function() {
if (_shop_config) return _shop_config
if (!global_shop_path) {
_shop_config = {policy: object(_default_policy)}
return _shop_config
}
var path = global_shop_path + '/shop.toml'
if (!fd.is_file(path)) {
_shop_config = {policy: object(_default_policy)}
fd.slurpwrite(path, stone(blob(toml.encode(_shop_config))))
return _shop_config
}
var content = text(fd.slurp(path))
if (!length(content)) {
_shop_config = {policy: object(_default_policy)}
return _shop_config
}
_shop_config = toml.decode(content)
if (!_shop_config.policy) _shop_config.policy = {}
var keys = array(_default_policy)
var i = 0
for (i = 0; i < length(keys); i++) {
if (_shop_config.policy[keys[i]] == null)
_shop_config.policy[keys[i]] = _default_policy[keys[i]]
}
return _shop_config
}
function get_policy() {
var config = Shop.load_config()
return config.policy
}
// Get information about how to resolve a package // Get information about how to resolve a package
// Local packages always start with / // Local packages always start with /
Shop.resolve_package_info = function(pkg) { Shop.resolve_package_info = function(pkg) {
@@ -377,9 +457,34 @@ Shop.extract_commit_hash = function(pkg, response) {
return null return null
} }
var dylib_visited = {}
var open_dls = {} 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
var sym = Shop.c_symbol_for_file(pkg, stem)
return os.native_module_load_named(handle, sym)
}
// Default capabilities injected into scripts // Default capabilities injected into scripts
// These map to $_ properties in engine.cm // 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'] var SHOP_DEFAULT_INJECT = ['$self', '$overling', '$clock', '$delay', '$start', '$receiver', '$contact', '$portal', '$time_limit', '$couple', '$stop', '$unneeded', '$connection', '$fd']
@@ -404,9 +509,8 @@ Shop.get_script_capabilities = function(path) {
// Matches engine.cm's approach: env properties become free variables in the module. // Matches engine.cm's approach: env properties become free variables in the module.
function inject_env(inject) { function inject_env(inject) {
var env = {} var env = {}
var rt = my$_.os ? my$_.os.runtime_env : null if (runtime_env) {
if (rt) { arrfor(array(runtime_env), function(k) { env[k] = runtime_env[k] })
arrfor(array(rt), function(k) { env[k] = rt[k] })
} }
// Add capability injections with $ prefix // Add capability injections with $ prefix
@@ -433,7 +537,9 @@ function resolve_mod_fn(path, pkg) {
if (!fd.is_file(path)) { print(`path ${path} is not a file`); disrupt } if (!fd.is_file(path)) { print(`path ${path} is not a file`); disrupt }
var content = text(fd.slurp(path)) 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 ast = null
var compiled = null var compiled = null
var mach_path = null var mach_path = null
@@ -441,38 +547,77 @@ function resolve_mod_fn(path, pkg) {
var mcode_path = null var mcode_path = null
var ir = null var ir = null
var optimized = null var optimized = null
var mcode_json = null
var cached_mcode_path = null
var _pkg_dir = null
var _stem = null
var policy = null
// Check cache for pre-compiled .mach blob policy = get_policy()
if (cached) {
return cached // Compute _pkg_dir and _stem early so all paths can use them
if (pkg) {
_pkg_dir = get_packages_dir() + '/' + safe_package_path(pkg)
if (starts_with(path, _pkg_dir + '/')) {
_stem = text(path, length(_pkg_dir) + 1)
}
} }
// Check for pre-compiled .mach or .mcode file alongside .cm source // Check for native .cm dylib at deterministic path first
if (ends_with(path, '.cm')) { if (policy.allow_dylib && pkg && _stem) {
mach_path = text(path, 0, length(path) - 3) + '.mach' native_result = try_native_mod_dylib(pkg, _stem)
if (fd.is_file(mach_path)) { if (native_result != null) {
mach_blob = fd.slurp(mach_path) return {_native: true, value: native_result}
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))) // Check cache for pre-compiled .mach blob
put_into_cache(stone(blob(content)), compiled) if (policy.allow_mach) {
cached = pull_from_cache(content_key)
if (cached) {
return cached
}
}
// Check for cached mcode in content-addressed store (salted hash to distinguish from mach)
if (policy.allow_compile) {
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 return compiled
} }
} }
// Compile via full pipeline: analyze → mcode → streamline → serialize // Compile via full pipeline: analyze → mcode → streamline → serialize
if (!_mcode_mod) _mcode_mod = Shop.use("mcode", null) // Load compiler modules from use_cache directly (NOT via Shop.use, which
if (!_streamline_mod) _streamline_mod = Shop.use("streamline", null) // would re-enter resolve_locator → resolve_mod_fn → infinite recursion)
ast = analyze(content, path) if (policy.allow_compile) {
ir = _mcode_mod(ast) if (!_mcode_mod) _mcode_mod = use_cache['core/mcode'] || use_cache['mcode']
optimized = _streamline_mod(ir) if (!_streamline_mod) _streamline_mod = use_cache['core/streamline'] || use_cache['streamline']
compiled = mach_compile_mcode_bin(path, shop_json.encode(optimized)) if (!_mcode_mod || !_streamline_mod) {
put_into_cache(stone(blob(content)), compiled) print(`error: compiler modules not loaded (mcode=${_mcode_mod != null}, streamline=${_streamline_mod != null})`)
disrupt
}
ast = analyze(content, path)
ir = _mcode_mod(ast)
optimized = _streamline_mod(ir)
mcode_json = shop_json.encode(optimized)
return compiled // 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
}
print(`Module ${path} could not be loaded: no artifact found or all methods blocked by policy`)
disrupt
} }
// given a path and a package context // given a path and a package context
@@ -561,71 +706,55 @@ function resolve_locator(path, ctx)
} }
// Generate symbol name for a C module file // Generate symbol name for a C module file
// Uses the same format as Shop.c_symbol_for_file // Uses the package's assigned ID (5-letter random or "core") instead of the full name
// Symbol names are based on canonical package names, not link targets
function make_c_symbol(pkg, file) { 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, '/', '_'), '.', '_'), '-', '_') 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 // Get the deterministic dylib path for a module in lib/<pkg>/<stem>.dylib
// Library names are based on canonical package names, not link targets function get_dylib_path(pkg, stem) {
function get_lib_path(pkg) { return global_shop_path + '/lib/' + safe_package_path(pkg) + '/' + stem + dylib_ext
var lib_name = replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
return global_shop_path + '/lib/' + lib_name + dylib_ext
} }
// Open a package's dynamic library and all its dependencies // Get the deterministic mach path for a module in lib/<pkg>/<stem>.mach
Shop.open_package_dylib = function(pkg) { function get_mach_path(pkg, stem) {
if (pkg == 'core' || !pkg) return return global_shop_path + '/lib/' + safe_package_path(pkg) + '/' + stem + '.mach'
if (dylib_visited[pkg]) return }
dylib_visited[pkg] = true
var link_target = link.get_target(pkg) // Open a per-module dylib and return the dlopen handle
var resolved_pkg = link_target ? link_target : pkg 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 pkg_dir = null // Try to resolve a C symbol from the deterministic dylib path
if (starts_with(resolved_pkg, '/')) { // Returns a loader function or null
pkg_dir = resolved_pkg function try_dylib_symbol(sym, pkg, file_stem) {
} else { var dylib_path = get_dylib_path(pkg, file_stem)
pkg_dir = get_packages_dir() + '/' + safe_package_path(resolved_pkg) var handle = open_module_dylib(dylib_path)
} if (!handle) return null
if (!os.dylib_has_symbol(handle, sym)) return null
var toml_path = pkg_dir + '/cell.toml' return function() { return os.dylib_symbol(handle, sym) }
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)
}
}
} }
// Resolve a C symbol by searching: // Resolve a C symbol by searching:
// 1. If package_context is null, only check core internal symbols // At each scope: check lib/ dylib first, then internal (static)
// 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
function resolve_c_symbol(path, package_context) { function resolve_c_symbol(path, package_context) {
var explicit = split_explicit_package_import(path) var explicit = split_explicit_package_import(path)
var sym = null var sym = null
var dl_path = null var loader = null
var _path = null var _path = null
var core_sym = null var core_sym = null
var canon_pkg = null var canon_pkg = null
var mod_name = null var mod_name = null
var file_stem = null
var policy = null
policy = get_policy()
if (explicit) { if (explicit) {
if (is_internal_path(explicit.path) && package_context && explicit.package != package_context) if (is_internal_path(explicit.path) && package_context && explicit.package != package_context)
@@ -633,7 +762,23 @@ function resolve_c_symbol(path, package_context) {
} }
if (explicit) { if (explicit) {
sym = make_c_symbol(explicit.package, explicit.path) sym = make_c_symbol(explicit.package, explicit.path)
if (os.internal_exists(sym)) { file_stem = replace(explicit.path, '.c', '')
// Check lib/ dylib first
if (policy.allow_dylib) {
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 (policy.allow_static && os.internal_exists(sym)) {
return { return {
symbol: function() { return os.load_internal(sym) }, symbol: function() { return os.load_internal(sym) },
scope: SCOPE_PACKAGE, scope: SCOPE_PACKAGE,
@@ -641,24 +786,25 @@ function resolve_c_symbol(path, package_context) {
path: sym 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') { if (!package_context || package_context == 'core') {
_path = replace(path, '/', '_') core_sym = make_c_symbol('core', path)
core_sym = `js_${_path}_use`
if (os.internal_exists(core_sym)) { // Check lib/ dylib first for core
if (policy.allow_dylib) {
loader = try_dylib_symbol(core_sym, 'core', path)
if (loader) {
return {
symbol: loader,
scope: SCOPE_CORE,
path: core_sym
}
}
}
if (policy.allow_static && os.internal_exists(core_sym)) {
return { return {
symbol: function() { return os.load_internal(core_sym) }, symbol: function() { return os.load_internal(core_sym) },
scope: SCOPE_CORE, scope: SCOPE_CORE,
@@ -668,22 +814,23 @@ function resolve_c_symbol(path, package_context) {
return null 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) sym = make_c_symbol(package_context, path)
if (os.internal_exists(sym)) {
return { if (policy.allow_dylib) {
symbol: function() { return os.load_internal(sym) }, loader = try_dylib_symbol(sym, package_context, path)
scope: SCOPE_LOCAL, if (loader) {
path: sym return {
symbol: loader,
scope: SCOPE_LOCAL,
path: sym
}
} }
} }
Shop.open_package_dylib(package_context) if (policy.allow_static && os.internal_exists(sym)) {
dl_path = get_lib_path(package_context)
if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) {
return { return {
symbol: function() { return os.dylib_symbol(open_dls[dl_path], sym) }, symbol: function() { return os.load_internal(sym) },
scope: SCOPE_LOCAL, scope: SCOPE_LOCAL,
path: sym path: sym
} }
@@ -700,22 +847,21 @@ function resolve_c_symbol(path, package_context) {
mod_name = get_import_name(path) mod_name = get_import_name(path)
sym = make_c_symbol(canon_pkg, mod_name) sym = make_c_symbol(canon_pkg, mod_name)
// Check internal first if (policy.allow_dylib) {
if (os.internal_exists(sym)) { loader = try_dylib_symbol(sym, canon_pkg, mod_name)
return { if (loader) {
symbol: function() { return os.load_internal(sym) }, return {
scope: SCOPE_PACKAGE, symbol: loader,
package: canon_pkg, scope: SCOPE_PACKAGE,
path: sym package: canon_pkg,
path: sym
}
} }
} }
// Then check dylib if (policy.allow_static && os.internal_exists(sym)) {
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 { return {
symbol: function() { return os.dylib_symbol(open_dls[dl_path], sym) }, symbol: function() { return os.load_internal(sym) },
scope: SCOPE_PACKAGE, scope: SCOPE_PACKAGE,
package: canon_pkg, package: canon_pkg,
path: sym path: sym
@@ -724,9 +870,21 @@ function resolve_c_symbol(path, package_context) {
} }
} }
// 3. Check core internal symbols (core is never a dynamic library) // 3. Check core (dylib first, then internal)
core_sym = `js_${replace(path, '/', '_')}_use` core_sym = make_c_symbol('core', path)
if (os.internal_exists(core_sym)) {
if (policy.allow_dylib) {
loader = try_dylib_symbol(core_sym, 'core', path)
if (loader) {
return {
symbol: loader,
scope: SCOPE_CORE,
path: core_sym
}
}
}
if (policy.allow_static && os.internal_exists(core_sym)) {
return { return {
symbol: function() { return os.load_internal(core_sym) }, symbol: function() { return os.load_internal(core_sym) },
scope: SCOPE_CORE, scope: SCOPE_CORE,
@@ -841,20 +999,21 @@ function execute_module(info)
var pkg = null var pkg = null
if (mod_resolve.scope < 900) { if (mod_resolve.scope < 900) {
// Build env with runtime fns, capabilities, and use function // Check if native dylib was resolved
file_info = Shop.file_info(mod_resolve.path) if (is_object(mod_resolve.symbol) && mod_resolve.symbol._native) {
inject = Shop.script_inject_for(file_info) used = mod_resolve.symbol.value
env = inject_env(inject) } else {
pkg = file_info.package // Build env with runtime fns, capabilities, and use function
env.use = make_use_fn(pkg) 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)
env = stone(env)
// Add C module as native context if available // Load compiled bytecode with env
if (c_resolve.scope < 900) { used = mach_load(mod_resolve.symbol, env)
env.native = call_c_module(c_resolve)
} }
// Load compiled bytecode with env
used = mach_load(mod_resolve.symbol, env)
} else if (c_resolve.scope < 900) { } else if (c_resolve.scope < 900) {
// C only // C only
used = call_c_module(c_resolve) used = call_c_module(c_resolve)
@@ -876,6 +1035,22 @@ function get_module(path, package_context) {
} }
Shop.use = function use(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)
embed_env = stone(embed_env)
use_cache[embed_key] = mach_load(embedded, embed_env)
return use_cache[embed_key]
}
}
var info = resolve_module_info(path, package_context) var info = resolve_module_info(path, package_context)
if (!info) { print(`Module ${path} could not be found in ${package_context}`); disrupt } if (!info) { print(`Module ${path} could not be found in ${package_context}`); disrupt }
@@ -1234,21 +1409,33 @@ Shop.module_reload = function(path, package) {
var lookup_key = package ? package + ':' + path : ':' + path var lookup_key = package ? package + ':' + path : ':' + path
module_info_cache[lookup_key] = null 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) var info = resolve_module_info(path, package)
if (!info) return if (!info) return
var cache_key = info.cache_key var cache_key = info.cache_key
var old = use_cache[cache_key] var old = use_cache[cache_key]
use_cache[cache_key] = null
var newmod = get_module(path, package) var newmod = get_module(path, package)
use_cache[cache_key] = newmod
arrfor(array(newmod), function(i, idx) { if (old && is_object(old) && is_object(newmod)) {
old[i] = newmod[i] arrfor(array(newmod), function(k) { old[k] = newmod[k] })
}) arrfor(array(old), function(k) {
if (!(k in newmod)) old[k] = null
arrfor(array(old), function(i, idx) { })
if (!(i in newmod)) use_cache[cache_key] = old
old[i] = null }
})
} }
function get_package_scripts(package) function get_package_scripts(package)
@@ -1290,6 +1477,8 @@ Shop.get_lib_dir = function() {
return global_shop_path + '/lib' return global_shop_path + '/lib'
} }
Shop.ensure_dir = ensure_dir
Shop.get_local_dir = function() { Shop.get_local_dir = function() {
return global_shop_path + "/local" return global_shop_path + "/local"
} }
@@ -1305,28 +1494,153 @@ Shop.get_package_dir = function(pkg) {
} }
// Generate C symbol name for a file within a package // Generate C symbol name for a file within a package
// 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') // e.g., c_symbol_for_file('gitea.pockle.world/john/prosperon', 'sprite.c')
// -> 'js_gitea_pockle_world_john_prosperon_sprite_use' // -> 'js_WAWOF_sprite_use'
Shop.c_symbol_for_file = function(pkg, file) { 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), '/', '_'), '.', '_') 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 // 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) { Shop.c_symbol_prefix = function(pkg) {
var pkg_safe = replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_') var pkg_id = get_package_id(pkg)
return 'js_' + pkg_safe + '_' return 'js_' + pkg_id + '_'
} }
// Get the library name for a package (without extension) // Get the library name for a package
// e.g., 'gitea.pockle.world/john/prosperon' -> 'gitea_pockle_world_john_prosperon' // e.g., 'gitea.pockle.world/john/prosperon' -> 'WAWOF'
Shop.lib_name_for_package = function(pkg) { 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}] } // 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)
}
// Get the deterministic mach path for a module (public API)
Shop.get_mach_path = function(pkg, stem) {
return get_mach_path(pkg, stem)
}
// Load a module explicitly as mach bytecode, bypassing dylib resolution.
// Returns the loaded module value. Disrupts if the module cannot be found.
Shop.load_as_mach = function(path, pkg) {
var locator = resolve_locator(path + '.cm', pkg)
var file_path = null
var content = null
var content_key = null
var cached = null
var cached_mcode_path = null
var mcode_json = null
var compiled = null
var ast = null
var ir = null
var optimized = null
var pkg_dir = null
var stem = null
var mach_path = null
var file_info = null
var inject = null
var env = null
if (!locator) { print('Module ' + path + ' not found'); disrupt }
file_path = locator.path
content = text(fd.slurp(file_path))
content_key = stone(blob(content))
// Try installed .mach in lib/
if (pkg) {
pkg_dir = get_packages_dir() + '/' + safe_package_path(pkg)
if (starts_with(file_path, pkg_dir + '/')) {
stem = text(file_path, length(pkg_dir) + 1)
mach_path = get_mach_path(pkg, stem)
if (fd.is_file(mach_path)) {
compiled = fd.slurp(mach_path)
}
}
}
// Try cached mach blob
if (!compiled) {
cached = pull_from_cache(content_key)
if (cached) compiled = cached
}
// Try cached mcode -> compile to mach
if (!compiled) {
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(file_path, mcode_json)
put_into_cache(content_key, compiled)
}
}
// Full compile from source
if (!compiled) {
if (!_mcode_mod) _mcode_mod = use_cache['core/mcode'] || use_cache['mcode']
if (!_streamline_mod) _streamline_mod = use_cache['core/streamline'] || use_cache['streamline']
if (!_mcode_mod || !_streamline_mod) {
print('error: compiler modules not loaded')
disrupt
}
ast = analyze(content, file_path)
ir = _mcode_mod(ast)
optimized = _streamline_mod(ir)
mcode_json = shop_json.encode(optimized)
cached_mcode_path = global_shop_path + '/build/' + content_hash(stone(blob(text(content_key) + "\nmcode")))
ensure_dir(global_shop_path + '/build')
fd.slurpwrite(cached_mcode_path, stone(blob(mcode_json)))
compiled = mach_compile_mcode_bin(file_path, mcode_json)
put_into_cache(content_key, compiled)
}
// Load the mach blob with proper env
file_info = Shop.file_info(file_path)
inject = Shop.script_inject_for(file_info)
env = inject_env(inject)
env.use = make_use_fn(file_info.package)
env = stone(env)
return mach_load(compiled, env)
}
// Load a module explicitly as a native dylib, bypassing mach resolution.
// Returns the loaded module value, or null if no dylib exists.
Shop.load_as_dylib = function(path, pkg) {
var locator = resolve_locator(path + '.cm', pkg)
var file_path = null
var file_info = null
var pkg_dir = null
var stem = null
var result = null
var real_pkg = pkg
if (!locator) { print('Module ' + path + ' not found'); disrupt }
file_path = locator.path
if (!real_pkg) {
file_info = Shop.file_info(file_path)
real_pkg = file_info.package
}
if (!real_pkg) return null
pkg_dir = get_packages_dir() + '/' + safe_package_path(real_pkg)
if (!starts_with(file_path, pkg_dir + '/')) return null
stem = text(file_path, length(pkg_dir) + 1)
result = try_native_mod_dylib(real_pkg, stem)
return result
}
Shop.audit_packages = function() { Shop.audit_packages = function() {
var packages = Shop.list_packages() var packages = Shop.list_packages()
@@ -1367,4 +1681,34 @@ Shop.parse_package = function(locator) {
} }
} }
Shop.use_native = function(path, package_context) {
var src_path = path
if (!starts_with(path, '/'))
src_path = fd.realpath(path)
if (!fd.is_file(src_path)) { print('File not found: ' + path); disrupt }
var file_info = Shop.file_info(src_path)
var pkg = file_info.package || package_context
var sym_name = null
if (pkg)
sym_name = Shop.c_symbol_for_file(pkg, fd.basename(src_path))
var build = Shop.use('build', 'core')
var dylib_path = build.compile_native(src_path, null, null, pkg)
var handle = os.dylib_open(dylib_path)
if (!handle) { print('Failed to open native dylib: ' + dylib_path); disrupt }
// Build env with runtime functions and capabilities
var inject = Shop.script_inject_for(file_info)
var env = inject_env(inject)
env.use = make_use_fn(pkg)
env = stone(env)
if (sym_name)
return os.native_module_load_named(handle, sym_name, env)
return os.native_module_load(handle, env)
}
return Shop return Shop

View File

@@ -69,18 +69,13 @@ static const JSCFunctionListEntry js_time_funcs[] = {
}; };
JSValue JSValue
js_internal_time_use(JSContext *ctx) js_core_internal_time_use(JSContext *ctx)
{ {
JSValue obj = JS_NewObject(ctx); JS_FRAME(ctx);
JS_SetPropertyFunctionList(ctx, obj, JS_ROOT(mod, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, mod.val,
js_time_funcs, js_time_funcs,
sizeof(js_time_funcs) / sizeof(js_time_funcs) /
sizeof(js_time_funcs[0])); sizeof(js_time_funcs[0]));
return obj; JS_RETURN(mod.val);
}
JSValue
js_time_use(JSContext *ctx)
{
return js_internal_time_use(ctx);
} }

79
link.ce
View File

@@ -17,6 +17,24 @@ var shop = use('internal/shop')
var fd = use('fd') var fd = use('fd')
var toml = use('toml') 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) { if (length(args) < 1) {
log.console("Usage: link <command> [args] or link [package] <target>") log.console("Usage: link <command> [args] or link [package] <target>")
log.console("Commands:") log.console("Commands:")
@@ -27,14 +45,13 @@ if (length(args) < 1) {
log.console(" <path> Link the package in <path> to that path") log.console(" <path> Link the package in <path> to that path")
log.console(" <package> <target> Link <package> to <target> (path or package)") log.console(" <package> <target> Link <package> to <target> (path or package)")
$stop() $stop()
return
} }
var cmd = args[0] cmd = args[0]
if (cmd == 'list') { if (cmd == 'list') {
var links = link.load() links = link.load()
var count = 0 count = 0
arrfor(array(links), function(k) { arrfor(array(links), function(k) {
log.console(k + " -> " + links[k]) log.console(k + " -> " + links[k])
count++ count++
@@ -43,11 +60,11 @@ if (cmd == 'list') {
} else if (cmd == 'sync') { } else if (cmd == 'sync') {
log.console("Syncing links...") log.console("Syncing links...")
var result = link.sync_all(shop) result = link.sync_all(shop)
log.console("Synced " + result.synced + " link(s)") log.console("Synced " + result.synced + " link(s)")
if (length(result.errors) > 0) { if (length(result.errors) > 0) {
log.console("Errors:") 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]) log.console(" " + result.errors[i])
} }
} }
@@ -56,22 +73,23 @@ if (cmd == 'list') {
if (length(args) < 2) { if (length(args) < 2) {
log.console("Usage: link delete <package>") log.console("Usage: link delete <package>")
$stop() $stop()
return
} }
var pkg = args[1] pkg = args[1]
_restore = null
if (link.remove(pkg)) { if (link.remove(pkg)) {
// Try to restore the original package // Try to restore the original package
log.console("Restoring " + pkg + "...") log.console("Restoring " + pkg + "...")
try { _restore = function() {
shop.fetch(pkg) shop.fetch(pkg)
shop.extract(pkg) shop.extract(pkg)
log.console("Restored " + pkg) log.console("Restored " + pkg)
} catch (e) { } disruption {
log.console("Could not restore: " + e.message) log.console("Could not restore")
log.console("Run 'cell update " + pkg + "' to restore") log.console("Run 'cell update " + pkg + "' to restore")
} }
_restore()
} else { } else {
log.console("No link found for " + pkg) log.console("No link found for " + pkg)
} }
@@ -82,22 +100,21 @@ if (cmd == 'list') {
} else { } else {
// Linking logic // Linking logic
var pkg_name = null pkg_name = null
var target = null target = null
// Check for 'add' compatibility // Check for 'add' compatibility
var start_idx = 0 start_idx = 0
if (cmd == 'add') { if (cmd == 'add') {
start_idx = 1 start_idx = 1
} }
var arg1 = args[start_idx] arg1 = args[start_idx]
var arg2 = (length(args) > start_idx + 1) ? args[start_idx + 1] : null arg2 = (length(args) > start_idx + 1) ? args[start_idx + 1] : null
if (!arg1) { if (!arg1) {
log.console("Error: target or package required") log.console("Error: target or package required")
$stop() $stop()
return
} }
if (arg2) { if (arg2) {
@@ -110,11 +127,11 @@ if (cmd == 'list') {
target = fd.realpath(target) target = fd.realpath(target)
} else if (starts_with(target, './') || starts_with(target, '../')) { } else if (starts_with(target, './') || starts_with(target, '../')) {
// Relative path that doesn't exist yet - try to resolve anyway // Relative path that doesn't exist yet - try to resolve anyway
var cwd = fd.realpath('.') cwd = fd.realpath('.')
if (starts_with(target, './')) { if (starts_with(target, './')) {
target = cwd + text(target, 1) target = cwd + text(target, 1)
} else { } else {
// For ../ paths, var fd.realpath handle it if possible // For ../ paths, let fd.realpath handle it if possible
target = fd.realpath(target) || target target = fd.realpath(target) || target
} }
} }
@@ -132,29 +149,27 @@ if (cmd == 'list') {
} }
// Must be a local path with cell.toml // 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)) { if (!fd.is_file(toml_path)) {
log.console("Error: No cell.toml found at " + target) log.console("Error: No cell.toml found at " + target)
log.console("For linking to another package, use: link <package> <target>") log.console("For linking to another package, use: link <package> <target>")
$stop() $stop()
return
} }
// Read package name from cell.toml // Read package name from cell.toml
try { _read_toml = function() {
var content = toml.decode(text(fd.slurp(toml_path))) content = toml.decode(text(fd.slurp(toml_path)))
if (content.package) { if (content.package) {
pkg_name = content.package pkg_name = content.package
} else { } else {
log.console("Error: cell.toml at " + target + " does not define 'package'") log.console("Error: cell.toml at " + target + " does not define 'package'")
$stop() $stop()
return
} }
} catch (e) { } disruption {
log.console("Error reading cell.toml: " + e) log.console("Error reading cell.toml")
$stop() $stop()
return
} }
_read_toml()
} }
// Validate: if target is a local path, it must have cell.toml // Validate: if target is a local path, it must have cell.toml
@@ -162,19 +177,17 @@ if (cmd == 'list') {
if (!fd.is_file(target + '/cell.toml')) { if (!fd.is_file(target + '/cell.toml')) {
log.console("Error: " + target + " is not a valid package (no cell.toml)") log.console("Error: " + target + " is not a valid package (no cell.toml)")
$stop() $stop()
return
} }
} }
// Add the link (this also creates the symlink) // Add the link (this also creates the symlink)
try { _add_link = function() {
link.add(pkg_name, target, shop) link.add(pkg_name, target, shop)
} catch (e) { } disruption {
log.console("Error: " + e.message) log.console("Error adding link")
log.error(e)
$stop() $stop()
return
} }
_add_link()
} }
$stop() $stop()

View File

@@ -4,17 +4,19 @@
var toml = use('toml') var toml = use('toml')
var fd = use('fd') var fd = use('fd')
var blob = use('blob') var blob = use('blob')
var os = use('os') var runtime = use('runtime')
var global_shop_path = os.global_shop_path var global_shop_path = runtime.shop_path
// Get the links file path (in the global shop) // Get the links file path (in the global shop)
function get_links_path() { function get_links_path() {
if (!global_shop_path) return null
return global_shop_path + '/link.toml' return global_shop_path + '/link.toml'
} }
// Get the packages directory (in the global shop) // Get the packages directory (in the global shop)
function get_packages_dir() { function get_packages_dir() {
if (!global_shop_path) return null
return global_shop_path + '/packages' return global_shop_path + '/packages'
} }
@@ -62,7 +64,7 @@ var link_cache = null
Link.load = function() { Link.load = function() {
if (link_cache) return link_cache if (link_cache) return link_cache
var path = get_links_path() var path = get_links_path()
if (!fd.is_file(path)) { if (!path || !fd.is_file(path)) {
link_cache = {} link_cache = {}
return link_cache return link_cache
} }

52
list.ce
View File

@@ -12,6 +12,13 @@ var fd = use('fd')
var mode = 'local' var mode = 'local'
var target_pkg = null 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 && length(args) > 0) {
if (args[0] == 'shop') { if (args[0] == 'shop') {
@@ -32,7 +39,7 @@ if (args && length(args) > 0) {
// Resolve local paths // Resolve local paths
if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) { 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) { if (resolved) {
target_pkg = resolved target_pkg = resolved
} }
@@ -43,22 +50,24 @@ if (args && length(args) > 0) {
var links = link.load() var links = link.load()
var lock = shop.load_lock() var lock = shop.load_lock()
function print_deps(ctx, indent) { function print_deps(ctx, raw_indent) {
indent = indent || "" var aliases = null
var deps var indent = raw_indent || ""
try { deps = null
var _read = function() {
deps = pkg.dependencies(ctx) deps = pkg.dependencies(ctx)
} catch (e) { } disruption {
log.console(indent + " (could not read dependencies)") log.console(indent + " (could not read dependencies)")
return return
} }
_read()
if (!deps) { if (!deps) {
log.console(indent + " (none)") log.console(indent + " (none)")
return return
} }
var aliases = array(deps) aliases = array(deps)
aliases = sort(aliases) aliases = sort(aliases)
if (length(aliases) == 0) { if (length(aliases) == 0) {
@@ -66,19 +75,26 @@ function print_deps(ctx, indent) {
return return
} }
for (var i = 0; i < length(aliases); i++) { var j = 0
var alias = aliases[i] var alias = null
var locator = deps[alias] var locator = null
var link_target = links[locator] var link_target = null
var lock_entry = lock[locator] 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) { if (alias != locator) {
line += " -> " + locator line += " -> " + locator
} }
// Add status indicators // Add status indicators
var status = [] status = []
if (link_target) { if (link_target) {
push(status, "linked -> " + link_target) push(status, "linked -> " + link_target)
} }
@@ -110,16 +126,16 @@ if (mode == 'local') {
log.console("Shop packages:") log.console("Shop packages:")
log.console("") log.console("")
var packages = shop.list_packages() packages = shop.list_packages()
if (length(packages) == 0) { if (length(packages) == 0) {
log.console(" (none)") log.console(" (none)")
} else { } else {
packages = sort(packages) packages = sort(packages)
// Group by type // Group by type
var local_pkgs = [] local_pkgs = []
var linked_pkgs = [] linked_pkgs = []
var remote_pkgs = [] remote_pkgs = []
arrfor(packages, function(p) { arrfor(packages, function(p) {
if (p == 'core') return if (p == 'core') return

5
ls.ce
View File

@@ -9,13 +9,14 @@ var ctx = null
var pkg = args[0] || package.find_package_dir('.') var pkg = args[0] || package.find_package_dir('.')
var modules = package.list_modules(pkg) var modules = package.list_modules(pkg)
var programs = package.list_programs(pkg) var programs = package.list_programs(pkg)
var i = 0
log.console("Modules in " + pkg + ":") log.console("Modules in " + pkg + ":")
modules = sort(modules) modules = sort(modules)
if (length(modules) == 0) { if (length(modules) == 0) {
log.console(" (none)") log.console(" (none)")
} else { } else {
for (var i = 0; i < length(modules); i++) { for (i = 0; i < length(modules); i++) {
log.console(" " + modules[i]) log.console(" " + modules[i])
} }
} }
@@ -26,7 +27,7 @@ programs = sort(programs)
if (length(programs) == 0) { if (length(programs) == 0) {
log.console(" (none)") log.console(" (none)")
} else { } else {
for (var i = 0; i < length(programs); i++) { for (i = 0; i < length(programs); i++) {
log.console(" " + programs[i]) log.console(" " + programs[i])
} }
} }

187
mcode.cm
View File

@@ -28,6 +28,13 @@ var mcode = function(ast) {
"<<=": "shl", ">>=": "shr", ">>>=": "ushr" "<<=": "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 // Compiler state
var s_instructions = null var s_instructions = null
var s_data = null var s_data = null
@@ -273,19 +280,75 @@ var mcode = function(ast) {
return node.kind == "null" 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 // reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
var emit_add_decomposed = function() { 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)) { if (is_known_text(_bp_ln) && is_known_text(_bp_rn)) {
emit_3("concat", _bp_dest, _bp_left, _bp_right) emit_3("concat", _bp_dest, _bp_left, _bp_right)
return null return null
} }
if (is_known_number(_bp_ln) && is_known_number(_bp_rn)) {
emit_3("add", _bp_dest, _bp_left, _bp_right)
return null
}
// If either operand is a known number, concat is impossible
if (is_known_number(_bp_ln) || is_known_number(_bp_rn)) {
emit_numeric_binop("add")
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_3("add", _bp_dest, _bp_left, _bp_right)
emit_jump(done)
emit_label(err)
emit_0("disrupt")
emit_label(done)
return null 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) // emit_eq_decomposed: identical -> int -> float -> text -> null -> bool -> mismatch(false)
// reads _bp_dest, _bp_left, _bp_right from closure // reads _bp_dest, _bp_left, _bp_right from closure
@@ -511,15 +574,36 @@ var mcode = function(ast) {
return null 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) { 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_2("negate", dest, src)
emit_jump(done)
emit_label(err)
emit_0("disrupt")
emit_label(done)
return null return null
} }
// Central router: maps op string to decomposition helper // Central router: maps op string to decomposition helper
// Sets _bp_* closure vars then calls helper with reduced args // 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 emit_binop = function(op_str, dest, left, right) {
var rel = null
_bp_dest = dest _bp_dest = dest
_bp_left = left _bp_left = left
_bp_right = right _bp_right = right
@@ -529,18 +613,17 @@ var mcode = function(ast) {
emit_eq_decomposed() emit_eq_decomposed()
} else if (op_str == "ne") { } else if (op_str == "ne") {
emit_ne_decomposed() 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 { } else {
// Passthrough for subtract, multiply, divide, modulo, rel = relational_ops[op_str]
// bitwise, pow, in, etc. if (rel != null) {
emit_3(op_str, dest, left, right) 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 return null
} }
@@ -766,7 +849,7 @@ var mcode = function(ast) {
if (scope == null) { if (scope == null) {
return null return null
} }
var keys = array(scope) var keys = sort(array(scope))
var _i = 0 var _i = 0
var name = null var name = null
var v = null var v = null
@@ -1139,7 +1222,7 @@ var mcode = function(ast) {
} }
// Binary expression compilation // Binary expression compilation
var gen_binary = function(node) { var gen_binary = function(node, target) {
var kind = node.kind var kind = node.kind
var left = node.left var left = node.left
var right = node.right var right = node.right
@@ -1194,7 +1277,8 @@ var mcode = function(ast) {
// Standard binary ops // Standard binary ops
left_slot = gen_expr(left, -1) left_slot = gen_expr(left, -1)
right_slot = gen_expr(right, -1) right_slot = gen_expr(right, -1)
dest = alloc_slot() // Use target slot for ops without multi-type dispatch (add has text+num paths)
dest = (target >= 0 && kind != "+") ? target : alloc_slot()
op = binop_map[kind] op = binop_map[kind]
if (op == null) { if (op == null) {
op = "add" op = "add"
@@ -1348,9 +1432,9 @@ var mcode = function(ast) {
return val_slot return val_slot
} }
val_slot = gen_expr(right, -1)
left_kind = left.kind left_kind = left.kind
// For local name assignments, try to write directly to the var's slot
if (left_kind == "name") { if (left_kind == "name") {
name = left.name name = left.name
level = left.level level = left.level
@@ -1360,17 +1444,30 @@ var mcode = function(ast) {
if (level == 0 || level == -1) { if (level == 0 || level == -1) {
slot = find_var(name) slot = find_var(name)
if (slot >= 0) { if (slot >= 0) {
emit_2("move", slot, val_slot) val_slot = gen_expr(right, slot)
} else if (level == -1) { if (val_slot != slot) {
emit_2("move", slot, val_slot)
}
return val_slot
}
val_slot = gen_expr(right, -1)
if (level == -1) {
add_instr(["set_var", name, val_slot]) add_instr(["set_var", name, val_slot])
} }
} else if (level > 0) { } else {
_lv = level - 1 val_slot = gen_expr(right, -1)
pstate = parent_states[length(parent_states) - 1 - _lv] if (level > 0) {
pslot = find_var_in_saved(pstate, name) _lv = level - 1
emit_3("put", val_slot, pslot, level) pstate = parent_states[length(parent_states) - 1 - _lv]
pslot = find_var_in_saved(pstate, name)
emit_3("put", val_slot, pslot, level)
}
} }
} else if (left_kind == ".") { return val_slot
}
val_slot = gen_expr(right, -1)
if (left_kind == ".") {
obj = left.left obj = left.left
prop = left.right prop = left.right
obj_slot = gen_expr(obj, -1) obj_slot = gen_expr(obj, -1)
@@ -1670,37 +1767,11 @@ var mcode = function(ast) {
fname = callee.name fname = callee.name
nargs = args_list != null ? length(args_list) : 0 nargs = args_list != null ? length(args_list) : 0
// 1-arg type check intrinsics → direct opcode // 1-arg type check intrinsics → direct opcode
if (nargs == 1) { if (nargs == 1 && sensory_ops[fname] != null) {
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") {
a0 = gen_expr(args_list[0], -1) a0 = gen_expr(args_list[0], -1)
d = alloc_slot() d = alloc_slot()
if (fname == "is_array") { emit_2(sensory_ops[fname], d, a0)
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)
}
return d return d
}
} }
// 2-arg push: push(arr, val) → guarded direct opcode // 2-arg push: push(arr, val) → guarded direct opcode
if (nargs == 2 && fname == "push") { if (nargs == 2 && fname == "push") {
@@ -1930,7 +2001,7 @@ var mcode = function(ast) {
_i = _i + 1 _i = _i + 1
} }
dest = alloc_slot() dest = alloc_slot()
add_instr(["array", dest, 0]) add_instr(["array", dest, count])
_i = 0 _i = 0
while (_i < count) { while (_i < count) {
emit_2("push", dest, elem_slots[_i]) emit_2("push", dest, elem_slots[_i])
@@ -1943,7 +2014,7 @@ var mcode = function(ast) {
if (kind == "record") { if (kind == "record") {
list = expr.list list = expr.list
dest = alloc_slot() dest = alloc_slot()
push(s_instructions, ["record", dest, 0]) push(s_instructions, ["record", dest, length(list)])
_i = 0 _i = 0
while (_i < length(list)) { while (_i < length(list)) {
pair = list[_i] pair = list[_i]
@@ -1993,7 +2064,7 @@ var mcode = function(ast) {
} }
// Binary operators (fallback) // Binary operators (fallback)
return gen_binary(expr) return gen_binary(expr, target)
} }
// Statement compilation // Statement compilation

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,15 @@ add_project_arguments(
) )
add_project_arguments('-Wno-narrowing', language: 'cpp') 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 = [] deps = []
if host_machine.system() == 'darwin' if host_machine.system() == 'darwin'
@@ -59,10 +68,10 @@ scripts = [
'fit.c', 'fit.c',
'crypto.c', 'crypto.c',
'internal/kim.c', 'internal/kim.c',
'time.c', 'internal/time.c',
'debug/debug.c', 'debug/debug.c',
'internal/os.c', 'internal/os.c',
'fd.c', 'internal/fd.c',
'net/http.c', 'net/http.c',
'net/enet.c', 'net/enet.c',
'wildstar.c', 'wildstar.c',

4
meson.options Normal file
View 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)')

View File

@@ -568,21 +568,23 @@ static const JSCFunctionListEntry js_enet_peer_funcs[] = {
// JS_CGETSET_DEF("address", js_enet_peer_get_address, NULL), // 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_NewClassID(&enet_host_id);
JS_NewClass(ctx, enet_host_id, &enet_host); JS_NewClass(ctx, enet_host_id, &enet_host);
JSValue host_proto = JS_NewObject(ctx); JS_ROOT(host_proto, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs)); JS_SetPropertyFunctionList(ctx, host_proto.val, js_enet_host_funcs, countof(js_enet_host_funcs));
JS_SetClassProto(ctx, enet_host_id, host_proto); JS_SetClassProto(ctx, enet_host_id, host_proto.val);
JS_NewClassID(&enet_peer_class_id); JS_NewClassID(&enet_peer_class_id);
JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class); JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class);
JSValue peer_proto = JS_NewObject(ctx); JS_ROOT(peer_proto, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs)); JS_SetPropertyFunctionList(ctx, peer_proto.val, js_enet_peer_funcs, countof(js_enet_peer_funcs));
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto); JS_SetClassProto(ctx, enet_peer_class_id, peer_proto.val);
JSValue export_obj = JS_NewObject(ctx); JS_ROOT(export_obj, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, export_obj, js_enet_funcs, countof(js_enet_funcs)); JS_SetPropertyFunctionList(ctx, export_obj.val, js_enet_funcs, countof(js_enet_funcs));
return export_obj; JS_RETURN(export_obj.val);
} }

View File

@@ -318,10 +318,11 @@ static const JSCFunctionListEntry js_http_funcs[] = {
JS_CFUNC_DEF("fetch", 2, js_fetch_picoparser), 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 par_easycurl_init(0); // Initialize platform HTTP backend
JSValue obj = JS_NewObject(js); JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, obj, js_http_funcs, JS_SetPropertyFunctionList(js, mod.val, js_http_funcs,
sizeof(js_http_funcs)/sizeof(js_http_funcs[0])); sizeof(js_http_funcs)/sizeof(js_http_funcs[0]));
return obj; JS_RETURN(mod.val);
} }

View File

@@ -594,27 +594,28 @@ static const JSCFunctionListEntry js_socket_funcs[] = {
MIST_FUNC_DEF(socket, close, 1), MIST_FUNC_DEF(socket, close, 1),
}; };
JSValue js_socket_use(JSContext *js) { JSValue js_core_socket_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js, mod, js_socket_funcs, countof(js_socket_funcs)); JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_socket_funcs, countof(js_socket_funcs));
// Add constants // Add constants
JS_SetPropertyStr(js, mod, "AF_UNSPEC", JS_NewInt32(js, AF_UNSPEC)); JS_SetPropertyStr(js, mod.val, "AF_UNSPEC", JS_NewInt32(js, AF_UNSPEC));
JS_SetPropertyStr(js, mod, "AF_INET", JS_NewInt32(js, AF_INET)); JS_SetPropertyStr(js, mod.val, "AF_INET", JS_NewInt32(js, AF_INET));
JS_SetPropertyStr(js, mod, "AF_INET6", JS_NewInt32(js, AF_INET6)); JS_SetPropertyStr(js, mod.val, "AF_INET6", JS_NewInt32(js, AF_INET6));
JS_SetPropertyStr(js, mod, "AF_UNIX", JS_NewInt32(js, AF_UNIX)); JS_SetPropertyStr(js, mod.val, "AF_UNIX", JS_NewInt32(js, AF_UNIX));
JS_SetPropertyStr(js, mod, "SOCK_STREAM", JS_NewInt32(js, SOCK_STREAM)); JS_SetPropertyStr(js, mod.val, "SOCK_STREAM", JS_NewInt32(js, SOCK_STREAM));
JS_SetPropertyStr(js, mod, "SOCK_DGRAM", JS_NewInt32(js, SOCK_DGRAM)); JS_SetPropertyStr(js, mod.val, "SOCK_DGRAM", JS_NewInt32(js, SOCK_DGRAM));
JS_SetPropertyStr(js, mod, "AI_PASSIVE", JS_NewInt32(js, AI_PASSIVE)); JS_SetPropertyStr(js, mod.val, "AI_PASSIVE", JS_NewInt32(js, AI_PASSIVE));
JS_SetPropertyStr(js, mod, "SHUT_RD", JS_NewInt32(js, SHUT_RD)); JS_SetPropertyStr(js, mod.val, "SHUT_RD", JS_NewInt32(js, SHUT_RD));
JS_SetPropertyStr(js, mod, "SHUT_WR", JS_NewInt32(js, SHUT_WR)); JS_SetPropertyStr(js, mod.val, "SHUT_WR", JS_NewInt32(js, SHUT_WR));
JS_SetPropertyStr(js, mod, "SHUT_RDWR", JS_NewInt32(js, SHUT_RDWR)); JS_SetPropertyStr(js, mod.val, "SHUT_RDWR", JS_NewInt32(js, SHUT_RDWR));
JS_SetPropertyStr(js, mod, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET)); JS_SetPropertyStr(js, mod.val, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET));
JS_SetPropertyStr(js, mod, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR)); JS_SetPropertyStr(js, mod.val, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR));
return mod; JS_RETURN(mod.val);
} }

16
pack.ce
View File

@@ -13,6 +13,7 @@ var target = null
var output_name = 'app' var output_name = 'app'
var target_package = null var target_package = null
var buildtype = 'debug' var buildtype = 'debug'
var i = 0
if (length(args) < 1) { if (length(args) < 1) {
log.error('Usage: cell pack <package> [options]') log.error('Usage: cell pack <package> [options]')
@@ -24,12 +25,11 @@ if (length(args) < 1) {
log.error('') log.error('')
log.error('Available targets: ' + text(build.list_targets(), ', ')) log.error('Available targets: ' + text(build.list_targets(), ', '))
$stop() $stop()
return
} }
target_package = args[0] 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 (args[i] == '-t' || args[i] == '--target') {
if (i + 1 < length(args)) { if (i + 1 < length(args)) {
target = args[++i] target = args[++i]
@@ -87,7 +87,7 @@ if (target && !build.has_target(target)) {
var packages = ['core'] var packages = ['core']
var deps = pkg_tools.gather_dependencies(target_package) 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, deps[i])
} }
push(packages, target_package) push(packages, target_package)
@@ -95,7 +95,7 @@ push(packages, target_package)
// Remove duplicates // Remove duplicates
var unique_packages = [] var unique_packages = []
var seen = {} var seen = {}
for (var i = 0; i < length(packages); i++) { for (i = 0; i < length(packages); i++) {
if (!seen[packages[i]]) { if (!seen[packages[i]]) {
seen[packages[i]] = true seen[packages[i]] = true
push(unique_packages, packages[i]) 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, ', ')) 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) var result = build.build_static(packages, target, output_name, buildtype)
log.console('Build complete: ' + result) log.console('Build complete: ' + result)
} catch (e) { } disruption {
log.error('Build failed: ') log.error('Build failed')
log.error(e)
$stop() $stop()
} }
_build()
$stop() $stop()

View File

@@ -2,9 +2,11 @@ var package = {}
var fd = use('fd') var fd = use('fd')
var toml = use('toml') var toml = use('toml')
var json = use('json') var json = use('json')
var os = use('os') var runtime = use('runtime')
var link = use('link') var link = use('link')
var global_shop_path = runtime.shop_path
// Cache for loaded configs to avoid toml re-parsing corruption // Cache for loaded configs to avoid toml re-parsing corruption
var config_cache = {} var config_cache = {}
@@ -35,11 +37,11 @@ function get_path(name)
if (starts_with(link_target, '/')) if (starts_with(link_target, '/'))
return link_target return link_target
// Otherwise it's another package name, resolve that // Otherwise it's another package name, resolve that
return os.global_shop_path + '/packages/' + replace(replace(link_target, '/', '_'), '@', '_') return global_shop_path + '/packages/' + replace(replace(link_target, '/', '_'), '@', '_')
} }
// Remote packages use nested directories, so don't transform slashes // Remote packages use nested directories, so don't transform slashes
return os.global_shop_path + '/packages/' + replace(name, '@', '_') return global_shop_path + '/packages/' + replace(name, '@', '_')
} }
package.load_config = function(name) package.load_config = function(name)

View File

@@ -6,4 +6,4 @@ var filename = args[0]
var src = text(fd.slurp(filename)) var src = text(fd.slurp(filename))
var result = tokenize(src, filename) var result = tokenize(src, filename)
var ast = parse(result.tokens, src, filename, tokenize) var ast = parse(result.tokens, src, filename, tokenize)
print(json.encode(ast)) print(json.encode(ast, true))

View File

@@ -1,6 +1,11 @@
var parse = function(tokens, src, filename, tokenizer) { var parse = function(tokens, src, filename, tokenizer) {
var _src_len = length(src) var _src_len = length(src)
var template_escape_map = {
n: "\n", t: "\t", r: "\r", "\\": "\\",
"`": "`", "$": "$", "0": character(0)
}
// ============================================================ // ============================================================
// Parser Cursor // Parser Cursor
// ============================================================ // ============================================================
@@ -175,6 +180,7 @@ var parse = function(tokens, src, filename, tokenizer) {
var tc = null var tc = null
var tq = null var tq = null
var esc_ch = null var esc_ch = null
var esc_val = null
var expr_tokens = null var expr_tokens = null
var sub_ast = null var sub_ast = null
var sub_stmt = null var sub_stmt = null
@@ -223,13 +229,8 @@ var parse = function(tokens, src, filename, tokenizer) {
while (tvi < tvlen) { while (tvi < tvlen) {
if (tv[tvi] == "\\" && tvi + 1 < tvlen) { if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
esc_ch = tv[tvi + 1] esc_ch = tv[tvi + 1]
if (esc_ch == "n") { push(fmt_parts, "\n") } esc_val = template_escape_map[esc_ch]
else if (esc_ch == "t") { push(fmt_parts, "\t") } if (esc_val != null) { push(fmt_parts, esc_val) }
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)) }
else { push(fmt_parts, esc_ch) } else { push(fmt_parts, esc_ch) }
tvi = tvi + 2 tvi = tvi + 2
} else if (tv[tvi] == "$" && tvi + 1 < tvlen && tv[tvi + 1] == "{") { } else if (tv[tvi] == "$" && tvi + 1 < tvlen && tv[tvi + 1] == "{") {
@@ -2061,7 +2062,7 @@ var parse = function(tokens, src, filename, tokenizer) {
enclosing = sem_find_func_scope(scope) enclosing = sem_find_func_scope(scope)
if (enclosing != null) enclosing.has_inner_func = true if (enclosing != null) enclosing.has_inner_func = true
name = stmt.name name = stmt.name
if (name != null) sem_add_var(scope, name, {make: "function", fn_nr: scope.function_nr}) if (name != null && sem_find_var(scope, name) == null) sem_add_var(scope, name, {make: "function", fn_nr: scope.function_nr})
fn_nr_val = stmt.function_nr fn_nr_val = stmt.function_nr
if (fn_nr_val == null) fn_nr_val = scope.function_nr if (fn_nr_val == null) fn_nr_val = scope.function_nr
fn_scope = make_scope(scope, fn_nr_val, {is_func: true}) fn_scope = make_scope(scope, fn_nr_val, {is_func: true})

File diff suppressed because it is too large Load Diff

356
qbe.cm
View File

@@ -98,6 +98,7 @@ var is_text = function(p, v) {
jmp @${p}.done jmp @${p}.done
@${p}.no @${p}.no
%${p} =w copy 0 %${p} =w copy 0
jmp @${p}.done
@${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}.fs63, %${p}.fe52
%${p}.fbits =l or %${p}.fbits, %${p}.fmant %${p}.fbits =l or %${p}.fbits, %${p}.fmant
%${p} =d cast %${p}.fbits %${p} =d cast %${p}.fbits
jmp @${p}.done
@${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} // new_float64 — C call to __JS_NewFloat64(ctx, val). Result: %{p}
var new_float64 = function(p, ctx, d) { 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) // Arithmetic — add/sub/mul/div/mod(p, ctx, a, b)
// Int fast path inline, text concat and float as C calls. // Simple C call wrappers. Type dispatch is handled in mcode.cm.
// Jumps to @disrupt on type mismatch.
// ============================================================ // ============================================================
var add = function(p, ctx, a, b) { var add = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_add(l ${ctx}, l ${a}, l ${b})
%${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
` `
} }
var sub = function(p, ctx, a, b) { var sub = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_sub(l ${ctx}, l ${a}, l ${b})
%${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
` `
} }
var mul = function(p, ctx, a, b) { var mul = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_mul(l ${ctx}, l ${a}, l ${b})
%${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
` `
} }
var div = function(p, ctx, a, b) { var div = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_div(l ${ctx}, l ${a}, l ${b})
%${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
` `
} }
var mod = function(p, ctx, a, b) { var mod = function(p, ctx, a, b) {
return `@${p}.start return ` %${p} =l call $qbe_float_mod(l ${ctx}, l ${a}, l ${b})
%${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
` `
} }
@@ -484,6 +322,7 @@ var cmp = function(p, ctx, a, b) {
jmp @${p}.done jmp @${p}.done
@${p}.mismatch @${p}.mismatch
%${p} =l copy ${mismatch_val} %${p} =l copy ${mismatch_val}
jmp @${p}.done
@${p}.done @${p}.done
` `
} }
@@ -518,90 +357,28 @@ var gt = function(p, ctx, a, b) {
var ge = 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} _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 // 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) { var neg = function(p, ctx, v) {
return `@${p}.start return ` %${p} =l call $qbe_float_neg(l ${ctx}, l ${v})
%${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
` `
} }
// 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) { var inc = function(p, ctx, v) {
return `@${p}.start return ` %${p} =l call $qbe_float_inc(l ${ctx}, l ${v})
%${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
` `
} }
// 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) { var dec = function(p, ctx, v) {
return `@${p}.start return ` %${p} =l call $qbe_float_dec(l ${ctx}, l ${v})
%${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
` `
} }
@@ -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) { var bnot = function(p, ctx, v) {
return `@${p}.start return ` %${p} =l call $qbe_bnot(l ${ctx}, l ${v})
%${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
` `
} }
@@ -639,92 +403,34 @@ var bnot = function(p, ctx, v) {
// Both operands must be numeric. Int fast path, float -> convert to int32. // Both operands must be numeric. Int fast path, float -> convert to int32.
// ============================================================ // ============================================================
// reads _qop from closure var band = function(p, ctx, a, b) {
var bitwise_op = function(p, ctx, a, b) { return ` %${p} =l call $qbe_bitwise_and(l ${ctx}, l ${a}, l ${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) {
_qop = "and"
return bitwise_op(p, ctx, a, b)
}
var bor = function(p, ctx, a, b) { var bor = function(p, ctx, a, b) {
_qop = "or" return ` %${p} =l call $qbe_bitwise_or(l ${ctx}, l ${a}, l ${b})
return bitwise_op(p, ctx, a, b) `
} }
var bxor = function(p, ctx, a, b) { var bxor = function(p, ctx, a, b) {
_qop = "xor" return ` %${p} =l call $qbe_bitwise_xor(l ${ctx}, l ${a}, l ${b})
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
` `
} }
var shl = function(p, ctx, a, b) { var shl = function(p, ctx, a, b) {
_qop = "shl" return ` %${p} =l call $qbe_shift_shl(l ${ctx}, l ${a}, l ${b})
return shift_op(p, ctx, a, b) `
} }
var shr = function(p, ctx, a, b) { var shr = function(p, ctx, a, b) {
_qop = "sar" return ` %${p} =l call $qbe_shift_sar(l ${ctx}, l ${a}, l ${b})
return shift_op(p, ctx, a, b) `
} }
var ushr = function(p, ctx, a, b) { var ushr = function(p, ctx, a, b) {
_qop = "shr" return ` %${p} =l call $qbe_shift_shr(l ${ctx}, l ${a}, l ${b})
return shift_op(p, ctx, a, b) `
} }
// ============================================================ // ============================================================

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

21
qop.c
View File

@@ -456,20 +456,21 @@ static const JSCFunctionListEntry js_qop_funcs[] = {
JS_PROP_INT32_DEF("FLAG_ENCRYPTED", QOP_FLAG_ENCRYPTED, 0), 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_NewClassID(&js_qop_archive_class_id);
JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class); JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class);
JSValue archive_proto = JS_NewObject(js); JS_ROOT(archive_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs)); 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); JS_SetClassProto(js, js_qop_archive_class_id, archive_proto.val);
JS_NewClassID(&js_qop_writer_class_id); JS_NewClassID(&js_qop_writer_class_id);
JS_NewClass(js, js_qop_writer_class_id, &js_qop_writer_class); JS_NewClass(js, js_qop_writer_class_id, &js_qop_writer_class);
JSValue writer_proto = JS_NewObject(js); JS_ROOT(writer_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, writer_proto, js_qop_writer_funcs, countof(js_qop_writer_funcs)); 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); JS_SetClassProto(js, js_qop_writer_class_id, writer_proto.val);
JSValue mod = JS_NewObject(js); JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod, js_qop_funcs, countof(js_qop_funcs)); JS_SetPropertyFunctionList(js, mod.val, js_qop_funcs, countof(js_qop_funcs));
return mod; JS_RETURN(mod.val);
} }

View File

@@ -9,19 +9,20 @@ function print_usage() {
log.console(" <sources...> <archive> .. create archive from sources") log.console(" <sources...> <archive> .. create archive from sources")
} }
function list(archive_path) { function list_archive(archive_path) {
var blob = fd.slurp(archive_path) var blob = fd.slurp(archive_path)
var archive = null
if (!blob) { if (!blob) {
log.console("Could not open archive " + archive_path) log.console("Could not open archive " + archive_path)
return return
} }
var archive = null var _open = function() {
try {
archive = qop.open(blob) archive = qop.open(blob)
} catch(e) { } disruption {
log.console("Could not open archive " + archive_path + ": " + e.message) log.console("Could not open archive " + archive_path)
return return
} }
_open()
var files = archive.list() var files = archive.list()
arrfor(files, function(f) { arrfor(files, function(f) {
@@ -35,34 +36,41 @@ function list(archive_path) {
function unpack(archive_path) { function unpack(archive_path) {
var blob = fd.slurp(archive_path) var blob = fd.slurp(archive_path)
var archive = null
if (!blob) { if (!blob) {
log.console("Could not open archive " + archive_path) log.console("Could not open archive " + archive_path)
return return
} }
var archive = null var _open = function() {
try {
archive = qop.open(blob) archive = qop.open(blob)
} catch(e) { } disruption {
log.console("Could not open archive " + archive_path + ": " + e.message) log.console("Could not open archive " + archive_path)
return return
} }
_open()
var files = archive.list() var files = archive.list()
arrfor(files, function(f) { arrfor(files, function(f) {
var data = archive.read(f) var data = archive.read(f)
var dir = null
var parts = null
var curr = null
var fh = null
var _mk = null
if (data) { if (data) {
// Ensure directory exists // Ensure directory exists
var dir = fd.dirname(f) dir = fd.dirname(f)
if (dir) { if (dir) {
// recursive mkdir // recursive mkdir
var parts = array(dir, '/') parts = array(dir, '/')
var curr = "." curr = "."
arrfor(parts, function(p) { arrfor(parts, function(p) {
curr += "/" + 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.write(fh, data)
fd.close(fh) fd.close(fh)
log.console("Extracted " + f) log.console("Extracted " + f)
@@ -78,24 +86,27 @@ function pack(sources, archive_path, read_dir) {
function add_recursive(path) { function add_recursive(path) {
var full_path = base_dir + "/" + path var full_path = base_dir + "/" + path
var st = null
var list = null
var data = null
if (path == ".") full_path = base_dir if (path == ".") full_path = base_dir
if (read_dir == null && path != ".") full_path = path if (read_dir == null && path != ".") full_path = path
var st = fd.stat(full_path) st = fd.stat(full_path)
if (!st) { if (!st) {
log.console("Could not stat " + full_path) log.console("Could not stat " + full_path)
return return
} }
if (st.isDirectory) { if (st.isDirectory) {
var list = fd.readdir(full_path) list = fd.readdir(full_path)
arrfor(list, function(item) { arrfor(list, function(item) {
if (item == "." || item == "..") return if (item == "." || item == "..") return
var sub = path == "." ? item : path + "/" + item var sub = path == "." ? item : path + "/" + item
add_recursive(sub) add_recursive(sub)
}) })
} else { } else {
var data = fd.slurp(full_path) data = fd.slurp(full_path)
if (data) { if (data) {
writer.add_file(path, data) writer.add_file(path, data)
log.console("Added " + path) log.console("Added " + path)
@@ -111,29 +122,34 @@ function pack(sources, archive_path, read_dir) {
log.console("Created " + archive_path) 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() print_usage()
} else { } else {
if (arg[0] == "-l") { if (args[0] == "-l") {
if (length(arg) < 2) print_usage() if (length(args) < 2) print_usage()
else list(arg[1]) else list_archive(args[1])
} else if (arg[0] == "-u") { } else if (args[0] == "-u") {
if (length(arg) < 2) print_usage() if (length(args) < 2) print_usage()
else unpack(arg[1]) else unpack(args[1])
} else { } else {
var sources = [] sources = []
var archive = null archive = null
var read_dir = null read_dir = null
var i = 0 i = 0
if (arg[0] == "-d") { if (args[0] == "-d") {
read_dir = arg[1] read_dir = args[1]
i = 2 i = 2
} }
for (; i < length(arg) - 1; i++) { for (; i < length(args) - 1; i++) {
push(sources, arg[i]) push(sources, args[i])
} }
archive = arg[length(arg) - 1] archive = args[length(args) - 1]
if (length(sources) == 0) { if (length(sources) == 0) {
print_usage() print_usage()

155
regen.ce
View File

@@ -1,155 +0,0 @@
// regen.ce — regenerate .mach bytecode files
// Run with: ./cell --core . regen
var fd = use("fd")
var json = use("json")
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"}
]
var i = 0
var entry = null
var src = null
var tok_result = null
var ast = null
var folded = null
var compiled = null
var optimized = null
var mcode_text = null
var f = null
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")
}
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)
// Check for parse/semantic errors
errs = ast.errors
if (errs != null && length(errs) > 0) {
ei = 0
while (ei < length(errs)) {
e = errs[ei]
if (e.line != null) {
print(`${entry.src}:${text(e.line)}:${text(e.column)}: error: ${e.message}`)
} else {
print(`${entry.src}: error: ${e.message}`)
}
ei = ei + 1
}
had_errors = true
i = i + 1
continue
}
folded = fold(ast)
compiled = mcode(folded)
optimized = streamline(compiled)
mcode_text = compact_arrays(json.encode(optimized, null, 2))
f = fd.open(entry.out, "w")
fd.write(f, mcode_text)
fd.close(f)
print(`wrote ${entry.out}`)
i = i + 1
}
if (had_errors) {
print("regen aborted: fix errors above")
}

View File

@@ -16,8 +16,10 @@ var fd = use('fd')
var target_pkg = null var target_pkg = null
var prune = false var prune = false
var dry_run = 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') { if (args[i] == '--prune') {
prune = true prune = true
} else if (args[i] == '--dry-run') { } else if (args[i] == '--dry-run') {
@@ -43,7 +45,7 @@ if (!target_pkg) {
// Resolve relative paths to absolute paths // Resolve relative paths to absolute paths
if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) { 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) { if (resolved) {
target_pkg = 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 packages_to_remove = [target_pkg]
var lock = null
var all_packages = null
var needed = null
if (prune) { if (prune) {
// Find packages no longer needed // Find packages no longer needed
// Get all dependencies of remaining packages // Get all dependencies of remaining packages
var lock = shop.load_lock() lock = shop.load_lock()
var all_packages = shop.list_packages() all_packages = shop.list_packages()
// Build set of all needed packages (excluding target) // Build set of all needed packages (excluding target)
var needed = {} needed = {}
arrfor(all_packages, function(p) { arrfor(all_packages, function(p) {
if (p == target_pkg || p == 'core') return if (p == target_pkg || p == 'core') return
// Mark this package and its deps as needed // Mark this package and its deps as needed
needed[p] = true needed[p] = true
try { var _gather = function() {
var deps = pkg.gather_dependencies(p) var deps = pkg.gather_dependencies(p)
arrfor(deps, function(dep) { arrfor(deps, function(dep) {
needed[dep] = true needed[dep] = true
}) })
} catch (e) { } disruption {
// Skip if can't read deps // Skip if can't read deps
} }
_gather()
}) })
// Find packages that are NOT needed // Find packages that are NOT needed

View File

@@ -20,8 +20,10 @@ var target_locator = null
var target_triple = null var target_triple = null
var show_locked = false var show_locked = false
var refresh_first = 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 (args[i] == '--target' || args[i] == '-t') {
if (i + 1 < length(args)) { if (i + 1 < length(args)) {
target_triple = args[++i] target_triple = args[++i]
@@ -55,16 +57,17 @@ if (!target_locator) {
// Resolve local paths // Resolve local paths
if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) { 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) { if (resolved) {
target_locator = resolved target_locator = resolved
} }
} }
// Check if it's a valid package // Check if it's a valid package
var pkg_dir = null
if (!fd.is_file(target_locator + '/cell.toml')) { if (!fd.is_file(target_locator + '/cell.toml')) {
// Try to find it in the shop // 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')) { if (!fd.is_file(pkg_dir + '/cell.toml')) {
log.error("Not a valid package: " + target_locator) log.error("Not a valid package: " + target_locator)
$stop() $stop()
@@ -89,7 +92,7 @@ function gather_deps(locator, depth) {
all_deps[locator] = { depth: depth } all_deps[locator] = { depth: depth }
try { var _gather = function() {
var deps = pkg.dependencies(locator) var deps = pkg.dependencies(locator)
if (deps) { if (deps) {
arrfor(array(deps), function(alias) { arrfor(array(deps), function(alias) {
@@ -97,9 +100,10 @@ function gather_deps(locator, depth) {
gather_deps(dep_locator, depth + 1) gather_deps(dep_locator, depth + 1)
}) })
} }
} catch (e) { } disruption {
// Package might not have dependencies // Package might not have dependencies
} }
_gather()
} }
gather_deps(target_locator, 0) gather_deps(target_locator, 0)
@@ -114,51 +118,75 @@ var sorted = array(array(all_deps), function(locator) { return { locator: locato
sorted = sort(sorted, "locator") sorted = sort(sorted, "locator")
sorted = sort(sorted, "depth") sorted = sort(sorted, "depth")
for (var i = 0; i < length(sorted); i++) { var j = 0
var locator = sorted[i].locator var locator = null
var depth = sorted[i].depth 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 _show_flags = null
var indent = "" for (i = 0; i < length(sorted); i++) {
for (var j = 0; j < depth; j++) indent += " " locator = sorted[i].locator
depth = sorted[i].depth
indent = ""
for (j = 0; j < depth; j++) indent += " "
// Get info about this package // Get info about this package
var info = shop.resolve_package_info(locator) info = shop.resolve_package_info(locator)
var lock_entry = lock[locator] lock_entry = lock[locator]
var link_target = show_locked ? null : links[locator] link_target = show_locked ? null : links[locator]
var effective_locator = link_target || locator effective_locator = link_target || locator
// Check status // Check status
var is_linked = link_target != null is_linked = link_target != null
var is_in_lock = lock_entry != null is_in_lock = lock_entry != null
var is_local = info == 'local' is_local = info == 'local'
// Check if fetched (package directory exists) // Check if fetched (package directory exists)
var pkg_dir = shop.get_package_dir(locator) pkg_dir = shop.get_package_dir(locator)
var is_fetched = fd.is_dir(pkg_dir) || fd.is_link(pkg_dir) is_fetched = fd.is_dir(pkg_dir) || fd.is_link(pkg_dir)
// Check if built (library exists) // Check if built (library exists)
var lib_dir = shop.get_lib_dir() lib_dir = shop.get_lib_dir()
var lib_name = shop.lib_name_for_package(locator) lib_name = shop.lib_name_for_package(locator)
var dylib_ext = '.dylib' // TODO: detect from target dylib_ext = '.dylib' // TODO: detect from target
var lib_path = lib_dir + '/' + lib_name + dylib_ext lib_path = lib_dir + '/' + lib_name + dylib_ext
var is_built = fd.is_file(lib_path) is_built = fd.is_file(lib_path)
// Format output // Format output
var status_parts = [] status_parts = []
if (is_linked) push(status_parts, "linked") if (is_linked) push(status_parts, "linked")
if (is_local) push(status_parts, "local") if (is_local) push(status_parts, "local")
if (!is_in_lock) push(status_parts, "not in lock") if (!is_in_lock) push(status_parts, "not in lock")
if (!is_fetched) push(status_parts, "not fetched") if (!is_fetched) push(status_parts, "not fetched")
if (is_built) push(status_parts, "built") if (is_built) push(status_parts, "built")
var commit_str = "" commit_str = ""
if (lock_entry && lock_entry.commit) { if (lock_entry && lock_entry.commit) {
commit_str = " @" + text(lock_entry.commit, 0, 8) commit_str = " @" + text(lock_entry.commit, 0, 8)
} else if (lock_entry && lock_entry.type == 'local') { } else if (lock_entry && lock_entry.type == 'local') {
commit_str = " (local)" commit_str = " (local)"
} }
var line = indent + locator + commit_str line = indent + locator + commit_str
if (is_linked && !show_locked) { if (is_linked && !show_locked) {
line += " -> " + link_target line += " -> " + link_target
@@ -172,9 +200,9 @@ for (var i = 0; i < length(sorted); i++) {
// Show compilation inputs if requested (verbose) // Show compilation inputs if requested (verbose)
if (depth == 0) { if (depth == 0) {
try { _show_flags = function() {
var cflags = pkg.get_flags(locator, 'CFLAGS', target_triple) cflags = pkg.get_flags(locator, 'CFLAGS', target_triple)
var ldflags = pkg.get_flags(locator, 'LDFLAGS', target_triple) ldflags = pkg.get_flags(locator, 'LDFLAGS', target_triple)
if (length(cflags) > 0 || length(ldflags) > 0) { if (length(cflags) > 0 || length(ldflags) > 0) {
log.console(indent + " Compilation inputs:") log.console(indent + " Compilation inputs:")
if (length(cflags) > 0) { if (length(cflags) > 0) {
@@ -184,9 +212,10 @@ for (var i = 0; i < length(sorted); i++) {
log.console(indent + " LDFLAGS: " + text(ldflags, ' ')) log.console(indent + " LDFLAGS: " + text(ldflags, ' '))
} }
} }
} catch (e) { } disruption {
// Skip if can't read config // Skip if can't read config
} }
_show_flags()
} }
} }

25
run_aot.ce Normal file
View File

@@ -0,0 +1,25 @@
// run_aot.ce — compile a .ce program to native dylib and run it
//
// Usage:
// cell run_aot <program.ce>
var shop = use('internal/shop')
var fd = use('fd')
if (length(args) < 1) {
print('usage: cell run_aot <program.ce>')
return
}
var file = args[0]
if (!fd.is_file(file)) {
if (!ends_with(file, '.ce') && fd.is_file(file + '.ce'))
file = file + '.ce'
else {
print('file not found: ' + file)
return
}
}
var abs = fd.realpath(file)
shop.use_native(abs)

View File

@@ -1,16 +1,16 @@
// run_native.ce — load a module both interpreted and native, compare speed // run_native.ce — load a module both interpreted and native, compare speed
// //
// Usage: // 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. // runs both and compares results and timing.
var os = use('os') var os = use('os')
if (length(args) < 1) { if (length(args) < 1) {
print('usage: cell --core . run_native.ce <module>') print('usage: cell --dev run_native.ce <module>')
print(' e.g. cell --core . run_native.ce num_torture') print(' e.g. cell --dev run_native.ce num_torture')
return return
} }
@@ -21,7 +21,7 @@ if (ends_with(name, '.cm')) {
var safe = replace(replace(name, '/', '_'), '-', '_') var safe = replace(replace(name, '/', '_'), '-', '_')
var symbol = 'js_' + safe + '_use' var symbol = 'js_' + safe + '_use'
var dylib_path = './' + name + '.dylib' var dylib_path = './' + name + '.cm.dylib'
var fd = use('fd') var fd = use('fd')
// --- Test argument for function-returning modules --- // --- Test argument for function-returning modules ---

78
run_native_seed.ce Normal file
View 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
View File

@@ -0,0 +1,5 @@
// Runtime configuration — available to all modules via use('runtime')
return stone({
shop_path: shop_path,
core_path: core_path
})

View File

@@ -8,7 +8,6 @@ if (length(args) < 1) {
log.console("Usage: cell search <query>") log.console("Usage: cell search <query>")
log.console("Searches for packages, actors, or modules matching the query.") log.console("Searches for packages, actors, or modules matching the query.")
$stop() $stop()
return
} }
var query = args[0] var query = args[0]
@@ -26,7 +25,7 @@ arrfor(packages, function(package_name) {
} }
// Search modules and actors within the package // Search modules and actors within the package
try { var _search = function() {
var modules = pkg.list_modules(package_name) var modules = pkg.list_modules(package_name)
arrfor(modules, function(mod) { arrfor(modules, function(mod) {
if (search(mod, query) != null) { if (search(mod, query) != null) {
@@ -40,9 +39,10 @@ arrfor(packages, function(package_name) {
push(found_actors, package_name + ':' + actor) push(found_actors, package_name + ':' + actor)
} }
}) })
} catch (e) { } disruption {
// Skip packages that can't be read // Skip packages that can't be read
} }
_search()
}) })
// Print results // Print results

66
source/buddy_debug.c Normal file
View 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");
}

View File

@@ -11,9 +11,8 @@
#include "cell_internal.h" #include "cell_internal.h"
#include "cJSON.h" #include "cJSON.h"
#define BOOTSTRAP_MACH "internal/bootstrap.cm.mach" #define BOOTSTRAP_MCODE "boot/bootstrap.cm.mcode"
#define BOOTSTRAP_MCODE "internal/bootstrap.cm.mcode" #define ENGINE_SRC "internal/engine.cm"
#define BOOTSTRAP_SRC "internal/bootstrap.cm"
#define CELL_SHOP_DIR ".cell" #define CELL_SHOP_DIR ".cell"
#define CELL_CORE_DIR "packages/core" #define CELL_CORE_DIR "packages/core"
@@ -21,6 +20,7 @@
#include <signal.h> #include <signal.h>
#include <unistd.h> #include <unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include "monocypher.h"
/* Test suite declarations */ /* Test suite declarations */
int run_c_test_suite(JSContext *ctx); int run_c_test_suite(JSContext *ctx);
@@ -31,6 +31,83 @@ static char *shop_path = NULL;
static char *core_path = NULL; static char *core_path = NULL;
static JSRuntime *g_runtime = 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 // Get the home directory
static const char* get_home_dir(void) { static const char* get_home_dir(void) {
const char *home = getenv("HOME"); const char *home = getenv("HOME");
@@ -135,6 +212,37 @@ static char* load_core_file(const char *filename, size_t *out_size) {
return data; return data;
} }
// Try loading engine.cm from source-hash cache
// Returns heap-allocated binary data and sets *out_size, or NULL on cache miss
static char *try_engine_cache(size_t *out_size) {
size_t src_size;
char *src = load_core_file(ENGINE_SRC, &src_size);
if (!src) return NULL;
char *hex = compute_blake2_hex(src, src_size);
free(src);
char *cpath = build_cache_path(hex);
if (!cpath) { free(hex); return NULL; }
free(hex);
FILE *fh = fopen(cpath, "rb");
if (!fh) { free(cpath); return NULL; }
free(cpath);
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);
*out_size = file_size;
return data;
}
free(data);
fclose(fh);
return NULL;
}
// Get the core path for use by scripts // Get the core path for use by scripts
const char* cell_get_core_path(void) { const char* cell_get_core_path(void) {
return core_path; return core_path;
@@ -147,11 +255,8 @@ void actor_disrupt(cell_rt *crt)
actor_free(crt); actor_free(crt);
} }
JSValue js_os_use(JSContext *js); JSValue js_core_os_use(JSContext *js);
JSValue js_math_use(JSContext *js); JSValue js_core_json_use(JSContext *js);
JSValue js_json_use(JSContext *js);
JSValue js_nota_use(JSContext *js);
JSValue js_wota_use(JSContext *js);
void script_startup(cell_rt *prt) void script_startup(cell_rt *prt)
{ {
@@ -177,57 +282,100 @@ void script_startup(cell_rt *prt)
prt->actor_sym_ref.val = JS_NULL; prt->actor_sym_ref.val = JS_NULL;
cell_rt *crt = JS_GetContextOpaque(js); 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) // Try engine fast-path: load engine.cm from source-hash cache
size_t boot_size; size_t bin_size;
char *boot_data = load_core_file(BOOTSTRAP_MACH, &boot_size); char *bin_data = try_engine_cache(&bin_size);
int boot_is_mcode = 0;
if (!boot_data) { if (!bin_data) {
boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size); // Cold path: run bootstrap to seed cache, then retry
boot_is_mcode = 1; size_t boot_size;
} char *boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size);
if (!boot_data) { if (!boot_data) {
printf("ERROR: Could not load bootstrap from %s!\n", core_path); printf("ERROR: Could not load bootstrap from %s!\n", core_path);
return; return;
}
size_t boot_bin_size;
char *boot_bin = load_or_cache_bootstrap(boot_data, boot_size, &boot_bin_size);
free(boot_data);
if (!boot_bin) {
printf("ERROR: Failed to compile bootstrap mcode!\n");
return;
}
// Build env for bootstrap (only needs os, core_path, shop_path)
JSGCRef boot_env_ref;
JS_AddGCRef(js, &boot_env_ref);
boot_env_ref.val = JS_NewObject(js);
JSValue btmp;
btmp = js_core_os_use(js);
JS_SetPropertyStr(js, boot_env_ref.val, "os", btmp);
if (core_path) {
btmp = JS_NewString(js, core_path);
JS_SetPropertyStr(js, boot_env_ref.val, "core_path", btmp);
}
btmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
JS_SetPropertyStr(js, boot_env_ref.val, "shop_path", btmp);
JSValue boot_env = JS_Stone(js, boot_env_ref.val);
JS_DeleteGCRef(js, &boot_env_ref);
crt->state = ACTOR_RUNNING;
JSValue bv = JS_RunMachBin(js, (const uint8_t *)boot_bin, boot_bin_size, boot_env);
free(boot_bin);
uncaught_exception(js, bv);
crt->state = ACTOR_IDLE;
// Retry engine from cache
bin_data = try_engine_cache(&bin_size);
if (!bin_data) {
printf("ERROR: Bootstrap ran but engine.cm not in cache!\n");
return;
}
} }
// Create hidden environment // Build engine environment
JSValue hidden_env = JS_NewObject(js); JSGCRef env_ref;
JS_SetPropertyStr(js, hidden_env, "os", js_os_use(js)); JS_AddGCRef(js, &env_ref);
JS_SetPropertyStr(js, hidden_env, "json", js_json_use(js)); env_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js)); JSValue tmp;
JS_SetPropertyStr(js, hidden_env, "wota", js_wota_use(js)); 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); crt->actor_sym_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val)); JS_SetActorSym(js, 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) // Always set init (even if null)
if (crt->init_wota) { 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); free(crt->init_wota);
crt->init_wota = NULL; crt->init_wota = NULL;
} else { } 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) // 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) if (core_path) {
JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path)); tmp = JS_NewString(js, core_path);
JS_SetPropertyStr(js, hidden_env, "shop_path", JS_SetPropertyStr(js, env_ref.val, "core_path", tmp);
shop_path ? JS_NewString(js, shop_path) : JS_NULL); }
tmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
JS_SetPropertyStr(js, env_ref.val, "shop_path", tmp);
// Stone the environment // 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 engine from binary
crt->state = ACTOR_RUNNING; crt->state = ACTOR_RUNNING;
JSValue v = boot_is_mcode JSValue v = JS_RunMachBin(js, (const uint8_t *)bin_data, bin_size, hidden_env);
? JS_RunMachMcode(js, boot_data, boot_size, hidden_env) free(bin_data);
: JS_RunMachBin(js, (const uint8_t *)boot_data, boot_size, hidden_env);
free(boot_data);
uncaught_exception(js, v); uncaught_exception(js, v);
crt->state = ACTOR_IDLE; crt->state = ACTOR_IDLE;
set_actor_state(crt); set_actor_state(crt);
@@ -283,6 +431,7 @@ static void print_usage(const char *prog)
printf(" --core <path> Set core path directly (overrides CELL_CORE)\n"); printf(" --core <path> Set core path directly (overrides CELL_CORE)\n");
printf(" --shop <path> Set shop path (overrides CELL_SHOP)\n"); printf(" --shop <path> Set shop path (overrides CELL_SHOP)\n");
printf(" --dev Dev mode (shop=.cell, core=.)\n"); printf(" --dev Dev mode (shop=.cell, core=.)\n");
printf(" --heap <size> Initial heap size (e.g. 256MB, 1GB)\n");
printf(" --test [heap_size] Run C test suite\n"); printf(" --test [heap_size] Run C test suite\n");
printf(" -h, --help Show this help message\n"); printf(" -h, --help Show this help message\n");
printf("\nEnvironment:\n"); printf("\nEnvironment:\n");
@@ -313,8 +462,9 @@ int cell_init(int argc, char **argv)
return run_test_suite(heap_size); return run_test_suite(heap_size);
} }
/* Default: run script through bootstrap pipeline */ /* Default: run script through engine pipeline */
int arg_start = 1; int arg_start = 1;
size_t heap_size = 1024 * 1024; /* 1MB default */
const char *shop_override = NULL; const char *shop_override = NULL;
const char *core_override = NULL; const char *core_override = NULL;
@@ -334,6 +484,17 @@ int cell_init(int argc, char **argv)
} }
core_override = argv[arg_start + 1]; core_override = argv[arg_start + 1];
arg_start += 2; arg_start += 2;
} 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) { } else if (strcmp(argv[arg_start], "--dev") == 0) {
shop_override = ".cell"; shop_override = ".cell";
core_override = "."; core_override = ".";
@@ -359,28 +520,15 @@ int cell_init(int argc, char **argv)
actor_initialize(); actor_initialize();
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;
}
if (!boot_data) {
printf("ERROR: Could not load bootstrap from %s\n", core_path);
return 1;
}
g_runtime = JS_NewRuntime(); g_runtime = JS_NewRuntime();
if (!g_runtime) { if (!g_runtime) {
printf("Failed to create JS runtime\n"); printf("Failed to create JS runtime\n");
free(boot_data);
return 1; return 1;
} }
JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, 1024 * 1024); JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, heap_size);
if (!ctx) { if (!ctx) {
printf("Failed to create JS context\n"); printf("Failed to create JS context\n");
free(boot_data); JS_FreeRuntime(g_runtime); JS_FreeRuntime(g_runtime);
return 1; return 1;
} }
@@ -409,49 +557,122 @@ int cell_init(int argc, char **argv)
cli_rt->message_handle_ref.val = JS_NULL; cli_rt->message_handle_ref.val = JS_NULL;
cli_rt->unneeded_ref.val = JS_NULL; cli_rt->unneeded_ref.val = JS_NULL;
cli_rt->actor_sym_ref.val = JS_NewObject(ctx); cli_rt->actor_sym_ref.val = JS_NewObject(ctx);
JS_SetActorSym(ctx, JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
root_cell = cli_rt; 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);
for (int i = arg_start; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]);
JS_ArrayPush(ctx, &args_arr, str);
}
JS_SetPropertyStr(ctx, hidden_env, "args", args_arr);
hidden_env = JS_Stone(ctx, hidden_env);
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);
int exit_code = 0; int exit_code = 0;
if (JS_IsException(result)) {
JS_GetException(ctx); // Try engine fast-path: load engine.cm from source-hash cache
exit_code = 1; size_t bin_size;
} else if (!JS_IsNull(result)) { char *bin_data = try_engine_cache(&bin_size);
const char *str = JS_ToCString(ctx, result);
if (str) { if (!bin_data) {
printf("%s\n", str); // Cold path: run bootstrap to seed cache, then retry
JS_FreeCString(ctx, str); size_t boot_size;
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 1;
}
size_t boot_bin_size;
char *boot_bin = load_or_cache_bootstrap(boot_data, boot_size, &boot_bin_size);
free(boot_data);
if (!boot_bin) {
printf("ERROR: Failed to compile bootstrap mcode\n");
return 1;
}
// Build env for bootstrap (os, core_path, shop_path required;
// args, json, actorsym provided for compatibility)
JSGCRef boot_env_ref;
JS_AddGCRef(ctx, &boot_env_ref);
boot_env_ref.val = JS_NewObject(ctx);
JSValue btmp;
btmp = js_core_os_use(ctx);
JS_SetPropertyStr(ctx, boot_env_ref.val, "os", btmp);
btmp = JS_NewString(ctx, core_path);
JS_SetPropertyStr(ctx, boot_env_ref.val, "core_path", btmp);
btmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
JS_SetPropertyStr(ctx, boot_env_ref.val, "shop_path", btmp);
JS_SetPropertyStr(ctx, boot_env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
btmp = js_core_json_use(ctx);
JS_SetPropertyStr(ctx, boot_env_ref.val, "json", btmp);
JS_SetPropertyStr(ctx, boot_env_ref.val, "init", JS_NULL);
JSGCRef boot_args_ref;
JS_AddGCRef(ctx, &boot_args_ref);
boot_args_ref.val = JS_NewArray(ctx);
for (int i = arg_start; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]);
JS_ArrayPush(ctx, &boot_args_ref.val, str);
}
JS_SetPropertyStr(ctx, boot_env_ref.val, "args", boot_args_ref.val);
JS_DeleteGCRef(ctx, &boot_args_ref);
JSValue boot_env = JS_Stone(ctx, boot_env_ref.val);
JS_DeleteGCRef(ctx, &boot_env_ref);
JSValue boot_result = JS_RunMachBin(ctx, (const uint8_t *)boot_bin, boot_bin_size, boot_env);
free(boot_bin);
if (JS_IsException(boot_result)) {
JS_GetException(ctx);
printf("ERROR: Bootstrap failed\n");
return 1;
}
// Retry engine from cache (new-style bootstrap seeds it)
bin_data = try_engine_cache(&bin_size);
if (!bin_data) {
// Old-style bootstrap already ran the program — skip engine load
goto check_actors;
} }
} }
{
// Build engine environment
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_ref.val, str);
}
JS_SetPropertyStr(ctx, env_ref.val, "args", args_ref.val);
JS_DeleteGCRef(ctx, &args_ref);
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
JSValue result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env);
JS_DeleteGCRef(ctx, &env_ref);
free(bin_data);
if (JS_IsException(result)) {
JS_GetException(ctx);
exit_code = 1;
} else if (!JS_IsNull(result)) {
const char *str = JS_ToCString(ctx, result);
if (str) {
printf("%s\n", str);
JS_FreeCString(ctx, str);
}
}
}
check_actors:
if (scheduler_actor_count() > 0) { if (scheduler_actor_count() > 0) {
scheduler_enable_quiescence(); scheduler_enable_quiescence();
actor_loop(); actor_loop();

View File

@@ -9,7 +9,7 @@ extern "C" {
#endif #endif
// blob fns // 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); 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(JSContext *js, size_t *size, JSValue v); // bytes
void *js_get_blob_data_bits(JSContext *js, size_t *bits, JSValue v); // bits 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); JSValue nota2value(JSContext *js, void *nota);
void *value2nota(JSContext *js, JSValue v); void *value2nota(JSContext *js, JSValue v);
JSValue js_json_use(JSContext *js); JSValue js_core_json_use(JSContext *js);
JSValue js_nota_use(JSContext *js);
JSValue js_wota_use(JSContext *js);
#define CELL_HOOK_ENTER 1 #define CELL_HOOK_ENTER 1
#define CELL_HOOK_EXIT 2 #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])) #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 // Common macros for property access
#define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\ #define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\
JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \ 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)); \ JS_SetPropertyFunctionList(js, mod, FUNCS, countof(FUNCS)); \
return mod; } return mod; }
#define CELL_PROGRAM_INIT(c) \
JSValue CELL_USE_NAME(JSContext *js) { do { c ; } while(0); }
#ifdef __cplusplus #ifdef __cplusplus
} }

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,15 @@
#include "quickjs-internal.h" #include "quickjs-internal.h"
#include <math.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) */ /* Comparison op IDs (must match qbe.cm float_cmp_op_id values) */
enum { enum {
QBE_CMP_EQ = 0, 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); 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) { JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b) {
return qbe_float_binop(ctx, a, b, op_sub); return qbe_float_binop(ctx, a, b, op_sub);
} }
@@ -241,23 +260,56 @@ void cell_rt_store_index(JSContext *ctx, JSValue val, JSValue arr,
/* --- Intrinsic/global lookup --- */ /* --- Intrinsic/global lookup --- */
/* Native module environment — set before executing a native module's cell_main.
Contains runtime functions (starts_with, ends_with, etc.) and use(). */
static JSGCRef g_native_env_ref;
static int g_has_native_env = 0;
void cell_rt_set_native_env(JSContext *ctx, JSValue env) {
if (!JS_IsNull(env) && !JS_IsStone(env)) {
fprintf(stderr, "cell_rt_set_native_env: WARNING env not stone\n");
}
if (g_has_native_env)
JS_DeleteGCRef(ctx, &g_native_env_ref);
if (!JS_IsNull(env)) {
JS_AddGCRef(ctx, &g_native_env_ref);
g_native_env_ref.val = env;
g_has_native_env = 1;
} else {
g_has_native_env = 0;
}
}
JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) { JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) {
/* Check native env first (runtime-provided functions like starts_with) */
if (g_has_native_env) {
JSValue v = JS_GetPropertyStr(ctx, g_native_env_ref.val, name);
if (!JS_IsNull(v)) return v;
}
return JS_GetPropertyStr(ctx, ctx->global_obj, name); return JS_GetPropertyStr(ctx, ctx->global_obj, name);
} }
/* --- Closure access --- /* --- Closure access ---
Slot 511 in each frame stores a pointer to the enclosing frame. Slot 511 in each frame stores the magic ID (registry index) of the
Walking depth levels up the chain gives the target frame. */ function that owns this frame. cell_rt_get/put_closure re-derive
the enclosing frame from the function's GC ref at call time, so
pointers stay valid even if GC moves frames. */
#define QBE_FRAME_OUTER_SLOT 511 #define QBE_FRAME_OUTER_SLOT 511
static JSValue *derive_outer_fp(int magic);
JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth, JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth,
int64_t slot) { int64_t slot) {
JSValue *frame = (JSValue *)fp; JSValue *frame = (JSValue *)fp;
for (int64_t d = 0; d < depth; d++) { for (int64_t d = 0; d < depth; d++) {
void *outer = (void *)(uintptr_t)frame[QBE_FRAME_OUTER_SLOT]; /* fp[511] stores the magic ID (registry index) of the function
if (!outer) return JS_NULL; that owns this frame. derive_outer_fp re-derives the enclosing
frame = (JSValue *)outer; frame from the function's GC ref, so it's always current even
if GC moved the frame. */
int magic = (int)(int64_t)frame[QBE_FRAME_OUTER_SLOT];
frame = derive_outer_fp(magic);
if (!frame) return JS_NULL;
} }
return frame[slot]; return frame[slot];
} }
@@ -266,50 +318,134 @@ void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
int64_t slot) { int64_t slot) {
JSValue *frame = (JSValue *)fp; JSValue *frame = (JSValue *)fp;
for (int64_t d = 0; d < depth; d++) { for (int64_t d = 0; d < depth; d++) {
void *outer = (void *)(uintptr_t)frame[QBE_FRAME_OUTER_SLOT]; int magic = (int)(int64_t)frame[QBE_FRAME_OUTER_SLOT];
if (!outer) return; frame = derive_outer_fp(magic);
frame = (JSValue *)outer; if (!frame) return;
} }
frame[slot] = val; frame[slot] = val;
} }
/* --- GC-managed AOT frame stack ---
Each AOT function call pushes a GC ref so the GC can find and
update frame pointers when it moves objects. cell_rt_refresh_fp
re-derives the slot pointer after any GC-triggering call. */
#define MAX_AOT_DEPTH 256
static JSGCRef g_aot_gc_refs[MAX_AOT_DEPTH];
static int g_aot_depth = 0;
JSValue *cell_rt_enter_frame(JSContext *ctx, int64_t nr_slots) {
if (g_aot_depth >= MAX_AOT_DEPTH)
return NULL;
JSFrameRegister *frame = alloc_frame_register(ctx, (int)nr_slots);
if (!frame) return NULL;
JSGCRef *ref = &g_aot_gc_refs[g_aot_depth];
JS_AddGCRef(ctx, ref);
ref->val = JS_MKPTR(frame);
g_aot_depth++;
return (JSValue *)frame->slots;
}
JSValue *cell_rt_refresh_fp(JSContext *ctx) {
(void)ctx;
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(
g_aot_gc_refs[g_aot_depth - 1].val);
return (JSValue *)frame->slots;
}
void cell_rt_leave_frame(JSContext *ctx) {
g_aot_depth--;
JS_DeleteGCRef(ctx, &g_aot_gc_refs[g_aot_depth]);
}
/* --- Function creation and calling --- */ /* --- Function creation and calling --- */
typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp); typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
/* Table mapping fn_idx → outer_fp at creation time. /* Per-module function registry.
Valid for single-threaded, non-recursive closure patterns. */ Each native .cm module gets its own dylib. When a module creates closures
#define MAX_QBE_FUNCTIONS 256 via cell_rt_make_function, we record the dylib handle so the trampoline
static void *g_outer_fp[MAX_QBE_FUNCTIONS]; 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;
JSGCRef frame_ref; /* independent GC ref for enclosing frame */
int has_frame_ref;
} 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;
/* Derive the outer frame's slots pointer from the closure's own GC ref.
Each closure keeps an independent GC ref so the enclosing frame
survives even after cell_rt_leave_frame pops the stack ref. */
static JSValue *derive_outer_fp(int magic) {
if (!g_native_fn_registry[magic].has_frame_ref) return NULL;
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(
g_native_fn_registry[magic].frame_ref.val);
return (JSValue *)frame->slots;
}
static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val, static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val,
int argc, JSValue *argv, int magic) { int argc, JSValue *argv, int magic) {
if (magic < 0 || magic >= g_native_fn_count)
return JS_ThrowTypeError(ctx, "invalid native function id %d", magic);
void *handle = g_native_fn_registry[magic].dl_handle;
int fn_idx = g_native_fn_registry[magic].fn_idx;
char name[64]; char name[64];
snprintf(name, sizeof(name), "cell_fn_%d", magic); snprintf(name, sizeof(name), "cell_fn_%d", fn_idx);
cell_compiled_fn fn = (cell_compiled_fn)dlsym(RTLD_DEFAULT, name); cell_compiled_fn fn = (cell_compiled_fn)dlsym(handle, name);
if (!fn) 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 */ /* Allocate GC-managed frame: slot 0 = this, slots 1..argc = args */
JSValue frame[512]; JSValue *fp = cell_rt_enter_frame(ctx, 512);
memset(frame, 0, sizeof(frame)); if (!fp) return JS_EXCEPTION;
frame[0] = this_val; fp[0] = this_val;
for (int i = 0; i < argc && i < 510; i++) for (int i = 0; i < argc && i < 510; i++)
frame[1 + i] = argv[i]; fp[1 + i] = argv[i];
/* Link to outer frame for closure access */ /* Store the magic ID (registry index) so cell_rt_get/put_closure
if (magic >= 0 && magic < MAX_QBE_FUNCTIONS) can re-derive the enclosing frame from the GC ref at call time,
frame[QBE_FRAME_OUTER_SLOT] = (JSValue)(uintptr_t)g_outer_fp[magic]; surviving GC moves */
fp[QBE_FRAME_OUTER_SLOT] = (JSValue)(int64_t)magic;
return fn(ctx, frame); JSValue result = fn(ctx, fp);
cell_rt_leave_frame(ctx);
if (result == JS_EXCEPTION)
return JS_EXCEPTION;
return result;
} }
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp) { JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp) {
if (fn_idx >= 0 && fn_idx < MAX_QBE_FUNCTIONS) (void)outer_fp;
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;
/* Create independent GC ref so the enclosing frame survives
even after cell_rt_leave_frame pops the stack ref */
if (g_aot_depth > 0) {
JSGCRef *ref = &g_native_fn_registry[global_id].frame_ref;
JS_AddGCRef(ctx, ref);
ref->val = g_aot_gc_refs[g_aot_depth - 1].val;
g_native_fn_registry[global_id].has_frame_ref = 1;
} else {
g_native_fn_registry[global_id].has_frame_ref = 0;
}
return JS_NewCFunction2(ctx, (JSCFunction *)cell_fn_trampoline, "native_fn", 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 --- */ /* --- Frame-based function calling --- */
@@ -327,6 +463,7 @@ JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) {
} }
void cell_rt_setarg(JSValue frame_val, int64_t idx, JSValue val) { void cell_rt_setarg(JSValue frame_val, int64_t idx, JSValue val) {
if (frame_val == JS_EXCEPTION) return;
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val); JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
fr->slots[idx] = val; fr->slots[idx] = val;
} }
@@ -423,27 +560,123 @@ JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b) {
return JS_NewBool(ctx, a != 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;
}
/* --- Short-circuit and/or (non-allocating) --- */
JSValue cell_rt_and(JSContext *ctx, JSValue left, JSValue right) {
return JS_ToBool(ctx, left) ? right : left;
}
JSValue cell_rt_or(JSContext *ctx, JSValue left, JSValue right) {
return JS_ToBool(ctx, left) ? left : right;
}
/* --- Disruption --- */ /* --- Disruption --- */
void cell_rt_disrupt(JSContext *ctx) { void cell_rt_disrupt(JSContext *ctx) {
JS_ThrowTypeError(ctx, "type error in native code"); JS_ThrowTypeError(ctx, "type error in native code");
} }
/* --- Module entry point --- /* --- set_var: set a variable in env_record or global --- */
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. */
JSValue cell_rt_module_entry(JSContext *ctx) { void cell_rt_set_var(JSContext *ctx, JSValue val, const char *name) {
cell_compiled_fn fn = (cell_compiled_fn)dlsym(RTLD_DEFAULT, "cell_main"); JSValue key = JS_NewString(ctx, name);
if (!fn) JS_SetProperty(ctx, ctx->global_obj, key, val);
return JS_ThrowTypeError(ctx, "cell_main not found in loaded dylib"); }
/* Heap-allocate so closures created in cell_main can reference /* --- in: key in obj --- */
this frame after the module entry returns. */
JSValue *frame = calloc(512, sizeof(JSValue)); JSValue cell_rt_in(JSContext *ctx, JSValue key, JSValue obj) {
if (!frame) int has = JS_HasProperty(ctx, obj, key);
return JS_ThrowTypeError(ctx, "frame allocation failed"); return JS_NewBool(ctx, has > 0);
}
return fn(ctx, frame);
/* --- regexp: create regex from pattern and flags --- */
JSValue cell_rt_regexp(JSContext *ctx, const char *pattern, const char *flags) {
JSValue argv[2];
argv[0] = JS_NewString(ctx, pattern);
argv[1] = JS_NewString(ctx, flags);
JSValue re = js_regexp_constructor(ctx, JS_NULL, 2, argv);
if (JS_IsException(re))
return JS_EXCEPTION;
return re;
}
/* --- Module entry point ---
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_native_module_load(JSContext *ctx, void *dl_handle, JSValue env) {
cell_compiled_fn fn = (cell_compiled_fn)dlsym(dl_handle, "cell_main");
if (!fn)
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;
/* Make env available for cell_rt_get_intrinsic lookups */
cell_rt_set_native_env(ctx, env);
/* GC-managed frame for module execution */
JSValue *fp = cell_rt_enter_frame(ctx, 512);
if (!fp) {
g_current_dl_handle = prev_handle;
return JS_ThrowTypeError(ctx, "frame allocation failed");
}
JSValue result = fn(ctx, fp);
cell_rt_leave_frame(ctx); /* safe — closures have independent GC refs */
g_current_dl_handle = prev_handle;
if (result == JS_EXCEPTION)
return JS_EXCEPTION;
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, JSValue env) {
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;
/* Make env available for cell_rt_get_intrinsic lookups */
cell_rt_set_native_env(ctx, env);
JSValue *fp = cell_rt_enter_frame(ctx, 512);
if (!fp) {
g_current_dl_handle = prev_handle;
return JS_ThrowTypeError(ctx, "frame allocation failed");
}
JSValue result = fn(ctx, fp);
cell_rt_leave_frame(ctx); /* safe — closures have independent GC refs */
g_current_dl_handle = prev_handle;
if (result == JS_EXCEPTION)
return JS_EXCEPTION;
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, JS_NULL);
} }

View File

@@ -155,8 +155,9 @@ static const JSCFunctionListEntry js_actor_funcs[] = {
MIST_FUNC_DEF(actor, clock, 1), MIST_FUNC_DEF(actor, clock, 1),
}; };
JSValue js_actor_use(JSContext *js) { JSValue js_core_actor_use(JSContext *js) {
JSValue mod = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyFunctionList(js,mod,js_actor_funcs,countof(js_actor_funcs)); JS_ROOT(mod, JS_NewObject(js));
return mod; JS_SetPropertyFunctionList(js, mod.val, js_actor_funcs, countof(js_actor_funcs));
JS_RETURN(mod.val);
} }

View File

@@ -95,9 +95,12 @@
/* test the GC by forcing it before each object allocation */ /* test the GC by forcing it before each object allocation */
// #define FORCE_GC_AT_MALLOC // #define FORCE_GC_AT_MALLOC
#define POISON_HEAP #include <sys/mman.h>
/* POISON_HEAP: Use ASan's memory poisoning to detect stale pointer access */ #include <unistd.h>
#ifdef POISON_HEAP
/* HEAP_CHECK: validate heap pointers at JS_VALUE_GET_* macros */
// #define HEAP_CHECK
#if defined(__has_feature) #if defined(__has_feature)
#if __has_feature(address_sanitizer) #if __has_feature(address_sanitizer)
#define HAVE_ASAN 1 #define HAVE_ASAN 1
@@ -106,25 +109,6 @@
#define HAVE_ASAN 1 #define HAVE_ASAN 1
#endif #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 #ifdef HAVE_ASAN
static struct JSContext *__asan_js_ctx; static struct JSContext *__asan_js_ctx;
#endif #endif
@@ -303,14 +287,27 @@ typedef enum JSErrorEnum {
/* Forward declaration for bytecode freeing */ /* 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_ARRAY(v) ((JSArray *)chase (v))
#define JS_VALUE_GET_OBJ(v) ((JSRecord *)chase (v)) #define JS_VALUE_GET_OBJ(v) ((JSRecord *)chase (v))
#define JS_VALUE_GET_TEXT(v) ((JSText *)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_FUNCTION(v) ((JSFunction *)chase (v))
#define JS_VALUE_GET_FRAME(v) ((JSFrame *)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_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 */ /* Compatibility: JS_TAG_STRING is an alias for text type checks */
#define JS_TAG_STRING JS_TAG_STRING_IMM #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 #else
#define BUDDY_MIN_ORDER 9 /* 512B minimum on 32-bit */ #define BUDDY_MIN_ORDER 9 /* 512B minimum on 32-bit */
#endif #endif
#define BUDDY_MAX_ORDER 28 /* 256MB maximum */ #define BUDDY_MAX_LEVELS 40 /* supports pools up to 2^(BUDDY_MIN_ORDER+39) */
#define BUDDY_LEVELS (BUDDY_MAX_ORDER - BUDDY_MIN_ORDER + 1) #define BUDDY_DEFAULT_POOL (1ULL << 24) /* 16MB initial pool */
#define BUDDY_POOL_SIZE (1ULL << BUDDY_MAX_ORDER)
typedef struct BuddyBlock { typedef struct BuddyBlock {
struct BuddyBlock *next; struct BuddyBlock *next;
@@ -344,15 +340,26 @@ typedef struct BuddyBlock {
uint8_t is_free; uint8_t is_free;
} BuddyBlock; } 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 { typedef struct BuddyAllocator {
uint8_t *base; /* 256MB base address */ BuddyPool *pools; /* linked list, newest first */
size_t total_size; /* 256MB */ size_t next_pool_size; /* next pool doubles from this */
BuddyBlock *free_lists[BUDDY_LEVELS]; size_t initial_size; /* starting pool size */
uint8_t initialized; size_t cap; /* 0 = no cap */
size_t total_mapped; /* sum of all pool sizes */
} BuddyAllocator; } BuddyAllocator;
/* Forward declarations for buddy allocator functions */ /* Forward declarations for buddy allocator functions */
static void buddy_destroy (BuddyAllocator *b); static void buddy_destroy (BuddyAllocator *b);
static size_t buddy_max_block (BuddyAllocator *b);
/* controls a host of contexts, handing out memory and scheduling */ /* controls a host of contexts, handing out memory and scheduling */
struct JSRuntime { struct JSRuntime {
@@ -488,8 +495,8 @@ typedef enum MachOpcode {
MACH_MOD, /* R(A) = R(B) % R(C) */ MACH_MOD, /* R(A) = R(B) % R(C) */
MACH_POW, /* R(A) = R(B) ** R(C) */ MACH_POW, /* R(A) = R(B) ** R(C) */
MACH_NEG, /* R(A) = -R(B) */ MACH_NEG, /* R(A) = -R(B) */
MACH_INC, /* R(A) = R(B) + 1 */ MACH__DEAD_INC, /* reserved — was MACH_INC, never emitted */
MACH_DEC, /* R(A) = R(B) - 1 */ MACH__DEAD_DEC, /* reserved — was MACH_DEC, never emitted */
/* Generic comparison (ABC) — used by legacy .mach */ /* Generic comparison (ABC) — used by legacy .mach */
MACH_EQ, /* R(A) = (R(B) == R(C)) */ MACH_EQ, /* R(A) = (R(B) == R(C)) */
@@ -664,8 +671,8 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = {
[MACH_MOD] = "mod", [MACH_MOD] = "mod",
[MACH_POW] = "pow", [MACH_POW] = "pow",
[MACH_NEG] = "neg", [MACH_NEG] = "neg",
[MACH_INC] = "inc", [MACH__DEAD_INC] = "dead_inc",
[MACH_DEC] = "dec", [MACH__DEAD_DEC] = "dead_dec",
[MACH_EQ] = "eq", [MACH_EQ] = "eq",
[MACH_NEQ] = "neq", [MACH_NEQ] = "neq",
[MACH_LT] = "lt", [MACH_LT] = "lt",
@@ -838,6 +845,23 @@ static inline objhdr_t *chase(JSValue v) {
return oh; 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 *oh = (objhdr_t *)JS_VALUE_GET_PTR(*slot);
if (objhdr_type(*oh) == OBJ_FORWARD) {
do {
objhdr_t *next = (objhdr_t *)objhdr_fwd_ptr(*oh);
if (!next) break;
oh = next;
} while (objhdr_type(*oh) == OBJ_FORWARD);
*slot = JS_MKPTR(oh);
}
}
}
/* Inline type checks — use these in the VM dispatch loop to avoid /* Inline type checks — use these in the VM dispatch loop to avoid
function call overhead. The public API (JS_IsArray etc. in quickjs.h) function call overhead. The public API (JS_IsArray etc. in quickjs.h)
remains non-inline for external callers; those wrappers live in runtime.c. */ remains non-inline for external callers; those wrappers live in runtime.c. */
@@ -1063,6 +1087,11 @@ struct JSContext {
uint8_t *heap_end; /* end of block */ uint8_t *heap_end; /* end of block */
size_t current_block_size; /* current block size (64KB initially) */ size_t current_block_size; /* current block size (64KB initially) */
size_t next_block_size; /* doubles if <10% recovered after GC */ 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 */ /* Constant text pool — compilation constants */
uint8_t *ct_base; /* pool base */ uint8_t *ct_base; /* pool base */
@@ -1085,6 +1114,7 @@ struct JSContext {
JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */ JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */
JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */ 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 */ CCallRoot *c_call_root; /* stack of auto-rooted C call argv arrays */
int class_count; /* size of class_array and class_proto */ int class_count; /* size of class_array and class_proto */
@@ -1117,6 +1147,9 @@ struct JSContext {
JSValue current_exception; JSValue current_exception;
/* Actor identity key — used by wota/nota PRIVATE serialization */
JSValue actor_sym;
/* Stack overflow protection */ /* Stack overflow protection */
// todo: want this, but should be a simple increment/decrement counter while frames are pushed // todo: want this, but should be a simple increment/decrement counter while frames are pushed
size_t stack_depth; size_t stack_depth;
@@ -1178,9 +1211,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 */ /* Helper to check if a pointer is in constant text pool memory */
static inline int is_ct_ptr (JSContext *ctx, void *ptr) { 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 */ /* Intern a UTF-32 string as a stone text, returning a JSValue string */
/* Create a stoned, interned key from a UTF-8 C string. /* Create a stoned, interned key from a UTF-8 C string.
@@ -1214,8 +1266,6 @@ typedef struct JSRegExp {
#define obj_is_stone(rec) objhdr_s ((rec)->mist_hdr) #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 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 /* Get prototype from object (works for both JSRecord and JSRecord since they
* share layout) */ * share layout) */
#define JS_OBJ_GET_PROTO(p) (JS_IsNull(((JSRecord *)(p))->proto) ? NULL : (JSRecord *)JS_VALUE_GET_PTR(((JSRecord *)(p))->proto)) #define JS_OBJ_GET_PROTO(p) (JS_IsNull(((JSRecord *)(p))->proto) ? NULL : (JSRecord *)JS_VALUE_GET_PTR(((JSRecord *)(p))->proto))

View File

@@ -146,10 +146,22 @@ typedef struct JSGCRef {
struct JSGCRef *prev; struct JSGCRef *prev;
} JSGCRef; } JSGCRef;
/* JSLocalRef - GC updates C locals through pointers (OCaml-style) */
typedef struct JSLocalRef {
JSValue *ptr;
struct JSLocalRef *prev;
} JSLocalRef;
/* stack of JSGCRef */ /* stack of JSGCRef */
JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref); JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref);
JSValue JS_PopGCRef(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_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) #define JS_POP_VALUE(ctx, v) v = JS_PopGCRef(ctx, &v ## _ref)
@@ -305,12 +317,15 @@ typedef JSValue JSCFunctionData (JSContext *ctx, JSValue this_val,
JSRuntime *JS_NewRuntime (void); JSRuntime *JS_NewRuntime (void);
void JS_FreeRuntime (JSRuntime *rt); void JS_FreeRuntime (JSRuntime *rt);
void JS_SetMemoryLimit (JSRuntime *rt, size_t limit); 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_NewContext (JSRuntime *rt);
JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size); JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size);
void JS_FreeContext (JSContext *s); void JS_FreeContext (JSContext *s);
void *JS_GetContextOpaque (JSContext *ctx); void *JS_GetContextOpaque (JSContext *ctx);
void JS_SetContextOpaque (JSContext *ctx, void *opaque); void JS_SetContextOpaque (JSContext *ctx, void *opaque);
void JS_SetActorSym (JSContext *ctx, JSValue sym);
JSValue JS_GetActorSym (JSContext *ctx);
JSRuntime *JS_GetRuntime (JSContext *ctx); JSRuntime *JS_GetRuntime (JSContext *ctx);
/* use 0 to disable maximum stack size check */ /* use 0 to disable maximum stack size check */
void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size); void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size);
@@ -501,9 +516,11 @@ JSValue JS_NewObjectProtoClass (JSContext *ctx, JSValue proto, JSClassID class_i
JSValue JS_NewObjectClass (JSContext *ctx, int class_id); JSValue JS_NewObjectClass (JSContext *ctx, int class_id);
JSValue JS_NewObjectProto (JSContext *ctx, JSValue proto); JSValue JS_NewObjectProto (JSContext *ctx, JSValue proto);
JSValue JS_NewObject (JSContext *ctx); JSValue JS_NewObject (JSContext *ctx);
JSValue JS_NewObjectCap (JSContext *ctx, uint32_t n);
JSValue JS_NewArray (JSContext *ctx); JSValue JS_NewArray (JSContext *ctx);
JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len); 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); JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values);
int JS_ArrayPush (JSContext *ctx, JSValue *arr_ptr, JSValue val); int JS_ArrayPush (JSContext *ctx, JSValue *arr_ptr, JSValue val);
JSValue JS_ArrayPop (JSContext *ctx, JSValue obj); JSValue JS_ArrayPop (JSContext *ctx, JSValue obj);
@@ -573,7 +590,8 @@ JSValue JS_ParseJSON (JSContext *ctx, const char *buf, size_t buf_len,
JSValue JS_ParseJSON2 (JSContext *ctx, const char *buf, size_t buf_len, JSValue JS_ParseJSON2 (JSContext *ctx, const char *buf, size_t buf_len,
const char *filename, int flags); const char *filename, int flags);
JSValue JS_JSONStringify (JSContext *ctx, JSValue obj, JSValue JS_JSONStringify (JSContext *ctx, JSValue obj,
JSValue replacer, JSValue space0); JSValue replacer, JSValue space0,
JS_BOOL compact_arrays);
/* ============================================================ /* ============================================================
9. Intrinsic Wrappers (JS_Cell* / JS_Array*) 9. Intrinsic Wrappers (JS_Cell* / JS_Array*)

File diff suppressed because it is too large Load Diff

View File

@@ -310,7 +310,6 @@ void actor_free(cell_rt *actor)
int actor_count = lockless_shlen(actors); int actor_count = lockless_shlen(actors);
if (actor_count == 0) { if (actor_count == 0) {
fprintf(stderr, "all actors are dead\n");
pthread_mutex_lock(&engine.lock); pthread_mutex_lock(&engine.lock);
engine.shutting_down = 1; engine.shutting_down = 1;
pthread_cond_broadcast(&engine.wake_cond); pthread_cond_broadcast(&engine.wake_cond);

View File

@@ -1428,7 +1428,7 @@ TEST(stringify_json_object) {
obj_ref.val = JS_NewObject(ctx); obj_ref.val = JS_NewObject(ctx);
JSValue v1 = JS_NewInt32(ctx, 1); JSValue v1 = JS_NewInt32(ctx, 1);
JS_SetPropertyStr(ctx, obj_ref.val, "a", v1); JS_SetPropertyStr(ctx, obj_ref.val, "a", v1);
JSValue str = JS_JSONStringify(ctx, obj_ref.val, JS_NULL, JS_NULL); JSValue str = JS_JSONStringify(ctx, obj_ref.val, JS_NULL, JS_NULL, 0);
JS_PopGCRef(ctx, &obj_ref); JS_PopGCRef(ctx, &obj_ref);
ASSERT(JS_IsText(str)); ASSERT(JS_IsText(str));
const char *s = JS_ToCString(ctx, str); const char *s = JS_ToCString(ctx, str);
@@ -1444,7 +1444,7 @@ TEST(stringify_json_array) {
arr_ref.val = JS_NewArray(ctx); arr_ref.val = JS_NewArray(ctx);
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 1)); JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 1));
JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 2)); JS_ArrayPush(ctx, &arr_ref.val, JS_NewInt32(ctx, 2));
JSValue str = JS_JSONStringify(ctx, arr_ref.val, JS_NULL, JS_NULL); JSValue str = JS_JSONStringify(ctx, arr_ref.val, JS_NULL, JS_NULL, 0);
JS_PopGCRef(ctx, &arr_ref); JS_PopGCRef(ctx, &arr_ref);
ASSERT(JS_IsText(str)); ASSERT(JS_IsText(str));
const char *s = JS_ToCString(ctx, str); const char *s = JS_ToCString(ctx, str);

View File

@@ -16,4 +16,4 @@ var ast = parse(result.tokens, src, filename, tokenize)
var folded = fold(ast) var folded = fold(ast)
var compiled = mcode(folded) var compiled = mcode(folded)
var optimized = streamline(compiled) var optimized = streamline(compiled)
print(json.encode(optimized)) print(json.encode(optimized, true))

View File

@@ -60,6 +60,26 @@ var streamline = function(ir, log) {
is_record: T_RECORD 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 --- // --- Logging support ---
var ir_stats = null var ir_stats = null
@@ -119,49 +139,30 @@ var streamline = function(ir, log) {
return T_UNKNOWN return T_UNKNOWN
} }
// track_types reuses write_rules table; move handled specially
var track_types = function(slot_types, instr) { var track_types = function(slot_types, instr) {
var op = instr[0] var op = instr[0]
var rule = null
var src_type = null var src_type = null
if (op == "access") { var typ = null
slot_types[text(instr[1])] = access_value_type(instr[2]) if (op == "move") {
} else if (op == "int") { src_type = slot_types[instr[2]]
slot_types[text(instr[1])] = T_INT slot_types[instr[1]] = src_type != null ? src_type : T_UNKNOWN
} else if (op == "true" || op == "false") { return null
slot_types[text(instr[1])] = T_BOOL }
} else if (op == "null") { rule = write_rules[op]
slot_types[text(instr[1])] = T_NULL if (rule != null) {
} else if (op == "move") { typ = rule[1]
src_type = slot_types[text(instr[2])] if (typ == null) {
slot_types[text(instr[1])] = src_type != null ? src_type : T_UNKNOWN typ = access_value_type(instr[2])
} else if (op == "concat") { }
slot_types[text(instr[1])] = T_TEXT slot_types[instr[rule[0]]] = typ
} 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
} }
return null return null
} }
var slot_is = function(slot_types, slot, typ) { var slot_is = function(slot_types, slot, typ) {
var known = slot_types[text(slot)] var known = slot_types[slot]
if (known == null) { if (known == null) {
return false return false
} }
@@ -175,24 +176,22 @@ var streamline = function(ir, log) {
} }
var merge_backward = function(backward_types, slot, typ) { var merge_backward = function(backward_types, slot, typ) {
var sk = null
var existing = null var existing = null
if (!is_number(slot)) { if (!is_number(slot)) {
return null return null
} }
sk = text(slot) existing = backward_types[slot]
existing = backward_types[sk]
if (existing == null) { if (existing == null) {
backward_types[sk] = typ backward_types[slot] = typ
} else if (existing != typ && existing != T_UNKNOWN) { } else if (existing != typ && existing != T_UNKNOWN) {
if ((existing == T_INT || existing == T_FLOAT) && typ == T_NUM) { if ((existing == T_INT || existing == T_FLOAT) && typ == T_NUM) {
// Keep more specific backward_types[slot] = T_NUM
} else if (existing == T_NUM && (typ == T_INT || typ == T_FLOAT)) { } else if (existing == T_NUM && (typ == T_INT || typ == T_FLOAT)) {
backward_types[sk] = typ // Keep wider T_NUM
} else if ((existing == T_INT && typ == T_FLOAT) || (existing == T_FLOAT && typ == T_INT)) { } 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 { } else {
backward_types[sk] = T_UNKNOWN backward_types[slot] = T_UNKNOWN
} }
} }
return null return null
@@ -201,8 +200,8 @@ var streamline = function(ir, log) {
var seed_params = function(slot_types, param_types, nr_args) { var seed_params = function(slot_types, param_types, nr_args) {
var j = 1 var j = 1
while (j <= nr_args) { while (j <= nr_args) {
if (param_types[text(j)] != null) { if (param_types[j] != null) {
slot_types[text(j)] = param_types[text(j)] slot_types[j] = param_types[j]
} }
j = j + 1 j = j + 1
} }
@@ -210,10 +209,11 @@ var streamline = function(ir, log) {
} }
var seed_writes = function(slot_types, write_types) { var seed_writes = function(slot_types, write_types) {
var keys = array(write_types)
var k = 0 var k = 0
while (k < length(keys)) { while (k < length(write_types)) {
slot_types[keys[k]] = write_types[keys[k]] if (write_types[k] != null) {
slot_types[k] = write_types[k]
}
k = k + 1 k = k + 1
} }
return null return null
@@ -222,7 +222,26 @@ var streamline = function(ir, log) {
// ========================================================= // =========================================================
// Pass: infer_param_types — backward type inference // Pass: infer_param_types — backward type inference
// Scans typed operators to infer immutable parameter types. // 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 = {
add: [2, T_NUM, 3, T_NUM],
subtract: [2, T_NUM, 3, T_NUM], multiply: [2, T_NUM, 3, T_NUM],
divide: [2, T_NUM, 3, T_NUM], modulo: [2, T_NUM, 3, T_NUM],
pow: [2, T_NUM, 3, T_NUM], negate: [2, T_NUM],
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],
concat: [2, T_TEXT, 3, T_TEXT],
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 infer_param_types = function(func) {
var instructions = func.instructions var instructions = func.instructions
var nr_args = func.nr_args != null ? func.nr_args : 0 var nr_args = func.nr_args != null ? func.nr_args : 0
@@ -232,76 +251,36 @@ var streamline = function(ir, log) {
var i = 0 var i = 0
var j = 0 var j = 0
var instr = null var instr = null
var op = null
var bt = null var bt = null
var rule = null
if (instructions == null || nr_args == 0) { if (instructions == null || nr_args == 0) {
return {} return array(func.nr_slots)
} }
num_instr = length(instructions) num_instr = length(instructions)
backward_types = {} backward_types = array(func.nr_slots)
i = 0 i = 0
while (i < num_instr) { while (i < num_instr) {
instr = instructions[i] instr = instructions[i]
if (is_array(instr)) { if (is_array(instr)) {
op = instr[0] rule = param_rules[instr[0]]
if (op == "subtract" || op == "multiply" || if (rule != null) {
op == "divide" || op == "modulo" || op == "pow") { merge_backward(backward_types, instr[rule[0]], rule[1])
merge_backward(backward_types, instr[2], T_NUM) if (length(rule) > 2) {
merge_backward(backward_types, instr[3], T_NUM) merge_backward(backward_types, instr[rule[2]], rule[3])
} 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)
} }
} }
i = i + 1 i = i + 1
} }
param_types = {} param_types = array(func.nr_slots)
j = 1 j = 1
while (j <= nr_args) { while (j <= nr_args) {
bt = backward_types[text(j)] bt = backward_types[j]
if (bt != null && bt != T_UNKNOWN) { if (bt != null && bt != T_UNKNOWN) {
param_types[text(j)] = bt param_types[j] = bt
} }
j = j + 1 j = j + 1
} }
@@ -313,114 +292,85 @@ var streamline = function(ir, log) {
// Scans all instructions to find non-parameter slots where // Scans all instructions to find non-parameter slots where
// every write produces the same type. These types persist // every write produces the same type. These types persist
// across label join points. // 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_NUM], 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_NUM], subtract: [1, T_NUM], multiply: [1, T_NUM],
divide: [1, T_NUM], modulo: [1, T_NUM], pow: [1, T_NUM],
move: [1, T_UNKNOWN], load_field: [1, T_UNKNOWN],
load_index: [1, T_UNKNOWN], load_dynamic: [1, T_UNKNOWN],
pop: [1, T_UNKNOWN], get: [1, T_UNKNOWN],
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 infer_slot_write_types = function(func) {
var instructions = func.instructions var instructions = func.instructions
var nr_args = func.nr_args != null ? func.nr_args : 0 var nr_args = func.nr_args != null ? func.nr_args : 0
var num_instr = 0 var num_instr = 0
var write_types = null var write_types = null
var result = null
var keys = null
var i = 0 var i = 0
var k = 0 var k = 0
var instr = null var instr = null
var op = null
var slot = 0 var slot = 0
var typ = null var typ = null
var wt = null var rule = null
if (instructions == null) { if (instructions == null) {
return {} return array(func.nr_slots)
} }
num_instr = length(instructions) num_instr = length(instructions)
write_types = {} write_types = array(func.nr_slots)
i = 0 i = 0
while (i < num_instr) { while (i < num_instr) {
instr = instructions[i] instr = instructions[i]
if (!is_array(instr)) { if (is_array(instr)) {
i = i + 1 rule = write_rules[instr[0]]
continue 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 i = i + 1
} }
// Filter to only slots with known (non-unknown) types // Filter to only slots with known (non-unknown) types
result = {}
keys = array(write_types)
k = 0 k = 0
while (k < length(keys)) { while (k < length(write_types)) {
wt = write_types[keys[k]] if (write_types[k] == T_UNKNOWN) {
if (wt != null && wt != T_UNKNOWN) { write_types[k] = null
result[keys[k]] = wt
} }
k = k + 1 k = k + 1
} }
return result return write_types
} }
// ========================================================= // =========================================================
@@ -431,9 +381,8 @@ var streamline = function(ir, log) {
var eliminate_type_checks = function(func, param_types, write_types, log) { var eliminate_type_checks = function(func, param_types, write_types, log) {
var instructions = func.instructions var instructions = func.instructions
var nr_args = func.nr_args != null ? func.nr_args : 0 var nr_args = func.nr_args != null ? func.nr_args : 0
var has_params = false
var has_writes = false
var num_instr = 0 var num_instr = 0
var base_types = null
var slot_types = null var slot_types = null
var nc = 0 var nc = 0
var i = 0 var i = 0
@@ -460,35 +409,32 @@ var streamline = function(ir, log) {
} }
num_instr = length(instructions) num_instr = length(instructions)
// Pre-compute base types: params + write-invariant types
base_types = array(func.nr_slots)
j = 1 j = 1
while (j <= nr_args) { while (j <= nr_args) {
if (param_types[text(j)] != null) { if (param_types[j] != null) {
has_params = true 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 j = j + 1
} }
has_writes = length(array(write_types)) > 0
slot_types = {} slot_types = array(base_types)
if (has_params) {
seed_params(slot_types, param_types, nr_args)
}
if (has_writes) {
seed_writes(slot_types, write_types)
}
i = 0 i = 0
while (i < num_instr) { while (i < num_instr) {
instr = instructions[i] instr = instructions[i]
if (is_text(instr)) { if (is_text(instr)) {
slot_types = {} slot_types = array(base_types)
if (has_params) {
seed_params(slot_types, param_types, nr_args)
}
if (has_writes) {
seed_writes(slot_types, write_types)
}
i = i + 1 i = i + 1
continue continue
} }
@@ -525,14 +471,14 @@ var streamline = function(ir, log) {
at: i, at: i,
before: [instr, next], before: [instr, next],
after: [instructions[i], instructions[i + 1]], 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 i = i + 2
continue 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 (src_known != null && src_known != T_UNKNOWN && src_known != checked_type) {
if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) { if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) {
nc = nc + 1 nc = nc + 1
@@ -550,7 +496,14 @@ var streamline = function(ir, log) {
why: {slot: src, known_type: src_known, checked_type: checked_type} 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
}
if ((checked_type == T_INT || checked_type == T_FLOAT) && src_known == T_NUM) {
// T_NUM could be int or float — not a mismatch, keep check
slot_types[dest] = T_BOOL
slot_types[src] = checked_type
i = i + 2 i = i + 2
continue continue
} }
@@ -569,12 +522,12 @@ var streamline = function(ir, log) {
why: {slot: src, known_type: src_known, checked_type: checked_type} 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 i = i + 2
continue continue
} }
slot_types[text(dest)] = T_BOOL slot_types[dest] = T_BOOL
slot_types[text(src)] = checked_type slot_types[src] = checked_type
i = i + 2 i = i + 2
continue continue
} }
@@ -594,14 +547,14 @@ var streamline = function(ir, log) {
at: i, at: i,
before: [instr, next], before: [instr, next],
after: [instructions[i], instructions[i + 1]], 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 i = i + 2
continue 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 (src_known != null && src_known != T_UNKNOWN && src_known != checked_type) {
if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) { if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) {
nc = nc + 1 nc = nc + 1
@@ -619,7 +572,13 @@ var streamline = function(ir, log) {
why: {slot: src, known_type: src_known, checked_type: checked_type} 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
}
if ((checked_type == T_INT || checked_type == T_FLOAT) && src_known == T_NUM) {
// T_NUM could be int or float — not a mismatch, keep check
slot_types[dest] = T_BOOL
i = i + 2 i = i + 2
continue continue
} }
@@ -638,17 +597,17 @@ var streamline = function(ir, log) {
why: {slot: src, known_type: src_known, checked_type: checked_type} 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 i = i + 2
continue continue
} }
slot_types[text(dest)] = T_BOOL slot_types[dest] = T_BOOL
i = i + 2 i = i + 2
continue continue
} }
} }
slot_types[text(dest)] = T_BOOL slot_types[dest] = T_BOOL
i = i + 1 i = i + 1
continue continue
} }
@@ -664,7 +623,7 @@ var streamline = function(ir, log) {
pass: "eliminate_type_checks", pass: "eliminate_type_checks",
rule: "dynamic_to_field", rule: "dynamic_to_field",
at: i, before: old_op, after: instr[0], 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)) { } else if (slot_is(slot_types, instr[3], T_INT)) {
@@ -675,11 +634,11 @@ var streamline = function(ir, log) {
pass: "eliminate_type_checks", pass: "eliminate_type_checks",
rule: "dynamic_to_index", rule: "dynamic_to_index",
at: i, before: old_op, after: instr[0], 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 i = i + 1
continue continue
} }
@@ -693,7 +652,7 @@ var streamline = function(ir, log) {
pass: "eliminate_type_checks", pass: "eliminate_type_checks",
rule: "dynamic_to_field", rule: "dynamic_to_field",
at: i, before: old_op, after: instr[0], 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)) { } else if (slot_is(slot_types, instr[3], T_INT)) {
@@ -704,7 +663,7 @@ var streamline = function(ir, log) {
pass: "eliminate_type_checks", pass: "eliminate_type_checks",
rule: "dynamic_to_index", rule: "dynamic_to_index",
at: i, before: old_op, after: instr[0], 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 +704,14 @@ var streamline = function(ir, log) {
} }
num_instr = length(instructions) num_instr = length(instructions)
slot_values = {} slot_values = array(func.nr_slots)
i = 0 i = 0
while (i < num_instr) { while (i < num_instr) {
instr = instructions[i] instr = instructions[i]
if (is_text(instr)) { if (is_text(instr)) {
slot_values = {} slot_values = array(func.nr_slots)
i = i + 1 i = i + 1
continue continue
} }
@@ -766,28 +725,25 @@ var streamline = function(ir, log) {
// Track known constant values // Track known constant values
if (op == "int") { if (op == "int") {
slot_values[text(instr[1])] = instr[2] slot_values[instr[1]] = instr[2]
} else if (op == "access" && is_number(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") { } else if (op == "true") {
slot_values[text(instr[1])] = true slot_values[instr[1]] = true
} else if (op == "false") { } else if (op == "false") {
slot_values[text(instr[1])] = false slot_values[instr[1]] = false
} else if (op == "move") { } else if (op == "move") {
sv = slot_values[text(instr[2])] sv = slot_values[instr[2]]
if (sv != null) { if (sv != null) {
slot_values[text(instr[1])] = sv slot_values[instr[1]] = sv
} else { } else {
slot_values[text(instr[1])] = null slot_values[instr[1]] = null
} }
} }
// Same-slot comparisons // Same-slot comparisons
if (is_number(instr[2]) && instr[2] == instr[3]) { if (is_number(instr[2]) && instr[2] == instr[3]) {
if (op == "eq_int" || op == "eq_float" || op == "eq_text" || if (self_true_ops[op] == true) {
op == "eq_bool" || op == "is_identical" ||
op == "le_int" || op == "le_float" || op == "le_text" ||
op == "ge_int" || op == "ge_float" || op == "ge_text") {
instructions[i] = ["true", instr[1], instr[ilen - 2], instr[ilen - 1]] instructions[i] = ["true", instr[1], instr[ilen - 2], instr[ilen - 1]]
if (events != null) { if (events != null) {
events[] = { events[] = {
@@ -797,14 +753,11 @@ var streamline = function(ir, log) {
why: {op: op, slot: instr[2]} why: {op: op, slot: instr[2]}
} }
} }
slot_values[text(instr[1])] = true slot_values[instr[1]] = true
i = i + 1 i = i + 1
continue continue
} }
if (op == "ne_int" || op == "ne_float" || op == "ne_text" || if (self_false_ops[op] == true) {
op == "ne_bool" ||
op == "lt_int" || op == "lt_float" || op == "lt_text" ||
op == "gt_int" || op == "gt_float" || op == "gt_text") {
instructions[i] = ["false", instr[1], instr[ilen - 2], instr[ilen - 1]] instructions[i] = ["false", instr[1], instr[ilen - 2], instr[ilen - 1]]
if (events != null) { if (events != null) {
events[] = { events[] = {
@@ -814,7 +767,7 @@ var streamline = function(ir, log) {
why: {op: op, slot: instr[2]} why: {op: op, slot: instr[2]}
} }
} }
slot_values[text(instr[1])] = false slot_values[instr[1]] = false
i = i + 1 i = i + 1
continue continue
} }
@@ -822,15 +775,10 @@ var streamline = function(ir, log) {
// Clear value tracking for dest-producing ops (not reads-only) // Clear value tracking for dest-producing ops (not reads-only)
if (op == "invoke" || op == "tail_invoke") { if (op == "invoke" || op == "tail_invoke") {
slot_values[text(instr[2])] = null slot_values[instr[2]] = null
} else if (op != "int" && op != "access" && op != "true" && } else if (no_clear_ops[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") {
if (is_number(instr[1])) { if (is_number(instr[1])) {
slot_values[text(instr[1])] = null slot_values[instr[1]] = null
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -418,9 +418,9 @@ function run_tests(package_name, specific_test) {
var src_path = prefix + '/' + f var src_path = prefix + '/' + f
var src = text(fd.slurp(src_path)) var src = text(fd.slurp(src_path))
var ast = analyze(src, src_path) var ast = analyze(src, src_path)
test_mod_noopt = run_ast_noopt_fn(mod_path + '_noopt', ast, { test_mod_noopt = run_ast_noopt_fn(mod_path + '_noopt', ast, stone({
use: function(path) { return shop.use(path, use_pkg) } use: function(path) { return shop.use(path, use_pkg) }
}) }))
} disruption { } disruption {
log.console(` DIFF: failed to load noopt module for ${f}`) log.console(` DIFF: failed to load noopt module for ${f}`)
} }

76
tests/build_audit.cm Normal file
View 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
}
}

Some files were not shown because too many files have changed in this diff Show More