18 Commits

Author SHA1 Message Date
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
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
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
66 changed files with 665209 additions and 129799 deletions

View File

@@ -103,6 +103,7 @@ var v = a[] // pop: v is 3, a is [1, 2]
- Most files don't have headers; files in a package are not shared between packages
- No undefined in C API: use `JS_IsNull` and `JS_NULL` only
- A C file with correct macros (`CELL_USE_FUNCS` etc) is loaded as a module by its name (e.g., `png.c` in a package → `use('<package>/png')`)
- 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

View File

@@ -11,7 +11,7 @@
CELL_SHOP = $(HOME)/.cell
CELL_CORE_PACKAGE = $(CELL_SHOP)/packages/core
# .cm sources that compile to .mach bytecode
# .cm sources that compile to .mcode bytecode
MACH_SOURCES = tokenize.cm parse.cm fold.cm mcode.cm \
internal/bootstrap.cm internal/engine.cm

View File

@@ -381,19 +381,21 @@ static const JSCFunctionListEntry js_reader_funcs[] = {
JSValue js_miniz_use(JSContext *js)
{
JS_FRAME(js);
JS_NewClassID(&js_reader_class_id);
JS_NewClass(js, js_reader_class_id, &js_reader_class);
JSValue reader_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, reader_proto, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_reader_class_id, reader_proto);
JS_ROOT(reader_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, reader_proto.val, js_reader_funcs, sizeof(js_reader_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_reader_class_id, reader_proto.val);
JS_NewClassID(&js_writer_class_id);
JS_NewClass(js, js_writer_class_id, &js_writer_class);
JSValue writer_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, writer_proto, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_writer_class_id, writer_proto);
JSValue export = JS_NewObject(js);
JS_SetPropertyFunctionList(js, export, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry));
return export;
JS_ROOT(writer_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, writer_proto.val, js_writer_funcs, sizeof(js_writer_funcs) / sizeof(JSCFunctionListEntry));
JS_SetClassProto(js, js_writer_class_id, writer_proto.val);
JS_ROOT(export, JS_NewObject(js));
JS_SetPropertyFunctionList(js, export.val, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry));
JS_RETURN(export.val);
}

11449
boot/bootstrap.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

40579
boot/engine.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

105949
boot/fold.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

133321
boot/mcode.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

148985
boot/parse.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

15481
boot/qbe.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

63737
boot/qbe_emit.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

153
boot/seed_bootstrap.cm Normal file
View File

@@ -0,0 +1,153 @@
// seed_bootstrap.cm — minimal bootstrap for regenerating boot files
// Loads only the compiler pipeline, runs a script directly (no engine/actors)
// Usage: ./cell --dev --seed regen
//
// Hidden env: os, core_path, shop_path, args, json
var load_internal = os.load_internal
var fd = load_internal("js_fd_use")
var use_cache = {}
use_cache['fd'] = fd
use_cache['os'] = os
use_cache['json'] = json
function use_basic(path) {
if (use_cache[path])
return use_cache[path]
var result = load_internal("js_" + replace(path, '/', '_') + "_use")
if (result) {
use_cache[path] = result
return result
}
return null
}
// Load a module from boot .mcode — no caching, just eval
function boot_load(name) {
var mcode_path = core_path + '/boot/' + name + ".cm.mcode"
var mcode_json = null
if (!fd.is_file(mcode_path)) {
print("seed: missing boot mcode: " + mcode_path + "\n")
disrupt
}
mcode_json = text(fd.slurp(mcode_path))
return mach_eval_mcode(name, mcode_json, {use: use_basic})
}
var tokenize_mod = boot_load("tokenize")
var parse_mod = boot_load("parse")
var fold_mod = boot_load("fold")
var mcode_mod = boot_load("mcode")
var streamline_mod = boot_load("streamline")
use_cache['tokenize'] = tokenize_mod
use_cache['parse'] = parse_mod
use_cache['fold'] = fold_mod
use_cache['mcode'] = mcode_mod
use_cache['streamline'] = streamline_mod
function analyze(src, filename) {
var tok_result = tokenize_mod(src, filename)
var ast = parse_mod(tok_result.tokens, src, filename, tokenize_mod)
var _i = 0
var e = null
var has_errors = ast.errors != null && length(ast.errors) > 0
if (has_errors) {
while (_i < length(ast.errors)) {
e = ast.errors[_i]
if (e.line != null) {
print(`${filename}:${text(e.line)}:${text(e.column)}: error: ${e.message}`)
} else {
print(`${filename}: error: ${e.message}`)
}
_i = _i + 1
}
disrupt
}
return fold_mod(ast)
}
function run_ast(name, ast, env) {
var compiled = mcode_mod(ast)
var optimized = streamline_mod(compiled)
var mcode_json = json.encode(optimized)
return mach_eval_mcode(name, mcode_json, env)
}
function use_fn(path) {
var result = null
var file_path = null
var script = null
var ast = null
var mcode_path = null
var mcode_json = null
if (use_cache[path])
return use_cache[path]
// Try C embed
result = load_internal("js_" + replace(path, '/', '_') + "_use")
if (result) {
use_cache[path] = result
return result
}
// Try boot mcode
mcode_path = core_path + '/boot/' + path + '.cm.mcode'
if (fd.is_file(mcode_path)) {
mcode_json = text(fd.slurp(mcode_path))
result = mach_eval_mcode(path, mcode_json, {use: use_fn})
use_cache[path] = result
return result
}
// Try .cm source (CWD then core)
file_path = path + '.cm'
if (!fd.is_file(file_path))
file_path = core_path + '/' + path + '.cm'
if (fd.is_file(file_path)) {
script = text(fd.slurp(file_path))
ast = analyze(script, file_path)
result = run_ast(path, ast, {use: use_fn})
use_cache[path] = result
return result
}
print("seed: module not found: " + path + "\n")
disrupt
}
// Run the program from args
var program = args[0]
var user_args = []
var _j = 1
var prog_path = null
var script = null
var ast = null
if (!program) {
print("seed: no program specified\n")
disrupt
}
while (_j < length(args)) {
push(user_args, args[_j])
_j = _j + 1
}
prog_path = program + '.ce'
if (!fd.is_file(prog_path))
prog_path = core_path + '/' + program + '.ce'
if (!fd.is_file(prog_path)) {
prog_path = program + '.cm'
if (!fd.is_file(prog_path))
prog_path = core_path + '/' + program + '.cm'
}
if (!fd.is_file(prog_path)) {
print("seed: program not found: " + program + "\n")
disrupt
}
script = text(fd.slurp(prog_path))
ast = analyze(script, prog_path)
run_ast(program, ast, {use: use_fn, args: user_args})

6295
boot/seed_bootstrap.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

56261
boot/streamline.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

55549
boot/tokenize.cm.mcode Normal file

File diff suppressed because it is too large Load Diff

24647
boot/verify_ir.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
}

365
build.cm
View File

@@ -214,36 +214,22 @@ function compute_link_key(objects, ldflags, target_ldflags, target, cc) {
return content_hash(text(parts, '\n'))
}
// Build a dynamic library for a package
// Output goes to .cell/lib/<package_name>.<ext>
// Dynamic libraries do NOT link against core; undefined symbols are resolved at dlopen time
// Uses content-addressed store + symlink for caching
Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildtype = 'release') {
var objects = Build.build_package(pkg, target, true, buildtype) // exclude main.c
// Build a per-module dynamic library for a single C file
// Returns the content-addressed dylib path in .cell/build/<hash>.<target>.dylib
Build.build_module_dylib = function(pkg, file, target, buildtype) {
var _target = target || Build.detect_host_target()
var _buildtype = buildtype || 'release'
var obj = Build.compile_file(pkg, file, _target, _buildtype)
if (length(objects) == 0) {
log.console('No C files in ' + pkg)
return null
}
var lib_dir = shop.get_lib_dir()
var store_dir = lib_dir + '/store'
ensure_dir(lib_dir)
ensure_dir(store_dir)
var lib_name = shop.lib_name_for_package(pkg)
var dylib_ext = toolchains[target].system == 'windows' ? '.dll' : (toolchains[target].system == 'darwin' ? '.dylib' : '.so')
var stable_path = lib_dir + '/' + lib_name + dylib_ext
// Get link flags (with sigil replacement)
var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', target))
var target_ldflags = toolchains[target].c_link_args || []
var cc = toolchains[target].cpp || toolchains[target].c
var pkg_dir = shop.get_package_dir(pkg)
var tc = toolchains[_target]
var dylib_ext = tc.system == 'windows' ? '.dll' : (tc.system == 'darwin' ? '.dylib' : '.so')
var cc = tc.cpp || tc.c
var local_dir = get_local_dir()
var tc = toolchains[target]
var pkg_dir = shop.get_package_dir(pkg)
// Resolve relative -L paths in ldflags for hash computation
// Get link flags
var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', _target))
var target_ldflags = tc.c_link_args || []
var resolved_ldflags = []
arrfor(ldflags, function(flag) {
if (starts_with(flag, '-L') && !starts_with(flag, '-L/')) {
@@ -252,36 +238,21 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty
push(resolved_ldflags, flag)
})
// Compute link key
var link_key = compute_link_key(objects, resolved_ldflags, target_ldflags, target, cc)
var store_path = store_dir + '/' + lib_name + '-' + link_key + dylib_ext
// Content-addressed output: hash of (object + link flags + target)
var link_key = compute_link_key([obj], resolved_ldflags, target_ldflags, _target, cc)
var build_dir = get_build_dir()
ensure_dir(build_dir)
var dylib_path = build_dir + '/' + link_key + '.' + _target + dylib_ext
// Check if already linked in store
if (fd.is_file(store_path)) {
// Ensure symlink points to the store file
if (fd.is_link(stable_path)) {
var current_target = fd.readlink(stable_path)
if (current_target == store_path) {
// Already up to date
return stable_path
}
fd.unlink(stable_path)
} else if (fd.is_file(stable_path)) {
fd.unlink(stable_path)
}
fd.symlink(store_path, stable_path)
return stable_path
}
if (fd.is_file(dylib_path))
return dylib_path
// Build link command
var cmd_parts = [cc, '-shared', '-fPIC']
// Platform-specific flags for undefined symbols (resolved at dlopen) and size optimization
if (tc.system == 'darwin') {
cmd_parts = array(cmd_parts, [
'-undefined', 'dynamic_lookup',
'-Wl,-dead_strip',
'-Wl,-install_name,' + stable_path,
'-Wl,-rpath,@loader_path/../local',
'-Wl,-rpath,' + local_dir
])
@@ -293,41 +264,54 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty
'-Wl,-rpath,' + local_dir
])
} else if (tc.system == 'windows') {
// Windows DLLs: use --allow-shlib-undefined for mingw
push(cmd_parts, '-Wl,--allow-shlib-undefined')
}
// Add .cell/local to library search path
push(cmd_parts, '-L"' + local_dir + '"')
arrfor(objects, function(obj) {
push(cmd_parts, '"' + obj + '"')
})
// Do NOT link against core library - symbols resolved at dlopen time
push(cmd_parts, '"' + obj + '"')
cmd_parts = array(cmd_parts, resolved_ldflags)
cmd_parts = array(cmd_parts, target_ldflags)
push(cmd_parts, '-o')
push(cmd_parts, '"' + store_path + '"')
push(cmd_parts, '"' + dylib_path + '"')
var cmd_str = text(cmd_parts, ' ')
log.console('Linking ' + lib_name + dylib_ext)
log.console('Linking module ' + file + ' -> ' + fd.basename(dylib_path))
var ret = os.system(cmd_str)
if (ret != 0) {
throw Error('Linking failed: ' + pkg)
print('Linking failed: ' + file); disrupt
}
// Update symlink to point to the new store file
if (fd.is_link(stable_path)) {
fd.unlink(stable_path)
} else if (fd.is_file(stable_path)) {
fd.unlink(stable_path)
}
fd.symlink(store_path, stable_path)
return dylib_path
}
return stable_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 = []
var manifest = {}
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})
manifest[sym_name] = dylib
})
// Write manifest so the loader can find per-module dylibs by symbol
if (length(results) > 0) {
var lib_dir = shop.get_lib_dir()
ensure_dir(lib_dir)
var lib_name = shop.lib_name_for_package(pkg)
var manifest_path = lib_dir + '/' + lib_name + '.manifest.json'
var json = use('json')
fd.slurpwrite(manifest_path, stone(blob(json.encode(manifest))))
}
return results
}
// ============================================================================
@@ -412,43 +396,236 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out
return output
}
// ============================================================================
// 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) {
var _target = target || Build.detect_host_target()
var _buildtype = buildtype || 'release'
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 il = qbe_emit(optimized, qbe_macros)
// 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)) {
var 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))
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
}
// ============================================================================
// Convenience functions
// ============================================================================
// Build dynamic libraries for all installed packages
Build.build_all_dynamic = function(target, buildtype = 'release') {
target = target || Build.detect_host_target()
Build.build_all_dynamic = function(target, buildtype) {
var _target = target || Build.detect_host_target()
var _buildtype = buildtype || 'release'
var packages = shop.list_packages()
var results = []
// Build core first
if (find(packages, 'core') != null) {
try {
var lib = Build.build_dynamic('core', target, buildtype)
push(results, { package: 'core', library: lib })
} catch (e) {
log.error('Failed to build core: ' + text(e))
push(results, { package: 'core', error: e })
}
if (find(packages, function(p) { return p == 'core' }) != null) {
var core_mods = Build.build_dynamic('core', _target, _buildtype)
push(results, {package: 'core', modules: core_mods})
}
// Build other packages
arrfor(packages, function(pkg) {
if (pkg == 'core') return
try {
var lib = Build.build_dynamic(pkg, target, buildtype)
push(results, { package: pkg, library: lib })
} catch (e) {
log.error('Failed to build ' + pkg + ': ')
log.console(e.message)
log.console(e.stack)
push(results, { package: pkg, error: e })
}
var pkg_mods = Build.build_dynamic(pkg, _target, _buildtype)
push(results, {package: pkg, modules: pkg_mods})
})
return results
}

View File

@@ -240,7 +240,8 @@ static const JSCFunctionListEntry js_crypto_funcs[] = {
JSValue js_crypto_use(JSContext *js)
{
JSValue obj = JS_NewObject(js);
JS_SetPropertyFunctionList(js, obj, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0]));
return obj;
JS_FRAME(js);
JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0]));
JS_RETURN(mod.val);
}

View File

@@ -22,7 +22,8 @@ static const JSCFunctionListEntry js_debug_funcs[] = {
};
JSValue js_debug_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_debug_funcs,countof(js_debug_funcs));
return mod;
JS_FRAME(js);
JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_debug_funcs, countof(js_debug_funcs));
JS_RETURN(mod.val);
}

View File

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

View File

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

169
docs/shop.md Normal file
View File

@@ -0,0 +1,169 @@
---
title: "Shop Architecture"
description: "How the shop resolves, compiles, caches, and loads modules"
weight: 35
type: "docs"
---
The shop is the module resolution and loading engine behind `use()`. It handles finding modules, compiling them, caching the results, and loading C extensions. The shop lives in `internal/shop.cm`.
## Startup Pipeline
When `pit` runs a program, three layers bootstrap in sequence:
```
bootstrap.cm → engine.cm → shop.cm → user program
```
**bootstrap.cm** loads the compiler toolchain (tokenize, parse, fold, mcode, streamline) from pre-compiled bytecode. It defines `analyze()` (source to AST) and `compile_to_blob()` (AST to binary blob). It then loads engine.cm.
**engine.cm** creates the actor runtime (`$_`), defines `use_core()` for loading core modules, and populates the environment that shop receives. It then loads shop.cm via `use_core('internal/shop')`.
**shop.cm** receives its dependencies through the module environment — `analyze`, `run_ast_fn`, `use_cache`, `shop_path`, `runtime_env`, `content_hash`, `cache_path`, and others. It defines `Shop.use()`, which is the function behind every `use()` call in user code.
## Module Resolution
When `use('path')` is called from a package context, the shop resolves the module through a multi-layer search. Both the `.cm` script file and C symbol are resolved independently, and the one with the narrowest scope wins.
### Resolution Order
For a call like `use('sprite')` from package `myapp`:
1. **Own package**`~/.pit/packages/myapp/sprite.cm` and C symbol `js_myapp_sprite_use`
2. **Aliased dependencies** — if `myapp/pit.toml` has `renderer = "gitea.pockle.world/john/renderer"`, checks `renderer/sprite.cm` and its C symbols
3. **Core** — built-in core modules and internal C symbols
For calls without a package context (from core modules), only core is searched.
### Private Modules
Paths starting with `internal/` are private to their package:
```javascript
use('internal/helpers') // OK from within the same package
// Cannot be accessed from other packages
```
### Explicit Package Imports
Paths containing a dot in the first component are treated as explicit package references:
```javascript
use('gitea.pockle.world/john/renderer/sprite')
// Resolves directly to the renderer package's sprite.cm
```
## Compilation and Caching
Every module goes through a content-addressed caching pipeline. The cache key is the BLAKE2 hash of the source content, so changing the source automatically invalidates the cache.
### Cache Hierarchy
When loading a module, the shop checks (in order):
1. **In-memory cache**`use_cache[key]`, checked first on every `use()` call
2. **Native dylib** — pre-compiled platform-specific `.dylib` in the content-addressed store
3. **Cached .mach blob** — binary bytecode in `~/.pit/build/<hash>.mach`
4. **Cached .mcode IR** — JSON IR in `~/.pit/build/<hash>.mcode`
5. **Adjacent .mach/.mcode** — files alongside the source (e.g., `sprite.mach`)
6. **Source compilation** — full pipeline: analyze, mcode, streamline, serialize
Results from steps 4-6 are cached back to the content-addressed store for future loads.
### Content-Addressed Store
All cached artifacts live in `~/.pit/build/` named by the BLAKE2 hash of their source content:
```
~/.pit/build/
├── a1b2c3d4...mach # compiled bytecode blob
├── e5f6a7b8...mach # another compiled module
├── c9d0e1f2...mcode # cached JSON IR
└── f3a4b5c6...macos_arm64.dylib # native compiled module
```
This scheme provides automatic cache invalidation: when source changes, its hash changes, and the old cache entry is simply never looked up again.
### Core Module Caching
Core modules loaded via `use_core()` in engine.cm follow the same pattern. On first startup after a fresh install, core modules are compiled from `.cm.mcode` JSON IR and cached as `.mach` blobs. Subsequent startups load from cache, skipping the JSON parse and compile steps entirely.
User scripts (`.ce` files) are also cached. The first run compiles and caches; subsequent runs with unchanged source load from cache.
## C Extension Resolution
C extensions are resolved alongside script modules. A C module is identified by a symbol name derived from the package and file name:
```
package: gitea.pockle.world/john/prosperon
file: sprite.c
symbol: js_gitea_pockle_world_john_prosperon_sprite_use
```
### C Resolution Sources
1. **Internal symbols** — statically linked into the `pit` binary (core modules)
2. **Per-module dylibs** — loaded from `~/.pit/lib/` via a manifest file
### Manifest Files
Each package with C extensions has a manifest at `~/.pit/lib/<package>.manifest.json` mapping symbol names to dylib paths:
```json
{
"js_mypackage_render_use": "/Users/john/.pit/lib/mypackage_render.dylib",
"js_mypackage_audio_use": "/Users/john/.pit/lib/mypackage_audio.dylib"
}
```
The shop loads manifests lazily on first access and caches them.
### Combined Resolution
When both a `.cm` script and a C symbol exist for the same module name, both are resolved. The C module is loaded first (as the base), then the `.cm` script can extend it:
```javascript
// render.cm — extends the C render module
var c_render = use('internal/render_c')
// Add ƿit-level helpers on top of C functions
return record(c_render, {
draw_circle: function(x, y, r) { /* ... */ }
})
```
## Environment Injection
When a module is loaded, the shop builds an `env` object that becomes the module's set of free variables. This includes:
- **Runtime functions** — `logical`, `some`, `every`, `starts_with`, `ends_with`, `is_actor`, `log`, `send`, `fallback`, `parallel`, `race`, `sequence`
- **Capability injections** — actor intrinsics like `$self`, `$delay`, `$start`, `$receiver`, `$fd`, etc.
- **`use` function** — scoped to the module's package context
The set of injected capabilities is controlled by `script_inject_for()`, which can be tuned per package or file.
## Shop Directory Layout
```
~/.pit/
├── packages/ # installed packages (directories and symlinks)
│ └── core -> ... # symlink to the ƿit core
├── lib/ # compiled C extension dylibs + manifests
├── build/ # content-addressed compilation cache
│ ├── <hash>.mach # cached bytecode blobs
│ ├── <hash>.mcode # cached JSON IR
│ └── <hash>.<target>.dylib # native compiled modules
├── cache/ # downloaded package zip archives
├── lock.toml # installed package versions and commit hashes
└── link.toml # local development link overrides
```
## Key Files
| File | Role |
|------|------|
| `internal/bootstrap.cm` | Loads compiler, defines `analyze()` and `compile_to_blob()` |
| `internal/engine.cm` | Actor runtime, `use_core()`, environment setup |
| `internal/shop.cm` | Module resolution, compilation, caching, C extension loading |
| `internal/os.c` | OS intrinsics: dylib ops, internal symbol lookup, embedded modules |
| `package.cm` | Package directory detection, alias resolution, file listing |
| `link.cm` | Development link management (link.toml read/write) |

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.
## Module Structure
An `.mcode` file is a JSON object representing a compiled module:
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Module name (typically the source filename) |
| `filename` | string | Source filename |
| `data` | object | Constant pool — string and number literals used by instructions |
| `main` | function | The top-level function (module body) |
| `functions` | array | Nested function definitions (referenced by `function dest, id`) |
### Function Record
Each function (both `main` and entries in `functions`) has:
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Function name (`"<anonymous>"` for lambdas) |
| `filename` | string | Source filename |
| `nr_args` | integer | Number of parameters |
| `nr_slots` | integer | Total register slots needed (args + locals + temporaries) |
| `nr_close_slots` | integer | Number of closure slots captured from parent scope |
| `disruption_pc` | integer | Instruction index of the disruption handler (0 if none) |
| `instructions` | array | Instruction arrays and label strings |
Slot 0 is reserved. Slots 1 through `nr_args` hold parameters. Remaining slots up to `nr_slots - 1` are locals and temporaries.
## Instruction Format
Each instruction is a JSON array. The first element is the instruction name (string), followed by operands. The last two elements are line and column numbers for source mapping:

153
fd.c
View File

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

4
fd.cm
View File

@@ -1,4 +1,4 @@
var fd = native
var fd = use('internal/fd_c')
var wildstar = use('wildstar')
function last_pos(str, sep) {
@@ -97,4 +97,4 @@ fd.globfs = function(globs, dir) {
return results
}
return fd
return fd

7
fit.c
View File

@@ -250,7 +250,8 @@ static const JSCFunctionListEntry js_fit_funcs[] = {
JSValue js_fit_use(JSContext *js)
{
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_fit_funcs, countof(js_fit_funcs));
return mod;
JS_FRAME(js);
JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_fit_funcs, countof(js_fit_funcs));
JS_RETURN(mod.val);
}

59
fold.cm
View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -9,11 +9,31 @@ function use_embed(name) {
var fd = use_embed('fd')
var json = use_embed('json')
var crypto = use_embed('crypto')
var use_cache = {}
use_cache['fd'] = fd
use_cache['os'] = os
use_cache['json'] = json
use_cache['crypto'] = crypto
function content_hash(content) {
return text(crypto.blake2(content), 'h')
}
function cache_path(hash) {
if (!shop_path) return null
return shop_path + '/build/' + hash + '.mach'
}
function ensure_build_dir() {
if (!shop_path) return null
var dir = shop_path + '/build'
if (!fd.is_dir(dir)) {
fd.mkdir(dir)
}
return dir
}
// Bootstrap: load tokenize.cm, parse.cm, fold.cm from pre-compiled mach bytecode
function use_basic(path) {
@@ -24,19 +44,28 @@ function use_basic(path) {
return result
}
// Load a module from .mach/.mcode bytecode (bootstrap modules have no source fallback)
// Load a module from cached .mach or .mcode bytecode
function boot_load(name, env) {
var mach_path = core_path + '/' + name + ".cm.mach"
var mcode_path = core_path + '/' + name + ".cm.mcode"
var data = null
var mcode_path = core_path + '/boot/' + name + ".cm.mcode"
var mcode_blob = null
var hash = null
var cached = null
var mcode_json = null
if (fd.is_file(mach_path)) {
data = fd.slurp(mach_path)
return mach_load(data, env)
}
var mach_blob = null
if (fd.is_file(mcode_path)) {
mcode_json = text(fd.slurp(mcode_path))
return mach_eval_mcode(name, mcode_json, env)
mcode_blob = fd.slurp(mcode_path)
hash = content_hash(mcode_blob)
cached = cache_path(hash)
if (cached && fd.is_file(cached)) {
return mach_load(fd.slurp(cached), env)
}
mcode_json = text(mcode_blob)
mach_blob = mach_compile_mcode_bin(name, mcode_json)
if (cached) {
ensure_build_dir()
fd.slurpwrite(cached, mach_blob)
}
return mach_load(mach_blob, env)
}
print("error: missing bootstrap bytecode: " + name + "\n")
disrupt
@@ -58,42 +87,33 @@ var streamline_mod = null
// Warn if any .cm source is newer than its compiled bytecode
function check_mach_stale() {
var sources = [
"tokenize.cm",
"parse.cm",
"fold.cm",
"mcode.cm",
"streamline.cm",
"qbe.cm",
"qbe_emit.cm",
"internal/bootstrap.cm",
"internal/engine.cm"
{src: "tokenize.cm", mcode: "boot/tokenize.cm.mcode"},
{src: "parse.cm", mcode: "boot/parse.cm.mcode"},
{src: "fold.cm", mcode: "boot/fold.cm.mcode"},
{src: "mcode.cm", mcode: "boot/mcode.cm.mcode"},
{src: "streamline.cm", mcode: "boot/streamline.cm.mcode"},
{src: "qbe.cm", mcode: "boot/qbe.cm.mcode"},
{src: "qbe_emit.cm", mcode: "boot/qbe_emit.cm.mcode"},
{src: "verify_ir.cm", mcode: "boot/verify_ir.cm.mcode"},
{src: "internal/bootstrap.cm", mcode: "boot/bootstrap.cm.mcode"},
{src: "internal/engine.cm", mcode: "boot/engine.cm.mcode"}
]
var stale = []
var _i = 0
var cm_path = null
var mach_path = null
var mcode_path = null
var cm_stat = null
var compiled_stat = null
var best_mtime = null
var entry = null
while (_i < length(sources)) {
cm_path = core_path + '/' + sources[_i]
mach_path = cm_path + '.mach'
mcode_path = cm_path + '.mcode'
best_mtime = null
if (fd.is_file(mach_path)) {
best_mtime = fd.stat(mach_path).mtime
}
if (fd.is_file(mcode_path)) {
entry = sources[_i]
cm_path = core_path + '/' + entry.src
mcode_path = core_path + '/' + entry.mcode
if (fd.is_file(mcode_path) && fd.is_file(cm_path)) {
compiled_stat = fd.stat(mcode_path)
if (best_mtime == null || compiled_stat.mtime > best_mtime) {
best_mtime = compiled_stat.mtime
}
}
if (best_mtime != null && fd.is_file(cm_path)) {
cm_stat = fd.stat(cm_path)
if (cm_stat.mtime > best_mtime) {
push(stale, sources[_i])
if (cm_stat.mtime > compiled_stat.mtime) {
push(stale, entry.src)
}
}
_i = _i + 1
@@ -140,38 +160,11 @@ function analyze(src, filename) {
return ast
}
// Load a module from .mach/.mcode bytecode, falling back to source compilation
function load_module(name, env) {
var mach_path = core_path + '/' + name + ".cm.mach"
var mcode_path = core_path + '/' + name + ".cm.mcode"
var data = null
var mcode_json = null
var src_path = null
var src = null
var ast = null
var compiled = null
var optimized = null
if (fd.is_file(mach_path)) {
data = fd.slurp(mach_path)
return mach_load(data, env)
}
if (fd.is_file(mcode_path)) {
mcode_json = text(fd.slurp(mcode_path))
return mach_eval_mcode(name, mcode_json, env)
}
src_path = core_path + '/' + name + ".cm"
src = text(fd.slurp(src_path))
ast = analyze(src, src_path)
compiled = mcode_mod(ast)
optimized = streamline_mod(compiled)
return mach_eval_mcode(name, json.encode(optimized), env)
}
// Load optimization pipeline modules (needs analyze to be defined)
streamline_mod = load_module("streamline", boot_env)
streamline_mod = boot_load("streamline", boot_env)
use_cache['streamline'] = streamline_mod
// Lazy-loaded verify_ir module (loaded on first use via use_fn)
// Lazy-loaded verify_ir module (loaded on first use)
var _verify_ir_mod = null
// Run AST through mcode pipeline → register VM
@@ -179,7 +172,7 @@ function run_ast(name, ast, env) {
var compiled = mcode_mod(ast)
if (os._verify_ir) {
if (_verify_ir_mod == null) {
_verify_ir_mod = use_fn('verify_ir')
_verify_ir_mod = boot_load('verify_ir', boot_env)
}
compiled._verify = true
compiled._verify_mod = _verify_ir_mod
@@ -190,88 +183,55 @@ function run_ast(name, ast, env) {
delete optimized._verify
delete optimized._verify_mod
}
return mach_eval_mcode(name, json.encode(optimized), env)
var mcode_json = json.encode(optimized)
var mach_blob = mach_compile_mcode_bin(name, mcode_json)
return mach_load(mach_blob, env)
}
// Run AST through mcode pipeline WITHOUT optimization → register VM
function run_ast_noopt(name, ast, env) {
var compiled = mcode_mod(ast)
return mach_eval_mcode(name, json.encode(compiled), env)
var mcode_json = json.encode(compiled)
var mach_blob = mach_compile_mcode_bin(name, mcode_json)
return mach_load(mach_blob, env)
}
// use() with ƿit pipeline for .cm modules
function use_fn(path) {
var file_path = null
var mach_path = null
var mcode_path = null
var mcode_json = null
var data = null
var script = null
var ast = null
var result = null
if (use_cache[path])
return use_cache[path]
// Try .cm.mach bytecode first (CWD then core_path)
mach_path = path + '.cm.mach'
if (!fd.is_file(mach_path))
mach_path = core_path + '/' + path + '.cm.mach'
if (fd.is_file(mach_path)) {
data = fd.slurp(mach_path)
result = mach_load(data, {use: use_fn})
use_cache[path] = result
return result
}
// Try .cm.mcode JSON IR (CWD then core_path)
mcode_path = path + '.cm.mcode'
if (!fd.is_file(mcode_path))
mcode_path = core_path + '/' + path + '.cm.mcode'
if (fd.is_file(mcode_path)) {
mcode_json = text(fd.slurp(mcode_path))
result = mach_eval_mcode(path, mcode_json, {use: use_fn})
use_cache[path] = result
return result
}
// Try .cm source (CWD then core_path)
file_path = path + '.cm'
if (!fd.is_file(file_path))
file_path = core_path + '/' + path + '.cm'
if (fd.is_file(file_path)) {
script = text(fd.slurp(file_path))
ast = analyze(script, file_path)
result = run_ast(path, ast, {use: use_fn})
use_cache[path] = result
return result
}
// Fallback to embedded C module
result = use_embed(replace(path, '/', '_'))
use_cache[path] = result
return result
// Compile AST to blob without loading (for caching)
function compile_to_blob(name, ast) {
var compiled = mcode_mod(ast)
var optimized = streamline_mod(compiled)
return mach_compile_mcode_bin(name, json.encode(optimized))
}
// Helper to load engine.cm and run it with given env
function load_engine(env) {
var engine_path = core_path + '/internal/engine.cm.mach'
var mcode_path = core_path + '/internal/engine.cm.mcode'
var data = null
var mcode_path = core_path + '/boot/engine.cm.mcode'
var mcode_blob = null
var hash = null
var cached = null
var mcode_json = null
var mach_blob = null
var engine_src = null
var engine_ast = null
if (fd.is_file(engine_path)) {
data = fd.slurp(engine_path)
return mach_load(data, env)
}
if (fd.is_file(mcode_path)) {
mcode_json = text(fd.slurp(mcode_path))
return mach_eval_mcode('engine', mcode_json, env)
mcode_blob = fd.slurp(mcode_path)
hash = content_hash(mcode_blob)
cached = cache_path(hash)
if (cached && fd.is_file(cached)) {
return mach_load(fd.slurp(cached), env)
}
mcode_json = text(mcode_blob)
mach_blob = mach_compile_mcode_bin('engine', mcode_json)
if (cached) {
ensure_build_dir()
fd.slurpwrite(cached, mach_blob)
}
return mach_load(mach_blob, env)
}
engine_path = core_path + '/internal/engine.cm'
engine_src = text(fd.slurp(engine_path))
engine_ast = analyze(engine_src, engine_path)
// Fallback: compile from source
var engine_cm = core_path + '/internal/engine.cm'
engine_src = text(fd.slurp(engine_cm))
engine_ast = analyze(engine_src, engine_cm)
return run_ast('engine', engine_ast, env)
}
@@ -298,13 +258,19 @@ if (args != null) {
os: os, actorsym: actorsym,
init: {program: program, arg: user_args},
core_path: core_path, shop_path: shop_path, json: json,
analyze: analyze, run_ast_fn: run_ast, run_ast_noopt_fn: run_ast_noopt
analyze: analyze, run_ast_fn: run_ast, run_ast_noopt_fn: run_ast_noopt,
use_cache: use_cache,
content_hash: content_hash, cache_path: cache_path,
ensure_build_dir: ensure_build_dir, compile_to_blob_fn: compile_to_blob
})
} else {
// Actor spawn mode — load engine.cm with full actor env
load_engine({
os: os, actorsym: actorsym, init: init,
core_path: core_path, shop_path: shop_path, json: json, nota: nota, wota: wota,
analyze: analyze, run_ast_fn: run_ast, run_ast_noopt_fn: run_ast_noopt
analyze: analyze, run_ast_fn: run_ast, run_ast_noopt_fn: run_ast_noopt,
use_cache: use_cache,
content_hash: content_hash, cache_path: cache_path,
ensure_build_dir: ensure_build_dir, compile_to_blob_fn: compile_to_blob
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
// 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, analyze, run_ast_fn, run_ast_noopt_fn, json, use_cache, content_hash, cache_path, ensure_build_dir, compile_to_blob_fn) come from env
// In actor spawn mode, also: nota, wota
var ACTORDATA = actorsym
var SYSYM = '__SYSTEM__'
@@ -53,7 +53,6 @@ var js = use_embed('js')
// shop_path may be null if --core was used without --shop
var packages_path = shop_path ? shop_path + '/packages' : null
var use_cache = {}
use_cache['core/os'] = os
// Extra env properties added as engine initializes (log, runtime fns, etc.)
@@ -70,11 +69,18 @@ function use_core(path) {
var result = null
var script = null
var ast = null
var c_cache_key = null
// Build env: merge core_extras, include C embed as 'native' if available
// If C embed exists, register it so .cm modules can use('internal/<name>_c')
if (sym) {
c_cache_key = 'core/internal/' + path + '_c'
if (!use_cache[c_cache_key])
use_cache[c_cache_key] = sym
}
// Build env: merge core_extras
env = {use: use_core}
arrfor(array(core_extras), function(k) { env[k] = core_extras[k] })
if (sym) env.native = sym
// Check for pre-compiled .cm.mach file first
var mach_path = core_path + '/' + path + '.cm.mach'
@@ -86,8 +92,25 @@ function use_core(path) {
// Check for .cm.mcode JSON IR
var mcode_path = core_path + '/' + path + '.cm.mcode'
var mcode_blob = null
var hash = null
var cached_path = null
var mach_blob = null
var source_blob = null
if (fd.is_file(mcode_path)) {
result = mach_eval_mcode('core:' + path, text(fd.slurp(mcode_path)), env)
mcode_blob = fd.slurp(mcode_path)
hash = content_hash(mcode_blob)
cached_path = cache_path(hash)
if (cached_path && fd.is_file(cached_path)) {
result = mach_load(fd.slurp(cached_path), env)
} else {
mach_blob = mach_compile_mcode_bin('core:' + path, text(mcode_blob))
if (cached_path) {
ensure_build_dir()
fd.slurpwrite(cached_path, mach_blob)
}
result = mach_load(mach_blob, env)
}
use_cache[cache_key] = result
return result
}
@@ -95,9 +118,21 @@ function use_core(path) {
// Fall back to source .cm file — compile at runtime
var file_path = core_path + '/' + path + MOD_EXT
if (fd.is_file(file_path)) {
script = text(fd.slurp(file_path))
ast = analyze(script, file_path)
result = run_ast_fn('core:' + path, ast, env)
source_blob = fd.slurp(file_path)
hash = content_hash(source_blob)
cached_path = cache_path(hash)
if (cached_path && fd.is_file(cached_path)) {
result = mach_load(fd.slurp(cached_path), env)
} else {
script = text(source_blob)
ast = analyze(script, file_path)
mach_blob = compile_to_blob_fn('core:' + path, ast)
if (cached_path) {
ensure_build_dir()
fd.slurpwrite(cached_path, mach_blob)
}
result = mach_load(mach_blob, env)
}
use_cache[cache_key] = result
return result
}
@@ -209,15 +244,27 @@ function create_actor(desc) {
var $_ = {}
$_.self = create_actor()
os.use_cache = use_cache
os.global_shop_path = shop_path
os.$_ = $_
os.analyze = analyze
os.run_ast_fn = run_ast_fn
os.run_ast_noopt_fn = run_ast_noopt_fn
os.json = json
use_cache['core/json'] = json
// Create runtime_env early (empty) — filled after pronto loads.
// Shop accesses it lazily (in inject_env, called at module-use time, not load time)
// so it sees the filled version.
var runtime_env = {}
// Populate core_extras with everything shop (and other core modules) need
core_extras.use_cache = use_cache
core_extras.shop_path = shop_path
core_extras.analyze = analyze
core_extras.run_ast_fn = run_ast_fn
core_extras.run_ast_noopt_fn = run_ast_noopt_fn
core_extras.core_json = json
core_extras.actor_api = $_
core_extras.runtime_env = runtime_env
core_extras.content_hash = content_hash
core_extras.cache_path = cache_path
core_extras.ensure_build_dir = ensure_build_dir
// NOW load shop — it receives all of the above via env
var shop = use_core('internal/shop')
var time = use_core('time')
@@ -227,29 +274,24 @@ var parallel = pronto.parallel
var race = pronto.race
var sequence = pronto.sequence
// Create runtime environment for modules
var runtime_env = {
logical: logical,
some: some,
every: every,
starts_with: starts_with,
ends_with: ends_with,
actor: actor,
is_actor: is_actor,
log: log,
send: send,
fallback: fallback,
parallel: parallel,
race: race,
sequence: sequence
}
// Fill runtime_env (same object reference shop holds)
runtime_env.logical = logical
runtime_env.some = some
runtime_env.every = every
runtime_env.starts_with = starts_with
runtime_env.ends_with = ends_with
runtime_env.actor = actor
runtime_env.is_actor = is_actor
runtime_env.log = log
runtime_env.send = send
runtime_env.fallback = fallback
runtime_env.parallel = parallel
runtime_env.race = race
runtime_env.sequence = sequence
// Make runtime functions available to modules loaded via use_core
arrfor(array(runtime_env), function(k) { core_extras[k] = runtime_env[k] })
// Pass to os for shop to access
os.runtime_env = runtime_env
$_.time_limit = function(requestor, seconds)
{
if (!pronto.is_requestor(requestor)) {
@@ -884,9 +926,25 @@ $_.clock(_ => {
env.args = _cell.args.arg
env.log = log
var script = text(fd.slurp(prog_path))
var ast = analyze(script, prog_path)
var val = run_ast_fn(prog, ast, env)
var source_blob = fd.slurp(prog_path)
var hash = content_hash(source_blob)
var cached_path = cache_path(hash)
var val = null
var script = null
var ast = null
var mach_blob = null
if (cached_path && fd.is_file(cached_path)) {
val = mach_load(fd.slurp(cached_path), env)
} else {
script = text(source_blob)
ast = analyze(script, prog_path)
mach_blob = compile_to_blob_fn(prog, ast)
if (cached_path) {
ensure_build_dir()
fd.slurpwrite(cached_path, mach_blob)
}
val = mach_load(mach_blob, env)
}
if (val) {
log.error('Program must not return anything')
disrupt

File diff suppressed because it is too large Load Diff

View File

@@ -75,7 +75,8 @@ static const JSCFunctionListEntry js_kim_funcs[] = {
JSValue js_kim_use(JSContext *js)
{
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_kim_funcs, countof(js_kim_funcs));
return mod;
JS_FRAME(js);
JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_kim_funcs, countof(js_kim_funcs));
JS_RETURN(mod.val);
}

View File

@@ -277,29 +277,31 @@ JSC_CCALL(os_mallinfo,
)
static JSValue js_os_rusage(JSContext *js, JSValue self, int argc, JSValue *argv) {
JSValue ret = JS_NULL;
ret = JS_NewObject(js);
JS_FRAME(js);
JS_ROOT(ret, JS_NewObject(js));
#if defined(__linux__) || defined(__APPLE__)
struct rusage jsmem;
getrusage(RUSAGE_SELF, &jsmem);
JSJMEMRET(ru_maxrss);
JSJMEMRET(ru_ixrss);
JSJMEMRET(ru_idrss);
JSJMEMRET(ru_isrss);
JSJMEMRET(ru_minflt);
JSJMEMRET(ru_majflt);
JSJMEMRET(ru_nswap);
JSJMEMRET(ru_inblock);
JSJMEMRET(ru_oublock);
JSJMEMRET(ru_msgsnd);
JSJMEMRET(ru_msgrcv);
JSJMEMRET(ru_nsignals);
JSJMEMRET(ru_nvcsw);
JSJMEMRET(ru_nivcsw);
#define JSJMEMRET_GC(FIELD) JS_SetPropertyStr(js, ret.val, #FIELD, number2js(js, jsmem.FIELD));
JSJMEMRET_GC(ru_maxrss);
JSJMEMRET_GC(ru_ixrss);
JSJMEMRET_GC(ru_idrss);
JSJMEMRET_GC(ru_isrss);
JSJMEMRET_GC(ru_minflt);
JSJMEMRET_GC(ru_majflt);
JSJMEMRET_GC(ru_nswap);
JSJMEMRET_GC(ru_inblock);
JSJMEMRET_GC(ru_oublock);
JSJMEMRET_GC(ru_msgsnd);
JSJMEMRET_GC(ru_msgrcv);
JSJMEMRET_GC(ru_nsignals);
JSJMEMRET_GC(ru_nvcsw);
JSJMEMRET_GC(ru_nivcsw);
#undef JSJMEMRET_GC
#endif
return ret;
JS_RETURN(ret.val);
}
JSC_SCALL(os_system,
@@ -425,6 +427,22 @@ static JSValue js_os_dylib_has_symbol(JSContext *js, JSValue self, int argc, JSV
return JS_NewBool(js, symbol != NULL);
}
/* Load a native .cm module from a dylib handle.
Uses cell_rt_native_module_load from qbe_helpers.c */
extern JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle);
static JSValue js_os_native_module_load(JSContext *js, JSValue self, int argc, JSValue *argv)
{
if (argc < 1)
return JS_ThrowTypeError(js, "native_module_load requires a dylib object");
void *handle = JS_GetOpaque(argv[0], js_dylib_class_id);
if (!handle)
return JS_ThrowTypeError(js, "First argument must be a dylib object");
return cell_rt_native_module_load(js, handle);
}
JSC_CCALL(os_print,
size_t len;
const char *str = JS_ToCStringLen(js, &len, argv[0]);
@@ -552,6 +570,37 @@ JSC_CCALL(os_getenv,
ret = JS_NULL;
)
/* --- Embedded module table (generated for static builds) ---
Uses dlsym to check if cell_embedded_module_lookup exists at runtime.
When linking a static build with a generated module_table.c, the symbol
will be found; in dynamic builds it returns NULL gracefully. */
struct cell_embedded_entry {
const char *name;
const unsigned char *data;
size_t size;
};
typedef const struct cell_embedded_entry *(*cell_embed_lookup_fn)(const char *);
static JSValue js_os_embedded_module(JSContext *js, JSValue self, int argc, JSValue *argv)
{
cell_embed_lookup_fn lookup = (cell_embed_lookup_fn)dlsym(RTLD_DEFAULT, "cell_embedded_module_lookup");
if (!lookup)
return JS_NULL;
const char *name = JS_ToCString(js, argv[0]);
if (!name) return JS_NULL;
const struct cell_embedded_entry *entry = lookup(name);
JS_FreeCString(js, name);
if (!entry) return JS_NULL;
/* Return the mach blob as a stoned blob */
return js_new_blob_stoned_copy(js, (void *)entry->data, entry->size);
}
static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, platform, 0),
MIST_FUNC_DEF(os, arch, 0),
@@ -568,6 +617,8 @@ static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, dylib_open, 1),
MIST_FUNC_DEF(os, dylib_symbol, 2),
MIST_FUNC_DEF(os, dylib_has_symbol, 2),
MIST_FUNC_DEF(os, native_module_load, 1),
MIST_FUNC_DEF(os, embedded_module, 1),
MIST_FUNC_DEF(os, load_internal, 1),
MIST_FUNC_DEF(os, internal_exists, 1),
MIST_FUNC_DEF(os, print, 1),
@@ -579,7 +630,8 @@ JSValue js_os_use(JSContext *js) {
JS_NewClassID(&js_dylib_class_id);
JS_NewClass(js, js_dylib_class_id, &js_dylib_class);
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_os_funcs,countof(js_os_funcs));
return mod;
JS_FRAME(js);
JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_os_funcs, countof(js_os_funcs));
JS_RETURN(mod.val);
}

View File

@@ -12,9 +12,12 @@ var pkg_tools = use('package')
var os = use('os')
var link = use('link')
var analyze = os.analyze
var run_ast_fn = os.run_ast_fn
var shop_json = os.json
// These come from env (via core_extras in engine.cm):
// analyze, run_ast_fn, core_json, use_cache, shop_path, actor_api, runtime_env,
// content_hash, cache_path, ensure_build_dir
var shop_json = core_json
var global_shop_path = shop_path
var my$_ = actor_api
var core = "core"
@@ -45,11 +48,6 @@ function ensure_dir(path) {
}
}
function content_hash(content)
{
return text(crypto.blake2(content), 'h')
}
function hash_path(content)
{
return global_shop_path + '/build' + '/' + content_hash(content)
@@ -66,9 +64,6 @@ var ACTOR_EXT = '.ce'
var dylib_ext = '.dylib' // Default extension
var use_cache = os.use_cache
var global_shop_path = os.global_shop_path
var my$_ = os.$_
Shop.get_package_dir = function(name) {
return global_shop_path + '/packages/' + name
@@ -379,6 +374,32 @@ Shop.extract_commit_hash = function(pkg, response) {
var dylib_visited = {}
var open_dls = {}
var loaded_manifests = {}
// 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 in the content-addressed cache
// Returns the loaded module value, or null if no native dylib exists
function try_native_dylib(content_key) {
var native_path = hash_path(content_key) + '.' + host_target + dylib_ext
if (!fd.is_file(native_path)) return null
var handle = os.dylib_open(native_path)
if (!handle) return null
return os.native_module_load(handle)
}
// Default capabilities injected into scripts
// These map to $_ properties in engine.cm
@@ -404,9 +425,8 @@ Shop.get_script_capabilities = function(path) {
// Matches engine.cm's approach: env properties become free variables in the module.
function inject_env(inject) {
var env = {}
var rt = my$_.os ? my$_.os.runtime_env : null
if (rt) {
arrfor(array(rt), function(k) { env[k] = rt[k] })
if (runtime_env) {
arrfor(array(runtime_env), function(k) { env[k] = runtime_env[k] })
}
// Add capability injections with $ prefix
@@ -433,7 +453,9 @@ function resolve_mod_fn(path, pkg) {
if (!fd.is_file(path)) { print(`path ${path} is not a file`); disrupt }
var content = text(fd.slurp(path))
var cached = pull_from_cache(stone(blob(content)))
var content_key = stone(blob(content))
var native_result = null
var cached = null
var ast = null
var compiled = null
var mach_path = null
@@ -441,24 +463,42 @@ function resolve_mod_fn(path, pkg) {
var mcode_path = null
var ir = null
var optimized = null
var mcode_json = null
var cached_mcode_path = null
// Check for native .cm dylib first (highest performance)
native_result = try_native_dylib(content_key)
if (native_result != null) {
return {_native: true, value: native_result}
}
// Check cache for pre-compiled .mach blob
cached = pull_from_cache(content_key)
if (cached) {
return cached
}
// Check for cached mcode in content-addressed store
cached_mcode_path = hash_path(content_key) + '.mcode'
if (fd.is_file(cached_mcode_path)) {
mcode_json = text(fd.slurp(cached_mcode_path))
compiled = mach_compile_mcode_bin(path, mcode_json)
put_into_cache(content_key, compiled)
return compiled
}
// Check for pre-compiled .mach or .mcode file alongside .cm source
if (ends_with(path, '.cm')) {
mach_path = text(path, 0, length(path) - 3) + '.mach'
if (fd.is_file(mach_path)) {
mach_blob = fd.slurp(mach_path)
put_into_cache(stone(blob(content)), mach_blob)
put_into_cache(content_key, mach_blob)
return mach_blob
}
mcode_path = path + '.mcode'
if (fd.is_file(mcode_path)) {
compiled = mach_compile_mcode_bin(path, text(fd.slurp(mcode_path)))
put_into_cache(stone(blob(content)), compiled)
put_into_cache(content_key, compiled)
return compiled
}
}
@@ -469,8 +509,15 @@ function resolve_mod_fn(path, pkg) {
ast = analyze(content, path)
ir = _mcode_mod(ast)
optimized = _streamline_mod(ir)
compiled = mach_compile_mcode_bin(path, shop_json.encode(optimized))
put_into_cache(stone(blob(content)), compiled)
mcode_json = shop_json.encode(optimized)
// Cache mcode (architecture-independent) in content-addressed store
ensure_dir(global_shop_path + '/build')
fd.slurpwrite(cached_mcode_path, stone(blob(mcode_json)))
// Cache mach blob
compiled = mach_compile_mcode_bin(path, mcode_json)
put_into_cache(content_key, compiled)
return compiled
}
@@ -576,7 +623,45 @@ function get_lib_path(pkg) {
return global_shop_path + '/lib/' + lib_name + dylib_ext
}
// Open a package's dynamic library and all its dependencies
// Load the manifest for a package's per-module dylibs
// Returns a map of symbol_name -> dylib_path, or null if no manifest
function load_package_manifest(pkg) {
if (loaded_manifests[pkg] != null) return loaded_manifests[pkg]
var lib_name = replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
var manifest_path = global_shop_path + '/lib/' + lib_name + '.manifest.json'
if (!fd.is_file(manifest_path)) {
loaded_manifests[pkg] = false
return null
}
var content = text(fd.slurp(manifest_path))
var manifest = json.decode(content)
loaded_manifests[pkg] = manifest
return manifest
}
// Open a per-module dylib from a manifest and return the dlopen handle
function open_module_dylib(dylib_path) {
if (open_dls[dylib_path]) return open_dls[dylib_path]
if (!fd.is_file(dylib_path)) return null
open_dls[dylib_path] = os.dylib_open(dylib_path)
return open_dls[dylib_path]
}
// Resolve a C symbol from per-module dylibs for a package
// Returns a loader function or null
function resolve_dylib_symbol(sym, pkg) {
var manifest = load_package_manifest(pkg)
if (!manifest) return null
var dylib_path = manifest[sym]
if (!dylib_path) return null
var handle = open_module_dylib(dylib_path)
if (!handle) return null
if (!os.dylib_has_symbol(handle, sym)) return null
return function() { return os.dylib_symbol(handle, sym) }
}
// Open a package's dynamic libraries (loads manifest + dependency manifests)
Shop.open_package_dylib = function(pkg) {
if (pkg == 'core' || !pkg) return
if (dylib_visited[pkg]) return
@@ -606,22 +691,18 @@ Shop.open_package_dylib = function(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)
}
}
// Pre-load the manifest
load_package_manifest(pkg)
}
// Resolve a C symbol by searching:
// 1. If package_context is null, only check core internal symbols
// 2. Otherwise: own package (internal then dylib) -> other packages (internal then dylib) -> core (internal only)
// 2. Otherwise: own package (internal then per-module dylib) -> aliased packages -> core (internal only)
// Core is never loaded as a dynamic library via dlopen
function resolve_c_symbol(path, package_context) {
var explicit = split_explicit_package_import(path)
var sym = null
var dl_path = null
var loader = null
var _path = null
var core_sym = null
var canon_pkg = null
@@ -643,10 +724,10 @@ function resolve_c_symbol(path, package_context) {
}
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)) {
loader = resolve_dylib_symbol(sym, explicit.package)
if (loader) {
return {
symbol: function() { return os.dylib_symbol(open_dls[dl_path], sym) },
symbol: loader,
scope: SCOPE_PACKAGE,
package: explicit.package,
path: sym
@@ -668,7 +749,7 @@ function resolve_c_symbol(path, package_context) {
return null
}
// 1. Check own package first (internal, then dylib)
// 1. Check own package first (internal, then per-module dylib)
sym = make_c_symbol(package_context, path)
if (os.internal_exists(sym)) {
return {
@@ -679,11 +760,10 @@ function resolve_c_symbol(path, package_context) {
}
Shop.open_package_dylib(package_context)
dl_path = get_lib_path(package_context)
if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) {
loader = resolve_dylib_symbol(sym, package_context)
if (loader) {
return {
symbol: function() { return os.dylib_symbol(open_dls[dl_path], sym) },
symbol: loader,
scope: SCOPE_LOCAL,
path: sym
}
@@ -700,7 +780,6 @@ function resolve_c_symbol(path, package_context) {
mod_name = get_import_name(path)
sym = make_c_symbol(canon_pkg, mod_name)
// Check internal first
if (os.internal_exists(sym)) {
return {
symbol: function() { return os.load_internal(sym) },
@@ -710,12 +789,11 @@ function resolve_c_symbol(path, package_context) {
}
}
// Then check dylib
Shop.open_package_dylib(canon_pkg)
dl_path = get_lib_path(canon_pkg)
if (open_dls[dl_path] && os.dylib_has_symbol(open_dls[dl_path], sym)) {
loader = resolve_dylib_symbol(sym, canon_pkg)
if (loader) {
return {
symbol: function() { return os.dylib_symbol(open_dls[dl_path], sym) },
symbol: loader,
scope: SCOPE_PACKAGE,
package: canon_pkg,
path: sym
@@ -841,20 +919,20 @@ function execute_module(info)
var pkg = null
if (mod_resolve.scope < 900) {
// Build env with runtime fns, capabilities, and use function
file_info = Shop.file_info(mod_resolve.path)
inject = Shop.script_inject_for(file_info)
env = inject_env(inject)
pkg = file_info.package
env.use = make_use_fn(pkg)
// Check if native dylib was resolved
if (is_object(mod_resolve.symbol) && mod_resolve.symbol._native) {
used = mod_resolve.symbol.value
} else {
// Build env with runtime fns, capabilities, and use function
file_info = Shop.file_info(mod_resolve.path)
inject = Shop.script_inject_for(file_info)
env = inject_env(inject)
pkg = file_info.package
env.use = make_use_fn(pkg)
// Add C module as native context if available
if (c_resolve.scope < 900) {
env.native = call_c_module(c_resolve)
// Load compiled bytecode with env
used = mach_load(mod_resolve.symbol, env)
}
// Load compiled bytecode with env
used = mach_load(mod_resolve.symbol, env)
} else if (c_resolve.scope < 900) {
// C only
used = call_c_module(c_resolve)
@@ -876,6 +954,21 @@ function get_module(path, package_context) {
}
Shop.use = function use(path, package_context) {
// Check for embedded module (static builds)
var embed_key = 'embedded:' + path
var embedded = null
var embed_env = null
if (use_cache[embed_key]) return use_cache[embed_key]
if (os.embedded_module) {
embedded = os.embedded_module(path)
if (embedded) {
embed_env = inject_env(SHOP_DEFAULT_INJECT)
embed_env.use = make_use_fn(package_context)
use_cache[embed_key] = mach_load(embedded, embed_env)
return use_cache[embed_key]
}
}
var info = resolve_module_info(path, package_context)
if (!info) { print(`Module ${path} could not be found in ${package_context}`); disrupt }

View File

@@ -28,6 +28,13 @@ var mcode = function(ast) {
"<<=": "shl", ">>=": "shr", ">>>=": "ushr"
}
var sensory_ops = {
is_array: "is_array", is_function: "is_func", is_object: "is_record",
is_stone: "is_stone", is_integer: "is_int", is_text: "is_text",
is_number: "is_num", is_logical: "is_bool", is_null: "is_null",
length: "length"
}
// Compiler state
var s_instructions = null
var s_data = null
@@ -519,7 +526,14 @@ var mcode = function(ast) {
// Central router: maps op string to decomposition helper
// Sets _bp_* closure vars then calls helper with reduced args
var relational_ops = {
lt: ["lt_int", "lt_float", "lt_text"],
le: ["le_int", "le_float", "le_text"],
gt: ["gt_int", "gt_float", "gt_text"],
ge: ["ge_int", "ge_float", "ge_text"]
}
var emit_binop = function(op_str, dest, left, right) {
var rel = null
_bp_dest = dest
_bp_left = left
_bp_right = right
@@ -529,18 +543,15 @@ var mcode = function(ast) {
emit_eq_decomposed()
} else if (op_str == "ne") {
emit_ne_decomposed()
} else if (op_str == "lt") {
emit_relational("lt_int", "lt_float", "lt_text")
} else if (op_str == "le") {
emit_relational("le_int", "le_float", "le_text")
} else if (op_str == "gt") {
emit_relational("gt_int", "gt_float", "gt_text")
} else if (op_str == "ge") {
emit_relational("ge_int", "ge_float", "ge_text")
} else {
// Passthrough for subtract, multiply, divide, modulo,
// bitwise, pow, in, etc.
emit_3(op_str, dest, left, right)
rel = relational_ops[op_str]
if (rel != null) {
emit_relational(rel[0], rel[1], rel[2])
} else {
// Passthrough for subtract, multiply, divide, modulo,
// bitwise, pow, in, etc.
emit_3(op_str, dest, left, right)
}
}
return null
}
@@ -1670,37 +1681,11 @@ var mcode = function(ast) {
fname = callee.name
nargs = args_list != null ? length(args_list) : 0
// 1-arg type check intrinsics → direct opcode
if (nargs == 1) {
if (fname == "is_array" || fname == "is_function" ||
fname == "is_object" || fname == "is_stone" ||
fname == "is_integer" || fname == "is_text" ||
fname == "is_number" || fname == "is_logical" ||
fname == "is_null" || fname == "length") {
if (nargs == 1 && sensory_ops[fname] != null) {
a0 = gen_expr(args_list[0], -1)
d = alloc_slot()
if (fname == "is_array") {
emit_2("is_array", d, a0)
} else if (fname == "is_function") {
emit_2("is_func", d, a0)
} else if (fname == "is_object") {
emit_2("is_record", d, a0)
} else if (fname == "is_stone") {
emit_2("is_stone", d, a0)
} else if (fname == "is_integer") {
emit_2("is_int", d, a0)
} else if (fname == "is_text") {
emit_2("is_text", d, a0)
} else if (fname == "is_number") {
emit_2("is_num", d, a0)
} else if (fname == "is_logical") {
emit_2("is_bool", d, a0)
} else if (fname == "is_null") {
emit_2("is_null", d, a0)
} else if (fname == "length") {
emit_2("length", d, a0)
}
emit_2(sensory_ops[fname], d, a0)
return d
}
}
// 2-arg push: push(arr, val) → guarded direct opcode
if (nargs == 2 && fname == "push") {
@@ -1930,7 +1915,7 @@ var mcode = function(ast) {
_i = _i + 1
}
dest = alloc_slot()
add_instr(["array", dest, 0])
add_instr(["array", dest, count])
_i = 0
while (_i < count) {
emit_2("push", dest, elem_slots[_i])
@@ -1943,7 +1928,7 @@ var mcode = function(ast) {
if (kind == "record") {
list = expr.list
dest = alloc_slot()
push(s_instructions, ["record", dest, 0])
push(s_instructions, ["record", dest, length(list)])
_i = 0
while (_i < length(list)) {
pair = list[_i]

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,14 @@ add_project_arguments(
)
add_project_arguments('-Wno-narrowing', language: 'cpp')
if get_option('validate_gc')
add_project_arguments('-DVALIDATE_GC', language: 'c')
endif
if get_option('force_gc')
add_project_arguments('-DFORCE_GC_AT_MALLOC', language: 'c')
endif
deps = []
if host_machine.system() == 'darwin'

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

@@ -570,19 +570,21 @@ static const JSCFunctionListEntry js_enet_peer_funcs[] = {
JSValue js_enet_use(JSContext *ctx)
{
JS_FRAME(ctx);
JS_NewClassID(&enet_host_id);
JS_NewClass(ctx, enet_host_id, &enet_host);
JSValue host_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, host_proto, js_enet_host_funcs, countof(js_enet_host_funcs));
JS_SetClassProto(ctx, enet_host_id, host_proto);
JS_ROOT(host_proto, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, host_proto.val, js_enet_host_funcs, countof(js_enet_host_funcs));
JS_SetClassProto(ctx, enet_host_id, host_proto.val);
JS_NewClassID(&enet_peer_class_id);
JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class);
JSValue peer_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, peer_proto, js_enet_peer_funcs, countof(js_enet_peer_funcs));
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto);
JS_ROOT(peer_proto, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, peer_proto.val, js_enet_peer_funcs, countof(js_enet_peer_funcs));
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto.val);
JSValue export_obj = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, export_obj, js_enet_funcs, countof(js_enet_funcs));
return export_obj;
JS_ROOT(export_obj, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, export_obj.val, js_enet_funcs, countof(js_enet_funcs));
JS_RETURN(export_obj.val);
}

View File

@@ -319,9 +319,10 @@ static const JSCFunctionListEntry js_http_funcs[] = {
};
JSValue js_http_use(JSContext *js) {
JS_FRAME(js);
par_easycurl_init(0); // Initialize platform HTTP backend
JSValue obj = JS_NewObject(js);
JS_SetPropertyFunctionList(js, obj, js_http_funcs,
JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_http_funcs,
sizeof(js_http_funcs)/sizeof(js_http_funcs[0]));
return obj;
JS_RETURN(mod.val);
}

View File

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

View File

@@ -1,6 +1,11 @@
var parse = function(tokens, src, filename, tokenizer) {
var _src_len = length(src)
var template_escape_map = {
n: "\n", t: "\t", r: "\r", "\\": "\\",
"`": "`", "$": "$", "0": character(0)
}
// ============================================================
// Parser Cursor
// ============================================================
@@ -175,6 +180,7 @@ var parse = function(tokens, src, filename, tokenizer) {
var tc = null
var tq = null
var esc_ch = null
var esc_val = null
var expr_tokens = null
var sub_ast = null
var sub_stmt = null
@@ -223,13 +229,8 @@ var parse = function(tokens, src, filename, tokenizer) {
while (tvi < tvlen) {
if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
esc_ch = tv[tvi + 1]
if (esc_ch == "n") { push(fmt_parts, "\n") }
else if (esc_ch == "t") { push(fmt_parts, "\t") }
else if (esc_ch == "r") { push(fmt_parts, "\r") }
else if (esc_ch == "\\") { push(fmt_parts, "\\") }
else if (esc_ch == "`") { push(fmt_parts, "`") }
else if (esc_ch == "$") { push(fmt_parts, "$") }
else if (esc_ch == "0") { push(fmt_parts, character(0)) }
esc_val = template_escape_map[esc_ch]
if (esc_val != null) { push(fmt_parts, esc_val) }
else { push(fmt_parts, esc_ch) }
tvi = tvi + 2
} else if (tv[tvi] == "$" && tvi + 1 < tvlen && tv[tvi + 1] == "{") {

File diff suppressed because it is too large Load Diff

116
prettify_mcode.ce Normal file
View File

@@ -0,0 +1,116 @@
// prettify_mcode.ce — reformat .mcode files to be human-readable
// Usage: ./cell --dev prettify_mcode boot/tokenize.cm.mcode
// ./cell --dev prettify_mcode boot/*.mcode
var fd = use("fd")
var json = use("json")
if (length(args) == 0) {
print("usage: cell prettify_mcode <file.mcode> [...]")
disrupt
}
// Collapse leaf arrays (instruction arrays) onto single lines
var compact_arrays = function(json_text) {
var lines = array(json_text, "\n")
var result = []
var i = 0
var line = null
var trimmed = null
var collecting = false
var collected = null
var indent = null
var is_leaf = null
var j = 0
var inner = null
var parts = null
var trailing = null
var chars = null
var k = 0
while (i < length(lines)) {
line = lines[i]
trimmed = trim(line)
if (collecting == false && trimmed == "[") {
collecting = true
chars = array(line)
k = 0
while (k < length(chars) && chars[k] == " ") {
k = k + 1
}
indent = text(line, 0, k)
collected = []
i = i + 1
continue
}
if (collecting) {
if (trimmed == "]" || trimmed == "],") {
is_leaf = true
j = 0
while (j < length(collected)) {
inner = trim(collected[j])
if (starts_with(inner, "[") || starts_with(inner, "{")) {
is_leaf = false
}
j = j + 1
}
if (is_leaf && length(collected) > 0) {
parts = []
j = 0
while (j < length(collected)) {
inner = trim(collected[j])
if (ends_with(inner, ",")) {
inner = text(inner, 0, length(inner) - 1)
}
parts[] = inner
j = j + 1
}
trailing = ""
if (ends_with(trimmed, ",")) {
trailing = ","
}
result[] = `${indent}[${text(parts, ", ")}]${trailing}`
} else {
result[] = `${indent}[`
j = 0
while (j < length(collected)) {
result[] = collected[j]
j = j + 1
}
result[] = line
}
collecting = false
} else {
collected[] = line
}
i = i + 1
continue
}
result[] = line
i = i + 1
}
return text(result, "\n")
}
var i = 0
var path = null
var raw = null
var obj = null
var pretty = null
var f = null
while (i < length(args)) {
path = args[i]
if (!fd.is_file(path)) {
print(`skip ${path} (not found)`)
i = i + 1
continue
}
raw = text(fd.slurp(path))
obj = json.decode(raw)
pretty = compact_arrays(json.encode(obj, null, 2))
f = fd.open(path, "w")
fd.write(f, pretty)
fd.close(f)
print(`prettified ${path}`)
i = i + 1
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

20
qop.c
View File

@@ -457,19 +457,21 @@ static const JSCFunctionListEntry js_qop_funcs[] = {
};
JSValue js_qop_use(JSContext *js) {
JS_FRAME(js);
JS_NewClassID(&js_qop_archive_class_id);
JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class);
JSValue archive_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs));
JS_SetClassProto(js, js_qop_archive_class_id, archive_proto);
JS_ROOT(archive_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, archive_proto.val, js_qop_archive_funcs, countof(js_qop_archive_funcs));
JS_SetClassProto(js, js_qop_archive_class_id, archive_proto.val);
JS_NewClassID(&js_qop_writer_class_id);
JS_NewClass(js, js_qop_writer_class_id, &js_qop_writer_class);
JSValue writer_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, writer_proto, js_qop_writer_funcs, countof(js_qop_writer_funcs));
JS_SetClassProto(js, js_qop_writer_class_id, writer_proto);
JS_ROOT(writer_proto, JS_NewObject(js));
JS_SetPropertyFunctionList(js, writer_proto.val, js_qop_writer_funcs, countof(js_qop_writer_funcs));
JS_SetClassProto(js, js_qop_writer_class_id, writer_proto.val);
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_qop_funcs, countof(js_qop_funcs));
return mod;
JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_qop_funcs, countof(js_qop_funcs));
JS_RETURN(mod.val);
}

137
regen.ce
View File

@@ -1,8 +1,8 @@
// regen.ce — regenerate .mach bytecode files
// Run with: ./cell --core . regen
// regen.ce — regenerate .mcode bytecode files and pre-warm .mach cache
var fd = use("fd")
var json = use("json")
var crypto = use("crypto")
var tokenize = use("tokenize")
var parse = use("parse")
var fold = use("fold")
@@ -10,23 +10,46 @@ 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"}
{src: "tokenize.cm", name: "tokenize", out: "boot/tokenize.cm.mcode"},
{src: "parse.cm", name: "parse", out: "boot/parse.cm.mcode"},
{src: "fold.cm", name: "fold", out: "boot/fold.cm.mcode"},
{src: "mcode.cm", name: "mcode", out: "boot/mcode.cm.mcode"},
{src: "streamline.cm", name: "streamline", out: "boot/streamline.cm.mcode"},
{src: "qbe.cm", name: "qbe", out: "boot/qbe.cm.mcode"},
{src: "qbe_emit.cm", name: "qbe_emit", out: "boot/qbe_emit.cm.mcode"},
{src: "verify_ir.cm", name: "verify_ir", out: "boot/verify_ir.cm.mcode"},
{src: "internal/bootstrap.cm", name: "bootstrap", out: "boot/bootstrap.cm.mcode"},
{src: "internal/engine.cm", name: "engine", out: "boot/engine.cm.mcode"},
{src: "boot/seed_bootstrap.cm", name: "seed_bootstrap", out: "boot/seed_bootstrap.cm.mcode"}
]
// Resolve shop_path for cache writes
var os = use('os')
var shop = os.getenv('CELL_SHOP')
var home = null
var cache_dir = null
if (!shop) {
home = os.getenv('HOME')
if (home) {
shop = home + '/.cell'
}
}
if (shop) {
cache_dir = shop + '/build'
if (!fd.is_dir(cache_dir)) {
fd.mkdir(cache_dir)
}
}
var i = 0
var entry = null
var src = null
var tok_result = null
var ast = null
var folded = null
var mcode_blob = null
var hash = null
var mach_blob = null
var compiled = null
var optimized = null
var mcode_text = null
@@ -36,88 +59,6 @@ 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))
@@ -143,11 +84,19 @@ while (i < length(files)) {
folded = fold(ast)
compiled = mcode(folded)
optimized = streamline(compiled)
mcode_text = compact_arrays(json.encode(optimized, null, 2))
mcode_text = json.encode(optimized)
f = fd.open(entry.out, "w")
fd.write(f, mcode_text)
fd.close(f)
print(`wrote ${entry.out}`)
// Pre-warm .mach cache
if (cache_dir) {
mcode_blob = stone(blob(mcode_text))
hash = text(crypto.blake2(mcode_blob), 'h')
mach_blob = mach_compile_mcode_bin(entry.name, mcode_text)
fd.slurpwrite(cache_dir + '/' + hash + '.mach', mach_blob)
print(` cached ${hash}.mach`)
}
i = i + 1
}
if (had_errors) {

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,9 @@
#include "cell_internal.h"
#include "cJSON.h"
#define BOOTSTRAP_MACH "internal/bootstrap.cm.mach"
#define BOOTSTRAP_MCODE "internal/bootstrap.cm.mcode"
#define BOOTSTRAP_SRC "internal/bootstrap.cm"
#define BOOTSTRAP_MCODE "boot/bootstrap.cm.mcode"
#define SEED_BOOTSTRAP_MCODE "boot/seed_bootstrap.cm.mcode"
#define BOOTSTRAP_SRC "internal/bootstrap.cm"
#define CELL_SHOP_DIR ".cell"
#define CELL_CORE_DIR "packages/core"
@@ -21,6 +21,7 @@
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
#include "monocypher.h"
/* Test suite declarations */
int run_c_test_suite(JSContext *ctx);
@@ -31,6 +32,83 @@ static char *shop_path = NULL;
static char *core_path = NULL;
static JSRuntime *g_runtime = NULL;
// Compute blake2b hash of data and return hex string (caller must free)
static char *compute_blake2_hex(const char *data, size_t size) {
uint8_t hash[32];
crypto_blake2b(hash, 32, (const uint8_t *)data, size);
char *hex = malloc(65);
for (int i = 0; i < 32; i++)
snprintf(hex + i * 2, 3, "%02x", hash[i]);
return hex;
}
// Build cache path: shop_path/build/<hex>.mach (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 + strlen(".mach") + 1;
char *path = malloc(len);
snprintf(path, len, "%s/build/%s.mach", shop_path, hex);
return path;
}
// Write binary data to file
static int write_cache_file(const char *path, const uint8_t *data, size_t size) {
FILE *fh = fopen(path, "wb");
if (!fh) return 0;
size_t written = fwrite(data, 1, size, fh);
fclose(fh);
return written == size;
}
// Load cached .mach or compile from .mcode and cache result
// Returns heap-allocated binary data and sets *out_size, or NULL on failure
static char *load_or_cache_bootstrap(const char *mcode_data, size_t mcode_size, size_t *out_size) {
char *hex = compute_blake2_hex(mcode_data, mcode_size);
char *cpath = build_cache_path(hex);
free(hex);
if (cpath) {
// Try loading from cache
FILE *fh = fopen(cpath, "rb");
if (fh) {
fseek(fh, 0, SEEK_END);
long file_size = ftell(fh);
fseek(fh, 0, SEEK_SET);
char *data = malloc(file_size);
if (data && fread(data, 1, file_size, fh) == (size_t)file_size) {
fclose(fh);
free(cpath);
*out_size = file_size;
return data;
}
free(data);
fclose(fh);
}
}
// Cache miss: compile mcode to binary
cJSON *mcode = cJSON_Parse(mcode_data);
if (!mcode) { free(cpath); return NULL; }
MachCode *mc = mach_compile_mcode(mcode);
cJSON_Delete(mcode);
if (!mc) { free(cpath); return NULL; }
size_t bin_size;
uint8_t *bin = JS_SerializeMachCode(mc, &bin_size);
JS_FreeMachCode(mc);
if (!bin) { free(cpath); return NULL; }
// Write to cache
if (cpath) {
write_cache_file(cpath, bin, bin_size);
free(cpath);
}
*out_size = bin_size;
return (char *)bin;
}
// Get the home directory
static const char* get_home_dir(void) {
const char *home = getenv("HOME");
@@ -179,55 +257,70 @@ void script_startup(cell_rt *prt)
cell_rt *crt = JS_GetContextOpaque(js);
JS_FreeValue(js, js_blob_use(js));
// Load pre-compiled bootstrap (.cm.mach or .cm.mcode)
// Load pre-compiled bootstrap .mcode
size_t boot_size;
char *boot_data = load_core_file(BOOTSTRAP_MACH, &boot_size);
int boot_is_mcode = 0;
if (!boot_data) {
boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size);
boot_is_mcode = 1;
}
char *boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size);
if (!boot_data) {
printf("ERROR: Could not load bootstrap from %s!\n", core_path);
return;
}
// Try cache or compile mcode → binary
size_t bin_size;
char *bin_data = load_or_cache_bootstrap(boot_data, boot_size, &bin_size);
free(boot_data);
if (!bin_data) {
printf("ERROR: Failed to compile bootstrap mcode!\n");
return;
}
// Create hidden environment
JSValue hidden_env = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_env, "os", js_os_use(js));
JS_SetPropertyStr(js, hidden_env, "json", js_json_use(js));
JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js));
JS_SetPropertyStr(js, hidden_env, "wota", js_wota_use(js));
// Note: evaluate allocating calls into temporaries before passing to
// JS_SetPropertyStr, so env_ref.val is read AFTER GC may have moved it.
JSGCRef env_ref;
JS_AddGCRef(js, &env_ref);
env_ref.val = JS_NewObject(js);
JSValue tmp;
tmp = js_os_use(js);
JS_SetPropertyStr(js, env_ref.val, "os", tmp);
tmp = js_json_use(js);
JS_SetPropertyStr(js, env_ref.val, "json", tmp);
tmp = js_nota_use(js);
JS_SetPropertyStr(js, env_ref.val, "nota", tmp);
tmp = js_wota_use(js);
JS_SetPropertyStr(js, env_ref.val, "wota", tmp);
crt->actor_sym_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
JS_SetPropertyStr(js, env_ref.val, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val));
// Always set init (even if null)
if (crt->init_wota) {
JS_SetPropertyStr(js, hidden_env, "init", wota2value(js, crt->init_wota));
tmp = wota2value(js, crt->init_wota);
JS_SetPropertyStr(js, env_ref.val, "init", tmp);
free(crt->init_wota);
crt->init_wota = NULL;
} else {
JS_SetPropertyStr(js, hidden_env, "init", JS_NULL);
JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
}
// Set args to null for actor spawn (not CLI mode)
JS_SetPropertyStr(js, hidden_env, "args", JS_NULL);
JS_SetPropertyStr(js, env_ref.val, "args", JS_NULL);
if (core_path)
JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path));
JS_SetPropertyStr(js, hidden_env, "shop_path",
shop_path ? JS_NewString(js, shop_path) : JS_NULL);
if (core_path) {
tmp = JS_NewString(js, core_path);
JS_SetPropertyStr(js, env_ref.val, "core_path", tmp);
}
tmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
JS_SetPropertyStr(js, env_ref.val, "shop_path", tmp);
// Stone the environment
hidden_env = JS_Stone(js, hidden_env);
JSValue hidden_env = JS_Stone(js, env_ref.val);
JS_DeleteGCRef(js, &env_ref);
// Run through MACH VM
// Run from binary
crt->state = ACTOR_RUNNING;
JSValue v = boot_is_mcode
? JS_RunMachMcode(js, boot_data, boot_size, hidden_env)
: JS_RunMachBin(js, (const uint8_t *)boot_data, boot_size, hidden_env);
free(boot_data);
JSValue v = JS_RunMachBin(js, (const uint8_t *)bin_data, bin_size, hidden_env);
free(bin_data);
uncaught_exception(js, v);
crt->state = ACTOR_IDLE;
set_actor_state(crt);
@@ -283,6 +376,8 @@ static void print_usage(const char *prog)
printf(" --core <path> Set core path directly (overrides CELL_CORE)\n");
printf(" --shop <path> Set shop path (overrides CELL_SHOP)\n");
printf(" --dev Dev mode (shop=.cell, core=.)\n");
printf(" --heap <size> Initial heap size (e.g. 256MB, 1GB)\n");
printf(" --seed Use seed bootstrap (minimal, for regen)\n");
printf(" --test [heap_size] Run C test suite\n");
printf(" -h, --help Show this help message\n");
printf("\nEnvironment:\n");
@@ -315,6 +410,8 @@ int cell_init(int argc, char **argv)
/* Default: run script through bootstrap pipeline */
int arg_start = 1;
int seed_mode = 0;
size_t heap_size = 1024 * 1024; /* 1MB default */
const char *shop_override = NULL;
const char *core_override = NULL;
@@ -334,6 +431,20 @@ int cell_init(int argc, char **argv)
}
core_override = argv[arg_start + 1];
arg_start += 2;
} else if (strcmp(argv[arg_start], "--seed") == 0) {
seed_mode = 1;
arg_start++;
} else if (strcmp(argv[arg_start], "--heap") == 0) {
if (arg_start + 1 >= argc) {
printf("ERROR: --heap requires a size argument (e.g. 1GB, 256MB, 65536)\n");
return 1;
}
char *end = NULL;
heap_size = strtoull(argv[arg_start + 1], &end, 0);
if (end && (*end == 'G' || *end == 'g')) heap_size *= 1024ULL * 1024 * 1024;
else if (end && (*end == 'M' || *end == 'm')) heap_size *= 1024ULL * 1024;
else if (end && (*end == 'K' || *end == 'k')) heap_size *= 1024ULL;
arg_start += 2;
} else if (strcmp(argv[arg_start], "--dev") == 0) {
shop_override = ".cell";
core_override = ".";
@@ -359,28 +470,33 @@ int cell_init(int argc, char **argv)
actor_initialize();
const char *boot_mcode = seed_mode ? SEED_BOOTSTRAP_MCODE : BOOTSTRAP_MCODE;
size_t boot_size;
char *boot_data = load_core_file(BOOTSTRAP_MACH, &boot_size);
int boot_is_mcode = 0;
if (!boot_data) {
boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size);
boot_is_mcode = 1;
}
char *boot_data = load_core_file(boot_mcode, &boot_size);
if (!boot_data) {
printf("ERROR: Could not load bootstrap from %s\n", core_path);
return 1;
}
// Try cache or compile mcode → binary
size_t bin_size;
char *bin_data = load_or_cache_bootstrap(boot_data, boot_size, &bin_size);
free(boot_data);
if (!bin_data) {
printf("ERROR: Failed to compile bootstrap mcode\n");
return 1;
}
g_runtime = JS_NewRuntime();
if (!g_runtime) {
printf("Failed to create JS runtime\n");
free(boot_data);
free(bin_data);
return 1;
}
JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, 1024 * 1024);
JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, heap_size);
if (!ctx) {
printf("Failed to create JS context\n");
free(boot_data); JS_FreeRuntime(g_runtime);
free(bin_data); JS_FreeRuntime(g_runtime);
return 1;
}
@@ -414,31 +530,38 @@ int cell_init(int argc, char **argv)
JS_FreeValue(ctx, js_blob_use(ctx));
JSValue hidden_env = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, hidden_env, "os", js_os_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path));
JS_SetPropertyStr(ctx, hidden_env, "shop_path",
shop_path ? JS_NewString(ctx, shop_path) : JS_NULL);
/* TODO: remove after next 'make regen' — old bootstrap.mach reads these */
JS_SetPropertyStr(ctx, hidden_env, "emit_qbe", JS_FALSE);
JS_SetPropertyStr(ctx, hidden_env, "dump_mach", JS_FALSE);
JS_SetPropertyStr(ctx, hidden_env, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
JS_SetPropertyStr(ctx, hidden_env, "json", js_json_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "nota", js_nota_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "wota", js_wota_use(ctx));
JS_SetPropertyStr(ctx, hidden_env, "init", JS_NULL);
JSValue args_arr = JS_NewArray(ctx);
JSGCRef env_ref;
JS_AddGCRef(ctx, &env_ref);
env_ref.val = JS_NewObject(ctx);
JSValue tmp;
tmp = js_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_json_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
tmp = js_nota_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "nota", tmp);
tmp = js_wota_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "wota", tmp);
JS_SetPropertyStr(ctx, env_ref.val, "init", JS_NULL);
JSGCRef args_ref;
JS_AddGCRef(ctx, &args_ref);
args_ref.val = JS_NewArray(ctx);
for (int i = arg_start; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]);
JS_ArrayPush(ctx, &args_arr, str);
JS_ArrayPush(ctx, &args_ref.val, str);
}
JS_SetPropertyStr(ctx, hidden_env, "args", args_arr);
hidden_env = JS_Stone(ctx, hidden_env);
JS_SetPropertyStr(ctx, env_ref.val, "args", args_ref.val);
JS_DeleteGCRef(ctx, &args_ref);
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
JS_DeleteGCRef(ctx, &env_ref);
JSValue result = boot_is_mcode
? JS_RunMachMcode(ctx, boot_data, boot_size, hidden_env)
: JS_RunMachBin(ctx, (const uint8_t *)boot_data, boot_size, hidden_env);
free(boot_data);
JSValue result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env);
free(bin_data);
int exit_code = 0;
if (JS_IsException(result)) {

View File

@@ -156,6 +156,43 @@ JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \
#define countof(x) (sizeof(x)/sizeof((x)[0]))
/* GC safety macros for C functions that allocate multiple heap objects.
Any allocation call (JS_NewObject, JS_SetPropertyStr, etc.) can trigger GC.
JS_ROOT style: explicit, use .val to access the rooted value.
JS_LOCAL style: transparent, GC updates the C local through a pointer. */
#define JS_FRAME(ctx) \
JSContext *_js_ctx = (ctx); \
JSGCRef *_js_gc_frame = JS_GetGCFrame(_js_ctx); \
JSLocalRef *_js_local_frame = JS_GetLocalFrame(_js_ctx)
#define JS_ROOT(name, init) \
JSGCRef name; \
JS_PushGCRef(_js_ctx, &name); \
name.val = (init)
#define JS_LOCAL(name, init) \
JSValue name = (init); \
JSLocalRef name##__lr; \
name##__lr.ptr = &name; \
JS_PushLocalRef(_js_ctx, &name##__lr)
#define JS_RETURN(val) do { \
JSValue _js_ret = (val); \
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
return _js_ret; \
} while (0)
#define JS_RETURN_NULL() do { \
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
return JS_NULL; \
} while (0)
#define JS_RETURN_EX() do { \
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame); \
return JS_EXCEPTION; \
} while (0)
// Common macros for property access
#define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\
JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \

View File

@@ -815,12 +815,10 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
int op = MACH_GET_OP(instr);
/* trace disabled */
int a = MACH_GET_A(instr);
int b = MACH_GET_B(instr);
int c = MACH_GET_C(instr);
switch (op) {
case MACH_NOP:
break;
@@ -983,6 +981,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
int ret = JS_SetProperty(ctx, obj, key, val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break;
}
@@ -1003,6 +1002,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[b]), val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(r)) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break;
}
@@ -1129,6 +1129,17 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
result = frame->slots[a];
if (JS_IsNull(frame->caller)) goto done;
{
#ifdef VALIDATE_GC
const char *callee_name = "?";
const char *callee_file = "?";
{
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(frame->function);
if (callee_fn->kind == JS_FUNC_KIND_REGISTER && callee_fn->u.reg.code) {
if (callee_fn->u.reg.code->name_cstr) callee_name = callee_fn->u.reg.code->name_cstr;
if (callee_fn->u.reg.code->filename_cstr) callee_file = callee_fn->u.reg.code->filename_cstr;
}
}
#endif
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
frame->caller = JS_NULL;
frame = caller;
@@ -1139,7 +1150,22 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
env = fn->u.reg.env_record;
pc = ret_info >> 16;
int ret_slot = ret_info & 0xFFFF;
if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result;
if (ret_slot != 0xFFFF) {
#ifdef VALIDATE_GC
if (JS_IsPtr(result)) {
void *rp = JS_VALUE_GET_PTR(result);
if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) {
if (!is_ct_ptr(ctx, rp))
fprintf(stderr, "VALIDATE_GC: stale RETURN into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u callee=%s (%s) caller=%s (%s)\n",
ret_slot, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc,
callee_name, callee_file,
code->name_cstr ? code->name_cstr : "?",
code->filename_cstr ? code->filename_cstr : "?");
}
}
#endif
frame->slots[ret_slot] = result;
}
}
break;
@@ -1170,7 +1196,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
}
case MACH_NEWARRAY: {
JSValue arr = JS_NewArray(ctx);
JSValue arr = JS_NewArrayCap(ctx, b);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(arr)) { goto disrupt; }
frame->slots[a] = arr;
@@ -1464,6 +1490,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
int ret = JS_SetProperty(ctx, obj, key, val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break;
}
case MACH_LOAD_INDEX: {
@@ -1482,6 +1509,7 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(frame->slots[b]), val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(r)) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break;
}
case MACH_LOAD_DYNAMIC: {
@@ -1516,12 +1544,13 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
mach_resolve_forward(&frame->slots[a]);
break;
}
/* New record */
case MACH_NEWRECORD: {
JSValue obj = JS_NewObject(ctx);
JSValue obj = b > 0 ? JS_NewObjectCap(ctx, b) : JS_NewObject(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(obj)) goto disrupt;
frame->slots[a] = obj;
@@ -1603,6 +1632,22 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(ret)) goto disrupt;
#ifdef VALIDATE_GC
if (JS_IsPtr(ret)) {
void *rp = JS_VALUE_GET_PTR(ret);
if ((uint8_t *)rp < ctx->heap_base || (uint8_t *)rp >= ctx->heap_free) {
if (!is_ct_ptr(ctx, rp)) {
int magic = (fn->kind == JS_FUNC_KIND_C) ? fn->u.cfunc.magic : -1;
void *cfp = (fn->kind == JS_FUNC_KIND_C) ? (void *)fn->u.cfunc.c_function.generic : NULL;
fprintf(stderr, "VALIDATE_GC: stale INVOKE result into slot %d, ptr=%p heap=[%p,%p) fn_slots=%d pc=%u kind=%d magic=%d cfunc=%p caller=%s (%s)\n",
b, rp, (void*)ctx->heap_base, (void*)ctx->heap_free, code->nr_slots, pc - 1, fn->kind,
magic, cfp,
code->name_cstr ? code->name_cstr : "?",
code->filename_cstr ? code->filename_cstr : "?");
}
}
}
#endif
frame->slots[b] = ret;
}
break;
@@ -1963,11 +2008,9 @@ static int mcode_reg_items(cJSON *it, cJSON **out) {
/* record: [1]=dest, [2]=0(const) — no line/col suffix */
if (!strcmp(op, "record")) { ADD(1); return c; }
/* array: [1]=dest, [2]=count(const), [3..]=elements (no line/col suffix) */
/* array: [1]=dest, [2]=count(const)elements added via separate push instrs */
if (!strcmp(op, "array")) {
ADD(1);
int cnt = (int)cJSON_GetArrayItem(it, 2)->valuedouble;
for (int j = 0; j < cnt; j++) ADD(3 + j);
return c;
}
@@ -2023,8 +2066,8 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
int pinned = 1 + nr_args;
for (int i = 0; i < pinned; i++) { first_ref[i] = 0; last_ref[i] = n; }
for (int i = 0; i < n; i++) {
cJSON *it = cJSON_GetArrayItem(instrs, i);
{ cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
if (!cJSON_IsArray(it)) continue;
cJSON *regs[MAX_REG_ITEMS];
int rc = mcode_reg_items(it, regs);
@@ -2034,7 +2077,7 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
if (first_ref[s] < 0) first_ref[s] = i;
last_ref[s] = i;
}
}
} }
/* Step 1a: extend live ranges for closure-captured slots.
If a child function captures a parent slot via get/put, that slot must
@@ -2056,8 +2099,8 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
typedef struct { const char *name; int pos; } LabelPos;
int lbl_cap = 32, lbl_n = 0;
LabelPos *lbls = sys_malloc(lbl_cap * sizeof(LabelPos));
for (int i = 0; i < n; i++) {
cJSON *it = cJSON_GetArrayItem(instrs, i);
{ cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
if (cJSON_IsString(it)) {
if (lbl_n >= lbl_cap) {
lbl_cap *= 2;
@@ -2065,23 +2108,23 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
}
lbls[lbl_n++] = (LabelPos){it->valuestring, i};
}
}
} }
/* Find backward jumps and extend live ranges */
int changed = 1;
while (changed) {
changed = 0;
for (int i = 0; i < n; i++) {
cJSON *it = cJSON_GetArrayItem(instrs, i);
cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
if (!cJSON_IsArray(it)) continue;
int sz = cJSON_GetArraySize(it);
if (sz < 3) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
const char *op = it->child->valuestring;
const char *target = NULL;
if (!strcmp(op, "jump")) {
target = cJSON_GetArrayItem(it, 1)->valuestring;
target = it->child->next->valuestring;
} else if (!strcmp(op, "jump_true") || !strcmp(op, "jump_false") ||
!strcmp(op, "jump_not_null")) {
target = cJSON_GetArrayItem(it, 2)->valuestring;
target = it->child->next->next->valuestring;
}
if (!target) continue;
/* Find label position */
@@ -2187,8 +2230,8 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
}
/* Step 3: apply remap to instructions */
for (int i = 0; i < n; i++) {
cJSON *it = cJSON_GetArrayItem(instrs, i);
{ cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
if (!cJSON_IsArray(it)) continue;
cJSON *regs[MAX_REG_ITEMS];
int rc = mcode_reg_items(it, regs);
@@ -2198,7 +2241,7 @@ static int *mcode_compress_regs(cJSON *fobj, int *out_old_nr_slots,
cJSON_SetNumberValue(regs[j], remap[old]);
}
}
}
} }
/* Update nr_slots in the JSON */
cJSON_SetNumberValue(nr_slots_j, new_max);
@@ -2230,8 +2273,8 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
s.flat_to_pc = sys_malloc((n + 1) * sizeof(int));
s.flat_count = n;
for (int i = 0; i < n; i++) {
cJSON *it = cJSON_GetArrayItem(instrs, i);
{ cJSON *it = instrs ? instrs->child : NULL;
for (int i = 0; it; i++, it = it->next) {
s.flat_to_pc[i] = s.code_count;
if (cJSON_IsString(it)) {
ml_label(&s, it->valuestring);
@@ -2430,15 +2473,10 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
}
/* Array/Object creation */
else if (strcmp(op, "array") == 0) {
int dest = A1, count = A2;
EM(MACH_ABC(MACH_NEWARRAY, dest, 0, 0));
for (int j = 0; j < count; j++) {
int elem = ml_int(it, 3 + j);
EM(MACH_ABC(MACH_PUSH, dest, elem, 0));
}
EM(MACH_ABC(MACH_NEWARRAY, A1, A2, 0));
}
else if (strcmp(op, "record") == 0) {
EM(MACH_ABC(MACH_NEWRECORD, A1, 0, 0));
EM(MACH_ABC(MACH_NEWRECORD, A1, A2, 0));
}
/* Push/Pop */
else if (strcmp(op, "push") == 0) {
@@ -2531,7 +2569,7 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) {
/* Unknown opcode — emit NOP */
EM(MACH_ABC(MACH_NOP, 0, 0, 0));
}
}
} }
/* Sentinel for flat_to_pc */
s.flat_to_pc[n] = s.code_count;
@@ -2670,34 +2708,32 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
/* Scan main's instructions */
{
cJSON *main_instrs = cJSON_GetObjectItemCaseSensitive(main_obj, "instructions");
int mn = main_instrs ? cJSON_GetArraySize(main_instrs) : 0;
for (int i = 0; i < mn; i++) {
cJSON *it = cJSON_GetArrayItem(main_instrs, i);
cJSON *it = main_instrs ? main_instrs->child : NULL;
for (; it; it = it->next) {
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 3) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
const char *op = it->child->valuestring;
if (!strcmp(op, "function")) {
int child_idx = (int)cJSON_GetArrayItem(it, 2)->valuedouble;
int child_idx = (int)it->child->next->next->valuedouble;
if (child_idx >= 0 && child_idx < func_count)
parent_of[child_idx] = func_count; /* main */
}
}
}
/* Scan each function's instructions */
for (int fi = 0; fi < func_count; fi++) {
cJSON *fobj = cJSON_GetArrayItem(funcs_arr, fi);
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
for (int fi = 0; fobj; fi++, fobj = fobj->next) {
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
int fn = finstrs ? cJSON_GetArraySize(finstrs) : 0;
for (int i = 0; i < fn; i++) {
cJSON *it = cJSON_GetArrayItem(finstrs, i);
cJSON *it = finstrs ? finstrs->child : NULL;
for (; it; it = it->next) {
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 3) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
const char *op = it->child->valuestring;
if (!strcmp(op, "function")) {
int child_idx = (int)cJSON_GetArrayItem(it, 2)->valuedouble;
int child_idx = (int)it->child->next->next->valuedouble;
if (child_idx >= 0 && child_idx < func_count)
parent_of[child_idx] = fi;
}
}
}
} }
/* Build per-function capture sets: for each function F, which of its slots
are captured by descendant functions via get/put. Captured slots must
@@ -2707,17 +2743,16 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
memset(cap_slots, 0, (func_count + 1) * sizeof(int *));
memset(cap_counts, 0, (func_count + 1) * sizeof(int));
for (int fi = 0; fi < func_count; fi++) {
cJSON *fobj = cJSON_GetArrayItem(funcs_arr, fi);
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
for (int fi = 0; fobj; fi++, fobj = fobj->next) {
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
int fn = finstrs ? cJSON_GetArraySize(finstrs) : 0;
for (int i = 0; i < fn; i++) {
cJSON *it = cJSON_GetArrayItem(finstrs, i);
cJSON *it = finstrs ? finstrs->child : NULL;
for (; it; it = it->next) {
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 4) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
const char *op = it->child->valuestring;
if (strcmp(op, "get") && strcmp(op, "put")) continue;
int slot = (int)cJSON_GetArrayItem(it, 2)->valuedouble;
int level = (int)cJSON_GetArrayItem(it, 3)->valuedouble;
int slot = (int)it->child->next->next->valuedouble;
int level = (int)it->child->next->next->next->valuedouble;
/* Walk up parent chain to find the ancestor whose slot is referenced */
int ancestor = fi;
for (int l = 0; l < level && ancestor >= 0; l++)
@@ -2733,7 +2768,7 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
cap_slots[ancestor][cap_counts[ancestor]++] = slot;
}
}
}
} }
/* Compress registers for functions that exceed 8-bit slot limits.
Save remap tables so we can fix get/put parent_slot references. */
@@ -2741,9 +2776,11 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
int *remap_sizes = sys_malloc((func_count + 1) * sizeof(int));
memset(remaps, 0, (func_count + 1) * sizeof(int *));
for (int i = 0; i < func_count; i++)
remaps[i] = mcode_compress_regs(cJSON_GetArrayItem(funcs_arr, i),
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
for (int i = 0; fobj; i++, fobj = fobj->next)
remaps[i] = mcode_compress_regs(fobj,
&remap_sizes[i], cap_slots[i], cap_counts[i]);
}
/* main is stored at index func_count in our arrays */
remaps[func_count] = mcode_compress_regs(main_obj,
&remap_sizes[func_count], cap_slots[func_count], cap_counts[func_count]);
@@ -2755,16 +2792,15 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
sys_free(cap_counts);
/* Fix up get/put parent_slot references using ancestor remap tables */
for (int fi = 0; fi < func_count; fi++) {
cJSON *fobj = cJSON_GetArrayItem(funcs_arr, fi);
{ cJSON *fobj = funcs_arr ? funcs_arr->child : NULL;
for (int fi = 0; fobj; fi++, fobj = fobj->next) {
cJSON *finstrs = cJSON_GetObjectItemCaseSensitive(fobj, "instructions");
int fn = finstrs ? cJSON_GetArraySize(finstrs) : 0;
for (int i = 0; i < fn; i++) {
cJSON *it = cJSON_GetArrayItem(finstrs, i);
cJSON *it = finstrs ? finstrs->child : NULL;
for (; it; it = it->next) {
if (!cJSON_IsArray(it) || cJSON_GetArraySize(it) < 4) continue;
const char *op = cJSON_GetArrayItem(it, 0)->valuestring;
const char *op = it->child->valuestring;
if (strcmp(op, "get") && strcmp(op, "put")) continue;
int level = (int)cJSON_GetArrayItem(it, 3)->valuedouble;
int level = (int)it->child->next->next->next->valuedouble;
/* Walk up parent chain 'level' times to find ancestor */
int ancestor = fi;
for (int l = 0; l < level && ancestor >= 0; l++) {
@@ -2773,14 +2809,14 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
if (ancestor < 0) continue; /* unknown parent — leave as is */
int *anc_remap = remaps[ancestor];
if (!anc_remap) continue; /* ancestor wasn't compressed */
cJSON *slot_item = cJSON_GetArrayItem(it, 2);
cJSON *slot_item = it->child->next->next;
int old_slot = (int)slot_item->valuedouble;
if (old_slot >= 0 && old_slot < remap_sizes[ancestor]) {
int new_slot = anc_remap[old_slot];
cJSON_SetNumberValue(slot_item, new_slot);
}
}
}
} }
/* Free remap tables */
for (int i = 0; i <= func_count; i++)
@@ -2794,8 +2830,10 @@ MachCode *mach_compile_mcode(cJSON *mcode_json) {
if (func_count > 0) {
compiled = sys_malloc(func_count * sizeof(MachCode *));
memset(compiled, 0, func_count * sizeof(MachCode *));
for (int i = 0; i < func_count; i++)
compiled[i] = mcode_lower_func(cJSON_GetArrayItem(funcs_arr, i), filename);
{ cJSON *fobj = funcs_arr->child;
for (int i = 0; fobj; i++, fobj = fobj->next)
compiled[i] = mcode_lower_func(fobj, filename);
}
}
/* Compile main */

View File

@@ -277,19 +277,37 @@ void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
/* Table mapping fn_idx → outer_fp at creation time.
Valid for single-threaded, non-recursive closure patterns. */
#define MAX_QBE_FUNCTIONS 256
static void *g_outer_fp[MAX_QBE_FUNCTIONS];
/* Per-module function registry.
Each native .cm module gets its own dylib. When a module creates closures
via cell_rt_make_function, we record the dylib handle so the trampoline
can look up the correct cell_fn_N in the right dylib. */
#define MAX_NATIVE_FN 4096
static struct {
void *dl_handle;
int fn_idx;
void *outer_fp;
} g_native_fn_registry[MAX_NATIVE_FN];
static int g_native_fn_count = 0;
/* Set before executing a native module's cell_main */
static void *g_current_dl_handle = NULL;
static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val,
int argc, JSValue *argv, int magic) {
char name[64];
snprintf(name, sizeof(name), "cell_fn_%d", magic);
if (magic < 0 || magic >= g_native_fn_count)
return JS_ThrowTypeError(ctx, "invalid native function id %d", magic);
cell_compiled_fn fn = (cell_compiled_fn)dlsym(RTLD_DEFAULT, name);
void *handle = g_native_fn_registry[magic].dl_handle;
int fn_idx = g_native_fn_registry[magic].fn_idx;
char name[64];
snprintf(name, sizeof(name), "cell_fn_%d", fn_idx);
cell_compiled_fn fn = (cell_compiled_fn)dlsym(handle, name);
if (!fn)
return JS_ThrowTypeError(ctx, "native function %s not found", name);
return JS_ThrowTypeError(ctx, "native function %s not found in dylib", name);
/* Allocate frame: slot 0 = this, slots 1..argc = args */
JSValue frame[512];
@@ -299,17 +317,22 @@ static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val,
frame[1 + i] = argv[i];
/* Link to outer frame for closure access */
if (magic >= 0 && magic < MAX_QBE_FUNCTIONS)
frame[QBE_FRAME_OUTER_SLOT] = (JSValue)(uintptr_t)g_outer_fp[magic];
frame[QBE_FRAME_OUTER_SLOT] = (JSValue)(uintptr_t)g_native_fn_registry[magic].outer_fp;
return fn(ctx, frame);
}
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp) {
if (fn_idx >= 0 && fn_idx < MAX_QBE_FUNCTIONS)
g_outer_fp[fn_idx] = outer_fp;
if (g_native_fn_count >= MAX_NATIVE_FN)
return JS_ThrowTypeError(ctx, "too many native functions (max %d)", MAX_NATIVE_FN);
int global_id = g_native_fn_count++;
g_native_fn_registry[global_id].dl_handle = g_current_dl_handle;
g_native_fn_registry[global_id].fn_idx = (int)fn_idx;
g_native_fn_registry[global_id].outer_fp = outer_fp;
return JS_NewCFunction2(ctx, (JSCFunction *)cell_fn_trampoline, "native_fn",
255, JS_CFUNC_generic_magic, (int)fn_idx);
255, JS_CFUNC_generic_magic, global_id);
}
/* --- Frame-based function calling --- */
@@ -430,20 +453,35 @@ void cell_rt_disrupt(JSContext *ctx) {
}
/* --- Module entry point ---
Called as symbol(ctx) by os.dylib_symbol. Looks up cell_main
in the loaded dylib, builds a heap-allocated frame (so closures
can reference it after the module returns), and runs the module body. */
Loads a native .cm module from a dylib handle.
Looks up cell_main, builds a heap-allocated frame, sets
g_current_dl_handle so closures register in the right module. */
JSValue cell_rt_module_entry(JSContext *ctx) {
cell_compiled_fn fn = (cell_compiled_fn)dlsym(RTLD_DEFAULT, "cell_main");
JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle) {
cell_compiled_fn fn = (cell_compiled_fn)dlsym(dl_handle, "cell_main");
if (!fn)
return JS_ThrowTypeError(ctx, "cell_main not found in loaded dylib");
return JS_ThrowTypeError(ctx, "cell_main not found in native module dylib");
/* Set current handle so cell_rt_make_function registers closures
against this module's dylib */
void *prev_handle = g_current_dl_handle;
g_current_dl_handle = dl_handle;
/* Heap-allocate so closures created in cell_main can reference
this frame after the module entry returns. */
JSValue *frame = calloc(512, sizeof(JSValue));
if (!frame)
if (!frame) {
g_current_dl_handle = prev_handle;
return JS_ThrowTypeError(ctx, "frame allocation failed");
}
return fn(ctx, frame);
JSValue result = fn(ctx, frame);
g_current_dl_handle = prev_handle;
return result;
}
/* 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);
}

View File

@@ -156,7 +156,8 @@ static const JSCFunctionListEntry js_actor_funcs[] = {
};
JSValue js_actor_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_actor_funcs,countof(js_actor_funcs));
return mod;
JS_FRAME(js);
JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_actor_funcs, countof(js_actor_funcs));
JS_RETURN(mod.val);
}

View File

@@ -95,9 +95,12 @@
/* test the GC by forcing it before each object allocation */
// #define FORCE_GC_AT_MALLOC
#define POISON_HEAP
/* POISON_HEAP: Use ASan's memory poisoning to detect stale pointer access */
#ifdef POISON_HEAP
#include <sys/mman.h>
#include <unistd.h>
/* HEAP_CHECK: validate heap pointers at JS_VALUE_GET_* macros */
// #define HEAP_CHECK
#if defined(__has_feature)
#if __has_feature(address_sanitizer)
#define HAVE_ASAN 1
@@ -106,25 +109,6 @@
#define HAVE_ASAN 1
#endif
#ifdef HAVE_ASAN
#include <sanitizer/asan_interface.h>
#define gc_poison_region(addr, size) __asan_poison_memory_region((addr), (size))
#define gc_unpoison_region(addr, size) __asan_unpoison_memory_region((addr), (size))
#else
/* Fallback: no-op when not building with ASan */
#define gc_poison_region(addr, size) ((void)0)
#define gc_unpoison_region(addr, size) ((void)0)
#endif
#include <sys/mman.h>
#include <unistd.h>
static inline size_t poison_page_align(size_t size) {
size_t ps = (size_t)sysconf(_SC_PAGESIZE);
return (size + ps - 1) & ~(ps - 1);
}
#endif /* POISON_HEAP */
#ifdef HAVE_ASAN
static struct JSContext *__asan_js_ctx;
#endif
@@ -303,14 +287,27 @@ typedef enum JSErrorEnum {
/* Forward declaration for bytecode freeing */
#define JS_VALUE_GET_BLOB(v) ((JSBlob *)JS_VALUE_GET_PTR (v))
#define JS_VALUE_GET_CODE(v) (JS_VALUE_GET_PTR (v))
#ifdef HEAP_CHECK
void heap_check_fail(void *ptr, struct JSContext *ctx);
#define JS_VALUE_GET_ARRAY(v) ((JSArray *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_OBJ(v) ((JSRecord *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_TEXT(v) ((JSText *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_FUNCTION(v) ((JSFunction *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_FRAME(v) ((JSFrame *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_STRING(v) ((JSText *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_RECORD(v) ((JSRecord *)heap_check_chase(ctx, v))
#else
#define JS_VALUE_GET_ARRAY(v) ((JSArray *)chase (v))
#define JS_VALUE_GET_OBJ(v) ((JSRecord *)chase (v))
#define JS_VALUE_GET_TEXT(v) ((JSText *)chase (v))
#define JS_VALUE_GET_BLOB(v) ((JSBlob *)JS_VALUE_GET_PTR (v))
#define JS_VALUE_GET_FUNCTION(v) ((JSFunction *)chase (v))
#define JS_VALUE_GET_FRAME(v) ((JSFrame *)chase (v))
#define JS_VALUE_GET_CODE(v) (JS_VALUE_GET_PTR (v))
#define JS_VALUE_GET_STRING(v) ((JSText *)chase (v))
#define JS_VALUE_GET_RECORD(v) ((JSRecord *)chase (v))
#endif
/* Compatibility: JS_TAG_STRING is an alias for text type checks */
#define JS_TAG_STRING JS_TAG_STRING_IMM
@@ -333,9 +330,8 @@ static inline objhdr_t objhdr_set_cap56 (objhdr_t h, uint64_t cap) {
#else
#define BUDDY_MIN_ORDER 9 /* 512B minimum on 32-bit */
#endif
#define BUDDY_MAX_ORDER 28 /* 256MB maximum */
#define BUDDY_LEVELS (BUDDY_MAX_ORDER - BUDDY_MIN_ORDER + 1)
#define BUDDY_POOL_SIZE (1ULL << BUDDY_MAX_ORDER)
#define BUDDY_MAX_LEVELS 40 /* supports pools up to 2^(BUDDY_MIN_ORDER+39) */
#define BUDDY_DEFAULT_POOL (1ULL << 24) /* 16MB initial pool */
typedef struct BuddyBlock {
struct BuddyBlock *next;
@@ -344,15 +340,26 @@ typedef struct BuddyBlock {
uint8_t is_free;
} BuddyBlock;
typedef struct BuddyPool {
struct BuddyPool *next;
uint8_t *base;
size_t total_size;
uint8_t max_order; /* log2(total_size) */
uint32_t alloc_count; /* outstanding allocations */
BuddyBlock *free_lists[BUDDY_MAX_LEVELS];
} BuddyPool;
typedef struct BuddyAllocator {
uint8_t *base; /* 256MB base address */
size_t total_size; /* 256MB */
BuddyBlock *free_lists[BUDDY_LEVELS];
uint8_t initialized;
BuddyPool *pools; /* linked list, newest first */
size_t next_pool_size; /* next pool doubles from this */
size_t initial_size; /* starting pool size */
size_t cap; /* 0 = no cap */
size_t total_mapped; /* sum of all pool sizes */
} BuddyAllocator;
/* Forward declarations for buddy allocator functions */
static void buddy_destroy (BuddyAllocator *b);
static size_t buddy_max_block (BuddyAllocator *b);
/* controls a host of contexts, handing out memory and scheduling */
struct JSRuntime {
@@ -838,6 +845,18 @@ static inline objhdr_t *chase(JSValue v) {
return oh;
}
/* Resolve a forward pointer in-place. After rec_resize the old record
gets a forward header; any JSValue slot still pointing at it must be
updated to follow the chain to the live copy. */
static inline void mach_resolve_forward(JSValue *slot) {
if (JS_IsPtr(*slot)) {
objhdr_t h = *(objhdr_t *)JS_VALUE_GET_PTR(*slot);
if (objhdr_type(h) == OBJ_FORWARD) {
*slot = JS_MKPTR(objhdr_fwd_ptr(h));
}
}
}
/* Inline type checks — use these in the VM dispatch loop to avoid
function call overhead. The public API (JS_IsArray etc. in quickjs.h)
remains non-inline for external callers; those wrappers live in runtime.c. */
@@ -1063,6 +1082,11 @@ struct JSContext {
uint8_t *heap_end; /* end of block */
size_t current_block_size; /* current block size (64KB initially) */
size_t next_block_size; /* doubles if <10% recovered after GC */
int gc_poor_streak; /* consecutive poor-recovery GC cycles */
/* GC stats (lightweight, always on) */
uint64_t gc_count; /* number of GC cycles */
uint64_t gc_bytes_copied; /* total bytes copied across all GCs */
/* Constant text pool — compilation constants */
uint8_t *ct_base; /* pool base */
@@ -1085,6 +1109,7 @@ struct JSContext {
JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */
JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */
JSLocalRef *top_local_ref; /* for JS_LOCAL macro - GC updates C locals through pointers */
CCallRoot *c_call_root; /* stack of auto-rooted C call argv arrays */
int class_count; /* size of class_array and class_proto */
@@ -1178,9 +1203,28 @@ static int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size);
/* Helper to check if a pointer is in constant text pool memory */
static inline int is_ct_ptr (JSContext *ctx, void *ptr) {
return (uint8_t *)ptr >= ctx->ct_base && (uint8_t *)ptr < ctx->ct_end;
uint8_t *p = (uint8_t *)ptr;
if (p >= ctx->ct_base && p < ctx->ct_end) return 1;
/* Also check overflow pages */
CTPage *page = (CTPage *)ctx->ct_pages;
while (page) {
if (p >= page->data && p < page->data + page->size) return 1;
page = page->next;
}
return 0;
}
#ifdef HEAP_CHECK
static inline objhdr_t *heap_check_chase(JSContext *ctx, JSValue v) {
objhdr_t *oh = chase(v);
uint8_t *p = (uint8_t *)oh;
if (!((p >= ctx->heap_base && p < ctx->heap_free) ||
(p >= ctx->ct_base && p < ctx->ct_end)))
heap_check_fail(oh, ctx);
return oh;
}
#endif
/* Intern a UTF-32 string as a stone text, returning a JSValue string */
/* Create a stoned, interned key from a UTF-8 C string.
@@ -1214,8 +1258,6 @@ typedef struct JSRegExp {
#define obj_is_stone(rec) objhdr_s ((rec)->mist_hdr)
#define obj_set_stone(rec) ((rec)->mist_hdr = objhdr_set_s ((rec)->mist_hdr, true))
#define JS_VALUE_GET_RECORD(v) ((JSRecord *)chase (v))
/* Get prototype from object (works for both JSRecord and JSRecord since they
* share layout) */
#define JS_OBJ_GET_PROTO(p) (JS_IsNull(((JSRecord *)(p))->proto) ? NULL : (JSRecord *)JS_VALUE_GET_PTR(((JSRecord *)(p))->proto))

View File

@@ -146,10 +146,22 @@ typedef struct JSGCRef {
struct JSGCRef *prev;
} JSGCRef;
/* JSLocalRef - GC updates C locals through pointers (OCaml-style) */
typedef struct JSLocalRef {
JSValue *ptr;
struct JSLocalRef *prev;
} JSLocalRef;
/* stack of JSGCRef */
JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref);
JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref);
/* JS_FRAME/JS_ROOT/JS_LOCAL helpers (for use from cell.h macros) */
JSGCRef *JS_GetGCFrame(JSContext *ctx);
JSLocalRef *JS_GetLocalFrame(JSContext *ctx);
void JS_PushLocalRef(JSContext *ctx, JSLocalRef *ref);
void JS_RestoreFrame(JSContext *ctx, JSGCRef *gc_frame, JSLocalRef *local_frame);
#define JS_PUSH_VALUE(ctx, v) do { JS_PushGCRef(ctx, &v ## _ref); v ## _ref.val = v; } while (0)
#define JS_POP_VALUE(ctx, v) v = JS_PopGCRef(ctx, &v ## _ref)
@@ -305,6 +317,7 @@ typedef JSValue JSCFunctionData (JSContext *ctx, JSValue this_val,
JSRuntime *JS_NewRuntime (void);
void JS_FreeRuntime (JSRuntime *rt);
void JS_SetMemoryLimit (JSRuntime *rt, size_t limit);
void JS_SetPoolSize (JSRuntime *rt, size_t initial, size_t cap);
JSContext *JS_NewContext (JSRuntime *rt);
JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size);
@@ -501,9 +514,11 @@ JSValue JS_NewObjectProtoClass (JSContext *ctx, JSValue proto, JSClassID class_i
JSValue JS_NewObjectClass (JSContext *ctx, int class_id);
JSValue JS_NewObjectProto (JSContext *ctx, JSValue proto);
JSValue JS_NewObject (JSContext *ctx);
JSValue JS_NewObjectCap (JSContext *ctx, uint32_t n);
JSValue JS_NewArray (JSContext *ctx);
JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len);
JSValue JS_NewArrayCap (JSContext *ctx, uint32_t cap);
JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values);
int JS_ArrayPush (JSContext *ctx, JSValue *arr_ptr, JSValue val);
JSValue JS_ArrayPop (JSContext *ctx, JSValue obj);

File diff suppressed because it is too large Load Diff

View File

@@ -60,6 +60,26 @@ var streamline = function(ir, log) {
is_record: T_RECORD
}
// simplify_algebra dispatch tables
var self_true_ops = {
eq_int: true, eq_float: true, eq_text: true, eq_bool: true,
is_identical: true,
le_int: true, le_float: true, le_text: true,
ge_int: true, ge_float: true, ge_text: true
}
var self_false_ops = {
ne_int: true, ne_float: true, ne_text: true, ne_bool: true,
lt_int: true, lt_float: true, lt_text: true,
gt_int: true, gt_float: true, gt_text: true
}
var no_clear_ops = {
int: true, access: true, true: true, false: true, move: true, null: true,
jump: true, jump_true: true, jump_false: true, jump_not_null: true,
return: true, disrupt: true,
store_field: true, store_index: true, store_dynamic: true,
push: true, setarg: true, invoke: true, tail_invoke: true
}
// --- Logging support ---
var ir_stats = null
@@ -119,49 +139,30 @@ var streamline = function(ir, log) {
return T_UNKNOWN
}
// track_types reuses write_rules table; move handled specially
var track_types = function(slot_types, instr) {
var op = instr[0]
var rule = null
var src_type = null
if (op == "access") {
slot_types[text(instr[1])] = access_value_type(instr[2])
} else if (op == "int") {
slot_types[text(instr[1])] = T_INT
} else if (op == "true" || op == "false") {
slot_types[text(instr[1])] = T_BOOL
} else if (op == "null") {
slot_types[text(instr[1])] = T_NULL
} else if (op == "move") {
src_type = slot_types[text(instr[2])]
slot_types[text(instr[1])] = src_type != null ? src_type : T_UNKNOWN
} else if (op == "concat") {
slot_types[text(instr[1])] = T_TEXT
} else if (bool_result_ops[op] == true) {
slot_types[text(instr[1])] = T_BOOL
} else if (op == "load_field" || op == "load_index" || op == "load_dynamic") {
slot_types[text(instr[1])] = T_UNKNOWN
} else if (op == "invoke" || op == "tail_invoke") {
slot_types[text(instr[2])] = T_UNKNOWN
} else if (op == "pop" || op == "get") {
slot_types[text(instr[1])] = T_UNKNOWN
} else if (op == "array") {
slot_types[text(instr[1])] = T_ARRAY
} else if (op == "record") {
slot_types[text(instr[1])] = T_RECORD
} else if (op == "function") {
slot_types[text(instr[1])] = T_FUNCTION
} else if (op == "length") {
slot_types[text(instr[1])] = T_INT
} else if (op == "negate" || numeric_ops[op] == true) {
slot_types[text(instr[1])] = T_UNKNOWN
} else if (op == "bitnot" || op == "bitand" || op == "bitor" ||
op == "bitxor" || op == "shl" || op == "shr" || op == "ushr") {
slot_types[text(instr[1])] = T_INT
var typ = null
if (op == "move") {
src_type = slot_types[instr[2]]
slot_types[instr[1]] = src_type != null ? src_type : T_UNKNOWN
return null
}
rule = write_rules[op]
if (rule != null) {
typ = rule[1]
if (typ == null) {
typ = access_value_type(instr[2])
}
slot_types[instr[rule[0]]] = typ
}
return null
}
var slot_is = function(slot_types, slot, typ) {
var known = slot_types[text(slot)]
var known = slot_types[slot]
if (known == null) {
return false
}
@@ -175,24 +176,22 @@ var streamline = function(ir, log) {
}
var merge_backward = function(backward_types, slot, typ) {
var sk = null
var existing = null
if (!is_number(slot)) {
return null
}
sk = text(slot)
existing = backward_types[sk]
existing = backward_types[slot]
if (existing == null) {
backward_types[sk] = typ
backward_types[slot] = typ
} else if (existing != typ && existing != T_UNKNOWN) {
if ((existing == T_INT || existing == T_FLOAT) && typ == T_NUM) {
// Keep more specific
} else if (existing == T_NUM && (typ == T_INT || typ == T_FLOAT)) {
backward_types[sk] = typ
backward_types[slot] = typ
} else if ((existing == T_INT && typ == T_FLOAT) || (existing == T_FLOAT && typ == T_INT)) {
backward_types[sk] = T_NUM
backward_types[slot] = T_NUM
} else {
backward_types[sk] = T_UNKNOWN
backward_types[slot] = T_UNKNOWN
}
}
return null
@@ -201,8 +200,8 @@ var streamline = function(ir, log) {
var seed_params = function(slot_types, param_types, nr_args) {
var j = 1
while (j <= nr_args) {
if (param_types[text(j)] != null) {
slot_types[text(j)] = param_types[text(j)]
if (param_types[j] != null) {
slot_types[j] = param_types[j]
}
j = j + 1
}
@@ -210,10 +209,11 @@ var streamline = function(ir, log) {
}
var seed_writes = function(slot_types, write_types) {
var keys = array(write_types)
var k = 0
while (k < length(keys)) {
slot_types[keys[k]] = write_types[keys[k]]
while (k < length(write_types)) {
if (write_types[k] != null) {
slot_types[k] = write_types[k]
}
k = k + 1
}
return null
@@ -222,7 +222,35 @@ var streamline = function(ir, log) {
// =========================================================
// Pass: infer_param_types — backward type inference
// Scans typed operators to infer immutable parameter types.
// Uses data-driven dispatch: each rule is [pos1, type1] or
// [pos1, type1, pos2, type2] for operand positions to merge.
// =========================================================
var param_rules = {
subtract: [2, T_NUM, 3, T_NUM], multiply: [2, T_NUM, 3, T_NUM],
divide: [2, T_NUM, 3, T_NUM], modulo: [2, T_NUM, 3, T_NUM],
pow: [2, T_NUM, 3, T_NUM], negate: [2, T_NUM],
eq_int: [2, T_INT, 3, T_INT], ne_int: [2, T_INT, 3, T_INT],
lt_int: [2, T_INT, 3, T_INT], gt_int: [2, T_INT, 3, T_INT],
le_int: [2, T_INT, 3, T_INT], ge_int: [2, T_INT, 3, T_INT],
bitand: [2, T_INT, 3, T_INT], bitor: [2, T_INT, 3, T_INT],
bitxor: [2, T_INT, 3, T_INT], shl: [2, T_INT, 3, T_INT],
shr: [2, T_INT, 3, T_INT], ushr: [2, T_INT, 3, T_INT],
bitnot: [2, T_INT],
eq_float: [2, T_FLOAT, 3, T_FLOAT], ne_float: [2, T_FLOAT, 3, T_FLOAT],
lt_float: [2, T_FLOAT, 3, T_FLOAT], gt_float: [2, T_FLOAT, 3, T_FLOAT],
le_float: [2, T_FLOAT, 3, T_FLOAT], ge_float: [2, T_FLOAT, 3, T_FLOAT],
concat: [2, T_TEXT, 3, T_TEXT],
eq_text: [2, T_TEXT, 3, T_TEXT], ne_text: [2, T_TEXT, 3, T_TEXT],
lt_text: [2, T_TEXT, 3, T_TEXT], gt_text: [2, T_TEXT, 3, T_TEXT],
le_text: [2, T_TEXT, 3, T_TEXT], ge_text: [2, T_TEXT, 3, T_TEXT],
eq_bool: [2, T_BOOL, 3, T_BOOL], ne_bool: [2, T_BOOL, 3, T_BOOL],
not: [2, T_BOOL], and: [2, T_BOOL, 3, T_BOOL], or: [2, T_BOOL, 3, T_BOOL],
store_index: [1, T_ARRAY, 2, T_INT], store_field: [1, T_RECORD],
push: [1, T_ARRAY],
load_index: [2, T_ARRAY, 3, T_INT], load_field: [2, T_RECORD],
pop: [2, T_ARRAY]
}
var infer_param_types = function(func) {
var instructions = func.instructions
var nr_args = func.nr_args != null ? func.nr_args : 0
@@ -232,76 +260,36 @@ var streamline = function(ir, log) {
var i = 0
var j = 0
var instr = null
var op = null
var bt = null
var rule = null
if (instructions == null || nr_args == 0) {
return {}
return array(func.nr_slots)
}
num_instr = length(instructions)
backward_types = {}
backward_types = array(func.nr_slots)
i = 0
while (i < num_instr) {
instr = instructions[i]
if (is_array(instr)) {
op = instr[0]
if (op == "subtract" || op == "multiply" ||
op == "divide" || op == "modulo" || op == "pow") {
merge_backward(backward_types, instr[2], T_NUM)
merge_backward(backward_types, instr[3], T_NUM)
} else if (op == "negate") {
merge_backward(backward_types, instr[2], T_NUM)
} else if (op == "eq_int" || op == "ne_int" || op == "lt_int" ||
op == "gt_int" || op == "le_int" || op == "ge_int" ||
op == "bitand" || op == "bitor" || op == "bitxor" ||
op == "shl" || op == "shr" || op == "ushr") {
merge_backward(backward_types, instr[2], T_INT)
merge_backward(backward_types, instr[3], T_INT)
} else if (op == "bitnot") {
merge_backward(backward_types, instr[2], T_INT)
} else if (op == "eq_float" || op == "ne_float" || op == "lt_float" ||
op == "gt_float" || op == "le_float" || op == "ge_float") {
merge_backward(backward_types, instr[2], T_FLOAT)
merge_backward(backward_types, instr[3], T_FLOAT)
} else if (op == "concat" ||
op == "eq_text" || op == "ne_text" || op == "lt_text" ||
op == "gt_text" || op == "le_text" || op == "ge_text") {
merge_backward(backward_types, instr[2], T_TEXT)
merge_backward(backward_types, instr[3], T_TEXT)
} else if (op == "eq_bool" || op == "ne_bool") {
merge_backward(backward_types, instr[2], T_BOOL)
merge_backward(backward_types, instr[3], T_BOOL)
} else if (op == "not") {
merge_backward(backward_types, instr[2], T_BOOL)
} else if (op == "and" || op == "or") {
merge_backward(backward_types, instr[2], T_BOOL)
merge_backward(backward_types, instr[3], T_BOOL)
} else if (op == "store_index") {
merge_backward(backward_types, instr[1], T_ARRAY)
merge_backward(backward_types, instr[2], T_INT)
} else if (op == "store_field") {
merge_backward(backward_types, instr[1], T_RECORD)
} else if (op == "push") {
merge_backward(backward_types, instr[1], T_ARRAY)
} else if (op == "load_index") {
merge_backward(backward_types, instr[2], T_ARRAY)
merge_backward(backward_types, instr[3], T_INT)
} else if (op == "load_field") {
merge_backward(backward_types, instr[2], T_RECORD)
} else if (op == "pop") {
merge_backward(backward_types, instr[2], T_ARRAY)
rule = param_rules[instr[0]]
if (rule != null) {
merge_backward(backward_types, instr[rule[0]], rule[1])
if (length(rule) > 2) {
merge_backward(backward_types, instr[rule[2]], rule[3])
}
}
}
i = i + 1
}
param_types = {}
param_types = array(func.nr_slots)
j = 1
while (j <= nr_args) {
bt = backward_types[text(j)]
bt = backward_types[j]
if (bt != null && bt != T_UNKNOWN) {
param_types[text(j)] = bt
param_types[j] = bt
}
j = j + 1
}
@@ -313,114 +301,85 @@ var streamline = function(ir, log) {
// Scans all instructions to find non-parameter slots where
// every write produces the same type. These types persist
// across label join points.
// Uses data-driven dispatch: each rule is [dest_pos, type].
// =========================================================
var write_rules = {
int: [1, T_INT], true: [1, T_BOOL], false: [1, T_BOOL],
null: [1, T_NULL], access: [1, null],
array: [1, T_ARRAY], record: [1, T_RECORD],
function: [1, T_FUNCTION], length: [1, T_INT],
bitnot: [1, T_INT], bitand: [1, T_INT], bitor: [1, T_INT],
bitxor: [1, T_INT], shl: [1, T_INT], shr: [1, T_INT], ushr: [1, T_INT],
negate: [1, T_UNKNOWN], concat: [1, T_TEXT],
eq: [1, T_BOOL], ne: [1, T_BOOL], lt: [1, T_BOOL],
le: [1, T_BOOL], gt: [1, T_BOOL], ge: [1, T_BOOL], in: [1, T_BOOL],
add: [1, T_UNKNOWN], subtract: [1, T_UNKNOWN], multiply: [1, T_UNKNOWN],
divide: [1, T_UNKNOWN], modulo: [1, T_UNKNOWN], pow: [1, T_UNKNOWN],
move: [1, T_UNKNOWN], load_field: [1, T_UNKNOWN],
load_index: [1, T_UNKNOWN], load_dynamic: [1, T_UNKNOWN],
pop: [1, T_UNKNOWN], get: [1, T_UNKNOWN],
invoke: [2, T_UNKNOWN], tail_invoke: [2, T_UNKNOWN],
eq_int: [1, T_BOOL], ne_int: [1, T_BOOL], lt_int: [1, T_BOOL],
gt_int: [1, T_BOOL], le_int: [1, T_BOOL], ge_int: [1, T_BOOL],
eq_float: [1, T_BOOL], ne_float: [1, T_BOOL], lt_float: [1, T_BOOL],
gt_float: [1, T_BOOL], le_float: [1, T_BOOL], ge_float: [1, T_BOOL],
eq_text: [1, T_BOOL], ne_text: [1, T_BOOL], lt_text: [1, T_BOOL],
gt_text: [1, T_BOOL], le_text: [1, T_BOOL], ge_text: [1, T_BOOL],
eq_bool: [1, T_BOOL], ne_bool: [1, T_BOOL],
eq_tol: [1, T_BOOL], ne_tol: [1, T_BOOL],
not: [1, T_BOOL], and: [1, T_BOOL], or: [1, T_BOOL],
is_int: [1, T_BOOL], is_text: [1, T_BOOL], is_num: [1, T_BOOL],
is_bool: [1, T_BOOL], is_null: [1, T_BOOL], is_identical: [1, T_BOOL],
is_array: [1, T_BOOL], is_func: [1, T_BOOL],
is_record: [1, T_BOOL], is_stone: [1, T_BOOL]
}
var infer_slot_write_types = function(func) {
var instructions = func.instructions
var nr_args = func.nr_args != null ? func.nr_args : 0
var num_instr = 0
var write_types = null
var result = null
var keys = null
var i = 0
var k = 0
var instr = null
var op = null
var slot = 0
var typ = null
var wt = null
var rule = null
if (instructions == null) {
return {}
return array(func.nr_slots)
}
num_instr = length(instructions)
write_types = {}
write_types = array(func.nr_slots)
i = 0
while (i < num_instr) {
instr = instructions[i]
if (!is_array(instr)) {
i = i + 1
continue
if (is_array(instr)) {
rule = write_rules[instr[0]]
if (rule != null) {
slot = instr[rule[0]]
typ = rule[1]
if (typ == null) {
typ = access_value_type(instr[2])
}
if (slot > 0 && slot > nr_args) {
merge_backward(write_types, slot, typ)
}
}
}
op = instr[0]
slot = -1
typ = null
if (op == "int") {
slot = instr[1]
typ = T_INT
} else if (op == "true" || op == "false") {
slot = instr[1]
typ = T_BOOL
} else if (op == "null") {
slot = instr[1]
typ = T_NULL
} else if (op == "access") {
slot = instr[1]
typ = access_value_type(instr[2])
} else if (op == "array") {
slot = instr[1]
typ = T_ARRAY
} else if (op == "record") {
slot = instr[1]
typ = T_RECORD
} else if (op == "function") {
slot = instr[1]
typ = T_FUNCTION
} else if (op == "length") {
slot = instr[1]
typ = T_INT
} else if (op == "bitnot" || op == "bitand" ||
op == "bitor" || op == "bitxor" || op == "shl" ||
op == "shr" || op == "ushr") {
slot = instr[1]
typ = T_INT
} else if (op == "negate") {
slot = instr[1]
typ = T_UNKNOWN
} else if (op == "concat") {
slot = instr[1]
typ = T_TEXT
} else if (bool_result_ops[op] == true) {
slot = instr[1]
typ = T_BOOL
} else if (op == "eq" || op == "ne" || op == "lt" ||
op == "le" || op == "gt" || op == "ge" || op == "in") {
slot = instr[1]
typ = T_BOOL
} else if (op == "add" || op == "subtract" || op == "multiply" ||
op == "divide" || op == "modulo" || op == "pow") {
slot = instr[1]
typ = T_UNKNOWN
} else if (op == "move" || op == "load_field" || op == "load_index" ||
op == "load_dynamic" || op == "pop" || op == "get") {
slot = instr[1]
typ = T_UNKNOWN
} else if (op == "invoke" || op == "tail_invoke") {
slot = instr[2]
typ = T_UNKNOWN
}
if (slot > 0 && slot > nr_args) {
merge_backward(write_types, slot, typ != null ? typ : T_UNKNOWN)
}
i = i + 1
}
// Filter to only slots with known (non-unknown) types
result = {}
keys = array(write_types)
k = 0
while (k < length(keys)) {
wt = write_types[keys[k]]
if (wt != null && wt != T_UNKNOWN) {
result[keys[k]] = wt
while (k < length(write_types)) {
if (write_types[k] == T_UNKNOWN) {
write_types[k] = null
}
k = k + 1
}
return result
return write_types
}
// =========================================================
@@ -431,9 +390,8 @@ var streamline = function(ir, log) {
var eliminate_type_checks = function(func, param_types, write_types, log) {
var instructions = func.instructions
var nr_args = func.nr_args != null ? func.nr_args : 0
var has_params = false
var has_writes = false
var num_instr = 0
var base_types = null
var slot_types = null
var nc = 0
var i = 0
@@ -460,35 +418,32 @@ var streamline = function(ir, log) {
}
num_instr = length(instructions)
// Pre-compute base types: params + write-invariant types
base_types = array(func.nr_slots)
j = 1
while (j <= nr_args) {
if (param_types[text(j)] != null) {
has_params = true
if (param_types[j] != null) {
base_types[j] = param_types[j]
}
j = j + 1
}
j = 0
while (j < length(write_types)) {
if (write_types[j] != null) {
base_types[j] = write_types[j]
}
j = j + 1
}
has_writes = length(array(write_types)) > 0
slot_types = {}
if (has_params) {
seed_params(slot_types, param_types, nr_args)
}
if (has_writes) {
seed_writes(slot_types, write_types)
}
slot_types = array(base_types)
i = 0
while (i < num_instr) {
instr = instructions[i]
if (is_text(instr)) {
slot_types = {}
if (has_params) {
seed_params(slot_types, param_types, nr_args)
}
if (has_writes) {
seed_writes(slot_types, write_types)
}
slot_types = array(base_types)
i = i + 1
continue
}
@@ -525,14 +480,14 @@ var streamline = function(ir, log) {
at: i,
before: [instr, next],
after: [instructions[i], instructions[i + 1]],
why: {slot: src, known_type: slot_types[text(src)], checked_type: checked_type}
why: {slot: src, known_type: slot_types[src], checked_type: checked_type}
}
}
slot_types[text(dest)] = T_BOOL
slot_types[dest] = T_BOOL
i = i + 2
continue
}
src_known = slot_types[text(src)]
src_known = slot_types[src]
if (src_known != null && src_known != T_UNKNOWN && src_known != checked_type) {
if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) {
nc = nc + 1
@@ -550,7 +505,7 @@ var streamline = function(ir, log) {
why: {slot: src, known_type: src_known, checked_type: checked_type}
}
}
slot_types[text(dest)] = T_BOOL
slot_types[dest] = T_BOOL
i = i + 2
continue
}
@@ -569,12 +524,12 @@ var streamline = function(ir, log) {
why: {slot: src, known_type: src_known, checked_type: checked_type}
}
}
slot_types[text(dest)] = T_UNKNOWN
slot_types[dest] = T_UNKNOWN
i = i + 2
continue
}
slot_types[text(dest)] = T_BOOL
slot_types[text(src)] = checked_type
slot_types[dest] = T_BOOL
slot_types[src] = checked_type
i = i + 2
continue
}
@@ -594,14 +549,14 @@ var streamline = function(ir, log) {
at: i,
before: [instr, next],
after: [instructions[i], instructions[i + 1]],
why: {slot: src, known_type: slot_types[text(src)], checked_type: checked_type}
why: {slot: src, known_type: slot_types[src], checked_type: checked_type}
}
}
slot_types[text(dest)] = T_BOOL
slot_types[dest] = T_BOOL
i = i + 2
continue
}
src_known = slot_types[text(src)]
src_known = slot_types[src]
if (src_known != null && src_known != T_UNKNOWN && src_known != checked_type) {
if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) {
nc = nc + 1
@@ -619,7 +574,7 @@ var streamline = function(ir, log) {
why: {slot: src, known_type: src_known, checked_type: checked_type}
}
}
slot_types[text(dest)] = T_BOOL
slot_types[dest] = T_BOOL
i = i + 2
continue
}
@@ -638,17 +593,17 @@ var streamline = function(ir, log) {
why: {slot: src, known_type: src_known, checked_type: checked_type}
}
}
slot_types[text(dest)] = T_BOOL
slot_types[dest] = T_BOOL
i = i + 2
continue
}
slot_types[text(dest)] = T_BOOL
slot_types[dest] = T_BOOL
i = i + 2
continue
}
}
slot_types[text(dest)] = T_BOOL
slot_types[dest] = T_BOOL
i = i + 1
continue
}
@@ -664,7 +619,7 @@ var streamline = function(ir, log) {
pass: "eliminate_type_checks",
rule: "dynamic_to_field",
at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[text(instr[3])]}
why: {slot: instr[3], known_type: slot_types[instr[3]]}
}
}
} else if (slot_is(slot_types, instr[3], T_INT)) {
@@ -675,11 +630,11 @@ var streamline = function(ir, log) {
pass: "eliminate_type_checks",
rule: "dynamic_to_index",
at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[text(instr[3])]}
why: {slot: instr[3], known_type: slot_types[instr[3]]}
}
}
}
slot_types[text(instr[1])] = T_UNKNOWN
slot_types[instr[1]] = T_UNKNOWN
i = i + 1
continue
}
@@ -693,7 +648,7 @@ var streamline = function(ir, log) {
pass: "eliminate_type_checks",
rule: "dynamic_to_field",
at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[text(instr[3])]}
why: {slot: instr[3], known_type: slot_types[instr[3]]}
}
}
} else if (slot_is(slot_types, instr[3], T_INT)) {
@@ -704,7 +659,7 @@ var streamline = function(ir, log) {
pass: "eliminate_type_checks",
rule: "dynamic_to_index",
at: i, before: old_op, after: instr[0],
why: {slot: instr[3], known_type: slot_types[text(instr[3])]}
why: {slot: instr[3], known_type: slot_types[instr[3]]}
}
}
}
@@ -745,14 +700,14 @@ var streamline = function(ir, log) {
}
num_instr = length(instructions)
slot_values = {}
slot_values = array(func.nr_slots)
i = 0
while (i < num_instr) {
instr = instructions[i]
if (is_text(instr)) {
slot_values = {}
slot_values = array(func.nr_slots)
i = i + 1
continue
}
@@ -766,28 +721,25 @@ var streamline = function(ir, log) {
// Track known constant values
if (op == "int") {
slot_values[text(instr[1])] = instr[2]
slot_values[instr[1]] = instr[2]
} else if (op == "access" && is_number(instr[2])) {
slot_values[text(instr[1])] = instr[2]
slot_values[instr[1]] = instr[2]
} else if (op == "true") {
slot_values[text(instr[1])] = true
slot_values[instr[1]] = true
} else if (op == "false") {
slot_values[text(instr[1])] = false
slot_values[instr[1]] = false
} else if (op == "move") {
sv = slot_values[text(instr[2])]
sv = slot_values[instr[2]]
if (sv != null) {
slot_values[text(instr[1])] = sv
slot_values[instr[1]] = sv
} else {
slot_values[text(instr[1])] = null
slot_values[instr[1]] = null
}
}
// Same-slot comparisons
if (is_number(instr[2]) && instr[2] == instr[3]) {
if (op == "eq_int" || op == "eq_float" || op == "eq_text" ||
op == "eq_bool" || op == "is_identical" ||
op == "le_int" || op == "le_float" || op == "le_text" ||
op == "ge_int" || op == "ge_float" || op == "ge_text") {
if (self_true_ops[op] == true) {
instructions[i] = ["true", instr[1], instr[ilen - 2], instr[ilen - 1]]
if (events != null) {
events[] = {
@@ -797,14 +749,11 @@ var streamline = function(ir, log) {
why: {op: op, slot: instr[2]}
}
}
slot_values[text(instr[1])] = true
slot_values[instr[1]] = true
i = i + 1
continue
}
if (op == "ne_int" || op == "ne_float" || op == "ne_text" ||
op == "ne_bool" ||
op == "lt_int" || op == "lt_float" || op == "lt_text" ||
op == "gt_int" || op == "gt_float" || op == "gt_text") {
if (self_false_ops[op] == true) {
instructions[i] = ["false", instr[1], instr[ilen - 2], instr[ilen - 1]]
if (events != null) {
events[] = {
@@ -814,7 +763,7 @@ var streamline = function(ir, log) {
why: {op: op, slot: instr[2]}
}
}
slot_values[text(instr[1])] = false
slot_values[instr[1]] = false
i = i + 1
continue
}
@@ -822,15 +771,10 @@ var streamline = function(ir, log) {
// Clear value tracking for dest-producing ops (not reads-only)
if (op == "invoke" || op == "tail_invoke") {
slot_values[text(instr[2])] = null
} else if (op != "int" && op != "access" && op != "true" &&
op != "false" && op != "move" && op != "null" &&
op != "jump" && op != "jump_true" && op != "jump_false" &&
op != "jump_not_null" && op != "return" && op != "disrupt" &&
op != "store_field" && op != "store_index" &&
op != "store_dynamic" && op != "push" && op != "setarg") {
slot_values[instr[2]] = null
} else if (no_clear_ops[op] != true) {
if (is_number(instr[1])) {
slot_values[text(instr[1])] = null
slot_values[instr[1]] = null
}
}

File diff suppressed because it is too large Load Diff

7
time.c
View File

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

View File

@@ -1,13 +1,13 @@
// epoch = 0000-01-01 00:00:00 +0000
var time = native
var time = use('internal/time_c')
var now = time.now
var computer_zone = time.computer_zone
var computer_dst = time.computer_dst
delete time.now
delete time.computer_zone
delete time.computer_dst
//delete time.now
//delete time.computer_zone
//delete time.computer_dst
time.second = 1
time.minute = 60

View File

@@ -1,72 +1,11 @@
var tokenize = function(src, filename) {
var len = length(src)
var cp = array(array(src), codepoint)
var pos = 0
var row = 0
var col = 0
var tokens = []
// Codepoint constants
def CP_LF = 10
def CP_CR = 13
def CP_TAB = 9
def CP_SPACE = 32
def CP_BANG = 33
def CP_DQUOTE = 34
def CP_HASH = 35
def CP_DOLLAR = 36
def CP_PERCENT = 37
def CP_AMP = 38
def CP_SQUOTE = 39
def CP_LPAREN = 40
def CP_RPAREN = 41
def CP_STAR = 42
def CP_PLUS = 43
def CP_COMMA = 44
def CP_MINUS = 45
def CP_DOT = 46
def CP_SLASH = 47
def CP_0 = 48
def CP_1 = 49
def CP_7 = 55
def CP_9 = 57
def CP_COLON = 58
def CP_SEMI = 59
def CP_LT = 60
def CP_EQ = 61
def CP_GT = 62
def CP_QMARK = 63
def CP_AT = 64
def CP_A = 65
def CP_B = 66
def CP_E = 69
def CP_F = 70
def CP_O = 79
def CP_X = 88
def CP_Z = 90
def CP_LBRACKET = 91
def CP_BSLASH = 92
def CP_RBRACKET = 93
def CP_CARET = 94
def CP_UNDERSCORE = 95
def CP_BACKTICK = 96
def CP_a = 97
def CP_b = 98
def CP_e = 101
def CP_f = 102
def CP_n = 110
def CP_o = 111
def CP_r = 114
def CP_t = 116
def CP_u = 117
def CP_x = 120
def CP_z = 122
def CP_LBRACE = 123
def CP_PIPE = 124
def CP_RBRACE = 125
def CP_TILDE = 126
// Keywords lookup
var keywords = {
if: "if", in: "in", do: "do", go: "go",
@@ -78,21 +17,27 @@ var tokenize = function(src, filename) {
disruption: "disruption"
}
var escape_map = {
n: "\n", t: "\t", r: "\r", "\\": "\\",
"'": "'", "\"": "\"", "`": "`",
"0": character(0)
}
var pk = function() {
if (pos >= len) return -1
return cp[pos]
if (pos >= len) return null
return src[pos]
}
var pk_at = function(n) {
var idx = pos + n
if (idx >= len) return -1
return cp[idx]
if (idx >= len) return null
return src[idx]
}
var adv = function() {
var c = cp[pos]
var c = src[pos]
pos = pos + 1
if (c == CP_LF) {
if (c == "\n") {
row = row + 1
col = 0
} else {
@@ -102,17 +47,17 @@ var tokenize = function(src, filename) {
}
var is_digit = function(c) {
return c >= CP_0 && c <= CP_9
return c >= "0" && c <= "9"
}
var is_hex = function(c) {
return (c >= CP_0 && c <= CP_9) || (c >= CP_a && c <= CP_f) || (c >= CP_A && c <= CP_F)
return (c >= "0" && c <= "9") || (c >= "a" && c <= "f") || (c >= "A" && c <= "F")
}
var hex_val = function(c) {
if (c >= CP_0 && c <= CP_9) return c - CP_0
if (c >= CP_a && c <= CP_f) return c - CP_a + 10
if (c >= CP_A && c <= CP_F) return c - CP_A + 10
if (c >= "0" && c <= "9") return codepoint(c) - codepoint("0")
if (c >= "a" && c <= "f") return codepoint(c) - codepoint("a") + 10
if (c >= "A" && c <= "F") return codepoint(c) - codepoint("A") + 10
return 0
}
@@ -127,7 +72,7 @@ var tokenize = function(src, filename) {
}
var is_alpha = function(c) {
return (c >= CP_a && c <= CP_z) || (c >= CP_A && c <= CP_Z)
return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z")
}
var is_alnum = function(c) {
@@ -135,41 +80,36 @@ var tokenize = function(src, filename) {
}
var is_ident_start = function(c) {
return is_alpha(c) || c == CP_UNDERSCORE || c == CP_DOLLAR
return is_alpha(c) || c == "_" || c == "$"
}
var is_ident_char = function(c) {
return is_alnum(c) || c == CP_UNDERSCORE || c == CP_DOLLAR || c == CP_QMARK || c == CP_BANG
return is_alnum(c) || c == "_" || c == "$" || c == "?" || c == "!"
}
var substr = function(start, end) {
return text(src, start, end)
}
var read_string = function(quote_cp) {
var read_string = function(quote) {
var start = pos
var start_row = row
var start_col = col
var parts = []
var run_start = 0
var esc = 0
var esc = null
var esc_val = null
adv() // skip opening quote
run_start = pos
while (pos < len && pk() != quote_cp) {
if (pk() == CP_BSLASH) {
while (pos < len && pk() != quote) {
if (pk() == "\\") {
if (pos > run_start) push(parts, text(src, run_start, pos))
adv()
esc = adv()
if (esc == CP_n) { push(parts, "\n") }
else if (esc == CP_t) { push(parts, "\t") }
else if (esc == CP_r) { push(parts, "\r") }
else if (esc == CP_BSLASH) { push(parts, "\\") }
else if (esc == CP_SQUOTE) { push(parts, "'") }
else if (esc == CP_DQUOTE) { push(parts, "\"") }
else if (esc == CP_0) { push(parts, character(0)) }
else if (esc == CP_BACKTICK) { push(parts, "`") }
else if (esc == CP_u) { push(parts, read_unicode_escape()) }
else { push(parts, character(esc)) }
esc_val = escape_map[esc]
if (esc_val != null) { push(parts, esc_val) }
else if (esc == "u") { push(parts, read_unicode_escape()) }
else { push(parts, esc) }
run_start = pos
} else {
adv()
@@ -192,33 +132,33 @@ var tokenize = function(src, filename) {
var parts = []
var run_start = 0
var depth = 0
var tc = 0
var q = 0
var tc = null
var q = null
var interp_start = 0
adv() // skip opening backtick
run_start = pos
while (pos < len && pk() != CP_BACKTICK) {
if (pk() == CP_BSLASH && pos + 1 < len) {
while (pos < len && pk() != "`") {
if (pk() == "\\" && pos + 1 < len) {
if (pos > run_start) push(parts, text(src, run_start, pos))
push(parts, text(src, pos, pos + 2))
adv(); adv()
run_start = pos
} else if (pk() == CP_DOLLAR && pos + 1 < len && pk_at(1) == CP_LBRACE) {
} else if (pk() == "$" && pos + 1 < len && pk_at(1) == "{") {
if (pos > run_start) push(parts, text(src, run_start, pos))
interp_start = pos
adv(); adv() // $ {
depth = 1
while (pos < len && depth > 0) {
tc = pk()
if (tc == CP_LBRACE) { depth = depth + 1; adv() }
else if (tc == CP_RBRACE) {
if (tc == "{") { depth = depth + 1; adv() }
else if (tc == "}") {
depth = depth - 1
adv()
}
else if (tc == CP_SQUOTE || tc == CP_DQUOTE || tc == CP_BACKTICK) {
else if (tc == "'" || tc == "\"" || tc == "`") {
q = adv()
while (pos < len && pk() != q) {
if (pk() == CP_BSLASH && pos + 1 < len) adv()
if (pk() == "\\" && pos + 1 < len) adv()
adv()
}
if (pos < len) adv()
@@ -245,24 +185,24 @@ var tokenize = function(src, filename) {
var start_row = row
var start_col = col
var raw = ""
if (pk() == CP_0 && (pk_at(1) == CP_x || pk_at(1) == CP_X)) {
if (pk() == "0" && (pk_at(1) == "x" || pk_at(1) == "X")) {
adv(); adv()
while (pos < len && (is_hex(pk()) || pk() == CP_UNDERSCORE)) adv()
} else if (pk() == CP_0 && (pk_at(1) == CP_b || pk_at(1) == CP_B)) {
while (pos < len && (is_hex(pk()) || pk() == "_")) adv()
} else if (pk() == "0" && (pk_at(1) == "b" || pk_at(1) == "B")) {
adv(); adv()
while (pos < len && (pk() == CP_0 || pk() == CP_1 || pk() == CP_UNDERSCORE)) adv()
} else if (pk() == CP_0 && (pk_at(1) == CP_o || pk_at(1) == CP_O)) {
while (pos < len && (pk() == "0" || pk() == "1" || pk() == "_")) adv()
} else if (pk() == "0" && (pk_at(1) == "o" || pk_at(1) == "O")) {
adv(); adv()
while (pos < len && pk() >= CP_0 && pk() <= CP_7) adv()
while (pos < len && pk() >= "0" && pk() <= "7") adv()
} else {
while (pos < len && (is_digit(pk()) || pk() == CP_UNDERSCORE)) adv()
if (pos < len && pk() == CP_DOT) {
while (pos < len && (is_digit(pk()) || pk() == "_")) adv()
if (pos < len && pk() == ".") {
adv()
while (pos < len && (is_digit(pk()) || pk() == CP_UNDERSCORE)) adv()
while (pos < len && (is_digit(pk()) || pk() == "_")) adv()
}
if (pos < len && (pk() == CP_e || pk() == CP_E)) {
if (pos < len && (pk() == "e" || pk() == "E")) {
adv()
if (pos < len && (pk() == CP_PLUS || pk() == CP_MINUS)) adv()
if (pos < len && (pk() == "+" || pk() == "-")) adv()
while (pos < len && is_digit(pk())) adv()
}
}
@@ -305,12 +245,12 @@ var tokenize = function(src, filename) {
var start_row = row
var start_col = col
var raw = ""
if (pk_at(1) == CP_SLASH) {
while (pos < len && pk() != CP_LF && pk() != CP_CR) adv()
if (pk_at(1) == "/") {
while (pos < len && pk() != "\n" && pk() != "\r") adv()
} else {
adv(); adv() // skip /*
while (pos < len) {
if (pk() == CP_STAR && pk_at(1) == CP_SLASH) {
if (pk() == "*" && pk_at(1) == "/") {
adv(); adv()
break
}
@@ -359,144 +299,144 @@ var tokenize = function(src, filename) {
var start_row = 0
var start_col = 0
var raw = ""
if (c == -1) return false
if (c == null) return false
if (c == CP_LF) {
if (c == "\n") {
start = pos; start_row = row; start_col = col
adv()
push(tokens, { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" })
return true
}
if (c == CP_CR) {
if (c == "\r") {
start = pos; start_row = row; start_col = col
adv()
if (pos < len && pk() == CP_LF) adv()
if (pos < len && pk() == "\n") adv()
push(tokens, { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" })
return true
}
if (c == CP_SPACE || c == CP_TAB) {
if (c == " " || c == "\t") {
start = pos; start_row = row; start_col = col
while (pos < len && (pk() == CP_SPACE || pk() == CP_TAB)) adv()
while (pos < len && (pk() == " " || pk() == "\t")) adv()
raw = substr(start, pos)
push(tokens, { kind: "space", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: raw })
return true
}
if (c == CP_SQUOTE || c == CP_DQUOTE) { read_string(c); return true }
if (c == CP_BACKTICK) { read_template(); return true }
if (c == "'" || c == "\"") { read_string(c); return true }
if (c == "`") { read_template(); return true }
if (is_digit(c)) { read_number(); return true }
if (c == CP_DOT && is_digit(pk_at(1))) { read_number(); return true }
if (c == "." && is_digit(pk_at(1))) { read_number(); return true }
if (is_ident_start(c)) { read_name(); return true }
if (c == CP_SLASH) {
if (pk_at(1) == CP_SLASH || pk_at(1) == CP_STAR) { read_comment(); return true }
if (pk_at(1) == CP_EQ) { emit_op("/=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
if (c == "/") {
if (pk_at(1) == "/" || pk_at(1) == "*") { read_comment(); return true }
if (pk_at(1) == "=") { emit_op("/=", 2); return true }
if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("/", 1); return true
}
if (c == CP_STAR) {
if (pk_at(1) == CP_STAR) {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("**=", 3); return true }
if (c == "*") {
if (pk_at(1) == "*") {
if (pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(2) == "=") { emit_op("**=", 3); return true }
emit_op("**", 2); return true
}
if (pk_at(1) == CP_EQ) { emit_op("*=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
if (pk_at(1) == "=") { emit_op("*=", 2); return true }
if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("*", 1); return true
}
if (c == CP_PERCENT) {
if (pk_at(1) == CP_EQ) { emit_op("%=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
if (c == "%") {
if (pk_at(1) == "=") { emit_op("%=", 2); return true }
if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("%", 1); return true
}
if (c == CP_PLUS) {
if (pk_at(1) == CP_EQ) { emit_op("+=", 2); return true }
if (pk_at(1) == CP_PLUS) { emit_op("++", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
if (c == "+") {
if (pk_at(1) == "=") { emit_op("+=", 2); return true }
if (pk_at(1) == "+") { emit_op("++", 2); return true }
if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("+", 1); return true
}
if (c == CP_MINUS) {
if (pk_at(1) == CP_EQ) { emit_op("-=", 2); return true }
if (pk_at(1) == CP_MINUS) { emit_op("--", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
if (c == "-") {
if (pk_at(1) == "=") { emit_op("-=", 2); return true }
if (pk_at(1) == "-") { emit_op("--", 2); return true }
if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("-", 1); return true
}
if (c == CP_LT) {
if (pk_at(1) == CP_EQ && pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(1) == CP_EQ) { emit_op("<=", 2); return true }
if (pk_at(1) == CP_LT) {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("<<=", 3); return true }
if (c == "<") {
if (pk_at(1) == "=" && pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(1) == "=") { emit_op("<=", 2); return true }
if (pk_at(1) == "<") {
if (pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(2) == "=") { emit_op("<<=", 3); return true }
emit_op("<<", 2); return true
}
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("<", 1); return true
}
if (c == CP_GT) {
if (pk_at(1) == CP_EQ && pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(1) == CP_EQ) { emit_op(">=", 2); return true }
if (pk_at(1) == CP_GT) {
if (pk_at(2) == CP_GT) {
if (pk_at(3) == CP_BANG) { emit_ident(4); return true }
if (pk_at(3) == CP_EQ) { emit_op(">>>=", 4); return true }
if (c == ">") {
if (pk_at(1) == "=" && pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(1) == "=") { emit_op(">=", 2); return true }
if (pk_at(1) == ">") {
if (pk_at(2) == ">") {
if (pk_at(3) == "!") { emit_ident(4); return true }
if (pk_at(3) == "=") { emit_op(">>>=", 4); return true }
emit_op(">>>", 3); return true
}
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op(">>=", 3); return true }
if (pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(2) == "=") { emit_op(">>=", 3); return true }
emit_op(">>", 2); return true
}
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op(">", 1); return true
}
if (c == CP_EQ) {
if (pk_at(1) == CP_EQ) {
if (pk_at(2) == CP_EQ) { emit_op("===", 3); return true }
if (c == "=") {
if (pk_at(1) == "=") {
if (pk_at(2) == "=") { emit_op("===", 3); return true }
emit_op("==", 2); return true
}
if (pk_at(1) == CP_GT) { emit_op("=>", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
if (pk_at(1) == ">") { emit_op("=>", 2); return true }
if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("=", 1); return true
}
if (c == CP_BANG) {
if (pk_at(1) == CP_EQ) {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("!==", 3); return true }
if (c == "!") {
if (pk_at(1) == "=") {
if (pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(2) == "=") { emit_op("!==", 3); return true }
emit_op("!=", 2); return true
}
emit_op("!", 1); return true
}
if (c == CP_AMP) {
if (pk_at(1) == CP_AMP) {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("&&=", 3); return true }
if (c == "&") {
if (pk_at(1) == "&") {
if (pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(2) == "=") { emit_op("&&=", 3); return true }
emit_op("&&", 2); return true
}
if (pk_at(1) == CP_EQ) { emit_op("&=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
if (pk_at(1) == "=") { emit_op("&=", 2); return true }
if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("&", 1); return true
}
if (c == CP_PIPE) {
if (pk_at(1) == CP_PIPE) {
if (pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (pk_at(2) == CP_EQ) { emit_op("||=", 3); return true }
if (c == "|") {
if (pk_at(1) == "|") {
if (pk_at(2) == "!") { emit_ident(3); return true }
if (pk_at(2) == "=") { emit_op("||=", 3); return true }
emit_op("||", 2); return true
}
if (pk_at(1) == CP_EQ) { emit_op("|=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
if (pk_at(1) == "=") { emit_op("|=", 2); return true }
if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("|", 1); return true
}
if (c == CP_CARET) {
if (pk_at(1) == CP_EQ) { emit_op("^=", 2); return true }
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
if (c == "^") {
if (pk_at(1) == "=") { emit_op("^=", 2); return true }
if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("^", 1); return true
}
if (c == CP_LBRACKET) {
if (pk_at(1) == CP_RBRACKET && pk_at(2) == CP_BANG) { emit_ident(3); return true }
if (c == "[") {
if (pk_at(1) == "]" && pk_at(2) == "!") { emit_ident(3); return true }
emit_op("[", 1); return true
}
if (c == CP_TILDE) {
if (pk_at(1) == CP_BANG) { emit_ident(2); return true }
if (c == "~") {
if (pk_at(1) == "!") { emit_ident(2); return true }
emit_op("~", 1); return true
}
emit_op(character(c), 1)
emit_op(c, 1)
return true
}
@@ -508,7 +448,7 @@ var tokenize = function(src, filename) {
// EOF token
push(tokens, { kind: "eof", at: pos, from_row: row, from_column: col, to_row: row, to_column: col })
return {filename: filename, tokens: tokens, cp: cp}
return {filename: filename, tokens: tokens}
}
return tokenize

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,8 @@ sections:
url: "/docs/requestors/"
- title: "Packages"
url: "/docs/packages/"
- title: "Shop Architecture"
url: "/docs/shop/"
- title: "Reference"
pages:
- title: "Built-in Functions"

View File

@@ -29,7 +29,8 @@ static const JSCFunctionListEntry js_wildstar_funcs[] = {
};
JSValue js_wildstar_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_wildstar_funcs, countof(js_wildstar_funcs));
return mod;
JS_FRAME(js);
JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_wildstar_funcs, countof(js_wildstar_funcs));
JS_RETURN(mod.val);
}