[JSC] The check for an untouched StringObject is for the realm of the conversion - #766
Conversation
… conversion The DFG and the FTL turn a StringObject into its string without a call (ToString with StringObjectUse or StringOrStringObjectUse) on two conditions: a CheckStructure for a realm's stringObjectStructure(), which says the object has no toString, valueOf or Symbol.toPrimitive of its own and still has String.prototype; and watchpoints on what String.prototype has for those, which Graph::canOptimizeStringObjectAccess() sets up. The two have to be about one realm. FixupPhase::addCheckStructureForOriginalStringObjectUse() took the realm from the origin of the node that produces the object, and its callers ask canOptimizeStringObjectAccess() about the origin of the conversion. They differ when the object comes from an inlined function of another realm. The check then accepted that realm's StringObjects while nobody watched its String.prototype, and compiled code went on returning the object's string after that realm replaced String.prototype.valueOf, toString or Symbol.toPrimitive. The structure now comes from the origin of the conversion. An object of another realm fails the check and is converted by the call.
|
Preview build of a49dfb6: |
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (2)
Included review availability: This review used your included allowance. Your plan provides up to 5 included reviews per hour; 0 remain after this review. WalkthroughThe DFG fixup now selects the StringObject structure from the conversion’s semantic origin. A new stress test checks String-object conversions when methods on another realm’s ChangesString Object Conversion
Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to The change targets cross-realm String-object conversion and adds stress coverage for the described cases. No merge-blocking issue is identified in the supplied context. 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Warning Git: CodeRabbit could not clone the repository, so clone-backed analysis was skipped and this review may be incomplete. Verify repository clone access, such as SSH credentials, before requesting another full review. If clone access is intentionally unavailable, use Comment |
…per names its operand
- string-object-from-inlined-function-of-another-realm.js did not reach two of the places the compilers make the conversion from,
ToPrimitive and ToPropertyKey, which give the wrong result on main as well: "<" + object + ">" and class { static [object] = 1 }.
With the realm taken from the operand at one group of callers at a time, each group is caught by cases of its own: an addition
by add, addLeft and concat; ToString and a call of String by template and stringCall; ToPrimitive by addThree; ToPropertyKey by
propertyKey. (The fifth, for the length of a StringObject, asks for no method, so nothing can be observed.)
- The result before anything is replaced is in the table with the others.
- FixupPhase::addCheckStructureForOriginalStringObjectUse() calls its operand child. It was node, which in every caller is the
conversion. The comment says which realm it has to be and why.
Bumps `WEBKIT_VERSION` from `fb1167ebf2cb` to `1600131e46b5` (current oven-sh/WebKit `main`). The `autobuild-1600131e46b5af48bbda3559af8d8a3327230b6e` release exists. oven-sh/WebKit changes picked up: - [JSC] FTL inline-cache patchpoints must declare fpTempRegister as clobbered (oven-sh/WebKit#536) - [JSC] JSObject::makePropertiesImmutable(): an object's own properties and prototype stop changing, and its property attributes stay as they are (oven-sh/WebKit#759) - [JSC] Module records share an executable only when their sources have the same SourceOrigin and start position (oven-sh/WebKit#639) - [JSC] The check for an untouched StringObject is for the realm of the conversion (oven-sh/WebKit#766) Not built or tested locally; relying on CI.
Compiled code can go on converting a
StringObjectto its original string aftervalueOf,toStringorSymbol.toPrimitivehas been replaced on itsString.prototype, when the object comes from another realm.Cause
The DFG and the FTL turn a
StringObjectinto its string without a call (ToStringwithStringObjectUseorStringOrStringObjectUse) on two conditions:CheckStructurefor a realm'sstringObjectStructure(), which says the object has notoString,valueOforSymbol.toPrimitiveof its own and still hasString.prototype;String.prototypehas for those three, whichGraph::canOptimizeStringObjectAccess()sets up.The two have to be about one realm.
FixupPhase::addCheckStructureForOriginalStringObjectUse()took the realm from the origin of the node that produces the object, and its callers askcanOptimizeStringObjectAccess()about the origin of the conversion. They differ when the object comes from an inlined function of another realm: the check accepted that realm's objects while nobody watched itsString.prototype.Fix
The structure comes from the origin of the conversion (
node->origin.semanticbecomesorigin.semantic). An object of another realm fails the check and is converted by the call. All nine callers askcanOptimizeStringObjectAccess()aboutnode->origin.semanticand passnode->origin, so the two now agree everywhere.The helper's parameter is renamed from
nodetochild: in every callernodeis the conversion.The other places in the compilers that take a realm from a child's origin (the spread of an array and of a Set:
Graph::canDoFastSpread(), the abstract interpreter, and the two lowerings) take their watchpoints from the child as well, and say so, so they do not have this.Cost
For an object of the site's own realm nothing changes: the DFG's graph and machine code for
object + suffixare the same as onmain, line for line (499 lines), and the FTL's differ by less than two runs ofmaindiffer from each other.An object of another realm loses a fast path it should not have had. A site that sees one is reoptimized once and then stays as it is (no further compile in a million calls). Bun built with this engine, two builds that differ by this change alone, the other realm a
node:vmcontext, median ns per call over 7 processes:make() + s`${make()}${s}`String(make())The fast path could be kept for such an object by watching the child's realm, as the spread does. That is more code, and is not done here.
Test
JSTests/stress/string-object-from-inlined-function-of-another-realm.js: seven conversions (+on either side,+with three operands, a template literal,String(),concat(), a computed class member), two ways of producing the object in the other realm's code (an allocation, a load from an object), and three replacements (valueOf,toString,Symbol.toPrimitive), each replacement in a realm of its own, made while every site is hot.main, LLInt and Baselinemain, DFG and FTLThe conversion is made from five places. With the realm taken from the operand again at one of them at a time:
attemptToMakeFastStringAdd()add,addLeft,concatfixupToStringOrCallStringConstructor()template,stringCallfixupToPrimitive()addThreeToPropertyKeypropertyKeyattemptToForceStringArrayModeByToStringConversion(), forlengthIt also checks that an object of the site's own realm is still converted without a reoptimization, and that a change to this realm's
String.prototypestill reaches it.Through
run-jsc-stress-tests: 25 runs of its 17 modes on a release build and 25 on a build with assertions, no failure. It takes 44 ms in the default mode and 144 ms in the slowest measured. All ofJSTests/stress(5,931 files) ends as it does onmain, apart from this test, one test the suite skips that fails now and then onmainas well, and two tests that need more memory than the run allowed.#765 has this change as one of its commits, because what it adds takes its realm from the same place.