You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Severity: performance (super-linear blowups on common shapes) Found by: audit fable-audit-perry-2.md (several measured).
A cluster of runtime data structures that go quadratic or leak on idiomatic workloads:
process.nextTick queue drains with Vec::remove(0) — O(n²).crates/perry-runtime/src/builtins/globals.rs:980,1008-1014. A burst of n ticks memmoves ~n²/2 entries. Fix: VecDeque::pop_front (the promise TASK_QUEUE two files over already does this). Good first issue.
Promise.all keeps per-pending-element state in a global Vec scanned in full on every settle.promise/combinators.rs:22,41-59,858-861; PROMISE_OVERFLOW_REACTIONS and PROMISE_SETTLE_LISTENERS (then.rs:255-389) have the same shape — any non-empty table taxes every unrelated settle. Promise.all of 10k pending promises ≈ 100M comparisons. Fix: key by promise (HashMap<usize, SmallVec<…>>) or hang an intrusive reaction list off the Promise.
Timers: three unsorted Mutex<Vec>s, full-scanned several times per loop turn, and TIMER_REF_STATES grows forever.timer.rs:331,504-521 — one i64→bool entry per timer ever created, never removed (self-documented). A server arming a per-request timeout leaks unboundedly and locks the map per liveness probe. Fix: BinaryHeap per queue + id-tombstone map + cached next-deadline atomic + fold ref-state into the timer record.
PROMISE_CONTEXTS (AsyncLocalStorage bookkeeping) churns a HashMap insert per .then + O(K) key scan per settle even when ALS is never used.promise/mod.rs:570-687. Fix: inline the snapshot into Promise, or gate on an "ALS ever instantiated" flag.
Object/BigInt-keyed Map/Set operations are O(n) each → O(n²) workloads.map.rs:751-830 restricts the hash index to non-pointer keys; object keys take a linear scan. Measured 1,793× slower than string keys (20k inserts+gets: 7,173 ms vs 4 ms). Object-keyed Maps are the default cache/registry idiom. Fix: a movable-safe identity hash (lazily assign a 32-bit id in the GcHeader spare bits, index by id).
One user Object.freeze/defineProperty permanently disables the dynamic-write fast path process-wide.GLOBAL_DESCRIPTORS_IN_USE (descriptor_state.rs:86,338,468) gates transition_fast + the in-function transition-cache path (field_set_by_name.rs:54,916); perf(runtime,codegen): stop unrelated descriptor installs disabling the class-field inline fast path (#5654) #6057 fixed the read side only. Fix: gate on the receiver's OBJ_FLAG_HAS_DESCRIPTORS + the existing object_proto_may_intercept_key check instead of the global.
Confidence: high (source; items 1/2/5 also probe/measured).
Severity: performance (super-linear blowups on common shapes)
Found by: audit
fable-audit-perry-2.md(several measured).A cluster of runtime data structures that go quadratic or leak on idiomatic workloads:
process.nextTickqueue drains withVec::remove(0)— O(n²).crates/perry-runtime/src/builtins/globals.rs:980,1008-1014. A burst of n ticks memmoves ~n²/2 entries. Fix:VecDeque::pop_front(the promiseTASK_QUEUEtwo files over already does this). Good first issue.Promise.allkeeps per-pending-element state in a global Vec scanned in full on every settle.promise/combinators.rs:22,41-59,858-861;PROMISE_OVERFLOW_REACTIONSandPROMISE_SETTLE_LISTENERS(then.rs:255-389) have the same shape — any non-empty table taxes every unrelated settle.Promise.allof 10k pending promises ≈ 100M comparisons. Fix: key by promise (HashMap<usize, SmallVec<…>>) or hang an intrusive reaction list off the Promise.Mutex<Vec>s, full-scanned several times per loop turn, andTIMER_REF_STATESgrows forever.timer.rs:331,504-521— onei64→boolentry per timer ever created, never removed (self-documented). A server arming a per-request timeout leaks unboundedly and locks the map per liveness probe. Fix:BinaryHeapper queue + id-tombstone map + cached next-deadline atomic + fold ref-state into the timer record.PROMISE_CONTEXTS(AsyncLocalStorage bookkeeping) churns a HashMap insert per.then+ O(K) key scan per settle even when ALS is never used.promise/mod.rs:570-687. Fix: inline the snapshot intoPromise, or gate on an "ALS ever instantiated" flag.map.rs:751-830restricts the hash index to non-pointer keys; object keys take a linear scan. Measured 1,793× slower than string keys (20k inserts+gets: 7,173 ms vs 4 ms). Object-keyed Maps are the default cache/registry idiom. Fix: a movable-safe identity hash (lazily assign a 32-bit id in the GcHeader spare bits, index by id).Object.freeze/definePropertypermanently disables the dynamic-write fast path process-wide.GLOBAL_DESCRIPTORS_IN_USE(descriptor_state.rs:86,338,468) gatestransition_fast+ the in-function transition-cache path (field_set_by_name.rs:54,916); perf(runtime,codegen): stop unrelated descriptor installs disabling the class-field inline fast path (#5654) #6057 fixed the read side only. Fix: gate on the receiver'sOBJ_FLAG_HAS_DESCRIPTORS+ the existingobject_proto_may_intercept_keycheck instead of the global.Confidence: high (source; items 1/2/5 also probe/measured).