tor-browser

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

commit 653729f665e59fc8ebee035ecb7c8ec46a298de8
parent 6be6f37dbb93dd9019ddde6bf8cc8abe8099d9dd
Author: Hannes Verschore <hv1989@gmail.com>
Date:   Thu, 18 Dec 2025 16:07:14 +0000

Bug 1999828: Part 4 - Implement GuardShapeListToOffset. r=iain

GuardShapeListToOffset is an improved GuardMultipleShapesToOffset for ion.
In baseline the shapes and offset are recorded, which are baked into
the GuardShapeListToOffset (with maximum of 4). This allows optimization
passes like GVN and LICM to better optimize.

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

Diffstat:
Mjs/src/jit/CodeGenerator.cpp | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Mjs/src/jit/Lowering.cpp | 16++++++++++++++++
Mjs/src/jit/MIR.cpp | 26++++++++++++++++++++++++++
Mjs/src/jit/MIR.h | 1+
Mjs/src/jit/MIROps.yaml | 13+++++++++++++
Mjs/src/jit/StubFolding.cpp | 3+--
Mjs/src/jit/WarpBuilder.cpp | 3+++
Mjs/src/jit/WarpCacheIRTranspiler.cpp | 13++++++++++---
Mjs/src/jit/WarpOracle.cpp | 24++++++++++++++++++++++++
Mjs/src/jit/WarpSnapshot.cpp | 19+++++++++++++++++++
Mjs/src/jit/WarpSnapshot.h | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
11 files changed, 220 insertions(+), 21 deletions(-)

diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp @@ -4540,6 +4540,56 @@ void CodeGenerator::visitGuardShapeList(LGuardShapeList* guard) { bailoutFrom(&bail, guard->snapshot()); } +void CodeGenerator::visitGuardShapeListToOffset( + LGuardShapeListToOffset* guard) { + Register obj = ToRegister(guard->object()); + Register temp = ToRegister(guard->temp0()); + Register spectre = ToTempRegisterOrInvalid(guard->temp1()); + Register offset = ToRegister(guard->output()); + + Label done, bail; + masm.loadObjShapeUnsafe(obj, temp); + + // Count the number of branches to emit. + const auto& shapes = guard->mir()->shapeList()->shapes(); + const auto& offsets = guard->mir()->shapeList()->offsets(); + size_t branchesLeft = std::count_if(shapes.begin(), shapes.end(), + [](Shape* s) { return s != nullptr; }); + MOZ_RELEASE_ASSERT(branchesLeft > 0); + + size_t index = 0; + for (Shape* shape : shapes) { + if (!shape) { + index++; + continue; + } + + if (branchesLeft > 1) { + Label next; + masm.branchPtr(Assembler::NotEqual, temp, ImmGCPtr(shape), &next); + if (spectre != InvalidReg) { + masm.spectreMovePtr(Assembler::NotEqual, spectre, obj); + } + masm.move32(Imm32(offsets[index]), offset); + masm.jump(&done); + masm.bind(&next); + } else { + masm.branchPtr(Assembler::NotEqual, temp, ImmGCPtr(shape), &bail); + if (spectre != InvalidReg) { + masm.spectreMovePtr(Assembler::NotEqual, spectre, obj); + } + masm.move32(Imm32(offsets[index]), offset); + } + + branchesLeft--; + index++; + } + MOZ_ASSERT(branchesLeft == 0); + + masm.bind(&done); + bailoutFrom(&bail, guard->snapshot()); +} + void CodeGenerator::visitGuardMultipleShapesToOffset( LGuardMultipleShapesToOffset* guard) { Register obj = ToRegister(guard->object()); diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp @@ -5809,6 +5809,22 @@ void LIRGenerator::visitGuardShapeList(MGuardShapeList* ins) { } } +void LIRGenerator::visitGuardShapeListToOffset(MGuardShapeListToOffset* ins) { + MOZ_ASSERT(ins->object()->type() == MIRType::Object); + + if (JitOptions.spectreObjectMitigations) { + auto* lir = new (alloc()) + LGuardShapeListToOffset(useRegister(ins->object()), temp(), temp()); + assignSnapshot(lir, ins->bailoutKind()); + define(lir, ins); + } else { + auto* lir = new (alloc()) LGuardShapeListToOffset( + useRegister(ins->object()), temp(), LDefinition::BogusTemp()); + assignSnapshot(lir, ins->bailoutKind()); + define(lir, ins); + } +} + void LIRGenerator::visitGuardMultipleShapesToOffset( MGuardMultipleShapesToOffset* ins) { MOZ_ASSERT(ins->object()->type() == MIRType::Object); diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp @@ -570,6 +570,10 @@ const MDefinition* MDefinition::skipObjectGuards() const { result = result->toGuardMultipleShapes()->object(); continue; } + if (result->isGuardShapeListToOffset()) { + result = result->toGuardShapeListToOffset()->object(); + continue; + } if (result->isGuardMultipleShapesToOffset()) { result = result->toGuardMultipleShapesToOffset()->object(); continue; @@ -7229,6 +7233,28 @@ AliasSet MGuardShapeList::getAliasSet() const { return AliasSet::Load(AliasSet::ObjectFields); } +bool MGuardShapeListToOffset::congruentTo(const MDefinition* ins) const { + if (!congruentIfOperandsEqual(ins)) { + return false; + } + + const auto& shapesA = this->shapeList()->shapes(); + const auto& shapesB = ins->toGuardShapeListToOffset()->shapeList()->shapes(); + if (!std::equal(shapesA.begin(), shapesA.end(), shapesB.begin(), + shapesB.end())) + return false; + + const auto& offsetsA = this->shapeList()->offsets(); + const auto& offsetsB = + ins->toGuardShapeListToOffset()->shapeList()->offsets(); + return std::equal(offsetsA.begin(), offsetsA.end(), offsetsB.begin(), + offsetsB.end()); +} + +AliasSet MGuardShapeListToOffset::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 @@ -75,6 +75,7 @@ namespace jit { class CallInfo; class ShapeListSnapshot; +class ShapeListWithOffsetsSnapshot; #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 @@ -2293,6 +2293,19 @@ generate_lir: true lir_temps: 2 +- name: GuardShapeListToOffset + operands: + object: Object + arguments: + shapeList: const ShapeListWithOffsetsSnapshot* + result_type: Int32 + guard: true + movable: true + congruent_to: custom + alias_set: custom + generate_lir: true + lir_temps: 2 + - name: GuardMultipleShapesToOffset operands: object: Object diff --git a/js/src/jit/StubFolding.cpp b/js/src/jit/StubFolding.cpp @@ -337,8 +337,7 @@ static bool TryFoldingGuardShapes(JSContext* cx, ICFallbackStub* fallback, // together. JitSpew(JitSpew_StubFolding, "Failed to fold GuardShape into GuardMultipleShapesToOffset at " - "offset %u " - "(icScript: %p) with %zu shapes (%s:%u:%u)", + "offset %u (icScript: %p) with %zu shapes (%s:%u:%u)", fallback->pcOffset(), icScript, shapeList.length(), script->filename(), script->lineno(), script->column().oneOriginValue()); diff --git a/js/src/jit/WarpBuilder.cpp b/js/src/jit/WarpBuilder.cpp @@ -3383,6 +3383,9 @@ bool WarpBuilder::buildIC(BytecodeLocation loc, CacheKind kind, const WarpCacheIRBase* cacheIRSnapshot = getOpSnapshot<WarpCacheIR>(loc); if (!cacheIRSnapshot) { cacheIRSnapshot = getOpSnapshot<WarpCacheIRWithShapeList>(loc); + if (!cacheIRSnapshot) { + cacheIRSnapshot = getOpSnapshot<WarpCacheIRWithShapeListAndOffsets>(loc); + } } if (cacheIRSnapshot) { return TranspileCacheIRToMIR(this, loc, cacheIRSnapshot, inputs); diff --git a/js/src/jit/WarpCacheIRTranspiler.cpp b/js/src/jit/WarpCacheIRTranspiler.cpp @@ -536,9 +536,16 @@ bool WarpCacheIRTranspiler::emitGuardMultipleShapesToOffset( // Use MGuardShapeListToOffset if we snapshotted the list of shapes on the // main thread. MInstruction* ins; - MInstruction* shapeList = objectStubField(shapesOffset); - ins = MGuardMultipleShapesToOffset::New(alloc(), obj, shapeList); - ins->setBailoutKind(BailoutKind::StubFoldingGuardMultipleShapes); + if (cacheIRSnapshot_->is<WarpCacheIRWithShapeListAndOffsets>()) { + auto* shapes = (ShapeListWithOffsetsSnapshot*)cacheIRSnapshot_ + ->as<WarpCacheIRWithShapeListAndOffsets>() + ->shapes(); + ins = MGuardShapeListToOffset::New(alloc(), obj, shapes); + } else { + MInstruction* shapeList = objectStubField(shapesOffset); + ins = MGuardMultipleShapesToOffset::New(alloc(), obj, shapeList); + ins->setBailoutKind(BailoutKind::StubFoldingGuardMultipleShapes); + } add(ins); return defineOperand(offsetId, ins); diff --git a/js/src/jit/WarpOracle.cpp b/js/src/jit/WarpOracle.cpp @@ -1106,6 +1106,7 @@ AbortReasonOr<Ok> WarpScriptOracle::maybeInlineIC(WarpOpSnapshotList& snapshots, // List of shapes for a GuardMultipleShapes op with a small number of shapes. mozilla::Maybe<ShapeListSnapshot> shapeList; + mozilla::Maybe<ShapeListWithOffsetsSnapshot> shapeListWithOffsets; // Only create a snapshot if all opcodes are supported by the transpiler. CacheIRReader reader(stubInfo); @@ -1223,6 +1224,22 @@ AbortReasonOr<Ok> WarpScriptOracle::maybeInlineIC(WarpOpSnapshotList& snapshots, } break; } + case CacheOp::GuardMultipleShapesToOffset: { + auto args = reader.argsForGuardMultipleShapesToOffset(); + JSObject* shapes = stubInfo->getStubField<StubField::Type::JSObject>( + stub, args.shapesOffset); + auto* shapesObject = &shapes->as<ShapeListWithOffsetsObject>(); + MOZ_ASSERT(shapeListWithOffsets.isNothing()); + size_t numShapes = shapesObject->numShapes(); + if (ShapeListSnapshot::shouldSnapshot(numShapes)) { + shapeListWithOffsets.emplace(); + for (size_t i = 0; i < numShapes; i++) { + shapeListWithOffsets->init(i, shapesObject->getShape(i), + shapesObject->getOffset(i)); + } + } + break; + } default: reader.skip(opInfo.argLength); break; @@ -1276,11 +1293,18 @@ AbortReasonOr<Ok> WarpScriptOracle::maybeInlineIC(WarpOpSnapshotList& snapshots, } if (shapeList.isSome()) { + MOZ_ASSERT(shapeListWithOffsets.isNothing()); if (!AddOpSnapshot<WarpCacheIRWithShapeList>(alloc_, snapshots, offset, jitCode, stubInfo, stubDataCopy, *shapeList)) { return abort(AbortReason::Alloc); } + } else if (shapeListWithOffsets.isSome()) { + if (!AddOpSnapshot<WarpCacheIRWithShapeListAndOffsets>( + alloc_, snapshots, offset, jitCode, stubInfo, stubDataCopy, + *shapeListWithOffsets)) { + return abort(AbortReason::Alloc); + } } else { if (!AddOpSnapshot<WarpCacheIR>(alloc_, snapshots, offset, jitCode, stubInfo, stubDataCopy)) { diff --git a/js/src/jit/WarpSnapshot.cpp b/js/src/jit/WarpSnapshot.cpp @@ -216,6 +216,20 @@ void WarpCacheIRWithShapeList::dumpData(GenericPrinter& out) const { } } +void WarpCacheIRWithShapeListAndOffsets::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++; + } + index = 0; + for (uint32_t offset : shapes_.offsets()) { + out.printf(" offset %u: %u\n", index, offset); + index++; + } +} + void WarpInlinedCall::dumpData(GenericPrinter& out) const { out.printf(" scriptSnapshot: 0x%p\n", scriptSnapshot_); out.printf(" info: 0x%p\n", info_); @@ -453,6 +467,11 @@ void WarpCacheIRWithShapeList::traceData(JSTracer* trc) { shapes_.trace(trc); } +void WarpCacheIRWithShapeListAndOffsets::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 @@ -34,21 +34,22 @@ class CacheIRStubInfo; class CompileInfo; class WarpScriptSnapshot; -#define WARP_OP_SNAPSHOT_LIST(_) \ - _(WarpArguments) \ - _(WarpRegExp) \ - _(WarpBuiltinObject) \ - _(WarpGetIntrinsic) \ - _(WarpGetImport) \ - _(WarpRest) \ - _(WarpBindUnqualifiedGName) \ - _(WarpVarEnvironment) \ - _(WarpLexicalEnvironment) \ - _(WarpClassBodyEnvironment) \ - _(WarpBailout) \ - _(WarpCacheIR) \ - _(WarpCacheIRWithShapeList) \ - _(WarpInlinedCall) \ +#define WARP_OP_SNAPSHOT_LIST(_) \ + _(WarpArguments) \ + _(WarpRegExp) \ + _(WarpBuiltinObject) \ + _(WarpGetIntrinsic) \ + _(WarpGetImport) \ + _(WarpRest) \ + _(WarpBindUnqualifiedGName) \ + _(WarpVarEnvironment) \ + _(WarpLexicalEnvironment) \ + _(WarpClassBodyEnvironment) \ + _(WarpBailout) \ + _(WarpCacheIR) \ + _(WarpCacheIRWithShapeList) \ + _(WarpCacheIRWithShapeListAndOffsets) \ + _(WarpInlinedCall) \ _(WarpPolymorphicTypes) // WarpOpSnapshot is the base class for data attached to a single bytecode op by @@ -282,11 +283,27 @@ class ShapeListSnapshot { void trace(JSTracer* trc) const; - private: + protected: static constexpr size_t NumShapes = 4; mozilla::Array<OffthreadGCPtr<Shape*>, NumShapes> shapes_{}; }; +class ShapeListWithOffsetsSnapshot : public ShapeListSnapshot { + public: + ShapeListWithOffsetsSnapshot() = default; + + void init(size_t index, Shape* shape, uint32_t offset) { + MOZ_ASSERT(shape); + shapes_[index].init(shape); + offsets_[index] = offset; + } + + const auto& offsets() const { return offsets_; } + + private: + mozilla::Array<uint32_t, NumShapes> offsets_{}; +}; + // Like WarpCacheIR, but also includes a ShapeListSnapshot for the // GuardMultipleShapes CacheIR op. class WarpCacheIRWithShapeList : public WarpCacheIRBase { @@ -311,6 +328,30 @@ class WarpCacheIRWithShapeList : public WarpCacheIRBase { const ShapeListSnapshot* shapes() const { return &shapes_; } }; +// Like WarpCacheIR, but also includes a ShapeListWithOffsetsSnapshot for the +// GuardMultipleShapesToOffset CacheIR op. +class WarpCacheIRWithShapeListAndOffsets : public WarpCacheIRBase { + const ShapeListWithOffsetsSnapshot shapes_; + + public: + static constexpr Kind ThisKind = Kind::WarpCacheIRWithShapeListAndOffsets; + + WarpCacheIRWithShapeListAndOffsets(uint32_t offset, JitCode* stubCode, + const CacheIRStubInfo* stubInfo, + const uint8_t* stubData, + const ShapeListWithOffsetsSnapshot& shapes) + : WarpCacheIRBase(ThisKind, offset, stubCode, stubInfo, stubData), + shapes_(shapes) {} + + void traceData(JSTracer* trc); + +#ifdef JS_JITSPEW + void dumpData(GenericPrinter& out) const; +#endif + + const ShapeListWithOffsetsSnapshot* shapes() const { return &shapes_; } +}; + // [SMDOC] Warp Nursery Object/Value support // // CacheIR stub data can contain nursery allocated objects or values. This can