tor-browser

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

commit 33caf3462cb418accd6013428bb83af20ac791ad
parent fbb75c78ef3221c211308b858dd2931f4f41295f
Author: Jan de Mooij <jdemooij@mozilla.com>
Date:   Tue, 18 Nov 2025 12:03:05 +0000

Bug 1992990 part 5 - Add MGuardShapeList and use for GuardMultipleShapes with a small number of shapes. r=iain

Snapshot the list of shapes on the main thread and then use this list for `MGuardShapeList`.
Because this MIR op has the list of shapes available at compile-time, we can perform more
MIR optimizations such as GVN. This improves some JetStream 3 subtests such as acorn-wtb a lot.

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

Diffstat:
Mjs/src/jit/CodeGenerator.cpp | 39+++++++++++++++++++++++++++++++++++++++
Mjs/src/jit/Lowering.cpp | 17+++++++++++++++++
Mjs/src/jit/MIR.cpp | 40++++++++++++++++++++++++++++++++++++++++
Mjs/src/jit/MIR.h | 1+
Mjs/src/jit/MIROps.yaml | 20++++++++++++++++++++
Mjs/src/jit/WarpBuilder.cpp | 6+++++-
Mjs/src/jit/WarpCacheIRTranspiler.cpp | 16++++++++++++----
Mjs/src/jit/WarpOracle.cpp | 33++++++++++++++++++++++++++++++---
Mjs/src/jit/WarpSnapshot.cpp | 22++++++++++++++++++++++
Mjs/src/jit/WarpSnapshot.h | 49+++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 235 insertions(+), 8 deletions(-)

diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp @@ -19,6 +19,7 @@ #include "mozilla/ScopeExit.h" #include "mozilla/SIMD.h" +#include <algorithm> #include <cmath> #include <limits> #include <type_traits> @@ -4500,6 +4501,44 @@ void CodeGenerator::visitGuardMultipleShapes(LGuardMultipleShapes* guard) { bailoutFrom(&bail, guard->snapshot()); } +void CodeGenerator::visitGuardShapeList(LGuardShapeList* guard) { + Register obj = ToRegister(guard->object()); + Register temp = ToRegister(guard->temp0()); + Register spectre = ToTempRegisterOrInvalid(guard->temp1()); + + Label done, bail; + masm.loadObjShapeUnsafe(obj, temp); + + // Count the number of branches to emit. + const auto& shapes = guard->mir()->shapeList()->shapes(); + size_t branchesLeft = std::count_if(shapes.begin(), shapes.end(), + [](Shape* s) { return s != nullptr; }); + MOZ_RELEASE_ASSERT(branchesLeft > 0); + + for (Shape* shape : shapes) { + if (!shape) { + continue; + } + if (branchesLeft > 1) { + masm.branchPtr(Assembler::Equal, temp, ImmGCPtr(shape), &done); + if (spectre != InvalidReg) { + masm.spectreMovePtr(Assembler::Equal, spectre, obj); + } + } else { + // This is the last branch so invert the condition and jump to |bail|. + masm.branchPtr(Assembler::NotEqual, temp, ImmGCPtr(shape), &bail); + if (spectre != InvalidReg) { + masm.spectreMovePtr(Assembler::NotEqual, spectre, obj); + } + } + branchesLeft--; + } + MOZ_ASSERT(branchesLeft == 0); + + masm.bind(&done); + bailoutFrom(&bail, guard->snapshot()); +} + void CodeGenerator::visitGuardProto(LGuardProto* guard) { Register obj = ToRegister(guard->object()); Register expected = ToRegister(guard->expected()); diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp @@ -5789,6 +5789,23 @@ void LIRGenerator::visitGuardMultipleShapes(MGuardMultipleShapes* ins) { } } +void LIRGenerator::visitGuardShapeList(MGuardShapeList* ins) { + MOZ_ASSERT(ins->object()->type() == MIRType::Object); + + if (JitOptions.spectreObjectMitigations) { + auto* lir = new (alloc()) + LGuardShapeList(useRegisterAtStart(ins->object()), temp(), temp()); + assignSnapshot(lir, ins->bailoutKind()); + defineReuseInput(lir, ins, 0); + } else { + auto* lir = new (alloc()) LGuardShapeList(useRegister(ins->object()), + temp(), LDefinition::BogusTemp()); + assignSnapshot(lir, ins->bailoutKind()); + add(lir, ins); + redefine(ins, ins->object()); + } +} + void LIRGenerator::visitGuardProto(MGuardProto* ins) { MOZ_ASSERT(ins->object()->type() == MIRType::Object); MOZ_ASSERT(ins->expected()->type() == MIRType::Object); diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp @@ -11,6 +11,7 @@ #include "mozilla/MathAlgorithms.h" #include "mozilla/Maybe.h" +#include <algorithm> #include <array> #include <utility> @@ -27,6 +28,7 @@ #include "jit/RangeAnalysis.h" #include "jit/VMFunctions.h" #include "jit/WarpBuilderShared.h" +#include "jit/WarpSnapshot.h" #include "js/Conversions.h" #include "js/experimental/JitInfo.h" // JSJitInfo, JSTypedMethodJitInfo #include "js/ScalarType.h" // js::Scalar::Type @@ -560,6 +562,14 @@ const MDefinition* MDefinition::skipObjectGuards() const { result = result->toGuardShape()->object(); continue; } + if (result->isGuardShapeList()) { + result = result->toGuardShapeList()->object(); + continue; + } + if (result->isGuardMultipleShapes()) { + result = result->toGuardMultipleShapes()->object(); + continue; + } if (result->isGuardNullProto()) { result = result->toGuardNullProto()->object(); continue; @@ -7184,6 +7194,36 @@ AliasSet MGuardShape::getAliasSet() const { return AliasSet::Load(AliasSet::ObjectFields); } +bool MGuardShapeList::congruentTo(const MDefinition* ins) const { + if (!congruentIfOperandsEqual(ins)) { + return false; + } + + // Returns true iff all non-nullptr shapes in |a| are also in |b|. + auto hasAllShapes = [](const auto& a, const auto& b) { + for (Shape* shape : a) { + if (!shape) { + continue; + } + auto isSameShape = [shape](Shape* other) { return shape == other; }; + if (!std::any_of(b.begin(), b.end(), isSameShape)) { + return false; + } + } + return true; + }; + + // Return true if all shapes in |shapesA| are also in |shapesB| and vice + // versa. + const auto& shapesA = this->shapeList()->shapes(); + const auto& shapesB = ins->toGuardShapeList()->shapeList()->shapes(); + return hasAllShapes(shapesA, shapesB) && hasAllShapes(shapesB, shapesA); +} + +AliasSet MGuardShapeList::getAliasSet() const { + return AliasSet::Load(AliasSet::ObjectFields); +} + bool MHasShape::congruentTo(const MDefinition* ins) const { if (!ins->isHasShape()) { return false; diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h @@ -74,6 +74,7 @@ bool CurrentThreadIsIonCompiling(); namespace jit { class CallInfo; +class ShapeListSnapshot; #ifdef JS_JITSPEW // Helper for debug printing. Avoids creating a MIR.h <--> MIRGraph.h cycle. diff --git a/js/src/jit/MIROps.yaml b/js/src/jit/MIROps.yaml @@ -2203,6 +2203,9 @@ alias_set: custom generate_lir: true +# This is the equivalent of the GuardMultipleShapes CacheIR op. Because it +# stores a pointer to the ShapeListObject, we don't need to recompile Ion code +# when a new shape is added to the list. - name: GuardMultipleShapes operands: object: Object @@ -2215,6 +2218,23 @@ generate_lir: true lir_temps: 4 +# Similar to GuardMultipleShapes but used when the list had a small number of +# shapes. These shapes have been copied to the ShapeListSnapshot and are +# available at compile-time. This lets us perform more MIR optimizations, but +# we need to recompile when a new shape is added to the list. +- name: GuardShapeList + operands: + object: Object + arguments: + shapeList: const ShapeListSnapshot* + result_type: Object + guard: true + movable: true + congruent_to: custom + alias_set: custom + generate_lir: true + lir_temps: 2 + - name: GuardProto gen_boilerplate: false diff --git a/js/src/jit/WarpBuilder.cpp b/js/src/jit/WarpBuilder.cpp @@ -3380,7 +3380,11 @@ bool WarpBuilder::buildIC(BytecodeLocation loc, CacheKind kind, mozilla::DebugOnly<size_t> numInputs = inputs.size(); MOZ_ASSERT(numInputs == NumInputsForCacheKind(kind)); - if (auto* cacheIRSnapshot = getOpSnapshot<WarpCacheIR>(loc)) { + const WarpCacheIRBase* cacheIRSnapshot = getOpSnapshot<WarpCacheIR>(loc); + if (!cacheIRSnapshot) { + cacheIRSnapshot = getOpSnapshot<WarpCacheIRWithShapeList>(loc); + } + if (cacheIRSnapshot) { return TranspileCacheIRToMIR(this, loc, cacheIRSnapshot, inputs); } diff --git a/js/src/jit/WarpCacheIRTranspiler.cpp b/js/src/jit/WarpCacheIRTranspiler.cpp @@ -512,11 +512,19 @@ bool WarpCacheIRTranspiler::emitGuardObjectFuseProperty( bool WarpCacheIRTranspiler::emitGuardMultipleShapes(ObjOperandId objId, uint32_t shapesOffset) { MDefinition* def = getOperand(objId); - MInstruction* shapeList = objectStubField(shapesOffset); - auto* ins = MGuardMultipleShapes::New(alloc(), def, shapeList); - if (builder_->info().inlineScriptTree()->hasSharedICScript()) { - ins->setBailoutKind(BailoutKind::MonomorphicInlinedStubFolding); + // Use MGuardShapeList if we snapshotted the list of shapes on the main + // thread. + MInstruction* ins; + if (cacheIRSnapshot_->is<WarpCacheIRWithShapeList>()) { + auto* shapes = cacheIRSnapshot_->as<WarpCacheIRWithShapeList>()->shapes(); + ins = MGuardShapeList::New(alloc(), def, shapes); + } else { + MInstruction* shapeList = objectStubField(shapesOffset); + ins = MGuardMultipleShapes::New(alloc(), def, shapeList); + if (builder_->info().inlineScriptTree()->hasSharedICScript()) { + ins->setBailoutKind(BailoutKind::MonomorphicInlinedStubFolding); + } } add(ins); diff --git a/js/src/jit/WarpOracle.cpp b/js/src/jit/WarpOracle.cpp @@ -22,6 +22,7 @@ #include "jit/JitSpewer.h" #include "jit/JitZone.h" #include "jit/MIRGenerator.h" +#include "jit/ShapeList.h" #include "jit/TrialInlining.h" #include "jit/TypeData.h" #include "jit/WarpBuilder.h" @@ -1103,6 +1104,9 @@ AbortReasonOr<Ok> WarpScriptOracle::maybeInlineIC(WarpOpSnapshotList& snapshots, const CacheIRStubInfo* stubInfo = stub->stubInfo(); const uint8_t* stubData = stub->stubDataStart(); + // List of shapes for a GuardMultipleShapes op with a small number of shapes. + mozilla::Maybe<ShapeListSnapshot> shapeList; + // Only create a snapshot if all opcodes are supported by the transpiler. CacheIRReader reader(stubInfo); bool hasInvalidFuseGuard = false; @@ -1204,6 +1208,21 @@ AbortReasonOr<Ok> WarpScriptOracle::maybeInlineIC(WarpOpSnapshotList& snapshots, } break; } + case CacheOp::GuardMultipleShapes: { + auto args = reader.argsForGuardMultipleShapes(); + JSObject* shapes = stubInfo->getStubField<StubField::Type::JSObject>( + stub, args.shapesOffset); + auto* shapesObject = &shapes->as<ShapeListObject>(); + MOZ_ASSERT(shapeList.isNothing()); + size_t numShapes = shapesObject->length(); + if (ShapeListSnapshot::shouldSnapshot(numShapes)) { + shapeList.emplace(); + for (size_t i = 0; i < numShapes; i++) { + shapeList->init(i, shapesObject->get(i)); + } + } + break; + } default: reader.skip(opInfo.argLength); break; @@ -1256,9 +1275,17 @@ AbortReasonOr<Ok> WarpScriptOracle::maybeInlineIC(WarpOpSnapshotList& snapshots, } } - if (!AddOpSnapshot<WarpCacheIR>(alloc_, snapshots, offset, jitCode, stubInfo, - stubDataCopy)) { - return abort(AbortReason::Alloc); + if (shapeList.isSome()) { + if (!AddOpSnapshot<WarpCacheIRWithShapeList>(alloc_, snapshots, offset, + jitCode, stubInfo, + stubDataCopy, *shapeList)) { + return abort(AbortReason::Alloc); + } + } else { + if (!AddOpSnapshot<WarpCacheIR>(alloc_, snapshots, offset, jitCode, + stubInfo, stubDataCopy)) { + return abort(AbortReason::Alloc); + } } fallbackStub->setUsedByTranspiler(); diff --git a/js/src/jit/WarpSnapshot.cpp b/js/src/jit/WarpSnapshot.cpp @@ -207,6 +207,15 @@ void WarpCacheIR::dumpData(GenericPrinter& out) const { WarpCacheIRBase::dumpData(out); } +void WarpCacheIRWithShapeList::dumpData(GenericPrinter& out) const { + WarpCacheIRBase::dumpData(out); + uint32_t index = 0; + for (Shape* shape : shapes_.shapes()) { + out.printf(" shape %u: 0x%p\n", index, shape); + index++; + } +} + void WarpInlinedCall::dumpData(GenericPrinter& out) const { out.printf(" scriptSnapshot: 0x%p\n", scriptSnapshot_); out.printf(" info: 0x%p\n", info_); @@ -431,6 +440,19 @@ void WarpCacheIRBase::traceData(JSTracer* trc) { void WarpCacheIR::traceData(JSTracer* trc) { WarpCacheIRBase::traceData(trc); } +void ShapeListSnapshot::trace(JSTracer* trc) const { + for (auto& shape : shapes_) { + if (shape) { + TraceOffthreadGCPtr(trc, shape, "warp-shape-list-shape"); + } + } +} + +void WarpCacheIRWithShapeList::traceData(JSTracer* trc) { + WarpCacheIRBase::traceData(trc); + shapes_.trace(trc); +} + void WarpInlinedCall::traceData(JSTracer* trc) { // Note: scriptSnapshot_ is traced through WarpSnapshot. cacheIRSnapshot_->trace(trc); diff --git a/js/src/jit/WarpSnapshot.h b/js/src/jit/WarpSnapshot.h @@ -47,6 +47,7 @@ class WarpScriptSnapshot; _(WarpClassBodyEnvironment) \ _(WarpBailout) \ _(WarpCacheIR) \ + _(WarpCacheIRWithShapeList) \ _(WarpInlinedCall) \ _(WarpPolymorphicTypes) @@ -262,6 +263,54 @@ class WarpCacheIR : public WarpCacheIRBase { #endif }; +class ShapeListSnapshot { + public: + ShapeListSnapshot() = default; + + void init(size_t index, Shape* shape) { + MOZ_ASSERT(shape); + shapes_[index].init(shape); + } + const auto& shapes() const { return shapes_; } + + static bool shouldSnapshot(size_t length) { + // ShapeListObject has weak pointers so a GC can remove shapes from the + // list. Don't snapshot empty shape lists to avoid bailouts and because + // handling this edge case would complicate the compiler code. + return length > 0 && length <= NumShapes; + } + + void trace(JSTracer* trc) const; + + private: + static constexpr size_t NumShapes = 4; + mozilla::Array<OffthreadGCPtr<Shape*>, NumShapes> shapes_{}; +}; + +// Like WarpCacheIR, but also includes a ShapeListSnapshot for the +// GuardMultipleShapes CacheIR op. +class WarpCacheIRWithShapeList : public WarpCacheIRBase { + const ShapeListSnapshot shapes_; + + public: + static constexpr Kind ThisKind = Kind::WarpCacheIRWithShapeList; + + WarpCacheIRWithShapeList(uint32_t offset, JitCode* stubCode, + const CacheIRStubInfo* stubInfo, + const uint8_t* stubData, + const ShapeListSnapshot& shapes) + : WarpCacheIRBase(ThisKind, offset, stubCode, stubInfo, stubData), + shapes_(shapes) {} + + void traceData(JSTracer* trc); + +#ifdef JS_JITSPEW + void dumpData(GenericPrinter& out) const; +#endif + + const ShapeListSnapshot* shapes() const { return &shapes_; } +}; + // [SMDOC] Warp Nursery Object/Value support // // CacheIR stub data can contain nursery allocated objects or values. This can