diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 52b58d3c1fcb..bafdf9955b3a 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 0e4ab8d9e82c..e4832bfb4afe 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 76fa99ed8cfb..ca867860cc30 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 52b58d3c1fcb..bafdf9955b3a 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 4a9bd515a7df..63b6aa7fee63 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -17,7 +17,7 @@ - + @@ -118,7 +118,7 @@ - + diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 6d03afe6482a..1abfbab2f760 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "89087e33f8ad411f955b7918f33bb54a100a1413", + "revision": "37cd9a62b2cb570985d99a5519b136672614b980", "repo_path": "/integration/gaia-central" } diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index 5c5249211bb5..fd51d4128361 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 4e53bfa7e317..0bdf351894d1 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index a507ea8de50e..39071a99677a 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index c296d38cdaa4..c78dd486d43c 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/content/base/public/FragmentOrElement.h b/content/base/public/FragmentOrElement.h index 0576e992b400..b0853e502374 100644 --- a/content/base/public/FragmentOrElement.h +++ b/content/base/public/FragmentOrElement.h @@ -34,6 +34,12 @@ class nsDOMStringMap; class nsINodeInfo; class nsIURI; +namespace mozilla { +namespace dom { +class Element; +} +} + /** * Class that implements the nsIDOMNodeList interface (a list of children of * the content), by holding a reference to the content and delegating GetLength @@ -230,6 +236,14 @@ public: return Children()->Length(); } + /** + * Sets the IsElementInStyleScope flag on each element in the subtree rooted + * at this node, including any elements reachable through shadow trees. + * + * @param aInStyleScope The flag value to set. + */ + void SetIsElementInStyleScopeFlagOnSubtree(bool aInStyleScope); + public: /** * If there are listeners for DOMNodeInserted event, fires the event on all @@ -415,6 +429,14 @@ protected: return static_cast(GetExistingSlots()); } + /** + * Calls SetIsElementInStyleScopeFlagOnSubtree for each shadow tree attached + * to this node, which is assumed to be an Element. + * + * @param aInStyleScope The IsElementInStyleScope flag value to set. + */ + void SetIsElementInStyleScopeFlagOnShadowTree(bool aInStyleScope); + friend class ::ContentUnbinder; /** * Array containing all attributes and children for this element diff --git a/content/base/public/nsINode.h b/content/base/public/nsINode.h index 6bb57f465b1a..3193274d94f3 100644 --- a/content/base/public/nsINode.h +++ b/content/base/public/nsINode.h @@ -816,6 +816,12 @@ public: return mParent && mParent->IsElement() ? mParent->AsElement() : nullptr; } + /** + * Get the parent Element of this node, traversing over a ShadowRoot + * to its host if necessary. + */ + mozilla::dom::Element* GetParentElementCrossingShadowRoot() const; + /** * Get the root of the subtree this node belongs to. This never returns * null. It may return 'this' (e.g. for document nodes, and nodes that diff --git a/content/base/src/Element.cpp b/content/base/src/Element.cpp index a7fb39e8d3f7..573527b023db 100644 --- a/content/base/src/Element.cpp +++ b/content/base/src/Element.cpp @@ -1289,15 +1289,28 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES | // And the restyle bits ELEMENT_ALL_RESTYLE_FLAGS); - - // Propagate scoped style sheet tracking bit. - SetIsElementInStyleScope(mParent->IsElementInStyleScope()); } else if (!HasFlag(NODE_IS_IN_SHADOW_TREE)) { // If we're not in the doc and not in a shadow tree, // update our subtree pointer. SetSubtreeRootPointer(aParent->SubtreeRoot()); } + // Propagate scoped style sheet tracking bit. + if (mParent->IsContent()) { + nsIContent* parent; + ShadowRoot* shadowRootParent = ShadowRoot::FromNode(mParent); + if (shadowRootParent) { + parent = shadowRootParent->GetHost(); + } else { + parent = mParent->AsContent(); + } + + bool inStyleScope = parent->IsElementInStyleScope(); + + SetIsElementInStyleScope(inStyleScope); + SetIsElementInStyleScopeFlagOnShadowTree(inStyleScope); + } + // This has to be here, rather than in nsGenericHTMLElement::BindToTree, // because it has to happen after updating the parent pointer, but before // recursively binding the kids. diff --git a/content/base/src/FragmentOrElement.cpp b/content/base/src/FragmentOrElement.cpp index 31f720f3c0d6..fa5c7749e1c0 100644 --- a/content/base/src/FragmentOrElement.cpp +++ b/content/base/src/FragmentOrElement.cpp @@ -2846,3 +2846,41 @@ FragmentOrElement::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const return n; } + +void +FragmentOrElement::SetIsElementInStyleScopeFlagOnSubtree(bool aInStyleScope) +{ + if (aInStyleScope && IsElementInStyleScope()) { + return; + } + + if (IsElement()) { + SetIsElementInStyleScope(aInStyleScope); + SetIsElementInStyleScopeFlagOnShadowTree(aInStyleScope); + } + + nsIContent* n = GetNextNode(this); + while (n) { + if (n->IsElementInStyleScope()) { + n = n->GetNextNonChildNode(this); + } else { + if (n->IsElement()) { + n->SetIsElementInStyleScope(aInStyleScope); + n->AsElement()->SetIsElementInStyleScopeFlagOnShadowTree(aInStyleScope); + } + n = n->GetNextNode(this); + } + } +} + +void +FragmentOrElement::SetIsElementInStyleScopeFlagOnShadowTree(bool aInStyleScope) +{ + NS_ASSERTION(IsElement(), "calling SetIsElementInStyleScopeFlagOnShadowTree " + "on a non-Element is useless"); + ShadowRoot* shadowRoot = GetShadowRoot(); + while (shadowRoot) { + shadowRoot->SetIsElementInStyleScopeFlagOnSubtree(aInStyleScope); + shadowRoot = shadowRoot->GetOlderShadow(); + } +} diff --git a/content/base/src/nsINode.cpp b/content/base/src/nsINode.cpp index f70ce10e640b..432f712f5542 100644 --- a/content/base/src/nsINode.cpp +++ b/content/base/src/nsINode.cpp @@ -2736,3 +2736,25 @@ EventTarget::DispatchEvent(Event& aEvent, aRv = DispatchEvent(&aEvent, &result); return result; } + +Element* +nsINode::GetParentElementCrossingShadowRoot() const +{ + if (!mParent) { + return nullptr; + } + + if (mParent->IsElement()) { + return mParent->AsElement(); + } + + ShadowRoot* shadowRoot = ShadowRoot::FromNode(mParent); + if (shadowRoot) { + nsIContent* host = shadowRoot->GetHost(); + MOZ_ASSERT(host, "ShowRoots should always have a host"); + MOZ_ASSERT(host->IsElement(), "ShadowRoot hosts should always be Elements"); + return host->AsElement(); + } + + return nullptr; +} diff --git a/content/base/src/nsStyleLinkElement.cpp b/content/base/src/nsStyleLinkElement.cpp index faa0f4ecf545..acfa98a4e29b 100644 --- a/content/base/src/nsStyleLinkElement.cpp +++ b/content/base/src/nsStyleLinkElement.cpp @@ -14,6 +14,7 @@ #include "mozilla/css/Loader.h" #include "mozilla/dom/Element.h" +#include "mozilla/dom/FragmentOrElement.h" #include "mozilla/dom/ShadowRoot.h" #include "mozilla/Preferences.h" #include "nsCSSStyleSheet.h" @@ -220,28 +221,6 @@ IsScopedStyleElement(nsIContent* aContent) aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::scoped); } -static void -SetIsElementInStyleScopeFlagOnSubtree(Element* aElement) -{ - if (aElement->IsElementInStyleScope()) { - return; - } - - aElement->SetIsElementInStyleScope(); - - nsIContent* n = aElement->GetNextNode(aElement); - while (n) { - if (n->IsElementInStyleScope()) { - n = n->GetNextNonChildNode(aElement); - } else { - if (n->IsElement()) { - n->SetIsElementInStyleScope(); - } - n = n->GetNextNode(aElement); - } - } -} - static bool HasScopedStyleSheetChild(nsIContent* aContent) { @@ -396,7 +375,7 @@ nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument* aOldDocument, Element* scopeElement = isScoped ? thisContent->GetParentElement() : nullptr; if (scopeElement) { NS_ASSERTION(isInline, "non-inline style must not have scope element"); - SetIsElementInStyleScopeFlagOnSubtree(scopeElement); + scopeElement->SetIsElementInStyleScopeFlagOnSubtree(true); } bool doneLoading = false; @@ -488,6 +467,6 @@ nsStyleLinkElement::UpdateStyleSheetScopedness(bool aIsNowScoped) UpdateIsElementInStyleScopeFlagOnSubtree(oldScopeElement); } if (newScopeElement) { - SetIsElementInStyleScopeFlagOnSubtree(newScopeElement); + newScopeElement->SetIsElementInStyleScopeFlagOnSubtree(true); } } diff --git a/content/media/MediaData.h b/content/media/MediaData.h index 65db1f9bd5e9..8332532e6eca 100644 --- a/content/media/MediaData.h +++ b/content/media/MediaData.h @@ -37,6 +37,7 @@ public: , mOffset(aOffset) , mTime(aTimestamp) , mDuration(aDuration) + , mDiscontinuity(false) {} virtual ~MediaData() {} @@ -53,6 +54,10 @@ public: // Duration of sample, in microseconds. const int64_t mDuration; + // True if this is the first sample after a gap or discontinuity in + // the stream. This is true for the first sample in a stream after a seek. + bool mDiscontinuity; + int64_t GetEndTime() const { return mTime + mDuration; } }; diff --git a/content/media/MediaDataDecodedListener.h b/content/media/MediaDataDecodedListener.h new file mode 100644 index 000000000000..6321a79188eb --- /dev/null +++ b/content/media/MediaDataDecodedListener.h @@ -0,0 +1,148 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MediaDataDecodedListener_h_ +#define MediaDataDecodedListener_h_ + +#include "mozilla/Monitor.h" +#include "MediaDecoderReader.h" + +namespace mozilla { + +class MediaDecoderStateMachine; +class MediaData; + +// A RequestSampleCallback implementation that forwards samples onto the +// MediaDecoderStateMachine via tasks that run on the supplied task queue. +template +class MediaDataDecodedListener : public RequestSampleCallback { +public: + MediaDataDecodedListener(Target* aTarget, + MediaTaskQueue* aTaskQueue) + : mMonitor("MediaDataDecodedListener") + , mTaskQueue(aTaskQueue) + , mTarget(aTarget) + { + MOZ_ASSERT(aTarget); + MOZ_ASSERT(aTaskQueue); + } + + virtual void OnAudioDecoded(AudioData* aSample) MOZ_OVERRIDE { + MonitorAutoLock lock(mMonitor); + nsAutoPtr sample(aSample); + if (!mTarget || !mTaskQueue) { + // We've been shutdown, abort. + return; + } + RefPtr task(new DeliverAudioTask(sample.forget(), mTarget)); + mTaskQueue->Dispatch(task); + } + + virtual void OnAudioEOS() MOZ_OVERRIDE { + MonitorAutoLock lock(mMonitor); + if (!mTarget || !mTaskQueue) { + // We've been shutdown, abort. + return; + } + RefPtr task(NS_NewRunnableMethod(mTarget, &Target::OnAudioEOS)); + if (NS_FAILED(mTaskQueue->Dispatch(task))) { + NS_WARNING("Failed to dispatch OnAudioEOS task"); + } + } + + virtual void OnVideoDecoded(VideoData* aSample) MOZ_OVERRIDE { + MonitorAutoLock lock(mMonitor); + nsAutoPtr sample(aSample); + if (!mTarget || !mTaskQueue) { + // We've been shutdown, abort. + return; + } + RefPtr task(new DeliverVideoTask(sample.forget(), mTarget)); + mTaskQueue->Dispatch(task); + } + + virtual void OnVideoEOS() MOZ_OVERRIDE { + MonitorAutoLock lock(mMonitor); + if (!mTarget || !mTaskQueue) { + // We've been shutdown, abort. + return; + } + RefPtr task(NS_NewRunnableMethod(mTarget, &Target::OnVideoEOS)); + if (NS_FAILED(mTaskQueue->Dispatch(task))) { + NS_WARNING("Failed to dispatch OnVideoEOS task"); + } + } + + virtual void OnDecodeError() MOZ_OVERRIDE { + MonitorAutoLock lock(mMonitor); + if (!mTarget || !mTaskQueue) { + // We've been shutdown, abort. + return; + } + RefPtr task(NS_NewRunnableMethod(mTarget, &Target::OnDecodeError)); + if (NS_FAILED(mTaskQueue->Dispatch(task))) { + NS_WARNING("Failed to dispatch OnAudioDecoded task"); + } + } + + void BreakCycles() { + MonitorAutoLock lock(mMonitor); + mTarget = nullptr; + mTaskQueue = nullptr; + } + +private: + + class DeliverAudioTask : public nsRunnable { + public: + DeliverAudioTask(AudioData* aSample, Target* aTarget) + : mSample(aSample) + , mTarget(aTarget) + { + MOZ_COUNT_CTOR(DeliverAudioTask); + } + ~DeliverAudioTask() + { + MOZ_COUNT_DTOR(DeliverAudioTask); + } + NS_METHOD Run() { + mTarget->OnAudioDecoded(mSample.forget()); + return NS_OK; + } + private: + nsAutoPtr mSample; + RefPtr mTarget; + }; + + class DeliverVideoTask : public nsRunnable { + public: + DeliverVideoTask(VideoData* aSample, Target* aTarget) + : mSample(aSample) + , mTarget(aTarget) + { + MOZ_COUNT_CTOR(DeliverVideoTask); + } + ~DeliverVideoTask() + { + MOZ_COUNT_DTOR(DeliverVideoTask); + } + NS_METHOD Run() { + mTarget->OnVideoDecoded(mSample.forget()); + return NS_OK; + } + private: + nsAutoPtr mSample; + RefPtr mTarget; + }; + + Monitor mMonitor; + RefPtr mTaskQueue; + RefPtr mTarget; +}; + +} + +#endif // MediaDataDecodedListener_h_ diff --git a/content/media/MediaDecoder.cpp b/content/media/MediaDecoder.cpp index a43302761142..b88b74c94dc3 100644 --- a/content/media/MediaDecoder.cpp +++ b/content/media/MediaDecoder.cpp @@ -1526,7 +1526,7 @@ int64_t MediaDecoder::GetEndMediaTime() const { } // Drop reference to state machine. Only called during shutdown dance. -void MediaDecoder::ReleaseStateMachine() { +void MediaDecoder::BreakCycles() { mDecoderStateMachine = nullptr; } diff --git a/content/media/MediaDecoder.h b/content/media/MediaDecoder.h index 3a981f697359..9957537bdf60 100755 --- a/content/media/MediaDecoder.h +++ b/content/media/MediaDecoder.h @@ -6,9 +6,9 @@ /* Each video element based on MediaDecoder has a state machine to manage its play state and keep the current frame up to date. All state machines -share time in a single shared thread. Each decoder also has one thread -dedicated to decoding audio and video data. This thread is shutdown when -playback is paused. Each decoder also has a thread to push decoded audio +share time in a single shared thread. Each decoder also has a MediaTaskQueue +running in a SharedThreadPool to decode audio and video data. +Each decoder also has a thread to push decoded audio to the hardware. This thread is not created until playback starts, but currently is not destroyed when paused, only when playback ends. @@ -234,6 +234,11 @@ struct SeekTarget { , mType(aType) { } + SeekTarget(const SeekTarget& aOther) + : mTime(aOther.mTime) + , mType(aOther.mType) + { + } bool IsValid() const { return mType != SeekTarget::Invalid; } @@ -823,7 +828,7 @@ public: MediaDecoderStateMachine* GetStateMachine() const; // Drop reference to state machine. Only called during shutdown dance. - virtual void ReleaseStateMachine(); + virtual void BreakCycles(); // Notifies the element that decoding has failed. virtual void DecodeError(); diff --git a/content/media/MediaDecoderReader.cpp b/content/media/MediaDecoderReader.cpp index 88858624527a..50f529b675b1 100644 --- a/content/media/MediaDecoderReader.cpp +++ b/content/media/MediaDecoderReader.cpp @@ -63,9 +63,11 @@ public: }; MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder) - : mAudioCompactor(mAudioQueue), - mDecoder(aDecoder), - mIgnoreAudioOutputFormat(false) + : mAudioCompactor(mAudioQueue) + , mDecoder(aDecoder) + , mIgnoreAudioOutputFormat(false) + , mAudioDiscontinuity(false) + , mVideoDiscontinuity(false) { MOZ_COUNT_CTOR(MediaDecoderReader); } @@ -97,6 +99,9 @@ nsresult MediaDecoderReader::ResetDecode() VideoQueue().Reset(); AudioQueue().Reset(); + mAudioDiscontinuity = true; + mVideoDiscontinuity = true; + return res; } @@ -173,169 +178,6 @@ VideoData* MediaDecoderReader::FindStartTime(int64_t& aOutStartTime) return videoData; } -nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget) -{ - DECODER_LOG(PR_LOG_DEBUG, ("MediaDecoderReader::DecodeToTarget(%lld) Begin", aTarget)); - - // Decode forward to the target frame. Start with video, if we have it. - if (HasVideo()) { - // Note: when decoding hits the end of stream we must keep the last frame - // in the video queue so that we'll have something to display after the - // seek completes. This makes our logic a bit messy. - bool eof = false; - nsAutoPtr video; - while (HasVideo() && !eof) { - while (VideoQueue().GetSize() == 0 && !eof) { - bool skip = false; - eof = !DecodeVideoFrame(skip, 0); - { - ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); - if (mDecoder->IsShutdown()) { - return NS_ERROR_FAILURE; - } - } - } - if (eof) { - // Hit end of file, we want to display the last frame of the video. - if (video) { - DECODER_LOG(PR_LOG_DEBUG, - ("MediaDecoderReader::DecodeToTarget(%lld) repushing video frame [%lld, %lld] at EOF", - aTarget, video->mTime, video->GetEndTime())); - VideoQueue().PushFront(video.forget()); - } - VideoQueue().Finish(); - break; - } - video = VideoQueue().PeekFront(); - // If the frame end time is less than the seek target, we won't want - // to display this frame after the seek, so discard it. - if (video && video->GetEndTime() <= aTarget) { - DECODER_LOG(PR_LOG_DEBUG, - ("MediaDecoderReader::DecodeToTarget(%lld) pop video frame [%lld, %lld]", - aTarget, video->mTime, video->GetEndTime())); - VideoQueue().PopFront(); - } else { - // Found a frame after or encompasing the seek target. - if (aTarget >= video->mTime && video->GetEndTime() >= aTarget) { - // The seek target lies inside this frame's time slice. Adjust the frame's - // start time to match the seek target. We do this by replacing the - // first frame with a shallow copy which has the new timestamp. - VideoQueue().PopFront(); - VideoData* temp = VideoData::ShallowCopyUpdateTimestamp(video, aTarget); - video = temp; - VideoQueue().PushFront(video); - } - DECODER_LOG(PR_LOG_DEBUG, - ("MediaDecoderReader::DecodeToTarget(%lld) found target video frame [%lld,%lld]", - aTarget, video->mTime, video->GetEndTime())); - - video.forget(); - break; - } - } - { - ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); - if (mDecoder->IsShutdown()) { - return NS_ERROR_FAILURE; - } - } -#ifdef PR_LOGGING - const VideoData* front = VideoQueue().PeekFront(); - DECODER_LOG(PR_LOG_DEBUG, ("First video frame after decode is %lld", - front ? front->mTime : -1)); -#endif - } - - if (HasAudio()) { - // Decode audio forward to the seek target. - bool eof = false; - while (HasAudio() && !eof) { - while (!eof && AudioQueue().GetSize() == 0) { - eof = !DecodeAudioData(); - { - ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor()); - if (mDecoder->IsShutdown()) { - return NS_ERROR_FAILURE; - } - } - } - const AudioData* audio = AudioQueue().PeekFront(); - if (!audio || eof) { - AudioQueue().Finish(); - break; - } - CheckedInt64 startFrame = UsecsToFrames(audio->mTime, mInfo.mAudio.mRate); - CheckedInt64 targetFrame = UsecsToFrames(aTarget, mInfo.mAudio.mRate); - if (!startFrame.isValid() || !targetFrame.isValid()) { - return NS_ERROR_FAILURE; - } - if (startFrame.value() + audio->mFrames <= targetFrame.value()) { - // Our seek target lies after the frames in this AudioData. Pop it - // off the queue, and keep decoding forwards. - delete AudioQueue().PopFront(); - audio = nullptr; - continue; - } - if (startFrame.value() > targetFrame.value()) { - // The seek target doesn't lie in the audio block just after the last - // audio frames we've seen which were before the seek target. This - // could have been the first audio data we've seen after seek, i.e. the - // seek terminated after the seek target in the audio stream. Just - // abort the audio decode-to-target, the state machine will play - // silence to cover the gap. Typically this happens in poorly muxed - // files. - NS_WARNING("Audio not synced after seek, maybe a poorly muxed file?"); - break; - } - - // The seek target lies somewhere in this AudioData's frames, strip off - // any frames which lie before the seek target, so we'll begin playback - // exactly at the seek target. - NS_ASSERTION(targetFrame.value() >= startFrame.value(), - "Target must at or be after data start."); - NS_ASSERTION(targetFrame.value() < startFrame.value() + audio->mFrames, - "Data must end after target."); - - int64_t framesToPrune = targetFrame.value() - startFrame.value(); - if (framesToPrune > audio->mFrames) { - // We've messed up somehow. Don't try to trim frames, the |frames| - // variable below will overflow. - NS_WARNING("Can't prune more frames that we have!"); - break; - } - uint32_t frames = audio->mFrames - static_cast(framesToPrune); - uint32_t channels = audio->mChannels; - nsAutoArrayPtr audioData(new AudioDataValue[frames * channels]); - memcpy(audioData.get(), - audio->mAudioData.get() + (framesToPrune * channels), - frames * channels * sizeof(AudioDataValue)); - CheckedInt64 duration = FramesToUsecs(frames, mInfo.mAudio.mRate); - if (!duration.isValid()) { - return NS_ERROR_FAILURE; - } - nsAutoPtr data(new AudioData(audio->mOffset, - aTarget, - duration.value(), - frames, - audioData.forget(), - channels)); - delete AudioQueue().PopFront(); - AudioQueue().PushFront(data.forget()); - break; - } - } - -#ifdef PR_LOGGING - const VideoData* v = VideoQueue().PeekFront(); - const AudioData* a = AudioQueue().PeekFront(); - DECODER_LOG(PR_LOG_DEBUG, - ("MediaDecoderReader::DecodeToTarget(%lld) finished v=%lld a=%lld", - aTarget, v ? v->mTime : -1, a ? a->mTime : -1)); -#endif - - return NS_OK; -} - nsresult MediaDecoderReader::GetBuffered(mozilla::dom::TimeRanges* aBuffered, int64_t aStartTime) @@ -350,4 +192,174 @@ MediaDecoderReader::GetBuffered(mozilla::dom::TimeRanges* aBuffered, return NS_OK; } +class RequestVideoWithSkipTask : public nsRunnable { +public: + RequestVideoWithSkipTask(MediaDecoderReader* aReader, + int64_t aTimeThreshold) + : mReader(aReader) + , mTimeThreshold(aTimeThreshold) + { + } + NS_METHOD Run() { + bool skip = true; + mReader->RequestVideoData(skip, mTimeThreshold); + return NS_OK; + } +private: + nsRefPtr mReader; + int64_t mTimeThreshold; +}; + +void +MediaDecoderReader::RequestVideoData(bool aSkipToNextKeyframe, + int64_t aTimeThreshold) +{ + bool skip = aSkipToNextKeyframe; + while (VideoQueue().GetSize() == 0 && + !VideoQueue().IsFinished()) { + if (!DecodeVideoFrame(skip, aTimeThreshold)) { + VideoQueue().Finish(); + } else if (skip) { + // We still need to decode more data in order to skip to the next + // keyframe. Post another task to the decode task queue to decode + // again. We don't just decode straight in a loop here, as that + // would hog the decode task queue. + RefPtr task(new RequestVideoWithSkipTask(this, aTimeThreshold)); + mTaskQueue->Dispatch(task); + return; + } + } + if (VideoQueue().GetSize() > 0) { + VideoData* v = VideoQueue().PopFront(); + if (v && mVideoDiscontinuity) { + v->mDiscontinuity = true; + mVideoDiscontinuity = false; + } + GetCallback()->OnVideoDecoded(v); + } else if (VideoQueue().IsFinished()) { + GetCallback()->OnVideoEOS(); + } +} + +void +MediaDecoderReader::RequestAudioData() +{ + while (AudioQueue().GetSize() == 0 && + !AudioQueue().IsFinished()) { + if (!DecodeAudioData()) { + AudioQueue().Finish(); + } + } + if (AudioQueue().GetSize() > 0) { + AudioData* a = AudioQueue().PopFront(); + if (mAudioDiscontinuity) { + a->mDiscontinuity = true; + mAudioDiscontinuity = false; + } + GetCallback()->OnAudioDecoded(a); + return; + } else if (AudioQueue().IsFinished()) { + GetCallback()->OnAudioEOS(); + return; + } +} + +void +MediaDecoderReader::SetCallback(RequestSampleCallback* aCallback) +{ + mSampleDecodedCallback = aCallback; +} + +void +MediaDecoderReader::SetTaskQueue(MediaTaskQueue* aTaskQueue) +{ + mTaskQueue = aTaskQueue; +} + +void +MediaDecoderReader::BreakCycles() +{ + if (mSampleDecodedCallback) { + mSampleDecodedCallback->BreakCycles(); + mSampleDecodedCallback = nullptr; + } + mTaskQueue = nullptr; +} + +void +MediaDecoderReader::Shutdown() +{ + ReleaseMediaResources(); +} + +AudioDecodeRendezvous::AudioDecodeRendezvous() + : mMonitor("AudioDecodeRendezvous") + , mHaveResult(false) +{ +} + +AudioDecodeRendezvous::~AudioDecodeRendezvous() +{ +} + +void +AudioDecodeRendezvous::OnAudioDecoded(AudioData* aSample) +{ + MonitorAutoLock mon(mMonitor); + mSample = aSample; + mStatus = NS_OK; + mHaveResult = true; + mon.NotifyAll(); +} + +void +AudioDecodeRendezvous::OnAudioEOS() +{ + MonitorAutoLock mon(mMonitor); + mSample = nullptr; + mStatus = NS_OK; + mHaveResult = true; + mon.NotifyAll(); +} + +void +AudioDecodeRendezvous::OnDecodeError() +{ + MonitorAutoLock mon(mMonitor); + mSample = nullptr; + mStatus = NS_ERROR_FAILURE; + mHaveResult = true; + mon.NotifyAll(); +} + +void +AudioDecodeRendezvous::Reset() +{ + MonitorAutoLock mon(mMonitor); + mHaveResult = false; + mStatus = NS_OK; + mSample = nullptr; +} + +nsresult +AudioDecodeRendezvous::Await(nsAutoPtr& aSample) +{ + MonitorAutoLock mon(mMonitor); + while (!mHaveResult) { + mon.Wait(); + } + mHaveResult = false; + aSample = mSample; + return mStatus; +} + +void +AudioDecodeRendezvous::Cancel() +{ + MonitorAutoLock mon(mMonitor); + mStatus = NS_ERROR_ABORT; + mHaveResult = true; + mon.NotifyAll(); +} + } // namespace mozilla diff --git a/content/media/MediaDecoderReader.h b/content/media/MediaDecoderReader.h index cbb06a68cdac..166fa936e57b 100644 --- a/content/media/MediaDecoderReader.h +++ b/content/media/MediaDecoderReader.h @@ -18,12 +18,19 @@ namespace dom { class TimeRanges; } -// Encapsulates the decoding and reading of media data. Reading can only be -// done on the decode thread. Never hold the decoder monitor when -// calling into this class. Unless otherwise specified, methods and fields of -// this class can only be accessed on the decode thread. +class RequestSampleCallback; + +// Encapsulates the decoding and reading of media data. Reading can either +// synchronous and done on the calling "decode" thread, or asynchronous and +// performed on a background thread, with the result being returned by +// callback. Never hold the decoder monitor when calling into this class. +// Unless otherwise specified, methods and fields of this class can only +// be accessed on the decode task queue. class MediaDecoderReader { public: + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReader) + MediaDecoderReader(AbstractMediaDecoder* aDecoder); virtual ~MediaDecoderReader(); @@ -36,24 +43,48 @@ public: // True when this reader need to become dormant state virtual bool IsDormantNeeded() { return false; } // Release media resources they should be released in dormant state + // The reader can be made usable again by calling ReadMetadata(). virtual void ReleaseMediaResources() {}; - // Release the decoder during shutdown - virtual void ReleaseDecoder() {}; + // Breaks reference-counted cycles. Called during shutdown. + // WARNING: If you override this, you must call the base implementation + // in your override. + virtual void BreakCycles(); + + // Destroys the decoding state. The reader cannot be made usable again. + // This is different from ReleaseMediaResources() as it is irreversable, + // whereas ReleaseMediaResources() is. + virtual void Shutdown(); + + virtual void SetCallback(RequestSampleCallback* aDecodedSampleCallback); + virtual void SetTaskQueue(MediaTaskQueue* aTaskQueue); // Resets all state related to decoding, emptying all buffers etc. + // Cancels all pending Request*Data() request callbacks, and flushes the + // decode pipeline. The decoder must not call any of the callbacks for + // outstanding Request*Data() calls after this is called. Calls to + // Request*Data() made after this should be processed as usual. + // Normally this call preceedes a Seek() call, or shutdown. + // The first samples of every stream produced after a ResetDecode() call + // *must* be marked as "discontinuities". If it's not, seeking work won't + // properly! virtual nsresult ResetDecode(); - // Decodes an unspecified amount of audio data, enqueuing the audio data - // in mAudioQueue. Returns true when there's more audio to decode, - // false if the audio is finished, end of file has been reached, - // or an un-recoverable read error has occured. - virtual bool DecodeAudioData() = 0; + // Requests the Reader to call OnAudioDecoded() on aCallback with one + // audio sample. The decode should be performed asynchronously, and + // the callback can be performed on any thread. Don't hold the decoder + // monitor while calling this, as the implementation may try to wait + // on something that needs the monitor and deadlock. + virtual void RequestAudioData(); - // Reads and decodes one video frame. Packets with a timestamp less - // than aTimeThreshold will be decoded (unless they're not keyframes - // and aKeyframeSkip is true), but will not be added to the queue. - virtual bool DecodeVideoFrame(bool &aKeyframeSkip, - int64_t aTimeThreshold) = 0; + // Requests the Reader to call OnVideoDecoded() on aCallback with one + // video sample. The decode should be performed asynchronously, and + // the callback can be performed on any thread. Don't hold the decoder + // monitor while calling this, as the implementation may try to wait + // on something that needs the monitor and deadlock. + // If aSkipToKeyframe is true, the decode should skip ahead to the + // the next keyframe at or after aTimeThreshold microseconds. + virtual void RequestVideoData(bool aSkipToNextKeyframe, + int64_t aTimeThreshold); virtual bool HasAudio() = 0; virtual bool HasVideo() = 0; @@ -65,6 +96,7 @@ public: virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) = 0; + // TODO: DEPRECATED. This uses synchronous decoding. // Stores the presentation time of the first frame we'd be able to play if // we started playback at the current position. Returns the first video // frame, if we have video. @@ -98,22 +130,6 @@ public: mIgnoreAudioOutputFormat = true; } -protected: - // Queue of audio frames. This queue is threadsafe, and is accessed from - // the audio, decoder, state machine, and main threads. - MediaQueue mAudioQueue; - - // Queue of video frames. This queue is threadsafe, and is accessed from - // the decoder, state machine, and main threads. - MediaQueue mVideoQueue; - - // An adapter to the audio queue which first copies data to buffers with - // minimal allocation slop and then pushes them to the queue. This is - // useful for decoders working with formats that give awkward numbers of - // frames such as mp3. - AudioCompactor mAudioCompactor; - -public: // Populates aBuffered with the time ranges which are buffered. aStartTime // must be the presentation time of the first frame in the media, e.g. // the media time corresponding to playback time/position 0. This function @@ -156,15 +172,51 @@ public: AudioData* DecodeToFirstAudioData(); VideoData* DecodeToFirstVideoData(); - // Decodes samples until we reach frames required to play at time aTarget - // (usecs). This also trims the samples to start exactly at aTarget, - // by discarding audio samples and adjusting start times of video frames. - nsresult DecodeToTarget(int64_t aTarget); - MediaInfo GetMediaInfo() { return mInfo; } protected: + // Overrides of this function should decodes an unspecified amount of + // audio data, enqueuing the audio data in mAudioQueue. Returns true + // when there's more audio to decode, false if the audio is finished, + // end of file has been reached, or an un-recoverable read error has + // occured. This function blocks until the decode is complete. + virtual bool DecodeAudioData() { + return false; + } + + // Overrides of this function should read and decodes one video frame. + // Packets with a timestamp less than aTimeThreshold will be decoded + // (unless they're not keyframes and aKeyframeSkip is true), but will + // not be added to the queue. This function blocks until the decode + // is complete. + virtual bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) { + return false; + } + + RequestSampleCallback* GetCallback() { + MOZ_ASSERT(mSampleDecodedCallback); + return mSampleDecodedCallback; + } + + virtual MediaTaskQueue* GetTaskQueue() { + return mTaskQueue; + } + + // Queue of audio frames. This queue is threadsafe, and is accessed from + // the audio, decoder, state machine, and main threads. + MediaQueue mAudioQueue; + + // Queue of video frames. This queue is threadsafe, and is accessed from + // the decoder, state machine, and main threads. + MediaQueue mVideoQueue; + + // An adapter to the audio queue which first copies data to buffers with + // minimal allocation slop and then pushes them to the queue. This is + // useful for decoders working with formats that give awkward numbers of + // frames such as mp3. + AudioCompactor mAudioCompactor; + // Reference to the owning decoder object. AbstractMediaDecoder* mDecoder; @@ -175,6 +227,82 @@ protected: // directly, because they have a number of channel higher than // what we support. bool mIgnoreAudioOutputFormat; + +private: + + nsRefPtr mSampleDecodedCallback; + + nsRefPtr mTaskQueue; + + // Flags whether a the next audio/video sample comes after a "gap" or + // "discontinuity" in the stream. For example after a seek. + bool mAudioDiscontinuity; + bool mVideoDiscontinuity; +}; + +// Interface that callers to MediaDecoderReader::Request{Audio,Video}Data() +// must implement to receive the requested samples asynchronously. +// This object is refcounted, and cycles must be broken by calling +// BreakCycles() during shutdown. +class RequestSampleCallback { +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RequestSampleCallback) + + // Receives the result of a RequestAudioData() call. + virtual void OnAudioDecoded(AudioData* aSample) = 0; + + // Called when a RequestAudioData() call can't be fulfiled as we've + // reached the end of stream. + virtual void OnAudioEOS() = 0; + + // Receives the result of a RequestVideoData() call. + virtual void OnVideoDecoded(VideoData* aSample) = 0; + + // Called when a RequestVideoData() call can't be fulfiled as we've + // reached the end of stream. + virtual void OnVideoEOS() = 0; + + // Called when there's a decode error. No more sample requests + // will succeed. + virtual void OnDecodeError() = 0; + + // Called during shutdown to break any reference cycles. + virtual void BreakCycles() = 0; + + virtual ~RequestSampleCallback() {} +}; + +// A RequestSampleCallback implementation that can be passed to the +// MediaDecoderReader to block the thread requesting an audio sample until +// the audio decode is complete. This is used to adapt the asynchronous +// model of the MediaDecoderReader to a synchronous model. +class AudioDecodeRendezvous : public RequestSampleCallback { +public: + AudioDecodeRendezvous(); + ~AudioDecodeRendezvous(); + + // RequestSampleCallback implementation. Called when decode is complete. + // Note: aSample is null at end of stream. + virtual void OnAudioDecoded(AudioData* aSample) MOZ_OVERRIDE; + virtual void OnAudioEOS() MOZ_OVERRIDE; + virtual void OnVideoDecoded(VideoData* aSample) MOZ_OVERRIDE {} + virtual void OnVideoEOS() MOZ_OVERRIDE {} + virtual void OnDecodeError() MOZ_OVERRIDE; + virtual void BreakCycles() MOZ_OVERRIDE {}; + void Reset(); + + // Returns failure on error, or NS_OK. + // If *aSample is null, EOS has been reached. + nsresult Await(nsAutoPtr& aSample); + + // Interrupts a call to Wait(). + void Cancel(); + +private: + Monitor mMonitor; + nsresult mStatus; + nsAutoPtr mSample; + bool mHaveResult; }; } // namespace mozilla diff --git a/content/media/MediaDecoderStateMachine.cpp b/content/media/MediaDecoderStateMachine.cpp index e7e5744bb020..e6e4c4191639 100644 --- a/content/media/MediaDecoderStateMachine.cpp +++ b/content/media/MediaDecoderStateMachine.cpp @@ -57,9 +57,16 @@ extern PRLogModuleInfo* gMediaDecoderLog; DECODER_LOG(PR_LOG_DEBUG, msg, ##__VA_ARGS__); \ } \ PR_END_MACRO +#define SAMPLE_LOG(msg, ...) \ + PR_BEGIN_MACRO \ + if (PR_GetEnv("MEDIA_LOG_SAMPLES")) { \ + DECODER_LOG(PR_LOG_DEBUG, msg, ##__VA_ARGS__); \ + } \ + PR_END_MACRO #else #define DECODER_LOG(type, msg, ...) #define VERBOSE_LOG(msg, ...) +#define SAMPLE_LOG(msg, ...) #endif // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to @@ -192,8 +199,8 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, mAmpleVideoFrames(2), mLowAudioThresholdUsecs(LOW_AUDIO_USECS), mAmpleAudioThresholdUsecs(AMPLE_AUDIO_USECS), - mDispatchedAudioDecodeTask(false), - mDispatchedVideoDecodeTask(false), + mAudioRequestPending(false), + mVideoRequestPending(false), mAudioCaptured(false), mTransportSeekable(true), mMediaSeekable(true), @@ -207,7 +214,9 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, mDecodeThreadWaiting(false), mRealTime(aRealTime), mDispatchedDecodeMetadataTask(false), - mDispatchedDecodeSeekTask(false), + mDropAudioUntilNextDiscontinuity(false), + mDropVideoUntilNextDiscontinuity(false), + mDecodeToSeekTarget(false), mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED), mTimerId(0) { @@ -557,91 +566,68 @@ MediaDecoderStateMachine::NeedToDecodeVideo() AssertCurrentThreadInMonitor(); NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(), "Should be on state machine or decode thread."); - return mIsVideoDecoding && - !mMinimizePreroll && - !HaveEnoughDecodedVideo(); + return IsVideoDecoding() && + ((mState == DECODER_STATE_SEEKING && mDecodeToSeekTarget) || + (!mMinimizePreroll && !HaveEnoughDecodedVideo())); } void MediaDecoderStateMachine::DecodeVideo() { - ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); - NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); - - if (mState != DECODER_STATE_DECODING && mState != DECODER_STATE_BUFFERING) { - mDispatchedVideoDecodeTask = false; - return; - } - - // We don't want to consider skipping to the next keyframe if we've - // only just started up the decode loop, so wait until we've decoded - // some frames before enabling the keyframe skip logic on video. - if (mIsVideoPrerolling && - (static_cast(VideoQueue().GetSize()) - >= mVideoPrerollFrames * mPlaybackRate)) + int64_t currentTime = 0; + bool skipToNextKeyFrame = false; { - mIsVideoPrerolling = false; - } + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); - // We'll skip the video decode to the nearest keyframe if we're low on - // audio, or if we're low on video, provided we're not running low on - // data to decode. If we're running low on downloaded data to decode, - // we won't start keyframe skipping, as we'll be pausing playback to buffer - // soon anyway and we'll want to be able to display frames immediately - // after buffering finishes. - if (mState == DECODER_STATE_DECODING && - !mSkipToNextKeyFrame && - mIsVideoDecoding && - ((!mIsAudioPrerolling && mIsAudioDecoding && - GetDecodedAudioDuration() < mLowAudioThresholdUsecs * mPlaybackRate) || - (!mIsVideoPrerolling && mIsVideoDecoding && - // don't skip frame when |clock time| <= |mVideoFrameEndTime| for - // we are still in the safe range without underrunning video frames - GetClock() > mVideoFrameEndTime && + if (mState != DECODER_STATE_DECODING && + mState != DECODER_STATE_BUFFERING && + mState != DECODER_STATE_SEEKING) { + mVideoRequestPending = false; + DispatchDecodeTasksIfNeeded(); + return; + } + + // We don't want to consider skipping to the next keyframe if we've + // only just started up the decode loop, so wait until we've decoded + // some frames before enabling the keyframe skip logic on video. + if (mIsVideoPrerolling && (static_cast(VideoQueue().GetSize()) - < LOW_VIDEO_FRAMES * mPlaybackRate))) && - !HasLowUndecodedData()) - { - mSkipToNextKeyFrame = true; - DECODER_LOG(PR_LOG_DEBUG, "Skipping video decode to the next keyframe"); + >= mVideoPrerollFrames * mPlaybackRate)) + { + mIsVideoPrerolling = false; + } + + // We'll skip the video decode to the nearest keyframe if we're low on + // audio, or if we're low on video, provided we're not running low on + // data to decode. If we're running low on downloaded data to decode, + // we won't start keyframe skipping, as we'll be pausing playback to buffer + // soon anyway and we'll want to be able to display frames immediately + // after buffering finishes. + if (mState == DECODER_STATE_DECODING && + mIsVideoDecoding && + ((!mIsAudioPrerolling && mIsAudioDecoding && + GetDecodedAudioDuration() < mLowAudioThresholdUsecs * mPlaybackRate) || + (!mIsVideoPrerolling && IsVideoDecoding() && + // don't skip frame when |clock time| <= |mVideoFrameEndTime| for + // we are still in the safe range without underrunning video frames + GetClock() > mVideoFrameEndTime && + (static_cast(VideoQueue().GetSize()) + < LOW_VIDEO_FRAMES * mPlaybackRate))) && + !HasLowUndecodedData()) + { + skipToNextKeyFrame = true; + DECODER_LOG(PR_LOG_DEBUG, "Skipping video decode to the next keyframe"); + } + currentTime = mState == DECODER_STATE_SEEKING ? 0 : GetMediaTime(); + + // Time the video decode, so that if it's slow, we can increase our low + // audio threshold to reduce the chance of an audio underrun while we're + // waiting for a video decode to complete. + mVideoDecodeStartTime = TimeStamp::Now(); } - // Time the video decode, so that if it's slow, we can increase our low - // audio threshold to reduce the chance of an audio underrun while we're - // waiting for a video decode to complete. - TimeDuration decodeTime; - { - int64_t currentTime = GetMediaTime(); - ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); - TimeStamp start = TimeStamp::Now(); - mIsVideoDecoding = mReader->DecodeVideoFrame(mSkipToNextKeyFrame, currentTime); - decodeTime = TimeStamp::Now() - start; - } - if (!mIsVideoDecoding) { - // Playback ended for this stream, close the sample queue. - VideoQueue().Finish(); - CheckIfDecodeComplete(); - } - - if (THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs && - !HasLowUndecodedData()) - { - mLowAudioThresholdUsecs = - std::min(THRESHOLD_FACTOR * DurationToUsecs(decodeTime), AMPLE_AUDIO_USECS); - mAmpleAudioThresholdUsecs = std::max(THRESHOLD_FACTOR * mLowAudioThresholdUsecs, - mAmpleAudioThresholdUsecs); - DECODER_LOG(PR_LOG_DEBUG, "Slow video decode, set mLowAudioThresholdUsecs=%lld mAmpleAudioThresholdUsecs=%lld", - mLowAudioThresholdUsecs, mAmpleAudioThresholdUsecs); - } - - SendStreamData(); - - // The ready state can change when we've decoded data, so update the - // ready state, so that DOM events can fire. - UpdateReadyState(); - - mDispatchedVideoDecodeTask = false; - DispatchDecodeTasksIfNeeded(); + mReader->RequestVideoData(skipToNextKeyFrame, currentTime); } bool @@ -650,52 +636,390 @@ MediaDecoderStateMachine::NeedToDecodeAudio() AssertCurrentThreadInMonitor(); NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(), "Should be on state machine or decode thread."); - return mIsAudioDecoding && - !mMinimizePreroll && - !HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate); + return IsAudioDecoding() && + ((mState == DECODER_STATE_SEEKING && mDecodeToSeekTarget) || + (!mMinimizePreroll && + !HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate) && + (mState != DECODER_STATE_SEEKING || mDecodeToSeekTarget))); } void MediaDecoderStateMachine::DecodeAudio() { - ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); - NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); + { + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); - if (mState != DECODER_STATE_DECODING && mState != DECODER_STATE_BUFFERING) { - mDispatchedAudioDecodeTask = false; + if (mState != DECODER_STATE_DECODING && + mState != DECODER_STATE_BUFFERING && + mState != DECODER_STATE_SEEKING) { + mAudioRequestPending = false; + DispatchDecodeTasksIfNeeded(); + mon.NotifyAll(); + return; + } + + // We don't want to consider skipping to the next keyframe if we've + // only just started up the decode loop, so wait until we've decoded + // some audio data before enabling the keyframe skip logic on audio. + if (mIsAudioPrerolling && + GetDecodedAudioDuration() >= mAudioPrerollUsecs * mPlaybackRate) { + mIsAudioPrerolling = false; + } + } + mReader->RequestAudioData(); +} + +bool +MediaDecoderStateMachine::IsAudioSeekComplete() +{ + AssertCurrentThreadInMonitor(); + SAMPLE_LOG("IsAudioSeekComplete() curTarVal=%d mAudDis=%d aqFin=%d aqSz=%d", + mCurrentSeekTarget.IsValid(), mDropAudioUntilNextDiscontinuity, AudioQueue().IsFinished(), AudioQueue().GetSize()); + return + !HasAudio() || + (mCurrentSeekTarget.IsValid() && + !mDropAudioUntilNextDiscontinuity && + (AudioQueue().IsFinished() || AudioQueue().GetSize() > 0)); +} + +bool +MediaDecoderStateMachine::IsVideoSeekComplete() +{ + AssertCurrentThreadInMonitor(); + SAMPLE_LOG("IsVideoSeekComplete() curTarVal=%d mVidDis=%d vqFin=%d vqSz=%d", + mCurrentSeekTarget.IsValid(), mDropVideoUntilNextDiscontinuity, VideoQueue().IsFinished(), VideoQueue().GetSize()); + return + !HasVideo() || + (mCurrentSeekTarget.IsValid() && + !mDropVideoUntilNextDiscontinuity && + (VideoQueue().IsFinished() || VideoQueue().GetSize() > 0)); +} + +void +MediaDecoderStateMachine::OnAudioEOS() +{ + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + SAMPLE_LOG("OnAudioEOS"); + mAudioRequestPending = false; + AudioQueue().Finish(); + switch (mState) { + case DECODER_STATE_DECODING_METADATA: { + MaybeFinishDecodeMetadata(); + return; + } + case DECODER_STATE_BUFFERING: + case DECODER_STATE_DECODING: { + CheckIfDecodeComplete(); + SendStreamData(); + // The ready state can change when we've decoded data, so update the + // ready state, so that DOM events can fire. + UpdateReadyState(); + mDecoder->GetReentrantMonitor().NotifyAll(); + return; + } + + case DECODER_STATE_SEEKING: { + if (!mCurrentSeekTarget.IsValid()) { + // We've received an EOS from a previous decode. Discard it. + return; + } + mDropAudioUntilNextDiscontinuity = false; + CheckIfSeekComplete(); + return; + } + default: { + // Ignore other cases. + return; + } + } +} + +void +MediaDecoderStateMachine::OnAudioDecoded(AudioData* aAudioSample) +{ + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + nsAutoPtr audio(aAudioSample); + MOZ_ASSERT(audio); + mAudioRequestPending = false; + + SAMPLE_LOG("OnAudioDecoded [%lld,%lld] disc=%d", + (audio ? audio->mTime : -1), + (audio ? audio->GetEndTime() : -1), + (audio ? audio->mDiscontinuity : 0)); + + switch (mState) { + case DECODER_STATE_DECODING_METADATA: { + Push(audio.forget()); + MaybeFinishDecodeMetadata(); + return; + } + + case DECODER_STATE_BUFFERING: + case DECODER_STATE_DECODING: { + // In buffering and decoding state, we simply enqueue samples. + Push(audio.forget()); + return; + } + + case DECODER_STATE_SEEKING: { + if (!mCurrentSeekTarget.IsValid()) { + // We've received a sample from a previous decode. Discard it. + return; + } + if (audio->mDiscontinuity) { + mDropAudioUntilNextDiscontinuity = false; + } + if (!mDropAudioUntilNextDiscontinuity) { + // We must be after the discontinuity; we're receiving samples + // at or after the seek target. + if (mCurrentSeekTarget.mType == SeekTarget::PrevSyncPoint) { + // Non-precise seek; we can stop the seek at the first sample. + AudioQueue().Push(audio.forget()); + } else { + // We're doing an accurate seek. We must discard + // MediaData up to the one containing exact seek target. + if (NS_FAILED(DropAudioUpToSeekTarget(audio.forget()))) { + DecodeError(); + return; + } + } + } + CheckIfSeekComplete(); + return; + } + default: { + // Ignore other cases. + return; + } + } +} + +void +MediaDecoderStateMachine::Push(AudioData* aSample) +{ + MOZ_ASSERT(aSample); + // TODO: Send aSample to MSG and recalculate readystate before pushing, + // otherwise AdvanceFrame may pop the sample before we have a chance + // to reach playing. + AudioQueue().Push(aSample); + if (mState > DECODER_STATE_DECODING_METADATA) { + SendStreamData(); + // The ready state can change when we've decoded data, so update the + // ready state, so that DOM events can fire. + UpdateReadyState(); + DispatchDecodeTasksIfNeeded(); + mDecoder->GetReentrantMonitor().NotifyAll(); + } +} + +void +MediaDecoderStateMachine::Push(VideoData* aSample) +{ + MOZ_ASSERT(aSample); + // TODO: Send aSample to MSG and recalculate readystate before pushing, + // otherwise AdvanceFrame may pop the sample before we have a chance + // to reach playing. + VideoQueue().Push(aSample); + if (mState > DECODER_STATE_DECODING_METADATA) { + SendStreamData(); + // The ready state can change when we've decoded data, so update the + // ready state, so that DOM events can fire. + UpdateReadyState(); + DispatchDecodeTasksIfNeeded(); + mDecoder->GetReentrantMonitor().NotifyAll(); + } +} + +void +MediaDecoderStateMachine::OnDecodeError() +{ + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + DecodeError(); +} + +void +MediaDecoderStateMachine::MaybeFinishDecodeMetadata() +{ + AssertCurrentThreadInMonitor(); + if ((IsAudioDecoding() && AudioQueue().GetSize() == 0) || + (IsVideoDecoding() && VideoQueue().GetSize() == 0)) { return; } + if (NS_FAILED(FinishDecodeMetadata())) { + DecodeError(); + } +} - // We don't want to consider skipping to the next keyframe if we've - // only just started up the decode loop, so wait until we've decoded - // some audio data before enabling the keyframe skip logic on audio. - if (mIsAudioPrerolling && - GetDecodedAudioDuration() >= mAudioPrerollUsecs * mPlaybackRate) { - mIsAudioPrerolling = false; +void +MediaDecoderStateMachine::OnVideoEOS() +{ + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + SAMPLE_LOG("OnVideoEOS"); + mVideoRequestPending = false; + switch (mState) { + case DECODER_STATE_DECODING_METADATA: { + VideoQueue().Finish(); + MaybeFinishDecodeMetadata(); + return; + } + + case DECODER_STATE_BUFFERING: + case DECODER_STATE_DECODING: { + VideoQueue().Finish(); + CheckIfDecodeComplete(); + SendStreamData(); + // The ready state can change when we've decoded data, so update the + // ready state, so that DOM events can fire. + UpdateReadyState(); + mDecoder->GetReentrantMonitor().NotifyAll(); + return; + } + case DECODER_STATE_SEEKING: { + if (!mCurrentSeekTarget.IsValid()) { + // We've received a sample from a previous decode. Discard it. + return; + } + // Null sample. Hit end of stream. If we have decoded a frame, + // insert it into the queue so that we have something to display. + if (mFirstVideoFrameAfterSeek) { + VideoQueue().Push(mFirstVideoFrameAfterSeek.forget()); + } + VideoQueue().Finish(); + mDropVideoUntilNextDiscontinuity = false; + CheckIfSeekComplete(); + return; + } + default: { + // Ignore other cases. + return; + } + } +} + +void +MediaDecoderStateMachine::OnVideoDecoded(VideoData* aVideoSample) +{ + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + nsAutoPtr video(aVideoSample); + mVideoRequestPending = false; + + SAMPLE_LOG("OnVideoDecoded [%lld,%lld] disc=%d", + (video ? video->mTime : -1), + (video ? video->GetEndTime() : -1), + (video ? video->mDiscontinuity : 0)); + + switch (mState) { + case DECODER_STATE_DECODING_METADATA: { + Push(video.forget()); + MaybeFinishDecodeMetadata(); + return; + } + + case DECODER_STATE_BUFFERING: + case DECODER_STATE_DECODING: { + Push(video.forget()); + // If the requested video sample was slow to arrive, increase the + // amount of audio we buffer to ensure that we don't run out of audio. + // TODO: Detect when we're truly async, and don't do this if so, as + // it's not necessary. + TimeDuration decodeTime = TimeStamp::Now() - mVideoDecodeStartTime; + if (THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs && + !HasLowUndecodedData()) + { + mLowAudioThresholdUsecs = + std::min(THRESHOLD_FACTOR * DurationToUsecs(decodeTime), AMPLE_AUDIO_USECS); + mAmpleAudioThresholdUsecs = std::max(THRESHOLD_FACTOR * mLowAudioThresholdUsecs, + mAmpleAudioThresholdUsecs); + DECODER_LOG(PR_LOG_DEBUG, "Slow video decode, set mLowAudioThresholdUsecs=%lld mAmpleAudioThresholdUsecs=%lld", + mLowAudioThresholdUsecs, mAmpleAudioThresholdUsecs); + } + return; + } + case DECODER_STATE_SEEKING: { + if (!mCurrentSeekTarget.IsValid()) { + // We've received a sample from a previous decode. Discard it. + return; + } + if (mDropVideoUntilNextDiscontinuity) { + if (video->mDiscontinuity) { + mDropVideoUntilNextDiscontinuity = false; + } + } + if (!mDropVideoUntilNextDiscontinuity) { + // We must be after the discontinuity; we're receiving samples + // at or after the seek target. + if (mCurrentSeekTarget.mType == SeekTarget::PrevSyncPoint) { + // Non-precise seek; we can stop the seek at the first sample. + VideoQueue().Push(video.forget()); + } else { + // We're doing an accurate seek. We still need to discard + // MediaData up to the one containing exact seek target. + if (NS_FAILED(DropVideoUpToSeekTarget(video.forget()))) { + DecodeError(); + return; + } + } + } + CheckIfSeekComplete(); + return; + } + default: { + // Ignore other cases. + return; + } + } +} + +void +MediaDecoderStateMachine::CheckIfSeekComplete() +{ + AssertCurrentThreadInMonitor(); + + const bool videoSeekComplete = IsVideoSeekComplete(); + if (HasVideo() && !videoSeekComplete) { + // We haven't reached the target. Ensure we have requested another sample. + if (NS_FAILED(EnsureVideoDecodeTaskQueued())) { + NS_WARNING("Failed to request video during seek"); + DecodeError(); + } } - { - ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); - mIsAudioDecoding = mReader->DecodeAudioData(); - } - if (!mIsAudioDecoding) { - // Playback ended for this stream, close the sample queue. - AudioQueue().Finish(); - CheckIfDecodeComplete(); + const bool audioSeekComplete = IsAudioSeekComplete(); + if (HasAudio() && !audioSeekComplete) { + // We haven't reached the target. Ensure we have requested another sample. + if (NS_FAILED(EnsureAudioDecodeTaskQueued())) { + NS_WARNING("Failed to request audio during seek"); + DecodeError(); + } } - SendStreamData(); + SAMPLE_LOG("CheckIfSeekComplete() audioSeekComplete=%d videoSeekComplete=%d", + audioSeekComplete, videoSeekComplete); - // Notify to ensure that the AudioLoop() is not waiting, in case it was - // waiting for more audio to be decoded. - mDecoder->GetReentrantMonitor().NotifyAll(); + if (audioSeekComplete && videoSeekComplete) { + mDecodeToSeekTarget = false; + RefPtr task( + NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted)); + nsresult rv = mDecodeTaskQueue->Dispatch(task); + if (NS_FAILED(rv)) { + DecodeError(); + } + } +} - // The ready state can change when we've decoded data, so update the - // ready state, so that DOM events can fire. - UpdateReadyState(); +bool +MediaDecoderStateMachine::IsAudioDecoding() +{ + AssertCurrentThreadInMonitor(); + return HasAudio() && !AudioQueue().IsFinished(); +} - mDispatchedAudioDecodeTask = false; - DispatchDecodeTasksIfNeeded(); +bool +MediaDecoderStateMachine::IsVideoDecoding() +{ + AssertCurrentThreadInMonitor(); + return HasVideo() && !VideoQueue().IsFinished(); } void @@ -709,9 +1033,7 @@ MediaDecoderStateMachine::CheckIfDecodeComplete() // since we don't want to abort the shutdown or seek processes. return; } - MOZ_ASSERT(!AudioQueue().IsFinished() || !mIsAudioDecoding); - MOZ_ASSERT(!VideoQueue().IsFinished() || !mIsVideoDecoding); - if (!mIsVideoDecoding && !mIsAudioDecoding) { + if (!IsVideoDecoding() && !IsAudioDecoding()) { // We've finished decoding all active streams, // so move to COMPLETED state. mState = DECODER_STATE_COMPLETED; @@ -1014,9 +1336,7 @@ nsresult MediaDecoderStateMachine::Init(MediaDecoderStateMachine* aCloneDonor) { MOZ_ASSERT(NS_IsMainThread()); - RefPtr decodePool( - SharedThreadPool::Get(NS_LITERAL_CSTRING("Media Decode"), - Preferences::GetUint("media.num-decode-threads", 25))); + RefPtr decodePool(GetMediaDecodeThreadPool()); NS_ENSURE_TRUE(decodePool, NS_ERROR_FAILURE); RefPtr stateMachinePool( @@ -1039,7 +1359,17 @@ nsresult MediaDecoderStateMachine::Init(MediaDecoderStateMachine* aCloneDonor) rv = mTimer->SetTarget(GetStateMachineThread()); NS_ENSURE_SUCCESS(rv, rv); - return mReader->Init(cloneReader); + // Note: This creates a cycle, broken in shutdown. + mMediaDecodedListener = + new MediaDataDecodedListener(this, + mDecodeTaskQueue); + mReader->SetCallback(mMediaDecodedListener); + mReader->SetTaskQueue(mDecodeTaskQueue); + + rv = mReader->Init(cloneReader); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; } void MediaDecoderStateMachine::StopPlayback() @@ -1101,10 +1431,12 @@ void MediaDecoderStateMachine::StartPlayback() } mDecoder->GetReentrantMonitor().NotifyAll(); mDecoder->UpdateStreamBlockingForStateMachinePlaying(); + DispatchDecodeTasksIfNeeded(); } void MediaDecoderStateMachine::UpdatePlaybackPositionInternal(int64_t aTime) { + SAMPLE_LOG("UpdatePlaybackPositionInternal(%lld) (mStartTime=%lld)", aTime, mStartTime); NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(), "Should be on state machine thread."); AssertCurrentThreadInMonitor(); @@ -1317,18 +1649,12 @@ void MediaDecoderStateMachine::StartDecoding() mDecodeStartTime = TimeStamp::Now(); - // Reset our "stream finished decoding" flags, so we try to decode all - // streams that we have when we start decoding. - mIsVideoDecoding = HasVideo() && !VideoQueue().IsFinished(); - mIsAudioDecoding = HasAudio() && !AudioQueue().IsFinished(); - CheckIfDecodeComplete(); if (mState == DECODER_STATE_COMPLETED) { return; } // Reset other state to pristine values before starting decode. - mSkipToNextKeyFrame = false; mIsAudioPrerolling = true; mIsVideoPrerolling = true; @@ -1380,10 +1706,16 @@ void MediaDecoderStateMachine::Play() void MediaDecoderStateMachine::ResetPlayback() { NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); + MOZ_ASSERT(mState == DECODER_STATE_SEEKING); mVideoFrameEndTime = -1; mAudioStartTime = -1; mAudioEndTime = -1; mAudioCompleted = false; + AudioQueue().Reset(); + VideoQueue().Reset(); + mFirstVideoFrameAfterSeek = nullptr; + mDropAudioUntilNextDiscontinuity = true; + mDropVideoUntilNextDiscontinuity = true; } void MediaDecoderStateMachine::NotifyDataArrived(const char* aBuffer, @@ -1483,8 +1815,9 @@ MediaDecoderStateMachine::EnqueueDecodeMetadataTask() mDispatchedDecodeMetadataTask) { return NS_OK; } - nsresult rv = mDecodeTaskQueue->Dispatch( + RefPtr task( NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeMetadata)); + nsresult rv = mDecodeTaskQueue->Dispatch(task); if (NS_SUCCEEDED(rv)) { mDispatchedDecodeMetadataTask = true; } else { @@ -1515,6 +1848,12 @@ MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded() { AssertCurrentThreadInMonitor(); + if (mState != DECODER_STATE_DECODING && + mState != DECODER_STATE_BUFFERING && + mState != DECODER_STATE_SEEKING) { + return; + } + // NeedToDecodeAudio() can go from false to true while we hold the // monitor, but it can't go from true to false. This can happen because // NeedToDecodeAudio() takes into account the amount of decoded audio @@ -1548,6 +1887,11 @@ MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded() EnsureVideoDecodeTaskQueued(); } + SAMPLE_LOG("DispatchDecodeTasksIfNeeded needAudio=%d dispAudio=%d needVideo=%d dispVid=%d needIdle=%d", + needToDecodeAudio, mAudioRequestPending, + needToDecodeVideo, mVideoRequestPending, + needIdle); + if (needIdle) { RefPtr event = NS_NewRunnableMethod( this, &MediaDecoderStateMachine::SetReaderIdle); @@ -1566,15 +1910,22 @@ MediaDecoderStateMachine::EnqueueDecodeSeekTask() AssertCurrentThreadInMonitor(); if (mState != DECODER_STATE_SEEKING || - mDispatchedDecodeSeekTask) { + !mSeekTarget.IsValid() || + mCurrentSeekTarget.IsValid()) { return NS_OK; } - nsresult rv = mDecodeTaskQueue->Dispatch( + mCurrentSeekTarget = mSeekTarget; + mSeekTarget.Reset(); + mDropAudioUntilNextDiscontinuity = HasAudio(); + mDropVideoUntilNextDiscontinuity = HasVideo(); + + RefPtr task( NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeSeek)); - if (NS_SUCCEEDED(rv)) { - mDispatchedDecodeSeekTask = true; - } else { + nsresult rv = mDecodeTaskQueue->Dispatch(task); + if (NS_FAILED(rv)) { NS_WARNING("Dispatch DecodeSeek task failed."); + mCurrentSeekTarget.Reset(); + DecodeError(); } return rv; } @@ -1596,21 +1947,25 @@ MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded() nsresult MediaDecoderStateMachine::EnsureAudioDecodeTaskQueued() { - ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + AssertCurrentThreadInMonitor(); NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(), "Should be on state machine or decode thread."); + SAMPLE_LOG("EnsureAudioDecodeTaskQueued isDecoding=%d dispatched=%d", + IsAudioDecoding(), mAudioRequestPending); + if (mState >= DECODER_STATE_COMPLETED) { return NS_OK; } MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA); - if (mIsAudioDecoding && !mDispatchedAudioDecodeTask) { - nsresult rv = mDecodeTaskQueue->Dispatch( + if (IsAudioDecoding() && !mAudioRequestPending) { + RefPtr task( NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeAudio)); + nsresult rv = mDecodeTaskQueue->Dispatch(task); if (NS_SUCCEEDED(rv)) { - mDispatchedAudioDecodeTask = true; + mAudioRequestPending = true; } else { NS_WARNING("Failed to dispatch task to decode audio"); } @@ -1636,7 +1991,11 @@ MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded() nsresult MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued() { - ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + AssertCurrentThreadInMonitor(); + + SAMPLE_LOG("EnsureVideoDecodeTaskQueued isDecoding=%d dispatched=%d", + IsVideoDecoding(), mVideoRequestPending); + NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(), "Should be on state machine or decode thread."); @@ -1646,11 +2005,12 @@ MediaDecoderStateMachine::EnsureVideoDecodeTaskQueued() MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA); - if (mIsVideoDecoding && !mDispatchedVideoDecodeTask) { - nsresult rv = mDecodeTaskQueue->Dispatch( + if (IsVideoDecoding() && !mVideoRequestPending) { + RefPtr task( NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeVideo)); + nsresult rv = mDecodeTaskQueue->Dispatch(task); if (NS_SUCCEEDED(rv)) { - mDispatchedVideoDecodeTask = true; + mVideoRequestPending = true; } else { NS_WARNING("Failed to dispatch task to decode video"); } @@ -1707,12 +2067,8 @@ bool MediaDecoderStateMachine::HasLowDecodedData(int64_t aAudioUsecs) // provided we've not decoded to the end of the audio stream, or // if we're low on video frames, provided // we've not decoded to the end of the video stream. - return ((HasAudio() && - !AudioQueue().IsFinished() && - AudioDecodedUsecs() < aAudioUsecs) - || - (HasVideo() && - !VideoQueue().IsFinished() && + return ((IsAudioDecoding() && AudioDecodedUsecs() < aAudioUsecs) || + (IsVideoDecoding() && static_cast(VideoQueue().GetSize()) < LOW_VIDEO_FRAMES)); } @@ -1751,10 +2107,15 @@ MediaDecoderStateMachine::DecodeError() AssertCurrentThreadInMonitor(); NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); + if (mState == DECODER_STATE_SHUTDOWN) { + // Already shutdown. + return; + } + // Change state to shutdown before sending error report to MediaDecoder // and the HTMLMediaElement, so that our pipeline can start exiting // cleanly during the sync dispatch below. - DECODER_LOG(PR_LOG_WARNING, "Decode error, changed state to SHUTDOWN"); + DECODER_LOG(PR_LOG_WARNING, "Decode error, changed state to SHUTDOWN due to error"); ScheduleStateMachine(); mState = DECODER_STATE_SHUTDOWN; mDecoder->GetReentrantMonitor().NotifyAll(); @@ -1798,17 +2159,17 @@ nsresult MediaDecoderStateMachine::DecodeMetadata() nsresult res; MediaInfo info; - MetadataTags* tags; { ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); - res = mReader->ReadMetadata(&info, &tags); + res = mReader->ReadMetadata(&info, getter_Transfers(mMetadataTags)); } - if (NS_SUCCEEDED(res) && - mState == DECODER_STATE_DECODING_METADATA && - mReader->IsWaitingMediaResources()) { - // change state to DECODER_STATE_WAIT_FOR_RESOURCES - StartWaitForResources(); - return NS_OK; + if (NS_SUCCEEDED(res)) { + if (mState == DECODER_STATE_DECODING_METADATA && + mReader->IsWaitingMediaResources()) { + // change state to DECODER_STATE_WAIT_FOR_RESOURCES + StartWaitForResources(); + return NS_OK; + } } mInfo = info; @@ -1819,16 +2180,67 @@ nsresult MediaDecoderStateMachine::DecodeMetadata() mDecoder->StartProgressUpdates(); mGotDurationFromMetaData = (GetDuration() != -1); - VideoData* videoData = FindStartTime(); - if (videoData) { - ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); - RenderVideoFrame(videoData, TimeStamp::Now()); + if (HasAudio()) { + RefPtr decodeTask( + NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded)); + AudioQueue().AddPopListener(decodeTask, mDecodeTaskQueue); } + if (HasVideo()) { + RefPtr decodeTask( + NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded)); + VideoQueue().AddPopListener(decodeTask, mDecodeTaskQueue); + } + + if (mRealTime) { + SetStartTime(0); + res = FinishDecodeMetadata(); + NS_ENSURE_SUCCESS(res, res); + } else { + if (HasAudio()) { + ReentrantMonitorAutoExit unlock(mDecoder->GetReentrantMonitor()); + mReader->RequestAudioData(); + } + if (HasVideo()) { + ReentrantMonitorAutoExit unlock(mDecoder->GetReentrantMonitor()); + mReader->RequestVideoData(false, 0); + } + } + + return NS_OK; +} + +nsresult +MediaDecoderStateMachine::FinishDecodeMetadata() +{ + AssertCurrentThreadInMonitor(); + NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); + DECODER_LOG(PR_LOG_DEBUG, "Decoding Media Headers"); if (mState == DECODER_STATE_SHUTDOWN) { return NS_ERROR_FAILURE; } + if (!mRealTime) { + + const VideoData* v = VideoQueue().PeekFront(); + const AudioData* a = AudioQueue().PeekFront(); + + int64_t startTime = std::min(a ? a->mTime : INT64_MAX, + v ? v->mTime : INT64_MAX); + if (startTime == INT64_MAX) { + startTime = 0; + } + DECODER_LOG(PR_LOG_DEBUG, "DecodeMetadata first video frame start %lld", + v ? v->mTime : -1); + DECODER_LOG(PR_LOG_DEBUG, "DecodeMetadata first audio frame start %lld", + a ? a->mTime : -1); + SetStartTime(startTime); + if (VideoQueue().GetSize()) { + ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); + RenderVideoFrame(VideoQueue().PeekFront(), TimeStamp::Now()); + } + } + NS_ASSERTION(mStartTime != -1, "Must have start time"); MOZ_ASSERT((!HasVideo() && !HasAudio()) || !(mMediaSeekable && mTransportSeekable) || mEndTime != -1, @@ -1854,26 +2266,15 @@ nsresult MediaDecoderStateMachine::DecodeMetadata() mInfo.mAudio.mRate, HasAudio(), HasVideo(), - tags); - NS_DispatchToMainThread(metadataLoadedEvent); - - if (HasAudio()) { - RefPtr decodeTask( - NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded)); - AudioQueue().AddPopListener(decodeTask, mDecodeTaskQueue); - } - if (HasVideo()) { - RefPtr decodeTask( - NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded)); - VideoQueue().AddPopListener(decodeTask, mDecodeTaskQueue); - } + mMetadataTags.forget()); + NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL); if (mState == DECODER_STATE_DECODING_METADATA) { DECODER_LOG(PR_LOG_DEBUG, "Changed state from DECODING_METADATA to DECODING"); StartDecoding(); } - // For very short media FindStartTime() can decode the entire media. + // For very short media the metadata decode can decode the entire media. // So we need to check if this has occurred, else our decode pipeline won't // run (since it doesn't need to) and we won't detect end of stream. CheckIfDecodeComplete(); @@ -1892,7 +2293,6 @@ void MediaDecoderStateMachine::DecodeSeek() { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); - AutoSetOnScopeExit unsetOnExit(mDispatchedDecodeSeekTask, false); if (mState != DECODER_STATE_SEEKING) { return; } @@ -1905,7 +2305,7 @@ void MediaDecoderStateMachine::DecodeSeek() // the lock since it won't deadlock. We check the state when // acquiring the lock again in case shutdown has occurred // during the time when we didn't have the lock. - int64_t seekTime = mSeekTarget.mTime; + int64_t seekTime = mCurrentSeekTarget.mTime; mDecoder->StopProgressUpdates(); bool currentTimeChanged = false; @@ -1934,63 +2334,94 @@ void MediaDecoderStateMachine::DecodeSeek() NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStarted); NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC); } + if (mState != DECODER_STATE_SEEKING) { + // May have shutdown while we released the monitor. + return; + } - int64_t newCurrentTime = seekTime; - if (currentTimeChanged) { + if (!currentTimeChanged) { + DECODER_LOG(PR_LOG_DEBUG, "Seek !currentTimeChanged..."); + mDecodeToSeekTarget = false; + nsresult rv = mDecodeTaskQueue->Dispatch( + NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted)); + if (NS_FAILED(rv)) { + DecodeError(); + } + } else { // The seek target is different than the current playback position, // we'll need to seek the playback position, so shutdown our decode // and audio threads. StopAudioThread(); ResetPlayback(); + nsresult res; { ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); - // Now perform the seek. We must not hold the state machine monitor - // while we seek, since the seek reads, which could block on I/O. - res = mReader->Seek(seekTime, - mStartTime, - mEndTime, - mediaTime); - - if (NS_SUCCEEDED(res) && mSeekTarget.mType == SeekTarget::Accurate) { - res = mReader->DecodeToTarget(seekTime); + // We must not hold the state machine monitor while we call into + // the reader, since it could do I/O or deadlock some other way. + res = mReader->ResetDecode(); + if (NS_SUCCEEDED(res)) { + res = mReader->Seek(seekTime, + mStartTime, + mEndTime, + mediaTime); } } + if (NS_FAILED(res)) { + DecodeError(); + return; + } - if (NS_SUCCEEDED(res)) { - int64_t nextSampleStartTime = 0; - VideoData* video = nullptr; + // We must decode the first samples of active streams, so we can determine + // the new stream time. So dispatch tasks to do that. + mDecodeToSeekTarget = true; + DispatchDecodeTasksIfNeeded(); + } +} + +void +MediaDecoderStateMachine::SeekCompleted() +{ + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + + // We must reset the seek target when exiting this function, but not + // before, as if we dropped the monitor in any function called here, + // we may begin a new seek on the state machine thread, and be in + // an inconsistent state. + AutoSetOnScopeExit reset(mCurrentSeekTarget, SeekTarget()); + + NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); + if (mState != DECODER_STATE_SEEKING) { + return; + } + + int64_t seekTime = mCurrentSeekTarget.mTime; + int64_t newCurrentTime = mCurrentSeekTarget.mTime; + + // Setup timestamp state. + VideoData* video = VideoQueue().PeekFront(); + if (seekTime == mEndTime) { + newCurrentTime = mAudioStartTime = seekTime; + } else if (HasAudio()) { + AudioData* audio = AudioQueue().PeekFront(); + newCurrentTime = mAudioStartTime = audio ? audio->mTime : seekTime; + } else { + newCurrentTime = video ? video->mTime : seekTime; + } + mPlayDuration = newCurrentTime - mStartTime; + + if (HasVideo()) { + if (video) { { ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); - video = mReader->FindStartTime(nextSampleStartTime); + RenderVideoFrame(video, TimeStamp::Now()); } - - // Setup timestamp state. - if (seekTime == mEndTime) { - newCurrentTime = mAudioStartTime = seekTime; - } else if (HasAudio()) { - AudioData* audio = AudioQueue().PeekFront(); - newCurrentTime = mAudioStartTime = audio ? audio->mTime : seekTime; - } else { - newCurrentTime = video ? video->mTime : seekTime; - } - mPlayDuration = newCurrentTime - mStartTime; - - if (HasVideo()) { - if (video) { - { - ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); - RenderVideoFrame(video, TimeStamp::Now()); - } - nsCOMPtr event = - NS_NewRunnableMethod(mDecoder, &MediaDecoder::Invalidate); - NS_DispatchToMainThread(event); - } - } - } else { - DecodeError(); + nsCOMPtr event = + NS_NewRunnableMethod(mDecoder, &MediaDecoder::Invalidate); + NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); } } + mDecoder->StartProgressUpdates(); if (mState == DECODER_STATE_DECODING_METADATA || mState == DECODER_STATE_DORMANT || @@ -2013,8 +2444,6 @@ void MediaDecoderStateMachine::DecodeSeek() // Explicitly set our state so we don't decode further, and so // we report playback ended to the media element. mState = DECODER_STATE_COMPLETED; - mIsAudioDecoding = false; - mIsVideoDecoding = false; DispatchDecodeTasksIfNeeded(); } else { DECODER_LOG(PR_LOG_DEBUG, "Changed state from SEEKING (to %lld) to DECODING", seekTime); @@ -2022,11 +2451,10 @@ void MediaDecoderStateMachine::DecodeSeek() StartDecoding(); } - if (newCurrentTime != mediaTime) { - UpdatePlaybackPositionInternal(newCurrentTime); - if (mDecoder->GetDecodedStream()) { - SetSyncPointForMediaStream(); - } + // Ensure timestamps are up to date. + UpdatePlaybackPositionInternal(newCurrentTime); + if (mDecoder->GetDecodedStream()) { + SetSyncPointForMediaStream(); } // Try to decode another frame to detect if we're at the end... @@ -2053,8 +2481,10 @@ public: : mDecoder(aDecoder), mStateMachine(aStateMachine) {} NS_IMETHOD Run() { NS_ASSERTION(NS_IsMainThread(), "Must be on main thread."); - mStateMachine->ReleaseDecoder(); - mDecoder->ReleaseStateMachine(); + MOZ_ASSERT(mStateMachine); + MOZ_ASSERT(mDecoder); + mStateMachine->BreakCycles(); + mDecoder->BreakCycles(); mStateMachine = nullptr; mDecoder = nullptr; return NS_OK; @@ -2095,6 +2525,12 @@ nsresult MediaDecoderStateMachine::RunStateMachine() if (IsPlaying()) { StopPlayback(); } + + // Put a task in the decode queue to shutdown the reader. + RefPtr task( + NS_NewRunnableMethod(mReader, &MediaDecoderReader::Shutdown)); + mDecodeTaskQueue->Dispatch(task); + StopAudioThread(); // If mAudioThread is non-null after StopAudioThread completes, we are // running in a nested event loop waiting for Shutdown() on @@ -2105,19 +2541,22 @@ nsresult MediaDecoderStateMachine::RunStateMachine() return NS_OK; } + { + // Wait for the thread decoding to exit. + ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); + mDecodeTaskQueue->Shutdown(); + mDecodeTaskQueue = nullptr; + } + + AudioQueue().Reset(); + VideoQueue().Reset(); + // The reader's listeners hold references to the state machine, // creating a cycle which keeps the state machine and its shared // thread pools alive. So break it here. AudioQueue().ClearListeners(); VideoQueue().ClearListeners(); - { - ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); - // Wait for the thread decoding to exit. - mDecodeTaskQueue->Shutdown(); - mDecodeTaskQueue = nullptr; - mReader->ReleaseMediaResources(); - } // Now that those threads are stopped, there's no possibility of // mPendingWakeDecoder being needed again. Revoke it. mPendingWakeDecoder = nullptr; @@ -2234,8 +2673,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine() } case DECODER_STATE_SEEKING: { - // Ensure we have a decode thread to perform the seek. - return EnqueueDecodeSeekTask(); + return EnqueueDecodeSeekTask(); } case DECODER_STATE_COMPLETED: { @@ -2534,19 +2972,116 @@ void MediaDecoderStateMachine::Wait(int64_t aUsecs) { } } -VideoData* MediaDecoderStateMachine::FindStartTime() +nsresult +MediaDecoderStateMachine::DropVideoUpToSeekTarget(VideoData* aSample) +{ + nsAutoPtr video(aSample); + + const int64_t target = mCurrentSeekTarget.mTime; + + // If the frame end time is less than the seek target, we won't want + // to display this frame after the seek, so discard it. + if (target >= video->GetEndTime()) { + DECODER_LOG(PR_LOG_DEBUG, + "DropVideoUpToSeekTarget() pop video frame [%lld, %lld] target=%lld", + video->mTime, video->GetEndTime(), target); + mFirstVideoFrameAfterSeek = video; + } else { + if (target >= video->mTime && video->GetEndTime() >= target) { + // The seek target lies inside this frame's time slice. Adjust the frame's + // start time to match the seek target. We do this by replacing the + // first frame with a shallow copy which has the new timestamp. + VideoData* temp = VideoData::ShallowCopyUpdateTimestamp(video, target); + video = temp; + } + mFirstVideoFrameAfterSeek = nullptr; + + DECODER_LOG(PR_LOG_DEBUG, + "DropVideoUpToSeekTarget() found video frame [%lld, %lld] containing target=%lld", + video->mTime, video->GetEndTime(), target); + + VideoQueue().PushFront(video.forget()); + + } + return NS_OK; +} + +nsresult +MediaDecoderStateMachine::DropAudioUpToSeekTarget(AudioData* aSample) +{ + nsAutoPtr audio(aSample); + MOZ_ASSERT(audio && + mCurrentSeekTarget.IsValid() && + mCurrentSeekTarget.mType == SeekTarget::Accurate); + + CheckedInt64 startFrame = UsecsToFrames(audio->mTime, + mInfo.mAudio.mRate); + CheckedInt64 targetFrame = UsecsToFrames(mCurrentSeekTarget.mTime, + mInfo.mAudio.mRate); + if (!startFrame.isValid() || !targetFrame.isValid()) { + return NS_ERROR_FAILURE; + } + if (startFrame.value() + audio->mFrames <= targetFrame.value()) { + // Our seek target lies after the frames in this AudioData. Don't + // push it onto the audio queue, and keep decoding forwards. + return NS_OK; + } + if (startFrame.value() > targetFrame.value()) { + // The seek target doesn't lie in the audio block just after the last + // audio frames we've seen which were before the seek target. This + // could have been the first audio data we've seen after seek, i.e. the + // seek terminated after the seek target in the audio stream. Just + // abort the audio decode-to-target, the state machine will play + // silence to cover the gap. Typically this happens in poorly muxed + // files. + NS_WARNING("Audio not synced after seek, maybe a poorly muxed file?"); + AudioQueue().Push(audio.forget()); + return NS_OK; + } + + // The seek target lies somewhere in this AudioData's frames, strip off + // any frames which lie before the seek target, so we'll begin playback + // exactly at the seek target. + NS_ASSERTION(targetFrame.value() >= startFrame.value(), + "Target must at or be after data start."); + NS_ASSERTION(targetFrame.value() < startFrame.value() + audio->mFrames, + "Data must end after target."); + + int64_t framesToPrune = targetFrame.value() - startFrame.value(); + if (framesToPrune > audio->mFrames) { + // We've messed up somehow. Don't try to trim frames, the |frames| + // variable below will overflow. + NS_WARNING("Can't prune more frames that we have!"); + return NS_ERROR_FAILURE; + } + uint32_t frames = audio->mFrames - static_cast(framesToPrune); + uint32_t channels = audio->mChannels; + nsAutoArrayPtr audioData(new AudioDataValue[frames * channels]); + memcpy(audioData.get(), + audio->mAudioData.get() + (framesToPrune * channels), + frames * channels * sizeof(AudioDataValue)); + CheckedInt64 duration = FramesToUsecs(frames, mInfo.mAudio.mRate); + if (!duration.isValid()) { + return NS_ERROR_FAILURE; + } + nsAutoPtr data(new AudioData(audio->mOffset, + mCurrentSeekTarget.mTime, + duration.value(), + frames, + audioData.forget(), + channels)); + AudioQueue().PushFront(data.forget()); + + return NS_OK; +} + +void MediaDecoderStateMachine::SetStartTime(int64_t aStartTimeUsecs) { NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); - AssertCurrentThreadInMonitor(); - int64_t startTime = 0; + DECODER_LOG(PR_LOG_DEBUG, "SetStartTime(%lld)", aStartTimeUsecs); mStartTime = 0; - VideoData* v = nullptr; - { - ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor()); - v = mReader->FindStartTime(startTime); - } - if (startTime != 0) { - mStartTime = startTime; + if (aStartTimeUsecs != 0) { + mStartTime = aStartTimeUsecs; if (mGotDurationFromMetaData) { NS_ASSERTION(mEndTime != -1, "We should have mEndTime as supplied duration here"); @@ -2560,8 +3095,7 @@ VideoData* MediaDecoderStateMachine::FindStartTime() // first actual audio frame we have, we'll inject silence during playback // to ensure the audio starts at the correct time. mAudioStartTime = mStartTime; - DECODER_LOG(PR_LOG_DEBUG, "Media start time is %lld", mStartTime); - return v; + DECODER_LOG(PR_LOG_DEBUG, "Set media start time to %lld", mStartTime); } void MediaDecoderStateMachine::UpdateReadyState() { @@ -2786,7 +3320,7 @@ void MediaDecoderStateMachine::SetPlaybackRate(double aPlaybackRate) if (!HasAudio()) { // mBasePosition is a position in the video stream, not an absolute time. if (mState == DECODER_STATE_SEEKING) { - mBasePosition = mSeekTarget.mTime - mStartTime; + mBasePosition = mCurrentSeekTarget.mTime - mStartTime; } else { mBasePosition = GetVideoStreamPosition(); } diff --git a/content/media/MediaDecoderStateMachine.h b/content/media/MediaDecoderStateMachine.h index 549d895a9339..71b5ee05e76b 100644 --- a/content/media/MediaDecoderStateMachine.h +++ b/content/media/MediaDecoderStateMachine.h @@ -4,29 +4,36 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* -Each video element for a media file has two threads: - 1) The Audio thread writes the decoded audio data to the audio - hardware. This is done in a separate thread to ensure that the - audio hardware gets a constant stream of data without - interruption due to decoding or display. At some point - AudioStream will be refactored to have a callback interface - where it asks for data and an extra thread will no longer be - needed. +Each media element for a media file has one thread called the "audio thread". - 2) The decode thread. This thread reads from the media stream and - decodes the Theora and Vorbis data. It places the decoded data into - queues for the other threads to pull from. +The audio thread writes the decoded audio data to the audio +hardware. This is done in a separate thread to ensure that the +audio hardware gets a constant stream of data without +interruption due to decoding or display. At some point +AudioStream will be refactored to have a callback interface +where it asks for data and this thread will no longer be +needed. + +The element/state machine also has a MediaTaskQueue which runs in a +SharedThreadPool that is shared with all other elements/decoders. The state +machine dispatches tasks to this to call into the MediaDecoderReader to +request decoded audio or video data. The Reader will callback with decoded +sampled when it has them available, and the state machine places the decoded +samples into its queues for the consuming threads to pull from. + +The MediaDecoderReader can choose to decode asynchronously, or synchronously +and return requested samples synchronously inside it's Request*Data() +functions via callback. Asynchronous decoding is preferred, and should be +used for any new readers. -All file reads, seeks, and all decoding must occur on the decode thread. Synchronisation of state between the thread is done via a monitor owned by MediaDecoder. -The lifetime of the decode and audio threads is controlled by the state -machine when it runs on the shared state machine thread. When playback -needs to occur they are created and events dispatched to them to run -them. These events exit when decoding/audio playback is completed or -no longer required. +The lifetime of the audio thread is controlled by the state machine when +it runs on the shared state machine thread. When playback needs to occur +the audio thread is created and an event dispatched to run it. The audio +thread exits when audio playback is completed or no longer required. A/V synchronisation is handled by the state machine. It examines the audio playback time and compares this to the next frame in the queue of video @@ -39,7 +46,7 @@ Frame skipping is done in the following ways: display time is less than the current audio time. This ensures the correct frame for the current time is always displayed. - 2) The decode thread will stop decoding interframes and read to the + 2) The decode tasks will stop decoding interframes and read to the next keyframe if it determines that decoding the remaining interframes will cause playback issues. It detects this by: a) If the amount of audio data in the audio queue drops @@ -47,11 +54,13 @@ Frame skipping is done in the following ways: b) If the video queue drops below a threshold where it will be decoding video data that won't be displayed due to the decode thread dropping the frame immediately. + TODO: In future we should only do this when the Reader is decoding + synchronously. When hardware accelerated graphics is not available, YCbCr conversion -is done on the decode thread when video frames are decoded. +is done on the decode task queue when video frames are decoded. -The decode thread pushes decoded audio and videos frames into two +The decode task queue pushes decoded audio and videos frames into two separate queues - one for audio and one for video. These are kept separate to make it easy to constantly feed audio data to the audio hardware while allowing frame skipping of video data. These queues are @@ -59,13 +68,10 @@ threadsafe, and neither the decode, audio, or state machine should be able to monopolize them, and cause starvation of the other threads. Both queues are bounded by a maximum size. When this size is reached -the decode thread will no longer decode video or audio depending on the -queue that has reached the threshold. If both queues are full, the decode -thread will wait on the decoder monitor. - -When the decode queues are full (they've reaced their maximum size) and -the decoder is not in PLAYING play state, the state machine may opt -to shut down the decode thread in order to conserve resources. +the decode tasks will no longer request video or audio depending on the +queue that has reached the threshold. If both queues are full, no more +decode tasks will be dispatched to the decode task queue, so other +decoders will have an opportunity to run. During playback the audio thread will be idle (via a Wait() on the monitor) if the audio queue is empty. Otherwise it constantly pops @@ -83,6 +89,7 @@ hardware (via AudioStream). #include "MediaDecoderReader.h" #include "MediaDecoderOwner.h" #include "MediaMetadataManager.h" +#include "MediaDataDecodedListener.h" class nsITimer; @@ -102,7 +109,7 @@ class SharedThreadPool; /* The state machine class. This manages the decoding and seeking in the - MediaDecoderReader on the decode thread, and A/V sync on the shared + MediaDecoderReader on the decode task queue, and A/V sync on the shared state machine thread, and controls the audio "push" thread. All internal state is synchronised via the decoder monitor. State changes @@ -312,10 +319,9 @@ public: void SetFragmentEndTime(int64_t aEndTime); // Drop reference to decoder. Only called during shutdown dance. - void ReleaseDecoder() { - MOZ_ASSERT(mReader); + void BreakCycles() { if (mReader) { - mReader->ReleaseDecoder(); + mReader->BreakCycles(); } mDecoder = nullptr; } @@ -357,11 +363,22 @@ public: // samples in advance of when they're needed for playback. void SetMinimizePrerollUntilPlaybackStarts(); + void OnAudioDecoded(AudioData* aSample); + void OnAudioEOS(); + void OnVideoDecoded(VideoData* aSample); + void OnVideoEOS(); + void OnDecodeError(); + protected: virtual ~MediaDecoderStateMachine(); void AssertCurrentThreadInMonitor() const { mDecoder->GetReentrantMonitor().AssertCurrentThreadIn(); } + // Inserts MediaData* samples into their respective MediaQueues. + // aSample must not be null. + void Push(AudioData* aSample); + void Push(VideoData* aSample); + class WakeDecoderRunnable : public nsRunnable { public: WakeDecoderRunnable(MediaDecoderStateMachine* aSM) @@ -397,8 +414,14 @@ protected: }; WakeDecoderRunnable* GetWakeDecoderRunnable(); - MediaQueue& AudioQueue() { return mReader->AudioQueue(); } - MediaQueue& VideoQueue() { return mReader->VideoQueue(); } + MediaQueue& AudioQueue() { return mAudioQueue; } + MediaQueue& VideoQueue() { return mVideoQueue; } + + nsresult FinishDecodeMetadata(); + + RefPtr> mMediaDecodedListener; + + nsAutoPtr mMetadataTags; // True if our buffers of decoded audio are not full, and we should // decode more. @@ -468,11 +491,10 @@ protected: // Called on the state machine thread. int64_t GetClock(); - // Returns the presentation time of the first audio or video frame in the - // media. If the media has video, it returns the first video frame. The - // decoder monitor must be held with exactly one lock count. Called on the - // state machine thread. - VideoData* FindStartTime(); + nsresult DropAudioUpToSeekTarget(AudioData* aSample); + nsresult DropVideoUpToSeekTarget(VideoData* aSample); + + void SetStartTime(int64_t aStartTimeUsecs); // Update only the state machine's current playback position (and duration, // if unknown). Does not update the playback position on the decoder or @@ -544,6 +566,10 @@ protected: // The decoder monitor must be held. nsresult EnqueueDecodeMetadataTask(); + // Dispatches a task to the decode task queue to seek the decoder. + // The decoder monitor must be held. + nsresult EnqueueDecodeSeekTask(); + nsresult DispatchAudioDecodeTaskIfNeeded(); // Ensures a to decode audio has been dispatched to the decode task queue. @@ -561,10 +587,6 @@ protected: // The decoder monitor must be held. nsresult EnsureVideoDecodeTaskQueued(); - // Dispatches a task to the decode task queue to seek the decoder. - // The decoder monitor must be held. - nsresult EnqueueDecodeSeekTask(); - // Calls the reader's SetIdle(). This is only called in a task dispatched to // the decode task queue, don't call it directly. void SetReaderIdle(); @@ -575,12 +597,6 @@ protected: // The decoder monitor must be held. void DispatchDecodeTasksIfNeeded(); - // Queries our state to see whether the decode has finished for all streams. - // If so, we move into DECODER_STATE_COMPLETED and schedule the state machine - // to run. - // The decoder monitor must be held. - void CheckIfDecodeComplete(); - // Returns the "media time". This is the absolute time which the media // playback has reached. i.e. this returns values in the range // [mStartTime, mEndTime], and mStartTime will not be 0 if the media does @@ -604,15 +620,29 @@ protected: // must be held with exactly one lock count. nsresult DecodeMetadata(); + // Wraps the call to DecodeMetadata(), signals a DecodeError() on failure. + void CallDecodeMetadata(); + + // Checks whether we're finished decoding metadata, and switches to DECODING + // state if so. + void MaybeFinishDecodeMetadata(); + // Seeks to mSeekTarget. Called on the decode thread. The decoder monitor // must be held with exactly one lock count. void DecodeSeek(); - // Decode loop, decodes data until EOF or shutdown. - // Called on the decode thread. - void DecodeLoop(); + void CheckIfSeekComplete(); + bool IsAudioSeekComplete(); + bool IsVideoSeekComplete(); - void CallDecodeMetadata(); + // Completes the seek operation, moves onto the next appropriate state. + void SeekCompleted(); + + // Queries our state to see whether the decode has finished for all streams. + // If so, we move into DECODER_STATE_COMPLETED and schedule the state machine + // to run. + // The decoder monitor must be held. + void CheckIfDecodeComplete(); // Copy audio from an AudioData packet to aOutput. This may require // inserting silence depending on the timing of the audio packet. @@ -637,6 +667,11 @@ protected: // case as it may not be needed again. bool IsPausedAndDecoderWaiting(); + // These return true if the respective stream's decode has not yet reached + // the end of stream. + bool IsAudioDecoding(); + bool IsVideoDecoding(); + // The decoder object that created this state machine. The state machine // holds a strong reference to the decoder to ensure that the decoder stays // alive once media element has started the decoder shutdown process, and has @@ -648,6 +683,19 @@ protected: // state machine, audio and main threads. nsRefPtr mDecoder; + // Time at which the last video sample was requested. If it takes too long + // before the sample arrives, we will increase the amount of audio we buffer. + // This is necessary for legacy synchronous decoders to prevent underruns. + TimeStamp mVideoDecodeStartTime; + + // Queue of audio frames. This queue is threadsafe, and is accessed from + // the audio, decoder, state machine, and main threads. + MediaQueue mAudioQueue; + + // Queue of video frames. This queue is threadsafe, and is accessed from + // the decoder, state machine, and main threads. + MediaQueue mVideoQueue; + // The decoder monitor must be obtained before modifying this state. // NotifyAll on the monitor must be called when the state is changed so // that interested threads can wake up and alter behaviour if appropriate @@ -719,6 +767,14 @@ protected: // this value. Accessed on main and decode thread. SeekTarget mSeekTarget; + // The position that we're currently seeking to. This differs from + // mSeekTarget, as mSeekTarget is the target we'll seek to next, whereas + // mCurrentSeekTarget is the position that the decode is in the process + // of seeking to. + // The decoder monitor lock must be obtained before reading or writing + // this value. + SeekTarget mCurrentSeekTarget; + // Media Fragment end time in microseconds. Access controlled by decoder monitor. int64_t mFragmentEndTime; @@ -729,9 +785,8 @@ protected: RefPtr mAudioStream; // The reader, don't call its methods with the decoder monitor held. - // This is created in the play state machine's constructor, and destroyed - // in the play state machine's destructor. - nsAutoPtr mReader; + // This is created in the state machine's constructor. + nsRefPtr mReader; // Accessed only on the state machine thread. // Not an nsRevocableEventPtr since we must Revoke() it well before @@ -817,6 +872,12 @@ protected: uint32_t mAudioPrerollUsecs; uint32_t mVideoPrerollFrames; + // This temporarily stores the first frame we decode after we seek. + // This is so that if we hit end of stream while we're decoding to reach + // the seek target, we will still have a frame that we can display as the + // last frame in the media. + nsAutoPtr mFirstVideoFrameAfterSeek; + // When we start decoding (either for the first time, or after a pause) // we may be low on decoded data. We don't want our "low data" logic to // kick in and decide that we're low on decoded data because the download @@ -836,19 +897,11 @@ protected: // yet decoded to end of stream. bool mIsVideoDecoding; - // True when we have dispatched a task to the decode task queue to run - // the audio decode. - bool mDispatchedAudioDecodeTask; - - // True when we have dispatched a task to the decode task queue to run - // the video decode. - bool mDispatchedVideoDecodeTask; - - // If the video decode is falling behind the audio, we'll start dropping the - // inter-frames up until the next keyframe which is at or before the current - // playback position. skipToNextKeyframe is true if we're currently - // skipping up to the next keyframe. - bool mSkipToNextKeyFrame; + // True when we have dispatched a task to the decode task queue to request + // decoded audio/video, and/or we are waiting for the requested sample to be + // returned by callback from the Reader. + bool mAudioRequestPending; + bool mVideoRequestPending; // True if we shouldn't play our audio (but still write it to any capturing // streams). When this is true, mStopAudioThread is always true and @@ -924,10 +977,16 @@ protected: // dispatch multiple tasks to re-do the metadata loading. bool mDispatchedDecodeMetadataTask; - // True if we've dispatched a task to the decode task queue to call - // Seek on the reader. We maintain a flag to ensure that we don't - // dispatch multiple tasks to re-do the seek. - bool mDispatchedDecodeSeekTask; + // These two flags are true when we need to drop decoded samples that + // we receive up to the next discontinuity. We do this when we seek; + // the first sample in each stream after the seek is marked as being + // a "discontinuity". + bool mDropAudioUntilNextDiscontinuity; + bool mDropVideoUntilNextDiscontinuity; + + // True if we need to decode forwards to the seek target inside + // mCurrentSeekTarget. + bool mDecodeToSeekTarget; // Stores presentation info required for playback. The decoder monitor // must be held when accessing this. diff --git a/content/media/MediaQueue.h b/content/media/MediaQueue.h index 15efd391b587..a8af29c0ba89 100644 --- a/content/media/MediaQueue.h +++ b/content/media/MediaQueue.h @@ -43,11 +43,13 @@ template class MediaQueue : private nsDeque { inline void Push(T* aItem) { ReentrantMonitorAutoEnter mon(mReentrantMonitor); + MOZ_ASSERT(aItem); nsDeque::Push(aItem); } inline void PushFront(T* aItem) { ReentrantMonitorAutoEnter mon(mReentrantMonitor); + MOZ_ASSERT(aItem); nsDeque::PushFront(aItem); } @@ -75,11 +77,6 @@ template class MediaQueue : private nsDeque { nsDeque::Empty(); } - inline void Erase() { - ReentrantMonitorAutoEnter mon(mReentrantMonitor); - nsDeque::Erase(); - } - void Reset() { ReentrantMonitorAutoEnter mon(mReentrantMonitor); while (GetSize() > 0) { diff --git a/content/media/VideoUtils.cpp b/content/media/VideoUtils.cpp index a456c2a24112..bb7e9e5bd38e 100644 --- a/content/media/VideoUtils.cpp +++ b/content/media/VideoUtils.cpp @@ -9,6 +9,8 @@ #include "nsSize.h" #include "VorbisUtils.h" #include "ImageContainer.h" +#include "SharedThreadPool.h" +#include "mozilla/Preferences.h" #include @@ -190,4 +192,10 @@ IsValidVideoRegion(const nsIntSize& aFrame, const nsIntRect& aPicture, aDisplay.width * aDisplay.height != 0; } +TemporaryRef GetMediaDecodeThreadPool() +{ + return SharedThreadPool::Get(NS_LITERAL_CSTRING("Media Decode"), + Preferences::GetUint("media.num-decode-threads", 25)); +} + } // end namespace mozilla diff --git a/content/media/VideoUtils.h b/content/media/VideoUtils.h index 97bbe1135222..6ea4cb260aff 100644 --- a/content/media/VideoUtils.h +++ b/content/media/VideoUtils.h @@ -19,6 +19,7 @@ #include "nsThreadUtils.h" #include "prtime.h" #include "AudioSampleFormat.h" +#include "mozilla/RefPtr.h" using mozilla::CheckedInt64; using mozilla::CheckedUint64; @@ -186,6 +187,12 @@ private: const T mValue; }; +class SharedThreadPool; + +// Returns the thread pool that is shared amongst all decoder state machines +// for decoding streams. +TemporaryRef GetMediaDecodeThreadPool(); + } // end namespace mozilla #endif diff --git a/content/media/mediasource/MediaSourceDecoder.cpp b/content/media/mediasource/MediaSourceDecoder.cpp index 20b44ad897b2..16147ace2304 100644 --- a/content/media/mediasource/MediaSourceDecoder.cpp +++ b/content/media/mediasource/MediaSourceDecoder.cpp @@ -43,6 +43,8 @@ class MediaSourceReader : public MediaDecoderReader public: MediaSourceReader(MediaSourceDecoder* aDecoder, dom::MediaSource* aSource) : MediaDecoderReader(aDecoder) + , mTimeThreshold(-1) + , mDropVideoBeforeThreshold(false) , mActiveVideoDecoder(-1) , mActiveAudioDecoder(-1) , mMediaSource(aSource) @@ -62,53 +64,72 @@ public: return mDecoders.IsEmpty() && mPendingDecoders.IsEmpty(); } - bool DecodeAudioData() MOZ_OVERRIDE + void RequestAudioData() MOZ_OVERRIDE { if (!GetAudioReader()) { MSE_DEBUG("%p DecodeAudioFrame called with no audio reader", this); MOZ_ASSERT(mPendingDecoders.IsEmpty()); - return false; + GetCallback()->OnDecodeError(); + return; } - bool rv = GetAudioReader()->DecodeAudioData(); - - nsAutoTArray audio; - GetAudioReader()->AudioQueue().GetElementsAfter(-1, &audio); - for (uint32_t i = 0; i < audio.Length(); ++i) { - AudioQueue().Push(audio[i]); - } - GetAudioReader()->AudioQueue().Empty(); - - return rv; + GetAudioReader()->RequestAudioData(); } - bool DecodeVideoFrame(bool& aKeyFrameSkip, int64_t aTimeThreshold) MOZ_OVERRIDE + void OnAudioDecoded(AudioData* aSample) + { + GetCallback()->OnAudioDecoded(aSample); + } + + void OnAudioEOS() + { + GetCallback()->OnAudioEOS(); + } + + void RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) MOZ_OVERRIDE { if (!GetVideoReader()) { MSE_DEBUG("%p DecodeVideoFrame called with no video reader", this); MOZ_ASSERT(mPendingDecoders.IsEmpty()); - return false; + GetCallback()->OnDecodeError(); + return; } + mTimeThreshold = aTimeThreshold; + GetVideoReader()->RequestVideoData(aSkipToNextKeyframe, aTimeThreshold); + } - if (MaybeSwitchVideoReaders(aTimeThreshold)) { - GetVideoReader()->DecodeToTarget(aTimeThreshold); - } - - bool rv = GetVideoReader()->DecodeVideoFrame(aKeyFrameSkip, aTimeThreshold); - - nsAutoTArray video; - GetVideoReader()->VideoQueue().GetElementsAfter(-1, &video); - for (uint32_t i = 0; i < video.Length(); ++i) { - VideoQueue().Push(video[i]); - } - GetVideoReader()->VideoQueue().Empty(); - - if (rv) { - return true; + void OnVideoDecoded(VideoData* aSample) + { + if (mDropVideoBeforeThreshold) { + if (aSample->mTime < mTimeThreshold) { + delete aSample; + GetVideoReader()->RequestVideoData(false, mTimeThreshold); + } else { + mDropVideoBeforeThreshold = false; + GetCallback()->OnVideoDecoded(aSample); + } + } else { + GetCallback()->OnVideoDecoded(aSample); } + } + void OnVideoEOS() + { + // End of stream. See if we can switch to another video decoder. MSE_DEBUG("%p MSR::DecodeVF %d (%p) returned false (readers=%u)", this, mActiveVideoDecoder, mDecoders[mActiveVideoDecoder].get(), mDecoders.Length()); - return rv; + if (MaybeSwitchVideoReaders()) { + // Success! Resume decoding with next video decoder. + RequestVideoData(false, mTimeThreshold); + } else { + // End of stream. + MSE_DEBUG("%p MSR::DecodeVF %d (%p) EOS (readers=%u)", + this, mActiveVideoDecoder, mDecoders[mActiveVideoDecoder].get(), mDecoders.Length()); + GetCallback()->OnVideoEOS(); + } + } + + void OnDecodeError() { + GetCallback()->OnDecodeError(); } bool HasVideo() MOZ_OVERRIDE @@ -126,7 +147,22 @@ public: int64_t aCurrentTime) MOZ_OVERRIDE; nsresult GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime) MOZ_OVERRIDE; already_AddRefed CreateSubDecoder(const nsACString& aType, - MediaSourceDecoder* aParentDecoder); + MediaSourceDecoder* aParentDecoder, + MediaTaskQueue* aTaskQueue); + + void Shutdown() MOZ_OVERRIDE { + MediaDecoderReader::Shutdown(); + for (uint32_t i = 0; i < mDecoders.Length(); ++i) { + mDecoders[i]->GetReader()->Shutdown(); + } + } + + virtual void BreakCycles() MOZ_OVERRIDE { + MediaDecoderReader::BreakCycles(); + for (uint32_t i = 0; i < mDecoders.Length(); ++i) { + mDecoders[i]->GetReader()->BreakCycles(); + } + } void InitializePendingDecoders(); @@ -136,7 +172,12 @@ public: } private: - bool MaybeSwitchVideoReaders(int64_t aTimeThreshold) { + + // These are read and written on the decode task queue threads. + int64_t mTimeThreshold; + bool mDropVideoBeforeThreshold; + + bool MaybeSwitchVideoReaders() { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); MOZ_ASSERT(mActiveVideoDecoder != -1); @@ -146,7 +187,7 @@ private: if (!mDecoders[i]->GetReader()->GetMediaInfo().HasVideo()) { continue; } - if (aTimeThreshold >= mDecoders[i]->GetMediaStartTime()) { + if (mTimeThreshold >= mDecoders[i]->GetMediaStartTime()) { GetVideoReader()->SetIdle(); mActiveVideoDecoder = i; @@ -196,7 +237,7 @@ public: if (!mReader) { return nullptr; } - return static_cast(mReader.get())->CreateSubDecoder(aType, aParentDecoder); + return static_cast(mReader.get())->CreateSubDecoder(aType, aParentDecoder, mDecodeTaskQueue); } nsresult EnqueueDecoderInitialization() { @@ -366,7 +407,9 @@ MediaSourceReader::InitializePendingDecoders() } already_AddRefed -MediaSourceReader::CreateSubDecoder(const nsACString& aType, MediaSourceDecoder* aParentDecoder) +MediaSourceReader::CreateSubDecoder(const nsACString& aType, + MediaSourceDecoder* aParentDecoder, + MediaTaskQueue* aTaskQueue) { // XXX: Why/when is mDecoder null here, since it should be equal to aParentDecoder?! nsRefPtr decoder = @@ -375,6 +418,13 @@ MediaSourceReader::CreateSubDecoder(const nsACString& aType, MediaSourceDecoder* if (!reader) { return nullptr; } + // Set a callback on the subreader that forwards calls to this reader. + // This reader will then forward them onto the state machine via this + // reader's callback. + RefPtr> callback = + new MediaDataDecodedListener(this, aTaskQueue); + reader->SetCallback(callback); + reader->SetTaskQueue(aTaskQueue); reader->Init(nullptr); ReentrantMonitorAutoEnter mon(aParentDecoder->GetReentrantMonitor()); MSE_DEBUG("Registered subdecoder %p subreader %p", decoder.get(), reader.get()); @@ -424,7 +474,7 @@ MediaSourceReader::Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, while (!mMediaSource->ActiveSourceBuffers()->AllContainsTime (aTime / USECS_PER_S) && !IsShutdown()) { mMediaSource->WaitForData(); - MaybeSwitchVideoReaders(aTime); + MaybeSwitchVideoReaders(); } if (IsShutdown()) { diff --git a/content/media/moz.build b/content/media/moz.build index 7fb87d200e98..e65c8ea90514 100644 --- a/content/media/moz.build +++ b/content/media/moz.build @@ -78,6 +78,7 @@ EXPORTS += [ 'Latency.h', 'MediaCache.h', 'MediaData.h', + 'MediaDataDecodedListener.h', 'MediaDecoder.h', 'MediaDecoderOwner.h', 'MediaDecoderReader.h', diff --git a/content/media/omx/MediaOmxReader.cpp b/content/media/omx/MediaOmxReader.cpp index 8960fa80b428..e60a7cd49742 100644 --- a/content/media/omx/MediaOmxReader.cpp +++ b/content/media/omx/MediaOmxReader.cpp @@ -59,9 +59,6 @@ MediaOmxReader::MediaOmxReader(AbstractMediaDecoder *aDecoder) MediaOmxReader::~MediaOmxReader() { - ReleaseMediaResources(); - ReleaseDecoder(); - mOmxDecoder.clear(); } nsresult MediaOmxReader::Init(MediaDecoderReader* aCloneDonor) @@ -69,6 +66,15 @@ nsresult MediaOmxReader::Init(MediaDecoderReader* aCloneDonor) return NS_OK; } +void MediaOmxReader::Shutdown() +{ + ReleaseMediaResources(); + if (mOmxDecoder.get()) { + mOmxDecoder->ReleaseDecoder(); + } + mOmxDecoder.clear(); +} + bool MediaOmxReader::IsWaitingMediaResources() { if (!mOmxDecoder.get()) { @@ -99,13 +105,6 @@ void MediaOmxReader::ReleaseMediaResources() } } -void MediaOmxReader::ReleaseDecoder() -{ - if (mOmxDecoder.get()) { - mOmxDecoder->ReleaseDecoder(); - } -} - nsresult MediaOmxReader::InitOmxDecoder() { if (!mOmxDecoder.get()) { @@ -375,7 +374,6 @@ nsresult MediaOmxReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndT NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); EnsureActive(); - ResetDecode(); VideoFrameContainer* container = mDecoder->GetVideoFrameContainer(); if (container && container->GetImageContainer()) { container->GetImageContainer()->ClearAllImagesExceptFront(); diff --git a/content/media/omx/MediaOmxReader.h b/content/media/omx/MediaOmxReader.h index 7daa3450ca6f..ac71ca529e54 100644 --- a/content/media/omx/MediaOmxReader.h +++ b/content/media/omx/MediaOmxReader.h @@ -80,14 +80,14 @@ public: virtual bool IsDormantNeeded(); virtual void ReleaseMediaResources(); - virtual void ReleaseDecoder() MOZ_OVERRIDE; - virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags); virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime); virtual void SetIdle() MOZ_OVERRIDE; + virtual void Shutdown() MOZ_OVERRIDE; + void SetAudioChannel(dom::AudioChannel aAudioChannel) { mAudioChannel = aAudioChannel; } diff --git a/content/media/plugins/MediaPluginReader.cpp b/content/media/plugins/MediaPluginReader.cpp index 0c7ffb486574..cbbdd87b7d26 100644 --- a/content/media/plugins/MediaPluginReader.cpp +++ b/content/media/plugins/MediaPluginReader.cpp @@ -35,11 +35,6 @@ MediaPluginReader::MediaPluginReader(AbstractMediaDecoder *aDecoder, { } -MediaPluginReader::~MediaPluginReader() -{ - ResetDecode(); -} - nsresult MediaPluginReader::Init(MediaDecoderReader* aCloneDonor) { return NS_OK; @@ -104,18 +99,22 @@ nsresult MediaPluginReader::ReadMetadata(MediaInfo* aInfo, return NS_OK; } +void MediaPluginReader::Shutdown() +{ + ResetDecode(); + if (mPlugin) { + GetMediaPluginHost()->DestroyDecoder(mPlugin); + mPlugin = nullptr; + } +} + // Resets all state related to decoding, emptying all buffers etc. nsresult MediaPluginReader::ResetDecode() { if (mLastVideoFrame) { mLastVideoFrame = nullptr; } - if (mPlugin) { - GetMediaPluginHost()->DestroyDecoder(mPlugin); - mPlugin = nullptr; - } - - return NS_OK; + return MediaDecoderReader::ResetDecode(); } bool MediaPluginReader::DecodeVideoFrame(bool &aKeyframeSkip, @@ -321,9 +320,6 @@ nsresult MediaPluginReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aE { NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); - mVideoQueue.Reset(); - mAudioQueue.Reset(); - if (mHasAudio && mHasVideo) { // The decoder seeks/demuxes audio and video streams separately. So if // we seek both audio and video to aTarget, the audio stream can typically diff --git a/content/media/plugins/MediaPluginReader.h b/content/media/plugins/MediaPluginReader.h index 8143c0507277..a778fb1ae0c5 100644 --- a/content/media/plugins/MediaPluginReader.h +++ b/content/media/plugins/MediaPluginReader.h @@ -43,7 +43,6 @@ class MediaPluginReader : public MediaDecoderReader public: MediaPluginReader(AbstractMediaDecoder* aDecoder, const nsACString& aContentType); - ~MediaPluginReader(); virtual nsresult Init(MediaDecoderReader* aCloneDonor); virtual nsresult ResetDecode(); @@ -66,6 +65,8 @@ public: MetadataTags** aTags); virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime); + virtual void Shutdown() MOZ_OVERRIDE; + class ImageBufferCallback : public MPAPI::BufferCallback { typedef mozilla::layers::Image Image; diff --git a/content/media/test/manifest.js b/content/media/test/manifest.js index b2f59f3f8ef7..80ee87e88d0a 100644 --- a/content/media/test/manifest.js +++ b/content/media/test/manifest.js @@ -389,7 +389,7 @@ var gUnseekableTests = [ { name:"bogus.duh", type:"bogus/duh"} ]; // Unfortunately big-buck-bunny-unseekable.mp4 is doesn't play on Windows 7, so -// only include it in the unseekable tests if we're on later versions of Windows. +// only include it in the unseekable tests if we're on later versions of Windows. // This test actually only passes on win8 at the moment. if (navigator.userAgent.indexOf("Windows") != -1 && IsWindows8OrLater()) { gUnseekableTests = gUnseekableTests.concat([ @@ -677,6 +677,14 @@ function MediaTestManager() { is(this.numTestsRunning, this.tokens.length, "[started " + token + "] Length of array should match number of running tests"); } + this.watchdog = null; + + this.watchdogFn = function() { + if (this.tokens.length > 0) { + info("Watchdog remaining tests= " + this.tokens); + } + } + // Registers that the test corresponding to 'token' has finished. Call when // you've finished your test. If all tests are complete this will finish the // run, otherwise it may start up the next run. It's ok to call multiple times @@ -687,10 +695,18 @@ function MediaTestManager() { // Remove the element from the list of running tests. this.tokens.splice(i, 1); } + + if (this.watchdog) { + clearTimeout(this.watchdog); + this.watchdog = null; + } + + info("[finished " + token + "] remaining= " + this.tokens); this.numTestsRunning--; is(this.numTestsRunning, this.tokens.length, "[finished " + token + "] Length of array should match number of running tests"); if (this.tokens.length < PARALLEL_TESTS) { this.nextTest(); + this.watchdog = setTimeout(this.watchdogFn.bind(this), 10000); } } diff --git a/content/media/test/test_bug465498.html b/content/media/test/test_bug465498.html index 6a1f3ace3f0c..9c3361b13e5f 100644 --- a/content/media/test/test_bug465498.html +++ b/content/media/test/test_bug465498.html @@ -14,11 +14,14 @@ var manager = new MediaTestManager; function startTest(e) { + var v = e.target; + info(v._name + " loadedmetadata"); e.target.play(); } function playbackEnded(e) { var v = e.target; + info(v._name + " ended"); if (v._finished) return; ok(v.currentTime >= v.duration - 0.1 && v.currentTime <= v.duration + 0.1, @@ -32,6 +35,7 @@ function playbackEnded(e) { function seekEnded(e) { var v = e.target; + info(v._name + " seeked"); if (v._finished) return; ok(v.currentTime == 0, "Checking currentTime after seek: " + @@ -42,6 +46,11 @@ function seekEnded(e) { manager.finished(v.token); } +function seeking(e) { + var v = e.target; + info(v._name + " seeking"); +} + function initTest(test, token) { var type = getMajorMimeType(test.type); var v = document.createElement(type); @@ -62,6 +71,7 @@ function initTest(test, token) { v.addEventListener("loadedmetadata", startTest, false); v.addEventListener("ended", playbackEnded, false); v.addEventListener("seeked", seekEnded, false); + v.addEventListener("seeking", seeking, false); document.body.appendChild(v); } diff --git a/content/media/test/test_bug493187.html b/content/media/test/test_bug493187.html index db9ce8ee8e80..d4887b91b9f4 100644 --- a/content/media/test/test_bug493187.html +++ b/content/media/test/test_bug493187.html @@ -20,17 +20,22 @@ SimpleTest.expectAssertions(0, 2); var manager = new MediaTestManager; function start(e) { + var v = e.target; + info("[" + v._name + "] start"); e.target.currentTime = e.target.duration / 4; } function startSeeking(e) { + var v = e.target; + info("[" + v._name + "] seeking"); e.target._seeked = true; } function canPlayThrough(e) { var v = e.target; + info("[" + v._name + "] canPlayThrough"); if (v._seeked && !v._finished) { - ok(true, "Got canplaythrough after seek for " + v._name); + ok(true, "[" + v._name + "] got canplaythrough after seek"); v._finished = true; v.parentNode.removeChild(v); v.src = ""; @@ -38,6 +43,16 @@ function canPlayThrough(e) { } } +function seeked(e) { + var v = e.target; + info("[" + v._name + "] seeked"); +} + +function error(e) { + var v = e.target; + info("[" + v._name + "] error"); +} + function startTest(test, token) { // TODO: Bug 568402, there's a bug in the WAV backend where we sometimes // don't send canplaythrough events after seeking. Once that is fixed, @@ -58,6 +73,8 @@ function startTest(test, token) { v.addEventListener("loadedmetadata", start, false); v.addEventListener("canplaythrough", canPlayThrough, false); v.addEventListener("seeking", startSeeking, false); + v.addEventListener("seeked", seeked, false); + v.addEventListener("error", error, false); document.body.appendChild(v); } diff --git a/content/media/test/test_seek.html b/content/media/test/test_seek.html index b5c6063f3a05..79a79468d800 100644 --- a/content/media/test/test_seek.html +++ b/content/media/test/test_seek.html @@ -61,10 +61,10 @@ function createTestArray() { function startTest(test, token) { var v = document.createElement('video'); - manager.started(token); + v.token = token += "-seek" + test.number + ".js"; + manager.started(v.token); v.src = test.name; v.preload = "metadata"; - v.token = token; document.body.appendChild(v); var name = test.name + " seek test " + test.number; var localIs = function(name) { return function(a, b, msg) { @@ -76,7 +76,7 @@ function startTest(test, token) { var localFinish = function(v, manager) { return function() { v.onerror = null; removeNodeAndSource(v); - dump("SEEK-TEST: Finished " + name + "\n"); + dump("SEEK-TEST: Finished " + name + " token: " + v.token + "\n"); manager.finished(v.token); }}(v, manager); dump("SEEK-TEST: Started " + name + "\n"); diff --git a/content/media/webaudio/MediaBufferDecoder.cpp b/content/media/webaudio/MediaBufferDecoder.cpp index f41c7d9f6c48..25129fb2e1ed 100644 --- a/content/media/webaudio/MediaBufferDecoder.cpp +++ b/content/media/webaudio/MediaBufferDecoder.cpp @@ -252,12 +252,25 @@ MediaDecodeTask::Decode() return; } - while (mDecoderReader->DecodeAudioData()) { - // consume all of the buffer - continue; + MediaQueue audioQueue; + nsRefPtr barrier(new AudioDecodeRendezvous()); + mDecoderReader->SetCallback(barrier); + while (1) { + mDecoderReader->RequestAudioData(); + nsAutoPtr audio; + if (NS_FAILED(barrier->Await(audio))) { + ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent); + return; + } + if (!audio) { + // End of stream. + break; + } + audioQueue.Push(audio.forget()); } + mDecoderReader->Shutdown(); + mDecoderReader->BreakCycles(); - MediaQueue& audioQueue = mDecoderReader->AudioQueue(); uint32_t frameCount = audioQueue.FrameCount(); uint32_t channelCount = mediaInfo.mAudio.mChannels; uint32_t sampleRate = mediaInfo.mAudio.mRate; diff --git a/dom/activities/src/ActivitiesService.jsm b/dom/activities/src/ActivitiesService.jsm index f37e0f1e97e1..5bbb20834e1f 100644 --- a/dom/activities/src/ActivitiesService.jsm +++ b/dom/activities/src/ActivitiesService.jsm @@ -207,13 +207,14 @@ let Activities = { startActivity: function activities_startActivity(aMsg) { debug("StartActivity: " + JSON.stringify(aMsg)); + let self = this; let successCb = function successCb(aResults) { debug(JSON.stringify(aResults)); function getActivityChoice(aResultType, aResult) { switch(aResultType) { case Ci.nsIActivityUIGlueCallback.NATIVE_ACTIVITY: { - Activities.callers[aMsg.id].mm.sendAsyncMessage("Activity:FireSuccess", { + self.callers[aMsg.id].mm.sendAsyncMessage("Activity:FireSuccess", { "id": aMsg.id, "result": aResult }); @@ -226,21 +227,19 @@ let Activities = { // Don't do this check until we have passed to UIGlue so the glue can choose to launch // its own activity if needed. if (aResults.options.length === 0) { - Activities.callers[aMsg.id].mm.sendAsyncMessage("Activity:FireError", { + self.trySendAndCleanup(aMsg.id, "Activity:FireError", { "id": aMsg.id, "error": "NO_PROVIDER" }); - delete Activities.callers[aMsg.id]; return; } // The user has cancelled the choice, fire an error. if (aResult === -1) { - Activities.callers[aMsg.id].mm.sendAsyncMessage("Activity:FireError", { + self.trySendAndCleanup(aMsg.id, "Activity:FireError", { "id": aMsg.id, "error": "ActivityCanceled" }); - delete Activities.callers[aMsg.id]; return; } @@ -248,7 +247,7 @@ let Activities = { .getService(Ci.nsISystemMessagesInternal); if (!sysmm) { // System message is not present, what should we do? - delete Activities.callers[aMsg.id]; + delete self.callers[aMsg.id]; return; } @@ -262,18 +261,17 @@ let Activities = { Services.io.newURI(result.description.href, null, null), Services.io.newURI(result.manifest, null, null), { - "manifestURL": Activities.callers[aMsg.id].manifestURL, - "pageURL": Activities.callers[aMsg.id].pageURL + "manifestURL": self.callers[aMsg.id].manifestURL, + "pageURL": self.callers[aMsg.id].pageURL }); if (!result.description.returnValue) { - Activities.callers[aMsg.id].mm.sendAsyncMessage("Activity:FireSuccess", { + // No need to notify observers, since we don't want the caller + // to be raised on the foreground that quick. + self.trySendAndCleanup(aMsg.id, "Activity:FireSuccess", { "id": aMsg.id, "result": null }); - // No need to notify observers, since we don't want the caller - // to be raised on the foreground that quick. - delete Activities.callers[aMsg.id]; } break; } @@ -332,6 +330,14 @@ let Activities = { this.db.find(aMsg, successCb, errorCb, matchFunc); }, + trySendAndCleanup: function activities_trySendAndCleanup(aId, aName, aPayload) { + try { + this.callers[aId].mm.sendAsyncMessage(aName, aPayload); + } finally { + delete this.callers[aId]; + } + }, + receiveMessage: function activities_receiveMessage(aMessage) { let mm = aMessage.target; let msg = aMessage.json; @@ -365,12 +371,10 @@ let Activities = { break; case "Activity:PostResult": - caller.mm.sendAsyncMessage("Activity:FireSuccess", msg); - delete this.callers[msg.id]; + this.trySendAndCleanup(msg.id, "Activity:FireSuccess", msg); break; case "Activity:PostError": - caller.mm.sendAsyncMessage("Activity:FireError", msg); - delete this.callers[msg.id]; + this.trySendAndCleanup(msg.id, "Activity:FireError", msg); break; case "Activities:Register": @@ -398,11 +402,10 @@ let Activities = { case "child-process-shutdown": for (let id in this.callers) { if (this.callers[id].childMM == mm) { - this.callers[id].mm.sendAsyncMessage("Activity:FireError", { + this.trySendAndCleanup(id, "Activity:FireError", { "id": id, "error": "ActivityCanceled" }); - delete this.callers[id]; break; } } diff --git a/dom/network/tests/test_networkstats_alarms.html b/dom/network/tests/test_networkstats_alarms.html index a7952b65da45..ec81a2c7adb5 100644 --- a/dom/network/tests/test_networkstats_alarms.html +++ b/dom/network/tests/test_networkstats_alarms.html @@ -171,7 +171,21 @@ function next() { SimpleTest.waitForExplicitFinish(); SpecialPowers.addPermission("networkstats-manage", true, document); -SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", true]]}, test); +SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", true]]}, + function() { + ok(SpecialPowers.hasPermission("networkstats-manage", document), + "Has permission 'networkstats-manage'."); + + ok(SpecialPowers.getBoolPref("dom.mozNetworkStats.enabled"), + "Preference 'dom.mozNetworkStats.enabled' is true."); + + ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should exist"); + + ok(navigator.mozNetworkStats instanceof SpecialPowers.Ci.nsIDOMMozNetworkStatsManager, + "navigator.mozNetworkStats should be a nsIDOMMozNetworkStatsManager object"); + + test(); +}); diff --git a/dom/network/tests/test_networkstats_basics.html b/dom/network/tests/test_networkstats_basics.html index b3fb8c0bd424..49fa35f3e227 100644 --- a/dom/network/tests/test_networkstats_basics.html +++ b/dom/network/tests/test_networkstats_basics.html @@ -323,7 +323,21 @@ function next() { SimpleTest.waitForExplicitFinish(); SpecialPowers.addPermission("networkstats-manage", true, document); -SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", true]]}, test); +SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", true]]}, + function() { + ok(SpecialPowers.hasPermission("networkstats-manage", document), + "Has permission 'networkstats-manage'."); + + ok(SpecialPowers.getBoolPref("dom.mozNetworkStats.enabled"), + "Preference 'dom.mozNetworkStats.enabled' is true."); + + ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should exist"); + + ok(navigator.mozNetworkStats instanceof SpecialPowers.Ci.nsIDOMMozNetworkStatsManager, + "navigator.mozNetworkStats should be a nsIDOMMozNetworkStatsManager object"); + + test(); +}); diff --git a/dom/network/tests/test_networkstats_disabled.html b/dom/network/tests/test_networkstats_disabled.html index 6cd05c315d3c..f515d778c6a4 100644 --- a/dom/network/tests/test_networkstats_disabled.html +++ b/dom/network/tests/test_networkstats_disabled.html @@ -17,10 +17,15 @@ SimpleTest.waitForExplicitFinish(); // Test to ensure NetworkStats is not accessible when it is disabled -SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", false]]}, function(){ +SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", false]]}, + function() { + ok(!SpecialPowers.getBoolPref("dom.mozNetworkStats.enabled"), + "Preference 'dom.mozNetworkStats.enabled' is false."); ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should exist"); - is(navigator.mozNetworkStats, null, "mozNetworkStats should be null when not enabled."); + + is(navigator.mozNetworkStats, null, + "mozNetworkStats should be null when not enabled."); SimpleTest.finish(); }); diff --git a/dom/network/tests/test_networkstats_enabled_no_perm.html b/dom/network/tests/test_networkstats_enabled_no_perm.html index 39615f968e6b..5190b3e660d3 100644 --- a/dom/network/tests/test_networkstats_enabled_no_perm.html +++ b/dom/network/tests/test_networkstats_enabled_no_perm.html @@ -13,21 +13,34 @@ diff --git a/dom/network/tests/test_networkstats_enabled_perm.html b/dom/network/tests/test_networkstats_enabled_perm.html index 7e24905d99bc..649ceb105d75 100644 --- a/dom/network/tests/test_networkstats_enabled_perm.html +++ b/dom/network/tests/test_networkstats_enabled_perm.html @@ -18,11 +18,18 @@ SimpleTest.waitForExplicitFinish(); // Test to ensure NetworkStats is not accessible when it is disabled SpecialPowers.addPermission("networkstats-manage", true, document); -SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", true]]}, function(){ +SpecialPowers.pushPrefEnv({'set': [["dom.mozNetworkStats.enabled", true]]}, + function(){ + ok(SpecialPowers.hasPermission("networkstats-manage", document), + "Has permission 'networkstats-manage'."); + + ok(SpecialPowers.getBoolPref("dom.mozNetworkStats.enabled"), + "Preference 'dom.mozNetworkStats.enabled' is true."); ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should exist"); + ok(navigator.mozNetworkStats instanceof SpecialPowers.Ci.nsIDOMMozNetworkStatsManager, - "navigator.mozNetworkStats should be a nsIDOMMozNetworkStatsManager object"); + "navigator.mozNetworkStats should be a nsIDOMMozNetworkStatsManager object"); SpecialPowers.removePermission("networkstats-manage", document); SimpleTest.finish(); diff --git a/dom/nfc/tests/marionette/head.js b/dom/nfc/tests/marionette/head.js index b65a75e45cf7..167281bdd85f 100644 --- a/dom/nfc/tests/marionette/head.js +++ b/dom/nfc/tests/marionette/head.js @@ -59,6 +59,17 @@ function toggleNFC(enabled) { return deferred.promise; } +function clearPendingMessages(type) { + if (!window.navigator.mozHasPendingMessage(type)) { + return; + } + + // setting a handler removes all messages from queue + window.navigator.mozSetMessageHandler(type, function() { + window.navigator.mozSetMessageHandler(type, null); + }); +} + function enableRE0() { let deferred = Promise.defer(); let cmd = 'nfc nci rf_intf_activated_ntf 0'; @@ -83,6 +94,9 @@ function cleanUp() { } function runNextTest() { + clearPendingMessages('nfc-manager-tech-discovered'); + clearPendingMessages('nfc-manager-tech-lost'); + let test = tests.shift(); if (!test) { cleanUp(); diff --git a/dom/system/gonk/NetworkUtils.cpp b/dom/system/gonk/NetworkUtils.cpp index 3bba0669285f..59534d31735b 100644 --- a/dom/system/gonk/NetworkUtils.cpp +++ b/dom/system/gonk/NetworkUtils.cpp @@ -1556,10 +1556,16 @@ bool NetworkUtils::setWifiTethering(NetworkParams& aOptions) getIFProperties(GET_CHAR(mExternalIfname), interfaceProperties); if (strcmp(interfaceProperties.dns1, "")) { - aOptions.mDns1 = NS_ConvertUTF8toUTF16(interfaceProperties.dns1); + int type = getIpType(interfaceProperties.dns1); + if (type != AF_INET6) { + aOptions.mDns1 = NS_ConvertUTF8toUTF16(interfaceProperties.dns1); + } } if (strcmp(interfaceProperties.dns2, "")) { - aOptions.mDns2 = NS_ConvertUTF8toUTF16(interfaceProperties.dns2); + int type = getIpType(interfaceProperties.dns2); + if (type != AF_INET6) { + aOptions.mDns2 = NS_ConvertUTF8toUTF16(interfaceProperties.dns2); + } } dumpParams(aOptions, "WIFI"); @@ -1582,10 +1588,16 @@ bool NetworkUtils::setUSBTethering(NetworkParams& aOptions) getIFProperties(GET_CHAR(mExternalIfname), interfaceProperties); if (strcmp(interfaceProperties.dns1, "")) { - aOptions.mDns1 = NS_ConvertUTF8toUTF16(interfaceProperties.dns1); + int type = getIpType(interfaceProperties.dns1); + if (type != AF_INET6) { + aOptions.mDns1 = NS_ConvertUTF8toUTF16(interfaceProperties.dns1); + } } if (strcmp(interfaceProperties.dns2, "")) { - aOptions.mDns2 = NS_ConvertUTF8toUTF16(interfaceProperties.dns2); + int type = getIpType(interfaceProperties.dns2); + if (type != AF_INET6) { + aOptions.mDns2 = NS_ConvertUTF8toUTF16(interfaceProperties.dns2); + } } dumpParams(aOptions, "USB"); diff --git a/dom/wifi/WifiWorker.js b/dom/wifi/WifiWorker.js index a4cba2f8aee4..a49ac679e3b3 100644 --- a/dom/wifi/WifiWorker.js +++ b/dom/wifi/WifiWorker.js @@ -2778,6 +2778,11 @@ WifiWorker.prototype = { var timer = null; var self = this; + if (!WifiManager.enabled) { + callback.onfailure(); + return; + } + self.waitForScan(waitForScanCallback); doScan(); function doScan() { diff --git a/gfx/thebes/gfx2DGlue.h b/gfx/thebes/gfx2DGlue.h index 78b9a0630d91..e4267cd2bb6f 100644 --- a/gfx/thebes/gfx2DGlue.h +++ b/gfx/thebes/gfx2DGlue.h @@ -49,6 +49,11 @@ inline Color ToColor(const gfxRGBA &aRGBA) Float(aRGBA.b), Float(aRGBA.a)); } +inline gfxRGBA ThebesColor(Color &aColor) +{ + return gfxRGBA(aColor.r, aColor.g, aColor.b, aColor.a); +} + inline Matrix ToMatrix(const gfxMatrix &aMatrix) { return Matrix(Float(aMatrix.xx), Float(aMatrix.yx), Float(aMatrix.xy), diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index 14160f7f73ee..114017cc0297 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -4871,6 +4871,11 @@ gfxFontGroup::ResolveGenericFontNames(FontFamilyType aGenericType, static const char kGeneric_cursive[] = "cursive"; static const char kGeneric_fantasy[] = "fantasy"; + // treat -moz-fixed as monospace + if (aGenericType == eFamily_moz_fixed) { + aGenericType = eFamily_monospace; + } + // type should be standard generic type at this point NS_ASSERTION(aGenericType >= eFamily_serif && aGenericType <= eFamily_fantasy, diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp index 6509839104c6..2709d56a2377 100644 --- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -539,30 +539,6 @@ gfxPlatform::PreferMemoryOverShmem() const { return mLayersPreferMemoryOverShmem; } -already_AddRefed -gfxPlatform::OptimizeImage(gfxImageSurface *aSurface, - gfxImageFormat format) -{ - IntSize surfaceSize = aSurface->GetSize().ToIntSize(); - -#ifdef XP_WIN - if (gfxWindowsPlatform::GetPlatform()->GetRenderMode() == - gfxWindowsPlatform::RENDER_DIRECT2D) { - return nullptr; - } -#endif - nsRefPtr optSurface = CreateOffscreenSurface(surfaceSize, gfxASurface::ContentFromFormat(format)); - if (!optSurface || optSurface->CairoStatus() != 0) - return nullptr; - - gfxContext tmpCtx(optSurface); - tmpCtx.SetOperator(gfxContext::OPERATOR_SOURCE); - tmpCtx.SetSource(aSurface); - tmpCtx.Paint(); - - return optSurface.forget(); -} - cairo_user_data_key_t kDrawTarget; RefPtr diff --git a/gfx/thebes/gfxPlatform.h b/gfx/thebes/gfxPlatform.h index 8f554566c566..ba6397939c11 100644 --- a/gfx/thebes/gfxPlatform.h +++ b/gfx/thebes/gfxPlatform.h @@ -178,9 +178,6 @@ public: CreateOffscreenSurface(const IntSize& size, gfxContentType contentType) = 0; - virtual already_AddRefed OptimizeImage(gfxImageSurface *aSurface, - gfxImageFormat format); - /** * Beware that these methods may return DrawTargets which are not fully supported * on the current platform and might fail silently in subtle ways. This is a massive diff --git a/image/src/imgFrame.cpp b/image/src/imgFrame.cpp index 7837cf97b8c6..b7e2baf04464 100644 --- a/image/src/imgFrame.cpp +++ b/image/src/imgFrame.cpp @@ -383,16 +383,29 @@ bool imgFrame::Draw(gfxContext *aContext, GraphicsFilter aFilter, bool doPadding = aPadding != nsIntMargin(0,0,0,0); bool doPartialDecode = !ImageComplete(); - RefPtr dt = aContext->GetDrawTarget(); - if (mSinglePixel && !doPadding && !doPartialDecode) { if (mSinglePixelColor.a == 0.0) { return true; } - Rect target(aFill.x, aFill.y, aFill.width, aFill.height); - dt->FillRect(target, ColorPattern(mSinglePixelColor), - DrawOptions(1.0f, CompositionOpForOp(aContext->CurrentOperator()))); + if (aContext->IsCairo()) { + gfxContext::GraphicsOperator op = aContext->CurrentOperator(); + if (op == gfxContext::OPERATOR_OVER && mSinglePixelColor.a == 1.0) { + aContext->SetOperator(gfxContext::OPERATOR_SOURCE); + } + aContext->SetDeviceColor(ThebesColor(mSinglePixelColor)); + aContext->NewPath(); + aContext->Rectangle(aFill); + aContext->Fill(); + aContext->SetOperator(op); + aContext->SetDeviceColor(gfxRGBA(0,0,0,0)); + return true; + } + RefPtr dt = aContext->GetDrawTarget(); + dt->FillRect(ToRect(aFill), + ColorPattern(mSinglePixelColor), + DrawOptions(1.0f, + CompositionOpForOp(aContext->CurrentOperator()))); return true; } diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp index d550555b5371..ec82ec5066f5 100644 --- a/ipc/glue/GeckoChildProcessHost.cpp +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -576,17 +576,19 @@ GeckoChildProcessHost::PerformAsyncLaunchInternal(std::vector& aExt } # if (MOZ_WIDGET_GTK == 3) - const char *ld_preload = PR_GetEnv("LD_PRELOAD"); - nsCString new_ld_preload; + if (mProcessType == GeckoProcessType_Plugin) { + const char *ld_preload = PR_GetEnv("LD_PRELOAD"); + nsCString new_ld_preload; - new_ld_preload.Assign(path.get()); - new_ld_preload.AppendLiteral("/" DLL_PREFIX "mozgtk2" DLL_SUFFIX); + new_ld_preload.Assign(path.get()); + new_ld_preload.AppendLiteral("/" DLL_PREFIX "mozgtk2" DLL_SUFFIX); - if (ld_preload && *ld_preload) { - new_ld_preload.AppendLiteral(":"); - new_ld_preload.Append(ld_preload); + if (ld_preload && *ld_preload) { + new_ld_preload.AppendLiteral(":"); + new_ld_preload.Append(ld_preload); + } + newEnvVars["LD_PRELOAD"] = new_ld_preload.get(); } - newEnvVars["LD_PRELOAD"] = new_ld_preload.get(); # endif // MOZ_WIDGET_GTK diff --git a/js/public/HeapAPI.h b/js/public/HeapAPI.h index f325c3d7f9ca..387292e71e82 100644 --- a/js/public/HeapAPI.h +++ b/js/public/HeapAPI.h @@ -55,11 +55,23 @@ static const uint32_t BLACK = 0; static const uint32_t GRAY = 1; /* - * Constants used to indicate whether a chunk is part of the tenured heap or the - * nusery. + * The "location" field in the Chunk trailer is a bit vector indicting various + * roles of the chunk. + * + * The value 0 for the "location" field is invalid, at least one bit must be + * set. + * + * Some bits preclude others, for example, any "nursery" bit precludes any + * "tenured" or "middle generation" bit. */ -const uint32_t ChunkLocationNursery = 0; -const uint32_t ChunkLocationTenuredHeap = 1; +const uintptr_t ChunkLocationBitNursery = 1; // Standard GGC nursery +const uintptr_t ChunkLocationBitTenuredHeap = 2; // Standard GGC tenured generation +const uintptr_t ChunkLocationBitPJSNewspace = 4; // The PJS generational GC's allocation space +const uintptr_t ChunkLocationBitPJSFromspace = 8; // The PJS generational GC's fromspace (during GC) + +const uintptr_t ChunkLocationAnyNursery = ChunkLocationBitNursery | + ChunkLocationBitPJSNewspace | + ChunkLocationBitPJSFromspace; #ifdef JS_DEBUG /* When downcasting, ensure we are actually the right type. */ @@ -225,9 +237,8 @@ IsInsideNursery(const js::gc::Cell *cell) addr &= ~js::gc::ChunkMask; addr |= js::gc::ChunkLocationOffset; uint32_t location = *reinterpret_cast(addr); - JS_ASSERT(location == gc::ChunkLocationNursery || - location == gc::ChunkLocationTenuredHeap); - return location == gc::ChunkLocationNursery; + JS_ASSERT(location != 0); + return location & ChunkLocationAnyNursery; #else return false; #endif diff --git a/js/public/Utility.h b/js/public/Utility.h index e9eaf46443d8..3205158c8df5 100644 --- a/js/public/Utility.h +++ b/js/public/Utility.h @@ -47,6 +47,7 @@ namespace js {} #define JS_ALLOCATED_TENURED_PATTERN 0x4D #define JS_SWEPT_CODE_PATTERN 0x3b #define JS_SWEPT_FRAME_PATTERN 0x5b +#define JS_POISONED_FORKJOIN_CHUNK 0xBD #define JS_ASSERT(expr) MOZ_ASSERT(expr) #define JS_ASSERT_IF(cond, expr) MOZ_ASSERT_IF(cond, expr) diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js index 4dc281d0fd97..9399ab4e4cd7 100644 --- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -768,7 +768,7 @@ function ArrayMapPar(func, mode) { break parallel; var slicesInfo = ComputeSlicesInfo(length); - ForkJoin(mapThread, 0, slicesInfo.count, ForkJoinMode(mode)); + ForkJoin(mapThread, 0, slicesInfo.count, ForkJoinMode(mode), buffer); return buffer; } @@ -817,7 +817,7 @@ function ArrayReducePar(func, mode) { var numSlices = slicesInfo.count; var subreductions = NewDenseArray(numSlices); - ForkJoin(reduceThread, 0, numSlices, ForkJoinMode(mode)); + ForkJoin(reduceThread, 0, numSlices, ForkJoinMode(mode), null); var accumulator = subreductions[0]; for (var i = 1; i < numSlices; i++) @@ -876,7 +876,7 @@ function ArrayScanPar(func, mode) { var numSlices = slicesInfo.count; // Scan slices individually (see comment on phase1()). - ForkJoin(phase1, 0, numSlices, ForkJoinMode(mode)); + ForkJoin(phase1, 0, numSlices, ForkJoinMode(mode), buffer); // Compute intermediates array (see comment on phase2()). var intermediates = []; @@ -892,7 +892,7 @@ function ArrayScanPar(func, mode) { // We start from slice 1 instead of 0 since there is no work to be done // for slice 0. if (numSlices > 1) - ForkJoin(phase2, 1, numSlices, ForkJoinMode(mode)); + ForkJoin(phase2, 1, numSlices, ForkJoinMode(mode), buffer); return buffer; } @@ -1106,7 +1106,7 @@ function ArrayFilterPar(func, mode) { UnsafePutElements(counts, i, 0); var survivors = new Uint8Array(length); - ForkJoin(findSurvivorsThread, 0, numSlices, ForkJoinMode(mode)); + ForkJoin(findSurvivorsThread, 0, numSlices, ForkJoinMode(mode), survivors); // Step 2. Compress the slices into one contiguous set. var count = 0; @@ -1114,7 +1114,7 @@ function ArrayFilterPar(func, mode) { count += counts[i]; var buffer = NewDenseArray(count); if (count > 0) - ForkJoin(copySurvivorsThread, 0, numSlices, ForkJoinMode(mode)); + ForkJoin(copySurvivorsThread, 0, numSlices, ForkJoinMode(mode), buffer); return buffer; } @@ -1224,7 +1224,7 @@ function ArrayStaticBuildPar(length, func, mode) { break parallel; var slicesInfo = ComputeSlicesInfo(length); - ForkJoin(constructThread, 0, slicesInfo.count, ForkJoinMode(mode)); + ForkJoin(constructThread, 0, slicesInfo.count, ForkJoinMode(mode), buffer); return buffer; } diff --git a/js/src/builtin/TypedObject.js b/js/src/builtin/TypedObject.js index a6846cc7c099..767af8cb4d94 100644 --- a/js/src/builtin/TypedObject.js +++ b/js/src/builtin/TypedObject.js @@ -1182,7 +1182,7 @@ function MapTypedParImplDepth1(inArray, inArrayType, outArrayType, func) { // relative to its owner (which is often but not always 0). const inBaseOffset = TYPEDOBJ_BYTEOFFSET(inArray); - ForkJoin(mapThread, 0, slicesInfo.count, ForkJoinMode(mode)); + ForkJoin(mapThread, 0, slicesInfo.count, ForkJoinMode(mode), outArray); return outArray; function mapThread(workerId, sliceStart, sliceEnd) { @@ -1238,11 +1238,17 @@ function MapTypedParImplDepth1(inArray, inArrayType, outArrayType, func) { inOffset += inGrainTypeSize; outOffset += outGrainTypeSize; +#ifndef JSGC_FJGENERATIONAL // A transparent result type cannot contain references, and // hence there is no way for a pointer to a thread-local object // to escape. + // + // This has been disabled for the PJS generational collector + // as it probably has little effect in that setting and adds + // per-iteration cost. if (outGrainTypeIsTransparent) ClearThreadLocalArenas(); +#endif } } diff --git a/js/src/configure.in b/js/src/configure.in index 80896ae65481..f1edd3a2f083 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -3203,6 +3203,8 @@ MOZ_ARG_DISABLE_BOOL(gcgenerational, if test -n "$JSGC_GENERATIONAL"; then AC_DEFINE(JSGC_GENERATIONAL) fi +JSGC_GENERATIONAL_CONFIGURED=$JSGC_GENERATIONAL +AC_SUBST(JSGC_GENERATIONAL_CONFIGURED) dnl ======================================================== dnl = Use exact stack rooting for GC diff --git a/js/src/gc/ForkJoinNursery-inl.h b/js/src/gc/ForkJoinNursery-inl.h new file mode 100644 index 000000000000..e82caf98d690 --- /dev/null +++ b/js/src/gc/ForkJoinNursery-inl.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef gc_ForkJoinNursery_inl_h +#define gc_ForkJoinNursery_inl_h + +#ifdef JSGC_FJGENERATIONAL + +#include "gc/ForkJoinNursery.h" + +namespace js { +namespace gc { + +// For the following two predicates we can't check the attributes on +// the chunk trailer because it's not known whether addr points into a +// chunk. +// +// A couple of optimizations are possible if performance is an issue: +// +// - The loop can be unrolled, and we can arrange for all array entries +// to be valid for this purpose so that the bound is constant. +// - The per-chunk test can be reduced to testing whether the high bits +// of the object pointer and the high bits of the chunk pointer are +// the same (and the latter value is essentially space[i]). +// Note, experiments with that do not show an improvement yet. +// - Taken together, those optimizations yield code that is one LOAD, +// one XOR, and one AND for each chunk, with the result being true +// iff the resulting value is zero. +// - We can have multiple versions of the predicates, and those that +// take known-good GCThing types can go directly to the attributes; +// it may be possible to ensure that more calls use GCThing types. +// Note, this requires the worker ID to be part of the chunk +// attribute bit vector. +// +// Performance may not be an issue as there may be few survivors of a +// collection in the ForkJoinNursery and few objects will be tested. +// If so then the bulk of the calls may come from the code that scans +// the roots. Behavior will be workload-dependent however. + +MOZ_ALWAYS_INLINE bool +ForkJoinNursery::isInsideNewspace(const void *addr) +{ + uintptr_t p = reinterpret_cast(addr); + for (unsigned i = 0 ; i <= currentChunk_ ; i++) { + if (p >= newspace[i]->start() && p < newspace[i]->end()) + return true; + } + return false; +} + +MOZ_ALWAYS_INLINE bool +ForkJoinNursery::isInsideFromspace(const void *addr) +{ + uintptr_t p = reinterpret_cast(addr); + for (unsigned i = 0 ; i < numFromspaceChunks_ ; i++) { + if (p >= fromspace[i]->start() && p < fromspace[i]->end()) + return true; + } + return false; +} + +template +MOZ_ALWAYS_INLINE bool +ForkJoinNursery::getForwardedPointer(T **ref) +{ + JS_ASSERT(ref); + JS_ASSERT(isInsideFromspace(*ref)); + const RelocationOverlay *overlay = reinterpret_cast(*ref); + if (!overlay->isForwarded()) + return false; + // This static_cast from Cell* restricts T to valid (GC thing) types. + *ref = static_cast(overlay->forwardingAddress()); + return true; +} + +} // namespace gc +} // namespace js + +#endif // JSGC_FJGENERATIONAL + +#endif // gc_ForkJoinNursery_inl_h diff --git a/js/src/gc/ForkJoinNursery.cpp b/js/src/gc/ForkJoinNursery.cpp new file mode 100644 index 000000000000..29ec7a5909d5 --- /dev/null +++ b/js/src/gc/ForkJoinNursery.cpp @@ -0,0 +1,908 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifdef JSGC_FJGENERATIONAL + +#include "gc/ForkJoinNursery-inl.h" + +#include "mozilla/IntegerPrintfMacros.h" + +#include "prmjtime.h" + +#include "gc/Heap.h" +#include "jit/IonFrames.h" +#include "vm/ArrayObject.h" +#include "vm/ForkJoin.h" +#include "vm/TypedArrayObject.h" + +#include "jsgcinlines.h" +#include "gc/Nursery-inl.h" +#include "vm/ObjectImpl-inl.h" + +// The ForkJoinNursery provides an object nursery for movable object +// types for one ForkJoin worker thread. There is a one-to-one +// correspondence between ForkJoinNursery and ForkJoinContext. +// +// For a general overview of how the ForkJoinNursery fits into the +// overall PJS system, see the comment block in vm/ForkJoin.h. +// +// +// Invariants on the ForkJoinNursery: +// +// Let "the tenured area" from the point of view of one +// ForkJoinNursery comprise the global tenured area and the nursery's +// owning worker's private tenured area. Then: +// +// - There can be pointers from the tenured area into a ForkJoinNursery, +// and from the ForkJoinNursery into the tenured area +// +// - There *cannot* be a pointer from one ForkJoinNursery into +// another, or from one private tenured area into another, or from a +// ForkJoinNursery into another worker's private tenured are or vice +// versa, or from any ForkJoinNursery or private tenured area into +// the normal Nursery. +// +// For those invariants to hold the normal Nursery must be empty before +// a ForkJoin section. +// +// +// General description: +// +// The nursery maintains a space into which small, movable objects +// are allocated. Other objects are allocated directly in the private +// tenured area for the worker. +// +// If an allocation request can't be satisfied because the nursery is +// full then a /minor collection/ is triggered without bailouts. This +// collection copies nursery-allocated objects reachable from the +// worker's roots into a fresh space. Then the old space is +// discarded. +// +// Nurseries are maintained in 1MB chunks. If the live data in a +// nursery after a collection exceeds some set fraction (currently +// 1/3) then the nursery is grown, independently of other nurseries. +// +// There is an upper limit on the number of chunks in a nursery. If +// the live data in a nursery after a collection exceeds the set +// fraction and the nursery can't grow, then the next collection will +// be an /evacuating collection/. +// +// An evacuating collection copies nursery-allocated objects reachable +// from the worker's roots into the worker's private tenured area. +// +// If an allocation request in the tenured area - whether the request +// comes from the mutator or from the garbage collector during +// evacuation - can't be satisified because the tenured area is full, +// then the worker bails out and triggers a full collection in the +// ForkJoin worker's zone. This is expected to happen very rarely in +// practice. +// +// The roots for a collection in the ForkJoinNursery are: the frames +// of the execution stack, any registered roots on the execution +// stack, any objects in the private tenured area, and the ForkJoin +// result object in the common tenured area. +// +// The entire private tenured area is considered to be rooted in order +// not to have to run write barriers during the ForkJoin section. +// During a minor or evacuating collection in a worker the GC will +// step through the worker's tenured area, examining each object for +// pointers into the nursery. +// +// The ForkJoinNursery contains its own object tracing machinery for +// most of the types that can be allocated in the nursery. But it +// does not handle all types, and there are two places where the code +// in ForkJoinNursery loses control of the tracing: +// +// - When calling clasp->trace() in traceObject() +// - When calling MarkForkJoinStack() in forwardFromStack() +// +// In both cases: +// +// - We pass a ForkJoinNurseryCollectionTracer object with a callback +// to ForkJoinNursery::MinorGCCallback +// +// - We should only ever end up in MarkInternal() in Marking.cpp, in +// the case in that code that calls back to trc->callback. We +// should /never/ end up in functions that trigger use of the mark +// stack internal to the general GC's marker. +// +// - Any function along the path to MarkInternal() that asks about +// whether something is in the nursery or is tenured /must/ be aware +// that there can be multiple nursery and tenured areas; assertions +// get this wrong a lot of the time and must be fixed when they do. +// In practice, such code either must have a case for each nursery +// kind or must use the IsInsideNursery(Cell*) method, which looks +// only at the chunk tag. +// +// +// Terminological note: +// +// - While the mutator is running it is allocating in what's known as +// the nursery's "newspace". The mutator may also allocate directly +// in the tenured space, but the tenured space is not part of the +// newspace. +// +// - While the gc is running, the previous "newspace" has been renamed +// as the gc's "fromspace", and the space that objects are copied +// into is known as the "tospace". The tospace may be a nursery +// space (during a minor collection), or it may be a tenured space +// (during an evacuation collection), but it's always one or the +// other, never a combination. After gc the fromspace is always +// discarded. +// +// - If the gc copies objects into a nursery tospace then this tospace +// becomes known as the "newspace" following gc. Otherwise, a new +// newspace won't be needed (if the parallel section is finished) or +// can be created empty (if the gc just needed to evacuate). +// +// +// Style note: +// +// - Use js_memcpy, malloc_, realloc_, and js_free uniformly, do not +// use PodCopy or pod_malloc: the type information for the latter is +// not always correct and surrounding code usually operates in terms +// of bytes, anyhow. +// +// With power comes responsibility, etc: code that used pod_malloc +// gets safe size computation built-in; here we must handle that +// manually. + +namespace js { +namespace gc { + +ForkJoinNursery::ForkJoinNursery(ForkJoinContext *cx, ForkJoinGCShared *shared, Allocator *tenured) + : cx_(cx) + , tenured_(tenured) + , shared_(shared) + , evacuationZone_(nullptr) + , currentStart_(0) + , currentEnd_(0) + , position_(0) + , currentChunk_(0) + , numActiveChunks_(0) + , numFromspaceChunks_(0) + , mustEvacuate_(false) + , isEvacuating_(false) + , movedSize_(0) + , head_(nullptr) + , tail_(&head_) + , hugeSlotsNew(0) + , hugeSlotsFrom(1) +{ + for ( size_t i=0 ; i < MaxNurseryChunks ; i++ ) { + newspace[i] = nullptr; + fromspace[i] = nullptr; + } + if (!hugeSlots[hugeSlotsNew].init() || !hugeSlots[hugeSlotsFrom].init()) + CrashAtUnhandlableOOM("Cannot initialize PJS nursery"); + initNewspace(); // This can fail to return +} + +ForkJoinNursery::~ForkJoinNursery() +{ + for ( size_t i=0 ; i < numActiveChunks_ ; i++ ) { + if (newspace[i]) + shared_->freeNurseryChunk(newspace[i]); + } +} + +void +ForkJoinNursery::minorGC() +{ + if (mustEvacuate_) { + mustEvacuate_ = false; + pjsCollection(Evacuate|Recreate); + } else { + pjsCollection(Collect|Recreate); + } +} + +void +ForkJoinNursery::evacuatingGC() +{ + pjsCollection(Evacuate); +} + +#define TIME_START(name) int64_t timstampStart_##name = PRMJ_Now() +#define TIME_END(name) int64_t timstampEnd_##name = PRMJ_Now() +#define TIME_TOTAL(name) (timstampEnd_##name - timstampStart_##name) + +void +ForkJoinNursery::pjsCollection(int op) +{ + JS_ASSERT((op & Collect) != (op & Evacuate)); + + bool evacuate = op & Evacuate; + bool recreate = op & Recreate; + + JS_ASSERT(!isEvacuating_); + JS_ASSERT(!evacuationZone_); + JS_ASSERT(!head_); + JS_ASSERT(tail_ == &head_); + + JSRuntime *const rt = shared_->runtime(); + const unsigned currentNumActiveChunks_ = numActiveChunks_; + const char *msg = ""; + + JS_ASSERT(!rt->needsBarrier()); + + TIME_START(pjsCollection); + + rt->incFJMinorCollecting(); + if (evacuate) { + isEvacuating_ = true; + evacuationZone_ = shared_->zone(); + } + + flip(); + if (recreate) { + initNewspace(); + // newspace must be at least as large as fromSpace + numActiveChunks_ = currentNumActiveChunks_; + } + ForkJoinNurseryCollectionTracer trc(rt, this); + forwardFromRoots(&trc); + collectToFixedPoint(&trc); +#ifdef JS_ION + jit::UpdateJitActivationsForMinorGC(TlsPerThreadData.get(), &trc); +#endif + freeFromspace(); + + size_t live = movedSize_; + computeNurserySizeAfterGC(live, &msg); + + sweepHugeSlots(); + JS_ASSERT(hugeSlots[hugeSlotsFrom].empty()); + JS_ASSERT_IF(isEvacuating_, hugeSlots[hugeSlotsNew].empty()); + + isEvacuating_ = false; + evacuationZone_ = nullptr; + head_ = nullptr; + tail_ = &head_; + movedSize_ = 0; + + rt->decFJMinorCollecting(); + + TIME_END(pjsCollection); + + // Note, the spew is awk-friendly, non-underlined words serve as markers: + // FJGC _tag_ us _value_ copied _value_ size _value_ _message-word_ ... + shared_->spewGC("FJGC %s us %5" PRId64 " copied %7" PRIu64 " size %" PRIu64 " %s", + (evacuate ? "evacuate " : "collect "), + TIME_TOTAL(pjsCollection), + (uint64_t)live, + (uint64_t)numActiveChunks_*1024*1024, + msg); +} + +#undef TIME_START +#undef TIME_END +#undef TIME_TOTAL + +void +ForkJoinNursery::computeNurserySizeAfterGC(size_t live, const char **msg) +{ + // Grow the nursery if it is too full. Do not bother to shrink it - lazy + // chunk allocation means that a too-large nursery will not really be a problem, + // the entire nursery will be deallocated soon anyway. + if (live * NurseryLoadFactor > numActiveChunks_ * ForkJoinNurseryChunk::UsableSize) { + if (numActiveChunks_ < MaxNurseryChunks) { + while (numActiveChunks_ < MaxNurseryChunks && + live * NurseryLoadFactor > numActiveChunks_ * ForkJoinNurseryChunk::UsableSize) + { + ++numActiveChunks_; + } + } else { + // Evacuation will tend to drive us toward the cliff of a bailout GC, which + // is not good, probably worse than working within the thread at a higher load + // than desirable. + // + // Thus it's possible to be more sophisticated than this: + // + // - evacuate only after several minor GCs in a row exceeded the set load + // - evacuate only if significantly less space than required is available, eg, + // if only 1/2 the required free space is available + *msg = " Overfull, will evacuate next"; + mustEvacuate_ = true; + } + } +} + +void +ForkJoinNursery::flip() +{ + size_t i; + for (i=0; i < numActiveChunks_; i++) { + if (!newspace[i]) + break; + fromspace[i] = newspace[i]; + newspace[i] = nullptr; + fromspace[i]->trailer.location = gc::ChunkLocationBitPJSFromspace; + } + numFromspaceChunks_ = i; + numActiveChunks_ = 0; + + int tmp = hugeSlotsNew; + hugeSlotsNew = hugeSlotsFrom; + hugeSlotsFrom = tmp; + + JS_ASSERT(hugeSlots[hugeSlotsNew].empty()); +} + +void +ForkJoinNursery::freeFromspace() +{ + for (size_t i=0; i < numFromspaceChunks_; i++) { + shared_->freeNurseryChunk(fromspace[i]); + fromspace[i] = nullptr; + } + numFromspaceChunks_ = 0; +} + +void +ForkJoinNursery::initNewspace() +{ + JS_ASSERT(newspace[0] == nullptr); + JS_ASSERT(numActiveChunks_ == 0); + + numActiveChunks_ = 1; + setCurrentChunk(0); +} + +MOZ_ALWAYS_INLINE bool +ForkJoinNursery::shouldMoveObject(void **thingp) +{ + // Note that thingp must really be a T** where T is some GCThing, + // ie, something that lives in a chunk (or nullptr). This should + // be the case because the MinorGCCallback is only called on exact + // roots on the stack or slots within in tenured objects and not + // on slot/element arrays that can be malloc'd; they are forwarded + // using the forwardBufferPointer() mechanism. + // + // The main reason for that restriction is so that we can call a + // method here that can check the chunk trailer for the cell (a + // future optimization). + Cell *cell = static_cast(*thingp); + return isInsideFromspace(cell) && !getForwardedPointer(thingp); +} + +/* static */ void +ForkJoinNursery::MinorGCCallback(JSTracer *trcArg, void **thingp, JSGCTraceKind traceKind) +{ + // traceKind can be all sorts of things, when we're marking from stack roots + ForkJoinNursery *nursery = static_cast(trcArg)->nursery_; + if (nursery->shouldMoveObject(thingp)) { + // When other types of objects become nursery-allocable then the static_cast + // to JSObject * will no longer be valid. + JS_ASSERT(traceKind == JSTRACE_OBJECT); + *thingp = nursery->moveObjectToTospace(static_cast(*thingp)); + } +} + +void +ForkJoinNursery::forwardFromRoots(ForkJoinNurseryCollectionTracer *trc) +{ + // There should be no other roots as a result of effect-freedom. + forwardFromUpdatable(trc); + forwardFromStack(trc); + forwardFromTenured(trc); +} + +void +ForkJoinNursery::forwardFromUpdatable(ForkJoinNurseryCollectionTracer *trc) +{ + JSObject *obj = shared_->updatable(); + if (obj) + traceObject(trc, obj); +} + +void +ForkJoinNursery::forwardFromStack(ForkJoinNurseryCollectionTracer *trc) +{ + MarkForkJoinStack(trc); +} + +void +ForkJoinNursery::forwardFromTenured(ForkJoinNurseryCollectionTracer *trc) +{ + JSObject *objs[ArenaCellCount]; + for (size_t k=0; k < FINALIZE_LIMIT; k++) { + AllocKind kind = (AllocKind)k; + if (!IsFJNurseryAllocable(kind)) + continue; + + // When non-JSObject types become nursery-allocable the assumptions in the + // loops below will no longer hold; other types than JSObject must be + // handled. + JS_ASSERT(kind <= FINALIZE_OBJECT_LAST); + + ArenaIter ai; + ai.init(const_cast(tenured_), kind); + for (; !ai.done(); ai.next()) { + // Do the walk in two steps to avoid problems resulting from allocating + // into the arena that's being walked: ArenaCellIter is not safe for that. + // It can happen during evacuation. + // + // ArenaCellIterUnderFinalize requires any free list to be flushed into + // its arena, and since we may allocate within traceObject() we must + // purge before each arena scan. This is probably not very expensive, + // it's constant work, and inlined. + // + // Use ArenaCellIterUnderFinalize, not ...UnderGC, because that side-steps + // some assertions in the latter that are wrong for PJS collection. + size_t numObjs = 0; + tenured_->arenas.purge(kind); + for (ArenaCellIterUnderFinalize i(ai.get()); !i.done(); i.next()) + objs[numObjs++] = i.get(); + for (size_t i=0; i < numObjs; i++) + traceObject(trc, objs[i]); + } + } +} + +/*static*/ void +ForkJoinNursery::forwardBufferPointer(JSTracer *trc, HeapSlot **pSlotsElems) +{ + ForkJoinNursery *nursery = static_cast(trc)->nursery_; + HeapSlot *old = *pSlotsElems; + + if (!nursery->isInsideFromspace(old)) + return; + + // If the elements buffer is zero length, the "first" item could be inside + // of the next object or past the end of the allocable area. However, + // since we always store the runtime as the last word in a nursery chunk, + // isInsideFromspace will still be true, even if this zero-size allocation + // abuts the end of the allocable area. Thus, it is always safe to read the + // first word of |old| here. + *pSlotsElems = *reinterpret_cast(old); + JS_ASSERT(!nursery->isInsideFromspace(*pSlotsElems)); +} + +void +ForkJoinNursery::collectToFixedPoint(ForkJoinNurseryCollectionTracer *trc) +{ + for (RelocationOverlay *p = head_; p; p = p->next()) + traceObject(trc, static_cast(p->forwardingAddress())); +} + +inline void +ForkJoinNursery::setCurrentChunk(int index) +{ + JS_ASSERT((size_t)index < numActiveChunks_); + JS_ASSERT(!newspace[index]); + + currentChunk_ = index; + ForkJoinNurseryChunk *c = shared_->allocateNurseryChunk(); + if (!c) + CrashAtUnhandlableOOM("Cannot expand PJS nursery"); + c->trailer.runtime = shared_->runtime(); + c->trailer.location = gc::ChunkLocationBitPJSNewspace; + c->trailer.storeBuffer = nullptr; + currentStart_ = c->start(); + currentEnd_ = c->end(); + position_ = currentStart_; + newspace[index] = c; +} + +void * +ForkJoinNursery::allocate(size_t size) +{ + JS_ASSERT(position_ >= currentStart_); + + if (currentEnd_ - position_ < size) { + if (currentChunk_ + 1 == numActiveChunks_) + return nullptr; + setCurrentChunk(currentChunk_ + 1); + } + + void *thing = reinterpret_cast(position_); + position_ += size; + + JS_POISON(thing, JS_ALLOCATED_NURSERY_PATTERN, size); + return thing; +} + +JSObject * +ForkJoinNursery::allocateObject(size_t baseSize, size_t numDynamic, bool& tooLarge) +{ + // Ensure there's enough space to replace the contents with a RelocationOverlay. + JS_ASSERT(baseSize >= sizeof(js::gc::RelocationOverlay)); + + // Too-large slot arrays cannot be accomodated. + if (numDynamic > MaxNurserySlots) { + tooLarge = true; + return nullptr; + } + + // Allocate slots contiguously after the object. + size_t totalSize = baseSize + sizeof(HeapSlot) * numDynamic; + JSObject *obj = static_cast(allocate(totalSize)); + if (!obj) { + tooLarge = false; + return nullptr; + } + obj->setInitialSlots(numDynamic + ? reinterpret_cast(size_t(obj) + baseSize) + : nullptr); + return obj; +} + +HeapSlot * +ForkJoinNursery::allocateSlots(JSObject *obj, uint32_t nslots) +{ + JS_ASSERT(obj); + JS_ASSERT(nslots > 0); + + if (nslots & mozilla::tl::MulOverflowMask::value) + return nullptr; + size_t size = nslots * sizeof(HeapSlot); + + if (!isInsideNewspace(obj)) + return reinterpret_cast(cx_->malloc_(size)); + + if (nslots > MaxNurserySlots) + return allocateHugeSlots(nslots); + + HeapSlot *slots = static_cast(allocate(size)); + if (slots) + return slots; + + return allocateHugeSlots(nslots); +} + +HeapSlot * +ForkJoinNursery::reallocateSlots(JSObject *obj, HeapSlot *oldSlots, + uint32_t oldCount, uint32_t newCount) +{ + if (newCount & mozilla::tl::MulOverflowMask::value) + return nullptr; + + size_t oldSize = oldCount * sizeof(HeapSlot); + size_t newSize = newCount * sizeof(HeapSlot); + + if (!isInsideNewspace(obj)) { + JS_ASSERT_IF(oldSlots, !isInsideNewspace(oldSlots)); + return static_cast(cx_->realloc_(oldSlots, oldSize, newSize)); + } + + if (!isInsideNewspace(oldSlots)) + return reallocateHugeSlots(oldSlots, oldSize, newSize); + + // No-op if we're shrinking, we can't make use of the freed portion. + if (newCount < oldCount) + return oldSlots; + + HeapSlot *newSlots = allocateSlots(obj, newCount); + if (!newSlots) + return nullptr; + + js_memcpy(newSlots, oldSlots, oldSize); + return newSlots; +} + +ObjectElements * +ForkJoinNursery::allocateElements(JSObject *obj, uint32_t nelems) +{ + JS_ASSERT(nelems >= ObjectElements::VALUES_PER_HEADER); + return reinterpret_cast(allocateSlots(obj, nelems)); +} + +ObjectElements * +ForkJoinNursery::reallocateElements(JSObject *obj, ObjectElements *oldHeader, + uint32_t oldCount, uint32_t newCount) +{ + HeapSlot *slots = reallocateSlots(obj, reinterpret_cast(oldHeader), + oldCount, newCount); + return reinterpret_cast(slots); +} + +void +ForkJoinNursery::freeSlots(HeapSlot *slots) +{ + if (!isInsideNewspace(slots)) { + hugeSlots[hugeSlotsNew].remove(slots); + js_free(slots); + } +} + +HeapSlot * +ForkJoinNursery::allocateHugeSlots(size_t nslots) +{ + if (nslots & mozilla::tl::MulOverflowMask::value) + return nullptr; + + size_t size = nslots * sizeof(HeapSlot); + HeapSlot *slots = reinterpret_cast(cx_->malloc_(size)); + if (!slots) + return slots; + + // If this put fails, we will only leak the slots. + (void)hugeSlots[hugeSlotsNew].put(slots); + return slots; +} + +HeapSlot * +ForkJoinNursery::reallocateHugeSlots(HeapSlot *oldSlots, uint32_t oldSize, uint32_t newSize) +{ + HeapSlot *newSlots = static_cast(cx_->realloc_(oldSlots, oldSize, newSize)); + if (!newSlots) + return newSlots; + + if (oldSlots != newSlots) { + hugeSlots[hugeSlotsNew].remove(oldSlots); + // If this put fails, we will only leak the slots. + (void)hugeSlots[hugeSlotsNew].put(newSlots); + } + return newSlots; +} + +void +ForkJoinNursery::sweepHugeSlots() +{ + for (HugeSlotsSet::Range r = hugeSlots[hugeSlotsFrom].all(); !r.empty(); r.popFront()) + js_free(r.front()); + hugeSlots[hugeSlotsFrom].clear(); +} + +MOZ_ALWAYS_INLINE void +ForkJoinNursery::traceObject(ForkJoinNurseryCollectionTracer *trc, JSObject *obj) +{ + const Class *clasp = obj->getClass(); + if (clasp->trace) + clasp->trace(trc, obj); + + if (!obj->isNative()) + return; + + if (!obj->hasEmptyElements()) + markSlots(obj->getDenseElements(), obj->getDenseInitializedLength()); + + HeapSlot *fixedStart, *fixedEnd, *dynStart, *dynEnd; + obj->getSlotRange(0, obj->slotSpan(), &fixedStart, &fixedEnd, &dynStart, &dynEnd); + markSlots(fixedStart, fixedEnd); + markSlots(dynStart, dynEnd); +} + +MOZ_ALWAYS_INLINE void +ForkJoinNursery::markSlots(HeapSlot *vp, uint32_t nslots) +{ + markSlots(vp, vp + nslots); +} + +MOZ_ALWAYS_INLINE void +ForkJoinNursery::markSlots(HeapSlot *vp, HeapSlot *end) +{ + for (; vp != end; ++vp) + markSlot(vp); +} + +MOZ_ALWAYS_INLINE void +ForkJoinNursery::markSlot(HeapSlot *slotp) +{ + if (!slotp->isObject()) + return; + + JSObject *obj = &slotp->toObject(); + if (!isInsideFromspace(obj)) + return; + + if (getForwardedPointer(&obj)) { + slotp->unsafeGet()->setObject(*obj); + return; + } + + JSObject *moved = static_cast(moveObjectToTospace(obj)); + slotp->unsafeGet()->setObject(*moved); +} + +AllocKind +ForkJoinNursery::getObjectAllocKind(JSObject *obj) +{ + if (obj->is()) { + JS_ASSERT(obj->numFixedSlots() == 0); + + // Use minimal size object if we are just going to copy the pointer. + if (!isInsideFromspace((void *)obj->getElementsHeader())) + return FINALIZE_OBJECT0_BACKGROUND; + + size_t nelements = obj->getDenseCapacity(); + return GetBackgroundAllocKind(GetGCArrayKind(nelements)); + } + + if (obj->is()) + return obj->as().getAllocKind(); + + AllocKind kind = GetGCObjectFixedSlotsKind(obj->numFixedSlots()); + JS_ASSERT(!IsBackgroundFinalized(kind)); + JS_ASSERT(CanBeFinalizedInBackground(kind, obj->getClass())); + return GetBackgroundAllocKind(kind); +} + +void * +ForkJoinNursery::allocateInTospace(gc::AllocKind thingKind) +{ + size_t thingSize = Arena::thingSize(thingKind); + if (isEvacuating_) { + void *t = tenured_->arenas.allocateFromFreeList(thingKind, thingSize); + if (t) + return t; + tenured_->arenas.checkEmptyFreeList(thingKind); + // This call may return NULL but should do so only if memory + // is truly exhausted. However, allocateFromArena() can fail + // either because memory is exhausted or if the allocation + // budget is used up. There is a guard in + // Chunk::allocateArena() against the latter case. + return tenured_->arenas.allocateFromArena(evacuationZone_, thingKind); + } else { + // Nursery allocation will never fail during GC - apart from + // true OOM - since newspace is at least as large as + // fromspace; true OOM is caught and signaled within + // ForkJoinNursery::setCurrentChunk(). + return allocate(thingSize); + } +} + +void * +ForkJoinNursery::allocateInTospace(size_t nelem, size_t elemSize) +{ + if (isEvacuating_) + return evacuationZone_->malloc_(nelem * elemSize); + return allocate(nelem * elemSize); +} + +MOZ_ALWAYS_INLINE void +ForkJoinNursery::insertIntoFixupList(RelocationOverlay *entry) +{ + *tail_ = entry; + tail_ = &entry->next_; + *tail_ = nullptr; +} + +void * +ForkJoinNursery::moveObjectToTospace(JSObject *src) +{ + AllocKind dstKind = getObjectAllocKind(src); + JSObject *dst = static_cast(allocateInTospace(dstKind)); + if (!dst) + CrashAtUnhandlableOOM("Failed to allocate object while moving object."); + + movedSize_ += copyObjectToTospace(dst, src, dstKind); + + RelocationOverlay *overlay = reinterpret_cast(src); + overlay->forwardTo(dst); + insertIntoFixupList(overlay); + + return static_cast(dst); +} + +size_t +ForkJoinNursery::copyObjectToTospace(JSObject *dst, JSObject *src, AllocKind dstKind) +{ + size_t srcSize = Arena::thingSize(dstKind); + size_t movedSize = srcSize; + + // Arrays do not necessarily have the same AllocKind between src and dst. + // We deal with this by copying elements manually, possibly re-inlining + // them if there is adequate room inline in dst. + if (src->is()) + srcSize = movedSize = sizeof(ObjectImpl); + + js_memcpy(dst, src, srcSize); + movedSize += copySlotsToTospace(dst, src, dstKind); + movedSize += copyElementsToTospace(dst, src, dstKind); + + if (src->is()) + dst->setPrivate(dst->fixedData(TypedArrayObject::FIXED_DATA_START)); + + // The shape's list head may point into the old object. + if (&src->shape_ == dst->shape_->listp) { + JS_ASSERT(cx_->isThreadLocal(dst->shape_.get())); + dst->shape_->listp = &dst->shape_; + } + + return movedSize; +} + +size_t +ForkJoinNursery::copySlotsToTospace(JSObject *dst, JSObject *src, AllocKind dstKind) +{ + // Fixed slots have already been copied over. + if (!src->hasDynamicSlots()) + return 0; + + if (!isInsideFromspace(src->slots)) { + hugeSlots[hugeSlotsFrom].remove(src->slots); + if (!isEvacuating_) + hugeSlots[hugeSlotsNew].put(src->slots); + return 0; + } + + size_t count = src->numDynamicSlots(); + dst->slots = reinterpret_cast(allocateInTospace(count, sizeof(HeapSlot))); + if (!dst->slots) + CrashAtUnhandlableOOM("Failed to allocate slots while moving object."); + js_memcpy(dst->slots, src->slots, count * sizeof(HeapSlot)); + setSlotsForwardingPointer(src->slots, dst->slots, count); + return count * sizeof(HeapSlot); +} + +size_t +ForkJoinNursery::copyElementsToTospace(JSObject *dst, JSObject *src, AllocKind dstKind) +{ + if (src->hasEmptyElements()) + return 0; + + ObjectElements *srcHeader = src->getElementsHeader(); + ObjectElements *dstHeader; + + // TODO Bug 874151: Prefer to put element data inline if we have space. + // (Note, not a correctness issue.) + if (!isInsideFromspace(srcHeader)) { + JS_ASSERT(src->elements == dst->elements); + hugeSlots[hugeSlotsFrom].remove(reinterpret_cast(srcHeader)); + if (!isEvacuating_) + hugeSlots[hugeSlotsNew].put(reinterpret_cast(srcHeader)); + return 0; + } + + size_t nslots = ObjectElements::VALUES_PER_HEADER + srcHeader->capacity; + + // Unlike other objects, Arrays can have fixed elements. + if (src->is() && nslots <= GetGCKindSlots(dstKind)) { + dst->setFixedElements(); + dstHeader = dst->getElementsHeader(); + js_memcpy(dstHeader, srcHeader, nslots * sizeof(HeapSlot)); + setElementsForwardingPointer(srcHeader, dstHeader, nslots); + return nslots * sizeof(HeapSlot); + } + + JS_ASSERT(nslots >= 2); + dstHeader = reinterpret_cast(allocateInTospace(nslots, sizeof(HeapSlot))); + if (!dstHeader) + CrashAtUnhandlableOOM("Failed to allocate elements while moving object."); + js_memcpy(dstHeader, srcHeader, nslots * sizeof(HeapSlot)); + setElementsForwardingPointer(srcHeader, dstHeader, nslots); + dst->elements = dstHeader->elements(); + return nslots * sizeof(HeapSlot); +} + +void +ForkJoinNursery::setSlotsForwardingPointer(HeapSlot *oldSlots, HeapSlot *newSlots, uint32_t nslots) +{ + JS_ASSERT(nslots > 0); + JS_ASSERT(isInsideFromspace(oldSlots)); + JS_ASSERT(!isInsideFromspace(newSlots)); + *reinterpret_cast(oldSlots) = newSlots; +} + +void +ForkJoinNursery::setElementsForwardingPointer(ObjectElements *oldHeader, ObjectElements *newHeader, + uint32_t nelems) +{ + // If the JIT has hoisted a zero length pointer, then we do not need to + // relocate it because reads and writes to/from this pointer are invalid. + if (nelems - ObjectElements::VALUES_PER_HEADER < 1) + return; + JS_ASSERT(isInsideFromspace(oldHeader)); + JS_ASSERT(!isInsideFromspace(newHeader)); + *reinterpret_cast(oldHeader->elements()) = newHeader->elements(); +} + +ForkJoinNurseryCollectionTracer::ForkJoinNurseryCollectionTracer(JSRuntime *rt, + ForkJoinNursery *nursery) + : JSTracer(rt, ForkJoinNursery::MinorGCCallback, TraceWeakMapKeysValues) + , nursery_(nursery) +{ + JS_ASSERT(rt); + JS_ASSERT(nursery); +} + +} // namespace gc +} // namespace js + +#endif /* JSGC_FJGENERATIONAL */ diff --git a/js/src/gc/ForkJoinNursery.h b/js/src/gc/ForkJoinNursery.h new file mode 100644 index 000000000000..82baa5ced85c --- /dev/null +++ b/js/src/gc/ForkJoinNursery.h @@ -0,0 +1,297 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef gc_ForkJoinNursery_h +#define gc_ForkJoinNursery_h + +#ifdef JSGC_FJGENERATIONAL + +#ifndef JSGC_GENERATIONAL +#error "JSGC_GENERATIONAL is required for the ForkJoinNursery" +#endif +#ifndef JS_THREADSAFE +#error "JS_THREADSAFE is required for the ForkJoinNursery" +#endif +#ifndef JS_ION +#error "JS_ION is required for the ForkJoinNursery" +#endif + +#include "jsalloc.h" +#include "jspubtd.h" + +#include "gc/Heap.h" +#include "gc/Memory.h" +#include "gc/Nursery.h" + +#include "js/HashTable.h" +#include "js/TracingAPI.h" + +namespace js { +class ObjectElements; +class HeapSlot; +class ForkJoinShared; +} + +namespace js { +namespace gc { + +class ForkJoinGCShared; +class ForkJoinNursery; +class ForkJoinNurseryCollectionTracer; + +// This tracer comes into play when a class has a tracer function, but +// is otherwise unused and has no other functionality. +// +// It could look like this could be merged into ForkJoinNursery by +// making the latter derive from JSTracer; I've decided to keep them +// separate for now, since it allows for multiple instantiations of +// this class with different parameters, for different purposes. That +// may change. + +class ForkJoinNurseryCollectionTracer : public JSTracer +{ + friend class ForkJoinNursery; + + public: + ForkJoinNurseryCollectionTracer(JSRuntime *rt, ForkJoinNursery *nursery); + + private: + ForkJoinNursery *const nursery_; +}; + +// The layout for a chunk used by the ForkJoinNursery. + +struct ForkJoinNurseryChunk +{ + // The amount of space in the mapped nursery available to allocations + static const size_t UsableSize = ChunkSize - sizeof(ChunkTrailer); + + char data[UsableSize]; + ChunkTrailer trailer; + uintptr_t start() { return uintptr_t(&data); } + uintptr_t end() { return uintptr_t(&trailer); } +}; + +// A GC adapter to ForkJoinShared, which is a complex class hidden +// inside ForkJoin.cpp. + +class ForkJoinGCShared +{ + public: + ForkJoinGCShared(ForkJoinShared *shared) : shared_(shared) {} + + JSRuntime *runtime(); + JS::Zone *zone(); + + // The updatable object (the ForkJoin result array), or nullptr. + JSObject *updatable(); + + // allocateNurseryChunk() returns nullptr on oom. + ForkJoinNurseryChunk *allocateNurseryChunk(); + + // p must have been obtained through allocateNurseryChunk. + void freeNurseryChunk(ForkJoinNurseryChunk *p); + + // GC statistics output. + void spewGC(const char *fmt, ...); + + private: + ForkJoinShared *const shared_; +}; + +// There is one ForkJoinNursery per ForkJoin worker. +// +// See the comment in ForkJoinNursery.cpp about how it works. + +class ForkJoinNursery +{ + friend class ForkJoinNurseryCollectionTracer; + friend class RelocationOverlay; + + static_assert(sizeof(ForkJoinNurseryChunk) == ChunkSize, + "ForkJoinNursery chunk size must match Chunk size."); + public: + ForkJoinNursery(ForkJoinContext *cx, ForkJoinGCShared *shared, Allocator *tenured); + ~ForkJoinNursery(); + + // Perform a collection within the nursery, and if that for some reason + // cannot be done then perform an evacuating collection. + void minorGC(); + + // Evacuate the live data from the nursery into the tenured area; + // do not recreate the nursery. + void evacuatingGC(); + + // Allocate an object with a number of dynamic slots. Returns an + // object, or nullptr in one of two circumstances: + // + // - The nursery was full, the collector must be run, and the + // allocation must be retried. tooLarge is set to 'false'. + // - The number of dynamic slots requested is too large and + // the object should be allocated in the tenured area. + // tooLarge is set to 'true'. + // + // This method will never run the garbage collector. + JSObject *allocateObject(size_t size, size_t numDynamic, bool& tooLarge); + + // Allocate and reallocate slot and element arrays for existing + // objects. These will create or maintain the arrays within the + // nursery if possible and appropriate, and otherwise will fall + // back to allocating in the tenured area. They will return + // nullptr only if memory is exhausted. If the reallocate methods + // return nullptr then the old array is still live. + // + // These methods will never run the garbage collector. + HeapSlot *allocateSlots(JSObject *obj, uint32_t nslots); + HeapSlot *reallocateSlots(JSObject *obj, HeapSlot *oldSlots, + uint32_t oldCount, uint32_t newCount); + ObjectElements *allocateElements(JSObject *obj, uint32_t nelems); + ObjectElements *reallocateElements(JSObject *obj, ObjectElements *oldHeader, + uint32_t oldCount, uint32_t newCount); + + // Free a slots array. + void freeSlots(HeapSlot *slots); + + // The method embedded in a ForkJoinNurseryCollectionTracer + static void MinorGCCallback(JSTracer *trcArg, void **thingp, JSGCTraceKind kind); + + // A method called from the JIT frame updater + static void forwardBufferPointer(JSTracer *trc, HeapSlot **pSlotsElems); + + // Return true iff obj is inside the current newspace. + MOZ_ALWAYS_INLINE bool isInsideNewspace(const void *obj); + + // Return true iff collection is ongoing and obj is inside the current fromspace. + MOZ_ALWAYS_INLINE bool isInsideFromspace(const void *obj); + + template + MOZ_ALWAYS_INLINE bool getForwardedPointer(T **ref); + + static size_t offsetOfPosition() { + return offsetof(ForkJoinNursery, position_); + } + + static size_t offsetOfCurrentEnd() { + return offsetof(ForkJoinNursery, currentEnd_); + } + + private: + // The largest slot arrays that will be allocated in the nursery. + // On the one hand we want this limit to be large, to avoid + // managing many hugeSlots. On the other hand, slot arrays have + // to be copied during GC and will induce some external + // fragmentation in the nursery at chunk boundaries. + static const size_t MaxNurserySlots = 2048; + + // The fixed limit on the per-worker nursery, in chunks. + // + // For production runs, 16 may be good - programs that need it, + // really need it, and as allocation is lazy programs that don't + // need it won't suck up a lot of resources. + // + // For debugging runs, 1 or 2 may sometimes be good, because it + // will more easily provoke bugs in the evacuation paths. + static const size_t MaxNurseryChunks = 16; + + // The inverse load factor in the per-worker nursery. Grow the nursery + // or schedule an evacuation if more than 1/NurseryLoadFactor of the + // current nursery size is live after minor GC. + static const int NurseryLoadFactor = 3; + + // Allocate an object in the nursery's newspace. Return nullptr + // when allocation fails (ie the object can't fit in the current + // chunk and the number of chunks it at its maximum). + void *allocate(size_t size); + + // Allocate an external slot array and register it with this nursery. + HeapSlot *allocateHugeSlots(size_t nslots); + + // Reallocate an external slot array, unregister the old array and + // register the new array. If the allocation fails then leave + // everything unchanged. + HeapSlot *reallocateHugeSlots(HeapSlot *oldSlots, uint32_t oldSize, uint32_t newSize); + + // Walk the list of registered slot arrays and free them all. + void sweepHugeSlots(); + + // Set the position/end pointers to correspond to the numbered + // chunk. + void setCurrentChunk(int index); + + enum PJSCollectionOp { + Evacuate = 1, + Collect = 2, + Recreate = 4 + }; + + // Misc GC internals. + void pjsCollection(int op /* A combination of PJSCollectionOp bits */); + void initNewspace(); + void flip(); + void forwardFromRoots(ForkJoinNurseryCollectionTracer *trc); + void forwardFromUpdatable(ForkJoinNurseryCollectionTracer *trc); + void forwardFromStack(ForkJoinNurseryCollectionTracer *trc); + void forwardFromTenured(ForkJoinNurseryCollectionTracer *trc); + void collectToFixedPoint(ForkJoinNurseryCollectionTracer *trc); + void freeFromspace(); + void computeNurserySizeAfterGC(size_t live, const char **msg); + + AllocKind getObjectAllocKind(JSObject *src); + void *allocateInTospace(AllocKind thingKind); + void *allocateInTospace(size_t nelem, size_t elemSize); + MOZ_ALWAYS_INLINE bool shouldMoveObject(void **thingp); + void *moveObjectToTospace(JSObject *src); + size_t copyObjectToTospace(JSObject *dst, JSObject *src, gc::AllocKind dstKind); + size_t copyElementsToTospace(JSObject *dst, JSObject *src, gc::AllocKind dstKind); + size_t copySlotsToTospace(JSObject *dst, JSObject *src, gc::AllocKind dstKind); + MOZ_ALWAYS_INLINE void insertIntoFixupList(RelocationOverlay *entry); + + void setSlotsForwardingPointer(HeapSlot *oldSlots, HeapSlot *newSlots, uint32_t nslots); + void setElementsForwardingPointer(ObjectElements *oldHeader, ObjectElements *newHeader, + uint32_t nelems); + + MOZ_ALWAYS_INLINE void traceObject(ForkJoinNurseryCollectionTracer *trc, JSObject *obj); + MOZ_ALWAYS_INLINE void markSlots(HeapSlot *vp, uint32_t nslots); + MOZ_ALWAYS_INLINE void markSlots(HeapSlot *vp, HeapSlot *end); + MOZ_ALWAYS_INLINE void markSlot(HeapSlot *slotp); + + ForkJoinContext *const cx_; // The context that owns this nursery + Allocator *const tenured_; // Private tenured area + ForkJoinGCShared *const shared_; // Common to all nurseries belonging to a ForkJoin instance + JS::Zone *evacuationZone_; // During evacuating GC this is non-NULL: the Zone we + // allocate into + + uintptr_t currentStart_; // Start of current area in newspace + uintptr_t currentEnd_; // End of current area in newspace (last byte + 1) + uintptr_t position_; // Next free byte in current newspace chunk + unsigned currentChunk_; // Index of current / highest numbered chunk in newspace + unsigned numActiveChunks_; // Number of active chunks in newspace, not all may be allocated + unsigned numFromspaceChunks_; // Number of active chunks in fromspace, all are allocated + bool mustEvacuate_; // Set to true after GC when the /next/ minor GC must evacuate + + bool isEvacuating_; // Set to true when the current minor GC is evacuating + size_t movedSize_; // Bytes copied during the current minor GC + RelocationOverlay *head_; // First node of relocation list + RelocationOverlay **tail_; // Pointer to 'next_' field of last node of relocation list + + typedef HashSet, SystemAllocPolicy> HugeSlotsSet; + + HugeSlotsSet hugeSlots[2]; // Hash sets for huge slots + + int hugeSlotsNew; // Huge slot arrays in the newspace (index in hugeSlots) + int hugeSlotsFrom; // Huge slot arrays in the fromspace (index in hugeSlots) + + ForkJoinNurseryChunk *newspace[MaxNurseryChunks]; // All allocation happens here + ForkJoinNurseryChunk *fromspace[MaxNurseryChunks]; // Meaningful during GC: the previous newspace +}; + +} // namespace gc +} // namespace js + +#endif // JSGC_FJGENERATIONAL + +#endif // gc_ForkJoinNursery_h diff --git a/js/src/gc/GCInternals.h b/js/src/gc/GCInternals.h index 7202a98311dd..368f27e4a06d 100644 --- a/js/src/gc/GCInternals.h +++ b/js/src/gc/GCInternals.h @@ -19,6 +19,13 @@ namespace gc { void MarkPersistentRootedChains(JSTracer *trc); +#ifdef JSGC_FJGENERATIONAL +class ForkJoinNurseryCollectionTracer; + +void +MarkForkJoinStack(ForkJoinNurseryCollectionTracer *trc); +#endif + class AutoCopyFreeListToArenas { JSRuntime *runtime; diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index 140cb4e7d761..c12d49a2ceff 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -484,6 +484,16 @@ class GCRuntime js::gc::StoreBuffer storeBuffer; #endif + /* + * ForkJoin workers enter and leave GC independently; this counter + * tracks the number that are currently in GC. + * + * Technically this should be #ifdef JSGC_FJGENERATIONAL but that + * affects the observed size of JSRuntime in problematic ways, see + * note in vm/ThreadPool.h. + */ + mozilla::Atomic fjCollectionCounter; + private: /* * These options control the zealousness of the GC. The fundamental values diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index e76113539826..da722d8a37b6 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -171,6 +171,16 @@ CheckMarkedThing(JSTracer *trc, T **thingp) JS_ASSERT(*thingp); #ifdef DEBUG +#ifdef JSGC_FJGENERATIONAL + /* + * The code below (runtimeFromMainThread(), etc) makes assumptions + * not valid for the ForkJoin worker threads during ForkJoin GGC, + * so just bail. + */ + if (ForkJoinContext::current()) + return; +#endif + /* This function uses data that's not available in the nursery. */ if (IsInsideNursery(thing)) return; @@ -229,6 +239,16 @@ MarkInternal(JSTracer *trc, T **thingp) T *thing = *thingp; if (!trc->callback) { +#ifdef JSGC_FJGENERATIONAL + /* + * This case should never be reached from PJS collections as + * those should all be using a ForkJoinNurseryCollectionTracer + * that carries a callback. + */ + JS_ASSERT(!ForkJoinContext::current()); + JS_ASSERT(!trc->runtime()->isFJMinorCollecting()); +#endif + /* * We may mark a Nursery thing outside the context of the * MinorCollectionTracer because of a pre-barrier. The pre-barrier is @@ -357,11 +377,25 @@ IsMarked(T **thingp) JS_ASSERT(thingp); JS_ASSERT(*thingp); #ifdef JSGC_GENERATIONAL - if (IsInsideNursery(*thingp)) { - Nursery &nursery = (*thingp)->runtimeFromMainThread()->gc.nursery; - return nursery.getForwardedPointer(thingp); + JSRuntime* rt = (*thingp)->runtimeFromAnyThread(); +#ifdef JSGC_FJGENERATIONAL + // Must precede the case for JSGC_GENERATIONAL because IsInsideNursery() + // will also be true for the ForkJoinNursery. + if (rt->isFJMinorCollecting()) { + ForkJoinContext *ctx = ForkJoinContext::current(); + ForkJoinNursery &fjNursery = ctx->fjNursery(); + if (fjNursery.isInsideFromspace(*thingp)) + return fjNursery.getForwardedPointer(thingp); } + else #endif + { + if (IsInsideNursery(*thingp)) { + Nursery &nursery = rt->gc.nursery; + return nursery.getForwardedPointer(thingp); + } + } +#endif // JSGC_GENERATIONAL Zone *zone = (*thingp)->tenuredZone(); if (!zone->isCollecting() || zone->isGCFinished()) return true; @@ -383,14 +417,25 @@ IsAboutToBeFinalized(T **thingp) return false; #ifdef JSGC_GENERATIONAL - Nursery &nursery = rt->gc.nursery; - JS_ASSERT_IF(!rt->isHeapMinorCollecting(), !IsInsideNursery(thing)); - if (rt->isHeapMinorCollecting()) { - if (IsInsideNursery(thing)) - return !nursery.getForwardedPointer(thingp); - return false; +#ifdef JSGC_FJGENERATIONAL + if (rt->isFJMinorCollecting()) { + ForkJoinContext *ctx = ForkJoinContext::current(); + ForkJoinNursery &fjNursery = ctx->fjNursery(); + if (fjNursery.isInsideFromspace(thing)) + return !fjNursery.getForwardedPointer(thingp); } + else #endif + { + Nursery &nursery = rt->gc.nursery; + JS_ASSERT_IF(!rt->isHeapMinorCollecting(), !IsInsideNursery(thing)); + if (rt->isHeapMinorCollecting()) { + if (IsInsideNursery(thing)) + return !nursery.getForwardedPointer(thingp); + return false; + } + } +#endif // JSGC_GENERATIONAL if (!thing->tenuredZone()->isGCSweeping()) return false; @@ -413,9 +458,20 @@ UpdateIfRelocated(JSRuntime *rt, T **thingp) { JS_ASSERT(thingp); #ifdef JSGC_GENERATIONAL - if (*thingp && rt->isHeapMinorCollecting() && IsInsideNursery(*thingp)) - rt->gc.nursery.getForwardedPointer(thingp); +#ifdef JSGC_FJGENERATIONAL + if (*thingp && rt->isFJMinorCollecting()) { + ForkJoinContext *ctx = ForkJoinContext::current(); + ForkJoinNursery &fjNursery = ctx->fjNursery(); + if (fjNursery.isInsideFromspace(*thingp)) + fjNursery.getForwardedPointer(thingp); + } + else #endif + { + if (*thingp && rt->isHeapMinorCollecting() && IsInsideNursery(*thingp)) + rt->gc.nursery.getForwardedPointer(thingp); + } +#endif // JSGC_GENERATIONAL return *thingp; } diff --git a/js/src/gc/Nursery-inl.h b/js/src/gc/Nursery-inl.h index e5c8542802cc..e852468eb50f 100644 --- a/js/src/gc/Nursery-inl.h +++ b/js/src/gc/Nursery-inl.h @@ -17,52 +17,41 @@ namespace js { namespace gc { -/* - * This structure overlays a Cell in the Nursery and re-purposes its memory - * for managing the Nursery collection process. - */ -class RelocationOverlay +/* static */ +inline RelocationOverlay * +RelocationOverlay::fromCell(Cell *cell) { - friend class MinorCollectionTracer; + JS_ASSERT(!cell->isTenured()); + return reinterpret_cast(cell); +} - /* The low bit is set so this should never equal a normal pointer. */ - static const uintptr_t Relocated = uintptr_t(0xbad0bad1); +inline bool +RelocationOverlay::isForwarded() const +{ + return magic_ == Relocated; +} - /* Set to Relocated when moved. */ - uintptr_t magic_; +inline Cell * +RelocationOverlay::forwardingAddress() const +{ + JS_ASSERT(isForwarded()); + return newLocation_; +} - /* The location |this| was moved to. */ - Cell *newLocation_; +inline void +RelocationOverlay::forwardTo(Cell *cell) +{ + JS_ASSERT(!isForwarded()); + magic_ = Relocated; + newLocation_ = cell; + next_ = nullptr; +} - /* A list entry to track all relocated things. */ - RelocationOverlay *next_; - - public: - static RelocationOverlay *fromCell(Cell *cell) { - JS_ASSERT(!cell->isTenured()); - return reinterpret_cast(cell); - } - - bool isForwarded() const { - return magic_ == Relocated; - } - - Cell *forwardingAddress() const { - JS_ASSERT(isForwarded()); - return newLocation_; - } - - void forwardTo(Cell *cell) { - JS_ASSERT(!isForwarded()); - magic_ = Relocated; - newLocation_ = cell; - next_ = nullptr; - } - - RelocationOverlay *next() const { - return next_; - } -}; +inline RelocationOverlay * +RelocationOverlay::next() const +{ + return next_; +} } /* namespace gc */ } /* namespace js */ diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index bd4c372ca3c2..36e28ed1ea75 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -914,6 +914,10 @@ js::Nursery::collect(JSRuntime *rt, JS::gcreason::Reason reason, TypeObjectList #endif } +#undef TIME_START +#undef TIME_END +#undef TIME_TOTAL + void js::Nursery::freeHugeSlots() { diff --git a/js/src/gc/Nursery.h b/js/src/gc/Nursery.h index 11879362fc3d..cf8c9b21e7a7 100644 --- a/js/src/gc/Nursery.h +++ b/js/src/gc/Nursery.h @@ -36,6 +36,7 @@ namespace gc { class Cell; class Collector; class MinorCollectionTracer; +class ForkJoinNursery; } /* namespace gc */ namespace types { @@ -49,6 +50,39 @@ class ICStubCompiler; class BaselineCompiler; } +namespace gc { + +/* + * This structure overlays a Cell in the Nursery and re-purposes its memory + * for managing the Nursery collection process. + */ +class RelocationOverlay +{ + friend class MinorCollectionTracer; + friend class ForkJoinNursery; + + /* The low bit is set so this should never equal a normal pointer. */ + static const uintptr_t Relocated = uintptr_t(0xbad0bad1); + + /* Set to Relocated when moved. */ + uintptr_t magic_; + + /* The location |this| was moved to. */ + Cell *newLocation_; + + /* A list entry to track all relocated things. */ + RelocationOverlay *next_; + + public: + static inline RelocationOverlay *fromCell(Cell *cell); + inline bool isForwarded() const; + inline Cell *forwardingAddress() const; + inline void forwardTo(Cell *cell); + inline RelocationOverlay *next() const; +}; + +} /* namespace gc */ + class Nursery { public: @@ -215,7 +249,7 @@ class Nursery MOZ_ALWAYS_INLINE void initChunk(int chunkno) { NurseryChunkLayout &c = chunk(chunkno); c.trailer.storeBuffer = JS::shadow::Runtime::asShadowRuntime(runtime())->gcStoreBufferPtr(); - c.trailer.location = gc::ChunkLocationNursery; + c.trailer.location = gc::ChunkLocationBitNursery; c.trailer.runtime = runtime(); } diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp index b877b5fdb09d..38e4aa9c4746 100644 --- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -19,6 +19,7 @@ #include "builtin/MapObject.h" #include "frontend/BytecodeCompiler.h" +#include "gc/ForkJoinNursery.h" #include "gc/GCInternals.h" #include "gc/Marking.h" #ifdef JS_ION @@ -102,30 +103,38 @@ MarkExactStackRootList(JSTracer *trc, Source *s, const char *name) template static inline void -MarkExactStackRootsForType(JSTracer *trc, const char *name = nullptr) +MarkExactStackRootsForType(JSRuntime* rt, JSTracer *trc, const char *name = nullptr) { - for (ContextIter cx(trc->runtime()); !cx.done(); cx.next()) + for (ContextIter cx(rt); !cx.done(); cx.next()) MarkExactStackRootList(trc, cx.get(), name); - MarkExactStackRootList(trc, &trc->runtime()->mainThread, name); + MarkExactStackRootList(trc, &rt->mainThread, name); } -static void -MarkExactStackRoots(JSTracer *trc) +template +static inline void +MarkExactStackRootsForType(ThreadSafeContext* cx, JSTracer *trc, const char *name = nullptr) { - MarkExactStackRootsForType(trc, "exact-object"); - MarkExactStackRootsForType(trc, "exact-shape"); - MarkExactStackRootsForType(trc, "exact-baseshape"); - MarkExactStackRootsForType(trc, "exact-typeobject"); - MarkExactStackRootsForType(trc, "exact-string"); - MarkExactStackRootsForType(trc, "exact-jitcode"); - MarkExactStackRootsForType(trc, "exact-script"); - MarkExactStackRootsForType(trc, "exact-lazy-script"); - MarkExactStackRootsForType(trc, "exact-id"); - MarkExactStackRootsForType(trc, "exact-value"); - MarkExactStackRootsForType(trc, "exact-type"); - MarkExactStackRootsForType(trc); - MarkExactStackRootsForType(trc); - MarkExactStackRootsForType(trc); + MarkExactStackRootList(trc, cx->perThreadData, name); +} + +template +static void +MarkExactStackRoots(T context, JSTracer *trc) +{ + MarkExactStackRootsForType(context, trc, "exact-object"); + MarkExactStackRootsForType(context, trc, "exact-shape"); + MarkExactStackRootsForType(context, trc, "exact-baseshape"); + MarkExactStackRootsForType(context, trc, "exact-typeobject"); + MarkExactStackRootsForType(context, trc, "exact-string"); + MarkExactStackRootsForType(context, trc, "exact-jitcode"); + MarkExactStackRootsForType(context, trc, "exact-script"); + MarkExactStackRootsForType(context, trc, "exact-lazy-script"); + MarkExactStackRootsForType(context, trc, "exact-id"); + MarkExactStackRootsForType(context, trc, "exact-value"); + MarkExactStackRootsForType(context, trc, "exact-type"); + MarkExactStackRootsForType(context, trc); + MarkExactStackRootsForType(context, trc); + MarkExactStackRootsForType(context, trc); } #endif /* JSGC_USE_EXACT_ROOTING */ @@ -583,10 +592,8 @@ AutoGCRooter::trace(JSTracer *trc) /* static */ void AutoGCRooter::traceAll(JSTracer *trc) { - for (ContextIter cx(trc->runtime()); !cx.done(); cx.next()) { - for (AutoGCRooter *gcr = cx->autoGCRooters; gcr; gcr = gcr->down) - gcr->trace(trc); - } + for (ContextIter cx(trc->runtime()); !cx.done(); cx.next()) + traceAllInContext(&*cx, trc); } /* static */ void @@ -685,6 +692,27 @@ js::gc::MarkPersistentRootedChains(JSTracer *trc) "PersistentRooted"); } +#ifdef JSGC_FJGENERATIONAL +void +js::gc::MarkForkJoinStack(ForkJoinNurseryCollectionTracer *trc) +{ + ForkJoinContext *cx = ForkJoinContext::current(); + PerThreadData *ptd = cx->perThreadData; + + AutoGCRooter::traceAllInContext(cx, trc); + MarkExactStackRoots(cx, trc); + jit::MarkJitActivations(ptd, trc); + +#ifdef DEBUG + // There should be only JIT activations on the stack + for (ActivationIterator iter(ptd); !iter.done(); ++iter) { + Activation *act = iter.activation(); + JS_ASSERT(act->isJit()); + } +#endif +} +#endif // JSGC_FJGENERATIONAL + void js::gc::GCRuntime::markRuntime(JSTracer *trc, bool useSavedRoots) { @@ -704,7 +732,7 @@ js::gc::GCRuntime::markRuntime(JSTracer *trc, bool useSavedRoots) if (!rt->isBeingDestroyed()) { #ifdef JSGC_USE_EXACT_ROOTING - MarkExactStackRoots(trc); + MarkExactStackRoots(rt, trc); #else markConservativeStackRoots(trc, useSavedRoots); #endif @@ -786,10 +814,10 @@ js::gc::GCRuntime::markRuntime(JSTracer *trc, bool useSavedRoots) c->debugScopes->mark(trc); } - MarkInterpreterActivations(rt, trc); + MarkInterpreterActivations(&rt->mainThread, trc); #ifdef JS_ION - jit::MarkJitActivations(rt, trc); + jit::MarkJitActivations(&rt->mainThread, trc); #endif if (!isHeapMinorCollecting()) { diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 4b366e6d8a8e..df41119c1440 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -1149,7 +1149,8 @@ CodeGenerator::visitLambdaPar(LLambdaPar *lir) JS_ASSERT(scopeChainReg != resultReg); - emitAllocateGCThingPar(lir, resultReg, cxReg, tempReg1, tempReg2, info.fun); + if (!emitAllocateGCThingPar(lir, resultReg, cxReg, tempReg1, tempReg2, info.fun)) + return false; emitLambdaInit(resultReg, scopeChainReg, info); return true; } @@ -3898,10 +3899,13 @@ CodeGenerator::visitNewCallObjectPar(LNewCallObjectPar *lir) Register tempReg2 = ToRegister(lir->getTemp1()); JSObject *templateObj = lir->mir()->templateObj(); - emitAllocateGCThingPar(lir, resultReg, cxReg, tempReg1, tempReg2, templateObj); - return true; + return emitAllocateGCThingPar(lir, resultReg, cxReg, tempReg1, tempReg2, templateObj); } +typedef JSObject *(*ExtendArrayParFn)(ForkJoinContext*, JSObject*, uint32_t); +static const VMFunction ExtendArrayParInfo = + FunctionInfo(ExtendArrayPar); + bool CodeGenerator::visitNewDenseArrayPar(LNewDenseArrayPar *lir) { @@ -3912,26 +3916,23 @@ CodeGenerator::visitNewDenseArrayPar(LNewDenseArrayPar *lir) Register tempReg2 = ToRegister(lir->getTemp2()); JSObject *templateObj = lir->mir()->templateObject(); - // Allocate the array into tempReg2. Don't use resultReg because it - // may alias cxReg etc. - emitAllocateGCThingPar(lir, tempReg2, cxReg, tempReg0, tempReg1, templateObj); + masm.push(lengthReg); + if (!emitAllocateGCThingPar(lir, tempReg2, cxReg, tempReg0, tempReg1, templateObj)) + return false; + masm.pop(lengthReg); - // Invoke a C helper to allocate the elements. For convenience, - // this helper also returns the array back to us, or nullptr, which - // obviates the need to preserve the register across the call. In - // reality, we should probably just have the C helper also - // *allocate* the array, but that would require that it initialize - // the various fields of the object, and I didn't want to - // duplicate the code in initGCThing() that already does such an - // admirable job. - masm.setupUnalignedABICall(3, tempReg0); - masm.passABIArg(cxReg); - masm.passABIArg(tempReg2); - masm.passABIArg(lengthReg); - masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ExtendArrayPar)); + // Invoke a C helper to allocate the elements. The helper returns + // nullptr on allocation error or the array object. + + saveLive(lir); + pushArg(lengthReg); + pushArg(tempReg2); + if (!callVM(ExtendArrayParInfo, lir)) + return false; + storeResultTo(ToRegister(lir->output())); + restoreLive(lir); Register resultReg = ToRegister(lir->output()); - JS_ASSERT(resultReg == ReturnReg); OutOfLineAbortPar *bail = oolAbortPar(ParallelBailoutOutOfMemory, lir); if (!bail) return false; @@ -3976,10 +3977,10 @@ CodeGenerator::visitNewPar(LNewPar *lir) Register tempReg1 = ToRegister(lir->getTemp0()); Register tempReg2 = ToRegister(lir->getTemp1()); JSObject *templateObject = lir->mir()->templateObject(); - emitAllocateGCThingPar(lir, objReg, cxReg, tempReg1, tempReg2, templateObject); - return true; + return emitAllocateGCThingPar(lir, objReg, cxReg, tempReg1, tempReg2, templateObject); } +#ifndef JSGC_FJGENERATIONAL class OutOfLineNewGCThingPar : public OutOfLineCodeBase { public: @@ -3997,15 +3998,27 @@ public: return codegen->visitOutOfLineNewGCThingPar(this); } }; +#endif // JSGC_FJGENERATIONAL + +typedef JSObject *(*NewGCThingParFn)(ForkJoinContext *, js::gc::AllocKind allocKind); +static const VMFunction NewGCThingParInfo = + FunctionInfo(NewGCThingPar); bool CodeGenerator::emitAllocateGCThingPar(LInstruction *lir, Register objReg, Register cxReg, Register tempReg1, Register tempReg2, JSObject *templateObj) { gc::AllocKind allocKind = templateObj->tenuredGetAllocKind(); +#ifdef JSGC_FJGENERATIONAL + OutOfLineCode *ool = oolCallVM(NewGCThingParInfo, lir, + (ArgList(), Imm32(allocKind)), StoreRegisterTo(objReg)); + if (!ool) + return false; +#else OutOfLineNewGCThingPar *ool = new(alloc()) OutOfLineNewGCThingPar(lir, allocKind, objReg, cxReg); if (!ool || !addOutOfLineCode(ool)) return false; +#endif masm.newGCThingPar(objReg, cxReg, tempReg1, tempReg2, templateObj, ool->entry()); masm.bind(ool->rejoin()); @@ -4013,6 +4026,7 @@ CodeGenerator::emitAllocateGCThingPar(LInstruction *lir, Register objReg, Regist return true; } +#ifndef JSGC_FJGENERATIONAL bool CodeGenerator::visitOutOfLineNewGCThingPar(OutOfLineNewGCThingPar *ool) { @@ -4038,6 +4052,7 @@ CodeGenerator::visitOutOfLineNewGCThingPar(OutOfLineNewGCThingPar *ool) masm.jump(ool->rejoin()); return true; } +#endif // JSGC_FJGENERATIONAL bool CodeGenerator::visitAbortPar(LAbortPar *lir) @@ -6508,7 +6523,7 @@ static const VMFunctionsModal InitRestParameterInfo = VMFunctionsModal( bool CodeGenerator::emitRest(LInstruction *lir, Register array, Register numActuals, Register temp0, Register temp1, unsigned numFormals, - JSObject *templateObject) + JSObject *templateObject, bool saveAndRestore, Register resultreg) { // Compute actuals() + numFormals. size_t actualsOffset = frameSize() + IonJSFrameLayout::offsetOfActualArgs(); @@ -6527,12 +6542,22 @@ CodeGenerator::emitRest(LInstruction *lir, Register array, Register numActuals, } masm.bind(&joinLength); + if (saveAndRestore) + saveLive(lir); + pushArg(array); pushArg(ImmGCPtr(templateObject)); pushArg(temp1); pushArg(temp0); - return callVM(InitRestParameterInfo, lir); + bool result = callVM(InitRestParameterInfo, lir); + + if (saveAndRestore) { + storeResultTo(resultreg); + restoreLive(lir); + } + + return result; } bool @@ -6554,9 +6579,12 @@ CodeGenerator::visitRest(LRest *lir) } masm.bind(&joinAlloc); - return emitRest(lir, temp2, numActuals, temp0, temp1, numFormals, templateObject); + return emitRest(lir, temp2, numActuals, temp0, temp1, numFormals, templateObject, false, ToRegister(lir->output())); } +// LRestPar cannot derive from LCallInstructionHelper because emitAllocateGCThingPar may +// itself contain a VM call. Thus there's some manual work here and in emitRest(). + bool CodeGenerator::visitRestPar(LRestPar *lir) { @@ -6568,10 +6596,12 @@ CodeGenerator::visitRestPar(LRestPar *lir) unsigned numFormals = lir->mir()->numFormals(); JSObject *templateObject = lir->mir()->templateObject(); + masm.push(numActuals); if (!emitAllocateGCThingPar(lir, temp2, cx, temp0, temp1, templateObject)) return false; + masm.pop(numActuals); - return emitRest(lir, temp2, numActuals, temp0, temp1, numFormals, templateObject); + return emitRest(lir, temp2, numActuals, temp0, temp1, numFormals, templateObject, true, ToRegister(lir->output())); } bool diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 2a4077cd9a92..767bfad3ce2c 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -269,7 +269,7 @@ class CodeGenerator : public CodeGeneratorSpecific bool visitRunOncePrologue(LRunOncePrologue *lir); bool emitRest(LInstruction *lir, Register array, Register numActuals, Register temp0, Register temp1, unsigned numFormals, - JSObject *templateObject); + JSObject *templateObject, bool saveAndRestore, Register resultreg); bool visitRest(LRest *lir); bool visitRestPar(LRestPar *lir); bool visitCallSetProperty(LCallSetProperty *ins); diff --git a/js/src/jit/IonFrames.cpp b/js/src/jit/IonFrames.cpp index f1ea645ea491..95ad037678f6 100644 --- a/js/src/jit/IonFrames.cpp +++ b/js/src/jit/IonFrames.cpp @@ -10,6 +10,7 @@ #include "jsobj.h" #include "jsscript.h" +#include "gc/ForkJoinNursery.h" #include "gc/Marking.h" #include "jit/BaselineDebugModeOSR.h" #include "jit/BaselineFrame.h" @@ -867,10 +868,8 @@ MarkIonJSFrame(JSTracer *trc, const JitFrameIterator &frame) // longer reachable through the callee token (JSFunction/JSScript->ion // is now nullptr or recompiled). Manually trace it here. IonScript::Trace(trc, ionScript); - } else if (CalleeTokenIsFunction(layout->calleeToken())) { - ionScript = CalleeTokenToFunction(layout->calleeToken())->nonLazyScript()->ionScript(); } else { - ionScript = CalleeTokenToScript(layout->calleeToken())->ionScript(); + ionScript = frame.ionScriptFromCalleeToken(); } if (CalleeTokenIsFunction(layout->calleeToken())) @@ -937,10 +936,8 @@ UpdateIonJSFrameForMinorGC(JSTracer *trc, const JitFrameIterator &frame) // This frame has been invalidated, meaning that its IonScript is no // longer reachable through the callee token (JSFunction/JSScript->ion // is now nullptr or recompiled). - } else if (CalleeTokenIsFunction(layout->calleeToken())) { - ionScript = CalleeTokenToFunction(layout->calleeToken())->nonLazyScript()->ionScript(); } else { - ionScript = CalleeTokenToScript(layout->calleeToken())->ionScript(); + ionScript = frame.ionScriptFromCalleeToken(); } const SafepointIndex *si = ionScript->getSafepointIndex(frame.returnAddressToFp()); @@ -950,8 +947,16 @@ UpdateIonJSFrameForMinorGC(JSTracer *trc, const JitFrameIterator &frame) uintptr_t *spill = frame.spillBase(); for (GeneralRegisterBackwardIterator iter(safepoint.allGprSpills()); iter.more(); iter++) { --spill; - if (slotsRegs.has(*iter)) + if (slotsRegs.has(*iter)) { +#ifdef JSGC_FJGENERATIONAL + if (trc->callback == gc::ForkJoinNursery::MinorGCCallback) { + gc::ForkJoinNursery::forwardBufferPointer(trc, + reinterpret_cast(spill)); + continue; + } +#endif trc->runtime()->gc.nursery.forwardBufferPointer(reinterpret_cast(spill)); + } } // Skip to the right place in the safepoint @@ -965,6 +970,12 @@ UpdateIonJSFrameForMinorGC(JSTracer *trc, const JitFrameIterator &frame) while (safepoint.getSlotsOrElementsSlot(&slot)) { HeapSlot **slots = reinterpret_cast(layout->slotRef(slot)); +#ifdef JSGC_FJGENERATIONAL + if (trc->callback == gc::ForkJoinNursery::MinorGCCallback) { + gc::ForkJoinNursery::forwardBufferPointer(trc, slots); + continue; + } +#endif trc->runtime()->gc.nursery.forwardBufferPointer(slots); } } @@ -1226,9 +1237,9 @@ MarkJitActivation(JSTracer *trc, const JitActivationIterator &activations) } void -MarkJitActivations(JSRuntime *rt, JSTracer *trc) +MarkJitActivations(PerThreadData *ptd, JSTracer *trc) { - for (JitActivationIterator activations(rt); !activations.done(); ++activations) + for (JitActivationIterator activations(ptd); !activations.done(); ++activations) MarkJitActivation(trc, activations); } @@ -1256,6 +1267,22 @@ UpdateJitActivationsForMinorGC(JSRuntime *rt, JSTracer *trc) } } } + +void +UpdateJitActivationsForMinorGC(PerThreadData *ptd, JSTracer *trc) +{ +#ifdef JSGC_FJGENERATIONAL + JS_ASSERT(trc->runtime()->isHeapMinorCollecting() || trc->runtime()->isFJMinorCollecting()); +#else + JS_ASSERT(trc->runtime()->isHeapMinorCollecting()); +#endif + for (JitActivationIterator activations(ptd); !activations.done(); ++activations) { + for (JitFrameIterator frames(activations); !frames.done(); ++frames) { + if (frames.type() == JitFrame_IonJS) + UpdateIonJSFrameForMinorGC(trc, frames); + } + } +} #endif void @@ -1650,6 +1677,15 @@ JitFrameIterator::ionScript() const IonScript *ionScript = nullptr; if (checkInvalidation(&ionScript)) return ionScript; + return ionScriptFromCalleeToken(); +} + +IonScript * +JitFrameIterator::ionScriptFromCalleeToken() const +{ + JS_ASSERT(type() == JitFrame_IonJS); + JS_ASSERT(!checkInvalidation()); + switch (GetCalleeTokenTag(calleeToken())) { case CalleeToken_Function: case CalleeToken_Script: diff --git a/js/src/jit/IonFrames.h b/js/src/jit/IonFrames.h index c760fda4a293..7c64ff935ea6 100644 --- a/js/src/jit/IonFrames.h +++ b/js/src/jit/IonFrames.h @@ -264,7 +264,7 @@ void HandleParallelFailure(ResumeFromException *rfe); void EnsureExitFrame(IonCommonFrameLayout *frame); -void MarkJitActivations(JSRuntime *rt, JSTracer *trc); +void MarkJitActivations(PerThreadData *ptd, JSTracer *trc); void MarkIonCompilerRoots(JSTracer *trc); JSCompartment * @@ -272,6 +272,7 @@ TopmostIonActivationCompartment(JSRuntime *rt); #ifdef JSGC_GENERATIONAL void UpdateJitActivationsForMinorGC(JSRuntime *rt, JSTracer *trc); +void UpdateJitActivationsForMinorGC(PerThreadData *ptd, JSTracer *trc); #endif static inline uint32_t diff --git a/js/src/jit/IonMacroAssembler.cpp b/js/src/jit/IonMacroAssembler.cpp index c8add24dd688..44f448ecd065 100644 --- a/js/src/jit/IonMacroAssembler.cpp +++ b/js/src/jit/IonMacroAssembler.cpp @@ -634,11 +634,54 @@ MacroAssembler::newGCFatInlineString(Register result, Register temp, Label *fail void MacroAssembler::newGCThingPar(Register result, Register cx, Register tempReg1, Register tempReg2, gc::AllocKind allocKind, Label *fail) +{ +#ifdef JSGC_FJGENERATIONAL + if (IsNurseryAllocable(allocKind)) + return newGCNurseryThingPar(result, cx, tempReg1, tempReg2, allocKind, fail); +#endif + return newGCTenuredThingPar(result, cx, tempReg1, tempReg2, allocKind, fail); +} + +#ifdef JSGC_FJGENERATIONAL +void +MacroAssembler::newGCNurseryThingPar(Register result, Register cx, + Register tempReg1, Register tempReg2, + gc::AllocKind allocKind, Label *fail) +{ + JS_ASSERT(IsNurseryAllocable(allocKind)); + + uint32_t thingSize = uint32_t(gc::Arena::thingSize(allocKind)); + + // Correctness depends on thingSize being smaller than a chunk + // (not a problem) and the last chunk of the nursery not being + // located at the very top of the address space. The regular + // Nursery makes the same assumption, see nurseryAllocate() above. + + // The ForkJoinNursery is a member variable of the ForkJoinContext. + size_t offsetOfPosition = + ForkJoinContext::offsetOfFJNursery() + gc::ForkJoinNursery::offsetOfPosition(); + size_t offsetOfEnd = + ForkJoinContext::offsetOfFJNursery() + gc::ForkJoinNursery::offsetOfCurrentEnd(); + loadPtr(Address(cx, offsetOfPosition), result); + loadPtr(Address(cx, offsetOfEnd), tempReg2); + computeEffectiveAddress(Address(result, thingSize), tempReg1); + branchPtr(Assembler::Below, tempReg2, tempReg1, fail); + storePtr(tempReg1, Address(cx, offsetOfPosition)); +} +#endif + +void +MacroAssembler::newGCTenuredThingPar(Register result, Register cx, + Register tempReg1, Register tempReg2, + gc::AllocKind allocKind, Label *fail) { // Similar to ::newGCThing(), except that it allocates from a custom // Allocator in the ForkJoinContext*, rather than being hardcoded to the // compartment allocator. This requires two temporary registers. // + // When the ForkJoin generational collector is enabled this is only used + // for those object types that cannot be allocated in the ForkJoinNursery. + // // Subtle: I wanted to reuse `result` for one of the temporaries, but the // register allocator was assigning it to the same register as `cx`. // Then we overwrite that register which messed up the OOL code. @@ -693,14 +736,14 @@ void MacroAssembler::newGCStringPar(Register result, Register cx, Register tempReg1, Register tempReg2, Label *fail) { - newGCThingPar(result, cx, tempReg1, tempReg2, js::gc::FINALIZE_STRING, fail); + newGCTenuredThingPar(result, cx, tempReg1, tempReg2, js::gc::FINALIZE_STRING, fail); } void MacroAssembler::newGCFatInlineStringPar(Register result, Register cx, Register tempReg1, Register tempReg2, Label *fail) { - newGCThingPar(result, cx, tempReg1, tempReg2, js::gc::FINALIZE_FAT_INLINE_STRING, fail); + newGCTenuredThingPar(result, cx, tempReg1, tempReg2, js::gc::FINALIZE_FAT_INLINE_STRING, fail); } void diff --git a/js/src/jit/IonMacroAssembler.h b/js/src/jit/IonMacroAssembler.h index 95df122f6153..ebee0f1f5e44 100644 --- a/js/src/jit/IonMacroAssembler.h +++ b/js/src/jit/IonMacroAssembler.h @@ -816,6 +816,12 @@ class MacroAssembler : public MacroAssemblerSpecific void newGCThingPar(Register result, Register cx, Register tempReg1, Register tempReg2, gc::AllocKind allocKind, Label *fail); +#ifdef JSGC_FJGENERATIONAL + void newGCNurseryThingPar(Register result, Register cx, Register tempReg1, Register tempReg2, + gc::AllocKind allocKind, Label *fail); +#endif + void newGCTenuredThingPar(Register result, Register cx, Register tempReg1, Register tempReg2, + gc::AllocKind allocKind, Label *fail); void newGCThingPar(Register result, Register cx, Register tempReg1, Register tempReg2, JSObject *templateObject, Label *fail); void newGCStringPar(Register result, Register cx, Register tempReg1, Register tempReg2, diff --git a/js/src/jit/JitFrameIterator.h b/js/src/jit/JitFrameIterator.h index f2dd1c4e232a..972ee9d75419 100644 --- a/js/src/jit/JitFrameIterator.h +++ b/js/src/jit/JitFrameIterator.h @@ -201,6 +201,10 @@ class JitFrameIterator // Returns the IonScript associated with this JS frame. IonScript *ionScript() const; + // Returns the IonScript associated with this JS frame; the frame must + // not be invalidated. + IonScript *ionScriptFromCalleeToken() const; + // Returns the Safepoint associated with this JS frame. Incurs a lookup // overhead. const SafepointIndex *safepoint() const; diff --git a/js/src/jit/LIR-Common.h b/js/src/jit/LIR-Common.h index 657f4b3b7c2c..ff2b5a906296 100644 --- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -374,7 +374,7 @@ class LNewPar : public LInstructionHelper<1, 1, 2> } }; -class LNewDenseArrayPar : public LCallInstructionHelper<1, 2, 3> +class LNewDenseArrayPar : public LInstructionHelper<1, 2, 3> { public: LIR_HEADER(NewDenseArrayPar); @@ -5380,7 +5380,7 @@ class LRest : public LCallInstructionHelper<1, 1, 3> } }; -class LRestPar : public LCallInstructionHelper<1, 2, 3> +class LRestPar : public LInstructionHelper<1, 2, 3> { public: LIR_HEADER(RestPar); diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 439a03af6a2c..2bc0c278e79a 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -132,11 +132,7 @@ LIRGenerator::visitCheckOverRecursedPar(MCheckOverRecursedPar *ins) { LCheckOverRecursedPar *lir = new(alloc()) LCheckOverRecursedPar(useRegister(ins->forkJoinContext()), temp()); - if (!add(lir, ins)) - return false; - if (!assignSafepoint(lir, ins)) - return false; - return true; + return add(lir, ins) && assignSafepoint(lir, ins); } bool @@ -229,7 +225,7 @@ LIRGenerator::visitNewCallObjectPar(MNewCallObjectPar *ins) { const LAllocation &parThreadContext = useRegister(ins->forkJoinContext()); LNewCallObjectPar *lir = LNewCallObjectPar::New(alloc(), parThreadContext, temp(), temp()); - return define(lir, ins); + return define(lir, ins) && assignSafepoint(lir, ins); } bool @@ -2094,7 +2090,7 @@ LIRGenerator::visitLambdaPar(MLambdaPar *ins) LLambdaPar *lir = new(alloc()) LLambdaPar(useRegister(ins->forkJoinContext()), useRegister(ins->scopeChain()), temp(), temp()); - return define(lir, ins); + return define(lir, ins) && assignSafepoint(lir, ins); } bool @@ -2200,30 +2196,30 @@ LIRGenerator::visitInterruptCheckPar(MInterruptCheckPar *ins) { LInterruptCheckPar *lir = new(alloc()) LInterruptCheckPar(useRegister(ins->forkJoinContext()), temp()); - if (!add(lir, ins)) - return false; - if (!assignSafepoint(lir, ins)) - return false; - return true; + return add(lir, ins) && assignSafepoint(lir, ins); } bool LIRGenerator::visitNewPar(MNewPar *ins) { LNewPar *lir = new(alloc()) LNewPar(useRegister(ins->forkJoinContext()), temp(), temp()); - return define(lir, ins); + return define(lir, ins) && assignSafepoint(lir, ins); } bool LIRGenerator::visitNewDenseArrayPar(MNewDenseArrayPar *ins) { + JS_ASSERT(ins->forkJoinContext()->type() == MIRType_ForkJoinContext); + JS_ASSERT(ins->length()->type() == MIRType_Int32); + JS_ASSERT(ins->type() == MIRType_Object); + LNewDenseArrayPar *lir = - new(alloc()) LNewDenseArrayPar(useFixed(ins->forkJoinContext(), CallTempReg0), - useFixed(ins->length(), CallTempReg1), - tempFixed(CallTempReg2), - tempFixed(CallTempReg3), - tempFixed(CallTempReg4)); - return defineReturn(lir, ins); + new(alloc()) LNewDenseArrayPar(useRegister(ins->forkJoinContext()), + useRegister(ins->length()), + temp(), + temp(), + temp()); + return define(lir, ins) && assignSafepoint(lir, ins); } bool @@ -3329,12 +3325,12 @@ LIRGenerator::visitRestPar(MRestPar *ins) { JS_ASSERT(ins->numActuals()->type() == MIRType_Int32); - LRestPar *lir = new(alloc()) LRestPar(useFixed(ins->forkJoinContext(), CallTempReg0), - useFixed(ins->numActuals(), CallTempReg1), - tempFixed(CallTempReg2), - tempFixed(CallTempReg3), - tempFixed(CallTempReg4)); - return defineReturn(lir, ins) && assignSafepoint(lir, ins); + LRestPar *lir = new(alloc()) LRestPar(useRegister(ins->forkJoinContext()), + useRegister(ins->numActuals()), + temp(), + temp(), + temp()); + return define(lir, ins) && assignSafepoint(lir, ins); } bool diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 482174b643d1..2a21463c690d 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -1695,6 +1695,10 @@ class MNewPar : public MUnaryInstruction JSObject *templateObject() const { return templateObject_; } + + AliasSet getAliasSet() const { + return AliasSet::None(); + } }; class MTypedObjectProto @@ -9862,6 +9866,10 @@ class MNewDenseArrayPar : public MBinaryInstruction bool possiblyCalls() const { return true; } + + AliasSet getAliasSet() const { + return AliasSet::None(); + } }; // A resume point contains the information needed to reconstruct the Baseline diff --git a/js/src/jit/ParallelFunctions.cpp b/js/src/jit/ParallelFunctions.cpp index 130b73809746..521dd7c3e19b 100644 --- a/js/src/jit/ParallelFunctions.cpp +++ b/js/src/jit/ParallelFunctions.cpp @@ -38,7 +38,11 @@ JSObject * jit::NewGCThingPar(ForkJoinContext *cx, gc::AllocKind allocKind) { JS_ASSERT(ForkJoinContext::current() == cx); +#ifdef JSGC_FJGENERATIONAL + return js::NewGCObject(cx, allocKind, 0, gc::DefaultHeap); +#else return js::NewGCObject(cx, allocKind, 0, gc::TenuredHeap); +#endif } bool diff --git a/js/src/jit/ParallelSafetyAnalysis.cpp b/js/src/jit/ParallelSafetyAnalysis.cpp index 91380ec58cf3..d5e5b1bff466 100644 --- a/js/src/jit/ParallelSafetyAnalysis.cpp +++ b/js/src/jit/ParallelSafetyAnalysis.cpp @@ -609,6 +609,11 @@ ParallelSafetyVisitor::replace(MInstruction *oldInstruction, MBasicBlock *block = oldInstruction->block(); block->insertBefore(oldInstruction, replacementInstruction); oldInstruction->replaceAllUsesWith(replacementInstruction); + MResumePoint *rp = oldInstruction->resumePoint(); + if (rp && rp->instruction() == oldInstruction) { + rp->setInstruction(replacementInstruction); + replacementInstruction->setResumePoint(rp); + } block->discard(oldInstruction); // We may have replaced a specialized Float32 instruction by its diff --git a/js/src/jscntxtinlines.h b/js/src/jscntxtinlines.h index 57f8bd5d47fd..9675c3486716 100644 --- a/js/src/jscntxtinlines.h +++ b/js/src/jscntxtinlines.h @@ -19,6 +19,8 @@ #include "vm/Interpreter.h" #include "vm/ProxyObject.h" +#include "gc/ForkJoinNursery-inl.h" + namespace js { #ifdef JS_CRASH_DIAGNOSTICS diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index dd085edd128d..f49a1578f2dc 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -771,7 +771,7 @@ Chunk::init(JSRuntime *rt) /* Initialize the chunk info. */ info.age = 0; info.trailer.storeBuffer = nullptr; - info.trailer.location = ChunkLocationTenuredHeap; + info.trailer.location = ChunkLocationBitTenuredHeap; info.trailer.runtime = rt; /* The rest of info fields are initialized in pickChunk. */ @@ -880,8 +880,17 @@ Chunk::allocateArena(Zone *zone, AllocKind thingKind) JS_ASSERT(hasAvailableArenas()); JSRuntime *rt = zone->runtimeFromAnyThread(); - if (!rt->isHeapMinorCollecting() && rt->gc.bytes >= rt->gc.maxBytes) + if (!rt->isHeapMinorCollecting() && rt->gc.bytes >= rt->gc.maxBytes) { +#ifdef JSGC_FJGENERATIONAL + // This is an approximation to the best test, which would check that + // this thread is currently promoting into the tenured area. I doubt + // the better test would make much difference. + if (!rt->isFJMinorCollecting()) + return nullptr; +#else return nullptr; +#endif + } ArenaHeader *aheader = MOZ_LIKELY(info.numArenasFreeCommitted > 0) ? fetchNextFreeArena(rt) @@ -1701,7 +1710,7 @@ ArenaLists::allocateFromArenaInline(Zone *zone, AllocKind thingKind, /* * While we still hold the GC lock get an arena from some chunk, mark it - * as full as its single free span is moved to the free lits, and insert + * as full as its single free span is moved to the free lists, and insert * it to the list as a fully allocated arena. * * We add the arena before the the head, so that after the GC the most @@ -2154,7 +2163,7 @@ GCRuntime::triggerGC(JS::gcreason::Reason reason) bool js::TriggerZoneGC(Zone *zone, JS::gcreason::Reason reason) { - return zone->runtimeFromAnyThread()->gc.triggerZoneGC(zone,reason); + return zone->runtimeFromAnyThread()->gc.triggerZoneGC(zone, reason); } bool @@ -2374,6 +2383,10 @@ DecommitArenas(JSRuntime *rt) static void ExpireChunksAndArenas(JSRuntime *rt, bool shouldShrink) { +#ifdef JSGC_FJGENERATIONAL + rt->threadPool.pruneChunkCache(); +#endif + if (Chunk *toFree = rt->gc.chunkPool.expire(rt, shouldShrink)) { AutoUnlockGC unlock(rt); FreeChunkList(rt, toFree); diff --git a/js/src/jsgc.h b/js/src/jsgc.h index d8ca91f12f01..0e89b8599433 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -41,6 +41,10 @@ class ScopeObject; class Shape; class UnownedBaseShape; +namespace gc { +class ForkJoinNursery; +} + unsigned GetCPUCount(); enum HeapState { @@ -196,6 +200,42 @@ IsNurseryAllocable(AllocKind kind) } #endif +#if defined(JSGC_FJGENERATIONAL) +// This is separate from IsNurseryAllocable() so that the latter can evolve +// without worrying about what the ForkJoinNursery's needs are, and vice +// versa to some extent. +static inline bool +IsFJNurseryAllocable(AllocKind kind) +{ + JS_ASSERT(kind >= 0 && unsigned(kind) < FINALIZE_LIMIT); + static const bool map[] = { + false, /* FINALIZE_OBJECT0 */ + true, /* FINALIZE_OBJECT0_BACKGROUND */ + false, /* FINALIZE_OBJECT2 */ + true, /* FINALIZE_OBJECT2_BACKGROUND */ + false, /* FINALIZE_OBJECT4 */ + true, /* FINALIZE_OBJECT4_BACKGROUND */ + false, /* FINALIZE_OBJECT8 */ + true, /* FINALIZE_OBJECT8_BACKGROUND */ + false, /* FINALIZE_OBJECT12 */ + true, /* FINALIZE_OBJECT12_BACKGROUND */ + false, /* FINALIZE_OBJECT16 */ + true, /* FINALIZE_OBJECT16_BACKGROUND */ + false, /* FINALIZE_SCRIPT */ + false, /* FINALIZE_LAZY_SCRIPT */ + false, /* FINALIZE_SHAPE */ + false, /* FINALIZE_BASE_SHAPE */ + false, /* FINALIZE_TYPE_OBJECT */ + false, /* FINALIZE_FAT_INLINE_STRING */ + false, /* FINALIZE_STRING */ + false, /* FINALIZE_EXTERNAL_STRING */ + false, /* FINALIZE_JITCODE */ + }; + JS_STATIC_ASSERT(JS_ARRAY_LENGTH(map) == FINALIZE_LIMIT); + return map[kind]; +} +#endif + static inline bool IsBackgroundFinalized(AllocKind kind) { @@ -782,6 +822,7 @@ class ArenaLists inline void normalizeBackgroundFinalizeState(AllocKind thingKind); friend class js::Nursery; + friend class js::gc::ForkJoinNursery; }; /* diff --git a/js/src/jsgcinlines.h b/js/src/jsgcinlines.h index 7c79586ecf57..6ef1f69e18aa 100644 --- a/js/src/jsgcinlines.h +++ b/js/src/jsgcinlines.h @@ -10,6 +10,7 @@ #include "jsgc.h" #include "gc/Zone.h" +#include "vm/ForkJoin.h" namespace js { @@ -56,8 +57,17 @@ ThreadSafeContext::isThreadLocal(T thing) const if (!isForkJoinContext()) return true; - if (!IsInsideNursery(thing) && - allocator_->arenas.containsArena(runtime_, thing->arenaHeader())) +#ifdef JSGC_FJGENERATIONAL + ForkJoinContext *cx = static_cast(const_cast(this)); + if (cx->fjNursery().isInsideNewspace(thing)) + return true; +#endif + + // Global invariant + JS_ASSERT(!IsInsideNursery(thing)); + + // The thing is not in the nursery, but is it in the private tenured area? + if (allocator_->arenas.containsArena(runtime_, thing->arenaHeader())) { // GC should be suppressed in preparation for mutating thread local // objects, as we don't want to trip any barriers. @@ -91,6 +101,14 @@ ShouldNurseryAllocate(const Nursery &nursery, AllocKind kind, InitialHeap heap) } #endif +#ifdef JSGC_FJGENERATIONAL +inline bool +ShouldFJNurseryAllocate(const ForkJoinNursery &nursery, AllocKind kind, InitialHeap heap) +{ + return IsFJNurseryAllocable(kind) && heap != TenuredHeap; +} +#endif + inline JSGCTraceKind GetGCThingTraceKind(const void *thing) { @@ -130,15 +148,19 @@ class ArenaIter init(zone, kind); } - void init(JS::Zone *zone, AllocKind kind) { - aheader = zone->allocator.arenas.getFirstArena(kind); - remainingHeader = zone->allocator.arenas.getFirstArenaToSweep(kind); + void init(Allocator *allocator, AllocKind kind) { + aheader = allocator->arenas.getFirstArena(kind); + remainingHeader = allocator->arenas.getFirstArenaToSweep(kind); if (!aheader) { aheader = remainingHeader; remainingHeader = nullptr; } } + void init(JS::Zone *zone, AllocKind kind) { + init(&zone->allocator, kind); + } + bool done() const { return !aheader; } @@ -187,7 +209,11 @@ class ArenaCellIterImpl } public: - ArenaCellIterImpl() {} + ArenaCellIterImpl() + : firstThingOffset(0) // Squelch + , thingSize(0) // warnings + { + } void initUnsynchronized(ArenaHeader *aheader) { AllocKind kind = aheader->getAllocKind(); @@ -479,6 +505,28 @@ TryNewNurseryObject(ThreadSafeContext *cxArg, size_t thingSize, size_t nDynamicS } #endif /* JSGC_GENERATIONAL */ +#ifdef JSGC_FJGENERATIONAL +template +inline JSObject * +TryNewFJNurseryObject(ForkJoinContext *cx, size_t thingSize, size_t nDynamicSlots) +{ + ForkJoinNursery &nursery = cx->fjNursery(); + bool tooLarge = false; + JSObject *obj = nursery.allocateObject(thingSize, nDynamicSlots, tooLarge); + if (obj) + return obj; + + if (!tooLarge && allowGC) { + nursery.minorGC(); + obj = nursery.allocateObject(thingSize, nDynamicSlots, tooLarge); + if (obj) + return obj; + } + + return nullptr; +} +#endif /* JSGC_FJGENERATIONAL */ + static inline bool PossiblyFail() { @@ -568,6 +616,16 @@ AllocateObject(ThreadSafeContext *cx, AllocKind kind, size_t nDynamicSlots, Init return obj; } #endif +#ifdef JSGC_FJGENERATIONAL + if (cx->isForkJoinContext() && + ShouldFJNurseryAllocate(cx->asForkJoinContext()->fjNursery(), kind, heap)) + { + JSObject *obj = + TryNewFJNurseryObject(cx->asForkJoinContext(), thingSize, nDynamicSlots); + if (obj) + return obj; + } +#endif HeapSlot *slots = nullptr; if (nDynamicSlots) { @@ -615,6 +673,8 @@ AllocateNonObject(ThreadSafeContext *cx) * other hand, since these allocations are extremely common, we don't want to * delay GC from these allocation sites. Instead we allow the GC, but still * fail the allocation, forcing the non-cached path. + * + * Observe this won't be used for ForkJoin allocation, as it takes a JSContext* */ template inline JSObject * diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index ef2e50fa2fde..28469feb0563 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -2824,16 +2824,26 @@ JSObject::setSlotSpan(ThreadSafeContext *cx, HandleObject obj, uint32_t span) return true; } +// This will not run the garbage collector. If a nursery cannot accomodate the slot array +// an attempt will be made to place the array in the tenured area. static HeapSlot * AllocateSlots(ThreadSafeContext *cx, JSObject *obj, uint32_t nslots) { #ifdef JSGC_GENERATIONAL if (cx->isJSContext()) return cx->asJSContext()->runtime()->gc.nursery.allocateSlots(cx->asJSContext(), obj, nslots); +#endif +#ifdef JSGC_FJGENERATIONAL + if (cx->isForkJoinContext()) + return cx->asForkJoinContext()->fjNursery().allocateSlots(obj, nslots); #endif return cx->pod_malloc(nslots); } +// This will not run the garbage collector. If a nursery cannot accomodate the slot array +// an attempt will be made to place the array in the tenured area. +// +// If this returns null then the old slots will be left alone. static HeapSlot * ReallocateSlots(ThreadSafeContext *cx, JSObject *obj, HeapSlot *oldSlots, uint32_t oldCount, uint32_t newCount) @@ -2841,8 +2851,14 @@ ReallocateSlots(ThreadSafeContext *cx, JSObject *obj, HeapSlot *oldSlots, #ifdef JSGC_GENERATIONAL if (cx->isJSContext()) { return cx->asJSContext()->runtime()->gc.nursery.reallocateSlots(cx->asJSContext(), - obj, oldSlots, - oldCount, newCount); + obj, oldSlots, + oldCount, newCount); + } +#endif +#ifdef JSGC_FJGENERATIONAL + if (cx->isForkJoinContext()) { + return cx->asForkJoinContext()->fjNursery().reallocateSlots(obj, oldSlots, + oldCount, newCount); } #endif return (HeapSlot *)cx->realloc_(oldSlots, oldCount * sizeof(HeapSlot), @@ -2914,10 +2930,14 @@ JSObject::growSlots(ThreadSafeContext *cx, HandleObject obj, uint32_t oldCount, static void FreeSlots(ThreadSafeContext *cx, HeapSlot *slots) { - // Note: threads without a JSContext do not have access to nursery allocated things. #ifdef JSGC_GENERATIONAL + // Note: threads without a JSContext do not have access to GGC nursery allocated things. if (cx->isJSContext()) return cx->asJSContext()->runtime()->gc.nursery.freeSlots(cx->asJSContext(), slots); +#endif +#ifdef JSGC_FJGENERATIONAL + if (cx->isForkJoinContext()) + return cx->asForkJoinContext()->fjNursery().freeSlots(slots); #endif js_free(slots); } @@ -3134,6 +3154,8 @@ JSObject::maybeDensifySparseElements(js::ExclusiveContext *cx, HandleObject obj) return ED_OK; } +// This will not run the garbage collector. If a nursery cannot accomodate the element array +// an attempt will be made to place the array in the tenured area. static ObjectElements * AllocateElements(ThreadSafeContext *cx, JSObject *obj, uint32_t nelems) { @@ -3141,10 +3163,16 @@ AllocateElements(ThreadSafeContext *cx, JSObject *obj, uint32_t nelems) if (cx->isJSContext()) return cx->asJSContext()->runtime()->gc.nursery.allocateElements(cx->asJSContext(), obj, nelems); #endif +#ifdef JSGC_FJGENERATIONAL + if (cx->isForkJoinContext()) + return cx->asForkJoinContext()->fjNursery().allocateElements(obj, nelems); +#endif return static_cast(cx->malloc_(nelems * sizeof(HeapValue))); } +// This will not run the garbage collector. If a nursery cannot accomodate the element array +// an attempt will be made to place the array in the tenured area. static ObjectElements * ReallocateElements(ThreadSafeContext *cx, JSObject *obj, ObjectElements *oldHeader, uint32_t oldCount, uint32_t newCount) @@ -3152,8 +3180,14 @@ ReallocateElements(ThreadSafeContext *cx, JSObject *obj, ObjectElements *oldHead #ifdef JSGC_GENERATIONAL if (cx->isJSContext()) { return cx->asJSContext()->runtime()->gc.nursery.reallocateElements(cx->asJSContext(), obj, - oldHeader, oldCount, - newCount); + oldHeader, oldCount, + newCount); + } +#endif +#ifdef JSGC_FJGENERATIONAL + if (cx->isForkJoinContext()) { + return cx->asForkJoinContext()->fjNursery().reallocateElements(obj, oldHeader, + oldCount, newCount); } #endif diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 71c50b00822d..d2bd3ad5f995 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -187,6 +187,10 @@ DenseRangeWriteBarrierPost(JSRuntime *rt, JSObject *obj, uint32_t start, uint32_ #endif } +namespace gc { +class ForkJoinNursery; +} + } /* namespace js */ /* @@ -206,6 +210,7 @@ class JSObject : public js::ObjectImpl friend struct js::GCMarker; friend class js::NewObjectCache; friend class js::Nursery; + friend class js::gc::ForkJoinNursery; /* Make the type object to use for LAZY_TYPE objects. */ static js::types::TypeObject *makeLazyType(JSContext *cx, js::HandleObject obj); diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index 94af5dac8954..0b755c58ef74 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -21,6 +21,7 @@ #include "jsgcinlines.h" #include "jsinferinlines.h" +#include "gc/ForkJoinNursery-inl.h" #include "vm/ObjectImpl-inl.h" /* static */ inline bool diff --git a/js/src/jspubtd.h b/js/src/jspubtd.h index 1e16c5d37e9a..57f9d91ada9e 100644 --- a/js/src/jspubtd.h +++ b/js/src/jspubtd.h @@ -266,6 +266,13 @@ class JS_PUBLIC_API(AutoGCRooter) static void traceAll(JSTracer *trc); static void traceAllWrappers(JSTracer *trc); + /* T must be a context type */ + template + static void traceAllInContext(T* cx, JSTracer *trc) { + for (AutoGCRooter *gcr = cx->autoGCRooters; gcr; gcr = gcr->down) + gcr->trace(trc); + } + protected: AutoGCRooter * const down; diff --git a/js/src/moz.build b/js/src/moz.build index e197c4d8da00..b817394b23b7 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -112,6 +112,7 @@ UNIFIED_SOURCES += [ 'frontend/ParseNode.cpp', 'frontend/TokenStream.cpp', 'gc/Barrier.cpp', + 'gc/ForkJoinNursery.cpp', 'gc/Iteration.cpp', 'gc/Marking.cpp', 'gc/Memory.cpp', @@ -457,6 +458,8 @@ if CONFIG['NIGHTLY_BUILD']: DEFINES['ENABLE_PARALLEL_JS'] = True DEFINES['ENABLE_BINARYDATA'] = True DEFINES['ENABLE_SHARED_ARRAY_BUFFER'] = True + if CONFIG['JSGC_GENERATIONAL_CONFIGURED']: + DEFINES['JSGC_FJGENERATIONAL'] = True DEFINES['EXPORT_JS_API'] = True diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index 062fa44b49ac..b302340c6172 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -799,8 +799,14 @@ ArrayBufferObject::finalize(FreeOp *fop, JSObject *obj) /* static */ void ArrayBufferObject::obj_trace(JSTracer *trc, JSObject *obj) { - if (!IS_GC_MARKING_TRACER(trc) && !trc->runtime()->isHeapMinorCollecting()) + if (!IS_GC_MARKING_TRACER(trc) && !trc->runtime()->isHeapMinorCollecting() +#ifdef JSGC_FJGENERATIONAL + && !trc->runtime()->isFJMinorCollecting() +#endif + ) + { return; + } // ArrayBufferObjects need to maintain a list of possibly-weak pointers to // their views. The straightforward way to update the weak pointers would diff --git a/js/src/vm/ForkJoin.cpp b/js/src/vm/ForkJoin.cpp index 4b618aee9d02..aecf8ba603d6 100644 --- a/js/src/vm/ForkJoin.cpp +++ b/js/src/vm/ForkJoin.cpp @@ -27,7 +27,7 @@ #if defined(JS_THREADSAFE) && defined(JS_ION) # include "jit/JitCommon.h" -# ifdef DEBUG +# ifdef FORKJOIN_SPEW # include "jit/Ion.h" # include "jit/JitCompartment.h" # include "jit/MIR.h" @@ -35,6 +35,7 @@ # endif #endif // THREADSAFE && ION +#include "gc/ForkJoinNursery-inl.h" #include "vm/Interpreter-inl.h" using namespace js; @@ -279,7 +280,7 @@ class ForkJoinOperation jsbytecode *bailoutBytecode; ForkJoinOperation(JSContext *cx, HandleFunction fun, uint16_t sliceStart, - uint16_t sliceEnd, ForkJoinMode mode); + uint16_t sliceEnd, ForkJoinMode mode, HandleObject updatable); ExecutionStatus apply(); private: @@ -318,6 +319,7 @@ class ForkJoinOperation JSContext *cx_; HandleFunction fun_; + HandleObject updatable_; uint16_t sliceStart_; uint16_t sliceEnd_; Vector bailoutRecords_; @@ -345,12 +347,17 @@ class ForkJoinOperation class ForkJoinShared : public ParallelJob, public Monitor { +#ifdef JSGC_FJGENERATIONAL + friend class gc::ForkJoinGCShared; +#endif + ///////////////////////////////////////////////////////////////////////// // Constant fields JSContext *const cx_; // Current context ThreadPool *const threadPool_; // The thread pool HandleFunction fun_; // The JavaScript function to execute + HandleObject updatable_; // Pre-existing object that might be updated uint16_t sliceStart_; // The starting slice id. uint16_t sliceEnd_; // The ending slice id + 1. PRLock *cxLock_; // Locks cx_ for parallel VM calls @@ -387,6 +394,7 @@ class ForkJoinShared : public ParallelJob, public Monitor ForkJoinShared(JSContext *cx, ThreadPool *threadPool, HandleFunction fun, + HandleObject updatable, uint16_t sliceStart, uint16_t sliceEnd, ParallelBailoutRecord *records); @@ -428,6 +436,8 @@ class ForkJoinShared : public ParallelJob, public Monitor JSContext *acquireJSContext() { PR_Lock(cxLock_); return cx_; } void releaseJSContext() { PR_Unlock(cxLock_); } + + HandleObject updatable() { return updatable_; } }; class AutoEnterWarmup @@ -502,24 +512,26 @@ static const char *ForkJoinModeString(ForkJoinMode mode); bool js::ForkJoin(JSContext *cx, CallArgs &args) { - JS_ASSERT(args.length() == 4); // else the self-hosted code is wrong + JS_ASSERT(args.length() == 5); // else the self-hosted code is wrong JS_ASSERT(args[0].isObject()); JS_ASSERT(args[0].toObject().is()); JS_ASSERT(args[1].isInt32()); JS_ASSERT(args[2].isInt32()); JS_ASSERT(args[3].isInt32()); JS_ASSERT(args[3].toInt32() < NumForkJoinModes); + JS_ASSERT(args[4].isObjectOrNull()); RootedFunction fun(cx, &args[0].toObject().as()); uint16_t sliceStart = (uint16_t)(args[1].toInt32()); uint16_t sliceEnd = (uint16_t)(args[2].toInt32()); ForkJoinMode mode = (ForkJoinMode)(args[3].toInt32()); + RootedObject updatable(cx, args[4].toObjectOrNull()); MOZ_ASSERT(sliceStart == args[1].toInt32()); MOZ_ASSERT(sliceEnd == args[2].toInt32()); MOZ_ASSERT(sliceStart <= sliceEnd); - ForkJoinOperation op(cx, fun, sliceStart, sliceEnd, mode); + ForkJoinOperation op(cx, fun, sliceStart, sliceEnd, mode, updatable); ExecutionStatus status = op.apply(); if (status == ExecutionFatal) return false; @@ -578,13 +590,14 @@ ForkJoinModeString(ForkJoinMode mode) { } ForkJoinOperation::ForkJoinOperation(JSContext *cx, HandleFunction fun, uint16_t sliceStart, - uint16_t sliceEnd, ForkJoinMode mode) + uint16_t sliceEnd, ForkJoinMode mode, HandleObject updatable) : bailouts(0), bailoutCause(ParallelBailoutNone), bailoutScript(cx), bailoutBytecode(nullptr), cx_(cx), fun_(fun), + updatable_(updatable), sliceStart_(sliceStart), sliceEnd_(sliceEnd), bailoutRecords_(cx), @@ -1237,7 +1250,8 @@ ForkJoinOperation::parallelExecution(ExecutionStatus *status) ForkJoinActivation activation(cx_); ThreadPool *threadPool = &cx_->runtime()->threadPool; - ForkJoinShared shared(cx_, threadPool, fun_, sliceStart_, sliceEnd_, &bailoutRecords_[0]); + ForkJoinShared shared(cx_, threadPool, fun_, updatable_, sliceStart_, sliceEnd_, + &bailoutRecords_[0]); if (!shared.init()) { *status = ExecutionFatal; return RedLight; @@ -1333,7 +1347,8 @@ class ParallelIonInvoke bool invoke(ForkJoinContext *cx) { JitActivation activation(cx); - Value result; + // In-out parameter: on input it denotes the number of values to preserve after the call. + Value result = Int32Value(0); CALL_GENERATED_CODE(enter_, jitcode_, argc_ + 1, argv_ + 1, nullptr, calleeToken_, nullptr, 0, &result); return !result.isMagic(); @@ -1347,12 +1362,14 @@ class ParallelIonInvoke ForkJoinShared::ForkJoinShared(JSContext *cx, ThreadPool *threadPool, HandleFunction fun, + HandleObject updatable, uint16_t sliceStart, uint16_t sliceEnd, ParallelBailoutRecord *records) : cx_(cx), threadPool_(threadPool), fun_(fun), + updatable_(updatable), sliceStart_(sliceStart), sliceEnd_(sliceEnd), cxLock_(nullptr), @@ -1402,7 +1419,8 @@ ForkJoinShared::init() ForkJoinShared::~ForkJoinShared() { - PR_DestroyLock(cxLock_); + if (cxLock_) + PR_DestroyLock(cxLock_); while (allocators_.length() > 0) js_delete(allocators_.popCopy()); @@ -1425,12 +1443,15 @@ ForkJoinShared::execute() // Push parallel tasks and wait until they're all done. jobResult = threadPool_->executeJob(cx_, this, sliceStart_, sliceEnd_); - if (jobResult == TP_FATAL) - return TP_FATAL; } + // Arenas must be transfered unconditionally until we have the means + // to clear the ForkJoin result array, see bug 993347. transferArenasToCompartmentAndProcessGCRequests(); + if (jobResult == TP_FATAL) + return TP_FATAL; + // Check if any of the workers failed. if (abort_) { if (fatal_) @@ -1438,11 +1459,15 @@ ForkJoinShared::execute() return TP_RETRY_SEQUENTIALLY; } -#ifdef DEBUG +#ifdef FORKJOIN_SPEW Spew(SpewOps, "Completed parallel job [slices: %d, threads: %d, stolen: %d (work stealing:%s)]", sliceEnd_ - sliceStart_, threadPool_->numWorkers(), +#ifdef DEBUG threadPool_->stolenSlices(), +#else + 0, +#endif threadPool_->workStealing() ? "ON" : "OFF"); #endif @@ -1458,6 +1483,7 @@ ForkJoinShared::transferArenasToCompartmentAndProcessGCRequests() comp->adoptWorkerAllocator(allocators_[i]); if (gcRequested_) { + Spew(SpewGC, "Triggering garbage collection in SpiderMonkey heap"); if (!gcZone_) TriggerGC(cx_->runtime(), gcReason_); else @@ -1493,7 +1519,22 @@ ForkJoinShared::executeFromWorker(ThreadPoolWorker *worker, uintptr_t stackLimit bool ForkJoinShared::executeFromMainThread(ThreadPoolWorker *worker) { - executePortion(&cx_->mainThread(), worker); + // Note that we need new PerThreadData on the main thread as well, + // so that PJS GC does not walk up the old mainThread stack. + PerThreadData *oldData = TlsPerThreadData.get(); + PerThreadData thisThread(cx_->runtime()); + if (!thisThread.init()) { + setAbortFlagAndRequestInterrupt(true); + return false; + } + TlsPerThreadData.set(&thisThread); + + // Don't use setIonStackLimit() because that acquires the ionStackLimitLock, and the + // lock has not been initialized in these cases. + thisThread.jitStackLimit = oldData->jitStackLimit; + executePortion(&thisThread, worker); + TlsPerThreadData.set(oldData); + return !abort_; } @@ -1512,7 +1553,7 @@ ForkJoinShared::executePortion(PerThreadData *perThread, ThreadPoolWorker *worke // assertion here for maximum clarity. JS::AutoSuppressGCAnalysis nogc; -#ifdef DEBUG +#ifdef FORKJOIN_SPEW // Set the maximum worker and slice number for prettier spewing. cx.maxWorkerId = threadPool_->numWorkers(); #endif @@ -1544,8 +1585,36 @@ ForkJoinShared::executePortion(PerThreadData *perThread, ThreadPoolWorker *worke bool ok = fii.invoke(&cx); JS_ASSERT(ok == !cx.bailoutRecord->topScript); - if (!ok) + if (!ok) { setAbortFlagAndRequestInterrupt(false); +#ifdef JSGC_FJGENERATIONAL + // TODO: See bugs 1010169, 993347. + // + // It is not desirable to promote here, but if we don't do + // this then we can't unconditionally transfer arenas to + // the compartment, since the arenas can contain objects + // that point into the nurseries. If those objects are + // touched at all by the GC, eg as part of a prebarrier, + // then chaos ensues. + // + // The proper fix might appear to be to note the abort and + // not transfer, but instead clear, the arenas. However, + // the result array will remain live and unless it is + // cleared immediately and without running barriers then + // it will have pointers into the now-cleared areas, which + // is also wrong. + // + // For the moment, until we figure out how to clear the + // result array properly and implement that, it may be + // that the best thing we can do here is to evacuate and + // then let the GC run its course. + cx.evacuateLiveData(); +#endif + } else { +#ifdef JSGC_FJGENERATIONAL + cx.evacuateLiveData(); +#endif + } } Spew(SpewOps, "Down"); @@ -1608,6 +1677,49 @@ ForkJoinShared::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason) } } +#ifdef JSGC_FJGENERATIONAL + +JSRuntime* +js::gc::ForkJoinGCShared::runtime() +{ + return shared_->runtime(); +} + +JS::Zone* +js::gc::ForkJoinGCShared::zone() +{ + return shared_->zone(); +} + +JSObject* +js::gc::ForkJoinGCShared::updatable() +{ + return shared_->updatable(); +} + +js::gc::ForkJoinNurseryChunk * +js::gc::ForkJoinGCShared::allocateNurseryChunk() +{ + return shared_->threadPool_->getChunk(); +} + +void +js::gc::ForkJoinGCShared::freeNurseryChunk(js::gc::ForkJoinNurseryChunk *p) +{ + shared_->threadPool_->putFreeChunk(p); +} + +void +js::gc::ForkJoinGCShared::spewGC(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + SpewVA(SpewGC, fmt, ap); + va_end(ap); +} + +#endif // JSGC_FJGENERATIONAL + ///////////////////////////////////////////////////////////////////////////// // ForkJoinContext // @@ -1620,6 +1732,10 @@ ForkJoinContext::ForkJoinContext(PerThreadData *perThreadData, ThreadPoolWorker targetRegionStart(nullptr), targetRegionEnd(nullptr), shared_(shared), +#ifdef JSGC_FJGENERATIONAL + gcShared_(shared), + fjNursery_(const_cast(this), &this->gcShared_, allocator), +#endif worker_(worker), acquiredJSContext_(false), nogc_() @@ -1779,7 +1895,7 @@ js::ParallelBailoutRecord::addTrace(JSScript *script, // Debug spew // -#ifdef DEBUG +#ifdef FORKJOIN_SPEW static const char * ExecutionStatusToString(ExecutionStatus status) @@ -1873,6 +1989,8 @@ class ParallelSpewer active[SpewCompile] = true; if (strstr(env, "bailouts")) active[SpewBailouts] = true; + if (strstr(env, "gc")) + active[SpewGC] = true; if (strstr(env, "full")) { for (uint32_t i = 0; i < NumSpewChannels; i++) active[i] = true; @@ -2077,6 +2195,12 @@ parallel::Spew(SpewChannel channel, const char *fmt, ...) va_end(ap); } +void +parallel::SpewVA(SpewChannel channel, const char *fmt, va_list ap) +{ + spewer.spewVA(channel, fmt, ap); +} + void parallel::SpewBeginOp(JSContext *cx, const char *name) { @@ -2125,7 +2249,7 @@ parallel::SpewBailoutIR(IonLIRTraceData *data) spewer.spewBailoutIR(data); } -#endif // DEBUG +#endif // FORKJOIN_SPEW bool js::InExclusiveParallelSection() diff --git a/js/src/vm/ForkJoin.h b/js/src/vm/ForkJoin.h index cf77a7ea89d1..89895f8c3f32 100644 --- a/js/src/vm/ForkJoin.h +++ b/js/src/vm/ForkJoin.h @@ -9,12 +9,19 @@ #include "mozilla/ThreadLocal.h" +#include + #include "jscntxt.h" +#include "gc/ForkJoinNursery.h" #include "gc/GCInternals.h" #include "jit/Ion.h" +#ifdef DEBUG + #define FORKJOIN_SPEW +#endif + /////////////////////////////////////////////////////////////////////////// // Read Me First // @@ -30,7 +37,7 @@ // to enable parallel execution. At the top-level, it consists of a native // function (exposed as the ForkJoin intrinsic) that is used like so: // -// ForkJoin(func, sliceStart, sliceEnd, mode) +// ForkJoin(func, sliceStart, sliceEnd, mode, updatable) // // The intention of this statement is to start some some number (usually the // number of hardware threads) of copies of |func()| running in parallel. Each @@ -47,6 +54,13 @@ // The fourth argument, |mode|, is an internal mode integer giving finer // control over the behavior of ForkJoin. See the |ForkJoinMode| enum. // +// The fifth argument, |updatable|, if not null, is an object that may +// be updated in a race-free manner by |func()| or its callees. +// Typically this is some sort of pre-sized array. Only this object +// may be updated by |func()|, and updates must not race. (A more +// general approach is perhaps desirable, eg passing an Array of +// objects that may be updated, but that is not presently needed.) +// // func() should expect the following arguments: // // func(workerId, sliceStart, sliceEnd) @@ -164,7 +178,7 @@ // the error location might not be in the same JSScript as the one // which was executing due to inlining. // -// Garbage collection and allocation: +// Garbage collection, allocation, and write barriers: // // Code which executes on these parallel threads must be very careful // with respect to garbage collection and allocation. The typical @@ -173,24 +187,49 @@ // any synchronization. They can also trigger GC in an ad-hoc way. // // To deal with this, the forkjoin code creates a distinct |Allocator| -// object for each slice. You can access the appropriate object via -// the |ForkJoinContext| object that is provided to the callbacks. Once -// the execution is complete, all the objects found in these distinct -// |Allocator| is merged back into the main compartment lists and -// things proceed normally. +// object for each worker, which is used as follows. +// +// In a non-generational setting you can access the appropriate +// allocator via the |ForkJoinContext| object that is provided to the +// callbacks. Once the parallel execution is complete, all the +// objects found in these distinct |Allocator| are merged back into +// the main compartment lists and things proceed normally. (If it is +// known that the result array contains no references then no merging +// is necessary.) +// +// In a generational setting there is a per-thread |ForkJoinNursery| +// in addition to the per-thread Allocator. All "simple" objects +// (meaning they are reasonably small, can be copied, and have no +// complicated finalization semantics) are allocated in the nurseries; +// other objects are allocated directly in the threads' Allocators, +// which serve as the tenured areas for the threads. +// +// When a thread's nursery fills up it can be collected independently +// of the other threads' nurseries, and does not require any of the +// threads to bail out of the parallel section. The nursery is +// copy-collected, and the expectation is that the survival rate will +// be very low and the collection will be very cheap. +// +// When the parallel execution is complete, and only if merging of the +// Allocators into the main compartment is necessary, then the live +// objects of the nurseries are copied into the respective Allocators, +// in parallel, before the merging takes place. // // In Ion-generated code, we will do allocation through the -// |Allocator| found in |ForkJoinContext| (which is obtained via TLS). -// Also, no write barriers are emitted. Conceptually, we should never -// need a write barrier because we only permit writes to objects that -// are newly allocated, and such objects are always black (to use -// incremental GC terminology). However, to be safe, we also block -// upon entering a parallel section to ensure that any concurrent -// marking or incremental GC has completed. +// |ForkJoinNursery| or |Allocator| found in |ForkJoinContext| (which +// is obtained via TLS). +// +// No write barriers are emitted. We permit writes to thread-local +// objects, and such writes can create cross-generational pointers or +// pointers that may interact with incremental GC. However, the +// per-thread generational collector scans its entire tenured area on +// each minor collection, and we block upon entering a parallel +// section to ensure that any concurrent marking or incremental GC has +// completed. // // In the future, it should be possible to lift the restriction that -// we must block until inc. GC has completed and also to permit GC -// during parallel exeution. But we're not there yet. +// we must block until incremental GC has completed. But we're not +// there yet. // // Load balancing (work stealing): // @@ -316,7 +355,7 @@ class ForkJoinContext : public ThreadSafeContext // Bailout record used to record the reason this thread stopped executing ParallelBailoutRecord *const bailoutRecord; -#ifdef DEBUG +#ifdef FORKJOIN_SPEW // Records the last instr. to execute on this thread. IonLIRTraceData traceData; @@ -412,6 +451,21 @@ class ForkJoinContext : public ThreadSafeContext return offsetof(ForkJoinContext, worker_); } +#ifdef JSGC_FJGENERATIONAL + // There is already a nursery() method in ThreadSafeContext. + gc::ForkJoinNursery &fjNursery() { return fjNursery_; } + + // Evacuate live data from the per-thread nursery into the per-thread + // tenured area. + void evacuateLiveData() { fjNursery_.evacuatingGC(); } + + // Used in inlining nursery allocation. Note the nursery is a + // member of the ForkJoinContext (a substructure), not a pointer. + static size_t offsetOfFJNursery() { + return offsetof(ForkJoinContext, fjNursery_); + } +#endif + private: friend class AutoSetForkJoinContext; @@ -420,6 +474,11 @@ class ForkJoinContext : public ThreadSafeContext ForkJoinShared *const shared_; +#ifdef JSGC_FJGENERATIONAL + gc::ForkJoinGCShared gcShared_; + gc::ForkJoinNursery fjNursery_; +#endif + ThreadPoolWorker *worker_; bool acquiredJSContext_; @@ -504,13 +563,15 @@ enum SpewChannel { SpewOps, SpewCompile, SpewBailouts, + SpewGC, NumSpewChannels }; -#if defined(DEBUG) && defined(JS_THREADSAFE) && defined(JS_ION) +#if defined(FORKJOIN_SPEW) && defined(JS_THREADSAFE) && defined(JS_ION) bool SpewEnabled(SpewChannel channel); void Spew(SpewChannel channel, const char *fmt, ...); +void SpewVA(SpewChannel channel, const char *fmt, va_list args); void SpewBeginOp(JSContext *cx, const char *name); void SpewBailout(uint32_t count, HandleScript script, jsbytecode *pc, ParallelBailoutCause cause); @@ -524,6 +585,7 @@ void SpewBailoutIR(IonLIRTraceData *data); static inline bool SpewEnabled(SpewChannel channel) { return false; } static inline void Spew(SpewChannel channel, const char *fmt, ...) { } +static inline void SpewVA(SpewChannel channel, const char *fmt, va_list args) { } static inline void SpewBeginOp(JSContext *cx, const char *name) { } static inline void SpewBailout(uint32_t count, HandleScript script, jsbytecode *pc, ParallelBailoutCause cause) {} @@ -535,7 +597,7 @@ static inline void SpewMIR(jit::MDefinition *mir, const char *fmt, ...) { } #endif static inline void SpewBailoutIR(IonLIRTraceData *data) { } -#endif // DEBUG && JS_THREADSAFE && JS_ION +#endif // FORKJOIN_SPEW && JS_THREADSAFE && JS_ION } // namespace parallel } // namespace js diff --git a/js/src/vm/ObjectImpl.h b/js/src/vm/ObjectImpl.h index 0fd8621ae213..f807e24ade83 100644 --- a/js/src/vm/ObjectImpl.h +++ b/js/src/vm/ObjectImpl.h @@ -30,6 +30,10 @@ class ObjectImpl; class Nursery; class Shape; +namespace gc { +class ForkJoinNursery; +} + /* * To really poison a set of values, using 'magic' or 'undefined' isn't good * enough since often these will just be ignored by buggy code (see bug 629974) @@ -177,6 +181,7 @@ class ObjectElements friend class ObjectImpl; friend class ArrayObject; friend class Nursery; + friend class gc::ForkJoinNursery; template friend bool @@ -445,6 +450,7 @@ class ObjectImpl : public gc::BarrieredCell private: friend class Nursery; + friend class gc::ForkJoinNursery; /* * Get internal pointers to the range of values starting at start and diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 0c009662e955..d0524eeed3d7 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -955,6 +955,15 @@ struct JSRuntime : public JS::shadow::Runtime, bool isHeapMinorCollecting() { return gc.isHeapMinorCollecting(); } bool isHeapCollecting() { return gc.isHeapCollecting(); } + // Performance note: if isFJMinorCollecting turns out to be slow + // because reading the counter is slow then we may be able to + // augment the counter with a volatile flag that is set iff the + // counter is greater than zero. (It will require some care to + // make sure the two variables stay in sync.) + bool isFJMinorCollecting() { return gc.fjCollectionCounter > 0; } + void incFJMinorCollecting() { gc.fjCollectionCounter++; } + void decFJMinorCollecting() { gc.fjCollectionCounter--; } + int gcZeal() { return gc.zeal(); } void lockGC() { diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index c6a0c95aa09a..7454f4da860d 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -304,7 +304,11 @@ JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(intrinsic_ParallelSpew_jitInfo, intrinsic_ #endif /* - * ForkJoin(func, feedback): Invokes |func| many times in parallel. + * ForkJoin(func, sliceStart, sliceEnd, mode, updatable): Invokes |func| many times in parallel. + * + * If "func" will update a pre-existing object then that object /must/ be passed + * as the object "updatable". It is /not/ correct to pass an object that + * references the updatable objects indirectly. * * See ForkJoin.cpp for details and ParallelArray.js for examples. */ @@ -786,7 +790,7 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("NewStringIterator", intrinsic_NewStringIterator, 0,0), JS_FN("IsStringIterator", intrinsic_IsStringIterator, 1,0), - JS_FN("ForkJoin", intrinsic_ForkJoin, 2,0), + JS_FN("ForkJoin", intrinsic_ForkJoin, 5,0), JS_FN("ForkJoinNumWorkers", intrinsic_ForkJoinNumWorkers, 0,0), JS_FN("NewDenseArray", intrinsic_NewDenseArray, 1,0), JS_FN("ShouldForceSequential", intrinsic_ShouldForceSequential, 0,0), diff --git a/js/src/vm/Shape.cpp b/js/src/vm/Shape.cpp index 6a5aab059d66..832654bb234e 100644 --- a/js/src/vm/Shape.cpp +++ b/js/src/vm/Shape.cpp @@ -22,6 +22,7 @@ #include "jscntxtinlines.h" #include "jsobjinlines.h" +#include "gc/ForkJoinNursery-inl.h" #include "vm/ObjectImpl-inl.h" #include "vm/Runtime-inl.h" diff --git a/js/src/vm/Shape.h b/js/src/vm/Shape.h index bc2d56a750d7..1d4bd968e467 100644 --- a/js/src/vm/Shape.h +++ b/js/src/vm/Shape.h @@ -111,6 +111,10 @@ class Nursery; class ObjectImpl; class StaticBlockObject; +namespace gc { +class ForkJoinNursery; +} + typedef JSPropertyOp PropertyOp; typedef JSStrictPropertyOp StrictPropertyOp; typedef JSPropertyDescriptor PropertyDescriptor; @@ -612,6 +616,7 @@ class Shape : public gc::BarrieredCell friend class ::JSFunction; friend class js::Bindings; friend class js::Nursery; + friend class js::gc::ForkJoinNursery; friend class js::ObjectImpl; friend class js::PropertyTree; friend class js::StaticBlockObject; diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index ee4a3990ecfa..d231b04d1515 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -437,14 +437,13 @@ MarkInterpreterActivation(JSTracer *trc, InterpreterActivation *act) } void -js::MarkInterpreterActivations(JSRuntime *rt, JSTracer *trc) +js::MarkInterpreterActivations(PerThreadData *ptd, JSTracer *trc) { - for (ActivationIterator iter(rt); !iter.done(); ++iter) { + for (ActivationIterator iter(ptd); !iter.done(); ++iter) { Activation *act = iter.activation(); if (act->isInterpreter()) MarkInterpreterActivation(trc, act->asInterpreter()); } - } /*****************************************************************************/ diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index eb62c659784a..309c1867f694 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -1093,7 +1093,7 @@ class InterpreterStack } }; -void MarkInterpreterActivations(JSRuntime *rt, JSTracer *trc); +void MarkInterpreterActivations(PerThreadData *ptd, JSTracer *trc); /*****************************************************************************/ diff --git a/js/src/vm/ThreadPool.cpp b/js/src/vm/ThreadPool.cpp index 0d9b302e5634..3108685d375a 100644 --- a/js/src/vm/ThreadPool.cpp +++ b/js/src/vm/ThreadPool.cpp @@ -10,10 +10,15 @@ #include "jslock.h" +#include "js/Utility.h" #include "vm/ForkJoin.h" #include "vm/Monitor.h" #include "vm/Runtime.h" +#ifdef JSGC_FJGENERATIONAL +#include "prmjtime.h" +#endif + using namespace js; const size_t WORKER_THREAD_STACK_SIZE = 1*1024*1024; @@ -256,18 +261,25 @@ ThreadPool::ThreadPool(JSRuntime *rt) : activeWorkers_(0), joinBarrier_(nullptr), job_(nullptr), -#ifdef DEBUG runtime_(rt), +#ifdef DEBUG stolenSlices_(0), #endif pendingSlices_(0), - isMainThreadActive_(false) + isMainThreadActive_(false), + chunkLock_(nullptr), + timeOfLastAllocation_(0), + freeChunks_(nullptr) { } ThreadPool::~ThreadPool() { terminateWorkers(); + if (chunkLock_) + clearChunkCache(); #ifdef JS_THREADSAFE + if (chunkLock_) + PR_DestroyLock(chunkLock_); if (joinBarrier_) PR_DestroyCondVar(joinBarrier_); #endif @@ -280,10 +292,13 @@ ThreadPool::init() if (!Monitor::init()) return false; joinBarrier_ = PR_NewCondVar(lock_); - return !!joinBarrier_; -#else - return true; + if (!joinBarrier_) + return false; + chunkLock_ = PR_NewLock(); + if (!chunkLock_) + return false; #endif + return true; } uint32_t @@ -482,3 +497,92 @@ ThreadPool::abortJob() // the thread pool having more work. while (hasWork()); } + +// We are not using the markPagesUnused() / markPagesInUse() APIs here +// for two reasons. One, the free list is threaded through the +// chunks, so some pages are actually in use. Two, the expectation is +// that a small number of chunks will be used intensively for a short +// while and then be abandoned at the next GC. +// +// It's an open question whether it's best to go directly to the +// pageAllocator, as now, or go via the GC's chunk pool. Either way +// there's a need to manage a predictable chunk cache here as we don't +// want chunks to be deallocated during a parallel section. + +gc::ForkJoinNurseryChunk * +ThreadPool::getChunk() +{ +#ifdef JSGC_FJGENERATIONAL + PR_Lock(chunkLock_); + timeOfLastAllocation_ = PRMJ_Now()/1000000; + ChunkFreeList *p = freeChunks_; + if (p) + freeChunks_ = p->next; + PR_Unlock(chunkLock_); + + if (p) { + // Already poisoned. + return reinterpret_cast(p); + } + gc::ForkJoinNurseryChunk *c = + reinterpret_cast( + runtime_->gc.pageAllocator.mapAlignedPages(gc::ChunkSize, gc::ChunkSize)); + if (!c) + return c; + poisonChunk(c); + return c; +#else + return nullptr; +#endif +} + +void +ThreadPool::putFreeChunk(gc::ForkJoinNurseryChunk *c) +{ +#ifdef JSGC_FJGENERATIONAL + poisonChunk(c); + + PR_Lock(chunkLock_); + ChunkFreeList *p = reinterpret_cast(c); + p->next = freeChunks_; + freeChunks_ = p; + PR_Unlock(chunkLock_); +#endif +} + +void +ThreadPool::poisonChunk(gc::ForkJoinNurseryChunk *c) +{ +#ifdef JSGC_FJGENERATIONAL +#ifdef DEBUG + memset(c, JS_POISONED_FORKJOIN_CHUNK, gc::ChunkSize); +#endif + c->trailer.runtime = nullptr; +#endif +} + +void +ThreadPool::pruneChunkCache() +{ +#ifdef JSGC_FJGENERATIONAL + if (PRMJ_Now()/1000000 - timeOfLastAllocation_ >= secondsBeforePrune) + clearChunkCache(); +#endif +} + +void +ThreadPool::clearChunkCache() +{ +#ifdef JSGC_FJGENERATIONAL + PR_Lock(chunkLock_); + ChunkFreeList *p = freeChunks_; + freeChunks_ = nullptr; + PR_Unlock(chunkLock_); + + while (p) { + ChunkFreeList *victim = p; + p = p->next; + runtime_->gc.pageAllocator.unmapPages(victim, gc::ChunkSize); + } +#endif +} diff --git a/js/src/vm/ThreadPool.h b/js/src/vm/ThreadPool.h index 047d94b88677..8a884a37a726 100644 --- a/js/src/vm/ThreadPool.h +++ b/js/src/vm/ThreadPool.h @@ -24,6 +24,10 @@ namespace js { class ThreadPool; +namespace gc { +struct ForkJoinNurseryChunk; +} + ///////////////////////////////////////////////////////////////////////////// // ThreadPoolWorker // @@ -174,10 +178,9 @@ class ThreadPool : public Monitor // The current job. ParallelJob *job_; -#ifdef DEBUG // Initialized at startup only. JSRuntime *const runtime_; - +#ifdef DEBUG // Number of stolen slices in the last parallel job. mozilla::Atomic stolenSlices_; #endif @@ -250,6 +253,80 @@ class ThreadPool : public Monitor // Abort the current job. void abortJob(); + + // Chunk pool for the PJS parallel nurseries. The nurseries need + // to have a useful pool of cheap chunks, they cannot map/unmap + // chunks as needed, as that slows down collection much too much. + // + // Technically the following should be #ifdef JSGC_FJGENERATIONAL + // but that affects the observed size of JSRuntime, of which + // ThreadPool is a member. JSGC_FJGENERATIONAL can only be set if + // PJS is enabled, but the latter is enabled in js/src/moz.build; + // meanwhile, JSGC_FJGENERATIONAL must be enabled globally if it + // is enabled at all, since plenty of Firefox code includes files + // to make JSRuntime visible. JSGC_FJGENERATIONAL will go away + // soon, in the mean time the problem is resolved by not making + // definitions exported from SpiderMonkey dependent on it. + + // Obtain chunk memory from the cache, or allocate new. In debug + // mode poison the memory, see poisionChunk(). + // + // Returns nullptr on OOM. + gc::ForkJoinNurseryChunk *getChunk(); + + // Free chunk memory to the cache. In debug mode poison it, see + // poisionChunk(). + void putFreeChunk(gc::ForkJoinNurseryChunk *mem); + + // If enough time has passed since any allocation activity on the + // chunk pool then release any free chunks. It's meaningful to + // call this from the main GC's chunk expiry mechanism; it has low + // cost if it does not do anything. + // + // This must be called with the GC lock taken. + void pruneChunkCache(); + + private: + // Ignore requests to prune the pool until this number of seconds + // has passed since the last allocation request. + static const int32_t secondsBeforePrune = 10; + + // This lock controls access to the following variables and to the + // 'next' field of any ChunkFreeList object reachable from freeChunks_. + // + // You will be tempted to remove this lock and instead introduce a + // lock-free push/pop data structure using Atomic.compareExchange. + // Before you do that, consider that such a data structure + // implemented naively is vulnerable to the ABA problem in a way + // that leads to a corrupt free list; the problem occurs in + // practice during very heavily loaded runs where preeption + // windows can be long (eg, running the parallel jit_tests on all + // cores means having a number of runnable threads quadratic in + // the number of cores). To do better some ABA-defeating scheme + // is needed additionally. + PRLock *chunkLock_; + + // Timestamp of last allocation from the chunk pool, in seconds. + int32_t timeOfLastAllocation_; + + // This structure overlays the beginning of the chunk when the + // chunk is on the free list; the rest of the chunk is unused. + struct ChunkFreeList { + ChunkFreeList *next; + }; + + // List of free chunks. + ChunkFreeList *freeChunks_; + + // Poison a free chunk by filling with JS_POISONED_FORKJOIN_CHUNK + // and setting the runtime pointer to null. + void poisonChunk(gc::ForkJoinNurseryChunk *c); + + // Release the memory of the chunks that are on the free list. + // + // This should be called only from the ThreadPool's destructor or + // from pruneChunkCache(). + void clearChunkCache(); }; } // namespace js diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp index 845088b86745..883186dc348b 100644 --- a/layout/base/RestyleManager.cpp +++ b/layout/base/RestyleManager.cpp @@ -2936,10 +2936,9 @@ RestyleManager::ComputeStyleChangeFor(nsIFrame* aFrame, TreeMatchContext treeMatchContext(true, nsRuleWalker::eRelevantLinkUnvisited, mPresContext->Document()); - nsIContent *parent = content ? content->GetParent() : nullptr; - Element *parentElement = - parent && parent->IsElement() ? parent->AsElement() : nullptr; - treeMatchContext.InitAncestors(parentElement); + Element* parent = + content ? content->GetParentElementCrossingShadowRoot() : nullptr; + treeMatchContext.InitAncestors(parent); nsTArray visibleKidsOfHiddenElement; for (nsIFrame* ibSibling = aFrame; ibSibling; ibSibling = GetNextBlockInInlineSibling(propTable, ibSibling)) { diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp index c790795aa93d..16c74da42add 100644 --- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -193,7 +193,6 @@ class nsIDocumentLoaderFactory; #endif /* MOZ_XUL */ #include "inDeepTreeWalker.h" -#include "inFlasher.h" #include "inCSSValueSearch.h" #include "inDOMUtils.h" @@ -507,7 +506,6 @@ MAKE_CTOR(CreateNewContainerBoxObject, nsIBoxObject, NS_NewContainerB NS_GENERIC_FACTORY_CONSTRUCTOR(inDOMView) #endif NS_GENERIC_FACTORY_CONSTRUCTOR(inDeepTreeWalker) -NS_GENERIC_FACTORY_CONSTRUCTOR(inFlasher) NS_GENERIC_FACTORY_CONSTRUCTOR(inCSSValueSearch) NS_GENERIC_FACTORY_CONSTRUCTOR(inDOMUtils) @@ -663,7 +661,6 @@ NS_DEFINE_NAMED_CID(NS_TREEBOXOBJECT_CID); NS_DEFINE_NAMED_CID(IN_DOMVIEW_CID); #endif NS_DEFINE_NAMED_CID(IN_DEEPTREEWALKER_CID); -NS_DEFINE_NAMED_CID(IN_FLASHER_CID); NS_DEFINE_NAMED_CID(IN_CSSVALUESEARCH_CID); NS_DEFINE_NAMED_CID(IN_DOMUTILS_CID); NS_DEFINE_NAMED_CID(NS_CONTENT_VIEWER_CID); @@ -953,7 +950,6 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = { { &kIN_DOMVIEW_CID, false, nullptr, inDOMViewConstructor }, #endif { &kIN_DEEPTREEWALKER_CID, false, nullptr, inDeepTreeWalkerConstructor }, - { &kIN_FLASHER_CID, false, nullptr, inFlasherConstructor }, { &kIN_CSSVALUESEARCH_CID, false, nullptr, inCSSValueSearchConstructor }, { &kIN_DOMUTILS_CID, false, nullptr, inDOMUtilsConstructor }, { &kNS_CONTENT_VIEWER_CID, false, nullptr, CreateContentViewer }, @@ -1107,7 +1103,6 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = { { "@mozilla.org/inspector/dom-view;1", &kIN_DOMVIEW_CID }, #endif { "@mozilla.org/inspector/deep-tree-walker;1", &kIN_DEEPTREEWALKER_CID }, - { "@mozilla.org/inspector/flasher;1", &kIN_FLASHER_CID }, { "@mozilla.org/inspector/search;1?type=cssvalue", &kIN_CSSVALUESEARCH_CID }, { IN_DOMUTILS_CONTRACTID, &kIN_DOMUTILS_CID }, { "@mozilla.org/xml/xml-document;1", &kNS_XMLDOCUMENT_CID }, diff --git a/layout/inspector/inFlasher.cpp b/layout/inspector/inFlasher.cpp deleted file mode 100644 index fd2acafb6a9b..000000000000 --- a/layout/inspector/inFlasher.cpp +++ /dev/null @@ -1,206 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "inFlasher.h" -#include "inLayoutUtils.h" - -#include "nsIDOMElement.h" -#include "nsIServiceManager.h" -#include "nsIPresShell.h" -#include "nsIFrame.h" -#include "nsIWidget.h" -#include "nsReadableUtils.h" -#include "nsRenderingContext.h" -#include "nsIDOMWindow.h" -#include "nsIContent.h" - -#include "prprf.h" - -/////////////////////////////////////////////////////////////////////////////// - -inFlasher::inFlasher() : - mColor(NS_RGB(0,0,0)), - mThickness(0), - mInvert(false) -{ -} - -inFlasher::~inFlasher() -{ -} - -NS_IMPL_ISUPPORTS(inFlasher, inIFlasher) - -/////////////////////////////////////////////////////////////////////////////// -// inIFlasher - -NS_IMETHODIMP -inFlasher::GetColor(nsAString& aColor) -{ - // Copied from nsGenericHTMLElement::ColorToString() - char buf[10]; - PR_snprintf(buf, sizeof(buf), "#%02x%02x%02x", - NS_GET_R(mColor), NS_GET_G(mColor), NS_GET_B(mColor)); - CopyASCIItoUTF16(buf, aColor); - - return NS_OK; -} - -NS_IMETHODIMP -inFlasher::SetColor(const nsAString& aColor) -{ - NS_ENSURE_FALSE(aColor.IsEmpty(), NS_ERROR_ILLEGAL_VALUE); - - nsAutoString colorStr; - colorStr.Assign(aColor); - - if (colorStr.CharAt(0) != '#') { - if (NS_ColorNameToRGB(colorStr, &mColor)) { - return NS_OK; - } - } - else { - colorStr.Cut(0, 1); - if (NS_HexToRGB(colorStr, &mColor)) { - return NS_OK; - } - } - - return NS_ERROR_ILLEGAL_VALUE; -} - -NS_IMETHODIMP -inFlasher::GetThickness(uint16_t *aThickness) -{ - NS_PRECONDITION(aThickness, "Null pointer"); - *aThickness = mThickness; - return NS_OK; -} - -NS_IMETHODIMP -inFlasher::SetThickness(uint16_t aThickness) -{ - mThickness = aThickness; - return NS_OK; -} - -NS_IMETHODIMP -inFlasher::GetInvert(bool *aInvert) -{ - NS_PRECONDITION(aInvert, "Null pointer"); - *aInvert = mInvert; - return NS_OK; -} - -NS_IMETHODIMP -inFlasher::SetInvert(bool aInvert) -{ - mInvert = aInvert; - return NS_OK; -} - -NS_IMETHODIMP -inFlasher::RepaintElement(nsIDOMElement* aElement) -{ - NS_ENSURE_ARG_POINTER(aElement); - nsIFrame* frame = inLayoutUtils::GetFrameFor(aElement); - if (!frame) return NS_OK; - - frame->InvalidateFrame(); - - return NS_OK; -} - -NS_IMETHODIMP -inFlasher::DrawElementOutline(nsIDOMElement* aElement) -{ - NS_ENSURE_ARG_POINTER(aElement); - nsCOMPtr window = inLayoutUtils::GetWindowFor(aElement); - if (!window) return NS_OK; - nsCOMPtr presShell = inLayoutUtils::GetPresShellFor(window); - if (!presShell) return NS_OK; - - nsIFrame* frame = inLayoutUtils::GetFrameFor(aElement); - - bool isFirstFrame = true; - - while (frame) { - nsPoint offset; - nsIWidget* widget = frame->GetNearestWidget(offset); - if (widget) { - nsRefPtr rcontext = new nsRenderingContext(); - rcontext->Init(frame->PresContext()->DeviceContext(), - widget->GetThebesSurface()); - - nsRect rect(offset, frame->GetSize()); - if (mInvert) { - rcontext->InvertRect(rect); - } - - bool isLastFrame = frame->GetNextContinuation() == nullptr; - DrawOutline(rect.x, rect.y, rect.width, rect.height, rcontext, - isFirstFrame, isLastFrame); - isFirstFrame = false; - } - frame = frame->GetNextContinuation(); - } - - return NS_OK; -} - -NS_IMETHODIMP -inFlasher::ScrollElementIntoView(nsIDOMElement *aElement) -{ - NS_ENSURE_ARG_POINTER(aElement); - nsCOMPtr window = inLayoutUtils::GetWindowFor(aElement); - if (!window) { - return NS_OK; - } - - nsCOMPtr presShell = inLayoutUtils::GetPresShellFor(window); - if (!presShell) { - return NS_OK; - } - - nsCOMPtr content = do_QueryInterface(aElement); - presShell->ScrollContentIntoView(content, - nsIPresShell::ScrollAxis(), - nsIPresShell::ScrollAxis(), - nsIPresShell::SCROLL_OVERFLOW_HIDDEN); - - return NS_OK; -} - -/////////////////////////////////////////////////////////////////////////////// -// inFlasher - -void -inFlasher::DrawOutline(nscoord aX, nscoord aY, nscoord aWidth, nscoord aHeight, - nsRenderingContext* aRenderContext, - bool aDrawBegin, bool aDrawEnd) -{ - aRenderContext->SetColor(mColor); - - DrawLine(aX, aY, aWidth, DIR_HORIZONTAL, BOUND_OUTER, aRenderContext); - if (aDrawBegin) { - DrawLine(aX, aY, aHeight, DIR_VERTICAL, BOUND_OUTER, aRenderContext); - } - DrawLine(aX, aY+aHeight, aWidth, DIR_HORIZONTAL, BOUND_INNER, aRenderContext); - if (aDrawEnd) { - DrawLine(aX+aWidth, aY, aHeight, DIR_VERTICAL, BOUND_INNER, aRenderContext); - } -} - -void -inFlasher::DrawLine(nscoord aX, nscoord aY, nscoord aLength, - bool aDir, bool aBounds, - nsRenderingContext* aRenderContext) -{ - nscoord thickTwips = nsPresContext::CSSPixelsToAppUnits(mThickness); - if (aDir) { // horizontal - aRenderContext->FillRect(aX, aY+(aBounds?0:-thickTwips), aLength, thickTwips); - } else { // vertical - aRenderContext->FillRect(aX+(aBounds?0:-thickTwips), aY, thickTwips, aLength); - } -} diff --git a/layout/inspector/inFlasher.h b/layout/inspector/inFlasher.h deleted file mode 100644 index 259c9b05d968..000000000000 --- a/layout/inspector/inFlasher.h +++ /dev/null @@ -1,47 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef __inFlasher_h__ -#define __inFlasher_h__ - -#include "inIFlasher.h" -#include "nsCoord.h" -#include "nsColor.h" - -class nsRenderingContext; - -#define BOUND_INNER 0 -#define BOUND_OUTER 1 - -#define DIR_VERTICAL 0 -#define DIR_HORIZONTAL 1 - -class inFlasher : public inIFlasher -{ -public: - NS_DECL_ISUPPORTS - NS_DECL_INIFLASHER - - inFlasher(); - virtual ~inFlasher(); - -protected: - void DrawOutline(nscoord aX, nscoord aY, nscoord aWidth, nscoord aHeight, - nsRenderingContext* aRenderContext, - bool aDrawBegin, bool aDrawEnd); - void DrawLine(nscoord aX, nscoord aY, nscoord aLength, - bool aDir, bool aBounds, - nsRenderingContext* aRenderContext); - - nscolor mColor; - - uint16_t mThickness; - bool mInvert; -}; - -// {9286E71A-621A-4b91-851E-9984C1A2E81A} -#define IN_FLASHER_CID \ -{ 0x9286e71a, 0x621a, 0x4b91, { 0x85, 0x1e, 0x99, 0x84, 0xc1, 0xa2, 0xe8, 0x1a } } - -#endif // __inFlasher_h__ diff --git a/layout/inspector/inIFlasher.idl b/layout/inspector/inIFlasher.idl deleted file mode 100644 index d6dab418c567..000000000000 --- a/layout/inspector/inIFlasher.idl +++ /dev/null @@ -1,20 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" - -interface nsIDOMElement; - -[scriptable, uuid(7B4A099F-6F6E-4565-977B-FB622ADBFF49)] -interface inIFlasher : nsISupports -{ - attribute DOMString color; - attribute boolean invert; - attribute unsigned short thickness; - - void drawElementOutline(in nsIDOMElement aElement); - void repaintElement(in nsIDOMElement aElement); - void scrollElementIntoView(in nsIDOMElement aElement); -}; - diff --git a/layout/inspector/inLayoutUtils.cpp b/layout/inspector/inLayoutUtils.cpp index ad524941fe7b..b5128b2483e0 100644 --- a/layout/inspector/inLayoutUtils.cpp +++ b/layout/inspector/inLayoutUtils.cpp @@ -20,38 +20,6 @@ using namespace mozilla; /////////////////////////////////////////////////////////////////////////////// -nsIDOMWindow* -inLayoutUtils::GetWindowFor(nsIDOMNode* aNode) -{ - nsCOMPtr doc1; - aNode->GetOwnerDocument(getter_AddRefs(doc1)); - return GetWindowFor(doc1.get()); -} - -nsIDOMWindow* -inLayoutUtils::GetWindowFor(nsIDOMDocument* aDoc) -{ - nsCOMPtr window; - aDoc->GetDefaultView(getter_AddRefs(window)); - return window; -} - -nsIPresShell* -inLayoutUtils::GetPresShellFor(nsISupports* aThing) -{ - nsCOMPtr window = do_QueryInterface(aThing); - - return window->GetDocShell()->GetPresShell(); -} - -/*static*/ -nsIFrame* -inLayoutUtils::GetFrameFor(nsIDOMElement* aElement) -{ - nsCOMPtr content = do_QueryInterface(aElement); - return content->GetPrimaryFrame(); -} - EventStateManager* inLayoutUtils::GetEventStateManagerFor(nsIDOMElement *aElement) { diff --git a/layout/inspector/inLayoutUtils.h b/layout/inspector/inLayoutUtils.h index 4aa9dbd279d9..826c19ef3362 100644 --- a/layout/inspector/inLayoutUtils.h +++ b/layout/inspector/inLayoutUtils.h @@ -22,10 +22,6 @@ class EventStateManager; class inLayoutUtils { public: - static nsIDOMWindow* GetWindowFor(nsIDOMNode* aNode); - static nsIDOMWindow* GetWindowFor(nsIDOMDocument* aDoc); - static nsIPresShell* GetPresShellFor(nsISupports* aThing); - static nsIFrame* GetFrameFor(nsIDOMElement* aElement); static mozilla::EventStateManager* GetEventStateManagerFor(nsIDOMElement *aElement); static nsIDOMDocument* GetSubDocumentFor(nsIDOMNode* aNode); diff --git a/layout/inspector/moz.build b/layout/inspector/moz.build index f081fca41108..288f6f888067 100644 --- a/layout/inspector/moz.build +++ b/layout/inspector/moz.build @@ -9,7 +9,6 @@ XPIDL_SOURCES += [ 'inIDeepTreeWalker.idl', 'inIDOMUtils.idl', 'inIDOMView.idl', - 'inIFlasher.idl', 'inISearchObserver.idl', 'inISearchProcess.idl', 'nsIDOMFontFace.idl', @@ -27,7 +26,6 @@ UNIFIED_SOURCES += [ 'inCSSValueSearch.cpp', 'inDeepTreeWalker.cpp', 'inDOMUtils.cpp', - 'inFlasher.cpp', 'inLayoutUtils.cpp', 'inSearchLoop.cpp', 'nsFontFace.cpp', diff --git a/layout/style/crashtests/1017798-1.css b/layout/style/crashtests/1017798-1.css new file mode 100644 index 000000000000..feb77d9dcf2e --- /dev/null +++ b/layout/style/crashtests/1017798-1.css @@ -0,0 +1,84 @@ +/* ---------------------------------- + * SWITCHES + * ---------------------------------- */ + +label.pack-switch { + display: inline-block; + vertical-align: middle; + width: 100%; + height: 5rem; + position: relative; + background: none; +} + +label.pack-switch span { + float: left; + font-size: 1.8rem; + color: #333; + padding: 1rem 0 0; + height: 6rem; + line-height: 3rem; + box-sizing: border-box; + display: block; +} + +label.pack-switch input { + margin: 0; + opacity: 0; + position: absolute; + top: 0; + left: 0; +} + +label.pack-switch input:checked ~ span:after { + background-position: center bottom; +} + +/* ---------------------------------- + * ON/OFF SWITCHES + * ---------------------------------- */ + +label.pack-switch input ~ span:after { + content: ''; + position: absolute; + right: 0; + top: 50%; + width: 6rem; + margin: -1.4rem 0 0; + height: 2.7rem; + border-radius: 1.35rem; + overflow: hidden; + background: #e6e6e6 url(images/background_off.png) no-repeat -3.2rem 0 / 9.2rem 2.7rem; + transition: background 0.2s ease; +} + +/* switch: 'ON' state */ +label.pack-switch input:checked ~ span:after { + background: #e6e6e6 url(images/background.png) no-repeat 0 0 / 9.2rem 2.7rem; +} + +/* switch: disabled state */ +label.pack-switch input:disabled ~ span:after { + opacity: 0.4; +} + +label.pack-switch input.uninit ~ span:after { + transition: none; +} + +/****************************************************************************** + * Right-To-Left tweaks + */ +html[dir="rtl"] label.pack-switch input { + left: auto; + right: 0; +} + +html[dir="rtl"] label.pack-switch input ~ span:after { + left: 0; + right: auto; +} + +html[dir="rtl"] label.pack-switch input ~ span:after { + background-position: 0; +} diff --git a/layout/style/crashtests/1017798-1.html b/layout/style/crashtests/1017798-1.html new file mode 100644 index 000000000000..097217d18882 --- /dev/null +++ b/layout/style/crashtests/1017798-1.html @@ -0,0 +1,124 @@ + + + + + + +
+ + + +
+ diff --git a/layout/style/crashtests/crashtests.list b/layout/style/crashtests/crashtests.list index ae342f181a5c..a1b472136329 100644 --- a/layout/style/crashtests/crashtests.list +++ b/layout/style/crashtests/crashtests.list @@ -104,5 +104,6 @@ load 945048-1.html load 972199-1.html load 989965-1.html load 992333-1.html +pref(dom.webcomponents.enabled,true) load 1017798-1.html load large_border_image_width.html load border-image-visited-link.html diff --git a/layout/style/nsCSSRuleProcessor.cpp b/layout/style/nsCSSRuleProcessor.cpp index b0c25f3e97fb..5134f2423f12 100644 --- a/layout/style/nsCSSRuleProcessor.cpp +++ b/layout/style/nsCSSRuleProcessor.cpp @@ -3519,13 +3519,8 @@ TreeMatchContext::InitAncestors(Element *aElement) Element* cur = aElement; do { ancestors.AppendElement(cur); - nsINode* parent = cur->GetParentNode(); - if (!parent->IsElement()) { - break; - } - - cur = parent->AsElement(); - } while (true); + cur = cur->GetParentElementCrossingShadowRoot(); + } while (cur); // Now push them in reverse order. for (uint32_t i = ancestors.Length(); i-- != 0; ) { @@ -3546,13 +3541,8 @@ TreeMatchContext::InitStyleScopes(Element* aElement) Element* cur = aElement; do { ancestors.AppendElement(cur); - nsINode* parent = cur->GetParentNode(); - if (!parent || !parent->IsElement()) { - break; - } - - cur = parent->AsElement(); - } while (true); + cur = cur->GetParentElementCrossingShadowRoot(); + } while (cur); // Now push them in reverse order. for (uint32_t i = ancestors.Length(); i-- != 0; ) { @@ -3616,10 +3606,22 @@ AncestorFilter::PopAncestor() void AncestorFilter::AssertHasAllAncestors(Element *aElement) const { - nsINode* cur = aElement->GetParentNode(); - while (cur && cur->IsElement()) { + Element* cur = aElement->GetParentElementCrossingShadowRoot(); + while (cur) { MOZ_ASSERT(mElements.Contains(cur)); - cur = cur->GetParentNode(); + cur = cur->GetParentElementCrossingShadowRoot(); + } +} + +void +TreeMatchContext::AssertHasAllStyleScopes(Element* aElement) const +{ + Element* cur = aElement->GetParentElementCrossingShadowRoot(); + while (cur) { + if (cur->IsScopedStyleRoot()) { + MOZ_ASSERT(mStyleScopes.Contains(cur)); + } + cur = cur->GetParentElementCrossingShadowRoot(); } } #endif diff --git a/layout/style/nsRuleProcessorData.h b/layout/style/nsRuleProcessorData.h index 26f326d9f709..a1e9b954440f 100644 --- a/layout/style/nsRuleProcessorData.h +++ b/layout/style/nsRuleProcessorData.h @@ -180,16 +180,7 @@ struct MOZ_STACK_CLASS TreeMatchContext { } #ifdef DEBUG - void AssertHasAllStyleScopes(mozilla::dom::Element* aElement) - { - nsINode* cur = aElement->GetParentNode(); - while (cur) { - if (cur->IsScopedStyleRoot()) { - MOZ_ASSERT(mStyleScopes.Contains(cur)); - } - cur = cur->GetParentNode(); - } - } + void AssertHasAllStyleScopes(mozilla::dom::Element* aElement) const; #endif bool SetStyleScopeForSelectorMatching(mozilla::dom::Element* aSubject, diff --git a/testing/specialpowers/content/specialpowers.js b/testing/specialpowers/content/specialpowers.js index 31ff09303ae5..947a1f172c3f 100644 --- a/testing/specialpowers/content/specialpowers.js +++ b/testing/specialpowers/content/specialpowers.js @@ -7,6 +7,9 @@ function SpecialPowers(window) { this.window = Components.utils.getWeakReference(window); + this._windowID = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils) + .currentInnerWindowID; this._encounteredCrashDumpFiles = []; this._unexpectedCrashDumpFiles = { }; this._crashDumpDir = null; @@ -21,6 +24,20 @@ function SpecialPowers(window) { this._pongHandlers = []; this._messageListener = this._messageReceived.bind(this); addMessageListener("SPPingService", this._messageListener); + let (self = this) { + Services.obs.addObserver(function onInnerWindowDestroyed(subject, topic, data) { + var id = subject.QueryInterface(Components.interfaces.nsISupportsPRUint64).data; + if (self._windowID === id) { + Services.obs.removeObserver(onInnerWindowDestroyed, "inner-window-destroyed"); + try { + removeMessageListener("SPPingService", self._messageListener); + } catch (e if e.result == Components.results.NS_ERROR_ILLEGAL_VALUE) { + // Ignore the exception which the message manager has been destroyed. + ; + } + } + }, "inner-window-destroyed", false); + } } SpecialPowers.prototype = new SpecialPowersAPI(); diff --git a/widget/cocoa/nsChildView.h b/widget/cocoa/nsChildView.h index a6e2cf73b5c6..5969473fa656 100644 --- a/widget/cocoa/nsChildView.h +++ b/widget/cocoa/nsChildView.h @@ -539,7 +539,6 @@ public: virtual CompositorParent* NewCompositorParent(int aSurfaceWidth, int aSurfaceHeight); virtual void CreateCompositor(); - virtual gfxASurface* GetThebesSurface(); virtual void PrepareWindowEffects() MOZ_OVERRIDE; virtual void CleanupWindowEffects() MOZ_OVERRIDE; virtual bool PreRender(LayerManagerComposite* aManager) MOZ_OVERRIDE; @@ -644,8 +643,6 @@ protected: nsWeakPtr mAccessible; #endif - nsRefPtr mTempThebesSurface; - // Protects the view from being teared down while a composition is in // progress on the compositor thread. mozilla::Mutex mViewTearDownLock; diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index e0efdc94cdad..02bee0f74504 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -2135,16 +2135,6 @@ nsChildView::NewCompositorParent(int aSurfaceWidth, int aSurfaceHeight) return compositor; } -gfxASurface* -nsChildView::GetThebesSurface() -{ - if (!mTempThebesSurface) { - mTempThebesSurface = new gfxQuartzSurface(gfxSize(1, 1), gfxImageFormat::ARGB32); - } - - return mTempThebesSurface; -} - void nsChildView::NotifyDirtyRegion(const nsIntRegion& aDirtyRegion) { diff --git a/widget/cocoa/nsCocoaWindow.h b/widget/cocoa/nsCocoaWindow.h index a54e69cfd125..1496c03e550c 100644 --- a/widget/cocoa/nsCocoaWindow.h +++ b/widget/cocoa/nsCocoaWindow.h @@ -315,8 +315,6 @@ public: void DispatchSizeModeEvent(); - virtual gfxASurface* GetThebesSurface(); - // be notified that a some form of drag event needs to go into Gecko virtual bool DragEvent(unsigned int aMessage, Point aMouseGlobal, UInt16 aKeyModifiers); diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm index 36d317fc9bdb..484bfbe4ccc8 100644 --- a/widget/cocoa/nsCocoaWindow.mm +++ b/widget/cocoa/nsCocoaWindow.mm @@ -2057,13 +2057,6 @@ NS_IMETHODIMP nsCocoaWindow::SynthesizeNativeMouseEvent(nsIntPoint aPoint, NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; } -gfxASurface* nsCocoaWindow::GetThebesSurface() -{ - if (mPopupContentView) - return mPopupContentView->GetThebesSurface(); - return nullptr; -} - void nsCocoaWindow::SetPopupWindowLevel() { if (!mWindow) diff --git a/widget/gonk/nsWindow.cpp b/widget/gonk/nsWindow.cpp index e6aa2a628948..0e7365d64866 100644 --- a/widget/gonk/nsWindow.cpp +++ b/widget/gonk/nsWindow.cpp @@ -595,18 +595,6 @@ nsWindow::GetLayerManager(PLayerTransactionChild* aShadowManager, return mLayerManager; } -gfxASurface * -nsWindow::GetThebesSurface() -{ - /* This is really a dummy surface; this is only used when doing reflow, because - * we need a RenderingContext to measure text against. - */ - - // XXX this really wants to return already_AddRefed, but this only really gets used - // on direct assignment to a gfxASurface - return new gfxImageSurface(gfxIntSize(5,5), gfxImageFormat::RGB24); -} - void nsWindow::BringToTop() { diff --git a/widget/gonk/nsWindow.h b/widget/gonk/nsWindow.h index 2264d6116c05..e1c9c5cb5183 100644 --- a/widget/gonk/nsWindow.h +++ b/widget/gonk/nsWindow.h @@ -101,7 +101,6 @@ public: LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE, LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT, bool* aAllowRetaining = nullptr); - gfxASurface* GetThebesSurface(); NS_IMETHOD_(void) SetInputContext(const InputContext& aContext, const InputContextAction& aAction); diff --git a/widget/gtk/mozgtk/gtk2/Makefile.in b/widget/gtk/mozgtk/gtk2/Makefile.in index 81953287343b..68c2aab1cb8e 100644 --- a/widget/gtk/mozgtk/gtk2/Makefile.in +++ b/widget/gtk/mozgtk/gtk2/Makefile.in @@ -2,8 +2,18 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. -EXTRA_DSO_LDOPTS += -lgtk-x11-2.0 -lgdk-x11-2.0 - include $(topsrcdir)/config/rules.mk LDFLAGS += -Wl,-soname=$(DLL_PREFIX)mozgtk$(DLL_SUFFIX) + +# If LDFLAGS contains -Wl,--as-needed, we need to add -Wl,--no-as-needed +# before the gtk libraries, otherwise the linker will drop those dependencies +# because no symbols are used from them. But those dependencies need to be +# kept for things to work properly. +ifeq (,$(filter -Wl,--as-needed),$(LDFLAGS)) +no_as_needed = $1 +else +no_as_needed = -Wl,--no-as-needed $1 -Wl,--as-needed +endif + +EXTRA_DSO_LDOPTS += $(call no_as_needed,-lgtk-x11-2.0 -lgdk-x11-2.0) diff --git a/widget/gtk/mozgtk/gtk3/Makefile.in b/widget/gtk/mozgtk/gtk3/Makefile.in index 45edee9aad52..fb9a9c379b47 100644 --- a/widget/gtk/mozgtk/gtk3/Makefile.in +++ b/widget/gtk/mozgtk/gtk3/Makefile.in @@ -2,6 +2,16 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. -EXTRA_DSO_LDOPTS += -lgtk-3 -lgdk-3 - include $(topsrcdir)/config/rules.mk + +# If LDFLAGS contains -Wl,--as-needed, we need to add -Wl,--no-as-needed +# before the gtk libraries, otherwise the linker will drop those dependencies +# because no symbols are used from them. But those dependencies need to be +# kept for things to work properly. +ifeq (,$(filter -Wl,--as-needed),$(LDFLAGS)) +no_as_needed = $1 +else +no_as_needed = -Wl,--no-as-needed $1 -Wl,--as-needed +endif + +EXTRA_DSO_LDOPTS += $(call no_as_needed,-lgtk-3 -lgdk-3) diff --git a/widget/gtk/mozgtk/mozgtk.c b/widget/gtk/mozgtk/mozgtk.c index 448d603d515e..7d829038507c 100644 --- a/widget/gtk/mozgtk/mozgtk.c +++ b/widget/gtk/mozgtk/mozgtk.c @@ -54,6 +54,7 @@ STUB(gdk_property_get) STUB(gdk_screen_get_default) STUB(gdk_screen_get_display) STUB(gdk_screen_get_font_options) +STUB(gdk_screen_get_number) STUB(gdk_screen_get_resolution) STUB(gdk_screen_get_rgba_visual) STUB(gdk_screen_get_root_window) diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h index af6c23981dad..9a372e1dfde2 100644 --- a/widget/gtk/nsWindow.h +++ b/widget/gtk/nsWindow.h @@ -282,7 +282,7 @@ public: virtual nsresult ConfigureChildren(const nsTArray& aConfigurations); nsresult UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect, uint8_t* aAlphas, int32_t aStride); - virtual gfxASurface *GetThebesSurface() MOZ_OVERRIDE; + virtual gfxASurface *GetThebesSurface(); #if (MOZ_WIDGET_GTK == 2) static already_AddRefed GetSurfaceForGdkDrawable(GdkDrawable* aDrawable, diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h index 5cad6cb8cc0f..fceaff168e40 100644 --- a/widget/nsIWidget.h +++ b/widget/nsIWidget.h @@ -30,7 +30,6 @@ class nsDeviceContext; struct nsFont; class nsIRollupListener; class imgIContainer; -class gfxASurface; class nsIContent; class ViewWrapper; class nsIWidgetListener; @@ -100,8 +99,8 @@ typedef void* nsNativeWidget; #endif #define NS_IWIDGET_IID \ -{ 0x5b27abd6, 0x9e53, 0x4a0a, \ - { 0x86, 0xf, 0x77, 0x5c, 0xc5, 0x69, 0x35, 0xf } }; +{ 0x5b27abd6, 0x9e53, 0x4a0a, \ + { 0x86, 0xf, 0x77, 0x5c, 0xc5, 0x69, 0x35, 0xf } }; /* * Window shadow styles @@ -1586,11 +1585,6 @@ class nsIWidget : public nsISupports { */ virtual bool ShowsResizeIndicator(nsIntRect* aResizerRect) = 0; - /** - * Get the Thebes surface associated with this widget. - */ - virtual gfxASurface *GetThebesSurface() = 0; - /** * Return the popup that was last rolled up, or null if there isn't one. */ diff --git a/widget/windows/nsWindow.cpp b/widget/windows/nsWindow.cpp index fa3c7a0b9529..4f5510a09f04 100644 --- a/widget/windows/nsWindow.cpp +++ b/widget/windows/nsWindow.cpp @@ -3387,26 +3387,6 @@ nsWindow::GetLayerManager(PLayerTransactionChild* aShadowManager, return mLayerManager; } -/************************************************************** - * - * SECTION: nsIWidget::GetThebesSurface - * - * Get the Thebes surface associated with this widget. - * - **************************************************************/ - -gfxASurface *nsWindow::GetThebesSurface() -{ - if (mPaintDC) - return (new gfxWindowsSurface(mPaintDC)); - - uint32_t flags = gfxWindowsSurface::FLAG_TAKE_DC; - if (mTransparencyMode != eTransparencyOpaque) { - flags |= gfxWindowsSurface::FLAG_IS_TRANSPARENT; - } - return (new gfxWindowsSurface(mWnd, flags)); -} - /************************************************************** * * SECTION: nsIWidget::OnDefaultButtonLoaded diff --git a/widget/windows/nsWindow.h b/widget/windows/nsWindow.h index e269c7e76d28..db1756953704 100644 --- a/widget/windows/nsWindow.h +++ b/widget/windows/nsWindow.h @@ -152,7 +152,6 @@ public: LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE, LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT, bool* aAllowRetaining = nullptr); - gfxASurface *GetThebesSurface(); NS_IMETHOD OnDefaultButtonLoaded(const nsIntRect &aButtonRect); NS_IMETHOD OverrideSystemMouseScrollSpeed(double aOriginalDeltaX, double aOriginalDeltaY, diff --git a/widget/xpwidgets/PuppetWidget.cpp b/widget/xpwidgets/PuppetWidget.cpp index be1f1cce0bcb..563daac687bc 100644 --- a/widget/xpwidgets/PuppetWidget.cpp +++ b/widget/xpwidgets/PuppetWidget.cpp @@ -22,6 +22,7 @@ #include "PuppetWidget.h" #include "nsIWidgetListener.h" +using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::hal; using namespace mozilla::gfx; @@ -108,9 +109,8 @@ PuppetWidget::Create(nsIWidget *aParent, mEnabled = true; mVisible = true; - mSurface = gfxPlatform::GetPlatform() - ->CreateOffscreenSurface(IntSize(1, 1), - gfxASurface::ContentFromFormat(gfxImageFormat::ARGB32)); + mDrawTarget = gfxPlatform::GetPlatform()-> + CreateOffscreenContentDrawTarget(IntSize(1, 1), SurfaceFormat::B8G8R8A8); mIMEComposing = false; mNeedIMEStateInit = MightNeedIMEFocus(aInitData); @@ -390,12 +390,6 @@ PuppetWidget::GetLayerManager(PLayerTransactionChild* aShadowManager, return mLayerManager; } -gfxASurface* -PuppetWidget::GetThebesSurface() -{ - return mSurface; -} - nsresult PuppetWidget::IMEEndComposition(bool aCancel) { @@ -694,7 +688,7 @@ PuppetWidget::Paint() mTabChild->NotifyPainted(); } } else { - nsRefPtr ctx = new gfxContext(mSurface); + nsRefPtr ctx = new gfxContext(mDrawTarget); ctx->Rectangle(gfxRect(0,0,0,0)); ctx->Clip(); AutoLayerManagerSetup setupLayerManager(this, ctx, diff --git a/widget/xpwidgets/PuppetWidget.h b/widget/xpwidgets/PuppetWidget.h index 589022e88957..5a4c1bccf861 100644 --- a/widget/xpwidgets/PuppetWidget.h +++ b/widget/xpwidgets/PuppetWidget.h @@ -15,6 +15,8 @@ #ifndef mozilla_widget_PuppetWidget_h__ #define mozilla_widget_PuppetWidget_h__ +#include "mozilla/gfx/2D.h" +#include "mozilla/RefPtr.h" #include "nsBaseScreen.h" #include "nsBaseWidget.h" #include "nsIScreenManager.h" @@ -38,6 +40,7 @@ class AutoCacheNativeKeyCommands; class PuppetWidget : public nsBaseWidget, public nsSupportsWeakReference { typedef mozilla::dom::TabChild TabChild; + typedef mozilla::gfx::DrawTarget DrawTarget; typedef nsBaseWidget Base; // The width and height of the "widget" are clamped to this. @@ -158,7 +161,6 @@ public: LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE, LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT, bool* aAllowRetaining = nullptr); - virtual gfxASurface* GetThebesSurface(); NS_IMETHOD NotifyIME(const IMENotification& aIMENotification) MOZ_OVERRIDE; NS_IMETHOD_(void) SetInputContext(const InputContext& aContext, @@ -225,7 +227,7 @@ private: bool mVisible; // XXX/cjones: keeping this around until we teach LayerManager to do // retained-content-only transactions - nsRefPtr mSurface; + mozilla::RefPtr mDrawTarget; // IME nsIMEUpdatePreference mIMEPreferenceOfParent; bool mIMEComposing; diff --git a/widget/xpwidgets/nsBaseWidget.cpp b/widget/xpwidgets/nsBaseWidget.cpp index 1061d5b4a79e..ae43f30994fd 100644 --- a/widget/xpwidgets/nsBaseWidget.cpp +++ b/widget/xpwidgets/nsBaseWidget.cpp @@ -1010,19 +1010,6 @@ nsDeviceContext* nsBaseWidget::GetDeviceContext() return mContext; } -//------------------------------------------------------------------------- -// -// Get the thebes surface -// -//------------------------------------------------------------------------- -gfxASurface *nsBaseWidget::GetThebesSurface() -{ - // in theory we should get our parent's surface, - // clone it, and set a device offset before returning - return nullptr; -} - - //------------------------------------------------------------------------- // // Destroy the window diff --git a/widget/xpwidgets/nsBaseWidget.h b/widget/xpwidgets/nsBaseWidget.h index 21238b8072bd..d89e15bf0df7 100644 --- a/widget/xpwidgets/nsBaseWidget.h +++ b/widget/xpwidgets/nsBaseWidget.h @@ -144,7 +144,6 @@ public: virtual void EndRemoteDrawing() { }; virtual void CleanupRemoteDrawing() { }; virtual void UpdateThemeGeometries(const nsTArray& aThemeGeometries) {} - virtual gfxASurface* GetThebesSurface(); NS_IMETHOD SetModal(bool aModal); virtual uint32_t GetMaxTouchPoints() const; NS_IMETHOD SetWindowClass(const nsAString& xulWinType);