diff --git a/interpreters/quickjs/Kconfig b/interpreters/quickjs/Kconfig index 6db7026e5..8969226cc 100644 --- a/interpreters/quickjs/Kconfig +++ b/interpreters/quickjs/Kconfig @@ -9,6 +9,10 @@ config INTERPRETERS_QUICKJS if INTERPRETERS_QUICKJS +config INTERPRETERS_QUICKJS_MINI + bool "Minimal interpreter" + default n + config INTERPRETERS_QUICKJS_BIGNUM bool "Bignum support" default n @@ -24,4 +28,12 @@ config INTERPRETERS_QUICKJS_STACKSIZE int "QuickJS interpreter stack size" default 8192 +config INTERPRETERS_QUICKJS_EXT_HOOK + bool "External init/destory hook" + default n + depends on INTERPRETERS_QUICKJS_MINI + ---help--- + Since minimal interpreter only support 'console.log', + you can export custom module by implement init/destory hook. + endif diff --git a/interpreters/quickjs/Makefile b/interpreters/quickjs/Makefile index 1a1f2ef0e..1d42636bc 100644 --- a/interpreters/quickjs/Makefile +++ b/interpreters/quickjs/Makefile @@ -27,9 +27,13 @@ QUICKJS_URL_BASE = https://bellard.org/quickjs/ QUICKJS_URL = $(QUICKJS_URL_BASE)/$(QUICKJS_TARBALL) CSRCS = quickjs.c libregexp.c libbf.c libunicode.c cutils.c -CSRCS += quickjs-libc.c repl.c +ifeq ($(CONFIG_INTERPRETERS_QUICKJS_MINI),y) +MAINSRC = qjsmini.c +else +CSRCS += quickjs-libc.c repl.c MAINSRC = qjs.c +endif VERSION=\"$(QUICKJS_VERSION)\" diff --git a/interpreters/quickjs/qjsmini.c b/interpreters/quickjs/qjsmini.c new file mode 100644 index 000000000..e34cd0110 --- /dev/null +++ b/interpreters/quickjs/qjsmini.c @@ -0,0 +1,676 @@ +/**************************************************************************** + * apps/interpreters/quickjs/qjsmini.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define MALLOC_OVERHEAD 8 + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +struct trace_malloc_data +{ + uint8_t *base; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: help + ****************************************************************************/ + +static void qjs_help(void) +{ + printf("QuickJS version " CONFIG_VERSION "\n" + "usage: " + "qjs" + " [options] [file [args]]\n" + "-h --help list options\n" + "-e --eval EXPR evaluate EXPR\n" + "-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); +} + +/**************************************************************************** + * Name: js_trace_malloc_ptr_offset + ****************************************************************************/ + +static inline unsigned long long +js_trace_malloc_ptr_offset(uint8_t *ptr, + struct trace_malloc_data *dp) +{ + return ptr - dp->base; +} + +/**************************************************************************** + * Name: js_trace_malloc_usable_size + ****************************************************************************/ + +static inline size_t js_trace_malloc_usable_size(void *ptr) +{ + return malloc_usable_size(ptr); +} + +/**************************************************************************** + * Name: js_trace_malloc_printf + ****************************************************************************/ + +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); +} + +/**************************************************************************** + * Name: js_trace_malloc_init + ****************************************************************************/ + +static void js_trace_malloc_init(struct trace_malloc_data *s) +{ + free(s->base = malloc(8)); +} + +/**************************************************************************** + * Name: js_trace_malloc + ****************************************************************************/ + +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; +} + +/**************************************************************************** + * Name: js_trace_free + ****************************************************************************/ + +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); +} + +/**************************************************************************** + * Name: js_trace_realloc + ****************************************************************************/ + +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; +} + +/**************************************************************************** + * Name: js_dump_obj + ****************************************************************************/ + +static void js_dump_obj(JSContext *ctx, FILE *f, JSValueConst val) +{ + const char *str; + + str = JS_ToCString(ctx, val); + if (str) + { + fprintf(f, "%s\n", str); + JS_FreeCString(ctx, str); + } + else + { + fprintf(f, "[exception]\n"); + } +} + +/**************************************************************************** + * Name: js_std_dump_error1 + ****************************************************************************/ + +static void js_std_dump_error1(JSContext *ctx, JSValueConst exception_val) +{ + JSValue val; + BOOL is_error; + + is_error = JS_IsError(ctx, exception_val); + js_dump_obj(ctx, stderr, exception_val); + if (is_error) + { + val = JS_GetPropertyStr(ctx, exception_val, "stack"); + if (!JS_IsUndefined(val)) + { + js_dump_obj(ctx, stderr, val); + } + + JS_FreeValue(ctx, val); + } +} + +/**************************************************************************** + * Name: js_std_dump_error + ****************************************************************************/ + +static void js_std_dump_error(JSContext *ctx) +{ + JSValue exception_val; + + exception_val = JS_GetException(ctx); + js_std_dump_error1(ctx, exception_val); + JS_FreeValue(ctx, exception_val); +} + +/**************************************************************************** + * Name: js_eval_buf + ****************************************************************************/ + +static int js_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) + { + val = JS_Eval(ctx, buf, buf_len, filename, + eval_flags | JS_EVAL_FLAG_COMPILE_ONLY); + if (!JS_IsException(val)) + { + 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; +} + +/**************************************************************************** + * Name: js_load_file + ****************************************************************************/ + +uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename) +{ + FILE *f; + uint8_t *buf; + size_t buf_len; + long lret; + + f = fopen(filename, "rb"); + if (!f) + return NULL; + if (fseek(f, 0, SEEK_END) < 0) + goto fail; + lret = ftell(f); + if (lret < 0) + goto fail; + + if (lret == LONG_MAX) + { + errno = EISDIR; + goto fail; + } + + buf_len = lret; + if (fseek(f, 0, SEEK_SET) < 0) + goto fail; + if (ctx) + buf = js_malloc(ctx, buf_len + 1); + else + buf = malloc(buf_len + 1); + if (!buf) + goto fail; + if (fread(buf, 1, buf_len, f) != buf_len) + { + errno = EIO; + if (ctx) + js_free(ctx, buf); + else + free(buf); + fail: + fclose(f); + return NULL; + } + + buf[buf_len] = '\0'; + fclose(f); + *pbuf_len = buf_len; + return buf; +} + +/**************************************************************************** + * Name: js_eval_file + ****************************************************************************/ + +static int js_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 = js_eval_buf(ctx, buf, buf_len, filename, eval_flags); + js_free(ctx, buf); + return ret; +} + +static JSValue js_print(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + int i; + const char *str; + size_t len; + + for (i = 0; i < argc; i++) + { + if (i != 0) + putchar(' '); + str = JS_ToCStringLen(ctx, &len, argv[i]); + if (!str) + return JS_EXCEPTION; + fwrite(str, 1, len, stdout); + JS_FreeCString(ctx, str); + } + + putchar('\n'); + return JS_UNDEFINED; +} + +void js_std_add_helpers(JSContext *ctx) +{ + JSValue global_obj, console; + global_obj = JS_GetGlobalObject(ctx); + + console = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, console, "log", + JS_NewCFunction(ctx, js_print, "log", 1)); + JS_SetPropertyStr(ctx, global_obj, "console", console); + + JS_FreeValue(ctx, global_obj); +} + +/**************************************************************************** + * Public Functions Prototypes + ****************************************************************************/ + +int js_ext_init(JSContext *ctx); +int js_ext_destroy(JSContext *ctx); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * qjs_main + ****************************************************************************/ + +int main(int argc, char *argv[]) +{ + JSRuntime *rt = NULL; + JSContext *ctx = NULL; + struct trace_malloc_data trace_data = + { + NULL + }; + + static const JSMallocFunctions trace_mf = + { + js_trace_malloc, + js_trace_free, + js_trace_realloc, + malloc_usable_size, + }; + + int optind; + char *expr = NULL; + int dump_memory = 0; + int trace_memory = 0; + int empty_run = 0; + size_t memory_limit = 0; + size_t stack_size = 0; + + optind = 1; + while (optind < argc && *argv[optind] == '-') + { + char *arg = argv[optind] + 1; + const char *longopt = ""; + + if (!*arg) + break; + optind++; + if (*arg == '-') + { + longopt = arg + 1; + arg += strlen(arg); + if (!*longopt) + break; + } + + for (; *arg || *longopt; longopt = "") + { + char opt = *arg; + if (opt) + { + arg++; + } + + if (opt == 'h' || opt == '?' || !strcmp(longopt, "help")) + { + qjs_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 == 'd' || !strcmp(longopt, "dump")) + { + dump_memory++; + continue; + } + + if (opt == 'T' || !strcmp(longopt, "trace")) + { + trace_memory++; + 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); + } + + qjs_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"); + goto fail; + } + + if (memory_limit != 0) + JS_SetMemoryLimit(rt, memory_limit); + if (stack_size != 0) + JS_SetMaxStackSize(rt, stack_size); + + ctx = JS_NewContext(rt); + + if (!ctx) + { + fprintf(stderr, "qjs: cannot allocate JS context\n"); + goto fail; + } + + js_std_add_helpers(ctx); + +#ifdef CONFIG_INTERPRETERS_QUICKJS_EXT_HOOK + if (OK != js_ext_init(ctx)) + { + fprintf(stderr, "qjs: external context init failed\n"); + goto fail_ctx; + } +#endif + + if (!empty_run) + { + if (expr) + { + if (js_eval_buf(ctx, expr, strlen(expr), "", 0)) + { + goto fail; + } + } + else if (optind < argc) + { + const char *filename; + filename = argv[optind]; + if (js_eval_file(ctx, filename, 1)) + { + goto fail; + } + } + } + + if (dump_memory) + { + JSMemoryUsage stats; + JS_ComputeMemoryUsage(rt, &stats); + JS_DumpMemoryUsage(stdout, &stats, rt); + } + +fail: + if (ctx) + { +#ifdef CONFIG_INTERPRETERS_QUICKJS_EXT_HOOK + js_ext_destroy(ctx); +#endif + JS_FreeContext(ctx); + } + + if (rt) + JS_FreeRuntime(rt); + + return 0; +}