/* * 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 #include #include #include #include #include #include #if !defined(_WIN32) #include #include #else #include "win/getopt.h" #endif #include "cutils.h" #include "quickjs-libc.h" #include "quickjs-version.h" 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 }, #ifdef CONFIG_BIGNUM { "bigint", "BigInt" }, #endif }; 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" " js_std_set_worker_new_context_func(JS_NewCustomContext);\n" " js_std_init_handlers(rt);\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 " QUICKJS_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" "-D module_name compile a dynamically loaded module or worker\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" "-S n set the maximum stack size to 'n' bytes (default=%d)\n", JS_DEFAULT_STACK_SIZE); #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++ = "-lpthread"; *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; int module; OutputTypeEnum output_type; size_t stack_size; #ifdef CONFIG_BIGNUM BOOL bignum_ext = FALSE; #endif namelist_t dynamic_module_list; out_filename = NULL; output_type = OUTPUT_EXECUTABLE; cname = NULL; feature_bitmap = FE_ALL; module = -1; byte_swap = FALSE; verbose = 0; use_lto = FALSE; stack_size = 0; memset(&dynamic_module_list, 0, sizeof(dynamic_module_list)); /* 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:S:D:"); 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 #ifdef CONFIG_BIGNUM if (!strcmp(optarg, "bignum")) { bignum_ext = TRUE; } else #endif { 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 'D': namelist_add(&dynamic_module_list, optarg, NULL, 0); break; case 'x': byte_swap = TRUE; break; case 'v': verbose++; break; case 'p': c_ident_prefix = optarg; break; case 'S': stack_size = (size_t)strtod(optarg, NULL); 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_AddIntrinsicOperators(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 \n" "\n" ); } for(i = optind; i < argc; i++) { const char *filename = argv[i]; compile_file(ctx, fo, filename, cname, module); cname = NULL; } for(i = 0; i < dynamic_module_list.count; i++) { if (!jsc_module_loader(ctx, dynamic_module_list.array[i].name, NULL)) { fprintf(stderr, "Could not load dynamic module '%s'\n", dynamic_module_list.array[i].name); exit(1); } } if (output_type != OUTPUT_C) { fprintf(fo, "static JSContext *JS_NewCustomContext(JSRuntime *rt)\n" "{\n" " JSContext *ctx = JS_NewContextRaw(rt);\n" " if (!ctx)\n" " return 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); } } #ifdef CONFIG_BIGNUM if (bignum_ext) { fprintf(fo, " JS_AddIntrinsicBigFloat(ctx);\n" " JS_AddIntrinsicBigDecimal(ctx);\n" " JS_AddIntrinsicOperators(ctx);\n" " JS_EnableBignumExt(ctx, 1);\n"); } #endif /* add the precompiled modules (XXX: could modify the module loader instead) */ 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]; if (e->flags) { fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, 1);\n", e->name, e->name); } } fprintf(fo, " return ctx;\n" "}\n\n"); fputs(main_c_template1, fo); if (stack_size != 0) { fprintf(fo, " JS_SetMaxStackSize(rt, %u);\n", (unsigned int)stack_size); } /* add the module loader if necessary */ if (feature_bitmap & (1 << FE_MODULE_LOADER)) { fprintf(fo, " JS_SetModuleLoaderFunc(rt, NULL, js_module_loader, NULL);\n"); } fprintf(fo, " ctx = JS_NewCustomContext(rt);\n" " js_std_add_helpers(ctx, argc, argv);\n"); for(i = 0; i < cname_list.count; i++) { namelist_entry_t *e = &cname_list.array[i]; if (!e->flags) { fprintf(fo, " js_std_eval_binary(ctx, %s, %s_size, 0);\n", e->name, e->name); } } 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; }