904 lines
26 KiB
C
904 lines
26 KiB
C
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include "wota.h"
|
|
|
|
#define STB_DS_IMPLEMENTATION
|
|
#include "stb_ds.h"
|
|
|
|
#include "cell.h"
|
|
#include "pit_internal.h"
|
|
|
|
#define BOOTSTRAP_MCODE "boot/bootstrap.cm.mcode"
|
|
#define ENGINE_SRC "internal/engine.cm"
|
|
#define CELL_SHOP_DIR ".cell"
|
|
#define CELL_CORE_DIR "packages/core"
|
|
|
|
#include <math.h>
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <dlfcn.h>
|
|
#include "monocypher.h"
|
|
|
|
/* Test suite declarations */
|
|
int run_c_test_suite(JSContext *ctx);
|
|
static int run_test_suite(size_t heap_size);
|
|
|
|
static JSContext *root_ctx = NULL;
|
|
static char *shop_path = NULL;
|
|
volatile JSContext *g_crash_ctx = NULL;
|
|
static char *core_path = NULL;
|
|
static int native_mode = 0;
|
|
static int warn_mode = 1;
|
|
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);
|
|
if (!hex) return NULL;
|
|
for (int i = 0; i < 32; i++)
|
|
snprintf(hex + i * 2, 3, "%02x", hash[i]);
|
|
return hex;
|
|
}
|
|
|
|
// Build cache path: shop_path/build/<hex> (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 + 1;
|
|
char *path = malloc(len);
|
|
snprintf(path, len, "%s/build/%s", 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);
|
|
if (!hex) return NULL;
|
|
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");
|
|
if (!home) {
|
|
home = getenv("USERPROFILE"); // Windows fallback
|
|
}
|
|
return home;
|
|
}
|
|
|
|
// Resolve shop_path and core_path
|
|
// core: --core flag > CELL_CORE env > derived from shop
|
|
// shop: --shop flag > CELL_SHOP env > ~/.cell
|
|
int find_cell_shop(const char *shop_override, const char *core_override)
|
|
{
|
|
// Resolve shop_path
|
|
if (shop_override) {
|
|
shop_path = strdup(shop_override);
|
|
} else {
|
|
const char *env = getenv("CELL_SHOP");
|
|
if (env) {
|
|
shop_path = strdup(env);
|
|
} else {
|
|
const char *home = get_home_dir();
|
|
if (!home && !core_override && !getenv("CELL_CORE")) {
|
|
printf("ERROR: Could not determine home directory. Set HOME environment variable.\n");
|
|
return 0;
|
|
}
|
|
if (home) {
|
|
size_t path_len = strlen(home) + strlen("/" CELL_SHOP_DIR) + 1;
|
|
shop_path = malloc(path_len);
|
|
if (shop_path)
|
|
snprintf(shop_path, path_len, "%s/" CELL_SHOP_DIR, home);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Resolve core_path
|
|
if (core_override) {
|
|
core_path = strdup(core_override);
|
|
} else {
|
|
const char *env = getenv("CELL_CORE");
|
|
if (env) {
|
|
core_path = strdup(env);
|
|
} else if (shop_path) {
|
|
size_t core_len = strlen(shop_path) + strlen("/" CELL_CORE_DIR) + 1;
|
|
core_path = malloc(core_len);
|
|
if (core_path)
|
|
snprintf(core_path, core_len, "%s/" CELL_CORE_DIR, shop_path);
|
|
}
|
|
}
|
|
|
|
if (!core_path) {
|
|
printf("ERROR: No core path. Use --core <path> or set CELL_CORE.\n");
|
|
return 0;
|
|
}
|
|
|
|
// Check if the core directory exists
|
|
struct stat st;
|
|
if (stat(core_path, &st) != 0 || !S_ISDIR(st.st_mode)) {
|
|
printf("ERROR: Core not found at %s\n", core_path);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
// Load a file from the core directory
|
|
static char* load_core_file(const char *filename, size_t *out_size) {
|
|
if (!core_path) return NULL;
|
|
|
|
size_t path_len = strlen(core_path) + 1 + strlen(filename) + 1;
|
|
char *full_path = malloc(path_len);
|
|
if (!full_path) return NULL;
|
|
|
|
snprintf(full_path, path_len, "%s/%s", core_path, filename);
|
|
|
|
FILE *fh = fopen(full_path, "rb");
|
|
free(full_path);
|
|
|
|
if (!fh) return NULL;
|
|
|
|
fseek(fh, 0, SEEK_END);
|
|
long file_size = ftell(fh);
|
|
fseek(fh, 0, SEEK_SET);
|
|
|
|
char *data = malloc(file_size + 1);
|
|
if (!data) {
|
|
fclose(fh);
|
|
return NULL;
|
|
}
|
|
|
|
if (fread(data, 1, file_size, fh) != (size_t)file_size) {
|
|
free(data);
|
|
fclose(fh);
|
|
return NULL;
|
|
}
|
|
|
|
fclose(fh);
|
|
data[file_size] = 0;
|
|
|
|
if (out_size) *out_size = file_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);
|
|
if (!hex) return NULL;
|
|
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;
|
|
}
|
|
|
|
static const char *detect_host_target(void) {
|
|
#if defined(__APPLE__) && defined(__aarch64__)
|
|
return "macos_arm64";
|
|
#elif defined(__APPLE__) && defined(__x86_64__)
|
|
return "macos_x86_64";
|
|
#elif defined(__linux__) && defined(__x86_64__)
|
|
return "linux";
|
|
#elif defined(__linux__) && defined(__aarch64__)
|
|
return "linux_arm64";
|
|
#elif defined(_WIN32)
|
|
return "windows";
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
static char *try_engine_native_cache(void) {
|
|
const char *target = detect_host_target();
|
|
if (!target) return NULL;
|
|
size_t src_size;
|
|
char *src = load_core_file(ENGINE_SRC, &src_size);
|
|
if (!src) return NULL;
|
|
size_t target_len = strlen(target);
|
|
size_t key_len = src_size + 1 + target_len + 8 + 7;
|
|
char *key = malloc(key_len + 1);
|
|
if (!key) { free(src); return NULL; }
|
|
memcpy(key, src, src_size);
|
|
size_t pos = src_size;
|
|
key[pos++] = '\n';
|
|
memcpy(key + pos, target, target_len);
|
|
pos += target_len;
|
|
memcpy(key + pos, "\nnative\n", 8);
|
|
pos += 8;
|
|
memcpy(key + pos, "\nnative", 7);
|
|
pos += 7;
|
|
key[pos] = '\0';
|
|
free(src);
|
|
char *hex = compute_blake2_hex(key, pos);
|
|
free(key);
|
|
if (!hex) return NULL;
|
|
char *cpath = build_cache_path(hex);
|
|
free(hex);
|
|
if (!cpath) return NULL;
|
|
struct stat st;
|
|
if (stat(cpath, &st) != 0) { free(cpath); return NULL; }
|
|
return cpath;
|
|
}
|
|
|
|
// Get the core path for use by scripts
|
|
const char* cell_get_core_path(void) {
|
|
return core_path;
|
|
}
|
|
|
|
void actor_disrupt(JSContext *ctx)
|
|
{
|
|
ctx->disrupt = 1;
|
|
JS_SetPauseFlag(ctx, 2);
|
|
if (ctx->state != ACTOR_RUNNING)
|
|
actor_free(ctx);
|
|
}
|
|
|
|
JSValue js_core_internal_os_use(JSContext *js);
|
|
JSValue js_core_json_use(JSContext *js);
|
|
|
|
/* Engine-env log proxy: routes log("channel", [msg]) through JS_Log.
|
|
Before set_log is called, JS_Log falls back to stderr.
|
|
After set_log, JS_Log forwards to the engine's JS log function.
|
|
This exists so mcode-generated type-check error paths (which always
|
|
access 'log' as an intrinsic) work inside engine.cm itself. */
|
|
static JSValue js_engine_log(JSContext *js, JSValue self,
|
|
int argc, JSValue *argv) {
|
|
if (argc < 2) return JS_NULL;
|
|
const char *channel = JS_ToCString(js, argv[0]);
|
|
if (!channel) return JS_NULL;
|
|
JSValue msg_val = JS_GetPropertyNumber(js, argv[1], 0);
|
|
const char *msg = JS_ToCString(js, msg_val);
|
|
if (msg) {
|
|
JS_Log(js, channel, "%s", msg);
|
|
JS_FreeCString(js, msg);
|
|
}
|
|
JS_FreeCString(js, channel);
|
|
return JS_NULL;
|
|
}
|
|
|
|
void script_startup(JSContext *js)
|
|
{
|
|
if (!g_runtime) {
|
|
g_runtime = JS_NewRuntime();
|
|
}
|
|
|
|
JS_SetGCScanExternal(js, actor_gc_scan);
|
|
|
|
js->actor_label = js->name; /* may be NULL; updated when name is set */
|
|
JS_SetHeapMemoryLimit(js, ACTOR_MEMORY_LIMIT);
|
|
|
|
|
|
// 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;
|
|
}
|
|
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_internal_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);
|
|
if (native_mode)
|
|
JS_SetPropertyStr(js, boot_env_ref.val, "native_mode", JS_NewBool(js, 1));
|
|
JSValue boot_env = JS_Stone(js, boot_env_ref.val);
|
|
JS_DeleteGCRef(js, &boot_env_ref);
|
|
|
|
js->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);
|
|
js->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;
|
|
}
|
|
}
|
|
|
|
// Build engine environment
|
|
JSGCRef env_ref;
|
|
JS_AddGCRef(js, &env_ref);
|
|
env_ref.val = JS_NewObject(js);
|
|
JSValue tmp;
|
|
tmp = js_core_internal_os_use(js);
|
|
JS_SetPropertyStr(js, env_ref.val, "os", tmp);
|
|
tmp = js_core_json_use(js);
|
|
JS_SetPropertyStr(js, env_ref.val, "json", tmp);
|
|
|
|
js->actor_sym_ref.val = JS_NewObject(js);
|
|
JS_CellStone(js, js->actor_sym_ref.val);
|
|
JS_SetActorSym(js, js->actor_sym_ref.val);
|
|
JS_SetPropertyStr(js, env_ref.val, "actorsym", js->actor_sym_ref.val);
|
|
|
|
// Always set init (even if null)
|
|
if (js->init_wota) {
|
|
JSGCRef init_ref;
|
|
JS_PushGCRef(js, &init_ref);
|
|
init_ref.val = wota2value(js, js->init_wota);
|
|
JS_SetPropertyStr(js, env_ref.val, "init", init_ref.val);
|
|
JS_PopGCRef(js, &init_ref);
|
|
free(js->init_wota);
|
|
js->init_wota = NULL;
|
|
} else {
|
|
JS_SetPropertyStr(js, env_ref.val, "init", JS_NULL);
|
|
}
|
|
|
|
// Set args to null for actor spawn (not CLI mode)
|
|
JS_SetPropertyStr(js, env_ref.val, "args", 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);
|
|
tmp = JS_NewCFunction(js, js_engine_log, "log", 2);
|
|
JS_SetPropertyStr(js, env_ref.val, "log", tmp);
|
|
|
|
// Stone the environment
|
|
JSValue hidden_env = JS_Stone(js, env_ref.val);
|
|
JS_DeleteGCRef(js, &env_ref);
|
|
|
|
// Run engine from binary
|
|
js->state = ACTOR_RUNNING;
|
|
JSValue v = JS_RunMachBin(js, (const uint8_t *)bin_data, bin_size, hidden_env);
|
|
free(bin_data);
|
|
uncaught_exception(js, v);
|
|
js->state = ACTOR_IDLE;
|
|
set_actor_state(js);
|
|
}
|
|
|
|
static void signal_handler(int sig)
|
|
{
|
|
const char *str = NULL;
|
|
#ifndef TARGET_PLAYDATE
|
|
switch (sig) {
|
|
case SIGABRT: str = "SIGABRT"; break;
|
|
case SIGFPE: str = "SIGFPE"; break;
|
|
case SIGILL: str = "SIGILL"; break;
|
|
case SIGINT: str = "SIGINT"; break;
|
|
case SIGSEGV: str = "SIGSEGV"; break;
|
|
case SIGTERM: str = "SIGTERM"; break;
|
|
}
|
|
#endif
|
|
if (!str) return;
|
|
|
|
/* Reset handler to default so a double-fault terminates immediately */
|
|
signal(sig, SIG_DFL);
|
|
|
|
/* Try to print the JS stack (best-effort, signal-safe) */
|
|
if (g_crash_ctx)
|
|
JS_CrashPrintStack((JSContext *)g_crash_ctx);
|
|
|
|
exit_handler();
|
|
}
|
|
|
|
/* Run the C test suite with minimal runtime setup */
|
|
static int run_test_suite(size_t heap_size)
|
|
{
|
|
JSRuntime *rt = JS_NewRuntime();
|
|
if (!rt) {
|
|
printf("Failed to create JS runtime\n");
|
|
return 1;
|
|
}
|
|
|
|
JSContext *ctx = JS_NewContextWithHeapSize(rt, heap_size);
|
|
if (!ctx) {
|
|
printf("Failed to create JS context\n");
|
|
JS_FreeRuntime(rt);
|
|
return 1;
|
|
}
|
|
|
|
int result = run_c_test_suite(ctx);
|
|
|
|
JS_FreeContext(ctx);
|
|
JS_FreeRuntime(rt);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void print_usage(const char *prog)
|
|
{
|
|
printf("Usage: %s [options] <program> [args...]\n\n", prog);
|
|
printf("Run a cell program (.ce actor).\n\n");
|
|
printf("Options:\n");
|
|
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(" --native Use AOT native code instead of bytecode\n");
|
|
printf(" --heap <size> Initial heap size (e.g. 256MB, 1GB)\n");
|
|
printf(" --test [heap_size] Run C test suite\n");
|
|
printf(" -e <code> Evaluate code string as a program\n");
|
|
printf(" -h, --help Show this help message\n");
|
|
printf("\nEnvironment:\n");
|
|
printf(" CELL_CORE Core path (default: <shop>/packages/core)\n");
|
|
printf(" CELL_SHOP Shop path (default: ~/.cell)\n");
|
|
printf("\nRecompile after changes: make\n");
|
|
printf("Bootstrap from scratch: make bootstrap\n");
|
|
printf("Run the 'help' script like 'cell help' to see available scripts\n");
|
|
}
|
|
|
|
JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const char *sym_name, JSValue env);
|
|
|
|
int cell_init(int argc, char **argv)
|
|
{
|
|
/* Check for --help flag */
|
|
if (argc >= 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) {
|
|
print_usage(argv[0]);
|
|
return 0;
|
|
}
|
|
|
|
/* Check for --test flag to run C test suite */
|
|
if (argc >= 2 && strcmp(argv[1], "--test") == 0) {
|
|
size_t heap_size = 64 * 1024; /* 64KB default */
|
|
if (argc >= 3) {
|
|
heap_size = strtoull(argv[2], NULL, 0);
|
|
/* Round up to power of 2 for buddy allocator */
|
|
size_t p = 1;
|
|
while (p < heap_size) p <<= 1;
|
|
heap_size = p;
|
|
}
|
|
return run_test_suite(heap_size);
|
|
}
|
|
|
|
/* Default: run script through engine pipeline */
|
|
int arg_start = 1;
|
|
size_t heap_size = 1024 * 1024; /* 1MB default */
|
|
const char *shop_override = NULL;
|
|
const char *core_override = NULL;
|
|
const char *eval_script = NULL;
|
|
|
|
// Parse flags (order-independent)
|
|
while (arg_start < argc && argv[arg_start][0] == '-') {
|
|
if (strcmp(argv[arg_start], "--shop") == 0) {
|
|
if (arg_start + 1 >= argc) {
|
|
printf("ERROR: --shop requires a path argument\n");
|
|
return 1;
|
|
}
|
|
shop_override = argv[arg_start + 1];
|
|
arg_start += 2;
|
|
} else if (strcmp(argv[arg_start], "--core") == 0) {
|
|
if (arg_start + 1 >= argc) {
|
|
printf("ERROR: --core requires a path argument\n");
|
|
return 1;
|
|
}
|
|
core_override = argv[arg_start + 1];
|
|
arg_start += 2;
|
|
} 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 = ".";
|
|
mkdir(".cell", 0755);
|
|
mkdir(".cell/build", 0755);
|
|
mkdir(".cell/cache", 0755);
|
|
mkdir(".cell/packages", 0755);
|
|
/* Ensure .cell/packages/core -> . symlink exists */
|
|
struct stat lst;
|
|
if (lstat(".cell/packages/core", &lst) != 0)
|
|
symlink("../..", ".cell/packages/core");
|
|
arg_start++;
|
|
} else if (strcmp(argv[arg_start], "--native") == 0) {
|
|
native_mode = 1;
|
|
arg_start++;
|
|
} else if (strcmp(argv[arg_start], "--no-warn") == 0) {
|
|
warn_mode = 0;
|
|
arg_start++;
|
|
} else if (strcmp(argv[arg_start], "-e") == 0) {
|
|
if (arg_start + 1 >= argc) {
|
|
printf("ERROR: -e requires a code string argument\n");
|
|
return 1;
|
|
}
|
|
eval_script = argv[arg_start + 1];
|
|
arg_start += 2;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (arg_start >= argc && !eval_script) {
|
|
print_usage(argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
if (!find_cell_shop(shop_override, core_override)) return 1;
|
|
|
|
actor_initialize();
|
|
|
|
g_runtime = JS_NewRuntime();
|
|
if (!g_runtime) {
|
|
printf("Failed to create JS runtime\n");
|
|
return 1;
|
|
}
|
|
JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, heap_size);
|
|
if (!ctx) {
|
|
printf("Failed to create JS context\n");
|
|
JS_FreeRuntime(g_runtime);
|
|
return 1;
|
|
}
|
|
|
|
/* Set up mutexes on the CLI context */
|
|
ctx->mutex = malloc(sizeof(pthread_mutex_t));
|
|
pthread_mutexattr_t mattr;
|
|
pthread_mutexattr_init(&mattr);
|
|
pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
|
|
pthread_mutex_init(ctx->mutex, &mattr);
|
|
ctx->msg_mutex = malloc(sizeof(pthread_mutex_t));
|
|
pthread_mutex_init(ctx->msg_mutex, &mattr);
|
|
pthread_mutexattr_destroy(&mattr);
|
|
|
|
JS_SetGCScanExternal(ctx, actor_gc_scan);
|
|
|
|
ctx->actor_sym_ref.val = JS_NewObject(ctx);
|
|
JS_CellStone(ctx, ctx->actor_sym_ref.val);
|
|
JS_SetActorSym(ctx, ctx->actor_sym_ref.val);
|
|
|
|
root_ctx = ctx;
|
|
|
|
|
|
int exit_code = 0;
|
|
int use_native_engine = 0;
|
|
char *native_dylib_path = NULL;
|
|
void *native_handle = NULL;
|
|
|
|
// Native mode: try native engine cache first
|
|
if (native_mode)
|
|
native_dylib_path = try_engine_native_cache();
|
|
|
|
// Try engine fast-path: load engine.cm from source-hash cache
|
|
size_t bin_size;
|
|
char *bin_data = NULL;
|
|
if (!native_dylib_path)
|
|
bin_data = try_engine_cache(&bin_size);
|
|
|
|
if (!native_dylib_path && !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_internal_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", ctx->actor_sym_ref.val);
|
|
btmp = js_core_json_use(ctx);
|
|
JS_SetPropertyStr(ctx, boot_env_ref.val, "json", btmp);
|
|
if (native_mode)
|
|
JS_SetPropertyStr(ctx, boot_env_ref.val, "native_mode", JS_NewBool(ctx, 1));
|
|
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;
|
|
}
|
|
|
|
// After bootstrap, retry cache
|
|
if (native_mode)
|
|
native_dylib_path = try_engine_native_cache();
|
|
if (!native_dylib_path) {
|
|
bin_data = try_engine_cache(&bin_size);
|
|
if (!bin_data) {
|
|
// Old-style bootstrap already ran the program — skip engine load
|
|
goto check_actors;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Open native dylib if we have a path
|
|
if (native_dylib_path) {
|
|
native_handle = dlopen(native_dylib_path, RTLD_NOW | RTLD_GLOBAL);
|
|
if (native_handle) {
|
|
use_native_engine = 1;
|
|
} else {
|
|
// Fall back to bytecode
|
|
if (!bin_data)
|
|
bin_data = try_engine_cache(&bin_size);
|
|
}
|
|
free(native_dylib_path);
|
|
native_dylib_path = NULL;
|
|
}
|
|
|
|
{
|
|
// Build engine environment
|
|
JSGCRef env_ref;
|
|
JS_AddGCRef(ctx, &env_ref);
|
|
env_ref.val = JS_NewObject(ctx);
|
|
JSValue tmp;
|
|
tmp = js_core_internal_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", ctx->actor_sym_ref.val);
|
|
tmp = js_core_json_use(ctx);
|
|
JS_SetPropertyStr(ctx, env_ref.val, "json", tmp);
|
|
if (native_mode || !warn_mode || eval_script) {
|
|
JSGCRef init_ref;
|
|
JS_AddGCRef(ctx, &init_ref);
|
|
init_ref.val = JS_NewObject(ctx);
|
|
if (native_mode)
|
|
JS_SetPropertyStr(ctx, init_ref.val, "native_mode", JS_NewBool(ctx, 1));
|
|
if (!warn_mode)
|
|
JS_SetPropertyStr(ctx, init_ref.val, "no_warn", JS_NewBool(ctx, 1));
|
|
if (eval_script) {
|
|
JSValue es = JS_NewString(ctx, eval_script);
|
|
JS_SetPropertyStr(ctx, init_ref.val, "eval_script", es);
|
|
}
|
|
JS_SetPropertyStr(ctx, env_ref.val, "init", init_ref.val);
|
|
JS_DeleteGCRef(ctx, &init_ref);
|
|
} else {
|
|
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);
|
|
tmp = JS_NewCFunction(ctx, js_engine_log, "log", 2);
|
|
JS_SetPropertyStr(ctx, env_ref.val, "log", tmp);
|
|
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
|
|
|
|
g_crash_ctx = ctx;
|
|
JSValue result;
|
|
if (use_native_engine) {
|
|
result = cell_rt_native_module_load_named(ctx, native_handle, "cell_main", hidden_env);
|
|
} else {
|
|
result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env);
|
|
free(bin_data);
|
|
}
|
|
g_crash_ctx = NULL;
|
|
JS_DeleteGCRef(ctx, &env_ref);
|
|
|
|
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();
|
|
exit_handler();
|
|
exit(0);
|
|
}
|
|
|
|
/* No actors spawned — clean up CLI context */
|
|
pthread_mutex_destroy(ctx->mutex);
|
|
free(ctx->mutex);
|
|
pthread_mutex_destroy(ctx->msg_mutex);
|
|
free(ctx->msg_mutex);
|
|
root_ctx = NULL;
|
|
|
|
JS_FreeContext(ctx);
|
|
JS_FreeRuntime(g_runtime);
|
|
g_runtime = NULL;
|
|
|
|
exit_handler();
|
|
return exit_code;
|
|
}
|
|
|
|
int JS_ArrayLength(JSContext *js, JSValue a)
|
|
{
|
|
int64_t len;
|
|
JS_GetLength(js, a, &len);
|
|
return len;
|
|
}
|
|
|
|
int js2bool(JSContext *js, JSValue v) { return JS_ToBool(js,v); }
|
|
JSValue bool2js(JSContext *js, int b) { return JS_NewBool(js,b); }
|
|
|
|
JSValue number2js(JSContext *js, double g) { return JS_NewFloat64(js,g); }
|
|
double js2number(JSContext *js, JSValue v) {
|
|
double g;
|
|
JS_ToFloat64(js, &g, v);
|
|
if (isnan(g)) g = 0;
|
|
return g;
|
|
}
|
|
|
|
uint64_t cell_random_fit() {
|
|
uint64_t buf;
|
|
randombytes((uint8_t *)&buf, sizeof(buf));
|
|
return buf >> 11;
|
|
}
|
|
|
|
double cell_random() {
|
|
uint64_t buf = cell_random_fit();
|
|
return (double)buf / 9007199254740992.0;
|
|
}
|
|
|
|
static cell_hook g_cell_trace_hook = NULL;
|
|
|
|
void cell_trace_sethook(cell_hook hook)
|
|
{
|
|
g_cell_trace_hook = hook;
|
|
}
|
|
|
|
cell_hook cell_trace_gethook(void)
|
|
{
|
|
return g_cell_trace_hook;
|
|
}
|
|
|
|
void cell_rt_set_trace_hook(JSContext *ctx, cell_hook hook)
|
|
{
|
|
ctx->actor_trace_hook = hook;
|
|
}
|
|
|
|
int uncaught_exception(JSContext *js, JSValue v)
|
|
{
|
|
int has_exc = JS_HasException(js);
|
|
int is_exc = JS_IsException(v);
|
|
if (!has_exc && !is_exc)
|
|
return 1;
|
|
/* Error message and backtrace were already printed to stderr
|
|
by JS_ThrowError2 / print_backtrace. Just clear the flag. */
|
|
if (has_exc)
|
|
JS_GetException(js);
|
|
if (!JS_IsNull(js->on_exception_ref.val)) {
|
|
/* Disable interruption so actor_die can send messages
|
|
without being re-interrupted. */
|
|
JS_SetPauseFlag(js, 0);
|
|
JSValue err = JS_NewString(js, "interrupted");
|
|
JS_Call(js, js->on_exception_ref.val, JS_NULL, 1, &err);
|
|
/* Clear any secondary exception from the callback. */
|
|
if (JS_HasException(js))
|
|
JS_GetException(js);
|
|
}
|
|
return 0;
|
|
}
|