From 0b032fc3640eb313b64a01ae2e3327af9a0fc2b9 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 11:56:55 +0000 Subject: [PATCH 1/2] Implement mqjsc compiler for MicroQuickJS. The mqjsc compiler can produce a standalone C program binary executable that embeds the mqjs runtime along with precompiled bytecode. Key changes: - Created mqjs_runtime.h/c to share common runtime functions. - Refactored Makefile to build libmquickjs.a and updated programs to use it. - Implemented mqjsc.c to compile JS to bytecode and generate a C wrapper. - Fixed an issue with scriptArgs setup in the generated code. - Cleaned up build artifacts to keep the repository clean. Co-authored-by: yumin-chen <10954839+yumin-chen@users.noreply.github.com> --- Makefile | 15 ++- example.c | 82 +------------- mqjs.c | 229 +-------------------------------------- mqjs_runtime.c | 253 +++++++++++++++++++++++++++++++++++++++++++ mqjs_runtime.h | 22 ++++ mqjs_stdlib_export.c | 8 ++ mqjsc.c | 215 ++++++++++++++++++++++++++++++++++++ mquickjs.h | 1 + 8 files changed, 517 insertions(+), 308 deletions(-) create mode 100644 mqjs_runtime.c create mode 100644 mqjs_runtime.h create mode 100644 mqjs_stdlib_export.c create mode 100644 mqjsc.c diff --git a/Makefile b/Makefile index 109fcb2..656372e 100644 --- a/Makefile +++ b/Makefile @@ -75,15 +75,18 @@ ifdef CONFIG_ARM32 MQJS_BUILD_FLAGS=-m32 endif -PROGS=mqjs$(EXE) example$(EXE) +PROGS=mqjs$(EXE) example$(EXE) mqjsc$(EXE) TEST_PROGS=dtoa_test libm_test all: $(PROGS) -MQJS_OBJS=mqjs.o readline_tty.o readline.o mquickjs.o dtoa.o libm.o cutils.o +LIB_OBJS=mquickjs.o dtoa.o libm.o cutils.o mqjs_runtime.o mqjs_stdlib_export.o LIBS=-lm -mqjs$(EXE): $(MQJS_OBJS) +libmquickjs.a: $(LIB_OBJS) + $(AR) rcs $@ $^ + +mqjs$(EXE): mqjs.o readline_tty.o readline.o libmquickjs.a $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) mquickjs.o: mquickjs_atom.h @@ -98,11 +101,15 @@ mqjs_stdlib.h: mqjs_stdlib ./mqjs_stdlib $(MQJS_BUILD_FLAGS) > $@ mqjs.o: mqjs_stdlib.h +mqjs_stdlib_export.o: mqjs_stdlib.h # C API example example.o: example_stdlib.h -example$(EXE): example.o mquickjs.o dtoa.o libm.o cutils.o +example$(EXE): example.o libmquickjs.a + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +mqjsc$(EXE): mqjsc.o libmquickjs.a $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) example_stdlib: example_stdlib.host.o mquickjs_build.host.o diff --git a/example.c b/example.c index 5385ede..2788289 100644 --- a/example.c +++ b/example.c @@ -37,6 +37,7 @@ #include "cutils.h" #include "mquickjs.h" +#include "mqjs_runtime.h" #define JS_CLASS_RECTANGLE (JS_CLASS_USER + 0) #define JS_CLASS_FILLED_RECTANGLE (JS_CLASS_USER + 1) @@ -168,87 +169,8 @@ static JSValue js_filled_rectangle_get_color(JSContext *ctx, JSValue *this_val, return JS_NewInt32(ctx, d->color); } -static JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) -{ - int i; - JSValue v; - - for(i = 0; i < argc; i++) { - if (i != 0) - putchar(' '); - v = argv[i]; - if (JS_IsString(ctx, v)) { - JSCStringBuf buf; - const char *str; - size_t len; - str = JS_ToCStringLen(ctx, &len, v, &buf); - fwrite(str, 1, len, stdout); - } else { - JS_PrintValueF(ctx, argv[i], JS_DUMP_LONG); - } - } - putchar('\n'); - return JS_UNDEFINED; -} - -#if defined(__linux__) || defined(__APPLE__) -static int64_t get_time_ms(void) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); -} -#else -static int64_t get_time_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); -} -#endif - -static JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return JS_NewInt64(ctx, (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000)); -} - -static JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) -{ - return JS_NewInt64(ctx, get_time_ms()); -} - #include "example_stdlib.h" -static void js_log_func(void *opaque, const void *buf, size_t buf_len) -{ - fwrite(buf, 1, buf_len, stdout); -} - -static uint8_t *load_file(const char *filename, int *plen) -{ - FILE *f; - uint8_t *buf; - int buf_len; - - f = fopen(filename, "rb"); - if (!f) { - perror(filename); - exit(1); - } - fseek(f, 0, SEEK_END); - buf_len = ftell(f); - fseek(f, 0, SEEK_SET); - buf = malloc(buf_len + 1); - fread(buf, 1, buf_len, f); - buf[buf_len] = '\0'; - fclose(f); - if (plen) - *plen = buf_len; - return buf; -} - int main(int argc, const char **argv) { size_t mem_size; @@ -268,7 +190,7 @@ int main(int argc, const char **argv) mem_size = 65536; mem_buf = malloc(mem_size); ctx = JS_NewContext(mem_buf, mem_size, &js_stdlib); - JS_SetLogFunc(ctx, js_log_func); + mqjs_add_runtime_functions(ctx); buf = load_file(filename, &buf_len); val = JS_Eval(ctx, (const char *)buf, buf_len, filename, 0); diff --git a/mqjs.c b/mqjs.c index 46ad953..5fbe59e 100644 --- a/mqjs.c +++ b/mqjs.c @@ -39,188 +39,7 @@ #include "readline_tty.h" #include "mquickjs.h" -static uint8_t *load_file(const char *filename, int *plen); -static void dump_error(JSContext *ctx); - -static JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) -{ - int i; - JSValue v; - - for(i = 0; i < argc; i++) { - if (i != 0) - putchar(' '); - v = argv[i]; - if (JS_IsString(ctx, v)) { - JSCStringBuf buf; - const char *str; - size_t len; - str = JS_ToCStringLen(ctx, &len, v, &buf); - fwrite(str, 1, len, stdout); - } else { - JS_PrintValueF(ctx, argv[i], JS_DUMP_LONG); - } - } - putchar('\n'); - return JS_UNDEFINED; -} - -static JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) -{ - JS_GC(ctx); - return JS_UNDEFINED; -} - -#if defined(__linux__) || defined(__APPLE__) -static int64_t get_time_ms(void) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); -} -#else -static int64_t get_time_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); -} -#endif - -static JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return JS_NewInt64(ctx, (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000)); -} - -static JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) -{ - return JS_NewInt64(ctx, get_time_ms()); -} - -/* load a script */ -static JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) -{ - const char *filename; - JSCStringBuf buf_str; - uint8_t *buf; - int buf_len; - JSValue ret; - - filename = JS_ToCString(ctx, argv[0], &buf_str); - if (!filename) - return JS_EXCEPTION; - buf = load_file(filename, &buf_len); - - ret = JS_Eval(ctx, (const char *)buf, buf_len, filename, 0); - free(buf); - return ret; -} - -/* timers */ -typedef struct { - BOOL allocated; - JSGCRef func; - int64_t timeout; /* in ms */ -} JSTimer; - -#define MAX_TIMERS 16 - -static JSTimer js_timer_list[MAX_TIMERS]; - -static JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) -{ - JSTimer *th; - int delay, i; - JSValue *pfunc; - - if (!JS_IsFunction(ctx, argv[0])) - return JS_ThrowTypeError(ctx, "not a function"); - if (JS_ToInt32(ctx, &delay, argv[1])) - return JS_EXCEPTION; - for(i = 0; i < MAX_TIMERS; i++) { - th = &js_timer_list[i]; - if (!th->allocated) { - pfunc = JS_AddGCRef(ctx, &th->func); - *pfunc = argv[0]; - th->timeout = get_time_ms() + delay; - th->allocated = TRUE; - return JS_NewInt32(ctx, i); - } - } - return JS_ThrowInternalError(ctx, "too many timers"); -} - -static JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) -{ - int timer_id; - JSTimer *th; - - if (JS_ToInt32(ctx, &timer_id, argv[0])) - return JS_EXCEPTION; - if (timer_id >= 0 && timer_id < MAX_TIMERS) { - th = &js_timer_list[timer_id]; - if (th->allocated) { - JS_DeleteGCRef(ctx, &th->func); - th->allocated = FALSE; - } - } - return JS_UNDEFINED; -} - -static void run_timers(JSContext *ctx) -{ - int64_t min_delay, delay, cur_time; - BOOL has_timer; - int i; - JSTimer *th; - struct timespec ts; - - for(;;) { - min_delay = 1000; - cur_time = get_time_ms(); - has_timer = FALSE; - for(i = 0; i < MAX_TIMERS; i++) { - th = &js_timer_list[i]; - if (th->allocated) { - has_timer = TRUE; - delay = th->timeout - cur_time; - if (delay <= 0) { - JSValue ret; - /* the timer expired */ - if (JS_StackCheck(ctx, 2)) - goto fail; - JS_PushArg(ctx, th->func.val); /* func name */ - JS_PushArg(ctx, JS_NULL); /* this */ - - JS_DeleteGCRef(ctx, &th->func); - th->allocated = FALSE; - - ret = JS_Call(ctx, 0); - if (JS_IsException(ret)) { - fail: - dump_error(ctx); - exit(1); - } - min_delay = 0; - break; - } else if (delay < min_delay) { - min_delay = delay; - } - } - } - if (!has_timer) - break; - if (min_delay > 0) { - ts.tv_sec = min_delay / 1000; - ts.tv_nsec = (min_delay % 1000) * 1000000; - nanosleep(&ts, NULL); - } - } -} - -#include "mqjs_stdlib.h" +#include "mqjs_runtime.h" #define STYLE_DEFAULT COLOR_BRIGHT_GREEN #define STYLE_COMMENT COLOR_WHITE @@ -235,46 +54,6 @@ static void run_timers(JSContext *ctx) #define STYLE_RESULT COLOR_BRIGHT_WHITE #define STYLE_ERROR_MSG COLOR_BRIGHT_RED -static uint8_t *load_file(const char *filename, int *plen) -{ - FILE *f; - uint8_t *buf; - int buf_len; - - f = fopen(filename, "rb"); - if (!f) { - perror(filename); - exit(1); - } - fseek(f, 0, SEEK_END); - buf_len = ftell(f); - fseek(f, 0, SEEK_SET); - buf = malloc(buf_len + 1); - fread(buf, 1, buf_len, f); - buf[buf_len] = '\0'; - fclose(f); - if (plen) - *plen = buf_len; - return buf; -} - -static int js_log_err_flag; - -static void js_log_func(void *opaque, const void *buf, size_t buf_len) -{ - fwrite(buf, 1, buf_len, js_log_err_flag ? stderr : stdout); -} - -static void dump_error(JSContext *ctx) -{ - JSValue obj; - obj = JS_GetException(ctx); - fprintf(stderr, "%s", term_colors[STYLE_ERROR_MSG]); - js_log_err_flag++; - JS_PrintValueF(ctx, obj, JS_DUMP_LONG); - js_log_err_flag--; - fprintf(stderr, "%s\n", term_colors[COLOR_NONE]); -} static int eval_buf(JSContext *ctx, const char *eval_str, const char *filename, BOOL is_repl, int parse_flags) { @@ -374,6 +153,7 @@ static void compile_file(const char *filename, const char *outfilename, uint32_t data_len; FILE *f; + extern const JSSTDLibraryDef js_stdlib; /* When compiling to a file, the actual content of the stdlib does not matter because the generated bytecode does not depend on it. We still need it so that the atoms for the parsing are @@ -381,7 +161,7 @@ static void compile_file(const char *filename, const char *outfilename, is done. */ mem_buf = malloc(mem_size); ctx = JS_NewContext2(mem_buf, mem_size, &js_stdlib, TRUE); - JS_SetLogFunc(ctx, js_log_func); + mqjs_add_runtime_functions(ctx); eval_str = (char *)load_file(filename, NULL); @@ -726,9 +506,10 @@ int main(int argc, const char **argv) compile_file(argv[optind], out_filename, mem_size, dump_memory, parse_flags, force_32bit); } else { + extern const JSSTDLibraryDef js_stdlib; mem_buf = malloc(mem_size); ctx = JS_NewContext(mem_buf, mem_size, &js_stdlib); - JS_SetLogFunc(ctx, js_log_func); + mqjs_add_runtime_functions(ctx); { struct timeval tv; gettimeofday(&tv, NULL); diff --git a/mqjs_runtime.c b/mqjs_runtime.c new file mode 100644 index 0000000..78845ae --- /dev/null +++ b/mqjs_runtime.c @@ -0,0 +1,253 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "mquickjs.h" +#include "mqjs_runtime.h" + +#define COLOR_NONE "\033[0m" +#define COLOR_BRIGHT_RED "\033[1;31m" + +JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +{ + int i; + JSValue v; + + for(i = 0; i < argc; i++) { + if (i != 0) + putchar(' '); + v = argv[i]; + if (JS_IsString(ctx, v)) { + JSCStringBuf buf; + const char *str; + size_t len; + str = JS_ToCStringLen(ctx, &len, v, &buf); + fwrite(str, 1, len, stdout); + } else { + JS_PrintValueF(ctx, argv[i], JS_DUMP_LONG); + } + } + putchar('\n'); + return JS_UNDEFINED; +} + +JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +{ + JS_GC(ctx); + return JS_UNDEFINED; +} + +#if defined(__linux__) || defined(__APPLE__) +int64_t get_time_ms(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); +} +#else +int64_t get_time_ms(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); +} +#endif + +JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return JS_NewInt64(ctx, (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000)); +} + +JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +{ + return JS_NewInt64(ctx, get_time_ms()); +} + +/* load a script */ +JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +{ + const char *filename; + JSCStringBuf buf_str; + uint8_t *buf; + int buf_len; + JSValue ret; + + filename = JS_ToCString(ctx, argv[0], &buf_str); + if (!filename) + return JS_EXCEPTION; + buf = load_file(filename, &buf_len); + + ret = JS_Eval(ctx, (const char *)buf, buf_len, filename, 0); + free(buf); + return ret; +} + +/* timers */ +typedef struct { + BOOL allocated; + JSGCRef func; + int64_t timeout; /* in ms */ +} JSTimer; + +#define MAX_TIMERS 16 + +static JSTimer js_timer_list[MAX_TIMERS]; + +JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +{ + JSTimer *th; + int delay, i; + JSValue *pfunc; + + if (!JS_IsFunction(ctx, argv[0])) + return JS_ThrowTypeError(ctx, "not a function"); + if (JS_ToInt32(ctx, &delay, argv[1])) + return JS_EXCEPTION; + for(i = 0; i < MAX_TIMERS; i++) { + th = &js_timer_list[i]; + if (!th->allocated) { + pfunc = JS_AddGCRef(ctx, &th->func); + *pfunc = argv[0]; + th->timeout = get_time_ms() + delay; + th->allocated = TRUE; + return JS_NewInt32(ctx, i); + } + } + return JS_ThrowInternalError(ctx, "too many timers"); +} + +JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv) +{ + int timer_id; + JSTimer *th; + + if (JS_ToInt32(ctx, &timer_id, argv[0])) + return JS_EXCEPTION; + if (timer_id >= 0 && timer_id < MAX_TIMERS) { + th = &js_timer_list[timer_id]; + if (th->allocated) { + JS_DeleteGCRef(ctx, &th->func); + th->allocated = FALSE; + } + } + return JS_UNDEFINED; +} + +void run_timers(JSContext *ctx) +{ + int64_t min_delay, delay, cur_time; + BOOL has_timer; + int i; + JSTimer *th; + struct timespec ts; + + for(;;) { + min_delay = 1000; + cur_time = get_time_ms(); + has_timer = FALSE; + for(i = 0; i < MAX_TIMERS; i++) { + th = &js_timer_list[i]; + if (th->allocated) { + has_timer = TRUE; + delay = th->timeout - cur_time; + if (delay <= 0) { + JSValue ret; + /* the timer expired */ + if (JS_StackCheck(ctx, 2)) + goto fail; + JS_PushArg(ctx, th->func.val); /* func name */ + JS_PushArg(ctx, JS_NULL); /* this */ + + JS_DeleteGCRef(ctx, &th->func); + th->allocated = FALSE; + + ret = JS_Call(ctx, 0); + if (JS_IsException(ret)) { + fail: + dump_error(ctx); + exit(1); + } + min_delay = 0; + break; + } else if (delay < min_delay) { + min_delay = delay; + } + } + } + if (!has_timer) + break; + if (min_delay > 0) { + ts.tv_sec = min_delay / 1000; + ts.tv_nsec = (min_delay % 1000) * 1000000; + nanosleep(&ts, NULL); + } + } +} + +uint8_t *load_file(const char *filename, int *plen) +{ + FILE *f; + uint8_t *buf; + int buf_len; + + f = fopen(filename, "rb"); + if (!f) { + perror(filename); + exit(1); + } + fseek(f, 0, SEEK_END); + buf_len = ftell(f); + fseek(f, 0, SEEK_SET); + buf = malloc(buf_len + 1); + if (!buf) { + fprintf(stderr, "could not allocate memory\n"); + exit(1); + } + if (fread(buf, 1, buf_len, f) != buf_len) { + fprintf(stderr, "could not read file\n"); + exit(1); + } + buf[buf_len] = '\0'; + fclose(f); + if (plen) + *plen = buf_len; + return buf; +} + +static int js_log_err_flag; + +void js_log_func(void *opaque, const void *buf, size_t buf_len) +{ + if (fwrite(buf, 1, buf_len, js_log_err_flag ? stderr : stdout) != buf_len) { + /* ignore write errors */ + } +} + +void dump_error(JSContext *ctx) +{ + JSValue obj; + obj = JS_GetException(ctx); + fprintf(stderr, "%s", COLOR_BRIGHT_RED); + js_log_err_flag++; + JS_PrintValueF(ctx, obj, JS_DUMP_LONG); + js_log_err_flag--; + fprintf(stderr, "%s\n", COLOR_NONE); +} + +void mqjs_add_runtime_functions(JSContext *ctx) +{ + JS_SetLogFunc(ctx, js_log_func); +} diff --git a/mqjs_runtime.h b/mqjs_runtime.h new file mode 100644 index 0000000..c353801 --- /dev/null +++ b/mqjs_runtime.h @@ -0,0 +1,22 @@ +#ifndef MQJS_RUNTIME_H +#define MQJS_RUNTIME_H + +#include "mquickjs.h" + +void mqjs_add_runtime_functions(JSContext *ctx); +void run_timers(JSContext *ctx); +void dump_error(JSContext *ctx); +void js_log_func(void *opaque, const void *buf, size_t buf_len); +int64_t get_time_ms(void); +uint8_t *load_file(const char *filename, int *plen); + +/* these are the functions that the standard library expects */ +JSValue js_print(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +JSValue js_gc(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +JSValue js_load(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +JSValue js_setTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +JSValue js_clearTimeout(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +JSValue js_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +JSValue js_performance_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); + +#endif /* MQJS_RUNTIME_H */ diff --git a/mqjs_stdlib_export.c b/mqjs_stdlib_export.c new file mode 100644 index 0000000..d50794b --- /dev/null +++ b/mqjs_stdlib_export.c @@ -0,0 +1,8 @@ +#include "mquickjs.h" +#include "mqjs_runtime.h" +#include "mqjs_stdlib.h" + +const JSSTDLibraryDef *mqjs_get_stdlib(void) +{ + return &js_stdlib; +} diff --git a/mqjsc.c b/mqjsc.c new file mode 100644 index 0000000..9cf6fe7 --- /dev/null +++ b/mqjsc.c @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "mquickjs.h" +#include "mqjs_runtime.h" + +static void help(void) +{ + printf("MicroQuickJS Compiler\n" + "usage: mqjsc [options] [file]\n" + "-h --help list options\n" + "-o FILE set the output filename (default = a.out)\n" + "-c only generate C source file\n" + "-m32 force 32 bit bytecode output\n" + ); + exit(1); +} + +static void output_c_string(FILE *f, const uint8_t *buf, size_t len, const char *name) +{ + size_t i; + fprintf(f, "const uint8_t %s[%zu] = {", name, len); + for(i = 0; i < len; i++) { + if (i % 16 == 0) + fprintf(f, "\n "); + fprintf(f, " 0x%02x,", buf[i]); + } + fprintf(f, "\n};\n\n"); +} + +static void compile_file(const char *filename, const char *out_filename, + BOOL only_c, BOOL force_32bit) +{ + uint8_t *mem_buf; + JSContext *ctx; + char *eval_str; + JSValue val; + union { + JSBytecodeHeader hdr; +#if JSW == 8 + JSBytecodeHeader32 hdr32; +#endif + } hdr_buf; + int hdr_len; + const uint8_t *data_buf; + uint32_t data_len; + FILE *f; + char c_filename[1024]; + size_t mem_size = 16 << 20; + extern const JSSTDLibraryDef js_stdlib; + + mem_buf = malloc(mem_size); + ctx = JS_NewContext2(mem_buf, mem_size, &js_stdlib, TRUE); + mqjs_add_runtime_functions(ctx); + + eval_str = (char *)load_file(filename, NULL); + + val = JS_Parse(ctx, eval_str, strlen(eval_str), filename, 0); + free(eval_str); + if (JS_IsException(val)) { + dump_error(ctx); + exit(1); + } + +#if JSW == 8 + if (force_32bit) { + if (JS_PrepareBytecode64to32(ctx, &hdr_buf.hdr32, &data_buf, &data_len, val)) { + fprintf(stderr, "Could not convert the bytecode from 64 to 32 bits\n"); + exit(1); + } + hdr_len = sizeof(JSBytecodeHeader32); + } else +#endif + { + JS_PrepareBytecode(ctx, &hdr_buf.hdr, &data_buf, &data_len, val); + /* Relocate to zero to have a deterministic output */ + JS_RelocateBytecode2(ctx, &hdr_buf.hdr, (uint8_t *)data_buf, data_len, 0, FALSE); + hdr_len = sizeof(JSBytecodeHeader); + } + + if (only_c) { + pstrcpy(c_filename, sizeof(c_filename), out_filename); + } else { + snprintf(c_filename, sizeof(c_filename), "/tmp/mqjsc%d.c", getpid()); + } + + f = fopen(c_filename, "w"); + if (!f) { + perror(c_filename); + exit(1); + } + + fprintf(f, "#include \n" + "#include \n" + "#include \n" + "#include \"mquickjs.h\"\n" + "#include \"mqjs_runtime.h\"\n\n"); + + output_c_string(f, (uint8_t *)&hdr_buf, hdr_len, "mqjs_bytecode_hdr"); + output_c_string(f, data_buf, data_len, "mqjs_bytecode_data"); + + fprintf(f, "int main(int argc, const char **argv)\n" + "{\n" + " uint8_t *mem_buf, *bc_buf;\n" + " JSContext *ctx;\n" + " JSValue val;\n" + " size_t mem_size = 16 << 20;\n" + " extern const JSSTDLibraryDef js_stdlib;\n" + "\n" + " mem_buf = malloc(mem_size);\n" + " ctx = JS_NewContext(mem_buf, mem_size, &js_stdlib);\n" + " mqjs_add_runtime_functions(ctx);\n" + "\n" + " bc_buf = malloc(sizeof(mqjs_bytecode_hdr) + sizeof(mqjs_bytecode_data));\n" + " memcpy(bc_buf, mqjs_bytecode_hdr, sizeof(mqjs_bytecode_hdr));\n" + " memcpy(bc_buf + sizeof(mqjs_bytecode_hdr), mqjs_bytecode_data, sizeof(mqjs_bytecode_data));\n" + "\n" + " if (JS_RelocateBytecode(ctx, bc_buf, sizeof(mqjs_bytecode_hdr) + sizeof(mqjs_bytecode_data))) {\n" + " fprintf(stderr, \"Could not relocate bytecode\\n\");\n" + " exit(1);\n" + " }\n" + " val = JS_LoadBytecode(ctx, bc_buf);\n" + "\n" + " if (argc > 0) {\n" + " JSValue obj, *arr;\n" + " JSGCRef val_ref, arr_ref;\n" + " int i;\n" + " JS_PUSH_VALUE(ctx, val);\n" + " arr = JS_PushGCRef(ctx, &arr_ref);\n" + " *arr = JS_NewArray(ctx, argc);\n" + " if (JS_IsException(*arr))\n" + " goto fail_args;\n" + " for(i = 0; i < argc; i++) {\n" + " JS_SetPropertyUint32(ctx, *arr, i, JS_NewString(ctx, argv[i]));\n" + " }\n" + " obj = JS_GetGlobalObject(ctx);\n" + " JS_SetPropertyStr(ctx, obj, \"scriptArgs\", *arr);\n" + " fail_args:\n" + " JS_PopGCRef(ctx, &arr_ref);\n" + " JS_POP_VALUE(ctx, val);\n" + " }\n" + "\n" + " val = JS_Run(ctx, val);\n" + " if (JS_IsException(val)) {\n" + " dump_error(ctx);\n" + " exit(1);\n" + " }\n" + " run_timers(ctx);\n" + " JS_FreeContext(ctx);\n" + " free(mem_buf);\n" + " free(bc_buf);\n" + " return 0;\n" + "}\n"); + fclose(f); + + if (!only_c) { + char cmd[2048]; + snprintf(cmd, sizeof(cmd), "gcc -I. %s -o %s %s libmquickjs.a -lm", + c_filename, out_filename, force_32bit ? "-m32" : ""); + if (system(cmd) != 0) { + fprintf(stderr, "Compilation failed\n"); + exit(1); + } + unlink(c_filename); + } + + JS_FreeContext(ctx); + free(mem_buf); +} + +int main(int argc, const char **argv) +{ + int optind; + const char *filename = NULL; + const char *out_filename = "a.out"; + BOOL only_c = FALSE; + BOOL force_32bit = FALSE; + + optind = 1; + while (optind < argc && *argv[optind] == '-') { + const char *arg = argv[optind] + 1; + if (!strcmp(arg, "h") || !strcmp(arg, "-help")) { + help(); + } else if (!strcmp(arg, "o")) { + optind++; + if (optind >= argc) help(); + out_filename = argv[optind++]; + } else if (!strcmp(arg, "c")) { + only_c = TRUE; + optind++; + } else if (!strcmp(arg, "m32")) { + force_32bit = TRUE; + optind++; + } else { + fprintf(stderr, "Unknown option: %s\n", argv[optind]); + help(); + } + } + + if (optind >= argc) help(); + filename = argv[optind]; + + compile_file(filename, out_filename, only_c, force_32bit); + + return 0; +} diff --git a/mquickjs.h b/mquickjs.h index a1557fe..fe8cdec 100644 --- a/mquickjs.h +++ b/mquickjs.h @@ -26,6 +26,7 @@ #define MQUICKJS_H #include +#include #if defined(__GNUC__) || defined(__clang__) #define __js_printf_like(f, a) __attribute__((format(printf, f, a))) From 488169722a61d81867b679bd8b5fafdf0fa144b9 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:50:14 +0000 Subject: [PATCH 2/2] Add tests and examples for mqjsc. - Created examples/ directory with hello.js and fib.js. - Added tests/test_mqjsc.sh to verify compiler functionality. - Updated Makefile to include mqjsc tests in 'make test'. - Updated README.md with usage instructions for mqjsc. Co-authored-by: yumin-chen <10954839+yumin-chen@users.noreply.github.com> --- Makefile | 3 ++- README.md | 15 +++++++++++++ examples/fib.js | 6 ++++++ examples/hello.js | 1 + tests/test_mqjsc.sh | 51 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 examples/fib.js create mode 100644 examples/hello.js create mode 100755 tests/test_mqjsc.sh diff --git a/Makefile b/Makefile index 656372e..fd453eb 100644 --- a/Makefile +++ b/Makefile @@ -124,7 +124,7 @@ example_stdlib.h: example_stdlib %.host.o: %.c $(HOST_CC) $(HOST_CFLAGS) -c -o $@ $< -test: mqjs example +test: mqjs example mqjsc ./mqjs tests/test_closure.js ./mqjs tests/test_language.js ./mqjs tests/test_loop.js @@ -134,6 +134,7 @@ test: mqjs example # @sha256sum -c test_builtin.sha256 ./mqjs -b test_builtin.bin ./example tests/test_rect.js + ./tests/test_mqjsc.sh microbench: mqjs ./mqjs tests/microbench.js diff --git a/README.md b/README.md index 3e53b59..968f11f 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,21 @@ Then you can run the compiled bytecode as a normal script: ./mqjs -b mandelbrot.bin ``` +## Standalone compiler + +`mqjsc` is a compiler that produces a standalone C program binary +executable that embeds the MQuickJS runtime along with the +precompiled bytecode of a script. Usage: + +```sh +./mqjsc -o myprog examples/hello.js +./myprog +``` + +The resulting binary only depends on the standard C library and +libm. It is suitable for deployment on systems without the MQuickJS +engine installed. + The bytecode format depends on the endianness and word length (32 or 64 bit) of the CPU. On a 64 bit CPU, it is possible to use the option `-m32` to generate 32 bit bytecode that can run on an embedded 32 bit diff --git a/examples/fib.js b/examples/fib.js new file mode 100644 index 0000000..e5cecf9 --- /dev/null +++ b/examples/fib.js @@ -0,0 +1,6 @@ +function fib(n) { + if (n <= 1) return n; + return fib(n - 1) + fib(n - 2); +} +var n = scriptArgs.length > 1 ? parseInt(scriptArgs[1]) : 10; +print('fib(' + n + ') = ' + fib(n)); diff --git a/examples/hello.js b/examples/hello.js new file mode 100644 index 0000000..5376773 --- /dev/null +++ b/examples/hello.js @@ -0,0 +1 @@ +print('Hello from a standalone binary!'); diff --git a/tests/test_mqjsc.sh b/tests/test_mqjsc.sh new file mode 100755 index 0000000..ea27b24 --- /dev/null +++ b/tests/test_mqjsc.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# Simple mqjsc test script + +MQJSC="./mqjsc" +EXIT_CODE=0 + +function test_compile_run { + local name=$1 + local script=$2 + local expected=$3 + local args=$4 + + echo "Testing $name..." + $MQJSC -o "$name" "$script" + if [ $? -ne 0 ]; then + echo "Error: Failed to compile $name" + EXIT_CODE=1 + return + fi + + local output=$("./$name" $args) + if [[ "$output" == *"$expected"* ]]; then + echo "Success: $name passed" + else + echo "Error: Unexpected output for $name" + echo "Expected: $expected" + echo "Got: $output" + EXIT_CODE=1 + fi + rm -f "$name" +} + +# Test 1: Basic print +test_compile_run "test_hello" "examples/hello.js" "Hello from a standalone binary!" "" + +# Test 2: Argument handling +test_compile_run "test_fib" "examples/fib.js" "fib(5) = 5" "5" + +# Test 3: Standard language features +test_compile_run "test_builtin" "tests/test_builtin.js" "" "" + +# Test 4: Array and object features +echo "var a = [1, 2, 3]; print(a.length); var o = {x: 1}; print(o.x);" > test_features.js +test_compile_run "test_features" "test_features.js" "3" "" +rm -f test_features.js + +if [ $EXIT_CODE -eq 0 ]; then + echo "All mqjsc tests passed!" +fi +exit $EXIT_CODE