563 lines
13 KiB
C
563 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 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;
|
|
}
|
|
|
|
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;
|
|
} |