Summary
Repeated Object.defineProperty and k in obj on a wide object (>32 keys) are O(N) per call — O(N²) for build loops. This is the webpack/Babel CJS re-export module shape, and it dominates large-bundle module init under perry.
Repro
const t = {};
for (let i = 0; i < 2400; i++)
Object.defineProperty(t, "g" + i, { enumerable: true, get: () => i });
// perry: 527ms (x3.4 per size doubling — quadratic); node: 0.6ms
Real-world: a single @babel/types/lib/index.js (webpack re-export module: if (k in exports) …; Object.defineProperty(exports, k, {get}) per key) took 292ms vs node's 3ms (94×) inside the pi coding agent's jiti/dist/babel.cjs — the largest single contributor to pi's 3.3s startup under perry (measured by per-module init instrumentation).
Root cause
The define flow runs THREE linear keys_array scans per call (enforce_define_property_invariants→own_key_present, the existing-attrs check→obj_value_has_own_key, ensure_key_in_keys_array's dup check), and the in operator's own-key walk runs a fourth. The [[Set]] fast path solved this long ago with the authoritative KEYS_INDEX sidecar (append-without-scan); the define/in paths never adopted it. Bonus: the sidecar's rebuild skipped SSO inline keys (is_string() is heap-tag-only), so an authoritative miss could duplicate a short key.
Fix
PR incoming: route the define flow's presence checks and ordinary_has_property's own-key step through the sidecar (threshold-gated, same trust model as [[Set]]), maintain it from ensure_key_in_keys_array appends, and make the rebuild SSO-aware. After: 2400 defines 527→96ms (linear), @babel/types 292→113ms. Differential regression test included (byte-match vs node on dedup/attrs-retention/SSO/delete-redefine/invariant-throws/in).
Summary
Repeated
Object.definePropertyandk in objon a wide object (>32 keys) are O(N) per call — O(N²) for build loops. This is the webpack/Babel CJS re-export module shape, and it dominates large-bundle module init under perry.Repro
Real-world: a single
@babel/types/lib/index.js(webpack re-export module:if (k in exports) …; Object.defineProperty(exports, k, {get})per key) took 292ms vs node's 3ms (94×) inside the pi coding agent'sjiti/dist/babel.cjs— the largest single contributor to pi's 3.3s startup under perry (measured by per-module init instrumentation).Root cause
The define flow runs THREE linear
keys_arrayscans per call (enforce_define_property_invariants→own_key_present, the existing-attrs check→obj_value_has_own_key,ensure_key_in_keys_array's dup check), and theinoperator's own-key walk runs a fourth. The [[Set]] fast path solved this long ago with the authoritativeKEYS_INDEXsidecar (append-without-scan); the define/inpaths never adopted it. Bonus: the sidecar's rebuild skipped SSO inline keys (is_string()is heap-tag-only), so an authoritative miss could duplicate a short key.Fix
PR incoming: route the define flow's presence checks and
ordinary_has_property's own-key step through the sidecar (threshold-gated, same trust model as [[Set]]), maintain it fromensure_key_in_keys_arrayappends, and make the rebuild SSO-aware. After: 2400 defines 527→96ms (linear),@babel/types292→113ms. Differential regression test included (byte-match vs node on dedup/attrs-retention/SSO/delete-redefine/invariant-throws/in).