Skip to main content

Bytecode Operations

QuickJS supports loading and executing pre-compiled bytecode, which is faster than parsing source code and allows for smaller executables when the compiler is not needed.

js_std_eval_binary

Evaluate compiled bytecode from quickjs-libc.
void js_std_eval_binary(JSContext *ctx, const uint8_t *buf,
                       size_t buf_len, int flags);

Parameters

  • ctx - The JavaScript context
  • buf - Pointer to bytecode buffer
  • buf_len - Length of bytecode buffer
  • flags - Load flags

Example

#include <quickjs-libc.h>

// Assuming bytecode was compiled with qjsc
extern const uint8_t my_script_bytecode[];
extern const size_t my_script_bytecode_size;

js_std_eval_binary(ctx, my_script_bytecode, my_script_bytecode_size, 0);

Low-Level Bytecode API

JS_WriteObject

Serialize a JavaScript value to bytecode.
uint8_t *JS_WriteObject(JSContext *ctx, size_t *psize, JSValueConst obj, int flags);

Parameters

  • ctx - The JavaScript context
  • psize - Pointer to receive the bytecode size
  • obj - The value to serialize (typically a function or module)
  • flags - Write flags

Write Flags

#define JS_WRITE_OBJ_BYTECODE      (1 << 0) /* allow function/module */
#define JS_WRITE_OBJ_SAB           (1 << 2) /* allow SharedArrayBuffer */
#define JS_WRITE_OBJ_REFERENCE     (1 << 3) /* allow object references */
#define JS_WRITE_OBJ_STRIP_SOURCE  (1 << 4) /* do not write source code */
#define JS_WRITE_OBJ_STRIP_DEBUG   (1 << 5) /* do not write debug info */

Returns

Returns a buffer containing the bytecode (must be freed with js_free()), or NULL on error.

Example

// Compile a function to bytecode
const char *code = "function greet(name) { return 'Hello, ' + name; }";
JSValue func = JS_Eval(ctx, code, strlen(code), "greet.js",
                       JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_COMPILE_ONLY);

if (!JS_IsException(func)) {
    size_t bytecode_size;
    uint8_t *bytecode = JS_WriteObject(ctx, &bytecode_size, func,
                                       JS_WRITE_OBJ_BYTECODE);
    
    if (bytecode) {
        // Save bytecode to file or embed in executable
        FILE *f = fopen("greet.qjsc", "wb");
        fwrite(bytecode, 1, bytecode_size, f);
        fclose(f);
        
        js_free(ctx, bytecode);
    }
    
    JS_FreeValue(ctx, func);
}

JS_ReadObject

Deserialize bytecode to a JavaScript value.
JSValue JS_ReadObject(JSContext *ctx, const uint8_t *buf, size_t buf_len, int flags);

Parameters

  • ctx - The JavaScript context
  • buf - Pointer to bytecode buffer
  • buf_len - Length of bytecode
  • flags - Read flags

Read Flags

#define JS_READ_OBJ_BYTECODE  (1 << 0) /* allow function/module */
#define JS_READ_OBJ_SAB       (1 << 2) /* allow SharedArrayBuffer */
#define JS_READ_OBJ_REFERENCE (1 << 3) /* allow object references */

Returns

Returns the deserialized value (function or module), or JS_EXCEPTION on error.

Example

// Load bytecode from file
FILE *f = fopen("greet.qjsc", "rb");
fseek(f, 0, SEEK_END);
size_t size = ftell(f);
fseek(f, 0, SEEK_SET);

uint8_t *buf = malloc(size);
fread(buf, 1, size, f);
fclose(f);

JSValue func = JS_ReadObject(ctx, buf, size, JS_READ_OBJ_BYTECODE);
free(buf);

if (!JS_IsException(func)) {
    // Execute the bytecode
    JSValue result = JS_EvalFunction(ctx, func);
    JS_FreeValue(ctx, result);
}

JS_EvalFunction

Execute a bytecode function or module.
JSValue JS_EvalFunction(JSContext *ctx, JSValue fun_obj);
See the JS_Eval documentation for details on evaluation flags.

Module Bytecode

JS_ResolveModule

Load the dependencies of a module.
int JS_ResolveModule(JSContext *ctx, JSValueConst obj);

Parameters

  • ctx - The JavaScript context
  • obj - A module object (from JS_ReadObject())

Returns

Returns 0 on success, -1 on error.

Example

// Load module bytecode
uint8_t *bytecode = load_bytecode_from_file("module.qjsc", &size);
JSValue module = JS_ReadObject(ctx, bytecode, size, JS_READ_OBJ_BYTECODE);
free(bytecode);

if (!JS_IsException(module)) {
    // Resolve module dependencies
    if (JS_ResolveModule(ctx, module) == 0) {
        // Execute the module
        JSValue result = JS_EvalFunction(ctx, module);
        JS_FreeValue(ctx, result);
    }
}

Complete Example: Compile and Execute

#include <quickjs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void compile_to_bytecode(JSContext *ctx, const char *source, const char *filename)
{
    // Compile source to bytecode
    JSValue obj = JS_Eval(ctx, source, strlen(source), filename,
                          JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_COMPILE_ONLY);
    
    if (!JS_IsException(obj)) {
        size_t bytecode_size;
        uint8_t *bytecode = JS_WriteObject(ctx, &bytecode_size, obj,
                                           JS_WRITE_OBJ_BYTECODE);
        
        if (bytecode) {
            // Save to file
            FILE *f = fopen("output.qjsc", "wb");
            fwrite(bytecode, 1, bytecode_size, f);
            fclose(f);
            
            printf("Compiled to output.qjsc (%zu bytes)\n", bytecode_size);
            js_free(ctx, bytecode);
        }
        
        JS_FreeValue(ctx, obj);
    } else {
        js_std_dump_error(ctx);
    }
}

void execute_bytecode(JSContext *ctx, const char *filename)
{
    FILE *f = fopen(filename, "rb");
    if (!f) {
        fprintf(stderr, "Cannot open %s\n", filename);
        return;
    }
    
    fseek(f, 0, SEEK_END);
    size_t size = ftell(f);
    fseek(f, 0, SEEK_SET);
    
    uint8_t *buf = malloc(size);
    fread(buf, 1, size, f);
    fclose(f);
    
    JSValue obj = JS_ReadObject(ctx, buf, size, JS_READ_OBJ_BYTECODE);
    free(buf);
    
    if (!JS_IsException(obj)) {
        JSValue result = JS_EvalFunction(ctx, obj);
        if (JS_IsException(result)) {
            js_std_dump_error(ctx);
        }
        JS_FreeValue(ctx, result);
    } else {
        js_std_dump_error(ctx);
    }
}

Using qjsc Compiler

The qjsc tool compiles JavaScript to C code with embedded bytecode:
# Compile to C code
qjsc -o script.c script.js

# Compile to bytecode file
qjsc -c -o script.qjsc script.js
Generated C code:
// script.c (generated by qjsc)
const uint8_t qjsc_script[] = {
    0x02, 0x04, 0x0e, 0x73, 0x63, 0x72, 0x69, 0x70,
    // ... bytecode bytes ...
};

const uint32_t qjsc_script_size = 1234;
Use in your program:
#include "script.c"

js_std_eval_binary(ctx, qjsc_script, qjsc_script_size, 0);

Security Considerations

Warning: The bytecode format is linked to a specific QuickJS version. No security checks are performed before execution. Only load bytecode from trusted sources.
  • Bytecode is version-specific
  • No signature verification
  • No sandboxing of bytecode execution
  • Malicious bytecode can compromise the runtime

Notes

  • Bytecode is faster to load than source code (no parsing)
  • Smaller executables when compiler is excluded
  • Use JS_WRITE_OBJ_STRIP_SOURCE and JS_WRITE_OBJ_STRIP_DEBUG to reduce bytecode size
  • Bytecode format may change between QuickJS versions
  • For modules, use JS_ResolveModule() before JS_EvalFunction()

Build docs developers (and LLMs) love