From 2e5f0ebf597ea8122d2f26f81a2c0462b5c09573 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Fri, 2 Oct 2026 23:01:48 +0000 Subject: [PATCH 1/3] [JSC] A StringObject with immutable properties keeps the compilers' fast conversion to a string The DFG and the FTL turn a StringObject into its string without a call (ToString with StringObjectUse or StringOrStringObjectUse) behind a CheckStructure for the realm's stringObjectStructure(), which says that the object has no toString, valueOf or Symbol.toPrimitive of its own and still has String.prototype. JSObject::makePropertiesImmutable() gives the object another structure, so the check failed, and Graph::canOptimizeStringObjectAccess() gives up on a site for good once it has a BadCache exit: the site made a call from then on, for ordinary StringObjects too. The structure makePropertiesImmutable() makes of stringObjectStructure() says as much about an object, so the check accepts it. - JSGlobalObject keeps that structure once there is one (stringObjectStructureWithImmutableProperties()), alive, so that it is not collected and made again as another Structure. - FixupPhase::addCheckStructureForOriginalStringObjectUse(), which every such check comes from, adds it to the StructureSet. - While there is none, the set has the one structure, and the code watches stringObjectImmutablePropertiesWatchpointSet(), which JSGlobalObject::didMakePropertiesImmutable() fires when it sets the structure. Code compiled before that is thrown away, which leaves no exit against its sites, and is compiled again with both. A realm in which no StringObject has immutable properties gets the code it got. --- .../immutable-properties-string-object.js | 140 ++++++++++++++++++ Source/JavaScriptCore/dfg/DFGFixupPhase.cpp | 13 +- .../JavaScriptCore/runtime/JSGlobalObject.cpp | 13 ++ .../JavaScriptCore/runtime/JSGlobalObject.h | 7 + Source/JavaScriptCore/runtime/JSObject.cpp | 2 + 5 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 JSTests/stress/immutable-properties-string-object.js diff --git a/JSTests/stress/immutable-properties-string-object.js b/JSTests/stress/immutable-properties-string-object.js new file mode 100644 index 0000000000000..bbf83974620bf --- /dev/null +++ b/JSTests/stress/immutable-properties-string-object.js @@ -0,0 +1,140 @@ +// The optimizing compilers turn a StringObject into its string without a call where they can check that nobody has touched the object, +// which they do by its structure. A StringObject whose properties were made immutable passes that check too: the operations below +// give the same results for it, and a site that sees one is not reoptimized for it. + +function shouldBe(actual, expected, message) { + if (actual !== expected) + throw new Error((message ? message + ": " : "") + "expected " + String(expected) + " but got " + String(actual)); +} + +// Enough calls for a site to be compiled, and then to exit often enough to be reoptimized if it is going to be. +let hot = Math.ceil(testLoopCount / 4); + +let operations = { + add: "object + suffix", + addLeft: "suffix + object", + template: "`${object}${suffix}`", + stringCall: "String(object) + suffix", + toStringCall: "object.toString() + suffix", + valueOfCall: "object.valueOf() + suffix", + charAt: "object.charAt(1) + suffix", + index: "object[1] + suffix", + length: "object.length + suffix", + equals: "(object == 'abc') + suffix", + concat: "object.concat(suffix)", +}; + +// (Functions with the same source share what the compilers have learnt about it, exits included: each site gets a source of its own.) +let sites = 0; +function makeSite(source) { + let site = new Function("object", "suffix", "return " + source + "; // site " + ++sites); + noInline(site); + return site; +} + +function run(site, pick, expected, label) { + for (let i = 0; i < hot; ++i) + shouldBe(site(pick(i), "-" + (i & 3)), expected(i), label); +} + +// The realm's first such object appears when sites are hot already. Their code is thrown away, once, and no exit is held against them. +{ + let hotSites = Object.values(operations).map(makeSite), ordinary = new String("abc"); + let results = hotSites.map(site => { run(site, () => ordinary, i => site(ordinary, "-" + (i & 3))); return site(ordinary, "!"); }); + let immutable = $vm.makePropertiesImmutable(new String("abc")); + hotSites.forEach((site, index) => { + shouldBe(site(immutable, "!"), results[index]); + run(site, i => i & 1 ? immutable : ordinary, i => site(ordinary, "-" + (i & 3))); + }); +} + +// A call of a method looks the method up by the object's structure first, and a site learns a second structure as it does for any +// other object, by being reoptimized if it was compiled too early. Those are here for their results. +let looksUpAMethod = new Set(["toStringCall", "valueOfCall", "charAt", "concat"]); + +for (let [name, source] of Object.entries(operations)) { + let reference = makeSite(source); + let expected = i => reference(new String("abc"), "-" + (i & 3)); + + // 1. The site is hot on ordinary objects, and then sees objects with immutable properties. + let later = makeSite(source), ordinary = new String("abc"); + run(later, () => ordinary, expected, name + ", ordinary"); + let immutable = $vm.makePropertiesImmutable(new String("abc")); + run(later, () => immutable, expected, name + ", immutable"); + run(later, i => i & 1 ? immutable : ordinary, expected, name + ", both"); + + // 2. The site sees both from the start. + let fromTheStart = makeSite(source); + run(fromTheStart, i => i & 1 ? immutable : ordinary, expected, name + ", both from the start"); + + // 3. The same number of calls with ordinary objects alone: whatever reoptimization this configuration causes by itself. + let control = makeSite(source); + for (let round = 0; round < 3; ++round) + run(control, () => ordinary, expected, name + ", control"); + + if (!looksUpAMethod.has(name)) { + shouldBe(reoptimizationRetryCount(later) <= reoptimizationRetryCount(control), true, name + ": reoptimized for an object with immutable properties, which it saw later"); + shouldBe(reoptimizationRetryCount(fromTheStart) <= reoptimizationRetryCount(control), true, name + ": reoptimized for an object with immutable properties"); + } + shouldBe($vm.hasImmutableProperties(immutable), true); +} + +// What the check is for still holds. An object that was touched first does not pass it, immutable properties or not. +{ + let site = makeSite("object + suffix"); + let ordinary = new String("abc"); + let withOwnToString = new String("abc"); + withOwnToString.toString = () => "own toString"; + withOwnToString.valueOf = () => "own valueOf"; + $vm.makePropertiesImmutable(withOwnToString); + let withOtherPrototype = Object.setPrototypeOf(new String("abc"), { __proto__: String.prototype, valueOf() { return "inherited valueOf"; } }); + $vm.makePropertiesImmutable(withOtherPrototype); + class Derived extends String { valueOf() { return "derived valueOf"; } } + let derived = $vm.makePropertiesImmutable(new Derived("abc")); + for (let i = 0; i < hot; ++i) { + shouldBe(site(ordinary, "!"), "abc!"); + shouldBe(site(withOwnToString, "!"), "own valueOf!"); + shouldBe(site(withOtherPrototype, "!"), "inherited valueOf!"); + shouldBe(site(derived, "!"), "derived valueOf!"); + } +} + +// Each realm has its own. +{ + let other = $vm.createGlobalObject(); + let site = makeSite("object + suffix"); + let objects = [new String("abc"), $vm.makePropertiesImmutable(new String("abc")), new other.String("abc"), $vm.makePropertiesImmutable(new other.String("abc"))]; + for (let i = 0; i < hot; ++i) + shouldBe(site(objects[i & 3], "!"), "abc!"); +} + +// A change to String.prototype reaches such an object, hot, as it reaches any other. +{ + let site = makeSite("object + suffix"); + let immutable = $vm.makePropertiesImmutable(new String("abc")); + for (let i = 0; i < hot; ++i) + shouldBe(site(immutable, "!"), "abc!"); + let valueOf = String.prototype.valueOf; + String.prototype.valueOf = function () { return "replaced"; }; + for (let i = 0; i < hot; ++i) + shouldBe(site(immutable, "!"), "replaced!"); + String.prototype.valueOf = valueOf; + shouldBe(site(immutable, "!"), "abc!"); +} + +// The structure is kept: after every such object is gone, a new one gets the structure compiled code checks for. +{ + let site = makeSite("object + suffix"); + for (let round = 0; round < 3; ++round) { + for (let i = 0; i < hot; ++i) + shouldBe(site($vm.makePropertiesImmutable(new String("abc")), "!"), "abc!"); + fullGC(); + } + let control = makeSite("object + suffix"); + for (let round = 0; round < 3; ++round) { + for (let i = 0; i < hot; ++i) + shouldBe(control(new String("abc"), "!"), "abc!"); + fullGC(); + } + shouldBe(reoptimizationRetryCount(site) <= reoptimizationRetryCount(control), true, "reoptimized after a collection"); +} diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp index 2f50e34cf27ff..3aafdebd4e8f7 100644 --- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp +++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp @@ -4004,8 +4004,19 @@ class FixupPhase : public Phase { { RELEASE_ASSERT(useKind == StringObjectUse || useKind == StringOrStringObjectUse); + JSGlobalObject* globalObject = m_graph.globalObjectFor(node->origin.semantic); StructureSet set; - set.add(m_graph.globalObjectFor(node->origin.semantic)->stringObjectStructure()); + set.add(globalObject->stringObjectStructure()); + // A StringObject with that structure whose properties were made immutable has no more properties of its own, and the same + // prototype. While no such structure exists, the check is for the one structure, and this code goes when one appears. + InlineWatchpointSet& immutablePropertiesWatchpointSet = globalObject->stringObjectImmutablePropertiesWatchpointSet(); + if (immutablePropertiesWatchpointSet.isStillValid()) { + m_graph.freeze(globalObject); + m_graph.watchpoints().addLazily(immutablePropertiesWatchpointSet); + } else { + WTF::loadLoadFence(); + set.add(globalObject->stringObjectStructureWithImmutableProperties()); + } if (useKind == StringOrStringObjectUse) set.add(vm().stringStructure.get()); diff --git a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp index d4cadc8068ae3..fd318e45599b2 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObject.cpp +++ b/Source/JavaScriptCore/runtime/JSGlobalObject.cpp @@ -2879,6 +2879,18 @@ void JSGlobalObject::clearStructureCache(VM& vm) m_structureCacheClearedWatchpointSet.fireAll(vm, "Clearing StructureCache"); } +// Compiled code recognizes an object nobody has touched by one of this realm's original structures. The structure such an object gets +// when its properties are made immutable says as much about it, so it is kept here, alive, for the compilers to accept as well. +// Code that was compiled when there was none is thrown away, which, unlike an exit, does not count against the code's site. +void JSGlobalObject::didMakePropertiesImmutable(VM& vm, Structure* oldStructure, Structure* newStructure) +{ + if (oldStructure != stringObjectStructure() || m_stringObjectStructureWithImmutableProperties) + return; + m_stringObjectStructureWithImmutableProperties.set(vm, this, newStructure); + WTF::storeStoreFence(); + m_stringObjectImmutablePropertiesWatchpointSet.fireAll(vm, "A StringObject's properties were made immutable"); +} + void JSGlobalObject::haveABadTime(VM& vm) { ASSERT(&vm == &this->vm()); @@ -3203,6 +3215,7 @@ void JSGlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_promiseCapabilityObjectStructure.visit(visitor); thisObject->m_promiseAllSettledFulfilledResultStructure.visit(visitor); thisObject->m_promiseAllSettledRejectedResultStructure.visit(visitor); + visitor.append(thisObject->m_stringObjectStructureWithImmutableProperties); visitor.append(thisObject->m_regExpMatchesArrayStructure); visitor.append(thisObject->m_regExpMatchesArrayWithIndicesStructure); visitor.append(thisObject->m_regExpMatchesIndicesArrayStructure); diff --git a/Source/JavaScriptCore/runtime/JSGlobalObject.h b/Source/JavaScriptCore/runtime/JSGlobalObject.h index f8d47bd27003e..57a40935e8341 100644 --- a/Source/JavaScriptCore/runtime/JSGlobalObject.h +++ b/Source/JavaScriptCore/runtime/JSGlobalObject.h @@ -438,6 +438,7 @@ class JSGlobalObject : public JSSegmentedVariableObject { WriteBarrierStructureID m_mapIteratorStructure; WriteBarrierStructureID m_setIteratorStructure; WriteBarrierStructureID m_wrapForValidIteratorStructure; + WriteBarrierStructureID m_stringObjectStructureWithImmutableProperties; WriteBarrierStructureID m_regExpMatchesArrayStructure; WriteBarrierStructureID m_regExpMatchesArrayWithIndicesStructure; WriteBarrierStructureID m_regExpMatchesIndicesArrayStructure; @@ -584,6 +585,7 @@ class JSGlobalObject : public JSSegmentedVariableObject { InlineWatchpointSet m_numberToStringWatchpointSet { IsWatched }; InlineWatchpointSet m_stringToStringWatchpointSet { IsWatched }; InlineWatchpointSet m_stringValueOfWatchpointSet { IsWatched }; + InlineWatchpointSet m_stringObjectImmutablePropertiesWatchpointSet { IsWatched }; InlineWatchpointSet m_objectPrototypeValueOfWatchpointSet { IsWatched }; InlineWatchpointSet m_arrayPrototypeValueOfWatchpointSet { IsWatched }; InlineWatchpointSet m_structureCacheClearedWatchpointSet { IsWatched }; @@ -646,6 +648,8 @@ class JSGlobalObject : public JSSegmentedVariableObject { InlineWatchpointSet& arraySymbolToPrimitiveWatchpointSet() LIFETIME_BOUND { return m_arraySymbolToPrimitiveWatchpointSet; } InlineWatchpointSet& stringToStringWatchpointSet() LIFETIME_BOUND { return m_stringToStringWatchpointSet; } InlineWatchpointSet& stringValueOfWatchpointSet() LIFETIME_BOUND { return m_stringValueOfWatchpointSet; } + // Valid until stringObjectStructureWithImmutableProperties() is set. + InlineWatchpointSet& stringObjectImmutablePropertiesWatchpointSet() LIFETIME_BOUND { return m_stringObjectImmutablePropertiesWatchpointSet; } InlineWatchpointSet& objectPrototypeValueOfWatchpointSet() LIFETIME_BOUND { return m_objectPrototypeValueOfWatchpointSet; } InlineWatchpointSet& arrayPrototypeValueOfWatchpointSet() LIFETIME_BOUND { return m_arrayPrototypeValueOfWatchpointSet; } InlineWatchpointSet& regExpPrimordialPropertiesWatchpointSet() LIFETIME_BOUND { return m_regExpPrimordialPropertiesWatchpointSet; } @@ -1062,6 +1066,8 @@ class JSGlobalObject : public JSSegmentedVariableObject { Structure* setIteratorStructure() const { return m_setIteratorStructure.get(); } Structure* wrapForValidIteratorStructure() const { return m_wrapForValidIteratorStructure.get(); } Structure* stringObjectStructure() const { return m_stringObjectStructure.get(); } + // What JSObject::makePropertiesImmutable() makes of stringObjectStructure(). Null until it has: see didMakePropertiesImmutable(). + Structure* stringObjectStructureWithImmutableProperties() const { return m_stringObjectStructureWithImmutableProperties.get(); } Structure* symbolObjectStructure() const { return m_symbolObjectStructure.get(); } Structure* iteratorResultObjectStructure() const { return m_iteratorResultObjectStructure.get(this); } Structure* iteratorResultObjectStructureConcurrently() const { return m_iteratorResultObjectStructure.getConcurrently(); } @@ -1263,6 +1269,7 @@ class JSGlobalObject : public JSSegmentedVariableObject { } void haveABadTime(VM&); + void didMakePropertiesImmutable(VM&, Structure* oldStructure, Structure* newStructure); void notifyArrayBufferDetaching(); diff --git a/Source/JavaScriptCore/runtime/JSObject.cpp b/Source/JavaScriptCore/runtime/JSObject.cpp index d328710aa3498..62fe531a38ddf 100644 --- a/Source/JavaScriptCore/runtime/JSObject.cpp +++ b/Source/JavaScriptCore/runtime/JSObject.cpp @@ -3133,6 +3133,8 @@ bool JSObject::makePropertiesImmutable(VM& vm) if (copyOnWriteButterfly) nukeStructureAndSetButterfly(vm, oldStructureID, copyOnWriteButterfly->toButterfly()); setStructure(vm, newStructure); + if (JSGlobalObject* realm = oldStructure->realm()) + realm->didMakePropertiesImmutable(vm, oldStructure, newStructure); if (mayBePrototype()) [[unlikely]] vm.invalidateStructureChainIntegrity(VM::StructureChainIntegrityEvent::Change); return true; From ba43cb23e5bc8c42515bb3ef3fd8857c78246967 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Fri, 2 Oct 2026 23:50:21 +0000 Subject: [PATCH 2/3] [JSC] The check for an untouched StringObject is for the realm of the conversion FixupPhase::addCheckStructureForOriginalStringObjectUse() took the realm from the origin of the node that produces the object, and its callers ask Graph::canOptimizeStringObjectAccess() about the origin of the conversion, which is where String.prototype is watched. The two differ when the object comes from an inlined function of another realm: the check accepted that realm's StringObjects while nobody watched its String.prototype, so compiled code went on returning the string after that realm replaced String.prototype.valueOf. (So it did before there were immutable properties.) The structures and the watchpoint set now come from the origin of the conversion. --- .../immutable-properties-string-object.js | 23 +++++++++++++++++++ Source/JavaScriptCore/dfg/DFGFixupPhase.cpp | 4 +++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/JSTests/stress/immutable-properties-string-object.js b/JSTests/stress/immutable-properties-string-object.js index bbf83974620bf..895c5b7d444c6 100644 --- a/JSTests/stress/immutable-properties-string-object.js +++ b/JSTests/stress/immutable-properties-string-object.js @@ -108,6 +108,29 @@ for (let [name, source] of Object.entries(operations)) { shouldBe(site(objects[i & 3], "!"), "abc!"); } +// The object comes from an inlined function of another realm: it is that realm's String.prototype that counts, which the site, in this +// realm, does not watch. Such an object, immutable properties or not, is not taken for one of this realm's. +for (let change of [object => object, object => $vm.makePropertiesImmutable(object)]) { + let other = $vm.createGlobalObject(); + change(new other.String("abc")); + let object = change(new other.String("abc")); + let fresh = other.Function("return new String('abc'); // site " + ++sites); + let existing = other.Function("object", "return object; // site " + ++sites); + let addFresh = new Function("make", "suffix", "return make() + suffix; // site " + ++sites); + let addExisting = new Function("make", "object", "suffix", "return make(object) + suffix; // site " + ++sites); + noInline(addFresh); + noInline(addExisting); + for (let i = 0; i < hot; ++i) { + shouldBe(addFresh(fresh, "!"), "abc!"); + shouldBe(addExisting(existing, object, "!"), "abc!"); + } + other.String.prototype.valueOf = other.Function("return 'replaced';"); + for (let i = 0; i < hot; ++i) { + shouldBe(addFresh(fresh, "!"), "replaced!"); + shouldBe(addExisting(existing, object, "!"), "replaced!"); + } +} + // A change to String.prototype reaches such an object, hot, as it reaches any other. { let site = makeSite("object + suffix"); diff --git a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp index 3aafdebd4e8f7..5058594bfe78a 100644 --- a/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp +++ b/Source/JavaScriptCore/dfg/DFGFixupPhase.cpp @@ -4004,7 +4004,9 @@ class FixupPhase : public Phase { { RELEASE_ASSERT(useKind == StringObjectUse || useKind == StringOrStringObjectUse); - JSGlobalObject* globalObject = m_graph.globalObjectFor(node->origin.semantic); + // The realm of the conversion, whose String.prototype canOptimizeStringObjectAccess() has the caller watch. (The node may come + // from an inlined function of another realm, whose String.prototype nobody here watches.) + JSGlobalObject* globalObject = m_graph.globalObjectFor(origin.semantic); StructureSet set; set.add(globalObject->stringObjectStructure()); // A StringObject with that structure whose properties were made immutable has no more properties of its own, and the same From 74c903ea135feeb3d62023449519994b4f2af189 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Sat, 3 Oct 2026 00:22:02 +0000 Subject: [PATCH 3/3] immutable-properties-string-object.js: three parts that could not fail can Each was checked against an engine with the line it is for taken out. - "The structure is kept" ran after String.prototype.valueOf had been replaced, which ends the fast conversion for every site of the realm compiled afterwards; and the structure never died, because earlier parts still held objects and code that use it. It has a realm of its own now, whose only such object is made in a function that has returned, and other structures take what a collected one would have left. It fails without the visitor.append() in JSGlobalObject::visitChildrenImpl(). The replacement of valueOf goes last. - With an object from another realm that has immutable properties, what produced the object was in this realm's code once everything was inlined (the function that makes the properties immutable, and the caller's own argument). It is a call that is not inlined and a load from an object now, both in the other realm's code, and the factory applies the change to what it makes. Each fails with the realm taken from the node's origin. --- .../immutable-properties-string-object.js | 71 +++++++++++-------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/JSTests/stress/immutable-properties-string-object.js b/JSTests/stress/immutable-properties-string-object.js index 895c5b7d444c6..15d8579860e7e 100644 --- a/JSTests/stress/immutable-properties-string-object.js +++ b/JSTests/stress/immutable-properties-string-object.js @@ -109,29 +109,61 @@ for (let [name, source] of Object.entries(operations)) { } // The object comes from an inlined function of another realm: it is that realm's String.prototype that counts, which the site, in this -// realm, does not watch. Such an object, immutable properties or not, is not taken for one of this realm's. +// realm, does not watch. Such an object, immutable properties or not, is not taken for one of this realm's. (What produces the object +// has to be in the other realm's code once that is inlined: a call that is not inlined itself, and a load from an object.) for (let change of [object => object, object => $vm.makePropertiesImmutable(object)]) { + noInline(change); let other = $vm.createGlobalObject(); change(new other.String("abc")); - let object = change(new other.String("abc")); - let fresh = other.Function("return new String('abc'); // site " + ++sites); - let existing = other.Function("object", "return object; // site " + ++sites); - let addFresh = new Function("make", "suffix", "return make() + suffix; // site " + ++sites); - let addExisting = new Function("make", "object", "suffix", "return make(object) + suffix; // site " + ++sites); + let holder = { object: change(new other.String("abc")) }; + let fresh = other.Function("change", "return change(new String('abc')); // site " + ++sites); + let existing = other.Function("holder", "return holder.object; // site " + ++sites); + let addFresh = new Function("make", "change", "suffix", "return make(change) + suffix; // site " + ++sites); + let addExisting = new Function("make", "holder", "suffix", "return make(holder) + suffix; // site " + ++sites); noInline(addFresh); noInline(addExisting); for (let i = 0; i < hot; ++i) { - shouldBe(addFresh(fresh, "!"), "abc!"); - shouldBe(addExisting(existing, object, "!"), "abc!"); + shouldBe(addFresh(fresh, change, "!"), "abc!"); + shouldBe(addExisting(existing, holder, "!"), "abc!"); } other.String.prototype.valueOf = other.Function("return 'replaced';"); for (let i = 0; i < hot; ++i) { - shouldBe(addFresh(fresh, "!"), "replaced!"); - shouldBe(addExisting(existing, object, "!"), "replaced!"); + shouldBe(addFresh(fresh, change, "!"), "replaced!"); + shouldBe(addExisting(existing, holder, "!"), "replaced!"); } } -// A change to String.prototype reaches such an object, hot, as it reaches any other. +// The structure is kept. A realm's only such object is gone, and what a collected Structure would have left is taken by others: a new +// such object gets the structure that compiled code checks for. +{ + let other = $vm.createGlobalObject(); + let makeAndDrop = () => { $vm.makePropertiesImmutable(new other.String("abc")); }; + let overwriteTheStack = depth => depth ? overwriteTheStack(depth - 1) + 1 : 0; + noInline(makeAndDrop); + noInline(overwriteTheStack); + makeAndDrop(); + overwriteTheStack(200); + let shapes = []; + for (let round = 0; round < 3; ++round) { + fullGC(); + for (let i = 0; i < 500; ++i) + shapes.push({ ["property" + round + "_" + i]: i }); + } + let makeSiteThere = () => { + let site = other.Function("object", "suffix", "return object + suffix; // site " + ++sites); + noInline(site); + return site; + }; + let site = makeSiteThere(), control = makeSiteThere(); + for (let i = 0; i < hot * 3; ++i) { + shouldBe(site($vm.makePropertiesImmutable(new other.String("abc")), "!"), "abc!"); + shouldBe(control(new other.String("abc"), "!"), "abc!"); + } + shouldBe(reoptimizationRetryCount(site) <= reoptimizationRetryCount(control), true, "reoptimized after a collection"); +} + +// A change to String.prototype reaches such an object, hot, as it reaches any other. (Last: no site of this realm that is compiled after +// this gets the fast conversion, whatever is put back.) { let site = makeSite("object + suffix"); let immutable = $vm.makePropertiesImmutable(new String("abc")); @@ -144,20 +176,3 @@ for (let change of [object => object, object => $vm.makePropertiesImmutable(obje String.prototype.valueOf = valueOf; shouldBe(site(immutable, "!"), "abc!"); } - -// The structure is kept: after every such object is gone, a new one gets the structure compiled code checks for. -{ - let site = makeSite("object + suffix"); - for (let round = 0; round < 3; ++round) { - for (let i = 0; i < hot; ++i) - shouldBe(site($vm.makePropertiesImmutable(new String("abc")), "!"), "abc!"); - fullGC(); - } - let control = makeSite("object + suffix"); - for (let round = 0; round < 3; ++round) { - for (let i = 0; i < hot; ++i) - shouldBe(control(new String("abc"), "!"), "abc!"); - fullGC(); - } - shouldBe(reoptimizationRetryCount(site) <= reoptimizationRetryCount(control), true, "reoptimized after a collection"); -}