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.