diff --git a/CLOBBER b/CLOBBER index 1b0ac3bd9e3..5c929901aae 100644 --- a/CLOBBER +++ b/CLOBBER @@ -22,4 +22,4 @@ # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Bug 1101553 - remove nsPIPlacesHistoryListenersNotifier +Bug 1118618's backout needed a clobber diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index 14f6ac7ce3e..8f37f95a3db 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -1430,7 +1430,9 @@ pref("devtools.timeline.hiddenMarkers", "[]"); pref("devtools.performance.ui.show-timeline-memory", false); // The default Profiler UI settings +pref("devtools.profiler.ui.flatten-tree-recursion", true); pref("devtools.profiler.ui.show-platform-data", false); +pref("devtools.profiler.ui.show-idle-blocks", true); // The default cache UI setting pref("devtools.cache.disabled", false); @@ -1675,7 +1677,6 @@ pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src 'self' data: #endif pref("loop.oauth.google.redirect_uri", "urn:ietf:wg:oauth:2.0:oob:auto"); pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds"); -pref("loop.rooms.enabled", true); pref("loop.fxa_oauth.tokendata", ""); pref("loop.fxa_oauth.profile", ""); pref("loop.support_url", "https://support.mozilla.org/kb/group-conversations-firefox-hello-webrtc"); diff --git a/browser/base/content/aboutDialog.js b/browser/base/content/aboutDialog.js index cbc1fcac518..e2d40db15b4 100644 --- a/browser/base/content/aboutDialog.js +++ b/browser/base/content/aboutDialog.js @@ -490,7 +490,7 @@ appUpdater.prototype = return; } - this.selectPanel("apply"); + this.selectPanel("applyBillboard"); }, /** diff --git a/browser/base/content/browser-ctrlTab.js b/browser/base/content/browser-ctrlTab.js index 8191cb6873f..c2b6a315604 100644 --- a/browser/base/content/browser-ctrlTab.js +++ b/browser/base/content/browser-ctrlTab.js @@ -399,9 +399,9 @@ var ctrlTab = { suspendGUI: function ctrlTab_suspendGUI() { document.removeEventListener("keyup", this, true); - Array.forEach(this.previews, function (preview) { + for (let preview of this.previews) { this.updatePreview(preview, null); - }, this); + } }, onKeyPress: function ctrlTab_onKeyPress(event) { diff --git a/browser/base/content/test/general/browser.ini b/browser/base/content/test/general/browser.ini index f453b09d9aa..856b56ada78 100644 --- a/browser/base/content/test/general/browser.ini +++ b/browser/base/content/test/general/browser.ini @@ -303,6 +303,8 @@ run-if = datareporting skip-if = buildapp == 'mulet' || (os == "linux" && debug) || e10s # linux: bug 976544; e10s: bug 1071623 [browser_devices_get_user_media_about_urls.js] skip-if = e10s # Bug 1071623 +[browser_devices_get_user_media_in_frame.js] +skip-if = e10s # Bug 1071623 [browser_discovery.js] [browser_double_close_tab.js] skip-if = e10s diff --git a/browser/base/content/test/general/browser_devices_get_user_media.js b/browser/base/content/test/general/browser_devices_get_user_media.js index 6473144cadf..19befedb250 100644 --- a/browser/base/content/test/general/browser_devices_get_user_media.js +++ b/browser/base/content/test/general/browser_devices_get_user_media.js @@ -459,6 +459,50 @@ let gTests = [ } }, +{ + desc: "getUserMedia audio+video: reloading the page removes all gUM UI", + run: function checkReloading() { + yield promisePopupNotificationShown("webRTC-shareDevices", () => { + info("requesting devices"); + content.wrappedJSObject.requestDevice(true, true); + }); + expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, true); + + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + expectObserverCalled("getUserMedia:response:allow"); + expectObserverCalled("recording-device-events"); + is(getMediaCaptureState(), "CameraAndMicrophone", + "expected camera and microphone to be shared"); + + yield checkSharingUI({video: true, audio: true}); + + yield promiseNotificationShown(PopupNotifications.getNotification("webRTC-sharingDevices")); + + info("reloading the web page"); + let deferred = Promise.defer(); + let browser = gBrowser.selectedBrowser; + browser.addEventListener("load", function onload() { + browser.removeEventListener("load", onload, true); + deferred.resolve(); + }, true); + content.location.reload(); + yield deferred.promise; + + yield promiseNoPopupNotification("webRTC-sharingDevices"); + if (gObservedTopics["recording-device-events"] == 2) { + todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719"); + --gObservedTopics["recording-device-events"]; + } + expectObserverCalled("recording-device-events"); + expectObserverCalled("recording-window-ended"); + expectNoObserverCalled(); + yield checkNotSharing(); + } +}, + { desc: "getUserMedia prompt: Always/Never Share", run: function checkRememberCheckbox() { diff --git a/browser/base/content/test/general/browser_devices_get_user_media_in_frame.js b/browser/base/content/test/general/browser_devices_get_user_media_in_frame.js new file mode 100644 index 00000000000..72582d6451b --- /dev/null +++ b/browser/base/content/test/general/browser_devices_get_user_media_in_frame.js @@ -0,0 +1,477 @@ +/* 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/. */ + +const kObservedTopics = [ + "getUserMedia:response:allow", + "getUserMedia:revoke", + "getUserMedia:response:deny", + "getUserMedia:request", + "recording-device-events", + "recording-window-ended" +]; + +const PREF_PERMISSION_FAKE = "media.navigator.permission.fake"; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService", + "@mozilla.org/mediaManagerService;1", + "nsIMediaManagerService"); + +var gObservedTopics = {}; +function observer(aSubject, aTopic, aData) { + if (!(aTopic in gObservedTopics)) + gObservedTopics[aTopic] = 1; + else + ++gObservedTopics[aTopic]; +} + +function promiseObserverCalled(aTopic, aAction) { + let deferred = Promise.defer(); + + Services.obs.addObserver(function observer() { + ok(true, "got " + aTopic + " notification"); + Services.obs.removeObserver(observer, aTopic); + + if (kObservedTopics.indexOf(aTopic) != -1) { + if (!(aTopic in gObservedTopics)) + gObservedTopics[aTopic] = -1; + else + --gObservedTopics[aTopic]; + } + + deferred.resolve(); + }, aTopic, false); + + if (aAction) + aAction(); + + return deferred.promise; +} + +function expectObserverCalled(aTopic) { + is(gObservedTopics[aTopic], 1, "expected notification " + aTopic); + if (aTopic in gObservedTopics) + --gObservedTopics[aTopic]; +} + +function expectNoObserverCalled() { + for (let topic in gObservedTopics) { + if (gObservedTopics[topic]) + is(gObservedTopics[topic], 0, topic + " notification unexpected"); + } + gObservedTopics = {}; +} + +function promiseMessage(aMessage, aAction) { + let deferred = Promise.defer(); + + content.addEventListener("message", function messageListener(event) { + content.removeEventListener("message", messageListener); + is(event.data, aMessage, "received " + aMessage); + if (event.data == aMessage) + deferred.resolve(); + else + deferred.reject(); + }); + + if (aAction) + aAction(); + + return deferred.promise; +} + +function promisePopupNotificationShown(aName, aAction) { + let deferred = Promise.defer(); + + PopupNotifications.panel.addEventListener("popupshown", function popupNotifShown() { + PopupNotifications.panel.removeEventListener("popupshown", popupNotifShown); + + ok(!!PopupNotifications.getNotification(aName), aName + " notification shown"); + ok(PopupNotifications.isPanelOpen, "notification panel open"); + ok(!!PopupNotifications.panel.firstChild, "notification panel populated"); + + deferred.resolve(); + }); + + if (aAction) + aAction(); + + return deferred.promise; +} + +function promisePopupNotification(aName) { + let deferred = Promise.defer(); + + waitForCondition(() => PopupNotifications.getNotification(aName), + () => { + ok(!!PopupNotifications.getNotification(aName), + aName + " notification appeared"); + + deferred.resolve(); + }, "timeout waiting for popup notification " + aName); + + return deferred.promise; +} + +function promiseNoPopupNotification(aName) { + let deferred = Promise.defer(); + + waitForCondition(() => !PopupNotifications.getNotification(aName), + () => { + ok(!PopupNotifications.getNotification(aName), + aName + " notification removed"); + deferred.resolve(); + }, "timeout waiting for popup notification " + aName + " to disappear"); + + return deferred.promise; +} + +const kActionAlways = 1; +const kActionDeny = 2; +const kActionNever = 3; + +function activateSecondaryAction(aAction) { + let notification = PopupNotifications.panel.firstChild; + notification.button.focus(); + let popup = notification.menupopup; + popup.addEventListener("popupshown", function () { + popup.removeEventListener("popupshown", arguments.callee, false); + + // Press 'down' as many time as needed to select the requested action. + while (aAction--) + EventUtils.synthesizeKey("VK_DOWN", {}); + + // Activate + EventUtils.synthesizeKey("VK_RETURN", {}); + }, false); + + // One down event to open the popup + EventUtils.synthesizeKey("VK_DOWN", + { altKey: !navigator.platform.contains("Mac") }); +} + +registerCleanupFunction(function() { + gBrowser.removeCurrentTab(); + kObservedTopics.forEach(topic => { + Services.obs.removeObserver(observer, topic); + }); + Services.prefs.clearUserPref(PREF_PERMISSION_FAKE); +}); + +function getMediaCaptureState() { + let hasVideo = {}; + let hasAudio = {}; + MediaManagerService.mediaCaptureWindowState(content, hasVideo, hasAudio); + if (hasVideo.value && hasAudio.value) + return "CameraAndMicrophone"; + if (hasVideo.value) + return "Camera"; + if (hasAudio.value) + return "Microphone"; + return "none"; +} + +function* closeStream(aGlobal, aAlreadyClosed) { + expectNoObserverCalled(); + + info("closing the stream"); + aGlobal.closeStream(); + + if (!aAlreadyClosed) + yield promiseObserverCalled("recording-device-events"); + + yield promiseNoPopupNotification("webRTC-sharingDevices"); + if (!aAlreadyClosed) + expectObserverCalled("recording-window-ended"); + + yield* assertWebRTCIndicatorStatus(null); +} + +function checkDeviceSelectors(aAudio, aVideo) { + let micSelector = document.getElementById("webRTC-selectMicrophone"); + if (aAudio) + ok(!micSelector.hidden, "microphone selector visible"); + else + ok(micSelector.hidden, "microphone selector hidden"); + + let cameraSelector = document.getElementById("webRTC-selectCamera"); + if (aVideo) + ok(!cameraSelector.hidden, "camera selector visible"); + else + ok(cameraSelector.hidden, "camera selector hidden"); +} + +function* checkSharingUI(aExpected) { + yield promisePopupNotification("webRTC-sharingDevices"); + + yield* assertWebRTCIndicatorStatus(aExpected); +} + +function* checkNotSharing() { + is(getMediaCaptureState(), "none", "expected nothing to be shared"); + + ok(!PopupNotifications.getNotification("webRTC-sharingDevices"), + "no webRTC-sharingDevices popup notification"); + + yield* assertWebRTCIndicatorStatus(null); +} + +function getFrameGlobal(aFrameId) { + return content.wrappedJSObject.document.getElementById(aFrameId).contentWindow; +} + +const permissionError = "error: PermissionDeniedError: The user did not grant permission for the operation."; + +let gTests = [ + +{ + desc: "getUserMedia audio+video", + run: function checkAudioVideo() { + let global = getFrameGlobal("frame1"); + yield promisePopupNotificationShown("webRTC-shareDevices", () => { + info("requesting devices"); + global.requestDevice(true, true); + }); + expectObserverCalled("getUserMedia:request"); + + is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID, + "webRTC-shareDevices-notification-icon", "anchored to device icon"); + checkDeviceSelectors(true, true); + is(PopupNotifications.panel.firstChild.getAttribute("popupid"), + "webRTC-shareDevices", "panel using devices icon"); + + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + expectObserverCalled("getUserMedia:response:allow"); + expectObserverCalled("recording-device-events"); + is(getMediaCaptureState(), "CameraAndMicrophone", + "expected camera and microphone to be shared"); + + yield checkSharingUI({audio: true, video: true}); + yield closeStream(global); + } +}, + +{ + desc: "getUserMedia audio+video: stop sharing", + run: function checkStopSharing() { + let global = getFrameGlobal("frame1"); + yield promisePopupNotificationShown("webRTC-shareDevices", () => { + info("requesting devices"); + global.requestDevice(true, true); + }); + expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, true); + + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + expectObserverCalled("getUserMedia:response:allow"); + expectObserverCalled("recording-device-events"); + is(getMediaCaptureState(), "CameraAndMicrophone", + "expected camera and microphone to be shared"); + + yield checkSharingUI({video: true, audio: true}); + + yield promiseNotificationShown(PopupNotifications.getNotification("webRTC-sharingDevices")); + activateSecondaryAction(kActionDeny); + + yield promiseObserverCalled("recording-device-events"); + expectObserverCalled("getUserMedia:revoke"); + + yield promiseNoPopupNotification("webRTC-sharingDevices"); + expectObserverCalled("recording-window-ended"); + + if (gObservedTopics["recording-device-events"] == 1) { + todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719"); + gObservedTopics["recording-device-events"] = 0; + } + + expectNoObserverCalled(); + yield checkNotSharing(); + + // the stream is already closed, but this will do some cleanup anyway + yield closeStream(global, true); + } +}, + +{ + desc: "getUserMedia audio+video: reloading the frame removes all sharing UI", + run: function checkReloading() { + let global = getFrameGlobal("frame1"); + yield promisePopupNotificationShown("webRTC-shareDevices", () => { + info("requesting devices"); + global.requestDevice(true, true); + }); + expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, true); + + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + expectObserverCalled("getUserMedia:response:allow"); + expectObserverCalled("recording-device-events"); + is(getMediaCaptureState(), "CameraAndMicrophone", + "expected camera and microphone to be shared"); + + yield checkSharingUI({video: true, audio: true}); + + info("reloading the frame"); + let deferred = Promise.defer(); + let browser = gBrowser.selectedBrowser; + browser.addEventListener("load", function onload() { + browser.removeEventListener("load", onload, true); + deferred.resolve(); + }, true); + global.location.reload(); + yield deferred.promise; + + yield promiseNoPopupNotification("webRTC-sharingDevices"); + if (gObservedTopics["recording-device-events"] == 2) { + todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719"); + --gObservedTopics["recording-device-events"]; + } + expectObserverCalled("recording-device-events"); + expectObserverCalled("recording-window-ended"); + expectNoObserverCalled(); + yield checkNotSharing(); + } +}, + +{ + desc: "getUserMedia audio+video: reloading the frame removes prompts", + run: function checkReloadingRemovesPrompts() { + let global = getFrameGlobal("frame1"); + yield promisePopupNotificationShown("webRTC-shareDevices", () => { + info("requesting devices"); + global.requestDevice(true, true); + }); + expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, true); + + info("reloading the frame"); + let deferred = Promise.defer(); + let browser = gBrowser.selectedBrowser; + browser.addEventListener("load", function onload() { + browser.removeEventListener("load", onload, true); + deferred.resolve(); + }, true); + global.location.reload(); + yield deferred.promise; + + yield promiseNoPopupNotification("webRTC-shareDevices"); + + expectObserverCalled("recording-window-ended"); + expectNoObserverCalled(); + yield checkNotSharing(); + } +}, + +{ + desc: "getUserMedia audio+video: reloading a frame updates the sharing UI", + run: function checkUpdateWhenReloading() { + // We'll share only the mic in the first frame, then share both in the + // second frame, then reload the second frame. After each step, we'll check + // the UI is in the correct state. + let g1 = getFrameGlobal("frame1"), g2 = getFrameGlobal("frame2"); + + yield promisePopupNotificationShown("webRTC-shareDevices", () => { + info("requesting microphone in the first frame"); + g1.requestDevice(true, false); + }); + expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, false); + + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + expectObserverCalled("getUserMedia:response:allow"); + expectObserverCalled("recording-device-events"); + is(getMediaCaptureState(), "Microphone", "microphone to be shared"); + + yield checkSharingUI({video: false, audio: true}); + expectNoObserverCalled(); + + yield promisePopupNotificationShown("webRTC-shareDevices", () => { + info("requesting both devices in the second frame"); + g2.requestDevice(true, true); + }); + expectObserverCalled("getUserMedia:request"); + checkDeviceSelectors(true, true); + + yield promiseMessage("ok", () => { + PopupNotifications.panel.firstChild.button.click(); + }); + expectObserverCalled("getUserMedia:response:allow"); + expectObserverCalled("recording-device-events"); + is(getMediaCaptureState(), "CameraAndMicrophone", + "expected camera and microphone to be shared"); + + yield checkSharingUI({video: true, audio: true}); + expectNoObserverCalled(); + + info("reloading the second frame"); + let deferred = Promise.defer(); + let browser = gBrowser.selectedBrowser; + browser.addEventListener("load", function onload() { + browser.removeEventListener("load", onload, true); + deferred.resolve(); + }, true); + g2.location.reload(); + yield deferred.promise; + + yield checkSharingUI({video: false, audio: true}); + expectObserverCalled("recording-window-ended"); + if (gObservedTopics["recording-device-events"] == 2) { + todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719"); + --gObservedTopics["recording-device-events"]; + } + expectObserverCalled("recording-device-events"); + expectNoObserverCalled(); + + yield closeStream(g1); + yield promiseNoPopupNotification("webRTC-sharingDevices"); + expectNoObserverCalled(); + yield checkNotSharing(); + } +} + +]; + +function test() { + waitForExplicitFinish(); + + let tab = gBrowser.addTab(); + gBrowser.selectedTab = tab; + tab.linkedBrowser.addEventListener("load", function onload() { + tab.linkedBrowser.removeEventListener("load", onload, true); + + kObservedTopics.forEach(topic => { + Services.obs.addObserver(observer, topic, false); + }); + Services.prefs.setBoolPref(PREF_PERMISSION_FAKE, true); + + is(PopupNotifications._currentNotifications.length, 0, + "should start the test without any prior popup notification"); + + Task.spawn(function () { + for (let test of gTests) { + info(test.desc); + yield test.run(); + + // Cleanup before the next test + expectNoObserverCalled(); + } + }).then(finish, ex => { + ok(false, "Unexpected Exception: " + ex); + finish(); + }); + }, true); + let rootDir = getRootDirectory(gTestPath); + rootDir = rootDir.replace("chrome://mochitests/content/", + "https://example.com/"); + let url = rootDir + "get_user_media.html"; + content.location = 'data:text/html,' +} diff --git a/browser/base/content/test/general/pinning_reports.sjs b/browser/base/content/test/general/pinning_reports.sjs index 16cf98d08f0..fde7d16ae4d 100644 --- a/browser/base/content/test/general/pinning_reports.sjs +++ b/browser/base/content/test/general/pinning_reports.sjs @@ -3,6 +3,8 @@ const EXPECTED_CHAIN = [ "MIIC2jCCAcKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDExtBbHRlcm5hdGUgVHJ1c3RlZCBBdXRob3JpdHkwHhcNMTQwOTI1MjEyMTU0WhcNMjQwOTI1MjEyMTU0WjAmMSQwIgYDVQQDExtBbHRlcm5hdGUgVHJ1c3RlZCBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBT+BwAhO52IWgSIdZZifU9LHOs3IR/+8DCC0WP5d/OuyKlZ6Rqd0tsd3i7durhQyjHSbLf2lJStcnFjcVEbEnNI76RuvlN8xLLn5eV+2Ayr4cZYKztudwRmw+DV/iYAiMSy0hs7m3ssfX7qpoi1aNRjUanwU0VTCPQhF1bEKAC2du+C5Z8e92zN5t87w7bYr7lt+m8197XliXEu+0s9RgnGwGaZ296BIRz6NOoJYTa43n06LU1I1+Z4d6lPdzUFrSR0GBaMhUSurUBtOin3yWiMhg1VHX/KwqGc4als5GyCVXy8HGrA/0zQPOhetxrlhEVAdK/xBt7CZvByj1Rcc7AgMBAAGjEzARMA8GA1UdEwQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggEBAJq/hogSRqzPWTwX4wTn/DVSNdWwFLv53qep9YrSMJ8ZsfbfK9Es4VP4dBLRQAVMJ0Z5mW1I6d/n0KayTanuUBvemYdxPi/qQNSs8UJcllqdhqWzmzAg6a0LxrMnEeKzPBPD6q8PwQ7tYP+B4sBN9tnnsnyPgti9ZiNZn5FwXZliHXseQ7FE9/SqHlLw5LXW3YtKjuti6RmuV6fq3j+D4oeC5vb1mKgIyoTqGN6ze57v8RHi+pQ8Q+kmoUn/L3Z2YmFe4SKN/4WoyXr8TdejpThGOCGCAd3565s5gOx5QfSQX11P8NZKO8hcN0tme3VzmGpHK0Z/6MTmdpNaTwQ6odk=" ]; +const MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE = -16384; + function parseReport(request) { // read the report from the request let inputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream); @@ -38,6 +40,12 @@ function handleRequest(request, response) { } } + if (report.errorCode !== MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE) { + response.setStatusLine("1.1", 500, "Server error"); + response.write("The report contained an unexpected error code"); + return; + } + // if all is as expected, send the 201 the client expects response.setStatusLine("1.1", 201, "Created"); response.write("OK"); diff --git a/browser/base/content/urlbarBindings.xml b/browser/base/content/urlbarBindings.xml index 4dcba14d572..0e9caeafc1c 100644 --- a/browser/base/content/urlbarBindings.xml +++ b/browser/base/content/urlbarBindings.xml @@ -334,14 +334,16 @@ Cu.reportError(ex); } - function loadCurrent() { + let loadCurrent = () => { openUILinkIn(url, "current", { allowThirdPartyFixup: true, disallowInheritPrincipal: !mayInheritPrincipal, allowPinnedTabHostChange: true, postData: postData }); - } + // Ensure the start of the URL is visible for UX reasons: + this.selectionStart = this.selectionEnd = 0; + }; // Focus the content area before triggering loads, since if the load // occurs in a new tab, we want focus to be restored to the content @@ -1149,7 +1151,8 @@ let header = document.getAnonymousElementByAttribute(this, "anonid", "search-panel-one-offs-header") - header.collapsed = list.collapsed = !engines.length; + // header is a xul:deck so collapsed doesn't work on it, see bug 589569. + header.hidden = list.collapsed = !engines.length; // 49px is the min-width of each search engine button, // adapt this const when changing the css. diff --git a/browser/components/downloads/test/browser/browser.ini b/browser/components/downloads/test/browser/browser.ini index c7f1fd16005..d9cb4e58c2d 100644 --- a/browser/components/downloads/test/browser/browser.ini +++ b/browser/components/downloads/test/browser/browser.ini @@ -8,3 +8,7 @@ skip-if = os == "linux" # Bug 949434 [browser_overflow_anchor.js] skip-if = os == "linux" # Bug 952422 [browser_confirm_unblock_download.js] + +[browser_iframe_gone_mid_download.js] +skip-if = e10s + diff --git a/browser/components/downloads/test/browser/browser_iframe_gone_mid_download.js b/browser/components/downloads/test/browser/browser_iframe_gone_mid_download.js new file mode 100644 index 00000000000..ebdd4f9af84 --- /dev/null +++ b/browser/components/downloads/test/browser/browser_iframe_gone_mid_download.js @@ -0,0 +1,62 @@ +const SAVE_PER_SITE_PREF = "browser.download.lastDir.savePerSite"; + +function test_deleted_iframe(perSitePref, windowOptions={}) { + return function*() { + Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, perSitePref); + let {DownloadLastDir} = Cu.import("resource://gre/modules/DownloadLastDir.jsm", {}); + + let win = yield promiseOpenAndLoadWindow(windowOptions); + let tab = win.gBrowser.addTab(); + yield promiseTabLoadEvent(tab, "about:mozilla"); + + let doc = tab.linkedBrowser.contentDocument; + let iframe = doc.createElement("iframe"); + doc.body.appendChild(iframe); + + ok(iframe.contentWindow, "iframe should have a window"); + let gDownloadLastDir = new DownloadLastDir(iframe.contentWindow); + let cw = iframe.contentWindow; + let promiseIframeWindowGone = new Promise((resolve, reject) => { + Services.obs.addObserver(function obs(subject, topic) { + if (subject == cw) { + Services.obs.removeObserver(obs, topic); + resolve(); + } + }, "dom-window-destroyed", false); + }); + iframe.remove(); + yield promiseIframeWindowGone; + cw = null; + ok(!iframe.contentWindow, "Managed to destroy iframe"); + + let someDir = "blah"; + try { + someDir = yield new Promise((resolve, reject) => { + gDownloadLastDir.getFileAsync("http://www.mozilla.org/", function(dir) { + resolve(dir); + }); + }); + } catch (ex) { + ok(false, "Got an exception trying to get the directory where things should be saved."); + Cu.reportError(ex); + } + // NB: someDir can legitimately be null here when set, hence the 'blah' workaround: + isnot(someDir, "blah", "Should get a file even after the window was destroyed."); + + try { + gDownloadLastDir.setFile("http://www.mozilla.org/", null); + } catch (ex) { + ok(false, "Got an exception trying to set the directory where things should be saved."); + Cu.reportError(ex); + } + + yield promiseWindowClosed(win); + Services.prefs.clearUserPref(SAVE_PER_SITE_PREF); + }; +} + +add_task(test_deleted_iframe(false)); +add_task(test_deleted_iframe(false)); +add_task(test_deleted_iframe(true, {private: true})); +add_task(test_deleted_iframe(true, {private: true})); + diff --git a/browser/components/downloads/test/browser/head.js b/browser/components/downloads/test/browser/head.js index 6566e693a02..7c4ffe5a342 100644 --- a/browser/components/downloads/test/browser/head.js +++ b/browser/components/downloads/test/browser/head.js @@ -31,6 +31,76 @@ registerCleanupFunction(function () { //////////////////////////////////////////////////////////////////////////////// //// Asynchronous support subroutines +function promiseOpenAndLoadWindow(aOptions) +{ + return new Promise((resolve, reject) => { + let win = OpenBrowserWindow(aOptions); + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad); + resolve(win); + }); + }); +} + +/** + * Waits for a load (or custom) event to finish in a given tab. If provided + * load an uri into the tab. + * + * @param tab + * The tab to load into. + * @param [optional] url + * The url to load, or the current url. + * @param [optional] event + * The load event type to wait for. Defaults to "load". + * @return {Promise} resolved when the event is handled. + * @resolves to the received event + * @rejects if a valid load event is not received within a meaningful interval + */ +function promiseTabLoadEvent(tab, url, eventType="load") +{ + let deferred = Promise.defer(); + info("Wait tab event: " + eventType); + + function handle(event) { + if (event.originalTarget != tab.linkedBrowser.contentDocument || + event.target.location.href == "about:blank" || + (url && event.target.location.href != url)) { + info("Skipping spurious '" + eventType + "'' event" + + " for " + event.target.location.href); + return; + } + // Remove reference to tab from the cleanup function: + realCleanup = () => {}; + tab.linkedBrowser.removeEventListener(eventType, handle, true); + info("Tab event received: " + eventType); + deferred.resolve(event); + } + + // Juggle a bit to avoid leaks: + let realCleanup = () => tab.linkedBrowser.removeEventListener(eventType, handle, true); + registerCleanupFunction(() => realCleanup()); + + tab.linkedBrowser.addEventListener(eventType, handle, true, true); + if (url) + tab.linkedBrowser.loadURI(url); + return deferred.promise; +} + +function promiseWindowClosed(win) +{ + let promise = new Promise((resolve, reject) => { + Services.obs.addObserver(function obs(subject, topic) { + if (subject == win) { + Services.obs.removeObserver(obs, topic); + resolve(); + } + }, "domwindowclosed", false); + }); + win.close(); + return promise; +} + + function promiseFocus() { let deferred = Promise.defer(); diff --git a/browser/components/loop/MozLoopAPI.jsm b/browser/components/loop/MozLoopAPI.jsm index 3458425e3db..0bea0dc8acc 100644 --- a/browser/components/loop/MozLoopAPI.jsm +++ b/browser/components/loop/MozLoopAPI.jsm @@ -416,26 +416,6 @@ function injectLoopAPI(targetWindow) { } }, - /** - * Used to note a call url expiry time. If the time is later than the current - * latest expiry time, then the stored expiry time is increased. For times - * sooner, this function is a no-op; this ensures we always have the latest - * expiry time for a url. - * - * This is used to determine whether or not we should be registering with the - * push server on start. - * - * @param {Integer} expiryTimeSeconds The seconds since epoch of the expiry time - * of the url. - */ - noteCallUrlExpiry: { - enumerable: true, - writable: true, - value: function(expiryTimeSeconds) { - MozLoopService.noteCallUrlExpiry(expiryTimeSeconds); - } - }, - /** * Set any preference under "loop." * diff --git a/browser/components/loop/MozLoopService.jsm b/browser/components/loop/MozLoopService.jsm index 2fda6510fde..05ea05a6ca2 100644 --- a/browser/components/loop/MozLoopService.jsm +++ b/browser/components/loop/MozLoopService.jsm @@ -1243,22 +1243,6 @@ this.MozLoopService = { return MozLoopServiceInternal.promiseRegisteredWithServers(sessionType); }, - /** - * Used to note a call url expiry time. If the time is later than the current - * latest expiry time, then the stored expiry time is increased. For times - * sooner, this function is a no-op; this ensures we always have the latest - * expiry time for a url. - * - * This is used to determine whether or not we should be registering with the - * push server on start. - * - * @param {Integer} expiryTimeSeconds The seconds since epoch of the expiry time - * of the url. - */ - noteCallUrlExpiry: function(expiryTimeSeconds) { - MozLoopServiceInternal.expiryTimeSeconds = expiryTimeSeconds; - }, - /** * Returns the strings for the specified element. Designed for use with l10n.js. * diff --git a/browser/components/loop/content/js/client.js b/browser/components/loop/content/js/client.js index 451e435e07f..9b6a0b6e42b 100644 --- a/browser/components/loop/content/js/client.js +++ b/browser/components/loop/content/js/client.js @@ -9,12 +9,6 @@ var loop = loop || {}; loop.Client = (function($) { "use strict"; - // The expected properties to be returned from the POST /call-url/ request. - var expectedCallUrlProperties = ["callUrl", "expiresAt"]; - - // The expected properties to be returned from the GET /calls request. - var expectedCallProperties = ["calls"]; - // THe expected properties to be returned from the POST /calls request. var expectedPostCallProperties = [ "apiKey", "callId", "progressURL", @@ -81,56 +75,6 @@ loop.Client = (function($) { cb(error); }, - /** - * Requests a call URL from the Loop server. It will note the - * expiry time for the url with the mozLoop api. It will select the - * appropriate hawk session to use based on whether or not the user - * is currently logged into a Firefox account profile. - * - * Callback parameters: - * - err null on successful request, non-null otherwise. - * - callUrlData an object of the obtained call url data if successful: - * -- callUrl: The url of the call - * -- expiresAt: The amount of hours until expiry of the url - * - * @param {String} simplepushUrl a registered Simple Push URL - * @param {string} nickname the nickname of the future caller - * @param {Function} cb Callback(err, callUrlData) - */ - requestCallUrl: function(nickname, cb) { - var sessionType; - if (this.mozLoop.userProfile) { - sessionType = this.mozLoop.LOOP_SESSION_TYPE.FXA; - } else { - sessionType = this.mozLoop.LOOP_SESSION_TYPE.GUEST; - } - - this.mozLoop.hawkRequest(sessionType, "/call-url/", "POST", - {callerId: nickname}, - function (error, responseText) { - if (error) { - this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", false); - this._failureHandler(cb, error); - return; - } - - try { - var urlData = JSON.parse(responseText); - - // This throws if the data is invalid, in which case only the failure - // telemetry will be recorded. - var returnData = this._validate(urlData, expectedCallUrlProperties); - - this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", true); - cb(null, returnData); - } catch (err) { - this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", false); - console.log("Error requesting call info", err); - cb(err); - } - }.bind(this)); - }, - /** * Block call URL based on the token identifier * @@ -203,20 +147,6 @@ loop.Client = (function($) { }.bind(this) ); }, - - /** - * Adds a value to a telemetry histogram, ignoring errors. - * - * @param {string} histogramId Name of the telemetry histogram to update. - * @param {integer} value Value to add to the histogram. - */ - _telemetryAdd: function(histogramId, value) { - try { - this.mozLoop.telemetryAdd(histogramId, value); - } catch (err) { - console.error("Error recording telemetry", err); - } - }, }; return Client; diff --git a/browser/components/loop/content/js/contacts.js b/browser/components/loop/content/js/contacts.js index ed8281e6489..bdfca3103a8 100644 --- a/browser/components/loop/content/js/contacts.js +++ b/browser/components/loop/content/js/contacts.js @@ -263,6 +263,11 @@ loop.contacts = (function(_, mozL10n) { loop.shared.mixins.WindowCloseMixin ], + propTypes: { + notifications: React.PropTypes.instanceOf( + loop.shared.models.NotificationCollection).isRequired + }, + /** * Contacts collection object */ @@ -389,10 +394,14 @@ loop.contacts = (function(_, mozL10n) { service: "google" }, (err, stats) => { this.setState({ importBusy: false }); - // TODO: bug 1076764 - proper error and success reporting. if (err) { - throw err; + console.error("Contact import error", err); + this.props.notifications.errorL10n("import_contacts_failure_message"); + return; } + this.props.notifications.successL10n("import_contacts_success_message", { + total: stats.total + }); }); }, diff --git a/browser/components/loop/content/js/contacts.jsx b/browser/components/loop/content/js/contacts.jsx index af80a7b48ac..af20771bfec 100644 --- a/browser/components/loop/content/js/contacts.jsx +++ b/browser/components/loop/content/js/contacts.jsx @@ -263,6 +263,11 @@ loop.contacts = (function(_, mozL10n) { loop.shared.mixins.WindowCloseMixin ], + propTypes: { + notifications: React.PropTypes.instanceOf( + loop.shared.models.NotificationCollection).isRequired + }, + /** * Contacts collection object */ @@ -389,10 +394,14 @@ loop.contacts = (function(_, mozL10n) { service: "google" }, (err, stats) => { this.setState({ importBusy: false }); - // TODO: bug 1076764 - proper error and success reporting. if (err) { - throw err; + console.error("Contact import error", err); + this.props.notifications.errorL10n("import_contacts_failure_message"); + return; } + this.props.notifications.successL10n("import_contacts_success_message", { + total: stats.total + }); }); }, diff --git a/browser/components/loop/content/js/conversationViews.js b/browser/components/loop/content/js/conversationViews.js index 90758f51528..69f353fd1d6 100644 --- a/browser/components/loop/content/js/conversationViews.js +++ b/browser/components/loop/content/js/conversationViews.js @@ -835,6 +835,10 @@ loop.conversationViews = (function(mozL10n) { }); var OngoingConversationView = React.createClass({displayName: "OngoingConversationView", + mixins: [ + sharedMixins.MediaSetupMixin + ], + propTypes: { dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired, video: React.PropTypes.object, @@ -849,75 +853,18 @@ loop.conversationViews = (function(mozL10n) { }, componentDidMount: function() { - /** - * OT inserts inline styles into the markup. Using a listener for - * resize events helps us trigger a full width/height on the element - * so that they update to the correct dimensions. - * XXX: this should be factored as a mixin. - */ - window.addEventListener('orientationchange', this.updateVideoContainer); - window.addEventListener('resize', this.updateVideoContainer); - // The SDK needs to know about the configuration and the elements to use // for display. So the best way seems to pass the information here - ideally // the sdk wouldn't need to know this, but we can't change that. this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({ - publisherConfig: this._getPublisherConfig(), + publisherConfig: this.getDefaultPublisherConfig({ + publishVideo: this.props.video.enabled + }), getLocalElementFunc: this._getElement.bind(this, ".local"), getRemoteElementFunc: this._getElement.bind(this, ".remote") })); }, - componentWillUnmount: function() { - window.removeEventListener('orientationchange', this.updateVideoContainer); - window.removeEventListener('resize', this.updateVideoContainer); - }, - - /** - * Returns either the required DOMNode - * - * @param {String} className The name of the class to get the element for. - */ - _getElement: function(className) { - return this.getDOMNode().querySelector(className); - }, - - /** - * Returns the required configuration for publishing video on the sdk. - */ - _getPublisherConfig: function() { - // height set to 100%" to fix video layout on Google Chrome - // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445 - return { - insertMode: "append", - width: "100%", - height: "100%", - publishVideo: this.props.video.enabled, - style: { - audioLevelDisplayMode: "off", - bugDisplayMode: "off", - buttonDisplayMode: "off", - nameDisplayMode: "off", - videoDisabledDisplayMode: "off" - } - }; - }, - - /** - * Used to update the video container whenever the orientation or size of the - * display area changes. - */ - updateVideoContainer: function() { - var localStreamParent = this._getElement('.local .OT_publisher'); - var remoteStreamParent = this._getElement('.remote .OT_subscriber'); - if (localStreamParent) { - localStreamParent.style.width = "100%"; - } - if (remoteStreamParent) { - remoteStreamParent.style.height = "100%"; - } - }, - /** * Hangs up the call. */ diff --git a/browser/components/loop/content/js/conversationViews.jsx b/browser/components/loop/content/js/conversationViews.jsx index 063526a6053..aae50118991 100644 --- a/browser/components/loop/content/js/conversationViews.jsx +++ b/browser/components/loop/content/js/conversationViews.jsx @@ -835,6 +835,10 @@ loop.conversationViews = (function(mozL10n) { }); var OngoingConversationView = React.createClass({ + mixins: [ + sharedMixins.MediaSetupMixin + ], + propTypes: { dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired, video: React.PropTypes.object, @@ -849,75 +853,18 @@ loop.conversationViews = (function(mozL10n) { }, componentDidMount: function() { - /** - * OT inserts inline styles into the markup. Using a listener for - * resize events helps us trigger a full width/height on the element - * so that they update to the correct dimensions. - * XXX: this should be factored as a mixin. - */ - window.addEventListener('orientationchange', this.updateVideoContainer); - window.addEventListener('resize', this.updateVideoContainer); - // The SDK needs to know about the configuration and the elements to use // for display. So the best way seems to pass the information here - ideally // the sdk wouldn't need to know this, but we can't change that. this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({ - publisherConfig: this._getPublisherConfig(), + publisherConfig: this.getDefaultPublisherConfig({ + publishVideo: this.props.video.enabled + }), getLocalElementFunc: this._getElement.bind(this, ".local"), getRemoteElementFunc: this._getElement.bind(this, ".remote") })); }, - componentWillUnmount: function() { - window.removeEventListener('orientationchange', this.updateVideoContainer); - window.removeEventListener('resize', this.updateVideoContainer); - }, - - /** - * Returns either the required DOMNode - * - * @param {String} className The name of the class to get the element for. - */ - _getElement: function(className) { - return this.getDOMNode().querySelector(className); - }, - - /** - * Returns the required configuration for publishing video on the sdk. - */ - _getPublisherConfig: function() { - // height set to 100%" to fix video layout on Google Chrome - // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445 - return { - insertMode: "append", - width: "100%", - height: "100%", - publishVideo: this.props.video.enabled, - style: { - audioLevelDisplayMode: "off", - bugDisplayMode: "off", - buttonDisplayMode: "off", - nameDisplayMode: "off", - videoDisabledDisplayMode: "off" - } - }; - }, - - /** - * Used to update the video container whenever the orientation or size of the - * display area changes. - */ - updateVideoContainer: function() { - var localStreamParent = this._getElement('.local .OT_publisher'); - var remoteStreamParent = this._getElement('.remote .OT_subscriber'); - if (localStreamParent) { - localStreamParent.style.width = "100%"; - } - if (remoteStreamParent) { - remoteStreamParent.style.height = "100%"; - } - }, - /** * Hangs up the call. */ diff --git a/browser/components/loop/content/js/panel.js b/browser/components/loop/content/js/panel.js index ffb2c519b31..3f36b224de6 100644 --- a/browser/components/loop/content/js/panel.js +++ b/browser/components/loop/content/js/panel.js @@ -39,9 +39,7 @@ loop.panel = (function(_, mozL10n) { // When we don't need to rely on the pref, this can move back to // getDefaultProps (bug 1100258). return { - selectedTab: this.props.selectedTab || - (navigator.mozLoop.getLoopPref("rooms.enabled") ? - "rooms" : "call") + selectedTab: this.props.selectedTab || "rooms" }; }, @@ -358,157 +356,6 @@ loop.panel = (function(_, mozL10n) { } }); - /** - * Call url result view. - */ - var CallUrlResult = React.createClass({displayName: "CallUrlResult", - mixins: [sharedMixins.DocumentVisibilityMixin], - - propTypes: { - callUrl: React.PropTypes.string, - callUrlExpiry: React.PropTypes.number, - notifications: React.PropTypes.object.isRequired, - client: React.PropTypes.object.isRequired - }, - - getInitialState: function() { - return { - pending: false, - copied: false, - callUrl: this.props.callUrl || "", - callUrlExpiry: 0 - }; - }, - - /** - * Provided by DocumentVisibilityMixin. Schedules retrieval of a new call - * URL everytime the panel is reopened. - */ - onDocumentVisible: function() { - this._fetchCallUrl(); - }, - - componentDidMount: function() { - // If we've already got a callURL, don't bother requesting a new one. - // As of this writing, only used for visual testing in the UI showcase. - if (this.state.callUrl.length) { - return; - } - - this._fetchCallUrl(); - }, - - /** - * Fetches a call URL. - */ - _fetchCallUrl: function() { - this.setState({pending: true}); - // XXX This is an empty string as a conversation identifier. Bug 1015938 implements - // a user-set string. - this.props.client.requestCallUrl("", - this._onCallUrlReceived); - }, - - _onCallUrlReceived: function(err, callUrlData) { - if (err) { - if (err.code != 401) { - // 401 errors are already handled in hawkRequest and show an error - // message about the session. - this.props.notifications.errorL10n("unable_retrieve_url"); - } - this.setState(this.getInitialState()); - } else { - try { - var callUrl = new window.URL(callUrlData.callUrl); - // XXX the current server vers does not implement the callToken field - // but it exists in the API. This workaround should be removed in the future - var token = callUrlData.callToken || - callUrl.pathname.split('/').pop(); - - // Now that a new URL is available, indicate it has not been shared. - this.linkExfiltrated = false; - - this.setState({pending: false, copied: false, - callUrl: callUrl.href, - callUrlExpiry: callUrlData.expiresAt}); - } catch(e) { - console.log(e); - this.props.notifications.errorL10n("unable_retrieve_url"); - this.setState(this.getInitialState()); - } - } - }, - - handleEmailButtonClick: function(event) { - this.handleLinkExfiltration(event); - - sharedUtils.composeCallUrlEmail(this.state.callUrl); - }, - - handleCopyButtonClick: function(event) { - this.handleLinkExfiltration(event); - // XXX the mozLoop object should be passed as a prop, to ease testing and - // using a fake implementation in UI components showcase. - navigator.mozLoop.copyString(this.state.callUrl); - this.setState({copied: true}); - }, - - linkExfiltrated: false, - - handleLinkExfiltration: function(event) { - // Update the count of shared URLs only once per generated URL. - if (!this.linkExfiltrated) { - this.linkExfiltrated = true; - try { - navigator.mozLoop.telemetryAdd("LOOP_CLIENT_CALL_URL_SHARED", true); - } catch (err) { - console.error("Error recording telemetry", err); - } - } - - // Note URL expiration every time it is shared. - if (this.state.callUrlExpiry) { - navigator.mozLoop.noteCallUrlExpiry(this.state.callUrlExpiry); - } - }, - - render: function() { - // XXX setting elem value from a state (in the callUrl input) - // makes it immutable ie read only but that is fine in our case. - // readOnly attr will suppress a warning regarding this issue - // from the react lib. - var cx = React.addons.classSet; - return ( - React.createElement("div", {className: "generate-url"}, - React.createElement("header", {id: "share-link-header"}, mozL10n.get("share_link_header_text")), - React.createElement("div", {className: "generate-url-stack"}, - React.createElement("input", {type: "url", value: this.state.callUrl, readOnly: "true", - onCopy: this.handleLinkExfiltration, - className: cx({"generate-url-input": true, - pending: this.state.pending, - // Used in functional testing, signals that - // call url was received from loop server - callUrl: !this.state.pending})}), - React.createElement("div", {className: cx({"generate-url-spinner": true, - spinner: true, - busy: this.state.pending})}) - ), - React.createElement(ButtonGroup, {additionalClass: "url-actions"}, - React.createElement(Button, {additionalClass: "button-email", - disabled: !this.state.callUrl, - onClick: this.handleEmailButtonClick, - caption: mozL10n.get("share_button")}), - React.createElement(Button, {additionalClass: "button-copy", - disabled: !this.state.callUrl, - onClick: this.handleCopyButtonClick, - caption: this.state.copied ? mozL10n.get("copied_url_button") : - mozL10n.get("copy_url_button")}) - ) - ) - ); - } - }); - /** * FxA sign in/up link component. */ @@ -820,9 +667,7 @@ loop.panel = (function(_, mozL10n) { var PanelView = React.createClass({displayName: "PanelView", propTypes: { notifications: React.PropTypes.object.isRequired, - client: React.PropTypes.object.isRequired, // Mostly used for UI components showcase and unit tests - callUrl: React.PropTypes.string, userProfile: React.PropTypes.object, // Used only for unit tests. showTabButtons: React.PropTypes.bool, @@ -869,17 +714,13 @@ loop.panel = (function(_, mozL10n) { } }, - _roomsEnabled: function() { - return this.props.mozLoop.getLoopPref("rooms.enabled"); - }, - _onStatusChanged: function() { var profile = this.props.mozLoop.userProfile; var currUid = this.state.userProfile ? this.state.userProfile.uid : null; var newUid = profile ? profile.uid : null; if (currUid != newUid) { // On profile change (login, logout), switch back to the default tab. - this.selectTab(this._roomsEnabled() ? "rooms" : "call"); + this.selectTab("rooms"); this.setState({userProfile: profile}); } this.updateServiceErrors(); @@ -902,34 +743,6 @@ loop.panel = (function(_, mozL10n) { } }, - /** - * The rooms feature is hidden by default for now. Once it gets mainstream, - * this method can be simplified. - */ - _renderRoomsOrCallTab: function() { - if (!this._roomsEnabled()) { - return ( - React.createElement(Tab, {name: "call"}, - React.createElement("div", {className: "content-area"}, - React.createElement(CallUrlResult, {client: this.props.client, - notifications: this.props.notifications, - callUrl: this.props.callUrl}), - React.createElement(ToSView, null) - ) - ) - ); - } - - return ( - React.createElement(Tab, {name: "rooms"}, - React.createElement(RoomList, {dispatcher: this.props.dispatcher, - store: this.props.roomStore, - userDisplayName: this._getUserDisplayName()}), - React.createElement(ToSView, null) - ) - ); - }, - startForm: function(name, contact) { this.refs[name].initForm(contact); this.selectTab(name); @@ -986,10 +799,16 @@ loop.panel = (function(_, mozL10n) { clearOnDocumentHidden: true}), React.createElement(TabView, {ref: "tabView", selectedTab: this.props.selectedTab, buttonsHidden: hideButtons}, - this._renderRoomsOrCallTab(), + React.createElement(Tab, {name: "rooms"}, + React.createElement(RoomList, {dispatcher: this.props.dispatcher, + store: this.props.roomStore, + userDisplayName: this._getUserDisplayName()}), + React.createElement(ToSView, null) + ), React.createElement(Tab, {name: "contacts"}, React.createElement(ContactsList, {selectTab: this.selectTab, - startForm: this.startForm}) + startForm: this.startForm, + notifications: this.props.notifications}) ), React.createElement(Tab, {name: "contacts_add", hidden: true}, React.createElement(ContactDetailsForm, {ref: "contacts_add", mode: "add", @@ -1028,7 +847,6 @@ loop.panel = (function(_, mozL10n) { // else to ensure the L10n environment is setup correctly. mozL10n.initialize(navigator.mozLoop); - var client = new loop.Client(); var notifications = new sharedModels.NotificationCollection(); var dispatcher = new loop.Dispatcher(); var roomStore = new loop.store.RoomStore(dispatcher, { @@ -1037,7 +855,6 @@ loop.panel = (function(_, mozL10n) { }); React.render(React.createElement(PanelView, { - client: client, notifications: notifications, roomStore: roomStore, mozLoop: navigator.mozLoop, @@ -1056,7 +873,6 @@ loop.panel = (function(_, mozL10n) { init: init, AuthLink: AuthLink, AvailabilityDropdown: AvailabilityDropdown, - CallUrlResult: CallUrlResult, GettingStartedView: GettingStartedView, PanelView: PanelView, RoomEntry: RoomEntry, diff --git a/browser/components/loop/content/js/panel.jsx b/browser/components/loop/content/js/panel.jsx index 20afd56c0c6..736dcfc7702 100644 --- a/browser/components/loop/content/js/panel.jsx +++ b/browser/components/loop/content/js/panel.jsx @@ -39,9 +39,7 @@ loop.panel = (function(_, mozL10n) { // When we don't need to rely on the pref, this can move back to // getDefaultProps (bug 1100258). return { - selectedTab: this.props.selectedTab || - (navigator.mozLoop.getLoopPref("rooms.enabled") ? - "rooms" : "call") + selectedTab: this.props.selectedTab || "rooms" }; }, @@ -358,157 +356,6 @@ loop.panel = (function(_, mozL10n) { } }); - /** - * Call url result view. - */ - var CallUrlResult = React.createClass({ - mixins: [sharedMixins.DocumentVisibilityMixin], - - propTypes: { - callUrl: React.PropTypes.string, - callUrlExpiry: React.PropTypes.number, - notifications: React.PropTypes.object.isRequired, - client: React.PropTypes.object.isRequired - }, - - getInitialState: function() { - return { - pending: false, - copied: false, - callUrl: this.props.callUrl || "", - callUrlExpiry: 0 - }; - }, - - /** - * Provided by DocumentVisibilityMixin. Schedules retrieval of a new call - * URL everytime the panel is reopened. - */ - onDocumentVisible: function() { - this._fetchCallUrl(); - }, - - componentDidMount: function() { - // If we've already got a callURL, don't bother requesting a new one. - // As of this writing, only used for visual testing in the UI showcase. - if (this.state.callUrl.length) { - return; - } - - this._fetchCallUrl(); - }, - - /** - * Fetches a call URL. - */ - _fetchCallUrl: function() { - this.setState({pending: true}); - // XXX This is an empty string as a conversation identifier. Bug 1015938 implements - // a user-set string. - this.props.client.requestCallUrl("", - this._onCallUrlReceived); - }, - - _onCallUrlReceived: function(err, callUrlData) { - if (err) { - if (err.code != 401) { - // 401 errors are already handled in hawkRequest and show an error - // message about the session. - this.props.notifications.errorL10n("unable_retrieve_url"); - } - this.setState(this.getInitialState()); - } else { - try { - var callUrl = new window.URL(callUrlData.callUrl); - // XXX the current server vers does not implement the callToken field - // but it exists in the API. This workaround should be removed in the future - var token = callUrlData.callToken || - callUrl.pathname.split('/').pop(); - - // Now that a new URL is available, indicate it has not been shared. - this.linkExfiltrated = false; - - this.setState({pending: false, copied: false, - callUrl: callUrl.href, - callUrlExpiry: callUrlData.expiresAt}); - } catch(e) { - console.log(e); - this.props.notifications.errorL10n("unable_retrieve_url"); - this.setState(this.getInitialState()); - } - } - }, - - handleEmailButtonClick: function(event) { - this.handleLinkExfiltration(event); - - sharedUtils.composeCallUrlEmail(this.state.callUrl); - }, - - handleCopyButtonClick: function(event) { - this.handleLinkExfiltration(event); - // XXX the mozLoop object should be passed as a prop, to ease testing and - // using a fake implementation in UI components showcase. - navigator.mozLoop.copyString(this.state.callUrl); - this.setState({copied: true}); - }, - - linkExfiltrated: false, - - handleLinkExfiltration: function(event) { - // Update the count of shared URLs only once per generated URL. - if (!this.linkExfiltrated) { - this.linkExfiltrated = true; - try { - navigator.mozLoop.telemetryAdd("LOOP_CLIENT_CALL_URL_SHARED", true); - } catch (err) { - console.error("Error recording telemetry", err); - } - } - - // Note URL expiration every time it is shared. - if (this.state.callUrlExpiry) { - navigator.mozLoop.noteCallUrlExpiry(this.state.callUrlExpiry); - } - }, - - render: function() { - // XXX setting elem value from a state (in the callUrl input) - // makes it immutable ie read only but that is fine in our case. - // readOnly attr will suppress a warning regarding this issue - // from the react lib. - var cx = React.addons.classSet; - return ( -
- -
- -
-
- -
- ); - } - }); - /** * FxA sign in/up link component. */ @@ -820,9 +667,7 @@ loop.panel = (function(_, mozL10n) { var PanelView = React.createClass({ propTypes: { notifications: React.PropTypes.object.isRequired, - client: React.PropTypes.object.isRequired, // Mostly used for UI components showcase and unit tests - callUrl: React.PropTypes.string, userProfile: React.PropTypes.object, // Used only for unit tests. showTabButtons: React.PropTypes.bool, @@ -869,17 +714,13 @@ loop.panel = (function(_, mozL10n) { } }, - _roomsEnabled: function() { - return this.props.mozLoop.getLoopPref("rooms.enabled"); - }, - _onStatusChanged: function() { var profile = this.props.mozLoop.userProfile; var currUid = this.state.userProfile ? this.state.userProfile.uid : null; var newUid = profile ? profile.uid : null; if (currUid != newUid) { // On profile change (login, logout), switch back to the default tab. - this.selectTab(this._roomsEnabled() ? "rooms" : "call"); + this.selectTab("rooms"); this.setState({userProfile: profile}); } this.updateServiceErrors(); @@ -902,34 +743,6 @@ loop.panel = (function(_, mozL10n) { } }, - /** - * The rooms feature is hidden by default for now. Once it gets mainstream, - * this method can be simplified. - */ - _renderRoomsOrCallTab: function() { - if (!this._roomsEnabled()) { - return ( - -
- - -
-
- ); - } - - return ( - - - - - ); - }, - startForm: function(name, contact) { this.refs[name].initForm(contact); this.selectTab(name); @@ -986,10 +799,16 @@ loop.panel = (function(_, mozL10n) { clearOnDocumentHidden={true} /> - {this._renderRoomsOrCallTab()} + + + + + startForm={this.startForm} + notifications={this.props.notifications} />
+ + + + diff --git a/dom/media/AbstractMediaDecoder.h b/dom/media/AbstractMediaDecoder.h index d96b327d1a6..e2523fbad34 100644 --- a/dom/media/AbstractMediaDecoder.h +++ b/dom/media/AbstractMediaDecoder.h @@ -91,9 +91,9 @@ public: // Return true if the transport layer supports seeking. virtual bool IsMediaSeekable() = 0; - virtual void MetadataLoaded(nsAutoPtr aInfo, nsAutoPtr aTags) = 0; + virtual void MetadataLoaded(nsAutoPtr aInfo, nsAutoPtr aTags, bool aRestoredFromDromant) = 0; virtual void QueueMetadata(int64_t aTime, nsAutoPtr aInfo, nsAutoPtr aTags) = 0; - virtual void FirstFrameLoaded(nsAutoPtr aInfo) = 0; + virtual void FirstFrameLoaded(nsAutoPtr aInfo, bool aRestoredFromDromant) = 0; virtual void RemoveMediaTracks() = 0; @@ -164,15 +164,18 @@ class MetadataContainer protected: MetadataContainer(AbstractMediaDecoder* aDecoder, nsAutoPtr aInfo, - nsAutoPtr aTags) + nsAutoPtr aTags, + bool aRestoredFromDromant) : mDecoder(aDecoder), mInfo(aInfo), - mTags(aTags) + mTags(aTags), + mRestoredFromDromant(aRestoredFromDromant) {} nsRefPtr mDecoder; nsAutoPtr mInfo; nsAutoPtr mTags; + bool mRestoredFromDromant; }; class MetadataEventRunner : public nsRunnable, private MetadataContainer @@ -180,13 +183,14 @@ class MetadataEventRunner : public nsRunnable, private MetadataContainer public: MetadataEventRunner(AbstractMediaDecoder* aDecoder, nsAutoPtr aInfo, - nsAutoPtr aTags) - : MetadataContainer(aDecoder, aInfo, aTags) + nsAutoPtr aTags, + bool aRestoredFromDromant = false) + : MetadataContainer(aDecoder, aInfo, aTags, aRestoredFromDromant) {} NS_IMETHOD Run() MOZ_OVERRIDE { - mDecoder->MetadataLoaded(mInfo, mTags); + mDecoder->MetadataLoaded(mInfo, mTags, mRestoredFromDromant); return NS_OK; } }; @@ -195,13 +199,14 @@ class FirstFrameLoadedEventRunner : public nsRunnable, private MetadataContainer { public: FirstFrameLoadedEventRunner(AbstractMediaDecoder* aDecoder, - nsAutoPtr aInfo) - : MetadataContainer(aDecoder, aInfo, nsAutoPtr(nullptr)) + nsAutoPtr aInfo, + bool aRestoredFromDromant = false) + : MetadataContainer(aDecoder, aInfo, nsAutoPtr(nullptr), aRestoredFromDromant) {} NS_IMETHOD Run() MOZ_OVERRIDE { - mDecoder->FirstFrameLoaded(mInfo); + mDecoder->FirstFrameLoaded(mInfo, mRestoredFromDromant); return NS_OK; } }; @@ -211,16 +216,17 @@ class MetadataUpdatedEventRunner : public nsRunnable, private MetadataContainer public: MetadataUpdatedEventRunner(AbstractMediaDecoder* aDecoder, nsAutoPtr aInfo, - nsAutoPtr aTags) - : MetadataContainer(aDecoder, aInfo, aTags) + nsAutoPtr aTags, + bool aRestoredFromDromant = false) + : MetadataContainer(aDecoder, aInfo, aTags, aRestoredFromDromant) {} NS_IMETHOD Run() MOZ_OVERRIDE { nsAutoPtr info(new MediaInfo()); *info = *mInfo; - mDecoder->MetadataLoaded(info, mTags); - mDecoder->FirstFrameLoaded(mInfo); + mDecoder->MetadataLoaded(info, mTags, mRestoredFromDromant); + mDecoder->FirstFrameLoaded(mInfo, mRestoredFromDromant); return NS_OK; } }; diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index dca8ca7089b..9ca898255d5 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -131,7 +131,6 @@ void MediaDecoder::SetDormantIfNecessary(bool aDormant) if(aDormant) { // enter dormant state - DestroyDecodedStream(); mDecoderStateMachine->SetDormant(true); int64_t timeUsecs = 0; @@ -633,10 +632,8 @@ nsresult MediaDecoder::Seek(double aTime, SeekTarget::Type aSeekType) mRequestedSeekTarget = SeekTarget(timeUsecs, aSeekType); mCurrentTime = aTime; - // If we are already in the seeking state, then setting mRequestedSeekTarget - // above will result in the new seek occurring when the current seek - // completes. - if (mPlayState != PLAY_STATE_LOADING && mPlayState != PLAY_STATE_SEEKING) { + // If we are already in the seeking state, the new seek overrides the old one. + if (mPlayState != PLAY_STATE_LOADING) { bool paused = false; if (mOwner) { paused = mOwner->GetPaused(); @@ -697,7 +694,8 @@ MediaDecoder::IsExpectingMoreData() } void MediaDecoder::MetadataLoaded(nsAutoPtr aInfo, - nsAutoPtr aTags) + nsAutoPtr aTags, + bool aRestoredFromDromant) { MOZ_ASSERT(NS_IsMainThread()); @@ -727,11 +725,14 @@ void MediaDecoder::MetadataLoaded(nsAutoPtr aInfo, // Make sure the element and the frame (if any) are told about // our new size. Invalidate(); - mOwner->MetadataLoaded(mInfo, nsAutoPtr(aTags.forget())); + if (!aRestoredFromDromant) { + mOwner->MetadataLoaded(mInfo, nsAutoPtr(aTags.forget())); + } } } -void MediaDecoder::FirstFrameLoaded(nsAutoPtr aInfo) +void MediaDecoder::FirstFrameLoaded(nsAutoPtr aInfo, + bool aRestoredFromDromant) { MOZ_ASSERT(NS_IsMainThread()); @@ -747,7 +748,9 @@ void MediaDecoder::FirstFrameLoaded(nsAutoPtr aInfo) if (mOwner) { Invalidate(); - mOwner->FirstFrameLoaded(); + if (!aRestoredFromDromant) { + mOwner->FirstFrameLoaded(); + } } // This can run cache callbacks. diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h index 2a1408b88e2..d07e12e5d3a 100644 --- a/dom/media/MediaDecoder.h +++ b/dom/media/MediaDecoder.h @@ -775,11 +775,13 @@ public: // Called when the metadata from the media file has been loaded by the // state machine. Call on the main thread only. virtual void MetadataLoaded(nsAutoPtr aInfo, - nsAutoPtr aTags) MOZ_OVERRIDE; + nsAutoPtr aTags, + bool aRestoredFromDromant) MOZ_OVERRIDE; // Called when the first audio and/or video from the media file has been loaded // by the state machine. Call on the main thread only. - virtual void FirstFrameLoaded(nsAutoPtr aInfo) MOZ_OVERRIDE; + virtual void FirstFrameLoaded(nsAutoPtr aInfo, + bool aRestoredFromDromant) MOZ_OVERRIDE; // Called from MetadataLoaded(). Creates audio tracks and adds them to its // owner's audio track list, and implies to video tracks respectively. diff --git a/dom/media/MediaDecoderReader.h b/dom/media/MediaDecoderReader.h index 534041430d2..bb0c174668d 100644 --- a/dom/media/MediaDecoderReader.h +++ b/dom/media/MediaDecoderReader.h @@ -150,12 +150,20 @@ public: // ReadUpdatedMetadata will always be called once ReadMetadata has succeeded. virtual void ReadUpdatedMetadata(MediaInfo* aInfo) { }; - // Moves the decode head to aTime microseconds. aStartTime and aEndTime - // denote the start and end times of the media in usecs, and aCurrentTime - // is the current playback position in microseconds. + // Moves the decode head to aTime microseconds. aEndTime denotes the end + // time of the media in usecs. This is only needed for OggReader, and should + // probably be removed somehow. virtual nsRefPtr - Seek(int64_t aTime, int64_t aStartTime, - int64_t aEndTime, int64_t aCurrentTime) = 0; + Seek(int64_t aTime, int64_t aEndTime) = 0; + + // Cancels an ongoing seek, if any. Any previously-requested seek is + // guaranteeed to be resolved or rejected in finite time, though no + // guarantees are made about precise nature of the resolve/reject, since the + // promise might have already dispatched a resolution or an error code before + // the cancel arrived. + // + // Must be called on the decode task queue. + virtual void CancelSeek() { }; // Called to move the reader into idle state. When the reader is // created it is assumed to be active (i.e. not idle). When the media diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp index f486cd76e12..9679e045d6d 100644 --- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -188,6 +188,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, mPlayDuration(0), mStartTime(-1), mEndTime(-1), + mDurationSet(false), mFragmentEndTime(-1), mReader(aReader), mCurrentFrameTime(0), @@ -219,10 +220,12 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, mDropVideoUntilNextDiscontinuity(false), mDecodeToSeekTarget(false), mWaitingForDecoderSeek(false), + mCancelingSeek(false), mCurrentTimeBeforeSeek(0), mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED), - mDecodingFrozenAtStateMetadata(false), - mDecodingFrozenAtStateDecoding(false) + mDecodingFrozenAtStateDecoding(false), + mSentLoadedMetadataEvent(false), + mSentFirstFrameLoadedEvent(false) { MOZ_COUNT_CTOR(MediaDecoderStateMachine); NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); @@ -1408,6 +1411,14 @@ int64_t MediaDecoderStateMachine::GetDuration() return mEndTime - mStartTime; } +int64_t MediaDecoderStateMachine::GetEndTime() +{ + if (mEndTime == -1 && mDurationSet) { + return INT64_MAX; + } + return mEndTime; +} + void MediaDecoderStateMachine::SetDuration(int64_t aDuration) { NS_ASSERTION(NS_IsMainThread() || OnDecodeThread(), @@ -1415,13 +1426,21 @@ void MediaDecoderStateMachine::SetDuration(int64_t aDuration) AssertCurrentThreadInMonitor(); if (aDuration == -1) { + mDurationSet = false; return; } + mDurationSet = true; + if (mStartTime == -1) { SetStartTime(0); } + if (aDuration == INT64_MAX) { + mEndTime = -1; + return; + } + mEndTime = mStartTime + aDuration; } @@ -1481,9 +1500,9 @@ void MediaDecoderStateMachine::SetDormant(bool aDormant) mCurrentSeekTarget.Reset(); ScheduleStateMachine(); SetState(DECODER_STATE_DORMANT); + StopPlayback(); mDecoder->GetReentrantMonitor().NotifyAll(); } else if ((aDormant != true) && (mState == DECODER_STATE_DORMANT)) { - mDecodingFrozenAtStateMetadata = true; mDecodingFrozenAtStateDecoding = true; ScheduleStateMachine(); mCurrentFrameTime = 0; @@ -1685,10 +1704,6 @@ void MediaDecoderStateMachine::Seek(const SeekTarget& aTarget) return; } - // MediaDecoder::mPlayState should be SEEKING while we seek, and - // in that case MediaDecoder shouldn't be calling us. - NS_ASSERTION(mState != DECODER_STATE_SEEKING, - "We shouldn't already be seeking"); NS_ASSERTION(mState > DECODER_STATE_DECODING_METADATA, "We should have got duration already"); @@ -1735,12 +1750,13 @@ MediaDecoderStateMachine::StartSeek(const SeekTarget& aTarget) } // Bound the seek time to be inside the media range. + int64_t end = GetEndTime(); NS_ASSERTION(mStartTime != -1, "Should know start time by now"); - NS_ASSERTION(mEndTime != -1, "Should know end time by now"); + NS_ASSERTION(end != -1, "Should know end time by now"); int64_t seekTime = aTarget.mTime + mStartTime; - seekTime = std::min(seekTime, mEndTime); + seekTime = std::min(seekTime, end); seekTime = std::max(mStartTime, seekTime); - NS_ASSERTION(seekTime >= mStartTime && seekTime <= mEndTime, + NS_ASSERTION(seekTime >= mStartTime && seekTime <= end, "Can only seek in range [0,duration]"); mSeekTarget = SeekTarget(seekTime, aTarget.mType); @@ -1896,24 +1912,14 @@ MediaDecoderStateMachine::EnqueueDecodeSeekTask() "Should be on state machine or decode thread."); AssertCurrentThreadInMonitor(); - if (mState != DECODER_STATE_SEEKING || - !mSeekTarget.IsValid() || - mCurrentSeekTarget.IsValid()) { - return NS_OK; - } - mCurrentSeekTarget = mSeekTarget; - mSeekTarget.Reset(); - mDropAudioUntilNextDiscontinuity = HasAudio(); - mDropVideoUntilNextDiscontinuity = HasVideo(); - RefPtr task( NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeSeek)); nsresult rv = DecodeTaskQueue()->Dispatch(task); if (NS_FAILED(rv)) { DECODER_WARN("Dispatch DecodeSeek task failed."); - mCurrentSeekTarget.Reset(); DecodeError(); } + return rv; } @@ -2188,7 +2194,7 @@ nsresult MediaDecoderStateMachine::DecodeMetadata() return NS_ERROR_FAILURE; } mDecoder->StartProgressUpdates(); - mGotDurationFromMetaData = (GetDuration() != -1); + mGotDurationFromMetaData = (GetDuration() != -1) || mDurationSet; if (mGotDurationFromMetaData) { // We have all the information required: duration and size @@ -2214,8 +2220,20 @@ MediaDecoderStateMachine::EnqueueLoadedMetadataEvent() nsAutoPtr info(new MediaInfo()); *info = mInfo; nsCOMPtr metadataLoadedEvent = - new MetadataEventRunner(mDecoder, info, mMetadataTags); + new MetadataEventRunner(mDecoder, info, mMetadataTags, mSentLoadedMetadataEvent); NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL); + mSentLoadedMetadataEvent = true; +} + +void +MediaDecoderStateMachine::EnqueueFirstFrameLoadedEvent() +{ + nsAutoPtr info(new MediaInfo()); + *info = mInfo; + nsCOMPtr event = + new FirstFrameLoadedEventRunner(mDecoder, info, mSentFirstFrameLoadedEvent); + NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); + mSentFirstFrameLoadedEvent = true; } void @@ -2254,7 +2272,10 @@ MediaDecoderStateMachine::DecodeFirstFrame() SetStartTime(0); nsresult res = FinishDecodeFirstFrame(); NS_ENSURE_SUCCESS(res, res); - } else if (mDecodingFrozenAtStateMetadata) { + } else if (mSentFirstFrameLoadedEvent) { + // We're resuming from dormant state, so we don't need to request + // the first samples in order to determine the media start time, + // we have the start time from last time we loaded. SetStartTime(mStartTime); nsresult res = FinishDecodeFirstFrame(); NS_ENSURE_SUCCESS(res, res); @@ -2289,7 +2310,7 @@ MediaDecoderStateMachine::FinishDecodeFirstFrame() return NS_ERROR_FAILURE; } - if (!IsRealTime() && !mDecodingFrozenAtStateMetadata) { + if (!IsRealTime() && !mSentFirstFrameLoadedEvent) { const VideoData* v = VideoQueue().PeekFront(); const AudioData* a = AudioQueue().PeekFront(); SetStartTime(mReader->ComputeStartTime(v, a)); @@ -2300,20 +2321,14 @@ MediaDecoderStateMachine::FinishDecodeFirstFrame() } NS_ASSERTION(mStartTime != -1, "Must have start time"); - MOZ_ASSERT((!HasVideo() && !HasAudio()) || - !(mDecoder->IsMediaSeekable() && mDecoder->IsTransportSeekable()) || - mEndTime != -1, - "Active seekable media should have end time"); MOZ_ASSERT(!(mDecoder->IsMediaSeekable() && mDecoder->IsTransportSeekable()) || - GetDuration() != -1, + (GetDuration() != -1) || mDurationSet, "Seekable media should have duration"); DECODER_LOG("Media goes from %lld to %lld (duration %lld) " "transportSeekable=%d, mediaSeekable=%d", mStartTime, mEndTime, GetDuration(), mDecoder->IsTransportSeekable(), mDecoder->IsMediaSeekable()); - mDecodingFrozenAtStateMetadata = false; - if (HasAudio() && !HasVideo()) { // We're playing audio only. We don't need to worry about slow video // decodes causing audio underruns, so don't buffer so much audio in @@ -2330,20 +2345,15 @@ MediaDecoderStateMachine::FinishDecodeFirstFrame() nsAutoPtr info(new MediaInfo()); *info = mInfo; - nsCOMPtr event; if (!mGotDurationFromMetaData) { // We now have a duration, we can fire the LoadedMetadata and // FirstFrame event. - event = - new MetadataUpdatedEventRunner(mDecoder, - info, - mMetadataTags); + EnqueueLoadedMetadataEvent(); + EnqueueFirstFrameLoadedEvent(); } else { // Inform the element that we've loaded the first frame. - event = - new FirstFrameLoadedEventRunner(mDecoder, info); + EnqueueFirstFrameLoadedEvent(); } - NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); if (mState == DECODER_STATE_DECODING_FIRSTFRAME) { StartDecoding(); @@ -2366,10 +2376,35 @@ void MediaDecoderStateMachine::DecodeSeek() { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); NS_ASSERTION(OnDecodeThread(), "Should be on decode thread."); - if (mState != DECODER_STATE_SEEKING) { + + if (mState != DECODER_STATE_SEEKING || + !mSeekTarget.IsValid()) { + DECODER_LOG("Early returning from DecodeSeek"); return; } + // If there's already an existing seek in progress, we need to handle that. + if (mCurrentSeekTarget.IsValid()) { + // There are 3 states we might be in, listed in the order that they occur: + // (1) Waiting for the seek to be resolved. + // (2) Waiting for the seek to be resolved, having already issued a cancel. + // (3) After seek resolution, waiting for SeekComplete to run. + // + // If we're in the first state, we move to the second. Otherwise, we just wait + // for things to sort themselves out. + if (mWaitingForDecoderSeek && !mCancelingSeek) { + mReader->CancelSeek(); + mCancelingSeek = true; + } + + return; + } + + mCurrentSeekTarget = mSeekTarget; + mSeekTarget.Reset(); + mDropAudioUntilNextDiscontinuity = HasAudio(); + mDropVideoUntilNextDiscontinuity = HasVideo(); + // During the seek, don't have a lock on the decoder state, // otherwise long seek operations can block the main thread. // The events dispatched to the main thread are SYNC calls. @@ -2429,7 +2464,7 @@ void MediaDecoderStateMachine::DecodeSeek() // the reader, since it could do I/O or deadlock some other way. res = mReader->ResetDecode(); if (NS_SUCCEEDED(res)) { - mReader->Seek(seekTime, mStartTime, mEndTime, mCurrentTimeBeforeSeek) + mReader->Seek(seekTime, GetEndTime()) ->Then(DecodeTaskQueue(), __func__, this, &MediaDecoderStateMachine::OnSeekCompleted, &MediaDecoderStateMachine::OnSeekFailed); @@ -2448,6 +2483,7 @@ MediaDecoderStateMachine::OnSeekCompleted(int64_t aTime) { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); mWaitingForDecoderSeek = false; + mCancelingSeek = false; // We must decode the first samples of active streams, so we can determine // the new stream time. So dispatch tasks to do that. @@ -2459,12 +2495,21 @@ void MediaDecoderStateMachine::OnSeekFailed(nsresult aResult) { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + bool wasCanceled = mCancelingSeek; mWaitingForDecoderSeek = false; - // Sometimes we reject the promise for non-failure reasons, like - // when we request a second seek before the previous one has - // completed. + mCancelingSeek = false; + if (NS_FAILED(aResult)) { DecodeError(); + } else if (wasCanceled && mSeekTarget.IsValid() && mState == DECODER_STATE_SEEKING) { + // Try again. + mCurrentSeekTarget = mSeekTarget; + mSeekTarget.Reset(); + mReader->Seek(mCurrentSeekTarget.mTime, mEndTime) + ->Then(DecodeTaskQueue(), __func__, this, + &MediaDecoderStateMachine::OnSeekCompleted, + &MediaDecoderStateMachine::OnSeekFailed); + mWaitingForDecoderSeek = true; } } @@ -2527,7 +2572,12 @@ MediaDecoderStateMachine::SeekCompleted() nsCOMPtr stopEvent; bool isLiveStream = mDecoder->GetResource()->GetLength() == -1; - if (GetMediaTime() == mEndTime && !isLiveStream) { + if (mSeekTarget.IsValid()) { + // A new seek target came in while we were processing the old one. No rest + // for the seeking. + DECODER_LOG("A new seek came along while we were finishing the old one - staying in SEEKING"); + SetState(DECODER_STATE_SEEKING); + } else if (GetMediaTime() == mEndTime && !isLiveStream) { // Seeked to end of media, move to COMPLETED state. Note we don't do // this if we're playing a live stream, since the end of media will advance // once we download more data! @@ -3244,7 +3294,7 @@ void MediaDecoderStateMachine::SetStartTime(int64_t aStartTimeUsecs) mStartTime = 0; if (aStartTimeUsecs != 0) { mStartTime = aStartTimeUsecs; - if (mGotDurationFromMetaData) { + if (mGotDurationFromMetaData && GetEndTime() != INT64_MAX) { NS_ASSERTION(mEndTime != -1, "We should have mEndTime as supplied duration here"); // We were specified a duration from a Content-Duration HTTP header. diff --git a/dom/media/MediaDecoderStateMachine.h b/dom/media/MediaDecoderStateMachine.h index a2f995a0757..9b8224186cf 100644 --- a/dom/media/MediaDecoderStateMachine.h +++ b/dom/media/MediaDecoderStateMachine.h @@ -171,11 +171,18 @@ public: // must be obtained before calling this. It is in units of microseconds. int64_t GetDuration(); + // Time of the last frame in the media, in microseconds or INT64_MAX if + // media has an infinite duration. + // Accessed on state machine, decode, and main threads. + // Access controlled by decoder monitor. + int64_t GetEndTime(); + // Called from the main thread to set the duration of the media resource // if it is able to be obtained via HTTP headers. Called from the // state machine thread to set the duration if it is obtained from the // media metadata. The decoder monitor must be obtained before calling this. // aDuration is in microseconds. + // A value of INT64_MAX will be treated as infinity. void SetDuration(int64_t aDuration); // Called while decoding metadata to set the end time of the media @@ -621,6 +628,8 @@ protected: // The decoder monitor must be held. void EnqueueLoadedMetadataEvent(); + void EnqueueFirstFrameLoadedEvent(); + // Dispatches a task to the decode task queue to begin decoding content. // This is threadsafe and can be called on any thread. // The decoder monitor must be held. @@ -832,8 +841,14 @@ protected: // Time of the last frame in the media, in microseconds. This is the // end time of the last frame in the media. Accessed on state // machine, decode, and main threads. Access controlled by decoder monitor. + // It will be set to -1 if the duration is infinite int64_t mEndTime; + // Will be set when SetDuration has been called with a value != -1 + // mDurationSet false doesn't indicate that we do not have a valid duration + // as mStartTime and mEndTime could have been set separately. + bool mDurationSet; + // Position to seek to in microseconds when the seek state transition occurs. // The decoder monitor lock must be obtained before reading or writing // this value. Accessed on main and decode thread. @@ -1097,6 +1112,10 @@ protected: // until this completes. bool mWaitingForDecoderSeek; + // True if we're in the process of canceling a seek. This allows us to avoid + // invoking CancelSeek() multiple times. + bool mCancelingSeek; + // We record the playback position before we seek in order to // determine where the seek terminated relative to the playback position // we were at before the seek. @@ -1110,16 +1129,19 @@ protected: MediaDecoderOwner::NextFrameStatus mLastFrameStatus; - // True if we are back from DECODER_STATE_DORMANT state, and we can skip + // mDecodingFrozenAtStateDecoding: turn on/off at + // SetDormant/Seek,Play. + bool mDecodingFrozenAtStateDecoding; + + // True if we are back from DECODER_STATE_DORMANT state and + // LoadedMetadataEvent was already sent. + bool mSentLoadedMetadataEvent; + // True if we are back from DECODER_STATE_DORMANT state and + // FirstFrameLoadedEvent was already sent, then we can skip // SetStartTime because the mStartTime already set before. Also we don't need // to decode any audio/video since the MediaDecoder will trigger a seek // operation soon. - // mDecodingFrozenAtStateMetadata: turn on/off at - // SetDormant/FinishDecodeMetadata. - // mDecodingFrozenAtStateDecoding: turn on/off at - // SetDormant/Seek,Play. - bool mDecodingFrozenAtStateMetadata; - bool mDecodingFrozenAtStateDecoding; + bool mSentFirstFrameLoadedEvent; }; } // namespace mozilla; diff --git a/dom/media/android/AndroidMediaReader.cpp b/dom/media/android/AndroidMediaReader.cpp index ef49972260f..15bf214b399 100644 --- a/dom/media/android/AndroidMediaReader.cpp +++ b/dom/media/android/AndroidMediaReader.cpp @@ -320,7 +320,7 @@ bool AndroidMediaReader::DecodeAudioData() } nsRefPtr -AndroidMediaReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) +AndroidMediaReader::Seek(int64_t aTarget, int64_t aEndTime) { NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); diff --git a/dom/media/android/AndroidMediaReader.h b/dom/media/android/AndroidMediaReader.h index 09894a16c97..5b17960bf35 100644 --- a/dom/media/android/AndroidMediaReader.h +++ b/dom/media/android/AndroidMediaReader.h @@ -70,7 +70,7 @@ public: virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags); virtual nsRefPtr - Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; virtual nsRefPtr Shutdown() MOZ_OVERRIDE; diff --git a/dom/media/apple/AppleMP3Reader.cpp b/dom/media/apple/AppleMP3Reader.cpp index a46ade41842..234cd3a026d 100644 --- a/dom/media/apple/AppleMP3Reader.cpp +++ b/dom/media/apple/AppleMP3Reader.cpp @@ -493,14 +493,9 @@ AppleMP3Reader::SetupDecoder() nsRefPtr -AppleMP3Reader::Seek(int64_t aTime, - int64_t aStartTime, - int64_t aEndTime, - int64_t aCurrentTime) +AppleMP3Reader::Seek(int64_t aTime, int64_t aEndTime) { MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread"); - NS_ASSERTION(aStartTime < aEndTime, - "Seeking should happen over a positive range"); // Find the exact frame/packet that contains |aTime|. mCurrentAudioFrame = aTime * mAudioSampleRate / USECS_PER_S; diff --git a/dom/media/apple/AppleMP3Reader.h b/dom/media/apple/AppleMP3Reader.h index 17547b2fd0a..31b5d0ac08b 100644 --- a/dom/media/apple/AppleMP3Reader.h +++ b/dom/media/apple/AppleMP3Reader.h @@ -34,10 +34,7 @@ public: MetadataTags** aTags) MOZ_OVERRIDE; virtual nsRefPtr - Seek(int64_t aTime, - int64_t aStartTime, - int64_t aEndTime, - int64_t aCurrentTime) MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; void AudioSampleCallback(UInt32 aNumBytes, UInt32 aNumPackets, diff --git a/dom/media/directshow/DirectShowReader.cpp b/dom/media/directshow/DirectShowReader.cpp index fbbe0c719a6..49532899420 100644 --- a/dom/media/directshow/DirectShowReader.cpp +++ b/dom/media/directshow/DirectShowReader.cpp @@ -368,10 +368,7 @@ DirectShowReader::HasVideo() } nsRefPtr -DirectShowReader::Seek(int64_t aTargetUs, - int64_t aStartTime, - int64_t aEndTime, - int64_t aCurrentTime) +DirectShowReader::Seek(int64_t aTargetUs, int64_t aEndTime) { nsresult res = SeekInternal(aTargetUs); if (NS_FAILED(res)) { diff --git a/dom/media/directshow/DirectShowReader.h b/dom/media/directshow/DirectShowReader.h index f6cf3590f7e..99c313636b0 100644 --- a/dom/media/directshow/DirectShowReader.h +++ b/dom/media/directshow/DirectShowReader.h @@ -61,10 +61,7 @@ public: MetadataTags** aTags) MOZ_OVERRIDE; nsRefPtr - Seek(int64_t aTime, - int64_t aStartTime, - int64_t aEndTime, - int64_t aCurrentTime) MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; void NotifyDataArrived(const char* aBuffer, uint32_t aLength, diff --git a/dom/media/fmp4/MP4Reader.cpp b/dom/media/fmp4/MP4Reader.cpp index 75791d36a38..66ffa3e724e 100644 --- a/dom/media/fmp4/MP4Reader.cpp +++ b/dom/media/fmp4/MP4Reader.cpp @@ -898,10 +898,7 @@ MP4Reader::SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed } nsRefPtr -MP4Reader::Seek(int64_t aTime, - int64_t aStartTime, - int64_t aEndTime, - int64_t aCurrentTime) +MP4Reader::Seek(int64_t aTime, int64_t aEndTime) { LOG("MP4Reader::Seek(%lld)", aTime); MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn()); diff --git a/dom/media/fmp4/MP4Reader.h b/dom/media/fmp4/MP4Reader.h index b4309cc327a..67ca44c91c5 100644 --- a/dom/media/fmp4/MP4Reader.h +++ b/dom/media/fmp4/MP4Reader.h @@ -59,10 +59,7 @@ public: virtual void ReadUpdatedMetadata(MediaInfo* aInfo) MOZ_OVERRIDE; virtual nsRefPtr - Seek(int64_t aTime, - int64_t aStartTime, - int64_t aEndTime, - int64_t aCurrentTime) MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; virtual bool IsMediaSeekable() MOZ_OVERRIDE; diff --git a/dom/media/gmp/GMPDecryptorChild.cpp b/dom/media/gmp/GMPDecryptorChild.cpp index 2c8598e7e4d..b8cf43da7cf 100644 --- a/dom/media/gmp/GMPDecryptorChild.cpp +++ b/dom/media/gmp/GMPDecryptorChild.cpp @@ -211,7 +211,7 @@ bool GMPDecryptorChild::RecvCreateSession(const uint32_t& aCreateSessionToken, const uint32_t& aPromiseId, const nsCString& aInitDataType, - const nsTArray& aInitData, + InfallibleTArray&& aInitData, const GMPSessionType& aSessionType) { if (!mSession) { @@ -247,7 +247,7 @@ GMPDecryptorChild::RecvLoadSession(const uint32_t& aPromiseId, bool GMPDecryptorChild::RecvUpdateSession(const uint32_t& aPromiseId, const nsCString& aSessionId, - const nsTArray& aResponse) + InfallibleTArray&& aResponse) { if (!mSession) { return false; @@ -294,7 +294,7 @@ GMPDecryptorChild::RecvRemoveSession(const uint32_t& aPromiseId, bool GMPDecryptorChild::RecvSetServerCertificate(const uint32_t& aPromiseId, - const nsTArray& aServerCert) + InfallibleTArray&& aServerCert) { if (!mSession) { return false; @@ -309,7 +309,7 @@ GMPDecryptorChild::RecvSetServerCertificate(const uint32_t& aPromiseId, bool GMPDecryptorChild::RecvDecrypt(const uint32_t& aId, - const nsTArray& aBuffer, + InfallibleTArray&& aBuffer, const GMPDecryptionData& aMetadata) { if (!mSession) { diff --git a/dom/media/gmp/GMPDecryptorChild.h b/dom/media/gmp/GMPDecryptorChild.h index cfb8a9d5f0a..ee1f72c327f 100644 --- a/dom/media/gmp/GMPDecryptorChild.h +++ b/dom/media/gmp/GMPDecryptorChild.h @@ -92,7 +92,7 @@ private: virtual bool RecvCreateSession(const uint32_t& aCreateSessionToken, const uint32_t& aPromiseId, const nsCString& aInitDataType, - const nsTArray& aInitData, + InfallibleTArray&& aInitData, const GMPSessionType& aSessionType) MOZ_OVERRIDE; virtual bool RecvLoadSession(const uint32_t& aPromiseId, @@ -100,7 +100,7 @@ private: virtual bool RecvUpdateSession(const uint32_t& aPromiseId, const nsCString& aSessionId, - const nsTArray& aResponse) MOZ_OVERRIDE; + InfallibleTArray&& aResponse) MOZ_OVERRIDE; virtual bool RecvCloseSession(const uint32_t& aPromiseId, const nsCString& aSessionId) MOZ_OVERRIDE; @@ -109,12 +109,12 @@ private: const nsCString& aSessionId) MOZ_OVERRIDE; virtual bool RecvDecrypt(const uint32_t& aId, - const nsTArray& aBuffer, + InfallibleTArray&& aBuffer, const GMPDecryptionData& aMetadata) MOZ_OVERRIDE; // Resolve/reject promise on completion. virtual bool RecvSetServerCertificate(const uint32_t& aPromiseId, - const nsTArray& aServerCert) MOZ_OVERRIDE; + InfallibleTArray&& aServerCert) MOZ_OVERRIDE; virtual bool RecvDecryptingComplete() MOZ_OVERRIDE; diff --git a/dom/media/gmp/GMPDecryptorParent.cpp b/dom/media/gmp/GMPDecryptorParent.cpp index 94f9020f839..946687b786e 100644 --- a/dom/media/gmp/GMPDecryptorParent.cpp +++ b/dom/media/gmp/GMPDecryptorParent.cpp @@ -214,7 +214,7 @@ GMPDecryptorParent::RecvRejectPromise(const uint32_t& aPromiseId, bool GMPDecryptorParent::RecvSessionMessage(const nsCString& aSessionId, const GMPSessionMessageType& aMessageType, - const nsTArray& aMessage) + nsTArray&& aMessage) { if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); @@ -266,7 +266,7 @@ GMPDecryptorParent::RecvSessionError(const nsCString& aSessionId, bool GMPDecryptorParent::RecvKeyIdUsable(const nsCString& aSessionId, - const nsTArray& aKeyId) + InfallibleTArray&& aKeyId) { if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); @@ -278,7 +278,7 @@ GMPDecryptorParent::RecvKeyIdUsable(const nsCString& aSessionId, bool GMPDecryptorParent::RecvKeyIdNotUsable(const nsCString& aSessionId, - const nsTArray& aKeyId) + InfallibleTArray&& aKeyId) { if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); @@ -302,7 +302,7 @@ GMPDecryptorParent::RecvSetCaps(const uint64_t& aCaps) bool GMPDecryptorParent::RecvDecrypted(const uint32_t& aId, const GMPErr& aErr, - const nsTArray& aBuffer) + InfallibleTArray&& aBuffer) { if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); diff --git a/dom/media/gmp/GMPDecryptorParent.h b/dom/media/gmp/GMPDecryptorParent.h index 207160602eb..b61e6e87e62 100644 --- a/dom/media/gmp/GMPDecryptorParent.h +++ b/dom/media/gmp/GMPDecryptorParent.h @@ -79,7 +79,7 @@ private: virtual bool RecvSessionMessage(const nsCString& aSessionId, const GMPSessionMessageType& aMessageType, - const nsTArray& aMessage) MOZ_OVERRIDE; + nsTArray&& aMessage) MOZ_OVERRIDE; virtual bool RecvExpirationChange(const nsCString& aSessionId, const double& aExpiryTime) MOZ_OVERRIDE; @@ -92,14 +92,14 @@ private: const nsCString& aMessage) MOZ_OVERRIDE; virtual bool RecvKeyIdUsable(const nsCString& aSessionId, - const nsTArray& aKeyId) MOZ_OVERRIDE; + InfallibleTArray&& aKeyId) MOZ_OVERRIDE; virtual bool RecvKeyIdNotUsable(const nsCString& aSessionId, - const nsTArray& aKeyId) MOZ_OVERRIDE; + InfallibleTArray&& aKeyId) MOZ_OVERRIDE; virtual bool RecvDecrypted(const uint32_t& aId, const GMPErr& aErr, - const nsTArray& aBuffer) MOZ_OVERRIDE; + InfallibleTArray&& aBuffer) MOZ_OVERRIDE; virtual bool RecvSetCaps(const uint64_t& aCaps) MOZ_OVERRIDE; diff --git a/dom/media/gmp/GMPStorageChild.cpp b/dom/media/gmp/GMPStorageChild.cpp index 30d16f27fde..e41c6cc3351 100644 --- a/dom/media/gmp/GMPStorageChild.cpp +++ b/dom/media/gmp/GMPStorageChild.cpp @@ -242,7 +242,7 @@ GMPStorageChild::RecvOpenComplete(const nsCString& aRecordName, bool GMPStorageChild::RecvReadComplete(const nsCString& aRecordName, const GMPErr& aStatus, - const InfallibleTArray& aBytes) + InfallibleTArray&& aBytes) { if (mShutdown) { return true; @@ -330,7 +330,7 @@ private: }; bool -GMPStorageChild::RecvRecordNames(const InfallibleTArray& aRecordNames, +GMPStorageChild::RecvRecordNames(InfallibleTArray&& aRecordNames, const GMPErr& aStatus) { RecordIteratorContext ctx; diff --git a/dom/media/gmp/GMPStorageChild.h b/dom/media/gmp/GMPStorageChild.h index 4973aeabf23..972b1b3e1be 100644 --- a/dom/media/gmp/GMPStorageChild.h +++ b/dom/media/gmp/GMPStorageChild.h @@ -85,10 +85,10 @@ protected: const GMPErr& aStatus) MOZ_OVERRIDE; virtual bool RecvReadComplete(const nsCString& aRecordName, const GMPErr& aStatus, - const InfallibleTArray& aBytes) MOZ_OVERRIDE; + InfallibleTArray&& aBytes) MOZ_OVERRIDE; virtual bool RecvWriteComplete(const nsCString& aRecordName, const GMPErr& aStatus) MOZ_OVERRIDE; - virtual bool RecvRecordNames(const InfallibleTArray& aRecordNames, + virtual bool RecvRecordNames(InfallibleTArray&& aRecordNames, const GMPErr& aStatus) MOZ_OVERRIDE; virtual bool RecvShutdown() MOZ_OVERRIDE; diff --git a/dom/media/gmp/GMPStorageParent.cpp b/dom/media/gmp/GMPStorageParent.cpp index 494702e2aa6..5274e8b8ec7 100644 --- a/dom/media/gmp/GMPStorageParent.cpp +++ b/dom/media/gmp/GMPStorageParent.cpp @@ -540,7 +540,7 @@ GMPStorageParent::RecvRead(const nsCString& aRecordName) bool GMPStorageParent::RecvWrite(const nsCString& aRecordName, - const InfallibleTArray& aBytes) + InfallibleTArray&& aBytes) { LOGD(("%s::%s: %p record=%s", __CLASS__, __FUNCTION__, this, aRecordName.get())); diff --git a/dom/media/gmp/GMPStorageParent.h b/dom/media/gmp/GMPStorageParent.h index a9e3bc46b64..403e43a3fbe 100644 --- a/dom/media/gmp/GMPStorageParent.h +++ b/dom/media/gmp/GMPStorageParent.h @@ -42,7 +42,7 @@ protected: virtual bool RecvOpen(const nsCString& aRecordName) MOZ_OVERRIDE; virtual bool RecvRead(const nsCString& aRecordName) MOZ_OVERRIDE; virtual bool RecvWrite(const nsCString& aRecordName, - const InfallibleTArray& aBytes) MOZ_OVERRIDE; + InfallibleTArray&& aBytes) MOZ_OVERRIDE; virtual bool RecvGetRecordNames() MOZ_OVERRIDE; virtual bool RecvClose(const nsCString& aRecordName) MOZ_OVERRIDE; virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE; diff --git a/dom/media/gmp/GMPVideoDecoderChild.cpp b/dom/media/gmp/GMPVideoDecoderChild.cpp index 8afe79e5140..bed62b923c9 100644 --- a/dom/media/gmp/GMPVideoDecoderChild.cpp +++ b/dom/media/gmp/GMPVideoDecoderChild.cpp @@ -107,7 +107,7 @@ GMPVideoDecoderChild::Error(GMPErr aError) bool GMPVideoDecoderChild::RecvInitDecode(const GMPVideoCodec& aCodecSettings, - const nsTArray& aCodecSpecific, + InfallibleTArray&& aCodecSpecific, const int32_t& aCoreCount) { if (!mVideoDecoder) { @@ -126,7 +126,7 @@ GMPVideoDecoderChild::RecvInitDecode(const GMPVideoCodec& aCodecSettings, bool GMPVideoDecoderChild::RecvDecode(const GMPVideoEncodedFrameData& aInputFrame, const bool& aMissingFrames, - const nsTArray& aCodecSpecificInfo, + InfallibleTArray&& aCodecSpecificInfo, const int64_t& aRenderTimeMs) { if (!mVideoDecoder) { @@ -146,7 +146,7 @@ GMPVideoDecoderChild::RecvDecode(const GMPVideoEncodedFrameData& aInputFrame, } bool -GMPVideoDecoderChild::RecvChildShmemForPool(Shmem& aFrameBuffer) +GMPVideoDecoderChild::RecvChildShmemForPool(Shmem&& aFrameBuffer) { if (aFrameBuffer.IsWritable()) { mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPFrameData, diff --git a/dom/media/gmp/GMPVideoDecoderChild.h b/dom/media/gmp/GMPVideoDecoderChild.h index d5438481d4e..c28e62f279d 100644 --- a/dom/media/gmp/GMPVideoDecoderChild.h +++ b/dom/media/gmp/GMPVideoDecoderChild.h @@ -63,13 +63,13 @@ public: private: // PGMPVideoDecoderChild virtual bool RecvInitDecode(const GMPVideoCodec& aCodecSettings, - const nsTArray& aCodecSpecific, + InfallibleTArray&& aCodecSpecific, const int32_t& aCoreCount) MOZ_OVERRIDE; virtual bool RecvDecode(const GMPVideoEncodedFrameData& aInputFrame, const bool& aMissingFrames, - const nsTArray& aCodecSpecificInfo, + InfallibleTArray&& aCodecSpecificInfo, const int64_t& aRenderTimeMs) MOZ_OVERRIDE; - virtual bool RecvChildShmemForPool(Shmem& aFrameBuffer) MOZ_OVERRIDE; + virtual bool RecvChildShmemForPool(Shmem&& aFrameBuffer) MOZ_OVERRIDE; virtual bool RecvReset() MOZ_OVERRIDE; virtual bool RecvDrain() MOZ_OVERRIDE; virtual bool RecvDecodingComplete() MOZ_OVERRIDE; diff --git a/dom/media/gmp/GMPVideoDecoderParent.cpp b/dom/media/gmp/GMPVideoDecoderParent.cpp index 27284a7f20f..4c26bfe508a 100644 --- a/dom/media/gmp/GMPVideoDecoderParent.cpp +++ b/dom/media/gmp/GMPVideoDecoderParent.cpp @@ -322,7 +322,7 @@ GMPVideoDecoderParent::RecvError(const GMPErr& aError) } bool -GMPVideoDecoderParent::RecvParentShmemForPool(Shmem& aEncodedBuffer) +GMPVideoDecoderParent::RecvParentShmemForPool(Shmem&& aEncodedBuffer) { if (aEncodedBuffer.IsWritable()) { mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData, diff --git a/dom/media/gmp/GMPVideoDecoderParent.h b/dom/media/gmp/GMPVideoDecoderParent.h index 36f3b2bc349..533c99cf0d4 100644 --- a/dom/media/gmp/GMPVideoDecoderParent.h +++ b/dom/media/gmp/GMPVideoDecoderParent.h @@ -71,7 +71,7 @@ private: virtual bool RecvDrainComplete() MOZ_OVERRIDE; virtual bool RecvResetComplete() MOZ_OVERRIDE; virtual bool RecvError(const GMPErr& aError) MOZ_OVERRIDE; - virtual bool RecvParentShmemForPool(Shmem& aEncodedBuffer) MOZ_OVERRIDE; + virtual bool RecvParentShmemForPool(Shmem&& aEncodedBuffer) MOZ_OVERRIDE; virtual bool AnswerNeedShmem(const uint32_t& aFrameBufferSize, Shmem* aMem) MOZ_OVERRIDE; virtual bool Recv__delete__() MOZ_OVERRIDE; diff --git a/dom/media/gmp/GMPVideoEncoderChild.cpp b/dom/media/gmp/GMPVideoEncoderChild.cpp index 446e4626378..14df35e28e4 100644 --- a/dom/media/gmp/GMPVideoEncoderChild.cpp +++ b/dom/media/gmp/GMPVideoEncoderChild.cpp @@ -68,7 +68,7 @@ GMPVideoEncoderChild::Error(GMPErr aError) bool GMPVideoEncoderChild::RecvInitEncode(const GMPVideoCodec& aCodecSettings, - const nsTArray& aCodecSpecific, + InfallibleTArray&& aCodecSpecific, const int32_t& aNumberOfCores, const uint32_t& aMaxPayloadSize) { @@ -89,8 +89,8 @@ GMPVideoEncoderChild::RecvInitEncode(const GMPVideoCodec& aCodecSettings, bool GMPVideoEncoderChild::RecvEncode(const GMPVideoi420FrameData& aInputFrame, - const nsTArray& aCodecSpecificInfo, - const nsTArray& aFrameTypes) + InfallibleTArray&& aCodecSpecificInfo, + InfallibleTArray&& aFrameTypes) { if (!mVideoEncoder) { return false; @@ -109,7 +109,7 @@ GMPVideoEncoderChild::RecvEncode(const GMPVideoi420FrameData& aInputFrame, } bool -GMPVideoEncoderChild::RecvChildShmemForPool(Shmem& aEncodedBuffer) +GMPVideoEncoderChild::RecvChildShmemForPool(Shmem&& aEncodedBuffer) { if (aEncodedBuffer.IsWritable()) { mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPEncodedData, diff --git a/dom/media/gmp/GMPVideoEncoderChild.h b/dom/media/gmp/GMPVideoEncoderChild.h index 70b71e05146..921e42f1ec6 100644 --- a/dom/media/gmp/GMPVideoEncoderChild.h +++ b/dom/media/gmp/GMPVideoEncoderChild.h @@ -59,13 +59,13 @@ public: private: // PGMPVideoEncoderChild virtual bool RecvInitEncode(const GMPVideoCodec& aCodecSettings, - const nsTArray& aCodecSpecific, + InfallibleTArray&& aCodecSpecific, const int32_t& aNumberOfCores, const uint32_t& aMaxPayloadSize) MOZ_OVERRIDE; virtual bool RecvEncode(const GMPVideoi420FrameData& aInputFrame, - const nsTArray& aCodecSpecificInfo, - const nsTArray& aFrameTypes) MOZ_OVERRIDE; - virtual bool RecvChildShmemForPool(Shmem& aEncodedBuffer) MOZ_OVERRIDE; + InfallibleTArray&& aCodecSpecificInfo, + InfallibleTArray&& aFrameTypes) MOZ_OVERRIDE; + virtual bool RecvChildShmemForPool(Shmem&& aEncodedBuffer) MOZ_OVERRIDE; virtual bool RecvSetChannelParameters(const uint32_t& aPacketLoss, const uint32_t& aRTT) MOZ_OVERRIDE; virtual bool RecvSetRates(const uint32_t& aNewBitRate, diff --git a/dom/media/gmp/GMPVideoEncoderParent.cpp b/dom/media/gmp/GMPVideoEncoderParent.cpp index 44dbd5e1d30..3882bcaa512 100644 --- a/dom/media/gmp/GMPVideoEncoderParent.cpp +++ b/dom/media/gmp/GMPVideoEncoderParent.cpp @@ -288,7 +288,7 @@ EncodedCallback(GMPVideoEncoderCallbackProxy* aCallback, bool GMPVideoEncoderParent::RecvEncoded(const GMPVideoEncodedFrameData& aEncodedFrame, - const nsTArray& aCodecSpecificInfo) + InfallibleTArray&& aCodecSpecificInfo) { if (!mCallback) { return false; @@ -320,7 +320,7 @@ GMPVideoEncoderParent::RecvError(const GMPErr& aError) } bool -GMPVideoEncoderParent::RecvParentShmemForPool(Shmem& aFrameBuffer) +GMPVideoEncoderParent::RecvParentShmemForPool(Shmem&& aFrameBuffer) { if (aFrameBuffer.IsWritable()) { mVideoHost.SharedMemMgr()->MgrDeallocShmem(GMPSharedMem::kGMPFrameData, diff --git a/dom/media/gmp/GMPVideoEncoderParent.h b/dom/media/gmp/GMPVideoEncoderParent.h index aaec972bf80..dcada4d865d 100644 --- a/dom/media/gmp/GMPVideoEncoderParent.h +++ b/dom/media/gmp/GMPVideoEncoderParent.h @@ -66,9 +66,9 @@ private: // PGMPVideoEncoderParent virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE; virtual bool RecvEncoded(const GMPVideoEncodedFrameData& aEncodedFrame, - const nsTArray& aCodecSpecificInfo) MOZ_OVERRIDE; + InfallibleTArray&& aCodecSpecificInfo) MOZ_OVERRIDE; virtual bool RecvError(const GMPErr& aError) MOZ_OVERRIDE; - virtual bool RecvParentShmemForPool(Shmem& aFrameBuffer) MOZ_OVERRIDE; + virtual bool RecvParentShmemForPool(Shmem&& aFrameBuffer) MOZ_OVERRIDE; virtual bool AnswerNeedShmem(const uint32_t& aEncodedBufferSize, Shmem* aMem) MOZ_OVERRIDE; virtual bool Recv__delete__() MOZ_OVERRIDE; diff --git a/dom/media/gstreamer/GStreamerReader.cpp b/dom/media/gstreamer/GStreamerReader.cpp index bcfa1c35a2d..7348d89c9a5 100644 --- a/dom/media/gstreamer/GStreamerReader.cpp +++ b/dom/media/gstreamer/GStreamerReader.cpp @@ -789,10 +789,7 @@ bool GStreamerReader::DecodeVideoFrame(bool &aKeyFrameSkip, } nsRefPtr -GStreamerReader::Seek(int64_t aTarget, - int64_t aStartTime, - int64_t aEndTime, - int64_t aCurrentTime) +GStreamerReader::Seek(int64_t aTarget, int64_t aEndTime) { NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); diff --git a/dom/media/gstreamer/GStreamerReader.h b/dom/media/gstreamer/GStreamerReader.h index 5c81b13f16d..097a00658f8 100644 --- a/dom/media/gstreamer/GStreamerReader.h +++ b/dom/media/gstreamer/GStreamerReader.h @@ -49,10 +49,7 @@ public: virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags); virtual nsRefPtr - Seek(int64_t aTime, - int64_t aStartTime, - int64_t aEndTime, - int64_t aCurrentTime) MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; virtual nsresult GetBuffered(dom::TimeRanges* aBuffered); virtual void NotifyDataArrived(const char *aBuffer, diff --git a/dom/media/mediasource/MediaSource.cpp b/dom/media/mediasource/MediaSource.cpp index 51e51d25c36..8f5eef6dfd9 100644 --- a/dom/media/mediasource/MediaSource.cpp +++ b/dom/media/mediasource/MediaSource.cpp @@ -189,15 +189,15 @@ MediaSource::SetDuration(double aDuration, ErrorResult& aRv) aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } - SetDuration(aDuration); + SetDuration(aDuration, MSRangeRemovalAction::RUN); } void -MediaSource::SetDuration(double aDuration) +MediaSource::SetDuration(double aDuration, MSRangeRemovalAction aAction) { MOZ_ASSERT(NS_IsMainThread()); MSE_API("MediaSource(%p)::SetDuration(aDuration=%f)", this, aDuration); - mDecoder->SetMediaSourceDuration(aDuration); + mDecoder->SetMediaSourceDuration(aDuration, aAction); } already_AddRefed @@ -285,7 +285,8 @@ MediaSource::EndOfStream(const Optional& aError, Er mSourceBuffers->Ended(); mDecoder->Ended(); if (!aError.WasPassed()) { - mDecoder->SetMediaSourceDuration(mSourceBuffers->GetHighestBufferedEndTime()); + mDecoder->SetMediaSourceDuration(mSourceBuffers->GetHighestBufferedEndTime(), + MSRangeRemovalAction::SKIP); if (aRv.Failed()) { return; } diff --git a/dom/media/mediasource/MediaSource.h b/dom/media/mediasource/MediaSource.h index 7011a2c6cb1..c6ddcaf7779 100644 --- a/dom/media/mediasource/MediaSource.h +++ b/dom/media/mediasource/MediaSource.h @@ -29,6 +29,11 @@ namespace mozilla { class ErrorResult; template class AsyncEventRunner; +enum MSRangeRemovalAction: uint8_t { + RUN = 0, + SKIP = 1 +}; + namespace dom { class GlobalObject; @@ -129,7 +134,7 @@ private: void InitializationEvent(); // SetDuration with no checks. - void SetDuration(double aDuration); + void SetDuration(double aDuration, MSRangeRemovalAction aAction); nsRefPtr mSourceBuffers; nsRefPtr mActiveSourceBuffers; diff --git a/dom/media/mediasource/MediaSourceDecoder.cpp b/dom/media/mediasource/MediaSourceDecoder.cpp index 196dcb1ad20..c35b1f35e74 100644 --- a/dom/media/mediasource/MediaSourceDecoder.cpp +++ b/dom/media/mediasource/MediaSourceDecoder.cpp @@ -173,9 +173,9 @@ MediaSourceDecoder::IsExpectingMoreData() class DurationChangedRunnable : public nsRunnable { public: - explicit DurationChangedRunnable(MediaSourceDecoder* aDecoder, - double aOldDuration, - double aNewDuration) + DurationChangedRunnable(MediaSourceDecoder* aDecoder, + double aOldDuration, + double aNewDuration) : mDecoder(aDecoder) , mOldDuration(aOldDuration) , mNewDuration(aNewDuration) @@ -214,23 +214,55 @@ MediaSourceDecoder::SetDecodedDuration(int64_t aDuration) return; } double duration = aDuration; - duration /= USECS_PER_S; - SetMediaSourceDuration(duration); + // A duration of -1 is +Infinity. + if (aDuration >= 0) { + duration /= USECS_PER_S; + } + DoSetMediaSourceDuration(duration); } void -MediaSourceDecoder::SetMediaSourceDuration(double aDuration) +MediaSourceDecoder::SetMediaSourceDuration(double aDuration, MSRangeRemovalAction aAction) { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); double oldDuration = mMediaSourceDuration; - mMediaSourceDuration = aDuration; - mDecoderStateMachine->SetDuration(aDuration * USECS_PER_S); - if (NS_IsMainThread()) { - DurationChanged(oldDuration, aDuration); + DoSetMediaSourceDuration(aDuration); + ScheduleDurationChange(oldDuration, aDuration, aAction); +} + +void +MediaSourceDecoder::DoSetMediaSourceDuration(double aDuration) +{ + if (aDuration >= 0) { + mDecoderStateMachine->SetDuration(aDuration * USECS_PER_S); + mMediaSourceDuration = aDuration; } else { - nsRefPtr task = - new DurationChangedRunnable(this, oldDuration, aDuration); - NS_DispatchToMainThread(task); + mDecoderStateMachine->SetDuration(INT64_MAX); + mMediaSourceDuration = PositiveInfinity(); + } +} + +void +MediaSourceDecoder::ScheduleDurationChange(double aOldDuration, + double aNewDuration, + MSRangeRemovalAction aAction) +{ + if (aAction == MSRangeRemovalAction::SKIP) { + if (NS_IsMainThread()) { + MediaDecoder::DurationChanged(); + } else { + nsCOMPtr task = + NS_NewRunnableMethod(this, &MediaDecoder::DurationChanged); + NS_DispatchToMainThread(task); + } + } else { + if (NS_IsMainThread()) { + DurationChanged(aOldDuration, aNewDuration); + } else { + nsRefPtr task = + new DurationChangedRunnable(this, aOldDuration, aNewDuration); + NS_DispatchToMainThread(task); + } } } diff --git a/dom/media/mediasource/MediaSourceDecoder.h b/dom/media/mediasource/MediaSourceDecoder.h index 6974698c5d8..6ac68a54c67 100644 --- a/dom/media/mediasource/MediaSourceDecoder.h +++ b/dom/media/mediasource/MediaSourceDecoder.h @@ -21,6 +21,7 @@ class MediaResource; class MediaDecoderStateMachine; class SourceBufferDecoder; class TrackBuffer; +enum MSRangeRemovalAction : uint8_t; namespace dom { @@ -56,7 +57,7 @@ public: bool IsExpectingMoreData() MOZ_OVERRIDE; void SetDecodedDuration(int64_t aDuration); - void SetMediaSourceDuration(double aDuration); + void SetMediaSourceDuration(double aDuration, MSRangeRemovalAction aAction); double GetMediaSourceDuration(); void DurationChanged(double aOldDuration, double aNewDuration); @@ -79,6 +80,11 @@ public: bool IsActiveReader(MediaDecoderReader* aReader); private: + void DoSetMediaSourceDuration(double aDuration); + void ScheduleDurationChange(double aOldDuration, + double aNewDuration, + MSRangeRemovalAction aAction); + // The owning MediaSource holds a strong reference to this decoder, and // calls Attach/DetachMediaSource on this decoder to set and clear // mMediaSource. diff --git a/dom/media/mediasource/MediaSourceReader.cpp b/dom/media/mediasource/MediaSourceReader.cpp index 294d33f61ab..fd4c2f1e176 100644 --- a/dom/media/mediasource/MediaSourceReader.cpp +++ b/dom/media/mediasource/MediaSourceReader.cpp @@ -49,16 +49,13 @@ MediaSourceReader::MediaSourceReader(MediaSourceDecoder* aDecoder) , mLastAudioTime(0) , mLastVideoTime(0) , mPendingSeekTime(-1) - , mPendingStartTime(-1) - , mPendingEndTime(-1) - , mPendingCurrentTime(-1) , mWaitingForSeekData(false) + , mAudioIsSeeking(false) + , mVideoIsSeeking(false) , mTimeThreshold(-1) , mDropAudioBeforeThreshold(false) , mDropVideoBeforeThreshold(false) , mEnded(false) - , mAudioIsSeeking(false) - , mVideoIsSeeking(false) , mHasEssentialTrackBuffers(false) #ifdef MOZ_FMP4 , mSharedDecoderManager(new SharedDecoderManager()) @@ -121,9 +118,13 @@ MediaSourceReader::RequestAudioData() mAudioPromise.Reject(DECODE_ERROR, __func__); return p; } - mAudioIsSeeking = false; + if (mAudioIsSeeking) { + MSE_DEBUG("MediaSourceReader(%p)::RequestAudioData called mid-seek. Rejecting.", this); + mAudioPromise.Reject(CANCELED, __func__); + return p; + } if (SwitchAudioReader(mLastAudioTime)) { - mAudioReader->Seek(mLastAudioTime, 0, 0, 0) + mAudioReader->Seek(mLastAudioTime, 0) ->Then(GetTaskQueue(), __func__, this, &MediaSourceReader::RequestAudioDataComplete, &MediaSourceReader::RequestAudioDataFailed); @@ -220,7 +221,7 @@ MediaSourceReader::OnAudioNotDecoded(NotDecodedReason aReason) // EOS_FUZZ_US to allow for the fact that our end time can be inaccurate due to bug // 1065207. if (SwitchAudioReader(mLastAudioTime, EOS_FUZZ_US)) { - mAudioReader->Seek(mLastAudioTime, 0, 0, 0) + mAudioReader->Seek(mLastAudioTime, 0) ->Then(GetTaskQueue(), __func__, this, &MediaSourceReader::RequestAudioDataComplete, &MediaSourceReader::RequestAudioDataFailed); @@ -255,9 +256,13 @@ MediaSourceReader::RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThres mDropAudioBeforeThreshold = true; mDropVideoBeforeThreshold = true; } - mVideoIsSeeking = false; + if (mVideoIsSeeking) { + MSE_DEBUG("MediaSourceReader(%p)::RequestVideoData called mid-seek. Rejecting.", this); + mVideoPromise.Reject(CANCELED, __func__); + return p; + } if (SwitchVideoReader(mLastVideoTime)) { - mVideoReader->Seek(mLastVideoTime, 0, 0, 0) + mVideoReader->Seek(mLastVideoTime, 0) ->Then(GetTaskQueue(), __func__, this, &MediaSourceReader::RequestVideoDataComplete, &MediaSourceReader::RequestVideoDataFailed); @@ -331,7 +336,7 @@ MediaSourceReader::OnVideoNotDecoded(NotDecodedReason aReason) // EOS_FUZZ_US to allow for the fact that our end time can be inaccurate due to bug // 1065207. if (SwitchVideoReader(mLastVideoTime, EOS_FUZZ_US)) { - mVideoReader->Seek(mLastVideoTime, 0, 0, 0) + mVideoReader->Seek(mLastVideoTime, 0) ->Then(GetTaskQueue(), __func__, this, &MediaSourceReader::RequestVideoDataComplete, &MediaSourceReader::RequestVideoDataFailed); @@ -625,13 +630,12 @@ MediaSourceReader::NotifyTimeRangesChanged() } nsRefPtr -MediaSourceReader::Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, - int64_t aCurrentTime) +MediaSourceReader::Seek(int64_t aTime, int64_t aIgnored /* Used only for ogg which is non-MSE */) { - MSE_DEBUG("MediaSourceReader(%p)::Seek(aTime=%lld, aStart=%lld, aEnd=%lld, aCurrent=%lld)", - this, aTime, aStartTime, aEndTime, aCurrentTime); + MSE_DEBUG("MediaSourceReader(%p)::Seek(aTime=%lld, aEnd=%lld, aCurrent=%lld)", + this, aTime); - mSeekPromise.RejectIfExists(NS_OK, __func__); + MOZ_ASSERT(mSeekPromise.IsEmpty()); nsRefPtr p = mSeekPromise.Ensure(__func__); if (IsShutdown()) { @@ -642,13 +646,7 @@ MediaSourceReader::Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, // Store pending seek target in case the track buffers don't contain // the desired time and we delay doing the seek. mPendingSeekTime = aTime; - mPendingStartTime = aStartTime; - mPendingEndTime = aEndTime; - mPendingCurrentTime = aCurrentTime; - // Only increment the number of expected OnSeekCompleted - // notifications if we weren't already waiting for AttemptSeek - // to complete (and they would have been accounted for already). { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); mWaitingForSeekData = true; @@ -658,17 +656,38 @@ MediaSourceReader::Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, return p; } +void +MediaSourceReader::CancelSeek() +{ + MOZ_ASSERT(OnDecodeThread()); + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + if (mWaitingForSeekData) { + mSeekPromise.Reject(NS_OK, __func__); + mWaitingForSeekData = false; + mPendingSeekTime = -1; + } else if (mVideoIsSeeking) { + // NB: Currently all readers have sync Seeks(), so this is a no-op. + mVideoReader->CancelSeek(); + } else if (mAudioIsSeeking) { + // NB: Currently all readers have sync Seeks(), so this is a no-op. + mAudioReader->CancelSeek(); + } else { + MOZ_ASSERT(mSeekPromise.IsEmpty()); + } +} + void MediaSourceReader::OnVideoSeekCompleted(int64_t aTime) { mPendingSeekTime = aTime; + MOZ_ASSERT(mVideoIsSeeking); + MOZ_ASSERT(!mAudioIsSeeking); + mVideoIsSeeking = false; + if (mAudioTrack) { mAudioIsSeeking = true; SwitchAudioReader(mPendingSeekTime); - mAudioReader->Seek(mPendingSeekTime, - mPendingStartTime, - mPendingEndTime, - mPendingCurrentTime) + mAudioReader->Seek(mPendingSeekTime, 0) ->Then(GetTaskQueue(), __func__, this, &MediaSourceReader::OnAudioSeekCompleted, &MediaSourceReader::OnSeekFailed); @@ -681,18 +700,32 @@ MediaSourceReader::OnVideoSeekCompleted(int64_t aTime) void MediaSourceReader::OnAudioSeekCompleted(int64_t aTime) { + mPendingSeekTime = aTime; + MOZ_ASSERT(mAudioIsSeeking); + MOZ_ASSERT(!mVideoIsSeeking); + mAudioIsSeeking = false; + mSeekPromise.Resolve(mPendingSeekTime, __func__); + mPendingSeekTime = -1; } void MediaSourceReader::OnSeekFailed(nsresult aResult) { - // Keep the most recent failed result (if any) - if (NS_FAILED(aResult)) { - mSeekPromise.Reject(aResult, __func__); - return; + MOZ_ASSERT(mVideoIsSeeking || mAudioIsSeeking); + + if (mVideoIsSeeking) { + MOZ_ASSERT(!mAudioIsSeeking); + mVideoIsSeeking = false; } - mSeekPromise.Resolve(mPendingSeekTime, __func__); + + if (mAudioIsSeeking) { + MOZ_ASSERT(!mVideoIsSeeking); + mAudioIsSeeking = false; + } + + mPendingSeekTime = -1; + mSeekPromise.Reject(aResult, __func__); } void @@ -705,6 +738,7 @@ MediaSourceReader::AttemptSeek() if (!mWaitingForSeekData || !TrackBuffersContainTime(mPendingSeekTime)) { return; } + mWaitingForSeekData = false; } ResetDecode(); @@ -719,10 +753,7 @@ MediaSourceReader::AttemptSeek() if (mVideoTrack) { mVideoIsSeeking = true; SwitchVideoReader(mPendingSeekTime); - mVideoReader->Seek(mPendingSeekTime, - mPendingStartTime, - mPendingEndTime, - mPendingCurrentTime) + mVideoReader->Seek(mPendingSeekTime, 0) ->Then(GetTaskQueue(), __func__, this, &MediaSourceReader::OnVideoSeekCompleted, &MediaSourceReader::OnSeekFailed); @@ -730,11 +761,6 @@ MediaSourceReader::AttemptSeek() } else { OnVideoSeekCompleted(mPendingSeekTime); } - - { - ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); - mWaitingForSeekData = false; - } } nsresult @@ -845,9 +871,11 @@ MediaSourceReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) this, mVideoReader.get(), maxDuration); } - if (maxDuration != -1) { - static_cast(mDecoder)->SetDecodedDuration(maxDuration); + if (!maxDuration) { + // Treat a duration of 0 as infinity + maxDuration = -1; } + static_cast(mDecoder)->SetDecodedDuration(maxDuration); *aInfo = mInfo; *aTags = nullptr; // TODO: Handle metadata. diff --git a/dom/media/mediasource/MediaSourceReader.h b/dom/media/mediasource/MediaSourceReader.h index 4e119c29835..c6ccc443a1a 100644 --- a/dom/media/mediasource/MediaSourceReader.h +++ b/dom/media/mediasource/MediaSourceReader.h @@ -97,8 +97,9 @@ public: nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) MOZ_OVERRIDE; void ReadUpdatedMetadata(MediaInfo* aInfo) MOZ_OVERRIDE; nsRefPtr - Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, - int64_t aCurrentTime) MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; + + void CancelSeek() MOZ_OVERRIDE; // Acquires the decoder monitor, and is thus callable on any thread. nsresult GetBuffered(dom::TimeRanges* aBuffered) MOZ_OVERRIDE; @@ -192,10 +193,9 @@ private: // to be added to the track buffer. MediaPromiseHolder mSeekPromise; int64_t mPendingSeekTime; - int64_t mPendingStartTime; - int64_t mPendingEndTime; - int64_t mPendingCurrentTime; bool mWaitingForSeekData; + bool mAudioIsSeeking; + bool mVideoIsSeeking; int64_t mTimeThreshold; bool mDropAudioBeforeThreshold; @@ -203,14 +203,6 @@ private: bool mEnded; - // For a seek to complete we need to send a sample with - // the mDiscontinuity field set to true once we have the - // first decoded sample. These flags are set during seeking - // so we can detect when we have the first decoded sample - // after a seek. - bool mAudioIsSeeking; - bool mVideoIsSeeking; - bool mHasEssentialTrackBuffers; void ContinueShutdown(); diff --git a/dom/media/mediasource/SourceBuffer.cpp b/dom/media/mediasource/SourceBuffer.cpp index 10aa327c231..953a57de16e 100644 --- a/dom/media/mediasource/SourceBuffer.cpp +++ b/dom/media/mediasource/SourceBuffer.cpp @@ -233,7 +233,7 @@ SourceBuffer::Ended() MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(IsAttached()); MSE_DEBUG("SourceBuffer(%p)::Ended", this); - mTrackBuffer->DiscardDecoder(); + mTrackBuffer->EndCurrentDecoder(); } SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType) @@ -332,7 +332,7 @@ SourceBuffer::CheckEndTime() // Check if we need to update mMediaSource duration double endTime = GetBufferedEnd(); if (endTime > mMediaSource->Duration()) { - mMediaSource->SetDuration(endTime); + mMediaSource->SetDuration(endTime, MSRangeRemovalAction::SKIP); } } @@ -347,19 +347,19 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR MOZ_ASSERT(mAppendMode == SourceBufferAppendMode::Segments, "We don't handle timestampOffset for sequence mode yet"); - if (!mTrackBuffer->AppendData(aData, aLength, mTimestampOffset * USECS_PER_S)) { - Optional decodeError(MediaSourceEndOfStreamError::Decode); - ErrorResult dummy; - mMediaSource->EndOfStream(decodeError, dummy); - aRv.Throw(NS_ERROR_FAILURE); - return; - } + if (aLength) { + if (!mTrackBuffer->AppendData(aData, aLength, mTimestampOffset * USECS_PER_S)) { + nsCOMPtr event = NS_NewRunnableMethodWithArg(this, &SourceBuffer::AppendError, true); + NS_DispatchToMainThread(event); + return; + } - if (mTrackBuffer->HasInitSegment()) { - mMediaSource->QueueInitializationEvent(); - } + if (mTrackBuffer->HasInitSegment()) { + mMediaSource->QueueInitializationEvent(); + } - CheckEndTime(); + CheckEndTime(); + } // Run the final step of the buffer append algorithm asynchronously to // ensure the SourceBuffer's updating flag transition behaves as required @@ -368,6 +368,29 @@ SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aR NS_DispatchToMainThread(event); } +void +SourceBuffer::AppendError(bool aDecoderError) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (!mUpdating) { + // The buffer append algorithm has been interrupted by abort(). + return; + } + mTrackBuffer->ResetParserState(); + + mUpdating = false; + + QueueAsyncSimpleEvent("error"); + QueueAsyncSimpleEvent("updateend"); + + if (aDecoderError) { + Optional decodeError( + MediaSourceEndOfStreamError::Decode); + ErrorResult dummy; + mMediaSource->EndOfStream(decodeError, dummy); + } +} + bool SourceBuffer::PrepareAppend(ErrorResult& aRv) { diff --git a/dom/media/mediasource/SourceBuffer.h b/dom/media/mediasource/SourceBuffer.h index a12375a4eea..204bf39afa8 100644 --- a/dom/media/mediasource/SourceBuffer.h +++ b/dom/media/mediasource/SourceBuffer.h @@ -135,6 +135,12 @@ private: // Shared implementation of AppendBuffer overloads. void AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv); + // Implement the "Append Error Algorithm". + // Will call endOfStream() with "decode" error if aDecodeError is true. + // 3.5.3 Append Error Algorithm + // http://w3c.github.io/media-source/#sourcebuffer-append-error + void AppendError(bool aDecoderError); + // Implements the "Prepare Append Algorithm". Returns true if the append // may continue, or false (with aRv set) on error. bool PrepareAppend(ErrorResult& aRv); diff --git a/dom/media/mediasource/SourceBufferDecoder.cpp b/dom/media/mediasource/SourceBufferDecoder.cpp index 5a727dfaa4d..6c3fdecceba 100644 --- a/dom/media/mediasource/SourceBufferDecoder.cpp +++ b/dom/media/mediasource/SourceBufferDecoder.cpp @@ -93,13 +93,15 @@ SourceBufferDecoder::IsMediaSeekable() void SourceBufferDecoder::MetadataLoaded(nsAutoPtr aInfo, - nsAutoPtr aTags) + nsAutoPtr aTags, + bool aRestoredFromDromant) { MSE_DEBUG("SourceBufferDecoder(%p)::MetadataLoaded UNIMPLEMENTED", this); } void -SourceBufferDecoder::FirstFrameLoaded(nsAutoPtr aInfo) +SourceBufferDecoder::FirstFrameLoaded(nsAutoPtr aInfo, + bool aRestoredFromDromant) { MSE_DEBUG("SourceBufferDecoder(%p)::FirstFrameLoaded UNIMPLEMENTED", this); } diff --git a/dom/media/mediasource/SourceBufferDecoder.h b/dom/media/mediasource/SourceBufferDecoder.h index 6931298ff52..53fdf800179 100644 --- a/dom/media/mediasource/SourceBufferDecoder.h +++ b/dom/media/mediasource/SourceBufferDecoder.h @@ -49,8 +49,8 @@ public: virtual SourceBufferResource* GetResource() const MOZ_FINAL MOZ_OVERRIDE; virtual ReentrantMonitor& GetReentrantMonitor() MOZ_FINAL MOZ_OVERRIDE; virtual VideoFrameContainer* GetVideoFrameContainer() MOZ_FINAL MOZ_OVERRIDE; - virtual void MetadataLoaded(nsAutoPtr aInfo, nsAutoPtr aTags) MOZ_FINAL MOZ_OVERRIDE; - virtual void FirstFrameLoaded(nsAutoPtr aInfo) MOZ_FINAL MOZ_OVERRIDE; + virtual void MetadataLoaded(nsAutoPtr aInfo, nsAutoPtr aTags, bool aRestoredFromDromant) MOZ_FINAL MOZ_OVERRIDE; + virtual void FirstFrameLoaded(nsAutoPtr aInfo, bool aRestoredFromDromant) MOZ_FINAL MOZ_OVERRIDE; virtual void NotifyBytesConsumed(int64_t aBytes, int64_t aOffset) MOZ_FINAL MOZ_OVERRIDE; virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) MOZ_FINAL MOZ_OVERRIDE; virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded) MOZ_FINAL MOZ_OVERRIDE; diff --git a/dom/media/mediasource/SourceBufferResource.cpp b/dom/media/mediasource/SourceBufferResource.cpp index b451d1ea2ca..d1eb47e3e68 100644 --- a/dom/media/mediasource/SourceBufferResource.cpp +++ b/dom/media/mediasource/SourceBufferResource.cpp @@ -205,6 +205,7 @@ SourceBufferResource::AppendData(const uint8_t* aData, uint32_t aLength) SBR_DEBUG("SourceBufferResource(%p)::AppendData(aData=%p, aLength=%u)", this, aData, aLength); ReentrantMonitorAutoEnter mon(mMonitor); mInputBuffer.AppendItem(aData, aLength); + mEnded = false; mon.NotifyAll(); } diff --git a/dom/media/mediasource/TrackBuffer.cpp b/dom/media/mediasource/TrackBuffer.cpp index 56a8b5e610a..980da0ed638 100644 --- a/dom/media/mediasource/TrackBuffer.cpp +++ b/dom/media/mediasource/TrackBuffer.cpp @@ -548,6 +548,15 @@ TrackBuffer::DiscardDecoder() mCurrentDecoder = nullptr; } +void +TrackBuffer::EndCurrentDecoder() +{ + ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); + if (mCurrentDecoder) { + mCurrentDecoder->GetResource()->Ended(); + } +} + void TrackBuffer::Detach() { @@ -611,6 +620,12 @@ TrackBuffer::ResetDecode() } } +void +TrackBuffer::ResetParserState() +{ + // TODO +} + const nsTArray>& TrackBuffer::Decoders() { diff --git a/dom/media/mediasource/TrackBuffer.h b/dom/media/mediasource/TrackBuffer.h index ea3a18b2480..a577e17faed 100644 --- a/dom/media/mediasource/TrackBuffer.h +++ b/dom/media/mediasource/TrackBuffer.h @@ -61,6 +61,8 @@ public: // Mark the current decoder's resource as ended, clear mCurrentDecoder and // reset mLast{Start,End}Timestamp. void DiscardDecoder(); + // Mark the current decoder's resource as ended. + void EndCurrentDecoder(); void Detach(); @@ -80,6 +82,11 @@ public: // Call ResetDecode() on each decoder in mDecoders. void ResetDecode(); + // Run MSE Reset Parser State Algorithm. + // 3.5.2 Reset Parser State + // http://w3c.github.io/media-source/#sourcebuffer-reset-parser-state + void ResetParserState(); + // Returns a reference to mInitializedDecoders, used by MediaSourceReader // to select decoders. // TODO: Refactor to a cleaner interface between TrackBuffer and MediaSourceReader. diff --git a/dom/media/mediasource/test/bipbop/bipbop1.m4s b/dom/media/mediasource/test/bipbop/bipbop1.m4s new file mode 100644 index 00000000000..a237f2e91e4 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop1.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop10.m4s b/dom/media/mediasource/test/bipbop/bipbop10.m4s new file mode 100644 index 00000000000..d1f5e6a0b01 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop10.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop11.m4s b/dom/media/mediasource/test/bipbop/bipbop11.m4s new file mode 100644 index 00000000000..57232fb359c Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop11.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop12.m4s b/dom/media/mediasource/test/bipbop/bipbop12.m4s new file mode 100644 index 00000000000..f9b18713eeb Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop12.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop13.m4s b/dom/media/mediasource/test/bipbop/bipbop13.m4s new file mode 100644 index 00000000000..f2a876946c5 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop13.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop2.m4s b/dom/media/mediasource/test/bipbop/bipbop2.m4s new file mode 100644 index 00000000000..baa0d8578cf Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop2.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop3.m4s b/dom/media/mediasource/test/bipbop/bipbop3.m4s new file mode 100644 index 00000000000..ed313e668ce Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop3.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop4.m4s b/dom/media/mediasource/test/bipbop/bipbop4.m4s new file mode 100644 index 00000000000..7709ac08c55 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop4.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop5.m4s b/dom/media/mediasource/test/bipbop/bipbop5.m4s new file mode 100644 index 00000000000..6d36788e44d Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop5.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop6.m4s b/dom/media/mediasource/test/bipbop/bipbop6.m4s new file mode 100644 index 00000000000..64f475c7008 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop6.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop7.m4s b/dom/media/mediasource/test/bipbop/bipbop7.m4s new file mode 100644 index 00000000000..c148918d6de Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop7.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop8.m4s b/dom/media/mediasource/test/bipbop/bipbop8.m4s new file mode 100644 index 00000000000..707dd48485f Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop8.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop9.m4s b/dom/media/mediasource/test/bipbop/bipbop9.m4s new file mode 100644 index 00000000000..538cf72a4da Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop9.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_audio1.m4s b/dom/media/mediasource/test/bipbop/bipbop_audio1.m4s new file mode 100644 index 00000000000..33da98b5a9b Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_audio1.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_audio10.m4s b/dom/media/mediasource/test/bipbop/bipbop_audio10.m4s new file mode 100644 index 00000000000..36a98afd290 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_audio10.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_audio11.m4s b/dom/media/mediasource/test/bipbop/bipbop_audio11.m4s new file mode 100644 index 00000000000..23d4aa8d861 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_audio11.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_audio2.m4s b/dom/media/mediasource/test/bipbop/bipbop_audio2.m4s new file mode 100644 index 00000000000..96f4bcc3445 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_audio2.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_audio3.m4s b/dom/media/mediasource/test/bipbop/bipbop_audio3.m4s new file mode 100644 index 00000000000..7de4bd0ca12 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_audio3.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_audio4.m4s b/dom/media/mediasource/test/bipbop/bipbop_audio4.m4s new file mode 100644 index 00000000000..494c71eb926 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_audio4.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_audio5.m4s b/dom/media/mediasource/test/bipbop/bipbop_audio5.m4s new file mode 100644 index 00000000000..b50496b6ce1 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_audio5.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_audio6.m4s b/dom/media/mediasource/test/bipbop/bipbop_audio6.m4s new file mode 100644 index 00000000000..02cf4d363cb Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_audio6.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_audio7.m4s b/dom/media/mediasource/test/bipbop/bipbop_audio7.m4s new file mode 100644 index 00000000000..bb2252889ff Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_audio7.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_audio8.m4s b/dom/media/mediasource/test/bipbop/bipbop_audio8.m4s new file mode 100644 index 00000000000..04a6a7af916 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_audio8.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_audio9.m4s b/dom/media/mediasource/test/bipbop/bipbop_audio9.m4s new file mode 100644 index 00000000000..cb94b529a75 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_audio9.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_audioinit.mp4 b/dom/media/mediasource/test/bipbop/bipbop_audioinit.mp4 new file mode 100644 index 00000000000..bbf272197de Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_audioinit.mp4 differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_dash.mpd b/dom/media/mediasource/test/bipbop/bipbop_dash.mpd new file mode 100644 index 00000000000..532cdc65d58 --- /dev/null +++ b/dom/media/mediasource/test/bipbop/bipbop_dash.mpd @@ -0,0 +1,48 @@ + + + + bipbop_dash.mpd handcrafted by JYA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dom/media/mediasource/test/bipbop/bipbop_video1.m4s b/dom/media/mediasource/test/bipbop/bipbop_video1.m4s new file mode 100644 index 00000000000..92911825161 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_video1.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_video10.m4s b/dom/media/mediasource/test/bipbop/bipbop_video10.m4s new file mode 100644 index 00000000000..72c7afaca72 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_video10.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_video11.m4s b/dom/media/mediasource/test/bipbop/bipbop_video11.m4s new file mode 100644 index 00000000000..e6109f5e718 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_video11.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_video12.m4s b/dom/media/mediasource/test/bipbop/bipbop_video12.m4s new file mode 100644 index 00000000000..5c54a510f7d Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_video12.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_video13.m4s b/dom/media/mediasource/test/bipbop/bipbop_video13.m4s new file mode 100644 index 00000000000..c64f38a337f Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_video13.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_video2.m4s b/dom/media/mediasource/test/bipbop/bipbop_video2.m4s new file mode 100644 index 00000000000..cd34fae5618 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_video2.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_video3.m4s b/dom/media/mediasource/test/bipbop/bipbop_video3.m4s new file mode 100644 index 00000000000..5a133400438 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_video3.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_video4.m4s b/dom/media/mediasource/test/bipbop/bipbop_video4.m4s new file mode 100644 index 00000000000..e8d96b6ed18 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_video4.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_video5.m4s b/dom/media/mediasource/test/bipbop/bipbop_video5.m4s new file mode 100644 index 00000000000..ca6a8204681 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_video5.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_video6.m4s b/dom/media/mediasource/test/bipbop/bipbop_video6.m4s new file mode 100644 index 00000000000..fe9824355bb Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_video6.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_video7.m4s b/dom/media/mediasource/test/bipbop/bipbop_video7.m4s new file mode 100644 index 00000000000..3351fa68597 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_video7.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_video8.m4s b/dom/media/mediasource/test/bipbop/bipbop_video8.m4s new file mode 100644 index 00000000000..af26ae5f9ee Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_video8.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_video9.m4s b/dom/media/mediasource/test/bipbop/bipbop_video9.m4s new file mode 100644 index 00000000000..25be672c156 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_video9.m4s differ diff --git a/dom/media/mediasource/test/bipbop/bipbop_videoinit.mp4 b/dom/media/mediasource/test/bipbop/bipbop_videoinit.mp4 new file mode 100644 index 00000000000..7c9c533c363 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbop_videoinit.mp4 differ diff --git a/dom/media/mediasource/test/bipbop/bipbopinit.mp4 b/dom/media/mediasource/test/bipbop/bipbopinit.mp4 new file mode 100644 index 00000000000..39f0575a716 Binary files /dev/null and b/dom/media/mediasource/test/bipbop/bipbopinit.mp4 differ diff --git a/dom/media/mediasource/test/mediasource.js b/dom/media/mediasource/test/mediasource.js index 0efb3000e05..3afd1a266ff 100644 --- a/dom/media/mediasource/test/mediasource.js +++ b/dom/media/mediasource/test/mediasource.js @@ -26,12 +26,75 @@ function runWithMSE(testFunction) { } function fetchWithXHR(uri, onLoadFunction) { - var xhr = new XMLHttpRequest(); - xhr.open("GET", uri, true); - xhr.responseType = "arraybuffer"; - xhr.addEventListener("load", function () { - is(xhr.status, 200, "fetchWithXHR load uri='" + uri + "' status=" + xhr.status); - onLoadFunction(xhr.response); + var p = new Promise(function(resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", uri, true); + xhr.responseType = "arraybuffer"; + xhr.addEventListener("load", function () { + is(xhr.status, 200, "fetchWithXHR load uri='" + uri + "' status=" + xhr.status); + resolve(xhr.response); + }); + xhr.send(); }); - xhr.send(); + + if (onLoadFunction) { + p.then(onLoadFunction); + } + + return p; }; + +function once(target, name, cb) { + var p = new Promise(function(resolve, reject) { + target.addEventListener(name, function() { + target.removeEventListener(name, cb); + resolve(); + }); + }); + if (cb) { + p.then(cb); + } + return p; +} + +function timeRangeToString(r) { + var str = "TimeRanges: "; + for (var i = 0; i < r.length; i++) { + str += "[" + r.start(i) + ", " + r.end(i) + ")"; + } + return str; +} + +function loadSegment(sb, typedArrayOrArrayBuffer) { + var typedArray = (typedArrayOrArrayBuffer instanceof ArrayBuffer) ? new Uint8Array(typedArrayOrArrayBuffer) + : typedArrayOrArrayBuffer; + info(`Loading buffer: [${typedArray.byteOffset}, ${typedArray.byteOffset + typedArray.byteLength})`); + var beforeBuffered = timeRangeToString(sb.buffered); + return new Promise(function(resolve, reject) { + once(sb, 'update').then(function() { + var afterBuffered = timeRangeToString(sb.buffered); + info(`SourceBuffer buffered ranges grew from ${beforeBuffered} to ${afterBuffered}`); + resolve(); + }); + sb.appendBuffer(typedArray); + }); +} + +function fetchAndLoad(sb, prefix, chunks, suffix) { + + // Fetch the buffers in parallel. + var buffers = {}; + var fetches = []; + for (var chunk of chunks) { + fetches.push(fetchWithXHR(prefix + chunk + suffix).then(((c, x) => buffers[c] = x).bind(null, chunk))); + } + + // Load them in series, as required per spec. + return Promise.all(fetches).then(function() { + var rv = Promise.resolve(); + for (var chunk of chunks) { + rv = rv.then(loadSegment.bind(null, sb, buffers[chunk])); + } + return rv; + }); +} diff --git a/dom/media/mediasource/test/mochitest.ini b/dom/media/mediasource/test/mochitest.ini index f0dcfac5682..fc3409a98ef 100644 --- a/dom/media/mediasource/test/mochitest.ini +++ b/dom/media/mediasource/test/mochitest.ini @@ -4,6 +4,20 @@ support-files = mediasource.js seek.webm seek.webm^headers^ seek_lowres.webm seek_lowres.webm^headers^ + bipbop/bipbopinit.mp4 bipbop/bipbop_audioinit.mp4 bipbop/bipbop_videoinit.mp4 + bipbop/bipbop1.m4s bipbop/bipbop_audio1.m4s bipbop/bipbop_video1.m4s + bipbop/bipbop2.m4s bipbop/bipbop_audio2.m4s bipbop/bipbop_video2.m4s + bipbop/bipbop3.m4s bipbop/bipbop_audio3.m4s bipbop/bipbop_video3.m4s + bipbop/bipbop4.m4s bipbop/bipbop_audio4.m4s bipbop/bipbop_video4.m4s + bipbop/bipbop5.m4s bipbop/bipbop_audio5.m4s bipbop/bipbop_video5.m4s + bipbop/bipbop6.m4s bipbop/bipbop_audio6.m4s bipbop/bipbop_video6.m4s + bipbop/bipbop7.m4s bipbop/bipbop_audio7.m4s bipbop/bipbop_video7.m4s + bipbop/bipbop8.m4s bipbop/bipbop_audio8.m4s bipbop/bipbop_video8.m4s + bipbop/bipbop9.m4s bipbop/bipbop_audio9.m4s bipbop/bipbop_video9.m4s + bipbop/bipbop10.m4s bipbop/bipbop_audio10.m4s bipbop/bipbop_video10.m4s + bipbop/bipbop11.m4s bipbop/bipbop_audio11.m4s bipbop/bipbop_video11.m4s + bipbop/bipbop12.m4s bipbop/bipbop_video12.m4s + bipbop/bipbop13.m4s bipbop/bipbop_video13.m4s [test_BufferedSeek.html] [test_BufferingWait.html] @@ -20,6 +34,9 @@ skip-if = (toolkit == 'android' || buildapp == 'mulet') #timeout android/mulet o [test_SeekableAfterEndOfStreamSplit.html] [test_SeekableBeforeEndOfStream.html] [test_SeekableBeforeEndOfStreamSplit.html] +[test_SeekTwice_mp4.html] +#skip-if = os == 'linux' # No fmp4 support on linux +skip-if = true # Fails on most platforms and was pushed anyway. [test_SetModeThrows.html] [test_SplitAppendDelay.html] [test_SplitAppend.html] diff --git a/dom/media/mediasource/test/test_BufferingWait.html b/dom/media/mediasource/test/test_BufferingWait.html index f2bfc4ba94d..0d04985b996 100644 --- a/dom/media/mediasource/test/test_BufferingWait.html +++ b/dom/media/mediasource/test/test_BufferingWait.html @@ -20,20 +20,6 @@ runWithMSE(function(ms, v) { var sb = ms.addSourceBuffer("video/webm"); ok(sb, "Create a SourceBuffer"); - function once(target, name, cb) { - target.addEventListener(name, function() { - target.removeEventListener(name, cb); - cb(); - }); - } - function loadSegment(typedArray) { - info(`Loading buffer: [${typedArray.byteOffset}, ${typedArray.byteOffset + typedArray.byteLength})`); - return new Promise(function(resolve, reject) { - once(sb, 'update', resolve); - sb.appendBuffer(typedArray); - }); - } - function waitUntilTime(targetTime) { return new Promise(function(resolve, reject) { v.addEventListener("waiting", function onwaiting() { @@ -49,19 +35,19 @@ runWithMSE(function(ms, v) { fetchWithXHR("seek.webm", function(arrayBuffer) { sb.addEventListener('error', (e) => { ok(false, "Got Error: " + e); SimpleTest.finish(); }); - loadSegment.bind(null, new Uint8Array(arrayBuffer, 0, 318))().then( - loadSegment.bind(null, new Uint8Array(arrayBuffer, 318, 25223-318))).then( - loadSegment.bind(null, new Uint8Array(arrayBuffer, 25223, 46712-25223))).then( + loadSegment.bind(null, sb, new Uint8Array(arrayBuffer, 0, 318))().then( + loadSegment.bind(null, sb, new Uint8Array(arrayBuffer, 318, 25223-318))).then( + loadSegment.bind(null, sb, new Uint8Array(arrayBuffer, 25223, 46712-25223))).then( /* Note - Missing |46712, 67833 - 46712| segment here corresponding to (0.8, 1.2] */ /* Note - Missing |67833, 88966 - 67833| segment here corresponding to (1.2, 1.6] */ - loadSegment.bind(null, new Uint8Array(arrayBuffer, 88966))).then(function() { + loadSegment.bind(null, sb, new Uint8Array(arrayBuffer, 88966))).then(function() { var promise = waitUntilTime(0.7); info("Playing video. It should play for a bit, then fire 'waiting'"); v.play(); return promise; }).then(function() { window.firstStop = Date.now(); - loadSegment(new Uint8Array(arrayBuffer, 46712, 67833 - 46712)); + loadSegment(sb, new Uint8Array(arrayBuffer, 46712, 67833 - 46712)); return waitUntilTime(1.0); }).then(function() { var waitDuration = (Date.now() - window.firstStop) / 1000; @@ -70,7 +56,7 @@ runWithMSE(function(ms, v) { /* If we allow the rest of the stream to be played, we get stuck at around 2s. See bug 1093133. once(v, 'ended', SimpleTest.finish.bind(SimpleTest)); - return loadSegment(new Uint8Array(arrayBuffer, 67833, 88966 - 67833)); + return loadSegment(sb, new Uint8Array(arrayBuffer, 67833, 88966 - 67833)); */ }); }); diff --git a/dom/media/mediasource/test/test_SeekTwice_mp4.html b/dom/media/mediasource/test/test_SeekTwice_mp4.html new file mode 100644 index 00000000000..bfc5950bce4 --- /dev/null +++ b/dom/media/mediasource/test/test_SeekTwice_mp4.html @@ -0,0 +1,62 @@ + + + + MSE: basic functionality + + + + + +
+
+
+ + diff --git a/dom/media/ogg/OggReader.cpp b/dom/media/ogg/OggReader.cpp index 8ba37c017eb..6280e31d8b7 100644 --- a/dom/media/ogg/OggReader.cpp +++ b/dom/media/ogg/OggReader.cpp @@ -1425,12 +1425,9 @@ nsresult OggReader::SeekInUnbuffered(int64_t aTarget, } nsRefPtr -OggReader::Seek(int64_t aTarget, - int64_t aStartTime, - int64_t aEndTime, - int64_t aCurrentTime) +OggReader::Seek(int64_t aTarget, int64_t aEndTime) { - nsresult res = SeekInternal(aTarget, aStartTime, aEndTime, aCurrentTime); + nsresult res = SeekInternal(aTarget, aEndTime); if (NS_FAILED(res)) { return SeekPromise::CreateAndReject(res, __func__); } else { @@ -1438,10 +1435,7 @@ OggReader::Seek(int64_t aTarget, } } -nsresult OggReader::SeekInternal(int64_t aTarget, - int64_t aStartTime, - int64_t aEndTime, - int64_t aCurrentTime) +nsresult OggReader::SeekInternal(int64_t aTarget, int64_t aEndTime) { NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); if (mIsChained) @@ -1452,10 +1446,10 @@ nsresult OggReader::SeekInternal(int64_t aTarget, NS_ENSURE_TRUE(resource != nullptr, NS_ERROR_FAILURE); int64_t adjustedTarget = aTarget; if (HasAudio() && mOpusState){ - adjustedTarget = std::max(aStartTime, aTarget - SEEK_OPUS_PREROLL); + adjustedTarget = std::max(mStartTime, aTarget - SEEK_OPUS_PREROLL); } - if (adjustedTarget == aStartTime) { + if (adjustedTarget == mStartTime) { // We've seeked to the media start. Just seek to the offset of the first // content page. res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0); @@ -1464,10 +1458,10 @@ nsresult OggReader::SeekInternal(int64_t aTarget, res = ResetDecode(true); NS_ENSURE_SUCCESS(res,res); - NS_ASSERTION(aStartTime != -1, "mStartTime should be known"); + NS_ASSERTION(mStartTime != -1, "mStartTime should be known"); { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); - mDecoder->UpdatePlaybackPosition(aStartTime); + mDecoder->UpdatePlaybackPosition(mStartTime); } } else { // TODO: This may seek back unnecessarily far in the video, but we don't @@ -1484,18 +1478,18 @@ nsresult OggReader::SeekInternal(int64_t aTarget, NS_ENSURE_SUCCESS(res,res); // Figure out if the seek target lies in a buffered range. - SeekRange r = SelectSeekRange(ranges, aTarget, aStartTime, aEndTime, true); + SeekRange r = SelectSeekRange(ranges, aTarget, mStartTime, aEndTime, true); if (!r.IsNull()) { // We know the buffered range in which the seek target lies, do a // bisection search in that buffered range. - res = SeekInBufferedRange(aTarget, adjustedTarget, aStartTime, aEndTime, ranges, r); + res = SeekInBufferedRange(aTarget, adjustedTarget, mStartTime, aEndTime, ranges, r); NS_ENSURE_SUCCESS(res,res); } else { // The target doesn't lie in a buffered range. Perform a bisection // search over the whole media, using the known buffered ranges to // reduce the search space. - res = SeekInUnbuffered(aTarget, aStartTime, aEndTime, ranges); + res = SeekInUnbuffered(aTarget, mStartTime, aEndTime, ranges); NS_ENSURE_SUCCESS(res,res); } } diff --git a/dom/media/ogg/OggReader.h b/dom/media/ogg/OggReader.h index b50a8938774..93b511df0f9 100644 --- a/dom/media/ogg/OggReader.h +++ b/dom/media/ogg/OggReader.h @@ -76,7 +76,7 @@ public: virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) MOZ_OVERRIDE; virtual nsRefPtr - Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; virtual nsresult GetBuffered(dom::TimeRanges* aBuffered) MOZ_OVERRIDE; virtual bool IsMediaSeekable() MOZ_OVERRIDE; @@ -96,7 +96,7 @@ private: // to the start of the stream. nsresult ResetDecode(bool start); - nsresult SeekInternal(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime); + nsresult SeekInternal(int64_t aTime, int64_t aEndTime); bool HasSkeleton() { return mSkeletonState != 0 && mSkeletonState->mActive; diff --git a/dom/media/omx/MediaCodecReader.cpp b/dom/media/omx/MediaCodecReader.cpp index a8ddae14bbd..b0bdbac9248 100644 --- a/dom/media/omx/MediaCodecReader.cpp +++ b/dom/media/omx/MediaCodecReader.cpp @@ -995,10 +995,7 @@ MediaCodecReader::DecodeVideoFrameSync(int64_t aTimeThreshold) } nsRefPtr -MediaCodecReader::Seek(int64_t aTime, - int64_t aStartTime, - int64_t aEndTime, - int64_t aCurrentTime) +MediaCodecReader::Seek(int64_t aTime, int64_t aEndTime) { MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); diff --git a/dom/media/omx/MediaCodecReader.h b/dom/media/omx/MediaCodecReader.h index 6c64b16ba00..4ed9223ef06 100644 --- a/dom/media/omx/MediaCodecReader.h +++ b/dom/media/omx/MediaCodecReader.h @@ -101,10 +101,7 @@ public: // denote the start and end times of the media in usecs, and aCurrentTime // is the current playback position in microseconds. virtual nsRefPtr - Seek(int64_t aTime, - int64_t aStartTime, - int64_t aEndTime, - int64_t aCurrentTime) MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; virtual bool IsMediaSeekable() MOZ_OVERRIDE; diff --git a/dom/media/omx/MediaOmxCommonDecoder.cpp b/dom/media/omx/MediaOmxCommonDecoder.cpp index c25035591f8..a6b026ae923 100644 --- a/dom/media/omx/MediaOmxCommonDecoder.cpp +++ b/dom/media/omx/MediaOmxCommonDecoder.cpp @@ -57,10 +57,11 @@ MediaOmxCommonDecoder::CheckDecoderCanOffloadAudio() } void -MediaOmxCommonDecoder::FirstFrameLoaded(nsAutoPtr aInfo) +MediaOmxCommonDecoder::FirstFrameLoaded(nsAutoPtr aInfo, + bool aRestoredFromDromant) { MOZ_ASSERT(NS_IsMainThread()); - MediaDecoder::FirstFrameLoaded(aInfo); + MediaDecoder::FirstFrameLoaded(aInfo, aRestoredFromDromant); ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); if (!CheckDecoderCanOffloadAudio()) { diff --git a/dom/media/omx/MediaOmxCommonDecoder.h b/dom/media/omx/MediaOmxCommonDecoder.h index 637300aae24..7f965b6872f 100644 --- a/dom/media/omx/MediaOmxCommonDecoder.h +++ b/dom/media/omx/MediaOmxCommonDecoder.h @@ -23,7 +23,8 @@ class MediaOmxCommonDecoder : public MediaDecoder public: MediaOmxCommonDecoder(); - virtual void FirstFrameLoaded(nsAutoPtr aInfo); + virtual void FirstFrameLoaded(nsAutoPtr aInfo, + bool aRestoredFromDromant); virtual void ChangeState(PlayState aState); virtual void ApplyStateToStateMachine(PlayState aState); virtual void SetVolume(double aVolume); diff --git a/dom/media/omx/MediaOmxReader.cpp b/dom/media/omx/MediaOmxReader.cpp index 31e8bd656de..9db1366b8e4 100644 --- a/dom/media/omx/MediaOmxReader.cpp +++ b/dom/media/omx/MediaOmxReader.cpp @@ -546,7 +546,7 @@ bool MediaOmxReader::DecodeAudioData() } nsRefPtr -MediaOmxReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) +MediaOmxReader::Seek(int64_t aTarget, int64_t aEndTime) { NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); EnsureActive(); diff --git a/dom/media/omx/MediaOmxReader.h b/dom/media/omx/MediaOmxReader.h index 4a952155237..cb5ea1629fa 100644 --- a/dom/media/omx/MediaOmxReader.h +++ b/dom/media/omx/MediaOmxReader.h @@ -99,7 +99,7 @@ public: virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags); virtual nsRefPtr - Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; virtual bool IsMediaSeekable() MOZ_OVERRIDE; diff --git a/dom/media/omx/RtspMediaCodecReader.cpp b/dom/media/omx/RtspMediaCodecReader.cpp index dce678ab4a8..4dbc3c3d19d 100644 --- a/dom/media/omx/RtspMediaCodecReader.cpp +++ b/dom/media/omx/RtspMediaCodecReader.cpp @@ -39,8 +39,7 @@ RtspMediaCodecReader::CreateExtractor() } nsRefPtr -RtspMediaCodecReader::Seek(int64_t aTime, int64_t aStartTime, - int64_t aEndTime, int64_t aCurrentTime) +RtspMediaCodecReader::Seek(int64_t aTime, int64_t aEndTime) { // The seek function of Rtsp is time-based, we call the SeekTime function in // RtspMediaResource. The SeekTime function finally send a seek command to @@ -48,7 +47,7 @@ RtspMediaCodecReader::Seek(int64_t aTime, int64_t aStartTime, // RtspMediaResource. mRtspResource->SeekTime(aTime); - return MediaCodecReader::Seek(aTime, aStartTime, aEndTime, aCurrentTime); + return MediaCodecReader::Seek(aTime, aEndTime); } void diff --git a/dom/media/omx/RtspMediaCodecReader.h b/dom/media/omx/RtspMediaCodecReader.h index 39de7d9aed6..7ca495f4d6f 100644 --- a/dom/media/omx/RtspMediaCodecReader.h +++ b/dom/media/omx/RtspMediaCodecReader.h @@ -37,8 +37,7 @@ public: // Implement a time-based seek instead of byte-based. virtual nsRefPtr - Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, - int64_t aCurrentTime) MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; // Override GetBuffered() to do nothing for below reasons: // 1. Because the Rtsp stream is a/v separated. The buffered data in a/v diff --git a/dom/media/omx/RtspOmxReader.cpp b/dom/media/omx/RtspOmxReader.cpp index 7bae223f17e..2c21d10bc3d 100644 --- a/dom/media/omx/RtspOmxReader.cpp +++ b/dom/media/omx/RtspOmxReader.cpp @@ -33,8 +33,7 @@ nsresult RtspOmxReader::InitOmxDecoder() } nsRefPtr -RtspOmxReader::Seek(int64_t aTime, int64_t aStartTime, - int64_t aEndTime, int64_t aCurrentTime) +RtspOmxReader::Seek(int64_t aTime, int64_t aEndTime) { // The seek function of Rtsp is time-based, we call the SeekTime function in // RtspMediaResource. The SeekTime function finally send a seek command to @@ -49,7 +48,7 @@ RtspOmxReader::Seek(int64_t aTime, int64_t aStartTime, // seek operation. The function will clear the |mVideoQueue| and |mAudioQueue| // that store the decoded data and also call the |DecodeToTarget| to pass // the seek time to OMX a/v decoders. - return MediaOmxReader::Seek(aTime, aStartTime, aEndTime, aCurrentTime); + return MediaOmxReader::Seek(aTime, aEndTime); } void RtspOmxReader::SetIdle() { diff --git a/dom/media/omx/RtspOmxReader.h b/dom/media/omx/RtspOmxReader.h index f2a530650ce..e77d2f2b1f6 100644 --- a/dom/media/omx/RtspOmxReader.h +++ b/dom/media/omx/RtspOmxReader.h @@ -47,8 +47,7 @@ public: // Implement a time-based seek instead of byte-based.. virtual nsRefPtr - Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, - int64_t aCurrentTime) MOZ_FINAL MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_FINAL MOZ_OVERRIDE; // Override GetBuffered() to do nothing for below reasons: // 1. Because the Rtsp stream is a/v separated. The buffered data in a/v diff --git a/dom/media/raw/RawReader.cpp b/dom/media/raw/RawReader.cpp index 997fc350b4c..2521608ed1d 100644 --- a/dom/media/raw/RawReader.cpp +++ b/dom/media/raw/RawReader.cpp @@ -236,7 +236,7 @@ bool RawReader::DecodeVideoFrame(bool &aKeyframeSkip, } nsRefPtr -RawReader::Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) +RawReader::Seek(int64_t aTime, int64_t aEndTime) { nsresult res = SeekInternal(aTime); if (NS_FAILED(res)) { diff --git a/dom/media/raw/RawReader.h b/dom/media/raw/RawReader.h index cc19cd1fe1e..b95e0135633 100644 --- a/dom/media/raw/RawReader.h +++ b/dom/media/raw/RawReader.h @@ -40,7 +40,7 @@ public: virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) MOZ_OVERRIDE; virtual nsRefPtr - Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; virtual nsresult GetBuffered(dom::TimeRanges* aBuffered) MOZ_OVERRIDE; diff --git a/dom/media/test/bipbop.mp4 b/dom/media/test/bipbop.mp4 new file mode 100644 index 00000000000..017d658f311 Binary files /dev/null and b/dom/media/test/bipbop.mp4 differ diff --git a/dom/media/test/manifest.js b/dom/media/test/manifest.js index ff7ecd79387..01fec953e57 100644 --- a/dom/media/test/manifest.js +++ b/dom/media/test/manifest.js @@ -14,7 +14,7 @@ var gSmallTests = [ { name:"seek.webm", type:"video/webm", width:320, height:240, duration:3.966 }, { name:"vp9.webm", type:"video/webm", width:320, height:240, duration:4 }, { name:"detodos.opus", type:"audio/ogg; codecs=opus", duration:2.9135 }, - { name:"gizmo.mp4", type:"video/mp4", duration:5.56 }, + { name:"gizmo.mp4", type:"video/mp4", width:560, height:320, duration:5.56 }, { name:"bogus.duh", type:"bogus/duh" } ]; diff --git a/dom/media/test/mochitest.ini b/dom/media/test/mochitest.ini index e6a007ba548..cb66459ba65 100644 --- a/dom/media/test/mochitest.ini +++ b/dom/media/test/mochitest.ini @@ -332,6 +332,7 @@ skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439 [test_bug726904.html] [test_bug874897.html] +[test_bug879717.html] [test_bug883173.html] [test_bug895091.html] [test_bug895305.html] @@ -470,6 +471,7 @@ skip-if = (toolkit == 'android' && processor == 'x86') #x86 only skip-if = (toolkit == 'android' && processor == 'x86') #x86 only bug 914439 [test_reset_events_async.html] [test_reset_src.html] +[test_video_dimensions.html] [test_resume.html] skip-if = true # bug 1021673 [test_seek_out_of_range.html] diff --git a/dom/media/test/test_bug879717.html b/dom/media/test/test_bug879717.html new file mode 100644 index 00000000000..35e68fb2860 --- /dev/null +++ b/dom/media/test/test_bug879717.html @@ -0,0 +1,119 @@ + + + + Test for bug 879717, check that a video element can be drawn into a canvas at various states of playback + + + + + +
+
+
+ + diff --git a/dom/media/test/test_streams_srcObject.html b/dom/media/test/test_streams_srcObject.html index 446c5466b41..ed9c5866783 100644 --- a/dom/media/test/test_streams_srcObject.html +++ b/dom/media/test/test_streams_srcObject.html @@ -45,6 +45,8 @@ function doTest() { } ++step; }); + a.play(); + b.play(); } diff --git a/dom/media/test/test_video_dimensions.html b/dom/media/test/test_video_dimensions.html new file mode 100644 index 00000000000..8c7439e825c --- /dev/null +++ b/dom/media/test/test_video_dimensions.html @@ -0,0 +1,72 @@ + + + + Test that a video element has set video dimensions on loadedmetadata + + + + + +
+
+
+ + diff --git a/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html b/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html index 9640b943fa9..423cdb2cfc5 100644 --- a/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html +++ b/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html @@ -19,22 +19,22 @@ visible: true }); - var domLoaded = new Promise(r => addEventListener("DOMContentLoaded", e => r())); - var test; - var stream; - var waitUntil = func => new Promise(resolve => { - var ival = setInterval(() => func() && resolve(clearInterval(ival)), 200); + var metadataLoaded = new Promise(resolve => { + if (v1.readyState < v1.HAVE_METADATA) { + v1.onloadedmetadata = e => resolve(); + return; + } + resolve(); }); runNetworkTest(function() { - test = new PeerConnectionTest(); + var test = new PeerConnectionTest(); test.setOfferOptions({ offerToReceiveVideo: false, offerToReceiveAudio: false }); test.chain.insertAfter("PC_LOCAL_GUM", [["PC_LOCAL_CAPTUREVIDEO", function (test) { - domLoaded - .then(() => waitUntil(() => v1.videoWidth > 0)) // TODO: Bug 1096723 + metadataLoaded .then(function() { - stream = v1.mozCaptureStreamUntilEnded(); + var stream = v1.mozCaptureStreamUntilEnded(); is(stream.getTracks().length, 2, "Captured stream has 2 tracks"); stream.getTracks().forEach(tr => test.pcLocal._pc.addTrack(tr, stream)); test.pcLocal.constraints = [{ video: true, audio:true }]; // fool tests diff --git a/dom/media/wave/WaveReader.cpp b/dom/media/wave/WaveReader.cpp index 0df0ad161e2..b3f4f92c278 100644 --- a/dom/media/wave/WaveReader.cpp +++ b/dom/media/wave/WaveReader.cpp @@ -258,7 +258,7 @@ bool WaveReader::DecodeVideoFrame(bool &aKeyframeSkip, } nsRefPtr -WaveReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) +WaveReader::Seek(int64_t aTarget, int64_t aEndTime) { NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); LOG(PR_LOG_DEBUG, ("%p About to seek to %lld", mDecoder, aTarget)); diff --git a/dom/media/wave/WaveReader.h b/dom/media/wave/WaveReader.h index 690b2eec1b3..aa1a09a4ff7 100644 --- a/dom/media/wave/WaveReader.h +++ b/dom/media/wave/WaveReader.h @@ -44,7 +44,7 @@ public: virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) MOZ_OVERRIDE; virtual nsRefPtr - Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; virtual nsresult GetBuffered(dom::TimeRanges* aBuffered) MOZ_OVERRIDE; diff --git a/dom/media/webaudio/BufferDecoder.cpp b/dom/media/webaudio/BufferDecoder.cpp index 07639b2609d..d33e4439171 100644 --- a/dom/media/webaudio/BufferDecoder.cpp +++ b/dom/media/webaudio/BufferDecoder.cpp @@ -140,13 +140,13 @@ BufferDecoder::IsMediaSeekable() } void -BufferDecoder::MetadataLoaded(nsAutoPtr aInfo, nsAutoPtr aTags) +BufferDecoder::MetadataLoaded(nsAutoPtr aInfo, nsAutoPtr aTags, bool aRestoredFromDromant) { // ignore } void -BufferDecoder::FirstFrameLoaded(nsAutoPtr aInfo) +BufferDecoder::FirstFrameLoaded(nsAutoPtr aInfo, bool aRestoredFromDromant) { // ignore } diff --git a/dom/media/webaudio/BufferDecoder.h b/dom/media/webaudio/BufferDecoder.h index 66c27834db4..f6b6f406c6c 100644 --- a/dom/media/webaudio/BufferDecoder.h +++ b/dom/media/webaudio/BufferDecoder.h @@ -59,9 +59,9 @@ public: virtual bool IsMediaSeekable() MOZ_FINAL MOZ_OVERRIDE; - virtual void MetadataLoaded(nsAutoPtr aInfo, nsAutoPtr aTags) MOZ_FINAL MOZ_OVERRIDE; + virtual void MetadataLoaded(nsAutoPtr aInfo, nsAutoPtr aTags, bool aRestoredFromDromant) MOZ_FINAL MOZ_OVERRIDE; virtual void QueueMetadata(int64_t aTime, nsAutoPtr aInfo, nsAutoPtr aTags) MOZ_FINAL MOZ_OVERRIDE; - virtual void FirstFrameLoaded(nsAutoPtr aInfo) MOZ_FINAL MOZ_OVERRIDE; + virtual void FirstFrameLoaded(nsAutoPtr aInfo, bool aRestoredFromDromant) MOZ_FINAL MOZ_OVERRIDE; virtual void RemoveMediaTracks() MOZ_FINAL MOZ_OVERRIDE; diff --git a/dom/media/webaudio/test/mochitest.ini b/dom/media/webaudio/test/mochitest.ini index 36cd3186dc3..83b47d29d1a 100644 --- a/dom/media/webaudio/test/mochitest.ini +++ b/dom/media/webaudio/test/mochitest.ini @@ -119,7 +119,7 @@ skip-if = toolkit == 'android' # bug 1056706 [test_mixingRules.html] skip-if = android_version == '10' # bug 1091965 [test_mozaudiochannel.html] -skip-if = (toolkit == 'gonk' && !debug) +skip-if = (toolkit == 'gonk' && !debug) || android_version == '10' # Android: bug 1061675 [test_nodeToParamConnection.html] [test_OfflineAudioContext.html] [test_offlineDestinationChannelCountLess.html] diff --git a/dom/media/webm/WebMReader.cpp b/dom/media/webm/WebMReader.cpp index 6f7c84ed63a..75bc83a376e 100644 --- a/dom/media/webm/WebMReader.cpp +++ b/dom/media/webm/WebMReader.cpp @@ -952,10 +952,9 @@ void WebMReader::PushVideoPacket(NesteggPacketHolder* aItem) } nsRefPtr -WebMReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, - int64_t aCurrentTime) +WebMReader::Seek(int64_t aTarget, int64_t aEndTime) { - nsresult res = SeekInternal(aTarget, aStartTime); + nsresult res = SeekInternal(aTarget); if (NS_FAILED(res)) { return SeekPromise::CreateAndReject(res, __func__); } else { @@ -963,7 +962,7 @@ WebMReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, } } -nsresult WebMReader::SeekInternal(int64_t aTarget, int64_t aStartTime) +nsresult WebMReader::SeekInternal(int64_t aTarget) { NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); if (mVideoDecoder) { @@ -980,7 +979,7 @@ nsresult WebMReader::SeekInternal(int64_t aTarget, int64_t aStartTime) uint64_t target = aTarget * NS_PER_USEC; if (mSeekPreroll) { - target = std::max(uint64_t(aStartTime * NS_PER_USEC), + target = std::max(uint64_t(mStartTime * NS_PER_USEC), target - mSeekPreroll); } int r = nestegg_track_seek(mContext, trackToSeek, target); diff --git a/dom/media/webm/WebMReader.h b/dom/media/webm/WebMReader.h index a14dce9f324..40b370dff5d 100644 --- a/dom/media/webm/WebMReader.h +++ b/dom/media/webm/WebMReader.h @@ -156,7 +156,7 @@ public: virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) MOZ_OVERRIDE; virtual nsRefPtr - Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; virtual nsresult GetBuffered(dom::TimeRanges* aBuffered) MOZ_OVERRIDE; virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, @@ -210,7 +210,7 @@ protected: // reading metadata or destruction of the reader itself. void Cleanup(); - virtual nsresult SeekInternal(int64_t aTime, int64_t aStartTime); + virtual nsresult SeekInternal(int64_t aTime); // Initializes mLayersBackendType if possible. void InitLayersBackendType(); diff --git a/dom/media/wmf/WMFReader.cpp b/dom/media/wmf/WMFReader.cpp index 94e9ce755bc..fd442d94da1 100644 --- a/dom/media/wmf/WMFReader.cpp +++ b/dom/media/wmf/WMFReader.cpp @@ -889,10 +889,7 @@ WMFReader::DecodeVideoFrame(bool &aKeyframeSkip, } nsRefPtr -WMFReader::Seek(int64_t aTargetUs, - int64_t aStartTime, - int64_t aEndTime, - int64_t aCurrentTime) +WMFReader::Seek(int64_t aTargetUs, int64_t aEndTime) { nsresult res = SeekInternal(aTargetUs); if (NS_FAILED(res)) { diff --git a/dom/media/wmf/WMFReader.h b/dom/media/wmf/WMFReader.h index 22e353877e8..2f1fd6f8ab6 100644 --- a/dom/media/wmf/WMFReader.h +++ b/dom/media/wmf/WMFReader.h @@ -44,10 +44,7 @@ public: MetadataTags** aTags) MOZ_OVERRIDE; nsRefPtr - Seek(int64_t aTime, - int64_t aStartTime, - int64_t aEndTime, - int64_t aCurrentTime) MOZ_OVERRIDE; + Seek(int64_t aTime, int64_t aEndTime) MOZ_OVERRIDE; bool IsMediaSeekable() MOZ_OVERRIDE; diff --git a/dom/network/UDPSocketChild.cpp b/dom/network/UDPSocketChild.cpp index 48bb0a1ee8d..eb6a86f503d 100644 --- a/dom/network/UDPSocketChild.cpp +++ b/dom/network/UDPSocketChild.cpp @@ -235,7 +235,7 @@ UDPSocketChild::RecvCallbackClosed() bool UDPSocketChild::RecvCallbackReceivedData(const UDPAddressInfo& aAddressInfo, - const InfallibleTArray& aData) + InfallibleTArray&& aData) { nsresult rv = mSocket->CallListenerReceivedData(aAddressInfo.addr(), aAddressInfo.port(), aData.Elements(), aData.Length()); diff --git a/dom/network/UDPSocketChild.h b/dom/network/UDPSocketChild.h index d154e304f7e..669ac2515bc 100644 --- a/dom/network/UDPSocketChild.h +++ b/dom/network/UDPSocketChild.h @@ -43,7 +43,7 @@ public: virtual bool RecvCallbackOpened(const UDPAddressInfo& aAddressInfo) MOZ_OVERRIDE; virtual bool RecvCallbackClosed() MOZ_OVERRIDE; virtual bool RecvCallbackReceivedData(const UDPAddressInfo& aAddressInfo, - const InfallibleTArray& aData) MOZ_OVERRIDE; + InfallibleTArray&& aData) MOZ_OVERRIDE; virtual bool RecvCallbackError(const nsCString& aMessage, const nsCString& aFilename, const uint32_t& aLineNumber) MOZ_OVERRIDE; diff --git a/dom/network/UDPSocketParent.h b/dom/network/UDPSocketParent.h index e09cb0c2c01..de7ae2a7260 100644 --- a/dom/network/UDPSocketParent.h +++ b/dom/network/UDPSocketParent.h @@ -34,7 +34,6 @@ public: virtual bool RecvOutgoingData(const UDPData& aData, const UDPSocketAddr& aAddr) MOZ_OVERRIDE; virtual bool RecvClose() MOZ_OVERRIDE; - virtual bool RecvRequestDelete() MOZ_OVERRIDE; virtual bool RecvJoinMulticast(const nsCString& aMulticastAddress, const nsCString& aInterface) MOZ_OVERRIDE; diff --git a/dom/notification/Notification.cpp b/dom/notification/Notification.cpp index 2c25d33492c..a6ae76a4753 100644 --- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -332,9 +332,9 @@ NotificationPermissionRequest::GetTypes(nsIArray** aTypes) { nsTArray emptyOptions; return nsContentPermissionUtils::CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"), - NS_LITERAL_CSTRING("unused"), - emptyOptions, - aTypes); + NS_LITERAL_CSTRING("unused"), + emptyOptions, + aTypes); } NS_IMPL_ISUPPORTS(NotificationTask, nsIRunnable) diff --git a/dom/plugins/base/nsJSNPRuntime.cpp b/dom/plugins/base/nsJSNPRuntime.cpp index f381c57e48b..cd08f7dbc80 100644 --- a/dom/plugins/base/nsJSNPRuntime.cpp +++ b/dom/plugins/base/nsJSNPRuntime.cpp @@ -1774,8 +1774,7 @@ NPObjWrapper_ObjectMoved(JSObject *obj, const JSObject *old) return; } - // The hazard analysis thinks that PL_DHashTableOperate() can GC but this is - // not possible if we pass PL_DHASH_LOOKUP. + // Calling PL_DHashTableLookup() will not result in GC. JS::AutoSuppressGCAnalysis nogc; NPObjWrapperHashEntry *entry = static_cast diff --git a/dom/plugins/base/nsNPAPIPlugin.cpp b/dom/plugins/base/nsNPAPIPlugin.cpp index 52720387d0b..678e30d7573 100644 --- a/dom/plugins/base/nsNPAPIPlugin.cpp +++ b/dom/plugins/base/nsNPAPIPlugin.cpp @@ -2171,6 +2171,20 @@ _getvalue(NPP npp, NPNVariable variable, void *result) nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *) (npp ? npp->ndata : nullptr); double scaleFactor = inst ? inst->GetContentsScaleFactor() : 1.0; + // Work around a Flash bug that causes long hangs when Flash tries to + // display its camera and microphone access dialog while it thinks HiDPI + // support is available. This is Adobe bug ADBE 3921114, which should get + // fixed in a future release. When this happens we'll no longer need this + // workaround. See QUIRK_FLASH_HIDE_HIDPI_SUPPORT in PluginModuleChild.h, + // and also bug 1118615. + if (inst) { + const char *mimeType; + inst->GetMIMEType(&mimeType); + NS_NAMED_LITERAL_CSTRING(flash, "application/x-shockwave-flash"); + if (!PL_strncasecmp(mimeType, flash.get(), flash.Length())) { + scaleFactor = 1.0; + } + } *(double*)result = scaleFactor; return NPERR_NO_ERROR; } diff --git a/dom/plugins/ipc/PluginBridge.h b/dom/plugins/ipc/PluginBridge.h index 6c0fad71b62..8bd5963602c 100644 --- a/dom/plugins/ipc/PluginBridge.h +++ b/dom/plugins/ipc/PluginBridge.h @@ -16,7 +16,8 @@ class ContentParent; namespace plugins { bool -SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent); +SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent, + bool aForceBridgeNow = false); bool FindPluginsForContent(uint32_t aPluginEpoch, diff --git a/dom/plugins/ipc/PluginInstanceChild.cpp b/dom/plugins/ipc/PluginInstanceChild.cpp index d9711646988..c87166a05b1 100644 --- a/dom/plugins/ipc/PluginInstanceChild.cpp +++ b/dom/plugins/ipc/PluginInstanceChild.cpp @@ -483,7 +483,11 @@ PluginInstanceChild::NPN_GetValue(NPNVariable aVar, #endif /* NP_NO_QUICKDRAW */ case NPNVcontentsScaleFactor: { - *static_cast(aValue) = mContentsScaleFactor; + double scaleFactor = mContentsScaleFactor; + if (GetQuirks() & PluginModuleChild::QUIRK_FLASH_HIDE_HIDPI_SUPPORT) { + scaleFactor = 1.0; + } + *static_cast(aValue) = scaleFactor; return NPERR_NO_ERROR; } #endif /* XP_MACOSX */ @@ -851,7 +855,7 @@ PluginInstanceChild::AnswerNPP_HandleEvent(const NPRemoteEvent& event, bool PluginInstanceChild::AnswerNPP_HandleEvent_Shmem(const NPRemoteEvent& event, - Shmem& mem, + Shmem&& mem, int16_t* handled, Shmem* rtnmem) { @@ -915,7 +919,7 @@ PluginInstanceChild::AnswerNPP_HandleEvent_Shmem(const NPRemoteEvent& event, #else bool PluginInstanceChild::AnswerNPP_HandleEvent_Shmem(const NPRemoteEvent& event, - Shmem& mem, + Shmem&& mem, int16_t* handled, Shmem* rtnmem) { diff --git a/dom/plugins/ipc/PluginInstanceChild.h b/dom/plugins/ipc/PluginInstanceChild.h index 242017f3b95..b7789dd1147 100644 --- a/dom/plugins/ipc/PluginInstanceChild.h +++ b/dom/plugins/ipc/PluginInstanceChild.h @@ -89,7 +89,7 @@ protected: AnswerNPP_HandleEvent(const NPRemoteEvent& event, int16_t* handled) MOZ_OVERRIDE; virtual bool AnswerNPP_HandleEvent_Shmem(const NPRemoteEvent& event, - Shmem& mem, + Shmem&& mem, int16_t* handled, Shmem* rtnmem) MOZ_OVERRIDE; virtual bool diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp index 034db619d28..e7960cff08c 100644 --- a/dom/plugins/ipc/PluginModuleChild.cpp +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -2118,6 +2118,7 @@ PluginModuleChild::InitQuirksModes(const nsCString& aMimeType) NS_NAMED_LITERAL_CSTRING(quicktime, "QuickTime Plugin.plugin"); if (FindInReadable(flash, aMimeType)) { mQuirks |= QUIRK_FLASH_AVOID_CGMODE_CRASHES; + mQuirks |= QUIRK_FLASH_HIDE_HIDPI_SUPPORT; } if (FindInReadable(flash, aMimeType) || FindInReadable(quicktime, mPluginFilename)) { @@ -2130,8 +2131,8 @@ bool PluginModuleChild::RecvPPluginInstanceConstructor(PPluginInstanceChild* aActor, const nsCString& aMimeType, const uint16_t& aMode, - const InfallibleTArray& aNames, - const InfallibleTArray& aValues) + InfallibleTArray&& aNames, + InfallibleTArray&& aValues) { PLUGIN_LOG_DEBUG_METHOD; AssertPluginThread(); @@ -2477,8 +2478,8 @@ PluginModuleChild::ProcessNativeEvents() { bool PluginModuleChild::RecvStartProfiler(const uint32_t& aEntries, const double& aInterval, - const nsTArray& aFeatures, - const nsTArray& aThreadNameFilters) + nsTArray&& aFeatures, + nsTArray&& aThreadNameFilters) { nsTArray featureArray; for (size_t i = 0; i < aFeatures.Length(); ++i) { diff --git a/dom/plugins/ipc/PluginModuleChild.h b/dom/plugins/ipc/PluginModuleChild.h index bf0770dfe1c..3b8a838baa5 100644 --- a/dom/plugins/ipc/PluginModuleChild.h +++ b/dom/plugins/ipc/PluginModuleChild.h @@ -101,8 +101,8 @@ protected: RecvPPluginInstanceConstructor(PPluginInstanceChild* aActor, const nsCString& aMimeType, const uint16_t& aMode, - const InfallibleTArray& aNames, - const InfallibleTArray& aValues) + InfallibleTArray&& aNames, + InfallibleTArray&& aValues) MOZ_OVERRIDE; virtual bool AnswerNP_Shutdown(NPError *rv) MOZ_OVERRIDE; @@ -149,8 +149,8 @@ protected: virtual bool RecvStartProfiler(const uint32_t& aEntries, const double& aInterval, - const nsTArray& aFeatures, - const nsTArray& aThreadNameFilters) MOZ_OVERRIDE; + nsTArray&& aFeatures, + nsTArray&& aThreadNameFilters) MOZ_OVERRIDE; virtual bool RecvStopProfiler() MOZ_OVERRIDE; virtual bool AnswerGetProfile(nsCString* aProfile) MOZ_OVERRIDE; @@ -283,6 +283,12 @@ public: // CGContextRef we pass to it in NPP_HandleEvent(NPCocoaEventDrawRect) // outside of that call. See bug 804606. QUIRK_FLASH_AVOID_CGMODE_CRASHES = 1 << 10, + // Mac: Work around a Flash bug that causes long hangs when Flash + // tries to display its camera and microphone access dialog while + // it thinks HiDPI support is available. This is Adobe bug + // ADBE 3921114, which should get fixed in a future release. When + // this happens we'll no longer need this quirk. See bug 1118615. + QUIRK_FLASH_HIDE_HIDPI_SUPPORT = 1 << 11, }; int GetQuirks() { return mQuirks; } diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp index 269f9d2f532..4e4562bd453 100755 --- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -90,7 +90,9 @@ struct RunnableMethodTraits }; bool -mozilla::plugins::SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent) +mozilla::plugins::SetupBridge(uint32_t aPluginId, + dom::ContentParent* aContentParent, + bool aForceBridgeNow) { nsRefPtr host = nsPluginHost::GetInst(); nsRefPtr plugin; @@ -100,7 +102,7 @@ mozilla::plugins::SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentPa } PluginModuleChromeParent* chromeParent = static_cast(plugin->GetLibrary()); chromeParent->SetContentParent(aContentParent); - if (chromeParent->IsStartingAsync()) { + if (!aForceBridgeNow && chromeParent->IsStartingAsync()) { // We'll handle the bridging asynchronously return true; } @@ -1778,12 +1780,8 @@ PluginModuleChromeParent::RecvNP_InitializeResult(const NPError& aError) bool initOk = aError == NPERR_NO_ERROR; if (initOk) { SetPluginFuncs(mNPPIface); - if (mIsStartingAsync) { - if (SendAssociatePluginId()) { - PPluginModule::Bridge(mContentParent, this); - } else { - initOk = false; - } + if (mIsStartingAsync && !SendAssociatePluginId()) { + initOk = false; } } mNPInitialized = initOk; @@ -1878,7 +1876,6 @@ PluginModuleChromeParent::RecvNP_InitializeResult(const NPError& aError) bool ok = true; if (mContentParent) { if ((ok = SendAssociatePluginId())) { - PPluginModule::Bridge(mContentParent, this); ok = mContentParent->SendLoadPluginResult(mPluginId, aError == NPERR_NO_ERROR); } diff --git a/dom/plugins/ipc/PluginScriptableObjectChild.cpp b/dom/plugins/ipc/PluginScriptableObjectChild.cpp index bd3f61eae2a..9f6231345bd 100644 --- a/dom/plugins/ipc/PluginScriptableObjectChild.cpp +++ b/dom/plugins/ipc/PluginScriptableObjectChild.cpp @@ -753,7 +753,7 @@ PluginScriptableObjectChild::AnswerHasMethod(const PluginIdentifier& aId, bool PluginScriptableObjectChild::AnswerInvoke(const PluginIdentifier& aId, - const InfallibleTArray& aArgs, + InfallibleTArray&& aArgs, Variant* aResult, bool* aSuccess) { @@ -823,7 +823,7 @@ PluginScriptableObjectChild::AnswerInvoke(const PluginIdentifier& aId, } bool -PluginScriptableObjectChild::AnswerInvokeDefault(const InfallibleTArray& aArgs, +PluginScriptableObjectChild::AnswerInvokeDefault(InfallibleTArray&& aArgs, Variant* aResult, bool* aSuccess) { @@ -1074,7 +1074,7 @@ PluginScriptableObjectChild::AnswerEnumerate(InfallibleTArray* } bool -PluginScriptableObjectChild::AnswerConstruct(const InfallibleTArray& aArgs, +PluginScriptableObjectChild::AnswerConstruct(InfallibleTArray&& aArgs, Variant* aResult, bool* aSuccess) { diff --git a/dom/plugins/ipc/PluginScriptableObjectChild.h b/dom/plugins/ipc/PluginScriptableObjectChild.h index 9079f96fa0e..c86435c8a0f 100644 --- a/dom/plugins/ipc/PluginScriptableObjectChild.h +++ b/dom/plugins/ipc/PluginScriptableObjectChild.h @@ -63,12 +63,12 @@ public: virtual bool AnswerInvoke(const PluginIdentifier& aId, - const InfallibleTArray& aArgs, + InfallibleTArray&& aArgs, Variant* aResult, bool* aSuccess) MOZ_OVERRIDE; virtual bool - AnswerInvokeDefault(const InfallibleTArray& aArgs, + AnswerInvokeDefault(InfallibleTArray&& aArgs, Variant* aResult, bool* aSuccess) MOZ_OVERRIDE; @@ -97,7 +97,7 @@ public: bool* aSuccess) MOZ_OVERRIDE; virtual bool - AnswerConstruct(const InfallibleTArray& aArgs, + AnswerConstruct(InfallibleTArray&& aArgs, Variant* aResult, bool* aSuccess) MOZ_OVERRIDE; diff --git a/dom/plugins/ipc/PluginScriptableObjectParent.cpp b/dom/plugins/ipc/PluginScriptableObjectParent.cpp index 60a5186e568..16387b99820 100644 --- a/dom/plugins/ipc/PluginScriptableObjectParent.cpp +++ b/dom/plugins/ipc/PluginScriptableObjectParent.cpp @@ -783,7 +783,7 @@ PluginScriptableObjectParent::AnswerHasMethod(const PluginIdentifier& aId, bool PluginScriptableObjectParent::AnswerInvoke(const PluginIdentifier& aId, - const InfallibleTArray& aArgs, + InfallibleTArray&& aArgs, Variant* aResult, bool* aSuccess) { @@ -873,7 +873,7 @@ PluginScriptableObjectParent::AnswerInvoke(const PluginIdentifier& aId, } bool -PluginScriptableObjectParent::AnswerInvokeDefault(const InfallibleTArray& aArgs, +PluginScriptableObjectParent::AnswerInvokeDefault(InfallibleTArray&& aArgs, Variant* aResult, bool* aSuccess) { @@ -1193,7 +1193,7 @@ PluginScriptableObjectParent::AnswerEnumerate(InfallibleTArray } bool -PluginScriptableObjectParent::AnswerConstruct(const InfallibleTArray& aArgs, +PluginScriptableObjectParent::AnswerConstruct(InfallibleTArray&& aArgs, Variant* aResult, bool* aSuccess) { diff --git a/dom/plugins/ipc/PluginScriptableObjectParent.h b/dom/plugins/ipc/PluginScriptableObjectParent.h index dfb67e99858..ed677ea8f9f 100644 --- a/dom/plugins/ipc/PluginScriptableObjectParent.h +++ b/dom/plugins/ipc/PluginScriptableObjectParent.h @@ -54,12 +54,12 @@ public: virtual bool AnswerInvoke(const PluginIdentifier& aId, - const InfallibleTArray& aArgs, + InfallibleTArray&& aArgs, Variant* aResult, bool* aSuccess) MOZ_OVERRIDE; virtual bool - AnswerInvokeDefault(const InfallibleTArray& aArgs, + AnswerInvokeDefault(InfallibleTArray&& aArgs, Variant* aResult, bool* aSuccess) MOZ_OVERRIDE; @@ -86,7 +86,7 @@ public: bool* aSuccess) MOZ_OVERRIDE; virtual bool - AnswerConstruct(const InfallibleTArray& aArgs, + AnswerConstruct(InfallibleTArray&& aArgs, Variant* aResult, bool* aSuccess) MOZ_OVERRIDE; diff --git a/dom/plugins/ipc/PluginWidgetParent.cpp b/dom/plugins/ipc/PluginWidgetParent.cpp index b784fb67035..7b580452b13 100644 --- a/dom/plugins/ipc/PluginWidgetParent.cpp +++ b/dom/plugins/ipc/PluginWidgetParent.cpp @@ -217,7 +217,7 @@ PluginWidgetParent::RecvMove(const double& aX, const double& aY) } bool -PluginWidgetParent::RecvSetWindowClipRegion(const nsTArray& Regions, +PluginWidgetParent::RecvSetWindowClipRegion(InfallibleTArray&& Regions, const bool& aIntersectWithExisting) { ENSURE_CHANNEL; diff --git a/dom/plugins/ipc/PluginWidgetParent.h b/dom/plugins/ipc/PluginWidgetParent.h index 676d02e7b94..176f934938f 100644 --- a/dom/plugins/ipc/PluginWidgetParent.h +++ b/dom/plugins/ipc/PluginWidgetParent.h @@ -36,8 +36,8 @@ public: virtual bool RecvGetNativePluginPort(uintptr_t* value) MOZ_OVERRIDE; virtual bool RecvResize(const nsIntRect& aRect) MOZ_OVERRIDE; virtual bool RecvMove(const double& aX, const double& aY) MOZ_OVERRIDE; - virtual bool RecvSetWindowClipRegion(const nsTArray& Regions, - const bool& aIntersectWithExisting) MOZ_OVERRIDE; + virtual bool RecvSetWindowClipRegion(InfallibleTArray&& Regions, + const bool& aIntersectWithExisting) MOZ_OVERRIDE; private: // The tab our connection is associated with. diff --git a/dom/storage/DOMStorageIPC.cpp b/dom/storage/DOMStorageIPC.cpp index eb8726705eb..10ae15a0fe6 100644 --- a/dom/storage/DOMStorageIPC.cpp +++ b/dom/storage/DOMStorageIPC.cpp @@ -214,7 +214,7 @@ DOMStorageDBChild::RecvObserve(const nsCString& aTopic, } bool -DOMStorageDBChild::RecvScopesHavingData(const InfallibleTArray& aScopes) +DOMStorageDBChild::RecvScopesHavingData(nsTArray&& aScopes) { for (uint32_t i = 0; i < aScopes.Length(); ++i) { ScopesHavingData().PutEntry(aScopes[i]); diff --git a/dom/storage/DOMStorageIPC.h b/dom/storage/DOMStorageIPC.h index a0bcdfdd035..934e0395172 100644 --- a/dom/storage/DOMStorageIPC.h +++ b/dom/storage/DOMStorageIPC.h @@ -74,7 +74,7 @@ private: const nsString& aValue); bool RecvLoadDone(const nsCString& aScope, const nsresult& aRv); - bool RecvScopesHavingData(const InfallibleTArray& aScopes); + bool RecvScopesHavingData(nsTArray&& aScopes); bool RecvLoadUsage(const nsCString& aScope, const int64_t& aUsage); bool RecvError(const nsresult& aRv); diff --git a/dom/system/NetworkGeolocationProvider.js b/dom/system/NetworkGeolocationProvider.js index 67cc415097c..190b9fb41c0 100755 --- a/dom/system/NetworkGeolocationProvider.js +++ b/dom/system/NetworkGeolocationProvider.js @@ -2,6 +2,8 @@ * 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/. */ +"use strict"; + Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://gre/modules/Services.jsm"); @@ -318,13 +320,12 @@ WifiGeoPositionProvider.prototype = { } }; - try { - Services.obs.addObserver(this, SETTINGS_CHANGED_TOPIC, false); - let settings = Cc["@mozilla.org/settingsService;1"].getService(Ci.nsISettingsService); + Services.obs.addObserver(this, SETTINGS_CHANGED_TOPIC, false); + let settingsService = Cc["@mozilla.org/settingsService;1"]; + if (settingsService) { + let settings = settingsService.getService(Ci.nsISettingsService); settings.createLock().get(SETTINGS_WIFI_ENABLED, settingsCallback); settings.createLock().get(SETTINGS_DEBUG_ENABLED, settingsCallback); - } catch(ex) { - // This platform doesn't have the settings interface, and that is just peachy } if (gWifiScanningEnabled && Cc["@mozilla.org/wifi/monitor;1"]) { @@ -509,7 +510,7 @@ WifiGeoPositionProvider.prototype = { [POSITION_UNAVAILABLE]); }).bind(this); xhr.onload = (function() { - LOG("gls returned status: " + xhr.status + " --> " + JSON.stringify(xhr.response)); + LOG("server returned status: " + xhr.status + " --> " + JSON.stringify(xhr.response)); if ((xhr.channel instanceof Ci.nsIHttpChannel && xhr.status != 200) || !xhr.response || !xhr.response.location) { this.notifyListener("notifyError", diff --git a/dom/webidl/CanvasRenderingContext2D.webidl b/dom/webidl/CanvasRenderingContext2D.webidl index 0f24f684a06..6902f1c3684 100644 --- a/dom/webidl/CanvasRenderingContext2D.webidl +++ b/dom/webidl/CanvasRenderingContext2D.webidl @@ -125,6 +125,7 @@ interface CanvasRenderingContext2D { // hit regions [Pref="canvas.hitregions.enabled", Throws] void addHitRegion(optional HitRegionOptions options); [Pref="canvas.hitregions.enabled"] void removeHitRegion(DOMString id); + [Pref="canvas.hitregions.enabled"] void clearHitRegions(); // pixel manipulation [NewObject, Throws] diff --git a/dom/workers/test/fetch/worker_test_request.js b/dom/workers/test/fetch/worker_test_request.js index c1cb11a9c47..bca12bab94e 100644 --- a/dom/workers/test/fetch/worker_test_request.js +++ b/dom/workers/test/fetch/worker_test_request.js @@ -11,7 +11,7 @@ function testDefaultCtor() { is(req.method, "GET", "Default Request method is GET"); ok(req.headers instanceof Headers, "Request should have non-null Headers object"); is(req.url, self.location.href, "URL should be resolved with entry settings object's API base URL"); - is(req.referrer, "", "Default referrer is `client` which serializes to empty string."); + is(req.referrer, "about:client", "Default referrer is `client` which serializes to about:client."); is(req.mode, "cors", "Request mode for string input is cors"); is(req.credentials, "omit", "Default Request credentials is omit"); @@ -19,7 +19,7 @@ function testDefaultCtor() { is(req.method, "GET", "Default Request method is GET"); ok(req.headers instanceof Headers, "Request should have non-null Headers object"); is(req.url, self.location.href, "URL should be resolved with entry settings object's API base URL"); - is(req.referrer, "", "Default referrer is `client` which serializes to empty string."); + is(req.referrer, "about:client", "Default referrer is `client` which serializes to about:client."); is(req.mode, "cors", "Request mode string input is cors"); is(req.credentials, "omit", "Default Request credentials is omit"); } @@ -37,7 +37,7 @@ function testClone() { is(req.headers.get('content-length'), "5", "Request content-length should be 5."); ok(req.url === (new URL("./cloned_request.txt", self.location.href)).href, "URL should be resolved with entry settings object's API base URL"); - ok(req.referrer === "", "Default referrer is `client` which serializes to empty string."); + ok(req.referrer === "about:client", "Default referrer is `client` which serializes to about:client."); ok(req.mode === "same-origin", "Request mode is same-origin"); ok(req.credentials === "same-origin", "Default credentials is same-origin"); } diff --git a/gfx/layers/FrameMetrics.h b/gfx/layers/FrameMetrics.h index a204e4b41cd..ab176db00e1 100644 --- a/gfx/layers/FrameMetrics.h +++ b/gfx/layers/FrameMetrics.h @@ -251,20 +251,17 @@ public: // layout/paint time. ParentLayerRect mCompositionBounds; - // --------------------------------------------------------------------------- - // The following metrics are dimensionless. - // - - // The pres-shell resolution that has been induced on the document containing - // this scroll frame as a result of zooming this scroll frame (whether via - // user action, or choosing an initial zoom level on page load). This can - // only be different from 1.0 for frames that are zoomable, which currently - // is just the root content document's root scroll frame (mIsRoot = true). - // This is a plain float rather than a ScaleFactor because in and of itself - // it does not convert between any coordinate spaces for which we have names. - float mPresShellResolution; - public: + void SetPresShellResolution(const float aPresShellResolution) + { + mPresShellResolution = aPresShellResolution; + } + + float GetPresShellResolution() const + { + return mPresShellResolution; + } + void SetDisplayPort(const CSSRect& aDisplayPort) { mDisplayPort = aDisplayPort; @@ -523,6 +520,16 @@ public: } private: + + // The pres-shell resolution that has been induced on the document containing + // this scroll frame as a result of zooming this scroll frame (whether via + // user action, or choosing an initial zoom level on page load). This can + // only be different from 1.0 for frames that are zoomable, which currently + // is just the root content document's root scroll frame (mIsRoot = true). + // This is a plain float rather than a ScaleFactor because in and of itself + // it does not convert between any coordinate spaces for which we have names. + float mPresShellResolution; + // The area of a frame's contents that has been painted, relative to // mCompositionBounds. // diff --git a/gfx/layers/LayersLogging.cpp b/gfx/layers/LayersLogging.cpp index 7d68e8f692f..a5659914fe4 100644 --- a/gfx/layers/LayersLogging.cpp +++ b/gfx/layers/LayersLogging.cpp @@ -190,7 +190,7 @@ AppendToString(std::stringstream& aStream, const FrameMetrics& m, AppendToString(aStream, m.GetRootCompositionSize(), "] [rcs="); AppendToString(aStream, m.GetViewport(), "] [v="); aStream << nsPrintfCString("] [z=(ld=%.3f r=%.3f cr=%.3f z=%.3f er=%.3f)", - m.GetDevPixelsPerCSSPixel().scale, m.mPresShellResolution, + m.GetDevPixelsPerCSSPixel().scale, m.GetPresShellResolution(), m.GetCumulativeResolution().scale, m.GetZoom().scale, m.GetExtraResolution().scale).get(); aStream << nsPrintfCString("] [u=(%d %d %lu)", diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp index 348907b032a..43f0ca86949 100644 --- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -246,7 +246,7 @@ ComputeClipRegion(GeckoContentController* aController, // resolution. LayoutDeviceToParentLayerScale parentCumulativeResolution = aLayer.Metrics().GetCumulativeResolution() - / ParentLayerToLayerScale(aLayer.Metrics().mPresShellResolution); + / ParentLayerToLayerScale(aLayer.Metrics().GetPresShellResolution()); // Not sure what rounding option is the most correct here, but if we ever // figure it out we can change this. For now I'm rounding in to minimize // the chances of getting a complex region. diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index 537a4c49b04..28437cd516f 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -2775,8 +2775,8 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetri // since the repaint request. float totalResolutionChange = aLayerMetrics.GetCumulativeResolution().scale / mFrameMetrics.GetCumulativeResolution().scale; - float presShellResolutionChange = aLayerMetrics.mPresShellResolution - / mFrameMetrics.mPresShellResolution; + float presShellResolutionChange = aLayerMetrics.GetPresShellResolution() + / mFrameMetrics.GetPresShellResolution(); mFrameMetrics.ZoomBy(totalResolutionChange / presShellResolutionChange); } else { // Take the new zoom as either device scale or composition width or @@ -2791,7 +2791,7 @@ void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetri } mFrameMetrics.mCompositionBounds = aLayerMetrics.mCompositionBounds; mFrameMetrics.SetRootCompositionSize(aLayerMetrics.GetRootCompositionSize()); - mFrameMetrics.mPresShellResolution = aLayerMetrics.mPresShellResolution; + mFrameMetrics.SetPresShellResolution(aLayerMetrics.GetPresShellResolution()); mFrameMetrics.SetCumulativeResolution(aLayerMetrics.GetCumulativeResolution()); mFrameMetrics.SetHasScrollgrab(aLayerMetrics.GetHasScrollgrab()); diff --git a/gfx/layers/apz/util/APZCCallbackHelper.cpp b/gfx/layers/apz/util/APZCCallbackHelper.cpp index e8990717e84..4fca6f7ed23 100644 --- a/gfx/layers/apz/util/APZCCallbackHelper.cpp +++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp @@ -143,7 +143,7 @@ APZCCallbackHelper::UpdateRootFrame(nsIDOMWindowUtils* aUtils, // The pres shell resolution is updated by the the async zoom since the // last paint. - float presShellResolution = aMetrics.mPresShellResolution + float presShellResolution = aMetrics.GetPresShellResolution() * aMetrics.GetAsyncZoom().scale; aUtils->SetResolutionAndScaleTo(presShellResolution, presShellResolution); diff --git a/gfx/layers/client/ClientTiledPaintedLayer.cpp b/gfx/layers/client/ClientTiledPaintedLayer.cpp index da803a4d09d..5189e1e2327 100644 --- a/gfx/layers/client/ClientTiledPaintedLayer.cpp +++ b/gfx/layers/client/ClientTiledPaintedLayer.cpp @@ -83,7 +83,7 @@ GetTransformToAncestorsParentLayer(Layer* aStart, const LayerMetricsWrapper& aAn // parent layer of the displayport-ancestor, which we don't reach in this // loop, so we don't need to worry about it. const FrameMetrics& metrics = iter.Metrics(); - transform.PostScale(metrics.mPresShellResolution, metrics.mPresShellResolution, 1.f); + transform.PostScale(metrics.GetPresShellResolution(), metrics.GetPresShellResolution(), 1.f); } } return transform; diff --git a/gfx/layers/client/TiledContentClient.cpp b/gfx/layers/client/TiledContentClient.cpp index f92b9a40e80..6b84dffb426 100644 --- a/gfx/layers/client/TiledContentClient.cpp +++ b/gfx/layers/client/TiledContentClient.cpp @@ -1272,7 +1272,7 @@ ClientTiledLayerBuffer::ValidateTile(TileClient aTile, #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY DrawDebugOverlay(drawTarget, aTileOrigin.x * mResolution, - aTileOrigin.y * mPresShellResolution, GetTileLength(), GetTileLength()); + aTileOrigin.y * GetPresShellResolution(), GetTileLength(), GetTileLength()); #endif ctxt = nullptr; diff --git a/gfx/layers/composite/AsyncCompositionManager.cpp b/gfx/layers/composite/AsyncCompositionManager.cpp index e981a7e6291..37c6a49c684 100644 --- a/gfx/layers/composite/AsyncCompositionManager.cpp +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -744,7 +744,7 @@ ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar, // aScrollbarIsDescendant hunk below we apply a resolution-cancelling // transform which ensures the scroll thumb isn't actually rendered // at a larger scale. - yTranslation *= metrics.mPresShellResolution; + yTranslation *= metrics.GetPresShellResolution(); } scrollbarTransform.PostScale(1.f, yScale, 1.f); @@ -772,7 +772,7 @@ ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar, xTranslation -= thumbOriginDeltaPL; if (aScrollbarIsDescendant) { - xTranslation *= metrics.mPresShellResolution; + xTranslation *= metrics.GetPresShellResolution(); } scrollbarTransform.PostScale(xScale, 1.f, 1.f); @@ -802,8 +802,8 @@ ApplyAsyncTransformToScrollbarForContent(Layer* aScrollbar, // in the same coordinate space. This requires applying the content // transform and then unapplying it after unapplying the async transform. Matrix4x4 resolutionCancellingTransform = - Matrix4x4::Scaling(metrics.mPresShellResolution, - metrics.mPresShellResolution, + Matrix4x4::Scaling(metrics.GetPresShellResolution(), + metrics.GetPresShellResolution(), 1.0f).Inverse(); Matrix4x4 asyncUntransform = (asyncTransform * apzc->GetOverscrollTransform()).Inverse(); Matrix4x4 contentTransform = aContent.GetTransform(); diff --git a/gfx/layers/composite/LayerManagerComposite.cpp b/gfx/layers/composite/LayerManagerComposite.cpp index 91d51b1ce0f..547b0a48d97 100644 --- a/gfx/layers/composite/LayerManagerComposite.cpp +++ b/gfx/layers/composite/LayerManagerComposite.cpp @@ -915,7 +915,7 @@ LayerManagerComposite::ComputeRenderIntegrity() Layer* rootScrollable = rootScrollableLayers[0]; const FrameMetrics& metrics = LayerMetricsWrapper::TopmostScrollableMetrics(rootScrollable); Matrix4x4 transform = rootScrollable->GetEffectiveTransform(); - transform.PostScale(metrics.mPresShellResolution, metrics.mPresShellResolution, 1); + transform.PostScale(metrics.GetPresShellResolution(), metrics.GetPresShellResolution(), 1); // Clip the screen rect to the document bounds Rect documentBounds = diff --git a/gfx/layers/ipc/CompositorParent.cpp b/gfx/layers/ipc/CompositorParent.cpp index e9b05d19c2b..0ab140ff404 100644 --- a/gfx/layers/ipc/CompositorParent.cpp +++ b/gfx/layers/ipc/CompositorParent.cpp @@ -481,6 +481,7 @@ CompositorParent::RecvWillStop() if (lts->mParent == this) { mLayerManager->ClearCachedResources(lts->mRoot); lts->mLayerManager = nullptr; + lts->mParent = nullptr; } } mLayerManager->Destroy(); @@ -1709,7 +1710,9 @@ CrossProcessCompositorParent::ForceComposite(LayerTransactionParent* aLayerTree) MonitorAutoLock lock(*sIndirectLayerTreesLock); parent = sIndirectLayerTrees[id].mParent; } - parent->ForceComposite(aLayerTree); + if (parent) { + parent->ForceComposite(aLayerTree); + } } bool diff --git a/gfx/layers/ipc/ImageBridgeChild.cpp b/gfx/layers/ipc/ImageBridgeChild.cpp index 596e978dc50..e41e7dab5f2 100644 --- a/gfx/layers/ipc/ImageBridgeChild.cpp +++ b/gfx/layers/ipc/ImageBridgeChild.cpp @@ -845,7 +845,7 @@ ImageBridgeChild::DeallocPTextureChild(PTextureChild* actor) } bool -ImageBridgeChild::RecvParentAsyncMessages(const InfallibleTArray& aMessages) +ImageBridgeChild::RecvParentAsyncMessages(InfallibleTArray&& aMessages) { for (AsyncParentMessageArray::index_type i = 0; i < aMessages.Length(); ++i) { const AsyncParentMessageData& message = aMessages[i]; diff --git a/gfx/layers/ipc/ImageBridgeChild.h b/gfx/layers/ipc/ImageBridgeChild.h index 6899cd06439..15f9f870f18 100644 --- a/gfx/layers/ipc/ImageBridgeChild.h +++ b/gfx/layers/ipc/ImageBridgeChild.h @@ -189,7 +189,7 @@ public: DeallocPTextureChild(PTextureChild* actor) MOZ_OVERRIDE; virtual bool - RecvParentAsyncMessages(const InfallibleTArray& aMessages) MOZ_OVERRIDE; + RecvParentAsyncMessages(InfallibleTArray&& aMessages) MOZ_OVERRIDE; TemporaryRef CreateImageClient(CompositableType aType); TemporaryRef CreateImageClientNow(CompositableType aType); diff --git a/gfx/layers/ipc/ImageBridgeParent.cpp b/gfx/layers/ipc/ImageBridgeParent.cpp index 6f77bcc0acd..d956c2bafc7 100644 --- a/gfx/layers/ipc/ImageBridgeParent.cpp +++ b/gfx/layers/ipc/ImageBridgeParent.cpp @@ -111,7 +111,7 @@ private: }; bool -ImageBridgeParent::RecvUpdate(const EditArray& aEdits, EditReplyArray* aReply) +ImageBridgeParent::RecvUpdate(EditArray&& aEdits, EditReplyArray* aReply) { AutoImageBridgeParentAsyncMessageSender autoAsyncMessageSender(this); @@ -144,10 +144,10 @@ ImageBridgeParent::RecvUpdate(const EditArray& aEdits, EditReplyArray* aReply) } bool -ImageBridgeParent::RecvUpdateNoSwap(const EditArray& aEdits) +ImageBridgeParent::RecvUpdateNoSwap(EditArray&& aEdits) { InfallibleTArray noReplies; - bool success = RecvUpdate(aEdits, &noReplies); + bool success = RecvUpdate(Move(aEdits), &noReplies); NS_ABORT_IF_FALSE(noReplies.Length() == 0, "RecvUpdateNoSwap requires a sync Update to carry Edits"); return success; } @@ -268,7 +268,7 @@ ImageBridgeParent::SendAsyncMessage(const InfallibleTArray& aMessages) +ImageBridgeParent::RecvChildAsyncMessages(InfallibleTArray&& aMessages) { for (AsyncChildMessageArray::index_type i = 0; i < aMessages.Length(); ++i) { const AsyncChildMessageData& message = aMessages[i]; diff --git a/gfx/layers/ipc/ImageBridgeParent.h b/gfx/layers/ipc/ImageBridgeParent.h index 2379c91193e..83eb71d6cfc 100644 --- a/gfx/layers/ipc/ImageBridgeParent.h +++ b/gfx/layers/ipc/ImageBridgeParent.h @@ -71,8 +71,8 @@ public: } // PImageBridge - virtual bool RecvUpdate(const EditArray& aEdits, EditReplyArray* aReply) MOZ_OVERRIDE; - virtual bool RecvUpdateNoSwap(const EditArray& aEdits) MOZ_OVERRIDE; + virtual bool RecvUpdate(EditArray&& aEdits, EditReplyArray* aReply) MOZ_OVERRIDE; + virtual bool RecvUpdateNoSwap(EditArray&& aEdits) MOZ_OVERRIDE; virtual bool IsAsync() const MOZ_OVERRIDE { return true; } @@ -85,7 +85,7 @@ public: virtual bool DeallocPTextureParent(PTextureParent* actor) MOZ_OVERRIDE; virtual bool - RecvChildAsyncMessages(const InfallibleTArray& aMessages) MOZ_OVERRIDE; + RecvChildAsyncMessages(InfallibleTArray&& aMessages) MOZ_OVERRIDE; // Shutdown step 1 virtual bool RecvWillStop() MOZ_OVERRIDE; diff --git a/gfx/layers/ipc/LayerTransactionChild.cpp b/gfx/layers/ipc/LayerTransactionChild.cpp index ce10a68a63c..392e4d5330c 100644 --- a/gfx/layers/ipc/LayerTransactionChild.cpp +++ b/gfx/layers/ipc/LayerTransactionChild.cpp @@ -75,7 +75,7 @@ LayerTransactionChild::DeallocPCompositableChild(PCompositableChild* actor) } bool -LayerTransactionChild::RecvParentAsyncMessages(const InfallibleTArray& aMessages) +LayerTransactionChild::RecvParentAsyncMessages(InfallibleTArray&& aMessages) { for (AsyncParentMessageArray::index_type i = 0; i < aMessages.Length(); ++i) { const AsyncParentMessageData& message = aMessages[i]; diff --git a/gfx/layers/ipc/LayerTransactionChild.h b/gfx/layers/ipc/LayerTransactionChild.h index 05eb69866b9..2a118053eeb 100644 --- a/gfx/layers/ipc/LayerTransactionChild.h +++ b/gfx/layers/ipc/LayerTransactionChild.h @@ -70,7 +70,7 @@ protected: virtual bool DeallocPTextureChild(PTextureChild* actor) MOZ_OVERRIDE; virtual bool - RecvParentAsyncMessages(const InfallibleTArray& aMessages) MOZ_OVERRIDE; + RecvParentAsyncMessages(InfallibleTArray&& aMessages) MOZ_OVERRIDE; virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE; diff --git a/gfx/layers/ipc/LayerTransactionParent.cpp b/gfx/layers/ipc/LayerTransactionParent.cpp index 8b3fe257737..55a066fae55 100644 --- a/gfx/layers/ipc/LayerTransactionParent.cpp +++ b/gfx/layers/ipc/LayerTransactionParent.cpp @@ -190,7 +190,7 @@ LayerTransactionParent::GetCompositorBackendType() const } bool -LayerTransactionParent::RecvUpdateNoSwap(const InfallibleTArray& cset, +LayerTransactionParent::RecvUpdateNoSwap(InfallibleTArray&& cset, const uint64_t& aTransactionId, const TargetConfig& targetConfig, const bool& isFirstPaint, @@ -199,7 +199,7 @@ LayerTransactionParent::RecvUpdateNoSwap(const InfallibleTArray& cset, const bool& isRepeatTransaction, const mozilla::TimeStamp& aTransactionStart) { - return RecvUpdate(cset, aTransactionId, targetConfig, isFirstPaint, + return RecvUpdate(Move(cset), aTransactionId, targetConfig, isFirstPaint, scheduleComposite, paintSequenceNumber, isRepeatTransaction, aTransactionStart, nullptr); } @@ -220,7 +220,7 @@ private: }; bool -LayerTransactionParent::RecvUpdate(const InfallibleTArray& cset, +LayerTransactionParent::RecvUpdate(InfallibleTArray&& cset, const uint64_t& aTransactionId, const TargetConfig& targetConfig, const bool& isFirstPaint, @@ -878,7 +878,7 @@ LayerTransactionParent::DeallocPTextureParent(PTextureParent* actor) } bool -LayerTransactionParent::RecvChildAsyncMessages(const InfallibleTArray& aMessages) +LayerTransactionParent::RecvChildAsyncMessages(InfallibleTArray&& aMessages) { AutoLayerTransactionParentAsyncMessageSender autoAsyncMessageSender(this); diff --git a/gfx/layers/ipc/LayerTransactionParent.h b/gfx/layers/ipc/LayerTransactionParent.h index a0739091546..17536e3057d 100644 --- a/gfx/layers/ipc/LayerTransactionParent.h +++ b/gfx/layers/ipc/LayerTransactionParent.h @@ -103,7 +103,7 @@ public: protected: virtual bool RecvShutdown() MOZ_OVERRIDE; - virtual bool RecvUpdate(const EditArray& cset, + virtual bool RecvUpdate(EditArray&& cset, const uint64_t& aTransactionId, const TargetConfig& targetConfig, const bool& isFirstPaint, @@ -113,7 +113,7 @@ protected: const mozilla::TimeStamp& aTransactionStart, EditReplyArray* reply) MOZ_OVERRIDE; - virtual bool RecvUpdateNoSwap(const EditArray& cset, + virtual bool RecvUpdateNoSwap(EditArray&& cset, const uint64_t& aTransactionId, const TargetConfig& targetConfig, const bool& isFirstPaint, @@ -147,7 +147,7 @@ protected: virtual bool DeallocPTextureParent(PTextureParent* actor) MOZ_OVERRIDE; virtual bool - RecvChildAsyncMessages(const InfallibleTArray& aMessages) MOZ_OVERRIDE; + RecvChildAsyncMessages(InfallibleTArray&& aMessages) MOZ_OVERRIDE; virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE; diff --git a/gfx/tests/gtest/TestAsyncPanZoomController.cpp b/gfx/tests/gtest/TestAsyncPanZoomController.cpp index 3822a8c86e8..966b1824157 100644 --- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp +++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp @@ -821,7 +821,7 @@ TEST_F(APZCBasicTester, ComplexTransform) { metrics.SetScrollOffset(CSSPoint(10, 10)); metrics.SetScrollableRect(CSSRect(0, 0, 50, 50)); metrics.SetCumulativeResolution(LayoutDeviceToLayerScale(2)); - metrics.mPresShellResolution = 2.0f; + metrics.SetPresShellResolution(2.0f); metrics.SetZoom(CSSToParentLayerScale(6)); metrics.SetDevPixelsPerCSSPixel(CSSToLayoutDeviceScale(3)); metrics.SetScrollId(FrameMetrics::START_SCROLL_ID); diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index 61feab91b43..5a677efc022 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -2269,7 +2269,7 @@ gfxFont::Measure(gfxTextRun *aTextRun, if (isRTL) { glyphRect -= gfxPoint(advance, 0); } - glyphRect += gfxPoint(x, 0); + glyphRect += glyphPt; metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect); x += direction*advance; } diff --git a/hal/sandbox/SandboxHal.cpp b/hal/sandbox/SandboxHal.cpp index d19b0375d3b..2f79c833151 100644 --- a/hal/sandbox/SandboxHal.cpp +++ b/hal/sandbox/SandboxHal.cpp @@ -502,8 +502,8 @@ public: } virtual bool - RecvVibrate(const InfallibleTArray& pattern, - const InfallibleTArray &id, + RecvVibrate(InfallibleTArray&& pattern, + InfallibleTArray&& id, PBrowserParent *browserParent) MOZ_OVERRIDE { // We give all content vibration permission. @@ -516,7 +516,7 @@ public: } virtual bool - RecvCancelVibrate(const InfallibleTArray &id, + RecvCancelVibrate(InfallibleTArray &&id, PBrowserParent *browserParent) MOZ_OVERRIDE { TabParent *tabParent = static_cast(browserParent); diff --git a/image/src/RasterImage.cpp b/image/src/RasterImage.cpp index f4fd4ed0d6e..b16864940bc 100644 --- a/image/src/RasterImage.cpp +++ b/image/src/RasterImage.cpp @@ -761,8 +761,11 @@ RasterImage::GetFrameInternal(uint32_t aWhichFrame, } already_AddRefed -RasterImage::GetCurrentImage() +RasterImage::GetCurrentImage(ImageContainer* aContainer) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aContainer); + RefPtr surface = GetFrameInternal(FRAME_CURRENT, FLAG_NONE, /* aShouldSyncNotify = */ false); if (!surface) { @@ -771,16 +774,13 @@ RasterImage::GetCurrentImage() return nullptr; } - if (!mImageContainer) { - mImageContainer = LayerManager::CreateImageContainer(); - } - CairoImage::Data cairoData; GetWidth(&cairoData.mSize.width); GetHeight(&cairoData.mSize.height); cairoData.mSourceSurface = surface; - nsRefPtr image = mImageContainer->CreateImage(ImageFormat::CAIRO_SURFACE); + nsRefPtr image = + aContainer->CreateImage(ImageFormat::CAIRO_SURFACE); NS_ASSERTION(image, "Failed to create Image"); static_cast(image.get())->SetData(cairoData); @@ -792,8 +792,13 @@ RasterImage::GetCurrentImage() NS_IMETHODIMP RasterImage::GetImageContainer(LayerManager* aManager, ImageContainer **_retval) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aManager); + int32_t maxTextureSize = aManager->GetMaxTextureSize(); - if (mSize.width > maxTextureSize || mSize.height > maxTextureSize) { + if (!mHasSize || + mSize.width > maxTextureSize || + mSize.height > maxTextureSize) { *_retval = nullptr; return NS_OK; } @@ -802,30 +807,27 @@ RasterImage::GetImageContainer(LayerManager* aManager, ImageContainer **_retval) mProgressTracker->OnUnlockedDraw(); } - if (!mImageContainer) { - mImageContainer = mImageContainerCache; - } - - if (mImageContainer) { - *_retval = mImageContainer; - NS_ADDREF(*_retval); + nsRefPtr container = mImageContainer.get(); + if (container) { + container.forget(_retval); return NS_OK; } - nsRefPtr image = GetCurrentImage(); + // We need a new ImageContainer, so create one. + container = LayerManager::CreateImageContainer(); + + nsRefPtr image = GetCurrentImage(container); if (!image) { return NS_ERROR_NOT_AVAILABLE; } - mImageContainer->SetCurrentImageInTransaction(image); - *_retval = mImageContainer; - NS_ADDREF(*_retval); - // We only need to be careful about holding on to the image when it is - // discardable by the OS. - if (CanDiscard()) { - mImageContainerCache = mImageContainer; - mImageContainer = nullptr; - } + // |image| holds a reference to a SourceSurface which in turn holds a lock on + // the current frame's VolatileBuffer, ensuring that it doesn't get freed as + // long as the layer system keeps this ImageContainer alive. + container->SetCurrentImageInTransaction(image); + + mImageContainer = container; + container.forget(_retval); return NS_OK; } @@ -833,16 +835,19 @@ RasterImage::GetImageContainer(LayerManager* aManager, ImageContainer **_retval) void RasterImage::UpdateImageContainer() { - if (!mImageContainer) { + MOZ_ASSERT(NS_IsMainThread()); + + nsRefPtr container = mImageContainer.get(); + if (!container) { return; } - nsRefPtr image = GetCurrentImage(); + nsRefPtr image = GetCurrentImage(container); if (!image) { return; } - mImageContainer->SetCurrentImage(image); + container->SetCurrentImage(image); } size_t diff --git a/image/src/RasterImage.h b/image/src/RasterImage.h index 24c5eace802..6a88791f20b 100644 --- a/image/src/RasterImage.h +++ b/image/src/RasterImage.h @@ -314,7 +314,8 @@ private: size_t SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation, MallocSizeOf aMallocSizeOf) const; - already_AddRefed GetCurrentImage(); + already_AddRefed + GetCurrentImage(layers::ImageContainer* aContainer); void UpdateImageContainer(); // We would like to just check if we have a zero lock count, but we can't do @@ -360,11 +361,9 @@ private: // data // A hint for image decoder that directly scale the image to smaller buffer int mRequestedSampleSize; - // Cached value for GetImageContainer. - nsRefPtr mImageContainer; - - // If not cached in mImageContainer, this might have our image container - WeakPtr mImageContainerCache; + // A weak pointer to our ImageContainer, which stays alive only as long as + // the layer system needs it. + WeakPtr mImageContainer; #ifdef DEBUG uint32_t mFramesNotified; diff --git a/ipc/glue/ProcessUtils_linux.cpp b/ipc/glue/ProcessUtils_linux.cpp index 09d020d8698..659119570fd 100644 --- a/ipc/glue/ProcessUtils_linux.cpp +++ b/ipc/glue/ProcessUtils_linux.cpp @@ -430,9 +430,9 @@ public: virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE; - virtual bool RecvLoad(const InfallibleTArray& aArgv, - const InfallibleTArray& aEnv, - const InfallibleTArray& aFdsremap, + virtual bool RecvLoad(InfallibleTArray&& aArgv, + InfallibleTArray&& aEnv, + InfallibleTArray&& aFdsremap, const uint32_t& aPrivs, const int32_t& aCookie); @@ -453,9 +453,9 @@ _ProcLoaderChildDestroy(ProcLoaderChild *aChild) } bool -ProcLoaderChild::RecvLoad(const InfallibleTArray& aArgv, - const InfallibleTArray& aEnv, - const InfallibleTArray& aFdsRemap, +ProcLoaderChild::RecvLoad(InfallibleTArray&& aArgv, + InfallibleTArray&& aEnv, + InfallibleTArray&& aFdsRemap, const uint32_t& aPrivs, const int32_t& aCookie) { if (!sProcLoaderServing) { diff --git a/ipc/ipdl/ipdl/cxx/ast.py b/ipc/ipdl/ipdl/cxx/ast.py index 7cb8839d8ef..56061c27cfd 100644 --- a/ipc/ipdl/ipdl/cxx/ast.py +++ b/ipc/ipdl/ipdl/cxx/ast.py @@ -663,6 +663,10 @@ class ExprCall(Node): self.func = func self.args = args +class ExprMove(ExprCall): + def __init__(self, arg): + ExprCall.__init__(self, ExprVar("mozilla::Move"), args=[arg]) + class ExprNew(Node): # XXX taking some poetic license ... def __init__(self, ctype, args=[ ], newargs=None): diff --git a/ipc/ipdl/ipdl/cxx/cgen.py b/ipc/ipdl/ipdl/cxx/cgen.py index 0febdb6ec4e..5d739e85351 100644 --- a/ipc/ipdl/ipdl/cxx/cgen.py +++ b/ipc/ipdl/ipdl/cxx/cgen.py @@ -47,7 +47,7 @@ class CxxCodeGen(CodePrinter, Visitor): elif t.ptrptr: ts += '**' elif t.ptrconstptr: ts += '* const*' - if t.ref: ts += '&' + ts += '&' * t.ref self.write(ts) @@ -357,6 +357,9 @@ class CxxCodeGen(CodePrinter, Visitor): self.writeExprList(ec.args) self.write(')') + def visitExprMove(self, em): + self.visitExprCall(em) + def visitExprNew(self, en): self.write('new ') if en.newargs is not None: diff --git a/ipc/ipdl/ipdl/lower.py b/ipc/ipdl/ipdl/lower.py index 031b15e14ed..3a6a9d67737 100644 --- a/ipc/ipdl/ipdl/lower.py +++ b/ipc/ipdl/ipdl/lower.py @@ -548,6 +548,13 @@ def _cxxConstRefType(ipdltype, side): t.ref = 1 return t +def _cxxMoveRefType(ipdltype, side): + t = _cxxBareType(ipdltype, side) + if ipdltype.isIPDL() and (ipdltype.isArray() or ipdltype.isShmem()): + t.ref = 2 + return t + return _cxxConstRefType(ipdltype, side) + def _cxxPtrToType(ipdltype, side): t = _cxxBareType(ipdltype, side) if ipdltype.isIPDL() and ipdltype.isActor(): @@ -602,6 +609,10 @@ necessarily a C++ reference.""" """Return this decl's C++ type as a const, 'reference' type.""" return _cxxConstRefType(self.ipdltype, side) + def rvalueRefType(self, side): + """Return this decl's C++ type as an r-value 'reference' type.""" + return _cxxMoveRefType(self.ipdltype, side) + def ptrToType(self, side): return _cxxPtrToType(self.ipdltype, side) @@ -614,6 +625,12 @@ necessarily a C++ reference.""" return self.bareType(side) return self.constRefType(side) + def moveType(self, side): + """Return this decl's C++ Type with move semantics.""" + if self.ipdltype.isIPDL() and self.ipdltype.isActor(): + return self.bareType(side) + return self.rvalueRefType(side); + def outType(self, side): """Return this decl's C++ Type with outparam semantics.""" t = self.bareType(side) @@ -939,6 +956,8 @@ class MessageDecl(ipdl.ast.MessageDecl): def makeDecl(d, sems): if sems is 'in': return Decl(d.inType(side), d.name) + elif sems is 'move': + return Decl(d.moveType(side), d.name) elif sems is 'out': return Decl(d.outType(side), d.name) else: assert 0 @@ -962,7 +981,7 @@ class MessageDecl(ipdl.ast.MessageDecl): cxxargs = [ ] if params: - cxxargs.extend([ p.var() for p in self.params ]) + cxxargs.extend([ ExprMove(p.var()) for p in self.params ]) for ret in self.returns: if retsems is 'in': @@ -2809,7 +2828,7 @@ class _GenerateProtocolActorCode(ipdl.ast.Visitor): implicit = (not isdtor) recvDecl = MethodDecl( md.recvMethod().name, - params=md.makeCxxParams(paramsems='in', returnsems='out', + params=md.makeCxxParams(paramsems='move', returnsems='out', side=self.side, implicit=implicit), ret=Type.BOOL, virtual=1) diff --git a/js/ipc/JavaScriptBase.h b/js/ipc/JavaScriptBase.h index 2ae230bb0f7..7ecc591e2e9 100644 --- a/js/ipc/JavaScriptBase.h +++ b/js/ipc/JavaScriptBase.h @@ -84,10 +84,10 @@ class JavaScriptBase : public WrapperOwner, public WrapperAnswer, public Base bool *result) { return Answer::RecvIsExtensible(ObjectId::deserialize(objId), rs, result); } - bool RecvCallOrConstruct(const uint64_t &objId, const nsTArray &argv, - const bool &construct, ReturnStatus *rs, JSVariant *result, - nsTArray *outparams) { - return Answer::RecvCallOrConstruct(ObjectId::deserialize(objId), argv, construct, rs, result, outparams); + bool RecvCallOrConstruct(const uint64_t &objId, InfallibleTArray &&argv, + const bool &construct, ReturnStatus *rs, JSVariant *result, + nsTArray *outparams) { + return Answer::RecvCallOrConstruct(ObjectId::deserialize(objId), Move(argv), construct, rs, result, outparams); } bool RecvHasInstance(const uint64_t &objId, const JSVariant &v, ReturnStatus *rs, bool *bp) { return Answer::RecvHasInstance(ObjectId::deserialize(objId), v, rs, bp); diff --git a/js/ipc/JavaScriptParent.cpp b/js/ipc/JavaScriptParent.cpp index 6c206e57720..3411c537bc5 100644 --- a/js/ipc/JavaScriptParent.cpp +++ b/js/ipc/JavaScriptParent.cpp @@ -51,11 +51,9 @@ JavaScriptParent::init() void JavaScriptParent::trace(JSTracer *trc) { - if (active()) { - objects_.trace(trc); - unwaivedObjectIds_.trace(trc); - waivedObjectIds_.trace(trc); - } + objects_.trace(trc); + unwaivedObjectIds_.trace(trc); + waivedObjectIds_.trace(trc); } JSObject * diff --git a/js/ipc/JavaScriptShared.cpp b/js/ipc/JavaScriptShared.cpp index 6c0179a6982..c5537e05af8 100644 --- a/js/ipc/JavaScriptShared.cpp +++ b/js/ipc/JavaScriptShared.cpp @@ -73,6 +73,18 @@ IdToObjectMap::remove(ObjectId id) table_.remove(id); } +void +IdToObjectMap::clear() +{ + table_.clear(); +} + +bool +IdToObjectMap::empty() const +{ + return table_.empty(); +} + ObjectToIdMap::ObjectToIdMap() : table_(nullptr) { @@ -157,6 +169,12 @@ ObjectToIdMap::remove(JSObject *obj) table_->remove(obj); } +void +ObjectToIdMap::clear() +{ + table_->clear(); +} + bool JavaScriptShared::sLoggingInitialized; bool JavaScriptShared::sLoggingEnabled; bool JavaScriptShared::sStackLoggingEnabled; @@ -181,6 +199,11 @@ JavaScriptShared::JavaScriptShared(JSRuntime *rt) } } +JavaScriptShared::~JavaScriptShared() +{ + MOZ_RELEASE_ASSERT(cpows_.empty()); +} + bool JavaScriptShared::init() { diff --git a/js/ipc/JavaScriptShared.h b/js/ipc/JavaScriptShared.h index 6d0ea40d28b..fb381680b94 100644 --- a/js/ipc/JavaScriptShared.h +++ b/js/ipc/JavaScriptShared.h @@ -110,6 +110,9 @@ class IdToObjectMap JSObject *find(ObjectId id); void remove(ObjectId id); + void clear(); + bool empty() const; + private: Table table_; }; @@ -131,6 +134,7 @@ class ObjectToIdMap bool add(JSContext *cx, JSObject *obj, ObjectId id); ObjectId find(JSObject *obj); void remove(JSObject *obj); + void clear(); private: static void keyMarkCallback(JSTracer *trc, JSObject *key, void *data); @@ -144,7 +148,7 @@ class JavaScriptShared { public: explicit JavaScriptShared(JSRuntime *rt); - virtual ~JavaScriptShared() {} + virtual ~JavaScriptShared(); bool init(); diff --git a/js/ipc/WrapperAnswer.cpp b/js/ipc/WrapperAnswer.cpp index c9ff9059a35..21a4d3399bd 100644 --- a/js/ipc/WrapperAnswer.cpp +++ b/js/ipc/WrapperAnswer.cpp @@ -387,7 +387,7 @@ WrapperAnswer::RecvIsExtensible(const ObjectId &objId, ReturnStatus *rs, bool *r bool WrapperAnswer::RecvCallOrConstruct(const ObjectId &objId, - const nsTArray &argv, + InfallibleTArray &&argv, const bool &construct, ReturnStatus *rs, JSVariant *result, diff --git a/js/ipc/WrapperAnswer.h b/js/ipc/WrapperAnswer.h index 772416abb2b..b35de2dbf6c 100644 --- a/js/ipc/WrapperAnswer.h +++ b/js/ipc/WrapperAnswer.h @@ -46,7 +46,7 @@ class WrapperAnswer : public virtual JavaScriptShared bool RecvIsExtensible(const ObjectId &objId, ReturnStatus *rs, bool *result); - bool RecvCallOrConstruct(const ObjectId &objId, const nsTArray &argv, + bool RecvCallOrConstruct(const ObjectId &objId, InfallibleTArray &&argv, const bool &construct, ReturnStatus *rs, JSVariant *result, nsTArray *outparams); bool RecvHasInstance(const ObjectId &objId, const JSVariant &v, ReturnStatus *rs, bool *bp); diff --git a/js/ipc/WrapperOwner.cpp b/js/ipc/WrapperOwner.cpp index 4b04532250f..d306d165277 100644 --- a/js/ipc/WrapperOwner.cpp +++ b/js/ipc/WrapperOwner.cpp @@ -877,6 +877,10 @@ void WrapperOwner::ActorDestroy(ActorDestroyReason why) { inactive_ = true; + + objects_.clear(); + unwaivedObjectIds_.clear(); + waivedObjectIds_.clear(); } bool diff --git a/js/public/ProfilingFrameIterator.h b/js/public/ProfilingFrameIterator.h index 52bb3a4f301..4f48034318a 100644 --- a/js/public/ProfilingFrameIterator.h +++ b/js/public/ProfilingFrameIterator.h @@ -19,6 +19,10 @@ struct JSRuntime; namespace js { class Activation; class AsmJSProfilingFrameIterator; + namespace jit { + class JitActivation; + class JitProfilingFrameIterator; + } } namespace JS { @@ -29,19 +33,39 @@ namespace JS { // unwound. class JS_PUBLIC_API(ProfilingFrameIterator) { + JSRuntime *rt_; js::Activation *activation_; + // When moving past a JitActivation, we need to save the prevJitTop + // from it to use as the exit-frame pointer when the next caller jit + // activation (if any) comes around. + void *savedPrevJitTop_; + static const unsigned StorageSpace = 6 * sizeof(void*); mozilla::AlignedStorage storage_; js::AsmJSProfilingFrameIterator &asmJSIter() { MOZ_ASSERT(!done()); + MOZ_ASSERT(isAsmJS()); return *reinterpret_cast(storage_.addr()); } const js::AsmJSProfilingFrameIterator &asmJSIter() const { MOZ_ASSERT(!done()); + MOZ_ASSERT(isAsmJS()); return *reinterpret_cast(storage_.addr()); } + js::jit::JitProfilingFrameIterator &jitIter() { + MOZ_ASSERT(!done()); + MOZ_ASSERT(isJit()); + return *reinterpret_cast(storage_.addr()); + } + + const js::jit::JitProfilingFrameIterator &jitIter() const { + MOZ_ASSERT(!done()); + MOZ_ASSERT(isJit()); + return *reinterpret_cast(storage_.addr()); + } + void settle(); public: @@ -65,15 +89,31 @@ class JS_PUBLIC_API(ProfilingFrameIterator) // and less than older native and psuedo-stack frame addresses void *stackAddress() const; - // Return a label suitable for regexp-matching as performed by - // browser/devtools/profiler/cleopatra/js/parserWorker.js - const char *label() const; + enum FrameKind + { + Frame_Baseline, + Frame_Ion, + Frame_AsmJS + }; + + struct Frame + { + FrameKind kind; + void *stackAddress; + void *returnAddress; + void *activation; + const char *label; + }; + uint32_t extractStack(Frame *frames, uint32_t offset, uint32_t end) const; private: void iteratorConstruct(const RegisterState &state); void iteratorConstruct(); void iteratorDestroy(); bool iteratorDone(); + + bool isAsmJS() const; + bool isJit() const; }; } // namespace JS diff --git a/js/public/ProfilingStack.h b/js/public/ProfilingStack.h index da4ebaa6eb1..d66eb264f66 100644 --- a/js/public/ProfilingStack.h +++ b/js/public/ProfilingStack.h @@ -60,24 +60,29 @@ class ProfileEntry // sample of the pseudostack. FRAME_LABEL_COPY = 0x02, - // This ProfileEntry was pushed immediately before calling into asm.js. - ASMJS = 0x04, + // This ProfileEntry is a dummy entry indicating the start of a run + // of JS pseudostack entries. + BEGIN_PSEUDO_JS = 0x04, + + // This flag is used to indicate that an interpreter JS entry has OSR-ed + // into baseline. + OSR = 0x08, // Mask for removing all flags except the category information. - CATEGORY_MASK = ~IS_CPP_ENTRY & ~FRAME_LABEL_COPY & ~ASMJS + CATEGORY_MASK = ~IS_CPP_ENTRY & ~FRAME_LABEL_COPY & ~BEGIN_PSEUDO_JS & ~OSR }; // Keep these in sync with browser/devtools/profiler/utils/global.js MOZ_BEGIN_NESTED_ENUM_CLASS(Category, uint32_t) - OTHER = 0x08, - CSS = 0x10, - JS = 0x20, - GC = 0x40, - CC = 0x80, - NETWORK = 0x100, - GRAPHICS = 0x200, - STORAGE = 0x400, - EVENTS = 0x800, + OTHER = 0x10, + CSS = 0x20, + JS = 0x40, + GC = 0x80, + CC = 0x100, + NETWORK = 0x200, + GRAPHICS = 0x400, + STORAGE = 0x800, + EVENTS = 0x1000, FIRST = OTHER, LAST = EVENTS @@ -126,6 +131,18 @@ class ProfileEntry return flags_ & CATEGORY_MASK; } + void setOSR() volatile { + MOZ_ASSERT(isJs()); + setFlag(OSR); + } + void unsetOSR() volatile { + MOZ_ASSERT(isJs()); + unsetFlag(OSR); + } + bool isOSR() const volatile { + return hasFlag(OSR); + } + void *stackAddress() const volatile { MOZ_ASSERT(!isJs()); return spOrScript; diff --git a/js/src/asmjs/AsmJSFrameIterator.cpp b/js/src/asmjs/AsmJSFrameIterator.cpp index df435d6676b..8fae349df03 100644 --- a/js/src/asmjs/AsmJSFrameIterator.cpp +++ b/js/src/asmjs/AsmJSFrameIterator.cpp @@ -414,6 +414,16 @@ AsmJSProfilingFrameIterator::AsmJSProfilingFrameIterator(const AsmJSActivation & exitReason_(AsmJSExit::None), codeRange_(nullptr) { + // If profiling hasn't been enabled for this module, then CallerFPFromFP + // will be trash, so ignore the entire activation. In practice, this only + // happens if profiling is enabled while module->active() (in this case, + // profiling will be enabled when the module becomes inactive and gets + // called again). + if (!module_->profilingEnabled()) { + MOZ_ASSERT(done()); + return; + } + initFromFP(activation); } diff --git a/js/src/asmjs/AsmJSValidate.cpp b/js/src/asmjs/AsmJSValidate.cpp index 8dfb08f1fc6..f61e89c6883 100644 --- a/js/src/asmjs/AsmJSValidate.cpp +++ b/js/src/asmjs/AsmJSValidate.cpp @@ -8509,22 +8509,49 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit // act.prevJitTop_ = cx->mainThread().jitTop; // act.prevJitJSContext_ = cx->mainThread().jitJSContext; // cx->mainThread().jitJSContext = cx; + // act.prevJitActivation_ = cx->mainThread().jitActivation; + // cx->mainThread().jitActivation = act; + // act.prevProfilingActivation_ = cx->mainThread().profilingActivation; + // cx->mainThread().profilingActivation_ = act; // On the ARM store8() uses the secondScratchReg (lr) as a temp. size_t offsetOfActivation = offsetof(JSRuntime, mainThread) + PerThreadData::offsetOfActivation(); size_t offsetOfJitTop = offsetof(JSRuntime, mainThread) + offsetof(PerThreadData, jitTop); size_t offsetOfJitJSContext = offsetof(JSRuntime, mainThread) + offsetof(PerThreadData, jitJSContext); + size_t offsetOfJitActivation = offsetof(JSRuntime, mainThread) + + offsetof(PerThreadData, jitActivation); + size_t offsetOfProfilingActivation = offsetof(JSRuntime, mainThread) + + PerThreadData::offsetOfProfilingActivation(); masm.loadAsmJSActivation(reg0); masm.loadPtr(Address(reg0, AsmJSActivation::offsetOfContext()), reg3); masm.loadPtr(Address(reg3, JSContext::offsetOfRuntime()), reg0); masm.loadPtr(Address(reg0, offsetOfActivation), reg1); + + // act.active_ = true; masm.store8(Imm32(1), Address(reg1, JitActivation::offsetOfActiveUint8())); + + // act.prevJitTop_ = cx->mainThread().jitTop; masm.loadPtr(Address(reg0, offsetOfJitTop), reg2); masm.storePtr(reg2, Address(reg1, JitActivation::offsetOfPrevJitTop())); + + // act.prevJitJSContext_ = cx->mainThread().jitJSContext; masm.loadPtr(Address(reg0, offsetOfJitJSContext), reg2); masm.storePtr(reg2, Address(reg1, JitActivation::offsetOfPrevJitJSContext())); + // cx->mainThread().jitJSContext = cx; masm.storePtr(reg3, Address(reg0, offsetOfJitJSContext)); + + // act.prevJitActivation_ = cx->mainThread().jitActivation; + masm.loadPtr(Address(reg0, offsetOfJitActivation), reg2); + masm.storePtr(reg2, Address(reg1, JitActivation::offsetOfPrevJitActivation())); + // cx->mainThread().jitActivation = act; + masm.storePtr(reg1, Address(reg0, offsetOfJitActivation)); + + // act.prevProfilingActivation_ = cx->mainThread().profilingActivation; + masm.loadPtr(Address(reg0, offsetOfProfilingActivation), reg2); + masm.storePtr(reg2, Address(reg1, Activation::offsetOfPrevProfiling())); + // cx->mainThread().profilingActivation_ = act; + masm.storePtr(reg1, Address(reg0, offsetOfProfilingActivation)); } // 2. Call @@ -8544,22 +8571,43 @@ GenerateFFIIonExit(ModuleCompiler &m, const ModuleCompiler::ExitDescriptor &exit Register reg2 = AsmJSIonExitRegD2; // The following is inlined: + // rt->mainThread.profilingActivation = prevProfilingActivation_; // rt->mainThread.activation()->active_ = false; // rt->mainThread.jitTop = prevJitTop_; // rt->mainThread.jitJSContext = prevJitJSContext_; + // rt->mainThread.jitActivation = prevJitActivation_; // On the ARM store8() uses the secondScratchReg (lr) as a temp. size_t offsetOfActivation = offsetof(JSRuntime, mainThread) + PerThreadData::offsetOfActivation(); size_t offsetOfJitTop = offsetof(JSRuntime, mainThread) + offsetof(PerThreadData, jitTop); size_t offsetOfJitJSContext = offsetof(JSRuntime, mainThread) + offsetof(PerThreadData, jitJSContext); + size_t offsetOfJitActivation = offsetof(JSRuntime, mainThread) + + offsetof(PerThreadData, jitActivation); + size_t offsetOfProfilingActivation = offsetof(JSRuntime, mainThread) + + PerThreadData::offsetOfProfilingActivation(); + masm.movePtr(AsmJSImmPtr(AsmJSImm_Runtime), reg0); masm.loadPtr(Address(reg0, offsetOfActivation), reg1); - masm.store8(Imm32(0), Address(reg1, JitActivation::offsetOfActiveUint8())); + + // rt->mainThread.jitTop = prevJitTop_; masm.loadPtr(Address(reg1, JitActivation::offsetOfPrevJitTop()), reg2); masm.storePtr(reg2, Address(reg0, offsetOfJitTop)); + + // rt->mainThread.profilingActivation = rt->mainThread.activation()->prevProfiling_; + masm.loadPtr(Address(reg1, Activation::offsetOfPrevProfiling()), reg2); + masm.storePtr(reg2, Address(reg0, offsetOfProfilingActivation)); + + // rt->mainThread.activation()->active_ = false; + masm.store8(Imm32(0), Address(reg1, JitActivation::offsetOfActiveUint8())); + + // rt->mainThread.jitJSContext = prevJitJSContext_; masm.loadPtr(Address(reg1, JitActivation::offsetOfPrevJitJSContext()), reg2); masm.storePtr(reg2, Address(reg0, offsetOfJitJSContext)); + + // rt->mainThread.jitActivation = prevJitActivation_; + masm.loadPtr(Address(reg1, JitActivation::offsetOfPrevJitActivation()), reg2); + masm.storePtr(reg2, Address(reg0, offsetOfJitActivation)); } MOZ_ASSERT(masm.framePushed() == framePushed); diff --git a/js/src/builtin/Array.js b/js/src/builtin/Array.js index f4fc89e017b..c6a07bfe790 100644 --- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -707,78 +707,87 @@ function ArrayKeys() { return CreateArrayIterator(this, ITEM_KIND_KEY); } -/* ES6 rev 25 (2014 May 22) 22.1.2.1 */ -function ArrayFrom(arrayLike, mapfn=undefined, thisArg=undefined) { +// ES6 draft rev31 (2015/01/15) 22.1.2.1 Array.from(source[, mapfn[, thisArg]]). +function ArrayFrom(items, mapfn=undefined, thisArg=undefined) { // Step 1. var C = this; // Steps 2-3. - var items = ToObject(arrayLike); - - // Steps 4-5. - var mapping = (mapfn !== undefined); + var mapping = mapfn !== undefined; if (mapping && !IsCallable(mapfn)) ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(1, mapfn)); + var T = thisArg; // All elements defined by this algorithm have the same attrs: var attrs = ATTR_CONFIGURABLE | ATTR_ENUMERABLE | ATTR_WRITABLE; - // Steps 6-8. - var usingIterator = items[std_iterator]; + // Steps 4-5. + var usingIterator = GetMethod(items, std_iterator); + + // Step 6. if (usingIterator !== undefined) { - // Steps 8.a-c. + // Steps 6.a-c. var A = IsConstructor(C) ? new C() : []; - // Steps 8.d-e. - var iterator = callFunction(usingIterator, items); + // Steps 6.d-e. + var iterator = GetIterator(items, usingIterator); - // Step 8.f. + // Step 6.f. var k = 0; - // Steps 8.g.i-vi. + // Step 6.g. // These steps cannot be implemented using a for-of loop. // See . - var next; while (true) { - // Steps 8.g.ii-vi. - next = iterator.next(); + // Steps 6.g.i-iii. + var next = iterator.next(); if (!IsObject(next)) ThrowError(JSMSG_NEXT_RETURNED_PRIMITIVE); - if (next.done) - break; // Substeps of 8.g.iv are implemented below. + + // Step 6.g.iv. + if (next.done) { + A.length = k; + return A; + } + + // Steps 6.g.v-vi. var nextValue = next.value; - // Steps 8.g.vii-viii. + // Steps 6.g.vii-viii. var mappedValue = mapping ? callFunction(mapfn, thisArg, nextValue, k) : nextValue; - // Steps 8.g.ix-xi. + // Steps 6.g.ix-xi. _DefineDataProperty(A, k++, mappedValue, attrs); } - } else { - // Step 9 is an assertion: items is not an Iterator. Testing this is - // literally the very last thing we did, so we don't assert here. - - // Steps 10-12. - // FIXME: Array operations should use ToLength (bug 924058). - var len = ToInteger(items.length); - - // Steps 13-15. - var A = IsConstructor(C) ? new C(len) : NewDenseArray(len); - - // Steps 16-17. - for (var k = 0; k < len; k++) { - // Steps 17.a-c. - var kValue = items[k]; - - // Steps 17.d-e. - var mappedValue = mapping ? callFunction(mapfn, thisArg, kValue, k) : kValue; - - // Steps 17.f-g. - _DefineDataProperty(A, k, mappedValue, attrs); - } } - // Steps 8.g.iv.1-3 and 18-20 are the same. - A.length = k; + // Step 7. + assert(usingIterator === undefined, "`items` can't be an Iterable after step 6.g.iv"); + + // Steps 8-9. + var arrayLike = ToObject(items); + + // Steps 10-11. + var len = ToLength(arrayLike.length); + + // Steps 12-14. + var A = IsConstructor(C) ? new C(len) : NewDenseArray(len); + + // Steps 15-16. + for (var k = 0; k < len; k++) { + // Steps 16.a-c. + var kValue = items[k]; + + // Steps 16.d-e. + var mappedValue = mapping ? callFunction(mapfn, thisArg, kValue, k) : kValue; + + // Steps 16.f-g. + _DefineDataProperty(A, k, mappedValue, attrs); + } + + // Steps 17-18. + A.length = len; + + // Step 19. return A; } diff --git a/js/src/gc/GCInternals.h b/js/src/gc/GCInternals.h index 81309133855..0a718a9ac32 100644 --- a/js/src/gc/GCInternals.h +++ b/js/src/gc/GCInternals.h @@ -145,7 +145,7 @@ CheckHashTablesAfterMovingGC(JSRuntime *rt); #ifdef JSGC_COMPACTING struct MovingTracer : JSTracer { - MovingTracer(JSRuntime *rt) : JSTracer(rt, Visit, TraceWeakMapKeysValues) {} + explicit MovingTracer(JSRuntime *rt) : JSTracer(rt, Visit, TraceWeakMapKeysValues) {} static void Visit(JSTracer *jstrc, void **thingp, JSGCTraceKind kind); static bool IsMovingTracer(JSTracer *trc) { diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index 65916c2f7c4..b493829df86 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -529,6 +529,7 @@ class GCRuntime // Public here for ReleaseArenaLists and FinalizeTypedArenas. void releaseArena(ArenaHeader *aheader, const AutoLockGC &lock); + void decommitArena(ArenaHeader *aheader, AutoLockGC &lock); void releaseHeldRelocatedArenas(); @@ -595,8 +596,8 @@ class GCRuntime void decommitAllWithoutUnlocking(const AutoLockGC &lock); void decommitArenas(AutoLockGC &lock); void expireChunksAndArenas(bool shouldShrink, AutoLockGC &lock); - void queueZonesForBackgroundSweep(js::gc::ZoneList& zones); - void sweepBackgroundThings(js::gc::ZoneList &zones, ThreadType threadType); + void queueZonesForBackgroundSweep(ZoneList &zones); + void sweepBackgroundThings(ZoneList &zones, LifoAlloc &freeBlocks, ThreadType threadType); void assertBackgroundSweepingFinished(); bool shouldCompact(); bool compactPhase(bool lastGC); @@ -752,7 +753,7 @@ class GCRuntime bool foundBlackGrayEdges; /* Singly linekd list of zones to be swept in the background. */ - js::gc::ZoneList backgroundSweepZones; + ZoneList backgroundSweepZones; /* * Free LIFO blocks are transferred to this allocator before being freed on * the background GC thread. diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index eec435e7194..cd85aece596 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -10,6 +10,7 @@ #include "jsprf.h" +#include "gc/GCInternals.h" #include "jit/IonCode.h" #include "js/SliceBudget.h" #include "vm/ArgumentsObject.h" @@ -1740,6 +1741,12 @@ GCMarker::processMarkStackTop(SliceBudget &budget) scan_value_array: MOZ_ASSERT(vp <= end); while (vp != end) { + budget.step(); + if (budget.isOverBudget()) { + pushValueArray(obj, vp, end); + return; + } + const Value &v = *vp++; if (v.isString()) { markAndScanString(obj, v.toString()); diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index c3140ed4602..85c7b87b4d4 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -316,7 +316,8 @@ ZoneList::check() const #endif } -bool ZoneList::isEmpty() const +bool +ZoneList::isEmpty() const { return head == nullptr; } diff --git a/js/src/jit-test/tests/asm.js/testProfiling.js b/js/src/jit-test/tests/asm.js/testProfiling.js index e1e2c1dc0d8..9c280741b39 100644 --- a/js/src/jit-test/tests/asm.js/testProfiling.js +++ b/js/src/jit-test/tests/asm.js/testProfiling.js @@ -5,15 +5,50 @@ load(libdir + "asserts.js"); if (!getBuildConfiguration()["arm-simulator"]) quit(); -function assertEqualStacks(got, expect) +function checkSubSequence(got, expect) { - // Strip off the " (script/library info)" - got = String(got).replace(/ \([^\)]*\)/g, ""); + var got_i = 0; + EXP: for (var exp_i = 0; exp_i < expect.length; exp_i++) { + var item = expect[exp_i]; + // Scan for next match in got. + while (got_i < got.length) { + if (got[got_i++] == expect[exp_i]) + continue EXP; + } + print("MISMATCH: " + got.join(",") + "\n" + + " VS " + expect.join(",")); + return false; + } + return true; +} - // Shorten FFI/entry trampolines - got = got.replace(/(fast|slow) FFI trampoline/g, "<").replace(/entry trampoline/g, ">"); +function assertStackContainsSeq(got, expect) +{ + var normalized = []; - assertEq(got, expect); + for (var i = 0; i < got.length; i++) { + if (got[i].length == 0) + continue; + var parts = got[i].split(','); + for (var j = 0; j < parts.length; j++) { + var frame = parts[j]; + frame = frame.replace(/ \([^\)]*\)/g, ""); + frame = frame.replace(/(fast|slow) FFI trampoline/g, "<"); + frame = frame.replace(/entry trampoline/g, ">"); + frame = frame.replace(/(\/[^\/,<]+)*\/testProfiling.js/g, ""); + frame = frame.replace(/testBuiltinD2D/g, ""); + frame = frame.replace(/testBuiltinF2F/g, ""); + frame = frame.replace(/testBuiltinDD2D/g, ""); + frame = frame.replace(/assertThrowsInstanceOf/g, ""); + frame = frame.replace(/^ffi[12]?/g, ""); + normalized.push(frame); + } + } + + var gotNorm = normalized.join(',').replace(/,+/g, ","); + gotNorm = gotNorm.replace(/^,/, "").replace(/,$/, ""); + + assertEq(checkSubSequence(gotNorm.split(','), expect.split(',')), true); } // Test profiling enablement while asm.js is running. @@ -28,15 +63,15 @@ var ffi = function(enable) { } var f = asmLink(asmCompile('global','ffis',USE_ASM + "var ffi=ffis.ffi; function g(i) { i=i|0; ffi(i|0) } function f(i) { i=i|0; g(i|0) } return f"), null, {ffi}); f(0); -assertEqualStacks(stacks, ""); +assertStackContainsSeq(stacks, "", true); f(+1); -assertEqualStacks(stacks, ""); +assertStackContainsSeq(stacks, "", true); f(0); -assertEqualStacks(stacks, ""); +assertStackContainsSeq(stacks, "<,g,f,>", true); f(-1); -assertEqualStacks(stacks, ""); +assertStackContainsSeq(stacks, "<,g,f,>", true); f(0); -assertEqualStacks(stacks, ""); +assertStackContainsSeq(stacks, "", true); // Enable profiling for the rest of the tests. enableSPSProfiling(); @@ -45,27 +80,27 @@ var f = asmLink(asmCompile(USE_ASM + "function f() { return 42 } return f")); enableSingleStepProfiling(); assertEq(f(), 42); var stacks = disableSingleStepProfiling(); -assertEqualStacks(stacks, ",>,f>,>,"); +assertStackContainsSeq(stacks, ">,f,>,>"); var f = asmLink(asmCompile(USE_ASM + "function g(i) { i=i|0; return (i+1)|0 } function f() { return g(42)|0 } return f")); enableSingleStepProfiling(); assertEq(f(), 43); var stacks = disableSingleStepProfiling(); -assertEqualStacks(stacks, ",>,f>,gf>,f>,>,"); +assertStackContainsSeq(stacks, ">,f,>,g,f,>,f,>,>"); var f = asmLink(asmCompile(USE_ASM + "function g1() { return 1 } function g2() { return 2 } function f(i) { i=i|0; return TBL[i&1]()|0 } var TBL=[g1,g2]; return f")); enableSingleStepProfiling(); assertEq(f(0), 1); assertEq(f(1), 2); var stacks = disableSingleStepProfiling(); -assertEqualStacks(stacks, ",>,f>,g1f>,f>,>,,>,f>,g2f>,f>,>,"); +assertStackContainsSeq(stacks, ">,f,>,g1,f,>,f,>,>,>,f,>,g2,f,>,f,>,>"); function testBuiltinD2D(name) { var f = asmLink(asmCompile('g', USE_ASM + "var fun=g.Math." + name + "; function f(d) { d=+d; return +fun(d) } return f"), this); enableSingleStepProfiling(); assertEq(f(.1), eval("Math." + name + "(.1)")); var stacks = disableSingleStepProfiling(); - assertEqualStacks(stacks, ",>,f>,Math." + name + "f>,f>,>,"); + assertStackContainsSeq(stacks, ">,f,>,Math." + name + ",f,>,f,>,>"); } for (name of ['sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'ceil', 'floor', 'exp', 'log']) testBuiltinD2D(name); @@ -74,7 +109,7 @@ function testBuiltinF2F(name) { enableSingleStepProfiling(); assertEq(f(.1), eval("Math.fround(Math." + name + "(Math.fround(.1)))")); var stacks = disableSingleStepProfiling(); - assertEqualStacks(stacks, ",>,f>,Math." + name + "f>,f>,>,"); + assertStackContainsSeq(stacks, ">,f,>,Math." + name + ",f,>,f,>,>"); } for (name of ['ceil', 'floor']) testBuiltinF2F(name); @@ -83,7 +118,7 @@ function testBuiltinDD2D(name) { enableSingleStepProfiling(); assertEq(f(.1, .2), eval("Math." + name + "(.1, .2)")); var stacks = disableSingleStepProfiling(); - assertEqualStacks(stacks, ",>,f>,Math." + name + "f>,f>,>,"); + assertStackContainsSeq(stacks, ">,f,>,Math." + name + ",f,>,f,>,>"); } for (name of ['atan2', 'pow']) testBuiltinDD2D(name); @@ -100,14 +135,14 @@ var f = asmLink(asmCompile('g','ffis', USE_ASM + "var ffi1=ffis.ffi1, ffi2=ffis. enableSingleStepProfiling(); assertEq(f(), 83); var stacks = disableSingleStepProfiling(); -assertEqualStacks(stacks, ",>,f>,,f>,,f>,>,"); -// Ion FFI exit +assertStackContainsSeq(stacks, ">,f,>,<,f,>,f,>,<,f,>,f,>,>"); + for (var i = 0; i < 20; i++) assertEq(f(), 83); enableSingleStepProfiling(); assertEq(f(), 83); var stacks = disableSingleStepProfiling(); -assertEqualStacks(stacks, ",>,f>,,f>,,f>,>,"); +assertStackContainsSeq(stacks, ">,f,>,<,f,>,f,>,<,f,>,f,>,>"); var ffi1 = function() { return 15 } var ffi2 = function() { return f2() + 17 } @@ -116,14 +151,17 @@ var {f1,f2} = asmLink(asmCompile('g','ffis', USE_ASM + "var ffi1=ffis.ffi1, ffi2 enableSingleStepProfiling(); assertEq(f1(), 32); var stacks = disableSingleStepProfiling(); -assertEqualStacks(stacks, ",>,f1>,,>,f2>,,f2>,>,,f1>,>,"); +assertStackContainsSeq(stacks, ">,f1,>,<,f1,>,>,<,f1,>,f2,>,<,f1,>,<,f2,>,<,f1,>,f2,>,<,f1,>,>,<,f1,>,<,f1,>,f1,>,>"); + + // Ion FFI exit for (var i = 0; i < 20; i++) assertEq(f1(), 32); enableSingleStepProfiling(); assertEq(f1(), 32); var stacks = disableSingleStepProfiling(); -assertEqualStacks(stacks, ",>,f1>,,>,f2>,,f2>,>,,f1>,>,"); +assertStackContainsSeq(stacks, ">,f1,>,<,f1,>,>,<,f1,>,f2,>,<,f1,>,<,f2,>,<,f1,>,f2,>,<,f1,>,>,<,f1,>,<,f1,>,f1,>,>"); + // Detachment exit var buf = new ArrayBuffer(BUF_CHANGE_MIN); @@ -132,7 +170,7 @@ var f = asmLink(asmCompile('g','ffis','buf', USE_ASM + 'var ffi = ffis.ffi; var enableSingleStepProfiling(); assertThrowsInstanceOf(f, InternalError); var stacks = disableSingleStepProfiling(); -assertEqualStacks(stacks, ",>,f>,,inline stubf>,,inline stubf>,"); +assertStackContainsSeq(stacks, ">,f,>,<,f,>,inline stub,f,>,<,f,>,inline stub,f,>"); // This takes forever to run. // Stack-overflow exit test diff --git a/js/src/jit/Bailouts.cpp b/js/src/jit/Bailouts.cpp index 25fa715d19e..2daf8612e42 100644 --- a/js/src/jit/Bailouts.cpp +++ b/js/src/jit/Bailouts.cpp @@ -40,6 +40,7 @@ jit::Bailout(BailoutStack *sp, BaselineBailoutInfo **bailoutInfo) BailoutFrameInfo bailoutData(jitActivations, sp); JitFrameIterator iter(jitActivations); MOZ_ASSERT(!iter.ionScript()->invalidated()); + CommonFrameLayout *currentFramePtr = iter.current(); TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); TraceLogTimestamp(logger, TraceLogger_Bailout); @@ -49,31 +50,17 @@ jit::Bailout(BailoutStack *sp, BaselineBailoutInfo **bailoutInfo) MOZ_ASSERT(IsBaselineEnabled(cx)); *bailoutInfo = nullptr; - bool poppedLastSPSFrame = false; uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, false, bailoutInfo, - /* excInfo = */ nullptr, &poppedLastSPSFrame); + /* excInfo = */ nullptr); MOZ_ASSERT(retval == BAILOUT_RETURN_OK || retval == BAILOUT_RETURN_FATAL_ERROR || retval == BAILOUT_RETURN_OVERRECURSED); MOZ_ASSERT_IF(retval == BAILOUT_RETURN_OK, *bailoutInfo != nullptr); if (retval != BAILOUT_RETURN_OK) { - // If the bailout failed, then bailout trampoline will pop the - // current frame and jump straight to exception handling code when - // this function returns. Any SPS entry pushed for this frame will - // be silently forgotten. - // - // We call ExitScript here to ensure that if the ionScript had SPS - // instrumentation, then the SPS entry for it is popped. - // - // However, if the bailout was during argument check, then a - // pseudostack frame would not have been pushed in the first - // place, so don't pop anything in that case. - bool popSPSFrame = iter.ionScript()->hasSPSInstrumentation() && - (SnapshotIterator(iter).bailoutKind() != Bailout_ArgumentCheck) && - !poppedLastSPSFrame; JSScript *script = iter.script(); - probes::ExitScript(cx, script, script->functionNonDelazifying(), popSPSFrame); + probes::ExitScript(cx, script, script->functionNonDelazifying(), + /* popSPSFrame = */ false); EnsureExitFrame(iter.jsFrame()); } @@ -89,6 +76,26 @@ jit::Bailout(BailoutStack *sp, BaselineBailoutInfo **bailoutInfo) if (iter.ionScript()->invalidated()) iter.ionScript()->decrementInvalidationCount(cx->runtime()->defaultFreeOp()); + // NB: Commentary on how |lastProfilingFrame| is set from bailouts. + // + // Once we return to jitcode, any following frames might get clobbered, + // but the current frame will not (as it will be clobbered "in-place" + // with a baseline frame that will share the same frame prefix). + // However, there may be multiple baseline frames unpacked from this + // single Ion frame, which means we will need to once again reset + // |lastProfilingFrame| to point to the correct unpacked last frame + // in |FinishBailoutToBaseline|. + // + // In the case of error, the jitcode will jump immediately to an + // exception handler, which will unwind the frames and properly set + // the |lastProfilingFrame| to point to the frame being resumed into + // (see |AutoResetLastProfilerFrameOnReturnFromException|). + // + // In both cases, we want to temporarily set the |lastProfilingFrame| + // to the current frame being bailed out, and then fix it up later. + if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) + cx->mainThread().jitActivation->setLastProfilingFrame(currentFramePtr); + return retval; } @@ -106,6 +113,7 @@ jit::InvalidationBailout(InvalidationBailoutStack *sp, size_t *frameSizeOut, JitActivationIterator jitActivations(cx->runtime()); BailoutFrameInfo bailoutData(jitActivations, sp); JitFrameIterator iter(jitActivations); + CommonFrameLayout *currentFramePtr = iter.current(); TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); TraceLogTimestamp(logger, TraceLogger_Invalidation); @@ -118,9 +126,8 @@ jit::InvalidationBailout(InvalidationBailoutStack *sp, size_t *frameSizeOut, MOZ_ASSERT(IsBaselineEnabled(cx)); *bailoutInfo = nullptr; - bool poppedLastSPSFrame = false; uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, true, bailoutInfo, - /* excInfo = */ nullptr, &poppedLastSPSFrame); + /* excInfo = */ nullptr); MOZ_ASSERT(retval == BAILOUT_RETURN_OK || retval == BAILOUT_RETURN_FATAL_ERROR || retval == BAILOUT_RETURN_OVERRECURSED); @@ -138,11 +145,9 @@ jit::InvalidationBailout(InvalidationBailoutStack *sp, size_t *frameSizeOut, // However, if the bailout was during argument check, then a // pseudostack frame would not have been pushed in the first // place, so don't pop anything in that case. - bool popSPSFrame = iter.ionScript()->hasSPSInstrumentation() && - (SnapshotIterator(iter).bailoutKind() != Bailout_ArgumentCheck) && - !poppedLastSPSFrame; JSScript *script = iter.script(); - probes::ExitScript(cx, script, script->functionNonDelazifying(), popSPSFrame); + probes::ExitScript(cx, script, script->functionNonDelazifying(), + /* popSPSFrame = */ false); JitFrameLayout *frame = iter.jsFrame(); JitSpew(JitSpew_IonInvalidate, "Bailout failed (%s): converting to exit frame", @@ -161,6 +166,10 @@ jit::InvalidationBailout(InvalidationBailoutStack *sp, size_t *frameSizeOut, iter.ionScript()->decrementInvalidationCount(cx->runtime()->defaultFreeOp()); + // Make the frame being bailed out the top profiled frame. + if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) + cx->mainThread().jitActivation->setLastProfilingFrame(currentFramePtr); + return retval; } @@ -181,7 +190,7 @@ uint32_t jit::ExceptionHandlerBailout(JSContext *cx, const InlineFrameIterator &frame, ResumeFromException *rfe, const ExceptionBailoutInfo &excInfo, - bool *overrecursed, bool *poppedLastSPSFrameOut) + bool *overrecursed) { // We can be propagating debug mode exceptions without there being an // actual exception pending. For instance, when we return false from an @@ -194,10 +203,11 @@ jit::ExceptionHandlerBailout(JSContext *cx, const InlineFrameIterator &frame, JitActivationIterator jitActivations(cx->runtime()); BailoutFrameInfo bailoutData(jitActivations, frame.frame()); JitFrameIterator iter(jitActivations); + CommonFrameLayout *currentFramePtr = iter.current(); BaselineBailoutInfo *bailoutInfo = nullptr; uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, true, - &bailoutInfo, &excInfo, poppedLastSPSFrameOut); + &bailoutInfo, &excInfo); if (retval == BAILOUT_RETURN_OK) { MOZ_ASSERT(bailoutInfo); @@ -226,6 +236,10 @@ jit::ExceptionHandlerBailout(JSContext *cx, const InlineFrameIterator &frame, MOZ_ASSERT(retval == BAILOUT_RETURN_FATAL_ERROR); } + // Make the frame being bailed out the top profiled frame. + if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) + cx->mainThread().jitActivation->setLastProfilingFrame(currentFramePtr); + return retval; } diff --git a/js/src/jit/Bailouts.h b/js/src/jit/Bailouts.h index c07f092b44f..aaf6cc5e8e6 100644 --- a/js/src/jit/Bailouts.h +++ b/js/src/jit/Bailouts.h @@ -209,7 +209,7 @@ class ExceptionBailoutInfo uint32_t ExceptionHandlerBailout(JSContext *cx, const InlineFrameIterator &frame, ResumeFromException *rfe, const ExceptionBailoutInfo &excInfo, - bool *overrecursed, bool *poppedLastSPSFrameOut); + bool *overrecursed); uint32_t FinishBailoutToBaseline(BaselineBailoutInfo *bailoutInfo); diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index a46e4f1f8ba..f5b29abd9e8 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -551,16 +551,13 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC, HandleFunction fun, HandleScript script, IonScript *ionScript, SnapshotIterator &iter, bool invalidate, BaselineStackBuilder &builder, AutoValueVector &startFrameFormals, MutableHandleFunction nextCallee, - jsbytecode **callPC, const ExceptionBailoutInfo *excInfo, - bool *poppedLastSPSFrameOut) + jsbytecode **callPC, const ExceptionBailoutInfo *excInfo) { // The Baseline frames we will reconstruct on the heap are not rooted, so GC // must be suppressed here. MOZ_ASSERT(cx->mainThread().suppressGC); MOZ_ASSERT(script->hasBaselineScript()); - MOZ_ASSERT(poppedLastSPSFrameOut); - MOZ_ASSERT(!*poppedLastSPSFrameOut); // Are we catching an exception? bool catchingException = excInfo && excInfo->catchingException(); @@ -626,16 +623,6 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC, uint32_t flags = 0; - // If SPS Profiler is enabled, mark the frame as having pushed an SPS entry. - // This may be wrong for the last frame of ArgumentCheck bailout, but - // that will be fixed later. - if (ionScript->hasSPSInstrumentation()) { - if (callerPC == nullptr) { - JitSpew(JitSpew_BaselineBailouts, " Setting SPS flag on top frame!"); - flags |= BaselineFrame::HAS_PUSHED_SPS_FRAME; - } - } - // If we are bailing to a script whose execution is observed, mark the // baseline frame as a debuggee frame. This is to cover the case where we // don't rematerialize the Ion frame via the Debugger. @@ -1098,35 +1085,6 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC, opReturnAddr = baselineScript->prologueEntryAddr(); JitSpew(JitSpew_BaselineBailouts, " Resuming into prologue."); - // If bailing into prologue, HAS_PUSHED_SPS_FRAME should not be set on frame. - blFrame->unsetPushedSPSFrame(); - - if (cx->runtime()->spsProfiler.enabled()) { - // 1. If resuming into inline code, then the top SPS entry will be - // for the outermost caller, and will have an uninitialized PC. - // This will be fixed up later in BailoutIonToBaseline. - // - // 2. If resuming into top-level code prologue, with ArgumentCheck, - // no SPS entry will have been pushed. Can be left alone. - // - // 3. If resuming into top-level code prologue, without ArgumentCheck, - // an SPS entry will have been pushed, and needs to be popped. - // - // 4. If resuming into top-level code main body, an SPS entry will - // have been pushed, and can be left alone. - // - // Only need to handle case 3 here. - if (!caller && bailoutKind != Bailout_ArgumentCheck) { - JitSpew(JitSpew_BaselineBailouts, - " Popping SPS entry for outermost frame"); - cx->runtime()->spsProfiler.exit(script, fun); - - // Notify caller that the last SPS frame was popped, so not - // to do it again. - if (poppedLastSPSFrameOut) - *poppedLastSPSFrameOut = true; - } - } } else { opReturnAddr = nativeCodeForPC; } @@ -1135,16 +1093,6 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC, } if (cx->runtime()->spsProfiler.enabled()) { - if (blFrame->hasPushedSPSFrame()) { - // Set PC index to 0 for the innermost frame to match what the - // interpreter and Baseline do: they update the SPS pc for - // JSOP_CALL ops but set it to 0 when running other ops. Ion code - // can set the pc to NullPCIndex and this will confuse SPS when - // Baseline calls into the VM at non-CALL ops and re-enters JS. - JitSpew(JitSpew_BaselineBailouts, " Setting PCidx for last frame to 0"); - cx->runtime()->spsProfiler.updatePC(script, script->code()); - } - // Register bailout with profiler. const char *filename = script->filename(); if (filename == nullptr) @@ -1381,14 +1329,11 @@ InitFromBailout(JSContext *cx, HandleScript caller, jsbytecode *callerPC, uint32_t jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, JitFrameIterator &iter, bool invalidate, BaselineBailoutInfo **bailoutInfo, - const ExceptionBailoutInfo *excInfo, bool *poppedLastSPSFrameOut) + const ExceptionBailoutInfo *excInfo) { MOZ_ASSERT(bailoutInfo != nullptr); MOZ_ASSERT(*bailoutInfo == nullptr); - MOZ_ASSERT(poppedLastSPSFrameOut); - MOZ_ASSERT(!*poppedLastSPSFrameOut); - TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); TraceLogStopEvent(logger, TraceLogger_IonMonkey); TraceLogStartEvent(logger, TraceLogger_Baseline); @@ -1492,9 +1437,6 @@ jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, JitFrameIter RootedFunction fun(cx, callee); AutoValueVector startFrameFormals(cx); - RootedScript topCaller(cx); - jsbytecode *topCallerPC = nullptr; - gc::AutoSuppressGC suppress(cx); while (true) { @@ -1524,8 +1466,7 @@ jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, JitFrameIter RootedFunction nextCallee(cx, nullptr); if (!InitFromBailout(cx, caller, callerPC, fun, scr, iter.ionScript(), snapIter, invalidate, builder, startFrameFormals, - &nextCallee, &callPC, passExcInfo ? excInfo : nullptr, - poppedLastSPSFrameOut)) + &nextCallee, &callPC, passExcInfo ? excInfo : nullptr)) { return BAILOUT_RETURN_FATAL_ERROR; } @@ -1545,24 +1486,12 @@ jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, JitFrameIter fun = nextCallee; scr = fun->existingScriptForInlinedFunction(); - // Save top caller info for adjusting SPS frames later. - if (!topCaller) { - MOZ_ASSERT(frameNo == 0); - topCaller = caller; - topCallerPC = callerPC; - } - frameNo++; snapIter.nextInstruction(); } JitSpew(JitSpew_BaselineBailouts, " Done restoring frames"); - // If there were multiple inline frames unpacked, then the current top SPS frame - // is for the outermost caller, and has an uninitialized PC. Initialize it now. - if (frameNo > 0) - cx->runtime()->spsProfiler.updatePC(topCaller, topCallerPC); - BailoutKind bailoutKind = snapIter.bailoutKind(); if (!startFrameFormals.empty()) { @@ -1721,6 +1650,12 @@ jit::FinishBailoutToBaseline(BaselineBailoutInfo *bailoutInfo) JitFrameIterator iter(cx); uint8_t *outerFp = nullptr; + // Iter currently points at the exit frame. Get the previous frame + // (which must be a baseline frame), and set it as the last profiling + // frame. + if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) + cx->mainThread().jitActivation->setLastProfilingFrame(iter.prevFp()); + uint32_t frameno = 0; while (frameno < numFrames) { MOZ_ASSERT(!iter.isIonJS()); diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index db7e52b5281..01edc4b48b5 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -175,7 +175,8 @@ BaselineCompiler::compile() prologueOffset_.fixup(&masm); epilogueOffset_.fixup(&masm); - spsPushToggleOffset_.fixup(&masm); + profilerEnterFrameToggleOffset_.fixup(&masm); + profilerExitFrameToggleOffset_.fixup(&masm); #ifdef JS_TRACE_LOGGING traceLoggerEnterToggleOffset_.fixup(&masm); traceLoggerExitToggleOffset_.fixup(&masm); @@ -188,7 +189,8 @@ BaselineCompiler::compile() mozilla::UniquePtr > baselineScript( BaselineScript::New(script, prologueOffset_.offset(), epilogueOffset_.offset(), - spsPushToggleOffset_.offset(), + profilerEnterFrameToggleOffset_.offset(), + profilerExitFrameToggleOffset_.offset(), traceLoggerEnterToggleOffset_.offset(), traceLoggerExitToggleOffset_.offset(), postDebugPrologueOffset_.offset(), @@ -242,10 +244,6 @@ BaselineCompiler::compile() if (cx->zone()->needsIncrementalBarrier()) baselineScript->toggleBarriers(true); - // All SPS instrumentation is emitted toggled off. Toggle them on if needed. - if (cx->runtime()->spsProfiler.enabled()) - baselineScript->toggleSPS(true); - #ifdef JS_TRACE_LOGGING // Initialize the tracelogger instrumentation. baselineScript->initTraceLogger(cx->runtime(), script); @@ -263,16 +261,29 @@ BaselineCompiler::compile() if (compileDebugInstrumentation_) baselineScript->setHasDebugInstrumentation(); - // Register a native => bytecode mapping entry for this script if needed. - if (cx->runtime()->jitRuntime()->isNativeToBytecodeMapEnabled(cx->runtime())) { + // If profiler instrumentation is enabled, toggle instrumentation on. + if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) + baselineScript->toggleProfilerInstrumentation(true); + + // Always register a native => bytecode mapping entry, since profiler can be + // turned on with baseline jitcode on stack, and baseline jitcode cannot be invalidated. + { JitSpew(JitSpew_Profiling, "Added JitcodeGlobalEntry for baseline script %s:%d (%p)", script->filename(), script->lineno(), baselineScript.get()); + + // Generate profiling string. + char *str = JitcodeGlobalEntry::createScriptString(cx, script); + if (!str) + return Method_Error; + JitcodeGlobalEntry::BaselineEntry entry; - entry.init(code->raw(), code->raw() + code->instructionsSize(), script); + entry.init(code->raw(), code->rawEnd(), script, str); JitcodeGlobalTable *globalTable = cx->runtime()->jitRuntime()->getJitcodeGlobalTable(); - if (!globalTable->addEntry(entry)) + if (!globalTable->addEntry(entry, cx->runtime())) { + entry.destroy(); return Method_Error; + } // Mark the jitcode as having a bytecode map. code->setHasBytecodeMap(); @@ -324,6 +335,8 @@ BaselineCompiler::emitPrologue() masm.pushReturnAddress(); masm.checkStackAlignment(); #endif + emitProfilerEnterFrame(); + masm.push(BaselineFrameReg); masm.mov(BaselineStackReg, BaselineFrameReg); @@ -412,9 +425,6 @@ BaselineCompiler::emitPrologue() if (!emitArgumentTypeChecks()) return false; - if (!emitSPSPush()) - return false; - return true; } @@ -432,12 +442,11 @@ BaselineCompiler::emitEpilogue() return false; #endif - // Pop SPS frame if necessary - emitSPSPop(); - masm.mov(BaselineFrameReg, BaselineStackReg); masm.pop(BaselineFrameReg); + emitProfilerExitFrame(); + masm.ret(); return true; } @@ -827,33 +836,34 @@ BaselineCompiler::emitTraceLoggerExit() } #endif -bool -BaselineCompiler::emitSPSPush() +void +BaselineCompiler::emitProfilerEnterFrame() { - // Enter the IC, guarded by a toggled jump (initially disabled). - Label noPush; - CodeOffsetLabel toggleOffset = masm.toggledJump(&noPush); - MOZ_ASSERT(frame.numUnsyncedSlots() == 0); - ICProfiler_Fallback::Compiler compiler(cx); - if (!emitNonOpIC(compiler.getStub(&stubSpace_))) - return false; - masm.bind(&noPush); + // Store stack position to lastProfilingFrame variable, guarded by a toggled jump. + // Starts off initially disabled. + Label noInstrument; + CodeOffsetLabel toggleOffset = masm.toggledJump(&noInstrument); + masm.profilerEnterFrame(BaselineStackReg, R0.scratchReg()); + masm.bind(&noInstrument); // Store the start offset in the appropriate location. - MOZ_ASSERT(spsPushToggleOffset_.offset() == 0); - spsPushToggleOffset_ = toggleOffset; - return true; + MOZ_ASSERT(profilerEnterFrameToggleOffset_.offset() == 0); + profilerEnterFrameToggleOffset_ = toggleOffset; } void -BaselineCompiler::emitSPSPop() +BaselineCompiler::emitProfilerExitFrame() { - // If profiler entry was pushed on this frame, pop it. - Label noPop; - masm.branchTest32(Assembler::Zero, frame.addressOfFlags(), - Imm32(BaselineFrame::HAS_PUSHED_SPS_FRAME), &noPop); - masm.spsPopFrameSafe(&cx->runtime()->spsProfiler, R1.scratchReg()); - masm.bind(&noPop); + // Store previous frame to lastProfilingFrame variable, guarded by a toggled jump. + // Starts off initially disabled. + Label noInstrument; + CodeOffsetLabel toggleOffset = masm.toggledJump(&noInstrument); + masm.profilerExitFrame(); + masm.bind(&noInstrument); + + // Store the start offset in the appropriate location. + MOZ_ASSERT(profilerExitFrameToggleOffset_.offset() == 0); + profilerExitFrameToggleOffset_ = toggleOffset; } MethodStatus @@ -3630,6 +3640,19 @@ BaselineCompiler::emit_JSOP_RESUME() masm.jump(&returnTarget); masm.bind(&genStart); + // If profiler instrumentation is on, update lastProfilingFrame on + // current JitActivation + { + Register scratchReg = scratch2; + Label skip; + AbsoluteAddress addressOfEnabled(cx->runtime()->spsProfiler.addressOfEnabled()); + masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skip); + masm.loadPtr(AbsoluteAddress(cx->mainThread().addressOfProfilingActivation()), scratchReg); + masm.storePtr(BaselineStackReg, + Address(scratchReg, JitActivation::offsetOfLastProfilingFrame())); + masm.bind(&skip); + } + // Construct BaselineFrame. masm.push(BaselineFrameReg); masm.mov(BaselineStackReg, BaselineFrameReg); diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 06754769616..50d6c4dfe76 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -256,8 +256,9 @@ class BaselineCompiler : public BaselineCompilerSpecific bool emitDebugTrap(); bool emitTraceLoggerEnter(); bool emitTraceLoggerExit(); - bool emitSPSPush(); - void emitSPSPop(); + + void emitProfilerEnterFrame(); + void emitProfilerExitFrame(); bool initScopeChain(); diff --git a/js/src/jit/BaselineFrame.cpp b/js/src/jit/BaselineFrame.cpp index eeedb40933f..29f7367b4d7 100644 --- a/js/src/jit/BaselineFrame.cpp +++ b/js/src/jit/BaselineFrame.cpp @@ -172,19 +172,6 @@ BaselineFrame::initForOsr(InterpreterFrame *fp, uint32_t numStackValues) if (fp->hasReturnValue()) setReturnValue(fp->returnValue()); - // If the interpreter pushed an SPS frame when it entered the function, the - // interpreter will pop it after the OSR trampoline returns. In order for - // the Baseline frame to have its SPS flag set, it must have its own SPS - // frame, which the Baseline code will pop on return. Note that the - // profiler may have been enabled or disabled after the function was entered - // but before OSR. - JSContext *cx = GetJSContextFromJitCode(); - SPSProfiler *p = &(cx->runtime()->spsProfiler); - if (p->enabled()) { - p->enter(fp->script(), fp->maybeFun()); - flags_ |= BaselineFrame::HAS_PUSHED_SPS_FRAME; - } - frameSize_ = BaselineFrame::FramePointerOffset + BaselineFrame::Size() + numStackValues * sizeof(Value); @@ -195,6 +182,8 @@ BaselineFrame::initForOsr(InterpreterFrame *fp, uint32_t numStackValues) *valueSlot(i) = fp->slots()[i]; if (fp->isDebuggee()) { + JSContext *cx = GetJSContextFromJitCode(); + // For debuggee frames, update any Debugger.Frame objects for the // InterpreterFrame to point to the BaselineFrame. diff --git a/js/src/jit/BaselineFrame.h b/js/src/jit/BaselineFrame.h index 4bb3e0b0dc9..30a42f4719f 100644 --- a/js/src/jit/BaselineFrame.h +++ b/js/src/jit/BaselineFrame.h @@ -56,9 +56,6 @@ class BaselineFrame // Eval frame, see the "eval frames" comment. EVAL = 1 << 7, - // Frame has profiler entry pushed. - HAS_PUSHED_SPS_FRAME = 1 << 8, - // Frame has over-recursed on an early check. OVER_RECURSED = 1 << 9, @@ -308,18 +305,6 @@ class BaselineFrame return evalScript_; } - bool hasPushedSPSFrame() const { - return flags_ & HAS_PUSHED_SPS_FRAME; - } - - void setPushedSPSFrame() { - flags_ |= HAS_PUSHED_SPS_FRAME; - } - - void unsetPushedSPSFrame() { - flags_ &= ~HAS_PUSHED_SPS_FRAME; - } - bool overRecursed() const { return flags_ & OVER_RECURSED; } diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index d6647cc8a5c..ed463635863 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -297,11 +297,6 @@ ICStub::trace(JSTracer *trc) MarkTypeObject(trc, &updateStub->type(), "baseline-update-typeobject"); break; } - case ICStub::Profiler_PushFunction: { - ICProfiler_PushFunction *pushFunStub = toProfiler_PushFunction(); - MarkScript(trc, &pushFunStub->script(), "baseline-profilerpushfunction-stub-script"); - break; - } case ICStub::GetName_Global: { ICGetName_Global *globalStub = toGetName_Global(); MarkShape(trc, &globalStub->shape(), "baseline-global-stub-shape"); @@ -723,66 +718,6 @@ ICStubCompiler::leaveStubFrame(MacroAssembler &masm, bool calledIntoIon) EmitLeaveStubFrame(masm, calledIntoIon); } -void -ICStubCompiler::guardProfilingEnabled(MacroAssembler &masm, Register scratch, Label *skip) -{ - // This should only be called from the following stubs. - MOZ_ASSERT(kind == ICStub::Call_Scripted || - kind == ICStub::Call_AnyScripted || - kind == ICStub::Call_Native || - kind == ICStub::Call_ClassHook || - kind == ICStub::Call_ScriptedApplyArray || - kind == ICStub::Call_ScriptedApplyArguments || - kind == ICStub::Call_ScriptedFunCall || - kind == ICStub::GetProp_CallScripted || - kind == ICStub::GetProp_CallNative || - kind == ICStub::GetProp_CallNativePrototype || - kind == ICStub::GetProp_CallDOMProxyNative || - kind == ICStub::GetElem_NativePrototypeCallNative || - kind == ICStub::GetElem_NativePrototypeCallScripted || - kind == ICStub::GetProp_CallDOMProxyWithGenerationNative || - kind == ICStub::GetProp_DOMProxyShadowed || - kind == ICStub::SetProp_CallScripted || - kind == ICStub::SetProp_CallNative); - - // Guard on bit in frame that indicates if the SPS frame was pushed in the first - // place. This code is expected to be called from within a stub that has already - // entered a stub frame. - MOZ_ASSERT(entersStubFrame_); - masm.loadPtr(Address(BaselineFrameReg, 0), scratch); - masm.branchTest32(Assembler::Zero, - Address(scratch, BaselineFrame::reverseOffsetOfFlags()), - Imm32(BaselineFrame::HAS_PUSHED_SPS_FRAME), - skip); - - // Check if profiling is enabled - uint32_t *enabledAddr = cx->runtime()->spsProfiler.addressOfEnabled(); - masm.branch32(Assembler::Equal, AbsoluteAddress(enabledAddr), Imm32(0), skip); -} - -void -ICStubCompiler::emitProfilingUpdate(MacroAssembler &masm, Register pcIdx, Register scratch, - uint32_t stubPcOffset) -{ - Label skipProfilerUpdate; - - // Check if profiling is enabled. - guardProfilingEnabled(masm, scratch, &skipProfilerUpdate); - - // Update profiling entry before leaving function. - masm.load32(Address(BaselineStubReg, stubPcOffset), pcIdx); - masm.spsUpdatePCIdx(&cx->runtime()->spsProfiler, pcIdx, scratch); - - masm.bind(&skipProfilerUpdate); -} - -void -ICStubCompiler::emitProfilingUpdate(MacroAssembler &masm, GeneralRegisterSet regs, - uint32_t stubPcOffset) -{ - emitProfilingUpdate(masm, regs.takeAny(), regs.takeAny(), stubPcOffset); -} - inline bool ICStubCompiler::emitPostWriteBarrierSlot(MacroAssembler &masm, Register obj, ValueOperand val, Register scratch, GeneralRegisterSet saveRegs) @@ -863,16 +798,9 @@ EnsureCanEnterIon(JSContext *cx, ICWarmUpCounter_Fallback *stub, BaselineFrame * if (isLoopEntry) { IonScript *ion = script->ionScript(); - MOZ_ASSERT(cx->runtime()->spsProfiler.enabled() == ion->hasSPSInstrumentation()); + MOZ_ASSERT(cx->runtime()->spsProfiler.enabled() == ion->hasProfilingInstrumentation()); MOZ_ASSERT(ion->osrPc() == pc); - // If the baseline frame's SPS handling doesn't match up with the Ion code's SPS - // handling, don't OSR. - if (frame->hasPushedSPSFrame() != ion->hasSPSInstrumentation()) { - JitSpew(JitSpew_BaselineOSR, " OSR crosses SPS handling boundaries, skipping!"); - return true; - } - JitSpew(JitSpew_BaselineOSR, " OSR possible!"); *jitcodePtr = ion->method()->raw() + ion->osrEntryOffset(); } @@ -1078,6 +1006,21 @@ ICWarmUpCounter_Fallback::Compiler::generateStubCode(MacroAssembler &masm) // the stack. masm.pop(scratchReg); +#ifdef DEBUG + // If profiler instrumentation is on, ensure that lastProfilingFrame is + // the frame currently being OSR-ed + { + Label checkOk; + AbsoluteAddress addressOfEnabled(cx->runtime()->spsProfiler.addressOfEnabled()); + masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &checkOk); + masm.loadPtr(AbsoluteAddress((void*)&cx->mainThread().jitActivation), scratchReg); + masm.loadPtr(Address(scratchReg, JitActivation::offsetOfLastProfilingFrame()), scratchReg); + masm.branchPtr(Assembler::Equal, scratchReg, BaselineStackReg, &checkOk); + masm.assumeUnreachable("Baseline OSR lastProfilingFrame mismatch."); + masm.bind(&checkOk); + } +#endif + // Jump into Ion. masm.loadPtr(Address(osrDataReg, offsetof(IonOsrTempData, jitcode)), scratchReg); masm.loadPtr(Address(osrDataReg, offsetof(IonOsrTempData, baselineFrame)), OsrFrameReg); @@ -1089,99 +1032,6 @@ ICWarmUpCounter_Fallback::Compiler::generateStubCode(MacroAssembler &masm) return true; } -// -// ICProfile_Fallback -// - -static bool -DoProfilerFallback(JSContext *cx, BaselineFrame *frame, ICProfiler_Fallback *stub) -{ - RootedScript script(cx, frame->script()); - RootedFunction func(cx, frame->maybeFun()); - mozilla::DebugOnly icEntry = stub->icEntry(); - - FallbackICSpew(cx, stub, "Profiler"); - - SPSProfiler *profiler = &cx->runtime()->spsProfiler; - - // Manually enter SPS this time. - MOZ_ASSERT(profiler->enabled()); - if (!cx->runtime()->spsProfiler.enter(script, func)) - return false; - frame->setPushedSPSFrame(); - - // Unlink any existing PushFunction stub (which may hold stale 'const char *' to - // the profile string. - MOZ_ASSERT_IF(icEntry->firstStub() != stub, - icEntry->firstStub()->isProfiler_PushFunction() && - icEntry->firstStub()->next() == stub); - stub->unlinkStubsWithKind(cx, ICStub::Profiler_PushFunction); - MOZ_ASSERT(icEntry->firstStub() == stub); - - // Generate the string to use to identify this stack frame. - const char *string = profiler->profileString(script, func); - if (string == nullptr) - return false; - - JitSpew(JitSpew_BaselineIC, " Generating Profiler_PushFunction stub for %s:%d", - script->filename(), script->lineno()); - - // Create a new optimized stub. - ICProfiler_PushFunction::Compiler compiler(cx, string, script); - ICStub *optStub = compiler.getStub(compiler.getStubSpace(script)); - if (!optStub) - return false; - stub->addNewStub(optStub); - - return true; -} - -typedef bool (*DoProfilerFallbackFn)(JSContext *, BaselineFrame *frame, ICProfiler_Fallback *); -static const VMFunction DoProfilerFallbackInfo = - FunctionInfo(DoProfilerFallback, TailCall); - -bool -ICProfiler_Fallback::Compiler::generateStubCode(MacroAssembler &masm) -{ - EmitRestoreTailCallReg(masm); - - masm.push(BaselineStubReg); // Push stub. - masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); // Push frame. - - return tailCallVM(DoProfilerFallbackInfo, masm); -} - -bool -ICProfiler_PushFunction::Compiler::generateStubCode(MacroAssembler &masm) -{ - - Register scratch = R0.scratchReg(); - Register scratch2 = R1.scratchReg(); - - // Profiling should be enabled if we ever reach here. -#ifdef DEBUG - Label spsEnabled; - uint32_t *enabledAddr = cx->runtime()->spsProfiler.addressOfEnabled(); - masm.branch32(Assembler::NotEqual, AbsoluteAddress(enabledAddr), Imm32(0), &spsEnabled); - masm.assumeUnreachable("Profiling should have been enabled."); - masm.bind(&spsEnabled); -#endif - - // Push SPS entry. - masm.spsPushFrame(&cx->runtime()->spsProfiler, - Address(BaselineStubReg, ICProfiler_PushFunction::offsetOfStr()), - Address(BaselineStubReg, ICProfiler_PushFunction::offsetOfScript()), - scratch, - scratch2); - - // Mark frame as having profiler entry pushed. - Address flagsOffset(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()); - masm.or32(Imm32(BaselineFrame::HAS_PUSHED_SPS_FRAME), flagsOffset); - - EmitReturnFromIC(masm); - - return true; -} // // TypeMonitor_Fallback @@ -4214,9 +4064,6 @@ ICGetElemNativeCompiler::emitCallNative(MacroAssembler &masm, Register objReg) regs.add(objReg); - // Profiler hook. - emitProfilingUpdate(masm, regs, ICGetElemNativeGetterStub::offsetOfPCOffset()); - // Call helper. if (!callVM(DoCallNativeGetterInfo, masm)) return false; @@ -4282,16 +4129,6 @@ ICGetElemNativeCompiler::emitCallScripted(MacroAssembler &masm, Register objReg) } masm.bind(&noUnderflow); - - // If needed, update SPS Profiler frame entry. At this point, callee and scratch can - // be clobbered. - { - GeneralRegisterSet availRegs = availableGeneralRegs(0); - availRegs.take(ArgumentsRectifierReg); - availRegs.take(code); - emitProfilingUpdate(masm, availRegs, ICGetElemNativeGetterStub::offsetOfPCOffset()); - } - masm.callJit(code); leaveStubFrame(masm, true); @@ -7448,16 +7285,6 @@ ICGetProp_CallScripted::Compiler::generateStubCode(MacroAssembler &masm) } masm.bind(&noUnderflow); - - // If needed, update SPS Profiler frame entry. At this point, callee and scratch can - // be clobbered. - { - GeneralRegisterSet availRegs = availableGeneralRegs(0); - availRegs.take(ArgumentsRectifierReg); - availRegs.take(code); - emitProfilingUpdate(masm, availRegs, ICGetProp_CallScripted::offsetOfPCOffset()); - } - masm.callJit(code); leaveStubFrame(masm, true); @@ -7519,9 +7346,6 @@ ICGetProp_CallNative::Compiler::generateStubCode(MacroAssembler &masm) if (!inputDefinitelyObject_) regs.add(R0); - // If needed, update SPS Profiler frame entry. - emitProfilingUpdate(masm, regs, ICGetProp_CallNative::offsetOfPCOffset()); - if (!callVM(DoCallNativeGetterInfo, masm)) return false; leaveStubFrame(masm); @@ -7591,9 +7415,6 @@ ICGetProp_CallNativePrototype::Compiler::generateStubCode(MacroAssembler &masm) else regs.add(objReg); - // If needed, update SPS Profiler frame entry. - emitProfilingUpdate(masm, regs, ICGetProp_CallNativePrototype::offsetOfPCOffset()); - if (!callVM(DoCallNativeGetterInfo, masm)) return false; leaveStubFrame(masm); @@ -7664,9 +7485,6 @@ ICGetPropCallDOMProxyNativeCompiler::generateStubCode(MacroAssembler &masm, // Don't have to preserve R0 anymore. regs.add(R0); - // If needed, update SPS Profiler frame entry. - emitProfilingUpdate(masm, regs, ICGetProp_CallDOMProxyNative::offsetOfPCOffset()); - if (!callVM(DoCallNativeGetterInfo, masm)) return false; leaveStubFrame(masm); @@ -7799,9 +7617,6 @@ ICGetProp_DOMProxyShadowed::Compiler::generateStubCode(MacroAssembler &masm) // Don't have to preserve R0 anymore. regs.add(R0); - // If needed, update SPS Profiler frame entry. - emitProfilingUpdate(masm, regs, ICGetProp_DOMProxyShadowed::offsetOfPCOffset()); - if (!callVM(ProxyGetInfo, masm)) return false; leaveStubFrame(masm); @@ -8825,16 +8640,6 @@ ICSetProp_CallScripted::Compiler::generateStubCode(MacroAssembler &masm) } masm.bind(&noUnderflow); - - // If needed, update SPS Profiler frame entry. At this point, callee and scratch can - // be clobbered. - { - GeneralRegisterSet availRegs = availableGeneralRegs(0); - availRegs.take(ArgumentsRectifierReg); - availRegs.take(code); - emitProfilingUpdate(masm, availRegs, ICSetProp_CallScripted::offsetOfPCOffset()); - } - masm.callJit(code); leaveStubFrame(masm, true); @@ -8920,9 +8725,6 @@ ICSetProp_CallNative::Compiler::generateStubCode(MacroAssembler &masm) // Don't need to preserve R0 anymore. regs.add(R0); - // If needed, update SPS Profiler frame entry. - emitProfilingUpdate(masm, regs, ICSetProp_CallNative::offsetOfPCOffset()); - if (!callVM(DoCallNativeSetterInfo, masm)) return false; leaveStubFrame(masm); @@ -9471,10 +9273,6 @@ DoCallFallback(JSContext *cx, BaselineFrame *frame, ICCall_Fallback *stub_, uint if (!TryAttachCallStub(cx, stub, script, pc, op, argc, vp, constructing, false, newType)) return false; - // Maybe update PC in profiler entry before leaving this script by call. - if (cx->runtime()->spsProfiler.enabled() && frame->hasPushedSPSFrame()) - cx->runtime()->spsProfiler.updatePC(script, pc); - if (!MaybeCloneFunctionAtCallsite(cx, &callee, script, pc)) return false; @@ -9547,10 +9345,6 @@ DoSpreadCallFallback(JSContext *cx, BaselineFrame *frame, ICCall_Fallback *stub_ return false; } - // Maybe update PC in profiler entry before leaving this script by call. - if (cx->runtime()->spsProfiler.enabled() && frame->hasPushedSPSFrame()) - cx->runtime()->spsProfiler.updatePC(script, pc); - if (!MaybeCloneFunctionAtCallsite(cx, &callee, script, pc)) return false; @@ -10130,18 +9924,6 @@ ICCallScriptedCompiler::generateStubCode(MacroAssembler &masm) } masm.bind(&noUnderflow); - - // If needed, update SPS Profiler frame entry before and after call. - { - MOZ_ASSERT(kind == ICStub::Call_Scripted || kind == ICStub::Call_AnyScripted); - GeneralRegisterSet availRegs = availableGeneralRegs(0); - availRegs.take(ArgumentsRectifierReg); - availRegs.take(code); - emitProfilingUpdate(masm, availRegs, kind == ICStub::Call_Scripted ? - ICCall_Scripted::offsetOfPCOffset() - : ICCall_AnyScripted::offsetOfPCOffset()); - } - masm.callJit(code); // If this is a constructing call, and the callee returns a non-object, replace it with @@ -10402,10 +10184,6 @@ ICCall_Native::Compiler::generateStubCode(MacroAssembler &masm) masm.push(BaselineTailCallReg); masm.enterFakeExitFrame(NativeExitFrameLayout::Token()); - // If needed, update SPS Profiler frame entry. At this point, BaselineTailCallReg - // and scratch can be clobbered. - emitProfilingUpdate(masm, BaselineTailCallReg, scratch, ICCall_Native::offsetOfPCOffset()); - // Execute call. masm.setupUnalignedABICall(3, scratch); masm.loadJSContext(scratch); @@ -10501,10 +10279,6 @@ ICCall_ClassHook::Compiler::generateStubCode(MacroAssembler &masm) masm.push(BaselineTailCallReg); masm.enterFakeExitFrame(NativeExitFrameLayout::Token()); - // If needed, update SPS Profiler frame entry. At this point, BaselineTailCallReg - // and scratch can be clobbered. - emitProfilingUpdate(masm, BaselineTailCallReg, scratch, ICCall_ClassHook::offsetOfPCOffset()); - // Execute call. masm.setupUnalignedABICall(3, scratch); masm.loadJSContext(scratch); @@ -10619,11 +10393,6 @@ ICCall_ScriptedApplyArray::Compiler::generateStubCode(MacroAssembler &masm) masm.bind(&noUnderflow); regs.add(argcReg); - // If needed, update SPS Profiler frame entry. At this point, BaselineTailCallReg - // and scratch can be clobbered. - emitProfilingUpdate(masm, regs.getAny(), scratch, - ICCall_ScriptedApplyArguments::offsetOfPCOffset()); - // Do call masm.callJit(target); leaveStubFrame(masm, true); @@ -10720,11 +10489,6 @@ ICCall_ScriptedApplyArguments::Compiler::generateStubCode(MacroAssembler &masm) masm.bind(&noUnderflow); regs.add(argcReg); - // If needed, update SPS Profiler frame entry. At this point, BaselineTailCallReg - // and scratch can be clobbered. - emitProfilingUpdate(masm, regs.getAny(), scratch, - ICCall_ScriptedApplyArguments::offsetOfPCOffset()); - // Do call masm.callJit(target); leaveStubFrame(masm, true); @@ -10841,16 +10605,6 @@ ICCall_ScriptedFunCall::Compiler::generateStubCode(MacroAssembler &masm) } masm.bind(&noUnderflow); - - // If needed, update SPS Profiler frame entry. - { - // Need to avoid using ArgumentsRectifierReg and code register. - GeneralRegisterSet availRegs = availableGeneralRegs(0); - availRegs.take(ArgumentsRectifierReg); - availRegs.take(code); - emitProfilingUpdate(masm, availRegs, ICCall_ScriptedFunCall::offsetOfPCOffset()); - } - masm.callJit(code); leaveStubFrame(masm, true); @@ -11503,13 +11257,6 @@ ICRetSub_Resume::Compiler::generateStubCode(MacroAssembler &masm) return true; } -ICProfiler_PushFunction::ICProfiler_PushFunction(JitCode *stubCode, const char *str, - HandleScript script) - : ICStub(ICStub::Profiler_PushFunction, stubCode), - str_(str), - script_(script) -{ } - ICTypeMonitor_SingleObject::ICTypeMonitor_SingleObject(JitCode *stubCode, HandleObject obj) : ICStub(TypeMonitor_SingleObject, stubCode), obj_(obj) diff --git a/js/src/jit/BaselineIC.h b/js/src/jit/BaselineIC.h index 60de69df253..8da23b04f56 100644 --- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -335,9 +335,6 @@ class ICEntry #define IC_STUB_KIND_LIST(_) \ _(WarmUpCounter_Fallback) \ \ - _(Profiler_Fallback) \ - _(Profiler_PushFunction) \ - \ _(TypeMonitor_Fallback) \ _(TypeMonitor_SingleObject) \ _(TypeMonitor_TypeObject) \ @@ -1122,11 +1119,6 @@ class ICStubCompiler // given label. void guardProfilingEnabled(MacroAssembler &masm, Register scratch, Label *skip); - // Higher-level helper to emit an update to the profiler pseudo-stack. - void emitProfilingUpdate(MacroAssembler &masm, Register pcIdx, Register scratch, - uint32_t stubPcOffset); - void emitProfilingUpdate(MacroAssembler &masm, GeneralRegisterSet regs, uint32_t stubPcOffset); - inline GeneralRegisterSet availableGeneralRegs(size_t numInputs) const { GeneralRegisterSet regs(GeneralRegisterSet::All()); MOZ_ASSERT(!regs.has(BaselineStackReg)); @@ -1229,91 +1221,6 @@ class ICWarmUpCounter_Fallback : public ICFallbackStub }; }; -// Profiler_Fallback - -class ICProfiler_Fallback : public ICFallbackStub -{ - friend class ICStubSpace; - - explicit ICProfiler_Fallback(JitCode *stubCode) - : ICFallbackStub(ICStub::Profiler_Fallback, stubCode) - { } - - public: - static inline ICProfiler_Fallback *New(ICStubSpace *space, JitCode *code) { - if (!code) - return nullptr; - return space->allocate(code); - } - - // Compiler for this stub kind. - class Compiler : public ICStubCompiler { - protected: - bool generateStubCode(MacroAssembler &masm); - - public: - explicit Compiler(JSContext *cx) - : ICStubCompiler(cx, ICStub::Profiler_Fallback) - { } - - ICProfiler_Fallback *getStub(ICStubSpace *space) { - return ICProfiler_Fallback::New(space, getStubCode()); - } - }; -}; - -// Profiler_PushFunction - -class ICProfiler_PushFunction : public ICStub -{ - friend class ICStubSpace; - - protected: - const char *str_; - HeapPtrScript script_; - - ICProfiler_PushFunction(JitCode *stubCode, const char *str, HandleScript script); - - public: - static inline ICProfiler_PushFunction *New(ICStubSpace *space, JitCode *code, - const char *str, HandleScript script) - { - if (!code) - return nullptr; - return space->allocate(code, str, script); - } - - HeapPtrScript &script() { - return script_; - } - - static size_t offsetOfStr() { - return offsetof(ICProfiler_PushFunction, str_); - } - static size_t offsetOfScript() { - return offsetof(ICProfiler_PushFunction, script_); - } - - // Compiler for this stub kind. - class Compiler : public ICStubCompiler { - protected: - const char *str_; - RootedScript script_; - bool generateStubCode(MacroAssembler &masm); - - public: - Compiler(JSContext *cx, const char *str, HandleScript script) - : ICStubCompiler(cx, ICStub::Profiler_PushFunction), - str_(str), - script_(cx, script) - { } - - ICProfiler_PushFunction *getStub(ICStubSpace *space) { - return ICProfiler_PushFunction::New(space, getStubCode(), str_, script_); - } - }; -}; - // TypeCheckPrimitiveSetStub // Base class for IC stubs (TypeUpdate or TypeMonitor) that check that a given diff --git a/js/src/jit/BaselineJIT.cpp b/js/src/jit/BaselineJIT.cpp index bf52d8926a6..2765e393827 100644 --- a/js/src/jit/BaselineJIT.cpp +++ b/js/src/jit/BaselineJIT.cpp @@ -42,7 +42,9 @@ PCMappingSlotInfo::ToSlotLocation(const StackValue *stackVal) } BaselineScript::BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset, - uint32_t spsPushToggleOffset, uint32_t traceLoggerEnterToggleOffset, + uint32_t profilerEnterToggleOffset, + uint32_t profilerExitToggleOffset, + uint32_t traceLoggerEnterToggleOffset, uint32_t traceLoggerExitToggleOffset, uint32_t postDebugPrologueOffset) : method_(nullptr), @@ -51,10 +53,8 @@ BaselineScript::BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset, dependentAsmJSModules_(nullptr), prologueOffset_(prologueOffset), epilogueOffset_(epilogueOffset), -#ifdef DEBUG - spsOn_(false), -#endif - spsPushToggleOffset_(spsPushToggleOffset), + profilerEnterToggleOffset_(profilerEnterToggleOffset), + profilerExitToggleOffset_(profilerExitToggleOffset), #ifdef JS_TRACE_LOGGING # ifdef DEBUG traceLoggerScriptsEnabled_(false), @@ -342,8 +342,9 @@ jit::CanEnterBaselineMethod(JSContext *cx, RunState &state) BaselineScript * BaselineScript::New(JSScript *jsscript, uint32_t prologueOffset, uint32_t epilogueOffset, - uint32_t spsPushToggleOffset, uint32_t traceLoggerEnterToggleOffset, - uint32_t traceLoggerExitToggleOffset, uint32_t postDebugPrologueOffset, + uint32_t profilerEnterToggleOffset, uint32_t profilerExitToggleOffset, + uint32_t traceLoggerEnterToggleOffset, uint32_t traceLoggerExitToggleOffset, + uint32_t postDebugPrologueOffset, size_t icEntries, size_t pcMappingIndexEntries, size_t pcMappingSize, size_t bytecodeTypeMapEntries, size_t yieldEntries) { @@ -370,8 +371,9 @@ BaselineScript::New(JSScript *jsscript, uint32_t prologueOffset, uint32_t epilog if (!script) return nullptr; new (script) BaselineScript(prologueOffset, epilogueOffset, - spsPushToggleOffset, traceLoggerEnterToggleOffset, - traceLoggerExitToggleOffset, postDebugPrologueOffset); + profilerEnterToggleOffset, profilerExitToggleOffset, + traceLoggerEnterToggleOffset, traceLoggerExitToggleOffset, + postDebugPrologueOffset); size_t offsetCursor = sizeof(BaselineScript); MOZ_ASSERT(offsetCursor == AlignBytes(sizeof(BaselineScript), DataAlignment)); @@ -847,25 +849,6 @@ BaselineScript::toggleDebugTraps(JSScript *script, jsbytecode *pc) } } -void -BaselineScript::toggleSPS(bool enable) -{ - MOZ_ASSERT(enable == !(bool)spsOn_); - - JitSpew(JitSpew_BaselineIC, " toggling SPS %s for BaselineScript %p", - enable ? "on" : "off", this); - - // Toggle the jump - CodeLocationLabel pushToggleLocation(method_, CodeOffsetLabel(spsPushToggleOffset_)); - if (enable) - Assembler::ToggleToCmp(pushToggleLocation); - else - Assembler::ToggleToJmp(pushToggleLocation); -#ifdef DEBUG - spsOn_ = enable; -#endif -} - #ifdef JS_TRACE_LOGGING void BaselineScript::initTraceLogger(JSRuntime *runtime, JSScript *script) @@ -950,6 +933,29 @@ BaselineScript::toggleTraceLoggerEngine(bool enable) } #endif +void +BaselineScript::toggleProfilerInstrumentation(bool enable) +{ + if (enable == isProfilerInstrumentationOn()) + return; + + JitSpew(JitSpew_BaselineIC, " toggling profiling %s for BaselineScript %p", + enable ? "on" : "off", this); + + // Toggle the jump + CodeLocationLabel enterToggleLocation(method_, CodeOffsetLabel(profilerEnterToggleOffset_)); + CodeLocationLabel exitToggleLocation(method_, CodeOffsetLabel(profilerExitToggleOffset_)); + if (enable) { + Assembler::ToggleToCmp(enterToggleLocation); + Assembler::ToggleToCmp(exitToggleLocation); + flags_ |= uint32_t(PROFILER_INSTRUMENTATION_ON); + } else { + Assembler::ToggleToJmp(enterToggleLocation); + Assembler::ToggleToJmp(exitToggleLocation); + flags_ &= ~uint32_t(PROFILER_INSTRUMENTATION_ON); + } +} + void BaselineScript::purgeOptimizedStubs(Zone *zone) { @@ -1045,14 +1051,14 @@ jit::AddSizeOfBaselineData(JSScript *script, mozilla::MallocSizeOf mallocSizeOf, } void -jit::ToggleBaselineSPS(JSRuntime *runtime, bool enable) +jit::ToggleBaselineProfiling(JSRuntime *runtime, bool enable) { for (ZonesIter zone(runtime, SkipAtoms); !zone.done(); zone.next()) { for (gc::ZoneCellIter i(zone, gc::FINALIZE_SCRIPT); !i.done(); i.next()) { JSScript *script = i.get(); if (!script->hasBaselineScript()) continue; - script->baselineScript()->toggleSPS(enable); + script->baselineScript()->toggleProfilerInstrumentation(enable); } } } diff --git a/js/src/jit/BaselineJIT.h b/js/src/jit/BaselineJIT.h index 714efa59d46..b0a5218b9ba 100644 --- a/js/src/jit/BaselineJIT.h +++ b/js/src/jit/BaselineJIT.h @@ -139,11 +139,9 @@ struct BaselineScript // returned from. uint32_t epilogueOffset_; - // The offsets for the toggledJump instructions for SPS update ICs. -#ifdef DEBUG - mozilla::DebugOnly spsOn_; -#endif - uint32_t spsPushToggleOffset_; + // The offsets for the toggledJump instructions for profiler instrumentation. + uint32_t profilerEnterToggleOffset_; + uint32_t profilerExitToggleOffset_; // The offsets and event used for Tracelogger toggling. #ifdef JS_TRACE_LOGGING @@ -185,7 +183,10 @@ struct BaselineScript // Flag set if this script has ever been Ion compiled, either directly // or inlined into another script. This is cleared when the script's // type information or caches are cleared. - ION_COMPILED_OR_INLINED = 1 << 4 + ION_COMPILED_OR_INLINED = 1 << 4, + + // Flag is set if this script has profiling instrumentation turned on. + PROFILER_INSTRUMENTATION_ON = 1 << 5 }; private: @@ -214,14 +215,20 @@ struct BaselineScript public: // Do not call directly, use BaselineScript::New. This is public for cx->new_. BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset, - uint32_t spsPushToggleOffset, uint32_t traceLoggerEnterToggleOffset, - uint32_t traceLoggerExitToggleOffset, uint32_t postDebugPrologueOffset); + uint32_t profilerEnterToggleOffset, + uint32_t profilerExitToggleOffset, + uint32_t traceLoggerEnterToggleOffset, + uint32_t traceLoggerExitToggleOffset, + uint32_t postDebugPrologueOffset); static BaselineScript *New(JSScript *jsscript, uint32_t prologueOffset, uint32_t epilogueOffset, uint32_t postDebugPrologueOffset, - uint32_t spsPushToggleOffset, uint32_t traceLoggerEnterToggleOffset, - uint32_t traceLoggerExitToggleOffset, size_t icEntries, - size_t pcMappingIndexEntries, size_t pcMappingSize, + uint32_t profilerEnterToggleOffset, + uint32_t profilerExitToggleOffset, + uint32_t traceLoggerEnterToggleOffset, + uint32_t traceLoggerExitToggleOffset, + size_t icEntries, size_t pcMappingIndexEntries, + size_t pcMappingSize, size_t bytecodeTypeMapEntries, size_t yieldEntries); static void Trace(JSTracer *trc, BaselineScript *script); @@ -385,7 +392,10 @@ struct BaselineScript // toggle traps at |pc|. void toggleDebugTraps(JSScript *script, jsbytecode *pc); - void toggleSPS(bool enable); + void toggleProfilerInstrumentation(bool enable); + bool isProfilerInstrumentationOn() const { + return flags_ & PROFILER_INSTRUMENTATION_ON; + } #ifdef JS_TRACE_LOGGING void initTraceLogger(JSRuntime *runtime, JSScript *script); @@ -447,7 +457,7 @@ AddSizeOfBaselineData(JSScript *script, mozilla::MallocSizeOf mallocSizeOf, size size_t *fallbackStubs); void -ToggleBaselineSPS(JSRuntime *runtime, bool enable); +ToggleBaselineProfiling(JSRuntime *runtime, bool enable); void ToggleBaselineTraceLoggerScripts(JSRuntime *runtime, bool enable); @@ -499,8 +509,7 @@ struct BaselineBailoutInfo uint32_t BailoutIonToBaseline(JSContext *cx, JitActivation *activation, JitFrameIterator &iter, bool invalidate, BaselineBailoutInfo **bailoutInfo, - const ExceptionBailoutInfo *exceptionInfo, - bool *poppedLastSPSFrame); + const ExceptionBailoutInfo *exceptionInfo); // Mark baseline scripts on the stack as active, so that they are not discarded // during GC. diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 1afb67c5f47..61ddde8f3d0 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -1981,6 +1981,8 @@ CodeGenerator::visitReturn(LReturn *lir) void CodeGenerator::visitOsrEntry(LOsrEntry *lir) { + Register temp = ToRegister(lir->temp()); + // Remember the OSR entry offset into the code buffer. masm.flushBuffer(); setOsrEntryOffset(masm.size()); @@ -1990,6 +1992,10 @@ CodeGenerator::visitOsrEntry(LOsrEntry *lir) emitTracelogStartEvent(TraceLogger_IonMonkey); #endif + // If profiling, save the current frame pointer to a per-thread global field. + if (isProfilerInstrumentationEnabled()) + masm.profilerEnterFrame(StackPointer, temp); + // Allocate the full frame for this function // Note we have a new entry here. So we reset MacroAssembler::framePushed() // to 0, before reserving the stack. @@ -3647,7 +3653,7 @@ CodeGenerator::emitObjectOrStringResultChecks(LInstruction *lir, MDefinition *mi MOZ_CRASH(); } - masm.callWithABINoProfiling(callee); + masm.callWithABI(callee); restoreVolatile(); masm.bind(&done); @@ -3713,7 +3719,7 @@ CodeGenerator::emitValueResultChecks(LInstruction *lir, MDefinition *mir) masm.loadJSContext(temp2); masm.passABIArg(temp2); masm.passABIArg(temp1); - masm.callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, AssertValidValue)); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, AssertValidValue)); masm.popValue(output); restoreVolatile(); @@ -6926,9 +6932,6 @@ CodeGenerator::generateAsmJS(AsmJSFunctionLabels *labels) { JitSpew(JitSpew_Codegen, "# Emitting asm.js code"); - // AsmJS doesn't do SPS instrumentation. - sps_.disable(); - if (!omitOverRecursedCheck()) labels->overflowThunk.emplace(); @@ -7154,7 +7157,7 @@ CodeGenerator::link(JSContext *cx, types::CompilerConstraintList *constraints) return false; // Encode native to bytecode map if profiling is enabled. - if (isNativeToBytecodeMapEnabled()) { + if (isProfilerInstrumentationEnabled()) { // Generate native-to-bytecode main table. if (!generateCompactNativeToBytecodeMap(cx, code)) return false; @@ -7177,7 +7180,22 @@ CodeGenerator::link(JSContext *cx, types::CompilerConstraintList *constraints) // Add entry to the global table. JitcodeGlobalTable *globalTable = cx->runtime()->jitRuntime()->getJitcodeGlobalTable(); - if (!globalTable->addEntry(entry)) { + if (!globalTable->addEntry(entry, cx->runtime())) { + // Memory may have been allocated for the entry. + entry.destroy(); + return false; + } + + // Mark the jitcode as having a bytecode map. + code->setHasBytecodeMap(); + } else { + // Add a dumy jitcodeGlobalTable entry. + JitcodeGlobalEntry::DummyEntry entry; + entry.init(code->raw(), code->rawEnd()); + + // Add entry to the global table. + JitcodeGlobalTable *globalTable = cx->runtime()->jitRuntime()->getJitcodeGlobalTable(); + if (!globalTable->addEntry(entry, cx->runtime())) { // Memory may have been allocated for the entry. entry.destroy(); return false; @@ -7204,8 +7222,8 @@ CodeGenerator::link(JSContext *cx, types::CompilerConstraintList *constraints) ionScript->setSkipArgCheckEntryOffset(getSkipArgCheckEntryOffset()); // If SPS is enabled, mark IonScript as having been instrumented with SPS - if (sps_.enabled()) - ionScript->setHasSPSInstrumentation(); + if (isProfilerInstrumentationEnabled()) + ionScript->setHasProfilingInstrumentation(); script->setIonScript(cx, ionScript); @@ -8891,49 +8909,6 @@ CodeGenerator::visitSetDOMProperty(LSetDOMProperty *ins) MOZ_ASSERT(masm.framePushed() == initialStack); } -typedef bool(*SPSFn)(JSContext *, HandleScript); -static const VMFunction SPSEnterInfo = FunctionInfo(SPSEnter); -static const VMFunction SPSExitInfo = FunctionInfo(SPSExit); - -void -CodeGenerator::visitProfilerStackOp(LProfilerStackOp *lir) -{ - Register temp = ToRegister(lir->temp()->output()); - - switch (lir->type()) { - case MProfilerStackOp::Enter: - if (gen->options.spsSlowAssertionsEnabled()) { - saveLive(lir); - pushArg(ImmGCPtr(lir->script())); - callVM(SPSEnterInfo, lir); - restoreLive(lir); - sps_.pushManual(lir->script(), masm, temp, /* inlinedFunction = */ false); - } else { - masm.propagateOOM(sps_.push(lir->script(), masm, temp, - /* inlinedFunction = */ false)); - } - return; - - case MProfilerStackOp::Exit: - if (gen->options.spsSlowAssertionsEnabled()) { - saveLive(lir); - pushArg(ImmGCPtr(lir->script())); - // Once we've exited, then we shouldn't emit instrumentation for - // the corresponding reenter() because we no longer have a - // frame. - sps_.skipNextReenter(); - callVM(SPSExitInfo, lir); - restoreLive(lir); - } else { - sps_.pop(masm, temp, /* inlinedFunction = */ false); - } - return; - - default: - MOZ_CRASH("invalid LProfilerStackOp type"); - } -} - class OutOfLineIsCallable : public OutOfLineCodeBase { LIsCallable *ins_; diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 7a625b3cef4..60dacf54745 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -293,7 +293,6 @@ class CodeGenerator : public CodeGeneratorSpecific void visitInstanceOfO(LInstanceOfO *ins); void visitInstanceOfV(LInstanceOfV *ins); void visitCallInstanceOf(LCallInstanceOf *ins); - void visitProfilerStackOp(LProfilerStackOp *lir); void visitGetDOMProperty(LGetDOMProperty *lir); void visitGetDOMMemberV(LGetDOMMemberV *lir); void visitGetDOMMemberT(LGetDOMMemberT *lir); diff --git a/js/src/jit/CompileWrappers.cpp b/js/src/jit/CompileWrappers.cpp index 645b0dbf683..79613533b8b 100644 --- a/js/src/jit/CompileWrappers.cpp +++ b/js/src/jit/CompileWrappers.cpp @@ -40,6 +40,18 @@ CompileRuntime::addressOfJitTop() return &runtime()->mainThread.jitTop; } +const void * +CompileRuntime::addressOfJitActivation() +{ + return &runtime()->mainThread.jitActivation; +} + +const void * +CompileRuntime::addressOfProfilingActivation() +{ + return (const void *) &runtime()->mainThread.profilingActivation_; +} + const void * CompileRuntime::addressOfJitStackLimit() { diff --git a/js/src/jit/CompileWrappers.h b/js/src/jit/CompileWrappers.h index f0e49ddade2..ef443327035 100644 --- a/js/src/jit/CompileWrappers.h +++ b/js/src/jit/CompileWrappers.h @@ -34,6 +34,12 @@ class CompileRuntime // &mainThread.jitTop const void *addressOfJitTop(); + // &mainThread.jitActivation + const void *addressOfJitActivation(); + + // &mainThread.profilingActivation + const void *addressOfProfilingActivation(); + // rt->mainThread.jitStackLimit; const void *addressOfJitStackLimit(); diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 151de8dde23..d0ce623ec8c 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -151,6 +151,7 @@ JitRuntime::JitRuntime() ionAlloc_(nullptr), exceptionTail_(nullptr), bailoutTail_(nullptr), + profilerExitFrameTail_(nullptr), enterJIT_(nullptr), bailoutHandler_(nullptr), argumentsRectifier_(nullptr), @@ -200,6 +201,11 @@ JitRuntime::initialize(JSContext *cx) if (!functionWrappers_ || !functionWrappers_->init()) return false; + JitSpew(JitSpew_Codegen, "# Emitting profiler exit frame tail stub"); + profilerExitFrameTail_ = generateProfilerExitFrameTailStub(cx); + if (!profilerExitFrameTail_) + return false; + JitSpew(JitSpew_Codegen, "# Emitting exception tail stub"); void *handler = JS_FUNC_TO_DATA_PTR(void *, jit::HandleException); @@ -650,17 +656,18 @@ JitCode::trace(JSTracer *trc) void JitCode::finalize(FreeOp *fop) { + JSRuntime *rt = fop->runtime(); + // If this jitcode has a bytecode map, de-register it. if (hasBytecodeMap_) { - MOZ_ASSERT(fop->runtime()->jitRuntime()->hasJitcodeGlobalTable()); - fop->runtime()->jitRuntime()->getJitcodeGlobalTable()->removeEntry(raw()); + MOZ_ASSERT(rt->jitRuntime()->hasJitcodeGlobalTable()); + rt->jitRuntime()->getJitcodeGlobalTable()->removeEntry(raw(), rt); } // Buffer can be freed at any time hereafter. Catch use-after-free bugs. // Don't do this if the Ion code is protected, as the signal handler will // deadlock trying to reacquire the interrupt lock. - if (fop->runtime()->jitRuntime()) - memset(code_, JS_SWEPT_CODE_PATTERN, bufferSize_); + memset(code_, JS_SWEPT_CODE_PATTERN, bufferSize_); code_ = nullptr; // Code buffers are stored inside JSC pools. @@ -699,7 +706,7 @@ IonScript::IonScript() invalidateEpilogueOffset_(0), invalidateEpilogueDataOffset_(0), numBailouts_(0), - hasSPSInstrumentation_(false), + hasProfilingInstrumentation_(false), recompiling_(false), runtimeData_(0), runtimeSize_(0), diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 3095731da1e..bddb2aa449c 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -705,8 +705,6 @@ IonBuilder::build() // Emit the start instruction, so we can begin real instructions. current->add(MStart::New(alloc(), MStart::StartType_Default)); - if (instrumentedProfiling()) - current->add(MProfilerStackOp::New(alloc(), script(), MProfilerStackOp::Enter)); // Guard against over-recursion. Do this before we start unboxing, since // this will create an OSI point that will read the incoming argument @@ -4072,9 +4070,6 @@ IonBuilder::processReturn(JSOp op) MOZ_CRASH("unknown return op"); } - if (instrumentedProfiling() && inliningDepth_ == 0) { - current->add(MProfilerStackOp::New(alloc(), script(), MProfilerStackOp::Exit)); - } MReturn *ret = MReturn::New(alloc(), def); current->end(ret); diff --git a/js/src/jit/IonCaches.cpp b/js/src/jit/IonCaches.cpp index 0a284d60375..32505e3463e 100644 --- a/js/src/jit/IonCaches.cpp +++ b/js/src/jit/IonCaches.cpp @@ -429,13 +429,26 @@ IonCache::linkAndAttachStub(JSContext *cx, MacroAssembler &masm, StubAttacher &a attachStub(masm, attacher, code); // Add entry to native => bytecode mapping for this stub if needed. - if (cx->runtime()->jitRuntime()->isNativeToBytecodeMapEnabled(cx->runtime())) { + if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) { JitcodeGlobalEntry::IonCacheEntry entry; - entry.init(code->raw(), code->raw() + code->instructionsSize(), rejoinAddress()); + entry.init(code->raw(), code->rawEnd(), rejoinAddress()); // Add entry to the global table. JitcodeGlobalTable *globalTable = cx->runtime()->jitRuntime()->getJitcodeGlobalTable(); - if (!globalTable->addEntry(entry)) { + if (!globalTable->addEntry(entry, cx->runtime())) { + entry.destroy(); + return false; + } + + // Mark the jitcode as having a bytecode map. + code->setHasBytecodeMap(); + } else { + JitcodeGlobalEntry::DummyEntry entry; + entry.init(code->raw(), code->rawEnd()); + + // Add entry to the global table. + JitcodeGlobalTable *globalTable = cx->runtime()->jitRuntime()->getJitcodeGlobalTable(); + if (!globalTable->addEntry(entry, cx->runtime())) { entry.destroy(); return false; } diff --git a/js/src/jit/IonCode.h b/js/src/jit/IonCode.h index c7342f63ed8..848281dfc49 100644 --- a/js/src/jit/IonCode.h +++ b/js/src/jit/IonCode.h @@ -98,6 +98,10 @@ class JitCode : public gc::TenuredCell uint8_t *rawEnd() const { return code_ + insnSize_; } + bool containsNativePC(const void *addr) const { + const uint8_t *addr_u8 = (const uint8_t *) addr; + return raw() <= addr_u8 && addr_u8 < rawEnd(); + } size_t instructionsSize() const { return insnSize_; } @@ -193,8 +197,8 @@ struct IonScript // Number of times this script bailed out without invalidation. uint32_t numBailouts_; - // Flag set if IonScript was compiled with SPS profiling enabled. - bool hasSPSInstrumentation_; + // Flag set if IonScript was compiled with profiling enabled. + bool hasProfilingInstrumentation_; // Flag for if this script is getting recompiled. uint32_t recompiling_; @@ -412,14 +416,14 @@ struct IonScript bool bailoutExpected() const { return numBailouts_ > 0; } - void setHasSPSInstrumentation() { - hasSPSInstrumentation_ = true; + void setHasProfilingInstrumentation() { + hasProfilingInstrumentation_ = true; } - void clearHasSPSInstrumentation() { - hasSPSInstrumentation_ = false; + void clearHasProfilingInstrumentation() { + hasProfilingInstrumentation_ = false; } - bool hasSPSInstrumentation() const { - return hasSPSInstrumentation_; + bool hasProfilingInstrumentation() const { + return hasProfilingInstrumentation_; } void setTraceLoggerEvent(TraceLoggerEvent &event) { traceLoggerScriptEvent_ = event; diff --git a/js/src/jit/IonInstrumentation.h b/js/src/jit/IonInstrumentation.h index 05c106178e6..93b96ad40e8 100644 --- a/js/src/jit/IonInstrumentation.h +++ b/js/src/jit/IonInstrumentation.h @@ -19,23 +19,12 @@ typedef SPSInstrumentation BaseInstrumentation; class IonInstrumentation : public BaseInstrumentation { - jsbytecode **trackedPc_; - public: IonInstrumentation(SPSProfiler *profiler, jsbytecode **pc) - : BaseInstrumentation(profiler), - trackedPc_(pc) + : BaseInstrumentation(profiler) { MOZ_ASSERT(pc != nullptr); } - - void leave(MacroAssembler &masm, Register reg, bool inlinedFunction = false) { - BaseInstrumentation::leave(*trackedPc_, masm, reg, inlinedFunction); - } - - bool enterInlineFrame() { - return BaseInstrumentation::enterInlineFrame(*trackedPc_); - } }; } // namespace jit diff --git a/js/src/jit/JitCompartment.h b/js/src/jit/JitCompartment.h index 0fa2d647e27..877b8867e60 100644 --- a/js/src/jit/JitCompartment.h +++ b/js/src/jit/JitCompartment.h @@ -158,6 +158,9 @@ class JitRuntime // Shared post-bailout-handler tail. JitCode *bailoutTail_; + // Shared profiler exit frame tail. + JitCode *profilerExitFrameTail_; + // Trampoline for entering JIT code. Contains OSR prologue. JitCode *enterJIT_; @@ -234,6 +237,7 @@ class JitRuntime private: JitCode *generateLazyLinkStub(JSContext *cx); + JitCode *generateProfilerExitFrameTailStub(JSContext *cx); JitCode *generateExceptionTailStub(JSContext *cx, void *handler); JitCode *generateBailoutTailStub(JSContext *cx); JitCode *generateEnterJIT(JSContext *cx, EnterJitType type); @@ -323,6 +327,10 @@ class JitRuntime return bailoutTail_; } + JitCode *getProfilerExitFrameTail() const { + return profilerExitFrameTail_; + } + JitCode *getBailoutTable(const FrameSizeClass &frameClass) const; JitCode *getArgumentsRectifier() const { @@ -391,12 +399,8 @@ class JitRuntime return jitcodeGlobalTable_; } - bool isNativeToBytecodeMapEnabled(JSRuntime *rt) { -#ifdef DEBUG - return true; -#else // DEBUG + bool isProfilerInstrumentationEnabled(JSRuntime *rt) { return rt->spsProfiler.enabled(); -#endif // DEBUG } }; diff --git a/js/src/jit/JitFrameIterator-inl.h b/js/src/jit/JitFrameIterator-inl.h index ad619777ebc..f6f29b71cd5 100644 --- a/js/src/jit/JitFrameIterator-inl.h +++ b/js/src/jit/JitFrameIterator-inl.h @@ -16,6 +16,19 @@ namespace js { namespace jit { +inline JitFrameLayout * +JitProfilingFrameIterator::framePtr() +{ + MOZ_ASSERT(!done()); + return (JitFrameLayout *) fp_; +} + +inline JSScript * +JitProfilingFrameIterator::frameScript() +{ + return ScriptFromCalleeToken(framePtr()->calleeToken()); +} + inline BaselineFrame * JitFrameIterator::baselineFrame() const { diff --git a/js/src/jit/JitFrameIterator.h b/js/src/jit/JitFrameIterator.h index 4e053b88d82..80c92caf3c3 100644 --- a/js/src/jit/JitFrameIterator.h +++ b/js/src/jit/JitFrameIterator.h @@ -14,6 +14,8 @@ #include "jit/IonCode.h" #include "jit/Snapshots.h" +#include "js/ProfilingFrameIterator.h" + namespace js { class ActivationIterator; }; @@ -255,6 +257,33 @@ class JitFrameIterator #endif }; +class JitcodeGlobalTable; + +class JitProfilingFrameIterator +{ + uint8_t *fp_; + FrameType type_; + void *returnAddressToFp_; + + inline JitFrameLayout *framePtr(); + inline JSScript *frameScript(); + bool tryInitWithPC(void *pc); + bool tryInitWithTable(JitcodeGlobalTable *table, void *pc, JSRuntime *rt); + + public: + JitProfilingFrameIterator(JSRuntime *rt, + const JS::ProfilingFrameIterator::RegisterState &state); + explicit JitProfilingFrameIterator(void *exitFrame); + + void operator++(); + bool done() const { return fp_ == nullptr; } + + void *fp() const { MOZ_ASSERT(!done()); return fp_; } + void *stackAddress() const { return fp(); } + FrameType frameType() const { MOZ_ASSERT(!done()); return type_; } + void *returnAddressToFp() const { MOZ_ASSERT(!done()); return returnAddressToFp_; } +}; + class RInstructionResults { // Vector of results of recover instructions. diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index 785246a6664..d57632165e1 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -29,6 +29,7 @@ #include "vm/ArgumentsObject.h" #include "vm/Debugger.h" #include "vm/Interpreter.h" +#include "vm/SPSProfiler.h" #include "vm/TraceLogging.h" #include "jsinferinlines.h" @@ -378,7 +379,7 @@ CloseLiveIterator(JSContext *cx, const InlineFrameIterator &frame, uint32_t loca static void HandleExceptionIon(JSContext *cx, const InlineFrameIterator &frame, ResumeFromException *rfe, - bool *overrecursed, bool *poppedLastSPSFrameOut) + bool *overrecursed) { RootedScript script(cx, frame.script()); jsbytecode *pc = frame.pc(); @@ -410,8 +411,7 @@ HandleExceptionIon(JSContext *cx, const InlineFrameIterator &frame, ResumeFromEx // to the stack depth at the snapshot, as we could've thrown in the // middle of a call. ExceptionBailoutInfo propagateInfo; - uint32_t retval = ExceptionHandlerBailout(cx, frame, rfe, propagateInfo, overrecursed, - poppedLastSPSFrameOut); + uint32_t retval = ExceptionHandlerBailout(cx, frame, rfe, propagateInfo, overrecursed); if (retval == BAILOUT_RETURN_OK) return; } @@ -453,8 +453,7 @@ HandleExceptionIon(JSContext *cx, const InlineFrameIterator &frame, ResumeFromEx // Bailout at the start of the catch block. jsbytecode *catchPC = script->main() + tn->start + tn->length; ExceptionBailoutInfo excInfo(frame.frameNo(), catchPC, tn->stackDepth); - uint32_t retval = ExceptionHandlerBailout(cx, frame, rfe, excInfo, overrecursed, - poppedLastSPSFrameOut); + uint32_t retval = ExceptionHandlerBailout(cx, frame, rfe, excInfo, overrecursed); if (retval == BAILOUT_RETURN_OK) return; @@ -682,12 +681,54 @@ struct AutoClearBaselineOverridePc ~AutoClearBaselineOverridePc() { frame->clearOverridePc(); } }; +struct AutoResetLastProfilerFrameOnReturnFromException +{ + JSContext *cx; + ResumeFromException *rfe; + + AutoResetLastProfilerFrameOnReturnFromException(JSContext *cx, ResumeFromException *rfe) + : cx(cx), rfe(rfe) {} + + ~AutoResetLastProfilerFrameOnReturnFromException() { + if (!cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) + return; + + MOZ_ASSERT(cx->mainThread().jitActivation == cx->mainThread().profilingActivation()); + + void *lastProfilingFrame = getLastProfilingFrame(); + cx->mainThread().jitActivation->setLastProfilingFrame(lastProfilingFrame); + } + + void *getLastProfilingFrame() { + switch (rfe->kind) { + case ResumeFromException::RESUME_ENTRY_FRAME: + return nullptr; + + // The following all return into baseline frames. + case ResumeFromException::RESUME_CATCH: + case ResumeFromException::RESUME_FINALLY: + case ResumeFromException::RESUME_FORCED_RETURN: + return rfe->framePointer + BaselineFrame::FramePointerOffset; + + // When resuming into a bailed-out ion frame, use the bailout info to + // find the frame we are resuming into. + case ResumeFromException::RESUME_BAILOUT: + return rfe->bailoutInfo->incomingStack; + } + + MOZ_CRASH("Invalid ResumeFromException type!"); + return nullptr; + } +}; + void HandleException(ResumeFromException *rfe) { JSContext *cx = GetJSContextFromJitCode(); TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + AutoResetLastProfilerFrameOnReturnFromException profFrameReset(cx, rfe); + rfe->kind = ResumeFromException::RESUME_ENTRY_FRAME; JitSpew(JitSpew_IonInvalidate, "handling exception"); @@ -720,8 +761,7 @@ HandleException(ResumeFromException *rfe) bool invalidated = iter.checkInvalidation(&ionScript); for (;;) { - bool poppedLastSPSFrame = false; - HandleExceptionIon(cx, frames, rfe, &overrecursed, &poppedLastSPSFrame); + HandleExceptionIon(cx, frames, rfe, &overrecursed); if (rfe->kind == ResumeFromException::RESUME_BAILOUT) { if (invalidated) @@ -731,29 +771,13 @@ HandleException(ResumeFromException *rfe) MOZ_ASSERT(rfe->kind == ResumeFromException::RESUME_ENTRY_FRAME); - // Figure out whether SPS frame was pushed for this frame or not. - // Even if profiler is enabled, the frame being popped might have - // been entered prior to SPS being enabled, and thus not have - // a pushed SPS frame. - bool popSPSFrame = cx->runtime()->spsProfiler.enabled(); - if (invalidated) - popSPSFrame = ionScript->hasSPSInstrumentation(); - - // Don't pop an SPS frame for inlined frames, since they are not instrumented. - if (frames.more()) - popSPSFrame = false; - - // Don't pop the last SPS frame if it's already been popped by - // bailing out. - if (poppedLastSPSFrame) - popSPSFrame = false; - // When profiling, each frame popped needs a notification that // the function has exited, so invoke the probe that a function // is exiting. JSScript *script = frames.script(); - probes::ExitScript(cx, script, script->functionNonDelazifying(), popSPSFrame); + probes::ExitScript(cx, script, script->functionNonDelazifying(), + /* popSPSFrame = */ false); if (!frames.more()) { TraceLogStopEvent(logger, TraceLogger_IonMonkey); TraceLogStopEvent(logger, TraceLogger_Scripts); @@ -804,11 +828,7 @@ HandleException(ResumeFromException *rfe) // Unwind profiler pseudo-stack JSScript *script = iter.script(); probes::ExitScript(cx, script, script->functionNonDelazifying(), - iter.baselineFrame()->hasPushedSPSFrame()); - // After this point, any pushed SPS frame would have been popped if it needed - // to be. Unset the flag here so that if we call DebugEpilogue below, - // it doesn't try to pop the SPS frame again. - iter.baselineFrame()->unsetPushedSPSFrame(); + /* popSPSFrame = */ false); if (iter.baselineFrame()->isDebuggee() && !calledDebugEpilogue) { // If we still need to call the DebugEpilogue, we must @@ -2651,7 +2671,7 @@ JitFrameIterator::verifyReturnAddressUsingNativeToBytecodeMap() // Look up and print bytecode info for the native address. JitcodeGlobalEntry entry; - if (!jitrt->getJitcodeGlobalTable()->lookup(returnAddressToFp_, &entry)) + if (!jitrt->getJitcodeGlobalTable()->lookup(returnAddressToFp_, &entry, rt)) return true; JitSpew(JitSpew_Profiling, "Found nativeToBytecode entry for %p: %p - %p", @@ -2698,6 +2718,278 @@ JitFrameIterator::verifyReturnAddressUsingNativeToBytecodeMap() } #endif // DEBUG +JitProfilingFrameIterator::JitProfilingFrameIterator( + JSRuntime *rt, const JS::ProfilingFrameIterator::RegisterState &state) +{ + // If no profilingActivation is live, initialize directly to + // end-of-iteration state. + if (!rt->mainThread.profilingActivation()) { + type_ = JitFrame_Entry; + fp_ = nullptr; + returnAddressToFp_ = nullptr; + return; + } + + MOZ_ASSERT(rt->mainThread.profilingActivation()->isJit()); + + JitActivation *act = rt->mainThread.profilingActivation()->asJit(); + + // If the top JitActivation has a null lastProfilingFrame, assume that + // it's a trivially empty activation, and initialize directly + // to end-of-iteration state. + if (!act->lastProfilingFrame()) { + type_ = JitFrame_Entry; + fp_ = nullptr; + returnAddressToFp_ = nullptr; + return; + } + + // Get the fp from the current profilingActivation + fp_ = (uint8_t *) act->lastProfilingFrame(); + void *lastCallSite = act->lastProfilingCallSite(); + + JitcodeGlobalTable *table = rt->jitRuntime()->getJitcodeGlobalTable(); + + // Profiler sampling must NOT be suppressed if we are here. + MOZ_ASSERT(rt->isProfilerSamplingEnabled()); + + // Since the frame is on stack, and is a jit frame, it MUST have Baseline jitcode. + MOZ_ASSERT(frameScript()->hasBaselineScript()); + + // Try initializing with sampler pc + if (tryInitWithPC(state.pc)) + return; + + // Try initializing with sampler pc using native=>bytecode table. + if (tryInitWithTable(table, state.pc, rt)) + return; + + // Try initializing with lastProfilingCallSite pc + if (lastCallSite) { + if (tryInitWithPC(lastCallSite)) + return; + + // Try initializing with lastProfilingCallSite pc using native=>bytecode table. + if (tryInitWithTable(table, lastCallSite, rt)) + return; + } + + // If nothing matches, for now just assume we are at the start of the last frame's + // baseline jit code. + type_ = JitFrame_BaselineJS; + returnAddressToFp_ = frameScript()->baselineScript()->method()->raw(); + //++(*this); +} + +template +inline ReturnType +GetPreviousRawFrame(FrameType *frame) +{ + size_t prevSize = frame->prevFrameLocalSize() + FrameType::Size(); + return (ReturnType) (((uint8_t *) frame) + prevSize); +} + +JitProfilingFrameIterator::JitProfilingFrameIterator(void *exitFrame) +{ + // Exit frame was en + ExitFrameLayout *frame = (ExitFrameLayout *) exitFrame; + FrameType prevType = frame->prevType(); + + if (prevType == JitFrame_IonJS || prevType == JitFrame_BaselineJS || + prevType == JitFrame_Unwound_IonJS) + { + returnAddressToFp_ = frame->returnAddress(); + fp_ = GetPreviousRawFrame(frame); + type_ = JitFrame_IonJS; + return; + } + + if (prevType == JitFrame_BaselineStub || prevType == JitFrame_Unwound_BaselineStub) { + BaselineStubFrameLayout *stubFrame = + GetPreviousRawFrame(frame); + MOZ_ASSERT_IF(prevType == JitFrame_BaselineStub, + stubFrame->prevType() == JitFrame_BaselineJS); + MOZ_ASSERT_IF(prevType == JitFrame_Unwound_BaselineStub, + stubFrame->prevType() == JitFrame_BaselineJS || + stubFrame->prevType() == JitFrame_IonJS); + returnAddressToFp_ = stubFrame->returnAddress(); + fp_ = ((uint8_t *) stubFrame->reverseSavedFramePtr()) + + jit::BaselineFrame::FramePointerOffset; + type_ = JitFrame_BaselineJS; + return; + } + + MOZ_CRASH("Invalid frame type prior to exit frame."); +} + +bool +JitProfilingFrameIterator::tryInitWithPC(void *pc) +{ + JSScript *callee = frameScript(); + + // Check for Ion first, since it's more likely for hot code. + if (callee->hasIonScript() && callee->ionScript()->method()->containsNativePC(pc)) { + type_ = JitFrame_IonJS; + returnAddressToFp_ = pc; + return true; + } + + // Check for containment in Baseline jitcode second. + if (callee->baselineScript()->method()->containsNativePC(pc)) { + type_ = JitFrame_BaselineJS; + returnAddressToFp_ = pc; + return true; + } + + return false; +} + +bool +JitProfilingFrameIterator::tryInitWithTable(JitcodeGlobalTable *table, void *pc, JSRuntime *rt) +{ + if (!pc) + return false; + + JitcodeGlobalEntry entry; + if (!table->lookup(pc, &entry, rt)) + return false; + + JSScript *callee = frameScript(); + + MOZ_ASSERT(entry.isIon() || entry.isBaseline() || entry.isIonCache()); + if (entry.isIon()) { + // If looked-up callee doesn't match frame callee, don't accept lastProfilingCallSite + if (entry.ionEntry().getScript(0) != callee) + return false; + + type_ = JitFrame_IonJS; + returnAddressToFp_ = pc; + return true; + } + + if (entry.isBaseline()) { + // If looked-up callee doesn't match frame callee, don't accept lastProfilingCallSite + if (entry.baselineEntry().script() != callee) + return false; + + type_ = JitFrame_BaselineJS; + returnAddressToFp_ = pc; + return true; + } + + if (entry.isIonCache()) { + JitcodeGlobalEntry ionEntry; + table->lookupInfallible(entry.ionCacheEntry().rejoinAddr(), &ionEntry, rt); + MOZ_ASSERT(ionEntry.isIon()); + + if (ionEntry.ionEntry().getScript(0) != callee) + return false; + + type_ = JitFrame_IonJS; + returnAddressToFp_ = entry.ionCacheEntry().rejoinAddr(); + return true; + } + + return false; +} + +void +JitProfilingFrameIterator::operator++() +{ + /* + * fp_ points to a Baseline or Ion frame. The possible call-stacks + * patterns occurring between this frame and a previous Ion or Baseline + * frame are as follows: + * + * + * ^ + * | + * ^--- Ion + * | + * ^--- Baseline Stub <---- Baseline + * | + * ^--- Argument Rectifier + * | ^ + * | | + * | ^--- Ion + * | | + * | ^--- Baseline Stub <---- Baseline + * | + * ^--- Entry Frame (From C++) + * Exit Frame (From previous JitActivation) + * ^ + * | + * ^--- Ion + * | + * ^--- Baseline + * | + * ^--- Baseline Stub <---- Baseline + */ + JitFrameLayout *frame = framePtr(); + FrameType prevType = frame->prevType(); + + if (prevType == JitFrame_IonJS) { + returnAddressToFp_ = frame->returnAddress(); + fp_ = GetPreviousRawFrame(frame); + type_ = JitFrame_IonJS; + return; + } + + if (prevType == JitFrame_BaselineJS) { + returnAddressToFp_ = frame->returnAddress(); + fp_ = GetPreviousRawFrame(frame); + type_ = JitFrame_BaselineJS; + return; + } + + if (prevType == JitFrame_BaselineStub) { + BaselineStubFrameLayout *stubFrame = + GetPreviousRawFrame(frame); + MOZ_ASSERT(stubFrame->prevType() == JitFrame_BaselineJS); + + returnAddressToFp_ = stubFrame->returnAddress(); + fp_ = ((uint8_t *) stubFrame->reverseSavedFramePtr()) + + jit::BaselineFrame::FramePointerOffset; + type_ = JitFrame_BaselineJS; + return; + } + + if (prevType == JitFrame_Rectifier) { + RectifierFrameLayout *rectFrame = + GetPreviousRawFrame(frame); + FrameType rectPrevType = rectFrame->prevType(); + + if (rectPrevType == JitFrame_IonJS) { + returnAddressToFp_ = rectFrame->returnAddress(); + fp_ = GetPreviousRawFrame(rectFrame); + type_ = JitFrame_IonJS; + return; + } + + if (rectPrevType == JitFrame_BaselineStub) { + BaselineStubFrameLayout *stubFrame = + GetPreviousRawFrame(rectFrame); + returnAddressToFp_ = stubFrame->returnAddress(); + fp_ = ((uint8_t *) stubFrame->reverseSavedFramePtr()) + + jit::BaselineFrame::FramePointerOffset; + type_ = JitFrame_BaselineJS; + return; + } + + MOZ_CRASH("Bad frame type prior to rectifier frame."); + } + + if (prevType == JitFrame_Entry) { + // No previous frame, set to null to indicate that JitFrameIterator is done() + returnAddressToFp_ = nullptr; + fp_ = nullptr; + type_ = JitFrame_Entry; + return; + } + + MOZ_CRASH("Bad frame type."); +} + JitFrameLayout * InvalidationBailoutStack::fp() const { diff --git a/js/src/jit/JitFrames.h b/js/src/jit/JitFrames.h index 3ca7ee17cdb..9c13dbb0fd2 100644 --- a/js/src/jit/JitFrames.h +++ b/js/src/jit/JitFrames.h @@ -331,6 +331,9 @@ class CommonFrameLayout static size_t offsetOfDescriptor() { return offsetof(CommonFrameLayout, descriptor_); } + uintptr_t descriptor() const { + return descriptor_; + } static size_t offsetOfReturnAddress() { return offsetof(CommonFrameLayout, returnAddress_); } @@ -823,6 +826,11 @@ class BaselineStubFrameLayout : public CommonFrameLayout return -int(2 * sizeof(void *)); } + void *reverseSavedFramePtr() { + uint8_t *addr = ((uint8_t *) this) + reverseOffsetOfSavedFramePtr(); + return *(void **)addr; + } + inline ICStub *maybeStubPtr() { uint8_t *fp = reinterpret_cast(this); return *reinterpret_cast(fp + reverseOffsetOfStubPtr()); diff --git a/js/src/jit/JitcodeMap.cpp b/js/src/jit/JitcodeMap.cpp index 5a5e0494d4c..8c65861212f 100644 --- a/js/src/jit/JitcodeMap.cpp +++ b/js/src/jit/JitcodeMap.cpp @@ -7,10 +7,15 @@ #include "jit/JitcodeMap.h" #include "mozilla/DebugOnly.h" +#include "mozilla/UniquePtr.h" +#include "jsprf.h" + #include "jit/BaselineJIT.h" #include "jit/JitSpewer.h" #include "js/Vector.h" +#include "vm/SPSProfiler.h" +#include "jsscriptinlines.h" namespace js { namespace jit { @@ -51,6 +56,38 @@ JitcodeGlobalEntry::IonEntry::callStackAtAddr(JSRuntime *rt, void *ptr, return true; } +uint32_t +JitcodeGlobalEntry::IonEntry::callStackAtAddr(JSRuntime *rt, void *ptr, + const char **results, + uint32_t maxResults) const +{ + MOZ_ASSERT(containsPointer(ptr)); + MOZ_ASSERT(maxResults >= 1); + uint32_t ptrOffset = reinterpret_cast(ptr) - + reinterpret_cast(nativeStartAddr()); + + uint32_t regionIdx = regionTable()->findRegionEntry(ptrOffset); + MOZ_ASSERT(regionIdx < regionTable()->numRegions()); + + JitcodeRegionEntry region = regionTable()->regionEntry(regionIdx); + + JitcodeRegionEntry::ScriptPcIterator locationIter = region.scriptPcIterator(); + MOZ_ASSERT(locationIter.hasMore()); + uint32_t count = 0; + while (locationIter.hasMore()) { + uint32_t scriptIdx, pcOffset; + + locationIter.readNext(&scriptIdx, &pcOffset); + MOZ_ASSERT(getStr(scriptIdx)); + + results[count++] = getStr(scriptIdx); + if (count >= maxResults) + break; + } + + return count; +} + void JitcodeGlobalEntry::IonEntry::destroy() { @@ -63,11 +100,15 @@ JitcodeGlobalEntry::IonEntry::destroy() js_free((void*) (regionTable_->payloadStart())); regionTable_ = nullptr; - // Single tag is just pointer-to-jsscript, no memory to free. - ScriptListTag tag = scriptListTag(); - if (tag > Single) - js_free(scriptListPointer()); - scriptList_ = 0; + // Free the scriptList strs. + for (uint32_t i = 0; i < scriptList_->size; i++) { + js_free(scriptList_->pairs[i].str); + scriptList_->pairs[i].str = nullptr; + } + + // Free the script list + js_free(scriptList_); + scriptList_ = nullptr; } bool @@ -88,6 +129,28 @@ JitcodeGlobalEntry::BaselineEntry::callStackAtAddr(JSRuntime *rt, void *ptr, return true; } +uint32_t +JitcodeGlobalEntry::BaselineEntry::callStackAtAddr(JSRuntime *rt, void *ptr, + const char **results, + uint32_t maxResults) const +{ + MOZ_ASSERT(containsPointer(ptr)); + MOZ_ASSERT(script_->hasBaselineScript()); + MOZ_ASSERT(maxResults >= 1); + + results[0] = str(); + return 1; +} + +void +JitcodeGlobalEntry::BaselineEntry::destroy() +{ + if (!str_) + return; + js_free((void*) str_); + str_ = nullptr; +} + bool JitcodeGlobalEntry::IonCacheEntry::callStackAtAddr(JSRuntime *rt, void *ptr, BytecodeLocationVector &results, @@ -98,12 +161,28 @@ JitcodeGlobalEntry::IonCacheEntry::callStackAtAddr(JSRuntime *rt, void *ptr, // There must exist an entry for the rejoin addr if this entry exists. JitRuntime *jitrt = rt->jitRuntime(); JitcodeGlobalEntry entry; - jitrt->getJitcodeGlobalTable()->lookupInfallible(rejoinAddr(), &entry); + jitrt->getJitcodeGlobalTable()->lookupInfallible(rejoinAddr(), &entry, rt); MOZ_ASSERT(entry.isIon()); return entry.callStackAtAddr(rt, rejoinAddr(), results, depth); } +uint32_t +JitcodeGlobalEntry::IonCacheEntry::callStackAtAddr(JSRuntime *rt, void *ptr, + const char **results, + uint32_t maxResults) const +{ + MOZ_ASSERT(containsPointer(ptr)); + + // There must exist an entry for the rejoin addr if this entry exists. + JitRuntime *jitrt = rt->jitRuntime(); + JitcodeGlobalEntry entry; + jitrt->getJitcodeGlobalTable()->lookupInfallible(rejoinAddr(), &entry, rt); + MOZ_ASSERT(entry.isIon()); + + return entry.callStackAtAddr(rt, rejoinAddr(), results, maxResults); +} + static int ComparePointers(const void *a, const void *b) { const uint8_t *a_ptr = reinterpret_cast(a); @@ -144,34 +223,135 @@ JitcodeGlobalEntry::compare(const JitcodeGlobalEntry &ent1, const JitcodeGlobalE return flip * -1; } +/* static */ char * +JitcodeGlobalEntry::createScriptString(JSContext *cx, JSScript *script, size_t *length) +{ + // If the script has a function, try calculating its name. + bool hasName = false; + size_t nameLength = 0; + mozilla::UniquePtr nameStr = nullptr; + JSFunction *func = script->functionDelazifying(); + if (func && func->displayAtom()) { + JSAtom *atom = func->displayAtom(); + + JS::AutoCheckCannotGC nogc; + nameStr = mozilla::UniquePtr( + atom->hasLatin1Chars() ? + JS::CharsToNewUTF8CharsZ(cx, atom->latin1Range(nogc)).c_str() + : JS::CharsToNewUTF8CharsZ(cx, atom->twoByteRange(nogc)).c_str()); + if (!nameStr) + return nullptr; + + nameLength = strlen(nameStr.get()); + hasName = true; + } + + // Calculate filename length + const char *filenameStr = script->filename() ? script->filename() : "(null)"; + size_t filenameLength = strlen(filenameStr); + + // Calculate lineno length + bool hasLineno = false; + size_t linenoLength = 0; + char linenoStr[15]; + if (hasName || (script->functionNonDelazifying() || script->isForEval())) { + linenoLength = JS_snprintf(linenoStr, 15, "%u", (unsigned) script->lineno()); + hasLineno = true; + } + + // Full profile string for scripts with functions is: + // FuncName (FileName:Lineno) + // Full profile string for scripts without functions is: + // FileName:Lineno + // Full profile string for scripts without functions and without linenos is: + // FileName + + // Calculate full string length. + size_t fullLength = 0; + if (hasName) { + MOZ_ASSERT(hasLineno); + fullLength = nameLength + 2 + filenameLength + 1 + linenoLength + 1; + } else if (hasLineno) { + fullLength = filenameLength + 1 + linenoLength; + } else { + fullLength = filenameLength; + } + + // Allocate string. + char *str = cx->pod_malloc(fullLength + 1); + if (!str) + return nullptr; + + size_t cur = 0; + + // Fill string with func name if needed. + if (hasName) { + memcpy(str + cur, nameStr.get(), nameLength); + cur += nameLength; + str[cur++] = ' '; + str[cur++] = '('; + } + + // Fill string with filename chars. + memcpy(str + cur, filenameStr, filenameLength); + cur += filenameLength; + + // Fill lineno chars. + if (hasLineno) { + str[cur++] = ':'; + memcpy(str + cur, linenoStr, linenoLength); + cur += linenoLength; + } + + // Terminal ')' if necessary. + if (hasName) + str[cur++] = ')'; + + MOZ_ASSERT(cur == fullLength); + str[cur] = 0; + + if (length) + *length = fullLength; + + return str; +} + bool -JitcodeGlobalTable::lookup(void *ptr, JitcodeGlobalEntry *result) +JitcodeGlobalTable::lookup(void *ptr, JitcodeGlobalEntry *result, JSRuntime *rt) { MOZ_ASSERT(result); // Construct a JitcodeGlobalEntry::Query to do the lookup JitcodeGlobalEntry query = JitcodeGlobalEntry::MakeQuery(ptr); + + // Lookups on tree does mutation. Suppress sampling when this is happening. + AutoSuppressProfilerSampling suppressSampling(rt); return tree_.contains(query, result); } void -JitcodeGlobalTable::lookupInfallible(void *ptr, JitcodeGlobalEntry *result) +JitcodeGlobalTable::lookupInfallible(void *ptr, JitcodeGlobalEntry *result, JSRuntime *rt) { - mozilla::DebugOnly success = lookup(ptr, result); + mozilla::DebugOnly success = lookup(ptr, result, rt); MOZ_ASSERT(success); } bool -JitcodeGlobalTable::addEntry(const JitcodeGlobalEntry &entry) +JitcodeGlobalTable::addEntry(const JitcodeGlobalEntry &entry, JSRuntime *rt) { - // Should only add Main entries for now. - MOZ_ASSERT(entry.isIon() || entry.isBaseline() || entry.isIonCache()); + // Suppress profiler sampling while table is being mutated. + AutoSuppressProfilerSampling suppressSampling(rt); + + MOZ_ASSERT(entry.isIon() || entry.isBaseline() || entry.isIonCache() || entry.isDummy()); return tree_.insert(entry); } void -JitcodeGlobalTable::removeEntry(void *startAddr) +JitcodeGlobalTable::removeEntry(void *startAddr, JSRuntime *rt) { + // Suppress profiler sampling while table is being mutated. + AutoSuppressProfilerSampling suppressSampling(rt); + JitcodeGlobalEntry query = JitcodeGlobalEntry::MakeQuery(startAddr); JitcodeGlobalEntry result; mozilla::DebugOnly success = tree_.contains(query, &result); @@ -556,6 +736,26 @@ JitcodeRegionEntry::findPcOffset(uint32_t queryNativeOffset, uint32_t startPcOff return curPcOffset; } +typedef js::Vector ProfilingStringVector; + +struct AutoFreeProfilingStrings { + ProfilingStringVector &profilingStrings_; + bool keep_; + explicit AutoFreeProfilingStrings(ProfilingStringVector &vec) + : profilingStrings_(vec), + keep_(false) + {} + + void keepStrings() { keep_ = true; } + + ~AutoFreeProfilingStrings() { + if (keep_) + return; + for (size_t i = 0; i < profilingStrings_.length(); i++) + js_free(profilingStrings_[i]); + } +}; + bool JitcodeIonTable::makeIonEntry(JSContext *cx, JitCode *code, uint32_t numScripts, JSScript **scripts, @@ -565,25 +765,32 @@ JitcodeIonTable::makeIonEntry(JSContext *cx, JitCode *code, MOZ_ASSERT(numScripts > 0); - if (numScripts == 1) { - out.init(code->raw(), code->rawEnd(), scripts[0], this); - return true; - } + // Create profiling strings for script, within vector. + typedef js::Vector ProfilingStringVector; - if (numScripts < uint32_t(JitcodeGlobalEntry::IonEntry::Multi)) { - JSScript **scriptsCopy = cx->pod_malloc(numScripts); - if (!scriptsCopy) + ProfilingStringVector profilingStrings; + if (!profilingStrings.reserve(numScripts)) + return false; + + AutoFreeProfilingStrings autoFreeProfilingStrings(profilingStrings); + for (uint32_t i = 0; i < numScripts; i++) { + char *str = JitcodeGlobalEntry::createScriptString(cx, scripts[i]); + if (!str) + return false; + if (!profilingStrings.append(str)) return false; - memcpy(scriptsCopy, scripts, sizeof(JSScript *) * numScripts); - out.init(code->raw(), code->rawEnd(), numScripts, scriptsCopy, this); - return true; } // Create SizedScriptList void *mem = (void *)cx->pod_malloc(SizedScriptList::AllocSizeFor(numScripts)); if (!mem) return false; - SizedScriptList *scriptList = new (mem) SizedScriptList(numScripts, scripts); + + // Keep allocated profiling strings on destruct. + autoFreeProfilingStrings.keepStrings(); + + SizedScriptList *scriptList = new (mem) SizedScriptList(numScripts, scripts, + &profilingStrings[0]); out.init(code->raw(), code->rawEnd(), scriptList, this); return true; } diff --git a/js/src/jit/JitcodeMap.h b/js/src/jit/JitcodeMap.h index b613dd9fc76..df81271afc5 100644 --- a/js/src/jit/JitcodeMap.h +++ b/js/src/jit/JitcodeMap.h @@ -41,6 +41,7 @@ class JitcodeGlobalEntry Ion, Baseline, IonCache, + Dummy, Query, LIMIT }; @@ -52,6 +53,7 @@ class JitcodeGlobalEntry BytecodeLocation(JSScript *script, jsbytecode *pc) : script(script), pc(pc) {} }; typedef Vector BytecodeLocationVector; + typedef Vector ProfileStringVector; struct BaseEntry { @@ -97,8 +99,6 @@ class JitcodeGlobalEntry struct IonEntry : public BaseEntry { - uintptr_t scriptList_; - // regionTable_ points to the start of the region table within the // packed map for compile represented by this entry. Since the // region table occurs at the tail of the memory region, this pointer @@ -106,111 +106,54 @@ class JitcodeGlobalEntry // of the memory space. JitcodeIonTable *regionTable_; - static const unsigned LowBits = 3; - static const uintptr_t LowMask = (uintptr_t(1) << LowBits) - 1; - - enum ScriptListTag { - Single = 0, - Multi = 7 + struct ScriptNamePair { + JSScript *script; + char *str; }; struct SizedScriptList { uint32_t size; - JSScript *scripts[0]; - SizedScriptList(uint32_t sz, JSScript **scr) : size(sz) { - for (uint32_t i = 0; i < size; i++) - scripts[i] = scr[i]; + ScriptNamePair pairs[0]; + SizedScriptList(uint32_t sz, JSScript **scrs, char **strs) : size(sz) { + for (uint32_t i = 0; i < size; i++) { + pairs[i].script = scrs[i]; + pairs[i].str = strs[i]; + } } static uint32_t AllocSizeFor(uint32_t nscripts) { - return sizeof(SizedScriptList) + (nscripts * sizeof(JSScript *)); + return sizeof(SizedScriptList) + (nscripts * sizeof(ScriptNamePair)); } }; - void init(void *nativeStartAddr, void *nativeEndAddr, - JSScript *script, JitcodeIonTable *regionTable) - { - MOZ_ASSERT((uintptr_t(script) & LowMask) == 0); - MOZ_ASSERT(script); - MOZ_ASSERT(regionTable); - BaseEntry::init(Ion, nativeStartAddr, nativeEndAddr); - scriptList_ = uintptr_t(script); - regionTable_ = regionTable; - } + SizedScriptList *scriptList_; void init(void *nativeStartAddr, void *nativeEndAddr, - unsigned numScripts, JSScript **scripts, JitcodeIonTable *regionTable) + SizedScriptList *scriptList, JitcodeIonTable *regionTable) { - MOZ_ASSERT((uintptr_t(scripts) & LowMask) == 0); - MOZ_ASSERT(numScripts >= 1); - MOZ_ASSERT(numScripts <= 6); - MOZ_ASSERT(scripts); + MOZ_ASSERT(scriptList); MOZ_ASSERT(regionTable); BaseEntry::init(Ion, nativeStartAddr, nativeEndAddr); - scriptList_ = uintptr_t(scripts) | numScripts; regionTable_ = regionTable; + scriptList_ = scriptList; } - void init(void *nativeStartAddr, void *nativeEndAddr, - SizedScriptList *scripts, JitcodeIonTable *regionTable) - { - MOZ_ASSERT((uintptr_t(scripts) & LowMask) == 0); - MOZ_ASSERT(scripts->size > 6); - MOZ_ASSERT(scripts); - MOZ_ASSERT(regionTable); - - BaseEntry::init(Ion, nativeStartAddr, nativeEndAddr); - scriptList_ = uintptr_t(scripts) | uintptr_t(Multi); - regionTable_ = regionTable; - } - - ScriptListTag scriptListTag() const { - return static_cast(scriptList_ & LowMask); - } - void *scriptListPointer() const { - return reinterpret_cast(scriptList_ & ~LowMask); - } - - JSScript *singleScript() const { - MOZ_ASSERT(scriptListTag() == Single); - return reinterpret_cast(scriptListPointer()); - } - JSScript **rawScriptArray() const { - MOZ_ASSERT(scriptListTag() < Multi); - return reinterpret_cast(scriptListPointer()); - } SizedScriptList *sizedScriptList() const { - MOZ_ASSERT(scriptListTag() == Multi); - return reinterpret_cast(scriptListPointer()); + return scriptList_; } unsigned numScripts() const { - ScriptListTag tag = scriptListTag(); - if (tag == Single) - return 1; - - if (tag < Multi) { - MOZ_ASSERT(int(tag) >= 2); - return static_cast(tag); - } - - return sizedScriptList()->size; + return scriptList_->size; } JSScript *getScript(unsigned idx) const { MOZ_ASSERT(idx < numScripts()); + return sizedScriptList()->pairs[idx].script; + } - ScriptListTag tag = scriptListTag(); - - if (tag == Single) - return singleScript(); - - if (tag < Multi) { - MOZ_ASSERT(int(tag) >= 2); - return rawScriptArray()[idx]; - } - - return sizedScriptList()->scripts[idx]; + const char *getStr(unsigned idx) const { + MOZ_ASSERT(idx < numScripts()); + return sizedScriptList()->pairs[idx].str; } void destroy(); @@ -230,27 +173,39 @@ class JitcodeGlobalEntry bool callStackAtAddr(JSRuntime *rt, void *ptr, BytecodeLocationVector &results, uint32_t *depth) const; + + uint32_t callStackAtAddr(JSRuntime *rt, void *ptr, const char **results, + uint32_t maxResults) const; }; struct BaselineEntry : public BaseEntry { JSScript *script_; + const char *str_; - void init(void *nativeStartAddr, void *nativeEndAddr, JSScript *script) + void init(void *nativeStartAddr, void *nativeEndAddr, JSScript *script, const char *str) { MOZ_ASSERT(script != nullptr); BaseEntry::init(Baseline, nativeStartAddr, nativeEndAddr); script_ = script; + str_ = str; } JSScript *script() const { return script_; } - void destroy() {} + const char *str() const { + return str_; + } + + void destroy(); bool callStackAtAddr(JSRuntime *rt, void *ptr, BytecodeLocationVector &results, uint32_t *depth) const; + + uint32_t callStackAtAddr(JSRuntime *rt, void *ptr, const char **results, + uint32_t maxResults) const; }; struct IonCacheEntry : public BaseEntry @@ -272,6 +227,33 @@ class JitcodeGlobalEntry bool callStackAtAddr(JSRuntime *rt, void *ptr, BytecodeLocationVector &results, uint32_t *depth) const; + + uint32_t callStackAtAddr(JSRuntime *rt, void *ptr, const char **results, + uint32_t maxResults) const; + }; + + // Dummy entries are created for jitcode generated when profiling is not turned on, + // so that they have representation in the global table if they are on the + // stack when profiling is enabled. + struct DummyEntry : public BaseEntry + { + void init(void *nativeStartAddr, void *nativeEndAddr) { + BaseEntry::init(Dummy, nativeStartAddr, nativeEndAddr); + } + + void destroy() {} + + bool callStackAtAddr(JSRuntime *rt, void *ptr, BytecodeLocationVector &results, + uint32_t *depth) const + { + return true; + } + + uint32_t callStackAtAddr(JSRuntime *rt, void *ptr, const char **results, + uint32_t maxResults) const + { + return 0; + } }; // QueryEntry is never stored in the table, just used for queries @@ -304,6 +286,9 @@ class JitcodeGlobalEntry // IonCache stubs. IonCacheEntry ionCache_; + // Dummy entries. + DummyEntry dummy_; + // When doing queries on the SplayTree for particular addresses, // the query addresses are representd using a QueryEntry. QueryEntry query_; @@ -326,6 +311,10 @@ class JitcodeGlobalEntry ionCache_ = ionCache; } + explicit JitcodeGlobalEntry(const DummyEntry &dummy) { + dummy_ = dummy; + } + explicit JitcodeGlobalEntry(const QueryEntry &query) { query_ = query; } @@ -347,6 +336,9 @@ class JitcodeGlobalEntry case IonCache: ionCacheEntry().destroy(); break; + case Dummy: + dummyEntry().destroy(); + break; case Query: queryEntry().destroy(); break; @@ -397,6 +389,9 @@ class JitcodeGlobalEntry bool isIonCache() const { return kind() == IonCache; } + bool isDummy() const { + return kind() == Dummy; + } bool isQuery() const { return kind() == Query; } @@ -413,6 +408,10 @@ class JitcodeGlobalEntry MOZ_ASSERT(isIonCache()); return ionCache_; } + DummyEntry &dummyEntry() { + MOZ_ASSERT(isDummy()); + return dummy_; + } QueryEntry &queryEntry() { MOZ_ASSERT(isQuery()); return query_; @@ -430,6 +429,10 @@ class JitcodeGlobalEntry MOZ_ASSERT(isIonCache()); return ionCache_; } + const DummyEntry &dummyEntry() const { + MOZ_ASSERT(isDummy()); + return dummy_; + } const QueryEntry &queryEntry() const { MOZ_ASSERT(isQuery()); return query_; @@ -450,6 +453,26 @@ class JitcodeGlobalEntry return baselineEntry().callStackAtAddr(rt, ptr, results, depth); case IonCache: return ionCacheEntry().callStackAtAddr(rt, ptr, results, depth); + case Dummy: + return dummyEntry().callStackAtAddr(rt, ptr, results, depth); + default: + MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); + } + return false; + } + + uint32_t callStackAtAddr(JSRuntime *rt, void *ptr, const char **results, + uint32_t maxResults) const + { + switch (kind()) { + case Ion: + return ionEntry().callStackAtAddr(rt, ptr, results, maxResults); + case Baseline: + return baselineEntry().callStackAtAddr(rt, ptr, results, maxResults); + case IonCache: + return ionCacheEntry().callStackAtAddr(rt, ptr, results, maxResults); + case Dummy: + return dummyEntry().callStackAtAddr(rt, ptr, results, maxResults); default: MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); } @@ -462,6 +485,9 @@ class JitcodeGlobalEntry // Compare two global entries. static int compare(const JitcodeGlobalEntry &ent1, const JitcodeGlobalEntry &ent2); + + // Compute a profiling string for a given script. + static char *createScriptString(JSContext *cx, JSScript *script, size_t *length=nullptr); }; /* @@ -492,23 +518,26 @@ class JitcodeGlobalTable return tree_.empty(); } - bool lookup(void *ptr, JitcodeGlobalEntry *result); - void lookupInfallible(void *ptr, JitcodeGlobalEntry *result); + bool lookup(void *ptr, JitcodeGlobalEntry *result, JSRuntime *rt); + void lookupInfallible(void *ptr, JitcodeGlobalEntry *result, JSRuntime *rt); - bool addEntry(const JitcodeGlobalEntry::IonEntry &entry) { - return addEntry(JitcodeGlobalEntry(entry)); + bool addEntry(const JitcodeGlobalEntry::IonEntry &entry, JSRuntime *rt) { + return addEntry(JitcodeGlobalEntry(entry), rt); } - bool addEntry(const JitcodeGlobalEntry::BaselineEntry &entry) { - return addEntry(JitcodeGlobalEntry(entry)); + bool addEntry(const JitcodeGlobalEntry::BaselineEntry &entry, JSRuntime *rt) { + return addEntry(JitcodeGlobalEntry(entry), rt); } - bool addEntry(const JitcodeGlobalEntry::IonCacheEntry &entry) { - return addEntry(JitcodeGlobalEntry(entry)); + bool addEntry(const JitcodeGlobalEntry::IonCacheEntry &entry, JSRuntime *rt) { + return addEntry(JitcodeGlobalEntry(entry), rt); + } + bool addEntry(const JitcodeGlobalEntry::DummyEntry &entry, JSRuntime *rt) { + return addEntry(JitcodeGlobalEntry(entry), rt); } - void removeEntry(void *startAddr); + void removeEntry(void *startAddr, JSRuntime *rt); private: - bool addEntry(const JitcodeGlobalEntry &entry); + bool addEntry(const JitcodeGlobalEntry &entry, JSRuntime *rt); }; @@ -815,8 +844,8 @@ class JitcodeIonTable regionOffsets_[i] = 0; } - bool makeIonEntry(JSContext *cx, JitCode *code, uint32_t numScripts, JSScript **scripts, - JitcodeGlobalEntry::IonEntry &out); + bool makeIonEntry(JSContext *cx, JitCode *code, uint32_t numScripts, + JSScript **scripts, JitcodeGlobalEntry::IonEntry &out); uint32_t numRegions() const { return numRegions_; diff --git a/js/src/jit/LIR-Common.h b/js/src/jit/LIR-Common.h index 27eaad1a6a6..d58c97dac0f 100644 --- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -3666,7 +3666,7 @@ class LStart : public LInstructionHelper<0, 0, 0> // Passed the BaselineFrame address in the OsrFrameReg by SideCannon(). // Forwards this object to the LOsrValues for Value materialization. -class LOsrEntry : public LInstructionHelper<1, 0, 0> +class LOsrEntry : public LInstructionHelper<1, 0, 1> { protected: Label label_; @@ -3675,9 +3675,11 @@ class LOsrEntry : public LInstructionHelper<1, 0, 0> public: LIR_HEADER(OsrEntry) - LOsrEntry() + explicit LOsrEntry(const LDefinition &temp) : frameDepth_(0) - { } + { + setTemp(0, temp); + } void setFrameDepth(uint32_t depth) { frameDepth_ = depth; @@ -3688,7 +3690,9 @@ class LOsrEntry : public LInstructionHelper<1, 0, 0> Label *label() { return &label_; } - + const LDefinition *temp() { + return getTemp(0); + } }; // Materialize a Value stored in an interpreter frame for OSR. @@ -6161,28 +6165,6 @@ class LCallInstanceOf : public LCallInstructionHelper<1, BOX_PIECES+1, 0> static const size_t RHS = BOX_PIECES; }; -class LProfilerStackOp : public LInstructionHelper<0, 0, 1> -{ - public: - LIR_HEADER(ProfilerStackOp) - - explicit LProfilerStackOp(const LDefinition &temp) { - setTemp(0, temp); - } - - const LDefinition *temp() { - return getTemp(0); - } - - JSScript *script() { - return mir_->toProfilerStackOp()->script(); - } - - MProfilerStackOp::Type type() { - return mir_->toProfilerStackOp()->type(); - } -}; - class LIsCallable : public LInstructionHelper<1, 1, 0> { public: diff --git a/js/src/jit/LOpcodes.h b/js/src/jit/LOpcodes.h index 2a50bfbbdbd..6df9f485a41 100644 --- a/js/src/jit/LOpcodes.h +++ b/js/src/jit/LOpcodes.h @@ -305,7 +305,6 @@ _(InterruptCheck) \ _(AsmJSInterruptCheck) \ _(InterruptCheckImplicit) \ - _(ProfilerStackOp) \ _(GetDOMProperty) \ _(GetDOMMemberV) \ _(GetDOMMemberT) \ diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index d40d214e089..3cb751eb247 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -1633,7 +1633,7 @@ LIRGenerator::visitLimitedTruncate(MLimitedTruncate *nop) void LIRGenerator::visitOsrEntry(MOsrEntry *entry) { - LOsrEntry *lir = new(alloc()) LOsrEntry; + LOsrEntry *lir = new(alloc()) LOsrEntry(temp()); defineFixed(lir, entry, LAllocation(AnyRegister(OsrFrameReg))); } @@ -3492,18 +3492,6 @@ LIRGenerator::visitCallInstanceOf(MCallInstanceOf *ins) assignSafepoint(lir, ins); } -void -LIRGenerator::visitProfilerStackOp(MProfilerStackOp *ins) -{ - LProfilerStackOp *lir = new(alloc()) LProfilerStackOp(temp()); - add(lir, ins); - - // If slow assertions are enabled, then this node will result in a callVM - // out to a C++ function for the assertions, so we will need a safepoint. - if (gen->options.spsSlowAssertionsEnabled()) - assignSafepoint(lir, ins); -} - void LIRGenerator::visitIsCallable(MIsCallable *ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 7c166bdb7fd..d53fd2a2dab 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -248,7 +248,6 @@ class LIRGenerator : public LIRGeneratorSpecific void visitInArray(MInArray *ins); void visitInstanceOf(MInstanceOf *ins); void visitCallInstanceOf(MCallInstanceOf *ins); - void visitProfilerStackOp(MProfilerStackOp *ins); void visitIsCallable(MIsCallable *ins); void visitIsObject(MIsObject *ins); void visitHasClass(MHasClass *ins); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 3f82ea68a37..3d726577aee 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -11449,48 +11449,6 @@ class MNewStringObject : StringObject *templateObj() const; }; -// Node that represents that a script has begun executing. This comes at the -// start of the function and is called once per function (including inline -// ones) -class MProfilerStackOp : public MNullaryInstruction -{ - public: - enum Type { - Enter, // a function has begun executing and it is not inline - Exit // any function has exited and is not inline - }; - - private: - JSScript *script_; - Type type_; - - MProfilerStackOp(JSScript *script, Type type) - : script_(script), type_(type) - { - MOZ_ASSERT(script); - setGuard(); - } - - public: - INSTRUCTION_HEADER(ProfilerStackOp) - - static MProfilerStackOp *New(TempAllocator &alloc, JSScript *script, Type type) { - return new(alloc) MProfilerStackOp(script, type); - } - - JSScript *script() { - return script_; - } - - Type type() { - return type_; - } - - AliasSet getAliasSet() const MOZ_OVERRIDE { - return AliasSet::None(); - } -}; - // This is an alias for MLoadFixedSlot. class MEnclosingScope : public MLoadFixedSlot { diff --git a/js/src/jit/MIRGenerator.h b/js/src/jit/MIRGenerator.h index 54c7df8794c..a5e5e724288 100644 --- a/js/src/jit/MIRGenerator.h +++ b/js/src/jit/MIRGenerator.h @@ -83,14 +83,8 @@ class MIRGenerator return instrumentedProfiling_; } - bool isNativeToBytecodeMapEnabled() { - if (compilingAsmJS()) - return false; -#ifdef DEBUG - return true; -#else - return instrumentedProfiling(); -#endif + bool isProfilerInstrumentationEnabled() { + return !compilingAsmJS() && instrumentedProfiling(); } // Whether the main thread is trying to cancel this build. diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index fd02ef48248..3cb83462d86 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -226,7 +226,6 @@ namespace jit { _(CallInstanceOf) \ _(InterruptCheck) \ _(AsmJSInterruptCheck) \ - _(ProfilerStackOp) \ _(GetDOMProperty) \ _(GetDOMMember) \ _(SetDOMProperty) \ diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index f17a5259e96..5301dda5a7d 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -1314,16 +1314,8 @@ MacroAssembler::handleFailure() { // Re-entry code is irrelevant because the exception will leave the // running function and never come back - if (sps_) - sps_->skipNextReenter(); - leaveSPSFrame(); - JitCode *excTail = GetJitContext()->runtime->jitRuntime()->getExceptionTail(); jump(excTail); - - // Doesn't actually emit code, but balances the leave() - if (sps_) - sps_->reenter(*this, InvalidReg); } #ifdef DEBUG @@ -1345,7 +1337,7 @@ MacroAssembler::assumeUnreachable(const char *output) setupUnalignedABICall(1, temp); movePtr(ImmPtr(output), temp); passABIArg(temp); - callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, AssumeUnreachable_)); + callWithABI(JS_FUNC_TO_DATA_PTR(void *, AssumeUnreachable_)); PopRegsInMask(RegisterSet::Volatile()); } @@ -1373,7 +1365,7 @@ MacroAssembler::printf(const char *output) setupUnalignedABICall(1, temp); movePtr(ImmPtr(output), temp); passABIArg(temp); - callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, Printf0_)); + callWithABI(JS_FUNC_TO_DATA_PTR(void *, Printf0_)); PopRegsInMask(RegisterSet::Volatile()); } @@ -1399,7 +1391,7 @@ MacroAssembler::printf(const char *output, Register value) movePtr(ImmPtr(output), temp); passABIArg(temp); passABIArg(value); - callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, Printf1_)); + callWithABI(JS_FUNC_TO_DATA_PTR(void *, Printf1_)); PopRegsInMask(RegisterSet::Volatile()); } @@ -1422,7 +1414,7 @@ MacroAssembler::tracelogStartId(Register logger, uint32_t textId, bool force) passABIArg(logger); move32(Imm32(textId), temp); passABIArg(temp); - callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogStartEventPrivate)); + callWithABI(JS_FUNC_TO_DATA_PTR(void *, TraceLogStartEventPrivate)); PopRegsInMask(RegisterSet::Volatile()); } @@ -1441,7 +1433,7 @@ MacroAssembler::tracelogStartId(Register logger, Register textId) setupUnalignedABICall(2, temp); passABIArg(logger); passABIArg(textId); - callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogStartEventPrivate)); + callWithABI(JS_FUNC_TO_DATA_PTR(void *, TraceLogStartEventPrivate)); PopRegsInMask(RegisterSet::Volatile()); } @@ -1462,7 +1454,7 @@ MacroAssembler::tracelogStartEvent(Register logger, Register event) setupUnalignedABICall(2, temp); passABIArg(logger); passABIArg(event); - callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogFunc)); + callWithABI(JS_FUNC_TO_DATA_PTR(void *, TraceLogFunc)); PopRegsInMask(RegisterSet::Volatile()); } @@ -1485,7 +1477,7 @@ MacroAssembler::tracelogStopId(Register logger, uint32_t textId, bool force) move32(Imm32(textId), temp); passABIArg(temp); - callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogStopEventPrivate)); + callWithABI(JS_FUNC_TO_DATA_PTR(void *, TraceLogStopEventPrivate)); PopRegsInMask(RegisterSet::Volatile()); } @@ -1504,7 +1496,7 @@ MacroAssembler::tracelogStopId(Register logger, Register textId) setupUnalignedABICall(2, temp); passABIArg(logger); passABIArg(textId); - callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogStopEventPrivate)); + callWithABI(JS_FUNC_TO_DATA_PTR(void *, TraceLogStopEventPrivate)); PopRegsInMask(RegisterSet::Volatile()); } @@ -1925,6 +1917,30 @@ MacroAssembler::finish() MacroAssemblerSpecific::finish(); } +void +MacroAssembler::link(JitCode *code) +{ + MOZ_ASSERT(!oom()); + // If this code can transition to C++ code and witness a GC, then we need to store + // the JitCode onto the stack in order to GC it correctly. exitCodePatch should + // be unset if the code never needed to push its JitCode*. + if (hasEnteredExitFrame()) { + exitCodePatch_.fixup(this); + PatchDataWithValueCheck(CodeLocationLabel(code, exitCodePatch_), + ImmPtr(code), + ImmPtr((void*)-1)); + } + + // Fix up the code pointers to be written for locations where profilerCallSite + // emitted moves of RIP to a register. + for (size_t i = 0; i < profilerCallSites_.length(); i++) { + CodeOffsetLabel offset = profilerCallSites_[i]; + offset.fixup(this); + CodeLocationLabel location(code, offset); + PatchDataWithValueCheck(location, ImmPtr(location.raw()), ImmPtr((void*)-1)); + } +} + void MacroAssembler::branchIfNotInterpretedConstructor(Register fun, Register scratch, Label *label) { @@ -1999,54 +2015,27 @@ MacroAssembler::branchEqualTypeIfNeeded(MIRType type, MDefinition *maybeDef, Reg } } - -// If a pseudostack frame has this as its label, its stack pointer -// field points to the registers saved on entry to JIT code. A native -// stack unwinder could use that information to continue unwinding -// past that point. -const char MacroAssembler::enterJitLabel[] = "EnterJIT"; - -// Creates an enterJIT pseudostack frame, as described above. Pushes -// a word to the stack to indicate whether this was done. |framePtr| is -// the pointer to the machine-dependent saved state. void -MacroAssembler::spsMarkJit(SPSProfiler *p, Register framePtr, Register temp) +MacroAssembler::profilerPreCallImpl() { - Label spsNotEnabled; - uint32_t *enabledAddr = p->addressOfEnabled(); - load32(AbsoluteAddress(enabledAddr), temp); - push(temp); // +4: Did we push an sps frame. - branchTest32(Assembler::Equal, temp, temp, &spsNotEnabled); - - Label stackFull; - // We always need the "safe" versions, because these are used in trampolines - // and won't be regenerated when SPS state changes. - spsProfileEntryAddressSafe(p, 0, temp, &stackFull); - - // Push a C++ frame with non-copy label - storePtr(ImmPtr(enterJitLabel), Address(temp, ProfileEntry::offsetOfLabel())); - storePtr(framePtr, Address(temp, ProfileEntry::offsetOfSpOrScript())); - store32(Imm32(0), Address(temp, ProfileEntry::offsetOfLineOrPc())); - store32(Imm32(ProfileEntry::IS_CPP_ENTRY), Address(temp, ProfileEntry::offsetOfFlags())); - - /* Always increment the stack size, whether or not we actually pushed. */ - bind(&stackFull); - loadPtr(AbsoluteAddress(p->addressOfSizePointer()), temp); - add32(Imm32(1), Address(temp, 0)); - - bind(&spsNotEnabled); + Register reg = CallTempReg0; + Register reg2 = CallTempReg1; + push(reg); + push(reg2); + profilerPreCallImpl(reg, reg2); + pop(reg2); + pop(reg); } -// Pops the word pushed by spsMarkJit and, if spsMarkJit pushed an SPS -// frame, pops it. void -MacroAssembler::spsUnmarkJit(SPSProfiler *p, Register temp) +MacroAssembler::profilerPreCallImpl(Register reg, Register reg2) { - Label spsNotEnabled; - pop(temp); // -4: Was the profiler enabled. - branchTest32(Assembler::Equal, temp, temp, &spsNotEnabled); + JitContext *icx = GetJitContext(); + AbsoluteAddress profilingActivation(icx->runtime->addressOfProfilingActivation()); - spsPopFrameSafe(p, temp); + CodeOffsetLabel label = movWithPatch(ImmWord(uintptr_t(-1)), reg); + loadPtr(profilingActivation, reg2); + storePtr(reg, Address(reg2, JitActivation::offsetOfLastProfilingCallSite())); - bind(&spsNotEnabled); + appendProfilerCallSite(label); } diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h index 1459c3f4549..782b217a876 100644 --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -185,27 +185,18 @@ class MacroAssembler : public MacroAssemblerSpecific mozilla::Maybe jitContext_; mozilla::Maybe alloc_; - // SPS instrumentation, only used for Ion caches. - mozilla::Maybe spsInstrumentation_; - jsbytecode *spsPc_; - private: // This field is used to manage profiling instrumentation output. If // provided and enabled, then instrumentation will be emitted around call - // sites. The IonInstrumentation instance is hosted inside of - // CodeGeneratorShared and is the manager of when instrumentation is - // actually emitted or not. If nullptr, then no instrumentation is emitted. - IonInstrumentation *sps_; + // sites. + bool emitProfilingInstrumentation_; // Labels for handling exceptions and failures. NonAssertingLabel failureLabel_; public: - // If instrumentation should be emitted, then the sps parameter should be - // provided, but otherwise it can be safely omitted to prevent all - // instrumentation from being emitted. MacroAssembler() - : sps_(nullptr) + : emitProfilingInstrumentation_(false) { JitContext *jcx = GetJitContext(); JSContext *cx = jcx->cx; @@ -228,7 +219,7 @@ class MacroAssembler : public MacroAssemblerSpecific // (for example, Trampoline-$(ARCH).cpp and IonCaches.cpp). explicit MacroAssembler(JSContext *cx, IonScript *ion = nullptr, JSScript *script = nullptr, jsbytecode *pc = nullptr) - : sps_(nullptr) + : emitProfilingInstrumentation_(false) { constructRoot(cx); jitContext_.emplace(cx, (js::jit::TempAllocator *)nullptr); @@ -240,21 +231,15 @@ class MacroAssembler : public MacroAssemblerSpecific #endif if (ion) { setFramePushed(ion->frameSize()); - if (pc && cx->runtime()->spsProfiler.enabled()) { - // We have to update the SPS pc when this IC stub calls into - // the VM. - spsPc_ = pc; - spsInstrumentation_.emplace(&cx->runtime()->spsProfiler, &spsPc_); - sps_ = spsInstrumentation_.ptr(); - sps_->setPushed(script); - } + if (pc && cx->runtime()->spsProfiler.enabled()) + emitProfilingInstrumentation_ = true; } } // asm.js compilation handles its own JitContext-pushing struct AsmJSToken {}; explicit MacroAssembler(AsmJSToken) - : sps_(nullptr) + : emitProfilingInstrumentation_(false) { #ifdef JS_CODEGEN_ARM initWithAllocator(); @@ -262,8 +247,8 @@ class MacroAssembler : public MacroAssemblerSpecific #endif } - void setInstrumentation(IonInstrumentation *sps) { - sps_ = sps; + void enableProfilingInstrumentation() { + emitProfilingInstrumentation_ = true; } void resetForNewCodeGenerator(TempAllocator &alloc) { @@ -864,20 +849,6 @@ class MacroAssembler : public MacroAssemblerSpecific return exitCodePatch_.offset() != 0; } - void link(JitCode *code) { - MOZ_ASSERT(!oom()); - // If this code can transition to C++ code and witness a GC, then we need to store - // the JitCode onto the stack in order to GC it correctly. exitCodePatch should - // be unset if the code never needed to push its JitCode*. - if (hasEnteredExitFrame()) { - exitCodePatch_.fixup(this); - PatchDataWithValueCheck(CodeLocationLabel(code, exitCodePatch_), - ImmPtr(code), - ImmPtr((void*)-1)); - } - - } - // Generates code used to complete a bailout. void generateBailoutTail(Register scratch, Register bailoutInfo); @@ -888,51 +859,46 @@ class MacroAssembler : public MacroAssemblerSpecific // they are returning the offset of the assembler just after the call has // been made so that a safepoint can be made at that location. - template - void callWithABINoProfiling(const T &fun, MoveOp::Type result = MoveOp::GENERAL) { - MacroAssemblerSpecific::callWithABI(fun, result); - } - template void callWithABI(const T &fun, MoveOp::Type result = MoveOp::GENERAL) { - leaveSPSFrame(); - callWithABINoProfiling(fun, result); - reenterSPSFrame(); + profilerPreCall(); + MacroAssemblerSpecific::callWithABI(fun, result); + profilerPostReturn(); } // see above comment for what is returned uint32_t callJit(Register callee) { - leaveSPSFrame(); + profilerPreCall(); MacroAssemblerSpecific::callJit(callee); uint32_t ret = currentOffset(); - reenterSPSFrame(); + profilerPostReturn(); return ret; } // see above comment for what is returned uint32_t callWithExitFrame(Label *target) { - leaveSPSFrame(); + profilerPreCall(); MacroAssemblerSpecific::callWithExitFrame(target); uint32_t ret = currentOffset(); - reenterSPSFrame(); + profilerPostReturn(); return ret; } // see above comment for what is returned uint32_t callWithExitFrame(JitCode *target) { - leaveSPSFrame(); + profilerPreCall(); MacroAssemblerSpecific::callWithExitFrame(target); uint32_t ret = currentOffset(); - reenterSPSFrame(); + profilerPostReturn(); return ret; } // see above comment for what is returned uint32_t callWithExitFrame(JitCode *target, Register dynStack) { - leaveSPSFrame(); + profilerPreCall(); MacroAssemblerSpecific::callWithExitFrame(target, dynStack); uint32_t ret = currentOffset(); - reenterSPSFrame(); + profilerPostReturn(); return ret; } @@ -968,165 +934,19 @@ class MacroAssembler : public MacroAssemblerSpecific // These two functions are helpers used around call sites throughout the // assembler. They are called from the above call wrappers to emit the // necessary instrumentation. - void leaveSPSFrame() { - if (!sps_ || !sps_->enabled()) + void profilerPreCall() { + if (!emitProfilingInstrumentation_) return; - // No registers are guaranteed to be available, so push/pop a register - // so we can use one - push(CallTempReg0); - sps_->leave(*this, CallTempReg0); - pop(CallTempReg0); + profilerPreCallImpl(); } - void reenterSPSFrame() { - if (!sps_ || !sps_->enabled()) + void profilerPostReturn() { + if (!emitProfilingInstrumentation_) return; - // Attempt to use a now-free register within a given set, but if the - // architecture being built doesn't have an available register, resort - // to push/pop - GeneralRegisterSet regs(Registers::TempMask & ~Registers::JSCallMask & - ~Registers::CallMask); - if (regs.empty()) { - push(CallTempReg0); - sps_->reenter(*this, CallTempReg0); - pop(CallTempReg0); - } else { - sps_->reenter(*this, regs.getAny()); - } - } - - void spsProfileEntryAddress(SPSProfiler *p, int offset, Register temp, - Label *full) - { - movePtr(ImmPtr(p->sizePointer()), temp); - load32(Address(temp, 0), temp); - if (offset != 0) - add32(Imm32(offset), temp); - branch32(Assembler::GreaterThanOrEqual, temp, Imm32(p->maxSize()), full); - - JS_STATIC_ASSERT(sizeof(ProfileEntry) == (2 * sizeof(void *)) + 8); - if (sizeof(void *) == 4) { - lshiftPtr(Imm32(4), temp); - } else { - lshiftPtr(Imm32(3), temp); - mulBy3(temp, temp); - } - - addPtr(ImmPtr(p->stack()), temp); - } - - // The safe version of the above method refrains from assuming that the fields - // of the SPSProfiler class are going to stay the same across different runs of - // the jitcode. Ion can use the more efficient unsafe version because ion jitcode - // will not survive changes to to the profiler settings. Baseline jitcode, however, - // can span these changes, so any hardcoded field values will be incorrect afterwards. - // All the sps-related methods used by baseline call |spsProfileEntryAddressSafe|. - void spsProfileEntryAddressSafe(SPSProfiler *p, int offset, Register temp, - Label *full) - { - // Load size pointer - loadPtr(AbsoluteAddress(p->addressOfSizePointer()), temp); - - // Load size - load32(Address(temp, 0), temp); - if (offset != 0) - add32(Imm32(offset), temp); - - // Test against max size. - branch32(Assembler::LessThanOrEqual, AbsoluteAddress(p->addressOfMaxSize()), temp, full); - - JS_STATIC_ASSERT(sizeof(ProfileEntry) == (2 * sizeof(void *)) + 8); - if (sizeof(void *) == 4) { - lshiftPtr(Imm32(4), temp); - } else { - lshiftPtr(Imm32(3), temp); - mulBy3(temp, temp); - } - - push(temp); - loadPtr(AbsoluteAddress(p->addressOfStack()), temp); - addPtr(Address(StackPointer, 0), temp); - addPtr(Imm32(sizeof(size_t)), StackPointer); + profilerPostReturnImpl(); } public: - // These functions are needed by the IonInstrumentation interface defined in - // vm/SPSProfiler.h. They will modify the pseudostack provided to SPS to - // perform the actual instrumentation. - - void spsUpdatePCIdx(SPSProfiler *p, int32_t idx, Register temp) { - Label stackFull; - spsProfileEntryAddress(p, -1, temp, &stackFull); - store32(Imm32(idx), Address(temp, ProfileEntry::offsetOfLineOrPc())); - bind(&stackFull); - } - - void spsUpdatePCIdx(SPSProfiler *p, Register idx, Register temp) { - Label stackFull; - spsProfileEntryAddressSafe(p, -1, temp, &stackFull); - store32(idx, Address(temp, ProfileEntry::offsetOfLineOrPc())); - bind(&stackFull); - } - - // spsPushFrame variant for Ion-optimized scripts. - void spsPushFrame(SPSProfiler *p, const char *str, JSScript *s, Register temp) { - Label stackFull; - spsProfileEntryAddress(p, 0, temp, &stackFull); - - // Push a JS frame with a copy label - storePtr(ImmPtr(str), Address(temp, ProfileEntry::offsetOfLabel())); - storePtr(ImmGCPtr(s), Address(temp, ProfileEntry::offsetOfSpOrScript())); - store32(Imm32(ProfileEntry::NullPCOffset), Address(temp, ProfileEntry::offsetOfLineOrPc())); - store32(Imm32(ProfileEntry::FRAME_LABEL_COPY), Address(temp, ProfileEntry::offsetOfFlags())); - - /* Always increment the stack size, whether or not we actually pushed. */ - bind(&stackFull); - movePtr(ImmPtr(p->sizePointer()), temp); - add32(Imm32(1), Address(temp, 0)); - } - - // spsPushFrame variant for Baseline-optimized scripts. - void spsPushFrame(SPSProfiler *p, const Address &str, const Address &script, - Register temp, Register temp2) - { - Label stackFull; - spsProfileEntryAddressSafe(p, 0, temp, &stackFull); - - // Push a JS frame with a copy label - loadPtr(str, temp2); - storePtr(temp2, Address(temp, ProfileEntry::offsetOfLabel())); - - loadPtr(script, temp2); - storePtr(temp2, Address(temp, ProfileEntry::offsetOfSpOrScript())); - - // Store 0 for PCIdx because that's what interpreter does. - // (See probes::EnterScript, which calls spsProfiler.enter, which pushes an entry - // with 0 pcIdx). - store32(Imm32(0), Address(temp, ProfileEntry::offsetOfLineOrPc())); - store32(Imm32(ProfileEntry::FRAME_LABEL_COPY), Address(temp, ProfileEntry::offsetOfFlags())); - - /* Always increment the stack size, whether or not we actually pushed. */ - bind(&stackFull); - movePtr(ImmPtr(p->addressOfSizePointer()), temp); - loadPtr(Address(temp, 0), temp); - add32(Imm32(1), Address(temp, 0)); - } - - void spsPopFrame(SPSProfiler *p, Register temp) { - movePtr(ImmPtr(p->sizePointer()), temp); - add32(Imm32(-1), Address(temp, 0)); - } - - // spsPropFrameSafe does not assume |profiler->sizePointer()| will stay constant. - void spsPopFrameSafe(SPSProfiler *p, Register temp) { - loadPtr(AbsoluteAddress(p->addressOfSizePointer()), temp); - add32(Imm32(-1), Address(temp, 0)); - } - - static const char enterJitLabel[]; - void spsMarkJit(SPSProfiler *p, Register framePtr, Register temp); - void spsUnmarkJit(SPSProfiler *p, Register temp); - void loadBaselineOrIonRaw(Register script, Register dest, Label *failure); void loadBaselineOrIonNoArgCheck(Register callee, Register dest, Label *failure); @@ -1151,6 +971,7 @@ class MacroAssembler : public MacroAssemblerSpecific } void finish(); + void link(JitCode *code); void assumeUnreachable(const char *output); void printf(const char *output); @@ -1422,6 +1243,10 @@ class MacroAssembler : public MacroAssemblerSpecific bind(&ok); #endif } + + void profilerPreCallImpl(); + void profilerPreCallImpl(Register reg, Register reg2); + void profilerPostReturnImpl() {} }; static inline Assembler::DoubleCondition diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 5884d1defc6..8b59c09bef5 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -576,19 +576,6 @@ NewStringObject(JSContext *cx, HandleString str) return StringObject::create(cx, str); } -bool -SPSEnter(JSContext *cx, HandleScript script) -{ - return cx->runtime()->spsProfiler.enter(script, script->functionNonDelazifying()); -} - -bool -SPSExit(JSContext *cx, HandleScript script) -{ - cx->runtime()->spsProfiler.exit(script, script->functionNonDelazifying()); - return true; -} - bool OperatorIn(JSContext *cx, HandleValue key, HandleObject obj, bool *out) { @@ -803,15 +790,6 @@ DebugEpilogue(JSContext *cx, BaselineFrame *frame, jsbytecode *pc, bool ok) DebugScopes::onPopStrictEvalScope(frame); } - // If the frame has a pushed SPS frame, make sure to pop it. - if (frame->hasPushedSPSFrame()) { - cx->runtime()->spsProfiler.exit(frame->script(), frame->maybeFun()); - // Unset the pushedSPSFrame flag because DebugEpilogue may get called before - // probes::ExitScript in baseline during exception handling, and we don't - // want to double-pop SPS frames. - frame->unsetPushedSPSFrame(); - } - if (!ok) { // Pop this frame by updating jitTop, so that the exception handling // code will start at the previous frame. diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 7252bff7344..8f0164d47d9 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -679,9 +679,6 @@ JSObject *NewCallObject(JSContext *cx, HandleShape shape, HandleTypeObject type, JSObject *NewSingletonCallObject(JSContext *cx, HandleShape shape, uint32_t lexicalBegin); JSObject *NewStringObject(JSContext *cx, HandleString str); -bool SPSEnter(JSContext *cx, HandleScript script); -bool SPSExit(JSContext *cx, HandleScript script); - bool OperatorIn(JSContext *cx, HandleValue key, HandleObject obj, bool *out); bool OperatorInI(JSContext *cx, uint32_t index, HandleObject obj, bool *out); diff --git a/js/src/jit/arm/CodeGenerator-arm.cpp b/js/src/jit/arm/CodeGenerator-arm.cpp index 496998fddb8..b1aa8046890 100644 --- a/js/src/jit/arm/CodeGenerator-arm.cpp +++ b/js/src/jit/arm/CodeGenerator-arm.cpp @@ -47,6 +47,11 @@ CodeGeneratorARM::generatePrologue() #ifdef JS_USE_LINK_REGISTER masm.pushReturnAddress(); #endif + + // If profiling, save the current frame pointer to a per-thread global field. + if (isProfilerInstrumentationEnabled()) + masm.profilerEnterFrame(StackPointer, CallTempReg0); + // Note that this automatically sets MacroAssembler::framePushed(). masm.reserveStack(frameSize()); masm.checkStackAlignment(); @@ -66,6 +71,12 @@ CodeGeneratorARM::generateEpilogue() masm.freeStack(frameSize()); MOZ_ASSERT(masm.framePushed() == 0); + + // If profiling, reset the per-thread global lastJitFrame to point to + // the previous frame. + if (isProfilerInstrumentationEnabled()) + masm.profilerExitFrame(); + masm.pop(pc); masm.flushBuffer(); return true; diff --git a/js/src/jit/arm/MacroAssembler-arm.cpp b/js/src/jit/arm/MacroAssembler-arm.cpp index f8ac9ec7bf2..0a6c42ad568 100644 --- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -4294,6 +4294,18 @@ MacroAssemblerARMCompat::handleFailureWithHandlerTail(void *handler) loadValue(Address(r11, BaselineFrame::reverseOffsetOfReturnValue()), JSReturnOperand); ma_mov(r11, sp); pop(r11); + + // If profiling is enabled, then update the lastProfilingFrame to refer to caller + // frame before returning. + { + Label skipProfilingInstrumentation; + // Test if profiler enabled. + AbsoluteAddress addressOfEnabled(GetJitContext()->runtime->spsProfiler().addressOfEnabled()); + branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skipProfilingInstrumentation); + profilerExitFrame(); + bind(&skipProfilingInstrumentation); + } + ret(); // If we are bailing out to baseline to handle an exception, jump to the @@ -5022,3 +5034,18 @@ template void js::jit::MacroAssemblerARMCompat::atomicFetchOp(int nbytes, bool signExtend, AtomicOp op, const Register &value, const BaseIndex &mem, Register temp, Register output); + +void +MacroAssemblerARMCompat::profilerEnterFrame(Register framePtr, Register scratch) +{ + AbsoluteAddress activation(GetJitContext()->runtime->addressOfProfilingActivation()); + loadPtr(activation, scratch); + storePtr(framePtr, Address(scratch, JitActivation::offsetOfLastProfilingFrame())); + storePtr(ImmPtr(nullptr), Address(scratch, JitActivation::offsetOfLastProfilingCallSite())); +} + +void +MacroAssemblerARMCompat::profilerExitFrame() +{ + branch(GetJitContext()->runtime->jitRuntime()->getProfilerExitFrameTail()); +} diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index d9bf342e3b8..1ee51fcf3ae 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -1838,6 +1838,10 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM void pushReturnAddress() { push(lr); } + + // Instrumentation for entering and leaving the profiler. + void profilerEnterFrame(Register framePtr, Register scratch); + void profilerExitFrame(); }; typedef MacroAssemblerARMCompat MacroAssemblerSpecific; diff --git a/js/src/jit/arm/Trampoline-arm.cpp b/js/src/jit/arm/Trampoline-arm.cpp index 4ecf5b8137e..c2394fc8106 100644 --- a/js/src/jit/arm/Trampoline-arm.cpp +++ b/js/src/jit/arm/Trampoline-arm.cpp @@ -36,8 +36,8 @@ GenerateReturn(MacroAssembler &masm, int returnCode, SPSProfiler *prof) // Restore non-volatile floating point registers. masm.transferMultipleByRuns(NonVolatileFloatRegs, IsLoad, StackPointer, IA); - // Unwind the sps mark. - masm.spsUnmarkJit(prof, r8); + // Get rid of padding word. + masm.addPtr(Imm32(sizeof(void*)), sp); // Set up return value masm.ma_mov(Imm32(returnCode), r0); @@ -69,7 +69,8 @@ struct EnterJITStack double d14; double d15; - size_t hasSPSMark; + // Padding. + void *padding; // Non-volatile registers. void *r4; @@ -129,9 +130,8 @@ JitRuntime::generateEnterJIT(JSContext *cx, EnterJitType type) // The 5th argument is located at [sp, 36] masm.finishDataTransfer(); - // Push the EnterJIT sps mark. "Frame pointer" = start of saved core regs. - masm.movePtr(sp, r8); - masm.spsMarkJit(&cx->runtime()->spsProfiler, r8, r9); + // Add padding word. + masm.subPtr(Imm32(sizeof(void*)), sp); // Push the float registers. masm.transferMultipleByRuns(NonVolatileFloatRegs, IsStore, sp, DB); @@ -283,6 +283,19 @@ JitRuntime::generateEnterJIT(JSContext *cx, EnterJitType type) masm.addPtr(Imm32(BaselineFrame::Size()), framePtr); masm.branchIfFalseBool(ReturnReg, &error); + // If OSR-ing, then emit instrumentation for setting lastProfilerFrame + // if profiler instrumentation is enabled. + { + Label skipProfilingInstrumentation; + Register realFramePtr = numStackValues; + AbsoluteAddress addressOfEnabled(cx->runtime()->spsProfiler.addressOfEnabled()); + masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), + &skipProfilingInstrumentation); + masm.ma_add(framePtr, Imm32(sizeof(void*)), realFramePtr); + masm.profilerEnterFrame(realFramePtr, scratch); + masm.bind(&skipProfilingInstrumentation); + } + masm.jump(jitcode); // OOM: Load error value, discard return address and previous frame @@ -949,6 +962,17 @@ JitRuntime::generateDebugTrapHandler(JSContext *cx) JSReturnOperand); masm.mov(r11, sp); masm.pop(r11); + + // Before returning, if profiling is turned on, make sure that lastProfilingFrame + // is set to the correct caller frame. + { + Label skipProfilingInstrumentation; + AbsoluteAddress addressOfEnabled(cx->runtime()->spsProfiler.addressOfEnabled()); + masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skipProfilingInstrumentation); + masm.profilerExitFrame(); + masm.bind(&skipProfilingInstrumentation); + } + masm.ret(); Linker linker(masm); @@ -997,3 +1021,294 @@ JitRuntime::generateBailoutTailStub(JSContext *cx) return code; } + +JitCode * +JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) +{ + MacroAssembler masm; + + Register scratch1 = r5; + Register scratch2 = r6; + Register scratch3 = r7; + Register scratch4 = r8; + + // + // The code generated below expects that the current stack pointer points + // to an Ion or Baseline frame, at the state it would be immediately + // before a ret(). Thus, after this stub's business is done, it executes + // a ret() and returns directly to the caller script, on behalf of the + // callee script that jumped to this code. + // + // Thus the expected stack is: + // + // StackPointer ----+ + // v + // ..., ActualArgc, CalleeToken, Descriptor, ReturnAddr + // MEM-HI MEM-LOW + // + // + // The generated jitcode is responsible for overwriting the + // jitActivation->lastProfilingFrame field with a pointer to the previous + // Ion or Baseline jit-frame that was pushed before this one. It is also + // responsible for overwriting jitActivation->lastProfilingCallSite with + // the return address into that frame. The frame could either be an + // immediate "caller" frame, or it could be a frame in a previous + // JitActivation (if the current frame was entered from C++, and the C++ + // was entered by some caller jit-frame further down the stack). + // + // So this jitcode is responsible for "walking up" the jit stack, finding + // the previous Ion or Baseline JS frame, and storing its address and the + // return address into the appropriate fields on the current jitActivation. + // + // There are a fixed number of different path types that can lead to the + // current frame, which is either a baseline or ion frame: + // + // + // ^ + // | + // ^--- Ion + // | + // ^--- Baseline Stub <---- Baseline + // | + // ^--- Argument Rectifier + // | ^ + // | | + // | ^--- Ion + // | | + // | ^--- Baseline Stub <---- Baseline + // | + // ^--- Entry Frame (From C++) + // + Register actReg = scratch4; + AbsoluteAddress activationAddr(GetJitContext()->runtime->addressOfProfilingActivation()); + masm.loadPtr(activationAddr, actReg); + + Address lastProfilingFrame(actReg, JitActivation::offsetOfLastProfilingFrame()); + Address lastProfilingCallSite(actReg, JitActivation::offsetOfLastProfilingCallSite()); + +#ifdef DEBUG + // Ensure that frame we are exiting is current lastProfilingFrame + { + masm.loadPtr(lastProfilingFrame, scratch1); + Label checkOk; + masm.branchPtr(Assembler::Equal, scratch1, ImmWord(0), &checkOk); + masm.branchPtr(Assembler::Equal, StackPointer, scratch1, &checkOk); + masm.assumeUnreachable( + "Mismatch between stored lastProfilingFrame and current stack pointer."); + masm.bind(&checkOk); + } +#endif + + // Load the frame descriptor into |scratch1|, figure out what to do depending on its type. + masm.loadPtr(Address(StackPointer, JitFrameLayout::offsetOfDescriptor()), scratch1); + + // Going into the conditionals, we will have: + // FrameDescriptor.size in scratch1 + // FrameDescriptor.type in scratch2 + masm.ma_and(Imm32((1 << FRAMESIZE_SHIFT) - 1), scratch1, scratch2); + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), scratch1); + + // Handling of each case is dependent on FrameDescriptor.type + Label handle_IonJS; + Label handle_BaselineStub; + Label handle_Rectifier; + Label handle_Entry; + Label end; + + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonJS), &handle_IonJS); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry); + + masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame."); + + // + // JitFrame_IonJS + // + // Stack layout: + // ... + // Ion-Descriptor + // Prev-FP ---> Ion-ReturnAddr + // ... previous frame data ... |- Descriptor.Size + // ... arguments ... | + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + masm.bind(&handle_IonJS); + { + // |scratch1| contains Descriptor.size + + // returning directly to an IonJS frame. Store return addr to frame + // in lastProfilingCallSite. + masm.loadPtr(Address(StackPointer, JitFrameLayout::offsetOfReturnAddress()), scratch2); + masm.storePtr(scratch2, lastProfilingCallSite); + + // Store return frame in lastProfilingFrame. + // scratch2 := StackPointer + Descriptor.size*1 + JitFrameLayout::Size(); + masm.ma_add(StackPointer, scratch1, scratch2); + masm.ma_add(scratch2, Imm32(JitFrameLayout::Size()), scratch2); + masm.storePtr(scratch2, lastProfilingFrame); + masm.ret(); + } + + // + // JitFrame_BaselineStub + // + // Look past the stub and store the frame pointer to + // the baselineJS frame prior to it. + // + // Stack layout: + // ... + // BL-Descriptor + // Prev-FP ---> BL-ReturnAddr + // +-----> BL-PrevFramePointer + // | ... BL-FrameData ... + // | BLStub-Descriptor + // | BLStub-ReturnAddr + // | BLStub-StubPointer | + // +------ BLStub-SavedFramePointer |- Descriptor.Size + // ... arguments ... | + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + // We take advantage of the fact that the stub frame saves the frame + // pointer pointing to the baseline frame, so a bunch of calculation can + // be avoided. + // + masm.bind(&handle_BaselineStub); + { + masm.ma_add(StackPointer, scratch1, scratch3); + Address stubFrameReturnAddr(scratch3, + JitFrameLayout::Size() + + BaselineStubFrameLayout::offsetOfReturnAddress()); + masm.loadPtr(stubFrameReturnAddr, scratch2); + masm.storePtr(scratch2, lastProfilingCallSite); + + Address stubFrameSavedFramePtr(scratch3, + JitFrameLayout::Size() - (2 * sizeof(void *))); + masm.loadPtr(stubFrameSavedFramePtr, scratch2); + masm.addPtr(Imm32(sizeof(void *)), scratch2); // Skip past BL-PrevFramePtr + masm.storePtr(scratch2, lastProfilingFrame); + masm.ret(); + } + + + // + // JitFrame_Rectifier + // + // The rectifier frame can be preceded by either an IonJS or a + // BaselineStub frame. + // + // Stack layout if caller of rectifier was Ion: + // + // Ion-Descriptor + // Ion-ReturnAddr + // ... ion frame data ... |- Rect-Descriptor.Size + // < COMMON LAYOUT > + // + // Stack layout if caller of rectifier was Baseline: + // + // BL-Descriptor + // Prev-FP ---> BL-ReturnAddr + // +-----> BL-SavedFramePointer + // | ... baseline frame data ... + // | BLStub-Descriptor + // | BLStub-ReturnAddr + // | BLStub-StubPointer | + // +------ BLStub-SavedFramePointer |- Rect-Descriptor.Size + // ... args to rectifier ... | + // < COMMON LAYOUT > + // + // Common stack layout: + // + // ActualArgc | + // CalleeToken |- IonRectitiferFrameLayout::Size() + // Rect-Descriptor | + // Rect-ReturnAddr | + // ... rectifier data & args ... |- Descriptor.Size + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + masm.bind(&handle_Rectifier); + { + // scratch2 := StackPointer + Descriptor.size*1 + JitFrameLayout::Size(); + masm.ma_add(StackPointer, scratch1, scratch2); + masm.add32(Imm32(JitFrameLayout::Size()), scratch2); + masm.loadPtr(Address(scratch2, RectifierFrameLayout::offsetOfDescriptor()), scratch3); + masm.ma_lsr(Imm32(FRAMESIZE_SHIFT), scratch3, scratch1); + masm.and32(Imm32((1 << FRAMETYPE_BITS) - 1), scratch3); + + // Now |scratch1| contains Rect-Descriptor.Size + // and |scratch2| points to Rectifier frame + // and |scratch3| contains Rect-Descriptor.Type + + // Check for either Ion or BaselineStub frame. + Label handle_Rectifier_BaselineStub; + masm.branch32(Assembler::NotEqual, scratch3, Imm32(JitFrame_IonJS), + &handle_Rectifier_BaselineStub); + + // Handle Rectifier <- IonJS + // scratch3 := RectFrame[ReturnAddr] + masm.loadPtr(Address(scratch2, RectifierFrameLayout::offsetOfReturnAddress()), scratch3); + masm.storePtr(scratch3, lastProfilingCallSite); + + // scratch3 := RectFrame + Rect-Descriptor.Size + RectifierFrameLayout::Size() + masm.ma_add(scratch2, scratch1, scratch3); + masm.add32(Imm32(RectifierFrameLayout::Size()), scratch3); + masm.storePtr(scratch3, lastProfilingFrame); + masm.ret(); + + // Handle Rectifier <- BaselineStub <- BaselineJS + masm.bind(&handle_Rectifier_BaselineStub); +#ifdef DEBUG + { + Label checkOk; + masm.branch32(Assembler::Equal, scratch3, Imm32(JitFrame_BaselineStub), &checkOk); + masm.assumeUnreachable("Unrecognized frame preceding baselineStub."); + masm.bind(&checkOk); + } +#endif + masm.ma_add(scratch2, scratch1, scratch3); + Address stubFrameReturnAddr(scratch3, RectifierFrameLayout::Size() + + BaselineStubFrameLayout::offsetOfReturnAddress()); + masm.loadPtr(stubFrameReturnAddr, scratch2); + masm.storePtr(scratch2, lastProfilingCallSite); + + Address stubFrameSavedFramePtr(scratch3, + RectifierFrameLayout::Size() - (2 * sizeof(void *))); + masm.loadPtr(stubFrameSavedFramePtr, scratch2); + masm.addPtr(Imm32(sizeof(void *)), scratch2); + masm.storePtr(scratch2, lastProfilingFrame); + masm.ret(); + } + + // + // JitFrame_Entry + // + // If at an entry frame, store null into both fields. + // + masm.bind(&handle_Entry); + { + masm.movePtr(ImmPtr(nullptr), scratch1); + masm.storePtr(scratch1, lastProfilingCallSite); + masm.storePtr(scratch1, lastProfilingFrame); + masm.ret(); + } + + Linker linker(masm); + AutoFlushICache afc("ProfilerExitFrameTailStub"); + JitCode *code = linker.newCode(cx, OTHER_CODE); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "ProfilerExitFrameStub"); +#endif + + return code; +} diff --git a/js/src/jit/mips/CodeGenerator-mips.cpp b/js/src/jit/mips/CodeGenerator-mips.cpp index 7ac3c91a3dd..fd3e32b873a 100644 --- a/js/src/jit/mips/CodeGenerator-mips.cpp +++ b/js/src/jit/mips/CodeGenerator-mips.cpp @@ -45,6 +45,10 @@ CodeGeneratorMIPS::generatePrologue() MOZ_ASSERT(masm.framePushed() == 0); MOZ_ASSERT(!gen->compilingAsmJS()); + // If profiling, save the current frame pointer to a per-thread global field. + if (isProfilerInstrumentationEnabled()) + masm.profilerEnterFrame(StackPointer, CallTempReg0); + // Note that this automatically sets MacroAssembler::framePushed(). masm.reserveStack(frameSize()); masm.checkStackAlignment(); @@ -64,6 +68,12 @@ CodeGeneratorMIPS::generateEpilogue() masm.freeStack(frameSize()); MOZ_ASSERT(masm.framePushed() == 0); + + // If profiling, reset the per-thread global lastJitFrame to point to + // the previous frame. + if (isProfilerInstrumentationEnabled()) + masm.profilerExitFrame(); + masm.ret(); return true; } diff --git a/js/src/jit/mips/MacroAssembler-mips.cpp b/js/src/jit/mips/MacroAssembler-mips.cpp index bdf8b7cdbba..81d880619dd 100644 --- a/js/src/jit/mips/MacroAssembler-mips.cpp +++ b/js/src/jit/mips/MacroAssembler-mips.cpp @@ -3604,6 +3604,18 @@ MacroAssemblerMIPSCompat::handleFailureWithHandlerTail(void *handler) JSReturnOperand); ma_move(StackPointer, BaselineFrameReg); pop(BaselineFrameReg); + + // If profiling is enabled, then update the lastProfilingFrame to refer to caller + // frame before returning. + { + Label skipProfilingInstrumentation; + // Test if profiler enabled. + AbsoluteAddress addressOfEnabled(GetJitContext()->runtime->spsProfiler().addressOfEnabled()); + branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skipProfilingInstrumentation); + profilerExitFrame(); + bind(&skipProfilingInstrumentation); + } + ret(); // If we are bailing out to baseline to handle an exception, jump to @@ -3669,3 +3681,18 @@ MacroAssemblerMIPSCompat::branchValueIsNurseryObject(Condition cond, ValueOperan bind(&done); } + +void +MacroAssemblerMIPSCompat::profilerEnterFrame(Register reg) +{ + AbsoluteAddress activation(GetJitContext()->runtime->addressOfProfilingActivation()); + loadPtr(activation, scratch); + storePtr(framePtr, Address(scratch, JitActivation::offsetOfLastProfilingFrame())); + storePtr(ImmPtr(nullptr), Address(scratch, JitActivation::offsetOfLastProfilingCallSite())); +} + +void +MacroAssemblerMIPSCompat::profilerExitFrame() +{ + branch(GetJitContext()->runtime->jitRuntime()->getProfilerExitFrameTail()); +} diff --git a/js/src/jit/mips/MacroAssembler-mips.h b/js/src/jit/mips/MacroAssembler-mips.h index ce9e30a3ced..8cdd56ae0d6 100644 --- a/js/src/jit/mips/MacroAssembler-mips.h +++ b/js/src/jit/mips/MacroAssembler-mips.h @@ -1470,6 +1470,10 @@ public: MOZ_ASSERT(Imm16::IsInSignedRange(AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias)); loadPtr(Address(GlobalReg, AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias), HeapReg); } + + // Instrumentation for entering and leaving the profiler. + void profilerEnterFrame(Register framePtr, Register scratch); + void profilerExitFrame(); }; typedef MacroAssemblerMIPSCompat MacroAssemblerSpecific; diff --git a/js/src/jit/mips/Trampoline-mips.cpp b/js/src/jit/mips/Trampoline-mips.cpp index d83bfb7ebd1..552f19877ad 100644 --- a/js/src/jit/mips/Trampoline-mips.cpp +++ b/js/src/jit/mips/Trampoline-mips.cpp @@ -209,10 +209,13 @@ JitRuntime::generateEnterJIT(JSContext *cx, EnterJitType type) Register numStackValues = regs.takeAny(); masm.load32(slotNumStackValues, numStackValues); - // Push return address, previous frame pointer. - masm.subPtr(Imm32(2 * sizeof(uintptr_t)), StackPointer); + // Push return address. + masm.subPtr(Imm32(sizeof(uintptr_t)), StackPointer); masm.ma_li(scratch, returnLabel.dest()); - masm.storePtr(scratch, Address(StackPointer, sizeof(uintptr_t))); + masm.storePtr(scratch, Address(StackPointer, 0)); + + // Push previous frame pointer. + masm.subPtr(Imm32(sizeof(uintptr_t)), StackPointer); masm.storePtr(BaselineFrameReg, Address(StackPointer, 0)); // Reserve frame. @@ -261,6 +264,19 @@ JitRuntime::generateEnterJIT(JSContext *cx, EnterJitType type) masm.addPtr(Imm32(BaselineFrame::Size()), framePtr); masm.branchIfFalseBool(ReturnReg, &error); + // If OSR-ing, then emit instrumentation for setting lastProfilerFrame + // if profiler instrumentation is enabled. + { + Label skipProfilingInstrumentation; + Register realFramePtr = numStackValues; + AbsoluteAddress addressOfEnabled(cx->runtime()->spsProfiler.addressOfEnabled()); + masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), + &skipProfilingInstrumentation); + masm.ma_add(realFramePtr, StackPointer, Imm32(sizeof(void*))); + masm.profilerEnterFrame(realFramePtr, scratch); + masm.bind(&skipProfilingInstrumentation); + } + masm.jump(jitcode); // OOM: load error value, discard return address and previous frame @@ -952,6 +968,17 @@ JitRuntime::generateDebugTrapHandler(JSContext *cx) JSReturnOperand); masm.movePtr(s5, StackPointer); masm.pop(s5); + + // Before returning, if profiling is turned on, make sure that lastProfilingFrame + // is set to the correct caller frame. + { + Label skipProfilingInstrumentation; + AbsoluteAddress addressOfEnabled(cx->runtime()->spsProfiler.addressOfEnabled()); + masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skipProfilingInstrumentation); + masm.profilerExitFrame(); + masm.bind(&skipProfilingInstrumentation); + } + masm.ret(); Linker linker(masm); @@ -1002,3 +1029,293 @@ JitRuntime::generateBailoutTailStub(JSContext *cx) return code; } +JitCode * +JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) +{ + MacroAssembler masm; + + Register scratch1 = t0; + Register scratch2 = t1; + Register scratch3 = t2; + Register scratch3 = t3; + + // + // The code generated below expects that the current stack pointer points + // to an Ion or Baseline frame, at the state it would be immediately + // before a ret(). Thus, after this stub's business is done, it executes + // a ret() and returns directly to the caller script, on behalf of the + // callee script that jumped to this code. + // + // Thus the expected stack is: + // + // StackPointer ----+ + // v + // ..., ActualArgc, CalleeToken, Descriptor, ReturnAddr + // MEM-HI MEM-LOW + // + // + // The generated jitcode is responsible for overwriting the + // jitActivation->lastProfilingFrame field with a pointer to the previous + // Ion or Baseline jit-frame that was pushed before this one. It is also + // responsible for overwriting jitActivation->lastProfilingCallSite with + // the return address into that frame. The frame could either be an + // immediate "caller" frame, or it could be a frame in a previous + // JitActivation (if the current frame was entered from C++, and the C++ + // was entered by some caller jit-frame further down the stack). + // + // So this jitcode is responsible for "walking up" the jit stack, finding + // the previous Ion or Baseline JS frame, and storing its address and the + // return address into the appropriate fields on the current jitActivation. + // + // There are a fixed number of different path types that can lead to the + // current frame, which is either a baseline or ion frame: + // + // + // ^ + // | + // ^--- Ion + // | + // ^--- Baseline Stub <---- Baseline + // | + // ^--- Argument Rectifier + // | ^ + // | | + // | ^--- Ion + // | | + // | ^--- Baseline Stub <---- Baseline + // | + // ^--- Entry Frame (From C++) + // + Register actReg = scratch4; + AbsoluteAddress activationAddr(GetJitContext()->runtime->addressOfProfilingActivation()); + masm.loadPtr(activationAddr, actReg); + + Address lastProfilingFrame(actReg, JitActivation::offsetOfLastProfilingFrame()); + Address lastProfilingCallSite(actReg, JitActivation::offsetOfLastProfilingCallSite()); + +#ifdef DEBUG + // Ensure that frame we are exiting is current lastProfilingFrame + { + masm.loadPtr(lastProfilingFrame, scratch1); + Label checkOk; + masm.branchPtr(Assembler::Equal, scratch1, ImmWord(0), &checkOk); + masm.branchPtr(Assembler::Equal, StackPointer, scratch1, &checkOk); + masm.assumeUnreachable( + "Mismatch between stored lastProfilingFrame and current stack pointer."); + masm.bind(&checkOk); + } +#endif + + // Load the frame descriptor into |scratch1|, figure out what to do depending on its type. + masm.loadPtr(Address(StackPointer, JitFrameLayout::offsetOfDescriptor()), scratch1); + + // Going into the conditionals, we will have: + // FrameDescriptor.size in scratch1 + // FrameDescriptor.type in scratch2 + masm.ma_and(scratch2, scratch1, Imm32((1 << FRAMESIZE_SHIFT) - 1)); + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), scratch1); + + // Handling of each case is dependent on FrameDescriptor.type + Label handle_IonJS; + Label handle_BaselineStub; + Label handle_Rectifier; + Label handle_Entry; + Label end; + + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonJS), &handle_IonJS); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry); + + masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame."); + + // + // JitFrame_IonJS + // + // Stack layout: + // ... + // Ion-Descriptor + // Prev-FP ---> Ion-ReturnAddr + // ... previous frame data ... |- Descriptor.Size + // ... arguments ... | + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + masm.bind(&handle_IonJS); + { + // |scratch1| contains Descriptor.size + + // returning directly to an IonJS frame. Store return addr to frame + // in lastProfilingCallSite. + masm.loadPtr(Address(StackPointer, JitFrameLayout::offsetOfReturnAddress()), scratch2); + masm.storePtr(scratch2, lastProfilingCallSite); + + // Store return frame in lastProfilingFrame. + // scratch2 := StackPointer + Descriptor.size*1 + JitFrameLayout::Size(); + masm.ma_add(scratch2, StackPointer, scratch1); + masm.ma_add(scratch2, scratch2, Imm32(JitFrameLayout::Size())); + masm.storePtr(scratch2, lastProfilingFrame); + masm.ret(); + } + + // + // JitFrame_BaselineStub + // + // Look past the stub and store the frame pointer to + // the baselineJS frame prior to it. + // + // Stack layout: + // ... + // BL-Descriptor + // Prev-FP ---> BL-ReturnAddr + // +-----> BL-PrevFramePointer + // | ... BL-FrameData ... + // | BLStub-Descriptor + // | BLStub-ReturnAddr + // | BLStub-StubPointer | + // +------ BLStub-SavedFramePointer |- Descriptor.Size + // ... arguments ... | + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + // We take advantage of the fact that the stub frame saves the frame + // pointer pointing to the baseline frame, so a bunch of calculation can + // be avoided. + // + masm.bind(&handle_BaselineStub); + { + masm.ma_add(scratch3, StackPointer, scratch1); + Address stubFrameReturnAddr(scratch3, + JitFrameLayout::Size() + + BaselineStubFrameLayout::offsetOfReturnAddress()); + masm.loadPtr(stubFrameReturnAddr, scratch2); + masm.storePtr(scratch2, lastProfilingCallSite); + + Address stubFrameSavedFramePtr(scratch3, + JitFrameLayout::Size() - (2 * sizeof(void *))); + masm.loadPtr(stubFrameSavedFramePtr, scratch2); + masm.addPtr(Imm32(sizeof(void *)), scratch2); // Skip past BL-PrevFramePtr + masm.storePtr(scratch2, lastProfilingFrame); + masm.ret(); + } + + + // + // JitFrame_Rectifier + // + // The rectifier frame can be preceded by either an IonJS or a + // BaselineStub frame. + // + // Stack layout if caller of rectifier was Ion: + // + // Ion-Descriptor + // Ion-ReturnAddr + // ... ion frame data ... |- Rect-Descriptor.Size + // < COMMON LAYOUT > + // + // Stack layout if caller of rectifier was Baseline: + // + // BL-Descriptor + // Prev-FP ---> BL-ReturnAddr + // +-----> BL-SavedFramePointer + // | ... baseline frame data ... + // | BLStub-Descriptor + // | BLStub-ReturnAddr + // | BLStub-StubPointer | + // +------ BLStub-SavedFramePointer |- Rect-Descriptor.Size + // ... args to rectifier ... | + // < COMMON LAYOUT > + // + // Common stack layout: + // + // ActualArgc | + // CalleeToken |- IonRectitiferFrameLayout::Size() + // Rect-Descriptor | + // Rect-ReturnAddr | + // ... rectifier data & args ... |- Descriptor.Size + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + masm.bind(&handle_Rectifier); + { + // scratch2 := StackPointer + Descriptor.size*1 + JitFrameLayout::Size(); + masm.ma_add(scratch2, StackPointer, scratch1); + masm.add32(Imm32(JitFrameLayout::Size()), scratch2); + masm.loadPtr(Address(scratch2, RectifierFrameLayout::offsetOfDescriptor()), scratch3); + masm.ma_lsr(scratch1, scratch3, Imm32(FRAMESIZE_SHIFT)); + masm.and32(Imm32((1 << FRAMETYPE_BITS) - 1), scratch3); + + // Now |scratch1| contains Rect-Descriptor.Size + // and |scratch2| points to Rectifier frame + // and |scratch3| contains Rect-Descriptor.Type + + // Check for either Ion or BaselineStub frame. + Label handle_Rectifier_BaselineStub; + masm.branch32(Assembler::NotEqual, scratch3, Imm32(JitFrame_IonJS), + &handle_Rectifier_BaselineStub); + + // Handle Rectifier <- IonJS + // scratch3 := RectFrame[ReturnAddr] + masm.loadPtr(Address(scratch2, RectifierFrameLayout::offsetOfReturnAddress()), scratch3); + masm.storePtr(scratch3, lastProfilingCallSite); + + // scratch3 := RectFrame + Rect-Descriptor.Size + RectifierFrameLayout::Size() + masm.ma_add(scratch3, scratch2, scratch1); + masm.add32(Imm32(RectifierFrameLayout::Size()), scratch3); + masm.storePtr(scratch3, lastProfilingFrame); + masm.ret(); + + // Handle Rectifier <- BaselineStub <- BaselineJS + masm.bind(&handle_Rectifier_BaselineStub); +#ifdef DEBUG + { + Label checkOk; + masm.branch32(Assembler::Equal, scratch3, Imm32(JitFrame_BaselineStub), &checkOk); + masm.assumeUnreachable("Unrecognized frame preceding baselineStub."); + masm.bind(&checkOk); + } +#endif + masm.ma_add(scratch3, scratch2, scratch1); + Address stubFrameReturnAddr(scratch3, RectifierFrameLayout::Size() + + BaselineStubFrameLayout::offsetOfReturnAddress()); + masm.loadPtr(stubFrameReturnAddr, scratch2); + masm.storePtr(scratch2, lastProfilingCallSite); + + Address stubFrameSavedFramePtr(scratch3, + RectifierFrameLayout::Size() - (2 * sizeof(void *))); + masm.loadPtr(stubFrameSavedFramePtr, scratch2); + masm.addPtr(Imm32(sizeof(void *)), scratch2); + masm.storePtr(scratch2, lastProfilingFrame); + masm.ret(); + } + + // + // JitFrame_Entry + // + // If at an entry frame, store null into both fields. + // + masm.bind(&handle_Entry); + { + masm.movePtr(ImmPtr(nullptr), scratch1); + masm.storePtr(scratch1, lastProfilingCallSite); + masm.storePtr(scratch1, lastProfilingFrame); + masm.ret(); + } + + Linker linker(masm); + AutoFlushICache afc("ProfilerExitFrameTailStub"); + JitCode *code = linker.newCode(cx, OTHER_CODE); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "ProfilerExitFrameStub"); +#endif + + return code; +} diff --git a/js/src/jit/shared/Assembler-shared.h b/js/src/jit/shared/Assembler-shared.h index 01c9e1144eb..2b4c064e603 100644 --- a/js/src/jit/shared/Assembler-shared.h +++ b/js/src/jit/shared/Assembler-shared.h @@ -897,6 +897,7 @@ class AssemblerShared Vector asmJSAbsoluteLinks_; protected: + Vector profilerCallSites_; bool enoughMemory_; bool embedsNurseryPointers_; @@ -918,6 +919,10 @@ class AssemblerShared return !enoughMemory_; } + void appendProfilerCallSite(CodeOffsetLabel label) { + enoughMemory_ &= profilerCallSites_.append(label); + } + bool embedsNurseryPointers() const { return embedsNurseryPointers_; } diff --git a/js/src/jit/shared/BaselineCompiler-shared.cpp b/js/src/jit/shared/BaselineCompiler-shared.cpp index 0ea502e2386..5d75980e962 100644 --- a/js/src/jit/shared/BaselineCompiler-shared.cpp +++ b/js/src/jit/shared/BaselineCompiler-shared.cpp @@ -31,6 +31,8 @@ BaselineCompilerShared::BaselineCompilerShared(JSContext *cx, TempAllocator &all pushedBeforeCall_(0), inCall_(false), spsPushToggleOffset_(), + profilerEnterFrameToggleOffset_(), + profilerExitFrameToggleOffset_(), traceLoggerEnterToggleOffset_(), traceLoggerExitToggleOffset_(), traceLoggerScriptTextIdOffset_() diff --git a/js/src/jit/shared/BaselineCompiler-shared.h b/js/src/jit/shared/BaselineCompiler-shared.h index fa7740bc224..425597865d7 100644 --- a/js/src/jit/shared/BaselineCompiler-shared.h +++ b/js/src/jit/shared/BaselineCompiler-shared.h @@ -68,6 +68,8 @@ class BaselineCompilerShared mozilla::DebugOnly inCall_; CodeOffsetLabel spsPushToggleOffset_; + CodeOffsetLabel profilerEnterFrameToggleOffset_; + CodeOffsetLabel profilerExitFrameToggleOffset_; CodeOffsetLabel traceLoggerEnterToggleOffset_; CodeOffsetLabel traceLoggerExitToggleOffset_; CodeOffsetLabel traceLoggerScriptTextIdOffset_; diff --git a/js/src/jit/shared/CodeGenerator-shared.cpp b/js/src/jit/shared/CodeGenerator-shared.cpp index 639dbc4567e..68647c8984d 100644 --- a/js/src/jit/shared/CodeGenerator-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-shared.cpp @@ -59,7 +59,6 @@ CodeGeneratorShared::CodeGeneratorShared(MIRGenerator *gen, LIRGraph *graph, Mac nativeToBytecodeNumRegions_(0), nativeToBytecodeScriptList_(nullptr), nativeToBytecodeScriptListLength_(0), - sps_(&GetJitContext()->runtime->spsProfiler(), &lastNotInlinedPC_), osrEntryOffset_(0), skipArgCheckEntryOffset_(0), #ifdef CHECK_OSIPOINT_REGISTERS @@ -68,8 +67,8 @@ CodeGeneratorShared::CodeGeneratorShared(MIRGenerator *gen, LIRGraph *graph, Mac frameDepth_(graph->paddedLocalSlotsSize() + graph->argumentsSize()), frameInitialAdjustment_(0) { - if (!gen->compilingAsmJS()) - masm.setInstrumentation(&sps_); + if (gen->isProfilerInstrumentationEnabled()) + masm.enableProfilingInstrumentation(); if (gen->compilingAsmJS()) { // Since asm.js uses the system ABI which does not necessarily use a @@ -107,7 +106,6 @@ CodeGeneratorShared::CodeGeneratorShared(MIRGenerator *gen, LIRGraph *graph, Mac bool CodeGeneratorShared::generateOutOfLineCode() { - JSScript *topScript = sps_.getPushed(); for (size_t i = 0; i < outOfLineCode_.length(); i++) { // Add native => bytecode mapping entries for OOL sites. // Not enabled on asm.js yet since asm doesn't contain bytecode mappings. @@ -123,16 +121,11 @@ CodeGeneratorShared::generateOutOfLineCode() masm.setFramePushed(outOfLineCode_[i]->framePushed()); lastPC_ = outOfLineCode_[i]->pc(); - if (!sps_.prepareForOOL()) - return false; - sps_.setPushed(outOfLineCode_[i]->script()); outOfLineCode_[i]->bind(&masm); oolIns = outOfLineCode_[i]; outOfLineCode_[i]->generate(this); - sps_.finishOOL(); } - sps_.setPushed(topScript); oolIns = nullptr; return true; @@ -158,7 +151,7 @@ bool CodeGeneratorShared::addNativeToBytecodeEntry(const BytecodeSite *site) { // Skip the table entirely if profiling is not enabled. - if (!isNativeToBytecodeMapEnabled()) + if (!isProfilerInstrumentationEnabled()) return true; MOZ_ASSERT(site); diff --git a/js/src/jit/shared/CodeGenerator-shared.h b/js/src/jit/shared/CodeGenerator-shared.h index 6ee06585051..a2255d8cf99 100644 --- a/js/src/jit/shared/CodeGenerator-shared.h +++ b/js/src/jit/shared/CodeGenerator-shared.h @@ -113,13 +113,8 @@ class CodeGeneratorShared : public LElementVisitor JSScript **nativeToBytecodeScriptList_; uint32_t nativeToBytecodeScriptListLength_; - // When profiling is enabled, this is the instrumentation manager which - // maintains state of what script is currently being generated (for inline - // scripts) and when instrumentation needs to be emitted or skipped. - IonInstrumentation sps_; - - bool isNativeToBytecodeMapEnabled() { - return gen->isNativeToBytecodeMapEnabled(); + bool isProfilerInstrumentationEnabled() { + return gen->isProfilerInstrumentationEnabled(); } protected: diff --git a/js/src/jit/shared/CodeGenerator-x86-shared.cpp b/js/src/jit/shared/CodeGenerator-x86-shared.cpp index 2224f50909c..461df0f66c2 100644 --- a/js/src/jit/shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-x86-shared.cpp @@ -44,6 +44,10 @@ CodeGeneratorX86Shared::generatePrologue() MOZ_ASSERT(masm.framePushed() == 0); MOZ_ASSERT(!gen->compilingAsmJS()); + // If profiling, save the current frame pointer to a per-thread global field. + if (isProfilerInstrumentationEnabled()) + masm.profilerEnterFrame(StackPointer, CallTempReg0); + // Note that this automatically sets MacroAssembler::framePushed(). masm.reserveStack(frameSize()); @@ -65,6 +69,11 @@ CodeGeneratorX86Shared::generateEpilogue() masm.freeStack(frameSize()); MOZ_ASSERT(masm.framePushed() == 0); + // If profiling, reset the per-thread global lastJitFrame to point to + // the previous frame. + if (isProfilerInstrumentationEnabled()) + masm.profilerExitFrame(); + masm.ret(); return true; } diff --git a/js/src/jit/x64/MacroAssembler-x64.cpp b/js/src/jit/x64/MacroAssembler-x64.cpp index 9343fa8642e..275d8c3610f 100644 --- a/js/src/jit/x64/MacroAssembler-x64.cpp +++ b/js/src/jit/x64/MacroAssembler-x64.cpp @@ -441,6 +441,17 @@ MacroAssemblerX64::handleFailureWithHandlerTail(void *handler) loadValue(Address(rbp, BaselineFrame::reverseOffsetOfReturnValue()), JSReturnOperand); movq(rbp, rsp); pop(rbp); + + // If profiling is enabled, then update the lastProfilingFrame to refer to caller + // frame before returning. + { + Label skipProfilingInstrumentation; + AbsoluteAddress addressOfEnabled(GetJitContext()->runtime->spsProfiler().addressOfEnabled()); + branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skipProfilingInstrumentation); + profilerExitFrame(); + bind(&skipProfilingInstrumentation); + } + ret(); // If we are bailing out to baseline to handle an exception, jump to @@ -519,3 +530,18 @@ MacroAssemblerX64::branchValueIsNurseryObject(Condition cond, ValueOperand value branchPtr(cond == Assembler::Equal ? Assembler::Below : Assembler::AboveOrEqual, ScratchReg, Imm32(nursery.nurserySize()), label); } + +void +MacroAssemblerX64::profilerEnterFrame(Register framePtr, Register scratch) +{ + AbsoluteAddress activation(GetJitContext()->runtime->addressOfProfilingActivation()); + loadPtr(activation, scratch); + storePtr(framePtr, Address(scratch, JitActivation::offsetOfLastProfilingFrame())); + storePtr(ImmPtr(nullptr), Address(scratch, JitActivation::offsetOfLastProfilingCallSite())); +} + +void +MacroAssemblerX64::profilerExitFrame() +{ + jmp(GetJitContext()->runtime->jitRuntime()->getProfilerExitFrameTail()); +} diff --git a/js/src/jit/x64/MacroAssembler-x64.h b/js/src/jit/x64/MacroAssembler-x64.h index ad6420c0999..d618d69be25 100644 --- a/js/src/jit/x64/MacroAssembler-x64.h +++ b/js/src/jit/x64/MacroAssembler-x64.h @@ -1445,6 +1445,10 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared void branchPtrInNurseryRange(Condition cond, Register ptr, Register temp, Label *label); void branchValueIsNurseryObject(Condition cond, ValueOperand value, Register temp, Label *label); + + // Instrumentation for entering and leaving the profiler. + void profilerEnterFrame(Register framePtr, Register scratch); + void profilerExitFrame(); }; typedef MacroAssemblerX64 MacroAssemblerSpecific; diff --git a/js/src/jit/x64/Trampoline-x64.cpp b/js/src/jit/x64/Trampoline-x64.cpp index d2a506c5461..a9e0a143a73 100644 --- a/js/src/jit/x64/Trampoline-x64.cpp +++ b/js/src/jit/x64/Trampoline-x64.cpp @@ -81,9 +81,6 @@ JitRuntime::generateEnterJIT(JSContext *cx, EnterJitType type) masm.vmovdqa(xmm15, Operand(rsp, 16 * 9)); #endif - // Push the EnterJIT sps mark. - masm.spsMarkJit(&cx->runtime()->spsProfiler, rbp, rbx); - // Save arguments passed in registers needed after function call. masm.push(result); @@ -165,9 +162,11 @@ JitRuntime::generateEnterJIT(JSContext *cx, EnterJitType type) Register numStackValues = regs.takeAny(); masm.movq(numStackValuesAddr, numStackValues); - // Push return address, previous frame pointer. + // Push return address masm.mov(returnLabel.dest(), scratch); masm.push(scratch); + + // Push previous frame pointer. masm.push(rbp); // Reserve frame. @@ -231,6 +230,19 @@ JitRuntime::generateEnterJIT(JSContext *cx, EnterJitType type) masm.addPtr(Imm32(BaselineFrame::Size()), framePtr); masm.branchIfFalseBool(ReturnReg, &error); + // If OSR-ing, then emit instrumentation for setting lastProfilerFrame + // if profiler instrumentation is enabled. + { + Label skipProfilingInstrumentation; + Register realFramePtr = numStackValues; + AbsoluteAddress addressOfEnabled(cx->runtime()->spsProfiler.addressOfEnabled()); + masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), + &skipProfilingInstrumentation); + masm.lea(Operand(framePtr, sizeof(void*)), realFramePtr); + masm.profilerEnterFrame(realFramePtr, scratch); + masm.bind(&skipProfilingInstrumentation); + } + masm.jump(reg_code); // OOM: load error value, discard return address and previous frame @@ -266,9 +278,6 @@ JitRuntime::generateEnterJIT(JSContext *cx, EnterJitType type) masm.pop(r12); // vp masm.storeValue(JSReturnOperand, Operand(r12, 0)); - // Unwind the sps mark. - masm.spsUnmarkJit(&cx->runtime()->spsProfiler, rbx); - // Restore non-volatile registers. #if defined(_WIN64) masm.vmovdqa(Operand(rsp, 16 * 0), xmm6); @@ -775,6 +784,17 @@ JitRuntime::generateDebugTrapHandler(JSContext *cx) JSReturnOperand); masm.mov(rbp, rsp); masm.pop(rbp); + + // Before returning, if profiling is turned on, make sure that lastProfilingFrame + // is set to the correct caller frame. + { + Label skipProfilingInstrumentation; + AbsoluteAddress addressOfEnabled(cx->runtime()->spsProfiler.addressOfEnabled()); + masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skipProfilingInstrumentation); + masm.profilerExitFrame(); + masm.bind(&skipProfilingInstrumentation); + } + masm.ret(); Linker linker(masm); @@ -820,3 +840,289 @@ JitRuntime::generateBailoutTailStub(JSContext *cx) return code; } + +JitCode * +JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) +{ + MacroAssembler masm; + + Register scratch1 = r8; + Register scratch2 = r9; + Register scratch3 = r10; + Register scratch4 = r11; + + // + // The code generated below expects that the current stack pointer points + // to an Ion or Baseline frame, at the state it would be immediately + // before a ret(). Thus, after this stub's business is done, it executes + // a ret() and returns directly to the caller script, on behalf of the + // callee script that jumped to this code. + // + // Thus the expected stack is: + // + // StackPointer ----+ + // v + // ..., ActualArgc, CalleeToken, Descriptor, ReturnAddr + // MEM-HI MEM-LOW + // + // + // The generated jitcode is responsible for overwriting the + // jitActivation->lastProfilingFrame field with a pointer to the previous + // Ion or Baseline jit-frame that was pushed before this one. It is also + // responsible for overwriting jitActivation->lastProfilingCallSite with + // the return address into that frame. The frame could either be an + // immediate "caller" frame, or it could be a frame in a previous + // JitActivation (if the current frame was entered from C++, and the C++ + // was entered by some caller jit-frame further down the stack). + // + // So this jitcode is responsible for "walking up" the jit stack, finding + // the previous Ion or Baseline JS frame, and storing its address and the + // return address into the appropriate fields on the current jitActivation. + // + // There are a fixed number of different path types that can lead to the + // current frame, which is either a baseline or ion frame: + // + // + // ^ + // | + // ^--- Ion + // | + // ^--- Baseline Stub <---- Baseline + // | + // ^--- Argument Rectifier + // | ^ + // | | + // | ^--- Ion + // | | + // | ^--- Baseline Stub <---- Baseline + // | + // ^--- Entry Frame (From C++) + // + Register actReg = scratch4; + AbsoluteAddress activationAddr(GetJitContext()->runtime->addressOfProfilingActivation()); + masm.loadPtr(activationAddr, actReg); + + Address lastProfilingFrame(actReg, JitActivation::offsetOfLastProfilingFrame()); + Address lastProfilingCallSite(actReg, JitActivation::offsetOfLastProfilingCallSite()); + +#ifdef DEBUG + // Ensure that frame we are exiting is current lastProfilingFrame + { + masm.loadPtr(lastProfilingFrame, scratch1); + Label checkOk; + masm.branchPtr(Assembler::Equal, scratch1, ImmWord(0), &checkOk); + masm.branchPtr(Assembler::Equal, StackPointer, scratch1, &checkOk); + masm.assumeUnreachable( + "Mismatch between stored lastProfilingFrame and current stack pointer."); + masm.bind(&checkOk); + } +#endif + + // Load the frame descriptor into |scratch1|, figure out what to do depending on its type. + masm.loadPtr(Address(StackPointer, JitFrameLayout::offsetOfDescriptor()), scratch1); + + // Going into the conditionals, we will have: + // FrameDescriptor.size in scratch1 + // FrameDescriptor.type in scratch2 + masm.movePtr(scratch1, scratch2); + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), scratch1); + masm.and32(Imm32((1 << FRAMETYPE_BITS) - 1), scratch2); + + // Handling of each case is dependent on FrameDescriptor.type + Label handle_IonJS; + Label handle_BaselineStub; + Label handle_Rectifier; + Label handle_Entry; + Label end; + + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonJS), &handle_IonJS); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry); + + masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame."); + + // + // JitFrame_IonJS + // + // Stack layout: + // ... + // Ion-Descriptor + // Prev-FP ---> Ion-ReturnAddr + // ... previous frame data ... |- Descriptor.Size + // ... arguments ... | + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + masm.bind(&handle_IonJS); + { + // returning directly to an IonJS frame. Store return addr to frame + // in lastProfilingCallSite. + masm.loadPtr(Address(StackPointer, JitFrameLayout::offsetOfReturnAddress()), scratch2); + masm.storePtr(scratch2, lastProfilingCallSite); + + // Store return frame in lastProfilingFrame. + // scratch2 := StackPointer + Descriptor.size*1 + JitFrameLayout::Size(); + masm.lea(Operand(StackPointer, scratch1, TimesOne, JitFrameLayout::Size()), scratch2); + masm.storePtr(scratch2, lastProfilingFrame); + masm.ret(); + } + + // + // JitFrame_BaselineStub + // + // Look past the stub and store the frame pointer to + // the baselineJS frame prior to it. + // + // Stack layout: + // ... + // BL-Descriptor + // Prev-FP ---> BL-ReturnAddr + // +-----> BL-PrevFramePointer + // | ... BL-FrameData ... + // | BLStub-Descriptor + // | BLStub-ReturnAddr + // | BLStub-StubPointer | + // +------ BLStub-SavedFramePointer |- Descriptor.Size + // ... arguments ... | + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + // We take advantage of the fact that the stub frame saves the frame + // pointer pointing to the baseline frame, so a bunch of calculation can + // be avoided. + // + masm.bind(&handle_BaselineStub); + { + BaseIndex stubFrameReturnAddr(StackPointer, scratch1, TimesOne, + JitFrameLayout::Size() + + BaselineStubFrameLayout::offsetOfReturnAddress()); + masm.loadPtr(stubFrameReturnAddr, scratch2); + masm.storePtr(scratch2, lastProfilingCallSite); + + BaseIndex stubFrameSavedFramePtr(StackPointer, scratch1, TimesOne, + JitFrameLayout::Size() - (2 * sizeof(void *))); + masm.loadPtr(stubFrameSavedFramePtr, scratch2); + masm.addPtr(Imm32(sizeof(void *)), scratch2); // Skip past BL-PrevFramePtr + masm.storePtr(scratch2, lastProfilingFrame); + masm.ret(); + } + + + // + // JitFrame_Rectifier + // + // The rectifier frame can be preceded by either an IonJS or a + // BaselineStub frame. + // + // Stack layout if caller of rectifier was Ion: + // + // Ion-Descriptor + // Ion-ReturnAddr + // ... ion frame data ... |- Rect-Descriptor.Size + // < COMMON LAYOUT > + // + // Stack layout if caller of rectifier was Baseline: + // + // BL-Descriptor + // Prev-FP ---> BL-ReturnAddr + // +-----> BL-SavedFramePointer + // | ... baseline frame data ... + // | BLStub-Descriptor + // | BLStub-ReturnAddr + // | BLStub-StubPointer | + // +------ BLStub-SavedFramePointer |- Rect-Descriptor.Size + // ... args to rectifier ... | + // < COMMON LAYOUT > + // + // Common stack layout: + // + // ActualArgc | + // CalleeToken |- IonRectitiferFrameLayout::Size() + // Rect-Descriptor | + // Rect-ReturnAddr | + // ... rectifier data & args ... |- Descriptor.Size + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + masm.bind(&handle_Rectifier); + { + // scratch2 := StackPointer + Descriptor.size + JitFrameLayout::Size() + masm.lea(Operand(StackPointer, scratch1, TimesOne, JitFrameLayout::Size()), scratch2); + masm.loadPtr(Address(scratch2, RectifierFrameLayout::offsetOfDescriptor()), scratch3); + masm.movePtr(scratch3, scratch1); + masm.and32(Imm32((1 << FRAMETYPE_BITS) - 1), scratch3); + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), scratch1); + + // Now |scratch1| contains Rect-Descriptor.Size + // and |scratch2| points to Rectifier frame + // and |scratch3| contains Rect-Descriptor.Type + + // Check for either Ion or BaselineStub frame. + Label handle_Rectifier_BaselineStub; + masm.branch32(Assembler::NotEqual, scratch3, Imm32(JitFrame_IonJS), + &handle_Rectifier_BaselineStub); + + // Handle Rectifier <- IonJS + // scratch3 := RectFrame[ReturnAddr] + masm.loadPtr(Address(scratch2, RectifierFrameLayout::offsetOfReturnAddress()), scratch3); + masm.storePtr(scratch3, lastProfilingCallSite); + + // scratch3 := RectFrame + Rect-Descriptor.Size + RectifierFrameLayout::Size() + masm.lea(Operand(scratch2, scratch1, TimesOne, RectifierFrameLayout::Size()), scratch3); + masm.storePtr(scratch3, lastProfilingFrame); + masm.ret(); + + // Handle Rectifier <- BaselineStub <- BaselineJS + masm.bind(&handle_Rectifier_BaselineStub); +#ifdef DEBUG + { + Label checkOk; + masm.branch32(Assembler::Equal, scratch3, Imm32(JitFrame_BaselineStub), &checkOk); + masm.assumeUnreachable("Unrecognized frame preceding baselineStub."); + masm.bind(&checkOk); + } +#endif + BaseIndex stubFrameReturnAddr(scratch2, scratch1, TimesOne, + RectifierFrameLayout::Size() + + BaselineStubFrameLayout::offsetOfReturnAddress()); + masm.loadPtr(stubFrameReturnAddr, scratch3); + masm.storePtr(scratch3, lastProfilingCallSite); + + BaseIndex stubFrameSavedFramePtr(scratch2, scratch1, TimesOne, + RectifierFrameLayout::Size() - (2 * sizeof(void *))); + masm.loadPtr(stubFrameSavedFramePtr, scratch3); + masm.addPtr(Imm32(sizeof(void *)), scratch3); + masm.storePtr(scratch3, lastProfilingFrame); + masm.ret(); + } + + // + // JitFrame_Entry + // + // If at an entry frame, store null into both fields. + // + masm.bind(&handle_Entry); + { + masm.movePtr(ImmPtr(nullptr), scratch1); + masm.storePtr(scratch1, lastProfilingCallSite); + masm.storePtr(scratch1, lastProfilingFrame); + masm.ret(); + } + + Linker linker(masm); + JitCode *code = linker.newCode(cx, OTHER_CODE); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "ProfilerExitFrameStub"); +#endif + + return code; +} diff --git a/js/src/jit/x86/MacroAssembler-x86.cpp b/js/src/jit/x86/MacroAssembler-x86.cpp index c158c3f9037..d83a664957a 100644 --- a/js/src/jit/x86/MacroAssembler-x86.cpp +++ b/js/src/jit/x86/MacroAssembler-x86.cpp @@ -421,6 +421,18 @@ MacroAssemblerX86::handleFailureWithHandlerTail(void *handler) loadValue(Address(ebp, BaselineFrame::reverseOffsetOfReturnValue()), JSReturnOperand); movl(ebp, esp); pop(ebp); + + // If profiling is enabled, then update the lastProfilingFrame to refer to caller + // frame before returning. + { + Label skipProfilingInstrumentation; + // Test if profiler enabled. + AbsoluteAddress addressOfEnabled(GetJitContext()->runtime->spsProfiler().addressOfEnabled()); + branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skipProfilingInstrumentation); + profilerExitFrame(); + bind(&skipProfilingInstrumentation); + } + ret(); // If we are bailing out to baseline to handle an exception, jump to @@ -514,3 +526,18 @@ MacroAssemblerX86::branchValueIsNurseryObject(Condition cond, ValueOperand value bind(&done); } + +void +MacroAssemblerX86::profilerEnterFrame(Register framePtr, Register scratch) +{ + AbsoluteAddress activation(GetJitContext()->runtime->addressOfProfilingActivation()); + loadPtr(activation, scratch); + storePtr(framePtr, Address(scratch, JitActivation::offsetOfLastProfilingFrame())); + storePtr(ImmPtr(nullptr), Address(scratch, JitActivation::offsetOfLastProfilingCallSite())); +} + +void +MacroAssemblerX86::profilerExitFrame() +{ + jmp(GetJitContext()->runtime->jitRuntime()->getProfilerExitFrameTail()); +} diff --git a/js/src/jit/x86/MacroAssembler-x86.h b/js/src/jit/x86/MacroAssembler-x86.h index 5e3486ae79e..25be7bef55f 100644 --- a/js/src/jit/x86/MacroAssembler-x86.h +++ b/js/src/jit/x86/MacroAssembler-x86.h @@ -1195,6 +1195,10 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared void branchPtrInNurseryRange(Condition cond, Register ptr, Register temp, Label *label); void branchValueIsNurseryObject(Condition cond, ValueOperand value, Register temp, Label *label); + + // Instrumentation for entering and leaving the profiler. + void profilerEnterFrame(Register framePtr, Register scratch); + void profilerExitFrame(); }; typedef MacroAssemblerX86 MacroAssemblerSpecific; diff --git a/js/src/jit/x86/Trampoline-x86.cpp b/js/src/jit/x86/Trampoline-x86.cpp index 7f690282255..46f7135a4e2 100644 --- a/js/src/jit/x86/Trampoline-x86.cpp +++ b/js/src/jit/x86/Trampoline-x86.cpp @@ -60,9 +60,6 @@ JitRuntime::generateEnterJIT(JSContext *cx, EnterJitType type) masm.push(esi); masm.push(edi); - // Push the EnterJIT sps mark. - masm.spsMarkJit(&cx->runtime()->spsProfiler, ebp, ebx); - // Keep track of the stack which has to be unwound after returning from the // compiled function. masm.movl(esp, esi); @@ -159,9 +156,11 @@ JitRuntime::generateEnterJIT(JSContext *cx, EnterJitType type) Register jitcode = regs.takeAny(); masm.loadPtr(Address(ebp, ARG_JITCODE), jitcode); - // Push return address, previous frame pointer. + // Push return address. masm.mov(returnLabel.dest(), scratch); masm.push(scratch); + + // Push previous frame pointer. masm.push(ebp); // Reserve frame. @@ -222,6 +221,19 @@ JitRuntime::generateEnterJIT(JSContext *cx, EnterJitType type) masm.addPtr(Imm32(BaselineFrame::Size()), framePtr); masm.branchIfFalseBool(ReturnReg, &error); + // If OSR-ing, then emit instrumentation for setting lastProfilerFrame + // if profiler instrumentation is enabled. + { + Label skipProfilingInstrumentation; + Register realFramePtr = numStackValues; + AbsoluteAddress addressOfEnabled(cx->runtime()->spsProfiler.addressOfEnabled()); + masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), + &skipProfilingInstrumentation); + masm.lea(Operand(framePtr, sizeof(void*)), realFramePtr); + masm.profilerEnterFrame(realFramePtr, scratch); + masm.bind(&skipProfilingInstrumentation); + } + masm.jump(jitcode); // OOM: load error value, discard return address and previous frame @@ -257,21 +269,18 @@ JitRuntime::generateEnterJIT(JSContext *cx, EnterJitType type) // |ebp| could have been clobbered by the inner function. // Grab the address for the Value result from the argument stack. - // +24 ... arguments ... - // +20 - // +16 ebp <- original %ebp pointing here. - // +12 ebx - // +8 esi - // +4 edi - // +0 hasSPSFrame - masm.loadPtr(Address(esp, ARG_RESULT + 4 * sizeof(void *)), eax); + // +20 ... arguments ... + // +16 + // +12 ebp <- original %ebp pointing here. + // +8 ebx + // +4 esi + // +0 edi + masm.loadPtr(Address(esp, ARG_RESULT + 3 * sizeof(void *)), eax); masm.storeValue(JSReturnOperand, Operand(eax, 0)); /************************************************************** Return stack and registers to correct state **************************************************************/ - // Unwind the sps mark. - masm.spsUnmarkJit(&cx->runtime()->spsProfiler, ebx); // Restore non-volatile registers masm.pop(edi); @@ -813,6 +822,17 @@ JitRuntime::generateDebugTrapHandler(JSContext *cx) JSReturnOperand); masm.mov(ebp, esp); masm.pop(ebp); + + // Before returning, if profiling is turned on, make sure that lastProfilingFrame + // is set to the correct caller frame. + { + Label skipProfilingInstrumentation; + AbsoluteAddress addressOfEnabled(cx->runtime()->spsProfiler.addressOfEnabled()); + masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skipProfilingInstrumentation); + masm.profilerExitFrame(); + masm.bind(&skipProfilingInstrumentation); + } + masm.ret(); Linker linker(masm); @@ -858,3 +878,292 @@ JitRuntime::generateBailoutTailStub(JSContext *cx) return code; } + +JitCode * +JitRuntime::generateProfilerExitFrameTailStub(JSContext *cx) +{ + MacroAssembler masm; + + Register scratch1 = eax; + Register scratch2 = ebx; + Register scratch3 = esi; + Register scratch4 = edi; + + // + // The code generated below expects that the current stack pointer points + // to an Ion or Baseline frame, at the state it would be immediately + // before a ret(). Thus, after this stub's business is done, it executes + // a ret() and returns directly to the caller script, on behalf of the + // callee script that jumped to this code. + // + // Thus the expected stack is: + // + // StackPointer ----+ + // v + // ..., ActualArgc, CalleeToken, Descriptor, ReturnAddr + // MEM-HI MEM-LOW + // + // + // The generated jitcode is responsible for overwriting the + // jitActivation->lastProfilingFrame field with a pointer to the previous + // Ion or Baseline jit-frame that was pushed before this one. It is also + // responsible for overwriting jitActivation->lastProfilingCallSite with + // the return address into that frame. The frame could either be an + // immediate "caller" frame, or it could be a frame in a previous + // JitActivation (if the current frame was entered from C++, and the C++ + // was entered by some caller jit-frame further down the stack). + // + // So this jitcode is responsible for "walking up" the jit stack, finding + // the previous Ion or Baseline JS frame, and storing its address and the + // return address into the appropriate fields on the current jitActivation. + // + // There are a fixed number of different path types that can lead to the + // current frame, which is either a baseline or ion frame: + // + // + // ^ + // | + // ^--- Ion + // | + // ^--- Baseline Stub <---- Baseline + // | + // ^--- Argument Rectifier + // | ^ + // | | + // | ^--- Ion + // | | + // | ^--- Baseline Stub <---- Baseline + // | + // ^--- Entry Frame (From C++) + // + Register actReg = scratch4; + AbsoluteAddress activationAddr(GetJitContext()->runtime->addressOfProfilingActivation()); + masm.loadPtr(activationAddr, actReg); + + Address lastProfilingFrame(actReg, JitActivation::offsetOfLastProfilingFrame()); + Address lastProfilingCallSite(actReg, JitActivation::offsetOfLastProfilingCallSite()); + +#ifdef DEBUG + // Ensure that frame we are exiting is current lastProfilingFrame + { + masm.loadPtr(lastProfilingFrame, scratch1); + Label checkOk; + masm.branchPtr(Assembler::Equal, scratch1, ImmWord(0), &checkOk); + masm.branchPtr(Assembler::Equal, StackPointer, scratch1, &checkOk); + masm.assumeUnreachable( + "Mismatch between stored lastProfilingFrame and current stack pointer."); + masm.bind(&checkOk); + } +#endif + + // Load the frame descriptor into |scratch1|, figure out what to do + // depending on its type. + masm.loadPtr(Address(StackPointer, JitFrameLayout::offsetOfDescriptor()), scratch1); + + // Going into the conditionals, we will have: + // FrameDescriptor.size in scratch1 + // FrameDescriptor.type in scratch2 + masm.movePtr(scratch1, scratch2); + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), scratch1); + masm.and32(Imm32((1 << FRAMETYPE_BITS) - 1), scratch2); + + // Handling of each case is dependent on FrameDescriptor.type + Label handle_IonJS; + Label handle_BaselineStub; + Label handle_Rectifier; + Label handle_Entry; + Label end; + + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonJS), &handle_IonJS); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry); + + masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame."); + + // + // JitFrame_IonJS + // + // Stack layout: + // ... + // Ion-Descriptor + // Prev-FP ---> Ion-ReturnAddr + // ... previous frame data ... |- Descriptor.Size + // ... arguments ... | + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + masm.bind(&handle_IonJS); + { + // |scratch1| contains Descriptor.size + + // returning directly to an IonJS frame. Store return addr to frame + // in lastProfilingCallSite. + masm.loadPtr(Address(StackPointer, JitFrameLayout::offsetOfReturnAddress()), scratch2); + masm.storePtr(scratch2, lastProfilingCallSite); + + // Store return frame in lastProfilingFrame. + // scratch2 := StackPointer + Descriptor.size*1 + JitFrameLayout::Size(); + masm.lea(Operand(StackPointer, scratch1, TimesOne, JitFrameLayout::Size()), scratch2); + masm.storePtr(scratch2, lastProfilingFrame); + masm.ret(); + } + + // + // JitFrame_BaselineStub + // + // Look past the stub and store the frame pointer to + // the baselineJS frame prior to it. + // + // Stack layout: + // ... + // BL-Descriptor + // Prev-FP ---> BL-ReturnAddr + // +-----> BL-PrevFramePointer + // | ... BL-FrameData ... + // | BLStub-Descriptor + // | BLStub-ReturnAddr + // | BLStub-StubPointer | + // +------ BLStub-SavedFramePointer |- Descriptor.Size + // ... arguments ... | + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + // We take advantage of the fact that the stub frame saves the frame + // pointer pointing to the baseline frame, so a bunch of calculation can + // be avoided. + // + masm.bind(&handle_BaselineStub); + { + BaseIndex stubFrameReturnAddr(StackPointer, scratch1, TimesOne, + JitFrameLayout::Size() + + BaselineStubFrameLayout::offsetOfReturnAddress()); + masm.loadPtr(stubFrameReturnAddr, scratch2); + masm.storePtr(scratch2, lastProfilingCallSite); + + BaseIndex stubFrameSavedFramePtr(StackPointer, scratch1, TimesOne, + JitFrameLayout::Size() - (2 * sizeof(void *))); + masm.loadPtr(stubFrameSavedFramePtr, scratch2); + masm.addPtr(Imm32(sizeof(void *)), scratch2); // Skip past BL-PrevFramePtr + masm.storePtr(scratch2, lastProfilingFrame); + masm.ret(); + } + + + // + // JitFrame_Rectifier + // + // The rectifier frame can be preceded by either an IonJS or a + // BaselineStub frame. + // + // Stack layout if caller of rectifier was Ion: + // + // Ion-Descriptor + // Ion-ReturnAddr + // ... ion frame data ... |- Rect-Descriptor.Size + // < COMMON LAYOUT > + // + // Stack layout if caller of rectifier was Baseline: + // + // BL-Descriptor + // Prev-FP ---> BL-ReturnAddr + // +-----> BL-SavedFramePointer + // | ... baseline frame data ... + // | BLStub-Descriptor + // | BLStub-ReturnAddr + // | BLStub-StubPointer | + // +------ BLStub-SavedFramePointer |- Rect-Descriptor.Size + // ... args to rectifier ... | + // < COMMON LAYOUT > + // + // Common stack layout: + // + // ActualArgc | + // CalleeToken |- IonRectitiferFrameLayout::Size() + // Rect-Descriptor | + // Rect-ReturnAddr | + // ... rectifier data & args ... |- Descriptor.Size + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + masm.bind(&handle_Rectifier); + { + // scratch2 := StackPointer + Descriptor.size + JitFrameLayout::Size() + masm.lea(Operand(StackPointer, scratch1, TimesOne, JitFrameLayout::Size()), scratch2); + masm.loadPtr(Address(scratch2, RectifierFrameLayout::offsetOfDescriptor()), scratch3); + masm.movePtr(scratch3, scratch1); + masm.and32(Imm32((1 << FRAMETYPE_BITS) - 1), scratch3); + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), scratch1); + + // Now |scratch1| contains Rect-Descriptor.Size + // and |scratch2| points to Rectifier frame + // and |scratch3| contains Rect-Descriptor.Type + + // Check for either Ion or BaselineStub frame. + Label handle_Rectifier_BaselineStub; + masm.branch32(Assembler::NotEqual, scratch3, Imm32(JitFrame_IonJS), + &handle_Rectifier_BaselineStub); + + // Handle Rectifier <- IonJS + // scratch3 := RectFrame[ReturnAddr] + masm.loadPtr(Address(scratch2, RectifierFrameLayout::offsetOfReturnAddress()), scratch3); + masm.storePtr(scratch3, lastProfilingCallSite); + + // scratch3 := RectFrame + Rect-Descriptor.Size + RectifierFrameLayout::Size() + masm.lea(Operand(scratch2, scratch1, TimesOne, RectifierFrameLayout::Size()), scratch3); + masm.storePtr(scratch3, lastProfilingFrame); + masm.ret(); + + // Handle Rectifier <- BaselineStub <- BaselineJS + masm.bind(&handle_Rectifier_BaselineStub); +#ifdef DEBUG + { + Label checkOk; + masm.branch32(Assembler::Equal, scratch3, Imm32(JitFrame_BaselineStub), &checkOk); + masm.assumeUnreachable("Unrecognized frame preceding baselineStub."); + masm.bind(&checkOk); + } +#endif + BaseIndex stubFrameReturnAddr(scratch2, scratch1, TimesOne, + RectifierFrameLayout::Size() + + BaselineStubFrameLayout::offsetOfReturnAddress()); + masm.loadPtr(stubFrameReturnAddr, scratch3); + masm.storePtr(scratch3, lastProfilingCallSite); + + BaseIndex stubFrameSavedFramePtr(scratch2, scratch1, TimesOne, + RectifierFrameLayout::Size() - (2 * sizeof(void *))); + masm.loadPtr(stubFrameSavedFramePtr, scratch3); + masm.addPtr(Imm32(sizeof(void *)), scratch3); + masm.storePtr(scratch3, lastProfilingFrame); + masm.ret(); + } + + // + // JitFrame_Entry + // + // If at an entry frame, store null into both fields. + // + masm.bind(&handle_Entry); + { + masm.movePtr(ImmPtr(nullptr), scratch1); + masm.storePtr(scratch1, lastProfilingCallSite); + masm.storePtr(scratch1, lastProfilingFrame); + masm.ret(); + } + + Linker linker(masm); + JitCode *code = linker.newCode(cx, OTHER_CODE); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "ProfilerExitFrameStub"); +#endif + + return code; +} diff --git a/js/src/jsapi-tests/testGCOutOfMemory.cpp b/js/src/jsapi-tests/testGCOutOfMemory.cpp index 6641ced5822..80f98987825 100644 --- a/js/src/jsapi-tests/testGCOutOfMemory.cpp +++ b/js/src/jsapi-tests/testGCOutOfMemory.cpp @@ -22,6 +22,7 @@ BEGIN_TEST(testGCOutOfMemory) JS::RootedValue root(cx); + // Count the number of allocations until we hit OOM, and store it in 'max'. static const char source[] = "var max = 0; (function() {" " var array = [];" @@ -38,9 +39,8 @@ BEGIN_TEST(testGCOutOfMemory) CHECK_EQUAL(errorCount, 1u); JS_GC(rt); - // Temporarily disabled to reopen the tree. Bug 847579. - return true; - + // The above GC should have discarded everything. Verify that we can now + // allocate half as many objects without OOMing. EVAL("(function() {" " var array = [];" " for (var i = max >> 2; i != 0;) {" @@ -53,7 +53,14 @@ BEGIN_TEST(testGCOutOfMemory) } virtual JSRuntime * createRuntime() MOZ_OVERRIDE { - JSRuntime *rt = JS_NewRuntime(768 * 1024); + // Note that the max nursery size must be less than the whole heap size, or + // the test will fail because 'max' (the number of allocations required for + // OOM) will be based on the nursery size, and that will overflow the + // tenured heap, which will cause the second pass with max/4 allocations to + // OOM. (Actually, this only happens with nursery zeal, because normally + // the nursery will start out with only a single chunk before triggering a + // major GC.) + JSRuntime *rt = JS_NewRuntime(768 * 1024, 128 * 1024); if (!rt) return nullptr; setNativeStackQuota(rt); diff --git a/js/src/jsapi-tests/testWeakMap.cpp b/js/src/jsapi-tests/testWeakMap.cpp index 9569dd677b7..a337bcc3ec6 100644 --- a/js/src/jsapi-tests/testWeakMap.cpp +++ b/js/src/jsapi-tests/testWeakMap.cpp @@ -129,6 +129,9 @@ BEGIN_TEST(testWeakMap_keyDelegates) static void DelegateObjectMoved(JSObject *obj, const JSObject *old) { + if (!keyDelegate) + return; // Object got moved before we set keyDelegate to point to it. + MOZ_RELEASE_ASSERT(keyDelegate == old); keyDelegate = obj; } @@ -234,12 +237,6 @@ JSObject *newDelegate() options); JS_SetReservedSlot(global, 0, JS::Int32Value(42)); - /* - * Ensure the delegate is not in the nursery because for the purpose of this - * test we're going to put it in a private slot where it won't get updated. - */ - JS_GC(rt); - return global; } diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 4b8d0163297..075370d2e96 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1064,6 +1064,21 @@ GCRuntime::releaseArena(ArenaHeader *aheader, const AutoLockGC &lock) return aheader->chunk()->releaseArena(rt, aheader, lock); } +void +GCRuntime::decommitArena(ArenaHeader *aheader, AutoLockGC &lock) +{ + aheader->zone->usage.removeGCArena(); + if (isBackgroundSweeping()) + aheader->zone->threshold.updateForRemovedArena(tunables); + + bool ok; + { + AutoUnlockGC unlock(lock); + ok = MarkPagesUnused(aheader, ArenaSize); + } + return aheader->chunk()->releaseArena(rt, aheader, lock, Chunk::ArenaDecommitState(ok)); +} + GCRuntime::GCRuntime(JSRuntime *rt) : rt(rt), systemZone(nullptr), @@ -2098,6 +2113,8 @@ PtrIsInRange(const void *ptr, const void *start, size_t length) static bool RelocateCell(Zone *zone, TenuredCell *src, AllocKind thingKind, size_t thingSize) { + JS::AutoSuppressGCAnalysis nogc(zone->runtimeFromMainThread()); + // Allocate a new cell. MOZ_ASSERT(zone == src->zone()); void *dstAlloc = zone->arenas.allocateFromFreeList(thingKind, thingSize); @@ -2373,7 +2390,7 @@ namespace gc { struct ArenasToUpdate { - ArenasToUpdate(JSRuntime *rt); + explicit ArenasToUpdate(JSRuntime *rt); bool done() { return initialized && arena == nullptr; } ArenaHeader* next(); ArenaHeader *getArenasToUpdate(AutoLockHelperThreadState& lock, unsigned max); @@ -2389,7 +2406,7 @@ struct ArenasToUpdate bool ArenasToUpdate::shouldProcessKind(unsigned kind) { - MOZ_ASSERT(kind >= 0 && kind < FINALIZE_LIMIT); + MOZ_ASSERT(kind < FINALIZE_LIMIT); return kind != FINALIZE_FAT_INLINE_STRING && kind != FINALIZE_STRING && @@ -2722,6 +2739,16 @@ ReleaseArenaList(JSRuntime *rt, ArenaHeader *aheader, const AutoLockGC &lock) } } +void +DecommitArenaList(JSRuntime *rt, ArenaHeader *aheader, AutoLockGC &lock) +{ + ArenaHeader *next; + for (; aheader; aheader = next) { + next = aheader->next; + rt->gc.decommitArena(aheader, lock); + } +} + ArenaLists::~ArenaLists() { AutoLockGC lock(runtime_); @@ -2766,7 +2793,7 @@ ArenaLists::forceFinalizeNow(FreeOp *fop, AllocKind thingKind, KeepArenasEnum ke return; arenaLists[thingKind].clear(); - size_t thingsPerArena = Arena::thingsPerArena(Arena::thingSize(thingKind)); + const size_t thingsPerArena = Arena::thingsPerArena(Arena::thingSize(thingKind)); SortedArenaList finalizedSorted(thingsPerArena); SliceBudget budget; @@ -2835,7 +2862,7 @@ ArenaLists::backgroundFinalize(FreeOp *fop, ArenaHeader *listHead, ArenaHeader * AllocKind thingKind = listHead->getAllocKind(); Zone *zone = listHead->zone; - size_t thingsPerArena = Arena::thingsPerArena(Arena::thingSize(thingKind)); + const size_t thingsPerArena = Arena::thingsPerArena(Arena::thingSize(thingKind)); SortedArenaList finalizedSorted(thingsPerArena); SliceBudget budget; @@ -3167,7 +3194,7 @@ GCRuntime::maybeAllocTriggerZoneGC(Zone *zone, const AutoLockGC &lock) // The threshold has been surpassed, immediately trigger a GC, // which will be done non-incrementally. triggerZoneGC(zone, JS::gcreason::ALLOC_TRIGGER); - } else if (usedBytes >= igcThresholdBytes) { + } else if (usedBytes >= igcThresholdBytes && interFrameGC) { // Reduce the delay to the start of the next incremental slice. if (zone->gcDelayBytes < ArenaSize) zone->gcDelayBytes = 0; @@ -3353,8 +3380,13 @@ GCRuntime::expireChunksAndArenas(bool shouldShrink, AutoLockGC &lock) } void -GCRuntime::sweepBackgroundThings(ZoneList &zones, ThreadType threadType) +GCRuntime::sweepBackgroundThings(ZoneList &zones, LifoAlloc &freeBlocks, ThreadType threadType) { + freeBlocks.freeAll(); + + if (zones.isEmpty()) + return; + // We must finalize thing kinds in the order specified by BackgroundFinalizePhases. ArenaHeader *emptyArenas = nullptr; FreeOp fop(rt, threadType); @@ -3370,7 +3402,7 @@ GCRuntime::sweepBackgroundThings(ZoneList &zones, ThreadType threadType) } AutoLockGC lock(rt); - ReleaseArenaList(rt, emptyArenas, lock); + DecommitArenaList(rt, emptyArenas, lock); while (!zones.isEmpty()) zones.removeFront(); } @@ -3604,10 +3636,9 @@ GCHelperState::doSweep(AutoLockGC &lock) zones.transferFrom(rt->gc.backgroundSweepZones); LifoAlloc freeLifoAlloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE); freeLifoAlloc.transferFrom(&rt->gc.freeLifoAlloc); - AutoUnlockGC unlock(lock); - rt->gc.sweepBackgroundThings(zones, BackgroundThread); - freeLifoAlloc.freeAll(); + AutoUnlockGC unlock(lock); + rt->gc.sweepBackgroundThings(zones, freeLifoAlloc, BackgroundThread); } bool shrinking = shrinkFlag; @@ -5106,12 +5137,13 @@ GCRuntime::endSweepingZoneGroup() } /* Start background thread to sweep zones if required. */ - if (sweepOnBackgroundThread) { - ZoneList zones; - for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) - zones.append(zone); + ZoneList zones; + for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) + zones.append(zone); + if (sweepOnBackgroundThread) queueZonesForBackgroundSweep(zones); - } + else + sweepBackgroundThings(zones, freeLifoAlloc, MainThread); /* Reset the list of arenas marked as being allocated during sweep phase. */ while (ArenaHeader *arena = arenasAllocatedDuringSweep) { @@ -5416,11 +5448,7 @@ GCRuntime::endSweepPhase(bool lastGC) if (!sweepOnBackgroundThread) { gcstats::AutoPhase ap(stats, gcstats::PHASE_DESTROY); - ZoneList zones; - for (GCZonesIter zone(rt); !zone.done(); zone.next()) - zones.append(zone); - if (!zones.isEmpty()) - sweepBackgroundThings(zones, MainThread); + assertBackgroundSweepingFinished(); /* * Destroy arenas after we finished the sweeping so finalizers can @@ -5433,8 +5461,6 @@ GCRuntime::endSweepPhase(bool lastGC) expireChunksAndArenas(invocationKind == GC_SHRINK, lock); } - freeLifoAlloc.freeAll(); - /* Ensure the compartments get swept if it's the last GC. */ if (lastGC) sweepZones(&fop, lastGC); diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 86d8dfab986..5e416e61b0d 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -1713,8 +1713,8 @@ SetGCCallback(JSContext *cx, unsigned argc, jsval *vp) return false; } - RootedObject opts(cx); - if (!JS_ValueToObject(cx, args[0], &opts)) + RootedObject opts(cx, ToObject(cx, args[0])); + if (!opts) return false; RootedValue v(cx); @@ -4160,12 +4160,19 @@ SingleStepCallback(void *arg, jit::Simulator *sim, void *pc) DebugOnly lastStackAddress = nullptr; StackChars stack; + uint32_t frameNo = 0; for (JS::ProfilingFrameIterator i(rt, state); !i.done(); ++i) { MOZ_ASSERT(i.stackAddress() != nullptr); MOZ_ASSERT(lastStackAddress <= i.stackAddress()); lastStackAddress = i.stackAddress(); - const char *label = i.label(); - stack.append(label, strlen(label)); + JS::ProfilingFrameIterator::Frame frames[16]; + uint32_t nframes = i.extractStack(frames, 0, 16); + for (uint32_t i = 0; i < nframes; i++) { + if (frameNo > 0) + stack.append(",", 1); + stack.append(frames[i].label, strlen(frames[i].label)); + frameNo++; + } } // Only append the stack if it differs from the last stack. diff --git a/js/src/tests/ecma_6/Array/from_basics.js b/js/src/tests/ecma_6/Array/from_basics.js index c35599b1fa1..623207a41a5 100644 --- a/js/src/tests/ecma_6/Array/from_basics.js +++ b/js/src/tests/ecma_6/Array/from_basics.js @@ -44,5 +44,8 @@ assertDeepEq(Array.from([0, , , ,]), [0, undefined, undefined, undefined]); // Even on non-iterable objects. assertDeepEq(Array.from({length: 4}), [undefined, undefined, undefined, undefined]); +// Array.from should coerce negative lengths to zero. +assertDeepEq(Array.from({length: -1}), []); + if (typeof reportCompare === 'function') reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/Array/from_errors.js b/js/src/tests/ecma_6/Array/from_errors.js index 4266cb206dc..98475a802ed 100644 --- a/js/src/tests/ecma_6/Array/from_errors.js +++ b/js/src/tests/ecma_6/Array/from_errors.js @@ -130,6 +130,13 @@ assertThrowsValue(() => Array.from.call(C, arrayish, () => { throw exc; }), exc) assertEq(log, "lC0"); assertEq(obj instanceof C, true); +// It's a TypeError if the @@iterator property is a primitive (except null and undefined). +for (var primitive of ["foo", 17, Symbol(), true]) { + assertThrowsInstanceOf(() => Array.from({[Symbol.iterator] : primitive}), TypeError); +} +assertDeepEq(Array.from({[Symbol.iterator]: null}), []); +assertDeepEq(Array.from({[Symbol.iterator]: undefined}), []); + // It's a TypeError if the iterator's .next() method returns a primitive. for (var primitive of [undefined, null, 17]) { assertThrowsInstanceOf( diff --git a/js/src/vm/DateTime.cpp b/js/src/vm/DateTime.cpp index fd029014804..8c97b4c216c 100644 --- a/js/src/vm/DateTime.cpp +++ b/js/src/vm/DateTime.cpp @@ -31,7 +31,9 @@ ComputeLocalTime(time_t local, struct tm *ptm) static bool ComputeUTCTime(time_t t, struct tm *ptm) { -#ifdef HAVE_GMTIME_R +#if defined(_WIN32) + return gmtime_s(ptm, &t) == 0; +#elif defined(HAVE_GMTIME_R) return gmtime_r(&t, ptm); #else struct tm *otm = gmtime(&t); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 4539be97def..fa9badcdaa3 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1684,7 +1684,12 @@ CASE(JSOP_LOOPENTRY) goto error; if (status == jit::Method_Compiled) { bool wasSPS = REGS.fp()->hasPushedSPSFrame(); - jit::JitExecStatus maybeOsr = jit::EnterBaselineAtBranch(cx, REGS.fp(), REGS.pc); + + jit::JitExecStatus maybeOsr; + { + SPSBaselineOSRMarker spsOSR(cx->runtime(), wasSPS); + maybeOsr = jit::EnterBaselineAtBranch(cx, REGS.fp(), REGS.pc); + } // We failed to call into baseline at all, so treat as an error. if (maybeOsr == jit::JitExec_Aborted) diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 0b79c6c6be4..d591cd33f02 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -75,6 +75,7 @@ PerThreadData::PerThreadData(JSRuntime *runtime) runtime_(runtime), jitTop(nullptr), jitJSContext(nullptr), + jitActivation(nullptr), jitStackLimit_(0xbad), #ifdef JS_TRACE_LOGGING traceLogger(nullptr), diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 32f2cbbe804..a23eccad1e2 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -87,6 +87,7 @@ struct PcScriptCache; class Simulator; class SimulatorRuntime; struct AutoFlushICache; +class CompileRuntime; } /* @@ -519,6 +520,12 @@ class PerThreadData : public PerThreadDataFriendFields */ JSContext *jitJSContext; + /* + * Points to the most recent JitActivation pushed on the thread. + * See JitActivation constructor in vm/Stack.cpp + */ + js::jit::JitActivation *jitActivation; + /* See comment for JSRuntime::interrupt_. */ private: mozilla::Atomic jitStackLimit_; @@ -545,6 +552,7 @@ class PerThreadData : public PerThreadDataFriendFields friend class js::ActivationIterator; friend class js::jit::JitActivation; friend class js::AsmJSActivation; + friend class js::jit::CompileRuntime; #ifdef DEBUG friend void js::AssertCurrentThreadCanLock(RuntimeLock which); #endif @@ -586,6 +594,12 @@ class PerThreadData : public PerThreadDataFriendFields js::Activation *profilingActivation() const { return profilingActivation_; } + void *addressOfProfilingActivation() { + return (void*) &profilingActivation_; + } + static unsigned offsetOfProfilingActivation() { + return offsetof(PerThreadData, profilingActivation_); + } js::AsmJSActivation *asmJSActivationStack() const { return asmJSActivationStack_; @@ -1018,7 +1032,7 @@ struct JSRuntime : public JS::shadow::Runtime, /* Whether sampling should be enabled or not. */ private: - bool suppressProfilerSampling; + mozilla::Atomic suppressProfilerSampling; public: bool isProfilerSamplingEnabled() const { diff --git a/js/src/vm/SPSProfiler.cpp b/js/src/vm/SPSProfiler.cpp index d41189d5138..459bc6efe29 100644 --- a/js/src/vm/SPSProfiler.cpp +++ b/js/src/vm/SPSProfiler.cpp @@ -12,7 +12,10 @@ #include "jsprf.h" #include "jsscript.h" +#include "jit/BaselineFrame.h" #include "jit/BaselineJIT.h" +#include "jit/JitFrameIterator.h" +#include "jit/JitFrames.h" #include "vm/StringBuffer.h" using namespace js; @@ -91,7 +94,15 @@ SPSProfiler::enable(bool enabled) * jitcode for scripts with active frames on the stack. These scripts need to have * their profiler state toggled so they behave properly. */ - jit::ToggleBaselineSPS(rt, enabled); + jit::ToggleBaselineProfiling(rt, enabled); + + /* Update lastProfilingFrame to point to the top-most JS jit-frame currently on + * stack. + */ + if (rt->mainThread.jitActivation) { + void *lastProfilingFrame = GetTopProfilingJitFrame(rt->mainThread.jitTop); + rt->mainThread.jitActivation->setLastProfilingFrame(lastProfilingFrame); + } } /* Lookup the string for the function/script, creating one if necessary */ @@ -199,18 +210,18 @@ SPSProfiler::exit(JSScript *script, JSFunction *maybeFun) } void -SPSProfiler::enterAsmJS(const char *string, void *sp) +SPSProfiler::beginPseudoJS(const char *string, void *sp) { /* these operations cannot be re-ordered, so volatile-ize operations */ volatile ProfileEntry *stack = stack_; volatile uint32_t *size = size_; uint32_t current = *size; - MOZ_ASSERT(enabled()); + MOZ_ASSERT(installed()); if (current < max_) { stack[current].setLabel(string); stack[current].setCppFrame(sp, 0); - stack[current].setFlag(ProfileEntry::ASMJS); + stack[current].setFlag(ProfileEntry::BEGIN_PSEUDO_JS); } *size = current + 1; } @@ -322,18 +333,51 @@ SPSEntryMarker::SPSEntryMarker(JSRuntime *rt, } size_before = *profiler->size_; // We want to push a CPP frame so the profiler can correctly order JS and native stacks. - profiler->push("js::RunScript", this, nullptr, nullptr, /* copy = */ false); - // We also want to push a JS frame so the hang monitor can catch script hangs. + profiler->beginPseudoJS("js::RunScript", this); profiler->push("js::RunScript", nullptr, script, script->code(), /* copy = */ false); } SPSEntryMarker::~SPSEntryMarker() { - if (profiler != nullptr) { - profiler->pop(); - profiler->pop(); - MOZ_ASSERT(size_before == *profiler->size_); + if (profiler == nullptr) + return; + + profiler->pop(); + profiler->endPseudoJS(); + MOZ_ASSERT(size_before == *profiler->size_); +} + +SPSBaselineOSRMarker::SPSBaselineOSRMarker(JSRuntime *rt, bool hasSPSFrame + MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) + : profiler(&rt->spsProfiler) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + if (!hasSPSFrame || !profiler->enabled()) { + profiler = nullptr; + return; } + + size_before = profiler->size(); + if (profiler->size() == 0) + return; + + ProfileEntry &entry = profiler->stack()[profiler->size() - 1]; + MOZ_ASSERT(entry.isJs()); + entry.setOSR(); +} + +SPSBaselineOSRMarker::~SPSBaselineOSRMarker() +{ + if (profiler == nullptr) + return; + + MOZ_ASSERT(size_before == *profiler->size_); + if (profiler->size() == 0) + return; + + ProfileEntry &entry = profiler->stack()[profiler->size() - 1]; + MOZ_ASSERT(entry.isJs()); + entry.unsetOSR(); } JS_FRIEND_API(jsbytecode*) @@ -375,8 +419,6 @@ js::ProfilingGetPC(JSRuntime *rt, JSScript *script, void *ip) return rt->spsProfiler.ipToPC(script, size_t(ip)); } - - AutoSuppressProfilerSampling::AutoSuppressProfilerSampling(JSContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) : rt_(cx->runtime()), @@ -402,3 +444,15 @@ AutoSuppressProfilerSampling::~AutoSuppressProfilerSampling() if (previouslyEnabled_) rt_->enableProfilerSampling(); } + +void * +js::GetTopProfilingJitFrame(uint8_t *exitFramePtr) +{ + // For null exitFrame, there is no previous exit frame, just return. + if (!exitFramePtr) + return nullptr; + + jit::JitProfilingFrameIterator iter(exitFramePtr); + MOZ_ASSERT(!iter.done()); + return iter.fp(); +} diff --git a/js/src/vm/SPSProfiler.h b/js/src/vm/SPSProfiler.h index 5c77368d5a6..ddba04b4ada 100644 --- a/js/src/vm/SPSProfiler.h +++ b/js/src/vm/SPSProfiler.h @@ -112,10 +112,12 @@ typedef HashMap, SystemAllocPol ProfileStringMap; class SPSEntryMarker; +class SPSBaselineOSRMarker; class SPSProfiler { friend class SPSEntryMarker; + friend class SPSBaselineOSRMarker; JSRuntime *rt; ProfileStringMap strings; @@ -151,6 +153,7 @@ class SPSProfiler uint32_t *sizePointer() { return size_; } uint32_t maxSize() { return max_; } + uint32_t size() { MOZ_ASSERT(installed()); return *size_; } ProfileEntry *stack() { return stack_; } /* management of whether instrumentation is on or off */ @@ -180,8 +183,8 @@ class SPSProfiler } /* Enter asm.js code */ - void enterAsmJS(const char *string, void *sp); - void exitAsmJS() { pop(); } + void beginPseudoJS(const char *string, void *sp); + void endPseudoJS() { pop(); } jsbytecode *ipToPC(JSScript *script, size_t ip) { return nullptr; } @@ -271,6 +274,24 @@ class SPSEntryMarker MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; +/* + * This class is used in the interpreter to bound regions where the baseline JIT + * being entered via OSR. It marks the current top pseudostack entry as + * OSR-ed + */ +class SPSBaselineOSRMarker +{ + public: + explicit SPSBaselineOSRMarker(JSRuntime *rt, bool hasSPSFrame + MOZ_GUARD_OBJECT_NOTIFIER_PARAM); + ~SPSBaselineOSRMarker(); + + private: + SPSProfiler *profiler; + mozilla::DebugOnly size_before; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + /* * SPS is the profiling backend used by the JS engine to enable time profiling. * More information can be found in vm/SPSProfiler.{h,cpp}. This class manages @@ -287,232 +308,25 @@ class SPSEntryMarker template class SPSInstrumentation { - /* Because of inline frames, this is a nested structure in a vector */ - struct FrameState { - JSScript *script; // script for this frame, nullptr if not pushed yet - jsbytecode *pc; // pc at which this frame was left for entry into a callee - bool skipNext; // should the next call to reenter be skipped? - int left; // number of leave() calls made without a matching reenter() - }; - SPSProfiler *profiler_; // Instrumentation location management - Vector frames; - FrameState *frame; - - static void clearFrame(FrameState *frame) { - frame->script = nullptr; - frame->pc = nullptr; - frame->skipNext = false; - frame->left = 0; - } - public: /* * Creates instrumentation which writes information out the the specified * profiler's stack and constituent fields. */ - explicit SPSInstrumentation(SPSProfiler *profiler) - : profiler_(profiler), frame(nullptr) - { - enterInlineFrame(nullptr); - } + explicit SPSInstrumentation(SPSProfiler *profiler) : profiler_(profiler) {} /* Small proxies around SPSProfiler */ bool enabled() { return profiler_ && profiler_->enabled(); } SPSProfiler *profiler() { MOZ_ASSERT(enabled()); return profiler_; } void disable() { profiler_ = nullptr; } - - /* Signals an inline function returned, reverting to the previous state */ - void leaveInlineFrame() { - if (!enabled()) - return; - MOZ_ASSERT(frame->left == 0); - MOZ_ASSERT(frame->script != nullptr); - frames.shrinkBy(1); - MOZ_ASSERT(frames.length() > 0); - frame = &frames[frames.length() - 1]; - } - - /* Saves the current state and assumes a fresh one for the inline function */ - bool enterInlineFrame(jsbytecode *callerPC) { - if (!enabled()) - return true; - MOZ_ASSERT_IF(frames.empty(), callerPC == nullptr); - - MOZ_ASSERT_IF(frame != nullptr, frame->script != nullptr); - MOZ_ASSERT_IF(frame != nullptr, frame->left == 1); - if (!frames.empty()) { - MOZ_ASSERT(frame == &frames[frames.length() - 1]); - frame->pc = callerPC; - } - if (!frames.growBy(1)) - return false; - frame = &frames[frames.length() - 1]; - clearFrame(frame); - return true; - } - - /* Prepares the instrumenter state for generating OOL code, by - * setting up the frame state to seem as if there are exactly - * two pushed frames: a frame for the top-level script, and - * a frame for the OOL code being generated. Any - * vm-calls from the OOL code will "leave" the OOL frame and - * return back to it. - */ - bool prepareForOOL() { - if (!enabled()) - return true; - MOZ_ASSERT(!frames.empty()); - if (frames.length() >= 2) { - frames.shrinkBy(frames.length() - 2); - - } else { // frames.length() == 1 - if (!frames.growBy(1)) - return false; - } - frames[0].pc = frames[0].script->code(); - frame = &frames[1]; - clearFrame(frame); - return true; - } - void finishOOL() { - if (!enabled()) - return; - MOZ_ASSERT(!frames.empty()); - frames.shrinkBy(frames.length() - 1); - } - - /* Number of inline frames currently active (doesn't include original one) */ - unsigned inliningDepth() { - return frames.length() - 1; - } - - /* - * When debugging or with slow assertions, sometimes a C++ method will be - * invoked to perform the pop operation from the SPS stack. When we leave - * JIT code, we need to record the current PC, but upon reentering JIT - * code, no update back to nullptr should happen. This method exists to - * flag this behavior. The next leave() will emit instrumentation, but the - * following reenter() will be a no-op. - */ - void skipNextReenter() { - /* If we've left the frame, the reenter will be skipped anyway */ - if (!enabled() || frame->left != 0) - return; - MOZ_ASSERT(frame->script); - MOZ_ASSERT(!frame->skipNext); - frame->skipNext = true; - } - - /* - * In some cases, a frame needs to be flagged as having been pushed, but no - * instrumentation should be emitted. This updates internal state to flag - * that further instrumentation should actually be emitted. - */ - void setPushed(JSScript *script) { - if (!enabled()) - return; - MOZ_ASSERT(frame->left == 0); - frame->script = script; - } - - JSScript *getPushed() { - if (!enabled()) - return nullptr; - return frame->script; - } - - /* - * Flags entry into a JS function for the first time. Before this is called, - * no instrumentation is emitted, but after this instrumentation is emitted. - */ - bool push(JSScript *script, Assembler &masm, Register scratch, bool inlinedFunction = false) { - if (!enabled()) - return true; - if (!inlinedFunction) { - const char *string = profiler_->profileString(script, script->functionNonDelazifying()); - if (string == nullptr) - return false; - masm.spsPushFrame(profiler_, string, script, scratch); - } - setPushed(script); - return true; - } - - /* - * Signifies that C++ performed the push() for this function. C++ always - * sets the current PC to something non-null, however, so as soon as JIT - * code is reentered this updates the current pc to nullptr. - */ - void pushManual(JSScript *script, Assembler &masm, Register scratch, - bool inlinedFunction = false) - { - if (!enabled()) - return; - - if (!inlinedFunction) - masm.spsUpdatePCIdx(profiler_, ProfileEntry::NullPCOffset, scratch); - - setPushed(script); - } - - /* - * Signals that the current function is leaving for a function call. This - * can happen both on JS function calls and also calls to C++. This - * internally manages how many leave() calls have been seen, and only the - * first leave() emits instrumentation. Similarly, only the last - * corresponding reenter() actually emits instrumentation. - */ - void leave(jsbytecode *pc, Assembler &masm, Register scratch, bool inlinedFunction = false) { - if (enabled() && frame->script && frame->left++ == 0) { - jsbytecode *updatePC = pc; - JSScript *script = frame->script; - if (!inlinedFunction) { - // We may be leaving an inlined frame for entry into a C++ frame. - // Use the top script's pc offset instead of the innermost script's. - if (inliningDepth() > 0) { - MOZ_ASSERT(frames[0].pc); - updatePC = frames[0].pc; - script = frames[0].script; - } - } - - if (!inlinedFunction) - masm.spsUpdatePCIdx(profiler_, script->pcToOffset(updatePC), scratch); - } - } - - /* - * Flags that the leaving of the current function has returned. This tracks - * state with leave() to only emit instrumentation at proper times. - */ - void reenter(Assembler &masm, Register scratch, bool inlinedFunction = false) { - if (!enabled() || !frame->script || frame->left-- != 1) - return; - if (frame->skipNext) { - frame->skipNext = false; - } else { - if (!inlinedFunction) - masm.spsUpdatePCIdx(profiler_, ProfileEntry::NullPCOffset, scratch); - } - } - - /* - * Signifies exiting a JS frame, popping the SPS entry. Because there can be - * multiple return sites of a function, this does not cease instrumentation - * emission. - */ - void pop(Assembler &masm, Register scratch, bool inlinedFunction = false) { - if (enabled()) { - MOZ_ASSERT(frame->left == 0); - MOZ_ASSERT(frame->script); - if (!inlinedFunction) - masm.spsPopFrame(profiler_, scratch); - } - } }; + +/* Get a pointer to the top-most profiling frame, given the exit frame pointer. */ +void *GetTopProfilingJitFrame(uint8_t *exitFramePtr); + } /* namespace js */ #endif /* vm_SPSProfiler_h */ diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index d46bf814693..d9b55daa954 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -14,6 +14,7 @@ #include "asmjs/AsmJSModule.h" #include "gc/Marking.h" #include "jit/BaselineFrame.h" +#include "jit/JitcodeMap.h" #include "jit/JitCompartment.h" #include "js/GCAPI.h" #include "vm/Opcodes.h" @@ -1373,7 +1374,8 @@ AbstractFramePtr::hasPushedSPSFrame() const { if (isInterpreterFrame()) return asInterpreterFrame()->hasPushedSPSFrame(); - return asBaselineFrame()->hasPushedSPSFrame(); + MOZ_ASSERT(isBaselineFrame()); + return false; } jit::JitActivation::JitActivation(JSContext *cx, bool active) @@ -1381,23 +1383,34 @@ jit::JitActivation::JitActivation(JSContext *cx, bool active) active_(active), rematerializedFrames_(nullptr), ionRecovery_(cx), - bailoutData_(nullptr) + bailoutData_(nullptr), + lastProfilingFrame_(nullptr), + lastProfilingCallSite_(nullptr) { if (active) { prevJitTop_ = cx->mainThread().jitTop; prevJitJSContext_ = cx->mainThread().jitJSContext; + prevJitActivation_ = cx->mainThread().jitActivation; cx->mainThread().jitJSContext = cx; + cx->mainThread().jitActivation = this; + + registerProfiling(); } else { prevJitTop_ = nullptr; prevJitJSContext_ = nullptr; + prevJitActivation_ = nullptr; } } jit::JitActivation::~JitActivation() { if (active_) { + if (isProfiling()) + unregisterProfiling(); + cx_->perThreadData->jitTop = prevJitTop_; cx_->perThreadData->jitJSContext = prevJitJSContext_; + cx_->perThreadData->jitActivation = prevJitActivation_; } // All reocvered value are taken from activation during the bailout. @@ -1411,6 +1424,13 @@ jit::JitActivation::~JitActivation() js_delete(rematerializedFrames_); } +bool +jit::JitActivation::isProfiling() const +{ + // All JitActivations can be profiled. + return true; +} + void jit::JitActivation::setBailoutData(jit::BailoutFrameInfo *bailoutData) { @@ -1435,15 +1455,25 @@ jit::JitActivation::setActive(JSContext *cx, bool active) // (Not tested and will probably fail in other situations.) MOZ_ASSERT(cx->mainThread().activation_ == this); MOZ_ASSERT(active != active_); - active_ = active; if (active) { + *((volatile bool *) active_) = true; prevJitTop_ = cx->mainThread().jitTop; prevJitJSContext_ = cx->mainThread().jitJSContext; + prevJitActivation_ = cx->mainThread().jitActivation; cx->mainThread().jitJSContext = cx; + cx->mainThread().jitActivation = this; + + registerProfiling(); + } else { + unregisterProfiling(); + cx->mainThread().jitTop = prevJitTop_; cx->mainThread().jitJSContext = prevJitJSContext_; + cx->mainThread().jitActivation = prevJitActivation_; + + *((volatile bool *) active_) = false; } } @@ -1590,10 +1620,8 @@ AsmJSActivation::AsmJSActivation(JSContext *cx, AsmJSModule &module) // NB: this is a hack and can be removed once Ion switches over to // JS::ProfilingFrameIterator. - if (cx->runtime()->spsProfiler.enabled()) { + if (cx->runtime()->spsProfiler.enabled()) profiler_ = &cx->runtime()->spsProfiler; - profiler_->enterAsmJS("asm.js code :0", this); - } prevAsmJSForModule_ = module.activation(); module.activation() = this; @@ -1611,9 +1639,6 @@ AsmJSActivation::~AsmJSActivation() // Hide this activation from the profiler before is is destroyed. unregisterProfiling(); - if (profiler_) - profiler_->exitAsmJS(); - MOZ_ASSERT(fp_ == nullptr); MOZ_ASSERT(module_.activation() == this); @@ -1653,7 +1678,13 @@ Activation::unregisterProfiling() { MOZ_ASSERT(isProfiling()); MOZ_ASSERT(cx_->perThreadData->profilingActivation_ == this); - cx_->perThreadData->profilingActivation_ = prevProfiling_; + + // There may be a non-active jit activation in the linked list. Skip past it. + Activation *prevProfiling = prevProfiling_; + while (prevProfiling && prevProfiling->isJit() && !prevProfiling->asJit()->isActive()) + prevProfiling = prevProfiling->prevProfiling_; + + cx_->perThreadData->profilingActivation_ = prevProfiling; } ActivationIterator::ActivationIterator(JSRuntime *rt) @@ -1691,14 +1722,24 @@ ActivationIterator::settle() } JS::ProfilingFrameIterator::ProfilingFrameIterator(JSRuntime *rt, const RegisterState &state) - : activation_(rt->mainThread.profilingActivation()) + : rt_(rt), + activation_(rt->mainThread.profilingActivation()), + savedPrevJitTop_(nullptr) { if (!activation_) return; + // If profiler sampling is not enabled, skip. + if (!rt_->isProfilerSamplingEnabled()) { + activation_ = nullptr; + return; + } + MOZ_ASSERT(activation_->isProfiling()); - static_assert(sizeof(AsmJSProfilingFrameIterator) <= StorageSpace, "Need to increase storage"); + static_assert(sizeof(AsmJSProfilingFrameIterator) <= StorageSpace && + sizeof(jit::JitProfilingFrameIterator) <= StorageSpace, + "Need to increase storage"); iteratorConstruct(state); settle(); @@ -1716,9 +1757,15 @@ void JS::ProfilingFrameIterator::operator++() { MOZ_ASSERT(!done()); + MOZ_ASSERT(activation_->isAsmJS() || activation_->isJit()); - MOZ_ASSERT(activation_->isAsmJS()); - ++asmJSIter(); + if (activation_->isAsmJS()) { + ++asmJSIter(); + settle(); + return; + } + + ++jitIter(); settle(); } @@ -1728,6 +1775,11 @@ JS::ProfilingFrameIterator::settle() while (iteratorDone()) { iteratorDestroy(); activation_ = activation_->prevProfiling(); + + // Skip past any non-active jit activations in the list. + while (activation_ && activation_->isJit() && !activation_->asJit()->isActive()) + activation_ = activation_->prevProfiling(); + if (!activation_) return; iteratorConstruct(); @@ -1738,52 +1790,134 @@ void JS::ProfilingFrameIterator::iteratorConstruct(const RegisterState &state) { MOZ_ASSERT(!done()); + MOZ_ASSERT(activation_->isAsmJS() || activation_->isJit()); - MOZ_ASSERT(activation_->isAsmJS()); - new (storage_.addr()) AsmJSProfilingFrameIterator(*activation_->asAsmJS(), state); + if (activation_->isAsmJS()) { + new (storage_.addr()) AsmJSProfilingFrameIterator(*activation_->asAsmJS(), state); + // Set savedPrevJitTop_ to the actual jitTop_ from the runtime. + savedPrevJitTop_ = activation_->cx()->perThreadData->jitTop; + return; + } + + MOZ_ASSERT(activation_->asJit()->isActive()); + new (storage_.addr()) jit::JitProfilingFrameIterator(rt_, state); } void JS::ProfilingFrameIterator::iteratorConstruct() { MOZ_ASSERT(!done()); + MOZ_ASSERT(activation_->isAsmJS() || activation_->isJit()); - MOZ_ASSERT(activation_->isAsmJS()); - new (storage_.addr()) AsmJSProfilingFrameIterator(*activation_->asAsmJS()); + if (activation_->isAsmJS()) { + new (storage_.addr()) AsmJSProfilingFrameIterator(*activation_->asAsmJS()); + return; + } + + MOZ_ASSERT(activation_->asJit()->isActive()); + MOZ_ASSERT(savedPrevJitTop_ != nullptr); + new (storage_.addr()) jit::JitProfilingFrameIterator(savedPrevJitTop_); } void JS::ProfilingFrameIterator::iteratorDestroy() { MOZ_ASSERT(!done()); + MOZ_ASSERT(activation_->isAsmJS() || activation_->isJit()); - MOZ_ASSERT(activation_->isAsmJS()); - asmJSIter().~AsmJSProfilingFrameIterator(); + if (activation_->isAsmJS()) { + asmJSIter().~AsmJSProfilingFrameIterator(); + return; + } + + // Save prevjitTop for later use + savedPrevJitTop_ = activation_->asJit()->prevJitTop(); + jitIter().~JitProfilingFrameIterator(); } bool JS::ProfilingFrameIterator::iteratorDone() { MOZ_ASSERT(!done()); + MOZ_ASSERT(activation_->isAsmJS() || activation_->isJit()); - MOZ_ASSERT(activation_->isAsmJS()); - return asmJSIter().done(); + if (activation_->isAsmJS()) + return asmJSIter().done(); + + return jitIter().done(); } void * JS::ProfilingFrameIterator::stackAddress() const { MOZ_ASSERT(!done()); + MOZ_ASSERT(activation_->isAsmJS() || activation_->isJit()); - MOZ_ASSERT(activation_->isAsmJS()); - return asmJSIter().stackAddress(); + if (activation_->isAsmJS()) + return asmJSIter().stackAddress(); + + return jitIter().stackAddress(); } -const char * -JS::ProfilingFrameIterator::label() const +uint32_t +JS::ProfilingFrameIterator::extractStack(Frame *frames, uint32_t offset, uint32_t end) const +{ + if (offset >= end) + return 0; + + void *stackAddr = stackAddress(); + + if (isAsmJS()) { + frames[offset].kind = Frame_AsmJS; + frames[offset].stackAddress = stackAddr; + frames[offset].returnAddress = nullptr; + frames[offset].activation = activation_; + frames[offset].label = asmJSIter().label(); + return 1; + } + + MOZ_ASSERT(isJit()); + void *returnAddr = jitIter().returnAddressToFp(); + + // Look up an entry for the return address. + jit::JitcodeGlobalTable *table = rt_->jitRuntime()->getJitcodeGlobalTable(); + jit::JitcodeGlobalEntry entry; + mozilla::DebugOnly result = table->lookup(returnAddr, &entry, rt_); + MOZ_ASSERT(result); + + MOZ_ASSERT(entry.isIon() || entry.isIonCache() || entry.isBaseline() || entry.isDummy()); + + // Dummy frames produce no stack frames. + if (entry.isDummy()) + return 0; + + FrameKind kind = entry.isBaseline() ? Frame_Baseline : Frame_Ion; + + // Extract the stack for the entry. Assume maximum inlining depth is <64 + const char *labels[64]; + uint32_t depth = entry.callStackAtAddr(rt_, returnAddr, labels, 64); + MOZ_ASSERT(depth < 64); + for (uint32_t i = 0; i < depth; i++) { + if (offset + i >= end) + return i; + frames[offset + i].kind = kind; + frames[offset + i].stackAddress = stackAddr; + frames[offset + i].returnAddress = returnAddr; + frames[offset + i].activation = activation_; + frames[offset + i].label = labels[i]; + } + return depth; +} + +bool +JS::ProfilingFrameIterator::isAsmJS() const { MOZ_ASSERT(!done()); - - MOZ_ASSERT(activation_->isAsmJS()); - return asmJSIter().label(); + return activation_->isAsmJS(); +} + +bool +JS::ProfilingFrameIterator::isJit() const +{ + return activation_->isJit(); } diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index 64633c911a1..da18a391177 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -7,6 +7,7 @@ #ifndef vm_Stack_h #define vm_Stack_h +#include "mozilla/Atomics.h" #include "mozilla/MemoryReporting.h" #include "jsfun.h" @@ -1130,6 +1131,10 @@ class Activation return hideScriptedCallerCount_ > 0; } + static size_t offsetOfPrevProfiling() { + return offsetof(Activation, prevProfiling_); + } + private: Activation(const Activation &other) = delete; void operator=(const Activation &other) = delete; @@ -1242,6 +1247,7 @@ class BailoutFrameInfo; class JitActivation : public Activation { uint8_t *prevJitTop_; + JitActivation *prevJitActivation_; JSContext *prevJitJSContext_; bool active_; @@ -1271,6 +1277,14 @@ class JitActivation : public Activation // reading it from the stack. BailoutFrameInfo *bailoutData_; + // When profiling is enabled, these fields will be updated to reflect the + // last pushed frame for this activation, and if that frame has been + // left for a call, the native code site of the call. + mozilla::Atomic lastProfilingFrame_; + mozilla::Atomic lastProfilingCallSite_; + static_assert(sizeof(mozilla::Atomic) == sizeof(void *), + "Atomic should have same memory format as underlying type."); + void clearRematerializedFrames(); #ifdef CHECK_OSIPOINT_REGISTERS @@ -1290,9 +1304,7 @@ class JitActivation : public Activation } void setActive(JSContext *cx, bool active = true); - bool isProfiling() const { - return false; - } + bool isProfiling() const; uint8_t *prevJitTop() const { return prevJitTop_; @@ -1303,6 +1315,9 @@ class JitActivation : public Activation static size_t offsetOfPrevJitJSContext() { return offsetof(JitActivation, prevJitJSContext_); } + static size_t offsetOfPrevJitActivation() { + return offsetof(JitActivation, prevJitActivation_); + } static size_t offsetOfActiveUint8() { MOZ_ASSERT(sizeof(bool) == 1); return offsetof(JitActivation, active_); @@ -1363,6 +1378,26 @@ class JitActivation : public Activation // Unregister the bailout data when the frame is reconstructed. void cleanBailoutData(); + + static size_t offsetOfLastProfilingFrame() { + return offsetof(JitActivation, lastProfilingFrame_); + } + void *lastProfilingFrame() { + return lastProfilingFrame_; + } + void setLastProfilingFrame(void *ptr) { + lastProfilingFrame_ = ptr; + } + + static size_t offsetOfLastProfilingCallSite() { + return offsetof(JitActivation, lastProfilingCallSite_); + } + void *lastProfilingCallSite() { + return lastProfilingCallSite_; + } + void setLastProfilingCallSite(void *ptr) { + lastProfilingCallSite_ = ptr; + } }; // A filtering of the ActivationIterator to only stop at JitActivations. diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 8aaa36fd421..41e6fd5bf98 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -769,9 +769,9 @@ nsDisplayScrollLayer::ComputeFrameMetrics(nsIFrame* aForFrame, // Only the root scrollable frame for a given presShell should pick up // the presShell's resolution. All the other frames are 1.0. if (aScrollFrame == presShell->GetRootScrollFrame()) { - metrics.mPresShellResolution = presShell->GetXResolution(); + metrics.SetPresShellResolution(presShell->GetXResolution()); } else { - metrics.mPresShellResolution = 1.0f; + metrics.SetPresShellResolution(1.0f); } // The cumulative resolution is the resolution at which the scroll frame's // content is actually rendered. It includes the pres shell resolutions of diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 1f64bc2f2ba..a474eff5d9a 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -2818,7 +2818,7 @@ CalculateFrameMetricsForDisplayPort(nsIScrollableFrame* aScrollFrame) { LayerToParentLayerScale layerToParentLayerScale(1.0f); metrics.SetDevPixelsPerCSSPixel(deviceScale); - metrics.mPresShellResolution = resolution; + metrics.SetPresShellResolution(resolution); metrics.SetCumulativeResolution(cumulativeResolution); metrics.SetZoom(deviceScale * cumulativeResolution * layerToParentLayerScale); diff --git a/layout/reftests/svg/as-image/reftest.list b/layout/reftests/svg/as-image/reftest.list index 8809159753f..c2534abb668 100644 --- a/layout/reftests/svg/as-image/reftest.list +++ b/layout/reftests/svg/as-image/reftest.list @@ -127,7 +127,7 @@ skip-if(B2G) == img-fragment-2a.html img-fragment-2-ref.html # bug 773482 skip-if(B2G) == img-fragment-2b.html img-fragment-2-ref.html # bug 773482 skip-if(B2G) == img-fragment-2c.html img-fragment-2-ref.html # bug 773482 -fuzzy-if(B2G,68,4) == list-simple-1.html list-simple-1-ref.html +fuzzy-if(B2G,68,4) random-if(winWidget||cocoaWidget) == list-simple-1.html list-simple-1-ref.html == svg-image-simple-1.svg lime100x100.svg == svg-image-simple-2.svg lime100x100.svg diff --git a/media/libstagefright/binding/mp4_demuxer.cpp b/media/libstagefright/binding/mp4_demuxer.cpp index d2505d62707..e77e01c026e 100644 --- a/media/libstagefright/binding/mp4_demuxer.cpp +++ b/media/libstagefright/binding/mp4_demuxer.cpp @@ -144,6 +144,13 @@ MP4Demuxer::Init() sp metaData = e->getMetaData(); mCrypto.Update(metaData); + int64_t movieDuration; + if (!mVideoConfig.duration && !mAudioConfig.duration && + metaData->findInt64(kKeyMovieDuration, &movieDuration)) { + // No duration were found in either tracks, use movie extend header box one. + mVideoConfig.duration = mAudioConfig.duration = movieDuration; + } + return mPrivate->mAudio.get() || mPrivate->mVideo.get(); } diff --git a/media/libstagefright/frameworks/av/include/media/stagefright/MetaData.h b/media/libstagefright/frameworks/av/include/media/stagefright/MetaData.h index 9c78a6948b3..30d969d29fd 100644 --- a/media/libstagefright/frameworks/av/include/media/stagefright/MetaData.h +++ b/media/libstagefright/frameworks/av/include/media/stagefright/MetaData.h @@ -67,6 +67,7 @@ enum { kKeyDriftTime = 'dftT', // int64_t (usecs) kKeyAnchorTime = 'ancT', // int64_t (usecs) kKeyDuration = 'dura', // int64_t (usecs) + kKeyMovieDuration = 'mdur', // int64_t (usecs) kKeyColorFormat = 'colf', kKeyPlatformPrivate = 'priv', // pointer kKeyDecoderComponent = 'decC', // cstring diff --git a/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp index 06ebd1b4fec..34adcf21b62 100644 --- a/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/frameworks/av/media/libstagefright/MPEG4Extractor.cpp @@ -1697,6 +1697,46 @@ status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) { break; } + case FOURCC('m', 'e', 'h', 'd'): + { + if (chunk_data_size < 8) { + return ERROR_MALFORMED; + } + + uint8_t version; + if (mDataSource->readAt( + data_offset, &version, sizeof(version)) + < (ssize_t)sizeof(version)) { + return ERROR_IO; + } + if (version > 1) { + break; + } + int64_t duration = 0; + if (version == 1) { + if (mDataSource->readAt( + data_offset + 4, &duration, sizeof(duration)) + < (ssize_t)sizeof(duration)) { + return ERROR_IO; + } + duration = ntoh64(duration); + } else { + uint32_t duration32; + if (mDataSource->readAt( + data_offset + 4, &duration32, sizeof(duration32)) + < (ssize_t)sizeof(duration32)) { + return ERROR_IO; + } + duration = ntohl(duration32); + } + if (duration) { + mFileMetaData->setInt64(kKeyMovieDuration, duration * 1000LL); + } + + *offset += chunk_size; + break; + } + case FOURCC('m', 'd', 'a', 't'): { ALOGV("mdat chunk, drm: %d", mIsDrm); diff --git a/mfbt/DebugOnly.h b/mfbt/DebugOnly.h index 5d0197b194e..0243c3f399f 100644 --- a/mfbt/DebugOnly.h +++ b/mfbt/DebugOnly.h @@ -51,6 +51,10 @@ public: void operator++(int) { value++; } void operator--(int) { value--; } + // Do not define operator+=() or operator-=() here. These will coerce via + // the implicit cast and built-in operators. Defining explicit methods here + // will create ambiguity the compiler can't deal with. + T* operator&() { return &value; } operator T&() { return value; } @@ -66,6 +70,8 @@ public: DebugOnly& operator=(const T&) { return *this; } void operator++(int) { } void operator--(int) { } + DebugOnly& operator+=(const T&) { return *this; } + DebugOnly& operator-=(const T&) { return *this; } #endif /* diff --git a/mobile/android/base/resources/drawable-hdpi/ab_background.9.png b/mobile/android/base/resources/drawable-hdpi/ab_background.9.png new file mode 100644 index 00000000000..eb6f5f653e7 Binary files /dev/null and b/mobile/android/base/resources/drawable-hdpi/ab_background.9.png differ diff --git a/mobile/android/base/resources/drawable-hdpi/ab_stacked_transparent_light_holo.9.png b/mobile/android/base/resources/drawable-hdpi/ab_stacked_transparent_light_holo.9.png deleted file mode 100644 index 684d6f3e705..00000000000 Binary files a/mobile/android/base/resources/drawable-hdpi/ab_stacked_transparent_light_holo.9.png and /dev/null differ diff --git a/mobile/android/base/resources/drawable-mdpi/ab_background.9.png b/mobile/android/base/resources/drawable-mdpi/ab_background.9.png new file mode 100644 index 00000000000..bad01d12080 Binary files /dev/null and b/mobile/android/base/resources/drawable-mdpi/ab_background.9.png differ diff --git a/mobile/android/base/resources/drawable-mdpi/ab_stacked_transparent_light_holo.9.png b/mobile/android/base/resources/drawable-mdpi/ab_stacked_transparent_light_holo.9.png deleted file mode 100644 index 0b82d97360f..00000000000 Binary files a/mobile/android/base/resources/drawable-mdpi/ab_stacked_transparent_light_holo.9.png and /dev/null differ diff --git a/mobile/android/base/resources/drawable-xhdpi/ab_background.9.png b/mobile/android/base/resources/drawable-xhdpi/ab_background.9.png new file mode 100644 index 00000000000..4ab8b40a191 Binary files /dev/null and b/mobile/android/base/resources/drawable-xhdpi/ab_background.9.png differ diff --git a/mobile/android/base/resources/drawable-xhdpi/ab_stacked_transparent_light_holo.9.png b/mobile/android/base/resources/drawable-xhdpi/ab_stacked_transparent_light_holo.9.png deleted file mode 100644 index 8d0e77b75ac..00000000000 Binary files a/mobile/android/base/resources/drawable-xhdpi/ab_stacked_transparent_light_holo.9.png and /dev/null differ diff --git a/mobile/android/base/resources/values-v11/styles.xml b/mobile/android/base/resources/values-v11/styles.xml index 8a99ada2a55..8dedd1b5404 100644 --- a/mobile/android/base/resources/values-v11/styles.xml +++ b/mobile/android/base/resources/values-v11/styles.xml @@ -77,7 +77,7 @@