#ifdef HAVE_MIMALLOC #include #endif #ifdef __APPLE__ #include #include #endif #ifdef _WIN32 #include #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 #endif #if defined(__APPLE__) #include #define MALLOC_OVERHEAD 0 #elif defined(_WIN32) #include #define MALLOC_OVERHEAD 8 #elif defined(__linux__) || defined(__GLIBC__) #define _GNU_SOURCE #include #include #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; }