2008 lines
72 KiB
C
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 */
|