From e094e2dc778977ae9a627198c4be466d3c1f96ac 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 16:22:44 +0000 Subject: [PATCH 1/3] Design and implement mqjsc compiler for standalone binaries - Refactor common runtime functions (js_print, timers, file loading) into mqjs_runtime.c/h. - Create mqjsc.c: a compiler that translates JS to bytecode and embeds it in a C wrapper. - Update Makefile to build a static library libmquickjs.a for shared engine and runtime code. - Update mqjs.c and example.c to use the shared mqjs_runtime. - Add test case for mqjsc in the Makefile to verify standalone binary generation. - Ensure generated binaries handle scriptArgs and clean up memory properly. Co-authored-by: yumin-chen <10954839+yumin-chen@users.noreply.github.com> --- Makefile | 24 +++- example.c | 75 +------------ mqjs.c | 202 +-------------------------------- mqjs_runtime.c | 243 +++++++++++++++++++++++++++++++++++++++ mqjs_runtime.h | 56 +++++++++ mqjsc.c | 300 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 619 insertions(+), 281 deletions(-) create mode 100644 mqjs_runtime.c create mode 100644 mqjs_runtime.h create mode 100644 mqjsc.c diff --git a/Makefile b/Makefile index 109fcb2..f4ee57a 100644 --- a/Makefile +++ b/Makefile @@ -75,17 +75,24 @@ ifdef CONFIG_ARM32 MQJS_BUILD_FLAGS=-m32 endif -PROGS=mqjs$(EXE) example$(EXE) +PROGS=mqjs$(EXE) mqjsc$(EXE) example$(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 +MQJS_OBJS=mqjs.o readline_tty.o readline.o mqjs_runtime.o mquickjs.o dtoa.o libm.o cutils.o +MQUICKJS_LIB_OBJS=mquickjs.o mqjs_runtime.o dtoa.o libm.o cutils.o LIBS=-lm -mqjs$(EXE): $(MQJS_OBJS) +mqjs$(EXE): mqjs.o readline_tty.o readline.o libmquickjs.a $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) +mqjsc$(EXE): mqjsc.o libmquickjs.a + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + +libmquickjs.a: $(MQUICKJS_LIB_OBJS) + $(AR) rcs $@ $^ + mquickjs.o: mquickjs_atom.h mqjs_stdlib: mqjs_stdlib.host.o mquickjs_build.host.o @@ -98,11 +105,12 @@ mqjs_stdlib.h: mqjs_stdlib ./mqjs_stdlib $(MQJS_BUILD_FLAGS) > $@ mqjs.o: mqjs_stdlib.h +mqjsc.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) example_stdlib: example_stdlib.host.o mquickjs_build.host.o @@ -117,7 +125,7 @@ example_stdlib.h: example_stdlib %.host.o: %.c $(HOST_CC) $(HOST_CFLAGS) -c -o $@ $< -test: mqjs example +test: mqjs mqjsc example ./mqjs tests/test_closure.js ./mqjs tests/test_language.js ./mqjs tests/test_loop.js @@ -127,6 +135,10 @@ test: mqjs example # @sha256sum -c test_builtin.sha256 ./mqjs -b test_builtin.bin ./example tests/test_rect.js +# test standalone compiler + ./mqjsc -o test_standalone tests/test_builtin.js + ./test_standalone + rm -f test_standalone microbench: mqjs ./mqjs tests/microbench.js @@ -147,6 +159,6 @@ rempio2_test: tests/rempio2_test.o libm.o $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) clean: - rm -f *.o *.d *~ tests/*.o tests/*.d tests/*~ test_builtin.bin mqjs_stdlib mqjs_stdlib.h mquickjs_build_atoms mquickjs_atom.h mqjs_example example_stdlib example_stdlib.h $(PROGS) $(TEST_PROGS) + rm -f *.o *.a *.d *~ tests/*.o tests/*.d tests/*~ test_builtin.bin mqjs_stdlib mqjs_stdlib.h mquickjs_build_atoms mquickjs_atom.h mqjs_example example_stdlib example_stdlib.h $(PROGS) $(TEST_PROGS) -include $(wildcard *.d) diff --git a/example.c b/example.c index 5385ede..338c1e7 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,57 +169,6 @@ 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) @@ -226,29 +176,6 @@ 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; diff --git a/mqjs.c b/mqjs.c index 46ad953..6ba92e6 100644 --- a/mqjs.c +++ b/mqjs.c @@ -38,188 +38,10 @@ #include "cutils.h" #include "readline_tty.h" #include "mquickjs.h" +#include "mqjs_runtime.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" #define STYLE_DEFAULT COLOR_BRIGHT_GREEN @@ -235,28 +57,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; diff --git a/mqjs_runtime.c b/mqjs_runtime.c new file mode 100644 index 0000000..0e8fc4d --- /dev/null +++ b/mqjs_runtime.c @@ -0,0 +1,243 @@ +/* + * Micro QuickJS Runtime + * + * Copyright (c) 2017-2025 Fabrice Bellard + * Copyright (c) 2017-2025 Charlie Gordon + * + * 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 +#include +#include +#include +#include +#include + +#include "cutils.h" +#include "mquickjs.h" +#include "mqjs_runtime.h" + +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) { + fclose(f); + return NULL; + } + if (fread(buf, 1, buf_len, f) != buf_len) { + free(buf); + fclose(f); + return NULL; + } + buf[buf_len] = '\0'; + fclose(f); + if (plen) + *plen = buf_len; + return buf; +} + +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__) +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 + +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); + if (!buf) + return JS_ThrowInternalError(ctx, "could not load file"); + + ret = JS_Eval(ctx, (const char *)buf, buf_len, filename, 0); + free(buf); + return ret; +} + +/* timers */ +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: + /* Error message should be dumped by the caller if needed */ + 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); + } + } +} diff --git a/mqjs_runtime.h b/mqjs_runtime.h new file mode 100644 index 0000000..6b19a61 --- /dev/null +++ b/mqjs_runtime.h @@ -0,0 +1,56 @@ +/* + * Micro QuickJS Runtime + * + * Copyright (c) 2017-2025 Fabrice Bellard + * Copyright (c) 2017-2025 Charlie Gordon + * + * 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. + */ +#ifndef MQJS_RUNTIME_H +#define MQJS_RUNTIME_H + +#include "mquickjs.h" + +/* defined in cutils.h, but we don't want to include it everywhere */ +#ifndef BOOL +typedef int BOOL; +#define FALSE 0 +#define TRUE 1 +#endif + +#define MAX_TIMERS 16 + +typedef struct { + BOOL allocated; + JSGCRef func; + int64_t timeout; /* in ms */ +} JSTimer; + +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_date_now(JSContext *ctx, JSValue *this_val, int argc, JSValue *argv); +JSValue js_performance_now(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); +void run_timers(JSContext *ctx); + +uint8_t *load_file(const char *filename, int *plen); + +#endif /* MQJS_RUNTIME_H */ diff --git a/mqjsc.c b/mqjsc.c new file mode 100644 index 0000000..3a2ef74 --- /dev/null +++ b/mqjsc.c @@ -0,0 +1,300 @@ +/* + * Micro QuickJS compiler + * + * Copyright (c) 2017-2025 Fabrice Bellard + * Copyright (c) 2017-2025 Charlie Gordon + * + * 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 +#include +#include +#include + +#include "cutils.h" +#include "mquickjs.h" +#include "mqjs_runtime.h" + +/* we include mqjs_stdlib.h so we have the atoms defined for compilation */ +#include "mqjs_stdlib.h" + +static void dump_error(JSContext *ctx) +{ + JSValue obj; + obj = JS_GetException(ctx); + JS_PrintValueF(ctx, obj, JS_DUMP_LONG); + printf("\n"); +} + +static void output_c_code(FILE *f, const uint8_t *bytecode, uint32_t bytecode_len, + size_t mem_size) +{ + uint32_t i; + + fprintf(f, "/* automatically generated by mqjsc */\n"); + fprintf(f, "#include \n"); + fprintf(f, "#include \n"); + fprintf(f, "#include \n"); + fprintf(f, "#include \"mquickjs.h\"\n"); + fprintf(f, "#include \"mqjs_runtime.h\"\n"); + fprintf(f, "#include \"mqjs_stdlib.h\"\n\n"); + + fprintf(f, "static const uint8_t bytecode[%u] = {", bytecode_len); + for (i = 0; i < bytecode_len; i++) { + if (i % 16 == 0) + fprintf(f, "\n "); + fprintf(f, "0x%02x,", bytecode[i]); + } + fprintf(f, "\n};\n\n"); + + fprintf(f, "int main(int argc, const char **argv)\n{\n"); + fprintf(f, " uint8_t *mem_buf;\n"); + fprintf(f, " JSContext *ctx;\n"); + fprintf(f, " JSValue val;\n"); + fprintf(f, " size_t mem_size = %zu;\n", mem_size); + fprintf(f, " mem_buf = malloc(mem_size);\n"); + fprintf(f, " if (!mem_buf) { fprintf(stderr, \"Could not allocate memory\\n\"); return 1; }\n"); + fprintf(f, " ctx = JS_NewContext(mem_buf, mem_size, &js_stdlib);\n"); + fprintf(f, " if (!ctx) { fprintf(stderr, \"Could not create JS context\\n\"); return 1; }\n"); + + fprintf(f, " if (argc > 1) {\n"); + fprintf(f, " JSValue obj;\n"); + fprintf(f, " JSGCRef arr_ref;\n"); + fprintf(f, " int i;\n"); + fprintf(f, " JS_AddGCRef(ctx, &arr_ref);\n"); + fprintf(f, " arr_ref.val = JS_NewArray(ctx, argc - 1);\n"); + fprintf(f, " for(i = 1; i < argc; i++) {\n"); + fprintf(f, " JSValue str = JS_NewString(ctx, argv[i]);\n"); + fprintf(f, " JS_SetPropertyUint32(ctx, arr_ref.val, i - 1, str);\n"); + fprintf(f, " }\n"); + fprintf(f, " obj = JS_GetGlobalObject(ctx);\n"); + fprintf(f, " JS_SetPropertyStr(ctx, obj, \"scriptArgs\", arr_ref.val);\n"); + fprintf(f, " JS_DeleteGCRef(ctx, &arr_ref);\n"); + fprintf(f, " }\n"); + + fprintf(f, " uint8_t *bytecode_copy = malloc(%u);\n", bytecode_len); + fprintf(f, " if (!bytecode_copy) { fprintf(stderr, \"Could not allocate bytecode copy\\n\"); return 1; }\n"); + fprintf(f, " memcpy(bytecode_copy, bytecode, %u);\n", bytecode_len); + fprintf(f, " if (JS_RelocateBytecode(ctx, bytecode_copy, %u)) {\n", bytecode_len); + fprintf(f, " fprintf(stderr, \"Could not relocate bytecode\\n\");\n"); + fprintf(f, " exit(1);\n"); + fprintf(f, " }\n"); + fprintf(f, " val = JS_LoadBytecode(ctx, bytecode_copy);\n"); + fprintf(f, " val = JS_Run(ctx, val);\n"); + fprintf(f, " if (JS_IsException(val)) {\n"); + fprintf(f, " JSValue obj = JS_GetException(ctx);\n"); + fprintf(f, " JS_PrintValueF(ctx, obj, JS_DUMP_LONG);\n"); + fprintf(f, " fprintf(stderr, \"\\n\");\n"); + fprintf(f, " exit(1);\n"); + fprintf(f, " }\n"); + fprintf(f, " run_timers(ctx);\n"); + fprintf(f, " JS_FreeContext(ctx);\n"); + fprintf(f, " free(bytecode_copy);\n"); + fprintf(f, " free(mem_buf);\n"); + fprintf(f, " return 0;\n"); + fprintf(f, "}\n"); +} + +static void help(void) +{ + printf("MicroQuickJS compiler\n" + "usage: mqjsc [options] [file]\n" + "-h list options\n" + "-o FILE set the output filename\n" + "-c only output the C source file\n" + "-m32 force 32 bit bytecode output\n" + "--memory-limit n limit the memory usage to 'n' bytes\n"); + exit(1); +} + +int main(int argc, char **argv) +{ + int optind; + const char *out_filename = "out.exe"; + const char *in_filename; + BOOL output_c_only = FALSE; + BOOL force_32bit = FALSE; + size_t mem_size = 16 << 20; + uint8_t *mem_buf; + JSContext *ctx; + uint8_t *js_buf; + int js_buf_len; + 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]; + + optind = 1; + while (optind < argc && *argv[optind] == '-') { + const 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' || !strcmp(longopt, "help")) { + help(); + } else if (opt == 'o') { + if (*arg) { + out_filename = arg; + break; + } + if (optind < argc) { + out_filename = argv[optind++]; + break; + } + } else if (opt == 'c') { + output_c_only = TRUE; + } else if (opt == 'm' && !strcmp(arg, "32")) { + force_32bit = TRUE; + arg += strlen(arg); + } else if (!strcmp(longopt, "memory-limit")) { + if (optind < argc) { + char *p; + double count = strtod(argv[optind++], &p); + switch (tolower((unsigned char)*p)) { + case 'g': count *= 1024; + case 'm': count *= 1024; + case 'k': count *= 1024; + default: mem_size = (size_t)count; break; + } + } + } else { + fprintf(stderr, "unknown option\n"); + help(); + } + } + } + + if (optind >= argc) + help(); + + in_filename = argv[optind]; + + mem_buf = malloc(mem_size); + ctx = JS_NewContext2(mem_buf, mem_size, &js_stdlib, TRUE); + + js_buf = load_file(in_filename, &js_buf_len); + val = JS_Parse(ctx, (char *)js_buf, js_buf_len, in_filename, 0); + free(js_buf); + + 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 bytecode to 32 bits\n"); + exit(1); + } + hdr_len = sizeof(JSBytecodeHeader32); + } else +#endif + { + JS_PrepareBytecode(ctx, &hdr_buf.hdr, &data_buf, &data_len, val); + JS_RelocateBytecode2(ctx, &hdr_buf.hdr, (uint8_t *)data_buf, data_len, 0, FALSE); + hdr_len = sizeof(JSBytecodeHeader); + } + + if (output_c_only) { + f = fopen(out_filename, "w"); + } else { + snprintf(c_filename, sizeof(c_filename), "%s.c", out_filename); + f = fopen(c_filename, "w"); + } + if (!f) { + perror("fopen"); + exit(1); + } + + uint8_t *full_bc = malloc(hdr_len + data_len); + memcpy(full_bc, &hdr_buf, hdr_len); + memcpy(full_bc + hdr_len, data_buf, data_len); + + output_c_code(f, full_bc, hdr_len + data_len, mem_size); + fclose(f); + free(full_bc); + + if (!output_c_only) { + pid_t pid; + int status; + const char *argv_cc[16]; + int i; + + i = 0; + argv_cc[i++] = "gcc"; + argv_cc[i++] = "-Os"; + argv_cc[i++] = "-Wall"; + argv_cc[i++] = "-o"; + argv_cc[i++] = out_filename; + argv_cc[i++] = c_filename; + argv_cc[i++] = "libmquickjs.a"; + argv_cc[i++] = "-lm"; + if (force_32bit) + argv_cc[i++] = "-m32"; + argv_cc[i++] = NULL; + + pid = fork(); + if (pid == 0) { + execvp(argv_cc[0], (char **)argv_cc); + perror("execvp"); + exit(1); + } else if (pid < 0) { + perror("fork"); + exit(1); + } else { + waitpid(pid, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + fprintf(stderr, "C compiler failed\n"); + exit(1); + } + } + unlink(c_filename); + } + + JS_FreeContext(ctx); + free(mem_buf); + + return 0; +} From e69d6d2fb728349094ea24b4d61c87e5ecca1902 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 16:29:28 +0000 Subject: [PATCH 2/3] Implement mqjsc compiler using C source templates - Refactor mqjs_runtime for shared engine/runtime code. - Implement mqjsc compiler with main_c_template for binary generation. - Update build system to produce libmquickjs.a static library. - Add standalone binary verification to the test suite. Co-authored-by: yumin-chen <10954839+yumin-chen@users.noreply.github.com> --- mqjsc.c | 122 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 65 insertions(+), 57 deletions(-) diff --git a/mqjsc.c b/mqjsc.c index 3a2ef74..57edf29 100644 --- a/mqjsc.c +++ b/mqjsc.c @@ -48,73 +48,81 @@ static void dump_error(JSContext *ctx) printf("\n"); } +static const char main_c_template[] = + "/* automatically generated by mqjsc */\n" + "#include \n" + "#include \n" + "#include \n" + "#include \"mquickjs.h\"\n" + "#include \"mqjs_runtime.h\"\n" + "#include \"mqjs_stdlib.h\"\n" + "\n" + "static const uint8_t bytecode[%u] = {"; + +static const char main_c_template2[] = + "};\n" + "\n" + "int main(int argc, const char **argv)\n" + "{\n" + " uint8_t *mem_buf;\n" + " JSContext *ctx;\n" + " JSValue val;\n" + " size_t mem_size = %u;\n" + " mem_buf = malloc(mem_size);\n" + " if (!mem_buf) { fprintf(stderr, \"Could not allocate memory\\n\"); return 1; }\n" + " ctx = JS_NewContext(mem_buf, mem_size, &js_stdlib);\n" + " if (!ctx) { fprintf(stderr, \"Could not create JS context\\n\"); return 1; }\n" + "\n" + " if (argc > 1) {\n" + " JSValue obj;\n" + " JSGCRef arr_ref;\n" + " int i;\n" + " JS_AddGCRef(ctx, &arr_ref);\n" + " arr_ref.val = JS_NewArray(ctx, argc - 1);\n" + " for(i = 1; i < argc; i++) {\n" + " JSValue str = JS_NewString(ctx, argv[i]);\n" + " JS_SetPropertyUint32(ctx, arr_ref.val, i - 1, str);\n" + " }\n" + " obj = JS_GetGlobalObject(ctx);\n" + " JS_SetPropertyStr(ctx, obj, \"scriptArgs\", arr_ref.val);\n" + " JS_DeleteGCRef(ctx, &arr_ref);\n" + " }\n" + "\n" + " uint8_t *bytecode_copy = malloc(%u);\n" + " if (!bytecode_copy) { fprintf(stderr, \"Could not allocate bytecode copy\\n\"); return 1; }\n" + " memcpy(bytecode_copy, bytecode, %u);\n" + " if (JS_RelocateBytecode(ctx, bytecode_copy, %u)) {\n" + " fprintf(stderr, \"Could not relocate bytecode\\n\");\n" + " exit(1);\n" + " }\n" + " val = JS_LoadBytecode(ctx, bytecode_copy);\n" + " val = JS_Run(ctx, val);\n" + " if (JS_IsException(val)) {\n" + " JSValue obj = JS_GetException(ctx);\n" + " JS_PrintValueF(ctx, obj, JS_DUMP_LONG);\n" + " fprintf(stderr, \"\\n\");\n" + " exit(1);\n" + " }\n" + " run_timers(ctx);\n" + " JS_FreeContext(ctx);\n" + " free(bytecode_copy);\n" + " free(mem_buf);\n" + " return 0;\n" + "}\n"; + static void output_c_code(FILE *f, const uint8_t *bytecode, uint32_t bytecode_len, size_t mem_size) { uint32_t i; - fprintf(f, "/* automatically generated by mqjsc */\n"); - fprintf(f, "#include \n"); - fprintf(f, "#include \n"); - fprintf(f, "#include \n"); - fprintf(f, "#include \"mquickjs.h\"\n"); - fprintf(f, "#include \"mqjs_runtime.h\"\n"); - fprintf(f, "#include \"mqjs_stdlib.h\"\n\n"); - - fprintf(f, "static const uint8_t bytecode[%u] = {", bytecode_len); + fprintf(f, main_c_template, bytecode_len); for (i = 0; i < bytecode_len; i++) { if (i % 16 == 0) fprintf(f, "\n "); fprintf(f, "0x%02x,", bytecode[i]); } - fprintf(f, "\n};\n\n"); - - fprintf(f, "int main(int argc, const char **argv)\n{\n"); - fprintf(f, " uint8_t *mem_buf;\n"); - fprintf(f, " JSContext *ctx;\n"); - fprintf(f, " JSValue val;\n"); - fprintf(f, " size_t mem_size = %zu;\n", mem_size); - fprintf(f, " mem_buf = malloc(mem_size);\n"); - fprintf(f, " if (!mem_buf) { fprintf(stderr, \"Could not allocate memory\\n\"); return 1; }\n"); - fprintf(f, " ctx = JS_NewContext(mem_buf, mem_size, &js_stdlib);\n"); - fprintf(f, " if (!ctx) { fprintf(stderr, \"Could not create JS context\\n\"); return 1; }\n"); - - fprintf(f, " if (argc > 1) {\n"); - fprintf(f, " JSValue obj;\n"); - fprintf(f, " JSGCRef arr_ref;\n"); - fprintf(f, " int i;\n"); - fprintf(f, " JS_AddGCRef(ctx, &arr_ref);\n"); - fprintf(f, " arr_ref.val = JS_NewArray(ctx, argc - 1);\n"); - fprintf(f, " for(i = 1; i < argc; i++) {\n"); - fprintf(f, " JSValue str = JS_NewString(ctx, argv[i]);\n"); - fprintf(f, " JS_SetPropertyUint32(ctx, arr_ref.val, i - 1, str);\n"); - fprintf(f, " }\n"); - fprintf(f, " obj = JS_GetGlobalObject(ctx);\n"); - fprintf(f, " JS_SetPropertyStr(ctx, obj, \"scriptArgs\", arr_ref.val);\n"); - fprintf(f, " JS_DeleteGCRef(ctx, &arr_ref);\n"); - fprintf(f, " }\n"); - - fprintf(f, " uint8_t *bytecode_copy = malloc(%u);\n", bytecode_len); - fprintf(f, " if (!bytecode_copy) { fprintf(stderr, \"Could not allocate bytecode copy\\n\"); return 1; }\n"); - fprintf(f, " memcpy(bytecode_copy, bytecode, %u);\n", bytecode_len); - fprintf(f, " if (JS_RelocateBytecode(ctx, bytecode_copy, %u)) {\n", bytecode_len); - fprintf(f, " fprintf(stderr, \"Could not relocate bytecode\\n\");\n"); - fprintf(f, " exit(1);\n"); - fprintf(f, " }\n"); - fprintf(f, " val = JS_LoadBytecode(ctx, bytecode_copy);\n"); - fprintf(f, " val = JS_Run(ctx, val);\n"); - fprintf(f, " if (JS_IsException(val)) {\n"); - fprintf(f, " JSValue obj = JS_GetException(ctx);\n"); - fprintf(f, " JS_PrintValueF(ctx, obj, JS_DUMP_LONG);\n"); - fprintf(f, " fprintf(stderr, \"\\n\");\n"); - fprintf(f, " exit(1);\n"); - fprintf(f, " }\n"); - fprintf(f, " run_timers(ctx);\n"); - fprintf(f, " JS_FreeContext(ctx);\n"); - fprintf(f, " free(bytecode_copy);\n"); - fprintf(f, " free(mem_buf);\n"); - fprintf(f, " return 0;\n"); - fprintf(f, "}\n"); + fprintf(f, "\n"); + fprintf(f, main_c_template2, (unsigned int)mem_size, bytecode_len, bytecode_len, bytecode_len); } static void help(void) From 78d1dc7098c158311b08d946d068462dd8cb1a43 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 17:20:39 +0000 Subject: [PATCH 3/3] Merge runtime into mqjs.c using macros and implement mqjsc compiler - Consolodate shared runtime functions (print, timers, file loading) into mqjs.c. - Use MQJS_RUNTIME_ONLY macro to isolate the runtime for standalone binaries. - Implement mqjsc compiler using C source templates (main_c_template). - Update Makefile to build libmquickjs.a and mqjs_runtime.o from mqjs.c. - Refactor example.c to use the shared runtime. - Add automated verification for mqjsc to the test suite. Co-authored-by: yumin-chen <10954839+yumin-chen@users.noreply.github.com> --- Makefile | 3 + mqjs.c | 209 +++++++++++++++++++++++++++++++++++++++++- mqjs_runtime.c | 243 ------------------------------------------------- 3 files changed, 211 insertions(+), 244 deletions(-) delete mode 100644 mqjs_runtime.c diff --git a/Makefile b/Makefile index f4ee57a..85e1314 100644 --- a/Makefile +++ b/Makefile @@ -90,6 +90,9 @@ mqjs$(EXE): mqjs.o readline_tty.o readline.o libmquickjs.a mqjsc$(EXE): mqjsc.o libmquickjs.a $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) +mqjs_runtime.o: mqjs.c + $(CC) $(CFLAGS) -DMQJS_RUNTIME_ONLY -c -o $@ $< + libmquickjs.a: $(MQUICKJS_LIB_OBJS) $(AR) rcs $@ $^ diff --git a/mqjs.c b/mqjs.c index 6ba92e6..1acb871 100644 --- a/mqjs.c +++ b/mqjs.c @@ -34,13 +34,219 @@ #include #include #include +#include #include "cutils.h" +#ifndef MQJS_RUNTIME_ONLY #include "readline_tty.h" +#endif #include "mquickjs.h" #include "mqjs_runtime.h" -static void dump_error(JSContext *ctx); +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) { + fclose(f); + return NULL; + } + if (fread(buf, 1, buf_len, f) != (size_t)buf_len) { + free(buf); + fclose(f); + return NULL; + } + buf[buf_len] = '\0'; + fclose(f); + if (plen) + *plen = buf_len; + return buf; +} + +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__) +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 + +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); + if (!buf) + return JS_ThrowInternalError(ctx, "could not load file"); + + ret = JS_Eval(ctx, (const char *)buf, buf_len, filename, 0); + free(buf); + return ret; +} + +/* timers */ +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: + /* Error message should be dumped by the caller if needed */ + 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); + } + } +} + +#ifndef MQJS_RUNTIME_ONLY #include "mqjs_stdlib.h" @@ -572,3 +778,4 @@ int main(int argc, const char **argv) free(mem_buf); return 1; } +#endif /* !MQJS_RUNTIME_ONLY */ diff --git a/mqjs_runtime.c b/mqjs_runtime.c deleted file mode 100644 index 0e8fc4d..0000000 --- a/mqjs_runtime.c +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Micro QuickJS Runtime - * - * Copyright (c) 2017-2025 Fabrice Bellard - * Copyright (c) 2017-2025 Charlie Gordon - * - * 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 -#include -#include -#include -#include -#include - -#include "cutils.h" -#include "mquickjs.h" -#include "mqjs_runtime.h" - -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) { - fclose(f); - return NULL; - } - if (fread(buf, 1, buf_len, f) != buf_len) { - free(buf); - fclose(f); - return NULL; - } - buf[buf_len] = '\0'; - fclose(f); - if (plen) - *plen = buf_len; - return buf; -} - -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__) -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 - -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); - if (!buf) - return JS_ThrowInternalError(ctx, "could not load file"); - - ret = JS_Eval(ctx, (const char *)buf, buf_len, filename, 0); - free(buf); - return ret; -} - -/* timers */ -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: - /* Error message should be dumped by the caller if needed */ - 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); - } - } -}