Skip to content

Built-in shapes for prototypes and constructors: composable storage, accessors, aliases#2580

Merged
lahma merged 11 commits into
sebastienros:mainfrom
lahma:builtin-object-shapes
Jul 5, 2026
Merged

Built-in shapes for prototypes and constructors: composable storage, accessors, aliases#2580
lahma merged 11 commits into
sebastienros:mainfrom
lahma:builtin-object-shapes

Conversation

@lahma

@lahma lahma commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Extends the built-in hidden-class shape storage (a process-shared BuiltinShape layout + a per-realm lazily-filled descriptor array, from the last release) from the 7 BuiltinShapeObject namespaces to ~96 built-in hosts across every base class — all Prototype-derived hosts (function-only, accessor-bearing, instance-typed, iterators, and the root Object.prototype) and Function-derived constructors. Each shaped host stops allocating a per-realm PropertyDictionary of lazy descriptors in Initialize.

The single blocker was C# single inheritance: prototypes derive from Prototype/instance types, constructors from Constructor : Function, so none could also derive from BuiltinShapeObject. This PR makes the shape storage composable so any [JsObject] host opts in with a one-liner, and it removes the hand-written registration tails (Intl/Temporal intrinsic refs, keys/trimLeft/toGMTString/exec aliases) — the "coherent, full-blown generator usage" goal.

How it works

  • Composable storageInternalTypes.BuiltinShapeMode flag + an IBuiltinShaped storage interface + logic on ObjectInstance (dispatched from the property virtuals via Unsafe.As<IBuiltinShaped>, mirroring how ShapeMode is already handled). The per-realm descriptor field is generator-emitted onto each host, so the million-population bases (ObjectInstance, Function, ArrayInstance, …) pay nothing.
  • Dual-mode generator — a host that doesn't derive from BuiltinShapeObject opts in with [JsObject(UseShape = true)]; the generator emits the field + IBuiltinShaped glue onto it.
  • Slot kindsConstant / Instance / Function / Accessor (GetSetPropertyDescriptor, materialized lazily — leaner than the dictionary path's eager LazyGetSetPropertyDescriptor) / Alias (new [JsAlias("name","target")] for spec function-identity aliases like Set.prototype.keys === values).
  • Member-read fast-path integration — the ObjectInstance _properties fast paths are gated (_type & (PlainObject | BuiltinShapeMode)) == PlainObject, and the JintMemberExpression receiver inline-cache gate broadened to PlainObject | BuiltinShapeMode, so shaped prototypes/constructors keep the version-gated descriptor cache. Redefine mutates the slot in place; add/delete deopts to a dictionary — byte-for-byte the pre-shape representation.

Two bugs the verification caught and fixed: a lazy-init fast-path bug (typeof WeakMap.prototype.getundefined on first read, fixed with an _initialized gate) and the string-alias hazard (a naive shaping of SetPrototype dropped keys and broke 74 tests — the reason for [JsAlias]).

Benchmarks

Jint.Benchmark/BuiltinShapeBenchmark.cs, default job. Every commit was A/B'd against an EngineOnly control that stayed byte-identical at 13.70 KB (no regression to bare-engine construction, and ordinary-object get/set/create allocation is byte-identical throughout). Per-realm Initialize allocation removed, per commit:

Commit Hosts Δ (per-realm init)
Intl/Temporal → [JsIntrinsicReference] Intl/Temporal namespaces cold-start −9…17% (eager→lazy)
Composable storage foundation (7 existing, refactor) byte-identical
Fast-path + 5 function-only prototypes Promise, Weak×3, FinalizationRegistry −1.60 KB
18 more function-only prototypes BigInt, errors, TypedArray, iterator helpers, … −1.94 KB
Accessor support + 6 accessor prototypes Symbol, Map, ArrayBuffer, DataView, … −5.74 KB
Object + Temporal + Intl prototypes root Object.prototype, 8× Temporal, Intl −24.69 KB
Constructors (Function-derived) Object, Array, Map, Set, Symbol, Promise, ArrayBuffer −4.95 KB (−39.3% of ctor storage, −10.7% time)
25 more constructors Date, Error, BigInt, String, Proxy, Intl, Temporal −4.72 KB
Iterators + instance-typed prototypes Array, Number, Boolean, Error, 5× iterators −5.71 KB
[JsAlias] → Set, String, Date (largest, always-used prototypes) −10.62 KB
RegExpPrototype (exec → instance slot) RegExp −1.35 KB

Summing the batches, the shaped surface eliminates ~61 KB of per-realm property-dictionary allocation (~51 KB prototypes + ~10 KB constructors) when the full built-in surface is exercised, plus the Intl/Temporal cold-start improvements. Final absolute figures (all-shaped): EngineInitPrototypes 79.09 KB and EngineInitConstructors 46.54 KB vs the 13.70 KB control.

Conformance

  • Full Test262: 0 new failures across every phase (only the pre-existing annexB RegExp-*-escape-BMP and Atomics.waitAsync concurrent-load timeout flakes, which flake identically on main and pass in isolation).
  • Jint.Tests 3210/0 (net10) + 3148/0 (net472); Jint.Tests.PublicInterface 82/0 (no public API change); Jint.Tests.SourceGenerators 30/30 (snapshots updated for the new attributes).

Notes / left on the dictionary path (intentional)

Cross-object aliases (%TypedArray%.prototype.toString === Array.prototype.toString; Number.parseInt === global parseInt), the TypedArray constructor hierarchy, and the intrinsic-reference / thrower-accessor hosts (GlobalObject, Intl/Temporal instances, FunctionPrototype). Extending [JsAlias] to cross-host would unblock the two alias cases; a natural follow-up.

🤖 Generated with Claude Code

https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3

lahma and others added 11 commits July 5, 2026 17:50
IntlInstance and TemporalInstance hand-registered their sub-namespace
constructor references (Intl.Collator/.../Segmenter; Temporal.Duration/.../Now)
with eager SetProperty(..., _realm.Intrinsics.X) calls plus [JsObject(ExtraCapacity)]
presizing, documented as "not generator-friendly". They are exactly the lazy
realm-intrinsic references [JsIntrinsicReference] already emits for globalThis's
56 constructor properties (GlobalObject.Properties.cs), so route them through the
same generator surface: one mechanism, no hand-written tail, no ExtraCapacity.

The references become lazy (resolved on first read) instead of eager, so touching
one Intl/Temporal sub-namespace no longer instantiates all ~9 siblings.

Benchmarks (BuiltinShapeBenchmark + IntlColdStartBenchmark, default job, vs base;
EngineOnly control byte-identical at 13.69 KB):

  EngineInitTemporalNow            19.52 -> 17.41 KB (-10.8%; time -16.4%)
  IntlColdStart.NumberFormat       28.99 -> 26.27 KB ( -9.4%; time -13.5%)
  IntlColdStart.DateTimeFormat     27.49 -> 24.77 KB ( -9.9%; time -14.3%)
  IntlColdStart.ListFormat         25.40 -> 22.67 KB (-10.7%; time -14.2%)
  IntlColdStart.RelativeTimeFormat 27.52 -> 25.12 KB ( -8.7%; time -16.8%)
  IntlColdStart.LocaleCanonicalize 30.06 -> 27.34 KB ( -9.1%; time -14.1%)

Conformance: Test262 Temporal+Intl 15808/0; Jint.Tests Intl/Temporal 88/0.
Order-safe (namespace tests assert presence + non-enumerability, not order).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3
…BuiltinShaped

Refactor the built-in shape store so its behavior lives on ObjectInstance behind a
new InternalTypes.BuiltinShapeMode flag and is reached through an IBuiltinShaped
storage interface, instead of being trapped in the BuiltinShapeObject base class.
This is the enabler for shaping prototypes and constructors, which derive from
Prototype / Constructor:Function / instance types and so cannot also derive from
BuiltinShapeObject under single inheritance.

- InternalTypes.BuiltinShapeMode: set when the shape is installed, cleared on deopt;
  mutually exclusive with ShapeMode. Folded into the existing ShapeMode flag test in
  the property virtuals so the common (non-shaped) path costs nothing extra.
- IBuiltinShaped: exposes only per-host storage (BuiltinShape, the per-realm
  PropertyDescriptor?[], MakeBuiltinFunction) — no logic — so the mechanism composes
  across host base classes. The field stays off the million-population bases.
- ObjectInstance.GetOwnProperty / SetOwnProperty / RemoveOwnProperty / GetOwnProperties
  / GetOwnPropertyKeys dispatch to shared private helpers (MaterializeBuiltinSlot /
  DeoptBuiltinShape / GetBuiltinShapeOwnPropertyKeys) via Unsafe.As<IBuiltinShaped>,
  mirroring how ShapeMode is already handled inline with Unsafe.As<JsObject>. Delete
  and DefineOwnProperty need no change (Delete routes through RemoveOwnProperty;
  DefineOwnProperty's deopt is ShapeMode-only, preserving redefine-in-place).
  Key enumeration prepends GetInitialOwnStringPropertyKeys() so Function-derived hosts
  surface length/name/prototype correctly (a no-op for today's hosts).
- BuiltinShapeObject becomes a thin convenience base: implements IBuiltinShaped with a
  field, sets the flag in InitializeBuiltinShape, and drops its virtual overrides.

No functional change. The 7 existing shaped hosts (Math, JSON, Reflect, Atomics,
Temporal.Now, Generator/AsyncGenerator prototypes) now route through the base.

Benchmarks (BuiltinShapeBenchmark + ObjectAccessBenchmark + PropertyAllocBenchmark,
default job, vs base): allocation byte-identical on every scenario; time within noise
(the EngineOnly control runs identical code yet varies +3% run-to-run, setting the
floor). Ordinary-object property get/set/create unaffected.

Conformance: full Test262 99260/0 (133 pre-existing skips).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3
…t-path integration

Extend built-in shape storage from the 7 BuiltinShapeObject namespaces to Prototype-
derived hosts, starting with the five function-only prototypes (Promise, WeakMap,
WeakSet, WeakRef, FinalizationRegistry). Each opts in with [JsObject(UseShape = true)]
and keeps deriving from Prototype; the generator emits the IBuiltinShaped storage onto
the host itself, so no shared base class is needed.

Generator (dual-mode):
- A host that does NOT derive from BuiltinShapeObject opts into shapes with an explicit
  [JsObject(UseShape = true)] (deriving from BuiltinShapeObject stays the implicit opt-in).
- For such a host the emitter adds ': IBuiltinShaped' to the partial and emits the
  per-realm __builtinDescriptors field plus explicit IBuiltinShaped implementations
  (BuiltinShape / BuiltinDescriptors / MakeBuiltinFunction), instead of the abstract
  overrides used for BuiltinShapeObject-derived hosts.

Runtime:
- InitializeBuiltinShape / SetBuiltinInstanceDescriptor moved to ObjectInstance
  (private protected, IBuiltinShaped-based) so any shaped host can call them.
- The raw-_properties PlainObject fast paths in Get / Set / CreateDataProperty are gated
  (_type & (PlainObject | BuiltinShapeMode)) == PlainObject so a shaped prototype (which
  is PlainObject) routes to the virtual GetOwnProperty. The JintMemberExpression receiver
  inline-cache gate is broadened to PlainObject | BuiltinShapeMode so shaped prototypes
  (and, later, constructors) get the version-gated descriptor cache.
- Fix: those fast paths also require _initialized. The BuiltinShapeMode flag is set lazily
  in Initialize, so a not-yet-initialized shaped prototype would otherwise pass the pre-init
  flag gate, initialize inside the fast path, then read the (now shape-backed, empty)
  _properties and fall through to the prototype chain — e.g. `typeof WeakMap.prototype.get`
  wrongly === "undefined" on first access. Matches the existing CreateDataProperty guard.

Benchmarks (BuiltinShapeBenchmark + ObjectAccessBenchmark + PropertyAllocBenchmark,
default job, vs base; EngineOnly control byte-identical at 13.69 KB, EngineInitGenerators
byte-identical at 19.66 KB):

  EngineInitPrototypes (the 5 shaped)   22.37 -> 20.77 KB  (-1.60 KB; storage over
                                        EngineOnly 8.68 -> 7.08 KB, -18.4%; time flat)

Ordinary-object get/set/create allocation byte-identical, time within noise.

Conformance: full Test262 99260/0; Jint.Tests 3210/0; Jint.Tests.SourceGenerators 30/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3
…on shaped hosts

Extend built-in shape storage to the remaining function-only Prototype-derived hosts:
AggregateError, SuppressedError, BigInt, ShadowRealm, TypedArray (%TypedArray% per-type
+ Uint8Array), the three function-kind prototypes (GeneratorFunction / AsyncFunction /
AsyncGeneratorFunction), the iterator-helper prototypes (Iterator/AsyncIterator helper,
WrapForValidIterator), and the Intl DisplayNames/DurationFormat/ListFormat/PluralRules/
RelativeTimeFormat/Segmenter prototypes. Each opts in with [JsObject(UseShape = true)];
all have a clean CreateProperties_Generated-only Initialize, so shaping is mechanical.
DatePrototype is deliberately left on the dictionary path (it adds a toGMTString string
alias in Initialize, which the fixed shape layout can't express).

Generator fix: a shaped host with no [JsFunction]s (only instance/constant properties +
symbols, e.g. GeneratorFunctionPrototype) has no dispatcher and no function slots, so its
IBuiltinShaped.MakeBuiltinFunction now emits a throwing body instead of referencing a
dispatcher type that isn't generated.

Benchmarks (BuiltinShapeBenchmark, default job, vs prior commit; EngineOnly control
byte-identical at 13.69 KB): EngineInitPrototypes (extended to touch these hosts)
36.78 -> 34.84 KB (-1.94 KB; storage over EngineOnly -8.4%). No hot-path code changed.

Conformance: full Test262 99260/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3
Teach the built-in shape path to represent string [JsAccessor]s, then shape the
accessor-bearing Prototype-derived hosts whose Initialize is generated-only: Symbol,
Map, ArrayBuffer, SharedArrayBuffer, DataView, Iterator prototypes.

- BuiltinShape gains an explicit BuiltinSlotKind per slot (Constant / Instance /
  Function / Accessor) and a SetterSlots array. Builder.Accessor(name, getterSlot,
  setterSlot, flags) reserves an accessor slot; MaterializeBuiltinSlot creates a
  GetSetPropertyDescriptor on first access (leaner than the dictionary path, which
  allocates a LazyGetSetPropertyDescriptor per accessor at Initialize even if unread).
- Generator: BuildBuiltinShape_Generated emits builder.Accessor(...) for each accessor
  pair, after the functions, sorted by JsName to match the dictionary path's key order.
  JINT022 no longer rejects accessors or symbol accessors (symbol accessors register in
  the symbol dictionary, orthogonal to the string shape) — only intrinsic references and
  thrower accessors remain unsupported.
- SetPrototype is deliberately NOT shaped: it aliases the string property `keys` to the
  `values` function via SetProperty in Initialize (spec requires keys === values), which
  the fixed shape layout can't express (a raw SetProperty on a shaped host writes to
  _properties, which the shape lookup ignores). Same class of case as Date/RegExp/String;
  a generator alias feature is a later step.

Benchmarks (BuiltinShapeBenchmark, default job, vs prior commit; EngineOnly control
byte-identical at 13.69 KB): EngineInitPrototypes (extended to touch the accessor hosts)
47.37 -> 41.63 KB (-5.74 KB; storage over EngineOnly -17.0%).

Conformance: full Test262 0 new failures (the 4 annexB RegExp-*-escape-BMP timeouts are
pre-existing concurrent-load flakes — RegExp is not shaped, and the same binary passed
99260/0 earlier); Set/Map/Symbol/ArrayBuffer/DataView/Iterator subset 6494/0;
Jint.Tests.SourceGenerators 30/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3
With accessor support in place, shape the remaining accessor-bearing Prototype-derived
hosts whose Initialize is generated-only: Object.prototype (the root — __proto__ get/set
accessor), all 8 Temporal prototypes (Duration, Instant, PlainDate, PlainDateTime,
PlainMonthDay, PlainTime, PlainYearMonth, ZonedDateTime), the Intl Collator/DateTimeFormat/
NumberFormat/Locale prototypes, and the (Async)DisposableStack and AsyncIterator prototypes.

The Temporal prototypes are accessor-dense (ZonedDateTime 30, PlainDateTime 22, PlainDate 16,
Duration 12), so the dictionary path allocated a large per-realm property dictionary of eager
LazyGetSetPropertyDescriptors even when the object was never touched beyond one property; the
shape path installs a shared layout and materializes each descriptor lazily on first read.

Shaping the root Object.prototype (in every ordinary object's prototype chain) with no
conformance change validates the member-read fast-path integration end to end.

Benchmarks (BuiltinShapeBenchmark, default job, vs prior commit; EngineOnly control
byte-identical at 13.69 KB): EngineInitPrototypes (extended to touch these hosts)
87.36 -> 62.67 KB (-24.69 KB; storage over EngineOnly -33.5%).

Conformance: full Test262 0 new failures (the annexB RegExp-*-escape-BMP timeouts are the
pre-existing concurrent-load flakes; RegExp is not shaped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3
…l, Promise, ArrayBuffer

Extend built-in shape storage to Constructor-derived hosts — the last major base class.
Constructors derive from Constructor : Function : ObjectInstance and are not PlainObject,
but the composable IBuiltinShaped storage (emitted onto the host by the generator) plus the
P2 fast-path work already handle them:

- The JintMemberExpression receiver gate was broadened to PlainObject | BuiltinShapeMode in
  P2, so a shaped constructor (not PlainObject) still gets the version-gated inline cache.
- Function keeps serving length/name/prototype from its own fields and chains base.GetOwnProperty
  into ObjectInstance, which dispatches to the shape store. GetBuiltinShapeOwnPropertyKeys
  prepends GetInitialOwnStringPropertyKeys() so getOwnPropertyNames yields length,name,prototype
  ahead of the shaped statics, matching the dictionary path.
- @@species getters (symbol accessors) and well-known symbol constants (SymbolConstructor)
  work via the accessor + symbol machinery.

This batch: Object, Array, ArrayBuffer, Map, Set, Symbol, Promise constructors.

Benchmarks (BuiltinShapeBenchmark, default job, vs prior commit; EngineOnly control
byte-identical at 13.70 KB): EngineInitConstructors 26.30 -> 21.35 KB (-4.95 KB; storage over
EngineOnly -39.3%; time -10.7%) — the per-realm constructor dictionary (ObjectConstructor 23
statics, SymbolConstructor 17) is pure overhead the shape removes.

Conformance: full Test262 0 new failures (the 4 annexB RegExp-*-escape-BMP timeouts are
pre-existing concurrent-load flakes; RegExp is not shaped); constructor smoke covers
length/name/prototype coexistence, static methods, @@species, construction, and deopt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3
…tor, Intl, Temporal)

With the Function-derived constructor path proven, shape the remaining clean constructors
whose Initialize is generated-only: Date, Error, BigInt, String, Proxy, SharedArrayBuffer,
Iterator/AsyncIterator, all 8 Temporal constructors, and all 9 Intl constructors.

Left on the dictionary path (hand-written string aliases / intrinsic refs, not shape-
expressible): NumberConstructor (Number.parseInt === parseInt), RegExpConstructor (legacy
statics), and the TypedArray constructor family (handled separately).

Also fixes a pre-existing IDE0055 formatting issue in CollatorConstructor (comments between
`=>` and the value in a switch expression) that surfaced once the file was re-analyzed.

Benchmarks (BuiltinShapeBenchmark, default job, vs prior commit; EngineOnly control
byte-identical at 13.70 KB): EngineInitConstructors (extended to touch these constructors)
51.23 -> 46.51 KB (-4.72 KB; storage over EngineOnly -12.6%).

Conformance: full Test262 0 new failures (the 4 annexB RegExp-*-escape-BMP timeouts are the
pre-existing concurrent-load flakes; RegExp is not shaped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3
… Error)

Two more prototype families join the shape path:

- Iterator prototypes (Array/Map/Set/String/RegExpString iterator prototypes), which derive
  from the already-shaped IteratorPrototype. A derived host re-implements IBuiltinShaped
  (interface re-implementation), so each carries its own shape — verified by the iterator
  suite passing unchanged.
- Instance-typed prototypes: ArrayPrototype (: ArrayInstance), NumberPrototype (: NumberInstance),
  BooleanPrototype (: BooleanInstance), ErrorPrototype (: ErrorInstance). These bases override
  GetOwnProperty for integer indices / length and chain base.GetOwnProperty into ObjectInstance,
  so string method names resolve through the shape store while length/index behavior is
  unchanged. Array.prototype's hand-written @@iterator===values identity and @@unscopables
  (symbols, orthogonal to the string shape) are preserved.

This confirms shape storage composes across every built-in base class, including the ones with
their own [[GetOwnProperty]] overrides.

Benchmarks (BuiltinShapeBenchmark, default job, vs prior commit; EngineOnly control byte-
identical at 13.70 KB): EngineInitPrototypes (extended to touch these hosts) 86.26 -> 80.55 KB
(-5.71 KB).

Conformance: full Test262 0 new failures across two runs (iterators, then instance-typed);
only the pre-existing annexB RegExp-*-escape-BMP and Atomics.waitAsync concurrent-load flakes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3
…types

Add a [JsAlias("name", "target")] class-level attribute for spec function-identity aliases —
a string own property that shares another generated member's descriptor so the two names
resolve to the very same function object. This removes the final hand-written string-property
tails that blocked shaping (raw SetProperty/AddDangerous lands in _properties, which the shape
lookup ignores).

- BuiltinShape gains a BuiltinSlotKind.Alias whose FunctionSlots holds the target slot index;
  MaterializeBuiltinSlot(alias) materializes the target and shares its descriptor. Builder.Alias
  resolves the target by name. The generator emits builder.Alias last (own-key order) in the
  shape path and, in the dictionary path, AddDangerous with the target's captured descriptor.
- Shape SetPrototype (keys === values), StringPrototype (trimLeft/trimRight === trimStart/trimEnd)
  and DatePrototype (toGMTString === toUTCString) via [JsAlias]; their ExtraCapacity + AddDangerous
  tails are gone. Symbol aliases (Set/String @@iterator, Date @@toPrimitive) stay hand-written —
  symbols are orthogonal to the string shape.

String (49 members) and Date (48) are the largest prototypes and always used, so their per-realm
dictionaries were the biggest single init allocations.

Benchmarks (BuiltinShapeBenchmark, default job, vs prior commit; EngineOnly control byte-identical
at 13.70 KB): EngineInitPrototypes (extended to touch String/Date/Set) 87.54 -> 76.92 KB
(-10.62 KB; storage over EngineOnly -14.4%).

Conformance: full Test262 0 new failures (only the pre-existing annexB RegExp-*-escape-BMP and
Atomics.waitAsync concurrent-load flakes); Jint.Tests.SourceGenerators 30/30 (JintAttributes
snapshot updated for [JsAlias]); the Set combination methods that a naive shaping broke earlier
now pass. Alias smoke: keys===values, trimLeft===trimStart, toGMTString===toUTCString.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3
RegExpPrototype was blocked only by its hand-registered `exec`: HasDefaultExec detects
user replacement by comparing the current exec value's underlying delegate against
_defaultExec, so exec must be a ClrFunction wrapping _defaultExec (a generated [JsFunction]
dispatcher is a different Function subtype and would break the check). Because the check is
on the delegate, not on instance identity, exec fits the existing per-realm instance-slot
mechanism: declare it as a [JsProperty] ClrFunction created in the constructor, filled via
SetBuiltinInstanceDescriptor. No new generator feature needed.

RegExpPrototype (10 accessors + ~15 methods + exec) now shapes; its ExtraCapacity + the raw
_properties.AddDangerous("exec", ...) tail are gone. HasDefaultExec (and the replace/split/
match fast paths that depend on it) is unchanged and verified: replacing RegExp.prototype.exec
is still observed.

Benchmarks (BuiltinShapeBenchmark, default job, vs prior commit; EngineOnly control byte-
identical at 13.70 KB): EngineInitPrototypes (extended to touch RegExp.prototype) 80.44 ->
79.09 KB (-1.35 KB).

Conformance: full Test262 0 new failures (the 4 annexB RegExp-*-escape-BMP timeouts are the
pre-existing concurrent-load flakes — pattern-parsing tests, unrelated to property storage).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Fq5mF1QEwh2qDMD8vVwt3
@lahma
lahma merged commit 09bf81d into sebastienros:main Jul 5, 2026
4 checks passed
@lahma
lahma deleted the builtin-object-shapes branch July 5, 2026 18:55
lahma added a commit that referenced this pull request Jul 8, 2026
Re-run after the builtin-shapes rollout and the 2026-07 performance
campaign (#2580-#2597). Highlights vs the 4.11.0 refresh: minimal script
-47% time / -45% allocation (prepared execution now under a microsecond),
evaluation -33% allocation, stopwatch-modern -22% (ahead of NiL.JS, 1.13x
behind Jurassic, from 1.44x), object-string -14%, string-base64 -12%
(taking the lead from NiL.JS), object-array -11%. Competitor versions
unchanged; their rows served as thermal canaries.


Claude-Session: https://claude.ai/code/session_01S2U2y9voMQV9rvCkYeQbka

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.

1 participant