diff --git a/internal/nota.c b/internal/nota.c index 8f240293..93a956b3 100755 --- a/internal/nota.c +++ b/internal/nota.c @@ -178,7 +178,7 @@ static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val, JSValueC break; } - if (JS_IsArray(ctx, replaced)) { + if (JS_IsArray(replaced)) { if (nota_stack_has(enc, replaced)) { enc->cycle = 1; break; diff --git a/playdate/json_playdate.c b/playdate/json_playdate.c index 8ddf2e81..8e36eb39 100644 --- a/playdate/json_playdate.c +++ b/playdate/json_playdate.c @@ -150,7 +150,7 @@ static void encode_js_value(json_encoder *enc, JSContext *js, JSValue val) { const char *str = JS_ToCStringLen(js, &len, val); enc->writeString(enc, str, len); JS_FreeCString(js, str); - } else if (JS_IsArray(js, val)) { + } else if (JS_IsArray(val)) { encode_js_array(enc, js, val); } else if (JS_IsObject(val)) { encode_js_object(enc, js, val); diff --git a/source/qjs_wota.c b/source/qjs_wota.c index 927c5055..aaea1d80 100644 --- a/source/qjs_wota.c +++ b/source/qjs_wota.c @@ -171,7 +171,7 @@ static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueC } break; } - if (JS_IsArray(ctx, replaced)) { + if (JS_IsArray(replaced)) { if (wota_stack_has(enc, replaced)) { enc->cycle = 1; break; diff --git a/source/quickjs.c b/source/quickjs.c index 059e1e83..2f9c6d25 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -110,15 +110,37 @@ typedef struct JSRecord JSRecord; typedef struct JSFunction JSFunction; typedef struct JSFrame JSFrame; typedef struct JSCode JSCode; +typedef struct JSVarRef JSVarRef; + +/* JSGCObjectHeader - legacy header for ref counting compatibility. + With copying GC this is mostly unused but kept for layout compatibility. + All heap objects have this at offset 0, followed by mist objhdr_t at offset 8. */ +struct JSGCObjectHeader { + int ref_count; /* legacy ref count - unused with copying GC */ + uint8_t gc_obj_type; /* legacy type enum */ + uint8_t mark; /* GC mark flag */ + uint8_t pad[2]; + struct list_head link; /* link in gc_obj_list - unused with copying GC */ +}; + +/* JSVarRef - variable reference for closures */ +struct JSVarRef { + struct JSGCObjectHeader header; + uint8_t is_detached; /* TRUE if pvalue points to value field */ + uint8_t pad[7]; + JSValue *pvalue; /* pointer to the value (stack or &value) */ + JSValue value; /* captured value when detached */ + struct list_head var_ref_link; /* link in frame's var_ref_list */ +}; static inline JS_BOOL JS_VALUE_IS_TEXT (JSValue v) { int tag = JS_VALUE_GET_TAG (v); - return tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM; + 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_FLOAT64; + return tag == JS_TAG_INT || tag == JS_TAG_SHORT_FLOAT; } /* JS_KEY_* macros: JSValue immediate ASCII strings for common property names. @@ -199,60 +221,15 @@ static inline JS_BOOL JS_VALUE_IS_NUMBER (JSValue v) { #define KEY_GET_STR_BUF_SIZE 256 -int JS_IsPretext (JSValue v) { - if (!JS_IsText (v)) return 0; - JSText *text = (JSText *)JS_VALUE_GET_PTR (v); - return !objhdr_s (text->hdr); -} +/* JS_IsPretext, JS_KeyGetStr, JS_PushGCRef, JS_PopGCRef, JS_AddGCRef, JS_DeleteGCRef + are defined after JSContext (they need its fields) */ -/* 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; -} +/* Forward declarations for memory functions (now declared in quickjs.h) */ +/* js_realloc2 is internal only */ +static void *js_realloc2 (JSContext *ctx, void **pptr, size_t size, size_t *pslack); -JSValue *JS_PushGCRef (JSContext *ctx, JSGCRef *ref) { - ref->prev = ctx->top_gc_ref; - ctx->top_gc_ref = ref; - ref->val = JS_NULL; - return &ref->val; -} - -JSValue JS_PopGCRef (JSContext *ctx, JSGCRef *ref) { - ctx->top_gc_ref = ref->prev; - return ref->val; -} - -JSValue *JS_AddGCRef (JSContext *ctx, JSGCRef *ref) { - ref->prev = ctx->last_gc_ref; - ctx->last_gc_ref = ref; - ref->val = JS_NULL; - return &ref->val; -} - -void JS_DeleteGCRef (JSContext *ctx, JSGCRef *ref) { - JSGCRef **pref, *ref1; - pref = &ctx->last_gc_ref; - for (;;) { - ref1 = *pref; - if (ref1 == NULL) - abort (); - if (ref1 == ref) { - *pref = ref1->prev; - break; - } - pref = &ref1->prev; - } -} +/* Forward declaration for string_get */ +static inline int string_get (const JSText *p, int idx); #undef JS_PUSH_VALUE #undef JS_POP_VALUE @@ -314,6 +291,29 @@ struct JSFunctionBytecode; #define JS_VALUE_GET_FUNCTION(v) ((JSFunction *)JS_VALUE_GET_PTR (v)) #define JS_VALUE_GET_FRAME(v) ((JSFrame *)JS_VALUE_GET_PTR (v)) #define JS_VALUE_GET_CODE(v) ((JSFunctionBytecode *)JS_VALUE_GET_PTR (v)) +#define JS_VALUE_GET_STRING(v) ((JSText *)JS_VALUE_GET_PTR (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); +} + +/* js_array_set_cap - defined after JSArray struct */ + +/* js_malloc_usable_size - with bump allocator, we don't track per-allocation size */ +static inline size_t js_malloc_usable_size (JSContext *ctx, void *ptr) { + (void)ctx; (void)ptr; + return 0; /* unknown - bump allocator doesn't track individual allocations */ +} typedef enum { JS_GC_PHASE_NONE, @@ -353,6 +353,8 @@ static void buddy_free (BuddyAllocator *b, void *ptr, size_t size); static void buddy_destroy (BuddyAllocator *b); struct JSRuntime { + JSMallocFunctions mf; + JSMallocState malloc_state; const char *rt_info; /* Buddy allocator for actor memory blocks */ @@ -363,6 +365,39 @@ struct JSRuntime { /* see JS_SetStripInfo() */ uint8_t strip_flags; + + /* Legacy GC fields - mostly unused with copying GC */ + struct list_head context_list; + struct list_head gc_obj_list; /* list of JSGCObjectHeader */ + struct list_head gc_zero_ref_count_list; + JSGCPhaseEnum gc_phase; + size_t malloc_gc_threshold; + + /* Stack overflow protection */ + size_t stack_size; + const uint8_t *stack_top; + const uint8_t *stack_limit; + + /* Current exception (for error propagation) */ + JSValue current_exception; + struct JSStackFrame *current_stack_frame; + + /* User data */ + void *user_opaque; + + /* Record key allocator */ + uint32_t rec_key_next; + + /* Interrupt handler for checking execution limits */ + JSInterruptHandler *interrupt_handler; + void *interrupt_opaque; + + /* Primary context (for single-context runtimes) */ + JSContext *js; + +#ifdef DUMP_LEAKS + struct list_head string_list; +#endif }; struct JSClass { @@ -463,41 +498,58 @@ static inline objhdr_t objhdr_make (uint64_t cap56, uint8_t type, bool r, bool a return h; } -/* Intrinsic array type - tagged as JS_TAG_OBJECT with mist_hdr type OBJ_ARRAY - */ +/* Intrinsic array type - tagged as JS_TAG_OBJECT with mist_hdr type OBJ_ARRAY */ typedef struct JSArray { - objhdr_t hdr; - word_t length; /* current length */ - JSValue values[]; /* array of values */ + objhdr_t mist_hdr; /* mist header at offset 8 */ + word_t len; /* current length */ + JSValue *values; /* pointer to values array (inline after struct) */ } JSArray; +/* js_array_set_cap - helper to set array capacity in mist header */ +static inline void js_array_set_cap (JSArray *arr, uint32_t cap) { + arr->mist_hdr = objhdr_set_cap56 (arr->mist_hdr, cap); +} + +/* JSBlob - binary data per memory.md */ typedef struct JSBlob { - objhdr_t hdr; + objhdr_t mist_hdr; word_t length; uint8_t bits[]; } JSBlob; typedef struct JSText { - objhdr_t hdr; - word_t length; - word_t packed[]; // two chars per packed + 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 value; + JSValue val; /* use 'val' to match existing code */ } slot; +/* Compatibility alias - old code uses JSRecordEntry */ +typedef slot JSRecordEntry; + typedef struct JSRecord { - objhdr_t hdr; + objhdr_t mist_hdr; struct JSRecord *proto; - word_t length; - slot slots[]; // slot[0] is never used - // slot[0].key first 32 bits = class id - // slot[0].key last 32 bits = obj id if used as record - // slot[0].value = opaque c object from class id + 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) + /*typedef struct JSFunction { objdr_t hdr; JSCode *code; @@ -690,16 +742,30 @@ static JSValue js_key_new_len (JSContext *ctx, const char *str, size_t len); #define JS_INTERRUPT_COUNTER_INIT 10000 struct JSContext { + struct JSGCObjectHeader header; /* GC header for context itself */ JSRuntime *rt; struct list_head link; /* Actor memory block (bump allocation) */ - void *heap_base; /* start of current block */ - void *heap_free; /* bump pointer */ - void *heap_end; /* end of block */ + 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; @@ -749,6 +815,70 @@ struct JSContext { void *interrupt_opaque; }; +/* ============================================================ + Functions that need JSContext definition + ============================================================ */ + +int JS_IsPretext (JSValue v) { + if (!JS_IsText (v)) return 0; + JSText *text = (JSText *)JS_VALUE_GET_PTR (v); + return !objhdr_s (text->hdr); +} + +/* 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; +} + +JSValue *JS_PushGCRef (JSContext *ctx, JSGCRef *ref) { + ref->prev = ctx->top_gc_ref; + ctx->top_gc_ref = ref; + ref->val = JS_NULL; + return &ref->val; +} + +JSValue JS_PopGCRef (JSContext *ctx, JSGCRef *ref) { + ctx->top_gc_ref = ref->prev; + return ref->val; +} + +JSValue *JS_AddGCRef (JSContext *ctx, JSGCRef *ref) { + ref->prev = ctx->last_gc_ref; + ctx->last_gc_ref = ref; + ref->val = JS_NULL; + return &ref->val; +} + +void JS_DeleteGCRef (JSContext *ctx, JSGCRef *ref) { + JSGCRef **pref, *ref1; + pref = &ctx->last_gc_ref; + for (;;) { + ref1 = *pref; + if (ref1 == NULL) + abort (); + if (ref1 == ref) { + *pref = ref1->prev; + break; + } + pref = &ref1->prev; + } +} + +/* 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 */ static JSValue intern_text_to_value (JSContext *ctx, const uint32_t *utf32, uint32_t len) { /* Pack UTF-32 for hashing and comparison */ @@ -787,9 +917,7 @@ static JSValue intern_text_to_value (JSContext *ctx, const uint32_t *utf32, uint JSText *text = st_alloc (ctx, text_size, 8); if (!text) return JS_NULL; /* OOM */ - /* Initialize the text (with unified layout: hdr at offset 8) */ - text->_dummy_header.ref_count = 1; /* dummy, not used for GC */ - text->_pad = 0; + /* Initialize the text */ text->hdr = objhdr_make (len, OBJ_TEXT, false, false, false, true); /* s=1 for stoned */ text->length = hash; /* Store hash in length field for stoned text */ memcpy (text->packed, packed, word_count * sizeof (uint64_t)); @@ -893,17 +1021,7 @@ typedef union JSFloat64Union { uint32_t u32[2]; } JSFloat64Union; -JS_BOOL JS_IsText (JSValue v) { - /* Check tag - immediate string? */ - if (MIST_IsImmediateASCII (v)) return 1; - - /* Check pointer type */ - if (JS_IsPtr (v)) { - objhdr_t *ptr = JS_VALUE_GET_PTR (v); - return objhdr_type (*ptr) == OBJ_TEXT; - } - return 0; -} +/* JS_IsText is defined in quickjs.h */ /* Helper to get array capacity from mist_hdr */ static inline uint32_t js_array_cap (JSArray *arr) { @@ -941,26 +1059,27 @@ static inline void rec_tab_init (JSRecordEntry *tab, uint32_t mask) { // can check if key by checking for 0 here static uint64_t js_key_hash (JSValue key) { - if (MIST_IsImmediateASCII (val)) { - uint64_t h = fash64_hash_one (val); + if (MIST_IsImmediateASCII (key)) { + uint64_t h = fash64_hash_one (key); return h ? h : 1; } if (!JS_IsPtr (key)) return 0; - objhdr_t *ptr = JS_VALUE_GET_PTR (key); - - uint8_t type = objhdr_type (*ptr); + void *ptr = JS_VALUE_GET_PTR (key); + objhdr_t hdr = *(objhdr_t *)ptr; + uint8_t type = objhdr_type (hdr); if (type == OBJ_TEXT) { /* For JSText (stoned strings), use get_text_hash */ - JSText *text = ptr; + JSText *text = (JSText *)ptr; return get_text_hash (text); } if (type == OBJ_RECORD) { - JSRecord *rec = ptr; - if (rec->rec_id == 0) return 0; - return fash64_hash_one (rec->rec_id); + JSRecord *rec = (JSRecord *)ptr; + uint32_t rec_id = REC_GET_REC_ID(rec); + if (rec_id == 0) return 0; + return fash64_hash_one (rec_id); } return 0; @@ -985,9 +1104,11 @@ static JS_BOOL js_key_equal (JSValue a, JSValue b) { if (!JS_IsPtr (a) || !JS_IsPtr (b)) return FALSE; - objhdr_t *ha = JS_VALUE_GET_PTR (a); - objhdr_t *hb = JS_VALUE_GET_PTR (b); - uint8_t type_a = objhdr_type (*ha); + void *pa = JS_VALUE_GET_PTR (a); + void *pb = JS_VALUE_GET_PTR (b); + objhdr_t ha = *(objhdr_t *)pa; + objhdr_t hb = *(objhdr_t *)pb; + uint8_t type_a = objhdr_type (ha); uint8_t type_b = objhdr_type (hb); if (type_a != type_b) return FALSE; @@ -1038,7 +1159,7 @@ static int rec_find_slot (JSRecord *rec, JSValue k) { uint64_t first_tomb = 0; for (uint64_t i = 0; i <= mask; i++) { - JSValue slot_key = rec->tab[slot].key; + JSValue slot_key = rec->slots[slot].key; if (rec_key_is_empty (slot_key)) { /* Empty slot - key not found */ @@ -1067,7 +1188,7 @@ static JSValue rec_get_own (JSContext *ctx, JSRecord *rec, JSValue k) { if (rec_key_is_empty (k) || rec_key_is_tomb (k)) return JS_NULL; int slot = rec_find_slot (rec, k); - if (slot > 0) { return JS_DupValue (ctx, rec->tab[slot].val); } + if (slot > 0) { return JS_DupValue (ctx, rec->slots[slot].val); } return JS_NULL; } @@ -1079,46 +1200,20 @@ static JSValue rec_get (JSContext *ctx, JSRecord *rec, JSValue k) { JSRecord *p = rec; while (p) { int slot = rec_find_slot (p, k); - if (slot > 0) { return JS_DupValue (ctx, p->tab[slot].val); } + if (slot > 0) { return JS_DupValue (ctx, p->slots[slot].val); } p = p->proto; } return JS_NULL; } -/* Resize record table when load factor too high */ +/* Resize record - NOT YET IMPLEMENTED for inline slots. + With inline slots (flexible array member), resizing requires allocating + a completely new record and copying. For now, this always fails. + TODO: Implement proper record growth with copying GC. */ static int rec_resize (JSContext *ctx, JSRecord *rec, uint64_t new_mask) { - uint64_t old_mask = objhdr_cap56 (rec->mist_hdr); - uint64_t new_size = new_mask + 1; - - JSRecordEntry *new_tab = js_mallocz (ctx, sizeof (JSRecordEntry) * new_size); - if (!new_tab) return -1; - - rec_tab_init (new_tab, new_mask); - - /* Rehash all entries */ - for (uint32_t i = 1; i <= old_mask; i++) { - JSValue k = rec->tab[i].key; - if (!rec_key_is_empty (k) && !rec_key_is_tomb (k)) { - uint64_t h64 = js_key_hash (k); - uint32_t slot = ((uint32_t)h64 & new_mask); - if (slot == 0) slot = 1; - - while (!rec_key_is_empty (new_tab[slot].key)) { - slot = (slot + 1) & new_mask; - if (slot == 0) slot = 1; - } - - new_tab[slot].key = k; - new_tab[slot].val = rec->tab[i].val; /* move value, no dup needed */ - } - } - - js_free (ctx, rec->tab); - rec->tab = new_tab; - rec->mist_hdr = objhdr_make (new_mask, OBJ_RECORD, false, false, false, objhdr_s (rec->mist_hdr)); - rec->tombs = 0; - - return 0; + (void)ctx; (void)rec; (void)new_mask; + /* Cannot resize inline slots - need to allocate new record */ + return -1; } /* Set own property on record, returns 0 on success, -1 on error */ @@ -1131,14 +1226,14 @@ static int rec_set_own (JSContext *ctx, JSRecord *rec, JSValue k, JSValue val) { if (slot > 0) { /* Existing key - replace value */ - rec->tab[slot].val = val; + rec->slots[slot].val = val; return 0; } - /* New key - check if resize needed */ + /* New key - check if resize needed (75% load factor) */ uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); - if ((rec->len + rec->tombs + 1) * 4 > mask * 3) { - /* Over 75% load factor - resize */ + if ((rec->len + 1) * 4 > mask * 3) { + /* Over 75% load factor - try resize (may fail with inline slots) */ uint32_t new_mask = (mask + 1) * 2 - 1; if (rec_resize (ctx, rec, new_mask) < 0) { JS_FreeValue (ctx, val); @@ -1150,42 +1245,57 @@ static int rec_set_own (JSContext *ctx, JSRecord *rec, JSValue k, JSValue val) { /* Insert at -slot */ int insert_slot = -slot; - if (rec_key_is_tomb (rec->tab[insert_slot].key)) rec->tombs--; - rec->tab[insert_slot].key = JS_DupValue (ctx, k); - rec->tab[insert_slot].val = val; + rec->slots[insert_slot].key = JS_DupValue (ctx, k); + rec->slots[insert_slot].val = val; rec->len++; return 0; } -/* Allocate a new record with specified class_id (default JS_CLASS_OBJECT) - Uses bump allocation from context heap. Tab is inline (flexible array member). */ +/* 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. */ static JSRecord *js_new_record_class (JSContext *ctx, uint32_t initial_mask, JSClassID class_id) { JSRuntime *rt = ctx->rt; if (initial_mask == 0) initial_mask = JS_RECORD_INITIAL_MASK; - /* Allocate record + inline tab in one bump allocation */ - size_t tab_size = sizeof (JSRecordEntry) * (initial_mask + 1); - size_t total_size = sizeof (JSRecord) + tab_size; + /* Allocate record + inline slots in one bump allocation */ + size_t slots_size = sizeof (slot) * (initial_mask + 1); + size_t total_size = sizeof (JSRecord) + slots_size; JSRecord *rec = js_malloc (ctx, total_size); if (!rec) return NULL; - rec->hdr = objhdr_make (initial_mask, OBJ_RECORD, false, false, false, false); - rec->shape = NULL; + rec->mist_hdr = objhdr_make (initial_mask, OBJ_RECORD, false, false, false, false); rec->proto = NULL; rec->len = 0; - rec->tombs = 0; - rec->rec_id = ++rt->rec_key_next; - rec->class_id = class_id; - rec->free_mark = 0; - rec->tmp_mark = 0; - rec->u.opaque = NULL; - /* Tab is inline right after the struct */ - rec->tab = (JSRecordEntry *)((uint8_t *)rec + sizeof (JSRecord)); - rec_tab_init (rec->tab, initial_mask); + /* Initialize all slots to empty (JS_NULL) */ + for (uint32_t i = 0; i <= initial_mask; i++) { + rec->slots[i].key = JS_NULL; + rec->slots[i].val = JS_NULL; + } + + /* slots[0] is reserved: store class_id (low 32) and rec_id (high 32) in key */ + uint32_t rec_id = ++rt->rec_key_next; + rec->slots[0].key = (JSValue)class_id | ((JSValue)rec_id << 32); + rec->slots[0].val = 0; /* opaque pointer, initially NULL */ return rec; } @@ -1383,11 +1493,11 @@ static JSValue JS_EvalObject (JSContext *ctx, JSValue this_obj, JSValue val, int int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop); JSValue __attribute__ ((format (printf, 2, 3))) JS_ThrowInternalError (JSContext *ctx, const char *fmt, ...); -static __maybe_unused void JS_DumpString (JSText *text); -static __maybe_unused void JS_DumpObjectHeader (JSContext *ctx); -static __maybe_unused void JS_DumpObject (JSContext *ctx, JSRecord *rec); -static __maybe_unused void JS_DumpGCObject (objhdr_t *objhdr); -static __maybe_unused void JS_DumpValue (JSValue val); +static __maybe_unused void JS_DumpString (JSRuntime *rt, const JSText *text); +static __maybe_unused void JS_DumpObjectHeader (JSRuntime *rt); +static __maybe_unused void JS_DumpObject (JSRuntime *rt, JSRecord *rec); +static __maybe_unused void JS_DumpGCObject (JSRuntime *rt, JSGCObjectHeader *p); +static __maybe_unused void JS_DumpValue (JSContext *ctx, const char *str, JSValue val); static void js_dump_value_write (void *opaque, const char *buf, size_t len); static JSValue js_function_apply (JSContext *ctx, JSValue this_val, int argc, JSValue *argv, int magic); static void js_regexp_finalizer (JSRuntime *rt, JSValue val); @@ -1399,6 +1509,17 @@ static void mark_function_children_decref (JSRuntime *rt, JSFunction *func); static void gc_decref_child (JSRuntime *rt, JSGCObjectHeader *p); +/* 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 */ +#define JS_VALUE_GET_STRING(v) ((JSText *)JS_VALUE_GET_PTR(v)) + +/* JS_ThrowMemoryError is an alias for JS_ThrowOutOfMemory */ +#define JS_ThrowMemoryError(ctx) JS_ThrowOutOfMemory(ctx) + /* JS_SetPropertyInternal: same as JS_SetProperty but doesn't check stone. Internal use only. */ int JS_SetPropertyInternal (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val) { @@ -1448,14 +1569,21 @@ static int JS_GetOwnPropertyInternal (JSContext *ctx, JSValue *desc, JSRecord *p, JSValue prop); -static void JS_AddIntrinsicBasicObjects (JSContext *ctx); +/* JS_AddIntrinsicBasicObjects is declared in quickjs.h */ static __exception int js_get_length32 (JSContext *ctx, uint32_t *pres, JSValue obj); static __exception int js_get_length64 (JSContext *ctx, int64_t *pres, JSValue obj); static void free_arg_list (JSContext *ctx, JSValue *tab, uint32_t len); static JSValue *build_arg_list (JSContext *ctx, uint32_t *plen, JSValue array_arg); static BOOL js_get_fast_array (JSContext *ctx, JSValue obj, JSValue **arrpp, uint32_t *countp); static JSValue js_c_function_data_call (JSContext *ctx, JSValue func_obj, JSValue this_val, int argc, JSValue *argv); -static void remove_gc_object (JSGCObjectHeader *h); +/* With copying GC, these are no-ops since we don't track objects in a list */ +static void add_gc_object (JSRuntime *rt, struct JSGCObjectHeader *h, int type) { + (void)rt; (void)h; + h->gc_obj_type = type; +} +static void remove_gc_object (JSGCObjectHeader *h) { + (void)h; +} static void JS_RunGCInternal (JSRuntime *rt); static JSValue js_regexp_toString (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); @@ -1516,7 +1644,9 @@ void *js_mallocz (JSContext *ctx, size_t size) { } void js_free (JSContext *ctx, void *ptr) { - assert (0, "bump allocator doesn't free individual allocations"); + /* Bump allocator doesn't free individual allocations - GC handles it */ + (void)ctx; + (void)ptr; } /* Throw out of memory in case of error */ @@ -1590,7 +1720,7 @@ 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->u[word_idx] = (p->u[word_idx] & ~mask) | ((uint64_t)c << shift); + p->packed[word_idx] = (p->packed[word_idx] & ~mask) | ((uint64_t)c << shift); } /* Get character from any string value (immediate ASCII or JSText) */ @@ -1917,7 +2047,7 @@ static JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t **to_free, uint /* Install forwarding pointer in old location */ *hdr_ptr = objhdr_make ((uint64_t)(uintptr_t)new_ptr, OBJ_FORWARD, 0, 0, 0, 0); - return JS_MKPTR (JS_TAG_PTR, new_ptr); + return JS_MKPTR (new_ptr); } /* Scan a copied object and update its internal references */ @@ -1937,17 +2067,17 @@ static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t **to_free, uint8_ JSRecord *rec = (JSRecord *)ptr; /* Copy prototype */ if (rec->proto) { - JSValue proto_val = JS_MKPTR (JS_TAG_PTR, rec->proto); + JSValue proto_val = JS_MKPTR (rec->proto); proto_val = gc_copy_value (ctx, proto_val, to_free, to_end); rec->proto = (JSRecord *)JS_VALUE_GET_PTR (proto_val); } /* Copy table entries */ uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); for (uint32_t i = 0; i <= mask; i++) { - JSValue k = rec->tab[i].key; + JSValue k = rec->slots[i].key; if (!rec_key_is_empty (k) && !rec_key_is_tomb (k)) { - rec->tab[i].key = gc_copy_value (ctx, k, to_free, to_end); - rec->tab[i].val = gc_copy_value (ctx, rec->tab[i].val, to_free, to_end); + rec->slots[i].key = gc_copy_value (ctx, k, to_free, to_end); + rec->slots[i].val = gc_copy_value (ctx, rec->slots[i].val, to_free, to_end); } } break; @@ -2554,7 +2684,7 @@ JSClassID JS_GetClassID (JSValue v) { JSRecord *rec; if (!JS_IsRecord (v)) return JS_INVALID_CLASS_ID; rec = JS_VALUE_GET_RECORD (v); - return rec->class_id; + return REC_GET_CLASS_ID(rec); } BOOL JS_IsRegisteredClass (JSRuntime *rt, JSClassID class_id) { @@ -2817,9 +2947,9 @@ static JSValue pretext_end (JSContext *ctx, JSText *s) { list_add_tail (&s->link, &ctx->rt->string_list); #endif /* Set final length in capacity field and clear length for hash storage */ - objhdr_set_cap56 (&s->hdr, len); + s->hdr = objhdr_set_cap56 (s->hdr, len); s->length = 0; - obj_set_stone (&s->hdr); + s->hdr = objhdr_set_s (s->hdr, true); /* mark as stone */ return JS_MKPTR (s); } @@ -3180,11 +3310,8 @@ JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len) { arr = js_malloc (ctx, total_size); if (!arr) return JS_EXCEPTION; - arr->header.ref_count = 1; - arr->header.gc_obj_type = JS_GC_OBJ_TYPE_ARRAY; - arr->len = len; arr->mist_hdr = objhdr_make (cap, OBJ_ARRAY, false, false, false, false); - arr->free_mark = 0; + arr->len = len; /* Values are inline right after the struct */ arr->values = (JSValue *)((uint8_t *)arr + sizeof (JSArray)); @@ -3194,7 +3321,7 @@ JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len) { arr->values[i] = JS_NULL; } - return JS_MKPTR (JS_TAG_OBJECT, arr); + return JS_MKPTR (arr); } JSValue JS_NewArray (JSContext *ctx) { return JS_NewArrayLen (ctx, 0); } @@ -3408,14 +3535,13 @@ static int js_intrinsic_array_set (JSContext *ctx, JSArray *arr, uint32_t idx, J /* Allocate intrinsic function (JS_TAG_FUNCTION) */ static JSValue js_new_function (JSContext *ctx, JSFunctionKind kind) { - JSRuntime *rt = ctx->rt; + (void)ctx->rt; /* unused with copying GC */ JSFunction *func = js_mallocz (ctx, sizeof (JSFunction)); if (!func) return JS_EXCEPTION; - func->header.ref_count = 1; + func->header = objhdr_make (0, OBJ_FUNCTION, false, false, false, false); func->kind = kind; func->name = JS_NULL; func->length = 0; - func->free_mark = 0; return JS_MKPTR (func); } @@ -3610,15 +3736,14 @@ void JS_ComputeMemoryUsage (JSRuntime *rt, JSMemoryUsage *s) { s->prop_count += rec->len; /* Estimate record table size */ s->prop_size += (mask + 1) * sizeof (JSRecordEntry); - /* Class-specific stats */ - switch (rec->class_id) { - case JS_CLASS_REGEXP: /* u.regexp */ - compute_JSText_size (rec->u.regexp.pattern, hp); - compute_JSText_size (rec->u.regexp.bytecode, hp); + /* Class-specific stats - use REC_GET_CLASS_ID and REC_GET_OPAQUE */ + switch (REC_GET_CLASS_ID(rec)) { + case JS_CLASS_REGEXP: /* regexp data stored separately, not in slots[0] */ + /* TODO: regexp pattern/bytecode tracking needs rework for new layout */ break; default: - /* XXX: class definition should have an opaque block size */ - if (rec->u.opaque) { s->memory_used_count += 1; } + /* Check if opaque data exists */ + if (REC_GET_OPAQUE(rec)) { s->memory_used_count += 1; } break; } continue; @@ -3683,7 +3808,7 @@ void JS_DumpMemoryUsage (FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) { JSGCObjectHeader *gp = list_entry (el, JSGCObjectHeader, link); if (gp->gc_obj_type == JS_GC_OBJ_TYPE_RECORD) { JSRecord *rec = (JSRecord *)gp; - obj_classes[min_uint32 (rec->class_id, JS_CLASS_INIT_COUNT)]++; + obj_classes[min_uint32 (REC_GET_CLASS_ID(rec), JS_CLASS_INIT_COUNT)]++; } } fprintf (fp, "\n" @@ -3752,7 +3877,7 @@ JSValue JS_Throw (JSContext *ctx, JSValue obj) { JSRuntime *rt = ctx->rt; JS_FreeValue (ctx, rt->current_exception); rt->current_exception = obj; - rt->current_exception_is_uncatchable = FALSE; + /* uncatchable flag removed - not needed with copying GC */ return JS_EXCEPTION; } @@ -3895,7 +4020,7 @@ static const char *get_prop_string (JSContext *ctx, JSValue obj, JSValue prop) { rec = proto; } - JSValue val = rec->tab[slot].val; + JSValue val = rec->slots[slot].val; if (!JS_IsText (val)) return NULL; return JS_ToCString (ctx, val); } @@ -3913,7 +4038,7 @@ static const char *get_func_name (JSContext *ctx, JSValue func) { int slot = rec_find_slot (rec, name_key); if (slot <= 0) return NULL; - JSValue val = rec->tab[slot].val; + JSValue val = rec->slots[slot].val; if (!JS_IsText (val)) return NULL; return JS_ToCString (ctx, val); } @@ -4011,7 +4136,7 @@ static BOOL is_backtrace_needed (JSContext *ctx, JSValue obj) { JSRecord *p; if (!JS_IsRecord (obj)) return FALSE; p = JS_VALUE_GET_OBJ (obj); - if (p->class_id != JS_CLASS_ERROR) return FALSE; + if (REC_GET_CLASS_ID(p) != JS_CLASS_ERROR) return FALSE; /* Check if "stack" property already exists */ JSValue stack_key = MIST_TryNewImmediateASCII ("stack", 5); JSRecord *rec = (JSRecord *)p; @@ -4048,8 +4173,7 @@ static JSValue JS_ThrowError (JSContext *ctx, JSErrorEnum error_num, const char /* the backtrace is added later if called from a bytecode function */ sf = rt->current_stack_frame; - add_backtrace = !rt->in_out_of_memory - && (!sf || (JS_GetFunctionBytecode (sf->cur_func) == NULL)); + add_backtrace = (!sf || (JS_GetFunctionBytecode (sf->cur_func) == NULL)); return JS_ThrowError2 (ctx, error_num, fmt, ap, add_backtrace); } @@ -4109,12 +4233,8 @@ JS_ThrowInternalError (JSContext *ctx, const char *fmt, ...) { } JSValue JS_ThrowOutOfMemory (JSContext *ctx) { - JSRuntime *rt = ctx->rt; - if (!rt->in_out_of_memory) { - rt->in_out_of_memory = TRUE; - JS_ThrowInternalError (ctx, "out of memory"); - rt->in_out_of_memory = FALSE; - } + /* Simplified: no re-entry guard needed with copying GC */ + JS_ThrowInternalError (ctx, "out of memory"); return JS_EXCEPTION; } @@ -4245,7 +4365,7 @@ JSValue JS_GetOwnPropertyNames (JSContext *ctx, JSValue obj) { /* Add string keys to array */ for (i = 1; i <= mask; i++) { - JSValue k = rec->tab[i].key; + JSValue k = rec->slots[i].key; if (!rec_key_is_empty (k) && !rec_key_is_tomb (k) && JS_IsText (k)) { JS_SetPropertyUint32 (ctx, arr, count, JS_DupValue (ctx, k)); count++; @@ -4268,7 +4388,7 @@ static int JS_GetOwnPropertyInternal (JSContext *ctx, if (slot > 0) { if (desc) - *desc = JS_DupValue (ctx, rec->tab[slot].val); + *desc = JS_DupValue (ctx, rec->slots[slot].val); return TRUE; } return FALSE; @@ -4462,14 +4582,14 @@ static int delete_property (JSContext *ctx, JSRecord *rec, JSValue key) { } /* Free key and value */ - JS_FreeValue (ctx, rec->tab[slot].key); - JS_FreeValue (ctx, rec->tab[slot].val); + JS_FreeValue (ctx, rec->slots[slot].key); + JS_FreeValue (ctx, rec->slots[slot].val); /* Mark as tombstone */ - rec->tab[slot].key = JS_EXCEPTION; /* tombstone marker */ - rec->tab[slot].val = JS_NULL; + rec->slots[slot].key = JS_EXCEPTION; /* tombstone marker */ + rec->slots[slot].val = JS_NULL; rec->len--; - rec->tombs++; + /* tombs tracking removed - not needed with copying GC */ return TRUE; } @@ -4641,12 +4761,12 @@ int JS_DeletePropertyKey (JSContext *ctx, JSValue obj, JSValue key) { int slot = rec_find_slot (rec, key); if (slot <= 0) return FALSE; /* Delete by marking as tombstone */ - JS_FreeValue (ctx, rec->tab[slot].key); - JS_FreeValue (ctx, rec->tab[slot].val); - rec->tab[slot].key = JS_EXCEPTION; /* tombstone */ - rec->tab[slot].val = JS_NULL; + JS_FreeValue (ctx, rec->slots[slot].key); + JS_FreeValue (ctx, rec->slots[slot].val); + rec->slots[slot].key = JS_EXCEPTION; /* tombstone */ + rec->slots[slot].val = JS_NULL; rec->len--; - rec->tombs++; + /* tombs tracking removed - not needed with copying GC */ return TRUE; } @@ -4672,7 +4792,7 @@ static BOOL js_object_has_name (JSContext *ctx, JSValue obj) { JSValue name_key = MIST_TryNewImmediateASCII ("name", 4); int slot = rec_find_slot (rec, name_key); if (slot <= 0) return FALSE; - JSValue val = rec->tab[slot].val; + JSValue val = rec->slots[slot].val; if (JS_VALUE_GET_TAG (val) != JS_TAG_STRING) return TRUE; JSText *p = JS_VALUE_GET_STRING (val); return (JSText_len (p) != 0); @@ -4771,7 +4891,7 @@ static JSValue JS_GetGlobalVar (JSContext *ctx, JSValue prop, BOOL throw_ref_err rec = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_var_obj); slot = rec_find_slot (rec, prop); if (slot > 0) { - JSValue val = rec->tab[slot].val; + JSValue val = rec->slots[slot].val; if (unlikely (JS_IsUninitialized (val))) return JS_ThrowReferenceErrorUninitialized (ctx, prop); return JS_DupValue (ctx, val); @@ -4792,7 +4912,7 @@ static int JS_GetGlobalVarRef (JSContext *ctx, JSValue prop, JSValue *sp) { rec = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_var_obj); slot = rec_find_slot (rec, prop); if (slot > 0) { - JSValue val = rec->tab[slot].val; + JSValue val = rec->slots[slot].val; if (unlikely (JS_IsUninitialized (val))) { JS_ThrowReferenceErrorUninitialized (ctx, prop); return -1; @@ -4839,14 +4959,14 @@ static int JS_SetGlobalVar (JSContext *ctx, JSValue prop, JSValue val, int flag) slot = rec_find_slot (rec, prop); if (slot > 0) { if (flag != 1) { - if (unlikely (JS_IsUninitialized (rec->tab[slot].val))) { + if (unlikely (JS_IsUninitialized (rec->slots[slot].val))) { JS_FreeValue (ctx, val); JS_ThrowReferenceErrorUninitialized (ctx, prop); return -1; } } - JS_FreeValue (ctx, rec->tab[slot].val); - rec->tab[slot].val = val; + JS_FreeValue (ctx, rec->slots[slot].val); + rec->slots[slot].val = val; return 0; } @@ -4895,12 +5015,12 @@ int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop) { slot = rec_find_slot (rec, prop); if (slot > 0) { /* Delete by marking as tombstone */ - JS_FreeValue (ctx, rec->tab[slot].key); - JS_FreeValue (ctx, rec->tab[slot].val); - rec->tab[slot].key = JS_EXCEPTION; /* tombstone */ - rec->tab[slot].val = JS_NULL; + JS_FreeValue (ctx, rec->slots[slot].key); + JS_FreeValue (ctx, rec->slots[slot].val); + rec->slots[slot].key = JS_EXCEPTION; /* tombstone */ + rec->slots[slot].val = JS_NULL; rec->len--; - rec->tombs++; + /* tombs tracking removed - not needed with copying GC */ return TRUE; } return TRUE; /* property not found = deletion succeeded */ @@ -4922,19 +5042,20 @@ BOOL JS_IsError (JSContext *ctx, JSValue val) { JSRecord *p; if (JS_VALUE_GET_TAG (val) != JS_TAG_OBJECT) return FALSE; p = JS_VALUE_GET_OBJ (val); - return (p->class_id == JS_CLASS_ERROR); + return (REC_GET_CLASS_ID(p) == JS_CLASS_ERROR); } -/* must be called after JS_Throw() */ -void JS_SetUncatchableException (JSContext *ctx, BOOL flag) { - ctx->rt->current_exception_is_uncatchable = flag; +/* must be called after JS_Throw() - stubbed out, uncatchable not implemented */ +void JS_SetUncatchableException (JSContext *ctx, JS_BOOL flag) { + (void)ctx; (void)flag; + /* uncatchable exception flag not supported with copying GC */ } void JS_SetOpaque (JSValue obj, void *opaque) { JSRecord *p; if (JS_VALUE_GET_TAG (obj) == JS_TAG_OBJECT) { p = JS_VALUE_GET_OBJ (obj); - p->u.opaque = opaque; + REC_SET_OPAQUE(p, opaque); } } @@ -4943,8 +5064,8 @@ void *JS_GetOpaque (JSValue obj, JSClassID class_id) { JSRecord *p; if (JS_VALUE_GET_TAG (obj) != JS_TAG_OBJECT) return NULL; p = JS_VALUE_GET_OBJ (obj); - if (p->class_id != class_id) return NULL; - return p->u.opaque; + if (REC_GET_CLASS_ID(p) != class_id) return NULL; + return REC_GET_OPAQUE(p); } void *JS_GetOpaque2 (JSContext *ctx, JSValue obj, JSClassID class_id) { @@ -4960,8 +5081,8 @@ void *JS_GetAnyOpaque (JSValue obj, JSClassID *class_id) { return NULL; } p = JS_VALUE_GET_OBJ (obj); - *class_id = p->class_id; - return p->u.opaque; + *class_id = REC_GET_CLASS_ID(p); + return REC_GET_OPAQUE(p); } static int JS_ToBoolFree (JSContext *ctx, JSValue val) { @@ -5885,14 +6006,14 @@ static void js_print_object (JSPrintValueState *s, JSRecord *p) { comma_state = 0; is_array = FALSE; - if (p->class_id == JS_CLASS_REGEXP && s->ctx && !s->options.raw_dump) { + if (REC_GET_CLASS_ID(p) == JS_CLASS_REGEXP && s->ctx && !s->options.raw_dump) { JSValue str = js_regexp_toString (s->ctx, JS_MKPTR (p), 0, NULL); if (JS_IsException (str)) goto default_obj; js_print_raw_string (s, str); JS_FreeValueRT (s->rt, str); comma_state = 2; - } else if (p->class_id == JS_CLASS_ERROR && s->ctx && !s->options.raw_dump) { + } else if (REC_GET_CLASS_ID(p) == JS_CLASS_ERROR && s->ctx && !s->options.raw_dump) { JSValue str = js_error_toString (s->ctx, JS_MKPTR (p), 0, NULL); if (JS_IsException (str)) goto default_obj; @@ -5908,8 +6029,8 @@ static void js_print_object (JSPrintValueState *s, JSRecord *p) { comma_state = 2; } else { default_obj: - if (p->class_id != JS_CLASS_OBJECT) { - const char *name = rt->class_array[p->class_id].class_name; + if (REC_GET_CLASS_ID(p) != JS_CLASS_OBJECT) { + const char *name = rt->class_array[REC_GET_CLASS_ID(p)].class_name; if (name) js_printf (s, "%s ", name); } js_printf (s, "{ "); @@ -5921,13 +6042,13 @@ static void js_print_object (JSPrintValueState *s, JSRecord *p) { uint32_t j = 0; for (i = 1; i <= mask; i++) { - JSValue k = rec->tab[i].key; + JSValue k = rec->slots[i].key; if (!rec_key_is_empty (k) && !rec_key_is_tomb (k)) { if (j < s->options.max_item_count) { js_print_comma (s, &comma_state); js_print_value (s, k); js_printf (s, ": "); - js_print_value (s, rec->tab[i].val); + js_print_value (s, rec->slots[i].val); } j++; } @@ -6028,7 +6149,7 @@ static void js_print_value (JSPrintValueState *s, JSValue val) { js_print_object (s, rec); s->level--; } else { - const char *class_name = s->rt->class_array[rec->class_id].class_name; + const char *class_name = s->rt->class_array[REC_GET_CLASS_ID(rec)].class_name; js_putc (s, '['); js_puts (s, class_name ? class_name : "Object"); if (s->options.raw_dump) { js_printf (s, " %p", (void *)rec); } @@ -6133,7 +6254,7 @@ static __maybe_unused void JS_DumpObjectHeader (JSRuntime *rt) { static __maybe_unused void JS_DumpObject (JSRuntime *rt, JSRecord *rec) { JSPrintValueOptions options; - printf ("%14p %4d ", (void *)rec, rec->header.ref_count); + printf ("%14p ", (void *)rec); /* Print prototype from JSRecord */ if (rec->proto) { printf ("%14p ", (void *)rec->proto); @@ -6184,10 +6305,6 @@ static __maybe_unused void JS_DumpGCObject (JSRuntime *rt, } /* return -1 if exception (proxy case) or TRUE/FALSE */ -int JS_IsArray (JSContext *ctx, JSValue val) { - return JS_IsArray (val); -} - static double js_pow (double a, double b) { if (unlikely (!isfinite (b)) && fabs (a) == 1) { /* not compatible with IEEE 754 */ @@ -6491,7 +6608,6 @@ static BOOL js_strict_eq2 (JSContext *ctx, JSValue op1, JSValue op2, JSStrictEqM case JS_TAG_NULL: res = (tag1 == tag2); break; - case JS_TAG_STRING: case JS_TAG_STRING_IMM: { if (!tag_is_string (tag2)) { res = FALSE; @@ -7996,8 +8112,7 @@ restart: obj = sp[-1]; /* Record property access - use JSValue key directly */ - if (JS_VALUE_GET_TAG (obj) == JS_TAG_OBJECT - && js_gc_obj_type (obj) == JS_GC_OBJ_TYPE_RECORD) { + if (JS_IsRecord (obj)) { JSRecord *rec = JS_VALUE_GET_RECORD (obj); val = rec_get (ctx, rec, key); JS_FreeValue (ctx, obj); @@ -8031,8 +8146,7 @@ restart: if (JS_IsFunction (obj)) { val = JS_DupValue (ctx, key); /* "name" as JSValue string */ *sp++ = val; /* stack becomes [func, "name"] */ - } else if (JS_VALUE_GET_TAG (obj) == JS_TAG_OBJECT - && js_gc_obj_type (obj) == JS_GC_OBJ_TYPE_RECORD) { + } else if (JS_IsRecord (obj)) { /* Record property access - use JSValue key directly */ JSRecord *rec = JS_VALUE_GET_RECORD (obj); val = rec_get (ctx, rec, key); @@ -8057,8 +8171,7 @@ restart: key = b->cpool[idx]; /* Must be a record to set property */ - if (!JS_IsObject (sp[-2]) - || js_gc_obj_type (sp[-2]) != JS_GC_OBJ_TYPE_RECORD) { + if (!JS_IsRecord (sp[-2])) { JS_FreeValue (ctx, sp[-1]); JS_FreeValue (ctx, sp[-2]); sp -= 2; @@ -8087,8 +8200,7 @@ restart: key = b->cpool[idx]; /* Must be a record */ - if (JS_VALUE_GET_TAG (sp[-2]) != JS_TAG_OBJECT - || js_gc_obj_type (sp[-2]) != JS_GC_OBJ_TYPE_RECORD) { + if (!JS_IsRecord (sp[-2])) { JS_FreeValue (ctx, sp[-1]); sp--; JS_ThrowTypeError (ctx, "cannot define field on non-record"); @@ -8217,8 +8329,8 @@ restart: /* Check if this is a var ref object */ if (JS_VALUE_GET_TAG (ref_obj) == JS_TAG_OBJECT) { JSRecord *p = JS_VALUE_GET_OBJ (ref_obj); - if (p->class_id == JS_CLASS_VAR_REF_OBJECT) { - JSVarRef *var_ref = (JSVarRef *)p->u.opaque; + if (REC_GET_CLASS_ID(p) == JS_CLASS_VAR_REF_OBJECT) { + JSVarRef *var_ref = (JSVarRef *)REC_GET_OPAQUE(p); if (var_ref) { val = var_ref->is_detached ? var_ref->value : *var_ref->pvalue; *sp++ = JS_DupValue (ctx, val); @@ -8274,8 +8386,8 @@ restart: /* Check if this is a var ref object */ if (JS_VALUE_GET_TAG (ref_obj) == JS_TAG_OBJECT) { JSRecord *p = JS_VALUE_GET_OBJ (ref_obj); - if (p->class_id == JS_CLASS_VAR_REF_OBJECT) { - JSVarRef *var_ref = (JSVarRef *)p->u.opaque; + if (REC_GET_CLASS_ID(p) == JS_CLASS_VAR_REF_OBJECT) { + JSVarRef *var_ref = (JSVarRef *)REC_GET_OPAQUE(p); if (var_ref) { JSValue *pval = var_ref->is_detached ? &var_ref->value : var_ref->pvalue; @@ -8969,18 +9081,17 @@ exception: sf->cur_pc = pc; build_backtrace (ctx, rt->current_exception, NULL, 0, 0, 0); } - if (!rt->current_exception_is_uncatchable) { - while (sp > stack_buf) { - JSValue val = *--sp; - JS_FreeValue (ctx, val); - if (JS_VALUE_GET_TAG (val) == JS_TAG_CATCH_OFFSET) { - int pos = JS_VALUE_GET_INT (val); - if (pos != 0) { - *sp++ = rt->current_exception; - rt->current_exception = JS_UNINITIALIZED; - pc = b->byte_code_buf + pos; - goto restart; - } + /* All exceptions are catchable in the simplified runtime */ + while (sp > stack_buf) { + JSValue val = *--sp; + JS_FreeValue (ctx, val); + if (JS_VALUE_GET_TAG (val) == JS_TAG_CATCH_OFFSET) { + int pos = JS_VALUE_GET_INT (val); + if (pos != 0) { + *sp++ = rt->current_exception; + rt->current_exception = JS_UNINITIALIZED; + pc = b->byte_code_buf + pos; + goto restart; } } } @@ -10260,7 +10371,7 @@ redo: ret = js_atof (s->ctx, (const char *)p, (const char **)&p, 0, flags); if (JS_IsException (ret)) goto fail; /* reject number immediately followed by identifier */ - if (JS_VALUE_IS_NAN (ret) + if (JS_IsNull (ret) || lre_js_is_ident_next ( unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p1))) { JS_FreeValue (s->ctx, ret); @@ -17294,7 +17405,7 @@ static JSValue js_create_function (JSContext *ctx, JSFunctionDef *fd) { b = js_mallocz (ctx, function_size); if (!b) goto fail; - b->header.ref_count = 1; + b->header = objhdr_make (0, OBJ_CODE, false, false, false, false); b->byte_code_buf = (void *)((uint8_t *)b + byte_code_offset); b->byte_code_len = fd->byte_code.size; @@ -17947,7 +18058,8 @@ static JSValue JS_EvalFunctionInternal (JSContext *ctx, JSValue fun_obj, JSValue uint32_t tag; tag = JS_VALUE_GET_TAG (fun_obj); - if (tag == JS_TAG_FUNCTION_BYTECODE) { + /* JSFunctionBytecode uses OBJ_CODE type with JS_TAG_PTR */ + if (tag == JS_TAG_PTR && objhdr_type (*(objhdr_t *)JS_VALUE_GET_PTR (fun_obj)) == OBJ_CODE) { fun_obj = js_closure (ctx, fun_obj, var_refs, sf); ret_val = JS_CallFree (ctx, fun_obj, this_obj, 0, NULL); } else { @@ -18503,14 +18615,14 @@ static int JS_WriteObjectTag (BCWriterState *s, JSValue obj) { for (pass = 0; pass < 2; pass++) { if (pass == 1) bc_put_leb128 (s, prop_count); for (i = 1; i <= mask; i++) { - JSValue k = rec->tab[i].key; + JSValue k = rec->slots[i].key; if (!rec_key_is_empty (k) && !rec_key_is_tomb (k) && JS_IsText (k)) { if (pass == 0) { prop_count++; } else { /* Write key as JSValue text directly */ bc_put_key (s, k); - if (JS_WriteObjectRec (s, rec->tab[i].val)) goto fail; + if (JS_WriteObjectRec (s, rec->slots[i].val)) goto fail; } } } @@ -18546,26 +18658,29 @@ static int JS_WriteObjectRec (BCWriterState *s, JSValue obj) { u.d = JS_VALUE_GET_FLOAT64 (obj); bc_put_u64 (s, u.u64); } break; - case JS_TAG_STRING: { - JSText *p = JS_VALUE_GET_STRING (obj); - bc_put_u8 (s, BC_TAG_STRING); - JS_WriteString (s, p); - } break; case JS_TAG_STRING_IMM: { - int len = MIST_GetImmediateASCIILen (obj); - char buf[8]; - for (int i = 0; i < len; i++) - buf[i] = MIST_GetImmediateASCIIChar (obj, i); - JSValue tmp = js_new_string8_len (s->ctx, buf, len); - if (JS_IsException (tmp)) goto fail; - JSText *p = JS_VALUE_GET_STRING (tmp); - JS_WriteString (s, p); - JS_FreeValue (s->ctx, tmp); + /* Handle both heap strings (JS_TAG_PTR with OBJ_TEXT) and immediate strings */ + if (tag_is_string (tag)) { + if (tag == JS_TAG_PTR) { + /* Heap string */ + JSText *p = JS_VALUE_GET_STRING (obj); + bc_put_u8 (s, BC_TAG_STRING); + JS_WriteString (s, p); + } else { + /* Immediate ASCII string */ + int len = MIST_GetImmediateASCIILen (obj); + char buf[8]; + for (int i = 0; i < len; i++) + buf[i] = MIST_GetImmediateASCIIChar (obj, i); + JSValue tmp = js_new_string8_len (s->ctx, buf, len); + if (JS_IsException (tmp)) goto fail; + JSText *p = JS_VALUE_GET_STRING (tmp); + bc_put_u8 (s, BC_TAG_STRING); + JS_WriteString (s, p); + JS_FreeValue (s->ctx, tmp); + } + } } break; - case JS_TAG_FUNCTION_BYTECODE: - if (!s->allow_bytecode) goto invalid_tag; - if (JS_WriteFunctionTag (s, obj)) goto fail; - break; case JS_TAG_OBJECT: /* Check if this is an intrinsic array */ if (JS_IsArray (obj)) { @@ -18580,23 +18695,20 @@ static int JS_WriteObjectRec (BCWriterState *s, JSValue obj) { JSRecord *p = JS_VALUE_GET_OBJ (obj); int ret, idx; - if (s->allow_reference) { - idx = js_object_list_find (s->ctx, &s->object_list, p); - if (idx >= 0) { + /* Always use object_list for cycle detection */ + idx = js_object_list_find (s->ctx, &s->object_list, p); + if (idx >= 0) { + if (s->allow_reference) { bc_put_u8 (s, BC_TAG_OBJECT_REFERENCE); bc_put_leb128 (s, idx); break; } else { - if (js_object_list_add (s->ctx, &s->object_list, p)) goto fail; - } - } else { - if (p->tmp_mark) { JS_ThrowTypeError (s->ctx, "circular reference"); goto fail; } - p->tmp_mark = 1; } - switch (p->class_id) { + if (js_object_list_add (s->ctx, &s->object_list, p)) goto fail; + switch (REC_GET_CLASS_ID(p)) { case JS_CLASS_OBJECT: ret = JS_WriteObjectTag (s, obj); break; @@ -18605,7 +18717,6 @@ static int JS_WriteObjectRec (BCWriterState *s, JSValue obj) { ret = -1; break; } - p->tmp_mark = 0; if (ret) goto fail; } break; @@ -18868,7 +18979,7 @@ static JSText *JS_ReadString (BCReaderState *s) { size = (size_t)len * sizeof (uint32_t); if ((s->buf_end - s->ptr) < size) { bc_read_error_end (s); - js_free_string (s->ctx->rt, p); + /* GC handles cleanup - partial string will be collected */ return NULL; } for (i = 0; i < len; i++) { @@ -18954,8 +19065,7 @@ static JSValue JS_ReadFunctionTag (BCReaderState *s) { int closure_var_offset, vardefs_offset; memset (&bc, 0, sizeof (bc)); - bc.header.ref_count = 1; - // bc.gc_header.mark = 0; + bc.header = objhdr_make (0, OBJ_CODE, false, false, false, false); if (bc_get_u16 (s, &v16)) goto fail; idx = 0; @@ -18995,7 +19105,7 @@ static JSValue JS_ReadFunctionTag (BCReaderState *s) { if (!b) return JS_EXCEPTION; memcpy (b, &bc, offsetof (JSFunctionBytecode, debug)); - b->header.ref_count = 1; + b->header = objhdr_make (0, OBJ_CODE, false, false, false, false); if (local_count != 0) { b->vardefs = (void *)((uint8_t *)b + vardefs_offset); } @@ -19591,7 +19701,7 @@ static JSValue js_error_constructor (JSContext *ctx, JSValue this_val, int argc, if (magic == JS_AGGREGATE_ERROR) { /* Require errors to be an array (no iterator support) */ JSValue error_list; - if (JS_IsArray (ctx, argv[0])) { + if (JS_IsArray (argv[0])) { uint32_t len, i; if (js_get_length32 (ctx, &len, argv[0])) goto exception; error_list = JS_NewArray (ctx); @@ -19672,7 +19782,7 @@ static int js_is_regexp (JSContext *ctx, JSValue obj); static void js_regexp_finalizer (JSRuntime *rt, JSValue val) { JSRecord *p = JS_VALUE_GET_OBJ (val); - JSRegExp *re = &p->u.regexp; + JSRegExp *re = (JSRegExp *)REC_GET_OPAQUE(p); JS_FreeValueRT (rt, JS_MKPTR (re->bytecode)); JS_FreeValueRT (rt, JS_MKPTR (re->pattern)); } @@ -19773,7 +19883,10 @@ static JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, obj = JS_NewObjectProtoClass (ctx, ctx->class_proto[JS_CLASS_REGEXP], JS_CLASS_REGEXP); if (JS_IsException (obj)) goto fail; p = JS_VALUE_GET_OBJ (obj); - re = &p->u.regexp; + /* Allocate JSRegExp and store in opaque slot */ + re = js_malloc (ctx, sizeof(JSRegExp)); + if (!re) goto fail; + REC_SET_OPAQUE(p, re); re->pattern = JS_VALUE_GET_STRING (pattern); re->bytecode = JS_VALUE_GET_STRING (bc); { @@ -19787,7 +19900,7 @@ static JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, static JSRegExp *js_get_regexp (JSContext *ctx, JSValue obj, BOOL throw_error) { if (JS_VALUE_GET_TAG (obj) == JS_TAG_OBJECT) { JSRecord *p = JS_VALUE_GET_OBJ (obj); - if (p->class_id == JS_CLASS_REGEXP) return &p->u.regexp; + if (REC_GET_CLASS_ID(p) == JS_CLASS_REGEXP) return (JSRegExp *)REC_GET_OPAQUE(p); } if (throw_error) { JS_ThrowTypeErrorInvalidClass (ctx, JS_CLASS_REGEXP); } return NULL; @@ -20000,7 +20113,7 @@ static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSVal if (JS_IsException (val) || JS_ToLengthFree (ctx, &last_index, val)) goto fail; - re_bytecode = (uint8_t *)re->bytecode->u; + re_bytecode = (uint8_t *)re->bytecode->packed; re_flags = lre_get_flags (re_bytecode); if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) last_index = 0; @@ -20178,7 +20291,7 @@ static JSValue JS_RegExpDelete (JSContext *ctx, JSValue this_val, JSValue arg) { str_val = JS_ToString (ctx, arg); if (JS_IsException (str_val)) goto fail; str = JS_VALUE_GET_STRING (str_val); - re_bytecode = (uint8_t *)re->bytecode->u; + re_bytecode = (uint8_t *)re->bytecode->packed; re_flags = lre_get_flags (re_bytecode); if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) { last_index = 0; @@ -20474,7 +20587,7 @@ static JSValue internalize_json_property (JSContext *ctx, JSValue holder, JSValu val = JS_GetProperty (ctx, holder, name); if (JS_IsException (val)) return val; - is_array = JS_IsArray (ctx, val); + is_array = JS_IsArray (val); if (is_array < 0) goto fail; if (is_array || JS_IsObject (val)) { if (is_array) { @@ -20584,7 +20697,6 @@ static JSValue js_json_check (JSContext *ctx, JSONStringifyContext *jsc, JSValue case JS_TAG_OBJECT: /* includes arrays (OBJ_ARRAY) via mist_hdr */ if (JS_IsFunction (val)) break; /* fall through */ - case JS_TAG_STRING: case JS_TAG_STRING_IMM: case JS_TAG_INT: case JS_TAG_FLOAT64: @@ -20642,7 +20754,7 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho } v = js_cell_push (ctx, jsc->stack, 1, (JSValue *)&val); if (check_exception_free (ctx, v)) goto exception; - ret = JS_IsArray (ctx, val); + ret = JS_IsArray (val); if (ret < 0) goto exception; if (ret) { if (js_get_length64 (ctx, &len, val)) goto exception; @@ -20736,7 +20848,6 @@ static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue ho } concat_primitive: switch (JS_VALUE_GET_NORM_TAG (val)) { - case JS_TAG_STRING: case JS_TAG_STRING_IMM: val = JS_ToQuotedStringFree (ctx, val); if (JS_IsException (val)) goto exception; @@ -20788,7 +20899,7 @@ JSValue JS_JSONStringify (JSContext *ctx, JSValue obj, JSValue replacer, JSValue if (JS_IsFunction (replacer)) { jsc->replacer_func = replacer; } else { - res = JS_IsArray (ctx, replacer); + res = JS_IsArray (replacer); if (res < 0) goto exception; if (res) { /* XXX: enumeration is not fully correct */ @@ -21788,7 +21899,7 @@ static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue } /* Handle array */ - if (JS_IsArray (ctx, arg)) { + if (JS_IsArray (arg)) { int64_t len; if (js_get_length64 (ctx, &len, arg)) return JS_EXCEPTION; @@ -22563,7 +22674,7 @@ static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, /* Append capture groups from exec_res.captures (skip [0] if it mirrors * full match) */ JSValue caps = JS_GetPropertyStr (ctx, exec_res, "captures"); - if (!JS_IsException (caps) && JS_IsArray (ctx, caps)) { + if (!JS_IsException (caps) && JS_IsArray (caps)) { int64_t caps_len = 0; if (js_get_length64 (ctx, &caps_len, caps) == 0 && caps_len > 0) { /* Many engines put full match at captures[0]. Your record already has @@ -22694,7 +22805,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu /* array(array, function) - map */ /* array(array, another_array) - concat */ /* array(array, from, to) - slice */ - if (JS_IsArray (ctx, arg)) { + if (JS_IsArray (arg)) { JSArray *arr = JS_VALUE_GET_ARRAY (arg); int len = arr->len; @@ -22794,7 +22905,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu return result; } - if (JS_IsArray (ctx, argv[1])) { + if (JS_IsArray (argv[1])) { /* Concat */ JSArray *arr2 = JS_VALUE_GET_ARRAY (argv[1]); int len2 = arr2->len; @@ -22846,7 +22957,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu } /* array(object) - keys */ - if (JS_IsObject (arg) && !JS_IsArray (ctx, arg)) { + if (JS_IsObject (arg) && !JS_IsArray (arg)) { /* Return object keys */ return JS_GetOwnPropertyNames (ctx, arg); } @@ -23102,7 +23213,7 @@ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValu /* array.reduce(arr, fn, initial, reverse) */ static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; - if (!JS_IsArray (ctx, argv[0])) return JS_NULL; + if (!JS_IsArray (argv[0])) return JS_NULL; if (!JS_IsFunction (argv[1])) return JS_NULL; JSArray *arr = JS_VALUE_GET_ARRAY (argv[0]); @@ -23168,7 +23279,7 @@ static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc, /* array.for(arr, fn, reverse, exit) */ static JSValue js_cell_array_for (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; - if (!JS_IsArray (ctx, argv[0])) return JS_NULL; + if (!JS_IsArray (argv[0])) return JS_NULL; if (!JS_IsFunction (argv[1])) return JS_NULL; JSArray *arr = JS_VALUE_GET_ARRAY (argv[0]); @@ -23235,7 +23346,7 @@ static JSValue js_cell_array_for (JSContext *ctx, JSValue this_val, int argc, JS /* array.find(arr, fn, reverse, from) */ static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; - if (!JS_IsArray (ctx, argv[0])) return JS_NULL; + if (!JS_IsArray (argv[0])) return JS_NULL; JSArray *arr = JS_VALUE_GET_ARRAY (argv[0]); int len = arr->len; @@ -23333,7 +23444,7 @@ static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, J /* array.filter(arr, fn) */ static JSValue js_cell_array_filter (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; - if (!JS_IsArray (ctx, argv[0])) return JS_NULL; + if (!JS_IsArray (argv[0])) return JS_NULL; if (!JS_IsFunction (argv[1])) return JS_NULL; JSArray *arr = JS_VALUE_GET_ARRAY (argv[0]); @@ -23420,7 +23531,7 @@ static JSValue js_cell_array_filter (JSContext *ctx, JSValue this_val, int argc, /* array.sort(arr, select) */ static JSValue js_cell_array_sort (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; - if (!JS_IsArray (ctx, argv[0])) return JS_NULL; + if (!JS_IsArray (argv[0])) return JS_NULL; JSArray *arr = JS_VALUE_GET_ARRAY (argv[0]); int len = arr->len; @@ -23446,7 +23557,7 @@ static JSValue js_cell_array_sort (JSContext *ctx, JSValue this_val, int argc, J /* Check if we have a key selector array */ JSArray *key_arr = NULL; - if (argc >= 2 && JS_IsArray (ctx, argv[1])) + if (argc >= 2 && JS_IsArray (argv[1])) key_arr = JS_VALUE_GET_ARRAY (argv[1]); /* Extract keys */ @@ -23460,7 +23571,7 @@ static JSValue js_cell_array_sort (JSContext *ctx, JSValue this_val, int argc, J /* Numeric index - use for nested arrays */ int32_t idx; JS_ToInt32 (ctx, &idx, argv[1]); - if (JS_IsArray (ctx, items[i])) { + if (JS_IsArray (items[i])) { JSArray *nested = JS_VALUE_GET_ARRAY (items[i]); if (idx >= 0 && idx < (int)nested->len) key = JS_DupValue (ctx, nested->values[idx]); @@ -23597,7 +23708,7 @@ static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSVal JSValue arg = argv[0]; /* object(object) - shallow mutable copy */ - if (JS_IsObject (arg) && !JS_IsArray (ctx, arg) && !JS_IsFunction (arg)) { + if (JS_IsObject (arg) && !JS_IsArray (arg) && !JS_IsFunction (arg)) { if (argc < 2 || JS_IsNull (argv[1])) { /* Shallow copy */ JSValue result = JS_NewObject (ctx); @@ -23631,7 +23742,7 @@ static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSVal } /* object(object, another_object) - combine */ - if (JS_IsObject (argv[1]) && !JS_IsArray (ctx, argv[1])) { + if (JS_IsObject (argv[1]) && !JS_IsArray (argv[1])) { JSValue result = JS_NewObject (ctx); if (JS_IsException (result)) return result; @@ -23687,7 +23798,7 @@ static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSVal } /* object(object, array_of_keys) - select */ - if (JS_IsArray (ctx, argv[1])) { + if (JS_IsArray (argv[1])) { JSValue result = JS_NewObject (ctx); if (JS_IsException (result)) return result; @@ -23717,7 +23828,7 @@ static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSVal /* object(array_of_keys) - set with true values */ /* object(array_of_keys, value) - value set */ /* object(array_of_keys, function) - functional value set */ - if (JS_IsArray (ctx, arg)) { + if (JS_IsArray (arg)) { JSArray *keys = JS_VALUE_GET_ARRAY (arg); int len = keys->len; @@ -23771,7 +23882,7 @@ static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSV if (argc < 2) { return JS_CallInternal (ctx, func, JS_NULL, 0, NULL, 0); } JSValue args_val = argv[1]; - if (!JS_IsArray (ctx, args_val)) { + if (!JS_IsArray (args_val)) { /* Wrap single value in array */ JSValue arg = JS_DupValue (ctx, args_val); JSValue result = JS_CallInternal (ctx, func, JS_NULL, 1, &arg, 0); @@ -23809,8 +23920,8 @@ static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSV static blob *js_get_blob (JSContext *ctx, JSValue val) { if (JS_VALUE_GET_TAG (val) != JS_TAG_OBJECT) return NULL; JSRecord *p = JS_VALUE_GET_OBJ (val); - if (p->class_id != JS_CLASS_BLOB) return NULL; - return p->u.opaque; + if (REC_GET_CLASS_ID(p) != JS_CLASS_BLOB) return NULL; + return REC_GET_OPAQUE(p); } /* Helper to create a new blob JSValue */ @@ -24382,7 +24493,7 @@ static JSValue js_cell_stone (JSContext *ctx, JSValue this_val, int argc, JSValu return JS_DupValue (ctx, obj); } - if (JS_IsArray (ctx, obj)) { + if (JS_IsArray (obj)) { JSArray *arr = JS_VALUE_GET_ARRAY (obj); arr->mist_hdr = objhdr_set_s (arr->mist_hdr, true); return JS_DupValue (ctx, obj); @@ -24402,7 +24513,7 @@ static JSValue js_cell_reverse (JSContext *ctx, JSValue this_val, int argc, JSVa JSValue value = argv[0]; /* Handle arrays */ - if (JS_IsArray (ctx, value)) { + if (JS_IsArray (value)) { JSArray *arr = JS_VALUE_GET_ARRAY (value); int len = arr->len; @@ -24435,7 +24546,7 @@ static JSValue js_cell_pop (JSContext *ctx, JSValue this_val, int argc, JSValue if (argc < 1) return JS_NULL; JSValue obj = argv[0]; - if (!JS_IsArray (ctx, obj)) return JS_NULL; + if (!JS_IsArray (obj)) return JS_NULL; JSArray *arr = JS_VALUE_GET_ARRAY (obj); @@ -24453,7 +24564,7 @@ JSValue JS_Stone (JSContext *ctx, JSValue this_val) { } int JS_ArrayPush (JSContext *ctx, JSValue obj, JSValue val) { - if (!JS_IsArray (ctx, obj)) { + if (!JS_IsArray (obj)) { JS_ThrowTypeError (ctx, "not an array"); return -1; } @@ -24468,7 +24579,7 @@ int JS_ArrayPush (JSContext *ctx, JSValue obj, JSValue val) { } JSValue JS_ArrayPop (JSContext *ctx, JSValue obj) { - if (!JS_IsArray (ctx, obj)) return JS_ThrowTypeError (ctx, "not an array"); + if (!JS_IsArray (obj)) return JS_ThrowTypeError (ctx, "not an array"); return js_cell_pop (ctx, JS_NULL, 1, &obj); } @@ -24494,7 +24605,7 @@ static JSValue js_cell_proto (JSContext *ctx, JSValue this_val, int argc, JSValu JSValue obj = argv[0]; /* Intrinsic arrays return the Object prototype */ - if (JS_IsArray (ctx, obj)) { + if (JS_IsArray (obj)) { return JS_DupValue (ctx, ctx->class_proto[JS_CLASS_OBJECT]); } @@ -24531,7 +24642,7 @@ static JSValue js_cell_meme (JSContext *ctx, JSValue this_val, int argc, JSValue /* Helper function to apply a single mixin */ #define APPLY_MIXIN(mix) \ do { \ - if (!JS_IsObject (mix) || JS_IsNull (mix) || JS_IsArray (ctx, mix)) \ + if (!JS_IsObject (mix) || JS_IsNull (mix) || JS_IsArray (mix)) \ break; \ JSValue _keys = JS_GetOwnPropertyNames (ctx, mix); \ if (JS_IsException (_keys)) { \ @@ -24562,7 +24673,7 @@ static JSValue js_cell_meme (JSContext *ctx, JSValue this_val, int argc, JSValue for (int i = 1; i < argc; i++) { JSValue mixins = argv[i]; - if (JS_IsArray (ctx, mixins)) { + if (JS_IsArray (mixins)) { /* Array of mixins */ int64_t len; if (js_get_length64 (ctx, &len, mixins)) { @@ -24720,7 +24831,7 @@ static JSValue js_cell_length (JSContext *ctx, JSValue this_val, int argc, JSVal if (bd) return JS_NewInt64 (ctx, bd->length); /* Arrays return element count */ - if (JS_IsArray (ctx, val)) { + if (JS_IsArray (val)) { JSArray *arr = JS_VALUE_GET_ARRAY (val); return JS_NewInt32 (ctx, arr->len); } @@ -24764,7 +24875,7 @@ static JSValue js_cell_call (JSContext *ctx, JSValue this_val, int argc, JSValue if (argc < 3 || JS_IsNull (argv[2])) return JS_CallInternal (ctx, func, this_arg, 0, NULL, 0); - if (!JS_IsArray (ctx, argv[2])) + if (!JS_IsArray (argv[2])) return JS_ThrowTypeError (ctx, "third argument must be an array"); uint32_t len; @@ -24784,7 +24895,7 @@ static JSValue js_cell_call (JSContext *ctx, JSValue this_val, int argc, JSValue /* is_array(val) */ static JSValue js_cell_is_array (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_FALSE; - return JS_NewBool (ctx, JS_IsArray (ctx, argv[0])); + return JS_NewBool (ctx, JS_IsArray (argv[0])); } /* is_blob(val) */ @@ -24798,7 +24909,7 @@ static JSValue js_cell_is_data (JSContext *ctx, JSValue this_val, int argc, JSVa if (argc < 1) return JS_FALSE; JSValue val = argv[0]; if (!JS_IsObject (val)) return JS_FALSE; - if (JS_IsArray (ctx, val)) return JS_FALSE; + if (JS_IsArray (val)) return JS_FALSE; if (JS_IsFunction (val)) return JS_FALSE; if (js_get_blob (ctx, val)) return JS_FALSE; /* Check if it's a plain object (prototype is Object.prototype or null) */ @@ -24847,7 +24958,7 @@ static JSValue js_cell_is_object (JSContext *ctx, JSValue this_val, int argc, JS if (argc < 1) return JS_FALSE; JSValue val = argv[0]; if (!JS_IsObject (val)) return JS_FALSE; - if (JS_IsArray (ctx, val)) return JS_FALSE; + if (JS_IsArray (val)) return JS_FALSE; return JS_TRUE; } @@ -24856,7 +24967,7 @@ static JSValue js_cell_is_stone (JSContext *ctx, JSValue this_val, int argc, JSV if (argc < 1) return JS_FALSE; JSValue obj = argv[0]; - return JS_NewBool (JS_IsStone (argv[0])); + return JS_NewBool (ctx, JS_IsStone (argv[0])); } /* is_text(val) */ @@ -24929,7 +25040,7 @@ static const char *const native_error_name[JS_NATIVE_ERROR_COUNT] = { /* Minimum amount of objects to be able to compile code and display error messages. No JSAtom should be allocated by this function. */ -static void JS_AddIntrinsicBasicObjects (JSContext *ctx) { +void JS_AddIntrinsicBasicObjects (JSContext *ctx) { JSValue proto; int i; diff --git a/source/quickjs.h b/source/quickjs.h index 3369e007..4e477ebd 100644 --- a/source/quickjs.h +++ b/source/quickjs.h @@ -148,8 +148,14 @@ enum { JS_TAG_UNINITIALIZED = 0x17, /* 10111 */ JS_TAG_STRING_IMM = 0x1B, /* 11011 - immediate ASCII (up to 7 chars) */ JS_TAG_CATCH_OFFSET = 0x1F, /* 11111 */ + JS_TAG_FORWARD = 0x13, /* 10011 - forwarding pointer (GC internal use) */ }; +/* Compatibility tag aliases for external code */ +#define JS_TAG_STRING JS_TAG_STRING_IMM /* Alias: text/string type */ +#define JS_TAG_OBJECT JS_TAG_PTR /* Alias: use JS_IsRecord/JS_IsArray instead */ +#define JS_TAG_FLOAT64 JS_TAG_SHORT_FLOAT /* Alias: floats use short float encoding */ + #define JS_EMPTY_TEXT ((JSValue)JS_TAG_STRING_IMM) /* JSGCRef - for rooting values during GC */ @@ -454,6 +460,18 @@ int JS_IsRegisteredClass (JSRuntime *rt, JSClassID class_id); extern JSClassID js_class_id_alloc; +/* ============================================================ + Copying GC - No Reference Counting Needed + ============================================================ + With a copying GC, reference counting is not needed since all live + objects are discovered by tracing from roots. These macros make + existing DupValue/FreeValue calls into no-ops. + ============================================================ */ +#define JS_DupValue(ctx, v) (v) +#define JS_FreeValue(ctx, v) ((void)0) +#define JS_DupValueRT(rt, v) (v) +#define JS_FreeValueRT(rt, v) ((void)0) + /* value handling */ static inline JSValue @@ -991,6 +1009,26 @@ JSValue js_debugger_fn_info (JSContext *ctx, JSValue fn); JSValue js_debugger_fn_bytecode (JSContext *js, JSValue fn); void *js_debugger_val_address (JSContext *js, JSValue val); +/* Memory allocation functions (bump allocator) */ +void *js_malloc (JSContext *ctx, size_t size); +void *js_mallocz (JSContext *ctx, size_t size); +void *js_realloc (JSContext *ctx, void *ptr, size_t size); +void js_free (JSContext *ctx, void *ptr); +char *js_strdup (JSContext *ctx, const char *str); + +/* Runtime-level memory functions */ +void *js_malloc_rt (JSRuntime *rt, size_t size); +void *js_mallocz_rt (JSRuntime *rt, size_t size); +void js_free_rt (JSRuntime *rt, void *ptr); +size_t js_malloc_usable_size_rt (JSRuntime *rt, const void *ptr); + +/* Intrinsic setup functions */ +void JS_AddIntrinsicBaseObjects (JSContext *ctx); +void JS_AddIntrinsicBasicObjects (JSContext *ctx); +void JS_AddIntrinsicEval (JSContext *ctx); +void JS_AddIntrinsicRegExp (JSContext *ctx); +void JS_AddIntrinsicJSON (JSContext *ctx); + #undef js_unlikely #undef inline