Files
cell/source/cell.c

558 lines
13 KiB
C

#ifdef HAVE_MIMALLOC
#include <mimalloc.h>
#endif
#ifdef __APPLE__
#include <sys/types.h>
#include <sys/sysctl.h>
#endif
#ifdef _WIN32
#include <windows.h>
#endif
#define WOTA_IMPLEMENTATION
#include "wota.h"
#define STB_DS_IMPLEMENTATION
#include "stb_ds.h"
#include "cell.h"
#include "cell_internal.h"
#ifdef TRACY_ENABLE
#include <tracy/TracyC.h>
#endif
#if defined(__APPLE__)
#include <malloc/malloc.h>
#define MALLOC_OVERHEAD 0
#elif defined(_WIN32)
#include <malloc.h>
#define MALLOC_OVERHEAD 8
#elif defined(__linux__) || defined(__GLIBC__)
#define _GNU_SOURCE
#include <malloc.h>
#include <unistd.h>
#define MALLOC_OVERHEAD 8
#else
#define MALLOC_OVERHEAD 0
#endif
#define QOP_IMPLEMENTATION
#include "qop.h"
#define unlikely(x) __builtin_expect(!!(x), 0)
int tracy_profiling_enabled = 0;
#define ENGINE "engine.cm"
static qop_desc qop_core;
static qop_file *qop_hashmap = NULL;
cell_rt *root_cell = NULL;
static size_t js_mi_malloc_usable_size(const void *ptr)
{
#if defined(__APPLE__)
return malloc_size(ptr);
#elif defined(_WIN32)
return _msize((void *)ptr);
#elif defined(EMSCRIPTEN)
return 0;
#elif defined(__linux__) || defined(__GLIBC__)
return malloc_usable_size((void *)ptr);
#else
return malloc_usable_size((void *)ptr);
#endif
}
void *js_mi_malloc(JSMallocState *s, size_t sz) {
void *ptr;
assert(sz != 0);
if (unlikely(s->malloc_size + sz > s->malloc_limit)) return NULL;
#ifdef HAVE_MIMALLOC
cell_rt *actor = (cell_rt*)s->opaque;
ptr = mi_heap_malloc(actor->heap, sz);
#else
ptr = malloc(sz);
#endif
if (!ptr) return NULL;
s->malloc_count++;
s->malloc_size += js_mi_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
#ifdef TRACY_ENABLE
if (tracy_profiling_enabled) {
#ifdef HAVE_MIMALLOC
cell_rt *actor = (cell_rt*)s->opaque;
TracyCAllocN(ptr, js_mi_malloc_usable_size(ptr) + MALLOC_OVERHEAD, actor->name);
#else
TracyCAllocN(ptr, js_mi_malloc_usable_size(ptr) + MALLOC_OVERHEAD, "actor");
#endif
}
#endif
return ptr;
}
void js_mi_free(JSMallocState *s, void *p) {
if (!p) return;
s->malloc_count--;
s->malloc_size -= js_mi_malloc_usable_size(p) + MALLOC_OVERHEAD;
#ifdef TRACY_ENABLE
if (tracy_profiling_enabled) {
#ifdef HAVE_MIMALLOC
cell_rt *actor = s->opaque;
TracyCFreeN(p, actor->name);
#else
TracyCFreeN(p, "actor");
#endif
}
#endif
#ifdef HAVE_MIMALLOC
mi_free(p);
#else
free(p);
#endif
}
void *js_mi_realloc(JSMallocState *s, void *p, size_t sz) {
size_t old_size;
if (!p) return sz ? js_mi_malloc(s, sz) : NULL;
old_size = js_mi_malloc_usable_size(p);
if (!sz) {
s->malloc_count--;
s->malloc_size -= old_size + MALLOC_OVERHEAD;
#ifdef TRACY_ENABLE
if (tracy_profiling_enabled) {
#ifdef HAVE_MIMALLOC
cell_rt *actor = (cell_rt*)s->opaque;
TracyCFreeN(p, actor->name);
#else
TracyCFreeN(p, "actor");
#endif
}
#endif
#ifdef HAVE_MIMALLOC
mi_free(p);
#else
free(p);
#endif
return NULL;
}
if (s->malloc_size + sz - old_size > s->malloc_limit) return NULL;
#ifdef TRACY_ENABLE
if (tracy_profiling_enabled) {
#ifdef HAVE_MIMALLOC
cell_rt *actor = (cell_rt*)s->opaque;
TracyCFreeN(p, actor->name);
#else
TracyCFreeN(p, "actor");
#endif
}
#endif
#ifdef HAVE_MIMALLOC
cell_rt *actor = (cell_rt*)s->opaque;
p = mi_heap_realloc(actor->heap, p, sz);
#else
p = realloc(p, sz);
#endif
if (!p) return NULL;
s->malloc_size += js_mi_malloc_usable_size(p) - old_size;
#ifdef TRACY_ENABLE
if (tracy_profiling_enabled) {
#ifdef HAVE_MIMALLOC
cell_rt *actor = (cell_rt*)s->opaque;
TracyCAllocN(p, js_mi_malloc_usable_size(p) + MALLOC_OVERHEAD, actor->name);
#else
TracyCAllocN(p, js_mi_malloc_usable_size(p) + MALLOC_OVERHEAD, "actor");
#endif
}
#endif
return p;
}
#ifdef HAVE_MIMALLOC
static const JSMallocFunctions mimalloc_funcs = {
js_mi_malloc,
js_mi_free,
js_mi_realloc,
js_mi_malloc_usable_size
};
#endif
int get_executable_path(char *buffer, unsigned int buffer_size) {
#if defined(__linux__)
ssize_t len = readlink("/proc/self/exe", buffer, buffer_size - 1);
if (len == -1) {
return 0;
}
buffer[len] = '\0';
return len;
#elif defined(__APPLE__)
if (_NSGetExecutablePath(buffer, &buffer_size) == 0) {
return buffer_size;
}
#elif defined(_WIN32)
return GetModuleFileName(NULL, buffer, buffer_size);
#endif
return 0;
}
int prosperon_mount_core(void)
{
char exe_path[PATH_MAX];
int exe_path_len = get_executable_path(exe_path, sizeof(exe_path));
if (exe_path_len == 0) {
printf("ERROR: Could not get executable path\n");
return 0;
}
// Load the entire executable into memory
FILE *fh = fopen(exe_path, "rb");
if (!fh) {
printf("ERROR: Could not open executable\n");
return 0;
}
fseek(fh, 0, SEEK_END);
long file_size = ftell(fh);
fseek(fh, 0, SEEK_SET);
unsigned char *buf = malloc(file_size);
if (!buf) {
printf("ERROR: Could not allocate memory for executable\n");
fclose(fh);
return 0;
}
if (fread(buf, 1, file_size, fh) != (size_t)file_size) {
printf("ERROR: Could not read executable\n");
free(buf);
fclose(fh);
return 0;
}
fclose(fh);
// Open the QOP archive from the in-memory data
int archive_size = qop_open_data(buf, file_size, &qop_core);
if (archive_size == 0) {
printf("ERROR: Could not open QOP archive\n");
free(buf);
return 0;
}
// Read the archive index
qop_hashmap = malloc(qop_core.hashmap_size);
if (!qop_hashmap) {
printf("ERROR: Could not allocate memory for QOP hashmap\n");
qop_close(&qop_core);
free(buf);
return 0;
}
int index_len = qop_read_index(&qop_core, qop_hashmap);
if (index_len == 0) {
printf("ERROR: Could not read QOP index\n");
free(qop_hashmap);
qop_hashmap = NULL;
qop_close(&qop_core);
free(buf);
return 0;
}
return 1;
}
// Wrapper struct to keep the array pointer stable
typedef struct {
#ifdef TRACY_ENABLE
TracyCZoneCtx *arr; // stb_ds dynamic array
#else
void *arr;
#endif
} tracy_stack_t;
#ifdef TRACY_ENABLE
static SDL_TLSID tracy_stack_id = {0};
static void tracy_cleanup_stack(void *value)
{
tracy_stack_t *stack = value;
if (stack) {
arrfree(stack->arr);
free(stack);
}
}
static tracy_stack_t *get_tracy_stack(void)
{
tracy_stack_t *stack = SDL_GetTLS(&tracy_stack_id);
if (!stack) {
stack = malloc(sizeof(tracy_stack_t));
stack->arr = NULL; // stb_ds starts with NULL
arrsetcap(stack->arr, 5); // Initial capacity
SDL_SetTLS(&tracy_stack_id, stack, tracy_cleanup_stack);
}
return stack;
}
void tracy_call_hook(JSContext *js, JSValue fn)
{
if (!tracy_profiling_enabled)
return;
tracy_stack_t *stack = get_tracy_stack();
js_debug debug;
js_debug_info(js, fn, &debug);
uint64_t srcloc = ___tracy_alloc_srcloc(debug.line, debug.filename, strlen(debug.filename), debug.name, strlen(debug.name), debug.unique);
arrput(stack->arr, ___tracy_emit_zone_begin_alloc(srcloc, 1));
free_js_debug_info(js, &debug);
}
void tracy_end_hook(JSContext *js, JSValue fn)
{
if (!tracy_profiling_enabled)
return;
tracy_stack_t *stack = get_tracy_stack();
if (arrlen(stack->arr) > 0)
___tracy_emit_zone_end(arrpop(stack->arr));
}
#endif
void actor_disrupt(cell_rt *crt)
{
crt->disrupt = 1;
if (crt->state != ACTOR_RUNNING)
actor_free(crt);
}
JSValue js_os_use(JSContext *js);
void script_startup(cell_rt *prt)
{
JSRuntime *rt;
#ifdef HAVE_MIMALLOC
rt = JS_NewRuntime2(&mimalloc_funcs, prt);
#else
rt = JS_NewRuntime();
#endif
JSContext *js = JS_NewContextRaw(rt);
JS_SetInterruptHandler(rt, actor_interrupt_cb, prt);
#ifdef TRACY_ENABLE
if (tracy_profiling_enabled) {
js_debug_sethook(js, tracy_call_hook, JS_HOOK_CALL);
js_debug_sethook(js, tracy_end_hook, JS_HOOK_RET);
}
#endif
JS_AddIntrinsicBaseObjects(js);
JS_AddIntrinsicEval(js);
JS_AddIntrinsicRegExp(js);
JS_AddIntrinsicJSON(js);
JS_AddIntrinsicMapSet(js);
JS_SetContextOpaque(js, prt);
prt->context = js;
cell_rt *crt = JS_GetContextOpaque(js);
JS_FreeValue(js, js_blob_use(js));
JSValue globalThis = JS_GetGlobalObject(js);
JSValue cell = JS_NewObject(js);
JS_SetPropertyStr(js,globalThis,"cell", cell);
JSValue hidden_fn = JS_NewObject(js);
JS_SetPropertyStr(js, cell, "hidden", hidden_fn);
JS_SetPropertyStr(js, hidden_fn, "os", js_os_use(js));
const char actorsym_script[] = "var sym = Symbol(`actordata`); sym;";
JSValue actorsym = JS_Eval(js, actorsym_script, sizeof(actorsym_script)-1, "internal", 0);
JS_SetPropertyStr(js, hidden_fn, "actorsym", actorsym);
crt->actor_sym = JS_ValueToAtom(js, actorsym);
if (crt->init_wota) {
JS_SetPropertyStr(js, hidden_fn, "init", wota2value(js, crt->init_wota));
// init wota can now be freed
free(crt->init_wota);
crt->init_wota = NULL;
}
JSValue js_cell = JS_GetPropertyStr(js, globalThis, "cell");
JSValue hidden = JS_GetPropertyStr(js, js_cell, "hidden");
size_t archive_size = qop_core.data_size - qop_core.files_offset;
JSValue blob = js_new_blob_stoned_copy(js, qop_core.data + qop_core.files_offset, archive_size);
JS_SetPropertyStr(js, hidden, "core_qop_blob", blob);
JS_FreeValue(js, hidden);
JS_FreeValue(js, js_cell);
JS_FreeValue(js, globalThis);
// Find and load engine.cm from QOP archive
qop_file *engine_file = qop_find(&qop_core, ENGINE);
if (!engine_file) {
printf("ERROR: Could not find file %s in QOP archive!\n", ENGINE);
return;
}
char *data = malloc(engine_file->size + 1);
if (!data) {
printf("ERROR: Could not allocate memory for %s!\n", ENGINE);
return;
}
int bytes_read = qop_read(&qop_core, engine_file, (unsigned char *)data);
if (bytes_read != (int)engine_file->size) {
printf("ERROR: Could not read file %s from QOP archive!\n", ENGINE);
free(data);
return;
}
data[engine_file->size] = 0;
crt->state = ACTOR_RUNNING;
JSValue v = JS_Eval(js, data, (size_t)engine_file->size, ENGINE, 0);
uncaught_exception(js, v);
crt->state = ACTOR_IDLE;
set_actor_state(crt);
}
static void signal_handler(int sig)
{
const char *str = NULL;
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;
}
if (!str) return;
exit_handler();
}
int main(int argc, char **argv)
{
int profile_enabled = 0;
int script_start = 1;
/* Check for --profile flag */
if (argc > 1 && strcmp(argv[1], "--profile") == 0) {
profile_enabled = 1; script_start = 2;
#ifndef TRACY_ENABLE
printf("Warning: --profile flag was specified but Tracy profiling is not compiled in\n");
#endif
}
#ifdef TRACY_ENABLE
tracy_profiling_enabled = profile_enabled;
#endif
/* Load QOP package attached to executable - this is now mandatory! */
int mounted = prosperon_mount_core();
if (!mounted) {
printf("ERROR: Could not load core QOP package.\n");
return 1;
}
/* Create the initial actor from the command line */
int actor_argc = argc - script_start;
char **actor_argv = argv + script_start;
WotaBuffer startwota;
wota_buffer_init(&startwota, 5);
wota_write_record(&startwota, 2);
wota_write_text(&startwota, "program");
wota_write_text(&startwota, actor_argv[0]);
wota_write_text(&startwota, "arg");
wota_write_array(&startwota, actor_argc - 1);
for (int i = 1; i < actor_argc; i++)
wota_write_text(&startwota, actor_argv[i]);
/* Initialize synchronization primitives */
actor_initialize();
root_cell = create_actor(startwota.data);
/* Set up signal and exit handlers */
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGSEGV, signal_handler);
signal(SIGABRT, signal_handler);
actor_loop();
return 0;
}
int JS_ArrayLength(JSContext *js, JSValue a)
{
JSValue length = JS_GetPropertyStr(js, a, "length");
int len;
JS_ToInt32(js,&len,length);
JS_FreeValue(js,length);
return len;
}
int cell_random() {
uint8_t buf[4];
randombytes(buf, sizeof(buf));
return *(int32_t *)buf;
}
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;
}
int uncaught_exception(JSContext *js, JSValue v)
{
cell_rt *rt = JS_GetContextOpaque(js);
if (!JS_HasException(js)) {
JS_FreeValue(js,v);
return 1;
}
JSValue exp = JS_GetException(js);
JSValue ret = JS_Call(js, rt->on_exception, JS_NULL, 1, &exp);
JS_FreeValue(js,ret);
JS_FreeValue(js, exp);
JS_FreeValue(js,v);
return 0;
}