123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819 |
- /**
- * interface.c
- *
- * We primarily use JSValue* (pointer to JSValue) when communicating with the
- * host javascript environment, because pointers are trivial to use for calls
- * into emscripten because they're just a number!
- *
- * As with the quickjs.h API, a JSValueConst* value is "borrowed" and should
- * not be freed. A JSValue* is "owned" and should be freed by the owner.
- *
- * Functions starting with "QTS_" are exported by generate.ts to:
- * - interface.h for native C code.
- * - ffi.ts for emscripten.
- *
- * We support building the following build outputs:
- *
- * ## 1. Native machine code
- * For internal development testing purposes.
- *
- * ## 2. WASM via Emscripten
- * For general production use.
- *
- * ## 3. Experimental: Asyncified WASM via Emscripten with -s ASYNCIFY=1.
- * This variant supports treating async host Javascript calls as synchronous
- * from the perspective of the WASM c code.
- *
- * The way this works is described here:
- * https://emscripten.org/docs/porting/asyncify.html
- *
- * In this variant, any call into our C code could return a promise if it ended
- * up suspended. We mark the methods we suspect might suspend due to users' code
- * as returning MaybeAsync(T). This information is ignored for the regular
- * build.
- */
- #ifdef __EMSCRIPTEN__
- #include <emscripten.h>
- #endif
- #include <math.h> // For NAN
- #include <stdbool.h>
- #include <stdio.h>
- #include <string.h>
- #ifdef QTS_SANITIZE_LEAK
- #include <sanitizer/lsan_interface.h>
- #endif
- #include "../quickjs/cutils.h"
- #include "../quickjs/quickjs-libc.h"
- #include "../quickjs/quickjs.h"
- #define PKG "quickjs-emscripten: "
- #ifdef QTS_DEBUG_MODE
- #define QTS_DEBUG(msg) qts_log(msg);
- #define QTS_DUMP(value) qts_dump(ctx, value);
- #else
- #define QTS_DEBUG(msg) ;
- #define QTS_DUMP(value) ;
- #endif
- /**
- * Signal to our FFI code generator that this string argument should be passed as a pointer
- * allocated by the caller on the heap, not a JS string on the stack.
- * https://github.com/emscripten-core/emscripten/issues/6860#issuecomment-405818401
- */
- #define BorrowedHeapChar const char
- #define OwnedHeapChar char
- #define JSBorrowedChar const char
- /**
- * Signal to our FFI code generator that this function should be called
- * asynchronously when compiled with ASYNCIFY.
- */
- #define MaybeAsync(T) T
- /**
- * Signal to our FFI code generator that this function is only available in
- * ASYNCIFY builds.
- */
- #define AsyncifyOnly(T) T
- #define JSVoid void
- #define EvalFlags int
- #define EvalDetectModule int
- void qts_log(char *msg) {
- fputs(PKG, stderr);
- fputs(msg, stderr);
- fputs("\n", stderr);
- }
- void qts_dump(JSContext *ctx, JSValueConst value) {
- const char *str = JS_ToCString(ctx, value);
- if (!str) {
- QTS_DEBUG("QTS_DUMP: can't dump");
- return;
- }
- fputs(str, stderr);
- JS_FreeCString(ctx, str);
- putchar('\n');
- }
- void copy_prop_if_needed(JSContext *ctx, JSValueConst dest, JSValueConst src, const char *prop_name) {
- JSAtom prop_atom = JS_NewAtom(ctx, prop_name);
- JSValue dest_prop = JS_GetProperty(ctx, dest, prop_atom);
- if (JS_IsUndefined(dest_prop)) {
- JSValue src_prop = JS_GetProperty(ctx, src, prop_atom);
- if (!JS_IsUndefined(src_prop) && !JS_IsException(src_prop)) {
- JS_SetProperty(ctx, dest, prop_atom, src_prop);
- }
- } else {
- JS_FreeValue(ctx, dest_prop);
- }
- JS_FreeAtom(ctx, prop_atom);
- }
- JSValue *jsvalue_to_heap(JSValueConst value) {
- JSValue *result = malloc(sizeof(JSValue));
- if (result) {
- // Could be better optimized, but at -0z / -ftlo, it
- // appears to produce the same binary code as a memcpy.
- *result = value;
- }
- return result;
- }
- JSValue *QTS_Throw(JSContext *ctx, JSValueConst *error) {
- JSValue copy = JS_DupValue(ctx, *error);
- return jsvalue_to_heap(JS_Throw(ctx, copy));
- }
- JSValue *QTS_NewError(JSContext *ctx) {
- return jsvalue_to_heap(JS_NewError(ctx));
- }
- /**
- * Limits.
- */
- /**
- * Memory limit. Set to -1 to disable.
- */
- void QTS_RuntimeSetMemoryLimit(JSRuntime *rt, size_t limit) {
- JS_SetMemoryLimit(rt, limit);
- }
- /**
- * Memory diagnostics
- */
- JSValue *QTS_RuntimeComputeMemoryUsage(JSRuntime *rt, JSContext *ctx) {
- JSMemoryUsage s;
- JS_ComputeMemoryUsage(rt, &s);
- // Note that we're going to allocate more memory just to report the memory usage.
- // A more sound approach would be to bind JSMemoryUsage struct directly - but that's
- // a lot of work. This should be okay in the mean time.
- JSValue result = JS_NewObject(ctx);
- // Manually generated via editor-fu from JSMemoryUsage struct definition in quickjs.h
- JS_SetPropertyStr(ctx, result, "malloc_limit", JS_NewInt64(ctx, s.malloc_limit));
- JS_SetPropertyStr(ctx, result, "memory_used_size", JS_NewInt64(ctx, s.memory_used_size));
- JS_SetPropertyStr(ctx, result, "malloc_count", JS_NewInt64(ctx, s.malloc_count));
- JS_SetPropertyStr(ctx, result, "memory_used_count", JS_NewInt64(ctx, s.memory_used_count));
- JS_SetPropertyStr(ctx, result, "atom_count", JS_NewInt64(ctx, s.atom_count));
- JS_SetPropertyStr(ctx, result, "atom_size", JS_NewInt64(ctx, s.atom_size));
- JS_SetPropertyStr(ctx, result, "str_count", JS_NewInt64(ctx, s.str_count));
- JS_SetPropertyStr(ctx, result, "str_size", JS_NewInt64(ctx, s.str_size));
- JS_SetPropertyStr(ctx, result, "obj_count", JS_NewInt64(ctx, s.obj_count));
- JS_SetPropertyStr(ctx, result, "obj_size", JS_NewInt64(ctx, s.obj_size));
- JS_SetPropertyStr(ctx, result, "prop_count", JS_NewInt64(ctx, s.prop_count));
- JS_SetPropertyStr(ctx, result, "prop_size", JS_NewInt64(ctx, s.prop_size));
- JS_SetPropertyStr(ctx, result, "shape_count", JS_NewInt64(ctx, s.shape_count));
- JS_SetPropertyStr(ctx, result, "shape_size", JS_NewInt64(ctx, s.shape_size));
- JS_SetPropertyStr(ctx, result, "js_func_count", JS_NewInt64(ctx, s.js_func_count));
- JS_SetPropertyStr(ctx, result, "js_func_size", JS_NewInt64(ctx, s.js_func_size));
- JS_SetPropertyStr(ctx, result, "js_func_code_size", JS_NewInt64(ctx, s.js_func_code_size));
- JS_SetPropertyStr(ctx, result, "js_func_pc2line_count", JS_NewInt64(ctx, s.js_func_pc2line_count));
- JS_SetPropertyStr(ctx, result, "js_func_pc2line_size", JS_NewInt64(ctx, s.js_func_pc2line_size));
- JS_SetPropertyStr(ctx, result, "c_func_count", JS_NewInt64(ctx, s.c_func_count));
- JS_SetPropertyStr(ctx, result, "array_count", JS_NewInt64(ctx, s.array_count));
- JS_SetPropertyStr(ctx, result, "fast_array_count", JS_NewInt64(ctx, s.fast_array_count));
- JS_SetPropertyStr(ctx, result, "fast_array_elements", JS_NewInt64(ctx, s.fast_array_elements));
- JS_SetPropertyStr(ctx, result, "binary_object_count", JS_NewInt64(ctx, s.binary_object_count));
- JS_SetPropertyStr(ctx, result, "binary_object_size", JS_NewInt64(ctx, s.binary_object_size));
- return jsvalue_to_heap(result);
- }
- OwnedHeapChar *QTS_RuntimeDumpMemoryUsage(JSRuntime *rt) {
- char *result = malloc(sizeof(char) * 1024);
- FILE *memfile = fmemopen(result, 1024, "w");
- JSMemoryUsage s;
- JS_ComputeMemoryUsage(rt, &s);
- JS_DumpMemoryUsage(memfile, &s, rt);
- fclose(memfile);
- return result;
- }
- int QTS_RecoverableLeakCheck() {
- #ifdef QTS_SANITIZE_LEAK
- return __lsan_do_recoverable_leak_check();
- #else
- return 0;
- #endif
- }
- int QTS_BuildIsSanitizeLeak() {
- #ifdef QTS_SANITIZE_LEAK
- return 1;
- #else
- return 0;
- #endif
- }
- #ifdef QTS_ASYNCIFY
- EM_JS(void, set_asyncify_stack_size, (size_t size), {
- Asyncify.StackSize = size || 81920;
- });
- #endif
- /**
- * Set the stack size limit, in bytes. Set to 0 to disable.
- */
- void QTS_RuntimeSetMaxStackSize(JSRuntime *rt, size_t stack_size) {
- #ifdef QTS_ASYNCIFY
- set_asyncify_stack_size(stack_size);
- #endif
- JS_SetMaxStackSize(rt, stack_size);
- }
- /**
- * Constant pointers. Because we always use JSValue* from the host Javascript environment,
- * we need helper fuctions to return pointers to these constants.
- */
- JSValueConst QTS_Undefined = JS_UNDEFINED;
- JSValueConst *QTS_GetUndefined() {
- return &QTS_Undefined;
- }
- JSValueConst QTS_Null = JS_NULL;
- JSValueConst *QTS_GetNull() {
- return &QTS_Null;
- }
- JSValueConst QTS_False = JS_FALSE;
- JSValueConst *QTS_GetFalse() {
- return &QTS_False;
- }
- JSValueConst QTS_True = JS_TRUE;
- JSValueConst *QTS_GetTrue() {
- return &QTS_True;
- }
- /**
- * Standard FFI functions
- */
- JSRuntime *QTS_NewRuntime() {
- return JS_NewRuntime();
- }
- void QTS_FreeRuntime(JSRuntime *rt) {
- JS_FreeRuntime(rt);
- }
- JSContext *QTS_NewContext(JSRuntime *rt) {
- return JS_NewContext(rt);
- }
- void QTS_FreeContext(JSContext *ctx) {
- JS_FreeContext(ctx);
- }
- void QTS_FreeValuePointer(JSContext *ctx, JSValue *value) {
- JS_FreeValue(ctx, *value);
- free(value);
- }
- void QTS_FreeValuePointerRuntime(JSRuntime *rt, JSValue *value) {
- JS_FreeValueRT(rt, *value);
- free(value);
- }
- void QTS_FreeVoidPointer(JSContext *ctx, JSVoid *ptr) {
- js_free(ctx, ptr);
- }
- void QTS_FreeCString(JSContext *ctx, JSBorrowedChar *str) {
- JS_FreeCString(ctx, str);
- }
- JSValue *QTS_DupValuePointer(JSContext *ctx, JSValueConst *val) {
- return jsvalue_to_heap(JS_DupValue(ctx, *val));
- }
- JSValue *QTS_NewObject(JSContext *ctx) {
- return jsvalue_to_heap(JS_NewObject(ctx));
- }
- JSValue *QTS_NewObjectProto(JSContext *ctx, JSValueConst *proto) {
- return jsvalue_to_heap(JS_NewObjectProto(ctx, *proto));
- }
- JSValue *QTS_NewArray(JSContext *ctx) {
- return jsvalue_to_heap(JS_NewArray(ctx));
- }
- JSValue *QTS_NewFloat64(JSContext *ctx, double num) {
- return jsvalue_to_heap(JS_NewFloat64(ctx, num));
- }
- double QTS_GetFloat64(JSContext *ctx, JSValueConst *value) {
- double result = NAN;
- JS_ToFloat64(ctx, &result, *value);
- return result;
- }
- JSValue *QTS_NewString(JSContext *ctx, BorrowedHeapChar *string) {
- return jsvalue_to_heap(JS_NewString(ctx, string));
- }
- JSBorrowedChar *QTS_GetString(JSContext *ctx, JSValueConst *value) {
- return JS_ToCString(ctx, *value);
- }
- JSValue qts_get_symbol_key(JSContext *ctx, JSValueConst *value) {
- JSValue global = JS_GetGlobalObject(ctx);
- JSValue Symbol = JS_GetPropertyStr(ctx, global, "Symbol");
- JS_FreeValue(ctx, global);
- JSValue Symbol_keyFor = JS_GetPropertyStr(ctx, Symbol, "keyFor");
- JSValue key = JS_Call(ctx, Symbol_keyFor, Symbol, 1, value);
- JS_FreeValue(ctx, Symbol_keyFor);
- JS_FreeValue(ctx, Symbol);
- return key;
- }
- JSValue *QTS_NewSymbol(JSContext *ctx, BorrowedHeapChar *description, int isGlobal) {
- JSValue global = JS_GetGlobalObject(ctx);
- JSValue Symbol = JS_GetPropertyStr(ctx, global, "Symbol");
- JS_FreeValue(ctx, global);
- JSValue descriptionValue = JS_NewString(ctx, description);
- JSValue symbol;
- if (isGlobal != 0) {
- JSValue Symbol_for = JS_GetPropertyStr(ctx, Symbol, "for");
- symbol = JS_Call(ctx, Symbol_for, Symbol, 1, &descriptionValue);
- JS_FreeValue(ctx, descriptionValue);
- JS_FreeValue(ctx, Symbol_for);
- JS_FreeValue(ctx, Symbol);
- return jsvalue_to_heap(symbol);
- }
- symbol = JS_Call(ctx, Symbol, JS_UNDEFINED, 1, &descriptionValue);
- JS_FreeValue(ctx, descriptionValue);
- JS_FreeValue(ctx, Symbol);
- return jsvalue_to_heap(symbol);
- }
- MaybeAsync(JSBorrowedChar *) QTS_GetSymbolDescriptionOrKey(JSContext *ctx, JSValueConst *value) {
- JSBorrowedChar *result;
- JSValue key = qts_get_symbol_key(ctx, value);
- if (!JS_IsUndefined(key)) {
- result = JS_ToCString(ctx, key);
- JS_FreeValue(ctx, key);
- return result;
- }
- JSValue description = JS_GetPropertyStr(ctx, *value, "description");
- result = JS_ToCString(ctx, description);
- JS_FreeValue(ctx, description);
- return result;
- }
- int QTS_IsGlobalSymbol(JSContext *ctx, JSValueConst *value) {
- JSValue key = qts_get_symbol_key(ctx, value);
- int undefined = JS_IsUndefined(key);
- JS_FreeValue(ctx, key);
- if (undefined) {
- return 0;
- } else {
- return 1;
- }
- }
- int QTS_IsJobPending(JSRuntime *rt) {
- return JS_IsJobPending(rt);
- }
- /*
- runs pending jobs (Promises/async functions) until it encounters
- an exception or it executed the passed maxJobsToExecute jobs.
- Passing a negative value will run the loop until there are no more
- pending jobs or an exception happened
- Returns the executed number of jobs or the exception encountered
- */
- MaybeAsync(JSValue *) QTS_ExecutePendingJob(JSRuntime *rt, int maxJobsToExecute, JSContext **lastJobContext) {
- JSContext *pctx;
- int status = 1;
- int executed = 0;
- while (executed != maxJobsToExecute && status == 1) {
- status = JS_ExecutePendingJob(rt, &pctx);
- if (status == -1) {
- *lastJobContext = pctx;
- return jsvalue_to_heap(JS_GetException(pctx));
- } else if (status == 1) {
- *lastJobContext = pctx;
- executed++;
- }
- }
- #ifdef QTS_DEBUG_MODE
- char msg[500];
- sprintf(msg, "QTS_ExecutePendingJob(executed: %d, pctx: %p, lastJobExecuted: %p)", executed, pctx, *lastJobContext);
- QTS_DEBUG(msg)
- #endif
- return jsvalue_to_heap(JS_NewFloat64(pctx, executed));
- }
- MaybeAsync(JSValue *) QTS_GetProp(JSContext *ctx, JSValueConst *this_val, JSValueConst *prop_name) {
- JSAtom prop_atom = JS_ValueToAtom(ctx, *prop_name);
- JSValue prop_val = JS_GetProperty(ctx, *this_val, prop_atom);
- JS_FreeAtom(ctx, prop_atom);
- return jsvalue_to_heap(prop_val);
- }
- MaybeAsync(void) QTS_SetProp(JSContext *ctx, JSValueConst *this_val, JSValueConst *prop_name, JSValueConst *prop_value) {
- JSAtom prop_atom = JS_ValueToAtom(ctx, *prop_name);
- JSValue extra_prop_value = JS_DupValue(ctx, *prop_value);
- // TODO: should we use DefineProperty internally if this object doesn't have the property yet?
- JS_SetProperty(ctx, *this_val, prop_atom, extra_prop_value); // consumes extra_prop_value
- JS_FreeAtom(ctx, prop_atom);
- }
- void QTS_DefineProp(JSContext *ctx, JSValueConst *this_val, JSValueConst *prop_name, JSValueConst *prop_value, JSValueConst *get, JSValueConst *set, bool configurable, bool enumerable, bool has_value) {
- JSAtom prop_atom = JS_ValueToAtom(ctx, *prop_name);
- int flags = 0;
- if (configurable) {
- flags = flags | JS_PROP_CONFIGURABLE;
- if (has_value) {
- flags = flags | JS_PROP_HAS_CONFIGURABLE;
- }
- }
- if (enumerable) {
- flags = flags | JS_PROP_ENUMERABLE;
- if (has_value) {
- flags = flags | JS_PROP_HAS_ENUMERABLE;
- }
- }
- if (!JS_IsUndefined(*get)) {
- flags = flags | JS_PROP_HAS_GET;
- }
- if (!JS_IsUndefined(*set)) {
- flags = flags | JS_PROP_HAS_SET;
- }
- if (has_value) {
- flags = flags | JS_PROP_HAS_VALUE;
- }
- JS_DefineProperty(ctx, *this_val, prop_atom, *prop_value, *get, *set, flags);
- JS_FreeAtom(ctx, prop_atom);
- }
- MaybeAsync(JSValue *) QTS_Call(JSContext *ctx, JSValueConst *func_obj, JSValueConst *this_obj, int argc, JSValueConst **argv_ptrs) {
- // convert array of pointers to array of values
- JSValueConst argv[argc];
- int i;
- for (i = 0; i < argc; i++) {
- argv[i] = *(argv_ptrs[i]);
- }
- return jsvalue_to_heap(JS_Call(ctx, *func_obj, *this_obj, argc, argv));
- }
- /**
- * If maybe_exception is an exception, get the error.
- * Otherwise, return NULL.
- */
- JSValue *QTS_ResolveException(JSContext *ctx, JSValue *maybe_exception) {
- if (JS_IsException(*maybe_exception)) {
- return jsvalue_to_heap(JS_GetException(ctx));
- }
- return NULL;
- }
- MaybeAsync(JSBorrowedChar *) QTS_Dump(JSContext *ctx, JSValueConst *obj) {
- JSValue obj_json_value = JS_JSONStringify(ctx, *obj, JS_UNDEFINED, JS_UNDEFINED);
- if (!JS_IsException(obj_json_value)) {
- const char *obj_json_chars = JS_ToCString(ctx, obj_json_value);
- JS_FreeValue(ctx, obj_json_value);
- if (obj_json_chars != NULL) {
- JSValue enumerable_props = JS_ParseJSON(ctx, obj_json_chars, strlen(obj_json_chars), "<dump>");
- JS_FreeCString(ctx, obj_json_chars);
- if (!JS_IsException(enumerable_props)) {
- // Copy common non-enumerable props for different object types.
- // Errors:
- copy_prop_if_needed(ctx, enumerable_props, *obj, "name");
- copy_prop_if_needed(ctx, enumerable_props, *obj, "message");
- copy_prop_if_needed(ctx, enumerable_props, *obj, "stack");
- // Serialize again.
- JSValue enumerable_json = JS_JSONStringify(ctx, enumerable_props, JS_UNDEFINED, JS_UNDEFINED);
- JS_FreeValue(ctx, enumerable_props);
- JSBorrowedChar *result = QTS_GetString(ctx, &enumerable_json);
- JS_FreeValue(ctx, enumerable_json);
- return result;
- }
- }
- }
- #ifdef QTS_DEBUG_MODE
- qts_log("Error dumping JSON:");
- js_std_dump_error(ctx);
- #endif
- // Fallback: convert to string
- return QTS_GetString(ctx, obj);
- }
- MaybeAsync(JSValue *) QTS_Eval(JSContext *ctx, BorrowedHeapChar *js_code, const char *filename, EvalDetectModule detectModule, EvalFlags evalFlags) {
- size_t js_code_len = strlen(js_code);
- if (detectModule) {
- if (JS_DetectModule((const char *)js_code, js_code_len)) {
- QTS_DEBUG("QTS_Eval: Detected module = true");
- evalFlags |= JS_EVAL_TYPE_MODULE;
- } else {
- QTS_DEBUG("QTS_Eval: Detected module = false");
- }
- } else {
- QTS_DEBUG("QTS_Eval: do not detect module");
- }
- return jsvalue_to_heap(JS_Eval(ctx, js_code, strlen(js_code), filename, evalFlags));
- }
- OwnedHeapChar *QTS_Typeof(JSContext *ctx, JSValueConst *value) {
- const char *result = "unknown";
- uint32_t tag = JS_VALUE_GET_TAG(*value);
- if (JS_IsNumber(*value)) {
- result = "number";
- } else if (JS_IsBigInt(ctx, *value)) {
- result = "bigint";
- } else if (JS_IsBigFloat(*value)) {
- result = "bigfloat";
- } else if (JS_IsBigDecimal(*value)) {
- result = "bigdecimal";
- } else if (JS_IsFunction(ctx, *value)) {
- result = "function";
- } else if (JS_IsBool(*value)) {
- result = "boolean";
- } else if (JS_IsNull(*value)) {
- result = "object";
- } else if (JS_IsUndefined(*value)) {
- result = "undefined";
- } else if (JS_IsUninitialized(*value)) {
- result = "undefined";
- } else if (JS_IsString(*value)) {
- result = "string";
- } else if (JS_IsSymbol(*value)) {
- result = "symbol";
- } else if (JS_IsObject(*value)) {
- result = "object";
- }
- char *out = strdup(result);
- return out;
- }
- JSValue *QTS_GetGlobalObject(JSContext *ctx) {
- return jsvalue_to_heap(JS_GetGlobalObject(ctx));
- }
- JSValue *QTS_NewPromiseCapability(JSContext *ctx, JSValue **resolve_funcs_out) {
- JSValue resolve_funcs[2];
- JSValue promise = JS_NewPromiseCapability(ctx, resolve_funcs);
- resolve_funcs_out[0] = jsvalue_to_heap(resolve_funcs[0]);
- resolve_funcs_out[1] = jsvalue_to_heap(resolve_funcs[1]);
- return jsvalue_to_heap(promise);
- }
- void QTS_TestStringArg(const char *string) {
- // pass
- }
- int QTS_BuildIsDebug() {
- #ifdef QTS_DEBUG_MODE
- return 1;
- #else
- return 0;
- #endif
- }
- int QTS_BuildIsAsyncify() {
- #ifdef QTS_ASYNCIFY
- return 1;
- #else
- return 0;
- #endif
- }
- // ----------------------------------------------------------------------------
- // Module loading helpers
- // ----------------------------------------------------------------------------
- // C -> Host Callbacks
- // Note: inside EM_JS, we need to use ['...'] subscript syntax for accessing JS
- // objects, because in optimized builds, Closure compiler will mangle all the
- // names.
- // -------------------
- // function: C -> Host
- #ifdef __EMSCRIPTEN__
- EM_JS(MaybeAsync(JSValue *), qts_host_call_function, (JSContext * ctx, JSValueConst *this_ptr, int argc, JSValueConst *argv, uint32_t magic_func_id), {
- #ifdef QTS_ASYNCIFY
- const asyncify = {['handleSleep'] : Asyncify.handleSleep};
- #else
- const asyncify = undefined;
- #endif
- return Module['callbacks']['callFunction'](asyncify, ctx, this_ptr, argc, argv, magic_func_id);
- });
- #endif
- // Function: QuickJS -> C
- JSValue qts_call_function(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) {
- JSValue *result_ptr = qts_host_call_function(ctx, &this_val, argc, argv, magic);
- if (result_ptr == NULL) {
- return JS_UNDEFINED;
- }
- JSValue result = *result_ptr;
- free(result_ptr);
- return result;
- }
- // Function: Host -> QuickJS
- JSValue *QTS_NewFunction(JSContext *ctx, uint32_t func_id, const char *name) {
- #ifdef QTS_DEBUG_MODE
- char msg[500];
- sprintf(msg, "new_function(name: %s, magic: %d)", name, func_id);
- QTS_DEBUG(msg)
- #endif
- JSValue func_obj = JS_NewCFunctionMagic(
- /* context */ ctx,
- /* JSCFunctionMagic* */ &qts_call_function,
- /* name */ name,
- /* min argc */ 0,
- /* function type */ JS_CFUNC_generic_magic,
- /* magic: fn id */ func_id);
- return jsvalue_to_heap(func_obj);
- }
- JSValueConst *QTS_ArgvGetJSValueConstPointer(JSValueConst *argv, int index) {
- return &argv[index];
- }
- // --------------------
- // interrupt: C -> Host
- #ifdef __EMSCRIPTEN__
- EM_JS(int, qts_host_interrupt_handler, (JSRuntime * rt), {
- // Async not supported here.
- // #ifdef QTS_ASYNCIFY
- // const asyncify = Asyncify;
- // #else
- const asyncify = undefined;
- // #endif
- return Module['callbacks']['shouldInterrupt'](asyncify, rt);
- });
- #endif
- // interrupt: QuickJS -> C
- int qts_interrupt_handler(JSRuntime *rt, void *_unused) {
- return qts_host_interrupt_handler(rt);
- }
- // interrupt: Host -> QuickJS
- void QTS_RuntimeEnableInterruptHandler(JSRuntime *rt) {
- JS_SetInterruptHandler(rt, &qts_interrupt_handler, NULL);
- }
- void QTS_RuntimeDisableInterruptHandler(JSRuntime *rt) {
- JS_SetInterruptHandler(rt, NULL, NULL);
- }
- // --------------------
- // load module: C -> Host
- // TODO: a future version can support host returning JSModuleDef* directly;
- // for now we only support loading module source code.
- /*
- The module loading model under ASYNCIFY is convoluted. We need to make sure we
- never have an async request running concurrently for loading modules.
- The first implemenation looked like this:
- C HOST SUSPENDED
- qts_host_load_module(name) ------> false
- call rt.loadModule(name) false
- Start async load module false
- Suspend C true
- Async load complete true
- < --------------- QTS_CompileModule(source) true
- QTS_Eval(source, COMPILE_ONLY) true
- Loaded module has import true
- qts_host_load_module(dep) -------> true
- call rt.loadModule(dep) true
- Start async load module true
- ALREADY SUSPENDED, CRASH
- We can solve this in two different ways:
- 1. Return to C as soon as we async load the module source.
- That way, we unsuspend before calling QTS_CompileModule.
- 2. Once we load the module, use a new API to detect and async
- load the module's downstream dependencies. This way
- they're loaded synchronously so we don't need to suspend "again".
- Probably we could optimize (2) to make it more performant, eg with parallel
- loading, but (1) seems much easier to implement in the sort run.
- */
- JSModuleDef *qts_compile_module(JSContext *ctx, const char *module_name, BorrowedHeapChar *module_body) {
- #ifdef QTS_DEBUG_MODE
- char msg[500];
- sprintf(msg, "QTS_CompileModule(ctx: %p, name: %s, bodyLength: %lu)", ctx, module_name, strlen(module_body));
- QTS_DEBUG(msg)
- #endif
- JSValue func_val = JS_Eval(ctx, module_body, strlen(module_body), module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
- if (JS_IsException(func_val)) {
- return NULL;
- }
- // TODO: Is exception ok?
- // TODO: set import.meta?
- JSModuleDef *module = JS_VALUE_GET_PTR(func_val);
- JS_FreeValue(ctx, func_val);
- return module;
- }
- #ifdef __EMSCRIPTEN__
- EM_JS(MaybeAsync(char *), qts_host_load_module_source, (JSRuntime * rt, JSContext *ctx, const char *module_name), {
- #ifdef QTS_ASYNCIFY
- const asyncify = {['handleSleep'] : Asyncify.handleSleep};
- #else
- const asyncify = undefined;
- #endif
- // https://emscripten.org/docs/api_reference/preamble.js.html#UTF8ToString
- const moduleNameString = UTF8ToString(module_name);
- return Module['callbacks']['loadModuleSource'](asyncify, rt, ctx, moduleNameString);
- });
- EM_JS(MaybeAsync(char *), qts_host_normalize_module, (JSRuntime * rt, JSContext *ctx, const char *module_base_name, const char *module_name), {
- #ifdef QTS_ASYNCIFY
- const asyncify = {['handleSleep'] : Asyncify.handleSleep};
- #else
- const asyncify = undefined;
- #endif
- // https://emscripten.org/docs/api_reference/preamble.js.html#UTF8ToString
- const moduleBaseNameString = UTF8ToString(module_base_name);
- const moduleNameString = UTF8ToString(module_name);
- return Module['callbacks']['normalizeModule'](asyncify, rt, ctx, moduleBaseNameString, moduleNameString);
- });
- #endif
- // load module: QuickJS -> C
- // See js_module_loader in quickjs/quickjs-libc.c:567
- JSModuleDef *qts_load_module(JSContext *ctx, const char *module_name, void *_unused) {
- JSRuntime *rt = JS_GetRuntime(ctx);
- #ifdef QTS_DEBUG_MODE
- char msg[500];
- sprintf(msg, "qts_load_module(rt: %p, ctx: %p, name: %s)", rt, ctx, module_name);
- QTS_DEBUG(msg)
- #endif
- char *module_source = qts_host_load_module_source(rt, ctx, module_name);
- if (module_source == NULL) {
- return NULL;
- }
- JSModuleDef *module = qts_compile_module(ctx, module_name, module_source);
- free(module_source);
- return module;
- }
- char *qts_normalize_module(JSContext *ctx, const char *module_base_name, const char *module_name, void *_unused) {
- JSRuntime *rt = JS_GetRuntime(ctx);
- #ifdef QTS_DEBUG_MODE
- char msg[500];
- sprintf(msg, "qts_normalize_module(rt: %p, ctx: %p, base_name: %s, name: %s)", rt, ctx, module_base_name, module_name);
- QTS_DEBUG(msg)
- #endif
- char *em_module_name = qts_host_normalize_module(rt, ctx, module_base_name, module_name);
- char *js_module_name = js_strdup(ctx, em_module_name);
- free(em_module_name);
- return js_module_name;
- }
- // Load module: Host -> QuickJS
- void QTS_RuntimeEnableModuleLoader(JSRuntime *rt, int use_custom_normalize) {
- JSModuleNormalizeFunc *module_normalize = NULL; /* use default name normalizer */
- if (use_custom_normalize) {
- module_normalize = &qts_normalize_module;
- }
- JS_SetModuleLoaderFunc(rt, module_normalize, &qts_load_module, NULL);
- }
- void QTS_RuntimeDisableModuleLoader(JSRuntime *rt) {
- JS_SetModuleLoaderFunc(rt, NULL, NULL, NULL);
- }
|