зеркало из https://github.com/mozilla/gecko-dev.git
Merge inbound to central, a=merge
MozReview-Commit-ID: FNYBhhHr4dg --HG-- rename : netwerk/base/security-prefs.js => security/manager/ssl/security-prefs.js
This commit is contained in:
Коммит
4062cb56a0
|
@ -545,6 +545,7 @@ DocAccessibleParent::SetCOMProxy(const RefPtr<IAccessible>& aCOMProxy)
|
|||
IAccessibleHolder::COMPtrType ptr(rawNative);
|
||||
IAccessibleHolder holder(Move(ptr));
|
||||
|
||||
IAccessibleHolder hWndAccHolder;
|
||||
if (nsWinUtils::IsWindowEmulationStarted()) {
|
||||
RootAccessible* rootDocument = outerDoc->RootAccessible();
|
||||
MOZ_ASSERT(rootDocument);
|
||||
|
@ -568,10 +569,16 @@ DocAccessibleParent::SetCOMProxy(const RefPtr<IAccessible>& aCOMProxy)
|
|||
// Attach accessible document to the emulated native window
|
||||
::SetPropW(hWnd, kPropNameDocAccParent, (HANDLE)this);
|
||||
SetEmulatedWindowHandle(hWnd);
|
||||
IAccessible* rawHWNDAcc = nullptr;
|
||||
if (SUCCEEDED(::AccessibleObjectFromWindow(hWnd, OBJID_WINDOW,
|
||||
IID_IAccessible,
|
||||
(void**)&rawHWNDAcc))) {
|
||||
hWndAccHolder.Set(IAccessibleHolder::COMPtrType(rawHWNDAcc));
|
||||
}
|
||||
}
|
||||
}
|
||||
Unused << SendParentCOMProxy(holder, reinterpret_cast<uintptr_t>(
|
||||
mEmulatedWindowHandle));
|
||||
mEmulatedWindowHandle), hWndAccHolder);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -47,12 +47,18 @@ DocAccessibleChild::Shutdown()
|
|||
|
||||
ipc::IPCResult
|
||||
DocAccessibleChild::RecvParentCOMProxy(const IAccessibleHolder& aParentCOMProxy,
|
||||
const WindowsHandle& aEmulatedWindowHandle)
|
||||
const WindowsHandle& aEmulatedWindowHandle,
|
||||
const IAccessibleHolder& aEmulatedWindowCOMProxy)
|
||||
{
|
||||
MOZ_ASSERT(!mParentProxy && !aParentCOMProxy.IsNull());
|
||||
mParentProxy.reset(const_cast<IAccessibleHolder&>(aParentCOMProxy).Release());
|
||||
SetConstructedInParentProcess();
|
||||
mEmulatedWindowHandle = reinterpret_cast<HWND>(aEmulatedWindowHandle);
|
||||
if (!aEmulatedWindowCOMProxy.IsNull()) {
|
||||
MOZ_ASSERT(!mEmulatedWindowProxy);
|
||||
mEmulatedWindowProxy.reset(
|
||||
const_cast<IAccessibleHolder&>(aEmulatedWindowCOMProxy).Release());
|
||||
}
|
||||
|
||||
for (uint32_t i = 0, l = mDeferredEvents.Length(); i < l; ++i) {
|
||||
mDeferredEvents[i]->Dispatch();
|
||||
|
|
|
@ -29,9 +29,11 @@ public:
|
|||
|
||||
virtual ipc::IPCResult
|
||||
RecvParentCOMProxy(const IAccessibleHolder& aParentCOMProxy,
|
||||
const WindowsHandle& aEmulatedWindowHandle) override;
|
||||
const WindowsHandle& aEmulatedWindowHandle,
|
||||
const IAccessibleHolder& aEmulatedWindowCOMProxy) override;
|
||||
|
||||
HWND GetEmulatedWindowHandle() const { return mEmulatedWindowHandle; }
|
||||
IAccessible* GetEmulatedWindowIAccessible() const { return mEmulatedWindowProxy.get(); }
|
||||
|
||||
IAccessible* GetParentIAccessible() const { return mParentProxy.get(); }
|
||||
|
||||
|
@ -312,6 +314,7 @@ private:
|
|||
|
||||
bool mIsRemoteConstructed;
|
||||
mscom::ProxyUniquePtr<IAccessible> mParentProxy;
|
||||
mscom::ProxyUniquePtr<IAccessible> mEmulatedWindowProxy;
|
||||
nsTArray<UniquePtr<DeferredEvent>> mDeferredEvents;
|
||||
HWND mEmulatedWindowHandle;
|
||||
};
|
||||
|
|
|
@ -67,7 +67,8 @@ parent:
|
|||
|
||||
child:
|
||||
async ParentCOMProxy(IAccessibleHolder aParentCOMProxy,
|
||||
WindowsHandle aEmulatedWindowHandle);
|
||||
WindowsHandle aEmulatedWindowHandle,
|
||||
IAccessibleHolder aEmulatedWindowCOMProxy);
|
||||
|
||||
async __delete__();
|
||||
};
|
||||
|
|
|
@ -53,7 +53,14 @@ DocAccessibleWrap::get_accParent(
|
|||
if (!ipcDoc) {
|
||||
return DocAccessible::get_accParent(ppdispParent);
|
||||
}
|
||||
IAccessible* dispParent = ipcDoc->GetParentIAccessible();
|
||||
|
||||
// Emulated window proxy is only set for the top level content document when
|
||||
// emulation is enabled.
|
||||
IAccessible* dispParent = ipcDoc->GetEmulatedWindowIAccessible();
|
||||
if (!dispParent) {
|
||||
dispParent = ipcDoc->GetParentIAccessible();
|
||||
}
|
||||
|
||||
if (!dispParent) {
|
||||
return S_FALSE;
|
||||
}
|
||||
|
|
|
@ -151,9 +151,16 @@ XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function() {
|
|||
let tmp = {};
|
||||
Cu.import("resource://gre/modules/PopupNotifications.jsm", tmp);
|
||||
try {
|
||||
// Hide all notifications while the URL is being edited and the address bar
|
||||
// has focus, including the virtual focus in the results popup.
|
||||
let shouldSuppress = () => {
|
||||
return gURLBar.getAttribute("pageproxystate") != "valid" &&
|
||||
gURLBar.focused;
|
||||
};
|
||||
return new tmp.PopupNotifications(gBrowser,
|
||||
document.getElementById("notification-popup"),
|
||||
document.getElementById("notification-popup-box"));
|
||||
document.getElementById("notification-popup-box"),
|
||||
{ shouldSuppress });
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
return null;
|
||||
|
@ -2441,17 +2448,8 @@ function BrowserPageInfo(documentURL, initialTab, imageElement, frameOuterWindow
|
|||
*
|
||||
* @param aURI [optional]
|
||||
* nsIURI to set. If this is unspecified, the current URI will be used.
|
||||
* @param aOptions [optional]
|
||||
* An object with the following properties:
|
||||
* {
|
||||
* isForLocationChange:
|
||||
* Set to true to indicate that the function was invoked to respond
|
||||
* to a location change event, rather than to reset the current URI
|
||||
* value. This is useful to avoid calling PopupNotifications.jsm
|
||||
* multiple times.
|
||||
* }
|
||||
*/
|
||||
function URLBarSetURI(aURI, aOptions = {}) {
|
||||
function URLBarSetURI(aURI) {
|
||||
var value = gBrowser.userTypedValue;
|
||||
var valid = false;
|
||||
|
||||
|
@ -2484,7 +2482,7 @@ function URLBarSetURI(aURI, aOptions = {}) {
|
|||
|
||||
gURLBar.value = value;
|
||||
gURLBar.valueIsTyped = !valid;
|
||||
SetPageProxyState(valid ? "valid" : "invalid", aOptions);
|
||||
SetPageProxyState(valid ? "valid" : "invalid");
|
||||
}
|
||||
|
||||
function losslessDecodeURI(aURI) {
|
||||
|
@ -2591,19 +2589,15 @@ function UpdatePageProxyState() {
|
|||
* related user interface elments should be shown because the URI in the
|
||||
* location bar matches the loaded page. The string "invalid" indicates
|
||||
* that the URI in the location bar is different than the loaded page.
|
||||
* @param aOptions [optional]
|
||||
* An object with the following properties:
|
||||
* {
|
||||
* isForLocationChange:
|
||||
* Set to true to indicate that the function was invoked to respond
|
||||
* to a location change event. This is useful to avoid calling
|
||||
* PopupNotifications.jsm multiple times.
|
||||
* }
|
||||
*/
|
||||
function SetPageProxyState(aState, aOptions = {}) {
|
||||
function SetPageProxyState(aState) {
|
||||
if (!gURLBar)
|
||||
return;
|
||||
|
||||
let oldPageProxyState = gURLBar.getAttribute("pageproxystate");
|
||||
// The "browser_urlbar_stop_pending.js" test uses a MutationObserver to do
|
||||
// some verifications at this point, and it breaks if we don't write the
|
||||
// attribute, even if it hasn't changed (bug 1338115).
|
||||
gURLBar.setAttribute("pageproxystate", aState);
|
||||
|
||||
// the page proxy state is set to valid via OnLocationChange, which
|
||||
|
@ -2615,14 +2609,26 @@ function SetPageProxyState(aState, aOptions = {}) {
|
|||
gURLBar.removeEventListener("input", UpdatePageProxyState);
|
||||
}
|
||||
|
||||
// Only need to call anchorVisibilityChange if the PopupNotifications object
|
||||
// for this window has already been initialized (i.e. its getter no
|
||||
// longer exists). If this is the result of a locations change, then we will
|
||||
// already invoke PopupNotifications.locationChange separately.
|
||||
if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get &&
|
||||
!aOptions.isForLocationChange) {
|
||||
PopupNotifications.anchorVisibilityChange();
|
||||
// After we've ensured that we've applied the listeners and updated the value
|
||||
// of gLastValidURLStr, return early if the actual state hasn't changed.
|
||||
if (oldPageProxyState == aState) {
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePopupNotificationsVisibility();
|
||||
}
|
||||
|
||||
function UpdatePopupNotificationsVisibility() {
|
||||
// Only need to do something if the PopupNotifications object for this window
|
||||
// has already been initialized (i.e. its getter no longer exists).
|
||||
if (Object.getOwnPropertyDescriptor(window, "PopupNotifications").get) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify PopupNotifications that the visible anchors may have changed. This
|
||||
// also checks the suppression state according to the "shouldSuppress"
|
||||
// function defined earlier in this file.
|
||||
PopupNotifications.anchorVisibilityChange();
|
||||
}
|
||||
|
||||
function PageProxyClickHandler(aEvent) {
|
||||
|
@ -4541,7 +4547,7 @@ var XULBrowserWindow = {
|
|||
this.reloadCommand.removeAttribute("disabled");
|
||||
}
|
||||
|
||||
URLBarSetURI(aLocationURI, { isForLocationChange: true });
|
||||
URLBarSetURI(aLocationURI);
|
||||
|
||||
BookmarkingUI.onLocationChange();
|
||||
|
||||
|
|
|
@ -102,49 +102,126 @@ var tests = [
|
|||
gBrowser.selectedTab = this.oldSelectedTab;
|
||||
}
|
||||
},
|
||||
// Test that popupnotifications are anchored to the identity icon while
|
||||
// editing the URL in the location bar, and restored to their anchors when the
|
||||
// URL is reverted.
|
||||
// Test that popupnotifications are hidden while editing the URL in the
|
||||
// location bar, anchored to the identity icon when the focus is moved away
|
||||
// from the location bar, and restored when the URL is reverted.
|
||||
{ id: "Test#4",
|
||||
*run() {
|
||||
this.oldSelectedTab = gBrowser.selectedTab;
|
||||
yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
|
||||
for (let persistent of [false, true]) {
|
||||
let shown = waitForNotificationPanel();
|
||||
this.notifyObj = new BasicNotification(this.id);
|
||||
this.notifyObj.anchorID = "geo-notification-icon";
|
||||
this.notifyObj.addOptions({ persistent });
|
||||
this.notification = showNotification(this.notifyObj);
|
||||
yield shown;
|
||||
|
||||
let shownInitially = waitForNotificationPanel();
|
||||
checkPopup(PopupNotifications.panel, this.notifyObj);
|
||||
|
||||
// Typing in the location bar should hide the notification.
|
||||
let hidden = waitForNotificationPanelHidden();
|
||||
gURLBar.select();
|
||||
EventUtils.synthesizeKey("*", {});
|
||||
yield hidden;
|
||||
|
||||
is(document.getElementById("geo-notification-icon").boxObject.width, 0,
|
||||
"geo anchor shouldn't be visible");
|
||||
|
||||
// Moving focus to the next control should show the notifications again,
|
||||
// anchored to the identity icon. We clear the URL bar before moving the
|
||||
// focus so that the awesomebar popup doesn't get in the way.
|
||||
shown = waitForNotificationPanel();
|
||||
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
yield shown;
|
||||
|
||||
is(PopupNotifications.panel.anchorNode.id, "identity-icon",
|
||||
"notification anchored to identity icon");
|
||||
|
||||
// Moving focus to the location bar should hide the notification again.
|
||||
hidden = waitForNotificationPanelHidden();
|
||||
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
|
||||
yield hidden;
|
||||
|
||||
// Reverting the URL should show the notification again.
|
||||
shown = waitForNotificationPanel();
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
yield shown;
|
||||
|
||||
checkPopup(PopupNotifications.panel, this.notifyObj);
|
||||
|
||||
hidden = waitForNotificationPanelHidden();
|
||||
this.notification.remove();
|
||||
yield hidden;
|
||||
}
|
||||
goNext();
|
||||
}
|
||||
},
|
||||
// Test that popupnotifications triggered while editing the URL in the
|
||||
// location bar are only shown later when the URL is reverted.
|
||||
{ id: "Test#5",
|
||||
*run() {
|
||||
for (let persistent of [false, true]) {
|
||||
// Start editing the URL, ensuring that the awesomebar popup is hidden.
|
||||
gURLBar.select();
|
||||
EventUtils.synthesizeKey("*", {});
|
||||
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
|
||||
|
||||
// Trying to show a notification should display nothing.
|
||||
let notShowing = promiseTopicObserved("PopupNotifications-updateNotShowing");
|
||||
this.notifyObj = new BasicNotification(this.id);
|
||||
this.notifyObj.anchorID = "geo-notification-icon";
|
||||
this.notifyObj.addOptions({ persistent });
|
||||
this.notification = showNotification(this.notifyObj);
|
||||
yield notShowing;
|
||||
|
||||
// Reverting the URL should show the notification.
|
||||
let shown = waitForNotificationPanel();
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
yield shown;
|
||||
|
||||
checkPopup(PopupNotifications.panel, this.notifyObj);
|
||||
|
||||
let hidden = waitForNotificationPanelHidden();
|
||||
this.notification.remove();
|
||||
yield hidden;
|
||||
}
|
||||
|
||||
goNext();
|
||||
}
|
||||
},
|
||||
// Test that persistent panels are still open after switching to another tab
|
||||
// and back, even while editing the URL in the new tab.
|
||||
{ id: "Test#6",
|
||||
*run() {
|
||||
let shown = waitForNotificationPanel();
|
||||
this.notifyObj = new BasicNotification(this.id);
|
||||
this.notifyObj.anchorID = "geo-notification-icon";
|
||||
this.notifyObj.addOptions({
|
||||
persistent: true,
|
||||
});
|
||||
this.notification = showNotification(this.notifyObj);
|
||||
yield shownInitially;
|
||||
yield shown;
|
||||
|
||||
checkPopup(PopupNotifications.panel, this.notifyObj);
|
||||
// Switching to a new tab should hide the notification.
|
||||
let hidden = waitForNotificationPanelHidden();
|
||||
this.oldSelectedTab = gBrowser.selectedTab;
|
||||
yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
|
||||
yield hidden;
|
||||
|
||||
let shownAgain = waitForNotificationPanel();
|
||||
// This will cause the popup to hide and show again.
|
||||
// Start editing the URL.
|
||||
gURLBar.select();
|
||||
EventUtils.synthesizeKey("*", {});
|
||||
// Keep the URL bar empty, so we don't show the awesomebar.
|
||||
EventUtils.synthesizeKey("VK_BACK_SPACE", {});
|
||||
yield shownAgain;
|
||||
|
||||
is(document.getElementById("geo-notification-icon").boxObject.width, 0,
|
||||
"geo anchor shouldn't be visible");
|
||||
is(PopupNotifications.panel.anchorNode.id, "identity-icon",
|
||||
"notification anchored to identity icon");
|
||||
|
||||
let shownLastTime = waitForNotificationPanel();
|
||||
// This will cause the popup to hide and show again.
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
yield shownLastTime;
|
||||
// Switching to the old tab should show the notification again.
|
||||
shown = waitForNotificationPanel();
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
gBrowser.selectedTab = this.oldSelectedTab;
|
||||
yield shown;
|
||||
|
||||
checkPopup(PopupNotifications.panel, this.notifyObj);
|
||||
|
||||
let hidden = new Promise(resolve => onPopupEvent("popuphidden", resolve));
|
||||
hidden = waitForNotificationPanelHidden();
|
||||
this.notification.remove();
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
gBrowser.selectedTab = this.oldSelectedTab;
|
||||
yield hidden;
|
||||
|
||||
goNext();
|
||||
|
|
|
@ -264,6 +264,14 @@ function waitForNotificationPanel() {
|
|||
});
|
||||
}
|
||||
|
||||
function waitForNotificationPanelHidden() {
|
||||
return new Promise(resolve => {
|
||||
onPopupEvent("popuphidden", function() {
|
||||
resolve(this);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function triggerMainCommand(popup) {
|
||||
let notifications = popup.childNodes;
|
||||
ok(notifications.length > 0, "at least one notification displayed");
|
||||
|
|
|
@ -1227,6 +1227,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|||
if (event.originalTarget == this.inputField) {
|
||||
this._hideURLTooltip();
|
||||
this.formatValue();
|
||||
if (this.getAttribute("pageproxystate") != "valid") {
|
||||
UpdatePopupNotificationsVisibility();
|
||||
}
|
||||
}
|
||||
]]></handler>
|
||||
|
||||
|
@ -1234,6 +1237,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|||
if (event.originalTarget == this.inputField) {
|
||||
this._clearNoActions();
|
||||
this.formatValue();
|
||||
if (this.getAttribute("pageproxystate") != "valid") {
|
||||
UpdatePopupNotificationsVisibility();
|
||||
}
|
||||
}
|
||||
if (ExtensionSearchHandler.hasActiveInputSession()) {
|
||||
ExtensionSearchHandler.handleInputCancelled();
|
||||
|
|
|
@ -1488,29 +1488,31 @@ var PlacesControllerDragHelper = {
|
|||
continue;
|
||||
|
||||
let data = dt.mozGetDataAt(flavor, i);
|
||||
let dragged;
|
||||
let nodes;
|
||||
try {
|
||||
dragged = PlacesUtils.unwrapNodes(data, flavor)[0];
|
||||
nodes = PlacesUtils.unwrapNodes(data, flavor);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only bookmarks and urls can be dropped into tag containers.
|
||||
if (ip.isTag &&
|
||||
dragged.type != PlacesUtils.TYPE_X_MOZ_URL &&
|
||||
(dragged.type != PlacesUtils.TYPE_X_MOZ_PLACE ||
|
||||
(dragged.uri && dragged.uri.startsWith("place:")) ))
|
||||
return false;
|
||||
for (let dragged of nodes) {
|
||||
// Only bookmarks and urls can be dropped into tag containers.
|
||||
if (ip.isTag &&
|
||||
dragged.type != PlacesUtils.TYPE_X_MOZ_URL &&
|
||||
(dragged.type != PlacesUtils.TYPE_X_MOZ_PLACE ||
|
||||
(dragged.uri && dragged.uri.startsWith("place:")) ))
|
||||
return false;
|
||||
|
||||
// The following loop disallows the dropping of a folder on itself or
|
||||
// on any of its descendants.
|
||||
if (dragged.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER ||
|
||||
(dragged.uri && dragged.uri.startsWith("place:")) ) {
|
||||
let parentId = ip.itemId;
|
||||
while (parentId != PlacesUtils.placesRootId) {
|
||||
if (dragged.concreteId == parentId || dragged.id == parentId)
|
||||
return false;
|
||||
parentId = PlacesUtils.bookmarks.getFolderIdForItem(parentId);
|
||||
// The following loop disallows the dropping of a folder on itself or
|
||||
// on any of its descendants.
|
||||
if (dragged.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER ||
|
||||
(dragged.uri && dragged.uri.startsWith("place:")) ) {
|
||||
let parentId = ip.itemId;
|
||||
while (parentId != PlacesUtils.placesRootId) {
|
||||
if (dragged.concreteId == parentId || dragged.id == parentId)
|
||||
return false;
|
||||
parentId = PlacesUtils.bookmarks.getFolderIdForItem(parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1576,63 +1578,77 @@ var PlacesControllerDragHelper = {
|
|||
let parentGuid = PlacesUIUtils.useAsyncTransactions ?
|
||||
(yield insertionPoint.promiseGuid()) : null;
|
||||
let tagName = insertionPoint.tagName;
|
||||
|
||||
// Following flavors may contain duplicated data.
|
||||
let duplicable = new Map();
|
||||
duplicable.set(PlacesUtils.TYPE_UNICODE, new Set());
|
||||
duplicable.set(PlacesUtils.TYPE_X_MOZ_URL, new Set());
|
||||
|
||||
for (let i = 0; i < dropCount; ++i) {
|
||||
let flavor = this.getFirstValidFlavor(dt.mozTypesAt(i));
|
||||
if (!flavor)
|
||||
return;
|
||||
|
||||
let data = dt.mozGetDataAt(flavor, i);
|
||||
let unwrapped;
|
||||
if (duplicable.has(flavor)) {
|
||||
let handled = duplicable.get(flavor);
|
||||
if (handled.has(data))
|
||||
continue;
|
||||
handled.add(data);
|
||||
}
|
||||
|
||||
let nodes;
|
||||
if (flavor != TAB_DROP_TYPE) {
|
||||
// There's only ever one in the D&D case.
|
||||
unwrapped = PlacesUtils.unwrapNodes(data, flavor)[0];
|
||||
nodes = PlacesUtils.unwrapNodes(data, flavor);
|
||||
} else if (data instanceof XULElement && data.localName == "tab" &&
|
||||
data.ownerGlobal instanceof ChromeWindow) {
|
||||
let uri = data.linkedBrowser.currentURI;
|
||||
let spec = uri ? uri.spec : "about:blank";
|
||||
unwrapped = { uri: spec,
|
||||
title: data.label,
|
||||
type: PlacesUtils.TYPE_X_MOZ_URL};
|
||||
nodes = [{ uri: spec,
|
||||
title: data.label,
|
||||
type: PlacesUtils.TYPE_X_MOZ_URL}];
|
||||
} else
|
||||
throw new Error("bogus data was passed as a tab");
|
||||
|
||||
let index = insertionPoint.index;
|
||||
for (let unwrapped of nodes) {
|
||||
let index = insertionPoint.index;
|
||||
|
||||
// Adjust insertion index to prevent reversal of dragged items. When you
|
||||
// drag multiple elts upward: need to increment index or each successive
|
||||
// elt will be inserted at the same index, each above the previous.
|
||||
let dragginUp = insertionPoint.itemId == unwrapped.parent &&
|
||||
index < PlacesUtils.bookmarks.getItemIndex(unwrapped.id);
|
||||
if (index != -1 && dragginUp)
|
||||
index += movedCount++;
|
||||
// Adjust insertion index to prevent reversal of dragged items. When you
|
||||
// drag multiple elts upward: need to increment index or each successive
|
||||
// elt will be inserted at the same index, each above the previous.
|
||||
let dragginUp = insertionPoint.itemId == unwrapped.parent &&
|
||||
index < PlacesUtils.bookmarks.getItemIndex(unwrapped.id);
|
||||
if (index != -1 && dragginUp)
|
||||
index += movedCount++;
|
||||
|
||||
// If dragging over a tag container we should tag the item.
|
||||
if (insertionPoint.isTag) {
|
||||
let uri = NetUtil.newURI(unwrapped.uri);
|
||||
let tagItemId = insertionPoint.itemId;
|
||||
if (PlacesUIUtils.useAsyncTransactions)
|
||||
transactions.push(PlacesTransactions.Tag({ uri, tag: tagName }));
|
||||
else
|
||||
transactions.push(new PlacesTagURITransaction(uri, [tagItemId]));
|
||||
} else {
|
||||
// If this is not a copy, check for safety that we can move the source,
|
||||
// otherwise report an error and fallback to a copy.
|
||||
if (!doCopy && !PlacesControllerDragHelper.canMoveUnwrappedNode(unwrapped)) {
|
||||
Components.utils.reportError("Tried to move an unmovable Places " +
|
||||
"node, reverting to a copy operation.");
|
||||
doCopy = true;
|
||||
}
|
||||
if (PlacesUIUtils.useAsyncTransactions) {
|
||||
transactions.push(
|
||||
PlacesUIUtils.getTransactionForData(unwrapped,
|
||||
flavor,
|
||||
parentGuid,
|
||||
index,
|
||||
doCopy));
|
||||
// If dragging over a tag container we should tag the item.
|
||||
if (insertionPoint.isTag) {
|
||||
let uri = NetUtil.newURI(unwrapped.uri);
|
||||
let tagItemId = insertionPoint.itemId;
|
||||
if (PlacesUIUtils.useAsyncTransactions)
|
||||
transactions.push(PlacesTransactions.Tag({ uri, tag: tagName }));
|
||||
else
|
||||
transactions.push(new PlacesTagURITransaction(uri, [tagItemId]));
|
||||
} else {
|
||||
transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
|
||||
flavor, insertionPoint.itemId,
|
||||
index, doCopy));
|
||||
// If this is not a copy, check for safety that we can move the
|
||||
// source, otherwise report an error and fallback to a copy.
|
||||
if (!doCopy && !PlacesControllerDragHelper.canMoveUnwrappedNode(unwrapped)) {
|
||||
Components.utils.reportError("Tried to move an unmovable Places " +
|
||||
"node, reverting to a copy operation.");
|
||||
doCopy = true;
|
||||
}
|
||||
if (PlacesUIUtils.useAsyncTransactions) {
|
||||
transactions.push(
|
||||
PlacesUIUtils.getTransactionForData(unwrapped,
|
||||
flavor,
|
||||
parentGuid,
|
||||
index,
|
||||
doCopy));
|
||||
} else {
|
||||
transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
|
||||
flavor, insertionPoint.itemId,
|
||||
index, doCopy));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,46 @@ add_task(function* test() {
|
|||
|
||||
// Verify that we removed the bookmark successfully.
|
||||
ok(!PlacesUtils.bookmarks.isBookmarked(uri), "URI should be removed");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Simulates a drop of multiple URIs onto the bookmarks bar.
|
||||
*
|
||||
* @param aEffect
|
||||
* The effect to use for the drop operation: move, copy, or link.
|
||||
* @param aMimeType
|
||||
* The mime type to use for the drop operation.
|
||||
*/
|
||||
let simulateDragDropMultiple = function(aEffect, aMimeType) {
|
||||
const uriSpecs = [
|
||||
"http://www.mozilla.org/C54263C6-A484-46CF-8E2B-FE131586348A",
|
||||
"http://www.mozilla.org/71381257-61E6-4376-AF7C-BF3C5FD8870D",
|
||||
"http://www.mozilla.org/091A88BD-5743-4C16-A005-3D2EA3A3B71E"
|
||||
];
|
||||
let uris = uriSpecs.map(spec => makeURI(spec));
|
||||
let data;
|
||||
if (aMimeType == "text/x-moz-url")
|
||||
data = uriSpecs.map(spec => spec + "\n" + spec).join("\n");
|
||||
else
|
||||
data = uriSpecs.join("\n");
|
||||
EventUtils.synthesizeDrop(placesItems.childNodes[0],
|
||||
placesItems,
|
||||
[[{type: aMimeType,
|
||||
data}]],
|
||||
aEffect, window);
|
||||
|
||||
// Verify that the drop produces exactly one bookmark per each URL.
|
||||
for (let uri of uris) {
|
||||
let bookmarkIds = PlacesUtils.bookmarks
|
||||
.getBookmarkIdsForURI(uri);
|
||||
ok(bookmarkIds.length == 1, "There should be exactly one bookmark");
|
||||
|
||||
PlacesUtils.bookmarks.removeItem(bookmarkIds[0]);
|
||||
|
||||
// Verify that we removed the bookmark successfully.
|
||||
ok(!PlacesUtils.bookmarks.isBookmarked(uri), "URI should be removed");
|
||||
}
|
||||
};
|
||||
|
||||
// Simulate a bookmark drop for all of the mime types and effects.
|
||||
let mimeTypes = ["text/plain", "text/unicode", "text/x-moz-url"];
|
||||
|
@ -60,6 +99,7 @@ add_task(function* test() {
|
|||
effects.forEach(function(effect) {
|
||||
mimeTypes.forEach(function(mimeType) {
|
||||
simulateDragDrop(effect, mimeType);
|
||||
simulateDragDropMultiple(effect, mimeType);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,25 +6,25 @@
|
|||
|
||||
Cu.import("resource:///modules/sessionstore/SessionStore.jsm");
|
||||
|
||||
function openAndCloseTab(window, url) {
|
||||
async function openAndCloseTab(window, url) {
|
||||
let tab = window.gBrowser.addTab(url);
|
||||
yield promiseBrowserLoaded(tab.linkedBrowser, true, url);
|
||||
yield TabStateFlusher.flush(tab.linkedBrowser);
|
||||
yield promiseRemoveTab(tab);
|
||||
await promiseBrowserLoaded(tab.linkedBrowser, true, url);
|
||||
await TabStateFlusher.flush(tab.linkedBrowser);
|
||||
await promiseRemoveTab(tab);
|
||||
}
|
||||
|
||||
function* openWindow(url) {
|
||||
let win = yield promiseNewWindowLoaded();
|
||||
async function openWindow(url) {
|
||||
let win = await promiseNewWindowLoaded();
|
||||
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
|
||||
win.gBrowser.selectedBrowser.loadURIWithFlags(url, flags);
|
||||
yield promiseBrowserLoaded(win.gBrowser.selectedBrowser, true, url);
|
||||
await promiseBrowserLoaded(win.gBrowser.selectedBrowser, true, url);
|
||||
return win;
|
||||
}
|
||||
|
||||
function closeWindow(win) {
|
||||
yield BrowserTestUtils.closeWindow(win);
|
||||
async function closeWindow(win) {
|
||||
await BrowserTestUtils.closeWindow(win);
|
||||
// Wait 20 ms to allow SessionStorage a chance to register the closed window.
|
||||
yield new Promise(resolve => setTimeout(resolve, 20));
|
||||
await new Promise(resolve => setTimeout(resolve, 20));
|
||||
}
|
||||
|
||||
add_task(function* test_undoCloseById() {
|
||||
|
|
|
@ -4,6 +4,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/.
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
|
||||
|
||||
SOURCES += [
|
||||
'host/rpc.cc',
|
||||
]
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
"use strict";
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
// For the following 5 lines of codes, we redirect the
|
||||
// path of the "ppapi.js" in addon to the exact file path.
|
||||
Components.utils.import("resource://gre/modules/NetUtil.jsm");
|
||||
let resHandler = Services.io.getProtocolHandler("resource")
|
||||
.QueryInterface(Components.interfaces.nsISubstitutingProtocolHandler);
|
||||
let dataURI = NetUtil.newURI(do_get_file("."));
|
||||
resHandler.setSubstitution("ppapi.js", dataURI);
|
||||
|
||||
// Load the script
|
||||
load("ppapi-runtime.jsm");
|
||||
|
||||
let instanceId = 1;
|
||||
let url = "http://example.com";
|
||||
let info = {
|
||||
documentURL: "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai",
|
||||
url,
|
||||
setupJSInstanceObject: false,
|
||||
isFullFrame: false, //pluginElement.ownerDocument.mozSyntheticDocument,
|
||||
arguments: {
|
||||
keys: ["src", "full-frame", "top-level-url"],
|
||||
values: [url, "", url],
|
||||
},
|
||||
};
|
||||
|
||||
// Head.js is a shared file for all the test_ppb***.js.
|
||||
// Right now, window, process and MessageManager are base classes here.
|
||||
// Fill in the classes when you need the functions of them
|
||||
// and add more mocked classses if you need them in
|
||||
// ppapi-runtime.jsm for your tests.
|
||||
class Mock_Window {}
|
||||
class Mock_Process {}
|
||||
class Mock_MessageManager {
|
||||
addMessageListener () {
|
||||
}
|
||||
}
|
||||
|
||||
// Here the new PPAPIRuntime, Call_PpbFunc and new PPAPIInstance are the
|
||||
// core part to invoke codes in ppapi-runtime.jsm.
|
||||
let rt = new PPAPIRuntime(new Mock_Process());
|
||||
|
||||
function Call_PpbFunc(obj) {
|
||||
if (!obj || !obj.__interface || !obj.__version || !obj.__method) {
|
||||
ok(false, 'invalid JSON');
|
||||
}
|
||||
let fn = obj.__interface + "_" + obj.__method;
|
||||
return rt.table[fn](obj);
|
||||
}
|
||||
|
||||
// PPAPIInstance constructor(id, rt, info, window, eventHandler, containerWindow, mm)
|
||||
let instance = new PPAPIInstance(instanceId, rt, info, new Mock_Window(), null /*docShell.chromeEventHandler*/, null, new Mock_MessageManager());
|
||||
|
||||
do_register_cleanup(function () {
|
||||
resHandler.setSubstitution("ppapi.js", null);
|
||||
})
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
"use strict";
|
||||
|
||||
const PPB_TESTCASES = [
|
||||
{"__interface":"PPB_KeyboardInputEvent","__version":"1.2","__method":"IsKeyboardInputEvent","resource":0},
|
||||
{"__interface":"PPB_KeyboardInputEvent","__version":"1.2","__method":"GetKeyCode","key_event":0},
|
||||
{"__interface":"PPB_KeyboardInputEvent","__version":"1.2","__method":"GetCharacterText","character_event":0}
|
||||
];
|
||||
|
||||
|
||||
class Mock_DomEvent {
|
||||
constructor(eventType) {
|
||||
this.type = eventType;
|
||||
this.timeStamp = 0;
|
||||
}
|
||||
}
|
||||
|
||||
class Mock_KeyboardInputEvent extends Mock_DomEvent {
|
||||
constructor(eventType, keyCode, charCode) {
|
||||
super(eventType);
|
||||
this.keyCode = keyCode;
|
||||
this.charCode = charCode;
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
// We mock a "keydown" event to test "PPB_KeyboardInputEvent".
|
||||
let event = new Mock_KeyboardInputEvent("keydown", 65, 0);
|
||||
// To test PPB_KeyboardInputEvent we need to invoke event resource constructor
|
||||
// in ppapi-runtime.jsm to get a resource id.
|
||||
let eventType = EventTypes.get(event.type);
|
||||
let resource = new eventType.resourceCtor(instance, event);
|
||||
let PP_ResourceID = resource.toJSON();
|
||||
|
||||
PPB_TESTCASES[0].resource = PP_ResourceID;
|
||||
PPB_TESTCASES[1].key_event = PP_ResourceID;
|
||||
Assert.equal(Call_PpbFunc(PPB_TESTCASES[0]), PP_Bool.PP_TRUE);
|
||||
Assert.equal(Call_PpbFunc(PPB_TESTCASES[1]), 65); // 65 is the keyCode when you press 'A'.
|
||||
|
||||
// We mock a "keypress" event to test "PPB_KeyboardInputEvent".
|
||||
event = new Mock_KeyboardInputEvent("keypress", 0, 65);
|
||||
eventType = EventTypes.get(event.type);
|
||||
resource = new eventType.resourceCtor(instance, event);
|
||||
PP_ResourceID = resource.toJSON();
|
||||
|
||||
PPB_TESTCASES[0].resource = PP_ResourceID;
|
||||
PPB_TESTCASES[2].character_event = PP_ResourceID;
|
||||
Assert.equal(Call_PpbFunc(PPB_TESTCASES[0]), PP_Bool.PP_TRUE);
|
||||
Assert.equal(Call_PpbFunc(PPB_TESTCASES[2]).type, PP_VarType.PP_VARTYPE_STRING);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
[DEFAULT]
|
||||
head = head.js
|
||||
tail =
|
||||
support-files =
|
||||
../../host/common/opengles2-utils.jsm
|
||||
../../host/common/ppapi-runtime.jsm
|
||||
|
||||
[test_ppbkeyboard.js]
|
|
@ -225,7 +225,7 @@ exports.MarkerDOMUtils = {
|
|||
hbox.className = "marker-details-customcontainer";
|
||||
|
||||
let label = doc.createElement("label");
|
||||
label.className = "custom-button devtools-button";
|
||||
label.className = "custom-button";
|
||||
label.setAttribute("value", "Show allocation triggers");
|
||||
label.setAttribute("type", "show-allocations");
|
||||
label.setAttribute("data-action", JSON.stringify({
|
||||
|
|
|
@ -695,6 +695,12 @@
|
|||
.marker-details-customcontainer .custom-button {
|
||||
padding: 2px 5px;
|
||||
border-width: 1px;
|
||||
color: var(--theme-highlight-blue);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.marker-details-customcontainer .custom-button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -97,7 +97,7 @@ function eventExpected(msg) {
|
|||
* enough, and we won't observe what should have been a failure of the test.
|
||||
* But it shouldn't happen that good code will randomly *fail* this test.
|
||||
*/
|
||||
function run_test() {
|
||||
function* run_test() {
|
||||
/*
|
||||
* TEST 1 tests that:
|
||||
* <body onhashchange = ... > works,
|
||||
|
|
|
@ -220,6 +220,17 @@ AudioChannelService::GetOrCreate()
|
|||
return service.forget();
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<AudioChannelService>
|
||||
AudioChannelService::Get()
|
||||
{
|
||||
if (sXPCOMShuttingDown) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<AudioChannelService> service = gAudioChannelService.get();
|
||||
return service.forget();
|
||||
}
|
||||
|
||||
/* static */ PRLogModuleInfo*
|
||||
AudioChannelService::GetAudioChannelLog()
|
||||
{
|
||||
|
@ -933,6 +944,17 @@ AudioChannelService::IsAudioChannelActive(mozIDOMWindowProxy* aWindow,
|
|||
*aActive = IsAudioChannelActive(window, (AudioChannel)aAudioChannel);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioChannelService::IsWindowActive(nsPIDOMWindowOuter* aWindow)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
auto* window = nsPIDOMWindowOuter::From(aWindow)->GetScriptableTop();
|
||||
AudioChannelWindow* winData = GetOrCreateWindowData(window);
|
||||
return !winData->mAudibleAgents.IsEmpty();
|
||||
}
|
||||
|
||||
void
|
||||
AudioChannelService::SetDefaultVolumeControlChannel(int32_t aChannel,
|
||||
bool aVisible)
|
||||
|
|
|
@ -89,11 +89,17 @@ public:
|
|||
|
||||
/**
|
||||
* Returns the AudioChannelServce singleton.
|
||||
* If AudioChannelServce is not exist, create and return new one.
|
||||
* If AudioChannelService doesn't exist, create and return new one.
|
||||
* Only to be called from main thread.
|
||||
*/
|
||||
static already_AddRefed<AudioChannelService> GetOrCreate();
|
||||
|
||||
/**
|
||||
* Returns the AudioChannelService singleton if one exists.
|
||||
* If AudioChannelService doesn't exist, returns null.
|
||||
*/
|
||||
static already_AddRefed<AudioChannelService> Get();
|
||||
|
||||
static bool IsAudioChannelMutedByDefault();
|
||||
|
||||
static PRLogModuleInfo* GetAudioChannelLog();
|
||||
|
@ -150,6 +156,8 @@ public:
|
|||
|
||||
bool IsAudioChannelActive(nsPIDOMWindowOuter* aWindow, AudioChannel aChannel);
|
||||
|
||||
bool IsWindowActive(nsPIDOMWindowOuter* aWindow);
|
||||
|
||||
/**
|
||||
* Return true if there is a telephony channel active in this process
|
||||
* or one of its subprocesses.
|
||||
|
|
|
@ -82,7 +82,10 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
explicit DOMParser(nsISupports* aOwner) : mOwner(aOwner), mAttemptedInit(false)
|
||||
explicit DOMParser(nsISupports* aOwner)
|
||||
: mOwner(aOwner)
|
||||
, mAttemptedInit(false)
|
||||
, mOriginalPrincipalWasSystem(false)
|
||||
{
|
||||
MOZ_ASSERT(aOwner);
|
||||
}
|
||||
|
|
|
@ -34,9 +34,10 @@ int32_t
|
|||
TimeoutManager::DOMMinTimeoutValue(bool aIsTracking) const {
|
||||
// First apply any back pressure delay that might be in effect.
|
||||
int32_t value = std::max(mBackPressureDelayMS, 0);
|
||||
// Don't use the background timeout value when there are audio contexts
|
||||
// present, so that background audio can keep running smoothly. (bug 1181073)
|
||||
bool isBackground = !mWindow.AsInner()->HasAudioContexts() &&
|
||||
// Don't use the background timeout value when the tab is playing audio.
|
||||
// Until bug 1336484 we only used to do this for pages that use Web Audio.
|
||||
// The original behavior was implemented in bug 11811073.
|
||||
bool isBackground = !mWindow.AsInner()->IsPlayingAudio() &&
|
||||
mWindow.IsBackgroundInternal();
|
||||
auto minValue = aIsTracking ? (isBackground ? gMinTrackingBackgroundTimeoutValue
|
||||
: gMinTrackingTimeoutValue)
|
||||
|
|
|
@ -4152,9 +4152,13 @@ nsPIDOMWindowInner::SyncStateFromParentWindow()
|
|||
}
|
||||
|
||||
bool
|
||||
nsPIDOMWindowInner::HasAudioContexts() const
|
||||
nsPIDOMWindowInner::IsPlayingAudio()
|
||||
{
|
||||
return !mAudioContexts.IsEmpty();
|
||||
RefPtr<AudioChannelService> acs = AudioChannelService::Get();
|
||||
if (!acs) {
|
||||
return false;
|
||||
}
|
||||
return acs->IsWindowActive(GetOuterWindow());
|
||||
}
|
||||
|
||||
mozilla::dom::TimeoutManager&
|
||||
|
|
|
@ -878,7 +878,7 @@ public:
|
|||
// window.
|
||||
void SyncStateFromParentWindow();
|
||||
|
||||
bool HasAudioContexts() const;
|
||||
bool IsPlayingAudio();
|
||||
|
||||
mozilla::dom::TimeoutManager& TimeoutManager();
|
||||
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
[DEFAULT]
|
||||
support-files =
|
||||
audio.ogg
|
||||
empty.html
|
||||
file_audioLoop.html
|
||||
file_audioLoopInIframe.html
|
||||
file_bug1011748_redirect.sjs
|
||||
file_bug1011748_OK.sjs
|
||||
file_messagemanager_unload.html
|
||||
file_pluginAudio.html
|
||||
file_use_counter_outer.html
|
||||
file_use_counter_svg_getElementById.svg
|
||||
file_use_counter_svg_currentScale.svg
|
||||
|
@ -11,6 +15,8 @@ support-files =
|
|||
file_use_counter_svg_fill_pattern.svg
|
||||
file_use_counter_svg_fill_pattern_internal.svg
|
||||
file_use_counter_svg_fill_pattern_data.svg
|
||||
file_webaudioLoop.html
|
||||
plugin.js
|
||||
|
||||
[browser_bug593387.js]
|
||||
[browser_bug902350.js]
|
||||
|
@ -27,3 +33,4 @@ skip-if = e10s # this tests non-e10s behavior. it's not expected to work in e10s
|
|||
skip-if = true # Bug 1271028
|
||||
[browser_use_counters.js]
|
||||
[browser_bug1307747.js]
|
||||
[browser_timeout_throttling_with_audio_playback.js]
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
const kBaseURI = "http://mochi.test:8888/browser/dom/base/test/empty.html";
|
||||
const kPluginJS = "chrome://mochitests/content/browser/dom/base/test/plugin.js";
|
||||
var testURLs = [
|
||||
"http://mochi.test:8888/browser/dom/base/test/file_audioLoop.html",
|
||||
"http://mochi.test:8888/browser/dom/base/test/file_audioLoopInIframe.html",
|
||||
"http://mochi.test:8888/browser/dom/base/test/file_pluginAudio.html",
|
||||
"http://mochi.test:8888/browser/dom/base/test/file_webaudioLoop.html",
|
||||
];
|
||||
|
||||
// We want to ensure that while audio is being played back, a background tab is
|
||||
// treated the same as a foreground tab as far as timeout throttling is concerned.
|
||||
// So we use a 10ms minimum timeout value for foreground tabs and a 100,000 second
|
||||
// minimum timeout value for background tabs. This means that in case the test
|
||||
// fails, it will time out in practice, but just for sanity the test condition
|
||||
// ensures that the observed timeout delay falls in this range.
|
||||
const kMinTimeoutForeground = 10;
|
||||
const kMinTimeoutBackground = 100 * 1000 * 1000;
|
||||
|
||||
Services.scriptloader.loadSubScript(kPluginJS, this);
|
||||
|
||||
function* runTest(url) {
|
||||
let currentTab = gBrowser.selectedTab;
|
||||
let newTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, kBaseURI);
|
||||
let newBrowser = gBrowser.getBrowserForTab(newTab);
|
||||
|
||||
// Wait for the UI to indicate that audio is being played back.
|
||||
let promise = BrowserTestUtils.waitForAttribute("soundplaying", newTab, "true");
|
||||
newBrowser.loadURI(url);
|
||||
yield promise;
|
||||
|
||||
// Put the tab in the background.
|
||||
yield BrowserTestUtils.switchTab(gBrowser, currentTab);
|
||||
|
||||
let timeout = yield ContentTask.spawn(newBrowser, {}, function() {
|
||||
return new Promise(resolve => {
|
||||
let before = new Date();
|
||||
content.window.setTimeout(function() {
|
||||
let after = new Date();
|
||||
resolve(after - before);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
ok(timeout >= kMinTimeoutForeground &&
|
||||
timeout <= kMinTimeoutBackground, `Got the correct timeout (${timeout})`);
|
||||
|
||||
// All done.
|
||||
yield BrowserTestUtils.removeTab(newTab);
|
||||
}
|
||||
|
||||
add_task(function* setup() {
|
||||
yield SpecialPowers.pushPrefEnv({"set": [
|
||||
["dom.min_timeout_value", kMinTimeoutForeground],
|
||||
["dom.min_background_timeout_value", kMinTimeoutBackground],
|
||||
]});
|
||||
});
|
||||
|
||||
add_task(function* test() {
|
||||
for (var url of testURLs) {
|
||||
yield runTest(url);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
<!DOCTYPE html>
|
||||
<iframe src="file_audioLoop.html"></iframe>
|
|
@ -189,6 +189,7 @@ support-files =
|
|||
invalid_accesscontrol.resource^headers^
|
||||
mutationobserver_dialog.html
|
||||
orientationcommon.js
|
||||
plugin.js
|
||||
script-1_bug597345.sjs
|
||||
script-2_bug597345.js
|
||||
script_bug602838.sjs
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
// Copied from /dom/plugins/test/mochitest/utils.js
|
||||
function getTestPlugin(pluginName) {
|
||||
var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
|
||||
.getService(SpecialPowers.Ci.nsIPluginHost);
|
||||
var tags = ph.getPluginTags();
|
||||
var name = pluginName || "Test Plug-in";
|
||||
for (var tag of tags) {
|
||||
if (tag.name == name) {
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
|
||||
ok(false, "Could not find plugin tag with plugin name '" + name + "'");
|
||||
return null;
|
||||
}
|
||||
// Copied from /dom/plugins/test/mochitest/utils.js
|
||||
function setTestPluginEnabledState(newEnabledState, pluginName) {
|
||||
var oldEnabledState = SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
|
||||
if (!oldEnabledState) {
|
||||
return;
|
||||
}
|
||||
var plugin = getTestPlugin(pluginName);
|
||||
while (plugin.enabledState != newEnabledState) {
|
||||
// Run a nested event loop to wait for the preference change to
|
||||
// propagate to the child. Yuck!
|
||||
SpecialPowers.Services.tm.currentThread.processNextEvent(true);
|
||||
}
|
||||
SimpleTest.registerCleanupFunction(function() {
|
||||
SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
|
||||
});
|
||||
}
|
||||
setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
|
|
@ -3,6 +3,7 @@
|
|||
<head>
|
||||
<title>Test for audio controller in windows</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="plugin.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -12,39 +13,6 @@
|
|||
|
||||
<script type="application/javascript">
|
||||
|
||||
// Copied from /dom/plugins/test/mochitest/utils.js
|
||||
function getTestPlugin(pluginName) {
|
||||
var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
|
||||
.getService(SpecialPowers.Ci.nsIPluginHost);
|
||||
var tags = ph.getPluginTags();
|
||||
var name = pluginName || "Test Plug-in";
|
||||
for (var tag of tags) {
|
||||
if (tag.name == name) {
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
|
||||
ok(false, "Could not find plugin tag with plugin name '" + name + "'");
|
||||
return null;
|
||||
}
|
||||
// Copied from /dom/plugins/test/mochitest/utils.js
|
||||
function setTestPluginEnabledState(newEnabledState, pluginName) {
|
||||
var oldEnabledState = SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
|
||||
if (!oldEnabledState) {
|
||||
return;
|
||||
}
|
||||
var plugin = getTestPlugin(pluginName);
|
||||
while (plugin.enabledState != newEnabledState) {
|
||||
// Run a nested event loop to wait for the preference change to
|
||||
// propagate to the child. Yuck!
|
||||
SpecialPowers.Services.tm.currentThread.processNextEvent(true);
|
||||
}
|
||||
SimpleTest.registerCleanupFunction(function() {
|
||||
SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
|
||||
});
|
||||
}
|
||||
setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var expectedNotification = null;
|
||||
|
|
|
@ -259,7 +259,9 @@ MP3TrackDemuxer::ScanUntil(const TimeUnit& aTime)
|
|||
}
|
||||
|
||||
if (Duration(mFrameIndex) > aTime) {
|
||||
FastSeek(aTime);
|
||||
// We've seeked past the target time, rewind back a little to correct it.
|
||||
const int64_t rewind = aTime.ToMicroseconds() / 100;
|
||||
FastSeek(aTime - TimeUnit::FromMicroseconds(rewind));
|
||||
}
|
||||
|
||||
if (Duration(mFrameIndex + 1) > aTime) {
|
||||
|
|
|
@ -16,10 +16,6 @@ MOCHITEST_MANIFESTS += [
|
|||
'test/mochitest.ini',
|
||||
]
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += [
|
||||
'test/browser.ini',
|
||||
]
|
||||
|
||||
TEST_HARNESS_FILES.testing.mochitest.tests.dom.media.webaudio.test.blink += [
|
||||
'test/blink/audio-testing.js',
|
||||
'test/blink/convolution-testing.js',
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
[browser_bug1181073.js]
|
|
@ -1,40 +0,0 @@
|
|||
add_task(function*() {
|
||||
// Make the min_background_timeout_value very high to avoid problems on slow machines
|
||||
yield SpecialPowers.pushPrefEnv({
|
||||
'set': [['dom.min_background_timeout_value', 3000]]
|
||||
});
|
||||
|
||||
// Make a new tab, and put it in the background
|
||||
yield BrowserTestUtils.withNewTab("about:blank", function*(browser) {
|
||||
yield BrowserTestUtils.withNewTab("about:blank", function*() {
|
||||
let time = yield ContentTask.spawn(browser, null, function () {
|
||||
return new Promise(resolve => {
|
||||
let start = content.performance.now();
|
||||
let id = content.window.setInterval(function() {
|
||||
let end = content.performance.now();
|
||||
content.window.clearInterval(id);
|
||||
resolve(end - start);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
|
||||
ok(time > 2000, "Interval is throttled with no webaudio (" + time + " ms)");
|
||||
|
||||
time = yield ContentTask.spawn(browser, null, function () {
|
||||
return new Promise(resolve => {
|
||||
// Create an audio context, and save it on the window so it doesn't get GCed
|
||||
content.window._audioCtx = new content.window.AudioContext();
|
||||
|
||||
let start = content.performance.now();
|
||||
let id = content.window.setInterval(function() {
|
||||
let end = content.performance.now();
|
||||
content.window.clearInterval(id);
|
||||
resolve(end - start);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
|
||||
ok(time < 1000, "Interval is not throttled with an audio context present (" + time + " ms)");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -426,7 +426,7 @@ function build_test_uri(base_uri, host, test_id, type, timeout) {
|
|||
}
|
||||
|
||||
// open a new tab, load the test, and wait for it to finish
|
||||
function execute_test(test, mimetype) {
|
||||
async function execute_test(test, mimetype) {
|
||||
var src = build_test_uri(TOP_URI, test_servers[test].host,
|
||||
test, test_settings[which_test].type,
|
||||
test_settings[which_test].timeout);
|
||||
|
@ -435,7 +435,7 @@ function execute_test(test, mimetype) {
|
|||
test_servers[test]['tab'] = tab;
|
||||
|
||||
let browser = gBrowser.getBrowserForTab(tab);
|
||||
yield BrowserTestUtils.browserLoaded(browser);
|
||||
await BrowserTestUtils.browserLoaded(browser);
|
||||
|
||||
yield BrowserTestUtils.removeTab(tab);
|
||||
await BrowserTestUtils.removeTab(tab);
|
||||
}
|
||||
|
|
|
@ -1247,7 +1247,8 @@ private:
|
|||
MOZ_ASSERT(equal);
|
||||
|
||||
mWorkerPrivate->InitChannelInfo(aChannelInfo);
|
||||
mWorkerPrivate->SetPrincipalOnMainThread(responsePrincipal, loadGroup);
|
||||
rv = mWorkerPrivate->SetPrincipalOnMainThread(responsePrincipal, loadGroup);
|
||||
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
|
|
|
@ -1457,17 +1457,10 @@ public:
|
|||
nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(httpChannel);
|
||||
if (uploadChannel) {
|
||||
MOZ_ASSERT(!mUploadStream);
|
||||
bool bodyHasHeaders = false;
|
||||
rv = uploadChannel->GetUploadStreamHasHeaders(&bodyHasHeaders);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<nsIInputStream> uploadStream;
|
||||
rv = uploadChannel->CloneUploadStream(getter_AddRefs(uploadStream));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (bodyHasHeaders) {
|
||||
HandleBodyWithHeaders(uploadStream);
|
||||
} else {
|
||||
mUploadStream = uploadStream;
|
||||
}
|
||||
mUploadStream = uploadStream;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
@ -1600,52 +1593,6 @@ private:
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
nsresult
|
||||
HandleBodyWithHeaders(nsIInputStream* aUploadStream)
|
||||
{
|
||||
// We are dealing with an nsMIMEInputStream which uses string input streams
|
||||
// under the hood, so all of the data is available synchronously.
|
||||
bool nonBlocking = false;
|
||||
nsresult rv = aUploadStream->IsNonBlocking(&nonBlocking);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (NS_WARN_IF(!nonBlocking)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
nsAutoCString body;
|
||||
rv = NS_ConsumeStream(aUploadStream, UINT32_MAX, body);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Extract the headers in the beginning of the buffer
|
||||
nsAutoCString::const_iterator begin, end;
|
||||
body.BeginReading(begin);
|
||||
body.EndReading(end);
|
||||
const nsAutoCString::const_iterator body_end = end;
|
||||
nsAutoCString headerName, headerValue;
|
||||
bool emptyHeader = false;
|
||||
while (FetchUtil::ExtractHeader(begin, end, headerName,
|
||||
headerValue, &emptyHeader) &&
|
||||
!emptyHeader) {
|
||||
mHeaderNames.AppendElement(headerName);
|
||||
mHeaderValues.AppendElement(headerValue);
|
||||
headerName.Truncate();
|
||||
headerValue.Truncate();
|
||||
}
|
||||
|
||||
// Replace the upload stream with one only containing the body text.
|
||||
nsCOMPtr<nsIStringInputStream> strStream =
|
||||
do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
// Skip past the "\r\n" that separates the headers and the body.
|
||||
++begin;
|
||||
++begin;
|
||||
body.Assign(Substring(begin, body_end));
|
||||
rv = strStream->SetData(body.BeginReading(), body.Length());
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mUploadStream = strStream;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS_INHERITED(FetchEventRunnable, WorkerRunnable, nsIHttpHeaderVisitor)
|
||||
|
|
|
@ -1835,7 +1835,7 @@ WorkerLoadInfo::StealFrom(WorkerLoadInfo& aOther)
|
|||
mOriginAttributes = aOther.mOriginAttributes;
|
||||
}
|
||||
|
||||
void
|
||||
nsresult
|
||||
WorkerLoadInfo::SetPrincipalOnMainThread(nsIPrincipal* aPrincipal,
|
||||
nsILoadGroup* aLoadGroup)
|
||||
{
|
||||
|
@ -1846,7 +1846,8 @@ WorkerLoadInfo::SetPrincipalOnMainThread(nsIPrincipal* aPrincipal,
|
|||
mPrincipal = aPrincipal;
|
||||
mPrincipalIsSystem = nsContentUtils::IsSystemPrincipal(aPrincipal);
|
||||
|
||||
aPrincipal->GetCsp(getter_AddRefs(mCSP));
|
||||
nsresult rv = aPrincipal->GetCsp(getter_AddRefs(mCSP));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (mCSP) {
|
||||
mCSP->GetAllowsEval(&mReportCSPViolations, &mEvalAllowed);
|
||||
|
@ -1854,8 +1855,8 @@ WorkerLoadInfo::SetPrincipalOnMainThread(nsIPrincipal* aPrincipal,
|
|||
bool hasReferrerPolicy = false;
|
||||
uint32_t rp = mozilla::net::RP_Unset;
|
||||
|
||||
nsresult rv = mCSP->GetReferrerPolicy(&rp, &hasReferrerPolicy);
|
||||
NS_ENSURE_SUCCESS_VOID(rv);
|
||||
rv = mCSP->GetReferrerPolicy(&rp, &hasReferrerPolicy);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (hasReferrerPolicy) {
|
||||
mReferrerPolicy = static_cast<net::ReferrerPolicy>(rp);
|
||||
|
@ -1870,8 +1871,10 @@ WorkerLoadInfo::SetPrincipalOnMainThread(nsIPrincipal* aPrincipal,
|
|||
mPrincipalInfo = new PrincipalInfo();
|
||||
mOriginAttributes = nsContentUtils::GetOriginAttributes(aLoadGroup);
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(
|
||||
PrincipalToPrincipalInfo(aPrincipal, mPrincipalInfo));
|
||||
rv = PrincipalToPrincipalInfo(aPrincipal, mPrincipalInfo);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -1953,8 +1956,7 @@ WorkerLoadInfo::SetPrincipalFromChannel(nsIChannel* aChannel)
|
|||
getter_AddRefs(loadGroup));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
SetPrincipalOnMainThread(principal, loadGroup);
|
||||
return NS_OK;
|
||||
return SetPrincipalOnMainThread(principal, loadGroup);
|
||||
}
|
||||
|
||||
#if defined(DEBUG) || !defined(RELEASE_OR_BETA)
|
||||
|
@ -3803,11 +3805,11 @@ WorkerPrivateParent<Derived>::SetBaseURI(nsIURI* aBaseURI)
|
|||
}
|
||||
|
||||
template <class Derived>
|
||||
void
|
||||
nsresult
|
||||
WorkerPrivateParent<Derived>::SetPrincipalOnMainThread(nsIPrincipal* aPrincipal,
|
||||
nsILoadGroup* aLoadGroup)
|
||||
{
|
||||
mLoadInfo.SetPrincipalOnMainThread(aPrincipal, aLoadGroup);
|
||||
return mLoadInfo.SetPrincipalOnMainThread(aPrincipal, aLoadGroup);
|
||||
}
|
||||
|
||||
template <class Derived>
|
||||
|
|
|
@ -607,7 +607,7 @@ public:
|
|||
return mLoadInfo.mPrincipal;
|
||||
}
|
||||
|
||||
void
|
||||
nsresult
|
||||
SetPrincipalOnMainThread(nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup);
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -278,7 +278,7 @@ struct WorkerLoadInfo
|
|||
|
||||
void StealFrom(WorkerLoadInfo& aOther);
|
||||
|
||||
void
|
||||
nsresult
|
||||
SetPrincipalOnMainThread(nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup);
|
||||
|
||||
nsresult
|
||||
|
|
|
@ -2298,6 +2298,7 @@ XMLHttpRequestMainThread::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
|
|||
mProgressEventSink = nullptr;
|
||||
|
||||
mFlagSyncLooping = false;
|
||||
mRequestSentTime = 0;
|
||||
|
||||
// update our charset and decoder to match mResponseXML,
|
||||
// before it is possibly nulled out
|
||||
|
|
|
@ -124,7 +124,7 @@ const tests = [
|
|||
["warn", "iso-8859-1"]
|
||||
];
|
||||
|
||||
function do_get_file_by_line(file, charset) {
|
||||
function* do_get_file_by_line(file, charset) {
|
||||
dump("getting file by line for file " + file.path + "\n");
|
||||
dump("using charset " + charset +"\n");
|
||||
let fis = Cc["@mozilla.org/network/file-input-stream;1"].
|
||||
|
@ -163,7 +163,7 @@ function do_run_test(checker, name, charset, todo_good, todo_bad) {
|
|||
|
||||
if (good.exists()) {
|
||||
var good_counter = 0;
|
||||
for (val in do_get_file_by_line(good, charset)) {
|
||||
for (val of do_get_file_by_line(good, charset)) {
|
||||
let todo = false;
|
||||
good_counter++;
|
||||
if (todo_good && todo_good[good_counter]) {
|
||||
|
@ -182,7 +182,7 @@ function do_run_test(checker, name, charset, todo_good, todo_bad) {
|
|||
|
||||
if (bad.exists()) {
|
||||
var bad_counter = 0;
|
||||
for (val in do_get_file_by_line(bad, charset)) {
|
||||
for (val of do_get_file_by_line(bad, charset)) {
|
||||
let todo = false;
|
||||
bad_counter++;
|
||||
if (todo_bad && todo_bad[bad_counter]) {
|
||||
|
|
|
@ -3330,7 +3330,7 @@ FilterNodeLightingSoftware<LightType, LightingType>::SetAttribute(uint32_t aInde
|
|||
}
|
||||
switch (aIndex) {
|
||||
case ATT_LIGHTING_SURFACE_SCALE:
|
||||
mSurfaceScale = aValue;
|
||||
mSurfaceScale = std::fpclassify(aValue) == FP_SUBNORMAL ? 0.0 : aValue;
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH("GFX: FilterNodeLightingSoftware::SetAttribute float");
|
||||
|
|
|
@ -397,7 +397,7 @@ ProcessOrDeferMessage(HWND hwnd,
|
|||
case WM_GETOBJECT: {
|
||||
if (!::GetPropW(hwnd, k3rdPartyWindowProp)) {
|
||||
DWORD objId = static_cast<DWORD>(lParam);
|
||||
if ((objId == OBJID_CLIENT || objId == MOZOBJID_UIAROOT)) {
|
||||
if (objId == OBJID_CLIENT || objId == MOZOBJID_UIAROOT) {
|
||||
WNDPROC oldWndProc = (WNDPROC)GetProp(hwnd, kOldWndProcProp);
|
||||
if (oldWndProc) {
|
||||
return CallWindowProcW(oldWndProc, hwnd, uMsg, wParam, lParam);
|
||||
|
@ -1021,7 +1021,7 @@ MessageChannel::WaitForSyncNotify(bool aHandleWindowsMessages)
|
|||
MOZ_ASSERT(gUIThreadId, "InitUIThread was not called!");
|
||||
|
||||
#if defined(ACCESSIBILITY)
|
||||
if ((mFlags & REQUIRE_A11Y_REENTRY)) {
|
||||
if (mFlags & REQUIRE_A11Y_REENTRY) {
|
||||
MOZ_ASSERT(!(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION));
|
||||
return WaitForSyncNotifyWithA11yReentry();
|
||||
}
|
||||
|
|
|
@ -629,8 +629,6 @@ BarriersAreAllowedOnCurrentThread();
|
|||
static MOZ_ALWAYS_INLINE void
|
||||
ExposeGCThingToActiveJS(JS::GCCellPtr thing)
|
||||
{
|
||||
MOZ_ASSERT(thing.kind() != JS::TraceKind::Shape);
|
||||
|
||||
// GC things residing in the nursery cannot be gray: they have no mark bits.
|
||||
// All live objects in the nursery are moved to tenured at the beginning of
|
||||
// each GC slice, so the gray marker never sees nursery things.
|
||||
|
|
|
@ -47,7 +47,6 @@ class MOZ_RAII AutoTraceSession
|
|||
|
||||
JS::HeapState prevState;
|
||||
AutoGeckoProfilerEntry pseudoFrame;
|
||||
JSRuntime::AutoProhibitActiveContextChange prohibitActiveContextChange;
|
||||
};
|
||||
|
||||
class MOZ_RAII AutoPrepareForTracing
|
||||
|
|
|
@ -631,6 +631,8 @@ class GCRuntime
|
|||
void startDebugGC(JSGCInvocationKind gckind, SliceBudget& budget);
|
||||
void debugGCSlice(SliceBudget& budget);
|
||||
|
||||
bool canChangeActiveContext(JSContext* cx);
|
||||
|
||||
void triggerFullGCForAtoms() {
|
||||
MOZ_ASSERT(fullGCForAtomsRequested_);
|
||||
fullGCForAtomsRequested_ = false;
|
||||
|
@ -691,8 +693,6 @@ class GCRuntime
|
|||
allocTask.cancel(GCParallelTask::CancelAndWait);
|
||||
}
|
||||
|
||||
void requestMinorGC(JS::gcreason::Reason reason);
|
||||
|
||||
#ifdef DEBUG
|
||||
bool onBackgroundThread() { return helperState.onBackgroundThread(); }
|
||||
#endif // DEBUG
|
||||
|
@ -777,9 +777,7 @@ class GCRuntime
|
|||
bool areGrayBitsValid() const { return grayBitsValid; }
|
||||
void setGrayBitsInvalid() { grayBitsValid = false; }
|
||||
|
||||
bool minorGCRequested() const { return minorGCTriggerReason != JS::gcreason::NO_REASON; }
|
||||
bool majorGCRequested() const { return majorGCTriggerReason != JS::gcreason::NO_REASON; }
|
||||
bool isGcNeeded() { return minorGCRequested() || majorGCRequested(); }
|
||||
|
||||
bool fullGCForAtomsRequested() const { return fullGCForAtomsRequested_; }
|
||||
|
||||
|
@ -940,7 +938,8 @@ class GCRuntime
|
|||
void endSweepingZoneGroup();
|
||||
IncrementalProgress sweepPhase(SliceBudget& sliceBudget, AutoLockForExclusiveAccess& lock);
|
||||
void endSweepPhase(bool lastGC, AutoLockForExclusiveAccess& lock);
|
||||
void sweepZones(FreeOp* fop, bool lastGC);
|
||||
void sweepZones(FreeOp* fop, ZoneGroup* group, bool lastGC);
|
||||
void sweepZoneGroups(FreeOp* fop, bool destroyingRuntime);
|
||||
void decommitAllWithoutUnlocking(const AutoLockGC& lock);
|
||||
void startDecommit();
|
||||
void queueZonesForBackgroundSweep(ZoneList& zones);
|
||||
|
@ -1084,9 +1083,6 @@ class GCRuntime
|
|||
|
||||
mozilla::Atomic<JS::gcreason::Reason, mozilla::Relaxed> majorGCTriggerReason;
|
||||
|
||||
public:
|
||||
ActiveThreadData<JS::gcreason::Reason> minorGCTriggerReason;
|
||||
|
||||
private:
|
||||
/* Perform full GC if rt->keepAtoms() becomes false. */
|
||||
ActiveThreadData<bool> fullGCForAtomsRequested_;
|
||||
|
|
|
@ -2721,7 +2721,7 @@ IsMarkedInternal(JSRuntime* rt, JSObject** thingp)
|
|||
|
||||
if (IsInsideNursery(*thingp)) {
|
||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
|
||||
return rt->zoneGroupFromMainThread()->nursery().getForwardedPointer(thingp);
|
||||
return Nursery::getForwardedPointer(thingp);
|
||||
}
|
||||
return IsMarkedInternalCommon(thingp);
|
||||
}
|
||||
|
|
|
@ -80,6 +80,13 @@ ReallocateObjectBuffer(JSContext* cx, JSObject* obj, T* oldBuffer,
|
|||
return obj->zone()->pod_realloc<T>(oldBuffer, oldCount, newCount);
|
||||
}
|
||||
|
||||
static inline void
|
||||
EvictAllNurseries(JSRuntime* rt, JS::gcreason::Reason reason = JS::gcreason::EVICT_NURSERY)
|
||||
{
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next())
|
||||
group->evictNursery(reason);
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* gc_Nursery_inl_h */
|
||||
|
|
|
@ -84,16 +84,16 @@ struct js::Nursery::Canary
|
|||
#endif
|
||||
|
||||
inline void
|
||||
js::Nursery::NurseryChunk::poisonAndInit(JSRuntime* rt, uint8_t poison)
|
||||
js::Nursery::NurseryChunk::poisonAndInit(ZoneGroup* group, uint8_t poison)
|
||||
{
|
||||
JS_POISON(this, poison, ChunkSize);
|
||||
init(rt);
|
||||
init(group);
|
||||
}
|
||||
|
||||
inline void
|
||||
js::Nursery::NurseryChunk::init(JSRuntime* rt)
|
||||
js::Nursery::NurseryChunk::init(ZoneGroup* group)
|
||||
{
|
||||
new (&trailer) gc::ChunkTrailer(rt, &rt->zoneGroupFromMainThread()->storeBuffer());
|
||||
new (&trailer) gc::ChunkTrailer(group->runtime, &group->storeBuffer());
|
||||
}
|
||||
|
||||
/* static */ inline js::Nursery::NurseryChunk*
|
||||
|
@ -122,6 +122,7 @@ js::Nursery::Nursery(ZoneGroup* group)
|
|||
, profileThreshold_(0)
|
||||
, enableProfiling_(false)
|
||||
, reportTenurings_(0)
|
||||
, minorGCTriggerReason_(JS::gcreason::NO_REASON)
|
||||
, minorGcCount_(0)
|
||||
, freeMallocedBuffersTask(nullptr)
|
||||
, sweepActions_(nullptr)
|
||||
|
@ -484,12 +485,9 @@ js::TenuringTracer::TenuringTracer(JSRuntime* rt, Nursery* nursery)
|
|||
{
|
||||
}
|
||||
|
||||
void
|
||||
/* static */ void
|
||||
js::Nursery::printProfileHeader()
|
||||
{
|
||||
if (!enableProfiling_)
|
||||
return;
|
||||
|
||||
fprintf(stderr, "MinorGC: Reason PRate Size ");
|
||||
#define PRINT_HEADER(name, text) \
|
||||
fprintf(stderr, " %6s", text);
|
||||
|
@ -515,6 +513,15 @@ js::Nursery::printTotalProfileTimes()
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
js::Nursery::maybeClearProfileDurations()
|
||||
{
|
||||
if (enableProfiling_) {
|
||||
for (auto& duration : profileDurations_)
|
||||
duration = mozilla::TimeDuration();
|
||||
}
|
||||
}
|
||||
|
||||
inline void
|
||||
js::Nursery::startProfile(ProfileKey key)
|
||||
{
|
||||
|
@ -573,6 +580,7 @@ js::Nursery::collect(JS::gcreason::Reason reason)
|
|||
rt->gc.stats().beginNurseryCollection(reason);
|
||||
TraceMinorGCStart();
|
||||
|
||||
maybeClearProfileDurations();
|
||||
startProfile(ProfileKey::Total);
|
||||
|
||||
// The hazard analysis thinks doCollection can invalidate pointers in
|
||||
|
@ -642,7 +650,7 @@ js::Nursery::collect(JS::gcreason::Reason reason)
|
|||
if (reportTenurings_) {
|
||||
for (auto& entry : tenureCounts.entries) {
|
||||
if (entry.count >= reportTenurings_) {
|
||||
fprintf(stderr, "%d x ", entry.count);
|
||||
fprintf(stderr, " %d x ", entry.count);
|
||||
entry.group->print();
|
||||
}
|
||||
}
|
||||
|
@ -703,7 +711,7 @@ js::Nursery::doCollection(JS::gcreason::Reason reason,
|
|||
maybeStartProfile(ProfileKey::MarkDebugger);
|
||||
{
|
||||
gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PHASE_MARK_ROOTS);
|
||||
Debugger::traceAll(&mover);
|
||||
Debugger::traceAllForMovingGC(&mover);
|
||||
}
|
||||
maybeEndProfile(ProfileKey::MarkDebugger);
|
||||
|
||||
|
@ -829,7 +837,7 @@ js::Nursery::sweep()
|
|||
#ifdef JS_GC_ZEAL
|
||||
/* Poison the nursery contents so touching a freed object will crash. */
|
||||
for (unsigned i = 0; i < numChunks(); i++)
|
||||
chunk(i).poisonAndInit(zoneGroup()->runtime, JS_SWEPT_NURSERY_PATTERN);
|
||||
chunk(i).poisonAndInit(zoneGroup(), JS_SWEPT_NURSERY_PATTERN);
|
||||
|
||||
if (zoneGroup()->runtime->hasZealMode(ZealMode::GenerationalGC)) {
|
||||
/* Only reset the alloc point when we are close to the end. */
|
||||
|
@ -840,7 +848,7 @@ js::Nursery::sweep()
|
|||
{
|
||||
#ifdef JS_CRASH_DIAGNOSTICS
|
||||
for (unsigned i = 0; i < numChunks(); ++i)
|
||||
chunk(i).poisonAndInit(zoneGroup()->runtime, JS_SWEPT_NURSERY_PATTERN);
|
||||
chunk(i).poisonAndInit(zoneGroup(), JS_SWEPT_NURSERY_PATTERN);
|
||||
#endif
|
||||
setCurrentChunk(0);
|
||||
}
|
||||
|
@ -874,7 +882,7 @@ js::Nursery::setCurrentChunk(unsigned chunkno)
|
|||
currentChunk_ = chunkno;
|
||||
position_ = chunk(chunkno).start();
|
||||
currentEnd_ = chunk(chunkno).end();
|
||||
chunk(chunkno).poisonAndInit(zoneGroup()->runtime, JS_FRESH_NURSERY_PATTERN);
|
||||
chunk(chunkno).poisonAndInit(zoneGroup(), JS_FRESH_NURSERY_PATTERN);
|
||||
}
|
||||
|
||||
MOZ_ALWAYS_INLINE void
|
||||
|
@ -974,7 +982,7 @@ js::Nursery::updateNumChunksLocked(unsigned newCount,
|
|||
}
|
||||
|
||||
chunks_[i] = NurseryChunk::fromChunk(newChunk);
|
||||
chunk(i).poisonAndInit(zoneGroup()->runtime, JS_FRESH_NURSERY_PATTERN);
|
||||
chunk(i).poisonAndInit(zoneGroup(), JS_FRESH_NURSERY_PATTERN);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -260,7 +260,7 @@ class Nursery
|
|||
#endif
|
||||
|
||||
/* Print header line for profile times. */
|
||||
void printProfileHeader();
|
||||
static void printProfileHeader();
|
||||
|
||||
/* Print total profile times on shutdown. */
|
||||
void printTotalProfileTimes();
|
||||
|
@ -268,6 +268,14 @@ class Nursery
|
|||
void* addressOfCurrentEnd() const { return (void*)¤tEnd_; }
|
||||
void* addressOfPosition() const { return (void*)&position_; }
|
||||
|
||||
void requestMinorGC(JS::gcreason::Reason reason) const;
|
||||
|
||||
bool minorGCRequested() const { return minorGCTriggerReason_ != JS::gcreason::NO_REASON; }
|
||||
JS::gcreason::Reason minorGCTriggerReason() const { return minorGCTriggerReason_; }
|
||||
void clearMinorGCRequest() { minorGCTriggerReason_ = JS::gcreason::NO_REASON; }
|
||||
|
||||
bool enableProfiling() const { return enableProfiling_; }
|
||||
|
||||
private:
|
||||
/* The amount of space in the mapped nursery available to allocations. */
|
||||
static const size_t NurseryChunkUsableSize = gc::ChunkSize - sizeof(gc::ChunkTrailer);
|
||||
|
@ -276,8 +284,8 @@ class Nursery
|
|||
char data[NurseryChunkUsableSize];
|
||||
gc::ChunkTrailer trailer;
|
||||
static NurseryChunk* fromChunk(gc::Chunk* chunk);
|
||||
void init(JSRuntime* rt);
|
||||
void poisonAndInit(JSRuntime* rt, uint8_t poison);
|
||||
void init(ZoneGroup* group);
|
||||
void poisonAndInit(ZoneGroup* group, uint8_t poison);
|
||||
uintptr_t start() const { return uintptr_t(&data); }
|
||||
uintptr_t end() const { return uintptr_t(&trailer); }
|
||||
gc::Chunk* toChunk(JSRuntime* rt);
|
||||
|
@ -317,6 +325,13 @@ class Nursery
|
|||
/* Report ObjectGroups with at lest this many instances tenured. */
|
||||
int64_t reportTenurings_;
|
||||
|
||||
/*
|
||||
* Whether and why a collection of this nursery has been requested. This is
|
||||
* mutable as it is set by the store buffer, which otherwise cannot modify
|
||||
* anything in the nursery.
|
||||
*/
|
||||
mutable JS::gcreason::Reason minorGCTriggerReason_;
|
||||
|
||||
/* Profiling data. */
|
||||
|
||||
enum class ProfileKey
|
||||
|
@ -455,6 +470,7 @@ class Nursery
|
|||
void minimizeAllocableSpace();
|
||||
|
||||
/* Profile recording and printing. */
|
||||
void maybeClearProfileDurations();
|
||||
void startProfile(ProfileKey key);
|
||||
void endProfile(ProfileKey key);
|
||||
void maybeStartProfile(ProfileKey key);
|
||||
|
|
|
@ -86,9 +86,9 @@ JS::RootingContext::traceStackRoots(JSTracer* trc)
|
|||
}
|
||||
|
||||
static void
|
||||
TraceExactStackRoots(JSRuntime* rt, JSTracer* trc)
|
||||
TraceExactStackRoots(const CooperatingContext& target, JSTracer* trc)
|
||||
{
|
||||
TlsContext.get()->traceStackRoots(trc);
|
||||
target.context()->traceStackRoots(trc);
|
||||
}
|
||||
|
||||
template <typename T, TraceFunction<T> TraceFn = TraceNullableRoot>
|
||||
|
@ -201,18 +201,16 @@ AutoGCRooter::trace(JSTracer* trc)
|
|||
}
|
||||
|
||||
/* static */ void
|
||||
AutoGCRooter::traceAll(JSTracer* trc)
|
||||
AutoGCRooter::traceAll(const CooperatingContext& target, JSTracer* trc)
|
||||
{
|
||||
for (AutoGCRooter* gcr = TlsContext.get()->autoGCRooters_; gcr; gcr = gcr->down)
|
||||
for (AutoGCRooter* gcr = target.context()->autoGCRooters_; gcr; gcr = gcr->down)
|
||||
gcr->trace(trc);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
AutoGCRooter::traceAllWrappers(JSTracer* trc)
|
||||
AutoGCRooter::traceAllWrappers(const CooperatingContext& target, JSTracer* trc)
|
||||
{
|
||||
JSContext* cx = TlsContext.get();
|
||||
|
||||
for (AutoGCRooter* gcr = cx->autoGCRooters_; gcr; gcr = gcr->down) {
|
||||
for (AutoGCRooter* gcr = target.context()->autoGCRooters_; gcr; gcr = gcr->down) {
|
||||
if (gcr->tag_ == WRAPVECTOR || gcr->tag_ == WRAPPER)
|
||||
gcr->trace(trc);
|
||||
}
|
||||
|
@ -290,7 +288,8 @@ js::TraceRuntime(JSTracer* trc)
|
|||
MOZ_ASSERT(!trc->isMarkingTracer());
|
||||
|
||||
JSRuntime* rt = trc->runtime();
|
||||
rt->zoneGroupFromMainThread()->evictNursery();
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next())
|
||||
group->evictNursery();
|
||||
AutoPrepareForTracing prep(TlsContext.get(), WithAtoms);
|
||||
gcstats::AutoPhase ap(rt->gc.stats(), gcstats::PHASE_TRACE_HEAP);
|
||||
rt->gc.traceRuntime(trc, prep.session().lock);
|
||||
|
@ -330,18 +329,18 @@ js::gc::GCRuntime::traceRuntimeCommon(JSTracer* trc, TraceOrMarkRuntime traceOrM
|
|||
// Trace active interpreter and JIT stack roots.
|
||||
TraceInterpreterActivations(cx, target, trc);
|
||||
jit::TraceJitActivations(cx, target, trc);
|
||||
}
|
||||
|
||||
// Trace legacy C stack roots.
|
||||
AutoGCRooter::traceAll(trc);
|
||||
// Trace legacy C stack roots.
|
||||
AutoGCRooter::traceAll(target, trc);
|
||||
|
||||
// Trace C stack roots.
|
||||
TraceExactStackRoots(target, trc);
|
||||
}
|
||||
|
||||
for (RootRange r = rootsHash.ref().all(); !r.empty(); r.popFront()) {
|
||||
const RootEntry& entry = r.front();
|
||||
TraceRoot(trc, entry.key(), entry.value());
|
||||
}
|
||||
|
||||
// Trace C stack roots.
|
||||
TraceExactStackRoots(rt, trc);
|
||||
}
|
||||
|
||||
// Trace runtime global roots.
|
||||
|
@ -353,9 +352,9 @@ js::gc::GCRuntime::traceRuntimeCommon(JSTracer* trc, TraceOrMarkRuntime traceOrM
|
|||
// Trace the shared Intl data.
|
||||
rt->traceSharedIntlData(trc);
|
||||
|
||||
// Trace anything in the current thread's context. Ignore other JSContexts,
|
||||
// as these will only refer to ZoneGroups which we are not collecting/tracing.
|
||||
TlsContext.get()->trace(trc);
|
||||
// Trace anything in any of the cooperating threads.
|
||||
for (const CooperatingContext& target : rt->cooperatingContexts())
|
||||
target.context()->trace(trc);
|
||||
|
||||
// Trace all compartment roots, but not the compartment itself; it is
|
||||
// traced via the parent pointer if traceRoots actually traces anything.
|
||||
|
|
|
@ -1405,7 +1405,12 @@ Statistics::maybePrintProfileHeaders()
|
|||
static int printedHeader = 0;
|
||||
if ((printedHeader++ % 200) == 0) {
|
||||
printProfileHeader();
|
||||
runtime->zoneGroupFromMainThread()->nursery().printProfileHeader();
|
||||
for (ZoneGroupsIter group(runtime); !group.done(); group.next()) {
|
||||
if (group->nursery().enableProfiling()) {
|
||||
Nursery::printProfileHeader();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ StoreBuffer::setAboutToOverflow()
|
|||
aboutToOverflow_ = true;
|
||||
runtime_->gc.stats().count(gcstats::STAT_STOREBUFFER_OVERFLOW);
|
||||
}
|
||||
runtime_->gc.requestMinorGC(JS::gcreason::FULL_STORE_BUFFER);
|
||||
nursery_.requestMinorGC(JS::gcreason::FULL_STORE_BUFFER);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -327,7 +327,7 @@ gc::GCRuntime::endVerifyPreBarriers()
|
|||
|
||||
MOZ_ASSERT(!JS::IsGenerationalGCEnabled(rt));
|
||||
|
||||
AutoPrepareForTracing prep(rt->contextFromMainThread(), SkipAtoms);
|
||||
AutoPrepareForTracing prep(rt->activeContextFromOwnThread(), SkipAtoms);
|
||||
|
||||
bool compartmentCreated = false;
|
||||
|
||||
|
|
|
@ -337,7 +337,7 @@ Zone::notifyObservingDebuggers()
|
|||
{
|
||||
for (CompartmentsInZoneIter comps(this); !comps.done(); comps.next()) {
|
||||
JSRuntime* rt = runtimeFromAnyThread();
|
||||
RootedGlobalObject global(rt->contextFromMainThread(), comps->unsafeUnbarrieredMaybeGlobal());
|
||||
RootedGlobalObject global(TlsContext.get(), comps->unsafeUnbarrieredMaybeGlobal());
|
||||
if (!global)
|
||||
continue;
|
||||
|
||||
|
|
|
@ -882,11 +882,11 @@ struct GCManagedDeletePolicy
|
|||
{
|
||||
void operator()(const T* ptr) {
|
||||
if (ptr) {
|
||||
JSRuntime* rt = TlsContext.get()->runtime();
|
||||
if (CurrentThreadCanAccessRuntime(rt) && rt->zoneGroupFromMainThread()->nursery().isEnabled()) {
|
||||
Zone* zone = ptr->zone();
|
||||
if (zone && zone->group()->nursery().isEnabled()) {
|
||||
// The object may contain nursery pointers and must only be
|
||||
// destroyed after a minor GC.
|
||||
rt->zoneGroupFromMainThread()->callAfterMinorGC(deletePtr, const_cast<T*>(ptr));
|
||||
zone->group()->callAfterMinorGC(deletePtr, const_cast<T*>(ptr));
|
||||
} else {
|
||||
// The object cannot contain nursery pointers so can be
|
||||
// destroyed immediately.
|
||||
|
|
|
@ -748,8 +748,7 @@ BaselineCacheIRCompiler::emitStoreSlotShared(bool isFixed)
|
|||
masm.storeValue(val, slot);
|
||||
}
|
||||
|
||||
if (cx_->nursery().exists())
|
||||
BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch1, LiveGeneralRegisterSet(), cx_);
|
||||
BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch1, LiveGeneralRegisterSet(), cx_);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -862,8 +861,7 @@ BaselineCacheIRCompiler::emitAddAndStoreSlotShared(CacheOp op)
|
|||
masm.storeValue(val, slot);
|
||||
}
|
||||
|
||||
if (cx_->nursery().exists())
|
||||
BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch1, LiveGeneralRegisterSet(), cx_);
|
||||
BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch1, LiveGeneralRegisterSet(), cx_);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1011,6 +1009,137 @@ BaselineCacheIRCompiler::emitStoreTypedObjectScalarProperty()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCacheIRCompiler::emitStoreDenseElement()
|
||||
{
|
||||
ObjOperandId objId = reader.objOperandId();
|
||||
Int32OperandId indexId = reader.int32OperandId();
|
||||
|
||||
// Allocate the fixed registers first. These need to be fixed for
|
||||
// callTypeUpdateIC.
|
||||
AutoScratchRegister scratch(allocator, masm, R1.scratchReg());
|
||||
ValueOperand val = allocator.useFixedValueRegister(masm, reader.valOperandId(), R0);
|
||||
|
||||
Register obj = allocator.useRegister(masm, objId);
|
||||
Register index = allocator.useRegister(masm, indexId);
|
||||
|
||||
FailurePath* failure;
|
||||
if (!addFailurePath(&failure))
|
||||
return false;
|
||||
|
||||
// Load obj->elements in scratch.
|
||||
masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
|
||||
|
||||
// Bounds check.
|
||||
Address initLength(scratch, ObjectElements::offsetOfInitializedLength());
|
||||
masm.branch32(Assembler::BelowOrEqual, initLength, index, failure->label());
|
||||
|
||||
// Hole check.
|
||||
BaseObjectElementIndex element(scratch, index);
|
||||
masm.branchTestMagic(Assembler::Equal, element, failure->label());
|
||||
|
||||
// Perform a single test to see if we either need to convert double
|
||||
// elements, clone the copy on write elements in the object or fail
|
||||
// due to a frozen element.
|
||||
Label noSpecialHandling;
|
||||
Address elementsFlags(scratch, ObjectElements::offsetOfFlags());
|
||||
masm.branchTest32(Assembler::Zero, elementsFlags,
|
||||
Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS |
|
||||
ObjectElements::COPY_ON_WRITE |
|
||||
ObjectElements::FROZEN),
|
||||
&noSpecialHandling);
|
||||
|
||||
// Fail if we need to clone copy on write elements or to throw due
|
||||
// to a frozen element.
|
||||
masm.branchTest32(Assembler::NonZero, elementsFlags,
|
||||
Imm32(ObjectElements::COPY_ON_WRITE |
|
||||
ObjectElements::FROZEN),
|
||||
failure->label());
|
||||
|
||||
// We need to convert int32 values being stored into doubles. Note that
|
||||
// double arrays are only created by IonMonkey, so if we have no FP support
|
||||
// Ion is disabled and there should be no double arrays.
|
||||
if (cx_->runtime()->jitSupportsFloatingPoint) {
|
||||
// It's fine to convert the value in place in Baseline. We can't do
|
||||
// this in Ion.
|
||||
masm.convertInt32ValueToDouble(val);
|
||||
} else {
|
||||
masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support.");
|
||||
}
|
||||
|
||||
masm.bind(&noSpecialHandling);
|
||||
|
||||
// Call the type update IC. After this everything must be infallible as we
|
||||
// don't save all registers here.
|
||||
LiveGeneralRegisterSet saveRegs;
|
||||
saveRegs.add(obj);
|
||||
saveRegs.add(index);
|
||||
saveRegs.add(val);
|
||||
if (!callTypeUpdateIC(obj, val, scratch, saveRegs))
|
||||
return false;
|
||||
|
||||
// Perform the store. Reload obj->elements because callTypeUpdateIC
|
||||
// used the scratch register.
|
||||
masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
|
||||
EmitPreBarrier(masm, element, MIRType::Value);
|
||||
masm.storeValue(val, element);
|
||||
|
||||
BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch, LiveGeneralRegisterSet(), cx_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCacheIRCompiler::emitStoreUnboxedArrayElement()
|
||||
{
|
||||
ObjOperandId objId = reader.objOperandId();
|
||||
Int32OperandId indexId = reader.int32OperandId();
|
||||
|
||||
// Allocate the fixed registers first. These need to be fixed for
|
||||
// callTypeUpdateIC.
|
||||
AutoScratchRegister scratch(allocator, masm, R1.scratchReg());
|
||||
ValueOperand val = allocator.useFixedValueRegister(masm, reader.valOperandId(), R0);
|
||||
|
||||
JSValueType elementType = reader.valueType();
|
||||
Register obj = allocator.useRegister(masm, objId);
|
||||
Register index = allocator.useRegister(masm, indexId);
|
||||
|
||||
FailurePath* failure;
|
||||
if (!addFailurePath(&failure))
|
||||
return false;
|
||||
|
||||
// Bounds check.
|
||||
Address initLength(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
|
||||
masm.load32(initLength, scratch);
|
||||
masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratch);
|
||||
masm.branch32(Assembler::BelowOrEqual, scratch, index, failure->label());
|
||||
|
||||
// Call the type update IC. After this everything must be infallible as we
|
||||
// don't save all registers here.
|
||||
if (elementType == JSVAL_TYPE_OBJECT) {
|
||||
LiveGeneralRegisterSet saveRegs;
|
||||
saveRegs.add(obj);
|
||||
saveRegs.add(index);
|
||||
saveRegs.add(val);
|
||||
if (!callTypeUpdateIC(obj, val, scratch, saveRegs))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load obj->elements.
|
||||
masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratch);
|
||||
|
||||
// Note that the storeUnboxedProperty call here is infallible, as the
|
||||
// IR emitter is responsible for guarding on |val|'s type.
|
||||
BaseIndex element(scratch, index, ScaleFromElemWidth(UnboxedTypeSize(elementType)));
|
||||
EmitUnboxedPreBarrierForBaseline(masm, element, elementType);
|
||||
masm.storeUnboxedProperty(element, elementType,
|
||||
ConstantOrRegister(TypedOrValueRegister(val)),
|
||||
/* failure = */ nullptr);
|
||||
|
||||
if (UnboxedTypeNeedsPostBarrier(elementType))
|
||||
BaselineEmitPostWriteBarrierSlot(masm, obj, val, scratch, LiveGeneralRegisterSet(), cx_);
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef bool (*CallNativeSetterFn)(JSContext*, HandleFunction, HandleObject, HandleValue);
|
||||
static const VMFunction CallNativeSetterInfo =
|
||||
FunctionInfo<CallNativeSetterFn>(CallNativeSetter, "CallNativeSetter");
|
||||
|
|
|
@ -325,7 +325,6 @@ DoTypeUpdateFallback(JSContext* cx, BaselineFrame* frame, ICUpdatedStub* stub, H
|
|||
AddTypePropertyId(cx, group, maybeSingleton, id, value);
|
||||
break;
|
||||
}
|
||||
case ICStub::SetElem_DenseOrUnboxedArray:
|
||||
case ICStub::SetElem_DenseOrUnboxedArrayAdd: {
|
||||
id = JSID_VOID;
|
||||
AddTypePropertyId(cx, obj, id, value);
|
||||
|
@ -953,20 +952,10 @@ static bool
|
|||
DenseOrUnboxedArraySetElemStubExists(JSContext* cx, ICStub::Kind kind,
|
||||
ICSetElem_Fallback* stub, HandleObject obj)
|
||||
{
|
||||
MOZ_ASSERT(kind == ICStub::SetElem_DenseOrUnboxedArray ||
|
||||
kind == ICStub::SetElem_DenseOrUnboxedArrayAdd);
|
||||
MOZ_ASSERT(kind == ICStub::SetElem_DenseOrUnboxedArrayAdd);
|
||||
|
||||
for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
|
||||
if (kind == ICStub::SetElem_DenseOrUnboxedArray && iter->isSetElem_DenseOrUnboxedArray()) {
|
||||
ICSetElem_DenseOrUnboxedArray* nstub = iter->toSetElem_DenseOrUnboxedArray();
|
||||
if (obj->maybeShape() == nstub->shape() &&
|
||||
JSObject::getGroup(cx, obj) == nstub->group())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (kind == ICStub::SetElem_DenseOrUnboxedArrayAdd && iter->isSetElem_DenseOrUnboxedArrayAdd()) {
|
||||
if (iter->isSetElem_DenseOrUnboxedArrayAdd()) {
|
||||
ICSetElem_DenseOrUnboxedArrayAdd* nstub = iter->toSetElem_DenseOrUnboxedArrayAdd();
|
||||
if (JSObject::getGroup(cx, obj) == nstub->group() &&
|
||||
SetElemAddHasSameShapes(nstub, obj))
|
||||
|
@ -1104,6 +1093,17 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_
|
|||
if (!obj)
|
||||
return false;
|
||||
|
||||
RootedShape oldShape(cx, obj->maybeShape());
|
||||
RootedObjectGroup oldGroup(cx, JSObject::getGroup(cx, obj));
|
||||
if (!oldGroup)
|
||||
return false;
|
||||
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
MOZ_ASSERT(!oldShape);
|
||||
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
|
||||
oldShape = expando->lastProperty();
|
||||
}
|
||||
|
||||
bool isTemporarilyUnoptimizable = false;
|
||||
|
||||
bool attached = false;
|
||||
|
@ -1132,17 +1132,6 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_
|
|||
}
|
||||
}
|
||||
|
||||
RootedShape oldShape(cx, obj->maybeShape());
|
||||
RootedObjectGroup oldGroup(cx, JSObject::getGroup(cx, obj));
|
||||
if (!oldGroup)
|
||||
return false;
|
||||
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
MOZ_ASSERT(!oldShape);
|
||||
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
|
||||
oldShape = expando->lastProperty();
|
||||
}
|
||||
|
||||
// Check the old capacity
|
||||
uint32_t oldCapacity = 0;
|
||||
uint32_t oldInitLength = 0;
|
||||
|
@ -1242,25 +1231,6 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_
|
|||
return false;
|
||||
}
|
||||
|
||||
stub->addNewStub(newStub);
|
||||
} else if (!addingCase &&
|
||||
!DenseOrUnboxedArraySetElemStubExists(cx,
|
||||
ICStub::SetElem_DenseOrUnboxedArray,
|
||||
stub, obj))
|
||||
{
|
||||
JitSpew(JitSpew_BaselineIC,
|
||||
" Generating SetElem_DenseOrUnboxedArray stub (shape=%p, group=%p)",
|
||||
shape.get(), group.get());
|
||||
ICSetElem_DenseOrUnboxedArray::Compiler compiler(cx, shape, group);
|
||||
ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(outerScript));
|
||||
if (!newStub)
|
||||
return false;
|
||||
if (compiler.needsUpdateStubs() &&
|
||||
!newStub->addUpdateStubForValue(cx, outerScript, obj, JSID_VOIDHANDLE, rhs))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
stub->addNewStub(newStub);
|
||||
}
|
||||
}
|
||||
|
@ -1388,163 +1358,6 @@ EmitUnboxedPreBarrierForBaseline(MacroAssembler &masm, const BaseIndex& address,
|
|||
MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(type));
|
||||
}
|
||||
|
||||
bool
|
||||
ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm)
|
||||
{
|
||||
MOZ_ASSERT(engine_ == Engine::Baseline);
|
||||
|
||||
// R0 = object
|
||||
// R1 = key
|
||||
// Stack = { ... rhs-value, <return-addr>? }
|
||||
Label failure, failurePopR0;
|
||||
masm.branchTestObject(Assembler::NotEqual, R0, &failure);
|
||||
masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
|
||||
|
||||
AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
|
||||
Register scratchReg = regs.takeAny();
|
||||
|
||||
// Unbox R0 and guard on its group and, if this is a native access, its shape.
|
||||
Register obj = masm.extractObject(R0, ExtractTemp0);
|
||||
masm.loadPtr(Address(ICStubReg, ICSetElem_DenseOrUnboxedArray::offsetOfGroup()),
|
||||
scratchReg);
|
||||
masm.branchTestObjGroup(Assembler::NotEqual, obj, scratchReg, &failure);
|
||||
if (unboxedType_ == JSVAL_TYPE_MAGIC) {
|
||||
masm.loadPtr(Address(ICStubReg, ICSetElem_DenseOrUnboxedArray::offsetOfShape()),
|
||||
scratchReg);
|
||||
masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
|
||||
}
|
||||
|
||||
if (needsUpdateStubs()) {
|
||||
// Stow both R0 and R1 (object and key)
|
||||
// But R0 and R1 still hold their values.
|
||||
EmitStowICValues(masm, 2);
|
||||
|
||||
// Stack is now: { ..., rhs-value, object-value, key-value, maybe?-RET-ADDR }
|
||||
// Load rhs-value into R0
|
||||
masm.loadValue(Address(masm.getStackPointer(), 2 * sizeof(Value) + ICStackValueOffset), R0);
|
||||
|
||||
// Call the type-update stub.
|
||||
if (!callTypeUpdateIC(masm, sizeof(Value)))
|
||||
return false;
|
||||
|
||||
// Unstow R0 and R1 (object and key)
|
||||
EmitUnstowICValues(masm, 2);
|
||||
|
||||
// Restore object.
|
||||
obj = masm.extractObject(R0, ExtractTemp0);
|
||||
|
||||
// Trigger post barriers here on the value being written. Fields which
|
||||
// objects can be written to also need update stubs.
|
||||
masm.Push(R1);
|
||||
masm.loadValue(Address(masm.getStackPointer(), sizeof(Value) + ICStackValueOffset), R1);
|
||||
|
||||
LiveGeneralRegisterSet saveRegs;
|
||||
saveRegs.add(R0);
|
||||
saveRegs.addUnchecked(obj);
|
||||
saveRegs.add(ICStubReg);
|
||||
BaselineEmitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs, cx);
|
||||
|
||||
masm.Pop(R1);
|
||||
}
|
||||
|
||||
// Unbox key.
|
||||
Register key = masm.extractInt32(R1, ExtractTemp1);
|
||||
|
||||
if (unboxedType_ == JSVAL_TYPE_MAGIC) {
|
||||
// Set element on a native object.
|
||||
|
||||
// Load obj->elements in scratchReg.
|
||||
masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratchReg);
|
||||
|
||||
// Bounds check.
|
||||
Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength());
|
||||
masm.branch32(Assembler::BelowOrEqual, initLength, key, &failure);
|
||||
|
||||
// Hole check.
|
||||
BaseIndex element(scratchReg, key, TimesEight);
|
||||
masm.branchTestMagic(Assembler::Equal, element, &failure);
|
||||
|
||||
// Perform a single test to see if we either need to convert double
|
||||
// elements, clone the copy on write elements in the object or fail
|
||||
// due to a frozen element.
|
||||
Label noSpecialHandling;
|
||||
Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags());
|
||||
masm.branchTest32(Assembler::Zero, elementsFlags,
|
||||
Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS |
|
||||
ObjectElements::COPY_ON_WRITE |
|
||||
ObjectElements::FROZEN),
|
||||
&noSpecialHandling);
|
||||
|
||||
// Fail if we need to clone copy on write elements or to throw due
|
||||
// to a frozen element.
|
||||
masm.branchTest32(Assembler::NonZero, elementsFlags,
|
||||
Imm32(ObjectElements::COPY_ON_WRITE |
|
||||
ObjectElements::FROZEN),
|
||||
&failure);
|
||||
|
||||
// Failure is not possible now. Free up registers.
|
||||
regs.add(R0);
|
||||
regs.add(R1);
|
||||
regs.takeUnchecked(obj);
|
||||
regs.takeUnchecked(key);
|
||||
|
||||
Address valueAddr(masm.getStackPointer(), ICStackValueOffset);
|
||||
|
||||
// We need to convert int32 values being stored into doubles. In this case
|
||||
// the heap typeset is guaranteed to contain both int32 and double, so it's
|
||||
// okay to store a double. Note that double arrays are only created by
|
||||
// IonMonkey, so if we have no floating-point support Ion is disabled and
|
||||
// there should be no double arrays.
|
||||
if (cx->runtime()->jitSupportsFloatingPoint)
|
||||
masm.convertInt32ValueToDouble(valueAddr, regs.getAny(), &noSpecialHandling);
|
||||
else
|
||||
masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support.");
|
||||
|
||||
masm.bind(&noSpecialHandling);
|
||||
|
||||
ValueOperand tmpVal = regs.takeAnyValue();
|
||||
masm.loadValue(valueAddr, tmpVal);
|
||||
EmitPreBarrier(masm, element, MIRType::Value);
|
||||
masm.storeValue(tmpVal, element);
|
||||
} else {
|
||||
// Set element on an unboxed array.
|
||||
|
||||
// Bounds check.
|
||||
Address initLength(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
|
||||
masm.load32(initLength, scratchReg);
|
||||
masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg);
|
||||
masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure);
|
||||
|
||||
// Load obj->elements.
|
||||
masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg);
|
||||
|
||||
// Compute the address being written to.
|
||||
BaseIndex address(scratchReg, key, ScaleFromElemWidth(UnboxedTypeSize(unboxedType_)));
|
||||
|
||||
EmitUnboxedPreBarrierForBaseline(masm, address, unboxedType_);
|
||||
|
||||
Address valueAddr(masm.getStackPointer(), ICStackValueOffset + sizeof(Value));
|
||||
masm.Push(R0);
|
||||
masm.loadValue(valueAddr, R0);
|
||||
masm.storeUnboxedProperty(address, unboxedType_,
|
||||
ConstantOrRegister(TypedOrValueRegister(R0)), &failurePopR0);
|
||||
masm.Pop(R0);
|
||||
}
|
||||
|
||||
EmitReturnFromIC(masm);
|
||||
|
||||
if (failurePopR0.used()) {
|
||||
// Failure case: restore the value of R0
|
||||
masm.bind(&failurePopR0);
|
||||
masm.popValue(R0);
|
||||
}
|
||||
|
||||
// Failure case - jump to next stub
|
||||
masm.bind(&failure);
|
||||
EmitStubGuardFailure(masm);
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// SetElem_DenseOrUnboxedArrayAdd
|
||||
//
|
||||
|
@ -5279,12 +5092,6 @@ ICTypeUpdate_ObjectGroup::ICTypeUpdate_ObjectGroup(JitCode* stubCode, ObjectGrou
|
|||
group_(group)
|
||||
{ }
|
||||
|
||||
ICSetElem_DenseOrUnboxedArray::ICSetElem_DenseOrUnboxedArray(JitCode* stubCode, Shape* shape, ObjectGroup* group)
|
||||
: ICUpdatedStub(SetElem_DenseOrUnboxedArray, stubCode),
|
||||
shape_(shape),
|
||||
group_(group)
|
||||
{ }
|
||||
|
||||
ICSetElem_DenseOrUnboxedArrayAdd::ICSetElem_DenseOrUnboxedArrayAdd(JitCode* stubCode, ObjectGroup* group,
|
||||
size_t protoChainDepth)
|
||||
: ICUpdatedStub(SetElem_DenseOrUnboxedArrayAdd, stubCode),
|
||||
|
|
|
@ -457,67 +457,6 @@ class ICSetElem_Fallback : public ICFallbackStub
|
|||
};
|
||||
};
|
||||
|
||||
class ICSetElem_DenseOrUnboxedArray : public ICUpdatedStub
|
||||
{
|
||||
friend class ICStubSpace;
|
||||
|
||||
GCPtrShape shape_; // null for unboxed arrays
|
||||
GCPtrObjectGroup group_;
|
||||
|
||||
ICSetElem_DenseOrUnboxedArray(JitCode* stubCode, Shape* shape, ObjectGroup* group);
|
||||
|
||||
public:
|
||||
static size_t offsetOfShape() {
|
||||
return offsetof(ICSetElem_DenseOrUnboxedArray, shape_);
|
||||
}
|
||||
static size_t offsetOfGroup() {
|
||||
return offsetof(ICSetElem_DenseOrUnboxedArray, group_);
|
||||
}
|
||||
|
||||
GCPtrShape& shape() {
|
||||
return shape_;
|
||||
}
|
||||
GCPtrObjectGroup& group() {
|
||||
return group_;
|
||||
}
|
||||
|
||||
class Compiler : public ICStubCompiler {
|
||||
RootedShape shape_;
|
||||
RootedObjectGroup group_;
|
||||
JSValueType unboxedType_;
|
||||
|
||||
MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
|
||||
|
||||
public:
|
||||
virtual int32_t getKey() const {
|
||||
return static_cast<int32_t>(engine_) |
|
||||
(static_cast<int32_t>(kind) << 1) |
|
||||
(static_cast<int32_t>(unboxedType_) << 17);
|
||||
}
|
||||
|
||||
Compiler(JSContext* cx, Shape* shape, HandleObjectGroup group)
|
||||
: ICStubCompiler(cx, ICStub::SetElem_DenseOrUnboxedArray, Engine::Baseline),
|
||||
shape_(cx, shape),
|
||||
group_(cx, group),
|
||||
unboxedType_(shape
|
||||
? JSVAL_TYPE_MAGIC
|
||||
: group->unboxedLayoutDontCheckGeneration().elementType())
|
||||
{}
|
||||
|
||||
ICUpdatedStub* getStub(ICStubSpace* space) {
|
||||
ICSetElem_DenseOrUnboxedArray* stub =
|
||||
newStub<ICSetElem_DenseOrUnboxedArray>(space, getStubCode(), shape_, group_);
|
||||
if (!stub || !stub->initUpdatingChain(cx, space))
|
||||
return nullptr;
|
||||
return stub;
|
||||
}
|
||||
|
||||
bool needsUpdateStubs() {
|
||||
return unboxedType_ == JSVAL_TYPE_MAGIC || unboxedType_ == JSVAL_TYPE_OBJECT;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
template <size_t ProtoChainDepth> class ICSetElem_DenseOrUnboxedArrayAddImpl;
|
||||
|
||||
class ICSetElem_DenseOrUnboxedArrayAdd : public ICUpdatedStub
|
||||
|
|
|
@ -51,7 +51,6 @@ namespace jit {
|
|||
_(GetElem_Fallback) \
|
||||
\
|
||||
_(SetElem_Fallback) \
|
||||
_(SetElem_DenseOrUnboxedArray) \
|
||||
_(SetElem_DenseOrUnboxedArrayAdd) \
|
||||
_(SetElem_TypedArray) \
|
||||
\
|
||||
|
|
|
@ -57,20 +57,6 @@ SetElemICInspector::sawOOBTypedArrayWrite() const
|
|||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
SetElemICInspector::sawDenseWrite() const
|
||||
{
|
||||
if (!icEntry_)
|
||||
return false;
|
||||
|
||||
// Check for a SetElem_DenseAdd or SetElem_Dense stub.
|
||||
for (ICStub* stub = icEntry_->firstStub(); stub; stub = stub->next()) {
|
||||
if (stub->isSetElem_DenseOrUnboxedArrayAdd() || stub->isSetElem_DenseOrUnboxedArray())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
SetElemICInspector::sawTypedArrayWrite() const
|
||||
{
|
||||
|
|
|
@ -37,7 +37,6 @@ class SetElemICInspector : public ICInspector
|
|||
|
||||
bool sawOOBDenseWrite() const;
|
||||
bool sawOOBTypedArrayWrite() const;
|
||||
bool sawDenseWrite() const;
|
||||
bool sawTypedArrayWrite() const;
|
||||
};
|
||||
|
||||
|
|
|
@ -1906,6 +1906,17 @@ SetPropIRGenerator::tryAttachStub()
|
|||
return true;
|
||||
if (tryAttachSetArrayLength(obj, objId, id, rhsValId))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t index;
|
||||
Int32OperandId indexId;
|
||||
if (maybeGuardInt32Index(idVal_, setElemKeyValueId(), &index, &indexId)) {
|
||||
if (tryAttachSetDenseElement(obj, objId, index, indexId, rhsValId))
|
||||
return true;
|
||||
if (tryAttachSetUnboxedArrayElement(obj, objId, index, indexId, rhsValId))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -2225,6 +2236,58 @@ SetPropIRGenerator::tryAttachSetArrayLength(HandleObject obj, ObjOperandId objId
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SetPropIRGenerator::tryAttachSetDenseElement(HandleObject obj, ObjOperandId objId, uint32_t index,
|
||||
Int32OperandId indexId, ValOperandId rhsId)
|
||||
{
|
||||
if (!obj->isNative())
|
||||
return false;
|
||||
|
||||
NativeObject* nobj = &obj->as<NativeObject>();
|
||||
if (!nobj->containsDenseElement(index) || nobj->getElementsHeader()->isFrozen())
|
||||
return false;
|
||||
|
||||
writer.guardGroup(objId, nobj->group());
|
||||
writer.guardShape(objId, nobj->shape());
|
||||
|
||||
writer.storeDenseElement(objId, indexId, rhsId);
|
||||
writer.returnFromIC();
|
||||
|
||||
// Type inference uses JSID_VOID for the element types.
|
||||
setUpdateStubInfo(nobj->group(), JSID_VOID);
|
||||
|
||||
trackAttached("SetDenseElement");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SetPropIRGenerator::tryAttachSetUnboxedArrayElement(HandleObject obj, ObjOperandId objId, uint32_t index,
|
||||
Int32OperandId indexId, ValOperandId rhsId)
|
||||
{
|
||||
if (!obj->is<UnboxedArrayObject>())
|
||||
return false;
|
||||
|
||||
if (!cx_->runtime()->jitSupportsFloatingPoint)
|
||||
return false;
|
||||
|
||||
if (index >= obj->as<UnboxedArrayObject>().initializedLength())
|
||||
return false;
|
||||
|
||||
writer.guardGroup(objId, obj->group());
|
||||
|
||||
JSValueType elementType = obj->group()->unboxedLayoutDontCheckGeneration().elementType();
|
||||
EmitGuardUnboxedPropertyType(writer, elementType, rhsId);
|
||||
|
||||
writer.storeUnboxedArrayElement(objId, indexId, rhsId, elementType);
|
||||
writer.returnFromIC();
|
||||
|
||||
// Type inference uses JSID_VOID for the element types.
|
||||
setUpdateStubInfo(obj->group(), JSID_VOID);
|
||||
|
||||
trackAttached("SetUnboxedArrayElement");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SetPropIRGenerator::tryAttachAddSlotStub(HandleObjectGroup oldGroup, HandleShape oldShape)
|
||||
{
|
||||
|
|
|
@ -187,6 +187,8 @@ extern const char* CacheKindNames[];
|
|||
_(StoreTypedObjectReferenceProperty) \
|
||||
_(StoreTypedObjectScalarProperty) \
|
||||
_(StoreUnboxedProperty) \
|
||||
_(StoreDenseElement) \
|
||||
_(StoreUnboxedArrayElement) \
|
||||
_(CallNativeSetter) \
|
||||
_(CallScriptedSetter) \
|
||||
_(CallSetArrayLength) \
|
||||
|
@ -648,6 +650,19 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter
|
|||
addStubField(offset, StubField::Type::RawWord);
|
||||
writeOperandId(rhs);
|
||||
}
|
||||
void storeDenseElement(ObjOperandId obj, Int32OperandId index, ValOperandId rhs) {
|
||||
writeOpWithOperandId(CacheOp::StoreDenseElement, obj);
|
||||
writeOperandId(index);
|
||||
writeOperandId(rhs);
|
||||
}
|
||||
void storeUnboxedArrayElement(ObjOperandId obj, Int32OperandId index, ValOperandId rhs,
|
||||
JSValueType elementType)
|
||||
{
|
||||
writeOpWithOperandId(CacheOp::StoreUnboxedArrayElement, obj);
|
||||
writeOperandId(index);
|
||||
writeOperandId(rhs);
|
||||
buffer_.writeByte(uint32_t(elementType));
|
||||
}
|
||||
void callScriptedSetter(ObjOperandId obj, JSFunction* setter, ValOperandId rhs) {
|
||||
writeOpWithOperandId(CacheOp::CallScriptedSetter, obj);
|
||||
addStubField(uintptr_t(setter), StubField::Type::JSObject);
|
||||
|
@ -1021,6 +1036,11 @@ class MOZ_RAII SetPropIRGenerator : public IRGenerator
|
|||
bool tryAttachSetArrayLength(HandleObject obj, ObjOperandId objId, HandleId id,
|
||||
ValOperandId rhsId);
|
||||
|
||||
bool tryAttachSetDenseElement(HandleObject obj, ObjOperandId objId, uint32_t index,
|
||||
Int32OperandId indexId, ValOperandId rhsId);
|
||||
bool tryAttachSetUnboxedArrayElement(HandleObject obj, ObjOperandId objId, uint32_t index,
|
||||
Int32OperandId indexId, ValOperandId rhsId);
|
||||
|
||||
void trackAttached(const char* name);
|
||||
|
||||
public:
|
||||
|
|
|
@ -324,7 +324,7 @@ ExecutableAllocator::reprotectPool(JSRuntime* rt, ExecutablePool* pool, Protecti
|
|||
{
|
||||
// Don't race with reprotectAll called from the signal handler.
|
||||
MOZ_ASSERT(rt->jitRuntime()->preventBackedgePatching() ||
|
||||
rt->unsafeContextFromAnyThread()->handlingJitInterrupt());
|
||||
rt->activeContext()->handlingJitInterrupt());
|
||||
|
||||
char* start = pool->m_allocation.pages;
|
||||
if (!ReprotectRegion(start, pool->m_freePtr - start, protection))
|
||||
|
|
|
@ -226,7 +226,7 @@ class ExecutableAllocator
|
|||
#elif defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64)
|
||||
static void cacheFlush(void* code, size_t size)
|
||||
{
|
||||
js::jit::Simulator::FlushICache(code, size);
|
||||
js::jit::SimulatorProcess::FlushICache(code, size);
|
||||
}
|
||||
#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
|
||||
static void cacheFlush(void* code, size_t size)
|
||||
|
|
|
@ -885,6 +885,18 @@ IonCacheIRCompiler::emitStoreTypedObjectScalarProperty()
|
|||
MOZ_CRASH("Baseline-specific op");
|
||||
}
|
||||
|
||||
bool
|
||||
IonCacheIRCompiler::emitStoreDenseElement()
|
||||
{
|
||||
MOZ_CRASH("Baseline-specific op");
|
||||
}
|
||||
|
||||
bool
|
||||
IonCacheIRCompiler::emitStoreUnboxedArrayElement()
|
||||
{
|
||||
MOZ_CRASH("Baseline-specific op");
|
||||
}
|
||||
|
||||
bool
|
||||
IonCacheIRCompiler::emitCallNativeSetter()
|
||||
{
|
||||
|
|
|
@ -1821,6 +1821,17 @@ MacroAssembler::convertInt32ValueToDouble(const Address& address, Register scrat
|
|||
storeDouble(ScratchDoubleReg, address);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::convertInt32ValueToDouble(ValueOperand val)
|
||||
{
|
||||
Label done;
|
||||
branchTestInt32(Assembler::NotEqual, val, &done);
|
||||
unboxInt32(val, val.scratchReg());
|
||||
convertInt32ToDouble(val.scratchReg(), ScratchDoubleReg);
|
||||
boxDouble(ScratchDoubleReg, val);
|
||||
bind(&done);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::convertValueToFloatingPoint(ValueOperand value, FloatRegister output,
|
||||
Label* fail, MIRType outputType)
|
||||
|
|
|
@ -1960,6 +1960,8 @@ class MacroAssembler : public MacroAssemblerSpecific
|
|||
bool compilingWasm);
|
||||
|
||||
void convertInt32ValueToDouble(const Address& address, Register scratch, Label* done);
|
||||
void convertInt32ValueToDouble(ValueOperand val);
|
||||
|
||||
void convertValueToDouble(ValueOperand value, FloatRegister output, Label* fail) {
|
||||
convertValueToFloatingPoint(value, output, fail, MIRType::Double);
|
||||
}
|
||||
|
|
|
@ -267,12 +267,6 @@ ICStub::trace(JSTracer* trc)
|
|||
TraceEdge(trc, &callStub->expectedStr(), "baseline-callstringsplit-str");
|
||||
break;
|
||||
}
|
||||
case ICStub::SetElem_DenseOrUnboxedArray: {
|
||||
ICSetElem_DenseOrUnboxedArray* setElemStub = toSetElem_DenseOrUnboxedArray();
|
||||
TraceNullableEdge(trc, &setElemStub->shape(), "baseline-getelem-dense-shape");
|
||||
TraceEdge(trc, &setElemStub->group(), "baseline-setelem-dense-group");
|
||||
break;
|
||||
}
|
||||
case ICStub::SetElem_DenseOrUnboxedArrayAdd: {
|
||||
ICSetElem_DenseOrUnboxedArrayAdd* setElemStub = toSetElem_DenseOrUnboxedArrayAdd();
|
||||
TraceEdge(trc, &setElemStub->group(), "baseline-setelem-denseadd-group");
|
||||
|
@ -657,6 +651,9 @@ BaselineEmitPostWriteBarrierSlot(MacroAssembler& masm, Register obj, ValueOperan
|
|||
Register scratch, LiveGeneralRegisterSet saveRegs,
|
||||
JSContext* cx)
|
||||
{
|
||||
if (!cx->nursery().exists())
|
||||
return;
|
||||
|
||||
Label skipBarrier;
|
||||
masm.branchPtrInNurseryChunk(Assembler::Equal, obj, scratch, &skipBarrier);
|
||||
masm.branchValueIsNurseryObject(Assembler::NotEqual, val, scratch, &skipBarrier);
|
||||
|
|
|
@ -365,28 +365,16 @@ class AutoLockSimulatorCache : public LockGuard<Mutex>
|
|||
using Base = LockGuard<Mutex>;
|
||||
|
||||
public:
|
||||
explicit AutoLockSimulatorCache(Simulator* sim)
|
||||
: Base(sim->cacheLock_)
|
||||
, sim_(sim)
|
||||
{
|
||||
MOZ_ASSERT(sim_->cacheLockHolder_.isNothing());
|
||||
#ifdef DEBUG
|
||||
sim_->cacheLockHolder_ = mozilla::Some(ThisThread::GetId());
|
||||
#endif
|
||||
}
|
||||
|
||||
~AutoLockSimulatorCache() {
|
||||
MOZ_ASSERT(sim_->cacheLockHolder_.isSome());
|
||||
#ifdef DEBUG
|
||||
sim_->cacheLockHolder_.reset();
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
Simulator* const sim_;
|
||||
explicit AutoLockSimulatorCache()
|
||||
: Base(SimulatorProcess::singleton_->cacheLock_)
|
||||
{}
|
||||
};
|
||||
|
||||
bool Simulator::ICacheCheckingEnabled = false;
|
||||
mozilla::Atomic<size_t, mozilla::ReleaseAcquire>
|
||||
SimulatorProcess::ICacheCheckingDisableCount(1); // Checking is disabled by default.
|
||||
mozilla::Atomic<bool, mozilla::ReleaseAcquire>
|
||||
SimulatorProcess::cacheInvalidatedBySignalHandler_(false);
|
||||
SimulatorProcess* SimulatorProcess::singleton_ = nullptr;
|
||||
|
||||
int64_t Simulator::StopSimAt = -1L;
|
||||
|
||||
|
@ -402,9 +390,6 @@ Simulator::Create(JSContext* cx)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (getenv("ARM_SIM_ICACHE_CHECKS"))
|
||||
Simulator::ICacheCheckingEnabled = true;
|
||||
|
||||
char* stopAtStr = getenv("ARM_SIM_STOP_AT");
|
||||
int64_t stopAt;
|
||||
if (stopAtStr && sscanf(stopAtStr, "%lld", &stopAt) == 1) {
|
||||
|
@ -998,9 +983,9 @@ AllOnOnePage(uintptr_t start, int size)
|
|||
}
|
||||
|
||||
static CachePage*
|
||||
GetCachePageLocked(Simulator::ICacheMap& i_cache, void* page)
|
||||
GetCachePageLocked(SimulatorProcess::ICacheMap& i_cache, void* page)
|
||||
{
|
||||
Simulator::ICacheMap::AddPtr p = i_cache.lookupForAdd(page);
|
||||
SimulatorProcess::ICacheMap::AddPtr p = i_cache.lookupForAdd(page);
|
||||
if (p)
|
||||
return p->value();
|
||||
|
||||
|
@ -1014,7 +999,7 @@ GetCachePageLocked(Simulator::ICacheMap& i_cache, void* page)
|
|||
|
||||
// Flush from start up to and not including start + size.
|
||||
static void
|
||||
FlushOnePageLocked(Simulator::ICacheMap& i_cache, intptr_t start, int size)
|
||||
FlushOnePageLocked(SimulatorProcess::ICacheMap& i_cache, intptr_t start, int size)
|
||||
{
|
||||
MOZ_ASSERT(size <= CachePage::kPageSize);
|
||||
MOZ_ASSERT(AllOnOnePage(start, size - 1));
|
||||
|
@ -1029,7 +1014,7 @@ FlushOnePageLocked(Simulator::ICacheMap& i_cache, intptr_t start, int size)
|
|||
}
|
||||
|
||||
static void
|
||||
FlushICacheLocked(Simulator::ICacheMap& i_cache, void* start_addr, size_t size)
|
||||
FlushICacheLocked(SimulatorProcess::ICacheMap& i_cache, void* start_addr, size_t size)
|
||||
{
|
||||
intptr_t start = reinterpret_cast<intptr_t>(start_addr);
|
||||
int intra_line = (start & CachePage::kLineMask);
|
||||
|
@ -1049,14 +1034,14 @@ FlushICacheLocked(Simulator::ICacheMap& i_cache, void* start_addr, size_t size)
|
|||
FlushOnePageLocked(i_cache, start, size);
|
||||
}
|
||||
|
||||
void
|
||||
Simulator::checkICacheLocked(Simulator::ICacheMap& i_cache, SimInstruction* instr)
|
||||
/* static */ void
|
||||
SimulatorProcess::checkICacheLocked(SimInstruction* instr)
|
||||
{
|
||||
intptr_t address = reinterpret_cast<intptr_t>(instr);
|
||||
void* page = reinterpret_cast<void*>(address & (~CachePage::kPageMask));
|
||||
void* line = reinterpret_cast<void*>(address & (~CachePage::kLineMask));
|
||||
int offset = (address & CachePage::kPageMask);
|
||||
CachePage* cache_page = GetCachePageLocked(i_cache, page);
|
||||
CachePage* cache_page = GetCachePageLocked(icache(), page);
|
||||
char* cache_valid_byte = cache_page->validityByte(offset);
|
||||
bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID);
|
||||
char* cached_line = cache_page->cachedData(offset & ~CachePage::kLineMask);
|
||||
|
@ -1074,7 +1059,7 @@ Simulator::checkICacheLocked(Simulator::ICacheMap& i_cache, SimInstruction* inst
|
|||
// It is safe for the signal to arrive during the !cache_hit path, since it
|
||||
// will be cleared the next time this function is called.
|
||||
if (cacheInvalidatedBySignalHandler_) {
|
||||
i_cache.clear();
|
||||
icache().clear();
|
||||
cacheInvalidatedBySignalHandler_ = false;
|
||||
return;
|
||||
}
|
||||
|
@ -1089,13 +1074,13 @@ Simulator::checkICacheLocked(Simulator::ICacheMap& i_cache, SimInstruction* inst
|
|||
}
|
||||
|
||||
HashNumber
|
||||
Simulator::ICacheHasher::hash(const Lookup& l)
|
||||
SimulatorProcess::ICacheHasher::hash(const Lookup& l)
|
||||
{
|
||||
return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(l)) >> 2;
|
||||
}
|
||||
|
||||
bool
|
||||
Simulator::ICacheHasher::match(const Key& k, const Lookup& l)
|
||||
SimulatorProcess::ICacheHasher::match(const Key& k, const Lookup& l)
|
||||
{
|
||||
MOZ_ASSERT((reinterpret_cast<intptr_t>(k) & CachePage::kPageMask) == 0);
|
||||
MOZ_ASSERT((reinterpret_cast<intptr_t>(l) & CachePage::kPageMask) == 0);
|
||||
|
@ -1109,22 +1094,18 @@ Simulator::setLastDebuggerInput(char* input)
|
|||
lastDebuggerInput_ = input;
|
||||
}
|
||||
|
||||
void
|
||||
Simulator::FlushICache(void* start_addr, size_t size)
|
||||
/* static */ void
|
||||
SimulatorProcess::FlushICache(void* start_addr, size_t size)
|
||||
{
|
||||
JitSpewCont(JitSpew_CacheFlush, "[%p %" PRIxSIZE "]", start_addr, size);
|
||||
if (Simulator::ICacheCheckingEnabled) {
|
||||
Simulator* sim = Simulator::Current();
|
||||
|
||||
AutoLockSimulatorCache als(sim);
|
||||
|
||||
js::jit::FlushICacheLocked(sim->icache(), start_addr, size);
|
||||
if (!ICacheCheckingDisableCount) {
|
||||
AutoLockSimulatorCache als;
|
||||
js::jit::FlushICacheLocked(icache(), start_addr, size);
|
||||
}
|
||||
}
|
||||
|
||||
Simulator::Simulator(JSContext* cx)
|
||||
: cx_(cx),
|
||||
cacheLock_(mutexid::SimulatorCacheLock)
|
||||
: cx_(cx)
|
||||
{
|
||||
// Set up simulator support first. Some of this information is needed to
|
||||
// setup the architecture state.
|
||||
|
@ -1142,7 +1123,6 @@ Simulator::Simulator(JSContext* cx)
|
|||
single_stepping_ = false;
|
||||
single_step_callback_ = nullptr;
|
||||
single_step_callback_arg_ = nullptr;
|
||||
cacheInvalidatedBySignalHandler_ = false;
|
||||
skipCalleeSavedRegsCheck = false;
|
||||
|
||||
// Set up architecture state.
|
||||
|
@ -1178,7 +1158,6 @@ Simulator::Simulator(JSContext* cx)
|
|||
|
||||
lastDebuggerInput_ = nullptr;
|
||||
|
||||
redirection_ = nullptr;
|
||||
exclusiveMonitorHeld_ = false;
|
||||
exclusiveMonitor_ = 0;
|
||||
}
|
||||
|
@ -1186,9 +1165,6 @@ Simulator::Simulator(JSContext* cx)
|
|||
bool
|
||||
Simulator::init()
|
||||
{
|
||||
if (!icache_.init())
|
||||
return false;
|
||||
|
||||
// Allocate 2MB for the stack. Note that we will only use 1MB, see below.
|
||||
static const size_t stackSize = 2 * 1024*1024;
|
||||
stack_ = reinterpret_cast<char*>(js_malloc(stackSize));
|
||||
|
@ -1215,19 +1191,21 @@ Simulator::init()
|
|||
// offset from the svc instruction so the simulator knows what to call.
|
||||
class Redirection
|
||||
{
|
||||
friend class Simulator;
|
||||
friend class SimulatorProcess;
|
||||
|
||||
// sim's lock must already be held.
|
||||
Redirection(void* nativeFunction, ABIFunctionType type, Simulator* sim)
|
||||
Redirection(void* nativeFunction, ABIFunctionType type)
|
||||
: nativeFunction_(nativeFunction),
|
||||
swiInstruction_(Assembler::AL | (0xf * (1 << 24)) | kCallRtRedirected),
|
||||
type_(type),
|
||||
next_(nullptr)
|
||||
{
|
||||
next_ = sim->redirection();
|
||||
if (Simulator::ICacheCheckingEnabled)
|
||||
FlushICacheLocked(sim->icache(), addressOfSwiInstruction(), SimInstruction::kInstrSize);
|
||||
sim->setRedirection(this);
|
||||
next_ = SimulatorProcess::redirection();
|
||||
if (!SimulatorProcess::ICacheCheckingDisableCount) {
|
||||
FlushICacheLocked(SimulatorProcess::icache(), addressOfSwiInstruction(),
|
||||
SimInstruction::kInstrSize);
|
||||
}
|
||||
SimulatorProcess::setRedirection(this);
|
||||
}
|
||||
|
||||
public:
|
||||
|
@ -1236,11 +1214,9 @@ class Redirection
|
|||
ABIFunctionType type() const { return type_; }
|
||||
|
||||
static Redirection* Get(void* nativeFunction, ABIFunctionType type) {
|
||||
Simulator* sim = Simulator::Current();
|
||||
AutoLockSimulatorCache als;
|
||||
|
||||
AutoLockSimulatorCache als(sim);
|
||||
|
||||
Redirection* current = sim->redirection();
|
||||
Redirection* current = SimulatorProcess::redirection();
|
||||
for (; current != nullptr; current = current->next_) {
|
||||
if (current->nativeFunction_ == nativeFunction) {
|
||||
MOZ_ASSERT(current->type() == type);
|
||||
|
@ -1252,7 +1228,7 @@ class Redirection
|
|||
Redirection* redir = (Redirection*)js_malloc(sizeof(Redirection));
|
||||
if (!redir)
|
||||
oomUnsafe.crash("Simulator redirection");
|
||||
new(redir) Redirection(nativeFunction, type, sim);
|
||||
new(redir) Redirection(nativeFunction, type);
|
||||
return redir;
|
||||
}
|
||||
|
||||
|
@ -1272,6 +1248,15 @@ class Redirection
|
|||
Simulator::~Simulator()
|
||||
{
|
||||
js_free(stack_);
|
||||
}
|
||||
|
||||
SimulatorProcess::SimulatorProcess()
|
||||
: cacheLock_(mutexid::SimulatorCacheLock)
|
||||
, redirection_(nullptr)
|
||||
{}
|
||||
|
||||
SimulatorProcess::~SimulatorProcess()
|
||||
{
|
||||
Redirection* r = redirection_;
|
||||
while (r) {
|
||||
Redirection* next = r->next_;
|
||||
|
@ -1280,6 +1265,15 @@ Simulator::~Simulator()
|
|||
}
|
||||
}
|
||||
|
||||
bool
|
||||
SimulatorProcess::init()
|
||||
{
|
||||
if (getenv("ARM_SIM_ICACHE_CHECKS"))
|
||||
ICacheCheckingDisableCount = 0;
|
||||
|
||||
return icache_.init();
|
||||
}
|
||||
|
||||
/* static */ void*
|
||||
Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type)
|
||||
{
|
||||
|
@ -4660,9 +4654,9 @@ Simulator::decodeSpecialCondition(SimInstruction* instr)
|
|||
void
|
||||
Simulator::instructionDecode(SimInstruction* instr)
|
||||
{
|
||||
if (Simulator::ICacheCheckingEnabled) {
|
||||
AutoLockSimulatorCache als(this);
|
||||
checkICacheLocked(icache(), instr);
|
||||
if (!SimulatorProcess::ICacheCheckingDisableCount) {
|
||||
AutoLockSimulatorCache als;
|
||||
SimulatorProcess::checkICacheLocked(instr);
|
||||
}
|
||||
|
||||
pc_modified_ = false;
|
||||
|
@ -4931,7 +4925,9 @@ Simulator::call(uint8_t* entry, int argument_count, ...)
|
|||
Simulator*
|
||||
Simulator::Current()
|
||||
{
|
||||
return TlsContext.get()->runtime()->unsafeContextFromAnyThread()->simulator();
|
||||
JSContext* cx = TlsContext.get();
|
||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
|
||||
return cx->simulator();
|
||||
}
|
||||
|
||||
} // namespace jit
|
||||
|
|
|
@ -71,11 +71,9 @@ const uint32_t kVFPRoundingModeMask = 3 << 22;
|
|||
typedef int32_t Instr;
|
||||
class SimInstruction;
|
||||
|
||||
// Per thread simulator state.
|
||||
class Simulator
|
||||
{
|
||||
friend class Redirection;
|
||||
friend class AutoLockSimulatorCache;
|
||||
|
||||
public:
|
||||
friend class ArmDebugger;
|
||||
enum Register {
|
||||
|
@ -347,30 +345,7 @@ class Simulator
|
|||
// Executes one instruction.
|
||||
void instructionDecode(SimInstruction* instr);
|
||||
|
||||
private:
|
||||
// ICache checking.
|
||||
struct ICacheHasher {
|
||||
typedef void* Key;
|
||||
typedef void* Lookup;
|
||||
static HashNumber hash(const Lookup& l);
|
||||
static bool match(const Key& k, const Lookup& l);
|
||||
};
|
||||
|
||||
public:
|
||||
typedef HashMap<void*, CachePage*, ICacheHasher, SystemAllocPolicy> ICacheMap;
|
||||
|
||||
public:
|
||||
static bool ICacheCheckingEnabled;
|
||||
static void FlushICache(void* start, size_t size);
|
||||
|
||||
// Jitcode may be rewritten from a signal handler, but is prevented from
|
||||
// calling FlushICache() because the signal may arrive within the critical
|
||||
// area of an AutoLockSimulatorCache. This flag instructs the Simulator
|
||||
// to remove all cache entries the next time it checks, avoiding false negatives.
|
||||
mozilla::Atomic<bool, mozilla::ReleaseAcquire> cacheInvalidatedBySignalHandler_;
|
||||
|
||||
void checkICacheLocked(ICacheMap& i_cache, SimInstruction* instr);
|
||||
|
||||
static int64_t StopSimAt;
|
||||
|
||||
// For testing the MoveResolver code, a MoveResolver is set up, and
|
||||
|
@ -472,37 +447,6 @@ class Simulator
|
|||
return icount_;
|
||||
}
|
||||
|
||||
private:
|
||||
// This lock creates a critical section around 'redirection_' and
|
||||
// 'icache_', which are referenced both by the execution engine
|
||||
// and by the off-thread compiler (see Redirection::Get in the cpp file).
|
||||
Mutex cacheLock_;
|
||||
#ifdef DEBUG
|
||||
mozilla::Maybe<Thread::Id> cacheLockHolder_;
|
||||
#endif
|
||||
|
||||
Redirection* redirection_;
|
||||
ICacheMap icache_;
|
||||
|
||||
public:
|
||||
ICacheMap& icache() {
|
||||
// Technically we need the lock to access the innards of the
|
||||
// icache, not to take its address, but the latter condition
|
||||
// serves as a useful complement to the former.
|
||||
MOZ_ASSERT(cacheLockHolder_.isSome());
|
||||
return icache_;
|
||||
}
|
||||
|
||||
Redirection* redirection() const {
|
||||
MOZ_ASSERT(cacheLockHolder_.isSome());
|
||||
return redirection_;
|
||||
}
|
||||
|
||||
void setRedirection(js::jit::Redirection* redirection) {
|
||||
MOZ_ASSERT(cacheLockHolder_.isSome());
|
||||
redirection_ = redirection;
|
||||
}
|
||||
|
||||
private:
|
||||
// Exclusive access monitor
|
||||
void exclusiveMonitorSet(uint64_t value);
|
||||
|
@ -513,7 +457,81 @@ class Simulator
|
|||
uint64_t exclusiveMonitor_;
|
||||
};
|
||||
|
||||
#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror) \
|
||||
// Process wide simulator state.
|
||||
class SimulatorProcess
|
||||
{
|
||||
friend class Redirection;
|
||||
friend class AutoLockSimulatorCache;
|
||||
|
||||
private:
|
||||
// ICache checking.
|
||||
struct ICacheHasher {
|
||||
typedef void* Key;
|
||||
typedef void* Lookup;
|
||||
static HashNumber hash(const Lookup& l);
|
||||
static bool match(const Key& k, const Lookup& l);
|
||||
};
|
||||
|
||||
public:
|
||||
typedef HashMap<void*, CachePage*, ICacheHasher, SystemAllocPolicy> ICacheMap;
|
||||
|
||||
static mozilla::Atomic<size_t, mozilla::ReleaseAcquire> ICacheCheckingDisableCount;
|
||||
static void FlushICache(void* start, size_t size);
|
||||
|
||||
// Jitcode may be rewritten from a signal handler, but is prevented from
|
||||
// calling FlushICache() because the signal may arrive within the critical
|
||||
// area of an AutoLockSimulatorCache. This flag instructs the Simulator
|
||||
// to remove all cache entries the next time it checks, avoiding false negatives.
|
||||
static mozilla::Atomic<bool, mozilla::ReleaseAcquire> cacheInvalidatedBySignalHandler_;
|
||||
|
||||
static void checkICacheLocked(SimInstruction* instr);
|
||||
|
||||
static bool initialize() {
|
||||
singleton_ = js_new<SimulatorProcess>();
|
||||
return singleton_ && singleton_->init();
|
||||
}
|
||||
static void destroy() {
|
||||
js_delete(singleton_);
|
||||
singleton_ = nullptr;
|
||||
}
|
||||
|
||||
SimulatorProcess();
|
||||
~SimulatorProcess();
|
||||
|
||||
private:
|
||||
bool init();
|
||||
|
||||
static SimulatorProcess* singleton_;
|
||||
|
||||
// This lock creates a critical section around 'redirection_' and
|
||||
// 'icache_', which are referenced both by the execution engine
|
||||
// and by the off-thread compiler (see Redirection::Get in the cpp file).
|
||||
Mutex cacheLock_;
|
||||
|
||||
Redirection* redirection_;
|
||||
ICacheMap icache_;
|
||||
|
||||
public:
|
||||
static ICacheMap& icache() {
|
||||
// Technically we need the lock to access the innards of the
|
||||
// icache, not to take its address, but the latter condition
|
||||
// serves as a useful complement to the former.
|
||||
MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
|
||||
return singleton_->icache_;
|
||||
}
|
||||
|
||||
static Redirection* redirection() {
|
||||
MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
|
||||
return singleton_->redirection_;
|
||||
}
|
||||
|
||||
static void setRedirection(js::jit::Redirection* redirection) {
|
||||
MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
|
||||
singleton_->redirection_ = redirection;
|
||||
}
|
||||
};
|
||||
|
||||
#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror) \
|
||||
JS_BEGIN_MACRO \
|
||||
if (cx->simulator()->overRecursedWithExtra(extra)) { \
|
||||
js::ReportOverRecursed(cx); \
|
||||
|
|
|
@ -32,11 +32,14 @@
|
|||
#include "threading/LockGuard.h"
|
||||
#include "vm/Runtime.h"
|
||||
|
||||
js::jit::SimulatorProcess* js::jit::SimulatorProcess::singleton_ = nullptr;
|
||||
|
||||
namespace vixl {
|
||||
|
||||
|
||||
using mozilla::DebugOnly;
|
||||
using js::jit::ABIFunctionType;
|
||||
using js::jit::SimulatorProcess;
|
||||
|
||||
Simulator::Simulator(Decoder* decoder, FILE* stream)
|
||||
: stream_(nullptr)
|
||||
|
@ -46,7 +49,6 @@ Simulator::Simulator(Decoder* decoder, FILE* stream)
|
|||
, stack_limit_(nullptr)
|
||||
, decoder_(nullptr)
|
||||
, oom_(false)
|
||||
, lock_(js::mutexid::Arm64SimulatorLock)
|
||||
{
|
||||
this->init(decoder, stream);
|
||||
}
|
||||
|
@ -144,13 +146,13 @@ void Simulator::init(Decoder* decoder, FILE* stream) {
|
|||
// time they are encountered. This warning can be silenced using
|
||||
// SilenceExclusiveAccessWarning().
|
||||
print_exclusive_access_warning_ = true;
|
||||
|
||||
redirection_ = nullptr;
|
||||
}
|
||||
|
||||
|
||||
Simulator* Simulator::Current() {
|
||||
return js::TlsContext.get()->simulator();
|
||||
JSContext* cx = js::TlsContext.get();
|
||||
MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(cx->runtime()));
|
||||
return cx->simulator();
|
||||
}
|
||||
|
||||
|
||||
|
@ -294,8 +296,8 @@ class AutoLockSimulatorCache : public js::LockGuard<js::Mutex>
|
|||
using Base = js::LockGuard<js::Mutex>;
|
||||
|
||||
public:
|
||||
explicit AutoLockSimulatorCache(Simulator* sim)
|
||||
: Base(sim->lock_)
|
||||
explicit AutoLockSimulatorCache()
|
||||
: Base(SimulatorProcess::singleton_->lock_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
@ -311,14 +313,14 @@ class Redirection
|
|||
{
|
||||
friend class Simulator;
|
||||
|
||||
Redirection(void* nativeFunction, ABIFunctionType type, Simulator* sim)
|
||||
Redirection(void* nativeFunction, ABIFunctionType type)
|
||||
: nativeFunction_(nativeFunction),
|
||||
type_(type),
|
||||
next_(nullptr)
|
||||
{
|
||||
next_ = sim->redirection();
|
||||
next_ = SimulatorProcess::redirection();
|
||||
// TODO: Flush ICache?
|
||||
sim->setRedirection(this);
|
||||
SimulatorProcess::setRedirection(this);
|
||||
|
||||
Instruction* instr = (Instruction*)(&svcInstruction_);
|
||||
vixl::Assembler::svc(instr, kCallRtRedirected);
|
||||
|
@ -330,13 +332,12 @@ class Redirection
|
|||
ABIFunctionType type() const { return type_; }
|
||||
|
||||
static Redirection* Get(void* nativeFunction, ABIFunctionType type) {
|
||||
Simulator* sim = Simulator::Current();
|
||||
AutoLockSimulatorCache alsr(sim);
|
||||
AutoLockSimulatorCache alsr;
|
||||
|
||||
// TODO: Store srt_ in the simulator for this assertion.
|
||||
// VIXL_ASSERT_IF(pt->simulator(), pt->simulator()->srt_ == srt);
|
||||
|
||||
Redirection* current = sim->redirection();
|
||||
Redirection* current = SimulatorProcess::redirection();
|
||||
for (; current != nullptr; current = current->next_) {
|
||||
if (current->nativeFunction_ == nativeFunction) {
|
||||
VIXL_ASSERT(current->type() == type);
|
||||
|
@ -348,7 +349,7 @@ class Redirection
|
|||
Redirection* redir = (Redirection*)js_malloc(sizeof(Redirection));
|
||||
if (!redir)
|
||||
oomUnsafe.crash("Simulator redirection");
|
||||
new(redir) Redirection(nativeFunction, type, sim);
|
||||
new(redir) Redirection(nativeFunction, type);
|
||||
return redir;
|
||||
}
|
||||
|
||||
|
@ -366,14 +367,6 @@ class Redirection
|
|||
};
|
||||
|
||||
|
||||
void Simulator::setRedirection(Redirection* redirection) {
|
||||
redirection_ = redirection;
|
||||
}
|
||||
|
||||
|
||||
Redirection* Simulator::redirection() const {
|
||||
return redirection_;
|
||||
}
|
||||
|
||||
|
||||
void* Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type) {
|
||||
|
|
|
@ -704,8 +704,6 @@ class SimExclusiveGlobalMonitor {
|
|||
class Redirection;
|
||||
|
||||
class Simulator : public DecoderVisitor {
|
||||
friend class AutoLockSimulatorCache;
|
||||
|
||||
public:
|
||||
explicit Simulator(Decoder* decoder, FILE* stream = stdout);
|
||||
~Simulator();
|
||||
|
@ -720,8 +718,6 @@ class Simulator : public DecoderVisitor {
|
|||
bool overRecursed(uintptr_t newsp = 0) const;
|
||||
bool overRecursedWithExtra(uint32_t extra) const;
|
||||
int64_t call(uint8_t* entry, int argument_count, ...);
|
||||
void setRedirection(Redirection* redirection);
|
||||
Redirection* redirection() const;
|
||||
static void* RedirectNativeFunction(void* nativeFunction, js::jit::ABIFunctionType type);
|
||||
void setGPR32Result(int32_t result);
|
||||
void setGPR64Result(int64_t result);
|
||||
|
@ -2666,12 +2662,50 @@ class Simulator : public DecoderVisitor {
|
|||
bool oom() const { return oom_; }
|
||||
|
||||
protected:
|
||||
// Moz: Synchronizes access between main thread and compilation threads.
|
||||
js::Mutex lock_;
|
||||
Redirection* redirection_;
|
||||
mozilla::Vector<int64_t, 0, js::SystemAllocPolicy> spStack_;
|
||||
};
|
||||
|
||||
} // namespace vixl
|
||||
|
||||
namespace js {
|
||||
namespace jit {
|
||||
|
||||
class SimulatorProcess
|
||||
{
|
||||
public:
|
||||
static SimulatorProcess* singleton_;
|
||||
|
||||
SimulatorProcess()
|
||||
: lock_(mutexid::Arm64SimulatorLock)
|
||||
, redirection_(nullptr)
|
||||
{}
|
||||
|
||||
// Synchronizes access between main thread and compilation threads.
|
||||
js::Mutex lock_;
|
||||
vixl::Redirection* redirection_;
|
||||
|
||||
static void setRedirection(vixl::Redirection* redirection) {
|
||||
MOZ_ASSERT(singleton_->lock_.ownedByCurrentThread());
|
||||
singleton_->redirection_ = redirection;
|
||||
}
|
||||
|
||||
static vixl::Redirection* redirection() {
|
||||
MOZ_ASSERT(singleton_->lock_.ownedByCurrentThread());
|
||||
return singleton_->redirection_;
|
||||
}
|
||||
|
||||
static bool initialize() {
|
||||
singleton_ = js_new<SimulatorProcess>();
|
||||
return !!singleton_;
|
||||
}
|
||||
static void destroy() {
|
||||
js_delete(singleton_);
|
||||
singleton_ = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace jit
|
||||
} // namespace js
|
||||
|
||||
#endif // JS_SIMULATOR_ARM64
|
||||
#endif // VIXL_A64_SIMULATOR_A64_H_
|
||||
|
|
|
@ -499,28 +499,16 @@ class AutoLockSimulatorCache : public LockGuard<Mutex>
|
|||
using Base = LockGuard<Mutex>;
|
||||
|
||||
public:
|
||||
explicit AutoLockSimulatorCache(Simulator* sim)
|
||||
: Base(sim->cacheLock_)
|
||||
, sim_(sim)
|
||||
{
|
||||
MOZ_ASSERT(sim_->cacheLockHolder_.isNothing());
|
||||
#ifdef DEBUG
|
||||
sim_->cacheLockHolder_ = mozilla::Some(ThisThread::GetId());
|
||||
#endif
|
||||
}
|
||||
|
||||
~AutoLockSimulatorCache() {
|
||||
MOZ_ASSERT(sim_->cacheLockHolder_.isSome());
|
||||
#ifdef DEBUG
|
||||
sim_->cacheLockHolder_.reset();
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
Simulator* const sim_;
|
||||
AutoLockSimulatorCache()
|
||||
: Base(SimulatorProcess::singleton_->cacheLock_)
|
||||
{}
|
||||
};
|
||||
|
||||
bool Simulator::ICacheCheckingEnabled = false;
|
||||
mozilla::Atomic<size_t, mozilla::ReleaseAcquire>
|
||||
SimulatorProcess::ICacheCheckingDisableCount(1); // Checking is disabled by default.
|
||||
mozilla::Atomic<bool, mozilla::ReleaseAcquire>
|
||||
SimulatorProcess::cacheInvalidatedBySignalHandler_(false);
|
||||
SimulatorProcess* SimulatorProcess::singleton_ = nullptr;
|
||||
|
||||
int Simulator::StopSimAt = -1;
|
||||
|
||||
|
@ -536,9 +524,6 @@ Simulator::Create(JSContext* cx)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (getenv("MIPS_SIM_ICACHE_CHECKS"))
|
||||
Simulator::ICacheCheckingEnabled = true;
|
||||
|
||||
char* stopAtStr = getenv("MIPS_SIM_STOP_AT");
|
||||
int64_t stopAt;
|
||||
if (stopAtStr && sscanf(stopAtStr, "%lld", &stopAt) == 1) {
|
||||
|
@ -1151,9 +1136,9 @@ Simulator::setLastDebuggerInput(char* input)
|
|||
}
|
||||
|
||||
static CachePage*
|
||||
GetCachePageLocked(Simulator::ICacheMap& i_cache, void* page)
|
||||
GetCachePageLocked(SimulatorProcess::ICacheMap& i_cache, void* page)
|
||||
{
|
||||
Simulator::ICacheMap::AddPtr p = i_cache.lookupForAdd(page);
|
||||
SimulatorProcess::ICacheMap::AddPtr p = i_cache.lookupForAdd(page);
|
||||
if (p)
|
||||
return p->value();
|
||||
|
||||
|
@ -1165,7 +1150,7 @@ GetCachePageLocked(Simulator::ICacheMap& i_cache, void* page)
|
|||
|
||||
// Flush from start up to and not including start + size.
|
||||
static void
|
||||
FlushOnePageLocked(Simulator::ICacheMap& i_cache, intptr_t start, int size)
|
||||
FlushOnePageLocked(SimulatorProcess::ICacheMap& i_cache, intptr_t start, int size)
|
||||
{
|
||||
MOZ_ASSERT(size <= CachePage::kPageSize);
|
||||
MOZ_ASSERT(AllOnOnePage(start, size - 1));
|
||||
|
@ -1179,7 +1164,7 @@ FlushOnePageLocked(Simulator::ICacheMap& i_cache, intptr_t start, int size)
|
|||
}
|
||||
|
||||
static void
|
||||
FlushICacheLocked(Simulator::ICacheMap& i_cache, void* start_addr, size_t size)
|
||||
FlushICacheLocked(SimulatorProcess::ICacheMap& i_cache, void* start_addr, size_t size)
|
||||
{
|
||||
intptr_t start = reinterpret_cast<intptr_t>(start_addr);
|
||||
int intra_line = (start & CachePage::kLineMask);
|
||||
|
@ -1200,14 +1185,14 @@ FlushICacheLocked(Simulator::ICacheMap& i_cache, void* start_addr, size_t size)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
Simulator::checkICacheLocked(Simulator::ICacheMap& i_cache, SimInstruction* instr)
|
||||
/* static */ void
|
||||
SimulatorProcess::checkICacheLocked(SimInstruction* instr)
|
||||
{
|
||||
intptr_t address = reinterpret_cast<intptr_t>(instr);
|
||||
void* page = reinterpret_cast<void*>(address & (~CachePage::kPageMask));
|
||||
void* line = reinterpret_cast<void*>(address & (~CachePage::kLineMask));
|
||||
int offset = (address & CachePage::kPageMask);
|
||||
CachePage* cache_page = GetCachePageLocked(i_cache, page);
|
||||
CachePage* cache_page = GetCachePageLocked(icache(), page);
|
||||
char* cache_valid_byte = cache_page->validityByte(offset);
|
||||
bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID);
|
||||
char* cached_line = cache_page->cachedData(offset & ~CachePage::kLineMask);
|
||||
|
@ -1225,7 +1210,7 @@ Simulator::checkICacheLocked(Simulator::ICacheMap& i_cache, SimInstruction* inst
|
|||
// It is safe for the signal to arrive during the !cache_hit path, since it
|
||||
// will be cleared the next time this function is called.
|
||||
if (cacheInvalidatedBySignalHandler_) {
|
||||
i_cache.clear();
|
||||
icache().clear();
|
||||
cacheInvalidatedBySignalHandler_ = false;
|
||||
return;
|
||||
}
|
||||
|
@ -1240,32 +1225,29 @@ Simulator::checkICacheLocked(Simulator::ICacheMap& i_cache, SimInstruction* inst
|
|||
}
|
||||
|
||||
HashNumber
|
||||
Simulator::ICacheHasher::hash(const Lookup& l)
|
||||
SimulatorProcess::ICacheHasher::hash(const Lookup& l)
|
||||
{
|
||||
return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(l)) >> 2;
|
||||
}
|
||||
|
||||
bool
|
||||
Simulator::ICacheHasher::match(const Key& k, const Lookup& l)
|
||||
SimulatorProcess::ICacheHasher::match(const Key& k, const Lookup& l)
|
||||
{
|
||||
MOZ_ASSERT((reinterpret_cast<intptr_t>(k) & CachePage::kPageMask) == 0);
|
||||
MOZ_ASSERT((reinterpret_cast<intptr_t>(l) & CachePage::kPageMask) == 0);
|
||||
return k == l;
|
||||
}
|
||||
|
||||
void
|
||||
Simulator::FlushICache(void* start_addr, size_t size)
|
||||
/* static */ void
|
||||
SimulatorProcess::FlushICache(void* start_addr, size_t size)
|
||||
{
|
||||
if (Simulator::ICacheCheckingEnabled) {
|
||||
Simulator* sim = Simulator::Current();
|
||||
AutoLockSimulatorCache als(sim);
|
||||
js::jit::FlushICacheLocked(sim->icache(), start_addr, size);
|
||||
if (!ICacheCheckingDisableCount) {
|
||||
AutoLockSimulatorCache als;
|
||||
js::jit::FlushICacheLocked(icache(), start_addr, size);
|
||||
}
|
||||
}
|
||||
|
||||
Simulator::Simulator()
|
||||
: cacheLock_(mutexid::SimulatorCacheLock),
|
||||
cacheInvalidatedBySignalHandler_(false)
|
||||
{
|
||||
// Set up simulator support first. Some of this information is needed to
|
||||
// setup the architecture state.
|
||||
|
@ -1301,16 +1283,11 @@ Simulator::Simulator()
|
|||
exceptions[i] = 0;
|
||||
|
||||
lastDebuggerInput_ = nullptr;
|
||||
|
||||
redirection_ = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
Simulator::init()
|
||||
{
|
||||
if (!icache_.init())
|
||||
return false;
|
||||
|
||||
// Allocate 2MB for the stack. Note that we will only use 1MB, see below.
|
||||
static const size_t stackSize = 2 * 1024 * 1024;
|
||||
stack_ = static_cast<char*>(js_malloc(stackSize));
|
||||
|
@ -1338,19 +1315,21 @@ Simulator::init()
|
|||
// offset from the swi instruction so the simulator knows what to call.
|
||||
class Redirection
|
||||
{
|
||||
friend class Simulator;
|
||||
friend class SimulatorProcess;
|
||||
|
||||
// sim's lock must already be held.
|
||||
Redirection(void* nativeFunction, ABIFunctionType type, Simulator* sim)
|
||||
Redirection(void* nativeFunction, ABIFunctionType type)
|
||||
: nativeFunction_(nativeFunction),
|
||||
swiInstruction_(kCallRedirInstr),
|
||||
type_(type),
|
||||
next_(nullptr)
|
||||
{
|
||||
next_ = sim->redirection();
|
||||
if (Simulator::ICacheCheckingEnabled)
|
||||
FlushICacheLocked(sim->icache(), addressOfSwiInstruction(), SimInstruction::kInstrSize);
|
||||
sim->setRedirection(this);
|
||||
next_ = SimulatorProcess::redirection();
|
||||
if (!SimulatorProcess::ICacheCheckingDisableCount) {
|
||||
FlushICacheLocked(SimulatorProcess::icache(), addressOfSwiInstruction(),
|
||||
SimInstruction::kInstrSize);
|
||||
}
|
||||
SimulatorProcess::setRedirection(this);
|
||||
}
|
||||
|
||||
public:
|
||||
|
@ -1359,11 +1338,9 @@ class Redirection
|
|||
ABIFunctionType type() const { return type_; }
|
||||
|
||||
static Redirection* Get(void* nativeFunction, ABIFunctionType type) {
|
||||
Simulator* sim = Simulator::Current();
|
||||
AutoLockSimulatorCache als;
|
||||
|
||||
AutoLockSimulatorCache als(sim);
|
||||
|
||||
Redirection* current = sim->redirection();
|
||||
Redirection* current = SimulatorProcess::redirection();
|
||||
for (; current != nullptr; current = current->next_) {
|
||||
if (current->nativeFunction_ == nativeFunction) {
|
||||
MOZ_ASSERT(current->type() == type);
|
||||
|
@ -1377,7 +1354,7 @@ class Redirection
|
|||
__FILE__, __LINE__);
|
||||
MOZ_CRASH();
|
||||
}
|
||||
new(redir) Redirection(nativeFunction, type, sim);
|
||||
new(redir) Redirection(nativeFunction, type);
|
||||
return redir;
|
||||
}
|
||||
|
||||
|
@ -1397,6 +1374,15 @@ class Redirection
|
|||
Simulator::~Simulator()
|
||||
{
|
||||
js_free(stack_);
|
||||
}
|
||||
|
||||
SimulatorProcess::SimulatorProcess()
|
||||
: cacheLock_(mutexid::SimulatorCacheLock)
|
||||
, redirection_(nullptr)
|
||||
{}
|
||||
|
||||
SimulatorProcess::~SimulatorProcess()
|
||||
{
|
||||
Redirection* r = redirection_;
|
||||
while (r) {
|
||||
Redirection* next = r->next_;
|
||||
|
@ -1405,6 +1391,15 @@ Simulator::~Simulator()
|
|||
}
|
||||
}
|
||||
|
||||
bool
|
||||
SimulatorProcess::init()
|
||||
{
|
||||
if (getenv("MIPS_SIM_ICACHE_CHECKS"))
|
||||
ICacheCheckingDisableCount = 0;
|
||||
|
||||
return icache_.init();
|
||||
}
|
||||
|
||||
/* static */ void*
|
||||
Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type)
|
||||
{
|
||||
|
@ -1416,7 +1411,9 @@ Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type)
|
|||
Simulator*
|
||||
Simulator::Current()
|
||||
{
|
||||
return TlsContext.get()->runtime()->unsafeContextFromAnyThread()->simulator();
|
||||
JSContext* cx = TlsContext.get();
|
||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
|
||||
return cx->simulator();
|
||||
}
|
||||
|
||||
// Sets the register in the architecture state. It will also deal with updating
|
||||
|
@ -3360,9 +3357,9 @@ Simulator::decodeTypeJump(SimInstruction* instr)
|
|||
void
|
||||
Simulator::instructionDecode(SimInstruction* instr)
|
||||
{
|
||||
if (Simulator::ICacheCheckingEnabled) {
|
||||
AutoLockSimulatorCache als(this);
|
||||
checkICacheLocked(icache(), instr);
|
||||
if (!SimulatorProcess::ICacheCheckingDisableCount) {
|
||||
AutoLockSimulatorCache als;
|
||||
SimulatorProcess::checkICacheLocked(instr);
|
||||
}
|
||||
pc_modified_ = false;
|
||||
|
||||
|
|
|
@ -104,10 +104,9 @@ const uint32_t kMaxStopCode = 127;
|
|||
typedef uint32_t Instr;
|
||||
class SimInstruction;
|
||||
|
||||
// Per thread simulator state.
|
||||
class Simulator {
|
||||
friend class Redirection;
|
||||
friend class MipsDebugger;
|
||||
friend class AutoLockSimulatorCache;
|
||||
public:
|
||||
|
||||
// Registers are declared in order. See "See MIPS Run Linux" chapter 2.
|
||||
|
@ -214,8 +213,6 @@ class Simulator {
|
|||
// Debugger input.
|
||||
void setLastDebuggerInput(char* input);
|
||||
char* lastDebuggerInput() { return lastDebuggerInput_; }
|
||||
// ICache checking.
|
||||
static void FlushICache(void* start, size_t size);
|
||||
|
||||
// Returns true if pc register contains one of the 'SpecialValues' defined
|
||||
// below (bad_ra, end_sim_pc).
|
||||
|
@ -365,6 +362,13 @@ class Simulator {
|
|||
char* desc_;
|
||||
};
|
||||
StopCountAndDesc watchedStops_[kNumOfWatchedStops];
|
||||
};
|
||||
|
||||
// Process wide simulator state.
|
||||
class SimulatorProcess
|
||||
{
|
||||
friend class Redirection;
|
||||
friend class AutoLockSimulatorCache;
|
||||
|
||||
private:
|
||||
// ICache checking.
|
||||
|
@ -378,44 +382,59 @@ class Simulator {
|
|||
public:
|
||||
typedef HashMap<void*, CachePage*, ICacheHasher, SystemAllocPolicy> ICacheMap;
|
||||
|
||||
private:
|
||||
// This lock creates a critical section around 'redirection_' and
|
||||
// 'icache_', which are referenced both by the execution engine
|
||||
// and by the off-thread compiler (see Redirection::Get in the cpp file).
|
||||
Mutex cacheLock_;
|
||||
#ifdef DEBUG
|
||||
mozilla::Maybe<Thread::Id> cacheLockHolder_;
|
||||
#endif
|
||||
static mozilla::Atomic<size_t, mozilla::ReleaseAcquire> ICacheCheckingDisableCount;
|
||||
static void FlushICache(void* start, size_t size);
|
||||
|
||||
Redirection* redirection_;
|
||||
ICacheMap icache_;
|
||||
|
||||
private:
|
||||
// Jitcode may be rewritten from a signal handler, but is prevented from
|
||||
// calling FlushICache() because the signal may arrive within the critical
|
||||
// area of an AutoLockSimulatorCache. This flag instructs the Simulator
|
||||
// to remove all cache entries the next time it checks, avoiding false negatives.
|
||||
mozilla::Atomic<bool, mozilla::ReleaseAcquire> cacheInvalidatedBySignalHandler_;
|
||||
static mozilla::Atomic<bool, mozilla::ReleaseAcquire> cacheInvalidatedBySignalHandler_;
|
||||
|
||||
void checkICacheLocked(ICacheMap& i_cache, SimInstruction* instr);
|
||||
static void checkICacheLocked(SimInstruction* instr);
|
||||
|
||||
static bool initialize() {
|
||||
singleton_ = js_new<SimulatorProcess>();
|
||||
return singleton_ && singleton_->init();
|
||||
}
|
||||
static void destroy() {
|
||||
js_delete(singleton_);
|
||||
singleton_ = nullptr;
|
||||
}
|
||||
|
||||
SimulatorProcess();
|
||||
~SimulatorProcess();
|
||||
|
||||
private:
|
||||
bool init();
|
||||
|
||||
static SimulatorProcess* singleton_;
|
||||
|
||||
// This lock creates a critical section around 'redirection_' and
|
||||
// 'icache_', which are referenced both by the execution engine
|
||||
// and by the off-thread compiler (see Redirection::Get in the cpp file).
|
||||
Mutex cacheLock_;
|
||||
|
||||
Redirection* redirection_;
|
||||
ICacheMap icache_;
|
||||
|
||||
public:
|
||||
ICacheMap& icache() {
|
||||
static ICacheMap& icache() {
|
||||
// Technically we need the lock to access the innards of the
|
||||
// icache, not to take its address, but the latter condition
|
||||
// serves as a useful complement to the former.
|
||||
MOZ_ASSERT(cacheLockHolder_.isSome());
|
||||
return icache_;
|
||||
MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
|
||||
return singleton_->icache_;
|
||||
}
|
||||
|
||||
Redirection* redirection() const {
|
||||
MOZ_ASSERT(cacheLockHolder_.isSome());
|
||||
return redirection_;
|
||||
static Redirection* redirection() {
|
||||
MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
|
||||
return singleton_->redirection_;
|
||||
}
|
||||
|
||||
void setRedirection(js::jit::Redirection* redirection) {
|
||||
MOZ_ASSERT(cacheLockHolder_.isSome());
|
||||
redirection_ = redirection;
|
||||
static void setRedirection(js::jit::Redirection* redirection) {
|
||||
MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
|
||||
singleton_->redirection_ = redirection;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -531,28 +531,16 @@ class AutoLockSimulatorCache : public LockGuard<Mutex>
|
|||
using Base = LockGuard<Mutex>;
|
||||
|
||||
public:
|
||||
explicit AutoLockSimulatorCache(Simulator* sim)
|
||||
: Base(sim->cacheLock_)
|
||||
, sim_(sim)
|
||||
{
|
||||
MOZ_ASSERT(sim_->cacheLockHolder_.isNothing());
|
||||
#ifdef DEBUG
|
||||
sim_->cacheLockHolder_ = mozilla::Some(ThisThread::GetId());
|
||||
#endif
|
||||
}
|
||||
|
||||
~AutoLockSimulatorCache() {
|
||||
MOZ_ASSERT(sim_->cacheLockHolder_.isSome());
|
||||
#ifdef DEBUG
|
||||
sim_->cacheLockHolder_.reset();
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
Simulator* const sim_;
|
||||
explicit AutoLockSimulatorCache()
|
||||
: Base(SimulatorProcess::singleton_->cacheLock_)
|
||||
{}
|
||||
};
|
||||
|
||||
bool Simulator::ICacheCheckingEnabled = false;
|
||||
mozilla::Atomic<size_t, mozilla::ReleaseAcquire>
|
||||
SimulatorProcess::ICacheCheckingDisableCount(1); // Checking is disabled by default.
|
||||
mozilla::Atomic<bool, mozilla::ReleaseAcquire>
|
||||
SimulatorProcess::cacheInvalidatedBySignalHandler_(false);
|
||||
SimulatorProcess* SimulatorProcess::singleton_ = nullptr;
|
||||
|
||||
int64_t Simulator::StopSimAt = -1;
|
||||
|
||||
|
@ -568,9 +556,6 @@ Simulator::Create(JSContext* cx)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (getenv("MIPS_SIM_ICACHE_CHECKS"))
|
||||
Simulator::ICacheCheckingEnabled = true;
|
||||
|
||||
int64_t stopAt;
|
||||
char* stopAtStr = getenv("MIPS_SIM_STOP_AT");
|
||||
if (stopAtStr && sscanf(stopAtStr, "%" PRIi64, &stopAt) == 1) {
|
||||
|
@ -1159,9 +1144,9 @@ Simulator::setLastDebuggerInput(char* input)
|
|||
}
|
||||
|
||||
static CachePage*
|
||||
GetCachePageLocked(Simulator::ICacheMap& i_cache, void* page)
|
||||
GetCachePageLocked(SimulatorProcess::ICacheMap& i_cache, void* page)
|
||||
{
|
||||
Simulator::ICacheMap::AddPtr p = i_cache.lookupForAdd(page);
|
||||
SimulatorProcess::ICacheMap::AddPtr p = i_cache.lookupForAdd(page);
|
||||
if (p)
|
||||
return p->value();
|
||||
|
||||
|
@ -1173,7 +1158,7 @@ GetCachePageLocked(Simulator::ICacheMap& i_cache, void* page)
|
|||
|
||||
// Flush from start up to and not including start + size.
|
||||
static void
|
||||
FlushOnePageLocked(Simulator::ICacheMap& i_cache, intptr_t start, int size)
|
||||
FlushOnePageLocked(SimulatorProcess::ICacheMap& i_cache, intptr_t start, int size)
|
||||
{
|
||||
MOZ_ASSERT(size <= CachePage::kPageSize);
|
||||
MOZ_ASSERT(AllOnOnePage(start, size - 1));
|
||||
|
@ -1187,7 +1172,7 @@ FlushOnePageLocked(Simulator::ICacheMap& i_cache, intptr_t start, int size)
|
|||
}
|
||||
|
||||
static void
|
||||
FlushICacheLocked(Simulator::ICacheMap& i_cache, void* start_addr, size_t size)
|
||||
FlushICacheLocked(SimulatorProcess::ICacheMap& i_cache, void* start_addr, size_t size)
|
||||
{
|
||||
intptr_t start = reinterpret_cast<intptr_t>(start_addr);
|
||||
int intra_line = (start & CachePage::kLineMask);
|
||||
|
@ -1207,14 +1192,14 @@ FlushICacheLocked(Simulator::ICacheMap& i_cache, void* start_addr, size_t size)
|
|||
FlushOnePageLocked(i_cache, start, size);
|
||||
}
|
||||
|
||||
void
|
||||
Simulator::checkICacheLocked(Simulator::ICacheMap& i_cache, SimInstruction* instr)
|
||||
/* static */ void
|
||||
SimulatorProcess::checkICacheLocked(SimInstruction* instr)
|
||||
{
|
||||
intptr_t address = reinterpret_cast<intptr_t>(instr);
|
||||
void* page = reinterpret_cast<void*>(address & (~CachePage::kPageMask));
|
||||
void* line = reinterpret_cast<void*>(address & (~CachePage::kLineMask));
|
||||
int offset = (address & CachePage::kPageMask);
|
||||
CachePage* cache_page = GetCachePageLocked(i_cache, page);
|
||||
CachePage* cache_page = GetCachePageLocked(icache(), page);
|
||||
char* cache_valid_byte = cache_page->validityByte(offset);
|
||||
bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID);
|
||||
char* cached_line = cache_page->cachedData(offset & ~CachePage::kLineMask);
|
||||
|
@ -1232,7 +1217,7 @@ Simulator::checkICacheLocked(Simulator::ICacheMap& i_cache, SimInstruction* inst
|
|||
// It is safe for the signal to arrive during the !cache_hit path, since it
|
||||
// will be cleared the next time this function is called.
|
||||
if (cacheInvalidatedBySignalHandler_) {
|
||||
i_cache.clear();
|
||||
icache().clear();
|
||||
cacheInvalidatedBySignalHandler_ = false;
|
||||
return;
|
||||
}
|
||||
|
@ -1247,32 +1232,29 @@ Simulator::checkICacheLocked(Simulator::ICacheMap& i_cache, SimInstruction* inst
|
|||
}
|
||||
|
||||
HashNumber
|
||||
Simulator::ICacheHasher::hash(const Lookup& l)
|
||||
SimulatorProcess::ICacheHasher::hash(const Lookup& l)
|
||||
{
|
||||
return U32(reinterpret_cast<uintptr_t>(l)) >> 2;
|
||||
}
|
||||
|
||||
bool
|
||||
Simulator::ICacheHasher::match(const Key& k, const Lookup& l)
|
||||
SimulatorProcess::ICacheHasher::match(const Key& k, const Lookup& l)
|
||||
{
|
||||
MOZ_ASSERT((reinterpret_cast<intptr_t>(k) & CachePage::kPageMask) == 0);
|
||||
MOZ_ASSERT((reinterpret_cast<intptr_t>(l) & CachePage::kPageMask) == 0);
|
||||
return k == l;
|
||||
}
|
||||
|
||||
void
|
||||
Simulator::FlushICache(void* start_addr, size_t size)
|
||||
/* static */ void
|
||||
SimulatorProcess::FlushICache(void* start_addr, size_t size)
|
||||
{
|
||||
if (Simulator::ICacheCheckingEnabled) {
|
||||
Simulator* sim = Simulator::Current();
|
||||
AutoLockSimulatorCache als(sim);
|
||||
js::jit::FlushICacheLocked(sim->icache(), start_addr, size);
|
||||
if (!ICacheCheckingDisableCount) {
|
||||
AutoLockSimulatorCache als;
|
||||
js::jit::FlushICacheLocked(icache(), start_addr, size);
|
||||
}
|
||||
}
|
||||
|
||||
Simulator::Simulator()
|
||||
: cacheLock_(mutexid::SimulatorCacheLock),
|
||||
cacheInvalidatedBySignalHandler_(false)
|
||||
{
|
||||
// Set up simulator support first. Some of this information is needed to
|
||||
// setup the architecture state.
|
||||
|
@ -1309,16 +1291,11 @@ Simulator::Simulator()
|
|||
exceptions[i] = 0;
|
||||
|
||||
lastDebuggerInput_ = nullptr;
|
||||
|
||||
redirection_ = nullptr;
|
||||
}
|
||||
|
||||
bool
|
||||
Simulator::init()
|
||||
{
|
||||
if (!icache_.init())
|
||||
return false;
|
||||
|
||||
// Allocate 2MB for the stack. Note that we will only use 1MB, see below.
|
||||
static const size_t stackSize = 2 * 1024 * 1024;
|
||||
stack_ = static_cast<char*>(js_malloc(stackSize));
|
||||
|
@ -1346,19 +1323,21 @@ Simulator::init()
|
|||
// offset from the swi instruction so the simulator knows what to call.
|
||||
class Redirection
|
||||
{
|
||||
friend class Simulator;
|
||||
friend class SimulatorProcess;
|
||||
|
||||
// sim's lock must already be held.
|
||||
Redirection(void* nativeFunction, ABIFunctionType type, Simulator* sim)
|
||||
Redirection(void* nativeFunction, ABIFunctionType type)
|
||||
: nativeFunction_(nativeFunction),
|
||||
swiInstruction_(kCallRedirInstr),
|
||||
type_(type),
|
||||
next_(nullptr)
|
||||
{
|
||||
next_ = sim->redirection();
|
||||
if (Simulator::ICacheCheckingEnabled)
|
||||
FlushICacheLocked(sim->icache(), addressOfSwiInstruction(), SimInstruction::kInstrSize);
|
||||
sim->setRedirection(this);
|
||||
next_ = SimulatorProcess::redirection();
|
||||
if (!SimulatorProcess::ICacheCheckingDisableCount) {
|
||||
FlushICacheLocked(SimulatorProcess::icache(), addressOfSwiInstruction(),
|
||||
SimInstruction::kInstrSize);
|
||||
}
|
||||
SimulatorProcess::setRedirection(this);
|
||||
}
|
||||
|
||||
public:
|
||||
|
@ -1367,11 +1346,9 @@ class Redirection
|
|||
ABIFunctionType type() const { return type_; }
|
||||
|
||||
static Redirection* Get(void* nativeFunction, ABIFunctionType type) {
|
||||
Simulator* sim = Simulator::Current();
|
||||
AutoLockSimulatorCache als;
|
||||
|
||||
AutoLockSimulatorCache als(sim);
|
||||
|
||||
Redirection* current = sim->redirection();
|
||||
Redirection* current = SimulatorProcess::redirection();
|
||||
for (; current != nullptr; current = current->next_) {
|
||||
if (current->nativeFunction_ == nativeFunction) {
|
||||
MOZ_ASSERT(current->type() == type);
|
||||
|
@ -1385,7 +1362,7 @@ class Redirection
|
|||
__FILE__, __LINE__);
|
||||
MOZ_CRASH();
|
||||
}
|
||||
new(redir) Redirection(nativeFunction, type, sim);
|
||||
new(redir) Redirection(nativeFunction, type);
|
||||
return redir;
|
||||
}
|
||||
|
||||
|
@ -1405,6 +1382,15 @@ class Redirection
|
|||
Simulator::~Simulator()
|
||||
{
|
||||
js_free(stack_);
|
||||
}
|
||||
|
||||
SimulatorProcess::SimulatorProcess()
|
||||
: cacheLock_(mutexid::SimulatorCacheLock)
|
||||
, redirection_(nullptr)
|
||||
{}
|
||||
|
||||
SimulatorProcess::~SimulatorProcess()
|
||||
{
|
||||
Redirection* r = redirection_;
|
||||
while (r) {
|
||||
Redirection* next = r->next_;
|
||||
|
@ -1413,6 +1399,15 @@ Simulator::~Simulator()
|
|||
}
|
||||
}
|
||||
|
||||
bool
|
||||
SimulatorProcess::init()
|
||||
{
|
||||
if (getenv("MIPS_SIM_ICACHE_CHECKS"))
|
||||
ICacheCheckingDisableCount = 0;
|
||||
|
||||
return icache_.init();
|
||||
}
|
||||
|
||||
/* static */ void*
|
||||
Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type)
|
||||
{
|
||||
|
@ -1424,7 +1419,9 @@ Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type)
|
|||
Simulator*
|
||||
Simulator::Current()
|
||||
{
|
||||
return TlsContext.get()->runtime()->unsafeContextFromAnyThread()->simulator();
|
||||
JSContext* cx = TlsContext.get();
|
||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
|
||||
return cx->simulator();
|
||||
}
|
||||
|
||||
// Sets the register in the architecture state. It will also deal with updating
|
||||
|
@ -3654,9 +3651,9 @@ Simulator::decodeTypeJump(SimInstruction* instr)
|
|||
void
|
||||
Simulator::instructionDecode(SimInstruction* instr)
|
||||
{
|
||||
if (Simulator::ICacheCheckingEnabled) {
|
||||
AutoLockSimulatorCache als(this);
|
||||
checkICacheLocked(icache(), instr);
|
||||
if (!SimulatorProcess::ICacheCheckingDisableCount) {
|
||||
AutoLockSimulatorCache als;
|
||||
SimulatorProcess::checkICacheLocked(instr);
|
||||
}
|
||||
pc_modified_ = false;
|
||||
|
||||
|
|
|
@ -110,10 +110,9 @@ const uint32_t kMaxStopCode = 127;
|
|||
typedef uint32_t Instr;
|
||||
class SimInstruction;
|
||||
|
||||
// Per thread simulator state.
|
||||
class Simulator {
|
||||
friend class Redirection;
|
||||
friend class MipsDebugger;
|
||||
friend class AutoLockSimulatorCache;
|
||||
public:
|
||||
|
||||
// Registers are declared in order. See "See MIPS Run Linux" chapter 2.
|
||||
|
@ -222,8 +221,6 @@ class Simulator {
|
|||
// Debugger input.
|
||||
void setLastDebuggerInput(char* input);
|
||||
char* lastDebuggerInput() { return lastDebuggerInput_; }
|
||||
// ICache checking.
|
||||
static void FlushICache(void* start, size_t size);
|
||||
|
||||
// Returns true if pc register contains one of the 'SpecialValues' defined
|
||||
// below (bad_ra, end_sim_pc).
|
||||
|
@ -307,8 +304,6 @@ class Simulator {
|
|||
void branchDelayInstructionDecode(SimInstruction* instr);
|
||||
|
||||
public:
|
||||
static bool ICacheCheckingEnabled;
|
||||
|
||||
static int64_t StopSimAt;
|
||||
|
||||
// Runtime call support.
|
||||
|
@ -381,6 +376,13 @@ class Simulator {
|
|||
char* desc_;
|
||||
};
|
||||
StopCountAndDesc watchedStops_[kNumOfWatchedStops];
|
||||
};
|
||||
|
||||
// Process wide simulator state.
|
||||
class SimulatorProcess
|
||||
{
|
||||
friend class Redirection;
|
||||
friend class AutoLockSimulatorCache;
|
||||
|
||||
private:
|
||||
// ICache checking.
|
||||
|
@ -394,48 +396,63 @@ class Simulator {
|
|||
public:
|
||||
typedef HashMap<void*, CachePage*, ICacheHasher, SystemAllocPolicy> ICacheMap;
|
||||
|
||||
private:
|
||||
// This lock creates a critical section around 'redirection_' and
|
||||
// 'icache_', which are referenced both by the execution engine
|
||||
// and by the off-thread compiler (see Redirection::Get in the cpp file).
|
||||
Mutex cacheLock_;
|
||||
#ifdef DEBUG
|
||||
mozilla::Maybe<Thread::Id> cacheLockHolder_;
|
||||
#endif
|
||||
static mozilla::Atomic<size_t, mozilla::ReleaseAcquire> ICacheCheckingDisableCount;
|
||||
static void FlushICache(void* start, size_t size);
|
||||
|
||||
Redirection* redirection_;
|
||||
ICacheMap icache_;
|
||||
|
||||
private:
|
||||
// Jitcode may be rewritten from a signal handler, but is prevented from
|
||||
// calling FlushICache() because the signal may arrive within the critical
|
||||
// area of an AutoLockSimulatorCache. This flag instructs the Simulator
|
||||
// to remove all cache entries the next time it checks, avoiding false negatives.
|
||||
mozilla::Atomic<bool, mozilla::ReleaseAcquire> cacheInvalidatedBySignalHandler_;
|
||||
static mozilla::Atomic<bool, mozilla::ReleaseAcquire> cacheInvalidatedBySignalHandler_;
|
||||
|
||||
void checkICacheLocked(ICacheMap& i_cache, SimInstruction* instr);
|
||||
static void checkICacheLocked(SimInstruction* instr);
|
||||
|
||||
static bool initialize() {
|
||||
singleton_ = js_new<SimulatorProcess>();
|
||||
return singleton_ && singleton_->init();
|
||||
}
|
||||
static void destroy() {
|
||||
js_delete(singleton_);
|
||||
singleton_ = nullptr;
|
||||
}
|
||||
|
||||
SimulatorProcess();
|
||||
~SimulatorProcess();
|
||||
|
||||
private:
|
||||
bool init();
|
||||
|
||||
static SimulatorProcess* singleton_;
|
||||
|
||||
// This lock creates a critical section around 'redirection_' and
|
||||
// 'icache_', which are referenced both by the execution engine
|
||||
// and by the off-thread compiler (see Redirection::Get in the cpp file).
|
||||
Mutex cacheLock_;
|
||||
|
||||
Redirection* redirection_;
|
||||
ICacheMap icache_;
|
||||
|
||||
public:
|
||||
ICacheMap& icache() {
|
||||
static ICacheMap& icache() {
|
||||
// Technically we need the lock to access the innards of the
|
||||
// icache, not to take its address, but the latter condition
|
||||
// serves as a useful complement to the former.
|
||||
MOZ_ASSERT(cacheLockHolder_.isSome());
|
||||
return icache_;
|
||||
MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
|
||||
return singleton_->icache_;
|
||||
}
|
||||
|
||||
Redirection* redirection() const {
|
||||
MOZ_ASSERT(cacheLockHolder_.isSome());
|
||||
return redirection_;
|
||||
static Redirection* redirection() {
|
||||
MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
|
||||
return singleton_->redirection_;
|
||||
}
|
||||
|
||||
void setRedirection(js::jit::Redirection* redirection) {
|
||||
MOZ_ASSERT(cacheLockHolder_.isSome());
|
||||
redirection_ = redirection;
|
||||
static void setRedirection(js::jit::Redirection* redirection) {
|
||||
MOZ_ASSERT(singleton_->cacheLock_.ownedByCurrentThread());
|
||||
singleton_->redirection_ = redirection;
|
||||
}
|
||||
};
|
||||
|
||||
#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror) \
|
||||
#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror) \
|
||||
JS_BEGIN_MACRO \
|
||||
if (cx->simulator()->overRecursedWithExtra(extra)) { \
|
||||
js::ReportOverRecursed(cx); \
|
||||
|
|
|
@ -28,7 +28,7 @@ struct TestStruct<js::GCPtr<T>>
|
|||
{
|
||||
js::GCPtr<T> wrapper;
|
||||
|
||||
JS::Zone* zone() { return wrapper->zone(); }
|
||||
JS::Zone* zone() const { return wrapper->zone(); }
|
||||
};
|
||||
|
||||
// Give the GCPtr version GCManagedDeletePolicy as required.
|
||||
|
|
|
@ -4123,7 +4123,7 @@ JS::CancelOffThreadScript(JSContext* cx, void* token)
|
|||
{
|
||||
MOZ_ASSERT(cx);
|
||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
|
||||
HelperThreadState().cancelParseTask(cx, ParseTaskKind::Script, token);
|
||||
HelperThreadState().cancelParseTask(cx->runtime(), ParseTaskKind::Script, token);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(bool)
|
||||
|
@ -4148,7 +4148,7 @@ JS::CancelOffThreadModule(JSContext* cx, void* token)
|
|||
{
|
||||
MOZ_ASSERT(cx);
|
||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
|
||||
HelperThreadState().cancelParseTask(cx, ParseTaskKind::Module, token);
|
||||
HelperThreadState().cancelParseTask(cx->runtime(), ParseTaskKind::Module, token);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(bool)
|
||||
|
@ -4173,7 +4173,7 @@ JS::CancelOffThreadScriptDecoder(JSContext* cx, void* token)
|
|||
{
|
||||
MOZ_ASSERT(cx);
|
||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
|
||||
HelperThreadState().cancelParseTask(cx, ParseTaskKind::ScriptDecode, token);
|
||||
HelperThreadState().cancelParseTask(cx->runtime(), ParseTaskKind::ScriptDecode, token);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(bool)
|
||||
|
|
|
@ -886,6 +886,13 @@ JSContext::boolToResult(bool ok)
|
|||
return JS::Result<>(reportedError);
|
||||
}
|
||||
|
||||
inline JSContext*
|
||||
JSRuntime::activeContextFromOwnThread()
|
||||
{
|
||||
MOZ_ASSERT(activeContext() == js::TlsContext.get());
|
||||
return activeContext();
|
||||
}
|
||||
|
||||
namespace js {
|
||||
|
||||
struct MOZ_RAII AutoResolving {
|
||||
|
|
|
@ -485,7 +485,7 @@ JSContext::setCompartment(JSCompartment* comp,
|
|||
comp->zone()->usedByExclusiveThread);
|
||||
|
||||
// Normal JSContexts cannot enter exclusive zones.
|
||||
MOZ_ASSERT_IF(this == runtime()->unsafeContextFromAnyThread() && comp,
|
||||
MOZ_ASSERT_IF(this == runtime()->activeContext() && comp,
|
||||
!comp->zone()->usedByExclusiveThread);
|
||||
|
||||
// Only one thread can be in the atoms compartment at a time.
|
||||
|
|
|
@ -1057,15 +1057,6 @@ AddLazyFunctionsForCompartment(JSContext* cx, AutoObjectVector& lazyFunctions, A
|
|||
continue;
|
||||
}
|
||||
|
||||
// This creates a new reference to an object that an ongoing incremental
|
||||
// GC may find to be unreachable. Treat as if we're reading a weak
|
||||
// reference and trigger the read barrier.
|
||||
if (cx->zone()->needsIncrementalBarrier())
|
||||
fun->readBarrier(fun);
|
||||
|
||||
// TODO: The above checks should be rolled into the cell iterator (see
|
||||
// bug 1322971).
|
||||
|
||||
if (fun->isInterpretedLazy()) {
|
||||
LazyScript* lazy = fun->lazyScriptOrNull();
|
||||
if (lazy && lazy->sourceObject() && !lazy->hasUncompiledEnclosingScript()) {
|
||||
|
|
|
@ -1164,8 +1164,10 @@ DumpHeapTracer::onChild(const JS::GCCellPtr& thing)
|
|||
void
|
||||
js::DumpHeap(JSContext* cx, FILE* fp, js::DumpHeapNurseryBehaviour nurseryBehaviour)
|
||||
{
|
||||
if (nurseryBehaviour == js::CollectNurseryBeforeDump)
|
||||
cx->runtime()->zoneGroupFromMainThread()->evictNursery(JS::gcreason::API);
|
||||
if (nurseryBehaviour == js::CollectNurseryBeforeDump) {
|
||||
for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next())
|
||||
group->evictNursery(JS::gcreason::API);
|
||||
}
|
||||
|
||||
DumpHeapTracer dtrc(fp, cx);
|
||||
|
||||
|
@ -1222,7 +1224,7 @@ js::GetAnyCompartmentInZone(JS::Zone* zone)
|
|||
void
|
||||
JS::ObjectPtr::finalize(JSRuntime* rt)
|
||||
{
|
||||
if (IsIncrementalBarrierNeeded(rt->contextFromMainThread()))
|
||||
if (IsIncrementalBarrierNeeded(rt->activeContextFromOwnThread()))
|
||||
IncrementalObjectBarrier(value);
|
||||
value = nullptr;
|
||||
}
|
||||
|
|
193
js/src/jsgc.cpp
193
js/src/jsgc.cpp
|
@ -243,6 +243,7 @@
|
|||
#include "jsscriptinlines.h"
|
||||
|
||||
#include "gc/Heap-inl.h"
|
||||
#include "gc/Nursery-inl.h"
|
||||
#include "vm/GeckoProfiler-inl.h"
|
||||
#include "vm/Stack-inl.h"
|
||||
#include "vm/String-inl.h"
|
||||
|
@ -826,7 +827,6 @@ GCRuntime::GCRuntime(JSRuntime* rt) :
|
|||
grayBufferState(GCRuntime::GrayBufferState::Unused),
|
||||
grayBitsValid(false),
|
||||
majorGCTriggerReason(JS::gcreason::NO_REASON),
|
||||
minorGCTriggerReason(JS::gcreason::NO_REASON),
|
||||
fullGCForAtomsRequested_(false),
|
||||
minorGCNumber(0),
|
||||
majorGCNumber(0),
|
||||
|
@ -920,13 +920,17 @@ GCRuntime::setZeal(uint8_t zeal, uint32_t frequency)
|
|||
VerifyBarriers(rt, PreBarrierVerifier);
|
||||
|
||||
if (zeal == 0 && hasZealMode(ZealMode::GenerationalGC)) {
|
||||
rt->zoneGroupFromMainThread()->evictNursery(JS::gcreason::DEBUG_GC);
|
||||
rt->zoneGroupFromMainThread()->nursery().leaveZealMode();
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
|
||||
group->evictNursery(JS::gcreason::DEBUG_GC);
|
||||
group->nursery().leaveZealMode();
|
||||
}
|
||||
}
|
||||
|
||||
ZealMode zealMode = ZealMode(zeal);
|
||||
if (zealMode == ZealMode::GenerationalGC)
|
||||
rt->zoneGroupFromMainThread()->nursery().enterZealMode();
|
||||
if (zealMode == ZealMode::GenerationalGC) {
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next())
|
||||
group->nursery().enterZealMode();
|
||||
}
|
||||
|
||||
// Zeal modes 8-10 are mutually exclusive. If we're setting one of those,
|
||||
// we first reset all of them.
|
||||
|
@ -1068,8 +1072,10 @@ void
|
|||
GCRuntime::finish()
|
||||
{
|
||||
/* Wait for the nursery sweeping to end. */
|
||||
if (rt->zoneGroupFromMainThread()->nursery().isEnabled())
|
||||
rt->zoneGroupFromMainThread()->nursery().waitBackgroundFreeEnd();
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
|
||||
if (group->nursery().isEnabled())
|
||||
group->nursery().waitBackgroundFreeEnd();
|
||||
}
|
||||
|
||||
/*
|
||||
* Wait until the background finalization and allocation stops and the
|
||||
|
@ -1103,7 +1109,8 @@ GCRuntime::finish()
|
|||
|
||||
FinishTrace();
|
||||
|
||||
rt->zoneGroupFromMainThread()->nursery().printTotalProfileTimes();
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next())
|
||||
group->nursery().printTotalProfileTimes();
|
||||
stats().printTotalProfileTimes();
|
||||
}
|
||||
|
||||
|
@ -2549,7 +2556,7 @@ GCRuntime::updateRuntimePointersToRelocatedCells(AutoLockForExclusiveAccess& loc
|
|||
// Mark roots to update them.
|
||||
{
|
||||
gcstats::AutoPhase ap2(stats(), gcstats::PHASE_MARK_ROOTS);
|
||||
Debugger::traceAll(&trc);
|
||||
Debugger::traceAllForMovingGC(&trc);
|
||||
Debugger::traceIncomingCrossCompartmentEdges(&trc);
|
||||
|
||||
// Mark all gray roots, making sure we call the trace callback to get the
|
||||
|
@ -2951,15 +2958,15 @@ GCRuntime::requestMajorGC(JS::gcreason::Reason reason)
|
|||
}
|
||||
|
||||
void
|
||||
GCRuntime::requestMinorGC(JS::gcreason::Reason reason)
|
||||
Nursery::requestMinorGC(JS::gcreason::Reason reason) const
|
||||
{
|
||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
|
||||
MOZ_ASSERT(CurrentThreadCanAccessRuntime(zoneGroup()->runtime));
|
||||
MOZ_ASSERT(!CurrentThreadIsPerformingGC());
|
||||
|
||||
if (minorGCRequested())
|
||||
return;
|
||||
|
||||
minorGCTriggerReason = reason;
|
||||
minorGCTriggerReason_ = reason;
|
||||
|
||||
// See comment in requestMajorGC.
|
||||
TlsContext.get()->requestInterrupt(JSContext::RequestInterruptCanWait);
|
||||
|
@ -2979,7 +2986,7 @@ GCRuntime::triggerGC(JS::gcreason::Reason reason)
|
|||
if (JS::CurrentThreadIsHeapCollecting())
|
||||
return false;
|
||||
|
||||
JS::PrepareForFullGC(rt->contextFromMainThread());
|
||||
JS::PrepareForFullGC(rt->activeContextFromOwnThread());
|
||||
requestMajorGC(reason);
|
||||
return true;
|
||||
}
|
||||
|
@ -3060,7 +3067,7 @@ GCRuntime::maybeGC(Zone* zone)
|
|||
|
||||
#ifdef JS_GC_ZEAL
|
||||
if (hasZealMode(ZealMode::Alloc) || hasZealMode(ZealMode::Poke)) {
|
||||
JS::PrepareForFullGC(rt->contextFromMainThread());
|
||||
JS::PrepareForFullGC(rt->activeContextFromOwnThread());
|
||||
gc(GC_NORMAL, JS::gcreason::DEBUG_GC);
|
||||
return;
|
||||
}
|
||||
|
@ -3478,7 +3485,7 @@ Zone::sweepCompartments(FreeOp* fop, bool keepAtleastOne, bool destroyingRuntime
|
|||
}
|
||||
|
||||
void
|
||||
GCRuntime::sweepZones(FreeOp* fop, bool destroyingRuntime)
|
||||
GCRuntime::sweepZones(FreeOp* fop, ZoneGroup* group, bool destroyingRuntime)
|
||||
{
|
||||
MOZ_ASSERT_IF(destroyingRuntime, numActiveZoneIters == 0);
|
||||
MOZ_ASSERT_IF(destroyingRuntime, arenasEmptyAtShutdown);
|
||||
|
@ -3490,8 +3497,8 @@ GCRuntime::sweepZones(FreeOp* fop, bool destroyingRuntime)
|
|||
|
||||
JSZoneCallback callback = rt->destroyZoneCallback;
|
||||
|
||||
Zone** read = rt->zoneGroupFromMainThread()->zones().begin();
|
||||
Zone** end = rt->zoneGroupFromMainThread()->zones().end();
|
||||
Zone** read = group->zones().begin();
|
||||
Zone** end = group->zones().end();
|
||||
Zone** write = read;
|
||||
|
||||
while (read < end) {
|
||||
|
@ -3529,7 +3536,30 @@ GCRuntime::sweepZones(FreeOp* fop, bool destroyingRuntime)
|
|||
}
|
||||
*write++ = zone;
|
||||
}
|
||||
rt->zoneGroupFromMainThread()->zones().shrinkTo(write - rt->zoneGroupFromMainThread()->zones().begin());
|
||||
group->zones().shrinkTo(write - group->zones().begin());
|
||||
}
|
||||
|
||||
void
|
||||
GCRuntime::sweepZoneGroups(FreeOp* fop, bool destroyingRuntime)
|
||||
{
|
||||
ZoneGroup** read = groups.ref().begin();
|
||||
ZoneGroup** end = groups.ref().end();
|
||||
ZoneGroup** write = read;
|
||||
|
||||
while (read < end) {
|
||||
ZoneGroup* group = *read++;
|
||||
sweepZones(fop, group, destroyingRuntime);
|
||||
|
||||
// For now, the singleton zone group is not destroyed until the runtime
|
||||
// itself is, bug 1323066.
|
||||
if (group->zones().empty() && group != rt->zoneGroupFromMainThread()) {
|
||||
MOZ_ASSERT(numActiveZoneIters == 0);
|
||||
fop->delete_(group);
|
||||
} else {
|
||||
*write++ = group;
|
||||
}
|
||||
}
|
||||
groups.ref().shrinkTo(write - groups.ref().begin());
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
|
@ -3560,8 +3590,8 @@ ArenaLists::checkEmptyArenaList(AllocKind kind)
|
|||
max_cells = atol(env);
|
||||
for (Arena* current = arenaLists(kind).head(); current; current = current->next) {
|
||||
for (ArenaCellIterUnderGC i(current); !i.done(); i.next()) {
|
||||
Cell* t = i.get<Cell>();
|
||||
MOZ_ASSERT(t->asTenured().isMarked(), "unmarked cells should have been finalized");
|
||||
TenuredCell* t = i.getCell();
|
||||
MOZ_ASSERT(t->isMarked(), "unmarked cells should have been finalized");
|
||||
if (++num_live <= max_cells) {
|
||||
fprintf(stderr, "ERROR: GC found live Cell %p of kind %s at shutdown\n",
|
||||
t, AllocKindToAscii(kind));
|
||||
|
@ -3580,19 +3610,21 @@ GCRuntime::purgeRuntime(AutoLockForExclusiveAccess& lock)
|
|||
for (GCCompartmentsIter comp(rt); !comp.done(); comp.next())
|
||||
comp->purge();
|
||||
|
||||
JSContext* cx = TlsContext.get();
|
||||
for (const CooperatingContext& target : rt->cooperatingContexts()) {
|
||||
freeUnusedLifoBlocksAfterSweeping(&target.context()->tempLifoAlloc());
|
||||
target.context()->interpreterStack().purge(rt);
|
||||
target.context()->frontendCollectionPool().purge();
|
||||
}
|
||||
|
||||
freeUnusedLifoBlocksAfterSweeping(&cx->tempLifoAlloc());
|
||||
cx->interpreterStack().purge(rt);
|
||||
cx->frontendCollectionPool().purge();
|
||||
|
||||
rt->zoneGroupFromMainThread()->caches().gsnCache.purge();
|
||||
rt->zoneGroupFromMainThread()->caches().envCoordinateNameCache.purge();
|
||||
rt->zoneGroupFromMainThread()->caches().newObjectCache.purge();
|
||||
rt->zoneGroupFromMainThread()->caches().nativeIterCache.purge();
|
||||
rt->zoneGroupFromMainThread()->caches().uncompressedSourceCache.purge();
|
||||
if (rt->zoneGroupFromMainThread()->caches().evalCache.initialized())
|
||||
rt->zoneGroupFromMainThread()->caches().evalCache.clear();
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
|
||||
group->caches().gsnCache.purge();
|
||||
group->caches().envCoordinateNameCache.purge();
|
||||
group->caches().newObjectCache.purge();
|
||||
group->caches().nativeIterCache.purge();
|
||||
group->caches().uncompressedSourceCache.purge();
|
||||
if (group->caches().evalCache.initialized())
|
||||
group->caches().evalCache.clear();
|
||||
}
|
||||
|
||||
if (auto cache = rt->maybeThisRuntimeSharedImmutableStrings())
|
||||
cache->purge();
|
||||
|
@ -5508,7 +5540,6 @@ GCRuntime::IncrementalProgress
|
|||
GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
|
||||
AutoLockForExclusiveAccess& lock)
|
||||
{
|
||||
MOZ_ASSERT(rt->zoneGroupFromMainThread()->nursery().isEmpty());
|
||||
assertBackgroundSweepingFinished();
|
||||
MOZ_ASSERT(startedCompacting);
|
||||
|
||||
|
@ -5527,6 +5558,7 @@ GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
|
|||
Zone* zone = zonesToMaybeCompact.ref().front();
|
||||
zonesToMaybeCompact.ref().removeFront();
|
||||
|
||||
MOZ_ASSERT(zone->group()->nursery().isEmpty());
|
||||
MOZ_ASSERT(zone->isGCFinished());
|
||||
zone->setGCState(Zone::Compact);
|
||||
|
||||
|
@ -5558,10 +5590,12 @@ GCRuntime::compactPhase(JS::gcreason::Reason reason, SliceBudget& sliceBudget,
|
|||
releaseRelocatedArenas(relocatedArenas);
|
||||
|
||||
// Clear caches that can contain cell pointers.
|
||||
rt->zoneGroupFromMainThread()->caches().newObjectCache.purge();
|
||||
rt->zoneGroupFromMainThread()->caches().nativeIterCache.purge();
|
||||
if (rt->zoneGroupFromMainThread()->caches().evalCache.initialized())
|
||||
rt->zoneGroupFromMainThread()->caches().evalCache.clear();
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
|
||||
group->caches().newObjectCache.purge();
|
||||
group->caches().nativeIterCache.purge();
|
||||
if (group->caches().evalCache.initialized())
|
||||
group->caches().evalCache.clear();
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
CheckHashTablesAfterMovingGC(rt);
|
||||
|
@ -5621,17 +5655,28 @@ HeapStateToLabel(JS::HeapState heapState)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
static bool
|
||||
AllNurseriesAreEmpty(JSRuntime* rt)
|
||||
{
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
|
||||
if (!group->nursery().isEmpty())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Start a new heap session. */
|
||||
AutoTraceSession::AutoTraceSession(JSRuntime* rt, JS::HeapState heapState)
|
||||
: lock(rt),
|
||||
runtime(rt),
|
||||
prevState(TlsContext.get()->heapState),
|
||||
pseudoFrame(rt, HeapStateToLabel(heapState), ProfileEntry::Category::GC),
|
||||
prohibitActiveContextChange(rt)
|
||||
pseudoFrame(rt, HeapStateToLabel(heapState), ProfileEntry::Category::GC)
|
||||
{
|
||||
MOZ_ASSERT(prevState == JS::HeapState::Idle);
|
||||
MOZ_ASSERT(heapState != JS::HeapState::Idle);
|
||||
MOZ_ASSERT_IF(heapState == JS::HeapState::MajorCollecting, rt->zoneGroupFromMainThread()->nursery().isEmpty());
|
||||
MOZ_ASSERT_IF(heapState == JS::HeapState::MajorCollecting, AllNurseriesAreEmpty(rt));
|
||||
TlsContext.get()->heapState = heapState;
|
||||
}
|
||||
|
||||
|
@ -5647,6 +5692,21 @@ JS::CurrentThreadHeapState()
|
|||
return TlsContext.get()->heapState;
|
||||
}
|
||||
|
||||
bool
|
||||
GCRuntime::canChangeActiveContext(JSContext* cx)
|
||||
{
|
||||
// Threads cannot be in the middle of any operation that affects GC
|
||||
// behavior when execution transfers to another thread for cooperative
|
||||
// scheduling.
|
||||
return cx->heapState == JS::HeapState::Idle
|
||||
&& !cx->suppressGC
|
||||
&& cx->allowGCBarriers
|
||||
&& !cx->inUnsafeRegion
|
||||
&& !cx->generationalDisabled
|
||||
&& !cx->compactingDisabledCount
|
||||
&& !cx->keepAtoms;
|
||||
}
|
||||
|
||||
GCRuntime::IncrementalResult
|
||||
GCRuntime::resetIncrementalGC(gc::AbortReason reason, AutoLockForExclusiveAccess& lock)
|
||||
{
|
||||
|
@ -5897,7 +5957,8 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea
|
|||
MOZ_FALLTHROUGH;
|
||||
|
||||
case State::Mark:
|
||||
AutoGCRooter::traceAllWrappers(&marker);
|
||||
for (const CooperatingContext& target : rt->cooperatingContexts())
|
||||
AutoGCRooter::traceAllWrappers(target, &marker);
|
||||
|
||||
/* If we needed delayed marking for gray roots, then collect until done. */
|
||||
if (!hasBufferedGrayRoots()) {
|
||||
|
@ -5982,7 +6043,7 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea
|
|||
gcstats::AutoPhase ap2(stats(), gcstats::PHASE_DESTROY);
|
||||
AutoSetThreadIsSweeping threadIsSweeping;
|
||||
FreeOp fop(rt);
|
||||
sweepZones(&fop, destroyingRuntime);
|
||||
sweepZoneGroups(&fop, destroyingRuntime);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(!startedCompacting);
|
||||
|
@ -6191,7 +6252,7 @@ GCRuntime::gcCycle(bool nonincrementalByAPI, SliceBudget& budget, JS::gcreason::
|
|||
|
||||
AutoExposeLiveCrossZoneEdges aelcze(&foundBlackGrayEdges.ref());
|
||||
|
||||
rt->zoneGroupFromMainThread()->evictNursery(reason);
|
||||
EvictAllNurseries(rt, reason);
|
||||
|
||||
AutoTraceSession session(rt, JS::HeapState::MajorCollecting);
|
||||
|
||||
|
@ -6317,7 +6378,7 @@ GCRuntime::maybeDoCycleCollection()
|
|||
}
|
||||
double grayFraction = double(compartmentsGray) / double(compartmentsTotal);
|
||||
if (grayFraction > ExcessiveGrayCompartments || compartmentsGray > LimitGrayCompartments)
|
||||
callDoCycleCollectionCallback(rt->contextFromMainThread());
|
||||
callDoCycleCollectionCallback(rt->activeContextFromOwnThread());
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -6394,7 +6455,7 @@ GCRuntime::collect(bool nonincrementalByAPI, SliceBudget budget, JS::gcreason::R
|
|||
bool repeatForDeadZone = false;
|
||||
if (poked && cleanUpEverything) {
|
||||
/* Need to re-schedule all zones for GC. */
|
||||
JS::PrepareForFullGC(rt->contextFromMainThread());
|
||||
JS::PrepareForFullGC(rt->activeContextFromOwnThread());
|
||||
} else if (shouldRepeatForDeadZone(reason) && !wasReset) {
|
||||
/*
|
||||
* This code makes an extra effort to collect compartments that we
|
||||
|
@ -6512,14 +6573,14 @@ GCRuntime::notifyDidPaint()
|
|||
verifyPreBarriers();
|
||||
|
||||
if (hasZealMode(ZealMode::FrameGC)) {
|
||||
JS::PrepareForFullGC(rt->contextFromMainThread());
|
||||
JS::PrepareForFullGC(rt->activeContextFromOwnThread());
|
||||
gc(GC_NORMAL, JS::gcreason::REFRESH_FRAME);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (isIncrementalGCInProgress() && !interFrameGC && tunables.areRefreshFrameSlicesEnabled()) {
|
||||
JS::PrepareForIncrementalGC(rt->contextFromMainThread());
|
||||
JS::PrepareForIncrementalGC(rt->activeContextFromOwnThread());
|
||||
gcSlice(JS::gcreason::REFRESH_FRAME);
|
||||
}
|
||||
|
||||
|
@ -6541,7 +6602,7 @@ GCRuntime::startDebugGC(JSGCInvocationKind gckind, SliceBudget& budget)
|
|||
{
|
||||
MOZ_ASSERT(!isIncrementalGCInProgress());
|
||||
if (!ZonesSelected(rt))
|
||||
JS::PrepareForFullGC(rt->contextFromMainThread());
|
||||
JS::PrepareForFullGC(rt->activeContextFromOwnThread());
|
||||
invocationKind = gckind;
|
||||
collect(false, budget, JS::gcreason::DEBUG_GC);
|
||||
}
|
||||
|
@ -6551,7 +6612,7 @@ GCRuntime::debugGCSlice(SliceBudget& budget)
|
|||
{
|
||||
MOZ_ASSERT(isIncrementalGCInProgress());
|
||||
if (!ZonesSelected(rt))
|
||||
JS::PrepareForIncrementalGC(rt->contextFromMainThread());
|
||||
JS::PrepareForIncrementalGC(rt->activeContextFromOwnThread());
|
||||
collect(false, budget, JS::gcreason::DEBUG_GC);
|
||||
}
|
||||
|
||||
|
@ -6560,7 +6621,7 @@ void
|
|||
js::PrepareForDebugGC(JSRuntime* rt)
|
||||
{
|
||||
if (!ZonesSelected(rt))
|
||||
JS::PrepareForFullGC(rt->contextFromMainThread());
|
||||
JS::PrepareForFullGC(rt->activeContextFromOwnThread());
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -6573,7 +6634,8 @@ GCRuntime::onOutOfMallocMemory()
|
|||
decommitTask.join();
|
||||
|
||||
// Wait for background free of nursery huge slots to finish.
|
||||
rt->zoneGroupFromMainThread()->nursery().waitBackgroundFreeEnd();
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next())
|
||||
group->nursery().waitBackgroundFreeEnd();
|
||||
|
||||
AutoLockGC lock(rt);
|
||||
onOutOfMallocMemory(lock);
|
||||
|
@ -6605,7 +6667,7 @@ ZoneGroup::minorGC(JS::gcreason::Reason reason, gcstats::Phase phase)
|
|||
|
||||
gcstats::AutoPhase ap(runtime->gc.stats(), phase);
|
||||
|
||||
runtime->gc.minorGCTriggerReason = JS::gcreason::NO_REASON;
|
||||
nursery().clearMinorGCRequest();
|
||||
TraceLoggerThread* logger = TraceLoggerForCurrentThread();
|
||||
AutoTraceLog logMinorGC(logger, TraceLogger_MinorGC);
|
||||
nursery().collect(reason);
|
||||
|
@ -6629,16 +6691,20 @@ JS::AutoDisableGenerationalGC::AutoDisableGenerationalGC(JSContext* cx)
|
|||
: cx(cx)
|
||||
{
|
||||
if (!cx->generationalDisabled) {
|
||||
cx->runtime()->zoneGroupFromMainThread()->evictNursery(JS::gcreason::API);
|
||||
cx->runtime()->zoneGroupFromMainThread()->nursery().disable();
|
||||
for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next()) {
|
||||
group->evictNursery(JS::gcreason::API);
|
||||
group->nursery().disable();
|
||||
}
|
||||
}
|
||||
++cx->generationalDisabled;
|
||||
}
|
||||
|
||||
JS::AutoDisableGenerationalGC::~AutoDisableGenerationalGC()
|
||||
{
|
||||
if (--cx->generationalDisabled == 0)
|
||||
cx->runtime()->zoneGroupFromMainThread()->nursery().enable();
|
||||
if (--cx->generationalDisabled == 0) {
|
||||
for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next())
|
||||
group->nursery().enable();
|
||||
}
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(bool)
|
||||
|
@ -6652,8 +6718,10 @@ GCRuntime::gcIfRequested()
|
|||
{
|
||||
// This method returns whether a major GC was performed.
|
||||
|
||||
if (minorGCRequested())
|
||||
rt->zoneGroupFromMainThread()->minorGC(minorGCTriggerReason);
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
|
||||
if (group->nursery().minorGCRequested())
|
||||
group->minorGC(group->nursery().minorGCTriggerReason());
|
||||
}
|
||||
|
||||
if (majorGCRequested()) {
|
||||
if (!isIncrementalGCInProgress())
|
||||
|
@ -6674,7 +6742,8 @@ js::gc::FinishGC(JSContext* cx)
|
|||
JS::FinishIncrementalGC(cx, JS::gcreason::API);
|
||||
}
|
||||
|
||||
cx->runtime()->zoneGroupFromMainThread()->nursery().waitBackgroundFreeEnd();
|
||||
for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next())
|
||||
group->nursery().waitBackgroundFreeEnd();
|
||||
}
|
||||
|
||||
AutoPrepareForTracing::AutoPrepareForTracing(JSContext* cx, ZoneSelector selector)
|
||||
|
@ -6740,7 +6809,7 @@ gc::MergeCompartments(JSCompartment* source, JSCompartment* target)
|
|||
MOZ_ASSERT(source->creationOptions().addonIdOrNull() ==
|
||||
target->creationOptions().addonIdOrNull());
|
||||
|
||||
JSContext* cx = source->contextFromMainThread();
|
||||
JSContext* cx = source->runtimeFromMainThread()->activeContextFromOwnThread();
|
||||
|
||||
AutoPrepareForTracing prepare(cx, SkipAtoms);
|
||||
|
||||
|
@ -7637,7 +7706,7 @@ AutoAssertEmptyNursery::checkCondition(JSContext* cx) {
|
|||
if (!noAlloc)
|
||||
noAlloc.emplace();
|
||||
this->cx = cx;
|
||||
MOZ_ASSERT(cx->runtime()->zoneGroupFromMainThread()->nursery().isEmpty());
|
||||
MOZ_ASSERT(AllNurseriesAreEmpty(cx->runtime()));
|
||||
}
|
||||
|
||||
AutoEmptyNursery::AutoEmptyNursery(JSContext* cx)
|
||||
|
@ -7645,7 +7714,7 @@ AutoEmptyNursery::AutoEmptyNursery(JSContext* cx)
|
|||
{
|
||||
MOZ_ASSERT(!cx->suppressGC);
|
||||
cx->runtime()->gc.stats().suspendPhases();
|
||||
cx->runtime()->zoneGroupFromMainThread()->evictNursery();
|
||||
EvictAllNurseries(cx->runtime(), JS::gcreason::EVICT_NURSERY);
|
||||
cx->runtime()->gc.stats().resumePhases();
|
||||
checkCondition(cx);
|
||||
}
|
||||
|
|
|
@ -102,6 +102,12 @@ class ArenaIter
|
|||
}
|
||||
};
|
||||
|
||||
enum CellIterNeedsBarrier : uint8_t
|
||||
{
|
||||
CellIterDoesntNeedBarrier = 0,
|
||||
CellIterMayNeedBarrier = 1
|
||||
};
|
||||
|
||||
class ArenaCellIterImpl
|
||||
{
|
||||
size_t firstThingOffset;
|
||||
|
@ -109,6 +115,8 @@ class ArenaCellIterImpl
|
|||
Arena* arenaAddr;
|
||||
FreeSpan span;
|
||||
uint_fast16_t thing;
|
||||
JS::TraceKind traceKind;
|
||||
bool needsBarrier;
|
||||
mozilla::DebugOnly<bool> initialized;
|
||||
|
||||
// Upon entry, |thing| points to any thing (free or used) and finds the
|
||||
|
@ -128,17 +136,32 @@ class ArenaCellIterImpl
|
|||
|
||||
public:
|
||||
ArenaCellIterImpl()
|
||||
: firstThingOffset(0), thingSize(0), arenaAddr(nullptr), thing(0), initialized(false) {}
|
||||
: firstThingOffset(0),
|
||||
thingSize(0),
|
||||
arenaAddr(nullptr),
|
||||
thing(0),
|
||||
traceKind(JS::TraceKind::Null),
|
||||
needsBarrier(false),
|
||||
initialized(false)
|
||||
{}
|
||||
|
||||
explicit ArenaCellIterImpl(Arena* arena) : initialized(false) { init(arena); }
|
||||
explicit ArenaCellIterImpl(Arena* arena, CellIterNeedsBarrier mayNeedBarrier)
|
||||
: initialized(false)
|
||||
{
|
||||
init(arena, mayNeedBarrier);
|
||||
}
|
||||
|
||||
void init(Arena* arena) {
|
||||
void init(Arena* arena, CellIterNeedsBarrier mayNeedBarrier) {
|
||||
MOZ_ASSERT(!initialized);
|
||||
MOZ_ASSERT(arena);
|
||||
MOZ_ASSERT_IF(!mayNeedBarrier,
|
||||
CurrentThreadIsPerformingGC() || CurrentThreadIsGCSweeping());
|
||||
initialized = true;
|
||||
AllocKind kind = arena->getAllocKind();
|
||||
firstThingOffset = Arena::firstThingOffset(kind);
|
||||
thingSize = Arena::thingSize(kind);
|
||||
traceKind = MapAllocToTraceKind(kind);
|
||||
needsBarrier = mayNeedBarrier && !JS::CurrentThreadIsHeapCollecting();
|
||||
reset(arena);
|
||||
}
|
||||
|
||||
|
@ -161,11 +184,20 @@ class ArenaCellIterImpl
|
|||
|
||||
TenuredCell* getCell() const {
|
||||
MOZ_ASSERT(!done());
|
||||
return reinterpret_cast<TenuredCell*>(uintptr_t(arenaAddr) + thing);
|
||||
TenuredCell* cell = reinterpret_cast<TenuredCell*>(uintptr_t(arenaAddr) + thing);
|
||||
|
||||
// This can result in a a new reference being created to an object that
|
||||
// an ongoing incremental GC may find to be unreachable, so we may need
|
||||
// a barrier here.
|
||||
if (needsBarrier)
|
||||
ExposeGCThingToActiveJS(JS::GCCellPtr(cell, traceKind));
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
template<typename T> T* get() const {
|
||||
MOZ_ASSERT(!done());
|
||||
MOZ_ASSERT(JS::MapTypeToTraceKind<T>::kind == traceKind);
|
||||
return static_cast<T*>(getCell());
|
||||
}
|
||||
|
||||
|
@ -185,7 +217,7 @@ class ArenaCellIter : public ArenaCellIterImpl
|
|||
{
|
||||
public:
|
||||
explicit ArenaCellIter(Arena* arena)
|
||||
: ArenaCellIterImpl(arena)
|
||||
: ArenaCellIterImpl(arena, CellIterMayNeedBarrier)
|
||||
{
|
||||
MOZ_ASSERT(JS::CurrentThreadIsHeapTracing());
|
||||
}
|
||||
|
@ -195,7 +227,7 @@ class ArenaCellIterUnderGC : public ArenaCellIterImpl
|
|||
{
|
||||
public:
|
||||
explicit ArenaCellIterUnderGC(Arena* arena)
|
||||
: ArenaCellIterImpl(arena)
|
||||
: ArenaCellIterImpl(arena, CellIterDoesntNeedBarrier)
|
||||
{
|
||||
MOZ_ASSERT(CurrentThreadIsPerformingGC());
|
||||
}
|
||||
|
@ -205,7 +237,7 @@ class ArenaCellIterUnderFinalize : public ArenaCellIterImpl
|
|||
{
|
||||
public:
|
||||
explicit ArenaCellIterUnderFinalize(Arena* arena)
|
||||
: ArenaCellIterImpl(arena)
|
||||
: ArenaCellIterImpl(arena, CellIterDoesntNeedBarrier)
|
||||
{
|
||||
MOZ_ASSERT(CurrentThreadIsGCSweeping());
|
||||
}
|
||||
|
@ -248,7 +280,7 @@ class ZoneCellIter<TenuredCell> {
|
|||
rt->gc.waitBackgroundSweepEnd();
|
||||
arenaIter.init(zone, kind);
|
||||
if (!arenaIter.done())
|
||||
cellIter.init(arenaIter.get());
|
||||
cellIter.init(arenaIter.get(), CellIterMayNeedBarrier);
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
|
@ -123,6 +123,8 @@ class AutoTraceSession;
|
|||
class StoreBuffer;
|
||||
} // namespace gc
|
||||
|
||||
class CooperatingContext;
|
||||
|
||||
inline JSCompartment* GetContextCompartment(const JSContext* cx);
|
||||
inline JS::Zone* GetContextZone(const JSContext* cx);
|
||||
|
||||
|
@ -226,8 +228,8 @@ class JS_PUBLIC_API(AutoGCRooter)
|
|||
|
||||
/* Implemented in gc/RootMarking.cpp. */
|
||||
inline void trace(JSTracer* trc);
|
||||
static void traceAll(JSTracer* trc);
|
||||
static void traceAllWrappers(JSTracer* trc);
|
||||
static void traceAll(const js::CooperatingContext& target, JSTracer* trc);
|
||||
static void traceAllWrappers(const js::CooperatingContext& target, JSTracer* trc);
|
||||
|
||||
protected:
|
||||
AutoGCRooter * const down;
|
||||
|
|
|
@ -24,7 +24,7 @@ using namespace js::gc;
|
|||
|
||||
WeakMapBase::WeakMapBase(JSObject* memOf, Zone* zone)
|
||||
: memberOf(memOf),
|
||||
zone(zone),
|
||||
zone_(zone),
|
||||
marked(false)
|
||||
{
|
||||
MOZ_ASSERT_IF(memberOf, memberOf->compartment()->zone() == zone);
|
||||
|
@ -122,7 +122,7 @@ WeakMapBase::restoreMarkedWeakMaps(WeakMapSet& markedWeakMaps)
|
|||
{
|
||||
for (WeakMapSet::Range r = markedWeakMaps.all(); !r.empty(); r.popFront()) {
|
||||
WeakMapBase* map = r.front();
|
||||
MOZ_ASSERT(map->zone->isGCMarking());
|
||||
MOZ_ASSERT(map->zone()->isGCMarking());
|
||||
MOZ_ASSERT(!map->marked);
|
||||
map->marked = true;
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ ObjectValueMap::findZoneEdges()
|
|||
if (!delegate)
|
||||
continue;
|
||||
Zone* delegateZone = delegate->zone();
|
||||
if (delegateZone == zone || !delegateZone->isGCMarking())
|
||||
if (delegateZone == zone() || !delegateZone->isGCMarking())
|
||||
continue;
|
||||
if (!delegateZone->gcZoneGroupEdges().put(key->zone()))
|
||||
return false;
|
||||
|
|
|
@ -49,6 +49,8 @@ class WeakMapBase : public mozilla::LinkedListElement<WeakMapBase>
|
|||
WeakMapBase(JSObject* memOf, JS::Zone* zone);
|
||||
virtual ~WeakMapBase();
|
||||
|
||||
Zone* zone() const { return zone_; }
|
||||
|
||||
// Garbage collector entry points.
|
||||
|
||||
// Unmark all weak maps in a zone.
|
||||
|
@ -99,7 +101,7 @@ class WeakMapBase : public mozilla::LinkedListElement<WeakMapBase>
|
|||
GCPtrObject memberOf;
|
||||
|
||||
// Zone containing this weak map.
|
||||
JS::Zone* zone;
|
||||
JS::Zone* zone_;
|
||||
|
||||
// Whether this object has been traced during garbage collection.
|
||||
bool marked;
|
||||
|
@ -136,7 +138,7 @@ class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>,
|
|||
bool init(uint32_t len = 16) {
|
||||
if (!Base::init(len))
|
||||
return false;
|
||||
zone->gcWeakMapList().insertFront(this);
|
||||
zone()->gcWeakMapList().insertFront(this);
|
||||
marked = JS::IsIncrementalGCInProgress(TlsContext.get());
|
||||
return true;
|
||||
}
|
||||
|
@ -284,7 +286,7 @@ class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>,
|
|||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
MOZ_ASSERT(obj->runtimeFromMainThread() == zone->runtimeFromMainThread());
|
||||
MOZ_ASSERT(obj->runtimeFromMainThread() == zone()->runtimeFromMainThread());
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
@ -302,7 +304,7 @@ class WeakMap : public HashMap<Key, Value, HashPolicy, RuntimeAllocPolicy>,
|
|||
* Check if the delegate is marked with any color to properly handle
|
||||
* gray marking when the key's delegate is black and the map is gray.
|
||||
*/
|
||||
return delegate && gc::IsMarkedUnbarriered(zone->runtimeFromMainThread(), &delegate);
|
||||
return delegate && gc::IsMarkedUnbarriered(zone()->runtimeFromMainThread(), &delegate);
|
||||
}
|
||||
|
||||
bool keyNeedsMark(JSScript* script) const {
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include "jscompartmentinlines.h"
|
||||
#include "jsobjinlines.h"
|
||||
|
||||
#include "gc/Nursery-inl.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
#define PIERCE(cx, wrapper, pre, op, post) \
|
||||
|
@ -516,7 +518,7 @@ js::NukeCrossCompartmentWrappers(JSContext* cx,
|
|||
CHECK_REQUEST(cx);
|
||||
JSRuntime* rt = cx->runtime();
|
||||
|
||||
rt->zoneGroupFromMainThread()->evictNursery(JS::gcreason::EVICT_NURSERY);
|
||||
EvictAllNurseries(rt);
|
||||
|
||||
// Iterate through scopes looking for system cross compartment wrappers
|
||||
// that point to an object that shares a global with obj.
|
||||
|
@ -656,7 +658,7 @@ js::RecomputeWrappers(JSContext* cx, const CompartmentFilter& sourceFilter,
|
|||
const CompartmentFilter& targetFilter)
|
||||
{
|
||||
// Drop any nursery-allocated wrappers.
|
||||
cx->runtime()->zoneGroupFromMainThread()->evictNursery(JS::gcreason::EVICT_NURSERY);
|
||||
EvictAllNurseries(cx->runtime());
|
||||
|
||||
AutoWrapperVector toRecompute(cx);
|
||||
for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
|
||||
|
|
|
@ -7661,7 +7661,7 @@ SetContextOptions(JSContext* cx, const OptionParser& op)
|
|||
|
||||
#if defined(JS_SIMULATOR_ARM)
|
||||
if (op.getBoolOption("arm-sim-icache-checks"))
|
||||
jit::Simulator::ICacheCheckingEnabled = true;
|
||||
jit::SimulatorProcess::ICacheCheckingDisableCount = 0;
|
||||
|
||||
int32_t stopAt = op.getIntOption("arm-sim-stop-at");
|
||||
if (stopAt >= 0)
|
||||
|
|
|
@ -3044,10 +3044,12 @@ Debugger::traceIncomingCrossCompartmentEdges(JSTracer* trc)
|
|||
gc::State state = rt->gc.state();
|
||||
MOZ_ASSERT(state == gc::State::MarkRoots || state == gc::State::Compact);
|
||||
|
||||
for (Debugger* dbg : rt->zoneGroupFromMainThread()->debuggerList()) {
|
||||
Zone* zone = MaybeForwarded(dbg->object.get())->zone();
|
||||
if (!zone->isCollecting() || state == gc::State::Compact)
|
||||
dbg->traceCrossCompartmentEdges(trc);
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
|
||||
for (Debugger* dbg : group->debuggerList()) {
|
||||
Zone* zone = MaybeForwarded(dbg->object.get())->zone();
|
||||
if (!zone->isCollecting() || state == gc::State::Compact)
|
||||
dbg->traceCrossCompartmentEdges(trc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3144,41 +3146,48 @@ Debugger::markIteratively(GCMarker* marker)
|
|||
return markedAny;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
Debugger::traceAllForMovingGC(JSTracer* trc)
|
||||
{
|
||||
JSRuntime* rt = trc->runtime();
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
|
||||
for (Debugger* dbg : group->debuggerList())
|
||||
dbg->traceForMovingGC(trc);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Trace all debugger-owned GC things unconditionally. This is used during
|
||||
* compacting GC and in minor GC: the minor GC cannot apply the weak constraints
|
||||
* of the full GC because it visits only part of the heap.
|
||||
*/
|
||||
/* static */ void
|
||||
Debugger::traceAll(JSTracer* trc)
|
||||
void
|
||||
Debugger::traceForMovingGC(JSTracer* trc)
|
||||
{
|
||||
JSRuntime* rt = trc->runtime();
|
||||
for (Debugger* dbg : rt->zoneGroupFromMainThread()->debuggerList()) {
|
||||
for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
|
||||
TraceManuallyBarrieredEdge(trc, e.mutableFront().unsafeGet(), "Global Object");
|
||||
for (WeakGlobalObjectSet::Enum e(debuggees); !e.empty(); e.popFront())
|
||||
TraceManuallyBarrieredEdge(trc, e.mutableFront().unsafeGet(), "Global Object");
|
||||
|
||||
GCPtrNativeObject& dbgobj = dbg->toJSObjectRef();
|
||||
TraceEdge(trc, &dbgobj, "Debugger Object");
|
||||
GCPtrNativeObject& dbgobj = toJSObjectRef();
|
||||
TraceEdge(trc, &dbgobj, "Debugger Object");
|
||||
|
||||
dbg->scripts.trace(trc);
|
||||
dbg->sources.trace(trc);
|
||||
dbg->objects.trace(trc);
|
||||
dbg->environments.trace(trc);
|
||||
dbg->wasmInstanceScripts.trace(trc);
|
||||
dbg->wasmInstanceSources.trace(trc);
|
||||
scripts.trace(trc);
|
||||
sources.trace(trc);
|
||||
objects.trace(trc);
|
||||
environments.trace(trc);
|
||||
wasmInstanceScripts.trace(trc);
|
||||
wasmInstanceSources.trace(trc);
|
||||
|
||||
for (Breakpoint* bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
|
||||
switch (bp->site->type()) {
|
||||
case BreakpointSite::Type::JS:
|
||||
TraceManuallyBarrieredEdge(trc, &bp->site->asJS()->script,
|
||||
"breakpoint script");
|
||||
break;
|
||||
case BreakpointSite::Type::Wasm:
|
||||
TraceManuallyBarrieredEdge(trc, &bp->asWasm()->wasmInstance, "breakpoint wasm instance");
|
||||
break;
|
||||
}
|
||||
TraceEdge(trc, &bp->getHandlerRef(), "breakpoint handler");
|
||||
for (Breakpoint* bp = firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
|
||||
switch (bp->site->type()) {
|
||||
case BreakpointSite::Type::JS:
|
||||
TraceManuallyBarrieredEdge(trc, &bp->site->asJS()->script,
|
||||
"breakpoint script");
|
||||
break;
|
||||
case BreakpointSite::Type::Wasm:
|
||||
TraceManuallyBarrieredEdge(trc, &bp->asWasm()->wasmInstance, "breakpoint wasm instance");
|
||||
break;
|
||||
}
|
||||
TraceEdge(trc, &bp->getHandlerRef(), "breakpoint handler");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3234,15 +3243,17 @@ Debugger::sweepAll(FreeOp* fop)
|
|||
{
|
||||
JSRuntime* rt = fop->runtime();
|
||||
|
||||
for (Debugger* dbg : rt->zoneGroupFromMainThread()->debuggerList()) {
|
||||
if (IsAboutToBeFinalized(&dbg->object)) {
|
||||
/*
|
||||
* dbg is being GC'd. Detach it from its debuggees. The debuggee
|
||||
* might be GC'd too. Since detaching requires access to both
|
||||
* objects, this must be done before finalize time.
|
||||
*/
|
||||
for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
|
||||
dbg->removeDebuggeeGlobal(fop, e.front().unbarrieredGet(), &e);
|
||||
for (ZoneGroupsIter group(rt); !group.done(); group.next()) {
|
||||
for (Debugger* dbg : group->debuggerList()) {
|
||||
if (IsAboutToBeFinalized(&dbg->object)) {
|
||||
/*
|
||||
* dbg is being GC'd. Detach it from its debuggees. The debuggee
|
||||
* might be GC'd too. Since detaching requires access to both
|
||||
* objects, this must be done before finalize time.
|
||||
*/
|
||||
for (WeakGlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront())
|
||||
dbg->removeDebuggeeGlobal(fop, e.front().unbarrieredGet(), &e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3263,9 +3274,12 @@ Debugger::findZoneEdges(Zone* zone, js::gc::ZoneComponentFinder& finder)
|
|||
* For debugger cross compartment wrappers, add edges in the opposite
|
||||
* direction to those already added by JSCompartment::findOutgoingEdges.
|
||||
* This ensure that debuggers and their debuggees are finalized in the same
|
||||
* group.
|
||||
* group. We only need to look at the zone's ZoneGroup, as debuggers and
|
||||
* debuggees are always in the same ZoneGroup.
|
||||
*/
|
||||
for (Debugger* dbg : zone->runtimeFromMainThread()->zoneGroupFromMainThread()->debuggerList()) {
|
||||
if (zone->isAtomsZone())
|
||||
return;
|
||||
for (Debugger* dbg : zone->group()->debuggerList()) {
|
||||
Zone* w = dbg->object->zone();
|
||||
if (w == zone || !w->isGCMarking())
|
||||
continue;
|
||||
|
@ -11773,14 +11787,16 @@ FireOnGarbageCollectionHook(JSContext* cx, JS::dbg::GarbageCollectionEvent::Ptr&
|
|||
// participated in this GC.
|
||||
AutoCheckCannotGC noGC;
|
||||
|
||||
for (Debugger* dbg : cx->runtime()->zoneGroupFromMainThread()->debuggerList()) {
|
||||
if (dbg->enabled &&
|
||||
dbg->observedGC(data->majorGCNumber()) &&
|
||||
dbg->getHook(Debugger::OnGarbageCollection))
|
||||
{
|
||||
if (!triggered.append(dbg->object)) {
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
for (ZoneGroupsIter group(cx->runtime()); !group.done(); group.next()) {
|
||||
for (Debugger* dbg : group->debuggerList()) {
|
||||
if (dbg->enabled &&
|
||||
dbg->observedGC(data->majorGCNumber()) &&
|
||||
dbg->getHook(Debugger::OnGarbageCollection))
|
||||
{
|
||||
if (!triggered.append(dbg->object)) {
|
||||
JS_ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -588,6 +588,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
|
|||
|
||||
static void traceObject(JSTracer* trc, JSObject* obj);
|
||||
void trace(JSTracer* trc);
|
||||
void traceForMovingGC(JSTracer* trc);
|
||||
static void finalize(FreeOp* fop, JSObject* obj);
|
||||
void traceCrossCompartmentEdges(JSTracer* tracer);
|
||||
|
||||
|
@ -805,6 +806,8 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
|
|||
static inline Debugger* fromJSObject(const JSObject* obj);
|
||||
static Debugger* fromChildJSObject(JSObject* obj);
|
||||
|
||||
Zone* zone() const { return toJSObject()->zone(); }
|
||||
|
||||
bool hasMemory() const;
|
||||
DebuggerMemory& memory() const;
|
||||
|
||||
|
@ -829,7 +832,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
|
|||
*/
|
||||
static void traceIncomingCrossCompartmentEdges(JSTracer* tracer);
|
||||
static MOZ_MUST_USE bool markIteratively(GCMarker* marker);
|
||||
static void traceAll(JSTracer* trc);
|
||||
static void traceAllForMovingGC(JSTracer* trc);
|
||||
static void sweepAll(FreeOp* fop);
|
||||
static void detachAllDebuggersFromGlobal(FreeOp* fop, GlobalObject* global);
|
||||
static void findZoneEdges(JS::Zone* v, gc::ZoneComponentFinder& finder);
|
||||
|
|
|
@ -2315,8 +2315,9 @@ DebugEnvironmentProxy::isOptimizedOut() const
|
|||
|
||||
/*****************************************************************************/
|
||||
|
||||
DebugEnvironments::DebugEnvironments(JSContext* cx)
|
||||
: proxiedEnvs(cx),
|
||||
DebugEnvironments::DebugEnvironments(JSContext* cx, Zone* zone)
|
||||
: zone_(zone),
|
||||
proxiedEnvs(cx),
|
||||
missingEnvs(cx->runtime()),
|
||||
liveEnvs(cx->runtime())
|
||||
{}
|
||||
|
@ -2430,7 +2431,7 @@ DebugEnvironments::ensureCompartmentData(JSContext* cx)
|
|||
if (c->debugEnvs)
|
||||
return c->debugEnvs;
|
||||
|
||||
auto debugEnvs = cx->make_unique<DebugEnvironments>(cx);
|
||||
auto debugEnvs = cx->make_unique<DebugEnvironments>(cx, cx->zone());
|
||||
if (!debugEnvs || !debugEnvs->init()) {
|
||||
ReportOutOfMemory(cx);
|
||||
return nullptr;
|
||||
|
|
|
@ -911,6 +911,8 @@ class DebugEnvironmentProxy : public ProxyObject
|
|||
/* Maintains per-compartment debug environment bookkeeping information. */
|
||||
class DebugEnvironments
|
||||
{
|
||||
Zone* zone_;
|
||||
|
||||
/* The map from (non-debug) environments to debug environments. */
|
||||
ObjectWeakMap proxiedEnvs;
|
||||
|
||||
|
@ -939,9 +941,11 @@ class DebugEnvironments
|
|||
LiveEnvironmentMap liveEnvs;
|
||||
|
||||
public:
|
||||
explicit DebugEnvironments(JSContext* cx);
|
||||
DebugEnvironments(JSContext* cx, Zone* zone);
|
||||
~DebugEnvironments();
|
||||
|
||||
Zone* zone() const { return zone_; }
|
||||
|
||||
private:
|
||||
bool init();
|
||||
|
||||
|
|
|
@ -470,7 +470,7 @@ js::CancelOffThreadParses(JSRuntime* rt)
|
|||
if (task->runtimeMatches(rt)) {
|
||||
found = true;
|
||||
AutoUnlockHelperThreadState unlock(lock);
|
||||
HelperThreadState().cancelParseTask(rt->contextFromMainThread(), task->kind, task);
|
||||
HelperThreadState().cancelParseTask(rt, task->kind, task);
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
|
@ -1352,10 +1352,10 @@ GlobalHelperThreadState::finishModuleParseTask(JSContext* cx, void* token)
|
|||
}
|
||||
|
||||
void
|
||||
GlobalHelperThreadState::cancelParseTask(JSContext* cx, ParseTaskKind kind, void* token)
|
||||
GlobalHelperThreadState::cancelParseTask(JSRuntime* rt, ParseTaskKind kind, void* token)
|
||||
{
|
||||
ScopedJSDeletePtr<ParseTask> parseTask(removeFinishedParseTask(kind, token));
|
||||
LeaveParseTaskZone(cx->runtime(), parseTask);
|
||||
LeaveParseTaskZone(rt, parseTask);
|
||||
}
|
||||
|
||||
JSObject*
|
||||
|
|
|
@ -241,7 +241,7 @@ class GlobalHelperThreadState
|
|||
}
|
||||
|
||||
JSScript* finishParseTask(JSContext* cx, ParseTaskKind kind, void* token);
|
||||
void cancelParseTask(JSContext* cx, ParseTaskKind kind, void* token);
|
||||
void cancelParseTask(JSRuntime* rt, ParseTaskKind kind, void* token);
|
||||
|
||||
void mergeParseTaskCompartment(JSContext* cx, ParseTask* parseTask,
|
||||
Handle<GlobalObject*> global,
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "gc/Statistics.h"
|
||||
#include "jit/ExecutableAllocator.h"
|
||||
#include "jit/Ion.h"
|
||||
#include "jit/JitCommon.h"
|
||||
#include "js/Utility.h"
|
||||
#if ENABLE_INTL_API
|
||||
#include "unicode/uclean.h"
|
||||
|
@ -123,6 +124,10 @@ JS::detail::InitWithFailureDiagnostic(bool isDebugBuild)
|
|||
RETURN_IF_FAIL(FutexThread::initialize());
|
||||
RETURN_IF_FAIL(js::gcstats::Statistics::initialize());
|
||||
|
||||
#ifdef JS_SIMULATOR
|
||||
RETURN_IF_FAIL(js::jit::SimulatorProcess::initialize());
|
||||
#endif
|
||||
|
||||
libraryInitState = InitState::Running;
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -148,6 +153,10 @@ JS_ShutDown(void)
|
|||
|
||||
js::DestroyHelperThreadsState();
|
||||
|
||||
#ifdef JS_SIMULATOR
|
||||
js::jit::SimulatorProcess::destroy();
|
||||
#endif
|
||||
|
||||
#ifdef JS_TRACE_LOGGING
|
||||
js::DestroyTraceLoggerThreadState();
|
||||
js::DestroyTraceLoggerGraphState();
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче