before gc rewrite
This commit is contained in:
478
gc_plan.md
Normal file
478
gc_plan.md
Normal file
@@ -0,0 +1,478 @@
|
||||
# GC Refactoring Plan: Cheney Copying Collector
|
||||
|
||||
## Overview
|
||||
|
||||
Replace the current reference-counting GC with a simple two-space Cheney copying collector.
|
||||
This fundamentally simplifies the system: no more `JSGCObjectHeader`, no ref counts,
|
||||
no cycle detection - just bump allocation and copying live objects when memory fills up.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
JSRuntime (256 MB pool)
|
||||
├── Buddy allocator for block management
|
||||
└── JSContext #1 (actor)
|
||||
├── Current block (64KB initially)
|
||||
├── heap_base: start of block
|
||||
├── heap_free: bump pointer
|
||||
└── On memory pressure: request new block, copy live objects, return old block
|
||||
```
|
||||
|
||||
## Memory Model (from docs/memory.md)
|
||||
|
||||
### Object Header (objhdr_t - 64 bits)
|
||||
```
|
||||
[56 bits: capacity] [1 bit: R flag] [3 bits: reserved] [1 bit: stone] [3 bits: type]
|
||||
```
|
||||
|
||||
### Object Types
|
||||
- 0: OBJ_ARRAY - Header, Length, Elements[]
|
||||
- 1: OBJ_BLOB - Header, Length (bits), BitWords[]
|
||||
- 2: OBJ_TEXT - Header, Length/Hash, PackedChars[]
|
||||
- 3: OBJ_RECORD - Header, Prototype, Length, Key/Value pairs
|
||||
- 4: OBJ_FUNCTION - Header, Code, Outer (always stone, 3 words)
|
||||
- 5: OBJ_CODE - Header, Arity, Size, ClosureSize, Entry, Disruption
|
||||
- 6: OBJ_FRAME - Header, Function, Caller, ReturnAddr, Slots[]
|
||||
- 7: OBJ_FORWARD - Forwarding pointer (used during GC)
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Add Buddy Allocator to JSRuntime
|
||||
|
||||
### File: source/quickjs.c
|
||||
|
||||
**1.1 Add buddy allocator structures**
|
||||
```c
|
||||
#define BUDDY_MIN_ORDER 16 /* 64KB minimum block */
|
||||
#define BUDDY_MAX_ORDER 28 /* 256MB maximum */
|
||||
#define BUDDY_LEVELS (BUDDY_MAX_ORDER - BUDDY_MIN_ORDER + 1)
|
||||
|
||||
typedef struct BuddyBlock {
|
||||
struct BuddyBlock *next;
|
||||
struct BuddyBlock *prev;
|
||||
uint8_t order; /* log2 of size */
|
||||
uint8_t is_free;
|
||||
} BuddyBlock;
|
||||
|
||||
typedef struct BuddyAllocator {
|
||||
uint8_t *base; /* 256MB base address */
|
||||
size_t total_size; /* 256MB */
|
||||
BuddyBlock *free_lists[BUDDY_LEVELS];
|
||||
} BuddyAllocator;
|
||||
```
|
||||
|
||||
**1.2 Update JSRuntime**
|
||||
```c
|
||||
struct JSRuntime {
|
||||
BuddyAllocator buddy;
|
||||
/* ... keep: class_count, class_array, context_list ... */
|
||||
/* REMOVE: gc_obj_list, gc_zero_ref_count_list, gc_phase, malloc_gc_threshold */
|
||||
};
|
||||
```
|
||||
|
||||
**1.3 Implement buddy functions**
|
||||
- `buddy_init(BuddyAllocator *b)` - allocate 256MB, initialize free lists
|
||||
- `buddy_alloc(BuddyAllocator *b, size_t size)` - allocate block of given size
|
||||
- `buddy_free(BuddyAllocator *b, void *ptr, size_t size)` - return block
|
||||
- `buddy_destroy(BuddyAllocator *b)` - free the 256MB
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Restructure JSContext for Bump Allocation
|
||||
|
||||
### File: source/quickjs.c
|
||||
|
||||
**2.1 Update JSContext**
|
||||
```c
|
||||
struct JSContext {
|
||||
JSRuntime *rt;
|
||||
struct list_head link;
|
||||
|
||||
/* Actor memory block */
|
||||
uint8_t *heap_base; /* start of current block */
|
||||
uint8_t *heap_free; /* bump pointer */
|
||||
uint8_t *heap_end; /* end of block */
|
||||
size_t current_block_size; /* 64KB initially */
|
||||
size_t next_block_size; /* doubles if <10% recovered */
|
||||
|
||||
/* Stack (VM execution) */
|
||||
JSValue *value_stack;
|
||||
int value_stack_top;
|
||||
int value_stack_capacity;
|
||||
struct VMFrame *frame_stack;
|
||||
int frame_stack_top;
|
||||
int frame_stack_capacity;
|
||||
|
||||
/* Roots */
|
||||
JSValue global_obj;
|
||||
JSValue *class_proto;
|
||||
JSValue current_exception;
|
||||
|
||||
/* Stone arena (immutable interned strings) */
|
||||
struct StoneArenaPage *st_pages;
|
||||
/* ... stone interning fields ... */
|
||||
|
||||
/* Other context state */
|
||||
uint16_t class_count;
|
||||
int interrupt_counter;
|
||||
void *user_opaque;
|
||||
/* REMOVE: JSGCObjectHeader header at start */
|
||||
};
|
||||
```
|
||||
|
||||
**2.2 Implement bump allocator**
|
||||
```c
|
||||
static void *ctx_alloc(JSContext *ctx, size_t size) {
|
||||
size = (size + 7) & ~7; /* 8-byte align */
|
||||
if (ctx->heap_free + size > ctx->heap_end) {
|
||||
if (ctx_gc(ctx) < 0) return NULL; /* triggers GC */
|
||||
if (ctx->heap_free + size > ctx->heap_end) {
|
||||
return NULL; /* still OOM after GC */
|
||||
}
|
||||
}
|
||||
void *ptr = ctx->heap_free;
|
||||
ctx->heap_free += size;
|
||||
return ptr;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Unify Object Headers (Remove JSGCObjectHeader)
|
||||
|
||||
### 3.1 New unified object layout
|
||||
|
||||
All heap objects start with just `objhdr_t`:
|
||||
|
||||
```c
|
||||
/* Array */
|
||||
typedef struct {
|
||||
objhdr_t hdr; /* type=OBJ_ARRAY, cap=element_capacity */
|
||||
uint64_t len;
|
||||
JSValue elem[];
|
||||
} MistArray;
|
||||
|
||||
/* Text */
|
||||
typedef struct {
|
||||
objhdr_t hdr; /* type=OBJ_TEXT, cap=char_capacity, s=stone bit */
|
||||
uint64_t len_or_hash; /* len if pretext, hash if stoned */
|
||||
uint64_t packed[]; /* 2 UTF32 chars per word */
|
||||
} MistText;
|
||||
|
||||
/* Record (object) */
|
||||
typedef struct MistRecord {
|
||||
objhdr_t hdr; /* type=OBJ_RECORD, cap=mask, s=stone bit */
|
||||
struct MistRecord *proto;
|
||||
uint64_t len;
|
||||
uint64_t tombs;
|
||||
uint16_t class_id;
|
||||
uint16_t _pad;
|
||||
uint32_t rec_id; /* for record-as-key hashing */
|
||||
JSValue slots[]; /* key[0], val[0], key[1], val[1], ... */
|
||||
} MistRecord;
|
||||
|
||||
/* Function */
|
||||
typedef struct {
|
||||
objhdr_t hdr; /* type=OBJ_FUNCTION, always stone */
|
||||
JSValue code; /* pointer to MistCode */
|
||||
JSValue outer; /* pointer to MistFrame */
|
||||
} MistFunction;
|
||||
|
||||
/* Frame */
|
||||
typedef struct {
|
||||
objhdr_t hdr; /* type=OBJ_FRAME, cap=slot_count */
|
||||
JSValue function; /* MistFunction */
|
||||
JSValue caller; /* MistFrame or null */
|
||||
uint64_t return_addr;
|
||||
JSValue slots[]; /* args, locals, temporaries */
|
||||
} MistFrame;
|
||||
|
||||
/* Code (always in stone/immutable memory) */
|
||||
typedef struct {
|
||||
objhdr_t hdr; /* type=OBJ_CODE, always stone */
|
||||
uint32_t arity;
|
||||
uint32_t frame_size;
|
||||
uint32_t closure_size;
|
||||
uint64_t entry_point;
|
||||
uint64_t disruption_point;
|
||||
uint8_t bytecode[];
|
||||
} MistCode;
|
||||
```
|
||||
|
||||
### 3.2 Delete JSGCObjectHeader usage
|
||||
|
||||
Remove from:
|
||||
- JSRecord, JSArray, JSFunction - remove `JSGCObjectHeader header` field
|
||||
- All `p->header.ref_count`, `p->header.gc_obj_type`, `p->header.mark` accesses
|
||||
- `add_gc_object()`, `remove_gc_object()` functions
|
||||
- `gc_obj_list`, `gc_zero_ref_count_list` in JSRuntime
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Implement Cheney Copying GC
|
||||
|
||||
### 4.1 Core GC function
|
||||
|
||||
```c
|
||||
static int ctx_gc(JSContext *ctx) {
|
||||
size_t old_used = ctx->heap_free - ctx->heap_base;
|
||||
|
||||
/* Request new block from runtime */
|
||||
size_t new_size = ctx->next_block_size;
|
||||
uint8_t *new_block = buddy_alloc(&ctx->rt->buddy, new_size);
|
||||
if (!new_block) return -1;
|
||||
|
||||
uint8_t *to_base = new_block;
|
||||
uint8_t *to_free = new_block;
|
||||
uint8_t *to_end = new_block + new_size;
|
||||
|
||||
/* Copy roots */
|
||||
ctx->global_obj = gc_copy_value(ctx, ctx->global_obj, &to_free, to_end);
|
||||
ctx->current_exception = gc_copy_value(ctx, ctx->current_exception, &to_free, to_end);
|
||||
for (int i = 0; i < ctx->class_count; i++) {
|
||||
ctx->class_proto[i] = gc_copy_value(ctx, ctx->class_proto[i], &to_free, to_end);
|
||||
}
|
||||
|
||||
/* Copy stack */
|
||||
for (int i = 0; i < ctx->value_stack_top; i++) {
|
||||
ctx->value_stack[i] = gc_copy_value(ctx, ctx->value_stack[i], &to_free, to_end);
|
||||
}
|
||||
|
||||
/* Scan copied objects (Cheney scan pointer) */
|
||||
uint8_t *scan = to_base;
|
||||
while (scan < to_free) {
|
||||
gc_scan_object(ctx, scan, &to_free, to_end);
|
||||
scan += gc_object_size(scan);
|
||||
}
|
||||
|
||||
/* Return old block */
|
||||
buddy_free(&ctx->rt->buddy, ctx->heap_base, ctx->current_block_size);
|
||||
|
||||
/* Update context */
|
||||
size_t new_used = to_free - to_base;
|
||||
size_t recovered = old_used - new_used;
|
||||
|
||||
ctx->heap_base = to_base;
|
||||
ctx->heap_free = to_free;
|
||||
ctx->heap_end = to_end;
|
||||
ctx->current_block_size = new_size;
|
||||
|
||||
/* If <10% recovered, double next block size */
|
||||
if (recovered < old_used / 10) {
|
||||
ctx->next_block_size = new_size * 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Copy functions per type
|
||||
|
||||
```c
|
||||
static JSValue gc_copy_value(JSContext *ctx, JSValue v, uint8_t **to_free, uint8_t *to_end) {
|
||||
if (!JS_IsPtr(v)) return v; /* immediate value */
|
||||
|
||||
void *ptr = JS_VALUE_GET_PTR(v);
|
||||
if (is_stone_ptr(ptr)) return v; /* stone memory, don't copy */
|
||||
|
||||
objhdr_t hdr = *(objhdr_t *)ptr;
|
||||
|
||||
/* Already forwarded? */
|
||||
if (objhdr_type(hdr) == OBJ_FORWARD) {
|
||||
return JS_MKPTR(JS_TAG_PTR, (void *)(hdr >> 3)); /* extract forwarding address */
|
||||
}
|
||||
|
||||
/* Copy object */
|
||||
size_t size = gc_object_size(ptr);
|
||||
if (*to_free + size > to_end) abort(); /* shouldn't happen */
|
||||
|
||||
void *new_ptr = *to_free;
|
||||
memcpy(new_ptr, ptr, size);
|
||||
*to_free += size;
|
||||
|
||||
/* Install forwarding pointer */
|
||||
*(objhdr_t *)ptr = ((objhdr_t)(uintptr_t)new_ptr << 3) | OBJ_FORWARD;
|
||||
|
||||
return JS_MKPTR(JS_TAG_PTR, new_ptr);
|
||||
}
|
||||
|
||||
static void gc_scan_object(JSContext *ctx, void *ptr, uint8_t **to_free, uint8_t *to_end) {
|
||||
objhdr_t hdr = *(objhdr_t *)ptr;
|
||||
switch (objhdr_type(hdr)) {
|
||||
case OBJ_ARRAY: {
|
||||
MistArray *arr = ptr;
|
||||
for (uint64_t i = 0; i < arr->len; i++) {
|
||||
arr->elem[i] = gc_copy_value(ctx, arr->elem[i], to_free, to_end);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_RECORD: {
|
||||
MistRecord *rec = ptr;
|
||||
rec->proto = gc_copy_value(ctx, rec->proto, to_free, to_end);
|
||||
uint64_t mask = objhdr_cap56(hdr);
|
||||
for (uint64_t i = 0; i <= mask; i++) {
|
||||
rec->slots[i*2] = gc_copy_value(ctx, rec->slots[i*2], to_free, to_end);
|
||||
rec->slots[i*2+1] = gc_copy_value(ctx, rec->slots[i*2+1], to_free, to_end);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_FUNCTION: {
|
||||
MistFunction *fn = ptr;
|
||||
fn->code = gc_copy_value(ctx, fn->code, to_free, to_end);
|
||||
fn->outer = gc_copy_value(ctx, fn->outer, to_free, to_end);
|
||||
break;
|
||||
}
|
||||
case OBJ_FRAME: {
|
||||
MistFrame *fr = ptr;
|
||||
fr->function = gc_copy_value(ctx, fr->function, to_free, to_end);
|
||||
fr->caller = gc_copy_value(ctx, fr->caller, to_free, to_end);
|
||||
uint64_t cap = objhdr_cap56(hdr);
|
||||
for (uint64_t i = 0; i < cap; i++) {
|
||||
fr->slots[i] = gc_copy_value(ctx, fr->slots[i], to_free, to_end);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_TEXT:
|
||||
case OBJ_BLOB:
|
||||
case OBJ_CODE:
|
||||
/* No references to scan */
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Remove Old GC Infrastructure
|
||||
|
||||
### Files: source/quickjs.c, source/quickjs.h
|
||||
|
||||
**Delete entirely:**
|
||||
- `RC_TRACE` conditional code (~100 lines)
|
||||
- `RcEvent` struct and `rc_log` array
|
||||
- `rc_log_event`, `rc_trace_inc_gc`, `rc_trace_dec_gc`, `rc_dump_history`
|
||||
- `gc_decref`, `gc_decref_child`, `gc_decref_child_dbg`, `gc_decref_child_edge`
|
||||
- `gc_free_cycles`, `free_zero_refcount`, `free_gc_object`
|
||||
- `add_gc_object`, `remove_gc_object`
|
||||
- `JS_RunGCInternal`, `JS_GC_PHASE_*` enum
|
||||
- `mark_children`, `gc_mark` (the old marking functions)
|
||||
- `JSGCPhaseEnum`, `gc_phase` field in JSRuntime
|
||||
- `gc_obj_list`, `gc_zero_ref_count_list` in JSRuntime
|
||||
|
||||
**Update:**
|
||||
- `JS_FreeValue` - no ref counting, just mark for GC or no-op
|
||||
- `JS_DupValue` - no ref counting, just return value
|
||||
- `__JS_FreeValueRT` - simplified, no ref count checks
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Update Allocation Sites
|
||||
|
||||
### 6.1 Replace js_malloc with ctx_alloc
|
||||
|
||||
All object allocations change from:
|
||||
```c
|
||||
JSRecord *rec = js_mallocz(ctx, sizeof(JSRecord));
|
||||
```
|
||||
to:
|
||||
```c
|
||||
MistRecord *rec = ctx_alloc(ctx, sizeof(MistRecord) + (mask+1) * 2 * sizeof(JSValue));
|
||||
rec->hdr = objhdr_make(mask, OBJ_RECORD, false, false, false, false);
|
||||
```
|
||||
|
||||
### 6.2 Update object creation functions
|
||||
|
||||
- `JS_NewObject` - use ctx_alloc, set hdr
|
||||
- `JS_NewArray` - use ctx_alloc, set hdr
|
||||
- `JS_NewStringLen` - use ctx_alloc for heap strings
|
||||
- `js_create_function` - use ctx_alloc
|
||||
- String concatenation, array push, etc.
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Update Type Checks
|
||||
|
||||
Replace `JSGCObjectHeader.gc_obj_type` checks with `objhdr_type`:
|
||||
|
||||
```c
|
||||
/* Old */
|
||||
((JSGCObjectHeader *)ptr)->gc_obj_type == JS_GC_OBJ_TYPE_RECORD
|
||||
|
||||
/* New */
|
||||
objhdr_type(*(objhdr_t *)ptr) == OBJ_RECORD
|
||||
```
|
||||
|
||||
Update helper functions:
|
||||
- `js_is_record(v)` - check `objhdr_type == OBJ_RECORD`
|
||||
- `js_is_array(v)` - check `objhdr_type == OBJ_ARRAY`
|
||||
- `js_is_function(v)` - check `objhdr_type == OBJ_FUNCTION`
|
||||
- `JS_IsString(v)` - check `objhdr_type == OBJ_TEXT`
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Handle C Opaque Objects
|
||||
|
||||
Per docs/memory.md, C opaque objects need special handling:
|
||||
|
||||
**8.1 Track live opaque objects**
|
||||
```c
|
||||
typedef struct {
|
||||
void *opaque;
|
||||
JSClassID class_id;
|
||||
uint8_t alive;
|
||||
} OpaqueRef;
|
||||
|
||||
/* In JSContext */
|
||||
OpaqueRef *opaque_refs;
|
||||
int opaque_ref_count;
|
||||
int opaque_ref_capacity;
|
||||
```
|
||||
|
||||
**8.2 During GC**
|
||||
1. When copying a MistRecord with opaque data, mark it alive
|
||||
2. After GC, iterate opaque_refs and call finalizer for those with `alive=0`
|
||||
3. Clear all alive flags for next cycle
|
||||
|
||||
---
|
||||
|
||||
## File Changes Summary
|
||||
|
||||
### source/quickjs.c
|
||||
- Remove ~500 lines: RC_TRACE, gc_decref, gc_free_cycles, JSGCObjectHeader usage
|
||||
- Add ~300 lines: buddy allocator, Cheney GC, new object layouts
|
||||
- Modify ~200 lines: allocation sites, type checks
|
||||
|
||||
### source/quickjs.h
|
||||
- Remove: JSGCObjectHeader from public API
|
||||
- Update: JS_FreeValue, JS_DupValue to be no-ops or trivial
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
1. **Build**: `make` should compile without errors
|
||||
2. **Basic test**: `./cell test suite` should pass
|
||||
3. **Memory test**: Run with ASAN to verify no leaks or corruption
|
||||
4. **GC trigger**: Test that GC runs when memory fills, objects survive correctly
|
||||
|
||||
---
|
||||
|
||||
## Dependencies / Order of Work
|
||||
|
||||
1. Phase 1 (Buddy) - independent, implement first
|
||||
2. Phase 2 (JSContext) - depends on Phase 1
|
||||
3. Phase 3 (Headers) - major refactor, careful testing needed
|
||||
4. Phase 4 (Cheney GC) - depends on Phases 1-3
|
||||
5. Phase 5 (Remove old GC) - after Phase 4 works
|
||||
6. Phase 6 (Allocation sites) - incremental, with Phase 3
|
||||
7. Phase 7 (Type checks) - with Phase 3
|
||||
8. Phase 8 (Opaque) - last, once basic GC works
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Stone arena (immutable interned strings) remains unchanged - not subject to GC
|
||||
- OBJ_CODE lives in stone memory, never copied
|
||||
- Frames use caller=null to signal returnable (can be shrunk during GC)
|
||||
- Forward pointer type (7) used during GC to mark copied objects
|
||||
90
plan.md
90
plan.md
@@ -235,6 +235,96 @@ Categories remaining:
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Migrate to New Tagging System (IN PROGRESS)
|
||||
|
||||
**Problem**: `JS_VALUE_GET_TAG` returns `JS_TAG_PTR` for all pointers, but ~200 places check for obsolete tags like `JS_TAG_OBJECT`, `JS_TAG_STRING`, `JS_TAG_FUNCTION`, etc., which are never returned. This causes crashes.
|
||||
|
||||
**Target Design** (from memory.md):
|
||||
- JSValue tags: Only `JS_TAG_INT`, `JS_TAG_PTR`, `JS_TAG_SHORT_FLOAT`, `JS_TAG_SPECIAL`
|
||||
- Pointer types determined by `objhdr_t` header at offset 8 in heap objects
|
||||
- mist_obj_type: `OBJ_ARRAY(0)`, `OBJ_BLOB(1)`, `OBJ_TEXT(2)`, `OBJ_RECORD(3)`, `OBJ_FUNCTION(4)`, etc.
|
||||
|
||||
### 8.1 Unified Heap Object Layout ✓
|
||||
- [x] Updated mist_text structure to have objhdr_t at offset 8:
|
||||
```c
|
||||
typedef struct mist_text {
|
||||
JSRefCountHeader _dummy_header; /* unused, for offset alignment */
|
||||
uint32_t _pad; /* padding to align objhdr_t to offset 8 */
|
||||
objhdr_t hdr; /* NOW at offset 8, like JSString */
|
||||
word_t length;
|
||||
word_t packed[];
|
||||
} mist_text;
|
||||
```
|
||||
- [x] JSString already has objhdr_t at offset 8
|
||||
|
||||
### 8.2 Type-Checking Helper Functions ✓
|
||||
Added lowercase internal helpers (to avoid conflict with quickjs.h declarations):
|
||||
```c
|
||||
static inline JS_BOOL js_is_gc_object(JSValue v) { return JS_IsPtr(v); }
|
||||
static inline JSGCObjectTypeEnum js_get_gc_type(JSValue v) {
|
||||
return ((JSGCObjectHeader *)JS_VALUE_GET_PTR(v))->gc_obj_type;
|
||||
}
|
||||
static inline JS_BOOL js_is_record(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_RECORD;
|
||||
}
|
||||
static inline JS_BOOL js_is_array(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_ARRAY;
|
||||
}
|
||||
static inline JS_BOOL js_is_function(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_FUNCTION;
|
||||
}
|
||||
static inline JS_BOOL js_is_object(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
JSGCObjectTypeEnum t = js_get_gc_type(v);
|
||||
return t == JS_GC_OBJ_TYPE_RECORD || t == JS_GC_OBJ_TYPE_ARRAY;
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 Updated Core Functions ✓
|
||||
- [x] Updated JS_IsString to read objhdr_t from offset 8
|
||||
- [x] Updated js_key_hash to read objhdr_t from offset 8
|
||||
- [x] Updated js_key_equal to read objhdr_t from offset 8
|
||||
- [x] Updated __JS_FreeValueRT to use objhdr_type for type dispatch
|
||||
- [x] Updated JS_MarkValue, JS_MarkValueEdgeEx for GC
|
||||
- [x] Added JS_SetPropertyValue function
|
||||
- [x] Changed quickjs.h JS_IsFunction/JS_IsObject from inline to extern declarations
|
||||
|
||||
### 8.4 Tag Check Migration (PARTIAL)
|
||||
Updated some critical tag checks:
|
||||
- [x] Some JS_TAG_OBJECT checks → js_is_object() or js_is_record()
|
||||
- [ ] Many more JS_TAG_OBJECT checks remain (~200 total)
|
||||
- [ ] JS_TAG_FUNCTION checks → js_is_function()
|
||||
- [ ] JS_TAG_STRING checks (some already use JS_IsString)
|
||||
|
||||
### 8.5 Remaining Work
|
||||
- [ ] Fix ASAN memory corruption error (attempting free on address not malloc'd)
|
||||
- Crash occurs in js_def_realloc during js_realloc_array
|
||||
- Address is 112 bytes inside a JSFunctionDef allocation
|
||||
- [ ] Complete remaining ~200 tag check migrations
|
||||
- [ ] Add mist_hdr to JSFunction (optional, gc_obj_type already works)
|
||||
- [ ] Remove obsolete tag definitions from quickjs.h:
|
||||
- JS_TAG_STRING = -8
|
||||
- JS_TAG_ARRAY = -6
|
||||
- JS_TAG_FUNCTION = -5
|
||||
- JS_TAG_FUNCTION_BYTECODE = -2
|
||||
- JS_TAG_OBJECT = -1
|
||||
|
||||
### Current Status
|
||||
|
||||
**Build: SUCCEEDS** with warnings
|
||||
|
||||
**Runtime: CRASHES** with ASAN error:
|
||||
```
|
||||
==16122==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed
|
||||
```
|
||||
The crash occurs during test execution in `js_def_realloc` called from `js_realloc_array`.
|
||||
Root cause not yet identified - likely a pointer being passed to realloc that wasn't allocated with malloc.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- JSVarDef.var_name is JSValue
|
||||
|
||||
595
source/quickjs.c
595
source/quickjs.c
@@ -499,7 +499,9 @@ typedef struct mist_blob {
|
||||
Packed: 2 UTF32 per word, high 32 then low 32.
|
||||
*/
|
||||
typedef struct mist_text {
|
||||
objhdr_t hdr;
|
||||
JSRefCountHeader _dummy_header; /* unused, for offset alignment with JSString */
|
||||
uint32_t _pad; /* padding to align objhdr_t to offset 8 */
|
||||
objhdr_t hdr; /* NOW at offset 8, like JSString */
|
||||
word_t length;
|
||||
word_t packed[]; // count = (cap + 1)/2
|
||||
} mist_text;
|
||||
@@ -1045,7 +1047,9 @@ static JSValue intern_text_to_value (JSContext *ctx, const uint32_t *utf32,
|
||||
mist_text *text = st_alloc (ctx, text_size, 8);
|
||||
if (!text) return JS_NULL; /* OOM */
|
||||
|
||||
/* Initialize the text */
|
||||
/* Initialize the text (with unified layout: hdr at offset 8) */
|
||||
text->_dummy_header.ref_count = 1; /* dummy, not used for GC */
|
||||
text->_pad = 0;
|
||||
text->hdr = objhdr_make (len, OBJ_TEXT, false, false, false,
|
||||
true); /* s=1 for stoned */
|
||||
text->length = hash; /* Store hash in length field for stoned text */
|
||||
@@ -1165,17 +1169,17 @@ static void rc_dump_atom (JSRuntime *rt, uint32_t atom) {
|
||||
}
|
||||
#endif
|
||||
|
||||
/* JS_IsString implementation: checks for immediate string or OBJ_TEXT pointer
|
||||
*/
|
||||
JS_BOOL
|
||||
JS_IsString (JSValue v) {
|
||||
/* JS_IsString implementation: checks for immediate string or OBJ_TEXT pointer.
|
||||
Both mist_text and JSString now have objhdr_t at offset 8. */
|
||||
JS_BOOL JS_IsString (JSValue v) {
|
||||
/* Check tag - immediate string? */
|
||||
if (MIST_IsImmediateASCII (v)) return 1;
|
||||
|
||||
/* Check pointer type */
|
||||
if (JS_IsPtr (v)) {
|
||||
void *ptr = JS_VALUE_GET_PTR (v);
|
||||
objhdr_t hdr = *(objhdr_t *)ptr;
|
||||
/* objhdr_t is at offset 8 (after dummy header/pad) */
|
||||
objhdr_t hdr = *((objhdr_t *)((char *)ptr + 8));
|
||||
return objhdr_type (hdr) == OBJ_TEXT;
|
||||
}
|
||||
return 0;
|
||||
@@ -1199,7 +1203,7 @@ static inline uint8_t JS_VALUE_GET_MIST_TYPE (JSValue v) {
|
||||
|
||||
/* Check if a JSValue points to a mist-based object (has mist_hdr) */
|
||||
static inline JS_BOOL JS_VALUE_IS_MIST (JSValue v) {
|
||||
if (JS_VALUE_GET_TAG (v) != JS_TAG_OBJECT) return FALSE;
|
||||
if (!JS_IsPtr (v)) return FALSE;
|
||||
JSMistBase *p = (JSMistBase *)JS_VALUE_GET_PTR (v);
|
||||
uint8_t type = objhdr_type (p->mist_hdr);
|
||||
return type != 0;
|
||||
@@ -1279,7 +1283,7 @@ typedef struct JSRecord {
|
||||
|
||||
/* Check if a JSValue is a JSRecord */
|
||||
static inline JS_BOOL JS_VALUE_IS_RECORD (JSValue v) {
|
||||
if (JS_VALUE_GET_TAG (v) != JS_TAG_OBJECT) return FALSE;
|
||||
if (!JS_IsPtr (v)) return FALSE;
|
||||
return ((JSGCObjectHeader *)JS_VALUE_GET_PTR (v))->gc_obj_type
|
||||
== JS_GC_OBJ_TYPE_RECORD;
|
||||
}
|
||||
@@ -1303,10 +1307,17 @@ static uint64_t js_key_hash (JSValue key) {
|
||||
if (!JS_IsPtr (key)) return 0;
|
||||
|
||||
void *ptr = JS_VALUE_GET_PTR (key);
|
||||
objhdr_t hdr = *(objhdr_t *)ptr;
|
||||
|
||||
/* Both mist_text and JSString now have objhdr_t at offset 8.
|
||||
JSRecord and JSArray also have mist_hdr at offset 8. */
|
||||
objhdr_t hdr = *((objhdr_t *)((char *)ptr + 8));
|
||||
uint8_t type = objhdr_type (hdr);
|
||||
|
||||
if (type == OBJ_TEXT) return get_text_hash ((mist_text *)ptr);
|
||||
if (type == OBJ_TEXT) {
|
||||
/* For mist_text (stoned strings), use get_text_hash */
|
||||
mist_text *text = (mist_text *)ptr;
|
||||
return get_text_hash (text);
|
||||
}
|
||||
if (type == OBJ_RECORD) {
|
||||
JSRecord *rec = (JSRecord *)ptr;
|
||||
if (rec->rec_id == 0) return 0;
|
||||
@@ -1337,14 +1348,15 @@ static JS_BOOL js_key_equal (JSValue a, JSValue b) {
|
||||
|
||||
void *pa = JS_VALUE_GET_PTR (a);
|
||||
void *pb = JS_VALUE_GET_PTR (b);
|
||||
objhdr_t ha = *(objhdr_t *)pa;
|
||||
objhdr_t hb = *(objhdr_t *)pb;
|
||||
uint8_t ta = objhdr_type (ha);
|
||||
uint8_t tb = objhdr_type (hb);
|
||||
/* objhdr_t is at offset 8 for all heap objects (mist_text, JSString, JSRecord) */
|
||||
objhdr_t ha = *((objhdr_t *)((char *)pa + 8));
|
||||
objhdr_t hb = *((objhdr_t *)((char *)pb + 8));
|
||||
uint8_t type_a = objhdr_type (ha);
|
||||
uint8_t type_b = objhdr_type (hb);
|
||||
|
||||
if (ta != tb) return FALSE;
|
||||
if (ta == OBJ_RECORD) return FALSE; /* pointer equality handled above */
|
||||
if (ta == OBJ_TEXT)
|
||||
if (type_a != type_b) return FALSE;
|
||||
if (type_a == OBJ_RECORD) return FALSE; /* pointer equality handled above */
|
||||
if (type_a == OBJ_TEXT)
|
||||
return mist_text_equal ((mist_text *)pa, (mist_text *)pb);
|
||||
|
||||
return FALSE;
|
||||
@@ -1630,11 +1642,73 @@ typedef struct JSFunction {
|
||||
#define JS_VALUE_GET_ARRAY(v) ((JSArray *)JS_VALUE_GET_PTR (v))
|
||||
#define JS_VALUE_GET_FUNCTION(v) ((JSFunction *)JS_VALUE_GET_PTR (v))
|
||||
|
||||
/* Check if a JSValue is an intrinsic array (JS_TAG_OBJECT with OBJ_ARRAY mist
|
||||
* type) */
|
||||
/* ============================================================
|
||||
Pointer Type Checking Helpers
|
||||
============================================================
|
||||
With the new tagging system, JS_VALUE_GET_TAG returns JS_TAG_PTR
|
||||
for all pointer types. To distinguish objects, arrays, functions,
|
||||
etc., we check the gc_obj_type field in JSGCObjectHeader.
|
||||
============================================================ */
|
||||
|
||||
/* Check if value is a GC object (record, array, function, etc) */
|
||||
static inline JS_BOOL js_is_gc_object (JSValue v) {
|
||||
return JS_IsPtr (v);
|
||||
}
|
||||
|
||||
/* Get gc_obj_type from a pointer JSValue */
|
||||
static inline JSGCObjectTypeEnum js_get_gc_type (JSValue v) {
|
||||
return ((JSGCObjectHeader *)JS_VALUE_GET_PTR (v))->gc_obj_type;
|
||||
}
|
||||
|
||||
/* Check if value is a Record (JS_GC_OBJ_TYPE_RECORD) */
|
||||
static inline JS_BOOL js_is_record (JSValue v) {
|
||||
if (!JS_IsPtr (v)) return FALSE;
|
||||
return js_get_gc_type (v) == JS_GC_OBJ_TYPE_RECORD;
|
||||
}
|
||||
|
||||
/* Check if value is an Array (JS_GC_OBJ_TYPE_ARRAY) */
|
||||
static inline JS_BOOL js_is_array (JSValue v) {
|
||||
if (!JS_IsPtr (v)) return FALSE;
|
||||
return js_get_gc_type (v) == JS_GC_OBJ_TYPE_ARRAY;
|
||||
}
|
||||
|
||||
/* Check if value is a Function (JS_GC_OBJ_TYPE_FUNCTION) */
|
||||
static inline JS_BOOL js_is_function (JSValue v) {
|
||||
if (!JS_IsPtr (v)) return FALSE;
|
||||
return js_get_gc_type (v) == JS_GC_OBJ_TYPE_FUNCTION;
|
||||
}
|
||||
|
||||
/* Check if value is a FunctionBytecode (JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) */
|
||||
static inline JS_BOOL js_is_function_bytecode (JSValue v) {
|
||||
if (!JS_IsPtr (v)) return FALSE;
|
||||
return js_get_gc_type (v) == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE;
|
||||
}
|
||||
|
||||
/* Check if value is an "object" - record or array (can have properties set) */
|
||||
static inline JS_BOOL js_is_object (JSValue v) {
|
||||
if (!JS_IsPtr (v)) return FALSE;
|
||||
JSGCObjectTypeEnum t = js_get_gc_type (v);
|
||||
return t == JS_GC_OBJ_TYPE_RECORD || t == JS_GC_OBJ_TYPE_ARRAY;
|
||||
}
|
||||
|
||||
/* Check if value is a callable (function) */
|
||||
static inline JS_BOOL js_is_callable (JSValue v) {
|
||||
return js_is_function (v);
|
||||
}
|
||||
|
||||
/* Check if a JSValue is an intrinsic array (OBJ_ARRAY via gc_obj_type) */
|
||||
static inline JS_BOOL JS_VALUE_IS_INTRINSIC_ARRAY (JSValue v) {
|
||||
if (JS_VALUE_GET_TAG (v) != JS_TAG_OBJECT) return FALSE;
|
||||
return JS_VALUE_GET_MIST_TYPE (v) == OBJ_ARRAY;
|
||||
return js_is_array (v);
|
||||
}
|
||||
|
||||
/* Public API: check if value is a function */
|
||||
JS_BOOL JS_IsFunction (JSValue v) {
|
||||
return js_is_function (v);
|
||||
}
|
||||
|
||||
/* Public API: check if value is an object (record or array) */
|
||||
JS_BOOL JS_IsObject (JSValue v) {
|
||||
return js_is_object (v);
|
||||
}
|
||||
|
||||
typedef struct JSClosureVar {
|
||||
@@ -1839,8 +1913,7 @@ static void gc_decref_child (JSRuntime *rt, JSGCObjectHeader *p);
|
||||
Internal use only. */
|
||||
int JS_SetPropertyInternal (JSContext *ctx, JSValue this_obj, JSValue prop,
|
||||
JSValue val) {
|
||||
uint32_t tag = JS_VALUE_GET_TAG (this_obj);
|
||||
if (tag != JS_TAG_OBJECT) {
|
||||
if (!js_is_object (this_obj)) {
|
||||
JS_FreeValue (ctx, val);
|
||||
return -1;
|
||||
}
|
||||
@@ -2337,6 +2410,9 @@ static JSString *js_alloc_string_rt (JSRuntime *rt, int max_len) {
|
||||
str = js_malloc_rt (rt, size);
|
||||
if (unlikely (!str)) return NULL;
|
||||
str->header.ref_count = 1;
|
||||
str->pad = 0;
|
||||
/* Initialize objhdr_t with OBJ_TEXT type so JS_IsString can detect it */
|
||||
str->hdr = objhdr_make (max_len, OBJ_TEXT, false, false, false, false);
|
||||
str->len = max_len;
|
||||
// Initialize content to 0?
|
||||
// memset(str->u, 0, data_words * sizeof(uint64_t));
|
||||
@@ -2801,7 +2877,7 @@ JSClassID JS_NewClassID (JSClassID *pclass_id) {
|
||||
|
||||
JSClassID JS_GetClassID (JSValue v) {
|
||||
JSRecord *rec;
|
||||
if (JS_VALUE_GET_TAG (v) != JS_TAG_OBJECT) return JS_INVALID_CLASS_ID;
|
||||
if (!js_is_record (v)) return JS_INVALID_CLASS_ID;
|
||||
rec = JS_VALUE_GET_RECORD (v);
|
||||
return rec->class_id;
|
||||
}
|
||||
@@ -3519,7 +3595,7 @@ static JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2) {
|
||||
}
|
||||
|
||||
static JSRecord *get_proto_obj (JSValue proto_val) {
|
||||
if (JS_VALUE_GET_TAG (proto_val) != JS_TAG_OBJECT)
|
||||
if (!js_is_record (proto_val))
|
||||
return NULL;
|
||||
else
|
||||
return JS_VALUE_GET_OBJ (proto_val);
|
||||
@@ -3532,8 +3608,7 @@ JSValue JS_NewObjectProtoClass (JSContext *ctx, JSValue proto_val,
|
||||
if (!rec) return JS_EXCEPTION;
|
||||
|
||||
/* Set prototype if provided */
|
||||
if (JS_VALUE_GET_TAG (proto_val) == JS_TAG_OBJECT
|
||||
&& JS_VALUE_IS_RECORD (proto_val)) {
|
||||
if (js_is_record (proto_val)) {
|
||||
rec->proto = JS_VALUE_GET_RECORD (proto_val);
|
||||
}
|
||||
|
||||
@@ -4035,12 +4110,10 @@ static void free_zero_refcount (JSRuntime *rt) {
|
||||
|
||||
/* called with the ref_count of 'v' reaches zero. */
|
||||
void __JS_FreeValueRT (JSRuntime *rt, JSValue v) {
|
||||
uint32_t tag = JS_VALUE_GET_TAG (v);
|
||||
|
||||
#ifdef DUMP_FREE
|
||||
{
|
||||
printf ("Freeing ");
|
||||
if (tag == JS_TAG_OBJECT) {
|
||||
if (js_is_record (v)) {
|
||||
JS_DumpObject (rt, JS_VALUE_GET_RECORD (v));
|
||||
} else {
|
||||
JS_DumpValueShort (rt, v);
|
||||
@@ -4049,33 +4122,52 @@ void __JS_FreeValueRT (JSRuntime *rt, JSValue v) {
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (tag) {
|
||||
case JS_TAG_STRING: {
|
||||
JSString *p = JS_VALUE_GET_STRING (v);
|
||||
/* atom_type removed - strings are no longer atoms */
|
||||
/* New tag system: JS_TAG_PTR for all pointer types */
|
||||
if (JS_IsPtr (v)) {
|
||||
void *ptr = JS_VALUE_GET_PTR (v);
|
||||
|
||||
/* Check objhdr_t at offset 8 to determine type.
|
||||
Both strings (mist_text/JSString) and GC objects have objhdr_t at offset 8. */
|
||||
objhdr_t hdr = *((objhdr_t *)((char *)ptr + 8));
|
||||
uint8_t type = objhdr_type (hdr);
|
||||
|
||||
/* Handle heap strings - type is OBJ_TEXT */
|
||||
if (type == OBJ_TEXT) {
|
||||
/* Check if this is a stone-allocated mist_text (don't free) or
|
||||
a heap-allocated JSString (do free) */
|
||||
if (objhdr_s (hdr)) {
|
||||
/* Stone-allocated mist_text - don't free (lives in stone arena) */
|
||||
return;
|
||||
}
|
||||
/* Heap-allocated JSString - free it */
|
||||
JSString *str = (JSString *)ptr;
|
||||
#ifdef DUMP_LEAKS
|
||||
list_del (&p->link);
|
||||
list_del (&str->link);
|
||||
#endif
|
||||
js_free_rt (rt, p);
|
||||
} break;
|
||||
case JS_TAG_STRING_IMM:
|
||||
/* Immediate strings do not need freeing */
|
||||
break;
|
||||
case JS_TAG_OBJECT: /* includes arrays (OBJ_ARRAY), functions, etc. via
|
||||
mist_hdr */
|
||||
case JS_TAG_FUNCTION_BYTECODE:
|
||||
case JS_TAG_FUNCTION: {
|
||||
JSGCObjectHeader *p = JS_VALUE_GET_PTR (v);
|
||||
js_free_rt (rt, str);
|
||||
return;
|
||||
}
|
||||
|
||||
/* GC objects: record, array, function, function_bytecode, var_ref, etc. */
|
||||
JSGCObjectHeader *p = (JSGCObjectHeader *)ptr;
|
||||
if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) {
|
||||
list_del (&p->link);
|
||||
list_add (&p->link, &rt->gc_zero_ref_count_list);
|
||||
p->mark = 1; /* indicate that the object is about to be freed */
|
||||
if (rt->gc_phase == JS_GC_PHASE_NONE) { free_zero_refcount (rt); }
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
abort ();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Non-pointer values that shouldn't reach here (except immediate strings) */
|
||||
uint32_t tag = JS_VALUE_GET_TAG (v);
|
||||
if (tag == JS_TAG_STRING_IMM) {
|
||||
/* Immediate strings do not need freeing */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Unexpected value type */
|
||||
abort ();
|
||||
}
|
||||
|
||||
void __JS_FreeValue (JSContext *ctx, JSValue v) {
|
||||
@@ -4094,15 +4186,14 @@ static void add_gc_object (JSRuntime *rt, JSGCObjectHeader *h,
|
||||
static void remove_gc_object (JSGCObjectHeader *h) { list_del (&h->link); }
|
||||
|
||||
void JS_MarkValue (JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) {
|
||||
if (JS_VALUE_HAS_REF_COUNT (val)) {
|
||||
switch (JS_VALUE_GET_TAG (val)) {
|
||||
case JS_TAG_OBJECT: /* includes arrays (OBJ_ARRAY) via mist_hdr */
|
||||
case JS_TAG_FUNCTION_BYTECODE:
|
||||
case JS_TAG_FUNCTION:
|
||||
mark_func (rt, JS_VALUE_GET_PTR (val));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (JS_IsPtr (val)) {
|
||||
void *ptr = JS_VALUE_GET_PTR (val);
|
||||
/* Check objhdr_t at offset 8 to determine type */
|
||||
objhdr_t hdr = *((objhdr_t *)((char *)ptr + 8));
|
||||
uint8_t type = objhdr_type (hdr);
|
||||
/* Only mark GC objects (not OBJ_TEXT strings) */
|
||||
if (type != OBJ_TEXT) {
|
||||
mark_func (rt, (JSGCObjectHeader *)ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4227,22 +4318,21 @@ static inline void JS_MarkValueEdgeEx (JSRuntime *rt, JSValue val,
|
||||
JSGCObjectHeader *parent,
|
||||
const char *edge, uint32_t atom,
|
||||
int32_t prop_index) {
|
||||
if (!JS_VALUE_HAS_REF_COUNT (val)) { return; }
|
||||
if (!JS_IsPtr (val)) { return; }
|
||||
|
||||
switch (JS_VALUE_GET_TAG (val)) {
|
||||
case JS_TAG_OBJECT: /* includes arrays (OBJ_ARRAY) via mist_hdr */
|
||||
case JS_TAG_FUNCTION_BYTECODE:
|
||||
case JS_TAG_FUNCTION: {
|
||||
JSGCObjectHeader *child = JS_VALUE_GET_PTR (val);
|
||||
void *ptr = JS_VALUE_GET_PTR (val);
|
||||
/* Check objhdr_t at offset 8 to determine type */
|
||||
objhdr_t hdr = *((objhdr_t *)((char *)ptr + 8));
|
||||
uint8_t type = objhdr_type (hdr);
|
||||
/* Only mark GC objects (not OBJ_TEXT strings) */
|
||||
if (type != OBJ_TEXT) {
|
||||
JSGCObjectHeader *child = (JSGCObjectHeader *)ptr;
|
||||
#ifdef RC_TRACE
|
||||
gc_decref_child_dbg (rt, parent, edge, atom, prop_index, child, __FILE__,
|
||||
__LINE__);
|
||||
#else
|
||||
gc_decref_child (rt, child);
|
||||
#endif
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4875,7 +4965,7 @@ fail:
|
||||
when dumping the stack trace or in debug print). */
|
||||
static const char *get_prop_string (JSContext *ctx, JSValue obj,
|
||||
JSValue prop) {
|
||||
if (JS_VALUE_GET_TAG (obj) != JS_TAG_OBJECT) return NULL;
|
||||
if (!js_is_record (obj)) return NULL;
|
||||
JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (obj);
|
||||
|
||||
int slot = rec_find_slot (rec, prop);
|
||||
@@ -4890,7 +4980,7 @@ static const char *get_prop_string (JSContext *ctx, JSValue obj,
|
||||
}
|
||||
|
||||
JSValue val = rec->tab[slot].val;
|
||||
if (JS_VALUE_GET_TAG (val) != JS_TAG_STRING) return NULL;
|
||||
if (!JS_IsString (val)) return NULL;
|
||||
return JS_ToCString (ctx, val);
|
||||
}
|
||||
|
||||
@@ -4898,7 +4988,7 @@ static const char *get_prop_string (JSContext *ctx, JSValue obj,
|
||||
generation, we only look at simple 'name' properties containing a
|
||||
string. */
|
||||
static const char *get_func_name (JSContext *ctx, JSValue func) {
|
||||
if (JS_VALUE_GET_TAG (func) != JS_TAG_OBJECT) return NULL;
|
||||
if (!js_is_record (func)) return NULL;
|
||||
JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (func);
|
||||
|
||||
/* Create "name" key as immediate ASCII string */
|
||||
@@ -4908,7 +4998,7 @@ static const char *get_func_name (JSContext *ctx, JSValue func) {
|
||||
if (slot <= 0) return NULL;
|
||||
|
||||
JSValue val = rec->tab[slot].val;
|
||||
if (JS_VALUE_GET_TAG (val) != JS_TAG_STRING) return NULL;
|
||||
if (!JS_IsString (val)) return NULL;
|
||||
return JS_ToCString (ctx, val);
|
||||
}
|
||||
|
||||
@@ -5008,7 +5098,7 @@ static void build_backtrace (JSContext *ctx, JSValue error_obj,
|
||||
/* Note: it is important that no exception is returned by this function */
|
||||
static BOOL is_backtrace_needed (JSContext *ctx, JSValue obj) {
|
||||
JSRecord *p;
|
||||
if (JS_VALUE_GET_TAG (obj) != JS_TAG_OBJECT) return FALSE;
|
||||
if (!js_is_record (obj)) return FALSE;
|
||||
p = JS_VALUE_GET_OBJ (obj);
|
||||
if (p->class_id != JS_CLASS_ERROR) return FALSE;
|
||||
/* Check if "stack" property already exists */
|
||||
@@ -5194,7 +5284,7 @@ static inline __exception int js_poll_interrupts (JSContext *ctx) {
|
||||
/* Return an Object, JS_NULL or JS_EXCEPTION in case of exotic object. */
|
||||
JSValue JS_GetPrototype (JSContext *ctx, JSValue obj) {
|
||||
JSValue val;
|
||||
if (JS_VALUE_GET_TAG (obj) == JS_TAG_OBJECT) {
|
||||
if (js_is_record (obj)) {
|
||||
JSRecord *p;
|
||||
p = JS_VALUE_GET_OBJ (obj);
|
||||
p = JS_OBJ_GET_PROTO (p);
|
||||
@@ -5218,18 +5308,12 @@ static JSValue JS_GetPrototypeFree (JSContext *ctx, JSValue obj) {
|
||||
|
||||
/* Get property from object using JSRecord-based lookup */
|
||||
JSValue JS_GetProperty (JSContext *ctx, JSValue obj, JSValue prop) {
|
||||
uint32_t tag = JS_VALUE_GET_TAG (obj);
|
||||
if (JS_IsNull (obj)) return JS_NULL;
|
||||
if (JS_IsException (obj)) return JS_EXCEPTION;
|
||||
|
||||
if (unlikely (tag != JS_TAG_OBJECT)) {
|
||||
switch (tag) {
|
||||
case JS_TAG_NULL:
|
||||
return JS_NULL;
|
||||
case JS_TAG_EXCEPTION:
|
||||
return JS_EXCEPTION;
|
||||
default:
|
||||
/* Primitives have no properties */
|
||||
return JS_NULL;
|
||||
}
|
||||
if (unlikely (!js_is_object (obj))) {
|
||||
/* Primitives have no properties */
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
/* All objects are JSRecords now */
|
||||
@@ -5244,7 +5328,7 @@ JSValue JS_GetOwnPropertyNames (JSContext *ctx, JSValue obj) {
|
||||
uint32_t mask, count, i;
|
||||
JSValue arr;
|
||||
|
||||
if (JS_VALUE_GET_TAG (obj) != JS_TAG_OBJECT) {
|
||||
if (!js_is_record (obj)) {
|
||||
JS_ThrowTypeErrorNotAnObject (ctx);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
@@ -5287,7 +5371,7 @@ static int JS_GetOwnPropertyInternal (JSContext *ctx,
|
||||
}
|
||||
|
||||
int JS_GetOwnProperty (JSContext *ctx, JSValue *desc, JSValue obj, JSValue prop) {
|
||||
if (JS_VALUE_GET_TAG (obj) != JS_TAG_OBJECT) {
|
||||
if (!js_is_record (obj)) {
|
||||
JS_ThrowTypeErrorNotAnObject (ctx);
|
||||
return -1;
|
||||
}
|
||||
@@ -5298,7 +5382,7 @@ int JS_GetOwnProperty (JSContext *ctx, JSValue *desc, JSValue obj, JSValue prop)
|
||||
int JS_HasProperty (JSContext *ctx, JSValue obj, JSValue prop) {
|
||||
JSRecord *p;
|
||||
int ret;
|
||||
if (unlikely (JS_VALUE_GET_TAG (obj) != JS_TAG_OBJECT)) return FALSE;
|
||||
if (unlikely (!js_is_record (obj))) return FALSE;
|
||||
p = JS_VALUE_GET_OBJ (obj);
|
||||
for (;;) {
|
||||
/* JS_GetOwnPropertyInternal can free the prototype */
|
||||
@@ -5313,9 +5397,15 @@ int JS_HasProperty (JSContext *ctx, JSValue obj, JSValue prop) {
|
||||
}
|
||||
|
||||
static uint32_t js_string_get_length (JSValue val) {
|
||||
if (JS_VALUE_GET_TAG (val) == JS_TAG_STRING) {
|
||||
JSString *p = JS_VALUE_GET_STRING (val);
|
||||
return p->len;
|
||||
if (JS_IsPtr (val)) {
|
||||
void *ptr = JS_VALUE_GET_PTR (val);
|
||||
/* Check objhdr_t at offset 8 for type */
|
||||
objhdr_t hdr = *((objhdr_t *)((char *)ptr + 8));
|
||||
if (objhdr_type (hdr) == OBJ_TEXT) {
|
||||
/* String (JSString or mist_text) */
|
||||
return (uint32_t)objhdr_cap56 (hdr);
|
||||
}
|
||||
return 0;
|
||||
} else if (MIST_IsImmediateASCII (val)) {
|
||||
return MIST_GetImmediateASCIILen (val);
|
||||
} else {
|
||||
@@ -5327,9 +5417,8 @@ static JSValue JS_GetPropertyValue (JSContext *ctx, JSValue this_obj,
|
||||
JSValue prop) {
|
||||
JSValue ret;
|
||||
uint32_t prop_tag = JS_VALUE_GET_TAG (prop);
|
||||
int this_tag = JS_VALUE_GET_TAG (this_obj);
|
||||
|
||||
if (this_tag == JS_TAG_NULL) {
|
||||
if (JS_IsNull (this_obj)) {
|
||||
JS_FreeValue (ctx, prop);
|
||||
return JS_NULL;
|
||||
}
|
||||
@@ -5340,7 +5429,7 @@ static JSValue JS_GetPropertyValue (JSContext *ctx, JSValue this_obj,
|
||||
return JS_GetPropertyNumber (ctx, this_obj, idx);
|
||||
}
|
||||
|
||||
if (prop_tag == JS_TAG_FLOAT64) {
|
||||
if (prop_tag == JS_TAG_SHORT_FLOAT) {
|
||||
double d = JS_VALUE_GET_FLOAT64 (prop);
|
||||
uint32_t idx = (uint32_t)d;
|
||||
JS_FreeValue (ctx, prop);
|
||||
@@ -5348,9 +5437,10 @@ static JSValue JS_GetPropertyValue (JSContext *ctx, JSValue this_obj,
|
||||
return JS_GetPropertyNumber (ctx, this_obj, idx);
|
||||
}
|
||||
|
||||
if (prop_tag == JS_TAG_STRING || prop_tag == JS_TAG_STRING_IMM) {
|
||||
/* Check for string property (immediate or heap) */
|
||||
if (JS_IsString (prop)) {
|
||||
/* Intrinsic arrays don't support string keys */
|
||||
if (JS_VALUE_IS_INTRINSIC_ARRAY (this_obj)) {
|
||||
if (js_is_array (this_obj)) {
|
||||
JS_FreeValue (ctx, prop);
|
||||
return JS_NULL;
|
||||
}
|
||||
@@ -5363,9 +5453,9 @@ static JSValue JS_GetPropertyValue (JSContext *ctx, JSValue this_obj,
|
||||
}
|
||||
|
||||
/* Handle object keys directly via objkey map */
|
||||
if (prop_tag == JS_TAG_OBJECT) {
|
||||
if (js_is_record (prop)) {
|
||||
/* Intrinsic arrays don't support object keys */
|
||||
if (this_tag != JS_TAG_OBJECT || JS_VALUE_IS_INTRINSIC_ARRAY (this_obj)) {
|
||||
if (!js_is_record (this_obj)) {
|
||||
JS_FreeValue (ctx, prop);
|
||||
return JS_NULL;
|
||||
}
|
||||
@@ -5400,17 +5490,16 @@ JSValue JS_SetPropertyNumber (JSContext *js, JSValue obj, int idx,
|
||||
}
|
||||
|
||||
JSValue JS_GetPropertyNumber (JSContext *js, JSValue obj, int idx) {
|
||||
if (JS_VALUE_IS_INTRINSIC_ARRAY (obj)) {
|
||||
if (js_is_array (obj)) {
|
||||
JSArray *a = JS_VALUE_GET_ARRAY (obj);
|
||||
int len = a->len;
|
||||
if (idx < 0 || idx >= len) { return JS_NULL; }
|
||||
return JS_DupValue (js, a->values[idx]);
|
||||
}
|
||||
|
||||
if (JS_VALUE_GET_TAG (obj) == JS_TAG_STRING
|
||||
|| JS_VALUE_GET_TAG (obj) == JS_TAG_STRING_IMM) {
|
||||
if (JS_IsString (obj)) {
|
||||
uint32_t len = js_string_get_length (obj);
|
||||
if (idx < 0 || idx >= len) { return JS_NULL; }
|
||||
if (idx < 0 || (uint32_t)idx >= len) { return JS_NULL; }
|
||||
return js_sub_string (js, JS_VALUE_GET_STRING (obj), idx, idx + 1);
|
||||
}
|
||||
|
||||
@@ -5486,11 +5575,9 @@ static int delete_property (JSContext *ctx, JSRecord *rec, JSValue key) {
|
||||
|
||||
int JS_SetProperty (JSContext *ctx, JSValue this_obj, JSValue prop,
|
||||
JSValue val) {
|
||||
uint32_t tag = JS_VALUE_GET_TAG (this_obj);
|
||||
|
||||
if (tag != JS_TAG_OBJECT) {
|
||||
if (!js_is_object (this_obj)) {
|
||||
JS_FreeValue (ctx, val);
|
||||
if (tag == JS_TAG_NULL) {
|
||||
if (JS_IsNull (this_obj)) {
|
||||
JS_ThrowTypeError (ctx, "cannot set property of null");
|
||||
} else {
|
||||
JS_ThrowTypeError (ctx, "cannot set property on a primitive");
|
||||
@@ -5549,17 +5636,46 @@ int JS_SetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop,
|
||||
return JS_SetProperty (ctx, this_obj, key, val);
|
||||
}
|
||||
|
||||
/* Set property with JSValue prop/key - handles int, string, object keys */
|
||||
static int JS_SetPropertyValue (JSContext *ctx, JSValue this_obj, JSValue prop,
|
||||
JSValue val) {
|
||||
uint32_t prop_tag = JS_VALUE_GET_TAG (prop);
|
||||
|
||||
if (prop_tag == JS_TAG_INT) {
|
||||
int idx = JS_VALUE_GET_INT (prop);
|
||||
JS_FreeValue (ctx, prop);
|
||||
JSValue ret = JS_SetPropertyNumber (ctx, this_obj, idx, val);
|
||||
if (JS_IsException (ret)) return -1;
|
||||
JS_FreeValue (ctx, ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (JS_IsString (prop)) {
|
||||
JSValue key = js_key_from_string (ctx, prop);
|
||||
JS_FreeValue (ctx, prop);
|
||||
return JS_SetProperty (ctx, this_obj, key, val);
|
||||
}
|
||||
|
||||
if (js_is_record (prop)) {
|
||||
JS_FreeValue (ctx, prop);
|
||||
return JS_SetProperty (ctx, this_obj, prop, val);
|
||||
}
|
||||
|
||||
JS_FreeValue (ctx, prop);
|
||||
JS_FreeValue (ctx, val);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Property access with JSValue key - supports object keys directly */
|
||||
JSValue JS_GetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key) {
|
||||
uint32_t tag = JS_VALUE_GET_TAG (key);
|
||||
if (tag == JS_TAG_OBJECT) {
|
||||
if (JS_VALUE_GET_TAG (this_obj) != JS_TAG_OBJECT) return JS_NULL;
|
||||
if (js_is_record (key)) {
|
||||
if (!js_is_record (this_obj)) return JS_NULL;
|
||||
JSRecord *rec = JS_VALUE_GET_RECORD (this_obj);
|
||||
return rec_get (ctx, rec, key);
|
||||
}
|
||||
|
||||
/* For string keys, create an interned key and use JS_GetProperty */
|
||||
if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) {
|
||||
if (JS_IsString (key)) {
|
||||
JSValue prop_key = js_key_from_string (ctx, key);
|
||||
return JS_GetProperty (ctx, this_obj, prop_key);
|
||||
}
|
||||
@@ -5570,9 +5686,8 @@ JSValue JS_GetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key) {
|
||||
|
||||
int JS_SetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key,
|
||||
JSValue val) {
|
||||
uint32_t tag = JS_VALUE_GET_TAG (key);
|
||||
if (tag == JS_TAG_OBJECT) {
|
||||
if (JS_VALUE_GET_TAG (this_obj) != JS_TAG_OBJECT) {
|
||||
if (js_is_record (key)) {
|
||||
if (!js_is_record (this_obj)) {
|
||||
JS_FreeValue (ctx, val);
|
||||
JS_ThrowTypeError (ctx, "cannot set property on this value");
|
||||
return -1;
|
||||
@@ -5587,7 +5702,7 @@ int JS_SetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key,
|
||||
}
|
||||
|
||||
/* For string keys, create an interned key */
|
||||
if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) {
|
||||
if (JS_IsString (key)) {
|
||||
JSValue prop_key = js_key_from_string (ctx, key);
|
||||
return JS_SetPropertyInternal (ctx, this_obj, prop_key, val);
|
||||
}
|
||||
@@ -5598,9 +5713,8 @@ int JS_SetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key,
|
||||
|
||||
/* Property existence check with JSValue key (supports object keys) */
|
||||
int JS_HasPropertyKey (JSContext *ctx, JSValue obj, JSValue key) {
|
||||
uint32_t tag = JS_VALUE_GET_TAG (key);
|
||||
if (tag == JS_TAG_OBJECT) {
|
||||
if (JS_VALUE_GET_TAG (obj) != JS_TAG_OBJECT) return FALSE;
|
||||
if (js_is_record (key)) {
|
||||
if (!js_is_record (obj)) return FALSE;
|
||||
JSRecord *rec = JS_VALUE_GET_RECORD (obj);
|
||||
/* Check own and prototype chain */
|
||||
while (rec) {
|
||||
@@ -5611,7 +5725,7 @@ int JS_HasPropertyKey (JSContext *ctx, JSValue obj, JSValue key) {
|
||||
}
|
||||
|
||||
/* For string keys, create an interned key */
|
||||
if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) {
|
||||
if (JS_IsString (key)) {
|
||||
JSValue prop_key = js_key_from_string (ctx, key);
|
||||
return JS_HasProperty (ctx, obj, prop_key);
|
||||
}
|
||||
@@ -5622,9 +5736,8 @@ int JS_HasPropertyKey (JSContext *ctx, JSValue obj, JSValue key) {
|
||||
|
||||
/* Property deletion with JSValue key (supports object keys) */
|
||||
int JS_DeletePropertyKey (JSContext *ctx, JSValue obj, JSValue key) {
|
||||
uint32_t tag = JS_VALUE_GET_TAG (key);
|
||||
if (tag == JS_TAG_OBJECT) {
|
||||
if (JS_VALUE_GET_TAG (obj) != JS_TAG_OBJECT) return FALSE;
|
||||
if (js_is_record (key)) {
|
||||
if (!js_is_record (obj)) return FALSE;
|
||||
JSRecord *rec = JS_VALUE_GET_RECORD (obj);
|
||||
if (rec_is_stone (rec)) {
|
||||
JS_ThrowTypeError (ctx, "cannot modify frozen object");
|
||||
@@ -5643,7 +5756,7 @@ int JS_DeletePropertyKey (JSContext *ctx, JSValue obj, JSValue key) {
|
||||
}
|
||||
|
||||
/* For string keys, create an interned key */
|
||||
if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) {
|
||||
if (JS_IsString (key)) {
|
||||
JSValue prop_key = js_key_from_string (ctx, key);
|
||||
return JS_DeleteProperty (ctx, obj, prop_key);
|
||||
}
|
||||
@@ -5955,7 +6068,7 @@ void *JS_GetOpaque2 (JSContext *ctx, JSValue obj, JSClassID class_id) {
|
||||
|
||||
void *JS_GetAnyOpaque (JSValue obj, JSClassID *class_id) {
|
||||
JSRecord *p;
|
||||
if (JS_VALUE_GET_TAG (obj) != JS_TAG_OBJECT) {
|
||||
if (!js_is_record (obj)) {
|
||||
*class_id = 0;
|
||||
return NULL;
|
||||
}
|
||||
@@ -5966,6 +6079,23 @@ void *JS_GetAnyOpaque (JSValue obj, JSClassID *class_id) {
|
||||
|
||||
static int JS_ToBoolFree (JSContext *ctx, JSValue val) {
|
||||
uint32_t tag = JS_VALUE_GET_TAG (val);
|
||||
|
||||
/* Check for pointer types first (new tagging system) */
|
||||
if (JS_IsPtr (val)) {
|
||||
void *ptr = JS_VALUE_GET_PTR (val);
|
||||
/* Check objhdr_t at offset 8 for type */
|
||||
objhdr_t hdr = *((objhdr_t *)((char *)ptr + 8));
|
||||
if (objhdr_type (hdr) == OBJ_TEXT) {
|
||||
/* String (JSString or mist_text) - truthy if non-empty */
|
||||
BOOL ret = objhdr_cap56 (hdr) != 0;
|
||||
JS_FreeValue (ctx, val);
|
||||
return ret;
|
||||
}
|
||||
/* Objects (record, array, function) are truthy */
|
||||
JS_FreeValue (ctx, val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
switch (tag) {
|
||||
case JS_TAG_INT:
|
||||
return JS_VALUE_GET_INT (val) != 0;
|
||||
@@ -5974,19 +6104,10 @@ static int JS_ToBoolFree (JSContext *ctx, JSValue val) {
|
||||
return JS_VALUE_GET_INT (val);
|
||||
case JS_TAG_EXCEPTION:
|
||||
return -1;
|
||||
case JS_TAG_STRING: {
|
||||
BOOL ret = JS_VALUE_GET_STRING (val)->len != 0;
|
||||
JS_FreeValue (ctx, val);
|
||||
return ret;
|
||||
}
|
||||
case JS_TAG_STRING_IMM: {
|
||||
BOOL ret = MIST_GetImmediateASCIILen (val) != 0;
|
||||
JS_FreeValue (ctx, val);
|
||||
return ret;
|
||||
}
|
||||
case JS_TAG_OBJECT: /* includes arrays (OBJ_ARRAY) via mist_hdr */
|
||||
JS_FreeValue (ctx, val);
|
||||
return 1;
|
||||
default:
|
||||
if (JS_TAG_IS_FLOAT64 (tag)) {
|
||||
double d = JS_VALUE_GET_FLOAT64 (val);
|
||||
@@ -6210,9 +6331,24 @@ static JSValue JS_ToNumberFree (JSContext *ctx, JSValue val) {
|
||||
uint32_t tag;
|
||||
JSValue ret;
|
||||
|
||||
/* Handle pointer types (new tagging system) */
|
||||
if (JS_IsPtr (val)) {
|
||||
void *ptr = JS_VALUE_GET_PTR (val);
|
||||
/* Check objhdr_t at offset 8 for type */
|
||||
objhdr_t hdr = *((objhdr_t *)((char *)ptr + 8));
|
||||
if (objhdr_type (hdr) == OBJ_TEXT) {
|
||||
/* String */
|
||||
JS_FreeValue (ctx, val);
|
||||
return JS_ThrowTypeError (ctx, "cannot convert text to a number");
|
||||
}
|
||||
/* Objects */
|
||||
JS_FreeValue (ctx, val);
|
||||
return JS_ThrowTypeError (ctx, "cannot convert object to number");
|
||||
}
|
||||
|
||||
tag = JS_VALUE_GET_NORM_TAG (val);
|
||||
switch (tag) {
|
||||
case JS_TAG_FLOAT64:
|
||||
case JS_TAG_SHORT_FLOAT:
|
||||
case JS_TAG_INT:
|
||||
case JS_TAG_EXCEPTION:
|
||||
ret = val;
|
||||
@@ -6221,12 +6357,7 @@ static JSValue JS_ToNumberFree (JSContext *ctx, JSValue val) {
|
||||
case JS_TAG_NULL:
|
||||
ret = JS_NewInt32 (ctx, JS_VALUE_GET_INT (val));
|
||||
break;
|
||||
case JS_TAG_OBJECT: /* includes arrays (OBJ_ARRAY) via mist_hdr */
|
||||
JS_FreeValue (ctx, val);
|
||||
return JS_ThrowTypeError (ctx, "cannot convert object to number");
|
||||
case JS_TAG_STRING:
|
||||
case JS_TAG_STRING_IMM:
|
||||
JS_FreeValue (ctx, val);
|
||||
return JS_ThrowTypeError (ctx, "cannot convert text to a number");
|
||||
default:
|
||||
JS_FreeValue (ctx, val);
|
||||
@@ -6602,10 +6733,27 @@ static JSValue JS_ToStringInternal (JSContext *ctx, JSValue val,
|
||||
uint32_t tag;
|
||||
char buf[32];
|
||||
|
||||
/* Handle pointer types (new tagging system) */
|
||||
if (JS_IsPtr (val)) {
|
||||
void *ptr = JS_VALUE_GET_PTR (val);
|
||||
/* Check objhdr_t at offset 8 for type */
|
||||
objhdr_t hdr = *((objhdr_t *)((char *)ptr + 8));
|
||||
uint8_t mist_type = objhdr_type (hdr);
|
||||
if (mist_type == OBJ_TEXT) {
|
||||
/* String - return as-is */
|
||||
return JS_DupValue (ctx, val);
|
||||
}
|
||||
/* For GC objects, check gc_obj_type */
|
||||
JSGCObjectHeader *p = (JSGCObjectHeader *)ptr;
|
||||
if (p->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) {
|
||||
return js_new_string8 (ctx, "[function bytecode]");
|
||||
}
|
||||
/* Objects (record, array, function) */
|
||||
return JS_KEY_true;
|
||||
}
|
||||
|
||||
tag = JS_VALUE_GET_NORM_TAG (val);
|
||||
switch (tag) {
|
||||
case JS_TAG_STRING:
|
||||
return JS_DupValue (ctx, val);
|
||||
case JS_TAG_STRING_IMM:
|
||||
return JS_DupValue (ctx, val);
|
||||
case JS_TAG_INT: {
|
||||
@@ -6619,11 +6767,7 @@ static JSValue JS_ToStringInternal (JSContext *ctx, JSValue val,
|
||||
return JS_KEY_null;
|
||||
case JS_TAG_EXCEPTION:
|
||||
return JS_EXCEPTION;
|
||||
case JS_TAG_OBJECT: /* includes arrays (OBJ_ARRAY) via mist_hdr */
|
||||
return JS_KEY_true;
|
||||
case JS_TAG_FUNCTION_BYTECODE:
|
||||
return js_new_string8 (ctx, "[function bytecode]");
|
||||
case JS_TAG_FLOAT64:
|
||||
case JS_TAG_SHORT_FLOAT:
|
||||
return js_dtoa2 (ctx, JS_VALUE_GET_FLOAT64 (val), 10, 0,
|
||||
JS_DTOA_FORMAT_FREE);
|
||||
default:
|
||||
@@ -6643,10 +6787,8 @@ static JSValue JS_ToStringFree (JSContext *ctx, JSValue val) {
|
||||
}
|
||||
|
||||
JSValue JS_ToPropertyKey (JSContext *ctx, JSValue val) {
|
||||
int tag = JS_VALUE_GET_TAG (val);
|
||||
|
||||
/* Objects are handled directly via objkey map, not through atoms */
|
||||
if (tag == JS_TAG_OBJECT) { return JS_DupValue (ctx, val); }
|
||||
if (js_is_object (val)) { return JS_DupValue (ctx, val); }
|
||||
|
||||
return JS_ToStringInternal (ctx, val, TRUE);
|
||||
}
|
||||
@@ -6939,6 +7081,95 @@ static void js_print_value (JSPrintValueState *s, JSValue val) {
|
||||
uint32_t tag = JS_VALUE_GET_NORM_TAG (val);
|
||||
const char *str;
|
||||
|
||||
/* Handle pointer types first (new tagging system) */
|
||||
if (JS_IsPtr (val)) {
|
||||
void *ptr = JS_VALUE_GET_PTR (val);
|
||||
/* Check objhdr_t at offset 8 for type */
|
||||
objhdr_t hdr = *((objhdr_t *)((char *)ptr + 8));
|
||||
uint8_t mist_type = objhdr_type (hdr);
|
||||
|
||||
if (mist_type == OBJ_TEXT) {
|
||||
/* String (JSString or mist_text) */
|
||||
js_print_string (s, val);
|
||||
return;
|
||||
}
|
||||
|
||||
/* For GC objects, use gc_obj_type */
|
||||
JSGCObjectHeader *p = (JSGCObjectHeader *)ptr;
|
||||
JSGCObjectTypeEnum obj_type = p->gc_obj_type;
|
||||
|
||||
switch (obj_type) {
|
||||
case JS_GC_OBJ_TYPE_FUNCTION_BYTECODE: {
|
||||
JSFunctionBytecode *b = (JSFunctionBytecode *)p;
|
||||
js_puts (s, "[bytecode ");
|
||||
if (JS_IsString (b->func_name)) {
|
||||
js_print_value (s, b->func_name);
|
||||
} else {
|
||||
js_puts (s, "<anon>");
|
||||
}
|
||||
js_putc (s, ']');
|
||||
} break;
|
||||
case JS_GC_OBJ_TYPE_FUNCTION: {
|
||||
JSFunction *f = (JSFunction *)p;
|
||||
js_puts (s, "[Function");
|
||||
if (JS_IsString (f->name)) {
|
||||
js_putc (s, ' ');
|
||||
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;
|
||||
if (JS_IsString (b->func_name)) {
|
||||
js_putc (s, ' ');
|
||||
js_print_value (s, b->func_name);
|
||||
}
|
||||
}
|
||||
js_putc (s, ']');
|
||||
} break;
|
||||
case JS_GC_OBJ_TYPE_ARRAY: {
|
||||
JSArray *arr = (JSArray *)p;
|
||||
uint32_t i, count;
|
||||
js_putc (s, '[');
|
||||
count = min_uint32 (arr->len, s->options.max_item_count);
|
||||
for (i = 0; i < count; i++) {
|
||||
if (i > 0) js_puts (s, ", ");
|
||||
if (s->level < s->options.max_depth) {
|
||||
s->level++;
|
||||
js_print_value (s, arr->values[i]);
|
||||
s->level--;
|
||||
} else {
|
||||
js_puts (s, "...");
|
||||
}
|
||||
}
|
||||
if (arr->len > count) {
|
||||
js_printf (s, ", ... %u more", arr->len - count);
|
||||
}
|
||||
js_putc (s, ']');
|
||||
} break;
|
||||
case JS_GC_OBJ_TYPE_RECORD: {
|
||||
JSRecord *rec = (JSRecord *)p;
|
||||
int idx;
|
||||
idx = js_print_stack_index (s, rec);
|
||||
if (idx >= 0) {
|
||||
js_printf (s, "[circular %d]", idx);
|
||||
} else if (s->level < s->options.max_depth) {
|
||||
s->print_stack[s->level++] = rec;
|
||||
js_print_object (s, rec);
|
||||
s->level--;
|
||||
} else {
|
||||
const char *class_name = s->rt->class_array[rec->class_id].class_name;
|
||||
js_putc (s, '[');
|
||||
js_puts (s, class_name ? class_name : "Object");
|
||||
if (s->options.raw_dump) { js_printf (s, " %p", (void *)rec); }
|
||||
js_putc (s, ']');
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
js_printf (s, "[gc_obj_type %d]", obj_type);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (tag) {
|
||||
case JS_TAG_INT:
|
||||
js_printf (s, "%d", JS_VALUE_GET_INT (val));
|
||||
@@ -6961,80 +7192,12 @@ static void js_print_value (JSPrintValueState *s, JSValue val) {
|
||||
print_str:
|
||||
js_puts (s, str);
|
||||
break;
|
||||
case JS_TAG_FLOAT64:
|
||||
case JS_TAG_SHORT_FLOAT:
|
||||
js_print_float64 (s, JS_VALUE_GET_FLOAT64 (val));
|
||||
break;
|
||||
case JS_TAG_STRING:
|
||||
case JS_TAG_STRING_IMM:
|
||||
js_print_string (s, val);
|
||||
break;
|
||||
case JS_TAG_FUNCTION_BYTECODE: {
|
||||
JSFunctionBytecode *b = JS_VALUE_GET_PTR (val);
|
||||
js_puts (s, "[bytecode ");
|
||||
/* func_name is a JSValue string now */
|
||||
if (JS_IsString (b->func_name)) {
|
||||
js_print_value (s, b->func_name);
|
||||
} else {
|
||||
js_puts (s, "<anon>");
|
||||
}
|
||||
js_putc (s, ']');
|
||||
} break;
|
||||
case JS_TAG_FUNCTION: {
|
||||
JSFunction *f = JS_VALUE_GET_FUNCTION (val);
|
||||
js_puts (s, "[Function");
|
||||
if (JS_IsString (f->name)) {
|
||||
js_putc (s, ' ');
|
||||
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;
|
||||
if (JS_IsString (b->func_name)) {
|
||||
js_putc (s, ' ');
|
||||
js_print_value (s, b->func_name);
|
||||
}
|
||||
}
|
||||
js_putc (s, ']');
|
||||
} break;
|
||||
case JS_TAG_OBJECT:
|
||||
/* Check if this is an intrinsic array (mist_hdr type OBJ_ARRAY) */
|
||||
if (JS_VALUE_IS_INTRINSIC_ARRAY (val)) {
|
||||
JSArray *arr = JS_VALUE_GET_ARRAY (val);
|
||||
uint32_t i, count;
|
||||
js_putc (s, '[');
|
||||
count = min_uint32 (arr->len, s->options.max_item_count);
|
||||
for (i = 0; i < count; i++) {
|
||||
if (i > 0) js_puts (s, ", ");
|
||||
if (s->level < s->options.max_depth) {
|
||||
s->level++;
|
||||
js_print_value (s, arr->values[i]);
|
||||
s->level--;
|
||||
} else {
|
||||
js_puts (s, "...");
|
||||
}
|
||||
}
|
||||
if (arr->len > count) {
|
||||
js_printf (s, ", ... %u more", arr->len - count);
|
||||
}
|
||||
js_putc (s, ']');
|
||||
} else {
|
||||
JSRecord *p = JS_VALUE_GET_OBJ (val);
|
||||
int idx;
|
||||
idx = js_print_stack_index (s, p);
|
||||
if (idx >= 0) {
|
||||
js_printf (s, "[circular %d]", idx);
|
||||
} else if (s->level < s->options.max_depth) {
|
||||
s->print_stack[s->level++] = p;
|
||||
js_print_object (s, JS_VALUE_GET_OBJ (val));
|
||||
s->level--;
|
||||
} else {
|
||||
const char *class_name = s->rt->class_array[p->class_id].class_name;
|
||||
js_putc (s, '[');
|
||||
js_puts (s, class_name ? class_name : "Object");
|
||||
if (s->options.raw_dump) { js_printf (s, " %p", (void *)p); }
|
||||
js_putc (s, ']');
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
js_printf (s, "[unknown tag %d]", tag);
|
||||
break;
|
||||
|
||||
@@ -575,20 +575,18 @@ JS_BOOL JS_IsText (JSValue v);
|
||||
|
||||
/* Symbols removed in Mist encoding */
|
||||
|
||||
static inline JS_BOOL
|
||||
JS_IsFunction (JSValue v) {
|
||||
return JS_VALUE_GET_TAG (v) == JS_TAG_FUNCTION;
|
||||
}
|
||||
/* JS_IsFunction - check if value is a callable function.
|
||||
With new tagging, must check gc_obj_type in JSGCObjectHeader. */
|
||||
JS_BOOL JS_IsFunction (JSValue v);
|
||||
|
||||
static inline JS_BOOL
|
||||
JS_IsInteger (JSValue v) {
|
||||
return JS_VALUE_GET_TAG (v) == JS_TAG_INT;
|
||||
}
|
||||
|
||||
static inline JS_BOOL
|
||||
JS_IsObject (JSValue v) {
|
||||
return JS_VALUE_GET_TAG (v) == JS_TAG_OBJECT;
|
||||
}
|
||||
/* JS_IsObject - check if value is a settable object (record or array).
|
||||
With new tagging, must check gc_obj_type in JSGCObjectHeader. */
|
||||
JS_BOOL JS_IsObject (JSValue v);
|
||||
int JS_IsArray (JSContext *ctx, JSValue val);
|
||||
|
||||
// Fundamental
|
||||
|
||||
Reference in New Issue
Block a user