transformation

This commit is contained in:
2026-02-01 07:45:44 -06:00
parent c74bee89a7
commit bb83327a52
2 changed files with 170 additions and 300 deletions

View File

@@ -99,8 +99,6 @@
// #define DUMP_READ_OBJECT
// #define DUMP_ROPE_REBALANCE
// #define RC_TRACE
/* test the GC by forcing it before each object allocation */
// #define FORCE_GC_AT_MALLOC
@@ -208,6 +206,59 @@ static inline const char *JS_KeyGetStr (JSContext *ctx, char *buf,
return buf;
}
JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref)
{
ref->prev = ctx->top_gc_ref;
ctx->top_gc_ref = ref;
ref->val = JS_UNDEFINED;
return &ref->val;
}
JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref)
{
ctx->top_gc_ref = ref->prev;
return ref->val;
}
JSValue *JS_AddGCRef(JSContext *ctx, JSGCRef *ref)
{
ref->prev = ctx->last_gc_ref;
ctx->last_gc_ref = ref;
ref->val = JS_UNDEFINED;
return &ref->val;
}
void JS_DeleteGCRef(JSContext *ctx, JSGCRef *ref)
{
JSGCRef **pref, *ref1;
pref = &ctx->last_gc_ref;
for(;;) {
ref1 = *pref;
if (ref1 == NULL)
abort();
if (ref1 == ref) {
*pref = ref1->prev;
break;
}
pref = &ref1->prev;
}
}
#undef JS_PUSH_VALUE
#undef JS_POP_VALUE
#define JS_PUSH_VALUE(ctx, v) do { \
v ## _ref.prev = ctx->top_gc_ref; \
ctx->top_gc_ref = &v ## _ref; \
v ## _ref.val = v; \
} while (0)
#define JS_POP_VALUE(ctx, v) do { \
v = v ## _ref.val; \
ctx->top_gc_ref = v ## _ref.prev; \
} while (0)
/* JS_Invoke - invoke method on object using JSValue key */
static JSValue JS_Invoke (JSContext *ctx, JSValue this_val, JSValue method,
int argc, JSValue *argv);
@@ -241,27 +292,20 @@ typedef enum JSErrorEnum {
#define JS_STACK_SIZE_MAX 65534
#define JS_STRING_LEN_MAX ((1 << 30) - 1)
/* strings <= this length are not concatenated using ropes. if too
small, the rope memory overhead becomes high. */
#define JS_STRING_ROPE_SHORT_LEN 512
/* specific threshold for initial rope use */
#define JS_STRING_ROPE_SHORT2_LEN 8192
/* rope depth at which we rebalance */
#define JS_STRING_ROPE_MAX_DEPTH 60
#define __exception __attribute__ ((warn_unused_result))
typedef struct JSString JSString;
/* JSRecord is now an alias for JSRecord - defined later after JSRecord */
/* Forward declaration for bytecode freeing */
struct JSFunctionBytecode;
static void free_function_bytecode (JSRuntime *rt,
struct JSFunctionBytecode *b);
#define JS_VALUE_GET_ARRAY(v) ((JSArray*)JS_VALUE_GET_PTR(v))
#define JS_VALUE_GET_OBJ(v) ((JSRecord *)JS_VALUE_GET_PTR (v))
#define JS_VALUE_GET_TEXT(v) ((JSText*)JS_VALUE_GET_PTR(v))
#define JS_VALUE_GET_BLOB(v) ((JSBlob*)JS_VALUE_GET_PTR(v))
#define JS_VALUE_GET_STRING(v) ((JSString *)JS_VALUE_GET_PTR (v))
#define JS_VALUE_GET_STRING_ROPE(v) ((JSStringRope *)JS_VALUE_GET_PTR (v))
typedef enum {
JS_GC_PHASE_NONE,
@@ -273,7 +317,7 @@ typedef enum OPCodeEnum OPCodeEnum;
/* Forward declarations for stone arena */
struct StoneArenaPage;
struct mist_text;
struct JSText;
/* ============================================================
Buddy Allocator for Actor Memory Blocks
@@ -496,56 +540,71 @@ static inline objhdr_t objhdr_make (uint64_t cap56, uint8_t type, bool r,
return h;
}
/* Array:
[hdr(type=0, cap=element_capacity)]
[len]
[elem0..elem(cap-1)]
*/
typedef struct mist_array {
/* Intrinsic array type - tagged as JS_TAG_OBJECT with mist_hdr type OBJ_ARRAY
*/
typedef struct JSArray {
objhdr_t hdr;
word_t len;
JSValue elem[]; // length 'cap' in words
} mist_array;
word_t length; /* current length */
JSValue values[]; /* array of values */
} JSArray;
/* Blob:
[hdr(type=1, cap=bit_capacity)]
[len_bits]
[bitwords...], big-endian bit numbering per word (bit0 is MSB of first data
word)
*/
typedef struct mist_blob {
typedef struct JSBlob {
objhdr_t hdr;
word_t len_bits;
word_t bits[]; // count = (cap + 63)/64
} mist_blob;
/* Text (type=2):
Pretext (mutable): s=0
[hdr(cap=char_capacity, s=0)]
[len_chars]
[packed UTF32 chars...]
Text (immutable): s=1
[hdr(cap=length, s=1)]
[hash (0 => not computed yet)]
[packed UTF32 chars...]
Packed: 2 UTF32 per word, high 32 then low 32.
*/
typedef struct mist_text {
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;
uint8_t bits[];
} JSBlob;
static inline uint32_t mist_string_get (const mist_text *t, int idx) {
typedef struct JSText {
objhdr_t hdr;
word_t length;
word_t packed[]; // two chars per packed
} JSText;
typedef struct slot {
JSValue key;
JSValue value;
} slot;
typedef struct JSRecord {
objhdr_t hdr;
JSRecord *proto;
word_t length;
slot slots[]; // slot[0] is never used
// slot[0].key first 32 bits = class id
// slot[0].key last 32 bits = obj id if used as record
// slot[0].value = opaque c object from class id
} JSRecord;
typedef struct JSFunction {
objdr_t hdr;
JSCode *code;
JSFrame *outer;
} JSFunction;
typedef struct JSFrame {
objhdr_t hdr;
JSFunction *function;
JSFrame *caller;
word_t ret; // return address of the instruction that should be executed
JSValue vars[]; // var[0] is this
} JSFrame;
typedef strut JSCode {
objhdr_t hdr;
word_t arity;
word_t size; // capacity of an activation from that will execute
word_t closure_size; // reduced capacity for return frames
word_t entry_point;
uint8_t *bytecode;
};
static inline uint32_t mist_string_get (const JSText *t, int idx) {
int word_idx = idx / 2;
int shift
= (1 - (idx % 2)) * 32; /* high 32 (idx%2==0) then low 32 (idx%2==1) */
int shift = (1 - (idx % 2)) * 32; /* high 32 (idx%2==0) then low 32 (idx%2==1) */
return (uint32_t)(t->packed[word_idx] >> shift);
}
static inline void mist_string_put (mist_text *t, int idx, uint32_t c) {
static inline void mist_string_put (JSText *t, int idx, uint32_t c) {
int word_idx = idx / 2;
int shift = (1 - (idx % 2)) * 32;
word_t mask = (word_t)0xFFFFFFFF << shift;
@@ -561,11 +620,7 @@ static inline void mist_string_put (mist_text *t, int idx, uint32_t c) {
[key1][val1]...[key_cap][val_cap]
Total words = 2*(cap+1) (matches spec).
*/
typedef struct mist_slot {
word_t key; // typically objref_t to a TEXT object (must be immutable for
// stable hash)
word_t value; // any value word
} mist_slot;
typedef struct mist_record {
objhdr_t hdr; // capacity is length of slots
@@ -667,23 +722,23 @@ static inline uint64_t fash64_hash_one (uint64_t word) {
return fash64_end (&s);
}
static inline uint32_t mist_text_len (const mist_text *text) {
static inline uint32_t JSText_len (const JSText *text) {
if (objhdr_s (text->hdr)) return (uint32_t)objhdr_cap56 (text->hdr);
return (uint32_t)text->length;
}
static inline JS_BOOL mist_text_equal (const mist_text *a,
const mist_text *b) {
uint32_t len_a = mist_text_len (a);
uint32_t len_b = mist_text_len (b);
static inline JS_BOOL JSText_equal (const JSText *a,
const JSText *b) {
uint32_t len_a = JSText_len (a);
uint32_t len_b = JSText_len (b);
if (len_a != len_b) return FALSE;
size_t word_count = (len_a + 1) / 2;
return memcmp (a->packed, b->packed, word_count * sizeof (uint64_t)) == 0;
}
static JS_BOOL mist_text_equal_ascii (const mist_text *text, JSValue imm) {
static JS_BOOL JSText_equal_ascii (const JSText *text, JSValue imm) {
int len = MIST_GetImmediateASCIILen (imm);
if ((uint32_t)len != mist_text_len (text)) return FALSE;
if ((uint32_t)len != JSText_len (text)) return FALSE;
for (int i = 0; i < len; i++) {
uint32_t c = mist_string_get (text, i);
if (c >= 0x80) return FALSE;
@@ -693,10 +748,10 @@ static JS_BOOL mist_text_equal_ascii (const mist_text *text, JSValue imm) {
return TRUE;
}
/* Get hash for a mist_text value.
/* Get hash for a JSText value.
For stoned text (s=1): hash is pre-computed in length field.
For pre-text (s=0): compute hash on the fly. */
static uint64_t get_text_hash (mist_text *text) {
static uint64_t get_text_hash (JSText *text) {
if (objhdr_s (text->hdr)) {
/* Stoned text: hash is stored in length field */
return text->length;
@@ -712,7 +767,7 @@ static uint64_t get_text_hash (mist_text *text) {
uint64_t get_value_hash (JSValue val) {
if (!JS_IsPtr (val)) return 0;
mist_text *text = (mist_text *)JS_VALUE_GET_PTR (val);
JSText *text = (JSText *)JS_VALUE_GET_PTR (val);
if (objhdr_type (text->hdr) != OBJ_TEXT) return 0;
return get_text_hash (text);
@@ -751,7 +806,7 @@ static void pack_utf32_to_words (const uint32_t *utf32, uint32_t len,
}
/* Compare two packed UTF-32 texts for equality */
static int text_equal (mist_text *a, const uint64_t *packed_b,
static int text_equal (JSText *a, const uint64_t *packed_b,
uint32_t len_b) {
uint32_t len_a = (uint32_t)objhdr_cap56 (a->hdr);
if (len_a != len_b) return 0;
@@ -771,113 +826,6 @@ static JSValue intern_text_to_value (JSContext *ctx, const uint32_t *utf32,
static JSValue js_key_new (JSContext *ctx, const char *str);
static JSValue js_key_new_len (JSContext *ctx, const char *str, size_t len);
#ifdef RC_TRACE
/* JS_ATOM_NULL stub for legacy RC_TRACE code */
#define JS_ATOM_NULL 0
typedef struct RcEvent {
JSGCObjectHeader *ptr;
uint32_t tag;
JSGCObjectHeader *parent;
uint32_t parent_type;
int32_t parent_class_id;
uint32_t atom;
int32_t prop_index;
const char *edge;
int32_t ref_before;
int32_t ref_after;
const char *file;
int line;
const char *op;
} RcEvent;
#define RC_LOG_CAP 16384
static RcEvent rc_log[RC_LOG_CAP];
static uint32_t rc_log_i;
static void rc_dump_atom (JSRuntime *rt, uint32_t atom);
static inline void rc_log_event (JSGCObjectHeader *ptr, uint32_t tag,
JSGCObjectHeader *parent,
uint32_t parent_type, int32_t parent_class_id,
const char *edge, uint32_t atom,
int32_t prop_index, int32_t rb, int32_t ra,
const char *file, int line, const char *op) {
RcEvent *e = &rc_log[rc_log_i++ & (RC_LOG_CAP - 1)];
e->ptr = ptr;
e->tag = tag;
e->parent = parent;
e->parent_type = parent_type;
e->parent_class_id = parent_class_id;
e->atom = atom;
e->prop_index = prop_index;
e->edge = edge;
e->ref_before = rb;
e->ref_after = ra;
e->file = file;
e->line = line;
e->op = op;
}
static inline void rc_trace_inc_gc (JSGCObjectHeader *p, const char *file,
int line) {
int32_t rb = p->ref_count;
p->ref_count = rb + 1;
rc_log_event (p, p->gc_obj_type, NULL, 0, -1, NULL, 0, -1, rb, p->ref_count,
file, line, "++");
}
static inline void rc_trace_dec_gc (JSGCObjectHeader *p, const char *file,
int line) {
int32_t rb = p->ref_count;
p->ref_count = rb - 1;
rc_log_event (p, p->gc_obj_type, NULL, 0, -1, NULL, 0, -1, rb, p->ref_count,
file, line, "--");
}
static inline void
rc_trace_dec_gc_edge (JSGCObjectHeader *p, JSGCObjectHeader *parent,
uint32_t parent_type, int32_t parent_class_id,
const char *edge, uint32_t atom, int32_t prop_index,
const char *file, int line) {
int32_t rb = p->ref_count;
p->ref_count = rb - 1;
rc_log_event (p, p->gc_obj_type, parent, parent_type, parent_class_id, edge,
atom, prop_index, rb, p->ref_count, file, line, "--");
}
static void rc_dump_history (JSGCObjectHeader *p, const char *why) {
int count = 0;
fprintf (stderr, "RC_TRACE: %s ptr=%p tag=%u ref=%d\n", why, (void *)p,
(unsigned)p->gc_obj_type, p->ref_count);
for (uint32_t k = 0; k < RC_LOG_CAP && count < 200; k++) {
RcEvent *e = &rc_log[(rc_log_i - 1 - k) & (RC_LOG_CAP - 1)];
if (e->ptr != p) continue;
fprintf (stderr,
"RC %s ptr=%p tag=%u %d->%d edge=%s parent=%p ptype=%u pclass=%d "
"atom=%u idx=%d at %s:%d\n",
e->op, (void *)e->ptr, (unsigned)e->tag, e->ref_before,
e->ref_after, e->edge ? e->edge : "-", (void *)e->parent,
(unsigned)e->parent_type, (int)e->parent_class_id,
(unsigned)e->atom, (int)e->prop_index, e->file, e->line);
count++;
}
}
#define RC_GC_INC(p) rc_trace_inc_gc ((p), __FILE__, __LINE__)
#define RC_GC_DEC(p) rc_trace_dec_gc ((p), __FILE__, __LINE__)
#define RC_GC_DEC_EDGE(p, parent, parent_type, parent_class_id, edge, atom, \
prop_index) \
rc_trace_dec_gc_edge ((p), (parent), (parent_type), (parent_class_id), \
(edge), (atom), (prop_index), __FILE__, __LINE__)
#else
#define RC_GC_INC(p) ((p)->ref_count++)
#define RC_GC_DEC(p) ((p)->ref_count--)
#define RC_GC_DEC_EDGE(p, parent, parent_type, parent_class_id, edge, atom, \
prop_index) \
((p)->ref_count--)
#endif
typedef struct JSVarRef {
union {
JSGCObjectHeader header; /* must come first */
@@ -902,7 +850,6 @@ typedef struct JSVarRef {
#define JS_INTERRUPT_COUNTER_INIT 10000
struct JSContext {
JSGCObjectHeader header; /* must come first */
JSRuntime *rt;
struct list_head link;
@@ -915,6 +862,9 @@ struct JSContext {
uint16_t binary_object_count;
int binary_object_size;
JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */
JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */
JSValue *class_proto;
JSValue function_proto;
@@ -955,7 +905,7 @@ struct JSContext {
/* Stone arena for interned strings (per context) */
struct StoneArenaPage *st_pages;
struct mist_text **st_text_array; /* indexed by ID */
struct JSText **st_text_array; /* indexed by ID */
uint32_t *st_text_hash; /* hash table mapping to IDs */
uint32_t st_text_count; /* number of interned texts */
uint32_t st_text_size; /* hash table size */
@@ -1020,8 +970,8 @@ static int st_text_resize (JSContext *ctx) {
uint32_t *new_hash = js_mallocz_rt (rt, new_size * sizeof (uint32_t));
if (!new_hash) return -1;
mist_text **new_array = js_realloc_rt (
rt, ctx->st_text_array, (new_size + 1) * sizeof (mist_text *));
JSText **new_array = js_realloc_rt (
rt, ctx->st_text_array, (new_size + 1) * sizeof (JSText *));
if (!new_array) {
js_free_rt (rt, new_hash);
return -1;
@@ -1031,7 +981,7 @@ static int st_text_resize (JSContext *ctx) {
for (uint32_t i = 0; i < ctx->st_text_size; i++) {
uint32_t id = ctx->st_text_hash ? ctx->st_text_hash[i] : 0;
if (id != 0) {
mist_text *text = ctx->st_text_array[id];
JSText *text = ctx->st_text_array[id];
uint64_t hash = text->length; /* hash stored in length for stoned text */
uint32_t slot = hash & new_mask;
while (new_hash[slot] != 0)
@@ -1066,7 +1016,7 @@ static JSValue intern_text_to_value (JSContext *ctx, const uint32_t *utf32,
while (ctx->st_text_hash[slot] != 0) {
uint32_t id = ctx->st_text_hash[slot];
mist_text *existing = ctx->st_text_array[id];
JSText *existing = ctx->st_text_array[id];
if (text_equal (existing, packed, len)) {
/* Found existing entry */
return JS_MKPTR (JS_TAG_STRING, existing);
@@ -1084,9 +1034,9 @@ static JSValue intern_text_to_value (JSContext *ctx, const uint32_t *utf32,
slot = (slot + 1) & mask;
}
/* Allocate mist_text in stone arena */
size_t text_size = sizeof (mist_text) + word_count * sizeof (uint64_t);
mist_text *text = st_alloc (ctx, text_size, 8);
/* Allocate JSText in stone arena */
size_t text_size = sizeof (JSText) + word_count * sizeof (uint64_t);
JSText *text = st_alloc (ctx, text_size, 8);
if (!text) return JS_NULL; /* OOM */
/* Initialize the text (with unified layout: hdr at offset 8) */
@@ -1204,15 +1154,8 @@ struct JSString {
uint64_t u[]; /* packed UTF-32 characters */
};
#ifdef RC_TRACE
static void rc_dump_atom (JSRuntime *rt, uint32_t atom) {
/* stubbed for mist_text refactor */
fprintf (stderr, "RC_TRACE: atom=%u (stubbed)\n", atom);
}
#endif
/* JS_IsString implementation: checks for immediate string or OBJ_TEXT pointer.
Both mist_text and JSString now have objhdr_t at offset 8. */
Both JSText 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;
@@ -1257,16 +1200,6 @@ static inline JSGCObjectTypeEnum js_gc_obj_type (JSValue v) {
return ((JSGCObjectHeader *)JS_VALUE_GET_PTR (v))->gc_obj_type;
}
/* Intrinsic array type - tagged as JS_TAG_OBJECT with mist_hdr type OBJ_ARRAY
*/
typedef struct JSArray {
JSGCObjectHeader header; /* must come first */
objhdr_t mist_hdr; /* type=OBJ_ARRAY, capacity in cap56 field, stone bit via objhdr_s() */
uint32_t len; /* current length */
JSValue *values; /* array of values */
uint8_t free_mark : 1; /* only used when freeing arrays with cycles */
} JSArray;
/* Helper to get array capacity from mist_hdr */
static inline uint32_t js_array_cap (JSArray *arr) {
return (uint32_t)objhdr_cap56 (arr->mist_hdr);
@@ -1350,14 +1283,14 @@ static uint64_t js_key_hash (JSValue key) {
void *ptr = JS_VALUE_GET_PTR (key);
/* Both mist_text and JSString now have objhdr_t at offset 8.
/* Both JSText 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) {
/* For mist_text (stoned strings), use get_text_hash */
mist_text *text = (mist_text *)ptr;
/* For JSText (stoned strings), use get_text_hash */
JSText *text = (JSText *)ptr;
return get_text_hash (text);
}
if (type == OBJ_RECORD) {
@@ -1375,22 +1308,22 @@ static JS_BOOL js_key_equal (JSValue a, JSValue b) {
if (MIST_IsImmediateASCII (a)) {
if (MIST_IsImmediateASCII (b)) return FALSE;
if (!JS_IsPtr (b)) return FALSE;
mist_text *tb = (mist_text *)JS_VALUE_GET_PTR (b);
JSText *tb = (JSText *)JS_VALUE_GET_PTR (b);
if (objhdr_type (tb->hdr) != OBJ_TEXT) return FALSE;
return mist_text_equal_ascii (tb, a);
return JSText_equal_ascii (tb, a);
}
if (MIST_IsImmediateASCII (b)) {
if (!JS_IsPtr (a)) return FALSE;
mist_text *ta = (mist_text *)JS_VALUE_GET_PTR (a);
JSText *ta = (JSText *)JS_VALUE_GET_PTR (a);
if (objhdr_type (ta->hdr) != OBJ_TEXT) return FALSE;
return mist_text_equal_ascii (ta, b);
return JSText_equal_ascii (ta, b);
}
if (!JS_IsPtr (a) || !JS_IsPtr (b)) return FALSE;
void *pa = JS_VALUE_GET_PTR (a);
void *pb = JS_VALUE_GET_PTR (b);
/* objhdr_t is at offset 8 for all heap objects (mist_text, JSString, JSRecord) */
/* objhdr_t is at offset 8 for all heap objects (JSText, 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);
@@ -1399,7 +1332,7 @@ static JS_BOOL js_key_equal (JSValue a, JSValue b) {
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 JSText_equal ((JSText *)pa, (JSText *)pb);
return FALSE;
}
@@ -1420,7 +1353,7 @@ static JS_BOOL js_key_equal_str (JSValue a, const char *str) {
}
if (!JS_IsPtr (a)) return FALSE;
mist_text *ta = (mist_text *)JS_VALUE_GET_PTR (a);
JSText *ta = (JSText *)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;
@@ -1940,12 +1873,6 @@ static void free_function (JSRuntime *rt, JSFunction *func);
static void mark_function_children (JSRuntime *rt, JSFunction *func,
JS_MarkFunc *mark_func);
static void mark_function_children_decref (JSRuntime *rt, JSFunction *func);
#ifdef RC_TRACE
static void gc_decref_child_dbg (JSRuntime *rt, JSGCObjectHeader *parent,
const char *edge, JSValue atom,
int prop_index, JSGCObjectHeader *child,
const char *file, int line);
#endif
static void gc_decref_child (JSRuntime *rt, JSGCObjectHeader *p);
@@ -2027,8 +1954,6 @@ static BOOL js_get_fast_array (JSContext *ctx, JSValue obj, JSValue **arrpp,
static JSValue js_c_function_data_call (JSContext *ctx, JSValue func_obj,
JSValue this_val, int argc,
JSValue *argv);
static void add_gc_object (JSRuntime *rt, JSGCObjectHeader *h,
JSGCObjectTypeEnum type);
static void remove_gc_object (JSGCObjectHeader *h);
static void JS_RunGCInternal (JSRuntime *rt);
static JSValue js_regexp_toString (JSContext *ctx, JSValue this_val, int argc,
@@ -3011,7 +2936,6 @@ JSContext *JS_NewContextRaw (JSRuntime *rt) {
if (!ctx) return NULL;
ctx->header.ref_count = 1;
ctx->trace_hook = NULL;
add_gc_object (rt, &ctx->header, JS_GC_OBJ_TYPE_JS_CONTEXT);
ctx->class_proto
= js_malloc_rt (rt, sizeof (ctx->class_proto[0]) * rt->class_count);
@@ -4382,7 +4306,6 @@ static JSValue js_new_function (JSContext *ctx, JSFunctionKind kind) {
func->name = JS_NULL;
func->length = 0;
func->free_mark = 0;
add_gc_object (rt, &func->header, JS_GC_OBJ_TYPE_FUNCTION);
return JS_MKPTR (JS_TAG_FUNCTION, func);
}
@@ -4491,12 +4414,7 @@ static void mark_function_children_decref (JSRuntime *rt, JSFunction *func) {
switch (func->kind) {
case JS_FUNC_KIND_C:
if (func->u.cfunc.realm) {
#ifdef RC_TRACE
gc_decref_child_dbg (rt, &func->header, "func.realm", 0, -1,
&func->u.cfunc.realm->header, __FILE__, __LINE__);
#else
gc_decref_child (rt, &func->u.cfunc.realm->header);
#endif
}
break;
case JS_FUNC_KIND_BYTECODE:
@@ -4506,12 +4424,7 @@ static void mark_function_children_decref (JSRuntime *rt, JSFunction *func) {
for (int i = 0; i < b->closure_var_count; i++) {
JSVarRef *var_ref = func->u.func.var_refs[i];
if (var_ref) {
#ifdef RC_TRACE
gc_decref_child_dbg (rt, &func->header, "func.var_ref", 0, i,
&var_ref->header, __FILE__, __LINE__);
#else
gc_decref_child (rt, &var_ref->header);
#endif
}
}
}
@@ -4597,16 +4510,16 @@ void __JS_FreeValueRT (JSRuntime *rt, JSValue 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. */
Both strings (JSText/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
/* Check if this is a stone-allocated JSText (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) */
/* Stone-allocated JSText - don't free (lives in stone arena) */
return;
}
/* Heap-allocated JSString - free it */
@@ -4729,9 +4642,6 @@ static void mark_children (JSRuntime *rt, JSGCObjectHeader *gp,
}
static void gc_decref_child (JSRuntime *rt, JSGCObjectHeader *p) {
#ifdef RC_TRACE
if (p->ref_count <= 0) { rc_dump_history (p, "gc_decref_child pre-assert"); }
#endif
assert (p->ref_count > 0);
RC_GC_DEC (p);
if (p->ref_count == 0 && p->mark == 1) {
@@ -4747,43 +4657,6 @@ static inline int32_t gc_parent_class_id (JSGCObjectHeader *parent) {
return -1;
}
#ifdef RC_TRACE
static JSGCObjectHeader *rc_edge_parent;
static const char *rc_edge_name;
static void gc_decref_child_dbg (JSRuntime *rt, JSGCObjectHeader *parent,
const char *edge, uint32_t atom,
int32_t prop_index, JSGCObjectHeader *p,
const char *file, int line) {
uint32_t parent_type = parent ? parent->gc_obj_type : 0;
int32_t parent_class_id = gc_parent_class_id (parent);
if (p->ref_count <= 0) {
fprintf (stderr,
"RC_TRACE: gc_decref_child pre-assert ptr=%p ref=%d edge=%s "
"parent=%p ptype=%u pclass=%d atom=%u idx=%d\n",
(void *)p, p->ref_count, edge ? edge : "-", (void *)parent,
(unsigned)parent_type, (int)parent_class_id, (unsigned)atom,
(int)prop_index);
fprintf (stderr, "RC_TRACE: gc_decref_child pre-assert atom=%u idx=%d\n",
(unsigned)atom, (int)prop_index);
if (atom != JS_ATOM_NULL) { rc_dump_atom (rt, atom); }
rc_dump_history (p, "gc_decref_child pre-assert");
}
assert (p->ref_count > 0);
RC_GC_DEC_EDGE (p, parent, parent_type, parent_class_id, edge, atom,
prop_index);
if (p->ref_count == 0 && p->mark == 1) {
list_del (&p->link);
list_add_tail (&p->link, &rt->tmp_obj_list);
}
}
static void gc_decref_child_edge (JSRuntime *rt, JSGCObjectHeader *p) {
gc_decref_child_dbg (rt, rc_edge_parent, rc_edge_name, 0, -1, p, __FILE__,
__LINE__);
}
#endif
static inline void JS_MarkValueEdgeEx (JSRuntime *rt, JSValue val,
JSGCObjectHeader *parent,
const char *edge, uint32_t atom,
@@ -4797,12 +4670,7 @@ static inline void JS_MarkValueEdgeEx (JSRuntime *rt, JSValue val,
/* 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
}
}
@@ -4840,12 +4708,7 @@ static void mark_children_decref (JSRuntime *rt, JSGCObjectHeader *gp) {
JS_MarkValueEdge (rt, b->cpool[i], gp, "bytecode.cpool");
}
if (b->realm) {
#ifdef RC_TRACE
gc_decref_child_dbg (rt, gp, "bytecode.realm", 0, -1, &b->realm->header,
__FILE__, __LINE__);
#else
gc_decref_child (rt, &b->realm->header);
#endif
}
} break;
case JS_GC_OBJ_TYPE_FUNCTION: {
@@ -4876,13 +4739,7 @@ static void mark_children_decref (JSRuntime *rt, JSGCObjectHeader *gp) {
if (rec->class_id != JS_CLASS_OBJECT && rec->class_id < rt->class_count) {
JSClassGCMark *gc_mark = rt->class_array[rec->class_id].gc_mark;
if (gc_mark) {
#ifdef RC_TRACE
rc_edge_parent = gp;
rc_edge_name = "rec.class_gc_mark";
gc_mark (rt, JS_MKPTR (JS_TAG_OBJECT, rec), gc_decref_child_edge);
#else
gc_mark (rt, JS_MKPTR (JS_TAG_OBJECT, rec), gc_decref_child);
#endif
}
}
/* Mark hash table entries */
@@ -4945,9 +4802,6 @@ static void gc_scan (JSRuntime *rt) {
/* keep the objects with a refcount > 0 and their children. */
list_for_each (el, &rt->gc_obj_list) {
p = list_entry (el, JSGCObjectHeader, link);
#ifdef RC_TRACE
if (p->ref_count <= 0) { rc_dump_history (p, "gc_scan pre-assert"); }
#endif
assert (p->ref_count > 0);
p->mark = 0; /* reset the mark for the next GC call */
mark_children (rt, p, gc_scan_incref_child);
@@ -5872,7 +5726,7 @@ static uint32_t js_string_get_length (JSValue 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) */
/* String (JSString or JSText) */
return (uint32_t)objhdr_cap56 (hdr);
}
return 0;
@@ -6556,7 +6410,7 @@ static int JS_ToBoolFree (JSContext *ctx, JSValue 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 */
/* String (JSString or JSText) - truthy if non-empty */
BOOL ret = objhdr_cap56 (hdr) != 0;
JS_FreeValue (ctx, val);
return ret;
@@ -7559,7 +7413,7 @@ static void js_print_value (JSPrintValueState *s, JSValue val) {
uint8_t mist_type = objhdr_type (hdr);
if (mist_type == OBJ_TEXT) {
/* String (JSString or mist_text) */
/* String (JSString or JSText) */
js_print_string (s, val);
return;
}

View File

@@ -64,13 +64,29 @@ typedef struct JSContext JSContext; // Each actor - has its own GC
typedef struct JSClass JSClass;
typedef uint32_t JSClassID;
typedef struct JSGCRef {
JSValue val;
struct JSGCRef *prev;
} JSGCRef;
/* stack of JSGCRef */
JSValue *JS_PushGCRef(JSContext *ctx, JSGCRef *ref);
JSValue JS_PopGCRef(JSContext *ctx, JSGCRef *ref);
#define JS_PUSH_VALUE(ctx, v) do { JS_PushGCRef(ctx, &v ## _ref); v ## _ref.val = v; } while (0)
#define JS_POP_VALUE(ctx, v) v = JS_PopGCRef(ctx, &v ## _ref)
/* list of JSGCRef (they can be removed in any order, slower) */
JSValue *JS_AddGCRef(JSContext *ctx, JSGCRef *ref);
void JS_DeleteGCRef(JSContext *ctx, JSGCRef *ref);
/* ============================================================
Mist Value Encoding (LSB-based type discrimination)
============================================================
64-bit builds:
LSB = 0 → 31-bit signed integer (value >> 1)
LSB = 01 → 61-bit pointer (aligned, clear low 2 bits)
LSB = 001 → 61-bit pointer (aligned, clear low 3 bits)
LSB = 101 → Short float (61-bit, 3 fewer exponent bits than double)
LSB = 11 → Special tag (next 3 bits = subtype, 5 bits total)