279 lines
7.9 KiB
Plaintext
279 lines
7.9 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
|
|
}
|
|
} 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
|
|
}
|
|
}
|