#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"
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);
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)
{
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);
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;
e = namelist_find(&cmodule_list, module_name);
if
(e) {
namelist_add(&init_module_list, e->name, e->short_name, 0);
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);
m = JS_NewCModule(ctx, module_name, js_module_dummy_init);
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;
}
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);
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_std_free_handlers(rt);\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 to a C file\n"
"-e output main() and bytecode to 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;
pstrcpy(exe_dir,
sizeof
(exe_dir), exename);
p =
strrchr
(exe_dir,
'/'
);
if
(p) {
*p =
'\0'
;
}
else
{
pstrcpy(exe_dir,
sizeof
(exe_dir),
"."
);
}
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
*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));
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__)
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
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;
}
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"
);
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
for
(i = 0; i < init_module_list.count; i++) {
namelist_entry_t *e = &init_module_list.array[i];
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);
}
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;
}