From b3f3bc8a5f7858fd0ac1b015bb1a6544128e9712 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 30 Jan 2026 20:16:08 -0600 Subject: [PATCH] rm atoms --- plan.md | 660 ++++++++------------------------ source/quickjs.c | 960 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 817 insertions(+), 803 deletions(-) diff --git a/plan.md b/plan.md index 4f3d10b1..094ba059 100644 --- a/plan.md +++ b/plan.md @@ -1,547 +1,215 @@ -# Refactoring QuickJS to Mist Memory Format +# Cell/QuickJS Refactoring Plan: Remove Atoms, Shapes, and Dual-Encoding -## Summary +## Overview -Complete rework of `quickjs.h` and `quickjs.c` to align with `docs/memory.md` and the new JSValue encoding scheme using LSB-based type discrimination with short floats. +Refactor `source/quickjs.c` to match `docs/memory.md` specification: +- Remove JSAtom system (171 references → ~41 remaining) +- Remove JSShape system (94 references) ✓ +- Remove IC caches (shape-based inline caches) ✓ +- Remove `is_wide_char` dual-encoding (18 locations) ✓ +- Use JSValue texts directly as property keys +- Reference: `mquickjs.c` shows the target pattern -## Key Design Decisions (from user) +## Completed Phases -1. **Remove NaN-boxing entirely** - Use LSB-based type tags instead -2. **Short float for numbers** - Truncated double (3 fewer exponent bits), out-of-range → NULL -3. **Optional 32-bit float mode** - Compile-time option, stored like ints -4. **Remove KeyId** - Use JSValue directly as keys in objects -5. **Remove JSStringRope** - No lazy concatenation, immediate text creation -6. **Remove JSObject/shapes** - Move to JSRecord only with direct key/value storage -7. **Remove atoms from objects** - String interning for literals/properties only +### Phase 1: Remove is_wide_char Remnants ✓ +### Phase 2: Remove IC Caches ✓ +### Phase 3: Remove JSShape System ✓ +### Phase 4: Complete Property Access with JSValue Keys ✓ -## New JSValue Encoding (64-bit) +Completed: +- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_get_field +- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_put_field +- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_define_field +- Created emit_key() function that adds JSValue to cpool and emits index -Based on the provided header, using LSB-based discrimination: +--- -``` -LSB = 0 → 31-bit signed integer (value >> 1) -LSB = 01 → 61-bit pointer -LSB = 101 → Short float (truncated double, 3 fewer exponent bits) -LSB = 11 → Special tag (next 3 bits for subtype, 5 bits total) -``` +## Phase 5: Convert JSAtom to JSValue Text (IN PROGRESS) -**Special tags (5 bits, LSB = 11):** -- `00011` (3) = JS_TAG_BOOL (payload bit 5 = value) -- `00111` (7) = JS_TAG_NULL -- `01011` (11) = JS_TAG_UNDEFINED (may not be needed - use NULL) -- `01111` (15) = JS_TAG_EXCEPTION -- `10111` (23) = JS_TAG_UNINITIALIZED -- `11011` (27) = JS_TAG_STRING_ASCII (immediate string: 3-bit len + up to 7 ASCII bytes) -- `11111` (31) = JS_TAG_CATCH_OFFSET +This is the core transformation. All identifier handling moves from atoms to JSValue. -## Critical Files +### Completed Items -- `/Users/johnalanbrook/work/cell/source/quickjs.h` - Complete rewrite of JSValue encoding -- `/Users/johnalanbrook/work/cell/source/quickjs.c` - Remove shapes, atoms from objects, string ropes +**Token and Parser Infrastructure:** +- [x] Change JSToken.u.ident.atom to JSToken.u.ident.str (JSValue) +- [x] Change parse_ident() to return JSValue +- [x] Create emit_key() function (cpool-based) +- [x] Create JS_KEY_* macros for common names (lines ~279-335 in quickjs.c) +- [x] Update all token.u.ident.atom references to .str +- [x] Create keyword lookup table (js_keywords[]) with string comparison +- [x] Rewrite update_token_ident() to use js_keyword_lookup() +- [x] Rewrite is_strict_future_keyword() to use JSValue +- [x] Update token_is_pseudo_keyword() to use JSValue and js_key_equal() -## Implementation Plan +**Function Declaration Parsing:** +- [x] Update js_parse_function_decl() signature to use JSValue func_name +- [x] Update js_parse_function_decl2() to use JSValue func_name throughout +- [x] Update js_parse_function_check_names() to use JSValue +- [x] Convert JS_DupAtom/JS_FreeAtom to JS_DupValue/JS_FreeValue in function parsing -### Phase 1: New JSValue Encoding in quickjs.h +**Variable Definition and Lookup:** +- [x] Update find_global_var() to use JSValue and js_key_equal() +- [x] Update find_lexical_global_var() to use JSValue +- [x] Update find_lexical_decl() to use JSValue and js_key_equal() +- [x] Update js_define_var() to use JSValue +- [x] Update js_parse_check_duplicate_parameter() to use JSValue and js_key_equal() +- [x] Update js_parse_destructuring_var() to return JSValue +- [x] Update js_parse_var() to use JSValue for variable names -Replace the entire JSValue system with LSB-based tags: +**Comparison Helpers:** +- [x] Create js_key_equal_str() for comparing JSValue with C string literals +- [x] Update is_var_in_arg_scope() to use js_key_equal/js_key_equal_str +- [x] Update has_with_scope() to use js_key_equal_str +- [x] Update closure variable comparisons (cv->var_name) to use js_key_equal_str +**Property Access:** +- [x] Fix JS_GetPropertyStr to create proper JSValue keys +- [x] Fix JS_SetPropertyInternal callers to use JS_KEY_* instead of JS_ATOM_* + +### JS_KEY_* Macros Added + +Compile-time immediate ASCII string constants (≤7 chars): ```c -#if INTPTR_MAX >= INT64_MAX -#define JS_PTR64 -typedef uint64_t JSValue; -#define JSW 8 -#define JS_USE_SHORT_FLOAT -#else -typedef uint32_t JSValue; -#define JSW 4 -#endif - -enum { - JS_TAG_INT = 0, /* LSB = 0, 31-bit int */ - JS_TAG_PTR = 1, /* LSB = 01, pointer */ - JS_TAG_SPECIAL = 3, /* LSB = 11, special values */ - JS_TAG_BOOL = JS_TAG_SPECIAL | (0 << 2), /* 5 bits */ - JS_TAG_NULL = JS_TAG_SPECIAL | (1 << 2), - JS_TAG_EXCEPTION = JS_TAG_SPECIAL | (3 << 2), - JS_TAG_UNINITIALIZED = JS_TAG_SPECIAL | (5 << 2), - JS_TAG_STRING_ASCII = JS_TAG_SPECIAL | (6 << 2), /* immediate ASCII string */ - JS_TAG_CATCH_OFFSET = JS_TAG_SPECIAL | (7 << 2), -#ifdef JS_USE_SHORT_FLOAT - JS_TAG_SHORT_FLOAT = 5, /* LSB = 101 */ -#endif -}; - -/* Value extraction */ -#define JS_VALUE_GET_INT(v) ((int32_t)(v) >> 1) -#define JS_VALUE_GET_PTR(v) ((void *)((v) & ~(JSW - 1))) -#define JS_VALUE_GET_SPECIAL_TAG(v) ((v) & 0x1F) -#define JS_VALUE_GET_SPECIAL_VALUE(v) ((int32_t)(v) >> 5) - -/* Value creation */ -#define JS_MKINT(val) (((JSValue)(val) << 1) | JS_TAG_INT) -#define JS_MKPTR(ptr) (((JSValue)(uintptr_t)(ptr)) | JS_TAG_PTR) -#define JS_MKSPECIAL(tag, val) ((JSValue)(tag) | ((JSValue)(val) << 5)) - -/* Type checks */ -static inline JS_BOOL JS_IsInt(JSValue v) { return (v & 1) == JS_TAG_INT; } -static inline JS_BOOL JS_IsPtr(JSValue v) { return (v & (JSW-1)) == JS_TAG_PTR; } -static inline JS_BOOL JS_IsNull(JSValue v) { return v == JS_MKSPECIAL(JS_TAG_NULL, 0); } -static inline JS_BOOL JS_IsException(JSValue v) { return JS_VALUE_GET_SPECIAL_TAG(v) == JS_TAG_EXCEPTION; } - -#ifdef JS_USE_SHORT_FLOAT -static inline JS_BOOL JS_IsShortFloat(JSValue v) { return (v & 7) == JS_TAG_SHORT_FLOAT; } -#endif - -/* Constants */ -#define JS_NULL JS_MKSPECIAL(JS_TAG_NULL, 0) -#define JS_FALSE JS_MKSPECIAL(JS_TAG_BOOL, 0) -#define JS_TRUE JS_MKSPECIAL(JS_TAG_BOOL, 1) -#define JS_EXCEPTION JS_MKSPECIAL(JS_TAG_EXCEPTION, 0) -#define JS_UNINITIALIZED JS_MKSPECIAL(JS_TAG_UNINITIALIZED, 0) +JS_KEY_empty, JS_KEY_name, JS_KEY_message, JS_KEY_stack, +JS_KEY_errors, JS_KEY_Error, JS_KEY_cause, JS_KEY_length, +JS_KEY_value, JS_KEY_get, JS_KEY_set, JS_KEY_raw, +JS_KEY_flags, JS_KEY_source, JS_KEY_exec, JS_KEY_toJSON, +JS_KEY_eval, JS_KEY_this, JS_KEY_true, JS_KEY_false, +JS_KEY_null, JS_KEY_NaN, JS_KEY_default, JS_KEY_index, +JS_KEY_input, JS_KEY_groups, JS_KEY_indices, JS_KEY_let, +JS_KEY_var, JS_KEY_new, JS_KEY_of, JS_KEY_yield, +JS_KEY_async, JS_KEY_target, JS_KEY_from, JS_KEY_meta, +JS_KEY_as, JS_KEY_with ``` -### Phase 2: Short Float Implementation - -Short float uses 3 fewer exponent bits than double. Numbers outside range become NULL. - +Runtime macro for strings >7 chars: ```c -/* Short float: 61 bits = 1 sign + 8 exp + 52 mantissa (vs double's 11 exp) - * Range: approximately +-3.4e38 (vs double's +-1.8e308) - * Out of range values become JS_NULL - * Zero and subnormals: 0.0 is representable, subnormals become 0.0 - */ -static inline JSValue JS_NewFloat64(JSContext *ctx, double d) { - union { double d; uint64_t u; } u; - u.d = d; - - /* Extract sign, exponent, mantissa */ - uint64_t sign = u.u >> 63; - int exp = (u.u >> 52) & 0x7FF; - uint64_t mantissa = u.u & ((1ULL << 52) - 1); - - /* Special case: zero (exp=0, mantissa=0) */ - if (exp == 0 && mantissa == 0) { - /* Encode +0.0 or -0.0 */ - return (sign << 63) | JS_TAG_SHORT_FLOAT; /* short_exp=0, mantissa=0 */ - } - - /* Check for NaN/Inf (exp=0x7FF) */ - if (exp == 0x7FF) { - return JS_NULL; /* NaN or Infinity → null */ - } - - /* Subnormals (exp=0, mantissa!=0): flush to zero */ - if (exp == 0) { - return (sign << 63) | JS_TAG_SHORT_FLOAT; /* becomes +/-0.0 */ - } - - /* Normal numbers: convert exponent bias */ - /* Double bias = 1023, short float bias = 127 */ - int short_exp = exp - 1023 + 127; - if (short_exp < 1 || short_exp > 254) { - return JS_NULL; /* Out of range (short_exp 0 and 255 are special) */ - } - - /* Check if it fits in int32 (prefer integer encoding) */ - if (d >= INT32_MIN && d <= INT32_MAX) { - int32_t i = (int32_t)d; - if ((double)i == d) { - return JS_MKINT(i); - } - } - - /* Encode as short float: - * [sign:1][short_exp:8][mantissa:52][tag:3] */ - JSValue v = (sign << 63) | ((uint64_t)short_exp << 55) | (mantissa << 3) | JS_TAG_SHORT_FLOAT; - return v; -} - -static inline double JS_VALUE_GET_FLOAT64(JSValue v) { - /* Decode short float back to double */ - uint64_t sign = v >> 63; - uint64_t short_exp = (v >> 55) & 0xFF; - uint64_t mantissa = (v >> 3) & ((1ULL << 52) - 1); - - /* Convert exponent: short bias 127 → double bias 1023 */ - uint64_t exp = short_exp - 127 + 1023; - - union { double d; uint64_t u; } u; - u.u = (sign << 63) | (exp << 52) | mantissa; - return u.d; -} +#define JS_KEY_STR(ctx, str) JS_NewStringLen((ctx), (str), sizeof(str) - 1) ``` -### Phase 3: Immediate ASCII String (JS_TAG_STRING_ASCII) - -Up to 7 ASCII characters stored directly in JSValue payload. - -**Layout (64-bit):** -- Bits 0-4: Tag (JS_TAG_STRING_ASCII = 27) -- Bits 5-7: Length (0-7) -- Bits 8-63: Up to 7 ASCII bytes (char[0] in bits 8-15, etc.) - +Helper function for comparing JSValue with C string literals: ```c -#define JS_ASCII_MAX_LEN 7 - -/* Check if value is immediate ASCII string */ -static inline JS_BOOL JS_IsImmediateASCII(JSValue v) { - return JS_VALUE_GET_SPECIAL_TAG(v) == JS_TAG_STRING_ASCII; -} - -/* Get immediate ASCII string length (bits 5-7) */ -static inline size_t JS_GetImmediateASCIILen(JSValue v) { - return (v >> 5) & 0x7; -} - -/* Get immediate ASCII string character at index */ -static inline char JS_GetImmediateASCIIChar(JSValue v, int idx) { - return (char)((v >> (8 + idx * 8)) & 0xFF); -} - -/* Try to create immediate ASCII string, returns JS_NULL if doesn't fit */ -static inline JSValue JS_TryNewImmediateASCII(const char *str, size_t len) { - if (len > JS_ASCII_MAX_LEN) return JS_NULL; - for (size_t i = 0; i < len; i++) { - if ((uint8_t)str[i] >= 0x80) return JS_NULL; /* non-ASCII */ - } - /* Tag (5 bits) | Length (3 bits) | chars (56 bits) */ - JSValue v = JS_TAG_STRING_ASCII | ((JSValue)len << 5); - for (size_t i = 0; i < len; i++) { - v |= ((JSValue)(uint8_t)str[i]) << (8 + i * 8); - } - return v; -} - -/* Hash an immediate ASCII string (hash the entire JSValue) */ -static inline uint64_t js_hash_immediate_ascii(JSValue v) { - fash64_state s; - fash64_begin(&s); - fash64_word(&s, v); - return fash64_end(&s); -} +static JS_BOOL js_key_equal_str(JSValue a, const char *str); ``` -### Phase 4: Remove JSStringRope +### Remaining Work -Delete `JSStringRope` structure and all rope-related functions: -- `js_new_string_rope()` (line 4815) -- `js_rebalancee_string_rope()` (line 4952) -- `string_rope_iter_*` functions -- `JS_TAG_STRING_ROPE` usage +#### 5.3 Update js_parse_property_name() +- [ ] Change return type from JSAtom* to JSValue* +- [ ] Update all callers (js_parse_object_literal, etc.) +- [ ] This is a larger change affecting many functions -String concatenation creates new `mist_text` objects immediately. +#### 5.4 Replace remaining emit_atom() calls with emit_key() +- [ ] Many emit_atom calls remain in bytecode generation +- [ ] emit_atom is currently a wrapper that calls emit_key +- [ ] Eventually remove emit_atom entirely -### Phase 5: UTF-32 Text Objects (mist_text) +#### 5.5 Update Variable Opcode Format in quickjs-opcode.h +- [ ] Change `atom` format opcodes to `key` format +- [ ] Change `atom_u8` and `atom_u16` to `key_u8` and `key_u16` -The `mist_text` structure already exists. Complete integration: +#### 5.6 Update VM Opcode Handlers +These read atoms from bytecode using get_u32(). Need to change to read cpool indices: +- [ ] OP_check_var, OP_get_var_undef, OP_get_var +- [ ] OP_put_var, OP_put_var_init, OP_put_var_strict +- [ ] OP_set_name, OP_make_var_ref, OP_delete_var +- [ ] OP_define_var, OP_define_func, OP_throw_error +- [ ] OP_make_loc_ref, OP_make_arg_ref -```c -/* Text object: UTF-32 packed 2 chars per 64-bit word - * Pretext (mutable, stone=0): hdr.cap = char capacity, length field = current length - * Text (immutable, stone=1): hdr.cap = length, length field = hash - */ -typedef struct mist_text { - objhdr_t hdr; /* type=OBJ_TEXT, cap=char count, stone bit */ - uint64_t length; /* pretext: char count | text: hash */ - uint64_t packed[]; /* UTF-32 chars, 2 per word (high then low) */ -} mist_text; +#### 5.7 Update resolve_scope_var() +- [ ] Currently reads var_name as atom from bytecode +- [ ] Compares with JS_ATOM_* constants +- [ ] Need to change to read cpool index and compare with JSValue -/* Create new text from UTF-8 C string */ -JSValue JS_NewStringLen(JSContext *ctx, const char *str, size_t len) { - /* Try immediate text first */ - JSValue imm = JS_TryNewImmediateText(str, len); - if (!JS_IsNull(imm)) return imm; +#### 5.8 Convert Remaining JS_ATOM_* Usages (~41 comparisons remain) +Categories: +- Bytecode reading (get_u32 reads atoms) - will change with opcode format +- js_parse_property_name callers - need function update first +- Stub atom functions - will be removed in Phase 7 - /* Convert UTF-8 to UTF-32 */ - uint32_t *utf32 = js_malloc(ctx, len * sizeof(uint32_t)); - size_t utf32_len = utf8_to_utf32(str, len, utf32); +--- - /* Allocate mist_text */ - size_t word_count = (utf32_len + 1) / 2; - mist_text *text = js_mallocz(ctx, sizeof(mist_text) + word_count * sizeof(uint64_t)); - text->hdr = objhdr_make(utf32_len, OBJ_TEXT, false, false, false, false); - text->length = utf32_len; +## Phase 6: Update Bytecode Serialization - /* Pack UTF-32 into words */ - for (size_t i = 0; i < utf32_len; i += 2) { - uint64_t hi = utf32[i]; - uint64_t lo = (i + 1 < utf32_len) ? utf32[i + 1] : 0; - text->packed[i / 2] = (hi << 32) | lo; - } +### 6.1 JS_WriteObjectTag Changes +- [ ] Write cpool values as JSValue (text serialization) +- [ ] Variable opcodes reference cpool indices (already u32) - js_free(ctx, utf32); - /* Add to GC list and return as JSValue */ - return JS_MKPTR(text); -} -``` +### 6.2 JS_ReadObject Changes +- [ ] Read cpool values as JSValue +- [ ] Variable opcode operands are cpool indices -### Phase 6: Remove JSObject, Use JSRecord Only +### 6.3 Version Bump +- [ ] Increment bytecode version to indicate new format -**Delete:** -- `JSObject` structure (line 1664) -- `JSShape` and `JSShapeProperty` structures -- Shape hash table in JSRuntime -- All shape-related functions -- `find_own_property()` and shape-based property access +--- -**Keep only JSRecord with direct key/value storage:** +## Phase 7: Final Cleanup -```c -/* Record: open-addressing hash table with JSValue keys - * Slot 0 reserved: key[0] = class_id<<32 | rec_key_id, value[0] = opaque - */ -/* Slot: key/value pair stored together */ -typedef struct JSSlot { - JSValue key; - JSValue val; -} JSSlot; +### 7.1 Remove JSAtom Type and Functions +- [ ] Remove `typedef uint32_t JSAtom` +- [ ] Remove JS_NewAtom, JS_NewAtomString +- [ ] Remove JS_FreeAtom, JS_DupAtom (currently stubs) +- [ ] Remove JS_AtomToValue, JS_ValueToAtom +- [ ] Remove JS_AtomToCString, JS_AtomGetStr +- [ ] Remove all JS_ATOM_* constants +- [ ] Remove JSAtomStruct and related -typedef struct JSRecord { - JSGCObjectHeader header; - objhdr_t mist_hdr; /* type=OBJ_RECORD, cap=slot_mask */ - struct JSRecord *proto; /* prototype chain */ - uint32_t len; /* number of live entries */ - uint32_t tombs; /* tombstone count */ - JSSlot *slots; /* key/value pairs, size = mask+1 */ -} JSRecord; +### 7.2 Remove quickjs-atom.h +- [ ] Delete or convert to JSValue text initialization -/* Three key types for property lookup: - * 1. Immediate ASCII (JS_TAG_STRING_ASCII): hash from JSValue itself - * 2. Text object (mist_text pointer): hash from object's stored hash - * 3. Record object used as key: hash from monotonic ID in record's key[0] - * - * Per memory.md: when a record is used as a key, it gets assigned a - * monotonically increasing 32-bit ID stored in lower 32 bits of keys[0]. - */ +### 7.3 Remove Legacy JSObject Type +- [ ] Remove JS_GC_OBJ_TYPE_JS_OBJECT if unused +- [ ] Remove JSObject struct (replaced by JSRecord) -/* Get hash for any key JSValue */ -static uint64_t js_key_hash(JSValue key) { - if (JS_IsImmediateASCII(key)) { - /* Hash the entire JSValue for immediate ASCII */ - return fash64_hash_one(key); - } +### 7.4 Update quickjs.h Public API +- [ ] Remove JSAtom references from public API +- [ ] Ensure all property functions use JSValue keys or const char* - if (!JS_IsPtr(key)) - return 0; /* Invalid key */ +--- - void *ptr = JS_VALUE_GET_PTR(key); - objhdr_t hdr = *(objhdr_t *)ptr; /* Read object header */ - uint8_t type = objhdr_type(hdr); +## Current Build Status - if (type == OBJ_TEXT) { - /* Text object: hash stored in length field (if stoned) or computed */ - mist_text *text = (mist_text *)ptr; - return get_text_hash(text); - } +**Build: SUCCEEDS** with warnings (unused variables, labels) - if (type == OBJ_RECORD) { - /* Record used as key: hash from monotonic ID in slots[0].key */ - JSRecord *rec = (JSRecord *)ptr; - uint32_t rec_id = (uint32_t)rec->slots[0].key; /* lower 32 bits */ - return fash64_hash_one(rec_id); - } +**Statistics:** +- JS_ATOM_* comparisons: ~41 remaining (down from 171+) +- Most remaining are in bytecode reading code (will change with opcode format) - return 0; /* Unknown type */ -} +**What Works:** +- Keyword detection via string comparison +- Function declaration parsing with JSValue names +- Variable definition with JSValue names +- Property access with JSValue keys +- Closure variable tracking with JSValue names -/* Ensure record has a key ID assigned (for use as property key) */ -static void rec_ensure_key_id(JSRuntime *rt, JSRecord *rec) { - uint32_t id = (uint32_t)rec->slots[0].key; - if (id == 0) { - /* Assign new monotonically increasing ID */ - id = ++rt->rec_key_next; - if (id == 0) id = ++rt->rec_key_next; /* Skip 0 */ - rec->slots[0].key = (rec->slots[0].key & 0xFFFFFFFF00000000ULL) | id; - } -} +**Next Priority:** +1. Update js_parse_property_name() to use JSValue +2. Update VM opcode handlers to read from cpool +3. Convert remaining bytecode-related JS_ATOM_* usages -/* Compare two keys for equality */ -static JS_BOOL js_key_equal(JSValue a, JSValue b) { - /* Fast path: identical values */ - if (a == b) return TRUE; - - /* Immediate ASCII: must be identical (handled above) */ - if (JS_IsImmediateASCII(a) || JS_IsImmediateASCII(b)) - return FALSE; - - /* Both must be pointers */ - if (!JS_IsPtr(a) || !JS_IsPtr(b)) - return FALSE; - - void *pa = JS_VALUE_GET_PTR(a); - void *pb = JS_VALUE_GET_PTR(b); - uint8_t ta = objhdr_type(*(objhdr_t *)pa); - uint8_t tb = objhdr_type(*(objhdr_t *)pb); - - /* Record keys: pointer equality (identity) */ - if (ta == OBJ_RECORD || tb == OBJ_RECORD) - return FALSE; /* Already checked a == b above */ - - /* Text objects: string content comparison */ - if (ta == OBJ_TEXT && tb == OBJ_TEXT) - return text_content_equal((mist_text *)pa, (mist_text *)pb); - - return FALSE; -} - -/* Property lookup using open-addressing hash table */ -static int rec_find_slot(JSRecord *rec, JSValue key) { - uint32_t mask = (uint32_t)objhdr_cap56(rec->mist_hdr); - uint64_t hash = js_key_hash(key); - uint32_t idx = hash & mask; - if (idx == 0) idx = 1; /* slot 0 reserved */ - - for (uint32_t i = 0; i <= mask; i++) { - JSValue k = rec->slots[idx].key; - if (JS_IsNull(k)) return -1; /* empty, not found */ - if (k == JS_EXCEPTION) { /* tombstone, continue */ - idx = (idx + 1) & mask; - if (idx == 0) idx = 1; - continue; - } - if (js_key_equal(k, key)) return idx; - idx = (idx + 1) & mask; - if (idx == 0) idx = 1; - } - return -1; -} -``` - -### Phase 7: Update Property Access Functions - -Replace all JSObject property functions with JSRecord equivalents: - -```c -JSValue JS_GetPropertyInternal(JSContext *ctx, JSValueConst this_obj, - JSValue prop, JS_BOOL throw_ref_error) { - if (!JS_IsPtr(this_obj)) { - if (throw_ref_error) - return JS_ThrowTypeError(ctx, "not an object"); - return JS_NULL; - } - - JSRecord *rec = (JSRecord *)JS_VALUE_GET_PTR(this_obj); - - while (rec) { - int idx = rec_find_slot(rec, prop); - if (idx >= 0) { - return rec->slots[idx].val; /* No dup needed if no ref counting */ - } - rec = rec->proto; - } - - if (throw_ref_error) - return JS_ThrowReferenceError(ctx, "property not found"); - return JS_NULL; -} - -int JS_SetPropertyInternal(JSContext *ctx, JSValueConst this_obj, - JSValue prop, JSValue val) { - if (!JS_IsPtr(this_obj)) - return -1; - - JSRecord *rec = (JSRecord *)JS_VALUE_GET_PTR(this_obj); - int idx = rec_find_slot(rec, prop); - - if (idx >= 0) { - rec->slots[idx].val = val; - return 0; - } - - /* Add new property */ - return rec_add_property(ctx, rec, prop, val); -} -``` - -### Phase 8: C Class Storage in Slot 0 - -Per memory.md, slot 0 is reserved for internal use: - -```c -/* slots[0].key: lower 32 bits = rec_key_id (for identity-based keys) - * upper 32 bits = class_id (C class) - * slots[0].val: opaque C pointer - */ - -void JS_SetOpaque(JSContext *ctx, JSValue obj, void *opaque) { - JSRecord *rec = (JSRecord *)JS_VALUE_GET_PTR(obj); - rec->slots[0].val = (JSValue)(uintptr_t)opaque; -} - -void *JS_GetOpaque(JSContext *ctx, JSValue obj, uint32_t class_id) { - JSRecord *rec = (JSRecord *)JS_VALUE_GET_PTR(obj); - uint32_t stored_class = (uint32_t)(rec->slots[0].key >> 32); - if (stored_class != class_id) return NULL; - return (void *)(uintptr_t)rec->slots[0].val; -} - -void JS_SetClassID(JSRecord *rec, uint32_t class_id) { - rec->slots[0].key = (rec->slots[0].key & 0xFFFFFFFF) | ((uint64_t)class_id << 32); -} -``` - -### Phase 9: Update GC - -The GC needs updates for the new object format: - -```c -static void mark_children(JSRuntime *rt, JSGCObjectHeader *gp, ...) { - switch (gp->gc_obj_type) { - case JS_GC_OBJ_TYPE_RECORD: - { - JSRecord *rec = (JSRecord *)gp; - uint32_t mask = objhdr_cap56(rec->mist_hdr); - - if (rec->proto) - mark_func(rt, &rec->proto->header); - - for (uint32_t i = 1; i <= mask; i++) { - if (!JS_IsNull(rec->keys[i]) && - rec->keys[i] != JS_EXCEPTION) { /* tombstone */ - /* Mark key if it's a pointer */ - if (JS_IsPtr(rec->keys[i])) - JS_MarkValue(rt, rec->keys[i], mark_func); - /* Mark value if it's a pointer */ - if (JS_IsPtr(rec->values[i])) - JS_MarkValue(rt, rec->values[i], mark_func); - } - } - } - break; - // ... other cases - } -} -``` - -## Cleanup - Items to Remove - -1. **quickjs.h:** - - Old NaN-boxing macros (JS_VALUE_GET_TAG, JS_MKVAL, etc.) - - JS_TAG_STRING, JS_TAG_STRING_ROPE, JS_TAG_OBJECT, JS_TAG_ARRAY, JS_TAG_FUNCTION - - JSValueConst (just use JSValue) - -2. **quickjs.c:** - - JSStringRope structure and functions - - JSShape and JSShapeProperty structures - - Shape hash table and functions - - Atom-based property access (keep atoms for parser/compiler) - - JSObject structure (replace with JSRecord) - - `find_own_property()`, `add_shape_property()`, etc. - -## Verification - -1. **Build:** `make` completes without errors -2. **Basic test:** Create objects, set/get properties -3. **Number test:** Verify short float encoding/decoding, out-of-range → null -4. **String test:** Immediate text for short strings, mist_text for long -5. **GC test:** Create cycles, verify collection works -6. **C class test:** SetOpaque/GetOpaque work with slot 0 storage +--- ## Notes -- This is a **major rework** affecting most of the codebase -- Atoms remain for parser/compiler but not for object property storage -- Reference counting may be simplified since fewer pointer types -- The short float range (+-3.4e38) covers most practical use cases -- Out-of-range numbers becoming NULL is intentional per memory.md +- JSVarDef.var_name is JSValue +- JSClosureVar.var_name is JSValue +- JSGlobalVar.var_name is JSValue +- JSFunctionDef.func_name is JSValue +- BlockEnv.label_name is JSValue +- OP_get_field/put_field/define_field already use cpool index format +- JSRecord with open addressing is fully implemented +- js_key_hash and js_key_equal work with both immediate and heap text +- js_key_equal_str enables comparison with C string literals for internal names +--- + +## Testing Strategy + +After each sub-phase: +1. `make` - verify compilation +2. Run basic eval: `./cell -e "1+1"` +3. Run property test: `./cell -e "var o = {a:1}; o.a"` +4. Run function test: `./cell -e "(function f(x){return x*2})(3)"` +5. Run closure test: `./cell -e "var f = (function(){var x=1;return function(){return x++}})(); f(); f()"` diff --git a/source/quickjs.c b/source/quickjs.c index 4884f073..bc6d6382 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -276,6 +276,68 @@ static inline JSAtom JS_NewAtomLen (JSContext *ctx, const char *str, size_t len) #define JS_ATOM_LAST_STRICT_KEYWORD 55 #define JS_ATOM_END 100 +/* 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') + +/* 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) + /* Buffer size for atom string conversion */ #define ATOM_GET_STR_BUF_SIZE 256 @@ -1059,6 +1121,90 @@ intern_text_to_value (JSRuntime *rt, const uint32_t *utf32, uint32_t len) { return JS_MKPTR (JS_TAG_STRING, text); } +/* Max length for key strings (identifiers, property names) */ +#define JS_KEY_MAX_LEN 4096 + +/* Create a stoned, interned key from a UTF-8 C string (runtime version). + 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). */ +static JSValue +js_key_new_rt (JSRuntime *rt, const char *str) { + size_t len = strlen (str); + + /* Try immediate ASCII first (≤7 ASCII chars) */ + if (len <= MIST_ASCII_MAX_LEN) { + JSValue imm = MIST_TryNewImmediateASCII (str, len); + if (!JS_IsNull (imm)) return imm; + } + + /* Check length limit */ + if (len > JS_KEY_MAX_LEN) return JS_NULL; + + /* Convert UTF-8 to UTF-32 for interning - use stack buffer */ + const uint8_t *p = (const uint8_t *)str; + const uint8_t *p_end = p + len; + uint32_t utf32_buf[JS_KEY_MAX_LEN]; + uint32_t utf32_len = 0; + + while (p < p_end && utf32_len < JS_KEY_MAX_LEN) { + uint32_t c; + const uint8_t *p_next; + if (*p < 0x80) { + c = *p++; + } else { + c = unicode_from_utf8 (p, p_end - p, &p_next); + if (c == (uint32_t)-1) { + c = 0xFFFD; /* replacement char for invalid UTF-8 */ + p++; + } else { + p = p_next; + } + } + utf32_buf[utf32_len++] = c; + } + + return intern_text_to_value (rt, utf32_buf, utf32_len); +} + +/* Create a key from a UTF-8 string with explicit length (runtime version) */ +static JSValue +js_key_new_len_rt (JSRuntime *rt, const char *str, size_t len) { + /* Try immediate ASCII first (≤7 ASCII chars) */ + if (len <= MIST_ASCII_MAX_LEN) { + JSValue imm = MIST_TryNewImmediateASCII (str, len); + if (!JS_IsNull (imm)) return imm; + } + + /* Check length limit */ + if (len > JS_KEY_MAX_LEN) return JS_NULL; + + /* Convert UTF-8 to UTF-32 for interning */ + const uint8_t *p = (const uint8_t *)str; + const uint8_t *p_end = p + len; + uint32_t utf32_buf[JS_KEY_MAX_LEN]; + uint32_t utf32_len = 0; + + while (p < p_end && utf32_len < JS_KEY_MAX_LEN) { + uint32_t c; + const uint8_t *p_next; + if (*p < 0x80) { + c = *p++; + } else { + c = unicode_from_utf8 (p, p_end - p, &p_next); + if (c == (uint32_t)-1) { + c = 0xFFFD; + p++; + } else { + p = p_next; + } + } + utf32_buf[utf32_len++] = c; + } + + return intern_text_to_value (rt, utf32_buf, utf32_len); +} + #ifdef RC_TRACE typedef struct RcEvent { JSGCObjectHeader *ptr; @@ -1430,6 +1576,34 @@ js_key_equal (JSValue a, JSValue b) { return FALSE; } +/* Compare a JSValue key with a C string literal. + Used for comparing with internal names that are too long for immediate ASCII. */ +static JS_BOOL +js_key_equal_str (JSValue a, const char *str) { + size_t len = strlen (str); + + if (MIST_IsImmediateASCII (a)) { + int imm_len = MIST_GetImmediateASCIILen (a); + if ((size_t)imm_len != len) return FALSE; + for (int i = 0; i < imm_len; i++) { + if (MIST_GetImmediateASCIIChar (a, i) != str[i]) return FALSE; + } + return TRUE; + } + + if (!JS_IsPtr (a)) return FALSE; + mist_text *ta = (mist_text *)JS_VALUE_GET_PTR (a); + if (objhdr_type (ta->hdr) != OBJ_TEXT) return FALSE; + uint64_t txt_len = objhdr_cap56 (ta->hdr); + if (txt_len != len) return FALSE; + + /* Compare character by character (UTF-32 vs ASCII) */ + for (size_t i = 0; i < len; i++) { + if (mist_string_get (ta, i) != (uint32_t)(unsigned char)str[i]) return FALSE; + } + return TRUE; +} + /* Find slot for a key in record's own table. Returns slot index (>0) if found, or -(insert_slot) if not found. */ static int @@ -1630,7 +1804,7 @@ typedef enum { typedef struct JSFunction { JSGCObjectHeader header; /* must come first */ - const char *name; + JSValue name; /* function name as JSValue text */ uint16_t length; /* arity: max allowed arguments */ uint8_t kind; uint8_t free_mark : 1; @@ -3623,7 +3797,7 @@ string_buffer_end (StringBuffer *s) { if (s->len == 0) { js_free (s->ctx, str); s->str = NULL; - return JS_AtomToString (s->ctx, JS_ATOM_empty_string); + return JS_KEY_empty; } if (s->len < s->size) { /* smaller size so js_realloc should not fail, but OK if it does */ @@ -4075,9 +4249,9 @@ static int js_method_set_properties (JSContext *ctx, JSValue func_obj, JSValue n (void)home_obj; if (JS_VALUE_GET_TAG (func_obj) != JS_TAG_FUNCTION) return -1; JSFunction *f = JS_VALUE_GET_FUNCTION (func_obj); - /* name is now const char* - convert from JSValue if string */ + /* name is now JSValue text */ if (JS_IsString (name)) { - f->name = JS_ToCString (ctx, name); + f->name = JS_DupValue (ctx, name); } return 0; } @@ -4099,7 +4273,7 @@ JS_NewCFunction3 (JSContext *ctx, JSCFunction *func, const char *name, f->u.cfunc.cproto = cproto; f->u.cfunc.magic = magic; f->length = length; - f->name = name ? name : ""; + f->name = name ? js_key_new_rt (ctx->rt, name) : JS_KEY_empty; return func_obj; } @@ -4165,7 +4339,7 @@ JS_NewCFunctionData (JSContext *ctx, JSCFunctionData *func, int length, f = JS_VALUE_GET_FUNCTION (func_obj); f->u.c_function_data_record = s; f->length = length; - f->name = JS_ATOM_empty_string; + f->name = JS_KEY_empty; return func_obj; } @@ -4299,7 +4473,7 @@ free_function (JSRuntime *rt, JSFunction *func) { func->free_mark = 1; /* used to tell the function is invalid when freeing cycles */ - JS_FreeAtomRT (rt, func->name); + JS_FreeValueRT (rt, func->name); switch (func->kind) { case JS_FUNC_KIND_C: @@ -5492,15 +5666,24 @@ build_backtrace (JSContext *ctx, JSValue error_obj, const char *filename, str = JS_NewString (ctx, filename); if (JS_IsException (str)) return; /* Note: SpiderMonkey does that, could update once there is a standard */ - if (JS_SetPropertyInternal (ctx, error_obj, JS_ATOM_fileName, str) < 0 - || JS_SetPropertyInternal (ctx, error_obj, JS_ATOM_lineNumber, + JSValue key_fileName = JS_KEY_STR (ctx, "fileName"); + JSValue key_lineNumber = JS_KEY_STR (ctx, "lineNumber"); + JSValue key_columnNumber = JS_KEY_STR (ctx, "columnNumber"); + if (JS_SetPropertyInternal (ctx, error_obj, key_fileName, str) < 0 + || JS_SetPropertyInternal (ctx, error_obj, key_lineNumber, JS_NewInt32 (ctx, line_num)) < 0 - || JS_SetPropertyInternal (ctx, error_obj, JS_ATOM_columnNumber, + || JS_SetPropertyInternal (ctx, error_obj, key_columnNumber, JS_NewInt32 (ctx, col_num)) < 0) { + JS_FreeValue (ctx, key_fileName); + JS_FreeValue (ctx, key_lineNumber); + JS_FreeValue (ctx, key_columnNumber); return; } + JS_FreeValue (ctx, key_fileName); + JS_FreeValue (ctx, key_lineNumber); + JS_FreeValue (ctx, key_columnNumber); } for (sf = ctx->rt->current_stack_frame; sf != NULL; sf = sf->prev_frame) { if (sf->js_mode & JS_MODE_BACKTRACE_BARRIER) break; @@ -5548,7 +5731,7 @@ build_backtrace (JSContext *ctx, JSValue error_obj, const char *filename, else str = JS_NewString (ctx, (char *)dbuf.buf); dbuf_free (&dbuf); - JS_SetPropertyInternal (ctx, error_obj, JS_ATOM_stack, str); + JS_SetPropertyInternal (ctx, error_obj, JS_KEY_stack, str); } /* Note: it is important that no exception is returned by this function */ @@ -5584,7 +5767,7 @@ JS_ThrowError2 (JSContext *ctx, JSErrorEnum error_num, const char *fmt, /* out of memory: throw JS_NULL to avoid recursing */ obj = JS_NULL; } else { - JS_SetPropertyInternal (ctx, obj, JS_ATOM_message, + JS_SetPropertyInternal (ctx, obj, JS_KEY_message, JS_NewString (ctx, buf)); if (add_backtrace) { build_backtrace (ctx, obj, NULL, 0, 0, 0); } } @@ -6047,12 +6230,22 @@ JSValue JS_GetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop) { if (JS_VALUE_GET_TAG (this_obj) != JS_TAG_OBJECT) return JS_NULL; - JSAtom atom; + size_t len = strlen (prop); + JSValue key; JSValue ret; - atom = JS_NewAtom (ctx, prop); - if (atom == JS_ATOM_NULL) return JS_EXCEPTION; - ret = JS_GetProperty (ctx, this_obj, atom); - JS_FreeAtom (ctx, atom); + + /* Try immediate ASCII first */ + if (len <= MIST_ASCII_MAX_LEN) { + key = MIST_TryNewImmediateASCII (prop, len); + if (JS_IsNull (key)) { + key = JS_NewStringLen (ctx, prop, len); + } + } else { + key = JS_NewStringLen (ctx, prop, len); + } + if (JS_IsException (key)) return JS_EXCEPTION; + ret = JS_GetProperty (ctx, this_obj, key); + JS_FreeValue (ctx, key); return ret; } @@ -7233,14 +7426,13 @@ JS_ToStringInternal (JSContext *ctx, JSValue val, BOOL is_ToPropertyKey) { return js_new_string8_len (ctx, buf, len); } break; case JS_TAG_BOOL: - return JS_AtomToString (ctx, JS_VALUE_GET_BOOL (val) ? JS_ATOM_true - : JS_ATOM_false); + return JS_VALUE_GET_BOOL (val) ? JS_KEY_true : JS_KEY_false; case JS_TAG_NULL: - return JS_AtomToString (ctx, JS_ATOM_null); + return JS_KEY_null; case JS_TAG_EXCEPTION: return JS_EXCEPTION; case JS_TAG_OBJECT: /* includes arrays (OBJ_ARRAY) via mist_hdr */ - return JS_AtomToString (ctx, JS_ATOM_true); + return JS_KEY_true; case JS_TAG_FUNCTION_BYTECODE: return js_new_string8 (ctx, "[function bytecode]"); case JS_TAG_SYMBOL: @@ -7633,9 +7825,9 @@ js_print_value (JSPrintValueState *s, JSValue val) { case JS_TAG_FUNCTION: { JSFunction *f = JS_VALUE_GET_FUNCTION (val); js_puts (s, "[Function"); - if (f->name && f->name[0]) { + if (JS_IsString (f->name)) { js_putc (s, ' '); - js_puts (s, f->name); + js_print_value (s, f->name); } else if (f->kind == JS_FUNC_KIND_BYTECODE && f->u.func.function_bytecode) { JSFunctionBytecode *b = f->u.func.function_bytecode; @@ -8431,7 +8623,6 @@ js_closure (JSContext *ctx, JSValue bfunc, JSVarRef **cur_var_refs, JSFunctionBytecode *b; JSValue func_obj; JSFunction *f; - JSAtom name_atom; b = JS_VALUE_GET_PTR (bfunc); func_obj = js_new_function (ctx, JS_FUNC_KIND_BYTECODE); @@ -8445,9 +8636,8 @@ js_closure (JSContext *ctx, JSValue bfunc, JSVarRef **cur_var_refs, goto fail; } f = JS_VALUE_GET_FUNCTION (func_obj); - name_atom = b->func_name; - if (name_atom == JS_ATOM_NULL) name_atom = JS_ATOM_empty_string; - f->name = JS_DupAtom (ctx, name_atom); + /* Use bytecode func_name if valid, otherwise empty string */ + f->name = JS_IsString (b->func_name) ? JS_DupValue (ctx, b->func_name) : JS_KEY_empty; f->length = b->arg_count; /* arity = total parameter count */ return func_obj; fail: @@ -8755,7 +8945,7 @@ JS_CallInternal (JSContext *caller_ctx, JSValue func_obj, char buf[ATOM_GET_STR_BUF_SIZE]; return JS_ThrowTypeError ( caller_ctx, "too many arguments for %s: expected %d, got %d", - JS_AtomGetStr (caller_ctx, buf, ATOM_GET_STR_BUF_SIZE, f->name), + JS_KeyGetStr (caller_ctx, buf, ATOM_GET_STR_BUF_SIZE, f->name), f->length, argc); } switch (f->kind) { @@ -8875,7 +9065,7 @@ restart: if (unlikely (JS_IsException (sp[-1]))) goto exception; BREAK; CASE (OP_push_empty_string) - : *sp++ = JS_AtomToString (ctx, JS_ATOM_empty_string); + : *sp++ = JS_KEY_empty; BREAK; #endif CASE (OP_null) : *sp++ = JS_NULL; @@ -9787,30 +9977,15 @@ restart: obj = sp[-1]; - /* Functions don't support property access in cell script */ - if (JS_IsFunction (obj)) { - JS_ThrowTypeError (ctx, "cannot get property of function"); - goto exception; - } - - /* Record fast path - use JSValue key directly */ + /* 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) { JSRecord *rec = JS_VALUE_GET_RECORD (obj); val = rec_get (ctx, rec, key); JS_FreeValue (ctx, obj); sp[-1] = val; - } else if (likely (JS_VALUE_GET_TAG (obj) == JS_TAG_OBJECT - && js_gc_obj_type (obj) == JS_GC_OBJ_TYPE_JS_OBJECT)) { - /* Legacy JSObject path - convert key to atom */ - JSAtom atom = JS_ValueToAtom (ctx, key); - val = JS_GetProperty (ctx, obj, atom); - JS_FreeAtom (ctx, atom); - if (unlikely (JS_IsException (val))) goto exception; - JS_FreeValue (ctx, sp[-1]); - sp[-1] = val; } else { - /* Fallback for non-object values */ + /* Non-record: return null */ JS_FreeValue (ctx, sp[-1]); sp[-1] = JS_NULL; } @@ -9840,20 +10015,12 @@ restart: *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) { - /* Record fast path - use JSValue key directly */ + /* Record property access - use JSValue key directly */ JSRecord *rec = JS_VALUE_GET_RECORD (obj); val = rec_get (ctx, rec, key); *sp++ = val; - } else if (likely (JS_VALUE_GET_TAG (obj) == JS_TAG_OBJECT - && js_gc_obj_type (obj) == JS_GC_OBJ_TYPE_JS_OBJECT)) { - /* Legacy JSObject path - convert key to atom */ - JSAtom atom = JS_ValueToAtom (ctx, key); - val = JS_GetProperty (ctx, obj, atom); - JS_FreeAtom (ctx, atom); - if (unlikely (JS_IsException (val))) goto exception; - *sp++ = val; } else { - /* Fallback for non-objects */ + /* Non-record: push null */ *sp++ = JS_NULL; } } @@ -9871,22 +10038,19 @@ restart: /* Get JSValue key from cpool */ key = b->cpool[idx]; - /* Functions don't support property assignment in cell script */ - if (!JS_IsObject (sp[-2])) { - JS_ThrowTypeError (ctx, "tried to set property of non-object"); + /* Must be a record to set property */ + if (!JS_IsObject (sp[-2]) + || js_gc_obj_type (sp[-2]) != JS_GC_OBJ_TYPE_RECORD) { + JS_FreeValue (ctx, sp[-1]); + JS_FreeValue (ctx, sp[-2]); + sp -= 2; + JS_ThrowTypeError (ctx, "cannot set property of non-record"); goto exception; } - /* Record fast path - use JSValue key directly */ - if (js_gc_obj_type (sp[-2]) == JS_GC_OBJ_TYPE_RECORD) { - JSRecord *rec = JS_VALUE_GET_RECORD (sp[-2]); - ret = rec_set_own (ctx, rec, key, sp[-1]); - } else { - /* Legacy JSObject path - convert key to atom */ - JSAtom atom = JS_ValueToAtom (ctx, key); - ret = JS_SetPropertyInternal (ctx, sp[-2], atom, sp[-1]); - JS_FreeAtom (ctx, atom); - } + /* Record property set - use JSValue key directly */ + JSRecord *rec = JS_VALUE_GET_RECORD (sp[-2]); + ret = rec_set_own (ctx, rec, key, sp[-1]); JS_FreeValue (ctx, sp[-2]); sp -= 2; if (unlikely (ret < 0)) goto exception; @@ -9904,17 +10068,18 @@ restart: /* Get JSValue key from cpool */ key = b->cpool[idx]; - /* Record fast path - use JSValue key directly */ - if (JS_VALUE_GET_TAG (sp[-2]) == JS_TAG_OBJECT - && js_gc_obj_type (sp[-2]) == JS_GC_OBJ_TYPE_RECORD) { - JSRecord *rec = JS_VALUE_GET_RECORD (sp[-2]); - ret = rec_set_own (ctx, rec, key, sp[-1]); - } else { - /* Legacy JSObject path - convert key to atom */ - JSAtom atom = JS_ValueToAtom (ctx, key); - ret = JS_SetPropertyInternal (ctx, sp[-2], atom, sp[-1]); - JS_FreeAtom (ctx, atom); + /* 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) { + JS_FreeValue (ctx, sp[-1]); + sp--; + JS_ThrowTypeError (ctx, "cannot define field on non-record"); + goto exception; } + + /* Record property set - use JSValue key directly */ + JSRecord *rec = JS_VALUE_GET_RECORD (sp[-2]); + ret = rec_set_own (ctx, rec, key, sp[-1]); sp--; if (unlikely (ret < 0)) goto exception; } @@ -10722,7 +10887,7 @@ restart: pc += 2; if (n <= 0) { - *sp++ = JS_AtomToString (ctx, JS_ATOM_empty_string); + *sp++ = JS_KEY_empty; BREAK; } @@ -10979,7 +11144,7 @@ enum { typedef struct BlockEnv { struct BlockEnv *prev; - JSAtom label_name; /* JS_ATOM_NULL if none */ + JSValue label_name; /* JS_NULL if none */ int label_break; /* -1 if none */ int label_cont; /* -1 if none */ int drop_count; /* number of stack elements to drop */ @@ -10995,7 +11160,7 @@ typedef struct JSGlobalVar { uint8_t is_lexical : 1; /* global let/const definition */ uint8_t is_const : 1; /* const definition */ int scope_level; /* scope of definition */ - JSAtom var_name; /* variable name */ + JSValue var_name; /* variable name as JSValue text */ } JSGlobalVar; typedef struct RelocEntry { @@ -11067,7 +11232,7 @@ typedef struct JSFunctionDef { BOOL in_function_body; JSParseFunctionEnum func_type : 8; uint8_t js_mode; /* bitmap of JS_MODE_x */ - JSAtom func_name; /* JS_ATOM_NULL if no name */ + JSValue func_name; /* JS_NULL if no name */ JSVarDef *vars; int var_size; /* allocated size for vars[] */ @@ -11153,7 +11318,7 @@ typedef struct JSToken { JSValue val; } num; struct { - JSAtom atom; + JSValue str; /* identifier as JSValue text */ BOOL has_escape; BOOL is_reserved; } ident; @@ -11234,11 +11399,11 @@ free_token (JSParseState *s, JSToken *token) { JS_FreeValue (s->ctx, token->u.regexp.flags); break; case TOK_IDENT: - JS_FreeAtom (s->ctx, token->u.ident.atom); + JS_FreeValue (s->ctx, token->u.ident.str); break; default: if (token->val >= TOK_FIRST_KEYWORD && token->val <= TOK_LAST_KEYWORD) { - JS_FreeAtom (s->ctx, token->u.ident.atom); + JS_FreeValue (s->ctx, token->u.ident.str); } break; } @@ -11253,10 +11418,10 @@ dump_token (JSParseState *s, const JSToken *token) { printf ("number: %.14g\n", d); } break; case TOK_IDENT: - dump_atom: { - char buf[ATOM_GET_STR_BUF_SIZE]; - printf ("ident: '%s'\n", - JS_AtomGetStr (s->ctx, buf, sizeof (buf), token->u.ident.atom)); + dump_ident: { + const char *str = JS_ToCString (s->ctx, token->u.ident.str); + printf ("ident: '%s'\n", str ? str : ""); + JS_FreeCString (s->ctx, str); } break; case TOK_STRING: { const char *str; @@ -11284,7 +11449,7 @@ dump_token (JSParseState *s, const JSToken *token) { break; default: if (s->token.val >= TOK_NULL && s->token.val <= TOK_LAST_KEYWORD) { - goto dump_atom; + goto dump_ident; } else if (s->token.val >= 256) { printf ("token: %d\n", token->val); } else { @@ -11413,7 +11578,7 @@ js_parse_error_reserved_identifier (JSParseState *s) { char buf1[ATOM_GET_STR_BUF_SIZE]; return js_parse_error ( s, "'%s' is a reserved identifier", - JS_AtomGetStr (s->ctx, buf1, sizeof (buf1), s->token.u.ident.atom)); + JS_AtomGetStr (s->ctx, buf1, sizeof (buf1), s->token.u.ident.str)); } static __exception int @@ -11595,8 +11760,8 @@ fail: } static inline BOOL -token_is_pseudo_keyword (JSParseState *s, JSAtom atom) { - return s->token.val == TOK_IDENT && s->token.u.ident.atom == atom +token_is_pseudo_keyword (JSParseState *s, JSValue key) { + return s->token.val == TOK_IDENT && js_key_equal (s->token.u.ident.str, key) && !s->token.u.ident.has_escape; } @@ -11721,20 +11886,107 @@ ident_realloc (JSContext *ctx, char **pbuf, size_t *psize, char *static_buf) { return 0; } +/* keyword lookup table */ +typedef struct { + const char *name; + int token; + BOOL is_strict_reserved; /* TRUE if reserved only in strict mode */ +} JSKeywordEntry; + +static const JSKeywordEntry js_keywords[] = { + { "null", TOK_NULL, FALSE }, + { "false", TOK_FALSE, FALSE }, + { "true", TOK_TRUE, FALSE }, + { "if", TOK_IF, FALSE }, + { "else", TOK_ELSE, FALSE }, + { "return", TOK_RETURN, FALSE }, + { "go", TOK_GO, FALSE }, + { "var", TOK_VAR, FALSE }, + { "def", TOK_DEF, FALSE }, + { "this", TOK_THIS, FALSE }, + { "delete", TOK_DELETE, FALSE }, + { "void", TOK_VOID, FALSE }, + { "new", TOK_NEW, FALSE }, + { "in", TOK_IN, FALSE }, + { "do", TOK_DO, FALSE }, + { "while", TOK_WHILE, FALSE }, + { "for", TOK_FOR, FALSE }, + { "break", TOK_BREAK, FALSE }, + { "continue", TOK_CONTINUE, FALSE }, + { "switch", TOK_SWITCH, FALSE }, + { "case", TOK_CASE, FALSE }, + { "default", TOK_DEFAULT, FALSE }, + { "throw", TOK_THROW, FALSE }, + { "try", TOK_TRY, FALSE }, + { "catch", TOK_CATCH, FALSE }, + { "finally", TOK_FINALLY, FALSE }, + { "function", TOK_FUNCTION, FALSE }, + { "debugger", TOK_DEBUGGER, FALSE }, + { "with", TOK_WITH, FALSE }, + { "class", TOK_CLASS, FALSE }, + { "const", TOK_CONST, FALSE }, + { "enum", TOK_ENUM, FALSE }, + { "export", TOK_EXPORT, FALSE }, + { "extends", TOK_EXTENDS, FALSE }, + { "import", TOK_IMPORT, FALSE }, + { "super", TOK_SUPER, FALSE }, + /* strict mode future reserved words */ + { "implements", TOK_IMPLEMENTS, TRUE }, + { "interface", TOK_INTERFACE, TRUE }, + { "let", TOK_LET, TRUE }, + { "private", TOK_PRIVATE, TRUE }, + { "protected", TOK_PROTECTED, TRUE }, + { "public", TOK_PUBLIC, TRUE }, + { "static", TOK_STATIC, TRUE }, + { "yield", TOK_YIELD, TRUE }, + { "await", TOK_AWAIT, TRUE }, +}; + +#define JS_KEYWORD_COUNT (sizeof(js_keywords) / sizeof(js_keywords[0])) + +/* lookup keyword by JSValue string, return token or -1 if not found */ +static int +js_keyword_lookup (JSValue str, BOOL *is_strict_reserved) { + char buf[16]; + const char *cstr; + size_t len; + + if (MIST_IsImmediateASCII (str)) { + len = MIST_GetImmediateASCIILen (str); + if (len >= sizeof (buf)) return -1; + for (size_t i = 0; i < len; i++) { + buf[i] = MIST_GetImmediateASCIIChar (str, i); + } + buf[len] = '\0'; + cstr = buf; + } else { + /* heap strings longer than immediate can't be keywords */ + return -1; + } + + for (size_t i = 0; i < JS_KEYWORD_COUNT; i++) { + if (strcmp (cstr, js_keywords[i].name) == 0) { + if (is_strict_reserved) + *is_strict_reserved = js_keywords[i].is_strict_reserved; + return js_keywords[i].token; + } + } + return -1; +} + /* convert a TOK_IDENT to a keyword when needed */ static void update_token_ident (JSParseState *s) { - JSAtom atom = s->token.u.ident.atom; - /* if it’s a (strict-mode) keyword, convert it */ - if (atom <= JS_ATOM_LAST_STRICT_KEYWORD) { + BOOL is_strict_reserved = FALSE; + int token = js_keyword_lookup (s->token.u.ident.str, &is_strict_reserved); + + if (token >= 0) { if (s->token.u.ident.has_escape) { /* identifiers with escape sequences stay identifiers */ s->token.u.ident.is_reserved = TRUE; s->token.val = TOK_IDENT; } else { - /* keyword atoms are laid out so: - TOK_FIRST_KEYWORD + (atom - 1) == the right token code */ - s->token.val = TOK_FIRST_KEYWORD + (atom - 1); + s->token.val = token; } } } @@ -11752,14 +12004,14 @@ reparse_ident_token (JSParseState *s) { } } -/* 'c' is the first character. Return JS_ATOM_NULL in case of error */ -static JSAtom +/* 'c' is the first character. Return JS_NULL in case of error */ +static JSValue parse_ident (JSParseState *s, const uint8_t **pp, BOOL *pident_has_escape, int c) { const uint8_t *p, *p1; char ident_buf[128], *buf; size_t ident_size, ident_pos; - JSAtom atom; + JSValue str; p = *pp; buf = ident_buf; @@ -11784,16 +12036,20 @@ parse_ident (JSParseState *s, const uint8_t **pp, BOOL *pident_has_escape, p = p1; if (unlikely (ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { if (ident_realloc (s->ctx, &buf, &ident_size, ident_buf)) { - atom = JS_ATOM_NULL; + str = JS_NULL; goto done; } } } - atom = JS_NewAtomLen (s->ctx, buf, ident_pos); + /* Create JSValue string - try immediate ASCII first */ + str = MIST_TryNewImmediateASCII (buf, ident_pos); + if (JS_IsNull (str)) { + str = JS_NewStringLen (s->ctx, buf, ident_pos); + } done: if (unlikely (buf != ident_buf)) js_free (s->ctx, buf); *pp = p; - return atom; + return str; } static __exception int @@ -11801,7 +12057,7 @@ next_token (JSParseState *s) { const uint8_t *p; int c; BOOL ident_has_escape; - JSAtom atom; + JSValue ident_str; if (js_check_stack_overflow (s->ctx->rt, 0)) { return js_parse_error (s, "stack overflow"); @@ -11972,9 +12228,9 @@ redo: p++; ident_has_escape = FALSE; has_ident: - atom = parse_ident (s, &p, &ident_has_escape, c); - if (atom == JS_ATOM_NULL) goto fail; - s->token.u.ident.atom = atom; + ident_str = parse_ident (s, &p, &ident_has_escape, c); + if (JS_IsNull (ident_str)) goto fail; + s->token.u.ident.str = ident_str; s->token.u.ident.has_escape = ident_has_escape; s->token.u.ident.is_reserved = FALSE; s->token.val = TOK_IDENT; @@ -12225,14 +12481,14 @@ fail: return -1; } -/* 'c' is the first character. Return JS_ATOM_NULL in case of error */ +/* 'c' is the first character. Return JS_NULL in case of error */ /* XXX: accept unicode identifiers as JSON5 ? */ -static JSAtom +static JSValue json_parse_ident (JSParseState *s, const uint8_t **pp, int c) { const uint8_t *p; char ident_buf[128], *buf; size_t ident_size, ident_pos; - JSAtom atom; + JSValue str; p = *pp; buf = ident_buf; @@ -12245,16 +12501,20 @@ json_parse_ident (JSParseState *s, const uint8_t **pp, int c) { p++; if (unlikely (ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { if (ident_realloc (s->ctx, &buf, &ident_size, ident_buf)) { - atom = JS_ATOM_NULL; + str = JS_NULL; goto done; } } } - atom = JS_NewAtomLen (s->ctx, buf, ident_pos); + /* Create JSValue string - try immediate ASCII first */ + str = MIST_TryNewImmediateASCII (buf, ident_pos); + if (JS_IsNull (str)) { + str = JS_NewStringLen (s->ctx, buf, ident_pos); + } done: if (unlikely (buf != ident_buf)) js_free (s->ctx, buf); *pp = p; - return atom; + return str; } static int @@ -12432,7 +12692,7 @@ static __exception int json_next_token (JSParseState *s) { const uint8_t *p; int c; - JSAtom atom; + JSValue ident_str; if (js_check_stack_overflow (s->ctx->rt, 0)) { return js_parse_error (s, "stack overflow"); @@ -12582,9 +12842,9 @@ redo: case '_': case '$': p++; - atom = json_parse_ident (s, &p, c); - if (atom == JS_ATOM_NULL) goto fail; - s->token.u.ident.atom = atom; + ident_str = json_parse_ident (s, &p, c); + if (JS_IsNull (ident_str)) goto fail; + s->token.u.ident.str = ident_str; s->token.u.ident.has_escape = FALSE; s->token.u.ident.is_reserved = FALSE; s->token.val = TOK_IDENT; @@ -12799,34 +13059,37 @@ emit_op (JSParseState *s, uint8_t val) { dbuf_putc (bc, val); } -static void -emit_atom (JSParseState *s, JSAtom name) { - DynBuf *bc = &s->cur_func->byte_code; - if (dbuf_realloc (bc, bc->size + 4)) - return; /* not enough memory : don't duplicate the atom */ - put_u32 (bc->buf + bc->size, JS_DupAtom (s->ctx, name)); - bc->size += 4; -} - /* Forward declarations for cpool */ static int fd_cpool_add (JSContext *ctx, JSFunctionDef *fd, JSValue val); static int cpool_add (JSParseState *s, JSValue val); -/* Emit a property key for field access opcodes. - The key is converted to a JSValue and added to cpool; the index is emitted. */ +/* Emit a key (JSValue) by adding to cpool and emitting the index. + Used for variable opcodes (get_var, put_var, etc.) and property ops. */ static int -emit_prop_key (JSParseState *s, JSAtom atom) { - JSValue key = JS_AtomToValue (s->ctx, atom); - if (JS_IsException (key)) return -1; - int idx = cpool_add (s, key); - if (idx < 0) { - JS_FreeValue (s->ctx, key); - return -1; - } +emit_key (JSParseState *s, JSValue name) { + int idx = cpool_add (s, JS_DupValue (s->ctx, name)); + if (idx < 0) return -1; emit_u32 (s, idx); return 0; } +/* Legacy emit_atom - now forwards to emit_key. + TODO: Remove once all callers converted. */ +static void +emit_atom (JSParseState *s, JSAtom name) { + JSValue key = JS_AtomToValue (s->ctx, name); + if (JS_IsException (key)) return; + if (emit_key (s, key) < 0) { + JS_FreeValue (s->ctx, key); + } +} + +/* Emit a property key for field access opcodes. */ +static int +emit_prop_key (JSParseState *s, JSValue key) { + return emit_key (s, key); +} + static int update_label (JSFunctionDef *s, int label, int delta) { LabelSlot *ls; @@ -12975,17 +13238,17 @@ find_var_in_child_scope (JSContext *ctx, JSFunctionDef *fd, JSAtom name, } static JSGlobalVar * -find_global_var (JSFunctionDef *fd, JSAtom name) { +find_global_var (JSFunctionDef *fd, JSValue name) { int i; for (i = 0; i < fd->global_var_count; i++) { JSGlobalVar *hf = &fd->global_vars[i]; - if (hf->var_name == name) return hf; + if (js_key_equal (hf->var_name, name)) return hf; } return NULL; } static JSGlobalVar * -find_lexical_global_var (JSFunctionDef *fd, JSAtom name) { +find_lexical_global_var (JSFunctionDef *fd, JSValue name) { JSGlobalVar *hf = find_global_var (fd, name); if (hf && hf->is_lexical) return hf; @@ -12994,11 +13257,11 @@ find_lexical_global_var (JSFunctionDef *fd, JSAtom name) { } static int -find_lexical_decl (JSContext *ctx, JSFunctionDef *fd, JSAtom name, +find_lexical_decl (JSContext *ctx, JSFunctionDef *fd, JSValue name, int scope_idx, BOOL check_catch_var) { while (scope_idx >= 0) { JSVarDef *vd = &fd->vars[scope_idx]; - if (vd->var_name == name + if (js_key_equal (vd->var_name, name) && (vd->is_lexical || (vd->var_kind == JS_VAR_CATCH && check_catch_var))) return scope_idx; @@ -13081,7 +13344,7 @@ close_scopes (JSParseState *s, int scope, int scope_stop) { /* return the variable index or -1 if error */ static int -add_var (JSContext *ctx, JSFunctionDef *fd, JSAtom name) { +add_var (JSContext *ctx, JSFunctionDef *fd, JSValue name) { JSVarDef *vd; /* the local variable indexes are currently stored on 16 bits */ @@ -13094,13 +13357,13 @@ add_var (JSContext *ctx, JSFunctionDef *fd, JSAtom name) { return -1; vd = &fd->vars[fd->var_count++]; memset (vd, 0, sizeof (*vd)); - vd->var_name = JS_DupAtom (ctx, name); + vd->var_name = JS_DupValue (ctx, name); vd->func_pool_idx = -1; return fd->var_count - 1; } static int -add_scope_var (JSContext *ctx, JSFunctionDef *fd, JSAtom name, +add_scope_var (JSContext *ctx, JSFunctionDef *fd, JSValue name, JSVarKindEnum var_kind) { int idx = add_var (ctx, fd, name); if (idx >= 0) { @@ -13115,7 +13378,7 @@ add_scope_var (JSContext *ctx, JSFunctionDef *fd, JSAtom name, } static int -add_func_var (JSContext *ctx, JSFunctionDef *fd, JSAtom name) { +add_func_var (JSContext *ctx, JSFunctionDef *fd, JSValue name) { int idx = fd->func_var_idx; if (idx < 0 && (idx = add_var (ctx, fd, name)) >= 0) { fd->func_var_idx = idx; @@ -13126,7 +13389,7 @@ add_func_var (JSContext *ctx, JSFunctionDef *fd, JSAtom name) { } static int -add_arg (JSContext *ctx, JSFunctionDef *fd, JSAtom name) { +add_arg (JSContext *ctx, JSFunctionDef *fd, JSValue name) { JSVarDef *vd; /* the local variable indexes are currently stored on 16 bits */ @@ -13139,14 +13402,14 @@ add_arg (JSContext *ctx, JSFunctionDef *fd, JSAtom name) { return -1; vd = &fd->args[fd->arg_count++]; memset (vd, 0, sizeof (*vd)); - vd->var_name = JS_DupAtom (ctx, name); + vd->var_name = JS_DupValue (ctx, name); vd->func_pool_idx = -1; return fd->arg_count - 1; } /* add a global variable definition */ static JSGlobalVar * -add_global_var (JSContext *ctx, JSFunctionDef *s, JSAtom name) { +add_global_var (JSContext *ctx, JSFunctionDef *s, JSValue name) { JSGlobalVar *hf; if (js_resize_array (ctx, (void **)&s->global_vars, @@ -13159,7 +13422,7 @@ add_global_var (JSContext *ctx, JSFunctionDef *s, JSAtom name) { hf->is_lexical = FALSE; hf->is_const = FALSE; hf->scope_level = s->scope_level; - hf->var_name = JS_DupAtom (ctx, name); + hf->var_name = JS_DupValue (ctx, name); return hf; } @@ -13174,7 +13437,7 @@ typedef enum { } JSVarDefEnum; static int -define_var (JSParseState *s, JSFunctionDef *fd, JSAtom name, +define_var (JSParseState *s, JSFunctionDef *fd, JSValue name, JSVarDefEnum var_def_type) { JSContext *ctx = s->ctx; JSVarDef *vd; @@ -13284,11 +13547,11 @@ define_var (JSParseState *s, JSFunctionDef *fd, JSAtom name, static __exception int js_parse_expr (JSParseState *s); static __exception int js_parse_function_decl (JSParseState *s, JSParseFunctionEnum func_type, - JSAtom func_name, + JSValue func_name, const uint8_t *ptr); static __exception int js_parse_function_decl2 (JSParseState *s, JSParseFunctionEnum func_type, - JSAtom func_name, + JSValue func_name, const uint8_t *ptr, JSFunctionDef **pfd); static __exception int js_parse_assign_expr2 (JSParseState *s, @@ -13320,7 +13583,7 @@ js_parse_template (JSParseState *s, int call, int *argc) { if (ret) return -1; raw_array = JS_NewArray (ctx); if (JS_IsException (raw_array)) return -1; - if (JS_SetPropertyInternal (ctx, template_object, JS_ATOM_raw, raw_array) + if (JS_SetPropertyInternal (ctx, template_object, JS_KEY_raw, raw_array) < 0) { return -1; } @@ -13418,7 +13681,7 @@ js_parse_property_name (JSParseState *s, JSAtom *pname, BOOL allow_method, is_non_reserved_ident = (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved); /* keywords and reserved words have a valid atom */ - name = JS_DupAtom (s->ctx, s->token.u.ident.atom); + name = JS_DupAtom (s->ctx, s->token.u.ident.str); if (next_token (s)) goto fail1; ident_found: if (is_non_reserved_ident && prop_type == PROP_TYPE_IDENT && allow_var) { @@ -13597,8 +13860,8 @@ js_parse_skip_parens_token (JSParseState *s, int *pbits, } /* last_tok is only used to recognize regexps */ if (s->token.val == TOK_IDENT - && (token_is_pseudo_keyword (s, JS_ATOM_of) - || token_is_pseudo_keyword (s, JS_ATOM_yield))) { + && (token_is_pseudo_keyword (s, JS_KEY_of) + || token_is_pseudo_keyword (s, JS_KEY_yield))) { last_tok = TOK_OF; } else { last_tok = s->token.val; @@ -13610,7 +13873,7 @@ js_parse_skip_parens_token (JSParseState *s, int *pbits, } if (level <= 1) { tok = s->token.val; - if (token_is_pseudo_keyword (s, JS_ATOM_of)) tok = TOK_OF; + if (token_is_pseudo_keyword (s, JS_KEY_of)) tok = TOK_OF; if (no_line_terminator && has_lf_in_range (last_token_ptr, s->token.ptr)) tok = '\n'; break; @@ -13700,7 +13963,7 @@ js_parse_object_literal (JSParseState *s) { int op_flags; func_type = JS_PARSE_FUNC_METHOD; - if (js_parse_function_decl (s, func_type, JS_ATOM_NULL, start_ptr)) + if (js_parse_function_decl (s, func_type, JS_NULL, start_ptr)) goto fail; if (name == JS_ATOM_NULL) { emit_op (s, OP_define_method_computed); @@ -13803,7 +14066,7 @@ has_with_scope (JSFunctionDef *s, int scope_level) { while (scope_idx >= 0) { JSVarDef *vd = &s->vars[scope_idx]; - if (vd->var_name == JS_ATOM__with_) return TRUE; + if (js_key_equal_str (vd->var_name, "_with_")) return TRUE; scope_idx = vd->scope_next; } /* check parent scopes */ @@ -14018,14 +14281,14 @@ js_unsupported_keyword (JSParseState *s, JSAtom atom) { } static __exception int -js_define_var (JSParseState *s, JSAtom name, int tok) { +js_define_var (JSParseState *s, JSValue name, int tok) { JSFunctionDef *fd = s->cur_func; JSVarDefEnum var_def_type; - if (name == JS_ATOM_eval) { + if (js_key_equal (name, JS_KEY_eval)) { return js_parse_error (s, "invalid variable name in strict mode"); } - if (name == JS_ATOM_let && tok == TOK_DEF) { + if (js_key_equal (name, JS_KEY_let) && tok == TOK_DEF) { return js_parse_error (s, "invalid lexical variable name"); } switch (tok) { @@ -14046,15 +14309,15 @@ js_define_var (JSParseState *s, JSAtom name, int tok) { } static int -js_parse_check_duplicate_parameter (JSParseState *s, JSAtom name) { +js_parse_check_duplicate_parameter (JSParseState *s, JSValue name) { /* Check for duplicate parameter names */ JSFunctionDef *fd = s->cur_func; int i; for (i = 0; i < fd->arg_count; i++) { - if (fd->args[i].var_name == name) goto duplicate; + if (js_key_equal (fd->args[i].var_name, name)) goto duplicate; } for (i = 0; i < fd->var_count; i++) { - if (fd->vars[i].var_name == name) goto duplicate; + if (js_key_equal (fd->vars[i].var_name, name)) goto duplicate; } return 0; @@ -14063,23 +14326,23 @@ duplicate: s, "duplicate parameter names not allowed in this context"); } -static JSAtom +static JSValue js_parse_destructuring_var (JSParseState *s, int tok, int is_arg) { - JSAtom name; + JSValue name; if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) - || s->token.u.ident.atom == JS_ATOM_eval) { + || js_key_equal (s->token.u.ident.str, JS_KEY_eval)) { js_parse_error (s, "invalid destructuring target"); - return JS_ATOM_NULL; + return JS_NULL; } - name = JS_DupAtom (s->ctx, s->token.u.ident.atom); + name = JS_DupValue (s->ctx, s->token.u.ident.str); if (is_arg && js_parse_check_duplicate_parameter (s, name)) goto fail; if (next_token (s)) goto fail; return name; fail: - JS_FreeAtom (s->ctx, name); - return JS_ATOM_NULL; + JS_FreeValue (s->ctx, name); + return JS_NULL; } /* Return -1 if error, 0 if no initializer, 1 if an initializer is @@ -14156,7 +14419,7 @@ js_parse_destructuring_element (JSParseState *s, int tok, int is_arg, } if (tok) { var_name = js_parse_destructuring_var (s, tok, is_arg); - if (var_name == JS_ATOM_NULL) goto prop_error; + if (JS_IsNull (var_name)) goto prop_error; /* no need to make a reference for let/const */ opcode = OP_scope_get_var; scope = s->cur_func->scope_level; @@ -14397,7 +14660,7 @@ js_parse_postfix_expr (JSParseState *s, int parse_flags) { if (js_parse_expr_paren (s)) return -1; break; case TOK_FUNCTION: - if (js_parse_function_decl (s, JS_PARSE_FUNC_EXPR, JS_ATOM_NULL, + if (js_parse_function_decl (s, JS_PARSE_FUNC_EXPR, JS_NULL, s->token.ptr)) return -1; break; @@ -14426,7 +14689,7 @@ js_parse_postfix_expr (JSParseState *s, int parse_flags) { return js_parse_error_reserved_identifier (s); } source_ptr = s->token.ptr; - name = JS_DupAtom (s->ctx, s->token.u.ident.atom); + name = JS_DupAtom (s->ctx, s->token.u.ident.str); if (next_token (s)) { JS_FreeAtom (s->ctx, name); return -1; @@ -14623,7 +14886,7 @@ js_parse_postfix_expr (JSParseState *s, int parse_flags) { optional_chain_test (s, &optional_chaining_label, 1); } emit_op (s, OP_get_field); - emit_prop_key (s, s->token.u.ident.atom); + emit_prop_key (s, s->token.u.ident.str); if (next_token (s)) return -1; } else if (s->token.val == '[') { op_token_ptr = s->token.ptr; @@ -15051,10 +15314,10 @@ js_parse_assign_expr2 (JSParseState *s, int parse_flags) { if (s->token.val == '(' && js_parse_skip_parens_token (s, NULL, TRUE) == TOK_ARROW) { - return js_parse_function_decl (s, JS_PARSE_FUNC_ARROW, JS_ATOM_NULL, + return js_parse_function_decl (s, JS_PARSE_FUNC_ARROW, JS_NULL, s->token.ptr); } else if (s->token.val == TOK_IDENT && peek_token (s, TRUE) == TOK_ARROW) { - return js_parse_function_decl (s, JS_PARSE_FUNC_ARROW, JS_ATOM_NULL, + return js_parse_function_decl (s, JS_PARSE_FUNC_ARROW, JS_NULL, s->token.ptr); } else if ((s->token.val == '{' || s->token.val == '[') && js_parse_skip_parens_token (s, &skip_bits, FALSE) == '=') { @@ -15065,7 +15328,7 @@ js_parse_assign_expr2 (JSParseState *s, int parse_flags) { next: if (s->token.val == TOK_IDENT) { /* name0 is used to check for OP_set_name pattern, not duplicated */ - name0 = s->token.u.ident.atom; + name0 = s->token.u.ident.str; } if (js_parse_cond_expr (s, parse_flags)) return -1; @@ -15310,15 +15573,15 @@ static __exception int js_parse_var (JSParseState *s, int parse_flags, int tok, BOOL export_flag) { JSContext *ctx = s->ctx; JSFunctionDef *fd = s->cur_func; - JSAtom name = JS_ATOM_NULL; + JSValue name = JS_NULL; for (;;) { if (s->token.val == TOK_IDENT) { if (s->token.u.ident.is_reserved) { return js_parse_error_reserved_identifier (s); } - name = JS_DupAtom (ctx, s->token.u.ident.atom); - if (name == JS_ATOM_let && tok == TOK_DEF) { + name = JS_DupValue (ctx, s->token.u.ident.str); + if (js_key_equal (name, JS_KEY_let) && tok == TOK_DEF) { js_parse_error (s, "'let' is not a valid lexical identifier"); goto var_error; } @@ -15332,7 +15595,7 @@ js_parse_var (JSParseState *s, int parse_flags, int tok, BOOL export_flag) { set_object_name (s, name); emit_op (s, (tok == TOK_DEF || tok == TOK_VAR) ? OP_scope_put_var_init : OP_scope_put_var); - emit_atom (s, name); + emit_key (s, name); emit_u16 (s, fd->scope_level); } else { if (tok == TOK_DEF) { @@ -15343,11 +15606,11 @@ js_parse_var (JSParseState *s, int parse_flags, int tok, BOOL export_flag) { /* initialize lexical variable upon entering its scope */ emit_op (s, OP_null); emit_op (s, OP_scope_put_var_init); - emit_atom (s, name); + emit_key (s, name); emit_u16 (s, fd->scope_level); } } - JS_FreeAtom (ctx, name); + JS_FreeValue (ctx, name); } else { int skip_bits; if ((s->token.val == '[' || s->token.val == '{') @@ -15366,7 +15629,7 @@ js_parse_var (JSParseState *s, int parse_flags, int tok, BOOL export_flag) { return 0; var_error: - JS_FreeAtom (ctx, name); + JS_FreeValue (ctx, name); return -1; } @@ -15384,7 +15647,7 @@ is_let (JSParseState *s, int decl_mask) { int res = FALSE; const uint8_t *last_token_ptr; - if (token_is_pseudo_keyword (s, JS_ATOM_let)) { + if (token_is_pseudo_keyword (s, JS_KEY_let)) { JSParsePos pos; js_parse_get_pos (s, &pos); for (;;) { @@ -15446,7 +15709,7 @@ js_parse_statement_or_decl (JSParseState *s, int decl_mask) { if (is_label (s)) { BlockEnv *be; - label_name = JS_DupAtom (ctx, s->token.u.ident.atom); + label_name = JS_DupAtom (ctx, s->token.u.ident.str); for (be = s->cur_func->top_break; be; be = be->prev) { if (be->label_name == label_name) { @@ -15724,7 +15987,7 @@ js_parse_statement_or_decl (JSParseState *s, int decl_mask) { if (next_token (s)) goto fail; if (!s->got_lf && s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) - label = s->token.u.ident.atom; + label = s->token.u.ident.str; else label = JS_ATOM_NULL; if (emit_break (s, label, is_cont)) goto fail; @@ -15873,7 +16136,7 @@ js_parse_statement_or_decl (JSParseState *s, int decl_mask) { goto fail; } } else { - name = JS_DupAtom (ctx, s->token.u.ident.atom); + name = JS_DupAtom (ctx, s->token.u.ident.str); if (next_token (s) || js_define_var (s, name, TOK_CATCH) < 0) { JS_FreeAtom (ctx, name); goto fail; @@ -15977,7 +16240,7 @@ js_parse_statement_or_decl (JSParseState *s, int decl_mask) { goto fail; } /* let keyword is no longer supported */ - if (token_is_pseudo_keyword (s, JS_ATOM_async) + if (token_is_pseudo_keyword (s, JS_KEY_async) && peek_token (s, TRUE) == TOK_FUNCTION) { if (!(decl_mask & DECL_MASK_OTHER)) { func_decl_error: @@ -15986,7 +16249,7 @@ js_parse_statement_or_decl (JSParseState *s, int decl_mask) { goto fail; } parse_func_var: - if (js_parse_function_decl (s, JS_PARSE_FUNC_VAR, JS_ATOM_NULL, + if (js_parse_function_decl (s, JS_PARSE_FUNC_VAR, JS_NULL, s->token.ptr)) goto fail; break; @@ -16001,7 +16264,7 @@ js_parse_statement_or_decl (JSParseState *s, int decl_mask) { case TOK_ENUM: case TOK_EXPORT: - js_unsupported_keyword (s, s->token.u.ident.atom); + js_unsupported_keyword (s, s->token.u.ident.str); goto fail; default: @@ -16035,9 +16298,9 @@ static int add_closure_var (JSContext *ctx, JSFunctionDef *s, BOOL is_local, static __exception int js_parse_source_element (JSParseState *s) { if (s->token.val == TOK_FUNCTION - || (token_is_pseudo_keyword (s, JS_ATOM_async) + || (token_is_pseudo_keyword (s, JS_KEY_async) && peek_token (s, TRUE) == TOK_FUNCTION)) { - if (js_parse_function_decl (s, JS_PARSE_FUNC_STATEMENT, JS_ATOM_NULL, + if (js_parse_function_decl (s, JS_PARSE_FUNC_STATEMENT, JS_NULL, s->token.ptr)) return -1; } else { @@ -16792,33 +17055,29 @@ optimize_scope_make_global_ref (JSContext *ctx, JSFunctionDef *s, DynBuf *bc, static int add_var_this (JSContext *ctx, JSFunctionDef *fd) { - return add_var (ctx, fd, JS_ATOM_this); + return add_var (ctx, fd, JS_KEY_this); } static int -resolve_pseudo_var (JSContext *ctx, JSFunctionDef *s, JSAtom var_name) { +resolve_pseudo_var (JSContext *ctx, JSFunctionDef *s, JSValue var_name) { int var_idx; if (!s->has_this_binding) return -1; - switch (var_name) { - case JS_ATOM_this_active_func: + + if (js_key_equal (var_name, JS_KEY_this)) { + /* 'this' pseudo variable */ + if (s->this_var_idx < 0) s->this_var_idx = add_var_this (ctx, s); + var_idx = s->this_var_idx; + } else if (js_key_equal (var_name, js_key_new_rt (ctx->rt, "this_active_func"))) { /* 'this.active_func' pseudo variable */ if (s->this_active_func_var_idx < 0) s->this_active_func_var_idx = add_var (ctx, s, var_name); var_idx = s->this_active_func_var_idx; - break; - case JS_ATOM_new_target: + } else if (js_key_equal (var_name, js_key_new_rt (ctx->rt, "new_target"))) { /* 'new.target' not supported - constructors removed */ var_idx = -1; - break; - case JS_ATOM_this: - /* 'this' pseudo variable */ - if (s->this_var_idx < 0) s->this_var_idx = add_var_this (ctx, s); - var_idx = s->this_var_idx; - break; - default: + } else { var_idx = -1; - break; } return var_idx; } @@ -17077,8 +17336,8 @@ resolve_scope_var (JSContext *ctx, JSFunctionDef *s, JSAtom var_name, idx = idx1; } goto has_idx; - } else if ((cv->var_name == JS_ATOM__var_ - || cv->var_name == JS_ATOM__arg_var_) + } else if ((js_key_equal_str (cv->var_name, "_var_") + || js_key_equal_str (cv->var_name, "_arg_var_")) && !is_pseudo_var) { if (fd != s) { idx = get_closure_var2 (ctx, s, fd, FALSE, cv->is_arg, idx1, @@ -17245,10 +17504,11 @@ mark_eval_captured_variables (JSContext *ctx, JSFunctionDef *s, /* XXX: should handle the argument scope generically */ static BOOL is_var_in_arg_scope (const JSVarDef *vd) { - return (vd->var_name == JS_ATOM_home_object - || vd->var_name == JS_ATOM_this_active_func - || vd->var_name == JS_ATOM_new_target || vd->var_name == JS_ATOM_this - || vd->var_name == JS_ATOM__arg_var_ + return (js_key_equal_str (vd->var_name, "home_object") + || js_key_equal_str (vd->var_name, "this_active_func") + || js_key_equal_str (vd->var_name, "new_target") + || js_key_equal (vd->var_name, JS_KEY_this) + || js_key_equal_str (vd->var_name, "_arg_var_") || vd->var_kind == JS_VAR_FUNCTION_NAME); } @@ -17590,12 +17850,12 @@ instantiate_hoisted_definitions (JSContext *ctx, JSFunctionDef *s, create a property for the variable there */ for (idx = 0; idx < s->closure_var_count; idx++) { JSClosureVar *cv = &s->closure_var[idx]; - if (cv->var_name == hf->var_name) { + if (js_key_equal (cv->var_name, hf->var_name)) { has_closure = 2; force_init = FALSE; break; } - if (cv->var_name == JS_ATOM__var_ || cv->var_name == JS_ATOM__arg_var_) { + if (js_key_equal_str (cv->var_name, "_var_") || js_key_equal_str (cv->var_name, "_arg_var_")) { dbuf_putc (bc, OP_get_var_ref); dbuf_put_u16 (bc, idx); has_closure = 1; @@ -17628,7 +17888,7 @@ instantiate_hoisted_definitions (JSContext *ctx, JSFunctionDef *s, if (hf->cpool_idx >= 0) { dbuf_putc (bc, OP_fclosure); dbuf_put_u32 (bc, hf->cpool_idx); - if (hf->var_name == JS_ATOM__default_) { + if (js_key_equal_str (hf->var_name, "_default_")) { /* set default export function name */ dbuf_putc (bc, OP_set_name); dbuf_put_u32 (bc, JS_DupAtom (ctx, JS_ATOM_default)); @@ -17764,7 +18024,7 @@ resolve_variables (JSContext *ctx, JSFunctionDef *s) { } goto next; } - if (cv->var_name == JS_ATOM__var_ || cv->var_name == JS_ATOM__arg_var_) + if (js_key_equal_str (cv->var_name, "_var_") || js_key_equal_str (cv->var_name, "_arg_var_")) goto next; } @@ -19547,42 +19807,43 @@ js_parse_directives (JSParseState *s) { /* return TRUE if the keyword is forbidden only in strict mode */ static BOOL -is_strict_future_keyword (JSAtom atom) { - return (atom >= JS_ATOM_LAST_KEYWORD + 1 - && atom <= JS_ATOM_LAST_STRICT_KEYWORD); +is_strict_future_keyword (JSValue name) { + BOOL is_strict_reserved = FALSE; + int token = js_keyword_lookup (name, &is_strict_reserved); + return (token >= 0 && is_strict_reserved); } static int js_parse_function_check_names (JSParseState *s, JSFunctionDef *fd, - JSAtom func_name) { - JSAtom name; + JSValue func_name) { + JSValue name; int i, idx; if (!fd->has_simple_parameter_list && fd->has_use_strict) { return js_parse_error (s, "\"use strict\" not allowed in function with " "default or destructuring parameter"); } - if (func_name == JS_ATOM_eval || is_strict_future_keyword (func_name)) { + if (js_key_equal (func_name, JS_KEY_eval) || is_strict_future_keyword (func_name)) { return js_parse_error (s, "invalid function name in strict code"); } for (idx = 0; idx < fd->arg_count; idx++) { name = fd->args[idx].var_name; - if (name == JS_ATOM_eval || is_strict_future_keyword (name)) { + if (js_key_equal (name, JS_KEY_eval) || is_strict_future_keyword (name)) { return js_parse_error (s, "invalid argument name in strict code"); } } for (idx = 0; idx < fd->arg_count; idx++) { name = fd->args[idx].var_name; - if (name != JS_ATOM_NULL) { + if (!JS_IsNull (name)) { for (i = 0; i < idx; i++) { - if (fd->args[i].var_name == name) goto duplicate; + if (js_key_equal (fd->args[i].var_name, name)) goto duplicate; } /* Check if argument name duplicates a destructuring parameter */ /* XXX: should have a flag for such variables */ for (i = 0; i < fd->var_count; i++) { - if (fd->vars[i].var_name == name && fd->vars[i].scope_level == 0) + if (js_key_equal (fd->vars[i].var_name, name) && fd->vars[i].scope_level == 0) goto duplicate; } } @@ -19594,11 +19855,11 @@ duplicate: s, "duplicate argument names not allowed in this context"); } -/* func_name must be JS_ATOM_NULL for JS_PARSE_FUNC_STATEMENT and +/* func_name must be JS_NULL for JS_PARSE_FUNC_STATEMENT and JS_PARSE_FUNC_EXPR, JS_PARSE_FUNC_ARROW and JS_PARSE_FUNC_VAR */ static __exception int js_parse_function_decl2 (JSParseState *s, JSParseFunctionEnum func_type, - JSAtom func_name, const uint8_t *ptr, + JSValue func_name, const uint8_t *ptr, JSFunctionDef **pfd) { JSContext *ctx = s->ctx; JSFunctionDef *fd = s->cur_func; @@ -19616,9 +19877,9 @@ js_parse_function_decl2 (JSParseState *s, JSParseFunctionEnum func_type, if (s->token.val == TOK_IDENT) { if (s->token.u.ident.is_reserved) return js_parse_error_reserved_identifier (s); - func_name = JS_DupAtom (ctx, s->token.u.ident.atom); + func_name = JS_DupValue (ctx, s->token.u.ident.str); if (next_token (s)) { - JS_FreeAtom (ctx, func_name); + JS_FreeValue (ctx, func_name); return -1; } } else { @@ -19627,7 +19888,7 @@ js_parse_function_decl2 (JSParseState *s, JSParseFunctionEnum func_type, } } else if (func_type != JS_PARSE_FUNC_ARROW) { - func_name = JS_DupAtom (ctx, func_name); + func_name = JS_DupValue (ctx, func_name); } if (func_type == JS_PARSE_FUNC_VAR) { @@ -19642,7 +19903,7 @@ js_parse_function_decl2 (JSParseState *s, JSParseFunctionEnum func_type, /* XXX: should check scope chain */ if (hf && hf->scope_level == fd->scope_level) { js_parse_error (s, "invalid redefinition of global identifier"); - JS_FreeAtom (ctx, func_name); + JS_FreeValue (ctx, func_name); return -1; } } else { @@ -19652,7 +19913,7 @@ js_parse_function_decl2 (JSParseState *s, JSParseFunctionEnum func_type, lexical_func_idx = define_var (s, fd, func_name, JS_VAR_DEF_FUNCTION_DECL); if (lexical_func_idx < 0) { - JS_FreeAtom (ctx, func_name); + JS_FreeValue (ctx, func_name); return -1; } } @@ -19661,7 +19922,7 @@ js_parse_function_decl2 (JSParseState *s, JSParseFunctionEnum func_type, fd = js_new_function_def (ctx, fd, FALSE, is_expr, s->filename, ptr, &s->get_line_col_cache); if (!fd) { - JS_FreeAtom (ctx, func_name); + JS_FreeValue (ctx, func_name); return -1; } if (pfd) *pfd = fd; @@ -19684,12 +19945,12 @@ js_parse_function_decl2 (JSParseState *s, JSParseFunctionEnum func_type, fd->has_simple_parameter_list = TRUE; fd->has_parameter_expressions = FALSE; if (func_type == JS_PARSE_FUNC_ARROW && s->token.val == TOK_IDENT) { - JSAtom name; + JSValue name; if (s->token.u.ident.is_reserved) { js_parse_error_reserved_identifier (s); goto fail; } - name = s->token.u.ident.atom; + name = s->token.u.ident.str; if (add_arg (ctx, fd, name) < 0) goto fail; } else if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT) { if (s->token.val == '(') { @@ -19710,13 +19971,13 @@ js_parse_function_decl2 (JSParseState *s, JSParseFunctionEnum func_type, } while (s->token.val != ')') { - JSAtom name; + JSValue name; int idx, has_initializer; if (s->token.val == '[' || s->token.val == '{') { fd->has_simple_parameter_list = FALSE; /* unnamed arg for destructuring */ - idx = add_arg (ctx, fd, JS_ATOM_NULL); + idx = add_arg (ctx, fd, JS_NULL); emit_op (s, OP_get_arg); emit_u16 (s, idx); has_initializer @@ -19727,7 +19988,7 @@ js_parse_function_decl2 (JSParseState *s, JSParseFunctionEnum func_type, js_parse_error_reserved_identifier (s); goto fail; } - name = s->token.u.ident.atom; + name = s->token.u.ident.str; if (fd->has_parameter_expressions) { if (js_parse_check_duplicate_parameter (s, name)) goto fail; if (define_var (s, fd, name, JS_VAR_DEF_LET) < 0) goto fail; @@ -19757,7 +20018,7 @@ js_parse_function_decl2 (JSParseState *s, JSParseFunctionEnum func_type, emit_u16 (s, idx); emit_label (s, label); emit_op (s, OP_scope_put_var_init); - emit_atom (s, name); + emit_key (s, name); emit_u16 (s, fd->scope_level); } else { if (fd->has_parameter_expressions) { @@ -19765,7 +20026,7 @@ js_parse_function_decl2 (JSParseState *s, JSParseFunctionEnum func_type, emit_op (s, OP_get_arg); emit_u16 (s, idx); emit_op (s, OP_scope_put_var_init); - emit_atom (s, name); + emit_key (s, name); emit_u16 (s, fd->scope_level); } } @@ -19801,10 +20062,10 @@ js_parse_function_decl2 (JSParseState *s, JSParseFunctionEnum func_type, if (add_var (ctx, fd, vd->var_name) < 0) goto fail; vd = &fd->vars[idx]; /* fd->vars may have been reallocated */ emit_op (s, OP_scope_get_var); - emit_atom (s, vd->var_name); + emit_key (s, vd->var_name); emit_u16 (s, fd->scope_level); emit_op (s, OP_scope_put_var); - emit_atom (s, vd->var_name); + emit_key (s, vd->var_name); emit_u16 (s, 0); } idx = vd->scope_next; @@ -19885,7 +20146,7 @@ done: /* create the function object */ { int idx; - JSAtom func_name = fd->func_name; + JSValue func_name_val = fd->func_name; /* the real object will be set at the end of the compilation */ idx = cpool_add (s, JS_NULL); @@ -19896,9 +20157,9 @@ done: and adds the scope information */ emit_op (s, OP_fclosure); emit_u32 (s, idx); - if (func_name == JS_ATOM_NULL) { + if (JS_IsNull (func_name_val)) { emit_op (s, OP_set_name); - emit_u32 (s, JS_ATOM_NULL); + emit_key (s, JS_NULL); } } else if (func_type == JS_PARSE_FUNC_VAR) { emit_op (s, OP_fclosure); @@ -19908,7 +20169,7 @@ done: JSGlobalVar *hf; /* the global variable must be defined at the start of the function */ - hf = add_global_var (ctx, s->cur_func, func_name); + hf = add_global_var (ctx, s->cur_func, func_name_val); if (!hf) goto fail; /* it is considered as defined at the top level (needed for annex B.3.3.4 and B.3.3.5 @@ -19918,19 +20179,19 @@ done: /* store directly into global var, bypass lexical scope */ emit_op (s, OP_dup); emit_op (s, OP_scope_put_var); - emit_atom (s, func_name); + emit_key (s, func_name_val); emit_u16 (s, 0); } else { /* do not call define_var to bypass lexical scope check */ - func_idx = find_var (ctx, s->cur_func, func_name); + func_idx = find_var (ctx, s->cur_func, func_name_val); if (func_idx < 0) { - func_idx = add_var (ctx, s->cur_func, func_name); + func_idx = add_var (ctx, s->cur_func, func_name_val); if (func_idx < 0) goto fail; } /* store directly into local var, bypass lexical catch scope */ emit_op (s, OP_dup); emit_op (s, OP_scope_put_var); - emit_atom (s, func_name); + emit_key (s, func_name_val); emit_u16 (s, 0); } } @@ -19942,12 +20203,12 @@ done: /* store function object into its lexical name */ /* XXX: could use OP_put_loc directly */ emit_op (s, OP_scope_put_var_init); - emit_atom (s, func_name); + emit_key (s, func_name_val); emit_u16 (s, s->cur_func->scope_level); } } else { if (!s->cur_func->is_global_var) { - int var_idx = define_var (s, s->cur_func, func_name, JS_VAR_DEF_VAR); + int var_idx = define_var (s, s->cur_func, func_name_val, JS_VAR_DEF_VAR); if (var_idx < 0) goto fail; /* the variable will be assigned at the top of the function */ @@ -19957,12 +20218,12 @@ done: s->cur_func->vars[var_idx].func_pool_idx = idx; } } else { - JSAtom func_var_name; + JSValue func_var_name; JSGlobalVar *hf; - if (func_name == JS_ATOM_NULL) - func_var_name = JS_ATOM__default_; /* export default */ + if (JS_IsNull (func_name_val)) + func_var_name = JS_KEY_STR (ctx, "default"); /* export default */ else - func_var_name = func_name; + func_var_name = func_name_val; /* the variable will be assigned at the top of the function */ hf = add_global_var (ctx, s->cur_func, func_var_name); if (!hf) goto fail; @@ -19980,7 +20241,7 @@ fail: static __exception int js_parse_function_decl (JSParseState *s, JSParseFunctionEnum func_type, - JSAtom func_name, const uint8_t *ptr) { + JSValue func_name, const uint8_t *ptr) { return js_parse_function_decl2 (s, func_type, func_name, ptr, NULL); } @@ -20426,6 +20687,60 @@ bc_put_atom (BCWriterState *s, JSAtom atom) { return 0; } +/* Write a JSValue key (text) to bytecode stream */ +static int +bc_put_key (BCWriterState *s, JSValue key) { + /* Handle immediate ASCII strings */ + if (MIST_IsImmediateASCII (key)) { + int len = MIST_GetImmediateASCIILen (key); + bc_put_leb128 (s, (uint32_t)len); + for (int i = 0; i < len; i++) { + bc_put_u8 (s, (uint8_t)MIST_GetImmediateASCIIChar (key, i)); + } + return 0; + } + + /* Handle heap strings */ + if (!JS_IsString (key)) { + /* Not a string - write empty */ + bc_put_leb128 (s, 0); + return 0; + } + + JSString *p = JS_VALUE_GET_STRING (key); + /* Write as UTF-8 */ + uint32_t len = p->len; + /* Calculate UTF-8 size */ + size_t utf8_size = 0; + for (uint32_t i = 0; i < len; i++) { + uint32_t c = string_get (p, i); + if (c < 0x80) utf8_size += 1; + else if (c < 0x800) utf8_size += 2; + else if (c < 0x10000) utf8_size += 3; + else utf8_size += 4; + } + bc_put_leb128 (s, (uint32_t)utf8_size); + for (uint32_t i = 0; i < len; i++) { + uint32_t c = string_get (p, i); + if (c < 0x80) { + bc_put_u8 (s, c); + } else if (c < 0x800) { + bc_put_u8 (s, 0xC0 | (c >> 6)); + bc_put_u8 (s, 0x80 | (c & 0x3F)); + } else if (c < 0x10000) { + bc_put_u8 (s, 0xE0 | (c >> 12)); + bc_put_u8 (s, 0x80 | ((c >> 6) & 0x3F)); + bc_put_u8 (s, 0x80 | (c & 0x3F)); + } else { + bc_put_u8 (s, 0xF0 | (c >> 18)); + bc_put_u8 (s, 0x80 | ((c >> 12) & 0x3F)); + bc_put_u8 (s, 0x80 | ((c >> 6) & 0x3F)); + bc_put_u8 (s, 0x80 | (c & 0x3F)); + } + } + return 0; +} + static void bc_byte_swap (uint8_t *bc_buf, int bc_len) { int pos, len, op, fmt; @@ -20554,7 +20869,7 @@ JS_WriteFunctionTag (BCWriterState *s, JSValue obj) { assert (idx <= 16); bc_put_u16 (s, flags); bc_put_u8 (s, b->js_mode); - bc_put_atom (s, b->func_name); + bc_put_key (s, b->func_name); bc_put_leb128 (s, b->arg_count); bc_put_leb128 (s, b->var_count); @@ -20568,7 +20883,7 @@ JS_WriteFunctionTag (BCWriterState *s, JSValue obj) { bc_put_leb128 (s, b->arg_count + b->var_count); for (i = 0; i < b->arg_count + b->var_count; i++) { JSVarDef *vd = &b->vardefs[i]; - bc_put_atom (s, vd->var_name); + bc_put_key (s, vd->var_name); bc_put_leb128 (s, vd->scope_level); bc_put_leb128 (s, vd->scope_next + 1); flags = idx = 0; @@ -20585,7 +20900,7 @@ JS_WriteFunctionTag (BCWriterState *s, JSValue obj) { for (i = 0; i < b->closure_var_count; i++) { JSClosureVar *cv = &b->closure_var[i]; - bc_put_atom (s, cv->var_name); + bc_put_key (s, cv->var_name); bc_put_leb128 (s, cv->var_idx); flags = idx = 0; bc_set_flags (&flags, &idx, cv->is_local, 1); @@ -20601,7 +20916,7 @@ JS_WriteFunctionTag (BCWriterState *s, JSValue obj) { goto fail; if (b->has_debug) { - bc_put_atom (s, b->debug.filename); + bc_put_key (s, b->debug.filename); bc_put_leb128 (s, b->debug.pc2line_len); dbuf_put (&s->dbuf, b->debug.pc2line_buf, b->debug.pc2line_len); if (b->debug.source) { @@ -21020,6 +21335,28 @@ bc_get_atom (BCReaderState *s, JSAtom *patom) { } } +/* Read a JSValue key (text) from bytecode stream */ +static int +bc_get_key (BCReaderState *s, JSValue *pkey) { + uint32_t len; + if (bc_get_leb128 (s, &len)) return -1; + if (len == 0) { + *pkey = JS_KEY_empty; + return 0; + } + /* Read UTF-8 bytes */ + char *buf = alloca (len + 1); + for (uint32_t i = 0; i < len; i++) { + uint8_t byte; + if (bc_get_u8 (s, &byte)) return -1; + buf[i] = (char)byte; + } + buf[len] = '\0'; + /* Create interned key from UTF-8 */ + *pkey = js_key_new_len_rt (s->ctx->rt, buf, len); + return JS_IsNull (*pkey) ? -1 : 0; +} + static JSString * JS_ReadString (BCReaderState *s) { uint32_t len; @@ -21169,7 +21506,7 @@ JS_ReadFunctionTag (BCReaderState *s) { bc.read_only_bytecode = s->is_rom_data; if (bc_get_u8 (s, &v8)) goto fail; bc.js_mode = v8; - if (bc_get_atom (s, &bc.func_name)) //@ atom leak if failure + if (bc_get_key (s, &bc.func_name)) goto fail; if (bc_get_leb128_u16 (s, &bc.arg_count)) goto fail; if (bc_get_leb128_u16 (s, &bc.var_count)) goto fail; @@ -21228,7 +21565,7 @@ JS_ReadFunctionTag (BCReaderState *s) { bc_read_trace (s, "vars {\n"); for (i = 0; i < local_count; i++) { JSVarDef *vd = &b->vardefs[i]; - if (bc_get_atom (s, &vd->var_name)) goto fail; + if (bc_get_key (s, &vd->var_name)) goto fail; if (bc_get_leb128_int (s, &vd->scope_level)) goto fail; if (bc_get_leb128_int (s, &vd->scope_next)) goto fail; vd->scope_next--; @@ -21251,7 +21588,7 @@ JS_ReadFunctionTag (BCReaderState *s) { for (i = 0; i < b->closure_var_count; i++) { JSClosureVar *cv = &b->closure_var[i]; int var_idx; - if (bc_get_atom (s, &cv->var_name)) goto fail; + if (bc_get_key (s, &cv->var_name)) goto fail; if (bc_get_leb128_int (s, &var_idx)) goto fail; cv->var_idx = var_idx; if (bc_get_u8 (s, &v8)) goto fail; @@ -21278,7 +21615,7 @@ JS_ReadFunctionTag (BCReaderState *s) { if (b->has_debug) { /* read optional debug information */ bc_read_trace (s, "debug {\n"); - if (bc_get_atom (s, &b->debug.filename)) goto fail; + if (bc_get_key (s, &b->debug.filename)) goto fail; #ifdef DUMP_READ_OBJECT bc_read_trace (s, "filename: "); print_atom (s->ctx, b->debug.filename); @@ -21876,7 +22213,7 @@ js_error_constructor (JSContext *ctx, JSValue this_val, int argc, if (!JS_IsNull (message)) { msg = JS_ToString (ctx, message); if (unlikely (JS_IsException (msg))) goto exception; - JS_SetPropertyInternal (ctx, obj, JS_ATOM_message, msg); + JS_SetPropertyInternal (ctx, obj, JS_KEY_message, msg); } if (arg_index < argc) { @@ -21887,7 +22224,7 @@ js_error_constructor (JSContext *ctx, JSValue this_val, int argc, if (present) { JSValue cause = JS_GetProperty (ctx, options, JS_ATOM_cause); if (JS_IsException (cause)) goto exception; - JS_SetPropertyInternal (ctx, obj, JS_ATOM_cause, cause); + JS_SetPropertyInternal (ctx, obj, JS_KEY_cause, cause); } } } @@ -21915,7 +22252,7 @@ js_error_constructor (JSContext *ctx, JSValue this_val, int argc, error_list = JS_NewArray (ctx); if (JS_IsException (error_list)) goto exception; } - JS_SetPropertyInternal (ctx, obj, JS_ATOM_errors, error_list); + JS_SetPropertyInternal (ctx, obj, JS_KEY_errors, error_list); } /* skip the Error() function in the backtrace */ @@ -21933,16 +22270,16 @@ js_error_toString (JSContext *ctx, JSValue this_val, int argc, JSValue name, msg; if (!JS_IsObject (this_val)) return JS_ThrowTypeErrorNotAnObject (ctx); - name = JS_GetProperty (ctx, this_val, JS_ATOM_name); + name = JS_GetProperty (ctx, this_val, JS_KEY_name); if (JS_IsNull (name)) - name = JS_AtomToString (ctx, JS_ATOM_Error); + name = JS_KEY_Error; else name = JS_ToStringFree (ctx, name); if (JS_IsException (name)) return JS_EXCEPTION; - msg = JS_GetProperty (ctx, this_val, JS_ATOM_message); + msg = JS_GetProperty (ctx, this_val, JS_KEY_message); if (JS_IsNull (msg)) - msg = JS_AtomToString (ctx, JS_ATOM_empty_string); + msg = JS_KEY_empty; else msg = JS_ToStringFree (ctx, msg); if (JS_IsException (msg)) { @@ -22091,7 +22428,11 @@ js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, JSValue bc) { re = &p->u.regexp; re->pattern = JS_VALUE_GET_STRING (pattern); re->bytecode = JS_VALUE_GET_STRING (bc); - JS_SetPropertyInternal (ctx, obj, JS_ATOM_lastIndex, JS_NewInt32 (ctx, 0)); + { + JSValue key = JS_KEY_STR (ctx, "lastIndex"); + JS_SetPropertyInternal (ctx, obj, key, JS_NewInt32 (ctx, 0)); + JS_FreeValue (ctx, key); + } return obj; } @@ -22160,7 +22501,7 @@ js_regexp_constructor (JSContext *ctx, JSValue this_val, int argc, flags = JS_DupValue (ctx, flags1); } if (JS_IsNull (pattern)) { - pattern = JS_AtomToString (ctx, JS_ATOM_empty_string); + pattern = JS_KEY_empty; } else { val = pattern; pattern = JS_ToString (ctx, val); @@ -22199,7 +22540,7 @@ js_regexp_compile (JSContext *ctx, JSValue this_val, int argc, } else { bc = JS_NULL; if (JS_IsNull (pattern1)) - pattern = JS_AtomToString (ctx, JS_ATOM_empty_string); + pattern = JS_KEY_empty; else pattern = JS_ToString (ctx, pattern1); if (JS_IsException (pattern)) goto fail; @@ -22210,9 +22551,12 @@ js_regexp_compile (JSContext *ctx, JSValue this_val, int argc, JS_FreeValue (ctx, JS_MKPTR (JS_TAG_STRING, re->bytecode)); re->pattern = JS_VALUE_GET_STRING (pattern); re->bytecode = JS_VALUE_GET_STRING (bc); - if (JS_SetProperty (ctx, this_val, JS_ATOM_lastIndex, JS_NewInt32 (ctx, 0)) - < 0) - return JS_EXCEPTION; + { + JSValue key = JS_KEY_STR (ctx, "lastIndex"); + int ret = JS_SetProperty (ctx, this_val, key, JS_NewInt32 (ctx, 0)); + JS_FreeValue (ctx, key); + if (ret < 0) return JS_EXCEPTION; + } return JS_DupValue (ctx, this_val); fail: JS_FreeValue (ctx, pattern); @@ -22684,7 +23028,7 @@ json_parse_value (JSParseState *s) { prop_name = JS_ValueToAtom (ctx, s->token.u.str.str); if (prop_name == JS_ATOM_NULL) goto fail; } else if (s->ext_json && s->token.val == TOK_IDENT) { - prop_name = JS_DupAtom (ctx, s->token.u.ident.atom); + prop_name = JS_DupAtom (ctx, s->token.u.ident.str); } else { js_parse_error (s, "expecting property name"); goto fail; @@ -22739,16 +23083,16 @@ json_parse_value (JSParseState *s) { if (json_next_token (s)) goto fail; break; case TOK_IDENT: - if (s->token.u.ident.atom == JS_ATOM_false - || s->token.u.ident.atom == JS_ATOM_true) { - val = JS_NewBool (ctx, s->token.u.ident.atom == JS_ATOM_true); - } else if (s->token.u.ident.atom == JS_ATOM_null) { + if (s->token.u.ident.str == JS_ATOM_false + || s->token.u.ident.str == JS_ATOM_true) { + val = JS_NewBool (ctx, s->token.u.ident.str == JS_ATOM_true); + } else if (s->token.u.ident.str == JS_ATOM_null) { val = JS_NULL; - } else if (s->token.u.ident.atom == JS_ATOM_NaN && s->ext_json) { + } else if (s->token.u.ident.str == JS_ATOM_NaN && s->ext_json) { /* Note: json5 identifier handling is ambiguous e.g. is '{ NaN: 1 }' a valid JSON5 production ? */ val = JS_NewFloat64 (s->ctx, NAN); - } else if (s->token.u.ident.atom == JS_ATOM_Infinity && s->ext_json) { + } else if (s->token.u.ident.str == JS_ATOM_Infinity && s->ext_json) { val = JS_NewFloat64 (s->ctx, INFINITY); } else { goto def_token; @@ -22882,7 +23226,7 @@ js_json_parse (JSContext *ctx, JSValue this_val, int argc, JS_FreeValue (ctx, obj); return JS_EXCEPTION; } - if (JS_SetPropertyInternal (ctx, root, JS_ATOM_empty_string, obj) < 0) { + if (JS_SetPropertyInternal (ctx, root, JS_KEY_empty, obj) < 0) { JS_FreeValue (ctx, root); return JS_EXCEPTION; } @@ -23121,7 +23465,7 @@ JS_JSONStringify (JSContext *ctx, JSValue obj, JSValue replacer, jsc->property_list = JS_NULL; jsc->gap = JS_NULL; jsc->b = &b_s; - jsc->empty = JS_AtomToString (ctx, JS_ATOM_empty_string); + jsc->empty = JS_KEY_empty; ret = JS_NULL; wrapper = JS_NULL; @@ -23182,7 +23526,7 @@ JS_JSONStringify (JSContext *ctx, JSValue obj, JSValue replacer, if (JS_IsException (jsc->gap)) goto exception; wrapper = JS_NewObject (ctx); if (JS_IsException (wrapper)) goto exception; - if (JS_SetPropertyInternal (ctx, wrapper, JS_ATOM_empty_string, + if (JS_SetPropertyInternal (ctx, wrapper, JS_KEY_empty, JS_DupValue (ctx, obj)) < 0) goto exception; @@ -24451,7 +24795,7 @@ make_replacement (JSContext *ctx, int argc, JSValue *argv, int found, JS_FreeValue (ctx, match_val); if (argc > 2) return JS_DupValue (ctx, argv[2]); - return JS_AtomToString (ctx, JS_ATOM_empty_string); + return JS_KEY_empty; } static int @@ -24531,7 +24875,7 @@ js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, for (int boundary = 0; boundary <= len; boundary++) { if (limit >= 0 && count >= limit) break; - JSValue match = JS_AtomToString (ctx, JS_ATOM_empty_string); + JSValue match = JS_KEY_empty; if (JS_IsException (match)) goto fail_str_target; JSValue rep = make_replacement (ctx, argc, argv, boundary, match); @@ -27443,10 +27787,10 @@ JS_AddIntrinsicBasicObjects (JSContext *ctx) { for (i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { proto = JS_NewObjectProto (ctx, ctx->class_proto[JS_CLASS_ERROR]); - JS_SetPropertyInternal (ctx, proto, JS_ATOM_name, + JS_SetPropertyInternal (ctx, proto, JS_KEY_name, JS_NewAtomString (ctx, native_error_name[i])); - JS_SetPropertyInternal (ctx, proto, JS_ATOM_message, - JS_AtomToString (ctx, JS_ATOM_empty_string)); + JS_SetPropertyInternal (ctx, proto, JS_KEY_message, + JS_KEY_empty); ctx->native_error_proto[i] = proto; } } @@ -27480,8 +27824,12 @@ JS_AddIntrinsicBaseObjects (JSContext *ctx) { } /* global properties */ - JS_SetPropertyInternal (ctx, ctx->global_obj, JS_ATOM_globalThis, - JS_DupValue (ctx, ctx->global_obj)); + { + JSValue key = JS_KEY_STR (ctx, "globalThis"); + JS_SetPropertyInternal (ctx, ctx->global_obj, key, + JS_DupValue (ctx, ctx->global_obj)); + JS_FreeValue (ctx, key); + } /* Cell Script global functions: text, number, array, object, fn */ { @@ -27798,7 +28146,7 @@ js_debugger_build_backtrace (JSContext *ctx, const uint8_t *cur_pc) { line_num1 = find_line_num (ctx, b, pc - b->byte_code_buf - 1, &col_num); JS_SetPropertyStr (ctx, current_frame, "filename", - JS_AtomToString (ctx, b->debug.filename)); + JS_DupValue (ctx, b->debug.filename)); if (line_num1 != -1) JS_SetPropertyStr (ctx, current_frame, "line", JS_NewUint32 (ctx, line_num1)); @@ -28434,13 +28782,11 @@ js_cell_json_decode (JSContext *ctx, JSValue this_val, int argc, JS_SetPropertyStr (ctx, wrapper, "", result); JSValue holder = wrapper; - JSAtom key = JS_NewAtom (ctx, ""); + JSValue key = JS_KEY_empty; JSValue args[2] - = { JS_AtomToString (ctx, key), JS_GetProperty (ctx, holder, key) }; + = { key, JS_GetProperty (ctx, holder, key) }; JSValue final = JS_Call (ctx, argv[1], holder, 2, args); - JS_FreeValue (ctx, args[0]); JS_FreeValue (ctx, args[1]); - JS_FreeAtom (ctx, key); JS_FreeValue (ctx, wrapper); result = final; }