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 }