#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 <stdatomic.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"

#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

#include <sys/mman.h>
#include <unistd.h>

/* HEAP_CHECK: validate heap pointers at JS_VALUE_GET_* macros */
// #define HEAP_CHECK

#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
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 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_REGEXP = 3,     /* u.regexp */
  JS_CLASS_BLOB,           /* u.opaque (blob *) */

  JS_CLASS_INIT_COUNT, /* last entry for predefined classes */
};

/* Log callback — set by engine.cm via $_set_log to route JS_Log through ƿit.
   For "memory" channel (OOM), the callback is skipped (can't allocate). */
typedef void (*JSLogCallback)(JSContext *ctx, const char *channel, const char *msg);

/* 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 */

#define JS_VALUE_GET_BLOB(v) ((JSBlob *)JS_VALUE_GET_PTR (v))
#define JS_VALUE_GET_CODE(v) (JS_VALUE_GET_PTR (v))

#ifdef HEAP_CHECK
void heap_check_fail(void *ptr, struct JSContext *ctx);
#define JS_VALUE_GET_ARRAY(v) ((JSArray *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_OBJ(v) ((JSRecord *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_TEXT(v) ((JSText *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_FUNCTION(v) ((JSFunction *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_FRAME(v) ((JSFrame *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_STRING(v) ((JSText *)heap_check_chase(ctx, v))
#define JS_VALUE_GET_RECORD(v) ((JSRecord *)heap_check_chase(ctx, v))
#else
#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_FUNCTION(v) ((JSFunction *)chase (v))
#define JS_VALUE_GET_FRAME(v) ((JSFrame *)chase (v))
#define JS_VALUE_GET_STRING(v) ((JSText *)chase (v))
#define JS_VALUE_GET_RECORD(v) ((JSRecord *)chase (v))
#endif

/* Compatibility: JS_TAG_STRING is an alias for text type checks */
#define JS_TAG_STRING JS_TAG_STRING_IMM

/* JS_ThrowMemoryError is an alias for JS_RaiseOOM */
#define JS_ThrowMemoryError(ctx) JS_RaiseOOM(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);
}

/* ============================================================
   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_LEVELS 40   /* supports pools up to 2^(BUDDY_MIN_ORDER+39) */
#define BUDDY_DEFAULT_POOL (1ULL << 24) /* 16MB initial pool */

typedef struct BuddyBlock {
  struct BuddyBlock *next;
  struct BuddyBlock *prev;
  uint8_t order; /* log2 of size */
  uint8_t is_free;
} BuddyBlock;

typedef struct BuddyPool {
  struct BuddyPool *next;
  uint8_t *base;
  size_t total_size;
  uint8_t max_order;        /* log2(total_size) */
  uint32_t alloc_count;     /* outstanding allocations */
  BuddyBlock *free_lists[BUDDY_MAX_LEVELS];
} BuddyPool;

typedef struct BuddyAllocator {
  BuddyPool *pools;         /* linked list, newest first */
  size_t next_pool_size;    /* next pool doubles from this */
  size_t initial_size;      /* starting pool size */
  size_t cap;               /* 0 = no cap */
  size_t total_mapped;      /* sum of all pool sizes */
} BuddyAllocator;

/* Forward declarations for buddy allocator functions */
static void buddy_destroy (BuddyAllocator *b);
static size_t buddy_max_block (BuddyAllocator *b);

/* controls a host of contexts, handing out memory and scheduling */
struct JSRuntime {
  size_t malloc_limit;
  BuddyAllocator buddy;
};

struct JSClass {
  const char *class_name;
  JSClassFinalizer *finalizer;
  uint32_t class_id; /* 0 means free entry */
};

#define JS_MODE_BACKTRACE_BARRIER \
  (1 << 3) /* stop backtrace before this frame */

/* JSFrameRegister is now an alias for JSFrame — see JSFrame definition below */

/* ============================================================
   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)

/* ============================================================
   GC Safepoint Classification for the MACH VM Dispatch Loop
   ============================================================

   Every opcode falls into one of three categories:

   [P] Pure inline — never calls C, never allocates. No GC possible.
       No frame re-derivation needed after execution.

   [N] Non-allocating C call — calls a C function that is guaranteed
       to never allocate (e.g. JS_ToBool, js_string_compare_value).
       No frame re-derivation needed.

   [G] GC safepoint — calls C that may allocate, triggering GC.
       After the call, all heap pointers (including `frame`) MUST be
       re-derived via: frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);

   The 18 C entry points that can allocate (GC safepoints):
    1. JS_GetProperty        — key interning for string keys >7 chars
    2. JS_SetProperty        — rec_resize when record grows
    3. JS_GetPropertyNumber   — text substring extraction
    4. JS_SetPropertyNumber   — array grow
    5. JS_NewObject           — allocates record
    6. JS_NewArray / JS_NewArrayLen — allocates array
    7. js_new_register_function — allocates function object (closure)
    8. alloc_frame_register   — allocates frame via js_mallocz
    9. js_call_c_function     — arbitrary C code
   10. JS_CallInternal        — arbitrary bytecode
   11. JS_Call                — arbitrary call
   12. JS_ConcatString        — allocates new string
   13. JS_ArrayPush           — array grow
   14. JS_ArrayPop            — reads, but frame refresh needed
   15. JS_DeleteProperty      — mutates record
   16. JS_HasProperty         — complex traversal
   17. js_regexp_constructor  — allocates regex
   18. reg_vm_binop           — polymorphic dispatch (legacy opcodes)

   Opcode-level classification:
     [P] LOADK, LOADI, LOADNULL, LOADTRUE, LOADFALSE, MOVE, NOP
     [P] ADD_INT..MOD_INT, NEG_INT, ADD_FLOAT..MOD_FLOAT, NEG_FLOAT
     [P] EQ_INT..GE_INT, EQ_FLOAT..GE_FLOAT, EQ_BOOL, NE_BOOL
     [P] IS_IDENTICAL, IS_INT, IS_NUM, IS_TEXT, IS_BOOL, IS_NULL
     [P] IS_ARRAY, IS_FUNC, IS_RECORD, IS_STONE, IS_PROXY
     [P] NOT, AND, OR, BITNOT, BITAND, BITOR, BITXOR
     [P] JMP, JMPTRUE, JMPFALSE, JMPNULL, JMPNOTNULL
     [P] RETURN, RETNIL, SETARG, GETUP, SETUP, DISRUPT, THROW
     [P] LENGTH (array + imm-ASCII fast path only; text/blob fallback is [G])
     [N] EQ_TEXT..GE_TEXT (js_string_compare_value — no allocation)
     [N] LNOT (JS_ToBool — no allocation)
     [G] ADD..USHR, NEG, INC, DEC, EQ..GE (legacy: reg_vm_binop)
     [G] EQ_TOL, NEQ_TOL (tolerance comparison, may fall back)
     [G] CONCAT (JS_ConcatString)
     [G] GETFIELD, SETFIELD, GETINDEX, SETINDEX (property access)
     [G] LOAD_FIELD, STORE_FIELD, LOAD_INDEX, STORE_INDEX
     [G] LOAD_DYNAMIC, STORE_DYNAMIC
     [G] GETNAME, GETINTRINSIC, GETENV, SET_VAR
     [G] NEWOBJECT, NEWRECORD, NEWARRAY (object/array creation)
     [G] CLOSURE (js_new_register_function)
     [G] FRAME, GOFRAME (alloc_frame_register)
     [G] INVOKE, GOINVOKE (function calls)
     [G] PUSH (JS_ArrayPush), POP (JS_ArrayPop)
     [G] DELETE, DELETEINDEX (JS_DeleteProperty)
     [G] HASPROP, IN (JS_HasProperty)
     [G] REGEXP (js_regexp_constructor)
   ============================================================ */

typedef enum MachOpcode {
  /* === Legacy opcodes (used by existing .mach files) === */

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

  /* Generic arithmetic (ABC) — used by legacy .mach */
  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__DEAD_INC,     /* reserved — was MACH_INC, never emitted */
  MACH__DEAD_DEC,     /* reserved — was MACH_DEC, never emitted */

  /* Generic comparison (ABC) — used by legacy .mach */
  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 — used by legacy .mach */
  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 — used by legacy .mach */
  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 (legacy .mach) */
  MACH_RETURN,        /* Return R(A) */
  MACH_RETNIL,        /* Return null */

  /* Object/array creation — legacy .mach */
  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_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,

  /* === New mcode-derived opcodes (1:1 mapping to mcode IR) === */

  /* Text */
  MACH_CONCAT,        /* R(A) = R(B) ++ R(C) — string concatenation */

  /* Typed integer comparisons (ABC) */
  MACH_EQ_INT,        /* R(A) = (R(B) == R(C)) — int */
  MACH_NE_INT,        /* R(A) = (R(B) != R(C)) — int */
  MACH_LT_INT,        /* R(A) = (R(B) <  R(C)) — int */
  MACH_LE_INT,        /* R(A) = (R(B) <= R(C)) — int */
  MACH_GT_INT,        /* R(A) = (R(B) >  R(C)) — int */
  MACH_GE_INT,        /* R(A) = (R(B) >= R(C)) — int */

  /* Typed float comparisons (ABC) */
  MACH_EQ_FLOAT,      /* R(A) = (R(B) == R(C)) — float */
  MACH_NE_FLOAT,      /* R(A) = (R(B) != R(C)) — float */
  MACH_LT_FLOAT,      /* R(A) = (R(B) <  R(C)) — float */
  MACH_LE_FLOAT,      /* R(A) = (R(B) <= R(C)) — float */
  MACH_GT_FLOAT,      /* R(A) = (R(B) >  R(C)) — float */
  MACH_GE_FLOAT,      /* R(A) = (R(B) >= R(C)) — float */

  /* Typed text comparisons (ABC) */
  MACH_EQ_TEXT,        /* R(A) = (R(B) == R(C)) — text */
  MACH_NE_TEXT,        /* R(A) = (R(B) != R(C)) — text */
  MACH_LT_TEXT,        /* R(A) = (R(B) <  R(C)) — text */
  MACH_LE_TEXT,        /* R(A) = (R(B) <= R(C)) — text */
  MACH_GT_TEXT,        /* R(A) = (R(B) >  R(C)) — text */
  MACH_GE_TEXT,        /* R(A) = (R(B) >= R(C)) — text */

  /* Typed bool comparisons (ABC) */
  MACH_EQ_BOOL,       /* R(A) = (R(B) == R(C)) — bool */
  MACH_NE_BOOL,       /* R(A) = (R(B) != R(C)) — bool */

  /* Special comparisons */
  MACH_IS_IDENTICAL,  /* R(A) = (R(B) === R(C)) — identity check (ABC) */

  /* Type checks (AB) */
  MACH_IS_INT,        /* R(A) = is_int(R(B)) */
  MACH_IS_NUM,        /* R(A) = is_num(R(B)) */
  MACH_IS_TEXT,       /* R(A) = is_text(R(B)) */
  MACH_IS_BOOL,       /* R(A) = is_bool(R(B)) */
  MACH_IS_NULL,       /* R(A) = is_null(R(B)) */

  /* Logical (mcode-style) */
  MACH_NOT,           /* R(A) = !R(B) — boolean not (AB) */
  MACH_AND,           /* R(A) = R(B) && R(C) (ABC) */
  MACH_OR,            /* R(A) = R(B) || R(C) (ABC) */

  /* Bitwise (mcode names) */
  MACH_BITNOT,        /* R(A) = ~R(B) (AB) */
  MACH_BITAND,        /* R(A) = R(B) & R(C) (ABC) */
  MACH_BITOR,         /* R(A) = R(B) | R(C) (ABC) */
  MACH_BITXOR,        /* R(A) = R(B) ^ R(C) (ABC) */

  /* Property access (mcode names) */
  MACH_LOAD_FIELD,    /* R(A) = R(B).K(C) — named property (ABC) */
  MACH_STORE_FIELD,   /* R(A).K(B) = R(C) — named property (ABC) */
  MACH_LOAD_INDEX,    /* R(A) = R(B)[R(C)] — integer index (ABC) */
  MACH_STORE_INDEX,   /* R(A)[R(B)] = R(C) — integer index (ABC) */
  MACH_LOAD_DYNAMIC,  /* R(A) = R(B)[R(C)] — dynamic key (ABC) */
  MACH_STORE_DYNAMIC, /* R(A)[R(B)] = R(C) — dynamic key (ABC) */

  /* Object/Array creation (mcode names) */
  MACH_NEWRECORD,     /* R(A) = {} — new empty record (A only) */

  /* Decomposed function calls (mcode-style) */
  MACH_FRAME,         /* R(A) = frame(R(B), C) — alloc call frame (ABC) */
  MACH_SETARG,        /* frame R(A)[B] = R(C) — set arg in frame (ABC) */
  MACH_INVOKE,        /* R(B) = invoke(R(A)) — call frame, result in R(B) (AB) */
  MACH_GOFRAME,       /* R(A) = goframe(R(B), C) — async frame (ABC) */
  MACH_GOINVOKE,      /* goinvoke(R(A)) — async invoke, no result (A only) */

  /* Control flow */
  MACH_JMPNOTNULL,    /* if R(A)!=null: pc += sBx (iAsBx) */

  /* Error handling */
  MACH_DISRUPT,       /* trigger disruption (A only) */

  /* Misc */
  MACH_IN,            /* R(A) = (R(B) in R(C)) — has property (ABC) */

  /* Extended type checks (AB) */
  MACH_IS_ARRAY,      /* R(A) = is_array(R(B)) */
  MACH_IS_FUNC,       /* R(A) = is_function(R(B)) */
  MACH_IS_RECORD,     /* R(A) = is_object(R(B)) */
  MACH_IS_STONE,      /* R(A) = is_stone(R(B)) */
  MACH_LENGTH,        /* R(A) = length(R(B)) — array/text/blob length */
  MACH_IS_PROXY,      /* R(A) = is_function(R(B)) && R(B).length == 2 */

  MACH_OP_COUNT
} MachOpcode;

static const char *mach_opcode_names[MACH_OP_COUNT] = {
  /* Legacy */
  [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__DEAD_INC] = "dead_inc",
  [MACH__DEAD_DEC] = "dead_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_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_EQ_TOL] = "eq_tol",
  [MACH_NEQ_TOL] = "neq_tol",
  [MACH_NOP] = "nop",
  /* Mcode-derived */
  [MACH_CONCAT] = "concat",
  [MACH_EQ_INT] = "eq_int",
  [MACH_NE_INT] = "ne_int",
  [MACH_LT_INT] = "lt_int",
  [MACH_LE_INT] = "le_int",
  [MACH_GT_INT] = "gt_int",
  [MACH_GE_INT] = "ge_int",
  [MACH_EQ_FLOAT] = "eq_float",
  [MACH_NE_FLOAT] = "ne_float",
  [MACH_LT_FLOAT] = "lt_float",
  [MACH_LE_FLOAT] = "le_float",
  [MACH_GT_FLOAT] = "gt_float",
  [MACH_GE_FLOAT] = "ge_float",
  [MACH_EQ_TEXT] = "eq_text",
  [MACH_NE_TEXT] = "ne_text",
  [MACH_LT_TEXT] = "lt_text",
  [MACH_LE_TEXT] = "le_text",
  [MACH_GT_TEXT] = "gt_text",
  [MACH_GE_TEXT] = "ge_text",
  [MACH_EQ_BOOL] = "eq_bool",
  [MACH_NE_BOOL] = "ne_bool",
  [MACH_IS_IDENTICAL] = "is_identical",
  [MACH_IS_INT] = "is_int",
  [MACH_IS_NUM] = "is_num",
  [MACH_IS_TEXT] = "is_text",
  [MACH_IS_BOOL] = "is_bool",
  [MACH_IS_NULL] = "is_null",
  [MACH_NOT] = "not",
  [MACH_AND] = "and",
  [MACH_OR] = "or",
  [MACH_BITNOT] = "bitnot",
  [MACH_BITAND] = "bitand",
  [MACH_BITOR] = "bitor",
  [MACH_BITXOR] = "bitxor",
  [MACH_LOAD_FIELD] = "load_field",
  [MACH_STORE_FIELD] = "store_field",
  [MACH_LOAD_INDEX] = "load_index",
  [MACH_STORE_INDEX] = "store_index",
  [MACH_LOAD_DYNAMIC] = "load_dynamic",
  [MACH_STORE_DYNAMIC] = "store_dynamic",
  [MACH_NEWRECORD] = "newrecord",
  [MACH_FRAME] = "frame",
  [MACH_SETARG] = "setarg",
  [MACH_INVOKE] = "invoke",
  [MACH_GOFRAME] = "goframe",
  [MACH_GOINVOKE] = "goinvoke",
  [MACH_JMPNOTNULL] = "jmpnotnull",
  [MACH_DISRUPT] = "disrupt",
  [MACH_IN] = "in",
  /* Extended type checks */
  [MACH_IS_ARRAY] = "is_array",
  [MACH_IS_FUNC] = "is_func",
  [MACH_IS_RECORD] = "is_record",
  [MACH_IS_STONE] = "is_stone",
  [MACH_LENGTH] = "length",
  [MACH_IS_PROXY] = "is_proxy",
};

/* 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;


/* Unified frame struct — used by the register VM and closures.
   All fields are JSValues so the GC can scan them uniformly. */
typedef struct JSFrame {
  objhdr_t header;           /* OBJ_FRAME, cap56 = slot count */
  JSValue function;          /* JSFunction, function object being invoked */
  JSValue caller;            /* JSFrame, the frame that called this one */
  JSValue address;           /* return PC stored as JS_NewInt32 */
  JSValue slots[];           /* [this][args][captured][locals][temps] */
} JSFrame;

typedef JSFrame JSFrameRegister;

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;
}

/* Resolve a forward pointer in-place.  After rec_resize the old record
   gets a forward header; any JSValue slot still pointing at it must be
   updated to follow the chain to the live copy. */
static inline void mach_resolve_forward(JSValue *slot) {
  if (JS_IsPtr(*slot)) {
    objhdr_t *oh = (objhdr_t *)JS_VALUE_GET_PTR(*slot);
    if (objhdr_type(*oh) == OBJ_FORWARD) {
      do {
        objhdr_t *next = (objhdr_t *)objhdr_fwd_ptr(*oh);
        if (!next) break;
        oh = next;
      } while (objhdr_type(*oh) == OBJ_FORWARD);
      *slot = JS_MKPTR(oh);
    }
  }
}

/* Inline type checks — use these in the VM dispatch loop to avoid
   function call overhead. The public API (JS_IsArray etc. in quickjs.h)
   remains non-inline for external callers; those wrappers live in runtime.c. */

static inline JS_BOOL mist_is_gc_object(JSValue v) {
  return JS_IsPtr(v);
}

static inline JS_BOOL mist_is_array(JSValue v) {
  return mist_is_gc_object(v) && objhdr_type(*chase(v)) == OBJ_ARRAY;
}

static inline JS_BOOL mist_is_record(JSValue v) {
  return mist_is_gc_object(v) && objhdr_type(*chase(v)) == OBJ_RECORD;
}

static inline JS_BOOL mist_is_function(JSValue v) {
  return mist_is_gc_object(v) && objhdr_type(*chase(v)) == OBJ_FUNCTION;
}

static inline JS_BOOL mist_is_text(JSValue v) {
  return MIST_IsImmediateASCII(v)
    || (mist_is_gc_object(v) && objhdr_type(*chase(v)) == OBJ_TEXT);
}

static inline JS_BOOL mist_is_blob(JSValue v) {
  return mist_is_gc_object(v) && objhdr_type(*chase(v)) == OBJ_BLOB;
}

static inline JS_BOOL mist_is_stone(JSValue v) {
  return !mist_is_gc_object(v) || objhdr_s(*chase(v));
}

/* 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 — inline bit data on the GC heap.
   cap56 = capacity in bits, S bit = stone (immutable). */
typedef struct JSBlob {
  objhdr_t mist_hdr;  /* type=OBJ_BLOB, cap56=capacity_bits, S=stone */
  word_t length;       /* used bits */
  word_t bits[];       /* inline bit data, ceil(cap56/64) words */
} JSBlob;

typedef struct JSText {
  objhdr_t hdr;                           /* mist header — cap56 = allocated capacity */
  word_t length;                          /* character count (always) */
  word_t hash;                            /* cached hash (stoned text only) */
  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;
  JSValue proto;                  /* prototype as JSValue (JS_NULL if none) */
  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) {
  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) */

/* Auto-rooted C call argv — GC updates values in-place */
typedef struct CCallRoot {
  JSValue *argv;          /* points to C-stack-local array */
  int argc;
  struct CCallRoot *prev; /* stack for nesting (C -> JS -> C -> ...) */
} CCallRoot;

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 */
  int gc_poor_streak;        /* consecutive poor-recovery GC cycles */

  /* GC stats (lightweight, always on) */
  uint64_t gc_count;         /* number of GC cycles */
  uint64_t gc_bytes_copied;  /* total bytes copied across all GCs */

  /* Constant text pool — compilation constants */
  uint8_t *ct_base;              /* pool base */
  uint8_t *ct_free;              /* pool bump pointer */
  uint8_t *ct_end;               /* pool end */

  /* Constant text intern table */
  void *ct_pages;                /* page list for large allocations */
  uint32_t *ct_hash;             /* hash table (slot -> id) */
  JSText **ct_array;             /* array of JSText pointers indexed by id */
  uint32_t ct_size;              /* hash table size (power of 2) */
  uint32_t ct_count;             /* number of interned texts */
  uint32_t ct_resize_threshold;  /* 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) */
  JSLocalRef *top_local_ref; /* for JS_LOCAL macro - GC updates C locals through pointers */
  CCallRoot *c_call_root; /* stack of auto-rooted C call argv arrays */

  int class_count; /* size of class_array and class_proto */
  JSClass *class_array;
  JSValue *class_proto;
  JSValue regexp_ctor;
  JSValue throw_type_error;

  JSValue global_obj;     /* global object (immutable intrinsics) */

  uint64_t random_state;

  /* 0 = normal, 1 = suspend (fast timer), 2 = kill (slow timer) */
  _Atomic int pause_flag;

  /* 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;

  /* 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 */

  /* VM suspend/resume state */
  int suspended;                  /* 1 = VM was suspended (not exception) */
  JSGCRef suspended_frame_ref;    /* GC-rooted saved frame for resume */
  uint32_t suspended_pc;          /* saved PC for resume */
  int vm_call_depth;              /* 0 = pure bytecode, >0 = C frames on stack */
  size_t heap_memory_limit;       /* 0 = no limit, else max heap bytes */

  JSValue current_exception;

  JS_BOOL disruption_reported;

  /* Log routing — set by $_set_log intrinsic */
  JSLogCallback log_callback;
  JSValue log_callback_js;  /* the ƿit log function (rooted) */

  /* Actor identity key — used by wota/nota PRIVATE serialization */
  JSValue actor_sym;

  /* Stack overflow protection */
  // todo: want this, but should be a simple increment/decrement counter while frames are pushed
  size_t stack_depth;
  size_t stack_limit;
};

/* ============================================================
   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;
}


/* ============================================================
   Constant Text Pool Functions
   ============================================================ */

/* Constant text page for large allocations */
typedef struct CTPage {
  struct CTPage *next;
  size_t size;
  uint8_t data[];
} CTPage;

/* Initial constant text table size */
#define CT_INITIAL_SIZE 256

/* Allocate from constant text pool */

/* Resize the constant 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 constant text pool memory */
static inline int is_ct_ptr (JSContext *ctx, void *ptr) {
  uint8_t *p = (uint8_t *)ptr;
  if (p >= ctx->ct_base && p < ctx->ct_end) return 1;
  /* Also check overflow pages */
  CTPage *page = (CTPage *)ctx->ct_pages;
  while (page) {
    if (p >= page->data && p < page->data + page->size) return 1;
    page = page->next;
  }
  return 0;
}

#ifdef HEAP_CHECK
static inline objhdr_t *heap_check_chase(JSContext *ctx, JSValue v) {
  objhdr_t *oh = chase(v);
  uint8_t *p = (uint8_t *)oh;
  if (!((p >= ctx->heap_base && p < ctx->heap_free) ||
        (p >= ctx->ct_base && p < ctx->ct_end)))
    heap_check_fail(oh, ctx);
  return oh;
}
#endif

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

/* Get prototype from object (works for both JSRecord and JSRecord since they
 * share layout) */
#define JS_OBJ_GET_PROTO(p) (JS_IsNull(((JSRecord *)(p))->proto) ? NULL : (JSRecord *)JS_VALUE_GET_PTR(((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 */
} 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 {
      JSCodeRegister *code;     /* compiled register code (off-heap) */
      JSValue env_record;       /* stone record, module environment */
      JSValue outer_frame;      /* JSFrame JSValue, for closures */
    } reg;
  } u;
} JSFunction;

/* ============================================================
   Context-Neutral Module Format (Phase 2+)
   Struct definitions are in quickjs.h
   ============================================================ */

#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

JSValue js_call_c_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);
int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop);
JSValue __attribute__ ((format (printf, 2, 3)))
JS_RaiseDisrupt (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 declaration - helper to set cap in objhdr */
static inline objhdr_t objhdr_set_cap56 (objhdr_t h, uint64_t cap);

/* 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_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);
JSValue JS_GetStack(JSContext *ctx);
JSValue JS_RaiseOOM (JSContext *ctx);


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



/* === 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;
}

static inline __exception int js_poll_interrupts (JSContext *ctx) {
  if (unlikely (atomic_load_explicit (&ctx->pause_flag, memory_order_relaxed) >= 2)) {
    JS_RaiseDisrupt (ctx, "interrupted");
    return -1;
  }
  return 0;
}

/* === PPretext (parser pretext, system-malloc, used by cell_js.c parser) === */
typedef struct PPretext {
  uint32_t *data;
  int len;
  int cap;
} PPretext;

extern JSClassID js_class_id_alloc;

/* === Forward declarations for functions split across modules === */

/* runtime.c — line/column, GC, and VM dispatch */
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);

/* runtime.c exports */
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);
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 *ct_alloc (JSContext *ctx, size_t bytes, size_t align);
void ct_free_all (JSContext *ctx);
int ct_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);

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

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

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;
}


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


#endif /* QUICKJS_INTERNAL_H */
