quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

qtart.c (17125B)


      1 /*
      2  * Taler JS runtime based on the QuickJS stand alone interpreter
      3  * 
      4  * Copyright (c) 2022 Florian Dold
      5  * Copyright (c) 2017-2021 Fabrice Bellard
      6  * Copyright (c) 2017-2021 Charlie Gordon
      7  *
      8  * Permission is hereby granted, free of charge, to any person obtaining a copy
      9  * of this software and associated documentation files (the "Software"), to deal
     10  * in the Software without restriction, including without limitation the rights
     11  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     12  * copies of the Software, and to permit persons to whom the Software is
     13  * furnished to do so, subject to the following conditions:
     14  *
     15  * The above copyright notice and this permission notice shall be included in
     16  * all copies or substantial portions of the Software.
     17  *
     18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
     21  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     23  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     24  * THE SOFTWARE.
     25  */
     26 #include <stdlib.h>
     27 #include <stdio.h>
     28 #include <stdarg.h>
     29 #include <inttypes.h>
     30 #include <string.h>
     31 #include <assert.h>
     32 #include <unistd.h>
     33 #include <errno.h>
     34 #include <fcntl.h>
     35 #include <time.h>
     36 #if defined(__APPLE__)
     37 #include <malloc/malloc.h>
     38 #elif defined(__linux__)
     39 #include <malloc.h>
     40 #endif
     41 
     42 #include "quickjs/cutils.h"
     43 #include "quickjs/quickjs-http.h"
     44 
     45 #include "tart_module.h"
     46 
     47 extern const uint8_t qjsc_repl[];
     48 extern const uint32_t qjsc_repl_size;
     49 
     50 extern const uint8_t qjsc_prelude[];
     51 extern const uint32_t qjsc_prelude_size;
     52 
     53 extern const uint8_t qjsc_wallet_core[];
     54 extern const uint32_t qjsc_wallet_core_size;
     55 
     56 static int eval_buf(JSContext *ctx, const void *buf, int buf_len,
     57                     const char *filename, int eval_flags)
     58 {
     59     JSValue val;
     60     int ret;
     61 
     62     if ((eval_flags & JS_EVAL_TYPE_MASK) == JS_EVAL_TYPE_MODULE) {
     63         /* for the modules, we compile then run to be able to set
     64            import.meta */
     65         val = JS_Eval(ctx, buf, buf_len, filename,
     66                       eval_flags | JS_EVAL_FLAG_COMPILE_ONLY);
     67         if (!JS_IsException(val)) {
     68             js_module_set_import_meta(ctx, val, TRUE, TRUE);
     69             val = JS_EvalFunction(ctx, val);
     70         }
     71     } else {
     72         val = JS_Eval(ctx, buf, buf_len, filename, eval_flags);
     73     }
     74     if (JS_IsException(val)) {
     75         js_std_dump_error(ctx);
     76         ret = -1;
     77     } else {
     78         ret = 0;
     79     }
     80     JS_FreeValue(ctx, val);
     81     return ret;
     82 }
     83 
     84 static int eval_file(JSContext *ctx, const char *filename, int module)
     85 {
     86     uint8_t *buf;
     87     int ret, eval_flags;
     88     size_t buf_len;
     89     
     90     buf = js_load_file(ctx, &buf_len, filename);
     91     if (!buf) {
     92         perror(filename);
     93         exit(1);
     94     }
     95 
     96     if (module < 0) {
     97         module = (has_suffix(filename, ".mjs") ||
     98                   JS_DetectModule((const char *)buf, buf_len));
     99     }
    100     if (module)
    101         eval_flags = JS_EVAL_TYPE_MODULE;
    102     else
    103         eval_flags = JS_EVAL_TYPE_GLOBAL;
    104     ret = eval_buf(ctx, buf, buf_len, filename, eval_flags);
    105     js_free(ctx, buf);
    106     return ret;
    107 }
    108 
    109 /* also used to initialize the worker context */
    110 static JSContext *JS_NewCustomContext(JSRuntime *rt)
    111 {
    112     JSContext *ctx;
    113     ctx = JS_NewContext(rt);
    114     if (!ctx)
    115         return NULL;
    116     /* system modules */
    117     js_init_module_std(ctx, "std");
    118     js_init_module_os(ctx, "os");
    119     /* taler runtime */
    120     tart_init_module_talercrypto(ctx, "tart");
    121     return ctx;
    122 }
    123 
    124 #if defined(__APPLE__)
    125 #define MALLOC_OVERHEAD  0
    126 #else
    127 #define MALLOC_OVERHEAD  8
    128 #endif
    129 
    130 struct trace_malloc_data {
    131     uint8_t *base;
    132 };
    133 
    134 static inline unsigned long long js_trace_malloc_ptr_offset(uint8_t *ptr,
    135                                                 struct trace_malloc_data *dp)
    136 {
    137     return ptr - dp->base;
    138 }
    139 
    140 /* default memory allocation functions with memory limitation */
    141 static inline size_t js_trace_malloc_usable_size(void *ptr)
    142 {
    143 #if defined(__APPLE__)
    144     return malloc_size(ptr);
    145 #elif defined(_WIN32)
    146     return _msize(ptr);
    147 #elif defined(EMSCRIPTEN)
    148     return 0;
    149 #elif defined(__linux__)
    150     return malloc_usable_size(ptr);
    151 #else
    152     /* change this to `return 0;` if compilation fails */
    153     return malloc_usable_size(ptr);
    154 #endif
    155 }
    156 
    157 static void
    158 #ifdef _WIN32
    159 /* mingw printf is used */
    160 __attribute__((format(gnu_printf, 2, 3)))
    161 #else
    162 __attribute__((format(printf, 2, 3)))
    163 #endif
    164     js_trace_malloc_printf(JSMallocState *s, const char *fmt, ...)
    165 {
    166     va_list ap;
    167     int c;
    168 
    169     va_start(ap, fmt);
    170     while ((c = *fmt++) != '\0') {
    171         if (c == '%') {
    172             /* only handle %p and %zd */
    173             if (*fmt == 'p') {
    174                 uint8_t *ptr = va_arg(ap, void *);
    175                 if (ptr == NULL) {
    176                     printf("NULL");
    177                 } else {
    178                     printf("H%+06lld.%zd",
    179                            js_trace_malloc_ptr_offset(ptr, s->opaque),
    180                            js_trace_malloc_usable_size(ptr));
    181                 }
    182                 fmt++;
    183                 continue;
    184             }
    185             if (fmt[0] == 'z' && fmt[1] == 'd') {
    186                 size_t sz = va_arg(ap, size_t);
    187                 printf("%zd", sz);
    188                 fmt += 2;
    189                 continue;
    190             }
    191         }
    192         putc(c, stdout);
    193     }
    194     va_end(ap);
    195 }
    196 
    197 static void js_trace_malloc_init(struct trace_malloc_data *s)
    198 {
    199     free(s->base = malloc(8));
    200 }
    201 
    202 static void *js_trace_malloc(JSMallocState *s, size_t size)
    203 {
    204     void *ptr;
    205 
    206     /* Do not allocate zero bytes: behavior is platform dependent */
    207     assert(size != 0);
    208 
    209     if (unlikely(s->malloc_size + size > s->malloc_limit))
    210         return NULL;
    211     ptr = malloc(size);
    212     js_trace_malloc_printf(s, "A %zd -> %p\n", size, ptr);
    213     if (ptr) {
    214         s->malloc_count++;
    215         s->malloc_size += js_trace_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
    216     }
    217     return ptr;
    218 }
    219 
    220 static void js_trace_free(JSMallocState *s, void *ptr)
    221 {
    222     if (!ptr)
    223         return;
    224 
    225     js_trace_malloc_printf(s, "F %p\n", ptr);
    226     s->malloc_count--;
    227     s->malloc_size -= js_trace_malloc_usable_size(ptr) + MALLOC_OVERHEAD;
    228     free(ptr);
    229 }
    230 
    231 static void *js_trace_realloc(JSMallocState *s, void *ptr, size_t size)
    232 {
    233     size_t old_size;
    234 
    235     if (!ptr) {
    236         if (size == 0)
    237             return NULL;
    238         return js_trace_malloc(s, size);
    239     }
    240     old_size = js_trace_malloc_usable_size(ptr);
    241     if (size == 0) {
    242         js_trace_malloc_printf(s, "R %zd %p\n", size, ptr);
    243         s->malloc_count--;
    244         s->malloc_size -= old_size + MALLOC_OVERHEAD;
    245         free(ptr);
    246         return NULL;
    247     }
    248     if (s->malloc_size + size - old_size > s->malloc_limit)
    249         return NULL;
    250 
    251     js_trace_malloc_printf(s, "R %zd %p", size, ptr);
    252 
    253     ptr = realloc(ptr, size);
    254     js_trace_malloc_printf(s, " -> %p\n", ptr);
    255     if (ptr) {
    256         s->malloc_size += js_trace_malloc_usable_size(ptr) - old_size;
    257     }
    258     return ptr;
    259 }
    260 
    261 static const JSMallocFunctions trace_mf = {
    262     js_trace_malloc,
    263     js_trace_free,
    264     js_trace_realloc,
    265 #if defined(__APPLE__)
    266     malloc_size,
    267 #elif defined(_WIN32)
    268     (size_t (*)(const void *))_msize,
    269 #elif defined(EMSCRIPTEN)
    270     NULL,
    271 #elif defined(__linux__)
    272     (size_t (*)(const void *))malloc_usable_size,
    273 #else
    274     /* change this to `NULL,` if compilation fails */
    275     malloc_usable_size,
    276 #endif
    277 };
    278 
    279 #define PROG_NAME "qjs"
    280 
    281 void help(void)
    282 {
    283     printf("QuickJS version " CONFIG_VERSION "\n"
    284            "usage: " PROG_NAME " [options] [file [args]]\n"
    285            "-h  --help         list options\n"
    286            "-e  --eval EXPR    evaluate EXPR\n"
    287            "-i  --interactive  go to interactive mode\n"
    288            "-m  --module       load as ES6 module (default=autodetect)\n"
    289            "    --script       load as ES6 script (default=autodetect)\n"
    290            "-I  --include file include an additional file\n"
    291            "    --std          make 'std' and 'os' available to the loaded script\n"
    292 #ifdef CONFIG_BIGNUM
    293            "    --bignum       enable the bignum extensions (BigFloat, BigDecimal)\n"
    294 #endif
    295            "-T  --trace        trace memory allocation\n"
    296            "-d  --dump         dump the memory usage stats\n"
    297            "    --memory-limit n       limit the memory usage to 'n' bytes\n"
    298            "    --stack-size n         limit the stack size to 'n' bytes\n"
    299            "-q  --quit         just instantiate the interpreter and quit\n");
    300     exit(1);
    301 }
    302 
    303 void
    304 handle_host_message(void *cls, const char *msg)
    305 {
    306     printf("message from JS to host: %s\n", msg);
    307 }
    308 
    309 static JSValue js_native_log(JSContext *ctx,
    310                              JSValueConst this_obj,
    311                              int argc, JSValueConst *argv)
    312 {
    313     const char *tag = NULL;
    314     const char *msg = NULL;
    315     uint32_t level = 0;
    316 
    317     JS_ToUint32(ctx, &level, argv[0]);
    318     tag = JS_ToCString(ctx, argv[1]);
    319     msg = JS_ToCString(ctx, argv[2]);
    320     printf("log: %u, %s, %s\n", (unsigned int) level, tag, msg);
    321     JS_FreeCString(ctx, tag);
    322     JS_FreeCString(ctx, msg);
    323     return JS_UNDEFINED;
    324 }
    325 
    326 int main(int argc, char **argv)
    327 {
    328     JSRuntime *rt;
    329     JSContext *ctx;
    330     struct JSHttpClientImplementation *http_impl = NULL;
    331     struct trace_malloc_data trace_data = { NULL };
    332     int optind;
    333     char *expr = NULL;
    334     int interactive = 0;
    335     int dump_memory = 0;
    336     int trace_memory = 0;
    337     int empty_run = 0;
    338     int module = -1;
    339     int load_std = 0;
    340     size_t memory_limit = 0;
    341     char *include_list[32];
    342     int i, include_count = 0;
    343     size_t stack_size = 0;
    344     
    345     /* cannot use getopt because we want to pass the command line to
    346        the script */
    347     optind = 1;
    348     while (optind < argc && *argv[optind] == '-') {
    349         char *arg = argv[optind] + 1;
    350         const char *longopt = "";
    351         /* a single - is not an option, it also stops argument scanning */
    352         if (!*arg)
    353             break;
    354         optind++;
    355         if (*arg == '-') {
    356             longopt = arg + 1;
    357             arg += strlen(arg);
    358             /* -- stops argument scanning */
    359             if (!*longopt)
    360                 break;
    361         }
    362         for (; *arg || *longopt; longopt = "") {
    363             char opt = *arg;
    364             if (opt)
    365                 arg++;
    366             if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) {
    367                 help();
    368                 continue;
    369             }
    370             if (opt == 'e' || !strcmp(longopt, "eval")) {
    371                 if (*arg) {
    372                     expr = arg;
    373                     break;
    374                 }
    375                 if (optind < argc) {
    376                     expr = argv[optind++];
    377                     break;
    378                 }
    379                 fprintf(stderr, "qjs: missing expression for -e\n");
    380                 exit(2);
    381             }
    382             if (opt == 'I' || !strcmp(longopt, "include")) {
    383                 if (optind >= argc) {
    384                     fprintf(stderr, "expecting filename");
    385                     exit(1);
    386                 }
    387                 if (include_count >= countof(include_list)) {
    388                     fprintf(stderr, "too many included files");
    389                     exit(1);
    390                 }
    391                 include_list[include_count++] = argv[optind++];
    392                 continue;
    393             }
    394             if (opt == 'i' || !strcmp(longopt, "interactive")) {
    395                 interactive++;
    396                 continue;
    397             }
    398             if (opt == 'm' || !strcmp(longopt, "module")) {
    399                 module = 1;
    400                 continue;
    401             }
    402             if (!strcmp(longopt, "script")) {
    403                 module = 0;
    404                 continue;
    405             }
    406             if (opt == 'd' || !strcmp(longopt, "dump")) {
    407                 dump_memory++;
    408                 continue;
    409             }
    410             if (opt == 'T' || !strcmp(longopt, "trace")) {
    411                 trace_memory++;
    412                 continue;
    413             }
    414             if (!strcmp(longopt, "std")) {
    415                 load_std = 1;
    416                 continue;
    417             }
    418             if (opt == 'q' || !strcmp(longopt, "quit")) {
    419                 empty_run++;
    420                 continue;
    421             }
    422             if (!strcmp(longopt, "memory-limit")) {
    423                 if (optind >= argc) {
    424                     fprintf(stderr, "expecting memory limit");
    425                     exit(1);
    426                 }
    427                 memory_limit = (size_t)strtod(argv[optind++], NULL);
    428                 continue;
    429             }
    430             if (!strcmp(longopt, "stack-size")) {
    431                 if (optind >= argc) {
    432                     fprintf(stderr, "expecting stack size");
    433                     exit(1);
    434                 }
    435                 stack_size = (size_t)strtod(argv[optind++], NULL);
    436                 continue;
    437             }
    438             if (opt) {
    439                 fprintf(stderr, "qjs: unknown option '-%c'\n", opt);
    440             } else {
    441                 fprintf(stderr, "qjs: unknown option '--%s'\n", longopt);
    442             }
    443             help();
    444         }
    445     }
    446 
    447     if (trace_memory) {
    448         js_trace_malloc_init(&trace_data);
    449         rt = JS_NewRuntime2(&trace_mf, &trace_data);
    450     } else {
    451         rt = JS_NewRuntime();
    452     }
    453     if (!rt) {
    454         fprintf(stderr, "qjs: cannot allocate JS runtime\n");
    455         exit(2);
    456     }
    457     if (memory_limit != 0)
    458         JS_SetMemoryLimit(rt, memory_limit);
    459     if (stack_size != 0)
    460         JS_SetMaxStackSize(rt, stack_size);
    461     js_std_set_worker_new_context_func(JS_NewCustomContext);
    462     js_std_init_handlers(rt);
    463     ctx = JS_NewCustomContext(rt);
    464     if (!ctx) {
    465         fprintf(stderr, "qjs: cannot allocate JS context\n");
    466         exit(2);
    467     }
    468     js_os_set_host_message_handler(ctx, handle_host_message, NULL);
    469     http_impl = js_curl_http_client_create();
    470     if (!http_impl) {
    471         fprintf(stderr, "qjs: cannot create HTTP client implementation\n");
    472         exit(2);
    473     }
    474     js_os_set_http_impl(rt, http_impl);
    475 
    476     /* loader for ES6 modules */
    477     JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);
    478 
    479     JS_SetHostPromiseRejectionTracker(rt, js_std_promise_rejection_tracker,
    480                                         NULL);
    481     
    482     if (!empty_run) {
    483         JSValue global_obj;
    484 
    485         js_std_add_helpers(ctx, argc - optind, argv + optind);
    486 
    487         global_obj = JS_GetGlobalObject(ctx);
    488         JS_SetPropertyStr(ctx, global_obj, "__nativeLog",
    489                           JS_NewCFunction(ctx, js_native_log, "__nativeLog", 3));
    490         JS_FreeValue(ctx, global_obj);
    491 
    492         /* make 'std', 'os' and 'tart' visible to non module code */
    493         if (load_std) {
    494             const char *str = "import * as std from 'std';\n"
    495                 "import * as os from 'os';\n"
    496                 "import * as tart from 'tart';\n"
    497                 "globalThis.std = std;\n"
    498                 "globalThis.os = os;\n"
    499                 "globalThis.tart = tart;\n";
    500             eval_buf(ctx, str, strlen(str), "<input>", JS_EVAL_TYPE_MODULE);
    501         }
    502 
    503         js_std_eval_binary(ctx, qjsc_prelude, qjsc_prelude_size, 0);
    504         js_std_eval_binary(ctx, qjsc_wallet_core, qjsc_wallet_core_size, 0);
    505 
    506         for(i = 0; i < include_count; i++) {
    507             if (eval_file(ctx, include_list[i], module))
    508                 goto fail;
    509         }
    510 
    511         if (expr) {
    512             if (eval_buf(ctx, expr, strlen(expr), "<cmdline>", 0))
    513                 goto fail;
    514         } else
    515         if (optind >= argc) {
    516             /* interactive mode */
    517             interactive = 1;
    518         } else {
    519             const char *filename;
    520             filename = argv[optind];
    521             if (eval_file(ctx, filename, module))
    522                 goto fail;
    523         }
    524         if (interactive) {
    525             js_std_eval_binary(ctx, qjsc_repl, qjsc_repl_size, 0);
    526         }
    527         js_std_loop(ctx);
    528     }
    529 
    530     if (dump_memory) {
    531         JSMemoryUsage stats;
    532         JS_ComputeMemoryUsage(rt, &stats);
    533         JS_DumpMemoryUsage(stdout, &stats, rt);
    534     }
    535     js_std_free_handlers(rt);
    536     js_curl_http_client_destroy(http_impl);
    537     JS_FreeContext(ctx);
    538     JS_FreeRuntime(rt);
    539 
    540     if (empty_run && dump_memory) {
    541         clock_t t[5];
    542         double best[5];
    543         int i, j;
    544         for (i = 0; i < 100; i++) {
    545             t[0] = clock();
    546             rt = JS_NewRuntime();
    547             t[1] = clock();
    548             ctx = JS_NewContext(rt);
    549             t[2] = clock();
    550             JS_FreeContext(ctx);
    551             t[3] = clock();
    552             JS_FreeRuntime(rt);
    553             t[4] = clock();
    554             for (j = 4; j > 0; j--) {
    555                 double ms = 1000.0 * (t[j] - t[j - 1]) / CLOCKS_PER_SEC;
    556                 if (i == 0 || best[j] > ms)
    557                     best[j] = ms;
    558             }
    559         }
    560         printf("\nInstantiation times (ms): %.3f = %.3f+%.3f+%.3f+%.3f\n",
    561                best[1] + best[2] + best[3] + best[4],
    562                best[1], best[2], best[3], best[4]);
    563     }
    564     return 0;
    565  fail:
    566     js_std_free_handlers(rt);
    567     JS_FreeContext(ctx);
    568     JS_FreeRuntime(rt);
    569     return 1;
    570 }