diff --git a/.obj-rt/cutils.bytecode.lto.o b/.obj-rt/cutils.bytecode.lto.o new file mode 100644 index 000000000..31a92fd16 Binary files /dev/null and b/.obj-rt/cutils.bytecode.lto.o differ diff --git a/.obj-rt/cutils.bytecode.lto.o.d b/.obj-rt/cutils.bytecode.lto.o.d new file mode 100644 index 000000000..4025fc9e8 --- /dev/null +++ b/.obj-rt/cutils.bytecode.lto.o.d @@ -0,0 +1 @@ +.obj-rt/cutils.bytecode.lto.o: cutils.c cutils.h diff --git a/.obj-rt/cutils.bytecode.o b/.obj-rt/cutils.bytecode.o new file mode 100644 index 000000000..e1a6dba4f Binary files /dev/null and b/.obj-rt/cutils.bytecode.o differ diff --git a/.obj-rt/cutils.bytecode.o.d b/.obj-rt/cutils.bytecode.o.d new file mode 100644 index 000000000..24b82be0a --- /dev/null +++ b/.obj-rt/cutils.bytecode.o.d @@ -0,0 +1 @@ +.obj-rt/cutils.bytecode.o: cutils.c cutils.h diff --git a/.obj-rt/dtoa.bytecode.lto.o b/.obj-rt/dtoa.bytecode.lto.o new file mode 100644 index 000000000..6d1a94b3c Binary files /dev/null and b/.obj-rt/dtoa.bytecode.lto.o differ diff --git a/.obj-rt/dtoa.bytecode.lto.o.d b/.obj-rt/dtoa.bytecode.lto.o.d new file mode 100644 index 000000000..18ab15d87 --- /dev/null +++ b/.obj-rt/dtoa.bytecode.lto.o.d @@ -0,0 +1 @@ +.obj-rt/dtoa.bytecode.lto.o: dtoa.c cutils.h dtoa.h diff --git a/.obj-rt/dtoa.bytecode.o b/.obj-rt/dtoa.bytecode.o new file mode 100644 index 000000000..fd111a5be Binary files /dev/null and b/.obj-rt/dtoa.bytecode.o differ diff --git a/.obj-rt/dtoa.bytecode.o.d b/.obj-rt/dtoa.bytecode.o.d new file mode 100644 index 000000000..4e83861d5 --- /dev/null +++ b/.obj-rt/dtoa.bytecode.o.d @@ -0,0 +1 @@ +.obj-rt/dtoa.bytecode.o: dtoa.c cutils.h dtoa.h diff --git a/.obj-rt/libregexp.bytecode.lto.o b/.obj-rt/libregexp.bytecode.lto.o new file mode 100644 index 000000000..912c8bd0d Binary files /dev/null and b/.obj-rt/libregexp.bytecode.lto.o differ diff --git a/.obj-rt/libregexp.bytecode.lto.o.d b/.obj-rt/libregexp.bytecode.lto.o.d new file mode 100644 index 000000000..27ad3a9ef --- /dev/null +++ b/.obj-rt/libregexp.bytecode.lto.o.d @@ -0,0 +1,2 @@ +.obj-rt/libregexp.bytecode.lto.o: libregexp.c cutils.h libregexp.h \ + libunicode.h libregexp-opcode.h diff --git a/.obj-rt/libregexp.bytecode.o b/.obj-rt/libregexp.bytecode.o new file mode 100644 index 000000000..10644468d Binary files /dev/null and b/.obj-rt/libregexp.bytecode.o differ diff --git a/.obj-rt/libregexp.bytecode.o.d b/.obj-rt/libregexp.bytecode.o.d new file mode 100644 index 000000000..b3b4368c4 --- /dev/null +++ b/.obj-rt/libregexp.bytecode.o.d @@ -0,0 +1,2 @@ +.obj-rt/libregexp.bytecode.o: libregexp.c cutils.h libregexp.h \ + libunicode.h libregexp-opcode.h diff --git a/.obj-rt/libunicode.bytecode.lto.o b/.obj-rt/libunicode.bytecode.lto.o new file mode 100644 index 000000000..fb8f4bdba Binary files /dev/null and b/.obj-rt/libunicode.bytecode.lto.o differ diff --git a/.obj-rt/libunicode.bytecode.lto.o.d b/.obj-rt/libunicode.bytecode.lto.o.d new file mode 100644 index 000000000..a5c5103dc --- /dev/null +++ b/.obj-rt/libunicode.bytecode.lto.o.d @@ -0,0 +1,2 @@ +.obj-rt/libunicode.bytecode.lto.o: libunicode.c cutils.h libunicode.h \ + libunicode-table.h diff --git a/.obj-rt/libunicode.bytecode.o b/.obj-rt/libunicode.bytecode.o new file mode 100644 index 000000000..dc5fa8dec Binary files /dev/null and b/.obj-rt/libunicode.bytecode.o differ diff --git a/.obj-rt/libunicode.bytecode.o.d b/.obj-rt/libunicode.bytecode.o.d new file mode 100644 index 000000000..031404da7 --- /dev/null +++ b/.obj-rt/libunicode.bytecode.o.d @@ -0,0 +1,2 @@ +.obj-rt/libunicode.bytecode.o: libunicode.c cutils.h libunicode.h \ + libunicode-table.h diff --git a/.obj-rt/quickjs-libc.bytecode.lto.o b/.obj-rt/quickjs-libc.bytecode.lto.o new file mode 100644 index 000000000..eee3193af Binary files /dev/null and b/.obj-rt/quickjs-libc.bytecode.lto.o differ diff --git a/.obj-rt/quickjs-libc.bytecode.lto.o.d b/.obj-rt/quickjs-libc.bytecode.lto.o.d new file mode 100644 index 000000000..6851cf8d7 --- /dev/null +++ b/.obj-rt/quickjs-libc.bytecode.lto.o.d @@ -0,0 +1,2 @@ +.obj-rt/quickjs-libc.bytecode.lto.o: quickjs-libc.c cutils.h list.h \ + quickjs-libc.h quickjs.h diff --git a/.obj-rt/quickjs-libc.bytecode.o b/.obj-rt/quickjs-libc.bytecode.o new file mode 100644 index 000000000..bc731dc63 Binary files /dev/null and b/.obj-rt/quickjs-libc.bytecode.o differ diff --git a/.obj-rt/quickjs-libc.bytecode.o.d b/.obj-rt/quickjs-libc.bytecode.o.d new file mode 100644 index 000000000..806b45960 --- /dev/null +++ b/.obj-rt/quickjs-libc.bytecode.o.d @@ -0,0 +1,2 @@ +.obj-rt/quickjs-libc.bytecode.o: quickjs-libc.c cutils.h list.h \ + quickjs-libc.h quickjs.h diff --git a/.obj-rt/quickjs.bytecode.lto.o b/.obj-rt/quickjs.bytecode.lto.o new file mode 100644 index 000000000..17bc78287 Binary files /dev/null and b/.obj-rt/quickjs.bytecode.lto.o differ diff --git a/.obj-rt/quickjs.bytecode.lto.o.d b/.obj-rt/quickjs.bytecode.lto.o.d new file mode 100644 index 000000000..2a5d905e1 --- /dev/null +++ b/.obj-rt/quickjs.bytecode.lto.o.d @@ -0,0 +1,2 @@ +.obj-rt/quickjs.bytecode.lto.o: quickjs.c cutils.h list.h quickjs.h \ + libregexp.h libunicode.h dtoa.h quickjs-atom.h quickjs-opcode.h diff --git a/.obj-rt/quickjs.bytecode.o b/.obj-rt/quickjs.bytecode.o new file mode 100644 index 000000000..7e1c19580 Binary files /dev/null and b/.obj-rt/quickjs.bytecode.o differ diff --git a/.obj-rt/quickjs.bytecode.o.d b/.obj-rt/quickjs.bytecode.o.d new file mode 100644 index 000000000..067b8b89e --- /dev/null +++ b/.obj-rt/quickjs.bytecode.o.d @@ -0,0 +1,2 @@ +.obj-rt/quickjs.bytecode.o: quickjs.c cutils.h list.h quickjs.h \ + libregexp.h libunicode.h dtoa.h quickjs-atom.h quickjs-opcode.h diff --git a/Makefile b/Makefile index 95885e374..c47a90b4a 100644 --- a/Makefile +++ b/Makefile @@ -245,6 +245,10 @@ all: $(OBJDIR) $(OBJDIR)/quickjs.check.o $(OBJDIR)/qjs.check.o $(PROGS) QJS_LIB_OBJS=$(OBJDIR)/quickjs.o $(OBJDIR)/dtoa.o $(OBJDIR)/libregexp.o $(OBJDIR)/libunicode.o $(OBJDIR)/cutils.o $(OBJDIR)/quickjs-libc.o +OBJDIR_RT=.obj-rt +QJS_BYTECODE_OBJS=$(patsubst $(OBJDIR)/%.o, $(OBJDIR_RT)/%.bytecode.o, $(QJS_LIB_OBJS)) +QJS_BYTECODE_LTO_OBJS=$(patsubst $(OBJDIR)/%.o, $(OBJDIR_RT)/%.bytecode.lto.o, $(QJS_LIB_OBJS)) + QJS_OBJS=$(OBJDIR)/qjs.o $(OBJDIR)/repl.o $(QJS_LIB_OBJS) HOST_LIBS=-lm -ldl -lpthread @@ -257,6 +261,9 @@ LIBS+=$(EXTRA_LIBS) $(OBJDIR): mkdir -p $(OBJDIR) $(OBJDIR)/examples $(OBJDIR)/tests +$(OBJDIR_RT): + mkdir -p $(OBJDIR_RT) + qjs$(EXE): $(QJS_OBJS) $(CC) $(LDFLAGS) $(LDEXPORT) -o $@ $^ $(LIBS) @@ -308,6 +315,12 @@ libquickjs.a: $(patsubst %.o, %.nolto.o, $(QJS_LIB_OBJS)) $(AR) rcs $@ $^ endif # CONFIG_LTO +libquickjs-bytecode.a: $(QJS_BYTECODE_OBJS) + $(AR) rcs $@ $^ + +libquickjs-bytecode.lto.a: $(QJS_BYTECODE_LTO_OBJS) + $(AR) rcs $@ $^ + libquickjs.fuzz.a: $(patsubst %.o, %.fuzz.o, $(QJS_LIB_OBJS)) $(AR) rcs $@ $^ @@ -353,6 +366,12 @@ $(OBJDIR)/%.fuzz.o: %.c | $(OBJDIR) $(OBJDIR)/%.check.o: %.c | $(OBJDIR) $(CC) $(CFLAGS) -DCONFIG_CHECK_JSVALUE -c -o $@ $< +$(OBJDIR_RT)/%.bytecode.o: %.c | $(OBJDIR_RT) + $(CC) $(CFLAGS_NOLTO) -MF $(OBJDIR_RT)/$(@F).d -DCONFIG_BYTECODE_ONLY_RUNTIME -c -o $@ $< + +$(OBJDIR_RT)/%.bytecode.lto.o: %.c | $(OBJDIR_RT) + $(CC) $(CFLAGS_OPT) -MF $(OBJDIR_RT)/$(@F).d -DCONFIG_BYTECODE_ONLY_RUNTIME -c -o $@ $< + regexp_test: libregexp.c libunicode.c cutils.c $(CC) $(LDFLAGS) $(CFLAGS) -DTEST -o $@ libregexp.c libunicode.c cutils.c $(LIBS) @@ -362,6 +381,7 @@ unicode_gen: $(OBJDIR)/unicode_gen.host.o $(OBJDIR)/cutils.host.o libunicode.c u clean: rm -f repl.c out.c rm -f *.a *.o *.d *~ unicode_gen regexp_test fuzz_eval fuzz_compile fuzz_regexp $(PROGS) + rm -rf $(OBJDIR_RT) rm -f hello.c test_fib.c rm -f examples/*.so tests/*.so rm -rf $(OBJDIR)/ *.dSYM/ qjs-debug$(EXE) @@ -446,6 +466,14 @@ ifdef CONFIG_SHARED_LIBS test: tests/bjson.so examples/point.so endif +test-bytecode-runtime: libquickjs-bytecode.a libquickjs-bytecode.lto.a qjsc$(EXE) + $(QJSC) -flto -fno-eval -fno-regexp -fno-json -fno-module-loader \ + -o tests/test-bytecode-rt tests/test_bytecode_runtime.js + @nm tests/test-bytecode-rt | grep -E ' [Tt] (__JS_EvalInternal|js_parse_|js_compile_|js_evalScript|js_loadScript|js_std_parseExtJSON|js_worker_ctor)' \ + && (echo "FAIL: parser symbols found in bytecode-only binary" && exit 1) \ + || echo "PASS: no parser symbols" + @tests/test-bytecode-rt + test: qjs$(EXE) $(WINE) ./qjs$(EXE) tests/test_closure.js $(WINE) ./qjs$(EXE) tests/test_language.js diff --git a/qjsc.c b/qjsc.c index e55ca61ce..57858263a 100644 --- a/qjsc.c +++ b/qjsc.c @@ -36,6 +36,10 @@ #include "cutils.h" #include "quickjs-libc.h" +#ifdef CONFIG_BYTECODE_ONLY_RUNTIME +#error "qjsc must be built with the full QuickJS engine" +#endif + typedef struct { char *name; char *short_name; @@ -79,6 +83,15 @@ static const FeatureEntry feature_list[] = { { "weakref", "WeakRef" }, }; +#define FE_MASK(i) ((uint64_t)1 << (i)) +#define BYTECODE_ONLY_TRIGGER_MASK \ + (FE_MASK(1) | FE_MASK(3) | FE_MASK(4) | FE_MASK(FE_MODULE_LOADER)) + +static BOOL runtime_needs_parser(void) +{ + return (feature_bitmap & BYTECODE_ONLY_TRIGGER_MASK) != 0; +} + void namelist_add(namelist_t *lp, const char *name, const char *short_name, int flags) { @@ -450,7 +463,7 @@ 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; + const char **arg, *lib_suffix, *lto_suffix; char libjsname[1024]; char exe_dir[1024], inc_dir[1024], lib_dir[1024], buf[1024], *p; int ret; @@ -476,7 +489,7 @@ static int output_executable(const char *out_filename, const char *cfilename, } lto_suffix = ""; - bn_suffix = ""; + lib_suffix = runtime_needs_parser() ? "" : "-bytecode"; arg = argv; *arg++ = CONFIG_CC; @@ -499,7 +512,7 @@ static int output_executable(const char *out_filename, const char *cfilename, *arg++ = "-rdynamic"; *arg++ = cfilename; snprintf(libjsname, sizeof(libjsname), "%s/libquickjs%s%s.a", - lib_dir, bn_suffix, lto_suffix); + lib_dir, lib_suffix, lto_suffix); *arg++ = libjsname; *arg++ = "-lm"; *arg++ = "-ldl"; diff --git a/quickjs-libc.c b/quickjs-libc.c index c24b6d53e..8cc2ae3b5 100644 --- a/quickjs-libc.c +++ b/quickjs-libc.c @@ -694,6 +694,7 @@ JSModuleDef *js_module_loader(JSContext *ctx, return NULL; } res = js_module_test_json(ctx, attributes); +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME if (has_suffix(module_name, ".json") || res > 0) { /* compile as JSON or JSON5 depending on "type" */ JSValue val; @@ -723,6 +724,12 @@ JSModuleDef *js_module_loader(JSContext *ctx, m = JS_VALUE_GET_PTR(func_val); JS_FreeValue(ctx, func_val); } +#else + js_free(ctx, buf); + JS_ThrowReferenceError(ctx, "could not load module filename '%s'", + module_name); + return NULL; +#endif } return m; } @@ -870,6 +877,7 @@ static int get_bool_option(JSContext *ctx, BOOL *pbool, return 0; } +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -918,6 +926,7 @@ static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val, } return ret; } +#endif static JSClassID js_std_file_class_id; @@ -957,6 +966,7 @@ static JSValue js_std_strerror(JSContext *ctx, JSValueConst this_val, return JS_NewString(ctx, strerror(err)); } +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME static JSValue js_std_parseExtJSON(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { @@ -971,6 +981,7 @@ static JSValue js_std_parseExtJSON(JSContext *ctx, JSValueConst this_val, JS_FreeCString(ctx, str); return obj; } +#endif static JSValue js_new_std_file(JSContext *ctx, FILE *f, BOOL close_in_finalizer, @@ -1640,8 +1651,10 @@ static const JSCFunctionListEntry js_std_error_props[] = { static const JSCFunctionListEntry js_std_funcs[] = { JS_CFUNC_DEF("exit", 1, js_std_exit ), JS_CFUNC_DEF("gc", 0, js_std_gc ), +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME JS_CFUNC_DEF("evalScript", 1, js_evalScript ), JS_CFUNC_DEF("loadScript", 1, js_loadScript ), +#endif JS_CFUNC_DEF("getenv", 1, js_std_getenv ), JS_CFUNC_DEF("setenv", 1, js_std_setenv ), JS_CFUNC_DEF("unsetenv", 1, js_std_unsetenv ), @@ -1649,7 +1662,9 @@ static const JSCFunctionListEntry js_std_funcs[] = { JS_CFUNC_DEF("urlGet", 1, js_std_urlGet ), JS_CFUNC_DEF("loadFile", 1, js_std_loadFile ), JS_CFUNC_DEF("strerror", 1, js_std_strerror ), +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME JS_CFUNC_DEF("parseExtJSON", 1, js_std_parseExtJSON ), +#endif /* FILE I/O */ JS_CFUNC_DEF("open", 2, js_std_open ), @@ -3580,6 +3595,7 @@ static JSClassDef js_worker_class = { static void *worker_func(void *opaque) { +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME WorkerFuncArgs *args = opaque; JSRuntime *rt; JSThreadState *ts; @@ -3626,6 +3642,7 @@ static void *worker_func(void *opaque) JS_FreeContext(ctx); js_std_free_handlers(rt); JS_FreeRuntime(rt); +#endif return NULL; } @@ -3664,6 +3681,9 @@ static JSValue js_worker_ctor_internal(JSContext *ctx, JSValueConst new_target, static JSValue js_worker_ctor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { +#ifdef CONFIG_BYTECODE_ONLY_RUNTIME + return JS_ThrowTypeError(ctx, "Worker is not supported"); +#else JSRuntime *rt = JS_GetRuntime(ctx); WorkerFuncArgs *args = NULL; pthread_t tid; @@ -3742,6 +3762,7 @@ static JSValue js_worker_ctor(JSContext *ctx, JSValueConst new_target, } JS_FreeValue(ctx, obj); return JS_EXCEPTION; +#endif } static JSValue js_worker_postMessage(JSContext *ctx, JSValueConst this_val, @@ -3987,8 +4008,12 @@ static int js_os_init(JSContext *ctx, JSModuleDef *m) proto = JS_NewObject(ctx); JS_SetPropertyFunctionList(ctx, proto, js_worker_proto_funcs, countof(js_worker_proto_funcs)); +#ifdef CONFIG_BYTECODE_ONLY_RUNTIME + obj = JS_UNDEFINED; +#else obj = JS_NewCFunction2(ctx, js_worker_ctor, "Worker", 1, JS_CFUNC_constructor, 0); +#endif JS_SetConstructor(ctx, obj, proto); JS_SetClassProto(ctx, js_worker_class_id, proto); @@ -4000,7 +4025,8 @@ static int js_os_init(JSContext *ctx, JSModuleDef *m) JS_PROP_C_W_E); } - JS_SetModuleExport(ctx, m, "Worker", obj); + if (!JS_IsUndefined(obj)) + JS_SetModuleExport(ctx, m, "Worker", obj); } #endif /* USE_WORKER */ @@ -4087,8 +4113,10 @@ void js_std_add_helpers(JSContext *ctx, int argc, char **argv) JS_SetPropertyStr(ctx, global_obj, "print", JS_NewCFunction(ctx, js_print, "print", 1)); +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME JS_SetPropertyStr(ctx, global_obj, "__loadScript", JS_NewCFunction(ctx, js_loadScript, "__loadScript", 1)); +#endif JS_FreeValue(ctx, global_obj); } diff --git a/quickjs.c b/quickjs.c index 2b33bfa5f..caa38cea4 100644 --- a/quickjs.c +++ b/quickjs.c @@ -35439,6 +35439,7 @@ static int add_global_variables(JSContext *ctx, JSFunctionDef *fd) /* create a function object from a function definition. The function definition is freed. All the child functions are also created. It must be done this way to resolve all the variables. */ +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) { JSValue func_obj; @@ -35697,6 +35698,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd) js_free_function_def(ctx, fd); return JS_EXCEPTION; } +#endif /* !CONFIG_BYTECODE_ONLY_RUNTIME */ static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b) { @@ -36493,6 +36495,7 @@ static __exception int js_parse_function_decl(JSParseState *s, JS_PARSE_EXPORT_NONE, NULL); } +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME static __exception int js_parse_program(JSParseState *s) { JSFunctionDef *fd = s->cur_func; @@ -36544,6 +36547,7 @@ static __exception int js_parse_program(JSParseState *s) return 0; } +#endif /* !CONFIG_BYTECODE_ONLY_RUNTIME */ static void js_parse_init(JSContext *ctx, JSParseState *s, const char *input, size_t input_len, @@ -36603,6 +36607,7 @@ JSValue JS_EvalFunction(JSContext *ctx, JSValue fun_obj) } /* 'input' must be zero terminated i.e. input[input_len] = '\0'. */ +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, const char *input, size_t input_len, const char *filename, int flags, int scope_idx) @@ -36717,6 +36722,7 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, JS_FreeValue(ctx, JS_MKPTR(JS_TAG_MODULE, m)); return JS_EXCEPTION; } +#endif /* !CONFIG_BYTECODE_ONLY_RUNTIME */ /* the indirection is needed to make 'eval' optional */ static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj, @@ -55479,7 +55485,9 @@ int JS_AddIntrinsicDate(JSContext *ctx) int JS_AddIntrinsicEval(JSContext *ctx) { +#ifndef CONFIG_BYTECODE_ONLY_RUNTIME ctx->eval_internal = __JS_EvalInternal; +#endif return 0; } diff --git a/tests/test-bytecode-rt b/tests/test-bytecode-rt new file mode 100755 index 000000000..6d9ec6bfc Binary files /dev/null and b/tests/test-bytecode-rt differ diff --git a/tests/test_bytecode_runtime.js b/tests/test_bytecode_runtime.js new file mode 100644 index 000000000..98cdec40e --- /dev/null +++ b/tests/test_bytecode_runtime.js @@ -0,0 +1,18 @@ +function test_closure() { + let x = 10; + return function(y) { + return x + y; + }; +} + +let f = test_closure(); +if (f(5) !== 15) throw new Error("Closure failed"); + +let arr = [1, 2, 3].map(x => x * 2); +if (arr[0] !== 2 || arr[1] !== 4 || arr[2] !== 6) throw new Error("Array map failed"); + +let p = Promise.resolve(42); +p.then(v => { + if (v !== 42) throw new Error("Promise failed"); + console.log("Bytecode runtime test passed"); +});