Files
cell/internal/bootstrap.cm

281 lines
8.0 KiB
Plaintext

// Minimal bootstrap — seeds the content-addressed cache
// Only runs on cold start (C runtime couldn't find engine in cache)
// Hidden vars: os, core_path, shop_path, native_mode (optional)
var load_internal = os.load_internal
function use_embed(name) {
return load_internal("js_core_" + name + "_use")
}
var fd = use_embed('internal_fd')
var json_mod = use_embed('json')
var crypto = use_embed('internal_crypto')
function content_hash(content) {
var data = content
if (!is_blob(data)) data = stone(blob(text(data)))
return text(crypto.blake2(data), 'h')
}
function cache_path(hash) {
if (!shop_path) return null
return shop_path + '/build/' + hash
}
function ensure_build_dir() {
if (!shop_path) return null
var dir = shop_path + '/build'
if (!fd.is_dir(dir)) fd.mkdir(dir)
return dir
}
// Load seed pipeline from boot/
function boot_load(name) {
var mcode_path = core_path + '/boot/' + name + '.cm.mcode'
var mcode_blob = null
var mach_blob = null
if (!fd.is_file(mcode_path)) {
os.print("error: missing seed: " + name + "\n")
disrupt
}
mcode_blob = fd.slurp(mcode_path)
mach_blob = mach_compile_mcode_bin(name, text(mcode_blob))
return mach_load(mach_blob, stone({use: use_embed}))
}
var tokenize_mod = boot_load("tokenize")
var parse_mod = boot_load("parse")
var fold_mod = boot_load("fold")
var mcode_mod = boot_load("mcode")
var streamline_mod = boot_load("streamline")
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 msg = null
var has_errors = ast.errors != null && length(ast.errors) > 0
if (has_errors) {
while (_i < length(ast.errors)) {
e = ast.errors[_i]
msg = e.message
if (e.line != null && e.column != null)
os.print(`${filename}:${text(e.line)}:${text(e.column)}: error: ${msg}\n`)
else
os.print(`${filename}: error: ${msg}\n`)
_i = _i + 1
}
disrupt
}
return fold_mod(ast)
}
function compile_and_cache(name, source_path) {
var source_blob = fd.slurp(source_path)
var hash = content_hash(source_blob)
var cached = cache_path(hash)
var ast = null
var compiled = null
var mcode_json = null
var mach_blob = null
if (cached && fd.is_file(cached)) return
ast = analyze(text(source_blob), source_path)
compiled = streamline_mod(mcode_mod(ast))
mcode_json = json_mod.encode(compiled)
mach_blob = mach_compile_mcode_bin(name, mcode_json)
if (cached) {
ensure_build_dir()
fd.slurpwrite(cached, mach_blob)
}
}
// --- Native compilation support ---
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
}
function detect_cc() {
var platform = os.platform()
if (platform == 'macOS') return 'clang'
return 'cc'
}
// Compute native dylib cache path matching build.cm's scheme:
// cache_path(native_cache_content(src, target, ''), SALT_NATIVE)
function native_dylib_cache_path(src, target) {
var native_key = src + '\n' + target + '\nnative\n'
var full_key = native_key + '\nnative'
return cache_path(content_hash(full_key))
}
// Compile a module to a native dylib and cache it
var _qbe_mod = null
var _qbe_emit_mod = null
var _host_target = null
var _cc = null
var _is_darwin = false
var _rt_compiled = false
function compile_native_cached(name, source_path) {
var source_blob = fd.slurp(source_path)
var src = text(source_blob)
var dylib_path = native_dylib_cache_path(src, _host_target)
var ast = null
var compiled = null
var il_parts = null
var helpers_il = null
var all_fns = null
var full_il = null
var asm_text = null
var tmp = null
var rc = null
var rt_o = null
var qbe_rt_path = null
var link_cmd = null
if (dylib_path && fd.is_file(dylib_path)) {
os.print("bootstrap: native cache hit: " + name + "\n")
return
}
var t0 = null
var t1 = null
os.print("bootstrap: compiling native: " + name + "\n")
t0 = os.now()
ast = analyze(src, source_path)
compiled = streamline_mod(mcode_mod(ast))
t1 = os.now()
os.print(" [" + name + "] pipeline (tok+parse+fold+mcode+streamline): " + text((t1 - t0) / 1000000) + "ms\n")
t0 = os.now()
il_parts = _qbe_emit_mod(compiled, _qbe_mod, null)
t1 = os.now()
os.print(" [" + name + "] qbe_emit: " + text((t1 - t0) / 1000000) + "ms\n")
helpers_il = (il_parts.helpers && length(il_parts.helpers) > 0)
? text(il_parts.helpers, "\n") : ""
all_fns = text(il_parts.functions, "\n")
full_il = il_parts.data + "\n\n" + helpers_il + "\n\n" + all_fns
t0 = os.now()
asm_text = os.qbe(full_il)
t1 = os.now()
os.print(" [" + name + "] os.qbe (QBE compile): " + text((t1 - t0) / 1000000) + "ms\n")
tmp = '/tmp/cell_boot_' + name
fd.slurpwrite(tmp + '.s', stone(blob(asm_text)))
t0 = os.now()
rc = os.system(_cc + ' -c ' + tmp + '.s -o ' + tmp + '.o')
t1 = os.now()
os.print(" [" + name + "] clang -c: " + text((t1 - t0) / 1000000) + "ms\n")
if (rc != 0) {
os.print("error: assembly failed for " + name + "\n")
disrupt
}
// Compile QBE runtime stubs (once)
rt_o = '/tmp/cell_qbe_rt.o'
if (!_rt_compiled && !fd.is_file(rt_o)) {
qbe_rt_path = core_path + '/src/qbe_rt.c'
rc = os.system(_cc + ' -c ' + qbe_rt_path + ' -o ' + rt_o + ' -fPIC')
if (rc != 0) {
os.print("error: qbe_rt compilation failed\n")
disrupt
}
_rt_compiled = true
}
// Link dylib
ensure_build_dir()
link_cmd = _cc + ' -shared -fPIC'
if (_is_darwin)
link_cmd = link_cmd + ' -undefined dynamic_lookup'
link_cmd = link_cmd + ' ' + tmp + '.o ' + rt_o + ' -o ' + dylib_path
t0 = os.now()
rc = os.system(link_cmd)
t1 = os.now()
os.print(" [" + name + "] clang -shared (link): " + text((t1 - t0) / 1000000) + "ms\n")
if (rc != 0) {
os.print("error: linking failed for " + name + "\n")
disrupt
}
}
// --- Main bootstrap logic ---
// Check if native_mode was passed from C runtime
var _native = false
var _check_nm = function() {
if (native_mode) _native = true
} disruption {}
_check_nm()
var _targets = null
var _ti = 0
var _te = null
if (_native) {
// Native path: compile everything to native dylibs
_qbe_mod = boot_load("qbe")
_qbe_emit_mod = boot_load("qbe_emit")
_host_target = detect_host_target()
_cc = detect_cc()
_is_darwin = os.platform() == 'macOS'
if (!_host_target) {
os.print("error: could not detect host target for native compilation\n")
disrupt
}
// Also seed bytecode cache for engine (so non-native path still works)
compile_and_cache("engine", core_path + '/internal/engine.cm')
// Compile pipeline modules + qbe/qbe_emit + engine to native dylibs
_targets = [
{name: "tokenize", path: "tokenize.cm"},
{name: "parse", path: "parse.cm"},
{name: "fold", path: "fold.cm"},
{name: "mcode", path: "mcode.cm"},
{name: "streamline", path: "streamline.cm"},
{name: "qbe", path: "qbe.cm"},
{name: "qbe_emit", path: "qbe_emit.cm"},
{name: "engine", path: "internal/engine.cm"}
]
_ti = 0
while (_ti < length(_targets)) {
_te = _targets[_ti]
compile_native_cached(_te.name, core_path + '/' + _te.path)
_ti = _ti + 1
}
os.print("bootstrap: native cache seeded\n")
} else {
// Bytecode path: seed cache with everything engine needs
_targets = [
{name: "tokenize", path: "tokenize.cm"},
{name: "parse", path: "parse.cm"},
{name: "fold", path: "fold.cm"},
{name: "mcode", path: "mcode.cm"},
{name: "streamline", path: "streamline.cm"},
{name: "engine", path: "internal/engine.cm"}
]
_ti = 0
while (_ti < length(_targets)) {
_te = _targets[_ti]
compile_and_cache(_te.name, core_path + '/' + _te.path)
_ti = _ti + 1
}
os.print("bootstrap: cache seeded\n")
}