bootstrap init

This commit is contained in:
2026-02-16 01:33:48 -06:00
parent 46c345d34e
commit 788ea98651
25 changed files with 3943 additions and 410026 deletions

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,153 +0,0 @@
// 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_core_internal_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_core_" + 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_core_" + 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})

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -9,18 +9,36 @@ The shop is the module resolution and loading engine behind `use()`. It handles
## Startup Pipeline
When `pit` runs a program, three layers bootstrap in sequence:
When `pit` runs a program, startup takes one of two paths:
### Fast path (warm cache)
```
bootstrap.cm → engine.cm → shop.cm → user program
C runtime → engine.cm (from cache) → 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.
The C runtime hashes the source of `internal/engine.cm` with BLAKE2 and looks up the hash in the content-addressed cache (`~/.pit/build/<hash>`). On a cache hit, engine.cm loads directly — no bootstrap involved.
**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')`.
### Cold path (first run or cache cleared)
```
C runtime → bootstrap.cm → (seeds cache) → engine.cm (from cache) → shop.cm → user program
```
On a cache miss, the C runtime loads `boot/bootstrap.cm.mcode` (a pre-compiled seed). Bootstrap compiles engine.cm and the pipeline modules (tokenize, parse, fold, mcode, streamline) from source and caches the results. The C runtime then retries the engine cache lookup, which now succeeds.
### Engine
**engine.cm** is self-sufficient. It loads its own compilation pipeline from the content-addressed cache, with fallback to the pre-compiled seeds in `boot/`. It defines `analyze()` (source to AST), `compile_to_blob()` (AST to binary blob), and `use_core()` for loading core modules. It creates the actor runtime and loads shop.cm via `use_core('internal/shop')`.
### Shop
**shop.cm** receives its dependencies through the module environment — `analyze`, `run_ast_fn`, `use_cache`, `shop_path`, `runtime_env`, `content_hash`, `cache_path`, and others. It defines `Shop.use()`, which is the function behind every `use()` call in user code.
### Cache invalidation
All caching is content-addressed by BLAKE2 hash of the source. When any source file changes, its hash changes and the old cache entry is simply never looked up again. No manual invalidation is needed. To force a full rebuild, delete `~/.pit/build/`.
## Module Resolution
When `use('path')` is called from a package context, the shop resolves the module through a multi-layer search. Both the `.cm` script file and C symbol are resolved independently, and the one with the narrowest scope wins.
@@ -90,7 +108,7 @@ This scheme provides automatic cache invalidation: when source changes, its hash
### 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.
Core modules loaded via `use_core()` in engine.cm follow the same content-addressed pattern. On first use, a module is compiled from source and cached by the BLAKE2 hash of its source content. Subsequent loads with unchanged source hit the cache directly.
User scripts (`.ce` files) are also cached. The first run compiles and caches; subsequent runs with unchanged source load from cache.
@@ -196,9 +214,10 @@ If `shop.toml` is missing or has no `[policy]` section, all methods are enabled
| File | Role |
|------|------|
| `internal/bootstrap.cm` | Loads compiler, defines `analyze()` and `compile_to_blob()` |
| `internal/engine.cm` | Actor runtime, `use_core()`, environment setup |
| `internal/bootstrap.cm` | Minimal cache seeder (cold start only) |
| `internal/engine.cm` | Self-sufficient entry point: compilation pipeline, actor runtime, `use_core()` |
| `internal/shop.cm` | Module resolution, compilation, caching, C extension loading |
| `internal/os.c` | OS intrinsics: dylib ops, internal symbol lookup, embedded modules |
| `package.cm` | Package directory detection, alias resolution, file listing |
| `link.cm` | Development link management (link.toml read/write) |
| `boot/*.cm.mcode` | Pre-compiled pipeline seeds (tokenize, parse, fold, mcode, bootstrap) |

View File

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

View File

@@ -1,112 +0,0 @@
# Fix Compilation Pipeline Bootstrap
## Problem
After merging `fix_gc` into `pitweb`, the compilation pipeline `.cm` source files
(tokenize.cm, parse.cm, fold.cm, mcode.cm, streamline.cm) cannot bootstrap themselves.
The old pitweb pipeline mcode compiles the merged `.cm` source without errors, but the
resulting new pipeline mcode is **semantically broken** — it can't even compile
`var x = 42; print(x)`.
Both branches worked independently. The merge introduced no syntax errors, but the old
pitweb compiler produces incorrect bytecode from the merged pipeline source. This is a
classic bootstrapping problem: the new pipeline needs a compatible compiler to build
itself, but the only available compiler (old pitweb) miscompiles it.
## Current State
- `boot/tokenize.cm.mcode` through `boot/streamline.cm.mcode` contain the **old pitweb**
pipeline mcode (pre-merge). These pass 641/641 vm_suite tests.
- All other boot mcode files (engine, bootstrap, seed_bootstrap, plus core modules like
fd, time, toml, etc.) are compiled from the merged source and work correctly.
- The merged pipeline `.cm` source has changes from fix_gc that are **not active** — the
runtime uses the old pitweb pipeline mcode.
**The old pitweb pipeline is NOT fully working.** While it passes the test suite, it
miscompiles nested function declarations. This breaks:
- `toml.encode()` — the encoder uses nested `function` declarations inside `encode_toml`
- `Shop.save_lock()` — calls `toml.encode()`, so any lock.toml mutation fails
- Any other `.cm` module that uses nested named function declarations
This means the **ID-based package symbol naming** (Phase 2 in the plan) is blocked: it
needs `save_lock()` to persist package IDs to lock.toml.
The shop.cm changes for ID-based naming are already written and correct — they just need
a working pipeline underneath. Once the pipeline is fixed, the ID system will work.
## What Changed in the Pipeline
The fix_gc merge brought these changes to the pipeline `.cm` files:
- **mcode.cm**: Type-guarded arithmetic (`emit_add_decomposed` now generates `is_text`/`is_num`
checks instead of letting the VM dispatch), `emit_numeric_binop` for subtract/multiply/etc.,
`sensory_ops` lookup table, array/record literal count args (`["array", dest, count]`
instead of `["array", dest, 0]`)
- **fold.cm**: Lookup tables (`binary_ops`, `unary_ops`, `assign_ops`, etc.) replacing
if-chains, combined `"array"` and `"text literal"` handling
- **tokenize.cm**: ~500 lines of changes
- **streamline.cm**: ~700 lines of changes
- **parse.cm**: ~40 lines of changes (minor)
## Regen Flags
`regen.ce` now has two modes:
```
./cell --dev --seed regen # default: skip pipeline files
./cell --dev --seed regen --all # include pipeline files (tokenize/parse/fold/mcode/streamline)
```
The default mode is safe — it regenerates everything except the 5 pipeline files,
preserving the working old pitweb pipeline mcode.
## How to Fix
The goal is to get the merged pipeline `.cm` source to produce working mcode when
compiled by the current (old pitweb) pipeline. The process:
1. Start from the current repo state (old pitweb pipeline mcode in boot/)
2. Edit one or more pipeline `.cm` files to fix the issue
3. Regen with `--all` to recompile everything including pipeline:
```
./cell --dev --seed regen --all
```
4. Test the new pipeline with a simple sanity check:
```
rm -rf .cell/build/*
echo 'var x = 42; print(x)' > /tmp/test.ce
./cell --dev --seed /tmp/test
```
5. If that works, run the full test suite:
```
rm -rf .cell/build/*
./cell --dev vm_suite
```
6. If tests pass, regen again (the new pipeline compiles itself):
```
./cell --dev --seed regen --all
```
7. Repeat steps 4-6 until **idempotent** — two consecutive `regen --all` runs produce
identical boot mcode and all tests pass.
## Debugging Tips
- The old pitweb pipeline mcode is always available via:
```
git checkout HEAD^1 -- boot/tokenize.cm.mcode boot/parse.cm.mcode \
boot/fold.cm.mcode boot/mcode.cm.mcode boot/streamline.cm.mcode
```
- Use `--seed` mode for testing compilation — it bypasses the engine entirely and
loads the pipeline directly from boot mcode.
- The failure mode is silent: the old compiler compiles the new source without errors
but produces wrong bytecode.
- Known broken patterns with the old pitweb pipeline:
- `var x = 42; print(x)` fails when compiled by the regenned pipeline mcode
- Nested named function declarations (`function foo() {}` inside another function)
produce "not a function" errors — this breaks `toml.encode()`
- Test with: `echo 'var toml = use("toml"); print(toml.encode({a: 1}))' > /tmp/t.ce && ./cell --dev /tmp/t.ce`
- The most likely culprits are the mcode.cm changes (type-guarded arithmetic, array/record
count args) since these change the bytecode format. The fold.cm changes (lookup tables)
are more likely safe refactors.

View File

@@ -1,22 +1,15 @@
// Hidden vars come from env:
// CLI mode (cell_init): os, args, core_path, shop_path
// Actor spawn (script_startup): os, json, actorsym, init, core_path, shop_path
// args[0] = script name, args[1..] = user args
// 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
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 = use_embed('json')
var json_mod = 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')
}
@@ -29,250 +22,84 @@ function cache_path(hash) {
function ensure_build_dir() {
if (!shop_path) return null
var dir = shop_path + '/build'
if (!fd.is_dir(dir)) {
fd.mkdir(dir)
}
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) {
if (use_cache[path])
return use_cache[path]
var result = use_embed(replace(path, '/', '_'))
use_cache[path] = result
return result
}
// Load a module from cached .mach or .mcode bytecode
function boot_load(name, env) {
var mcode_path = core_path + '/boot/' + name + ".cm.mcode"
// Load seed pipeline from boot/ (tokenize, parse, mcode only)
function boot_load(name) {
var mcode_path = core_path + '/boot/' + name + '.cm.mcode'
var mcode_blob = null
var hash = null
var cached = null
var mcode_json = null
var mach_blob = null
if (fd.is_file(mcode_path)) {
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)
if (!fd.is_file(mcode_path)) {
print("error: missing seed: " + name + "\n")
disrupt
}
print("error: missing bootstrap bytecode: " + name + "\n")
disrupt
mcode_blob = fd.slurp(mcode_path)
mach_blob = mach_compile_mcode_bin(name, text(mcode_blob))
return mach_load(mach_blob, {use: use_embed})
}
var boot_env = {use: use_basic}
var tokenize_mod = boot_load("tokenize", boot_env)
var parse_mod = boot_load("parse", boot_env)
var fold_mod = boot_load("fold", boot_env)
use_cache['tokenize'] = tokenize_mod
use_cache['parse'] = parse_mod
use_cache['fold'] = fold_mod
var tokenize_mod = boot_load("tokenize")
var parse_mod = boot_load("parse")
var fold_mod = boot_load("fold")
var mcode_mod = boot_load("mcode")
// Always load mcode compiler module
var mcode_mod = boot_load("mcode", boot_env)
use_cache['mcode'] = mcode_mod
use_cache['core/mcode'] = mcode_mod
var streamline_mod = null
// Warn if any .cm source is newer than its compiled bytecode
function check_mach_stale() {
var sources = [
{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 mcode_path = null
var cm_stat = null
var compiled_stat = null
var entry = null
while (_i < length(sources)) {
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)
cm_stat = fd.stat(cm_path)
if (cm_stat.mtime > compiled_stat.mtime) {
push(stale, entry.src)
}
}
_i = _i + 1
}
if (length(stale) > 0) {
print("warning: bytecode is stale for: " + text(stale, ", ") + "\n")
print("run 'make regen' to update\n")
}
}
check_mach_stale()
// analyze: tokenize + parse, check for errors
function analyze(src, filename) {
var tok_result = tokenize_mod(src, filename)
var ast = parse_mod(tok_result.tokens, src, filename, tokenize_mod)
var _i = 0
var prev_line = -1
var prev_msg = null
var e = null
var msg = null
var line = null
var col = null
var has_errors = ast.errors != null && length(ast.errors) > 0
if (has_errors) {
while (_i < length(ast.errors)) {
e = ast.errors[_i]
msg = e.message
line = e.line
col = e.column
if (msg != prev_msg || line != prev_line) {
if (line != null && col != null) {
print(`${filename}:${text(line)}:${text(col)}: error: ${msg}`)
} else {
print(`${filename}: error: ${msg}`)
}
}
prev_line = line
prev_msg = msg
if (e.line != null && e.column != null)
print(`${filename}:${text(e.line)}:${text(e.column)}: error: ${msg}`)
else
print(`${filename}: error: ${msg}`)
_i = _i + 1
}
disrupt
}
ast = fold_mod(ast)
return ast
return fold_mod(ast)
}
// Load optimization pipeline modules (needs analyze to be defined)
streamline_mod = boot_load("streamline", boot_env)
use_cache['streamline'] = streamline_mod
use_cache['core/streamline'] = streamline_mod
// Lazy-loaded verify_ir module (loaded on first use)
var _verify_ir_mod = null
// Run AST through mcode pipeline → register VM
function run_ast(name, ast, env) {
var compiled = mcode_mod(ast)
if (os._verify_ir) {
if (_verify_ir_mod == null) {
_verify_ir_mod = boot_load('verify_ir', boot_env)
}
compiled._verify = true
compiled._verify_mod = _verify_ir_mod
}
var optimized = streamline_mod(compiled)
// Clean up verify properties before JSON encoding
if (optimized._verify) {
delete optimized._verify
delete optimized._verify_mod
}
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)
var mcode_json = json.encode(compiled)
var mach_blob = mach_compile_mcode_bin(name, mcode_json)
return mach_load(mach_blob, env)
}
// Compile AST to blob without loading (for caching)
function compile_to_blob(name, ast) {
var compiled = mcode_mod(ast)
var optimized = streamline_mod(compiled)
return mach_compile_mcode_bin(name, json.encode(optimized))
}
// Helper to load engine.cm and run it with given env
function load_engine(env) {
var mcode_path = core_path + '/boot/engine.cm.mcode'
var mcode_blob = null
var hash = null
var cached = null
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
var engine_src = null
var engine_ast = null
if (fd.is_file(mcode_path)) {
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)
if (cached && fd.is_file(cached)) return
ast = analyze(text(source_blob), source_path)
compiled = 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)
}
// 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)
}
// Detect mode and route
// CLI mode has 'args'; actor spawn mode has 'init'
var program = null
var user_args = []
var _j = 0
if (args != null) {
// CLI mode — always run as actor program (.ce)
program = args[0]
if (!program) {
print("error: no program specified\n")
disrupt
}
_j = 1
while (_j < length(args)) {
push(user_args, args[_j])
_j = _j + 1
}
load_engine({
os: os, actorsym: actorsym,
init: {program: program, arg: user_args},
core_path: core_path, shop_path: shop_path, json: json,
analyze: analyze, run_ast_fn: run_ast, run_ast_noopt_fn: run_ast_noopt,
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,
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
})
// Seed the cache with everything engine needs
var seed_files = [
{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"}
]
var _i = 0
var entry = null
while (_i < length(seed_files)) {
entry = seed_files[_i]
compile_and_cache(entry.name, core_path + '/' + entry.path)
_i = _i + 1
}
print("bootstrap: cache seeded\n")

View File

@@ -1,4 +1,5 @@
// 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
// Hidden vars (os, actorsym, init, core_path, shop_path, json, args) come from env
// Engine is self-sufficient: defines its own compilation pipeline
var ACTORDATA = actorsym
var SYSYM = '__SYSTEM__'
@@ -14,7 +15,7 @@ var cases = {
var dylib_ext = cases[os.platform()]
var MOD_EXT = '.cm'
var ACTOR_EXT = '.ce'
var ACTOR_EXT = '.ce'
var load_internal = os.load_internal
function use_embed(name) {
@@ -47,11 +48,159 @@ function ends_with(str, suffix) {
var fd = use_embed('internal_fd')
var js = use_embed('js')
var crypto = use_embed('crypto')
// core_path and shop_path come from env (bootstrap.cm passes them through)
// core_path and shop_path come from env (C runtime passes them through)
// shop_path may be null if --core was used without --shop
var packages_path = shop_path ? shop_path + '/packages' : null
// Self-sufficient initialization: content-addressed cache
var use_cache = {}
function content_hash(content) {
return text(crypto.blake2(content), 'h')
}
function cache_path(hash) {
if (!shop_path) return null
return shop_path + '/build/' + hash
}
function ensure_build_dir() {
if (!shop_path) return null
var dir = shop_path + '/build'
if (!fd.is_dir(dir)) fd.mkdir(dir)
return dir
}
// Load a pipeline module from cache, with boot/ seed fallback
function load_pipeline_module(name, env) {
var source_path = core_path + '/' + name + '.cm'
var source_blob = null
var hash = null
var cached = null
var mcode_path = null
var mcode_blob = null
var mach_blob = null
if (fd.is_file(source_path)) {
source_blob = fd.slurp(source_path)
hash = content_hash(source_blob)
cached = cache_path(hash)
if (cached && fd.is_file(cached))
return mach_load(fd.slurp(cached), env)
}
// Boot seed fallback
mcode_path = core_path + '/boot/' + name + '.cm.mcode'
if (fd.is_file(mcode_path)) {
mcode_blob = fd.slurp(mcode_path)
mach_blob = mach_compile_mcode_bin(name, text(mcode_blob))
return mach_load(mach_blob, env)
}
print("error: cannot load pipeline module: " + name + "\n")
disrupt
}
// Load compilation pipeline
var pipeline_env = {use: use_embed}
var tokenize_mod = load_pipeline_module('tokenize', pipeline_env)
var parse_mod = load_pipeline_module('parse', pipeline_env)
var fold_mod = load_pipeline_module('fold', pipeline_env)
var mcode_mod = load_pipeline_module('mcode', pipeline_env)
var streamline_mod = load_pipeline_module('streamline', pipeline_env)
use_cache['tokenize'] = tokenize_mod
use_cache['parse'] = parse_mod
use_cache['fold'] = fold_mod
use_cache['mcode'] = mcode_mod
use_cache['core/mcode'] = mcode_mod
use_cache['streamline'] = streamline_mod
use_cache['core/streamline'] = streamline_mod
// analyze: tokenize + parse + fold, check for errors
function analyze(src, filename) {
var tok_result = tokenize_mod(src, filename)
var _ast = parse_mod(tok_result.tokens, src, filename, tokenize_mod)
var _i = 0
var prev_line = -1
var prev_msg = null
var e = null
var msg = null
var line = null
var col = null
var has_errors = _ast.errors != null && length(_ast.errors) > 0
if (has_errors) {
while (_i < length(_ast.errors)) {
e = _ast.errors[_i]
msg = e.message
line = e.line
col = e.column
if (msg != prev_msg || line != prev_line) {
if (line != null && col != null)
print(`${filename}:${text(line)}:${text(col)}: error: ${msg}`)
else
print(`${filename}: error: ${msg}`)
}
prev_line = line
prev_msg = msg
_i = _i + 1
}
disrupt
}
return fold_mod(_ast)
}
// Lazy-loaded verify_ir module (loaded on first use)
var _verify_ir_mod = null
// Run AST through mcode pipeline -> register VM
function run_ast_fn(name, ast, env) {
var compiled = mcode_mod(ast)
if (os._verify_ir) {
if (_verify_ir_mod == null) {
_verify_ir_mod = load_pipeline_module('verify_ir', pipeline_env)
}
compiled._verify = true
compiled._verify_mod = _verify_ir_mod
}
var optimized = streamline_mod(compiled)
if (optimized._verify) {
delete optimized._verify
delete optimized._verify_mod
}
var mcode_json = json.encode(optimized)
var mach_blob = mach_compile_mcode_bin(name, mcode_json)
return mach_load(mach_blob, env)
}
// Run AST through mcode pipeline WITHOUT optimization -> register VM
function run_ast_noopt_fn(name, ast, env) {
var compiled = mcode_mod(ast)
var mcode_json = json.encode(compiled)
var mach_blob = mach_compile_mcode_bin(name, mcode_json)
return mach_load(mach_blob, env)
}
// Compile AST to blob without loading (for caching)
function compile_to_blob(name, ast) {
var compiled = mcode_mod(ast)
var optimized = streamline_mod(compiled)
return mach_compile_mcode_bin(name, json.encode(optimized))
}
// If loaded directly by C runtime (not via bootstrap), convert args -> init
var _program = null
var _user_args = []
var _j = 1
var _init = init
if (args != null && _init == null) {
_program = args[0]
while (_j < length(args)) {
push(_user_args, args[_j])
_j = _j + 1
}
_init = {program: _program, arg: _user_args}
}
use_cache['core/os'] = os
// Extra env properties added as engine initializes (log, runtime fns, etc.)
@@ -68,8 +217,6 @@ function use_core(path) {
var result = null
var script = null
var ast = null
var mcode_path = null
var mcode_blob = null
var _load_mod = null
// Build env: merge core_extras
@@ -82,32 +229,6 @@ function use_core(path) {
var source_blob = null
var file_path = null
// Check for pre-compiled .cm.mcode JSON IR (generated by regen)
mcode_path = core_path + '/boot/' + replace(path, '/', '_') + '.cm.mcode'
if (fd.is_file(mcode_path)) {
_load_mod = function() {
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)
}
} disruption {
print("use('" + path + "'): failed to load from " + mcode_path + "\n")
disrupt
}
_load_mod()
use_cache[cache_key] = result
return result
}
// Compile from source .cm file
file_path = core_path + '/' + path + MOD_EXT
if (fd.is_file(file_path)) {
@@ -120,7 +241,7 @@ function use_core(path) {
} else {
script = text(source_blob)
ast = analyze(script, file_path)
mach_blob = compile_to_blob_fn('core:' + path, ast)
mach_blob = compile_to_blob('core:' + path, ast)
if (cached_path) {
ensure_build_dir()
fd.slurpwrite(cached_path, mach_blob)
@@ -230,7 +351,7 @@ function actor_die(err)
//actor_mod.on_exception(actor_die)
_cell.args = init != null ? init : {}
_cell.args = _init != null ? _init : {}
_cell.id = "newguy"
function create_actor(desc) {
@@ -245,7 +366,7 @@ $_.self = create_actor()
use_cache['core/json'] = json
// Create runtime_env early (empty) filled after pronto loads.
// 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 = {}
@@ -266,8 +387,9 @@ core_extras.runtime_env = runtime_env
core_extras.content_hash = content_hash
core_extras.cache_path = cache_path
core_extras.ensure_build_dir = ensure_build_dir
core_extras.compile_to_blob = compile_to_blob
// NOW load shop it receives all of the above via env
// NOW load shop -- it receives all of the above via env
var shop = use_core('internal/shop')
var time = use_core('time')
@@ -388,7 +510,7 @@ REPLYTIMEOUT = config.reply_timeout
replycc: the actor that is waiting for the reply
target: ID of the actor that's supposed to receive the message. Only added to non direct sends (out of portals)
return: reply ID so the replycc actor can know what callback to send the message to
data: the actual content of the message
}
@@ -459,7 +581,7 @@ $_.connection = function(callback, actor, config) {
callback({type:"local"})
return
}
callback()
}
@@ -540,10 +662,10 @@ $_.start = function start(cb, program) {
if (!program) return
var id = guid()
var startup = {
id,
overling: $_.self,
root,
var startup = {
id,
overling: $_.self,
root,
program,
}
greeters[id] = cb
@@ -749,7 +871,7 @@ actor_mod.register_actor(_cell.id, turn, true, config.ar_timer)
if (config.actor_memory)
js.mem_limit(config.actor_memory)
if (config.stack_max)
js.max_stacksize(config.system.stack_max);
@@ -862,7 +984,7 @@ function handle_message(msg) {
function enet_check()
{
if (portal) portal.service(handle_host)
$_.delay(enet_check, ENETSERVICE);
}
@@ -941,7 +1063,7 @@ $_.clock(_ => {
} else {
script = text(source_blob)
ast = analyze(script, prog_path)
mach_blob = compile_to_blob_fn(prog, ast)
mach_blob = compile_to_blob(prog, ast)
if (cached_path) {
ensure_build_dir()
fd.slurpwrite(cached_path, mach_blob)

125
regen.ce
View File

@@ -1,125 +0,0 @@
// 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")
var mcode = use("mcode")
var streamline = use("streamline")
// Pipeline files (tokenize/parse/fold/mcode/streamline) are only regenerated
// with --all flag since they require a self-consistent compiler to bootstrap.
var pipeline_files = [
{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"}
]
var files = [
{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"},
{src: "fd.cm", name: "fd", out: "boot/fd.cm.mcode"},
{src: "time.cm", name: "time", out: "boot/time.cm.mcode"},
{src: "pronto.cm", name: "pronto", out: "boot/pronto.cm.mcode"},
{src: "toml.cm", name: "toml", out: "boot/toml.cm.mcode"},
{src: "link.cm", name: "link", out: "boot/link.cm.mcode"},
{src: "toolchains.cm", name: "toolchains", out: "boot/toolchains.cm.mcode"},
{src: "package.cm", name: "package", out: "boot/package.cm.mcode"},
{src: "internal/shop.cm", name: "internal_shop", out: "boot/internal_shop.cm.mcode"}
]
// Include pipeline files with --all flag
var os = use('os')
var regen_all = args != null && length(args) > 0 && args[0] == "--all"
if (regen_all) {
files = array(pipeline_files, files)
}
// Resolve shop_path for cache writes
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
var f = null
var errs = null
var ei = 0
var e = null
var had_errors = false
var compact_mcode = 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)
// Check for parse/semantic errors
errs = ast.errors
if (errs != null && length(errs) > 0) {
ei = 0
while (ei < length(errs)) {
e = errs[ei]
if (e.line != null) {
print(`${entry.src}:${text(e.line)}:${text(e.column)}: error: ${e.message}`)
} else {
print(`${entry.src}: error: ${e.message}`)
}
ei = ei + 1
}
had_errors = true
i = i + 1
continue
}
folded = fold(ast)
compiled = mcode(folded)
optimized = streamline(compiled)
mcode_text = 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')
compact_mcode = json.encode(optimized)
mach_blob = mach_compile_mcode_bin(entry.name, compact_mcode)
fd.slurpwrite(cache_dir + '/' + hash, mach_blob)
print(` cached ${hash}`)
}
i = i + 1
}
if (had_errors) {
print("regen aborted: fix errors above")
}

View File

@@ -12,8 +12,7 @@
#include "cJSON.h"
#define BOOTSTRAP_MCODE "boot/bootstrap.cm.mcode"
#define SEED_BOOTSTRAP_MCODE "boot/seed_bootstrap.cm.mcode"
#define BOOTSTRAP_SRC "internal/bootstrap.cm"
#define ENGINE_SRC "internal/engine.cm"
#define CELL_SHOP_DIR ".cell"
#define CELL_CORE_DIR "packages/core"
@@ -213,6 +212,37 @@ static char* load_core_file(const char *filename, size_t *out_size) {
return data;
}
// Try loading engine.cm from source-hash cache
// Returns heap-allocated binary data and sets *out_size, or NULL on cache miss
static char *try_engine_cache(size_t *out_size) {
size_t src_size;
char *src = load_core_file(ENGINE_SRC, &src_size);
if (!src) return NULL;
char *hex = compute_blake2_hex(src, src_size);
free(src);
char *cpath = build_cache_path(hex);
if (!cpath) { free(hex); return NULL; }
free(hex);
FILE *fh = fopen(cpath, "rb");
if (!fh) { free(cpath); return NULL; }
free(cpath);
fseek(fh, 0, SEEK_END);
long file_size = ftell(fh);
fseek(fh, 0, SEEK_SET);
char *data = malloc(file_size);
if (data && fread(data, 1, file_size, fh) == (size_t)file_size) {
fclose(fh);
*out_size = file_size;
return data;
}
free(data);
fclose(fh);
return NULL;
}
// Get the core path for use by scripts
const char* cell_get_core_path(void) {
return core_path;
@@ -254,26 +284,57 @@ void script_startup(cell_rt *prt)
cell_rt *crt = JS_GetContextOpaque(js);
JS_FreeValue(js, js_core_blob_use(js));
// Load pre-compiled bootstrap .mcode
size_t boot_size;
char *boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size);
if (!boot_data) {
printf("ERROR: Could not load bootstrap from %s!\n", core_path);
return;
}
// Try cache or compile mcode → binary
// Try engine fast-path: load engine.cm from source-hash cache
size_t bin_size;
char *bin_data = load_or_cache_bootstrap(boot_data, boot_size, &bin_size);
free(boot_data);
char *bin_data = try_engine_cache(&bin_size);
if (!bin_data) {
printf("ERROR: Failed to compile bootstrap mcode!\n");
return;
// Cold path: run bootstrap to seed cache, then retry
size_t boot_size;
char *boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size);
if (!boot_data) {
printf("ERROR: Could not load bootstrap from %s!\n", core_path);
return;
}
size_t boot_bin_size;
char *boot_bin = load_or_cache_bootstrap(boot_data, boot_size, &boot_bin_size);
free(boot_data);
if (!boot_bin) {
printf("ERROR: Failed to compile bootstrap mcode!\n");
return;
}
// Build env for bootstrap (only needs os, core_path, shop_path)
JSGCRef boot_env_ref;
JS_AddGCRef(js, &boot_env_ref);
boot_env_ref.val = JS_NewObject(js);
JSValue btmp;
btmp = js_core_os_use(js);
JS_SetPropertyStr(js, boot_env_ref.val, "os", btmp);
if (core_path) {
btmp = JS_NewString(js, core_path);
JS_SetPropertyStr(js, boot_env_ref.val, "core_path", btmp);
}
btmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
JS_SetPropertyStr(js, boot_env_ref.val, "shop_path", btmp);
JSValue boot_env = JS_Stone(js, boot_env_ref.val);
JS_DeleteGCRef(js, &boot_env_ref);
crt->state = ACTOR_RUNNING;
JSValue bv = JS_RunMachBin(js, (const uint8_t *)boot_bin, boot_bin_size, boot_env);
free(boot_bin);
uncaught_exception(js, bv);
crt->state = ACTOR_IDLE;
// Retry engine from cache
bin_data = try_engine_cache(&bin_size);
if (!bin_data) {
printf("ERROR: Bootstrap ran but engine.cm not in cache!\n");
return;
}
}
// Create hidden environment
// Note: evaluate allocating calls into temporaries before passing to
// JS_SetPropertyStr, so env_ref.val is read AFTER GC may have moved it.
// Build engine environment
JSGCRef env_ref;
JS_AddGCRef(js, &env_ref);
env_ref.val = JS_NewObject(js);
@@ -310,7 +371,7 @@ void script_startup(cell_rt *prt)
JSValue hidden_env = JS_Stone(js, env_ref.val);
JS_DeleteGCRef(js, &env_ref);
// Run from binary
// Run engine from binary
crt->state = ACTOR_RUNNING;
JSValue v = JS_RunMachBin(js, (const uint8_t *)bin_data, bin_size, hidden_env);
free(bin_data);
@@ -370,7 +431,6 @@ static void print_usage(const char *prog)
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");
@@ -401,9 +461,8 @@ int cell_init(int argc, char **argv)
return run_test_suite(heap_size);
}
/* Default: run script through bootstrap pipeline */
/* Default: run script through engine 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;
@@ -424,9 +483,6 @@ 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");
@@ -463,33 +519,15 @@ 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(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(bin_data);
return 1;
}
JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, heap_size);
if (!ctx) {
printf("Failed to create JS context\n");
free(bin_data); JS_FreeRuntime(g_runtime);
JS_FreeRuntime(g_runtime);
return 1;
}
@@ -523,47 +561,116 @@ int cell_init(int argc, char **argv)
JS_FreeValue(ctx, js_core_blob_use(ctx));
JSGCRef env_ref;
JS_AddGCRef(ctx, &env_ref);
env_ref.val = JS_NewObject(ctx);
JSValue tmp;
tmp = js_core_os_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "os", tmp);
tmp = JS_NewString(ctx, core_path);
JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp);
tmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
JS_SetPropertyStr(ctx, env_ref.val, "shop_path", tmp);
JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
tmp = js_core_json_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
JS_SetPropertyStr(ctx, env_ref.val, "init", JS_NULL);
JSGCRef args_ref;
JS_AddGCRef(ctx, &args_ref);
args_ref.val = JS_NewArray(ctx);
for (int i = arg_start; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]);
JS_ArrayPush(ctx, &args_ref.val, str);
}
JS_SetPropertyStr(ctx, env_ref.val, "args", args_ref.val);
JS_DeleteGCRef(ctx, &args_ref);
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
JS_DeleteGCRef(ctx, &env_ref);
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)) {
JS_GetException(ctx);
exit_code = 1;
} else if (!JS_IsNull(result)) {
const char *str = JS_ToCString(ctx, result);
if (str) {
printf("%s\n", str);
JS_FreeCString(ctx, str);
// Try engine fast-path: load engine.cm from source-hash cache
size_t bin_size;
char *bin_data = try_engine_cache(&bin_size);
if (!bin_data) {
// Cold path: run bootstrap to seed cache, then retry
size_t boot_size;
char *boot_data = load_core_file(BOOTSTRAP_MCODE, &boot_size);
if (!boot_data) {
printf("ERROR: Could not load bootstrap from %s\n", core_path);
return 1;
}
size_t boot_bin_size;
char *boot_bin = load_or_cache_bootstrap(boot_data, boot_size, &boot_bin_size);
free(boot_data);
if (!boot_bin) {
printf("ERROR: Failed to compile bootstrap mcode\n");
return 1;
}
// Build env for bootstrap (os, core_path, shop_path required;
// args, json, actorsym provided for compatibility)
JSGCRef boot_env_ref;
JS_AddGCRef(ctx, &boot_env_ref);
boot_env_ref.val = JS_NewObject(ctx);
JSValue btmp;
btmp = js_core_os_use(ctx);
JS_SetPropertyStr(ctx, boot_env_ref.val, "os", btmp);
btmp = JS_NewString(ctx, core_path);
JS_SetPropertyStr(ctx, boot_env_ref.val, "core_path", btmp);
btmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
JS_SetPropertyStr(ctx, boot_env_ref.val, "shop_path", btmp);
JS_SetPropertyStr(ctx, boot_env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
btmp = js_core_json_use(ctx);
JS_SetPropertyStr(ctx, boot_env_ref.val, "json", btmp);
JS_SetPropertyStr(ctx, boot_env_ref.val, "init", JS_NULL);
JSGCRef boot_args_ref;
JS_AddGCRef(ctx, &boot_args_ref);
boot_args_ref.val = JS_NewArray(ctx);
for (int i = arg_start; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]);
JS_ArrayPush(ctx, &boot_args_ref.val, str);
}
JS_SetPropertyStr(ctx, boot_env_ref.val, "args", boot_args_ref.val);
JS_DeleteGCRef(ctx, &boot_args_ref);
JSValue boot_env = JS_Stone(ctx, boot_env_ref.val);
JS_DeleteGCRef(ctx, &boot_env_ref);
JSValue boot_result = JS_RunMachBin(ctx, (const uint8_t *)boot_bin, boot_bin_size, boot_env);
free(boot_bin);
if (JS_IsException(boot_result)) {
JS_GetException(ctx);
printf("ERROR: Bootstrap failed\n");
return 1;
}
// Retry engine from cache (new-style bootstrap seeds it)
bin_data = try_engine_cache(&bin_size);
if (!bin_data) {
// Old-style bootstrap already ran the program — skip engine load
goto check_actors;
}
}
{
// Build engine environment
JSGCRef env_ref;
JS_AddGCRef(ctx, &env_ref);
env_ref.val = JS_NewObject(ctx);
JSValue tmp;
tmp = js_core_os_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "os", tmp);
tmp = JS_NewString(ctx, core_path);
JS_SetPropertyStr(ctx, env_ref.val, "core_path", tmp);
tmp = shop_path ? JS_NewString(ctx, shop_path) : JS_NULL;
JS_SetPropertyStr(ctx, env_ref.val, "shop_path", tmp);
JS_SetPropertyStr(ctx, env_ref.val, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
tmp = js_core_json_use(ctx);
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
JS_SetPropertyStr(ctx, env_ref.val, "init", JS_NULL);
JSGCRef args_ref;
JS_AddGCRef(ctx, &args_ref);
args_ref.val = JS_NewArray(ctx);
for (int i = arg_start; i < argc; i++) {
JSValue str = JS_NewString(ctx, argv[i]);
JS_ArrayPush(ctx, &args_ref.val, str);
}
JS_SetPropertyStr(ctx, env_ref.val, "args", args_ref.val);
JS_DeleteGCRef(ctx, &args_ref);
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
JSValue result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env);
JS_DeleteGCRef(ctx, &env_ref);
free(bin_data);
if (JS_IsException(result)) {
JS_GetException(ctx);
exit_code = 1;
} else if (!JS_IsNull(result)) {
const char *str = JS_ToCString(ctx, result);
if (str) {
printf("%s\n", str);
JS_FreeCString(ctx, str);
}
}
}
check_actors:
if (scheduler_actor_count() > 0) {
scheduler_enable_quiescence();
actor_loop();

View File

@@ -95,8 +95,7 @@ static void mach_link_code(JSContext *ctx, JSCodeRegister *code, JSValue env) {
int bx = MACH_GET_Bx(instr);
int in_env = 0;
if (!JS_IsNull(env_ref.val) && (uint32_t)bx < code->cpool_count) {
JSValue val = JS_GetProperty(ctx, env_ref.val, code->cpool[bx]);
in_env = !JS_IsNull(val) && !JS_IsException(val);
in_env = JS_HasProperty(ctx, env_ref.val, code->cpool[bx]);
}
code->instructions[i] = MACH_ABx(in_env ? MACH_GETENV : MACH_GETINTRINSIC, a, bx);
}