mach loading

This commit is contained in:
2026-02-13 07:26:49 -06:00
parent 4f18a0b524
commit 77fa058135
11 changed files with 7940 additions and 8637 deletions

View File

@@ -65,6 +65,7 @@ function check_mach_stale() {
"streamline.cm",
"qbe.cm",
"qbe_emit.cm",
"verify_ir.cm",
"internal/bootstrap.cm",
"internal/engine.cm"
]
@@ -140,38 +141,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 +153,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
@@ -199,60 +173,6 @@ function run_ast_noopt(name, ast, env) {
return mach_eval_mcode(name, json.encode(compiled), env)
}
// use() with ƿit pipeline for .cm modules
function use_fn(path) {
var file_path = null
var mach_path = null
var mcode_path = null
var mcode_json = null
var data = null
var script = null
var ast = null
var result = null
if (use_cache[path])
return use_cache[path]
// Try .cm.mach bytecode first (CWD then core_path)
mach_path = path + '.cm.mach'
if (!fd.is_file(mach_path))
mach_path = core_path + '/' + path + '.cm.mach'
if (fd.is_file(mach_path)) {
data = fd.slurp(mach_path)
result = mach_load(data, {use: use_fn})
use_cache[path] = result
return result
}
// Try .cm.mcode JSON IR (CWD then core_path)
mcode_path = path + '.cm.mcode'
if (!fd.is_file(mcode_path))
mcode_path = core_path + '/' + path + '.cm.mcode'
if (fd.is_file(mcode_path)) {
mcode_json = text(fd.slurp(mcode_path))
result = mach_eval_mcode(path, mcode_json, {use: use_fn})
use_cache[path] = result
return result
}
// Try .cm source (CWD then core_path)
file_path = path + '.cm'
if (!fd.is_file(file_path))
file_path = core_path + '/' + path + '.cm'
if (fd.is_file(file_path)) {
script = text(fd.slurp(file_path))
ast = analyze(script, file_path)
result = run_ast(path, ast, {use: use_fn})
use_cache[path] = result
return result
}
// Fallback to embedded C module
result = use_embed(replace(path, '/', '_'))
use_cache[path] = result
return result
}
// Helper to load engine.cm and run it with given env
function load_engine(env) {
var engine_path = core_path + '/internal/engine.cm.mach'
@@ -298,13 +218,15 @@ 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
})
} 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
})
}

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) 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'

File diff suppressed because it is too large Load Diff

View File

@@ -425,6 +425,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 +568,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 +615,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),

View File

@@ -379,6 +379,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
@@ -433,7 +459,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 +469,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 +515,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 +629,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 +697,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 +730,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 +755,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 +766,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 +786,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 +795,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 +925,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 +960,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 }