Files
cell/source/quickjs-internal.h

2008 lines
72 KiB
C

#ifndef QUICKJS_INTERNAL_H
#define QUICKJS_INTERNAL_H
/*
* QuickJS Javascript Engine
*
* 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 <assert.h>
#include <ctype.h>
#include <inttypes.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#if defined(__APPLE__)
#include <malloc/malloc.h>
#elif defined(__linux__) || defined(__GLIBC__)
#include <malloc.h>
#elif defined(__FreeBSD__)
#include <malloc_np.h>
#endif
#include "cutils.h"
#include "dtoa.h"
#include "libregexp.h"
#include "libunicode.h"
#include "list.h"
#include "quickjs.h"
#include "cJSON.h"
#include "blob.h"
#include "nota.h"
#include "wota.h"
#define OPTIMIZE 1
#define SHORT_OPCODES 1
#if defined(EMSCRIPTEN)
#define DIRECT_DISPATCH 0
#else
#define DIRECT_DISPATCH 1
#endif
#if !defined(_WIN32)
/* define it if printf uses the RNDN rounding mode instead of RNDNA */
#define CONFIG_PRINTF_RNDN
#endif
#if !defined(EMSCRIPTEN)
/* enable stack limitation */
#define CONFIG_STACK_CHECK
#endif
/* dump object free */
// #define DUMP_FREE
// #define DUMP_CLOSURE
/* dump the bytecode of the compiled functions: combination of bits
1: dump pass 3 final byte code
2: dump pass 2 code
4: dump pass 1 code
8: dump stdlib functions
16: dump bytecode in hex
32: dump line number table
64: dump compute_stack_size
*/
// #define DUMP_BYTECODE (1)
/* dump GC summary: old/new heap, recovery %, heap growth */
// #define DUMP_GC
/* dump detailed GC: roots, scanning, object traversal (implies DUMP_GC) */
// #define DUMP_GC_DETAIL
#ifdef DUMP_GC_DETAIL
#define DUMP_GC
#endif
/* dump objects freed by the garbage collector */
// #define DUMP_GC_FREE
/* dump memory usage before running the garbage collector */
// #define DUMP_MEM
// #define DUMP_OBJECTS /* dump objects in JS_FreeContext */
// #define DUMP_READ_OBJECT
// #define DUMP_ROPE_REBALANCE
/* test the GC by forcing it before each object allocation */
#define FORCE_GC_AT_MALLOC
#define POISON_HEAP
/* POISON_HEAP: Use ASan's memory poisoning to detect stale pointer access */
#ifdef POISON_HEAP
#if defined(__has_feature)
#if __has_feature(address_sanitizer)
#define HAVE_ASAN 1
#endif
#elif defined(__SANITIZE_ADDRESS__)
#define HAVE_ASAN 1
#endif
#ifdef HAVE_ASAN
#include <sanitizer/asan_interface.h>
#define gc_poison_region(addr, size) __asan_poison_memory_region((addr), (size))
#define gc_unpoison_region(addr, size) __asan_unpoison_memory_region((addr), (size))
#else
/* Fallback: no-op when not building with ASan */
#define gc_poison_region(addr, size) ((void)0)
#define gc_unpoison_region(addr, size) ((void)0)
#endif
#endif /* POISON_HEAP */
#ifdef HAVE_ASAN
static struct JSContext *__asan_js_ctx;
#endif
/* Forward declarations for heap object types */
typedef struct JSArray JSArray;
typedef struct JSBlob JSBlob;
typedef struct JSText JSText;
typedef struct JSRecord JSRecord;
typedef struct JSFunction JSFunction;
typedef struct JSFrame JSFrame;
typedef struct JSCode JSCode;
#define OBJHDR_CAP_SHIFT 8u
#define OBJHDR_CAP_MASK (((objhdr_t)1ull << 56) - 1ull)
#define JS_MKPTR(ptr) (((JSValue)(uintptr_t)(ptr)) | JS_TAG_PTR)
#define JS_MKFWD(ptr) (((objhdr_t)(uintptr_t)(ptr) << OBJHDR_FWD_PTR_SHIFT) | OBJ_FORWARD)
#define JS_VALUE_GET_FWD_PTR(v) ((void*)(uintptr_t)(((v) >> OBJHDR_FWD_PTR_SHIFT) & OBJHDR_FWD_PTR_MASK))
/* For OBJ_FORWARD type: bits 3-63 contain a 61-bit pointer */
#define OBJHDR_FWD_PTR_SHIFT 3u
#define OBJHDR_FWD_PTR_MASK (((objhdr_t)1ull << 61) - 1ull)
#define objhdr_fwd_ptr(h) ((void*)(uintptr_t)(((h) >> OBJHDR_FWD_PTR_SHIFT) & OBJHDR_FWD_PTR_MASK))
#define objhdr_make_fwd(ptr) (((objhdr_t)(uintptr_t)(ptr) << OBJHDR_FWD_PTR_SHIFT) | OBJ_FORWARD)
/* Extract pointer (clear low bits) */
#define JS_VALUE_GET_PTR(v) ((void *)((v) & ~((JSValue)(JSW - 1))))
static inline JS_BOOL JS_VALUE_IS_TEXT (JSValue v) {
int tag = JS_VALUE_GET_TAG (v);
return tag == JS_TAG_STRING_IMM || (JS_IsPtr(v) && objhdr_type(*(objhdr_t *)JS_VALUE_GET_PTR(v)) == OBJ_TEXT);
}
static inline JS_BOOL JS_VALUE_IS_NUMBER (JSValue v) {
int tag = JS_VALUE_GET_TAG (v);
return tag == JS_TAG_INT || tag == JS_TAG_SHORT_FLOAT;
}
/* JS_KEY_* macros: JSValue immediate ASCII strings for common property names.
These replace JS_ATOM_* for use with the new JSValue-based property API. */
#define _JS_KEY1(c1) \
((JSValue)JS_TAG_STRING_IMM | ((JSValue)1 << 5) | ((JSValue)(c1) << 8))
#define _JS_KEY2(c1, c2) \
((JSValue)JS_TAG_STRING_IMM | ((JSValue)2 << 5) | ((JSValue)(c1) << 8) \
| ((JSValue)(c2) << 16))
#define _JS_KEY3(c1, c2, c3) \
((JSValue)JS_TAG_STRING_IMM | ((JSValue)3 << 5) | ((JSValue)(c1) << 8) \
| ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24))
#define _JS_KEY4(c1, c2, c3, c4) \
((JSValue)JS_TAG_STRING_IMM | ((JSValue)4 << 5) | ((JSValue)(c1) << 8) \
| ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24) | ((JSValue)(c4) << 32))
#define _JS_KEY5(c1, c2, c3, c4, c5) \
((JSValue)JS_TAG_STRING_IMM | ((JSValue)5 << 5) | ((JSValue)(c1) << 8) \
| ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24) | ((JSValue)(c4) << 32) \
| ((JSValue)(c5) << 40))
#define _JS_KEY6(c1, c2, c3, c4, c5, c6) \
((JSValue)JS_TAG_STRING_IMM | ((JSValue)6 << 5) | ((JSValue)(c1) << 8) \
| ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24) | ((JSValue)(c4) << 32) \
| ((JSValue)(c5) << 40) | ((JSValue)(c6) << 48))
#define _JS_KEY7(c1, c2, c3, c4, c5, c6, c7) \
((JSValue)JS_TAG_STRING_IMM | ((JSValue)7 << 5) | ((JSValue)(c1) << 8) \
| ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24) | ((JSValue)(c4) << 32) \
| ((JSValue)(c5) << 40) | ((JSValue)(c6) << 48) | ((JSValue)(c7) << 56))
/* Common property keys as JSValue immediate strings (max 7 ASCII chars) */
/* Empty string: tag with length 0 */
#define JS_KEY_empty ((JSValue)JS_TAG_STRING_IMM)
#define JS_KEY_name _JS_KEY4 ('n', 'a', 'm', 'e')
#define JS_KEY_length _JS_KEY6 ('l', 'e', 'n', 'g', 't', 'h')
#define JS_KEY_message _JS_KEY7 ('m', 'e', 's', 's', 'a', 'g', 'e')
#define JS_KEY_stack _JS_KEY5 ('s', 't', 'a', 'c', 'k')
#define JS_KEY_cause _JS_KEY5 ('c', 'a', 'u', 's', 'e')
#define JS_KEY_errors _JS_KEY6 ('e', 'r', 'r', 'o', 'r', 's')
#define JS_KEY_Error _JS_KEY5 ('E', 'r', 'r', 'o', 'r')
#define JS_KEY_raw _JS_KEY3 ('r', 'a', 'w')
#define JS_KEY_flags _JS_KEY5 ('f', 'l', 'a', 'g', 's')
#define JS_KEY_source _JS_KEY6 ('s', 'o', 'u', 'r', 'c', 'e')
#define JS_KEY_exec _JS_KEY4 ('e', 'x', 'e', 'c')
#define JS_KEY_toJSON _JS_KEY6 ('t', 'o', 'J', 'S', 'O', 'N')
#define JS_KEY_eval _JS_KEY4 ('e', 'v', 'a', 'l')
#define JS_KEY_this _JS_KEY4 ('t', 'h', 'i', 's')
#define JS_KEY_true _JS_KEY4 ('t', 'r', 'u', 'e')
#define JS_KEY_false _JS_KEY5 ('f', 'a', 'l', 's', 'e')
#define JS_KEY_null _JS_KEY4 ('n', 'u', 'l', 'l')
#define JS_KEY_NaN _JS_KEY3 ('N', 'a', 'N')
#define JS_KEY_default _JS_KEY7 ('d', 'e', 'f', 'a', 'u', 'l', 't')
#define JS_KEY_value _JS_KEY5 ('v', 'a', 'l', 'u', 'e')
#define JS_KEY_index _JS_KEY5 ('i', 'n', 'd', 'e', 'x')
#define JS_KEY_input _JS_KEY5 ('i', 'n', 'p', 'u', 't')
#define JS_KEY_groups _JS_KEY6 ('g', 'r', 'o', 'u', 'p', 's')
#define JS_KEY_indices _JS_KEY7 ('i', 'n', 'd', 'i', 'c', 'e', 's')
#define JS_KEY_let _JS_KEY3 ('l', 'e', 't')
#define JS_KEY_var _JS_KEY3 ('v', 'a', 'r')
#define JS_KEY_new _JS_KEY3 ('n', 'e', 'w')
#define JS_KEY_of _JS_KEY2 ('o', 'f')
#define JS_KEY_yield _JS_KEY5 ('y', 'i', 'e', 'l', 'd')
#define JS_KEY_async _JS_KEY5 ('a', 's', 'y', 'n', 'c')
#define JS_KEY_target _JS_KEY6 ('t', 'a', 'r', 'g', 'e', 't')
#define JS_KEY_from _JS_KEY4 ('f', 'r', 'o', 'm')
#define JS_KEY_meta _JS_KEY4 ('m', 'e', 't', 'a')
#define JS_KEY_as _JS_KEY2 ('a', 's')
#define JS_KEY_get _JS_KEY3 ('g', 'e', 't')
#define JS_KEY_set _JS_KEY3 ('s', 'e', 't')
#define JS_KEY_with _JS_KEY4 ('w', 'i', 't', 'h')
/* Internal variable names */
#define JS_KEY__ret_ _JS_KEY5 ('<', 'r', 'e', 't', '>')
#define JS_KEY__eval_ _JS_KEY6 ('<', 'e', 'v', 'a', 'l', '>')
#define JS_KEY__var_ _JS_KEY5 ('<', 'v', 'a', 'r', '>')
/* Keys for strings > 7 chars - these use string literals and are created at
runtime. The caller must free the returned JSValue if it's a heap string. */
#define JS_KEY_STR(ctx, str) JS_NewStringLen ((ctx), (str), sizeof (str) - 1)
#define KEY_GET_STR_BUF_SIZE 256
/* JS_IsPretext, JS_KeyGetStr, JS_PushGCRef, JS_PopGCRef, JS_AddGCRef, JS_DeleteGCRef
are defined after JSContext (they need its fields) */
/* Forward declarations for memory functions (now declared in quickjs.h) */
void *js_realloc (JSContext *ctx, void *ptr, size_t size);
/* Forward declaration for string_get */
static inline int string_get (const JSText *p, int idx);
#undef JS_PUSH_VALUE
#undef JS_POP_VALUE
#define JS_PUSH_VALUE(ctx, v) \
do { \
v##_ref.prev = ctx->top_gc_ref; \
ctx->top_gc_ref = &v##_ref; \
v##_ref.val = v; \
} while (0)
#define JS_POP_VALUE(ctx, v) \
do { \
v = v##_ref.val; \
ctx->top_gc_ref = v##_ref.prev; \
} while (0)
/* JS_Invoke - invoke method on object using JSValue key */
static JSValue JS_Invoke (JSContext *ctx, JSValue this_val, JSValue method, int argc, JSValue *argv);
enum {
/* classid tag */ /* union usage | properties */
JS_CLASS_OBJECT = 1, /* must be first */
JS_CLASS_ERROR,
JS_CLASS_REGEXP, /* u.regexp */
JS_CLASS_BLOB, /* u.opaque (blob *) */
JS_CLASS_INIT_COUNT, /* last entry for predefined classes */
};
typedef enum JSErrorEnum {
JS_EVAL_ERROR,
JS_RANGE_ERROR,
JS_REFERENCE_ERROR,
JS_SYNTAX_ERROR,
JS_TYPE_ERROR,
JS_URI_ERROR,
JS_INTERNAL_ERROR,
JS_AGGREGATE_ERROR,
JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */
} JSErrorEnum;
/* the variable and scope indexes must fit on 16 bits. The (-1) and
ARG_SCOPE_END values are reserved. */
#define JS_MAX_LOCAL_VARS 65534
#define JS_STACK_SIZE_MAX 65534
/* Max string length matches header capacity (56 bits on 64-bit builds) */
#define JS_STRING_LEN_MAX OBJHDR_CAP_MASK
#define __exception __attribute__ ((warn_unused_result))
/* Forward declaration for bytecode freeing */
struct JSFunctionBytecode;
#define JS_VALUE_GET_ARRAY(v) ((JSArray *)chase (v))
#define JS_VALUE_GET_OBJ(v) ((JSRecord *)chase (v))
#define JS_VALUE_GET_TEXT(v) ((JSText *)chase (v))
#define JS_VALUE_GET_BLOB(v) ((JSBlob *)JS_VALUE_GET_PTR (v))
#define JS_VALUE_GET_FUNCTION(v) ((JSFunction *)chase (v))
#define JS_VALUE_GET_FRAME(v) ((JSFrame *)chase (v))
#define JS_VALUE_GET_CODE(v) ((JSFunctionBytecode *)JS_VALUE_GET_PTR (v))
#define JS_VALUE_GET_STRING(v) ((JSText *)chase (v))
/* Compatibility: JS_TAG_STRING is an alias for text type checks */
#define JS_TAG_STRING JS_TAG_STRING_IMM
/* JS_TAG_FUNCTION doesn't exist in new encoding - use JS_IsFunction check instead */
#define JS_TAG_FUNCTION 0xFE /* dummy value, never matches any tag */
/* JS_ThrowMemoryError is an alias for JS_ThrowOutOfMemory */
#define JS_ThrowMemoryError(ctx) JS_ThrowOutOfMemory(ctx)
/* Helper to set cap in objhdr */
static inline objhdr_t objhdr_set_cap56 (objhdr_t h, uint64_t cap) {
return (h & 0xFF) | ((cap & OBJHDR_CAP_MASK) << OBJHDR_CAP_SHIFT);
}
typedef enum OPCodeEnum OPCodeEnum;
/* ============================================================
Buddy Allocator for Actor Memory Blocks
============================================================ */
/* Platform-specific minimum block size */
#if defined(__LP64__) || defined(_WIN64)
#define BUDDY_MIN_ORDER 10 /* 1KB minimum on 64-bit */
#else
#define BUDDY_MIN_ORDER 9 /* 512B minimum on 32-bit */
#endif
#define BUDDY_MAX_ORDER 28 /* 256MB maximum */
#define BUDDY_LEVELS (BUDDY_MAX_ORDER - BUDDY_MIN_ORDER + 1)
#define BUDDY_POOL_SIZE (1ULL << BUDDY_MAX_ORDER)
typedef struct BuddyBlock {
struct BuddyBlock *next;
struct BuddyBlock *prev;
uint8_t order; /* log2 of size */
uint8_t is_free;
} BuddyBlock;
typedef struct BuddyAllocator {
uint8_t *base; /* 256MB base address */
size_t total_size; /* 256MB */
BuddyBlock *free_lists[BUDDY_LEVELS];
uint8_t initialized;
} BuddyAllocator;
/* Forward declarations for buddy allocator functions */
static void buddy_destroy (BuddyAllocator *b);
struct JSRuntime {
const char *rt_info;
size_t malloc_limit;
/* Buddy allocator for actor memory blocks */
BuddyAllocator buddy;
/* see JS_SetStripInfo() */
uint8_t strip_flags;
/* User data */
void *user_opaque;
};
struct JSClass {
const char *class_name;
JSClassFinalizer *finalizer;
JSClassGCMark *gc_mark;
uint32_t class_id; /* 0 means free entry */
};
#define JS_MODE_BACKTRACE_BARRIER \
(1 << 3) /* stop backtrace before this frame */
typedef struct JSStackFrame {
struct JSStackFrame *prev_frame; /* NULL if first stack frame */
JSValue cur_func; /* current function, JS_NULL if the frame is detached */
JSValue *arg_buf; /* arguments */
JSValue *var_buf; /* variables */
const uint8_t *cur_pc; /* only used in bytecode functions : PC of the
instruction after the call */
int arg_count;
int js_mode; /* not supported for C functions */
JSValue js_frame; /* GC-managed JSFrame (use JS_VALUE_GET_FRAME to access) */
JSValue *stack_buf; /* operand stack base (for GC scanning) */
JSValue **p_sp; /* pointer to current sp (for GC scanning) */
} JSStackFrame;
/* Heap-allocated VM frame for trampoline execution */
struct VMFrame {
struct JSFunctionBytecode *b; /* current function bytecode */
JSContext *ctx; /* execution context / realm */
const uint8_t *pc; /* program counter */
/* Offset-based storage (safe with realloc) */
int value_stack_base; /* base index into ctx->value_stack */
int sp_offset; /* sp offset from base */
int arg_buf_offset; /* arg buffer offset from base (or -1 if aliased) */
int var_buf_offset; /* var buffer offset from base */
/* Continuation info for return */
const uint8_t *ret_pc; /* where to resume in caller */
int ret_sp_offset; /* caller's sp before call (for cleanup) */
int call_argc; /* number of args to clean up */
int call_has_this; /* whether call had this (method call) */
JSValue cur_func; /* current function object */
JSValue this_obj; /* this binding */
int arg_count;
int js_mode;
int stack_size_allocated; /* total size allocated for this frame */
};
typedef struct JSFrameRegister {
objhdr_t hdr; // capacity in this is the total number of words of the object, including the 4 words of overhead and all slots
JSValue function; // JSFunction, function object being invoked
JSValue caller; // JSFrameRegister, the frame that called this one
JSValue address; // address of the instruction in the code that should be executed upon return
JSValue slots[]; // inline memory. order is [this][input args][closed over vars][non closed over vars][temporaries]
} JSFrameRegister; /// extra note: when this frame returns, caller should be set to 0. If caller is found to be 0, then the GC can reduce this frame's slots down to [this][input_args][closed over vars]; if no closed over vars it can be totally removed; may happen naturally in GC since it would have no refs?
/* ============================================================
Register-Based VM Data Structures
============================================================ */
/* 32-bit instruction encoding (Lua-style)
Formats:
iABC: [op:8][A:8][B:8][C:8] — register ops
iABx: [op:8][A:8][Bx:16] — constant/global loads (unsigned)
iAsBx: [op:8][A:8][sBx:16] — conditional jumps (signed offset)
isJ: [op:8][sJ:24] — unconditional jump (signed offset) */
typedef uint32_t MachInstr32;
typedef struct { uint16_t line; uint16_t col; } MachLineEntry;
/* Encoding macros */
#define MACH_ABC(op, a, b, c) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(b)<<16) | ((uint32_t)(c)<<24))
#define MACH_ABx(op, a, bx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(bx)<<16))
#define MACH_AsBx(op, a, sbx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(uint16_t)(sbx)<<16))
#define MACH_sJ(op, sj) ((uint32_t)(op) | (((uint32_t)(sj) & 0xFFFFFF) << 8))
/* Decoding macros */
#define MACH_GET_OP(i) ((i) & 0xFF)
#define MACH_GET_A(i) (((i) >> 8) & 0xFF)
#define MACH_GET_B(i) (((i) >> 16) & 0xFF)
#define MACH_GET_C(i) (((i) >> 24) & 0xFF)
#define MACH_GET_Bx(i) ((i) >> 16)
#define MACH_GET_sBx(i) ((int16_t)((i) >> 16))
#define MACH_GET_sJ(i) ((int32_t)((i) & 0xFFFFFF00) >> 8)
typedef enum MachOpcode {
/* Constants & Loading */
MACH_LOADK, /* R(A) = K(Bx) — load from constant pool (ABx) */
MACH_LOADI, /* R(A) = (int16_t)sBx — load small integer (AsBx) */
MACH_LOADNULL, /* R(A) = null (A only) */
MACH_LOADTRUE, /* R(A) = true (A only) */
MACH_LOADFALSE, /* R(A) = false (A only) */
/* Movement */
MACH_MOVE, /* R(A) = R(B) */
/* Arithmetic (ABC) */
MACH_ADD, /* R(A) = R(B) + R(C) */
MACH_SUB, /* R(A) = R(B) - R(C) */
MACH_MUL, /* R(A) = R(B) * R(C) */
MACH_DIV, /* R(A) = R(B) / R(C) */
MACH_MOD, /* R(A) = R(B) % R(C) */
MACH_POW, /* R(A) = R(B) ** R(C) */
MACH_NEG, /* R(A) = -R(B) */
MACH_INC, /* R(A) = R(B) + 1 */
MACH_DEC, /* R(A) = R(B) - 1 */
/* Comparison (ABC) */
MACH_EQ, /* R(A) = (R(B) == R(C)) */
MACH_NEQ, /* R(A) = (R(B) != R(C)) */
MACH_LT, /* R(A) = (R(B) < R(C)) */
MACH_LE, /* R(A) = (R(B) <= R(C)) */
MACH_GT, /* R(A) = (R(B) > R(C)) */
MACH_GE, /* R(A) = (R(B) >= R(C)) */
/* Logical/Bitwise */
MACH_LNOT, /* R(A) = !R(B) */
MACH_BNOT, /* R(A) = ~R(B) */
MACH_BAND, /* R(A) = R(B) & R(C) */
MACH_BOR, /* R(A) = R(B) | R(C) */
MACH_BXOR, /* R(A) = R(B) ^ R(C) */
MACH_SHL, /* R(A) = R(B) << R(C) */
MACH_SHR, /* R(A) = R(B) >> R(C) */
MACH_USHR, /* R(A) = R(B) >>> R(C) */
/* Property access */
MACH_GETFIELD, /* R(A) = R(B)[K(C)] — named property */
MACH_SETFIELD, /* R(A)[K(B)] = R(C) — named property */
MACH_GETINDEX, /* R(A) = R(B)[R(C)] — computed property */
MACH_SETINDEX, /* R(A)[R(B)] = R(C) — computed property */
/* Unbound variable access (ABx) */
MACH_GETNAME, /* R(A) = resolve(K(Bx)) — compiler placeholder, patched by link */
MACH_GETINTRINSIC, /* R(A) = global[K(Bx)] — post-link, intrinsic/built-in */
MACH_GETENV, /* R(A) = env[K(Bx)] — post-link, module environment */
/* Closure access (ABC) */
MACH_GETUP, /* R(A) = outer_frame[B].slots[C] */
MACH_SETUP, /* outer_frame[B].slots[C] = R(A) */
/* Control flow */
MACH_JMP, /* pc += sJ — unconditional (isJ format) */
MACH_JMPTRUE, /* if R(A): pc += sBx — (iAsBx format) */
MACH_JMPFALSE, /* if !R(A): pc += sBx — (iAsBx format) */
MACH_JMPNULL, /* if R(A)==null: pc += sBx */
/* Function calls — Lua-style consecutive registers */
MACH_CALL, /* Call R(A) with B args R(A+1)..R(A+B), C=0 discard, C=1 keep result in R(A) */
MACH_RETURN, /* Return R(A) */
MACH_RETNIL, /* Return null */
/* Object/array creation */
MACH_NEWOBJECT, /* R(A) = {} */
MACH_NEWARRAY, /* R(A) = new array, B = element count in R(A+1)..R(A+B) */
MACH_CLOSURE, /* R(A) = closure(functions[Bx]) (ABx) */
MACH_THROW, /* disrupt — trigger disruption */
MACH_PUSH, /* push R(B) onto array R(A) */
MACH_POP, /* R(A) = pop last element from array R(B) */
MACH_DELETE, /* R(A) = delete R(B)[K(C)] — named property delete */
MACH_DELETEINDEX, /* R(A) = delete R(B)[R(C)] — computed property delete */
MACH_HASPROP, /* R(A) = R(C) in R(B) — has property check */
MACH_REGEXP, /* R(A) = regexp(K(B), K(C)) — regex literal */
MACH_CALLMETHOD, /* Method call: R(A)=obj, B=nargs in R(A+2)..R(A+1+B), C=cpool key */
MACH_EQ_TOL, /* R(A) = eq_tol(R(B), R(B+1), R(B+2)), C=3 */
MACH_NEQ_TOL, /* R(A) = ne_tol(R(B), R(B+1), R(B+2)), C=3 */
MACH_NOP,
MACH_OP_COUNT
} MachOpcode;
static const char *mach_opcode_names[MACH_OP_COUNT] = {
[MACH_LOADK] = "loadk",
[MACH_LOADI] = "loadi",
[MACH_LOADNULL] = "loadnull",
[MACH_LOADTRUE] = "loadtrue",
[MACH_LOADFALSE] = "loadfalse",
[MACH_MOVE] = "move",
[MACH_ADD] = "add",
[MACH_SUB] = "sub",
[MACH_MUL] = "mul",
[MACH_DIV] = "div",
[MACH_MOD] = "mod",
[MACH_POW] = "pow",
[MACH_NEG] = "neg",
[MACH_INC] = "inc",
[MACH_DEC] = "dec",
[MACH_EQ] = "eq",
[MACH_NEQ] = "neq",
[MACH_LT] = "lt",
[MACH_LE] = "le",
[MACH_GT] = "gt",
[MACH_GE] = "ge",
[MACH_LNOT] = "lnot",
[MACH_BNOT] = "bnot",
[MACH_BAND] = "band",
[MACH_BOR] = "bor",
[MACH_BXOR] = "bxor",
[MACH_SHL] = "shl",
[MACH_SHR] = "shr",
[MACH_USHR] = "ushr",
[MACH_GETFIELD] = "getfield",
[MACH_SETFIELD] = "setfield",
[MACH_GETINDEX] = "getindex",
[MACH_SETINDEX] = "setindex",
[MACH_GETNAME] = "getname",
[MACH_GETINTRINSIC] = "getintrinsic",
[MACH_GETENV] = "getenv",
[MACH_GETUP] = "getup",
[MACH_SETUP] = "setup",
[MACH_JMP] = "jmp",
[MACH_JMPTRUE] = "jmptrue",
[MACH_JMPFALSE] = "jmpfalse",
[MACH_JMPNULL] = "jmpnull",
[MACH_CALL] = "call",
[MACH_RETURN] = "return",
[MACH_RETNIL] = "retnil",
[MACH_NEWOBJECT] = "newobject",
[MACH_NEWARRAY] = "newarray",
[MACH_CLOSURE] = "closure",
[MACH_THROW] = "throw",
[MACH_PUSH] = "push",
[MACH_POP] = "pop",
[MACH_DELETE] = "delete",
[MACH_DELETEINDEX] = "deleteindex",
[MACH_HASPROP] = "hasprop",
[MACH_REGEXP] = "regexp",
[MACH_CALLMETHOD] = "callmethod",
[MACH_EQ_TOL] = "eq_tol",
[MACH_NEQ_TOL] = "neq_tol",
[MACH_NOP] = "nop",
};
/* Compiled register-based code (off-heap, never GC'd).
Created by JS_CompileMach from AST JSON. */
typedef struct JSCodeRegister {
uint16_t arity; /* number of arguments */
uint16_t nr_close_slots; /* closed-over variable count */
uint16_t nr_slots; /* total frame size */
uint16_t entry_point; /* start instruction (usually 0) */
/* Constant pool */
uint32_t cpool_count;
JSValue *cpool; /* allocated via js_malloc_rt */
/* Compiled 32-bit instructions */
uint32_t instr_count;
MachInstr32 *instructions;
/* Nested functions (for closure) */
uint32_t func_count;
struct JSCodeRegister **functions;
/* Debug info */
JSValue name; /* function name (stone text) */
MachLineEntry *line_table; /* [instr_count], parallel to instructions[] */
char *filename_cstr; /* plain C string for stack trace (js_malloc_rt) */
char *name_cstr; /* plain C string for stack trace (js_malloc_rt) */
uint16_t disruption_pc; /* start of disruption handler (0 = none) */
} JSCodeRegister;
/* Pre-parsed MCODE for a single function (off-heap, never GC'd).
Created by jsmcode_parse from cJSON MCODE output.
Instructions remain as cJSON pointers for string-based dispatch. */
typedef struct JSMCode {
uint16_t nr_args;
uint16_t nr_slots;
/* Pre-flattened instruction array (cJSON array items → C array for O(1) access) */
cJSON **instrs;
uint32_t instr_count;
/* Label map: label string → instruction index */
struct { const char *name; uint32_t index; } *labels;
uint32_t label_count;
/* Nested function definitions (indexes into top-level functions array) */
struct JSMCode **functions;
uint32_t func_count;
/* Keep root cJSON alive (owns all the cJSON nodes instrs[] point into) */
cJSON *json_root;
MachLineEntry *line_table; /* [instr_count], parallel to instrs[] */
const char *name; /* function name (points into cJSON tree) */
const char *filename; /* source filename (points into cJSON tree) */
uint16_t disruption_pc; /* start of disruption handler (0 = none) */
} JSMCode;
/* Frame for closures - used by link-time relocation model where closures
reference outer frames via (depth, slot) addressing.
Stores function as JSValue to survive GC movements. */
typedef struct JSFrame {
objhdr_t header; /* OBJ_FRAME, cap56 = slot count */
JSValue function; /* JSValue for GC safety (use JS_VALUE_GET_FUNCTION) */
JSValue caller; /* JSValue for GC safety (unused currently) */
uint32_t return_pc;
JSValue slots[]; /* args, captured, locals, temps */
} JSFrame;
/* Execution state returned by vm_execute_frame */
typedef enum {
VM_EXEC_NORMAL, /* Continue executing current frame */
VM_EXEC_RETURN, /* Frame returned, pop and resume caller */
VM_EXEC_CALL, /* Need to push new frame for call */
VM_EXEC_EXCEPTION, /* Exception thrown, unwind frames */
} VMExecState;
/* Call info for frame push */
typedef struct {
JSValue func_obj;
JSValue this_obj;
int argc;
JSValue *argv;
const uint8_t *ret_pc;
int ret_sp_offset;
int call_argc;
int call_has_this;
int is_tail_call;
} VMCallInfo;
static inline objhdr_t objhdr_set_s (objhdr_t h, bool s) {
return s ? (h | OBJHDR_S_MASK) : (h & ~OBJHDR_S_MASK);
}
static inline uint64_t objhdr_cap56 (objhdr_t h) {
return (uint64_t)((h >> OBJHDR_CAP_SHIFT) & OBJHDR_CAP_MASK);
}
static inline objhdr_t objhdr_make (uint64_t cap56, uint8_t type, bool r, bool a, bool p, bool s) {
objhdr_t h = 0;
h |= ((objhdr_t)(cap56 & OBJHDR_CAP_MASK)) << OBJHDR_CAP_SHIFT;
h |= (objhdr_t)(type & 7u);
if (s) h |= OBJHDR_S_MASK;
if (p) h |= OBJHDR_P_MASK;
if (a) h |= OBJHDR_A_MASK;
if (r) h |= OBJHDR_R_MASK;
return h;
}
static inline objhdr_t *chase(JSValue v) {
objhdr_t *oh = JS_VALUE_GET_PTR(v);
if (objhdr_type(*oh) != OBJ_FORWARD) return oh;
do {
objhdr_t *next = (objhdr_t*)objhdr_fwd_ptr(*oh);
if (!next) return oh; /* NULL forward pointer (stale reference bug) */
oh = next;
} while (objhdr_type(*oh) == OBJ_FORWARD);
return oh;
}
/* Intrinsic array type - tagged as JS_TAG_PTR with mist_hdr type OBJ_ARRAY */
typedef struct JSArray {
objhdr_t mist_hdr; /* mist header at offset 0 */
word_t len; /* current length */
JSValue values[]; /* inline flexible array member */
} JSArray;
/* JSBlob - binary data per memory.md */
typedef struct JSBlob {
objhdr_t mist_hdr;
word_t length;
uint8_t bits[];
} JSBlob;
typedef struct JSText {
objhdr_t hdr; /* mist header */
word_t length; /* length (or hash for stoned text) */
word_t packed[]; /* two chars per packed word */
} JSText;
typedef struct slot {
JSValue key;
JSValue val; /* use 'val' to match existing code */
} slot;
/* Compatibility alias - old code uses JSRecordEntry */
typedef slot JSRecordEntry;
typedef struct JSRecord {
objhdr_t mist_hdr;
struct JSRecord *proto;
word_t len; /* number of entries */
slot slots[]; /* slots[0] reserved: key low32=class_id, key high32=rec_id, val=opaque */
} JSRecord;
/* Helper macros to access class_id, rec_id, opaque from slots[0] per memory.md */
#define REC_GET_CLASS_ID(rec) ((uint32_t)((rec)->slots[0].key & 0xFFFFFFFF))
#define REC_GET_REC_ID(rec) ((uint32_t)((rec)->slots[0].key >> 32))
#define REC_SET_CLASS_ID(rec, cid) do { \
(rec)->slots[0].key = ((rec)->slots[0].key & 0xFFFFFFFF00000000ULL) | (uint64_t)(cid); \
} while(0)
#define REC_SET_REC_ID(rec, rid) do { \
(rec)->slots[0].key = ((rec)->slots[0].key & 0xFFFFFFFF) | ((uint64_t)(rid) << 32); \
} while(0)
#define REC_GET_OPAQUE(rec) ((void*)(uintptr_t)(rec)->slots[0].val)
#define REC_SET_OPAQUE(rec, ptr) do { (rec)->slots[0].val = (JSValue)(uintptr_t)(ptr); } while(0)
/* ============================================================
Record key helpers (JSValue keys)
============================================================ */
/* GC-SAFE: No allocations. */
static inline JS_BOOL rec_key_is_empty (JSValue key) {
return JS_IsNull (key);
}
/* GC-SAFE: No allocations. */
static inline JS_BOOL rec_key_is_tomb (JSValue key) {
return JS_IsException (key);
}
typedef struct fash64_state {
uint64_t result;
uint64_t sum;
} fash64_state;
enum {
FASH64_PRIME_11 = 11111111111111111027ull,
FASH64_PRIME_8 = 8888888888888888881ull,
FASH64_PRIME_3 = 3333333333333333271ull
};
static inline void fash64_mul_hi_lo (uint64_t a, uint64_t b, uint64_t *hi, uint64_t *lo) {
#if defined(__SIZEOF_INT128__)
__uint128_t p = (__uint128_t)a * (__uint128_t)b;
*lo = (uint64_t)p;
*hi = (uint64_t)(p >> 64);
#elif defined(_MSC_VER) && defined(_M_X64)
*lo = _umul128 (a, b, hi);
#else
/* Portable fallback (no 128-bit type, no _umul128). */
uint64_t a0 = (uint32_t)a;
uint64_t a1 = a >> 32;
uint64_t b0 = (uint32_t)b;
uint64_t b1 = b >> 32;
uint64_t p00 = a0 * b0;
uint64_t p01 = a0 * b1;
uint64_t p10 = a1 * b0;
uint64_t p11 = a1 * b1;
uint64_t mid = (p00 >> 32) + (uint32_t)p01 + (uint32_t)p10;
*lo = (p00 & 0xffffffffull) | (mid << 32);
*hi = p11 + (p01 >> 32) + (p10 >> 32) + (mid >> 32);
#endif
}
static inline void fash64_begin (fash64_state *s) {
s->result = (uint64_t)FASH64_PRIME_8;
s->sum = (uint64_t)FASH64_PRIME_3;
}
static inline void fash64_word (fash64_state *s, uint64_t word) {
uint64_t high, low;
uint64_t mixed = s->result ^ word;
fash64_mul_hi_lo (mixed, (uint64_t)FASH64_PRIME_11, &high, &low);
s->sum += high;
s->result = low ^ s->sum;
}
static inline void fash64_block (fash64_state *s, const uint64_t *block, size_t word_count) {
for (size_t i = 0; i < word_count; i++)
fash64_word (s, block[i]);
}
static inline uint64_t fash64_end (const fash64_state *s) { return s->result; }
/* Convenience one-shot helper */
static inline uint64_t fash64_hash_words (const uint64_t *words,
size_t word_count,
uint64_t extra_word) {
fash64_state s;
fash64_begin (&s);
fash64_block (&s, words, word_count);
fash64_word (&s, extra_word);
return fash64_end (&s);
}
static inline uint64_t fash64_hash_one (uint64_t word) {
uint64_t high, low;
uint64_t mixed = (uint64_t)FASH64_PRIME_8 ^ word;
fash64_mul_hi_lo (mixed, (uint64_t)FASH64_PRIME_11, &high, &low);
uint64_t sum = (uint64_t)FASH64_PRIME_3 + high;
return low ^ sum;
}
static inline word_t JSText_len (const JSText *text) {
if (objhdr_s (text->hdr)) return objhdr_cap56 (text->hdr);
return text->length;
}
static inline JS_BOOL JSText_equal (const JSText *a, const JSText *b) {
word_t len_a = JSText_len (a);
word_t len_b = JSText_len (b);
if (len_a != len_b) return FALSE;
size_t word_count = (len_a + 1) / 2;
return memcmp (a->packed, b->packed, word_count * sizeof (word_t)) == 0;
}
static JS_BOOL JSText_equal_ascii (const JSText *text, JSValue imm) {
int len = MIST_GetImmediateASCIILen (imm);
if (len != JSText_len (text)) return FALSE;
for (int i = 0; i < len; i++) {
uint32_t c = string_get (text, i);
if (c >= 0x80) return FALSE;
if ((uint8_t)c != (uint8_t)MIST_GetImmediateASCIIChar (imm, i))
return FALSE;
}
return TRUE;
}
/* Get hash for a JSText value.
For stoned text (s=1): hash is cached in length field (computed on first access).
For pre-text (s=0): compute hash on the fly. */
/* Max length for key strings (identifiers, property names) */
#define JS_KEY_MAX_LEN 4096
/* Forward declarations for stone arena functions (defined after JSContext) */
/* must be large enough to have a negligible runtime cost and small
enough to call the interrupt callback often. */
#define JS_INTERRUPT_COUNTER_INIT 10000
struct JSContext {
JSRuntime *rt;
/* Actor memory block (bump allocation) */
uint8_t *heap_base; /* start of current block */
uint8_t *heap_free; /* bump pointer */
uint8_t *heap_end; /* end of block */
size_t current_block_size; /* current block size (64KB initially) */
size_t next_block_size; /* doubles if <10% recovered after GC */
/* Stone arena - permanent immutable allocations */
uint8_t *stone_base; /* stone arena base */
uint8_t *stone_free; /* stone arena bump pointer */
uint8_t *stone_end; /* stone arena end */
/* Stone text intern table */
void *st_pages; /* stone page list for large allocations */
uint32_t *st_text_hash; /* hash table (slot -> id) */
JSText **st_text_array; /* array of JSText pointers indexed by id */
uint32_t st_text_size; /* hash table size (power of 2) */
uint32_t st_text_count; /* number of interned texts */
uint32_t st_text_resize; /* threshold for resize */
uint16_t binary_object_count;
int binary_object_size;
/* Record key allocator */
uint32_t rec_key_next;
JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */
JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */
int class_count; /* size of class_array and class_proto */
JSClass *class_array;
JSValue *class_proto;
JSValue regexp_ctor;
JSValue native_error_proto[JS_NATIVE_ERROR_COUNT];
JSValue throw_type_error;
JSValue global_obj; /* global object (immutable intrinsics) */
uint64_t random_state;
/* when the counter reaches zero, JSRutime.interrupt_handler is called */
int interrupt_counter;
/* if NULL, RegExp compilation is not supported */
JSValue (*compile_regexp) (JSContext *ctx, JSValue pattern, JSValue flags);
void *user_opaque;
js_hook trace_hook;
int trace_type;
void *trace_data;
/* Trampoline VM stacks (per actor/context) */
struct VMFrame *frame_stack; /* array of frames */
int frame_stack_top; /* current frame index (-1 = empty) */
int frame_stack_capacity; /* allocated capacity */
JSValue *value_stack; /* array of JSValues for locals/operands */
int value_stack_top; /* current top index */
int value_stack_capacity; /* allocated capacity */
/* Register VM frame root (updated by GC when frame moves) */
JSValue reg_current_frame; /* current JSFrameRegister being executed */
uint32_t current_register_pc; /* PC at exception time */
/* Execution state (moved from JSRuntime — per-actor) */
JSValue current_exception;
struct JSStackFrame *current_stack_frame;
BOOL current_exception_is_uncatchable : 8;
BOOL in_out_of_memory : 8;
JSInterruptHandler *interrupt_handler;
void *interrupt_opaque;
/* Stack overflow protection */
size_t stack_size;
const uint8_t *stack_top;
const uint8_t *stack_limit;
/* Parser state (for GC to scan cpool during parsing) */
struct JSFunctionDef *current_parse_fd;
};
/* ============================================================
Functions that need JSContext definition
============================================================ */
/* Convert JSValue key (string) to C string in buffer.
Returns buf, filled with the string content or "[?]" if not a string. */
static inline const char *JS_KeyGetStr (JSContext *ctx, char *buf, size_t buf_size, JSValue key) {
if (JS_IsText (key)) {
const char *cstr = JS_ToCString (ctx, key);
if (cstr) {
snprintf (buf, buf_size, "%s", cstr);
JS_FreeCString (ctx, cstr);
return buf;
}
}
snprintf (buf, buf_size, "[?]");
return buf;
}
/* ============================================================
Stone Arena Functions
============================================================ */
/* Stone page for large allocations */
typedef struct StonePage {
struct StonePage *next;
size_t size;
uint8_t data[];
} StonePage;
/* Initial stone text table size */
#define ST_TEXT_INITIAL_SIZE 256
/* Allocate from stone arena (permanent, immutable memory) */
/* Resize the stone text intern hash table */
/* Realloc with slack reporting (for bump allocator)
WARNING: This function is NOT GC-safe! The caller must protect the source
object with a GC ref before calling, and re-chase the pointer after. */
/* ============================================================
GC Public API
============================================================ */
/* Forward declaration for ctx_gc */
static int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size);
/* JS_MarkValue - mark a value during GC traversal.
With copying GC, this is a no-op as we discover live objects by tracing. */
/* Helper to check if a pointer is in stone memory */
static inline int is_stone_ptr (JSContext *ctx, void *ptr) {
return (uint8_t *)ptr >= ctx->stone_base && (uint8_t *)ptr < ctx->stone_end;
}
/* Intern a UTF-32 string as a stone text, returning a JSValue string */
/* Create a stoned, interned key from a UTF-8 C string.
Returns immediate ASCII text if ≤7 ASCII chars, otherwise stoned interned
text. The returned JSValue does NOT need to be freed (it's either immediate
or part of the stone arena). */
/* Create a key from a UTF-8 string with explicit length */
typedef union JSFloat64Union {
double d;
uint64_t u64;
uint32_t u32[2];
} JSFloat64Union;
/* JS_IsText is defined in quickjs.h */
/* Helper to get array capacity from mist_hdr */
static inline uint32_t js_array_cap (JSArray *arr) {
return objhdr_cap56 (arr->mist_hdr);
}
/* JSRegExp: regular expression object data (must come before JSRecord/JSRecord) */
typedef struct JSRegExp {
char *pattern; /* UTF-8, null-terminated, js_malloc_rt'd */
uint32_t pattern_len;
uint8_t *bytecode; /* raw lre bytecode, js_malloc_rt'd */
uint32_t bytecode_len;
} JSRegExp;
#define obj_is_stone(rec) objhdr_s ((rec)->mist_hdr)
#define obj_set_stone(rec) ((rec)->mist_hdr = objhdr_set_s ((rec)->mist_hdr, true))
#define JS_VALUE_GET_RECORD(v) ((JSRecord *)chase (v))
/* Get prototype from object (works for both JSRecord and JSRecord since they
* share layout) */
#define JS_OBJ_GET_PROTO(p) ((JSRecord *)((JSRecord *)(p))->proto)
/* Initial capacity for new records (mask = 7, 8 slots total) */
#define JS_RECORD_INITIAL_MASK 7
/* ============================================================
JSRecord Core Operations
============================================================ */
// can check if key by checking for 0 here
/* Compare a JSValue key with a C string literal.
Used for comparing with internal names that are too long for immediate
ASCII. */
/* GC-SAFE: No allocations. Caller must pass freshly-chased rec.
Find slot for a key in record's own table.
Returns slot index (>0) if found, or -(insert_slot) if not found. */
/* GC-SAFE: No allocations. Walks prototype chain via direct pointers.
Caller must pass freshly-chased rec. */
/* GC-SAFE: Resize record by allocating a new larger record and copying all data.
Uses GC ref to protect source during allocation.
Updates *pobj to point to the new record.
Returns 0 on success, -1 on failure. */
/* GC-SAFE: May call rec_resize which allocates.
Takes JSValue* so the object can be tracked through GC.
Updates *pobj if the record is resized. */
/* Helper macros to access class_id and rec_id from slots[0].key per memory.md:
- Low 32 bits of slots[0].key = class_id
- High 32 bits of slots[0].key = rec_id
- slots[0].val = opaque C pointer */
#define REC_GET_CLASS_ID(rec) ((uint32_t)((rec)->slots[0].key & 0xFFFFFFFF))
#define REC_GET_REC_ID(rec) ((uint32_t)((rec)->slots[0].key >> 32))
#define REC_SET_CLASS_ID(rec, cid) do { \
(rec)->slots[0].key = ((rec)->slots[0].key & 0xFFFFFFFF00000000ULL) | (uint64_t)(cid); \
} while(0)
#define REC_SET_REC_ID(rec, rid) do { \
(rec)->slots[0].key = ((rec)->slots[0].key & 0xFFFFFFFF) | ((uint64_t)(rid) << 32); \
} while(0)
#define REC_GET_OPAQUE(rec) ((void*)(uintptr_t)(rec)->slots[0].val)
#define REC_SET_OPAQUE(rec, ptr) do { (rec)->slots[0].val = (JSValue)(uintptr_t)(ptr); } while(0)
/* Allocate a new record with specified class_id.
Uses bump allocation. Slots are inline (flexible array member).
Per memory.md: slots[0] is reserved for class_id, rec_id, and opaque. */
typedef enum {
JS_FUNC_KIND_C,
JS_FUNC_KIND_BYTECODE,
JS_FUNC_KIND_C_DATA,
JS_FUNC_KIND_REGISTER, /* register-based VM function */
JS_FUNC_KIND_MCODE, /* MCODE JSON interpreter */
} JSFunctionKind;
typedef struct JSFunction {
objhdr_t header; /* must come first */
JSValue name; /* function name as JSValue text */
int16_t length; /* arity: max allowed arguments (-1 = variadic) */
uint8_t kind;
union {
struct {
JSCFunctionType c_function;
uint8_t cproto;
int16_t magic;
} cfunc;
struct {
struct JSFunctionBytecode *function_bytecode;
JSValue outer_frame; /* JSFrame JSValue, lexical parent for closures */
JSValue env_record; /* stone record, module environment */
} func;
struct {
JSCodeRegister *code; /* compiled register code (off-heap) */
JSValue env_record; /* stone record, module environment */
JSValue outer_frame; /* JSFrame JSValue, for closures */
} reg;
struct {
JSMCode *code; /* pre-parsed MCODE (off-heap) */
JSValue outer_frame; /* lexical parent frame for closures */
JSValue env_record; /* module env or JS_NULL */
} mcode;
} u;
} JSFunction;
typedef struct JSClosureVar {
uint8_t is_local : 1;
uint8_t is_arg : 1;
uint8_t is_const : 1;
uint8_t is_lexical : 1;
uint8_t var_kind : 4; /* see JSVarKindEnum */
/* 8 bits available */
uint16_t var_idx; /* is_local = TRUE: index to a normal variable of the
parent function. otherwise: index to a closure
variable of the parent function */
JSValue var_name;
} JSClosureVar;
#define ARG_SCOPE_INDEX 1
#define ARG_SCOPE_END (-2)
typedef struct JSVarScope {
int parent; /* index into fd->scopes of the enclosing scope */
int first; /* index into fd->vars of the last variable in this scope */
} JSVarScope;
typedef enum {
/* XXX: add more variable kinds here instead of using bit fields */
JS_VAR_NORMAL,
JS_VAR_FUNCTION_DECL, /* lexical var with function declaration */
JS_VAR_NEW_FUNCTION_DECL, /* lexical var with async/generator
function declaration */
JS_VAR_CATCH,
JS_VAR_FUNCTION_NAME, /* function expression name */
} JSVarKindEnum;
/* XXX: could use a different structure in bytecode functions to save
memory */
typedef struct JSVarDef {
JSValue var_name;
/* index into fd->scopes of this variable lexical scope */
int scope_level;
/* during compilation:
- if scope_level = 0: scope in which the variable is defined
- if scope_level != 0: index into fd->vars of the next
variable in the same or enclosing lexical scope
in a bytecode function:
index into fd->vars of the next
variable in the same or enclosing lexical scope
*/
int scope_next;
uint8_t is_const : 1;
uint8_t is_lexical : 1;
uint8_t is_captured : 1;
uint8_t var_kind : 4; /* see JSVarKindEnum */
/* only used during compilation: function pool index for lexical
variables with var_kind =
JS_VAR_FUNCTION_DECL/JS_VAR_NEW_FUNCTION_DECL or scope level of
the definition of the 'var' variables (they have scope_level =
0) */
int func_pool_idx : 24; /* only used during compilation : index in
the constant pool for hoisted function
definition */
} JSVarDef;
/* for the encoding of the pc2line table */
#define PC2LINE_BASE (-1)
#define PC2LINE_RANGE 5
#define PC2LINE_OP_FIRST 1
#define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE)
typedef struct JSFunctionBytecode {
objhdr_t header; /* must come first */
uint8_t js_mode;
uint8_t has_prototype : 1; /* true if a prototype field is necessary */
uint8_t has_simple_parameter_list : 1;
uint8_t func_kind : 2;
uint8_t has_debug : 1;
uint8_t read_only_bytecode : 1;
uint8_t is_direct_or_indirect_eval
: 1; /* used by JS_GetScriptOrModuleName() */
/* XXX: 10 bits available */
uint8_t *byte_code_buf; /* (self pointer) */
int byte_code_len;
JSValue func_name;
JSVarDef *vardefs; /* arguments + local variables (arg_count + var_count)
(self pointer) */
JSClosureVar
*closure_var; /* list of variables in the closure (self pointer) */
uint16_t arg_count;
uint16_t var_count;
uint16_t defined_arg_count; /* for length function property */
uint16_t stack_size; /* maximum stack size */
JSValue *cpool; /* constant pool (self pointer) */
int cpool_count;
int closure_var_count;
struct {
/* debug info, move to separate structure to save memory? */
JSValue filename;
int source_len;
int pc2line_len;
uint8_t *pc2line_buf;
char *source;
} debug;
} JSFunctionBytecode;
/* New simplified compiled unit structure for Phase 1+ simplification.
Replaces JSFunctionBytecode with a simpler model:
- No closure machinery (uses outer_frame chain at runtime)
- Free variables resolved at link time against env + globals
- Nested functions stored as separate units in cpool */
typedef struct JSCompiledUnit {
objhdr_t header; /* must come first */
/* Bytecode (self pointer) */
uint8_t *byte_code_buf;
int byte_code_len;
/* Constants - strings, numbers, nested unit refs (self pointer) */
JSValue *cpool;
int cpool_count;
/* Stack requirements */
uint16_t local_count; /* total local slots (args + vars) */
uint16_t stack_size; /* operand stack depth */
/* Flags */
uint8_t has_debug : 1;
uint8_t read_only_bytecode : 1;
/* Debug info (optional - only present if has_debug) */
struct {
JSValue filename;
int source_len;
int pc2line_len;
uint8_t *pc2line_buf;
char *source;
} debug;
} JSCompiledUnit;
/* ============================================================
Context-Neutral Module Format (Phase 2+)
Struct definitions are in quickjs.h
============================================================ */
typedef struct JSProperty {
JSValue value;
} JSProperty;
#define JS_PROP_INITIAL_SIZE 2
#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */
#define JS_ARRAY_INITIAL_SIZE 2
/* Max capacity depends on platform - cap field size varies */
#ifdef JS_PTR64
#define JS_ARRAY_MAX_CAP ((word_t)((1ULL << 56) - 1))
#else
#define JS_ARRAY_MAX_CAP ((word_t)((1UL << 24) - 1))
#endif
typedef enum OPCodeFormat {
#define FMT(f) OP_FMT_##f,
#define DEF(id, size, n_pop, n_push, f)
#include "quickjs-opcode.h"
#undef DEF
#undef FMT
} OPCodeFormat;
enum OPCodeEnum {
#define FMT(f)
#define DEF(id, size, n_pop, n_push, f) OP_##id,
#define def(id, size, n_pop, n_push, f)
#include "quickjs-opcode.h"
#undef def
#undef DEF
#undef FMT
OP_COUNT, /* excluding temporary opcodes */
/* temporary opcodes : overlap with the short opcodes */
OP_TEMP_START = OP_nop + 1,
OP___dummy = OP_TEMP_START - 1,
#define FMT(f)
#define DEF(id, size, n_pop, n_push, f)
#define def(id, size, n_pop, n_push, f) OP_##id,
#include "quickjs-opcode.h"
#undef def
#undef DEF
#undef FMT
OP_TEMP_END,
};
JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv);
JSValue js_call_bound_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv);
JSValue JS_CallInternal (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags);
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame);
JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
int argc, JSValue *argv, JSValue outer_frame);
int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop);
JSValue __attribute__ ((format (printf, 2, 3)))
JS_ThrowInternalError (JSContext *ctx, const char *fmt, ...);
__maybe_unused void JS_DumpString (JSRuntime *rt, const JSText *text);
__maybe_unused void JS_DumpObjectHeader (JSRuntime *rt);
__maybe_unused void JS_DumpObject (JSRuntime *rt, JSRecord *rec);
__maybe_unused void JS_DumpGCObject (JSRuntime *rt, objhdr_t *p);
__maybe_unused void JS_DumpValue (JSContext *ctx, const char *str, JSValue val);
void js_dump_value_write (void *opaque, const char *buf, size_t len);
void js_regexp_finalizer (JSRuntime *rt, JSValue val);
JSValue js_new_function (JSContext *ctx, JSFunctionKind kind);
/* Forward declarations for intrinsics (now declared in quickjs.h) */
/* Forward declaration - helper to set cap in objhdr */
static inline objhdr_t objhdr_set_cap56 (objhdr_t h, uint64_t cap);
/* JS_VALUE_GET_STRING is an alias for getting JSText from a string value */
/* Note: Uses chase() for GC safety - already defined at line 293 */
/* JS_ThrowMemoryError is an alias for JS_ThrowOutOfMemory */
#define JS_ThrowMemoryError(ctx) JS_ThrowOutOfMemory(ctx)
/* GC-SAFE: JS_SetPropertyInternal: same as JS_SetProperty but doesn't check stone.
Internal use only. May trigger GC if record needs to resize. */
blob *js_get_blob (JSContext *ctx, JSValue val);
JSValue js_new_string8_len (JSContext *ctx, const char *buf, int len);
JSValue pretext_end (JSContext *ctx, JSText *s);
JSValue js_compile_regexp (JSContext *ctx, JSValue pattern, JSValue flags);
JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, JSValue bc);
int JS_NewClass1 (JSContext *ctx, JSClassID class_id, const JSClassDef *class_def, const char *name);
BOOL js_strict_eq (JSContext *ctx, JSValue op1, JSValue op2);
static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_push (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_pop (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_mach_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_stone (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_length (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_reverse (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_proto (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_splat (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_meme (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_call (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_modulo (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_neg (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_not (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
JSValue js_cell_text_lower (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
JSValue js_cell_text_upper (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_text_trim (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
JSValue js_cell_text_codepoint (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_text_search (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
JSValue js_cell_character (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_abs (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_sign (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_floor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_ceiling (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_round (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_trunc (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_whole (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_fraction (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_min (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_max (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_number_remainder (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
static JSValue js_print (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
cJSON *JS_GetStack(JSContext *ctx);
JSValue JS_ThrowOutOfMemory (JSContext *ctx);
JSValue JS_EvalInternal (JSContext *ctx, JSValue this_obj, const char *input, size_t input_len, const char *filename, int flags, int scope_idx);
JSValue JS_ToNumber (JSContext *ctx, JSValue val);
int JS_SetPropertyValue (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val);
int JS_GetOwnPropertyInternal (JSContext *ctx,
JSValue *desc,
JSRecord *p,
JSValue prop);
/* JS_AddIntrinsicBasicObjects is declared in quickjs.h */
__exception int js_get_length32 (JSContext *ctx, uint32_t *pres, JSValue obj);
__exception int js_get_length64 (JSContext *ctx, int64_t *pres, JSValue obj);
void free_arg_list (JSContext *ctx, JSValue *tab, uint32_t len);
JSValue *build_arg_list (JSContext *ctx, uint32_t *plen, JSValue *parray_arg);
JSValue js_regexp_toString (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
JSValue js_error_toString (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
/* === Inline utility functions (used across modules) === */
static inline int is_digit (int c) { return c >= '0' && c <= '9'; }
static inline int string_get (const JSText *p, int idx) {
int word_idx = idx >> 1;
int shift = (1 - (idx & 1)) * 32;
return (uint32_t)((p->packed[word_idx] >> shift) & 0xFFFFFFFF);
}
static inline void string_put (JSText *p, int idx, uint32_t c) {
int word_idx = idx >> 1;
int shift = (1 - (idx & 1)) * 32;
uint64_t mask = 0xFFFFFFFFULL << shift;
p->packed[word_idx] = (p->packed[word_idx] & ~mask) | ((uint64_t)c << shift);
}
/* Get character from any string value (immediate ASCII or JSText) */
static inline uint32_t js_string_value_get (JSValue v, int idx) {
if (MIST_IsImmediateASCII (v)) {
return MIST_GetImmediateASCIIChar (v, idx);
} else {
JSText *s = JS_VALUE_GET_TEXT (v);
return string_get (s, idx);
}
}
/* Get length from any string value */
static inline int js_string_value_len (JSValue v) {
if (MIST_IsImmediateASCII (v)) {
return MIST_GetImmediateASCIILen (v);
} else {
return JSText_len (JS_VALUE_GET_TEXT (v));
}
}
/* System allocator wrappers (not GC-managed) */
static inline void sys_free (void *ptr) { free (ptr); }
static inline void *sys_malloc (size_t size) { return malloc (size); }
static inline void *sys_realloc (void *ptr, size_t size) { return realloc (ptr, size); }
/* Parser system allocator wrappers (not GC-managed) */
static inline void *pjs_malloc (size_t size) {
return malloc (size);
}
static inline void *pjs_mallocz (size_t size) {
void *ptr = malloc (size);
if (ptr) memset (ptr, 0, size);
return ptr;
}
static inline void *pjs_realloc (void *ptr, size_t size) {
return realloc (ptr, size);
}
static inline void pjs_free (void *ptr) {
free (ptr);
}
static inline int pjs_realloc_array (void **parray, int elem_size, int *psize, int req_size) {
int new_size;
void *new_array;
size_t old_size;
new_size = max_int (req_size, *psize * 3 / 2);
old_size = (size_t)(*psize) * elem_size;
new_array = pjs_realloc (*parray, (size_t)new_size * elem_size);
if (!new_array) return -1;
if (new_size > *psize) {
memset ((char *)new_array + old_size, 0, (size_t)(new_size - *psize) * elem_size);
}
*psize = new_size;
*parray = new_array;
return 0;
}
static inline int pjs_resize_array (void **parray, int elem_size, int *psize, int req_size) {
if (unlikely (req_size > *psize))
return pjs_realloc_array (parray, elem_size, psize, req_size);
else
return 0;
}
/* set the new value and free the old value after */
static inline void set_value (JSContext *ctx, JSValue *pval, JSValue new_val) {
(void)ctx;
*pval = new_val;
}
#if !defined(CONFIG_STACK_CHECK)
static inline uintptr_t js_get_stack_pointer (void) { return 0; }
static inline BOOL js_check_stack_overflow (JSContext *ctx, size_t alloca_size) {
return FALSE;
}
#else
static inline uintptr_t js_get_stack_pointer (void) {
return (uintptr_t)__builtin_frame_address (0);
}
static inline BOOL js_check_stack_overflow (JSContext *ctx, size_t alloca_size) {
uintptr_t sp;
sp = js_get_stack_pointer () - alloca_size;
return unlikely (sp < (uintptr_t)ctx->stack_limit);
}
#endif
void JS_ThrowInterrupted (JSContext *ctx);
static no_inline __exception int __js_poll_interrupts (JSContext *ctx) {
ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT;
if (ctx->interrupt_handler) {
if (ctx->interrupt_handler (ctx->rt, ctx->interrupt_opaque)) {
JS_ThrowInterrupted (ctx);
return -1;
}
}
return 0;
}
static inline __exception int js_poll_interrupts (JSContext *ctx) {
if (unlikely (--ctx->interrupt_counter <= 0)) {
return __js_poll_interrupts (ctx);
} else {
return 0;
}
}
/* === Token enum (shared by parser, tokenizer, AST) === */
enum {
TOK_NUMBER = -128,
TOK_STRING,
TOK_TEMPLATE,
TOK_IDENT,
TOK_REGEXP,
/* warning: order matters (see js_parse_assign_expr) */
TOK_MUL_ASSIGN,
TOK_DIV_ASSIGN,
TOK_MOD_ASSIGN,
TOK_PLUS_ASSIGN,
TOK_MINUS_ASSIGN,
TOK_SHL_ASSIGN,
TOK_SAR_ASSIGN,
TOK_SHR_ASSIGN,
TOK_AND_ASSIGN,
TOK_XOR_ASSIGN,
TOK_OR_ASSIGN,
TOK_POW_ASSIGN,
TOK_LAND_ASSIGN,
TOK_LOR_ASSIGN,
TOK_DEC,
TOK_INC,
TOK_SHL,
TOK_SAR,
TOK_SHR,
TOK_LT,
TOK_LTE,
TOK_GT,
TOK_GTE,
TOK_EQ,
TOK_STRICT_EQ,
TOK_NEQ,
TOK_STRICT_NEQ,
TOK_LAND,
TOK_LOR,
TOK_POW,
TOK_ARROW,
TOK_ERROR,
TOK_PRIVATE_NAME,
TOK_EOF,
/* whitespace/comment tokens for tokenizer */
TOK_COMMENT,
TOK_NEWLINE,
TOK_SPACE,
/* keywords: WARNING: same order as atoms */
TOK_NULL, /* must be first */
TOK_FALSE,
TOK_TRUE,
TOK_IF,
TOK_ELSE,
TOK_RETURN,
TOK_GO,
TOK_VAR,
TOK_DEF,
TOK_THIS,
TOK_DELETE,
TOK_IN,
TOK_DO,
TOK_WHILE,
TOK_FOR,
TOK_BREAK,
TOK_CONTINUE,
TOK_DISRUPT,
TOK_DISRUPTION,
TOK_FUNCTION,
TOK_DEBUGGER,
TOK_WITH,
/* FutureReservedWord */
TOK_CLASS,
TOK_CONST,
TOK_ENUM,
TOK_EXPORT,
TOK_EXTENDS,
TOK_IMPORT,
TOK_SUPER,
/* FutureReservedWords when parsing strict mode code */
TOK_IMPLEMENTS,
TOK_INTERFACE,
TOK_LET,
TOK_PRIVATE,
TOK_PROTECTED,
TOK_PUBLIC,
TOK_STATIC,
TOK_YIELD,
TOK_AWAIT, /* must be last */
TOK_OF, /* only used for js_parse_skip_parens_token() */
};
#define TOK_FIRST_KEYWORD TOK_NULL
#define TOK_LAST_KEYWORD TOK_AWAIT
/* unicode code points */
#define CP_NBSP 0x00a0
#define CP_BOM 0xfeff
#define CP_LS 0x2028
#define CP_PS 0x2029
/* === Line/column cache === */
typedef struct {
/* last source position */
const uint8_t *ptr;
int line_num;
int col_num;
const uint8_t *buf_start;
} GetLineColCache;
/* === JSOpCode (needed by debugger in runtime.c and bytecode in cell_js.c) === */
typedef struct JSOpCode {
#ifdef DUMP_BYTECODE
const char *name;
#endif
uint8_t size; /* in bytes */
/* the opcodes remove n_pop items from the top of the stack, then
pushes n_push items */
uint8_t n_pop;
uint8_t n_push;
uint8_t fmt;
} JSOpCode;
static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = {
#define FMT(f)
#ifdef DUMP_BYTECODE
#define DEF(id, size, n_pop, n_push, f) \
{ #id, size, n_pop, n_push, OP_FMT_##f },
#else
#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_##f },
#endif
#include "quickjs-opcode.h"
#undef DEF
#undef FMT
};
#if SHORT_OPCODES
/* After the final compilation pass, short opcodes are used. Their
opcodes overlap with the temporary opcodes which cannot appear in
the final bytecode. Their description is after the temporary
opcodes in opcode_info[]. */
#define short_opcode_info(op) \
opcode_info[(op) >= OP_TEMP_START ? (op) + (OP_TEMP_END - OP_TEMP_START) \
: (op)]
#else
#define short_opcode_info(op) opcode_info[op]
#endif
/* === MachVarInfo (shared by mach.c and mcode.c) === */
typedef struct MachVarInfo {
char *name;
int slot;
int is_const; /* 1 for def, function args; 0 for var */
int is_closure; /* 1 if captured by a nested function */
int scope_depth; /* block scope nesting level */
} MachVarInfo;
/* === PPretext (parser pretext, system-malloc, used by cell_js.c parser) === */
typedef struct PPretext {
uint32_t *data;
int len;
int cap;
} PPretext;
/* === AST Parse State (shared by tokenize.c and parse.c) === */
typedef struct ASTParseState {
const char *filename;
const uint8_t *buf_start;
const uint8_t *buf_ptr;
const uint8_t *buf_end;
const uint8_t *token_ptr;
int token_val;
BOOL got_lf;
int function_nr;
cJSON *errors; /* array of error objects */
int has_error;
int error_count;
int in_disruption;
char *decoded_str; /* allocated buffer for decoded string escapes */
GetLineColCache lc_cache;
union {
struct {
const char *str;
size_t len;
} str;
struct {
double val;
} num;
struct {
const char *str;
size_t len;
BOOL has_escape;
BOOL is_reserved;
} ident;
struct {
const char *body;
size_t body_len;
const char *flags;
size_t flags_len;
} regexp;
} token_u;
} ASTParseState;
#define JS_CALL_FLAG_COPY_ARGV (1 << 1)
extern JSClassID js_class_id_alloc;
/* === Forward declarations for functions split across modules === */
/* cell_js.c exports */
void gc_scan_parser_cpool (JSContext *ctx, uint8_t *from_base, uint8_t *from_end,
uint8_t *to_base, uint8_t **to_free, uint8_t *to_end);
void gc_scan_bytecode_cpool (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end,
uint8_t *to_base, uint8_t **to_free, uint8_t *to_end);
int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size);
JSValue JS_CallInternal (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags);
int get_line_col (int *pcol_num, const uint8_t *buf, size_t len);
int get_line_col_cached (GetLineColCache *s, int *pcol_num, const uint8_t *ptr);
/* runtime.c exports */
JSValue JS_ThrowStackOverflow (JSContext *ctx);
JSValue JS_ThrowSyntaxErrorVarRedeclaration (JSContext *ctx, JSValue prop);
JSValue JS_ThrowReferenceErrorUninitialized (JSContext *ctx, JSValue name);
JSValue JS_ThrowReferenceErrorUninitialized2 (JSContext *ctx, JSFunctionBytecode *b, int idx, BOOL is_ref);
int JS_DefineObjectName (JSContext *ctx, JSValue obj, JSValue name);
int JS_DefineObjectNameComputed (JSContext *ctx, JSValue obj, JSValue str);
int js_method_set_properties (JSContext *ctx, JSValue func_obj, JSValue name, int flags, JSValue home_obj);
JSValue JS_GetPropertyValue (JSContext *ctx, JSValue this_obj, JSValue prop);
JSValue js_closure (JSContext *ctx, JSValue bfunc, JSStackFrame *sf);
__exception int JS_CopyDataProperties (JSContext *ctx, JSValue target, JSValue source, JSValue excluded, BOOL setprop);
int js_string_compare_value (JSContext *ctx, JSValue op1, JSValue op2, BOOL eq_only);
int js_string_compare_value_nocase (JSContext *ctx, JSValue op1, JSValue op2);
JSValue js_regexp_constructor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv);
int JS_HasProperty (JSContext *ctx, JSValue obj, JSValue prop);
int JS_HasPropertyKey (JSContext *ctx, JSValue obj, JSValue key);
void *js_realloc_rt (void *ptr, size_t size);
char *js_strdup_rt (const char *str);
JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2);
no_inline __exception int js_binary_arith_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op);
no_inline __exception int js_unary_arith_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op);
__exception int js_post_inc_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op);
no_inline int js_not_slow (JSContext *ctx, JSValue *sp);
no_inline int js_relational_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op);
no_inline int js_strict_eq_slow (JSContext *ctx, JSValue *sp, BOOL is_neq);
__exception int js_operator_in (JSContext *ctx, JSValue *sp);
__exception int js_operator_delete (JSContext *ctx, JSValue *sp);
JSText *pretext_init (JSContext *ctx, int capacity);
JSText *pretext_putc (JSContext *ctx, JSText *s, uint32_t c);
JSText *pretext_concat_value (JSContext *ctx, JSText *s, JSValue v);
JSValue js_new_blob (JSContext *ctx, blob *b);
/* Functions from header region (defined in runtime.c) */
void *js_realloc (JSContext *ctx, void *ptr, size_t size);
void *st_alloc (JSContext *ctx, size_t bytes, size_t align);
void st_free_all (JSContext *ctx);
int st_text_resize (JSContext *ctx);
JSValue intern_text_to_value (JSContext *ctx, const uint32_t *utf32, uint32_t len);
JSValue js_key_new (JSContext *ctx, const char *str);
JSValue js_key_new_len (JSContext *ctx, const char *str, size_t len);
uint64_t js_key_hash (JSValue key);
JS_BOOL js_key_equal (JSValue a, JSValue b);
JS_BOOL js_key_equal_str (JSValue a, const char *str);
int rec_find_slot (JSRecord *rec, JSValue k);
JSValue rec_get (JSContext *ctx, JSRecord *rec, JSValue k);
int rec_resize (JSContext *ctx, JSValue *pobj, uint64_t new_mask);
int rec_set_own (JSContext *ctx, JSValue *pobj, JSValue k, JSValue val);
JSRecord *js_new_record_class (JSContext *ctx, uint32_t initial_mask, JSClassID class_id);
int JS_SetPropertyInternal (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val);
uint64_t get_text_hash (JSText *text);
void pack_utf32_to_words (const uint32_t *utf32, uint32_t len, uint64_t *packed);
int text_equal (JSText *a, const uint64_t *packed_b, uint32_t len_b);
static inline JSValue *get_upvalue_ptr (JSValue frame_val, int depth, int slot) {
if (JS_IsNull(frame_val)) return NULL;
JSFrame *frame = JS_VALUE_GET_FRAME(frame_val);
while (depth > 0) {
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
frame_val = fn->u.func.outer_frame;
if (JS_IsNull(frame_val)) return NULL;
frame = JS_VALUE_GET_FRAME(frame_val);
depth--;
}
return &frame->slots[slot];
}
void build_backtrace (JSContext *ctx, JSValue error_obj, const char *filename, int line_num, int col_num, int backtrace_flags);
BOOL is_backtrace_needed (JSContext *ctx, JSValue obj);
JSValue JS_ThrowError2 (JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap, BOOL add_backtrace);
JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end);
PPretext *ppretext_init (int capacity);
PPretext *ppretext_putc (PPretext *p, uint32_t c);
void ppretext_free (PPretext *p);
JSValue ppretext_end (JSContext *ctx, PPretext *p);
PPretext *ppretext_append_jsvalue (PPretext *p, JSValue str);
PPretext *ppretext_append_int (PPretext *p, int n);
JSValue js_atof (JSContext *ctx, const char *str, const char **pp, int radix, int flags);
/* Defines from runtime section needed by cell_js.c */
#define DEFINE_GLOBAL_LEX_VAR (1 << 7)
#define DEFINE_GLOBAL_FUNC_VAR (1 << 6)
#define ATOD_INT_ONLY (1 << 0)
/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */
#define ATOD_ACCEPT_BIN_OCT (1 << 2)
/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */
#define ATOD_ACCEPT_LEGACY_OCTAL (1 << 4)
/* accept _ between digits as a digit separator */
#define ATOD_ACCEPT_UNDERSCORES (1 << 5)
/* allow a suffix to override the type */
#define ATOD_ACCEPT_SUFFIX (1 << 6)
/* default type */
#define ATOD_TYPE_MASK (3 << 7)
#define ATOD_TYPE_FLOAT64 (0 << 7)
#define ATOD_TYPE_BIG_INT (1 << 7)
/* accept -0x1 */
#define ATOD_ACCEPT_PREFIX_AFTER_SIGN (1 << 10)
#define GLOBAL_VAR_OFFSET 0x40000000
#define ARGUMENT_VAR_OFFSET 0x20000000
/* Inline functions from runtime section needed by cell_js.c */
static inline void js_dbuf_init (JSContext *ctx, DynBuf *s) {
dbuf_init2 (s, ctx->rt, NULL);
}
static inline int to_digit (int c) {
if (c >= '0' && c <= '9') return c - '0';
else if (c >= 'A' && c <= 'Z') return c - 'A' + 10;
else if (c >= 'a' && c <= 'z') return c - 'a' + 10;
else return 36;
}
static inline void dbuf_put_leb128 (DynBuf *s, uint32_t v) {
uint32_t a;
for (;;) {
a = v & 0x7f;
v >>= 7;
if (v != 0) {
dbuf_putc (s, a | 0x80);
} else {
dbuf_putc (s, a);
break;
}
}
}
static inline void dbuf_put_sleb128 (DynBuf *s, int32_t v1) {
uint32_t v = v1;
dbuf_put_leb128 (s, (2 * v) ^ -(v >> 31));
}
static inline int get_leb128 (uint32_t *pval, const uint8_t *buf, const uint8_t *buf_end) {
const uint8_t *ptr = buf;
uint32_t v, a, i;
v = 0;
for (i = 0; i < 5; i++) {
if (unlikely (ptr >= buf_end)) break;
a = *ptr++;
v |= (a & 0x7f) << (i * 7);
if (!(a & 0x80)) {
*pval = v;
return ptr - buf;
}
}
*pval = 0;
return -1;
}
static inline int get_sleb128 (int32_t *pval, const uint8_t *buf, const uint8_t *buf_end) {
int ret;
uint32_t val;
ret = get_leb128 (&val, buf, buf_end);
if (ret < 0) {
*pval = 0;
return -1;
}
*pval = (val >> 1) ^ -(val & 1);
return ret;
}
no_inline int js_realloc_array (JSContext *ctx, void **parray, int elem_size, int *psize, int req_size);
static inline int js_resize_array (JSContext *ctx, void **parray, int elem_size, int *psize, int req_size) {
if (unlikely (req_size > *psize))
return js_realloc_array (ctx, parray, elem_size, psize, req_size);
else
return 0;
}
JSText *js_alloc_string (JSContext *ctx, int max_len);
JSValue js_key_from_string (JSContext *ctx, JSValue val);
/* mach.c exports */
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame);
JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count);
cJSON *mach_find_scope_record(cJSON *scopes, int function_nr);
int reg_vm_check_interrupt(JSContext *ctx);
/* mcode.c exports */
JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj, int argc, JSValue *argv, JSValue outer_frame);
/* tokenize.c exports (called by parse.c) */
void cjson_add_strn (cJSON *obj, const char *key, const char *str, size_t len);
cJSON *ast_node (ASTParseState *s, const char *kind, const uint8_t *start_ptr);
void ast_node_end (ASTParseState *s, cJSON *node, const uint8_t *end_ptr);
int ast_next_token (ASTParseState *s);
void ast_free_token (ASTParseState *s);
void ast_get_line_col (ASTParseState *s, const uint8_t *ptr, int *line, int *col);
BOOL tok_eq (const char *str, size_t len, const char *lit);
BOOL ast_is_arrow_function (ASTParseState *s);
int tokenize_next (ASTParseState *s);
void ast_error (ASTParseState *s, const uint8_t *ptr, const char *fmt, ...);
/* parse.c forward declarations */
cJSON *ast_parse_expr (ASTParseState *s);
cJSON *ast_parse_assign_expr (ASTParseState *s);
cJSON *ast_parse_statement (ASTParseState *s);
void ast_sync_to_statement (ASTParseState *s);
cJSON *ast_parse_block_statements (ASTParseState *s);
cJSON *ast_parse_function_inner (ASTParseState *s, BOOL is_expr);
cJSON *ast_parse_arrow_function (ASTParseState *s);
#endif /* QUICKJS_INTERNAL_H */