Extend the member-call fast path to primitive string receivers#2717
Merged
Conversation
Calls like str.slice(...) / str.charCodeAt(i) previously always fell to the Reference slow path, re-resolving the method through Engine.GetValue -> TryHandleStringValue on every call (profiles show StringInstance.GetOwnProperty 4.4% + TryHandleStringValue 3.1% on dromaeo-object-string and Engine.GetValue 8.4% on string-base64, all from JintCallExpression). GetCalleeForCall now resolves the method for a JsString receiver directly from the realm's %String.prototype%, caching the descriptor per AST node under the same holder-identity + _propertiesVersion guard as the existing prototype-method inline cache. In-place replacement (String.prototype.slice = fn) flows through the cached descriptor's live value; defineProperty/delete bump the version and re-resolve. Safety rails: - Build-time denylist keeps names that can be OWN properties of a boxed string out of the lane: `length` and any name whose ToNumber coercion is a non-negative int32 (StringInstance.GetOwnProperty coerces the name, so "0", "01", "0x1", "1e1", " 1", "-0" and "" all address characters). Denied names keep the unchanged Reference slow path, so own-property shadowing of prototype plants is preserved. - Accessor-backed slots are never cached: they fall to the Reference path so getter side effects run exactly once even when the result is non-callable. - The receiver is never boxed or materialized (no ToString/Length touch), keeping lazy CustomString implementations intact; `this` is the primitive itself, matching Reference.ThisValue on the slow path. - A miss on the direct prototype (method absent or found deeper, e.g. Object.prototype.hasOwnProperty) falls back to the Reference path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lahma
enabled auto-merge (squash)
July 21, 2026 10:00
This was referenced Jul 21, 2026
This was referenced Jul 22, 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.
Part of the V8-gap performance campaign (#1775 follow-up). CPU profiles of the two string comparison rows showed the callee method for
str.slice(...)-style calls being re-resolved on every invocation (StringInstance.GetOwnProperty+Engine.TryHandleStringValue≈ 7.5% on object-string,Engine.GetValue(Reference)8.4% on base64 — all driven fromJintCallExpression).Change
JintMemberExpression.GetCalleeForCall— which already serves object receivers from the member IC — gains a string-receiver lane: the method is resolved fromString.prototypeand the descriptor is cached per AST node under the same holder-identity +_propertiesVersionguard as the existing prototype-method cache (caching the descriptor, not the callable, keeps in-placeString.prototype.slice = fnreplacement honored — its value is read fresh per call).thisis the primitive itself, matching the slow path's this-binding exactly.Safety rails:
lengthand any name that Jint'sStringInstance.GetOwnPropertywould treat as an own index property — the check mirrors the runtimeToNumbercoercion exactly ("0x1","1e1"," 1","01","-0"all denied), so prototype-planted functions can never shadow own character properties through the cache.RavenApiUsageTests.CanInheritCustomStringstays green.Numbers (default BDN job, same-base A/B vs main)
Gates
Jint.Tests 3658/3595 (net10/net472), PublicInterface 114/114 both TFMs, Test262 99,431 passed / 0 failed. New tests cover loop correctness, mid-loop prototype method replacement (assignment, defineProperty, delete),
lengthsemantics, and index-shaped prototype plants.🤖 Generated with Claude Code