tor-browser

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

commit ac7d7c131dd65058ad6b7ecfa65dc8f31a0f203e
parent d4456d810664a2a13a871a68e5323522a78c1131
Author: Sandor Molnar <smolnar@mozilla.com>
Date:   Mon,  8 Dec 2025 11:42:55 +0200

Revert "Bug 998485 - Removed analyzeEdgeCasesForward from MIR.h/.cpp - r=nbp" for causing perf regression (Bug 2003121)

This reverts commit b0c596352f76e501680fc2fbe4419e701e488ece.

Diffstat:
Ajs/src/jit/EdgeCaseAnalysis.cpp | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Ajs/src/jit/EdgeCaseAnalysis.h | 28++++++++++++++++++++++++++++
Mjs/src/jit/Ion.cpp | 17+++++++++++++++++
Mjs/src/jit/IonOptimizationLevels.h | 10++++++++++
Mjs/src/jit/JitOptions.cpp | 3+++
Mjs/src/jit/JitOptions.h | 1+
Mjs/src/jit/MIR.cpp | 257+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mjs/src/jit/MIR.h | 9+++++++++
Mjs/src/jit/moz.build | 1+
Mjs/src/shell/js.cpp | 10++++++++++
10 files changed, 387 insertions(+), 0 deletions(-)

diff --git a/js/src/jit/EdgeCaseAnalysis.cpp b/js/src/jit/EdgeCaseAnalysis.cpp @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/EdgeCaseAnalysis.h" + +#include "jit/MIR-wasm.h" +#include "jit/MIR.h" +#include "jit/MIRGenerator.h" +#include "jit/MIRGraph.h" + +using namespace js; +using namespace js::jit; + +EdgeCaseAnalysis::EdgeCaseAnalysis(const MIRGenerator* mir, MIRGraph& graph) + : mir(mir), graph(graph) {} + +bool EdgeCaseAnalysis::analyzeLate() { + // Renumber definitions for NeedNegativeZeroCheck under + // analyzeEdgeCasesBackward. + uint32_t nextId = 0; + + for (ReversePostorderIterator block(graph.rpoBegin()); + block != graph.rpoEnd(); block++) { + for (MDefinitionIterator iter(*block); iter; iter++) { + if (mir->shouldCancel("Analyze Late (first loop)")) { + return false; + } + + iter->setId(nextId++); + iter->analyzeEdgeCasesForward(); + } + block->lastIns()->setId(nextId++); + } + + for (PostorderIterator block(graph.poBegin()); block != graph.poEnd(); + block++) { + for (MInstructionReverseIterator riter(block->rbegin()); + riter != block->rend(); riter++) { + if (mir->shouldCancel("Analyze Late (second loop)")) { + return false; + } + + riter->analyzeEdgeCasesBackward(); + } + } + + return true; +} diff --git a/js/src/jit/EdgeCaseAnalysis.h b/js/src/jit/EdgeCaseAnalysis.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_EdgeCaseAnalysis_h +#define jit_EdgeCaseAnalysis_h + +namespace js { +namespace jit { + +class MIRGenerator; +class MIRGraph; + +class EdgeCaseAnalysis { + const MIRGenerator* mir; + MIRGraph& graph; + + public: + EdgeCaseAnalysis(const MIRGenerator* mir, MIRGraph& graph); + [[nodiscard]] bool analyzeLate(); +}; + +} // namespace jit +} // namespace js + +#endif /* jit_EdgeCaseAnalysis_h */ diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp @@ -23,6 +23,7 @@ #include "jit/CodeGenerator.h" #include "jit/CompileInfo.h" #include "jit/DominatorTree.h" +#include "jit/EdgeCaseAnalysis.h" #include "jit/EffectiveAddressAnalysis.h" #include "jit/ExecutableAllocator.h" #include "jit/FoldLinearArithConstants.h" @@ -1491,6 +1492,22 @@ bool OptimizeMIR(MIRGenerator* mir) { } } + // Passes after this point must not move instructions; these analyses + // depend on knowing the final order in which instructions will execute. + + if (mir->optimizationInfo().edgeCaseAnalysisEnabled()) { + EdgeCaseAnalysis edgeCaseAnalysis(mir, graph); + if (!edgeCaseAnalysis.analyzeLate()) { + return false; + } + mir->spewPass("Edge Case Analysis (Late)"); + AssertGraphCoherency(graph); + + if (mir->shouldCancel("Edge Case Analysis (Late)")) { + return false; + } + } + if (mir->optimizationInfo().eliminateRedundantChecksEnabled()) { // Note: check elimination has to run after all other passes that move // instructions. Since check uses are replaced with the actual index, diff --git a/js/src/jit/IonOptimizationLevels.h b/js/src/jit/IonOptimizationLevels.h @@ -44,6 +44,9 @@ class OptimizationInfo { // Toggles whether Alignment Mask Analysis is performed. bool ama_; + // Toggles whether Edge Case Analysis is used. + bool edgeCaseAnalysis_; + // Toggles whether redundant checks get removed. bool eliminateRedundantChecks_; @@ -88,6 +91,7 @@ class OptimizationInfo { : level_(OptimizationLevel::Normal), eaa_(false), ama_(false), + edgeCaseAnalysis_(false), eliminateRedundantChecks_(false), eliminateRedundantShapeGuards_(false), eliminateRedundantGCBarriers_(false), @@ -107,6 +111,7 @@ class OptimizationInfo { autoTruncate_ = true; eaa_ = true; + edgeCaseAnalysis_ = true; eliminateRedundantChecks_ = true; eliminateRedundantShapeGuards_ = true; eliminateRedundantGCBarriers_ = true; @@ -132,6 +137,7 @@ class OptimizationInfo { ama_ = true; autoTruncate_ = false; + edgeCaseAnalysis_ = false; eliminateRedundantChecks_ = false; eliminateRedundantShapeGuards_ = false; eliminateRedundantGCBarriers_ = false; @@ -175,6 +181,10 @@ class OptimizationInfo { bool amaEnabled() const { return ama_ && !JitOptions.disableAma; } + bool edgeCaseAnalysisEnabled() const { + return edgeCaseAnalysis_ && !JitOptions.disableEdgeCaseAnalysis; + } + bool eliminateRedundantChecksEnabled() const { return eliminateRedundantChecks_; } diff --git a/js/src/jit/JitOptions.cpp b/js/src/jit/JitOptions.cpp @@ -81,6 +81,9 @@ DefaultJitOptions::DefaultJitOptions() { // Toggles whether Effective Address Analysis is globally disabled. SET_DEFAULT(disableEaa, false); + // Toggles whether Edge Case Analysis is gobally disabled. + SET_DEFAULT(disableEdgeCaseAnalysis, false); + // Toggle whether global value numbering is globally disabled. SET_DEFAULT(disableGvn, false); diff --git a/js/src/jit/JitOptions.h b/js/src/jit/JitOptions.h @@ -54,6 +54,7 @@ struct DefaultJitOptions { bool disableJitHints; bool disableAma; bool disableEaa; + bool disableEdgeCaseAnalysis; bool disableGvn; bool disableInlining; bool disableLicm; diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp @@ -662,6 +662,10 @@ MDefinition* MInstruction::foldsToStore(TempAllocator& alloc) { return value; } +void MDefinition::analyzeEdgeCasesForward() {} + +void MDefinition::analyzeEdgeCasesBackward() {} + void MInstruction::setResumePoint(MResumePoint* resumePoint) { MOZ_ASSERT(!resumePoint_); resumePoint_ = resumePoint; @@ -2850,6 +2854,159 @@ MDefinition* MBinaryBitwiseInstruction::foldUnnecessaryBitop() { return this; } +static inline bool CanProduceNegativeZero(MDefinition* def) { + // Test if this instruction can produce negative zero even when bailing out + // and changing types. + switch (def->op()) { + case MDefinition::Opcode::Constant: + if (def->type() == MIRType::Double && + def->toConstant()->toDouble() == -0.0) { + return true; + } + [[fallthrough]]; + case MDefinition::Opcode::BitAnd: + case MDefinition::Opcode::BitOr: + case MDefinition::Opcode::BitXor: + case MDefinition::Opcode::BitNot: + case MDefinition::Opcode::Lsh: + case MDefinition::Opcode::Rsh: + return false; + default: + return true; + } +} + +static inline bool NeedNegativeZeroCheck(MDefinition* def) { + if (def->isGuard() || def->isGuardRangeBailouts()) { + return true; + } + + // Test if all uses have the same semantics for -0 and 0 + for (MUseIterator use = def->usesBegin(); use != def->usesEnd(); use++) { + if (use->consumer()->isResumePoint()) { + return true; + } + + MDefinition* use_def = use->consumer()->toDefinition(); + switch (use_def->op()) { + case MDefinition::Opcode::Add: { + // If add is truncating -0 and 0 are observed as the same. + if (use_def->toAdd()->isTruncated()) { + break; + } + + // x + y gives -0, when both x and y are -0 + + // Figure out the order in which the addition's operands will + // execute. EdgeCaseAnalysis::analyzeLate has renumbered the MIR + // definitions for us so that this just requires comparing ids. + MDefinition* first = use_def->toAdd()->lhs(); + MDefinition* second = use_def->toAdd()->rhs(); + if (first->id() > second->id()) { + std::swap(first, second); + } + // Negative zero checks can be removed on the first executed + // operand only if it is guaranteed the second executed operand + // will produce a value other than -0. While the second is + // typed as an int32, a bailout taken between execution of the + // operands may change that type and cause a -0 to flow to the + // second. + // + // There is no way to test whether there are any bailouts + // between execution of the operands, so remove negative + // zero checks from the first only if the second's type is + // independent from type changes that may occur after bailing. + if (def == first && CanProduceNegativeZero(second)) { + return true; + } + + // The negative zero check can always be removed on the second + // executed operand; by the time this executes the first will have + // been evaluated as int32 and the addition's result cannot be -0. + break; + } + case MDefinition::Opcode::Sub: { + // If sub is truncating -0 and 0 are observed as the same + if (use_def->toSub()->isTruncated()) { + break; + } + + // x + y gives -0, when x is -0 and y is 0 + + // We can remove the negative zero check on the rhs, only if we + // are sure the lhs isn't negative zero. + + // The lhs is typed as integer (i.e. not -0.0), but it can bailout + // and change type. This should be fine if the lhs is executed + // first. However if the rhs is executed first, the lhs can bail, + // change type and become -0.0 while the rhs has already been + // optimized to not make a difference between zero and negative zero. + MDefinition* lhs = use_def->toSub()->lhs(); + MDefinition* rhs = use_def->toSub()->rhs(); + if (rhs->id() < lhs->id() && CanProduceNegativeZero(lhs)) { + return true; + } + + [[fallthrough]]; + } + case MDefinition::Opcode::StoreElement: + case MDefinition::Opcode::StoreHoleValueElement: + case MDefinition::Opcode::LoadElement: + case MDefinition::Opcode::LoadElementHole: + case MDefinition::Opcode::LoadUnboxedScalar: + case MDefinition::Opcode::LoadDataViewElement: + case MDefinition::Opcode::LoadTypedArrayElementHole: + case MDefinition::Opcode::CharCodeAt: + case MDefinition::Opcode::Mod: + case MDefinition::Opcode::InArray: + // Only allowed to remove check when definition is the second operand + if (use_def->getOperand(0) == def) { + return true; + } + for (size_t i = 2, e = use_def->numOperands(); i < e; i++) { + if (use_def->getOperand(i) == def) { + return true; + } + } + break; + case MDefinition::Opcode::BoundsCheck: + // Only allowed to remove check when definition is the first operand + if (use_def->toBoundsCheck()->getOperand(1) == def) { + return true; + } + break; + case MDefinition::Opcode::ToString: + case MDefinition::Opcode::FromCharCode: + case MDefinition::Opcode::FromCodePoint: + case MDefinition::Opcode::TableSwitch: + case MDefinition::Opcode::Compare: + case MDefinition::Opcode::BitAnd: + case MDefinition::Opcode::BitOr: + case MDefinition::Opcode::BitXor: + case MDefinition::Opcode::Abs: + case MDefinition::Opcode::TruncateToInt32: + // Always allowed to remove check. No matter which operand. + break; + case MDefinition::Opcode::StoreElementHole: + case MDefinition::Opcode::StoreTypedArrayElementHole: + case MDefinition::Opcode::PostWriteElementBarrier: + // Only allowed to remove check when definition is the third operand. + for (size_t i = 0, e = use_def->numOperands(); i < e; i++) { + if (i == 2) { + continue; + } + if (use_def->getOperand(i) == def) { + return true; + } + } + break; + default: + return true; + } + } + return false; +} + #ifdef JS_JITSPEW void MBinaryArithInstruction::printOpcode(GenericPrinter& out) const { MDefinition::printOpcode(out); @@ -3780,6 +3937,54 @@ MDefinition* MDiv::foldsTo(TempAllocator& alloc) { return this; } +void MDiv::analyzeEdgeCasesForward() { + // This is only meaningful when doing integer division. + if (type() != MIRType::Int32) { + return; + } + + MOZ_ASSERT(lhs()->type() == MIRType::Int32); + MOZ_ASSERT(rhs()->type() == MIRType::Int32); + + // Try removing divide by zero check + if (rhs()->isConstant() && !rhs()->toConstant()->isInt32(0)) { + canBeDivideByZero_ = false; + } + + // If lhs is a constant int != INT32_MIN, then + // negative overflow check can be skipped. + if (lhs()->isConstant() && !lhs()->toConstant()->isInt32(INT32_MIN)) { + canBeNegativeOverflow_ = false; + } + + // If rhs is a constant int != -1, likewise. + if (rhs()->isConstant() && !rhs()->toConstant()->isInt32(-1)) { + canBeNegativeOverflow_ = false; + } + + // If lhs is != 0, then negative zero check can be skipped. + if (lhs()->isConstant() && !lhs()->toConstant()->isInt32(0)) { + setCanBeNegativeZero(false); + } + + // If rhs is >= 0, likewise. + if (rhs()->isConstant() && rhs()->type() == MIRType::Int32) { + if (rhs()->toConstant()->toInt32() >= 0) { + setCanBeNegativeZero(false); + } + } +} + +void MDiv::analyzeEdgeCasesBackward() { + // In general, canBeNegativeZero_ is only valid for integer divides. + // It's fine to access here because we're only using it to avoid + // wasting effort to decide whether we can clear an already cleared + // flag. + if (canBeNegativeZero_ && !NeedNegativeZeroCheck(this)) { + setCanBeNegativeZero(false); + } +} + bool MDiv::fallible() const { return !isTruncated(); } MDefinition* MMod::foldsTo(TempAllocator& alloc) { @@ -3798,6 +4003,24 @@ MDefinition* MMod::foldsTo(TempAllocator& alloc) { return this; } +void MMod::analyzeEdgeCasesForward() { + // These optimizations make sense only for integer division + if (type() != MIRType::Int32) { + return; + } + + if (rhs()->isConstant() && !rhs()->toConstant()->isInt32(0)) { + canBeDivideByZero_ = false; + } + + if (rhs()->isConstant()) { + int32_t n = rhs()->toConstant()->toInt32(); + if (n > 0 && !IsPowerOfTwo(uint32_t(n))) { + canBePowerOfTwoDivisor_ = false; + } + } +} + bool MMod::fallible() const { return !isTruncated() && (isUnsigned() || canBeDivideByZero() || canBeNegativeDividend()); @@ -3903,6 +4126,34 @@ MDefinition* MMul::foldsTo(TempAllocator& alloc) { return this; } +void MMul::analyzeEdgeCasesForward() { + // Try to remove the check for negative zero + // This only makes sense when using the integer multiplication + if (type() != MIRType::Int32) { + return; + } + + // If lhs is > 0, no need for negative zero check. + if (lhs()->isConstant() && lhs()->type() == MIRType::Int32) { + if (lhs()->toConstant()->toInt32() > 0) { + setCanBeNegativeZero(false); + } + } + + // If rhs is > 0, likewise. + if (rhs()->isConstant() && rhs()->type() == MIRType::Int32) { + if (rhs()->toConstant()->toInt32() > 0) { + setCanBeNegativeZero(false); + } + } +} + +void MMul::analyzeEdgeCasesBackward() { + if (canBeNegativeZero() && !NeedNegativeZeroCheck(this)) { + setCanBeNegativeZero(false); + } +} + bool MMul::canOverflow() const { if (isTruncated()) { return false; @@ -4448,6 +4699,12 @@ MDefinition* MBooleanToInt32::foldsTo(TempAllocator& alloc) { return this; } +void MToNumberInt32::analyzeEdgeCasesBackward() { + if (!NeedNegativeZeroCheck(this)) { + setNeedsNegativeZeroCheck(false); + } +} + MDefinition* MTruncateToInt32::foldsTo(TempAllocator& alloc) { MDefinition* input = getOperand(0); if (input->isBox()) { diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h @@ -678,6 +678,8 @@ class MDefinition : public MNode { bool congruentIfOperandsEqual(const MDefinition* ins) const; virtual MDefinition* foldsTo(TempAllocator& alloc); + virtual void analyzeEdgeCasesForward(); + virtual void analyzeEdgeCasesBackward(); // |canTruncate| reports if this instruction supports truncation. If // |canTruncate| function returns true, then the |truncate| function is @@ -3824,6 +3826,7 @@ class MToNumberInt32 : public MUnaryInstruction, public ToInt32Policy::Data { MDefinition* foldsTo(TempAllocator& alloc) override; // this only has backwards information flow. + void analyzeEdgeCasesBackward() override; bool needsNegativeZeroCheck() const { return needsNegativeZeroCheck_; } void setNeedsNegativeZeroCheck(bool needsCheck) { @@ -5212,6 +5215,8 @@ class MMul : public MBinaryArithInstruction { } MDefinition* foldsTo(TempAllocator& alloc) override; + void analyzeEdgeCasesForward() override; + void analyzeEdgeCasesBackward() override; void collectRangeInfoPreTrunc() override; double getIdentity() const override { return 1; } @@ -5309,6 +5314,8 @@ class MDiv : public MBinaryArithInstruction { } MDefinition* foldsTo(TempAllocator& alloc) override; + void analyzeEdgeCasesForward() override; + void analyzeEdgeCasesBackward() override; double getIdentity() const override { MOZ_CRASH("not used"); } @@ -5445,6 +5452,8 @@ class MMod : public MBinaryArithInstruction { return canBePowerOfTwoDivisor_; } + void analyzeEdgeCasesForward() override; + bool isUnsigned() const { return unsigned_; } bool trapOnError() const { return trapOnError_; } diff --git a/js/src/jit/moz.build b/js/src/jit/moz.build @@ -38,6 +38,7 @@ UNIFIED_SOURCES += [ "CompileWrappers.cpp", "Disassemble.cpp", "DominatorTree.cpp", + "EdgeCaseAnalysis.cpp", "EffectiveAddressAnalysis.cpp", "ExecutableAllocator.cpp", "FlushICache.cpp", diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp @@ -13895,6 +13895,16 @@ bool SetContextJITOptions(JSContext* cx, const OptionParser& op) { } } + if (const char* str = op.getStringOption("ion-edgecase-analysis")) { + if (strcmp(str, "on") == 0) { + jit::JitOptions.disableEdgeCaseAnalysis = false; + } else if (strcmp(str, "off") == 0) { + jit::JitOptions.disableEdgeCaseAnalysis = true; + } else { + return OptionFailure("ion-edgecase-analysis", str); + } + } + if (const char* str = op.getStringOption("ion-pruning")) { if (strcmp(str, "on") == 0) { jit::JitOptions.disablePruning = false;