Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .obj-rt/cutils.bytecode.lto.o
Binary file not shown.
1 change: 1 addition & 0 deletions .obj-rt/cutils.bytecode.lto.o.d
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.obj-rt/cutils.bytecode.lto.o: cutils.c cutils.h
Binary file added .obj-rt/dtoa.bytecode.lto.o
Binary file not shown.
1 change: 1 addition & 0 deletions .obj-rt/dtoa.bytecode.lto.o.d
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.obj-rt/dtoa.bytecode.lto.o: dtoa.c cutils.h dtoa.h
Binary file added .obj-rt/libregexp.bytecode.lto.o
Binary file not shown.
2 changes: 2 additions & 0 deletions .obj-rt/libregexp.bytecode.lto.o.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.obj-rt/libregexp.bytecode.lto.o: libregexp.c cutils.h libregexp.h \
libunicode.h libregexp-opcode.h
Binary file added .obj-rt/libunicode.bytecode.lto.o
Binary file not shown.
2 changes: 2 additions & 0 deletions .obj-rt/libunicode.bytecode.lto.o.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.obj-rt/libunicode.bytecode.lto.o: libunicode.c cutils.h libunicode.h \
libunicode-table.h
Binary file added .obj-rt/quickjs-libc.bytecode.lto.o
Binary file not shown.
2 changes: 2 additions & 0 deletions .obj-rt/quickjs-libc.bytecode.lto.o.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.obj-rt/quickjs-libc.bytecode.lto.o: quickjs-libc.c cutils.h list.h \
quickjs-libc.h quickjs.h
Binary file added .obj-rt/quickjs.bytecode.lto.o
Binary file not shown.
2 changes: 2 additions & 0 deletions .obj-rt/quickjs.bytecode.lto.o.d
Original file line number Diff line number Diff line change
@@ -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
34 changes: 34 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ TEST262_COMMIT?=5c8206929d81b2d3d727ca6aac56c18358c8d790
TEST262_SINCE?=2025-09-01

OBJDIR=.obj
OBJDIR_RT=.obj-rt

ifdef CONFIG_ASAN
OBJDIR:=$(OBJDIR)/asan
Expand Down Expand Up @@ -257,6 +258,10 @@ LIBS+=$(EXTRA_LIBS)
$(OBJDIR):
mkdir -p $(OBJDIR) $(OBJDIR)/examples $(OBJDIR)/tests

$(OBJDIR_RT):
mkdir -p $(OBJDIR_RT)
mkdir -p $(OBJDIR_RT)/examples $(OBJDIR_RT)/tests

qjs$(EXE): $(QJS_OBJS)
$(CC) $(LDFLAGS) $(LDEXPORT) -o $@ $^ $(LIBS)

Expand Down Expand Up @@ -311,6 +316,15 @@ endif # CONFIG_LTO
libquickjs.fuzz.a: $(patsubst %.o, %.fuzz.o, $(QJS_LIB_OBJS))
$(AR) rcs $@ $^

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))

libquickjs-bytecode.a: $(QJS_BYTECODE_OBJS)
$(AR) rcs $@ $^

libquickjs-bytecode.lto.a: $(QJS_BYTECODE_LTO_OBJS)
$(AR) rcs $@ $^

repl.c: $(QJSC) repl.js
$(QJSC) -s -c -o $@ -m repl.js

Expand Down Expand Up @@ -353,6 +367,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) -DCONFIG_BYTECODE_ONLY_RUNTIME -MMD -MF $(OBJDIR_RT)/$(@F).d -c -o $@ $<

$(OBJDIR_RT)/%.bytecode.lto.o: %.c | $(OBJDIR_RT)
$(CC) $(CFLAGS_OPT) -DCONFIG_BYTECODE_ONLY_RUNTIME -MMD -MF $(OBJDIR_RT)/$(@F).d -c -o $@ $<

regexp_test: libregexp.c libunicode.c cutils.c
$(CC) $(LDFLAGS) $(CFLAGS) -DTEST -o $@ libregexp.c libunicode.c cutils.c $(LIBS)

Expand All @@ -374,8 +394,10 @@ install: all
install -m755 qjs$(EXE) qjsc$(EXE) "$(DESTDIR)$(PREFIX)/bin"
mkdir -p "$(DESTDIR)$(PREFIX)/lib/quickjs"
install -m644 libquickjs.a "$(DESTDIR)$(PREFIX)/lib/quickjs"
install -m644 libquickjs-bytecode.a "$(DESTDIR)$(PREFIX)/lib/quickjs"
ifdef CONFIG_LTO
install -m644 libquickjs.lto.a "$(DESTDIR)$(PREFIX)/lib/quickjs"
install -m644 libquickjs-bytecode.lto.a "$(DESTDIR)$(PREFIX)/lib/quickjs"
endif
mkdir -p "$(DESTDIR)$(PREFIX)/include/quickjs"
install -m644 quickjs.h quickjs-libc.h "$(DESTDIR)$(PREFIX)/include/quickjs"
Expand Down Expand Up @@ -511,6 +533,18 @@ testall: all test microbench test2o test2

testall-complete: testall

test-bytecode-runtime: libquickjs-bytecode.lto.a qjsc$(EXE) qjs$(EXE)
$(QJSC) -fno-eval -fno-regexp -fno-json -fno-module-loader \
-o /tmp/test-bytecode-rt tests/test_bytecode_runtime.js
@nm /tmp/test-bytecode-rt | grep -E ' T (__JS_EvalInternal|js_parse_|js_compile_|js_evalScript|js_loadScript|js_std_parseExtJSON|js_worker_ctor)' \
&& (echo "FAIL: forbidden symbols found in bytecode-only binary" && exit 1) \
|| echo "PASS: no forbidden symbols"
@/tmp/test-bytecode-rt > /tmp/test-bytecode-rt.out
@./qjs$(EXE) tests/test_bytecode_runtime.js > /tmp/test-bytecode-rt.expected
@diff /tmp/test-bytecode-rt.out /tmp/test-bytecode-rt.expected \
&& echo "PASS: bytecode round-trip equivalence" \
|| (echo "FAIL: bytecode round-trip equivalence" && exit 1)

node-test:
node tests/test_closure.js
node tests/test_language.js
Expand Down
18 changes: 17 additions & 1 deletion qjsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifdef CONFIG_BYTECODE_ONLY_RUNTIME
#error "qjsc must be built with the full QuickJS engine"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -476,7 +489,10 @@ static int output_executable(const char *out_filename, const char *cfilename,
}

lto_suffix = "";
bn_suffix = "";
if (runtime_needs_parser())
bn_suffix = "";
else
bn_suffix = "-bytecode";

arg = argv;
*arg++ = CONFIG_CC;
Expand Down
27 changes: 24 additions & 3 deletions quickjs-libc.c
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename)
}

/* load and evaluate a file */
#ifndef CONFIG_BYTECODE_ONLY_RUNTIME
static JSValue js_loadScript(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
Expand All @@ -454,6 +455,7 @@ static JSValue js_loadScript(JSContext *ctx, JSValueConst this_val,
JS_FreeCString(ctx, filename);
return ret;
}
#endif

/* load a file as a UTF-8 encoded string */
static JSValue js_std_loadFile(JSContext *ctx, JSValueConst this_val,
Expand Down Expand Up @@ -694,6 +696,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;
Expand Down Expand Up @@ -723,6 +726,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' (bytecode only runtime)",
module_name);
return NULL;
#endif
}
return m;
}
Expand Down Expand Up @@ -870,6 +879,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)
{
Expand Down Expand Up @@ -918,6 +928,7 @@ static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val,
}
return ret;
}
#endif

static JSClassID js_std_file_class_id;

Expand Down Expand Up @@ -957,6 +968,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)
{
Expand All @@ -971,6 +983,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,
Expand Down Expand Up @@ -1640,16 +1653,20 @@ 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 ),
JS_CFUNC_DEF("getenviron", 1, js_std_getenviron ),
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 ),
Expand Down Expand Up @@ -3578,6 +3595,7 @@ static JSClassDef js_worker_class = {
.gc_mark = js_worker_mark,
};

#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME)
static void *worker_func(void *opaque)
{
WorkerFuncArgs *args = opaque;
Expand Down Expand Up @@ -3743,6 +3761,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,
int argc, JSValueConst *argv)
Expand Down Expand Up @@ -3976,7 +3995,7 @@ static int js_os_init(JSContext *ctx, JSModuleDef *m)
{
os_poll_func = js_os_poll;

#ifdef USE_WORKER
#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME)
{
JSRuntime *rt = JS_GetRuntime(ctx);
JSThreadState *ts = JS_GetRuntimeOpaque(rt);
Expand All @@ -4002,7 +4021,7 @@ static int js_os_init(JSContext *ctx, JSModuleDef *m)

JS_SetModuleExport(ctx, m, "Worker", obj);
}
#endif /* USE_WORKER */
#endif /* USE_WORKER && !CONFIG_BYTECODE_ONLY_RUNTIME */

return JS_SetModuleExportList(ctx, m, js_os_funcs,
countof(js_os_funcs));
Expand All @@ -4015,7 +4034,7 @@ JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name)
if (!m)
return NULL;
JS_AddModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs));
#ifdef USE_WORKER
#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME)
JS_AddModuleExport(ctx, m, "Worker");
#endif
return m;
Expand Down Expand Up @@ -4087,8 +4106,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);
}
Expand Down
22 changes: 22 additions & 0 deletions quickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1195,8 +1195,10 @@ static int JS_ToInt32Free(JSContext *ctx, int32_t *pres, JSValue val);
static int JS_ToFloat64Free(JSContext *ctx, double *pres, JSValue val);
static int JS_ToUint8ClampFree(JSContext *ctx, int32_t *pres, JSValue val);
static JSValue js_new_string8_len(JSContext *ctx, const char *buf, int len);
#ifndef CONFIG_BYTECODE_ONLY_RUNTIME
static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern,
JSValueConst flags);
#endif
static JSValue JS_NewRegexp(JSContext *ctx, JSValue pattern, JSValue bc);
static void gc_decref(JSRuntime *rt);
static int JS_NewClass1(JSRuntime *rt, JSClassID class_id,
Expand Down Expand Up @@ -35439,6 +35441,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;
Expand Down Expand Up @@ -35697,6 +35700,7 @@ static JSValue js_create_function(JSContext *ctx, JSFunctionDef *fd)
js_free_function_def(ctx, fd);
return JS_EXCEPTION;
}
#endif

static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b)
{
Expand Down Expand Up @@ -36493,6 +36497,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;
Expand Down Expand Up @@ -36544,6 +36549,7 @@ static __exception int js_parse_program(JSParseState *s)

return 0;
}
#endif

static void js_parse_init(JSContext *ctx, JSParseState *s,
const char *input, size_t input_len,
Expand Down Expand Up @@ -36603,6 +36609,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)
Expand Down Expand Up @@ -36717,6 +36724,7 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
JS_FreeValue(ctx, JS_MKPTR(JS_TAG_MODULE, m));
return JS_EXCEPTION;
}
#endif

/* the indirection is needed to make 'eval' optional */
static JSValue JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
Expand Down Expand Up @@ -46870,6 +46878,7 @@ static void js_regexp_finalizer(JSRuntime *rt, JSValue val)
}

/* create a string containing the RegExp bytecode */
#ifndef CONFIG_BYTECODE_ONLY_RUNTIME
static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern,
JSValueConst flags)
{
Expand Down Expand Up @@ -46947,6 +46956,7 @@ static JSValue js_compile_regexp(JSContext *ctx, JSValueConst pattern,
js_free(ctx, re_bytecode_buf);
return ret;
}
#endif

/* fast regexp creation */
static JSValue JS_NewRegexp(JSContext *ctx, JSValue pattern, JSValue bc)
Expand Down Expand Up @@ -47103,7 +47113,11 @@ static JSValue js_regexp_constructor(JSContext *ctx, JSValueConst new_target,
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_REGEXP);
if (JS_IsException(obj))
goto fail;
#ifndef CONFIG_BYTECODE_ONLY_RUNTIME
bc = js_compile_regexp(ctx, pattern, flags);
#else
bc = JS_UNDEFINED;
#endif
if (JS_IsException(bc))
goto fail;
JS_FreeValue(ctx, flags);
Expand Down Expand Up @@ -47142,7 +47156,11 @@ static JSValue js_regexp_compile(JSContext *ctx, JSValueConst this_val,
pattern = JS_ToString(ctx, pattern1);
if (JS_IsException(pattern))
goto fail;
#ifndef CONFIG_BYTECODE_ONLY_RUNTIME
bc = js_compile_regexp(ctx, pattern, flags1);
#else
bc = JS_UNDEFINED;
#endif
if (JS_IsException(bc))
goto fail;
}
Expand Down Expand Up @@ -48566,7 +48584,9 @@ static const JSCFunctionListEntry js_regexp_string_iterator_proto_funcs[] = {

void JS_AddIntrinsicRegExpCompiler(JSContext *ctx)
{
#ifndef CONFIG_BYTECODE_ONLY_RUNTIME
ctx->compile_regexp = js_compile_regexp;
#endif
}

int JS_AddIntrinsicRegExp(JSContext *ctx)
Expand Down Expand Up @@ -55479,7 +55499,9 @@ int JS_AddIntrinsicDate(JSContext *ctx)

int JS_AddIntrinsicEval(JSContext *ctx)
{
#ifndef CONFIG_BYTECODE_ONLY_RUNTIME
ctx->eval_internal = __JS_EvalInternal;
#endif
return 0;
}

Expand Down
Loading