Skip to content

GlobalObject joins the builtin-shape system via hybrid shape+overflow storage#2595

Merged
lahma merged 2 commits into
sebastienros:mainfrom
lahma:perf-globalobject-shape-hybrid
Jul 7, 2026
Merged

GlobalObject joins the builtin-shape system via hybrid shape+overflow storage#2595
lahma merged 2 commits into
sebastienros:mainfrom
lahma:perf-globalobject-shape-hybrid

Conversation

@lahma

@lahma lahma commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Hybrid shape+overflow storage for builtin-shaped hosts; GlobalObject joins the shape system

Finishes the built-in shapes rollout (#2580, #2581, #2582) by flipping its largest remaining dictionary host — GlobalObject, 71 entries built per realm — and introduces the mechanism that makes the flip viable: a hybrid mode where a builtin-shaped host can gain new own properties in a side dictionary while the shape keeps serving its slots.

Why hybrid

A shaped global without hybrid storage deopts on the first top-level var (every script-created global adds an own property the fixed layout can't express). Paying shape initialization and an immediate deopt-rebuild made evaluate-style workloads sharply worse in an earlier attempt at this flip (Evaluate("1") +72%), which is why it was parked. With hybrid mode the shape survives script-created globals, so both the construction win and the evaluate win land:

Results (BenchmarkDotNet default jobs, .NET 10)

Benchmark main this PR time alloc
EngineConstruction BuildEngine 1.275 µs / 13.71 KB 0.515 µs / 6.63 KB −60% −52%
EngineConstruction EvaluateSimple 1.399 µs / 14.33 KB 0.683 µs / 7.24 KB −51% −49%
MinimalScript Execute 2.692 µs / 17.33 KB 1.944 µs / 10.36 KB −28% −40%
MinimalScript Execute_ParsedScript 1.666 µs / 15.41 KB 0.909 µs / 8.45 KB −45% −45%

No-regression gates:

  • GlobalAccessBenchmarks: GlobalVarLoop/GlobalUpdateLoop neutral in A/B and reversed-order B/A; LocalVarLoop neutral at --launchCount 5 (single-launch deltas were PGO mode-locking — both sides alternate ~38.5/~41.5 ms launch modes).
  • StopwatchBenchmark: classic neutral-or-better; modern prepared +1.1% at --launchCount 3, within the noise of sibling rows that flip sign between runs.
  • SunSpider, full 26-file suite: all rows within the suite's usual ±4% run-to-run band with mixed signs (mean ≈ 0); date-format-xparb −7.8% (it extends Date.prototype, which now stays shaped). The one outlier, controlflow-recursive +11% in the single-launch pair, disappears at --launchCount 5: baseline 67.86 ms vs this PR 67.72 ms with fully overlapping launch distributions (the original baseline reading of 64.5 ms is not reproduced by any of its own five launches).

Semantics

For a host in builtin-shape mode:

  • Add a new own string property (script global via var/assignment, Engine.SetValue, prototype extension like Date.prototype.format = ...): joins the hybrid side dictionary; shape stays live. Own-key order remains correct because every addition post-dates every shape name (shape names, then dictionary, then symbols).
  • Integer-like keys must sort before string keys in own-key order, which shape-then-additions cannot express ⇒ full deopt (unchanged).
  • Delete / redefine of a shape name ⇒ full deopt (unchanged), which since Keep builtin-shape deopt lazy: unmaterialized function slots stay deferred #2588 preserves laziness via LazyBuiltinSlotDescriptor.
  • Write to a shape name (parseInt = 5, Object.defineProperty on an existing slot): in-place slot replace + version bump, no deopt.

Every read/enumeration surface consults dictionary-then-shape: GetOwnProperty, GetOwnProperties, own-key enumeration, ProbeOwnProperty, the member-expression inline caches (validated by _propertiesVersion, which hybrid additions bump), and the global-environment binding protocol (TryGetBinding / GetBindingValue / CanDeclareGlobalVar / CreateGlobalVarBinding / SetFromMutableBinding). The global identifier-binding cache now also learns shape-slot descriptors — materialization is identity-stable and never bumps the version, while any slot redefine/deopt does, so the existing validation scheme covers them.

GlobalObject's three non-declarative entries (parseInt/parseFloat — spec identity with Number.parseInt/parseFloat — and the globalThis self-reference) fill reserved [JsInstanceSlot]s with the same lazy descriptors as before.

Also fixes a latent hole on the same path: SetPropertyUnlikely (non-JsString keys) wrote straight to the raw dictionary, bypassing shape handling; it now routes through the mode-aware SetProperty(Key).

Testing

  • Jint.Tests, Jint.Tests.PublicInterface green (net10.0 + net472)
  • Full test262: 99,260 passed, 0 failed, 133 skipped — global property attributes, own-key ordering and var/function/lexical global-binding semantics are heavily covered there
  • REPL smoke: script globals visible through globalThis, key order (shape names before additions), var Array resolving to the slot in place, non-configurable var surviving delete

🤖 Generated with Claude Code

https://claude.ai/code/session_01S2U2y9voMQV9rvCkYeQbka

lahma and others added 2 commits July 7, 2026 01:39
… storage

Finishes the built-in shapes rollout by flipping its largest remaining
dictionary host - GlobalObject, 71 entries built per realm - and introduces
the mechanism that makes the flip viable: a hybrid mode where a builtin-shaped
host gains new own string properties in a side dictionary while the shape
keeps serving its slots.

Script-created globals (var/function bindings, host SetValue) and prototype
extensions no longer deopt the shape; integer-like keys and deletes/redefines
of shape names still do (own-key order). Read and enumeration surfaces consult
dictionary-then-shape; the global identifier-binding cache learns shape-slot
descriptors (materialization is identity-stable and version-neutral, while
slot redefines and deopts bump the version).

BuildEngine -60% time / -52% alloc, EvaluateSimple -51%/-49%, minimal script
Execute -28%/-40% (prepared -45%/-45%); GlobalAccess/Stopwatch/SunSpider
neutral. Full test262 green.

Also fixes SetPropertyUnlikely (non-JsString keys) bypassing shape handling
by writing straight to the raw dictionary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2U2y9voMQV9rvCkYeQbka
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