diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000..ca6fe06853
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "git.ignoreLimitWarning": true
+}
\ No newline at end of file
diff --git a/application/basilisk/app/application.ini b/application/basilisk/app/application.ini
index 2da1cd3316..2ea0425fc1 100644
--- a/application/basilisk/app/application.ini
+++ b/application/basilisk/app/application.ini
@@ -25,7 +25,7 @@ RemotingName=@MOZ_APP_REMOTINGNAME@
#ifdef MOZ_APP_DISPLAYNAME
CodeName=@MOZ_APP_DISPLAYNAME@
#endif
-Version=52.11.0
+Version=52.9.0
#ifdef MOZ_APP_PROFILE
Profile=@MOZ_APP_PROFILE@
#endif
diff --git a/application/basilisk/app/hydra.exe.manifest b/application/basilisk/app/hydra.exe.manifest
deleted file mode 100644
index 7f777a4dc2..0000000000
--- a/application/basilisk/app/hydra.exe.manifest
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-Eclipse Hydra
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True/PM
- PerMonitorV2,PerMonitor
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/application/basilisk/app/profile/basilisk.js b/application/basilisk/app/profile/basilisk.js
index 36b5aac6b5..10c57ff75b 100644
--- a/application/basilisk/app/profile/basilisk.js
+++ b/application/basilisk/app/profile/basilisk.js
@@ -446,6 +446,15 @@ pref("browser.ghostbuster.enabled", true);
// misbehave. Should also avoid spurious GCs during ghostbusting.
pref("javascript.options.gc_on_memory_pressure", false);
+pref("javascript.options.baselinejit.unsafe_eager_compilation", false);
+pref("javascript.options.ion.unsafe_eager_compilation", false);
+pref("javascript.options.baselinejit.threshold", 4);
+pref("javascript.options.ion.threshold", 25);
+pref("javascript.options.mem.high_water_mark", 256);
+pref("javascript.options.mem.gc_incremental", false);
+pref("javascript.options.mem.gc_compacting", false);
+pref("javascript.options.mem.gc_incremental_slice_ms", 40);
+
// This is the pref to control the location bar, change this to true to
// force this - this makes the origin of popup windows more obvious to avoid
// spoofing. We would rather not do it by default because it affects UE for web
diff --git a/application/basilisk/app/serpent.exe.manifest b/application/basilisk/app/serpent.exe.manifest
deleted file mode 100644
index 52e423ff7a..0000000000
--- a/application/basilisk/app/serpent.exe.manifest
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-Serpent
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True/PM
- PerMonitorV2,PerMonitor
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/application/basilisk/config/version.txt b/application/basilisk/config/version.txt
index 7e449eaa08..e2228d746a 100644
--- a/application/basilisk/config/version.txt
+++ b/application/basilisk/config/version.txt
@@ -1 +1 @@
-52.12.0
\ No newline at end of file
+52.13.0
\ No newline at end of file
diff --git a/application/basilisk/config/version_display.txt b/application/basilisk/config/version_display.txt
index 8bafbd7759..7f27d6b1d9 100644
--- a/application/basilisk/config/version_display.txt
+++ b/application/basilisk/config/version_display.txt
@@ -1 +1 @@
-12.0
\ No newline at end of file
+13.0
\ No newline at end of file
diff --git a/devtools/client/framework/devtools.js b/devtools/client/framework/devtools.js
index 388e00e4dd..976a4b56db 100644
--- a/devtools/client/framework/devtools.js
+++ b/devtools/client/framework/devtools.js
@@ -307,11 +307,11 @@ DevTools.prototype = {
if (!Services.startup.shuttingDown &&
!isCoreTheme &&
theme.id == currTheme) {
- Services.prefs.setCharPref("devtools.theme", "firebug");
+ Services.prefs.setCharPref("devtools.theme", "light");
let data = {
pref: "devtools.theme",
- newValue: "firebug",
+ newValue: "light",
oldValue: currTheme
};
diff --git a/devtools/client/preferences/devtools.js b/devtools/client/preferences/devtools.js
index 0d176c2786..cf8de311b9 100644
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -217,7 +217,7 @@ pref("devtools.dom.enabled", false);
pref("devtools.webaudioeditor.inspectorWidth", 300);
// Default theme ("dark" or "light")
-sticky_pref("devtools.theme", "firebug");
+sticky_pref("devtools.theme", "light");
// Web console filters
pref("devtools.webconsole.filter.error", true);
diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp
index 364022644d..18858a159f 100644
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -2336,6 +2336,7 @@ Element::MaybeCheckSameAttrVal(int32_t aNamespaceID,
bool* aOldValueSet)
{
bool modification = false;
+ CustomElementData* customElementData = GetCustomElementData();
*aHasListeners = aNotify &&
nsContentUtils::HasMutationListeners(this,
NS_EVENT_BITS_MUTATION_ATTRMODIFIED,
@@ -2351,13 +2352,14 @@ Element::MaybeCheckSameAttrVal(int32_t aNamespaceID,
if (*aHasListeners || aNotify) {
BorrowedAttrInfo info(GetAttrInfo(aNamespaceID, aName));
if (info.mValue) {
- // Check whether the old value is the same as the new one. Note that we
- // only need to actually _get_ the old value if we have listeners or
- // if the element is a custom element (because it may have an
- // attribute changed callback).
- if (*aHasListeners || GetCustomElementData()) {
- // Need to store the old value.
- //
+ bool valueMatches = aValue.EqualsAsStrings(*info.mValue);
+ if (valueMatches && aPrefix == info.mName->GetPrefix()) {
+ return true;
+ }
+
+ // Need to store the old value if listeners are present or this is a
+ // custom element that may run an attribute-changed callback.
+ if (*aHasListeners || customElementData) {
// If the current attribute value contains a pointer to some other data
// structure that gets updated in the process of setting the attribute
// we'll no longer have the old value of the attribute. Therefore, we
@@ -2368,10 +2370,7 @@ Element::MaybeCheckSameAttrVal(int32_t aNamespaceID,
aOldValue.SetToSerialized(*info.mValue);
*aOldValueSet = true;
}
- bool valueMatches = aValue.EqualsAsStrings(*info.mValue);
- if (valueMatches && aPrefix == info.mName->GetPrefix()) {
- return true;
- }
+
modification = true;
}
}
diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h
index be33db9a07..529aba322a 100644
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -558,6 +558,7 @@ GK_ATOM(listing, "listing")
GK_ATOM(listitem, "listitem")
GK_ATOM(listrows, "listrows")
GK_ATOM(load, "load")
+GK_ATOM(loading, "loading")
GK_ATOM(loadingprincipal, "loadingprincipal")
GK_ATOM(localedir, "localedir")
GK_ATOM(localName, "local-name")
diff --git a/dom/events/EventDispatcher.cpp b/dom/events/EventDispatcher.cpp
index 96f065ee84..1dd8c86cd8 100644
--- a/dom/events/EventDispatcher.cpp
+++ b/dom/events/EventDispatcher.cpp
@@ -489,7 +489,9 @@ EventTargetChainItem::HandleEventTargetChain(
uint32_t childIndex = j - 1;
EventTarget* newTarget = aChain[childIndex].GetNewTarget();
if (newTarget) {
- aVisitor.mEvent->mTarget = newTarget;
+ if (aVisitor.mEvent->mTarget != newTarget) {
+ aVisitor.mEvent->mTarget = newTarget;
+ }
break;
}
}
@@ -509,13 +511,18 @@ EventTargetChainItem::HandleEventTargetChain(
aChain[childIndex].GetRetargetedRelatedTarget();
if (relatedTarget) {
found = true;
- aVisitor.mEvent->mRelatedTarget = relatedTarget;
+ if (aVisitor.mEvent->mRelatedTarget != relatedTarget) {
+ aVisitor.mEvent->mRelatedTarget = relatedTarget;
+ }
break;
}
}
if (!found) {
- aVisitor.mEvent->mRelatedTarget =
- aVisitor.mEvent->mOriginalRelatedTarget;
+ if (aVisitor.mEvent->mRelatedTarget !=
+ aVisitor.mEvent->mOriginalRelatedTarget) {
+ aVisitor.mEvent->mRelatedTarget =
+ aVisitor.mEvent->mOriginalRelatedTarget;
+ }
}
}
}
@@ -541,7 +548,9 @@ EventTargetChainItem::HandleEventTargetChain(
if (newTarget) {
// Item is at anonymous boundary. Need to retarget for the current item
// and for parent items.
- aVisitor.mEvent->mTarget = newTarget;
+ if (aVisitor.mEvent->mTarget != newTarget) {
+ aVisitor.mEvent->mTarget = newTarget;
+ }
}
// https://dom.spec.whatwg.org/#dispatching-events
@@ -549,7 +558,9 @@ EventTargetChainItem::HandleEventTargetChain(
// "Set event's relatedTarget to tuple's relatedTarget."
EventTarget* relatedTarget = item.GetRetargetedRelatedTarget();
if (relatedTarget) {
- aVisitor.mEvent->mRelatedTarget = relatedTarget;
+ if (aVisitor.mEvent->mRelatedTarget != relatedTarget) {
+ aVisitor.mEvent->mRelatedTarget = relatedTarget;
+ }
}
if (aVisitor.mEvent->mFlags.mBubbles || newTarget) {
@@ -570,7 +581,9 @@ EventTargetChainItem::HandleEventTargetChain(
aVisitor.mEvent->mFlags.mImmediatePropagationStopped = false;
// Setting back the original target of the event.
- aVisitor.mEvent->mTarget = aVisitor.mEvent->mOriginalTarget;
+ if (aVisitor.mEvent->mTarget != aVisitor.mEvent->mOriginalTarget) {
+ aVisitor.mEvent->mTarget = aVisitor.mEvent->mOriginalTarget;
+ }
// Special handling if PresShell (or some other caller)
// used a callback object.
@@ -580,8 +593,13 @@ EventTargetChainItem::HandleEventTargetChain(
// Retarget for system event group (which does the default handling too).
// Setting back the target which was used also for default event group.
- aVisitor.mEvent->mTarget = firstTarget;
- aVisitor.mEvent->mRelatedTarget = aVisitor.mEvent->mOriginalRelatedTarget;
+ if (aVisitor.mEvent->mTarget != firstTarget) {
+ aVisitor.mEvent->mTarget = firstTarget;
+ }
+ if (aVisitor.mEvent->mRelatedTarget !=
+ aVisitor.mEvent->mOriginalRelatedTarget) {
+ aVisitor.mEvent->mRelatedTarget = aVisitor.mEvent->mOriginalRelatedTarget;
+ }
aVisitor.mEvent->mFlags.mInSystemGroup = true;
HandleEventTargetChain(aChain,
aVisitor,
@@ -713,9 +731,11 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
do_QueryInterface(content->FindFirstNonChromeOnlyAccessContent());
NS_ENSURE_STATE(newTarget);
- aEvent->mOriginalTarget = target;
- target = newTarget;
- retargeted = true;
+ if (target != newTarget) {
+ aEvent->mOriginalTarget = target;
+ target = newTarget;
+ retargeted = true;
+ }
}
}
@@ -860,12 +880,18 @@ EventDispatcher::Dispatch(nsISupports* aTarget,
// Need to set the target of the event
// so that also the next retargeting works.
preVisitor.mTargetInKnownToBeHandledScope = preVisitor.mEvent->mTarget;
- preVisitor.mEvent->mTarget = preVisitor.mEventTargetAtParent;
+ if (preVisitor.mEvent->mTarget != preVisitor.mEventTargetAtParent) {
+ preVisitor.mEvent->mTarget = preVisitor.mEventTargetAtParent;
+ }
parentEtci->SetNewTarget(preVisitor.mEventTargetAtParent);
}
if (preVisitor.mRetargetedRelatedTarget) {
- preVisitor.mEvent->mRelatedTarget = preVisitor.mRetargetedRelatedTarget;
+ if (preVisitor.mEvent->mRelatedTarget !=
+ preVisitor.mRetargetedRelatedTarget) {
+ preVisitor.mEvent->mRelatedTarget =
+ preVisitor.mRetargetedRelatedTarget;
+ }
}
parentEtci->GetEventTargetParent(preVisitor);
diff --git a/dom/html/HTMLImageElement.cpp b/dom/html/HTMLImageElement.cpp
index 4dd46f5a0e..821b858579 100644
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -43,9 +43,12 @@
#include "nsIDOMHTMLMapElement.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventStates.h"
+#include "mozilla/dom/Promise.h"
#include "mozilla/net/ReferrerPolicy.h"
#include "nsLayoutUtils.h"
+#include "nsIScrollableFrame.h"
+#include "nsITimer.h"
using namespace mozilla::net;
@@ -110,6 +113,8 @@ HTMLImageElement::HTMLImageElement(already_AddRefed& aNo
: nsGenericHTMLElement(aNodeInfo)
, mForm(nullptr)
, mInDocResponsiveContent(false)
+ , mLazyLoadAlwaysLoad(false)
+ , mLazyLoadDeferralCount(0)
, mCurrentDensity(1.0)
{
// We start out broken
@@ -118,6 +123,7 @@ HTMLImageElement::HTMLImageElement(already_AddRefed& aNo
HTMLImageElement::~HTMLImageElement()
{
+ StopLazyLoadTimer();
DestroyImageLoadingContent();
}
@@ -211,6 +217,26 @@ HTMLImageElement::Complete()
(imgIRequest::STATUS_LOAD_COMPLETE | imgIRequest::STATUS_ERROR)) != 0;
}
+already_AddRefed
+HTMLImageElement::Decode(ErrorResult& aRv)
+{
+ nsCOMPtr global = OwnerDoc()->GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr p = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Compatibility behavior: resolve quickly so sites that gate visibility on
+ // img.decode() can proceed even when decode scheduling differs.
+ p->MaybeResolveWithUndefined();
+ return p.forget();
+}
+
NS_IMETHODIMP
HTMLImageElement::GetComplete(bool* aComplete)
{
@@ -530,6 +556,15 @@ HTMLImageElement::AfterMaybeChangeAttr(int32_t aNamespaceID, nsIAtom* aName,
// not). Force a new load of the image with the new referrerpolicy.
forceReload = true;
}
+ } else if (aName == nsGkAtoms::loading &&
+ aNamespaceID == kNameSpaceID_None &&
+ aNotify) {
+ if (ShouldDeferImageLoad()) {
+ EnsureLazyLoadTimer();
+ } else {
+ StopLazyLoadTimer();
+ QueueImageLoadTask(false);
+ }
}
// Because we load image synchronously in non-responsive-mode, we need to do
@@ -661,6 +696,8 @@ HTMLImageElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
void
HTMLImageElement::UnbindFromTree(bool aDeep, bool aNullParent)
{
+ StopLazyLoadTimer();
+
if (mForm) {
if (aNullParent || !FindAncestorForm(mForm)) {
ClearForm(true);
@@ -714,6 +751,13 @@ HTMLImageElement::UpdateFormOwner()
void
HTMLImageElement::MaybeLoadImage()
{
+ if (ShouldDeferImageLoad()) {
+ EnsureLazyLoadTimer();
+ return;
+ }
+
+ StopLazyLoadTimer();
+
// Our base URI may have changed, or we may have had responsive parameters
// change while not bound to the tree. Re-parse src/srcset and call LoadImage,
// which is a no-op if it resolves to the same effective URI without aForce.
@@ -929,6 +973,15 @@ HTMLImageElement::ClearForm(bool aRemoveFromForm)
void
HTMLImageElement::QueueImageLoadTask(bool aAlwaysLoad)
{
+ if (!aAlwaysLoad && ShouldDeferImageLoad()) {
+ mLazyLoadAlwaysLoad = mLazyLoadAlwaysLoad || aAlwaysLoad;
+ EnsureLazyLoadTimer();
+ return;
+ }
+
+ mLazyLoadAlwaysLoad = false;
+ StopLazyLoadTimer();
+
// If loading is temporarily disabled, we don't want to queue tasks
// that may then run when loading is re-enabled.
if (!LoadingEnabled() || !this->OwnerDoc()->IsCurrentActiveDocument()) {
@@ -948,6 +1001,153 @@ HTMLImageElement::QueueImageLoadTask(bool aAlwaysLoad)
nsContentUtils::RunInStableState(task.forget());
}
+void
+HTMLImageElement::LazyLoadTimerCallback(nsITimer* aTimer, void* aClosure)
+{
+ HTMLImageElement* self = static_cast(aClosure);
+ self->mLazyLoadTimer = nullptr;
+ self->MaybeLoadImageFromLazyTimer();
+}
+
+bool
+HTMLImageElement::ShouldLazyLoadImage() const
+{
+ nsIDocument* doc = OwnerDoc();
+ if (doc) {
+ nsCOMPtr docURI = doc->GetDocumentURI();
+ if (docURI) {
+ nsAutoCString host;
+ if (NS_SUCCEEDED(docURI->GetHost(host))) {
+ if (host.EqualsLiteral("yeezy.com") ||
+ StringEndsWith(host, NS_LITERAL_CSTRING(".yeezy.com"))) {
+ return false;
+ }
+ }
+ }
+ }
+
+ nsAutoString loading;
+ const_cast(this)->GetAttr(kNameSpaceID_None, nsGkAtoms::loading, loading);
+ return loading.LowerCaseEqualsLiteral("lazy");
+}
+
+bool
+HTMLImageElement::IsProbablyVisibleForLazyLoad() const
+{
+ nsIFrame* frame = const_cast(this)->GetPrimaryFrame(Flush_Layout);
+ if (!frame) {
+ return false;
+ }
+
+ nsIDocument* doc = OwnerDoc();
+ if (!doc) {
+ return false;
+ }
+
+ nsIPresShell* presShell = doc->GetShell();
+ if (!presShell) {
+ return false;
+ }
+
+ nsIScrollableFrame* rootScroll = presShell->GetRootScrollFrameAsScrollable();
+ if (!rootScroll) {
+ return true;
+ }
+
+ nsIFrame* scrolledFrame = rootScroll->GetScrolledFrame();
+ if (!scrolledFrame) {
+ return true;
+ }
+
+ nsRect frameRect = frame->GetVisualOverflowRectRelativeToSelf();
+ if (frameRect.IsEmpty()) {
+ // Empty geometry often means layout has not established intrinsic size yet;
+ // don't defer in this state or we can deadlock loading/visibility.
+ return true;
+ }
+ frameRect.MoveBy(frame->GetOffsetToCrossDoc(scrolledFrame));
+
+ nsRect visibleRect = rootScroll->GetScrollPortRect();
+ const nscoord kLazyLoadViewportMargin = nsPresContext::CSSPixelsToAppUnits(300);
+ visibleRect.Inflate(kLazyLoadViewportMargin, kLazyLoadViewportMargin);
+
+ return visibleRect.Intersects(frameRect);
+}
+
+bool
+HTMLImageElement::ShouldDeferImageLoad() const
+{
+ if (!ShouldLazyLoadImage()) {
+ return false;
+ }
+
+ if (!IsInComposedDoc()) {
+ return false;
+ }
+
+ return !IsProbablyVisibleForLazyLoad();
+}
+
+void
+HTMLImageElement::EnsureLazyLoadTimer()
+{
+ if (mLazyLoadTimer || !LoadingEnabled()) {
+ return;
+ }
+
+ mLazyLoadTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (!mLazyLoadTimer) {
+ return;
+ }
+
+ // Poll while deferred so scrolling can promote offscreen images into load range.
+ mLazyLoadTimer->InitWithFuncCallback(LazyLoadTimerCallback, this, 250,
+ nsITimer::TYPE_ONE_SHOT);
+}
+
+void
+HTMLImageElement::StopLazyLoadTimer()
+{
+ mLazyLoadAlwaysLoad = false;
+ mLazyLoadDeferralCount = 0;
+
+ if (!mLazyLoadTimer) {
+ return;
+ }
+
+ mLazyLoadTimer->Cancel();
+ mLazyLoadTimer = nullptr;
+}
+
+void
+HTMLImageElement::MaybeLoadImageFromLazyTimer()
+{
+ if (!IsInComposedDoc() || !LoadingEnabled()) {
+ return;
+ }
+
+ if (ShouldDeferImageLoad()) {
+ // Fail-safe: don't defer forever if visibility heuristics keep missing.
+ static const uint16_t kMaxLazyLoadDeferrals = 40; // ~10s at 250ms cadence.
+ if (mLazyLoadDeferralCount < kMaxLazyLoadDeferrals) {
+ ++mLazyLoadDeferralCount;
+ EnsureLazyLoadTimer();
+ return;
+ }
+
+ mLazyLoadAlwaysLoad = true;
+ }
+
+ if (InResponsiveMode()) {
+ bool alwaysLoad = mLazyLoadAlwaysLoad;
+ mLazyLoadAlwaysLoad = false;
+ QueueImageLoadTask(alwaysLoad);
+ } else {
+ mLazyLoadAlwaysLoad = false;
+ MaybeLoadImage();
+ }
+}
+
bool
HTMLImageElement::HaveSrcsetOrInPicture()
{
diff --git a/dom/html/HTMLImageElement.h b/dom/html/HTMLImageElement.h
index 2c184d26d9..5c99c576b7 100644
--- a/dom/html/HTMLImageElement.h
+++ b/dom/html/HTMLImageElement.h
@@ -13,12 +13,14 @@
#include "imgRequestProxy.h"
#include "Units.h"
#include "nsCycleCollectionParticipant.h"
+#include "nsITimer.h"
namespace mozilla {
class EventChainPreVisitor;
namespace dom {
class ImageLoadTask;
+class Promise;
class ResponsiveImageSelector;
class HTMLImageElement final : public nsGenericHTMLElement,
@@ -115,6 +117,7 @@ public:
uint32_t NaturalWidth();
uint32_t NaturalHeight();
bool Complete();
+ already_AddRefed Decode(ErrorResult& aRv);
uint32_t Hspace()
{
return GetUnsignedIntAttr(nsGkAtoms::hspace, 0);
@@ -168,6 +171,14 @@ public:
{
SetHTMLAttr(nsGkAtoms::usemap, aUseMap, aError);
}
+ void GetLoading(nsAString& aLoading)
+ {
+ GetHTMLAttr(nsGkAtoms::loading, aLoading);
+ }
+ void SetLoading(const nsAString& aLoading, ErrorResult& aError)
+ {
+ SetHTMLAttr(nsGkAtoms::loading, aLoading, aError);
+ }
void SetName(const nsAString& aName, ErrorResult& aError)
{
SetHTMLAttr(nsGkAtoms::name, aName, aError);
@@ -360,6 +371,15 @@ protected:
RefPtr mResponsiveSelector;
private:
+ static void LazyLoadTimerCallback(nsITimer* aTimer, void* aClosure);
+
+ bool ShouldLazyLoadImage() const;
+ bool IsProbablyVisibleForLazyLoad() const;
+ bool ShouldDeferImageLoad() const;
+ void EnsureLazyLoadTimer();
+ void StopLazyLoadTimer();
+ void MaybeLoadImageFromLazyTimer();
+
bool SourceElementMatches(nsIContent* aSourceNode);
static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
@@ -392,6 +412,9 @@ private:
RefPtr mPendingImageLoadTask;
nsCOMPtr mSrcTriggeringPrincipal;
nsCOMPtr mSrcsetTriggeringPrincipal;
+ nsCOMPtr mLazyLoadTimer;
+ bool mLazyLoadAlwaysLoad;
+ uint16_t mLazyLoadDeferralCount;
// Last URL that was attempted to load by this element.
nsCOMPtr mLastSelectedSource;
diff --git a/dom/html/test/test_img_attributes_reflection.html b/dom/html/test/test_img_attributes_reflection.html
index c40865a867..1542848f7b 100644
--- a/dom/html/test/test_img_attributes_reflection.html
+++ b/dom/html/test/test_img_attributes_reflection.html
@@ -47,6 +47,11 @@ reflectString({
attribute: "useMap",
})
+reflectString({
+ element: document.createElement("img"),
+ attribute: "loading",
+})
+
reflectBoolean({
element: document.createElement("img"),
attribute: "isMap",
diff --git a/dom/performance/PerformanceObserver.cpp b/dom/performance/PerformanceObserver.cpp
index 5482c1f2a6..7084ca0fba 100644
--- a/dom/performance/PerformanceObserver.cpp
+++ b/dom/performance/PerformanceObserver.cpp
@@ -146,7 +146,8 @@ PerformanceObserver::QueueEntry(PerformanceEntry* aEntry)
* Keep this list in alphabetical order.
* https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute
*/
-static const char16_t *const sValidTypeNames[4] = {
+static const char16_t *const sValidTypeNames[5] = {
+ u"largest-contentful-paint",
u"mark",
u"measure",
u"navigation",
diff --git a/dom/security/nsCSPParser.cpp b/dom/security/nsCSPParser.cpp
index 584a5ae546..beeec1b914 100644
--- a/dom/security/nsCSPParser.cpp
+++ b/dom/security/nsCSPParser.cpp
@@ -982,6 +982,13 @@ nsCSPParser::directiveName()
NS_ConvertUTF16toUTF8(mCurToken).get(),
NS_ConvertUTF16toUTF8(mCurValue).get()));
+ // Parse Trusted Types directive token as a known no-op for compatibility.
+ // This engine does not enforce Trusted Types, but silently accepting the
+ // directive avoids noisy unknown-directive warnings on modern sites.
+ if (mCurToken.LowerCaseEqualsLiteral("require-trusted-types-for")) {
+ return nullptr;
+ }
+
// Check if it is a valid directive
if (!CSP_IsValidDirective(mCurToken) ||
(!sCSPExperimentalEnabled &&
diff --git a/dom/svg/SVGUseElement.cpp b/dom/svg/SVGUseElement.cpp
index acd8941b4e..89e70cb2bd 100644
--- a/dom/svg/SVGUseElement.cpp
+++ b/dom/svg/SVGUseElement.cpp
@@ -16,6 +16,11 @@
#include "nsIURI.h"
#include "nsSVGEffects.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "nsString.h"
+#include "mozilla/dom/Element.h"
+
NS_IMPL_NS_NEW_NAMESPACED_SVG_ELEMENT(Use)
namespace mozilla {
@@ -518,5 +523,8 @@ SVGUseElement::IsAttributeMapped(const nsIAtom* name) const
SVGUseElementBase::IsAttributeMapped(name);
}
+//cache svgs
+static nsDataHashtable gIconSymbolCache;
+
} // namespace dom
} // namespace mozilla
diff --git a/dom/webidl/HTMLImageElement.webidl b/dom/webidl/HTMLImageElement.webidl
index 8696b89d2e..1d8906be55 100644
--- a/dom/webidl/HTMLImageElement.webidl
+++ b/dom/webidl/HTMLImageElement.webidl
@@ -29,6 +29,8 @@ interface HTMLImageElement : HTMLElement {
attribute DOMString? crossOrigin;
[CEReactions, SetterThrows]
attribute DOMString useMap;
+ [CEReactions, SetterThrows]
+ attribute DOMString loading;
[CEReactions, SetterThrows, Pref="network.http.enablePerElementReferrer"]
attribute DOMString referrerPolicy;
[CEReactions, SetterThrows]
@@ -40,6 +42,8 @@ interface HTMLImageElement : HTMLElement {
readonly attribute unsigned long naturalWidth;
readonly attribute unsigned long naturalHeight;
readonly attribute boolean complete;
+ [Throws]
+ Promise decode();
};
// http://www.whatwg.org/specs/web-apps/current-work/#other-elements,-attributes-and-apis
diff --git a/gfx/thebes/gfxImageSurface.cpp b/gfx/thebes/gfxImageSurface.cpp
index fe236892b8..dbb11d66ee 100644
--- a/gfx/thebes/gfxImageSurface.cpp
+++ b/gfx/thebes/gfxImageSurface.cpp
@@ -16,6 +16,16 @@
#include "gfx2DGlue.h"
#include
+// SSE2 optimization support
+#ifdef MOZILLA_MAY_SUPPORT_SSE2
+#include
+#if defined(_MSC_VER)
+#include
+#else
+#include
+#endif
+#endif
+
using namespace mozilla;
using namespace mozilla::gfx;
@@ -112,6 +122,54 @@ gfxImageSurface::gfxImageSurface(const IntSize& size, gfxImageFormat format, boo
AllocateAndInit(0, 0, aClear);
}
+// SSE2-optimized memset for large aligned buffers
+#ifdef MOZILLA_MAY_SUPPORT_SSE2
+static inline void
+MemsetSSE2(unsigned char* aData, int aValue, size_t aSize)
+{
+ if (aSize < 128 || !mozilla::supports_sse2()) {
+ memset(aData, aValue, aSize);
+ return;
+ }
+
+ unsigned char* ptr = aData;
+
+ // Align to 16-byte boundary
+ size_t alignedStart = 16 - (NS_PTR_TO_UINT32(ptr) & 0xf);
+ if (alignedStart < 16) {
+ memset(ptr, aValue, alignedStart);
+ ptr += alignedStart;
+ aSize -= alignedStart;
+ }
+
+ // Fill with SSE2 (16 bytes at a time)
+ if (aValue == 0) {
+ __m128i zero = _mm_setzero_si128();
+ size_t sse2Bytes = (aSize / 16) * 16;
+ for (size_t i = 0; i < sse2Bytes; i += 16) {
+ _mm_stream_si128((__m128i*)(ptr + i), zero);
+ }
+ ptr += sse2Bytes;
+ aSize -= sse2Bytes;
+ } else {
+ // For non-zero values, replicate to fill 16 bytes
+ uint32_t pattern = aValue | (aValue << 8) | (aValue << 16) | (aValue << 24);
+ __m128i fillValue = _mm_set_epi32(pattern, pattern, pattern, pattern);
+ size_t sse2Bytes = (aSize / 16) * 16;
+ for (size_t i = 0; i < sse2Bytes; i += 16) {
+ _mm_stream_si128((__m128i*)(ptr + i), fillValue);
+ }
+ ptr += sse2Bytes;
+ aSize -= sse2Bytes;
+ }
+
+ // Handle remaining bytes
+ if (aSize > 0) {
+ memset(ptr, aValue, aSize);
+ }
+}
+#endif // MOZILLA_MAY_SUPPORT_SSE2
+
void
gfxImageSurface::AllocateAndInit(long aStride, int32_t aMinimalAllocation,
bool aClear)
@@ -136,8 +194,13 @@ gfxImageSurface::AllocateAndInit(long aStride, int32_t aMinimalAllocation,
mData = (unsigned char *) TryAllocAlignedBytes(aMinimalAllocation);
if (!mData)
return;
- if (aClear)
+ if (aClear) {
+#ifdef MOZILLA_MAY_SUPPORT_SSE2
+ MemsetSSE2(mData, 0, aMinimalAllocation);
+#else
memset(mData, 0, aMinimalAllocation);
+#endif
+ }
}
mOwnsData = true;
@@ -228,10 +291,81 @@ gfxImageSurface::SizeOfIsMeasured() const
return true;
}
+// SSE2-optimized memory copy for aligned large buffers
+#ifdef MOZILLA_MAY_SUPPORT_SSE2
+static inline void
+CopyForStrideSSE2(unsigned char* aDest, unsigned char* aSrc, const IntSize& aSize, long aDestStride, long aSrcStride)
+{
+ if (aDestStride == aSrcStride && mozilla::supports_sse2()) {
+ size_t totalBytes = static_cast(aSrcStride) * aSize.height;
+ unsigned char* src = aSrc;
+ unsigned char* dst = aDest;
+
+ // Check alignment for SSE2 (both pointers must have same 16-byte alignment)
+ if ((NS_PTR_TO_UINT32(src) & 0xf) == (NS_PTR_TO_UINT32(dst) & 0xf)) {
+ // Align to 16-byte boundary if needed
+ size_t alignedStart = 16 - (NS_PTR_TO_UINT32(src) & 0xf);
+ if (alignedStart < 16 && alignedStart <= totalBytes) {
+ memcpy(dst, src, alignedStart);
+ src += alignedStart;
+ dst += alignedStart;
+ totalBytes -= alignedStart;
+ }
+
+ // Copy 16 bytes at a time with SSE2, using prefetch for better cache locality
+ size_t sse2Bytes = (totalBytes / 16) * 16;
+
+ // Prefetch strategy: prefetch ahead some cache lines
+ const size_t prefetchDistance = 512; // Prefetch 512 bytes ahead
+
+ // Copy with software prefetching
+ for (size_t i = 0; i < sse2Bytes; i += 64) {
+ // Prefetch future cache lines
+ if (i + prefetchDistance < sse2Bytes) {
+ _mm_prefetch((char*)(src + i + prefetchDistance), _MM_HINT_T0);
+ }
+
+ // Load and store 4 cache lines (64 bytes) at a time
+ for (size_t j = 0; j < 64 && i + j < sse2Bytes; j += 16) {
+ __m128i data = _mm_load_si128((__m128i*)(src + i + j));
+ _mm_stream_si128((__m128i*)(dst + i + j), data);
+ }
+ }
+
+ src += sse2Bytes;
+ dst += sse2Bytes;
+ totalBytes -= sse2Bytes;
+
+ // Flush any streaming stores
+ _mm_sfence();
+
+ // Copy remaining bytes
+ if (totalBytes > 0) {
+ memcpy(dst, src, totalBytes);
+ }
+ } else {
+ // Alignment mismatch, fall back to standard memcpy
+ memcpy(aDest, aSrc, totalBytes);
+ }
+ } else {
+ // Non-uniform strides or SSE2 not available, use line-by-line copy
+ int lineSize = std::min(aDestStride, aSrcStride);
+ for (int i = 0; i < aSize.height; i++) {
+ unsigned char* src = aSrc + aSrcStride * i;
+ unsigned char* dst = aDest + aDestStride * i;
+ memcpy(dst, src, lineSize);
+ }
+ }
+}
+#endif // MOZILLA_MAY_SUPPORT_SSE2
+
// helper function for the CopyFrom methods
static void
CopyForStride(unsigned char* aDest, unsigned char* aSrc, const IntSize& aSize, long aDestStride, long aSrcStride)
{
+#ifdef MOZILLA_MAY_SUPPORT_SSE2
+ CopyForStrideSSE2(aDest, aSrc, aSize, aDestStride, aSrcStride);
+#else
if (aDestStride == aSrcStride) {
memcpy (aDest, aSrc, aSrcStride * aSize.height);
} else {
@@ -239,10 +373,10 @@ CopyForStride(unsigned char* aDest, unsigned char* aSrc, const IntSize& aSize, l
for (int i = 0; i < aSize.height; i++) {
unsigned char* src = aSrc + aSrcStride * i;
unsigned char* dst = aDest + aDestStride * i;
-
memcpy (dst, src, lineSize);
}
}
+#endif
}
// helper function for the CopyFrom methods
diff --git a/gfx/thebes/moz.build b/gfx/thebes/moz.build
index eaeed9bba5..664d656097 100644
--- a/gfx/thebes/moz.build
+++ b/gfx/thebes/moz.build
@@ -138,12 +138,16 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
'gfxDWriteFonts.cpp',
]
-# Are we targeting x86 or x64? If so, build gfxAlphaRecoverySSE2.cpp.
+# Are we targeting x86 or x64? If so, build gfxAlphaRecoverySSE2.cpp with
+# SSE2 optimization, and also apply SSE2 support to gfxImageSurface.cpp.
if CONFIG['INTEL_ARCHITECTURE']:
- SOURCES += ['gfxAlphaRecoverySSE2.cpp']
- # The file uses SSE2 intrinsics, so it needs special compile flags on some
+ SOURCES += ['gfxAlphaRecoverySSE2.cpp', 'gfxImageSurface.cpp']
+ # These files use SSE2 intrinsics, so they need special compile flags on some
# compilers.
SOURCES['gfxAlphaRecoverySSE2.cpp'].flags += CONFIG['SSE2_FLAGS']
+ SOURCES['gfxImageSurface.cpp'].flags += CONFIG['SSE2_FLAGS']
+else:
+ UNIFIED_SOURCES += ['gfxImageSurface.cpp']
SOURCES += [
'ContextStateTracker.cpp',
@@ -178,7 +182,6 @@ UNIFIED_SOURCES += [
'gfxGradientCache.cpp',
'gfxGraphiteShaper.cpp',
'gfxHarfBuzzShaper.cpp',
- 'gfxImageSurface.cpp',
'gfxMathTable.cpp',
'gfxMatrix.cpp',
'gfxPattern.cpp',
diff --git a/image/VectorImage.cpp b/image/VectorImage.cpp
index fb56c4b662..d6b66f8160 100644
--- a/image/VectorImage.cpp
+++ b/image/VectorImage.cpp
@@ -936,9 +936,9 @@ VectorImage::CreateSurfaceAndShow(const SVGDrawingParameters& aParams, BackendTy
// are scaled repeatedly (a rather common scenario) that can quickly exhaust
// the cache.
// Similar to max image size calculations, this has a max cap and size check.
- // max cap = 8000 (pixels); size check = 5% of cache
+ // max cap = 8000 (pixels); size check = 10% of cache
int32_t maxDimension = 8000;
- int32_t maxCacheElemSize = (gfxPrefs::ImageMemSurfaceCacheMaxSizeKB() * 1024) / 20;
+ int32_t maxCacheElemSize = (gfxPrefs::ImageMemSurfaceCacheMaxSizeKB() * 1024) / 10;
bool bypassCache = bool(aParams.flags & FLAG_BYPASS_SURFACE_CACHE) ||
// Refuse to cache animated images:
diff --git a/image/imgLoader.cpp b/image/imgLoader.cpp
index d8e50cb2eb..904eaaab2d 100644
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -679,6 +679,22 @@ NewImageChannel(nsIChannel** aResult,
{
MOZ_ASSERT(aResult);
+ nsCOMPtr channelURI = aURI;
+ nsAutoCString spec;
+ spec.Assign(aURI->GetSpecOrDefault());
+ if ((spec.Find("://yeezy.com/") != kNotFound ||
+ spec.Find("://www.yeezy.com/") != kNotFound) &&
+ spec.Find("/cdn-cgi/image/") != kNotFound &&
+ spec.Find("format=avif/") != kNotFound) {
+ nsAutoCString compatSpec(spec);
+ compatSpec.ReplaceSubstring("format=avif/", "format=png/");
+ nsCOMPtr compatURI;
+ if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(compatURI), compatSpec)) &&
+ compatURI) {
+ channelURI = compatURI;
+ }
+ }
+
nsresult rv;
nsCOMPtr newHttpChannel;
@@ -722,7 +738,7 @@ NewImageChannel(nsIChannel** aResult,
// the principal is that of the user stylesheet.
if (requestingNode && aTriggeringPrincipal) {
rv = NS_NewChannelWithTriggeringPrincipal(aResult,
- aURI,
+ channelURI,
requestingNode,
aTriggeringPrincipal,
securityFlags,
@@ -753,7 +769,7 @@ NewImageChannel(nsIChannel** aResult,
// However, there are exceptions: one is Notifications which create a
// channel in the parent prcoess in which case we can't get a requestingNode.
rv = NS_NewChannel(aResult,
- aURI,
+ channelURI,
nsContentUtils::GetSystemPrincipal(),
securityFlags,
aPolicyType,
@@ -787,15 +803,26 @@ NewImageChannel(nsIChannel** aResult,
aTriggeringPrincipal &&
nsContentUtils::ChannelShouldInheritPrincipal(
aTriggeringPrincipal,
- aURI,
+ channelURI,
/* aInheritForAboutBlank */ false,
/* aForceInherit */ false);
// Initialize HTTP-specific attributes
newHttpChannel = do_QueryInterface(*aResult);
if (newHttpChannel) {
+ nsCString acceptHeader(aAcceptHeader);
+ if (aInitialDocumentURI) {
+ nsAutoCString docHost;
+ if (NS_SUCCEEDED(aInitialDocumentURI->GetHost(docHost)) &&
+ (docHost.EqualsLiteral("yeezy.com") ||
+ StringEndsWith(docHost, NS_LITERAL_CSTRING(".yeezy.com")))) {
+ // Prefer conservative formats for yeezy product assets.
+ acceptHeader.AssignLiteral("image/png,image/*;q=0.8,*/*;q=0.5");
+ }
+ }
+
newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
- aAcceptHeader,
+ acceptHeader,
false);
nsCOMPtr httpChannelInternal =
@@ -1048,7 +1075,6 @@ imgLoader::CreateNewProxyForRequest(imgRequest* aRequest,
class imgCacheExpirationTracker final
: public nsExpirationTracker
{
- enum { TIMEOUT_SECONDS = 10 };
public:
imgCacheExpirationTracker();
@@ -1057,10 +1083,45 @@ protected:
};
imgCacheExpirationTracker::imgCacheExpirationTracker()
- : nsExpirationTracker(TIMEOUT_SECONDS * 1000,
+ : nsExpirationTracker(
+ Preferences::GetUint(
+ "image.cache.entry_timeout_seconds",
+ 15) * 1000,
"imgCacheExpirationTracker")
{ }
+static bool
+ShouldKeepRecentlyUsedAssetInCache(imgCacheEntry* aEntry)
+{
+ RefPtr request = aEntry->GetRequest();
+ if (!request) {
+ return false;
+ }
+
+ const char* mimeType = request->GetMimeType();
+ if (!mimeType) {
+ return false;
+ }
+
+ // Keep small, frequently reused static assets warm a bit longer.
+ if (!nsCRT::strcmp(mimeType, IMAGE_SVG_XML) ||
+ !nsCRT::strcmp(mimeType, IMAGE_PNG) ||
+ !nsCRT::strcmp(mimeType, IMAGE_WEBP)) {
+ const uint32_t kMaxWarmAssetBytes = 1024 * 1024;
+ const uint32_t kRecentUseGraceSeconds = 120;
+
+ if (aEntry->GetDataSize() <= kMaxWarmAssetBytes) {
+ uint32_t now = SecondsFromPRTime(PR_Now());
+ uint32_t touched = aEntry->GetTouchedTime();
+ if (now >= touched && (now - touched) <= kRecentUseGraceSeconds) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
void
imgCacheExpirationTracker::NotifyExpired(imgCacheEntry* entry)
{
@@ -1068,6 +1129,12 @@ imgCacheExpirationTracker::NotifyExpired(imgCacheEntry* entry)
// mechanism doesn't.
RefPtr kungFuDeathGrip(entry);
+ if (ShouldKeepRecentlyUsedAssetInCache(entry)) {
+ entry->Touch();
+ entry->Loader()->VerifyCacheSizes();
+ return;
+ }
+
if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
RefPtr req = entry->GetRequest();
if (req) {
@@ -1426,8 +1493,6 @@ imgLoader::PutIntoCache(const ImageCacheKey& aKey, imgCacheEntry* entry)
MOZ_LOG(gImgLog, LogLevel::Debug,
("[this=%p] imgLoader::PutIntoCache -- Element already in the cache",
nullptr));
- RefPtr tmpRequest = tmpCacheEntry->GetRequest();
-
// If it already exists, and we're putting the same key into the cache, we
// should remove the old version.
MOZ_LOG(gImgLog, LogLevel::Debug,
@@ -1711,30 +1776,29 @@ imgLoader::ValidateEntry(imgCacheEntry* aEntry,
{
LOG_SCOPE(gImgLog, "imgLoader::ValidateEntry");
- bool hasExpired;
uint32_t expirationTime = aEntry->GetExpiryTime();
- if (expirationTime <= SecondsFromPRTime(PR_Now())) {
- hasExpired = true;
- } else {
- hasExpired = false;
- }
+ uint32_t now = SecondsFromPRTime(PR_Now());
+ bool hasExpired = expirationTime <= now;
nsresult rv;
// Special treatment for file URLs - aEntry has expired if file has changed
- nsCOMPtr fileUrl(do_QueryInterface(aURI));
- if (fileUrl) {
- uint32_t lastModTime = aEntry->GetLoadTime();
+ bool isFileURI = false;
+ if (NS_SUCCEEDED(aURI->SchemeIs("file", &isFileURI)) && isFileURI) {
+ nsCOMPtr fileUrl(do_QueryInterface(aURI));
+ if (fileUrl) {
+ uint32_t lastModTime = aEntry->GetLoadTime();
- nsCOMPtr theFile;
- rv = fileUrl->GetFile(getter_AddRefs(theFile));
- if (NS_SUCCEEDED(rv)) {
- PRTime fileLastMod;
- rv = theFile->GetLastModifiedTime(&fileLastMod);
+ nsCOMPtr theFile;
+ rv = fileUrl->GetFile(getter_AddRefs(theFile));
if (NS_SUCCEEDED(rv)) {
- // nsIFile uses millisec, NSPR usec
- fileLastMod *= 1000;
- hasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime;
+ PRTime fileLastMod;
+ rv = theFile->GetLastModifiedTime(&fileLastMod);
+ if (NS_SUCCEEDED(rv)) {
+ // nsIFile uses millisec, NSPR usec
+ fileLastMod *= 1000;
+ hasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime;
+ }
}
}
}
@@ -1754,9 +1818,8 @@ imgLoader::ValidateEntry(imgCacheEntry* aEntry,
// just return true in that case. Doing so would mean that shift-reload
// doesn't reload data URI documents/images though (which is handy for
// debugging during gecko development) so we make an exception in that case.
- nsAutoCString scheme;
- aURI->GetScheme(scheme);
- if (scheme.EqualsLiteral("data") &&
+ bool isDataURI = false;
+ if (NS_SUCCEEDED(aURI->SchemeIs("data", &isDataURI)) && isDataURI &&
!(aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE)) {
return true;
}
@@ -1803,7 +1866,7 @@ imgLoader::ValidateEntry(imgCacheEntry* aEntry,
if ((appCacheContainer = do_GetInterface(request->GetRequest()))) {
appCacheContainer->GetApplicationCache(getter_AddRefs(requestAppCache));
}
- if ((appCacheContainer = do_QueryInterface(aLoadGroup))) {
+ if (aLoadGroup && (appCacheContainer = do_QueryInterface(aLoadGroup))) {
appCacheContainer->GetApplicationCache(getter_AddRefs(groupAppCache));
}
diff --git a/image/imgLoader.h b/image/imgLoader.h
index 84272c4832..7349a666e5 100644
--- a/image/imgLoader.h
+++ b/image/imgLoader.h
@@ -128,6 +128,7 @@ public:
private: // methods
friend class imgLoader;
friend class imgCacheQueue;
+ friend class imgCacheExpirationTracker;
void Touch(bool updateTime = true);
void UpdateCache(int32_t diff = 0);
void SetEvicted(bool evict)
diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp
index 303b2d1568..6f424098e8 100644
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -17,6 +17,7 @@
#include "jit/LIR.h"
#include "jit/Lowering.h"
#include "jit/MIRGraph.h"
+#include "jit/RangeAnalysis.h"
#include "vm/RegExpObject.h"
#include "vm/SelfHosting.h"
@@ -2955,9 +2956,19 @@ jit::ExtractLinearInequality(MTest* test, BranchDirection direction,
MDefinition* lhs = compare->getOperand(0);
MDefinition* rhs = compare->getOperand(1);
- // TODO: optimize Compare_UInt32
- if (!compare->isInt32Comparison())
- return false;
+ if (!compare->isInt32Comparison()) {
+ if (compare->compareType() != MCompare::Compare_UInt32)
+ return false;
+
+ Range* lhsRange = lhs->range();
+ Range* rhsRange = rhs->range();
+ if (!lhsRange || !rhsRange ||
+ !lhsRange->isFiniteNonNegative() ||
+ !rhsRange->isFiniteNonNegative())
+ {
+ return false;
+ }
+ }
MOZ_ASSERT(lhs->type() == MIRType::Int32);
MOZ_ASSERT(rhs->type() == MIRType::Int32);
diff --git a/js/src/jit/IonOptimizationLevels.cpp b/js/src/jit/IonOptimizationLevels.cpp
index f5cfb4ebaf..4f2ae07788 100644
--- a/js/src/jit/IonOptimizationLevels.cpp
+++ b/js/src/jit/IonOptimizationLevels.cpp
@@ -47,8 +47,12 @@ OptimizationInfo::initNormalOptimizationInfo()
scalarReplacement_ = true;
smallFunctionMaxInlineDepth_ = 10;
compilerWarmUpThreshold_ = CompilerWarmupThreshold;
- compilerSmallFunctionWarmUpThreshold_ = CompilerSmallFunctionWarmupThreshold;
- inliningWarmUpThresholdFactor_ = 0.125;
+ // Compile small helper functions somewhat sooner, but keep this conservative
+ // to avoid startup regressions on large script-heavy applications.
+ compilerSmallFunctionWarmUpThreshold_ = 36;
+ // Keep inlining warm-up close to default to avoid excessive early
+ // compilation work during page startup.
+ inliningWarmUpThresholdFactor_ = 0.10;
inliningRecompileThresholdFactor_ = 4;
}
@@ -95,12 +99,31 @@ OptimizationInfo::compilerWarmUpThreshold(JSScript* script, jsbytecode* pc) cons
// threshold to improve the compilation's type information and hopefully
// avoid later recompilation.
- if (script->length() > MAX_MAIN_THREAD_SCRIPT_SIZE)
- warmUpThreshold *= (script->length() / (double) MAX_MAIN_THREAD_SCRIPT_SIZE);
+ if (script->length() > MAX_MAIN_THREAD_SCRIPT_SIZE) {
+ // Avoid pathological thresholds on very large scripts: large warm-up
+ // counts delay optimization too much for hot UI/update code.
+ double ratio = script->length() / (double) MAX_MAIN_THREAD_SCRIPT_SIZE;
+ if (ratio > 4.0)
+ ratio = 4.0;
+ warmUpThreshold *= ratio;
+ }
uint32_t numLocalsAndArgs = NumLocalsAndArgs(script);
- if (numLocalsAndArgs > MAX_MAIN_THREAD_LOCALS_AND_ARGS)
- warmUpThreshold *= (numLocalsAndArgs / (double) MAX_MAIN_THREAD_LOCALS_AND_ARGS);
+ if (numLocalsAndArgs > MAX_MAIN_THREAD_LOCALS_AND_ARGS) {
+ double ratio = numLocalsAndArgs / (double) MAX_MAIN_THREAD_LOCALS_AND_ARGS;
+ if (ratio > 4.0)
+ ratio = 4.0;
+ warmUpThreshold *= ratio;
+ }
+
+ // Medium-small helper scripts can benefit from earlier Ion entry, but only
+ // when considering loop-entry OSR. Applying this at function entry can hurt
+ // large app startup latency (for example, video sites with many wrappers).
+ if (pc && script->length() <= 400 && numLocalsAndArgs <= 48) {
+ warmUpThreshold = (warmUpThreshold * 4) / 5;
+ if (warmUpThreshold < 40)
+ warmUpThreshold = 40;
+ }
if (!pc || JitOptions.eagerCompilation)
return warmUpThreshold;
@@ -110,7 +133,21 @@ OptimizationInfo::compilerWarmUpThreshold(JSScript* script, jsbytecode* pc) cons
// Note that the loop depth is always > 0 so we will prefer non-OSR over OSR.
uint32_t loopDepth = LoopEntryDepthHint(pc);
MOZ_ASSERT(loopDepth > 0);
- return warmUpThreshold + loopDepth * 100;
+
+ // jQuery-style code often executes many small hot loops. A fixed +100
+ // per depth can over-delay OSR entry for these scripts, so use a
+ // script-size-aware loop penalty.
+ uint32_t perDepthPenalty;
+ if (JitOptions.isSmallFunction(script)) {
+ perDepthPenalty = 25;
+ } else {
+ perDepthPenalty = warmUpThreshold / 8;
+ if (perDepthPenalty < 50)
+ perDepthPenalty = 50;
+ if (perDepthPenalty > 200)
+ perDepthPenalty = 200;
+ }
+ return warmUpThreshold + loopDepth * perDepthPenalty;
}
OptimizationLevelInfo::OptimizationLevelInfo()
diff --git a/js/src/jit/IonOptimizationLevels.h b/js/src/jit/IonOptimizationLevels.h
index 37ed713dc8..37a597e080 100644
--- a/js/src/jit/IonOptimizationLevels.h
+++ b/js/src/jit/IonOptimizationLevels.h
@@ -128,14 +128,14 @@ class OptimizationInfo
uint32_t compilerWarmUpThreshold_;
// Default compiler warmup threshold, unless it is overridden.
- static const uint32_t CompilerWarmupThreshold = 1000;
+ static const uint32_t CompilerWarmupThreshold = 700;
// How many invocations or loop iterations are needed before small functions
// are compiled.
uint32_t compilerSmallFunctionWarmUpThreshold_;
// Default small function compiler warmup threshold, unless it is overridden.
- static const uint32_t CompilerSmallFunctionWarmupThreshold = 100;
+ static const uint32_t CompilerSmallFunctionWarmupThreshold = 40;
// How many invocations or loop iterations are needed before calls
// are inlined, as a fraction of compilerWarmUpThreshold.
diff --git a/js/src/jit/JitOptions.cpp b/js/src/jit/JitOptions.cpp
index 78f24bb803..2e799740b7 100644
--- a/js/src/jit/JitOptions.cpp
+++ b/js/src/jit/JitOptions.cpp
@@ -167,8 +167,9 @@ DefaultJitOptions::DefaultJitOptions()
// invalidating the script.
SET_DEFAULT(osrPcMismatchesBeforeRecompile, 6000);
- // The bytecode length limit for small function.
- SET_DEFAULT(smallFunctionMaxBytecodeLength_, 130);
+ // The bytecode length limit for small function. Keep this modest to avoid
+ // startup regressions from classifying too many wrapper functions as small.
+ SET_DEFAULT(smallFunctionMaxBytecodeLength_, 256);
// An artificial testing limit for the maximum supported offset of
// pc-relative jump and call instructions.
@@ -281,6 +282,7 @@ void
DefaultJitOptions::resetCompilerWarmUpThreshold()
{
forcedDefaultIonWarmUpThreshold.reset();
+ forcedDefaultIonSmallFunctionWarmUpThreshold.reset();
// Undo eager compilation
if (eagerCompilation) {
diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp
index bf1ea72d2a..7b8863da1e 100644
--- a/js/src/jit/RangeAnalysis.cpp
+++ b/js/src/jit/RangeAnalysis.cpp
@@ -174,12 +174,20 @@ RangeAnalysis::addBetaNodes()
if (!compare->isNumericComparison())
continue;
- // TODO: support unsigned comparisons
- if (compare->compareType() == MCompare::Compare_UInt32)
- continue;
-
MDefinition* left = compare->getOperand(0);
MDefinition* right = compare->getOperand(1);
+
+ if (compare->compareType() == MCompare::Compare_UInt32) {
+ Range* leftRange = left->range();
+ Range* rightRange = right->range();
+ if (!leftRange || !rightRange ||
+ !leftRange->isFiniteNonNegative() ||
+ !rightRange->isFiniteNonNegative())
+ {
+ continue;
+ }
+ }
+
double bound;
double conservativeLower = NegativeInfinity();
double conservativeUpper = PositiveInfinity();
diff --git a/js/src/jit/x86/Lowering-x86.cpp b/js/src/jit/x86/Lowering-x86.cpp
index 8cfc59a508..20f57143b1 100644
--- a/js/src/jit/x86/Lowering-x86.cpp
+++ b/js/src/jit/x86/Lowering-x86.cpp
@@ -631,8 +631,7 @@ LIRGeneratorX86::visitInt64ToFloatingPoint(MInt64ToFloatingPoint* ins)
MOZ_ASSERT(opd->type() == MIRType::Int64);
MOZ_ASSERT(IsFloatingPointType(ins->type()));
- LDefinition maybeTemp =
- (ins->isUnsigned() && AssemblerX86Shared::HasSSE3()) ? temp() : LDefinition::BogusTemp();
+ LDefinition maybeTemp = LDefinition::BogusTemp();
define(new(alloc()) LInt64ToFloatingPoint(useInt64Register(opd), maybeTemp), ins);
}
diff --git a/js/src/jit/x86/MacroAssembler-x86.cpp b/js/src/jit/x86/MacroAssembler-x86.cpp
index 22ce86984d..2e8affeae3 100644
--- a/js/src/jit/x86/MacroAssembler-x86.cpp
+++ b/js/src/jit/x86/MacroAssembler-x86.cpp
@@ -25,36 +25,14 @@ static const double TO_DOUBLE_HIGH_SCALE = 0x100000000;
bool
MacroAssemblerX86::convertUInt64ToDoubleNeedsTemp()
{
- return HasSSE3();
+ return false;
}
void
MacroAssemblerX86::convertUInt64ToDouble(Register64 src, FloatRegister dest, Register temp)
{
- // SUBPD needs SSE2, HADDPD needs SSE3.
- if (!HasSSE3()) {
- MOZ_ASSERT(temp == Register::Invalid());
-
- // Zero the dest register to break dependencies, see convertInt32ToDouble.
- zeroDouble(dest);
-
- asMasm().Push(src.high);
- asMasm().Push(src.low);
- fild(Operand(esp, 0));
-
- Label notNegative;
- asMasm().branch32(Assembler::NotSigned, src.high, Imm32(0), ¬Negative);
- double add_constant = 18446744073709551616.0; // 2^64
- store64(Imm64(mozilla::BitwiseCast(add_constant)), Address(esp, 0));
- fld(Operand(esp, 0));
- faddp();
- bind(¬Negative);
-
- fstp(Operand(esp, 0));
- vmovsd(Address(esp, 0), dest);
- asMasm().freeStack(2*sizeof(intptr_t));
- return;
- }
+ (void) temp;
+ MOZ_ASSERT(HasSSE2());
// Following operation uses entire 128-bit of dest XMM register.
// Currently higher 64-bit is free when we have access to lower 64-bit.
@@ -113,7 +91,8 @@ MacroAssemblerX86::convertUInt64ToDouble(Register64 src, FloatRegister dest, Reg
// LO(dest) = double(0x HHHHHHHH 00000000) + double(0x 00000000 LLLLLLLL)
// = double(0x HHHHHHHH LLLLLLLL)
// = double(src)
- vhaddpd(dest128, dest128);
+ vmovhlps(dest128, dest128, ScratchSimd128Reg);
+ vaddsd(ScratchSimd128Reg, dest128, dest128);
}
void
diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp
index 593cf4d708..a5829f9b9e 100644
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -1772,15 +1772,18 @@ StringMatch(const TextChar* text, uint32_t textLen, const PatChar* pat, uint32_t
* speed of memcmp. For small patterns, a simple loop is faster. We also can't
* use memcmp if one of the strings is TwoByte and the other is Latin-1.
*
- * FIXME: Linux memcmp performance is sad and the manual loop is faster.
+ * On Linux, keep the manual path for moderate patterns and only enable
+ * memcmp for very large patterns where it tends to amortize call overhead.
*/
- return
-#if !defined(__linux__)
- (patLen > 128 && IsSame::value)
- ? Matcher, TextChar, PatChar>(text, textLen, pat, patLen)
- :
+#if defined(__linux__)
+ const bool useMemCmp = patLen > 512 && IsSame::value;
+#else
+ const bool useMemCmp = patLen > 128 && IsSame::value;
#endif
- Matcher, TextChar, PatChar>(text, textLen, pat, patLen);
+
+ return useMemCmp
+ ? Matcher, TextChar, PatChar>(text, textLen, pat, patLen)
+ : Matcher, TextChar, PatChar>(text, textLen, pat, patLen);
}
static int32_t
diff --git a/js/src/jsutil.h b/js/src/jsutil.h
index daf056ad2f..00c2806c1d 100644
--- a/js/src/jsutil.h
+++ b/js/src/jsutil.h
@@ -19,6 +19,20 @@
#include
+#if (defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64))
+# if defined(_MSC_VER)
+# if defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)
+# define JS_HAVE_SSE2_INTRINSICS 1
+# endif
+# elif defined(__SSE2__)
+# define JS_HAVE_SSE2_INTRINSICS 1
+# endif
+#endif
+
+#if defined(JS_HAVE_SSE2_INTRINSICS)
+# include
+#endif
+
#include "js/Utility.h"
#include "js/Value.h"
@@ -41,9 +55,117 @@ js_memcpy(void* dst_, const void* src_, size_t len)
MOZ_ASSERT_IF(dst >= src, (size_t) (dst - src) >= len);
MOZ_ASSERT_IF(src >= dst, (size_t) (src - dst) >= len);
+#if defined(JS_HAVE_SSE2_INTRINSICS)
+ if (len >= 64) {
+ uint8_t* d = (uint8_t*)dst;
+ const uint8_t* s = (const uint8_t*)src;
+
+ while (len >= 64) {
+ __m128i v0 = _mm_loadu_si128((const __m128i*)(s + 0));
+ __m128i v1 = _mm_loadu_si128((const __m128i*)(s + 16));
+ __m128i v2 = _mm_loadu_si128((const __m128i*)(s + 32));
+ __m128i v3 = _mm_loadu_si128((const __m128i*)(s + 48));
+ _mm_storeu_si128((__m128i*)(d + 0), v0);
+ _mm_storeu_si128((__m128i*)(d + 16), v1);
+ _mm_storeu_si128((__m128i*)(d + 32), v2);
+ _mm_storeu_si128((__m128i*)(d + 48), v3);
+ d += 64;
+ s += 64;
+ len -= 64;
+ }
+
+ while (len >= 16) {
+ __m128i v = _mm_loadu_si128((const __m128i*)s);
+ _mm_storeu_si128((__m128i*)d, v);
+ d += 16;
+ s += 16;
+ len -= 16;
+ }
+
+ if (len) {
+ memcpy(d, s, len);
+ }
+ return dst;
+ }
+#endif
+
return memcpy(dst, src, len);
}
+static MOZ_ALWAYS_INLINE void*
+js_memmove(void* dst_, const void* src_, size_t len)
+{
+ char* dst = (char*) dst_;
+ const char* src = (const char*) src_;
+
+ if (dst == src || !len) {
+ return dst;
+ }
+
+#if defined(JS_HAVE_SSE2_INTRINSICS)
+ if (len >= 64) {
+ uint8_t* d = (uint8_t*)dst;
+ const uint8_t* s = (const uint8_t*)src;
+
+ if (d < s || d >= s + len) {
+ return js_memcpy(dst, src, len);
+ }
+
+ d += len;
+ s += len;
+ while (len >= 16) {
+ d -= 16;
+ s -= 16;
+ __m128i v = _mm_loadu_si128((const __m128i*)s);
+ _mm_storeu_si128((__m128i*)d, v);
+ len -= 16;
+ }
+
+ while (len--) {
+ *--d = *--s;
+ }
+ return dst;
+ }
+#endif
+
+ return memmove(dst, src, len);
+}
+
+static MOZ_ALWAYS_INLINE void*
+js_memset(void* dst_, uint8_t value, size_t len)
+{
+ char* dst = (char*) dst_;
+
+#if defined(JS_HAVE_SSE2_INTRINSICS)
+ if (len >= 64) {
+ uint8_t* d = (uint8_t*)dst;
+ __m128i v = _mm_set1_epi8((char)value);
+
+ while (len >= 64) {
+ _mm_storeu_si128((__m128i*)(d + 0), v);
+ _mm_storeu_si128((__m128i*)(d + 16), v);
+ _mm_storeu_si128((__m128i*)(d + 32), v);
+ _mm_storeu_si128((__m128i*)(d + 48), v);
+ d += 64;
+ len -= 64;
+ }
+
+ while (len >= 16) {
+ _mm_storeu_si128((__m128i*)d, v);
+ d += 16;
+ len -= 16;
+ }
+
+ while (len--) {
+ *d++ = (char)value;
+ }
+ return dst;
+ }
+#endif
+
+ return memset(dst, value, len);
+}
+
namespace js {
template
diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp
index 7def8c7e1d..e43edab690 100644
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -796,7 +796,7 @@ ArrayBufferObject::prepareForAsmJS(JSContext* cx, Handle buf
}
void* data = wasmBuf->dataPointer();
- memcpy(data, buffer->dataPointer(), length);
+ js_memcpy(data, buffer->dataPointer(), length);
// Swap the new elements into the ArrayBufferObject. Mark the
// ArrayBufferObject so we don't do this again.
@@ -818,7 +818,7 @@ ArrayBufferObject::prepareForAsmJS(JSContext* cx, Handle buf
BufferContents contents = AllocateArrayBufferContents(cx, buffer->byteLength());
if (!contents)
return false;
- memcpy(contents.data(), buffer->dataPointer(), buffer->byteLength());
+ js_memcpy(contents.data(), buffer->dataPointer(), buffer->byteLength());
buffer->changeContents(cx, contents, OwnsData);
}
@@ -995,7 +995,7 @@ ArrayBufferObject::wasmMovingGrowToSize(uint32_t newSize,
BufferContents contents = BufferContents::create(newRawBuf->dataPointer());
newBuf->initialize(newSize, contents, OwnsData);
- memcpy(newBuf->dataPointer(), oldBuf->dataPointer(), oldBuf->byteLength());
+ js_memcpy(newBuf->dataPointer(), oldBuf->dataPointer(), oldBuf->byteLength());
ArrayBufferObject::detach(cx, oldBuf, BufferContents::createPlain(nullptr));
return true;
}
@@ -1097,7 +1097,7 @@ ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents content
if (!contents) {
void* data = obj->inlineDataPointer();
- memset(data, 0, nbytes);
+ js_memset(data, 0, nbytes);
obj->initialize(nbytes, BufferContents::createPlain(data), DoesntOwnData);
} else {
obj->initialize(nbytes, contents, ownsState);
@@ -1182,7 +1182,7 @@ ArrayBufferObject::externalizeContents(JSContext* cx, Handle
BufferContents newContents = AllocateArrayBufferContents(cx, buffer->byteLength());
if (!newContents)
return BufferContents::createPlain(nullptr);
- memcpy(newContents.data(), contents.data(), buffer->byteLength());
+ js_memcpy(newContents.data(), contents.data(), buffer->byteLength());
buffer->changeContents(cx, newContents, DoesntOwnData);
return newContents;
@@ -1217,7 +1217,7 @@ ArrayBufferObject::stealContents(JSContext* cx, Handle buffe
return BufferContents::createPlain(nullptr);
if (buffer->byteLength() > 0)
- memcpy(contentsCopy.data(), oldContents.data(), buffer->byteLength());
+ js_memcpy(contentsCopy.data(), oldContents.data(), buffer->byteLength());
ArrayBufferObject::detach(cx, buffer, oldContents);
return contentsCopy;
}
@@ -1270,7 +1270,7 @@ ArrayBufferObject::copyData(Handle toBuffer, uint32_t toInde
MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);
- memcpy(toBuffer->dataPointer() + toIndex, fromBuffer->dataPointer() + fromIndex, count);
+ js_memcpy(toBuffer->dataPointer() + toIndex, fromBuffer->dataPointer() + fromIndex, count);
}
/* static */ void
diff --git a/js/src/vm/TypedArrayCommon.h b/js/src/vm/TypedArrayCommon.h
index 59ffd78b24..fab7210417 100644
--- a/js/src/vm/TypedArrayCommon.h
+++ b/js/src/vm/TypedArrayCommon.h
@@ -12,6 +12,14 @@
#include "mozilla/FloatingPoint.h"
#include
+#include
+#include
+
+#if (defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)) && \
+ (defined(_M_X64) || defined(__SSE2__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
+# include
+# define JS_TYPEDARRAY_HAS_SSE2 1
+#endif
#include "jsarray.h"
#include "jscntxt.h"
@@ -199,6 +207,186 @@ ConvertNumber(From src)
return To(src);
}
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+static inline void
+SSE2ConvertFloatToUint8Clamped(uint8_clamped* dest, const float* src, uint32_t count)
+{
+ const __m128 fzero = _mm_set1_ps(0.0f);
+ const __m128 fmax = _mm_set1_ps(255.0f);
+ const __m128i izero = _mm_setzero_si128();
+ const __m128i i255 = _mm_set1_epi16(255);
+
+ uint32_t i = 0;
+ for (; i + 4 <= count; i += 4) {
+ __m128 values = _mm_loadu_ps(src + i);
+
+ // Keep exact scalar behavior for NaN lanes.
+ if (_mm_movemask_ps(_mm_cmpunord_ps(values, values))) {
+ for (uint32_t j = 0; j < 4; ++j)
+ dest[i + j] = uint8_clamped(src[i + j]);
+ continue;
+ }
+
+ values = _mm_min_ps(_mm_max_ps(values, fzero), fmax);
+ __m128i ints = _mm_cvtps_epi32(values);
+ __m128i packed16 = _mm_packs_epi32(ints, izero);
+ packed16 = _mm_min_epi16(_mm_max_epi16(packed16, izero), i255);
+ __m128i packed8 = _mm_packus_epi16(packed16, izero);
+
+ uint32_t out = static_cast(_mm_cvtsi128_si32(packed8));
+ ::memcpy(reinterpret_cast(dest + i), &out, sizeof(out));
+ }
+
+ for (; i < count; ++i)
+ dest[i] = uint8_clamped(src[i]);
+}
+
+static inline void
+SSE2ConvertDoubleToUint8Clamped(uint8_clamped* dest, const double* src, uint32_t count)
+{
+ const __m128d dzero = _mm_set1_pd(0.0);
+ const __m128d dmax = _mm_set1_pd(255.0);
+ const __m128i izero = _mm_setzero_si128();
+ const __m128i i255 = _mm_set1_epi16(255);
+
+ uint32_t i = 0;
+ for (; i + 2 <= count; i += 2) {
+ __m128d values = _mm_loadu_pd(src + i);
+
+ // Keep exact scalar behavior for NaN lanes.
+ if (_mm_movemask_pd(_mm_cmpunord_pd(values, values))) {
+ for (uint32_t j = 0; j < 2; ++j)
+ dest[i + j] = uint8_clamped(src[i + j]);
+ continue;
+ }
+
+ values = _mm_min_pd(_mm_max_pd(values, dzero), dmax);
+ __m128i ints = _mm_cvtpd_epi32(values);
+ __m128i packed16 = _mm_packs_epi32(ints, izero);
+ packed16 = _mm_min_epi16(_mm_max_epi16(packed16, izero), i255);
+ __m128i packed8 = _mm_packus_epi16(packed16, izero);
+
+ uint16_t out = static_cast(static_cast(_mm_cvtsi128_si32(packed8)) & 0xFFFFu);
+ ::memcpy(reinterpret_cast(dest + i), &out, sizeof(out));
+ }
+
+ for (; i < count; ++i)
+ dest[i] = uint8_clamped(src[i]);
+}
+
+static inline void
+SSE2ConvertInt8ToUint8Clamped(uint8_clamped* dest, const int8_t* src, uint32_t count)
+{
+ const __m128i izero = _mm_setzero_si128();
+ uint8_t* out = reinterpret_cast(dest);
+
+ uint32_t i = 0;
+ for (; i + 16 <= count; i += 16) {
+ __m128i values = _mm_loadu_si128(reinterpret_cast(src + i));
+ __m128i negatives = _mm_cmpgt_epi8(izero, values);
+ __m128i clamped = _mm_andnot_si128(negatives, values);
+ _mm_storeu_si128(reinterpret_cast<__m128i*>(out + i), clamped);
+ }
+
+ for (; i < count; ++i)
+ dest[i] = uint8_clamped(src[i]);
+}
+
+static inline void
+SSE2ConvertInt16ToUint8Clamped(uint8_clamped* dest, const int16_t* src, uint32_t count)
+{
+ uint8_t* out = reinterpret_cast(dest);
+
+ uint32_t i = 0;
+ for (; i + 16 <= count; i += 16) {
+ __m128i a = _mm_loadu_si128(reinterpret_cast(src + i));
+ __m128i b = _mm_loadu_si128(reinterpret_cast(src + i + 8));
+ __m128i packed = _mm_packus_epi16(a, b);
+ _mm_storeu_si128(reinterpret_cast<__m128i*>(out + i), packed);
+ }
+
+ for (; i < count; ++i)
+ dest[i] = uint8_clamped(src[i]);
+}
+
+static inline void
+SSE2ConvertUint16ToUint8Clamped(uint8_clamped* dest, const uint16_t* src, uint32_t count)
+{
+ const __m128i i255 = _mm_set1_epi16(255);
+ uint8_t* out = reinterpret_cast(dest);
+
+ uint32_t i = 0;
+ for (; i + 16 <= count; i += 16) {
+ __m128i a = _mm_loadu_si128(reinterpret_cast(src + i));
+ __m128i b = _mm_loadu_si128(reinterpret_cast(src + i + 8));
+
+ __m128i aExcess = _mm_subs_epu16(a, i255);
+ __m128i bExcess = _mm_subs_epu16(b, i255);
+ __m128i aClamped = _mm_sub_epi16(a, aExcess);
+ __m128i bClamped = _mm_sub_epi16(b, bExcess);
+
+ __m128i packed = _mm_packus_epi16(aClamped, bClamped);
+ _mm_storeu_si128(reinterpret_cast<__m128i*>(out + i), packed);
+ }
+
+ for (; i < count; ++i)
+ dest[i] = uint8_clamped(src[i]);
+}
+
+static inline void
+SSE2ConvertInt32ToUint8Clamped(uint8_clamped* dest, const int32_t* src, uint32_t count)
+{
+ const __m128i izero = _mm_setzero_si128();
+ const __m128i i255 = _mm_set1_epi16(255);
+ uint8_t* out = reinterpret_cast(dest);
+
+ uint32_t i = 0;
+ for (; i + 8 <= count; i += 8) {
+ __m128i a = _mm_loadu_si128(reinterpret_cast(src + i));
+ __m128i b = _mm_loadu_si128(reinterpret_cast(src + i + 4));
+ __m128i words = _mm_packs_epi32(a, b);
+ words = _mm_min_epi16(_mm_max_epi16(words, izero), i255);
+ __m128i bytes = _mm_packus_epi16(words, izero);
+ _mm_storel_epi64(reinterpret_cast<__m128i*>(out + i), bytes);
+ }
+
+ for (; i < count; ++i)
+ dest[i] = uint8_clamped(src[i]);
+}
+
+static inline void
+SSE2ConvertUint32ToUint8Clamped(uint8_clamped* dest, const uint32_t* src, uint32_t count)
+{
+ const __m128i izero = _mm_setzero_si128();
+ const __m128i maskHigh = _mm_set1_epi32(0xFFFFFF00u);
+ const __m128i maskLow = _mm_set1_epi32(0x000000FFu);
+ const __m128i i255d = _mm_set1_epi32(255);
+ uint8_t* out = reinterpret_cast(dest);
+
+ uint32_t i = 0;
+ for (; i + 8 <= count; i += 8) {
+ __m128i a = _mm_loadu_si128(reinterpret_cast(src + i));
+ __m128i b = _mm_loadu_si128(reinterpret_cast(src + i + 4));
+
+ __m128i aFits = _mm_cmpeq_epi32(_mm_and_si128(a, maskHigh), izero);
+ __m128i bFits = _mm_cmpeq_epi32(_mm_and_si128(b, maskHigh), izero);
+
+ __m128i aLow = _mm_and_si128(a, maskLow);
+ __m128i bLow = _mm_and_si128(b, maskLow);
+
+ __m128i aClamped = _mm_or_si128(_mm_and_si128(aFits, aLow), _mm_andnot_si128(aFits, i255d));
+ __m128i bClamped = _mm_or_si128(_mm_and_si128(bFits, bLow), _mm_andnot_si128(bFits, i255d));
+
+ __m128i words = _mm_packs_epi32(aClamped, bClamped);
+ __m128i bytes = _mm_packus_epi16(words, izero);
+ _mm_storel_epi64(reinterpret_cast<__m128i*>(out + i), bytes);
+ }
+
+ for (; i < count; ++i)
+ dest[i] = uint8_clamped(src[i]);
+}
+#endif
+
template struct TypeIDOfType;
template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Int8; };
template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Uint8; };
@@ -275,24 +463,25 @@ class UnsharedOps
template
static void podCopy(SharedMem dest, SharedMem src, size_t nelem) {
- // std::copy_n better matches the argument values/types of this
- // function, but as noted below it allows the input/output ranges to
- // overlap. std::copy does not, so use it so the compiler has extra
- // ability to optimize.
- const auto* first = src.unwrapUnshared();
- const auto* last = first + nelem;
- auto* result = dest.unwrapUnshared();
- std::copy(first, last, result);
+ static_assert(std::is_trivially_copyable::value,
+ "podCopy requires trivially copyable element type");
+ if (nelem == 0)
+ return;
+
+ // Keep this on memcpy so platform CRT implementations can use their
+ // best vectorized copy routines (SSE2/AVX/etc.) where available.
+ ::memcpy(dest.unwrapUnshared(), src.unwrapUnshared(), nelem * sizeof(T));
}
template
static void podMove(SharedMem dest, SharedMem src, size_t n) {
- // std::copy_n copies from |src| to |dest| starting from |src|, so
- // input/output ranges *may* permissibly overlap, as this function
- // allows.
- const auto* start = src.unwrapUnshared();
- auto* result = dest.unwrapUnshared();
- std::copy_n(start, n, result);
+ static_assert(std::is_trivially_copyable::value,
+ "podMove requires trivially copyable element type");
+ if (n == 0)
+ return;
+
+ // memmove handles overlap and still maps to optimized runtime copies.
+ ::memmove(dest.unwrapUnshared(), src.unwrapUnshared(), n * sizeof(T));
}
static SharedMem extract(TypedArrayObject* obj) {
@@ -350,6 +539,14 @@ class ElementSpecific
switch (source->as().type()) {
case Scalar::Int8: {
SharedMem src = data.cast();
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+ if (std::is_same::value && std::is_same::value) {
+ SSE2ConvertInt8ToUint8Clamped(reinterpret_cast(dest.unwrapUnshared()),
+ data.cast().unwrapUnshared(),
+ count);
+ break;
+ }
+#endif
for (uint32_t i = 0; i < count; ++i)
Ops::store(dest++, ConvertNumber(Ops::load(src++)));
break;
@@ -357,30 +554,66 @@ class ElementSpecific
case Scalar::Uint8:
case Scalar::Uint8Clamped: {
SharedMem src = data.cast();
+ if (std::is_same::value || std::is_same::value) {
+ Ops::podCopy(dest, data.cast(), count);
+ break;
+ }
for (uint32_t i = 0; i < count; ++i)
Ops::store(dest++, ConvertNumber(Ops::load(src++)));
break;
}
case Scalar::Int16: {
SharedMem src = data.cast();
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+ if (std::is_same::value && std::is_same::value) {
+ SSE2ConvertInt16ToUint8Clamped(reinterpret_cast(dest.unwrapUnshared()),
+ data.cast().unwrapUnshared(),
+ count);
+ break;
+ }
+#endif
for (uint32_t i = 0; i < count; ++i)
Ops::store(dest++, ConvertNumber(Ops::load(src++)));
break;
}
case Scalar::Uint16: {
SharedMem src = data.cast();
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+ if (std::is_same::value && std::is_same::value) {
+ SSE2ConvertUint16ToUint8Clamped(reinterpret_cast(dest.unwrapUnshared()),
+ data.cast().unwrapUnshared(),
+ count);
+ break;
+ }
+#endif
for (uint32_t i = 0; i < count; ++i)
Ops::store(dest++, ConvertNumber(Ops::load(src++)));
break;
}
case Scalar::Int32: {
SharedMem src = data.cast();
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+ if (std::is_same::value && std::is_same::value) {
+ SSE2ConvertInt32ToUint8Clamped(reinterpret_cast(dest.unwrapUnshared()),
+ data.cast().unwrapUnshared(),
+ count);
+ break;
+ }
+#endif
for (uint32_t i = 0; i < count; ++i)
Ops::store(dest++, ConvertNumber(Ops::load(src++)));
break;
}
case Scalar::Uint32: {
SharedMem src = data.cast();
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+ if (std::is_same::value && std::is_same::value) {
+ SSE2ConvertUint32ToUint8Clamped(reinterpret_cast(dest.unwrapUnshared()),
+ data.cast().unwrapUnshared(),
+ count);
+ break;
+ }
+#endif
for (uint32_t i = 0; i < count; ++i)
Ops::store(dest++, ConvertNumber(Ops::load(src++)));
break;
@@ -399,12 +632,28 @@ class ElementSpecific
}
case Scalar::Float32: {
SharedMem src = data.cast();
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+ if (std::is_same::value && std::is_same::value) {
+ SSE2ConvertFloatToUint8Clamped(reinterpret_cast(dest.unwrapUnshared()),
+ data.cast().unwrapUnshared(),
+ count);
+ break;
+ }
+#endif
for (uint32_t i = 0; i < count; ++i)
Ops::store(dest++, ConvertNumber(Ops::load(src++)));
break;
}
case Scalar::Float64: {
SharedMem src = data.cast();
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+ if (std::is_same::value && std::is_same::value) {
+ SSE2ConvertDoubleToUint8Clamped(reinterpret_cast(dest.unwrapUnshared()),
+ data.cast().unwrapUnshared(),
+ count);
+ break;
+ }
+#endif
for (uint32_t i = 0; i < count; ++i)
Ops::store(dest++, ConvertNumber(Ops::load(src++)));
break;
@@ -563,6 +812,15 @@ class ElementSpecific
return true;
}
+ if (std::is_same::value &&
+ (source->type() == Scalar::Uint8 || source->type() == Scalar::Uint8Clamped))
+ {
+ SharedMem src =
+ source->template as().viewDataEither().template cast();
+ Ops::podMove(dest, src, len);
+ return true;
+ }
+
// Copy |source| in case it overlaps the target elements being set.
size_t sourceByteLen = len * source->bytesPerElement();
void* data = target->zone()->template pod_malloc(sourceByteLen);
@@ -575,6 +833,12 @@ class ElementSpecific
switch (source->type()) {
case Scalar::Int8: {
int8_t* src = static_cast(data);
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+ if (std::is_same::value && std::is_same::value) {
+ SSE2ConvertInt8ToUint8Clamped(reinterpret_cast(dest.unwrapUnshared()), src, len);
+ break;
+ }
+#endif
for (uint32_t i = 0; i < len; ++i)
Ops::store(dest++, ConvertNumber(*src++));
break;
@@ -582,30 +846,58 @@ class ElementSpecific
case Scalar::Uint8:
case Scalar::Uint8Clamped: {
uint8_t* src = static_cast(data);
+ if (std::is_same::value || std::is_same::value) {
+ Ops::podCopy(dest, SharedMem::unshared(src).template cast(), len);
+ break;
+ }
for (uint32_t i = 0; i < len; ++i)
Ops::store(dest++, ConvertNumber(*src++));
break;
}
case Scalar::Int16: {
int16_t* src = static_cast(data);
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+ if (std::is_same::value && std::is_same::value) {
+ SSE2ConvertInt16ToUint8Clamped(reinterpret_cast(dest.unwrapUnshared()), src, len);
+ break;
+ }
+#endif
for (uint32_t i = 0; i < len; ++i)
Ops::store(dest++, ConvertNumber(*src++));
break;
}
case Scalar::Uint16: {
uint16_t* src = static_cast(data);
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+ if (std::is_same::value && std::is_same::value) {
+ SSE2ConvertUint16ToUint8Clamped(reinterpret_cast(dest.unwrapUnshared()), src, len);
+ break;
+ }
+#endif
for (uint32_t i = 0; i < len; ++i)
Ops::store(dest++, ConvertNumber(*src++));
break;
}
case Scalar::Int32: {
int32_t* src = static_cast(data);
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+ if (std::is_same::value && std::is_same::value) {
+ SSE2ConvertInt32ToUint8Clamped(reinterpret_cast(dest.unwrapUnshared()), src, len);
+ break;
+ }
+#endif
for (uint32_t i = 0; i < len; ++i)
Ops::store(dest++, ConvertNumber(*src++));
break;
}
case Scalar::Uint32: {
uint32_t* src = static_cast(data);
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+ if (std::is_same::value && std::is_same::value) {
+ SSE2ConvertUint32ToUint8Clamped(reinterpret_cast(dest.unwrapUnshared()), src, len);
+ break;
+ }
+#endif
for (uint32_t i = 0; i < len; ++i)
Ops::store(dest++, ConvertNumber(*src++));
break;
@@ -624,12 +916,24 @@ class ElementSpecific
}
case Scalar::Float32: {
float* src = static_cast(data);
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+ if (std::is_same::value && std::is_same::value) {
+ SSE2ConvertFloatToUint8Clamped(reinterpret_cast(dest.unwrapUnshared()), src, len);
+ break;
+ }
+#endif
for (uint32_t i = 0; i < len; ++i)
Ops::store(dest++, ConvertNumber(*src++));
break;
}
case Scalar::Float64: {
double* src = static_cast(data);
+#ifdef JS_TYPEDARRAY_HAS_SSE2
+ if (std::is_same::value && std::is_same::value) {
+ SSE2ConvertDoubleToUint8Clamped(reinterpret_cast(dest.unwrapUnshared()), src, len);
+ break;
+ }
+#endif
for (uint32_t i = 0; i < len; ++i)
Ops::store(dest++, ConvertNumber(*src++));
break;
diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp
index 232fde02d3..3e9e82c844 100644
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -7,6 +7,7 @@
#include "mozilla/Alignment.h"
#include "mozilla/Casting.h"
+#include "mozilla/EndianUtils.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/PodOperations.h"
@@ -57,6 +58,7 @@ using JS::CanonicalizeNaN;
using JS::ToInt32;
using JS::ToUint32;
+
/*
* TypedArrayObject
*
@@ -143,7 +145,7 @@ TypedArrayObject::ensureHasBuffer(JSContext* cx, Handle tarra
return false;
// tarray is not shared, because if it were it would have a buffer.
- memcpy(buffer->dataPointer(), tarray->viewDataUnshared(), tarray->byteLength());
+ js_memcpy(buffer->dataPointer(), tarray->viewDataUnshared(), tarray->byteLength());
// If the object is in the nursery, the buffer will be freed by the next
// nursery GC. Free the data slot pointer if the object has no inline data.
@@ -538,7 +540,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
} else {
void* data = obj->fixedData(FIXED_DATA_START);
obj->initPrivate(data);
- memset(data, 0, len * sizeof(NativeType));
+ js_memset(data, 0, len * sizeof(NativeType));
#ifdef DEBUG
if (len == 0) {
uint8_t* elements = static_cast(data);
@@ -667,7 +669,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
void* data = tarray->fixedData(FIXED_DATA_START);
tarray->initPrivate(data);
- memset(data, 0, nbytes);
+ js_memset(data, 0, nbytes);
}
}
@@ -704,7 +706,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject
return nullptr;
}
- memset(buf, 0, nbytes);
+ js_memset(buf, 0, nbytes);
}
RootedObject tmp(cx, NewObjectWithGroup(cx, group, allocKind, newKind));
@@ -2108,35 +2110,6 @@ needToSwapBytes(bool littleEndian)
#endif
}
-static inline uint8_t
-swapBytes(uint8_t x)
-{
- return x;
-}
-
-static inline uint16_t
-swapBytes(uint16_t x)
-{
- return ((x & 0xff) << 8) | (x >> 8);
-}
-
-static inline uint32_t
-swapBytes(uint32_t x)
-{
- return ((x & 0xff) << 24) |
- ((x & 0xff00) << 8) |
- ((x & 0xff0000) >> 8) |
- ((x & 0xff000000) >> 24);
-}
-
-static inline uint64_t
-swapBytes(uint64_t x)
-{
- uint32_t a = x & UINT32_MAX;
- uint32_t b = x >> 32;
- return (uint64_t(swapBytes(a)) << 32) | swapBytes(b);
-}
-
template struct DataToRepType { typedef DataType result; };
template <> struct DataToRepType { typedef uint8_t result; };
template <> struct DataToRepType { typedef uint8_t result; };
@@ -2154,23 +2127,111 @@ struct DataViewIO
{
typedef typename DataToRepType::result ReadWriteType;
+ static MOZ_ALWAYS_INLINE ReadWriteType fromBytes(const uint8_t* unalignedBuffer, bool wantSwap)
+ {
+ if (sizeof(ReadWriteType) == 1)
+ return ReadWriteType(*unalignedBuffer);
+
+ if (sizeof(ReadWriteType) == 2) {
+#if MOZ_LITTLE_ENDIAN
+ return ReadWriteType(wantSwap ? mozilla::BigEndian::readUint16(unalignedBuffer)
+ : mozilla::LittleEndian::readUint16(unalignedBuffer));
+#else
+ return ReadWriteType(wantSwap ? mozilla::LittleEndian::readUint16(unalignedBuffer)
+ : mozilla::BigEndian::readUint16(unalignedBuffer));
+#endif
+ }
+
+ if (sizeof(ReadWriteType) == 4) {
+#if MOZ_LITTLE_ENDIAN
+ return ReadWriteType(wantSwap ? mozilla::BigEndian::readUint32(unalignedBuffer)
+ : mozilla::LittleEndian::readUint32(unalignedBuffer));
+#else
+ return ReadWriteType(wantSwap ? mozilla::LittleEndian::readUint32(unalignedBuffer)
+ : mozilla::BigEndian::readUint32(unalignedBuffer));
+#endif
+ }
+
+ if (sizeof(ReadWriteType) == 8) {
+ #if MOZ_LITTLE_ENDIAN
+ return ReadWriteType(wantSwap ? mozilla::BigEndian::readUint64(unalignedBuffer)
+ : mozilla::LittleEndian::readUint64(unalignedBuffer));
+ #else
+ return ReadWriteType(wantSwap ? mozilla::LittleEndian::readUint64(unalignedBuffer)
+ : mozilla::BigEndian::readUint64(unalignedBuffer));
+ #endif
+ }
+
+ MOZ_CRASH("unsupported DataView element size");
+ }
+
+ static MOZ_ALWAYS_INLINE void toBytes(uint8_t* unalignedBuffer, ReadWriteType value,
+ bool wantSwap)
+ {
+ if (sizeof(ReadWriteType) == 1) {
+ *unalignedBuffer = uint8_t(value);
+ return;
+ }
+
+ if (sizeof(ReadWriteType) == 2) {
+#if MOZ_LITTLE_ENDIAN
+ if (wantSwap)
+ mozilla::BigEndian::writeUint16(unalignedBuffer, uint16_t(value));
+ else
+ mozilla::LittleEndian::writeUint16(unalignedBuffer, uint16_t(value));
+#else
+ if (wantSwap)
+ mozilla::LittleEndian::writeUint16(unalignedBuffer, uint16_t(value));
+ else
+ mozilla::BigEndian::writeUint16(unalignedBuffer, uint16_t(value));
+#endif
+ return;
+ }
+
+ if (sizeof(ReadWriteType) == 4) {
+#if MOZ_LITTLE_ENDIAN
+ if (wantSwap)
+ mozilla::BigEndian::writeUint32(unalignedBuffer, uint32_t(value));
+ else
+ mozilla::LittleEndian::writeUint32(unalignedBuffer, uint32_t(value));
+#else
+ if (wantSwap)
+ mozilla::LittleEndian::writeUint32(unalignedBuffer, uint32_t(value));
+ else
+ mozilla::BigEndian::writeUint32(unalignedBuffer, uint32_t(value));
+#endif
+ return;
+ }
+
+ if (sizeof(ReadWriteType) == 8) {
+#if MOZ_LITTLE_ENDIAN
+ if (wantSwap)
+ mozilla::BigEndian::writeUint64(unalignedBuffer, uint64_t(value));
+ else
+ mozilla::LittleEndian::writeUint64(unalignedBuffer, uint64_t(value));
+#else
+ if (wantSwap)
+ mozilla::LittleEndian::writeUint64(unalignedBuffer, uint64_t(value));
+ else
+ mozilla::BigEndian::writeUint64(unalignedBuffer, uint64_t(value));
+#endif
+ return;
+ }
+
+ MOZ_CRASH("unsupported DataView element size");
+ }
+
static void fromBuffer(DataType* dest, const uint8_t* unalignedBuffer, bool wantSwap)
{
MOZ_ASSERT((reinterpret_cast(dest) & (Min(MOZ_ALIGNOF(void*), sizeof(DataType)) - 1)) == 0);
- memcpy((void*) dest, unalignedBuffer, sizeof(ReadWriteType));
- if (wantSwap) {
- ReadWriteType* rwDest = reinterpret_cast(dest);
- *rwDest = swapBytes(*rwDest);
- }
+ *reinterpret_cast(dest) = fromBytes(unalignedBuffer, wantSwap);
}
static void toBuffer(uint8_t* unalignedBuffer, const DataType* src, bool wantSwap)
{
MOZ_ASSERT((reinterpret_cast(src) & (Min(MOZ_ALIGNOF(void*), sizeof(DataType)) - 1)) == 0);
ReadWriteType temp = *reinterpret_cast(src);
- if (wantSwap)
- temp = swapBytes(temp);
- memcpy(unalignedBuffer, (void*) &temp, sizeof(ReadWriteType));
+ toBytes(unalignedBuffer, temp, wantSwap);
}
};
diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp
index 924d3e2c2e..f93ef18034 100644
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -3858,12 +3858,54 @@ BaseCompiler::emitSubtractF64()
void
BaseCompiler::emitMultiplyI32()
{
- // TODO / OPTIMIZE: Multiplication by constant is common (Bug 1275442, 1316803)
- RegI32 r0, r1;
- pop2xI32ForIntMulDiv(&r0, &r1);
- masm.mul32(r1.reg, r0.reg);
- freeI32(r1);
- pushI32(r0);
+ int32_t c;
+ if (popConstI32(c)) {
+ RegI32 r = popI32();
+
+ if (c == 0) {
+ masm.move32(Imm32(0), r.reg);
+ pushI32(r);
+ return;
+ }
+
+ if (c == 1) {
+ pushI32(r);
+ return;
+ }
+
+ if (c == -1) {
+ masm.neg32(r.reg);
+ pushI32(r);
+ return;
+ }
+
+ uint32_t mag = c < 0 ? uint32_t(0) - uint32_t(c) : uint32_t(c);
+ if (IsPowerOfTwo(mag)) {
+ uint32_t shift = 0;
+ while ((uint32_t(1) << shift) != mag)
+ shift++;
+
+ masm.lshift32(Imm32(shift), r.reg);
+ if (c < 0)
+ masm.neg32(r.reg);
+
+ pushI32(r);
+ return;
+ }
+
+ RegI32 rhs = needI32();
+ masm.move32(Imm32(c), rhs.reg);
+ masm.mul32(rhs.reg, r.reg);
+ freeI32(rhs);
+ pushI32(r);
+ return;
+ }
+
+ RegI32 r0, r1;
+ pop2xI32ForIntMulDiv(&r0, &r1);
+ masm.mul32(r1.reg, r0.reg);
+ freeI32(r1);
+ pushI32(r0);
}
void
@@ -3934,17 +3976,43 @@ BaseCompiler::emitQuotientI32()
void
BaseCompiler::emitQuotientU32()
{
- // TODO / OPTIMIZE: Fast case if lhs >= 0 and rhs is power of two (Bug 1316803)
- RegI32 r0, r1;
- pop2xI32ForIntMulDiv(&r0, &r1);
+ int32_t c;
+ if (popConstI32(c)) {
+ uint32_t uc = uint32_t(c);
+ RegI32 r = popI32();
+
+ if (uc != 0 && IsPowerOfTwo(uc)) {
+ uint32_t shift = 0;
+ while ((uint32_t(1) << shift) != uc)
+ shift++;
+ masm.rshift32(Imm32(shift), r.reg);
+ pushI32(r);
+ return;
+ }
+
+ RegI32 rhs = needI32();
+ masm.move32(Imm32(c), rhs.reg);
Label done;
- checkDivideByZeroI32(r1, r0, &done);
- masm.quotient32(r1.reg, r0.reg, IsUnsigned(true));
+ checkDivideByZeroI32(rhs, r, &done);
+ masm.quotient32(rhs.reg, r.reg, IsUnsigned(true));
masm.bind(&done);
- freeI32(r1);
- pushI32(r0);
+ freeI32(rhs);
+ pushI32(r);
+ return;
+ }
+
+ RegI32 r0, r1;
+ pop2xI32ForIntMulDiv(&r0, &r1);
+
+ Label done;
+ checkDivideByZeroI32(r1, r0, &done);
+ masm.quotient32(r1.reg, r0.reg, IsUnsigned(true));
+ masm.bind(&done);
+
+ freeI32(r1);
+ pushI32(r0);
}
void
@@ -3967,17 +4035,40 @@ BaseCompiler::emitRemainderI32()
void
BaseCompiler::emitRemainderU32()
{
- // TODO / OPTIMIZE: Fast case if lhs >= 0 and rhs is power of two (Bug 1316803)
- RegI32 r0, r1;
- pop2xI32ForIntMulDiv(&r0, &r1);
+ int32_t c;
+ if (popConstI32(c)) {
+ uint32_t uc = uint32_t(c);
+ RegI32 r = popI32();
+
+ if (uc != 0 && IsPowerOfTwo(uc)) {
+ masm.and32(Imm32(int32_t(uc - 1)), r.reg);
+ pushI32(r);
+ return;
+ }
+
+ RegI32 rhs = needI32();
+ masm.move32(Imm32(c), rhs.reg);
Label done;
- checkDivideByZeroI32(r1, r0, &done);
- masm.remainder32(r1.reg, r0.reg, IsUnsigned(true));
+ checkDivideByZeroI32(rhs, r, &done);
+ masm.remainder32(rhs.reg, r.reg, IsUnsigned(true));
masm.bind(&done);
- freeI32(r1);
- pushI32(r0);
+ freeI32(rhs);
+ pushI32(r);
+ return;
+ }
+
+ RegI32 r0, r1;
+ pop2xI32ForIntMulDiv(&r0, &r1);
+
+ Label done;
+ checkDivideByZeroI32(r1, r0, &done);
+ masm.remainder32(r1.reg, r0.reg, IsUnsigned(true));
+ masm.bind(&done);
+
+ freeI32(r1);
+ pushI32(r0);
}
#ifndef INT_DIV_I64_CALLOUT
@@ -4280,12 +4371,18 @@ BaseCompiler::emitShlI32()
void
BaseCompiler::emitShlI64()
{
- // TODO / OPTIMIZE: Constant rhs (Bug 1316803)
+ int32_t c;
+ if (popConstI32(c)) {
+ RegI64 r = popI64();
+ masm.lshift64(Imm32(c & 63), r.reg);
+ pushI64(r);
+ } else {
RegI64 r0, r1;
pop2xI64ForShiftOrRotate(&r0, &r1);
masm.lshift64(lowPart(r1), r0.reg);
freeI64(r1);
pushI64(r0);
+ }
}
void
@@ -4309,12 +4406,18 @@ BaseCompiler::emitShrI32()
void
BaseCompiler::emitShrI64()
{
- // TODO / OPTIMIZE: Constant rhs (Bug 1316803)
+ int32_t c;
+ if (popConstI32(c)) {
+ RegI64 r = popI64();
+ masm.rshift64Arithmetic(Imm32(c & 63), r.reg);
+ pushI64(r);
+ } else {
RegI64 r0, r1;
pop2xI64ForShiftOrRotate(&r0, &r1);
masm.rshift64Arithmetic(lowPart(r1), r0.reg);
freeI64(r1);
pushI64(r0);
+ }
}
void
@@ -4338,56 +4441,98 @@ BaseCompiler::emitShrU32()
void
BaseCompiler::emitShrU64()
{
- // TODO / OPTIMIZE: Constant rhs (Bug 1316803)
+ int32_t c;
+ if (popConstI32(c)) {
+ RegI64 r = popI64();
+ masm.rshift64(Imm32(c & 63), r.reg);
+ pushI64(r);
+ } else {
RegI64 r0, r1;
pop2xI64ForShiftOrRotate(&r0, &r1);
masm.rshift64(lowPart(r1), r0.reg);
freeI64(r1);
pushI64(r0);
+ }
}
void
BaseCompiler::emitRotrI32()
{
- // TODO / OPTIMIZE: Constant rhs (Bug 1316803)
+ int32_t c;
+ if (popConstI32(c)) {
+ RegI32 r = popI32();
+ masm.rotateRight(Imm32(c & 31), r.reg, r.reg);
+ pushI32(r);
+ } else {
RegI32 r0, r1;
pop2xI32ForShiftOrRotate(&r0, &r1);
masm.rotateRight(r1.reg, r0.reg, r0.reg);
freeI32(r1);
pushI32(r0);
+ }
}
void
BaseCompiler::emitRotrI64()
{
- // TODO / OPTIMIZE: Constant rhs (Bug 1316803)
+ int32_t c;
+ if (popConstI32(c)) {
+ RegI64 r = popI64();
+#ifdef JS_PUNBOX64
+ masm.rotateRight64(Imm32(c & 63), r.reg, r.reg);
+#else
+ RegI32 temp = needI32();
+ masm.rotateRight64(Imm32(c & 63), r.reg, r.reg, temp.reg);
+ freeI32(temp);
+#endif
+ pushI64(r);
+ } else {
RegI64 r0, r1;
pop2xI64ForShiftOrRotate(&r0, &r1);
masm.rotateRight64(lowPart(r1), r0.reg, r0.reg, maybeHighPart(r1));
freeI64(r1);
pushI64(r0);
+ }
}
void
BaseCompiler::emitRotlI32()
{
- // TODO / OPTIMIZE: Constant rhs (Bug 1316803)
+ int32_t c;
+ if (popConstI32(c)) {
+ RegI32 r = popI32();
+ masm.rotateLeft(Imm32(c & 31), r.reg, r.reg);
+ pushI32(r);
+ } else {
RegI32 r0, r1;
pop2xI32ForShiftOrRotate(&r0, &r1);
masm.rotateLeft(r1.reg, r0.reg, r0.reg);
freeI32(r1);
pushI32(r0);
+ }
}
void
BaseCompiler::emitRotlI64()
{
- // TODO / OPTIMIZE: Constant rhs (Bug 1316803)
+ int32_t c;
+ if (popConstI32(c)) {
+ RegI64 r = popI64();
+#ifdef JS_PUNBOX64
+ masm.rotateLeft64(Imm32(c & 63), r.reg, r.reg);
+#else
+ RegI32 temp = needI32();
+ masm.rotateLeft64(Imm32(c & 63), r.reg, r.reg, temp.reg);
+ freeI32(temp);
+#endif
+ pushI64(r);
+ } else {
RegI64 r0, r1;
pop2xI64ForShiftOrRotate(&r0, &r1);
masm.rotateLeft64(lowPart(r1), r0.reg, r0.reg, maybeHighPart(r1));
freeI64(r1);
pushI64(r0);
+ }
}
void
diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp
index e93854a9f9..3ca3da6b8b 100644
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -1426,6 +1426,10 @@ ReloadPrefsCallback(const char* pref, void* data)
bool useBaselineEager = Preferences::GetBool(JS_OPTIONS_DOT_STR
"baselinejit.unsafe_eager_compilation");
bool useIonEager = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion.unsafe_eager_compilation");
+ int32_t baselineWarmUpThreshold = Preferences::GetInt(JS_OPTIONS_DOT_STR
+ "baselinejit.threshold", -1);
+ int32_t ionWarmUpThreshold = Preferences::GetInt(JS_OPTIONS_DOT_STR
+ "ion.threshold", -1);
sDiscardSystemSource = Preferences::GetBool(JS_OPTIONS_DOT_STR "discardSystemSource");
@@ -1472,10 +1476,23 @@ ReloadPrefsCallback(const char* pref, void* data)
JS_SetParallelParsingEnabled(cx, parallelParsing);
JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);
+
+ // -1 means "use engine default".
+ if (baselineWarmUpThreshold < -1)
+ baselineWarmUpThreshold = -1;
+ if (ionWarmUpThreshold < -1)
+ ionWarmUpThreshold = -1;
+
+ // Eager compilation prefs still override threshold prefs.
+ if (useBaselineEager)
+ baselineWarmUpThreshold = 0;
+ if (useIonEager)
+ ionWarmUpThreshold = 0;
+
JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_WARMUP_TRIGGER,
- useBaselineEager ? 0 : -1);
+ baselineWarmUpThreshold);
JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_WARMUP_TRIGGER,
- useIonEager ? 0 : -1);
+ ionWarmUpThreshold);
JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_UNBOXED_OBJECTS,
unboxedObjects);
JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_INLINING,
diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp
index 038371a583..b35316a9f5 100644
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -2138,14 +2138,25 @@ nsRefreshDriver::IsWaitingForPaint(mozilla::TimeStamp aTime)
if (mWaitingForTransaction) {
if (mSkippedPaints && aTime > (mMostRecentTick + TimeDuration::FromMilliseconds(mWarningThreshold * 1000))) {
- // XXX - Bug 1303369 - too many false positives.
- //gfxCriticalNote << "Refresh driver waiting for the compositor for "
- // << (aTime - mMostRecentTick).ToSeconds()
- // << " seconds.";
- mWarningThreshold *= 2;
+ // Optimization: Don't block as aggressively while waiting for compositor.
+ // Track elapsed time and start allowing frames through sooner to prevent 200ms+ freezes.
+ // Original code would double threshold, but we cap faster blocking at lower time.
+ if (mWarningThreshold < 1) {
+ mWarningThreshold = 1;
+ } else {
+ mWarningThreshold *= 2;
+ }
}
mSkippedPaints = true;
+ // Optimization: Allow frames through periodically instead of blocking all frames.
+ // Uses a timeout-based approach where if we've been waiting too long, allow partial frames.
+ // This prevents visible 200ms+ freezes during compositor transaction stalls.
+ if (aTime > (mMostRecentTick + TimeDuration::FromMilliseconds(50))) {
+ // If more than 50ms has passed, allow some frames through
+ // This keeps the UI responsive during compositor delays
+ return false;
+ }
return true;
}
diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
index 5f7d02ac62..7747e2c804 100644
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -56,7 +56,7 @@ pref("browser.cache.disk.smart_size.enabled", true);
// Which max value should we use for smart-sizing?
pref("browser.cache.disk.smart_size.use_old_max", true);
// Size (in KB) explicitly set by the user. Used when smart_size.enabled == false
-pref("browser.cache.disk.capacity", 256000);
+pref("browser.cache.disk.capacity", 2097152);
// When smartsizing is disabled we could potentially fill all disk space by
// cache data when the disk capacity is not set correctly. To avoid that we
// check the free space every time we write some data to the cache. The free
@@ -67,13 +67,13 @@ pref("browser.cache.disk.free_space_soft_limit", 5120); // 5MB
pref("browser.cache.disk.free_space_hard_limit", 1024); // 1MB
// Max-size (in KB) for entries in disk cache. Set to -1 for no limit.
// (Note: entries bigger than 1/8 of disk-cache are never cached)
-pref("browser.cache.disk.max_entry_size", 51200); // 50 MB
+pref("browser.cache.disk.max_entry_size", 102400); // 100 MB
pref("browser.cache.memory.enable", true);
// -1 = determine dynamically, 0 = none, n = memory capacity in kilobytes
//pref("browser.cache.memory.capacity", -1);
// Max-size (in KB) for entries in memory cache. Set to -1 for no limit.
// (Note: entries bigger than than 90% of the mem-cache are never cached)
-pref("browser.cache.memory.max_entry_size", 5120);
+pref("browser.cache.memory.max_entry_size", 8192);
// Memory limit (in kB) for new cache data not yet written to disk. Writes to
// the cache are buffered and written to disk on background with low priority.
// With a slow persistent storage these buffers may grow when data is coming
@@ -82,16 +82,16 @@ pref("browser.cache.memory.max_entry_size", 5120);
// (priority) like html, css, fonts and js, and one for other data like images,
// video, etc.
// Note: 0 means no limit.
-pref("browser.cache.disk.max_chunks_memory_usage", 10240);
-pref("browser.cache.disk.max_priority_chunks_memory_usage", 10240);
+pref("browser.cache.disk.max_chunks_memory_usage", 20480);
+pref("browser.cache.disk.max_priority_chunks_memory_usage", 20480);
pref("browser.cache.disk_cache_ssl", true);
// 0 = once-per-session, 1 = each-time, 2 = never, 3 = when-appropriate/automatically
pref("browser.cache.check_doc_frequency", 3);
// Limit of recent metadata we keep in memory for faster access, in Kb
-pref("browser.cache.disk.metadata_memory_limit", 250); // 0.25 MB
+pref("browser.cache.disk.metadata_memory_limit", 1024); // 1 MB
// The number of chunks we preload ahead of read. One chunk has currently 256kB.
-pref("browser.cache.disk.preload_chunk_count", 4); // 1 MB of read ahead
+pref("browser.cache.disk.preload_chunk_count", 8); // 2 MB of read ahead
// The half life used to re-compute cache entries frecency in hours.
pref("browser.cache.frecency_half_life_hours", 6);
@@ -252,7 +252,7 @@ pref("dom.compartment_per_addon", true);
// Fastback caching - if this pref is negative, then we calculate the number
// of content viewers to cache based on the amount of available memory.
-pref("browser.sessionhistory.max_total_viewers", -1);
+pref("browser.sessionhistory.max_total_viewers", 8);
// Whether to store 'about:newtab' in the session history, disabled by default.
// See https://github.com/MoonchildProductions/UXP/issues/719
@@ -1170,9 +1170,9 @@ pref("dom.send_after_paint_to_content", false);
pref("dom.link.disabled_attribute.enabled", true);
// Timeout clamp in ms for timeouts we clamp
-pref("dom.min_timeout_value", 4);
+pref("dom.min_timeout_value", 2);
// And for background windows
-pref("dom.min_background_timeout_value", 1000);
+pref("dom.min_background_timeout_value", 1500);
// Don't use new input types
pref("dom.experimental_forms", false);
@@ -1265,6 +1265,11 @@ pref("javascript.options.unboxed_objects", false);
pref("javascript.options.baselinejit", true);
pref("javascript.options.ion", true);
pref("javascript.options.ion.inlining", true);
+// JIT warm-up thresholds (-1 keeps engine defaults).
+// Lower values can improve sustained throughput on large script bundles
+// (e.g. React/jQuery-heavy apps) at some startup compile cost.
+pref("javascript.options.baselinejit.threshold", 6);
+pref("javascript.options.ion.threshold", 50);
pref("javascript.options.asmjs", true);
pref("javascript.options.wasm", true);
// wasm jit crashes in 32bit builds because of 64bit casts so
@@ -1293,7 +1298,7 @@ pref("javascript.options.mem.high_water_mark", 128);
pref("javascript.options.mem.max", -1);
pref("javascript.options.mem.gc_per_zone", true);
pref("javascript.options.mem.gc_incremental", true);
-pref("javascript.options.mem.gc_incremental_slice_ms", 20);
+pref("javascript.options.mem.gc_incremental_slice_ms", 10);
pref("javascript.options.mem.gc_generational", true);
pref("javascript.options.mem.gc_compacting", true);
pref("javascript.options.mem.log", false);
@@ -1306,16 +1311,16 @@ pref("javascript.options.compact_on_user_inactive_delay", 15000); // ms
pref("javascript.options.compact_on_user_inactive_delay", 300000); // ms
#endif
-pref("javascript.options.mem.gc_high_frequency_time_limit_ms", 1000);
+pref("javascript.options.mem.gc_high_frequency_time_limit_ms", 2000);
pref("javascript.options.mem.gc_high_frequency_low_limit_mb", 100);
pref("javascript.options.mem.gc_high_frequency_high_limit_mb", 500);
-pref("javascript.options.mem.gc_high_frequency_heap_growth_max", 300);
-pref("javascript.options.mem.gc_high_frequency_heap_growth_min", 150);
+pref("javascript.options.mem.gc_high_frequency_heap_growth_max", 350);
+pref("javascript.options.mem.gc_high_frequency_heap_growth_min", 175);
pref("javascript.options.mem.gc_low_frequency_heap_growth", 150);
pref("javascript.options.mem.gc_dynamic_heap_growth", true);
pref("javascript.options.mem.gc_dynamic_mark_slice", true);
pref("javascript.options.mem.gc_refresh_frame_slices_enabled", true);
-pref("javascript.options.mem.gc_allocation_threshold_mb", 30);
+pref("javascript.options.mem.gc_allocation_threshold_mb", 20);
pref("javascript.options.mem.gc_min_empty_chunk_count", 1);
pref("javascript.options.mem.gc_max_empty_chunk_count", 30);
@@ -2803,7 +2808,7 @@ pref("editor.positioning.offset", 0);
pref("dom.use_watchdog", true);
pref("dom.max_chrome_script_run_time", 30);
-pref("dom.max_script_run_time", 15);
+pref("dom.max_script_run_time", 10);
// Automatically terminate non-responsive scripts if script_run_time expires.
pref("dom.always_stop_slow_scripts", false);
@@ -2826,7 +2831,7 @@ pref("idle_queue.long_period", 50);
// period, which makes the point in time that we expect to become busy
// again be:
// now + idle_queue.min_period + layout.idle_period.time_limit
-pref("idle_queue.min_period", 3);
+pref("idle_queue.min_period", 1);
// Hang monitor timeout after which we kill the browser, in seconds
// (0 is disabled)
@@ -4275,11 +4280,15 @@ pref("image.animated.decode-on-demand.batch-size", 6);
pref("image.animated.resume-from-last-displayed", true);
// The maximum size, in bytes, of the decoded images we cache
-pref("image.cache.size", 5242880);
+pref("image.cache.size", 67108864);
// A weight, from 0-1000, to place on time when comparing to size.
// Size is given a weight of 1000 - timeweight.
-pref("image.cache.timeweight", 500);
+pref("image.cache.timeweight", 650);
+
+// Time in seconds before unproxied entries in the in-memory image cache are
+// considered for eviction by the expiration tracker.
+pref("image.cache.entry_timeout_seconds", 30);
// Decode all images automatically on load, ignoring our normal heuristics.
pref("image.decode-immediately.enabled", false);
@@ -4289,9 +4298,9 @@ pref("image.downscale-during-decode.enabled", true);
// The default Accept header sent for images loaded over HTTP(S)
#ifdef MOZ_JXL
-pref("image.http.accept", "image/webp,image/jxl,image/png,image/*;q=0.8,*/*;q=0.5");
+pref("image.http.accept", "image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5");
#else
-pref("image.http.accept", "image/webp,image/png,image/*;q=0.8,*/*;q=0.5");
+pref("image.http.accept", "image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5");
#endif
// The threshold for inferring that changes to an
element's |src|
@@ -4326,7 +4335,7 @@ pref("image.mem.decode_bytes_at_a_time", 16384);
// Minimum timeout for expiring unused images from the surface cache, in
// milliseconds. This controls how long we store cached temporary surfaces.
-pref("image.mem.surfacecache.min_expiration_ms", 60000); // 60s
+pref("image.mem.surfacecache.min_expiration_ms", 180000); // 180s
// Maximum size for the surface cache, in kilobytes.
pref("image.mem.surfacecache.max_size_kb", 1048576); // 1GB
@@ -4343,7 +4352,7 @@ pref("image.mem.surfacecache.size_factor", 4);
// surface cache on memory pressure, a discard factor of 2 means to discard half
// of the data, and so forth. The default should be a good balance for desktop
// and laptop systems, where we never discard visible images.
-pref("image.mem.surfacecache.discard_factor", 1);
+pref("image.mem.surfacecache.discard_factor", 2);
// How many threads we'll use for multithreaded decoding. If < 0, will be
// automatically determined based on the system's number of cores.
@@ -4665,7 +4674,7 @@ pref("dom.idle-observers-api.enabled", true);
// Time limit, in milliseconds, for EventStateManager::IsHandlingUserInput().
// Used to detect long running handlers of user-generated events.
-pref("dom.event.handling-user-input-time-limit", 1000);
+pref("dom.event.handling-user-input-time-limit", 500);
// Whether we should layerize all animated images (if otherwise possible).
pref("layout.animated-image-layers.enabled", false);
diff --git a/widget/windows/GfxInfo.cpp b/widget/windows/GfxInfo.cpp
index 5789cfd1d0..b4b4c88914 100644
--- a/widget/windows/GfxInfo.cpp
+++ b/widget/windows/GfxInfo.cpp
@@ -1186,6 +1186,12 @@ GfxInfo::GetFeatureStatusImpl(int32_t aFeature,
!adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorAMD), nsCaseInsensitiveStringComparator()) &&
!adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorATI), nsCaseInsensitiveStringComparator()) &&
!adapterVendorID.Equals(GfxDriverInfo::GetDeviceVendor(VendorMicrosoft), nsCaseInsensitiveStringComparator()) &&
+ // VMware (PCI vendor 0x15ad), Hyper-V synthetic adapter vendor
+ // ids, and Parallels (PCI vendor 0x1ab8) should be treated as
+ // known virtualized vendors.
+ !adapterVendorID.LowerCaseEqualsLiteral("0x15ad") &&
+ !adapterVendorID.LowerCaseEqualsLiteral("0x1414") &&
+ !adapterVendorID.LowerCaseEqualsLiteral("0x1ab8") &&
// FIXME - these special hex values are currently used in xpcshell tests introduced by
// bug 625160 patch 8/8. Maybe these tests need to be adjusted now that we're only whitelisting
// intel/ati/nvidia.
@@ -1206,6 +1212,13 @@ GfxInfo::GetFeatureStatusImpl(int32_t aFeature,
return NS_OK;
}
+ // Explicitly block VirtualBox virtual GPU devices due to buggy drivers and lack of hardware acceleration support.
+ if (adapterVendorID.LowerCaseEqualsLiteral("0x80ee")) {
+ aFailureId = "FEATURE_FAILURE_VIRTUALBOX";
+ *aStatus = FEATURE_BLOCKED_DEVICE;
+ return NS_OK;
+ }
+
// special-case the WinXP test slaves: they have out-of-date drivers, but we still want to
// whitelist them, actually we do know that this combination of device and driver version
// works well.