summaryrefslogtreecommitdiff
path: root/qtart.c
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2022-12-21 19:09:36 +0100
committerFlorian Dold <florian@dold.me>2022-12-21 19:09:36 +0100
commitc9e2023e15ce70d6754e3e2aedad9e8c8093d071 (patch)
tree0c02a80d873f118671fa176075391aeb61c3b29c /qtart.c
parent67e1d6709faa82837ae8dbfce914466c9c373d18 (diff)
downloadquickjs-tart-c9e2023e15ce70d6754e3e2aedad9e8c8093d071.tar.gz
quickjs-tart-c9e2023e15ce70d6754e3e2aedad9e8c8093d071.tar.bz2
quickjs-tart-c9e2023e15ce70d6754e3e2aedad9e8c8093d071.zip
factor out taler-specific code
Diffstat (limited to 'qtart.c')
-rw-r--r--qtart.c527
1 files changed, 527 insertions, 0 deletions
diff --git a/qtart.c b/qtart.c
new file mode 100644
index 0000000..2e71767
--- /dev/null
+++ b/qtart.c
@@ -0,0 +1,527 @@
+/*
+ * Taler JS runtime based on the QuickJS stand alone interpreter
+ *
+ * Copyright (c) 2022 Florian Dold
+ * Copyright (c) 2017-2021 Fabrice Bellard
+ * Copyright (c) 2017-2021 Charlie Gordon
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * 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,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#if defined(__APPLE__)
+#include <malloc/malloc.h>
+#elif defined(__linux__)
+#include <malloc.h>
+#endif
+
+#include "tart_module.h"
+
+#include "quickjs/cutils.h"
+
+extern const uint8_t qjsc_repl[];
+extern const uint32_t qjsc_repl_size;
+
+static int eval_buf(JSContext *ctx, const void *buf, int buf_len,
+ const char *filename, int eval_flags)
+{
+ JSValue val;
+ int ret;
+
+ if ((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE) {
+ /* for the modules, we compile then run to be able to set
+ import.meta */
+ val = JS_Eval(ctx, buf, buf_len, filename,
+ eval_flags | JS_EVAL_FLAG_COMPILE_ONLY);
+ if (!JS_IsException(val)) {
+ js_module_set_import_meta(ctx, val, TRUE, TRUE);
+ val = JS_EvalFunction(ctx, val);
+ }
+ } else {
+ val = JS_Eval(ctx, buf, buf_len, filename, eval_flags);
+ }
+ if (JS_IsException(val)) {
+ js_std_dump_error(ctx);
+ ret = -1;
+ } else {
+ ret = 0;
+ }
+ JS_FreeValue(ctx, val);
+ return ret;
+}
+
+static int eval_file(JSContext *ctx, const char *filename, int module)
+{
+ uint8_t *buf;
+ int ret, eval_flags;
+ size_t buf_len;
+
+ buf = js_load_file(ctx, &buf_len, filename);
+ if (!buf) {
+ perror(filename);
+ exit(1);
+ }
+
+ if (module < 0) {
+ module = (has_suffix(filename, ".mjs") ||
+ JS_DetectModule((const char *)buf, buf_len));
+ }
+ if (module)
+ eval_flags = JS_EVAL_TYPE_MODULE;
+ else
+ eval_flags = JS_EVAL_TYPE_GLOBAL;
+ ret = eval_buf(ctx, buf, buf_len, filename, eval_flags);
+ js_free(ctx, buf);
+ return ret;
+}
+
+/* also used to initialize the worker context */
+static JSContext *JS_NewCustomContext(JSRuntime *rt)
+{
+ JSContext *ctx;
+ ctx = JS_NewContext(rt);
+ if (!ctx)
+ return NULL;
+ /* system modules */
+ js_init_module_std(ctx, "std");
+ js_init_module_os(ctx, "os");
+ /* taler runtime */
+ tart_init_module_talercrypto(ctx, "tart");
+ return ctx;
+}
+
+#if defined(__APPLE__)
+#define MALLOC_OVERHEAD 0
+#else
+#define MALLOC_OVERHEAD 8
+#endif
+
+struct trace_malloc_data {
+ uint8_t *base;
+};
+
+static inline unsigned long long js_trace_malloc_ptr_offset(uint8_t *ptr,
+ struct trace_malloc_data *dp)
+{
+ return ptr - dp->base;
+}
+
+/* default memory allocation functions with memory limitation */
+static inline size_t js_trace_malloc_usable_size(void *ptr)
+{
+#if defined(__APPLE__)
+ return malloc_size(ptr);
+#elif defined(_WIN32)
+ return _msize(ptr);
+#elif defined(EMSCRIPTEN)
+ return 0;
+#elif defined(__linux__)
+ return malloc_usable_size(ptr);
+#else
+ /* change this to `return 0;` if compilation fails */
+ return malloc_usable_size(ptr);
+#endif
+}
+
+static void
+#ifdef _WIN32
+/* mingw printf is used */
+__attribute__((format(gnu_printf, 2, 3)))
+#else
+__attribute__((format(printf, 2, 3)))
+#endif
+ js_trace_malloc_printf(JSMallocState *s, const char *fmt, ...)
+{
+ va_list ap;
+ int c;
+
+ va_start(ap, fmt);
+ while ((c = *fmt++) != '\0') {
+ if (c == '%') {
+ /* only handle %p and %zd */
+ if (*fmt == 'p') {
+ uint8_t *ptr = va_arg(ap, void *);
+ if (ptr == NULL) {
+ printf("NULL");
+ } else {
+ printf("H%+06lld.%zd",
+ js_trace_malloc_ptr_offset(ptr, s->opaque),
+ js_trace_malloc_usable_size(ptr));
+ }
+ fmt++;
+ continue;
+ }
+ if (fmt[0] == 'z' && fmt[1] == 'd') {
+ size_t sz = va_arg(ap, size_t);
+ printf("%zd", sz);
+ fmt += 2;
+ continue;
+ }
+ }
+ putc(c, stdout);
+ }
+ va_end(ap);
+}
+
+static void js_trace_malloc_init(struct trace_malloc_data *s)
+{
+ free(s->base = malloc(8));
+}
+
+static void *js_trace_malloc(JSMallocState *s, size_t size)
+{
+ void *ptr;
+
+ /* Do not allocate zero bytes: behavior is platform dependent */
+ assert(size != 0);
+
+ if (unlikely(s->malloc_size + size > s->malloc_limit))
+ return NULL;
+ ptr = malloc(size);
+ js_trace_malloc_printf(s, "A %zd -> %p\n", size, ptr);
+ if (ptr) {
+ s->malloc_count++;
+ s->malloc_size += js_trace_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
+ }
+ return ptr;
+}
+
+static void js_trace_free(JSMallocState *s, void *ptr)
+{
+ if (!ptr)
+ return;
+
+ js_trace_malloc_printf(s, "F %p\n", ptr);
+ s->malloc_count--;
+ s->malloc_size -= js_trace_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
+ free(ptr);
+}
+
+static void *js_trace_realloc(JSMallocState *s, void *ptr, size_t size)
+{
+ size_t old_size;
+
+ if (!ptr) {
+ if (size == 0)
+ return NULL;
+ return js_trace_malloc(s, size);
+ }
+ old_size = js_trace_malloc_usable_size(ptr);
+ if (size == 0) {
+ js_trace_malloc_printf(s, "R %zd %p\n", size, ptr);
+ s->malloc_count--;
+ s->malloc_size -= old_size + MALLOC_OVERHEAD;
+ free(ptr);
+ return NULL;
+ }
+ if (s->malloc_size + size - old_size > s->malloc_limit)
+ return NULL;
+
+ js_trace_malloc_printf(s, "R %zd %p", size, ptr);
+
+ ptr = realloc(ptr, size);
+ js_trace_malloc_printf(s, " -> %p\n", ptr);
+ if (ptr) {
+ s->malloc_size += js_trace_malloc_usable_size(ptr) - old_size;
+ }
+ return ptr;
+}
+
+static const JSMallocFunctions trace_mf = {
+ js_trace_malloc,
+ js_trace_free,
+ js_trace_realloc,
+#if defined(__APPLE__)
+ malloc_size,
+#elif defined(_WIN32)
+ (size_t (*)(const void *))_msize,
+#elif defined(EMSCRIPTEN)
+ NULL,
+#elif defined(__linux__)
+ (size_t (*)(const void *))malloc_usable_size,
+#else
+ /* change this to `NULL,` if compilation fails */
+ malloc_usable_size,
+#endif
+};
+
+#define PROG_NAME "qjs"
+
+void help(void)
+{
+ printf("QuickJS version " CONFIG_VERSION "\n"
+ "usage: " PROG_NAME " [options] [file [args]]\n"
+ "-h --help list options\n"
+ "-e --eval EXPR evaluate EXPR\n"
+ "-i --interactive go to interactive mode\n"
+ "-m --module load as ES6 module (default=autodetect)\n"
+ " --script load as ES6 script (default=autodetect)\n"
+ "-I --include file include an additional file\n"
+ " --std make 'std' and 'os' available to the loaded script\n"
+#ifdef CONFIG_BIGNUM
+ " --bignum enable the bignum extensions (BigFloat, BigDecimal)\n"
+#endif
+ "-T --trace trace memory allocation\n"
+ "-d --dump dump the memory usage stats\n"
+ " --memory-limit n limit the memory usage to 'n' bytes\n"
+ " --stack-size n limit the stack size to 'n' bytes\n"
+ " --unhandled-rejection dump unhandled promise rejections\n"
+ "-q --quit just instantiate the interpreter and quit\n");
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ JSRuntime *rt;
+ JSContext *ctx;
+ struct trace_malloc_data trace_data = { NULL };
+ int optind;
+ char *expr = NULL;
+ int interactive = 0;
+ int dump_memory = 0;
+ int trace_memory = 0;
+ int empty_run = 0;
+ int module = -1;
+ int load_std = 0;
+ int dump_unhandled_promise_rejection = 0;
+ size_t memory_limit = 0;
+ char *include_list[32];
+ int i, include_count = 0;
+ size_t stack_size = 0;
+
+ /* cannot use getopt because we want to pass the command line to
+ the script */
+ optind = 1;
+ while (optind < argc && *argv[optind] == '-') {
+ char *arg = argv[optind] + 1;
+ const char *longopt = "";
+ /* a single - is not an option, it also stops argument scanning */
+ if (!*arg)
+ break;
+ optind++;
+ if (*arg == '-') {
+ longopt = arg + 1;
+ arg += strlen(arg);
+ /* -- stops argument scanning */
+ if (!*longopt)
+ break;
+ }
+ for (; *arg || *longopt; longopt = "") {
+ char opt = *arg;
+ if (opt)
+ arg++;
+ if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) {
+ help();
+ continue;
+ }
+ if (opt == 'e' || !strcmp(longopt, "eval")) {
+ if (*arg) {
+ expr = arg;
+ break;
+ }
+ if (optind < argc) {
+ expr = argv[optind++];
+ break;
+ }
+ fprintf(stderr, "qjs: missing expression for -e\n");
+ exit(2);
+ }
+ if (opt == 'I' || !strcmp(longopt, "include")) {
+ if (optind >= argc) {
+ fprintf(stderr, "expecting filename");
+ exit(1);
+ }
+ if (include_count >= countof(include_list)) {
+ fprintf(stderr, "too many included files");
+ exit(1);
+ }
+ include_list[include_count++] = argv[optind++];
+ continue;
+ }
+ if (opt == 'i' || !strcmp(longopt, "interactive")) {
+ interactive++;
+ continue;
+ }
+ if (opt == 'm' || !strcmp(longopt, "module")) {
+ module = 1;
+ continue;
+ }
+ if (!strcmp(longopt, "script")) {
+ module = 0;
+ continue;
+ }
+ if (opt == 'd' || !strcmp(longopt, "dump")) {
+ dump_memory++;
+ continue;
+ }
+ if (opt == 'T' || !strcmp(longopt, "trace")) {
+ trace_memory++;
+ continue;
+ }
+ if (!strcmp(longopt, "std")) {
+ load_std = 1;
+ continue;
+ }
+ if (!strcmp(longopt, "unhandled-rejection")) {
+ dump_unhandled_promise_rejection = 1;
+ continue;
+ }
+ if (opt == 'q' || !strcmp(longopt, "quit")) {
+ empty_run++;
+ continue;
+ }
+ if (!strcmp(longopt, "memory-limit")) {
+ if (optind >= argc) {
+ fprintf(stderr, "expecting memory limit");
+ exit(1);
+ }
+ memory_limit = (size_t)strtod(argv[optind++], NULL);
+ continue;
+ }
+ if (!strcmp(longopt, "stack-size")) {
+ if (optind >= argc) {
+ fprintf(stderr, "expecting stack size");
+ exit(1);
+ }
+ stack_size = (size_t)strtod(argv[optind++], NULL);
+ continue;
+ }
+ if (opt) {
+ fprintf(stderr, "qjs: unknown option '-%c'\n", opt);
+ } else {
+ fprintf(stderr, "qjs: unknown option '--%s'\n", longopt);
+ }
+ help();
+ }
+ }
+
+ if (trace_memory) {
+ js_trace_malloc_init(&trace_data);
+ rt = JS_NewRuntime2(&trace_mf, &trace_data);
+ } else {
+ rt = JS_NewRuntime();
+ }
+ if (!rt) {
+ fprintf(stderr, "qjs: cannot allocate JS runtime\n");
+ exit(2);
+ }
+ if (memory_limit != 0)
+ JS_SetMemoryLimit(rt, memory_limit);
+ if (stack_size != 0)
+ JS_SetMaxStackSize(rt, stack_size);
+ js_std_set_worker_new_context_func(JS_NewCustomContext);
+ js_std_init_handlers(rt);
+ ctx = JS_NewCustomContext(rt);
+ if (!ctx) {
+ fprintf(stderr, "qjs: cannot allocate JS context\n");
+ exit(2);
+ }
+
+ /* loader for ES6 modules */
+ JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);
+
+ if (dump_unhandled_promise_rejection) {
+ JS_SetHostPromiseRejectionTracker(rt, js_std_promise_rejection_tracker,
+ NULL);
+ }
+
+ if (!empty_run) {
+ js_std_add_helpers(ctx, argc - optind, argv + optind);
+
+ /* make 'std' and 'os' visible to non module code */
+ if (load_std) {
+ const char *str = "import * as std from 'std';\n"
+ "import * as os from 'os';\n"
+ "globalThis.std = std;\n"
+ "globalThis.os = os;\n";
+ eval_buf(ctx, str, strlen(str), "<input>", JS_EVAL_TYPE_MODULE);
+ }
+
+ for(i = 0; i < include_count; i++) {
+ if (eval_file(ctx, include_list[i], module))
+ goto fail;
+ }
+
+ if (expr) {
+ if (eval_buf(ctx, expr, strlen(expr), "<cmdline>", 0))
+ goto fail;
+ } else
+ if (optind >= argc) {
+ /* interactive mode */
+ interactive = 1;
+ } else {
+ const char *filename;
+ filename = argv[optind];
+ if (eval_file(ctx, filename, module))
+ goto fail;
+ }
+ if (interactive) {
+ js_std_eval_binary(ctx, qjsc_repl, qjsc_repl_size, 0);
+ }
+ js_std_loop(ctx);
+ }
+
+ if (dump_memory) {
+ JSMemoryUsage stats;
+ JS_ComputeMemoryUsage(rt, &stats);
+ JS_DumpMemoryUsage(stdout, &stats, rt);
+ }
+ js_std_free_handlers(rt);
+ JS_FreeContext(ctx);
+ JS_FreeRuntime(rt);
+
+ if (empty_run && dump_memory) {
+ clock_t t[5];
+ double best[5];
+ int i, j;
+ for (i = 0; i < 100; i++) {
+ t[0] = clock();
+ rt = JS_NewRuntime();
+ t[1] = clock();
+ ctx = JS_NewContext(rt);
+ t[2] = clock();
+ JS_FreeContext(ctx);
+ t[3] = clock();
+ JS_FreeRuntime(rt);
+ t[4] = clock();
+ for (j = 4; j > 0; j--) {
+ double ms = 1000.0 * (t[j] - t[j - 1]) / CLOCKS_PER_SEC;
+ if (i == 0 || best[j] > ms)
+ best[j] = ms;
+ }
+ }
+ printf("\nInstantiation times (ms): %.3f = %.3f+%.3f+%.3f+%.3f\n",
+ best[1] + best[2] + best[3] + best[4],
+ best[1], best[2], best[3], best[4]);
+ }
+ return 0;
+ fail:
+ js_std_free_handlers(rt);
+ JS_FreeContext(ctx);
+ JS_FreeRuntime(rt);
+ return 1;
+}