txiki.js vm.c FFI support removed

Creato Il diff non scade mai
0 rimozioni
607 linee
2 aggiunte
608 linee
/*
/*
* txiki.js
* txiki.js
*
*
* Copyright (c) 2019-present Saúl Ibarra Corretgé <s@saghul.net>
* Copyright (c) 2019-present Saúl Ibarra Corretgé <s@saghul.net>
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* furnished to do so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* THE SOFTWARE.
*/
*/


#include "mem.h"
#include "mem.h"
#include "private.h"
#include "private.h"
#include "tjs.h"
#include "tjs.h"


#include <signal.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <string.h>


#define TJS__DEFAULT_STACK_SIZE 1024 * 1024 // 1 MB
#define TJS__DEFAULT_STACK_SIZE 1024 * 1024 // 1 MB


/* JS malloc functions */
/* JS malloc functions */


static void *tjs__mf_calloc(void *opaque, size_t count, size_t size) {
static void *tjs__mf_calloc(void *opaque, size_t count, size_t size) {
(void) opaque;
(void) opaque;
return tjs__calloc(count, size);
return tjs__calloc(count, size);
}
}


static void *tjs__mf_malloc(void *opaque, size_t size) {
static void *tjs__mf_malloc(void *opaque, size_t size) {
(void) opaque;
(void) opaque;
return tjs__malloc(size);
return tjs__malloc(size);
}
}


static void tjs__mf_free(void *opaque, void *ptr) {
static void tjs__mf_free(void *opaque, void *ptr) {
(void) opaque;
(void) opaque;
tjs__free(ptr);
tjs__free(ptr);
}
}


static void *tjs__mf_realloc(void *opaque, void *ptr, size_t size) {
static void *tjs__mf_realloc(void *opaque, void *ptr, size_t size) {
(void) opaque;
(void) opaque;
return tjs__realloc(ptr, size);
return tjs__realloc(ptr, size);
}
}


static const JSMallocFunctions tjs_mf = {
static const JSMallocFunctions tjs_mf = {
.js_calloc = tjs__mf_calloc,
.js_calloc = tjs__mf_calloc,
.js_malloc = tjs__mf_malloc,
.js_malloc = tjs__mf_malloc,
.js_free = tjs__mf_free,
.js_free = tjs__mf_free,
.js_realloc = tjs__mf_realloc,
.js_realloc = tjs__mf_realloc,
.js_malloc_usable_size = tjs__malloc_usable_size,
.js_malloc_usable_size = tjs__malloc_usable_size,
};
};


/* SharedArrayBuffer functions */
/* SharedArrayBuffer functions */


typedef struct {
typedef struct {
int ref_count;
int ref_count;
uint8_t buf[0];
uint8_t buf[0];
} TJSSABHeader;
} TJSSABHeader;


static int atomic_add_int(int *ptr, int v) {
static int atomic_add_int(int *ptr, int v) {
return atomic_fetch_add((_Atomic(uint32_t) *) ptr, v) + v;
return atomic_fetch_add((_Atomic(uint32_t) *) ptr, v) + v;
}
}


static void *tjs__sab_alloc(void *opaque, size_t size) {
static void *tjs__sab_alloc(void *opaque, size_t size) {
TJSSABHeader *sab = tjs__malloc(sizeof(*sab) + size);
TJSSABHeader *sab = tjs__malloc(sizeof(*sab) + size);
if (!sab) {
if (!sab) {
return NULL;
return NULL;
}
}
sab->ref_count = 1;
sab->ref_count = 1;
return sab->buf;
return sab->buf;
}
}


void tjs__sab_free(void *opaque, void *ptr) {
void tjs__sab_free(void *opaque, void *ptr) {
TJSSABHeader *sab = (TJSSABHeader *) ((uint8_t *) ptr - sizeof(TJSSABHeader));
TJSSABHeader *sab = (TJSSABHeader *) ((uint8_t *) ptr - sizeof(TJSSABHeader));
int ref_count = atomic_add_int(&sab->ref_count, -1);
int ref_count = atomic_add_int(&sab->ref_count, -1);
assert(ref_count >= 0);
assert(ref_count >= 0);
if (ref_count == 0) {
if (ref_count == 0) {
tjs__free(sab);
tjs__free(sab);
}
}
}
}


void tjs__sab_dup(void *opaque, void *ptr) {
void tjs__sab_dup(void *opaque, void *ptr) {
TJSSABHeader *sab = (TJSSABHeader *) ((uint8_t *) ptr - sizeof(TJSSABHeader));
TJSSABHeader *sab = (TJSSABHeader *) ((uint8_t *) ptr - sizeof(TJSSABHeader));
atomic_add_int(&sab->ref_count, 1);
atomic_add_int(&sab->ref_count, 1);
}
}


static const JSSharedArrayBufferFunctions tjs_sf = {
static const JSSharedArrayBufferFunctions tjs_sf = {
.sab_alloc = tjs__sab_alloc,
.sab_alloc = tjs__sab_alloc,
.sab_dup = tjs__sab_dup,
.sab_dup = tjs__sab_dup,
.sab_free = tjs__sab_free,
.sab_free = tjs__sab_free,
.sab_opaque = NULL,
.sab_opaque = NULL,
};
};


/* core */
/* core */
extern const uint8_t tjs__core[];
extern const uint8_t tjs__core[];
extern const uint32_t tjs__core_size;
extern const uint32_t tjs__core_size;
extern const uint8_t tjs__polyfills[];
extern const uint8_t tjs__polyfills[];
extern const uint32_t tjs__polyfills_size;
extern const uint32_t tjs__polyfills_size;
extern const uint8_t tjs__run_main[];
extern const uint8_t tjs__run_main[];
extern const uint32_t tjs__run_main_size;
extern const uint32_t tjs__run_main_size;




static int tjs__argc = 0;
static int tjs__argc = 0;
static char **tjs__argv = NULL;
static char **tjs__argv = NULL;




static void tjs__bootstrap_core(JSContext *ctx, JSValue ns) {
static void tjs__bootstrap_core(JSContext *ctx, JSValue ns) {
tjs__mod_dns_init(ctx, ns);
tjs__mod_dns_init(ctx, ns);
tjs__mod_engine_init(ctx, ns);
tjs__mod_engine_init(ctx, ns);
tjs__mod_error_init(ctx, ns);
tjs__mod_error_init(ctx, ns);
tjs__mod_ffi_init(ctx, ns);
// tjs__mod_ffi_init(ctx, ns);
tjs__mod_fs_init(ctx, ns);
tjs__mod_fs_init(ctx, ns);
tjs__mod_fswatch_init(ctx, ns);
tjs__mod_fswatch_init(ctx, ns);
tjs__mod_os_init(ctx, ns);
tjs__mod_os_init(ctx, ns);
tjs__mod_process_init(ctx, ns);
tjs__mod_process_init(ctx, ns);
tjs__mod_signals_init(ctx, ns);
tjs__mod_signals_init(ctx, ns);
tjs__mod_sqlite3_init(ctx, ns);
tjs__mod_sqlite3_init(ctx, ns);
tjs__mod_streams_init(ctx, ns);
tjs__mod_streams_init(ctx, ns);
tjs__mod_sys_init(ctx, ns);
tjs__mod_sys_init(ctx, ns);
tjs__mod_timers_init(ctx, ns);
tjs__mod_timers_init(ctx, ns);
tjs__mod_udp_init(ctx, ns);
tjs__mod_udp_init(ctx, ns);
tjs__mod_wasm_init(ctx, ns);
tjs__mod_wasm_init(ctx, ns);
tjs__mod_worker_init(ctx, ns);
tjs__mod_worker_init(ctx, ns);
tjs__mod_ws_init(ctx, ns);
tjs__mod_ws_init(ctx, ns);
tjs__mod_xhr_init(ctx, ns);
tjs__mod_xhr_init(ctx, ns);
#ifndef _WIN32
#ifndef _WIN32
tjs__mod_posix_socket_init(ctx, ns);
tjs__mod_posix_socket_init(ctx, ns);
#endif
#endif
}
}


JSValue tjs__get_args(JSContext *ctx) {
JSValue tjs__get_args(JSContext *ctx) {
JSValue args = JS_NewArray(ctx);
JSValue args = JS_NewArray(ctx);
for (int i = 0; i < tjs__argc; i++) {
for (int i = 0; i < tjs__argc; i++) {
JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, tjs__argv[i]));
JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, tjs__argv[i]));
}
}
return args;
return args;
}
}


static JSValue tjs__dispatch_event(JSContext *ctx, JSValue *event) {
static JSValue tjs__dispatch_event(JSContext *ctx, JSValue *event) {
TJSRuntime *qrt = TJS_GetRuntime(ctx);
TJSRuntime *qrt = TJS_GetRuntime(ctx);
CHECK_NOT_NULL(qrt);
CHECK_NOT_NULL(qrt);


if (qrt->freeing) {
if (qrt->freeing) {
return JS_UNDEFINED;
return JS_UNDEFINED;
}
}


JSValue global_obj = JS_GetGlobalObject(ctx);
JSValue global_obj = JS_GetGlobalObject(ctx);
JSValue ret = JS_Call(ctx, qrt->builtins.dispatch_event_func, global_obj, 1, event);
JSValue ret = JS_Call(ctx, qrt->builtins.dispatch_event_func, global_obj, 1, event);
JS_FreeValue(ctx, global_obj);
JS_FreeValue(ctx, global_obj);


return ret;
return ret;
}
}


static void tjs__promise_rejection_tracker(JSContext *ctx,
static void tjs__promise_rejection_tracker(JSContext *ctx,
JSValue promise,
JSValue promise,
JSValue reason,
JSValue reason,
bool is_handled,
bool is_handled,
void *opaque) {
void *opaque) {
TJSRuntime *qrt = TJS_GetRuntime(ctx);
TJSRuntime *qrt = TJS_GetRuntime(ctx);
CHECK_NOT_NULL(qrt);
CHECK_NOT_NULL(qrt);


if (qrt->freeing) {
if (qrt->freeing) {
return;
return;
}
}


if (!is_handled) {
if (!is_handled) {
JSValue event_name = JS_NewString(ctx, "unhandledrejection");
JSValue event_name = JS_NewString(ctx, "unhandledrejection");
JSValue args[3];
JSValue args[3];
args[0] = event_name;
args[0] = event_name;
args[1] = promise;
args[1] = promise;
args[2] = reason;
args[2] = reason;


JSValue event = JS_CallConstructor(ctx, qrt->builtins.promise_event_ctor, countof(args), args);
JSValue event = JS_CallConstructor(ctx, qrt->builtins.promise_event_ctor, countof(args), args);
CHECK_EQ(JS_IsException(event), 0);
CHECK_EQ(JS_IsException(event), 0);
JSValue ret = tjs__dispatch_event(ctx, &event);
JSValue ret = tjs__dispatch_event(ctx, &event);


JS_FreeValue(ctx, event);
JS_FreeValue(ctx, event);
JS_FreeValue(ctx, event_name);
JS_FreeValue(ctx, event_name);


if (JS_IsException(ret)) {
if (JS_IsException(ret)) {
tjs_dump_error(ctx);
tjs_dump_error(ctx);
goto fail;
goto fail;
} else {
} else {
if (JS_ToBool(ctx, ret)) {
if (JS_ToBool(ctx, ret)) {
// The event wasn't cancelled, maybe abort.
// The event wasn't cancelled, maybe abort.
fail:;
fail:;
TJSRuntime *qrt = TJS_GetRuntime(ctx);
TJSRuntime *qrt = TJS_GetRuntime(ctx);
CHECK_NOT_NULL(qrt);
CHECK_NOT_NULL(qrt);
JS_Throw(qrt->ctx, JS_DupValue(qrt->ctx, reason));
JS_Throw(qrt->ctx, JS_DupValue(qrt->ctx, reason));
TJS_Stop(qrt);
TJS_Stop(qrt);
}
}
}
}


JS_FreeValue(ctx, ret);
JS_FreeValue(ctx, ret);
}
}
}
}


static void uv__stop(uv_async_t *handle) {
static void uv__stop(uv_async_t *handle) {
TJSRuntime *qrt = handle->data;
TJSRuntime *qrt = handle->data;
CHECK_NOT_NULL(qrt);
CHECK_NOT_NULL(qrt);


uv_stop(&qrt->loop);
uv_stop(&qrt->loop);
}
}


void TJS_DefaultOptions(TJSRunOptions *options) {
void TJS_DefaultOptions(TJSRunOptions *options) {
static TJSRunOptions default_options = { .mem_limit = 0, .stack_size = TJS__DEFAULT_STACK_SIZE };
static TJSRunOptions default_options = { .mem_limit = 0, .stack_size = TJS__DEFAULT_STACK_SIZE };


memcpy(options, &default_options, sizeof(*options));
memcpy(options, &default_options, sizeof(*options));
}
}


TJSRuntime *TJS_NewRuntime(void) {
TJSRuntime *TJS_NewRuntime(void) {
TJSRunOptions options;
TJSRunOptions options;
TJS_DefaultOptions(&options);
TJS_DefaultOptions(&options);
return TJS_NewRuntimeInternal(false, &options);
return TJS_NewRuntimeInternal(false, &options);
}
}


TJSRuntime *TJS_NewRuntimeOptions(TJSRunOptions *options) {
TJSRuntime *TJS_NewRuntimeOptions(TJSRunOptions *options) {
return TJS_NewRuntimeInternal(false, options);
return TJS_NewRuntimeInternal(false, options);
}
}


TJSRuntime *TJS_NewRuntimeWorker(void) {
TJSRuntime *TJS_NewRuntimeWorker(void) {
TJSRunOptions options;
TJSRunOptions options;
TJS_DefaultOptions(&options);
TJS_DefaultOptions(&options);
return TJS_NewRuntimeInternal(true, &options);
return TJS_NewRuntimeInternal(true, &options);
}
}


TJSRuntime *TJS_NewRuntimeInternal(bool is_worker, TJSRunOptions *options) {
TJSRuntime *TJS_NewRuntimeInternal(bool is_worker, TJSRunOptions *options) {
JSRuntime *rt = NULL;
JSRuntime *rt = NULL;
JSContext *ctx = NULL;
JSContext *ctx = NULL;
TJSRuntime *qrt = tjs__mallocz(sizeof(*qrt));
TJSRuntime *qrt = tjs__mallocz(sizeof(*qrt));


memcpy(&qrt->options, options, sizeof(*options));
memcpy(&qrt->options, options, sizeof(*options));


rt = JS_NewRuntime2(&tjs_mf, NULL);
rt = JS_NewRuntime2(&tjs_mf, NULL);
CHECK_NOT_NULL(rt);
CHECK_NOT_NULL(rt);
qrt->rt = rt;
qrt->rt = rt;


ctx = JS_NewContext(rt);
ctx = JS_NewContext(rt);
CHECK_NOT_NULL(ctx);
CHECK_NOT_NULL(ctx);
qrt->ctx = ctx;
qrt->ctx = ctx;


JS_SetRuntimeOpaque(rt, qrt);
JS_SetRuntimeOpaque(rt, qrt);
JS_SetContextOpaque(ctx, qrt);
JS_SetContextOpaque(ctx, qrt);


/* Set memory limit */
/* Set memory limit */
JS_SetMemoryLimit(rt, options->mem_limit);
JS_SetMemoryLimit(rt, options->mem_limit);


/* Set stack size */
/* Set stack size */
JS_SetMaxStackSize(rt, options->stack_size);
JS_SetMaxStackSize(rt, options->stack_size);


/* SharedArrayBuffer functions */
/* SharedArrayBuffer functions */
JS_SetSharedArrayBufferFunctions(rt, &tjs_sf);
JS_SetSharedArrayBufferFunctions(rt, &tjs_sf);


/* Worker support */
/* Worker support */
qrt->is_worker = is_worker;
qrt->is_worker = is_worker;
JS_SetCanBlock(rt, is_worker);
JS_SetCanBlock(rt, is_worker);


CHECK_EQ(uv_loop_init(&qrt->loop), 0);
CHECK_EQ(uv_loop_init(&qrt->loop), 0);


/* handle which runs the job queue */
/* handle which runs the job queue */
CHECK_EQ(uv_prepare_init(&qrt->loop, &qrt->jobs.prepare), 0);
CHECK_EQ(uv_prepare_init(&qrt->loop, &qrt->jobs.prepare), 0);
qrt->jobs.prepare.data = qrt;
qrt->jobs.prepare.data = qrt;


/* handle to prevent the loop from blocking for i/o when there are pending jobs. */
/* handle to prevent the loop from blocking for i/o when there are pending jobs. */
CHECK_EQ(uv_idle_init(&qrt->loop, &qrt->jobs.idle), 0);
CHECK_EQ(uv_idle_init(&qrt->loop, &qrt->jobs.idle), 0);
qrt->jobs.idle.data = qrt;
qrt->jobs.idle.data = qrt;


/* handle which runs the job queue */
/* handle which runs the job queue */
CHECK_EQ(uv_check_init(&qrt->loop, &qrt->jobs.check), 0);
CHECK_EQ(uv_check_init(&qrt->loop, &qrt->jobs.check), 0);
qrt->jobs.check.data = qrt;
qrt->jobs.check.data = qrt;


/* handle for stopping this runtime (also works from another thread) */
/* handle for stopping this runtime (also works from another thread) */
CHECK_EQ(uv_async_init(&qrt->loop, &qrt->stop, uv__stop), 0);
CHECK_EQ(uv_async_init(&qrt->loop, &qrt->stop, uv__stop), 0);
qrt->stop.data = qrt;
qrt->stop.data = qrt;


/* loader for ES modules */
/* loader for ES modules */
JS_SetModuleLoaderFunc(rt, tjs_module_normalizer, tjs_module_loader, qrt);
JS_SetModuleLoaderFunc(rt, tjs_module_normalizer, tjs_module_loader, qrt);


/* unhandled promise rejection tracker */
/* unhandled promise rejection tracker */
JS_SetHostPromiseRejectionTracker(rt, tjs__promise_rejection_tracker, NULL);
JS_SetHostPromiseRejectionTracker(rt, tjs__promise_rejection_tracker, NULL);


/* start bootstrap */
/* start bootstrap */
JSValue global_obj = JS_GetGlobalObject(ctx);
JSValue global_obj = JS_GetGlobalObject(ctx);
JSValue core_sym = JS_NewSymbol(ctx, "tjs.internal.core", true);
JSValue core_sym = JS_NewSymbol(ctx, "tjs.internal.core", true);
JSAtom core_atom = JS_ValueToAtom(ctx, core_sym);
JSAtom core_atom = JS_ValueToAtom(ctx, core_sym);
JSValue core = JS_NewObjectProto(ctx, JS_NULL);
JSValue core = JS_NewObjectProto(ctx, JS_NULL);


CHECK_EQ(JS_DefinePropertyValue(ctx, global_obj, core_atom, core, JS_PROP_C_W_E), true);
CHECK_EQ(JS_DefinePropertyValue(ctx, global_obj, core_atom, core, JS_PROP_C_W_E), true);
CHECK_EQ(JS_DefinePropertyValueStr(ctx, core, "isWorker", JS_NewBool(ctx, is_worker), JS_PROP_C_W_E), true);
CHECK_EQ(JS_DefinePropertyValueStr(ctx, core, "isWorker", JS_NewBool(ctx, is_worker), JS_PROP_C_W_E), true);


tjs__bootstrap_core(ctx, core);
tjs__bootstrap_core(ctx, core);


CHECK_EQ(tjs__eval_bytecode(ctx, tjs__polyfills, tjs__polyfills_size, true), 0);
CHECK_EQ(tjs__eval_bytecode(ctx, tjs__polyfills, tjs__polyfills_size, true), 0);
CHECK_EQ(tjs__eval_bytecode(ctx, tjs__core, tjs__core_size, true), 0);
CHECK_EQ(tjs__eval_bytecode(ctx, tjs__core, tjs__core_size, true), 0);


/* Load some builtin references for easy access */
/* Load some builtin references for easy access */
qrt->builtins.dispatch_event_func = JS_GetPropertyStr(ctx, global_obj, "dispatchEvent");
qrt->builtins.dispatch_event_func = JS_GetPropertyStr(ctx, global_obj, "dispatchEvent");
CHECK_EQ(JS_IsUndefined(qrt->builtins.dispatch_event_func), 0);
CHECK_EQ(JS_IsUndefined(qrt->builtins.dispatch_event_func), 0);
qrt->builtins.promise_event_ctor = JS_GetPropertyStr(qrt->ctx, global_obj, "PromiseRejectionEvent");
qrt->builtins.promise_event_ctor = JS_GetPropertyStr(qrt->ctx, global_obj, "PromiseRejectionEvent");
CHECK_EQ(JS_IsUndefined(qrt->builtins.promise_event_ctor), 0);
CHECK_EQ(JS_IsUndefined(qrt->builtins.promise_event_ctor), 0);


/* end bootstrap */
/* end bootstrap */
JS_FreeAtom(ctx, core_atom);
JS_FreeAtom(ctx, core_atom);
JS_FreeValue(ctx, core_sym);
JS_FreeValue(ctx, core_sym);
JS_FreeValue(ctx, global_obj);
JS_FreeValue(ctx, global_obj);


/* WASM */
/* WASM */
qrt->wasm_ctx.env = m3_NewEnvironment();
qrt->wasm_ctx.env = m3_NewEnvironment();


/* Timers */
/* Timers */
qrt->timers.timers = NULL;
qrt->timers.timers = NULL;
qrt->timers.next_timer = 1;
qrt->timers.next_timer = 1;


return qrt;
return qrt;
}
}


void TJS_FreeRuntime(TJSRuntime *qrt) {
void TJS_FreeRuntime(TJSRuntime *qrt) {
qrt->freeing = true;
qrt->freeing = true;


/* Close all core loop handles. */
/* Close all core loop handles. */
uv_close((uv_handle_t *) &qrt->jobs.prepare, NULL);
uv_close((uv_handle_t *) &qrt->jobs.prepare, NULL);
uv_close((uv_handle_t *) &qrt->jobs.idle, NULL);
uv_close((uv_handle_t *) &qrt->jobs.idle, NULL);
uv_close((uv_handle_t *) &qrt->jobs.check, NULL);
uv_close((uv_handle_t *) &qrt->jobs.check, NULL);
uv_close((uv_handle_t *) &qrt->stop, NULL);
uv_close((uv_handle_t *) &qrt->stop, NULL);
if (qrt->curl_ctx.curlm_h) {
if (qrt->curl_ctx.curlm_h) {
uv_close((uv_handle_t *) &qrt->curl_ctx.timer, NULL);
uv_close((uv_handle_t *) &qrt->curl_ctx.timer, NULL);
}
}


/* Destroy all timers */
/* Destroy all timers */
tjs__destroy_timers(qrt);
tjs__destroy_timers(qrt);


/* Destroy the JS engine. */
/* Destroy the JS engine. */
JS_FreeValue(qrt->ctx, qrt->builtins.dispatch_event_func);
JS_FreeValue(qrt->ctx, qrt->builtins.dispatch_event_func);
qrt->builtins.dispatch_event_func = JS_UNDEFINED;
qrt->builtins.dispatch_event_func = JS_UNDEFINED;
JS_FreeValue(qrt->ctx, qrt->builtins.promise_event_ctor);
JS_FreeValue(qrt->ctx, qrt->builtins.promise_event_ctor);
qrt->builtins.promise_event_ctor = JS_UNDEFINED;
qrt->builtins.promise_event_ctor = JS_UNDEFINED;
JS_FreeContext(qrt->ctx);
JS_FreeContext(qrt->ctx);
JS_FreeRuntime(qrt->rt);
JS_FreeRuntime(qrt->rt);


/* Destroy CURLM handle. */
/* Destroy CURLM handle. */
if (qrt->curl_ctx.curlm_h) {
if (qrt->curl_ctx.curlm_h) {
curl_multi_cleanup(qrt->curl_ctx.curlm_h);
curl_multi_cleanup(qrt->curl_ctx.curlm_h);
qrt->curl_ctx.curlm_h = NULL;
qrt->curl_ctx.curlm_h = NULL;
}
}


/* Destroy WASM runtime. */
/* Destroy WASM runtime. */
m3_FreeEnvironment(qrt->wasm_ctx.env);
m3_FreeEnvironment(qrt->wasm_ctx.env);
qrt->wasm_ctx.env = NULL;
qrt->wasm_ctx.env = NULL;


/* Cleanup loop. All handles should be closed. */
/* Cleanup loop. All handles should be closed. */
int closed = 0;
int closed = 0;
for (int i = 0; i < 5; i++) {
for (int i = 0; i < 5; i++) {
if (uv_loop_close(&qrt->loop) == 0) {
if (uv_loop_close(&qrt->loop) == 0) {
closed = 1;
closed = 1;
break;
break;
}
}
uv_run(&qrt->loop, UV_RUN_NOWAIT);
uv_run(&qrt->loop, UV_RUN_NOWAIT);
}
}
#ifdef DEBUG
#ifdef DEBUG
if (!closed) {
if (!closed) {
uv_print_all_handles(&qrt->loop, stderr);
uv_print_all_handles(&qrt->loop, stderr);
}
}
CHECK_EQ(closed, 1);
CHECK_EQ(closed, 1);
#else
#else
(void) closed;
(void) closed;
#endif
#endif


tjs__free(qrt);
tjs__free(qrt);
}
}


void TJS_Initialize(int argc, char **argv) {
void TJS_Initialize(int argc, char **argv) {
curl_global_init(CURL_GLOBAL_ALL);
curl_global_init(CURL_GLOBAL_ALL);


CHECK_EQ(0, uv_replace_allocator(tjs__malloc, tjs__realloc, tjs__calloc, tjs__free));
CHECK_EQ(0, uv_replace_allocator(tjs__malloc, tjs__realloc, tjs__calloc, tjs__free));


tjs__argc = argc;
tjs__argc = argc;
tjs__argv = uv_setup_args(argc, argv);
tjs__argv = uv_setup_args(argc, argv);


setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);


#ifdef SIGPIPE
#ifdef SIGPIPE
signal(SIGPIPE, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
#endif
#endif
}
}


JSContext *TJS_GetJSContext(TJSRuntime *qrt) {
JSContext *TJS_GetJSContext(TJSRuntime *qrt) {
return qrt->ctx;
return qrt->ctx;
}
}


TJSRuntime *TJS_GetRuntime(JSContext *ctx) {
TJSRuntime *TJS_GetRuntime(JSContext *ctx) {
return JS_GetContextOpaque(ctx);
return JS_GetContextOpaque(ctx);
}
}


static void uv__idle_cb(uv_idle_t *handle) {
static void uv__idle_cb(uv_idle_t *handle) {
// Noop
// Noop
}
}


static void uv__maybe_idle(TJSRuntime *qrt) {
static void uv__maybe_idle(TJSRuntime *qrt) {
if (JS_IsJobPending(qrt->rt)) {
if (JS_IsJobPending(qrt->rt)) {
CHECK_EQ(uv_idle_start(&qrt->jobs.idle, uv__idle_cb), 0);
CHECK_EQ(uv_idle_start(&qrt->jobs.idle, uv__idle_cb), 0);
} else {
} else {
CHECK_EQ(uv_idle_stop(&qrt->jobs.idle), 0);
CHECK_EQ(uv_idle_stop(&qrt->jobs.idle), 0);
}
}
}
}


static void uv__prepare_cb(uv_prepare_t *handle) {
static void uv__prepare_cb(uv_prepare_t *handle) {
TJSRuntime *qrt = handle->data;
TJSRuntime *qrt = handle->data;
CHECK_NOT_NULL(qrt);
CHECK_NOT_NULL(qrt);


uv__maybe_idle(qrt);
uv__maybe_idle(qrt);
}
}


void tjs__execute_jobs(JSContext *ctx) {
void tjs__execute_jobs(JSContext *ctx) {
JSContext *ctx1;
JSContext *ctx1;
int err;
int err;


/* execute the pending jobs */
/* execute the pending jobs */
for (;;) {
for (;;) {
err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1);
if (err <= 0) {
if (err <= 0) {
if (err < 0) {
if (err < 0) {
TJSRuntime *qrt = TJS_GetRuntime(ctx);
TJSRuntime *qrt = TJS_GetRuntime(ctx);
CHECK_NOT_NULL(qrt);
CHECK_NOT_NULL(qrt);
TJS_Stop(qrt);
TJS_Stop(qrt);
}
}


break;
break;
}
}
}
}
}
}


static void uv__check_cb(uv_check_t *handle) {
static void uv__check_cb(uv_check_t *handle) {
TJSRuntime *qrt = handle->data;
TJSRuntime *qrt = handle->data;
CHECK_NOT_NULL(qrt);
CHECK_NOT_NULL(qrt);


tjs__execute_jobs(qrt->ctx);
tjs__execute_jobs(qrt->ctx);


uv__maybe_idle(qrt);
uv__maybe_idle(qrt);
}
}


/* main loop which calls the user JS callbacks */
/* main loop which calls the user JS callbacks */
int TJS_Run(TJSRuntime *qrt) {
int TJS_Run(TJSRuntime *qrt) {
int ret = 0;
int ret = 0;


CHECK_EQ(uv_prepare_start(&qrt->jobs.prepare, uv__prepare_cb), 0);
CHECK_EQ(uv_prepare_start(&qrt->jobs.prepare, uv__prepare_cb), 0);
uv_unref((uv_handle_t *) &qrt->jobs.prepare);
uv_unref((uv_handle_t *) &qrt->jobs.prepare);
CHECK_EQ(uv_check_start(&qrt->jobs.check, uv__check_cb), 0);
CHECK_EQ(uv_check_start(&qrt->jobs.check, uv__check_cb), 0);
uv_unref((uv_handle_t *) &qrt->jobs.check);
uv_unref((uv_handle_t *) &qrt->jobs.check);


/* Use the async handle to keep the worker alive even when there is nothing to do. */
/* Use the async handle to keep the worker alive even when there is nothing to do. */
if (!qrt->is_worker) {
if (!qrt->is_worker) {
uv_unref((uv_handle_t *) &qrt->stop);
uv_unref((uv_handle_t *) &qrt->stop);


/* If we are running the main interpreter, run the entrypoint. */
/* If we are running the main interpreter, run the entrypoint. */
ret = tjs__eval_bytecode(qrt->ctx, tjs__run_main, tjs__run_main_size, true);
ret = tjs__eval_bytecode(qrt->ctx, tjs__run_main, tjs__run_main_size, true);
}
}


if (ret != 0) {
if (ret != 0) {
return ret;
return ret;
}
}


int r;
int r;
do {
do {
uv__maybe_idle(qrt);
uv__maybe_idle(qrt);
r = uv_run(&qrt->loop, UV_RUN_DEFAULT);
r = uv_run(&qrt->loop, UV_RUN_DEFAULT);
} while (r == 0 && JS_IsJobPending(qrt->rt));
} while (r == 0 && JS_IsJobPending(qrt->rt));


if (JS_HasException(qrt->ctx)) {
if (JS_HasException(qrt->ctx)) {
tjs_dump_error(qrt->ctx);
tjs_dump_error(qrt->ctx);
ret = 1;
ret = 1;
}
}


return ret;
return ret;
}
}


void TJS_Stop(TJSRuntime *qrt) {
void TJS_Stop(TJSRuntime *qrt) {
CHECK_NOT_NULL(qrt);
CHECK_NOT_NULL(qrt);
uv_async_send(&qrt->stop);
uv_async_send(&qrt->stop);
}
}


uv_loop_t *TJS_GetLoop(TJSRuntime *qrt) {
uv_loop_t *TJS_GetLoop(TJSRuntime *qrt) {
return &qrt->loop;
return &qrt->loop;
}
}


int tjs__load_file(JSContext *ctx, DynBuf *dbuf, const char *filename) {
int tjs__load_file(JSContext *ctx, DynBuf *dbuf, const char *filename) {
uv_fs_t req;
uv_fs_t req;
uv_file fd;
uv_file fd;
int r;
int r;


r = uv_fs_open(NULL, &req, filename, O_RDONLY, 0, NULL);
r = uv_fs_open(NULL, &req, filename, O_RDONLY, 0, NULL);
uv_fs_req_cleanup(&req);
uv_fs_req_cleanup(&req);
if (r < 0) {
if (r < 0) {
return r;
return r;
}
}


fd = r;
fd = r;
char buf[64 * 1024];
char buf[64 * 1024];
uv_buf_t b = uv_buf_init(buf, sizeof(buf));
uv_buf_t b = uv_buf_init(buf, sizeof(buf));
size_t offset = 0;
size_t offset = 0;


do {
do {
r = uv_fs_read(NULL, &req, fd, &b, 1, offset, NULL);
r = uv_fs_read(NULL, &req, fd, &b, 1, offset, NULL);
uv_fs_req_cleanup(&req);
uv_fs_req_cleanup(&req);
if (r <= 0) {
if (r <= 0) {
break;
break;
}
}
offset += r;
offset += r;
r = dbuf_put(dbuf, (const uint8_t *) b.base, r);
r = dbuf_put(dbuf, (const uint8_t *) b.base, r);
if (r != 0) {
if (r != 0) {
break;
break;
}
}
} while (1);
} while (1);


uv_fs_close(NULL, &req, fd, NULL);
uv_fs_close(NULL, &req, fd, NULL);
uv_fs_req_cleanup(&req);
uv_fs_req_cleanup(&req);


return r;
return r;
}
}


JSValue TJS_EvalModuleContent(JSContext *ctx,
JSValue TJS_EvalModuleContent(JSContext *ctx,
const char *specifier,
const char *specifier,
bool is_main,
bool is_main,
bool use_real_path,
bool use_real_path,
const char *content,
const char *content,
size_t len) {
size_t len) {
/* Compile then run to be able to set import.meta */
/* Compile then run to be able to set import.meta */
JSValue ret = JS_Eval(ctx, content, len, specifier, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
JSValue ret = JS_Eval(ctx, content, len, specifier, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
if (!JS_IsException(ret)) {
if (!JS_IsException(ret)) {
js_module_set_import_meta(ctx, ret, use_real_path, is_main);
js_module_set_import_meta(ctx, ret, use_real_path, is_main);
ret = JS_EvalFunction(ctx, ret);
ret = JS_EvalFunction(ctx, ret);
}
}


/* Emit window 'load' event. */
/* Emit window 'load' event. */
if (!JS_IsException(ret) && is_main) {
if (!JS_IsException(ret) && is_main) {
static char emit_window_load[] = "window.dispatchEvent(new Event('load'));";
static char emit_window_load[] = "window.dispatchEvent(new Event('load'));";
JSValue ret1 = JS_Eval(ctx, emit_window_load, strlen(emit_window_load), "<global>", JS_EVAL_TYPE_GLOBAL);
JSValue ret1 = JS_Eval(ctx, emit_window_load, strlen(emit_window_load), "<global>", JS_EVAL_TYPE_GLOBAL);
if (JS_IsException(ret1)) {
if (JS_IsException(ret1)) {
tjs_dump_error(ctx);
tjs_dump_error(ctx);
}
}
}
}


return ret;
return ret;
}
}


JSValue TJS_EvalScript(JSContext *ctx, const char *filename) {
JSValue TJS_EvalScript(JSContext *ctx, const char *filename) {
DynBuf dbuf;
DynBuf dbuf;
size_t dbuf_size;
size_t dbuf_size;
int r;
int r;
JSValue ret;
JSValue ret;


tjs_dbuf_init(ctx, &dbuf);
tjs_dbuf_init(ctx, &dbuf);
r = tjs__load_file(ctx, &dbuf, filename);
r = tjs__load_file(ctx, &dbuf, filename);
if (r != 0) {
if (r != 0) {
dbuf_free(&dbuf);
dbuf_free(&dbuf);
JS_ThrowReferenceError(ctx, "could not load '%s' - %s: %s", filename, uv_err_name(r), uv_strerror(r));
JS_ThrowReferenceError(ctx, "could not load '%s' - %s: %s", filename, uv_err_name(r), uv_strerror(r));
return JS_EXCEPTION;
return JS_EXCEPTION;
}
}


dbuf_size = dbuf.size;
dbuf_size = dbuf.size;


/* Add null termination, required by JS_Eval. */
/* Add null termination, required by JS_Eval. */
dbuf_putc(&dbuf, '\0');
dbuf_putc(&dbuf, '\0');


ret = JS_Eval(ctx, (char *) dbuf.buf, dbuf_size - 1, filename, JS_EVAL_TYPE_GLOBAL);
ret = JS_Eval(ctx, (char *) dbuf.buf, dbuf_size - 1, filename, JS_EVAL_TYPE_GLOBAL);


dbuf_free(&dbuf);
dbuf_free(&dbuf);
return ret;
return ret;
}
}


JSValue TJS_EvalModule(JSContext *ctx, const char *filename, bool is_main) {
JSValue TJS_EvalModule(JSContext *ctx, const char *filename, bool is_main) {
DynBuf dbuf;
DynBuf dbuf;
size_t dbuf_size;
size_t dbuf_size;
int r;
int r;
JSValue ret;
JSValue ret;


tjs_dbuf_init(ctx, &dbuf);
tjs_dbuf_init(ctx, &dbuf);
r = tjs__load_file(ctx, &dbuf, filename);
r = tjs__load_file(ctx, &dbuf, filename);
if (r != 0) {
if (r != 0) {
dbuf_free(&dbuf);
dbuf_free(&dbuf);
JS_ThrowReferenceError(ctx, "could not load '%s' - %s: %s", filename, uv_err_name(r), uv_strerror(r));
JS_ThrowReferenceError(ctx, "could not load '%s' - %s: %s", filename, uv_err_name(r), uv_strerror(r));
return JS_EXCEPTION;
return JS_EXCEPTION;
}
}


dbuf_size = dbuf.size;
dbuf_size = dbuf.size;


/* Add null termination, required by JS_Eval. */
/* Add null termination, required by JS_Eval. */
dbuf_putc(&dbuf, '\0');
dbuf_putc(&dbuf, '\0');


ret = TJS_EvalModuleContent(ctx, filename, is_main, true, (char *) dbuf.buf, dbuf_size - 1);
ret = TJS_EvalModuleContent(ctx, filename, is_main, true, (char *) dbuf.buf, dbuf_size - 1);


dbuf_free(&dbuf);
dbuf_free(&dbuf);
return ret;
return ret;
}
}