text: /*
   * QuickJS stand alone interpreter
   * 
   * Copyright (c) 2017-2018 Fabrice Bellard
   * Copyright (c) 2017-2018 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 "cutils.h"
  #include "quickjs-libc.h"
  
  extern const uint8_t repl[];
  extern const uint32_t repl_size;
  #ifdef CONFIG_BIGNUM
  extern const uint8_t qjscalc[];
  extern const uint32_t qjscalc_size;
  #endif
  
  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;
  }
  
  #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 __attribute__((format(printf, 2, 3)))
      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
  };
  
  #ifdef CONFIG_BIGNUM
  #define PROG_NAME "qjsbn"
  #else
  #define PROG_NAME "qjs"
  #endif
  
  void help(void)
  {
      printf("QuickJS version " CONFIG_VERSION "\n"
             "usage: " PROG_NAME " [options] [file]\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"
             "    --std          make 'std' and 'os' available to the loaded script\n"
  #ifdef CONFIG_BIGNUM
             "    --qjscalc      load the QJSCalc runtime (default if invoked as qjscalc)\n"
  #endif
             "-T  --trace        trace memory allocation\n"
             "-d  --dump         dump the memory usage stats\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;
  #ifdef CONFIG_BIGNUM
      int load_jscalc;
  #endif
  
  #ifdef CONFIG_BIGNUM
      /* load jscalc runtime if invoked as 'qjscalc' */
      {
          const char *p, *exename;
          exename = argv[0];
          p = strrchr(exename, '/');
          if (p)
              exename = p + 1;
          load_jscalc = !strcmp(exename, "qjscalc");
      }
  #endif
      
      /* 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, "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;
              }
  #ifdef CONFIG_BIGNUM
              if (!strcmp(longopt, "qjscalc")) {
                  load_jscalc = 1;
                  continue;
              }
  #endif
              if (opt == 'q' || !strcmp(longopt, "quit")) {
                  empty_run++;
                  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);
      }
      ctx = JS_NewContext(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 (!empty_run) {
  #ifdef CONFIG_BIGNUM
          if (load_jscalc) {
              js_std_eval_binary(ctx, qjscalc, qjscalc_size, 0);
          }
  #endif
          js_std_add_helpers(ctx, argc - optind, argv + optind);
  
          /* system modules */
          js_init_module_std(ctx, "std");
          js_init_module_os(ctx, "os");
  
          /* 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"
                  "std.global.std = std;\n"
                  "std.global.os = os;\n";
              eval_buf(ctx, str, strlen(str), "<input>", JS_EVAL_TYPE_MODULE);
          }
  
          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, repl, 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;
  }
  