Skip to content

Answer an authoritative miss from the builtin-shape probe lane - #2858

Merged
lahma merged 2 commits into
sebastienros:mainfrom
lahma:absent-name
Jul 28, 2026
Merged

Answer an authoritative miss from the builtin-shape probe lane#2858
lahma merged 2 commits into
sebastienros:mainfrom
lahma:absent-name

Conversation

@lahma

@lahma lahma commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Fixes #2857.

ProbeOwnProperty's builtin-shape lane could only answer a hit; a miss declined into the virtual GetOwnProperty, which redid the key conversion and repeated the same shape-index lookup — at every level of a HasProperty chain walk. A chain of JsObjectShape prototypes therefore answered an absent name measurably slower than the dictionary prototypes it replaced, which WebIDL named-indexer checks (el.attributes['nope']) hit constantly.

The fix

A JsString probe that is a true shape-index miss is answered Missing directly when the side dictionary is empty and the type qualifies, gated by a new derived InternalTypes.BuiltinShapeIndexAuthoritative bit:

  • Derived in InitializeBuiltinShape from the existing sentinel check ReferenceEquals(GetInitialOwnStringPropertyKeys(), Enumerable.Empty<JsValue>()) — the same predicate GetForInStringKeys already trusts. Function-derived hosts (length/name/prototype from fields) and StringInstance override that provider and are excluded automatically; future overriders self-exclude. ArrayInstance keeps the flag soundly because its own ProbeOwnProperty intercepts length and every canonical index before calling base, and digit-leading names can never be hybrid additions (the write deopts).
  • Cleared on DeoptBuiltinShape, re-derived on global snapshot restore — a bare |= BuiltinShapeMode there would have permanently lost the lane after one deopt+restore cycle, exactly the capture/restore reuse pattern the lane serves.

The trap that ruled out the issue's original diff

TryProbeBuiltinShapeSlot returns false for two different reasons: a true index miss, and a declared name whose slot is an unmaterialized Factory slot. The issue's narrow SharedShapeObject diff fired on both — and JsObjectShape.Builder.PerRealmSlot(name, factory) produces exactly such slots, so proto.hasOwnProperty('lazyCtor') on an untouched factory slot would have answered false while proto.lazyCtor materializes fine. The signature now distinguishes the cases (out bool nameDeclared), and only a true index miss may fast-miss; factory declines keep falling through to GetOwnProperty. (The GlobalObject.AggregateError failure that killed the issue's wide-guard attempt was a factory decline, not an index miss — the 56 lazy intrinsics are generated Factory slots.) A mutation check confirms the new tests pin this: reverting the gate to fire on any decline fails both SharedObjectShapeTests and the new HostPrototypeShapeTests factory-slot fact in Release.

Measurements

New HostPrototypeShapeBenchmark chain lanes (depth-4 shaped chain, ordinary receiver, in-operator loops; every level's representation asserted in GlobalSetup), default job, idle machine, paired runs:

Row old engine fixed Δ
AbsentNameMissOverChain, Shape 2,430.6 µs 1,831.5 µs −24.6%
AbsentNameMissOverChain, Dictionary 1,905.6 µs 1,821.3 µs −4.4%
DeepHitOverChain, Shape 2,058.3 µs 1,644.1 µs −20.1%
DeepHitOverChain, Dictionary 1,549.5 µs 1,551.9 µs flat
SteadyStateReads (hit control) ~14 µs ~14 µs flat

The shaped miss walk goes from +27.6% over dictionary prototypes to parity (+0.6%, CIs touching). The Dictionary row improving too is the derived-flag design's bonus: every miss walk ends at Object.prototype, which is builtin-shaped and now fast-misses as well.

AngleSharp.Js (all DOM prototypes on JsObjectShape, two independent baseline/fix pairs, LoopOnly-subtracted): NamedIndex (attrs['title'], the WebIDL named-indexer walk) −11.1% / −16.8% across the pairs; every other row within its cross-run noise with sign-flips, and Allocated byte-identical on all rows.

Validation

  • Jint.Tests 4588/0 (Release and Debug — the Debug run re-verifies every fast miss against the virtual GetOwnProperty via AssertBuiltinShapeProbeAgrees, which is wired onto the new return), Jint.Tests.PublicInterface 1172/0, Test262 99441/0.
  • AngleSharp.Js 240-test DOM suite green on net8.0/net472/net462 against a Debug build of this branch.
  • New pins: probe-vs-descriptor sweep extended with absent/tricky names over every reachable shaped object; snapshot-restore flag round-trip; public-surface facts for chain misses, untouched factory slots, hybrid additions, and digit-leading deopt.

🤖 Generated with Claude Code

lahma and others added 2 commits July 28, 2026 22:44
A depth-4 chain of shaped prototypes with an ordinary receiver in front,
walked by the in operator: AbsentNameMissOverChain pays every level's
refusal, DeepHitOverChain resolves on the root as the hit-path control.
Every chain level's representation is asserted in GlobalSetup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A JsString probe that misses the shared layout index used to decline into
the virtual GetOwnProperty, which redid the key conversion and repeated
the same index lookup - at every level of a HasProperty chain walk. When
the side dictionary is empty and the type resolves no string names ahead
of the layout (derived: it does not override GetInitialOwnStringPropertyKeys),
the miss is authoritative and is answered directly.

TryProbeBuiltinShapeSlot now distinguishes a true index miss from a
declared-but-unanswerable Factory slot; only the former may fast-miss.
The derived BuiltinShapeIndexAuthoritative bit rides BuiltinShapeMode:
set in InitializeBuiltinShape, cleared on deopt, re-derived on global
snapshot restore.

Closes sebastienros#2857.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shaped prototype chains answer an absent name slower than dictionary prototypes: ProbeOwnProperty's builtin-shape lane cannot answer a miss

1 participant