tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 288840fe5c1198a36b9294f7a07df25dc76b865d
parent eb924ab3efd367691af60c7388d00416886b85bb
Author: alexical <dothayer@mozilla.com>
Date:   Thu, 27 Nov 2025 06:22:35 +0000

Bug 2001383 - Allow using another object's iterator indices r=iain,nbp

Differential Revision: https://phabricator.services.mozilla.com/D273459

Diffstat:
Ajs/src/jit-test/tests/ion/iterator-indices-borrowed-iterator.js | 39+++++++++++++++++++++++++++++++++++++++
Mjs/src/jit/CodeGenerator.cpp | 51++++++++++++++++++++++++++++++++++++++-------------
Mjs/src/jit/CodeGenerator.h | 4++++
Mjs/src/jit/IonAnalysis.cpp | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mjs/src/jit/LIROps.yaml | 8++++++++
Mjs/src/jit/Lowering.cpp | 23+++++++++++++++++++++--
Mjs/src/jit/MIR.cpp | 6++++--
Mjs/src/jit/MIROps.yaml | 15++++++++-------
Mjs/src/jit/RangeAnalysis.cpp | 8+++++---
9 files changed, 197 insertions(+), 48 deletions(-)

diff --git a/js/src/jit-test/tests/ion/iterator-indices-borrowed-iterator.js b/js/src/jit-test/tests/ion/iterator-indices-borrowed-iterator.js @@ -0,0 +1,38 @@ +function test(obj, otherObj) { + var total = 0; + var keys = Object.keys(obj); + var otherKeys = Object.keys(otherObj); + if (keys.length != otherKeys.length) { + return -1; + } + for (var i = 0; i < keys.length; i++) { + var s = keys[i]; + if (otherObj.hasOwnProperty(s)) { + total += otherObj[s]; + } + } + + return total; +} + +var arr = []; +var arr2 = []; +for (var i = 0; i < 20; i++) { + var obj = {}; + var obj2 = {}; + for (var j = 0; j < i; j++) { + obj["x_" + i + "_" + j] = 1; + obj2["x_" + i + "_" + j] = 2; + } + arr.push(obj); + arr2.push(obj2); +} + +with ({}) {} +for (var i = 0; i < 2000; i++) { + var idx = i % arr.length; + assertEq(test(arr[idx], arr2[idx]), idx * 2); +} + +assertEq(-1, test({a: 1, b: 1, c: 1}, {a: 2, b: 2})); +assertEq(4, test({a: 1, b: 1, c: 1}, {a: 2, b: 2, d: 2})); +\ No newline at end of file diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp @@ -18585,15 +18585,11 @@ void CodeGenerator::visitValueToIterator(LValueToIterator* lir) { callVM<Fn, ValueToIterator>(lir); } -void CodeGenerator::visitIteratorHasIndicesAndBranch( - LIteratorHasIndicesAndBranch* lir) { - Register iterator = ToRegister(lir->iterator()); - Register object = ToRegister(lir->object()); - Register temp = ToRegister(lir->temp0()); - Register temp2 = ToRegister(lir->temp1()); - Label* ifTrue = getJumpLabelForBranch(lir->ifTrue()); - Label* ifFalse = getJumpLabelForBranch(lir->ifFalse()); - +void CodeGenerator::emitIteratorHasIndicesAndBranch(Register iterator, + Register object, + Register temp, + Register temp2, + Label* ifFalse) { // Check that the iterator has indices available. Address nativeIterAddr(iterator, PropertyIteratorObject::offsetOfIteratorSlot()); @@ -18608,6 +18604,39 @@ void CodeGenerator::visitIteratorHasIndicesAndBranch( masm.loadPtr(objShapeAddr, temp); masm.branchTestObjShape(Assembler::NotEqual, object, temp, temp2, object, ifFalse); +} + +void CodeGenerator::visitIteratorHasIndicesAndBranch( + LIteratorHasIndicesAndBranch* lir) { + Register iterator = ToRegister(lir->iterator()); + Register object = ToRegister(lir->object()); + Register temp = ToRegister(lir->temp0()); + Register temp2 = ToRegister(lir->temp1()); + Label* ifTrue = getJumpLabelForBranch(lir->ifTrue()); + Label* ifFalse = getJumpLabelForBranch(lir->ifFalse()); + + emitIteratorHasIndicesAndBranch(iterator, object, temp, temp2, ifFalse); + + if (!isNextBlock(lir->ifTrue()->lir())) { + masm.jump(ifTrue); + } +} + +void CodeGenerator::visitIteratorsMatchAndHaveIndicesAndBranch( + LIteratorsMatchAndHaveIndicesAndBranch* lir) { + Register iterator = ToRegister(lir->iterator()); + Register otherIterator = ToRegister(lir->otherIterator()); + Register object = ToRegister(lir->object()); + Register temp = ToRegister(lir->temp0()); + Register temp2 = ToRegister(lir->temp1()); + Label* ifTrue = getJumpLabelForBranch(lir->ifTrue()); + Label* ifFalse = getJumpLabelForBranch(lir->ifFalse()); + + // Check that the iterators match, and then we can use either iterator + // as a basis as if this were visitIteratorHasIndicesAndBranch + masm.branchPtr(Assembler::NotEqual, iterator, otherIterator, ifFalse); + + emitIteratorHasIndicesAndBranch(iterator, object, temp, temp2, ifFalse); if (!isNextBlock(lir->ifTrue()->lir())) { masm.jump(ifTrue); @@ -18669,7 +18698,6 @@ void CodeGenerator::visitLoadSlotByIteratorIndex( visitLoadSlotByIteratorIndexCommon(object, indexScratch, kindScratch, result); } -#ifndef JS_CODEGEN_X86 void CodeGenerator::visitLoadSlotByIteratorIndexIndexed( LLoadSlotByIteratorIndexIndexed* lir) { Register object = ToRegister(lir->object()); @@ -18684,7 +18712,6 @@ void CodeGenerator::visitLoadSlotByIteratorIndexIndexed( visitLoadSlotByIteratorIndexCommon(object, indexScratch, kindScratch, result); } -#endif void CodeGenerator::visitStoreSlotByIteratorIndexCommon(Register object, Register indexScratch, @@ -18756,7 +18783,6 @@ void CodeGenerator::visitStoreSlotByIteratorIndex( visitStoreSlotByIteratorIndexCommon(object, indexScratch, kindScratch, value); } -#ifndef JS_CODEGEN_X86 void CodeGenerator::visitStoreSlotByIteratorIndexIndexed( LStoreSlotByIteratorIndexIndexed* lir) { Register object = ToRegister(lir->object()); @@ -18771,7 +18797,6 @@ void CodeGenerator::visitStoreSlotByIteratorIndexIndexed( visitStoreSlotByIteratorIndexCommon(object, indexScratch, kindScratch, value); } -#endif void CodeGenerator::visitSetPropertyCache(LSetPropertyCache* ins) { LiveRegisterSet liveRegs = ins->safepoint()->liveRegs(); diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h @@ -273,6 +273,10 @@ class CodeGenerator final : public CodeGeneratorSpecific { void emitInstanceOf(LInstruction* ins, Register protoReg); + void emitIteratorHasIndicesAndBranch(Register iterator, Register object, + Register temp, Register temp2, + Label* ifFalse); + #ifdef DEBUG void emitAssertResultV(const ValueOperand output, const MDefinition* mir); void emitAssertGCThingResult(Register input, const MDefinition* mir); diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp @@ -5145,14 +5145,36 @@ static MDefinition* SkipIterObjectUnbox(MDefinition* ins) { return ins; } -#ifndef JS_CODEGEN_X86 static MDefinition* SkipBox(MDefinition* ins) { if (ins->isBox()) { return ins->toBox()->input(); } return ins; } -#endif + +static MObjectToIterator* FindObjectToIteratorUse(MDefinition* ins) { + for (MUseIterator use(ins->usesBegin()); use != ins->usesEnd(); use++) { + if (!(*use)->consumer()->isDefinition()) { + continue; + } + MDefinition* def = (*use)->consumer()->toDefinition(); + if (def->isGuardIsNotProxy()) { + MObjectToIterator* recursed = FindObjectToIteratorUse(def); + if (recursed) { + return recursed; + } + } else if (def->isUnbox()) { + MObjectToIterator* recursed = FindObjectToIteratorUse(def); + if (recursed) { + return recursed; + } + } else if (def->isObjectToIterator()) { + return def->toObjectToIterator(); + } + } + + return nullptr; +} bool jit::OptimizeIteratorIndices(const MIRGenerator* mir, MIRGraph& graph) { bool changed = false; @@ -5198,7 +5220,8 @@ bool jit::OptimizeIteratorIndices(const MIRGenerator* mir, MIRGraph& graph) { continue; } - // Given the following structure (that occurs inside for-in loops): + // Given the following structure (that occurs inside for-in loops or + // when iterating a scalar-replaced Object.keys result): // obj: some object // iter: ObjectToIterator <obj> // iterLoad: IteratorMore <iter> | LoadIteratorElement <iter, index> @@ -5215,11 +5238,33 @@ bool jit::OptimizeIteratorIndices(const MIRGenerator* mir, MIRGraph& graph) { // 3. If the property access is a SetProp, then we can use the contents // of the indices array to find the correct slots faster than the // megamorphic cache. + // + // In some cases involving Object.keys, we can also end up with a pattern + // like this: + // + // obj1: some object + // obj2: some object + // iter1: ObjectToIterator <obj1> + // iter2: ObjectToIterator <obj2> + // iterLoad: LoadIteratorElement <iter1> + // access: GetElem <obj2> <iterLoad> + // + // This corresponds to `obj2[Object.keys(obj1)[index]]`. In the general + // case we can't do much with this, but if obj1 and obj2 have the same + // shape, then we may reuse the iterator, in which case iter1 == iter2. + // In that case, we can optimize the access as if it were using iter2, + // at the cost of a single comparison to see if iter1 == iter2. +#ifdef JS_CODEGEN_X86 + // The ops required for this want more registers than is convenient on + // x86 + bool supportObjectKeys = false; +#else + bool supportObjectKeys = true; +#endif MObjectToIterator* iter = nullptr; -#ifndef JS_CODEGEN_X86 + MObjectToIterator* otherIter = nullptr; MDefinition* iterElementIndex = nullptr; -#endif if (idVal->isIteratorMore()) { auto* iterNext = idVal->toIteratorMore(); @@ -5232,8 +5277,7 @@ bool jit::OptimizeIteratorIndices(const MIRGenerator* mir, MIRGraph& graph) { SkipIterObjectUnbox(receiver)) { continue; } -#ifndef JS_CODEGEN_X86 - } else if (SkipBox(idVal)->isLoadIteratorElement()) { + } else if (supportObjectKeys && SkipBox(idVal)->isLoadIteratorElement()) { auto* iterLoad = SkipBox(idVal)->toLoadIteratorElement(); if (!iterLoad->iter()->isObjectToIterator()) { @@ -5243,16 +5287,31 @@ bool jit::OptimizeIteratorIndices(const MIRGenerator* mir, MIRGraph& graph) { iter = iterLoad->iter()->toObjectToIterator(); if (SkipIterObjectUnbox(iter->object()) != SkipIterObjectUnbox(receiver)) { - continue; + if (!setValue) { + otherIter = FindObjectToIteratorUse(SkipIterObjectUnbox(receiver)); + } + + if (!otherIter || !otherIter->block()->dominates(ins->block())) { + continue; + } } iterElementIndex = iterLoad->index(); -#endif } else { continue; } - MInstruction* indicesCheck = - MIteratorHasIndices::New(graph.alloc(), iter->object(), iter); + MOZ_ASSERT_IF(iterElementIndex, supportObjectKeys); + MOZ_ASSERT_IF(otherIter, supportObjectKeys); + + MInstruction* indicesCheck = nullptr; + if (otherIter) { + indicesCheck = MIteratorsMatchAndHaveIndices::New( + graph.alloc(), otherIter->object(), iter, otherIter); + } else { + indicesCheck = + MIteratorHasIndices::New(graph.alloc(), iter->object(), iter); + } + MInstruction* replacement; if (ins->isHasOwnCache() || ins->isMegamorphicHasProp()) { MOZ_ASSERT(!setValue); @@ -5260,7 +5319,6 @@ bool jit::OptimizeIteratorIndices(const MIRGenerator* mir, MIRGraph& graph) { } else if (ins->isMegamorphicLoadSlotByValue() || ins->isGetPropertyCache()) { MOZ_ASSERT(!setValue); -#ifndef JS_CODEGEN_X86 if (iterElementIndex) { replacement = MLoadSlotByIteratorIndexIndexed::New( graph.alloc(), receiver, iter, iterElementIndex); @@ -5268,14 +5326,9 @@ bool jit::OptimizeIteratorIndices(const MIRGenerator* mir, MIRGraph& graph) { replacement = MLoadSlotByIteratorIndex::New(graph.alloc(), receiver, iter); } -#else - replacement = - MLoadSlotByIteratorIndex::New(graph.alloc(), receiver, iter); -#endif } else { MOZ_ASSERT(ins->isMegamorphicSetElement() || ins->isSetPropertyCache()); MOZ_ASSERT(setValue); -#ifndef JS_CODEGEN_X86 if (iterElementIndex) { replacement = MStoreSlotByIteratorIndexIndexed::New( graph.alloc(), receiver, iter, iterElementIndex, setValue); @@ -5283,10 +5336,6 @@ bool jit::OptimizeIteratorIndices(const MIRGenerator* mir, MIRGraph& graph) { replacement = MStoreSlotByIteratorIndex::New(graph.alloc(), receiver, iter, setValue); } -#else - replacement = MStoreSlotByIteratorIndex::New(graph.alloc(), receiver, - iter, setValue); -#endif } if (!block->wrapInstructionInFastpath(ins, replacement, indicesCheck)) { diff --git a/js/src/jit/LIROps.yaml b/js/src/jit/LIROps.yaml @@ -2810,6 +2810,14 @@ iterator: WordSized num_temps: 2 +- name: IteratorsMatchAndHaveIndicesAndBranch + successors: [ifTrue, ifFalse] + operands: + object: WordSized + iterator: WordSized + otherIterator: WordSized + num_temps: 2 + # Patchable jump to stubs generated for a SetProperty cache. - name: SetPropertyCache operands: diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp @@ -1283,6 +1283,21 @@ void LIRGenerator::visitTest(MTest* test) { return; } + if (opd->isIteratorsMatchAndHaveIndices()) { + MOZ_ASSERT(opd->isEmittedAtUses()); + + MDefinition* object = opd->toIteratorsMatchAndHaveIndices()->object(); + MDefinition* iterator = opd->toIteratorsMatchAndHaveIndices()->iterator(); + MDefinition* otherIterator = + opd->toIteratorsMatchAndHaveIndices()->otherIterator(); + LIteratorsMatchAndHaveIndicesAndBranch* lir = + new (alloc()) LIteratorsMatchAndHaveIndicesAndBranch( + ifTrue, ifFalse, useRegister(object), useRegister(iterator), + useRegister(otherIterator), temp(), temp()); + add(lir, test); + return; + } + switch (opd->type()) { case MIRType::Double: add(new (alloc()) LTestDAndBranch(ifTrue, ifFalse, useRegister(opd))); @@ -6271,7 +6286,6 @@ void LIRGenerator::visitStoreSlotByIteratorIndex( add(lir, ins); } -#ifndef JS_CODEGEN_X86 void LIRGenerator::visitLoadSlotByIteratorIndexIndexed( MLoadSlotByIteratorIndexIndexed* ins) { auto* lir = new (alloc()) LLoadSlotByIteratorIndexIndexed( @@ -6287,13 +6301,18 @@ void LIRGenerator::visitStoreSlotByIteratorIndexIndexed( useRegister(ins->index()), useBox(ins->value()), temp(), temp()); add(lir, ins); } -#endif void LIRGenerator::visitIteratorHasIndices(MIteratorHasIndices* ins) { MOZ_ASSERT(ins->hasOneUse()); emitAtUses(ins); } +void LIRGenerator::visitIteratorsMatchAndHaveIndices( + MIteratorsMatchAndHaveIndices* ins) { + MOZ_ASSERT(ins->hasOneUse()); + emitAtUses(ins); +} + void LIRGenerator::visitSetPropertyCache(MSetPropertyCache* ins) { MOZ_ASSERT(ins->object()->type() == MIRType::Object); diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp @@ -7531,6 +7531,10 @@ AliasSet MIteratorHasIndices::getAliasSet() const { return AliasSet::Load(AliasSet::ObjectFields); } +AliasSet MIteratorsMatchAndHaveIndices::getAliasSet() const { + return AliasSet::Load(AliasSet::ObjectFields); +} + AliasSet MAllocateAndStoreSlot::getAliasSet() const { return AliasSet::Store(AliasSet::ObjectFields | AliasSet::DynamicSlot); } @@ -7850,7 +7854,6 @@ AliasSet MStoreSlotByIteratorIndex::getAliasSet() const { AliasSet::DynamicSlot | AliasSet::Element); } -#ifndef JS_CODEGEN_X86 AliasSet MLoadSlotByIteratorIndexIndexed::getAliasSet() const { return AliasSet::Load(AliasSet::ObjectFields | AliasSet::FixedSlot | AliasSet::DynamicSlot | AliasSet::Element); @@ -7860,7 +7863,6 @@ AliasSet MStoreSlotByIteratorIndexIndexed::getAliasSet() const { return AliasSet::Store(AliasSet::ObjectFields | AliasSet::FixedSlot | AliasSet::DynamicSlot | AliasSet::Element); } -#endif MDefinition* MGuardInt32IsNonNegative::foldsTo(TempAllocator& alloc) { MOZ_ASSERT(index()->type() == MIRType::Int32); diff --git a/js/src/jit/MIROps.yaml b/js/src/jit/MIROps.yaml @@ -2826,6 +2826,14 @@ result_type: Boolean alias_set: custom +- name: IteratorsMatchAndHaveIndices + operands: + object: Object + iterator: Object + otherIterator: Object + result_type: Boolean + alias_set: custom + - name: LoadSlotByIteratorIndex operands: object: Object @@ -2844,12 +2852,6 @@ generate_lir: true lir_temps: 2 - -# The following two instructions need too many registers on x86. We could -# spill or do some cheeky stuff potentially but it's likely not worth the -# complexity -#ifndef JS_CODEGEN_X86 - # Okay this is a confusing name, but essentially, the above ops load/store a # slot of an object using the PropertyIndex associated with the *current* key # for the iterator. The below op uses the PropertyIndex associated with the @@ -2873,7 +2875,6 @@ alias_set: custom generate_lir: true lir_temps: 2 -#endif # Load the private value expando from a DOM proxy. The target is stored in the # proxy object's private slot. diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp @@ -2499,9 +2499,11 @@ bool RangeAnalysis::addRangeAssertions() { // MIsNoIter is fused with the MTest that follows it and emitted as // LIsNoIterAndBranch. Similarly, MIteratorHasIndices is fused to - // become LIteratorHasIndicesAndBranch. Skip them to avoid complicating - // lowering. - if (ins->isIsNoIter() || ins->isIteratorHasIndices()) { + // become LIteratorHasIndicesAndBranch and IteratorsMatchAndHaveIndices + // becomes LIteratorsMatchAndHaveIndicesAndBranch. Skip them to avoid + // complicating lowering. + if (ins->isIsNoIter() || ins->isIteratorHasIndices() || + ins->isIteratorsMatchAndHaveIndices()) { MOZ_ASSERT(ins->hasOneUse()); continue; }