Shape hot constructors' instances (−22..36% Constructor allocation)#2553
Merged
Conversation
Extend hidden-class shapes from object literals to constructor instances
(`new T(){ this.a; this.b; this.c }` — TreeNode / dromaeo-object-* pattern, the
dominant object-creation shape in real JS).
Allocation-site feedback (the key to avoiding the diverse-object pitfall the first
spike hit): a constructor's first CtorShapePromoteThreshold (16) instances build
dictionaries, so a constructor called once or twice — the overwhelming norm —
never grows the shared per-prototype transition tree. Once it proves "hot" it is
promoted: ScriptFunction.Construct starts each fresh `this` in shape-building mode
(InternalTypes.ShapeBuilding), so this.x= / class fields transition an interned
hidden class shared across instances instead of a per-object dictionary. A
megamorphic guard (Shape.MaxShapeProperties / MaxFanout) deopts object-as-hashmap
growth. Object literals keep their merged behavior (deopt on a new key — they are
not a reused allocation site); Object.assign / spread / JSON targets stay
dictionary (they never enter shape mode).
Benchmarks (PropertyAllocBenchmark, default job, vs merged main; allocation is
deterministic):
Constructor3 136.41 -> 87.58 MB (-35.8%) Gen0 8500 -> 5250
Constructor1 85.15 -> 66.84 MB (-21.5%) Gen0 5200 -> 4167
Literal3/8 unchanged (already shaped)
ObjectAccess unchanged
Time is parity-to-better (Constructor3 -3.7%). Test262 matches the merged-main
baseline run for run (a handful of slow annexB RegExp eval-loop tests
intermittently time out under heavy concurrent load on a busy machine, identically
with and without this change — confirmed via a clean-main control run); on a fresh
machine both are 0-fail.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS23QeSSNRkaUB2KL74U9
This was referenced Jul 6, 2026
This was referenced Jul 13, 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.
Summary
Extend the merged object-literal hidden classes (#2552) to constructor instances —
new T(){ this.a; this.b; this.c }, the TreeNode / dromaeo-object-* pattern and the dominant object-creation shape in real JS. A hot constructor'sthisbuilds a shared hidden class instead of a per-object dictionary.Avoiding the diverse-object pitfall (allocation-site feedback)
Naively shaping every
this.x=regresses diverse one-off objects (each unique layout spawns aShape+ transition with no reuse). This PR gates shaping on a constructor proving hot:CtorShapePromoteThreshold(16) instances build dictionaries, so a constructor called once or twice — the overwhelming norm — never grows the shared per-prototype transition tree.ScriptFunction.Constructstarts each freshthisin shape-building mode (InternalTypes.ShapeBuilding), sothis.x=/ class fields transition an interned hidden class shared across instances.Shape.MaxShapeProperties/MaxFanout) deopts object-as-hashmap growth.Object.assign/ spread /JSON.parsetargets stay on the dictionary (they never enter shape mode).Benchmarks (BenchmarkDotNet default job, vs merged main; allocation is deterministic)
PropertyAllocBenchmark / ObjectAccessBenchmark
this.a=;this.b=;this.c=)this.value=)EngineComparison (
Jint_ParsedScript, 19 scripts)Allocation flat to slightly better —
dromaeo-3d-cube−1.3% (its hot constructors now shape), everything else within ±1%. No regression.Conformance & tests
RegExp-*-escape-BMPeval-loop tests intermittently time out under heavy concurrent load on a busy machine — identically with and without this change, confirmed via a clean-main control run; on a fresh machine both are 0-fail.)Jint.Tests: 3138 (net10.0) / 3076 (net472), 0 failures.Jint.Tests.PublicInterface: 79/79 — no public API change (all additions internal).Design
ScriptFunction: per-constructor feedback (_ctorShaped,_ctorSampleCount, threshold 16) + a cached prototype empty-shape root (avoids a per-construct lookup).JsObject.StartShapeBuilding(installs the empty root + a growable store, setsShapeMode | ShapeBuilding);JsObject.TryShapeAdd(transition to the interned child + grow store + megamorphic caps).ObjectInstance.CreateDataProperty: a new key on aShapeBuildingobject transitions the shape; a plain shaped object (literal) deopts.Shape.MaxShapeProperties/MaxFanout/TransitionCountre-added for the guard.🤖 Generated with Claude Code
https://claude.ai/code/session_01RDS23QeSSNRkaUB2KL74U9