mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-31 07:23:06 +08:00
Skip NULL children and key-cache walks when disposing dynamic values
Disposing a checked-dynamic node released each entry's getter and setter and the node's prototype and symbol tables through scr_dyn_release, though almost all of them are NULL: in a zod validation loop, over half of all dyn releases. Dispose now tests each child first. A pooled object also walked all its slots again on reuse, to take the key bytes it kept back from the pool's key budget. It now records that total when it is pooled, and reuse subtracts it.
This commit is contained in:
@@ -480,7 +480,12 @@ struct ScrDyn {
|
||||
* exits, and those clear it before release; reuse of a freed object
|
||||
* resets it (scr_dyn_alloc). */
|
||||
const char *class_name;
|
||||
struct ScrDynRegion *region; /* see ScrDynRegion */
|
||||
union {
|
||||
struct ScrDynRegion *region; /* see ScrDynRegion */
|
||||
/* While the node is pooled: the key bytes it keeps for reuse,
|
||||
* counted in the pool's key budget (scr_dyn_obj_drop_keys). */
|
||||
size_t cached_key_bytes;
|
||||
};
|
||||
/* The dynamic tier's hidden class (scr_js_object.c): the transition
|
||||
* tree node naming this object's key sequence, or NULL while it has
|
||||
* none. Only programs with dynamic-tier code read or maintain it:
|
||||
|
||||
@@ -846,15 +846,21 @@ static SCR_TL size_t scr_dyn_cached_key_bytes;
|
||||
#define SCR_DYN_KEY_CACHE_LENGTH 64
|
||||
#endif
|
||||
|
||||
static void scr_dyn_obj_drop_keys(ScrDyn *d, bool cache) {
|
||||
/* Returns the key bytes it kept for reuse, which it added to the budget;
|
||||
* the pooled node records them (v.obj.cached_key_bytes) so its reuse takes
|
||||
* them back without walking its slots. */
|
||||
static size_t scr_dyn_obj_drop_keys(ScrDyn *d, bool cache) {
|
||||
size_t kept = 0;
|
||||
for (size_t i = 0; i < d->v.obj.cap; i++) {
|
||||
ScrDynEntry *entry = &d->v.obj.entries[i];
|
||||
if (!entry->key) continue;
|
||||
#ifndef SCR_RC_AUDIT
|
||||
size_t bytes = sizeof(ScrDynKey) + entry->key_len + 1;
|
||||
if (cache && d->v.obj.cap <= SCR_DYN_KEY_CACHE_SLOTS &&
|
||||
entry->key_len <= SCR_DYN_KEY_CACHE_LENGTH &&
|
||||
sizeof(ScrDynKey) + entry->key_len + 1 <= SCR_DYN_KEY_CACHE_MAX - scr_dyn_cached_key_bytes) {
|
||||
scr_dyn_cached_key_bytes += sizeof(ScrDynKey) + entry->key_len + 1;
|
||||
bytes <= SCR_DYN_KEY_CACHE_MAX - scr_dyn_cached_key_bytes) {
|
||||
scr_dyn_cached_key_bytes += bytes;
|
||||
kept += bytes;
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
@@ -864,6 +870,7 @@ static void scr_dyn_obj_drop_keys(ScrDyn *d, bool cache) {
|
||||
entry->key = NULL;
|
||||
entry->key_len = 0;
|
||||
}
|
||||
return kept;
|
||||
}
|
||||
|
||||
/* Primitive kinds cannot acquire properties or change kind while live, so
|
||||
@@ -1054,15 +1061,11 @@ static ScrDyn *scr_dyn_alloc(ScrDynKind kind) {
|
||||
d->v.arr.region = NULL;
|
||||
} else if (kind == SCR_DYN_OBJ) {
|
||||
d->v.obj.len = 0; /* cap/entries preserved */
|
||||
/* The kept keys leave the pool's budget with the node. */
|
||||
scr_dyn_cached_key_bytes -= d->v.obj.cached_key_bytes;
|
||||
d->v.obj.region = NULL;
|
||||
d->v.obj.class_name = NULL; /* a released class prototype's name */
|
||||
SCR_JS_SHAPE_RESET(d);
|
||||
if (d->v.obj.cap <= SCR_DYN_KEY_CACHE_SLOTS) {
|
||||
for (size_t i = 0; i < d->v.obj.cap; i++) {
|
||||
ScrDynEntry *entry = &d->v.obj.entries[i];
|
||||
if (entry->key) scr_dyn_cached_key_bytes -= sizeof(ScrDynKey) + entry->key_len + 1;
|
||||
}
|
||||
}
|
||||
d->v.obj.source_identity = NULL;
|
||||
d->v.obj.source_access = NULL;
|
||||
} else if (kind != SCR_DYN_NUM && kind != SCR_DYN_STR) {
|
||||
@@ -1128,6 +1131,12 @@ static inline void scr_dyn_weak_dispose(ScrDyn *d) {
|
||||
if (scr_weak_observing) scr_weak_disposed(d);
|
||||
}
|
||||
|
||||
/* A disposed node's child: most getters, setters, prototypes and symbol
|
||||
* tables are NULL, and the test costs less than the call. */
|
||||
static inline void scr_dyn_release_child(ScrDyn *child) {
|
||||
if (child) scr_dyn_release(child);
|
||||
}
|
||||
|
||||
static void scr_dyn_dispose(ScrDyn *d, bool collected) {
|
||||
#ifndef SCR_RC_AUDIT
|
||||
/* A capsule with nothing but its referent and adapters: the common end
|
||||
@@ -1149,9 +1158,9 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) {
|
||||
d->v.obj.index = NULL;
|
||||
}
|
||||
if (d->kind == SCR_DYN_ARR || d->kind == SCR_DYN_OBJ) scr_dyn_region_leave(d);
|
||||
if (!collected) scr_dyn_release(d->prototype);
|
||||
if (!collected) scr_dyn_release(d->symbol_properties);
|
||||
if (!collected) scr_dyn_release(d->symbol_keys);
|
||||
if (!collected) scr_dyn_release_child(d->prototype);
|
||||
if (!collected) scr_dyn_release_child(d->symbol_properties);
|
||||
if (!collected) scr_dyn_release_child(d->symbol_keys);
|
||||
/* Pooled nodes keep all three NULL (scr_dyn_leaf_alloc relies on it). */
|
||||
d->prototype = d->symbol_properties = d->symbol_keys = NULL;
|
||||
switch (d->kind) {
|
||||
@@ -1171,17 +1180,17 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) {
|
||||
free(d->v.arr.presence);
|
||||
d->v.arr.presence = NULL;
|
||||
if (!collected) {
|
||||
for (size_t i = 0; i < d->v.arr.len; i++) scr_dyn_release(d->v.arr.items[i]);
|
||||
scr_dyn_release(d->v.arr.properties);
|
||||
for (size_t i = 0; i < d->v.arr.len; i++) scr_dyn_release_child(d->v.arr.items[i]);
|
||||
scr_dyn_release_child(d->v.arr.properties);
|
||||
}
|
||||
break;
|
||||
case SCR_DYN_OBJ:
|
||||
for (size_t i = 0; i < d->v.obj.len; i++) {
|
||||
ScrDyn *value = d->v.obj.entries[i].value;
|
||||
if (!collected || !d->v.obj.entries[i].value_traced) scr_dyn_release(value);
|
||||
if (!collected || !d->v.obj.entries[i].value_traced) scr_dyn_release_child(value);
|
||||
if (!collected) {
|
||||
scr_dyn_release(d->v.obj.entries[i].getter);
|
||||
scr_dyn_release(d->v.obj.entries[i].setter);
|
||||
scr_dyn_release_child(d->v.obj.entries[i].getter);
|
||||
scr_dyn_release_child(d->v.obj.entries[i].setter);
|
||||
}
|
||||
d->v.obj.entries[i].value = NULL;
|
||||
d->v.obj.entries[i].getter = NULL;
|
||||
@@ -1207,7 +1216,7 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) {
|
||||
|
||||
case SCR_DYN_TYPED_REF:
|
||||
scr_typed_ref_forget(d);
|
||||
if (!collected) scr_dyn_release(d->v.typed_ref.materialized);
|
||||
if (!collected) scr_dyn_release_child(d->v.typed_ref.materialized);
|
||||
while (d->v.typed_ref.casts) {
|
||||
ScrDynTypedCast *cast = d->v.typed_ref.casts;
|
||||
d->v.typed_ref.casts = cast->next;
|
||||
@@ -1218,8 +1227,8 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) {
|
||||
break;
|
||||
case SCR_DYN_PROXY:
|
||||
if (!collected) {
|
||||
scr_dyn_release(d->v.proxy.target);
|
||||
scr_dyn_release(d->v.proxy.handler);
|
||||
scr_dyn_release_child(d->v.proxy.target);
|
||||
scr_dyn_release_child(d->v.proxy.handler);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -1230,7 +1239,7 @@ static void scr_dyn_dispose(ScrDyn *d, bool collected) {
|
||||
#endif
|
||||
#ifndef SCR_RC_AUDIT
|
||||
if (!collected && scr_dyn_free_count < SCR_DYN_FREE_MAX) {
|
||||
if (d->kind == SCR_DYN_OBJ) scr_dyn_obj_drop_keys(d, true);
|
||||
if (d->kind == SCR_DYN_OBJ) d->v.obj.cached_key_bytes = scr_dyn_obj_drop_keys(d, true);
|
||||
/* Pooled blocks retain their headers and count toward the bounded
|
||||
* allocated heap, but never remain in a candidate buffer. */
|
||||
scr_dyn_weak_dispose(d);
|
||||
|
||||
Reference in New Issue
Block a user