Add call-site constructor cache with zero-arg leaf fast path for new Date()#2624
Merged
Conversation
…Date() Every new Date() re-tested IsConstructor and entered the generic Engine.Construct with its call-stack push/pop and recursion-depth compare - 171k times per stopwatch benchmark op - although the resolved constructor is version-validated by the global identifier cache and its verdicts are immutable per instance. JintNewExpression now remembers the last constructor object it constructed through. An identity hit skips the IsConstructor test; when the cached constructor is additionally a zero-argument leaf built-in (new virtual Function.IsZeroArgLeafConstructor, overridden by DateConstructor when the engine runs the exact stock DefaultTimeSystem), the construct bypasses Engine.Construct entirely - such a constructor runs no user-observable code and cannot raise a JavaScript error, so the call-stack frame is dead. Custom or derived time systems keep the frame (a throwing GetUtcNow would otherwise lose its new Date() stack entry), as do debug-mode engines and suspendable frames. A cache miss (callee reassigned, another engine sharing the prepared node) falls through to the generic path and re-caches - never a permanent decline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BY8HoKam5H8vTPn1bMY2Hv
lahma
enabled auto-merge (squash)
July 10, 2026 18:36
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.
Every
new Date()re-testedIsConstructorand entered the genericEngine.Construct— call-stack push/pop, recursion-depth compare — although the resolved constructor is already version-validated by the global identifier cache and its verdicts are immutable per instance. The stopwatch row pays this 171k times per op.JintNewExpressionnow remembers the last constructor object it constructed through (monomorphic,ReferenceEquals-validated — identity pins the realm and the per-instance verdicts):IsConstructortype-test.internal virtual Function.IsZeroArgLeafConstructor, overridden byDateConstructoronly when the engine runs the exact stockDefaultTimeSystem— the construct bypassesEngine.Constructentirely. Such a constructor runs no user-observable code and cannot raise a JavaScript error, so the call-stack frame is dead weight. Derived or custom time systems keep the frame (a throwingGetUtcNowwould otherwise lose itsnew Date()stack entry), as do debug-mode engines and suspendable frames;LastSyntaxElementis still set so a .NET-level failure resolves its script location.Scope is deliberately Date-first: the mechanism is general, but
Errorcaptures the stack (never leaf) and other built-ins aren't exercised by any row today.Six pins in
NewExpressionCacheTests: realm-prototype identity over a warmed call site, mid-scriptDatereassignment picked up at a warm site, subclassnewTargetprototypes, two engines alternating over one shared prepared script, custom-ITimeSystemclocks served through the generic path, and a throwing custom time system surfacing its exception per construct.Benchmarks (before/after, same machine, sequential)
Stopwatch rows moved within their single-launch mode bands in both directions (classic Execute −1.1%, classic ParsedScript +5.5%, modern flat; the mechanism's expected row share is only ~2%, inside the bands — the isolated driver row above is the evidence; row certification comes with the next EngineComparison refresh). PreparedAnomaly rows: times ±3..7% mixed-sign, allocations +10–20 B/op from the two new fields on
JintNewExpressionhandler instances (fresh-engine-per-op hosts rebuild handlers each op) — disclosed, negligible.Gates: Jint.Tests 3,295+3,232 pass (6 new pins), PublicInterface 82×2*, Test262 99,426 pass / 0 fail (exact baseline).
* the net472
TimeSystemTests.CanUseTimeProviderwall-clock-tolerance test currently fails on this machine on unmodified main as well (verified 3/3, ~7 ms past its 100 ms window, post-sleep timer state) — environmental, not attributable to the change; the net10 arm and all other net472 tests pass.🤖 Generated with Claude Code
https://claude.ai/code/session_01BY8HoKam5H8vTPn1bMY2Hv