Probe own-property existence/enumerability without materializing descriptors#2585
Merged
Conversation
…riptors Read-only callers that only need "does this own property exist and is it enumerable" previously went through GetOwnProperty, which on shape-mode objects allocates a fresh SlotPropertyDescriptor per call, and on dense arrays allocates one CEW descriptor per touched index AND grows a permanent _sparse shadow dictionary beside _dense. New internal ObjectInstance.ProbeOwnProperty returns a tri-state (Missing/NonEnumerable/Enumerable): - base implementation answers shape-mode objects from the shape (slots are always configurable/enumerable/writable; anything else deopts), everything else routes through the virtual GetOwnProperty so exotics — proxies (traps still fire per key), typed arrays, wrappers — keep exact semantics; - ArrayInstance override answers dense/sparse elements allocation-free; a previously materialized _sparse descriptor stays authoritative for flags; - converted callers: HasOwnProperty, HasProperty own-arm, for-in GetKeys, EnumerableOwnProperties (Object.keys/values/entries), CopyDataProperties (spread), Object.assign, Object.defineProperties filter, Object.prototype.hasOwnProperty/propertyIsEnumerable, JSON serializer. GetOwnProperty and TryGetDescriptor stay byte-for-byte identical for every spec/API boundary that needs a real descriptor. Census (AllocTypeProbe): Object.keys/assign over fresh dense arrays -63% bytes/iter; for-in over dense arrays -33%; handlebars-style extend() -4% with SlotPropertyDescriptor eliminated from the profile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lahma
enabled auto-merge (squash)
July 6, 2026 13:44
This was referenced Jul 6, 2026
This was referenced Jul 13, 2026
This was referenced Jul 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Read-only callers that only need "does this own property exist, and is it enumerable" previously went through
GetOwnProperty, which has two allocation problems on hot enumeration paths:SlotPropertyDescriptoron every call (hasOwnProperty,propertyIsEnumerable, for-in,Object.keys/values/entries, spread,Object.assign,JSON.stringifyall hit this per key — the handlebarsextend()pattern conjures three of them per key per call);_sparseshadow dictionary beside_dense(TryGetDescriptor(createIfMissing: true)), so merely enumerating a dense array bloats it for life.This adds an internal tri-state probe and converts the read-only callers:
ObjectInstance.ProbeOwnProperty(JsValue)returnsMissing/NonEnumerable/Enumerable. The base implementation answers shape-mode objects straight from the shape (shape slots are always configurable/enumerable/writable — anything else deopts to dictionary mode); everything else routes through the virtualGetOwnProperty, so exotics keep exact semantics — proxygetOwnPropertyDescriptortraps still fire per key during for-in/Object.keys, typed arrays and interop wrappers are untouched.ArrayInstanceoverrides the probe to answer dense/sparse elements allocation-free with no_sparsematerialization; a previously materialized_sparsedescriptor (freeze/defineProperty flows) stays authoritative for its flags.GetOwnProperty/TryGetDescriptorremain byte-for-byte identical for every boundary that needs a real descriptor.HasOwnProperty, the own-property arm ofHasProperty, for-in key enumeration (GetKeys),EnumerableOwnProperties(Object.keys/values/entries),CopyDataProperties(object spread /Object.assign), theObject.definePropertiesprops filter,Object.prototype.hasOwnProperty/propertyIsEnumerable, and the JSON serializer's key filter.Measurements
Allocation census (
--profile-alloc-types, fresh engine per iteration — fresh arrays per round, so first-enumeration materialization is visible):Object.keys/Object.assignover fresh dense arraysDictionary<uint,PropertyDescriptor>entry arrays disappear from the profile)extend()over object literalsSlotPropertyDescriptoreliminated from the profile)BenchmarkDotNet default jobs (A/B vs main from separate worktrees; note these suites reuse arrays across iterations, so they mostly show the steady-state time win rather than the first-enumeration allocations):
No regressions; PreparedScript source mode (parse-dominated) moved within its error bars.
Considered and deferred: hoisting the loop-invariant transient descriptors in
SetIntegrityLevel— needs aValidateAndApplyPropertyDescriptorretention audit first, and freeze/seal is cold.Verification
Jint.Testsgreen (net10.0 + net472),Jint.Tests.PublicInterfacegreenownKeys/getOwnPropertyDescriptortrap ordering, for-in semantics all covered)🤖 Generated with Claude Code