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
40 changes: 38 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,9 @@ else
QJSC_CC=$(CC)
QJSC=./qjsc$(EXE)
endif
PROGS+=libquickjs.a
PROGS+=libquickjs.a libquickjs-bytecode.a
ifdef CONFIG_LTO
PROGS+=libquickjs.lto.a
PROGS+=libquickjs.lto.a libquickjs-bytecode.lto.a
endif

# examples
Expand All @@ -244,6 +244,7 @@ endif
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
QJS_LIB_BYTECODE_OBJS=$(patsubst $(OBJDIR)/%.o, $(OBJDIR)/%.bytecode.o, $(QJS_LIB_OBJS))

QJS_OBJS=$(OBJDIR)/qjs.o $(OBJDIR)/repl.o $(QJS_LIB_OBJS)

Expand Down Expand Up @@ -303,9 +304,15 @@ endif
libquickjs$(LTOEXT).a: $(QJS_LIB_OBJS)
$(AR) rcs $@ $^

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

ifdef CONFIG_LTO
libquickjs.a: $(patsubst %.o, %.nolto.o, $(QJS_LIB_OBJS))
$(AR) rcs $@ $^

libquickjs-bytecode.a: $(patsubst %.o, %.bytecode.nolto.o, $(QJS_LIB_OBJS))
$(AR) rcs $@ $^
endif # CONFIG_LTO

libquickjs.fuzz.a: $(patsubst %.o, %.fuzz.o, $(QJS_LIB_OBJS))
Expand Down Expand Up @@ -344,6 +351,12 @@ $(OBJDIR)/%.pic.o: %.c | $(OBJDIR)
$(OBJDIR)/%.nolto.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS_NOLTO) -c -o $@ $<

$(OBJDIR)/%.bytecode.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS_OPT) -DCONFIG_BYTECODE_ONLY_RUNTIME -c -o $@ $<

$(OBJDIR)/%.bytecode.nolto.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS_NOLTO) -DCONFIG_BYTECODE_ONLY_RUNTIME -c -o $@ $<

$(OBJDIR)/%.debug.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS_DEBUG) -c -o $@ $<

Expand Down Expand Up @@ -389,6 +402,8 @@ HELLO_OPTS=-fno-string-normalize -fno-map -fno-promise -fno-typedarray \
-fno-typedarray -fno-regexp -fno-json -fno-eval -fno-proxy \
-fno-date -fno-module-loader

BYTECODE_RUNTIME_OPTS=-fno-eval -fno-regexp -fno-json -fno-module-loader

hello.c: $(QJSC) $(HELLO_SRCS)
$(QJSC) -e $(HELLO_OPTS) -o $@ $(HELLO_SRCS)

Expand Down Expand Up @@ -465,6 +480,27 @@ endif
stats: qjs$(EXE)
$(WINE) ./qjs$(EXE) -qd

test-bytecode-runtime: qjsc$(EXE) libquickjs-bytecode.lto.a
./qjsc -e $(BYTECODE_RUNTIME_OPTS) -o hello_bytecode.c examples/hello.js
$(CC) $(CFLAGS_OPT) -flto -o hello_bytecode hello_bytecode.c libquickjs-bytecode.lto.a $(LIBS)
./hello_bytecode | grep "Hello World"
./qjsc -e -m $(BYTECODE_RUNTIME_OPTS) -o test_bytecode_runtime.c tests/test_bytecode_runtime.js
$(CC) $(CFLAGS_OPT) -flto -o test_bytecode_runtime test_bytecode_runtime.c libquickjs-bytecode.lto.a $(LIBS)
./test_bytecode_runtime
@if nm hello_bytecode | grep -E "__JS_EvalInternal|js_parse_program|JS_ParseJSON3|js_compile_regexp|JS_LoadModule"; then \
echo "Error: forbidden symbols found in hello_bytecode"; \
nm hello_bytecode | grep -E "__JS_EvalInternal|js_parse_program|JS_ParseJSON3|js_compile_regexp|JS_LoadModule"; \
exit 1; \
fi
@echo "Testing corrupted bytecode rejection..."
echo "corrupted" > /tmp/bad.bin
@if ./hello_bytecode /tmp/bad.bin 2>&1 | grep -q "Segmentation fault"; then \
echo "Error: Bytecode-only runtime crashed on corrupted input"; \
exit 1; \
fi
@echo "Bytecode-only runtime test passed"
rm -f hello_bytecode hello_bytecode.c test_bytecode_runtime test_bytecode_runtime.c /tmp/bad.bin

microbench: qjs$(EXE)
$(WINE) ./qjs$(EXE) --std tests/microbench.js

Expand Down
2 changes: 1 addition & 1 deletion qjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ int main(int argc, char **argv)
}

if (!empty_run) {
js_std_add_helpers(ctx, argc - optind, argv + optind);
js_std_add_helpers(ctx, argc - optind, argv + optind, TRUE);

/* make 'std' and 'os' visible to non module code */
if (load_std) {
Expand Down
23 changes: 17 additions & 6 deletions qjsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
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 @@ -477,6 +490,8 @@ static int output_executable(const char *out_filename, const char *cfilename,

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

arg = argv;
*arg++ = CONFIG_CC;
Expand Down Expand Up @@ -841,14 +856,10 @@ int main(int argc, char **argv)
(unsigned int)stack_size);
}

/* add the module loader if necessary */
if (feature_bitmap & (1 << FE_MODULE_LOADER)) {
fprintf(fo, " JS_SetModuleLoaderFunc2(rt, NULL, js_module_loader, js_module_check_attributes, NULL);\n");
}

fprintf(fo,
" ctx = JS_NewCustomContext(rt);\n"
" js_std_add_helpers(ctx, argc, argv);\n");
" js_std_add_helpers(ctx, argc, argv, %d);\n",
(feature_bitmap & (1 << FE_MODULE_LOADER)) != 0);

for(i = 0; i < cname_list.count; i++) {
namelist_entry_t *e = &cname_list.array[i];
Expand Down
46 changes: 34 additions & 12 deletions quickjs-libc.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ typedef sig_t sighandler_t;
/* enable the os.Worker API. It relies on POSIX threads */
#define USE_WORKER

#ifdef USE_WORKER
#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME)
#include <pthread.h>
#include <stdatomic.h>
#endif
Expand Down Expand Up @@ -123,7 +123,7 @@ typedef struct JSWaker {

typedef struct {
int ref_count;
#ifdef USE_WORKER
#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME)
pthread_mutex_t mutex;
#endif
struct list_head msg_queue; /* list of JSWorkerMessage.link */
Expand Down 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 @@ -679,11 +681,16 @@ JSModuleDef *js_module_loader(JSContext *ctx,
JSValueConst attributes)
{
JSModuleDef *m;
int res;

if (has_suffix(module_name, ".so")) {
m = js_module_loader_so(ctx, module_name);
} else {
#ifdef CONFIG_BYTECODE_ONLY_RUNTIME
JS_ThrowTypeError(ctx, "could not load module filename '%s': source module loading is not supported",
module_name);
return NULL;
#else
int res;
size_t buf_len;
uint8_t *buf;

Expand Down Expand Up @@ -723,6 +730,7 @@ JSModuleDef *js_module_loader(JSContext *ctx,
m = JS_VALUE_GET_PTR(func_val);
JS_FreeValue(ctx, func_val);
}
#endif
}
return m;
}
Expand Down Expand Up @@ -870,6 +878,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 +927,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 +967,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 +982,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 +1652,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 @@ -2268,7 +2284,7 @@ static void call_handler(JSContext *ctx, JSValueConst func)
JS_FreeValue(ctx, ret);
}

#ifdef USE_WORKER
#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME)

#ifdef _WIN32

Expand Down Expand Up @@ -3421,7 +3437,7 @@ static JSValue js_os_dup2(JSContext *ctx, JSValueConst this_val,

#endif /* !_WIN32 */

#ifdef USE_WORKER
#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME)

/* Worker */

Expand Down Expand Up @@ -3610,7 +3626,7 @@ static void *worker_func(void *opaque)

JS_SetCanBlock(rt, TRUE);

js_std_add_helpers(ctx, -1, NULL);
js_std_add_helpers(ctx, -1, NULL, TRUE);

val = JS_LoadModule(ctx, args->basename, args->filename);
free(args->filename);
Expand Down Expand Up @@ -3870,7 +3886,7 @@ static const JSCFunctionListEntry js_worker_proto_funcs[] = {

void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt))
{
#ifdef USE_WORKER
#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME)
js_worker_new_context_func = func;
#endif
}
Expand Down Expand Up @@ -3976,7 +3992,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 Down Expand Up @@ -4015,7 +4031,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 @@ -4058,14 +4074,18 @@ static JSValue js_console_log(JSContext *ctx, JSValueConst this_val,
return ret;
}

void js_std_add_helpers(JSContext *ctx, int argc, char **argv)
void js_std_add_helpers(JSContext *ctx, int argc, char **argv, int use_module_loader)
{
JSValue global_obj, console, args, performance;
int i;

/* XXX: should these global definitions be enumerable? */
global_obj = JS_GetGlobalObject(ctx);

if (use_module_loader) {
JS_SetModuleLoaderFunc2(JS_GetRuntime(ctx), NULL, js_module_loader, js_module_check_attributes, NULL);
}

console = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, console, "log",
JS_NewCFunction(ctx, js_console_log, "log", 1));
Expand All @@ -4087,8 +4107,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 All @@ -4112,7 +4134,7 @@ void js_std_init_handlers(JSRuntime *rt)

JS_SetRuntimeOpaque(rt, ts);

#ifdef USE_WORKER
#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME)
/* set the SharedArrayBuffer memory handlers */
{
JSSharedArrayBufferFunctions sf;
Expand Down Expand Up @@ -4152,7 +4174,7 @@ void js_std_free_handlers(JSRuntime *rt)
free(rp);
}

#ifdef USE_WORKER
#if defined(USE_WORKER) && !defined(CONFIG_BYTECODE_ONLY_RUNTIME)
js_free_message_pipe(ts->recv_pipe);
js_free_message_pipe(ts->send_pipe);

Expand Down
2 changes: 1 addition & 1 deletion quickjs-libc.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ extern "C" {

JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name);
JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name);
void js_std_add_helpers(JSContext *ctx, int argc, char **argv);
void js_std_add_helpers(JSContext *ctx, int argc, char **argv, int use_module_loader);
void js_std_loop(JSContext *ctx);
JSValue js_std_await(JSContext *ctx, JSValue obj);
void js_std_init_handlers(JSRuntime *rt);
Expand Down
Loading