summaryrefslogtreecommitdiff
path: root/qjsc.c
diff options
context:
space:
mode:
Diffstat (limited to 'qjsc.c')
-rw-r--r--qjsc.c707
1 files changed, 707 insertions, 0 deletions
diff --git a/qjsc.c b/qjsc.c
new file mode 100644
index 0000000..0706887
--- /dev/null
+++ b/qjsc.c
@@ -0,0 +1,707 @@
+/*
+ * QuickJS command line compiler
+ *
+ * Copyright (c) 2018-2020 Fabrice Bellard
+ *
+ * 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>
+#if !defined(_WIN32)
+#include <sys/wait.h>
+#endif
+
+#include "cutils.h"
+#include "quickjs-libc.h"
+
+/* enable bignums */
+#define CONFIG_BIGNUM
+
+typedef struct {
+ char *name;
+ char *short_name;
+ int flags;
+} namelist_entry_t;
+
+typedef struct namelist_t {
+ namelist_entry_t *array;
+ int count;
+ int size;
+} namelist_t;
+
+typedef struct {
+ const char *option_name;
+ const char *init_name;
+} FeatureEntry;
+
+static namelist_t cname_list;
+static namelist_t cmodule_list;
+static namelist_t init_module_list;
+static uint64_t feature_bitmap;
+static FILE *outfile;
+static BOOL byte_swap;
+static BOOL dynamic_export;
+static const char *c_ident_prefix = "qjsc_";
+
+#define FE_ALL (-1)
+
+static const FeatureEntry feature_list[] = {
+ { "date", "Date" },
+ { "eval", "Eval" },
+ { "string-normalize", "StringNormalize" },
+ { "regexp", "RegExp" },
+ { "json", "JSON" },
+ { "proxy", "Proxy" },
+ { "map", "MapSet" },
+ { "typedarray", "TypedArrays" },
+ { "promise", "Promise" },
+#define FE_MODULE_LOADER 9
+ { "module-loader", NULL },
+ { "bigint", "BigInt" },
+};
+
+void namelist_add(namelist_t *lp, const char *name, const char *short_name,
+ int flags)
+{
+ namelist_entry_t *e;
+ if (lp->count == lp->size) {
+ size_t newsize = lp->size + (lp->size >> 1) + 4;
+ namelist_entry_t *a =
+ realloc(lp->array, sizeof(lp->array[0]) * newsize);
+ /* XXX: check for realloc failure */
+ lp->array = a;
+ lp->size = newsize;
+ }
+ e = &lp->array[lp->count++];
+ e->name = strdup(name);
+ if (short_name)
+ e->short_name = strdup(short_name);
+ else
+ e->short_name = NULL;
+ e->flags = flags;
+}
+
+void namelist_free(namelist_t *lp)
+{
+ while (lp->count > 0) {
+ namelist_entry_t *e = &lp->array[--lp->count];
+ free(e->name);
+ free(e->short_name);
+ }
+ free(lp->array);
+ lp->array = NULL;
+ lp->size = 0;
+}
+
+namelist_entry_t *namelist_find(namelist_t *lp, const char *name)
+{
+ int i;
+ for(i = 0; i < lp->count; i++) {
+ namelist_entry_t *e = &lp->array[i];
+ if (!strcmp(e->name, name))
+ return e;
+ }
+ return NULL;
+}
+
+static void get_c_name(char *buf, size_t buf_size, const char *file)
+{
+ const char *p, *r;
+ size_t len, i;
+ int c;
+ char *q;
+
+ p = strrchr(file, '/');
+ if (!p)
+ p = file;
+ else
+ p++;
+ r = strrchr(p, '.');
+ if (!r)
+ len = strlen(p);
+ else
+ len = r - p;
+ pstrcpy(buf, buf_size, c_ident_prefix);
+ q = buf + strlen(buf);
+ for(i = 0; i < len; i++) {
+ c = p[i];
+ if (!((c >= '0' && c <= '9') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z'))) {
+ c = '_';
+ }
+ if ((q - buf) < buf_size - 1)
+ *q++ = c;
+ }
+ *q = '\0';
+}
+
+static void dump_hex(FILE *f, const uint8_t *buf, size_t len)
+{
+ size_t i, col;
+ col = 0;
+ for(i = 0; i < len; i++) {
+ fprintf(f, " 0x%02x,", buf[i]);
+ if (++col == 8) {
+ fprintf(f, "\n");
+ col = 0;
+ }
+ }
+ if (col != 0)
+ fprintf(f, "\n");
+}
+
+static void output_object_code(JSContext *ctx,
+ FILE *fo, JSValueConst obj, const char *c_name,
+ BOOL load_only)
+{
+ uint8_t *out_buf;
+ size_t out_buf_len;
+ int flags;
+ flags = JS_WRITE_OBJ_BYTECODE;
+ if (byte_swap)
+ flags |= JS_WRITE_OBJ_BSWAP;
+ out_buf = JS_WriteObject(ctx, &out_buf_len, obj, flags);
+ if (!out_buf) {
+ js_std_dump_error(ctx);
+ exit(1);
+ }
+
+ namelist_add(&cname_list, c_name, NULL, load_only);
+
+ fprintf(fo, "const uint32_t %s_size = %u;\n\n",
+ c_name, (unsigned int)out_buf_len);
+ fprintf(fo, "const uint8_t %s[%u] = {\n",
+ c_name, (unsigned int)out_buf_len);
+ dump_hex(fo, out_buf, out_buf_len);
+ fprintf(fo, "};\n\n");
+
+ js_free(ctx, out_buf);
+}
+
+static int js_module_dummy_init(JSContext *ctx, JSModuleDef *m)
+{
+ /* should never be called when compiling JS code */
+ abort();
+}
+
+static void find_unique_cname(char *cname, size_t cname_size)
+{
+ char cname1[1024];
+ int suffix_num;
+ size_t len, max_len;
+ assert(cname_size >= 32);
+ /* find a C name not matching an existing module C name by
+ adding a numeric suffix */
+ len = strlen(cname);
+ max_len = cname_size - 16;
+ if (len > max_len)
+ cname[max_len] = '\0';
+ suffix_num = 1;
+ for(;;) {
+ snprintf(cname1, sizeof(cname1), "%s_%d", cname, suffix_num);
+ if (!namelist_find(&cname_list, cname1))
+ break;
+ suffix_num++;
+ }
+ pstrcpy(cname, cname_size, cname1);
+}
+
+JSModuleDef *jsc_module_loader(JSContext *ctx,
+ const char *module_name, void *opaque)
+{
+ JSModuleDef *m;
+ namelist_entry_t *e;
+
+ /* check if it is a declared C or system module */
+ e = namelist_find(&cmodule_list, module_name);
+ if (e) {
+ /* add in the static init module list */
+ namelist_add(&init_module_list, e->name, e->short_name, 0);
+ /* create a dummy module */
+ m = JS_NewCModule(ctx, module_name, js_module_dummy_init);
+ } else if (has_suffix(module_name, ".so")) {
+ fprintf(stderr, "Warning: binary module '%s' will be dynamically loaded\n", module_name);
+ /* create a dummy module */
+ m = JS_NewCModule(ctx, module_name, js_module_dummy_init);
+ /* the resulting executable will export its symbols for the
+ dynamic library */
+ dynamic_export = TRUE;
+ } else {
+ size_t buf_len;
+ uint8_t *buf;
+ JSValue func_val;
+ char cname[1024];
+
+ buf = js_load_file(ctx, &buf_len, module_name);
+ if (!buf) {
+ JS_ThrowReferenceError(ctx, "could not load module filename '%s'",
+ module_name);
+ return NULL;
+ }
+
+ /* compile the module */
+ func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name,
+ JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
+ js_free(ctx, buf);
+ if (JS_IsException(func_val))
+ return NULL;
+ get_c_name(cname, sizeof(cname), module_name);
+ if (namelist_find(&cname_list, cname)) {
+ find_unique_cname(cname, sizeof(cname));
+ }
+ output_object_code(ctx, outfile, func_val, cname, TRUE);
+
+ /* the module is already referenced, so we must free it */
+ m = JS_VALUE_GET_PTR(func_val);
+ JS_FreeValue(ctx, func_val);
+ }
+ return m;
+}
+
+static void compile_file(JSContext *ctx, FILE *fo,
+ const char *filename,
+ const char *c_name1,
+ int module)
+{
+ uint8_t *buf;
+ char c_name[1024];
+ int eval_flags;
+ JSValue obj;
+ size_t buf_len;
+
+ buf = js_load_file(ctx, &buf_len, filename);
+ if (!buf) {
+ fprintf(stderr, "Could not load '%s'\n", filename);
+ exit(1);
+ }
+ eval_flags = JS_EVAL_FLAG_COMPILE_ONLY;
+ 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;
+ obj = JS_Eval(ctx, (const char *)buf, buf_len, filename, eval_flags);
+ if (JS_IsException(obj)) {
+ js_std_dump_error(ctx);
+ exit(1);
+ }
+ js_free(ctx, buf);
+ if (c_name1) {
+ pstrcpy(c_name, sizeof(c_name), c_name1);
+ } else {
+ get_c_name(c_name, sizeof(c_name), filename);
+ }
+ output_object_code(ctx, fo, obj, c_name, FALSE);
+ JS_FreeValue(ctx, obj);
+}
+
+static const char main_c_template1[] =
+ "int main(int argc, char **argv)\n"
+ "{\n"
+ " JSRuntime *rt;\n"
+ " JSContext *ctx;\n"
+ " rt = JS_NewRuntime();\n"
+ ;
+
+static const char main_c_template2[] =
+ " js_std_loop(ctx);\n"
+ " JS_FreeContext(ctx);\n"
+ " JS_FreeRuntime(rt);\n"
+ " return 0;\n"
+ "}\n";
+
+#define PROG_NAME "qjsc"
+
+void help(void)
+{
+ printf("QuickJS Compiler version " CONFIG_VERSION "\n"
+ "usage: " PROG_NAME " [options] [files]\n"
+ "\n"
+ "options are:\n"
+ "-c only output bytecode in a C file\n"
+ "-e output main() and bytecode in a C file (default = executable output)\n"
+ "-o output set the output filename\n"
+ "-N cname set the C name of the generated data\n"
+ "-m compile as Javascript module (default=autodetect)\n"
+ "-M module_name[,cname] add initialization code for an external C module\n"
+ "-x byte swapped output\n"
+ "-p prefix set the prefix of the generated C names\n"
+ );
+#ifdef CONFIG_LTO
+ {
+ int i;
+ printf("-flto use link time optimization\n");
+ printf("-fbignum enable bignum extensions\n");
+ printf("-fno-[");
+ for(i = 0; i < countof(feature_list); i++) {
+ if (i != 0)
+ printf("|");
+ printf("%s", feature_list[i].option_name);
+ }
+ printf("]\n"
+ " disable selected language features (smaller code size)\n");
+ }
+#endif
+ exit(1);
+}
+
+#if defined(CONFIG_CC) && !defined(_WIN32)
+
+int exec_cmd(char **argv)
+{
+ int pid, status, ret;
+
+ pid = fork();
+ if (pid == 0) {
+ execvp(argv[0], argv);
+ exit(1);
+ }
+
+ for(;;) {
+ ret = waitpid(pid, &status, 0);
+ if (ret == pid && WIFEXITED(status))
+ break;
+ }
+ return WEXITSTATUS(status);
+}
+
+static int output_executable(const char *out_filename, const char *cfilename,
+ BOOL use_lto, BOOL verbose, const char *exename)
+{
+ const char *argv[64];
+ const char **arg, *bn_suffix, *lto_suffix;
+ char libjsname[1024];
+ char exe_dir[1024], inc_dir[1024], lib_dir[1024], buf[1024], *p;
+ int ret;
+
+ /* get the directory of the executable */
+ pstrcpy(exe_dir, sizeof(exe_dir), exename);
+ p = strrchr(exe_dir, '/');
+ if (p) {
+ *p = '\0';
+ } else {
+ pstrcpy(exe_dir, sizeof(exe_dir), ".");
+ }
+
+ /* if 'quickjs.h' is present at the same path as the executable, we
+ use it as include and lib directory */
+ snprintf(buf, sizeof(buf), "%s/quickjs.h", exe_dir);
+ if (access(buf, R_OK) == 0) {
+ pstrcpy(inc_dir, sizeof(inc_dir), exe_dir);
+ pstrcpy(lib_dir, sizeof(lib_dir), exe_dir);
+ } else {
+ snprintf(inc_dir, sizeof(inc_dir), "%s/include/quickjs", CONFIG_PREFIX);
+ snprintf(lib_dir, sizeof(lib_dir), "%s/lib/quickjs", CONFIG_PREFIX);
+ }
+
+ lto_suffix = "";
+ bn_suffix = "";
+
+ arg = argv;
+ *arg++ = CONFIG_CC;
+ *arg++ = "-O2";
+#ifdef CONFIG_LTO
+ if (use_lto) {
+ *arg++ = "-flto";
+ lto_suffix = ".lto";
+ }
+#endif
+ /* XXX: use the executable path to find the includes files and
+ libraries */
+ *arg++ = "-D";
+ *arg++ = "_GNU_SOURCE";
+ *arg++ = "-I";
+ *arg++ = inc_dir;
+ *arg++ = "-o";
+ *arg++ = out_filename;
+ if (dynamic_export)
+ *arg++ = "-rdynamic";
+ *arg++ = cfilename;
+ snprintf(libjsname, sizeof(libjsname), "%s/libquickjs%s%s.a",
+ lib_dir, bn_suffix, lto_suffix);
+ *arg++ = libjsname;
+ *arg++ = "-lm";
+ *arg++ = "-ldl";
+ *arg = NULL;
+
+ if (verbose) {
+ for(arg = argv; *arg != NULL; arg++)
+ printf("%s ", *arg);
+ printf("\n");
+ }
+
+ ret = exec_cmd((char **)argv);
+ unlink(cfilename);
+ return ret;
+}
+#else
+static int output_executable(const char *out_filename, const char *cfilename,
+ BOOL use_lto, BOOL verbose, const char *exename)
+{
+ fprintf(stderr, "Executable output is not supported for this target\n");
+ exit(1);
+ return 0;
+}
+#endif
+
+
+typedef enum {
+ OUTPUT_C,
+ OUTPUT_C_MAIN,
+ OUTPUT_EXECUTABLE,
+} OutputTypeEnum;
+
+int main(int argc, char **argv)
+{
+ int c, i, verbose;
+ const char *out_filename, *cname;
+ char cfilename[1024];
+ FILE *fo;
+ JSRuntime *rt;
+ JSContext *ctx;
+ BOOL use_lto, bignum_ext;
+ int module;
+ OutputTypeEnum output_type;
+
+ out_filename = NULL;
+ output_type = OUTPUT_EXECUTABLE;
+ cname = NULL;
+ feature_bitmap = FE_ALL;
+ module = -1;
+ byte_swap = FALSE;
+ verbose = 0;
+ use_lto = FALSE;
+ bignum_ext = FALSE;
+
+ /* add system modules */
+ namelist_add(&cmodule_list, "std", "std", 0);
+ namelist_add(&cmodule_list, "os", "os", 0);
+
+ for(;;) {
+ c = getopt(argc, argv, "ho:cN:f:mxevM:p:");
+ if (c == -1)
+ break;
+ switch(c) {
+ case 'h':
+ help();
+ case 'o':
+ out_filename = optarg;
+ break;
+ case 'c':
+ output_type = OUTPUT_C;
+ break;
+ case 'e':
+ output_type = OUTPUT_C_MAIN;
+ break;
+ case 'N':
+ cname = optarg;
+ break;
+ case 'f':
+ {
+ const char *p;
+ p = optarg;
+ if (!strcmp(optarg, "lto")) {
+ use_lto = TRUE;
+ } else if (strstart(p, "no-", &p)) {
+ use_lto = TRUE;
+ for(i = 0; i < countof(feature_list); i++) {
+ if (!strcmp(p, feature_list[i].option_name)) {
+ feature_bitmap &= ~((uint64_t)1 << i);
+ break;
+ }
+ }
+ if (i == countof(feature_list))
+ goto bad_feature;
+ } else if (!strcmp(optarg, "bignum")) {
+ bignum_ext = TRUE;
+ } else {
+ bad_feature:
+ fprintf(stderr, "unsupported feature: %s\n", optarg);
+ exit(1);
+ }
+ }
+ break;
+ case 'm':
+ module = 1;
+ break;
+ case 'M':
+ {
+ char *p;
+ char path[1024];
+ char cname[1024];
+ pstrcpy(path, sizeof(path), optarg);
+ p = strchr(path, ',');
+ if (p) {
+ *p = '\0';
+ pstrcpy(cname, sizeof(cname), p + 1);
+ } else {
+ get_c_name(cname, sizeof(cname), path);
+ }
+ namelist_add(&cmodule_list, path, cname, 0);
+ }
+ break;
+ case 'x':
+ byte_swap = TRUE;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'p':
+ c_ident_prefix = optarg;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (optind >= argc)
+ help();
+
+ if (!out_filename) {
+ if (output_type == OUTPUT_EXECUTABLE) {
+ out_filename = "a.out";
+ } else {
+ out_filename = "out.c";
+ }
+ }
+
+ if (output_type == OUTPUT_EXECUTABLE) {
+#if defined(_WIN32) || defined(__ANDROID__)
+ /* XXX: find a /tmp directory ? */
+ snprintf(cfilename, sizeof(cfilename), "out%d.c", getpid());
+#else
+ snprintf(cfilename, sizeof(cfilename), "/tmp/out%d.c", getpid());
+#endif
+ } else {
+ pstrcpy(cfilename, sizeof(cfilename), out_filename);
+ }
+
+ fo = fopen(cfilename, "w");
+ if (!fo) {
+ perror(cfilename);
+ exit(1);
+ }
+ outfile = fo;
+
+ rt = JS_NewRuntime();
+ ctx = JS_NewContext(rt);
+#ifdef CONFIG_BIGNUM
+ if (bignum_ext) {
+ JS_AddIntrinsicBigFloat(ctx);
+ JS_AddIntrinsicBigDecimal(ctx);
+ JS_EnableBignumExt(ctx, TRUE);
+ }
+#endif
+
+ /* loader for ES6 modules */
+ JS_SetModuleLoaderFunc(rt, NULL, jsc_module_loader, NULL);
+
+ fprintf(fo, "/* File generated automatically by the QuickJS compiler. */\n"
+ "\n"
+ );
+
+ if (output_type != OUTPUT_C) {
+ fprintf(fo, "#include \"quickjs-libc.h\"\n"
+ "\n"
+ );
+ } else {
+ fprintf(fo, "#include <inttypes.h>\n"
+ "\n"
+ );
+ }
+
+ for(i = optind; i < argc; i++) {
+ const char *filename = argv[i];
+ compile_file(ctx, fo, filename, cname, module);
+ cname = NULL;
+ }
+
+ if (output_type != OUTPUT_C) {
+ fputs(main_c_template1, fo);
+ fprintf(fo, " ctx = JS_NewContextRaw(rt);\n");
+
+ /* add the module loader if necessary */
+ if (feature_bitmap & (1 << FE_MODULE_LOADER)) {
+ fprintf(fo, " JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);\n");
+ }
+
+ /* add the basic objects */
+
+ fprintf(fo, " JS_AddIntrinsicBaseObjects(ctx);\n");
+ for(i = 0; i < countof(feature_list); i++) {
+ if ((feature_bitmap & ((uint64_t)1 << i)) &&
+ feature_list[i].init_name) {
+ fprintf(fo, " JS_AddIntrinsic%s(ctx);\n",
+ feature_list[i].init_name);
+ }
+ }
+ if (bignum_ext) {
+ fprintf(fo,
+ " JS_AddIntrinsicBigFloat(ctx);\n"
+ " JS_AddIntrinsicBigDecimal(ctx);\n"
+ " JS_EnableBignumExt(ctx, 1);\n");
+ }
+
+ fprintf(fo, " js_std_add_helpers(ctx, argc, argv);\n");
+
+ for(i = 0; i < init_module_list.count; i++) {
+ namelist_entry_t *e = &init_module_list.array[i];
+ /* initialize the static C modules */
+
+ fprintf(fo,
+ " {\n"
+ " extern JSModuleDef *js_init_module_%s(JSContext *ctx, const char *name);\n"
+ " js_init_module_%s(ctx, \"%s\");\n"
+ " }\n",
+ e->short_name, e->short_name, e->name);
+ }
+
+ for(i = 0; i < cname_list.count; i++) {
+ namelist_entry_t *e = &cname_list.array[i];
+ fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, %s);\n",
+ e->name, e->name,
+ e->flags ? "1" : "0");
+ }
+ fputs(main_c_template2, fo);
+ }
+
+ JS_FreeContext(ctx);
+ JS_FreeRuntime(rt);
+
+ fclose(fo);
+
+ if (output_type == OUTPUT_EXECUTABLE) {
+ return output_executable(out_filename, cfilename, use_lto, verbose,
+ argv[0]);
+ }
+ namelist_free(&cname_list);
+ namelist_free(&cmodule_list);
+ namelist_free(&init_module_list);
+ return 0;
+}