Store ordinary objects as a layout plus value slots (#2353)

This commit is contained in:
Chris Tate
2026-10-26 16:18:38 -05:00
committed by GitHub
35 changed files with 1375 additions and 966 deletions
+14 -21
View File
@@ -32,10 +32,9 @@ import { InternalCompilerError } from "../../errors.js";
* are selected from the target C ABI.
* v.num double | v.b i8 | v.str/v.bytes ptr at +16;
* v.arr { len +16, cap +24, items +32 };
* v.obj { len +16, cap +24, entries +32 };
* v.obj { len +16 } (own properties; read them through
* scr_dyn_member_*, which know both object modes);
* v.fn { clo +16, thunk +24, sig +32, name +40, arity +48 }.
* ScrDynEntry { key, key_len, value, getter, setter, accessor, writable,
* enumerable, configurable } — 48 bytes on 64-bit, 24 on 32-bit.
* ScrDynKind: NULL=0 BOOL=1 NUM=2 STR=3 ARR=4 OBJ=5 UNDEF=6 BYTES=7
* FUNC=8 HANDLE=9.
* ScrBytes { rc +0; len +8; elem +16; data +24 }.
@@ -849,27 +848,23 @@ export class LlDyn {
return e;
}
/** Object entry field addresses: entries + i*sizeof(ScrDynEntry), with
* key, key_len, and value at offsets 0, 8, and 16 on 64-bit targets. */
/** Own property i of object `d` (borrowed key bytes, key length and
* value), read through the runtime's accessors: the object may be in
* layout or dictionary mode (scr_dyn.h). */
private entryAt(
B: BlockBuilder,
entries: string,
d: string,
i: string,
): { key: string; keyLen: string; value: string } {
const off = B.tmp();
const base = B.tmp();
B.line(`${off} = mul ${this.S} ${i}, ${this.abiOffset(48, 24)} ; sizeof(ScrDynEntry)`);
B.line(`${base} = getelementptr inbounds i8, ptr ${entries}, ${this.S} ${off}`);
this.host.declare(`declare ptr @scr_dyn_member_key(ptr, ${this.S})`);
this.host.declare(`declare ${this.S} @scr_dyn_member_key_len(ptr, ${this.S})`);
this.host.declare(`declare ptr @scr_dyn_member_value(ptr, ${this.S})`);
const key = B.tmp();
B.line(`${key} = load ptr, ptr ${base}`);
const klp = B.tmp();
const keyLen = B.tmp();
B.line(`${klp} = getelementptr inbounds i8, ptr ${base}, i64 ${this.abiOffset(8, 4)}`);
B.line(`${keyLen} = load ${this.S}, ptr ${klp}`);
const vp = B.tmp();
const value = B.tmp();
B.line(`${vp} = getelementptr inbounds i8, ptr ${base}, i64 ${this.abiOffset(16, 8)}`);
B.line(`${value} = load ptr, ptr ${vp}`);
B.line(`${key} = call ptr @scr_dyn_member_key(ptr ${d}, ${this.S} ${i})`);
B.line(`${keyLen} = call ${this.S} @scr_dyn_member_key_len(ptr ${d}, ${this.S} ${i})`);
B.line(`${value} = call ptr @scr_dyn_member_value(ptr ${d}, ${this.S} ${i})`);
return { key, keyLen, value };
}
@@ -1441,9 +1436,8 @@ export class LlDyn {
if (shape.indexValue && shape.indexValue.kind !== "dyn") {
this.host.declare(`declare i32 @memcmp(ptr, ptr, ${this.S})`);
const n = this.lenOf(B, "%d");
const entries = this.itemsOf(B, "%d");
this.i64Loop(B, "dm.ov", n, (i, brNext) => {
const ent = this.entryAt(B, entries, i);
const ent = this.entryAt(B, "%d", i);
// Skip declared keys.
for (const f of shape.fields) {
const klen = Buffer.byteLength(f.name, "utf8");
@@ -2777,9 +2771,8 @@ export class LlDyn {
B.line(`store i8 ${np}, ptr ${flagsp}`);
}
const n = this.lenOf(B, "%d");
const entries = this.itemsOf(B, "%d");
this.i64Loop(B, "dcv", n, (i, brNext) => {
const ent = this.entryAt(B, entries, i);
const ent = this.entryAt(B, "%d", i);
for (const f of shape.fields) {
const klen = Buffer.byteLength(f.name, "utf8");
const lenEq = B.tmp();
+25 -58
View File
@@ -4,13 +4,12 @@
* A property site in a dynamic-tier function owns one zeroed thread-local
* ScrJsIc record (`@sc_js_ic_N`). The code at the site is fixed:
*
* load ordinary object whose shape is entry 0's own-property shape:
* read entry `slot` inline unless it is an accessor; otherwise
* scr_js_get_probe (polymorphic, prototype hits, absent keys,
* misses)
* store ordinary object whose shape is entry 0's replace shape, outside
* an intact parse region, writable data entry: replace inline;
* otherwise scr_js_store_probe (adds, polymorphic, misses)
* load ordinary object whose layout is entry 0's own-property layout:
* read slot `slot` inline; otherwise scr_js_get_probe
* (polymorphic, prototype hits, absent keys, misses)
* store ordinary object whose layout is entry 0's replace layout,
* outside an intact parse region: replace inline; otherwise
* scr_js_store_probe (adds, polymorphic, misses)
* keyed dense array element at an integral number key: inline;
* otherwise scr_js_keyed_probe (string keys of ordinary objects)
* method dynInvoke's method read: scr_js_load_probe
@@ -22,12 +21,13 @@
* defines the operation's semantics; nothing is ever patched.
*
* Layout facts (64-bit only; 32-bit targets keep the helper calls):
* ScrDyn kind +8; v.obj {len +16, entries +32, region +72,
* shape +80}; v.arr {len +16, items +32, presence +56};
* v.num +16
* ScrDynEntry 48 bytes: value +16, accessor bit 0 of +40, writable +41
* ScrJsIc entries of 40 bytes {shape, target, holder, holder_shape,
* slot}; 168 bytes in all */
* ScrDyn kind +8; v.obj {len +16, slots +32, region +72,
* layout +80}; v.arr {len +16, items +32, presence +56};
* v.num +16. Every property of an object with a layout is
* a writable data property (scr_dyn.h's layout mode);
* dictionary-mode objects have a null layout.
* ScrJsIc entries of 40 bytes {layout, target, holder,
* holder_layout, slot}; 168 bytes in all */
import { BOOL, DYN, F64, STRING, type IrExpr, type IrFunction } from "../../ir/ir.js";
import { jsIcMethodSites, type JsIcSite } from "../../ir/js-ic.js";
import { mangleBorrowedFunction } from "../mangle.js";
@@ -42,8 +42,6 @@ type Host = LlvmEmitterContext;
/** Words of one ScrJsIc record. */
export const JS_IC_WORDS = 21;
const ENTRY_STRIDE = 48;
/** The cache site a call is, when it is emitted in a dynamic-tier function
* of a 64-bit target. */
export function jsIcSiteOf(host: Host, e: ExprOf<"call">): JsIcSite | null {
@@ -88,32 +86,17 @@ function kindIs(host: Host, d: string, kind: number, yes: string, no: string): v
* the prototype hit made method-heavy code 3-6% faster for 15-90 bytes.
* So a site tests entry 0, own or prototype, and its probe does the rest.
* (0 makes every access probe: with a runtime built with SCR_JS_VERIFY,
* that checks every cached shape against its object.) */
* that probes every access.) */
const INLINE_WAYS: number = 1;
const INLINE_PROTO = true;
/** The address of entry `slot` of object `d`'s entries. */
function entryAt(host: Host, d: string, slot: string): string {
/** The address of slot `slot` of layout-mode object `d`. */
function slotAt(host: Host, d: string, slot: string): string {
const B = host.B;
const entries = load(host, "ptr", d, 32);
const offset = B.tmp();
const entry = B.tmp();
B.line(`${offset} = mul i64 ${slot}, ${ENTRY_STRIDE}`);
B.line(`${entry} = getelementptr inbounds i8, ptr ${entries}, i64 ${offset}`);
return entry;
}
/** Branches to `miss` when the entry at `entry` is an accessor. */
function dataEntry(host: Host, entry: string, miss: string): void {
const B = host.B;
const flags = load(host, "i8", entry, 40);
const accessor = B.tmp();
const isAccessor = B.tmp();
B.line(`${accessor} = and i8 ${flags}, 1`);
B.line(`${isAccessor} = icmp ne i8 ${accessor}, 0`);
const data = B.newLabel("ic.data");
B.condBr(isAccessor, miss, data);
B.startBlock(data);
const slots = load(host, "ptr", d, 32);
const address = B.tmp();
B.line(`${address} = getelementptr inbounds ptr, ptr ${slots}, i64 ${slot}`);
return address;
}
/** The inline part of a load site, entered for an ordinary object `d`:
@@ -137,7 +120,7 @@ function inlineLoad(
B.br(miss);
return;
}
const shape = load(host, "ptr", d, 80);
const shape = load(host, "ptr", d, 80); // v.obj.layout
const present = B.tmp();
B.line(`${present} = icmp ne ptr ${shape}, null`);
let next = B.newLabel("ic.w");
@@ -160,9 +143,7 @@ function inlineLoad(
B.condBr(own, ownLabel, protoLabel);
const hit = (holder: string): void => {
const slot = load(host, "i64", ic, base + 32);
const entry = entryAt(host, holder, slot);
dataEntry(host, entry, miss);
const value = load(host, "ptr", entry, 16);
const value = load(host, "ptr", slotAt(host, holder, slot), 0);
const out = result(value);
incoming.push(`[ ${out}, %${B.currentLabel()} ]`);
B.br(join);
@@ -194,7 +175,7 @@ function inlineLoad(
/** The inline part of a store site that replaces an own data property,
* entered for an ordinary object `d` outside an intact parse region:
* tests the first `ways` cache entries; a hit calls `store` with the
* address of the entry's value word, which must end its block by
* address of the property's slot, which must end its block by
* branching to the site's join. */
function inlineReplace(
host: Host,
@@ -209,7 +190,7 @@ function inlineReplace(
B.br(miss);
return;
}
const shape = load(host, "ptr", d, 80);
const shape = load(host, "ptr", d, 80); // v.obj.layout
const present = B.tmp();
B.line(`${present} = icmp ne ptr ${shape}, null`);
let next = B.newLabel("ic.w");
@@ -230,21 +211,7 @@ function inlineReplace(
B.condBr(ok, matched, next);
B.startBlock(matched);
const slot = load(host, "i64", ic, base + 32);
const entry = entryAt(host, d, slot);
const flags = load(host, "i8", entry, 40);
const writable = load(host, "i8", entry, 41);
const accessor = B.tmp();
const data = B.tmp();
const canWrite = B.tmp();
const writes = B.tmp();
B.line(`${accessor} = and i8 ${flags}, 1`);
B.line(`${data} = icmp eq i8 ${accessor}, 0`);
B.line(`${canWrite} = icmp ne i8 ${writable}, 0`);
B.line(`${writes} = and i1 ${data}, ${canWrite}`);
const hit = B.newLabel("ic.st");
B.condBr(writes, hit, miss);
B.startBlock(hit);
store(gep(host, entry, 16));
store(slotAt(host, d, slot));
}
}
@@ -384,7 +384,7 @@ const RUNTIME_DEFINES: readonly {
when: (opts: CcOptions) => boolean | undefined;
}[] = [
{ define: "SCR_TEXT_DECODER_LEGACY", when: (opts) => opts.textDecoderLegacy },
// Checked objects carry the dynamic tier's shapes (scr_json.c's SCR_JS_TIER build); only
// scr_json.c's SCR_JS_TIER build exports the dynamic tier's cache primitives; only
// programs with dynamic-tier code select it.
{ define: "SCR_JS_TIER", when: (opts) => opts.dynTier === true },
{ define: "SCR_WARNING_REPLACE", when: (opts) => opts.warningReplace },
+1 -1
View File
@@ -1369,7 +1369,7 @@ function scanRuntimeFeatures(
for (const t of nodes.types) type(t);
} else if (keepGoing()) everyModuleNode(mod, { expr, stmt: keepGoing, type });
for (const { name, scan } of scans) if (scan.result()) features[name] = true;
// Inline-cache sites of dynamic-tier functions use the tier's shapes
// Inline-cache sites of dynamic-tier functions use the tier's caches
// (scr_js_object.c, with the SCR_JS_TIER build of scr_json.c).
if (!features.dynTier && mod.functions.some((fn) => fn.tier === "dynamic")) {
const sites = new JsIcSites(mod);
@@ -187,9 +187,9 @@ export const OPTIONAL_RUNTIME_SOURCES = [
export function variantsFor(source) {
const variants = [{ id: "default", when: {}, defines: [] }];
// Programs with dynamic-tier code keep the tier's hidden classes current
// in checked objects (two-tier design S4); every other program links the
// ordinary object, byte for byte.
// Programs with dynamic-tier code link the build that exports the
// primitives the tier's inline caches use (two-tier design S4); every
// other program links the ordinary object, byte for byte.
if (source === "scr_json.c") {
variants.push({ id: "dyn-tier", when: { dynTier: true }, defines: ["SCR_JS_TIER"] });
}
+22 -20
View File
@@ -5982,8 +5982,8 @@ void scr_arr_copy_metadata(ScrArr *a, ScrDyn *target) {
target->v.arr.properties->v.obj.class_name = cls->name;
ScrDyn *fields = scr_arr_class_fields(a);
if (fields) {
for (size_t i = 0; i < fields->v.obj.len; i++)
scr_dyn_copy_entry(target, fields, &fields->v.obj.entries[i]);
for (size_t i = 0; i < scr_dyn_obj_count(fields); i++)
scr_dyn_copy_prop_at(target, fields, i);
scr_dyn_release(fields);
}
return;
@@ -5994,7 +5994,7 @@ void scr_arr_copy_metadata(ScrArr *a, ScrDyn *target) {
return;
metadata = SCR_AX(a)->metadata;
if (!metadata) return;
for (size_t i = 0; i < metadata->v.obj.len; i++) scr_dyn_copy_entry(target, metadata, &metadata->v.obj.entries[i]);
for (size_t i = 0; i < scr_dyn_obj_count(metadata); i++) scr_dyn_copy_prop_at(target, metadata, i);
}
ScrArr *scr_arr_named_keys(ScrArr *a) {
@@ -6008,9 +6008,9 @@ ScrArr *scr_arr_named_keys(ScrArr *a) {
scr_arr_push_ref(keys, scr_str_new(x->props[i].key, x->props[i].key_len));
ScrDyn *fields = scr_arr_class_fields(a);
if (fields) {
for (size_t i = 0; i < fields->v.obj.len; i++) {
const ScrDynEntry *entry = &fields->v.obj.entries[i];
if (entry->enumerable) scr_arr_push_ref(keys, scr_str_new(entry->key, entry->key_len));
for (size_t i = 0; i < scr_dyn_obj_count(fields); i++) {
ScrDynProp entry = scr_dyn_obj_prop_at(fields, i);
if (entry.enumerable) scr_arr_push_ref(keys, scr_str_new(entry.key, entry.key_len));
}
scr_dyn_release(fields);
return keys;
@@ -6022,10 +6022,10 @@ ScrArr *scr_arr_named_keys(ScrArr *a) {
return keys;
}
if (!metadata || metadata->kind != SCR_DYN_OBJ) return keys;
for (size_t i = 0; i < metadata->v.obj.len; i++) {
const ScrDynEntry *entry = &metadata->v.obj.entries[i];
if (!entry->enumerable || scr_arr_prop_index(a, entry->key, entry->key_len) != SIZE_MAX) continue;
scr_arr_push_ref(keys, scr_str_new(entry->key, entry->key_len));
for (size_t i = 0; i < scr_dyn_obj_count(metadata); i++) {
ScrDynProp entry = scr_dyn_obj_prop_at(metadata, i);
if (!entry.enumerable || scr_arr_prop_index(a, entry.key, entry.key_len) != SIZE_MAX) continue;
scr_arr_push_ref(keys, scr_str_new(entry.key, entry.key_len));
}
return keys;
}
@@ -6038,9 +6038,9 @@ bool scr_arr_has_named_key(ScrArr *a, ScrStr *key) {
if (metadata && metadata->kind == SCR_DYN_HANDLE && metadata->v.handle.tag == SCR_DYNH_MATCH_SOURCE)
return true; /* index, input and groups, the only keys a pending result lists */
if (!metadata || metadata->kind != SCR_DYN_OBJ) return false;
for (size_t i = 0; i < metadata->v.obj.len; i++) {
const ScrDynEntry *entry = &metadata->v.obj.entries[i];
if (entry->key_len == key->len && memcmp(entry->key, key->data, key->len) == 0) return true;
for (size_t i = 0; i < scr_dyn_obj_count(metadata); i++) {
ScrDynProp entry = scr_dyn_obj_prop_at(metadata, i);
if (entry.key_len == key->len && memcmp(entry.key, key->data, key->len) == 0) return true;
}
return false;
}
@@ -6067,20 +6067,22 @@ void scr_arr_adopt_view_properties(ScrArr *a, ScrDyn *view, ScrArr *target) {
from->prop_cap = cap;
}
ScrDyn *named = view && view->kind == SCR_DYN_ARR ? view->v.arr.properties : NULL;
if (!named || named->v.obj.len == 0) return;
if (!named || scr_dyn_obj_count(named) == 0) return;
/* The view's numeric properties are copies of the native ones (every
* write to them goes to the native array), so only the others move. */
bool numeric = false;
for (size_t i = 0; i < named->v.obj.len && !numeric; i++) {
for (size_t i = 0; i < scr_dyn_obj_count(named) && !numeric; i++) {
double number;
numeric = scr_arr_numeric_key(named->v.obj.entries[i].key, named->v.obj.entries[i].key_len, &number);
size_t key_len;
const char *key = scr_dyn_obj_key_at(named, i, &key_len);
numeric = scr_arr_numeric_key(key, key_len, &number);
}
if (numeric) {
ScrDyn *rest = scr_dyn_new_obj();
for (size_t i = 0; i < named->v.obj.len; i++) {
const ScrDynEntry *entry = &named->v.obj.entries[i];
for (size_t i = 0; i < scr_dyn_obj_count(named); i++) {
ScrDynProp entry = scr_dyn_obj_prop_at(named, i);
double number;
if (!scr_arr_numeric_key(entry->key, entry->key_len, &number)) scr_dyn_copy_entry(rest, named, entry);
if (!scr_arr_numeric_key(entry.key, entry.key_len, &number)) scr_dyn_copy_prop_at(rest, named, i);
}
named = rest;
} else {
@@ -6088,7 +6090,7 @@ void scr_arr_adopt_view_properties(ScrArr *a, ScrDyn *view, ScrArr *target) {
}
ScrArrExt *x = scr_arr_ext(a);
ScrDyn *old = x->metadata;
x->metadata = named->v.obj.len ? named : NULL;
x->metadata = scr_dyn_obj_count(named) ? named : NULL;
if (!x->metadata) scr_dyn_release(named);
scr_dyn_release(old);
}
+55 -56
View File
@@ -718,7 +718,7 @@ static bool scr_assert_dyn_deep_eq(const ScrDyn *a, const ScrDyn *b) {
if (a->v.arr.properties || b->v.arr.properties) {
if (!a->v.arr.properties || !b->v.arr.properties) {
const ScrDyn *table = a->v.arr.properties ? a->v.arr.properties : b->v.arr.properties;
if (table->v.obj.len != 0) return false;
if (scr_dyn_obj_count(table) != 0) return false;
} else if (!scr_assert_dyn_deep_eq(a->v.arr.properties, b->v.arr.properties)) return false;
}
return true;
@@ -738,11 +738,11 @@ static bool scr_assert_dyn_deep_eq(const ScrDyn *a, const ScrDyn *b) {
} else if (a->null_proto != b->null_proto || a->prototype != b->prototype) {
return false;
}
if (a->v.obj.len != b->v.obj.len) return false;
for (size_t i = 0; i < a->v.obj.len; i++) {
const ScrDynEntry *ent = &a->v.obj.entries[i];
ScrDyn *bv = scr_dyn_obj_get(b, ent->key, ent->key_len);
if (!bv || !scr_assert_dyn_deep_eq(ent->value, bv)) return false;
if (scr_dyn_obj_count(a) != scr_dyn_obj_count(b)) return false;
for (size_t i = 0; i < scr_dyn_obj_count(a); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(a, i);
ScrDyn *bv = scr_dyn_obj_get(b, ent.key, ent.key_len);
if (!bv || !scr_assert_dyn_deep_eq(ent.value, bv)) return false;
}
return true;
}
@@ -782,30 +782,29 @@ static bool scr_assert_loose_nan(const ScrDyn *d) {
return d->kind == SCR_DYN_NUM && d->v.num != d->v.num;
}
/* An enumerable own entry, or NULL. Borrowed. */
static const ScrDynEntry *scr_assert_loose_entry(const ScrDyn *d, const char *key, size_t len) {
for (size_t i = 0; i < d->v.obj.len; i++) {
const ScrDynEntry *ent = &d->v.obj.entries[i];
if (ent->key_len == len && memcmp(ent->key, key, len) == 0) return ent->enumerable ? ent : NULL;
}
return NULL;
/* The value of an enumerable own property, or NULL. Borrowed. */
static const ScrDyn *scr_assert_loose_entry(const ScrDyn *d, const char *key, size_t len) {
size_t i = scr_dyn_obj_index_of(d, key, len);
if (i == SIZE_MAX) return NULL;
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
return ent.enumerable ? ent.value : NULL;
}
static bool scr_assert_loose_counted(const ScrDynEntry *ent) {
return ent->enumerable && !(ent->key_len == 6 && memcmp(ent->key, "%error", 6) == 0);
static bool scr_assert_loose_counted(ScrDynProp ent) {
return ent.enumerable && !(ent.key_len == 6 && memcmp(ent.key, "%error", 6) == 0);
}
/* keyCheck and objEquiv over two objects' enumerable own keys. */
static bool scr_assert_loose_keys(const ScrDyn *a, const ScrDyn *b) {
size_t na = 0, nb = 0;
for (size_t i = 0; i < a->v.obj.len; i++) na += scr_assert_loose_counted(&a->v.obj.entries[i]);
for (size_t i = 0; i < b->v.obj.len; i++) nb += scr_assert_loose_counted(&b->v.obj.entries[i]);
for (size_t i = 0; i < scr_dyn_obj_count(a); i++) na += scr_assert_loose_counted(scr_dyn_obj_prop_at(a, i));
for (size_t i = 0; i < scr_dyn_obj_count(b); i++) nb += scr_assert_loose_counted(scr_dyn_obj_prop_at(b, i));
if (na != nb) return false;
for (size_t i = 0; i < b->v.obj.len; i++) {
const ScrDynEntry *ent = &b->v.obj.entries[i];
for (size_t i = 0; i < scr_dyn_obj_count(b); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(b, i);
if (!scr_assert_loose_counted(ent)) continue;
const ScrDynEntry *other = scr_assert_loose_entry(a, ent->key, ent->key_len);
if (!other || !scr_assert_loose_eq(other->value, ent->value)) return false;
const ScrDyn *other = scr_assert_loose_entry(a, ent.key, ent.key_len);
if (!other || !scr_assert_loose_eq(other, ent.value)) return false;
}
return true;
}
@@ -1180,8 +1179,8 @@ static bool scr_assert_loose_array(const ScrDyn *a, const ScrDyn *b) {
if (pa || pb) {
if (!pa || !pb) {
const ScrDyn *table = pa ? pa : pb;
for (size_t i = 0; i < table->v.obj.len; i++)
if (scr_assert_loose_counted(&table->v.obj.entries[i])) return false;
for (size_t i = 0; i < scr_dyn_obj_count(table); i++)
if (scr_assert_loose_counted(scr_dyn_obj_prop_at(table, i))) return false;
} else if (!scr_assert_loose_keys(pa, pb)) {
return false;
}
@@ -1295,7 +1294,7 @@ static void scr_assert_cf_value(ScrAssertBuf *b, const ScrDyn *d, size_t indent,
* or `{`, then its entries. */
static void scr_assert_cf_object(ScrAssertBuf *b, const ScrDyn *d, size_t indent, size_t depth,
const char *cls) {
if (d->v.obj.len == 0) {
if (scr_dyn_obj_count(d) == 0) {
if (cls) {
ab_cstr(b, cls);
ab_cstr(b, " {}");
@@ -1320,25 +1319,25 @@ static void scr_assert_cf_object(ScrAssertBuf *b, const ScrDyn *d, size_t indent
} else if (d->null_proto) ab_cstr(b, "[Object: null prototype] ");
/* Render every entry, then sort the RENDERED texts (Node's
* sorted:true sorts formatted entries, quotes included). */
ScrCfEntry *ents = malloc(d->v.obj.len * sizeof *ents);
ScrCfEntry *ents = malloc(scr_dyn_obj_count(d) * sizeof *ents);
if (!ents) scr_assert_oom();
for (size_t i = 0; i < d->v.obj.len; i++) {
const ScrDynEntry *ent = &d->v.obj.entries[i];
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
ScrAssertBuf eb = {0};
scr_assert_cf_key(&eb, ent->key, ent->key_len);
scr_assert_cf_key(&eb, ent.key, ent.key_len);
ab_cstr(&eb, ": ");
scr_assert_cf_value(&eb, ent->value, indent + 2, depth - 1);
scr_assert_cf_value(&eb, ent.value, indent + 2, depth - 1);
ents[i].text = eb.data ? eb.data : malloc(1);
if (!ents[i].text) scr_assert_oom();
ents[i].len = eb.len;
}
qsort(ents, d->v.obj.len, sizeof *ents, scr_assert_cf_entry_cmp);
qsort(ents, scr_dyn_obj_count(d), sizeof *ents, scr_assert_cf_entry_cmp);
ab_char(b, '{');
for (size_t i = 0; i < d->v.obj.len; i++) {
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
ab_char(b, '\n');
scr_assert_cf_pad(b, indent + 2);
ab_bytes(b, ents[i].text, ents[i].len);
if (i + 1 < d->v.obj.len) ab_char(b, ',');
if (i + 1 < scr_dyn_obj_count(d)) ab_char(b, ',');
free(ents[i].text);
}
free(ents);
@@ -1364,11 +1363,11 @@ static const char *scr_assert_cf_bytes_class(ScrBytesElem elem) {
}
static bool scr_assert_cf_error_extra(const ScrDynEntry *ent) {
if (!ent->enumerable || (ent->key_len > 0 && ent->key[0] == '%')) return false;
return !((ent->key_len == 4 && memcmp(ent->key, "name", 4) == 0) ||
(ent->key_len == 7 && memcmp(ent->key, "message", 7) == 0) ||
(ent->key_len == 5 && memcmp(ent->key, "stack", 5) == 0));
static bool scr_assert_cf_error_extra(ScrDynProp ent) {
if (!ent.enumerable || (ent.key_len > 0 && ent.key[0] == '%')) return false;
return !((ent.key_len == 4 && memcmp(ent.key, "name", 4) == 0) ||
(ent.key_len == 7 && memcmp(ent.key, "message", 7) == 0) ||
(ent.key_len == 5 && memcmp(ent.key, "stack", 5) == 0));
}
/* An encoded Error: Node's stackless `[Name: message]` (assertion_error.js
@@ -1386,8 +1385,8 @@ static void scr_assert_cf_error(ScrAssertBuf *b, const ScrDyn *d, size_t indent,
}
ab_char(b, ']');
size_t extra = 0;
for (size_t i = 0; i < d->v.obj.len; i++) {
const ScrDynEntry *ent = &d->v.obj.entries[i];
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
if (scr_assert_cf_error_extra(ent)) extra++;
}
if (extra == 0) return;
@@ -1395,13 +1394,13 @@ static void scr_assert_cf_error(ScrAssertBuf *b, const ScrDyn *d, size_t indent,
ScrCfEntry *ents = malloc(extra * sizeof *ents);
if (!ents) scr_assert_oom();
size_t n = 0;
for (size_t i = 0; i < d->v.obj.len; i++) {
const ScrDynEntry *ent = &d->v.obj.entries[i];
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
if (!scr_assert_cf_error_extra(ent)) continue;
ScrAssertBuf eb = {0};
scr_assert_cf_key(&eb, ent->key, ent->key_len);
scr_assert_cf_key(&eb, ent.key, ent.key_len);
ab_cstr(&eb, ": ");
scr_assert_cf_value(&eb, ent->value, indent + 2, depth - 1);
scr_assert_cf_value(&eb, ent.value, indent + 2, depth - 1);
ents[n].text = eb.data ? eb.data : malloc(1);
if (!ents[n].text) scr_assert_oom();
ents[n++].len = eb.len;
@@ -1989,11 +1988,11 @@ static bool scr_assert_partial_eq(const ScrDyn *a, const ScrDyn *b);
/* objEquiv over the expected side's enumerable own keys. */
static bool scr_assert_partial_keys(const ScrDyn *a, const ScrDyn *b) {
for (size_t i = 0; i < b->v.obj.len; i++) {
const ScrDynEntry *ent = &b->v.obj.entries[i];
for (size_t i = 0; i < scr_dyn_obj_count(b); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(b, i);
if (!scr_assert_loose_counted(ent)) continue;
const ScrDynEntry *other = scr_assert_loose_entry(a, ent->key, ent->key_len);
if (!other || !scr_assert_partial_eq(other->value, ent->value)) return false;
const ScrDyn *other = scr_assert_loose_entry(a, ent.key, ent.key_len);
if (!other || !scr_assert_partial_eq(other, ent.value)) return false;
}
return true;
}
@@ -2043,11 +2042,11 @@ static bool scr_assert_partial_array(const ScrDyn *a, const ScrDyn *b) {
const ScrDyn *pb = b->v.arr.properties;
if (pb) {
const ScrDyn *pa = a->v.arr.properties;
for (size_t i = 0; i < pb->v.obj.len; i++) {
const ScrDynEntry *ent = &pb->v.obj.entries[i];
for (size_t i = 0; i < scr_dyn_obj_count(pb); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(pb, i);
if (!scr_assert_loose_counted(ent)) continue;
const ScrDynEntry *other = pa ? scr_assert_loose_entry(pa, ent->key, ent->key_len) : NULL;
if (!other || !scr_assert_partial_eq(other->value, ent->value)) return false;
const ScrDyn *other = pa ? scr_assert_loose_entry(pa, ent.key, ent.key_len) : NULL;
if (!other || !scr_assert_partial_eq(other, ent.value)) return false;
}
}
size_t pos = 0;
@@ -2434,11 +2433,11 @@ void scr_assert_loose_deep_result(bool equal, bool negated, ScrStr *msg, bool ha
void scr_assert_expects_err_dyn(ScrDyn *actual, ScrDyn *expected, ScrStr *msg, bool has_msg) {
if (actual->kind == SCR_DYN_OBJ && expected->kind == SCR_DYN_OBJ) {
bool ok = true;
for (size_t i = 0; i < expected->v.obj.len && ok; i++) {
const ScrDynEntry *e = &expected->v.obj.entries[i];
if (e->key_len == 6 && memcmp(e->key, "%error", 6) == 0) continue;
ScrDyn *av = scr_dyn_obj_get(actual, e->key, e->key_len);
if (av == NULL || !scr_assert_dyn_deep_eq(av, e->value)) ok = false;
for (size_t i = 0; i < scr_dyn_obj_count(expected) && ok; i++) {
ScrDynProp e = scr_dyn_obj_prop_at(expected, i);
if (e.key_len == 6 && memcmp(e.key, "%error", 6) == 0) continue;
ScrDyn *av = scr_dyn_obj_get(actual, e.key, e.key_len);
if (av == NULL || !scr_assert_dyn_deep_eq(av, e.value)) ok = false;
}
if (ok) return;
}
+1 -2
View File
@@ -441,8 +441,7 @@ ScrDyn *scr_buffer_constructor_value(void) {
ScrDyn *table = scr_dyn_function_properties(scr_bv_buffer);
ScrDyn *getter = scr_dyn_new_func(scr_closure_new(NULL, 0), scr_bv_prototype, 0, "native:function", "get prototype");
scr_dyn_obj_set_accessor(table, "prototype", 9, getter, NULL);
ScrDynEntry *entry = &table->v.obj.entries[table->v.obj.len - 1];
entry->enumerable = entry->configurable = false;
scr_dyn_obj_set_attrs_at(table, scr_dyn_obj_count(table) - 1, false, false, false);
scr_dyn_obj_set(table, "poolSize", 8, scr_dyn_new_num(pool_size));
ScrDyn *is_encoding = NULL;
for (size_t i = 0; i < sizeof statics / sizeof statics[0]; i++) {
+5 -5
View File
@@ -1757,11 +1757,11 @@ ScrStr *scr_fs_mkdtemp_sync_chk(const ScrDyn *prefix, const ScrDyn *opts, const
if (!scr_fs_encoding_chk(opts)) return NULL;
bool utf8 = true;
if (opts->kind == SCR_DYN_OBJ) {
for (size_t i = 0; i < opts->v.obj.len; i++) {
const ScrDynEntry *e = &opts->v.obj.entries[i];
if (scr_fs_dyn_absent(e->value)) continue;
if (strcmp(e->key, "encoding") == 0) {
if (!scr_fs_utf8_encoding(e->value)) utf8 = false;
for (size_t i = 0; i < scr_dyn_obj_count(opts); i++) {
ScrDynProp e = scr_dyn_obj_prop_at(opts, i);
if (scr_fs_dyn_absent(e.value)) continue;
if (strcmp(e.key, "encoding") == 0) {
if (!scr_fs_utf8_encoding(e.value)) utf8 = false;
continue;
}
utf8 = false; /* an unmodeled effectful option */
+14 -16
View File
@@ -507,8 +507,8 @@ static bool scr_console_holds_buffer(const ScrDyn *d) {
if (scr_console_holds_buffer(d->v.arr.items[i])) return true;
return false;
case SCR_DYN_OBJ:
for (size_t i = 0; i < d->v.obj.len; i++) {
const ScrDyn *value = d->v.obj.entries[i].value;
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
const ScrDyn *value = scr_dyn_obj_value_at(d, i);
if (value && scr_console_holds_buffer(value)) return true;
}
return false;
@@ -626,7 +626,7 @@ static ScrStr **scr_table_own_keys(const ScrDyn *item, size_t *count) {
return keys;
}
if (item->kind != SCR_DYN_OBJ) return NULL;
size_t len = item->v.obj.len;
size_t len = scr_dyn_obj_count(item);
const char **names = calloc(len ? len : 1, sizeof *names);
size_t *lens = calloc(len ? len : 1, sizeof *lens);
size_t *order = calloc(len ? len : 1, sizeof *order);
@@ -634,10 +634,10 @@ static ScrStr **scr_table_own_keys(const ScrDyn *item, size_t *count) {
if (!names || !lens || !order || !keys) scr_trap("scriptc: out of memory\n");
size_t n = 0;
for (size_t i = 0; i < len; i++) {
const ScrDynEntry *ent = &item->v.obj.entries[i];
if (!ent->enumerable) continue;
names[n] = ent->key;
lens[n] = ent->key_len;
ScrDynProp prop = scr_dyn_obj_prop_at(item, i);
if (!prop.enumerable) continue;
names[n] = prop.key;
lens[n] = prop.key_len;
n++;
}
scr_table_key_order(names, lens, n, order);
@@ -652,16 +652,14 @@ static ScrStr **scr_table_own_keys(const ScrDyn *item, size_t *count) {
/* item[key] when it is an own property; NULL otherwise. +1. */
static ScrDyn *scr_table_own_value(const ScrDyn *item, const ScrStr *key) {
if (item->kind == SCR_DYN_OBJ) {
for (size_t i = 0; i < item->v.obj.len; i++) {
const ScrDynEntry *ent = &item->v.obj.entries[i];
if (ent->key_len != key->len || memcmp(ent->key, key->data, key->len) != 0) continue;
if (ent->accessor) {
scr_table_fence("console.table rows with accessor properties have no lowering");
return NULL;
}
return scr_dyn_retain(ent->value);
size_t i = scr_dyn_obj_index_of(item, key->data, key->len);
if (i == SIZE_MAX) return NULL;
ScrDynProp prop = scr_dyn_obj_prop_at(item, i);
if (prop.accessor) {
scr_table_fence("console.table rows with accessor properties have no lowering");
return NULL;
}
return NULL;
return scr_dyn_retain(prop.value);
}
if (item->kind == SCR_DYN_ARR || item->kind == SCR_DYN_BYTES) {
size_t len = item->kind == SCR_DYN_ARR ? item->v.arr.len : item->v.bytes->len;
+4 -4
View File
@@ -63,10 +63,10 @@ static bool scr_decl_adopt_plain(const ScrDyn *n, const ScrDynPath *path, bool l
else if (n->v.obj.source_access && !n->v.obj.source_identity)
want = "a plain object";
else {
for (size_t i = 0; i < n->v.obj.len && !want; i++) {
const ScrDynEntry *e = &n->v.obj.entries[i];
if (e->accessor) want = "a plain object (this value has accessor properties)";
else if (!e->writable || !e->enumerable || !e->configurable)
for (size_t i = 0; i < scr_dyn_obj_count(n) && !want; i++) {
ScrDynProp e = scr_dyn_obj_prop_at(n, i);
if (e.accessor) want = "a plain object (this value has accessor properties)";
else if (!e.writable || !e.enumerable || !e.configurable)
want = "a plain object (this value has non-default property attributes)";
}
}
+191 -27
View File
@@ -514,9 +514,16 @@ struct ScrDyn {
struct { ScrSym *value; void (*release)(ScrSym *); ScrStr *(*render)(ScrSym *); ScrStr *(*description)(ScrSym *); } symbol;
struct { size_t len; size_t cap; ScrDyn **items; struct ScrDyn *properties; bool sealed; bool frozen; unsigned char *presence; struct ScrDynRegion *region; } arr; /* owned; NULL presence means dense; region: see ScrDynRegion */
struct {
size_t len;
size_t cap;
ScrDynEntry *entries;
size_t len; /* own string-keyed properties, in both modes */
size_t cap; /* capacity of entries (dictionary mode) or slots (layout mode) */
/* Layout mode (`layout` non-NULL): slots[i] holds the value of the
* property layout->keys[i] names, a writable, enumerable,
* configurable data property. Dictionary mode (`layout` NULL):
* entries hold keys, values and attributes. See ScrLayout. */
union {
ScrDynEntry *entries;
struct ScrDyn **slots;
};
/* Optional identity of the typed record that produced this ordinary
* snapshot. Empty records and callback interfaces can retain their
* source across repeated static-to-dynamic crossings. */
@@ -524,7 +531,7 @@ struct ScrDyn {
* interfaces may request a fresh snapshot with the true arm. */
void *source_identity;
ScrDyn *(*source_access)(void *, bool materialize);
struct ScrDynObjectIndex *index; /* optional wide-object lookup; owns no values */
struct ScrDynObjectIndex *index; /* dictionary mode: optional wide-object lookup; owns no values */
/* On a runtime-built class prototype (e.g. url.Url.prototype) and
* on a compiled class's prototype object: the constructor name
* (static or immortal text) util.inspect prints for objects
@@ -533,19 +540,13 @@ struct ScrDyn {
* exits, and those clear it before release; reuse of a freed object
* resets it (scr_dyn_alloc). */
const char *class_name;
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:
* their runtime is built with SCR_JS_TIER, where every allocation and
* every change to the key set resets it. Other programs never look
* at it, so it may hold anything there. */
struct ScrJsShape *shape;
struct ScrDynRegion *region; /* see ScrDynRegion */
/* The object's layout in layout mode, NULL in dictionary mode. The
* dynamic tier's inline caches key on it. */
const struct ScrLayout *layout;
/* Layout mode: bit i is the collector's snapshot of whether slot i's
* value is traced (ScrDynEntry.value_traced in dictionary mode). */
size_t slots_traced;
} obj; /* owned */
/* SCR_DYN_FUNC: the boxed closure (owned) + its call descriptor. `sig`
* and `name` are static compiler-emitted literals (never freed); name
@@ -624,10 +625,155 @@ _Static_assert(offsetof(ScrDyn, v.typed_ref.casts) == 16 + 8 * sizeof(void *),
_Static_assert(sizeof(((ScrDyn *)0)->v.arr) <= sizeof(((ScrDyn *)0)->v.typed_ref) &&
sizeof(((ScrDyn *)0)->v.obj) <= sizeof(((ScrDyn *)0)->v.typed_ref),
"dyn region links must not grow the node");
/* Dynamic-tier inline caches read the shape word in place (64-bit). */
_Static_assert(sizeof(void *) != 8 || offsetof(ScrDyn, v.obj.shape) == 80, "v.obj.shape offset");
/* Dynamic-tier inline caches read the layout word and slots in place (64-bit). */
_Static_assert(sizeof(void *) != 8 || offsetof(ScrDyn, v.obj.layout) == 80, "v.obj.layout offset");
_Static_assert(sizeof(void *) != 8 || offsetof(ScrDyn, v.obj.slots) == 32, "v.obj.slots offset");
_Static_assert(sizeof(void *) != 8 || offsetof(ScrDyn, v.obj.region) == 72, "v.obj.region offset");
/* ── layouts ───────────────────────────────────────────────────────────
* An ordinary object (SCR_DYN_OBJ) is in one of two modes.
*
* Layout mode: v.obj.layout names the object's key sequence, a node of the
* thread's interned transition tree, and v.obj.slots holds one value per
* key (8 bytes per property). Every property of a layout-mode object is a
* writable, enumerable, configurable data property; objects of one key
* sequence share one layout, so a layout pointer proves which key sits at
* each slot and which keys the object lacks (the dynamic tier's inline
* caches key on it). New objects start in layout mode at the thread's
* empty layout.
*
* Dictionary mode: v.obj.layout is NULL and v.obj.entries holds keys,
* values and attributes (ScrDynEntry). An object moves there, once and for
* good, when it gets an accessor, a non-default attribute, a delete, more
* than SCR_LAYOUT_MAX_KEYS keys, when the thread's layout budgets are
* spent, when it is published to other threads, and whenever code that
* reads entries directly touches it (scr_dyn_obj_dict_entries): the conversion
* is unobservable, so code that has not been taught layouts stays correct.
*
* Layouts belong to one thread and live until its runtime state is torn
* down; they are never owned by a parse region. Each holds one reference
* to its key's bytes (scr_json.c's shared key storage). */
#define SCR_LAYOUT_MAX_KEYS (8 * sizeof(size_t))
typedef struct ScrLayoutKey {
char *key; /* shared key bytes; one reference belongs to the layout that added it */
uint32_t len;
uint32_t hash; /* low bits of scr_key_hash */
} ScrLayoutKey;
typedef struct ScrLayout {
const ScrLayoutKey *keys; /* keys[0..count); shared along a chain of layouts */
uint32_t count;
uint32_t lookup_mask; /* capacity - 1 of `lookup`; 0 while it has none */
uint8_t *lookup; /* lazily built hash index: slot + 1, 0 = empty */
const struct ScrLayout *parent;
struct ScrLayout *last_child; /* the most recent transition from here */
struct ScrLayoutKeys *store; /* the key array `keys` lives in */
struct ScrLayout *all_next;
bool owns_store;
} ScrLayout;
/* The index of `key` in layout `layout`, or UINT32_MAX. */
uint32_t scr_layout_find(const ScrLayout *layout, const char *key, size_t key_len);
/* The layout that appends `key` (borrowed bytes) to `layout`, created on
* first use; NULL past SCR_LAYOUT_MAX_KEYS or the thread's budgets. */
const ScrLayout *scr_layout_add(const ScrLayout *layout, const char *key, size_t key_len);
/* The thread's empty layout. */
const ScrLayout *scr_layout_root(void);
/* Moves a layout-mode object to dictionary mode (no-op otherwise). */
void scr_dyn_to_dictionary(ScrDyn *object);
/* v.obj.entries of an object in dictionary mode, converting a layout-mode
* object first: code that reads or writes entries directly goes through
* this. */
static inline __attribute__((always_inline)) ScrDynEntry *scr_dyn_obj_dict_entries(const ScrDyn *object) {
if (__builtin_expect(object->v.obj.layout != NULL, 0)) scr_dyn_to_dictionary((ScrDyn *)object);
return object->v.obj.entries;
}
/* scr_dyn_obj_key_at and scr_dyn_obj_value_at out of line, for emitted
* code (borrowed results). */
const char *scr_dyn_member_key(const ScrDyn *object, size_t i);
size_t scr_dyn_member_key_len(const ScrDyn *object, size_t i);
ScrDyn *scr_dyn_member_value(const ScrDyn *object, size_t i);
/* ── own properties of ordinary objects ──────────────────────────────
* The accessors are always inlined, so a binary that never sees an
* ordinary object links no out-of-line copies. Code outside the object core (scr_json.c and scr_js_object.c) reads and
* adjusts an SCR_DYN_OBJ's own string-keyed properties through these
* accessors, never through v.obj.entries or v.obj.slots, so they work in
* both modes. Index i runs over the properties in creation order,
* 0 <= i < scr_dyn_obj_count. Keys and values are borrowed: they stay
* valid until the object changes. */
typedef struct {
const char *key;
size_t key_len;
ScrDyn *value; /* data property; the undefined value for an accessor */
ScrDyn *getter; /* accessor property; NULL when absent */
ScrDyn *setter; /* accessor property; NULL when absent */
bool accessor;
bool writable;
bool enumerable;
bool configurable;
} ScrDynProp;
static inline __attribute__((always_inline)) size_t scr_dyn_obj_count(const ScrDyn *o) {
return o->v.obj.len;
}
static inline __attribute__((always_inline)) const char *scr_dyn_obj_key_at(const ScrDyn *o, size_t i, size_t *key_len) {
const ScrLayout *layout = o->v.obj.layout;
if (layout) {
*key_len = layout->keys[i].len;
return layout->keys[i].key;
}
*key_len = o->v.obj.entries[i].key_len;
return o->v.obj.entries[i].key;
}
/* The value of data property i (borrowed); the undefined value for an
* accessor property, whose functions scr_dyn_obj_prop_at answers. */
static inline __attribute__((always_inline)) ScrDyn *scr_dyn_obj_value_at(const ScrDyn *o, size_t i) {
return o->v.obj.layout ? o->v.obj.slots[i] : o->v.obj.entries[i].value;
}
static inline __attribute__((always_inline)) ScrDynProp scr_dyn_obj_prop_at(const ScrDyn *o, size_t i) {
const ScrLayout *layout = o->v.obj.layout;
if (layout)
return (ScrDynProp){layout->keys[i].key, layout->keys[i].len, o->v.obj.slots[i], NULL, NULL,
false, true, true, true};
const ScrDynEntry *e = &o->v.obj.entries[i];
return (ScrDynProp){e->key, e->key_len, e->value, e->getter, e->setter,
e->accessor, e->writable, e->enumerable, e->configurable};
}
/* The index of own property `key`, or SIZE_MAX. A linear scan in
* dictionary mode: for code that looks up options and headers, not for hot
* property reads. */
static inline size_t scr_dyn_obj_index_of(const ScrDyn *o, const char *key, size_t key_len) {
if (o->v.obj.layout) {
uint32_t i = scr_layout_find(o->v.obj.layout, key, key_len);
return i == UINT32_MAX ? SIZE_MAX : i;
}
for (size_t i = 0; i < o->v.obj.len; i++) {
const ScrDynEntry *e = &o->v.obj.entries[i];
if (e->key_len == key_len && memcmp(e->key, key, key_len) == 0) return i;
}
return SIZE_MAX;
}
/* Sets the attributes of property i (`writable` is ignored for an
* accessor property): builtin objects the runtime builds restrict their
* members this way right after adding them. */
static inline void scr_dyn_obj_set_attrs_at(ScrDyn *o, size_t i, bool writable, bool enumerable,
bool configurable) {
if (o->v.obj.layout && writable && enumerable && configurable) return;
ScrDynEntry *e = &scr_dyn_obj_dict_entries(o)[i];
if (!e->accessor) e->writable = writable;
e->enumerable = enumerable;
e->configurable = configurable;
}
/* ── intact parse regions ─────────────────────────────────────────────
* Every heap dyn node carries a cycle header, so an ordinary release that
* leaves an array or object alive buffers it as a possible cycle root, and
@@ -792,6 +938,27 @@ ScrDyn *scr_dyn_get_own_property_symbols(const ScrDyn *value);
/* The own property stored under a symbol key on an object (borrowed), or
* NULL when it has none. */
ScrDynEntry *scr_dyn_own_symbol_entry(const ScrDyn *object, const ScrDyn *key);
/* The own symbol-keyed property `key` of `object` as a property view
* (scr_dyn_obj_prop_at; `key` and `key_len` name its private storage
* key); false when there is none. */
static inline bool scr_dyn_own_symbol_prop(const ScrDyn *object, const ScrDyn *key, ScrDynProp *out) {
const ScrDynEntry *e = scr_dyn_own_symbol_entry(object, key);
if (!e) return false;
*out = (ScrDynProp){e->key, e->key_len, e->value, e->getter, e->setter,
e->accessor, e->writable, e->enumerable, e->configurable};
return true;
}
/* Copies own property i of `source` to `target` (scr_dyn_copy_entry). */
static inline void scr_dyn_copy_prop_at(ScrDyn *target, const ScrDyn *source, size_t i) {
if (source->v.obj.layout) {
ScrDynProp prop = scr_dyn_obj_prop_at(source, i);
ScrDynEntry entry = {(char *)prop.key, prop.key_len, prop.value, NULL, NULL,
false, false, false, true, true, true};
scr_dyn_copy_entry(target, source, &entry);
return;
}
scr_dyn_copy_entry(target, source, &source->v.obj.entries[i]);
}
/* The private storage name of a symbol key (+1). With it,
* scr_dyn_symbol_init_own gives an object that has no symbol properties
* yet its first one, a writable, enumerable, configurable data property
@@ -2027,7 +2194,7 @@ void scr_dyn_trace_v(void *d, ScrTraceVisit visit, void *ctx);
long scr_dyn_live_count(void);
#endif
/* Checked-object primitives the dynamic tier's shapes and caches build on
/* Checked-object primitives the dynamic tier's inline caches build on
* (scr_js_object.c). They exist only in the SCR_JS_TIER build of
* scr_json.c, which programs with dynamic-tier code link. */
/* The builtin Object.prototype node, or NULL before its first use. */
@@ -2035,12 +2202,9 @@ const ScrDyn *scr_dyn_tier_object_prototype(void);
/* A read site's answer for a native array's length or a native iterator's
* next (scr_js_get_probe_native): +1, or NULL. */
ScrDyn *scr_dyn_tier_unshaped_get(const ScrDyn *receiver, const ScrStr *key);
/* Shared key bytes for `key` (+1, NUL-terminated), and their release. */
char *scr_dyn_tier_key_copy(const char *key, size_t key_len);
void scr_dyn_tier_key_release(char *key, size_t key_len);
/* Appends an own data property (writable, enumerable, configurable) that
* `obj` does not have, sharing `key`'s bytes and taking ownership of
* `value`, exactly as an ordinary insertion would; leaves the shape reset. */
void scr_dyn_tier_append(ScrDyn *obj, char *key, size_t key_len, ScrDyn *value);
/* Appends to a layout-mode object the property that `next`, a child of
* its layout, adds, taking ownership of `value`, exactly as an ordinary
* insertion would. */
void scr_dyn_tier_append(ScrDyn *obj, const ScrLayout *next, ScrDyn *value);
#endif /* SCR_DYN_H */
+14 -13
View File
@@ -60,17 +60,17 @@ static ScrDyn *hp_props_of(void *child) {
return props;
}
static ScrDynEntry *hp_entry(const ScrDyn *props, const char *key, size_t key_len) {
if (!props) return NULL;
for (size_t i = 0; i < props->v.obj.len; i++) {
ScrDynEntry *e = &props->v.obj.entries[i];
if (e->key_len == key_len && !memcmp(e->key, key, key_len)) return e;
}
return NULL;
/* Own property `key` of the table object, in *out; false when absent. */
static bool hp_entry(const ScrDyn *props, const char *key, size_t key_len, ScrDynProp *out) {
if (!props) return false;
size_t i = scr_dyn_obj_index_of(props, key, key_len);
if (i == SIZE_MAX) return false;
*out = scr_dyn_obj_prop_at(props, i);
return true;
}
static bool hp_has(const ScrDyn *props, const char *key, size_t key_len) {
return hp_entry(props, key, key_len) != NULL;
return props && scr_dyn_obj_index_of(props, key, key_len) != SIZE_MAX;
}
/* A property the program added, read with the child as `this`: an
@@ -78,10 +78,10 @@ static bool hp_has(const ScrDyn *props, const char *key, size_t key_len) {
* it (`Object.defineProperty(child, "x", { get() { return this.pid; } })`).
* +1, or NULL with the exception pending. */
static ScrDyn *hp_read(ScrDyn *props, ScrDyn *self, const char *key, size_t key_len) {
ScrDynEntry *e = hp_entry(props, key, key_len);
if (!e || !e->accessor) return scr_dyn_obj_read(props, key, key_len);
if (!e->getter) return scr_dyn_retain(scr_dyn_undefined());
ScrDyn *getter = scr_dyn_retain(e->getter);
ScrDynProp e;
if (!hp_entry(props, key, key_len, &e) || !e.accessor) return scr_dyn_obj_read(props, key, key_len);
if (!e.getter) return scr_dyn_retain(scr_dyn_undefined());
ScrDyn *getter = scr_dyn_retain(e.getter);
scr_dyn_this_push_dyn(self);
ScrDyn *result = scr_dyn_call(getter, NULL, 0, "getter");
scr_dyn_this_pop();
@@ -132,7 +132,8 @@ static ScrDyn *hp_get(void *h, const char *key, size_t key_len) {
static bool hp_set(void *h, const char *key, size_t key_len, const ScrDyn *value) {
ScrDyn *props = hp_find(h);
ScrDynEntry *entry = hp_entry(props, key, key_len);
ScrDynProp found;
const ScrDynProp *entry = hp_entry(props, key, key_len, &found) ? &found : NULL;
if (entry && entry->accessor) {
/* An accessor's setter runs on the child. Without one the write throws
* the strict-mode TypeError, as the other handle getters do. */
+29 -29
View File
@@ -4821,14 +4821,14 @@ static ScrArr *sf_headers_convert_init(const ScrDyn *headers, bool *canonical_ou
return converted;
}
if (headers->kind == SCR_DYN_OBJ) {
for (size_t i = 0; i < headers->v.obj.len; i++) {
const ScrDynEntry *e = &headers->v.obj.entries[i];
ScrStr *raw_name = scr_str_new(e->key, e->key_len);
for (size_t i = 0; i < scr_dyn_obj_count(headers); i++) {
ScrDynProp e = scr_dyn_obj_prop_at(headers, i);
ScrStr *raw_name = scr_str_new(e.key, e.key_len);
ScrStr *name_bytes = sf_header_value_bytestring(raw_name);
ScrStr *name = name_bytes ? sf_latin1_to_utf8(name_bytes) : NULL;
scr_str_release(name_bytes);
scr_str_release(raw_name);
ScrDyn *source = scr_dyn_retain(e->value);
ScrDyn *source = scr_dyn_retain(e.value);
ScrStr *raw_value = name ? scr_dyn_string_coerce_js(source) : NULL;
scr_dyn_release(source);
ScrStr *value_bytes = raw_value
@@ -6088,11 +6088,11 @@ static int8_t sf_light_header_form(const ScrDyn *headers, bool json_body,
}
int8_t form = 0;
if (headers->kind == SCR_DYN_OBJ) {
ScrArr *out = scr_arr_new(SCR_ELEM_STR, 2 * headers->v.obj.len);
ScrArr *out = scr_arr_new(SCR_ELEM_STR, 2 * scr_dyn_obj_count(headers));
form = 3;
for (size_t i = 0; i < headers->v.obj.len && form == 3; i++) {
const ScrDynEntry *e = &headers->v.obj.entries[i];
const ScrDyn *value = e->value;
for (size_t i = 0; i < scr_dyn_obj_count(headers) && form == 3; i++) {
ScrDynProp e = scr_dyn_obj_prop_at(headers, i);
const ScrDyn *value = e.value;
size_t count = value->kind == SCR_DYN_ARR ? value->v.arr.len : 1;
for (size_t j = 0; j < count; j++) {
const ScrDyn *item = value->kind == SCR_DYN_ARR ? value->v.arr.items[j] : value;
@@ -6106,7 +6106,7 @@ static int8_t sf_light_header_form(const ScrDyn *headers, bool json_body,
form = 0;
break;
}
scr_arr_push_ref(out, scr_str_new(e->key, e->key_len));
scr_arr_push_ref(out, scr_str_new(e.key, e.key_len));
scr_arr_push_ref(out, text);
}
}
@@ -6666,27 +6666,27 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) {
return sf_reject_now(promise, "fetch failed");
}
if (init && init->kind == SCR_DYN_OBJ) {
for (size_t i = 0; i < init->v.obj.len; i++) {
const ScrDynEntry *entry = &init->v.obj.entries[i];
bool supported = sf_name(entry->key, entry->key_len, "method") ||
sf_name(entry->key, entry->key_len, "headers") ||
sf_name(entry->key, entry->key_len, "body") ||
sf_name(entry->key, entry->key_len, "duplex") ||
sf_name(entry->key, entry->key_len, "redirect") ||
sf_name(entry->key, entry->key_len, "signal");
for (size_t i = 0; i < scr_dyn_obj_count(init); i++) {
ScrDynProp entry = scr_dyn_obj_prop_at(init, i);
bool supported = sf_name(entry.key, entry.key_len, "method") ||
sf_name(entry.key, entry.key_len, "headers") ||
sf_name(entry.key, entry.key_len, "body") ||
sf_name(entry.key, entry.key_len, "duplex") ||
sf_name(entry.key, entry.key_len, "redirect") ||
sf_name(entry.key, entry.key_len, "signal");
bool recognized_unsupported =
sf_name(entry->key, entry->key_len, "cache") ||
sf_name(entry->key, entry->key_len, "credentials") ||
sf_name(entry->key, entry->key_len, "dispatcher") ||
sf_name(entry->key, entry->key_len, "integrity") ||
sf_name(entry->key, entry->key_len, "keepalive") ||
sf_name(entry->key, entry->key_len, "mode") ||
sf_name(entry->key, entry->key_len, "priority") ||
sf_name(entry->key, entry->key_len, "referrer") ||
sf_name(entry->key, entry->key_len, "referrerPolicy") ||
sf_name(entry->key, entry->key_len, "window");
if (!supported && recognized_unsupported && entry->value &&
entry->value->kind != SCR_DYN_UNDEF) {
sf_name(entry.key, entry.key_len, "cache") ||
sf_name(entry.key, entry.key_len, "credentials") ||
sf_name(entry.key, entry.key_len, "dispatcher") ||
sf_name(entry.key, entry.key_len, "integrity") ||
sf_name(entry.key, entry.key_len, "keepalive") ||
sf_name(entry.key, entry.key_len, "mode") ||
sf_name(entry.key, entry.key_len, "priority") ||
sf_name(entry.key, entry.key_len, "referrer") ||
sf_name(entry.key, entry.key_len, "referrerPolicy") ||
sf_name(entry.key, entry.key_len, "window");
if (!supported && recognized_unsupported && entry.value &&
entry.value->kind != SCR_DYN_UNDEF) {
return sf_reject_now(promise, "fetch failed");
}
}
+16 -16
View File
@@ -2047,10 +2047,10 @@ void scr_http_res_write_head_dyn(ScrHttpRes *r, double status, const ScrDyn *hea
scr_dyn_arg_type_fail("headers", "an instance of Object", headers);
return;
}
for (size_t i = 0; i < headers->v.obj.len; i++) {
const ScrDynEntry *e = &headers->v.obj.entries[i];
const ScrDyn *v = e->value;
ScrStr *name = scr_str_new(e->key, e->key_len);
for (size_t i = 0; i < scr_dyn_obj_count(headers); i++) {
ScrDynProp e = scr_dyn_obj_prop_at(headers, i);
const ScrDyn *v = e.value;
ScrStr *name = scr_str_new(e.key, e.key_len);
if (v->kind == SCR_DYN_STR) {
scr_http_res_set_header(r, name, v->v.str);
} else if (v->kind == SCR_DYN_NUM) {
@@ -6200,10 +6200,10 @@ static bool scr_http_dynh_req_set(void *h, const char *key, size_t key_len, cons
* consecutive same-name pairs). Numbers format through String(n). NULL =
* a value this surface cannot honestly carry (exception pending). */
static ScrArr *scr_http_dynh_pairs(const ScrDyn *headers) {
ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, headers->v.obj.len * 2);
for (size_t i = 0; i < headers->v.obj.len; i++) {
const ScrDynEntry *e = &headers->v.obj.entries[i];
const ScrDyn *v = e->value;
ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, scr_dyn_obj_count(headers) * 2);
for (size_t i = 0; i < scr_dyn_obj_count(headers); i++) {
ScrDynProp e = scr_dyn_obj_prop_at(headers, i);
const ScrDyn *v = e.value;
size_t reps = v->kind == SCR_DYN_ARR ? v->v.arr.len : 1;
for (size_t k = 0; k < reps; k++) {
const ScrDyn *one = v->kind == SCR_DYN_ARR ? v->v.arr.items[k] : v;
@@ -6218,7 +6218,7 @@ static ScrArr *scr_http_dynh_pairs(const ScrDyn *headers) {
scr_jb_puts(&b, "Invalid value \"");
scr_jb_puts(&b, one->kind == SCR_DYN_UNDEF ? "undefined" : "object");
scr_jb_puts(&b, "\" for header \"");
for (size_t c = 0; c < e->key_len; c++) scr_jb_putc(&b, e->key[c]);
for (size_t c = 0; c < e.key_len; c++) scr_jb_putc(&b, e.key[c]);
scr_jb_putc(&b, '"');
ScrStr *msg = scr_jb_finish(&b);
scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len, "ERR_HTTP_INVALID_HEADER_VALUE");
@@ -6226,7 +6226,7 @@ static ScrArr *scr_http_dynh_pairs(const ScrDyn *headers) {
scr_arr_release(pairs);
return NULL;
}
scr_arr_push_ref(pairs, scr_str_new(e->key, e->key_len));
scr_arr_push_ref(pairs, scr_str_new(e.key, e.key_len));
scr_arr_push_ref(pairs, vs);
}
}
@@ -6945,8 +6945,8 @@ static ScrDyn *scr_http_dynh_agent_get(void *h, const char *key, size_t key_len)
scr_dyn_arr_push(arr, item);
}
/* the list is most recent first: reverse each array into Node's order */
for (size_t i = 0; i < obj->v.obj.len; i++) {
ScrDyn *arr = obj->v.obj.entries[i].value;
for (size_t i = 0; i < scr_dyn_obj_count(obj); i++) {
ScrDyn *arr = scr_dyn_obj_value_at(obj, i);
for (size_t l = 0, r = arr->v.arr.len ? arr->v.arr.len - 1 : 0; l < r; l++, r--) {
ScrDyn *t = arr->v.arr.items[l];
arr->v.arr.items[l] = arr->v.arr.items[r];
@@ -7449,13 +7449,13 @@ bool scr_http_req_opts_read(const ScrDyn *opts, bool secure, const char *api, Sc
headers = headers_view;
}
if (headers != NULL && headers->kind == SCR_DYN_OBJ) {
for (size_t i = 0; i < headers->v.obj.len; i++) {
const ScrDynEntry *e = &headers->v.obj.entries[i];
ScrStr *name = scr_str_new(e->key, e->key_len);
for (size_t i = 0; i < scr_dyn_obj_count(headers); i++) {
ScrDynProp e = scr_dyn_obj_prop_at(headers, i);
ScrStr *name = scr_str_new(e.key, e.key_len);
ScrStr *label = scr_str_new("", 0);
scr_http_validate_header_name(name, label);
scr_str_release(label);
const ScrDyn *v = e->value;
const ScrDyn *v = e.value;
if (!scr_exc_pending()) {
if (v->kind == SCR_DYN_ARR) {
for (size_t k = 0; k < v->v.arr.len && !scr_exc_pending(); k++)
+8 -8
View File
@@ -2559,9 +2559,9 @@ ScrNetServer *scr_http2_create_secure_server_dyn(const ScrDyn *opts /*borrowed*/
ScrHttpReqFn fn) {
bool allow_http1 = false;
if (opts != NULL && opts->kind == SCR_DYN_OBJ) {
for (size_t i = 0; i < opts->v.obj.len; i++) {
if (strcmp(opts->v.obj.entries[i].key, "allowHTTP1") == 0) {
allow_http1 = scr_dyn_truthy(opts->v.obj.entries[i].value);
for (size_t i = 0; i < scr_dyn_obj_count(opts); i++) {
if (strcmp(scr_dyn_obj_prop_at(opts, i).key, "allowHTTP1") == 0) {
allow_http1 = scr_dyn_truthy(scr_dyn_obj_value_at(opts, i));
}
}
}
@@ -3105,15 +3105,15 @@ static void scr_h2_dyn_fire_goaway(ScrClosure *cb, double code, double last) {
/* Flatten a dyn headers object into the flat pairs array (numbers via
* String(n); the :status/:method/... pseudo-headers pass through). */
static ScrArr *scr_h2_dom_to_pairs(const ScrDyn *headers) {
ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, headers->v.obj.len * 2);
for (size_t i = 0; i < headers->v.obj.len; i++) {
const ScrDynEntry *e = &headers->v.obj.entries[i];
const ScrDyn *v = e->value;
ScrArr *pairs = scr_arr_new(SCR_ELEM_STR, scr_dyn_obj_count(headers) * 2);
for (size_t i = 0; i < scr_dyn_obj_count(headers); i++) {
ScrDynProp e = scr_dyn_obj_prop_at(headers, i);
const ScrDyn *v = e.value;
ScrStr *vs;
if (v->kind == SCR_DYN_STR) vs = scr_str_retain(v->v.str);
else if (v->kind == SCR_DYN_NUM) vs = scr_f64_to_scrstr(v->v.num);
else continue; /* undefined/null header: dropped, Node's stance */
scr_arr_push_ref(pairs, scr_str_new(e->key, e->key_len));
scr_arr_push_ref(pairs, scr_str_new(e.key, e.key_len));
scr_arr_push_ref(pairs, vs);
}
return pairs;
+107 -90
View File
@@ -973,21 +973,23 @@ ScrStr *scr_insp_regex(ScrRegex *re) {
* the k-th symbol key. */
static size_t insp_view_property_slots(const ScrDyn *props) {
if (!props) return 0;
return props->v.obj.len + (props->symbol_keys ? props->symbol_keys->v.arr.len : 0);
return scr_dyn_obj_count(props) + (props->symbol_keys ? props->symbol_keys->v.arr.len : 0);
}
static ScrStr *insp_accessor_tag(const ScrDynEntry *ent);
static ScrStr *insp_accessor_tag(const ScrDynProp *ent);
static void insp_key_into(InspBuf *b, const char *key, size_t len);
/* Slot k's `key: value` entry when it is enumerable, else NULL. */
static ScrStr *insp_view_property(ScrDyn *props, size_t k, double recurse, double depth) {
const ScrDynEntry *ent;
ScrDynProp found;
const ScrDynProp *ent = NULL;
ScrStr *key = NULL;
if (k < props->v.obj.len) {
ent = &props->v.obj.entries[k];
if (k < scr_dyn_obj_count(props)) {
found = scr_dyn_obj_prop_at(props, k);
ent = &found;
} else {
ScrDyn *symbol = props->symbol_keys->v.arr.items[k - props->v.obj.len];
ent = scr_dyn_own_symbol_entry(props, symbol);
ScrDyn *symbol = props->symbol_keys->v.arr.items[k - scr_dyn_obj_count(props)];
if (scr_dyn_own_symbol_prop(props, symbol, &found)) ent = &found;
if (ent && ent->enumerable) key = scr_insp_dyn(symbol, recurse, depth);
}
if (!ent || !ent->enumerable) return NULL;
@@ -1005,10 +1007,11 @@ static ScrStr *insp_view_property(ScrDyn *props, size_t k, double recurse, doubl
static size_t insp_view_property_count(ScrDyn *props) {
size_t count = 0;
for (size_t k = 0; k < insp_view_property_slots(props); k++) {
if (k < props->v.obj.len) count += props->v.obj.entries[k].enumerable;
if (k < scr_dyn_obj_count(props)) count += scr_dyn_obj_prop_at(props, k).enumerable;
else {
const ScrDynEntry *ent = scr_dyn_own_symbol_entry(props, props->symbol_keys->v.arr.items[k - props->v.obj.len]);
count += ent && ent->enumerable;
ScrDynProp ent;
count += scr_dyn_own_symbol_prop(props, props->symbol_keys->v.arr.items[k - scr_dyn_obj_count(props)], &ent) &&
ent.enumerable;
}
}
return count;
@@ -1157,11 +1160,11 @@ static ScrDyn *insp_error_cause(ScrError *e, bool *enumerable, ScrDyn **view) {
ScrDyn *node = scr_errdyn_view_of(e);
if (node != NULL && node->kind == SCR_DYN_TYPED_REF) node = *view = scr_dyn_typed_ref_materialize(node);
if (node != NULL && node->kind == SCR_DYN_OBJ) {
for (size_t i = 0; i < node->v.obj.len; i++) {
ScrDynEntry *ent = &node->v.obj.entries[i];
if (ent->key_len == 5 && memcmp(ent->key, "cause", 5) == 0) {
*enumerable = ent->enumerable;
return ent->value;
for (size_t i = 0; i < scr_dyn_obj_count(node); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(node, i);
if (ent.key_len == 5 && memcmp(ent.key, "cause", 5) == 0) {
*enumerable = ent.enumerable;
return ent.value;
}
}
return NULL;
@@ -1176,9 +1179,9 @@ static ScrDyn *insp_error_cause(ScrError *e, bool *enumerable, ScrDyn **view) {
* other non-enumerable property of an error. */
bool scr_insp_error_bag_has_properties(ScrDyn *bag) {
if (bag == NULL || bag->kind != SCR_DYN_OBJ) return false;
for (size_t i = 0; i < bag->v.obj.len; i++) {
const ScrDynEntry *ent = &bag->v.obj.entries[i];
if (ent->enumerable && !(ent->key_len == 5 && memcmp(ent->key, "cause", 5) == 0)) return true;
for (size_t i = 0; i < scr_dyn_obj_count(bag); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(bag, i);
if (ent.enumerable && !(ent.key_len == 5 && memcmp(ent.key, "cause", 5) == 0)) return true;
}
return false;
}
@@ -1282,7 +1285,7 @@ ScrStr *scr_insp_error(ScrError *e, double recurse, double depth) {
* (/^[a-zA-Z_][a-zA-Z_0-9]*$/, '__proto__' excepted), quoted otherwise. */
/* util.inspect's text for an accessor property: [Getter], [Setter] or
* [Getter/Setter], by which of the two are functions. +1. */
static ScrStr *insp_accessor_tag(const ScrDynEntry *ent) {
static ScrStr *insp_accessor_tag(const ScrDynProp *ent) {
bool get = ent->getter && ent->getter->kind == SCR_DYN_FUNC;
bool set = ent->setter && ent->setter->kind == SCR_DYN_FUNC;
const char *text = get && set ? "[Getter/Setter]" : get ? "[Getter]" : set ? "[Setter]" : "undefined";
@@ -1371,12 +1374,12 @@ static ScrDyn *insp_assertion_ellipsis(ScrDyn *value) {
* Node's `{ ... }` tail. */
/* An own non-enumerable entry of the error encoding, NULL when absent or
* enumerable (an enumerable one prints with the other own keys). */
static const ScrDynEntry *insp_error_hidden_entry(const ScrDyn *d, const char *key, size_t len) {
for (size_t i = 0; i < d->v.obj.len; i++) {
const ScrDynEntry *ent = &d->v.obj.entries[i];
if (ent->key_len == len && memcmp(ent->key, key, len) == 0) return ent->enumerable ? NULL : ent;
}
return NULL;
static const ScrDynProp *insp_error_hidden_entry(const ScrDyn *d, const char *key, size_t len,
ScrDynProp *out) {
size_t i = scr_dyn_obj_index_of(d, key, len);
if (i == SIZE_MAX) return NULL;
*out = scr_dyn_obj_prop_at(d, i);
return out->enumerable ? NULL : out;
}
static ScrStr *scr_insp_dyn_error(ScrDyn *d, double recurse, double depth) {
@@ -1390,19 +1393,20 @@ static ScrStr *scr_insp_dyn_error(ScrDyn *d, double recurse, double depth) {
memcmp(name->v.str->data, "AssertionError", 14) == 0;
double value_depth = assertion ? recurse : depth;
size_t extra = 0;
for (size_t i = 0; i < d->v.obj.len; i++) {
ScrDynEntry *ent = &d->v.obj.entries[i];
if (!ent->enumerable) continue;
if ((ent->key_len == 4 && memcmp(ent->key, "name", 4) == 0) ||
(ent->key_len == 7 && memcmp(ent->key, "message", 7) == 0) ||
(ent->key_len == 5 && memcmp(ent->key, "stack", 5) == 0))
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
if (!ent.enumerable) continue;
if ((ent.key_len == 4 && memcmp(ent.key, "name", 4) == 0) ||
(ent.key_len == 7 && memcmp(ent.key, "message", 7) == 0) ||
(ent.key_len == 5 && memcmp(ent.key, "stack", 5) == 0))
continue;
extra++;
}
/* formatError shows a non-enumerable `cause`, and an AggregateError's
* `errors` array, after the enumerable keys: `[cause]`, `[errors]`. */
const ScrDynEntry *cause = insp_error_hidden_entry(d, "cause", 5);
const ScrDynEntry *errors = insp_error_hidden_entry(d, "errors", 6);
ScrDynProp cause_prop, errors_prop;
const ScrDynProp *cause = insp_error_hidden_entry(d, "cause", 5, &cause_prop);
const ScrDynProp *errors = insp_error_hidden_entry(d, "errors", 6, &errors_prop);
if (errors && errors->value->kind != SCR_DYN_ARR) errors = NULL;
extra += (cause != NULL) + (errors != NULL);
InspBuf base = {0};
@@ -1426,20 +1430,20 @@ static ScrStr *scr_insp_dyn_error(ScrDyn *d, double recurse, double depth) {
if (extra == 0) return base_str;
scr_insp_begin(recurse + 1);
scr_insp_seen_push(d);
for (size_t i = 0; i < d->v.obj.len; i++) {
ScrDynEntry *ent = &d->v.obj.entries[i];
if (!ent->enumerable) continue;
if ((ent->key_len == 4 && memcmp(ent->key, "name", 4) == 0) ||
(ent->key_len == 7 && memcmp(ent->key, "message", 7) == 0) ||
(ent->key_len == 5 && memcmp(ent->key, "stack", 5) == 0))
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
if (!ent.enumerable) continue;
if ((ent.key_len == 4 && memcmp(ent.key, "name", 4) == 0) ||
(ent.key_len == 7 && memcmp(ent.key, "message", 7) == 0) ||
(ent.key_len == 5 && memcmp(ent.key, "stack", 5) == 0))
continue;
bool shortened = assertion && ((ent->key_len == 6 && memcmp(ent->key, "actual", 6) == 0) ||
(ent->key_len == 8 && memcmp(ent->key, "expected", 8) == 0));
ScrDyn *shown = shortened ? insp_assertion_ellipsis(ent->value) : scr_dyn_retain(ent->value);
bool shortened = assertion && ((ent.key_len == 6 && memcmp(ent.key, "actual", 6) == 0) ||
(ent.key_len == 8 && memcmp(ent.key, "expected", 8) == 0));
ScrDyn *shown = shortened ? insp_assertion_ellipsis(ent.value) : scr_dyn_retain(ent.value);
ScrStr *val = scr_insp_dyn(shown, recurse + 1, value_depth);
scr_dyn_release(shown);
InspBuf eb = {0};
insp_key_into(&eb, ent->key, ent->key_len);
insp_key_into(&eb, ent.key, ent.key_len);
ib_cstr(&eb, ": ");
ib_bytes(&eb, val->data, val->len);
scr_str_release(val);
@@ -1447,7 +1451,7 @@ static ScrStr *scr_insp_dyn_error(ScrDyn *d, double recurse, double depth) {
scr_insp_entry(entry, false);
scr_str_release(entry);
}
const ScrDynEntry *hidden[] = {cause, errors};
const ScrDynProp *hidden[] = {cause, errors};
for (size_t i = 0; i < 2; i++) {
if (!hidden[i]) continue;
ScrStr *val = scr_insp_dyn(hidden[i]->value, recurse + 1, value_depth);
@@ -1690,9 +1694,9 @@ static ScrStr *scr_insp_dyn_collection(ScrDyn *d, double recurse, double depth,
/* An own symbol-keyed property of `d` (borrowed) this render shows, or
* NULL: enumerable ones, and under showHidden the non-enumerable ones too. */
static ScrDynEntry *scr_insp_symbol_entry(ScrDyn *d, size_t i) {
ScrDynEntry *ent = scr_dyn_own_symbol_entry(d, d->symbol_keys->v.arr.items[i]);
return ent && (ent->enumerable || g_show_hidden) ? ent : NULL;
static bool scr_insp_symbol_entry(ScrDyn *d, size_t i, ScrDynProp *out) {
return scr_dyn_own_symbol_prop(d, d->symbol_keys->v.arr.items[i], out) &&
(out->enumerable || g_show_hidden);
}
/* An ordinary object: `Name { key: value }` with its constructor name
@@ -1700,15 +1704,21 @@ static ScrDynEntry *scr_insp_symbol_entry(ScrDyn *d, size_t i) {
* enumerable string keys, then the own enumerable symbol keys. */
/* An own entry this render shows: enumerable ones, and under showHidden
* the non-enumerable ones too (never the encoding's `%` markers). */
static bool insp_obj_shows(const ScrDynEntry *ent) {
static bool insp_obj_shows(const ScrDynProp *ent) {
return ent->enumerable || (g_show_hidden && !(ent->key_len && ent->key[0] == '%'));
}
static ScrStr *scr_insp_obj(ScrDyn *d, double recurse, double depth, const char *cls) {
size_t shown = 0;
for (size_t i = 0; i < d->v.obj.len; i++) shown += insp_obj_shows(&d->v.obj.entries[i]);
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
shown += insp_obj_shows(&ent);
}
size_t nsymbols = d->symbol_keys ? d->symbol_keys->v.arr.len : 0;
for (size_t i = 0; i < nsymbols; i++) shown += scr_insp_symbol_entry(d, i) != NULL;
for (size_t i = 0; i < nsymbols; i++) {
ScrDynProp ent;
shown += scr_insp_symbol_entry(d, i, &ent);
}
if (shown == 0) {
if (cls) return scr_insp_class_tag(cls, " {}");
return d->null_proto ? scr_str_new("[Object: null prototype] {}", 27)
@@ -1720,17 +1730,17 @@ static ScrStr *scr_insp_obj(ScrDyn *d, double recurse, double depth, const char
: insp_styled_lit("[Object]", 8, INSP_SPECIAL);
}
scr_insp_begin(recurse + 1);
for (size_t i = 0; i < d->v.obj.len; i++) {
ScrDynEntry *ent = &d->v.obj.entries[i];
if (!insp_obj_shows(ent)) continue;
ScrStr *val = ent->accessor ? insp_accessor_tag(ent) : scr_insp_dyn(ent->value, recurse + 1, depth);
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(d, i);
if (!insp_obj_shows(&ent)) continue;
ScrStr *val = ent.accessor ? insp_accessor_tag(&ent) : scr_insp_dyn(ent.value, recurse + 1, depth);
InspBuf eb = {0};
if (ent->enumerable) {
insp_key_into(&eb, ent->key, ent->key_len);
if (ent.enumerable) {
insp_key_into(&eb, ent.key, ent.key_len);
} else {
/* formatProperty's hidden key: the key's rendering in brackets. */
ib_char(&eb, '[');
insp_key_into(&eb, ent->key, ent->key_len);
insp_key_into(&eb, ent.key, ent.key_len);
ib_char(&eb, ']');
}
ib_cstr(&eb, ": ");
@@ -1742,8 +1752,9 @@ static ScrStr *scr_insp_obj(ScrDyn *d, double recurse, double depth, const char
if (scr_exc_pending()) break;
}
for (size_t i = 0; i < nsymbols; i++) {
ScrDynEntry *ent = scr_insp_symbol_entry(d, i);
if (!ent) continue;
ScrDynProp found;
if (!scr_insp_symbol_entry(d, i, &found)) continue;
const ScrDynProp *ent = &found;
ScrStr *key = scr_insp_dyn(d->symbol_keys->v.arr.items[i], recurse + 1, depth);
ScrStr *val = ent->accessor ? insp_accessor_tag(ent) : scr_insp_dyn(ent->value, recurse + 1, depth);
InspBuf eb = {0};
@@ -2063,7 +2074,7 @@ static ScrDyn *insp_inspect_value(void) {
ScrDyn *set = scr_dyn_new_func(scr_closure_new((void *)members[i], 0), insp_inspect_member_call, 1,
"native:function", members[i]);
scr_dyn_obj_set_accessor(table, members[i], strlen(members[i]), get, set);
table->v.obj.entries[table->v.obj.len - 1].enumerable = false;
scr_dyn_obj_set_attrs_at(table, scr_dyn_obj_count(table) - 1, true, false, true);
}
scr_dyn_release(table);
}
@@ -2100,15 +2111,15 @@ static ScrDyn *insp_user_options(double depth) {
insp_opt_set(options, "getters", scr_dyn_new_bool(false));
insp_opt_set(options, "numericSeparator", scr_dyn_new_bool(false));
if (g_user_options && g_user_options->kind == SCR_DYN_OBJ) {
for (size_t i = 0; i < g_user_options->v.obj.len; i++) {
const ScrDynEntry *ent = &g_user_options->v.obj.entries[i];
if (!ent->enumerable) continue;
ScrDyn *value = scr_dyn_obj_read((ScrDyn *)g_user_options, ent->key, ent->key_len);
for (size_t i = 0; i < scr_dyn_obj_count(g_user_options); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(g_user_options, i);
if (!ent.enumerable) continue;
ScrDyn *value = scr_dyn_obj_read((ScrDyn *)g_user_options, ent.key, ent.key_len);
if (!value) {
scr_dyn_release(options);
return NULL;
}
scr_dyn_obj_set(options, ent->key, ent->key_len, value);
scr_dyn_obj_set(options, ent.key, ent.key_len, value);
}
}
return options;
@@ -2343,9 +2354,12 @@ static ScrStr *scr_insp_dyn_func(ScrDyn *d, double recurse, double depth) {
}
size_t shown = 0;
if (table && table->kind == SCR_DYN_OBJ) {
for (size_t i = 0; i < table->v.obj.len; i++) shown += table->v.obj.entries[i].enumerable;
for (size_t i = 0; i < scr_dyn_obj_count(table); i++) shown += scr_dyn_obj_prop_at(table, i).enumerable;
size_t nsymbols = table->symbol_keys ? table->symbol_keys->v.arr.len : 0;
for (size_t i = 0; i < nsymbols; i++) shown += scr_insp_symbol_entry(table, i) != NULL;
for (size_t i = 0; i < nsymbols; i++) {
ScrDynProp ent;
shown += scr_insp_symbol_entry(table, i, &ent);
}
}
if (shown == 0) {
scr_dyn_release(table);
@@ -2498,7 +2512,10 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
* own `length` comes first, and non-enumerable properties show too. */
const ScrDyn *props = d->v.arr.properties;
size_t nprops = 0;
for (size_t i = 0; props && i < props->v.obj.len; i++) nprops += insp_obj_shows(&props->v.obj.entries[i]);
for (size_t i = 0; props && i < scr_dyn_obj_count(props); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(props, i);
nprops += insp_obj_shows(&ent);
}
/* An instance of a class that extends Array: Node's `Name(length) `
* prefix (scr_arr_copy_metadata names the class). */
const char *cls = props ? props->v.obj.class_name : NULL;
@@ -2562,15 +2579,15 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
scr_str_release(s);
entries++;
}
for (size_t k = 0; props && k < props->v.obj.len; k++) {
const ScrDynEntry *ent = &props->v.obj.entries[k];
if (!insp_obj_shows(ent)) continue;
ScrStr *val = ent->accessor ? insp_accessor_tag(ent) : scr_insp_dyn(ent->value, recurse + 1, depth);
for (size_t k = 0; props && k < scr_dyn_obj_count(props); k++) {
ScrDynProp ent = scr_dyn_obj_prop_at(props, k);
if (!insp_obj_shows(&ent)) continue;
ScrStr *val = ent.accessor ? insp_accessor_tag(&ent) : scr_insp_dyn(ent.value, recurse + 1, depth);
InspBuf eb = {0};
/* formatProperty's hidden key: the key's rendering in brackets. */
if (!ent->enumerable) ib_char(&eb, '[');
insp_key_into(&eb, ent->key, ent->key_len);
if (!ent->enumerable) ib_char(&eb, ']');
if (!ent.enumerable) ib_char(&eb, '[');
insp_key_into(&eb, ent.key, ent.key_len);
if (!ent.enumerable) ib_char(&eb, ']');
ib_cstr(&eb, ": ");
ib_bytes(&eb, val->data, val->len);
scr_str_release(val);
@@ -2753,8 +2770,8 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
scr_str_release(iso);
ScrStr *prefix = ib_take(&head);
bool fields = false;
for (size_t i = 0; i < materialized->v.obj.len && !fields; i++)
fields = materialized->v.obj.entries[i].enumerable;
for (size_t i = 0; i < scr_dyn_obj_count(materialized) && !fields; i++)
fields = scr_dyn_obj_prop_at(materialized, i).enumerable;
if (!fields || recurse > depth) {
scr_dyn_release(materialized);
return prefix;
@@ -3323,12 +3340,12 @@ bool scr_insp_format_read_options(const ScrDyn *options, ScrInspFormatOptions *o
"maxArrayLength", "maxStringLength", "breakLength", "compact", "sorted",
"getters", "numericSeparator", "stylize",
};
for (size_t i = 0; i < options->v.obj.len && !o->user_options; i++) {
const ScrDynEntry *ent = &options->v.obj.entries[i];
if (!ent->enumerable) continue;
for (size_t i = 0; i < scr_dyn_obj_count(options) && !o->user_options; i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(options, i);
if (!ent.enumerable) continue;
bool read = false;
for (size_t k = 0; k < sizeof known / sizeof *known && !read; k++)
read = ent->key_len == strlen(known[k]) && memcmp(ent->key, known[k], ent->key_len) == 0;
read = ent.key_len == strlen(known[k]) && memcmp(ent.key, known[k], ent.key_len) == 0;
if (!read) o->user_options = options;
}
}
@@ -3436,10 +3453,10 @@ static ScrStr *insp_table_cell(ScrDyn *v) {
}
const char *base = v->null_proto ? "[Object: null prototype]" : "Object";
size_t n = 0;
for (size_t i = 0; i < v->v.obj.len; i++) {
ScrDynEntry *ent = &v->v.obj.entries[i];
if (!ent->enumerable) continue;
if (ent->accessor) {
for (size_t i = 0; i < scr_dyn_obj_count(v); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(v, i);
if (!ent.enumerable) continue;
if (ent.accessor) {
static const char message[] = "console.table cells holding accessor properties have no lowering";
scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020");
return scr_str_new("", 0);
@@ -3463,12 +3480,12 @@ static ScrStr *insp_table_cell(ScrDyn *v) {
}
ScrStr *items[2];
size_t count = 0;
for (size_t i = 0; i < v->v.obj.len; i++) {
ScrDynEntry *ent = &v->v.obj.entries[i];
if (!ent->enumerable) continue;
ScrStr *val = scr_insp_dyn(ent->value, 1, 0);
for (size_t i = 0; i < scr_dyn_obj_count(v); i++) {
ScrDynProp ent = scr_dyn_obj_prop_at(v, i);
if (!ent.enumerable) continue;
ScrStr *val = scr_insp_dyn(ent.value, 1, 0);
InspBuf eb = {0};
insp_key_into(&eb, ent->key, ent->key_len);
insp_key_into(&eb, ent.key, ent.key_len);
ib_cstr(&eb, ": ");
ib_str(&eb, val);
scr_str_release(val);
+20 -28
View File
@@ -180,45 +180,37 @@ ScrJsVal scr_js_increment(ScrJsVal value, bool increment);
double scr_js_to_number(ScrJsVal value);
bool scr_js_truthy(ScrJsVal value);
/* ── shapes and inline caches (scr_js_object.c) ─────────────────────────
* A checked object's hidden class (ScrDyn.v.obj.shape) names its key
* sequence: a node of the thread's transition tree, whose depth is the
* entry count and whose last key sits at entry index depth - 1. Shapes are
* per thread and live until the thread's runtime state is torn down; a
* thread's shape count and the key bytes they hold are bounded, and an
* object whose key sequence would need a shape past either bound stays
* unshaped, as do immortal objects, which threads share. The tier
* computes an object's shape on a cache miss; the SCR_JS_TIER build of
* scr_json.c resets it on every change to the key set.
* Attributes stay in the entries and the prototype in the node: cache hits
* test both live, so a shape only ever proves which key sits at which entry
* index and which keys an object lacks.
/* ── inline caches (scr_js_object.c) ───────────────────────────────────
* Caches key on object layouts (scr_dyn.h): a layout-mode object's layout
* names its key sequence, and all its properties are writable data
* properties, so a layout proves which key sits at which slot and which
* keys the object lacks. Dictionary-mode objects (layout NULL), including
* immortal objects, which threads share, never hit. The prototype stays in
* the node: cache hits test it live.
*
* Each property site of compiled dynamic-tier code owns one zeroed,
* thread-local ScrJsIc with static storage: the reset that frees a
* thread's shapes (at thread exit, and when a library session ends) clears
* thread's layouts (at thread exit, and when a library session ends) clears
* every record that holds entries. The code at the site is fixed: it tests
* entry 0 inline and calls a probe for everything else. Probes never run
* user code: they answer from plain data properties or return a miss
* (NULL / false) with nothing done, and the site then runs its generic
* path.
*
* load entry: shape = receiver shape; target = NULL for an own property
* (slot indexes the receiver's entries), else the shape of
* `holder`, the receiver's prototype, whose entry `slot` holds it.
* store entry: shape = receiver shape before the store; target == shape
* replaces entry `slot`; any other target adds the key as entry
* `slot` and moves the receiver to `target`. An add requires the
* receiver's prototype to be `holder` (NULL or an object whose
* shape is holder_shape and that lacks the key).
* keyed entry: slot only; a hit compares the key bytes stored there. */
typedef struct ScrJsShape ScrJsShape;
* load entry: layout = receiver layout; target = NULL for an own
* property (slot indexes the receiver's slots), else the layout of
* `holder`, the receiver's prototype, whose slot `slot` holds it.
* store entry: layout = receiver layout before the store; target ==
* layout replaces slot `slot`; any other target adds the key as
* slot `slot` and moves the receiver to `target`. An add requires
* the receiver's prototype to be `holder` (NULL or an object whose
* layout is holder_layout and that lacks the key).
* keyed entry: slot only; a hit compares the key stored there. */
typedef struct ScrJsIcEntry {
const ScrJsShape *shape;
const ScrJsShape *target;
const ScrLayout *layout;
const ScrLayout *target;
const ScrDyn *holder;
const ScrJsShape *holder_shape;
const ScrLayout *holder_layout;
size_t slot;
} ScrJsIcEntry;
+74 -223
View File
@@ -1,19 +1,15 @@
/* scriptc runtime — the dynamic tier's hidden classes and inline caches
* (scr_js.h; two-tier design stage S4).
/* scriptc runtime — the dynamic tier's inline caches (scr_js.h; two-tier
* design stage S4).
*
* Shapes are attached to the existing checked objects (ScrDyn OBJ nodes)
* rather than replacing their entry storage: static code and the shared
* runtime read those entries in place, so a second object representation
* would have to be understood or converted at every boundary. A shape is a
* node of the thread's transition tree over key sequences; an object's
* shape proves which key sits at each entry index and which keys the
* object lacks. Everything else a cache hit depends on is tested live: the
* node kind, the accessor and writable bits of the entry it uses, and the
* prototype pointer.
* Caches key on object layouts (scr_dyn.h): a layout-mode object's layout
* proves which key sits at each slot and which keys the object lacks, and
* every property of such an object is a writable data property, so a hit
* tests the layout word and, for prototype hits, the prototype pointer.
* Dictionary-mode objects (layout NULL) never hit and take the generic
* paths.
*
* Linked only into programs with dynamic-tier code (the dynTier runtime
* feature), together with the SCR_JS_TIER build of scr_json.c, which
* resets an object's shape on every change to its key set. */
* feature). */
#include "scr_js.h"
#include "scr_key.h"
@@ -24,61 +20,26 @@
* backend/llvm/js-ic.ts). */
#if SCR_JS_IMMEDIATES
_Static_assert(offsetof(ScrDyn, kind) == 8, "kind offset");
_Static_assert(offsetof(ScrDyn, v.obj.len) == 16 && offsetof(ScrDyn, v.obj.entries) == 32,
"object entry offsets");
_Static_assert(offsetof(ScrDyn, v.obj.len) == 16 && offsetof(ScrDyn, v.obj.slots) == 32 &&
offsetof(ScrDyn, v.obj.layout) == 80, "object slot offsets");
_Static_assert(offsetof(ScrDyn, v.arr.len) == 16 && offsetof(ScrDyn, v.arr.items) == 32 &&
offsetof(ScrDyn, v.arr.presence) == 56, "array element offsets");
_Static_assert(offsetof(ScrDyn, prototype) == 96, "prototype offset");
_Static_assert(sizeof(ScrDynEntry) == 48 && offsetof(ScrDynEntry, value) == 16 &&
offsetof(ScrDynEntry, writable) == 41, "entry layout");
_Static_assert(sizeof(ScrJsIcEntry) == 40 && sizeof(ScrJsIc) == 168, "cache layout");
#endif
/* Objects with more entries stay unshaped (dictionary mode): their reads
* take the generic path and its hash index. */
#define SCR_JS_SHAPE_MAX 64
/* Shapes live until the thread exits or a library session ends, so a thread
* creates at most this many, holding at most this many key bytes. Once
* either budget is spent, objects whose key sequence has no shape yet stay
* unshaped; shapes that exist keep serving. */
#define SCR_JS_SHAPE_BUDGET ((size_t)1 << 16)
#define SCR_JS_SHAPE_KEY_BYTES_BUDGET ((size_t)4 << 20)
struct ScrJsShape {
const ScrJsShape *parent;
char *key; /* one reference to shared key bytes (scr_json.c) */
size_t key_len;
uint64_t hash;
uint32_t count; /* entries: the depth in the tree */
ScrJsShape *all_next;
};
/* The root is the empty key sequence. Transitions live in one open
* addressing table per thread keyed by (parent, key). */
static SCR_TL ScrJsShape scr_js_root;
static SCR_TL ScrJsShape *scr_js_all_shapes;
static SCR_TL ScrJsShape **scr_js_transitions;
static SCR_TL size_t scr_js_transition_mask; /* capacity - 1; 0 when absent */
static SCR_TL size_t scr_js_transition_count;
static SCR_TL size_t scr_js_shape_key_bytes;
static SCR_TL bool scr_js_shapes_registered;
/* The cache records that hold entries. Entries name shapes and prototype
* objects, so the reset that frees the shapes also clears these records: a
* library's next session runs the same sites with the same records. */
/* The cache records that hold entries. Entries name layouts and prototype
* objects, so the reset of a thread's runtime state (which frees its
* layouts) also clears these records: a library's next session runs the
* same sites with the same records. */
static SCR_TL ScrJsIc **scr_js_filled_ics;
static SCR_TL size_t scr_js_filled_count;
static SCR_TL size_t scr_js_filled_capacity;
/* The shape word of an object whose key sequence has no shape within the
* budgets. No cache entry names it, so its accesses take the probes,
* which answer with a miss until the key set changes. */
static ScrJsShape scr_js_unshaped;
static SCR_TL bool scr_js_ics_registered;
/* The second-level cache of load sites whose own record is full: one entry
* per (receiver shape, key bytes) slot, filled by the probe and checked like
* a site entry. It names shapes, so the shape reset clears it. */
* per (receiver layout, key bytes) slot, filled by the probe and checked
* like a site entry. It names layouts, so the reset clears it. */
#define SCR_JS_SHARED_LOADS 2048
typedef struct ScrJsSharedLoad {
const char *key;
@@ -86,7 +47,7 @@ typedef struct ScrJsSharedLoad {
} ScrJsSharedLoad;
static SCR_TL ScrJsSharedLoad *scr_js_shared_loads;
static void scr_js_shapes_reset(void) {
static void scr_js_ics_reset(void) {
for (size_t i = 0; i < scr_js_filled_count; i++)
memset(scr_js_filled_ics[i], 0, sizeof *scr_js_filled_ics[i]);
free(scr_js_shared_loads);
@@ -95,130 +56,33 @@ static void scr_js_shapes_reset(void) {
scr_js_filled_ics = NULL;
scr_js_filled_count = 0;
scr_js_filled_capacity = 0;
while (scr_js_all_shapes) {
ScrJsShape *shape = scr_js_all_shapes;
scr_js_all_shapes = shape->all_next;
scr_dyn_tier_key_release(shape->key, shape->key_len);
free(shape);
}
free(scr_js_transitions);
scr_js_transitions = NULL;
scr_js_transition_mask = 0;
scr_js_transition_count = 0;
scr_js_shape_key_bytes = 0;
scr_js_shapes_registered = false;
scr_js_ics_registered = false;
}
static size_t scr_js_transition_slot(const ScrJsShape *parent, uint64_t hash) {
return (size_t)scr_key_mix(hash ^ (uint64_t)(uintptr_t)parent);
}
static void scr_js_transition_insert(ScrJsShape *child) {
size_t i = scr_js_transition_slot(child->parent, child->hash) & scr_js_transition_mask;
while (scr_js_transitions[i]) i = (i + 1) & scr_js_transition_mask;
scr_js_transitions[i] = child;
}
static void scr_js_transitions_grow(void) {
size_t old_cap = scr_js_transition_mask ? scr_js_transition_mask + 1 : 0;
size_t cap = old_cap ? old_cap * 2 : 256;
ScrJsShape **old = scr_js_transitions;
scr_js_transitions = calloc(cap, sizeof *scr_js_transitions);
if (!scr_js_transitions) scr_trap("scriptc: out of memory\n");
scr_js_transition_mask = cap - 1;
for (size_t i = 0; i < old_cap; i++)
if (old[i]) scr_js_transition_insert(old[i]);
free(old);
}
/* The child of `parent` for appending `key` (borrowed bytes), created on
* first use; NULL when creating it would exceed a budget. */
static const ScrJsShape *scr_js_transition(const ScrJsShape *parent, const char *key, size_t key_len) {
uint64_t hash = scr_key_hash(key, key_len);
if (scr_js_transition_mask) {
size_t i = scr_js_transition_slot(parent, hash) & scr_js_transition_mask;
for (ScrJsShape *child; (child = scr_js_transitions[i]); i = (i + 1) & scr_js_transition_mask) {
if (child->parent == parent && child->hash == hash && child->key_len == key_len &&
scr_key_equal(child->key, key, key_len))
return child;
}
}
if (scr_js_transition_count >= SCR_JS_SHAPE_BUDGET ||
key_len > SCR_JS_SHAPE_KEY_BYTES_BUDGET - scr_js_shape_key_bytes)
return NULL;
if ((scr_js_transition_count + 1) * 2 > (scr_js_transition_mask ? scr_js_transition_mask + 1 : 0))
scr_js_transitions_grow();
ScrJsShape *child = malloc(sizeof *child);
if (!child) scr_trap("scriptc: out of memory\n");
*child = (ScrJsShape){parent, scr_dyn_tier_key_copy(key, key_len), key_len, hash, parent->count + 1,
scr_js_all_shapes};
scr_js_all_shapes = child;
if (!scr_js_shapes_registered) {
scr_js_shapes_registered = true;
scr_atexit(scr_js_shapes_reset);
}
scr_js_transition_insert(child);
scr_js_transition_count++;
scr_js_shape_key_bytes += key_len;
return child;
}
/* An object's shape, computed from its entries when it has none; NULL for
* objects that stay unshaped (other kinds, immortal objects, the builtin
* Object.prototype, dictionary-sized objects, key sequences past the
* budgets). Immortal objects (rc == SIZE_MAX: published graphs, which
* threads share by pointer) are never shaped: shapes belong to one thread
* and die with it, and no thread writes an immortal object's header. */
#ifdef SCR_JS_VERIFY
/* Debugging aid (build the runtime with -DSCR_JS_VERIFY and compile with
* SCRIPTC_JS_IC_WAYS=0 so every access probes): traps when an object's
* shape no longer names its key sequence, i.e. when some mutation of the
* key set missed its SCR_JS_SHAPE_RESET. */
static void scr_js_verify(const ScrDyn *object, const ScrJsShape *shape) {
size_t len = object->v.obj.len;
if (shape->count != len) scr_trap("scriptc: stale shape (entry count)\n");
for (const ScrJsShape *s = shape; s->parent; s = s->parent) {
const ScrDynEntry *entry = &object->v.obj.entries[s->count - 1];
if (entry->key_len != s->key_len || !scr_key_equal(entry->key, s->key, s->key_len))
scr_trap("scriptc: stale shape (key)\n");
}
}
#endif
static const ScrJsShape *scr_js_shape_of(ScrDyn *object) {
/* An object's layout, or NULL for objects caches leave alone: other kinds,
* dictionary-mode objects (immortal objects, which threads share, are
* always in dictionary mode), and the builtin Object.prototype. */
static inline const ScrLayout *scr_js_layout_of(const ScrDyn *object) {
if (object->kind != SCR_DYN_OBJ || object->rc == SIZE_MAX) return NULL;
const ScrJsShape *shape = object->v.obj.shape;
if (shape == &scr_js_unshaped) return NULL;
#ifdef SCR_JS_VERIFY
if (shape) scr_js_verify(object, shape);
#endif
if (shape) return shape;
size_t len = object->v.obj.len;
if (len > SCR_JS_SHAPE_MAX || object == scr_dyn_tier_object_prototype()) return NULL;
shape = &scr_js_root;
for (size_t i = 0; i < len; i++) {
ScrDynEntry *entry = &object->v.obj.entries[i];
shape = scr_js_transition(shape, entry->key, entry->key_len);
if (!shape) {
object->v.obj.shape = &scr_js_unshaped;
return NULL;
}
}
object->v.obj.shape = (ScrJsShape *)shape;
return shape;
const ScrLayout *layout = object->v.obj.layout;
if (!layout || object == scr_dyn_tier_object_prototype()) return NULL;
return layout;
}
/* The entry index of `key` in an object of shape `shape`, or SIZE_MAX. */
static size_t scr_js_shape_find(const ScrJsShape *shape, const char *key, size_t key_len) {
for (; shape->parent; shape = shape->parent)
if (shape->key_len == key_len && scr_key_equal(shape->key, key, key_len)) return shape->count - 1;
return SIZE_MAX;
/* The slot of `key` in an object of layout `layout`, or SIZE_MAX. */
static size_t scr_js_layout_find(const ScrLayout *layout, const char *key, size_t key_len) {
uint32_t slot = scr_layout_find(layout, key, key_len);
return slot == UINT32_MAX ? SIZE_MAX : slot;
}
/* Fills a cache entry: `stale` (a matching entry whose hit failed) when
* there is one, else a free or replaceable one. */
static void scr_js_ic_add(ScrJsIc *ic, uint32_t stale, ScrJsIcEntry entry) {
if (ic->count == 0) {
if (!scr_js_ics_registered) {
scr_js_ics_registered = true;
scr_atexit(scr_js_ics_reset);
}
if (scr_js_filled_count == scr_js_filled_capacity) {
size_t capacity = scr_js_filled_capacity ? scr_js_filled_capacity * 2 : 64;
ScrJsIc **grown = realloc(scr_js_filled_ics, capacity * sizeof *grown);
@@ -243,26 +107,25 @@ static void scr_js_ic_add(ScrJsIc *ic, uint32_t stale, ScrJsIcEntry entry) {
/* ── loads ────────────────────────────────────────────────────────────── */
/* The cached answer of an entry whose receiver shape matched, or NULL. */
/* The cached answer of an entry whose receiver layout matched, or NULL. */
static ScrDyn *scr_js_load_hit(ScrDyn *receiver, const ScrJsIcEntry *entry) {
const ScrDyn *holder = receiver;
if (entry->target) {
holder = entry->holder;
if (receiver->prototype != holder || holder->kind != SCR_DYN_OBJ ||
holder->v.obj.shape != entry->target)
holder->v.obj.layout != entry->target)
return NULL;
}
const ScrDynEntry *found = &holder->v.obj.entries[entry->slot];
return found->accessor ? NULL : scr_dyn_retain(found->value);
return scr_dyn_retain(holder->v.obj.slots[entry->slot]);
}
/* An absent entry: its target is this tag and its holder is a placeholder
* that is never a prototype, so the inline test of entry 0 in compiled code
* (a prototype hit needs the receiver's prototype to be the holder) always
* sends such a receiver to the probe. Its holder_shape and slot name the one
* prototype the read walked past (shape and object), or NULL and 0 when the
* receiver had no prototype. */
static const ScrJsShape scr_js_absent;
* sends such a receiver to the probe. Its holder_layout and slot name the
* one prototype the read walked past (layout and object), or NULL and 0 when
* the receiver had no prototype. */
static const ScrLayout scr_js_absent;
static const ScrDyn scr_js_absent_holder;
/* Keys whose read on a plain object is not just its [[Get]]: the members of
@@ -288,7 +151,7 @@ static bool scr_js_absent_cacheable(const ScrStr *key) {
return true;
}
static ScrJsSharedLoad *scr_js_shared_load(const ScrJsShape *shape, const ScrStr *key) {
static ScrJsSharedLoad *scr_js_shared_load(const ScrLayout *shape, const ScrStr *key) {
if (!scr_js_shared_loads) {
scr_js_shared_loads = calloc(SCR_JS_SHARED_LOADS, sizeof *scr_js_shared_loads);
if (!scr_js_shared_loads) scr_trap("scriptc: out of memory\n");
@@ -305,7 +168,7 @@ static bool scr_js_absent_hit(const ScrDyn *receiver, const ScrJsIcEntry *entry)
const ScrDyn *walked = (const ScrDyn *)entry->slot;
if (!walked) return prototype == NULL;
return prototype == walked && walked->kind == SCR_DYN_OBJ &&
walked->v.obj.shape == entry->holder_shape && walked->prototype == NULL;
walked->v.obj.layout == entry->holder_layout && walked->prototype == NULL;
}
/* The answer of a matching entry for `receiver`, or NULL when the entry no
@@ -344,11 +207,11 @@ static void scr_js_load_fill(ScrJsIc *ic, uint32_t stale, ScrJsSharedLoad *share
* methods to the slow path, which names the missing function. */
static __attribute__((noinline)) ScrDyn *scr_js_probe_load(ScrDyn *receiver, const ScrStr *key,
ScrJsIc *ic, bool absent) {
const ScrJsShape *shape = scr_js_shape_of(receiver);
const ScrLayout *shape = scr_js_layout_of(receiver);
if (!shape) return NULL;
uint32_t stale = UINT32_MAX;
for (uint32_t i = 0; i < ic->count; i++) {
if (ic->entry[i].shape != shape) continue;
if (ic->entry[i].layout != shape) continue;
ScrDyn *value = scr_js_entry_answer(receiver, &ic->entry[i]);
if (value) return value;
if (stale == UINT32_MAX) stale = i;
@@ -356,18 +219,16 @@ static __attribute__((noinline)) ScrDyn *scr_js_probe_load(ScrDyn *receiver, con
ScrJsSharedLoad *shared = NULL;
if (ic->count == SCR_JS_IC_WAYS) {
shared = scr_js_shared_load(shape, key);
if (shared->key == key->data && shared->entry.shape == shape &&
if (shared->key == key->data && shared->entry.layout == shape &&
(absent || shared->entry.target != &scr_js_absent)) {
ScrDyn *value = scr_js_entry_answer(receiver, &shared->entry);
if (value) return value;
}
}
size_t slot = scr_js_shape_find(shape, key->data, key->len);
size_t slot = scr_js_layout_find(shape, key->data, key->len);
if (slot != SIZE_MAX) {
const ScrDynEntry *found = &receiver->v.obj.entries[slot];
if (found->accessor) return NULL;
scr_js_load_fill(ic, stale, shared, key, (ScrJsIcEntry){shape, NULL, NULL, NULL, slot});
return scr_dyn_retain(found->value);
return scr_dyn_retain(receiver->v.obj.slots[slot]);
}
absent = absent && scr_js_absent_cacheable(key);
/* One prototype link: an ordinary object other than the builtin
@@ -379,9 +240,9 @@ static __attribute__((noinline)) ScrDyn *scr_js_probe_load(ScrDyn *receiver, con
(ScrJsIcEntry){shape, &scr_js_absent, &scr_js_absent_holder, NULL, 0});
return scr_dyn_retain(scr_dyn_undefined());
}
const ScrJsShape *holder_shape = scr_js_shape_of(holder);
const ScrLayout *holder_shape = scr_js_layout_of(holder);
if (!holder_shape) return NULL;
slot = scr_js_shape_find(holder_shape, key->data, key->len);
slot = scr_js_layout_find(holder_shape, key->data, key->len);
if (slot == SIZE_MAX) {
if (!absent || holder->prototype != NULL) return NULL;
scr_js_load_fill(ic, stale, shared, key,
@@ -389,13 +250,11 @@ static __attribute__((noinline)) ScrDyn *scr_js_probe_load(ScrDyn *receiver, con
(size_t)holder});
return scr_dyn_retain(scr_dyn_undefined());
}
const ScrDynEntry *found = &holder->v.obj.entries[slot];
if (found->accessor) return NULL;
scr_js_load_fill(ic, stale, shared, key, (ScrJsIcEntry){shape, holder_shape, holder, NULL, slot});
return scr_dyn_retain(found->value);
return scr_dyn_retain(holder->v.obj.slots[slot]);
}
/* Receivers other than ordinary objects have no shape: they miss before
/* Receivers other than ordinary objects have no layout: they miss before
* the probe sets up its frame. */
ScrDyn *scr_js_load_probe(ScrDyn *receiver, const ScrStr *key, ScrJsIc *ic) {
if (receiver->kind != SCR_DYN_OBJ) return NULL;
@@ -427,62 +286,56 @@ static bool scr_js_error_synced_key(const ScrStr *key) {
}
static bool scr_js_replace(ScrDyn *receiver, size_t slot, ScrDyn *value) {
ScrDynEntry *found = &receiver->v.obj.entries[slot];
if (found->accessor || !found->writable || found->fenced) return false;
scr_dyn_region_store(receiver, value);
ScrDyn *old = found->value;
found->value = scr_dyn_retain(value);
ScrDyn **found = &receiver->v.obj.slots[slot];
ScrDyn *old = *found;
*found = scr_dyn_retain(value);
scr_dyn_release(old);
return true;
}
/* Whether an add may skip the generic path's prototype-chain walk: the
* chain is empty, or one ordinary object that lacks the key and ends it. */
static bool scr_js_add_chain(const ScrDyn *prototype, const ScrJsShape *prototype_shape,
static bool scr_js_add_chain(const ScrDyn *prototype, const ScrLayout *prototype_shape,
const ScrStr *key) {
if (!prototype) return true;
return prototype_shape && !prototype->prototype &&
scr_js_shape_find(prototype_shape, key->data, key->len) == SIZE_MAX;
scr_js_layout_find(prototype_shape, key->data, key->len) == SIZE_MAX;
}
static bool scr_js_append_shaped(ScrDyn *receiver, const ScrJsShape *target, ScrDyn *value) {
static bool scr_js_append_shaped(ScrDyn *receiver, const ScrLayout *target, ScrDyn *value) {
if (receiver->non_extensible) return false;
scr_dyn_tier_append(receiver, target->key, target->key_len, scr_dyn_retain(value));
receiver->v.obj.shape = (ScrJsShape *)target;
scr_dyn_tier_append(receiver, target, scr_dyn_retain(value));
return true;
}
bool scr_js_store_probe(ScrDyn *receiver, const ScrStr *key, ScrDyn *value, ScrJsIc *ic) {
const ScrJsShape *shape = scr_js_shape_of(receiver);
const ScrLayout *shape = scr_js_layout_of(receiver);
if (!shape || scr_js_error_synced_key(key)) return false;
uint32_t stale = UINT32_MAX;
for (uint32_t i = 0; i < ic->count; i++) {
const ScrJsIcEntry *entry = &ic->entry[i];
if (entry->shape != shape) continue;
if (entry->layout != shape) continue;
if (entry->target == shape) {
if (scr_js_replace(receiver, entry->slot, value)) return true;
} else if (receiver->prototype == entry->holder &&
(!entry->holder || (entry->holder->kind == SCR_DYN_OBJ &&
entry->holder->v.obj.shape == entry->holder_shape &&
entry->holder->v.obj.layout == entry->holder_layout &&
!entry->holder->prototype))) {
if (scr_js_append_shaped(receiver, entry->target, value)) return true;
}
stale = i;
break;
}
size_t slot = scr_js_shape_find(shape, key->data, key->len);
size_t slot = scr_js_layout_find(shape, key->data, key->len);
if (slot != SIZE_MAX) {
const ScrDynEntry *found = &receiver->v.obj.entries[slot];
if (found->accessor || !found->writable || found->fenced) return false;
scr_js_ic_add(ic, stale, (ScrJsIcEntry){shape, shape, NULL, NULL, slot});
return scr_js_replace(receiver, slot, value);
}
ScrDyn *prototype = receiver->prototype;
const ScrJsShape *prototype_shape = prototype ? scr_js_shape_of(prototype) : NULL;
if (!scr_js_add_chain(prototype, prototype_shape, key) || receiver->non_extensible ||
shape->count >= SCR_JS_SHAPE_MAX)
return false;
const ScrJsShape *target = scr_js_transition(shape, key->data, key->len);
const ScrLayout *prototype_shape = prototype ? scr_js_layout_of(prototype) : NULL;
if (!scr_js_add_chain(prototype, prototype_shape, key) || receiver->non_extensible) return false;
const ScrLayout *target = scr_layout_add(shape, key->data, key->len);
if (!target) return false;
scr_js_ic_add(ic, stale, (ScrJsIcEntry){shape, target, prototype, prototype_shape, target->count - 1});
return scr_js_append_shaped(receiver, target, value);
@@ -505,20 +358,18 @@ ScrDyn *scr_js_keyed_probe(ScrDyn *receiver, const ScrDyn *key, ScrJsIc *ic) {
if (key->kind == SCR_DYN_NUM) return scr_js_index_probe(receiver, key->v.num);
if (key->kind != SCR_DYN_STR || receiver->kind != SCR_DYN_OBJ) return NULL;
const ScrStr *name = key->v.str;
const ScrLayout *shape = scr_js_layout_of(receiver);
if (!shape) return NULL;
size_t len = receiver->v.obj.len;
for (uint32_t i = 0; i < ic->count; i++) {
size_t slot = ic->entry[i].slot;
if (slot >= len) continue;
const ScrDynEntry *found = &receiver->v.obj.entries[slot];
if (found->key_len == name->len && scr_key_equal(found->key, name->data, name->len))
return found->accessor ? NULL : scr_dyn_retain(found->value);
const ScrLayoutKey *found = &shape->keys[slot];
if (found->len == name->len && scr_key_equal(found->key, name->data, name->len))
return scr_dyn_retain(receiver->v.obj.slots[slot]);
}
const ScrJsShape *shape = scr_js_shape_of(receiver);
if (!shape) return NULL;
size_t slot = scr_js_shape_find(shape, name->data, name->len);
size_t slot = scr_js_layout_find(shape, name->data, name->len);
if (slot == SIZE_MAX) return NULL;
const ScrDynEntry *found = &receiver->v.obj.entries[slot];
if (found->accessor) return NULL;
scr_js_ic_add(ic, UINT32_MAX, (ScrJsIcEntry){NULL, NULL, NULL, NULL, slot});
return scr_dyn_retain(found->value);
return scr_dyn_retain(receiver->v.obj.slots[slot]);
}
+6 -5
View File
@@ -5,7 +5,7 @@
#include <stdlib.h>
#include <string.h>
/* Mirrors scr_js_object.c's budgets. */
/* Mirrors scr_json.c's layout budgets. */
#define SHAPE_BUDGET ((size_t)1 << 16)
#define KEY_BYTES_BUDGET ((size_t)4 << 20)
@@ -63,7 +63,8 @@ static void budget_bounds_shapes(size_t key_len, size_t budget) {
scr_str_release(key);
scr_dyn_release(object);
}
assert(shaped == budget);
/* Objects the earlier checks built hold a few layouts already. */
assert(shaped <= budget && shaped + 64 > budget);
/* A key sequence shaped before the budget ran out. */
ScrStr *first = key_of(bytes, key_len, 0);
@@ -73,7 +74,7 @@ static void budget_bounds_shapes(size_t key_len, size_t budget) {
/* A store probe that would add a new shape leaves the add to the generic
* store; one that replaces through an existing shape still runs. */
memset(&store_ic, 0, sizeof store_ic);
ScrStr *added = scr_str_new("added", 5);
ScrStr *added = key_of(bytes, key_len, budget + 1000);
ScrDyn *value = scr_dyn_new_num(8);
assert(!scr_js_store_probe(object, added, value, &store_ic));
assert(object->v.obj.len == 1);
@@ -94,11 +95,11 @@ static void immortal_objects_stay_unshaped(void) {
ScrDyn *object = object_with(key, 3);
object->rc = SIZE_MAX;
assert(!probe_reads(object, key, 3));
assert(object->v.obj.shape == NULL);
assert(object->v.obj.len == 1);
memset(&store_ic, 0, sizeof store_ic);
ScrDyn *value = scr_dyn_new_num(4);
assert(!scr_js_store_probe(object, key, value, &store_ic));
assert(object->v.obj.shape == NULL);
assert(object->v.obj.len == 1);
scr_dyn_release(value);
scr_str_release(key);
}
+1 -1
View File
@@ -7,7 +7,7 @@ import { expect, test } from "vitest";
const exec = promisify(execFile);
test("dynamic-tier shapes stay within the shape and key-byte budgets, and absent reads are cached", async () => {
test("layouts stay within the layout and key-byte budgets, and dynamic-tier absent reads are cached", async () => {
const scratch = await mkdtemp(join(tmpdir(), "scriptc-js-object-"));
const src = import.meta.dirname;
const binary = join(scratch, "js-object");
File diff suppressed because it is too large Load Diff
+11 -4
View File
@@ -10,6 +10,7 @@
#ifdef SCR_RC_AUDIT
long scr_str_live_count(void);
long scr_dyn_key_live_count(void);
void scr_layout_reset_idle(void);
#endif
static void put(ScrDyn *map, ScrDyn *key, ScrDyn *value) {
@@ -167,7 +168,7 @@ static void checked_storage(void) {
char key[16];
int length = snprintf(key, sizeof key, "key-%d", i);
scr_dyn_obj_set(object, key, (size_t)length, scr_dyn_new_num(i));
assert((object->v.obj.index != NULL) == (i >= 31));
assert((object->v.obj.index != NULL) == (i >= (int)SCR_LAYOUT_MAX_KEYS));
}
for (int i = 0; i < 129; i++) {
char key[16];
@@ -229,6 +230,8 @@ static void checked_storage(void) {
static void shared_property_keys(void) {
#ifdef SCR_RC_AUDIT
/* Layouts hold key references until the thread's state is torn down. */
scr_layout_reset_idle();
long before = scr_dyn_key_live_count();
#endif
/* Distinct objects share bytes, not values or property attributes. Key
@@ -238,7 +241,8 @@ static void shared_property_keys(void) {
char key[] = "shared\0name";
scr_dyn_obj_set(first, key, sizeof key - 1, scr_dyn_new_num(1));
scr_dyn_obj_set(second, key, sizeof key - 1, scr_dyn_new_num(2));
assert(first->v.obj.entries[0].key == second->v.obj.entries[0].key);
size_t first_len, second_len;
assert(scr_dyn_obj_key_at(first, 0, &first_len) == scr_dyn_obj_key_at(second, 0, &second_len));
key[0] = 'x';
ScrStr *name = scr_str_new("shared\0name", sizeof key - 1);
scr_dyn_key_delete(first, name, true);
@@ -269,6 +273,7 @@ static void shared_property_keys(void) {
}
}
#ifdef SCR_RC_AUDIT
scr_layout_reset_idle();
assert(scr_dyn_key_live_count() == before);
#endif
}
@@ -651,8 +656,10 @@ static void json_schema_parse(void) {
static const char *const order[] = {"tags", "ok", "extra", "qty", "sku"};
assert(view->kind == SCR_DYN_OBJ && view->v.obj.len == 5);
for (size_t i = 0; i < 5; i++) {
assert(view->v.obj.entries[i].key_len == strlen(order[i]));
assert(memcmp(view->v.obj.entries[i].key, order[i], strlen(order[i])) == 0);
size_t key_len;
const char *key = scr_dyn_obj_key_at(view, i, &key_len);
assert(key_len == strlen(order[i]));
assert(memcmp(key, order[i], strlen(order[i])) == 0);
}
assert(scr_dyn_obj_get(view, "extra", 5)->kind == SCR_DYN_OBJ);
test_item_release(second);
+16 -16
View File
@@ -716,8 +716,9 @@ ScrDyn *scr_process_versions(void) {
scr_dyn_obj_set(scr_versions_object, "openssl", 7, scr_dyn_new_str(openssl));
scr_str_release(node);
scr_str_release(openssl);
for (size_t i = 0; i < scr_versions_object->v.obj.len; i++) {
scr_versions_object->v.obj.entries[i].writable = false;
for (size_t i = 0; i < scr_dyn_obj_count(scr_versions_object); i++) {
ScrDynProp prop = scr_dyn_obj_prop_at(scr_versions_object, i);
scr_dyn_obj_set_attrs_at(scr_versions_object, i, false, prop.enumerable, prop.configurable);
}
scr_atexit(&scr_versions_object_cleanup);
}
@@ -914,8 +915,8 @@ static void scr_env_store(ScrDyn *out, const char *text, size_t ks, size_t ke,
}
static int scr_env_entry_order(const void *left, const void *right) {
const ScrDynEntry *a = *(const ScrDynEntry *const *)left;
const ScrDynEntry *b = *(const ScrDynEntry *const *)right;
const ScrDynProp *a = left;
const ScrDynProp *b = right;
size_t n = a->key_len < b->key_len ? a->key_len : b->key_len;
int cmp = memcmp(a->key, b->key, n);
return cmp ? cmp : (a->key_len > b->key_len) - (a->key_len < b->key_len);
@@ -991,14 +992,14 @@ static ScrDyn *scr_env_parse_content(const ScrStr *input, bool ordinary) {
/* Node's std::map orders UTF-8 keys before installing object properties.
* Ordinary objects ignore string-valued __proto__ assignments; loading
* the native environment retains that name as an ordinary variable. */
size_t count = entries->v.obj.len;
const ScrDynEntry **sorted = count ? malloc(count * sizeof(*sorted)) : NULL;
size_t count = scr_dyn_obj_count(entries);
ScrDynProp *sorted = count ? malloc(count * sizeof(*sorted)) : NULL;
if (count && !sorted) scr_trap("scriptc: out of memory\n");
for (size_t i = 0; i < count; i++) sorted[i] = &entries->v.obj.entries[i];
for (size_t i = 0; i < count; i++) sorted[i] = scr_dyn_obj_prop_at(entries, i);
if (count > 1) qsort(sorted, count, sizeof(*sorted), scr_env_entry_order);
ScrDyn *result = ordinary ? scr_dyn_new_obj() : scr_dyn_new_obj_null_proto();
for (size_t i = 0; i < count; i++) {
const ScrDynEntry *entry = sorted[i];
const ScrDynProp *entry = &sorted[i];
if (ordinary && entry->key_len == 9 && memcmp(entry->key, "__proto__", 9) == 0) continue;
if (!ordinary) {
scr_dyn_obj_set(result, entry->key, entry->key_len, scr_dyn_retain(entry->value));
@@ -1103,15 +1104,15 @@ void scr_process_load_env_file(const ScrDyn *value) {
if (!content) return;
ScrDyn *entries = scr_env_parse_content(content, false);
scr_str_release(content);
for (size_t i = 0; i < entries->v.obj.len; i++) {
const ScrDynEntry *entry = &entries->v.obj.entries[i];
for (size_t i = 0; i < scr_dyn_obj_count(entries); i++) {
ScrDynProp entry = scr_dyn_obj_prop_at(entries, i);
/* Node normalizes each raw sorted entry immediately before checking
* the environment. Distinct invalid byte keys can normalize alike;
* the first absent name wins, rather than the last parsed entry. */
ScrStr *key = scr_str_from_utf8_lossy((const uint8_t *)entry->key, entry->key_len);
ScrStr *key = scr_str_from_utf8_lossy((const uint8_t *)entry.key, entry.key_len);
ScrStr *existing = scr_env_get(key);
if (!existing) {
const ScrStr *raw = entry->value->v.str;
const ScrStr *raw = entry.value->v.str;
ScrStr *value = scr_str_from_utf8_lossy((const uint8_t *)raw->data, raw->len);
scr_env_set(key, value);
scr_str_release(value);
@@ -2810,10 +2811,9 @@ ScrDyn *scr_process_release(void) {
#endif
#undef SCR_RELEASE_FILE
#undef SCR_RELEASE_PREFIX
for (size_t i = 0; i < scr_release_object->v.obj.len; i++) {
scr_release_object->v.obj.entries[i].writable = false;
scr_release_object->v.obj.entries[i].configurable = false;
}
for (size_t i = 0; i < scr_dyn_obj_count(scr_release_object); i++)
scr_dyn_obj_set_attrs_at(scr_release_object, i, false,
scr_dyn_obj_prop_at(scr_release_object, i).enumerable, false);
scr_atexit(&scr_release_object_cleanup);
}
return scr_dyn_retain(scr_release_object);
+15 -13
View File
@@ -636,9 +636,9 @@ static bool scr_publish_container_ok(const ScrDyn *d) {
if (!scr_dyn_is_frozen(d)) return false;
if (d->kind == SCR_DYN_ARR) return d->v.arr.properties == NULL;
if (d->null_proto || d->v.obj.source_identity || d->v.obj.source_access) return false;
for (size_t i = 0; i < d->v.obj.len; i++) {
const ScrDynEntry *e = &d->v.obj.entries[i];
if (e->accessor || !e->enumerable) return false;
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
ScrDynProp e = scr_dyn_obj_prop_at(d, i);
if (e.accessor || !e.enumerable) return false;
}
return true;
}
@@ -666,8 +666,8 @@ static bool scr_publish_collect(ScrPublish *p, ScrDyn *root) {
break;
case SCR_DYN_OBJ:
if (!scr_publish_container_ok(d)) return false;
for (size_t i = 0; i < d->v.obj.len; i++) {
ScrDyn *child = d->v.obj.entries[i].value;
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
ScrDyn *child = scr_dyn_obj_value_at(d, i);
if (child && !scr_publish_seen(p, child)) scr_publish_push(p, child);
}
break;
@@ -702,9 +702,10 @@ static ScrMessage *scr_message_try_publish(const ScrDyn *value) {
} else if (d->rc != SIZE_MAX) {
scr_cyc_on_dead(d); /* leave the candidate and tenured buffers */
d->rc = SIZE_MAX;
/* A shape names a node of this thread's transition tree (scr_js.h),
* which other threads cannot use and which ends with this thread. */
if (d->kind == SCR_DYN_OBJ) d->v.obj.shape = NULL;
/* A layout names a node of this thread's transition tree
* (scr_dyn.h), which other threads cannot use and which ends with
* this thread: the object moves to dictionary mode. */
if (d->kind == SCR_DYN_OBJ) scr_dyn_to_dictionary(d);
#ifdef SCR_RC_AUDIT
scr_dyn_live_forget();
#endif
@@ -1033,14 +1034,15 @@ ScrDyn *scr_message_decode(const ScrMessage *message) {
} else scr_dyn_obj_set(values[i], property->key, property->length, value);
}
if (node->kind == SCR_MESSAGE_ERROR) {
for (size_t j = 0; j < values[i]->v.obj.len; j++) {
ScrDynEntry *entry = &values[i]->v.obj.entries[j];
entry->enumerable = false;
for (size_t j = 0; j < scr_dyn_obj_count(values[i]); j++) {
ScrDynProp entry = scr_dyn_obj_prop_at(values[i], j);
bool enumerable = false;
for (size_t k = 0; k < node->count; k++) {
const ScrMessageProperty *property = &node->properties[k];
if (!property->hidden && property->length == entry->key_len && !memcmp(property->key, entry->key, entry->key_len))
entry->enumerable = true;
if (!property->hidden && property->length == entry.key_len && !memcmp(property->key, entry.key, entry.key_len))
enumerable = true;
}
scr_dyn_obj_set_attrs_at(values[i], j, entry.writable, enumerable, entry.configurable);
}
ScrError *error = scr_errdyn_err_of(values[i]);
scr_error_commit_dyn(error, values[i]);
+5 -2
View File
@@ -225,8 +225,11 @@ static void check_large(void) {
ScrDyn *decoded = scr_message_decode(message);
assert(decoded && !scr_exc_pending());
assert(decoded->v.obj.len == 42);
assert(decoded->v.obj.entries[0].key_len == 5 && !memcmp(decoded->v.obj.entries[0].key, "item0", 5));
assert(decoded->v.obj.entries[41].key_len == 6 && !memcmp(decoded->v.obj.entries[41].key, "nested", 6));
size_t member_len;
const char *member = scr_dyn_obj_key_at(decoded, 0, &member_len);
assert(member_len == 5 && !memcmp(member, "item0", 5));
member = scr_dyn_obj_key_at(decoded, 41, &member_len);
assert(member_len == 6 && !memcmp(member, "nested", 6));
assert(scr_dyn_obj_get(decoded, "nested", 6)->v.num == 42);
ScrDyn *first = scr_dyn_obj_get(decoded, "item0", 5);
for (int i = 0; i < 40; i++) {
+10 -9
View File
@@ -745,9 +745,8 @@ double scr_os_cpus_field(double i, double field) {
static void scr_os_constant(ScrDyn *o, const char *name, double value) {
scr_dyn_obj_set(o, name, strlen(name), scr_dyn_new_num(value));
ScrDynEntry *entry = &o->v.obj.entries[o->v.obj.len - 1];
entry->writable = false;
entry->configurable = false;
size_t last = scr_dyn_obj_count(o) - 1;
scr_dyn_obj_set_attrs_at(o, last, false, scr_dyn_obj_prop_at(o, last).enumerable, false);
}
#define SCR_OS_CONSTANT(o, name) scr_os_constant((o), #name, (double)(name))
@@ -1514,10 +1513,10 @@ ScrDyn *scr_node_constants(const ScrDyn *crypto_table) {
scr_os_signal_constants(o);
scr_fs_constants(o);
if (crypto && crypto->kind == SCR_DYN_OBJ) {
for (size_t i = 0; i < crypto->v.obj.len; i++) {
const ScrDynEntry *entry = &crypto->v.obj.entries[i];
if (entry->accessor || !entry->value) continue;
scr_dyn_obj_set(o, entry->key, entry->key_len, scr_dyn_retain(entry->value));
for (size_t i = 0; i < scr_dyn_obj_count(crypto); i++) {
ScrDynProp entry = scr_dyn_obj_prop_at(crypto, i);
if (entry.accessor || !entry.value) continue;
scr_dyn_obj_set(o, entry.key, entry.key_len, scr_dyn_retain(entry.value));
}
}
scr_dyn_release(crypto);
@@ -1718,10 +1717,12 @@ double scr_util_signal_exit_code(const ScrDyn *code) {
ScrJsonBuf reason;
scr_jb_init(&reason);
scr_jb_puts(&reason, "must be one of: ");
for (size_t i = 0; i < signals->v.obj.len; i++) {
for (size_t i = 0; i < scr_dyn_obj_count(signals); i++) {
if (i > 0) scr_jb_puts(&reason, ", ");
scr_jb_putc(&reason, '\'');
scr_jb_write(&reason, signals->v.obj.entries[i].key, signals->v.obj.entries[i].key_len);
size_t key_len;
const char *key = scr_dyn_obj_key_at(signals, i, &key_len);
scr_jb_write(&reason, key, key_len);
scr_jb_putc(&reason, '\'');
}
ScrStr *text = scr_jb_finish(&reason);
+7 -7
View File
@@ -278,8 +278,8 @@ static bool scr_pub_dyn_ok(const ScrDyn *d, const char **why) {
*why = "Cannot publish an Object object";
return false;
}
for (size_t i = 0; i < d->v.obj.len; i++)
if (d->v.obj.entries[i].accessor) {
for (size_t i = 0; i < scr_dyn_obj_count(d); i++)
if (scr_dyn_obj_prop_at(d, i).accessor) {
*why = "Cannot publish an accessor property";
return false;
}
@@ -325,9 +325,9 @@ void scr_pub_dyn_fn(void *v, ScrPub *ctx) {
case SCR_DYN_ARR:
case SCR_DYN_OBJ: {
if (!scr_pub_dyn_ok(d, &why)) break;
size_t n = d->kind == SCR_DYN_ARR ? d->v.arr.len : d->v.obj.len;
size_t n = d->kind == SCR_DYN_ARR ? d->v.arr.len : scr_dyn_obj_count(d);
for (size_t i = 0; i < n; i++) {
ScrDyn *child = d->kind == SCR_DYN_ARR ? d->v.arr.items[i] : d->v.obj.entries[i].value;
ScrDyn *child = d->kind == SCR_DYN_ARR ? d->v.arr.items[i] : scr_dyn_obj_value_at(d, i);
if (child && child->rc != SIZE_MAX) SCR_PUB_ADD((uintptr_t)child);
}
break;
@@ -481,9 +481,9 @@ void scr_publish(void *root, ScrPubFn fn) {
void *obj = scr_pub_log_obj(&ctx.log[i]);
if (ctx.log[i].obj & SCR_PUB_DYN) {
ScrDyn *d = obj;
/* Other threads share the object; its shape belongs to this one
* (scr_js.h). */
if (d->kind == SCR_DYN_OBJ) d->v.obj.shape = NULL;
/* Other threads share the object; layouts belong to this thread
* (scr_dyn.h), so it moves to dictionary mode. */
if (d->kind == SCR_DYN_OBJ) scr_dyn_to_dictionary(d);
scr_dyn_release(scr_dyn_freeze_data(d));
}
#ifdef SCR_RC_AUDIT
+6 -6
View File
@@ -858,12 +858,12 @@ static void scr_rgn_walk_dyn(ScrRgnWalk *w, ScrDyn *d) {
break;
case SCR_DYN_OBJ:
/* source_identity is owned through its access callback: not walked. */
for (size_t i = 0; i < d->v.obj.len; i++) {
const ScrDynEntry *e = &d->v.obj.entries[i];
if (e->accessor) {
scr_rgn_enter(w, e->getter, SCR_RGN_W_DYN, NULL);
scr_rgn_enter(w, e->setter, SCR_RGN_W_DYN, NULL);
} else scr_rgn_enter(w, e->value, SCR_RGN_W_DYN, NULL);
for (size_t i = 0; i < scr_dyn_obj_count(d); i++) {
ScrDynProp e = scr_dyn_obj_prop_at(d, i);
if (e.accessor) {
scr_rgn_enter(w, e.getter, SCR_RGN_W_DYN, NULL);
scr_rgn_enter(w, e.setter, SCR_RGN_W_DYN, NULL);
} else scr_rgn_enter(w, e.value, SCR_RGN_W_DYN, NULL);
}
break;
case SCR_DYN_FUNC: scr_rgn_enter(w, d->v.fn.clo, SCR_RGN_W_CLOSURE, NULL); break;
+7 -8
View File
@@ -333,7 +333,7 @@ static void scr_dirent_init(void) {
scr_dyn_obj_set(proto, methods[i].name, strlen(methods[i].name),
scr_dyn_new_func(scr_closure_new(NULL, 0), methods[i].thunk, 0, "native:fs.Dirent",
methods[i].name));
proto->v.obj.entries[proto->v.obj.len - 1].enumerable = false;
scr_dyn_obj_set_attrs_at(proto, scr_dyn_obj_count(proto) - 1, true, false, true);
}
ScrStr *desc = scr_str_new("type", 4);
ScrSym *sym = scr_sym_new(desc);
@@ -350,15 +350,14 @@ void scr_dyn_dirent_finish(ScrDyn *obj) {
scr_dyn_region_store(obj, NULL); /* a prototype store (ScrDynRegion) */
scr_dyn_release(obj->prototype);
obj->prototype = scr_dyn_retain(scr_dirent_prototype);
for (size_t i = 0; i < obj->v.obj.len; i++) {
ScrDynEntry *ent = &obj->v.obj.entries[i];
if (ent->key_len != 6 || memcmp(ent->key, "%dtype", 6) != 0) continue;
ent->enumerable = false;
size_t i = scr_dyn_obj_index_of(obj, "%dtype", 6);
if (i != SIZE_MAX) {
ScrDynProp ent = scr_dyn_obj_prop_at(obj, i);
scr_dyn_obj_set_attrs_at(obj, i, ent.writable, false, ent.configurable);
if (!obj->symbol_properties && !obj->symbol_keys)
scr_dyn_symbol_init_own(obj, scr_dirent_type_key, scr_dirent_type_storage, ent->value);
scr_dyn_symbol_init_own(obj, scr_dirent_type_key, scr_dirent_type_storage, ent.value);
else
scr_dyn_symbol_key_set(obj, scr_dirent_type_key, ent->value);
break;
scr_dyn_symbol_key_set(obj, scr_dirent_type_key, ent.value);
}
}
+47 -47
View File
@@ -2597,21 +2597,21 @@ static bool scr_tls_srv_opts_walk_ex(const ScrDyn *opts, const char *api, ScrByt
}
if (!scr_tls_opts_validate(opts)) return false; /* Node's typed errors first */
bool ok = true;
for (size_t i = 0; ok && i < opts->v.obj.len; i++) {
const ScrDynEntry *e = &opts->v.obj.entries[i];
const ScrDyn *v = e->value;
for (size_t i = 0; ok && i < scr_dyn_obj_count(opts); i++) {
ScrDynProp e = scr_dyn_obj_prop_at(opts, i);
const ScrDyn *v = e.value;
if (v->kind == SCR_DYN_UNDEF) continue; /* an undefined member reads as absent, like Node */
if (strcmp(e->key, "cert") == 0 || strcmp(e->key, "key") == 0) {
if (strcmp(e.key, "cert") == 0 || strcmp(e.key, "key") == 0) {
char what[96];
snprintf(what, sizeof what, "a %s '%s' option", api, e->key);
snprintf(what, sizeof what, "a %s '%s' option", api, e.key);
ScrBytes *b = scr_tls_pem_dyn(v, what, false);
if (b == NULL) { ok = false; break; }
ScrBytes **slot = e->key[0] == 'c' ? cert_out : key_out;
ScrBytes **slot = e.key[0] == 'c' ? cert_out : key_out;
scr_bytes_release(*slot);
*slot = b;
continue;
}
if (strcmp(e->key, "ca") == 0) {
if (strcmp(e.key, "ca") == 0) {
/* the server's `ca` verifies client certificates and completes the
* server's own chain (scr_tls_srv_client_auth); a walk without
* client certificates drops it */
@@ -2623,7 +2623,7 @@ static bool scr_tls_srv_opts_walk_ex(const ScrDyn *opts, const char *api, ScrByt
}
continue;
}
if (strcmp(e->key, "requestCert") == 0) {
if (strcmp(e.key, "requestCert") == 0) {
if (auth != NULL) {
auth->request = scr_tls_dyn_truthy(v);
continue;
@@ -2635,22 +2635,22 @@ static bool scr_tls_srv_opts_walk_ex(const ScrDyn *opts, const char *api, ScrByt
}
continue;
}
if (strcmp(e->key, "rejectUnauthorized") == 0) {
if (strcmp(e.key, "rejectUnauthorized") == 0) {
if (auth != NULL) auth->reject = scr_tls_dyn_not_false(v);
continue; /* without client certificates it has no effect */
}
if (server_ok && (strcmp(e->key, "ALPNProtocols") == 0 || strcmp(e->key, "minVersion") == 0 ||
strcmp(e->key, "maxVersion") == 0))
if (server_ok && (strcmp(e.key, "ALPNProtocols") == 0 || strcmp(e.key, "minVersion") == 0 ||
strcmp(e.key, "maxVersion") == 0))
continue;
if (strcmp(e->key, "SNICallback") == 0) {
if (strcmp(e.key, "SNICallback") == 0) {
scr_tls_opt_fence(api, "SNICallback",
"SNICallback lowers only on http2.createSecureServer({ allowHTTP1: true, ... }) with a literal options object");
ok = false;
continue;
}
for (size_t k = 0; SCR_TLS_SRV_FENCED_OPTIONS[k] != NULL; k++) {
if (strcmp(e->key, SCR_TLS_SRV_FENCED_OPTIONS[k]) == 0) {
scr_tls_opt_fence(api, e->key,
if (strcmp(e.key, SCR_TLS_SRV_FENCED_OPTIONS[k]) == 0) {
scr_tls_opt_fence(api, e.key,
"cert and key (PEM strings or Buffers) are the honestly-implemented members of a runtime options record");
ok = false;
break;
@@ -2957,17 +2957,17 @@ static const char *const SCR_TLS_WRAP_FENCED_OPTIONS[] = {
* certificates are not modeled), SNICallback and ALPNCallback fence.
* False with the exception pending. */
static bool scr_tls_wrap_socket_opts_check(const ScrDyn *record, const char *api) {
for (size_t i = 0; record != NULL && i < record->v.obj.len; i++) {
const ScrDynEntry *e = &record->v.obj.entries[i];
if (e->value->kind == SCR_DYN_UNDEF) continue;
if (strcmp(e->key, "requestCert") == 0) {
if (!scr_tls_dyn_truthy(e->value)) continue;
for (size_t i = 0; record != NULL && i < scr_dyn_obj_count(record); i++) {
ScrDynProp e = scr_dyn_obj_prop_at(record, i);
if (e.value->kind == SCR_DYN_UNDEF) continue;
if (strcmp(e.key, "requestCert") == 0) {
if (!scr_tls_dyn_truthy(e.value)) continue;
scr_tls_opt_fence(api, "requestCert",
"requestCert lowers with cert and key options, not beside a secureContext");
return false;
}
if (strcmp(e->key, "SNICallback") == 0 || strcmp(e->key, "ALPNCallback") == 0) {
scr_tls_opt_fence(api, e->key,
if (strcmp(e.key, "SNICallback") == 0 || strcmp(e.key, "ALPNCallback") == 0) {
scr_tls_opt_fence(api, e.key,
"isServer, cert, key, secureContext, ALPNProtocols, minVersion and maxVersion are the implemented members");
return false;
}
@@ -2980,21 +2980,21 @@ ScrNetSocket *scr_tls_wrap_socket(ScrNetSocket *raw, const ScrDyn *opts /*borrow
static const char api[] = "new tls.TLSSocket";
bool is_server = false;
const ScrDyn *record = opts != NULL && opts->kind == SCR_DYN_OBJ ? opts : NULL;
for (size_t i = 0; record != NULL && i < record->v.obj.len; i++) {
const ScrDynEntry *e = &record->v.obj.entries[i];
if (e->value->kind == SCR_DYN_UNDEF) continue;
if (strcmp(e->key, "isServer") == 0) {
is_server = scr_tls_dyn_truthy(e->value);
for (size_t i = 0; record != NULL && i < scr_dyn_obj_count(record); i++) {
ScrDynProp e = scr_dyn_obj_prop_at(record, i);
if (e.value->kind == SCR_DYN_UNDEF) continue;
if (strcmp(e.key, "isServer") == 0) {
is_server = scr_tls_dyn_truthy(e.value);
continue;
}
if (strcmp(e->key, "secureContext") == 0) {
if (strcmp(e.key, "secureContext") == 0) {
scr_tls_opt_fence(api, "secureContext",
"secureContext lowers as a member of an options object literal");
return NULL;
}
for (size_t k = 0; SCR_TLS_WRAP_FENCED_OPTIONS[k] != NULL; k++) {
if (strcmp(e->key, SCR_TLS_WRAP_FENCED_OPTIONS[k]) == 0) {
scr_tls_opt_fence(api, e->key,
if (strcmp(e.key, SCR_TLS_WRAP_FENCED_OPTIONS[k]) == 0) {
scr_tls_opt_fence(api, e.key,
"isServer, cert, key, secureContext, ALPNProtocols, minVersion and maxVersion are the implemented members");
return NULL;
}
@@ -3267,10 +3267,10 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed,
/* createSecureContext's version checks come before its other options
* (Node's SecureContext constructor). */
if (ok && !scr_tls_versions_dyn(opts, &min_version, &max_version)) ok = false;
for (size_t i = 0; ok && i < opts->v.obj.len; i++) {
const ScrDynEntry *e = &opts->v.obj.entries[i];
const ScrDyn *v = e->value;
if (strcmp(e->key, "checkServerIdentity") == 0) {
for (size_t i = 0; ok && i < scr_dyn_obj_count(opts); i++) {
ScrDynProp e = scr_dyn_obj_prop_at(opts, i);
const ScrDyn *v = e.value;
if (strcmp(e.key, "checkServerIdentity") == 0) {
/* Node spreads user options over the defaults, so a PRESENT key
* replaces the builtin verifier even when its value is undefined
* — validateFunction then throws for anything non-callable. A
@@ -3287,7 +3287,7 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed,
continue;
}
if (v->kind == SCR_DYN_UNDEF) continue;
if (strcmp(e->key, "port") == 0) {
if (strcmp(e.key, "port") == 0) {
if (port >= 0) continue; /* the argument form wins, like Node */
if (v->kind == SCR_DYN_NUM) port = v->v.num;
else if (v->kind == SCR_DYN_STR) port = strtod(v->v.str->data, NULL); /* Node coerces numeric strings */
@@ -3297,7 +3297,7 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed,
}
continue;
}
if (strcmp(e->key, "host") == 0) {
if (strcmp(e.key, "host") == 0) {
if (v->kind == SCR_DYN_STR) opt_host = v->v.str;
else {
scr_dyn_arg_type_fail("options.host", "of type string", v);
@@ -3305,34 +3305,34 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed,
}
continue;
}
if (strcmp(e->key, "rejectUnauthorized") == 0) {
if (strcmp(e.key, "rejectUnauthorized") == 0) {
reject = scr_tls_dyn_not_false(v);
continue;
}
if (strcmp(e->key, "ca") == 0) {
if (strcmp(e.key, "ca") == 0) {
scr_bytes_release(ca);
if (!scr_tls_ca_dyn(v, "a tls.connect 'ca' option", &ca)) ok = false;
continue;
}
if (strcmp(e->key, "cert") == 0 || strcmp(e->key, "key") == 0) {
ScrBytes **slot = e->key[0] == 'c' ? &own_cert : &own_key;
if (strcmp(e.key, "cert") == 0 || strcmp(e.key, "key") == 0) {
ScrBytes **slot = e.key[0] == 'c' ? &own_cert : &own_key;
scr_bytes_release(*slot);
*slot = scr_tls_pem_dyn(v, e->key[0] == 'c' ? "a tls.connect 'cert' option" : "a tls.connect 'key' option",
*slot = scr_tls_pem_dyn(v, e.key[0] == 'c' ? "a tls.connect 'cert' option" : "a tls.connect 'key' option",
false);
if (*slot == NULL) ok = false;
continue;
}
if (strcmp(e->key, "ALPNProtocols") == 0) {
if (strcmp(e.key, "ALPNProtocols") == 0) {
alpn_value = v; /* converted after the context options, as TLSSocket does */
continue;
}
if (strcmp(e->key, "minVersion") == 0 || strcmp(e->key, "maxVersion") == 0) continue;
if (strcmp(e->key, "timeout") == 0) {
if (strcmp(e.key, "minVersion") == 0 || strcmp(e.key, "maxVersion") == 0) continue;
if (strcmp(e.key, "timeout") == 0) {
/* `if (options.timeout) tlssock.setTimeout(options.timeout)` */
timeout_value = scr_tls_dyn_truthy(v) ? v : NULL;
continue;
}
if (strcmp(e->key, "socket") == 0) {
if (strcmp(e.key, "socket") == 0) {
if (v->kind == SCR_DYN_HANDLE && v->v.handle.tag == SCR_DYNH_NET_SOCKET) {
wrap = (ScrNetSocket *)v->v.handle.ptr;
} else {
@@ -3341,7 +3341,7 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed,
}
continue;
}
if (strcmp(e->key, "servername") == 0) {
if (strcmp(e.key, "servername") == 0) {
if (v->kind == SCR_DYN_STR) servername = v->v.str;
else {
scr_dyn_arg_type_fail("options.servername", "of type string", v);
@@ -3350,8 +3350,8 @@ static ScrNetSocket *scr_tls_connect_plain(double port, ScrStr *host /*borrowed,
continue;
}
for (size_t k = 0; SCR_TLS_CONNECT_FENCED_OPTIONS[k] != NULL; k++) {
if (strcmp(e->key, SCR_TLS_CONNECT_FENCED_OPTIONS[k]) == 0) {
scr_tls_opt_fence("tls.connect", e->key,
if (strcmp(e.key, SCR_TLS_CONNECT_FENCED_OPTIONS[k]) == 0) {
scr_tls_opt_fence("tls.connect", e.key,
"port/host/socket/rejectUnauthorized/ca/servername/ALPNProtocols/minVersion/maxVersion/timeout are the implemented members of a runtime options record");
ok = false;
break;
+23 -22
View File
@@ -26,15 +26,15 @@ static bool util_deep_equal(const ScrDyn *, const ScrDyn *, bool, const ScrDeepF
static size_t util_deep_key_count(const ScrDyn *table) {
size_t count = 0;
if (table) for (size_t i = 0; i < table->v.obj.len; i++) count += table->v.obj.entries[i].enumerable;
if (table) for (size_t i = 0; i < scr_dyn_obj_count(table); i++) count += scr_dyn_obj_prop_at(table, i).enumerable;
return count;
}
static bool util_deep_same_keys(const ScrDyn *a, const ScrDyn *b) {
if (util_deep_key_count(a) != util_deep_key_count(b)) return false;
if (a) for (size_t i = 0; i < a->v.obj.len; i++) {
const ScrDynEntry *e = &a->v.obj.entries[i];
if (e->enumerable && (!b || !scr_dyn_obj_enumerable(b, e->key, e->key_len))) return false;
if (a) for (size_t i = 0; i < scr_dyn_obj_count(a); i++) {
ScrDynProp e = scr_dyn_obj_prop_at(a, i);
if (e.enumerable && (!b || !scr_dyn_obj_enumerable(b, e.key, e.key_len))) return false;
}
return true;
}
@@ -42,14 +42,14 @@ static bool util_deep_same_keys(const ScrDyn *a, const ScrDyn *b) {
static ScrDyn *util_deep_constructor(const ScrDyn *value) {
for (const ScrDyn *p = value; p; p = p->prototype) {
const ScrDyn *table = p->kind == SCR_DYN_OBJ ? p : p->kind == SCR_DYN_ARR ? p->v.arr.properties : NULL;
if (table) for (size_t i = 0; i < table->v.obj.len; i++) {
const ScrDynEntry *e = &table->v.obj.entries[i];
if (e->key_len == 11 && !memcmp(e->key, "constructor", 11)) {
if (e->accessor) { util_deep_refused("native accessor properties"); return NULL; }
if (p == value && e->value->kind == SCR_DYN_FUNC) {
if (table) for (size_t i = 0; i < scr_dyn_obj_count(table); i++) {
ScrDynProp e = scr_dyn_obj_prop_at(table, i);
if (e.key_len == 11 && !memcmp(e.key, "constructor", 11)) {
if (e.accessor) { util_deep_refused("native accessor properties"); return NULL; }
if (p == value && e.value->kind == SCR_DYN_FUNC) {
util_deep_refused("native own constructor functions"); return NULL;
}
return scr_dyn_retain(e->value);
return scr_dyn_retain(e.value);
}
}
if (!p->prototype) {
@@ -77,8 +77,8 @@ static ScrDyn *util_deep_tag(const ScrDyn *value) {
if (!scr_dyn_same_value(keys->v.arr.items[i], key)) continue;
// General native getters retain an explicit boundary, including
// non-enumerable Symbol.toStringTag descriptors read by Node.
for (size_t j = 0; j < p->symbol_properties->v.obj.len; j++) {
if (p->symbol_properties->v.obj.entries[j].accessor) {
for (size_t j = 0; j < scr_dyn_obj_count(p->symbol_properties); j++) {
if (scr_dyn_obj_prop_at(p->symbol_properties, j).accessor) {
scr_dyn_release(key); util_deep_refused("native accessor properties"); return NULL;
}
}
@@ -95,20 +95,21 @@ static bool util_deep_properties(const ScrDyn *a, const ScrDyn *b, bool skip,
const ScrDeepFrame *frame) {
if (util_deep_key_count(a) != util_deep_key_count(b)) return false;
if (!a) return true;
for (size_t i = 0; i < a->v.obj.len; i++) {
const ScrDynEntry *x = &a->v.obj.entries[i];
if (!x->enumerable) continue;
const ScrDynEntry *y = NULL;
if (b) for (size_t j = 0; j < b->v.obj.len; j++) {
const ScrDynEntry *candidate = &b->v.obj.entries[j];
if (candidate->enumerable && candidate->key_len == x->key_len &&
!memcmp(candidate->key, x->key, x->key_len)) { y = candidate; break; }
for (size_t i = 0; i < scr_dyn_obj_count(a); i++) {
ScrDynProp x = scr_dyn_obj_prop_at(a, i);
if (!x.enumerable) continue;
ScrDynProp yp;
const ScrDynProp *y = NULL;
if (b) for (size_t j = 0; j < scr_dyn_obj_count(b); j++) {
ScrDynProp candidate = scr_dyn_obj_prop_at(b, j);
if (candidate.enumerable && candidate.key_len == x.key_len &&
!memcmp(candidate.key, x.key, x.key_len)) { yp = candidate; y = &yp; break; }
}
if (!y) return false;
if (x->accessor || y->accessor) return util_deep_refused("native accessor properties");
if (x.accessor || y->accessor) return util_deep_refused("native accessor properties");
// Materializing a cyclic typed child can refresh its ancestor table.
// Retain the values before that refresh replaces descriptor storage.
ScrDyn *xv = scr_dyn_retain(x->value), *yv = scr_dyn_retain(y->value);
ScrDyn *xv = scr_dyn_retain(x.value), *yv = scr_dyn_retain(y->value);
bool same = util_deep_equal(xv, yv, skip, frame);
scr_dyn_release(xv); scr_dyn_release(yv);
if (!same) return false;
+11 -11
View File
@@ -1385,17 +1385,17 @@ static ScrDyn *scr_worker_new_options(double root, ScrStr *filename, ScrDyn *opt
scr_dyn_arg_type_fail("options", "object", options);
return NULL;
}
for (size_t i = 0; i < options->v.obj.len; i++) {
const ScrDynEntry *entry = &options->v.obj.entries[i];
if (entry->accessor) return scr_worker_refusal("Worker option accessors are not supported yet");
if (entry->key_len == 10 && !memcmp(entry->key, "workerData", 10)) data = entry->value;
else if (entry->key_len == 4 && !memcmp(entry->key, "argv", 4)) argv = entry->value;
else if (entry->key_len == 12 && !memcmp(entry->key, "transferList", 12)) transfer_list = entry->value;
else if (entry->key_len == 14 && !memcmp(entry->key, "resourceLimits", 14)) limits = entry->value;
else if (entry->key_len == 4 && !memcmp(entry->key, "name", 4)) name = entry->value;
else if (entry->key_len == 6 && !memcmp(entry->key, "stdout", 6)) capture |= scr_dyn_truthy(entry->value) ? 1 : 0;
else if (entry->key_len == 6 && !memcmp(entry->key, "stderr", 6)) capture |= scr_dyn_truthy(entry->value) ? 2 : 0;
else if (entry->key_len == 5 && !memcmp(entry->key, "stdin", 5)) capture |= scr_dyn_truthy(entry->value) ? 4 : 0;
for (size_t i = 0; i < scr_dyn_obj_count(options); i++) {
ScrDynProp entry = scr_dyn_obj_prop_at(options, i);
if (entry.accessor) return scr_worker_refusal("Worker option accessors are not supported yet");
if (entry.key_len == 10 && !memcmp(entry.key, "workerData", 10)) data = entry.value;
else if (entry.key_len == 4 && !memcmp(entry.key, "argv", 4)) argv = entry.value;
else if (entry.key_len == 12 && !memcmp(entry.key, "transferList", 12)) transfer_list = entry.value;
else if (entry.key_len == 14 && !memcmp(entry.key, "resourceLimits", 14)) limits = entry.value;
else if (entry.key_len == 4 && !memcmp(entry.key, "name", 4)) name = entry.value;
else if (entry.key_len == 6 && !memcmp(entry.key, "stdout", 6)) capture |= scr_dyn_truthy(entry.value) ? 1 : 0;
else if (entry.key_len == 6 && !memcmp(entry.key, "stderr", 6)) capture |= scr_dyn_truthy(entry.value) ? 2 : 0;
else if (entry.key_len == 5 && !memcmp(entry.key, "stdin", 5)) capture |= scr_dyn_truthy(entry.value) ? 4 : 0;
else return scr_worker_refusal("This Worker constructor option is not supported yet");
}
}
+24
View File
@@ -0,0 +1,24 @@
import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { expect, test } from "vitest";
const src = join(import.meta.dirname, "..", "src");
/* Ordinary objects keep their properties behind the object core: units
* outside it read and adjust them through the scr_dyn.h own-property
* accessors (scr_dyn_obj_count, scr_dyn_obj_prop_at, ...), so the core can
* change how it stores them. */
const CORE = new Set(["scr_json.c", "scr_js_object.c", "scr_dyn.h"]);
test("only the object core reads ordinary objects' storage", () => {
const offenders: string[] = [];
for (const name of readdirSync(src)) {
if (!/\.(c|h)$/.test(name) || name.endsWith(".test.c") || CORE.has(name)) continue;
const lines = readFileSync(join(src, name), "utf8").split("\n");
lines.forEach((line, i) => {
if (/\bv\.obj\.(entries|slots|layout|len|cap|index)\b/.test(line))
offenders.push(`${name}:${i + 1}: ${line.trim()}`);
});
}
expect(offenders).toEqual([]);
});
+4 -4
View File
@@ -571,10 +571,10 @@ static ScrDyn *define_from(ScrDyn *target, const char *key, ScrDyn *value, bool
}
static const ScrDynEntry *entry_of(const ScrDyn *obj, const char *key) {
const ScrDynEntry *entries = scr_dyn_obj_dict_entries(obj);
for (size_t i = 0; i < obj->v.obj.len; i++)
if (obj->v.obj.entries[i].key_len == strlen(key) &&
memcmp(obj->v.obj.entries[i].key, key, strlen(key)) == 0)
return &obj->v.obj.entries[i];
if (entries[i].key_len == strlen(key) && memcmp(entries[i].key, key, strlen(key)) == 0)
return &entries[i];
return NULL;
}
@@ -618,7 +618,7 @@ static void test_copy_property_descriptors(void) {
scr_dyn_obj_set(existing, "label", 5, scr_dyn_new_num(9));
scr_dyn_release(scr_dyn_copy_property_descriptors(existing, bag));
const ScrDynEntry *label = entry_of(existing, "label");
check(!scr_exc_pending() && existing->v.obj.len == 5 && existing->v.obj.entries[0].key_len == 5 &&
check(!scr_exc_pending() && existing->v.obj.len == 5 && scr_dyn_obj_dict_entries(existing)[0].key_len == 5 &&
label->value->v.num == 1,
"copy redefines an existing property in place");